@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.
Files changed (75) 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 +36 -30
  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 +8 -4
  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 +24 -13
  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.js +17 -4
  33. package/dist/src/commands/setup.js.map +1 -1
  34. package/dist/src/utils/discovery.js +1 -1
  35. package/dist/src/utils/git.js +1 -1
  36. package/dist/src/utils/url-resolver.js +1 -1
  37. package/eslint.config.mjs +67 -0
  38. package/index.ts +34 -20
  39. package/package.json +56 -32
  40. package/src/commands/init.ts +79 -76
  41. package/src/commands/module/add.ts +158 -148
  42. package/src/commands/module/list.ts +61 -50
  43. package/src/commands/module/remove.ts +59 -54
  44. package/src/commands/module/update.ts +44 -42
  45. package/src/commands/run.ts +89 -81
  46. package/src/commands/setup.ts +78 -60
  47. package/src/utils/discovery.ts +98 -113
  48. package/src/utils/git.ts +35 -28
  49. package/src/utils/url-resolver.ts +50 -45
  50. package/test/e2e/lifecycle.e2e.test.ts +139 -131
  51. package/test/integration/commands/init.integration.test.ts +64 -64
  52. package/test/integration/commands/module.integration.test.ts +122 -122
  53. package/test/integration/commands/run.integration.test.ts +70 -63
  54. package/test/integration/utils/command-loading.integration.test.ts +40 -53
  55. package/test/unit/commands/init.test.ts +163 -128
  56. package/test/unit/commands/module/add.test.ts +312 -245
  57. package/test/unit/commands/module/list.test.ts +108 -91
  58. package/test/unit/commands/module/remove.test.ts +74 -67
  59. package/test/unit/commands/module/update.test.ts +74 -70
  60. package/test/unit/commands/run.test.ts +253 -201
  61. package/test/unit/commands/setup.test.ts +146 -128
  62. package/test/unit/utils/command-discovery.test.ts +138 -125
  63. package/test/unit/utils/git.test.ts +135 -117
  64. package/test/unit/utils/integration-helpers.test.ts +59 -49
  65. package/test/unit/utils/url-resolver.test.ts +46 -34
  66. package/test/utils/integration-helpers.ts +36 -29
  67. package/tsconfig.json +15 -25
  68. package/tsup.config.ts +14 -14
  69. package/vitest.config.ts +10 -10
  70. package/vitest.e2e.config.ts +6 -6
  71. package/vitest.integration.config.ts +17 -17
  72. package/dist/chunk-JYASTIIW.js.map +0 -1
  73. package/dist/chunk-OKXOCNXP.js +0 -105
  74. package/dist/chunk-OKXOCNXP.js.map +0 -1
  75. package/dist/chunk-WKERTCM6.js.map +0 -1
@@ -1,4 +1,3 @@
1
- import { logger } from '@nexical/cli-core';
2
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
2
  import RunCommand from '../../../src/commands/run.js';
4
3
  import fs from 'fs-extra';
@@ -7,246 +6,299 @@ import EventEmitter from 'events';
7
6
  import process from 'node:process';
8
7
 
9
8
  vi.mock('@nexical/cli-core', async (importOriginal) => {
10
- const mod = await importOriginal<typeof import('@nexical/cli-core')>();
11
- return {
12
- ...mod,
13
- logger: { code: vi.fn(), debug: vi.fn(), error: vi.fn(), success: vi.fn(), info: vi.fn(), warn: vi.fn() }
14
- }
9
+ const mod = await importOriginal<typeof import('@nexical/cli-core')>();
10
+ return {
11
+ ...mod,
12
+ logger: {
13
+ code: vi.fn(),
14
+ debug: vi.fn(),
15
+ error: vi.fn(),
16
+ success: vi.fn(),
17
+ info: vi.fn(),
18
+ warn: vi.fn(),
19
+ },
20
+ };
15
21
  });
