@somewhatabstract/x 0.0.1 → 0.1.0
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/dependabot.yml +28 -0
- package/.github/workflows/codeql-analysis.yml +29 -29
- package/.github/workflows/dependabot-pr-approval.yml +36 -0
- package/.github/workflows/nodejs.yml +84 -86
- package/.github/workflows/release.yml +4 -7
- package/.vscode/settings.json +19 -0
- package/CHANGELOG.md +23 -0
- package/CONTRIBUTING.md +3 -3
- package/README.md +132 -1
- package/biome.json +39 -0
- package/dist/x.mjs +278 -3
- package/package.json +14 -4
- package/src/__tests__/build-environment.test.ts +285 -0
- package/src/__tests__/discover-packages.test.ts +196 -0
- package/src/__tests__/errors.test.ts +59 -0
- package/src/__tests__/execute-script.test.ts +1042 -0
- package/src/__tests__/find-matching-bins.test.ts +506 -0
- package/src/__tests__/find-workspace-root.test.ts +73 -0
- package/src/__tests__/is-node-executable.test.ts +125 -0
- package/src/__tests__/resolve-bin-path.test.ts +344 -0
- package/src/__tests__/x-impl.test.ts +306 -7
- package/src/__tests__/x.test.ts +236 -0
- package/src/bin/x.ts +55 -1
- package/src/build-environment.ts +98 -0
- package/src/discover-packages.ts +35 -0
- package/src/errors.ts +10 -0
- package/src/execute-script.ts +56 -0
- package/src/find-matching-bins.ts +72 -0
- package/src/find-workspace-root.ts +24 -0
- package/src/is-node-executable.ts +16 -0
- package/src/resolve-bin-path.ts +48 -0
- package/src/x-impl.ts +95 -4
- package/tsconfig-types.json +2 -4
- package/tsconfig.json +5 -13
- package/tsdown.config.ts +1 -1
- package/vitest.config.ts +1 -0
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
import * as childProcess from "node:child_process";
|
|
2
|
+
import {beforeEach, describe, expect, it, vi} from "vitest";
|
|
3
|
+
import * as buildEnv from "../build-environment";
|
|
4
|
+
import {executeScript} from "../execute-script";
|
|
5
|
+
import type {BinInfo} from "../find-matching-bins";
|
|
6
|
+
|
|
7
|
+
// Mock child_process
|
|
8
|
+
vi.mock("node:child_process", () => ({
|
|
9
|
+
spawn: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
// Mock build-environment
|
|
13
|
+
vi.mock("../build-environment", () => ({
|
|
14
|
+
buildEnvironment: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe("executeScript", () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should call buildEnvironment with workspace root and process.env", async () => {
|
|
23
|
+
// Arrange
|
|
24
|
+
const bin: BinInfo = {
|
|
25
|
+
packageName: "test-package",
|
|
26
|
+
packagePath: "/test/package",
|
|
27
|
+
binName: "test-bin",
|
|
28
|
+
binPath: "/test/package/bin/test",
|
|
29
|
+
};
|
|
30
|
+
const args = ["--arg1", "--arg2"];
|
|
31
|
+
const workspaceRoot = "/test/workspace";
|
|
32
|
+
|
|
33
|
+
const mockEnv = {TEST_VAR: "test"};
|
|
34
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue(mockEnv);
|
|
35
|
+
|
|
36
|
+
const mockChild = {
|
|
37
|
+
on: vi.fn((event, callback) => {
|
|
38
|
+
if (event === "exit") {
|
|
39
|
+
// Immediately call exit with code 0
|
|
40
|
+
setTimeout(() => callback(0), 0);
|
|
41
|
+
}
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
45
|
+
|
|
46
|
+
// Act
|
|
47
|
+
await executeScript(bin, args, workspaceRoot);
|
|
48
|
+
|
|
49
|
+
// Assert
|
|
50
|
+
expect(buildEnv.buildEnvironment).toHaveBeenCalledWith(
|
|
51
|
+
workspaceRoot,
|
|
52
|
+
process.env,
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should spawn process with bin path and args", async () => {
|
|
57
|
+
// Arrange
|
|
58
|
+
const bin: BinInfo = {
|
|
59
|
+
packageName: "test-package",
|
|
60
|
+
packagePath: "/test/package",
|
|
61
|
+
binName: "test-bin",
|
|
62
|
+
binPath: "/test/package/bin/test",
|
|
63
|
+
};
|
|
64
|
+
const args = ["--arg1", "--arg2"];
|
|
65
|
+
const workspaceRoot = "/test/workspace";
|
|
66
|
+
|
|
67
|
+
const mockEnv = {TEST_VAR: "test"};
|
|
68
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue(mockEnv);
|
|
69
|
+
|
|
70
|
+
const mockChild = {
|
|
71
|
+
on: vi.fn((event, callback) => {
|
|
72
|
+
if (event === "exit") {
|
|
73
|
+
setTimeout(() => callback(0), 0);
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
78
|
+
|
|
79
|
+
// Act
|
|
80
|
+
await executeScript(bin, args, workspaceRoot);
|
|
81
|
+
|
|
82
|
+
// Assert
|
|
83
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
84
|
+
"/test/package/bin/test",
|
|
85
|
+
["--arg1", "--arg2"],
|
|
86
|
+
expect.objectContaining({
|
|
87
|
+
stdio: "inherit",
|
|
88
|
+
env: mockEnv,
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should pass built environment to spawn", async () => {
|
|
94
|
+
// Arrange
|
|
95
|
+
const bin: BinInfo = {
|
|
96
|
+
packageName: "test-package",
|
|
97
|
+
packagePath: "/test/package",
|
|
98
|
+
binName: "test-bin",
|
|
99
|
+
binPath: "/test/package/bin/test",
|
|
100
|
+
};
|
|
101
|
+
const args: string[] = [];
|
|
102
|
+
const workspaceRoot = "/test/workspace";
|
|
103
|
+
|
|
104
|
+
const mockEnv = {
|
|
105
|
+
TEST_VAR: "test",
|
|
106
|
+
npm_package_name: "workspace",
|
|
107
|
+
};
|
|
108
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue(mockEnv);
|
|
109
|
+
|
|
110
|
+
const mockChild = {
|
|
111
|
+
on: vi.fn((event, callback) => {
|
|
112
|
+
if (event === "exit") {
|
|
113
|
+
setTimeout(() => callback(0), 0);
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
118
|
+
|
|
119
|
+
// Act
|
|
120
|
+
await executeScript(bin, args, workspaceRoot);
|
|
121
|
+
|
|
122
|
+
// Assert
|
|
123
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
124
|
+
expect.any(String),
|
|
125
|
+
expect.any(Array),
|
|
126
|
+
expect.objectContaining({
|
|
127
|
+
env: mockEnv,
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should set stdio to inherit", async () => {
|
|
133
|
+
// Arrange
|
|
134
|
+
const bin: BinInfo = {
|
|
135
|
+
packageName: "test-package",
|
|
136
|
+
packagePath: "/test/package",
|
|
137
|
+
binName: "test-bin",
|
|
138
|
+
binPath: "/test/package/bin/test",
|
|
139
|
+
};
|
|
140
|
+
const args: string[] = [];
|
|
141
|
+
const workspaceRoot = "/test/workspace";
|
|
142
|
+
|
|
143
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
144
|
+
|
|
145
|
+
const mockChild = {
|
|
146
|
+
on: vi.fn((event, callback) => {
|
|
147
|
+
if (event === "exit") {
|
|
148
|
+
setTimeout(() => callback(0), 0);
|
|
149
|
+
}
|
|
150
|
+
}),
|
|
151
|
+
};
|
|
152
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
153
|
+
|
|
154
|
+
// Act
|
|
155
|
+
await executeScript(bin, args, workspaceRoot);
|
|
156
|
+
|
|
157
|
+
// Assert
|
|
158
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
159
|
+
expect.any(String),
|
|
160
|
+
expect.any(Array),
|
|
161
|
+
expect.objectContaining({
|
|
162
|
+
stdio: "inherit",
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should return exit code 0 on successful execution", async () => {
|
|
168
|
+
// Arrange
|
|
169
|
+
const bin: BinInfo = {
|
|
170
|
+
packageName: "test-package",
|
|
171
|
+
packagePath: "/test/package",
|
|
172
|
+
binName: "test-bin",
|
|
173
|
+
binPath: "/test/package/bin/test",
|
|
174
|
+
};
|
|
175
|
+
const args: string[] = [];
|
|
176
|
+
const workspaceRoot = "/test/workspace";
|
|
177
|
+
|
|
178
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
179
|
+
|
|
180
|
+
const mockChild = {
|
|
181
|
+
on: vi.fn((event, callback) => {
|
|
182
|
+
if (event === "exit") {
|
|
183
|
+
setTimeout(() => callback(0), 0);
|
|
184
|
+
}
|
|
185
|
+
}),
|
|
186
|
+
};
|
|
187
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
188
|
+
|
|
189
|
+
// Act
|
|
190
|
+
const exitCode = await executeScript(bin, args, workspaceRoot);
|
|
191
|
+
|
|
192
|
+
// Assert
|
|
193
|
+
expect(exitCode).toBe(0);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should return exit code from child process", async () => {
|
|
197
|
+
// Arrange
|
|
198
|
+
const bin: BinInfo = {
|
|
199
|
+
packageName: "test-package",
|
|
200
|
+
packagePath: "/test/package",
|
|
201
|
+
binName: "test-bin",
|
|
202
|
+
binPath: "/test/package/bin/test",
|
|
203
|
+
};
|
|
204
|
+
const args: string[] = [];
|
|
205
|
+
const workspaceRoot = "/test/workspace";
|
|
206
|
+
|
|
207
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
208
|
+
|
|
209
|
+
const mockChild = {
|
|
210
|
+
on: vi.fn((event, callback) => {
|
|
211
|
+
if (event === "exit") {
|
|
212
|
+
setTimeout(() => callback(42), 0);
|
|
213
|
+
}
|
|
214
|
+
}),
|
|
215
|
+
};
|
|
216
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
217
|
+
|
|
218
|
+
// Act
|
|
219
|
+
const exitCode = await executeScript(bin, args, workspaceRoot);
|
|
220
|
+
|
|
221
|
+
// Assert
|
|
222
|
+
expect(exitCode).toBe(42);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should return 1 when exit code is null", async () => {
|
|
226
|
+
// Arrange
|
|
227
|
+
const bin: BinInfo = {
|
|
228
|
+
packageName: "test-package",
|
|
229
|
+
packagePath: "/test/package",
|
|
230
|
+
binName: "test-bin",
|
|
231
|
+
binPath: "/test/package/bin/test",
|
|
232
|
+
};
|
|
233
|
+
const args: string[] = [];
|
|
234
|
+
const workspaceRoot = "/test/workspace";
|
|
235
|
+
|
|
236
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
237
|
+
|
|
238
|
+
const mockChild = {
|
|
239
|
+
on: vi.fn((event, callback) => {
|
|
240
|
+
if (event === "exit") {
|
|
241
|
+
setTimeout(() => callback(null), 0);
|
|
242
|
+
}
|
|
243
|
+
}),
|
|
244
|
+
};
|
|
245
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
246
|
+
|
|
247
|
+
// Act
|
|
248
|
+
const exitCode = await executeScript(bin, args, workspaceRoot);
|
|
249
|
+
|
|
250
|
+
// Assert
|
|
251
|
+
expect(exitCode).toBe(1);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should return 1 on spawn error (ENOENT)", async () => {
|
|
255
|
+
// Arrange
|
|
256
|
+
const bin: BinInfo = {
|
|
257
|
+
packageName: "test-package",
|
|
258
|
+
packagePath: "/test/package",
|
|
259
|
+
binName: "test-bin",
|
|
260
|
+
binPath: "/test/package/bin/nonexistent",
|
|
261
|
+
};
|
|
262
|
+
const args: string[] = [];
|
|
263
|
+
const workspaceRoot = "/test/workspace";
|
|
264
|
+
|
|
265
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
266
|
+
|
|
267
|
+
const mockChild = {
|
|
268
|
+
on: vi.fn((event, callback) => {
|
|
269
|
+
if (event === "error") {
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
const error: any = new Error("ENOENT");
|
|
272
|
+
error.code = "ENOENT";
|
|
273
|
+
callback(error);
|
|
274
|
+
}, 0);
|
|
275
|
+
}
|
|
276
|
+
}),
|
|
277
|
+
};
|
|
278
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
279
|
+
|
|
280
|
+
// Act
|
|
281
|
+
const exitCode = await executeScript(bin, args, workspaceRoot);
|
|
282
|
+
|
|
283
|
+
// Assert
|
|
284
|
+
expect(exitCode).toBe(1);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should return 1 on spawn error (EACCES)", async () => {
|
|
288
|
+
// Arrange
|
|
289
|
+
const bin: BinInfo = {
|
|
290
|
+
packageName: "test-package",
|
|
291
|
+
packagePath: "/test/package",
|
|
292
|
+
binName: "test-bin",
|
|
293
|
+
binPath: "/test/package/bin/nopermission",
|
|
294
|
+
};
|
|
295
|
+
const args: string[] = [];
|
|
296
|
+
const workspaceRoot = "/test/workspace";
|
|
297
|
+
|
|
298
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
299
|
+
|
|
300
|
+
const mockChild = {
|
|
301
|
+
on: vi.fn((event, callback) => {
|
|
302
|
+
if (event === "error") {
|
|
303
|
+
setTimeout(() => {
|
|
304
|
+
const error: any = new Error("EACCES");
|
|
305
|
+
error.code = "EACCES";
|
|
306
|
+
callback(error);
|
|
307
|
+
}, 0);
|
|
308
|
+
}
|
|
309
|
+
}),
|
|
310
|
+
};
|
|
311
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
312
|
+
|
|
313
|
+
// Act
|
|
314
|
+
const exitCode = await executeScript(bin, args, workspaceRoot);
|
|
315
|
+
|
|
316
|
+
// Assert
|
|
317
|
+
expect(exitCode).toBe(1);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should return 1 when process is killed by signal", async () => {
|
|
321
|
+
// Arrange
|
|
322
|
+
const bin: BinInfo = {
|
|
323
|
+
packageName: "test-package",
|
|
324
|
+
packagePath: "/test/package",
|
|
325
|
+
binName: "test-bin",
|
|
326
|
+
binPath: "/test/package/bin/test",
|
|
327
|
+
};
|
|
328
|
+
const args: string[] = [];
|
|
329
|
+
const workspaceRoot = "/test/workspace";
|
|
330
|
+
|
|
331
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
332
|
+
|
|
333
|
+
const mockChild = {
|
|
334
|
+
on: vi.fn((event, callback) => {
|
|
335
|
+
if (event === "exit") {
|
|
336
|
+
// Simulate SIGTERM - exit with null code and signal
|
|
337
|
+
setTimeout(() => callback(null, "SIGTERM"), 0);
|
|
338
|
+
}
|
|
339
|
+
}),
|
|
340
|
+
};
|
|
341
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
342
|
+
|
|
343
|
+
// Act
|
|
344
|
+
const exitCode = await executeScript(bin, args, workspaceRoot);
|
|
345
|
+
|
|
346
|
+
// Assert
|
|
347
|
+
expect(exitCode).toBe(1);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("should return 1 when process is killed by SIGINT", async () => {
|
|
351
|
+
// Arrange
|
|
352
|
+
const bin: BinInfo = {
|
|
353
|
+
packageName: "test-package",
|
|
354
|
+
packagePath: "/test/package",
|
|
355
|
+
binName: "test-bin",
|
|
356
|
+
binPath: "/test/package/bin/test",
|
|
357
|
+
};
|
|
358
|
+
const args: string[] = [];
|
|
359
|
+
const workspaceRoot = "/test/workspace";
|
|
360
|
+
|
|
361
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
362
|
+
|
|
363
|
+
const mockChild = {
|
|
364
|
+
on: vi.fn((event, callback) => {
|
|
365
|
+
if (event === "exit") {
|
|
366
|
+
// Simulate SIGINT (Ctrl+C) - exit with null code and signal
|
|
367
|
+
setTimeout(() => callback(null, "SIGINT"), 0);
|
|
368
|
+
}
|
|
369
|
+
}),
|
|
370
|
+
};
|
|
371
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
372
|
+
|
|
373
|
+
// Act
|
|
374
|
+
const exitCode = await executeScript(bin, args, workspaceRoot);
|
|
375
|
+
|
|
376
|
+
// Assert
|
|
377
|
+
expect(exitCode).toBe(1);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
describe("executeScript - node-executable extensions", () => {
|
|
382
|
+
beforeEach(() => {
|
|
383
|
+
vi.clearAllMocks();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("should invoke .js file via node with correct args passed through", async () => {
|
|
387
|
+
// Arrange
|
|
388
|
+
const bin: BinInfo = {
|
|
389
|
+
packageName: "test-package",
|
|
390
|
+
packagePath: "/test/package",
|
|
391
|
+
binName: "test-bin",
|
|
392
|
+
binPath: "/test/package/bin/test.js",
|
|
393
|
+
};
|
|
394
|
+
const args = ["--arg1", "--arg2"];
|
|
395
|
+
const workspaceRoot = "/test/workspace";
|
|
396
|
+
|
|
397
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
398
|
+
|
|
399
|
+
const mockChild = {
|
|
400
|
+
on: vi.fn((event, callback) => {
|
|
401
|
+
if (event === "exit") {
|
|
402
|
+
setTimeout(() => callback(0), 0);
|
|
403
|
+
}
|
|
404
|
+
}),
|
|
405
|
+
};
|
|
406
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
407
|
+
|
|
408
|
+
// Act
|
|
409
|
+
await executeScript(bin, args, workspaceRoot);
|
|
410
|
+
|
|
411
|
+
// Assert
|
|
412
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
413
|
+
process.execPath,
|
|
414
|
+
["/test/package/bin/test.js", "--arg1", "--arg2"],
|
|
415
|
+
expect.objectContaining({stdio: "inherit"}),
|
|
416
|
+
);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("should invoke .mjs file via node with correct args passed through", async () => {
|
|
420
|
+
// Arrange
|
|
421
|
+
const bin: BinInfo = {
|
|
422
|
+
packageName: "test-package",
|
|
423
|
+
packagePath: "/test/package",
|
|
424
|
+
binName: "test-bin",
|
|
425
|
+
binPath: "/test/package/bin/test.mjs",
|
|
426
|
+
};
|
|
427
|
+
const args = ["--verbose"];
|
|
428
|
+
const workspaceRoot = "/test/workspace";
|
|
429
|
+
|
|
430
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
431
|
+
|
|
432
|
+
const mockChild = {
|
|
433
|
+
on: vi.fn((event, callback) => {
|
|
434
|
+
if (event === "exit") {
|
|
435
|
+
setTimeout(() => callback(0), 0);
|
|
436
|
+
}
|
|
437
|
+
}),
|
|
438
|
+
};
|
|
439
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
440
|
+
|
|
441
|
+
// Act
|
|
442
|
+
await executeScript(bin, args, workspaceRoot);
|
|
443
|
+
|
|
444
|
+
// Assert
|
|
445
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
446
|
+
process.execPath,
|
|
447
|
+
["/test/package/bin/test.mjs", "--verbose"],
|
|
448
|
+
expect.objectContaining({stdio: "inherit"}),
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it("should invoke .cjs file via node with correct args passed through", async () => {
|
|
453
|
+
// Arrange
|
|
454
|
+
const bin: BinInfo = {
|
|
455
|
+
packageName: "test-package",
|
|
456
|
+
packagePath: "/test/package",
|
|
457
|
+
binName: "test-bin",
|
|
458
|
+
binPath: "/test/package/bin/test.cjs",
|
|
459
|
+
};
|
|
460
|
+
const args = ["input.txt"];
|
|
461
|
+
const workspaceRoot = "/test/workspace";
|
|
462
|
+
|
|
463
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
464
|
+
|
|
465
|
+
const mockChild = {
|
|
466
|
+
on: vi.fn((event, callback) => {
|
|
467
|
+
if (event === "exit") {
|
|
468
|
+
setTimeout(() => callback(0), 0);
|
|
469
|
+
}
|
|
470
|
+
}),
|
|
471
|
+
};
|
|
472
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
473
|
+
|
|
474
|
+
// Act
|
|
475
|
+
await executeScript(bin, args, workspaceRoot);
|
|
476
|
+
|
|
477
|
+
// Assert
|
|
478
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
479
|
+
process.execPath,
|
|
480
|
+
["/test/package/bin/test.cjs", "input.txt"],
|
|
481
|
+
expect.objectContaining({stdio: "inherit"}),
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it("should invoke .JS file via node (case-insensitive detection)", async () => {
|
|
486
|
+
// Arrange
|
|
487
|
+
const bin: BinInfo = {
|
|
488
|
+
packageName: "test-package",
|
|
489
|
+
packagePath: "/test/package",
|
|
490
|
+
binName: "test-bin",
|
|
491
|
+
binPath: "/test/package/bin/test.JS",
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
495
|
+
|
|
496
|
+
const mockChild = {
|
|
497
|
+
on: vi.fn((event, callback) => {
|
|
498
|
+
if (event === "exit") {
|
|
499
|
+
setTimeout(() => callback(0), 0);
|
|
500
|
+
}
|
|
501
|
+
}),
|
|
502
|
+
};
|
|
503
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
504
|
+
|
|
505
|
+
// Act
|
|
506
|
+
await executeScript(bin, [], "/test/workspace");
|
|
507
|
+
|
|
508
|
+
// Assert: original casing preserved; node used as executable
|
|
509
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
510
|
+
process.execPath,
|
|
511
|
+
["/test/package/bin/test.JS"],
|
|
512
|
+
expect.anything(),
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it("should invoke .Js file via node (case-insensitive detection)", async () => {
|
|
517
|
+
// Arrange
|
|
518
|
+
const bin: BinInfo = {
|
|
519
|
+
packageName: "test-package",
|
|
520
|
+
packagePath: "/test/package",
|
|
521
|
+
binName: "test-bin",
|
|
522
|
+
binPath: "/test/package/bin/test.Js",
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
526
|
+
|
|
527
|
+
const mockChild = {
|
|
528
|
+
on: vi.fn((event, callback) => {
|
|
529
|
+
if (event === "exit") {
|
|
530
|
+
setTimeout(() => callback(0), 0);
|
|
531
|
+
}
|
|
532
|
+
}),
|
|
533
|
+
};
|
|
534
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
535
|
+
|
|
536
|
+
// Act
|
|
537
|
+
await executeScript(bin, [], "/test/workspace");
|
|
538
|
+
|
|
539
|
+
// Assert
|
|
540
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
541
|
+
process.execPath,
|
|
542
|
+
["/test/package/bin/test.Js"],
|
|
543
|
+
expect.anything(),
|
|
544
|
+
);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it("should invoke .MJS file via node (case-insensitive detection)", async () => {
|
|
548
|
+
// Arrange
|
|
549
|
+
const bin: BinInfo = {
|
|
550
|
+
packageName: "test-package",
|
|
551
|
+
packagePath: "/test/package",
|
|
552
|
+
binName: "test-bin",
|
|
553
|
+
binPath: "/test/package/bin/test.MJS",
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
557
|
+
|
|
558
|
+
const mockChild = {
|
|
559
|
+
on: vi.fn((event, callback) => {
|
|
560
|
+
if (event === "exit") {
|
|
561
|
+
setTimeout(() => callback(0), 0);
|
|
562
|
+
}
|
|
563
|
+
}),
|
|
564
|
+
};
|
|
565
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
566
|
+
|
|
567
|
+
// Act
|
|
568
|
+
await executeScript(bin, [], "/test/workspace");
|
|
569
|
+
|
|
570
|
+
// Assert
|
|
571
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
572
|
+
process.execPath,
|
|
573
|
+
["/test/package/bin/test.MJS"],
|
|
574
|
+
expect.anything(),
|
|
575
|
+
);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it("should invoke .CJS file via node (case-insensitive detection)", async () => {
|
|
579
|
+
// Arrange
|
|
580
|
+
const bin: BinInfo = {
|
|
581
|
+
packageName: "test-package",
|
|
582
|
+
packagePath: "/test/package",
|
|
583
|
+
binName: "test-bin",
|
|
584
|
+
binPath: "/test/package/bin/test.CJS",
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
588
|
+
|
|
589
|
+
const mockChild = {
|
|
590
|
+
on: vi.fn((event, callback) => {
|
|
591
|
+
if (event === "exit") {
|
|
592
|
+
setTimeout(() => callback(0), 0);
|
|
593
|
+
}
|
|
594
|
+
}),
|
|
595
|
+
};
|
|
596
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
597
|
+
|
|
598
|
+
// Act
|
|
599
|
+
await executeScript(bin, [], "/test/workspace");
|
|
600
|
+
|
|
601
|
+
// Assert
|
|
602
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
603
|
+
process.execPath,
|
|
604
|
+
["/test/package/bin/test.CJS"],
|
|
605
|
+
expect.anything(),
|
|
606
|
+
);
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it("should invoke .Mjs file via node (case-insensitive detection)", async () => {
|
|
610
|
+
// Arrange
|
|
611
|
+
const bin: BinInfo = {
|
|
612
|
+
packageName: "test-package",
|
|
613
|
+
packagePath: "/test/package",
|
|
614
|
+
binName: "test-bin",
|
|
615
|
+
binPath: "/test/package/bin/test.Mjs",
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
619
|
+
|
|
620
|
+
const mockChild = {
|
|
621
|
+
on: vi.fn((event, callback) => {
|
|
622
|
+
if (event === "exit") {
|
|
623
|
+
setTimeout(() => callback(0), 0);
|
|
624
|
+
}
|
|
625
|
+
}),
|
|
626
|
+
};
|
|
627
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
628
|
+
|
|
629
|
+
// Act
|
|
630
|
+
await executeScript(bin, [], "/test/workspace");
|
|
631
|
+
|
|
632
|
+
// Assert
|
|
633
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
634
|
+
process.execPath,
|
|
635
|
+
["/test/package/bin/test.Mjs"],
|
|
636
|
+
expect.anything(),
|
|
637
|
+
);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it("should invoke executable bash script directly (not via node)", async () => {
|
|
641
|
+
// Arrange
|
|
642
|
+
const bin: BinInfo = {
|
|
643
|
+
packageName: "test-package",
|
|
644
|
+
packagePath: "/test/package",
|
|
645
|
+
binName: "test-bin",
|
|
646
|
+
binPath: "/test/package/bin/test.sh",
|
|
647
|
+
};
|
|
648
|
+
const args = ["--flag"];
|
|
649
|
+
|
|
650
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
651
|
+
|
|
652
|
+
const mockChild = {
|
|
653
|
+
on: vi.fn((event, callback) => {
|
|
654
|
+
if (event === "exit") {
|
|
655
|
+
setTimeout(() => callback(0), 0);
|
|
656
|
+
}
|
|
657
|
+
}),
|
|
658
|
+
};
|
|
659
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
660
|
+
|
|
661
|
+
// Act
|
|
662
|
+
await executeScript(bin, args, "/test/workspace");
|
|
663
|
+
|
|
664
|
+
// Assert: invoked directly, not via node
|
|
665
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
666
|
+
"/test/package/bin/test.sh",
|
|
667
|
+
["--flag"],
|
|
668
|
+
expect.objectContaining({stdio: "inherit"}),
|
|
669
|
+
);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("should invoke non-executable bash script directly (will error at OS level)", async () => {
|
|
673
|
+
// Arrange - a .sh without executable bit; OS will raise EACCES
|
|
674
|
+
const bin: BinInfo = {
|
|
675
|
+
packageName: "test-package",
|
|
676
|
+
packagePath: "/test/package",
|
|
677
|
+
binName: "test-bin",
|
|
678
|
+
binPath: "/test/package/bin/noperm.sh",
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
682
|
+
|
|
683
|
+
const mockChild = {
|
|
684
|
+
on: vi.fn((event, callback) => {
|
|
685
|
+
if (event === "error") {
|
|
686
|
+
setTimeout(() => {
|
|
687
|
+
const error: any = new Error("EACCES");
|
|
688
|
+
error.code = "EACCES";
|
|
689
|
+
callback(error);
|
|
690
|
+
}, 0);
|
|
691
|
+
}
|
|
692
|
+
}),
|
|
693
|
+
};
|
|
694
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
695
|
+
|
|
696
|
+
// Act
|
|
697
|
+
await executeScript(bin, [], "/test/workspace");
|
|
698
|
+
|
|
699
|
+
// Assert: invoked directly, not via node
|
|
700
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
701
|
+
"/test/package/bin/noperm.sh",
|
|
702
|
+
[],
|
|
703
|
+
expect.anything(),
|
|
704
|
+
);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it("should return 1 when non-executable bash script errors", async () => {
|
|
708
|
+
// Arrange
|
|
709
|
+
const bin: BinInfo = {
|
|
710
|
+
packageName: "test-package",
|
|
711
|
+
packagePath: "/test/package",
|
|
712
|
+
binName: "test-bin",
|
|
713
|
+
binPath: "/test/package/bin/noperm.sh",
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
717
|
+
|
|
718
|
+
const mockChild = {
|
|
719
|
+
on: vi.fn((event, callback) => {
|
|
720
|
+
if (event === "error") {
|
|
721
|
+
setTimeout(() => {
|
|
722
|
+
const error: any = new Error("EACCES");
|
|
723
|
+
error.code = "EACCES";
|
|
724
|
+
callback(error);
|
|
725
|
+
}, 0);
|
|
726
|
+
}
|
|
727
|
+
}),
|
|
728
|
+
};
|
|
729
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
730
|
+
|
|
731
|
+
// Act
|
|
732
|
+
const exitCode = await executeScript(bin, [], "/test/workspace");
|
|
733
|
+
|
|
734
|
+
// Assert
|
|
735
|
+
expect(exitCode).toBe(1);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
it("should invoke file with no extension directly", async () => {
|
|
739
|
+
// Arrange
|
|
740
|
+
const bin: BinInfo = {
|
|
741
|
+
packageName: "test-package",
|
|
742
|
+
packagePath: "/test/package",
|
|
743
|
+
binName: "test-bin",
|
|
744
|
+
binPath: "/test/package/bin/mycli",
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
748
|
+
|
|
749
|
+
const mockChild = {
|
|
750
|
+
on: vi.fn((event, callback) => {
|
|
751
|
+
if (event === "exit") {
|
|
752
|
+
setTimeout(() => callback(0), 0);
|
|
753
|
+
}
|
|
754
|
+
}),
|
|
755
|
+
};
|
|
756
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
757
|
+
|
|
758
|
+
// Act
|
|
759
|
+
await executeScript(bin, [], "/test/workspace");
|
|
760
|
+
|
|
761
|
+
// Assert
|
|
762
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
763
|
+
"/test/package/bin/mycli",
|
|
764
|
+
[],
|
|
765
|
+
expect.anything(),
|
|
766
|
+
);
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
it("should invoke file with .js.bak extension directly (not via node)", async () => {
|
|
770
|
+
// Arrange - .js is in the middle, not the final extension
|
|
771
|
+
const bin: BinInfo = {
|
|
772
|
+
packageName: "test-package",
|
|
773
|
+
packagePath: "/test/package",
|
|
774
|
+
binName: "test-bin",
|
|
775
|
+
binPath: "/test/package/bin/script.js.bak",
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
779
|
+
|
|
780
|
+
const mockChild = {
|
|
781
|
+
on: vi.fn((event, callback) => {
|
|
782
|
+
if (event === "exit") {
|
|
783
|
+
setTimeout(() => callback(0), 0);
|
|
784
|
+
}
|
|
785
|
+
}),
|
|
786
|
+
};
|
|
787
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
788
|
+
|
|
789
|
+
// Act
|
|
790
|
+
await executeScript(bin, [], "/test/workspace");
|
|
791
|
+
|
|
792
|
+
// Assert: .js.bak is not a node-executable extension
|
|
793
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
794
|
+
"/test/package/bin/script.js.bak",
|
|
795
|
+
[],
|
|
796
|
+
expect.anything(),
|
|
797
|
+
);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
it("should pass environment variables to node-invoked scripts", async () => {
|
|
801
|
+
// Arrange
|
|
802
|
+
const bin: BinInfo = {
|
|
803
|
+
packageName: "test-package",
|
|
804
|
+
packagePath: "/test/package",
|
|
805
|
+
binName: "test-bin",
|
|
806
|
+
binPath: "/test/package/bin/test.js",
|
|
807
|
+
};
|
|
808
|
+
const mockEnv = {MY_VAR: "my-value", npm_package_name: "workspace"};
|
|
809
|
+
|
|
810
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue(mockEnv);
|
|
811
|
+
|
|
812
|
+
const mockChild = {
|
|
813
|
+
on: vi.fn((event, callback) => {
|
|
814
|
+
if (event === "exit") {
|
|
815
|
+
setTimeout(() => callback(0), 0);
|
|
816
|
+
}
|
|
817
|
+
}),
|
|
818
|
+
};
|
|
819
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
820
|
+
|
|
821
|
+
// Act
|
|
822
|
+
await executeScript(bin, [], "/test/workspace");
|
|
823
|
+
|
|
824
|
+
// Assert
|
|
825
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
826
|
+
process.execPath,
|
|
827
|
+
expect.any(Array),
|
|
828
|
+
expect.objectContaining({env: mockEnv}),
|
|
829
|
+
);
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
it("should inherit stdio for node-invoked scripts", async () => {
|
|
833
|
+
// Arrange
|
|
834
|
+
const bin: BinInfo = {
|
|
835
|
+
packageName: "test-package",
|
|
836
|
+
packagePath: "/test/package",
|
|
837
|
+
binName: "test-bin",
|
|
838
|
+
binPath: "/test/package/bin/test.mjs",
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
842
|
+
|
|
843
|
+
const mockChild = {
|
|
844
|
+
on: vi.fn((event, callback) => {
|
|
845
|
+
if (event === "exit") {
|
|
846
|
+
setTimeout(() => callback(0), 0);
|
|
847
|
+
}
|
|
848
|
+
}),
|
|
849
|
+
};
|
|
850
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
851
|
+
|
|
852
|
+
// Act
|
|
853
|
+
await executeScript(bin, [], "/test/workspace");
|
|
854
|
+
|
|
855
|
+
// Assert
|
|
856
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
857
|
+
process.execPath,
|
|
858
|
+
expect.any(Array),
|
|
859
|
+
expect.objectContaining({stdio: "inherit"}),
|
|
860
|
+
);
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
it("should pass exit code through from node-invoked scripts", async () => {
|
|
864
|
+
// Arrange
|
|
865
|
+
const bin: BinInfo = {
|
|
866
|
+
packageName: "test-package",
|
|
867
|
+
packagePath: "/test/package",
|
|
868
|
+
binName: "test-bin",
|
|
869
|
+
binPath: "/test/package/bin/test.js",
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
873
|
+
|
|
874
|
+
const mockChild = {
|
|
875
|
+
on: vi.fn((event, callback) => {
|
|
876
|
+
if (event === "exit") {
|
|
877
|
+
setTimeout(() => callback(42), 0);
|
|
878
|
+
}
|
|
879
|
+
}),
|
|
880
|
+
};
|
|
881
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
882
|
+
|
|
883
|
+
// Act
|
|
884
|
+
const exitCode = await executeScript(bin, [], "/test/workspace");
|
|
885
|
+
|
|
886
|
+
// Assert
|
|
887
|
+
expect(exitCode).toBe(42);
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
it("should return 1 on spawn error (ENOENT) for node-invoked scripts", async () => {
|
|
891
|
+
// Arrange
|
|
892
|
+
const bin: BinInfo = {
|
|
893
|
+
packageName: "test-package",
|
|
894
|
+
packagePath: "/test/package",
|
|
895
|
+
binName: "test-bin",
|
|
896
|
+
binPath: "/test/package/bin/missing.js",
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
900
|
+
|
|
901
|
+
const mockChild = {
|
|
902
|
+
on: vi.fn((event, callback) => {
|
|
903
|
+
if (event === "error") {
|
|
904
|
+
setTimeout(() => {
|
|
905
|
+
const error: any = new Error("ENOENT");
|
|
906
|
+
error.code = "ENOENT";
|
|
907
|
+
callback(error);
|
|
908
|
+
}, 0);
|
|
909
|
+
}
|
|
910
|
+
}),
|
|
911
|
+
};
|
|
912
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
913
|
+
|
|
914
|
+
// Act
|
|
915
|
+
const exitCode = await executeScript(bin, [], "/test/workspace");
|
|
916
|
+
|
|
917
|
+
// Assert
|
|
918
|
+
expect(exitCode).toBe(1);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
it("should return 1 when node-invoked script is killed by SIGTERM", async () => {
|
|
922
|
+
// Arrange
|
|
923
|
+
const bin: BinInfo = {
|
|
924
|
+
packageName: "test-package",
|
|
925
|
+
packagePath: "/test/package",
|
|
926
|
+
binName: "test-bin",
|
|
927
|
+
binPath: "/test/package/bin/test.js",
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
931
|
+
|
|
932
|
+
const mockChild = {
|
|
933
|
+
on: vi.fn((event, callback) => {
|
|
934
|
+
if (event === "exit") {
|
|
935
|
+
setTimeout(() => callback(null, "SIGTERM"), 0);
|
|
936
|
+
}
|
|
937
|
+
}),
|
|
938
|
+
};
|
|
939
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
940
|
+
|
|
941
|
+
// Act
|
|
942
|
+
const exitCode = await executeScript(bin, [], "/test/workspace");
|
|
943
|
+
|
|
944
|
+
// Assert
|
|
945
|
+
expect(exitCode).toBe(1);
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
it("should return 1 when node-invoked script is killed by SIGINT", async () => {
|
|
949
|
+
// Arrange
|
|
950
|
+
const bin: BinInfo = {
|
|
951
|
+
packageName: "test-package",
|
|
952
|
+
packagePath: "/test/package",
|
|
953
|
+
binName: "test-bin",
|
|
954
|
+
binPath: "/test/package/bin/test.mjs",
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
958
|
+
|
|
959
|
+
const mockChild = {
|
|
960
|
+
on: vi.fn((event, callback) => {
|
|
961
|
+
if (event === "exit") {
|
|
962
|
+
setTimeout(() => callback(null, "SIGINT"), 0);
|
|
963
|
+
}
|
|
964
|
+
}),
|
|
965
|
+
};
|
|
966
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
967
|
+
|
|
968
|
+
// Act
|
|
969
|
+
const exitCode = await executeScript(bin, [], "/test/workspace");
|
|
970
|
+
|
|
971
|
+
// Assert
|
|
972
|
+
expect(exitCode).toBe(1);
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
it("should preserve arguments with spaces for node-invoked scripts", async () => {
|
|
976
|
+
// Arrange
|
|
977
|
+
const bin: BinInfo = {
|
|
978
|
+
packageName: "test-package",
|
|
979
|
+
packagePath: "/test/package",
|
|
980
|
+
binName: "test-bin",
|
|
981
|
+
binPath: "/test/package/bin/test.js",
|
|
982
|
+
};
|
|
983
|
+
const args = ["file with spaces.txt", "--message=hello world"];
|
|
984
|
+
|
|
985
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
986
|
+
|
|
987
|
+
const mockChild = {
|
|
988
|
+
on: vi.fn((event, callback) => {
|
|
989
|
+
if (event === "exit") {
|
|
990
|
+
setTimeout(() => callback(0), 0);
|
|
991
|
+
}
|
|
992
|
+
}),
|
|
993
|
+
};
|
|
994
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
995
|
+
|
|
996
|
+
// Act
|
|
997
|
+
await executeScript(bin, args, "/test/workspace");
|
|
998
|
+
|
|
999
|
+
// Assert
|
|
1000
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
1001
|
+
process.execPath,
|
|
1002
|
+
[
|
|
1003
|
+
"/test/package/bin/test.js",
|
|
1004
|
+
"file with spaces.txt",
|
|
1005
|
+
"--message=hello world",
|
|
1006
|
+
],
|
|
1007
|
+
expect.anything(),
|
|
1008
|
+
);
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
it("should pass multiple arguments correctly to node-invoked scripts", async () => {
|
|
1012
|
+
// Arrange
|
|
1013
|
+
const bin: BinInfo = {
|
|
1014
|
+
packageName: "test-package",
|
|
1015
|
+
packagePath: "/test/package",
|
|
1016
|
+
binName: "test-bin",
|
|
1017
|
+
binPath: "/test/package/bin/test.cjs",
|
|
1018
|
+
};
|
|
1019
|
+
const args = ["--a", "--b", "--c", "value"];
|
|
1020
|
+
|
|
1021
|
+
vi.mocked(buildEnv.buildEnvironment).mockResolvedValue({});
|
|
1022
|
+
|
|
1023
|
+
const mockChild = {
|
|
1024
|
+
on: vi.fn((event, callback) => {
|
|
1025
|
+
if (event === "exit") {
|
|
1026
|
+
setTimeout(() => callback(0), 0);
|
|
1027
|
+
}
|
|
1028
|
+
}),
|
|
1029
|
+
};
|
|
1030
|
+
vi.mocked(childProcess.spawn).mockReturnValue(mockChild as any);
|
|
1031
|
+
|
|
1032
|
+
// Act
|
|
1033
|
+
await executeScript(bin, args, "/test/workspace");
|
|
1034
|
+
|
|
1035
|
+
// Assert
|
|
1036
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
1037
|
+
process.execPath,
|
|
1038
|
+
["/test/package/bin/test.cjs", "--a", "--b", "--c", "value"],
|
|
1039
|
+
expect.anything(),
|
|
1040
|
+
);
|
|
1041
|
+
});
|
|
1042
|
+
});
|