@orkestrel/process 0.0.3 → 0.0.4

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.
@@ -2,7 +2,13 @@ import { Buffer as Buffer_2 } from 'node:buffer';
2
2
  import { DetachOptions } from '@orkestrel/process';
3
3
  import { EmitterInterface } from '@orkestrel/emitter';
4
4
  import { ExecutableOptions } from '@orkestrel/process';
5
+ import { ExecuteInput } from '@orkestrel/process';
6
+ import { ExecuteOptions } from '@orkestrel/process';
7
+ import { ExecuteResult } from '@orkestrel/process';
8
+ import { ExecuteResult as ExecuteResult_2 } from '@orkestrel/process';
9
+ import { ExecuteSyncOptions } from '@orkestrel/process';
5
10
  import { ProcessCommand } from '@orkestrel/process';
11
+ import { ProcessCommand as ProcessCommand_2 } from '@orkestrel/process';
6
12
  import { ProcessEventMap } from '@orkestrel/process';
7
13
  import { ProcessExit } from '@orkestrel/process';
8
14
  import { ProcessInterface } from '@orkestrel/process';
@@ -10,19 +16,43 @@ import { ProcessManagerEventMap } from '@orkestrel/process';
10
16
  import { ProcessManagerInterface } from '@orkestrel/process';
11
17
  import { ProcessManagerOptions } from '@orkestrel/process';
12
18
  import { ProcessOptions } from '@orkestrel/process';
13
- import { RunInput } from '@orkestrel/process';
14
- import { RunOptions } from '@orkestrel/process';
15
- import { RunResult } from '@orkestrel/process';
16
- import { RunSyncOptions } from '@orkestrel/process';
17
19
  import { SpawnInput } from '@orkestrel/process';
18
20
 
19
21
  /**
20
- * Builds one settled {@link RunResult} from a completed run's captured bytes and terminal facts.
22
+ * Builds the executable candidates an explicit platform would search.
23
+ *
24
+ * @remarks
25
+ * Windows alone needs this list: the host searches the working directory before `PATH` and applies
26
+ * `PATHEXT`, and Node reproduces neither for a direct spawn. Within each searched directory the
27
+ * literal name is tried first and each `PATHEXT` candidate after it, whether or not the name already
28
+ * carries an extension — so `report.txt` resolves to a `report.txt` file where one exists and to
29
+ * `report.txt.cmd` where none does. The lookup reads the child's effective environment, so an
30
+ * overridden `PATH` selects the executable the child would have found. A POSIX host resolves the
31
+ * file itself, so the candidate list there is empty. An appended extension is spelled the way
32
+ * `PATHEXT` spells it rather than the way the directory entry does, which the case-insensitive host
33
+ * treats as the same file. A non-Windows platform returns an empty list because `execvp` performs
34
+ * its own lookup.
35
+ *
36
+ * @param file - The command executable name or path
37
+ * @param workspace - The directory searched first
38
+ * @param environment - The child's effective environment
39
+ * @param platform - The platform whose lookup rules apply
40
+ * @returns The ordered absolute candidate paths
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * buildExecutableCandidates('git', 'C:\\work', { PATH: 'C:\\bin' }, 'win32')
45
+ * ```
46
+ */
47
+ export declare function buildExecutableCandidates(file: string, workspace: string, environment: Readonly<Record<string, string | undefined>>, platform: NodeJS.Platform): readonly string[];
48
+
49
+ /**
50
+ * Builds one settled {@link ExecuteResult} from a completed run's captured bytes and terminal facts.
21
51
  *
22
52
  * @remarks
23
53
  * `failed` is derived: a run failed when it timed out, was aborted, ended on a host fault, was ended
24
54
  * by a signal, or exited with a code other than `0` — a `null` code from a spawn fault is therefore
25
- * a failure. Both byte fields are bounded by `limit` on a code-point boundary here, which is the one
55
+ * a failure. Each byte field is bounded by `limit` on a code-point boundary here, which is the one
26
56
  * place the captured bytes are decoded.
27
57
  *
28
58
  * @param input - The captured bytes, terminal state, and capture limit
@@ -30,7 +60,7 @@ import { SpawnInput } from '@orkestrel/process';
30
60
  *
31
61
  * @example
32
62
  * ```ts
33
- * buildRunResult({
63
+ * buildExecuteResult({
34
64
  * command: 'git status',
35
65
  * stdout: Buffer.from('ok'),
36
66
  * stderr: Buffer.alloc(0),
@@ -43,20 +73,37 @@ import { SpawnInput } from '@orkestrel/process';
43
73
  * }).failed // false
44
74
  * ```
45
75
  */
