Commit ce05968

Anton Golub <antongolub@antongolub.com>
2025-08-07 19:00:08
feat: accept array as `stdio()` arg (#1311)
1 parent c5bf796
build/core.cjs
@@ -521,14 +521,14 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
     );
   }
   run() {
-    var _a, _b, _c;
+    var _a, _b;
     _ProcessPromise.bus.runBack(this);
     if (this.isRunning() || this.isSettled()) return this;
     this._stage = "running";
     const self = this;
     const $2 = self._snapshot;
     const id = self.id;
-    const cwd = (_a = $2.cwd) != null ? _a : $2[CWD];
+    const cwd = $2.cwd || $2[CWD];
     if ($2.preferLocal) {
       const dirs = $2.preferLocal === true ? [$2.cwd, $2[CWD]] : [$2.preferLocal].flat();
       $2.env = (0, import_util.preferLocalBin)($2.env, ...dirs);
@@ -536,7 +536,7 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
     this._zurk = (0, import_vendor_core2.exec)({
       cmd: self.fullCmd,
       cwd,
-      input: (_c = (_b = $2.input) == null ? void 0 : _b.stdout) != null ? _c : $2.input,
+      input: (_b = (_a = $2.input) == null ? void 0 : _a.stdout) != null ? _b : $2.input,
       stdin: self._stdin,
       sync: self.sync,
       signal: self.signal,
@@ -567,8 +567,7 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
           self.timeout($2.timeout, $2.timeoutSignal);
         },
         stdout: (data) => {
-          if (self._piped) return;
-          $2.log({ kind: "stdout", data, verbose: self.isVerbose(), id });
+          $2.log({ kind: "stdout", data, verbose: !self._piped && self.isVerbose(), id });
         },
         stderr: (data) => {
           $2.log({ kind: "stderr", data, verbose: !self.isQuiet(), id });
@@ -576,7 +575,7 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
         end: (data, c) => {
           const { error: _error, status, signal: __signal, duration, ctx: { store } } = data;
           const { stdout, stderr } = store;
-          const { cause, exitCode, signal: _signal } = __spreadValues({}, self._breakData);
+          const { cause, exitCode, signal: _signal } = self._breakerData || {};
           const signal = _signal != null ? _signal : __signal;
           const code = exitCode != null ? exitCode : status;
           const error = cause != null ? cause : _error;
@@ -599,7 +598,7 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
   }
   break(exitCode, signal, cause) {
     if (!this.isRunning()) return;
-    this._breakData = { exitCode, signal, cause };
+    this._breakerData = { exitCode, signal, cause };
     this.kill(signal);
   }
   finalize(output, legacy = false) {
@@ -636,7 +635,7 @@ var _ProcessPromise = class _ProcessPromise extends Promise {
   }
   // Configurators
   stdio(stdin, stdout = "pipe", stderr = "pipe") {
-    this._snapshot.stdio = [stdin, stdout, stderr];
+    this._snapshot.stdio = Array.isArray(stdin) ? stdin : [stdin, stdout, stderr];
     return this;
   }
   nothrow(v = true) {
build/core.d.ts
@@ -103,12 +103,12 @@ export declare class ProcessPromise extends Promise<ProcessOutput> {
     constructor(executor: PromiseCallback);
     private build;
     run(): this;
-    private _breakData?;
+    private _breakerData?;
     private break;
     private finalize;
     abort(reason?: string): void;
     kill(signal?: NodeJS.Signals | null): Promise<void>;
-    stdio(stdin: IOType, stdout?: IOType, stderr?: IOType): this;
+    stdio(stdin: IOType | StdioOptions, stdout?: IOType, stderr?: IOType): this;
     nothrow(v?: boolean): this;
     quiet(v?: boolean): this;
     verbose(v?: boolean): this;
build/util.d.ts
@@ -1,12 +1,12 @@
 import { type Buffer } from 'node:buffer';
-import { type TSpawnStoreChunks } from './vendor-core.js';
+import { type TSpawnStore } from './vendor-core.js';
 export { isStringLiteral } from './vendor-core.js';
 export declare function noop(): void;
 export declare function identity<T>(v: T): T;
 export declare function randomId(): string;
 export declare function isString(obj: any): obj is string;
 export declare const bufToString: (buf: Buffer | string) => string;
-export declare const bufArrJoin: (arr: TSpawnStoreChunks) => any;
+export declare const bufArrJoin: (arr: TSpawnStore[keyof TSpawnStore]) => string;
 export declare const getLast: <T>(arr: {
     length: number;
     [i: number]: any;
build/vendor-core.d.ts
@@ -266,13 +266,13 @@ type TArrayLike<T> = Iterable<T> & TPushable<T> & TJoinable & TReducible<T, any>
 	length: number;
 	[i: number]: T | undefined;
 };
-export type TSpawnStoreChunks = TArrayLike<string | Buffer>;
+type TSpawnStoreChunks = TArrayLike<string | Buffer>;
 export type TSpawnStore = {
 	stdout: TSpawnStoreChunks;
 	stderr: TSpawnStoreChunks;
 	stdall: TSpawnStoreChunks;
 };
-export type TSpawnResult = {
+type TSpawnResult = {
 	stderr: string;
 	stdout: string;
 	stdall: string;
src/core.ts
@@ -302,7 +302,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
     const self = this
     const $ = self._snapshot
     const id = self.id
-    const cwd = $.cwd ?? $[CWD]
+    const cwd = $.cwd || $[CWD]
 
     if ($.preferLocal) {
       const dirs =
@@ -344,8 +344,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
         },
         stdout: (data) => {
           // If the process is piped, don't print its output.
-          if (self._piped) return
-          $.log({ kind: 'stdout', data, verbose: self.isVerbose(), id })
+          $.log({ kind: 'stdout', data, verbose: !self._piped && self.isVerbose(), id })
         },
         stderr: (data) => {
           // Stderr should be printed regardless of piping.
@@ -354,7 +353,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
         end: (data, c) => {
           const { error: _error, status, signal: __signal, duration, ctx: { store }} = data
           const { stdout, stderr } = store
-          const { cause, exitCode, signal: _signal } = {...self._breakData}
+          const { cause, exitCode, signal: _signal } = self._breakerData || {}
 
           const signal = _signal ?? __signal
           const code = exitCode ?? status
@@ -381,7 +380,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
 
     return this
   }
-  private _breakData?: Partial<
+  private _breakerData?: Partial<
     Pick<ProcessOutput, 'exitCode' | 'signal' | 'cause'>
   >
 
@@ -391,7 +390,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
     cause?: ProcessOutput['cause']
   ): void {
     if (!this.isRunning()) return
-    this._breakData = { exitCode, signal, cause }
+    this._breakerData = { exitCode, signal, cause }
     this.kill(signal)
   }
 
@@ -433,8 +432,14 @@ export class ProcessPromise extends Promise<ProcessOutput> {
   }
 
   // Configurators
-  stdio(stdin: IOType, stdout: IOType = 'pipe', stderr: IOType = 'pipe'): this {
-    this._snapshot.stdio = [stdin, stdout, stderr]
+  stdio(
+    stdin: IOType | StdioOptions,
+    stdout: IOType = 'pipe',
+    stderr: IOType = 'pipe'
+  ): this {
+    this._snapshot.stdio = Array.isArray(stdin)
+      ? stdin
+      : [stdin, stdout, stderr]
     return this
   }
 
src/util.ts
@@ -15,7 +15,7 @@
 import path from 'node:path'
 import { type Buffer } from 'node:buffer'
 import process from 'node:process'
-import { type TSpawnStoreChunks } from './vendor-core.ts'
+import { type TSpawnStore } from './vendor-core.ts'
 
 export { isStringLiteral } from './vendor-core.ts'
 
@@ -37,7 +37,7 @@ const utf8Decoder = new TextDecoder('utf-8')
 export const bufToString = (buf: Buffer | string): string =>
   isString(buf) ? buf : utf8Decoder.decode(buf)
 
-export const bufArrJoin = (arr: TSpawnStoreChunks) =>
+export const bufArrJoin = (arr: TSpawnStore[keyof TSpawnStore]): string =>
   arr.reduce((acc, buf) => acc + bufToString(buf), '')
 
 export const getLast = <T>(arr: { length: number; [i: number]: any }): T =>
src/vendor-core.ts
@@ -19,8 +19,6 @@ import { bus } from './internals.ts'
 
 export {
   type TSpawnStore,
-  type TSpawnStoreChunks,
-  type TSpawnResult,
   exec,
   buildCmd,
   isStringLiteral,
test/core.test.js
@@ -429,6 +429,9 @@ describe('core', () => {
           ).toString(),
           'ok\n'
         )
+      })
+
+      test('via stdio() method', async () => {
         assert.equal(
           (
             await $({ halt: true })`>&2 echo error; echo ok`
@@ -438,6 +441,16 @@ describe('core', () => {
           ).toString(),
           'error\n'
         )
+
+        assert.equal(
+          (
+            await $({ halt: true })`>&2 echo error; echo ok`
+              .stdio(['inherit', 'pipe', 'ignore'])
+              .quiet()
+              .run()
+          ).toString(),
+          'ok\n'
+        )
       })
 
       test('file stream as stdout', async () => {
@@ -605,14 +618,10 @@ describe('core', () => {
       assert.equal(p.fullCmd, "set -euo pipefail;echo $'#bar' --t 1")
     })
 
-    test('stdio() works', async () => {
-      const p1 = $`printf foo`
-      await p1
-      // assert.throws(() => p.stdin)
-      assert.equal((await p1).stdout, 'foo')
-      const p2 = $`read; printf $REPLY`
-      p2.stdin.write('bar\n')
-      assert.equal((await p2).stdout, 'bar')
+    test('stdin works', async () => {
+      const p = $`read; printf $REPLY`
+      p.stdin.write('bar\n')
+      assert.equal((await p).stdout, 'bar')
     })
 
     describe('pipe()', () => {