Skip to content

Commit 601fbc4

Browse files
fix(devx): the cross-package detector sees the seed walked from import.meta.url (#8995) (#9010)
Three packages/cli e2e tests seed their paths as `resolve(fileURLToPath(import.meta.url), '..')` -- walking to the directory from the FILE rather than naming it with `dirname(...)`. `import.meta.url` was not a recognised expression, so the whole chain resolved to undefined, their reads of `content/docs/**` produced no flag, and the radius went undeclared silently. The gate's OK line said "all declared" while it was not. Model the file at `hereDepth + 1` -- one level below its directory -- so the walked seed comes out equal to the named seed through the ordinary literal walk instead of needing a case of its own. This is exactly Node's resolve/join, which treat a file argument as a directory prefix. Declare the radius that then becomes visible, re-derived from the detector's own output rather than assumed: three content/docs pages, connector-mcp's plugin source (a relative literal the coverage check cannot name), and one gate script named in a comment. +7 --self-test cases, one per newly recognised shape (26 -> 33), and the published RECOGNISED_PATH_SPELLINGS list moves in AGENTS.md in the same edit. Co-authored-by: Claude <noreply@anthropic.com>
1 parent ccc917c commit 601fbc4

3 files changed

Lines changed: 119 additions & 1 deletion

File tree

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ recognised list is published rather than left inside the implementation. Seed fr
9696
const HERE = dirname(fileURLToPath(import.meta.url)); // seed (ESM)
9797
const HERE = __dirname; // seed (CJS)
9898
const HERE = import.meta.dirname; // and dirname(import.meta.filename)
99+
const HERE = resolve(fileURLToPath(import.meta.url), '..'); // seed walked from the
100+
// FILE rather than named;
101+
// import.meta.filename too
99102
const P = resolve(HERE, '<rel>'); // join() and path.* too
100103
const P = fileURLToPath(new URL('<rel>', import.meta.url));
101104
const P = new URL('<rel>', import.meta.url);

scripts/check-cross-package-test-inputs.mjs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,51 @@ const CROSS_PACKAGE_TEST_INPUTS = {
138138
// import outside these paths fails `check:examples-live-imports`, which
139139
// matches each coupling target against these globs -- so narrowing here
140140
// cannot quietly reopen the blind spot.
141+
//
142+
// The `content/docs` globs are hand-written prose three e2e tests pin, to
143+
// enforce the #6730 ruling that the NDJSON exception "stays declared, not just
144+
// implemented" -- the declaration has to be findable in the page a script
145+
// author actually meets, so the page IS an input. All three were invisible to
146+
// this gate until #8995 taught the detector their seed spelling, and the miss
147+
// is not theoretical: PR #8983 reworded `deployment/index.mdx` to "one compact
148+
// JSON document per line", which every fact survived but the literal
149+
// `/one\s+per\s+line/i` pin did not. Undeclared, cli was outside the affected
150+
// set, so PR CI was green and the merge queue was the first signal -- it
151+
// dequeued the PR and took two unrelated PRs down as batch collateral.
152+
// test/cloud-login-json-ndjson.e2e.test.ts reads deployment/cli.mdx and
153+
// deployment/index.mdx.
154+
// test/login-json-ndjson.e2e.test.ts reads deployment/cli.mdx and
155+
// permissions/authentication.mdx (the page describing the device flow).
156+
// test/login-json-noninteractive.e2e.test.ts reads deployment/cli.mdx.
157+
// Per-page rather than `content/docs/**`: docs are edited far more often than
158+
// any package here, and a subtree glob would put cli's e2e suite on every
159+
// documentation PR.
160+
//
161+
// `connector-mcp-plugin.ts` is read by test/serve-capability-identity.test.ts,
162+
// which pins that the connector still registers the name the #7652 repro uses
163+
// rather than importing the class. It surfaced with the three above and has the
164+
// same shape of blind spot, but the gate could not have named it: the test
165+
// spells the path RELATIVE (`resolve(HERE, '../../connectors/...')`), and the
166+
// literal-coverage check below only collects repo-relative literals.
167+
//
168+
// `check-nul-bytes.mjs` is the one entry no test READS -- it is named in a
169+
// comment in login-json-noninteractive.e2e.test.ts. The literal collector takes
170+
// quoted paths without parsing, so a mention forces a declaration; that is the
171+
// designed trade (over-collection can only widen a radius, never narrow one),
172+
// and declaring one rarely-touched file is cheaper than teaching the scanner to
173+
// tell prose from code, or than rewording a comment to dodge a scanner.
141174
globs: [
142175
'packages/verify/src/**',
143176
'packages/plugins/plugin-security/src/**',
144177
'packages/services/service-cluster/src/**',
178+
'packages/connectors/connector-mcp/src/connector-mcp-plugin.ts',
145179
'examples/app-showcase/src/ui/views/contact.view.ts',
146180
'examples/app-showcase/src/data/objects/semantic-zoo.object.ts',
147181
'examples/app-showcase/src/ui/pages/task-triage.page.ts',
182+
'content/docs/deployment/cli.mdx',
183+
'content/docs/deployment/index.mdx',
184+
'content/docs/permissions/authentication.mdx',
185+
'scripts/check-nul-bytes.mjs',
148186
],
149187
},
150188
'@objectstack/lint': {
@@ -299,6 +337,9 @@ export const RECOGNISED_PATH_SPELLINGS = [
299337
"const HERE = dirname(fileURLToPath(import.meta.url)); // seed (ESM)",
300338
'const HERE = __dirname; // seed (CJS)',
301339
'const HERE = import.meta.dirname; // and dirname(import.meta.filename)',
340+
"const HERE = resolve(fileURLToPath(import.meta.url), '..'); // seed, walked",
341+
' // from the FILE instead of named;',
342+
' // import.meta.filename works too',
302343
"const P = resolve(HERE, '<rel>'); // join() and the path.* forms too",
303344
"const P = fileURLToPath(new URL('<rel>', import.meta.url));",
304345
"const P = new URL('<rel>', import.meta.url);",
@@ -416,6 +457,20 @@ function pathExpression(expr, hereDepth, known) {
416457
if (/^(?:path\.)?dirname\(\s*import\.meta\.filename\s*\)$/.test(expr)) {
417458
return { end: hereDepth, min: hereDepth, vendored: false };
418459
}
460+
// The two seeds above NAME the directory. `import.meta.url` and
461+
// `import.meta.filename` name the FILE, which sits one level below it, and an
462+
// author reaches that same directory by WALKING instead — most often
463+
// `resolve(fileURLToPath(import.meta.url), '..')`. Modelling the file at
464+
// `hereDepth + 1` is what makes the walked form come out equal to the named one
465+
// through the ordinary literal walk below, rather than needing a case of its own,
466+
// and it is precisely Node's `resolve`/`join`, which treat a file argument as a
467+
// directory prefix like any other. Unrecognised until #8995: three packages/cli
468+
// e2e tests seed this way, so their reads of `content/docs/**` produced no flag
469+
// and went undeclared — the silence this list exists to prevent, and it cost a
470+
// merge-queue dequeue (PR #8983) before anyone saw it.
471+
if (expr === 'import.meta.url' || expr === 'import.meta.filename') {
472+
return { end: hereDepth + 1, min: hereDepth + 1, vendored: false };
473+
}
419474

420475
// A `new URL(rel, import.meta.url)` resolves against the importing FILE, so
421476
// its base is the file's directory — the same base as the two seeds above.
@@ -838,6 +893,61 @@ function selfTest() {
838893
!at("const HERE = import.meta.dirname;\nconst FIX = resolve(HERE, '../fixtures');", 2),
839894
);
840895

896+
// The seed WALKED from the file rather than named off it (#8995). Three
897+
// packages/cli e2e tests spell it this way; before the file itself was a
898+
// recognised expression the whole chain below resolved to `undefined`, so the
899+
// reads produced no flag and no declaration -- silently, which is the one
900+
// failure mode this detector exists to not have.
901+
ok(
902+
'flags a resolve(fileURLToPath(import.meta.url), $DOTDOT) seed (packages/cli e2e)',
903+
at(
904+
"const HERE = resolve(fileURLToPath(import.meta.url), '..');\n" +
905+
"const REPO_ROOT = resolve(HERE, '../../..');\n" +
906+
"const D = resolve(REPO_ROOT, 'content/docs/deployment/cli.mdx');",
907+
1,
908+
),
909+
);
910+
ok(
911+
'flags the same seed with the climb and the tail in ONE three-argument resolve',
912+
at(
913+
"const HERE = resolve(fileURLToPath(import.meta.url), '..');\n" +
914+
"const D = resolve(HERE, '../../..', 'content/docs/deployment/cli.mdx');",
915+
1,
916+
),
917+
);
918+
ok(
919+
'flags the walked seed via join() and the path.* form',
920+
at(
921+
"const HERE = path.join(path.dirname(fileURLToPath(import.meta.url)), '.');\n" +
922+
"const S = path.resolve(HERE, '../../other-pkg/src/x.ts');",
923+
1,
924+
),
925+
);
926+
ok(
927+
'flags a walked import.meta.filename seed',
928+
at("const HERE = resolve(import.meta.filename, '..');\nconst S = resolve(HERE, '../../other-pkg/src/x.ts');", 1),
929+
);
930+
// The walked seed and the named seed address the same directory, so every
931+
// verdict must agree between them. This is the case that fails if the file is
932+
// ever modelled at its directory's depth instead of one below it.
933+
ok(
934+
'walked seed agrees with the named seed on an in-package path',
935+
!at("const HERE = resolve(fileURLToPath(import.meta.url), '..');\nconst FIX = resolve(HERE, '../fixtures');", 2) &&
936+
!at("const HERE = dirname(fileURLToPath(import.meta.url));\nconst FIX = resolve(HERE, '../fixtures');", 2),
937+
);
938+
ok(
939+
'does NOT flag the walked seed climbing into node_modules (the tsx bin those tests resolve)',
940+
!at(
941+
"const HERE = resolve(fileURLToPath(import.meta.url), '..');\n" +
942+
"const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');",
943+
1,
944+
),
945+
);
946+
ok(
947+
'does NOT flag the bare file expression itself (it names its own file)',
948+
!at("const SELF = fileURLToPath(import.meta.url);\nconst C = readFileSync(SELF, 'utf8');", 2),
949+
);
950+
841951
const failed = cases.filter((c) => !c.cond);
842952
for (const c of cases) console.log(`${c.cond ? 'ok ' : 'FAIL'} ${c.label}`);
843953
if (failed.length) {

turbo.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,14 @@
6262
"$TURBO_ROOT$/packages/verify/src/**",
6363
"$TURBO_ROOT$/packages/plugins/plugin-security/src/**",
6464
"$TURBO_ROOT$/packages/services/service-cluster/src/**",
65+
"$TURBO_ROOT$/packages/connectors/connector-mcp/src/connector-mcp-plugin.ts",
6566
"$TURBO_ROOT$/examples/app-showcase/src/ui/views/contact.view.ts",
6667
"$TURBO_ROOT$/examples/app-showcase/src/data/objects/semantic-zoo.object.ts",
67-
"$TURBO_ROOT$/examples/app-showcase/src/ui/pages/task-triage.page.ts"
68+
"$TURBO_ROOT$/examples/app-showcase/src/ui/pages/task-triage.page.ts",
69+
"$TURBO_ROOT$/content/docs/deployment/cli.mdx",
70+
"$TURBO_ROOT$/content/docs/deployment/index.mdx",
71+
"$TURBO_ROOT$/content/docs/permissions/authentication.mdx",
72+
"$TURBO_ROOT$/scripts/check-nul-bytes.mjs"
6873
]
6974
},
7075
"@objectstack/lint#test": {

0 commit comments

Comments
 (0)