From b62c972f61f977bcb50e6255736085238ed8cd8f Mon Sep 17 00:00:00 2001 From: margaretjgu Date: Tue, 4 Aug 2026 14:42:48 -0400 Subject: [PATCH 1/2] fix(factory): route stderr writes through configureOutput --- src/factory.ts | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/factory.ts b/src/factory.ts index c04ee9e3..acd97b9a 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -241,6 +241,21 @@ function parseJsonContent (raw: string, source: string, cmd: OpaqueCommandHandle } } +/** + * Writes to stderr through Commander's `configureOutput().writeErr` channel rather than + * calling `process.stderr.write` directly. This is the same channel `cmd.error()` and + * tests (via `cmd.configureOutput({ writeErr })`) already use, so error output is never + * lost to a global stream patch racing across the CJS/ESM boundary (see #455). + * + * Note: `writeOut` is intentionally NOT migrated here. {@link configureHelpWithSchema} + * already overrides `writeOut` on every command for `--help` truncation, so routing + * regular handler output through it would change unrelated write semantics. + */ +function writeErr (cmd: OpaqueCommandHandle, str: string): void { + const write = cmd.configureOutput().writeErr ?? ((s: string) => process.stderr.write(s)) + write(str) +} + function isErrorResult (value: JsonValue): boolean { return ( typeof value === 'object' && @@ -535,7 +550,7 @@ export function defineCommand (config: CommandConfig): O const dotPath = (parts.length > 1 ? parts.slice(1) : parts).join('.') if (!isCommandAllowed(dotPath, resolvedConfig.commands)) { if (jsonFormat === true) { - process.stderr.write(JSON.stringify({ + writeErr(cmd, JSON.stringify({ error: { code: 'command_blocked', message: `command "${dotPath}" is not allowed by the current policy`, @@ -593,8 +608,7 @@ export function defineCommand (config: CommandConfig): O const { simplifyZodIssues, formatIssuesText } = await import('./lib/zod-error.js') const issues = simplifyZodIssues(result.error.issues) if (jsonFormat === true) { - const writeErr = cmd.configureOutput().writeErr ?? ((s: string) => process.stderr.write(s)) - writeErr(JSON.stringify({ + writeErr(cmd, JSON.stringify({ error: { code: 'input_validation_failed', message: `Input validation failed with ${issues.length} issue(s)`, @@ -621,9 +635,9 @@ export function defineCommand (config: CommandConfig): O assert(handlerResult !== undefined, `command ${JSON.stringify(config.name)}: handler must return a JsonValue`) if (isErrorResult(handlerResult)) { if (jsonFormat === true) { - process.stderr.write(JSON.stringify(handlerResult) + '\n') + writeErr(cmd, JSON.stringify(handlerResult) + '\n') } else { - process.stderr.write(`Error: ${formatHandlerError(handlerResult)}\n`) + writeErr(cmd, `Error: ${formatHandlerError(handlerResult)}\n`) } process.exitCode = 1 } else { @@ -639,11 +653,11 @@ export function defineCommand (config: CommandConfig): O } catch (err) { if (err instanceof TemplateAgainstPrimitiveError) { if (jsonFormat === true) { - process.stderr.write(JSON.stringify({ + writeErr(cmd, JSON.stringify({ error: { code: 'output_template_error', message: err.message }, }) + '\n') } else { - process.stderr.write(`Error: ${err.message}\n`) + writeErr(cmd, `Error: ${err.message}\n`) } process.exitCode = 1 } else { From 36596bb5c2489985385e94fa64266471b4d8a3bb Mon Sep 17 00:00:00 2001 From: margaretjgu Date: Tue, 4 Aug 2026 15:01:05 -0400 Subject: [PATCH 2/2] chore: bump fast-uri to fix trivy vuln --- NOTICE.txt | 2 +- package-lock.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NOTICE.txt b/NOTICE.txt index 93ecaf47..5edcd0b1 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1230,7 +1230,7 @@ SOFTWARE. ------------------------------------------------------------------------ -fast-uri@3.1.4 +fast-uri@3.1.5 License: BSD-3-Clause Repository: https://github.com/fastify/fast-uri Publisher: Vincent Le Goff (https://github.com/zekth) diff --git a/package-lock.json b/package-lock.json index 4aa03d56..98d4193e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3508,9 +3508,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github",