@zokugun/exec-lite 0.0.0 → 0.3.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 +306 -34
- package/lib/cjs/create-command.cjs +19 -0
- package/lib/cjs/create-command.d.cts +3 -0
- package/lib/cjs/exec-process.cjs +241 -0
- package/lib/cjs/exec-process.d.cts +41 -0
- package/lib/cjs/exec.cjs +9 -0
- package/lib/cjs/exec.d.cts +3 -0
- package/lib/cjs/index.cjs +9 -0
- package/lib/cjs/index.d.cts +4 -0
- package/lib/cjs/types.cjs +2 -0
- package/lib/cjs/types.d.cts +70 -0
- package/lib/cjs/utils/combineSignals.cjs +19 -0
- package/lib/cjs/utils/combineSignals.d.cts +1 -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/create-command.d.mts +3 -0
- package/lib/esm/create-command.mjs +17 -0
- package/lib/esm/exec-process.d.mts +41 -0
- package/lib/esm/exec-process.mjs +235 -0
- package/lib/esm/exec.d.mts +3 -0
- package/lib/esm/exec.mjs +7 -0
- package/lib/esm/index.d.mts +4 -0
- package/lib/esm/index.mjs +3 -0
- package/lib/esm/types.d.mts +70 -0
- package/lib/esm/types.mjs +1 -0
- package/lib/esm/utils/combineSignals.d.mts +1 -0
- package/lib/esm/utils/combineSignals.mjs +16 -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 +145 -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,324 @@
|
|
|
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
|
|
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
|
+
#### `createCommand`
|
|
10
130
|
|
|
11
|
-
|
|
131
|
+
```typescript
|
|
132
|
+
import { createCommand } from '@zokugun/exec-lite';
|
|
12
133
|
|
|
13
|
-
|
|
14
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
15
|
-
3. Establish provenance for packages published under this name
|
|
134
|
+
const git = createCommand('git', [], { resultMode: 'merged' });
|
|
16
135
|
|
|
17
|
-
|
|
18
|
-
|
|
136
|
+
await git(['status'], { cwd: repository });
|
|
137
|
+
await git.withOptions({ cwd: repository })(['status']);
|
|
138
|
+
await git.toCwdFirst()(repository, 'status');
|
|
139
|
+
await git.toFlatArgs()('status');
|
|
140
|
+
```
|
|
19
141
|
|
|
20
|
-
|
|
142
|
+
```typescript
|
|
143
|
+
type Command = {
|
|
144
|
+
(args?: readonly string[] | null, options?: Partial<Options>): Subprocess;
|
|
145
|
+
toCwdFirst(): (cwd: string, ...args: string[]) => Subprocess;
|
|
146
|
+
toFlatArgs(): (...args: string[]) => Subprocess;
|
|
147
|
+
withOptions(options: Partial<Options>): Command;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
21
150
|
|
|
22
|
-
|
|
23
|
-
------------------
|
|
151
|
+
Creates a reusable command with optional default arguments and options.
|
|
24
152
|
|
|
25
|
-
|
|
153
|
+
### Merged Result Mode
|
|
26
154
|
|
|
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
|
|
155
|
+
Set `resultMode: 'merged'` to make the awaited subprocess resolve directly to a string containing combined stdout and stderr, rather than the default `Result` object:
|
|
31
156
|
|
|
32
|
-
|
|
33
|
-
|
|
157
|
+
```typescript
|
|
158
|
+
const task = exec('git', ['status'], { resultMode: 'merged' });
|
|
159
|
+
const output = await task;
|
|
34
160
|
|
|
35
|
-
|
|
161
|
+
console.log(output);
|
|
36
162
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
163
|
+
if(task.failed) {
|
|
164
|
+
console.error(`Command failed with exit code: ${task.exitCode}`);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
41
167
|
|
|
42
|
-
|
|
43
|
-
----------------
|
|
168
|
+
The subprocess does not reject on a non-zero exit. Check `.failed` and, when needed, `.exitCode` or `.error` for process status and errors.
|
|
44
169
|
|
|
45
|
-
|
|
170
|
+
---
|
|
46
171
|
|
|
47
|
-
|
|
48
|
-
|
|
172
|
+
### Classes
|
|
173
|
+
|
|
174
|
+
#### `ExecProcess`
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
class ExecProcess implements Subprocess { ... }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The class behind every subprocess returned by `exec`. You rarely need to construct it directly — use `exec()` instead.
|
|
181
|
+
|
|
182
|
+
**Key properties:**
|
|
183
|
+
|
|
184
|
+
| Property | Type | Description |
|
|
185
|
+
| ------------- | --------------------------- | ------------------------------------------------------ |
|
|
186
|
+
| `.aborted` | `boolean` | True if the process was aborted via signal |
|
|
187
|
+
| `.error` | `Error \| undefined` | The error that caused failure, if any |
|
|
188
|
+
| `.exitCode` | `number \| undefined` | Exit code after the process closes |
|
|
189
|
+
| `.failed` | `boolean` | True if exit code is non-zero or a signal was received |
|
|
190
|
+
| `.killed` | `boolean` | True if `kill()` was called |
|
|
191
|
+
| `.pid` | `number \| undefined` | OS process ID |
|
|
192
|
+
| `.process` | `ChildProcess \| undefined` | The raw Node.js child process handle |
|
|
193
|
+
| `.signalCode` | `string \| undefined` | Signal code that terminated the process |
|
|
194
|
+
|
|
195
|
+
**Key methods:**
|
|
196
|
+
|
|
197
|
+
| Method | Returns | Description |
|
|
198
|
+
| --------------------------------- | ------------ | ----------------------------------------------------------------- |
|
|
199
|
+
| `.kill(signal?)` | `boolean` | Sends a signal to the process. Returns false if no handle exists. |
|
|
200
|
+
| `.pipe(command, args?, options?)` | `Subprocess` | Chains another command, piping this stdout into its stdin. |
|
|
201
|
+
| `.spawn()` | `void` | Starts the process. Called automatically by `exec()`. |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### Types
|
|
206
|
+
|
|
207
|
+
#### `ExecResult`
|
|
208
|
+
|
|
209
|
+
A structured success or failure result by default. In merged mode, this resolves directly to a string:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
type ResultMode = 'result' | 'merged';
|
|
213
|
+
type ExecResult<M extends ResultMode = 'result'> = M extends 'merged'
|
|
214
|
+
? string
|
|
215
|
+
: Result<ExecSuccess, ExecFailure>;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
#### `ExecSuccess`
|
|
221
|
+
|
|
222
|
+
Returned when the process exits with code 0 and no error occurred.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
type ExecSuccess = {
|
|
226
|
+
args: readonly string[];
|
|
227
|
+
command: string;
|
|
228
|
+
cwd: string;
|
|
229
|
+
durationMs: number;
|
|
230
|
+
exitCode: number;
|
|
231
|
+
merged: string; // combined stdout + stderr
|
|
232
|
+
stdout: string;
|
|
233
|
+
stderr: string;
|
|
234
|
+
};
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
#### `ExecFailure`
|
|
240
|
+
|
|
241
|
+
Returned when the process errors, exits non-zero, or gets killed.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
type ExecFailure = {
|
|
245
|
+
args: readonly string[];
|
|
246
|
+
canceled: boolean; // true if aborted via signal
|
|
247
|
+
command: string;
|
|
248
|
+
cwd?: string;
|
|
249
|
+
durationMs: number;
|
|
250
|
+
error?: Error;
|
|
251
|
+
exitCode?: number;
|
|
252
|
+
merged?: string;
|
|
253
|
+
signalCode?: string;
|
|
254
|
+
stderr?: string;
|
|
255
|
+
stdout?: string;
|
|
256
|
+
};
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
#### `Subprocess`
|
|
262
|
+
|
|
263
|
+
The shape returned by `exec()`. It is:
|
|
264
|
+
|
|
265
|
+
- A **promise-like** — resolves to `ExecResult` when awaited.
|
|
266
|
+
- An **async iterable** — yields lines from stdout + stderr.
|
|
267
|
+
- A **live handle** — inspect `.exitCode`, `.pid`, etc. at any time after spawn.
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
type Subprocess = AsyncIterable<string> & PromiseLike<ExecResult> & { ... };
|
|
271
|
+
```
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
#### `Options`
|
|
275
|
+
|
|
276
|
+
Extended `SpawnOptions` with extra fields:
|
|
277
|
+
|
|
278
|
+
| Field | Type | Default | Description |
|
|
279
|
+
| ------------------- | ------------------------------------- | -------------------- | ---------------------------------------------------- |
|
|
280
|
+
| `cwd` | `string` | `process.cwd()` | Working directory |
|
|
281
|
+
| `env` | `Record<string, string>` | inherited + computed | Environment variables |
|
|
282
|
+
| `localDir` | `string` | `cwd` | Directory to resolve local paths from |
|
|
283
|
+
| `preferLocal` | `boolean` | `false` | Prepends `node_modules/.bin` to PATH |
|
|
284
|
+
| `resultMode` | `'result' \| 'merged'` | `'result'` | Resolve to a structured result or merged output text |
|
|
285
|
+
| `signal` | `AbortSignal` | none | Abort signal for cancellation |
|
|
286
|
+
| `stdin` | `string \| Subprocess \| { process }` | `'pipe'` | Input source |
|
|
287
|
+
| `stdio` | | none | Standard I/O configurations. |
|
|
288
|
+
| `stdout` / `stderr` | `StdioOption` | none | Stream configuration |
|
|
289
|
+
| `timeout` | `number` | none | Auto-abort after N milliseconds |
|
|
290
|
+
|
|
291
|
+
Contributions
|
|
292
|
+
-------------
|
|
293
|
+
|
|
294
|
+
Contributions are most welcome. Please:
|
|
295
|
+
|
|
296
|
+
- Open issues and feature requests under the repository discussions.
|
|
297
|
+
- Follow the [`CONTRIBUTING.md`](./CONTRIBUTING.md).
|
|
298
|
+
|
|
299
|
+
Donations
|
|
300
|
+
---------
|
|
301
|
+
|
|
302
|
+
Support this project by becoming a financial contributor.
|
|
303
|
+
|
|
304
|
+
<table>
|
|
305
|
+
<tr>
|
|
306
|
+
<td><img src="https://raw.githubusercontent.com/daiyam/assets/master/icons/256/funding_kofi.png" alt="Ko-fi" width="80px" height="80px"></td>
|
|
307
|
+
<td><a href="https://ko-fi.com/daiyam" target="_blank">ko-fi.com/daiyam</a></td>
|
|
308
|
+
</tr>
|
|
309
|
+
<tr>
|
|
310
|
+
<td><img src="https://raw.githubusercontent.com/daiyam/assets/master/icons/256/funding_liberapay.png" alt="Liberapay" width="80px" height="80px"></td>
|
|
311
|
+
<td><a href="https://liberapay.com/daiyam/donate" target="_blank">liberapay.com/daiyam/donate</a></td>
|
|
312
|
+
</tr>
|
|
313
|
+
<tr>
|
|
314
|
+
<td><img src="https://raw.githubusercontent.com/daiyam/assets/master/icons/256/funding_paypal.png" alt="PayPal" width="80px" height="80px"></td>
|
|
315
|
+
<td><a href="https://paypal.me/daiyam99" target="_blank">paypal.me/daiyam99</a></td>
|
|
316
|
+
</tr>
|
|
317
|
+
</table>
|
|
318
|
+
|
|
319
|
+
License
|
|
320
|
+
-------
|
|
49
321
|
|
|
50
|
-
|
|
322
|
+
Copyright © 2026-present Baptiste Augrain
|
|
51
323
|
|
|
52
|
-
|
|
324
|
+
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.createCommand = createCommand;
|
|
4
|
+
const exec_js_1 = require("./exec.cjs");
|
|
5
|
+
function createCommand(command, args = [], options = {}) {
|
|
6
|
+
const create = (options) => {
|
|
7
|
+
const run = ((runArgs, runOptions) => {
|
|
8
|
+
return (0, exec_js_1.exec)(command, [...(args ?? []), ...(runArgs ?? [])], {
|
|
9
|
+
...options,
|
|
10
|
+
...runOptions,
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
run.withOptions = (overrides) => create({ ...options, ...overrides });
|
|
14
|
+
run.toCwdFirst = () => ((cwd, ...runArgs) => run(runArgs, { cwd }));
|
|
15
|
+
run.toFlatArgs = () => ((...runArgs) => run(runArgs));
|
|
16
|
+
return run;
|
|
17
|
+
};
|
|
18
|
+
return create(options);
|
|
19
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Command, Options, ResultMode, Subprocess } from './types.cjs';
|
|
2
|
+
declare function createCommand<M extends ResultMode = 'result'>(command: string, args?: readonly string[] | null, options?: Partial<Options<M>>): Command<M>;
|
|
3
|
+
export { type Command, type Options, type ResultMode, type Subprocess, createCommand, };
|
|
@@ -0,0 +1,241 @@
|
|
|
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
|
+
_cwd;
|
|
20
|
+
_failed = false;
|
|
21
|
+
_options;
|
|
22
|
+
_process;
|
|
23
|
+
_processClosed;
|
|
24
|
+
_resolveClose;
|
|
25
|
+
_signal;
|
|
26
|
+
_start;
|
|
27
|
+
_streamErr;
|
|
28
|
+
_streamOut;
|
|
29
|
+
_thrownError;
|
|
30
|
+
constructor(command, args, options) {
|
|
31
|
+
this._options = {
|
|
32
|
+
timeout: undefined,
|
|
33
|
+
windowsHide: true,
|
|
34
|
+
...options,
|
|
35
|
+
};
|
|
36
|
+
this._command = command;
|
|
37
|
+
this._args = args ?? [];
|
|
38
|
+
this._processClosed = new Promise((resolve) => {
|
|
39
|
+
this._resolveClose = resolve;
|
|
40
|
+
});
|
|
41
|
+
} // }}}
|
|
42
|
+
get aborted() {
|
|
43
|
+
return this._aborted;
|
|
44
|
+
} // }}}
|
|
45
|
+
get error() {
|
|
46
|
+
return this._thrownError;
|
|
47
|
+
} // }}}
|
|
48
|
+
get exitCode() {
|
|
49
|
+
return this._process?.exitCode ?? undefined;
|
|
50
|
+
} // }}}
|
|
51
|
+
get failed() {
|
|
52
|
+
return this._failed;
|
|
53
|
+
} // }}}
|
|
54
|
+
get killed() {
|
|
55
|
+
return this._process?.killed === true;
|
|
56
|
+
} // }}}
|
|
57
|
+
get pid() {
|
|
58
|
+
return this._process?.pid;
|
|
59
|
+
} // }}}
|
|
60
|
+
get process() {
|
|
61
|
+
return this._process;
|
|
62
|
+
} // }}}
|
|
63
|
+
get signalCode() {
|
|
64
|
+
return this._process?.signalCode ?? undefined;
|
|
65
|
+
} // }}}
|
|
66
|
+
kill(signal) {
|
|
67
|
+
return this._process?.kill(signal) === true;
|
|
68
|
+
} // }}}
|
|
69
|
+
pipe(command, args, options) {
|
|
70
|
+
return (0, exec_js_1.exec)(command, args, {
|
|
71
|
+
...options,
|
|
72
|
+
stdin: this,
|
|
73
|
+
});
|
|
74
|
+
} // }}}
|
|
75
|
+
spawn() {
|
|
76
|
+
this._start = process.hrtime.bigint();
|
|
77
|
+
this.reset();
|
|
78
|
+
const normalizedOptions = (0, normalizeOptions_js_1.normalizeOptions)(this._options);
|
|
79
|
+
this._cwd = normalizedOptions.cwd;
|
|
80
|
+
if (normalizedOptions.signal) {
|
|
81
|
+
this._signal = normalizedOptions.signal;
|
|
82
|
+
}
|
|
83
|
+
const { command: spawnCommand, args: spawnArgs, options: spawnOptions } = (0, normalizeCommand_js_1.normalizeCommand)(this._command, this._args, normalizedOptions);
|
|
84
|
+
let task;
|
|
85
|
+
try {
|
|
86
|
+
task = (0, node_child_process_1.spawn)(spawnCommand, spawnArgs, spawnOptions);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof Error) {
|
|
90
|
+
this._thrownError = error;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
this._thrownError = new Error(String(error));
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (task.stderr) {
|
|
98
|
+
this._streamErr = task.stderr;
|
|
99
|
+
}
|
|
100
|
+
if (task.stdout) {
|
|
101
|
+
this._streamOut = task.stdout;
|
|
102
|
+
}
|
|
103
|
+
this._process = task;
|
|
104
|
+
task.once('error', this.onError.bind(this));
|
|
105
|
+
task.once('close', this.onClose.bind(this));
|
|
106
|
+
if (task.stdin) {
|
|
107
|
+
const { stdin } = this._options;
|
|
108
|
+
if (typeof stdin === 'string') {
|
|
109
|
+
task.stdin.end(stdin);
|
|
110
|
+
}
|
|
111
|
+
else if (typeof stdin === 'object' && stdin !== null && 'process' in stdin) {
|
|
112
|
+
stdin.process?.stdout?.pipe(task.stdin);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} // }}}
|
|
116
|
+
// eslint-disable-next-line ts/explicit-member-accessibility
|
|
117
|
+
async *[Symbol.asyncIterator]() {
|
|
118
|
+
const handle = this._process;
|
|
119
|
+
if (!handle) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const streams = [];
|
|
123
|
+
if (this._streamErr) {
|
|
124
|
+
streams.push(this._streamErr);
|
|
125
|
+
}
|
|
126
|
+
if (this._streamOut) {
|
|
127
|
+
streams.push(this._streamOut);
|
|
128
|
+
}
|
|
129
|
+
const streamCombined = (0, combineStreams_js_1.combineStreams)(streams);
|
|
130
|
+
const rl = node_readline_1.default.createInterface({ input: streamCombined });
|
|
131
|
+
for await (const chunk of rl) {
|
|
132
|
+
// eslint-disable-next-line ts/no-unnecessary-type-conversion
|
|
133
|
+
yield chunk.toString();
|
|
134
|
+
}
|
|
135
|
+
await this._processClosed;
|
|
136
|
+
handle.removeAllListeners();
|
|
137
|
+
if (this._thrownError || (this.exitCode !== 0 && this.exitCode !== undefined) || this.signalCode !== undefined) {
|
|
138
|
+
this._failed = true;
|
|
139
|
+
}
|
|
140
|
+
} // }}}
|
|
141
|
+
// eslint-disable-next-line ts/promise-function-async
|
|
142
|
+
then(onfulfilled, onrejected) {
|
|
143
|
+
return this.toResult().then((result) => {
|
|
144
|
+
if (onfulfilled) {
|
|
145
|
+
return onfulfilled(result);
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}, onrejected);
|
|
149
|
+
} // }}}
|
|
150
|
+
onClose() {
|
|
151
|
+
if (this._resolveClose) {
|
|
152
|
+
this._resolveClose();
|
|
153
|
+
}
|
|
154
|
+
} // }}}
|
|
155
|
+
onError(error) {
|
|
156
|
+
if (error.name === 'AbortError' && (!(error.cause instanceof Error) || error.cause.name !== 'TimeoutError')) {
|
|
157
|
+
this._aborted = true;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this._thrownError = error;
|
|
161
|
+
} // }}}
|
|
162
|
+
reset() {
|
|
163
|
+
this._aborted = false;
|
|
164
|
+
this._process = undefined;
|
|
165
|
+
this._processClosed = new Promise((resolve) => {
|
|
166
|
+
this._resolveClose = resolve;
|
|
167
|
+
});
|
|
168
|
+
this._signal = undefined;
|
|
169
|
+
this._streamErr = undefined;
|
|
170
|
+
this._streamOut = undefined;
|
|
171
|
+
this._thrownError = undefined;
|
|
172
|
+
} // }}}
|
|
173
|
+
toFailure(error) {
|
|
174
|
+
this._failed = true;
|
|
175
|
+
if (this._options.resultMode === 'merged') {
|
|
176
|
+
return (0, xtry_1.stringifyError)(error);
|
|
177
|
+
}
|
|
178
|
+
const result = {
|
|
179
|
+
args: this._args,
|
|
180
|
+
canceled: this._signal?.aborted ?? false,
|
|
181
|
+
command: this._command,
|
|
182
|
+
cwd: this._cwd,
|
|
183
|
+
durationMs: Number(process.hrtime.bigint() - this._start) / 1e6,
|
|
184
|
+
error,
|
|
185
|
+
exitCode: this.exitCode,
|
|
186
|
+
merged: '',
|
|
187
|
+
signalCode: this.signalCode,
|
|
188
|
+
};
|
|
189
|
+
return (0, xtry_1.err)(result);
|
|
190
|
+
} // }}}
|
|
191
|
+
async toResult() {
|
|
192
|
+
if (this._thrownError) {
|
|
193
|
+
return this.toFailure(this._thrownError);
|
|
194
|
+
}
|
|
195
|
+
const task = this._process;
|
|
196
|
+
if (!task) {
|
|
197
|
+
return this.toFailure(new Error('No process was started'));
|
|
198
|
+
}
|
|
199
|
+
const context = { merged: '' };
|
|
200
|
+
const [stdout, stderr] = await Promise.all([
|
|
201
|
+
this._streamOut ? (0, readStream_js_1.readStream)(this._streamOut, context) : '',
|
|
202
|
+
this._streamErr ? (0, readStream_js_1.readStream)(this._streamErr, context) : '',
|
|
203
|
+
]);
|
|
204
|
+
await this._processClosed;
|
|
205
|
+
const { stdin } = this._options;
|
|
206
|
+
if (stdin && typeof stdin !== 'string') {
|
|
207
|
+
await stdin;
|
|
208
|
+
}
|
|
209
|
+
task.removeAllListeners();
|
|
210
|
+
if (this._thrownError) {
|
|
211
|
+
return this.toFailure(this._thrownError);
|
|
212
|
+
}
|
|
213
|
+
if (this._options.resultMode === 'merged') {
|
|
214
|
+
if ((this.exitCode !== 0 && this.exitCode !== undefined) || this.signalCode !== undefined) {
|
|
215
|
+
this._failed = true;
|
|
216
|
+
}
|
|
217
|
+
return context.merged;
|
|
218
|
+
}
|
|
219
|
+
const result = {
|
|
220
|
+
args: this._args,
|
|
221
|
+
command: this._command,
|
|
222
|
+
cwd: this._cwd,
|
|
223
|
+
durationMs: Number(process.hrtime.bigint() - this._start) / 1e6,
|
|
224
|
+
exitCode: this.exitCode,
|
|
225
|
+
merged: context.merged,
|
|
226
|
+
stderr,
|
|
227
|
+
stdout,
|
|
228
|
+
};
|
|
229
|
+
// if(this._options.stdin instanceof ExecProcess) {
|
|
230
|
+
// result.pipedFrom = this._options.stdin;
|
|
231
|
+
// }
|
|
232
|
+
if ((this.exitCode !== 0 && this.exitCode !== undefined) || this.signalCode !== undefined) {
|
|
233
|
+
this._failed = true;
|
|
234
|
+
const failure = result;
|
|
235
|
+
failure.signalCode = this.signalCode;
|
|
236
|
+
return (0, xtry_1.err)(failure);
|
|
237
|
+
}
|
|
238
|
+
return (0, xtry_1.ok)(result);
|
|
239
|
+
} // }}}
|
|
240
|
+
}
|
|
241
|
+
exports.ExecProcess = ExecProcess;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
import type { Readable } from 'node:stream';
|
|
3
|
+
import type { ExecFailure, ExecResult, ExecSuccess, KillSignal, Options, ResultMode, Subprocess } from './types.cjs';
|
|
4
|
+
declare class ExecProcess implements Subprocess<ResultMode> {
|
|
5
|
+
protected _aborted: boolean;
|
|
6
|
+
protected _args: readonly string[];
|
|
7
|
+
protected _command: string;
|
|
8
|
+
protected _cwd: string | undefined;
|
|
9
|
+
protected _failed: boolean;
|
|
10
|
+
protected _options: Partial<Options>;
|
|
11
|
+
protected _process?: ChildProcess;
|
|
12
|
+
protected _processClosed: Promise<void>;
|
|
13
|
+
protected _resolveClose?: () => void;
|
|
14
|
+
protected _signal?: AbortSignal;
|
|
15
|
+
protected _start?: bigint;
|
|
16
|
+
protected _streamErr?: Readable;
|
|
17
|
+
protected _streamOut?: Readable;
|
|
18
|
+
protected _thrownError?: Error;
|
|
19
|
+
constructor(command: string, args?: readonly string[] | null, options?: Partial<Options>);
|
|
20
|
+
get aborted(): boolean;
|
|
21
|
+
get error(): Error | undefined;
|
|
22
|
+
get exitCode(): number | undefined;
|
|
23
|
+
get failed(): boolean;
|
|
24
|
+
get killed(): boolean;
|
|
25
|
+
get pid(): number | undefined;
|
|
26
|
+
get process(): ChildProcess | undefined;
|
|
27
|
+
get signalCode(): string | undefined;
|
|
28
|
+
kill(signal?: KillSignal): boolean;
|
|
29
|
+
pipe<N extends ResultMode = 'result'>(command: string, args?: readonly string[], options?: Partial<Options> & {
|
|
30
|
+
resultMode?: N;
|
|
31
|
+
}): Subprocess<N>;
|
|
32
|
+
spawn(): void;
|
|
33
|
+
[Symbol.asyncIterator](): AsyncIterator<string>;
|
|
34
|
+
then<TResult1 = ExecResult<ResultMode>, TResult2 = never>(onfulfilled?: ((value: ExecResult<ResultMode>) => PromiseLike<TResult1> | TResult1) | null, onrejected?: ((reason: unknown) => PromiseLike<TResult2> | TResult2) | null): Promise<TResult1 | TResult2>;
|
|
35
|
+
protected onClose(): void;
|
|
36
|
+
protected onError(error: Error): void;
|
|
37
|
+
protected reset(): void;
|
|
38
|
+
protected toFailure(error: Error): ExecResult<ResultMode>;
|
|
39
|
+
protected toResult(): Promise<ExecResult<ResultMode>>;
|
|
40
|
+
}
|
|
41
|
+
export { type ExecFailure, type ExecResult, type ExecSuccess, type KillSignal, type Options, type ResultMode, type Subprocess, ExecProcess, };
|