Skip to content

Commit 7ca50a8

Browse files
committed
tooling(ui-wiring): two more workspace-mirroring gates learn the ui/ roots (objectstack#25697)
PR A's first CI run turned two gates red that the derived gate list for its paths did not name, both because pnpm-workspace.yaml now declares four globs that match nothing until the import: - check-dispatcher-error-vocabulary refuses any workspace glob that expands to no member (#16649), in production and in its self-test. WORKSPACE_GLOBS_AWAITING_IMPORT excuses the four ui globs ONLY while ui/package.json is absent (awaitingImportGlobs); the self-test holds the map against the live workspace, pins the exact key set, and drives both marker states on a scratch root so the excuse voids itself at the import. The excuse is opt-in at the call site, never a default. - check-duration-unit-keys (packages/spec) mirrors the workspace roots as ROOT_DIR_WATCH_HINTS literals and holds them against the live file in both directions; the four ui roots are declared as `ui/src/**`, `ui/packages/*/src/**`, `ui/apps/*/src/**`, `ui/examples/*/src/**`. Both gates and their self-tests exit 0 on the PR merge commit; every gate the dispatch derivation names for these two files exits 0. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RSDL3SFtJaKzcymvCD8RsU
1 parent 597471c commit 7ca50a8

2 files changed

Lines changed: 88 additions & 2 deletions

File tree

‎packages/spec/scripts/check-duration-unit-keys.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,14 @@ export const ROOT_DIR_WATCH_HINTS = [
313313
'packages/connectors/*/src/**',
314314
'apps/*/src/**',
315315
'examples/*/src/**',
316+
// The objectui tree, imported under `ui/` (objectstack#25677 / #25697): four
317+
// workspace roots pnpm-workspace.yaml declares ahead of the import. Each is a
318+
// live workspace root (the self-test holds both directions), and each walks
319+
// nothing until `ui/` arrives.
320+
'ui/src/**',
321+
'ui/packages/*/src/**',
322+
'ui/apps/*/src/**',
323+
'ui/examples/*/src/**',
316324
];
317325

318326
/** Canonical unit → every spelling the describe prose or a key token may use. */

‎scripts/check-dispatcher-error-vocabulary.mjs‎

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,8 @@
288288
* accept set, deliberately not taken here.
289289
*/
290290

291-
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
291+
import { readFileSync, readdirSync, statSync, existsSync, mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
292+
import { tmpdir } from 'node:os';
292293
// [#15487] The SHARED half of the regex-literal recogniser: the 14-word
293294
// `REGEX_AFTER_KEYWORD` set, `IDENT_CHAR`, and the literal WALK (character
294295
// class, backslash sequences, line-terminator refusal, flags) live ONCE, in
@@ -493,12 +494,46 @@ function expandWorkspaceMembers(root, glob) {
493494
* passes {@link expandWorkspaceMembers} — production and `--self-test` reading
494495
* two different populations is the defect #17145 carded, not a spare knob.
495496
*/
496-
export function emptyWorkspaceGlobs({ readGlobs, expand }) {
497+
export function emptyWorkspaceGlobs({ readGlobs, expand, awaiting = new Set() }) {
497498
return readGlobs()
498499
.filter((glob) => !isExclusionGlob(glob))
500+
.filter((glob) => !awaiting.has(glob))
499501
.filter((glob) => expand(glob).length === 0);
500502
}
501503

504+
/**
505+
* Workspace globs `pnpm-workspace.yaml` declares AHEAD of the tree they match:
506+
* the objectui import under `ui/` (objectstack#25677, wired by #25697). Each
507+
* is excused from the empty-glob refusal above ONLY while
508+
* {@link AWAITING_IMPORT_MARKER} is absent; the moment the import lands, every
509+
* one of them must expand to members or it is the finding it always was. The
510+
* self-test holds the map against the live workspace (every key is a declared
511+
* glob) and against both states of the marker. Retire the map with the import.
512+
*/
513+
export const WORKSPACE_GLOBS_AWAITING_IMPORT = Object.freeze({
514+
ui: 'objectui import (objectstack#25677): the `ui` root manifest itself (`object-ui-monorepo`)',
515+
'ui/packages/*': 'objectui import (objectstack#25677): the @object-ui/* packages',
516+
'ui/apps/*': 'objectui import (objectstack#25677): the Console and the docs site',
517+
'ui/examples/*': 'objectui import (objectstack#25677): objectui example apps',
518+
});
519+
520+
/** The file whose presence ends the excuse: the imported tree's root manifest. */
521+
export const AWAITING_IMPORT_MARKER = 'ui/package.json';
522+
523+
/**
524+
* The globs excused from the empty-glob refusal on the tree at `root`: the
525+
* whole {@link WORKSPACE_GLOBS_AWAITING_IMPORT} key set while the marker is
526+
* absent, nothing once it exists.
527+
*
528+
* @param {string} root
529+
* @returns {Set<string>}
530+
*/
531+
export function awaitingImportGlobs(root) {
532+
return existsSync(join(root, AWAITING_IMPORT_MARKER))
533+
? new Set()
534+
: new Set(Object.keys(WORKSPACE_GLOBS_AWAITING_IMPORT));
535+
}
536+
502537
/**
503538
* [#16649] Every published workspace member's `src/`, as repo-relative prefixes.
504539
*
@@ -6145,8 +6180,50 @@ function selfTest() {
61456180
ok(emptyWorkspaceGlobs({
61466181
readGlobs: () => readWorkspaceGlobs(ROOT),
61476182
expand: (g) => expandWorkspaceMembers(ROOT, g),
6183+
awaiting: awaitingImportGlobs(ROOT),
61486184
}).length === 0,
61496185
'the LIVE workspace has a glob that expands to nothing — the production run refuses on this too');
6186+
// [#25697] The awaiting-import excuse, narrow in every direction: only the
6187+
// globs it names, only while the marker is absent, and only globs the live
6188+
// workspace really declares (so the map cannot outlive the import unnoticed).
6189+
ok(emptyWorkspaceGlobs({
6190+
readGlobs: () => ['packages/*', 'ui/packages/*', 'zz-gone/*'],
6191+
expand: (g) => (g === 'packages/*' ? ['a'] : []),
6192+
awaiting: new Set(['ui/packages/*']),
6193+
}).join(',') === 'zz-gone/*',
6194+
'an empty glob the awaiting set names is excused; an empty glob it does not name is still reported by name');
6195+
ok(emptyWorkspaceGlobs({
6196+
readGlobs: () => ['ui/packages/*'],
6197+
expand: () => [],
6198+
}).join(',') === 'ui/packages/*',
6199+
'with no awaiting set the same empty glob is a finding — the excuse is opt-in at the call site, never a default');
6200+
{
6201+
const liveGlobs = new Set(readWorkspaceGlobs(ROOT));
6202+
const keys = Object.keys(WORKSPACE_GLOBS_AWAITING_IMPORT);
6203+
ok(keys.every((g) => liveGlobs.has(g)),
6204+
`every awaiting-import glob is one pnpm-workspace.yaml declares — stale keys: ` +
6205+
`${keys.filter((g) => !liveGlobs.has(g)).join(', ') || 'none'}`);
6206+
ok(keys.every((g) => typeof WORKSPACE_GLOBS_AWAITING_IMPORT[g] === 'string' && WORKSPACE_GLOBS_AWAITING_IMPORT[g].length > 0),
6207+
'every awaiting-import entry carries a reason');
6208+
ok(keys.sort().join() === 'ui,ui/apps/*,ui/examples/*,ui/packages/*',
6209+
'the awaiting-import map holds exactly the four ui roots and nothing else');
6210+
// Both states of the marker, on a scratch root: absent → the whole key
6211+
// set is excused; present → nothing is, and the import commit meets the
6212+
// refusal with no further edit here.
6213+
const scratch = mkdtempSync(join(tmpdir(), 'dispatcher-awaiting-'));
6214+
try {
6215+
ok(awaitingImportGlobs(scratch).size === keys.length,
6216+
'with the marker absent, every awaiting-import glob is excused');
6217+
mkdirSync(join(scratch, 'ui'), { recursive: true });
6218+
writeFileSync(join(scratch, 'ui', 'package.json'), '{"name":"object-ui-monorepo","private":true}\n');
6219+
ok(awaitingImportGlobs(scratch).size === 0,
6220+
'with the marker present, nothing is excused — the excuse voids itself at the import');
6221+
} finally {
6222+
rmSync(scratch, { recursive: true, force: true });
6223+
}
6224+
ok(existsSync(join(ROOT, AWAITING_IMPORT_MARKER)) === (awaitingImportGlobs(ROOT).size === 0),
6225+
'the live reading agrees with the live marker');
6226+
}
61506227
// [#17145] The same expansion, shown to be the MANIFEST-filtered one and not
61516228
// the raw directory listing. Without this the two could drift apart again
61526229
// silently, which is the whole defect: a glob resolving only to category
@@ -6516,6 +6593,7 @@ function main() {
65166593
const emptyGlobs = emptyWorkspaceGlobs({
65176594
readGlobs: () => readWorkspaceGlobs(ROOT),
65186595
expand: (glob) => expandWorkspaceMembers(ROOT, glob),
6596+
awaiting: awaitingImportGlobs(ROOT),
65196597
});
65206598
if (emptyGlobs.length > 0) {
65216599
throw new Error(

0 commit comments

Comments
 (0)