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
4 changes: 4 additions & 0 deletions .changeset/6092-entry-guard-sweep.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
---
---

Tooling only, no package released: every `scripts/**` entry guard now goes through one predicate. The 29 hand-typed guards `check-entry-guard.mjs` baselined (nine distinct spellings across 28 `.mjs` files, plus `shadcn-sync.js`) are converted to `isEntrypoint(import.meta.url)` and `KNOWN_HAND_TYPED_GUARDS` is empty. Twenty-eight of them were silently wrong: reached through a symlink they compared two different paths, answered `false`, and did nothing — exit 0 with no output, which a CI wrapper holding `result.status` reads as a pass (objectui#6092).
32 changes: 29 additions & 3 deletions scripts/__tests__/check-doc-component-types.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -546,9 +546,35 @@ describe('wiring — the gate is reachable and a docs-only PR starts it', () =>
const yaml = yamlOf('doc-component-types.yml');
expect(yaml).not.toContain('pnpm install');
expect(yaml).not.toContain('corepack');
const gate = fs.readFileSync(path.join(repoRoot, SCRIPT), 'utf8');
const imports = [...gate.matchAll(/^import .* from '([^']+)';$/gm)].map((m) => m[1]);
expect(imports.every((spec) => spec.startsWith('node:')), `non-builtin import in the gate: ${imports}`).toBe(true);

// Walk the WHOLE static import graph, not just the gate's own first line.
// objectui#6092 converted this gate's entry guard to `./invoked-as.mjs`, a
// relative import — install-free, but not spelled `node:`. Asserting on the
// gate's own imports alone would have had to be loosened to let that
// through, and a loosened one-file assertion is how a relative import that
// DOES pull a package in later lands unnoticed. Following the graph keeps
// the original claim ("this needs no node_modules") literally true, and
// makes it true of every module the gate reaches.
const seen = new Set<string>();
const external: string[] = [];
const walk = (abs: string) => {
if (seen.has(abs)) return;
seen.add(abs);
const source = fs.readFileSync(abs, 'utf8');
for (const m of source.matchAll(/^import .* from '([^']+)';$/gm)) {
const spec = m[1];
if (spec.startsWith('node:')) continue;
if (!spec.startsWith('.')) {
external.push(`${path.relative(repoRoot, abs)} -> ${spec}`);
continue;
}
walk(path.resolve(path.dirname(abs), spec));
}
};
walk(path.join(repoRoot, SCRIPT));

expect(seen.size, 'the import walk read only the gate itself — it followed nothing').toBeGreaterThan(1);
expect(external, `the gate's import graph reaches a package, so it needs an install: ${external}`).toEqual([]);
});
});

Expand Down
3 changes: 2 additions & 1 deletion scripts/check-action-forward-parity.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,6 +127,7 @@ import { createRequire } from "module";
import { readFileSync, existsSync } from "fs";
import { resolve, dirname } from "path";
import { fileURLToPath } from "url";
import { isEntrypoint } from "./invoked-as.mjs";

const HERE = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(HERE, "..");
Expand DownExpand Up@@ -1058,7 +1059,7 @@ export function analyze(root = REPO_ROOT, options = {}) {
}

// ── CLI ──────────────────────────────────────────────────────────────────────
const invokedDirectly = process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
const invokedDirectly = isEntrypoint(import.meta.url);

