@effectionx/process 0.7.2 → 0.7.4

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/package.json CHANGED
@@ -1,13 +1,22 @@
1
1
  {
2
2
  "name": "@effectionx/process",
3
3
  "description": "Spawn and manage child processes with structured concurrency",
4
- "version": "0.7.2",
4
+ "version": "0.7.4",
5
+ "keywords": [
6
+ "effection",
7
+ "effectionx",
8
+ "process",
9
+ "spawn",
10
+ "exec",
11
+ "child-process"
12
+ ],
5
13
  "type": "module",
6
14
  "main": "./dist/mod.js",
7
15
  "types": "./dist/mod.d.ts",
8
16
  "exports": {
9
17
  ".": {
10
- "development": "./mod.ts",
18
+ "types": "./dist/mod.d.ts",
19
+ "import": "./dist/mod.js",
11
20
  "default": "./dist/mod.js"
12
21
  }
13
22
  },
@@ -23,19 +32,17 @@
23
32
  "bugs": {
24
33
  "url": "https://github.com/thefrontside/effectionx/issues"
25
34
  },
26
- "engines": {
27
- "node": ">= 22"
28
- },
29
35
  "sideEffects": false,
30
36
  "dependencies": {
31
37
  "cross-spawn": "^7",
32
38
  "ctrlc-windows": "^2",
33
39
  "shellwords-ts": "^3.0.1",
34
- "@effectionx/node": "0.2.1"
40
+ "@effectionx/node": "0.2.4"
35
41
  },
36
42
  "devDependencies": {
37
43
  "@types/cross-spawn": "^6",
38
- "@effectionx/bdd": "0.4.2",
39
- "@effectionx/stream-helpers": "0.7.2"
44
+ "effection": "^4",
45
+ "@effectionx/bdd": "0.5.2",
46
+ "@effectionx/stream-helpers": "0.8.2"
40
47
  }
41
48
  }
@@ -1,6 +1,6 @@
1
1
  import process from "node:process";
2
2
  import { beforeEach, describe, it } from "@effectionx/bdd";
3
- import { type Task, spawn, until, withResolvers } from "effection";
3
+ import { type Task, sleep, spawn, until, withResolvers } from "effection";
4
4
  import { expect } from "expect";
5
5
 
6
6
  import { lines } from "@effectionx/stream-helpers";
@@ -10,14 +10,13 @@ import { captureError, expectMatch, fetchText } from "./helpers.ts";
10
10
  const SystemRoot = process.env.SystemRoot;
11
11
 
