fix(cli): avoid cold compile-cache writes for piped bare usage (#2643)

bin-wrapper enables Node's compile cache except for a bare invocation with stdin or stdout piped or redirected, which only prints usage. That case loaded the command tree only to print usage, and with a cold cache the deferred cache writes near exit could push it past a caller's deadline after the usage was already printed. Any script argument, including piped --help and --version, and a bare run with both streams on a terminal, keep the existing cache attempt, matching the front door's own predicate. A new bin-wrapper test covers the piped and terminal cases. Contributed by seanmcn; reported in #2642.
This commit is contained in:
Seán McNamara authored and GitHub committed 2026-11-07 01:07:22 -08:00
1 parent b1065870f6
commit 378d25301d
2 files changed
+39 -1

No files matched your search

+2 -1
View File
@@ -12,7 +12,8 @@ const REEXEC_GUARD_ENV = "OPENRIG_BIN_REEXEC";
// Each `rig` is a new process: Node's on-disk compile cache lets the next call skip compiling the modules
// again. The cache dates from Node v22.1.0, but this JavaScript API from v22.8.0: before it, `?.()` does
// nothing. NODE_COMPILE_CACHE and NODE_DISABLE_COMPILE_CACHE still control it.
module.enableCompileCache?.();
// A piped bare invocation only prints usage; cold-cache writes can otherwise delay its exit.
if (process.argv.length > 2 || (process.stdin.isTTY && process.stdout.isTTY)) module.enableCompileCache?.();
export function resolveBinEntry(invokedPath = process.argv[1], moduleUrl = import.meta.url): string {
const wrapperPath = invokedPath ? realpathSync(invokedPath) : realpathSync(fileURLToPath(moduleUrl));
+37
View File
@@ -1,6 +1,9 @@
import { afterEach, describe, expect, it } from "vitest";
import { spawnSync } from "node:child_process";
import ts from "typescript";
import fs from "node:fs";
import os from "node:os";
import module from "node:module";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { resolveBinEntry, resolveNodeReexecBinary, run } from "../src/bin-wrapper.js";
@@ -15,6 +18,40 @@ describe("bin-wrapper", () => {
}
});
it.skipIf(!module.getCompileCacheDir || !module.enableCompileCache).each([
{ args: [], interactive: false, explicitCache: false, expected: false },
{ args: ["--version"], interactive: false, explicitCache: false, expected: true },
{ args: [], interactive: false, explicitCache: true, expected: true },
{ args: [], interactive: true, explicitCache: false, expected: true },
])("compile cache for args=$args, interactive=$interactive, explicitCache=$explicitCache", ({ args, interactive, explicitCache, expected }) => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openrig-bin-cache-"));
const dist = path.join(tempDir, "dist");
fs.mkdirSync(dist);
fs.writeFileSync(path.join(tempDir, "package.json"), '{ "type": "module" }');
const wrapper = path.join(dist, "bin-wrapper.js");
const source = fs.readFileSync(new URL("../src/bin-wrapper.ts", import.meta.url), "utf8");
const { outputText } = ts.transpileModule(source, {
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext },
});
fs.writeFileSync(wrapper, outputText);
const report = `import { getCompileCacheDir } from "node:module";
export async function runProgram() { console.log(JSON.stringify({ enabled: !!getCompileCacheDir() })); }
`;
fs.writeFileSync(path.join(dist, "index.js"), report + "export function createProgram() { return {}; }\n");
fs.writeFileSync(path.join(dist, "lazy-program.js"), report + "export async function createLazyProgram() { return {}; }\n");
const env = { ...process.env, TMPDIR: tempDir };
delete env.NODE_DISABLE_COMPILE_CACHE;
delete env.NODE_COMPILE_CACHE;
if (explicitCache) env.NODE_COMPILE_CACHE = path.join(tempDir, "explicit-cache");
// Set stream flags before the wrapper loads; front-door.test.ts covers a real PTY launch.
const nodeArgs = interactive
? ["--import", "data:text/javascript,process.stdin.isTTY=true;process.stdout.isTTY=true"]
: [];
const result = spawnSync(process.execPath, [...nodeArgs, wrapper, ...args], { env, encoding: "utf8", timeout: 5000 });
expect(result.status, result.stderr).toBe(0);
expect(JSON.parse(result.stdout)).toEqual({ enabled: expected });
});
it("resolves the real dist entry from a symlinked wrapper path", () => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openrig-bin-wrapper-"));
const wrapperSource = fileURLToPath(new URL("../src/bin-wrapper.ts", import.meta.url));