148148// node scripts/check-cross-package-test-inputs.mjs --self-test
149149
150150import { readFileSync , readdirSync , statSync , existsSync , writeFileSync , mkdtempSync } from 'node:fs' ;
151+ import { spawnSync } from 'node:child_process' ;
151152import { tmpdir } from 'node:os' ;
152153import { join , resolve , relative , dirname , sep , isAbsolute } from 'node:path' ;
153- import { fileURLToPath } from 'node:url' ;
154+ import { fileURLToPath , pathToFileURL } from 'node:url' ;
154155import process from 'node:process' ;
155156
157+ import { isEntrypoint } from './invoked-as.mjs' ;
158+
156159const HERE = dirname ( fileURLToPath ( import . meta. url ) ) ;
157160const REPO_ROOT = resolve ( HERE , '..' ) ;
158161
@@ -1882,6 +1885,31 @@ function selfTest() {
18821885 unioned . length > 0 && unioned . every ( ( i ) => existsSync ( resolve ( REPO_ROOT , i . path ) ) ) ,
18831886 ) ;
18841887
1888+ // ── the entry guard, driven for real ────────────────────────────────────
1889+ //
1890+ // This module EXPORTS helpers, and the dispatch below used to run on IMPORT:
1891+ // `await import(...)` printed this gate's verdict into the importer's stdout
1892+ // and, on an unhappy tree, called `process.exit(1)` -- handing a consumer that
1893+ // asked for `globToRegExp` this gate's verdict as its own exit status.
1894+ // `check-examples-live-imports.mjs` hand-copied the helper rather than pay it.
1895+ //
1896+ // A spawned child is the only honest witness: the guard's answer depends on
1897+ // what node puts in `process.argv[1]`, which cannot be modelled in-process.
1898+ // Without this case the guard can be deleted as quietly as it was missing.
1899+ const importProbe = spawnSync (
1900+ process . execPath ,
1901+ [ '--input-type=module' , '-e' , `await import(${ JSON . stringify ( pathToFileURL ( fileURLToPath ( import . meta. url ) ) . href ) } );\nconsole.log('ALIVE');` ] ,
1902+ { encoding : 'utf8' } ,
1903+ ) ;
1904+ ok (
1905+ 'importing this module prints NOTHING -- the dispatch is behind the entry guard' ,
1906+ ( importProbe . stdout || '' ) . trim ( ) === 'ALIVE' && ( importProbe . stderr || '' ) . trim ( ) === '' ,
1907+ ) ;
1908+ ok (
1909+ 'importing this module does not exit the importer -- it survives to run its own code' ,
1910+ importProbe . status === 0 && ( importProbe . stdout || '' ) . includes ( 'ALIVE' ) ,
1911+ ) ;
1912+
18851913 const failed = cases . filter ( ( c ) => ! c . cond ) ;
18861914 for ( const c of cases ) console . log ( `${ c . cond ? 'ok ' : 'FAIL' } ${ c . label } ` ) ;
18871915 if ( failed . length ) {
@@ -1891,19 +1919,37 @@ function selfTest() {
18911919 console . log ( `\nAll ${ cases . length } self-test cases passed.` ) ;
18921920}
18931921
1894- const argv = process . argv . slice ( 2 ) ;
1895- if ( argv . includes ( '--self-test' ) ) selfTest ( ) ;
1896- else if ( argv . includes ( '--list-escapes' ) ) {
1897- for ( const [ name , info ] of [ ...findEscapingPackages ( ) ] . sort ( ) ) {
1898- console . log ( `${ name } (${ info . dir } )` ) ;
1899- for ( const t of info . tests ) console . log ( ` ${ t } ` ) ;
1900- }
1901- } else if ( argv . includes ( '--union-into' ) ) {
1902- const listPath = argv [ argv . indexOf ( '--union-into' ) + 1 ] ;
1903- const changedPath = argv [ argv . indexOf ( '--changed' ) + 1 ] ;
1904- if ( ! listPath || ! changedPath ) {
1905- console . error ( 'usage: check-cross-package-test-inputs.mjs --union-into <turbo-ls.json> --changed <file>' ) ;
1906- process . exit ( 2 ) ;
1907- }
1908- unionInto ( listPath , changedPath ) ;
1909- } else verify ( ) ;
1922+ // ---------------------------------------------------------------------------
1923+ // Entry guard -- this module EXPORTS helpers, so the dispatch must not run on
1924+ // import.
1925+ //
1926+ // Until the guard was added, the `else verify()` fallthrough below fired on
1927+ // `await import(...)` as well as on invocation. Importing the module for
1928+ // `globToRegExp` or `findEscapingPackages` printed this gate's verdict to the
1929+ // importer's stdout, and on an unhappy tree called `process.exit(1)` -- so a
1930+ // consumer inherited THIS gate's verdict as its own exit status, having asked
1931+ // only for a helper. `check-examples-live-imports.mjs` paid that cost: it
1932+ // hand-copied `globToRegExp` rather than import it, naming this load-time gate
1933+ // as the reason.
1934+ //
1935+ // `isEntrypoint` is the repo's one answer to "was I run?" -- see
1936+ // `scripts/invoked-as.mjs` for why the hand-typed spellings are wrong, and
1937+ // `check:entry-guard`, which fails any other spelling in `scripts/**`.
1938+ if ( isEntrypoint ( import . meta. url ) ) {
1939+ const argv = process . argv . slice ( 2 ) ;
1940+ if ( argv . includes ( '--self-test' ) ) selfTest ( ) ;
1941+ else if ( argv . includes ( '--list-escapes' ) ) {
1942+ for ( const [ name , info ] of [ ...findEscapingPackages ( ) ] . sort ( ) ) {
1943+ console . log ( `${ name } (${ info . dir } )` ) ;
1944+ for ( const t of info . tests ) console . log ( ` ${ t } ` ) ;
1945+ }
1946+ } else if ( argv . includes ( '--union-into' ) ) {
1947+ const listPath = argv [ argv . indexOf ( '--union-into' ) + 1 ] ;
1948+ const changedPath = argv [ argv . indexOf ( '--changed' ) + 1 ] ;
1949+ if ( ! listPath || ! changedPath ) {
1950+ console . error ( 'usage: check-cross-package-test-inputs.mjs --union-into <turbo-ls.json> --changed <file>' ) ;
1951+ process . exit ( 2 ) ;
1952+ }
1953+ unionInto ( listPath , changedPath ) ;
1954+ } else verify ( ) ;
1955+ }
0 commit comments