From 0c478e26b9753bdd71a3d7b7c8e4f9ebeb5a1f98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 04:48:26 +0000 Subject: [PATCH 1/5] Bump the nuget-deps group with 1 update (#148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 18.5.1 to 18.6.0.
Release notes _Sourced from [Microsoft.NET.Test.Sdk's releases](https://github.com/microsoft/vstest/releases)._ ## 18.6.0 ## What's Changed * Revert removal of Video Recorder by @​nohwnd in https://github.com/microsoft/vstest/pull/15336 * Speed up blame by filtering non-.NET processes from dump collection by @​nohwnd in https://github.com/microsoft/vstest/pull/15518 * Add README.md to NuGet packages by @​nohwnd in https://github.com/microsoft/vstest/pull/15550 * Report child process info on connection timeout by @​nohwnd in https://github.com/microsoft/vstest/pull/15603 ### Changes to tests and infra * Brand as 18.6 by @​nohwnd in https://github.com/microsoft/vstest/pull/15423 * Upgrading code coverage version to 18.5.1, by @​fhnaseer in https://github.com/microsoft/vstest/pull/15422 * Updating System.Collections.Immutable to 9.0.11 by @​MSLukeWest in https://github.com/microsoft/vstest/pull/15425 * Fix attachVS when used for debugging integration tests by @​nohwnd in https://github.com/microsoft/vstest/pull/15451 * Replace dotnet.config, with global.json by @​nohwnd in https://github.com/microsoft/vstest/pull/15449 * Document debugging integration tests with AttachVS by @​Copilot in https://github.com/microsoft/vstest/pull/15452 * Fix stack overflow tests by @​nohwnd in https://github.com/microsoft/vstest/pull/15461 * Make TestAssets.sln buildable locally by @​Youssef1313 in https://github.com/microsoft/vstest/pull/15466 * Try filtering out tests by @​nohwnd in https://github.com/microsoft/vstest/pull/15463 * Build just once when tfms run in parallel by @​nohwnd in https://github.com/microsoft/vstest/pull/15465 * Review simplify compatibility sources, deduplicate tests by @​nohwnd in https://github.com/microsoft/vstest/pull/15472 * Cleanup dead TRX code by @​Youssef1313 in https://github.com/microsoft/vstest/pull/15474 * Update .NET runtimes to 8.0.25, 9.0.14, and 10.0.4 by @​nohwnd in https://github.com/microsoft/vstest/pull/15481 * Compat matrix checker by @​nohwnd in https://github.com/microsoft/vstest/pull/15480 * Add trx analysis skill by @​nohwnd in https://github.com/microsoft/vstest/pull/15486 * Split integration tests to single tfm and multi tfm project by @​nohwnd in https://github.com/microsoft/vstest/pull/15484 * Update matrix by @​nohwnd in https://github.com/microsoft/vstest/pull/15477 * Break infinite restore loop in VS by @​nohwnd in https://github.com/microsoft/vstest/pull/15503 * Use global package cache for build, and local for running integration tests by @​nohwnd in https://github.com/microsoft/vstest/pull/15500 * Update contributing by @​nohwnd in https://github.com/microsoft/vstest/pull/15505 * Reduce test wall-clock time by increasing minThreads by @​drognanar in https://github.com/microsoft/vstest/pull/15502 * Indicator flakiness by @​nohwnd in https://github.com/microsoft/vstest/pull/15513 * Fix ci build by @​nohwnd in https://github.com/microsoft/vstest/pull/15515 * Fix thread safety issues by @​Evangelink in https://github.com/microsoft/vstest/pull/15512 * Optimize DotnetSDKSimulation_PostProcessing test (163s → 61s) by @​nohwnd in https://github.com/microsoft/vstest/pull/15516 * Build isolated test assets for single TFM instead of 7 by @​nohwnd in https://github.com/microsoft/vstest/pull/15517 * Remove unused dependencies from Library.IntegrationTests by @​nohwnd in https://github.com/microsoft/vstest/pull/15527 * Remove printing _attachments content to console by @​nohwnd in https://github.com/microsoft/vstest/pull/15520 * Add Linux/macOS test filtering guide to CONTRIBUTING.md by @​nohwnd in https://github.com/microsoft/vstest/pull/15521 * Change integration test parallelization from ClassLevel to MethodLevel by @​nohwnd in https://github.com/microsoft/vstest/pull/15526 * Unify target framework checks with IsNetFrameworkTarget/IsNetTarget by @​nohwnd in https://github.com/microsoft/vstest/pull/15523 * Add unattended work instructions to copilot-instructions.md by @​nohwnd in https://github.com/microsoft/vstest/pull/15531 * Reduce code style rule severity from warning to suggestion by @​nohwnd in https://github.com/microsoft/vstest/pull/15522 * Remove Debug/Release line number branching from tests by @​nohwnd in https://github.com/microsoft/vstest/pull/15519 * Revise unattended work instructions in copilot-instructions.md by @​nohwnd in https://github.com/microsoft/vstest/pull/15532 * Improve CompatibilityRowsBuilder error message with diagnostic details by @​nohwnd in https://github.com/microsoft/vstest/pull/15529 * docs: add git worktree and upstream sync workflow to copilot-instructions.md by @​nohwnd in https://github.com/microsoft/vstest/pull/15538 * Add VSIX runner to smoke tests by @​nohwnd in https://github.com/microsoft/vstest/pull/15541 * Remove deprecated WebTest and TMI test methods by @​nohwnd in https://github.com/microsoft/vstest/pull/15525 * Fix compatibility test failures for legacy vstest.console and MSTest adapter by @​nohwnd in https://github.com/microsoft/vstest/pull/15534 * Convert TestPlatform.sln to slnx format by @​nohwnd in https://github.com/microsoft/vstest/pull/15551 * Convert test/TestAssets .sln files to .slnx format by @​nohwnd in https://github.com/microsoft/vstest/pull/15557 ... (truncated) Commits viewable in [compare view](https://github.com/microsoft/vstest/compare/v18.5.1...v18.6.0).
[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Microsoft.NET.Test.Sdk&package-manager=nuget&previous-version=18.5.1&new-version=18.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 893b9f3..373bdc1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,7 +3,7 @@ - + From 701a2f32d00ff3148b17cb9bf1945d57f64cefd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 02:48:29 +0000 Subject: [PATCH 2/5] Bump actions/setup-dotnet from 5.2.0 to 5.3.0 in the actions-deps group (#150) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the actions-deps group with 1 update: [actions/setup-dotnet](https://github.com/actions/setup-dotnet). Updates `actions/setup-dotnet` from 5.2.0 to 5.3.0
Release notes

Sourced from actions/setup-dotnet's releases.

v5.3.0

What's Changed

Enhancements

Dependency Updates

Bug Fixes

New Contributors

Full Changelog: https://github.com/actions/setup-dotnet/compare/v5...v5.3.0

Commits
  • 9a946fd Add rollForward note in README, improve proxy health check in e2e tests and b...
  • 98af08b Support global.json's rollForward latest* variants (#538)
  • 8404272 Update install scripts to v2026.05.19 (#736)
  • f1970f5 Don't download releases-index.json to resolve major version (#560)
  • af9211b Add dotnet-version: latest support with dotnet-channel input (#730)
  • df991ae chore: bump @actions/* and fast-xml-parser dependencies (#728)
  • a66eefa CI: remove manual PowerShell install from test-proxy job (e2e-tests.yml) (#703)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-dotnet&package-manager=github_actions&previous-version=5.2.0&new-version=5.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-library-task.yml | 2 +- .github/workflows/get-version-task.yml | 2 +- .github/workflows/run-codegen-pull-request-task.yml | 2 +- .github/workflows/test-release-task.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-library-task.yml b/.github/workflows/build-library-task.yml index d4a5860..fc8c46c 100644 --- a/.github/workflows/build-library-task.yml +++ b/.github/workflows/build-library-task.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Setup .NET SDK step - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0 with: dotnet-version: 10.x diff --git a/.github/workflows/get-version-task.yml b/.github/workflows/get-version-task.yml index 3dfa243..4f84af3 100644 --- a/.github/workflows/get-version-task.yml +++ b/.github/workflows/get-version-task.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Setup .NET SDK step - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0 with: dotnet-version: 10.x diff --git a/.github/workflows/run-codegen-pull-request-task.yml b/.github/workflows/run-codegen-pull-request-task.yml index 122c536..93021ce 100644 --- a/.github/workflows/run-codegen-pull-request-task.yml +++ b/.github/workflows/run-codegen-pull-request-task.yml @@ -50,7 +50,7 @@ jobs: private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} - name: Setup .NET SDK step - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0 with: dotnet-version: 10.x diff --git a/.github/workflows/test-release-task.yml b/.github/workflows/test-release-task.yml index ba5d1cc..c415440 100644 --- a/.github/workflows/test-release-task.yml +++ b/.github/workflows/test-release-task.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Setup .NET SDK step - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0 with: dotnet-version: 10.x From 815d51f395053ae05df37b57c565863b4c26eda9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 04:50:51 +0000 Subject: [PATCH 3/5] Bump the nuget-deps group with 1 update (#152) Updated [dotnet-outdated-tool](https://github.com/dotnet-outdated/dotnet-outdated) from 4.7.1 to 4.8.0.
Release notes _Sourced from [dotnet-outdated-tool's releases](https://github.com/dotnet-outdated/dotnet-outdated/releases)._ ## 4.8.0 ## 4.7.2 Commits viewable in [compare view](https://github.com/dotnet-outdated/dotnet-outdated/compare/v4.7.1...v4.8.0).
[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dotnet-outdated-tool&package-manager=nuget&previous-version=4.7.1&new-version=4.8.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 2b10359..c657430 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -17,7 +17,7 @@ "rollForward": false }, "dotnet-outdated-tool": { - "version": "4.7.1", + "version": "4.8.0", "commands": [ "dotnet-outdated" ], From e6a6147320be9a2884e6a64414eb7cb8e22612cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:58:22 +0000 Subject: [PATCH 4/5] Bump actions/checkout from 6.0.2 to 6.0.3 in the actions-deps group (#153) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the actions-deps group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 6.0.2 to 6.0.3
Release notes

Sourced from actions/checkout's releases.

v6.0.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v6...v6.0.3

Changelog

Sourced from actions/checkout's changelog.

Changelog

v6.0.3

v6.0.2

v6.0.1

v6.0.0

v5.0.1

v5.0.0

v4.3.1

v4.3.0

v4.2.2

v4.2.1

v4.2.0

v4.1.7

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=6.0.2&new-version=6.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-library-task.yml | 2 +- .github/workflows/build-release-task.yml | 2 +- .github/workflows/get-version-task.yml | 2 +- .github/workflows/run-codegen-pull-request-task.yml | 2 +- .github/workflows/test-release-task.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-library-task.yml b/.github/workflows/build-library-task.yml index fc8c46c..f2ad476 100644 --- a/.github/workflows/build-library-task.yml +++ b/.github/workflows/build-library-task.yml @@ -39,7 +39,7 @@ jobs: dotnet-version: 10.x - name: Checkout code step - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Build library project step run: | diff --git a/.github/workflows/build-release-task.yml b/.github/workflows/build-release-task.yml index 1062fd9..de6461f 100644 --- a/.github/workflows/build-release-task.yml +++ b/.github/workflows/build-release-task.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout code step - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Download library build artifacts step uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/workflows/get-version-task.yml b/.github/workflows/get-version-task.yml index 4f84af3..9e8e2bc 100644 --- a/.github/workflows/get-version-task.yml +++ b/.github/workflows/get-version-task.yml @@ -32,7 +32,7 @@ jobs: dotnet-version: 10.x - name: Checkout code step - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 diff --git a/.github/workflows/run-codegen-pull-request-task.yml b/.github/workflows/run-codegen-pull-request-task.yml index 93021ce..d70a93c 100644 --- a/.github/workflows/run-codegen-pull-request-task.yml +++ b/.github/workflows/run-codegen-pull-request-task.yml @@ -55,7 +55,7 @@ jobs: dotnet-version: 10.x - name: Checkout code step - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: ref: ${{ matrix.target.ref }} token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/test-release-task.yml b/.github/workflows/test-release-task.yml index c415440..707c4d6 100644 --- a/.github/workflows/test-release-task.yml +++ b/.github/workflows/test-release-task.yml @@ -18,7 +18,7 @@ jobs: dotnet-version: 10.x - name: Checkout code step - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Check code style step run: | From 325efba627beb9071749e371b7d33ba9c3a4bb0c Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Thu, 4 Jun 2026 12:18:39 -0700 Subject: [PATCH 5/5] Adopt two-phase CI/CD with weekly publish and PR smoke tests (#155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Migrates LanguageTags to the **two-phase commit deploy** model used by the shared ProjectTemplate (and already ported to NxWitness / PlexCleaner), reduced to this project's single NuGet-library target. - **PRs smoke-test only.** `test-pull-request.yml` runs unit tests, then a `dorny/paths-filter` `changes` gate runs a reduced, never-published library build (`smoke: true`). Workflow/docs-only PRs skip the build. - **Merges don't publish by default.** `publish-release.yml` is the sole publisher: a **weekly schedule** (Mon 02:00 UTC) + manual `workflow_dispatch` rebuild/publish **both** `main` and `develop` via a branch matrix. The `push` trigger only publishes when the `PUBLISH_ON_MERGE` repo variable is `true` (opt-in continuous-release). - **Codegen weekly → daily** (`run-periodic-codegen-pull-request.yml`, 04:00 UTC, staggered after the publish). Dependabot already daily — unchanged. ## Workflow contract / naming - Reusable tasks thread `ref` / `branch` / `smoke`; config keys off `inputs.branch` (Release on main, Debug elsewhere); builds + release tag pin to NBGV `GitCommitId`. - Renamed `build-library-task.yml` → `build-nugetlibrary-task.yml` and removed `test-release-task.yml` to match the upstream template's file schema. - New `publish-release.yml` concurrency exception (global ref-independent group + `cancel-in-progress: false`) documented in AGENTS.md. ## Docs - AGENTS.md: Release Model section + concurrency exception. - README: two-phase description, `event=schedule` on the Releases Build badge, develop-ruleset strict-check correction (matches AGENTS.md). - copilot-instructions.md: workflow list refresh. ## Verification - `dotnet build LanguageTags.csproj` passes in Debug and Release (0 warnings). - All workflow YAML parses; no stale `build-library-task` / `test-release-task` references. - `actionlint` not run locally (binary unavailable) — relies on CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/copilot-instructions.md | 11 +- .github/workflows/build-datebadge-task.yml | 10 +- ...y-task.yml => build-nugetlibrary-task.yml} | 33 ++++- .github/workflows/build-release-task.yml | 103 +++++++++++-- .github/workflows/get-version-task.yml | 17 +++ .github/workflows/publish-release.yml | 140 ++++++++++++++---- .../run-periodic-codegen-pull-request.yml | 11 +- .github/workflows/test-pull-request.yml | 108 +++++++++++++- .github/workflows/test-release-task.yml | 41 ----- AGENTS.md | 15 +- README.md | 8 +- 11 files changed, 382 insertions(+), 115 deletions(-) rename .github/workflows/{build-library-task.yml => build-nugetlibrary-task.yml} (67%) delete mode 100644 .github/workflows/test-release-task.yml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 63058f7..e2503a8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -141,13 +141,14 @@ After the final push, sweep-resolve stale older threads for removed code paths. - **LanguageData/** - Contains downloaded language data files - JSON converted data files - - Updated weekly via GitHub Actions + - Refreshed by daily codegen PRs; published with the weekly release - **.github/workflows/** - - `run-periodic-codegen-pull-request.yml`: Weekly scheduled job to update language data - - `publish-release.yml`: Release and NuGet publishing workflow + - `run-periodic-codegen-pull-request.yml`: Daily scheduled job that opens codegen PRs to update language data + - `publish-release.yml`: Sole publisher — weekly scheduled (Mon 02:00 UTC) + manual dispatch full build/publish of both branches; pushes only publish when `PUBLISH_ON_MERGE` is set (two-phase model) + - `test-pull-request.yml`: PR smoke test — unit tests + a reduced, never-published library build gated by `dorny/paths-filter` - `merge-bot-pull-request.yml`: Automated PR merge workflow - - `build-release-task.yml`, `build-library-task.yml`: Build tasks + - `build-release-task.yml`, `build-nugetlibrary-task.yml`: Build tasks - `get-version-task.yml`, `build-datebadge-task.yml`: Version and badge generation ### Project Configuration @@ -335,7 +336,7 @@ Examples: ### Data Updates -- Language data is updated weekly via GitHub Actions workflow +- Language data is refreshed by daily codegen PRs and shipped with the weekly release (GitHub Actions) - The `LanguageTagsCreate` tool downloads data from: - ISO 639-2: Library of Congress - ISO 639-3: SIL International diff --git a/.github/workflows/build-datebadge-task.yml b/.github/workflows/build-datebadge-task.yml index c33fd6e..2f97f2f 100644 --- a/.github/workflows/build-datebadge-task.yml +++ b/.github/workflows/build-datebadge-task.yml @@ -2,6 +2,14 @@ name: Build BYOB date badge task on: workflow_call: + inputs: + # Logical branch this badge run is for. The badge only updates on + # `main`; the publisher passes the branch explicitly so a scheduled + # run building `develop` doesn't try to write the main badge. Required + # (no `github.ref_name` fallback) so the gate can't silently misfire. + branch: + required: true + type: string jobs: @@ -18,7 +26,7 @@ jobs: echo "date=$(date)" >> $GITHUB_OUTPUT - name: Build BYOB date badge step - if: ${{ github.ref_name == 'main' }} + if: ${{ inputs.branch == 'main' }} uses: RubbaBoy/BYOB@a4919104bc0ec7cfd7f113e42c405cc45246f2a4 # v1 with: name: lastbuild diff --git a/.github/workflows/build-library-task.yml b/.github/workflows/build-nugetlibrary-task.yml similarity index 67% rename from .github/workflows/build-library-task.yml rename to .github/workflows/build-nugetlibrary-task.yml index f2ad476..f3e4113 100644 --- a/.github/workflows/build-library-task.yml +++ b/.github/workflows/build-nugetlibrary-task.yml @@ -1,4 +1,4 @@ -name: Build library task +name: Build NuGet library task env: PROJECT_FILE: ./LanguageTags/LanguageTags.csproj @@ -7,15 +7,26 @@ env: on: workflow_call: inputs: - # Input to control whether to push the library to NuGet.org + # Input to control whether to push the NuGet library to NuGet.org push: required: false type: boolean default: false + # Git ref to check out / version (empty = default checkout ref). + ref: + required: false + type: string + default: '' + # Logical branch driving build configuration (`main` => Release, else + # Debug). Required (no `github.ref_name` fallback, which would mislabel + # the develop leg of the publisher's matrix); the orchestrator passes it. + branch: + required: true + type: string outputs: # Output of the uploaded artifact id artifact-id: - value: ${{ jobs.build-library.outputs.artifact-id }} + value: ${{ jobs.build-nugetlibrary.outputs.artifact-id }} jobs: @@ -23,9 +34,11 @@ jobs: name: Get version information job uses: ./.github/workflows/get-version-task.yml secrets: inherit + with: + ref: ${{ inputs.ref }} - build-library: - name: Build library project job + build-nugetlibrary: + name: Build NuGet library project job runs-on: ubuntu-latest outputs: artifact-id: ${{ steps.artifact-upload-step.outputs.artifact-id }} @@ -40,14 +53,16 @@ jobs: - name: Checkout code step uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ inputs.ref }} - - name: Build library project step + - name: Build NuGet library project step run: | set -euo pipefail dotnet build ${{ env.PROJECT_FILE }} \ -property:OutputPath=${{ runner.temp }}/publish/ \ -property:PackageOutputPath=${{ runner.temp }}/publish/ \ - --configuration ${{ inputs.push && 'Release' || 'Debug' }} \ + --configuration ${{ inputs.branch == 'main' && 'Release' || 'Debug' }} \ -property:Version=${{ needs.get-version.outputs.AssemblyVersion }} \ -property:FileVersion=${{ needs.get-version.outputs.AssemblyFileVersion }} \ -property:AssemblyVersion=${{ needs.get-version.outputs.AssemblyVersion }} \ @@ -68,9 +83,11 @@ jobs: set -euo pipefail 7z a -t7z ${{ runner.temp }}/${{ env.PROJECT_ARTIFACT }} ${{ runner.temp }}/publish/* + # Branch-suffixed so the publisher's branch matrix can build both + # branches in one run without colliding on the artifact name. - name: Upload build artifacts step id: artifact-upload-step uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: library-build + name: nugetlibrary-build-${{ inputs.branch }} path: ${{ runner.temp }}/${{ env.PROJECT_ARTIFACT }} diff --git a/.github/workflows/build-release-task.yml b/.github/workflows/build-release-task.yml index de6461f..9a6880a 100644 --- a/.github/workflows/build-release-task.yml +++ b/.github/workflows/build-release-task.yml @@ -13,6 +13,33 @@ on: required: false type: boolean default: false + # Git ref to check out / version (empty = default checkout ref). + ref: + required: false + type: string + default: '' + # Logical branch driving config / tags / prerelease for every target. + # Required (no `github.ref_name` fallback): the publisher builds both + # `main` and `develop` from one run whose `github.ref_name` is `main`, + # so a silent fallback would mislabel the develop leg. Every caller + # passes it explicitly; a missing value should fail loudly. + branch: + required: true + type: string + # Smoke mode: reduced, never-published build for fast PR feedback. + # Forwarded to every target; also hard-disables every push below so a + # smoke run can never publish regardless of the publish flags. + smoke: + required: false + type: boolean + default: false + # Per-target presence gate. Default true (build everything). A PR smoke + # run sets this from the paths-filter so the library only builds when it + # actually changed. + enable_nuget: + required: false + type: boolean + default: true jobs: @@ -20,45 +47,93 @@ jobs: name: Get version information job uses: ./.github/workflows/get-version-task.yml secrets: inherit + with: + ref: ${{ inputs.ref }} - build-library: - name: Build library job - uses: ./.github/workflows/build-library-task.yml + build-nugetlibrary: + name: Build NuGet library job + if: ${{ inputs.enable_nuget }} + needs: [get-version] + uses: ./.github/workflows/build-nugetlibrary-task.yml secrets: inherit with: - # Conditional push to NuGet.org - push: ${{ inputs.nuget }} + # Pin to the exact commit get-version resolved (immutable), not the + # possibly-moving branch ref: the publisher passes a branch name, and a + # commit landing mid-run could otherwise build artifacts from a different + # commit than the one the release tag (also GitCommitId) points at. + ref: ${{ needs.get-version.outputs.GitCommitId }} + branch: ${{ inputs.branch }} + # Conditional push to NuGet.org — never on a smoke build. + push: ${{ inputs.nuget && !inputs.smoke }} github-release: name: Publish GitHub release job - if: ${{ inputs.github }} + # `&& !inputs.smoke` enforces the "smoke never publishes" guarantee at the + # job level too (matching the `&& !inputs.smoke` push gate above), so a + # smoke caller that also set `github: true` still can't create a release. + if: ${{ inputs.github && !inputs.smoke }} runs-on: ubuntu-latest - needs: [get-version, build-library] + needs: [get-version, build-nugetlibrary] steps: + # Check out the exact built commit (NBGV `GitCommitId`), not the + # possibly-moving `inputs.ref` branch, so the uploaded release files + # match the tag even if the branch advances mid-run. - name: Checkout code step uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ needs.get-version.outputs.GitCommitId }} - name: Download library build artifacts step uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - artifact-ids: ${{ needs.build-library.outputs.artifact-id }} + artifact-ids: ${{ needs.build-nugetlibrary.outputs.artifact-id }} path: ./Publish + # The weekly publisher re-runs even when a branch has no new commits, so + # NBGV can produce a SemVer2 that was already released. GitHub release + # creation has no built-in skip-duplicate (unlike NuGet's + # `--skip-duplicate`), and re-publishing an unchanged version is exactly + # the churn the two-phase model avoids — so skip the release step when a + # release for this tag already exists. + - name: Check for existing release step + id: release-exists + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ needs.get-version.outputs.SemVer2 }} + run: | + set -euo pipefail + if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "Release $TAG already exists; workflow_dispatch will refresh it." + else + echo "Release $TAG already exists; skipping release creation (no-op republish)." + fi + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + # `target_commitish` MUST be set explicitly: softprops doesn't pass a # default through, and GitHub's REST API then defaults the new tag to - # the repository's default branch (main). On `push: develop` runs the - # tag would land on main's tip instead of the develop commit that - # built the artifact, leaving "Browse files" and `git checkout ` - # pointing at unrelated code. + # the repository's default branch (main). We pin it to NBGV's + # `GitCommitId` — the exact commit the version was computed from. This + # avoids two bugs: `github.sha` would be wrong (the publisher's branch + # matrix builds `develop` from a run whose `github.sha` is main's tip), + # and `inputs.branch` would be a moving ref (a commit landing mid-run + # could tag the release on a newer commit than the one that was built). + # Skip the no-op weekly republish when the tag already exists, but always + # allow a manual `workflow_dispatch` through so it can repair/refresh a + # partially-created release for the same tag. - name: Create GitHub release step + if: ${{ steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch' }} uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: generate_release_notes: true tag_name: ${{ needs.get-version.outputs.SemVer2 }} - target_commitish: ${{ github.sha }} - prerelease: ${{ github.ref_name != 'main' }} + target_commitish: ${{ needs.get-version.outputs.GitCommitId }} + prerelease: ${{ inputs.branch != 'main' }} files: | LICENSE README.md diff --git a/.github/workflows/get-version-task.yml b/.github/workflows/get-version-task.yml index 9e8e2bc..41fdac3 100644 --- a/.github/workflows/get-version-task.yml +++ b/.github/workflows/get-version-task.yml @@ -2,6 +2,16 @@ name: Get version information task on: workflow_call: + inputs: + # Git ref to check out and version. Empty string falls back to the + # caller's default checkout ref (`github.ref`), preserving the original + # behavior. The publisher passes an explicit branch so a scheduled run — + # which always reports `github.ref` as the default branch — can still + # compute NBGV versions for `develop` too. + ref: + required: false + type: string + default: '' outputs: # Version information outputs SemVer2: @@ -12,6 +22,11 @@ on: value: ${{ jobs.get-version.outputs.AssemblyFileVersion }} AssemblyInformationalVersion: value: ${{ jobs.get-version.outputs.AssemblyInformationalVersion }} + # Full SHA of the commit NBGV computed the version from. Used to pin the + # GitHub release tag and the built artifacts to the exact built commit + # (immutable), rather than a moving branch ref. + GitCommitId: + value: ${{ jobs.get-version.outputs.GitCommitId }} jobs: @@ -23,6 +38,7 @@ jobs: AssemblyVersion: ${{ steps.nbgv.outputs.AssemblyVersion }} AssemblyFileVersion: ${{ steps.nbgv.outputs.AssemblyFileVersion }} AssemblyInformationalVersion: ${{ steps.nbgv.outputs.AssemblyInformationalVersion }} + GitCommitId: ${{ steps.nbgv.outputs.GitCommitId }} steps: @@ -34,6 +50,7 @@ jobs: - name: Checkout code step uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: + ref: ${{ inputs.ref }} fetch-depth: 0 # nbgv is intentionally NOT SHA-pinned: the upstream tag stream lags diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 500fd9a..d0e92b8 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -1,31 +1,109 @@ -name: Publish project release action - -on: - push: - branches: [ main, develop ] - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - create-release: - name: Publish project release job - uses: ./.github/workflows/build-release-task.yml - secrets: inherit - permissions: - contents: write - with: - # Push to GitHub and NuGet - github: true - nuget: true - - date-badge: - name: Create BYOB date badge job - needs: [create-release] - uses: ./.github/workflows/build-datebadge-task.yml - secrets: inherit - permissions: - contents: write +name: Publish project release action + +on: + push: + branches: [ main, develop ] + workflow_dispatch: + schedule: + # Weekly full build/publish of both branches on Mondays at 02:00 UTC. + # This is the guaranteed publisher in the default two-phase model: routine + # merges only smoke-test, and this scheduled run republishes everything. + - cron: '0 2 * * MON' + +# Real publishes (schedule, dispatch, or push when PUBLISH_ON_MERGE is set) +# share a single GLOBAL, ref-independent group so they serialize: a scheduled +# run and a manual dispatch both build BOTH branches regardless of the +# triggering ref, so a ref-scoped group would let a scheduled run (ref=main) +# and a manual dispatch (ref=develop) run concurrently and double-publish. +# Non-publishing `push` runs (the two-phase default) get a unique per-run group +# so they don't queue behind — or delay — a real publish; they only execute the +# no-op `setup` job and skip everything else. +concurrency: + group: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || vars.PUBLISH_ON_MERGE == 'true') && github.workflow || format('{0}-noop-{1}', github.workflow, github.run_id) }} + # Documented exception to the standard `cancel-in-progress: true` (see + # AGENTS.md "Workflow YAML Conventions"): cancelling a publish mid-flight can + # leave a half-created GitHub release or a partially pushed NuGet package. + # Queue instead of cancel so each publish runs to completion. + cancel-in-progress: false + +jobs: + + # Decide WHICH branches to publish and WHETHER to publish at all: + # - push -> publish only the pushed branch, and only when the + # `PUBLISH_ON_MERGE` repository variable is `true` + # (opt-in legacy continuous-release). Unset/false => the + # default two-phase model: merges don't publish. + # - schedule -> always publish BOTH branches (the weekly full build). + # - dispatch -> always publish BOTH branches (manual on-demand publish). + setup: + name: Resolve publish plan job + runs-on: ubuntu-latest + outputs: + branches: ${{ steps.plan.outputs.branches }} + publish: ${{ steps.plan.outputs.publish }} + steps: + - name: Compute publish plan step + id: plan + env: + # Repository variable (Settings -> Actions -> Variables). Unset reads + # as empty string, so the default is the two-phase model. + PUBLISH_ON_MERGE: ${{ vars.PUBLISH_ON_MERGE }} + run: | + set -euo pipefail + case "${{ github.event_name }}" in + push) + branches='["${{ github.ref_name }}"]' + if [[ "${PUBLISH_ON_MERGE:-}" == "true" ]]; then + publish=true + else + publish=false + fi + ;; + *) + # schedule / workflow_dispatch + branches='["main","develop"]' + publish=true + ;; + esac + echo "Event=${{ github.event_name }} branches=$branches publish=$publish" + echo "branches=$branches" >> "$GITHUB_OUTPUT" + echo "publish=$publish" >> "$GITHUB_OUTPUT" + + # Full build + publish for each planned branch. The branch matrix lets a + # single scheduled run publish both `main` (Release, non-prerelease) and + # `develop` (Debug, prerelease) — each leg checks out and versions its own + # branch via the threaded `ref`/`branch`. + publish: + name: Publish project release job + needs: [setup] + if: ${{ needs.setup.outputs.publish == 'true' }} + strategy: + fail-fast: false + matrix: + branch: ${{ fromJSON(needs.setup.outputs.branches) }} + uses: ./.github/workflows/build-release-task.yml + secrets: inherit + permissions: + contents: write + with: + ref: ${{ matrix.branch }} + branch: ${{ matrix.branch }} + smoke: false + # Push to GitHub and NuGet. + github: true + nuget: true + + date-badge: + name: Create BYOB date badge job + needs: [setup, publish] + if: ${{ needs.setup.outputs.publish == 'true' }} + strategy: + matrix: + branch: ${{ fromJSON(needs.setup.outputs.branches) }} + uses: ./.github/workflows/build-datebadge-task.yml + secrets: inherit + permissions: + contents: write + with: + # The badge task self-gates to `main`; the develop leg is a no-op. + branch: ${{ matrix.branch }} diff --git a/.github/workflows/run-periodic-codegen-pull-request.yml b/.github/workflows/run-periodic-codegen-pull-request.yml index b506981..141f02d 100644 --- a/.github/workflows/run-periodic-codegen-pull-request.yml +++ b/.github/workflows/run-periodic-codegen-pull-request.yml @@ -1,10 +1,15 @@ -name: Run weekly codegen and pull request action +name: Run daily codegen and pull request action on: workflow_dispatch: schedule: - # Run weekly on Mondays at 02:00 UTC - - cron: '0 2 * * MON' + # Run daily at 04:00 UTC. Staggered two hours after the weekly publish + # (`publish-release.yml`, Mondays 02:00) so the two don't start together + # on Mondays. Codegen merges are cheap in the default two-phase model + # (they only smoke-test, the weekly publish batches the actual release), + # so running daily keeps both branches' generated content fresh without + # triggering a build per merge. + - cron: '0 4 * * *' concurrency: # Constant (workflow-name only, no ref) rather than the standard diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index f731203..59db962 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -15,18 +15,105 @@ concurrency: jobs: - test-release: - name: Test release job - uses: ./.github/workflows/test-release-task.yml + # Detect whether a PR actually touches the library so we only smoke-build + # when it changed. Build-workflow files are intentionally NOT in the filter: + # a path filter can't tell a logic change in a build workflow from an action- + # version bump. A workflow-only change is therefore not smoke-built — the + # reusable workflows are exercised instead by the next run that uses them (a + # later code PR's smoke build, or the scheduled/publish run); lint workflow + # edits with `actionlint` locally before pushing (there is no CI lint job). + # On `workflow_dispatch` (no PR base to diff against) the target is forced on + # so a manual run is a full smoke build. + changes: + name: Detect changed targets job + runs-on: ubuntu-latest + # `dorny/paths-filter` lists the PR's changed files via the GitHub API + # (this job does not check out the tree), which needs `pull-requests: read`. + # The repo's default GITHUB_TOKEN is restricted, so grant it explicitly. + permissions: + contents: read + pull-requests: read + outputs: + nuget: ${{ github.event_name == 'pull_request' && steps.filter.outputs.nuget || 'true' }} + steps: + - name: Filter changed paths step + id: filter + if: ${{ github.event_name == 'pull_request' }} + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + with: + filters: | + shared: &shared + - 'Directory.Build.props' + - 'Directory.Packages.props' + - 'version.json' + - '*.slnx' + nuget: + - *shared + - 'LanguageTags/**' + + # Unit tests are cheap and validate the library, so they always run + # regardless of whether the smoke build is gated off. + unit-test: + name: Run unit tests job + runs-on: ubuntu-latest + + steps: + + - name: Setup .NET SDK step + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0 + with: + dotnet-version: 10.x + + - name: Checkout code step + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + # `dotnet husky run` is the repo's git-hook runner; it invokes the same + # CSharpier + dotnet format style checks the build conventions require. + - name: Check code style step + run: | + set -euo pipefail + dotnet tool restore + dotnet husky install + dotnet husky run + + - name: Run unit tests step + run: dotnet test + + # Fast PR feedback: build the library in smoke mode (Debug for develop / + # Release for main, no publishing). Validates the PR's base-branch + # configuration by passing `branch: github.base_ref`. Skipped entirely when + # the library didn't change (e.g. a docs-only or workflow-only PR) — unit + # tests still run. + smoke-build: + name: Smoke build changed targets job + needs: [changes, unit-test] + if: ${{ needs.changes.outputs.nuget == 'true' }} + uses: ./.github/workflows/build-release-task.yml secrets: inherit + with: + smoke: true + # Do not publish anything from a PR. + github: false + nuget: false + # Check out the PR head by SHA (not head_ref): the head SHA is reachable + # in the base repo via refs/pull/N/head even for fork PRs, whereas the + # head_ref branch name does not exist in the base repo for forks and + # would fail checkout. Validate it in the base branch's configuration. + # `workflow_dispatch` has no pull_request payload, so fall back to the + # triggering ref. + ref: ${{ github.event.pull_request.head.sha || github.ref_name }} + branch: ${{ github.base_ref || github.ref_name }} + enable_nuget: ${{ needs.changes.outputs.nuget == 'true' }} # TODO: Workaround for GitHub Actions not supporting status checks on conditional jobs # https://github.com/orgs/community/discussions/12395#discussioncomment-12970019 + # This job's name is bound to the branch ruleset as the required status check + # context — do NOT rename it (see AGENTS.md "Workflow YAML Conventions"). check-workflow-status: name: Check pull request workflow status runs-on: ubuntu-latest needs: - [ test-release ] + [ changes, unit-test, smoke-build ] if: always() steps: - name: Check workflow results step @@ -38,4 +125,15 @@ jobs: exit 1 fi } - exit_on_result "test-release" "${{ needs.test-release.result }}" + # The paths-filter job MUST succeed: if it failed we don't know + # whether the library changed, so a library-changing PR could merge + # with its smoke build silently skipped. Treat anything other than + # success as a block. + if [[ "${{ needs.changes.result }}" != "success" ]]; then + echo "Job 'changes' did not succeed (${{ needs.changes.result }}); refusing to pass." + exit 1 + fi + # unit-test always runs; smoke-build may be legitimately skipped + # (library unchanged) — `skipped` passes, only failure/cancelled blocks. + exit_on_result "unit-test" "${{ needs.unit-test.result }}" + exit_on_result "smoke-build" "${{ needs.smoke-build.result }}" diff --git a/.github/workflows/test-release-task.yml b/.github/workflows/test-release-task.yml deleted file mode 100644 index 707c4d6..0000000 --- a/.github/workflows/test-release-task.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Test release task - -on: - workflow_call: - workflow_dispatch: - -jobs: - - unit-test: - name: Run unit tests job - runs-on: ubuntu-latest - - steps: - - - name: Setup .NET SDK step - uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0 - with: - dotnet-version: 10.x - - - name: Checkout code step - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - - name: Check code style step - run: | - set -euo pipefail - dotnet tool restore - dotnet husky install - dotnet husky run - - - name: Run unit tests step - run: dotnet test - - build-release: - name: Build release without publishing job - needs: [unit-test] - uses: ./.github/workflows/build-release-task.yml - secrets: inherit - with: - # Do not publish - github: false - nuget: false diff --git a/AGENTS.md b/AGENTS.md index 606435e..1dcbe03 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ This file is the canonical reference for cross-cutting AI-agent and workflow rul ## Branching Model - `develop` is the integration branch. Feature branches → `develop` is **squash-only**; develop is kept linear. -- `develop` → `main` is **merge-commit only** (no squash, no rebase). Merge commits preserve develop's commit list as a real second-parent reference on main, which is what makes the "release on every push" model attribute releases to the develop commits that produced them. Branch protection enforces this: the develop ruleset allows only `squash`, the main ruleset allows only `merge`. +- `develop` → `main` is **merge-commit only** (no squash, no rebase). Merge commits preserve develop's commit list as a real second-parent reference on main, which lets the release model attribute releases to the develop commits that produced them (relevant both for the weekly publish and the opt-in `PUBLISH_ON_MERGE` mode — see "Release Model" below). Branch protection enforces this: the develop ruleset allows only `squash`, the main ruleset allows only `merge`. - All commits on both branches must be cryptographically signed (SSH or GPG). Squash and merge commits created via the GitHub UI are signed by GitHub's web-flow key. - **`develop` is forward-only — no `main → develop` back-merges.** The develop ruleset's squash-only setting physically blocks merge commits on develop. Historical back-merge commits visible in `git log` predate this rule and must not be repeated. - **Both rulesets intentionally omit "Require branches to be up to date before merging" (`strict_required_status_checks_policy: false`), for two distinct reasons:** @@ -24,7 +24,16 @@ This file is the canonical reference for cross-cutting AI-agent and workflow rul - *Develop* — bot auto-merge incompatibility. When two bot PRs against develop land in the same minute (e.g. two grouped Dependabot PRs from the same daily run), the first to merge pushes the second into `mergeStateStatus: BEHIND`. GitHub's auto-merge will not fire while the strict flag is on, and nothing in the workflow set auto-updates a bot branch in that window — the merge-bot only *enables* auto-merge on `opened`/`reopened` (see [`merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml)). Real file-level conflicts are still caught textually (`mergeable: CONFLICTING` blocks merge regardless); semantic-but-not-textual conflicts that combine cleanly are caught by the post-merge develop CI run rather than pre-merge. Do not reintroduce the strict flag on develop thinking it's hygiene — it breaks bot auto-merge. - **Bots (Dependabot and codegen) target both `main` and `develop` in parallel.** [`.github/dependabot.yml`](./.github/dependabot.yml) duplicates every ecosystem entry (one per branch) and [`.github/workflows/run-codegen-pull-request-task.yml`](./.github/workflows/run-codegen-pull-request-task.yml) runs as a matrix over both branches with branch names `codegen-main` and `codegen-develop`. Each branch absorbs its own bot PRs independently, so neither falls behind, and the forward-only rule still holds (nothing is back-merged from main to develop — both branches receive their updates directly). Parallel auto-merge across same-batch bot PRs is race-proof only because both rulesets have the strict "up to date" flag off (see bullet above). The merge-bot ([`.github/workflows/merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml)) dispatches `--squash` or `--merge` from each PR's base ref via a `case` statement so the form matches the ruleset on either base. Dependabot **security** PRs (CVE-driven) always open against the repo default branch (`main`) regardless of `target-branch` — the same `case` statement covers them. - **Maintainer-pushed commits on a bot PR auto-disable auto-merge.** The merge-bot's `merge-dependabot` and `merge-codegen` jobs only fire on `opened` / `reopened` events (auto-merge is enabled exactly once per PR). When a maintainer pushes commits to a bot's branch (a `synchronize` event with an actor that isn't the same bot), the merge-bot's `disable-auto-merge-on-maintainer-push` job fires and calls `gh pr merge --disable-auto`. The maintainer's commits stay in the PR but won't auto-merge with the bot's content; re-enable auto-merge manually (`gh pr merge --auto ` or the GitHub UI) when ready. -- **Why parallel dual-target rather than develop-only with eventual flow-through:** consumers (NuGet.org, GitHub releases) pull from `main` directly. A develop-only model would leave `main` running stale code during long-running develop features. Codegen content here is the embedded ISO 639-2/3 + RFC 5646 language data — production-critical and refreshed weekly — so both branches need fresh codegen on their own cadence. +- **Why parallel dual-target rather than develop-only with eventual flow-through:** consumers (NuGet.org, GitHub releases) pull from `main` directly. A develop-only model would leave `main` running stale code during long-running develop features. Codegen content here is the embedded ISO 639-2/3 + RFC 5646 language data — production-critical — so both branches need fresh codegen on their own cadence (codegen PRs are opened **daily**; the actual release is **published weekly** — see "Release Model" below). + +## Release Model + +This repo uses a **two-phase model by default**: PRs build fast, publishing is batched weekly. The load-bearing rules: + +- **PRs smoke-test only.** [`test-pull-request.yml`](./.github/workflows/test-pull-request.yml) always runs unit tests, then a `dorny/paths-filter` `changes` job gates a **reduced, never-published** library build (`smoke: true`) that runs only when the library actually changed. Build-workflow files are intentionally not in the path filter — a filter can't tell a logic change from an action-version bump — so a workflow-only change isn't smoke-built; the reusable workflows are exercised by the next run that uses them. There is no CI workflow-lint job; lint workflow edits with `actionlint` locally before pushing. +- **Merges don't publish by default.** [`publish-release.yml`](./.github/workflows/publish-release.yml) is the sole publisher: its **weekly schedule** (Mondays 02:00 UTC) and **manual `workflow_dispatch`** always do the full build/publish of **both** `main` and `develop` (a branch matrix). Its `push` trigger publishes only when the **`PUBLISH_ON_MERGE` repository variable** is `true` (opt-in legacy continuous-release). Unset/`false` = two-phase. Codegen runs **daily** ([`run-periodic-codegen-pull-request.yml`](./.github/workflows/run-periodic-codegen-pull-request.yml), 04:00 UTC), staggered after the weekly publish; Dependabot also runs daily — both only smoke-test on merge. +- **Required check.** The `changes` job is in the `Check pull request workflow status` aggregator's `needs` and **must succeed** (not just "not fail") — a paths-filter error must never let a library-changing PR merge with its smoke build silently skipped. Skipped smoke jobs (no matching change) pass; `failure`/`cancelled` blocks. +- **Reusable-task parameter contract.** [`build-release-task.yml`](./.github/workflows/build-release-task.yml) and [`build-nugetlibrary-task.yml`](./.github/workflows/build-nugetlibrary-task.yml) take `ref` (git ref to check out/version), `branch` (logical branch driving config/tags/prerelease — `main` ⇒ Release/non-prerelease, else Debug/prerelease), and where relevant `smoke`. **Branch-derived config keys off `inputs.branch`, never `github.ref_name`** — the publisher's matrix builds `develop` from a run whose `github.ref_name` is `main`, so `ref_name` would be wrong. Artifact names are branch-suffixed so both matrix legs coexist in one run. [`get-version-task.yml`](./.github/workflows/get-version-task.yml) takes a `ref` so NBGV versions the right branch, and exposes `GitCommitId` so the release tag and built artifacts pin to the exact built commit. ## Pull Request Title and Commit Message Conventions @@ -115,7 +124,7 @@ These conventions describe the target state. New and modified workflows must res - **Filename**: reusable workflows (those with `on: workflow_call`) end in `-task.yml`. Entry-point workflows (`on: push` / `pull_request` / `schedule` / `workflow_dispatch`) do NOT use the `-task` suffix; they end with what they do — `-pull-request.yml`, `-release.yml`, etc. The suffix carries semantic meaning: a `-task.yml` file is meant to be `uses:`-d, never triggered directly. - **Workflow `name:`** (the top-level `name:` field): reusable workflow names end in **"task"** (e.g. `Build library task`); entry-point workflow names end in **"action"** (e.g. `Publish project release action`, `Test pull request action`). The displayed action name in the GitHub Actions UI tells you at a glance whether you're looking at an orchestrator or a callee. - **Job and step `name:` suffixes**: every job's `name:` ends in **"job"**; every step's `name:` ends in **"step"**. **Exception**: a job whose `name:` is also referenced as a required-status-check `context:` in a branch ruleset (currently `Check pull request workflow status` in `test-pull-request.yml`) keeps the ruleset-bound name verbatim — renaming would silently break required-status-check enforcement. Do not "fix" that name; if a future job becomes ruleset-bound, mark it the same way. -- **Concurrency**: top-level workflows declare `concurrency: { group: '${{ github.workflow }}-${{ github.ref }}', cancel-in-progress: true }` so a fresh push supersedes an in-flight run on the same ref. **Documented exception**: [`merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml) uses `cancel-in-progress: false` because its three-job model (enable-auto-merge on opened, disable-auto-merge on maintainer-pushed synchronize, with method dispatched by base) requires each event to run to completion in arrival order. Cancellation would leave auto-merge in an inconsistent state. The rationale is recorded inline in that workflow's header comment. +- **Concurrency**: top-level workflows declare `concurrency: { group: '${{ github.workflow }}-${{ github.ref }}', cancel-in-progress: true }` so a fresh push supersedes an in-flight run on the same ref. **Documented exceptions** (both record the rationale inline in their header comment): (1) [`merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml) uses `cancel-in-progress: false` because its three-job model (enable-auto-merge on opened, disable-auto-merge on maintainer-pushed synchronize, with method dispatched by base) requires each event to run to completion in arrival order — cancellation would leave auto-merge in an inconsistent state. (2) [`publish-release.yml`](./.github/workflows/publish-release.yml) uses both a **global, ref-independent group** for real publishes (`group: ${{ github.workflow }}`, dropping the usual `-${{ github.ref }}`) and `cancel-in-progress: false`. Its schedule/dispatch runs publish both branches regardless of the triggering ref, so a ref-scoped group would let a scheduled run (ref `main`) and a manual dispatch (ref `develop`) run concurrently and double-publish; and cancelling a publish mid-flight can leave a half-created GitHub release. Non-publishing (two-phase default) `push` runs get a unique per-run group so they never queue behind a real publish. - **Shells**: multi-line `run:` blocks with bash start with `set -euo pipefail` — fail fast, fail on undefined vars, fail on a failed pipe segment. - **Conditionals**: multi-line `if:` uses folded scalar `if: >-` so YAML preserves whitespace correctly. Literal block (`if: |`) is wrong because it embeds newlines inside the boolean expression. - **Boolean inputs**: workflows triggered both via `workflow_call` and `workflow_dispatch` must declare each boolean input in *both* trigger blocks — one definition does not propagate to the other. `workflow_call` delivers booleans as actual booleans; `workflow_dispatch` delivers them as the *strings* `"true"`/`"false"`. Any `if:` consuming a boolean input must compare against both forms — `if: ${{ inputs.foo == true || inputs.foo == 'true' }}`. diff --git a/README.md b/README.md index caa568b..4572b4a 100644 --- a/README.md +++ b/README.md @@ -415,7 +415,7 @@ LogOptions.SetFactory(loggerFactory); - ISO 639-2: [Source](https://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt), [Data](./LanguageData/iso6392), [JSON](./LanguageData/iso6392.json), [Code](./LanguageTags/Iso6392DataGen.cs) - ISO 639-3: [Source](https://iso639-3.sil.org/sites/iso639-3/files/downloads/iso-639-3.tab), [Data](./LanguageData/iso6393), [JSON](./LanguageData/iso6393.json), [Code](./LanguageTags/Iso6393DataGen.cs) - RFC 5646 : [Source](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry), [Data](./LanguageData/rfc5646), [JSON](./LanguageData/rfc5646.json), [Code](./LanguageTags/Rfc5646DataGen.cs) -- A weekly [GitHub Actions](./.github/workflows/run-periodic-codegen-pull-request.yml) job keeps the data files up to date and automatically publishes new releases. +- A daily [GitHub Actions](./.github/workflows/run-periodic-codegen-pull-request.yml) job opens PRs to keep the data files up to date; a [weekly scheduled job](./.github/workflows/publish-release.yml) publishes new releases. Routine merges (Dependabot, codegen) only smoke-test — the actual build/publish is batched into the weekly run (two-phase model). ## Contributing @@ -425,7 +425,7 @@ The repo uses a two-branch model with strict ruleset-enforced merge methods: - Feature branch → `develop` via **squash merge** (develop is kept linear). - `develop` → `main` via **merge commit** (preserves develop's commit list on main as the second parent of each release commit). -- `develop` is **forward-only** — there are no `main → develop` back-merges. Dependabot and the weekly codegen workflow both target `main` and `develop` in parallel via separate PRs. +- `develop` is **forward-only** — there are no `main → develop` back-merges. Dependabot and the daily codegen workflow both target `main` and `develop` in parallel via separate PRs. See [`AGENTS.md`](./AGENTS.md) for the complete branching, PR, and workflow conventions and [`CODESTYLE.md`](./CODESTYLE.md) for C# code style rules. @@ -438,7 +438,7 @@ CI/CD relies on these secrets being configured on the repo: Branch protection is split across two rulesets: -- **Develop** ruleset: squash-only, linear history, "branches up to date" check on, signed commits required. +- **Develop** ruleset: squash-only, linear history, "branches up to date" check off (the strict check blocks auto-merge when two same-batch bot PRs race — see AGENTS.md), signed commits required. - **Main** ruleset: merge-commit only, linear history off, "branches up to date" check off (forward-only develop makes this check incompatible with the merge-commit release shape), signed commits required. Both rulesets require the `Check pull request workflow status` status check and request Copilot review on every push. @@ -585,7 +585,7 @@ Licensed under the [MIT License][license-link]\ [releaseversion-shield]: https://img.shields.io/github/v/release/ptr727/LanguageTags?logo=github&label=GitHub%20Release [prereleaseversion-shield]: https://img.shields.io/github/v/release/ptr727/LanguageTags?include_prereleases&filter=*-g*&label=GitHub%20Pre-Release&logo=github -[releasebuildstatus-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/LanguageTags/publish-release.yml?logo=github&label=Releases%20Build +[releasebuildstatus-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/LanguageTags/publish-release.yml?logo=github&label=Releases%20Build&event=schedule [nuget-link]: https://www.nuget.org/packages/ptr727.LanguageTags/ [nugetreleaseversion-shield]: https://img.shields.io/nuget/v/ptr727.LanguageTags?logo=nuget&label=NuGet%20Release