Skip to content

Commit 798fc42

Browse files
authored
ci: one composite setup action for every repo-owned workflow; the yaml lint gate runs for real; one loader behind the workflow pins (#199)
1 parent f1f938e commit 798fc42

19 files changed

Lines changed: 570 additions & 311 deletions

‎.github/actions/fetch-test-artifacts/action.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Restores the two fetched, gitignored test artifacts (trimmed OpenAPI spec, GraphQL schema)
22
# from cache, fetching each on a miss: the ONE place an artifact is cached (the drift-tripwire
3-
# nightlies fetch uncached by design). Needs setup-bun and `bun install` earlier in the job.
3+
# nightlies fetch uncached by design). Needs ./.github/actions/setup (bun and the install) earlier in the job.
44
name: Fetch the test artifacts
55
description: Restore the trimmed OpenAPI spec and the GraphQL schema from cache, fetching each on a miss
66

‎.github/actions/setup/action.yml‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# The bun setup and the dependency install of every repo-owned workflow job; the caller keeps its own checkout and
2+
# setup-node, whose options differ per job.
3+
# install -> --ignore-scripts: scripts.prepare installs lefthook, a git hook no runner uses; "false" (any letter
4+
# case, as GitHub compares) skips the install for a job that runs a bare script or resolves the lockfile itself
5+
# yamllint -> "true" installs the pinned yamllint behind `bun run lint:yaml`, which fails a CI run where it is missing
6+
name: Set up bun
7+
description: The pinned bun, the locked dependencies unless told not to, and yamllint for the jobs that lint YAML
8+
9+
inputs:
10+
install:
11+
description: '"false" sets up bun alone; anything else runs bun install --frozen-lockfile --ignore-scripts'
12+
default: "true"
13+
yamllint:
14+
description: '"true" installs the pinned yamllint for lint:yaml'
15+
default: "false"
16+
17+
runs:
18+
using: composite
19+
steps:
20+
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
21+
with:
22+
bun-version-file: .bun-version
23+
- name: Install dependencies
24+
if: inputs.install != 'false'
25+
shell: bash
26+
run: bun install --frozen-lockfile --ignore-scripts
27+
# pipx ships on the ubuntu runner image with its bin directory on PATH; a venv of its own leaves the runner's
28+
# python untouched.
29+
- name: Install yamllint
30+
if: inputs.yamllint == 'true'
31+
shell: bash
32+
run: pipx install yamllint==1.38.0

‎.github/workflows/auto-fix.yml‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,7 @@ jobs:
6868
with:
6969
ref: ${{ github.event.pull_request.head.ref }}
7070
persist-credentials: false
71-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
72-
with:
73-
bun-version-file: .bun-version
74-
- name: Install dependencies
75-
run: bun install --frozen-lockfile --ignore-scripts
71+
- uses: ./.github/actions/setup
7672
- name: Graduate upstream gaps octokit now ships
7773
shell: bash
7874
run: bun .github/scripts/graduate-upstream-gaps.ts

‎.github/workflows/auto-format.yml‎

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
# commit to the PR branch, then removes the label. Generated once by
44
# the fleet sync and never overwritten by a later
55
# sync: adjust the format commands to this repository's tooling.
6+
#
7+
# Formatting runs PR-controlled code (the locked biome, the lint:fix script), so it happens in a job without a write
8+
# token and crosses to the push job as a git patch; applying a patch executes nothing, as in auto-fix.yml.
69

710
name: Auto Format
811

@@ -13,42 +16,95 @@ on:
1316
permissions:
1417
contents: read
1518

19+
# One run per PR at a time: the label removed and re-applied while a run is pushing would start a second run against
20+
# the same branch, and two formatting pushes race. The later run queues and formats the pushed head instead.
21+
concurrency:
22+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
23+
cancel-in-progress: false
24+
1625
jobs:
1726
format:
18-
# Same-repo PRs only: GITHUB_TOKEN cannot push to a fork's branch, and
19-
# PR-controlled tool configuration should not run next to write
20-
# credentials (checkout below persists none; the push step scopes them).
27+
# Same-repo PRs only: GITHUB_TOKEN cannot push to a fork's branch.
2128
if: github.event.label.name == 'fix-lint' && github.event.pull_request.head.repo.full_name == github.repository
2229
runs-on: ubuntu-latest
30+
timeout-minutes: 15
31+
outputs:
32+
changed: ${{ steps.format.outputs.changed }}
33+
head: ${{ steps.format.outputs.head }}
34+
steps:
35+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
36+
with:
37+
ref: ${{ github.event.pull_request.head.ref }}
38+
persist-credentials: false
39+
# The install puts the locked biome in node_modules, so the format runs the gate's biome, not the newest one.
40+
- uses: ./.github/actions/setup
41+
- name: Format (biome) and stage the patch
42+
id: format
43+
run: |
44+
# The patch belongs to this commit alone; the push job refuses any other head.
45+
echo "head=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
46+
bun run lint:fix
47+
# Anything the formatter left staged is not this workflow's fix: start from an empty index, then stage
48+
# modifications and deletions of tracked files alone (a formatter adds nothing).
49+
git reset -q
50+
git add -u
51+
git diff --cached --binary > "$RUNNER_TEMP/format.patch"
52+
if [ -s "$RUNNER_TEMP/format.patch" ]; then
53+
git diff --cached --stat
54+
echo "changed=true" >> "$GITHUB_OUTPUT"
55+
else
56+
echo "nothing to format"
57+
echo "changed=false" >> "$GITHUB_OUTPUT"
58+
fi
59+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
60+
if: steps.format.outputs.changed == 'true'
61+
with:
62+
name: format-patch
63+
path: ${{ runner.temp }}/format.patch
64+
if-no-files-found: error
65+
66+
# Runs whenever the format job ran, so the label comes off even after a failed format: the label is a one-shot
67+
# request, and a broken run must not loop.
68+
push:
69+
needs: format
70+
if: always() && needs.format.result != 'skipped'
71+
runs-on: ubuntu-latest
72+
timeout-minutes: 10
2373
permissions:
2474
contents: write
2575
pull-requests: write
2676
steps:
2777
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
78+
if: needs.format.outputs.changed == 'true'
2879
with:
2980
ref: ${{ github.event.pull_request.head.ref }}
3081
persist-credentials: false
31-
32-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
33-
- name: Format (biome)
34-
run: bun x @biomejs/biome check --write .
35-
36-
- name: Commit and push changes
82+
# The patch lands OUTSIDE the checkout so nothing in the work tree can shadow or stage it.
83+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
84+
if: needs.format.outputs.changed == 'true'
85+
with:
86+
name: format-patch
87+
path: ${{ runner.temp }}/format
88+
- name: Commit and push the formatting
89+
if: needs.format.outputs.changed == 'true'
3790
env:
3891
GH_TOKEN: ${{ github.token }}
3992
HEAD_REF: ${{ github.event.pull_request.head.ref }}
93+
HEAD_SHA: ${{ needs.format.outputs.head }}
4094
run: |
41-
if git diff --quiet; then
42-
echo "nothing to format"
43-
else
44-
git config user.name "github-actions[bot]"
45-
git config user.email "github-actions[bot]@users.noreply.github.com"
46-
git add -A
47-
git commit -m "style: apply automated formatting"
48-
git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:"$HEAD_REF"
95+
# The branch can move between the two jobs; the patch was cut on HEAD_SHA and formats that tree alone, so a
96+
# newer head gets its own run (re-apply the label) instead of a commit formatting the old one.
97+
if [ "$(git rev-parse HEAD)" != "$HEAD_SHA" ]; then
98+
echo "::notice::head moved since the format; skipping the stale formatting push"
99+
exit 0
49100
fi
50-
# The label is a one-shot request: remove it even when a step failed,
51-
# so a broken run cannot loop.
101+
git apply --index --binary "$RUNNER_TEMP/format/format.patch"
102+
git config user.name "github-actions[bot]"
103+
git config user.email "github-actions[bot]@users.noreply.github.com"
104+
git commit --no-verify -m "style: apply automated formatting"
105+
# The lease pins the remote to the commit the patch was cut on, so a push that lands in between rejects this one.
106+
git push --force-with-lease="refs/heads/${HEAD_REF}:${HEAD_SHA}" \
107+
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:"refs/heads/${HEAD_REF}"
52108
- name: Remove the fix-lint label
53109
if: always()
54110
env:

‎.github/workflows/checks.yml‎

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@ on:
1212
jobs:
1313
check:
1414
runs-on: ubuntu-latest
15+
timeout-minutes: 15
1516
steps:
1617
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
18+
- uses: ./.github/actions/setup
1819
with:
19-
bun-version-file: .bun-version
20-
- name: Install dependencies
21-
run: bun install --frozen-lockfile
20+
yamllint: "true"
2221
# bun test loads the fetched, gitignored OpenAPI spec and GraphQL
2322
# schema; the composite restores each from cache or fetches on a miss.
2423
- uses: ./.github/actions/fetch-test-artifacts
2524
- name: Lint (biome)
2625
run: bun run lint
26+
- name: Lint (yaml)
27+
run: bun run lint:yaml
2728
- name: Lint (architecture)
2829
run: bun run lint:arch
2930
- name: Typecheck
@@ -50,9 +51,9 @@ jobs:
5051
with:
5152
# The newest release merge can be arbitrarily far behind HEAD.
5253
fetch-depth: 0
53-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
54+
- uses: ./.github/actions/setup
5455
with:
55-
bun-version-file: .bun-version
56+
install: "false"
5657
- name: Confirm last-release-sha matches the newest release merge
5758
run: bun .github/scripts/release-pipeline.ts boundary-check
5859

@@ -63,21 +64,18 @@ jobs:
6364
timeout-minutes: 5
6465
steps:
6566
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
66-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
67+
- uses: ./.github/actions/setup
6768
with:
68-
bun-version-file: .bun-version
69+
install: "false"
6970
- name: Require the release PR to carry this cycle's anchor
7071
run: bun .github/scripts/release-pipeline.ts anchor-check
7172

7273
schema-check:
7374
runs-on: ubuntu-latest
75+
timeout-minutes: 15
7476
steps:
7577
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
76-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
77-
with:
78-
bun-version-file: .bun-version
79-
- name: Install dependencies
80-
run: bun install --frozen-lockfile
78+
- uses: ./.github/actions/setup
8179
- name: Regenerate the schema and compare
8280
run: bun run build:check
8381

@@ -87,15 +85,14 @@ jobs:
8785
# than the floor, so the PATH node is 24 and the floor's binary is handed to the consumer alone.
8886
package-smoke:
8987
runs-on: ubuntu-latest
88+
timeout-minutes: 15
9089
strategy:
9190
fail-fast: false
9291
matrix:
9392
node-version: ["22.14", "24"]
9493
steps:
9594
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
96-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
97-
with:
98-
bun-version-file: .bun-version
95+
- uses: ./.github/actions/setup
9996
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
10097
with:
10198
node-version: ${{ matrix.node-version }}
@@ -104,22 +101,17 @@ jobs:
104101
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
105102
with:
106103
node-version: 24
107-
- name: Install dependencies
108-
run: bun install --frozen-lockfile
109104
- name: Build, pack, install, import, and typecheck the library
110105
run: bun .github/scripts/package-smoke.ts
111106

112107
# Drift and permission skips are fine here (the default token cannot read admin surfaces); a CRASH is not, and an
113108
# exception never sets the result output.
114109
self-check:
115110
runs-on: ubuntu-latest
111+
timeout-minutes: 15
116112
steps:
117113
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
118-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
119-
with:
120-
bun-version-file: .bun-version
121-
- name: Install dependencies
122-
run: bun install --frozen-lockfile --ignore-scripts
114+
- uses: ./.github/actions/setup
123115
- name: Build the action bundle
124116
run: bun run build:bundle
125117
- name: Run settings-as-code (check mode)
@@ -142,18 +134,15 @@ jobs:
142134
# to diff against, so it runs the full corpus.
143135
e2e-smoke:
144136
runs-on: ubuntu-latest
137+
timeout-minutes: 15
145138
steps:
146139
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
147140
with:
148141
fetch-depth: 0
149-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
150-
with:
151-
bun-version-file: .bun-version
142+
- uses: ./.github/actions/setup
152143
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
153144
with:
154145
node-version: 24
155-
- name: Install dependencies
156-
run: bun install --frozen-lockfile
157146
- name: Select sections from the diff
158147
id: select
159148
env:
@@ -188,18 +177,15 @@ jobs:
188177
# the selector finds nothing settings-related in the diff.
189178
endpoint-coverage:
190179
runs-on: ubuntu-latest
180+
timeout-minutes: 15
191181
steps:
192182
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
193183
with:
194184
fetch-depth: 0
195-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
196-
with:
197-
bun-version-file: .bun-version
185+
- uses: ./.github/actions/setup
198186
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
199187
with:
200188
node-version: 24
201-
- name: Install dependencies
202-
run: bun install --frozen-lockfile
203189
# Only the selector's "none" answer matters here. It runs BEFORE the spec cache/fetch so a PR that selects
204190
# nothing skips the download too.
205191
- name: Check whether the diff can affect route coverage

‎.github/workflows/copilot-setup-steps.yml‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ permissions:
2121
jobs:
2222
copilot-setup-steps:
2323
runs-on: ubuntu-latest
24+
# The most Copilot accepts for this job (its documented ceiling); the setup itself takes seconds.
2425
timeout-minutes: 59
2526
steps:
2627
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27-
28-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
29-
- name: Install dependencies (bun)
30-
run: bun install --frozen-lockfile
28+
- uses: ./.github/actions/setup

‎.github/workflows/e2e-nightly.yml‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# The nightly run of the whole curated e2e corpus (test/e2e/run.ts) plus the endpoint-coverage tripwire, against an
2+
# OpenAPI spec fetched fresh: PR CI restores a cached spec for speed, so this fetch is where upstream drift surfaces.
3+
# A red night files or updates one tracking issue through the fleet's fuzz-issue action; a green one closes it.
4+
# Repo-owned, never overwritten by sync.
5+
16
name: E2E Nightly
27

38
on:
@@ -18,16 +23,13 @@ concurrency:
1823
jobs:
1924
nightly:
2025
runs-on: ubuntu-latest
26+
timeout-minutes: 15
2127
steps:
2228
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
24-
with:
25-
bun-version-file: .bun-version
29+
- uses: ./.github/actions/setup
2630
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
2731
with:
2832
node-version: 24
29-
- name: Install dependencies
30-
run: bun install --frozen-lockfile
3133

3234
# The trimmed OpenAPI spec is a fetched, gitignored artifact. The nightly
3335
# ALWAYS fetches it (no cache) on purpose: the fetch is the drift tripwire.

‎.github/workflows/nightly-fuzz.yml‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,10 @@ jobs:
5252
timeout-minutes: 60
5353
steps:
5454
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
55-
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
56-
with:
57-
bun-version-file: .bun-version
55+
- uses: ./.github/actions/setup
5856
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
5957
with:
6058
node-version: 24
61-
- name: Install dependencies
62-
run: bun install --frozen-lockfile
6359

6460
# The trimmed OpenAPI spec is a fetched, gitignored artifact the mock
6561
# validates responses against. Always fetched fresh (no cache), same as

0 commit comments

Comments
 (0)