From 7fc5a9d714ed15da3f259979fa086d961f586054 Mon Sep 17 00:00:00 2001 From: jsdavid278-cyber Date: Sat, 13 Jun 2026 18:27:22 -0600 Subject: [PATCH] Validate deploy numeric options --- packages/cli/src/commands/deploy.test.ts | 27 ++++++++++++++++++++ packages/cli/src/commands/deploy.ts | 32 ++++++++++++++++++------ 2 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 packages/cli/src/commands/deploy.test.ts diff --git a/packages/cli/src/commands/deploy.test.ts b/packages/cli/src/commands/deploy.test.ts new file mode 100644 index 00000000..0cc42a24 --- /dev/null +++ b/packages/cli/src/commands/deploy.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest'; +import { parsePositiveFiniteNumber, parsePositiveSafeInteger } from './deploy.js'; + +describe('deploy numeric option parsers', () => { + it('accepts positive safe integer resource counts', () => { + expect(parsePositiveSafeInteger('4')).toBe(4); + }); + + it.each(['nope', '0', '-1', '1.5', 'Infinity', '9007199254740992'])( + 'rejects invalid resource count %s', + (value) => { + expect(() => parsePositiveSafeInteger(value)).toThrow('positive safe integer'); + }, + ); + + it('accepts positive finite memory and price values', () => { + expect(parsePositiveFiniteNumber('0.5')).toBe(0.5); + expect(parsePositiveFiniteNumber('12.75')).toBe(12.75); + }); + + it.each(['nope', '0', '-1', 'NaN', 'Infinity', '-Infinity'])( + 'rejects invalid finite value %s', + (value) => { + expect(() => parsePositiveFiniteNumber(value)).toThrow('positive finite number'); + }, + ); +}); diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts index 79cebce8..bd3e787c 100644 --- a/packages/cli/src/commands/deploy.ts +++ b/packages/cli/src/commands/deploy.ts @@ -1,6 +1,22 @@ -import { Command } from 'commander'; +import { Command, InvalidArgumentError } from 'commander'; import kleur from 'kleur'; +export function parsePositiveSafeInteger(value: string): number { + const parsed = Number(value); + if (!Number.isSafeInteger(parsed) || parsed < 1) { + throw new InvalidArgumentError('must be a positive safe integer'); + } + return parsed; +} + +export function parsePositiveFiniteNumber(value: string): number { + const parsed = Number(value); + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new InvalidArgumentError('must be a positive finite number'); + } + return parsed; +} + export const deployCmd = new Command('deploy') .description('Provision cloud infrastructure — VPS, GPU, bare metal, managed databases, object storage') .action(() => { @@ -21,10 +37,10 @@ deployCmd .command('quote') .description('Price-check a spec across every connected provider before provisioning') .requiredOption('--kind ', 'cpu-vps | gpu | bare-metal | managed-db | block-storage | object-storage') - .option('--cpu ', 'vCPU count', Number) - .option('--memory ', 'RAM in GB', Number) + .option('--cpu ', 'vCPU count', parsePositiveSafeInteger) + .option('--memory ', 'RAM in GB', parsePositiveFiniteNumber) .option('--gpu ', 'GPU model, e.g. A100, H100, RTX-4090') - .option('--gpu-count ', 'GPUs per instance', Number) + .option('--gpu-count ', 'GPUs per instance', parsePositiveSafeInteger) .option('--region ') .option('--spot', 'accept interruptible / spot instances for lower price') .action((opts) => { @@ -37,14 +53,14 @@ deployCmd .description('Spin up a new instance (WILL start billing — pair with a --max-hourly-price guardrail)') .requiredOption('--provider ', 'e.g. cloud-runpod, cloud-digitalocean') .requiredOption('--kind ') - .option('--cpu ', 'vCPU count', Number) - .option('--memory ', 'memory in GB', Number) + .option('--cpu ', 'vCPU count', parsePositiveSafeInteger) + .option('--memory ', 'memory in GB', parsePositiveFiniteNumber) .option('--gpu ', 'GPU model, e.g. A100, H100') - .option('--gpu-count ', 'number of GPUs', Number) + .option('--gpu-count ', 'number of GPUs', parsePositiveSafeInteger) .option('--region ') .option('--image ') .option('--spot') - .option('--max-hourly-price ', 'abort if quote exceeds this (strongly recommended for GPU)', Number) + .option('--max-hourly-price ', 'abort if quote exceeds this (strongly recommended for GPU)', parsePositiveFiniteNumber) .option('--dry-run', 'show the plan without starting a bill') .action((opts) => { if (opts.kind === 'gpu' && !opts.maxHourlyPrice) {