@effectionx/process 0.5.0 → 0.6.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.
- package/README.md +198 -9
- package/esm/src/exec/posix.d.ts.map +1 -1
- package/esm/src/exec/posix.js +86 -86
- package/esm/src/exec/win32.d.ts.map +1 -1
- package/esm/src/exec/win32.js +90 -91
- package/package.json +2 -2
- package/script/src/exec/posix.d.ts.map +1 -1
- package/script/src/exec/posix.js +85 -85
- package/script/src/exec/win32.d.ts.map +1 -1
- package/script/src/exec/win32.js +89 -90
package/README.md
CHANGED
|
@@ -1,12 +1,201 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Process
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[](https://discord.gg/Ug5nWH8)
|
|
3
|
+
Execute and manage system processes with structured concurrency. A library for
|
|
4
|
+
spawning and controlling child processes in Effection programs.
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
can find detailed information about using effection to manage system process in
|
|
9
|
-
node in the
|
|
10
|
-
[spawning processes guide](https://frontside.com/effection/docs/guides/processes)
|
|
6
|
+
---
|
|
11
7
|
|
|
12
|
-
|
|
8
|
+
This package provides two main functions: `exec()` for running processes with a
|
|
9
|
+
finite lifetime, and `daemon()` for long-running processes like servers.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Stream-based access to stdout and stderr
|
|
14
|
+
- Writable stdin for sending input to processes
|
|
15
|
+
- Proper signal handling and cleanup on both POSIX and Windows
|
|
16
|
+
- Shell mode for complex commands with glob expansion
|
|
17
|
+
- Structured error handling with `join()` and `expect()` methods
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
### Running a Command
|
|
22
|
+
|
|
23
|
+
Use `exec()` to run a command and wait for it to complete:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { main } from "effection";
|
|
27
|
+
import { exec } from "@effectionx/process";
|
|
28
|
+
|
|
29
|
+
await main(function* () {
|
|
30
|
+
// Run a command and get the result
|
|
31
|
+
let result = yield* exec("echo 'Hello World'").join();
|
|
32
|
+
|
|
33
|
+
console.log(result.stdout); // "Hello World\n"
|
|
34
|
+
console.log(result.code); // 0
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Streaming Output
|
|
39
|
+
|
|
40
|
+
Access stdout and stderr as streams for real-time output processing:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { each, main, spawn } from "effection";
|
|
44
|
+
import { exec } from "@effectionx/process";
|
|
45
|
+
|
|
46
|
+
await main(function* () {
|
|
47
|
+
let process = yield* exec("npm install");
|
|
48
|
+
|
|
49
|
+
// Stream stdout in real-time
|
|
50
|
+
yield* spawn(function* () {
|
|
51
|
+
for (let chunk of yield* each(yield* process.stdout)) {
|
|
52
|
+
console.log(chunk);
|
|
53
|
+
yield* each.next();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Wait for the process to complete
|
|
58
|
+
yield* process.expect();
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Sending Input to stdin
|
|
63
|
+
|
|
64
|
+
Write to a process's stdin:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { main } from "effection";
|
|
68
|
+
import { exec } from "@effectionx/process";
|
|
69
|
+
|
|
70
|
+
await main(function* () {
|
|
71
|
+
let process = yield* exec("cat");
|
|
72
|
+
|
|
73
|
+
process.stdin.send("Hello from stdin!\n");
|
|
74
|
+
|
|
75
|
+
let result = yield* process.join();
|
|
76
|
+
console.log(result.stdout); // "Hello from stdin!\n"
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## join() vs expect()
|
|
81
|
+
|
|
82
|
+
Both methods wait for the process to complete and collect stdout/stderr, but
|
|
83
|
+
they differ in error handling:
|
|
84
|
+
|
|
85
|
+
- **`join()`** - Always returns the result, regardless of exit code
|
|
86
|
+
- **`expect()`** - Throws an `ExecError` if the process exits with a non-zero
|
|
87
|
+
code
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { main } from "effection";
|
|
91
|
+
import { exec, ExecError } from "@effectionx/process";
|
|
92
|
+
|
|
93
|
+
await main(function* () {
|
|
94
|
+
// join() returns result even on failure
|
|
95
|
+
let result = yield* exec("exit 1", { shell: true }).join();
|
|
96
|
+
console.log(result.code); // 1
|
|
97
|
+
|
|
98
|
+
// expect() throws on non-zero exit
|
|
99
|
+
try {
|
|
100
|
+
yield* exec("exit 1", { shell: true }).expect();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (error instanceof ExecError) {
|
|
103
|
+
console.log(error.message); // Command failed with exit code 1
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Running Daemons
|
|
110
|
+
|
|
111
|
+
Use `daemon()` for long-running processes like servers. Unlike `exec()`, a
|
|
112
|
+
daemon is expected to run forever - if it exits prematurely, it raises an error:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { main, suspend } from "effection";
|
|
116
|
+
import { daemon } from "@effectionx/process";
|
|
117
|
+
|
|
118
|
+
await main(function* () {
|
|
119
|
+
// Start a web server
|
|
120
|
+
let server = yield* daemon("node server.js");
|
|
121
|
+
|
|
122
|
+
console.log(`Server started with PID: ${server.pid}`);
|
|
123
|
+
|
|
124
|
+
// The server will be automatically terminated when this scope exits
|
|
125
|
+
yield* suspend();
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Options
|
|
130
|
+
|
|
131
|
+
The `exec()` and `daemon()` functions accept an options object:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
interface ExecOptions {
|
|
135
|
+
// Additional arguments to pass to the command
|
|
136
|
+
arguments?: string[];
|
|
137
|
+
|
|
138
|
+
// Environment variables for the process
|
|
139
|
+
env?: Record<string, string>;
|
|
140
|
+
|
|
141
|
+
// Use shell to interpret the command (enables glob expansion, pipes, etc.)
|
|
142
|
+
// Can be true for default shell or a path to a specific shell
|
|
143
|
+
shell?: boolean | string;
|
|
144
|
+
|
|
145
|
+
// Working directory for the process
|
|
146
|
+
cwd?: string;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Examples
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { main } from "effection";
|
|
154
|
+
import { exec } from "@effectionx/process";
|
|
155
|
+
|
|
156
|
+
await main(function* () {
|
|
157
|
+
// Pass arguments
|
|
158
|
+
yield* exec("git", {
|
|
159
|
+
arguments: ["commit", "-m", "Initial commit"],
|
|
160
|
+
}).expect();
|
|
161
|
+
|
|
162
|
+
// Set environment variables
|
|
163
|
+
yield* exec("node app.js", {
|
|
164
|
+
env: { NODE_ENV: "production", PORT: "3000" },
|
|
165
|
+
}).expect();
|
|
166
|
+
|
|
167
|
+
// Use shell mode for complex commands
|
|
168
|
+
yield* exec("ls *.ts | wc -l", {
|
|
169
|
+
shell: true,
|
|
170
|
+
}).expect();
|
|
171
|
+
|
|
172
|
+
// Set working directory
|
|
173
|
+
yield* exec("npm install", {
|
|
174
|
+
cwd: "./packages/my-package",
|
|
175
|
+
}).expect();
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Process Interface
|
|
180
|
+
|
|
181
|
+
The `Process` object returned by `exec()` provides:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
interface Process {
|
|
185
|
+
// Process ID
|
|
186
|
+
readonly pid: number;
|
|
187
|
+
|
|
188
|
+
// Output streams
|
|
189
|
+
stdout: Stream<string>;
|
|
190
|
+
stderr: Stream<string>;
|
|
191
|
+
|
|
192
|
+
// Input stream
|
|
193
|
+
stdin: Writable<string>;
|
|
194
|
+
|
|
195
|
+
// Wait for completion (returns exit status)
|
|
196
|
+
join(): Operation<ExitStatus>;
|
|
197
|
+
|
|
198
|
+
// Wait for successful completion (throws on non-zero exit)
|
|
199
|
+
expect(): Operation<ExitStatus>;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posix.d.ts","sourceRoot":"","sources":["../../../src/src/exec/posix.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"posix.d.ts","sourceRoot":"","sources":["../../../src/src/exec/posix.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,UAAU,CAAC;AAKtE,eAAO,MAAM,kBAAkB,EAAE,eAkHhC,CAAC"}
|
package/esm/src/exec/posix.js
CHANGED
|
@@ -1,69 +1,97 @@
|
|
|
1
1
|
import { spawn as spawnProcess } from "node:child_process";
|
|
2
|
-
import { all, createSignal, Err, Ok, spawn, withResolvers, } from "effection";
|
|
2
|
+
import { all, createSignal, Err, Ok, resource, spawn, withResolvers, } from "effection";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { once } from "../eventemitter.js";
|
|
5
5
|
import { useReadable } from "../helpers.js";
|
|
6
6
|
import { ExecError } from "./error.js";
|
|
7
|
-
export const createPosixProcess =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
7
|
+
export const createPosixProcess = (command, options) => {
|
|
8
|
+
return resource(function* (provide) {
|
|
9
|
+
let processResult = withResolvers();
|
|
10
|
+
// Killing all child processes started by this command is surprisingly
|
|
11
|
+
// tricky. If a process spawns another processes and we kill the parent,
|
|
12
|
+
// then the child process is NOT automatically killed. Instead we're using
|
|
13
|
+
// the `detached` option to force the child into its own process group,
|
|
14
|
+
// which all of its children in turn will inherit. By sending the signal to
|
|
15
|
+
// `-pid` rather than `pid`, we are sending it to the entire process group
|
|
16
|
+
// instead. This will send the signal to all processes started by the child
|
|
17
|
+
// process.
|
|
18
|
+
//
|
|
19
|
+
// More information here: https://unix.stackexchange.com/questions/14815/process-descendants
|
|
20
|
+
let childProcess = spawnProcess(command, options.arguments || [], {
|
|
21
|
+
detached: true,
|
|
22
|
+
shell: options.shell,
|
|
23
|
+
env: options.env,
|
|
24
|
+
cwd: options.cwd,
|
|
25
|
+
stdio: "pipe",
|
|
26
|
+
});
|
|
27
|
+
let { pid } = childProcess;
|
|
28
|
+
let io = {
|
|
29
|
+
stdout: yield* useReadable(childProcess.stdout),
|
|
30
|
+
stderr: yield* useReadable(childProcess.stderr),
|
|
31
|
+
stdoutDone: withResolvers(),
|
|
32
|
+
stderrDone: withResolvers(),
|
|
33
|
+
};
|
|
34
|
+
let stdout = createSignal();
|
|
35
|
+
let stderr = createSignal();
|
|
36
|
+
yield* spawn(function* () {
|
|
37
|
+
let next = yield* io.stdout.next();
|
|
38
|
+
while (!next.done) {
|
|
39
|
+
stdout.send(next.value);
|
|
40
|
+
next = yield* io.stdout.next();
|
|
41
|
+
}
|
|
42
|
+
stdout.close();
|
|
43
|
+
io.stdoutDone.resolve();
|
|
44
|
+
});
|
|
45
|
+
yield* spawn(function* () {
|
|
46
|
+
let next = yield* io.stderr.next();
|
|
47
|
+
while (!next.done) {
|
|
48
|
+
stderr.send(next.value);
|
|
49
|
+
next = yield* io.stderr.next();
|
|
50
|
+
}
|
|
51
|
+
stderr.close();
|
|
52
|
+
io.stderrDone.resolve();
|
|
53
|
+
});
|
|
54
|
+
let stdin = {
|
|
55
|
+
send(data) {
|
|
56
|
+
childProcess.stdin.write(data);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
yield* spawn(function* trapError() {
|
|
60
|
+
let [error] = yield* once(childProcess, "error");
|
|
61
|
+
processResult.resolve(Err(error));
|
|
62
|
+
});
|
|
63
|
+
yield* spawn(function* () {
|
|
64
|
+
let value = yield* once(childProcess, "close");
|
|
65
|
+
processResult.resolve(Ok(value));
|
|
66
|
+
});
|
|
67
|
+
function* join() {
|
|
68
|
+
let result = yield* processResult.operation;
|
|
69
|
+
if (result.ok) {
|
|
70
|
+
let [code, signal] = result.value;
|
|
71
|
+
return { command, options, code, signal };
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
throw result.error;
|
|
75
|
+
}
|
|
40
76
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
77
|
+
function* expect() {
|
|
78
|
+
let status = yield* join();
|
|
79
|
+
if (status.code != 0) {
|
|
80
|
+
throw new ExecError(status, command, options);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
return status;
|
|
84
|
+
}
|
|
49
85
|
}
|
|
50
|
-
stderr.close();
|
|
51
|
-
io.stderrDone.resolve();
|
|
52
|
-
});
|
|
53
|
-
yield* spawn(function* trapError() {
|
|
54
|
-
let [error] = yield* once(childProcess, "error");
|
|
55
|
-
processResult.resolve(Err(error));
|
|
56
|
-
});
|
|
57
|
-
let stdin = {
|
|
58
|
-
send(data) {
|
|
59
|
-
childProcess.stdin.write(data);
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
yield* spawn(function* () {
|
|
63
86
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
87
|
+
yield* provide({
|
|
88
|
+
pid: pid,
|
|
89
|
+
stdin,
|
|
90
|
+
stdout,
|
|
91
|
+
stderr,
|
|
92
|
+
join,
|
|
93
|
+
expect,
|
|
94
|
+
});
|
|
67
95
|
}
|
|
68
96
|
finally {
|
|
69
97
|
try {
|
|
@@ -79,32 +107,4 @@ export const createPosixProcess = function* createPosixProcess(command, options)
|
|
|
79
107
|
}
|
|
80
108
|
}
|
|
81
109
|
});
|
|
82
|
-
function* join() {
|
|
83
|
-
let result = yield* processResult.operation;
|
|
84
|
-
if (result.ok) {
|
|
85
|
-
let [code, signal] = result.value;
|
|
86
|
-
return { command, options, code, signal };
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
throw result.error;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
function* expect() {
|
|
93
|
-
let status = yield* join();
|
|
94
|
-
if (status.code != 0) {
|
|
95
|
-
throw new ExecError(status, command, options);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
return status;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// FYI: this function starts a process and returns without blocking
|
|
102
|
-
return {
|
|
103
|
-
pid: pid,
|
|
104
|
-
stdin,
|
|
105
|
-
stdout,
|
|
106
|
-
stderr,
|
|
107
|
-
join,
|
|
108
|
-
expect,
|
|
109
|
-
};
|
|
110
110
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"win32.d.ts","sourceRoot":"","sources":["../../../src/src/exec/win32.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"win32.d.ts","sourceRoot":"","sources":["../../../src/src/exec/win32.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,UAAU,CAAC;AAkBtE,eAAO,MAAM,kBAAkB,EAAE,eA4KhC,CAAC;AAEF,eAAO,MAAM,OAAO,QAAO,OAAiC,CAAC"}
|
package/esm/src/exec/win32.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { platform } from "node:os";
|
|
2
|
-
import { all, createSignal, Err, Ok,
|
|
2
|
+
import { all, createSignal, Err, Ok, resource, spawn, withResolvers, } from "effection";
|
|
3
3
|
// @ts-types="npm:@types/cross-spawn@6.0.6"
|
|
4
4
|
import { spawn as spawnProcess } from "cross-spawn";
|
|
5
5
|
import { ctrlc } from "ctrlc-windows";
|
|
@@ -15,69 +15,98 @@ function* killTree(pid) {
|
|
|
15
15
|
// best-effort; ignore errors
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
export const createWin32Process =
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
18
|
+
export const createWin32Process = (command, options) => {
|
|
19
|
+
return resource(function* (provide) {
|
|
20
|
+
let processResult = withResolvers();
|
|
21
|
+
// Windows-specific process spawning with different options than POSIX
|
|
22
|
+
let childProcess = spawnProcess(command, options.arguments || [], {
|
|
23
|
+
// We lose exit information and events if this is detached in windows
|
|
24
|
+
// and it opens a window in windows+powershell.
|
|
25
|
+
detached: false,
|
|
26
|
+
// The `shell` option is passed to `cross-spawn` to control whether a shell is used.
|
|
27
|
+
// On Windows, `shell: true` is necessary to run command strings, as it uses
|
|
28
|
+
// `cmd.exe` to parse the command and find executables in the PATH.
|
|
29
|
+
// Using a boolean `true` was previously disabled, causing ENOENT errors for
|
|
30
|
+
// commands that were not a direct path to an executable.
|
|
31
|
+
shell: options.shell || false,
|
|
32
|
+
// With stdio as pipe, windows gets stuck where neither the child nor the
|
|
33
|
+
// parent wants to close the stream, so we call it ourselves in the exit event.
|
|
34
|
+
stdio: "pipe",
|
|
35
|
+
// Hide the child window so that killing it will not block the parent
|
|
36
|
+
// with a Terminate Batch Process (Y/n)
|
|
37
|
+
windowsHide: true,
|
|
38
|
+
env: options.env,
|
|
39
|
+
cwd: options.cwd,
|
|
40
|
+
});
|
|
41
|
+
let { pid } = childProcess;
|
|
42
|
+
let io = {
|
|
43
|
+
stdout: yield* useReadable(childProcess.stdout),
|
|
44
|
+
stderr: yield* useReadable(childProcess.stderr),
|
|
45
|
+
stdoutDone: withResolvers(),
|
|
46
|
+
stderrDone: withResolvers(),
|
|
47
|
+
};
|
|
48
|
+
const stdout = createSignal();
|
|
49
|
+
const stderr = createSignal();
|
|
50
|
+
yield* spawn(function* () {
|
|
51
|
+
let next = yield* io.stdout.next();
|
|
52
|
+
while (!next.done) {
|
|
53
|
+
stdout.send(next.value);
|
|
54
|
+
next = yield* io.stdout.next();
|
|
55
|
+
}
|
|
56
|
+
stdout.close();
|
|
57
|
+
io.stdoutDone.resolve();
|
|
58
|
+
});
|
|
59
|
+
yield* spawn(function* () {
|
|
60
|
+
let next = yield* io.stderr.next();
|
|
61
|
+
while (!next.done) {
|
|
62
|
+
stderr.send(next.value);
|
|
63
|
+
next = yield* io.stderr.next();
|
|
64
|
+
}
|
|
65
|
+
stderr.close();
|
|
66
|
+
io.stderrDone.resolve();
|
|
67
|
+
});
|
|
68
|
+
let stdin = {
|
|
69
|
+
send(data) {
|
|
70
|
+
childProcess.stdin.write(data);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
yield* spawn(function* trapError() {
|
|
74
|
+
const [error] = yield* once(childProcess, "error");
|
|
75
|
+
processResult.resolve(Err(error));
|
|
76
|
+
});
|
|
77
|
+
yield* spawn(function* () {
|
|
78
78
|
let value = yield* once(childProcess, "close");
|
|
79
79
|
yield* all([io.stdoutDone.operation, io.stderrDone.operation]);
|
|
80
80
|
processResult.resolve(Ok(value));
|
|
81
|
+
});
|
|
82
|
+
function* join() {
|
|
83
|
+
let result = yield* processResult.operation;
|
|
84
|
+
if (result.ok) {
|
|
85
|
+
let [code, signal] = result.value;
|
|
86
|
+
return { command, options, code, signal };
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
throw result.error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function* expect() {
|
|
93
|
+
let status = yield* join();
|
|
94
|
+
if (status.code != 0) {
|
|
95
|
+
throw new ExecError(status, command, options);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
return status;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
yield* provide({
|
|
103
|
+
pid: pid,
|
|
104
|
+
stdin,
|
|
105
|
+
stdout,
|
|
106
|
+
stderr,
|
|
107
|
+
join,
|
|
108
|
+
expect,
|
|
109
|
+
});
|
|
81
110
|
}
|
|
82
111
|
finally {
|
|
83
112
|
try {
|
|
@@ -112,8 +141,6 @@ export const createWin32Process = function* createWin32Process(command, options)
|
|
|
112
141
|
catch (_err) {
|
|
113
142
|
// stdin might already be closed
|
|
114
143
|
}
|
|
115
|
-
// Wait for graceful exit with a timeout
|
|
116
|
-
yield* race([processResult.operation, sleep(300)]);
|
|
117
144
|
// If process still hasn't exited, escalate
|
|
118
145
|
if (childProcess.exitCode === null &&
|
|
119
146
|
childProcess.signalCode === null) {
|
|
@@ -141,33 +168,5 @@ export const createWin32Process = function* createWin32Process(command, options)
|
|
|
141
168
|
}
|
|
142
169
|
}
|
|
143
170
|
});
|
|
144
|
-
function* join() {
|
|
145
|
-
let result = yield* processResult.operation;
|
|
146
|
-
if (result.ok) {
|
|
147
|
-
let [code, signal] = result.value;
|
|
148
|
-
return { command, options, code, signal };
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
throw result.error;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
function* expect() {
|
|
155
|
-
let status = yield* join();
|
|
156
|
-
if (status.code != 0) {
|
|
157
|
-
throw new ExecError(status, command, options);
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
return status;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// FYI: this function starts a process and returns without blocking
|
|
164
|
-
return {
|
|
165
|
-
pid: pid,
|
|
166
|
-
stdin,
|
|
167
|
-
stdout,
|
|
168
|
-
stderr,
|
|
169
|
-
join,
|
|
170
|
-
expect,
|
|
171
|
-
};
|
|
172
171
|
};
|
|
173
172
|
export const isWin32 = () => platform() === "win32";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effectionx/process",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"author": "engineering@frontside.com",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@types/cross-spawn": "6.0.6",
|
|
28
28
|
"cross-spawn": "7.0.6",
|
|
29
29
|
"ctrlc-windows": "2.2.0",
|
|
30
|
-
"effection": "^3",
|
|
30
|
+
"effection": "^3 || ^4.0.0-0",
|
|
31
31
|
"shellwords": "^1.1.1"
|
|
32
32
|
},
|
|
33
33
|
"_generatedBy": "dnt@dev"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posix.d.ts","sourceRoot":"","sources":["../../../src/src/exec/posix.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"posix.d.ts","sourceRoot":"","sources":["../../../src/src/exec/posix.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,UAAU,CAAC;AAKtE,eAAO,MAAM,kBAAkB,EAAE,eAkHhC,CAAC"}
|
package/script/src/exec/posix.js
CHANGED
|
@@ -10,66 +10,94 @@ const node_process_1 = __importDefault(require("node:process"));
|
|
|
10
10
|
const eventemitter_js_1 = require("../eventemitter.js");
|
|
11
11
|
const helpers_js_1 = require("../helpers.js");
|
|
12
12
|
const error_js_1 = require("./error.js");
|
|
13
|
-
const createPosixProcess =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
13
|
+
const createPosixProcess = (command, options) => {
|
|
14
|
+
return (0, effection_1.resource)(function* (provide) {
|
|
15
|
+
let processResult = (0, effection_1.withResolvers)();
|
|
16
|
+
// Killing all child processes started by this command is surprisingly
|
|
17
|
+
// tricky. If a process spawns another processes and we kill the parent,
|
|
18
|
+
// then the child process is NOT automatically killed. Instead we're using
|
|
19
|
+
// the `detached` option to force the child into its own process group,
|
|
20
|
+
// which all of its children in turn will inherit. By sending the signal to
|
|
21
|
+
// `-pid` rather than `pid`, we are sending it to the entire process group
|
|
22
|
+
// instead. This will send the signal to all processes started by the child
|
|
23
|
+
// process.
|
|
24
|
+
//
|
|
25
|
+
// More information here: https://unix.stackexchange.com/questions/14815/process-descendants
|
|
26
|
+
let childProcess = (0, node_child_process_1.spawn)(command, options.arguments || [], {
|
|
27
|
+
detached: true,
|
|
28
|
+
shell: options.shell,
|
|
29
|
+
env: options.env,
|
|
30
|
+
cwd: options.cwd,
|
|
31
|
+
stdio: "pipe",
|
|
32
|
+
});
|
|
33
|
+
let { pid } = childProcess;
|
|
34
|
+
let io = {
|
|
35
|
+
stdout: yield* (0, helpers_js_1.useReadable)(childProcess.stdout),
|
|
36
|
+
stderr: yield* (0, helpers_js_1.useReadable)(childProcess.stderr),
|
|
37
|
+
stdoutDone: (0, effection_1.withResolvers)(),
|
|
38
|
+
stderrDone: (0, effection_1.withResolvers)(),
|
|
39
|
+
};
|
|
40
|
+
let stdout = (0, effection_1.createSignal)();
|
|
41
|
+
let stderr = (0, effection_1.createSignal)();
|
|
42
|
+
yield* (0, effection_1.spawn)(function* () {
|
|
43
|
+
let next = yield* io.stdout.next();
|
|
44
|
+
while (!next.done) {
|
|
45
|
+
stdout.send(next.value);
|
|
46
|
+
next = yield* io.stdout.next();
|
|
47
|
+
}
|
|
48
|
+
stdout.close();
|
|
49
|
+
io.stdoutDone.resolve();
|
|
50
|
+
});
|
|
51
|
+
yield* (0, effection_1.spawn)(function* () {
|
|
52
|
+
let next = yield* io.stderr.next();
|
|
53
|
+
while (!next.done) {
|
|
54
|
+
stderr.send(next.value);
|
|
55
|
+
next = yield* io.stderr.next();
|
|
56
|
+
}
|
|
57
|
+
stderr.close();
|
|
58
|
+
io.stderrDone.resolve();
|
|
59
|
+
});
|
|
60
|
+
let stdin = {
|
|
61
|
+
send(data) {
|
|
62
|
+
childProcess.stdin.write(data);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
yield* (0, effection_1.spawn)(function* trapError() {
|
|
66
|
+
let [error] = yield* (0, eventemitter_js_1.once)(childProcess, "error");
|
|
67
|
+
processResult.resolve((0, effection_1.Err)(error));
|
|
68
|
+
});
|
|
69
|
+
yield* (0, effection_1.spawn)(function* () {
|
|
70
|
+
let value = yield* (0, eventemitter_js_1.once)(childProcess, "close");
|
|
71
|
+
processResult.resolve((0, effection_1.Ok)(value));
|
|
72
|
+
});
|
|
73
|
+
function* join() {
|
|
74
|
+
let result = yield* processResult.operation;
|
|
75
|
+
if (result.ok) {
|
|
76
|
+
let [code, signal] = result.value;
|
|
77
|
+
return { command, options, code, signal };
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
throw result.error;
|
|
81
|
+
}
|
|
46
82
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
83
|
+
function* expect() {
|
|
84
|
+
let status = yield* join();
|
|
85
|
+
if (status.code != 0) {
|
|
86
|
+
throw new error_js_1.ExecError(status, command, options);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
return status;
|
|
90
|
+
}
|
|
55
91
|
}
|
|
56
|
-
stderr.close();
|
|
57
|
-
io.stderrDone.resolve();
|
|
58
|
-
});
|
|
59
|
-
yield* (0, effection_1.spawn)(function* trapError() {
|
|
60
|
-
let [error] = yield* (0, eventemitter_js_1.once)(childProcess, "error");
|
|
61
|
-
processResult.resolve((0, effection_1.Err)(error));
|
|
62
|
-
});
|
|
63
|
-
let stdin = {
|
|
64
|
-
send(data) {
|
|
65
|
-
childProcess.stdin.write(data);
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
yield* (0, effection_1.spawn)(function* () {
|
|
69
92
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
93
|
+
yield* provide({
|
|
94
|
+
pid: pid,
|
|
95
|
+
stdin,
|
|
96
|
+
stdout,
|
|
97
|
+
stderr,
|
|
98
|
+
join,
|
|
99
|
+
expect,
|
|
100
|
+
});
|
|
73
101
|
}
|
|
74
102
|
finally {
|
|
75
103
|
try {
|
|
@@ -85,33 +113,5 @@ const createPosixProcess = function* createPosixProcess(command, options) {
|
|
|
85
113
|
}
|
|
86
114
|
}
|
|
87
115
|
});
|
|
88
|
-
function* join() {
|
|
89
|
-
let result = yield* processResult.operation;
|
|
90
|
-
if (result.ok) {
|
|
91
|
-
let [code, signal] = result.value;
|
|
92
|
-
return { command, options, code, signal };
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
throw result.error;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
function* expect() {
|
|
99
|
-
let status = yield* join();
|
|
100
|
-
if (status.code != 0) {
|
|
101
|
-
throw new error_js_1.ExecError(status, command, options);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
return status;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// FYI: this function starts a process and returns without blocking
|
|
108
|
-
return {
|
|
109
|
-
pid: pid,
|
|
110
|
-
stdin,
|
|
111
|
-
stdout,
|
|
112
|
-
stderr,
|
|
113
|
-
join,
|
|
114
|
-
expect,
|
|
115
|
-
};
|
|
116
116
|
};
|
|
117
117
|
exports.createPosixProcess = createPosixProcess;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"win32.d.ts","sourceRoot":"","sources":["../../../src/src/exec/win32.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"win32.d.ts","sourceRoot":"","sources":["../../../src/src/exec/win32.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,UAAU,CAAC;AAkBtE,eAAO,MAAM,kBAAkB,EAAE,eA4KhC,CAAC;AAEF,eAAO,MAAM,OAAO,QAAO,OAAiC,CAAC"}
|
package/script/src/exec/win32.js
CHANGED
|
@@ -18,69 +18,98 @@ function* killTree(pid) {
|
|
|
18
18
|
// best-effort; ignore errors
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
const createWin32Process =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
21
|
+
const createWin32Process = (command, options) => {
|
|
22
|
+
return (0, effection_1.resource)(function* (provide) {
|
|
23
|
+
let processResult = (0, effection_1.withResolvers)();
|
|
24
|
+
// Windows-specific process spawning with different options than POSIX
|
|
25
|
+
let childProcess = (0, cross_spawn_1.spawn)(command, options.arguments || [], {
|
|
26
|
+
// We lose exit information and events if this is detached in windows
|
|
27
|
+
// and it opens a window in windows+powershell.
|
|
28
|
+
detached: false,
|
|
29
|
+
// The `shell` option is passed to `cross-spawn` to control whether a shell is used.
|
|
30
|
+
// On Windows, `shell: true` is necessary to run command strings, as it uses
|
|
31
|
+
// `cmd.exe` to parse the command and find executables in the PATH.
|
|
32
|
+
// Using a boolean `true` was previously disabled, causing ENOENT errors for
|
|
33
|
+
// commands that were not a direct path to an executable.
|
|
34
|
+
shell: options.shell || false,
|
|
35
|
+
// With stdio as pipe, windows gets stuck where neither the child nor the
|
|
36
|
+
// parent wants to close the stream, so we call it ourselves in the exit event.
|
|
37
|
+
stdio: "pipe",
|
|
38
|
+
// Hide the child window so that killing it will not block the parent
|
|
39
|
+
// with a Terminate Batch Process (Y/n)
|
|
40
|
+
windowsHide: true,
|
|
41
|
+
env: options.env,
|
|
42
|
+
cwd: options.cwd,
|
|
43
|
+
});
|
|
44
|
+
let { pid } = childProcess;
|
|
45
|
+
let io = {
|
|
46
|
+
stdout: yield* (0, helpers_js_1.useReadable)(childProcess.stdout),
|
|
47
|
+
stderr: yield* (0, helpers_js_1.useReadable)(childProcess.stderr),
|
|
48
|
+
stdoutDone: (0, effection_1.withResolvers)(),
|
|
49
|
+
stderrDone: (0, effection_1.withResolvers)(),
|
|
50
|
+
};
|
|
51
|
+
const stdout = (0, effection_1.createSignal)();
|
|
52
|
+
const stderr = (0, effection_1.createSignal)();
|
|
53
|
+
yield* (0, effection_1.spawn)(function* () {
|
|
54
|
+
let next = yield* io.stdout.next();
|
|
55
|
+
while (!next.done) {
|
|
56
|
+
stdout.send(next.value);
|
|
57
|
+
next = yield* io.stdout.next();
|
|
58
|
+
}
|
|
59
|
+
stdout.close();
|
|
60
|
+
io.stdoutDone.resolve();
|
|
61
|
+
});
|
|
62
|
+
yield* (0, effection_1.spawn)(function* () {
|
|
63
|
+
let next = yield* io.stderr.next();
|
|
64
|
+
while (!next.done) {
|
|
65
|
+
stderr.send(next.value);
|
|
66
|
+
next = yield* io.stderr.next();
|
|
67
|
+
}
|
|
68
|
+
stderr.close();
|
|
69
|
+
io.stderrDone.resolve();
|
|
70
|
+
});
|
|
71
|
+
let stdin = {
|
|
72
|
+
send(data) {
|
|
73
|
+
childProcess.stdin.write(data);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
yield* (0, effection_1.spawn)(function* trapError() {
|
|
77
|
+
const [error] = yield* (0, eventemitter_js_1.once)(childProcess, "error");
|
|
78
|
+
processResult.resolve((0, effection_1.Err)(error));
|
|
79
|
+
});
|
|
80
|
+
yield* (0, effection_1.spawn)(function* () {
|
|
81
81
|
let value = yield* (0, eventemitter_js_1.once)(childProcess, "close");
|
|
82
82
|
yield* (0, effection_1.all)([io.stdoutDone.operation, io.stderrDone.operation]);
|
|
83
83
|
processResult.resolve((0, effection_1.Ok)(value));
|
|
84
|
+
});
|
|
85
|
+
function* join() {
|
|
86
|
+
let result = yield* processResult.operation;
|
|
87
|
+
if (result.ok) {
|
|
88
|
+
let [code, signal] = result.value;
|
|
89
|
+
return { command, options, code, signal };
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
throw result.error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function* expect() {
|
|
96
|
+
let status = yield* join();
|
|
97
|
+
if (status.code != 0) {
|
|
98
|
+
throw new error_js_1.ExecError(status, command, options);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
return status;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
yield* provide({
|
|
106
|
+
pid: pid,
|
|
107
|
+
stdin,
|
|
108
|
+
stdout,
|
|
109
|
+
stderr,
|
|
110
|
+
join,
|
|
111
|
+
expect,
|
|
112
|
+
});
|
|
84
113
|
}
|
|
85
114
|
finally {
|
|
86
115
|
try {
|
|
@@ -115,8 +144,6 @@ const createWin32Process = function* createWin32Process(command, options) {
|
|
|
115
144
|
catch (_err) {
|
|
116
145
|
// stdin might already be closed
|
|
117
146
|
}
|
|
118
|
-
// Wait for graceful exit with a timeout
|
|
119
|
-
yield* (0, effection_1.race)([processResult.operation, (0, effection_1.sleep)(300)]);
|
|
120
147
|
// If process still hasn't exited, escalate
|
|
121
148
|
if (childProcess.exitCode === null &&
|
|
122
149
|
childProcess.signalCode === null) {
|
|
@@ -144,34 +171,6 @@ const createWin32Process = function* createWin32Process(command, options) {
|
|
|
144
171
|
}
|
|
145
172
|
}
|
|
146
173
|
});
|
|
147
|
-
function* join() {
|
|
148
|
-
let result = yield* processResult.operation;
|
|
149
|
-
if (result.ok) {
|
|
150
|
-
let [code, signal] = result.value;
|
|
151
|
-
return { command, options, code, signal };
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
throw result.error;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
function* expect() {
|
|
158
|
-
let status = yield* join();
|
|
159
|
-
if (status.code != 0) {
|
|
160
|
-
throw new error_js_1.ExecError(status, command, options);
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
return status;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
// FYI: this function starts a process and returns without blocking
|
|
167
|
-
return {
|
|
168
|
-
pid: pid,
|
|
169
|
-
stdin,
|
|
170
|
-
stdout,
|
|
171
|
-
stderr,
|
|
172
|
-
join,
|
|
173
|
-
expect,
|
|
174
|
-
};
|
|
175
174
|
};
|
|
176
175
|
exports.createWin32Process = createWin32Process;
|
|
177
176
|
const isWin32 = () => (0, node_os_1.platform)() === "win32";
|