diff --git a/.github/workflows/update-packages.yml b/.github/workflows/update-packages.yml index e21d834..5363a8a 100644 --- a/.github/workflows/update-packages.yml +++ b/.github/workflows/update-packages.yml @@ -1,7 +1,7 @@ name: Update Packages -# Reusable workflow that updates NuGet and/or NPM packages in the calling -# repository, builds to verify nothing is broken, and pushes the result -# directly to the default branch. +# Reusable workflow that updates NuGet, NPM, Gradle (Kotlin/JVM), and/or Mix +# (Elixir) packages in the calling repository, builds to verify nothing is +# broken, and pushes the result directly to the default branch. # # This workflow requires PAT_WORKFLOWS secret with permissions: # Classic PAT: repo scope @@ -13,6 +13,9 @@ name: Update Packages env: DOTNET_VERSION: "10.0.x" + JAVA_VERSION: "21" + ELIXIR_VERSION: "1.18" + OTP_VERSION: "27" on: workflow_call: @@ -57,12 +60,67 @@ jobs: echo "update_npm=false" >> "$GITHUB_OUTPUT" fi + # Gradle is enabled only when root contains a Gradle wrapper plus a settings/build script. + if [ -f "gradlew" ] && find . -maxdepth 1 -type f \( -name 'settings.gradle.kts' -o -name 'settings.gradle' -o -name 'build.gradle.kts' -o -name 'build.gradle' \) | grep -q .; then + echo "update_gradle=true" >> "$GITHUB_OUTPUT" + else + echo "No root gradlew + settings/build.gradle(.kts) found; skipping Java setup and Gradle update" + echo "update_gradle=false" >> "$GITHUB_OUTPUT" + fi + + # Mix (Elixir) is enabled when a mix.exs is found a bounded few directories down (Elixir + # repos in this org keep the actual package nested, e.g. Source//mix.exs, rather + # than at the repo root), ignoring dependency/build output directories. + mix_path=$(find . -maxdepth 3 -type f -name 'mix.exs' -not -path '*/deps/*' -not -path '*/_build/*' | head -1) + if [ -n "$mix_path" ]; then + echo "update_mix=true" >> "$GITHUB_OUTPUT" + echo "mix_dir=$(dirname "$mix_path")" >> "$GITHUB_OUTPUT" + else + echo "No mix.exs found; skipping Elixir setup and Mix update" + echo "update_mix=false" >> "$GITHUB_OUTPUT" + fi + + # All toolchain setup and dependency-update steps run first, across every detected + # ecosystem, before any build step runs. A build in one ecosystem can depend on tooling + # from another (e.g. a .csproj with an MSBuild target that shells out to `yarn build`), so + # builds must not start until every ecosystem's dependencies are actually in place. - name: Setup .NET if: steps.detect.outputs.update_nuget == 'true' uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Setup Node.js + if: steps.detect.outputs.update_npm == 'true' + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install NPM dependencies + if: steps.detect.outputs.update_npm == 'true' + run: yarn install + + - name: Setup Java + if: steps.detect.outputs.update_gradle == 'true' + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} + + - name: Setup Elixir + if: steps.detect.outputs.update_mix == 'true' + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.ELIXIR_VERSION }} + otp-version: ${{ env.OTP_VERSION }} + + - name: Install Mix dependencies + if: steps.detect.outputs.update_mix == 'true' + working-directory: ${{ steps.detect.outputs.mix_dir }} + run: | + mix local.hex --force + mix deps.get + - name: Update NuGet packages if: steps.detect.outputs.update_nuget == 'true' run: | @@ -82,32 +140,61 @@ jobs: exit 0 fi - exit $exit_code - - - name: Build .NET - if: steps.detect.outputs.update_nuget == 'true' - run: dotnet build - - - name: Setup Node.js - if: steps.detect.outputs.update_npm == 'true' - uses: actions/setup-node@v4 - with: - node-version: '22' + if [ $exit_code -eq 3 ]; then + # Exit code 3 has been observed for two distinct cases where the command can't produce + # an internally consistent result and applies no changes at all: (1) a package with + # different centrally-managed versions per target framework - e.g. a deliberate + # multi-TFM override via conditional Directory.Packages.*.props imports - which the + # command explicitly refuses ("... is not supported by this command"); and (2) a plain + # NU1109 downgrade conflict, seemingly from the command updating one centrally-pinned + # package without correctly co-updating another package that's transitively pinned to + # it (e.g. CentralPackageTransitivePinningEnabled). Neither is actionable here, and + # since no changes are written to disk in either case, skipping is safe. + echo "dotnet package update could not produce a consistent result (exit 3); skipping NuGet update for this run." + exit 0 + fi - - name: Install dependencies - if: steps.detect.outputs.update_npm == 'true' - run: yarn install + exit $exit_code - name: Update NPM packages if: steps.detect.outputs.update_npm == 'true' run: | - npx npm-check-updates -u -w + # typescript is temporarily excluded: 7.x restructured the published package's internal + # layout and Yarn Berry's builtin compat/typescript patch hasn't been updated for it yet, + # so `yarn install` crashes applying the patch. Remove this exclusion once Yarn ships a fix. + npx npm-check-updates -u -w -x typescript yarn install + env: + # GitHub Actions sets CI=true, which makes Yarn Berry default to immutable installs and + # refuse to write the lockfile changes this step exists to produce. + YARN_ENABLE_IMMUTABLE_INSTALLS: false + + - name: Update Gradle packages + if: steps.detect.outputs.update_gradle == 'true' + run: ./gradlew useLatestVersions --no-daemon + + - name: Update Mix packages + if: steps.detect.outputs.update_mix == 'true' + working-directory: ${{ steps.detect.outputs.mix_dir }} + run: mix deps.update --all + + - name: Build .NET + if: steps.detect.outputs.update_nuget == 'true' + run: dotnet build - name: Build NPM if: steps.detect.outputs.update_npm == 'true' run: yarn ci + - name: Build Gradle + if: steps.detect.outputs.update_gradle == 'true' + run: ./gradlew build --no-daemon + + - name: Build Mix + if: steps.detect.outputs.update_mix == 'true' + working-directory: ${{ steps.detect.outputs.mix_dir }} + run: mix compile + - name: Commit and push changes env: GH_TOKEN: ${{ secrets.PAT_WORKFLOWS }} @@ -134,6 +221,20 @@ jobs: updated_types="NPM" fi fi + if [ "${{ steps.detect.outputs.update_gradle }}" == "true" ]; then + if [ -n "$updated_types" ]; then + updated_types="$updated_types and Gradle" + else + updated_types="Gradle" + fi + fi + if [ "${{ steps.detect.outputs.update_mix }}" == "true" ]; then + if [ -n "$updated_types" ]; then + updated_types="$updated_types and Mix" + else + updated_types="Mix" + fi + fi git add -A git commit -m "chore: update ${updated_types} packages"