16
22
  vi.mock('fs-extra');
17
23
  vi.mock('child_process');
18
24
  vi.mock('child_process');
19
25
 
20
26
  describe('RunCommand', () => {
21
- let command: RunCommand;
22
- let mockChild: any;
23
- let mockExit: any;
24
-
25
- beforeEach(async () => {
26
- vi.clearAllMocks();
27
- command = new RunCommand({}, { rootDir: '/mock/root' });
28
-
29
- mockChild = new EventEmitter();
30
- mockChild.kill = vi.fn();
31
- mockChild.stdout = new EventEmitter();
32
- mockChild.stderr = new EventEmitter();
33
- vi.mocked(cp.spawn).mockReturnValue(mockChild as any);
34
-
35
- vi.spyOn(command, 'error').mockImplementation((() => { }) as any);
36
- vi.spyOn(command, 'info').mockImplementation((() => { }) as any);
37
- vi.spyOn(command, 'success').mockImplementation((() => { }) as any);
38
- vi.spyOn(command, 'warn').mockImplementation((() => { }) as any);
39
-
40
- // Defaultfs mocks
41
- vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
42
- if (p.includes('package.json')) return true;
43
- return false;
44
- });
45
- vi.mocked(fs.readJson).mockImplementation(async (p: any) => {
46
- return { scripts: { test: 'echo test', sc: 'echo sc' } };
47
- });
48
-
49
- vi.spyOn(process, 'on').mockImplementation((event: string | symbol, listener: any) => {
50
- return process;
51
- });
52
-
53
- await command.init();
54
- mockExit = vi.spyOn(process, 'exit').mockImplementation((() => { }) as any);
27
+ let command: RunCommand;
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ let mockChild: any;
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ let mockExit: any;
32
+
33
+ beforeEach(async () => {
34
+ vi.clearAllMocks();
35
+ command = new RunCommand({}, { rootDir: '/mock/root' });
36
+
37
+ mockChild = new EventEmitter();
38
+ mockChild.kill = vi.fn();
39
+ mockChild.stdout = new EventEmitter();
40
+ mockChild.stderr = new EventEmitter();
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ vi.mocked(cp.spawn).mockReturnValue(mockChild as any);
43
+
44
+ vi.spyOn(command, 'error').mockImplementation(() => {});
45
+ vi.spyOn(command, 'info').mockImplementation(() => {});
46
+ vi.spyOn(command, 'success').mockImplementation(() => {});
47
+ vi.spyOn(command, 'warn').mockImplementation(() => {});
48
+
49
+ // Defaultfs mocks
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
52
+ if (p.includes('package.json')) return true;
53
+ return false;
55
54
  });
56
-
57
- afterEach(() => {
58
- vi.resetAllMocks();
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ vi.mocked(fs.readJson).mockImplementation(async (p: any) => {
57
+ return { scripts: { test: 'echo test', sc: 'echo sc' } };
59
58
  });
60
59
 
61
- it('should have correct static properties', () => {
62
- // expect(RunCommand.paths).toEqual([['run']]); // run is default? Check base command implementation if needed, but 'usage' covers it.
63
- expect(RunCommand.usage).toBe('run <script> [args...]');
64
- expect(RunCommand.requiresProject).toBe(true);
65
- expect(RunCommand.args).toBeDefined();
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ vi.spyOn(process, 'on').mockImplementation((event: string | symbol, listener: any) => {
62
+ return process;
66
63
  });
67
64
 
68
- it('should error if project root is missing', async () => {
69
- command = new RunCommand({}, { rootDir: undefined });
70
- vi.spyOn(command, 'init').mockImplementation(async () => { });
71
- vi.spyOn(command, 'error').mockImplementation((() => { }) as any);
72
-
73
- await command.runInit({ script: 'script', args: [] });
74
- expect(command.error).toHaveBeenCalledWith(expect.stringContaining('requires to be run within an app project'), 1);
65
+ await command.init();
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
+ mockExit = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
68
+ });
69
+
70
+ afterEach(() => {
71
+ vi.resetAllMocks();
72
+ });
73
+
74
+ it('should have correct static properties', () => {
75
+ // expect(RunCommand.paths).toEqual([['run']]); // run is default? Check base command implementation if needed, but 'usage' covers it.
76
+ expect(RunCommand.usage).toBe('run <script> [args...]');
77
+ expect(RunCommand.requiresProject).toBe(true);
78
+ expect(RunCommand.args).toBeDefined();
79
+ });
80
+
81
+ it('should error if project root is missing', async () => {
82
+ command = new RunCommand({}, { rootDir: undefined });
83
+ vi.spyOn(command, 'init').mockImplementation(async () => {});
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ vi.spyOn(command, 'error').mockImplementation((() => {}) as any);
86
+
87
+ await command.runInit({ script: 'script', args: [] });
88
+ expect(command.error).toHaveBeenCalledWith(
89
+ expect.stringContaining('requires to be run within an app project'),
90
+ 1,
91
+ );
92
+ });
93
+
94
+ it('should error if script is missing', async () => {
95
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
+ await command.run({} as any);
97
+ expect(command.error).toHaveBeenCalledWith('Please specify a script to run.');
98
+ });
99
+
100
+ it('should run core script via npm', async () => {
101
+ setTimeout(() => {
102
+ mockChild.emit('close', 0);
103
+ }, 10);
104
+
105
+ // run(options)
106
+ await command.run({ script: 'test', args: [] });
107
+
108
+ expect(cp.spawn).toHaveBeenCalledWith(
109
+ 'npm',
110
+ ['run', 'test', '--'],
111
+ expect.objectContaining({
112
+ cwd: '/mock/root',
113
+ }),
114
+ );
115
+ });
116
+
117
+ it('should run module script if resolved', async () => {
118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
+ vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
120
+ return p.includes('stripe/package.json') || p.includes('stripe') || p.includes('core');
75
121
  });
76
-
77
- it('should error if script is missing', async () => {
78
- await command.run({} as any);
79
- expect(command.error).toHaveBeenCalledWith('Please specify a script to run.');
122
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
+ vi.mocked(fs.readJson).mockImplementation(async (p: any) => {
124
+ if (p.includes('stripe')) {
125
+ return { scripts: { sync: 'node scripts/sync.js' } };
126
+ }
127
+ return { scripts: { test: 'echo test' } };
80
128
  });
81
129
 
82
- it('should run core script via npm', async () => {
83
- setTimeout(() => {
84
- mockChild.emit('close', 0);
85
- }, 10);
86
-
87
- // run(options)
88
- await command.run({ script: 'test', args: [] });
89
-
90
- expect(cp.spawn).toHaveBeenCalledWith('npm', ['run', 'test', '--'], expect.objectContaining({
91
- cwd: '/mock/root'
92
- }));
130
+ setTimeout(() => {
131
+ mockChild.emit('close', 0);
132
+ }, 10);
133
+
134
+ await command.run({ script: 'stripe:sync', args: ['--flag'] });
135
+
136
+ // Expect shell execution of raw command
137
+ // Expect npm run <scriptName>
138
+ expect(cp.spawn).toHaveBeenCalledWith(
139
+ 'npm',
140
+ expect.arrayContaining(['run', 'sync', '--', '--flag']),
141
+ expect.objectContaining({
142
+ cwd: expect.stringContaining('/modules/stripe'),
143
+ }),
144
+ );
145
+ expect(cp.spawn).toHaveBeenCalledWith(
146
+ 'npm',
147
+ expect.arrayContaining(['run', 'sync', '--', '--flag']),
148
+ expect.objectContaining({
149
+ cwd: expect.stringContaining('/modules/stripe'),
150
+ }),
151
+ );
152
+ // strict run.ts does not log "Running module script..." in new revision
153
+ // expect(command.info).toHaveBeenCalledWith(expect.stringContaining('Running module script'));
154
+ });
155
+
156
+ it('should handle module script read error', async () => {
157
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
+ vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
159
+ return p.includes('stripe'); // module exists
93
160
  });
94
-
95
- it('should run module script if resolved', async () => {
96
- vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
97
- return p.includes('stripe/package.json') || p.includes('stripe') || p.includes('core');
98
- });
99
- vi.mocked(fs.readJson).mockImplementation(async (p: any) => {
100
- if (p.includes('stripe')) {
101
- return { scripts: { sync: 'node scripts/sync.js' } };
102
- }
103
- return { scripts: { test: 'echo test' } };
104
- });
105
-
106
- setTimeout(() => {
107
- mockChild.emit('close', 0);
108
- }, 10);
109
-
110
- await command.run({ script: 'stripe:sync', args: ['--flag'] });
111
-
112
- // Expect shell execution of raw command
113
- // Expect npm run <scriptName>
114
- expect(cp.spawn).toHaveBeenCalledWith('npm', expect.arrayContaining([
115
- 'run', 'sync', '--', '--flag'
116
- ]), expect.objectContaining({
117
- cwd: expect.stringContaining('/modules/stripe')
118
- }));
119
- expect(cp.spawn).toHaveBeenCalledWith('npm', expect.arrayContaining([
120
- 'run', 'sync', '--', '--flag'
121
- ]), expect.objectContaining({
122
- cwd: expect.stringContaining('/modules/stripe')
123
- }));
124
- // strict run.ts does not log "Running module script..." in new revision
125
- // expect(command.info).toHaveBeenCalledWith(expect.stringContaining('Running module script'));
161
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
+ vi.mocked(fs.readJson).mockImplementation(async (p: any) => {
163
+ if (p.includes('stripe')) {
164
+ throw new Error('Read failed');
165
+ }
166
+ return { scripts: {} };
126
167
  });
127
168
 
128
- it('should handle module script read error', async () => {
129
- vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
130
- return p.includes('stripe'); // module exists
131
- });
132
- vi.mocked(fs.readJson).mockImplementation(async (p: any) => {
133
- if (p.includes('stripe')) {
134
- throw new Error('Read failed');
135
- }
136
- return { scripts: {} };
137
- });
169
+ setTimeout(() => {
170
+ mockChild.emit('close', 0);
171
+ }, 10);
138
172
 
139
- setTimeout(() => {
140
- mockChild.emit('close', 0);
141
- }, 10);
173
+ await command.run({ script: 'stripe:sync', args: [] });
142
174
 
143
- await command.run({ script: 'stripe:sync', args: [] });
175
+ expect(command.error).toHaveBeenCalledWith(
176
+ expect.stringContaining('Failed to read package.json'),
177
+ );
178
+ });
144
179
 
145
- expect(command.error).toHaveBeenCalledWith(expect.stringContaining('Failed to read package.json'));
180
+ it('should ignore module script if package.json missing', async () => {
181
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
+ vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
183
+ return p.includes('stripe') && !p.includes('package.json');
146
184
  });
147
185
 
148
- it('should ignore module script if package.json missing', async () => {
149
- vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
150
- return p.includes('stripe') && !p.includes('package.json');
151
- });
186
+ vi.mocked(fs.readJson).mockResolvedValue({
187
+ scripts: { 'stripe:sync': 'fallback' },
188
+ });
152
189
 
153
- vi.mocked(fs.readJson).mockResolvedValue({
154
- scripts: { 'stripe:sync': 'fallback' }
155
- });
190
+ setTimeout(() => {
191
+ mockChild.emit('close', 0);
192
+ }, 10);
193
+ await command.run({ script: 'stripe:sync', args: [] });
194
+
195
+ // Should error strict
196
+ expect(command.error).toHaveBeenCalledWith(
197
+ expect.stringContaining('Failed to find package.json'),
198
+ );
199
+ });
200
+
201
+ it('should handle cleanup signals', async () => {
202
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
203
+ const listeners: Record<string, Function> = {};
204
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
205
+ vi.spyOn(process, 'on').mockImplementation((event: string | symbol, listener: any) => {
206
+ listeners[event.toString()] = listener;
207
+ return process;
208
+ });
156
209
 
157
- setTimeout(() => { mockChild.emit('close', 0); }, 10);
158
- await command.run({ script: 'stripe:sync', args: [] });
210
+ const runPromise = command.run({ script: 'test', args: [] });
211
+ await new Promise((resolve) => setTimeout(resolve, 0));
159
212
 
160
- // Should error strict
161
- expect(command.error).toHaveBeenCalledWith(expect.stringContaining('Failed to find package.json'));
162
- });
213
+ // Simulate signal by calling listener directly
214
+ if (listeners['SIGINT']) listeners['SIGINT']();
215
+ mockChild.emit('close', 0);
163
216
 
164
- it('should handle cleanup signals', async () => {
165
- const listeners: Record<string, Function> = {};
166
- vi.spyOn(process, 'on').mockImplementation((event: string | symbol, listener: any) => {
167
- listeners[event.toString()] = listener;
168
- return process;
169
- });
217
+ await runPromise;
170
218
 
171
- const runPromise = command.run({ script: 'test', args: [] });
172
- await new Promise(resolve => setTimeout(resolve, 0));
219
+ expect(mockExit).toHaveBeenCalled();
220
+ });
173
221
 
174
- // Simulate signal by calling listener directly
175
- if (listeners['SIGINT']) listeners['SIGINT']();
176
- mockChild.emit('close', 0);
222
+ it('should handle non-zero exit code', async () => {
223
+ setTimeout(() => {
224
+ mockChild.emit('close');
225
+ }, 10);
226
+ await command.run({ script: 'test', args: [] });
227
+ await new Promise((resolve) => setTimeout(resolve, 100));
228
+ expect(mockExit).toHaveBeenCalledWith(1);
229
+ });
177
230
 
178
- await runPromise;
231
+ it('should use cmd on windows for module scripts', async () => {
232
+ const originalPlatform = process.platform;
233
+ Object.defineProperty(process, 'platform', { value: 'win32' });
179
234
 
180
- expect(mockExit).toHaveBeenCalled();
235
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
236
+ vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
237
+ return p.includes('stripe');
181
238
  });
182
-
183
- it('should handle non-zero exit code', async () => {
184
- setTimeout(() => {
185
- mockChild.emit('close');
186
- }, 10);
187
- await command.run({ script: 'test', args: [] });
188
- await new Promise(resolve => setTimeout(resolve, 100));
189
- expect(mockExit).toHaveBeenCalledWith(1);
239
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
240
+ vi.mocked(fs.readJson).mockImplementation(async (p: any) => {
241
+ if (p.includes('stripe')) {
242
+ return { scripts: { sync: 'node scripts/sync.js' } };
243
+ }
244
+ return { scripts: {} };
190
245
  });
191
246
 
192
- it('should use cmd on windows for module scripts', async () => {
193
- const originalPlatform = process.platform;
194
- Object.defineProperty(process, 'platform', { value: 'win32' });
195
-
196
- vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
197
- return p.includes('stripe');
198
- });
199
- vi.mocked(fs.readJson).mockImplementation(async (p: any) => {
200
- if (p.includes('stripe')) {
201
- return { scripts: { sync: 'node scripts/sync.js' } };
202
- }
203
- return { scripts: {} };
204
- });
205
-
206
- setTimeout(() => { mockChild.emit('close', 0); }, 10);
207
- await command.run({ script: 'stripe:sync', args: [] });
208
-
209
- expect(cp.spawn).toHaveBeenCalledWith('npm', expect.arrayContaining([
210
- 'run', 'sync'
211
- ]), expect.anything());
212
-
213
- Object.defineProperty(process, 'platform', { value: originalPlatform });
247
+ setTimeout(() => {
248
+ mockChild.emit('close', 0);
249
+ }, 10);
250
+ await command.run({ script: 'stripe:sync', args: [] });
251
+
252
+ expect(cp.spawn).toHaveBeenCalledWith(
253
+ 'npm',
254
+ expect.arrayContaining(['run', 'sync']),
255
+ expect.anything(),
256
+ );
257
+
258
+ Object.defineProperty(process, 'platform', { value: originalPlatform });
259
+ });
260
+ it('should fall back to default behavior if script not found in module', async () => {
261
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
262
+ vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
263
+ return p.includes('src/modules/mymod') || p.includes('package.json');
214
264
  });
215
- it('should fall back to default behavior if script not found in module', async () => {
216
- vi.mocked(fs.pathExists).mockImplementation(async (p: any) => {
217
- return p.includes('src/modules/mymod') || p.includes('package.json');
218
- });
219
- vi.mocked(fs.readJson).mockResolvedValue({
220
- name: 'mymod',
221
- scripts: { other: 'command' }
222
- });
223
-
224
- setTimeout(() => { mockChild.emit('close', 0); }, 10);
225
- await command.run({ script: 'mymod:missing', args: [] });
226
-
227
- // Should error strict
228
- expect(command.error).toHaveBeenCalledWith(expect.stringContaining('does not exist in module mymod'));
229
- expect(cp.spawn).not.toHaveBeenCalled();
265
+ vi.mocked(fs.readJson).mockResolvedValue({
266
+ name: 'mymod',
267
+ scripts: { other: 'command' },
230
268
  });
231
269
 
232
- it('should handle null exit code', async () => {
233
- setTimeout(() => {
234
- mockChild.emit('close'); // emit undefined
235
- }, 10);
270
+ setTimeout(() => {
271
+ mockChild.emit('close', 0);
272
+ }, 10);
273
+ await command.run({ script: 'mymod:missing', args: [] });
236
274
 
237
- await command.run({ script: 'test', args: [] });
238
- await new Promise(resolve => setTimeout(resolve, 100));
275
+ // Should error strict
276
+ expect(command.error).toHaveBeenCalledWith(
277
+ expect.stringContaining('does not exist in module mymod'),
278
+ );
279
+ expect(cp.spawn).not.toHaveBeenCalled();
280
+ });
239
281
 
240
- expect(mockExit).toHaveBeenCalledWith(1);
241
- });
282
+ it('should handle null exit code', async () => {
283
+ setTimeout(() => {
284
+ mockChild.emit('close'); // emit undefined
285
+ }, 10);
242
286
 
243
- it('should error if script not found in core', async () => {
244
- vi.mocked(fs.readJson).mockResolvedValue({
245
- scripts: { test: 'echo test' }
246
- });
287
+ await command.run({ script: 'test', args: [] });
288
+ await new Promise((resolve) => setTimeout(resolve, 100));
247
289
 
248
- await command.run({ script: 'missing-script', args: [] });
290
+ expect(mockExit).toHaveBeenCalledWith(1);
291
+ });
249
292
 
250
- expect(command.error).toHaveBeenCalledWith(expect.stringContaining('does not exist in Nexical core'));
293
+ it('should error if script not found in core', async () => {
294
+ vi.mocked(fs.readJson).mockResolvedValue({
295
+ scripts: { test: 'echo test' },
251
296
  });
297
+
298
+ await command.run({ script: 'missing-script', args: [] });
299
+
300
+ expect(command.error).toHaveBeenCalledWith(
301
+ expect.stringContaining('does not exist in Nexical core'),
302
+ );
303
+ });
252
304
  });