From 26ec948216e3f5fbb131c547a9a46c393aa217be Mon Sep 17 00:00:00 2001 From: repo-platform-sync Date: Fri, 18 Sep 2026 06:47:31 +0000 Subject: [PATCH 1/2] chore: sync repo-platform build c81f6ac15753 --- .github/repo-platform-manifest.json | 4 ++-- .github/settings.yml | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/repo-platform-manifest.json b/.github/repo-platform-manifest.json index 63c4df8b..b9b2609f 100644 --- a/.github/repo-platform-manifest.json +++ b/.github/repo-platform-manifest.json @@ -11,9 +11,9 @@ ".github/copilot-instructions.md": {"class": "mirror", "kind": "symlink", "hash": "79909577a70fc4971a7fcfd3cbec6a1af7182ca2dadc87f2979dce5e28f93ca0"}, ".github/dependabot.yml": {"class": "managed", "hash": "a9e955fa9fd485a2d4cd9c86113252a6176d841223e6846c3f091c4317675223"}, ".github/instructions/review.instructions.md": {"class": "managed", "hash": "89d5c9bc2bea185a076b37a4cefb5ffea25a37f050c4e39512b875b95e2db380"}, - ".github/repo-platform-manifest.json": {"class": "managed", "hash": null, "commit": "5c9df351f3ee2a145d49f62481f3687d49caec47"}, + ".github/repo-platform-manifest.json": {"class": "managed", "hash": null, "commit": "c81f6ac157538498b02b50ee37846e4656f6a578"}, ".github/settings.local.yml": {"class": "starter"}, - ".github/settings.yml": {"class": "managed", "hash": "4f32ad98113f837b6ba7723973a6f534af7239a092fa2faab56b41b034a9c96d"}, + ".github/settings.yml": {"class": "managed", "hash": "9a128071b1207cccdd42bb5731afbe5e7bf7aac12ad76293f27939fa571e5445"}, ".github/workflows/auto-assign.yml": {"class": "managed", "hash": "bd512e0e114675a95f42468802fc9e8bb118215f38ed7c7b70d4235a88592f54"}, ".github/workflows/auto-format.yml": {"class": "starter"}, ".github/workflows/checks.yml": {"class": "starter"}, diff --git a/.github/settings.yml b/.github/settings.yml index 440fb8f2..0a487e5c 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -46,9 +46,6 @@ labels: - name: docs-link-rot color: D4A72C description: Automated docs-site link-rot report - - name: e2e-fuzz - color: B60205 - description: Automated e2e nightly failure - name: enhancement color: a2eeef description: New feature or request From 38a42caca1208ff897326527259d444bbf953d68 Mon Sep 17 00:00:00 2001 From: Vivswan Shah <58091053+Vivswan@users.noreply.github.com> Date: Fri, 18 Sep 2026 02:41:43 -0400 Subject: [PATCH 2/2] chore(ci): drop the gates the fleet already runs and fold the e2e nightly into nightly.yml The managed ci.yml's standard-checks job runs the fleet knip and yamllint actions against the same knip.json and the managed .yamllint on every PR, so the knip and lint:yaml steps in checks.yml judged the same tree twice, at a second knip version and behind a second yamllint pin in the setup composite. Both steps go, with the yamllint input of the composite and the CI-fails-when-missing branch of lint:yaml; the check script mirrors checks.yml again. e2e-nightly.yml duplicated the shape of the nightly starter. Its corpus run and endpoint-coverage tripwire are now the e2e job of nightly.yml, in the report job's needs and folded into its red and green conditions, so a red corpus files the nightly-failure issue and the e2e-fuzz label is gone. The test of the report job evaluates the conditions with each sibling's result substituted, so a sibling left out of either side fails the suite. --- .github/actions/setup/action.yml | 16 +--- .github/settings.local.yml | 7 -- .github/workflows/checks.yml | 6 -- .github/workflows/e2e-nightly.yml | 91 ------------------- .github/workflows/nightly-fuzz.yml | 4 +- .github/workflows/nightly.yml | 58 +++++++++--- CONTRIBUTING.md | 2 +- package.json | 4 +- ...flow.test.ts => nightly-workflows.test.ts} | 71 ++++++++++++--- test/docs/repo-owned-workflows.test.ts | 35 +------ 10 files changed, 113 insertions(+), 181 deletions(-) delete mode 100644 .github/workflows/e2e-nightly.yml rename test/docs/{e2e-nightly-workflow.test.ts => nightly-workflows.test.ts} (65%) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 9a71107b..16d58fcf 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,18 +1,14 @@ # The bun setup and the dependency install of every repo-owned workflow job; the caller keeps its own checkout and # setup-node, whose options differ per job. -# install -> --ignore-scripts: scripts.prepare installs lefthook, a git hook no runner uses; "false" (any letter -# case, as GitHub compares) skips the install for a job that runs a bare script or resolves the lockfile itself -# yamllint -> "true" installs the pinned yamllint behind `bun run lint:yaml`, which fails a CI run where it is missing +# install -> --ignore-scripts: scripts.prepare installs lefthook, a git hook no runner uses; "false" (any letter +# case, as GitHub compares) skips the install for a job that runs a bare script or resolves the lockfile itself name: Set up bun -description: The pinned bun, the locked dependencies unless told not to, and yamllint for the jobs that lint YAML +description: The pinned bun, and the locked dependencies unless told not to inputs: install: description: '"false" sets up bun alone; anything else runs bun install --frozen-lockfile --ignore-scripts' default: "true" - yamllint: - description: '"true" installs the pinned yamllint for lint:yaml' - default: "false" runs: using: composite @@ -24,9 +20,3 @@ runs: if: inputs.install != 'false' shell: bash run: bun install --frozen-lockfile --ignore-scripts - # pipx ships on the ubuntu runner image with its bin directory on PATH; a venv of its own leaves the runner's - # python untouched. - - name: Install yamllint - if: inputs.yamllint == 'true' - shell: bash - run: pipx install yamllint==1.38.0 diff --git a/.github/settings.local.yml b/.github/settings.local.yml index faa0d014..83fc672a 100644 --- a/.github/settings.local.yml +++ b/.github/settings.local.yml @@ -24,13 +24,6 @@ repository: topics: github-action, repository-settings, settings-as-code, configuration-as-code, probot-settings, rulesets, branch-protection, labels private: false -labels: - # e2e-nightly.yml files its tracking issue under this label, so it must - # survive the label sync. - - name: e2e-fuzz - color: "B60205" - description: Automated e2e nightly failure - rulesets: # NARROWS the release-please module's release-tags ruleset: the fleet # declares it over `v*`, which would also freeze the moving major tag diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 1f1fc676..561ac5a5 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -16,21 +16,15 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: ./.github/actions/setup - with: - yamllint: "true" # bun test loads the fetched, gitignored OpenAPI spec and GraphQL # schema; the composite restores each from cache or fetches on a miss. - uses: ./.github/actions/fetch-test-artifacts - name: Lint (biome) run: bun run lint - - name: Lint (yaml) - run: bun run lint:yaml - name: Lint (architecture) run: bun run lint:arch - name: Typecheck run: bun run typecheck - - name: Dead code (knip) - run: bun run knip - name: Compat markers run: bun run check:compat - name: Test diff --git a/.github/workflows/e2e-nightly.yml b/.github/workflows/e2e-nightly.yml deleted file mode 100644 index 04dc1bc1..00000000 --- a/.github/workflows/e2e-nightly.yml +++ /dev/null @@ -1,91 +0,0 @@ -# The nightly run of the whole curated e2e corpus (test/e2e/run.ts) plus the endpoint-coverage tripwire, against an -# OpenAPI spec fetched fresh: PR CI restores a cached spec for speed, so this fetch is where upstream drift surfaces. -# A red night files or updates one tracking issue through the fleet's fuzz-issue action; a green one closes it. -# Repo-owned, never overwritten by sync. - -name: E2E Nightly - -on: - schedule: - # 5:28 am EST, randomly chosen. - - cron: "28 10 * * *" - workflow_dispatch: - -permissions: - contents: read - issues: write - actions: write - -concurrency: - group: e2e-nightly - cancel-in-progress: false - -jobs: - nightly: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: ./.github/actions/setup - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: 24 - - # The trimmed OpenAPI spec is a fetched, gitignored artifact. The nightly - # ALWAYS fetches it (no cache) on purpose: the fetch is the drift tripwire. - # trim-openapi.ts HARD-ERRORS when a USED_PATH is absent upstream (the - # action calls a path GitHub no longer documents at the pinned version) - - # the drift worth catching now that there is no committed artifact to diff. - # e2e-smoke caches this for per-PR speed; the nightly re-fetches so the - # tripwire runs every night regardless of a cache. - - name: Fetch the trimmed OpenAPI spec (drift tripwire) - run: bun .github/scripts/trim-openapi.ts - - - name: Curated corpus - run: bun test/e2e/run.ts - - # Tripwire: every "section.role" route in allEndpoints() must be hit at - # least once by the curated corpus. The checker runs the corpus and reads - # each run's request log, then fails naming any route no scenario reached. - - name: Endpoint-coverage tripwire - run: bun .github/scripts/check-endpoint-coverage.ts - - - name: Upload failure artifacts - if: failure() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: e2e-artifacts-${{ github.run_attempt }} - path: test/e2e/.artifacts/ - if-no-files-found: ignore - - # The same fleet action nightly-fuzz.yml files through: one open issue per - # label, the head of each artifact's report.md (title + replay block), the - # rest in the uploaded artifact it points at. - - name: File or update the e2e issue - id: file-issue - if: failure() - uses: Vivswan/repo-platform/actions/fuzz-issue@stable - with: - mode: report - label: e2e-fuzz - title: e2e nightly failures - label-description: Automated e2e nightly failure - stream: generic - artifacts-dir: test/e2e/.artifacts - artifact-name: e2e-artifacts-${{ github.run_attempt }} - - # A GITHUB_TOKEN-created issue fires no triggers, so dispatch auto-assign. - - name: Trigger auto-assign for the filed issue - if: failure() && steps.file-issue.outputs.issue-number != '' - env: - GH_TOKEN: ${{ github.token }} - ISSUE_NUMBER: ${{ steps.file-issue.outputs.issue-number }} - run: gh workflow run auto-assign.yml -f "issue=$ISSUE_NUMBER" || echo "::warning::could not dispatch auto-assign.yml" - - - name: Close the e2e issue on a green night - if: success() - uses: Vivswan/repo-platform/actions/fuzz-issue@stable - with: - mode: resolve - label: e2e-fuzz - stream: generic diff --git a/.github/workflows/nightly-fuzz.yml b/.github/workflows/nightly-fuzz.yml index 689500b9..8c66103b 100644 --- a/.github/workflows/nightly-fuzz.yml +++ b/.github/workflows/nightly-fuzz.yml @@ -11,7 +11,7 @@ on: schedule: # Off-peak, off the top of the hour, and distinct from the fleet's other # nightlies (chromium-bridge 47 4, litellm-vscode-chat 59 7, this repo's - # e2e-nightly 28 10). + # nightly.yml 47 2). - cron: "41 5 * * *" workflow_dispatch: inputs: @@ -59,7 +59,7 @@ jobs: # The trimmed OpenAPI spec is a fetched, gitignored artifact the mock # validates responses against. Always fetched fresh (no cache), same as - # e2e-nightly: the fetch doubles as an upstream-drift tripwire. + # nightly.yml's e2e job: the fetch doubles as an upstream-drift tripwire. - name: Fetch the trimmed OpenAPI spec run: bun .github/scripts/trim-openapi.ts diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index eeac86ca..4583b583 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,13 +1,15 @@ -# Nightly upstream-freshness probes. Generated from the fleet sync's nightly -# starter and never overwritten by a later sync. The checks job re-cuts the -# trimmed OpenAPI spec from upstream HEAD (a path-level staleness check) and -# typechecks against the latest @octokit/types, so pinned-dependency staleness -# fails a night instead of surprising the next manual bump; the float-canary -# job re-resolves the lockfile from scratch the way the Dependabot lockfile -# workflow does, so a breaking upstream release fails a night instead of -# reddening every open Dependabot PR; the report job below files or updates -# one label-deduplicated tracking issue on a red night and closes it on a -# green one (see the platform repository's docs/nightly.md). +# Nightly upstream-freshness probes and the whole-corpus e2e run. Generated +# from the fleet sync's nightly starter and never overwritten by a later +# sync. The checks job re-cuts the trimmed OpenAPI spec from upstream HEAD +# (a path-level staleness check) and typechecks against the latest +# @octokit/types, so pinned-dependency staleness fails a night instead of +# surprising the next manual bump; the float-canary job re-resolves the +# lockfile from scratch the way the Dependabot lockfile workflow does, so a +# breaking upstream release fails a night instead of reddening every open +# Dependabot PR; the e2e job runs the curated corpus and the endpoint-coverage +# tripwire against a freshly fetched spec; the report job below files or +# updates one label-deduplicated tracking issue on a red night and closes it +# on a green one (see the platform repository's docs/nightly.md). name: Nightly on: @@ -110,11 +112,10 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - # The gate below runs lint:yaml; the from-scratch resolve is this job's install. + # The from-scratch resolve is this job's install. - uses: ./.github/actions/setup with: install: "false" - yamllint: "true" # Delete before resolving, exactly like the Dependabot workflow: bun # keeps a stale lockfile as a valid resolution, and only a # from-scratch resolve floats what that workflow would float. @@ -130,11 +131,38 @@ jobs: - name: Run the gate on the floated resolution run: bun run check + # The whole curated corpus (PR CI runs only the sections a PR changed) plus the endpoint-coverage tripwire, + # against the pinned spec fetched fresh: PR CI restores a cached spec for speed, so this fetch is where a used + # path GitHub stopped documenting surfaces. The bundle runs under node24, so node is installed beside bun. + e2e: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: ./.github/actions/setup + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + - name: Fetch the trimmed OpenAPI spec (drift tripwire) + run: bun .github/scripts/trim-openapi.ts + - name: Curated corpus + run: bun test/e2e/run.ts + - name: Endpoint-coverage tripwire + run: bun .github/scripts/check-endpoint-coverage.ts + # The tracking issue points at the run; the failing scenarios' reports and request logs are here. + - name: Upload failure artifacts + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: e2e-artifacts-${{ github.run_attempt }} + path: test/e2e/.artifacts/ + if-no-files-found: ignore + # Runs whatever happened to the probe jobs, and treats a CANCELLED probe # as red: a job that hits its timeout is cancelled, not failed, and a # hang is exactly what a nightly run exists to catch. report: - needs: [checks, float-canary] + needs: [checks, float-canary, e2e] if: always() runs-on: ubuntu-latest # A few gh calls behind a bun setup; a hung reporter is the residual @@ -148,7 +176,7 @@ jobs: steps: - name: File or update the nightly issue id: file-issue - if: needs.checks.result != 'success' || needs.float-canary.result != 'success' + if: needs.checks.result != 'success' || needs.float-canary.result != 'success' || needs.e2e.result != 'success' uses: Vivswan/repo-platform/actions/fuzz-issue@stable # This job checks nothing out (kept tight), so gh has no git # context; GH_REPO points it at this repository (run 31563375394 @@ -178,7 +206,7 @@ jobs: run: gh workflow run auto-assign.yml -f "issue=$ISSUE_NUMBER" || echo "::warning::could not dispatch auto-assign.yml" - name: Close the nightly issue on a green night - if: needs.checks.result == 'success' && needs.float-canary.result == 'success' + if: needs.checks.result == 'success' && needs.float-canary.result == 'success' && needs.e2e.result == 'success' uses: Vivswan/repo-platform/actions/fuzz-issue@stable env: GH_REPO: ${{ github.repository }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 525dfc49..54e87a35 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,7 @@ The end-to-end tests build the bundle to a temp path and run it as a subprocess - Every section ships the standard scenario set under `src/sections//scenarios/`, named after the section's dashed key: `-apply-converges`, `-check-drift` (a section with a planning read), `-snapshot-roundtrip` (a section with snapshot()), and for a section under the undeclared policy `-undeclared-delete` and `-undeclared-keep-note`; `test/sections/scenario-set.test.ts` derives the set from the registry. - `bun run fuzz` runs seeded property fuzzing: random scenarios, each checked against an oracle that predicts the outcome class from the token mask, policy, and mode. - The mock serves the section endpoints plus the core routes the action calls outside the sections. A request that matches no registered route fails loudly; the mock never invents a response. -- PR CI runs the sections a pull request changed. Two nightly workflows run the full corpus and the full fuzz, filing issues labeled `e2e-fuzz` and `fuzz-nightly` that carry a replay command. +- PR CI runs the sections a pull request changed. The nightly workflow's `e2e` job runs the full corpus and files a red night under the `nightly-failure` issue; the fuzz nightly runs the full fuzz and files under `fuzz-nightly` with a replay command. The fuzzer is deterministic. It prints a master seed and a per-iteration seed: diff --git a/package.json b/package.json index 14766a27..206769c6 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,10 @@ "access": "public" }, "scripts": { - "check": "bun run lint && bun run lint:yaml && bun run lint:arch && bun run check:compat && bun run typecheck && bun run knip && bun run test && bun run build:check", + "check": "bun run lint && bun run lint:arch && bun run check:compat && bun run typecheck && bun run test && bun run build:check", "check:compat": "bun .github/scripts/check-compat-markers.ts", "lint": "bun x biome ci --error-on-warnings .", - "lint:yaml": "if command -v yamllint >/dev/null 2>&1; then git ls-files -z '*.yml' '*.yaml' | xargs -0 yamllint -s; elif [ -n \"$CI\" ]; then echo 'lint:yaml: yamllint is missing on this runner; the job needs ./.github/actions/setup with yamllint: \"true\"' >&2; exit 1; else echo 'lint:yaml: yamllint not installed, skipping (CI runs it; install with pip install yamllint or brew install yamllint)'; fi", + "lint:yaml": "git ls-files -z '*.yml' '*.yaml' | xargs -0 yamllint -s", "lint:fix": "bun x biome check --write .", "lint:arch": "bun .github/scripts/arch-lint.ts", "lint:package": "bun x publint --strict && bun x attw --pack . --profile esm-only", diff --git a/test/docs/e2e-nightly-workflow.test.ts b/test/docs/nightly-workflows.test.ts similarity index 65% rename from test/docs/e2e-nightly-workflow.test.ts rename to test/docs/nightly-workflows.test.ts index 5fc05bd8..4ba36769 100644 --- a/test/docs/e2e-nightly-workflow.test.ts +++ b/test/docs/nightly-workflows.test.ts @@ -1,8 +1,9 @@ /** - * Both nightlies file a failure issue through the fleet's fuzz-issue action, pointing at the artifact the run uploaded, resolve it on the - * next green night, and dispatch auto-assign.yml with the issue number. The links a rename on one side breaks with no other check noticing: - * the directory the runner writes, the artifact the issue cites, the conditions the steps run under, the step ids the expressions read, the - * label the report and the resolve share, and the input names the dispatch passes to a workflow the platform syncs. + * Both nightlies file a failure issue through the fleet's fuzz-issue action, resolve it on the next green night, and dispatch + * auto-assign.yml with the issue number. The links a rename on one side breaks with no other check noticing: the directory the + * runner writes and the artifact the fuzz issue cites, the conditions the steps run under, the step ids the expressions read, the + * label the report and the resolve share, the input names the dispatch passes to a workflow the platform syncs, and in nightly.yml + * the sibling jobs the report job's red and green conditions fold in. */ import { describe, expect, test } from "bun:test"; @@ -13,10 +14,8 @@ import { readWorkflow, type Step } from "./workflow-loader.js"; const FUZZ_ISSUE_ACTION = "Vivswan/repo-platform/actions/fuzz-issue@stable"; -const NIGHTLIES: ReadonlyArray<[file: string, job: string]> = [ - ["e2e-nightly.yml", "nightly"], - ["nightly-fuzz.yml", "fuzz"], -]; +/** The nightlies whose one job runs the checks and files the issue; nightly.yml's report job is judged on its own below. */ +const NIGHTLIES: ReadonlyArray<[file: string, job: string]> = [["nightly-fuzz.yml", "fuzz"]]; const filerIn = (steps: Step[], mode: string) => steps.find((s) => s.uses === FUZZ_ISSUE_ACTION && s.with?.mode === mode); @@ -133,10 +132,60 @@ describe.each(NIGHTLIES)("%s failure path", (file, job) => { }); }); +/** + * A job condition over `needs..result` evaluated the way the runner does, with each job's result substituted; the remaining text is + * checked to be nothing but string literals and the ==, !=, &&, ||, ! and parenthesis operators before it runs. + */ +function evaluate(raw: unknown, results: Record): boolean { + const expression = condition(raw).replace(/\bneeds\.([\w-]+)\.result\b/g, (_, job: string) => { + const result = results[job]; + if (result === undefined) + throw new Error(`the condition reads needs.${job}, which is not in needs`); + return JSON.stringify(result); + }); + if (!/^(?:"[a-z]*"|'[a-z]*'|==|!=|&&|\|\||!|[()\s])+$/.test(expression)) { + throw new Error(`the condition uses more than the evaluator knows: ${expression}`); + } + return Boolean(new Function(`return (${expression});`)()); +} + +describe("nightly.yml report job", () => { + const workflow = readWorkflow("nightly.yml"); + const report = workflow.jobs.report; + const steps = report?.steps ?? []; + const siblings = Object.keys(workflow.jobs).filter((job) => job !== "report"); + const red = filerIn(steps, "report")?.if; + const green = filerIn(steps, "resolve")?.if; + const night = (job: string, result: string) => + Object.fromEntries(siblings.map((sibling) => [sibling, sibling === job ? result : "success"])); + + test("every sibling job is in its needs, and a sibling that is not green files the issue and does not close it", () => { + // GitHub enforces neither (docs/nightly.md in the platform repository): a sibling outside `needs` never reaches the report, and a + // result the conditions do not fold in is a red night the green branch closes, or one that matches neither side and files nothing. + const needs = report?.needs; + expect(Array.isArray(needs) ? [...needs].sort() : needs).toEqual([...siblings].sort()); + const verdicts = siblings.flatMap((job) => + ["failure", "cancelled", "skipped"].map((result) => ({ + night: `${job} ${result}`, + files: evaluate(red, night(job, result)), + closes: evaluate(green, night(job, result)), + })), + ); + expect(verdicts).toEqual( + verdicts.map(({ night: name }) => ({ night: name, files: true, closes: false })), + ); + expect(evaluate(red, night("", ""))).toBe(false); + expect(evaluate(green, night("", ""))).toBe(true); + }); +}); + test("the nightlies file under distinct labels, so one's green night cannot close the other's issue", () => { - const labels = NIGHTLIES.map( - ([file, job]) => filerIn(readWorkflow(file).jobs[job]?.steps ?? [], "report")?.with?.label, - ); + const labels = [ + ...NIGHTLIES.map( + ([file, job]) => filerIn(readWorkflow(file).jobs[job]?.steps ?? [], "report")?.with?.label, + ), + filerIn(readWorkflow("nightly.yml").jobs.report?.steps ?? [], "report")?.with?.label, + ]; // GitHub compares label names without regard to case. expect(new Set(labels.map((label) => String(label).toLowerCase())).size).toBe(labels.length); }); diff --git a/test/docs/repo-owned-workflows.test.ts b/test/docs/repo-owned-workflows.test.ts index 856aa6d5..da159293 100644 --- a/test/docs/repo-owned-workflows.test.ts +++ b/test/docs/repo-owned-workflows.test.ts @@ -1,21 +1,11 @@ /** - * Two properties of the repo-owned workflows a later edit breaks with no other check noticing: the commit-back push - * jobs run no PR code under their write token and push only over the head they patched, and lint:yaml never skips - * in CI. + * A property of the repo-owned commit-back workflows a later edit breaks with no other check noticing: the push jobs + * run no PR code under their write token and push only over the head they patched. */ import { describe, expect, test } from "bun:test"; -import { execFileSync } from "node:child_process"; -import { readFileSync } from "node:fs"; -import { join } from "node:path"; -import { ROOT } from "../root.js"; import { readWorkflow, type Step } from "./workflow-loader.js"; -const SCRIPTS = ( - JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")) as { - scripts: Record; - } -).scripts; /** A runtime or package manager at a command position: each reads the checkout's manifest or scripts and runs what it finds there. */ const RUNS_CHECKOUT = /(?:^|[\s;&|(])(?:bun|bunx|node|npm|npx|pnpm|yarn|deno|tsx)(?=\s|$)/m; /** The script with its quoted strings blanked, so a word inside an echo is not read as a command. */ @@ -60,24 +50,3 @@ describe("the commit-back push jobs", () => { }, ); }); - -describe("lint:yaml", () => { - /** The script's exit status from the repository root with PATH cut to the system directories, where yamllint is absent. */ - function lintYamlWithout(env: Record): number { - try { - execFileSync("bash", ["-c", SCRIPTS["lint:yaml"] ?? ""], { - cwd: ROOT, - stdio: "pipe", - env: { HOME: process.env.HOME ?? "", ...env, PATH: "/usr/bin:/bin" }, - }); - return 0; - } catch (error) { - return (error as { status?: number }).status ?? -1; - } - } - - test("without yamllint a CI run fails and a local run skips", () => { - expect(lintYamlWithout({ CI: "true" })).toBe(1); - expect(lintYamlWithout({})).toBe(0); - }); -});