if (invokedDirectly) {
let result;
Expand Down
3 changes: 2 additions & 1 deletion scripts/check-changeset-fixed.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,6 +62,7 @@
import { readFileSync, readdirSync, statSync } from "fs";
import { resolve, dirname, join } from "path";
import { fileURLToPath } from "url";
import { isEntrypoint } from "./invoked-as.mjs";

const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");

Expand DownExpand Up@@ -229,6 +230,6 @@ function main() {
}

// Only run when invoked as a script — the tests import the helpers above.
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
if (isEntrypoint(import.meta.url)) {
process.exit(main());
}
3 changes: 2 additions & 1 deletion scripts/check-changeset-no-major.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,7 @@
import { readFileSync, readdirSync } from 'node:fs';
import { resolve, dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { isEntrypoint } from './invoked-as.mjs';

const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const changesetDir = resolve(root, '.changeset');
Expand DownExpand Up@@ -158,6 +159,6 @@ release sets OBJECTUI_ALLOW_MAJOR=1.
}

// Only run when invoked as a script — the tests import the parser above.
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
if (isEntrypoint(import.meta.url)) {
process.exit(main());
}
3 changes: 2 additions & 1 deletion scripts/check-changeset-presence.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -247,6 +247,7 @@ import { execFileSync } from 'node:child_process';
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { isEntrypoint } from './invoked-as.mjs';

const scriptDir = dirname(fileURLToPath(import.meta.url));

Expand DownExpand Up@@ -722,7 +723,7 @@ export function verdict(analysis) {

// -- CLI ----------------------------------------------------------------------

const invokedDirectly = process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
const invokedDirectly = isEntrypoint(import.meta.url);

if (invokedDirectly) {
const argOf = (name) => {
Expand Down
3 changes: 2 additions & 1 deletion scripts/check-control-bytes.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,6 +88,7 @@ import { execFileSync } from 'node:child_process';
import { lstatSync, readFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { isEntrypoint } from './invoked-as.mjs';

/**
* Control characters that are never legitimate in a text file: the C0 range
Expand DownExpand Up@@ -356,7 +357,7 @@ nobody removes is how a baseline turns into a permanent skip-list.`);
// Run only when invoked directly — the test suite imports `scan`/`classify`
// from here and must not trigger a repo scan (or a process.exit) on import.
// Same guard shape as scripts/check-changeset-no-major.mjs.
const invokedDirectly = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
const invokedDirectly = isEntrypoint(import.meta.url);

if (invokedDirectly) {
if (process.argv.includes('--list')) {
Expand Down
4 changes: 2 additions & 2 deletions scripts/check-cross-repo-closer-outcome.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,8 +129,8 @@ import { execFileSync } from 'node:child_process';
import { createRequire } from 'node:module';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { isMap, isSeq, parseDocument } from 'yaml';
import { isEntrypoint } from './invoked-as.mjs';

const WORKFLOW = '.github/workflows/cross-repo-issue-closer.yml';
const JOB = 'close-foreign-issues';
Expand DownExpand Up@@ -1170,7 +1170,7 @@ async function selfTest() {
// reverse-verification route needs `extractScript` / `judge` pointed at another
// tree (a pre-fix checkout), and a module that runs its gate on import would
// silently judge THIS repo instead and print a pass about the wrong subject.
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
if (isEntrypoint(import.meta.url)) {
if (process.argv.includes('--self-test')) await selfTest();
else if (process.argv.includes('--list')) list();
else await main();
Expand Down
3 changes: 2 additions & 1 deletion scripts/check-designer-field-key-parity.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -125,6 +125,7 @@ import { createRequire } from "module";
import { readFileSync, existsSync } from "fs";
import { resolve, dirname } from "path";
import { fileURLToPath } from "url";
import { isEntrypoint } from "./invoked-as.mjs";

const HERE = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(HERE, "..");
Expand DownExpand Up@@ -459,6 +460,6 @@ async function main() {
console.log("\ndesigner-field-key-parity: OK");
}

if (resolve(process.argv[1] ?? "") === resolve(fileURLToPath(import.meta.url))) {
if (isEntrypoint(import.meta.url)) {
await main();
}
3 changes: 2 additions & 1 deletion scripts/check-doc-component-types.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -137,6 +137,7 @@
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { dirname, join, relative, resolve, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import { isEntrypoint } from './invoked-as.mjs';

const scriptDir = dirname(fileURLToPath(import.meta.url));

Expand DownExpand Up@@ -995,7 +996,7 @@ const HINTS = {
'A doc file has an unclosed ``` fence. The scan cannot separate code from prose past that point.',
};

const invokedDirectly = process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
const invokedDirectly = isEntrypoint(import.meta.url);

if (invokedDirectly) {
const argOf = (name) => {
Expand Down
3 changes: 2 additions & 1 deletion scripts/check-doc-links.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -460,6 +460,7 @@
import { readdirSync, readFileSync, statSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { isEntrypoint } from './invoked-as.mjs';

const DOCS_ROUTE_PREFIX = '/docs';
const MARKDOWN_LINK_RE = /\[[^\]]+\]\(([^)]+)\)/g;
Expand DownExpand Up@@ -967,7 +968,7 @@ export function collectBrokenLinks(repoRoot) {
// Run only when invoked directly — the test suite imports the helpers above and
// must not trigger a repo scan (or a `process.exit`) on import. Same guard shape
// as scripts/check-control-bytes.mjs.
const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
const invokedDirectly = isEntrypoint(import.meta.url);

const HINTS = {
relative:
Expand Down
3 changes: 2 additions & 1 deletion scripts/check-doc-snippet-types.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -259,6 +259,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
import { dirname, join, relative, resolve, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import ts from 'typescript';
import { isEntrypoint } from './invoked-as.mjs';

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

Expand DownExpand Up@@ -1162,7 +1163,7 @@ function main() {
return 0;
}

if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
if (isEntrypoint(import.meta.url)) {
process.exit(main());
}

Expand Down
4 changes: 2 additions & 2 deletions scripts/check-eager-closure-budget.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,7 @@

import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { isEntrypoint } from './invoked-as.mjs';

/**
* Ceiling for the console eager closure, in gzipped bytes. See the header for
Expand DownExpand Up@@ -340,6 +340,6 @@ export function main(argv = process.argv.slice(2)) {
return result.status === 'fail' ? 1 : 2;
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
if (isEntrypoint(import.meta.url)) {
process.exit(main());
}
Loading
Loading