Skip to content

Commit 3f3f095

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-4m2bvo
2 parents 24e5933 + 6e0597f commit 3f3f095

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)