From 5e01ab714a1d68091383a1a4135d7fd4543b66f8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 27 Feb 2026 08:46:44 +0100 Subject: [PATCH] repo-tools: Add _handle stub preload for file-backed stdout Add a --require preload that stubs process.stdout._handle when stdout is a SyncWriteStream (file-backed). Some CLI code accesses _handle.setBlocking directly and would crash without the stub. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- packages/repo-tools/src/commands/util.ts | 48 +++++++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/repo-tools/src/commands/util.ts b/packages/repo-tools/src/commands/util.ts index bce2359589..c56a98fc04 100644 --- a/packages/repo-tools/src/commands/util.ts +++ b/packages/repo-tools/src/commands/util.ts @@ -15,10 +15,42 @@ */ import { spawnSync } from 'node:child_process'; -import { openSync, closeSync, readFileSync, unlinkSync } from 'node:fs'; +import { + openSync, + closeSync, + readFileSync, + unlinkSync, + writeFileSync, +} from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +// Preload script that stubs process.stdout._handle when stdout is backed by a +// file (SyncWriteStream). Some CLI code accesses _handle.setBlocking directly +// and would crash without this. +const preloadContent = ` +if (process.stdout && !process.stdout._handle) { + process.stdout._handle = { setBlocking() {} }; +} +`; + +let preloadPath: string | undefined; + +function getPreloadPath() { + if (!preloadPath) { + preloadPath = join(tmpdir(), `backstage-stdout-preload-${process.pid}.cjs`); + writeFileSync(preloadPath, preloadContent); + process.on('exit', () => { + try { + unlinkSync(preloadPath!); + } catch { + /* ignore */ + } + }); + } + return preloadPath; +} + /** * Redirect stdout to a temp file so that Node.js creates a SyncWriteStream * (synchronous writes) in the child instead of an async pipe stream. This @@ -35,11 +67,15 @@ export function createBinRunner(cwd: string, path: string) { const outFd = openSync(outPath, 'w'); try { - const result = spawnSync('node', args, { - cwd, - stdio: ['ignore', outFd, 'pipe'], - maxBuffer: 10 * 1024 * 1024, - }); + const result = spawnSync( + 'node', + ['--require', getPreloadPath(), ...args], + { + cwd, + stdio: ['ignore', outFd, 'pipe'], + maxBuffer: 10 * 1024 * 1024, + }, + ); closeSync(outFd); const stdout = readFileSync(outPath, 'utf8');