printable-shell-command 1.2.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,8 +50,8 @@ const command = new PrintableShellCommand(/* … */);
50
50
  const child_process = spawn(...command.toCommandWithFlatArgs()); // Note the `...`
51
51
 
52
52
  // or directly
53
- await command.spawnNode().success;
54
- await command.spawnNodeInherit().success;
53
+ await command.spawn().success;
54
+ await command.spawnInherit().success;
55
55
  ```
56
56
 
57
57
  ### Spawn a process in `bun`
@@ -64,8 +64,8 @@ const command = new PrintableShellCommand(/* … */);
64
64
  await spawn(command.toFlatCommand()).exited;
65
65
 
66
66
  // or directly
67
- await command.spawnBun().success;
68
- await command.spawnBunInherit().success;
67
+ await command.bun.spawnBun().success;
68
+ await command.bun.spawnBunInherit().success;
69
69
  ```
70
70
 
71
71
  ## Protections
@@ -1,6 +1,8 @@
1
- import type { ChildProcess as NodeChildProcess, SpawnOptions as NodeSpawnOptions, SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio, SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple, StdioNull as NodeStdioNull, StdioPipe as NodeStdioPipe } from "node:child_process";
1
+ import type { ChildProcess as NodeChildProcess, SpawnOptions as NodeSpawnOptions, SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio, SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple, StdioNull as NodeStdioNull, StdioPipe as NodeStdioPipe, ProcessEnvOptions } from "node:child_process";
2
2
  import { styleText } from "node:util";
3
- import type { SpawnOptions as BunSpawnOptions, Subprocess as BunSubprocess } from "bun";
3
+ import type { SpawnOptions as BunSpawnOptions, Subprocess as BunSubprocess, SpawnOptions } from "bun";
4
+ import { Path } from "path-class";
5
+ import type { SetFieldType } from "type-fest";
4
6
  type SingleArgument = string;
5
7
  type FlagArgumentGroup = string[];
6
8
  type ArgsEntry = SingleArgument | FlagArgumentGroup;
@@ -34,6 +36,14 @@ export interface PrintOptions {
34
36
  * */
35
37
  styleTextFormat?: Parameters<typeof styleText>[0];
36
38
  }
