@zokugun/exec-lite 0.0.0-stage → 0.2.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 (63) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +275 -2
  3. package/lib/cjs/combineSignals.cjs +19 -0
  4. package/lib/cjs/combineSignals.d.cts +1 -0
  5. package/lib/cjs/exec-process.cjs +221 -0
  6. package/lib/cjs/exec-process.d.cts +37 -0
  7. package/lib/cjs/exec.cjs +9 -0
  8. package/lib/cjs/exec.d.cts +2 -0
  9. package/lib/cjs/index.cjs +7 -0
  10. package/lib/cjs/index.d.cts +3 -0
  11. package/lib/cjs/types.cjs +2 -0
  12. package/lib/cjs/types.d.cts +56 -0
  13. package/lib/cjs/utils/command/detectShebang.cjs +36 -0
  14. package/lib/cjs/utils/command/detectShebang.d.cts +1 -0
  15. package/lib/cjs/utils/command/normalizeCommand.cjs +59 -0
  16. package/lib/cjs/utils/command/normalizeCommand.d.cts +6 -0
  17. package/lib/cjs/utils/command/resolveCommand.cjs +43 -0
  18. package/lib/cjs/utils/command/resolveCommand.d.cts +2 -0
  19. package/lib/cjs/utils/env/addNodeBinToPath.cjs +24 -0
  20. package/lib/cjs/utils/env/addNodeBinToPath.d.cts +2 -0
  21. package/lib/cjs/utils/env/computeEnv.cjs +17 -0
  22. package/lib/cjs/utils/env/computeEnv.d.cts +2 -0
  23. package/lib/cjs/utils/env/getPathFromEnv.cjs +22 -0
  24. package/lib/cjs/utils/env/getPathFromEnv.d.cts +2 -0
  25. package/lib/cjs/utils/env/types.cjs +2 -0
  26. package/lib/cjs/utils/env/types.d.cts +5 -0
  27. package/lib/cjs/utils/normalizeOptions.cjs +31 -0
  28. package/lib/cjs/utils/normalizeOptions.d.cts +2 -0
  29. package/lib/cjs/utils/stream/combineStreams.cjs +25 -0
  30. package/lib/cjs/utils/stream/combineStreams.d.cts +2 -0
  31. package/lib/cjs/utils/stream/readStream.cjs +16 -0
  32. package/lib/cjs/utils/stream/readStream.d.cts +5 -0
  33. package/lib/esm/combineSignals.d.mts +1 -0
  34. package/lib/esm/combineSignals.mjs +16 -0
  35. package/lib/esm/exec-process.d.mts +37 -0
  36. package/lib/esm/exec-process.mjs +214 -0
  37. package/lib/esm/exec.d.mts +2 -0
  38. package/lib/esm/exec.mjs +6 -0
  39. package/lib/esm/index.d.mts +3 -0
  40. package/lib/esm/index.mjs +2 -0
  41. package/lib/esm/types.d.mts +56 -0
  42. package/lib/esm/types.mjs +1 -0
  43. package/lib/esm/utils/command/detectShebang.d.mts +1 -0
  44. package/lib/esm/utils/command/detectShebang.mjs +30 -0
  45. package/lib/esm/utils/command/normalizeCommand.d.mts +6 -0
  46. package/lib/esm/utils/command/normalizeCommand.mjs +53 -0
  47. package/lib/esm/utils/command/resolveCommand.d.mts +2 -0
  48. package/lib/esm/utils/command/resolveCommand.mjs +37 -0
  49. package/lib/esm/utils/env/addNodeBinToPath.d.mts +2 -0
  50. package/lib/esm/utils/env/addNodeBinToPath.mjs +18 -0
  51. package/lib/esm/utils/env/computeEnv.d.mts +2 -0
  52. package/lib/esm/utils/env/computeEnv.mjs +14 -0
  53. package/lib/esm/utils/env/getPathFromEnv.d.mts +2 -0
  54. package/lib/esm/utils/env/getPathFromEnv.mjs +19 -0
  55. package/lib/esm/utils/env/types.d.mts +5 -0
  56. package/lib/esm/utils/env/types.mjs +1 -0
  57. package/lib/esm/utils/normalizeOptions.d.mts +2 -0
  58. package/lib/esm/utils/normalizeOptions.mjs +25 -0
  59. package/lib/esm/utils/stream/combineStreams.d.mts +2 -0
  60. package/lib/esm/utils/stream/combineStreams.mjs +22 -0
  61. package/lib/esm/utils/stream/readStream.d.mts +5 -0
  62. package/lib/esm/utils/stream/readStream.mjs +13 -0
  63. package/package.json +123 -5
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2026-present Baptiste Augrain
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,276 @@
1
- # Temporary Holding Version
1
+ [@zokugun/exec-lite](https://github.com/zokugun/node-exec-lite)
2
+ ===============================================================
2
3
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
5
+ [![NPM Version](https://img.shields.io/npm/v/@zokugun/exec-lite?color=brightgreen)](https://www.npmjs.com/package/@zokugun/exec-lite)
6
+ [![NodeJS Version](https://img.shields.io/badge/node.js-%3E%3D%2018.20-green)](#requirements)
7
+ [![Modules](https://img.shields.io/badge/modules-ESM%20%7C%20CJS-green)](#requirements)
8
+ [![Ko-fi](https://img.shields.io/badge/Ko--fi-f87171?logo=kofi&logoColor=white)](https://ko-fi.com/daiyam)
9
+ [![Liberapay](https://img.shields.io/badge/Liberapay-facc15?logo=liberapay&logoColor=black)](https://liberapay.com/daiyam/donate)
10
+ [![PayPal](https://img.shields.io/badge/PayPal-5277C3?logo=paypal&logoColor=white)](https://paypal.me/daiyam99)
11
+
12
+ > Run external commands and manage their output easily. You get simple success or failure objects containing exit codes, execution time, and all captured text from standard output and error streams.
13
+
14
+ Features
15
+ --------
16
+
17
+ - **Process Management**: Spawn and manage external commands with a clean API.
18
+ - **Result Types**: Get structured success and failure objects for every run.
19
+ - **Unified Output**: Combines stdout and stderr into a single stream for easier logging.
20
+ - **Windows Normalization**: Automatically handles `cmd.exe` and path escaping for Windows.
21
+ - **Automated Resolution**: Locate binaries automatically using PATH and file extensions.
22
+ - **Process Piping**: Chain multiple commands together to pass data between them.
23
+ - **Abort Support**: Cancel running processes using standard abort signals.
24
+ - **Exit Tracking**: Capture precise exit codes and signal codes from tasks.
25
+ - **Execution Timing**: Measure the exact duration of a command in milliseconds.
26
+
27
+ Installation
28
+ ------------
29
+
30
+ ```bash
31
+ npm add @zokugun/exec-lite
32
+ ```
33
+
34
+ Requirements
35
+ ------------
36
+
37
+ - **Node.js**: `v18.20` or higher
38
+ - **Modules**: ECMAScript Modules (`ESM`) or CommonJS (`CJS`)
39
+
40
+ Quick Start
41
+ -----------
42
+
43
+ ### 1. Basic Execution
44
+
45
+ Run a command and wait for the final output. Use this when you only need the end result.
46
+
47
+ ```typescript
48
+ import { exec } from '@zokugun/exec-lite';
49
+
50
+ async function runSimpleCommand(): Promise<void> {
51
+ const result = await exec('echo', ['hello world']);
52
+
53
+ if(result.fails) {
54
+ console.error(`Failed with exit code: ${result.value.exitCode}`);
55
+
56
+ return;
57
+ }
58
+
59
+ const { stdout } = result.value;
60
+
61
+ console.log(`Output: ${stdout}`);
62
+ }
63
+ ```
64
+
65
+ ### 2. Streaming Output
66
+
67
+ Iterate over the process output in real-time.
68
+
69
+ ```typescript
70
+ import { exec } from '@zokugun/exec-lite';
71
+
72
+ async function runPipedCommands(): Promise<void> {
73
+ const task = exec('npm', ['install']);
74
+
75
+ for await(const line of task) {
76
+ console.log(`Log: ${line}`);
77
+ }
78
+
79
+ if(task.failed) {
80
+ console.error(`Failed with exit code: ${task.exitCode}`);
81
+ }
82
+ }
83
+ ```
84
+
85
+ ### 3. Piping Processes
86
+
87
+ Chain multiple commands together using the `.pipe()` method. The output of the first process is passed as the input to the second.
88
+
89
+ ```typescript
90
+ import { exec } from '@zokugun/exec-lite';
91
+
92
+ async function runPipedCommands(): Promise<void> {
93
+ const result = await exec('cat', ['package.json']).pipe('grep', ['dependencies']);
94
+
95
+ if(result.fails) {
96
+ console.log('Pipeline failed');
97
+
98
+ return;
99
+ }
100
+
101
+ console.log(`Filtered output: ${result.value.stdout}`);
102
+ }
103
+
104
+ await runPipedCommands();
105
+ ```
106
+
107
+ API Reference
108
+ -------------
109
+
110
+ ### Functions
111
+
112
+ #### `exec`
113
+
114
+ ```typescript
115
+ export function exec(
116
+ command: string,
117
+ args?: readonly string[] | null,
118
+ options?: Partial<Options>,
119
+ ): Subprocess;
120
+ ```
121
+
122
+ Spawns a child process and returns a `Subprocess` instance. The process starts immediately on return.
123
+
124
+ **Caveats:**
125
+
126
+ - `exec` does not throw. Errors surface through the `fails` flag of the `Result`.
127
+ - On Windows, batch files (`.bat`, `.cmd`) are wrapped in `cmd.exe` with verbatim argument handling.
128
+
129
+ ---
130
+
131
+ ### Classes
132
+
133
+ #### `ExecProcess`
134
+
135
+ ```typescript
136
+ class ExecProcess implements Subprocess { ... }
137
+ ```
138
+
139
+ The class behind every subprocess returned by `exec`. You rarely need to construct it directly — use `exec()` instead.
140
+
141
+ **Key properties:**
142
+
143
+ | Property | Type | Description |
144
+ | ------------- | --------------------------- | ------------------------------------------------------ |
145
+ | `.aborted` | `boolean` | True if the process was aborted via signal |
146
+ | `.error` | `Error \| undefined` | The error that caused failure, if any |
147
+ | `.exitCode` | `number \| undefined` | Exit code after the process closes |
148
+ | `.failed` | `boolean` | True if exit code is non-zero or a signal was received |
149
+ | `.killed` | `boolean` | True if `kill()` was called |
150
+ | `.pid` | `number \| undefined` | OS process ID |
151
+ | `.process` | `ChildProcess \| undefined` | The raw Node.js child process handle |
152
+ | `.signalCode` | `string \| undefined` | Signal code that terminated the process |
153
+
154
+ **Key methods:**
155
+
156
+ | Method | Returns | Description |
157
+ | --------------------------------- | ------------ | ----------------------------------------------------------------- |
158
+ | `.kill(signal?)` | `boolean` | Sends a signal to the process. Returns false if no handle exists. |
159
+ | `.pipe(command, args?, options?)` | `Subprocess` | Chains another command, piping this stdout into its stdin. |
160
+ | `.spawn()` | `void` | Starts the process. Called automatically by `exec()`. |
161
+
162
+ ---
163
+
164
+ ### Types
165
+
166
+ #### `ExecResult`
167
+
168
+ A discriminated union wrapping success or failure:
169
+
170
+ ```typescript
171
+ type ExecResult = Result<ExecSuccess, ExecFailure>;
172
+ ```
173
+
174
+ ---
175
+
176
+ #### `ExecSuccess`
177
+
178
+ Returned when the process exits with code 0 and no error occurred.
179
+
180
+ ```typescript
181
+ type ExecSuccess = {
182
+ all: string; // combined stdout + stderr
183
+ command: string;
184
+ durationMs: number;
185
+ exitCode: number;
186
+ stdout: string;
187
+ stderr: string;
188
+ };
189
+ ```
190
+
191
+ ---
192
+
193
+ #### `ExecFailure`
194
+
195
+ Returned when the process errors, exits non-zero, or gets killed.
196
+
197
+ ```typescript
198
+ type ExecFailure = {
199
+ all?: string;
200
+ canceled: boolean; // true if aborted via signal
201
+ command: string;
202
+ durationMs: number;
203
+ error?: Error;
204
+ exitCode?: number;
205
+ signalCode?: string;
206
+ stderr?: string;
207
+ stdout?: string;
208
+ };
209
+ ```
210
+
211
+ ---
212
+
213
+ #### `Subprocess`
214
+
215
+ The shape returned by `exec()`. It is:
216
+
217
+ - A **promise-like** — resolves to `ExecResult` when awaited.
218
+ - An **async iterable** — yields lines from stdout + stderr.
219
+ - A **live handle** — inspect `.exitCode`, `.pid`, etc. at any time after spawn.
220
+
221
+ ```typescript
222
+ type Subprocess = AsyncIterable<string> & PromiseLike<ExecResult> & { ... };
223
+ ```
224
+
225
+ ---
226
+
227
+ #### `Options`
228
+
229
+ Extended `SpawnOptions` with extra fields:
230
+
231
+ | Field | Type | Default | Description |
232
+ | ------------------- | ------------------------------------- | -------------------- | ------------------------------------- |
233
+ | `cwd` | `string` | `process.cwd()` | Working directory |
234
+ | `env` | `Record<string, string>` | inherited + computed | Environment variables |
235
+ | `localDir` | `string` | `cwd` | Directory to resolve local paths from |
236
+ | `preferLocal` | `boolean` | `false` | Prepends `node_modules/.bin` to PATH |
237
+ | `signal` | `AbortSignal` | none | Abort signal for cancellation |
238
+ | `stdin` | `string \| Subprocess \| { process }` | `pipe` | Input source |
239
+ | `stdio` | | none | Standard I/O configurations. |
240
+ | `stdout` / `stderr` | `StdioOption` | none | Stream configuration |
241
+ | `timeout` | `number` | none | Auto-abort after N milliseconds |
242
+
243
+ Contributions
244
+ -------------
245
+
246
+ Contributions are most welcome. Please:
247
+
248
+ - Open issues and feature requests under the repository discussions.
249
+ - Follow the [`CONTRIBUTING.md`](./CONTRIBUTING.md).
250
+
251
+ Donations
252
+ ---------
253
+
254
+ Support this project by becoming a financial contributor.
255
+
256
+ <table>
257
+ <tr>
258
+ <td><img src="https://raw.githubusercontent.com/daiyam/assets/master/icons/256/funding_kofi.png" alt="Ko-fi" width="80px" height="80px"></td>
259
+ <td><a href="https://ko-fi.com/daiyam" target="_blank">ko-fi.com/daiyam</a></td>
260
+ </tr>
261
+ <tr>
262
+ <td><img src="https://raw.githubusercontent.com/daiyam/assets/master/icons/256/funding_liberapay.png" alt="Liberapay" width="80px" height="80px"></td>
263
+ <td><a href="https://liberapay.com/daiyam/donate" target="_blank">liberapay.com/daiyam/donate</a></td>
264
+ </tr>
265
+ <tr>
266
+ <td><img src="https://raw.githubusercontent.com/daiyam/assets/master/icons/256/funding_paypal.png" alt="PayPal" width="80px" height="80px"></td>
267
+ <td><a href="https://paypal.me/daiyam99" target="_blank">paypal.me/daiyam99</a></td>
268
+ </tr>
269
+ </table>
270
+
271
+ License
272
+ -------
273
+
274
+ Copyright &copy; 2026-present Baptiste Augrain
275
+
276
+ Licensed under the [MIT license](https://opensource.org/licenses/MIT).
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.combineSignals = combineSignals;
4
+ function combineSignals(signals) {
5
+ const controller = new AbortController();
6
+ for (const signal of signals) {
7
+ if (signal.aborted) {
8
+ controller.abort();
9
+ return signal;
10
+ }
11
+ const onAbort = () => {
12
+ controller.abort(signal.reason);
13
+ };
14
+ signal.addEventListener('abort', onAbort, {
15
+ signal: controller.signal,
16
+ });
17
+ }
18
+ return controller.signal;
19
+ }
@@ -0,0 +1 @@
1
+ export declare function combineSignals(signals: AbortSignal[]): AbortSignal;
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ExecProcess = void 0;
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_readline_1 = __importDefault(require("node:readline"));
9
+ const xtry_1 = require("@zokugun/xtry");
10
+ const exec_js_1 = require("./exec.cjs");
11
+ const normalizeCommand_js_1 = require("./utils/command/normalizeCommand.cjs");
12
+ const normalizeOptions_js_1 = require("./utils/normalizeOptions.cjs");
13
+ const combineStreams_js_1 = require("./utils/stream/combineStreams.cjs");
14
+ const readStream_js_1 = require("./utils/stream/readStream.cjs");
15
+ class ExecProcess {
16
+ _aborted = false;
17
+ _args;
18
+ _command;
19
+ _failed = false;
20
+ _options;
21
+ _process;
22
+ _processClosed;
23
+ _resolveClose;
24
+ _signal;
25
+ _start;
26
+ _streamErr;
27
+ _streamOut;
28
+ _thrownError;
29
+ constructor(command, args, options) {
30
+ this._options = {
31
+ timeout: undefined,
32
+ windowsHide: true,
33
+ ...options,
34
+ };
35
+ this._command = command;
36
+ this._args = args ?? [];
37
+ this._processClosed = new Promise((resolve) => {
38
+ this._resolveClose = resolve;
39
+ });
40
+ } // }}}
41
+ get aborted() {
42
+ return this._aborted;
43
+ } // }}}
44
+ get error() {
45
+ return this._thrownError;
46
+ } // }}}
47
+ get exitCode() {
48
+ return this._process?.exitCode ?? undefined;
49
+ } // }}}
50
+ get failed() {
51
+ return this._failed;
52
+ } // }}}
53
+ get killed() {
54
+ return this._process?.killed === true;
55
+ } // }}}
56
+ get pid() {
57
+ return this._process?.pid;
58
+ } // }}}
59
+ get process() {
60
+ return this._process;
61
+ } // }}}
62
+ get signalCode() {
63
+ return this._process?.signalCode ?? undefined;
64
+ } // }}}
65
+ kill(signal) {
66
+ return this._process?.kill(signal) === true;
67
+ } // }}}
68
+ pipe(command, args, options) {
69
+ return (0, exec_js_1.exec)(command, args, {
70
+ ...options,
71
+ stdin: this,
72
+ });
73
+ } // }}}
74
+ spawn() {
75
+ this._start = process.hrtime.bigint();
76
+ this.reset();
77
+ const normalizedOptions = (0, normalizeOptions_js_1.normalizeOptions)(this._options);
78
+ if (normalizedOptions.signal) {
79
+ this._signal = normalizedOptions.signal;
80
+ }
81
+ const { command: spawnCommand, args: spawnArgs, options: spawnOptions } = (0, normalizeCommand_js_1.normalizeCommand)(this._command, this._args, normalizedOptions);
82
+ let task;
83
+ try {
84
+ task = (0, node_child_process_1.spawn)(spawnCommand, spawnArgs, spawnOptions);
85
+ }
86
+ catch (error) {
87
+ if (error instanceof Error) {
88
+ this._thrownError = error;
89
+ }
90
+ else {
91
+ this._thrownError = new Error(String(error));
92
+ }
93
+ return;
94
+ }
95
+ if (task.stderr) {
96
+ this._streamErr = task.stderr;
97
+ }
98
+ if (task.stdout) {
99
+ this._streamOut = task.stdout;
100
+ }
101
+ this._process = task;
102
+ task.once('error', this.onError.bind(this));
103
+ task.once('close', this.onClose.bind(this));
104
+ if (task.stdin) {
105
+ const { stdin } = this._options;
106
+ if (typeof stdin === 'string') {
107
+ task.stdin.end(stdin);
108
+ }
109
+ else if (typeof stdin === 'object' && stdin !== null && 'process' in stdin) {
110
+ stdin.process?.stdout?.pipe(task.stdin);
111
+ }
112
+ }
113
+ } // }}}
114
+ // eslint-disable-next-line ts/explicit-member-accessibility
115
+ async *[Symbol.asyncIterator]() {
116
+ const handle = this._process;
117
+ if (!handle) {
118
+ return;
119
+ }
120
+ const streams = [];
121
+ if (this._streamErr) {
122
+ streams.push(this._streamErr);
123
+ }
124
+ if (this._streamOut) {
125
+ streams.push(this._streamOut);
126
+ }
127
+ const streamCombined = (0, combineStreams_js_1.combineStreams)(streams);
128
+ const rl = node_readline_1.default.createInterface({ input: streamCombined });
129
+ for await (const chunk of rl) {
130
+ // eslint-disable-next-line ts/no-unnecessary-type-conversion
131
+ yield chunk.toString();
132
+ }
133
+ await this._processClosed;
134
+ handle.removeAllListeners();
135
+ if (this._thrownError || (this.exitCode !== 0 && this.exitCode !== undefined) || this.signalCode !== undefined) {
136
+ this._failed = true;
137
+ }
138
+ } // }}}
139
+ // eslint-disable-next-line ts/promise-function-async
140
+ then(onfulfilled, onrejected) {
141
+ return this.toResult().then(onfulfilled, onrejected);
142
+ } // }}}
143
+ onClose() {
144
+ if (this._resolveClose) {
145
+ this._resolveClose();
146
+ }
147
+ } // }}}
148
+ onError(error) {
149
+ if (error.name === 'AbortError' && (!(error.cause instanceof Error) || error.cause.name !== 'TimeoutError')) {
150
+ this._aborted = true;
151
+ return;
152
+ }
153
+ this._thrownError = error;
154
+ } // }}}
155
+ reset() {
156
+ this._aborted = false;
157
+ this._process = undefined;
158
+ this._processClosed = new Promise((resolve) => {
159
+ this._resolveClose = resolve;
160
+ });
161
+ this._signal = undefined;
162
+ this._streamErr = undefined;
163
+ this._streamOut = undefined;
164
+ this._thrownError = undefined;
165
+ } // }}}
166
+ toFailure(error) {
167
+ this._failed = true;
168
+ const result = {
169
+ all: '',
170
+ canceled: this._signal?.aborted ?? false,
171
+ command: this._command,
172
+ durationMs: Number(process.hrtime.bigint() - this._start) / 1e6,
173
+ error,
174
+ exitCode: this.exitCode,
175
+ signalCode: this.signalCode,
176
+ };
177
+ return result;
178
+ } // }}}
179
+ async toResult() {
180
+ if (this._thrownError) {
181
+ return (0, xtry_1.err)(this.toFailure(this._thrownError));
182
+ }
183
+ const task = this._process;
184
+ if (!task) {
185
+ return (0, xtry_1.err)(this.toFailure(new Error('No process was started')));
186
+ }
187
+ const context = { all: '' };
188
+ const [stdout, stderr] = await Promise.all([
189
+ this._streamOut ? (0, readStream_js_1.readStream)(this._streamOut, context) : '',
190
+ this._streamErr ? (0, readStream_js_1.readStream)(this._streamErr, context) : '',
191
+ ]);
192
+ await this._processClosed;
193
+ const { stdin } = this._options;
194
+ if (stdin && typeof stdin !== 'string') {
195
+ await stdin;
196
+ }
197
+ task.removeAllListeners();
198
+ if (this._thrownError) {
199
+ return (0, xtry_1.err)(this.toFailure(this._thrownError));
200
+ }
201
+ const result = {
202
+ all: context.all,
203
+ command: this._command,
204
+ durationMs: Number(process.hrtime.bigint() - this._start) / 1e6,
205
+ exitCode: this.exitCode,
206
+ stderr,
207
+ stdout,
208
+ };
209
+ // if(this._options.stdin instanceof ExecProcess) {
210
+ // result.pipedFrom = this._options.stdin;
211
+ // }
212
+ if ((this.exitCode !== 0 && this.exitCode !== undefined) || this.signalCode !== undefined) {
213
+ this._failed = true;
214
+ const failure = result;
215
+ failure.signalCode = this.signalCode;
216
+ return (0, xtry_1.err)(failure);
217
+ }
218
+ return (0, xtry_1.ok)(result);
219
+ } // }}}
220
+ }
221
+ exports.ExecProcess = ExecProcess;
@@ -0,0 +1,37 @@
1
+ import type { ChildProcess } from 'node:child_process';
2
+ import type { Readable } from 'node:stream';
3
+ import type { ExecFailure, ExecResult, KillSignal, Options, Subprocess } from './types.cjs';
4
+ export declare class ExecProcess implements Subprocess {
5
+ protected _aborted: boolean;
6
+ protected _args: readonly string[];
7
+ protected _command: string;
8
+ protected _failed: boolean;
9
+ protected _options: Partial<Options>;
10
+ protected _process?: ChildProcess;
11
+ protected _processClosed: Promise<void>;
12
+ protected _resolveClose?: () => void;
13
+ protected _signal?: AbortSignal;
14
+ protected _start?: bigint;
15
+ protected _streamErr?: Readable;
16
+ protected _streamOut?: Readable;
17
+ protected _thrownError?: Error;
18
+ constructor(command: string, args?: readonly string[] | null, options?: Partial<Options>);
19
+ get aborted(): boolean;
20
+ get error(): Error | undefined;
21
+ get exitCode(): number | undefined;
22
+ get failed(): boolean;
23
+ get killed(): boolean;
24
+ get pid(): number | undefined;
25
+ get process(): ChildProcess | undefined;
26
+ get signalCode(): string | undefined;
27
+ kill(signal?: KillSignal): boolean;
28
+ pipe(command: string, args?: readonly string[], options?: Partial<Options>): Subprocess;
29
+ spawn(): void;
30
+ [Symbol.asyncIterator](): AsyncIterator<string>;
31
+ then<TResult1 = ExecResult, TResult2 = never>(onfulfilled?: ((value: ExecResult) => PromiseLike<TResult1> | TResult1) | null, onrejected?: ((reason: unknown) => PromiseLike<TResult2> | TResult2) | null): Promise<TResult1 | TResult2>;
32
+ protected onClose(): void;
33
+ protected onError(error: Error): void;
34
+ protected reset(): void;
35
+ protected toFailure(error: Error): ExecFailure;
36
+ protected toResult(): Promise<ExecResult>;
37
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exec = exec;
4
+ const exec_process_js_1 = require("./exec-process.cjs");
5
+ function exec(command, args, options) {
6
+ const subprocess = new exec_process_js_1.ExecProcess(command, args, options);
7
+ subprocess.spawn();
8
+ return subprocess;
9
+ }
@@ -0,0 +1,2 @@
1
+ import type { Options, Subprocess } from './types.cjs';
2
+ export declare function exec(command: string, args?: readonly string[] | null, options?: Partial<Options>): Subprocess;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exec = exports.ExecProcess = void 0;
4
+ var exec_process_js_1 = require("./exec-process.cjs");
5
+ Object.defineProperty(exports, "ExecProcess", { enumerable: true, get: function () { return exec_process_js_1.ExecProcess; } });
6
+ var exec_js_1 = require("./exec.cjs");
7
+ Object.defineProperty(exports, "exec", { enumerable: true, get: function () { return exec_js_1.exec; } });
@@ -0,0 +1,3 @@
1
+ export { ExecProcess } from './exec-process.cjs';
2
+ export { exec } from './exec.cjs';
3
+ export { ExecFailure, ExecResult, ExecSuccess, KillSignal, Options, StdinOption, StdioOption, Subprocess, } from './types.cjs';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,56 @@
1
+ import type { ChildProcess, SpawnOptions as ProcessSpawnOptions } from 'node:child_process';
2
+ import type { Result } from '@zokugun/xtry';
3
+ export type ExecFailure = {
4
+ all?: string;
5
+ canceled: boolean;
6
+ command: string;
7
+ durationMs: number;
8
+ error?: Error;
9
+ exitCode?: number;
10
+ pipedFrom?: ExecResult;
11
+ signalCode?: string;
12
+ stderr?: string;
13
+ stdout?: string;
14
+ };
15
+ export type ExecResult = Result<ExecSuccess, ExecFailure>;
16
+ export type ExecSuccess = {
17
+ all: string;
18
+ command: string;
19
+ durationMs: number;
20
+ error?: undefined;
21
+ exitCode: number;
22
+ pipedFrom?: ExecResult;
23
+ stderr: string;
24
+ stdout: string;
25
+ };
26
+ export type KillSignal = Parameters<ChildProcess['kill']>[0];
27
+ export type Options = Omit<ProcessSpawnOptions, 'cwd' | 'env' | 'stdio'> & Readonly<Partial<{
28
+ cwd?: string;
29
+ env: Readonly<Partial<Record<string, string>>>;
30
+ localDir: string;
31
+ preferLocal: boolean;
32
+ signal: AbortSignal;
33
+ stderr: StdioOption;
34
+ stdin: ExecResult | StdinOption | Subprocess;
35
+ stdio: readonly [StdinOption, ...readonly StdioOption[]] | SpawnOptions['stdio'];
36
+ stdout: StdioOption;
37
+ }>>;
38
+ export type SpawnOptions = {
39
+ cwd: string;
40
+ } & Omit<ProcessSpawnOptions, 'cwd'>;
41
+ export type StdinOption = StdioOption | {
42
+ readonly string?: string;
43
+ };
44
+ export type StdioOption = Readonly<Exclude<SpawnOptions['stdio'], undefined>[number]>;
45
+ export type Subprocess = AsyncIterable<string> & PromiseLike<ExecResult> & {
46
+ get aborted(): boolean;
47
+ get error(): Error | undefined;
48
+ get exitCode(): number | undefined;
49
+ get failed(): boolean;
50
+ kill: (signal?: KillSignal) => boolean;
51
+ get killed(): boolean;
52
+ get pid(): number | undefined;
53
+ pipe: (command: string, args?: readonly string[], options?: Partial<Options>) => Subprocess;
54
+ get process(): ChildProcess | undefined;
55
+ get signalCode(): string | undefined;
56
+ };