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