46
- export declare function buildRunResult(input: RunInput): RunResult;
76
+ export declare function buildExecuteResult(input: ExecuteInput): ExecuteResult;
47
77
 
48
78
  /**
49
- * Builds the resolved spawn form of one command.
79
+ * Builds a spawn form from a resolved file and an explicit platform.
50
80
  *
51
81
  * @remarks
52
- * The executable is resolved against the child's effective environment. A resolved `.cmd` or `.bat`
53
- * script cannot be spawned directly **on Windows**, so it runs there through an explicitly quoted
54
- * `cmd.exe /d /s /c` command line with the argument vector passed verbatim. No path uses a shell,
55
- * so a metacharacter in an argument is never interpreted. `cmd.exe` expands `%NAME%` before it
56
- * parses quotes, so no quoting can carry a percent sign through to a Windows batch target: an
57
- * argument carrying one is refused there rather than silently rewritten. The whole batch path is
58
- * Windows-only. A POSIX host has no `cmd.exe` and no restriction on spawning a file directly, so a
59
- * target named `worker.cmd` spawns directly there and receives a percent sign as literal text.
82
+ * A resolved `.cmd` or `.bat` script cannot be spawned directly **on Windows**, so it runs there
83
+ * through an explicitly quoted `cmd.exe /d /s /c` command line with the argument vector passed
84
+ * verbatim. No path uses a shell, so a metacharacter in an argument is never interpreted. `cmd.exe`
85
+ * expands `%NAME%` before it parses quotes, so no quoting can carry a percent sign through to a
86
+ * Windows batch target: an argument carrying one is refused there rather than silently rewritten.
87
+ * The whole batch path is Windows-only. A POSIX host has no `cmd.exe` and no restriction on spawning
88
+ * a file directly, so a target named `worker.cmd` spawns directly there and receives a percent sign
89
+ * as literal text.
90
+ *
91
+ * @param command - The executable and its argument vector
92
+ * @param file - The resolved executable file
93
+ * @param environment - The child's effective environment
94
+ * @param platform - The platform whose batch rules apply
95
+ * @returns The file, argument vector, and verbatim flag to spawn with
96
+ * @throws A {@link ProcessError} coded `invalid` when `platform` is `win32`, the resolved target is a batch script, and an argument carries a percent sign
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * buildPlatformSpawn({ file: 'node', arguments: ['--version'] }, 'node', {}, 'linux').verbatim // false
101
+ * ```
102
+ */
103
+ export declare function buildPlatformSpawn(command: ProcessCommand, file: string, environment: Readonly<Record<string, string | undefined>>, platform: NodeJS.Platform): SpawnInput;
104
+
105
+ /**
106
+ * Builds the resolved spawn form of one command for the current host.
60
107
  *
61
108
  * @param command - The executable and its argument vector
62
109
  * @param options - The directory searched first and the child's effective environment
@@ -123,13 +170,70 @@ export declare function createProcessManager(options?: ProcessManagerOptions): P
123
170
  * detach({ file: 'node', arguments: ['daemon.js'] }, { workspace: process.cwd() })
124
171
  * ```
125
172
  */
