@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.
Files changed (58) hide show
  1. package/README.md +12 -0
  2. package/esm/mod.d.ts +3 -0
  3. package/esm/mod.d.ts.map +1 -0
  4. package/esm/mod.js +2 -0
  5. package/esm/package.json +3 -0
  6. package/esm/src/daemon.d.ts +11 -0
  7. package/esm/src/daemon.d.ts.map +1 -0
  8. package/esm/src/daemon.js +20 -0
  9. package/esm/src/eventemitter.d.ts +22 -0
  10. package/esm/src/eventemitter.d.ts.map +1 -0
  11. package/esm/src/eventemitter.js +40 -0
  12. package/esm/src/exec/api.d.ts +70 -0
  13. package/esm/src/exec/api.d.ts.map +1 -0
  14. package/esm/src/exec/api.js +1 -0
  15. package/esm/src/exec/error.d.ts +14 -0
  16. package/esm/src/exec/error.d.ts.map +1 -0
  17. package/esm/src/exec/error.js +54 -0
  18. package/esm/src/exec/posix.d.ts +3 -0
  19. package/esm/src/exec/posix.d.ts.map +1 -0
  20. package/esm/src/exec/posix.js +110 -0
  21. package/esm/src/exec/win32.d.ts +4 -0
  22. package/esm/src/exec/win32.d.ts.map +1 -0
  23. package/esm/src/exec/win32.js +173 -0
  24. package/esm/src/exec.d.ts +16 -0
  25. package/esm/src/exec.d.ts.map +1 -0
  26. package/esm/src/exec.js +75 -0
  27. package/esm/src/helpers.d.ts +12 -0
  28. package/esm/src/helpers.d.ts.map +1 -0
  29. package/esm/src/helpers.js +71 -0
  30. package/package.json +34 -0
  31. package/script/mod.d.ts +3 -0
  32. package/script/mod.d.ts.map +1 -0
  33. package/script/mod.js +20 -0
  34. package/script/package.json +3 -0
  35. package/script/src/daemon.d.ts +11 -0
  36. package/script/src/daemon.d.ts.map +1 -0
  37. package/script/src/daemon.js +23 -0
  38. package/script/src/eventemitter.d.ts +22 -0
  39. package/script/src/eventemitter.d.ts.map +1 -0
  40. package/script/src/eventemitter.js +44 -0
  41. package/script/src/exec/api.d.ts +70 -0
  42. package/script/src/exec/api.d.ts.map +1 -0
  43. package/script/src/exec/api.js +2 -0
  44. package/script/src/exec/error.d.ts +14 -0
  45. package/script/src/exec/error.d.ts.map +1 -0
  46. package/script/src/exec/error.js +59 -0
  47. package/script/src/exec/posix.d.ts +3 -0
  48. package/script/src/exec/posix.d.ts.map +1 -0
  49. package/script/src/exec/posix.js +117 -0
  50. package/script/src/exec/win32.d.ts +4 -0
  51. package/script/src/exec/win32.d.ts.map +1 -0
  52. package/script/src/exec/win32.js +178 -0
  53. package/script/src/exec.d.ts +16 -0
  54. package/script/src/exec.d.ts.map +1 -0
  55. package/script/src/exec.js +92 -0
  56. package/script/src/helpers.d.ts +12 -0
  57. package/script/src/helpers.d.ts.map +1 -0
  58. package/script/src/helpers.js +76 -0
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isWin32 = exports.createWin32Process = void 0;
4
+ const node_os_1 = require("node:os");
5
+ const effection_1 = require("effection");
6
+ // @ts-types="npm:@types/cross-spawn@6.0.6"
7
+ const cross_spawn_1 = require("cross-spawn");
8
+ const ctrlc_windows_1 = require("ctrlc-windows");
9
+ const eventemitter_js_1 = require("../eventemitter.js");
10
+ const helpers_js_1 = require("../helpers.js");
11
+ const error_js_1 = require("./error.js");
12
+ function* killTree(pid) {
13
+ try {
14
+ const killer = (0, cross_spawn_1.spawn)("cmd.exe", ["/c", "taskkill", "/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
15
+ yield* (0, eventemitter_js_1.once)(killer, "close");
16
+ }
17
+ catch (_) {
18
+ // best-effort; ignore errors
19
+ }
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 {
81
+ let value = yield* (0, eventemitter_js_1.once)(childProcess, "close");
82
+ yield* (0, effection_1.all)([io.stdoutDone.operation, io.stderrDone.operation]);
83
+ processResult.resolve((0, effection_1.Ok)(value));
84
+ }
85
+ finally {
86
+ try {
87
+ // Only try to kill the process if it hasn't exited yet
88
+ if (childProcess.exitCode === null &&
89
+ childProcess.signalCode === null) {
90
+ if (typeof childProcess.pid === "undefined") {
91
+ // deno-lint-ignore no-unsafe-finally
92
+ throw new Error("no pid for childProcess");
93
+ }
94
+ let stdinStream = childProcess.stdin;
95
+ // Try graceful shutdown with ctrlc
96
+ try {
97
+ (0, ctrlc_windows_1.ctrlc)(childProcess.pid);
98
+ if (stdinStream.writable) {
99
+ try {
100
+ // Terminate batch process (Y/N)
101
+ stdinStream.write("Y\n");
102
+ }
103
+ catch (_err) {
104
+ // not much we can do here
105
+ }
106
+ }
107
+ }
108
+ catch (_err) {
109
+ // ctrlc might fail
110
+ }
111
+ // Close stdin to allow process to exit cleanly
112
+ try {
113
+ stdinStream.end();
114
+ }
115
+ catch (_err) {
116
+ // stdin might already be closed
117
+ }
118
+ // Wait for graceful exit with a timeout
119
+ yield* (0, effection_1.race)([processResult.operation, (0, effection_1.sleep)(300)]);
120
+ // If process still hasn't exited, escalate
121
+ if (childProcess.exitCode === null &&
122
+ childProcess.signalCode === null) {
123
+ // Try regular kill first
124
+ try {
125
+ childProcess.kill();
126
+ }
127
+ catch (_err) {
128
+ // process might already be dead
129
+ }
130
+ // If still alive after kill, force-kill entire process tree
131
+ // This is necessary for bash on Windows where ctrlc doesn't work
132
+ // and child.kill() only kills the shell, leaving grandchildren alive
133
+ if (childProcess.exitCode === null &&
134
+ childProcess.signalCode === null) {
135
+ yield* killTree(childProcess.pid);
136
+ }
137
+ }
138
+ // Wait for streams to finish
139
+ yield* (0, effection_1.all)([io.stdoutDone.operation, io.stderrDone.operation]);
140
+ }
141
+ }
142
+ catch (_e) {
143
+ // do nothing, process is probably already dead
144
+ }
145
+ }
146
+ });
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
+ };
176
+ exports.createWin32Process = createWin32Process;
177
+ const isWin32 = () => (0, node_os_1.platform)() === "win32";
178
+ exports.isWin32 = isWin32;
@@ -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"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.exec = exec;
18
+ const shellwords_1 = require("shellwords");
19
+ const effection_1 = require("effection");
20
+ const posix_js_1 = require("./exec/posix.js");
21
+ const win32_js_1 = require("./exec/win32.js");
22
+ __exportStar(require("./exec/api.js"), exports);
23
+ __exportStar(require("./exec/error.js"), exports);
24
+ const createProcess = (cmd, opts) => {
25
+ if ((0, win32_js_1.isWin32)()) {
26
+ return (0, win32_js_1.createWin32Process)(cmd, opts);
27
+ }
28
+ else {
29
+ return (0, posix_js_1.createPosixProcess)(cmd, opts);
30
+ }
31
+ };
32
+ /**
33
+ * Execute `command` with `options`. You should use this operation for processes
34
+ * that have a finite lifetime and on which you may wish to synchronize on the
35
+ * exit status. If you want to start a process like a server that spins up and runs
36
+ * forever, consider using `daemon()`
37
+ */
38
+ function exec(command, options = {}) {
39
+ let [cmd, ...args] = options.shell ? [command] : (0, shellwords_1.split)(command);
40
+ let opts = { ...options, arguments: args.concat(options.arguments || []) };
41
+ return {
42
+ *[Symbol.iterator]() {
43
+ return yield* createProcess(cmd, opts);
44
+ },
45
+ *join() {
46
+ const process = yield* createProcess(cmd, opts);
47
+ let stdout = "";
48
+ let stderr = "";
49
+ yield* (0, effection_1.spawn)(function* () {
50
+ let subscription = yield* process.stdout;
51
+ let next = yield* subscription.next();
52
+ while (!next.done) {
53
+ stdout += next.value;
54
+ next = yield* subscription.next();
55
+ }
56
+ });
57
+ yield* (0, effection_1.spawn)(function* () {
58
+ let subscription = yield* process.stderr;
59
+ let next = yield* subscription.next();
60
+ while (!next.done) {
61
+ stderr += next.value;
62
+ next = yield* subscription.next();
63
+ }
64
+ });
65
+ let status = yield* process.join();
66
+ return { ...status, stdout, stderr };
67
+ },
68
+ *expect() {
69
+ const process = yield* createProcess(cmd, opts);
70
+ let stdout = "";
71
+ let stderr = "";
72
+ yield* (0, effection_1.spawn)(function* () {
73
+ let subscription = yield* process.stdout;
74
+ let next = yield* subscription.next();
75
+ while (!next.done) {
76
+ stdout += next.value;
77
+ next = yield* subscription.next();
78
+ }
79
+ });
80
+ yield* (0, effection_1.spawn)(function* () {
81
+ let subscription = yield* process.stderr;
82
+ let next = yield* subscription.next();
83
+ while (!next.done) {
84
+ stderr += next.value;
85
+ next = yield* subscription.next();
86
+ }
87
+ });
88
+ let status = yield* process.expect();
89
+ return { ...status, stdout, stderr };
90
+ },
91
+ };
92
+ }
@@ -0,0 +1,12 @@
1
+ import { type Operation, type Result, type Stream } from "effection";
2
+ import type { Readable } from "node:stream";
3
+ export type OutputStream = Stream<Uint8Array, void>;
4
+ export declare function useReadable(target: Readable | null): Stream<Uint8Array, void>;
5
+ interface Remainder<T> {
6
+ remainder: string;
7
+ result: T;
8
+ }
9
+ export declare function lines(): <T extends Uint8Array, TReturn>(stream: Stream<T, TReturn>) => Stream<string, Remainder<TReturn>>;
10
+ export declare function box<T>(op: () => Operation<T>): Operation<Result<T>>;
11
+ export {};
12
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EAEd,KAAK,MAAM,EACX,KAAK,MAAM,EACZ,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AAEpD,wBAAgB,WAAW,CACzB,MAAM,EAAE,QAAQ,GAAG,IAAI,GACtB,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,CAoB1B;AAED,UAAU,SAAS,CAAC,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,CAAC,CAAC;CACX;AAED,wBAAgB,KAAK,IAAI,CAAC,CAAC,SAAS,UAAU,EAAE,OAAO,EACrD,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,KACvB,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAyCtC;AAED,wBAAiB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAOpE"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useReadable = useReadable;
4
+ exports.lines = lines;
5
+ exports.box = box;
6
+ const effection_1 = require("effection");
7
+ function useReadable(target) {
8
+ return (0, effection_1.resource)(function* (provide) {
9
+ let signal = (0, effection_1.createSignal)();
10
+ let listener = (chunk) => {
11
+ signal.send(chunk);
12
+ };
13
+ target?.on("data", listener);
14
+ target?.on("end", signal.close);
15
+ try {
16
+ yield* provide(yield* signal);
17
+ }
18
+ finally {
19
+ target?.off("data", listener);
20
+ target?.off("end", signal.close);
21
+ signal.close();
22
+ }
23
+ });
24
+ }
25
+ function lines() {
26
+ const decoder = new TextDecoder();
27
+ return function (stream) {
28
+ return {
29
+ *[Symbol.iterator]() {
30
+ let subscription = yield* stream;
31
+ let buffer = [];
32
+ let remainder = "";
33
+ return {
34
+ *next() {
35
+ while (buffer.length === 0) {
36
+ let next = yield* subscription.next();
37
+ if (next.done) {
38
+ return {
39
+ done: true,
40
+ value: {
41
+ remainder,
42
+ result: next.value,
43
+ },
44
+ };
45
+ }
46
+ else {
47
+ let current = remainder + decoder.decode(next.value);
48
+ let lines = current.split("\n");
49
+ if (lines.length > 0) {
50
+ buffer.push(...lines.slice(0, -1));
51
+ remainder = lines.slice(-1)[0];
52
+ }
53
+ else {
54
+ remainder = current;
55
+ }
56
+ }
57
+ }
58
+ return {
59
+ done: false,
60
+ value: buffer.pop(),
61
+ };
62
+ },
63
+ };
64
+ },
65
+ };
66
+ };
67
+ }
68
+ function* box(op) {
69
+ try {
70
+ let value = yield* op();
71
+ return (0, effection_1.Ok)(value);
72
+ }
73
+ catch (e) {
74
+ return (0, effection_1.Err)(e);
75
+ }
76
+ }