diff --git a/packages/cli/src/commands/scale.test.ts b/packages/cli/src/commands/scale.test.ts index e6a21cfe..ba208d44 100644 --- a/packages/cli/src/commands/scale.test.ts +++ b/packages/cli/src/commands/scale.test.ts @@ -16,6 +16,7 @@ import { parsePositiveNumber, parsePercentage, aggregateFleetCosts, + rollbackPlanIps, } from './scale.js'; // Helper to create a temp dir and override CREDS_FILE path @@ -375,3 +376,48 @@ describe('auto-scale rules', () => { expect(rules.cooldownSeconds).toBe(300); }); }); + +// --------------------------------------------------------------------------- +// rollbackPlanIps — rollback plan must list the OLD fleet, not the new one +// --------------------------------------------------------------------------- + +describe('rollbackPlanIps', () => { + const fleet: FleetState = { + instances: [ + { id: 'inst-0001', provider: 'aws', status: 'stopped', createdAt: '', hourlyRate: 0.1, publicIp: '10.0.0.1' }, + { id: 'inst-0002', provider: 'aws', status: 'stopped', createdAt: '', hourlyRate: 0.1, publicIp: '10.0.0.2' }, + { id: 'inst-0003', provider: 'aws', status: 'running', createdAt: '', hourlyRate: 0.1, publicIp: '10.1.0.1' }, + ], + }; + const rollout: RolloutRecord = { + id: 'roll-1', + version: 'v2', + strategy: 'blue-green', + status: 'completed', + startedAt: '2026-08-08T00:00:00.000Z', + newInstanceIds: ['inst-0003'], + oldInstanceIds: ['inst-0001', 'inst-0002'], + }; + + it('lists the old (pre-rollout) instances, not the new ones', () => { + const ips = rollbackPlanIps(fleet, rollout); + expect(ips).toEqual(['10.0.0.1', '10.0.0.2']); + expect(ips).not.toContain('10.1.0.1'); + }); + + it('falls back to privateIp when publicIp is missing', () => { + const fleetNoPub: FleetState = { + instances: [ + { id: 'inst-0001', provider: 'aws', status: 'stopped', createdAt: '', hourlyRate: 0.1, privateIp: '172.16.0.5' }, + ], + }; + expect(rollbackPlanIps(fleetNoPub, rollout)).toEqual(['172.16.0.5']); + }); + + it('returns placeholder for instances without any ip', () => { + const fleetNoIp: FleetState = { + instances: [{ id: 'inst-0001', provider: 'aws', status: 'stopped', createdAt: '', hourlyRate: 0.1 }], + }; + expect(rollbackPlanIps(fleetNoIp, rollout)).toEqual(['?.?.?.?']); + }); +}); diff --git a/packages/cli/src/commands/scale.ts b/packages/cli/src/commands/scale.ts index 556a000d..c57cc9f7 100644 --- a/packages/cli/src/commands/scale.ts +++ b/packages/cli/src/commands/scale.ts @@ -215,6 +215,13 @@ export function aggregateFleetCosts( return providerMap; } +/** Resolve the IPs of the old (pre-rollout) fleet for a rollback plan display. */ +export function rollbackPlanIps(fleet: FleetState, target: RolloutRecord): string[] { + return fleet.instances + .filter(i => target.oldInstanceIds.includes(i.id)) + .map(i => i.publicIp || i.privateIp || '?.?.?.?'); +} + export const scaleCmd = new Command('scale') .description('Provision + scale cloud infra. DNS round-robin, rollouts, rightsizing — all the capacity ops.') .option('--from ', 'existing live url, repo, or local path to probe + propose scaling for') @@ -728,9 +735,7 @@ scaleCmd process.exit(1); } const fleet = loadFleet(); - const oldIps = fleet.instances - .filter(i => target.newInstanceIds.includes(i.id)) - .map(i => i.publicIp || i.privateIp || '?.?.?.?'); + const oldIps = rollbackPlanIps(fleet, target); console.log(kleur.bold('\\nā® Rollback Plan')); console.log(kleur.dim('─'.repeat(56))); console.log(`${kleur.cyan('Rollout ID:'.padEnd(20))} ${target.id.slice(0, 8)} (v${target.version})`);