126
- export declare function detach(command: ProcessCommand, options?: DetachOptions): void;
173
+ export declare function detach(command: ProcessCommand_2, options?: DetachOptions): void;
174
+
175
+ /**
176
+ * Runs one command to completion, buffering its output, and settles with the outcome.
177
+ *
178
+ * @remarks
179
+ * The executable is resolved through {@link buildSpawn}, so no run uses a shell. On a POSIX host the
180
+ * child is detached, which is what lets its whole process group be terminated. Standard output and
181
+ * error are each byte-bounded by `limit` (default {@link PROCESS_OUTPUT}) and `truncated` reports
182
+ * whether either stream exceeded it. A positive `timeout` and an aborting `signal` both terminate the
183
+ * child through the same bounded stop, and only the earliest is recorded: a timeout reports
184
+ * `expired` and an abort reports `aborted`, never both. After termination the outcome is awaited for
185
+ * a bounded window, so a descendant holding the child's stdio cannot keep the run pending. That bound
186
+ * covers a terminated run alone: a run with no `timeout` and no `signal` settles on stdio completion
187
+ * rather than on process exit, so a descendant that inherited the child's stdio holds the run open
188
+ * after the child itself has gone. Give such a run a `timeout`. The child's `environment` merges over
189
+ * the parent unless the command is `isolated`, then `options.environment` on top, and `options.input`
190
+ * overrides `command.input`. Unless `strict` is `false`, a failed run rejects with a
191
+ * {@link createExecuteError} carrying the {@link ExecuteResult}. An invalid option or command string rejects
192
+ * before the child is spawned, because an async function cannot throw synchronously.
193
+ *
194
+ * @param command - The executable, arguments, and optional environment and input
195
+ * @param options - Working directory, timeout, grace, signal, capture limit, and failure delivery
196
+ * @returns The settled run outcome
197
+ * @throws A {@link ProcessError} coded `invalid` for a malformed option, command string, or batch-bound argument, or one carrying the {@link ExecuteResult} when the run failed and `strict` is not `false`
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * const result = await execute({ file: 'git', arguments: ['status'] }, { workspace: process.cwd() })
202
+ * ```
203
+ */
204
+ export declare function execute(command: ProcessCommand_2, options?: ExecuteOptions): Promise<ExecuteResult_2>;
205
+
206
+ /**
207
+ * Runs one command to completion synchronously, buffering its output, and returns the outcome.
208
+ *
209
+ * @remarks
210
+ * The synchronous counterpart of {@link execute}, spawned through the same resolver and never through a
211
+ * shell. The host offers no cooperative termination window and no in-flight cancellation, so this
212
+ * contract carries neither. A positive `timeout` ends only the root process and can leave
213
+ * descendants running; use {@link execute} or {@link Process} when timeout must terminate the tree. A
214
+ * timeout and an output overflow both end the root with `SIGKILL`: an overflow reports `truncated`
215
+ * and `failed` together and trims the partial output to `limit`, where {@link execute} keeps reading and
216
+ * reports `truncated` without failing. The environment and input follow the same merge as
217
+ * {@link execute}. Unless `strict` is `false`, a failed run throws a {@link createExecuteError}
218
+ * carrying the {@link ExecuteResult}.
219
+ *
220
+ * @param command - The executable, arguments, and optional environment and input
221
+ * @param options - Working directory, timeout, capture limit, and failure delivery
222
+ * @returns The run outcome
223
+ * @throws A {@link ProcessError} coded `invalid` for a malformed option, command string, or batch-bound argument, or one carrying the {@link ExecuteResult} when the run failed and `strict` is not `false`
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * const result = executeSync({ file: 'git', arguments: ['--version'] }, { strict: false })
228
+ * ```
229
+ */
230
+ export declare function executeSync(command: ProcessCommand_2, options?: ExecuteSyncOptions): ExecuteResult_2;
127
231
 
128
232
  /**
129
233
  * Renders one command into its diagnostic command line.
130
234
  *
131
235
  * @param command - The executable and its argument vector
132
- * @returns The space-joined command line, for a {@link RunResult} and error messages
236
+ * @returns The space-joined command line, for an {@link ExecuteResult} and error messages
133
237
  *
134
238
  * @example
135
239
  * ```ts
@@ -178,10 +282,11 @@ export declare function isFile(target: string): boolean;
178
282
  *
179
283
  * @remarks
180
284
  * On a POSIX host a child spawned `detached` leads its own process group, so signalling the
181
- * negated pid reaches the whole tree. On Windows, or when no pid is available, the signal goes to
182
- * the child directly. A throw during signalling is swallowed: the process can exit between the
183
- * caller's liveness check and this call, and the child's native exit remains the authoritative
184
- * terminal state.
285
+ * negated pid reaches the whole tree. When no group owns the pid and the host reports `ESRCH`, the
286
+ * signal falls back to the child directly. On Windows, or when no pid is available, the signal also
287
+ * goes to the child directly. Every other throw during signalling is swallowed: the process can
288
+ * exit between the caller's liveness check and this call, and the child's native exit remains the
289
+ * authoritative terminal state.
185
290
  *
186
291
  * @param child - The child boundary
187
292
  * @param signal - The signal to deliver
@@ -216,13 +321,30 @@ export declare function killProcess(child: Pick<ProcessChild, 'pid' | 'kill'>, s
216
321
  export declare function killTree(pid: number, timeout: number): Promise<boolean>;
217
322
 
218
323
  /**
219
- * Merges environment overrides into the environment one child receives.
324
+ * Merges environment overrides into the environment one child receives on the current host.
325
+ *
326
+ * @param isolated - If `true`, the parent environment is excluded; if `false`, the overrides layer over it
327
+ * @param base - The command's own environment overrides
328
+ * @param override - Per-invocation overrides applied last
329
+ * @returns The environment for a spawn, carrying no unset key
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * mergeEnvironment(true, { TOKEN: 'a' }) // { TOKEN: 'a' }
334
+ * ```
335
+ */
336
+ export declare function mergeEnvironment(isolated: boolean, base?: Readonly<Record<string, string | undefined>>, override?: Readonly<Record<string, string | undefined>>): NodeJS.ProcessEnv;
337
+
338
+ /**
339
+ * Merges environment layers under an explicit platform's key rules.
220
340
  *
221
341
  * @remarks
222
- * Later maps override earlier ones and an `undefined` value unsets a key. On Windows the keys fold
342
+ * Later maps override earlier ones and an `undefined` value unsets a key. For `win32` the keys fold
223
343
  * case-insensitively and the last writer wins, so `PATH` followed by `Path` yields one variable
224
344
  * rather than two the host would resolve unpredictably.
225
345
  *
346
+ * @param platform - The platform whose key rules apply
347
+ * @param parent - The parent environment layer
226
348
  * @param isolated - If `true`, the parent environment is excluded; if `false`, the overrides layer over it
227
349
  * @param base - The command's own environment overrides
228
350
  * @param override - Per-invocation overrides applied last
@@ -230,30 +352,32 @@ export declare function killTree(pid: number, timeout: number): Promise<boolean>
230
352
  *
231
353
  * @example
232
354
  * ```ts
233
- * mergeEnvironment(false, { TOKEN: 'a' }, { TOKEN: undefined }) // TOKEN unset
355
+ * mergePlatformEnvironment('linux', {}, false, { TOKEN: 'a' }, { TOKEN: undefined }) // TOKEN unset
234
356
  * ```
235
357
  */
