@effectionx/process 0.6.2 → 0.7.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 (91) hide show
  1. package/CHANGELOG.md +269 -0
  2. package/LICENSE +9 -0
  3. package/dist/mod.d.ts +3 -0
  4. package/dist/mod.d.ts.map +1 -0
  5. package/{esm → dist}/src/daemon.d.ts +1 -1
  6. package/dist/src/daemon.d.ts.map +1 -0
  7. package/{script → dist}/src/exec/api.d.ts +2 -4
  8. package/dist/src/exec/api.d.ts.map +1 -0
  9. package/{script → dist}/src/exec/error.d.ts +1 -1
  10. package/dist/src/exec/error.d.ts.map +1 -0
  11. package/dist/src/exec/error.js +29 -0
  12. package/dist/src/exec/posix.d.ts +3 -0
  13. package/dist/src/exec/posix.d.ts.map +1 -0
  14. package/{esm → dist}/src/exec/posix.js +12 -13
  15. package/{esm → dist}/src/exec/win32.d.ts +1 -1
  16. package/dist/src/exec/win32.d.ts.map +1 -0
  17. package/{esm → dist}/src/exec/win32.js +20 -13
  18. package/{script → dist}/src/exec.d.ts +3 -3
  19. package/dist/src/exec.d.ts.map +1 -0
  20. package/{esm → dist}/src/exec.js +2 -4
  21. package/dist/src/helpers.d.ts +4 -0
  22. package/dist/src/helpers.d.ts.map +1 -0
  23. package/dist/src/helpers.js +10 -0
  24. package/dist/tsconfig.tsbuildinfo +1 -0
  25. package/mod.ts +2 -0
  26. package/package.json +24 -18
  27. package/src/daemon.ts +34 -0
  28. package/src/exec/api.ts +83 -0
  29. package/src/exec/error.ts +43 -0
  30. package/src/exec/posix.ts +133 -0
  31. package/src/exec/win32.ts +217 -0
  32. package/src/exec.ts +100 -0
  33. package/src/helpers.ts +12 -0
  34. package/test/daemon.test.ts +106 -0
  35. package/test/eventemitter.test.ts +82 -0
  36. package/test/exec.test.ts +524 -0
  37. package/test/fixtures/dump-args.js +13 -0
  38. package/test/fixtures/echo-server.ts +49 -0
  39. package/test/fixtures/hello-world-failed.js +5 -0
  40. package/test/fixtures/hello-world.js +5 -0
  41. package/test/helpers.ts +67 -0
  42. package/test/output-stream.test.ts +73 -0
  43. package/tsconfig.json +20 -0
  44. package/esm/mod.d.ts +0 -3
  45. package/esm/mod.d.ts.map +0 -1
  46. package/esm/package.json +0 -3
  47. package/esm/src/daemon.d.ts.map +0 -1
  48. package/esm/src/eventemitter.d.ts +0 -22
  49. package/esm/src/eventemitter.d.ts.map +0 -1
  50. package/esm/src/eventemitter.js +0 -40
  51. package/esm/src/exec/api.d.ts +0 -70
  52. package/esm/src/exec/api.d.ts.map +0 -1
  53. package/esm/src/exec/error.d.ts +0 -14
  54. package/esm/src/exec/error.d.ts.map +0 -1
  55. package/esm/src/exec/error.js +0 -54
  56. package/esm/src/exec/posix.d.ts +0 -3
  57. package/esm/src/exec/posix.d.ts.map +0 -1
  58. package/esm/src/exec/win32.d.ts.map +0 -1
  59. package/esm/src/exec.d.ts +0 -16
  60. package/esm/src/exec.d.ts.map +0 -1
  61. package/esm/src/helpers.d.ts +0 -12
  62. package/esm/src/helpers.d.ts.map +0 -1
  63. package/esm/src/helpers.js +0 -71
  64. package/script/mod.d.ts +0 -3
  65. package/script/mod.d.ts.map +0 -1
  66. package/script/mod.js +0 -20
  67. package/script/package.json +0 -3
  68. package/script/src/daemon.d.ts +0 -11
  69. package/script/src/daemon.d.ts.map +0 -1
  70. package/script/src/daemon.js +0 -23
  71. package/script/src/eventemitter.d.ts +0 -22
  72. package/script/src/eventemitter.d.ts.map +0 -1
  73. package/script/src/eventemitter.js +0 -44
  74. package/script/src/exec/api.d.ts.map +0 -1
  75. package/script/src/exec/api.js +0 -2
  76. package/script/src/exec/error.d.ts.map +0 -1
  77. package/script/src/exec/error.js +0 -59
  78. package/script/src/exec/posix.d.ts +0 -3
  79. package/script/src/exec/posix.d.ts.map +0 -1
  80. package/script/src/exec/posix.js +0 -117
  81. package/script/src/exec/win32.d.ts +0 -4
  82. package/script/src/exec/win32.d.ts.map +0 -1
  83. package/script/src/exec/win32.js +0 -177
  84. package/script/src/exec.d.ts.map +0 -1
  85. package/script/src/exec.js +0 -114
  86. package/script/src/helpers.d.ts +0 -12
  87. package/script/src/helpers.d.ts.map +0 -1
  88. package/script/src/helpers.js +0 -76
  89. /package/{esm → dist}/mod.js +0 -0
  90. /package/{esm → dist}/src/daemon.js +0 -0
  91. /package/{esm → dist}/src/exec/api.js +0 -0
