@zokugun/exec-lite 0.0.0 → 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.
- package/LICENSE +22 -0
- package/README.md +258 -34
- package/lib/cjs/combineSignals.cjs +19 -0
- package/lib/cjs/combineSignals.d.cts +1 -0
- package/lib/cjs/exec-process.cjs +221 -0
- package/lib/cjs/exec-process.d.cts +37 -0
- package/lib/cjs/exec.cjs +9 -0
- package/lib/cjs/exec.d.cts +2 -0
- package/lib/cjs/index.cjs +7 -0
- package/lib/cjs/index.d.cts +3 -0
- package/lib/cjs/types.cjs +2 -0
- package/lib/cjs/types.d.cts +56 -0
- package/lib/cjs/utils/command/detectShebang.cjs +36 -0
- package/lib/cjs/utils/command/detectShebang.d.cts +1 -0
- package/lib/cjs/utils/command/normalizeCommand.cjs +59 -0
- package/lib/cjs/utils/command/normalizeCommand.d.cts +6 -0
- package/lib/cjs/utils/command/resolveCommand.cjs +43 -0
- package/lib/cjs/utils/command/resolveCommand.d.cts +2 -0
- package/lib/cjs/utils/env/addNodeBinToPath.cjs +24 -0
- package/lib/cjs/utils/env/addNodeBinToPath.d.cts +2 -0
- package/lib/cjs/utils/env/computeEnv.cjs +17 -0
- package/lib/cjs/utils/env/computeEnv.d.cts +2 -0
- package/lib/cjs/utils/env/getPathFromEnv.cjs +22 -0
- package/lib/cjs/utils/env/getPathFromEnv.d.cts +2 -0
- package/lib/cjs/utils/env/types.cjs +2 -0
- package/lib/cjs/utils/env/types.d.cts +5 -0
- package/lib/cjs/utils/normalizeOptions.cjs +31 -0
- package/lib/cjs/utils/normalizeOptions.d.cts +2 -0
- package/lib/cjs/utils/stream/combineStreams.cjs +25 -0
- package/lib/cjs/utils/stream/combineStreams.d.cts +2 -0
- package/lib/cjs/utils/stream/readStream.cjs +16 -0
- package/lib/cjs/utils/stream/readStream.d.cts +5 -0
- package/lib/esm/combineSignals.d.mts +1 -0
- package/lib/esm/combineSignals.mjs +16 -0
- package/lib/esm/exec-process.d.mts +37 -0
- package/lib/esm/exec-process.mjs +214 -0
- package/lib/esm/exec.d.mts +2 -0
- package/lib/esm/exec.mjs +6 -0
- package/lib/esm/index.d.mts +3 -0
- package/lib/esm/index.mjs +2 -0
- package/lib/esm/types.d.mts +56 -0
- package/lib/esm/types.mjs +1 -0
- package/lib/esm/utils/command/detectShebang.d.mts +1 -0
- package/lib/esm/utils/command/detectShebang.mjs +30 -0
- package/lib/esm/utils/command/normalizeCommand.d.mts +6 -0
- package/lib/esm/utils/command/normalizeCommand.mjs +53 -0
- package/lib/esm/utils/command/resolveCommand.d.mts +2 -0
- package/lib/esm/utils/command/resolveCommand.mjs +37 -0
- package/lib/esm/utils/env/addNodeBinToPath.d.mts +2 -0
- package/lib/esm/utils/env/addNodeBinToPath.mjs +18 -0
- package/lib/esm/utils/env/computeEnv.d.mts +2 -0
- package/lib/esm/utils/env/computeEnv.mjs +14 -0
- package/lib/esm/utils/env/getPathFromEnv.d.mts +2 -0
- package/lib/esm/utils/env/getPathFromEnv.mjs +19 -0
- package/lib/esm/utils/env/types.d.mts +5 -0
- package/lib/esm/utils/env/types.mjs +1 -0
- package/lib/esm/utils/normalizeOptions.d.mts +2 -0
- package/lib/esm/utils/normalizeOptions.mjs +25 -0
- package/lib/esm/utils/stream/combineStreams.d.mts +2 -0
- package/lib/esm/utils/stream/combineStreams.mjs +22 -0
- package/lib/esm/utils/stream/readStream.d.mts +5 -0
- package/lib/esm/utils/stream/readStream.mjs +13 -0
- package/package.json +122 -3
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,52 +1,276 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
[@zokugun/exec-lite](https://github.com/zokugun/node-exec-lite)
|
|
2
|
+
===============================================================
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/@zokugun/exec-lite)
|
|
6
|
+
[](#requirements)
|
|
7
|
+
[](#requirements)
|
|
8
|
+
[](https://ko-fi.com/daiyam)
|
|
9
|
+
[](https://liberapay.com/daiyam/donate)
|
|
10
|
+
[](https://paypal.me/daiyam99)
|
|
5
11
|
|
|
6
|
-
|
|
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.
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
10
111
|
|
|
11
|
-
|
|
112
|
+
#### `exec`
|
|
12
113
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
114
|
+
```typescript
|
|
115
|
+
export function exec(
|
|
116
|
+
command: string,
|
|
117
|
+
args?: readonly string[] | null,
|
|
118
|
+
options?: Partial<Options>,
|
|
119
|
+
): Subprocess;
|
|
120
|
+
```
|
|
16
121
|
|
|
17
|
-
|
|
18
|
-
--------------------------------
|
|
122
|
+
Spawns a child process and returns a `Subprocess` instance. The process starts immediately on return.
|
|
19
123
|
|
|
20
|
-
|
|
124
|
+
**Caveats:**
|
|
21
125
|
|
|
22
|
-
|
|
23
|
-
|
|
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.
|
|
24
128
|
|
|
25
|
-
|
|
129
|
+
---
|
|
26
130
|
|
|
27
|
-
|
|
28
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
29
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
30
|
-
4. Use the configured workflow to publish your actual package
|
|
131
|
+
### Classes
|
|
31
132
|
|
|
32
|
-
|
|
33
|
-
-----------------------
|
|
133
|
+
#### `ExecProcess`
|
|
34
134
|
|
|
35
|
-
|
|
135
|
+
```typescript
|
|
136
|
+
class ExecProcess implements Subprocess { ... }
|
|
137
|
+
```
|
|
36
138
|
|
|
37
|
-
|
|
38
|
-
- Provides no functionality
|
|
39
|
-
- Should not be installed as a dependency
|
|
40
|
-
- Exists only for administrative purposes
|
|
139
|
+
The class behind every subprocess returned by `exec`. You rarely need to construct it directly — use `exec()` instead.
|
|
41
140
|
|
|
42
|
-
|
|
43
|
-
----------------
|
|
141
|
+
**Key properties:**
|
|
44
142
|
|
|
45
|
-
|
|
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 |
|
|
46
153
|
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
-------
|
|
49
273
|
|
|
50
|
-
|
|
274
|
+
Copyright © 2026-present Baptiste Augrain
|
|
51
275
|
|
|
52
|
-
|
|
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
|
+
}
|
package/lib/cjs/exec.cjs
ADDED
|
@@ -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,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; } });
|