236
- export declare function mergeEnvironment(isolated: boolean, base?: Readonly<Record<string, string | undefined>>, override?: Readonly<Record<string, string | undefined>>): NodeJS.ProcessEnv;
358
+ export declare function mergePlatformEnvironment(platform: NodeJS.Platform, parent: Readonly<Record<string, string | undefined>>, isolated: boolean, base?: Readonly<Record<string, string | undefined>>, override?: Readonly<Record<string, string | undefined>>): NodeJS.ProcessEnv;
237
359
 
238
360
  /**
239
361
  * One supervised child process with framed output, a bounded backlog, and bounded termination.
240
362
  *
241
363
  * @remarks
242
364
  * Standard output is framed through `readline` into a single-consumer stream: each line goes to
243
- * exactly one waiting iterator, so two iterators split the output rather than each receiving all of
244
- * it. While no consumer has ever requested an iterator,
245
- * stdout keeps draining so `exit` still resolves, and retention stops at the `backlog` mark: a
246
- * consumer attaching later receives the retained head, a gap, then the live stream. Once an iterator
365
+ * exactly one waiting iterator, so concurrent iterators split the output rather than each receiving
366
+ * all of it. While no consumer has ever requested an iterator, stdout keeps draining so `exit` still
367
+ * resolves, and retention stops at the `backlog` mark: a
368
+ * consumer attaching later receives the retained head, a gap, then the live stream. After an iterator
247
369
  * has been requested, stdout pauses at the mark and resumes at half of it, so that consumer loses
248
- * nothing and the child feels real backpressure. The mark is soft — the host delivers whole chunks,
249
- * so the backlog overshoots it by up to one delivered chunk plus the drain that termination
250
- * releases. Standard error is decoded and forwarded live as the `stderr` event while a byte-bounded
251
- * raw tail is retained as `evidence`. The typed `emitter` also carries the child `error` cause on a
252
- * spawn fault and the terminal `exit`, alongside the `exit` promise. `stop` ends the whole tree and
253
- * reports whether the native exit was observed; `destroy` stops the child, destroys stdin so every
254
- * pending `send` settles, and destroys the emitter last. `destroy` resolving does not imply the
255
- * child's stdio has closed, so `exit` and `lines` can still be outstanding when a descendant holds
256
- * an inherited pipe.
370
+ * nothing before termination and the child feels real backpressure. The mark is soft — the ordinary backlog can
371
+ * overshoot it by the line that crossed it plus the rest of its delivered chunk. Termination never
372
+ * reapplies backpressure, and retained lines are capped at twice `backlog`; later lines are dropped
373
+ * and `truncated` reports the omission. Standard error is decoded and forwarded live as the `stderr`
374
+ * event while a byte-bounded raw tail is retained as `evidence`. The typed `emitter` also carries the
375
+ * child `error` cause on a spawn fault and the terminal `exit`, alongside the `exit` promise. `pid`,
376
+ * `code`, and `signal` read the spawned child directly, so the native exit is readable before the
377
+ * `exit` promise settles on stdio close. `stop` ends the whole tree and reports whether the native
378
+ * exit was observed; `destroy` stops the child, destroys stdin so every pending `send` settles, and
379
+ * destroys the emitter last. `destroy` resolving does not imply the child's stdio has closed, so
380
+ * `exit` and `lines` can still be outstanding when a descendant holds an inherited pipe.
257
381
  *
258
382
  * @example
259
383
  * ```ts
@@ -276,12 +400,20 @@ export declare class Process implements ProcessInterface {
276
400
  * @throws A {@link ProcessError} coded `invalid` when an option or command string is malformed
277
401
  */
