Skip to content

Commit e4fa8ff

Browse files
committed
Handle deploy dry-run app errors cleanly
1 parent c45eb10 commit e4fa8ff

2 files changed

Lines changed: 48 additions & 21 deletions

File tree

src/cli/commands/deploy.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,18 @@ import { getPm2Name } from '../../domain/pm2/apps.js';
1010
import { configForServer, getServerTargets, resolveServerName } from '../../domain/servers.js';
1111

1212
export async function cmdDeploy(cwd: string, options: { dryRun?: boolean; skipBuild?: boolean; app?: string; config?: string }): Promise<void> {
13-
const config = await loadConfig(cwd, options.config);
14-
const selectedApp = options.app ? getActiveApp(config, options.app) : undefined;
15-
const targetConfig = selectedApp ? { ...config, apps: [selectedApp] } : config;
13+
let config: ShipnodeConfig;
14+
let targetConfig: ShipnodeConfig;
15+
try {
16+
config = await loadConfig(cwd, options.config);
17+
const selectedApp = options.app ? getActiveApp(config, options.app) : undefined;
18+
targetConfig = selectedApp ? { ...config, apps: [selectedApp] } : config;
19+
} catch (error) {
20+
const message = error instanceof Error ? error.message : String(error);
21+
ui.error(message);
22+
process.exit(1);
23+
return;
24+
}
1625

1726
if (options.dryRun) {
1827
printDryRun(targetConfig, options.skipBuild ?? false);

tests/unit/deploy-command.test.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,55 @@
11
import { describe, expect, it, vi } from 'vitest';
2-
import { printDryRun } from '../../src/cli/commands/deploy.js';
2+
import { cmdDeploy, printDryRun } from '../../src/cli/commands/deploy.js';
33
import type { ShipnodeConfig } from '../../src/shared/types.js';
44

5+
vi.mock('../../src/config/loader.js', () => ({
6+
loadConfig: vi.fn(),
7+
}));
8+
59
vi.mock('../../src/cli/ui.js', () => ({
610
ui: {
711
banner: vi.fn(),
12+
error: vi.fn(),
813
note: vi.fn(),
914
},
1015
}));
1116

1217
const { ui } = await import('../../src/cli/ui.js');
18+
const { loadConfig } = await import('../../src/config/loader.js');
1319

1420
describe('deploy command dry run', () => {
15-
it('shows the resolved single server target instead of default', () => {
16-
const config: ShipnodeConfig = {
17-
ssh: { host: '1.1.1.1', user: 'deploy', port: 22 },
18-
servers: {
19-
app: { host: '1.1.1.1', user: 'deploy', port: 22 },
20-
},
21-
remotePath: '/var/www/app',
22-
nodeVersion: 'lts',
23-
apps: [{
24-
name: 'api',
25-
appType: 'backend',
26-
pm2: { apps: [{ name: 'api', port: 3000 }] },
27-
healthCheck: { enabled: true, path: '/health', timeout: 30, retries: 3, startupDelay: 3 },
28-
envFile: '.env',
29-
keepReleases: 5,
30-
}],
31-
};
21+
const config: ShipnodeConfig = {
22+
ssh: { host: '1.1.1.1', user: 'deploy', port: 22 },
23+
servers: {
24+
app: { host: '1.1.1.1', user: 'deploy', port: 22 },
25+
},
26+
remotePath: '/var/www/app',
27+
nodeVersion: 'lts',
28+
apps: [{
29+
name: 'api',
30+
appType: 'backend',
31+
pm2: { apps: [{ name: 'api', port: 3000 }] },
32+
healthCheck: { enabled: true, path: '/health', timeout: 30, retries: 3, startupDelay: 3 },
33+
envFile: '.env',
34+
keepReleases: 5,
35+
}],
36+
};
3237

38+
it('shows the resolved single server target instead of default', () => {
3339
printDryRun(config, false);
3440

3541
expect(vi.mocked(ui.note).mock.calls[0]?.[0]).toContain('Server app');
3642
});
43+
44+
it('prints a clean error for an unknown dry-run app', async () => {
45+
vi.mocked(loadConfig).mockResolvedValue(config);
46+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
47+
48+
await cmdDeploy('/tmp/project', { dryRun: true, app: 'missing' });
49+
50+
expect(ui.error).toHaveBeenCalledWith('No app named "missing" in this workspace');
51+
expect(exitSpy).toHaveBeenCalledWith(1);
52+
53+
exitSpy.mockRestore();
54+
});
3755
});

0 commit comments

Comments
 (0)