From 7b0310cac427ef629a7604be72098ca20ce39320 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:18:11 +0800 Subject: [PATCH] fix(scripts): run publint/tsc via node JS entry, not a shell .cmd shim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shell:true space-joins the executable and args UNESCAPED (Node DEP0190), so an absolute .cmd path breaks whenever the repo path contains spaces and `pnpm run hygiene` fails. Invoke publint's (`node_modules/publint/src/cli.js`) and tsc's (`node_modules/typescript/bin/tsc`) JS entry through process.execPath instead — no shell, extension-agnostic, identical on every platform, matching the pattern already used by doc-typecheck.ts. Addresses ds-review-bot on #324. --- scripts/publint-all.ts | 14 +++++++------- scripts/verify-node-next-types.ts | 11 +++++------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/scripts/publint-all.ts b/scripts/publint-all.ts index 722cf4d2ac..0911316f18 100644 --- a/scripts/publint-all.ts +++ b/scripts/publint-all.ts @@ -6,17 +6,18 @@ import { promisify } from 'node:util' const execFileAsync = promisify(execFile) const CONCURRENCY_ENV = 'DSH_PUBLINT_CONCURRENCY' -const isWindows = process.platform === 'win32' // Discover harness packages at packages//; group containers, // examples, and private vendored sources are not package targets. const root = resolve(import.meta.dirname, '..') const packagesRoot = resolve(root, 'packages') -// On Windows recent Node (CVE-2024-27980) refuses to launch .cmd/.bat bin -// shims without shell:true. Use the absolute path to the .cmd shim so the -// subprocess (not a pnpm child — PATH lacks node_modules/.bin) still finds it. -const publintBin = resolve(root, `node_modules/.bin/publint${isWindows ? '.cmd' : ''}`) +// Run publint's JS CLI through the current node, not the .bin shim: the +// extensionless shim isn't spawnable on Windows (CVE-2024-27980) and the .cmd +// variant needs shell:true, which space-joins args UNESCAPED (DEP0190) and +// breaks when the repo path contains spaces. The JS entry is identical on every +// platform (`bin` is `./src/cli.js` per publint's package.json). +const publintCli = resolve(root, 'node_modules/publint/src/cli.js') type PublintResult = | { path: string; status: 'passed'; stdout: string; stderr: string } @@ -56,11 +57,10 @@ function outputText(value: unknown): string { async function runPublint(path: string): Promise { try { - const { stdout, stderr } = await execFileAsync(publintBin, [path], { + const { stdout, stderr } = await execFileAsync(process.execPath, [publintCli, path], { cwd: root, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, - shell: isWindows, }) return { path, status: 'passed', stdout, stderr } } catch (error: unknown) { diff --git a/scripts/verify-node-next-types.ts b/scripts/verify-node-next-types.ts index 7ee046c7bb..3b577a39bc 100644 --- a/scripts/verify-node-next-types.ts +++ b/scripts/verify-node-next-types.ts @@ -10,7 +10,6 @@ import { execFileSync } from 'node:child_process' import { existsSync, globSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' -const isWindows = process.platform === 'win32' const root = resolve(import.meta.dirname, '..') interface ExportTarget { @@ -144,13 +143,13 @@ try { .join('\n') writeFileSync(resolve(tmp, 'index.ts'), `${imports}\n`) - // On Windows the bin shim is a .cmd file; recent Node (CVE-2024-27980) - // refuses to launch .cmd/.bat via execFileSync without shell:true. - const tscBin = isWindows ? resolve(root, 'node_modules/.bin/tsc.cmd') : resolve(root, 'node_modules/.bin/tsc') - execFileSync(tscBin, ['-p', resolve(tmp, 'tsconfig.json'), '--pretty', 'false'], { + // tsc's JS entry via the current node, not the .bin shim: the extensionless + // shim isn't spawnable on Windows (CVE-2024-27980) and the .cmd variant needs + // shell:true, which space-joins args UNESCAPED (DEP0190) — a hazard for the + // temp tsconfig path. The JS entry behaves identically on every platform. + execFileSync(process.execPath, ['node_modules/typescript/bin/tsc', '-p', resolve(tmp, 'tsconfig.json'), '--pretty', 'false'], { cwd: root, stdio: 'pipe', - shell: isWindows, }) console.log(`verify-node-next-types: ${packages.length} workspace package declaration surface(s) compile under NodeNext.`) } catch (error: unknown) {