Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 63 additions & 17 deletions scripts/check-cross-package-test-inputs.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,11 +148,14 @@
// node scripts/check-cross-package-test-inputs.mjs --self-test

import { readFileSync, readdirSync, statSync, existsSync, writeFileSync, mkdtempSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { tmpdir } from 'node:os';
import { join, resolve, relative, dirname, sep, isAbsolute } from 'node:path';
import { fileURLToPath } from 'node:url';
import { fileURLToPath, pathToFileURL } from 'node:url';
import process from 'node:process';

import { isEntrypoint } from './invoked-as.mjs';

const HERE = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(HERE, '..');

Expand DownExpand Up@@ -1882,6 +1885,31 @@ function selfTest() {
unioned.length > 0 && unioned.every((i) => existsSync(resolve(REPO_ROOT, i.path))),
);

// ── the entry guard, driven for real ────────────────────────────────────
//
// This module EXPORTS helpers, and the dispatch below used to run on IMPORT:
// `await import(...)` printed this gate's verdict into the importer's stdout
// and, on an unhappy tree, called `process.exit(1)` -- handing a consumer that
// asked for `globToRegExp` this gate's verdict as its own exit status.
// `check-examples-live-imports.mjs` hand-copied the helper rather than pay it.
//
// A spawned child is the only honest witness: the guard's answer depends on
// what node puts in `process.argv[1]`, which cannot be modelled in-process.
// Without this case the guard can be deleted as quietly as it was missing.
const importProbe = spawnSync(
process.execPath,
['--input-type=module', '-e', `await import(${JSON.stringify(pathToFileURL(fileURLToPath(import.meta.url)).href)});\nconsole.log('ALIVE');`],
{ encoding: 'utf8' },
);
ok(
'importing this module prints NOTHING -- the dispatch is behind the entry guard',
(importProbe.stdout || '').trim() === 'ALIVE' && (importProbe.stderr || '').trim() === '',
);
ok(
'importing this module does not exit the importer -- it survives to run its own code',
importProbe.status === 0 && (importProbe.stdout || '').includes('ALIVE'),
);

const failed = cases.filter((c) => !c.cond);
for (const c of cases) console.log(`${c.cond ? 'ok ' : 'FAIL'} ${c.label}`);
if (failed.length) {
Expand All@@ -1891,19 +1919,37 @@ function selfTest() {
console.log(`\nAll ${cases.length} self-test cases passed.`);
}

const argv = process.argv.slice(2);
if (argv.includes('--self-test')) selfTest();
else if (argv.includes('--list-escapes')) {
for (const [name, info] of [...findEscapingPackages()].sort()) {
console.log(`${name} (${info.dir})`);
for (const t of info.tests) console.log(` ${t}`);
}
} else if (argv.includes('--union-into')) {
const listPath = argv[argv.indexOf('--union-into') + 1];
const changedPath = argv[argv.indexOf('--changed') + 1];
if (!listPath || !changedPath) {
console.error('usage: check-cross-package-test-inputs.mjs --union-into <turbo-ls.json> --changed <file>');
process.exit(2);
}
unionInto(listPath, changedPath);
} else verify();
// ---------------------------------------------------------------------------
// Entry guard -- this module EXPORTS helpers, so the dispatch must not run on
// import.
//
// Until the guard was added, the `else verify()` fallthrough below fired on
// `await import(...)` as well as on invocation. Importing the module for
// `globToRegExp` or `findEscapingPackages` printed this gate's verdict to the
// importer's stdout, and on an unhappy tree called `process.exit(1)` -- so a
// consumer inherited THIS gate's verdict as its own exit status, having asked
// only for a helper. `check-examples-live-imports.mjs` paid that cost: it
// hand-copied `globToRegExp` rather than import it, naming this load-time gate
// as the reason.
//
// `isEntrypoint` is the repo's one answer to "was I run?" -- see
// `scripts/invoked-as.mjs` for why the hand-typed spellings are wrong, and
// `check:entry-guard`, which fails any other spelling in `scripts/**`.
if (isEntrypoint(import.meta.url)) {
const argv = process.argv.slice(2);
if (argv.includes('--self-test')) selfTest();
else if (argv.includes('--list-escapes')) {
for (const [name, info] of [...findEscapingPackages()].sort()) {
console.log(`${name} (${info.dir})`);
for (const t of info.tests) console.log(` ${t}`);
}
} else if (argv.includes('--union-into')) {
const listPath = argv[argv.indexOf('--union-into') + 1];
const changedPath = argv[argv.indexOf('--changed') + 1];
if (!listPath || !changedPath) {
console.error('usage: check-cross-package-test-inputs.mjs --union-into <turbo-ls.json> --changed <file>');
process.exit(2);
}
unionInto(listPath, changedPath);
} else verify();
}
46 changes: 12 additions & 34 deletions scripts/check-examples-live-imports.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -107,6 +107,18 @@

import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
import { maskComments } from './js-comment-mask.mjs';
// `globToRegExp` decides whether a declared input glob really covers a coupling,
// so this gate and `check-cross-package-test-inputs` MUST agree about turbo's
// glob semantics -- `**` spans whole segments, `*` stays inside one.
//
// It used to be hand-copied into this file, with the reason recorded in the
// copy: that module "runs its gate at load time -- importing it would execute a
// second gate as a side effect of classifying". That was true, and it is no
// longer: the module's dispatch is behind `isEntrypoint(import.meta.url)`, so
// importing it is silent and exit-neutral. The reason for the copy is gone, so
// the copy is too -- one implementation, and no way for the two gates to drift
// apart on the semantics that decide both their verdicts.
import { globToRegExp } from './check-cross-package-test-inputs.mjs';
import { join, resolve, relative, dirname, sep, posix } from 'node:path';
import { fileURLToPath } from 'node:url';
import process from 'node:process';
Expand DownExpand Up@@ -309,40 +321,6 @@ function packagesWithExampleInputs() {
return declared;
}

/**
* Turbo input-glob semantics: `**` spans whole segments, `*` stays inside one.
*
* Mirrored from `globToRegExp` in `check-cross-package-test-inputs.mjs` rather
* than imported, because that module runs its gate at load time -- importing it
* would execute a second gate as a side effect of classifying. The duplication
* is pinned by `--self-test` on both sides; the two must agree, since this is
* the check that decides whether a declared radius really covers a coupling.
*/
function globToRegExp(glob) {
let re = '';
for (let i = 0; i < glob.length; i++) {
const c = glob[i];
if (c === '*') {
if (glob[i + 1] === '*') {
if (glob[i + 2] === '/') {
re += '(?:[^/]+/)*';
i += 2;
} else {
re += '.*';
i += 1;
}
} else {
re += '[^/]*';
}
} else if ('.+?^${}()|[]\\/'.includes(c)) {
re += `\\${c}`;
} else {
re += c;
}
}
return new RegExp(`^${re}$`);
}

/**
* Does one declared input glob cover this coupling target?
*
Expand Down
Loading