From 47c4e466ab44efb62e657c7b9d9333247b1dfb63 Mon Sep 17 00:00:00 2001 From: JerryNee Date: Sat, 27 Jun 2026 22:15:36 -0500 Subject: [PATCH] fix(usage): reject invalid output modes --- src/commands/usage.test.ts | 18 ++++++++++++++++++ src/commands/usage.ts | 7 ++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/commands/usage.test.ts b/src/commands/usage.test.ts index 28bdef3..cee701b 100644 --- a/src/commands/usage.test.ts +++ b/src/commands/usage.test.ts @@ -7,6 +7,7 @@ import { mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import { Command } from 'commander'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { writeProfile } from '../lib/credentials.js'; import type { UsageDeps, UsageResponse } from './usage.js'; @@ -310,4 +311,21 @@ describe('createUsageCommand wiring', () => { // Commander's helpInformation() includes the command description. expect(helpText).toContain('credit balance'); }); + + it('rejects unsupported global --output modes instead of falling back to text', async () => { + const root = new Command('testsprite') + .exitOverride() + .option('--output ', 'Output format (json|text)', 'text') + .option('--dry-run', 'Skip the network and emit a canned response', false); + + root.addCommand(createUsageCommand(makeCapture().deps)); + + await expect( + root.parseAsync(['usage', '--output', 'yaml', '--dry-run'], { from: 'user' }), + ).rejects.toMatchObject({ + code: 'VALIDATION_ERROR', + exitCode: 5, + details: { field: 'output' }, + }); + }); }); diff --git a/src/commands/usage.ts b/src/commands/usage.ts index d8a0f21..5de8412 100644 --- a/src/commands/usage.ts +++ b/src/commands/usage.ts @@ -20,6 +20,7 @@ import { type CommonOptions as FactoryCommonOptions, } from '../lib/client-factory.js'; import { loadConfig } from '../lib/config.js'; +import { localValidationError } from '../lib/errors.js'; import { resolvePortalBase } from '../lib/facade.js'; import type { FetchImpl } from '../lib/http.js'; import { GLOBAL_OPTS_HINT, Output, type OutputMode } from '../lib/output.js'; @@ -214,9 +215,13 @@ function resolveCommonOptions(command: Command): CommonOptions { const globals = command.optsWithGlobals() as Partial & { requestTimeout?: string; }; + const rawOutput = globals.output; + if (rawOutput !== undefined && rawOutput !== 'json' && rawOutput !== 'text') { + throw localValidationError('output', 'must be one of: json, text', ['json', 'text']); + } return { profile: globals.profile ?? 'default', - output: globals.output ?? 'text', + output: (globals.output as OutputMode | undefined) ?? 'text', endpointUrl: globals.endpointUrl, debug: globals.debug ?? false, verbose: globals.verbose ?? false,