278
402
  constructor(options: ProcessOptions);
403
+ /** The host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
404
+ get pid(): number | undefined;
405
+ /** The exit code the host recorded, or `null` while the child has not exited and when a signal ended it. */
406
+ get code(): number | null;
407
+ /** The terminating signal name the host recorded, or `null` while the child has not exited and when it exited on its own. */
408
+ get signal(): string | null;
279
409
  /** The typed lifecycle observation surface. */
280
410
  get emitter(): EmitterInterface<ProcessEventMap>;
281
411
  /** The captured stdout lines, in arrival order, ending when the child's stdout closes. */
282
412
  get lines(): AsyncIterable<string>;
283
413
  /** The decoded byte-bounded stderr tail. */
284
414
  get evidence(): string;
415
+ /** True when the `lines` stream omitted output after a retention bound was reached. */
416
+ get truncated(): boolean;
285
417
  /** The terminal child state, observed once from the close event. */
286
418
  get exit(): Promise<ProcessExit>;
287
419
  /**
@@ -329,11 +461,16 @@ export declare class Process implements ProcessInterface {
329
461
  * The child boundary the termination helpers drive.
330
462
  *
331
463
  * @remarks
332
- * A `ChildProcess` satisfies this structurally, and so does any object carrying the same four
464
+ * A `ChildProcess` satisfies this structurally, and so does any object carrying the same
333
465
  * members, which is what lets a caller drive `stopChild` over a child it spawned itself. Each helper
334
466
  * takes the slice of this contract it reads: `exitCode` and `signalCode` are the host's
335
467
  * authoritative liveness answer, `pid` addresses a POSIX process group, `kill` delivers one signal,
336
- * and `once` reports the native exit.
468
+ * `once` reports the native exit, and `off` releases that listener when the wait ends at its
469
+ * deadline instead.
470
+ *
471
+ * A non-detached child stays in its caller's process group, so no group carries its pid. `kill` is
472
+ * the route that reaches it: `killProcess` signals the negated `pid` first and falls back to `kill`
473
+ * when the host reports that no group owns that pid.
337
474
  */
