From c2caa8c2c614022a4caafec5060b494ecf3f233e Mon Sep 17 00:00:00 2001 From: aiirvizionz Date: Sat, 18 Jul 2026 17:37:42 -0600 Subject: [PATCH] fix(scale): reject non-decimal numeric options --- packages/cli/src/commands/scale.test.ts | 6 +++--- packages/cli/src/commands/scale.ts | 24 ++++++++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/commands/scale.test.ts b/packages/cli/src/commands/scale.test.ts index 7e6f5295..52c2175f 100644 --- a/packages/cli/src/commands/scale.test.ts +++ b/packages/cli/src/commands/scale.test.ts @@ -74,21 +74,21 @@ describe('scale numeric option parsers', () => { expect(parsePercentage('100')).toBe(100); }); - it.each(['nope', '1.5', '0', '-1', 'Infinity', 'NaN', ''])( + it.each(['nope', '1.5', '1e2', '0x10', '0', '-1', 'Infinity', 'NaN', ''])( 'rejects invalid positive integers: %s', (value) => { expect(() => parsePositiveInteger(value)).toThrow(); }, ); - it.each(['nope', '1.5', '-1', 'Infinity', 'NaN', ''])( + it.each(['nope', '1.5', '1e2', '0x10', '-1', 'Infinity', 'NaN', ''])( 'rejects invalid non-negative integers: %s', (value) => { expect(() => parseNonNegativeInteger(value)).toThrow(); }, ); - it.each(['nope', '0', '-1', 'Infinity', 'NaN', ''])( + it.each(['nope', '1e2', '0x10', '0', '-1', 'Infinity', 'NaN', ''])( 'rejects invalid positive finite numbers: %s', (value) => { expect(() => parsePositiveNumber(value)).toThrow(); diff --git a/packages/cli/src/commands/scale.ts b/packages/cli/src/commands/scale.ts index 9ca82aa3..22a4b74b 100644 --- a/packages/cli/src/commands/scale.ts +++ b/packages/cli/src/commands/scale.ts @@ -122,29 +122,41 @@ export function getNextId(instances: FleetEntry[]): string { } export function parsePositiveInteger(value: string): number { - const parsed = Number(value); - if (value.trim() === '' || !Number.isSafeInteger(parsed) || parsed < 1) { + const parsed = parseDecimalInteger(value); + if (parsed === null || parsed < 1) { throw new InvalidArgumentError('must be a positive integer'); } return parsed; } export function parseNonNegativeInteger(value: string): number { - const parsed = Number(value); - if (value.trim() === '' || !Number.isSafeInteger(parsed) || parsed < 0) { + const parsed = parseDecimalInteger(value); + if (parsed === null || parsed < 0) { throw new InvalidArgumentError('must be zero or a positive integer'); } return parsed; } export function parsePositiveNumber(value: string): number { - const parsed = Number(value); - if (value.trim() === '' || !Number.isFinite(parsed) || parsed <= 0) { + const parsed = parseDecimalNumber(value); + if (parsed === null || parsed <= 0) { throw new InvalidArgumentError('must be a positive finite number'); } return parsed; } +function parseDecimalInteger(value: string): number | null { + if (!/^\d+$/.test(value.trim())) return null; + const parsed = Number(value); + return Number.isSafeInteger(parsed) ? parsed : null; +} + +function parseDecimalNumber(value: string): number | null { + if (!/^\d+(?:\.\d+)?$/.test(value.trim())) return null; + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : null; +} + export function parsePercentage(value: string): number { const parsed = parsePositiveInteger(value); if (parsed > 100) {