@nexical/cli 0.11.7 → 0.11.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-LZ3YQWAR.js → chunk-OUGA4CB4.js} +15 -11
- package/dist/chunk-OUGA4CB4.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/src/commands/init.js +1 -1
- package/dist/src/commands/module/add.js +51 -20
- package/dist/src/commands/module/add.js.map +1 -1
- package/dist/src/commands/module/list.d.ts +1 -0
- package/dist/src/commands/module/list.js +55 -46
- package/dist/src/commands/module/list.js.map +1 -1
- package/dist/src/commands/module/remove.js +38 -13
- package/dist/src/commands/module/remove.js.map +1 -1
- package/dist/src/commands/module/update.js +16 -4
- package/dist/src/commands/module/update.js.map +1 -1
- package/dist/src/commands/run.js +19 -2
- package/dist/src/commands/run.js.map +1 -1
- package/dist/src/commands/setup.js +1 -1
- package/package.json +1 -1
- package/src/commands/module/add.ts +74 -31
- package/src/commands/module/list.ts +80 -57
- package/src/commands/module/remove.ts +50 -14
- package/src/commands/module/update.ts +19 -5
- package/src/commands/run.ts +21 -1
- package/test/e2e/lifecycle.e2e.test.ts +3 -2
- package/test/integration/commands/deploy.integration.test.ts +102 -0
- package/test/integration/commands/init.integration.test.ts +16 -1
- package/test/integration/commands/module.integration.test.ts +81 -55
- package/test/integration/commands/run.integration.test.ts +69 -74
- package/test/integration/commands/setup.integration.test.ts +53 -0
- package/test/unit/commands/deploy.test.ts +285 -0
- package/test/unit/commands/init.test.ts +15 -0
- package/test/unit/commands/module/add.test.ts +363 -254
- package/test/unit/commands/module/list.test.ts +100 -99
- package/test/unit/commands/module/remove.test.ts +143 -58
- package/test/unit/commands/module/update.test.ts +45 -62
- package/test/unit/commands/run.test.ts +16 -1
- package/test/unit/commands/setup.test.ts +25 -66
- package/test/unit/deploy/config-manager.test.ts +65 -0
- package/test/unit/deploy/providers/cloudflare.test.ts +210 -0
- package/test/unit/deploy/providers/github.test.ts +139 -0
- package/test/unit/deploy/providers/railway.test.ts +328 -0
- package/test/unit/deploy/registry.test.ts +227 -0
- package/test/unit/deploy/utils.test.ts +30 -0
- package/test/unit/utils/command-discovery.test.ts +145 -142
- package/test/unit/utils/git_utils.test.ts +49 -0
- package/dist/chunk-LZ3YQWAR.js.map +0 -1
|
@@ -1,97 +1,92 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from 'vitest';
|
|
1
|
+
import { describe, it, beforeEach, afterAll } from 'vitest';
|
|
3
2
|
import RunCommand from '../../../src/commands/run.js';
|
|
4
|
-
import { createTempDir } from '../../utils/integration-helpers.js';
|
|
3
|
+
import { createTempDir, createMockRepo, cleanupTestRoot } from '../../utils/integration-helpers.js';
|
|
5
4
|
import path from 'node:path';
|
|
6
5
|
import fs from 'fs-extra';
|
|
7
|
-
import {
|
|
8
|
-
import EventEmitter from 'events';
|
|
9
|
-
|
|
10
|
-
vi.mock('child_process', () => ({
|
|
11
|
-
spawn: vi.fn(),
|
|
12
|
-
exec: vi.fn(),
|
|
13
|
-
}));
|
|
6
|
+
import { CLI } from '@nexical/cli-core';
|
|
14
7
|
|
|
15
|
-
describe('
|
|
8
|
+
describe('Run Command Integration', () => {
|
|
16
9
|
let projectDir: string;
|
|
17
|
-
let spawnMock: ReturnType<typeof vi.mocked>;
|
|
18
10
|
|
|
19
11
|
beforeEach(async () => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
await fs.outputFile(
|
|
26
|
-
path.join(projectDir, 'package.json'),
|
|
27
|
-
JSON.stringify({
|
|
28
|
-
name: 'nexical-core',
|
|
29
|
-
scripts: {
|
|
30
|
-
'test-script': 'echo test',
|
|
31
|
-
},
|
|
32
|
-
}),
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
await fs.ensureDir(path.join(projectDir, 'modules', 'my-auth'));
|
|
36
|
-
await fs.outputFile(
|
|
37
|
-
path.join(projectDir, 'modules', 'my-auth', 'package.json'),
|
|
38
|
-
JSON.stringify({
|
|
39
|
-
scripts: {
|
|
40
|
-
seed: 'node seed.js',
|
|
41
|
-
},
|
|
42
|
-
}),
|
|
43
|
-
);
|
|
12
|
+
const temp = await createTempDir('run-project-');
|
|
13
|
+
projectDir = await createMockRepo(temp, {
|
|
14
|
+
'package.json': '{"name": "run-project", "version": "1.0.0"}',
|
|
15
|
+
'nexical.yaml': 'site: run-test\nmodules: []',
|
|
16
|
+
});
|
|
44
17
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
18
|
+
// Create a mock module with a script
|
|
19
|
+
const moduleDir = path.join(projectDir, 'apps/backend/modules/script-mod');
|
|
20
|
+
await fs.ensureDir(moduleDir);
|
|
21
|
+
await fs.writeJson(path.join(moduleDir, 'package.json'), {
|
|
22
|
+
name: 'script-mod',
|
|
23
|
+
version: '1.0.0',
|
|
24
|
+
scripts: {
|
|
25
|
+
'test-script': 'echo "Hello from script-mod"',
|
|
26
|
+
},
|
|
53
27
|
});
|
|
54
|
-
});
|
|
55
28
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
29
|
+
// Add module to nexical.yaml manually or via helper
|
|
30
|
+
// For RunCommand, it relies on discovery or explicit path?
|
|
31
|
+
// RunCommand iterates over ALL modules found in nexical.yaml or file system?
|
|
32
|
+
// RunCommand implementation:
|
|
33
|
+
// It runs a command in ALL modules or specific ones.
|
|
34
|
+
|
|
35
|
+
// We need to register it in nexical.yaml for it to be found commonly
|
|
36
|
+
const configPath = path.join(projectDir, 'nexical.yaml');
|
|
37
|
+
await fs.writeFile(configPath, 'site: run-test\nmodules:\n backend:\n - script-mod');
|
|
59
38
|
});
|
|
60
39
|
|
|
61
40
|
afterAll(async () => {
|
|
62
|
-
|
|
41
|
+
await cleanupTestRoot();
|
|
63
42
|
});
|
|
64
43
|
|
|
65
|
-
it('should run
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
44
|
+
it('should run a script in a specific module', async () => {
|
|
45
|
+
const originalCwd = process.cwd();
|
|
46
|
+
try {
|
|
47
|
+
process.chdir(projectDir);
|
|
48
|
+
const cli = new CLI({ commandName: 'nexical' });
|
|
49
|
+
const runCmd = new RunCommand(cli);
|
|
50
|
+
(runCmd as unknown as { projectRoot: string }).projectRoot = projectDir;
|
|
51
|
+
|
|
52
|
+
// We need to capture stdout/stderr to verify execution
|
|
53
|
+
// But RunCommand uses `runCommand` from cli-core which uses execa.
|
|
54
|
+
// In integration test, execa is REAL.
|
|
55
|
+
|
|
56
|
+
// However, BaseCommand.run() might just spawn it.
|
|
57
|
+
// We can inspect the output if we could capture it.
|
|
58
|
+
// But `execa` streams to stdio usually.
|
|
59
|
+
|
|
60
|
+
// Let's rely on side effects or just that it doesn't throw.
|
|
61
|
+
// Or we can mock `runCommand` from `@nexical/cli-core` if we want to verify it called the script?
|
|
62
|
+
// But this is integration test, we want real execution.
|
|
63
|
+
// "Hello from script-mod" should be printed.
|
|
69
64
|
|
|
70
|
-
|
|
65
|
+
await runCmd.run({ script: 'script-mod:test-script' });
|
|
71
66
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
cwd: projectDir,
|
|
77
|
-
}),
|
|
78
|
-
);
|
|
67
|
+
// If passing, it means it found the module and ran the script (which echoed and exited 0).
|
|
68
|
+
} finally {
|
|
69
|
+
process.chdir(originalCwd);
|
|
70
|
+
}
|
|
79
71
|
});
|
|
80
72
|
|
|
81
|
-
it('should run
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
it('should run a script in root', async () => {
|
|
74
|
+
const originalCwd = process.cwd();
|
|
75
|
+
try {
|
|
76
|
+
process.chdir(projectDir);
|
|
77
|
+
const cli = new CLI({ commandName: 'nexical' });
|
|
78
|
+
const runCmd = new RunCommand(cli);
|
|
79
|
+
(runCmd as unknown as { projectRoot: string }).projectRoot = projectDir;
|
|
85
80
|
|
|
86
|
-
|
|
81
|
+
// Add a script to root package.json first
|
|
82
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
83
|
+
const pkg = await fs.readJson(pkgPath);
|
|
84
|
+
pkg.scripts = { 'root-script': 'echo "Hello from root"' };
|
|
85
|
+
await fs.writeJson(pkgPath, pkg);
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
expect.objectContaining({
|
|
93
|
-
cwd: path.resolve(projectDir, 'modules', 'my-auth'),
|
|
94
|
-
}),
|
|
95
|
-
);
|
|
87
|
+
await runCmd.run({ script: 'root-script' });
|
|
88
|
+
} finally {
|
|
89
|
+
process.chdir(originalCwd);
|
|
90
|
+
}
|
|
96
91
|
});
|
|
97
92
|
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
|
|
2
|
+
import SetupCommand from '../../../src/commands/setup.js';
|
|
3
|
+
import { createTempDir, createMockRepo, cleanupTestRoot } from '../../utils/integration-helpers.js';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import { CLI } from '@nexical/cli-core';
|
|
7
|
+
|
|
8
|
+
describe('Setup Command Integration', () => {
|
|
9
|
+
let projectDir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
const temp = await createTempDir('setup-project-');
|
|
13
|
+
projectDir = await createMockRepo(temp, {
|
|
14
|
+
'package.json': '{"name": "setup-project", "version": "1.0.0"}',
|
|
15
|
+
'nexical.yaml': 'site: setup-test\nmodules: []',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Create core assets
|
|
19
|
+
await fs.ensureDir(path.join(projectDir, 'core/src'));
|
|
20
|
+
await fs.writeFile(path.join(projectDir, 'core/src/shared.ts'), 'export const shared = true;');
|
|
21
|
+
|
|
22
|
+
// Create app directories
|
|
23
|
+
await fs.ensureDir(path.join(projectDir, 'apps/backend'));
|
|
24
|
+
await fs.ensureDir(path.join(projectDir, 'apps/frontend'));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(async () => {
|
|
28
|
+
await cleanupTestRoot();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should symlink core assets to apps', async () => {
|
|
32
|
+
const originalCwd = process.cwd();
|
|
33
|
+
try {
|
|
34
|
+
process.chdir(projectDir); // Change CWD to simulate running from root
|
|
35
|
+
const cli = new CLI({ commandName: 'nexical' });
|
|
36
|
+
const setupCmd = new SetupCommand(cli);
|
|
37
|
+
|
|
38
|
+
// Execute setup command
|
|
39
|
+
await setupCmd.run();
|
|
40
|
+
|
|
41
|
+
// Verify symlinks
|
|
42
|
+
const backendSrc = path.join(projectDir, 'apps/backend/src');
|
|
43
|
+
expect(await fs.pathExists(backendSrc)).toBe(true);
|
|
44
|
+
const stat = await fs.lstat(backendSrc);
|
|
45
|
+
expect(stat.isSymbolicLink()).toBe(true);
|
|
46
|
+
|
|
47
|
+
const frontendSrc = path.join(projectDir, 'apps/frontend/src');
|
|
48
|
+
expect(await fs.pathExists(frontendSrc)).toBe(true);
|
|
49
|
+
} finally {
|
|
50
|
+
process.chdir(originalCwd);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import DeployCommand from '../../../src/commands/deploy.js';
|
|
4
|
+
|
|
5
|
+
// Fully mock the modules to return classes
|
|
6
|
+
vi.mock('../../../src/deploy/config-manager.js', () => {
|
|
7
|
+
return {
|
|
8
|
+
ConfigManager: vi.fn().mockImplementation(function () {
|
|
9
|
+
return {
|
|
10
|
+
load: vi.fn().mockResolvedValue({
|
|
11
|
+
deploy: {
|
|
12
|
+
backend: { provider: 'railway' },
|
|
13
|
+
frontend: { provider: 'cloudflare' },
|
|
14
|
+
repository: { provider: 'github' },
|
|
15
|
+
},
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
vi.mock('../../../src/deploy/registry.js', () => {
|
|
23
|
+
return {
|
|
24
|
+
ProviderRegistry: vi.fn().mockImplementation(function () {
|
|
25
|
+
return {
|
|
26
|
+
loadCoreProviders: vi.fn(),
|
|
27
|
+
loadLocalProviders: vi.fn(),
|
|
28
|
+
getDeploymentProvider: vi.fn(),
|
|
29
|
+
getRepositoryProvider: vi.fn(),
|
|
30
|
+
};
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
vi.mock('dotenv');
|
|
36
|
+
vi.mock('@nexical/cli-core', async (importOriginal) => {
|
|
37
|
+
const mod = await importOriginal<typeof import('@nexical/cli-core')>();
|
|
38
|
+
return {
|
|
39
|
+
...mod,
|
|
40
|
+
logger: {
|
|
41
|
+
info: vi.fn(),
|
|
42
|
+
error: vi.fn(),
|
|
43
|
+
success: vi.fn(),
|
|
44
|
+
warn: vi.fn(),
|
|
45
|
+
debug: vi.fn(),
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Import them after vi.mock to get the mocked versions
|
|
51
|
+
import { ConfigManager } from '../../../src/deploy/config-manager.js';
|
|
52
|
+
import { ProviderRegistry } from '../../../src/deploy/registry.js';
|
|
53
|
+
|
|
54
|
+
describe('DeployCommand', () => {
|
|
55
|
+
let command: DeployCommand;
|
|
56
|
+
let mockRegistry: {
|
|
57
|
+
loadCoreProviders: any;
|
|
58
|
+
loadLocalProviders: any;
|
|
59
|
+
getDeploymentProvider: any;
|
|
60
|
+
getRepositoryProvider: any;
|
|
61
|
+
};
|
|
62
|
+
let mockConfigManager: { load: any };
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
vi.clearAllMocks();
|
|
66
|
+
|
|
67
|
+
command = new DeployCommand({} as unknown as any, { rootDir: '/mock/root' });
|
|
68
|
+
(command as unknown as { projectRoot: string }).projectRoot = '/mock/root';
|
|
69
|
+
|
|
70
|
+
// Get the instance that will be returned by the constructor
|
|
71
|
+
mockConfigManager = {
|
|
72
|
+
load: vi.fn().mockResolvedValue({
|
|
73
|
+
deploy: {
|
|
74
|
+
backend: { provider: 'railway' },
|
|
75
|
+
frontend: { provider: 'cloudflare' },
|
|
76
|
+
repository: { provider: 'github' },
|
|
77
|
+
},
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
vi.mocked(ConfigManager).mockImplementation(function () {
|
|
81
|
+
return mockConfigManager;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
mockRegistry = {
|
|
85
|
+
loadCoreProviders: vi.fn(),
|
|
86
|
+
loadLocalProviders: vi.fn(),
|
|
87
|
+
getDeploymentProvider: vi.fn(),
|
|
88
|
+
getRepositoryProvider: vi.fn(),
|
|
89
|
+
};
|
|
90
|
+
vi.mocked(ProviderRegistry).mockImplementation(function () {
|
|
91
|
+
return mockRegistry;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
vi.spyOn(command, 'info').mockImplementation(() => {});
|
|
95
|
+
vi.spyOn(command, 'error').mockImplementation(() => {
|
|
96
|
+
throw new Error('CLI ERROR');
|
|
97
|
+
});
|
|
98
|
+
vi.spyOn(command, 'success').mockImplementation(() => {});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
vi.restoreAllMocks();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should run a full deployment successfully', async () => {
|
|
106
|
+
const mockBackend = {
|
|
107
|
+
name: 'railway',
|
|
108
|
+
provision: vi.fn().mockResolvedValue(undefined),
|
|
109
|
+
getSecrets: vi.fn().mockResolvedValue({ B_SEC: 'val' }),
|
|
110
|
+
getVariables: vi.fn().mockResolvedValue({ B_VAR: 'val' }),
|
|
111
|
+
};
|
|
112
|
+
const mockFrontend = {
|
|
113
|
+
name: 'cloudflare',
|
|
114
|
+
provision: vi.fn().mockResolvedValue(undefined),
|
|
115
|
+
getSecrets: vi.fn().mockResolvedValue({ F_SEC: 'val' }),
|
|
116
|
+
getVariables: vi.fn().mockResolvedValue({ F_VAR: 'val' }),
|
|
117
|
+
};
|
|
118
|
+
const mockRepo = {
|
|
119
|
+
name: 'github',
|
|
120
|
+
configureSecrets: vi.fn().mockResolvedValue(undefined),
|
|
121
|
+
configureVariables: vi.fn().mockResolvedValue(undefined),
|
|
122
|
+
generateWorkflow: vi.fn().mockResolvedValue(undefined),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
mockRegistry.getDeploymentProvider.mockImplementation((name: string) => {
|
|
126
|
+
if (name === 'railway') return mockBackend;
|
|
127
|
+
if (name === 'cloudflare') return mockFrontend;
|
|
128
|
+
return undefined;
|
|
129
|
+
});
|
|
130
|
+
mockRegistry.getRepositoryProvider.mockReturnValue(mockRepo);
|
|
131
|
+
|
|
132
|
+
await command.run({ env: 'production' });
|
|
133
|
+
|
|
134
|
+
expect(dotenv.config).toHaveBeenCalled();
|
|
135
|
+
expect(mockBackend.provision).toHaveBeenCalled();
|
|
136
|
+
expect(mockFrontend.provision).toHaveBeenCalled();
|
|
137
|
+
expect(mockRepo.configureSecrets).toHaveBeenCalled();
|
|
138
|
+
expect(mockRepo.configureVariables).toHaveBeenCalled();
|
|
139
|
+
expect(mockRepo.generateWorkflow).toHaveBeenCalled();
|
|
140
|
+
expect(command.success).toHaveBeenCalledWith('Deployment configuration complete!');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should error if backend provider is missing', async () => {
|
|
144
|
+
mockConfigManager.load.mockResolvedValue({ deploy: { frontend: { provider: 'cf' } } });
|
|
145
|
+
await expect(command.run({})).rejects.toThrow('CLI ERROR');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should error if frontend provider is missing', async () => {
|
|
149
|
+
mockConfigManager.load.mockResolvedValue({ deploy: { backend: { provider: 'rw' } } });
|
|
150
|
+
await expect(command.run({})).rejects.toThrow('CLI ERROR');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should error if repo provider is missing', async () => {
|
|
154
|
+
mockConfigManager.load.mockResolvedValue({
|
|
155
|
+
deploy: { backend: { provider: 'rw' }, frontend: { provider: 'cf' } },
|
|
156
|
+
});
|
|
157
|
+
await expect(command.run({})).rejects.toThrow('CLI ERROR');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should throw if provider is not found in registry', async () => {
|
|
161
|
+
mockRegistry.getDeploymentProvider.mockReturnValue(undefined);
|
|
162
|
+
await expect(command.run({ backend: 'unknown' })).rejects.toThrow(
|
|
163
|
+
"Backend provider 'unknown' not found.",
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should handle errors during secret resolution', async () => {
|
|
168
|
+
const mockBackend = {
|
|
169
|
+
name: 'railway',
|
|
170
|
+
provision: vi.fn(),
|
|
171
|
+
getSecrets: vi.fn().mockRejectedValue(new Error('Secret fail')),
|
|
172
|
+
getVariables: vi.fn().mockResolvedValue({}),
|
|
173
|
+
};
|
|
174
|
+
mockRegistry.getDeploymentProvider.mockReturnValue(mockBackend);
|
|
175
|
+
mockRegistry.getRepositoryProvider.mockReturnValue({
|
|
176
|
+
configureSecrets: vi.fn(),
|
|
177
|
+
configureVariables: vi.fn(),
|
|
178
|
+
generateWorkflow: vi.fn(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await expect(command.run({})).rejects.toThrow('CLI ERROR');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should handle non-Error exceptions during secret resolution', async () => {
|
|
185
|
+
const mockBackend = {
|
|
186
|
+
name: 'railway',
|
|
187
|
+
provision: vi.fn(),
|
|
188
|
+
getSecrets: vi.fn().mockRejectedValue('String error'),
|
|
189
|
+
getVariables: vi.fn().mockResolvedValue({}),
|
|
190
|
+
};
|
|
191
|
+
mockRegistry.getDeploymentProvider.mockReturnValue(mockBackend);
|
|
192
|
+
mockRegistry.getRepositoryProvider.mockReturnValue({
|
|
193
|
+
configureSecrets: vi.fn(),
|
|
194
|
+
configureVariables: vi.fn(),
|
|
195
|
+
generateWorkflow: vi.fn(),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await expect(command.run({})).rejects.toThrow('CLI ERROR');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should handle errors during variable resolution', async () => {
|
|
202
|
+
const mockBackend = {
|
|
203
|
+
name: 'railway',
|
|
204
|
+
provision: vi.fn(),
|
|
205
|
+
getSecrets: vi.fn().mockResolvedValue({}),
|
|
206
|
+
getVariables: vi.fn().mockRejectedValue(new Error('Var fail')),
|
|
207
|
+
};
|
|
208
|
+
mockRegistry.getDeploymentProvider.mockReturnValue(mockBackend);
|
|
209
|
+
mockRegistry.getRepositoryProvider.mockReturnValue({
|
|
210
|
+
configureSecrets: vi.fn(),
|
|
211
|
+
configureVariables: vi.fn(),
|
|
212
|
+
generateWorkflow: vi.fn(),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await expect(command.run({})).rejects.toThrow('CLI ERROR');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should handle non-Error exceptions during variable resolution', async () => {
|
|
219
|
+
const mockBackend = {
|
|
220
|
+
name: 'railway',
|
|
221
|
+
provision: vi.fn(),
|
|
222
|
+
getSecrets: vi.fn().mockResolvedValue({}),
|
|
223
|
+
getVariables: vi.fn().mockRejectedValue('String var fail'),
|
|
224
|
+
};
|
|
225
|
+
mockRegistry.getDeploymentProvider.mockReturnValue(mockBackend);
|
|
226
|
+
mockRegistry.getRepositoryProvider.mockReturnValue({
|
|
227
|
+
configureSecrets: vi.fn(),
|
|
228
|
+
configureVariables: vi.fn(),
|
|
229
|
+
generateWorkflow: vi.fn(),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
await expect(command.run({})).rejects.toThrow('CLI ERROR');
|
|
233
|
+
});
|
|
234
|
+
it('should handle errors during frontend secret resolution', async () => {
|
|
235
|
+
const mockBackend = {
|
|
236
|
+
name: 'railway',
|
|
237
|
+
provision: vi.fn(),
|
|
238
|
+
getSecrets: vi.fn().mockResolvedValue({}),
|
|
239
|
+
getVariables: vi.fn().mockResolvedValue({}),
|
|
240
|
+
};
|
|
241
|
+
const mockFrontend = {
|
|
242
|
+
name: 'cloudflare',
|
|
243
|
+
provision: vi.fn(),
|
|
244
|
+
getSecrets: vi.fn().mockRejectedValue(new Error('Front secret fail')),
|
|
245
|
+
getVariables: vi.fn().mockResolvedValue({}),
|
|
246
|
+
};
|
|
247
|
+
mockRegistry.getDeploymentProvider.mockImplementation((name: string) => {
|
|
248
|
+
if (name === 'railway') return mockBackend;
|
|
249
|
+
if (name === 'cloudflare') return mockFrontend;
|
|
250
|
+
});
|
|
251
|
+
mockRegistry.getRepositoryProvider.mockReturnValue({
|
|
252
|
+
configureSecrets: vi.fn(),
|
|
253
|
+
configureVariables: vi.fn(),
|
|
254
|
+
generateWorkflow: vi.fn(),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
await expect(command.run({})).rejects.toThrow('CLI ERROR');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should handle errors during frontend variable resolution', async () => {
|
|
261
|
+
const mockBackend = {
|
|
262
|
+
name: 'railway',
|
|
263
|
+
provision: vi.fn(),
|
|
264
|
+
getSecrets: vi.fn().mockResolvedValue({}),
|
|
265
|
+
getVariables: vi.fn().mockResolvedValue({}),
|
|
266
|
+
};
|
|
267
|
+
const mockFrontend = {
|
|
268
|
+
name: 'cloudflare',
|
|
269
|
+
provision: vi.fn(),
|
|
270
|
+
getSecrets: vi.fn().mockResolvedValue({}),
|
|
271
|
+
getVariables: vi.fn().mockRejectedValue(new Error('Front var fail')),
|
|
272
|
+
};
|
|
273
|
+
mockRegistry.getDeploymentProvider.mockImplementation((name: string) => {
|
|
274
|
+
if (name === 'railway') return mockBackend;
|
|
275
|
+
if (name === 'cloudflare') return mockFrontend;
|
|
276
|
+
});
|
|
277
|
+
mockRegistry.getRepositoryProvider.mockReturnValue({
|
|
278
|
+
configureSecrets: vi.fn(),
|
|
279
|
+
configureVariables: vi.fn(),
|
|
280
|
+
generateWorkflow: vi.fn(),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await expect(command.run({})).rejects.toThrow('CLI ERROR');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -184,5 +184,20 @@ describe('InitCommand', () => {
|
|
|
184
184
|
expect(command.error).toHaveBeenCalledWith(
|
|
185
185
|
expect.stringContaining('Failed to initialize project'),
|
|
186
186
|
);
|
|
187
|
+
expect(command.error).toHaveBeenCalledWith(
|
|
188
|
+
expect.stringContaining('Failed to initialize project'),
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should handle non-Error objects in catch', async () => {
|
|
193
|
+
vi.mocked(git.clone).mockRejectedValueOnce('String error');
|
|
194
|
+
|
|
195
|
+
await expect(command.run({ directory: 'fail-project', repo: 'foo' })).rejects.toThrow(
|
|
196
|
+
'Process.exit(1)',
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(command.error).toHaveBeenCalledWith(
|
|
200
|
+
expect.stringContaining('Failed to initialize project: String error'),
|
|
201
|
+
);
|
|
187
202
|
});
|
|
188
203
|
});
|