Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 119 additions & 18 deletions .github/workflows/update-packages.yml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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/<name>/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: |
Expand All @@ -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 }}
Expand All @@ -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"
Expand Down