From 3a251779c7add4d821eccf2241320f3c0c5245e5 Mon Sep 17 00:00:00 2001 From: x15967311210-crypto Date: Sun, 11 Oct 2026 11:26:02 +0800 Subject: [PATCH] fix(doctor): use the shared parseRequestTimeoutFlag doctor carried a private copy of parseRequestTimeoutFlag whose error message had already drifted from the shared helper every other command uses. Same flag, same bad input, two different messages. Delete the copy and import the shared helper, so the timeout flag reads identically across doctor and the rest of the CLI. Adds a wiring test that pins doctor's message to the shared wording. Refs #364 --- src/commands/doctor.test.ts | 17 +++++++++++++++++ src/commands/doctor.ts | 23 ++--------------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/commands/doctor.test.ts b/src/commands/doctor.test.ts index 55f3e2e7..b497fd0b 100644 --- a/src/commands/doctor.test.ts +++ b/src/commands/doctor.test.ts @@ -89,6 +89,7 @@ function makeDoctorProgram(deps: DoctorDeps = {}): Command { const program = new Command(); program.exitOverride(); program.option('--output ', 'output', 'text'); + program.option('--request-timeout ', 'per-request timeout in seconds'); program.addCommand(createDoctorCommand(deps)); return program; } @@ -470,6 +471,22 @@ describe('createDoctorCommand wiring', () => { }); }); + it('rejects an invalid --request-timeout with the SHARED validation message', async () => { + // Guards the drift this repo's shared helper exists to prevent: the doctor + // command once carried a private copy whose message differed from every + // other command's. Same flag, same bad input, one message. + const rejection = await makeDoctorProgram() + .parseAsync(['node', 'ts', '--request-timeout', 'abc', 'doctor']) + .catch((error: unknown) => error); + expect(rejection).toBeInstanceOf(ApiError); + expect(rejection).toMatchObject({ + code: 'VALIDATION_ERROR', + exitCode: 5, + nextAction: + 'Flag `--request-timeout` is invalid: "abc" is not valid — must be a positive number of seconds.', + }); + }); + it('accepts valid --output modes through command wiring', async () => { writeProfile('default', { apiKey: 'sk-user-abc' }, { path: credentialsPath }); for (const mode of ['text', 'json'] as const) { diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index bf653043..3d812ad3 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -19,6 +19,7 @@ import { Command } from 'commander'; import { assertValidEndpointUrl, makeHttpClient, + parseRequestTimeoutFlag, type CommonOptions as FactoryCommonOptions, } from '../lib/client-factory.js'; import { @@ -28,13 +29,7 @@ import { normalizeEnvVar, type Config, } from '../lib/config.js'; -import { - ApiError, - CLIError, - InterruptError, - RequestTimeoutError, - localValidationError, -} from '../lib/errors.js'; +import { ApiError, CLIError, InterruptError, RequestTimeoutError } from '../lib/errors.js'; import type { FetchImpl } from '../lib/http.js'; import { globalShutdown } from '../lib/interrupt.js'; import type { CliOrgBinding, CliOrgSummary } from '../lib/org-render.js'; @@ -504,20 +499,6 @@ function resolveCommonOptions(command: Command, env?: NodeJS.ProcessEnv): Common }; } -function parseRequestTimeoutFlag(raw: string | undefined): number | undefined { - if (raw === undefined) return undefined; - const seconds = Number(raw); - if (!Number.isFinite(seconds) || seconds <= 0) { - // Match the other commands: a malformed --request-timeout is a validation - // error, not a silently-ignored default. - throw localValidationError( - 'request-timeout', - `must be a positive number of seconds (got "${raw}")`, - ); - } - return Math.round(seconds * 1000); -} - function makeOutput(mode: OutputMode, deps: DoctorDeps): Output { return new Output(mode, { stdout: deps.stdout, stderr: deps.stderr }); }