|
15 | 15 | */ |
16 | 16 | import { loadConfig } from './config.js'; |
17 | 17 | import { defaultCredentialsPath } from './credentials.js'; |
18 | | -import { ApiError } from './errors.js'; |
| 18 | +import { ApiError, localValidationError } from './errors.js'; |
19 | 19 | import { facadeBaseUrl } from './facade.js'; |
20 | 20 | import type { DebugEvent, FetchImpl } from './http.js'; |
21 | 21 | import { |
@@ -139,6 +139,38 @@ function clampRequestTimeout(ms: number): number { |
139 | 139 | return Math.min(Math.max(Math.round(ms), REQUEST_TIMEOUT_MIN_MS), REQUEST_TIMEOUT_MAX_MS); |
140 | 140 | } |
141 | 141 |
|
| 142 | +/** |
| 143 | + * Parse the `--request-timeout <seconds>` flag value into milliseconds. |
| 144 | + * |
| 145 | + * Returns `undefined` when the flag was omitted (the factory then falls back to |
| 146 | + * the `TESTSPRITE_REQUEST_TIMEOUT_MS` env var, else the 120s default). |
| 147 | + * |
| 148 | + * A supplied-but-invalid value (non-numeric, zero, or negative) throws a typed |
| 149 | + * VALIDATION_ERROR (exit 5) rather than being silently dropped. An explicit |
| 150 | + * `--request-timeout 30s` typo previously resolved to `undefined` and the |
| 151 | + * command ran with the default 120s deadline — the operator believed they had |
| 152 | + * set a timeout but had not, with no signal. Failing loudly here is consistent |
| 153 | + * with every other validated flag (`--page-size`, `--output`, `--type`). |
| 154 | + * |
| 155 | + * Out-of-range but positive values are intentionally NOT rejected — they flow |
| 156 | + * through to {@link resolveRequestTimeoutMs}, which clamps to |
| 157 | + * `[REQUEST_TIMEOUT_MIN_MS, REQUEST_TIMEOUT_MAX_MS]`. The env-var path stays |
| 158 | + * lenient by design (a stray global env var should not hard-fail every |
| 159 | + * command); only the explicit per-invocation flag is strict. |
| 160 | + * |
| 161 | + * This single definition replaces five byte-identical copies that previously |
| 162 | + * lived in `auth`, `project`, `usage`, `init`, and `test` — drift between them |
| 163 | + * would have silently changed timeout behaviour depending on the command. |
| 164 | + */ |
| 165 | +export function parseRequestTimeoutFlag(raw: string | undefined): number | undefined { |
| 166 | + if (raw === undefined) return undefined; |
| 167 | + const n = Number(raw); |
| 168 | + if (!Number.isFinite(n) || n <= 0) { |
| 169 | + throw localValidationError('request-timeout', 'must be a positive number of seconds'); |
| 170 | + } |
| 171 | + return Math.round(n * 1000); // seconds → milliseconds |
| 172 | +} |
| 173 | + |
142 | 174 | export function makeHttpClient(opts: CommonOptions, deps: ClientFactoryDeps = {}): HttpClient { |
143 | 175 | const stderr = deps.stderr ?? ((line: string) => process.stderr.write(`${line}\n`)); |
144 | 176 | const env = deps.env ?? process.env; |
|
0 commit comments