@@ -0,0 +1,524 @@
1
+ import process from "node:process";
2
+ import { beforeEach, describe, it } from "@effectionx/bdd";
3
+ import { type Task, spawn, withResolvers } from "effection";
4
+ import { expect } from "expect";
5
+
6
+ import { captureError, fetchText } from "./helpers.ts";
7
+
8
+ import { lines } from "@effectionx/stream-helpers";
9
+ import { type Process, type ProcessResult, exec } from "../mod.ts";
10
+
11
+ const SystemRoot = process.env.SystemRoot;
12
+
13
+ // Validate SHELL environment variable is set for proper test execution
14
+ if (process.platform === "win32" && !process.env.SHELL) {
15
+ throw new Error(
16
+ "SHELL environment variable is required for Windows tests.\n" +
17
+ "Please set SHELL using one of these commands:\n" +
18
+ " PowerShell: $env:SHELL = 'powershell'\n" +
19
+ " pwsh: $env:SHELL = 'pwsh'\n" +
20
+ " CMD: set SHELL=cmd\n" +
21
+ " Git Bash: export SHELL=bash",
22
+ );
23
+ }
24
+
25
+ const isBash = () => {
26
+ // On POSIX systems, SHELL is undefined so default to bash
27
+ if (process.platform !== "win32") return true;
28
+
29
+ // On Windows, SHELL is required and set, check it or fallback to process.env.shell
30
+ const shell = process.env.SHELL?.toLowerCase();
31
+ const processShell = process.env.shell;
32
+
33
+ return shell === "bash" || processShell?.endsWith("bash.exe");
34
+ };
35
+
36
+ describe("exec", () => {
37
+ describe(".join", () => {
38
+ it("runs successfully to completion", function* () {
39
+ let result: ProcessResult = yield* exec(
40
+ "node './fixtures/hello-world.js'",
41
+ {
42
+ cwd: import.meta.dirname,
43
+ },
44
+ ).join();
45
+
46
+ expect(result).toMatchObject({
47
+ code: 0,
48
+ stdout: "hello\nworld\n",
49
+ stderr: "boom\n",
50
+ });
51
+ });
52
+
53
+ it("runs failed process to completion", function* () {
54
+ let result: ProcessResult = yield* exec(
55
+ "node './fixtures/hello-world-failed.js'",
56
+ {
57
+ cwd: import.meta.dirname,
58
+ },
59
+ ).join();
60
+
61
+ expect(result.code).toEqual(37);
62
+ expect(result.stdout).toEqual("hello world\n");
63
+ expect(result.stderr).toEqual("boom\n");
64
+ });
65
+ });
66
+
67
+ describe(".expect", () => {
68
+ expect.assertions(1);
69
+ it("runs successfully to completion", function* () {
70
+ let result: ProcessResult = yield* exec(
71
+ "node './fixtures/hello-world.js'",
72
+ {
73
+ cwd: import.meta.dirname,
74
+ },
75
+ ).expect();
76
+
77
+ expect(result).toMatchObject({
78
+ code: 0,
79
+ stdout: "hello\nworld\n",
80
+ stderr: "boom\n",
81
+ });
82
+ });
83
+
84
+ it("throws an error if process fails", function* () {
85
+ let error: Error = yield* captureError(
86
+ exec("node './test/fixtures/hello-world-failed.js'").expect(),
87
+ );
88
+
89
+ expect(error.name).toEqual("ExecError");
90
+ });
91
+ });
92
+
93
+ describe("spawning", () => {
94
+ describe("a process that fails to start because executable is not found", () => {
95
+ it("calling join() throws an exception", function* () {
96
+ let error: unknown;
97
+ let proc = yield* exec("argle", { arguments: ["bargle"] });
98
+ try {
99
+ yield* proc.join();
100
+ } catch (e) {
101
+ error = e;
102
+ }
103
+ expect(error).toBeInstanceOf(Error);
104
+ });
105
+ it("calling expect() throws an exception", function* () {
106
+ let error: unknown;
107
+ let proc = yield* exec("argle", { arguments: ["bargle"] });
108
+ try {
109
+ yield* proc.expect();
110
+ } catch (e) {
111
+ error = e;
112
+ }
113
+
114
+ expect(error).toBeDefined();
115
+ });
116
+ });
117
+ });
118
+ describe("successfully", () => {
119
+ let proc: Process;
120
+ let stdoutTask: Task<{ sawData: boolean; matched: boolean }>;
121
+ let stderrTask: Task<{ sawData: boolean }>;
122
+
123
+ beforeEach(function* () {
124
+ const stdoutReady = withResolvers<boolean>();
125
+
126
+ proc = yield* exec(
127
+ "node --experimental-strip-types './fixtures/echo-server.ts'",
128
+ {
129
+ env: {
130
+ PORT: "29000",
131
+ PATH: process.env.PATH as string,
132
+ ...(SystemRoot ? { SystemRoot } : {}),
133
+ },
134
+ cwd: import.meta.dirname,
135
+ },
136
+ );
137
+
138
+ stdoutTask = yield* spawn(function* () {
139
+ const subscription = yield* lines()(proc.stdout);
140
+ let sawData = false;
141
+ let matched = false;
142
+ let resolved = false;
143
+ let next = yield* subscription.next();
144
+ while (!next.done) {
145
+ sawData = true;
146
+ if (!matched && /listening/.test(next.value)) {
147
+ matched = true;
148
+ if (!resolved) {
149
+ stdoutReady.resolve(true);
150
+ resolved = true;
151
+ }
152
+ }
153
+ next = yield* subscription.next();
154
+ }
155
+ if (!resolved) {
156
+ stdoutReady.resolve(false);
157
+ }
158
+ return { sawData, matched };
159
+ });
160
+
161
+ stderrTask = yield* spawn(function* () {
162
+ const subscription = yield* proc.stderr;
163
+ let sawData = false;
164
+ let next = yield* subscription.next();
165
+ while (!next.done) {
166
+ sawData = true;
167
+ next = yield* subscription.next();
168
+ }
169
+ return { sawData };
170
+ });
171
+
172
+ const listening = yield* stdoutReady.operation;
173
+ if (!listening) {
174
+ throw new Error("Expected echo server to log listening.");
175
+ }
176
+ });
177
+
178
+ describe("when it succeeds", () => {
179
+ beforeEach(function* () {
180
+ yield* fetchText("http://localhost:29000", {
181
+ method: "POST",
182
+ body: "exit",
183
+ });
184
+ });
185
+
186
+ it("has a pid", function* () {
187
+ expect(typeof proc.pid).toBe("number");
188
+ expect(proc.pid).not.toBeNaN();
189
+ // make sure it completes
190
+ yield* proc.join();
191
+ });
192
+
193
+ it("joins successfully", function* () {
194
+ let status = yield* proc.join();
195
+ expect(status.code).toEqual(0);
196
+ });
197
+
198
+ it("expects successfully", function* () {
199
+ let status = yield* proc.expect();
200
+ expect(status.code).toEqual(0);
201
+ });
202
+
203
+ it("closes stdout and stderr", function* () {
204
+ yield* proc.expect();
205
+
206
+ const stdoutResult = yield* stdoutTask;
207
+ const stderrResult = yield* stderrTask;
208
+
209
+ expect(stdoutResult.matched).toEqual(true);
210
+ expect(stdoutResult.sawData).toEqual(true);
211
+ expect(stderrResult.sawData).toEqual(true);
212
+ });
213
+ });
214
+
215
+ describe("when it fails", () => {
216
+ let error: Error;
217
+ beforeEach(function* () {
218
+ yield* fetchText("http://localhost:29000", {
219
+ method: "POST",
220
+ body: "fail",
221
+ });
222
+ });
223
+
224
+ it("joins successfully", function* () {
225
+ let status = yield* proc.join();
226
+ expect(status.code).not.toEqual(0);
227
+ });
228
+
229
+ it("expects unsuccessfully", function* () {
230
+ try {
231
+ yield* proc.expect();
232
+ } catch (e) {
233
+ error = e as Error;
234
+ }
235
+ expect(error).toBeDefined();
236
+
237
+ const stdoutResult = yield* stdoutTask;
238
+ expect(stdoutResult.matched).toEqual(true);
239
+ expect(stdoutResult.sawData).toEqual(true);
240
+ });
241
+
242
+ it("closes stdout and stderr", function* () {
243
+ const stdoutResult = yield* stdoutTask;
244
+ const stderrResult = yield* stderrTask;
245
+
246
+ expect(stdoutResult.matched).toEqual(true);
247
+ expect(stdoutResult.sawData).toEqual(true);
248
+ expect(stderrResult.sawData).toEqual(true);
249
+ });
250
+ });
251
+ });
252
+ });
253
+
254
+ // running shell scripts in windows is not well supported, our windows
255
+ // process stuff sets shell to `false` and so you probably shouldn't do this
256
+ // in windows at all.
257
+ if (process.platform !== "win32") {
258
+ describe("when the `shell` option is true", () => {
259
+ it("lets the shell do all of the shellword parsing", function* () {
260
+ let proc = exec('echo "first" | echo "second"', {
261
+ shell: true,
262
+ });
263
+ let { stdout }: ProcessResult = yield* proc.expect();
264
+
265
+ expect(stdout).toEqual("second\n");
266
+ });
267
+ });
268
+ }
269
+
270
+ describe("when the `shell` option is `false`", () => {
271
+ it("correctly receives literal arguments when shell: false", function* () {
272
+ // Arguments are passed literally as parsed by shellwords-ts
273
+ let proc = exec("node ./fixtures/dump-args.js first | echo second", {
274
+ shell: false,
275
+ cwd: import.meta.dirname,
276
+ });
277
+ let { stdout }: ProcessResult = yield* proc.expect();
278
+
279
+ // Node's console.log uses a single LF (\n) line ending.
280
+ const expected = `${JSON.stringify({
281
+ args: ["first", "|", "echo", "second"], // Arguments received by Node
282
+ envVar: undefined,
283
+ })}\n`;
284
+
285
+ expect(stdout).toEqual(expected);
286
+ });
287
+
288
+ it("verifies environment variable handling and literal argument passing", function* () {
289
+ // Execute the custom script with the literal argument
290
+ let proc = exec("node ./fixtures/dump-args.js $EFFECTION_TEST_ENV_VAL", {
291
+ shell: false, // Ensures the argument is passed literally
292
+ env: {
293
+ EFFECTION_TEST_ENV_VAL: "boop",
294
+ PATH: process.env.PATH as string,
295
+ },
296
+ cwd: import.meta.dirname,
297
+ });
298
+ let { stdout, code }: ProcessResult = yield* proc.expect();
299
+
300
+ // The argument is passed literally, and the env var is available in the child process's env.
301
+ const expected = `${JSON.stringify({
302
+ args: ["$EFFECTION_TEST_ENV_VAL"], // Argument is passed literally
303
+ envVar: "boop", // Env variable is read from process.env
304
+ })}\n`;
305
+
306
+ expect(stdout).toEqual(expected);
307
+ expect(code).toBe(0);
308
+ });
309
+ });
310
+
311
+ describe("handles env vars", () => {
312
+ describe("when the `shell` option is `bash`", () => {
313
+ let shell = "bash";
314
+
315
+ it("can pass in an environment variable", function* () {
316
+ let proc = exec("node ./fixtures/dump-args.js $EFFECTION_TEST_ENV_VAL", {
317
+ shell,
318
+ env: {
319
+ EFFECTION_TEST_ENV_VAL: "boop",
320
+ PATH: process.env.PATH as string,
321
+ },
322
+ cwd: import.meta.dirname,
323
+ });
324
+ let { stdout, code }: ProcessResult = yield* proc.expect();
325
+
326
+ const expected = `${JSON.stringify({
327
+ args: ["boop"],
328
+ envVar: "boop",
329
+ })}\n`;
330
+
331
+ expect(stdout).toEqual(expected);
332
+ expect(code).toBe(0);
333
+ });
334
+
335
+ it("can pass in an environment variable with curly brace syntax", function* () {
336
+ let proc = exec(
337
+ "node ./fixtures/dump-args.js ${EFFECTION_TEST_ENV_VAL}",
338
+ {
339
+ shell,
340
+ env: {
341
+ EFFECTION_TEST_ENV_VAL: "boop",
342
+ PATH: process.env.PATH as string,
343
+ },
344
+ cwd: import.meta.dirname,
345
+ },
346
+ );
347
+ let { stdout, code }: ProcessResult = yield* proc.expect();
348
+
349
+ const expected = `${JSON.stringify({
350
+ args: ["boop"],
351
+ envVar: "boop",
352
+ })}\n`;
353
+
354
+ expect(stdout).toEqual(expected);
355
+ expect(code).toBe(0);
356
+ });
357
+ });
358
+
359
+ describe("when the `shell` option is `true`", () => {
360
+ let shell = true;
361
+
362
+ it("can pass in an environment variable", function* () {
363
+ let proc = exec("node ./fixtures/dump-args.js $EFFECTION_TEST_ENV_VAL", {
364
+ shell,
365
+ env: {
366
+ EFFECTION_TEST_ENV_VAL: "boop",
367
+ PATH: process.env.PATH as string,
368
+ },
369
+ cwd: import.meta.dirname,
370
+ });
371
+ let { stdout, code }: ProcessResult = yield* proc.expect();
372
+
373
+ // this fails on windows, this shell option doesn't work on windows
374
+ // due to it generally running through cmd.exe which can't handle this syntax
375
+ let expected =
376
+ process.platform !== "win32"
377
+ ? `${JSON.stringify({ args: ["boop"], envVar: "boop" })}\n`
378
+ : // note the additional \r that is added
379
+ `${JSON.stringify({
380
+ args: ["$EFFECTION_TEST_ENV_VAL"],
381
+ envVar: "boop",
382
+ })}\n`;
383
+
384
+ expect(stdout).toEqual(expected);
385
+ expect(code).toBe(0);
386
+ });
387
+
388
+ it("can pass in an environment variable with curly brace syntax", function* () {
389
+ let proc = exec(
390
+ "node ./fixtures/dump-args.js ${EFFECTION_TEST_ENV_VAL}",
391
+ {
392
+ shell,
393
+ env: {
394
+ EFFECTION_TEST_ENV_VAL: "boop",
395
+ PATH: process.env.PATH as string,
396
+ },
397
+ cwd: import.meta.dirname,
398
+ },
399
+ );
400
+ let { stdout, code }: ProcessResult = yield* proc.expect();
401
+
402
+ // this fails on windows, this shell option doesn't work on windows
403
+ // due to it generally running through cmd.exe which can't handle this syntax
404
+ let expected =
405
+ process.platform !== "win32"
406
+ ? `${JSON.stringify({ args: ["boop"], envVar: "boop" })}\n`
407
+ : // note the additional \r that is added
408
+ `${JSON.stringify({
409
+ args: ["${EFFECTION_TEST_ENV_VAL}"],
410
+ envVar: "boop",
411
+ })}\n`;
412
+
413
+ expect(stdout).toEqual(expected);
414
+ expect(code).toBe(0);
415
+ });
416
+ });
417
+
418
+ describe("when the `shell` option is `false`", () => {
419
+ let shell = false;
420
+
421
+ it("can pass in an environment variable", function* () {
422
+ let proc = exec("node ./fixtures/dump-args.js $EFFECTION_TEST_ENV_VAL", {
423
+ shell,
424
+ env: {
425
+ EFFECTION_TEST_ENV_VAL: "boop",
426
+ PATH: process.env.PATH as string,
427
+ },
428
+ cwd: import.meta.dirname,
429
+ });
430
+ let { stdout, code }: ProcessResult = yield* proc.expect();
431
+
432
+ const expected = `${JSON.stringify({
433
+ args: ["$EFFECTION_TEST_ENV_VAL"],
434
+ envVar: "boop",
435
+ })}\n`;
436
+
437
+ expect(stdout).toEqual(expected);
438
+ expect(code).toBe(0);
439
+ });
440
+
441
+ it("can pass in an environment variable with curly brace syntax", function* () {
442
+ let proc = exec(
443
+ "node ./fixtures/dump-args.js ${EFFECTION_TEST_ENV_VAL}",
444
+ {
445
+ shell,
446
+ env: {
447
+ EFFECTION_TEST_ENV_VAL: "boop",
448
+ PATH: process.env.PATH as string,
449
+ },
450
+ cwd: import.meta.dirname,
451
+ },
452
+ );
453
+ let { stdout, code }: ProcessResult = yield* proc.expect();
454
+
455
+ // Platform behavior differences with shell: false:
456
+ // - PowerShell/CMD: Preserves quotes around arguments and keeps curly braces: "${EFFECTION_TEST_ENV_VAL}" + CRLF
457
+ // - Bash (Windows): Normalizes ${VAR} to $VAR during argument processing: $EFFECTION_TEST_ENV_VAL + LF
458
+ // - Bash (Unix): Keeps curly braces intact: ${EFFECTION_TEST_ENV_VAL} + LF
459
+ // Note: Shellwords parsing preserves braces on all platforms, but bash execution normalizes them
460
+ const expected = `${JSON.stringify({
461
+ args: ["${EFFECTION_TEST_ENV_VAL}"],
462
+ envVar: "boop",
463
+ })}\n`;
464
+
465
+ expect(stdout).toEqual(expected);
466
+ expect(code).toBe(0);
467
+ });
468
+ });
469
+
470
+ if (process.platform === "win32" && isBash()) {
471
+ describe("when the `shell` option is `process.env.shell` (Windows bash only)", () => {
472
+ let shell = process.env.shell;
473
+ // This tests Git Bash on Windows where process.env.shell is set to bash.exe
474
+
475
+ it("can pass in an environment variable", function* () {
476
+ let proc = exec(
477
+ "node ./fixtures/dump-args.js $EFFECTION_TEST_ENV_VAL",
478
+ {
479
+ shell,
480
+ env: {
481
+ EFFECTION_TEST_ENV_VAL: "boop",
482
+ PATH: process.env.PATH as string,
483
+ },
484
+ cwd: import.meta.dirname,
485
+ },
486
+ );
487
+ let { stdout, code }: ProcessResult = yield* proc.expect();
488
+
489
+ // Windows bash should resolve environment variables
490
+ const expected = `${JSON.stringify({
491
+ args: ["boop"],
492
+ envVar: "boop",
493
+ })}\n`;
494
+ expect(stdout).toEqual(expected);
495
+ expect(code).toBe(0);
496
+ });
497
+
498
+ it("can pass in an environment variable with curly brace syntax", function* () {
499
+ let proc = exec(
500
+ "node ./fixtures/dump-args.js ${EFFECTION_TEST_ENV_VAL}",
501
+ {
502
+ shell,
503
+ env: {
504
+ EFFECTION_TEST_ENV_VAL: "boop",
505
+ PATH: process.env.PATH as string,
506
+ },
507
+ cwd: import.meta.dirname,
508
+ },
509
+ );
510
+ let { stdout, code }: ProcessResult = yield* proc.expect();
511
+
512
+ // Windows bash should resolve environment variables with curly brace syntax
513
+ const expected = `${JSON.stringify({
514
+ args: ["boop"],
515
+ envVar: "boop",
516
+ })}\n`;
517
+ expect(stdout).toEqual(expected);
518
+ expect(code).toBe(0);
519
+ });
520
+ });
521
+ }
522
+
523
+ // Close the main "handles env vars" describe block
524
+ });
@@ -0,0 +1,13 @@
1
+ import process from "node:process";
2
+
3
+ // This script is executed via Node: 'node dump-args.js arg1 arg2 ...'
4
+ // process.argv[0] is the node executable path
5
+ // process.argv[1] is the path to this script (dump-args.js)
6
+ // process.argv[2] and onwards are the arguments passed from your 'exec' call.
7
+ const args = process.argv.slice(2);
8
+
9
+ // Look up the specific environment variable
10
+ const envVar = process.env.EFFECTION_TEST_ENV_VAL;
11
+
12
+ // Print a stable, predictable output string with a Unix line ending (\n)
13
+ console.log(JSON.stringify({ args, envVar }));
@@ -0,0 +1,49 @@
1
+ import { createServer } from "node:http";
2
+ import process from "node:process";
3
+
4
+ console.log("starting server");
5
+
6
+ const port = Number.parseInt(process.env.PORT || "29000");
7
+
8
+ const server = createServer(async (req, res) => {
9
+ process.stderr.write("got request\n");
10
+
11
+ // Read the entire request body
12
+ const chunks: Buffer[] = [];
13
+ for await (const chunk of req) {
14
+ chunks.push(chunk);
15
+ }
16
+ const command = Buffer.concat(chunks).toString();
17
+
18
+ // Handle the command asynchronously to allow response to be sent first
19
+ setTimeout(() => {
20
+ readCommand(command);
21
+ }, 100);
22
+
23
+ // Echo the request body back
24
+ res.writeHead(200, { "Content-Type": "text/plain" });
25
+ res.end(command);
26
+ });
27
+
28
+ server.listen(port, () => {
29
+ console.log("listening");
30
+ });
31
+
32
+ process.on("message", (message: unknown) => {
33
+ console.log("got message", message);
34
+ if (process.send) {
35
+ process.send(message);
36
+ }
37
+ });
38
+
39
+ function readCommand(command: string) {
40
+ if (command.includes("exit")) {
41
+ console.log("exit(0)");
42
+ process.exit(0);
43
+ } else if (command.includes("fail")) {
44
+ console.log("exit(1)");
45
+ process.exit(1);
46
+ }
47
+ }
48
+
49
+ process.on("exit", () => console.log("exiting..."));
@@ -0,0 +1,5 @@
1
+ const { process } = globalThis;
2
+
3
+ process.stdout.write("hello world\n");
4
+ process.stderr.write("boom\n");
5
+ process.exit(37);
@@ -0,0 +1,5 @@
1
+ const { process } = globalThis;
2
+
3
+ process.stdout.write("hello\n");
4
+ process.stdout.write("world\n");
5
+ process.stderr.write("boom\n");
@@ -0,0 +1,67 @@
1
+ import { type Operation, type Stream, until } from "effection";
2
+
3
+ export function* captureError(op: Operation<unknown>): Operation<Error> {
4
+ try {
5
+ yield* op;
6
+ } catch (error) {
7
+ return error as Error;
8
+ }
9
+ throw new Error("expected operation to throw an error, but it did not!");
10
+ }
11
+
12
+ export function expectStreamNotEmpty(
13
+ stream: Stream<unknown, unknown>,
14
+ ): Operation<void> {
15
+ return {
16
+ *[Symbol.iterator]() {
17
+ const subscription = yield* stream;
18
+ let next = yield* subscription.next();
19
+ if (next.done) {
20
+ throw new Error(
21
+ "Expected the stream to produce at least one value before closing.",
22
+ );
23
+ }
24
+ },
25
+ };
26
+ }
27
+
28
+ export function* fetchText(input: RequestInfo | URL, init?: RequestInit) {
29
+ try {
30
+ const response = yield* until(globalThis.fetch(input, init));
31
+ if (!response.ok) {
32
+ throw new Error(response.statusText);
33
+ }
34
+ return {
35
+ status: response.status,
36
+ text: yield* until(response.text()),
37
+ };
38
+ } catch (e) {
39
+ throw new Error(`FetchError: ${(e as Error).message}`);
40
+ }
41
+ }
42
+
43
+ export function streamClose<TClose>(
44
+ stream: Stream<unknown, TClose>,
45
+ ): () => Operation<TClose> {
46
+ return function* () {
47
+ const subscription = yield* stream;
48
+ let next = yield* subscription.next();
49
+ while (!next.done) {
50
+ next = yield* subscription.next();
51
+ }
52
+ return next.value;
53
+ };
54
+ }
55
+
56
+ export function* expectMatch(pattern: RegExp, stream: Stream<string, unknown>) {
57
+ const subscription = yield* stream;
58
+ let next = yield* subscription.next();
59
+ while (!next.done) {
60
+ if (pattern.test(next.value)) {
61
+ return true;
62
+ }
63
+ next = yield* subscription.next();
64
+ }
65
+
66
+ return false;
67
+ }