@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 +15 -8
- package/test/daemon.test.ts +44 -7
- package/test/exec.test.ts +45 -2
- package/test/fixtures/forever.ts +26 -0
- package/test/helpers.ts +21 -11
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.
|
|
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
|
-
"
|
|
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.
|
|
40
|
+
"@effectionx/node": "0.2.4"
|
|
35
41
|
},
|
|
36
42
|
"devDependencies": {
|
|
37
43
|
"@types/cross-spawn": "^6",
|
|
38
|
-
"
|
|
39
|
-
"@effectionx/
|
|
44
|
+
"effection": "^4",
|
|
45
|
+
"@effectionx/bdd": "0.5.2",
|
|
46
|
+
"@effectionx/stream-helpers": "0.8.2"
|
|
40
47
|
}
|
|
41
48
|
}
|
package/test/daemon.test.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
}
|