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