Bump the dotnet-misc group with 1 update (#1000) #2158
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: CI Build | |
| # Deliberately unfiltered by path. `ci-gate` is the job that gates merges, and a workflow | |
| # skipped by a `paths:` filter never creates its check runs - a required context that never | |
| # reports stays pending forever, which is why docs-only changes used to need a ruleset | |
| # bypass. Per-language filtering happens in the `changes` job instead, where a skipped job | |
| # still reports a conclusion. | |
| on: | |
| push: | |
| branches: | |
| - "main" | |
| pull_request: | |
| types: | |
| - opened | |
| - synchronize | |
| - reopened | |
| concurrency: | |
| # Supersede in-flight runs for the same pull request only. Runs on `main` are never | |
| # cancelled, so the commit that lands always keeps a complete set of results. | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| dotnet: ${{ steps.filter.outputs.dotnet }} | |
| go: ${{ steps.filter.outputs.go }} | |
| python: ${{ steps.filter.outputs.python }} | |
| web: ${{ steps.filter.outputs.web }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| persist-credentials: false | |
| - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 | |
| id: filter | |
| with: | |
| filters: | | |
| dotnet: | |
| - 'src/Garage.ApiService/**' | |
| - 'src/Garage.ApiDatabaseSeeder/**' | |
| - 'src/Garage.ApiModel/**' | |
| - 'src/Garage.AppHost/**' | |
| - 'src/Garage.ServiceDefaults/**' | |
| - 'src/Garage.Shared/**' | |
| - '**/*.props' | |
| - '*.slnx' | |
| - 'global.json' | |
| - '.github/workflows/ci.yml' | |
| go: | |
| - 'src/Garage.FeatureFlags/**' | |
| - '.github/workflows/ci.yml' | |
| python: | |
| - 'src/Garage.ChatService/**' | |
| - '.github/workflows/ci.yml' | |
| web: | |
| - 'src/Garage.Web/**' | |
| - '.github/workflows/ci.yml' | |
| build-dotnet: | |
| needs: changes | |
| if: needs.changes.outputs.dotnet == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| persist-credentials: false | |
| - name: Cache NuGet packages | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget- | |
| - name: Setup .NET Core | |
| uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 | |
| with: | |
| global-json-file: global.json | |
| - name: Restore dependencies | |
| run: dotnet restore | |
| - name: dotnet format | |
| run: dotnet format --verify-no-changes --no-restore | |
| - name: Build with dotnet | |
| run: dotnet build --configuration Release --no-restore | |
| # - name: Test with dotnet | |
| # run: dotnet test | |
| build-go: | |
| needs: changes | |
| if: needs.changes.outputs.go == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| defaults: | |
| run: | |
| working-directory: src/Garage.FeatureFlags | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Go | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version-file: src/Garage.FeatureFlags/go.mod | |
| cache-dependency-path: src/Garage.FeatureFlags/go.sum | |
| - name: Format check | |
| run: | | |
| if [ -n "$(gofmt -l .)" ]; then | |
| echo "Go files are not formatted. Run 'go fmt ./...' to fix." | |
| gofmt -d . | |
| exit 1 | |
| fi | |
| # setup-go with cache-dependency-path already restores the Go module cache. | |
| # go test compiles all packages (including those with no test files) before running, | |
| # so a separate `go build` step would double the compilation time. | |
| - name: Test | |
| run: go test -v ./... | |
| build-python: | |
| needs: changes | |
| if: needs.changes.outputs.python == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| defaults: | |
| run: | |
| working-directory: src/Garage.ChatService | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3.14' | |
| allow-prereleases: true | |
| - name: Setup uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: | | |
| src/Garage.ChatService/pyproject.toml | |
| src/Garage.ChatService/uv.lock | |
| - name: Install dependencies | |
| run: uv sync --frozen --group dev | |
| - name: Run tests | |
| run: uv run pytest | |
| build-web: | |
| needs: changes | |
| if: needs.changes.outputs.web == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| defaults: | |
| run: | |
| working-directory: src/Garage.Web | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 22 | |
| cache: 'npm' | |
| cache-dependency-path: src/Garage.Web/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Lint | |
| run: npm run lint | |
| - name: Build | |
| run: npm run build | |
| # The gating job. Every other job hangs off this one, so gating a new job means adding it | |
| # to `needs:` here. It is intended to be the only status check the `main` ruleset | |
| # requires, so the ruleset does not have to change when CI jobs do. | |
| ci-gate: | |
| name: CI Gate | |
| # `always()` is load-bearing. Without it the job inherits the implicit `success()` | |
| # condition and reports as *skipped* when a dependency fails - and a skipped check | |
| # satisfies a ruleset, letting through exactly the pull requests this is meant to stop. | |
| if: always() | |
| needs: [changes, build-dotnet, build-go, build-python, build-web] | |
| runs-on: ubuntu-slim | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Check that every gated job succeeded | |
| # GitHub's default shell is `bash -e {0}` - `set -e` but *no* `pipefail`. Naming | |
| # the shell explicitly upgrades it to `bash --noprofile --norc -eo pipefail {0}`, | |
| # so a failure anywhere in `echo ... | jq ...` is observed rather than masked by | |
| # jq's own exit status. A gate that fails open is worse than no gate at all. | |
| shell: bash | |
| env: | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| # Comma-separated jobs allowed to report `skipped`. The four language builds are | |
| # skipped by design when `changes` reports their paths were untouched. `changes` | |
| # is deliberately absent: if it fails, the builds cascade to `skipped` and only | |
| # its own result is left to fail the gate. | |
| ALLOWED_SKIPS: "build-dotnet,build-go,build-python,build-web" | |
| run: | | |
| # Restated rather than relying on the shell keyword alone, so the script behaves | |
| # the same when extracted and run outside Actions. | |
| set -euo pipefail | |
| # An empty `needs` would sail through every check below and report success | |
| # without gating anything - e.g. if a future edit drops the `needs:` key. | |
| # Assigned rather than inlined into the `if`, because `set -e` is suspended | |
| # inside a condition: a jq parse failure there would fall through to the | |
| # comparison instead of aborting. | |
| job_count=$(jq -r 'length' <<<"$NEEDS_JSON") | |
| if [ "$job_count" -eq 0 ]; then | |
| echo "::error::CI Gate has no upstream jobs to check; its needs list is empty." | |
| exit 1 | |
| fi | |
| { | |
| echo "### CI Gate" | |
| echo "" | |
| echo "| Job | Result |" | |
| echo "| --- | --- |" | |
| jq -r 'to_entries[] | "| \(.key) | \(.value.result) |"' <<<"$NEEDS_JSON" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| failing=$(jq -r --arg allowed "$ALLOWED_SKIPS" ' | |
| ($allowed | split(",") | map(select(length > 0))) as $ok | |
| | to_entries[] | |
| | select(.value.result != "success") | |
| | select(.value.result != "skipped" or ([.key] - $ok | length) > 0) | |
| | "\(.key): \(.value.result)" | |
| ' <<<"$NEEDS_JSON") | |
| if [ -n "$failing" ]; then | |
| echo "::error::One or more gated jobs did not succeed:" | |
| echo "$failing" | |
| exit 1 | |
| fi | |
| echo "All gated jobs succeeded or were intentionally skipped." |