Skip to content

Commit 6e0597f

Browse files
test(ui-merge): the ui gate-wiring pins read the root ci.yml that runs them; pins on jobs it did not port retire (#25879)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoAqVfFyhsxKuFcEP9YTBj25)_ Part of #25712 (4b PR 2d-1: the `ui/scripts/__tests__` files that read `ui/.github/workflows/ci.yml` read the root ci.yml instead, or retire). Part of objectstack#25677. Clause-②: no Metadata-protocol: none ## 维护者速读(草稿) - **改了什么:** `ui/scripts/__tests__` 里有 28 个测试文件读的是嵌套的 `ui/.github/workflows/ci.yml`,而这个文件在 monorepo 里根本不会运行(GitHub 只运行仓库根目录的 `.github/workflows/`)。本 PR 新增一个小 helper,按路径读根目录的 `ci.yml`。17 个文件改为断言根 `ci.yml` 里真正运行的 `type-check-ui` / `test-ui` / `build-e2e-ui` 任务;另外 11 个文件里,凡是断言对象是根目录没有移植的 objectui 任务(coverage、Build Docs、shard 汇总、timings 上传、两个选择器、turbo 缓存规则)的用例,都退役,并在原处写明由 #25697 workflow 清单的哪一行承接。其中 4 个文件没有别的内容,整文件删除。 - **为什么改:** 2d-3 要删除 `ui/.github/`。删之前这 28 个文件必须先不依赖它,否则删除时会红 28 个文件。改完以后,无论 `ui/.github/` 在不在,这 28 个文件都是绿的。 - **风险与代价(含回滚):** 只动测试和一个测试 ledger,不动任何会运行的代码、workflow 或受管面。退役的 pin 不会悄悄消失:每一条都列在下面的 follow-up 表里。其中一条是真问题:根目录 `type-check-ui` 的 Turbo 缓存保存没有 objectui#6577 的上限(把那组 pin 指向根任务,6 条里 5 条红)。按 PM 的裁定不在本 PR 里改根 workflow,只列为 follow-up。回滚 = revert 本 PR。 - **席位意见:**(留空) - **你要做的:** 无。Tier S,由席位落地。 ## 席位核对 (left for the dispatch seat) ## Why GitHub runs only the repository root's `.github/workflows/`. Since the cutover (#25825), objectui's own `ci.yml` sits at `ui/.github/workflows/ci.yml` and runs nowhere. Its four jobs that matter were ported into the root `ci.yml` by #25697: `Type Check` became `type-check-ui`, `Test` became `test-ui`, `Build & E2E` became `build-e2e-ui`, and `lint.yml`'s `Lint` became `lint-ui`. The 2d census (local, `main` `e167956e` plus 2c, with `ui/.github/` deleted; per-file table below) found that deleting `ui/.github/` reds 81 `ui/scripts/__tests__` files. Thirty of them read `ui/.github/workflows/ci.yml`. Two of those thirty, `markdown-test-inputs.test.ts` and `select-affected-tests-wiring.test.ts`, belong to #25851 and are not touched here. This PR is the other 28. The 51 files that scan the workflow directory or read other single workflows are 2d-2 and 2d-3. The rule for each pin, per the dispatcher's ruling: retarget it to the root workflow by path, or, when the root did not port its subject, retire it and name the roster row (#25697, comment 6388182608) or the root job that now carries the subject. Nothing is skipped, and nothing is folded into a root workflow here; work worth folding is listed as follow-up rows. ## What changes - **New `ui/scripts/__tests__/helpers/root-ci.ts`.** It reads `<workspaceRoot()>/.github/workflows/ci.yml` and never a copy under `ui/`. It provides: - `jobBlock(ci, key)`, which throws on a missing job, so a renamed job is a red pin and never an empty block; - `triggerBlock` and `withoutComments`; - `uiFilterGlobs`, the `filter` job's `ui:` paths filter, which throws on an empty list; - `globMatches` and `repoPath`; - `uiRun(alias)`, which returns `pnpm --dir ui run <alias>`, the root's spelling of objectui's commands. - **17 files retargeted** to the root jobs. The ordering pins keep their meaning inside the root job: "after `pnpm install --frozen-lockfile`", "before or after turbo's `type-check` task", "`fetch-depth: 0` above the step". The three path-filter pins asserted that objectui's `push` `paths-ignore` did not hide a surface. They now assert two things: the root triggers carry no path filter at all, and the `filter` job's `ui:` globs cover each surface the gate judges, which is what the job's `if:` reads. - **11 files retire pins on jobs the root did not port.** Each retirement is a comment where the pin was, naming its row. Four files had nothing else and are deleted: `coverage-shard-reporter-readability`, `coverage-shard-width`, `docs-build-trigger` and `turbo-cache-save-bound`. The pure-module cases in the other seven stay, for example `check-test-shard-results`'s budget sizing, which reads only constants. - **`ui/scripts/markdown-test-inputs.mjs`** drops two ledger rows: `docs-build-trigger.test.ts` (deleted) and `check-action-forward-parity.test.ts`, whose only markdown literal (`content/docs/guide/ci-cd-pipeline.md`, an argument to the old matcher self-check) went with the retargeted pin. `markdown-test-inputs.test.ts` itself is not touched. Without these two removals it reports `stale-test`. ### Disposition table (28 files) | # | File | What it read in `ui/.github/workflows/ci.yml` | Disposition | Carried by now | |---|---|---|---|---| | 1 | `check-action-forward-parity` | step after install; `push` `paths-ignore` | retarget | `type-check-ui`; no trigger path filter; `ui:` globs cover every `SURFACES` file | | 2 | `check-doc-example-shared-reader` | the step | retarget | `type-check-ui` | | 3 | `check-handler-key-read-sites` | the step | retarget | `type-check-ui` | | 4 | `check-i18n-call-site-keys` | step after install | retarget | `type-check-ui` | | 5 | `check-i18n-designer-table-parity` | step after install; `paths-ignore` | retarget | `type-check-ui`; `ui:` globs cover the designer table and the gate | | 6 | `check-i18n-en-drift` | step after install; `fetch-depth: 0`; `paths-ignore` | retarget | `type-check-ui` (`fetch-depth: 0`); `ui:` globs cover the 10 packs, the ledger and the designer table | | 7 | `check-lucide-icon-record-names` | `pnpm <alias>` anywhere | retarget | root ci.yml `run: pnpm --dir ui run <alias>` | | 8 | `check-object-metadata-write-doors` | the step | retarget | `type-check-ui` | | 9 | `check-package-self-import` | step after install, in `type-check` | retarget | `type-check-ui` | | 10 | `check-phantom-dependencies` | step after install, in `type-check` | retarget | `type-check-ui` | | 11 | `check-unreferenced-sources` | step after install, in `type-check` | retarget | `type-check-ui` | | 12 | `check-published-tsconfig-tooling-exclude` | the step; `published-dist-gate.yml` | retarget; workflow half retired | `type-check-ui`; `published-dist-gate.yml` is roster **row 23** (kept, follow-up port) | | 13 | `check-pending-changeset-literals` | the `Decide whether this change needs a full run` exclude pathspecs | retarget | `test-ui` reads the `ui` output; `ui:` globs cover `scripts/` and `packages/` test sources | | 14 | `scripts-type-check` | `type-check` job, step after install | retarget | `type-check-ui` | | 15 | `vitest-config-type-check` | after install, before `pnpm type-check` | retarget | `type-check-ui`, before `pnpm exec turbo run type-check` | | 16 | `vitest-setup-type-check` | after `pnpm type-check` | retarget | `type-check-ui`, after `pnpm exec turbo run type-check` | | 17 | `ensure-chromium-ready` | `ci.yml` and `live-e2e.yml` use the script, no `--with-deps` | retarget; `live-e2e` half retired | `build-e2e-ui`; `live-e2e.yml` is roster **row 17** (kept, follow-up port) | | 18 | `check-doc-expression-carriage` | `node scripts/check-doc-expression-carriage.mjs` in `docs` | 1 case retired | roster **row 7 `Build Docs (@object-ui/site)`** (kept, follow-up port); the page-row pin stays | | 19 | `check-test-shard-results` | `test-aggregate` wiring; the `Test (shard N/M)` name | 8 cases retired, budget case kept | roster **row 7 `Test (the shard aggregate)`** (dropped until cutover; port with objectstack's shard attestation) | | 20 | `shard-headroom` | headroom step in `test-aggregate` and `coverage-report` | 2 cases retired | roster **row 7** aggregate and coverage | | 21 | `test-file-timings` | `test` job name, timing reporter, upload; refresh `--shards` | 4 cases retired | roster **row 28** `test-shard-timings-refresh.yml` (→ sub-issue 4; "restore Test UI's timings reporter and upload"); `test-ui`'s comment records the wait | | 22 | `select-affected-tests` | the `Select the tests this change affects` wiring | 4 cases retired | roster **row 7 `Test (shard n/16)`**: `test-ui` runs the whole suite and does not wire the selector | | 23 | `select-type-check-scope` | `tc-scope` step, `TURBO_FORCE`, root-programs build | 5 cases retired | roster **row 7 `Type Check`**: `type-check-ui` uses turbo `...[base]` and its own root-programs build step | | 24 | `report-turbo-cache` | `type-check` prune and save; every workflow's turbo cache | 7 cases retired | the four root `* UI` jobs carry objectstack's cache steps (job key, `build-core` fallback, push-only save, no prune or report); follow-up **F2** | | 25 | `coverage-shard-reporter-readability` (deleted, 4 cases) | `test-coverage` reporter flags | retired | roster **row 7 coverage** (kept, follow-up port) | | 26 | `coverage-shard-width` (deleted, 5 cases) | `test-coverage` and `coverage-report` width | retired | roster **row 7 coverage** (kept, follow-up port) | | 27 | `docs-build-trigger` (deleted, 17 cases) | `docs` job's `docs-changes` decision step | retired | roster **row 7 `Build Docs`** (kept, follow-up port) | | 28 | `turbo-cache-save-bound` (deleted, 6 cases) | `type-check`'s split, bounded cache save | retired | `type-check-ui` carries the save but not the bound; measured below; follow-up **F1** | ### Follow-up rows (fold-worthy, not done here) | Row | What | Why it is not in this PR | |---|---|---| | **F1** | Bound the root `* UI` jobs' Turbo cache saves the objectui#6577 / #7048 way: split restore and save, `timeout-minutes`, `continue-on-error`, the `cache-hit` guard, and a ceiling that leaves the verdict room. Then restore `turbo-cache-save-bound`'s pins against `type-check-ui`. | **A real finding.** Its six pins, pointed at the root `type-check-ui` job by a scratch copy, fail 5 of 6. Only "save after the last command" holds. The other five fail because the job keeps a combined `actions/cache` (the pnpm store), its save has no bound, `continue-on-error` or `cache-hit` guard, and its ceiling is 40 minutes. Fixing it is a root `ci.yml` edit (Tier H), and the dispatcher ruled no folds into root workflows in these PRs. | | F2 | Decide whether the root ui jobs run `report-turbo-cache.mjs` (prune before save, restored-key notice), or retire the script. | Root cache design is objectstack's; a fold. | | F3 | Test UI shard aggregate at the required set: port with objectstack's shard attestation, or retire `check-test-shard-results.mjs` and `shard-headroom.mjs`. | Roster row 7, the maintainer's required-set call. | | F4 | Coverage lane: push lane vs objectstack's `coverage-nightly.yml`, then restore the reporter and width pins. | Roster row 7, kept, follow-up port. | | F5 | `Build Docs (@object-ui/site)`: port, then restore `docs-build-trigger`'s derived closure and the carriage-census wiring pin. | Roster row 7, kept, follow-up port. | | F6 | Timings reporter and upload on `test-ui` with the refresh workflow, then restore `test-file-timings`'s ci pins. | Roster row 28 (sub-issue 4). | | F7 | Wire `select-affected-tests.mjs` into `test-ui` once its `git diff` paths are cwd-relative, or retire it, and the same for `select-type-check-scope.mjs`. | Roster row 7. | | F8 | `live-e2e.yml` (row 17) and `published-dist-gate.yml` (row 23) ports bring their workflow halves back. | Kept, follow-up port. | ## Verification Run from `ui/` per `ui/AGENTS.md`, at this head (`main` `c475b45b` plus this commit): | Run | Result | |---|---| | The 24 modified test files plus `markdown-test-inputs.test.ts`, with `ui/.github/` present | 25 files, all passed | | The same 24 files with `ui/.github/` moved aside (the 2d-3 layout) | 24 passed (779 tests). `markdown-test-inputs.test.ts` fails at collection on `ENOENT ui/.github/workflows/ci.yml`; that file is #25851's. | | Whole `scripts/__tests__` suite, `--shard=1/4` through `4/4`, under `scripts/pm/os-verify-lock.sh` | 5,447 passed, 26 failed, 2 skipped | | `pnpm type-check:scripts` | the one error is `packages/components/src/renderers/action/resolve-icon.ts(265,69)` TS2339, identical on unmodified `main` here; none in a touched file | | `pnpm exec eslint` (touched files), `pnpm lint:root`, `lint:coverage`, `check:test-path-roots`, `check:ui-root-lookups`, `check:lint-rule-coverage`, `check:entry-guard`, `check:vi-mock-override-shape`; root `pnpm check:test-source-alias` | all exit 0; no lint warning in a touched file | Of the 26 suite failures, one is the owned row from 2c (`check-pre-install-import-graph`, `unresolved: scripts/objectstack-pin.mjs`; it clears in 2d-3 with the nested workflows). The other 25 are `date-only-zone-pins-10183` ×23 and `unit-registry-absence-collision` ×2. They fail identically on a clean `main` checkout here ("ran no case at all" on this unbuilt box), so they are not this diff's. Derived gates (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` from this diff): 18 families, all exit 0 at this head. `--ran` reconciles 18 run, 0 NOT-MEASURED, 0 UNRUN. The slowest were `check:query-options-erasure` (308 s), `check:type-check-debt` (287 s) and `check-comment-mask-corpus` (208 s). <details> <summary>The 2d census: what deleting <code>ui/.github/</code> reds (81 files; 30 read <code>ci.yml</code>)</summary> Local, `main` `e167956e` plus 2c, whole suite in two shards with `ui/.github/` deleted. 83 files fail. Two of them (`date-only-zone-pins-10183`, `unit-registry-absence-collision`) fail identically on clean `main` on that box. The other 81 read `ui/.github/`. - `ui/.github/workflows/ci.yml` (30 files): rows 1–28 above, plus #25851's `markdown-test-inputs` and `select-affected-tests-wiring`. - The workflow directory as a population (27 files, 2d-2): `check-doc-component-types`, `check-doc-example-ids`, `check-doc-example-types`, `check-doc-json-fences`, `check-doc-snippet-types`, `check-hand-rolled-comment-mask`, `check-i18n-dead-keys`, `check-lint-rule-coverage`, `check-lockfile-dedupe`, `check-lockfile-integrity`, `check-pre-install-import-graph`, `check-prompt-component-keys`, `check-published-dist-tooling`, `check-readme-exports`, `check-shell-escape-residue`, `check-skill-eval-tokens`, `check-skill-examples`, `check-skills-paths`, `check-vi-mock-inherit`, `check-vi-mock-specifiers`, `ci-setup-pnpm-wiring`, `dependabot-merge-gate`, `docs-links-workflow`, `merge-queue-reporting`, `workflow-cache-save-bound`, `doc-version-claims`, `labeler-package-coverage` (`.github/labeler.yml`). - Single workflows (24 files, 2d-2/2d-3): - `repo-gates.yml`: `check-action-ref-convention`, `check-control-bytes`, `check-doc-fence-languages`, `check-doc-snippet-emitted-census`, `check-docs-route-eager-closure`; - `lint.yml`: `bash32-floor-wiring`, `check-test-path-roots`, `check-vi-mock-override-shape`, `entry-guard-wiring`, `lint-workflow`; - `changeset-presence.yml`: `check-changeset-claims`, `check-changeset-presence`, `render-changeset-claims-comment`; - `changeset-guard.yml`: `check-changeset-no-major`, `check-changeset-overwrite`; - `performance-budget.yml`: `check-eager-closure-budget`, `ci-cd-pipeline-doc`, `render-budget-comment`; - one file each: `check-governed-queue-guard` (`governed-surface-guard.yml`), `check-links-workflow` (`check-links.yml`), `check-merge-queue-head` (`merge-queue-head-patrol.yml`), `check-new-cross-file-line-citations` (`line-citation-gate.yml`), `check-required-check-set` (`dependabot-auto-merge.yml`, `required-check-set-patrol.yml`), `shadcn-check-report` (`shadcn-check.yml`). </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01A6SEitNngAMugGbuJHWsDS Co-authored-by: Claude <noreply@anthropic.com>
1 parent c475b45 commit 6e0597f

30 files changed

Lines changed: 447 additions & 1616 deletions

‎ui/scripts/__tests__/check-action-forward-parity.test.ts‎

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ import {
2222
SURFACES,
2323
RUNTIME_CONSUMERS,
2424
} from '../check-action-forward-parity.mjs';
25+
import {
26+
globMatches,
27+
jobBlock,
28+
repoPath,
29+
rootCi,
30+
triggerBlock,
31+
uiFilterGlobs,
32+
uiRun,
33+
UI_JOBS,
34+
withoutComments,
35+
} from './helpers/root-ci';
2536

2637
/**
2738
* objectui#4050 — the behaviour test for `scripts/check-action-forward-parity.mjs`.
@@ -723,60 +734,47 @@ describe('the gate is wired to run', () => {
723734
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as {
724735
scripts: Record<string, string>;
725736
};
726-
const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
737+
// The workflow GitHub runs: objectstack's root ci.yml, whose `type-check-ui`
738+
// job is objectui's `Type Check` (objectstack#25697). objectui's own ci.yml
739+
// under `ui/.github/` runs nowhere (objectstack#25712).
740+
const ci = rootCi();
741+
const job = jobBlock(ci, UI_JOBS.typeCheck);
727742

728743
it('package.json exposes it as a named script', () => {
729744
expect(pkg.scripts['check:action-forward-parity']).toBe('node scripts/check-action-forward-parity.mjs');
730745
});
731746

732747
it('ci.yml runs it after the install it needs (it resolves @objectstack/spec)', () => {
733-
const install = ci.indexOf('pnpm install --frozen-lockfile');
734-
const step = ci.indexOf('run: pnpm check:action-forward-parity');
735-
expect(step, 'ci.yml does not run `pnpm check:action-forward-parity`').toBeGreaterThan(-1);
748+
const install = job.indexOf('pnpm install --frozen-lockfile');
749+
const step = job.indexOf(`run: ${uiRun('check:action-forward-parity')}`);
750+
expect(step, '`type-check-ui` does not run `pnpm --dir ui run check:action-forward-parity`').toBeGreaterThan(-1);
751+
expect(install, '`type-check-ui` no longer installs dependencies').toBeGreaterThan(-1);
736752
expect(step, 'the check runs before dependencies are installed').toBeGreaterThan(install);
737753
});
738754

739755
it('is not hidden from the renderers by ci.yml path filters', () => {
740-
// ci.yml `paths-ignore`s markdown, content/, docs/, apps/site/ and
741-
// .changeset/ on its `push` trigger — since objectui#3523 step 2 the only
742-
// trigger that still carries the filter (ci.yml:6; lint.yml:32 is the twin),
743-
// and the only thing the assertion below can see, since it slices the `on:`
744-
// block. None can match `packages/components/src/renderers/**`, so a push
745-
// that edits a forward whitelist always starts this workflow.
746-
//
747-
// For a PULL REQUEST the conclusion holds for a second and stronger reason.
748-
// This lead-in used to state it in a bare present tense that read as if the
749-
// filter still applied there; objectui#4384 qualified it, together with its
750-
// near-verbatim twin in `check-i18n-en-drift.test.ts`, closing the #3857 /
751-
// #4369 / #4381 family (PRs #4371 / #4380 / #4382). No `paths-ignore`
752-
// survives on `pull_request` at all, so such a PR starts this workflow a
753-
// fortiori — measured there: PR #3856, one markdown file, 16 checks. What
754-
// decides a pull request now is the in-job `Decide whether this change needs
755-
// a full run` step, whose exclusion list is that `push` filter unchanged,
756-
// held identical to it by `scripts/__tests__/merge-queue-reporting.test.ts`
757-
// — so the patterns pinned below are also what keeps the expensive steps
758-
// running on a renderer PR.
759-
const ignored = (ci.slice(0, ci.indexOf('jobs:')).match(/^\s+- '.*'$/gm) ?? []).map((line) =>
760-
line.trim().replace(/^- '/, '').replace(/'$/, ''),
756+
// The root ci.yml carries no `paths-ignore` on any trigger: it starts on
757+
// every pull_request, every queue build, every push to main and the hourly
758+
// schedule. What decides whether `type-check-ui` then runs is the `filter`
759+
// job's `ui:` paths filter, which the job's `if:` reads, so every renderer
760+
// a forward whitelist lives in must match one of its globs or a pull
761+
// request that edits one skips this gate. (Until objectstack#25712 this
762+
// pinned objectui's own `push` `paths-ignore` list, in a file nothing runs.)
763+
expect(withoutComments(triggerBlock(ci)), 'a trigger grew a path filter').not.toMatch(/^\s+paths(-ignore)?:/m);
764+
expect(job, '`type-check-ui` no longer reads the `ui` filter output').toContain(
765+
"needs.filter.outputs.ui != 'false'",
761766
);
762-
expect(ignored.length, 'the paths-ignore parse found nothing — this pin would be vacuous').toBeGreaterThan(3);
763-
764-
/** `**` crosses separators, `*` does not — enough for the five patterns above. */
765-
const matches = (glob: string, file: string): boolean =>
766-
new RegExp(
767-
`^${glob
768-
.split('**')
769-
.map((part) => part.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*'))
770-
.join('.*')}$`,
771-
).test(file);
772767

768+
const globs = uiFilterGlobs(ci);
773769
for (const surface of SURFACES) {
774-
for (const glob of ignored) {
775-
expect(matches(glob, surface.file), `${glob} hides ${surface.file}`).toBe(false);
776-
}
777-
// The matcher itself must be able to say yes, or the loop above proves
778-
// nothing — the vacuity trap this whole file is about.
779-
expect(matches('**/*.md', 'content/docs/guide/ci-cd-pipeline.md')).toBe(true);
770+
const file = repoPath(surface.file);
771+
expect(
772+
globs.some((glob) => globMatches(glob, file)),
773+
`no \`ui:\` filter glob covers ${file}`,
774+
).toBe(true);
780775
}
776+
// The matcher itself must be able to say no, or the loop above proves
777+
// nothing — the vacuity trap this whole file is about.
778+
expect(globs.some((glob) => globMatches(glob, 'packages/spec/src/index.ts'))).toBe(false);
781779
});
782780
});