39
+ type NodeCwd = ProcessEnvOptions["cwd"] | Path;
40
+ type NodeWithCwd<T extends {
41
+ cwd?: ProcessEnvOptions["cwd"];
42
+ }> = SetFieldType<T, "cwd", NodeCwd | undefined>;
43
+ type BunCwd = SpawnOptions.OptionsObject<any, any, any>["cwd"] | Path;
44
+ type BunWithCwd<T extends {
45
+ cwd?: SpawnOptions.OptionsObject<any, any, any>["cwd"];
46
+ }> = SetFieldType<T, "cwd", BunCwd | undefined>;
37
47
  export declare class PrintableShellCommand {
38
48
  #private;
39
49
  private args;
@@ -103,51 +113,50 @@ export declare class PrintableShellCommand {
103
113
  /**
104
114
  * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
105
115
  */
106
- spawnNode<Stdin extends NodeStdioNull | NodeStdioPipe, Stdout extends NodeStdioNull | NodeStdioPipe, Stderr extends NodeStdioNull | NodeStdioPipe>(options?: NodeSpawnOptions | NodeSpawnOptionsWithoutStdio | NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>): // TODO: figure out how to return `ChildProcessByStdio<…>` without duplicating fragile boilerplate.
116
+ spawn<Stdin extends NodeStdioNull | NodeStdioPipe, Stdout extends NodeStdioNull | NodeStdioPipe, Stderr extends NodeStdioNull | NodeStdioPipe>(options?: NodeWithCwd<NodeSpawnOptions> | NodeWithCwd<NodeSpawnOptionsWithoutStdio> | NodeWithCwd<NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>>): // TODO: figure out how to return `ChildProcessByStdio<…>` without duplicating fragile boilerplate.
107
117
  NodeChildProcess & {
108
118
  success: Promise<void>;
109
119
  };
110
120
  /** A wrapper for `.spawnNode(…)` that sets stdio to `"inherit"` (common for
111
121
  * invoking commands from scripts whose output and interaction should be
112
122
  * surfaced to the user). */
113
- spawnNodeInherit(options?: Omit<NodeSpawnOptions, "stdio">): NodeChildProcess & {
123
+ spawnInherit(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): NodeChildProcess & {
114
124
  success: Promise<void>;
115
125
  };
116
- spawnNodeStdout(options?: Omit<NodeSpawnOptions, "stdio">): Response;
126
+ stdout(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): Response;
117
127
  /** Equivalent to:
118
128
  *
119
129
  * ```
120
- * await this.print().spawnNodeInherit(…).success;
130
+ * await this.print().spawnInherit(…).success;
121
131
  * ```
122
132
  */
123
- shellOutNode(options?: Omit<NodeSpawnOptions, "stdio">): Promise<void>;
124
- /**
125
- * The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
126
- */
127
- spawnBun<const In extends BunSpawnOptions.Writable = "ignore", const Out extends BunSpawnOptions.Readable = "pipe", const Err extends BunSpawnOptions.Readable = "inherit">(options?: Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, "cmd">): BunSubprocess<In, Out, Err> & {
128
- success: Promise<void>;
133
+ shellOut(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): Promise<void>;
134
+ bun: {
135
+ /** Equivalent to:
136
+ *
137
+ * ```
138
+ * await this.print().bun.spawnBunInherit(…).success;
139
+ * ```
140
+ */
141
+ spawnBun: <const In extends BunSpawnOptions.Writable = "ignore", const Out extends BunSpawnOptions.Readable = "pipe", const Err extends BunSpawnOptions.Readable = "inherit">(options?: BunWithCwd<Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, "cmd">>) => BunSubprocess<In, Out, Err> & {
142
+ success: Promise<void>;
143
+ };
144
+ /**
145
+ * A wrapper for `.spawnBunInherit(…)` that sets stdio to `"inherit"` (common
146
+ * for invoking commands from scripts whose output and interaction should be
147
+ * surfaced to the user).
148
+ */
149
+ spawnBunInherit: (options?: BunWithCwd<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd" | "stdio">>) => BunSubprocess<"inherit", "inherit", "inherit"> & {
150
+ success: Promise<void>;
151
+ };
152
+ /** Equivalent to:
153
+ *
154
+ * ```
155
+ * new Response(this.bun.spawnBun(…).stdout);
156
+ * ```
157
+ */
158
+ spawnBunStdout: (options?: BunWithCwd<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd" | "stdio">>) => Response;
159
+ shellOutBun: (options?: BunWithCwd<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd" | "stdio">>) => Promise<void>;
129
160
  };
130
- /**
131
- * A wrapper for `.spawnBunInherit(…)` that sets stdio to `"inherit"` (common
132
- * for invoking commands from scripts whose output and interaction should be
133
- * surfaced to the user).
134
- */
135
- spawnBunInherit(options?: Omit<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd">, "stdio">): BunSubprocess<"inherit", "inherit", "inherit"> & {
136
- success: Promise<void>;
137
- };
138
- /** Equivalent to:
139
- *
140
- * ```
141
- * new Response(this.spawnBun(…).stdout);
142
- * ```
143
- */
144
- spawnBunStdout(options?: Omit<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd">, "stdio">): Response;
145
- /** Equivalent to:
146
- *
147
- * ```
148
- * await this.print().spawnBunInherit(…).success;
149
- * ```
150
- */
151
- shellOutBun(options?: Omit<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd">, "stdio">): Promise<void>;
152
161
  }
153
162
  export {};
@@ -1,6 +1,7 @@
1
1
  // src/index.ts
2
2
  import { Readable } from "node:stream";
3
3
  import { styleText } from "node:util";
4
+ import { Path } from "path-class";
4
5
  var DEFAULT_MAIN_INDENTATION = "";
5
6
  var DEFAULT_ARG_INDENTATION = " ";
6
7
  var DEFAULT_ARGUMENT_LINE_WRAPPING = "by-entry";
@@ -224,9 +225,20 @@ var PrintableShellCommand = class {
224
225
  /**
225
226
  * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
226
227
  */
227
- spawnNode(options) {
228
+ spawn(options) {
228
229
  const { spawn } = process.getBuiltinModule("node:child_process");
229
- const subprocess = spawn(...this.forNode(), options);
230
+ const cwd = (() => {
231
+ if (typeof options?.cwd !== "undefined") {
232
+ if (options.cwd instanceof Path) {
233
+ return options.cwd.toString();
234
+ }
235
+ }
236
+ return options?.cwd;
237
+ })();
238
+ const subprocess = spawn(...this.forNode(), {
239
+ ...options,
240
+ cwd
241
+ });
230
242
  Object.defineProperty(subprocess, "success", {
231
243
  get() {
232
244
  return new Promise(
@@ -249,17 +261,17 @@ var PrintableShellCommand = class {
249
261
  /** A wrapper for `.spawnNode(…)` that sets stdio to `"inherit"` (common for
250
262
  * invoking commands from scripts whose output and interaction should be
251
263
  * surfaced to the user). */
252
- spawnNodeInherit(options) {
264
+ spawnInherit(options) {
253
265
  if (options && "stdio" in options) {
254
266
  throw new Error("Unexpected `stdio` field.");
255
267
  }
256
- return this.spawnNode({ ...options, stdio: "inherit" });
268
+ return this.spawn({ ...options, stdio: "inherit" });
257
269
  }
258
- spawnNodeStdout(options) {
270
+ stdout(options) {
259
271
  if (options && "stdio" in options) {
260
272
  throw new Error("Unexpected `stdio` field.");
261
273
  }
262
- const subprocess = this.spawnNode({
274
+ const subprocess = this.spawn({
263
275
  ...options,
264
276
  stdio: ["ignore", "pipe", "inherit"]
265
277
  });
@@ -268,22 +280,31 @@ var PrintableShellCommand = class {
268
280
  /** Equivalent to:
269
281
  *
270
282
  * ```
271
- * await this.print().spawnNodeInherit(…).success;
283
+ * await this.print().spawnInherit(…).success;
272
284
  * ```
273
285
  */
274
- async shellOutNode(options) {
275
- await this.print().spawnNodeInherit(options).success;
286
+ async shellOut(options) {
287
+ await this.print().spawnInherit(options).success;
276
288
  }
277
289
  /**
278
290
  * The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
279
291
  */
280
- spawnBun(options) {
292
+ #spawnBun(options) {
281
293
  if (options && "cmd" in options) {
282
294
  throw new Error("Unexpected `cmd` field.");
283
295
  }
284
296
  const { spawn } = process.getBuiltinModule("bun");
297
+ const cwd = (() => {
298
+ if (typeof options?.cwd !== "undefined") {
299
+ if (options.cwd instanceof Path) {
300
+ return options.cwd.toString();
301
+ }
302
+ }
303
+ return options?.cwd;
304
+ })();
285
305
  const subprocess = spawn({
286
306
  ...options,
307
+ cwd,
287
308
  cmd: this.forBun()
288
309
  });
289
310
  Object.defineProperty(subprocess, "success", {
@@ -306,39 +327,46 @@ var PrintableShellCommand = class {
306
327
  });
307
328
  return subprocess;
308
329
  }
309
- /**
310
- * A wrapper for `.spawnBunInherit(…)` that sets stdio to `"inherit"` (common
311
- * for invoking commands from scripts whose output and interaction should be
312
- * surfaced to the user).
313
- */
314
- spawnBunInherit(options) {
330
+ #spawnBunInherit(options) {
315
331
  if (options && "stdio" in options) {
316
332
  throw new Error("Unexpected `stdio` field.");
317
333
  }
318
- return this.spawnBun({
334
+ return this.bun.spawnBun({
319
335
  ...options,
320
336
  stdio: ["inherit", "inherit", "inherit"]
321
337
  });
322
338
  }
323
- /** Equivalent to:
324
- *
325
- * ```
326
- * new Response(this.spawnBun(…).stdout);
327
- * ```
328
- */
329
- spawnBunStdout(options) {
330
- return new Response(this.spawnBun(options).stdout);
339
+ #spawnBunStdout(options) {
340
+ return new Response(this.bun.spawnBun(options).stdout);
331
341
  }
332
- /** Equivalent to:
333
- *
334
- * ```
335
- * await this.print().spawnBunInherit(…).success;
336
- * ```
337
- */
338
- async shellOutBun(options) {
339
- await this.print().spawnBunInherit(options).success;
342
+ async #shellOutBun(options) {
343
+ await this.print().bun.spawnBunInherit(options).success;
340
344
  }
345
+ bun = {
346
+ /** Equivalent to:
347
+ *
348
+ * ```
349
+ * await this.print().bun.spawnBunInherit(…).success;
350
+ * ```
351
+ */
352
+ spawnBun: this.#spawnBun.bind(this),
353
+ /**
354
+ * A wrapper for `.spawnBunInherit(…)` that sets stdio to `"inherit"` (common
355
+ * for invoking commands from scripts whose output and interaction should be
356
+ * surfaced to the user).
357
+ */
358
+ spawnBunInherit: this.#spawnBunInherit.bind(this),
359
+ /** Equivalent to:
360
+ *
361
+ * ```
362
+ * new Response(this.bun.spawnBun(…).stdout);
363
+ * ```
364
+ */
365
+ spawnBunStdout: this.#spawnBunStdout.bind(this),
366
+ shellOutBun: this.#shellOutBun.bind(this)
367
+ };
341
368
  };
369
+ await new PrintableShellCommand("ls").shellOut({ cwd: Path.homedir });
342
370
  export {
343
371
  PrintableShellCommand
344
372
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/index.ts"],
4
- "sourcesContent": ["import type {\n ChildProcess as NodeChildProcess,\n SpawnOptions as NodeSpawnOptions,\n SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio,\n SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple,\n StdioNull as NodeStdioNull,\n StdioPipe as NodeStdioPipe,\n} from \"node:child_process\";\nimport { Readable } from \"node:stream\";\nimport { styleText } from \"node:util\";\nimport type {\n SpawnOptions as BunSpawnOptions,\n Subprocess as BunSubprocess,\n} from \"bun\";\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\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// biome-ignore lint/suspicious/noExplicitAny: This is the correct type nere.\nfunction isStringArray(entries: any[]): entries is string[] {\n for (const entry of entries) {\n if (!isString(entry)) {\n return false;\n }\n }\n return true;\n}\n\n// TODO: allow `.toString()`ables?\ntype SingleArgument = string;\ntype FlagArgumentGroup = string[];\ntype ArgsEntry = SingleArgument | FlagArgumentGroup;\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 [`styleText(\u2026)`](https://nodejs.org/api/util.html#utilstyletextformat-text-options)\n *\n * Example usage:\n *\n * ```\n * new PrintableShellCommand(\"echo\", [\"hi\"]).print({\n * styleTextFormat: [\"gray\", \"bold\"],\n * });\n * */\n styleTextFormat?: Parameters<typeof styleText>[0];\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\nexport class PrintableShellCommand {\n #commandName: string;\n constructor(\n commandName: string,\n private args: Args = [],\n ) {\n if (!isString(commandName)) {\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 (Array.isArray(argEntry) && isStringArray(argEntry)) {\n continue;\n }\n throw new Error(`Invalid arg entry at index: ${i}`);\n }\n }\n\n get commandName(): string {\n return 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()];\n }\n\n /**\n * Convenient alias for `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()];\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 `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 = 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) => this.#escapeArg(part, false, options))\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 public print(options?: PrintOptions): PrintableShellCommand {\n console.log(this.getPrintableCommand(options));\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 spawnNode<\n Stdin extends NodeStdioNull | NodeStdioPipe,\n Stdout extends NodeStdioNull | NodeStdioPipe,\n Stderr extends NodeStdioNull | NodeStdioPipe,\n >(\n options?:\n | NodeSpawnOptions\n | NodeSpawnOptionsWithoutStdio\n | NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>,\n ): // TODO: figure out how to return `ChildProcessByStdio<\u2026>` without duplicating fragile boilerplate.\n NodeChildProcess & { success: Promise<void> } {\n const { spawn } = process.getBuiltinModule(\"node:child_process\");\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(), options) 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 }\n\n /** A wrapper for `.spawnNode(\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 public spawnNodeInherit(\n options?: Omit<NodeSpawnOptions, \"stdio\">,\n ): NodeChildProcess & { success: Promise<void> } {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n return this.spawnNode({ ...options, stdio: \"inherit\" });\n }\n\n public spawnNodeStdout(options?: Omit<NodeSpawnOptions, \"stdio\">): Response {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n const subprocess = this.spawnNode({\n ...options,\n stdio: [\"ignore\", \"pipe\", \"inherit\"],\n });\n\n // biome-ignore lint/style/noNonNullAssertion: dude\n return new Response(Readable.toWeb(subprocess.stdout!));\n }\n\n /** Equivalent to:\n *\n * ```\n * await this.print().spawnNodeInherit(\u2026).success;\n * ```\n */\n public async shellOutNode(\n options?: Omit<NodeSpawnOptions, \"stdio\">,\n ): Promise<void> {\n await this.print().spawnNodeInherit(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 public 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?: Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, \"cmd\">,\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 subprocess = spawn({\n ...options,\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 /**\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 public spawnBunInherit(\n options?: Omit<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\"\n >,\n \"stdio\"\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.spawnBun({\n ...options,\n stdio: [\"inherit\", \"inherit\", \"inherit\"],\n });\n }\n\n /** Equivalent to:\n *\n * ```\n * new Response(this.spawnBun(\u2026).stdout);\n * ```\n */\n public spawnBunStdout(\n options?: Omit<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\"\n >,\n \"stdio\"\n >,\n ): Response {\n // biome-ignore lint/suspicious/noExplicitAny: Avoid breaking the lib check when used without `@types/bun`.\n return new Response((this.spawnBun(options) as any).stdout);\n }\n\n /** Equivalent to:\n *\n * ```\n * await this.print().spawnBunInherit(\u2026).success;\n * ```\n */\n public async shellOutBun(\n options?: Omit<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\"\n >,\n \"stdio\"\n >,\n ): Promise<void> {\n await this.print().spawnBunInherit(options).success;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n ChildProcess as NodeChildProcess,\n SpawnOptions as NodeSpawnOptions,\n SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio,\n SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple,\n StdioNull as NodeStdioNull,\n StdioPipe as NodeStdioPipe,\n ProcessEnvOptions,\n} from \"node:child_process\";\nimport { Readable } from \"node:stream\";\nimport { styleText } from \"node:util\";\nimport type {\n SpawnOptions as BunSpawnOptions,\n Subprocess as BunSubprocess,\n SpawnOptions,\n} from \"bun\";\nimport { Path } from \"path-class\";\nimport type { SetFieldType } from \"type-fest\";\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\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// biome-ignore lint/suspicious/noExplicitAny: This is the correct type nere.\nfunction isStringArray(entries: any[]): entries is string[] {\n for (const entry of entries) {\n if (!isString(entry)) {\n return false;\n }\n }\n return true;\n}\n\n// TODO: allow `.toString()`ables?\ntype SingleArgument = string;\ntype FlagArgumentGroup = string[];\ntype ArgsEntry = SingleArgument | FlagArgumentGroup;\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 [`styleText(\u2026)`](https://nodejs.org/api/util.html#utilstyletextformat-text-options)\n *\n * Example usage:\n *\n * ```\n * new PrintableShellCommand(\"echo\", [\"hi\"]).print({\n * styleTextFormat: [\"gray\", \"bold\"],\n * });\n * */\n styleTextFormat?: Parameters<typeof styleText>[0];\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\ntype NodeCwd = ProcessEnvOptions[\"cwd\"] | Path;\ntype NodeWithCwd<T extends { cwd?: ProcessEnvOptions[\"cwd\"] }> = SetFieldType<\n T,\n \"cwd\",\n NodeCwd | undefined\n>;\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\"] },\n> = SetFieldType<T, \"cwd\", BunCwd | undefined>;\n\nexport class PrintableShellCommand {\n #commandName: string;\n constructor(\n commandName: string,\n private args: Args = [],\n ) {\n if (!isString(commandName)) {\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 (Array.isArray(argEntry) && isStringArray(argEntry)) {\n continue;\n }\n throw new Error(`Invalid arg entry at index: ${i}`);\n }\n }\n\n get commandName(): string {\n return 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()];\n }\n\n /**\n * Convenient alias for `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()];\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 `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 = 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) => this.#escapeArg(part, false, options))\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 public print(options?: PrintOptions): PrintableShellCommand {\n console.log(this.getPrintableCommand(options));\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<\n Stdin extends NodeStdioNull | NodeStdioPipe,\n Stdout extends NodeStdioNull | NodeStdioPipe,\n Stderr extends NodeStdioNull | NodeStdioPipe,\n >(\n options?:\n | NodeWithCwd<NodeSpawnOptions>\n | NodeWithCwd<NodeSpawnOptionsWithoutStdio>\n | NodeWithCwd<NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>>,\n ): // TODO: figure out how to return `ChildProcessByStdio<\u2026>` without duplicating fragile boilerplate.\n NodeChildProcess & { success: Promise<void> } {\n const { spawn } = process.getBuiltinModule(\"node:child_process\");\n const cwd = (() => {\n if (typeof options?.cwd !== \"undefined\") {\n if (options.cwd instanceof Path) {\n return options.cwd.toString();\n }\n }\n return options?.cwd;\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,\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 }\n\n /** A wrapper for `.spawnNode(\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 public spawnInherit(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): NodeChildProcess & { success: Promise<void> } {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n return this.spawn({ ...options, stdio: \"inherit\" });\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 // biome-ignore lint/style/noNonNullAssertion: dude\n return new Response(Readable.toWeb(subprocess.stdout!));\n }\n\n /** Equivalent to:\n *\n * ```\n * await this.print().spawnInherit(\u2026).success;\n * ```\n */\n public async shellOut(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<void> {\n await this.print().spawnInherit(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 = (() => {\n if (typeof options?.cwd !== \"undefined\") {\n if (options.cwd instanceof Path) {\n return options.cwd.toString();\n }\n }\n return options?.cwd;\n })();\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\nawait new PrintableShellCommand(\"ls\").shellOut({ cwd: Path.homedir });\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "printable-shell-command",
3
- "version": "1.2.1",
3
+ "version": "2.1.0",
4
4
  "main": "./src/index.ts",
5
5
  "devDependencies": {
6
6
  "@biomejs/biome": "^2.1.4",
@@ -27,5 +27,9 @@
27
27
  "type": "module",
28
28
  "scripts": {
29
29
  "prepublishOnly": "make prepublishOnly"
30
+ },
31
+ "dependencies": {
32
+ "path-class": "^0.3.1",
33
+ "type-fest": "^5.1.0"
30
34
  }
31
35
  }
package/src/index.ts CHANGED
@@ -5,13 +5,17 @@ import type {
5
5
  SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple,
6
6
  StdioNull as NodeStdioNull,
7
7
  StdioPipe as NodeStdioPipe,
8
+ ProcessEnvOptions,
8
9
  } from "node:child_process";
9
10
  import { Readable } from "node:stream";
10
11
  import { styleText } from "node:util";
11
12
  import type {
12
13
  SpawnOptions as BunSpawnOptions,
13
14
  Subprocess as BunSubprocess,
15
+ SpawnOptions,
14
16
  } from "bun";
17
+ import { Path } from "path-class";
18
+ import type { SetFieldType } from "type-fest";
15
19
 
16
20
  const DEFAULT_MAIN_INDENTATION = "";
17
21
  const DEFAULT_ARG_INDENTATION = " ";
@@ -103,6 +107,20 @@ const SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND =
103
107
  // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)
104
108
  (SPECIAL_SHELL_CHARACTERS as unknown as any).union(new Set(["="]));
105
109
 
110
+ type NodeCwd = ProcessEnvOptions["cwd"] | Path;
111
+ type NodeWithCwd<T extends { cwd?: ProcessEnvOptions["cwd"] }> = SetFieldType<
112
+ T,
113
+ "cwd",
114
+ NodeCwd | undefined
115
+ >;
116
+
117
+ // biome-ignore lint/suspicious/noExplicitAny: Just matching
118
+ type BunCwd = SpawnOptions.OptionsObject<any, any, any>["cwd"] | Path;
119
+ type BunWithCwd<
120
+ // biome-ignore lint/suspicious/noExplicitAny: Just matching
121
+ T extends { cwd?: SpawnOptions.OptionsObject<any, any, any>["cwd"] },
122
+ > = SetFieldType<T, "cwd", BunCwd | undefined>;
123
+
106
124
  export class PrintableShellCommand {
107
125
  #commandName: string;
108
126
  constructor(
@@ -329,20 +347,32 @@ export class PrintableShellCommand {
329
347
  /**
330
348
  * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
331
349
  */
332
- public spawnNode<
350
+ public spawn<
333
351
  Stdin extends NodeStdioNull | NodeStdioPipe,
334
352
  Stdout extends NodeStdioNull | NodeStdioPipe,
335
353
  Stderr extends NodeStdioNull | NodeStdioPipe,
336
354
  >(
337
355
  options?:
338
- | NodeSpawnOptions
339
- | NodeSpawnOptionsWithoutStdio
340
- | NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>,
356
+ | NodeWithCwd<NodeSpawnOptions>
357
+ | NodeWithCwd<NodeSpawnOptionsWithoutStdio>
358
+ | NodeWithCwd<NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>>,
341
359
  ): // TODO: figure out how to return `ChildProcessByStdio<…>` without duplicating fragile boilerplate.
342
360
  NodeChildProcess & { success: Promise<void> } {
343
361
  const { spawn } = process.getBuiltinModule("node:child_process");
362
+ const cwd = (() => {
363
+ if (typeof options?.cwd !== "undefined") {
364
+ if (options.cwd instanceof Path) {
365
+ return options.cwd.toString();
366
+ }
367
+ }
368
+ return options?.cwd;
369
+ })();
370
+ // biome-ignore lint/suspicious/noTsIgnore: We don't want linting to depend on *broken* type checking.
344
371
  // @ts-ignore: The TypeScript checker has trouble reconciling the optional (i.e. potentially `undefined`) `options` with the third argument.
345
- const subprocess = spawn(...this.forNode(), options) as NodeChildProcess & {
372
+ const subprocess = spawn(...this.forNode(), {
373
+ ...options,
374
+ cwd,
375
+ }) as NodeChildProcess & {
346
376
  success: Promise<void>;
347
377
  };
348
378
  Object.defineProperty(subprocess, "success", {
@@ -368,20 +398,22 @@ export class PrintableShellCommand {
368
398
  /** A wrapper for `.spawnNode(…)` that sets stdio to `"inherit"` (common for
369
399
  * invoking commands from scripts whose output and interaction should be
370
400
  * surfaced to the user). */
371
- public spawnNodeInherit(
372
- options?: Omit<NodeSpawnOptions, "stdio">,
401
+ public spawnInherit(
402
+ options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
373
403
  ): NodeChildProcess & { success: Promise<void> } {
374
404
  if (options && "stdio" in options) {
375
405
  throw new Error("Unexpected `stdio` field.");
376
406
  }
377
- return this.spawnNode({ ...options, stdio: "inherit" });
407
+ return this.spawn({ ...options, stdio: "inherit" });
378
408
  }
379
409
 
380
- public spawnNodeStdout(options?: Omit<NodeSpawnOptions, "stdio">): Response {
410
+ public stdout(
411
+ options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
412
+ ): Response {
381
413
  if (options && "stdio" in options) {
382
414
  throw new Error("Unexpected `stdio` field.");
383
415
  }
384
- const subprocess = this.spawnNode({
416
+ const subprocess = this.spawn({
385
417
  ...options,
386
418
  stdio: ["ignore", "pipe", "inherit"],
387
419
  });
@@ -393,31 +425,42 @@ export class PrintableShellCommand {
393
425
  /** Equivalent to:
394
426
  *
395
427
  * ```
396
- * await this.print().spawnNodeInherit(…).success;
428
+ * await this.print().spawnInherit(…).success;
397
429
  * ```
398
430
  */
399
- public async shellOutNode(
400
- options?: Omit<NodeSpawnOptions, "stdio">,
431
+ public async shellOut(
432
+ options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
401
433
  ): Promise<void> {
402
- await this.print().spawnNodeInherit(options).success;
434
+ await this.print().spawnInherit(options).success;
403
435
  }
404
436
 
405
437
  /**
406
438
  * The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
407
439
  */
408
- public spawnBun<
440
+ #spawnBun<
409
441
  const In extends BunSpawnOptions.Writable = "ignore",
410
442
  const Out extends BunSpawnOptions.Readable = "pipe",
411
443
  const Err extends BunSpawnOptions.Readable = "inherit",
412
444
  >(
413
- options?: Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, "cmd">,
445
+ options?: BunWithCwd<
446
+ Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, "cmd">
447
+ >,
414
448
  ): BunSubprocess<In, Out, Err> & { success: Promise<void> } {
415
449
  if (options && "cmd" in options) {
416
450
  throw new Error("Unexpected `cmd` field.");
417
451
  }
418
452
  const { spawn } = process.getBuiltinModule("bun") as typeof import("bun");
453
+ const cwd = (() => {
454
+ if (typeof options?.cwd !== "undefined") {
455
+ if (options.cwd instanceof Path) {
456
+ return options.cwd.toString();
457
+ }
458
+ }
459
+ return options?.cwd;
460
+ })();
419
461
  const subprocess = spawn({
420
462
  ...options,
463
+ cwd,
421
464
  cmd: this.forBun(),
422
465
  }) as BunSubprocess<In, Out, Err> & { success: Promise<void> };
423
466
  Object.defineProperty(subprocess, "success", {
@@ -443,18 +486,12 @@ export class PrintableShellCommand {
443
486
  return subprocess;
444
487
  }
445
488
 
446
- /**
447
- * A wrapper for `.spawnBunInherit(…)` that sets stdio to `"inherit"` (common
448
- * for invoking commands from scripts whose output and interaction should be
449
- * surfaced to the user).
450
- */
451
- public spawnBunInherit(
452
- options?: Omit<
489
+ #spawnBunInherit(
490
+ options?: BunWithCwd<
453
491
  Omit<
454
492
  BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
455
- "cmd"
456
- >,
457
- "stdio"
493
+ "cmd" | "stdio"
494
+ >
458
495
  >,
459
496
  ): BunSubprocess<"inherit", "inherit", "inherit"> & {
460
497
  success: Promise<void>;
@@ -462,46 +499,58 @@ export class PrintableShellCommand {
462
499
  if (options && "stdio" in options) {
463
500
  throw new Error("Unexpected `stdio` field.");
464
501
  }
465
- return this.spawnBun({
502
+ return this.bun.spawnBun({
466
503
  ...options,
467
504
  stdio: ["inherit", "inherit", "inherit"],
468
505
  });
469
506
  }
470
507
 
471
- /** Equivalent to:
472
- *
473
- * ```
474
- * new Response(this.spawnBun(…).stdout);
475
- * ```
476
- */
477
- public spawnBunStdout(
478
- options?: Omit<
508
+ #spawnBunStdout(
509
+ options?: BunWithCwd<
479
510
  Omit<
480
511
  BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
481
- "cmd"
482
- >,
483
- "stdio"
512
+ "cmd" | "stdio"
513
+ >
484
514
  >,
485
515
  ): Response {
486
516
  // biome-ignore lint/suspicious/noExplicitAny: Avoid breaking the lib check when used without `@types/bun`.
487
- return new Response((this.spawnBun(options) as any).stdout);
517
+ return new Response((this.bun.spawnBun(options) as any).stdout);
488
518
  }
489
519
 
490
- /** Equivalent to:
491
- *
492
- * ```
493
- * await this.print().spawnBunInherit(…).success;
494
- * ```
495
- */
496
- public async shellOutBun(
497
- options?: Omit<
520
+ async #shellOutBun(
521
+ options?: BunWithCwd<
498
522
  Omit<
499
523
  BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
500
- "cmd"
501
- >,
502
- "stdio"
524
+ "cmd" | "stdio"
525
+ >
503
526
  >,
504
527
  ): Promise<void> {
505
- await this.print().spawnBunInherit(options).success;
528
+ await this.print().bun.spawnBunInherit(options).success;
506
529
  }
530
+
531
+ bun = {
532
+ /** Equivalent to:
533
+ *
534
+ * ```
535
+ * await this.print().bun.spawnBunInherit(…).success;
536
+ * ```
537
+ */
538
+ spawnBun: this.#spawnBun.bind(this),
539
+ /**
540
+ * A wrapper for `.spawnBunInherit(…)` that sets stdio to `"inherit"` (common
541
+ * for invoking commands from scripts whose output and interaction should be
542
+ * surfaced to the user).
543
+ */
544
+ spawnBunInherit: this.#spawnBunInherit.bind(this),
545
+ /** Equivalent to:
546
+ *
547
+ * ```
548
+ * new Response(this.bun.spawnBun(…).stdout);
549
+ * ```
550
+ */
551
+ spawnBunStdout: this.#spawnBunStdout.bind(this),
552
+ shellOutBun: this.#shellOutBun.bind(this),
553
+ };
507
554
  }
555
+
556
+ await new PrintableShellCommand("ls").shellOut({ cwd: Path.homedir });