Reject invalid --format and --period values instead of silently falling back (#258)

getDateRange() silently fell back to week on unknown periods, and no command
validated --format. A typo like --period mounth or --format yaml produced
wrong output with exit 0. Now all 6 format-accepting commands and all
period-accepting paths reject unknown values with a clear message and exit 1.
Also fixes the status description (said today+week+month, only shows
today+month).
This commit is contained in:
Resham Joshi 2026-05-06 23:03:41 -07:00 committed by GitHub
parent daa673449c
commit fb8f25fb97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 22 additions and 7 deletions

View file

@ -137,8 +137,10 @@ export function getDateRange(period: string): { range: DateRange; label: string
return { range: { start, end }, label: 'Last 6 months' }
}
default: {
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7)
return { range: { start, end }, label: 'Last 7 Days' }
process.stderr.write(
`codeburn: unknown period "${period}". Valid values: today, week, 30days, month, all.\n`
)
process.exit(1)
}
}
}

View file

@ -75,6 +75,15 @@ function toJsonPlanSummary(planUsage: PlanUsage): JsonPlanSummary {
}
}
function assertFormat(value: string, allowed: readonly string[], command: string): void {
if (!allowed.includes(value)) {
process.stderr.write(
`codeburn ${command}: unknown format "${value}". Valid values: ${allowed.join(', ')}.\n`
)
process.exit(1)
}
}
async function runJsonReport(period: Period, provider: string, project: string[], exclude: string[]): Promise<void> {
await loadPricing()
const { range, label } = getDateRange(period)
@ -273,6 +282,7 @@ program
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
.option('--refresh <seconds>', 'Auto-refresh interval in seconds (0 to disable)', parseInteger, 30)
.action(async (opts) => {
assertFormat(opts.format, ['tui', 'json'], 'report')
let customRange: DateRange | null = null
try {
customRange = parseDateRangeFlags(opts.from, opts.to)
@ -346,7 +356,7 @@ function buildPeriodData(label: string, projects: ProjectSummary[]): PeriodData
program
.command('status')
.description('Compact status output (today + week + month)')
.description('Compact status output (today + month)')
.option('--format <format>', 'Output format: terminal, menubar-json, json', 'terminal')
.option('--provider <provider>', 'Filter by provider (e.g. claude, gemini, cursor, copilot)', 'all')
.option('--project <name>', 'Show only projects matching name (repeatable)', collect, [])
@ -354,6 +364,7 @@ program
.option('--period <period>', 'Primary period for menubar-json: today, week, 30days, month, all', 'today')
.option('--no-optimize', 'Skip optimize findings (menubar-json only, faster)')
.action(async (opts) => {
assertFormat(opts.format, ['terminal', 'menubar-json', 'json'], 'status')
await loadPricing()
const pf = opts.provider
const fp = (p: ProjectSummary[]) => filterProjectsByName(p, opts.project, opts.exclude)
@ -518,6 +529,7 @@ program
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
.option('--refresh <seconds>', 'Auto-refresh interval in seconds (0 to disable)', parseInteger, 30)
.action(async (opts) => {
assertFormat(opts.format, ['tui', 'json'], 'today')
if (opts.format === 'json') {
await runJsonReport('today', opts.provider, opts.project, opts.exclude)
return
@ -535,6 +547,7 @@ program
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
.option('--refresh <seconds>', 'Auto-refresh interval in seconds (0 to disable)', parseInteger, 30)
.action(async (opts) => {
assertFormat(opts.format, ['tui', 'json'], 'month')
if (opts.format === 'json') {
await runJsonReport('month', opts.provider, opts.project, opts.exclude)
return
@ -554,6 +567,7 @@ program
.option('--project <name>', 'Show only projects matching name (repeatable)', collect, [])
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
.action(async (opts) => {
assertFormat(opts.format, ['csv', 'json'], 'export')
await loadPricing()
await hydrateCache()
const pf = opts.provider
@ -727,6 +741,7 @@ program
.option('--provider <name>', 'Provider scope: all, claude, codex, cursor', 'all')
.option('--reset-day <n>', 'Day of month plan resets (1-28)', parseInteger, 1)
.action(async (action?: string, id?: string, opts?: { format?: string; monthlyUsd?: number; provider?: string; resetDay?: number }) => {
assertFormat(opts?.format ?? 'text', ['text', 'json'], 'plan')
const mode = action ?? 'show'
if (mode === 'show') {

View file

@ -81,10 +81,8 @@ describe('getDateRange', () => {
expect(range.end.getHours()).toBe(23)
})
it('unknown period falls back to "week"', () => {
const fallback = getDateRange('not-a-period')
const week = getDateRange('week')
expect(fallback.label).toBe(week.label)
it('unknown period exits with an error instead of silently falling back', () => {
expect(() => getDateRange('not-a-period')).toThrow()
})
})