Skip to content

Bump the dotnet-misc group with 1 update (#1000) #2158

Bump the dotnet-misc group with 1 update (#1000)

Bump the dotnet-misc group with 1 update (#1000) #2158

Workflow file for this run

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."