From 4d72405e87305d5f203ef90b732c0f5aec868222 Mon Sep 17 00:00:00 2001 From: Mao Haibo Date: Sun, 30 Aug 2026 20:31:42 +0800 Subject: [PATCH] fix(cli): persist DNS config in JSON mode --- packages/cli/src/commands/scale.test.ts | 52 ++++++++++++++++++++++++- packages/cli/src/commands/scale.ts | 44 ++++++++++++--------- 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/packages/cli/src/commands/scale.test.ts b/packages/cli/src/commands/scale.test.ts index 6a3065b8..f381d9e5 100644 --- a/packages/cli/src/commands/scale.test.ts +++ b/packages/cli/src/commands/scale.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, afterEach } from 'vitest'; -import { mkdtempSync, writeFileSync, rmSync, existsSync, readFileSync } from 'node:fs'; +import { mkdtempSync, writeFileSync, rmSync, existsSync, readFileSync, mkdirSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { @@ -414,6 +415,55 @@ describe('auto-scale rules', () => { }); }); +describe('DNS JSON mode', () => { + it('persists DNS config when --json is used without --dry-run', () => { + const dir = makeTempDir(); + const stateDir = join(dir, '.sh1pt'); + mkdirSync(stateDir, { recursive: true }); + const statePath = join(stateDir, 'credentials.json'); + writeFileSync(statePath, JSON.stringify({ + apiKey: 'preserve-me', + instances: [ + { + id: 'inst-0001', + provider: 'aws', + status: 'running', + publicIp: '203.0.113.10', + createdAt: '', + hourlyRate: 0.1, + }, + ], + lastUpdated: '', + })); + + const scaleModule = new URL('./scale.ts', import.meta.url).href; + const runDns = [ + `import { scaleCmd } from ${JSON.stringify(scaleModule)};`, + `await scaleCmd.parseAsync(['node', 'sh1pt', 'dns', '--provider', 'dns-cloudflare', '--domain', 'api.example.com', '--json']);`, + ].join('\n'); + const result = spawnSync( + process.execPath, + ['--import', 'tsx', '--input-type=module', '--eval', runDns], + { + env: { ...process.env, HOME: dir, USERPROFILE: dir }, + encoding: 'utf8', + }, + ); + + expect(result.status, result.stderr).toBe(0); + const output = JSON.parse(result.stdout); + expect(output.recordCount).toBe(1); + + const saved = JSON.parse(readFileSync(statePath, 'utf-8')); + expect(saved.apiKey).toBe('preserve-me'); + expect(saved.dns).toMatchObject({ + domain: 'api.example.com', + provider: 'dns-cloudflare', + ips: ['203.0.113.10'], + }); + }); +}); + // --------------------------------------------------------------------------- // rollbackPlanIps — rollback plan must list the OLD fleet, not the new one // --------------------------------------------------------------------------- diff --git a/packages/cli/src/commands/scale.ts b/packages/cli/src/commands/scale.ts index 6ecf0c71..2a75b328 100644 --- a/packages/cli/src/commands/scale.ts +++ b/packages/cli/src/commands/scale.ts @@ -643,30 +643,31 @@ scaleCmd records, }; - if (opts.json) { - console.log(JSON.stringify(summary, null, 2)); - return; - } + if (!opts.json) { + console.log(kleur.bold('\n🌐 DNS Round-Robin Plan')); + console.log(kleur.dim('─'.repeat(56))); + console.log(`${kleur.cyan('Domain:'.padEnd(20))} ${opts.domain}`); + console.log(`${kleur.cyan('DNS Provider:'.padEnd(20))} ${opts.provider}`); + console.log(`${kleur.cyan('TTL:'.padEnd(20))} ${opts.ttl}s`); + if (opts.proxied) { + console.log(`${kleur.cyan('Proxied:'.padEnd(20))} ${kleur.yellow('yes (Cloudflare edge)')}`); + } + console.log(`${kleur.cyan('Records:'.padEnd(20))} ${records.length} A record(s)`); + console.log(kleur.dim('─'.repeat(56))); - console.log(kleur.bold('\n🌐 DNS Round-Robin Plan')); - console.log(kleur.dim('─'.repeat(56))); - console.log(`${kleur.cyan('Domain:'.padEnd(20))} ${opts.domain}`); - console.log(`${kleur.cyan('DNS Provider:'.padEnd(20))} ${opts.provider}`); - console.log(`${kleur.cyan('TTL:'.padEnd(20))} ${opts.ttl}s`); - if (opts.proxied) { - console.log(`${kleur.cyan('Proxied:'.padEnd(20))} ${kleur.yellow('yes (Cloudflare edge)')}`); - } - console.log(`${kleur.cyan('Records:'.padEnd(20))} ${records.length} A record(s)`); - console.log(kleur.dim('─'.repeat(56))); + for (const rec of records) { + console.log(` ${kleur.green('A')} ${rec.name.padEnd(30)} → ${rec.value} ${kleur.dim(`(inst: ${rec.instanceId}, ${rec.provider})`)}`); + } - for (const rec of records) { - console.log(` ${kleur.green('A')} ${rec.name.padEnd(30)} → ${rec.value} ${kleur.dim(`(inst: ${rec.instanceId}, ${rec.provider})`)}`); + console.log(kleur.dim('─'.repeat(56))); } - console.log(kleur.dim('─'.repeat(56))); - if (opts.dryRun) { - console.log(kleur.dim('Dry-run — no DNS changes made.')); + if (opts.json) { + console.log(JSON.stringify(summary, null, 2)); + } else { + console.log(kleur.dim('Dry-run — no DNS changes made.')); + } return; } @@ -692,6 +693,11 @@ scaleCmd if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); writeFileSync(credsPath, JSON.stringify(creds, null, 2)); + if (opts.json) { + console.log(JSON.stringify(summary, null, 2)); + return; + } + console.log(kleur.green(`āœ… DNS round-robin configured for ${opts.domain} with ${records.length} A record(s).`)); console.log(kleur.dim(`DNS provider: ${opts.provider}`)); console.log(kleur.dim(`Next step: verify DNS propagation with \`dig ${opts.domain}\``));