12
12
  describe("daemon", () => {
13
- let task: Task<void>;
14
- let proc: Daemon;
15
-
16
13
  describe("controlling from outside", () => {
14
+ let task: Task<void>;
15
+ let proc: Daemon;
17
16
  beforeEach(function* () {
18
17
  const result = withResolvers<Daemon>();
19
18
  task = yield* spawn<void>(function* () {
20
- let proc = yield* daemon("node", {
19
+ proc = yield* daemon("node", {
21
20
  arguments: [
22
21
  "--experimental-strip-types",
23
22
  "./fixtures/echo-server.ts",
@@ -35,7 +34,8 @@ describe("daemon", () => {
35
34
 
36
35
  proc = yield* result.operation;
37
36
 
38
- yield* expectMatch(/listening/, lines()(proc.stdout));
37
+ const listening = yield* expectMatch(/listening/, lines()(proc.stdout));
38
+ expect(listening).toBe(true);
39
39
  });
40
40
 
41
41
  it("starts the given child", function* () {
@@ -89,7 +89,8 @@ describe("daemon", () => {
89
89
  return new Error(`this shouldn't happen`);
90
90
  });
91
91
 
92
- yield* expectMatch(/listening/, lines()(proc.stdout));
92
+ const listening = yield* expectMatch(/listening/, lines()(proc.stdout));
93
+ expect(listening).toBe(true);
93
94
 
94
95
  yield* fetchText("http://localhost:29001", {
95
96
  method: "POST",
@@ -103,4 +104,40 @@ describe("daemon", () => {
103
104
  );
104
105
  });
105
106
  });
107
+
108
+ describe("shutting down an effection-based daemon process prematurely", () => {
109
+ let task: Task<void>;
110
+ let proc: Daemon;
111
+ beforeEach(function* () {
112
+ const ready = withResolvers<void>();
113
+ task = yield* spawn(function* () {
114
+ try {
115
+ proc = yield* daemon("node", {
116
+ arguments: ["--experimental-strip-types", "fixtures/forever.ts"],
117
+ cwd: import.meta.dirname,
118
+ });
119
+ ready.resolve();
120
+ yield* proc;
121
+ } catch (e) {
122
+ // ignore the error from the process exiting
123
+ // we just want to check that the finally block runs
124
+ }
125
+ });
126
+
127
+ yield* ready.operation;
128
+ const suspending = yield* expectMatch(/suspending/, lines()(proc.stdout));
129
+ expect(suspending).toBe(true);
130
+ });
131
+
132
+ it("still executes process finally block on kill", function* () {
133
+ const finallyCheck = yield* spawn(() =>
134
+ expectMatch(/shutting/, lines()(proc.stdout)),
135
+ );
136
+ // ensure that spawn has kicked off
137
+ yield* sleep(0);
138
+ yield* task.halt();
139
+ const completed = yield* finallyCheck;
140
+ expect(completed).toBe(true);
141
+ });
142
+ });
106
143
  });
package/test/exec.test.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import process from "node:process";
2
2
  import { beforeEach, describe, it } from "@effectionx/bdd";
3
- import { type Task, spawn, withResolvers } from "effection";
3
+ import { type Task, sleep, spawn, withResolvers } from "effection";
4
4
  import { expect } from "expect";
5
5
 
6
- import { captureError, fetchText } from "./helpers.ts";
6
+ import { captureError, expectMatch, fetchText } from "./helpers.ts";
7
7
 
8
8
  import { lines } from "@effectionx/stream-helpers";
9
9
  import { type Process, type ProcessResult, exec } from "../mod.ts";
@@ -102,6 +102,7 @@ describe("exec", () => {
102
102
  }
103
103
  expect(error).toBeInstanceOf(Error);
104
104
  });
105
+
105
106
  it("calling expect() throws an exception", function* () {
106
107
  let error: unknown;
107
108
  let proc = yield* exec("argle", { arguments: ["bargle"] });
@@ -114,7 +115,49 @@ describe("exec", () => {
114
115
  expect(error).toBeDefined();
115
116
  });
116
117
  });
118
+
119
+ describe("process can gracefully shut down if killed before completed", () => {
120
+ it("runs process finally block on kill", function* () {
121
+ let status: unknown;
122
+ let proc: Process | undefined;
123
+ let ready = withResolvers<void>();
124
+ let task = yield* spawn(function* () {
125
+ try {
126
+ proc = yield* exec("node", {
127
+ // using fixture that suspends so we can kill before it "completes"
128
+ arguments: ["--experimental-strip-types", "fixtures/forever.ts"],
129
+ cwd: import.meta.dirname,
130
+ });
131
+ ready.resolve();
132
+ status = yield* proc.join();
133
+ } catch (error: unknown) {
134
+ ready.reject(error as Error);
135
+ }
136
+ });
137
+
138
+ yield* ready.operation;
139
+ const suspending = yield* expectMatch(
140
+ /suspending/,
141
+ lines()(proc!.stdout),
142
+ );
143
+ expect(suspending).toBe(true);
144
+
145
+ // kill the process before it finishes and make sure it runs the finally block
146
+ const finallyCheck = yield* spawn(() =>
147
+ expectMatch(/shutting down/, lines()(proc!.stdout)),
148
+ );
149
+ // ensure that spawn has kicked off
150
+ yield* sleep(0);
151
+ yield* task.halt();
152
+
153
+ const completed = yield* finallyCheck;
154
+ expect(completed).toBe(true);
155
+
156
+ expect(status).toBeUndefined();
157
+ });
158
+ });
117
159
  });
160
+
118
161
  describe("successfully", () => {
119
162
  let proc: Process;
120
163
  let stdoutTask: Task<{ sawData: boolean; matched: boolean }>;
@@ -0,0 +1,26 @@
1
+ import { main, suspend, spawn, sleep, type Task } from "effection";
2
+
3
+ await main(function* () {
4
+ try {
5
+ let tasks: Task<string>[] = [];
6
+ for (let i = 1; i <= 5; i++) {
7
+ let task = yield* spawn(function* () {
8
+ yield* sleep(50 * i);
9
+ return `child ${i} done`;
10
+ });
11
+ tasks.push(task);
12
+ }
13
+
14
+ for (let t of tasks) {
15
+ yield* t.halt();
16
+ }
17
+
18
+ console.log("suspending");
19
+
20
+ // never finish; keeps the loader running which keeps the UI server up during tests
21
+ // but also simulates a long-running process that we can attach to
22
+ yield* suspend();
23
+ } finally {
24
+ console.log("shutting down, forever was a fallacy");
25
+ }
26
+ });
package/test/helpers.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Operation, type Stream, until } from "effection";
1
+ import { type Operation, race, sleep, type Stream, until } from "effection";
2
2
 
3
3
  export function* captureError(op: Operation<unknown>): Operation<Error> {
4
4
  try {
@@ -54,14 +54,24 @@ export function streamClose<TClose>(
54
54
  }
55
55
 
56
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;
57
+ const prevLogs: string[] = [];
58
+ return yield* race([
59
+ (function* (): Operation<boolean> {
60
+ yield* sleep(8000);
61
+ console.log(`saw logs: ${prevLogs.join("")}`);
62
+ return false;
63
+ })(),
64
+ (function* (): Operation<boolean> {
65
+ const subscription = yield* stream;
66
+ let next = yield* subscription.next();
67
+ while (!next.done) {
68
+ prevLogs.push(next.value);
69
+ if (pattern.test(next.value)) {
70
+ return true;
71
+ }
72
+ next = yield* subscription.next();
73
+ }
74
+ return false;
75
+ })(),
76
+ ]);
67
77
  }