@zokugun/exec-lite 0.2.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/README.md +64 -16
- package/lib/cjs/create-command.cjs +19 -0
- package/lib/cjs/create-command.d.cts +3 -0
- package/lib/cjs/exec-process.cjs +28 -8
- package/lib/cjs/exec-process.d.cts +10 -6
- package/lib/cjs/exec.d.cts +3 -2
- package/lib/cjs/index.cjs +3 -1
- package/lib/cjs/index.d.cts +2 -1
- package/lib/cjs/types.d.cts +21 -7
- package/lib/cjs/utils/normalizeOptions.cjs +2 -2
- package/lib/cjs/utils/stream/readStream.cjs +1 -1
- package/lib/cjs/utils/stream/readStream.d.cts +1 -1
- package/lib/esm/create-command.d.mts +3 -0
- package/lib/esm/create-command.mjs +17 -0
- package/lib/esm/exec-process.d.mts +10 -6
- package/lib/esm/exec-process.mjs +31 -10
- package/lib/esm/exec.d.mts +3 -2
- package/lib/esm/exec.mjs +2 -1
- package/lib/esm/index.d.mts +2 -1
- package/lib/esm/index.mjs +1 -0
- package/lib/esm/types.d.mts +21 -7
- package/lib/esm/utils/normalizeOptions.mjs +2 -2
- package/lib/esm/utils/stream/readStream.d.mts +1 -1
- package/lib/esm/utils/stream/readStream.mjs +1 -1
- package/package.json +25 -2
- /package/lib/cjs/{combineSignals.cjs → utils/combineSignals.cjs} +0 -0
- /package/lib/cjs/{combineSignals.d.cts → utils/combineSignals.d.cts} +0 -0
- /package/lib/esm/{combineSignals.d.mts → utils/combineSignals.d.mts} +0 -0
- /package/lib/esm/{combineSignals.mjs → utils/combineSignals.mjs} +0 -0
package/README.md
CHANGED
|
@@ -126,6 +126,47 @@ Spawns a child process and returns a `Subprocess` instance. The process starts i
|
|
|
126
126
|
- `exec` does not throw. Errors surface through the `fails` flag of the `Result`.
|
|
127
127
|
- On Windows, batch files (`.bat`, `.cmd`) are wrapped in `cmd.exe` with verbatim argument handling.
|
|
128
128
|
|
|
129
|
+
#### `createCommand`
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { createCommand } from '@zokugun/exec-lite';
|
|
133
|
+
|
|
134
|
+
const git = createCommand('git', [], { resultMode: 'merged' });
|
|
135
|
+
|
|
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
|
+
```
|
|
141
|
+
|
|
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
|
+
```
|
|
150
|
+
|
|
151
|
+
Creates a reusable command with optional default arguments and options.
|
|
152
|
+
|
|
153
|
+
### Merged Result Mode
|
|
154
|
+
|
|
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:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const task = exec('git', ['status'], { resultMode: 'merged' });
|
|
159
|
+
const output = await task;
|
|
160
|
+
|
|
161
|
+
console.log(output);
|
|
162
|
+
|
|
163
|
+
if(task.failed) {
|
|
164
|
+
console.error(`Command failed with exit code: ${task.exitCode}`);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The subprocess does not reject on a non-zero exit. Check `.failed` and, when needed, `.exitCode` or `.error` for process status and errors.
|
|
169
|
+
|
|
129
170
|
---
|
|
130
171
|
|
|
131
172
|
### Classes
|
|
@@ -165,10 +206,13 @@ The class behind every subprocess returned by `exec`. You rarely need to constru
|
|
|
165
206
|
|
|
166
207
|
#### `ExecResult`
|
|
167
208
|
|
|
168
|
-
A
|
|
209
|
+
A structured success or failure result by default. In merged mode, this resolves directly to a string:
|
|
169
210
|
|
|
170
211
|
```typescript
|
|
171
|
-
type
|
|
212
|
+
type ResultMode = 'result' | 'merged';
|
|
213
|
+
type ExecResult<M extends ResultMode = 'result'> = M extends 'merged'
|
|
214
|
+
? string
|
|
215
|
+
: Result<ExecSuccess, ExecFailure>;
|
|
172
216
|
```
|
|
173
217
|
|
|
174
218
|
---
|
|
@@ -179,10 +223,12 @@ Returned when the process exits with code 0 and no error occurred.
|
|
|
179
223
|
|
|
180
224
|
```typescript
|
|
181
225
|
type ExecSuccess = {
|
|
182
|
-
|
|
226
|
+
args: readonly string[];
|
|
183
227
|
command: string;
|
|
228
|
+
cwd: string;
|
|
184
229
|
durationMs: number;
|
|
185
230
|
exitCode: number;
|
|
231
|
+
merged: string; // combined stdout + stderr
|
|
186
232
|
stdout: string;
|
|
187
233
|
stderr: string;
|
|
188
234
|
};
|
|
@@ -196,12 +242,14 @@ Returned when the process errors, exits non-zero, or gets killed.
|
|
|
196
242
|
|
|
197
243
|
```typescript
|
|
198
244
|
type ExecFailure = {
|
|
199
|
-
|
|
245
|
+
args: readonly string[];
|
|
200
246
|
canceled: boolean; // true if aborted via signal
|
|
201
247
|
command: string;
|
|
248
|
+
cwd?: string;
|
|
202
249
|
durationMs: number;
|
|
203
250
|
error?: Error;
|
|
204
251
|
exitCode?: number;
|
|
252
|
+
merged?: string;
|
|
205
253
|
signalCode?: string;
|
|
206
254
|
stderr?: string;
|
|
207
255
|
stdout?: string;
|
|
@@ -221,24 +269,24 @@ The shape returned by `exec()`. It is:
|
|
|
221
269
|
```typescript
|
|
222
270
|
type Subprocess = AsyncIterable<string> & PromiseLike<ExecResult> & { ... };
|
|
223
271
|
```
|
|
224
|
-
|
|
225
272
|
---
|
|
226
273
|
|
|
227
274
|
#### `Options`
|
|
228
275
|
|
|
229
276
|
Extended `SpawnOptions` with extra fields:
|
|
230
277
|
|
|
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
|
-
| `
|
|
238
|
-
| `
|
|
239
|
-
| `
|
|
240
|
-
| `
|
|
241
|
-
| `
|
|
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 |
|
|
242
290
|
|
|
243
291
|
Contributions
|
|
244
292
|
-------------
|
|
@@ -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, };
|
package/lib/cjs/exec-process.cjs
CHANGED
|
@@ -16,6 +16,7 @@ class ExecProcess {
|
|
|
16
16
|
_aborted = false;
|
|
17
17
|
_args;
|
|
18
18
|
_command;
|
|
19
|
+
_cwd;
|
|
19
20
|
_failed = false;
|
|
20
21
|
_options;
|
|
21
22
|
_process;
|
|
@@ -75,6 +76,7 @@ class ExecProcess {
|
|
|
75
76
|
this._start = process.hrtime.bigint();
|
|
76
77
|
this.reset();
|
|
77
78
|
const normalizedOptions = (0, normalizeOptions_js_1.normalizeOptions)(this._options);
|
|
79
|
+
this._cwd = normalizedOptions.cwd;
|
|
78
80
|
if (normalizedOptions.signal) {
|
|
79
81
|
this._signal = normalizedOptions.signal;
|
|
80
82
|
}
|
|
@@ -138,7 +140,12 @@ class ExecProcess {
|
|
|
138
140
|
} // }}}
|
|
139
141
|
// eslint-disable-next-line ts/promise-function-async
|
|
140
142
|
then(onfulfilled, onrejected) {
|
|
141
|
-
return this.toResult().then(
|
|
143
|
+
return this.toResult().then((result) => {
|
|
144
|
+
if (onfulfilled) {
|
|
145
|
+
return onfulfilled(result);
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}, onrejected);
|
|
142
149
|
} // }}}
|
|
143
150
|
onClose() {
|
|
144
151
|
if (this._resolveClose) {
|
|
@@ -165,26 +172,31 @@ class ExecProcess {
|
|
|
165
172
|
} // }}}
|
|
166
173
|
toFailure(error) {
|
|
167
174
|
this._failed = true;
|
|
175
|
+
if (this._options.resultMode === 'merged') {
|
|
176
|
+
return (0, xtry_1.stringifyError)(error);
|
|
177
|
+
}
|
|
168
178
|
const result = {
|
|
169
|
-
|
|
179
|
+
args: this._args,
|
|
170
180
|
canceled: this._signal?.aborted ?? false,
|
|
171
181
|
command: this._command,
|
|
182
|
+
cwd: this._cwd,
|
|
172
183
|
durationMs: Number(process.hrtime.bigint() - this._start) / 1e6,
|
|
173
184
|
error,
|
|
174
185
|
exitCode: this.exitCode,
|
|
186
|
+
merged: '',
|
|
175
187
|
signalCode: this.signalCode,
|
|
176
188
|
};
|
|
177
|
-
return result;
|
|
189
|
+
return (0, xtry_1.err)(result);
|
|
178
190
|
} // }}}
|
|
179
191
|
async toResult() {
|
|
180
192
|
if (this._thrownError) {
|
|
181
|
-
return
|
|
193
|
+
return this.toFailure(this._thrownError);
|
|
182
194
|
}
|
|
183
195
|
const task = this._process;
|
|
184
196
|
if (!task) {
|
|
185
|
-
return
|
|
197
|
+
return this.toFailure(new Error('No process was started'));
|
|
186
198
|
}
|
|
187
|
-
const context = {
|
|
199
|
+
const context = { merged: '' };
|
|
188
200
|
const [stdout, stderr] = await Promise.all([
|
|
189
201
|
this._streamOut ? (0, readStream_js_1.readStream)(this._streamOut, context) : '',
|
|
190
202
|
this._streamErr ? (0, readStream_js_1.readStream)(this._streamErr, context) : '',
|
|
@@ -196,13 +208,21 @@ class ExecProcess {
|
|
|
196
208
|
}
|
|
197
209
|
task.removeAllListeners();
|
|
198
210
|
if (this._thrownError) {
|
|
199
|
-
return
|
|
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;
|
|
200
218
|
}
|
|
201
219
|
const result = {
|
|
202
|
-
|
|
220
|
+
args: this._args,
|
|
203
221
|
command: this._command,
|
|
222
|
+
cwd: this._cwd,
|
|
204
223
|
durationMs: Number(process.hrtime.bigint() - this._start) / 1e6,
|
|
205
224
|
exitCode: this.exitCode,
|
|
225
|
+
merged: context.merged,
|
|
206
226
|
stderr,
|
|
207
227
|
stdout,
|
|
208
228
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ChildProcess } from 'node:child_process';
|
|
2
2
|
import type { Readable } from 'node:stream';
|
|
3
|
-
import type { ExecFailure, ExecResult, KillSignal, Options, Subprocess } from './types.cjs';
|
|
4
|
-
|
|
3
|
+
import type { ExecFailure, ExecResult, ExecSuccess, KillSignal, Options, ResultMode, Subprocess } from './types.cjs';
|
|
4
|
+
declare class ExecProcess implements Subprocess<ResultMode> {
|
|
5
5
|
protected _aborted: boolean;
|
|
6
6
|
protected _args: readonly string[];
|
|
7
7
|
protected _command: string;
|
|
8
|
+
protected _cwd: string | undefined;
|
|
8
9
|
protected _failed: boolean;
|
|
9
10
|
protected _options: Partial<Options>;
|
|
10
11
|
protected _process?: ChildProcess;
|
|
@@ -25,13 +26,16 @@ export declare class ExecProcess implements Subprocess {
|
|
|
25
26
|
get process(): ChildProcess | undefined;
|
|
26
27
|
get signalCode(): string | undefined;
|
|
27
28
|
kill(signal?: KillSignal): boolean;
|
|
28
|
-
pipe(command: string, args?: readonly string[], options?: Partial<Options>
|
|
29
|
+
pipe<N extends ResultMode = 'result'>(command: string, args?: readonly string[], options?: Partial<Options> & {
|
|
30
|
+
resultMode?: N;
|
|
31
|
+
}): Subprocess<N>;
|
|
29
32
|
spawn(): void;
|
|
30
33
|
[Symbol.asyncIterator](): AsyncIterator<string>;
|
|
31
|
-
then<TResult1 = ExecResult
|
|
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>;
|
|
32
35
|
protected onClose(): void;
|
|
33
36
|
protected onError(error: Error): void;
|
|
34
37
|
protected reset(): void;
|
|
35
|
-
protected toFailure(error: Error):
|
|
36
|
-
protected toResult(): Promise<ExecResult
|
|
38
|
+
protected toFailure(error: Error): ExecResult<ResultMode>;
|
|
39
|
+
protected toResult(): Promise<ExecResult<ResultMode>>;
|
|
37
40
|
}
|
|
41
|
+
export { type ExecFailure, type ExecResult, type ExecSuccess, type KillSignal, type Options, type ResultMode, type Subprocess, ExecProcess, };
|
package/lib/cjs/exec.d.cts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type { Options, Subprocess } from './types.cjs';
|
|
2
|
-
|
|
1
|
+
import type { Options, ResultMode, Subprocess } from './types.cjs';
|
|
2
|
+
declare function exec<M extends ResultMode = 'result'>(command: string, args?: readonly string[] | null, options?: Partial<Options<M>>): Subprocess<M>;
|
|
3
|
+
export { type Options, type ResultMode, type Subprocess, exec, };
|
package/lib/cjs/index.cjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.exec = exports.ExecProcess = void 0;
|
|
3
|
+
exports.exec = exports.ExecProcess = exports.createCommand = void 0;
|
|
4
|
+
var create_command_js_1 = require("./create-command.cjs");
|
|
5
|
+
Object.defineProperty(exports, "createCommand", { enumerable: true, get: function () { return create_command_js_1.createCommand; } });
|
|
4
6
|
var exec_process_js_1 = require("./exec-process.cjs");
|
|
5
7
|
Object.defineProperty(exports, "ExecProcess", { enumerable: true, get: function () { return exec_process_js_1.ExecProcess; } });
|
|
6
8
|
var exec_js_1 = require("./exec.cjs");
|
package/lib/cjs/index.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type { Command, ExecFailure, ExecResult, ExecSuccess, KillSignal, Options, ResultMode, StdinOption, StdioOption, Subprocess, } from './types.cjs';
|
|
2
|
+
export { createCommand } from './create-command.cjs';
|
|
1
3
|
export { ExecProcess } from './exec-process.cjs';
|
|
2
4
|
export { exec } from './exec.cjs';
|
|
3
|
-
export { ExecFailure, ExecResult, ExecSuccess, KillSignal, Options, StdinOption, StdioOption, Subprocess, } from './types.cjs';
|
package/lib/cjs/types.d.cts
CHANGED
|
@@ -1,37 +1,49 @@
|
|
|
1
1
|
import type { ChildProcess, SpawnOptions as ProcessSpawnOptions } from 'node:child_process';
|
|
2
2
|
import type { Result } from '@zokugun/xtry';
|
|
3
|
+
export type Command<M extends ResultMode = 'result'> = {
|
|
4
|
+
(args?: readonly string[] | null, options?: Partial<Options<M>>): Subprocess<M>;
|
|
5
|
+
toCwdFirst(): (cwd: string, ...args: string[]) => Subprocess<M>;
|
|
6
|
+
toFlatArgs(): (...args: string[]) => Subprocess<M>;
|
|
7
|
+
withOptions(options: Partial<Options<M>>): Command<M>;
|
|
8
|
+
};
|
|
3
9
|
export type ExecFailure = {
|
|
4
|
-
|
|
10
|
+
args: readonly string[];
|
|
5
11
|
canceled: boolean;
|
|
12
|
+
cwd?: string;
|
|
6
13
|
command: string;
|
|
7
14
|
durationMs: number;
|
|
8
15
|
error?: Error;
|
|
9
16
|
exitCode?: number;
|
|
17
|
+
merged?: string;
|
|
10
18
|
pipedFrom?: ExecResult;
|
|
11
19
|
signalCode?: string;
|
|
12
20
|
stderr?: string;
|
|
13
21
|
stdout?: string;
|
|
14
22
|
};
|
|
15
|
-
export type
|
|
23
|
+
export type ResultMode = 'merged' | 'result';
|
|
24
|
+
export type ExecResult<M extends ResultMode = 'result'> = M extends 'merged' ? string : Result<ExecSuccess, ExecFailure>;
|
|
16
25
|
export type ExecSuccess = {
|
|
17
|
-
|
|
26
|
+
args: readonly string[];
|
|
18
27
|
command: string;
|
|
28
|
+
cwd: string;
|
|
19
29
|
durationMs: number;
|
|
20
30
|
error?: undefined;
|
|
21
31
|
exitCode: number;
|
|
32
|
+
merged: string;
|
|
22
33
|
pipedFrom?: ExecResult;
|
|
23
34
|
stderr: string;
|
|
24
35
|
stdout: string;
|
|
25
36
|
};
|
|
26
37
|
export type KillSignal = Parameters<ChildProcess['kill']>[0];
|
|
27
|
-
export type Options = Omit<ProcessSpawnOptions, 'cwd' | 'env' | 'stdio'> & Readonly<Partial<{
|
|
38
|
+
export type Options<M extends ResultMode = ResultMode> = Omit<ProcessSpawnOptions, 'cwd' | 'env' | 'stdio'> & Readonly<Partial<{
|
|
28
39
|
cwd?: string;
|
|
29
40
|
env: Readonly<Partial<Record<string, string>>>;
|
|
30
41
|
localDir: string;
|
|
31
42
|
preferLocal: boolean;
|
|
43
|
+
resultMode: M;
|
|
32
44
|
signal: AbortSignal;
|
|
33
45
|
stderr: StdioOption;
|
|
34
|
-
stdin: ExecResult | StdinOption | Subprocess
|
|
46
|
+
stdin: ExecResult | StdinOption | Subprocess<ResultMode>;
|
|
35
47
|
stdio: readonly [StdinOption, ...readonly StdioOption[]] | SpawnOptions['stdio'];
|
|
36
48
|
stdout: StdioOption;
|
|
37
49
|
}>>;
|
|
@@ -42,7 +54,7 @@ export type StdinOption = StdioOption | {
|
|
|
42
54
|
readonly string?: string;
|
|
43
55
|
};
|
|
44
56
|
export type StdioOption = Readonly<Exclude<SpawnOptions['stdio'], undefined>[number]>;
|
|
45
|
-
export type Subprocess = AsyncIterable<string> & PromiseLike<ExecResult
|
|
57
|
+
export type Subprocess<M extends ResultMode = 'result'> = AsyncIterable<string> & PromiseLike<ExecResult<M>> & {
|
|
46
58
|
get aborted(): boolean;
|
|
47
59
|
get error(): Error | undefined;
|
|
48
60
|
get exitCode(): number | undefined;
|
|
@@ -50,7 +62,9 @@ export type Subprocess = AsyncIterable<string> & PromiseLike<ExecResult> & {
|
|
|
50
62
|
kill: (signal?: KillSignal) => boolean;
|
|
51
63
|
get killed(): boolean;
|
|
52
64
|
get pid(): number | undefined;
|
|
53
|
-
pipe: (command: string, args?: readonly string[], options?: Partial<Options>
|
|
65
|
+
pipe: <N extends ResultMode = 'result'>(command: string, args?: readonly string[], options?: Partial<Options> & {
|
|
66
|
+
resultMode?: N;
|
|
67
|
+
}) => Subprocess<N>;
|
|
54
68
|
get process(): ChildProcess | undefined;
|
|
55
69
|
get signalCode(): string | undefined;
|
|
56
70
|
};
|
|
@@ -6,9 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.normalizeOptions = normalizeOptions;
|
|
7
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
8
|
const computeEnv_js_1 = require("./env/computeEnv.cjs");
|
|
9
|
-
const combineSignals_js_1 = require("
|
|
9
|
+
const combineSignals_js_1 = require("./combineSignals.cjs");
|
|
10
10
|
function normalizeOptions(options) {
|
|
11
|
-
const { cwd, env, preferLocal, signal, stderr, stdin, stdio: stdioOption, stdout, ...restOptions } = options;
|
|
11
|
+
const { cwd, env, preferLocal, resultMode: _resultMode, signal, stderr, stdin, stdio: stdioOption, stdout, ...restOptions } = options;
|
|
12
12
|
const spawnOptions = restOptions;
|
|
13
13
|
spawnOptions.cwd = node_path_1.default.resolve(cwd ?? process.cwd());
|
|
14
14
|
spawnOptions.env = (0, computeEnv_js_1.computeEnv)(env, preferLocal ?? false, options.localDir ?? spawnOptions.cwd);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Command, Options, ResultMode, Subprocess } from './types.mjs';
|
|
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,17 @@
|
|
|
1
|
+
import { exec } from './exec.mjs';
|
|
2
|
+
function createCommand(command, args = [], options = {}) {
|
|
3
|
+
const create = (options) => {
|
|
4
|
+
const run = ((runArgs, runOptions) => {
|
|
5
|
+
return exec(command, [...(args ?? []), ...(runArgs ?? [])], {
|
|
6
|
+
...options,
|
|
7
|
+
...runOptions,
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
run.withOptions = (overrides) => create({ ...options, ...overrides });
|
|
11
|
+
run.toCwdFirst = () => ((cwd, ...runArgs) => run(runArgs, { cwd }));
|
|
12
|
+
run.toFlatArgs = () => ((...runArgs) => run(runArgs));
|
|
13
|
+
return run;
|
|
14
|
+
};
|
|
15
|
+
return create(options);
|
|
16
|
+
}
|
|
17
|
+
export { createCommand, };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ChildProcess } from 'node:child_process';
|
|
2
2
|
import type { Readable } from 'node:stream';
|
|
3
|
-
import type { ExecFailure, ExecResult, KillSignal, Options, Subprocess } from './types.mjs';
|
|
4
|
-
|
|
3
|
+
import type { ExecFailure, ExecResult, ExecSuccess, KillSignal, Options, ResultMode, Subprocess } from './types.mjs';
|
|
4
|
+
declare class ExecProcess implements Subprocess<ResultMode> {
|
|
5
5
|
protected _aborted: boolean;
|
|
6
6
|
protected _args: readonly string[];
|
|
7
7
|
protected _command: string;
|
|
8
|
+
protected _cwd: string | undefined;
|
|
8
9
|
protected _failed: boolean;
|
|
9
10
|
protected _options: Partial<Options>;
|
|
10
11
|
protected _process?: ChildProcess;
|
|
@@ -25,13 +26,16 @@ export declare class ExecProcess implements Subprocess {
|
|
|
25
26
|
get process(): ChildProcess | undefined;
|
|
26
27
|
get signalCode(): string | undefined;
|
|
27
28
|
kill(signal?: KillSignal): boolean;
|
|
28
|
-
pipe(command: string, args?: readonly string[], options?: Partial<Options>
|
|
29
|
+
pipe<N extends ResultMode = 'result'>(command: string, args?: readonly string[], options?: Partial<Options> & {
|
|
30
|
+
resultMode?: N;
|
|
31
|
+
}): Subprocess<N>;
|
|
29
32
|
spawn(): void;
|
|
30
33
|
[Symbol.asyncIterator](): AsyncIterator<string>;
|
|
31
|
-
then<TResult1 = ExecResult
|
|
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>;
|
|
32
35
|
protected onClose(): void;
|
|
33
36
|
protected onError(error: Error): void;
|
|
34
37
|
protected reset(): void;
|
|
35
|
-
protected toFailure(error: Error):
|
|
36
|
-
protected toResult(): Promise<ExecResult
|
|
38
|
+
protected toFailure(error: Error): ExecResult<ResultMode>;
|
|
39
|
+
protected toResult(): Promise<ExecResult<ResultMode>>;
|
|
37
40
|
}
|
|
41
|
+
export { type ExecFailure, type ExecResult, type ExecSuccess, type KillSignal, type Options, type ResultMode, type Subprocess, ExecProcess, };
|
package/lib/esm/exec-process.mjs
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import readline from 'node:readline';
|
|
3
|
-
import { err, ok } from '@zokugun/xtry';
|
|
3
|
+
import { err, ok, stringifyError } from '@zokugun/xtry';
|
|
4
4
|
import { exec } from './exec.mjs';
|
|
5
5
|
import { normalizeCommand } from './utils/command/normalizeCommand.mjs';
|
|
6
6
|
import { normalizeOptions } from './utils/normalizeOptions.mjs';
|
|
7
7
|
import { combineStreams } from './utils/stream/combineStreams.mjs';
|
|
8
8
|
import { readStream } from './utils/stream/readStream.mjs';
|
|
9
|
-
|
|
9
|
+
class ExecProcess {
|
|
10
10
|
_aborted = false;
|
|
11
11
|
_args;
|
|
12
12
|
_command;
|
|
13
|
+
_cwd;
|
|
13
14
|
_failed = false;
|
|
14
15
|
_options;
|
|
15
16
|
_process;
|
|
@@ -69,6 +70,7 @@ export class ExecProcess {
|
|
|
69
70
|
this._start = process.hrtime.bigint();
|
|
70
71
|
this.reset();
|
|
71
72
|
const normalizedOptions = normalizeOptions(this._options);
|
|
73
|
+
this._cwd = normalizedOptions.cwd;
|
|
72
74
|
if (normalizedOptions.signal) {
|
|
73
75
|
this._signal = normalizedOptions.signal;
|
|
74
76
|
}
|
|
@@ -132,7 +134,12 @@ export class ExecProcess {
|
|
|
132
134
|
} // }}}
|
|
133
135
|
// eslint-disable-next-line ts/promise-function-async
|
|
134
136
|
then(onfulfilled, onrejected) {
|
|
135
|
-
return this.toResult().then(
|
|
137
|
+
return this.toResult().then((result) => {
|
|
138
|
+
if (onfulfilled) {
|
|
139
|
+
return onfulfilled(result);
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}, onrejected);
|
|
136
143
|
} // }}}
|
|
137
144
|
onClose() {
|
|
138
145
|
if (this._resolveClose) {
|
|
@@ -159,26 +166,31 @@ export class ExecProcess {
|
|
|
159
166
|
} // }}}
|
|
160
167
|
toFailure(error) {
|
|
161
168
|
this._failed = true;
|
|
169
|
+
if (this._options.resultMode === 'merged') {
|
|
170
|
+
return stringifyError(error);
|
|
171
|
+
}
|
|
162
172
|
const result = {
|
|
163
|
-
|
|
173
|
+
args: this._args,
|
|
164
174
|
canceled: this._signal?.aborted ?? false,
|
|
165
175
|
command: this._command,
|
|
176
|
+
cwd: this._cwd,
|
|
166
177
|
durationMs: Number(process.hrtime.bigint() - this._start) / 1e6,
|
|
167
178
|
error,
|
|
168
179
|
exitCode: this.exitCode,
|
|
180
|
+
merged: '',
|
|
169
181
|
signalCode: this.signalCode,
|
|
170
182
|
};
|
|
171
|
-
return result;
|
|
183
|
+
return err(result);
|
|
172
184
|
} // }}}
|
|
173
185
|
async toResult() {
|
|
174
186
|
if (this._thrownError) {
|
|
175
|
-
return
|
|
187
|
+
return this.toFailure(this._thrownError);
|
|
176
188
|
}
|
|
177
189
|
const task = this._process;
|
|
178
190
|
if (!task) {
|
|
179
|
-
return
|
|
191
|
+
return this.toFailure(new Error('No process was started'));
|
|
180
192
|
}
|
|
181
|
-
const context = {
|
|
193
|
+
const context = { merged: '' };
|
|
182
194
|
const [stdout, stderr] = await Promise.all([
|
|
183
195
|
this._streamOut ? readStream(this._streamOut, context) : '',
|
|
184
196
|
this._streamErr ? readStream(this._streamErr, context) : '',
|
|
@@ -190,13 +202,21 @@ export class ExecProcess {
|
|
|
190
202
|
}
|
|
191
203
|
task.removeAllListeners();
|
|
192
204
|
if (this._thrownError) {
|
|
193
|
-
return
|
|
205
|
+
return this.toFailure(this._thrownError);
|
|
206
|
+
}
|
|
207
|
+
if (this._options.resultMode === 'merged') {
|
|
208
|
+
if ((this.exitCode !== 0 && this.exitCode !== undefined) || this.signalCode !== undefined) {
|
|
209
|
+
this._failed = true;
|
|
210
|
+
}
|
|
211
|
+
return context.merged;
|
|
194
212
|
}
|
|
195
213
|
const result = {
|
|
196
|
-
|
|
214
|
+
args: this._args,
|
|
197
215
|
command: this._command,
|
|
216
|
+
cwd: this._cwd,
|
|
198
217
|
durationMs: Number(process.hrtime.bigint() - this._start) / 1e6,
|
|
199
218
|
exitCode: this.exitCode,
|
|
219
|
+
merged: context.merged,
|
|
200
220
|
stderr,
|
|
201
221
|
stdout,
|
|
202
222
|
};
|
|
@@ -212,3 +232,4 @@ export class ExecProcess {
|
|
|
212
232
|
return ok(result);
|
|
213
233
|
} // }}}
|
|
214
234
|
}
|
|
235
|
+
export { ExecProcess, };
|
package/lib/esm/exec.d.mts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type { Options, Subprocess } from './types.mjs';
|
|
2
|
-
|
|
1
|
+
import type { Options, ResultMode, Subprocess } from './types.mjs';
|
|
2
|
+
declare function exec<M extends ResultMode = 'result'>(command: string, args?: readonly string[] | null, options?: Partial<Options<M>>): Subprocess<M>;
|
|
3
|
+
export { type Options, type ResultMode, type Subprocess, exec, };
|
package/lib/esm/exec.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ExecProcess } from './exec-process.mjs';
|
|
2
|
-
|
|
2
|
+
function exec(command, args, options) {
|
|
3
3
|
const subprocess = new ExecProcess(command, args, options);
|
|
4
4
|
subprocess.spawn();
|
|
5
5
|
return subprocess;
|
|
6
6
|
}
|
|
7
|
+
export { exec, };
|
package/lib/esm/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type { Command, ExecFailure, ExecResult, ExecSuccess, KillSignal, Options, ResultMode, StdinOption, StdioOption, Subprocess, } from './types.mjs';
|
|
2
|
+
export { createCommand } from './create-command.mjs';
|
|
1
3
|
export { ExecProcess } from './exec-process.mjs';
|
|
2
4
|
export { exec } from './exec.mjs';
|
|
3
|
-
export { ExecFailure, ExecResult, ExecSuccess, KillSignal, Options, StdinOption, StdioOption, Subprocess, } from './types.mjs';
|
package/lib/esm/index.mjs
CHANGED
package/lib/esm/types.d.mts
CHANGED
|
@@ -1,37 +1,49 @@
|
|
|
1
1
|
import type { ChildProcess, SpawnOptions as ProcessSpawnOptions } from 'node:child_process';
|
|
2
2
|
import type { Result } from '@zokugun/xtry';
|
|
3
|
+
export type Command<M extends ResultMode = 'result'> = {
|
|
4
|
+
(args?: readonly string[] | null, options?: Partial<Options<M>>): Subprocess<M>;
|
|
5
|
+
toCwdFirst(): (cwd: string, ...args: string[]) => Subprocess<M>;
|
|
6
|
+
toFlatArgs(): (...args: string[]) => Subprocess<M>;
|
|
7
|
+
withOptions(options: Partial<Options<M>>): Command<M>;
|
|
8
|
+
};
|
|
3
9
|
export type ExecFailure = {
|
|
4
|
-
|
|
10
|
+
args: readonly string[];
|
|
5
11
|
canceled: boolean;
|
|
12
|
+
cwd?: string;
|
|
6
13
|
command: string;
|
|
7
14
|
durationMs: number;
|
|
8
15
|
error?: Error;
|
|
9
16
|
exitCode?: number;
|
|
17
|
+
merged?: string;
|
|
10
18
|
pipedFrom?: ExecResult;
|
|
11
19
|
signalCode?: string;
|
|
12
20
|
stderr?: string;
|
|
13
21
|
stdout?: string;
|
|
14
22
|
};
|
|
15
|
-
export type
|
|
23
|
+
export type ResultMode = 'merged' | 'result';
|
|
24
|
+
export type ExecResult<M extends ResultMode = 'result'> = M extends 'merged' ? string : Result<ExecSuccess, ExecFailure>;
|
|
16
25
|
export type ExecSuccess = {
|
|
17
|
-
|
|
26
|
+
args: readonly string[];
|
|
18
27
|
command: string;
|
|
28
|
+
cwd: string;
|
|
19
29
|
durationMs: number;
|
|
20
30
|
error?: undefined;
|
|
21
31
|
exitCode: number;
|
|
32
|
+
merged: string;
|
|
22
33
|
pipedFrom?: ExecResult;
|
|
23
34
|
stderr: string;
|
|
24
35
|
stdout: string;
|
|
25
36
|
};
|
|
26
37
|
export type KillSignal = Parameters<ChildProcess['kill']>[0];
|
|
27
|
-
export type Options = Omit<ProcessSpawnOptions, 'cwd' | 'env' | 'stdio'> & Readonly<Partial<{
|
|
38
|
+
export type Options<M extends ResultMode = ResultMode> = Omit<ProcessSpawnOptions, 'cwd' | 'env' | 'stdio'> & Readonly<Partial<{
|
|
28
39
|
cwd?: string;
|
|
29
40
|
env: Readonly<Partial<Record<string, string>>>;
|
|
30
41
|
localDir: string;
|
|
31
42
|
preferLocal: boolean;
|
|
43
|
+
resultMode: M;
|
|
32
44
|
signal: AbortSignal;
|
|
33
45
|
stderr: StdioOption;
|
|
34
|
-
stdin: ExecResult | StdinOption | Subprocess
|
|
46
|
+
stdin: ExecResult | StdinOption | Subprocess<ResultMode>;
|
|
35
47
|
stdio: readonly [StdinOption, ...readonly StdioOption[]] | SpawnOptions['stdio'];
|
|
36
48
|
stdout: StdioOption;
|
|
37
49
|
}>>;
|
|
@@ -42,7 +54,7 @@ export type StdinOption = StdioOption | {
|
|
|
42
54
|
readonly string?: string;
|
|
43
55
|
};
|
|
44
56
|
export type StdioOption = Readonly<Exclude<SpawnOptions['stdio'], undefined>[number]>;
|
|
45
|
-
export type Subprocess = AsyncIterable<string> & PromiseLike<ExecResult
|
|
57
|
+
export type Subprocess<M extends ResultMode = 'result'> = AsyncIterable<string> & PromiseLike<ExecResult<M>> & {
|
|
46
58
|
get aborted(): boolean;
|
|
47
59
|
get error(): Error | undefined;
|
|
48
60
|
get exitCode(): number | undefined;
|
|
@@ -50,7 +62,9 @@ export type Subprocess = AsyncIterable<string> & PromiseLike<ExecResult> & {
|
|
|
50
62
|
kill: (signal?: KillSignal) => boolean;
|
|
51
63
|
get killed(): boolean;
|
|
52
64
|
get pid(): number | undefined;
|
|
53
|
-
pipe: (command: string, args?: readonly string[], options?: Partial<Options>
|
|
65
|
+
pipe: <N extends ResultMode = 'result'>(command: string, args?: readonly string[], options?: Partial<Options> & {
|
|
66
|
+
resultMode?: N;
|
|
67
|
+
}) => Subprocess<N>;
|
|
54
68
|
get process(): ChildProcess | undefined;
|
|
55
69
|
get signalCode(): string | undefined;
|
|
56
70
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { computeEnv } from './env/computeEnv.mjs';
|
|
3
|
-
import { combineSignals } from '
|
|
3
|
+
import { combineSignals } from './combineSignals.mjs';
|
|
4
4
|
export function normalizeOptions(options) {
|
|
5
|
-
const { cwd, env, preferLocal, signal, stderr, stdin, stdio: stdioOption, stdout, ...restOptions } = options;
|
|
5
|
+
const { cwd, env, preferLocal, resultMode: _resultMode, signal, stderr, stdin, stdio: stdioOption, stdout, ...restOptions } = options;
|
|
6
6
|
const spawnOptions = restOptions;
|
|
7
7
|
spawnOptions.cwd = path.resolve(cwd ?? process.cwd());
|
|
8
8
|
spawnOptions.env = computeEnv(env, preferLocal ?? false, options.localDir ?? spawnOptions.cwd);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zokugun/exec-lite",
|
|
3
3
|
"description": "A lightweight tool for running shell commands and reading their output easily.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Baptiste Augrain",
|
|
7
7
|
"email": "daiyam@zokugun.org"
|
|
@@ -20,6 +20,18 @@
|
|
|
20
20
|
".": {
|
|
21
21
|
"import": "./lib/esm/index.mjs",
|
|
22
22
|
"require": "./lib/cjs/index.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./command-line": {
|
|
25
|
+
"import": "./lib/esm/command-line.mjs",
|
|
26
|
+
"require": "./lib/cjs/command-line.cjs"
|
|
27
|
+
},
|
|
28
|
+
"./exec-process": {
|
|
29
|
+
"import": "./lib/esm/exec-process.mjs",
|
|
30
|
+
"require": "./lib/cjs/exec-process.cjs"
|
|
31
|
+
},
|
|
32
|
+
"./exec": {
|
|
33
|
+
"import": "./lib/esm/exec.mjs",
|
|
34
|
+
"require": "./lib/cjs/exec.cjs"
|
|
23
35
|
}
|
|
24
36
|
},
|
|
25
37
|
"main": "lib/cjs/index.cjs",
|
|
@@ -28,6 +40,15 @@
|
|
|
28
40
|
"*": {
|
|
29
41
|
".": [
|
|
30
42
|
"./lib/cjs/index.d.cts"
|
|
43
|
+
],
|
|
44
|
+
"command-line": [
|
|
45
|
+
"./lib/cjs/command-line.d.cts"
|
|
46
|
+
],
|
|
47
|
+
"exec-process": [
|
|
48
|
+
"./lib/cjs/exec-process.d.cts"
|
|
49
|
+
],
|
|
50
|
+
"exec": [
|
|
51
|
+
"./lib/cjs/exec.d.cts"
|
|
31
52
|
]
|
|
32
53
|
}
|
|
33
54
|
},
|
|
@@ -47,6 +68,7 @@
|
|
|
47
68
|
"lint:package": "fixpack || true",
|
|
48
69
|
"prepack": "npm run build",
|
|
49
70
|
"prepare": "husky; npm run lint:package",
|
|
71
|
+
"release": "bump-n-ship release --all --pause",
|
|
50
72
|
"test": "vitest run --reporter tree",
|
|
51
73
|
"test:coverage": "vitest run --reporter tree --coverage --coverage.reporter text",
|
|
52
74
|
"test:ui": "vitest --ui --coverage",
|
|
@@ -67,6 +89,7 @@
|
|
|
67
89
|
"@types/node": "20.19.43",
|
|
68
90
|
"@vitest/coverage-v8": "4.1.12",
|
|
69
91
|
"@vitest/ui": "4.1.12",
|
|
92
|
+
"@zokugun/bump-n-ship": "0.1.1",
|
|
70
93
|
"@zokugun/eslint-config": "0.2.1",
|
|
71
94
|
"@zokugun/eslint-config-data": "0.2.1",
|
|
72
95
|
"@zokugun/eslint-config-glossary": "0.2.0",
|
|
@@ -77,7 +100,7 @@
|
|
|
77
100
|
"@zokugun/eslint-config-test": "0.2.0",
|
|
78
101
|
"@zokugun/eslint-config-ts": "0.3.2",
|
|
79
102
|
"@zokugun/fs-extra-plus": "0.7.0",
|
|
80
|
-
"@zokugun/tsc-leda": "0.9.
|
|
103
|
+
"@zokugun/tsc-leda": "0.9.5",
|
|
81
104
|
"eslint": "10.13.0",
|
|
82
105
|
"fixpack": "4.0.0",
|
|
83
106
|
"get-node": "15.0.4",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|