@ac-kit/node 0.1.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 (139) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +39 -0
  3. package/dist/child-process/exec-async.d.ts +24 -0
  4. package/dist/child-process/exec-async.d.ts.map +1 -0
  5. package/dist/child-process/exec-async.js +33 -0
  6. package/dist/child-process/exec-async.js.map +1 -0
  7. package/dist/error/node-aggregate-error.d.ts +18 -0
  8. package/dist/error/node-aggregate-error.d.ts.map +1 -0
  9. package/dist/error/node-aggregate-error.js +13 -0
  10. package/dist/error/node-aggregate-error.js.map +1 -0
  11. package/dist/error/node-error.d.ts +26 -0
  12. package/dist/error/node-error.d.ts.map +1 -0
  13. package/dist/error/node-error.js +21 -0
  14. package/dist/error/node-error.js.map +1 -0
  15. package/dist/error/node-exec-error.d.ts +21 -0
  16. package/dist/error/node-exec-error.d.ts.map +1 -0
  17. package/dist/error/node-exec-error.js +17 -0
  18. package/dist/error/node-exec-error.js.map +1 -0
  19. package/dist/error/node-system-error.d.ts +46 -0
  20. package/dist/error/node-system-error.d.ts.map +1 -0
  21. package/dist/error/node-system-error.js +35 -0
  22. package/dist/error/node-system-error.js.map +1 -0
  23. package/dist/error/process-exit-error.d.ts +22 -0
  24. package/dist/error/process-exit-error.d.ts.map +1 -0
  25. package/dist/error/process-exit-error.js +46 -0
  26. package/dist/error/process-exit-error.js.map +1 -0
  27. package/dist/fs/compress-file.d.ts +42 -0
  28. package/dist/fs/compress-file.d.ts.map +1 -0
  29. package/dist/fs/compress-file.js +72 -0
  30. package/dist/fs/compress-file.js.map +1 -0
  31. package/dist/fs/create-temp-dir.d.ts +13 -0
  32. package/dist/fs/create-temp-dir.d.ts.map +1 -0
  33. package/dist/fs/create-temp-dir.js +18 -0
  34. package/dist/fs/create-temp-dir.js.map +1 -0
  35. package/dist/fs/escape-path.d.ts +19 -0
  36. package/dist/fs/escape-path.d.ts.map +1 -0
  37. package/dist/fs/escape-path.js +20 -0
  38. package/dist/fs/escape-path.js.map +1 -0
  39. package/dist/fs/exists-async.d.ts +9 -0
  40. package/dist/fs/exists-async.d.ts.map +1 -0
  41. package/dist/fs/exists-async.js +17 -0
  42. package/dist/fs/exists-async.js.map +1 -0
  43. package/dist/fs/file-content-equal.d.ts +18 -0
  44. package/dist/fs/file-content-equal.d.ts.map +1 -0
  45. package/dist/fs/file-content-equal.js +50 -0
  46. package/dist/fs/file-content-equal.js.map +1 -0
  47. package/dist/fs/is-directory-async.d.ts +11 -0
  48. package/dist/fs/is-directory-async.d.ts.map +1 -0
  49. package/dist/fs/is-directory-async.js +18 -0
  50. package/dist/fs/is-directory-async.js.map +1 -0
  51. package/dist/fs/types.d.ts +19 -0
  52. package/dist/fs/types.d.ts.map +1 -0
  53. package/dist/fs/types.js +23 -0
  54. package/dist/fs/types.js.map +1 -0
  55. package/dist/fs/walk-paths.d.ts +108 -0
  56. package/dist/fs/walk-paths.d.ts.map +1 -0
  57. package/dist/fs/walk-paths.js +197 -0
  58. package/dist/fs/walk-paths.js.map +1 -0
  59. package/dist/fs/write-file-atomic.d.ts +42 -0
  60. package/dist/fs/write-file-atomic.d.ts.map +1 -0
  61. package/dist/fs/write-file-atomic.js +126 -0
  62. package/dist/fs/write-file-atomic.js.map +1 -0
  63. package/dist/index.d.ts +34 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +34 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/module/import-module.d.ts +13 -0
  68. package/dist/module/import-module.d.ts.map +1 -0
  69. package/dist/module/import-module.js +18 -0
  70. package/dist/module/import-module.js.map +1 -0
  71. package/dist/module/optional-import.d.ts +19 -0
  72. package/dist/module/optional-import.d.ts.map +1 -0
  73. package/dist/module/optional-import.js +30 -0
  74. package/dist/module/optional-import.js.map +1 -0
  75. package/dist/module/resolve-module.d.ts +13 -0
  76. package/dist/module/resolve-module.d.ts.map +1 -0
  77. package/dist/module/resolve-module.js +26 -0
  78. package/dist/module/resolve-module.js.map +1 -0
  79. package/dist/net/dgram-socket.d.ts +235 -0
  80. package/dist/net/dgram-socket.d.ts.map +1 -0
  81. package/dist/net/dgram-socket.js +341 -0
  82. package/dist/net/dgram-socket.js.map +1 -0
  83. package/dist/net/ipc-socket.d.ts +34 -0
  84. package/dist/net/ipc-socket.d.ts.map +1 -0
  85. package/dist/net/ipc-socket.js +63 -0
  86. package/dist/net/ipc-socket.js.map +1 -0
  87. package/dist/net/stream-socket.d.ts +156 -0
  88. package/dist/net/stream-socket.d.ts.map +1 -0
  89. package/dist/net/stream-socket.js +188 -0
  90. package/dist/net/stream-socket.js.map +1 -0
  91. package/dist/net/tcp-server.d.ts +110 -0
  92. package/dist/net/tcp-server.d.ts.map +1 -0
  93. package/dist/net/tcp-server.js +198 -0
  94. package/dist/net/tcp-server.js.map +1 -0
  95. package/dist/net/tcp-socket.d.ts +87 -0
  96. package/dist/net/tcp-socket.d.ts.map +1 -0
  97. package/dist/net/tcp-socket.js +153 -0
  98. package/dist/net/tcp-socket.js.map +1 -0
  99. package/dist/net/tls-socket.d.ts +45 -0
  100. package/dist/net/tls-socket.d.ts.map +1 -0
  101. package/dist/net/tls-socket.js +148 -0
  102. package/dist/net/tls-socket.js.map +1 -0
  103. package/dist/process/fork-process.d.ts +63 -0
  104. package/dist/process/fork-process.d.ts.map +1 -0
  105. package/dist/process/fork-process.js +98 -0
  106. package/dist/process/fork-process.js.map +1 -0
  107. package/dist/process/guarded.d.ts +12 -0
  108. package/dist/process/guarded.d.ts.map +1 -0
  109. package/dist/process/guarded.js +25 -0
  110. package/dist/process/guarded.js.map +1 -0
  111. package/dist/process/spawn-process.d.ts +37 -0
  112. package/dist/process/spawn-process.d.ts.map +1 -0
  113. package/dist/process/spawn-process.js +73 -0
  114. package/dist/process/spawn-process.js.map +1 -0
  115. package/dist/process/spawned-process.d.ts +102 -0
  116. package/dist/process/spawned-process.d.ts.map +1 -0
  117. package/dist/process/spawned-process.js +226 -0
  118. package/dist/process/spawned-process.js.map +1 -0
  119. package/dist/process/start-process.d.ts +54 -0
  120. package/dist/process/start-process.d.ts.map +1 -0
  121. package/dist/process/start-process.js +73 -0
  122. package/dist/process/start-process.js.map +1 -0
  123. package/dist/shell/escape-command-arg.d.ts +9 -0
  124. package/dist/shell/escape-command-arg.d.ts.map +1 -0
  125. package/dist/shell/escape-command-arg.js +13 -0
  126. package/dist/shell/escape-command-arg.js.map +1 -0
  127. package/dist/shell/escape-command.d.ts +10 -0
  128. package/dist/shell/escape-command.d.ts.map +1 -0
  129. package/dist/shell/escape-command.js +12 -0
  130. package/dist/shell/escape-command.js.map +1 -0
  131. package/dist/stream/non-closing-writable-stream.d.ts +14 -0
  132. package/dist/stream/non-closing-writable-stream.d.ts.map +1 -0
  133. package/dist/stream/non-closing-writable-stream.js +20 -0
  134. package/dist/stream/non-closing-writable-stream.js.map +1 -0
  135. package/dist/stream/write-async.d.ts +12 -0
  136. package/dist/stream/write-async.d.ts.map +1 -0
  137. package/dist/stream/write-async.js +22 -0
  138. package/dist/stream/write-async.js.map +1 -0
  139. package/package.json +60 -0
