@nexical/cli 0.11.0 → 0.11.1
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/.github/workflows/deploy.yml +1 -1
- package/.husky/pre-commit +1 -0
- package/.prettierignore +8 -0
- package/.prettierrc +7 -0
- package/GEMINI.md +36 -30
- package/README.md +85 -56
- package/dist/chunk-AC4B3HPJ.js +93 -0
- package/dist/chunk-AC4B3HPJ.js.map +1 -0
- package/dist/{chunk-JYASTIIW.js → chunk-PJIOCW2A.js} +1 -1
- package/dist/chunk-PJIOCW2A.js.map +1 -0
- package/dist/{chunk-WKERTCM6.js → chunk-Q7YLW5HJ.js} +5 -2
- package/dist/chunk-Q7YLW5HJ.js.map +1 -0
- package/dist/index.js +41 -12
- package/dist/index.js.map +1 -1
- package/dist/src/commands/init.d.ts +4 -1
- package/dist/src/commands/init.js +8 -4
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/module/add.d.ts +3 -1
- package/dist/src/commands/module/add.js +24 -13
- package/dist/src/commands/module/add.js.map +1 -1
- package/dist/src/commands/module/list.js +9 -5
- package/dist/src/commands/module/list.js.map +1 -1
- package/dist/src/commands/module/remove.d.ts +3 -1
- package/dist/src/commands/module/remove.js +13 -7
- package/dist/src/commands/module/remove.js.map +1 -1
- package/dist/src/commands/module/update.d.ts +3 -1
- package/dist/src/commands/module/update.js +7 -5
- package/dist/src/commands/module/update.js.map +1 -1
- package/dist/src/commands/run.d.ts +4 -1
- package/dist/src/commands/run.js +10 -2
- package/dist/src/commands/run.js.map +1 -1
- package/dist/src/commands/setup.js +17 -4
- package/dist/src/commands/setup.js.map +1 -1
- package/dist/src/utils/discovery.js +1 -1
- package/dist/src/utils/git.js +1 -1
- package/dist/src/utils/url-resolver.js +1 -1
- package/eslint.config.mjs +67 -0
- package/index.ts +34 -20
- package/package.json +56 -32
- package/src/commands/init.ts +79 -76
- package/src/commands/module/add.ts +158 -148
- package/src/commands/module/list.ts +61 -50
- package/src/commands/module/remove.ts +59 -54
- package/src/commands/module/update.ts +44 -42
- package/src/commands/run.ts +89 -81
- package/src/commands/setup.ts +78 -60
- package/src/utils/discovery.ts +98 -113
- package/src/utils/git.ts +35 -28
- package/src/utils/url-resolver.ts +50 -45
- package/test/e2e/lifecycle.e2e.test.ts +139 -131
- package/test/integration/commands/init.integration.test.ts +64 -64
- package/test/integration/commands/module.integration.test.ts +122 -122
- package/test/integration/commands/run.integration.test.ts +70 -63
- package/test/integration/utils/command-loading.integration.test.ts +40 -53
- package/test/unit/commands/init.test.ts +163 -128
- package/test/unit/commands/module/add.test.ts +312 -245
- package/test/unit/commands/module/list.test.ts +108 -91
- package/test/unit/commands/module/remove.test.ts +74 -67
- package/test/unit/commands/module/update.test.ts +74 -70
- package/test/unit/commands/run.test.ts +253 -201
- package/test/unit/commands/setup.test.ts +146 -128
- package/test/unit/utils/command-discovery.test.ts +138 -125
- package/test/unit/utils/git.test.ts +135 -117
- package/test/unit/utils/integration-helpers.test.ts +59 -49
- package/test/unit/utils/url-resolver.test.ts +46 -34
- package/test/utils/integration-helpers.ts +36 -29
- package/tsconfig.json +15 -25
- package/tsup.config.ts +14 -14
- package/vitest.config.ts +10 -10
- package/vitest.e2e.config.ts +6 -6
- package/vitest.integration.config.ts +17 -17
- package/dist/chunk-JYASTIIW.js.map +0 -1
- package/dist/chunk-OKXOCNXP.js +0 -105
- package/dist/chunk-OKXOCNXP.js.map +0 -1
- package/dist/chunk-WKERTCM6.js.map +0 -1
|
@@ -1,115 +1,132 @@
|
|
|
1
|
-
import { BaseCommand
|
|
1
|
+
// import { BaseCommand } from '@nexical/cli-core';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
3
|
import ModuleListCommand from '../../../../src/commands/module/list.js';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
|
|
6
6
|
vi.mock('@nexical/cli-core', async (importOriginal) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
const mod = await importOriginal<typeof import('@nexical/cli-core')>();
|
|
8
|
+
return {
|
|
9
|
+
...mod,
|
|
10
|
+
logger: {
|
|
11
|
+
...mod.logger,
|
|
12
|
+
success: vi.fn(),
|
|
13
|
+
info: vi.fn(),
|
|
14
|
+
debug: vi.fn(),
|
|
15
|
+
error: vi.fn(),
|
|
16
|
+
warn: vi.fn(),
|
|
17
|
+
},
|
|
18
|
+
runCommand: vi.fn(),
|
|
19
|
+
};
|
|
20
20
|
});
|
|
21
21
|
vi.mock('fs-extra');
|
|
22
22
|
|
|
23
23
|
describe('ModuleListCommand', () => {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
let command: ModuleListCommand;
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
let consoleTableSpy: any;
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
vi.spyOn(process, 'exit').mockImplementation((() => { }) as any);
|
|
39
|
-
await command.init();
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
vi.clearAllMocks();
|
|
30
|
+
command = new ModuleListCommand({}, { rootDir: '/mock/root' });
|
|
31
|
+
consoleTableSpy = vi.spyOn(console, 'table').mockImplementation(() => {});
|
|
32
|
+
vi.spyOn(command, 'error').mockImplementation(() => {});
|
|
33
|
+
vi.spyOn(command, 'success').mockImplementation(() => {});
|
|
34
|
+
vi.spyOn(command, 'info').mockImplementation(() => {});
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
|
|
37
|
+
if (p.includes('app.yml') || p.includes('nexical.yml')) return true;
|
|
38
|
+
return true;
|
|
40
39
|
});
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
42
|
+
await command.init();
|
|
43
|
+
});
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.resetAllMocks();
|
|
47
|
+
});
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
it('should have correct static properties', () => {
|
|
50
|
+
expect(ModuleListCommand.usage).toContain('module list');
|
|
51
|
+
expect(ModuleListCommand.description).toBeDefined();
|
|
52
|
+
expect(ModuleListCommand.requiresProject).toBe(true);
|
|
53
|
+
});
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
it('should error if project root is missing', async () => {
|
|
56
|
+
command = new ModuleListCommand({}, { rootDir: undefined });
|
|
57
|
+
vi.spyOn(command, 'init').mockImplementation(async () => {});
|
|
58
|
+
vi.spyOn(command, 'error').mockImplementation(() => {});
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
await command.runInit({});
|
|
61
|
+
expect(command.error).toHaveBeenCalledWith(
|
|
62
|
+
expect.stringContaining('requires to be run within an app project'),
|
|
63
|
+
1,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
it('should handle missing modules directory', async () => {
|
|
68
|
+
vi.mocked(fs.pathExists).mockImplementation(async () => false);
|
|
69
|
+
await command.run();
|
|
70
|
+
expect(command.info).toHaveBeenCalledWith(expect.stringContaining('No modules installed'));
|
|
71
|
+
});
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
it('should list modules with details', async () => {
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
vi.mocked(fs.readdir).mockResolvedValue(['mod1', 'file.txt', 'mod2', 'mod3', 'mod4'] as any);
|
|
76
|
+
// Mock directory check: mod1=dir, file.txt=file, mod2=dir, mod3=dir
|
|
77
|
+
vi.mocked(fs.stat).mockImplementation(
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
async (p: any) =>
|
|
80
|
+
({
|
|
81
|
+
isDirectory: () => !p.includes('file.txt'),
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
|
+
}) as any,
|
|
84
|
+
);
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
// Mock package.json existence: mod1=yes, mod2=no, mod3=yes
|
|
87
|
+
// Also ensure modules directory itself exists!
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
+
vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
|
|
90
|
+
if (p.includes('app.yml') || p.includes('nexical.yml')) return true;
|
|
91
|
+
if (p.endsWith('/modules')) return true;
|
|
92
|
+
return p.includes('package.json') && !p.includes('mod2');
|
|
93
|
+
});
|
|
81
94
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
// Mock reading json: mod1=valid, mod3=invalid, mod4=empty
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
|
+
vi.mocked(fs.readJson).mockImplementation(async (p: any) => {
|
|
98
|
+
if (p.includes('mod3')) throw new Error('Invalid JSON');
|
|
99
|
+
if (p.includes('mod4')) return {}; // No version/desc
|
|
100
|
+
return { version: '1.0.0', description: 'Desc' };
|
|
101
|
+
});
|
|
88
102
|
|
|
89
|
-
|
|
103
|
+
await command.run();
|
|
90
104
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
// mod1: listed with version
|
|
106
|
+
// file.txt: ignored
|
|
107
|
+
// mod2: listed with unknown version (dir exists, no pkg.json)
|
|
108
|
+
// mod3: listed with unknown version (invalid pkg.json)
|
|
109
|
+
// mod4: listed with unknown/empty (fallback logic)
|
|
110
|
+
expect(consoleTableSpy).toHaveBeenCalledWith(
|
|
111
|
+
expect.arrayContaining([
|
|
112
|
+
{ name: 'mod1', version: '1.0.0', description: 'Desc' },
|
|
113
|
+
{ name: 'mod2', version: 'unknown', description: '' },
|
|
114
|
+
{ name: 'mod3', version: 'unknown', description: '' },
|
|
115
|
+
{ name: 'mod4', version: 'unknown', description: '' },
|
|
116
|
+
]),
|
|
117
|
+
);
|
|
118
|
+
});
|
|
103
119
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
it('should handle empty modules directory', async () => {
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
|
+
vi.mocked(fs.readdir).mockResolvedValue([] as any);
|
|
123
|
+
await command.run();
|
|
124
|
+
expect(command.info).toHaveBeenCalledWith('No modules installed.');
|
|
125
|
+
});
|
|
109
126
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
127
|
+
it('should handle failure during list', async () => {
|
|
128
|
+
vi.mocked(fs.readdir).mockRejectedValue(new Error('FS Error'));
|
|
129
|
+
await command.run();
|
|
130
|
+
expect(command.error).toHaveBeenCalledWith(expect.stringContaining('Failed to list modules'));
|
|
131
|
+
});
|
|
115
132
|
});
|
|
@@ -1,89 +1,96 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runCommand } from '@nexical/cli-core';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
3
|
import ModuleRemoveCommand from '../../../../src/commands/module/remove.js';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
|
|
6
6
|
vi.mock('@nexical/cli-core', async (importOriginal) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
const mod = await importOriginal<typeof import('@nexical/cli-core')>();
|
|
8
|
+
return {
|
|
9
|
+
...mod,
|
|
10
|
+
logger: {
|
|
11
|
+
...mod.logger,
|
|
12
|
+
success: vi.fn(),
|
|
13
|
+
info: vi.fn(),
|
|
14
|
+
debug: vi.fn(),
|
|
15
|
+
error: vi.fn(),
|
|
16
|
+
warn: vi.fn(),
|
|
17
|
+
},
|
|
18
|
+
runCommand: vi.fn(),
|
|
19
|
+
};
|
|
20
20
|
});
|
|
21
21
|
vi.mock('fs-extra');
|
|
22
22
|
|
|
23
23
|
describe('ModuleRemoveCommand', () => {
|
|
24
|
-
|
|
24
|
+
let command: ModuleRemoveCommand;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
36
|
-
vi.spyOn(process, 'exit').mockImplementation((() => { }) as any);
|
|
37
|
-
await command.init();
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
command = new ModuleRemoveCommand({}, { rootDir: '/mock/root' });
|
|
29
|
+
vi.spyOn(command, 'error').mockImplementation(() => {});
|
|
30
|
+
vi.spyOn(command, 'success').mockImplementation(() => {});
|
|
31
|
+
vi.spyOn(command, 'info').mockImplementation(() => {});
|
|
32
|
+
vi.mocked(fs.pathExists).mockImplementation(async (p: string) => {
|
|
33
|
+
if (p.includes('app.yml') || p.includes('nexical.yml')) return true;
|
|
34
|
+
return true;
|
|
38
35
|
});
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
38
|
+
await command.init();
|
|
39
|
+
});
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
vi.resetAllMocks();
|
|
43
|
+
});
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
it('should have correct static properties', () => {
|
|
46
|
+
expect(ModuleRemoveCommand.usage).toContain('module remove');
|
|
47
|
+
expect(ModuleRemoveCommand.description).toBeDefined();
|
|
48
|
+
expect(ModuleRemoveCommand.requiresProject).toBe(true);
|
|
49
|
+
expect(ModuleRemoveCommand.args).toBeDefined();
|
|
50
|
+
});
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
it('should error if project root is missing', async () => {
|
|
53
|
+
command = new ModuleRemoveCommand({}, { rootDir: undefined });
|
|
54
|
+
vi.spyOn(command, 'init').mockImplementation(async () => {});
|
|
55
|
+
vi.spyOn(command, 'error').mockImplementation(() => {});
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
await command.runInit({ name: 'mod' });
|
|
58
|
+
expect(command.error).toHaveBeenCalledWith(
|
|
59
|
+
expect.stringContaining('requires to be run within an app project'),
|
|
60
|
+
1,
|
|
61
|
+
);
|
|
62
|
+
});
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
it('should remove submodule and sync', async () => {
|
|
65
|
+
await command.run({ name: 'mod' });
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
expect(runCommand).toHaveBeenCalledWith(
|
|
68
|
+
expect.stringContaining('git submodule deinit'),
|
|
69
|
+
'/mock/root',
|
|
70
|
+
);
|
|
71
|
+
expect(runCommand).toHaveBeenCalledWith(expect.stringContaining('git rm'), '/mock/root');
|
|
72
|
+
expect(fs.remove).toHaveBeenCalledWith(expect.stringContaining('.git/modules'));
|
|
73
|
+
expect(runCommand).toHaveBeenCalledWith('npm install', '/mock/root');
|
|
74
|
+
});
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
it('should error if module not found', async () => {
|
|
77
|
+
vi.mocked(fs.pathExists).mockImplementation(async () => false);
|
|
78
|
+
await command.run({ name: 'missing' });
|
|
79
|
+
expect(command.error).toHaveBeenCalledWith(expect.stringContaining('not found'));
|
|
80
|
+
});
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
it('should handle failure during remove', async () => {
|
|
83
|
+
vi.mocked(runCommand).mockRejectedValue(new Error('Git remove failed'));
|
|
84
|
+
await command.run({ name: 'mod' });
|
|
85
|
+
expect(command.error).toHaveBeenCalledWith(expect.stringContaining('Failed to remove module'));
|
|
86
|
+
});
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
});
|
|
86
|
-
await command.run({ name: 'mod' });
|
|
87
|
-
expect(fs.remove).not.toHaveBeenCalledWith(expect.stringContaining('.git/modules'));
|
|
88
|
+
it('should skip .git/modules cleanup if not found', async () => {
|
|
89
|
+
vi.mocked(fs.pathExists).mockImplementation(async (p: string) => {
|
|
90
|
+
if (p.includes('.git/modules')) return false;
|
|
91
|
+
return true;
|
|
88
92
|
});
|
|
93
|
+
await command.run({ name: 'mod' });
|
|
94
|
+
expect(fs.remove).not.toHaveBeenCalledWith(expect.stringContaining('.git/modules'));
|
|
95
|
+
});
|
|
89
96
|
});
|
|
@@ -1,91 +1,95 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runCommand } from '@nexical/cli-core';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
3
|
import ModuleUpdateCommand from '../../../../src/commands/module/update.js';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
|
|
6
6
|
vi.mock('@nexical/cli-core', async (importOriginal) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
const mod = await importOriginal<typeof import('@nexical/cli-core')>();
|
|
8
|
+
return {
|
|
9
|
+
...mod,
|
|
10
|
+
logger: {
|
|
11
|
+
...mod.logger,
|
|
12
|
+
success: vi.fn(),
|
|
13
|
+
info: vi.fn(),
|
|
14
|
+
debug: vi.fn(),
|
|
15
|
+
error: vi.fn(),
|
|
16
|
+
warn: vi.fn(),
|
|
17
|
+
},
|
|
18
|
+
runCommand: vi.fn(),
|
|
19
|
+
};
|
|
20
20
|
});
|
|
21
21
|
vi.mock('fs-extra');
|
|
22
22
|
|
|
23
23
|
describe('ModuleUpdateCommand', () => {
|
|
24
|
-
|
|
24
|
+
let command: ModuleUpdateCommand;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
36
|
-
vi.spyOn(process, 'exit').mockImplementation((() => { }) as any);
|
|
37
|
-
await command.init();
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
command = new ModuleUpdateCommand({}, { rootDir: '/mock/root' });
|
|
29
|
+
vi.spyOn(command, 'error').mockImplementation(() => {});
|
|
30
|
+
vi.spyOn(command, 'success').mockImplementation(() => {});
|
|
31
|
+
vi.spyOn(command, 'info').mockImplementation(() => {});
|
|
32
|
+
vi.mocked(fs.pathExists).mockImplementation(async (p: string) => {
|
|
33
|
+
if (p.includes('app.yml') || p.includes('nexical.yml')) return true;
|
|
34
|
+
return true;
|
|
38
35
|
});
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
38
|
+
await command.init();
|
|
39
|
+
});
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
vi.resetAllMocks();
|
|
43
|
+
});
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
it('should have correct static properties', () => {
|
|
46
|
+
expect(ModuleUpdateCommand.usage).toContain('module update');
|
|
47
|
+
expect(ModuleUpdateCommand.description).toBeDefined();
|
|
48
|
+
expect(ModuleUpdateCommand.requiresProject).toBe(true);
|
|
49
|
+
expect(ModuleUpdateCommand.args).toBeDefined();
|
|
50
|
+
});
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
it('should error if project root is missing', async () => {
|
|
53
|
+
command = new ModuleUpdateCommand({}, { rootDir: undefined });
|
|
54
|
+
vi.spyOn(command, 'init').mockImplementation(async () => {});
|
|
55
|
+
vi.spyOn(command, 'error').mockImplementation(() => {});
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
await command.runInit({});
|
|
58
|
+
expect(command.error).toHaveBeenCalledWith(
|
|
59
|
+
expect.stringContaining('requires to be run within an app project'),
|
|
60
|
+
1,
|
|
61
|
+
);
|
|
62
|
+
});
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
it('should update all modules if no name provided', async () => {
|
|
65
|
+
await command.run({});
|
|
66
|
+
expect(runCommand).toHaveBeenCalledWith(
|
|
67
|
+
expect.stringContaining('git submodule update --remote'),
|
|
68
|
+
'/mock/root',
|
|
69
|
+
);
|
|
70
|
+
expect(runCommand).toHaveBeenCalledWith('npm install', '/mock/root');
|
|
71
|
+
});
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
it('should update specific module', async () => {
|
|
74
|
+
await command.run({ name: 'mod' });
|
|
75
|
+
expect(runCommand).toHaveBeenCalledWith(
|
|
76
|
+
expect.stringContaining('git submodule update --remote --merge modules/mod'),
|
|
77
|
+
'/mock/root',
|
|
78
|
+
);
|
|
79
|
+
});
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
it('should handle failure during update', async () => {
|
|
82
|
+
vi.mocked(runCommand).mockRejectedValue(new Error('Update failed'));
|
|
83
|
+
await command.run({});
|
|
84
|
+
expect(command.error).toHaveBeenCalledWith(expect.stringContaining('Failed to update'));
|
|
85
|
+
});
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
await command.run({ name: 'missing-mod' });
|
|
89
|
-
expect(command.error).toHaveBeenCalledWith('Module missing-mod not found.');
|
|
87
|
+
it('should error if module to update not found', async () => {
|
|
88
|
+
vi.mocked(fs.pathExists).mockImplementation(async (p) => {
|
|
89
|
+
// console.log('UpdateTest: pathExists check:', p);
|
|
90
|
+
return false;
|
|
90
91
|
});
|
|
92
|
+
await command.run({ name: 'missing-mod' });
|
|
93
|
+
expect(command.error).toHaveBeenCalledWith('Module missing-mod not found.');
|
|
94
|
+
});
|
|
91
95
|
});
|