@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 CHANGED
@@ -1,12 +1,201 @@
1
- # @effection/process
1
+ # Process
2
2
 
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![Created by Frontside](https://img.shields.io/badge/created%20by-frontside-26abe8.svg)](https://frontside.com)
5
- [![Chat on Discord](https://img.shields.io/discord/700803887132704931?Label=Discord)](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
- [Effection][Effection] is the structured concurrency toolkit for JavaScript. You
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
- [Effection]: https://frontside.com/effection
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":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,UAAU,CAAC;AAKtE,eAAO,MAAM,kBAAkB,EAAE,eAkHhC,CAAC"}
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"}
@@ -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 = function* createPosixProcess(command, options) {
8
- let processResult = withResolvers();
9
- // Killing all child processes started by this command is surprisingly
10
- // tricky. If a process spawns another processes and we kill the parent,
11
- // then the child process is NOT automatically killed. Instead we're using
12
- // the `detached` option to force the child into its own process group,
13
- // which all of its children in turn will inherit. By sending the signal to
14
- // `-pid` rather than `pid`, we are sending it to the entire process group
15
- // instead. This will send the signal to all processes started by the child
16
- // process.
17
- //
18
- // More information here: https://unix.stackexchange.com/questions/14815/process-descendants
19
- let childProcess = spawnProcess(command, options.arguments || [], {
20
- detached: true,
21
- shell: options.shell,
22
- env: options.env,
23
- cwd: options.cwd,
24
- stdio: "pipe",
25
- });
26
- let { pid } = childProcess;
27
- let io = {
28
- stdout: yield* useReadable(childProcess.stdout),
29
- stderr: yield* useReadable(childProcess.stderr),
30
- stdoutDone: withResolvers(),
31
- stderrDone: withResolvers(),
32
- };
33
- let stdout = createSignal();
34
- let stderr = createSignal();
35
- yield* spawn(function* () {
36
- let next = yield* io.stdout.next();
37
- while (!next.done) {
38
- stdout.send(next.value);
39
- next = yield* io.stdout.next();
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
- stdout.close();
42
- io.stdoutDone.resolve();
43
- });
44
- yield* spawn(function* () {
45
- let next = yield* io.stderr.next();
46
- while (!next.done) {
47
- stderr.send(next.value);
48
- next = yield* io.stderr.next();
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
- let value = yield* once(childProcess, "close");
65
- yield* all([io.stdoutDone.operation, io.stderrDone.operation]);
66
- processResult.resolve(Ok(value));
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":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,UAAU,CAAC;AAkBtE,eAAO,MAAM,kBAAkB,EAAE,eA8KhC,CAAC;AAEF,eAAO,MAAM,OAAO,QAAO,OAAiC,CAAC"}
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"}
@@ -1,5 +1,5 @@
1
1
  import { platform } from "node:os";
2
- import { all, createSignal, Err, Ok, race, sleep, spawn, withResolvers, } from "effection";
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 = function* createWin32Process(command, options) {
19
- let processResult = withResolvers();
20
- // Windows-specific process spawning with different options than POSIX
21
- let childProcess = spawnProcess(command, options.arguments || [], {
22
- // We lose exit information and events if this is detached in windows
23
- // and it opens a window in windows+powershell.
24
- detached: false,
25
- // The `shell` option is passed to `cross-spawn` to control whether a shell is used.
26
- // On Windows, `shell: true` is necessary to run command strings, as it uses
27
- // `cmd.exe` to parse the command and find executables in the PATH.
28
- // Using a boolean `true` was previously disabled, causing ENOENT errors for
29
- // commands that were not a direct path to an executable.
30
- shell: options.shell || false,
31
- // With stdio as pipe, windows gets stuck where neither the child nor the
32
- // parent wants to close the stream, so we call it ourselves in the exit event.
33
- stdio: "pipe",
34
- // Hide the child window so that killing it will not block the parent
35
- // with a Terminate Batch Process (Y/n)
36
- windowsHide: true,
37
- env: options.env,
38
- cwd: options.cwd,
39
- });
40
- let { pid } = childProcess;
41
- let io = {
42
- stdout: yield* useReadable(childProcess.stdout),
43
- stderr: yield* useReadable(childProcess.stderr),
44
- stdoutDone: withResolvers(),
45
- stderrDone: withResolvers(),
46
- };
47
- const stdout = createSignal();
48
- const stderr = createSignal();
49
- yield* spawn(function* () {
50
- let next = yield* io.stdout.next();
51
- while (!next.done) {
52
- stdout.send(next.value);
53
- next = yield* io.stdout.next();
54
- }
55
- stdout.close();
56
- io.stdoutDone.resolve();
57
- });
58
- yield* spawn(function* () {
59
- let next = yield* io.stderr.next();
60
- while (!next.done) {
61
- stderr.send(next.value);
62
- next = yield* io.stderr.next();
63
- }
64
- stderr.close();
65
- io.stderrDone.resolve();
66
- });
67
- yield* spawn(function* trapError() {
68
- const [error] = yield* once(childProcess, "error");
69
- processResult.resolve(Err(error));
70
- });
71
- let stdin = {
72
- send(data) {
73
- childProcess.stdin.write(data);
74
- },
75
- };
76
- yield* spawn(function* () {
77
- try {
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.5.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":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,UAAU,CAAC;AAKtE,eAAO,MAAM,kBAAkB,EAAE,eAkHhC,CAAC"}
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"}
@@ -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 = function* createPosixProcess(command, options) {
14
- let processResult = (0, effection_1.withResolvers)();
15
- // Killing all child processes started by this command is surprisingly
16
- // tricky. If a process spawns another processes and we kill the parent,
17
- // then the child process is NOT automatically killed. Instead we're using
18
- // the `detached` option to force the child into its own process group,
19
- // which all of its children in turn will inherit. By sending the signal to
20
- // `-pid` rather than `pid`, we are sending it to the entire process group
21
- // instead. This will send the signal to all processes started by the child
22
- // process.
23
- //
24
- // More information here: https://unix.stackexchange.com/questions/14815/process-descendants
25
- let childProcess = (0, node_child_process_1.spawn)(command, options.arguments || [], {
26
- detached: true,
27
- shell: options.shell,
28
- env: options.env,
29
- cwd: options.cwd,
30
- stdio: "pipe",
31
- });
32
- let { pid } = childProcess;
33
- let io = {
34
- stdout: yield* (0, helpers_js_1.useReadable)(childProcess.stdout),
35
- stderr: yield* (0, helpers_js_1.useReadable)(childProcess.stderr),
36
- stdoutDone: (0, effection_1.withResolvers)(),
37
- stderrDone: (0, effection_1.withResolvers)(),
38
- };
39
- let stdout = (0, effection_1.createSignal)();
40
- let stderr = (0, effection_1.createSignal)();
41
- yield* (0, effection_1.spawn)(function* () {
42
- let next = yield* io.stdout.next();
43
- while (!next.done) {
44
- stdout.send(next.value);
45
- next = yield* io.stdout.next();
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
- stdout.close();
48
- io.stdoutDone.resolve();
49
- });
50
- yield* (0, effection_1.spawn)(function* () {
51
- let next = yield* io.stderr.next();
52
- while (!next.done) {
53
- stderr.send(next.value);
54
- next = yield* io.stderr.next();
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
- let value = yield* (0, eventemitter_js_1.once)(childProcess, "close");
71
- yield* (0, effection_1.all)([io.stdoutDone.operation, io.stderrDone.operation]);
72
- processResult.resolve((0, effection_1.Ok)(value));
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":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,UAAU,CAAC;AAkBtE,eAAO,MAAM,kBAAkB,EAAE,eA8KhC,CAAC;AAEF,eAAO,MAAM,OAAO,QAAO,OAAiC,CAAC"}
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"}
@@ -18,69 +18,98 @@ function* killTree(pid) {
18
18
  // best-effort; ignore errors
19
19
  }
20
20
  }
21
- const createWin32Process = function* createWin32Process(command, options) {
22
- let processResult = (0, effection_1.withResolvers)();
23
- // Windows-specific process spawning with different options than POSIX
24
- let childProcess = (0, cross_spawn_1.spawn)(command, options.arguments || [], {
25
- // We lose exit information and events if this is detached in windows
26
- // and it opens a window in windows+powershell.
27
- detached: false,
28
- // The `shell` option is passed to `cross-spawn` to control whether a shell is used.
29
- // On Windows, `shell: true` is necessary to run command strings, as it uses
30
- // `cmd.exe` to parse the command and find executables in the PATH.
31
- // Using a boolean `true` was previously disabled, causing ENOENT errors for
32
- // commands that were not a direct path to an executable.
33
- shell: options.shell || false,
34
- // With stdio as pipe, windows gets stuck where neither the child nor the
35
- // parent wants to close the stream, so we call it ourselves in the exit event.
36
- stdio: "pipe",
37
- // Hide the child window so that killing it will not block the parent
38
- // with a Terminate Batch Process (Y/n)
39
- windowsHide: true,
40
- env: options.env,
41
- cwd: options.cwd,
42
- });
43
- let { pid } = childProcess;
44
- let io = {
45
- stdout: yield* (0, helpers_js_1.useReadable)(childProcess.stdout),
46
- stderr: yield* (0, helpers_js_1.useReadable)(childProcess.stderr),
47
- stdoutDone: (0, effection_1.withResolvers)(),
48
- stderrDone: (0, effection_1.withResolvers)(),
49
- };
50
- const stdout = (0, effection_1.createSignal)();
51
- const stderr = (0, effection_1.createSignal)();
52
- yield* (0, effection_1.spawn)(function* () {
53
- let next = yield* io.stdout.next();
54
- while (!next.done) {
55
- stdout.send(next.value);
56
- next = yield* io.stdout.next();
57
- }
58
- stdout.close();
59
- io.stdoutDone.resolve();
60
- });
61
- yield* (0, effection_1.spawn)(function* () {
62
- let next = yield* io.stderr.next();
63
- while (!next.done) {
64
- stderr.send(next.value);
65
- next = yield* io.stderr.next();
66
- }
67
- stderr.close();
68
- io.stderrDone.resolve();
69
- });
70
- yield* (0, effection_1.spawn)(function* trapError() {
71
- const [error] = yield* (0, eventemitter_js_1.once)(childProcess, "error");
72
- processResult.resolve((0, effection_1.Err)(error));
73
- });
74
- let stdin = {
75
- send(data) {
76
- childProcess.stdin.write(data);
77
- },
78
- };
79
- yield* (0, effection_1.spawn)(function* () {
80
- try {
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";