printable-shell-command 0.2.0 → 0.3.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
@@ -47,7 +47,11 @@ import { PrintableShellCommand } from "printable-shell-command";
47
47
  import { spawn } from "node:child_process";
48
48
 
49
49
  const command = new PrintableShellCommand(/* … */);
50
- const child_process = spawn(...command.toCommandWithFlatArgs()); // Note the `...`
50
+ const child_process = spawn(...command.toCommandWithFlatAr()); // Note the `...`
51
+
52
+ // or directly
53
+ await command.spawnNode().success;
54
+ await command.spawnNodeInherit().success;
51
55
  ```
52
56
 
53
57
  ### Spawn a process in `bun`
@@ -58,6 +62,10 @@ import { spawn } from "bun";
58
62
 
59
63
  const command = new PrintableShellCommand(/* … */);
60
64
  await spawn(command.toFlatCommand()).exited;
65
+
66
+ // or directly
67
+ await command.spawnBun().success;
68
+ await command.spawnBunInherit().success;
61
69
  ```
62
70
 
63
71
  ## Protections
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "printable-shell-command",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "main": "./src/index.ts",
5
5
  "type": "module",
6
6
  "exports": {
@@ -9,11 +9,13 @@
9
9
  "import": "./src/index.ts"
10
10
  }
11
11
  },
