@j-o-r/sh 1.1.29 → 1.1.32
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/.editorconfig +21 -0
- package/README.md +32 -3
- package/TODO.md +349 -12
- package/lib/SH.js +274 -208
- package/lib/SHDispatch.js +48 -32
- package/lib/SHExecute.js +233 -228
- package/lib/internal.js +113 -0
- package/package.json +2 -2
- package/types/SH.d.ts +109 -50
- package/types/SHDispatch.d.ts +49 -28
- package/types/SHExecute.d.ts +21 -10
- package/types/internal.d.ts +62 -0
package/lib/internal.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared internals for the SH library.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the default command options used by the `SH`
|
|
5
|
+
* template tag (`lib/SH.js`) and `SHDispatch` (`lib/SHDispatch.js`), and for
|
|
6
|
+
* the shared `parseDuration` used by `lib/SH.js` and `lib/SHExecute.js`.
|
|
7
|
+
* Not part of the public API; nothing here is re-exported from `lib/SH.js`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Explicit default `cwd` override, set via `SH.cwd = dir`.
|
|
12
|
+
*
|
|
13
|
+
* @type {string | undefined}
|
|
14
|
+
*/
|
|
15
|
+
let cwdOverride;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default maximum buffered bytes per stdout/stderr stream: 512000 (500 KiB).
|
|
19
|
+
* Used by `SHExecute` when `options.maxBuffer` is unset or invalid.
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_MAX_BUFFER = 500 * 1024;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default options applied to all SH commands unless overridden.
|
|
25
|
+
*
|
|
26
|
+
* `cwd` is intentionally lazy: the getter resolves `process.cwd()` at read
|
|
27
|
+
* time, so commands created after a `cd()` run in the new directory instead of
|
|
28
|
+
* the directory the process was started in. `SHDispatch` spreads these
|
|
29
|
+
* defaults (`{ ...defaultOptions }`) per command, which invokes the getter and
|
|
30
|
+
* freezes the value for that command — "defaults captured at creation time".
|
|
31
|
+
* Do not "optimize" this into a static snapshot.
|
|
32
|
+
*
|
|
33
|
+
* An explicit `SH.cwd = dir` assignment takes precedence over `process.cwd()`
|
|
34
|
+
* (it only redirects SH commands; it does not `chdir` the process). `cd()`
|
|
35
|
+
* clears the override again, so the most recent of the two always wins.
|
|
36
|
+
*
|
|
37
|
+
* `maxBuffer` and `detached` are initialized explicitly so every key the `SH`
|
|
38
|
+
* proxy exposes ({@link defaultOptionKeys}) also has a defined value here.
|
|
39
|
+
* This is behavior-neutral: `SHExecute` already fell back to
|
|
40
|
+
* `DEFAULT_MAX_BUFFER` for unset/invalid values and truthy-checks `detached`.
|
|
41
|
+
*
|
|
42
|
+
* @type {import('./SHDispatch.js').SHOptions}
|
|
43
|
+
*/
|
|
44
|
+
const defaultOptions = {
|
|
45
|
+
get cwd() {
|
|
46
|
+
return cwdOverride ?? process.cwd();
|
|
47
|
+
},
|
|
48
|
+
set cwd(value) {
|
|
49
|
+
cwdOverride = value;
|
|
50
|
+
},
|
|
51
|
+
env: process.env,
|
|
52
|
+
shell: 'bash',
|
|
53
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
54
|
+
timeout: 0, // when 0 there is no timeout
|
|
55
|
+
maxBuffer: DEFAULT_MAX_BUFFER,
|
|
56
|
+
detached: false,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Known global option keys exposed by the `SH` proxy in `lib/SH.js`.
|
|
61
|
+
*
|
|
62
|
+
* Derived from the own keys of {@link defaultOptions}, so the key set can
|
|
63
|
+
* never drift from the defaults. (The previous hard-coded list in `lib/SH.js`
|
|
64
|
+
* readable-exposed `maxBuffer`/`detached`, which were uninitialized in the
|
|
65
|
+
* defaults.) The proxy routes reads/writes for these keys to
|
|
66
|
+
* `defaultOptions` and throws a `TypeError` on writes to any other key
|
|
67
|
+
* (decision D1).
|
|
68
|
+
*
|
|
69
|
+
* @type {Set<string>}
|
|
70
|
+
*/
|
|
71
|
+
const defaultOptionKeys = new Set(Object.keys(defaultOptions));
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Clears the `SH.cwd` override so the lazy `cwd` getter follows
|
|
75
|
+
* `process.cwd()` again. Called by `cd()` after a successful
|
|
76
|
+
* `process.chdir()`.
|
|
77
|
+
*/
|
|
78
|
+
const clearCwdOverride = () => {
|
|
79
|
+
cwdOverride = undefined;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parses a human-readable duration into milliseconds.
|
|
84
|
+
*
|
|
85
|
+
* Accepts finite non-negative numbers (milliseconds) and strings in the exact
|
|
86
|
+
* forms `'Nms'`, `'Ns'`, or a bare `'N'`. The bare-number form is treated as
|
|
87
|
+
* milliseconds on purpose: it preserves the leniency of the former `SHExecute`
|
|
88
|
+
* parser, so e.g. `timeout: '100'` keeps working.
|
|
89
|
+
*
|
|
90
|
+
* `null`/`undefined` are not special-cased here; call sites that allow an
|
|
91
|
+
* absent duration handle it themselves (e.g. `parseDuration(timeout ?? 0)`).
|
|
92
|
+
*
|
|
93
|
+
* @param {number|string} d - Duration as number (ms) or string ('5s', '100ms', '100').
|
|
94
|
+
* @returns {number} Duration in milliseconds.
|
|
95
|
+
* @throws {Error} If the duration type or format is invalid.
|
|
96
|
+
*/
|
|
97
|
+
const parseDuration = (d) => {
|
|
98
|
+
if (typeof d == 'number') {
|
|
99
|
+
if (!Number.isFinite(d) || d < 0)
|
|
100
|
+
throw new Error(`Invalid duration: "${d}".`);
|
|
101
|
+
return d;
|
|
102
|
+
}
|
|
103
|
+
if (typeof d == 'string') {
|
|
104
|
+
const match = d.match(/^(\d+)(ms|s)?$/);
|
|
105
|
+
if (!match)
|
|
106
|
+
throw new Error(`Unknown duration: "${d}".`);
|
|
107
|
+
const amount = Number(match[1]);
|
|
108
|
+
return match[2] == 's' ? amount * 1000 : amount;
|
|
109
|
+
}
|
|
110
|
+
throw new Error(`Invalid duration type: "${d === null ? 'Null' : typeof d}".`);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export { defaultOptions, defaultOptionKeys, clearCwdOverride, DEFAULT_MAX_BUFFER, parseDuration };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@j-o-r/sh",
|
|
3
3
|
"author": "Jorrit Duin <j-o-r@duin.work>",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.32",
|
|
6
6
|
"description": "Execute shell commands on Linux-based systems from javascript",
|
|
7
7
|
"main": "lib/SH.js",
|
|
8
8
|
"types": "types/SH.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"license": "Apache License, Version 2.0",
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@types/node": "
|
|
25
|
+
"@types/node": "*"
|
|
26
26
|
},
|
|
27
27
|
"bugs": {
|
|
28
28
|
"url": "https://codeberg.org/duin/sh/issues"
|
package/types/SH.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsed command-line arguments.
|
|
3
|
+
*
|
|
4
|
+
* Named options map to their string value, or `true` when no value is supplied.
|
|
5
|
+
* Bare positional arguments are collected in `_`.
|
|
6
|
+
*/
|
|
1
7
|
export type ArgsObject = {
|
|
2
|
-
|
|
8
|
+
_: string[];
|
|
9
|
+
} & {
|
|
10
|
+
[x: string]: string | true;
|
|
3
11
|
};
|
|
4
|
-
export type RejectCallback = Function;
|
|
5
|
-
export type ResolveCallback = Function;
|
|
6
|
-
export type SHOptions = import("./SHDispatch.js").SHOptions;
|
|
7
12
|
export type AbortableInput = {
|
|
8
13
|
/**
|
|
9
14
|
* - Promise that resolves to user input or void if aborted.
|
|
@@ -14,33 +19,56 @@ export type AbortableInput = {
|
|
|
14
19
|
*/
|
|
15
20
|
abort: () => void;
|
|
16
21
|
};
|
|
17
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Generator that yields retry delay durations in milliseconds.
|
|
24
|
+
*/
|
|
25
|
+
export type ExpBackoffGenerator = Generator<number, void, unknown>;
|
|
18
26
|
/**
|
|
19
27
|
* Template tag for building and executing shell commands.
|
|
20
28
|
*
|
|
21
29
|
* Returns an {@link SHDispatch} instance for configuration (.options()) and execution (.run(), .runSync()).
|
|
22
30
|
*
|
|
23
|
-
* Interpolation rules:
|
|
24
|
-
* - Arrays:
|
|
25
|
-
* - Other values: String(value)
|
|
31
|
+
* Interpolation rules are raw-by-default:
|
|
32
|
+
* - Arrays: Each element is converted with `String(value)` and joined with one space.
|
|
33
|
+
* - Other values: `String(value)` is inserted directly into the shell source.
|
|
34
|
+
*
|
|
35
|
+
* Raw interpolation allows trusted shell fragments such as pipes, redirects, and
|
|
36
|
+
* command separators. It is not safe for untrusted input. Wrap untrusted values
|
|
37
|
+
* with {@link bashEscape} before interpolating them.
|
|
26
38
|
*
|
|
27
|
-
* SH acts as
|
|
39
|
+
* SH also acts as a global options setter (see the last example). Global
|
|
40
|
+
* defaults are captured when each command is created, not when it runs — see
|
|
41
|
+
* the "defaults captured at creation time" note in {@link SHDispatch#options}.
|
|
42
|
+
* Only the known option keys (cwd, env, shell, stdio, timeout, maxBuffer,
|
|
43
|
+
* detached) may be assigned; assigning anything else throws a TypeError
|
|
44
|
+
* listing the known keys (typo guard, decision D1).
|
|
28
45
|
*
|
|
29
46
|
* @param {TemplateStringsArray} pieces - String literals from template.
|
|
30
|
-
* @param {...unknown
|
|
47
|
+
* @param {...unknown} args - Values to interpolate.
|
|
31
48
|
* @returns {SHDispatch} Command dispatcher.
|
|
32
49
|
* @throws {Error} If pieces contain undefined.
|
|
33
50
|
* @example
|
|
34
51
|
* const cmd = SH`echo ${'Hello'}`;
|
|
35
52
|
* await cmd.run(); // Executes: echo Hello
|
|
36
53
|
*
|
|
37
|
-
* // Array interpolation
|
|
38
|
-
* SH`ls ${['-la', '/']}`.run(); // ls
|
|
54
|
+
* // Array interpolation is raw and joins with spaces
|
|
55
|
+
* SH`ls ${['-la', '/']}`.run(); // Executes: ls -la /
|
|
56
|
+
*
|
|
57
|
+
* // Quote untrusted values explicitly
|
|
58
|
+
* SH`printf '%s\n' ${bashEscape('semi; colon')}`.run();
|
|
59
|
+
*
|
|
60
|
+
* // Set global defaults for subsequently created commands
|
|
61
|
+
* SH.timeout = 5000;
|
|
62
|
+
* await SH`cmd`.run();
|
|
39
63
|
*/
|
|
40
64
|
export const SH: any;
|
|
41
65
|
/**
|
|
42
66
|
* Changes the current working directory.
|
|
43
67
|
*
|
|
68
|
+
* Affects the cwd of subsequent `SH` commands: default options capture
|
|
69
|
+
* `process.cwd()` lazily when each command is created. Also clears any
|
|
70
|
+
* `SH.cwd` override, so the most recent cwd change always wins.
|
|
71
|
+
*
|
|
44
72
|
* @param {string} dir - Path to new directory.
|
|
45
73
|
* @example
|
|
46
74
|
* cd('/tmp');
|
|
@@ -58,30 +86,45 @@ export function sleep(duration: string | number): Promise<void>;
|
|
|
58
86
|
/**
|
|
59
87
|
* Retries an async function up to N times with optional delays.
|
|
60
88
|
*
|
|
61
|
-
* Delay
|
|
89
|
+
* Delay precedence: when `delayOrCallback` is a function it is the retried
|
|
90
|
+
* callback and there is no delay between attempts. Otherwise it is a static
|
|
91
|
+
* duration ('1s', 100) or a delay generator (e.g. {@link expBackoff}), and
|
|
92
|
+
* `callback` is the retried function. A generator yield of `undefined`
|
|
93
|
+
* (exhausted generator) or `0` means "no further delay" — the next attempt
|
|
94
|
+
* runs immediately.
|
|
62
95
|
*
|
|
63
|
-
* @param {number} count -
|
|
64
|
-
* @param {string|number|
|
|
65
|
-
* @param {
|
|
96
|
+
* @param {number} count - Positive integer number of attempts.
|
|
97
|
+
* @param {string|number|Iterator<number|string>|(() => (Promise<any>|any))} delayOrCallback - Delay, delay generator, or the callback when no separate callback is supplied.
|
|
98
|
+
* @param {() => (Promise<any>|any)} [callback] - Callback to retry when a delay is supplied.
|
|
66
99
|
* @returns {Promise<any>} Successful result.
|
|
67
|
-
* @throws {Error}
|
|
100
|
+
* @throws {Error} If arguments are invalid, or the last callback error if all attempts fail.
|
|
68
101
|
* @example
|
|
69
102
|
* await retry(3, '1s', () => SH`curl http://unreliable`.run());
|
|
70
103
|
* await retry(3, expBackoff(), () => flakyOp());
|
|
71
104
|
*/
|
|
72
|
-
export function retry(count: number,
|
|
105
|
+
export function retry(count: number, delayOrCallback: string | number | Iterator<number | string> | (() => (Promise<any> | any)), callback?: () => (Promise<any> | any)): Promise<any>;
|
|
73
106
|
/**
|
|
74
107
|
* Reads entire stdin as UTF-8 string. Resolves to empty string if TTY (no pipe).
|
|
75
108
|
*
|
|
76
|
-
*
|
|
109
|
+
* Side effect: permanently sets the `process.stdin` encoding to utf8 and
|
|
110
|
+
* switches it to flowing mode.
|
|
111
|
+
*
|
|
112
|
+
* @returns {Promise<string>} Stdin content, or an empty string if TTY.
|
|
77
113
|
* @example
|
|
78
114
|
* const input = await readIn(); // Use in piped scripts
|
|
79
115
|
*/
|
|
80
|
-
export function readIn(): Promise<string
|
|
116
|
+
export function readIn(): Promise<string>;
|
|
81
117
|
/**
|
|
82
118
|
* Prompts user for input on stdin with custom prompt.
|
|
83
119
|
*
|
|
84
|
-
*
|
|
120
|
+
* Submit heuristics (non-obvious): each 'line' event is accumulated into a
|
|
121
|
+
* multi-line buffer and (re)starts a debounce timer
|
|
122
|
+
* ({@link SUBMIT_DEBOUNCE_MS}); when the timer fires without further input,
|
|
123
|
+
* the promise auto-resolves with the accumulated text plus any partially
|
|
124
|
+
* typed readline line. A paste — a stdin chunk longer than
|
|
125
|
+
* {@link PASTE_MIN_CHUNK} chars containing a newline — (re)starts the same
|
|
126
|
+
* timer, so pasted multi-line text resolves as one input. `abort()` resolves
|
|
127
|
+
* the promise with `undefined`.
|
|
85
128
|
*
|
|
86
129
|
* @param {string} prompt - Prompt text to display.
|
|
87
130
|
* @returns {AbortableInput} Object with input Promise and abort function.
|
|
@@ -92,9 +135,13 @@ export function readIn(): Promise<string | undefined>;
|
|
|
92
135
|
*/
|
|
93
136
|
export function userIn(prompt: string): AbortableInput;
|
|
94
137
|
/**
|
|
95
|
-
*
|
|
138
|
+
* Awaits `callback()` and returns its result.
|
|
96
139
|
*
|
|
97
|
-
*
|
|
140
|
+
* Despite the historical name, this is currently a thin wrapper: no new async
|
|
141
|
+
* context or fresh callstack is created — the callback runs in the current
|
|
142
|
+
* one. It is kept as an intent marker for grouping async work (and for API
|
|
143
|
+
* compatibility); real isolation via AsyncLocalStorage is a pending decision
|
|
144
|
+
* (see the project TODO, decision D2).
|
|
98
145
|
*
|
|
99
146
|
* @param {() => Promise<any>} callback - Async function to execute.
|
|
100
147
|
* @returns {Promise<any>} Result of callback.
|
|
@@ -111,16 +158,21 @@ export function within(callback: () => Promise<any>): Promise<any>;
|
|
|
111
158
|
* @param {string} [max='60s'] - Max backoff duration.
|
|
112
159
|
* @param {string} [rand='100ms'] - Max jitter.
|
|
113
160
|
* @yields {number} Next backoff ms.
|
|
161
|
+
* @returns {ExpBackoffGenerator} Infinite generator of backoff delays in ms.
|
|
114
162
|
* @example
|
|
115
163
|
* const backoff = expBackoff();
|
|
116
164
|
* await sleep(backoff.next().value);
|
|
117
165
|
*/
|
|
118
|
-
export function expBackoff(max?: string, rand?: string):
|
|
166
|
+
export function expBackoff(max?: string, rand?: string): ExpBackoffGenerator;
|
|
119
167
|
/**
|
|
120
168
|
* Parses CLI arguments into an object.
|
|
121
169
|
*
|
|
122
170
|
* Supports --key value, -k value (no shorts grouped).
|
|
123
171
|
* Bares go to _[]. Duplicates error. No = syntax.
|
|
172
|
+
* Negative number tokens such as -1, -0.5, and -1e3 are parsed as values or
|
|
173
|
+
* positionals, not options. A lone `-` is a positional (stdin convention).
|
|
174
|
+
* `--` is the conventional end-of-options terminator: every token after it
|
|
175
|
+
* goes into `_`, even option-looking ones.
|
|
124
176
|
*
|
|
125
177
|
* Defaults to process.argv.slice(2).
|
|
126
178
|
*
|
|
@@ -129,24 +181,18 @@ export function expBackoff(max?: string, rand?: string): Generator<number, void,
|
|
|
129
181
|
* @throws {Error} On invalid/dupe args.
|
|
130
182
|
* @example
|
|
131
183
|
* parseArgs(['--port', '8080', 'file.txt']); // { port: '8080', _: ['file.txt'] }
|
|
184
|
+
* parseArgs(['--n', '-1']); // { n: '-1', _: [] }
|
|
185
|
+
* parseArgs(['-', '--port', '1']); // { port: '1', _: ['-'] }
|
|
186
|
+
* parseArgs(['--', '--x']); // { _: ['--x'] }
|
|
132
187
|
*/
|
|
133
188
|
export function parseArgs(args?: string[]): ArgsObject;
|
|
134
189
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
* @typedef {Function} RejectCallback
|
|
142
|
-
* @param {Error} error - The error object passed to the callback.
|
|
143
|
-
*/
|
|
144
|
-
/**
|
|
145
|
-
* @typedef {Function} ResolveCallback
|
|
146
|
-
* @param {any} [param] - Optional callback any value
|
|
147
|
-
*/
|
|
148
|
-
/**
|
|
149
|
-
* @typedef {import('./SHDispatch.js').SHOptions} SHOptions
|
|
190
|
+
* Parsed command-line arguments.
|
|
191
|
+
*
|
|
192
|
+
* Named options map to their string value, or `true` when no value is supplied.
|
|
193
|
+
* Bare positional arguments are collected in `_`.
|
|
194
|
+
*
|
|
195
|
+
* @typedef {{_: string[]} & Object.<string, string|true>} ArgsObject
|
|
150
196
|
*/
|
|
151
197
|
/**
|
|
152
198
|
* @typedef {Object} AbortableInput
|
|
@@ -154,22 +200,29 @@ export function parseArgs(args?: string[]): ArgsObject;
|
|
|
154
200
|
* @property {() => void} abort - Function to abort input collection.
|
|
155
201
|
*/
|
|
156
202
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* @
|
|
203
|
+
* Generator that yields retry delay durations in milliseconds.
|
|
204
|
+
*
|
|
205
|
+
* @typedef {Generator<number, void, unknown>} ExpBackoffGenerator
|
|
160
206
|
*/
|
|
161
207
|
/**
|
|
162
208
|
* Utility to determine the JavaScript type of a value.
|
|
163
209
|
*
|
|
164
|
-
*
|
|
210
|
+
* Uses `Object.prototype.toString`, so `jsType(null)` returns `'Null'`;
|
|
211
|
+
* `undefined` is special-cased to `'undefined'`. Plain objects report their
|
|
212
|
+
* constructor name, or `'Object'` when there is none (null-prototype objects).
|
|
213
|
+
*
|
|
214
|
+
* @param {unknown} value - Any value to inspect.
|
|
165
215
|
* @returns {string} The "real" object type name (e.g., 'Array', 'Promise') or primitive typeof.
|
|
166
216
|
* @example
|
|
167
217
|
* jsType([]); // 'Array'
|
|
168
218
|
* jsType(Promise.resolve()); // 'Promise'
|
|
219
|
+
* jsType(null); // 'Null'
|
|
220
|
+
* jsType(undefined); // 'undefined'
|
|
169
221
|
*/
|
|
170
|
-
export function jsType(
|
|
222
|
+
export function jsType(value: unknown): string;
|
|
171
223
|
/**
|
|
172
|
-
* Checks if an object has a specific own property
|
|
224
|
+
* Checks if an object has a specific own property. Safe for null-prototype
|
|
225
|
+
* objects and objects shadowing `hasOwnProperty`.
|
|
173
226
|
*
|
|
174
227
|
* @param {any} o - Object to examine.
|
|
175
228
|
* @param {string} p - Property name to check.
|
|
@@ -180,12 +233,18 @@ import Test from './Test.js';
|
|
|
180
233
|
import assert from 'node:assert';
|
|
181
234
|
import AsyncTracker from './AsyncTracker.js';
|
|
182
235
|
/**
|
|
183
|
-
*
|
|
236
|
+
* Quotes a value as one POSIX shell argument.
|
|
184
237
|
*
|
|
185
|
-
*
|
|
238
|
+
* The `SH` template tag interpolates values as raw shell source by default. Use
|
|
239
|
+
* this helper when a JavaScript value must be passed as data instead of shell
|
|
240
|
+
* syntax. The returned string is single-quoted and preserves the exact string
|
|
241
|
+
* value, including leading/trailing whitespace, empty strings, quotes,
|
|
242
|
+
* semicolons, glob characters, tabs, and newlines.
|
|
186
243
|
*
|
|
187
|
-
* @param {
|
|
188
|
-
* @returns {string}
|
|
244
|
+
* @param {unknown} x - Value to quote as a single shell argument.
|
|
245
|
+
* @returns {string} POSIX-shell-quoted argument string.
|
|
246
|
+
* @example
|
|
247
|
+
* await SH`printf '%s\n' ${bashEscape(userInput)}`.run();
|
|
189
248
|
*/
|
|
190
|
-
export function bashEscape(x:
|
|
249
|
+
export function bashEscape(x: unknown): string;
|
|
191
250
|
export { Test, assert, AsyncTracker };
|
package/types/SHDispatch.d.ts
CHANGED
|
@@ -1,32 +1,54 @@
|
|
|
1
1
|
export default SHDispatch;
|
|
2
|
-
export type
|
|
2
|
+
export type StdioOption = ("pipe" | "ignore" | "inherit" | number);
|
|
3
|
+
export type StdioOptions = Array<StdioOption> | StdioOption;
|
|
4
|
+
/**
|
|
5
|
+
* Core options for SH/SHDispatch.
|
|
6
|
+
*
|
|
7
|
+
* Defaults (merged from global SH):
|
|
8
|
+
* - `cwd`: `process.cwd()`
|
|
9
|
+
* - `env`: `process.env`
|
|
10
|
+
* - `shell`: `'bash'` (string/true → shell; false → no-shell `/usr/bin/env -S`)
|
|
11
|
+
* - `stdio`: `['inherit', 'pipe', 'pipe']`
|
|
12
|
+
* - `timeout`: `0` (no timeout; rolling on data)
|
|
13
|
+
* - `maxBuffer`: `512000` (500 KiB per stream in SHExecute)
|
|
14
|
+
*
|
|
15
|
+
* Prefix (`.options(undefined, prefix)`) only for shell mode.
|
|
16
|
+
*/
|
|
17
|
+
export type SHOptions = {
|
|
3
18
|
/**
|
|
4
|
-
* -
|
|
19
|
+
* - Working directory for spawned commands.
|
|
5
20
|
*/
|
|
6
|
-
|
|
21
|
+
cwd: string;
|
|
7
22
|
/**
|
|
8
|
-
* -
|
|
23
|
+
* - Environment for spawned commands.
|
|
9
24
|
*/
|
|
10
|
-
|
|
25
|
+
env: NodeJS.ProcessEnv;
|
|
11
26
|
/**
|
|
12
|
-
* -
|
|
27
|
+
* - Shell executable, true for bash, or false for no-shell mode.
|
|
13
28
|
*/
|
|
14
|
-
|
|
29
|
+
shell: string | boolean;
|
|
15
30
|
/**
|
|
16
|
-
* -
|
|
31
|
+
* - Stdio config passed to child_process.
|
|
17
32
|
*/
|
|
18
|
-
|
|
33
|
+
stdio: StdioOptions;
|
|
19
34
|
/**
|
|
20
|
-
* -
|
|
35
|
+
* - Timeout in ms or duration string; 0 disables.
|
|
36
|
+
* Async `run()` uses a rolling timeout that resets on stdout/stderr data.
|
|
37
|
+
* `runSync()` passes it to `spawnSync`, where it is absolute: the process is
|
|
38
|
+
* killed after the full duration no matter how much output it produces.
|
|
21
39
|
*/
|
|
22
|
-
|
|
40
|
+
timeout: number | string;
|
|
23
41
|
/**
|
|
24
|
-
* -
|
|
42
|
+
* - Maximum buffered bytes per stdout/stderr stream.
|
|
25
43
|
*/
|
|
26
|
-
|
|
44
|
+
maxBuffer?: number | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* - Run process detached and resolve early (~1s).
|
|
47
|
+
* Unless stdio is explicitly set for the command, stdio is forced to
|
|
48
|
+
* `'ignore'`, because open pipes would keep the parent's event loop alive.
|
|
49
|
+
*/
|
|
50
|
+
detached?: boolean | undefined;
|
|
27
51
|
};
|
|
28
|
-
export type StdioOption = ("pipe" | "ignore" | "inherit" | number);
|
|
29
|
-
export type StdioOptions = Array<StdioOption> | StdioOption;
|
|
30
52
|
/**
|
|
31
53
|
* High-level command dispatcher.
|
|
32
54
|
*
|
|
@@ -41,24 +63,30 @@ export type StdioOptions = Array<StdioOption> | StdioOption;
|
|
|
41
63
|
declare class SHDispatch {
|
|
42
64
|
/**
|
|
43
65
|
* @param {string} cmd - Command string.
|
|
44
|
-
* @param {Partial<
|
|
66
|
+
* @param {Partial<SHOptions>} [options] - Initial options.
|
|
45
67
|
* @param {string} [prefix] - Shell prefix (e.g., 'set -euo pipefail').
|
|
46
68
|
* @throws {Error} Invalid/empty cmd.
|
|
47
69
|
*/
|
|
48
|
-
constructor(cmd: string, options?: Partial<
|
|
70
|
+
constructor(cmd: string, options?: Partial<SHOptions>, prefix?: string);
|
|
49
71
|
/**
|
|
50
|
-
* Updates options/prefix
|
|
72
|
+
* Updates options/prefix by merging into the dispatch instance's current options.
|
|
51
73
|
*
|
|
52
|
-
*
|
|
74
|
+
* This preserves global `SH.*` defaults captured when the command was created
|
|
75
|
+
* and only overrides options explicitly supplied for this command. Strings for
|
|
76
|
+
* `stdio` are expanded to `[value, value, value]`.
|
|
53
77
|
*
|
|
54
|
-
* @param {Partial<
|
|
78
|
+
* @param {Partial<SHOptions>} [options] - New options.
|
|
55
79
|
* @param {string} [prefix] - New prefix.
|
|
56
80
|
* @returns {SHDispatch} Self for chaining.
|
|
57
81
|
*/
|
|
58
|
-
options(options?: Partial<
|
|
82
|
+
options(options?: Partial<SHOptions>, prefix?: string): SHDispatch;
|
|
59
83
|
/**
|
|
60
84
|
* Async run: Captures stdout; rejects on error/timeout.
|
|
61
85
|
*
|
|
86
|
+
* Replaces the internal process handle: calling `run()` again while a
|
|
87
|
+
* previous run is still active makes that first process unkillable via
|
|
88
|
+
* `kill()`.
|
|
89
|
+
*
|
|
62
90
|
* @param {string} [payload] - Stdin payload.
|
|
63
91
|
* @returns {Promise<string>} Stdout.
|
|
64
92
|
*/
|
|
@@ -81,10 +109,3 @@ declare class SHDispatch {
|
|
|
81
109
|
kill(signal?: number | string): Promise<number[]>;
|
|
82
110
|
#private;
|
|
83
111
|
}
|
|
84
|
-
declare namespace SHOptions {
|
|
85
|
-
let cwd: string;
|
|
86
|
-
let env: NodeJS.ProcessEnv;
|
|
87
|
-
let shell: string;
|
|
88
|
-
let stdio: string[];
|
|
89
|
-
let timeout: number;
|
|
90
|
-
}
|
package/types/SHExecute.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
export default SHExecute;
|
|
2
|
-
export type SHExecuteOptions =
|
|
3
|
-
maxBuffer?: number;
|
|
4
|
-
};
|
|
2
|
+
export type SHExecuteOptions = import("./SHDispatch.js").SHOptions;
|
|
5
3
|
/**
|
|
6
|
-
* @typedef {import('
|
|
7
|
-
* @description
|
|
4
|
+
* @typedef {import('./SHDispatch.js').SHOptions} SHExecuteOptions
|
|
5
|
+
* @description Options accepted by SHExecute — the shared SHOptions; `maxBuffer`
|
|
6
|
+
* (bytes per stream) defaults to 512000 (500 KiB).
|
|
8
7
|
*/
|
|
9
8
|
/**
|
|
10
9
|
* Low-level process executor for shell commands.
|
|
@@ -15,10 +14,16 @@ export type SHExecuteOptions = any & {
|
|
|
15
14
|
* - **Shell mode**: If `options.shell` is string/true, runs `${prefix}; ${command}` via shell ('bash' default).
|
|
16
15
|
* - **No-shell mode**: Uses `/usr/bin/env -S ${command}` for direct exec (ignores prefix).
|
|
17
16
|
* - **Rolling timeout**: `options.timeout` (ms/'2s'); resets on stdout/stderr data. SIGTERM on expiry.
|
|
18
|
-
*
|
|
17
|
+
* Async `run()` only — the timeout is stripped from the spawn options so Node's
|
|
18
|
+
* native absolute spawn timeout never interferes. `runSync()` instead passes the
|
|
19
|
+
* timeout to `spawnSync`, whose semantics are absolute (kills after the full
|
|
20
|
+
* duration, regardless of output).
|
|
21
|
+
* - **Buffering**: Captures stdout/stderr up to `maxBuffer` (512000 bytes / 500 KiB default); appends truncation markers.
|
|
19
22
|
* - **Payload**: `run(payload)` writes string to stdin (forces pipe).
|
|
20
|
-
* - **Detached**: If `options.detached`, resolves early (~1s) and unrefs.
|
|
21
|
-
*
|
|
23
|
+
* - **Detached**: If `options.detached`, resolves early (~1s) and unrefs. Unless the
|
|
24
|
+
* caller explicitly set `stdio`, SHDispatch forces `stdio: 'ignore'` for detached
|
|
25
|
+
* runs, because open pipes keep the parent's event loop alive and defeat detachment.
|
|
26
|
+
* - **Kill**: Terminates process + direct children via pgrep (requires procps; grandchildren survive).
|
|
22
27
|
*
|
|
23
28
|
* @example
|
|
24
29
|
* const exec = new SHExecute('ls', 'set -euo pipefail', { timeout: '5s' });
|
|
@@ -42,16 +47,22 @@ declare class SHExecute {
|
|
|
42
47
|
/**
|
|
43
48
|
* Asynchronous execution with buffering/timeout/kill.
|
|
44
49
|
*
|
|
45
|
-
* Resolves stdout (trimmed) on success
|
|
50
|
+
* Resolves stdout (trimmed) on success.
|
|
46
51
|
*
|
|
47
52
|
* @param {string} [payload] - Stdin data (forces pipe).
|
|
48
53
|
* @returns {Promise<string>} Trimmed UTF-8 stdout (+ truncation marker if exceeded).
|
|
49
|
-
*
|
|
54
|
+
* Rejects with an Error on command failure (message includes the exit code,
|
|
55
|
+
* or the signal name for signal kills, plus stderr), rolling-timeout expiry,
|
|
56
|
+
* forced kill, or spawn failure.
|
|
50
57
|
*/
|
|
51
58
|
run(payload?: string): Promise<string>;
|
|
52
59
|
/**
|
|
53
60
|
* Terminates process and its children (via pgrep).
|
|
54
61
|
*
|
|
62
|
+
* Requires `pgrep` (procps) at runtime. Only direct children are
|
|
63
|
+
* discovered — grandchildren and deeper descendants survive. Best-effort:
|
|
64
|
+
* pgrep failures resolve to an empty child list instead of rejecting.
|
|
65
|
+
*
|
|
55
66
|
* @param {number | string} [signal='SIGTERM'] - Signal to send.
|
|
56
67
|
* @returns {Promise<number[]>} Killed PIDs.
|
|
57
68
|
* @throws {Error} No process/PID.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default options applied to all SH commands unless overridden.
|
|
3
|
+
*
|
|
4
|
+
* `cwd` is intentionally lazy: the getter resolves `process.cwd()` at read
|
|
5
|
+
* time, so commands created after a `cd()` run in the new directory instead of
|
|
6
|
+
* the directory the process was started in. `SHDispatch` spreads these
|
|
7
|
+
* defaults (`{ ...defaultOptions }`) per command, which invokes the getter and
|
|
8
|
+
* freezes the value for that command — "defaults captured at creation time".
|
|
9
|
+
* Do not "optimize" this into a static snapshot.
|
|
10
|
+
*
|
|
11
|
+
* An explicit `SH.cwd = dir` assignment takes precedence over `process.cwd()`
|
|
12
|
+
* (it only redirects SH commands; it does not `chdir` the process). `cd()`
|
|
13
|
+
* clears the override again, so the most recent of the two always wins.
|
|
14
|
+
*
|
|
15
|
+
* `maxBuffer` and `detached` are initialized explicitly so every key the `SH`
|
|
16
|
+
* proxy exposes ({@link defaultOptionKeys}) also has a defined value here.
|
|
17
|
+
* This is behavior-neutral: `SHExecute` already fell back to
|
|
18
|
+
* `DEFAULT_MAX_BUFFER` for unset/invalid values and truthy-checks `detached`.
|
|
19
|
+
*
|
|
20
|
+
* @type {import('./SHDispatch.js').SHOptions}
|
|
21
|
+
*/
|
|
22
|
+
export const defaultOptions: import("./SHDispatch.js").SHOptions;
|
|
23
|
+
/**
|
|
24
|
+
* Known global option keys exposed by the `SH` proxy in `lib/SH.js`.
|
|
25
|
+
*
|
|
26
|
+
* Derived from the own keys of {@link defaultOptions}, so the key set can
|
|
27
|
+
* never drift from the defaults. (The previous hard-coded list in `lib/SH.js`
|
|
28
|
+
* readable-exposed `maxBuffer`/`detached`, which were uninitialized in the
|
|
29
|
+
* defaults.) The proxy routes reads/writes for these keys to
|
|
30
|
+
* `defaultOptions` and throws a `TypeError` on writes to any other key
|
|
31
|
+
* (decision D1).
|
|
32
|
+
*
|
|
33
|
+
* @type {Set<string>}
|
|
34
|
+
*/
|
|
35
|
+
export const defaultOptionKeys: Set<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Clears the `SH.cwd` override so the lazy `cwd` getter follows
|
|
38
|
+
* `process.cwd()` again. Called by `cd()` after a successful
|
|
39
|
+
* `process.chdir()`.
|
|
40
|
+
*/
|
|
41
|
+
export function clearCwdOverride(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Default maximum buffered bytes per stdout/stderr stream: 512000 (500 KiB).
|
|
44
|
+
* Used by `SHExecute` when `options.maxBuffer` is unset or invalid.
|
|
45
|
+
*/
|
|
46
|
+
export const DEFAULT_MAX_BUFFER: number;
|
|
47
|
+
/**
|
|
48
|
+
* Parses a human-readable duration into milliseconds.
|
|
49
|
+
*
|
|
50
|
+
* Accepts finite non-negative numbers (milliseconds) and strings in the exact
|
|
51
|
+
* forms `'Nms'`, `'Ns'`, or a bare `'N'`. The bare-number form is treated as
|
|
52
|
+
* milliseconds on purpose: it preserves the leniency of the former `SHExecute`
|
|
53
|
+
* parser, so e.g. `timeout: '100'` keeps working.
|
|
54
|
+
*
|
|
55
|
+
* `null`/`undefined` are not special-cased here; call sites that allow an
|
|
56
|
+
* absent duration handle it themselves (e.g. `parseDuration(timeout ?? 0)`).
|
|
57
|
+
*
|
|
58
|
+
* @param {number|string} d - Duration as number (ms) or string ('5s', '100ms', '100').
|
|
59
|
+
* @returns {number} Duration in milliseconds.
|
|
60
|
+
* @throws {Error} If the duration type or format is invalid.
|
|
61
|
+
*/
|
|
62
|
+
export function parseDuration(d: number | string): number;
|