Skip to content

Commit 597471c

Browse files
committed
tooling(ui-wiring): workspace-mirroring gates learn the four ui/ roots (objectstack#25697)
Three gates hold a hand-written copy of pnpm-workspace.yaml's `packages:` block and red when the live file names a root they do not declare. Each now declares the ui roots, which match nothing until the import: - check-published-files: ROOT_DIR_WATCH_HINTS gains ui/*, ui/packages/*, ui/apps/*, ui/examples/* (ui/* stands for the bare `ui` member, spelled with a separator as the declaration requires). - check-test-source-alias: WORKSPACE_PARENT_GLOBS gains the three `/*` roots; the bare `ui` member is declared as WORKSPACE_BARE_MEMBER_MANIFESTS (`ui/package.json`), walked as the one package at that directory and reconciled against the live file like the globs. - check-undeclared-dep-imports: its parser refused any entry that is not `<dir>/*`, so the bare `ui` entry made the sweep fatal. A bare `<dir>` is now the one package at that directory, absent contributes nothing; two new refusal-battery cases drive both directions, and `ui/**` joins ROOT_DIR_WATCH_HINTS. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RSDL3SFtJaKzcymvCD8RsU
1 parent 26fb5ba commit 597471c

3 files changed

Lines changed: 72 additions & 23 deletions

File tree

‎scripts/check-published-files.mjs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,13 @@ const ROOT_DIR_WATCH_HINTS = [
458458
'packages/triggers/*',
459459
'apps/*',
460460
'examples/*',
461+
// The objectui tree's roots (objectstack#25677). `ui/*` stands for the bare
462+
// `ui` member (objectui's root manifest), spelled with a separator for the
463+
// reason above; the other three match its own `<dir>/*` globs.
464+
'ui/*',
465+
'ui/packages/*',
466+
'ui/apps/*',
467+
'ui/examples/*',
461468
];
462469

463470
/**

‎scripts/check-test-source-alias.mjs‎

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -633,8 +633,21 @@ const WORKSPACE_PARENT_GLOBS = [
633633
'packages/triggers/*',
634634
'apps/*',
635635
'examples/*',
636+
// The objectui tree's package roots (objectstack#25677); they match nothing
637+
// until it is imported under ui/.
638+
'ui/packages/*',
639+
'ui/apps/*',
640+
'ui/examples/*',
636641
];
637642

643+
/**
644+
* Workspace members pnpm-workspace.yaml names as a BARE directory (no `/*`):
645+
* the one package AT that directory, declared by its manifest path so the entry
646+
* keeps the separator the dispatch deriver needs. `ui` is objectui's root
647+
* manifest (objectstack#25677); absent until the import, like the globs above.
648+
*/
649+
const WORKSPACE_BARE_MEMBER_MANIFESTS = ['ui/package.json'];
650+
638651
/** The parent directories those globs enumerate — each glob minus its leaf. */
639652
const WORKSPACE_PARENT_DIRS = WORKSPACE_PARENT_GLOBS.map((glob) => glob.replace(/\/\*$/, ''));
640653

@@ -650,22 +663,24 @@ function readJson(file) {
650663

651664
function listWorkspacePackages(root) {
652665
const out = [];
666+
const candidates = [];
653667
for (const parent of WORKSPACE_PARENT_DIRS) {
654668
const abs = join(root, parent);
655669
if (!existsSync(abs)) continue;
656-
for (const name of readdirSync(abs)) {
657-
const dir = join(abs, name);
658-
const manifest = join(dir, 'package.json');
659-
if (!existsSync(manifest)) continue;
660-
let json;
661-
try {
662-
json = readJson(manifest);
663-
} catch {
664-
continue;
665-
}
666-
if (!json.name) continue;
667-
out.push({ name: json.name, dir, rel: relative(root, dir), json });
670+
for (const name of readdirSync(abs)) candidates.push(join(abs, name));
671+
}
672+
for (const manifestPath of WORKSPACE_BARE_MEMBER_MANIFESTS) candidates.push(dirname(join(root, manifestPath)));
673+
for (const dir of candidates) {
674+
const manifest = join(dir, 'package.json');
675+
if (!existsSync(manifest)) continue;
676+
let json;
677+
try {
678+
json = readJson(manifest);
679+
} catch {
680+
continue;
668681
}
682+
if (!json.name) continue;
683+
out.push({ name: json.name, dir, rel: relative(root, dir), json });
669684
}
670685
return out;
671686
}
@@ -3322,7 +3337,7 @@ function selfTest() {
33223337
// docblock (#9955). Asserted here rather than left to review because the
33233338
// regression is a tidy-up nobody would flag.
33243339
battery('the declared population must stay READABLE by the dispatch deriver');
3325-
for (const glob of WORKSPACE_PARENT_GLOBS) {
3340+
for (const glob of [...WORKSPACE_PARENT_GLOBS, ...WORKSPACE_BARE_MEMBER_MANIFESTS]) {
33263341
expect(
33273342
glob.replace(/^(?:\.\.?(?:\/|$))+/, '').includes('/'),
33283343
`workspace parent ${glob} carries no path separator, so scripts/pm/dispatch-gates.mjs refuses it as too generic and every package under it drops out of the derived gate list`,
@@ -3346,7 +3361,12 @@ function selfTest() {
33463361
// the declaration stays and the live parse becomes its CHECK, in both
33473362
// directions, the shape check-published-files.mjs already uses.
33483363
battery('the declaration must still BE the workspace (#11510)');
3349-
const declaredParents = WORKSPACE_PARENT_GLOBS.map((g) => g.replace(/\/\*+$/, ''));
3364+
// A bare-directory member is declared by its manifest path; both spellings
3365+
// reduce to the directory pnpm-workspace.yaml names.
3366+
const declaredParents = [
3367+
...WORKSPACE_PARENT_GLOBS.map((g) => g.replace(/\/\*+$/, '')),
3368+
...WORKSPACE_BARE_MEMBER_MANIFESTS.map((m) => m.replace(/\/package\.json$/, '')),
3369+
];
33503370
const liveParents = readWorkspaceGlobs(REPO_ROOT)
33513371
.filter((g) => !isExclusionGlob(g))
33523372
.map((g) => g.replace(/\/\*+$/, ''));

‎scripts/check-undeclared-dep-imports.mjs‎

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
238238
'the detector STAYS SILENT where it must': 6,
239239
'the comment mask, in both directions': 3,
240240
'tsconfig exclusions are honoured, and only where declared': 2,
241-
'refusals': 8,
241+
'refusals': 10,
242242
'provenance: the record must stay reproducible, and visibly so': 8,
243243
'ledger reconciliation, in both directions': 10,
244244
'the declared watch hints stay equal to the real population': 2,
@@ -276,7 +276,7 @@ const REPO_ROOT = resolve(HERE, '..');
276276
* root reds here instead of quietly leaving part of the tree unswept and this
277277
* gate unnameable.
278278
*/
279-
const ROOT_DIR_WATCH_HINTS = ['packages/**', 'apps/**', 'examples/**'];
279+
const ROOT_DIR_WATCH_HINTS = ['packages/**', 'apps/**', 'examples/**', 'ui/**'];
280280

281281
// ---------------------------------------------------------------------------
282282
// Refusal floors, and the provenance of the census they were derived from --
@@ -331,8 +331,10 @@ const LEDGER_KINDS = new Set(['optional-runtime-probe', 'type-only']);
331331
// Workspace discovery
332332

333333
/**
334-
* The `packages:` globs from pnpm-workspace.yaml. Only the `<dir>/*` shape this
335-
* repo uses is understood; anything else REFUSES rather than being skipped, so
334+
* The `packages:` globs from pnpm-workspace.yaml. Two shapes are understood:
335+
* `<dir>/*` (every package one level under `<dir>`) and a bare `<dir>` with no
336+
* wildcard at all (the one package AT `<dir>` -- the objectui root manifest,
337+
* `ui`, objectstack#25677). Anything else REFUSES rather than being skipped, so
336338
* a new glob shape cannot silently shrink the population.
337339
*/
338340
export function workspaceGlobs(yamlText) {
@@ -354,12 +356,16 @@ function discoverPackages(root, globs) {
354356
const found = [];
355357
const bad = [];
356358
for (const glob of globs) {
357-
if (!glob.endsWith('/*') || glob.slice(0, -2).includes('*')) { bad.push(glob); continue; }
358-
const parent = join(root, glob.slice(0, -2));
359+
const bare = !glob.includes('*');
360+
if (!bare && (!glob.endsWith('/*') || glob.slice(0, -2).includes('*'))) { bad.push(glob); continue; }
361+
const parent = join(root, bare ? glob : glob.slice(0, -2));
362+
// An absent root contributes nothing, in both shapes: a workspace glob that
363+
// matches no directory is legal pnpm and this gate walks what pnpm walks.
359364
if (!existsSync(parent)) continue;
360-
for (const entry of readdirSync(parent, { withFileTypes: true })) {
361-
if (!entry.isDirectory()) continue;
362-
const dir = join(parent, entry.name);
365+
const candidates = bare
366+
? [parent]
367+
: readdirSync(parent, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join(parent, e.name));
368+
for (const dir of candidates) {
363369
const manifestPath = join(dir, 'package.json');
364370
if (!existsSync(manifestPath)) continue;
365371
let manifest;
@@ -914,6 +920,22 @@ function selfTest() {
914920
t('REFUSAL — a glob shape this parser does not understand is fatal, not skipped',
915921
typeof badGlob.fatal === 'string');
916922

923+
// The bare `<dir>` shape, both directions: present, its one package is
924+
// swept like any other; absent, it contributes nothing and is not fatal.
925+
const bareDir = mkdtempSync(join(tmp, 'baredir-'));
926+
fixture(bareDir, 'pnpm-workspace.yaml', 'packages:\n - packages/*\n - ui\n');
927+
fixture(bareDir, 'packages/subject/package.json', JSON.stringify(baseManifest, null, 2));
928+
fixture(bareDir, 'packages/subject/src/a.ts', 'export const ok = 1;\n');
929+
fixture(bareDir, 'ui/package.json', JSON.stringify({ name: 'ui-root' }, null, 2));
930+
fixture(bareDir, 'ui/src/a.ts', "import { x } from '@objectstack/undeclared';\n");
931+
const bareSwept = sweep(bareDir);
932+
t('a bare `<dir>` workspace entry sweeps the one package at that directory',
933+
bareSwept.fatal === undefined
934+
&& bareSwept.findings.length === 1 && bareSwept.findings[0].dep === '@objectstack/undeclared');
935+
const bareAbsent = run({ manifest: baseManifest, files: { 'src/a.ts': 'export const ok = 1;\n' }, workspace: 'packages:\n - packages/*\n - ui\n' });
936+
t('an ABSENT bare `<dir>` entry contributes nothing and is not fatal',
937+
bareAbsent.fatal === undefined && bareAbsent.findings.length === 0);
938+
917939
t('FLOOR — an empty population trips the package floor rather than passing',
918940
floorProblems({ packages: [], scannedFiles: 99999, specifiers: 99999 }) !== null);
919941
t('FLOOR — a broken walk trips the file floor',

0 commit comments

Comments
 (0)