printable-shell-command 2.7.4 → 3.0.1

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.
@@ -1,5 +1,5 @@
1
- import type { ChildProcessByStdio, ChildProcess as NodeChildProcess, SpawnOptions as NodeSpawnOptions } from "node:child_process";
2
- import { Writable } from "node:stream";
1
+ import type { ChildProcessByStdio, SpawnOptions as NodeSpawnOptions } from "node:child_process";
2
+ import { Readable, Writable } from "node:stream";
3
3
  import type { WriteStream } from "node:tty";
4
4
  import { styleText } from "node:util";
5
5
  import type { SpawnOptions as BunSpawnOptions, Subprocess as BunSubprocess, SpawnOptions } from "bun";
@@ -55,6 +55,15 @@ type BunCwd = SpawnOptions.OptionsObject<any, any, any>["cwd"] | Path;
55
55
  type BunWithCwd<T extends {
56
56
  cwd?: SpawnOptions.OptionsObject<any, any, any>["cwd"] | Path;
57
57
  }> = SetFieldType<T, "cwd", BunCwd | undefined>;
58
+ export type StdinSource = {
59
+ text: string;
60
+ } | {
61
+ json: any;
62
+ } | {
63
+ path: string | Path;
64
+ } | {
65
+ stream: Readable | ReadableStream;
66
+ };
58
67
  export declare class PrintableShellCommand {
59
68
  #private;
60
69
  private args;
@@ -130,6 +139,15 @@ export declare class PrintableShellCommand {
130
139
  *
131
140
  */
132
141
  print(options?: StreamPrintOptions): PrintableShellCommand;
142
+ /**
143
+ * Send data to `stdin` of the subprocess.
144
+ *
145
+ * Note that this will overwrite:
146
+ *
147
+ * - Any previous value set using {@link PrintableShellCommand.stdin | `.stdin(…)`}.
148
+ * - Any value set for `stdin` using the `"stdio"` field of {@link PrintableShellCommand.spawn | `.spawn(…)`}.
149
+ */
150
+ stdin(source: StdinSource): PrintableShellCommand;
133
151
  /**
134
152
  * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
135
153
  */
@@ -143,8 +161,6 @@ export declare class PrintableShellCommand {
143
161
  * in its stead.
144
162
  */
145
163
  spawnTransparently(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): ChildProcessByStdio<null, null, null> & WithSuccess;
146
- /** @deprecated: Use `.spawnTransparently(…)`. */
147
- spawnInherit(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): NodeChildProcess & WithSuccess;
148
164
  /**
149
165
  * A wrapper for {@link PrintableShellCommand.spawn | `.spawn(…)`} that:
150
166
  *
@@ -153,27 +169,10 @@ export declare class PrintableShellCommand {
153
169
  * - calls `.unref()`, and
154
170
  * - does not wait for the process to exit.
155
171
  *
156
- * This is similar to starting a command int he background and disowning it (in a shell).
157
- *
158
- * The `stdio` field is left overridable. To capture `stdout` and `stderr`, connect them to output files like this:
159
- *
160
- * ```
161
- * import { open } from "node:fs/promises";
162
- * import { Path } from "path-class";
163
- * import { PrintableShellCommand } from "printable-shell-command";
164
- *
165
- * const tempDir = await Path.makeTempDir();
166
- * console.log(`Temp dir: ${tempDir}`);
167
- * const stdout = await open(tempDir.join("stdout.log").path, "a");
168
- * const stderr = await open(tempDir.join("stderr.log").path, "a");
169
- *
170
- * new PrintableShellCommand("echo", ["hi"]).spawnDetached({
171
- * stdio: ["ignore", stdout.fd, stderr.fd],
172
- * });
173
- * ```
172
+ * This is similar to starting a command in the background and disowning it (in a shell).
174
173
  *
175
174
  */
176
- spawnDetached(options?: NodeWithCwd<Omit<NodeSpawnOptions, "detached">>): void;
175
+ spawnDetached(options?: NodeWithCwd<Omit<Omit<NodeSpawnOptions, "stdio">, "detached">>): void;
177
176
  stdout(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): Response;
178
177
  /**
179
178
  * Convenience function for:
@@ -191,6 +190,18 @@ export declare class PrintableShellCommand {
191
190
  * This can make some simple invocations easier to read and/or fit on a single line.
192
191
  */
193
192
  json<T>(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): Promise<T>;
193
+ /**
194
+ * Parse `stdout` into a generator of string values using a NULL delimiter.
195
+ *
196
+ * A trailing NULL delimiter from `stdout` is required and removed.
197
+ */
198
+ text0(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): AsyncGenerator<string>;
199
+ /**
200
+ * Parse `stdout` into a generator of JSON values using a NULL delimiter.
201
+ *
202
+ * A trailing NULL delimiter from `stdout` is required and removed.
203
+ */
204
+ json0(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): AsyncGenerator<any>;
194
205
  /** Equivalent to:
195
206
  *
196
207
  * ```
@@ -226,4 +237,5 @@ export declare class PrintableShellCommand {
226
237
  shellOutBun: (options?: BunWithCwd<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd" | "stdio">>) => Promise<void>;
227
238
  };
228
239
  }
240
+ export declare function escapeArg(arg: string, isMainCommand: boolean, options: PrintOptions): string;
229
241
  export {};
@@ -1,4 +1,6 @@
1
1
  // src/index.ts
2
+ import assert from "node:assert";
3
+ import { createReadStream } from "node:fs";
2
4
  import { stderr } from "node:process";
3
5
  import { Readable, Writable } from "node:stream";
4
6
  import { styleText } from "node:util";
@@ -50,6 +52,7 @@ var SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND = (
50
52
  // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)
51
53
  SPECIAL_SHELL_CHARACTERS.union(/* @__PURE__ */ new Set(["="]))
52
54
  );
55
+ var STDIN_SOURCE_KEYS = ["text", "json", "path", "stream"];
53
56
  var PrintableShellCommand = class {
54
57
  constructor(commandName, args = []) {
55
58
  this.args = args;
@@ -148,16 +151,6 @@ var PrintableShellCommand = class {
148
151
  forNode() {
149
152
  return this.toCommandWithFlatArgs();
150
153
  }
151
- #escapeArg(arg, isMainCommand, options) {
152
- const argCharacters = new Set(arg);
153
- const specialShellCharacters = isMainCommand ? SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND : SPECIAL_SHELL_CHARACTERS;
154
- if (options?.quoting === "extra-safe" || // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)
155
- argCharacters.intersection(specialShellCharacters).size > 0) {
156
- const escaped = arg.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
157
- return `'${escaped}'`;
158
- }
159
- return arg;
160
- }
161
154
  #mainIndentation(options) {
162
155
  return options?.mainIndentation ?? DEFAULT_MAIN_INDENTATION;
163
156
  }
@@ -214,16 +207,14 @@ var PrintableShellCommand = class {
214
207
  for (let i = 0; i < this.args.length; i++) {
215
208
  const argsEntry = stringifyIfPath(this.args[i]);
216
209
  if (isString(argsEntry)) {
217
- serializedEntries.push(this.#escapeArg(argsEntry, false, options));
210
+ serializedEntries.push(escapeArg(argsEntry, false, options));
218
211
  } else {
219
212
  serializedEntries.push(
220
- argsEntry.map(
221
- (part) => this.#escapeArg(stringifyIfPath(part), false, options)
222
- ).join(this.#argPairSeparator(options))
213
+ argsEntry.map((part) => escapeArg(stringifyIfPath(part), false, options)).join(this.#argPairSeparator(options))
223
214
  );
224
215
  }
225
216
  }
226
- let text = this.#mainIndentation(options) + this.#escapeArg(this.commandName, true, options) + this.#separatorAfterCommand(options, serializedEntries.length) + serializedEntries.join(this.#intraEntrySeparator(options));
217
+ let text = this.#mainIndentation(options) + escapeArg(this.commandName, true, options) + this.#separatorAfterCommand(options, serializedEntries.length) + serializedEntries.join(this.#intraEntrySeparator(options));
227
218
  if (options?.styleTextFormat) {
228
219
  text = styleText(options.styleTextFormat, text);
229
220
  }
@@ -247,12 +238,38 @@ var PrintableShellCommand = class {
247
238
  writable.write("\n");
248
239
  return this;
249
240
  }
241
+ #stdinSource;
242
+ /**
243
+ * Send data to `stdin` of the subprocess.
244
+ *
245
+ * Note that this will overwrite:
246
+ *
247
+ * - Any previous value set using {@link PrintableShellCommand.stdin | `.stdin(…)`}.
248
+ * - Any value set for `stdin` using the `"stdio"` field of {@link PrintableShellCommand.spawn | `.spawn(…)`}.
249
+ */
250
+ stdin(source) {
251
+ const [key, ...moreKeys] = Object.keys(source);
252
+ assert.equal(moreKeys.length, 0);
253
+ assert(STDIN_SOURCE_KEYS.includes(key));
254
+ this.#stdinSource = source;
255
+ return this;
256
+ }
250
257
  /**
251
258
  * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
252
259
  */
253
260
  spawn = ((options) => {
254
261
  const { spawn } = process.getBuiltinModule("node:child_process");
255
262
  const cwd = stringifyIfPath(options?.cwd);
263
+ if (this.#stdinSource) {
264
+ options ??= {};
265
+ if (typeof options.stdio === "undefined") {
266
+ options.stdio = "pipe";
267
+ }
268
+ if (typeof options.stdio === "string") {
269
+ options.stdio = new Array(3).fill(options.stdio);
270
+ }
271
+ options.stdio[0] = "pipe";
272
+ }
256
273
  const subprocess = spawn(...this.forNode(), {
257
274
  ...options,
258
275
  cwd
@@ -274,6 +291,47 @@ var PrintableShellCommand = class {
274
291
  },
275
292
  enumerable: false
276
293
  });
294
+ if (subprocess.stdout) {
295
+ const s = subprocess;
296
+ s.stdout.response = () => new Response(Readable.from(this.#generator(s.stdout, s.success)));
297
+ s.stdout.text = () => s.stdout.response().text();
298
+ const thisCached = this;
299
+ s.stdout.text0 = async function* () {
300
+ yield* thisCached.#split0(thisCached.#generator(s.stdout, s.success));
301
+ };
302
+ s.stdout.json = () => s.stdout.response().json();
303
+ }
304
+ if (subprocess.stderr) {
305
+ const s = subprocess;
306
+ s.stderr.response = () => new Response(Readable.from(this.#generator(s.stderr, s.success)));
307
+ s.stderr.text = () => s.stderr.response().text();
308
+ const thisCached = this;
309
+ s.stderr.text0 = async function* () {
310
+ yield* thisCached.#split0(thisCached.#generator(s.stderr, s.success));
311
+ };
312
+ s.stderr.json = () => s.stderr.response().json();
313
+ }
314
+ if (this.#stdinSource) {
315
+ const { stdin } = subprocess;
316
+ assert(stdin);
317
+ if ("text" in this.#stdinSource) {
318
+ stdin.write(this.#stdinSource.text);
319
+ stdin.end();
320
+ } else if ("json" in this.#stdinSource) {
321
+ stdin.write(JSON.stringify(this.#stdinSource.json));
322
+ stdin.end();
323
+ } else if ("path" in this.#stdinSource) {
324
+ createReadStream(stringifyIfPath(this.#stdinSource.path)).pipe(stdin);
325
+ } else if ("stream" in this.#stdinSource) {
326
+ const stream = (() => {
327
+ const { stream: stream2 } = this.#stdinSource;
328
+ return stream2 instanceof Readable ? stream2 : Readable.fromWeb(stream2);
329
+ })();
330
+ stream.pipe(stdin);
331
+ } else {
332
+ throw new Error("Invalid `.stdin(\u2026)` source?");
333
+ }
334
+ }
277
335
  return subprocess;
278
336
  });
279
337
  /** A wrapper for `.spawn(…)` that sets stdio to `"inherit"` (common for
@@ -290,10 +348,6 @@ var PrintableShellCommand = class {
290
348
  }
291
349
  return this.spawn({ ...options, stdio: "inherit" });
292
350
  }
293
- /** @deprecated: Use `.spawnTransparently(…)`. */
294
- spawnInherit(options) {
295
- return this.spawnTransparently(options);
296
- }
297
351
  /**
298
352
  * A wrapper for {@link PrintableShellCommand.spawn | `.spawn(…)`} that:
299
353
  *
@@ -302,29 +356,16 @@ var PrintableShellCommand = class {
302
356
  * - calls `.unref()`, and
303
357
  * - does not wait for the process to exit.
304
358
  *
305
- * This is similar to starting a command int he background and disowning it (in a shell).
306
- *
307
- * The `stdio` field is left overridable. To capture `stdout` and `stderr`, connect them to output files like this:
308
- *
309
- * ```
310
- * import { open } from "node:fs/promises";
311
- * import { Path } from "path-class";
312
- * import { PrintableShellCommand } from "printable-shell-command";
313
- *
314
- * const tempDir = await Path.makeTempDir();
315
- * console.log(`Temp dir: ${tempDir}`);
316
- * const stdout = await open(tempDir.join("stdout.log").path, "a");
317
- * const stderr = await open(tempDir.join("stderr.log").path, "a");
318
- *
319
- * new PrintableShellCommand("echo", ["hi"]).spawnDetached({
320
- * stdio: ["ignore", stdout.fd, stderr.fd],
321
- * });
322
- * ```
359
+ * This is similar to starting a command in the background and disowning it (in a shell).
323
360
  *
324
361
  */
325
362
  spawnDetached(options) {
326
- if (options && "detached" in options) {
327
- throw new Error("Unexpected `detached` field.");
363
+ if (options) {
364
+ for (const field of ["stdio", "detached"]) {
365
+ if (field in options) {
366
+ throw new Error(`Unexpected \`${field}\` field.`);
367
+ }
368
+ }
328
369
  }
329
370
  const childProcess = this.spawn({
330
371
  stdio: "ignore",
@@ -333,7 +374,15 @@ var PrintableShellCommand = class {
333
374
  });
334
375
  childProcess.unref();
335
376
  }
336
- stdout(options) {
377
+ #generator(readable, successPromise) {
378
+ return (async function* () {
379
+ for await (const chunk of readable) {
380
+ yield chunk;
381
+ }
382
+ await successPromise;
383
+ })();
384
+ }
385
+ #stdoutSpawnGenerator(options) {
337
386
  if (options && "stdio" in options) {
338
387
  throw new Error("Unexpected `stdio` field.");
339
388
  }
@@ -341,7 +390,24 @@ var PrintableShellCommand = class {
341
390
  ...options,
342
391
  stdio: ["ignore", "pipe", "inherit"]
343
392
  });
344
- return new Response(Readable.toWeb(subprocess.stdout));
393
+ return this.#generator(subprocess.stdout, subprocess.success);
394
+ }
395
+ stdout(options) {
396
+ return new Response(Readable.from(this.#stdoutSpawnGenerator(options)));
397
+ }
398
+ async *#split0(generator) {
399
+ let pending = "";
400
+ for await (const chunk of generator) {
401
+ pending += chunk;
402
+ const newChunks = pending.split("\0");
403
+ pending = newChunks.splice(-1)[0];
404
+ yield* newChunks;
405
+ }
406
+ if (pending !== "") {
407
+ throw new Error(
408
+ "Missing a trailing NUL character at the end of a NUL-delimited stream."
409
+ );
410
+ }
345
411
  }
346
412
  /**
347
413
  * Convenience function for:
@@ -363,6 +429,26 @@ var PrintableShellCommand = class {
363
429
  json(options) {
364
430
  return this.stdout(options).json();
365
431
  }
432
+ /**
433
+ * Parse `stdout` into a generator of string values using a NULL delimiter.
434
+ *
435
+ * A trailing NULL delimiter from `stdout` is required and removed.
436
+ */
437
+ async *text0(options) {
438
+ yield* this.#split0(this.#stdoutSpawnGenerator(options));
439
+ }
440
+ /**
441
+ * Parse `stdout` into a generator of JSON values using a NULL delimiter.
442
+ *
443
+ * A trailing NULL delimiter from `stdout` is required and removed.
444
+ */
445
+ async *json0(options) {
446
+ for await (const part of this.#split0(
447
+ this.#stdoutSpawnGenerator(options)
448
+ )) {
449
+ yield JSON.parse(part);
450
+ }
451
+ }
366
452
  /** Equivalent to:
367
453
  *
368
454
  * ```
@@ -445,7 +531,18 @@ var PrintableShellCommand = class {
445
531
  shellOutBun: this.#shellOutBun.bind(this)
446
532
  };
447
533
  };
534
+ function escapeArg(arg, isMainCommand, options) {
535
+ const argCharacters = new Set(arg);
536
+ const specialShellCharacters = isMainCommand ? SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND : SPECIAL_SHELL_CHARACTERS;
537
+ if (options?.quoting === "extra-safe" || // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)
538
+ argCharacters.intersection(specialShellCharacters).size > 0) {
539
+ const escaped = arg.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
540
+ return `'${escaped}'`;
541
+ }
542
+ return arg;
543
+ }
448
544
  export {
449
- PrintableShellCommand
545
+ PrintableShellCommand,
546
+ escapeArg
450
547
  };
451
548
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/index.ts"],
4
- "sourcesContent": ["import type {\n ChildProcessByStdio,\n ChildProcess as NodeChildProcess,\n SpawnOptions as NodeSpawnOptions,\n} from \"node:child_process\";\nimport { stderr } from \"node:process\";\nimport { Readable, Writable } from \"node:stream\";\nimport type { WriteStream } from \"node:tty\";\nimport { styleText } from \"node:util\";\nimport type {\n SpawnOptions as BunSpawnOptions,\n Subprocess as BunSubprocess,\n SpawnOptions,\n} from \"bun\";\nimport { Path, stringifyIfPath } from \"path-class\";\nimport type { SetFieldType } from \"type-fest\";\nimport type { NodeWithCwd, spawnType, WithSuccess } from \"./spawn\";\n\n// TODO: does this import work?\n/**\n * @import { stdout } from \"node:process\"\n */\n\nconst DEFAULT_MAIN_INDENTATION = \"\";\nconst DEFAULT_ARG_INDENTATION = \" \";\nconst DEFAULT_ARGUMENT_LINE_WRAPPING = \"by-entry\";\n\nconst INLINE_SEPARATOR = \" \";\nconst LINE_WRAP_LINE_END = \" \\\\\\n\";\n\ntype StyleTextFormat = Parameters<typeof styleText>[0];\n\nconst TTY_AUTO_STYLE: StyleTextFormat = [\"gray\", \"bold\"];\n\n// biome-ignore lint/suspicious/noExplicitAny: This is the correct type nere.\nfunction isString(s: any): s is string {\n return typeof s === \"string\";\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: This is the correct type here.\nfunction isValidArgsEntryArray(entries: any[]): entries is SingleArgument[] {\n for (const entry of entries) {\n if (isString(entry)) {\n continue;\n }\n if (entry instanceof Path) {\n continue;\n }\n return false;\n }\n return true;\n}\n\n// TODO: allow `.toString()`ables?\ntype SingleArgument = string | Path;\ntype ArgsEntry = SingleArgument | SingleArgument[];\ntype Args = ArgsEntry[];\n\nexport interface PrintOptions {\n /** Defaults to \"\" */\n mainIndentation?: string;\n /** Defaults to \" \" */\n argIndentation?: string;\n /**\n * - `\"auto\"`: Quote only arguments that need it for safety. This tries to be\n * portable and safe across shells, but true safety and portability is hard\n * to guarantee.\n * - `\"extra-safe\"`: Quote all arguments, even ones that don't need it. This is\n * more likely to be safe under all circumstances.\n */\n quoting?: \"auto\" | \"extra-safe\";\n /** Line wrapping to use between arguments. Defaults to `\"by-entry\"`. */\n argumentLineWrapping?:\n | \"by-entry\"\n | \"nested-by-entry\"\n | \"by-argument\"\n | \"inline\";\n /** Include the first arg (or first arg group) on the same line as the command, regardless of the `argumentLineWrapping` setting. */\n skipLineWrapBeforeFirstArg?: true | false;\n /**\n * Style text using `node`'s {@link styleText | `styleText(\u2026)`}.\n *\n * Example usage:\n *\n * ```\n * new PrintableShellCommand(\"echo\", [\"hi\"]).print({\n * styleTextFormat: [\"green\", \"underline\"],\n * });\n * */\n styleTextFormat?: StyleTextFormat;\n}\n\nexport interface StreamPrintOptions extends PrintOptions {\n /**\n * Auto-style the text when:\n *\n * - the output stream is detected to be a TTY\n * - `styleTextFormat` is not specified.\n *\n * The current auto style is: `[\"gray\", \"bold\"]`\n */\n autoStyle?: \"tty\" | \"never\";\n // This would be a `WritableStream` (open web standard), but `WriteStream` allows us to query `.isTTY`.\n stream?: WriteStream | Writable;\n}\n\n// https://mywiki.wooledge.org/BashGuide/SpecialCharacters\nconst SPECIAL_SHELL_CHARACTERS = new Set([\n \" \",\n '\"',\n \"'\",\n \"`\",\n \"|\",\n \"$\",\n \"*\",\n \"?\",\n \">\",\n \"<\",\n \"(\",\n \")\",\n \"[\",\n \"]\",\n \"{\",\n \"}\",\n \"&\",\n \"\\\\\",\n \";\",\n \"#\",\n]);\n\n// https://mywiki.wooledge.org/BashGuide/SpecialCharacters\nconst SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND =\n // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)\n (SPECIAL_SHELL_CHARACTERS as unknown as any).union(new Set([\"=\"]));\n\n// biome-ignore lint/suspicious/noExplicitAny: Just matching\ntype BunCwd = SpawnOptions.OptionsObject<any, any, any>[\"cwd\"] | Path;\ntype BunWithCwd<\n // biome-ignore lint/suspicious/noExplicitAny: Just matching\n T extends { cwd?: SpawnOptions.OptionsObject<any, any, any>[\"cwd\"] | Path },\n> = SetFieldType<T, \"cwd\", BunCwd | undefined>;\n\nexport class PrintableShellCommand {\n #commandName: string | Path;\n constructor(\n commandName: string | Path,\n private args: Args = [],\n ) {\n if (!isString(commandName) && !(commandName instanceof Path)) {\n // biome-ignore lint/suspicious/noExplicitAny: We want to print this, no matter what it is.\n throw new Error(\"Command name is not a string:\", commandName as any);\n }\n this.#commandName = commandName;\n if (typeof args === \"undefined\") {\n return;\n }\n if (!Array.isArray(args)) {\n throw new Error(\"Command arguments are not an array\");\n }\n for (let i = 0; i < args.length; i++) {\n const argEntry = args[i];\n if (typeof argEntry === \"string\") {\n continue;\n }\n if (argEntry instanceof Path) {\n continue;\n }\n if (Array.isArray(argEntry) && isValidArgsEntryArray(argEntry)) {\n continue;\n }\n throw new Error(`Invalid arg entry at index: ${i}`);\n }\n }\n\n get commandName(): string {\n return stringifyIfPath(this.#commandName);\n }\n\n /** For use with `bun`.\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"bun\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * await spawn(command.toFlatCommand()).exited;\n * ```\n */\n public toFlatCommand(): string[] {\n return [this.commandName, ...this.args.flat().map(stringifyIfPath)];\n }\n\n /**\n * Convenient alias for {@link PrintableShellCommand.toFlatCommand | `.toFlatCommand()`}.\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"bun\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * await spawn(command.forBun()).exited;\n * ```\n *\n * */\n public forBun(): string[] {\n return this.toFlatCommand();\n }\n\n /**\n * For use with `node:child_process`\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"node:child_process\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * const child_process = spawn(...command.toCommandWithFlatArgs()); // Note the `...`\n * ```\n *\n */\n public toCommandWithFlatArgs(): [string, string[]] {\n return [this.commandName, this.args.flat().map(stringifyIfPath)];\n }\n\n /**\n * For use with `node:child_process`\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"node:child_process\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * const child_process = spawn(...command.forNode()); // Note the `...`\n * ```\n *\n * Convenient alias for {@link PrintableShellCommand.toCommandWithFlatArgs | `toCommandWithFlatArgs()`}.\n */\n public forNode(): [string, string[]] {\n return this.toCommandWithFlatArgs();\n }\n\n #escapeArg(\n arg: string,\n isMainCommand: boolean,\n options: PrintOptions,\n ): string {\n const argCharacters = new Set(arg);\n const specialShellCharacters = isMainCommand\n ? SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND\n : SPECIAL_SHELL_CHARACTERS;\n if (\n options?.quoting === \"extra-safe\" ||\n // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)\n (argCharacters as unknown as any).intersection(specialShellCharacters)\n .size > 0\n ) {\n // Use single quote to reduce the need to escape (and therefore reduce the chance for bugs/security issues).\n const escaped = arg.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"'\", \"\\\\'\");\n return `'${escaped}'`;\n }\n return arg;\n }\n\n #mainIndentation(options: PrintOptions): string {\n return options?.mainIndentation ?? DEFAULT_MAIN_INDENTATION;\n }\n\n #argIndentation(options: PrintOptions): string {\n return (\n this.#mainIndentation(options) +\n (options?.argIndentation ?? DEFAULT_ARG_INDENTATION)\n );\n }\n\n #lineWrapSeparator(options: PrintOptions): string {\n return LINE_WRAP_LINE_END + this.#argIndentation(options);\n }\n\n #argPairSeparator(options: PrintOptions): string {\n switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n case \"by-entry\": {\n return INLINE_SEPARATOR;\n }\n case \"nested-by-entry\": {\n return this.#lineWrapSeparator(options) + this.#argIndentation(options);\n }\n case \"by-argument\": {\n return this.#lineWrapSeparator(options);\n }\n case \"inline\": {\n return INLINE_SEPARATOR;\n }\n default:\n throw new Error(\"Invalid argument line wrapping argument.\");\n }\n }\n\n #intraEntrySeparator(options: PrintOptions): string {\n switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n case \"by-entry\":\n case \"nested-by-entry\":\n case \"by-argument\": {\n return LINE_WRAP_LINE_END + this.#argIndentation(options);\n }\n case \"inline\": {\n return INLINE_SEPARATOR;\n }\n default:\n throw new Error(\"Invalid argument line wrapping argument.\");\n }\n }\n\n #separatorAfterCommand(\n options: PrintOptions,\n numFollowingEntries: number,\n ): string {\n if (numFollowingEntries === 0) {\n return \"\";\n }\n if (options.skipLineWrapBeforeFirstArg ?? false) {\n return INLINE_SEPARATOR;\n }\n return this.#intraEntrySeparator(options);\n }\n\n public getPrintableCommand(options?: PrintOptions): string {\n // TODO: Why in the world does TypeScript not give the `options` arg the type of `PrintOptions | undefined`???\n options ??= {};\n const serializedEntries: string[] = [];\n\n for (let i = 0; i < this.args.length; i++) {\n const argsEntry = stringifyIfPath(this.args[i]);\n\n if (isString(argsEntry)) {\n serializedEntries.push(this.#escapeArg(argsEntry, false, options));\n } else {\n serializedEntries.push(\n argsEntry\n .map((part) =>\n this.#escapeArg(stringifyIfPath(part), false, options),\n )\n .join(this.#argPairSeparator(options)),\n );\n }\n }\n\n let text =\n this.#mainIndentation(options) +\n this.#escapeArg(this.commandName, true, options) +\n this.#separatorAfterCommand(options, serializedEntries.length) +\n serializedEntries.join(this.#intraEntrySeparator(options));\n if (options?.styleTextFormat) {\n text = styleText(options.styleTextFormat, text);\n }\n return text;\n }\n\n /**\n * Print the shell command to {@link stderr} (default) or a specified stream.\n *\n * By default, this will be auto-styled (as bold gray) when `.isTTY` is true\n * for the stream. `.isTTY` is populated for the {@link stderr} and\n * {@link stdout} objects. Pass `\"autoStyle\": \"never\"` or an explicit\n * `styleTextFormat` to disable this.\n *\n */\n public print(options?: StreamPrintOptions): PrintableShellCommand {\n const stream = options?.stream ?? stderr;\n // Note: we only need to modify top-level fields, so `structuredClone(\u2026)`\n // would be overkill and can only cause performance issues.\n const optionsCopy = { ...options };\n optionsCopy.styleTextFormat ??=\n options?.autoStyle !== \"never\" &&\n (stream as { isTTY?: boolean }).isTTY === true\n ? TTY_AUTO_STYLE\n : undefined;\n const writable =\n stream instanceof Writable ? stream : Writable.fromWeb(stream);\n writable.write(this.getPrintableCommand(optionsCopy));\n writable.write(\"\\n\");\n return this;\n }\n\n /**\n * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313\n */\n public spawn: typeof spawnType = ((\n options?: Parameters<typeof spawnType>[0],\n ) => {\n const { spawn } = process.getBuiltinModule(\"node:child_process\");\n const cwd = stringifyIfPath(options?.cwd);\n // biome-ignore lint/suspicious/noTsIgnore: We don't want linting to depend on *broken* type checking.\n // @ts-ignore: The TypeScript checker has trouble reconciling the optional (i.e. potentially `undefined`) `options` with the third argument.\n const subprocess = spawn(...this.forNode(), {\n ...(options as object),\n cwd,\n }) as NodeChildProcess & {\n success: Promise<void>;\n };\n Object.defineProperty(subprocess, \"success\", {\n get() {\n return new Promise<void>((resolve, reject) =>\n this.addListener(\n \"exit\",\n (exitCode: number /* we only use the first arg */) => {\n if (exitCode === 0) {\n resolve();\n } else {\n reject(`Command failed with non-zero exit code: ${exitCode}`);\n }\n },\n ),\n );\n },\n enumerable: false,\n });\n return subprocess;\n // biome-ignore lint/suspicious/noExplicitAny: Type wrangling\n }) as any;\n\n /** A wrapper for `.spawn(\u2026)` that sets stdio to `\"inherit\"` (common for\n * invoking commands from scripts whose output and interaction should be\n * surfaced to the user).\n *\n * If there is no other interaction with the shell from the calling process,\n * then it acts \"transparent\" and allows user to interact with the subprocess\n * in its stead.\n */\n public spawnTransparently(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): ChildProcessByStdio<null, null, null> & WithSuccess {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Type wrangling.\n return this.spawn({ ...options, stdio: \"inherit\" }) as any;\n }\n\n /** @deprecated: Use `.spawnTransparently(\u2026)`. */\n public spawnInherit(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): NodeChildProcess & WithSuccess {\n return this.spawnTransparently(options);\n }\n\n /**\n * A wrapper for {@link PrintableShellCommand.spawn | `.spawn(\u2026)`} that:\n *\n * - sets `detached` to `true`,\n * - sets stdio to `\"inherit\"`,\n * - calls `.unref()`, and\n * - does not wait for the process to exit.\n *\n * This is similar to starting a command int he background and disowning it (in a shell).\n *\n * The `stdio` field is left overridable. To capture `stdout` and `stderr`, connect them to output files like this:\n *\n * ```\n * import { open } from \"node:fs/promises\";\n * import { Path } from \"path-class\";\n * import { PrintableShellCommand } from \"printable-shell-command\";\n *\n * const tempDir = await Path.makeTempDir();\n * console.log(`Temp dir: ${tempDir}`);\n * const stdout = await open(tempDir.join(\"stdout.log\").path, \"a\");\n * const stderr = await open(tempDir.join(\"stderr.log\").path, \"a\");\n *\n * new PrintableShellCommand(\"echo\", [\"hi\"]).spawnDetached({\n * stdio: [\"ignore\", stdout.fd, stderr.fd],\n * });\n * ```\n *\n */\n public spawnDetached(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"detached\">>,\n ): void {\n if (options && \"detached\" in options) {\n throw new Error(\"Unexpected `detached` field.\");\n }\n const childProcess = this.spawn({\n stdio: \"ignore\",\n ...options,\n detached: true,\n });\n childProcess.unref();\n }\n\n public stdout(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Response {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n const subprocess = this.spawn({\n ...options,\n stdio: [\"ignore\", \"pipe\", \"inherit\"],\n });\n\n return new Response(Readable.toWeb(subprocess.stdout));\n }\n\n /**\n * Convenience function for:\n *\n * .stdout(options).text()\n *\n * This can make some simple invocations easier to read and/or fit on a single line.\n */\n public text(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<string> {\n return this.stdout(options).text();\n }\n\n /**\n * Convenience function for:\n *\n * .stdout(options).json()\n *\n * This can make some simple invocations easier to read and/or fit on a single line.\n */\n public json<T>(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<T> {\n return this.stdout(options).json() as Promise<T>;\n }\n\n /** Equivalent to:\n *\n * ```\n * await this.print().spawnTransparently(\u2026).success;\n * ```\n */\n public async shellOut(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<void> {\n await this.print().spawnTransparently(options).success;\n }\n\n /**\n * The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313\n */\n #spawnBun<\n const In extends BunSpawnOptions.Writable = \"ignore\",\n const Out extends BunSpawnOptions.Readable = \"pipe\",\n const Err extends BunSpawnOptions.Readable = \"inherit\",\n >(\n options?: BunWithCwd<\n Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, \"cmd\">\n >,\n ): BunSubprocess<In, Out, Err> & { success: Promise<void> } {\n if (options && \"cmd\" in options) {\n throw new Error(\"Unexpected `cmd` field.\");\n }\n const { spawn } = process.getBuiltinModule(\"bun\") as typeof import(\"bun\");\n const cwd = stringifyIfPath(options?.cwd);\n const subprocess = spawn({\n ...options,\n cwd,\n cmd: this.forBun(),\n }) as BunSubprocess<In, Out, Err> & { success: Promise<void> };\n Object.defineProperty(subprocess, \"success\", {\n get() {\n return new Promise<void>((resolve, reject) =>\n this.exited\n .then((exitCode: number) => {\n if (exitCode === 0) {\n resolve();\n } else {\n reject(\n new Error(\n `Command failed with non-zero exit code: ${exitCode}`,\n ),\n );\n }\n })\n .catch(reject),\n );\n },\n enumerable: false,\n });\n return subprocess;\n }\n\n #spawnBunInherit(\n options?: BunWithCwd<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\" | \"stdio\"\n >\n >,\n ): BunSubprocess<\"inherit\", \"inherit\", \"inherit\"> & {\n success: Promise<void>;\n } {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n return this.bun.spawnBun({\n ...options,\n stdio: [\"inherit\", \"inherit\", \"inherit\"],\n });\n }\n\n #spawnBunStdout(\n options?: BunWithCwd<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\" | \"stdio\"\n >\n >,\n ): Response {\n // biome-ignore lint/suspicious/noExplicitAny: Avoid breaking the lib check when used without `@types/bun`.\n return new Response((this.bun.spawnBun(options) as any).stdout);\n }\n\n async #shellOutBun(\n options?: BunWithCwd<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\" | \"stdio\"\n >\n >,\n ): Promise<void> {\n await this.print().bun.spawnBunInherit(options).success;\n }\n\n bun = {\n /** Equivalent to:\n *\n * ```\n * await this.print().bun.spawnBunInherit(\u2026).success;\n * ```\n */\n spawnBun: this.#spawnBun.bind(this),\n /**\n * A wrapper for `.spawnBunInherit(\u2026)` that sets stdio to `\"inherit\"` (common\n * for invoking commands from scripts whose output and interaction should be\n * surfaced to the user).\n */\n spawnBunInherit: this.#spawnBunInherit.bind(this),\n /** Equivalent to:\n *\n * ```\n * new Response(this.bun.spawnBun(\u2026).stdout);\n * ```\n */\n spawnBunStdout: this.#spawnBunStdout.bind(this),\n shellOutBun: this.#shellOutBun.bind(this),\n };\n}\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["import assert from \"node:assert\";\nimport type {\n ChildProcessByStdio,\n ChildProcess as NodeChildProcess,\n SpawnOptions as NodeSpawnOptions,\n} from \"node:child_process\";\nimport { createReadStream } from \"node:fs\";\nimport { stderr } from \"node:process\";\nimport { Readable, Writable } from \"node:stream\";\nimport type { WriteStream } from \"node:tty\";\nimport { styleText } from \"node:util\";\nimport type {\n SpawnOptions as BunSpawnOptions,\n Subprocess as BunSubprocess,\n SpawnOptions,\n} from \"bun\";\nimport { Path, stringifyIfPath } from \"path-class\";\nimport type { SetFieldType } from \"type-fest\";\nimport type {\n NodeWithCwd,\n spawnType,\n WithStderrResponse,\n WithStdoutResponse,\n WithSuccess,\n} from \"./spawn\";\n\n// TODO: does this import work?\n/**\n * @import { stdout } from \"node:process\"\n */\n\nconst DEFAULT_MAIN_INDENTATION = \"\";\nconst DEFAULT_ARG_INDENTATION = \" \";\nconst DEFAULT_ARGUMENT_LINE_WRAPPING = \"by-entry\";\n\nconst INLINE_SEPARATOR = \" \";\nconst LINE_WRAP_LINE_END = \" \\\\\\n\";\n\ntype StyleTextFormat = Parameters<typeof styleText>[0];\n\nconst TTY_AUTO_STYLE: StyleTextFormat = [\"gray\", \"bold\"];\n\n// biome-ignore lint/suspicious/noExplicitAny: This is the correct type nere.\nfunction isString(s: any): s is string {\n return typeof s === \"string\";\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: This is the correct type here.\nfunction isValidArgsEntryArray(entries: any[]): entries is SingleArgument[] {\n for (const entry of entries) {\n if (isString(entry)) {\n continue;\n }\n if (entry instanceof Path) {\n continue;\n }\n return false;\n }\n return true;\n}\n\n// TODO: allow `.toString()`ables?\ntype SingleArgument = string | Path;\ntype ArgsEntry = SingleArgument | SingleArgument[];\ntype Args = ArgsEntry[];\n\nexport interface PrintOptions {\n /** Defaults to \"\" */\n mainIndentation?: string;\n /** Defaults to \" \" */\n argIndentation?: string;\n /**\n * - `\"auto\"`: Quote only arguments that need it for safety. This tries to be\n * portable and safe across shells, but true safety and portability is hard\n * to guarantee.\n * - `\"extra-safe\"`: Quote all arguments, even ones that don't need it. This is\n * more likely to be safe under all circumstances.\n */\n quoting?: \"auto\" | \"extra-safe\";\n /** Line wrapping to use between arguments. Defaults to `\"by-entry\"`. */\n argumentLineWrapping?:\n | \"by-entry\"\n | \"nested-by-entry\"\n | \"by-argument\"\n | \"inline\";\n /** Include the first arg (or first arg group) on the same line as the command, regardless of the `argumentLineWrapping` setting. */\n skipLineWrapBeforeFirstArg?: true | false;\n /**\n * Style text using `node`'s {@link styleText | `styleText(\u2026)`}.\n *\n * Example usage:\n *\n * ```\n * new PrintableShellCommand(\"echo\", [\"hi\"]).print({\n * styleTextFormat: [\"green\", \"underline\"],\n * });\n * */\n styleTextFormat?: StyleTextFormat;\n}\n\nexport interface StreamPrintOptions extends PrintOptions {\n /**\n * Auto-style the text when:\n *\n * - the output stream is detected to be a TTY\n * - `styleTextFormat` is not specified.\n *\n * The current auto style is: `[\"gray\", \"bold\"]`\n */\n autoStyle?: \"tty\" | \"never\";\n // This would be a `WritableStream` (open web standard), but `WriteStream` allows us to query `.isTTY`.\n stream?: WriteStream | Writable;\n}\n\n// https://mywiki.wooledge.org/BashGuide/SpecialCharacters\nconst SPECIAL_SHELL_CHARACTERS = new Set([\n \" \",\n '\"',\n \"'\",\n \"`\",\n \"|\",\n \"$\",\n \"*\",\n \"?\",\n \">\",\n \"<\",\n \"(\",\n \")\",\n \"[\",\n \"]\",\n \"{\",\n \"}\",\n \"&\",\n \"\\\\\",\n \";\",\n \"#\",\n]);\n\n// https://mywiki.wooledge.org/BashGuide/SpecialCharacters\nconst SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND =\n // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)\n (SPECIAL_SHELL_CHARACTERS as unknown as any).union(new Set([\"=\"]));\n\n// biome-ignore lint/suspicious/noExplicitAny: Just matching\ntype BunCwd = SpawnOptions.OptionsObject<any, any, any>[\"cwd\"] | Path;\ntype BunWithCwd<\n // biome-ignore lint/suspicious/noExplicitAny: Just matching\n T extends { cwd?: SpawnOptions.OptionsObject<any, any, any>[\"cwd\"] | Path },\n> = SetFieldType<T, \"cwd\", BunCwd | undefined>;\n\n// TODO: Is there an idiomatic ways to check that all potential fields of\n// `StdinSource` satisfy `(typeof STDIN_SOURCE_KEYS)[number]`, without adding\n// extra indirection for type wrangling?\nconst STDIN_SOURCE_KEYS = [\"text\", \"json\", \"path\", \"stream\"] as const;\nexport type StdinSource =\n | { text: string }\n // biome-ignore lint/suspicious/noExplicitAny: `any` is the correct type for JSON data.\n | { json: any }\n | { path: string | Path }\n | { stream: Readable | ReadableStream };\n\nexport class PrintableShellCommand {\n #commandName: string | Path;\n constructor(\n commandName: string | Path,\n private args: Args = [],\n ) {\n if (!isString(commandName) && !(commandName instanceof Path)) {\n // biome-ignore lint/suspicious/noExplicitAny: We want to print this, no matter what it is.\n throw new Error(\"Command name is not a string:\", commandName as any);\n }\n this.#commandName = commandName;\n if (typeof args === \"undefined\") {\n return;\n }\n if (!Array.isArray(args)) {\n throw new Error(\"Command arguments are not an array\");\n }\n for (let i = 0; i < args.length; i++) {\n const argEntry = args[i];\n if (typeof argEntry === \"string\") {\n continue;\n }\n if (argEntry instanceof Path) {\n continue;\n }\n if (Array.isArray(argEntry) && isValidArgsEntryArray(argEntry)) {\n continue;\n }\n throw new Error(`Invalid arg entry at index: ${i}`);\n }\n }\n\n get commandName(): string {\n return stringifyIfPath(this.#commandName);\n }\n\n /** For use with `bun`.\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"bun\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * await spawn(command.toFlatCommand()).exited;\n * ```\n */\n public toFlatCommand(): string[] {\n return [this.commandName, ...this.args.flat().map(stringifyIfPath)];\n }\n\n /**\n * Convenient alias for {@link PrintableShellCommand.toFlatCommand | `.toFlatCommand()`}.\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"bun\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * await spawn(command.forBun()).exited;\n * ```\n *\n * */\n public forBun(): string[] {\n return this.toFlatCommand();\n }\n\n /**\n * For use with `node:child_process`\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"node:child_process\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * const child_process = spawn(...command.toCommandWithFlatArgs()); // Note the `...`\n * ```\n *\n */\n public toCommandWithFlatArgs(): [string, string[]] {\n return [this.commandName, this.args.flat().map(stringifyIfPath)];\n }\n\n /**\n * For use with `node:child_process`\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"node:child_process\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * const child_process = spawn(...command.forNode()); // Note the `...`\n * ```\n *\n * Convenient alias for {@link PrintableShellCommand.toCommandWithFlatArgs | `toCommandWithFlatArgs()`}.\n */\n public forNode(): [string, string[]] {\n return this.toCommandWithFlatArgs();\n }\n\n #mainIndentation(options: PrintOptions): string {\n return options?.mainIndentation ?? DEFAULT_MAIN_INDENTATION;\n }\n\n #argIndentation(options: PrintOptions): string {\n return (\n this.#mainIndentation(options) +\n (options?.argIndentation ?? DEFAULT_ARG_INDENTATION)\n );\n }\n\n #lineWrapSeparator(options: PrintOptions): string {\n return LINE_WRAP_LINE_END + this.#argIndentation(options);\n }\n\n #argPairSeparator(options: PrintOptions): string {\n switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n case \"by-entry\": {\n return INLINE_SEPARATOR;\n }\n case \"nested-by-entry\": {\n return this.#lineWrapSeparator(options) + this.#argIndentation(options);\n }\n case \"by-argument\": {\n return this.#lineWrapSeparator(options);\n }\n case \"inline\": {\n return INLINE_SEPARATOR;\n }\n default:\n throw new Error(\"Invalid argument line wrapping argument.\");\n }\n }\n\n #intraEntrySeparator(options: PrintOptions): string {\n switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n case \"by-entry\":\n case \"nested-by-entry\":\n case \"by-argument\": {\n return LINE_WRAP_LINE_END + this.#argIndentation(options);\n }\n case \"inline\": {\n return INLINE_SEPARATOR;\n }\n default:\n throw new Error(\"Invalid argument line wrapping argument.\");\n }\n }\n\n #separatorAfterCommand(\n options: PrintOptions,\n numFollowingEntries: number,\n ): string {\n if (numFollowingEntries === 0) {\n return \"\";\n }\n if (options.skipLineWrapBeforeFirstArg ?? false) {\n return INLINE_SEPARATOR;\n }\n return this.#intraEntrySeparator(options);\n }\n\n public getPrintableCommand(options?: PrintOptions): string {\n // TODO: Why in the world does TypeScript not give the `options` arg the type of `PrintOptions | undefined`???\n options ??= {};\n const serializedEntries: string[] = [];\n\n for (let i = 0; i < this.args.length; i++) {\n const argsEntry = stringifyIfPath(this.args[i]);\n\n if (isString(argsEntry)) {\n serializedEntries.push(escapeArg(argsEntry, false, options));\n } else {\n serializedEntries.push(\n argsEntry\n .map((part) => escapeArg(stringifyIfPath(part), false, options))\n .join(this.#argPairSeparator(options)),\n );\n }\n }\n\n let text =\n this.#mainIndentation(options) +\n escapeArg(this.commandName, true, options) +\n this.#separatorAfterCommand(options, serializedEntries.length) +\n serializedEntries.join(this.#intraEntrySeparator(options));\n if (options?.styleTextFormat) {\n text = styleText(options.styleTextFormat, text);\n }\n return text;\n }\n\n /**\n * Print the shell command to {@link stderr} (default) or a specified stream.\n *\n * By default, this will be auto-styled (as bold gray) when `.isTTY` is true\n * for the stream. `.isTTY` is populated for the {@link stderr} and\n * {@link stdout} objects. Pass `\"autoStyle\": \"never\"` or an explicit\n * `styleTextFormat` to disable this.\n *\n */\n public print(options?: StreamPrintOptions): PrintableShellCommand {\n const stream = options?.stream ?? stderr;\n // Note: we only need to modify top-level fields, so `structuredClone(\u2026)`\n // would be overkill and can only cause performance issues.\n const optionsCopy = { ...options };\n optionsCopy.styleTextFormat ??=\n options?.autoStyle !== \"never\" &&\n (stream as { isTTY?: boolean }).isTTY === true\n ? TTY_AUTO_STYLE\n : undefined;\n const writable =\n stream instanceof Writable ? stream : Writable.fromWeb(stream);\n writable.write(this.getPrintableCommand(optionsCopy));\n writable.write(\"\\n\");\n return this;\n }\n\n #stdinSource: StdinSource | undefined;\n /**\n * Send data to `stdin` of the subprocess.\n *\n * Note that this will overwrite:\n *\n * - Any previous value set using {@link PrintableShellCommand.stdin | `.stdin(\u2026)`}.\n * - Any value set for `stdin` using the `\"stdio\"` field of {@link PrintableShellCommand.spawn | `.spawn(\u2026)`}.\n */\n stdin(source: StdinSource): PrintableShellCommand {\n const [key, ...moreKeys] = Object.keys(source);\n assert.equal(moreKeys.length, 0);\n // TODO: validate values?\n assert((STDIN_SOURCE_KEYS as unknown as string[]).includes(key));\n\n this.#stdinSource = source;\n return this;\n }\n\n /**\n * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313\n */\n public spawn: typeof spawnType = ((\n options?: Parameters<typeof spawnType>[0],\n ) => {\n const { spawn } = process.getBuiltinModule(\"node:child_process\");\n const cwd = stringifyIfPath(options?.cwd);\n if (this.#stdinSource) {\n options ??= {};\n if (typeof options.stdio === \"undefined\") {\n options.stdio = \"pipe\";\n }\n if (typeof options.stdio === \"string\") {\n options.stdio = new Array(3).fill(options.stdio);\n }\n options.stdio[0] = \"pipe\";\n }\n // biome-ignore lint/suspicious/noTsIgnore: We don't want linting to depend on *broken* type checking.\n // @ts-ignore: The TypeScript checker has trouble reconciling the optional (i.e. potentially `undefined`) `options` with the third argument.\n const subprocess = spawn(...this.forNode(), {\n ...(options as object),\n cwd,\n }) as NodeChildProcess & {\n success: Promise<void>;\n };\n // TODO: define properties on prototypes instead.\n Object.defineProperty(subprocess, \"success\", {\n get() {\n return new Promise<void>((resolve, reject) =>\n this.addListener(\n \"exit\",\n (exitCode: number /* we only use the first arg */) => {\n if (exitCode === 0) {\n resolve();\n } else {\n reject(`Command failed with non-zero exit code: ${exitCode}`);\n }\n },\n ),\n );\n },\n enumerable: false,\n });\n if (subprocess.stdout) {\n // TODO: dedupe\n const s = subprocess as unknown as Readable &\n WithStdoutResponse &\n WithSuccess;\n s.stdout.response = () =>\n new Response(Readable.from(this.#generator(s.stdout, s.success)));\n s.stdout.text = () => s.stdout.response().text();\n const thisCached = this; // TODO: make this type-check using `.bind(\u2026)`\n s.stdout.text0 = async function* () {\n yield* thisCached.#split0(thisCached.#generator(s.stdout, s.success));\n };\n s.stdout.json = <T>() => s.stdout.response().json() as Promise<T>;\n }\n if (subprocess.stderr) {\n // TODO: dedupe\n const s = subprocess as unknown as Readable &\n WithStderrResponse &\n WithSuccess;\n s.stderr.response = () =>\n new Response(Readable.from(this.#generator(s.stderr, s.success)));\n s.stderr.text = () => s.stderr.response().text();\n const thisCached = this; // TODO: make this type-check using `.bind(\u2026)`\n s.stderr.text0 = async function* () {\n yield* thisCached.#split0(thisCached.#generator(s.stderr, s.success));\n };\n s.stderr.json = <T>() => s.stderr.response().json() as Promise<T>;\n }\n if (this.#stdinSource) {\n const { stdin } = subprocess;\n assert(stdin);\n if (\"text\" in this.#stdinSource) {\n stdin.write(this.#stdinSource.text);\n stdin.end();\n } else if (\"json\" in this.#stdinSource) {\n stdin.write(JSON.stringify(this.#stdinSource.json));\n stdin.end();\n } else if (\"path\" in this.#stdinSource) {\n createReadStream(stringifyIfPath(this.#stdinSource.path)).pipe(stdin);\n } else if (\"stream\" in this.#stdinSource) {\n const stream = (() => {\n const { stream } = this.#stdinSource;\n return stream instanceof Readable ? stream : Readable.fromWeb(stream);\n })();\n stream.pipe(stdin);\n } else {\n throw new Error(\"Invalid `.stdin(\u2026)` source?\");\n }\n }\n return subprocess;\n // biome-ignore lint/suspicious/noExplicitAny: Type wrangling\n }) as any;\n\n /** A wrapper for `.spawn(\u2026)` that sets stdio to `\"inherit\"` (common for\n * invoking commands from scripts whose output and interaction should be\n * surfaced to the user).\n *\n * If there is no other interaction with the shell from the calling process,\n * then it acts \"transparent\" and allows user to interact with the subprocess\n * in its stead.\n */\n public spawnTransparently(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): ChildProcessByStdio<null, null, null> & WithSuccess {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Type wrangling.\n return this.spawn({ ...options, stdio: \"inherit\" }) as any;\n }\n\n /**\n * A wrapper for {@link PrintableShellCommand.spawn | `.spawn(\u2026)`} that:\n *\n * - sets `detached` to `true`,\n * - sets stdio to `\"inherit\"`,\n * - calls `.unref()`, and\n * - does not wait for the process to exit.\n *\n * This is similar to starting a command in the background and disowning it (in a shell).\n *\n */\n public spawnDetached(\n options?: NodeWithCwd<Omit<Omit<NodeSpawnOptions, \"stdio\">, \"detached\">>,\n ): void {\n if (options) {\n for (const field of [\"stdio\", \"detached\"]) {\n if (field in options) {\n throw new Error(`Unexpected \\`${field}\\` field.`);\n }\n }\n }\n const childProcess = this.spawn({\n stdio: \"ignore\",\n ...options,\n detached: true,\n });\n childProcess.unref();\n }\n\n #generator(\n readable: Readable,\n successPromise: Promise<void>,\n ): AsyncGenerator<string> {\n // TODO: we'd make this a `ReadableStream`, but `ReadableStream.from(\u2026)` is\n // not implemented in `bun`: https://github.com/oven-sh/bun/issues/3700\n return (async function* () {\n for await (const chunk of readable) {\n yield chunk;\n }\n await successPromise;\n })();\n }\n\n #stdoutSpawnGenerator(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): AsyncGenerator<string> {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n const subprocess = this.spawn({\n ...options,\n stdio: [\"ignore\", \"pipe\", \"inherit\"],\n });\n return this.#generator(subprocess.stdout, subprocess.success);\n }\n\n public stdout(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Response {\n // TODO: Use `ReadableStream.from(\u2026)` once `bun` implements it: https://github.com/oven-sh/bun/pull/21269\n return new Response(Readable.from(this.#stdoutSpawnGenerator(options)));\n }\n\n async *#split0(generator: AsyncGenerator<string>): AsyncGenerator<string> {\n let pending = \"\";\n for await (const chunk of generator) {\n pending += chunk;\n const newChunks = pending.split(\"\\x00\");\n pending = newChunks.splice(-1)[0];\n yield* newChunks;\n }\n if (pending !== \"\") {\n throw new Error(\n \"Missing a trailing NUL character at the end of a NUL-delimited stream.\",\n );\n }\n }\n\n /**\n * Convenience function for:\n *\n * .stdout(options).text()\n *\n * This can make some simple invocations easier to read and/or fit on a single line.\n */\n public text(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<string> {\n return this.stdout(options).text();\n }\n\n /**\n * Convenience function for:\n *\n * .stdout(options).json()\n *\n * This can make some simple invocations easier to read and/or fit on a single line.\n */\n public json<T>(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<T> {\n return this.stdout(options).json() as Promise<T>;\n }\n\n /**\n * Parse `stdout` into a generator of string values using a NULL delimiter.\n *\n * A trailing NULL delimiter from `stdout` is required and removed.\n */\n public async *text0(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): AsyncGenerator<string> {\n yield* this.#split0(this.#stdoutSpawnGenerator(options));\n }\n\n /**\n * Parse `stdout` into a generator of JSON values using a NULL delimiter.\n *\n * A trailing NULL delimiter from `stdout` is required and removed.\n */\n public async *json0(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): // biome-ignore lint/suspicious/noExplicitAny: `any` is the correct type for JSON\n AsyncGenerator<any> {\n for await (const part of this.#split0(\n this.#stdoutSpawnGenerator(options),\n )) {\n yield JSON.parse(part);\n }\n }\n\n /** Equivalent to:\n *\n * ```\n * await this.print().spawnTransparently(\u2026).success;\n * ```\n */\n public async shellOut(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<void> {\n await this.print().spawnTransparently(options).success;\n }\n\n /**\n * The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313\n */\n #spawnBun<\n const In extends BunSpawnOptions.Writable = \"ignore\",\n const Out extends BunSpawnOptions.Readable = \"pipe\",\n const Err extends BunSpawnOptions.Readable = \"inherit\",\n >(\n options?: BunWithCwd<\n Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, \"cmd\">\n >,\n ): BunSubprocess<In, Out, Err> & { success: Promise<void> } {\n if (options && \"cmd\" in options) {\n throw new Error(\"Unexpected `cmd` field.\");\n }\n const { spawn } = process.getBuiltinModule(\"bun\") as typeof import(\"bun\");\n const cwd = stringifyIfPath(options?.cwd);\n const subprocess = spawn({\n ...options,\n cwd,\n cmd: this.forBun(),\n }) as BunSubprocess<In, Out, Err> & { success: Promise<void> };\n Object.defineProperty(subprocess, \"success\", {\n get() {\n return new Promise<void>((resolve, reject) =>\n this.exited\n .then((exitCode: number) => {\n if (exitCode === 0) {\n resolve();\n } else {\n reject(\n new Error(\n `Command failed with non-zero exit code: ${exitCode}`,\n ),\n );\n }\n })\n .catch(reject),\n );\n },\n enumerable: false,\n });\n return subprocess;\n }\n\n #spawnBunInherit(\n options?: BunWithCwd<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\" | \"stdio\"\n >\n >,\n ): BunSubprocess<\"inherit\", \"inherit\", \"inherit\"> & {\n success: Promise<void>;\n } {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n return this.bun.spawnBun({\n ...options,\n stdio: [\"inherit\", \"inherit\", \"inherit\"],\n });\n }\n\n #spawnBunStdout(\n options?: BunWithCwd<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\" | \"stdio\"\n >\n >,\n ): Response {\n // biome-ignore lint/suspicious/noExplicitAny: Avoid breaking the lib check when used without `@types/bun`.\n return new Response((this.bun.spawnBun(options) as any).stdout);\n }\n\n async #shellOutBun(\n options?: BunWithCwd<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\" | \"stdio\"\n >\n >,\n ): Promise<void> {\n await this.print().bun.spawnBunInherit(options).success;\n }\n\n bun = {\n /** Equivalent to:\n *\n * ```\n * await this.print().bun.spawnBunInherit(\u2026).success;\n * ```\n */\n spawnBun: this.#spawnBun.bind(this),\n /**\n * A wrapper for `.spawnBunInherit(\u2026)` that sets stdio to `\"inherit\"` (common\n * for invoking commands from scripts whose output and interaction should be\n * surfaced to the user).\n */\n spawnBunInherit: this.#spawnBunInherit.bind(this),\n /** Equivalent to:\n *\n * ```\n * new Response(this.bun.spawnBun(\u2026).stdout);\n * ```\n */\n spawnBunStdout: this.#spawnBunStdout.bind(this),\n shellOutBun: this.#shellOutBun.bind(this),\n };\n}\n\nexport function escapeArg(\n arg: string,\n isMainCommand: boolean,\n options: PrintOptions,\n): string {\n const argCharacters = new Set(arg);\n const specialShellCharacters = isMainCommand\n ? SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND\n : SPECIAL_SHELL_CHARACTERS;\n if (\n options?.quoting === \"extra-safe\" ||\n // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)\n (argCharacters as unknown as any).intersection(specialShellCharacters)\n .size > 0\n ) {\n // Use single quote to reduce the need to escape (and therefore reduce the chance for bugs/security issues).\n const escaped = arg.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"'\", \"\\\\'\");\n return `'${escaped}'`;\n }\n return arg;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["stream"]
7
7
  }
@@ -9,13 +9,25 @@ export type NodeWithCwd<T extends {
9
9
  export interface WithSuccess {
10
10
  success: Promise<void>;
11
11
  }
12
+ export interface WithResponse {
13
+ response: () => Response;
14
+ text: () => Promise<string>;
15
+ text0: () => AsyncGenerator<string>;
16
+ json: <T>() => Promise<T>;
17
+ }
18
+ export interface WithStdoutResponse {
19
+ stdout: Readable & WithResponse;
20
+ }
21
+ export interface WithStderrResponse {
22
+ stderr: Readable & WithResponse;
23
+ }
12
24
  export declare function spawnType(options?: NodeWithCwd<SpawnOptionsWithoutStdio>): ChildProcessWithoutNullStreams & WithSuccess;
13
- export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioPipe, StdioPipe, StdioPipe>>): ChildProcessByStdio<Writable, Readable, Readable> & WithSuccess;
14
- export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioPipe, StdioPipe, StdioNull>>): ChildProcessByStdio<Writable, Readable, null> & WithSuccess;
15
- export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioPipe, StdioNull, StdioPipe>>): ChildProcessByStdio<Writable, null, Readable> & WithSuccess;
16
- export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioNull, StdioPipe, StdioPipe>>): ChildProcessByStdio<null, Readable, Readable> & WithSuccess;
25
+ export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioPipe, StdioPipe, StdioPipe>>): ChildProcessByStdio<Writable, Readable, Readable> & WithSuccess & WithStdoutResponse & WithStderrResponse;
26
+ export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioPipe, StdioPipe, StdioNull>>): ChildProcessByStdio<Writable, Readable, null> & WithSuccess & WithStdoutResponse;
27
+ export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioPipe, StdioNull, StdioPipe>>): ChildProcessByStdio<Writable, null, Readable> & WithSuccess & WithStderrResponse;
28
+ export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioNull, StdioPipe, StdioPipe>>): ChildProcessByStdio<null, Readable, Readable> & WithSuccess & WithStdoutResponse & WithStderrResponse;
17
29
  export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioPipe, StdioNull, StdioNull>>): ChildProcessByStdio<Writable, null, null> & WithSuccess;
18
- export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioNull, StdioPipe, StdioNull>>): ChildProcessByStdio<null, Readable, null> & WithSuccess;
19
- export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioNull, StdioNull, StdioPipe>>): ChildProcessByStdio<null, null, Readable> & WithSuccess;
30
+ export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioNull, StdioPipe, StdioNull>>): ChildProcessByStdio<null, Readable, null> & WithSuccess & WithStdoutResponse;
31
+ export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioNull, StdioNull, StdioPipe>>): ChildProcessByStdio<null, null, Readable> & WithSuccess & WithStderrResponse;
20
32
  export declare function spawnType(options: NodeWithCwd<SpawnOptionsWithStdioTuple<StdioNull, StdioNull, StdioNull>>): ChildProcessByStdio<null, null, null> & WithSuccess;
21
33
  export declare function spawnType(options: NodeWithCwd<SpawnOptions>): ChildProcess & WithSuccess;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "printable-shell-command",
3
- "version": "2.7.4",
3
+ "version": "3.0.1",
4
4
  "devDependencies": {
5
5
  "@biomejs/biome": "^2.1.4",
6
6
  "@cubing/dev-config": "^0.3.6",
package/src/index.ts CHANGED
@@ -1,8 +1,10 @@
1
+ import assert from "node:assert";
1
2
  import type {
2
3
  ChildProcessByStdio,
3
4
  ChildProcess as NodeChildProcess,
4
5
  SpawnOptions as NodeSpawnOptions,
5
6
  } from "node:child_process";
7
+ import { createReadStream } from "node:fs";
6
8
  import { stderr } from "node:process";
7
9
  import { Readable, Writable } from "node:stream";
8
10
  import type { WriteStream } from "node:tty";
@@ -14,7 +16,13 @@ import type {
14
16
  } from "bun";
15
17
  import { Path, stringifyIfPath } from "path-class";
16
18
  import type { SetFieldType } from "type-fest";
17
- import type { NodeWithCwd, spawnType, WithSuccess } from "./spawn";
19
+ import type {
20
+ NodeWithCwd,
21
+ spawnType,
22
+ WithStderrResponse,
23
+ WithStdoutResponse,
24
+ WithSuccess,
25
+ } from "./spawn";
18
26
 
19
27
  // TODO: does this import work?
20
28
  /**
@@ -140,6 +148,17 @@ type BunWithCwd<
140
148
  T extends { cwd?: SpawnOptions.OptionsObject<any, any, any>["cwd"] | Path },
141
149
  > = SetFieldType<T, "cwd", BunCwd | undefined>;
142
150
 
151
+ // TODO: Is there an idiomatic ways to check that all potential fields of
152
+ // `StdinSource` satisfy `(typeof STDIN_SOURCE_KEYS)[number]`, without adding
153
+ // extra indirection for type wrangling?
154
+ const STDIN_SOURCE_KEYS = ["text", "json", "path", "stream"] as const;
155
+ export type StdinSource =
156
+ | { text: string }
157
+ // biome-ignore lint/suspicious/noExplicitAny: `any` is the correct type for JSON data.
158
+ | { json: any }
159
+ | { path: string | Path }
160
+ | { stream: Readable | ReadableStream };
161
+
143
162
  export class PrintableShellCommand {
144
163
  #commandName: string | Path;
145
164
  constructor(
@@ -247,28 +266,6 @@ export class PrintableShellCommand {
247
266
  return this.toCommandWithFlatArgs();
248
267
  }
249
268
 
250
- #escapeArg(
251
- arg: string,
252
- isMainCommand: boolean,
253
- options: PrintOptions,
254
- ): string {
255
- const argCharacters = new Set(arg);
256
- const specialShellCharacters = isMainCommand
257
- ? SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND
258
- : SPECIAL_SHELL_CHARACTERS;
259
- if (
260
- options?.quoting === "extra-safe" ||
261
- // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)
262
- (argCharacters as unknown as any).intersection(specialShellCharacters)
263
- .size > 0
264
- ) {
265
- // Use single quote to reduce the need to escape (and therefore reduce the chance for bugs/security issues).
266
- const escaped = arg.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
267
- return `'${escaped}'`;
268
- }
269
- return arg;
270
- }
271
-
272
269
  #mainIndentation(options: PrintOptions): string {
273
270
  return options?.mainIndentation ?? DEFAULT_MAIN_INDENTATION;
274
271
  }
@@ -340,13 +337,11 @@ export class PrintableShellCommand {
340
337
  const argsEntry = stringifyIfPath(this.args[i]);
341
338
 
342
339
  if (isString(argsEntry)) {
343
- serializedEntries.push(this.#escapeArg(argsEntry, false, options));
340
+ serializedEntries.push(escapeArg(argsEntry, false, options));
344
341
  } else {
345
342
  serializedEntries.push(
346
343
  argsEntry
347
- .map((part) =>
348
- this.#escapeArg(stringifyIfPath(part), false, options),
349
- )
344
+ .map((part) => escapeArg(stringifyIfPath(part), false, options))
350
345
  .join(this.#argPairSeparator(options)),
351
346
  );
352
347
  }
@@ -354,7 +349,7 @@ export class PrintableShellCommand {
354
349
 
355
350
  let text =
356
351
  this.#mainIndentation(options) +
357
- this.#escapeArg(this.commandName, true, options) +
352
+ escapeArg(this.commandName, true, options) +
358
353
  this.#separatorAfterCommand(options, serializedEntries.length) +
359
354
  serializedEntries.join(this.#intraEntrySeparator(options));
360
355
  if (options?.styleTextFormat) {
@@ -389,6 +384,25 @@ export class PrintableShellCommand {
389
384
  return this;
390
385
  }
391
386
 
387
+ #stdinSource: StdinSource | undefined;
388
+ /**
389
+ * Send data to `stdin` of the subprocess.
390
+ *
391
+ * Note that this will overwrite:
392
+ *
393
+ * - Any previous value set using {@link PrintableShellCommand.stdin | `.stdin(…)`}.
394
+ * - Any value set for `stdin` using the `"stdio"` field of {@link PrintableShellCommand.spawn | `.spawn(…)`}.
395
+ */
396
+ stdin(source: StdinSource): PrintableShellCommand {
397
+ const [key, ...moreKeys] = Object.keys(source);
398
+ assert.equal(moreKeys.length, 0);
399
+ // TODO: validate values?
400
+ assert((STDIN_SOURCE_KEYS as unknown as string[]).includes(key));
401
+
402
+ this.#stdinSource = source;
403
+ return this;
404
+ }
405
+
392
406
  /**
393
407
  * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
394
408
  */
@@ -397,6 +411,16 @@ export class PrintableShellCommand {
397
411
  ) => {
398
412
  const { spawn } = process.getBuiltinModule("node:child_process");
399
413
  const cwd = stringifyIfPath(options?.cwd);
414
+ if (this.#stdinSource) {
415
+ options ??= {};
416
+ if (typeof options.stdio === "undefined") {
417
+ options.stdio = "pipe";
418
+ }
419
+ if (typeof options.stdio === "string") {
420
+ options.stdio = new Array(3).fill(options.stdio);
421
+ }
422
+ options.stdio[0] = "pipe";
423
+ }
400
424
  // biome-ignore lint/suspicious/noTsIgnore: We don't want linting to depend on *broken* type checking.
401
425
  // @ts-ignore: The TypeScript checker has trouble reconciling the optional (i.e. potentially `undefined`) `options` with the third argument.
402
426
  const subprocess = spawn(...this.forNode(), {
@@ -405,6 +429,7 @@ export class PrintableShellCommand {
405
429
  }) as NodeChildProcess & {
406
430
  success: Promise<void>;
407
431
  };
432
+ // TODO: define properties on prototypes instead.
408
433
  Object.defineProperty(subprocess, "success", {
409
434
  get() {
410
435
  return new Promise<void>((resolve, reject) =>
@@ -422,6 +447,55 @@ export class PrintableShellCommand {
422
447
  },
423
448
  enumerable: false,
424
449
  });
450
+ if (subprocess.stdout) {
451
+ // TODO: dedupe
452
+ const s = subprocess as unknown as Readable &
453
+ WithStdoutResponse &
454
+ WithSuccess;
455
+ s.stdout.response = () =>
456
+ new Response(Readable.from(this.#generator(s.stdout, s.success)));
457
+ s.stdout.text = () => s.stdout.response().text();
458
+ const thisCached = this; // TODO: make this type-check using `.bind(…)`
459
+ s.stdout.text0 = async function* () {
460
+ yield* thisCached.#split0(thisCached.#generator(s.stdout, s.success));
461
+ };
462
+ s.stdout.json = <T>() => s.stdout.response().json() as Promise<T>;
463
+ }
464
+ if (subprocess.stderr) {
465
+ // TODO: dedupe
466
+ const s = subprocess as unknown as Readable &
467
+ WithStderrResponse &
468
+ WithSuccess;
469
+ s.stderr.response = () =>
470
+ new Response(Readable.from(this.#generator(s.stderr, s.success)));
471
+ s.stderr.text = () => s.stderr.response().text();
472
+ const thisCached = this; // TODO: make this type-check using `.bind(…)`
473
+ s.stderr.text0 = async function* () {
474
+ yield* thisCached.#split0(thisCached.#generator(s.stderr, s.success));
475
+ };
476
+ s.stderr.json = <T>() => s.stderr.response().json() as Promise<T>;
477
+ }
478
+ if (this.#stdinSource) {
479
+ const { stdin } = subprocess;
480
+ assert(stdin);
481
+ if ("text" in this.#stdinSource) {
482
+ stdin.write(this.#stdinSource.text);
483
+ stdin.end();
484
+ } else if ("json" in this.#stdinSource) {
485
+ stdin.write(JSON.stringify(this.#stdinSource.json));
486
+ stdin.end();
487
+ } else if ("path" in this.#stdinSource) {
488
+ createReadStream(stringifyIfPath(this.#stdinSource.path)).pipe(stdin);
489
+ } else if ("stream" in this.#stdinSource) {
490
+ const stream = (() => {
491
+ const { stream } = this.#stdinSource;
492
+ return stream instanceof Readable ? stream : Readable.fromWeb(stream);
493
+ })();
494
+ stream.pipe(stdin);
495
+ } else {
496
+ throw new Error("Invalid `.stdin(…)` source?");
497
+ }
498
+ }
425
499
  return subprocess;
426
500
  // biome-ignore lint/suspicious/noExplicitAny: Type wrangling
427
501
  }) as any;
@@ -445,13 +519,6 @@ export class PrintableShellCommand {
445
519
  return this.spawn({ ...options, stdio: "inherit" }) as any;
446
520
  }
447
521
 
448
- /** @deprecated: Use `.spawnTransparently(…)`. */
449
- public spawnInherit(
450
- options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
451
- ): NodeChildProcess & WithSuccess {
452
- return this.spawnTransparently(options);
453
- }
454
-
455
522
  /**
456
523
  * A wrapper for {@link PrintableShellCommand.spawn | `.spawn(…)`} that:
457
524
  *
@@ -460,31 +527,18 @@ export class PrintableShellCommand {
460
527
  * - calls `.unref()`, and
461
528
  * - does not wait for the process to exit.
462
529
  *
463
- * This is similar to starting a command int he background and disowning it (in a shell).
464
- *
465
- * The `stdio` field is left overridable. To capture `stdout` and `stderr`, connect them to output files like this:
466
- *
467
- * ```
468
- * import { open } from "node:fs/promises";
469
- * import { Path } from "path-class";
470
- * import { PrintableShellCommand } from "printable-shell-command";
471
- *
472
- * const tempDir = await Path.makeTempDir();
473
- * console.log(`Temp dir: ${tempDir}`);
474
- * const stdout = await open(tempDir.join("stdout.log").path, "a");
475
- * const stderr = await open(tempDir.join("stderr.log").path, "a");
476
- *
477
- * new PrintableShellCommand("echo", ["hi"]).spawnDetached({
478
- * stdio: ["ignore", stdout.fd, stderr.fd],
479
- * });
480
- * ```
530
+ * This is similar to starting a command in the background and disowning it (in a shell).
481
531
  *
482
532
  */
483
533
  public spawnDetached(
484
- options?: NodeWithCwd<Omit<NodeSpawnOptions, "detached">>,
534
+ options?: NodeWithCwd<Omit<Omit<NodeSpawnOptions, "stdio">, "detached">>,
485
535
  ): void {
486
- if (options && "detached" in options) {
487
- throw new Error("Unexpected `detached` field.");
536
+ if (options) {
537
+ for (const field of ["stdio", "detached"]) {
538
+ if (field in options) {
539
+ throw new Error(`Unexpected \`${field}\` field.`);
540
+ }
541
+ }
488
542
  }
489
543
  const childProcess = this.spawn({
490
544
  stdio: "ignore",
@@ -494,9 +548,23 @@ export class PrintableShellCommand {
494
548
  childProcess.unref();
495
549
  }
496
550
 
497
- public stdout(
551
+ #generator(
552
+ readable: Readable,
553
+ successPromise: Promise<void>,
554
+ ): AsyncGenerator<string> {
555
+ // TODO: we'd make this a `ReadableStream`, but `ReadableStream.from(…)` is
556
+ // not implemented in `bun`: https://github.com/oven-sh/bun/issues/3700
557
+ return (async function* () {
558
+ for await (const chunk of readable) {
559
+ yield chunk;
560
+ }
561
+ await successPromise;
562
+ })();
563
+ }
564
+
565
+ #stdoutSpawnGenerator(
498
566
  options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
499
- ): Response {
567
+ ): AsyncGenerator<string> {
500
568
  if (options && "stdio" in options) {
501
569
  throw new Error("Unexpected `stdio` field.");
502
570
  }
@@ -504,8 +572,29 @@ export class PrintableShellCommand {
504
572
  ...options,
505
573
  stdio: ["ignore", "pipe", "inherit"],
506
574
  });
575
+ return this.#generator(subprocess.stdout, subprocess.success);
576
+ }
507
577
 
508
- return new Response(Readable.toWeb(subprocess.stdout));
578
+ public stdout(
579
+ options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
580
+ ): Response {
581
+ // TODO: Use `ReadableStream.from(…)` once `bun` implements it: https://github.com/oven-sh/bun/pull/21269
582
+ return new Response(Readable.from(this.#stdoutSpawnGenerator(options)));
583
+ }
584
+
585
+ async *#split0(generator: AsyncGenerator<string>): AsyncGenerator<string> {
586
+ let pending = "";
587
+ for await (const chunk of generator) {
588
+ pending += chunk;
589
+ const newChunks = pending.split("\x00");
590
+ pending = newChunks.splice(-1)[0];
591
+ yield* newChunks;
592
+ }
593
+ if (pending !== "") {
594
+ throw new Error(
595
+ "Missing a trailing NUL character at the end of a NUL-delimited stream.",
596
+ );
597
+ }
509
598
  }
510
599
 
511
600
  /**
@@ -534,6 +623,33 @@ export class PrintableShellCommand {
534
623
  return this.stdout(options).json() as Promise<T>;
535
624
  }
536
625
 
626
+ /**
627
+ * Parse `stdout` into a generator of string values using a NULL delimiter.
628
+ *
629
+ * A trailing NULL delimiter from `stdout` is required and removed.
630
+ */
631
+ public async *text0(
632
+ options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
633
+ ): AsyncGenerator<string> {
634
+ yield* this.#split0(this.#stdoutSpawnGenerator(options));
635
+ }
636
+
637
+ /**
638
+ * Parse `stdout` into a generator of JSON values using a NULL delimiter.
639
+ *
640
+ * A trailing NULL delimiter from `stdout` is required and removed.
641
+ */
642
+ public async *json0(
643
+ options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
644
+ ): // biome-ignore lint/suspicious/noExplicitAny: `any` is the correct type for JSON
645
+ AsyncGenerator<any> {
646
+ for await (const part of this.#split0(
647
+ this.#stdoutSpawnGenerator(options),
648
+ )) {
649
+ yield JSON.parse(part);
650
+ }
651
+ }
652
+
537
653
  /** Equivalent to:
538
654
  *
539
655
  * ```
@@ -657,3 +773,25 @@ export class PrintableShellCommand {
657
773
  shellOutBun: this.#shellOutBun.bind(this),
658
774
  };
659
775
  }
776
+
777
+ export function escapeArg(
778
+ arg: string,
779
+ isMainCommand: boolean,
780
+ options: PrintOptions,
781
+ ): string {
782
+ const argCharacters = new Set(arg);
783
+ const specialShellCharacters = isMainCommand
784
+ ? SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND
785
+ : SPECIAL_SHELL_CHARACTERS;
786
+ if (
787
+ options?.quoting === "extra-safe" ||
788
+ // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)
789
+ (argCharacters as unknown as any).intersection(specialShellCharacters)
790
+ .size > 0
791
+ ) {
792
+ // Use single quote to reduce the need to escape (and therefore reduce the chance for bugs/security issues).
793
+ const escaped = arg.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
794
+ return `'${escaped}'`;
795
+ }
796
+ return arg;
797
+ }
package/src/spawn.ts CHANGED
@@ -21,6 +21,20 @@ export interface WithSuccess {
21
21
  success: Promise<void>;
22
22
  }
23
23
 
24
+ export interface WithResponse {
25
+ response: () => Response;
26
+ text: () => Promise<string>;
27
+ text0: () => AsyncGenerator<string>;
28
+ json: <T>() => Promise<T>;
29
+ }
30
+ export interface WithStdoutResponse {
31
+ stdout: Readable & WithResponse;
32
+ }
33
+
34
+ export interface WithStderrResponse {
35
+ stderr: Readable & WithResponse;
36
+ }
37
+
24
38
  export declare function spawnType(
25
39
  options?: NodeWithCwd<SpawnOptionsWithoutStdio>,
26
40
  ): ChildProcessWithoutNullStreams & WithSuccess;
@@ -28,22 +42,32 @@ export declare function spawnType(
28
42
  options: NodeWithCwd<
29
43
  SpawnOptionsWithStdioTuple<StdioPipe, StdioPipe, StdioPipe>
30
44
  >,
31
- ): ChildProcessByStdio<Writable, Readable, Readable> & WithSuccess;
45
+ ): ChildProcessByStdio<Writable, Readable, Readable> &
46
+ WithSuccess &
47
+ WithStdoutResponse &
48
+ WithStderrResponse;
32
49
  export declare function spawnType(
33
50
  options: NodeWithCwd<
34
51
  SpawnOptionsWithStdioTuple<StdioPipe, StdioPipe, StdioNull>
35
52
  >,
36
- ): ChildProcessByStdio<Writable, Readable, null> & WithSuccess;
53
+ ): ChildProcessByStdio<Writable, Readable, null> &
54
+ WithSuccess &
55
+ WithStdoutResponse;
37
56
  export declare function spawnType(
38
57
  options: NodeWithCwd<
39
58
  SpawnOptionsWithStdioTuple<StdioPipe, StdioNull, StdioPipe>
40
59
  >,
41
- ): ChildProcessByStdio<Writable, null, Readable> & WithSuccess;
60
+ ): ChildProcessByStdio<Writable, null, Readable> &
61
+ WithSuccess &
62
+ WithStderrResponse;
42
63
  export declare function spawnType(
43
64
  options: NodeWithCwd<
44
65
  SpawnOptionsWithStdioTuple<StdioNull, StdioPipe, StdioPipe>
45
66
  >,
46
- ): ChildProcessByStdio<null, Readable, Readable> & WithSuccess;
67
+ ): ChildProcessByStdio<null, Readable, Readable> &
68
+ WithSuccess &
69
+ WithStdoutResponse &
70
+ WithStderrResponse;
47
71
  export declare function spawnType(
48
72
  options: NodeWithCwd<
49
73
  SpawnOptionsWithStdioTuple<StdioPipe, StdioNull, StdioNull>
@@ -53,12 +77,12 @@ export declare function spawnType(
53
77
  options: NodeWithCwd<
54
78
  SpawnOptionsWithStdioTuple<StdioNull, StdioPipe, StdioNull>
55
79
  >,
56
- ): ChildProcessByStdio<null, Readable, null> & WithSuccess;
80
+ ): ChildProcessByStdio<null, Readable, null> & WithSuccess & WithStdoutResponse;
57
81
  export declare function spawnType(
58
82
  options: NodeWithCwd<
59
83
  SpawnOptionsWithStdioTuple<StdioNull, StdioNull, StdioPipe>
60
84
  >,
61
- ): ChildProcessByStdio<null, null, Readable> & WithSuccess;
85
+ ): ChildProcessByStdio<null, null, Readable> & WithSuccess & WithStderrResponse;
62
86
  export declare function spawnType(
63
87
  options: NodeWithCwd<
64
88
  SpawnOptionsWithStdioTuple<StdioNull, StdioNull, StdioNull>