‎ui/scripts/__tests__/check-doc-example-shared-reader.test.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
inline,
1616
rungsOf,
1717
} from '../check-doc-example-shared-reader.mjs';
18+
import { jobBlock, rootCi, uiRun, UI_JOBS } from './helpers/root-ci';
1819

1920
/**
2021
* objectui#7652 — a doc comment must not prescribe a call-site spelling that a
@@ -482,10 +483,13 @@ describe('check-doc-example-shared-reader — this repository', () => {
482483
const manifest = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
483484
expect(manifest.scripts['check:doc-example-readers']).toBe('node scripts/check-doc-example-shared-reader.mjs');
484485

485-
const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
486-
expect(ci, 'the gate must run in CI, next to the other source-parsing gates').toContain(
487-
'run: pnpm check:doc-example-readers',
488-
);
486+
// objectstack's root ci.yml runs objectui's gates: its `type-check-ui` job is
487+
// objectui's `Type Check`, where the source-parsing gates run (objectstack#25697).
488+
// The ci.yml under `ui/.github/` runs nowhere (objectstack#25712).
489+
expect(
490+
jobBlock(rootCi(), UI_JOBS.typeCheck),
491+
'the gate must run in CI, next to the other source-parsing gates',
492+
).toContain(`run: ${uiRun('check:doc-example-readers')}`);
489493

490494
// The page that inventories the gates is pinned by command (objectui#3653), so
491495
// this would fail there too — asserted here as well because a reader looking

‎ui/scripts/__tests__/check-doc-expression-carriage.test.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -848,10 +848,12 @@ describe('check-doc-expression-carriage: the real tree, and the posture', () =>
848848
});
849849

850850
describe('check-doc-expression-carriage: the wiring', () => {
851-
it('is actually run by ci.yml — a gate nobody runs cannot be told from one that passes', () => {
852-
const ci = fs.readFileSync(path.join(ROOT, '.github/workflows/ci.yml'), 'utf8');
853-
expect(ci).toContain(`node ${GATE}`);
854-
});
851+
// `is actually run by ci.yml` retired with objectstack#25712. The census ran,
852+
// report-only, in objectui ci.yml's `docs` job (`Build Docs`), and that file
853+
// runs nowhere under the monorepo. The job is row 7 `Build Docs
854+
// (@object-ui/site)` of the objectstack#25697 workflow roster, "kept,
855+
// follow-up port": no root job runs the census until that port lands, and
856+
// the port brings this pin back against its job (`helpers/root-ci.ts`).
855857

856858
it('is named on the page that documents what CI runs', () => {
857859
const page = fs.readFileSync(path.join(ROOT, 'content/docs/guide/ci-cd-pipeline.md'), 'utf8');

‎ui/scripts/__tests__/check-handler-key-read-sites.test.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
registrationsIn,
1414
parseSource,
1515
} from '../check-handler-key-read-sites.mjs';
16+
import { jobBlock, rootCi, uiRun, UI_JOBS } from './helpers/root-ci';
1617

1718
/**
1819
* objectui#7753 — every `on*` key a registered renderer READS off the authored
@@ -1148,10 +1149,13 @@ describe('check-handler-key-read-sites — this repository', () => {
11481149
const manifest = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
11491150
expect(manifest.scripts['check:handler-key-reads']).toBe('node scripts/check-handler-key-read-sites.mjs');
11501151

1151-
const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
1152-
expect(ci, 'the gate must run in CI, next to the other source-parsing gates').toContain(
1153-
'run: pnpm check:handler-key-reads',
1154-
);
1152+
// objectstack's root ci.yml runs objectui's gates: its `type-check-ui` job is
1153+
// objectui's `Type Check`, where the source-parsing gates run (objectstack#25697).
1154+
// The ci.yml under `ui/.github/` runs nowhere (objectstack#25712).
1155+
expect(
1156+
jobBlock(rootCi(), UI_JOBS.typeCheck),
1157+
'the gate must run in CI, next to the other source-parsing gates',
1158+
).toContain(`run: ${uiRun('check:handler-key-reads')}`);
11551159

11561160
// The page that inventories the gates is pinned by command (objectui#3653), so
11571161
// this would fail there too — asserted here as well because a reader looking

‎ui/scripts/__tests__/check-i18n-call-site-keys.test.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
RESERVED_OPTION_NAMES,
2121
unresolvableSpellings,
2222
} from '../check-i18n-call-site-keys.mjs';
23+
import { jobBlock, rootCi, uiRun, UI_JOBS } from './helpers/root-ci';
2324

2425
/**
2526
* objectui#3530 — the behaviour test for `scripts/check-i18n-call-site-keys.mjs`.
@@ -2129,10 +2130,14 @@ describe('the gate is wired to run', () => {
21292130
});
21302131

21312132
it('ci.yml runs it after the install it needs (it imports typescript)', () => {
2132-
const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
2133-
const install = ci.indexOf('pnpm install --frozen-lockfile');
2134-
const step = ci.indexOf('pnpm check:i18n-keys');
2135-
expect(step, 'ci.yml does not run `pnpm check:i18n-keys`').toBeGreaterThan(-1);
2133+
// The workflow GitHub runs: objectstack's root ci.yml, whose `type-check-ui`
2134+
// job is objectui's `Type Check` (objectstack#25697). objectui's own ci.yml
2135+
// under `ui/.github/` runs nowhere (objectstack#25712).
2136+
const job = jobBlock(rootCi(), UI_JOBS.typeCheck);
2137+
const install = job.indexOf('pnpm install --frozen-lockfile');
2138+
const step = job.indexOf(`run: ${uiRun('check:i18n-keys')}`);
2139+
expect(step, '`type-check-ui` does not run `pnpm --dir ui run check:i18n-keys`').toBeGreaterThan(-1);
2140+
expect(install, '`type-check-ui` no longer installs dependencies').toBeGreaterThan(-1);
21362141
expect(step, 'the check runs before dependencies are installed').toBeGreaterThan(install);
21372142
});
21382143
});

‎ui/scripts/__tests__/check-i18n-designer-table-parity.test.ts‎

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
readDesignerPairs,
1414
ZH_ONLY_FAMILIES,
1515
} from '../check-i18n-designer-table-parity.mjs';
16+
import { globMatches, jobBlock, repoPath, rootCi, triggerBlock, uiFilterGlobs, uiRun, UI_JOBS, withoutComments } from './helpers/root-ci';
1617
import { DESIGNER_TABLE_CONSTS } from '../check-i18n-dead-keys.mjs';
1718

1819
/**
@@ -322,7 +323,11 @@ describe('the CLI, end to end', () => {
322323

323324
describe('the gate is wired to run', () => {
324325
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
325-
const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
326+
// The workflow GitHub runs: objectstack's root ci.yml, whose `type-check-ui`
327+
// job is objectui's `Type Check` (objectstack#25697). objectui's own ci.yml
328+
// under `ui/.github/` runs nowhere (objectstack#25712).
329+
const ci = rootCi();
330+
const job = jobBlock(ci, UI_JOBS.typeCheck);
326331

327332
it('package.json exposes it as a named script', () => {
328333
expect(pkg.scripts['check:i18n-designer-parity']).toBe('node scripts/check-i18n-designer-table-parity.mjs');
@@ -333,18 +338,26 @@ describe('the gate is wired to run', () => {
333338
// check-i18n-dead-keys.mjs: that one is invoked in no workflow at all, by
334339
// design ("This is a REPORT, not a gate"), so a leg there could never be
335340
// observed red in CI — the disease objectui#8834 exists to cure.
336-
const install = ci.indexOf('pnpm install --frozen-lockfile');
337-
const step = ci.indexOf('run: pnpm check:i18n-designer-parity');
338-
expect(step, 'ci.yml does not run `pnpm check:i18n-designer-parity`').toBeGreaterThan(-1);
341+
const install = job.indexOf('pnpm install --frozen-lockfile');
342+
const step = job.indexOf(`run: ${uiRun('check:i18n-designer-parity')}`);
343+
expect(step, '`type-check-ui` does not run `pnpm --dir ui run check:i18n-designer-parity`').toBeGreaterThan(-1);
344+
expect(install, '`type-check-ui` no longer installs dependencies').toBeGreaterThan(-1);
339345
expect(step, 'the check runs before dependencies are installed').toBeGreaterThan(install);
340346
});
341347

342348
it('is not hidden from the designer table by ci.yml path filters', () => {
343-
// Same reasoning as check-i18n-en-drift.test.ts's twin assertion: an ignore
344-
// entry that covered `packages/` would make this gate unreachable for
345-
// exactly the PRs it judges.
346-
const ignored = ci.slice(0, ci.indexOf('jobs:')).match(/^\s+- '.*'$/gm) ?? [];
347-
for (const pattern of ignored) expect(pattern).not.toMatch(/packages|scripts/);
348-
expect(ignored.length).toBeGreaterThan(0);
349+
// Same reasoning as check-i18n-en-drift.test.ts's twin assertion: the root
350+
// ci.yml's triggers carry no path filter, and the `filter` job's `ui:` globs
351+
// decide whether `type-check-ui` runs, so a glob set that missed the
352+
// designer table or this gate would make it unreachable for exactly the
353+
// PRs it judges.
354+
expect(withoutComments(triggerBlock(ci)), 'a trigger grew a path filter').not.toMatch(/^\s+paths(-ignore)?:/m);
355+
expect(job, '`type-check-ui` no longer reads the `ui` filter output').toContain(
356+
"needs.filter.outputs.ui != 'false'",
357+
);
358+
const globs = uiFilterGlobs(ci);
359+
for (const file of [DESIGNER_TABLE, 'scripts/check-i18n-designer-table-parity.mjs'].map(repoPath)) {
360+
expect(globs.some((glob) => globMatches(glob, file)), `no \`ui:\` filter glob covers ${file}`).toBe(true);
361+
}
349362
});
350363
});

‎ui/scripts/__tests__/check-i18n-en-drift.test.ts‎

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
readDesignerTables,
2626
} from '../check-i18n-en-drift.mjs';
2727
import { DESIGNER_TABLE } from '../check-i18n-designer-table-parity.mjs';
28+
import { globMatches, jobBlock, repoPath, rootCi, triggerBlock, uiFilterGlobs, uiRun, UI_JOBS, withoutComments } from './helpers/root-ci';
2829

2930
/**
3031
* objectui#3650 — the behaviour test for `scripts/check-i18n-en-drift.mjs`.
@@ -964,55 +965,54 @@ describe('resolving the commit to compare against', () => {
964965

965966
describe('the gate is wired to run', () => {
966967
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
967-
const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
968+
// The workflow GitHub runs: objectstack's root ci.yml, whose `type-check-ui`
969+
// job is objectui's `Type Check` (objectstack#25697). objectui's own ci.yml
970+
// under `ui/.github/` runs nowhere (objectstack#25712).
971+
const ci = rootCi();
972+
const job = jobBlock(ci, UI_JOBS.typeCheck);
968973

969974
it('package.json exposes it as a named script', () => {
970975
expect(pkg.scripts['check:i18n-drift']).toBe('node scripts/check-i18n-en-drift.mjs');
971976
});
972977

973978
it('ci.yml runs it after the install it needs (it imports typescript)', () => {
974-
const install = ci.indexOf('pnpm install --frozen-lockfile');
975-
const step = ci.indexOf('run: pnpm check:i18n-drift');
976-
expect(step, 'ci.yml does not run `pnpm check:i18n-drift`').toBeGreaterThan(-1);
979+
const install = job.indexOf('pnpm install --frozen-lockfile');
980+
const step = job.indexOf(`run: ${uiRun('check:i18n-drift')}`);
981+
expect(step, '`type-check-ui` does not run `pnpm --dir ui run check:i18n-drift`').toBeGreaterThan(-1);
982+
expect(install, '`type-check-ui` no longer installs dependencies').toBeGreaterThan(-1);
977983
expect(step, 'the check runs before dependencies are installed').toBeGreaterThan(install);
978984
});
979985

980986
it('gives that job a full-history checkout, or the merge base does not exist', () => {
981987
// actions/checkout defaults to depth 1. Without this the gate cannot resolve
982988
// a base — and by design that is a red build, not a silent pass, so losing
983989
// this line breaks CI loudly. The pin is here so the reason survives.
984-
const job = ci.slice(ci.indexOf(' type-check:'), ci.indexOf('run: pnpm check:i18n-drift'));
985-
expect(job).toContain('fetch-depth: 0');
990+
const step = job.indexOf(`run: ${uiRun('check:i18n-drift')}`);
991+
expect(step, '`type-check-ui` does not run `pnpm --dir ui run check:i18n-drift`').toBeGreaterThan(-1);
992+
expect(job.slice(0, step)).toContain('fetch-depth: 0');
986993
});
987994

988995
it('is not hidden from the locale packs by ci.yml path filters', () => {
989-
// ci.yml `paths-ignore`s markdown, content/, docs/, apps/site/ and
990-
// .changeset/ on its `push` trigger — since objectui#3523 step 2 the only
991-
// trigger that still carries the filter (ci.yml:6; lint.yml:32 is the twin),
992-
// and the only thing the assertion below can see, since it slices the `on:`
993-
// block. None of them can match `packages/i18n/src/locales/*.ts` or the
994-
// ledger, so a push that edits an en string always starts this workflow.
996+
// The root ci.yml carries no `paths-ignore` on any trigger: it starts on
997+
// every pull_request, every queue build, every push to main and the hourly
998+
// schedule. What decides whether `type-check-ui` then runs is the `filter`
999+
// job's `ui:` paths filter, which the job's `if:` reads, so the ten locale
1000+
// packs, the ledger and the designer table must each match one of its
1001+
// globs. (Until objectstack#25712 this pinned objectui's own `push`
1002+
// `paths-ignore` list and its in-job `Decide whether this change needs a
1003+
// full run` step, in a ci.yml nothing runs; its near-verbatim twin is in
1004+
// `check-action-forward-parity.test.ts`.)
9951005
//
996-
// For a PULL REQUEST the conclusion holds for a second and stronger reason.
997-
// This lead-in used to state it in a bare present tense that read as if the
998-
// filter still applied there; objectui#4384 qualified it, together with its
999-
// near-verbatim twin in `check-action-forward-parity.test.ts`, closing the
1000-
// #3857 / #4369 / #4381 family (PRs #4371 / #4380 / #4382). No
1001-
// `paths-ignore` survives on `pull_request` at all, so such a PR starts this
1002-
// workflow a fortiori — measured there: PR #3856, one markdown file, 16
1003-
// checks. What decides a pull request now is the in-job `Decide whether this
1004-
// change needs a full run` step, whose exclusion list is that `push` filter
1005-
// unchanged, held identical to it by
1006-
// `scripts/__tests__/merge-queue-reporting.test.ts` — so the patterns pinned
1007-
// below are also what keeps the expensive steps running on a locale-pack PR.
1008-
//
1009-
// A new entry that DID cover them would make the gate unreachable for
1010-
// exactly the PRs it judges, on both lanes at once — the objectui#3547 /
1011-
// control-bytes.yml lesson, one workflow over.
1012-
const ignored = ci.slice(0, ci.indexOf('jobs:')).match(/^\s+- '.*'$/gm) ?? [];
1013-
for (const pattern of ignored) {
1014-
expect(pattern).not.toMatch(/packages|scripts|locales/);
1006+
// A glob set that missed them would make the gate unreachable for exactly
1007+
// the PRs it judges — the objectui#3547 / control-bytes.yml lesson, one
1008+
// workflow over.
1009+
expect(withoutComments(triggerBlock(ci)), 'a trigger grew a path filter').not.toMatch(/^\s+paths(-ignore)?:/m);
1010+
expect(job, '`type-check-ui` no longer reads the `ui` filter output').toContain(
1011+
"needs.filter.outputs.ui != 'false'",
1012+
);
1013+
const globs = uiFilterGlobs(ci);
1014+
for (const file of [...LOCALES.map(packPath), LEDGER_PATH, DESIGNER_TABLE].map(repoPath)) {
1015+
expect(globs.some((glob) => globMatches(glob, file)), `no \`ui:\` filter glob covers ${file}`).toBe(true);
10151016
}
1016-
expect(ignored.length).toBeGreaterThan(0);
10171017
});
10181018
});

0 commit comments

Comments
 (0)