338
475
  export declare interface ProcessChild {
339
476
  /** The process id the host assigned, or `undefined` when the spawn never produced one. */
@@ -353,10 +490,22 @@ export declare interface ProcessChild {
353
490
  * Register a one-shot listener for the native exit.
354
491
  *
355
492
  * @param event - The `exit` event name
356
- * @param listener - The listener invoked once the process exits
493
+ * @param listener - The listener invoked after the process exits
357
494
  * @returns Whatever the emitter returns, which the helpers ignore
358
495
  */
359
496
  once(event: 'exit', listener: () => void): unknown;
497
+ /**
498
+ * Release one previously registered exit listener.
499
+ *
500
+ * @remarks
501
+ * `waitForExit` calls this when its deadline elapses before the exit, so a child that outlives
502
+ * several bounded waits accumulates no listeners.
503
+ *
504
+ * @param event - The `exit` event name
505
+ * @param listener - The listener registered through `once`
506
+ * @returns Whatever the emitter returns, which the helpers ignore
507
+ */
508
+ off(event: 'exit', listener: () => void): unknown;
360
509
  }
361
510
 
362
511
  /**
@@ -421,8 +570,9 @@ export declare class ProcessManager implements ProcessManagerInterface {
421
570
  * nothing has started and a throw from one strands no process. A getter that begins `destroy`
422
571
  * without throwing is the one remaining race, and it leaves a bounded residual: the child is
423
572
  * already spawned, so the launch is refused with a {@link createProtocolError} and that child is
424
- * torn down asynchronously, bounded by `grace` plus the confirmation window. The `destroy` barrier
425
- * settled before the refusal, so awaiting it does not cover that teardown.
573
+ * torn down asynchronously, bounded by `grace` plus the confirmation window. The `protocol`
574
+ * refusal throws synchronously before the `destroy` barrier settles. The barrier does not cover
575
+ * that child's asynchronous teardown.
426
576
  *
427
577
  * @param id - The registry key, unique among live children
428
578
  * @param options - The child construction options
@@ -469,8 +619,9 @@ export declare class ProcessManager implements ProcessManagerInterface {
469
619
  * @remarks
470
620
  * A token carrying whitespace or a shell metacharacter is wrapped in double quotes so `cmd.exe`
471
621
  * passes it as one literal argument; an embedded double quote is doubled, which is how `cmd.exe`
472
- * reads a literal quote inside a quoted token. Every other token is left exactly as written, so a
473
- * batch script still receives `%1` without added quotes.
622
+ * reads a literal quote inside a quoted token. The quoted set includes `%`, although quoting cannot
623
+ * stop `cmd.exe` expansion; {@link buildSpawn} therefore refuses `%` in an argument to a Windows
624
+ * batch target.
474
625
  *
475
626
  * @param value - The token to quote
476
627
  * @returns The token, quoted only when `cmd.exe` would otherwise split or interpret it
@@ -483,139 +634,127 @@ export declare class ProcessManager implements ProcessManagerInterface {
483
634
  export declare function quoteArgument(value: string): string;
484
635
 
485
636
  /**
486
- * Reads one environment variable the way the host resolves it.
637
+ * Reads one environment variable under an explicit platform's key rules.
487
638
  *
488
639
  * @remarks
489
640
  * Windows environment keys are case-insensitive, so a merged record can hold `Path` where the
490
- * caller asks for `PATH`. The exact key is tried first and a folded scan runs only on Windows.
641
+ * caller asks for `PATH`. The exact key is tried first and a folded scan runs only for `win32`.
491
642
  *
492
643
  * @param environment - The environment record to read
493
644
  * @param name - The variable name
645
+ * @param platform - The platform whose key rules apply
494
646
  * @returns The value, or `undefined` when the environment declares no such variable
495
647
  *
496
648
  * @example
497
649
  * ```ts
498
- * readVariable({ Path: 'C:\\Windows' }, 'PATH') // 'C:\\Windows' on Windows
650
+ * readPlatformVariable({ Path: 'C:\\Windows' }, 'PATH', 'win32') // 'C:\\Windows'
499
651
  * ```
500
652
  */
501
- export declare function readVariable(environment: Readonly<Record<string, string | undefined>>, name: string): string | undefined;
653
+ export declare function readPlatformVariable(environment: Readonly<Record<string, string | undefined>>, name: string, platform: NodeJS.Platform): string | undefined;
502
654
 
503
655
  /**
504
- * Resolves a command file to the executable path the host would launch.
505
- *
506
- * @remarks
507
- * Windows alone needs this: the host searches the working directory before `PATH` and applies
508
- * `PATHEXT`, and Node reproduces neither for a direct spawn. Within each searched directory the
509
- * literal name is tried first and each `PATHEXT` candidate after it, whether or not the name already
510
- * carries an extension — so `report.txt` resolves to a `report.txt` file where one exists and to
511
- * `report.txt.cmd` where none does. The lookup reads the child's effective environment, so an
512
- * overridden `PATH` selects the executable the child would have found. A POSIX host resolves the
513
- * file itself, so the answer there is always `undefined`. An appended extension is spelled the way
514
- * `PATHEXT` spells it rather than the way the directory entry does, which the case-insensitive host
515
- * treats as the same file.
656
+ * Reads one environment variable the way the current host resolves it.
516
657
  *
517
- * @param file - The command executable name or path
518
- * @param options - The directory searched first and the child's effective environment
519
- * @returns The resolved absolute path, or `undefined` when no candidate is a regular file
658
+ * @param environment - The environment record to read
659
+ * @param name - The variable name
660
+ * @returns The value, or `undefined` when the environment declares no such variable
520
661
  *
521
662
  * @example
522
663
  * ```ts
523
- * resolveExecutable('git', {}) ?? 'git' // an absolute path on Windows, 'git' elsewhere
664
+ * readVariable({ PATH: '/usr/bin' }, 'PATH') // '/usr/bin'
524
665
  * ```
525
666
  */
526
- export declare function resolveExecutable(file: string, options?: ExecutableOptions): string | undefined;
667
+ export declare function readVariable(environment: Readonly<Record<string, string | undefined>>, name: string): string | undefined;
527
668
 
528
669
  /**
529
- * Retains one delivered chunk up to a byte limit and records what the stream delivered.
670
+ * Resolves a command file to the executable path the host would launch.
530
671
  *
531
- * @remarks
532
- * The retained head is kept as chunk slices and joined once, so a long stream never repeatedly
533
- * concatenates a growing buffer. `counts` is a two-slot tally: slot `0` counts every delivered byte
534
- * and slot `1` counts the retained bytes, so a caller compares slot `0` against `limit` to learn
535
- * whether the capture was truncated.
536
- *
537
- * @param chunk - The delivered chunk, ignored when it is not a buffer
538
- * @param chunks - The retained head, appended to in place
539
- * @param counts - The `[delivered, retained]` tally, updated in place
540
- * @param limit - The maximum retained byte count
541
- * @returns Nothing
672
+ * @param file - The command executable name or path
673
+ * @param options - The directory searched first and the child's effective environment
674
+ * @returns The resolved absolute path, or `undefined` when no candidate is a regular file
542
675
  *
543
676
  * @example
544
677
  * ```ts
545
- * const chunks: Buffer[] = []
546
- * const counts = [0, 0]
547
- * retainChunk(Buffer.from('hello'), chunks, counts, 3)
548
- * counts[1] // 3
678
+ * resolveExecutable('git', {}) ?? 'git' // an absolute path on Windows, 'git' elsewhere
549
679
  * ```
550
680
  */
551
- export declare function retainChunk(chunk: unknown, chunks: Buffer_2[], counts: number[], limit: number): void;
681
+ export declare function resolveExecutable(file: string, options?: ExecutableOptions): string | undefined;
552
682
 
553
683
  /**
554
- * Runs one command to completion, buffering its output, and settles with the outcome.
555
- *
556
- * @remarks
557
- * The executable is resolved through {@link buildSpawn}, so no run uses a shell. On a POSIX host the
558
- * child is detached, which is what lets its whole process group be terminated. Standard output and
559
- * error are each byte-bounded by `limit` (default {@link PROCESS_OUTPUT}) and `truncated` reports
560
- * whether either stream exceeded it. A positive `timeout` and an aborting `signal` both terminate the
561
- * child through the same bounded stop, and only the first of them is recorded: a timeout reports
562
- * `expired` and an abort reports `aborted`, never both. After termination the outcome is awaited for
563
- * a bounded window, so a descendant holding the child's stdio cannot keep the run pending. That bound
564
- * covers a terminated run alone: a run with no `timeout` and no `signal` settles on stdio completion
565
- * rather than on process exit, so a descendant that inherited the child's stdio holds the run open
566
- * after the child itself has gone. Give such a run a `timeout`. The child's `environment` merges over
567
- * the parent unless the command is `isolated`, then `options.environment` on top, and `options.input`
568
- * overrides `command.input`. Unless `strict` is `false`, a failed run rejects with a
569
- * {@link createRunError} carrying the {@link RunResult}. An invalid option or command string rejects
570
- * before the child is spawned, because an async function cannot throw synchronously.
571
- *
572
- * @param command - The executable, arguments, and optional environment and input
573
- * @param options - Working directory, timeout, grace, signal, capture limit, and failure delivery
574
- * @returns The settled run outcome
575
- * @throws A {@link ProcessError} coded `invalid` for a malformed option, command string, or batch-bound argument, or one carrying the {@link RunResult} when the run failed and `strict` is not `false`
684
+ * Accumulates byte totals while retaining a bounded stream head.
576
685
  *
577
686
  * @example
578
687
  * ```ts
579
- * const result = await run({ file: 'git', arguments: ['status'] }, { workspace: process.cwd() })
688
+ * const retention = new Retention()
689
+ * retention.retain(Buffer.from('hello'), 3)?.toString('utf8') // 'hel'
690
+ * retention.retained // 3
580
691
  * ```
581
692
  */
582
- export declare function run(command: ProcessCommand, options?: RunOptions): Promise<RunResult>;
693
+ export declare class Retention implements RetentionInterface {
694
+ #private;
695
+ /** The bytes delivered by the stream. */
696
+ get delivered(): number;
697
+ /** The bytes retained from the stream head. */
698
+ get retained(): number;
699
+ /**
700
+ * Retains a delivered chunk within a byte limit.
701
+ *
702
+ * @param chunk - The delivered chunk, ignored when it is not a buffer
703
+ * @param limit - The maximum retained byte count
704
+ * @returns The retained slice, or `undefined` when the chunk contributes no retained bytes
705
+ */
706
+ retain(chunk: unknown, limit: number): Buffer_2 | undefined;
707
+ }
708
+
709
+ /** Exposes byte totals and bounded stream-head retention. */
710
+ export declare interface RetentionInterface {
711
+ /** The bytes delivered by the stream. */
712
+ readonly delivered: number;
713
+ /** The bytes retained from the stream head. */
714
+ readonly retained: number;
715
+ /**
716
+ * Retains a delivered chunk within a byte limit.
717
+ *
718
+ * @param chunk - The delivered chunk, ignored when it is not a buffer
719
+ * @param limit - The maximum retained byte count
720
+ * @returns The retained slice, or `undefined` when the chunk contributes no retained bytes
721
+ */
722
+ retain(chunk: unknown, limit: number): Buffer_2 | undefined;
723
+ }
583
724
 
584
725
  /**
585
- * Runs one command to completion synchronously, buffering its output, and returns the outcome.
726
+ * Takes one owned frozen snapshot of a caller's command.
586
727
  *
587
728
  * @remarks
588
- * The synchronous counterpart of {@link run}, spawned through the same resolver and never through a
589
- * shell. The host offers no cooperative termination window and no in-flight cancellation, so this
590
- * contract carries neither. A positive `timeout` and an output overflow both end the child with
591
- * `SIGKILL`: an overflow reports `truncated` and `failed` together and trims the partial output to
592
- * `limit`, where {@link run} keeps reading and reports `truncated` without failing. The environment
593
- * and input follow the same merge as {@link run}. Unless `strict` is `false`, a failed run throws a
594
- * {@link createRunError} carrying the {@link RunResult}.
729
+ * Every public entry point snapshots before it validates, so the object validated is the object
730
+ * spawned. Each property is read exactly once, because reading one runs the caller's own getter: a
731
+ * command whose `file` changes between reads would otherwise validate one executable and spawn
732
+ * another. The argument vector and the environment record are copied and frozen, so a caller
733
+ * mutating either after the call cannot reach the spawn. An absent optional stays absent rather than
734
+ * becoming an explicit `undefined`.
595
735
  *
596
- * @param command - The executable, arguments, and optional environment and input
597
- * @param options - Working directory, timeout, capture limit, and failure delivery
598
- * @returns The run outcome
599
- * @throws A {@link ProcessError} coded `invalid` for a malformed option, command string, or batch-bound argument, or one carrying the {@link RunResult} when the run failed and `strict` is not `false`
736
+ * @param command - The caller's command, whose properties may be getters
737
+ * @returns A frozen command carrying the values read at this instant
600
738
  *
601
739
  * @example
602
740
  * ```ts
603
- * const result = runSync({ file: 'git', arguments: ['--version'] }, { strict: false })
741
+ * snapshotCommand({ file: 'git', arguments: ['status'] }) // { file: 'git', arguments: ['status'] }
604
742
  * ```
605
743
  */
606
- export declare function runSync(command: ProcessCommand, options?: RunSyncOptions): RunResult;
744
+ export declare function snapshotCommand(command: ProcessCommand): ProcessCommand;
607
745
 
608
746
  /**
609
747
  * Terminates one child process tree and reports whether its native exit was observed.
610
748
  *
611
749
  * @remarks
612
- * Windows ends the tree at once through {@link killTree}, falling back to a direct kill when the
613
- * utility fails, because the host has no cooperative termination to offer. A POSIX host signals the
614
- * process group `SIGTERM`, waits `grace`, then signals `SIGKILL`. No signal is initiated once the
615
- * native exit is observed; the window between initiating a signal and the host delivering it belongs
616
- * to the operating system. `confirm` bounds each awaited step rather than the call as a whole: on
617
- * Windows it bounds the `taskkill` call and then the final wait, and on a POSIX host `grace` bounds
618
- * the cooperative wait and `confirm` the final one. Never rejects.
750
+ * Windows ends the whole tree at once through {@link killTree}, falling back to a direct kill after
751
+ * the utility fails, because the host has no cooperative termination to offer. A POSIX host signals
752
+ * the process group `SIGTERM`, falls back to the direct child when no group owns its pid, waits
753
+ * `grace`, then sends `SIGKILL` through the same route. No signal is initiated after the native exit
754
+ * is observed; the window between initiating a signal and the host delivering it belongs to the
755
+ * operating system. `confirm` bounds each awaited step rather than the call as a whole: on Windows
756
+ * it bounds the `taskkill` call and then the final wait, and on a POSIX host `grace` bounds the
757
+ * cooperative wait and `confirm` the final one. Never rejects.
619
758
  *
620
759
  * @param child - The child boundary
621
760
  * @param grace - The cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL`
@@ -780,6 +919,6 @@ export declare function validateWorkspace(workspace: string | undefined): void;
780
919
  * await waitForExit(child, 5_000)
781
920
  * ```
782
921
  */
783
- export declare function waitForExit(child: Pick<ProcessChild, 'exitCode' | 'signalCode' | 'once'>, timeout: number): Promise<void>;
922
+ export declare function waitForExit(child: Pick<ProcessChild, 'exitCode' | 'signalCode' | 'once' | 'off'>, timeout: number): Promise<void>;
784
923
 
785
924
  export { }