@@ -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 ( / ^ (?: p a t h \. ) ? d i r n a m e \( \s * i m p o r t \. m e t a \. f i l e n a m e \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 ) {
0 commit comments