@@ -0,0 +1,73 @@
1
+ import { BYTES_PER_MIB } from "@ac-kit/core";
2
+ import { ProcessExitWithOutputError } from "../error/process-exit-error.js";
3
+ import { startProcess } from "./start-process.js";
4
+ /**
5
+ * Runs a command to completion and returns what it printed.
6
+ *
7
+ * The one-shot form of {@link startProcess}: no handle is exposed, so streaming
8
+ * consumers pass `onOutput` instead of subscribing.
9
+ *
10
+ * @param command The executable to run.
11
+ * @param args Arguments passed to it verbatim.
12
+ * @param options Capture limits and everything {@link StartProcessOptions}
13
+ * accepts.
14
+ * @returns The exit status and the captured streams.
15
+ * @throws {ProcessExitWithOutputError} On a non-zero exit, unless
16
+ * `throwOnNonZero` is `false`.
17
+ * @throws {RangeError} When either stream exceeds `maxBuffer`.
18
+ */
19
+ export async function spawnProcess(command, args, options) {
20
+ const encoding = options?.encoding ?? "utf8";
21
+ const maxBuffer = options?.maxBuffer ?? 8 * BYTES_PER_MIB;
22
+ const capture = options?.capture ?? "both";
23
+ const throwOnNonZero = options?.throwOnNonZero ?? true;
24
+ const capturesStdout = capture === "both" || capture === "stdout";
25
+ const capturesStderr = capture === "both" || capture === "stderr";
26
+ const streams = Boolean(options?.onOutput);
27
+ const spawned = await startProcess(command, args, {
28
+ ...options,
29
+ // An unread pipe fills and blocks the child, so nothing is piped in vain.
30
+ stdout: options?.stdout ?? (capturesStdout || streams ? "pipe" : "ignore"),
31
+ stderr: options?.stderr ?? (capturesStderr || streams ? "pipe" : "ignore"),
32
+ });
33
+ let stdout = "";
34
+ let stderr = "";
35
+ let overflowError = null;
36
+ const collect = (source, name, append) => {
37
+ if (!source) {
38
+ return;
39
+ }
40
+ let bytes = 0;
41
+ source.on("data", (chunk) => {
42
+ bytes += chunk.length;
43
+ if (bytes <= maxBuffer) {
44
+ append(chunk.toString(encoding));
45
+ return;
46
+ }
47
+ if (!overflowError) {
48
+ overflowError = new RangeError(`${name} maxBuffer (${maxBuffer} bytes) exceeded`);
49
+ spawned.kill();
50
+ }
51
+ });
52
+ };
53
+ if (capturesStdout) {
54
+ collect(spawned.nodeStdout_, "stdout", (text) => {
55
+ stdout += text;
56
+ });
57
+ }
58
+ if (capturesStderr) {
59
+ collect(spawned.nodeStderr_, "stderr", (text) => {
60
+ stderr += text;
61
+ });
62
+ }
63
+ const exit = await spawned.waitForExit();
64
+ options?.signal?.throwIfAborted();
65
+ if (overflowError) {
66
+ throw overflowError;
67
+ }
68
+ if (throwOnNonZero && exit.code !== 0) {
69
+ throw new ProcessExitWithOutputError(exit.code, exit.signal, exit.signal !== null, stdout, stderr);
70
+ }
71
+ return { ...exit, stdout, stderr };
72
+ }
73
+ //# sourceMappingURL=spawn-process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-process.js","sourceRoot":"","sources":["../../src/process/spawn-process.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ import type { ChildProcess } from "node:child_process";
2
+ import { Readable } from "node:stream";
3
+ import { EventDispatcherMapBase, type EventDispatcherSubscribeOptions, type EventDispatcherWaitOptions, type IEventDispatcherMap } from "@ac-kit/async";
4
+ import { type Callable } from "@ac-kit/core";
5
+ export type ProcessOutputStream = "stdout" | "stderr";
6
+ export type ProcessExit = {
7
+ code: number | null;
8
+ signal: NodeJS.Signals | null;
9
+ durationMs: number;
10
+ };
11
+ export type SpawnedProcessEvents = {
12
+ /** One line of the child's output, with its terminator stripped. */
13
+ output: [stream: ProcessOutputStream, line: string];
14
+ };
15
+ /** Time a terminated child gets to exit before it is killed outright. */
16
+ export declare const DEFAULT_PROCESS_GRACE_MS = 5000;
17
+ export type SpawnedProcessOptions = {
18
+ /** Grace period before {@link SpawnedProcess.terminate} escalates. */
19
+ readonly graceMs?: number | null;
20
+ /**
21
+ * The child leads its own process group, so signals address the group rather
22
+ * than the child alone.
23
+ */
24
+ readonly detached?: boolean | null;
25
+ };
26
+ /**
27
+ * A running child process: its output as events, its exit as an awaitable, and
28
+ * a two-stage termination.
29
+ *
30
+ * Obtained from `startProcess` or `forkProcess`, both of which resolve only
31
+ * once the child has actually spawned — so a handle always has a `pid`, and a
32
+ * spawn failure surfaces as a rejection there rather than later on the handle.
33
+ *
34
+ * Line splitting is attached on the first `output` subscription, so a caller
35
+ * that only wants the exit status never pays for it.
36
+ */
37
+ export declare class SpawnedProcess<TEvents extends SpawnedProcessEvents & Record<PropertyKey, unknown[]> = SpawnedProcessEvents> extends EventDispatcherMapBase<TEvents> implements IEventDispatcherMap<TEvents>, AsyncDisposable {
38
+ protected readonly child: ChildProcess;
39
+ private readonly startedAt;
40
+ private readonly exited;
41
+ private readonly outputDrained;
42
+ private outputAttached;
43
+ private webStdout;
44
+ private webStderr;
45
+ private terminating;
46
+ private killTimer;
47
+ private readonly graceMs;
48
+ private readonly detached;
49
+ constructor(child: ChildProcess, startedAt: number, options?: SpawnedProcessOptions);
50
+ get pid(): number | undefined;
51
+ /** The child's standard output, `null` unless it was piped. */
52
+ get stdout(): ReadableStream<Uint8Array> | null;
53
+ /** The child's standard error, `null` unless it was piped. */
54
+ get stderr(): ReadableStream<Uint8Array> | null;
55
+ /** @internal Node stream behind {@link stdout}, for in-package buffering. */
56
+ get nodeStdout_(): Readable | null;
57
+ /** @internal Node stream behind {@link stderr}, for in-package buffering. */
58
+ get nodeStderr_(): Readable | null;
59
+ subscribe<K extends keyof TEvents>(event: K, listener: Callable<TEvents[K]>, options?: EventDispatcherSubscribeOptions): Callable | null;
60
+ wait<K extends keyof TEvents>(event: K, options?: EventDispatcherWaitOptions<TEvents[K]>): Promise<TEvents[K]>;
61
+ /**
62
+ * Resolves once the child has exited, immediately if it already has.
63
+ *
64
+ * @param signal Abandons the wait; the child is left running.
65
+ * @returns The exit code, terminating signal and wall-clock duration.
66
+ */
67
+ waitForExit(signal?: AbortSignal | null): Promise<ProcessExit>;
68
+ /**
69
+ * Sends `SIGTERM`, then `SIGKILL` if the child is still alive after
70
+ * `graceMs`. Idempotent: concurrent and repeated calls share one escalation.
71
+ *
72
+ * @param graceMs Overrides the grace period given at spawn time.
73
+ * @returns The child's exit, once it has happened.
74
+ */
75
+ terminate(graceMs?: number): Promise<ProcessExit>;
76
+ /**
77
+ * Sends one signal and returns, with no escalation — for signals that mean
78
+ * something to the child other than "shut down".
79
+ *
80
+ * @param signal Defaults to `SIGTERM`.
81
+ * @returns Whether the signal was delivered.
82
+ */
83
+ kill(signal?: NodeJS.Signals): boolean;
84
+ [Symbol.asyncDispose](): Promise<void>;
85
+ /**
86
+ * A detached child leads its own group, so the negated pid reaches whatever
87
+ * it spawned too — otherwise a grandchild outlives the run, reparented to
88
+ * init with nothing left pointing at it.
89
+ */
90
+ private signalProcess;
91
+ protected attachOutput(): void;
92
+ /**
93
+ * Adapts by listening rather than by `Readable.toWeb`, which would lock the
94
+ * source: every consumer here is one more `data` listener on the same stream.
95
+ * No backpressure — the child's output is drained as fast as it arrives.
96
+ */
97
+ private toWebStream;
98
+ private attachOutputFor;
99
+ private pipeOutput;
100
+ private waitForClose;
101
+ }
102
+ //# sourceMappingURL=spawned-process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawned-process.d.ts","sourceRoot":"","sources":["../../src/process/spawned-process.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,226 @@
1
+ import { createInterface } from "node:readline";
2
+ import { EventDispatcherMapBase, } from "@ac-kit/async";
3
+ import { Timer } from "@ac-kit/core";
4
+ /** Time a terminated child gets to exit before it is killed outright. */
5
+ export const DEFAULT_PROCESS_GRACE_MS = 5000;
6
+ /**
7
+ * A running child process: its output as events, its exit as an awaitable, and
8
+ * a two-stage termination.
9
+ *
10
+ * Obtained from `startProcess` or `forkProcess`, both of which resolve only
11
+ * once the child has actually spawned — so a handle always has a `pid`, and a
12
+ * spawn failure surfaces as a rejection there rather than later on the handle.
13
+ *
14
+ * Line splitting is attached on the first `output` subscription, so a caller
15
+ * that only wants the exit status never pays for it.
16
+ */
17
+ export class SpawnedProcess extends EventDispatcherMapBase {
18
+ child;
19
+ startedAt;
20
+ exited;
21
+ outputDrained = [];
22
+ outputAttached = false;
23
+ webStdout = null;
24
+ webStderr = null;
25
+ terminating = null;
26
+ killTimer = null;
27
+ graceMs;
28
+ detached;
29
+ constructor(child, startedAt, options) {
30
+ super();
31
+ this.child = child;
32
+ this.startedAt = startedAt;
33
+ this.graceMs = options?.graceMs ?? DEFAULT_PROCESS_GRACE_MS;
34
+ this.detached = options?.detached ?? false;
35
+ // Post-spawn errors (a failed kill, a broken channel) must not become an
36
+ // unhandled 'error'; `close` still fires and settles the exit.
37
+ child.on("error", () => { });
38
+ this.exited = this.waitForClose();
39
+ }
40
+ get pid() {
41
+ return this.child.pid;
42
+ }
43
+ /** The child's standard output, `null` unless it was piped. */
44
+ get stdout() {
45
+ this.webStdout ??= this.toWebStream(this.child.stdout);
46
+ return this.webStdout;
47
+ }
48
+ /** The child's standard error, `null` unless it was piped. */
49
+ get stderr() {
50
+ this.webStderr ??= this.toWebStream(this.child.stderr);
51
+ return this.webStderr;
52
+ }
53
+ /** @internal Node stream behind {@link stdout}, for in-package buffering. */
54
+ get nodeStdout_() {
55
+ return this.child.stdout;
56
+ }
57
+ /** @internal Node stream behind {@link stderr}, for in-package buffering. */
58
+ get nodeStderr_() {
59
+ return this.child.stderr;
60
+ }
61
+ subscribe(event, listener, options) {
62
+ this.attachOutputFor(event);
63
+ return super.subscribe(event, listener, options);
64
+ }
65
+ wait(event, options) {
66
+ this.attachOutputFor(event);
67
+ return super.wait(event, options);
68
+ }
69
+ /**
70
+ * Resolves once the child has exited, immediately if it already has.
71
+ *
72
+ * @param signal Abandons the wait; the child is left running.
73
+ * @returns The exit code, terminating signal and wall-clock duration.
74
+ */
75
+ waitForExit(signal) {
76
+ if (!signal) {
77
+ return this.exited;
78
+ }
79
+ // Rejects rather than throws: the signature promises a promise.
80
+ if (signal.aborted) {
81
+ return Promise.reject(signal.reason);
82
+ }
83
+ return new Promise((resolve, reject) => {
84
+ const onAbort = () => reject(signal.reason);
85
+ signal.addEventListener("abort", onAbort, { once: true });
86
+ void this.exited.then((exit) => {
87
+ signal.removeEventListener("abort", onAbort);
88
+ resolve(exit);
89
+ });
90
+ });
91
+ }
92
+ /**
93
+ * Sends `SIGTERM`, then `SIGKILL` if the child is still alive after
94
+ * `graceMs`. Idempotent: concurrent and repeated calls share one escalation.
95
+ *
96
+ * @param graceMs Overrides the grace period given at spawn time.
97
+ * @returns The child's exit, once it has happened.
98
+ */
99
+ terminate(graceMs) {
100
+ if (this.terminating) {
101
+ return this.terminating;
102
+ }
103
+ this.terminating = this.exited;
104
+ if (this.child.exitCode === null && this.child.signalCode === null) {
105
+ this.signalProcess("SIGTERM");
106
+ this.killTimer = new Timer(() => this.signalProcess("SIGKILL"), graceMs ?? this.graceMs);
107
+ this.killTimer.start();
108
+ }
109
+ return this.terminating;
110
+ }
111
+ /**
112
+ * Sends one signal and returns, with no escalation — for signals that mean
113
+ * something to the child other than "shut down".
114
+ *
115
+ * @param signal Defaults to `SIGTERM`.
116
+ * @returns Whether the signal was delivered.
117
+ */
118
+ kill(signal = "SIGTERM") {
119
+ return this.signalProcess(signal);
120
+ }
121
+ async [Symbol.asyncDispose]() {
122
+ await this.terminate();
123
+ }
124
+ /**
125
+ * A detached child leads its own group, so the negated pid reaches whatever
126
+ * it spawned too — otherwise a grandchild outlives the run, reparented to
127
+ * init with nothing left pointing at it.
128
+ */
129
+ signalProcess(signal) {
130
+ const pid = this.child.pid;
131
+ if (pid === undefined ||
132
+ this.child.exitCode !== null ||
133
+ this.child.signalCode !== null) {
134
+ return false;
135
+ }
136
+ // Windows has no process groups; `detached` only opens a console there.
137
+ if (!this.detached || process.platform === "win32") {
138
+ return this.child.kill(signal);
139
+ }
140
+ try {
141
+ process.kill(-pid, signal);
142
+ return true;
143
+ }
144
+ catch {
145
+ // Already gone between the liveness check and the signal.
146
+ return false;
147
+ }
148
+ }
149
+ attachOutput() {
150
+ if (this.outputAttached) {
151
+ return;
152
+ }
153
+ this.outputAttached = true;
154
+ this.pipeOutput("stdout", this.child.stdout);
155
+ this.pipeOutput("stderr", this.child.stderr);
156
+ }
157
+ /**
158
+ * Adapts by listening rather than by `Readable.toWeb`, which would lock the
159
+ * source: every consumer here is one more `data` listener on the same stream.
160
+ * No backpressure — the child's output is drained as fast as it arrives.
161
+ */
162
+ toWebStream(source) {
163
+ if (!source) {
164
+ return null;
165
+ }
166
+ let live = true;
167
+ let onData = null;
168
+ return new ReadableStream({
169
+ start(controller) {
170
+ onData = (chunk) => {
171
+ if (live) {
172
+ controller.enqueue(chunk);
173
+ }
174
+ };
175
+ source.on("data", onData);
176
+ source.once("end", () => {
177
+ if (live) {
178
+ live = false;
179
+ controller.close();
180
+ }
181
+ });
182
+ source.once("error", (error) => {
183
+ if (live) {
184
+ live = false;
185
+ controller.error(error);
186
+ }
187
+ });
188
+ },
189
+ cancel() {
190
+ live = false;
191
+ if (onData) {
192
+ source.off("data", onData);
193
+ onData = null;
194
+ }
195
+ },
196
+ });
197
+ }
198
+ attachOutputFor(event) {
199
+ if (event === "output") {
200
+ this.attachOutput();
201
+ }
202
+ }
203
+ pipeOutput(stream, source) {
204
+ if (!source) {
205
+ return;
206
+ }
207
+ const lines = createInterface({ input: source });
208
+ lines.on("line", (line) => {
209
+ this.dispatch("output", [stream, line]);
210
+ });
211
+ // `close` on the child can beat readline's final line out of the door.
212
+ this.outputDrained.push(new Promise((resolve) => lines.once("close", () => resolve())));
213
+ }
214
+ async waitForClose() {
215
+ const [code, signal] = await new Promise((resolve) => {
216
+ this.child.once("close", (closeCode, closeSignal) => resolve([closeCode, closeSignal]));
217
+ });
218
+ if (this.killTimer) {
219
+ this.killTimer.cancel();
220
+ this.killTimer = null;
221
+ }
222
+ await Promise.allSettled(this.outputDrained);
223
+ return { code, signal, durationMs: Date.now() - this.startedAt };
224
+ }
225
+ }
226
+ //# sourceMappingURL=spawned-process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawned-process.js","sourceRoot":"","sources":["../../src/process/spawned-process.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { Callable } from "@ac-kit/core";
2
+ import { type ProcessOutputStream, SpawnedProcess } from "./spawned-process.js";
3
+ /** What the child does with one of its standard streams. */
4
+ export type ProcessStdio = "pipe" | "inherit" | "ignore";
5
+ export type StartProcessOptions = {
6
+ /**
7
+ * Runs the command through a shell, so redirection and piping work and
8
+ * arguments are **not** escaped. Only ever pass a trusted command line.
9
+ */
10
+ readonly shell?: boolean | string | null;
11
+ readonly cwd?: string | null;
12
+ readonly env?: NodeJS.ProcessEnv | null;
13
+ /** Aborting terminates the child. */
14
+ readonly signal?: AbortSignal | null;
15
+ /** Written to the child's stdin, which is then closed. */
16
+ readonly input?: string | Uint8Array | null;
17
+ /** Default `"pipe"`, which is what `output` events are read from. */
18
+ readonly stdout?: ProcessStdio | null;
19
+ /** Default `"pipe"`. */
20
+ readonly stderr?: ProcessStdio | null;
21
+ /**
22
+ * Makes the child lead its own process group, so `terminate`, `kill` and an
23
+ * aborted signal reach everything it spawns rather than the child alone.
24
+ */
25
+ readonly detached?: boolean | null;
26
+ /** Pre-subscribed before the child can write anything. */
27
+ readonly onOutput?: Callable<[
28
+ stream: ProcessOutputStream,
29
+ line: string
30
+ ]> | null;
31
+ /** Grace period before `terminate` escalates to `SIGKILL`. Default 5000. */
32
+ readonly graceMs?: number | null;
33
+ };
34
+ /**
35
+ * Spawns a command and resolves once it is running.
36
+ *
37
+ * Nothing in `args` can be interpreted as a command unless `shell` is set, so
38
+ * the default form is safe with untrusted arguments.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * await using child = await startProcess("tail", ["-f", "app.log"]);
43
+ * child.subscribe("output", (_stream, line) => console.log(line));
44
+ * ```;
45
+ *
46
+ * @param command The executable to run.
47
+ * @param args Arguments passed to it verbatim.
48
+ * @param options Streams, environment, cancellation. See
49
+ * {@link StartProcessOptions}.
50
+ * @returns A handle over the running child.
51
+ * @throws When the command cannot be spawned.
52
+ */
53
+ export declare function startProcess(command: string, args?: readonly string[], options?: StartProcessOptions): Promise<SpawnedProcess>;
54
+ //# sourceMappingURL=start-process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-process.d.ts","sourceRoot":"","sources":["../../src/process/start-process.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,KAAK,mBAAmB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEhF,4DAA4D;AAC5D,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzD,MAAM,MAAM,mBAAmB,GAAG;IACjC;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;IAEzC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE7B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;IAExC,qCAAqC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAErC,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAE5C,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAEtC,wBAAwB;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAEtC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAEnC,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAC3B;QAAC,MAAM,EAAE,mBAAmB;QAAE,IAAI,EAAE,MAAM;KAAC,CAC3C,GAAG,IAAI,CAAC;IAET,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,YAAY,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,cAAc,CAAC,CAyDzB"}
@@ -0,0 +1,73 @@
1
+ import { spawn } from "node:child_process";
2
+ import { SpawnedProcess } from "./spawned-process.js";
3
+ /**
4
+ * Spawns a command and resolves once it is running.
5
+ *
6
+ * Nothing in `args` can be interpreted as a command unless `shell` is set, so
7
+ * the default form is safe with untrusted arguments.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * await using child = await startProcess("tail", ["-f", "app.log"]);
12
+ * child.subscribe("output", (_stream, line) => console.log(line));
13
+ * ```;
14
+ *
15
+ * @param command The executable to run.
16
+ * @param args Arguments passed to it verbatim.
17
+ * @param options Streams, environment, cancellation. See
18
+ * {@link StartProcessOptions}.
19
+ * @returns A handle over the running child.
20
+ * @throws When the command cannot be spawned.
21
+ */
22
+ export async function startProcess(command, args, options) {
23
+ const startedAt = performance.now();
24
+ const child = spawn(command, args ? [...args] : [], {
25
+ shell: options?.shell ?? false,
26
+ cwd: options?.cwd ?? undefined,
27
+ env: options?.env ?? undefined,
28
+ detached: options?.detached ?? false,
29
+ stdio: [
30
+ options?.input !== undefined && options?.input !== null
31
+ ? "pipe"
32
+ : "ignore",
33
+ options?.stdout ?? "pipe",
34
+ options?.stderr ?? "pipe",
35
+ ],
36
+ });
37
+ // Attached before anything can await: an abort signal fires 'error' on the
38
+ // next tick, which would go unhandled during the gap below.
39
+ child.on("error", () => { });
40
+ await new Promise((resolve, reject) => {
41
+ child.once("error", reject);
42
+ child.once("spawn", () => {
43
+ child.removeListener("error", reject);
44
+ resolve();
45
+ });
46
+ });
47
+ const spawned = new SpawnedProcess(child, startedAt, {
48
+ graceMs: options?.graceMs,
49
+ detached: options?.detached,
50
+ });
51
+ const onOutput = options?.onOutput;
52
+ if (onOutput) {
53
+ spawned.subscribe("output", onOutput);
54
+ }
55
+ if (options?.input) {
56
+ // A child that never reads stdin makes this write emit its own error.
57
+ child.stdin?.on("error", () => { });
58
+ child.stdin?.end(options.input);
59
+ }
60
+ // Wired here rather than handed to `spawn`, whose own handling signals the
61
+ // child alone and never escalates.
62
+ const signal = options?.signal;
63
+ if (signal?.aborted === true) {
64
+ void spawned.terminate();
65
+ }
66
+ else {
67
+ signal?.addEventListener("abort", () => void spawned.terminate(), {
68
+ once: true,
69
+ });
70
+ }
71
+ return spawned;
72
+ }
73
+ //# sourceMappingURL=start-process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-process.js","sourceRoot":"","sources":["../../src/process/start-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAI3C,OAAO,EAA4B,cAAc,EAAE,MAAM,sBAAsB,CAAC;AA2ChF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,OAAe,EACf,IAAwB,EACxB,OAA6B;IAE7B,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAEpC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;QACnD,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK;QAC9B,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,SAAS;QAC9B,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,SAAS;QAC9B,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,KAAK;QACpC,KAAK,EAAE;YACN,OAAO,EAAE,KAAK,KAAK,SAAS,IAAI,OAAO,EAAE,KAAK,KAAK,IAAI;gBACtD,CAAC,CAAC,MAAM;gBACR,CAAC,CAAC,QAAQ;YACX,OAAO,EAAE,MAAM,IAAI,MAAM;YACzB,OAAO,EAAE,MAAM,IAAI,MAAM;SACzB;KACD,CAAC,CAAC;IAEH,2EAA2E;IAC3E,4DAA4D;IAC5D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAE5B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACxB,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACtC,OAAO,EAAE,CAAC;QACX,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE;QACpD,OAAO,EAAE,OAAO,EAAE,OAAO;QACzB,QAAQ,EAAE,OAAO,EAAE,QAAQ;KAC3B,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,CAAC;IACnC,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;QACpB,sEAAsE;QACtE,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,2EAA2E;IAC3E,mCAAmC;IACnC,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;IAC/B,IAAI,MAAM,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;QAC9B,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC;IAC1B,CAAC;SAAM,CAAC;QACP,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,SAAS,EAAE,EAAE;YACjE,IAAI,EAAE,IAAI;SACV,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC;AAChB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Escape a string to be safely used as a shell argument, using the rules of the
3
+ * current platform.
4
+ *
5
+ * @param expr The string to escape
6
+ * @returns The escaped string
7
+ */
8
+ export declare const escapeCommandArg: (expr: string) => string;
9
+ //# sourceMappingURL=escape-command-arg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape-command-arg.d.ts","sourceRoot":"","sources":["../../src/shell/escape-command-arg.ts"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAGtB,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { platform as processPlatform } from "node:process";
2
+ import { escapePosixShCommandArg, escapeWin32CmdCommandArg, } from "@ac-kit/core";
3
+ /**
4
+ * Escape a string to be safely used as a shell argument, using the rules of the
5
+ * current platform.
6
+ *
7
+ * @param expr The string to escape
8
+ * @returns The escaped string
9
+ */
10
+ export const escapeCommandArg = processPlatform === "win32"
11
+ ? escapeWin32CmdCommandArg
12
+ : escapePosixShCommandArg;
13
+ //# sourceMappingURL=escape-command-arg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape-command-arg.js","sourceRoot":"","sources":["../../src/shell/escape-command-arg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,eAAe,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EACN,uBAAuB,EACvB,wBAAwB,GACxB,MAAM,cAAc,CAAC;AAEtB;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC5B,eAAe,KAAK,OAAO;IAC1B,CAAC,CAAC,wBAAwB;IAC1B,CAAC,CAAC,uBAAuB,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Escapes any characters in a string that might be used to trick a shell
3
+ * command into executing arbitrary commands, using the rules of the current
4
+ * platform.
5
+ *
6
+ * @param cmd The command to escape
7
+ * @returns The escaped command
8
+ */
9
+ export declare const escapeCommand: (cmd: string) => string;
10
+ //# sourceMappingURL=escape-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape-command.d.ts","sourceRoot":"","sources":["../../src/shell/escape-command.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAC8B,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { platform as processPlatform } from "node:process";
2
+ import { escapePosixShCommand, escapeWin32CmdCommand } from "@ac-kit/core";
3
+ /**
4
+ * Escapes any characters in a string that might be used to trick a shell
5
+ * command into executing arbitrary commands, using the rules of the current
6
+ * platform.
7
+ *
8
+ * @param cmd The command to escape
9
+ * @returns The escaped command
10
+ */
11
+ export const escapeCommand = processPlatform === "win32" ? escapeWin32CmdCommand : escapePosixShCommand;
12
+ //# sourceMappingURL=escape-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape-command.js","sourceRoot":"","sources":["../../src/shell/escape-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,eAAe,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE3E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GACzB,eAAe,KAAK,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,oBAAoB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { Writable } from "node:stream";
2
+ /**
3
+ * Create a `WritableStream<string>` that wraps a Node `Writable` but never
4
+ * closes it.
5
+ *
6
+ * `Writable.toWeb(stream).getWriter().close()` ends the underlying stream,
7
+ * which never resolves for `process.stdout`/`process.stderr` on a TTY (a
8
+ * well-known Node limitation — closing a WHATWG-wrapped TTY writable hangs
9
+ * forever, reproduced with nothing more than `Writable.toWeb(process.stdout)` +
10
+ * `writer.close()`). Use this instead of `Writable.toWeb` directly for any
11
+ * stream whose lifecycle isn't the caller's to end.
12
+ */
13
+ export declare function nonClosingWritableStream(stream: Writable): WritableStream<string>;
14
+ //# sourceMappingURL=non-closing-writable-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"non-closing-writable-stream.d.ts","sourceRoot":"","sources":["../../src/stream/non-closing-writable-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACvC,MAAM,EAAE,QAAQ,GACd,cAAc,CAAC,MAAM,CAAC,CAOxB"}