From 8086728e63301ffe22d198b401e5450733b82ad0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 03:07:12 +0000 Subject: [PATCH] test(ui-merge): the repo-gates wiring pins retire with their port; "no workflow runs this" pins read the root workflows GitHub runs only the repository root's .github/workflows/. 18 of the ui/scripts/__tests__ files that scan ui/.github/workflows/ ask a question of a directory that runs nowhere. - 14 files pin the wiring of a gate whose only home is objectui's repo-gates.yml: its home, triggers, path filters, install order, step names, and the header-count pin. No root workflow runs these gates; the card that ports repo-gates.yml into the root Lint UI job brings the pins back against that job. They retire with a comment naming it. What reads no workflow stays: the package.json aliases, the import-graph walks, the build-filter emission, the REQUIRED_CONTEXTS classifications, the CI page sections, and the header-count controls. The doc-snippet-types file's six cases that ran repo-gates.yml's derive and build steps under bash retire the same way. - 4 files assert that no workflow runs a gate (comment-mask, i18n dead keys, lint-rule coverage, the published-dist scan per PR). They now ask the root directory through a new helpers/root-ci.ts rootWorkflowFiles(), which refuses a scan that reads implausibly few files. The published-dist scan's "runs nightly" pin retires as roster row 23, a follow-up port. - markdown-test-inputs.mjs drops the ten `walker` declarations those workflow-directory walks produced, and the three entries the scanner no longer offers at all. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01A6SEitNngAMugGbuJHWsDS --- .../check-doc-component-types.test.ts | 143 ++----------- .../__tests__/check-doc-example-ids.test.ts | 82 +------- .../__tests__/check-doc-example-types.test.ts | 77 ++----- .../__tests__/check-doc-json-fences.test.ts | 44 +--- .../__tests__/check-doc-snippet-types.test.ts | 195 ++---------------- .../check-hand-rolled-comment-mask.test.ts | 11 +- .../__tests__/check-i18n-dead-keys.test.ts | 9 +- .../check-lint-rule-coverage.test.ts | 8 +- .../check-prompt-component-keys.test.ts | 47 +---- .../check-published-dist-tooling.test.ts | 23 +-- .../__tests__/check-readme-exports.test.ts | 49 ++--- .../check-shell-escape-residue.test.ts | 69 ++----- .../__tests__/check-skill-eval-tokens.test.ts | 94 ++------- .../__tests__/check-skill-examples.test.ts | 91 ++------ .../__tests__/check-skills-paths.test.ts | 59 +----- .../__tests__/check-vi-mock-inherit.test.ts | 62 ++---- .../check-vi-mock-specifiers.test.ts | 71 +------ .../__tests__/docs-links-workflow.test.ts | 80 +------ ui/scripts/__tests__/helpers/root-ci.ts | 28 ++- ui/scripts/markdown-test-inputs.mjs | 33 --- 20 files changed, 230 insertions(+), 1045 deletions(-) diff --git a/ui/scripts/__tests__/check-doc-component-types.test.ts b/ui/scripts/__tests__/check-doc-component-types.test.ts index de57ff7b112..9d278484ea0 100644 --- a/ui/scripts/__tests__/check-doc-component-types.test.ts +++ b/ui/scripts/__tests__/check-doc-component-types.test.ts @@ -2096,22 +2096,17 @@ describe('objectui#7896 — every package README is inside the scan surface', () }); }); -describe('wiring — the gate is reachable and a docs-only PR starts it', () => { - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowPath = path.join(workflowDir, 'repo-gates.yml'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - - /** - * A workflow's YAML with whole-line comments removed — these headers name each - * other's scripts in prose, and a scan that counted comments would report - * duplicate homes that no file has. - */ - const yamlOf = (file: string) => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); +describe('wiring — the gate is reachable by name, and needs no install', () => { + // Retired with objectstack#25712: `has a workflow that gates pull requests, + // not just pushes`, `runs it in NO path-filtered workflow — the change that + // breaks it is docs-only`, `has exactly one home`, the install ORDER half of + // `needs no install`, and the header-count pin further down. Each read + // objectui's `repo-gates.yml`, this gate's only home, and that file runs + // nowhere under the monorepo: GitHub runs the root `.github/workflows/` + // alone, and no workflow there runs this gate. objectstack#25880 ports the + // `repo-gates.yml` gates (row 24 of the objectstack#25697 workflow roster) + // into the root `Lint UI` job and brings these pins back against that job, by + // path. it('is exposed as a root package script', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as { @@ -2120,46 +2115,9 @@ describe('wiring — the gate is reachable and a docs-only PR starts it', () => expect(pkg.scripts['check:doc-types']).toBe(`node ${SCRIPT}`); }); - it('has a workflow that gates pull requests, not just pushes', () => { - expect(fs.existsSync(workflowPath), 'a check nothing runs is not a gate').toBe(true); - const yaml = yamlOf('repo-gates.yml'); - expect(yaml).toMatch(new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}`)); - expect(yaml).toMatch(/^\s*pull_request:/m); - expect(yaml).toMatch(/^\s*push:/m); - expect(yaml).toMatch(/^\s*merge_group:/m); - }); - - it('runs it in NO path-filtered workflow — the change that breaks it is docs-only', () => { - // The whole reason this is its own workflow. `ci.yml`'s type-check job - // excludes `content/**` from the diff that decides whether its gates run, so - // a PR editing only `content/docs/**` would start this gate nowhere. - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - for (const file of workflowFiles) { - const yaml = yamlOf(file); - if (!yaml.includes(SCRIPT)) continue; - expect(yaml, `${file} runs ${SCRIPT} behind a paths-ignore — a docs-only change would not start it`).not.toMatch( - /paths-ignore:/, - ); - expect(yaml, `${file} runs ${SCRIPT} behind a paths filter — see objectui#3448`).not.toMatch(/^\s+paths:/m); - } - }); - - it('has exactly one home', () => { - expect(workflowFiles.filter((f) => yamlOf(f).includes(SCRIPT))).toEqual(['repo-gates.yml']); - }); - - it('needs no install, so it can afford to run unfiltered', () => { + it('needs no install: its import graph reaches no package', () => { // The moment this needs `pnpm install` it stops being cheap enough to run on // every PR shape, and the filter that follows is the hole. - // Since objectui#12214 the home is `repo-gates.yml`, ONE job that installs - // after its install-free gates: the property is now an ORDER -- this gate's - // step sits before the job's `pnpm install` -- and no longer an absence. - const yaml = yamlOf('repo-gates.yml'); - const gateAt = yaml.indexOf(`run: node ${SCRIPT}`); - const installAt = yaml.indexOf('pnpm install'); - expect(gateAt).toBeGreaterThan(-1); - expect(installAt === -1 || gateAt < installAt, 'the gate step moved below the install step').toBe(true); - // Walk the WHOLE static import graph, not just the gate's own first line. // objectui#6092 converted this gate's entry guard to `./invoked-as.mjs`, a // relative import — install-free, but not spelled `node:`. Asserting on the @@ -2190,77 +2148,12 @@ describe('wiring — the gate is reachable and a docs-only PR starts it', () => expect(external, `the gate's import graph reaches a package, so it needs an install: ${external}`).toEqual([]); }); - /** - * objectui#7448. The header of this workflow described its scan surface as - * "184 pages (144 `.mdx` + 40 `.md`)". Every part of that had drifted by the - * time the card was worked — the gate's own verdict line reported 188 doc - * files, and the `.md` half of the split was four short — and NO check went - * red over the whole distance, because nothing fails on a stale number written - * in a comment. That is the same lesson `UNGATED_DOCS`'s header in - * `check-doc-snippet-types.mjs` records after both halves of its own copied - * count went stale ("a pointer to the list now rather than a copy of its - * length"). - * - * Changing 184 to 188 would only have restarted that clock. This is what makes - * the class fail loudly instead: the header may state the population — which - * trees, which extensions — but never count it. `doc-fence-languages.yml` - * carries the twin of this pin in its own test file; one gate, one home, so - * each workflow's header is asserted beside its own gate rather than in a - * shared sweep that would own neither. - * - * Deliberately narrow, and narrow in the same place as the other two copies: - * a numeral qualifying a document-population noun, with at most two words - * allowed to sit between the two. Issue references (ruled out at the pattern - * level by the negative lookbehind, not by luck), `node-version`, - * `timeout-minutes` and "the fifth instance of the shape" are all numbers - * this header legitimately carries, and - * none of them rots when a page is added or deleted. It also means the header - * must not quote another header's stale literal verbatim — this pin cannot - * tell a quotation from a claim, and refusing both is the safe direction for a - * check on prose accuracy. - * - * ⭐ Those two intervening words are objectui#7888, and they are the sentence - * above implemented rather than a new rule. This pin and its twin in - * `check-doc-fence-languages.test.ts` both said "a numeral DIRECTLY - * qualifying" and both coded it as strict adjacency, so a single adjective - * defeated them. Measured: run verbatim over `check-links.yml`'s header as it - * stood on `origin/main` at `83fe6e741` — a header carrying TWO live drifted - * counts — the adjacent-only pattern reported ONE of them. It found the - * sentence about the files the published site is built from, and scored the - * one reading "holds 15 INTERNAL documents" clean — which was the count that - * had drifted furthest (15 against a measured 17), because an adjective sat - * between the numeral and the noun. The pattern below is the third copy's, - * carried here verbatim (objectui#7825, PR objectui#7885, - * `check-links-workflow.test.ts`), with the noun set unchanged; the word - * DIRECTLY is gone from the sentence above because keeping it would only have - * inverted the same gap between what this pin claims and what it does. - * - * Both twin headers were clean under BOTH patterns when this was carried - * across, so this closes a proven hole rather than a live violation. - */ - it('its header states the population and never counts it — no count can rot here', () => { - const header = headerComments(fs.readFileSync(workflowPath, 'utf8')); - - // objectui#7901 — the floor first, or the assertion below is vacuous. The - // count half cannot distinguish a header that carries no counts from a - // header this pin has stopped reading; both score `[]`. Measured when this - // was added, so it is latent rather than live: the extraction returned 4312 - // characters from this header, 10.8 times the floor. It read 4514 at 11.3 - // before objectui#7967 made this extraction strip the `#` markers: 202 of - // those characters were markers, and none of them were prose. - expect( - header.length, - 'doc-component-types.yml: the header prose this pin reads came back empty or near-empty, so it asserted over nothing', - ).toBeGreaterThan(MIN_HEADER_PROSE); - - const counts = documentCounts(header); - expect( - counts, - `doc-component-types.yml's header states a page count (${counts.join(', ')}). Nothing fails when ` + - 'it drifts, so it will. State the population — or point at the gate\u2019s own verdict line, which ' + - 'prints the live figure on every run — instead of copying a number into a comment (objectui#7448).', - ).toEqual([]); - }); + // The header-count pin (objectui#7448, widened by objectui#7888, floored by + // objectui#7901) retired with the pins above: it read `repo-gates.yml`'s + // header. The three controls below stay. They exercise `documentCounts` and + // `headerComments` on synthetic text and read no workflow, and once + // objectstack#25880 gives this gate a root home they are the controls of the + // pin that comes back against that file's header. /** * The positive control for the pin above — objectui#7914. diff --git a/ui/scripts/__tests__/check-doc-example-ids.test.ts b/ui/scripts/__tests__/check-doc-example-ids.test.ts index 96f0d717ad4..e6e8052d1b7 100644 --- a/ui/scripts/__tests__/check-doc-example-ids.test.ts +++ b/ui/scripts/__tests__/check-doc-example-ids.test.ts @@ -315,23 +315,17 @@ describe('this repository', () => { }); }); -describe('wiring — the gate is reachable and a docs-only PR starts it', () => { - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowFile = 'repo-gates.yml'; - const workflowPath = path.join(workflowDir, workflowFile); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - - /** - * A workflow's YAML with whole-line comments removed — these headers name each - * other's scripts in prose, and a scan that counted comments would report - * duplicate homes that no file has. - */ - const yamlOf = (file: string) => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); +describe('wiring — the gate is reachable by name', () => { + // Retired with objectstack#25712: `has a workflow that gates pull requests, + // not just pushes`, `runs it in NO path-filtered workflow — the change that + // breaks it is docs-only`, `has exactly one home`, `needs no install, so it + // can afford to run unfiltered`, and `carries no population count in its + // header`. Each read objectui's `repo-gates.yml`, this gate's only home, and + // that file runs nowhere under the monorepo: GitHub runs the root + // `.github/workflows/` alone, and no workflow there runs this gate. + // objectstack#25880 ports the `repo-gates.yml` gates (row 24 of the + // objectstack#25697 workflow roster) into the root `Lint UI` job and brings + // these pins back against that job, by path. it('is exposed as a root package script', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as { @@ -349,58 +343,4 @@ describe('wiring — the gate is reachable and a docs-only PR starts it', () => expect(pkg.scripts['check:doc-example-readers']).toBe('node scripts/check-doc-example-shared-reader.mjs'); expect(pkg.scripts['check:doc-types']).toBe('node scripts/check-doc-component-types.mjs'); }); - - it('has a workflow that gates pull requests, not just pushes', () => { - expect(fs.existsSync(workflowPath), 'a check nothing runs is not a gate').toBe(true); - const yaml = yamlOf(workflowFile); - expect(yaml).toMatch(new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}`)); - expect(yaml).toMatch(/^\s*pull_request:/m); - expect(yaml).toMatch(/^\s*push:/m); - expect(yaml).toMatch(/^\s*merge_group:/m); - }); - - it('runs it in NO path-filtered workflow — the change that breaks it is docs-only', () => { - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - for (const file of workflowFiles) { - const yaml = yamlOf(file); - if (!yaml.includes(SCRIPT)) continue; - expect(yaml, `${file} runs ${SCRIPT} behind a paths-ignore — a docs-only change would not start it`).not.toMatch( - /paths-ignore:/, - ); - expect(yaml, `${file} runs ${SCRIPT} behind a paths filter`).not.toMatch(/^\s+paths:/m); - } - }); - - it('has exactly one home', () => { - expect(workflowFiles.filter((f) => yamlOf(f).includes(SCRIPT))).toEqual([workflowFile]); - }); - - it('needs no install, so it can afford to run unfiltered', () => { - // Since objectui#12214 the home is `repo-gates.yml`, ONE job that installs - // after its install-free gates: the property is now an ORDER -- this gate's - // step sits before the job's `pnpm install` -- and no longer an absence. - const yaml = yamlOf(workflowFile); - const gateAt = yaml.indexOf('scripts/check-doc-example-ids.mjs'); - const installAt = yaml.indexOf('pnpm install'); - expect(gateAt).toBeGreaterThan(-1); - expect(installAt === -1 || gateAt < installAt, 'the gate step moved below the install step').toBe(true); - }); - - it('carries no population count in its header — a number in a comment has nothing that fails when it drifts', () => { - const header = fs - .readFileSync(workflowPath, 'utf8') - .split('\n') - .filter((line) => /^\s*#/.test(line)) - .map((line) => line.replace(/^\s*#\s?/, '')) - .join('\n'); - - // The floor is what makes "no counts here" a reading rather than the absence - // of one: an extraction that has stopped reading returns '' and passes. - expect(header.length, 'the header extraction read nothing, so the pin below is vacuous').toBeGreaterThan(400); - - const counts = [ - ...header.matchAll(/(? m[0].replace(/\s+/g, ' ').trim()); - expect(counts).toEqual([]); - }); }); diff --git a/ui/scripts/__tests__/check-doc-example-types.test.ts b/ui/scripts/__tests__/check-doc-example-types.test.ts index 5acef19d422..2c342f72e63 100644 --- a/ui/scripts/__tests__/check-doc-example-types.test.ts +++ b/ui/scripts/__tests__/check-doc-example-types.test.ts @@ -531,70 +531,20 @@ describe('the sibling harness is imported, not forked', () => { * the YAML as text with whole-line comments removed — the headers in this * repository name other workflows and other scripts in prose. */ -describe('wiring — the gate runs in CI, and in a workflow a docs-only pull request can start', () => { +describe('wiring — the gate is reachable by name, and documented by command', () => { const GATE = 'scripts/check-doc-example-types.mjs'; - const SIBLING = 'scripts/check-doc-snippet-types.mjs'; - const HOME = 'repo-gates.yml'; - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - - const yamlOf = (file: string): string => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); - - it('has a workflow that gates pull requests, not just pushes', () => { - expect(workflowFiles, 'the workflow directory scan returned implausibly few files').toContain(HOME); - const yaml = yamlOf(HOME); - expect(yaml, 'a check nothing runs is not a gate — objectui#8757').toContain(GATE); - expect(yaml).toContain('pull_request:'); - }); - - it('runs it in NO path-filtered workflow — an `@example` moves with the source it documents', () => { - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - for (const file of workflowFiles) { - const yaml = yamlOf(file); - if (!yaml.includes(GATE)) continue; - expect(yaml, `${file} filters paths and would miss a change this gate exists to judge`).not.toMatch( - /^\s*paths(-ignore)?:/m, - ); - } - }); - - it('lives in exactly one workflow — one gate, one home', () => { - expect(workflowFiles.filter((f) => yamlOf(f).includes(GATE))).toEqual([HOME]); - }); - - /** - * The gate's exit `2` means THE GATE COULD NOT RUN — the packages are unbuilt - * — and is neither green nor red. Invoked before its own build it would - * return exactly that on every run, which is the counterfeit shape: a step - * that runs, prints a page of text and judged nothing. - */ - it('is invoked AFTER the build, so a precondition exit is not a state CI can reach', () => { - const yaml = yamlOf(HOME); - const build = yaml.indexOf('turbo run build'); - const invoke = yaml.search(new RegExp(`run: node ${GATE.replace(/[.\\/]/g, '\\$&')}\\s*$`, 'm')); - expect(build, 'the workflow must build the packages the covered examples import').toBeGreaterThan(-1); - expect(invoke, 'the workflow must invoke the gate itself').toBeGreaterThan(-1); - expect(build, 'invoked before its own build, the gate would exit 2 on every run').toBeLessThan(invoke); - }); - /** - * Both gates block, so whichever is second is skipped when the first is red. - * Ordering the newly wired gate FIRST would let it mask an established one — - * the worse of the two directions, and the one this asserts against. - */ - it('is invoked AFTER the sibling blocking gate, so a new gate cannot mask an established one', () => { - const yaml = yamlOf(HOME); - const sibling = yaml.search(new RegExp(`run: node ${SIBLING.replace(/[.\\/]/g, '\\$&')}\\s*$`, 'm')); - const invoke = yaml.search(new RegExp(`run: node ${GATE.replace(/[.\\/]/g, '\\$&')}\\s*$`, 'm')); - expect(sibling, 'the sibling blocking gate must still be invoked').toBeGreaterThan(-1); - expect(invoke).toBeGreaterThan(-1); - expect(invoke).toBeGreaterThan(sibling); - }); + // Retired with objectstack#25712: `has a workflow that gates pull requests, + // not just pushes`, `runs it in NO path-filtered workflow`, `lives in exactly + // one workflow — one gate, one home`, `is invoked AFTER the build`, and `is + // invoked AFTER the sibling blocking gate`. Each read objectui's + // `repo-gates.yml`, this gate's only home, and that file runs nowhere under + // the monorepo: GitHub runs the root `.github/workflows/` alone, and no + // workflow there runs this gate. objectstack#25880 ports the `repo-gates.yml` + // gates (row 24 of the objectstack#25697 workflow roster) into the root `Lint + // UI` job and brings these pins back against that job, by path. The alias + // case at the bottom keeps its `package.json` half and drops the workflow + // half for the same reason. /** * objectui#3653's discipline, applied by hand here: the page that inventories @@ -622,11 +572,10 @@ describe('wiring — the gate runs in CI, and in a workflow a docs-only pull req expect(row[0]).toContain(GATE); }); - it('the root package.json alias and the workflow invoke the same script', () => { + it('the root package.json alias invokes the gate', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as { scripts: Record; }; expect(pkg.scripts['check:doc-examples']).toBe(`node ${GATE}`); - expect(yamlOf(HOME)).toContain(`run: node ${GATE}`); }); }); diff --git a/ui/scripts/__tests__/check-doc-json-fences.test.ts b/ui/scripts/__tests__/check-doc-json-fences.test.ts index 4219ff88f10..b67c0738af0 100644 --- a/ui/scripts/__tests__/check-doc-json-fences.test.ts +++ b/ui/scripts/__tests__/check-doc-json-fences.test.ts @@ -513,40 +513,18 @@ describe('main — exit codes', () => { * Wired like its two siblings, and pinned the way they pin theirs: a check no * workflow runs is not a gate (objectui#8757). */ -describe('wiring — the gate runs in CI, after the build and after its siblings', () => { +describe('wiring — the gate is reachable by name, and documented', () => { const GATE = 'scripts/check-doc-json-fences.mjs'; - const SIBLINGS = ['scripts/check-doc-snippet-types.mjs', 'scripts/check-doc-example-types.mjs']; - const HOME = 'repo-gates.yml'; - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - const yamlOf = (file: string): string => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); - const invocation = (yaml: string, script: string) => yaml.search(new RegExp(`run: node ${script.replace(/[.\\/]/g, '\\$&')}\\s*$`, 'm')); - - it('lives in exactly one workflow, which gates pull requests and filters no path', () => { - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - expect(workflowFiles.filter((f) => yamlOf(f).includes(GATE))).toEqual([HOME]); - const yaml = yamlOf(HOME); - expect(yaml).toContain('pull_request:'); - expect(yaml).not.toMatch(/^\s*paths(-ignore)?:/m); - }); - - it('is invoked after the build that produces @object-ui/types, and after both sibling gates', () => { - const yaml = yamlOf(HOME); - const build = yaml.indexOf('turbo run build'); - const invoke = invocation(yaml, GATE); - expect(build).toBeGreaterThan(-1); - expect(invoke, 'the workflow must invoke the gate itself').toBeGreaterThan(build); - for (const sibling of SIBLINGS) { - const at = invocation(yaml, sibling); - expect(at, `${sibling} must still be invoked`).toBeGreaterThan(-1); - expect(invoke, `a newly wired gate must not mask ${sibling}`).toBeGreaterThan(at); - } - }); + + // Retired with objectstack#25712: `lives in exactly one workflow, which gates + // pull requests and filters no path` and `is invoked after the build that + // produces @object-ui/types, and after both sibling gates`. Each read + // objectui's `repo-gates.yml`, this gate's only home, and that file runs + // nowhere under the monorepo: GitHub runs the root `.github/workflows/` + // alone, and no workflow there runs this gate. objectstack#25880 ports the + // `repo-gates.yml` gates (row 24 of the objectstack#25697 workflow roster) + // into the root `Lint UI` job and brings these pins back against that job, by + // path. it('has a root script and is documented on the page that inventories the gates', () => { const manifest = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); diff --git a/ui/scripts/__tests__/check-doc-snippet-types.test.ts b/ui/scripts/__tests__/check-doc-snippet-types.test.ts index 8a245f2901e..29be91f29f1 100644 --- a/ui/scripts/__tests__/check-doc-snippet-types.test.ts +++ b/ui/scripts/__tests__/check-doc-snippet-types.test.ts @@ -4,7 +4,6 @@ import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { spawnSync } from 'node:child_process'; -import { parse as parseYaml } from 'yaml'; // Plain-JS CI helper; its types are inferred from the .mjs source by // `tsconfig.scripts.json` (`allowJs`), so no `@ts-expect-error` here. @@ -1890,61 +1889,18 @@ describe('the printed build command says what it does NOT build (objectui#7795)' }); }); -describe('wiring — a script nothing runs is not a gate', () => { - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowPath = path.join(workflowDir, 'repo-gates.yml'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - - /** A workflow's YAML with whole-line comments removed — the headers in this - * repository name other workflows and other scripts in prose. */ - const yamlOf = (file: string): string => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); - - it('has a workflow that gates pull requests, not just pushes', () => { - expect(fs.existsSync(workflowPath), 'a check nothing runs is not a gate').toBe(true); - const yaml = yamlOf('repo-gates.yml'); - expect(yaml).toContain('pull_request:'); - expect(yaml).toContain(SCRIPT); - }); - - it('runs it in NO path-filtered workflow — the change that breaks a snippet is docs-only', () => { - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - for (const file of workflowFiles) { - const yaml = yamlOf(file); - if (!yaml.includes(SCRIPT)) continue; - expect(yaml, `${file} filters paths and would miss a docs-only change`).not.toMatch( - /^\s*paths(-ignore)?:/m, - ); - } - }); - - it('lives in exactly one workflow — one gate, one home', () => { - expect(workflowFiles.filter((f) => yamlOf(f).includes(SCRIPT))).toEqual(['repo-gates.yml']); - }); - - it('builds a FILTER, never the whole workspace', () => { - const yaml = yamlOf('repo-gates.yml'); - expect(yaml).toContain('--build-filter'); - expect(yaml, 'the 2026-08-16 ruling on objectui#4846 rejected a per-PR full-repo build').not.toMatch( - /run: pnpm( exec turbo run)? build\s*$/m, - ); - }); - - it('builds BEFORE it invokes the gate, so a precondition exit is not a state CI can reach', () => { - const yaml = yamlOf('repo-gates.yml'); - const build = yaml.indexOf('turbo run build'); - const invoke = yaml.search(new RegExp(`run: node ${SCRIPT.replace(/[.\\/]/g, '\\$&')}\\s*$`, 'm')); - expect(build, 'the workflow must build the packages the covered snippets import').toBeGreaterThan(-1); - expect(invoke, 'the workflow must invoke the gate itself').toBeGreaterThan(-1); - expect( - build, - 'invoked before its own build, the gate would red a healthy pull request on a precondition', - ).toBeLessThan(invoke); - }); +describe('wiring — the build filter it emits, and its name at the root', () => { + // Retired with objectstack#25712: `has a workflow that gates pull requests, + // not just pushes`, `runs it in NO path-filtered workflow — the change that + // breaks a snippet is docs-only`, `lives in exactly one workflow — one gate, + // one home`, `builds a FILTER, never the whole workspace`, and `builds BEFORE + // it invokes the gate`. Each read objectui's `repo-gates.yml`, this gate's + // only home, and that file runs nowhere under the monorepo: GitHub runs the + // root `.github/workflows/` alone, and no workflow there runs this gate. + // objectstack#25880 ports the `repo-gates.yml` gates (row 24 of the + // objectstack#25697 workflow roster) into the root `Lint UI` job and brings + // these pins back against that job, by path. The cases below hold what the + // gate emits and how it is named, and read no workflow. /** * objectui#5911 — the emitted list must be BUILDABLE, not merely accurate. @@ -1994,122 +1950,11 @@ describe('wiring — a script nothing runs is not a gate', () => { }); }); -/** - * The step's own shell, executed — objectui#6221. - * - * The defect this pins was not in the gate but in the SHELL wrapped around it: - * `echo "args=$(node …)" >> "$GITHUB_OUTPUT"` gives the step `echo`'s status, so - * a gate that exited non-zero read as a gate that named no packages, and the - * build step below it expanded to a bare `turbo run build` over the whole - * workspace — the one thing this workflow's header forbids, with no signal - * anywhere. A regex over the YAML would pin the letter of the fix; these run the - * step scripts the workflow actually carries, under the runner's own default - * shell (`bash -e {0}`), with `node` and `pnpm` shimmed so that what is measured - * is the shell's handling of a failure rather than the gate's behaviour. - */ -describe('the build-filter steps propagate failure instead of silently building everything (objectui#6221)', () => { - const stepScript = (name: string): string => { - const workflow = parseYaml(fs.readFileSync(path.join(repoRoot, '.github/workflows/repo-gates.yml'), 'utf8')) as { - jobs: Record; - }; - const step = Object.values(workflow.jobs) - .flatMap((job) => job.steps ?? []) - .find((s) => s.name === name); - expect(step?.run, `repo-gates.yml must keep a step named "${name}" with a run: script`).toBeTypeOf( - 'string', - ); - return step!.run!; - }; - - /** Run a step's `run:` script as the runner does, with the named executables shimmed. */ - const runStep = (script: string, shims: Record, env: Record = {}) => { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'doc-snippet-step-')); - const bin = path.join(dir, 'bin'); - fs.mkdirSync(bin); - for (const [name, body] of Object.entries(shims)) { - fs.writeFileSync(path.join(bin, name), body); - fs.chmodSync(path.join(bin, name), 0o755); - } - const scriptPath = path.join(dir, 'step.sh'); - fs.writeFileSync(scriptPath, script); - const githubOutput = path.join(dir, 'github_output'); - fs.writeFileSync(githubOutput, ''); - const turboArgv = path.join(dir, 'turbo_argv'); - // `bash -e {0}` is the default shell for a `run:` step on a Linux runner. - const proc = spawnSync('bash', ['-e', scriptPath], { - encoding: 'utf8', - env: { ...process.env, PATH: `${bin}:${process.env.PATH}`, GITHUB_OUTPUT: githubOutput, TURBO_ARGV: turboArgv, ...env }, - }); - return { - status: proc.status, - stderr: proc.stderr, - githubOutput: fs.readFileSync(githubOutput, 'utf8'), - turboInvoked: fs.existsSync(turboArgv), - turboArgv: fs.existsSync(turboArgv) ? fs.readFileSync(turboArgv, 'utf8').trim() : null, - }; - }; - - // objectui#12214 folded this gate into `repo-gates.yml`: its derive step is - // named after the check, and ONE build step serves the three gates that read - // built types. The empty-filter refusal moved into the derive step, because - // the shared build always carries `--filter='./packages/*'` and so can no - // longer turn an empty filter into an unfiltered workspace build. - const DERIVE = 'Doc Snippet Type Check — Derive the packages the covered snippets import'; - const BUILD = 'Build every package the gates below read'; - const failingGate = '#!/usr/bin/env bash\necho "the filter could not be derived" >&2\nexit 3\n'; - const silentGate = '#!/usr/bin/env bash\nexit 0\n'; - const healthyGate = '#!/usr/bin/env bash\necho "--filter=@object-ui/core --filter=@object-ui/react"\n'; - const recordingPnpm = '#!/usr/bin/env bash\necho "$*" > "$TURBO_ARGV"\n'; - - it('fails the filter step when the gate fails, and writes no output at all', () => { - const result = runStep(stepScript(DERIVE), { node: failingGate }); - expect(result.status, 'a failed filter must not read as a successful step').not.toBe(0); - expect(result.stderr, 'the step must say what failed, not just fail').toContain('--build-filter'); - expect(result.stderr, 'a failure the log does not annotate is a failure someone has to go looking for').toContain( - '::error::', - ); - expect( - result.githubOutput, - 'an `args=` line written after a failed gate is the empty filter that becomes an unfiltered build', - ).toBe(''); - }); - - it('writes the derived filter through unchanged when the gate succeeds', () => { - const result = runStep(stepScript(DERIVE), { node: healthyGate }); - expect(result.status).toBe(0); - expect(result.githubOutput.trim()).toBe('args=--filter=@object-ui/core --filter=@object-ui/react'); - }); - - it('refuses an empty filter in the derive step, and writes no output', () => { - const result = runStep(stepScript(DERIVE), { node: silentGate }); - expect(result.status, 'an empty filter can only mean something upstream went wrong').not.toBe(0); - expect(result.stderr, "the refusal must be the step's own, not a shell error that happens to mention a filter").toContain( - '::error::', - ); - expect(result.githubOutput).toBe(''); - }); - - it('hands turbo the derived packages as separate words, beside every package under packages/', () => { - const result = runStep(stepScript(BUILD), { pnpm: recordingPnpm }, { - SKILL_FILTER: '', - SNIPPET_FILTER: '--filter=@object-ui/core --filter=@object-ui/react', - }); - expect(result.status).toBe(0); - expect(result.turboArgv).toBe( - 'exec turbo run build --filter=./packages/* --filter=@object-ui/core --filter=@object-ui/react', - ); - }); - - it('never runs an unfiltered workspace build, even with no derived filter at all', () => { - const result = runStep(stepScript(BUILD), { pnpm: recordingPnpm }, { SKILL_FILTER: '', SNIPPET_FILTER: '' }); - expect(result.status).toBe(0); - expect(result.turboArgv).toBe('exec turbo run build --filter=./packages/*'); - }); - - it('never puts the gate back inside a command substitution whose status is discarded', () => { - expect( - stepScript(DERIVE), - 'the step status would be `echo`\'s again, and a failed gate would read as an empty filter', - ).not.toMatch(/echo\s+"?args=\$\(/); - }); -}); +// `the build-filter steps propagate failure instead of silently building +// everything (objectui#6221)` retired with objectstack#25712, all six cases. +// They ran the `run:` scripts of two `repo-gates.yml` steps under the runner's +// shell, and that file runs nowhere under the monorepo: GitHub runs the root +// `.github/workflows/` alone, and no workflow there carries those steps. +// objectstack#25880 ports the `repo-gates.yml` gates (row 24 of the +// objectstack#25697 workflow roster) into the root `Lint UI` job; it brings +// these back against the steps that derive and build the filter there, by path. diff --git a/ui/scripts/__tests__/check-hand-rolled-comment-mask.test.ts b/ui/scripts/__tests__/check-hand-rolled-comment-mask.test.ts index 492964f9f68..086f6c7e520 100644 --- a/ui/scripts/__tests__/check-hand-rolled-comment-mask.test.ts +++ b/ui/scripts/__tests__/check-hand-rolled-comment-mask.test.ts @@ -23,6 +23,7 @@ import { spansAComment, sweep, } from '../check-hand-rolled-comment-mask.mjs'; +import { rootWorkflow, rootWorkflowFiles } from './helpers/root-ci'; const scriptsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const REPO_ROOT = path.resolve(scriptsDir, '..'); @@ -249,12 +250,10 @@ describe('wiring -- a gate nobody runs is indistinguishable from a gate that pas expect(config).toContain("'scripts/**/*.test.ts'"); }); - it('needs no workflow of its own: nothing under .github names this gate', async () => { - const { readFileSync, readdirSync } = await import('node:fs'); - const dir = path.join(REPO_ROOT, '.github', 'workflows'); - const naming = readdirSync(dir).filter((f) => - readFileSync(path.join(dir, f), 'utf8').includes('check-hand-rolled-comment-mask'), - ); + it('needs no workflow of its own: nothing GitHub runs names this gate', () => { + // Asked of the root `.github/workflows/`, the only workflows GitHub runs under + // the monorepo (objectstack#25712); `ui/.github/` runs nowhere. + const naming = rootWorkflowFiles().filter((f) => rootWorkflow(f).includes('check-hand-rolled-comment-mask')); expect( naming, 'this gate is deliberately wired through the existing test job and adds no CI context; ' + diff --git a/ui/scripts/__tests__/check-i18n-dead-keys.test.ts b/ui/scripts/__tests__/check-i18n-dead-keys.test.ts index 92a37846818..fd18c517e3c 100644 --- a/ui/scripts/__tests__/check-i18n-dead-keys.test.ts +++ b/ui/scripts/__tests__/check-i18n-dead-keys.test.ts @@ -18,6 +18,7 @@ import { textFootprint, } from '../check-i18n-dead-keys.mjs'; import { analyze, collectEnKeys, readVocabulary } from '../check-i18n-call-site-keys.mjs'; +import { rootWorkflow, rootWorkflowFiles } from './helpers/root-ci'; /** * objectui#4658 — the behaviour test for `scripts/check-i18n-dead-keys.mjs`, @@ -572,10 +573,10 @@ describe('the script is wired for discovery, not for enforcement', () => { }); it('is NOT invoked by any GitHub workflow — report-only, per its own header', () => { - const workflowsDir = path.join(repoRoot, '.github/workflows'); - for (const file of fs.readdirSync(workflowsDir)) { - if (!file.endsWith('.yml') && !file.endsWith('.yaml')) continue; - const contents = fs.readFileSync(path.join(workflowsDir, file), 'utf8'); + // Asked of the root `.github/workflows/`, the only workflows GitHub runs under + // the monorepo (objectstack#25712); `ui/.github/` runs nowhere. + for (const file of rootWorkflowFiles()) { + const contents = rootWorkflow(file); expect(contents, `${file} must not run check:i18n-dead-keys`).not.toMatch(/check:i18n-dead-keys/); expect(contents, `${file} must not run check-i18n-dead-keys.mjs directly`).not.toMatch( /check-i18n-dead-keys\.mjs/, diff --git a/ui/scripts/__tests__/check-lint-rule-coverage.test.ts b/ui/scripts/__tests__/check-lint-rule-coverage.test.ts index def899b79fb..53efc3b0c5c 100644 --- a/ui/scripts/__tests__/check-lint-rule-coverage.test.ts +++ b/ui/scripts/__tests__/check-lint-rule-coverage.test.ts @@ -24,6 +24,7 @@ import { ruleCountFor, walkedFiles, } from '../check-lint-rule-coverage.mjs'; +import { rootWorkflow, rootWorkflowFiles } from './helpers/root-ci'; /** * objectui#7908 -- `eslint.config.js` resolves ZERO rules for every @@ -689,10 +690,9 @@ describe('this repository', () => { // in when it was closed `not planned`. If someone adds the step, this line // is what fails, and it points at the header paragraph that has to be // rewritten. A missing assertion would have left the claim uncheckable. - const workflows = fs - .readdirSync(path.join(repoRoot, '.github/workflows')) - .map((f) => fs.readFileSync(path.join(repoRoot, '.github/workflows', f), 'utf8')) - .join('\n'); + // Asked of the root `.github/workflows/`, the only workflows GitHub runs + // under the monorepo (objectstack#25712); `ui/.github/` runs nowhere. + const workflows = rootWorkflowFiles().map(rootWorkflow).join('\n'); expect(workflows).not.toContain('check:lint-rule-coverage'); }); }); diff --git a/ui/scripts/__tests__/check-prompt-component-keys.test.ts b/ui/scripts/__tests__/check-prompt-component-keys.test.ts index da58cd26e36..f764babcf5f 100644 --- a/ui/scripts/__tests__/check-prompt-component-keys.test.ts +++ b/ui/scripts/__tests__/check-prompt-component-keys.test.ts @@ -282,15 +282,16 @@ describe('the repository this gate guards', () => { // ── 4. wiring: a check nothing runs is not a gate ──────────────────────────── describe('wiring', () => { - const workflowDir = path.join(repoRoot, '.github/workflows'); - const yamlOf = (file: string) => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - const homes = workflowFiles.filter((f) => yamlOf(f).includes(SCRIPT)); + // Retired with objectstack#25712: `has exactly one home, and that workflow + // gates pull requests`, `runs unfiltered — the change that breaks it is a + // markdown-only change`, and the install ORDER half of `needs no install, so + // it can afford to run unfiltered`. Each read objectui's `repo-gates.yml`, + // this gate's only home, and that file runs nowhere under the monorepo: + // GitHub runs the root `.github/workflows/` alone, and no workflow there runs + // this gate. objectstack#25880 ports the `repo-gates.yml` gates (row 24 of + // the objectstack#25697 workflow roster) into the root `Lint UI` job and + // brings these pins back against that job, by path. That case's import-graph + // walk reads no workflow and stays below. it('is exposed as a root package script', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as { @@ -299,33 +300,7 @@ describe('wiring', () => { expect(pkg.scripts['check:prompt-keys']).toBe(`node ${SCRIPT}`); }); - it('has exactly one home, and that workflow gates pull requests', () => { - expect(homes.length, 'one gate, one home').toBe(1); - const yaml = yamlOf(homes[0]); - expect(yaml).toMatch(/^\s*pull_request:/m); - expect(yaml).toMatch(/^\s*push:/m); - expect(yaml).toMatch(/^\s*merge_group:/m); - }); - - it('runs unfiltered — the change that breaks it is a markdown-only change', () => { - // `ci.yml`'s type-check job decides whether to run with a diff that excludes - // `'**/*.md'`, and `.github/prompts/**` is nothing but `.md`. Behind a paths - // filter this gate would be blind to the only PR shape that can break it. - const yaml = yamlOf(homes[0]); - expect(yaml, `${homes[0]} runs ${SCRIPT} behind a paths-ignore`).not.toMatch(/paths-ignore:/); - expect(yaml, `${homes[0]} runs ${SCRIPT} behind a paths filter`).not.toMatch(/^\s+paths:/m); - }); - - it('needs no install, so it can afford to run unfiltered', () => { - // Since objectui#12214 the home is `repo-gates.yml`, ONE job that installs - // after its install-free gates: the property is now an ORDER -- this gate's - // step sits before the job's `pnpm install` -- and no longer an absence. - const yaml = yamlOf(homes[0]); - const gateAt = yaml.indexOf('scripts/check-prompt-component-keys.mjs'); - const installAt = yaml.indexOf('pnpm install'); - expect(gateAt).toBeGreaterThan(-1); - expect(installAt === -1 || gateAt < installAt, 'the gate step moved below the install step').toBe(true); - + it('needs no install: its import graph reaches no package', () => { // Walk the WHOLE static import graph, not just this gate's first line: it // imports a sibling gate, and "needs no node_modules" has to stay true of // everything it reaches, not of its own header. diff --git a/ui/scripts/__tests__/check-published-dist-tooling.test.ts b/ui/scripts/__tests__/check-published-dist-tooling.test.ts index cea6e6a39d7..d2724c5294b 100644 --- a/ui/scripts/__tests__/check-published-dist-tooling.test.ts +++ b/ui/scripts/__tests__/check-published-dist-tooling.test.ts @@ -19,6 +19,7 @@ import { packedFiles, toolingConventionFrom, } from '../check-published-dist-tooling.mjs'; +import { rootWorkflow, rootWorkflowFiles } from './helpers/root-ci'; /** * objectui#4846 — no published package's build output may carry tooling material. @@ -472,23 +473,19 @@ describe('wiring', () => { ); }); - it('runs nightly', () => { - const workflow = fs.readFileSync(path.join(repoRoot, '.github/workflows/published-dist-gate.yml'), 'utf8'); - expect(workflow).toContain('schedule:'); - expect(workflow).toContain('workflow_dispatch:'); - expect(workflow).toContain('check:published-dist'); - }); + // `runs nightly` retired with objectstack#25712. It read objectui's + // `published-dist-gate.yml`, which runs nowhere under the monorepo: GitHub + // runs the root `.github/workflows/` alone. That workflow is row 23 of the + // objectstack#25697 workflow roster, "kept, follow-up port", and the port + // brings the pin back against the root file that schedules the scan. it('is NOT wired into any per-PR workflow — the ruling forbade a per-PR full-repo build', () => { - const workflowsDir = path.join(repoRoot, '.github/workflows'); - const perPr = fs - .readdirSync(workflowsDir) - .filter((file) => file.endsWith('.yml') || file.endsWith('.yaml')) - .filter((file) => file !== 'published-dist-gate.yml') - .filter((file) => /^on:[\s\S]*?\bpull_request:/m.test(fs.readFileSync(path.join(workflowsDir, file), 'utf8'))); + // Asked of the root `.github/workflows/`, the only workflows GitHub runs + // under the monorepo (objectstack#25712); `ui/.github/` runs nowhere. + const perPr = rootWorkflowFiles().filter((file) => /^on:[\s\S]*?\bpull_request:/m.test(rootWorkflow(file))); expect(perPr.length).toBeGreaterThan(0); // the scan itself must not be empty for (const file of perPr) { - const text = fs.readFileSync(path.join(workflowsDir, file), 'utf8'); + const text = rootWorkflow(file); expect(text.includes(GATE) || text.includes('check:published-dist'), file).toBe(false); } }); diff --git a/ui/scripts/__tests__/check-readme-exports.test.ts b/ui/scripts/__tests__/check-readme-exports.test.ts index 8286160aeca..1bf87535efe 100644 --- a/ui/scripts/__tests__/check-readme-exports.test.ts +++ b/ui/scripts/__tests__/check-readme-exports.test.ts @@ -1403,52 +1403,27 @@ describe('`--list` REFUSES on an unbuilt tree instead of dying in it (objectui#9 }); }); -describe('wiring — the gate is reachable and every pull-request shape starts it', () => { - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - const workflow = fs.readFileSync(path.join(workflowDir, 'repo-gates.yml'), 'utf8'); - const uncommented = workflow - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); +describe('wiring — the gate is reachable by name, and classified', () => { + // Retired with objectstack#25712: `is run by EXACTLY ONE workflow — two homes + // is two answers`, `carries NO trigger-level path filter — a README-only PR + // must start it`, `subscribes pull_request, push, merge_group and + // workflow_dispatch`, and `builds before it judges`. Each read objectui's + // `repo-gates.yml`, this gate's only home, and that file runs nowhere under + // the monorepo: GitHub runs the root `.github/workflows/` alone, and no + // workflow there runs this gate. objectstack#25880 ports the `repo-gates.yml` + // gates (row 24 of the objectstack#25697 workflow roster) into the root `Lint + // UI` job and brings these pins back against that job, by path. The + // classification case keeps its `REQUIRED_CONTEXTS` half and drops the two + // step names it read from the workflow, for the same reason. it('is runnable by name', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); expect(pkg.scripts['check:readme-exports']).toBe('node scripts/check-readme-exports.mjs'); }); - it('is run by EXACTLY ONE workflow — two homes is two answers', () => { - const runners = workflowFiles.filter((file) => - fs.readFileSync(path.join(workflowDir, file), 'utf8').includes('check:readme-exports'), - ); - expect(runners).toEqual(['repo-gates.yml']); - }); - - it('carries NO trigger-level path filter — a README-only PR must start it', () => { - // This is the shape `ci.yml` structurally cannot see, which is the whole - // reason this gate has its own workflow. - expect(uncommented).not.toMatch(/^\s*paths(-ignore)?:/m); - }); - - it('subscribes pull_request, push, merge_group and workflow_dispatch', () => { - for (const trigger of ['pull_request:', 'push:', 'merge_group:', 'workflow_dispatch:']) { - expect(uncommented).toContain(trigger); - } - }); - it('is classified as a required context, so a Dependabot merge waits for it', () => { // Since objectui#12214 the check reports as a step of the ONE `Repo Gates` // context, which is the name the classification and the floor read. expect(REQUIRED_CONTEXTS).toContain('Repo Gates'); - expect(uncommented).toContain('name: Repo Gates'); - expect(uncommented).toContain('name: README Export Check'); - }); - - it('builds before it judges — without `dist/` the gate can only report `unbuilt`', () => { - const buildAt = uncommented.indexOf('turbo run build'); - const checkAt = uncommented.indexOf('pnpm check:readme-exports'); - expect(buildAt).toBeGreaterThan(-1); - expect(checkAt).toBeGreaterThan(buildAt); - expect(uncommented).toContain('pnpm install --frozen-lockfile'); }); }); diff --git a/ui/scripts/__tests__/check-shell-escape-residue.test.ts b/ui/scripts/__tests__/check-shell-escape-residue.test.ts index cfa32071ba8..62b89d40400 100644 --- a/ui/scripts/__tests__/check-shell-escape-residue.test.ts +++ b/ui/scripts/__tests__/check-shell-escape-residue.test.ts @@ -685,43 +685,25 @@ describe('repo state — the gate is green on this tree, over a real population' // Wiring // --------------------------------------------------------------------------- -describe('wiring — the gate is reachable and every PR shape starts it', () => { +describe('wiring — the gate is reachable by name, and classified', () => { const SCRIPT = 'scripts/check-shell-escape-residue.mjs'; - const WORKFLOW = 'repo-gates.yml'; - const CHECK_NAME = 'Shell Escape Residue Scan'; - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - - /** A workflow's YAML with whole-line comments removed — headers discuss - * `paths` and each other's scripts in prose. */ - const yamlOf = (file: string) => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); + + // Retired with objectstack#25712: `has a workflow that gates pull requests, + // not just pushes`, `subscribes merge_group — a required check that skips a + // queue build stalls it`, `runs it in NO path-filtered workflow`, `has + // exactly one home`, and `installs nothing before running the gate — the + // cheap tier, mechanically`. Each read objectui's `repo-gates.yml`, this + // gate's only home, and that file runs nowhere under the monorepo: GitHub + // runs the root `.github/workflows/` alone, and no workflow there runs this + // gate. objectstack#25880 ports the `repo-gates.yml` gates (row 24 of the + // objectstack#25697 workflow roster) into the root `Lint UI` job and brings + // these pins back against that job, by path. it('is exposed as a root package script', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); expect(pkg.scripts['check:shell-escape-residue']).toBe(`node ${SCRIPT}`); }); - it('has a workflow that gates pull requests, not just pushes', () => { - expect(fs.existsSync(path.join(workflowDir, WORKFLOW)), 'a check nothing runs is not a gate').toBe(true); - const yaml = yamlOf(WORKFLOW); - expect(yaml).toMatch(new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}`)); - expect(yaml).toMatch(/^\s*pull_request:/m); - expect(yaml).toMatch(/^\s*push:/m); - expect(yaml).toContain(`name: ${CHECK_NAME}`); - }); - - it('subscribes merge_group — a required check that skips a queue build stalls it', () => { - // objectui#3523: `main` sits behind an enforced queue, and a required - // context that never reports does not fail the queue, it hangs it until the - // ruleset's 60-minute timeout. - expect(yamlOf(WORKFLOW)).toMatch(/^\s*merge_group:/m); - }); - it('is classified as a required context — the merge_group floor DERIVES from that list', () => { // objectui#6160 / PR #6187: `merge-queue-reporting.test.ts` reads // `REQUIRED_CONTEXTS` to decide which workflows must subscribe the queue, @@ -731,31 +713,4 @@ describe('wiring — the gate is reachable and every PR shape starts it', () => // context, which is the name the classification and the floor read. expect(REQUIRED_CONTEXTS).toContain('Repo Gates'); }); - - it('runs it in NO path-filtered workflow', () => { - // The scan surface is markdown that any pull request shape can touch, and - // reporting on every shape is also what makes the check requirable. - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - for (const file of workflowFiles) { - const yaml = yamlOf(file); - if (!yaml.includes(SCRIPT)) continue; - expect(yaml, `${file} runs ${SCRIPT} behind a paths-ignore`).not.toMatch(/paths-ignore:/); - expect(yaml, `${file} runs ${SCRIPT} behind a paths filter — see objectui#3448`).not.toMatch(/^\s+paths:/m); - } - }); - - it('has exactly one home', () => { - expect(workflowFiles.filter((f) => yamlOf(f).includes(SCRIPT))).toEqual([WORKFLOW]); - }); - - it('installs nothing before running the gate — the cheap tier, mechanically', () => { - // Since objectui#12214 the home is `repo-gates.yml`, ONE job that installs - // after its install-free gates: the property is now an ORDER -- this gate's - // step sits before the job's `pnpm install` -- and no longer an absence. - const yaml = yamlOf(WORKFLOW); - const gateAt = yaml.indexOf(SCRIPT); - const installAt = yaml.indexOf('pnpm install'); - expect(gateAt).toBeGreaterThan(-1); - expect(installAt === -1 || gateAt < installAt, 'the gate step moved below the install step').toBe(true); - }); }); diff --git a/ui/scripts/__tests__/check-skill-eval-tokens.test.ts b/ui/scripts/__tests__/check-skill-eval-tokens.test.ts index b84eb360172..2048c87e1a8 100644 --- a/ui/scripts/__tests__/check-skill-eval-tokens.test.ts +++ b/ui/scripts/__tests__/check-skill-eval-tokens.test.ts @@ -361,99 +361,33 @@ describe('the real corpus', () => { }); }); -describe('wiring — the gate is reachable and a markdown-only PR starts it', () => { +describe('wiring — the gate is reachable by name, classified and documented', () => { const SCRIPT = 'scripts/check-skill-eval-tokens.mjs'; - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowPath = path.join(workflowDir, 'repo-gates.yml'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - /** - * A workflow's YAML with whole-line comments removed. Every workflow in this - * repository discusses `paths`, `paths-ignore` and its neighbours' scripts in - * prose; a scan that counted comments would report filters and duplicate homes - * that no file has. - */ - const yamlOf = (file: string) => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); + // Retired with objectstack#25712: `has a workflow that gates pull requests, + // not just pushes`, `runs the self-test too — a probe nobody runs is a probe + // that passes`, `installs nothing — the criterion is text in the checkout`, + // `runs it in NO path-filtered workflow — the scan surface is entirely + // markdown and JSON`, `has exactly one home`, and `does not run a NEIGHBOUR + // gate from this workflow`. Each read objectui's `repo-gates.yml`, this + // gate's only home, and that file runs nowhere under the monorepo: GitHub + // runs the root `.github/workflows/` alone, and no workflow there runs this + // gate. objectstack#25880 ports the `repo-gates.yml` gates (row 24 of the + // objectstack#25697 workflow roster) into the root `Lint UI` job and brings + // these pins back against that job, by path. The classification case keeps + // its `dependabot-merge-gate.mjs` half and drops the step name it read from + // the workflow, for the same reason. it('is exposed as a root package script', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); expect(pkg.scripts['check:skill-eval-tokens']).toBe(`node ${SCRIPT}`); }); - it('has a workflow that gates pull requests, not just pushes', () => { - expect(fs.existsSync(workflowPath), 'a check nothing runs is not a gate').toBe(true); - const yaml = yamlOf('repo-gates.yml'); - expect(yaml).toMatch(new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}\\s*$`, 'm')); - expect(yaml).toMatch(/^\s*pull_request:/m); - expect(yaml).toMatch(/^\s*push:/m); - expect(yaml).toMatch(/^\s*merge_group:/m); - }); - - it('runs the self-test too — a probe nobody runs is a probe that passes', () => { - expect(yamlOf('repo-gates.yml')).toMatch( - new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}\\s+--self-test`), - ); - }); - - it('installs nothing — the criterion is text in the checkout', () => { - // The property `check-pre-install-import-graph.mjs` then enforces on the - // script: with no `pnpm install` step in the job, every step is a - // pre-install step, so the whole static import graph must stay free of - // `node_modules`. Folding this gate into `check-skill-examples.mjs` would - // have lost exactly this, since that one exits 2 on an unbuilt tree. - // Since objectui#12214 the home is `repo-gates.yml`, ONE job that installs - // after its install-free gates: the property is now an ORDER -- this gate's - // step sits before the job's `pnpm install` -- and no longer an absence. - const yaml = yamlOf('repo-gates.yml'); - const gateAt = yaml.indexOf(`run: node ${SCRIPT} --self-test`); - expect(gateAt).toBeGreaterThan(-1); - for (const marker of ['pnpm install', 'turbo run build']) { - const at = yaml.indexOf(marker); - expect(at === -1 || gateAt < at, `the gate step moved below \`${marker}\``).toBe(true); - } - expect(yaml).not.toMatch(/pnpm build/); - }); - - it('runs it in NO path-filtered workflow — the scan surface is entirely markdown and JSON', () => { - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - for (const file of workflowFiles) { - const yaml = yamlOf(file); - if (!yaml.includes(SCRIPT)) continue; - expect(yaml, `${file} runs ${SCRIPT} behind a paths-ignore — a guide-only change would not start it`).not.toMatch( - /paths-ignore:/, - ); - expect(yaml, `${file} runs ${SCRIPT} behind a paths filter — see objectui#3448`).not.toMatch(/^\s+paths:/m); - } - }); - - it('has exactly one home', () => { - expect(workflowFiles.filter((f) => yamlOf(f).includes(SCRIPT))).toEqual(['repo-gates.yml']); - }); - - it('does not run a NEIGHBOUR gate from this workflow', () => { - // `check-skill-examples.test.ts` pins that its own script lives in exactly - // one workflow. Invoking it from this YAML would break that pin and give - // one gate two homes. - // Since objectui#12214 every gate shares `repo-gates.yml`, so "this - // workflow" is now this gate's OWN steps: the ones named after its check. - const own = yamlOf('repo-gates.yml') - .split(/^(?= {6}- name: )/m) - .filter((step) => step.startsWith(' - name: Skill Eval Token Check')); - expect(own.length).toBeGreaterThan(0); - for (const step of own) expect(step).not.toContain('scripts/check-skill-examples.mjs'); - }); - it('is classified as a blocking context rather than defaulting into silence', () => { // Since objectui#12214 the check reports as a step of the ONE `Repo Gates` // context, which is the name the classification and the floor read. const gate = fs.readFileSync(path.join(repoRoot, 'scripts/dependabot-merge-gate.mjs'), 'utf8'); expect(gate).toContain("'Repo Gates'"); - expect(yamlOf('repo-gates.yml')).toContain('name: Skill Eval Token Check'); }); it('has a section on the CI page, named by heading', () => { diff --git a/ui/scripts/__tests__/check-skill-examples.test.ts b/ui/scripts/__tests__/check-skill-examples.test.ts index 7b6915bdabd..0bb315151e8 100644 --- a/ui/scripts/__tests__/check-skill-examples.test.ts +++ b/ui/scripts/__tests__/check-skill-examples.test.ts @@ -642,90 +642,28 @@ describe('the printed build command says what it does NOT build (objectui#7811)' }); }); -describe('wiring — the gate is reachable and a markdown-only PR starts it', () => { +describe('wiring — the gate is reachable by name, classified and documented', () => { const SCRIPT = 'scripts/check-skill-examples.mjs'; - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowPath = path.join(workflowDir, 'repo-gates.yml'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - /** - * A workflow's YAML with whole-line comments removed. Every workflow in this - * repository discusses `paths`, `paths-ignore` and its neighbours' scripts in - * prose; a scan that counted comments would report filters and duplicate homes - * that no file has. - */ - const yamlOf = (file: string) => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); + // Retired with objectstack#25712: `has a workflow that gates pull requests, + // not just pushes`, `runs the self-test too — a probe nobody runs is a probe + // that passes`, `derives its build filter from the gate rather than + // hand-maintaining one`, `runs it in NO path-filtered workflow — the scan + // surface is entirely markdown`, `has exactly one home`, and `does not run a + // NEIGHBOUR gate from this workflow`. Each read objectui's `repo-gates.yml`, + // this gate's only home, and that file runs nowhere under the monorepo: + // GitHub runs the root `.github/workflows/` alone, and no workflow there runs + // this gate. objectstack#25880 ports the `repo-gates.yml` gates (row 24 of + // the objectstack#25697 workflow roster) into the root `Lint UI` job and + // brings these pins back against that job, by path. The classification case + // keeps its `dependabot-merge-gate.mjs` half and drops the step name it read + // from the workflow, for the same reason. it('is exposed as a root package script', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); expect(pkg.scripts['check:skill-examples']).toBe(`node ${SCRIPT}`); }); - it('has a workflow that gates pull requests, not just pushes', () => { - expect(fs.existsSync(workflowPath), 'a check nothing runs is not a gate').toBe(true); - const yaml = yamlOf('repo-gates.yml'); - expect(yaml).toMatch(new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}\\s*$`, 'm')); - expect(yaml).toMatch(/^\s*pull_request:/m); - expect(yaml).toMatch(/^\s*push:/m); - expect(yaml).toMatch(/^\s*merge_group:/m); - }); - - it('runs the self-test too — a probe nobody runs is a probe that passes', () => { - expect(yamlOf('repo-gates.yml')).toMatch( - new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}\\s+--self-test`), - ); - }); - - it('derives its build filter from the gate rather than hand-maintaining one', () => { - const yaml = yamlOf('repo-gates.yml'); - expect(yaml).toContain('--build-filter'); - // ⛔ The one thing the header forbids: an unfiltered workspace build. - expect(yaml).not.toMatch(/turbo run build\s*$/m); - expect(yaml).not.toMatch(/pnpm build/); - }); - - it('runs it in NO path-filtered workflow — the scan surface is entirely markdown', () => { - // The whole reason this is its own workflow. `ci.yml` and `lint.yml` list - // `'**/*.md'` under the `paths-ignore` of their `push` trigger and GitHub - // has no per-job path filter, so a push that only edits a guide would never - // start them. That is exactly the hole #3448 (docs links), control-bytes.yml - // and skills-paths.yml were split out to close. - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - for (const file of workflowFiles) { - const yaml = yamlOf(file); - if (!yaml.includes(SCRIPT)) continue; - expect(yaml, `${file} runs ${SCRIPT} behind a paths-ignore — a guide-only change would not start it`).not.toMatch( - /paths-ignore:/, - ); - expect(yaml, `${file} runs ${SCRIPT} behind a paths filter — see objectui#3448`).not.toMatch(/^\s+paths:/m); - } - }); - - it('has exactly one home', () => { - // A second copy in a path-filtered workflow is how a gate ends up looking - // covered while the change it exists for still slips past. - expect(workflowFiles.filter((f) => yamlOf(f).includes(SCRIPT))).toEqual(['repo-gates.yml']); - }); - - it('does not run a NEIGHBOUR gate from this workflow', () => { - // `check-doc-snippet-types.test.ts` pins that its own script lives in - // exactly one workflow. This gate imports that script's harness as a MODULE, - // which is the intended reuse; invoking it from this YAML would break that - // pin and give one gate two homes. - // Since objectui#12214 every gate shares `repo-gates.yml`, so "this - // workflow" is now this gate's OWN steps: the ones named after its check. - const own = yamlOf('repo-gates.yml') - .split(/^(?= {6}- name: )/m) - .filter((step) => step.startsWith(' - name: Skill Example Check')); - expect(own.length).toBeGreaterThan(0); - for (const step of own) expect(step).not.toContain('scripts/check-doc-snippet-types.mjs'); - }); - it('is classified as a blocking context rather than defaulting into silence', () => { // `dependabot-merge-gate.test.ts` asserts its three buckets partition the // produced check-run set exactly, and `merge-queue-reporting.test.ts` @@ -735,7 +673,6 @@ describe('wiring — the gate is reachable and a markdown-only PR starts it', () // context, which is the name the classification and the floor read. const gate = fs.readFileSync(path.join(repoRoot, 'scripts/dependabot-merge-gate.mjs'), 'utf8'); expect(gate).toContain("'Repo Gates'"); - expect(yamlOf('repo-gates.yml')).toContain('name: Skill Example Check'); }); it('has a section on the CI page, named by heading', () => { diff --git a/ui/scripts/__tests__/check-skills-paths.test.ts b/ui/scripts/__tests__/check-skills-paths.test.ts index 5d9df329997..dc0dec8a1f9 100644 --- a/ui/scripts/__tests__/check-skills-paths.test.ts +++ b/ui/scripts/__tests__/check-skills-paths.test.ts @@ -506,62 +506,23 @@ describe('objectui#3713 / #3730 — the guide those two rounds corrected by hand }); }); -describe('wiring — the gate is reachable and a markdown-only PR starts it', () => { +describe('wiring — the gate is reachable by name, beside its baseline', () => { const SCRIPT = 'scripts/check-skills-paths.mjs'; - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowPath = path.join(workflowDir, 'repo-gates.yml'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - - /** - * A workflow's YAML with whole-line comments removed — this file's own header - * discusses `paths` and `paths-ignore` in prose, and `docs-links.yml` / - * `control-bytes.yml` name each other's scripts in theirs. A scan that counted - * comments would report filters and duplicate homes that no file has. - */ - const yamlOf = (file: string) => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); + + // Retired with objectstack#25712: `has a workflow that gates pull requests, + // not just pushes`, `runs it in NO path-filtered workflow — the scan surface + // is entirely markdown`, and `has exactly one home`. Each read objectui's + // `repo-gates.yml`, this gate's only home, and that file runs nowhere under + // the monorepo: GitHub runs the root `.github/workflows/` alone, and no + // workflow there runs this gate. objectstack#25880 ports the `repo-gates.yml` + // gates (row 24 of the objectstack#25697 workflow roster) into the root `Lint + // UI` job and brings these pins back against that job, by path. it('is exposed as a root package script', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); expect(pkg.scripts['check:skills-paths']).toBe(`node ${SCRIPT}`); }); - it('has a workflow that gates pull requests, not just pushes', () => { - expect(fs.existsSync(workflowPath), 'a check nothing runs is not a gate').toBe(true); - const yaml = yamlOf('repo-gates.yml'); - expect(yaml).toMatch(new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}`)); - expect(yaml).toMatch(/^\s*pull_request:/m); - expect(yaml).toMatch(/^\s*push:/m); - }); - - it('runs it in NO path-filtered workflow — the scan surface is entirely markdown', () => { - // The whole reason this is its own workflow. `ci.yml` lists `'**/*.md'` under - // the `paths-ignore` of its `push` trigger and GitHub has no per-job path - // filter, so a push that only edits guides would never start it. That is - // exactly the hole #3448 (docs links) and control-bytes.yml were split out - // to close; a gate whose entire surface is markdown cannot afford to - // rebuild it. - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - for (const file of workflowFiles) { - const yaml = yamlOf(file); - if (!yaml.includes(SCRIPT)) continue; - expect(yaml, `${file} runs ${SCRIPT} behind a paths-ignore — a guide-only change would not start it`).not.toMatch( - /paths-ignore:/, - ); - expect(yaml, `${file} runs ${SCRIPT} behind a paths filter — see objectui#3448`).not.toMatch(/^\s+paths:/m); - } - }); - - it('has exactly one home', () => { - // A second copy in a path-filtered workflow is how a gate ends up looking - // covered while the change it exists for still slips past. - expect(workflowFiles.filter((f) => yamlOf(f).includes(SCRIPT))).toEqual(['repo-gates.yml']); - }); - it('ships its baseline next to the script', () => { expect(fs.existsSync(path.join(repoRoot, BASELINE_FILE))).toBe(true); }); diff --git a/ui/scripts/__tests__/check-vi-mock-inherit.test.ts b/ui/scripts/__tests__/check-vi-mock-inherit.test.ts index 9687cdc96bf..c5d295de83c 100644 --- a/ui/scripts/__tests__/check-vi-mock-inherit.test.ts +++ b/ui/scripts/__tests__/check-vi-mock-inherit.test.ts @@ -1104,62 +1104,24 @@ describe('repo state — the gate is green on this tree', () => { // Wiring // --------------------------------------------------------------------------- -describe('wiring — the gate is reachable and every PR shape starts it', () => { +describe('wiring — the gate is reachable by name', () => { const SCRIPT = 'scripts/check-vi-mock-inherit.mjs'; - const WORKFLOW = 'repo-gates.yml'; - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - - /** A workflow's YAML with whole-line comments removed — see the sibling suite. */ - const yamlOf = (file: string) => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); + + // Retired with objectstack#25712: `has a workflow that gates pull requests, + // not just pushes`, `subscribes merge_group — a required check that skips a + // queue build stalls it`, `runs it in NO path-filtered workflow`, `has + // exactly one home`, and `installs nothing before running the gate — the + // cheap tier, mechanically`. Each read objectui's `repo-gates.yml`, this + // gate's only home, and that file runs nowhere under the monorepo: GitHub + // runs the root `.github/workflows/` alone, and no workflow there runs this + // gate. objectstack#25880 ports the `repo-gates.yml` gates (row 24 of the + // objectstack#25697 workflow roster) into the root `Lint UI` job and brings + // these pins back against that job, by path. it('is exposed as a root package script', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); expect(pkg.scripts['check:vi-mock-inherit']).toBe(`node ${SCRIPT}`); }); - - it('has a workflow that gates pull requests, not just pushes', () => { - expect(fs.existsSync(path.join(workflowDir, WORKFLOW)), 'a check nothing runs is not a gate').toBe(true); - const yaml = yamlOf(WORKFLOW); - expect(yaml).toMatch(new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}`)); - expect(yaml).toMatch(/^\s*pull_request:/m); - expect(yaml).toMatch(/^\s*push:/m); - }); - - it('subscribes merge_group — a required check that skips a queue build stalls it', () => { - expect(yamlOf(WORKFLOW)).toMatch(/^\s*merge_group:/m); - }); - - it('runs it in NO path-filtered workflow', () => { - // A mock can be written into any package, so no path filter is correct. - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - for (const file of workflowFiles) { - const yaml = yamlOf(file); - if (!yaml.includes(SCRIPT)) continue; - expect(yaml, `${file} runs ${SCRIPT} behind a paths-ignore`).not.toMatch(/paths-ignore:/); - expect(yaml, `${file} runs ${SCRIPT} behind a paths filter — see objectui#3448`).not.toMatch(/^\s+paths:/m); - } - }); - - it('has exactly one home', () => { - expect(workflowFiles.filter((f) => yamlOf(f).includes(SCRIPT))).toEqual([WORKFLOW]); - }); - - it('installs nothing before running the gate — the cheap tier, mechanically', () => { - // Since objectui#12214 the home is `repo-gates.yml`, ONE job that installs - // after its install-free gates: the property is now an ORDER -- this gate's - // step sits before the job's `pnpm install` -- and no longer an absence. - const yaml = yamlOf(WORKFLOW); - const gateAt = yaml.indexOf(SCRIPT); - const installAt = yaml.indexOf('pnpm install'); - expect(gateAt).toBeGreaterThan(-1); - expect(installAt === -1 || gateAt < installAt, 'the gate step moved below the install step').toBe(true); - }); }); // --------------------------------------------------------------------------- diff --git a/ui/scripts/__tests__/check-vi-mock-specifiers.test.ts b/ui/scripts/__tests__/check-vi-mock-specifiers.test.ts index f74de8a56c1..f5c56bc5c8c 100644 --- a/ui/scripts/__tests__/check-vi-mock-specifiers.test.ts +++ b/ui/scripts/__tests__/check-vi-mock-specifiers.test.ts @@ -445,71 +445,22 @@ describe('objectui#5646 — the real file the instance was written in', () => { // Wiring // --------------------------------------------------------------------------- -describe('wiring — the gate is reachable and every PR shape starts it', () => { +describe('wiring — the gate is reachable by name', () => { const SCRIPT = 'scripts/check-vi-mock-specifiers.mjs'; - const WORKFLOW = 'repo-gates.yml'; - const workflowDir = path.join(repoRoot, '.github/workflows'); - const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); - /** - * A workflow's YAML with whole-line comments removed — this workflow's header - * discusses `paths` and `paths-ignore` in prose, and the sibling cheap gates - * name each other's scripts in theirs. A scan that counted comments would - * report filters and duplicate homes that no file has. - */ - const yamlOf = (file: string) => - fs - .readFileSync(path.join(workflowDir, file), 'utf8') - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); + // Retired with objectstack#25712: `has a workflow that gates pull requests, + // not just pushes`, `subscribes merge_group — a required check that skips a + // queue build stalls it`, `runs it in NO path-filtered workflow`, `has + // exactly one home`, and `installs nothing before running the gate — the + // cheap tier, mechanically`. Each read objectui's `repo-gates.yml`, this + // gate's only home, and that file runs nowhere under the monorepo: GitHub + // runs the root `.github/workflows/` alone, and no workflow there runs this + // gate. objectstack#25880 ports the `repo-gates.yml` gates (row 24 of the + // objectstack#25697 workflow roster) into the root `Lint UI` job and brings + // these pins back against that job, by path. it('is exposed as a root package script', () => { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); expect(pkg.scripts['check:vi-mock-specifiers']).toBe(`node ${SCRIPT}`); }); - - it('has a workflow that gates pull requests, not just pushes', () => { - expect(fs.existsSync(path.join(workflowDir, WORKFLOW)), 'a check nothing runs is not a gate').toBe(true); - const yaml = yamlOf(WORKFLOW); - expect(yaml).toMatch(new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}`)); - expect(yaml).toMatch(/^\s*pull_request:/m); - expect(yaml).toMatch(/^\s*push:/m); - }); - - it('subscribes merge_group — a required check that skips a queue build stalls it', () => { - // objectui#3523: `main` sits behind an enforced queue, and a required - // context that never reports on the queue build does not fail it, it hangs - // it until the ruleset's 60-minute timeout. - expect(yamlOf(WORKFLOW)).toMatch(/^\s*merge_group:/m); - }); - - it('runs it in NO path-filtered workflow', () => { - // A mock can be written into any package, so no path filter is correct — - // and reporting on every PR shape is also what makes the check requirable. - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - for (const file of workflowFiles) { - const yaml = yamlOf(file); - if (!yaml.includes(SCRIPT)) continue; - expect(yaml, `${file} runs ${SCRIPT} behind a paths-ignore`).not.toMatch(/paths-ignore:/); - expect(yaml, `${file} runs ${SCRIPT} behind a paths filter — see objectui#3448`).not.toMatch(/^\s+paths:/m); - } - }); - - it('has exactly one home', () => { - // A second copy in a path-filtered workflow is how a gate ends up looking - // covered while the change it exists for still slips past. - expect(workflowFiles.filter((f) => yamlOf(f).includes(SCRIPT))).toEqual([WORKFLOW]); - }); - - it('installs nothing before running the gate — the cheap tier, mechanically', () => { - // Since objectui#12214 the home is `repo-gates.yml`, ONE job that installs - // after its install-free gates: the property is now an ORDER -- this gate's - // step sits before the job's `pnpm install` -- and no longer an absence. - const yaml = yamlOf(WORKFLOW); - const gateAt = yaml.indexOf(SCRIPT); - const installAt = yaml.indexOf('pnpm install'); - expect(gateAt).toBeGreaterThan(-1); - expect(installAt === -1 || gateAt < installAt, 'the gate step moved below the install step').toBe(true); - }); }); diff --git a/ui/scripts/__tests__/docs-links-workflow.test.ts b/ui/scripts/__tests__/docs-links-workflow.test.ts index 4b40d042ee6..20cce8187ec 100644 --- a/ui/scripts/__tests__/docs-links-workflow.test.ts +++ b/ui/scripts/__tests__/docs-links-workflow.test.ts @@ -42,79 +42,21 @@ import { fileURLToPath } from 'node:url'; */ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); -const workflowDir = path.join(repoRoot, '.github/workflows'); -const workflowPath = path.join(workflowDir, 'repo-gates.yml'); const SCRIPT = 'scripts/check-doc-links.mjs'; -/** - * A workflow's YAML with whole-line comments removed. - * - * Required, not cosmetic: `ci.yml` still *names* the script in the comment that - * explains why the step was removed, and this workflow's own header discusses - * `paths` / `paths-ignore` in prose. A scan that counted those would report a - * duplicate gate and a path filter that neither file has. - */ -function withoutComments(yaml: string): string { - return yaml - .split('\n') - .filter((line) => !/^\s*#/.test(line)) - .join('\n'); -} - -const workflowFiles = fs.readdirSync(workflowDir).filter((f) => f.endsWith('.yml')); +// Retired with objectstack#25712: the workflow half of `exists at all`, `runs +// the link checker`, `gates pull requests, not just pushes`, `carries NO path +// filter of any kind`, `is the only workflow that runs the link checker`, and +// `every workflow that runs it is one a docs-only PR can start`. Each read +// objectui's `repo-gates.yml`, this gate's only home, and that file runs +// nowhere under the monorepo: GitHub runs the root `.github/workflows/` alone, +// and no workflow there runs this gate. objectstack#25880 ports the +// `repo-gates.yml` gates (row 24 of the objectstack#25697 workflow roster) into +// the root `Lint UI` job and brings these pins back against that job, by path. -describe('docs-links.yml — the internal link gate is reachable', () => { +describe('the internal link checker is reachable by name', () => { it('exists at all', () => { - expect(fs.existsSync(workflowPath), 'a check nothing runs is not a gate').toBe(true); - expect(fs.existsSync(path.join(repoRoot, SCRIPT)), `${SCRIPT} must exist for the workflow to run it`).toBe(true); - }); - - it('runs the link checker', () => { - expect(withoutComments(fs.readFileSync(workflowPath, 'utf8'))).toMatch( - new RegExp(`run:\\s*node\\s+${SCRIPT.replace(/[.]/g, '\\.')}`), - ); - }); - - it('gates pull requests, not just pushes', () => { - const yaml = withoutComments(fs.readFileSync(workflowPath, 'utf8')); - expect(yaml).toMatch(/^\s*pull_request:/m); - expect(yaml).toMatch(/^\s*push:/m); - }); - - it('carries NO path filter of any kind', () => { - // The whole reason this is its own workflow. `paths-ignore` would recreate - // #3448 verbatim; a `paths: content/**` filter would look tighter and buy - // nothing — the run is a checkout plus one `node` call, and the filter would - // be a second copy of the script's scan surface, free to drift from it. - const yaml = withoutComments(fs.readFileSync(workflowPath, 'utf8')); - expect(yaml).not.toMatch(/paths-ignore:/); - expect(yaml).not.toMatch(/^\s+paths:/m); - }); - - it('is the only workflow that runs the link checker', () => { - // Not tidiness: a second copy living in a path-filtered workflow is exactly - // how a gate ends up looking covered while the docs-only PR still slips - // past. One gate, one home — and one place to fix when it needs changing. - const runners = workflowFiles.filter((f) => - withoutComments(fs.readFileSync(path.join(workflowDir, f), 'utf8')).includes(SCRIPT), - ); - expect(runners).toEqual(['repo-gates.yml']); - }); - - it('every workflow that runs it is one a docs-only PR can start', () => { - // States the invariant rather than the file name, so it still holds if the - // gate is ever moved or renamed: whoever runs this check must be startable - // by a PR that touches nothing but markdown. - expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5); - - for (const file of workflowFiles) { - const yaml = withoutComments(fs.readFileSync(path.join(workflowDir, file), 'utf8')); - if (!yaml.includes(SCRIPT)) continue; - expect(yaml, `${file} runs ${SCRIPT} behind a paths-ignore — a docs-only PR would not start it`).not.toMatch( - /paths-ignore:/, - ); - expect(yaml, `${file} runs ${SCRIPT} behind a paths filter — see objectui#3448`).not.toMatch(/^\s+paths:/m); - } + expect(fs.existsSync(path.join(repoRoot, SCRIPT)), `${SCRIPT} must exist for a workflow to run it`).toBe(true); }); it('is runnable locally under the name the docs give', () => { diff --git a/ui/scripts/__tests__/helpers/root-ci.ts b/ui/scripts/__tests__/helpers/root-ci.ts index eeba4613d51..f321cf149da 100644 --- a/ui/scripts/__tests__/helpers/root-ci.ts +++ b/ui/scripts/__tests__/helpers/root-ci.ts @@ -8,7 +8,9 @@ * `lint-ui`), and every objectui command in them is spelled * `pnpm --dir ui run `. A pin in this directory that asks "does CI run * this gate, and in the right place" asks it of that file, by path, through - * this module. It never reads a copy under `ui/.github/`: nothing runs one. + * this module, and a pin that asks whether ANY workflow runs something asks + * the root `.github/workflows/` directory through `rootWorkflowFiles()`. It + * never reads a copy under `ui/.github/`: nothing runs one. * * What the port did not carry (the coverage lane, the docs job, the shard * aggregate, the affected-test and type-check-scope selectors, the timings @@ -28,14 +30,36 @@ export const UI_JOBS = { buildE2e: 'build-e2e-ui', } as const; +/** The directory GitHub runs: `.github/workflows/` at the workspace root. */ +export const rootWorkflowsDir = path.join(workspaceRoot(), '.github/workflows'); + /** The workflow GitHub runs: `.github/workflows/ci.yml` at the workspace root. */ -export const rootCiPath = path.join(workspaceRoot(), '.github/workflows/ci.yml'); +export const rootCiPath = path.join(rootWorkflowsDir, 'ci.yml'); /** That workflow's text. */ export function rootCi(): string { return fs.readFileSync(rootCiPath, 'utf8'); } +/** + * Every workflow file in that directory, by name. Throws on a scan that reads + * implausibly few, the reading that would make every "no workflow names this" + * pin vacuous. + */ +export function rootWorkflowFiles(): string[] { + const files = fs + .readdirSync(rootWorkflowsDir) + .filter((f) => /\.ya?ml$/.test(f)) + .sort(); + if (files.length < 10) throw new Error(`${rootWorkflowsDir}: the scan read ${files.length} workflow file(s)`); + return files; +} + +/** One root workflow's text, by file name. */ +export function rootWorkflow(file: string): string { + return fs.readFileSync(path.join(rootWorkflowsDir, file), 'utf8'); +} + /** How the root workflow spells one of objectui's root scripts. */ export function uiRun(alias: string): string { return `pnpm --dir ui run ${alias}`; diff --git a/ui/scripts/markdown-test-inputs.mjs b/ui/scripts/markdown-test-inputs.mjs index 2713aee1f8f..53d83147a97 100644 --- a/ui/scripts/markdown-test-inputs.mjs +++ b/ui/scripts/markdown-test-inputs.mjs @@ -778,7 +778,6 @@ export const ADJUDICATED = new Map([ // ONE package README, `packages/plugin-dashboard/README.md`, whose shard // reported success in ten seconds having run nothing. reads: ['README.md', 'apps/console/docs/UI_IMPROVEMENT_PROPOSAL.md', 'apps/console/docs/deployment.md', 'apps/console/docs/error-tracking.md', 'content/docs/**', 'packages/**'], - walker: 'not-markdown: `.github/workflows/*.yml`', }, ], [ @@ -786,7 +785,6 @@ export const ADJUDICATED = new Map([ { reads: ['content/docs/**'], notRead: ['examples/README.md'], - walker: 'not-markdown: `.github/workflows/*.yml`', }, ], [ @@ -799,7 +797,6 @@ export const ADJUDICATED = new Map([ 'scripts/__tests__/check-doc-example-types.test.ts', { reads: ['content/docs/guide/ci-cd-pipeline.md'], - walker: 'not-markdown: `.github/workflows/*.yml`', }, ], [ @@ -834,7 +831,6 @@ export const ADJUDICATED = new Map([ // two bundle pages the scanner resolves by name (`SKILL.md`, the anchor) // are fixture keys of the leg's order and anchor pins, inside the tree. reads: ['README.md', 'content/docs/**', 'packages/**', 'skills/objectui/**'], - walker: 'markdown-tree', }, ], // `./*` rather than the eight root documents it used to list, and the @@ -878,14 +874,6 @@ export const ADJUDICATED = new Map([ reads: ['content/docs/guide/ci-cd-pipeline.md'], }, ], - // Reads no markdown: opens no markdown; the `.md` literal is a message string. - [ - 'scripts/__tests__/check-i18n-dead-keys.test.ts', - { - reads: [], - walker: 'not-markdown: `.github/workflows/*.yml`', - }, - ], [ 'scripts/__tests__/check-links-workflow.test.ts', { @@ -894,14 +882,6 @@ export const ADJUDICATED = new Map([ walker: 'markdown-tree', }, ], - // Reads no markdown: fixture trees only, and the suite asserts markdown is OUT of the ESLint walk. - [ - 'scripts/__tests__/check-lint-rule-coverage.test.ts', - { - reads: [], - walker: 'not-markdown: the files ESLint walks — the suite asserts `.md` is OUT of that walk', - }, - ], [ 'scripts/__tests__/check-new-cross-file-line-citations.test.ts', { @@ -930,27 +910,18 @@ export const ADJUDICATED = new Map([ walker: 'not-markdown: `.github/workflows/*.yml`', }, ], - [ - 'scripts/__tests__/check-prompt-component-keys.test.ts', - { - reads: ['.github/prompts/component.prompt.md', '.github/prompts/engine.prompt.md', '.github/prompts/ui-library.prompt.md'], - walker: 'not-markdown: `.github/workflows/*.yml`', - }, - ], // Reads no markdown: the `README.md` literals are packed-file names inside a manifest fixture. [ 'scripts/__tests__/check-published-dist-tooling.test.ts', { reads: [], notRead: ['README.md'], - walker: 'not-markdown: `.github/workflows/*.yml`', }, ], [ 'scripts/__tests__/check-readme-exports.test.ts', { reads: ['packages/**'], - walker: 'not-markdown: `.github/workflows/*.yml`', }, ], // objectui#9502 widened this suite to read prose: the repaired carriers must @@ -967,14 +938,12 @@ export const ADJUDICATED = new Map([ 'scripts/__tests__/check-shell-escape-residue.test.ts', { reads: ['.claude/skills/**', 'AGENTS.md', 'CLAUDE.md', 'content/docs/**', 'skills/objectui/**'], - walker: 'not-markdown: `.github/workflows/*.yml`', }, ], [ 'scripts/__tests__/check-skill-eval-tokens.test.ts', { reads: ['content/docs/guide/ci-cd-pipeline.md', 'skills/objectui/**'], - walker: 'not-markdown: `.github/workflows/*.yml`', }, ], [ @@ -982,14 +951,12 @@ export const ADJUDICATED = new Map([ { reads: ['.claude/skills/**', 'content/docs/guide/ci-cd-pipeline.md', 'skills/objectui/**'], notRead: ['README.md'], - walker: 'not-markdown: `.github/workflows/*.yml`', }, ], [ 'scripts/__tests__/check-skills-paths.test.ts', { reads: ['.claude/skills/**', 'skills/objectui/**'], - walker: 'not-markdown: `.github/workflows/*.yml`', }, ], [