fix(release): classify releases via cargo-semver-checks vs last published version #3907
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: main | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| on: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] | |
| jobs: | |
| # Emits per-tier `--exclude NAME ...` args (modified/affected/required) to scope | |
| # downstream `cargo --workspace` jobs. PRs snapshot base vs. the merge commit | |
| # (same SHA downstream jobs build); pushes emit empty (full backstop). | |
| # `skip=true` when no crates are impacted (e.g. doc-only PR). | |
| delta: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| exclude_not_modified: ${{ steps.compute.outputs.exclude_not_modified }} | |
| exclude_not_affected: ${{ steps.compute.outputs.exclude_not_affected }} | |
| exclude_not_required: ${{ steps.compute.outputs.exclude_not_required }} | |
| skip: ${{ steps.compute.outputs.skip }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup | |
| if: github.event_name == 'pull_request' | |
| uses: ./.github/actions/setup | |
| with: | |
| rust-toolchain: RUST_LATEST | |
| cargo-tools: CARGO_DELTA_VERSION | |
| - name: Compute Impact | |
| id: compute | |
| shell: bash | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${{ github.event_name }}" != "pull_request" ]]; then | |
| # push to main (or any non-PR trigger) -> full backstop: no excludes. | |
| { | |
| echo "exclude_not_modified=" | |
| echo "exclude_not_affected=" | |
| echo "exclude_not_required=" | |
| echo "skip=false" | |
| } >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Snapshots written outside the working tree so `git checkout` can't trip on them. | |
| baseline="$RUNNER_TEMP/delta-baseline.json" | |
| current="$RUNNER_TEMP/delta-current.json" | |
| head_sha=$(git rev-parse HEAD) | |
| git fetch --no-tags origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}" | |
| git checkout "origin/$BASE_REF" | |
| cargo delta -c .delta.toml snapshot > "$baseline" | |
| git checkout "$head_sha" | |
| cargo delta -c .delta.toml snapshot > "$current" | |
| # `cargo-excludes` emits the workspace complement of the selected tier as | |
| # `--exclude NAME ...`, scoped via downstream `cargo --workspace`. `--exclude` | |
| # only matches workspace members, avoiding `-p` ambiguity with same-named | |
| # registry deps. | |
| impact() { | |
| cargo delta -c .delta.toml impact --baseline "$baseline" --current "$current" "$@" | |
| } | |
| exclude_not_modified=$(impact -f cargo-excludes --modified) | |
| exclude_not_affected=$(impact -f cargo-excludes --affected) | |
| exclude_not_required=$(impact -f cargo-excludes --required) | |
| # `required` is the superset (required ⊇ affected ⊇ modified). If it | |
| # has no impacted crates, nothing is impacted and downstream | |
| # source-scoped jobs can be skipped entirely. | |
| if [[ -z "$(impact -f names --required)" ]]; then | |
| skip=true | |
| else | |
| skip=false | |
| fi | |
| { | |
| echo "exclude_not_modified=$exclude_not_modified" | |
| echo "exclude_not_affected=$exclude_not_affected" | |
| echo "exclude_not_required=$exclude_not_required" | |
| echo "skip=$skip" | |
| } | tee -a "$GITHUB_OUTPUT" | |
| testing: | |
| needs: [delta] | |
| if: needs.delta.outputs.skip != 'true' | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, ubuntu-24.04-arm, windows-11-arm] | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| enable-sccache: true | |
| rust-toolchain: RUST_MSRV, RUST_NIGHTLY | |
| cargo-tools: CARGO_NEXTEST_VERSION, CARGO_HACK_VERSION, JUST_VERSION | |
| # execute | |
| - name: Build | |
| run: cargo hack build --each-feature --workspace ${{ needs.delta.outputs.exclude_not_affected }} --verbose --locked --color always | |
| - name: Tests | |
| if: success() || failure() | |
| run: cargo nextest run --verbose --workspace ${{ needs.delta.outputs.exclude_not_affected }} --all-features | |
| - name: Docs | |
| if: success() || failure() | |
| env: | |
| RUSTDOCFLAGS: "--cfg docsrs -D warnings" | |
| run: cargo +${{ env.RUST_NIGHTLY }} doc --no-deps --verbose --workspace ${{ needs.delta.outputs.exclude_not_required }} --all-features | |
| - name: Doc Tests | |
| if: success() || failure() | |
| run: cargo test --doc --verbose --workspace ${{ needs.delta.outputs.exclude_not_affected }} --all-features | |
| - name: Doc Tests (default features) | |
| if: success() || failure() | |
| run: cargo test --doc --verbose --workspace ${{ needs.delta.outputs.exclude_not_affected }} | |
| - name: Examples | |
| if: success() || failure() | |
| run: cargo +${{ env.RUST_NIGHTLY }} -Zscript ./scripts/run-examples.rs --cargo-profile dev ${{ needs.delta.outputs.exclude_not_affected }} | |
| static-analysis: | |
| needs: [delta] | |
| if: needs.delta.outputs.skip != 'true' | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, ubuntu-24.04-arm, windows-11-arm] | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| enable-sccache: true | |
| rust-toolchain: RUST_LATEST | |
| rust-components: clippy | |
| cargo-tools: CARGO_DOC2README_VERSION, CARGO_WORKSPACES_VERSION, CARGO_ENSURE_NO_DEFAULT_FEATURES_VERSION, CARGO_ENSURE_NO_CYCLIC_DEPS_VERSION, CARGO_DENY_VERSION, CARGO_SORT_VERSION | |
| # execute | |
| - name: Clippy | |
| run: cargo clippy --workspace ${{ needs.delta.outputs.exclude_not_modified }} --all-targets --all-features -- -D warnings | |
| - name: Install Nightly Rustfmt | |
| if: success() || failure() | |
| run: rustup toolchain install ${{ env.RUST_NIGHTLY }} --profile minimal --component rustfmt | |
| - name: Source Format (nightly) | |
| if: success() || failure() | |
| run: cargo +${{ env.RUST_NIGHTLY }} fmt --all -- --config-path ./unstable-rustfmt.toml --check | |
| - name: Cargo.toml Format | |
| if: success() || failure() | |
| run: cargo sort --check --grouped --workspace | |
| - name: Readmes | |
| if: success() || failure() | |
| run: cargo workspaces exec --ignore-private cargo doc2readme --check --lib --template ../README.j2 | |
| env: | |
| # Disable sccache: macro expansion (rustc -Zunpretty=expanded) does not | |
| # produce the artifact files sccache expects to cache, causing it to fail. | |
| RUSTC_WRAPPER: "" | |
| - name: Default Features | |
| if: success() || failure() | |
| run: cargo ensure-no-default-features | |
| - name: Cyclic Dependencies | |
| if: success() || failure() | |
| run: cargo ensure-no-cyclic-deps | |
| - name: Dependency Validation | |
| if: success() || failure() | |
| run: cargo deny --all-features --workspace --color always check all | |
| spell-check: | |
| runs-on: ubuntu-slim | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| rust-toolchain: RUST_LATEST | |
| cargo-tools: CARGO_SPELLCHECK_VERSION, JUST_VERSION | |
| - name: Check Spelling | |
| run: just spellcheck | |
| # Runs cargo-semver-checks against each crate's LAST PUBLISHED version on | |
| # crates.io (the tool's default baseline — no --baseline-rev), scoped to the | |
| # crates whose `version =` is bumped in this PR (the "publishing set"). The | |
| # job is informational only: it never fails the build and posts a fresh PR | |
| # comment on every push with a per-crate table (crates.io vs this-PR vs the | |
| # minimum required version) plus a link to the run. It is a no-op on PRs that | |
| # do not bump any crate version, since only version-bumping PRs trigger a | |
| # publish and thus need a compatibility check against the published baseline. | |
| semver: | |
| needs: [delta] | |
| if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| enable-sccache: true | |
| rust-toolchain: RUST_LATEST | |
| cargo-tools: CARGO_SEMVER_CHECKS_VERSION | |
| # Compute the publishing set (version-bumped published crates), run | |
| # cargo-semver-checks per crate against its last published version on the | |
| # configured registry (SEMVER_REGISTRY, crates.io by default), and render | |
| # a Markdown report (scripts/ci/semver-report.ps1 — reuses the release | |
| # library's version-diff / next-version helpers). Sets outputs: | |
| # publishing = true|false (any crate version bumped) | |
| # status = pass|fail (any crate under-incremented for its API changes) | |
| # Kept non-failing so registry/network hiccups never block the PR. | |
| - name: Semver Report | |
| id: report | |
| continue-on-error: true | |
| shell: pwsh | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| # Registry the baseline version is read from. Override in a fork that | |
| # publishes to a private registry (name as in .cargo/config.toml). | |
| SEMVER_REGISTRY: crates-io | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| git fetch --no-tags origin "+refs/heads/$env:BASE_REF`:refs/remotes/origin/$env:BASE_REF" | |
| ./scripts/ci/semver-report.ps1 -BaseRef "origin/$env:BASE_REF" -ReportPath semver-comment.md -RunUrl "$env:RUN_URL" -Registry "$env:SEMVER_REGISTRY" | |
| # Post a NEW comment on every push (not a sticky update) so each run's | |
| # result is visible in the PR timeline. Only fires when the PR actually | |
| # publishes something and is not from a fork (forks lack a write token). | |
| - name: Post Semver Comment | |
| if: steps.report.outputs.publishing == 'true' && github.event.pull_request.head.repo.full_name == github.repository | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: gh pr comment ${{ github.event.pull_request.number }} --body-file semver-comment.md | |
| extended-analysis: | |
| name: extended-analysis (${{ matrix.os }}) | |
| if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true' | |
| needs: [delta, testing, static-analysis] | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| - os: ubuntu-24.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| - os: windows-11-arm | |
| target: aarch64-pc-windows-msvc | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| enable-sccache: true | |
| rust-toolchain: RUST_NIGHTLY | |
| rust-components: miri | |
| cargo-tools: CARGO_UDEPS_VERSION, CARGO_CAREFUL_VERSION, CARGO_NEXTEST_VERSION | |
| # execute | |
| - name: Udeps | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $PSNativeCommandUseErrorActionPreference = $true | |
| # Without --all-targets: enforce that every [dependencies] entry is used | |
| # by the library/bins (catches normal deps used only by tests/benches/ | |
| # examples -- i.e. misplaced deps that should be [dev-dependencies]). | |
| cargo +${{ env.RUST_NIGHTLY }} udeps --locked --all-features --workspace ${{ needs.delta.outputs.exclude_not_modified }} --color always | |
| # With --all-targets: enforce that every [dev-dependencies] entry is used | |
| # by some target (catches unused dev-dependencies). | |
| cargo +${{ env.RUST_NIGHTLY }} udeps --locked --all-features --all-targets --workspace ${{ needs.delta.outputs.exclude_not_modified }} --color always | |
| - name: Careful | |
| if: success() || failure() | |
| run: cargo +${{ env.RUST_NIGHTLY }} careful nextest run --all-features --workspace ${{ needs.delta.outputs.exclude_not_affected }} --color always --target ${{ matrix.target }} | |
| - name: Miri (stacked borrows) | |
| if: success() || failure() | |
| run: cargo +${{ env.RUST_NIGHTLY }} miri test --all-features --workspace ${{ needs.delta.outputs.exclude_not_affected }} --lib --tests | |
| model-checking: | |
| if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true' | |
| needs: [delta, testing] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| enable-sccache: true | |
| rust-toolchain: RUST_LATEST | |
| cargo-tools: JUST_VERSION | |
| # execute | |
| - name: Loom | |
| run: just loom | |
| fuzz-testing: | |
| if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true' | |
| needs: [delta, testing] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| enable-sccache: true | |
| rust-toolchain: RUST_NIGHTLY | |
| cargo-tools: CARGO_BOLERO_VERSION, JUST_VERSION | |
| # execute | |
| - name: Bolero | |
| run: just bolero 60s | |
| mutation-testing: | |
| if: github.event_name == 'pull_request' | |
| needs: [testing] | |
| runs-on: ubuntu-latest | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| enable-sccache: true | |
| enable-wild-linker: true | |
| rust-toolchain: RUST_NIGHTLY | |
| cargo-tools: CARGO_MUTANTS_VERSION | |
| # execute | |
| - name: Generate PR Diff | |
| run: git diff origin/main...HEAD >diff.txt | |
| - name: Mutate Diff | |
| timeout-minutes: 240 | |
| run: cargo +${{ env.RUST_NIGHTLY }} -Zscript scripts/mutants.rs --in-place --in-diff diff.txt | |
| coverage: | |
| needs: [delta] | |
| # Coverage always runs (no `delta.skip` gate) so the externally-posted | |
| # `codecov/project` status check is always reported. Codecov posts that | |
| # status in reaction to a coverage upload from this job; if `coverage` | |
| # is skipped, `codecov/project` stays in | |
| # `Expected — Waiting for status to be reported` and blocks merge. The | |
| # `required-checks` fan-in only solves stuck contexts for jobs we own, | |
| # so we eat the cost of running coverage on doc-only PRs here to keep | |
| # the externally-posted check unblocked. | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Note: windows-11-arm excluded due to LLVM coverage instrumentation bugs | |
| # causing "malformed instrumentation profile data: symbol name is empty" errors | |
| # Note: when changing the number of OS entries here, update | |
| # `codecov.notify.after_n_builds` in codecov.yml to match, otherwise | |
| # Codecov reports the project status before all platforms have uploaded. | |
| os: [ubuntu-latest, windows-latest, ubuntu-24.04-arm] | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| enable-sccache: true | |
| rust-toolchain: RUST_NIGHTLY | |
| rust-components: llvm-tools-preview | |
| cargo-tools: CARGO_LLVM_COV_VERSION | |
| # execute | |
| - name: Generate Coverage (all-features) | |
| run: cargo +${{ env.RUST_NIGHTLY }} llvm-cov --all-features --workspace --lcov --output-path lcov-all.info | |
| - name: Generate Coverage (no-default-features) | |
| run: cargo +${{ env.RUST_NIGHTLY }} llvm-cov --no-default-features --workspace --lcov --output-path lcov-no-def.info | |
| - name: Upload Coverage to Codecov | |
| uses: codecov/codecov-action@v7.0.0 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: lcov-all.info,lcov-no-def.info | |
| fail_ci_if_error: true | |
| pr-title: | |
| runs-on: ubuntu-slim | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - name: Check PR Title | |
| uses: amannn/action-semantic-pull-request@v6.1.1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| types: | | |
| build | |
| chore | |
| ci | |
| doc | |
| docs | |
| feat | |
| fix | |
| misc | |
| miscellaneous | |
| perf | |
| refactor | |
| style | |
| task | |
| test | |
| license-headers: | |
| runs-on: ubuntu-slim | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| rust-toolchain: RUST_LATEST | |
| cargo-tools: CARGO_HEATHER_VERSION | |
| - name: Check License Headers | |
| run: cargo heather | |
| external-type-exposure: | |
| needs: [delta] | |
| if: needs.delta.outputs.skip != 'true' | |
| # Runs `cargo check-external-types --all-features` sequentially over every | |
| # workspace crate, which exceeds the 15-minute `ubuntu-slim` execution cap as | |
| # the crate/dependency graph grows. Use a standard runner with an explicit | |
| # timeout so the job has headroom. | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| - name: Setup | |
| uses: ./.github/actions/setup | |
| with: | |
| enable-sccache: true | |
| rust-toolchain: RUST_NIGHTLY_EXTERNAL_TYPES | |
| cargo-tools: CARGO_CHECK_EXTERNAL_TYPES_VERSION | |
| # execute | |
| - name: Check External Type Exposure | |
| run: cargo +${{ env.RUST_NIGHTLY_EXTERNAL_TYPES }} -Zscript scripts/check-external-types.rs ${{ env.RUST_NIGHTLY_EXTERNAL_TYPES }} | |
| script-tests: | |
| # Pester test suite for the release-related PowerShell scripts | |
| # (release-packages.ps1, lib/releasing.ps1, lib/release-flow.ps1). | |
| # Always runs (no delta gate) because script-level changes don't move any | |
| # Rust crates so delta would mark the PR skippable, but the scripts | |
| # themselves may still need validation. Cross-platform matrix mirrors the | |
| # platforms developers run the scripts on locally. | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest] | |
| steps: | |
| # prep | |
| - name: Checkout | |
| uses: actions/checkout@v7.0.0 | |
| - name: Setup | |
| # The synthetic-workspace fixture builder shells out to `cargo metadata`, | |
| # so Rust must be available even though no Rust code is compiled. | |
| uses: ./.github/actions/setup | |
| with: | |
| rust-toolchain: RUST_LATEST | |
| - name: Install Pester | |
| shell: pwsh | |
| run: | | |
| $existing = Get-Module Pester -ListAvailable | | |
| Sort-Object Version -Descending | | |
| Select-Object -First 1 | |
| if ($null -eq $existing -or $existing.Version -lt [version]'5.7.0') { | |
| Install-Module -Name Pester -MinimumVersion 5.7.1 -Force -Scope CurrentUser -SkipPublisherCheck | |
| } else { | |
| Write-Host "Pester $($existing.Version) already installed; skipping." | |
| } | |
| # execute | |
| - name: Run Pester Suite | |
| shell: pwsh | |
| run: ./scripts/tests/Pester/Run-Tests.ps1 | |
| # Single aggregating ("fan-in") status check. Branch protection should | |
| # require ONLY this context for the workflow jobs below: it succeeds when | |
| # every dependency either succeeded or was skipped, and fails if any | |
| # dependency failed or was cancelled. This avoids the GitHub Actions | |
| # quirk where a matrix job gated by a job-level `if:` posts only a single | |
| # skipped check under the bare job name, leaving matrix-expanded required | |
| # contexts (`testing (ubuntu-latest)` etc.) stuck on | |
| # `Expected — Waiting for status to be reported`. | |
| # | |
| # Maintenance — when to add a job to `needs:` below: | |
| # | |
| # * Do NOT add jobs that are intentionally optional / advisory; this | |
| # fan-in is only for jobs that must pass before merge. | |
| # | |
| # * MUST add if the job has BOTH a `strategy.matrix` AND a job-level | |
| # `if:` that can evaluate to false (e.g. gated on | |
| # `needs.delta.outputs.skip`, `github.event_name`, etc.). Such jobs | |
| # trigger the stuck-context bug above and can only be made required | |
| # safely via this fan-in. | |
| # | |
| # * SHOULD add any other job that must pass before merge. Funnelling | |
| # every required job through this single context keeps branch | |
| # protection simple: one workflow context to require here, plus | |
| # externally-posted statuses (`license/cla`, `codecov/project`). | |
| # | |
| # When a new job is added below, also remove any direct entry for it | |
| # from the branch protection ruleset (only `required-checks` should be | |
| # listed for jobs defined in this workflow). | |
| required-checks: | |
| name: required-checks | |
| if: always() | |
| needs: | |
| - delta | |
| - testing | |
| - static-analysis | |
| - spell-check | |
| - semver | |
| - extended-analysis | |
| - model-checking | |
| - fuzz-testing | |
| - mutation-testing | |
| - coverage | |
| - pr-title | |
| - license-headers | |
| - external-type-exposure | |
| - script-tests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Validate Dependency Results | |
| shell: bash | |
| env: | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| run: | | |
| set -euo pipefail | |
| echo "$NEEDS_JSON" | jq -r 'to_entries[] | " \(.key): \(.value.result)"' | |
| # Pass when every dependency is success or skipped; fail otherwise | |
| # (failure, cancelled, or any unexpected state). | |
| bad=$(echo "$NEEDS_JSON" | jq -r ' | |
| to_entries | |
| | map(select(.value.result != "success" and .value.result != "skipped")) | |
| | .[] | "\(.key): \(.value.result)" | |
| ') | |
| if [[ -n "$bad" ]]; then | |
| printf '::error::One or more required jobs did not succeed or skip:\n%s\n' "$bad" | |
| exit 1 | |
| fi | |
| echo "All required jobs succeeded or were skipped." |