@j-o-r/sh 1.1.31 → 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/types/SH.d.ts CHANGED
@@ -7,11 +7,8 @@
7
7
  export type ArgsObject = {
8
8
  _: string[];
9
9
  } & {
10
- [x: string]: string | true | string[];
10
+ [x: string]: string | true;
11
11
  };
12
- export type RejectCallback = Function;
13
- export type ResolveCallback = Function;
14
- export type SHOptions = import("./SHDispatch.js").SHOptions;
15
12
  export type AbortableInput = {
16
13
  /**
17
14
  * - Promise that resolves to user input or void if aborted.
@@ -23,9 +20,9 @@ export type AbortableInput = {
23
20
  abort: () => void;
24
21
  };
25
22
  /**
26
- * Generator-like object that yields retry delay durations.
23
+ * Generator that yields retry delay durations in milliseconds.
27
24
  */
28
- export type ExpBackoffGenerator = Iterator<number | string>;
25
+ export type ExpBackoffGenerator = Generator<number, void, unknown>;
29
26
  /**
30
27
  * Template tag for building and executing shell commands.
31
28
  *
@@ -39,10 +36,15 @@ export type ExpBackoffGenerator = Iterator<number | string>;
39
36
  * command separators. It is not safe for untrusted input. Wrap untrusted values
40
37
  * with {@link bashEscape} before interpolating them.
41
38
  *
42
- * SH acts as both template tag and options setter: `SH.timeout = 5000; SH`cmd``
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).
43
45
  *
44
46
  * @param {TemplateStringsArray} pieces - String literals from template.
45
- * @param {...unknown[]} args - Values to interpolate.
47
+ * @param {...unknown} args - Values to interpolate.
46
48
  * @returns {SHDispatch} Command dispatcher.
47
49
  * @throws {Error} If pieces contain undefined.
48
50
  * @example
@@ -54,11 +56,19 @@ export type ExpBackoffGenerator = Iterator<number | string>;
54
56
  *
55
57
  * // Quote untrusted values explicitly
56
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();
57
63
  */
58
64
  export const SH: any;
59
65
  /**
60
66
  * Changes the current working directory.
61
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
+ *
62
72
  * @param {string} dir - Path to new directory.
63
73
  * @example
64
74
  * cd('/tmp');
@@ -76,21 +86,29 @@ export function sleep(duration: string | number): Promise<void>;
76
86
  /**
77
87
  * Retries an async function up to N times with optional delays.
78
88
  *
79
- * Delay can be fixed ('1s'), generator-like (expBackoff()), or none.
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.
80
95
  *
81
96
  * @param {number} count - Positive integer number of attempts.
82
- * @param {string|number|ExpBackoffGenerator|Function} delayOrCallback - Delay, delay generator, or callback if no separate callback is supplied.
83
- * @param {Function} [callback] - Callback to retry when a delay is supplied.
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.
84
99
  * @returns {Promise<any>} Successful result.
85
100
  * @throws {Error} If arguments are invalid, or the last callback error if all attempts fail.
86
101
  * @example
87
102
  * await retry(3, '1s', () => SH`curl http://unreliable`.run());
88
103
  * await retry(3, expBackoff(), () => flakyOp());
89
104
  */
90
- export function retry(count: number, delayOrCallback: string | number | ExpBackoffGenerator | Function, callback?: Function): Promise<any>;
105
+ export function retry(count: number, delayOrCallback: string | number | Iterator<number | string> | (() => (Promise<any> | any)), callback?: () => (Promise<any> | any)): Promise<any>;
91
106
  /**
92
107
  * Reads entire stdin as UTF-8 string. Resolves to empty string if TTY (no pipe).
93
108
  *
109
+ * Side effect: permanently sets the `process.stdin` encoding to utf8 and
110
+ * switches it to flowing mode.
111
+ *
94
112
  * @returns {Promise<string>} Stdin content, or an empty string if TTY.
95
113
  * @example
96
114
  * const input = await readIn(); // Use in piped scripts
@@ -99,7 +117,14 @@ export function readIn(): Promise<string>;
99
117
  /**
100
118
  * Prompts user for input on stdin with custom prompt.
101
119
  *
102
- * Supports aborting input collection.
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`.
103
128
  *
104
129
  * @param {string} prompt - Prompt text to display.
105
130
  * @returns {AbortableInput} Object with input Promise and abort function.
@@ -110,9 +135,13 @@ export function readIn(): Promise<string>;
110
135
  */
111
136
  export function userIn(prompt: string): AbortableInput;
112
137
  /**
113
- * Executes a callback in a new async context (fresh callstack).
138
+ * Awaits `callback()` and returns its result.
114
139
  *
115
- * Useful for parallel operations without nesting.
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).
116
145
  *
117
146
  * @param {() => Promise<any>} callback - Async function to execute.
118
147
  * @returns {Promise<any>} Result of callback.
@@ -129,18 +158,21 @@ export function within(callback: () => Promise<any>): Promise<any>;
129
158
  * @param {string} [max='60s'] - Max backoff duration.
130
159
  * @param {string} [rand='100ms'] - Max jitter.
131
160
  * @yields {number} Next backoff ms.
161
+ * @returns {ExpBackoffGenerator} Infinite generator of backoff delays in ms.
132
162
  * @example
133
163
  * const backoff = expBackoff();
134
164
  * await sleep(backoff.next().value);
135
165
  */
136
- export function expBackoff(max?: string, rand?: string): Generator<number, void, unknown>;
166
+ export function expBackoff(max?: string, rand?: string): ExpBackoffGenerator;
137
167
  /**
138
168
  * Parses CLI arguments into an object.
139
169
  *
140
170
  * Supports --key value, -k value (no shorts grouped).
141
171
  * Bares go to _[]. Duplicates error. No = syntax.
142
172
  * Negative number tokens such as -1, -0.5, and -1e3 are parsed as values or
143
- * positionals, not options.
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.
144
176
  *
145
177
  * Defaults to process.argv.slice(2).
146
178
  *
@@ -150,6 +182,8 @@ export function expBackoff(max?: string, rand?: string): Generator<number, void,
150
182
  * @example
151
183
  * parseArgs(['--port', '8080', 'file.txt']); // { port: '8080', _: ['file.txt'] }
152
184
  * parseArgs(['--n', '-1']); // { n: '-1', _: [] }
185
+ * parseArgs(['-', '--port', '1']); // { port: '1', _: ['-'] }
186
+ * parseArgs(['--', '--x']); // { _: ['--x'] }
153
187
  */
154
188
  export function parseArgs(args?: string[]): ArgsObject;
155
189
  /**
@@ -158,18 +192,7 @@ export function parseArgs(args?: string[]): ArgsObject;
158
192
  * Named options map to their string value, or `true` when no value is supplied.
159
193
  * Bare positional arguments are collected in `_`.
160
194
  *
161
- * @typedef {{_: string[]} & Object.<string, string|true|string[]>} ArgsObject
162
- */
163
- /**
164
- * @typedef {Function} RejectCallback
165
- * @param {Error} error - The error object passed to the callback.
166
- */
167
- /**
168
- * @typedef {Function} ResolveCallback
169
- * @param {any} [param] - Optional callback any value
170
- */
171
- /**
172
- * @typedef {import('./SHDispatch.js').SHOptions} SHOptions
195
+ * @typedef {{_: string[]} & Object.<string, string|true>} ArgsObject
173
196
  */
174
197
  /**
175
198
  * @typedef {Object} AbortableInput
@@ -177,22 +200,29 @@ export function parseArgs(args?: string[]): ArgsObject;
177
200
  * @property {() => void} abort - Function to abort input collection.
178
201
  */
179
202
  /**
180
- * Generator-like object that yields retry delay durations.
203
+ * Generator that yields retry delay durations in milliseconds.
181
204
  *
182
- * @typedef {Iterator<number|string>} ExpBackoffGenerator
205
+ * @typedef {Generator<number, void, unknown>} ExpBackoffGenerator
183
206
  */
184
207
  /**
185
208
  * Utility to determine the JavaScript type of a value.
186
209
  *
187
- * @param {any} fn - Any value to inspect.
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.
188
215
  * @returns {string} The "real" object type name (e.g., 'Array', 'Promise') or primitive typeof.
189
216
  * @example
190
217
  * jsType([]); // 'Array'
191
218
  * jsType(Promise.resolve()); // 'Promise'
219
+ * jsType(null); // 'Null'
220
+ * jsType(undefined); // 'undefined'
192
221
  */
193
- export function jsType(fn: any): string;
222
+ export function jsType(value: unknown): string;
194
223
  /**
195
- * Checks if an object has a specific own property (code-safe).
224
+ * Checks if an object has a specific own property. Safe for null-prototype
225
+ * objects and objects shadowing `hasOwnProperty`.
196
226
  *
197
227
  * @param {any} o - Object to examine.
198
228
  * @param {string} p - Property name to check.
@@ -1,30 +1,4 @@
1
1
  export default SHDispatch;
2
- export type SpawnSyncResponse = {
3
- /**
4
- * - Exit code (null if signal).
5
- */
6
- status: number | null;
7
- /**
8
- * - Terminating signal.
9
- */
10
- signal: string | null;
11
- /**
12
- * - [stdin, stdout, stderr].
13
- */
14
- output: (string | Buffer | null)[];
15
- /**
16
- * - Process ID.
17
- */
18
- pid: number;
19
- /**
20
- * - Captured stdout.
21
- */
22
- stdout: string | Buffer | null;
23
- /**
24
- * - Captured stderr.
25
- */
26
- stderr: string | Buffer | null;
27
- };
28
2
  export type StdioOption = ("pipe" | "ignore" | "inherit" | number);