12
- "dependencies": {
12
+ "devDependencies": {
13
+ "@biomejs/biome": "^1.9.4",
13
14
  "@types/bun": "^1.2.11",
14
15
  "@types/node": "^22.15.3"
15
16
  },
16
- "devDependencies": {
17
- "@biomejs/biome": "^1.9.4"
17
+ "optionalDependencies": {
18
+ "@types/bun": "^1.2.11",
19
+ "@types/node": "^22.15.3"
18
20
  }
19
21
  }
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type {
2
2
  ChildProcess as NodeChildProcess,
3
+ SpawnOptions as NodeSpawnOptions,
3
4
  SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple,
5
+ SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio,
4
6
  StdioNull as NodeStdioNull,
5
7
  StdioPipe as NodeStdioPipe,
6
- SpawnOptions as NodeSpawnOptions,
7
- SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio,
8
8
  } from "node:child_process";
9
9
  import type {
10
10
  SpawnOptions as BunSpawnOptions,
@@ -273,8 +273,9 @@ export class PrintableShellCommand {
273
273
  return serializedEntries.join(this.#entrySeparator(options));
274
274
  }
275
275
 
276
- public print(options?: PrintOptions): void {
276
+ public print(options?: PrintOptions): PrintableShellCommand {
277
277
  console.log(this.getPrintableCommand(options));
278
+ return this;
278
279
  }
279
280
 
280
281
  public spawnNode<
@@ -282,12 +283,58 @@ export class PrintableShellCommand {
282
283
  Stdout extends NodeStdioNull | NodeStdioPipe,
283
284
  Stderr extends NodeStdioNull | NodeStdioPipe,
284
285
  >(
285
- options: NodeSpawnOptions
286
- | NodeSpawnOptionsWithoutStdio | NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>,
286
+ options?:
287
+ | NodeSpawnOptions
288
+ | NodeSpawnOptionsWithoutStdio
289
+ | NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>,
287
290
  ): // TODO: figure out how to return `ChildProcessByStdio<…>` without duplicating fragile boilerplate.
288
- NodeChildProcess {
291
+ NodeChildProcess & { success: Promise<void> } {
289
292
  const { spawn } = process.getBuiltinModule("node:child_process");
290
- return spawn(...this.forNode(), options);
293
+ const subprocess = spawn(...this.forNode(), options) as NodeChildProcess & {
294
+ success: Promise<void>;
295
+ };
296
+ Object.defineProperty(subprocess, "success", {
297
+ get() {
298
+ return new Promise<void>((resolve, reject) =>
299
+ this.addListener(
300
+ "exit",
301
+ (exitCode: number /* we only use the first arg */) => {
302
+ if (exitCode === 0) {
303
+ resolve();
304
+ } else {
305
+ reject(`Command failed with non-zero exit code: ${exitCode}`);
306
+ }
307
+ },
308
+ ),
309
+ );
310
+ },
311
+ enumerable: false,
312
+ });
313
+ return subprocess;
314
+ }
315
+
316
+ // A wrapper for `.spawnNode(…)` that sets stdio to `"inherit"` (common for
317
+ // invoking commands from scripts whose output and interaction should be
318
+ // surfaced to the user).
319
+ public spawnNodeInherit(
320
+ options?: Omit<NodeSpawnOptions, "stdio">,
321
+ ): NodeChildProcess & { success: Promise<void> } {
322
+ if (options && "stdio" in options) {
323
+ throw new Error("Unexpected `stdio` field.");
324
+ }
325
+ return this.spawnNode({ ...options, stdio: "inherit" });
326
+ }
327
+
328
+ /** Equivalent to:
329
+ *
330
+ * ```
331
+ * await this.print().spawnNodeInherit().success;
332
+ * ```
333
+ */
334
+ public async shellOutNode(
335
+ options?: Omit<NodeSpawnOptions, "stdio">,
336
+ ): Promise<void> {
337
+ await this.print().spawnNodeInherit(options).success;
291
338
  }
292
339
 
293
340
  // The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
@@ -298,6 +345,9 @@ export class PrintableShellCommand {
298
345
  >(
299
346
  options?: Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, "cmd">,
300
347
  ): BunSubprocess<In, Out, Err> & { success: Promise<void> } {
348
+ if (options && "cmd" in options) {
349
+ throw new Error("Unexpected `cmd` field.");
350
+ }
301
351
  const { spawn } = process.getBuiltinModule("bun") as typeof import("bun");
302
352
  const subprocess = spawn({
303
353
  ...options,
@@ -311,7 +361,11 @@ export class PrintableShellCommand {
311
361
  if (exitCode === 0) {
312
362
  resolve();
313
363
  } else {
314
- reject("Command failed.");
364
+ reject(
365
+ new Error(
366
+ `Command failed with non-zero exit code: ${exitCode}`,
367
+ ),
368
+ );
315
369
  }
316
370
  })
317
371
  .catch(reject),
@@ -321,4 +375,45 @@ export class PrintableShellCommand {
321
375
  });
322
376
  return subprocess;
323
377
  }
378
+
379
+ // A wrapper for `.spawnBunInherit(…)` that sets stdio to `"inherit"` (common for
380
+ // invoking commands from scripts whose output and interaction should be
381
+ // surfaced to the user).
382
+ public spawnBunInherit(
383
+ options?: Omit<
384
+ Omit<
385
+ BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
386
+ "cmd"
387
+ >,
388
+ "stdio"
389
+ >,
390
+ ): BunSubprocess<"inherit", "inherit", "inherit"> & {
391
+ success: Promise<void>;
392
+ } {
393
+ if (options && "stdio" in options) {
394
+ throw new Error("Unexpected `stdio` field.");
395
+ }
396
+ return this.spawnBun({
397
+ ...options,
398
+ stdio: ["inherit", "inherit", "inherit"],
399
+ });
400
+ }
401
+
402
+ /** Equivalent to:
403
+ *
404
+ * ```
405
+ * await this.print().spawnBunInherit().success;
406
+ * ```
407
+ */
408
+ public async shellOutBun(
409
+ options?: Omit<
410
+ Omit<
411
+ BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
412
+ "cmd"
413
+ >,
414
+ "stdio"
415
+ >,
416
+ ): Promise<void> {
417
+ await this.print().spawnBunInherit(options).success;
418
+ }
324
419
  }
@@ -1,4 +1,3 @@
1
- import { spawn } from "bun";
2
1
  import { PrintableShellCommand } from "../src";
3
2
 
4
3
  const command = new PrintableShellCommand("ffmpeg", [
@@ -8,5 +7,4 @@ const command = new PrintableShellCommand("ffmpeg", [
8
7
  "./test/My video (slow-mo).mov",
9
8
  ]);
10
9
 
11
- command.print();
12
- await spawn(command.toFlatCommand()).exited;
10
+ await command.shellOutBun();
@@ -1,4 +1,3 @@
1
- import { spawn } from "node:child_process";
2
1
  import { PrintableShellCommand } from "../src";
3
2
 
4
3
  const command = new PrintableShellCommand("ffmpeg", [
@@ -8,7 +7,4 @@ const command = new PrintableShellCommand("ffmpeg", [
8
7
  "./test/My video (slow-mo).mov",
9
8
  ]);
10
9
 
11
- command.print();
12
- await new Promise((resolve, reject) => {
13
- spawn(...command.toCommandWithFlatArgs()).addListener("exit", resolve);
14
- });
10
+ command.print().shellOutNode();