@effectionx/process 0.5.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.
- package/README.md +12 -0
- package/esm/mod.d.ts +3 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +2 -0
- package/esm/package.json +3 -0
- package/esm/src/daemon.d.ts +11 -0
- package/esm/src/daemon.d.ts.map +1 -0
- package/esm/src/daemon.js +20 -0
- package/esm/src/eventemitter.d.ts +22 -0
- package/esm/src/eventemitter.d.ts.map +1 -0
- package/esm/src/eventemitter.js +40 -0
- package/esm/src/exec/api.d.ts +70 -0
- package/esm/src/exec/api.d.ts.map +1 -0
- package/esm/src/exec/api.js +1 -0
- package/esm/src/exec/error.d.ts +14 -0
- package/esm/src/exec/error.d.ts.map +1 -0
- package/esm/src/exec/error.js +54 -0
- package/esm/src/exec/posix.d.ts +3 -0
- package/esm/src/exec/posix.d.ts.map +1 -0
- package/esm/src/exec/posix.js +110 -0
- package/esm/src/exec/win32.d.ts +4 -0
- package/esm/src/exec/win32.d.ts.map +1 -0
- package/esm/src/exec/win32.js +173 -0
- package/esm/src/exec.d.ts +16 -0
- package/esm/src/exec.d.ts.map +1 -0
- package/esm/src/exec.js +75 -0
- package/esm/src/helpers.d.ts +12 -0
- package/esm/src/helpers.d.ts.map +1 -0
- package/esm/src/helpers.js +71 -0
- package/package.json +34 -0
- package/script/mod.d.ts +3 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +20 -0
- package/script/package.json +3 -0
- package/script/src/daemon.d.ts +11 -0
- package/script/src/daemon.d.ts.map +1 -0
- package/script/src/daemon.js +23 -0
- package/script/src/eventemitter.d.ts +22 -0
- package/script/src/eventemitter.d.ts.map +1 -0
- package/script/src/eventemitter.js +44 -0
- package/script/src/exec/api.d.ts +70 -0
- package/script/src/exec/api.d.ts.map +1 -0
- package/script/src/exec/api.js +2 -0
- package/script/src/exec/error.d.ts +14 -0
- package/script/src/exec/error.d.ts.map +1 -0
- package/script/src/exec/error.js +59 -0
- package/script/src/exec/posix.d.ts +3 -0
- package/script/src/exec/posix.d.ts.map +1 -0
- package/script/src/exec/posix.js +117 -0
- package/script/src/exec/win32.d.ts +4 -0
- package/script/src/exec/win32.d.ts.map +1 -0
- package/script/src/exec/win32.js +178 -0
- package/script/src/exec.d.ts +16 -0
- package/script/src/exec.d.ts.map +1 -0
- package/script/src/exec.js +92 -0
- package/script/src/helpers.d.ts +12 -0
- package/script/src/helpers.d.ts.map +1 -0
- package/script/src/helpers.js +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @effection/process
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://frontside.com)
|
|
5
|
+
[](https://discord.gg/Ug5nWH8)
|
|
6
|
+
|
|
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)
|
|
11
|
+
|
|
12
|
+
[Effection]: https://frontside.com/effection
|
package/esm/mod.d.ts
ADDED
package/esm/mod.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC"}
|
package/esm/mod.js
ADDED
package/esm/package.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Operation } from "effection";
|
|
2
|
+
import { type ExecOptions, type Process } from "./exec.js";
|
|
3
|
+
export interface Daemon extends Operation<void>, Process {
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Start a long-running process, like a web server that run perpetually.
|
|
7
|
+
* Daemon operations are expected to run forever, and if they exit pre-maturely
|
|
8
|
+
* before the operation containing them passes out of scope it raises an error.
|
|
9
|
+
*/
|
|
10
|
+
export declare function daemon(command: string, options?: ExecOptions): Operation<Daemon>;
|
|
11
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/src/daemon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,WAAW,CAAC;AAErD,OAAO,EAGL,KAAK,WAAW,EAEhB,KAAK,OAAO,EACb,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,MAAO,SAAQ,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO;CACvD;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CACpB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,WAAgB,GACxB,SAAS,CAAC,MAAM,CAAC,CAanB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { resource } from "effection";
|
|
2
|
+
import { DaemonExitError, exec, } from "./exec.js";
|
|
3
|
+
/**
|
|
4
|
+
* Start a long-running process, like a web server that run perpetually.
|
|
5
|
+
* Daemon operations are expected to run forever, and if they exit pre-maturely
|
|
6
|
+
* before the operation containing them passes out of scope it raises an error.
|
|
7
|
+
*/
|
|
8
|
+
export function daemon(command, options = {}) {
|
|
9
|
+
return resource(function* (provide) {
|
|
10
|
+
// TODO: should we be able to terminate the process from here?
|
|
11
|
+
let process = yield* exec(command, options);
|
|
12
|
+
yield* provide({
|
|
13
|
+
*[Symbol.iterator]() {
|
|
14
|
+
let status = yield* process.join();
|
|
15
|
+
throw new DaemonExitError(status, command, options);
|
|
16
|
+
},
|
|
17
|
+
...process,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Operation, Stream } from "effection";
|
|
2
|
+
import type { EventEmitter } from "node:stream";
|
|
3
|
+
/**
|
|
4
|
+
* Create a {@link Stream} of events from any EventEmitter.
|
|
5
|
+
*
|
|
6
|
+
* See the guide on [Streams and Subscriptions](https://frontside.com/effection/docs/collections)
|
|
7
|
+
* for details on how to use streams.
|
|
8
|
+
*
|
|
9
|
+
* @param target - the event target whose events will be streamed
|
|
10
|
+
* @param name - the name of the event to stream. E.g. "click"
|
|
11
|
+
* @returns a stream that will see one item for each event
|
|
12
|
+
*/
|
|
13
|
+
export declare function on<T extends unknown[]>(target: EventEmitter | null, eventName: string): Stream<T, never>;
|
|
14
|
+
/**
|
|
15
|
+
* Create an {@link Operation} that yields the next event to be emitted by an EventEmitter.
|
|
16
|
+
*
|
|
17
|
+
* @param target - the event target to be watched
|
|
18
|
+
* @param name - the name of the event to watch. E.g. "click"
|
|
19
|
+
* @returns an Operation that yields the next emitted event
|
|
20
|
+
*/
|
|
21
|
+
export declare function once<TArgs extends unknown[] = unknown[]>(source: EventEmitter | null, eventName: string): Operation<TArgs>;
|
|
22
|
+
//# sourceMappingURL=eventemitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventemitter.d.ts","sourceRoot":"","sources":["../../src/src/eventemitter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAgB,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,EAAE,CAChB,CAAC,SAAS,OAAO,EAAE,EACnB,MAAM,EAAE,YAAY,GAAG,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAkBlE;AAED;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,KAAK,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,EACtD,MAAM,EAAE,YAAY,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAChB,SAAS,CAAC,KAAK,CAAC,CAWlB"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createSignal, resource, withResolvers } from "effection";
|
|
2
|
+
/**
|
|
3
|
+
* Create a {@link Stream} of events from any EventEmitter.
|
|
4
|
+
*
|
|
5
|
+
* See the guide on [Streams and Subscriptions](https://frontside.com/effection/docs/collections)
|
|
6
|
+
* for details on how to use streams.
|
|
7
|
+
*
|
|
8
|
+
* @param target - the event target whose events will be streamed
|
|
9
|
+
* @param name - the name of the event to stream. E.g. "click"
|
|
10
|
+
* @returns a stream that will see one item for each event
|
|
11
|
+
*/
|
|
12
|
+
export function on(target, eventName) {
|
|
13
|
+
return resource(function* (provide) {
|
|
14
|
+
let signal = createSignal();
|
|
15
|
+
let listener = (...args) => signal.send(args);
|
|
16
|
+
target?.on(eventName, listener);
|
|
17
|
+
try {
|
|
18
|
+
yield* provide(yield* signal);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
target?.off(eventName, listener);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create an {@link Operation} that yields the next event to be emitted by an EventEmitter.
|
|
27
|
+
*
|
|
28
|
+
* @param target - the event target to be watched
|
|
29
|
+
* @param name - the name of the event to watch. E.g. "click"
|
|
30
|
+
* @returns an Operation that yields the next emitted event
|
|
31
|
+
*/
|
|
32
|
+
export function once(source, eventName) {
|
|
33
|
+
const result = withResolvers();
|
|
34
|
+
let listener = (...args) => {
|
|
35
|
+
result.resolve(args);
|
|
36
|
+
source?.off(eventName, listener);
|
|
37
|
+
};
|
|
38
|
+
source?.on(eventName, listener);
|
|
39
|
+
return result.operation;
|
|
40
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Operation } from "effection";
|
|
2
|
+
import type { OutputStream } from "../helpers.js";
|
|
3
|
+
export interface Writable<T> {
|
|
4
|
+
send(message: T): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The process type is what is returned by the `exec` operation. It has all of
|
|
8
|
+
* standard io handles, and methods for synchronizing on return.
|
|
9
|
+
*/
|
|
10
|
+
export interface Process extends StdIO {
|
|
11
|
+
readonly pid: number;
|
|
12
|
+
/**
|
|
13
|
+
* Completes once the process has finished regardless of whether it was
|
|
14
|
+
* successful or not.
|
|
15
|
+
*/
|
|
16
|
+
join(): Operation<ExitStatus>;
|
|
17
|
+
/**
|
|
18
|
+
* Completes once the process has finished successfully. If the process does
|
|
19
|
+
* not complete successfully, it will raise an ExecError.
|
|
20
|
+
*/
|
|
21
|
+
expect(): Operation<ExitStatus>;
|
|
22
|
+
}
|
|
23
|
+
export interface ExecOptions {
|
|
24
|
+
/**
|
|
25
|
+
* When not using passing the `shell` option all arguments must be passed
|
|
26
|
+
* as an array.
|
|
27
|
+
*/
|
|
28
|
+
arguments?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Map of environment variables to use for the process.
|
|
31
|
+
*/
|
|
32
|
+
env?: Record<string, string>;
|
|
33
|
+
/**
|
|
34
|
+
* Create an intermediate shell process; defaults to `false`. Useful if you
|
|
35
|
+
* need to handle glob expansion or passing environment variables. A truthy value
|
|
36
|
+
* will use an intermediate shell to interpret the command using the default system shell.
|
|
37
|
+
* However, if the value is a string, that will be used as the executable path
|
|
38
|
+
* for the intermediate shell.
|
|
39
|
+
*/
|
|
40
|
+
shell?: boolean | string;
|
|
41
|
+
/**
|
|
42
|
+
* Sets the working directory of the process
|
|
43
|
+
*/
|
|
44
|
+
cwd?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface StdIO {
|
|
47
|
+
stdout: OutputStream;
|
|
48
|
+
stderr: OutputStream;
|
|
49
|
+
stdin: Writable<string>;
|
|
50
|
+
}
|
|
51
|
+
export interface ExitStatus {
|
|
52
|
+
/**
|
|
53
|
+
* exit code
|
|
54
|
+
* //TODO: is this pertinent on Windows? Do we need an 'OK' flag
|
|
55
|
+
*/
|
|
56
|
+
code?: number;
|
|
57
|
+
/**
|
|
58
|
+
* If the process exited with a signal instead of an exit code, it
|
|
59
|
+
* is recorded here.
|
|
60
|
+
*/
|
|
61
|
+
signal?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface ProcessResult extends ExitStatus {
|
|
64
|
+
stdout: string;
|
|
65
|
+
stderr: string;
|
|
66
|
+
}
|
|
67
|
+
export interface CreateOSProcess {
|
|
68
|
+
(command: string, options: ExecOptions): Operation<Process>;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/src/exec/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGlD,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,OAAQ,SAAQ,KAAK;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,IAAI,IAAI,SAAS,CAAC,UAAU,CAAC,CAAC;IAE9B;;;OAGG;IACH,MAAM,IAAI,SAAS,CAAC,UAAU,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7B;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAEzB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,MAAM,WAAW,eAAe;IAC9B,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;CAC7D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ExecOptions, ExitStatus } from "./api.js";
|
|
2
|
+
export declare class ExecError extends Error {
|
|
3
|
+
status: ExitStatus;
|
|
4
|
+
command: string;
|
|
5
|
+
options: ExecOptions;
|
|
6
|
+
constructor(status: ExitStatus, command: string, options: ExecOptions);
|
|
7
|
+
name: string;
|
|
8
|
+
get message(): string;
|
|
9
|
+
}
|
|
10
|
+
export declare class DaemonExitError extends ExecError {
|
|
11
|
+
name: string;
|
|
12
|
+
get message(): string;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../../src/src/exec/error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAExD,qBAAa,SAAU,SAAQ,KAAK;IAEzB,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,MAAM;IACf,OAAO,EAAE,WAAW;gBAFpB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,WAAW;IAKpB,IAAI,SAAe;IAE5B,IAAa,OAAO,IAAI,MAAM,CAgB7B;CACF;AAED,qBAAa,eAAgB,SAAQ,SAAS;IACnC,IAAI,SAAqB;IAElC,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export class ExecError extends Error {
|
|
2
|
+
constructor(status, command, options) {
|
|
3
|
+
super();
|
|
4
|
+
Object.defineProperty(this, "status", {
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true,
|
|
8
|
+
value: status
|
|
9
|
+
});
|
|
10
|
+
Object.defineProperty(this, "command", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: command
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(this, "options", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
value: options
|
|
21
|
+
});
|
|
22
|
+
Object.defineProperty(this, "name", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
value: "ExecError"
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
get message() {
|
|
30
|
+
let code = this.status.code ? `code: ${this.status.code}` : null;
|
|
31
|
+
let signal = this.status.signal ? `signal: ${this.status.signal}` : null;
|
|
32
|
+
let env = `env: ${JSON.stringify(this.options.env || {})}`;
|
|
33
|
+
let shell = this.options.shell ? `shell: ${this.options.shell}` : null;
|
|
34
|
+
let cwd = this.options.cwd ? `cwd: ${this.options.cwd}` : null;
|
|
35
|
+
let command = `$ ${this.command} ${this.options.arguments?.join(" ")}`
|
|
36
|
+
.trim();
|
|
37
|
+
return [code, signal, env, shell, cwd, command].filter((item) => !!item)
|
|
38
|
+
.join("\n");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class DaemonExitError extends ExecError {
|
|
42
|
+
constructor() {
|
|
43
|
+
super(...arguments);
|
|
44
|
+
Object.defineProperty(this, "name", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: "DaemonExitError"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
get message() {
|
|
52
|
+
return `daemon process quit unexpectedly\n${super.message}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { spawn as spawnProcess } from "node:child_process";
|
|
2
|
+
import { all, createSignal, Err, Ok, spawn, withResolvers, } from "effection";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { once } from "../eventemitter.js";
|
|
5
|
+
import { useReadable } from "../helpers.js";
|
|
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();
|
|
40
|
+
}
|
|
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();
|
|
49
|
+
}
|
|
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
|
+
try {
|
|
64
|
+
let value = yield* once(childProcess, "close");
|
|
65
|
+
yield* all([io.stdoutDone.operation, io.stderrDone.operation]);
|
|
66
|
+
processResult.resolve(Ok(value));
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
try {
|
|
70
|
+
if (typeof childProcess.pid === "undefined") {
|
|
71
|
+
// deno-lint-ignore no-unsafe-finally
|
|
72
|
+
throw new Error("no pid for childProcess");
|
|
73
|
+
}
|
|
74
|
+
process.kill(-childProcess.pid, "SIGTERM");
|
|
75
|
+
yield* all([io.stdoutDone.operation, io.stderrDone.operation]);
|
|
76
|
+
}
|
|
77
|
+
catch (_e) {
|
|
78
|
+
// do nothing, process is probably already dead
|
|
79
|
+
}
|
|
80
|
+
}
|
|
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
|
+
// 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
|
+
};
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { platform } from "node:os";
|
|
2
|
+
import { all, createSignal, Err, Ok, race, sleep, spawn, withResolvers, } from "effection";
|
|
3
|
+
// @ts-types="npm:@types/cross-spawn@6.0.6"
|
|
4
|
+
import { spawn as spawnProcess } from "cross-spawn";
|
|
5
|
+
import { ctrlc } from "ctrlc-windows";
|
|
6
|
+
import { once } from "../eventemitter.js";
|
|
7
|
+
import { useReadable } from "../helpers.js";
|
|
8
|
+
import { ExecError } from "./error.js";
|
|
9
|
+
function* killTree(pid) {
|
|
10
|
+
try {
|
|
11
|
+
const killer = spawnProcess("cmd.exe", ["/c", "taskkill", "/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
|
|
12
|
+
yield* once(killer, "close");
|
|
13
|
+
}
|
|
14
|
+
catch (_) {
|
|
15
|
+
// best-effort; ignore errors
|
|
16
|
+
}
|
|
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 {
|
|
78
|
+
let value = yield* once(childProcess, "close");
|
|
79
|
+
yield* all([io.stdoutDone.operation, io.stderrDone.operation]);
|
|
80
|
+
processResult.resolve(Ok(value));
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
try {
|
|
84
|
+
// Only try to kill the process if it hasn't exited yet
|
|
85
|
+
if (childProcess.exitCode === null &&
|
|
86
|
+
childProcess.signalCode === null) {
|
|
87
|
+
if (typeof childProcess.pid === "undefined") {
|
|
88
|
+
// deno-lint-ignore no-unsafe-finally
|
|
89
|
+
throw new Error("no pid for childProcess");
|
|
90
|
+
}
|
|
91
|
+
let stdinStream = childProcess.stdin;
|
|
92
|
+
// Try graceful shutdown with ctrlc
|
|
93
|
+
try {
|
|
94
|
+
ctrlc(childProcess.pid);
|
|
95
|
+
if (stdinStream.writable) {
|
|
96
|
+
try {
|
|
97
|
+
// Terminate batch process (Y/N)
|
|
98
|
+
stdinStream.write("Y\n");
|
|
99
|
+
}
|
|
100
|
+
catch (_err) {
|
|
101
|
+
// not much we can do here
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (_err) {
|
|
106
|
+
// ctrlc might fail
|
|
107
|
+
}
|
|
108
|
+
// Close stdin to allow process to exit cleanly
|
|
109
|
+
try {
|
|
110
|
+
stdinStream.end();
|
|
111
|
+
}
|
|
112
|
+
catch (_err) {
|
|
113
|
+
// stdin might already be closed
|
|
114
|
+
}
|
|
115
|
+
// Wait for graceful exit with a timeout
|
|
116
|
+
yield* race([processResult.operation, sleep(300)]);
|
|
117
|
+
// If process still hasn't exited, escalate
|
|
118
|
+
if (childProcess.exitCode === null &&
|
|
119
|
+
childProcess.signalCode === null) {
|
|
120
|
+
// Try regular kill first
|
|
121
|
+
try {
|
|
122
|
+
childProcess.kill();
|
|
123
|
+
}
|
|
124
|
+
catch (_err) {
|
|
125
|
+
// process might already be dead
|
|
126
|
+
}
|
|
127
|
+
// If still alive after kill, force-kill entire process tree
|
|
128
|
+
// This is necessary for bash on Windows where ctrlc doesn't work
|
|
129
|
+
// and child.kill() only kills the shell, leaving grandchildren alive
|
|
130
|
+
if (childProcess.exitCode === null &&
|
|
131
|
+
childProcess.signalCode === null) {
|
|
132
|
+
yield* killTree(childProcess.pid);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Wait for streams to finish
|
|
136
|
+
yield* all([io.stdoutDone.operation, io.stderrDone.operation]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (_e) {
|
|
140
|
+
// do nothing, process is probably already dead
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
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
|
+
};
|
|
173
|
+
export const isWin32 = () => platform() === "win32";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Operation } from "effection";
|
|
2
|
+
import type { ExecOptions, Process, ProcessResult } from "./exec/api.js";
|
|
3
|
+
export * from "./exec/api.js";
|
|
4
|
+
export * from "./exec/error.js";
|
|
5
|
+
export interface Exec extends Operation<Process> {
|
|
6
|
+
join(): Operation<ProcessResult>;
|
|
7
|
+
expect(): Operation<ProcessResult>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Execute `command` with `options`. You should use this operation for processes
|
|
11
|
+
* that have a finite lifetime and on which you may wish to synchronize on the
|
|
12
|
+
* exit status. If you want to start a process like a server that spins up and runs
|
|
13
|
+
* forever, consider using `daemon()`
|
|
14
|
+
*/
|
|
15
|
+
export declare function exec(command: string, options?: ExecOptions): Exec;
|
|
16
|
+
//# sourceMappingURL=exec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/src/exec.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAS,MAAM,WAAW,CAAC;AAClD,OAAO,KAAK,EAEV,WAAW,EAEX,OAAO,EACP,aAAa,EACd,MAAM,eAAe,CAAC;AAIvB,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAEhC,MAAM,WAAW,IAAK,SAAQ,SAAS,CAAC,OAAO,CAAC;IAC9C,IAAI,IAAI,SAAS,CAAC,aAAa,CAAC,CAAC;IACjC,MAAM,IAAI,SAAS,CAAC,aAAa,CAAC,CAAC;CACpC;AAUD;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,IAAI,CAiErE"}
|