29
3
  export type StdioOptions = Array<StdioOption> | StdioOption;
30
4
  /**
@@ -36,7 +10,7 @@ export type StdioOptions = Array<StdioOption> | StdioOption;
36
10
  * - `shell`: `'bash'` (string/true → shell; false → no-shell `/usr/bin/env -S`)
37
11
  * - `stdio`: `['inherit', 'pipe', 'pipe']`
38
12
  * - `timeout`: `0` (no timeout; rolling on data)
39
- * - `maxBuffer`: `512000` (500 kb per stream in SHExecute)
13
+ * - `maxBuffer`: `512000` (500 KiB per stream in SHExecute)
40
14
  *
41
15
  * Prefix (`.options(undefined, prefix)`) only for shell mode.
42
16
  */
@@ -58,7 +32,10 @@ export type SHOptions = {
58
32
  */
59
33
  stdio: StdioOptions;
60
34
  /**
61
- * - Rolling timeout in ms or duration string; 0 disables timeout.
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.
62
39
  */
63
40
  timeout: number | string;
64
41
  /**
@@ -66,7 +43,9 @@ export type SHOptions = {
66
43
  */
67
44
  maxBuffer?: number | undefined;
68
45
  /**
69
- * - Run process detached and resolve early.
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.
70
49
  */
71
50
  detached?: boolean | undefined;
72
51
  };
@@ -104,6 +83,10 @@ declare class SHDispatch {
104
83
  /**
105
84
  * Async run: Captures stdout; rejects on error/timeout.
106
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
+ *
107
90
  * @param {string} [payload] - Stdin payload.
108
91
  * @returns {Promise<string>} Stdout.
109
92
  */
@@ -1,10 +1,9 @@
1
1
  export default SHExecute;
2
- export type SHExecuteOptions = any & {
3
- maxBuffer?: number;
4
- };
2
+ export type SHExecuteOptions = import("./SHDispatch.js").SHOptions;
5
3
  /**
6
- * @typedef {import('../SH.js').SHOptions & { maxBuffer?: number }} SHExecuteOptions
7
- * @description Extended options for SHExecute: adds `maxBuffer` (bytes per stream, default 1MB).
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
- * - **Buffering**: Captures stdout/stderr up to `maxBuffer` (1MB default); appends truncation markers.
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
- * - **Kill**: Terminates process + children via pgrep.
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; rejects on error/timeout/kill.
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
- * @throws {Error} Command failure (incl. code, stderr), timeout, kill, spawn error.
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;