printable-shell-command 1.1.4 → 1.2.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.
@@ -2,8 +2,8 @@ import type { ChildProcess as NodeChildProcess, SpawnOptions as NodeSpawnOptions
2
2
  import { styleText } from "node:util";
3
3
  import type { SpawnOptions as BunSpawnOptions, Subprocess as BunSubprocess } from "bun";
4
4
  type SingleArgument = string;
5
- type FlagArgumentPair = [string, string];
6
- type ArgsEntry = SingleArgument | FlagArgumentPair;
5
+ type FlagArgumentGroup = string[];
6
+ type ArgsEntry = SingleArgument | FlagArgumentGroup;
7
7
  type Args = ArgsEntry[];
8
8
  export interface PrintOptions {
9
9
  /** Defaults to "" */
@@ -20,6 +20,8 @@ export interface PrintOptions {
20
20
  quoting?: "auto" | "extra-safe";
21
21
  /** Line wrapping to use between arguments. Defaults to `"by-entry"`. */
22
22
  argumentLineWrapping?: "by-entry" | "nested-by-entry" | "by-argument" | "inline";
23
+ /** Include the first arg (or first arg group) on the same line as the command, regardless of the `argumentLineWrapping` setting. */
24
+ skipLineWrapBeforeFirstArg?: true | false;
23
25
  /**
24
26
  * Style text using `node`'s [`styleText(…)`](https://nodejs.org/api/util.html#utilstyletextformat-text-options)
25
27
  *
@@ -114,7 +116,7 @@ export declare class PrintableShellCommand {
114
116
  /** Equivalent to:
115
117
  *
116
118
  * ```
117
- * await this.print().spawnNodeInherit().success;
119
+ * await this.print().spawnNodeInherit().success;
118
120
  * ```
119
121
  */
120
122
  shellOutNode(options?: Omit<NodeSpawnOptions, "stdio">): Promise<void>;
@@ -135,14 +137,14 @@ export declare class PrintableShellCommand {
135
137
  /** Equivalent to:
136
138
  *
137
139
  * ```
138
- * new Response(this.spawnBun(options).stdout);
140
+ * new Response(this.spawnBun().stdout);
139
141
  * ```
140
142
  */
141
143
  spawnBunStdout(options?: Omit<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd">, "stdio">): Response;
142
144
  /** Equivalent to:
143
145
  *
144
146
  * ```
145
- * await this.print().spawnBunInherit().success;
147
+ * await this.print().spawnBunInherit().success;
146
148
  * ```
147
149
  */
148
150
  shellOutBun(options?: Omit<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd">, "stdio">): Promise<void>;
@@ -8,6 +8,14 @@ var LINE_WRAP_LINE_END = " \\\n";
8
8
  function isString(s) {
9
9
  return typeof s === "string";
10
10
  }
11
+ function isStringArray(entries) {
12
+ for (const entry of entries) {
13
+ if (!isString(entry)) {
14
+ return false;
15
+ }
16
+ }
17
+ return true;
18
+ }
11
19
  var SPECIAL_SHELL_CHARACTERS = /* @__PURE__ */ new Set([
12
20
  " ",
13
21
  '"',
@@ -52,7 +60,7 @@ var PrintableShellCommand = class {
52
60
  if (typeof argEntry === "string") {
53
61
  continue;
54
62
  }
55
- if (Array.isArray(argEntry) && argEntry.length === 2 && isString(argEntry[0]) && isString(argEntry[1])) {
63
+ if (Array.isArray(argEntry) && isStringArray(argEntry)) {
56
64
  continue;
57
65
  }
58
66
  throw new Error(`Invalid arg entry at index: ${i}`);
@@ -166,14 +174,10 @@ var PrintableShellCommand = class {
166
174
  throw new Error("Invalid argument line wrapping argument.");
167
175
  }
168
176
  }
169
- #entrySeparator(options) {
177
+ #intraEntrySeparator(options) {
170
178
  switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {
171
- case "by-entry": {
172
- return LINE_WRAP_LINE_END + this.#argIndentation(options);
173
- }
174
- case "nested-by-entry": {
175
- return LINE_WRAP_LINE_END + this.#argIndentation(options);
176
- }
179
+ case "by-entry":
180
+ case "nested-by-entry":
177
181
  case "by-argument": {
178
182
  return LINE_WRAP_LINE_END + this.#argIndentation(options);
179
183
  }
@@ -184,24 +188,29 @@ var PrintableShellCommand = class {
184
188
  throw new Error("Invalid argument line wrapping argument.");
185
189
  }
186
190
  }
191
+ #separatorAfterCommand(options, numFollowingEntries) {
192
+ if (numFollowingEntries === 0) {
193
+ return "";
194
+ }
195
+ if (options.skipLineWrapBeforeFirstArg ?? false) {
196
+ return " ";
197
+ }
198
+ return this.#intraEntrySeparator(options);
199
+ }
187
200
  getPrintableCommand(options) {
188
201
  options ??= {};
189
202
  const serializedEntries = [];
190
- serializedEntries.push(
191
- this.#mainIndentation(options) + this.#escapeArg(this.commandName, true, options)
192
- );
193
203
  for (let i = 0; i < this.args.length; i++) {
194
204
  const argsEntry = this.args[i];
195
205
  if (isString(argsEntry)) {
196
206
  serializedEntries.push(this.#escapeArg(argsEntry, false, options));
197
207
  } else {
198
- const [part1, part2] = argsEntry;
199
208
  serializedEntries.push(
200
- this.#escapeArg(part1, false, options) + this.#argPairSeparator(options) + this.#escapeArg(part2, false, options)
209
+ argsEntry.map((part) => this.#escapeArg(part, false, options)).join(this.#argPairSeparator(options))
201
210
  );
202
211
  }
203
212
  }
204
- let text = serializedEntries.join(this.#entrySeparator(options));
213
+ let text = this.#mainIndentation(options) + this.#escapeArg(this.commandName, true, options) + this.#separatorAfterCommand(options, serializedEntries.length) + serializedEntries.join(this.#intraEntrySeparator(options));
205
214
  if (options?.styleTextFormat) {
206
215
  text = styleText(options.styleTextFormat, text);
207
216
  }
@@ -248,7 +257,7 @@ var PrintableShellCommand = class {
248
257
  /** Equivalent to:
249
258
  *
250
259
  * ```
251
- * await this.print().spawnNodeInherit().success;
260
+ * await this.print().spawnNodeInherit().success;
252
261
  * ```
253
262
  */
254
263
  async shellOutNode(options) {
@@ -303,7 +312,7 @@ var PrintableShellCommand = class {
303
312
  /** Equivalent to:
304
313
  *
305
314
  * ```
306
- * new Response(this.spawnBun(options).stdout);
315
+ * new Response(this.spawnBun().stdout);
307
316
  * ```
308
317
  */
309
318
  spawnBunStdout(options) {
@@ -312,7 +321,7 @@ var PrintableShellCommand = class {
312
321
  /** Equivalent to:
313
322
  *
314
323
  * ```
315
- * await this.print().spawnBunInherit().success;
324
+ * await this.print().spawnBunInherit().success;
316
325
  * ```
317
326
  */
318
327
  async shellOutBun(options) {
@@ -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 { 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\n// TODO: allow `.toString()`ables?\ntype SingleArgument = string;\ntype FlagArgumentPair = [string, string];\ntype ArgsEntry = SingleArgument | FlagArgumentPair;\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 /**\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 (\n Array.isArray(argEntry) &&\n argEntry.length === 2 &&\n isString(argEntry[0]) &&\n isString(argEntry[1])\n ) {\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 #entrySeparator(options: PrintOptions): string {\n switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n case \"by-entry\": {\n return LINE_WRAP_LINE_END + this.#argIndentation(options);\n }\n case \"nested-by-entry\": {\n return LINE_WRAP_LINE_END + this.#argIndentation(options);\n }\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 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 serializedEntries.push(\n this.#mainIndentation(options) +\n this.#escapeArg(this.commandName, true, options),\n );\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 const [part1, part2] = argsEntry;\n serializedEntries.push(\n this.#escapeArg(part1, false, options) +\n this.#argPairSeparator(options) +\n this.#escapeArg(part2, false, options),\n );\n }\n }\n\n let text = serializedEntries.join(this.#entrySeparator(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 /** Equivalent to:\n *\n * ```\n * await this.print().spawnNodeInherit().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(options).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().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} from \"node:child_process\";\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 \" \";\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 /** 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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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "printable-shell-command",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
4
4
  "main": "./src/index.ts",
5
5
  "devDependencies": {
6
6
  "@biomejs/biome": "^2.1.4",
package/src/index.ts CHANGED
@@ -23,11 +23,20 @@ const LINE_WRAP_LINE_END = " \\\n";
23
23
  function isString(s: any): s is string {
24
24
  return typeof s === "string";
25
25
  }
26
+ // biome-ignore lint/suspicious/noExplicitAny: This is the correct type nere.
27
+ function isStringArray(entries: any[]): entries is string[] {
28
+ for (const entry of entries) {
29
+ if (!isString(entry)) {
30
+ return false;
31
+ }
32
+ }
33
+ return true;
34
+ }
26
35
 
27
36
  // TODO: allow `.toString()`ables?
28
37
  type SingleArgument = string;
29
- type FlagArgumentPair = [string, string];
30
- type ArgsEntry = SingleArgument | FlagArgumentPair;
38
+ type FlagArgumentGroup = string[];
39
+ type ArgsEntry = SingleArgument | FlagArgumentGroup;
31
40
  type Args = ArgsEntry[];
32
41
 
33
42
  export interface PrintOptions {
@@ -49,6 +58,8 @@ export interface PrintOptions {
49
58
  | "nested-by-entry"
50
59
  | "by-argument"
51
60
  | "inline";
61
+ /** Include the first arg (or first arg group) on the same line as the command, regardless of the `argumentLineWrapping` setting. */
62
+ skipLineWrapBeforeFirstArg?: true | false;
52
63
  /**
53
64
  * Style text using `node`'s [`styleText(…)`](https://nodejs.org/api/util.html#utilstyletextformat-text-options)
54
65
  *
@@ -113,12 +124,7 @@ export class PrintableShellCommand {
113
124
  if (typeof argEntry === "string") {
114
125
  continue;
115
126
  }
116
- if (
117
- Array.isArray(argEntry) &&
118
- argEntry.length === 2 &&
119
- isString(argEntry[0]) &&
120
- isString(argEntry[1])
121
- ) {
127
+ if (Array.isArray(argEntry) && isStringArray(argEntry)) {
122
128
  continue;
123
129
  }
124
130
  throw new Error(`Invalid arg entry at index: ${i}`);
@@ -256,14 +262,10 @@ export class PrintableShellCommand {
256
262
  }
257
263
  }
258
264
 
259
- #entrySeparator(options: PrintOptions): string {
265
+ #intraEntrySeparator(options: PrintOptions): string {
260
266
  switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {
261
- case "by-entry": {
262
- return LINE_WRAP_LINE_END + this.#argIndentation(options);
263
- }
264
- case "nested-by-entry": {
265
- return LINE_WRAP_LINE_END + this.#argIndentation(options);
266
- }
267
+ case "by-entry":
268
+ case "nested-by-entry":
267
269
  case "by-argument": {
268
270
  return LINE_WRAP_LINE_END + this.#argIndentation(options);
269
271
  }
@@ -275,32 +277,43 @@ export class PrintableShellCommand {
275
277
  }
276
278
  }
277
279
 
280
+ #separatorAfterCommand(
281
+ options: PrintOptions,
282
+ numFollowingEntries: number,
283
+ ): string {
284
+ if (numFollowingEntries === 0) {
285
+ return "";
286
+ }
287
+ if (options.skipLineWrapBeforeFirstArg ?? false) {
288
+ return " ";
289
+ }
290
+ return this.#intraEntrySeparator(options);
291
+ }
292
+
278
293
  public getPrintableCommand(options?: PrintOptions): string {
279
294
  // TODO: Why in the world does TypeScript not give the `options` arg the type of `PrintOptions | undefined`???
280
295
  options ??= {};
281
296
  const serializedEntries: string[] = [];
282
297
 
283
- serializedEntries.push(
284
- this.#mainIndentation(options) +
285
- this.#escapeArg(this.commandName, true, options),
286
- );
287
-
288
298
  for (let i = 0; i < this.args.length; i++) {
289
299
  const argsEntry = this.args[i];
290
300
 
291
301
  if (isString(argsEntry)) {
292
302
  serializedEntries.push(this.#escapeArg(argsEntry, false, options));
293
303
  } else {
294
- const [part1, part2] = argsEntry;
295
304
  serializedEntries.push(
296
- this.#escapeArg(part1, false, options) +
297
- this.#argPairSeparator(options) +
298
- this.#escapeArg(part2, false, options),
305
+ argsEntry
306
+ .map((part) => this.#escapeArg(part, false, options))
307
+ .join(this.#argPairSeparator(options)),
299
308
  );
300
309
  }
301
310
  }
302
311
 
303
- let text = serializedEntries.join(this.#entrySeparator(options));
312
+ let text =
313
+ this.#mainIndentation(options) +
314
+ this.#escapeArg(this.commandName, true, options) +
315
+ this.#separatorAfterCommand(options, serializedEntries.length) +
316
+ serializedEntries.join(this.#intraEntrySeparator(options));
304
317
  if (options?.styleTextFormat) {
305
318
  text = styleText(options.styleTextFormat, text);
306
319
  }
@@ -366,7 +379,7 @@ export class PrintableShellCommand {
366
379
  /** Equivalent to:
367
380
  *
368
381
  * ```
369
- * await this.print().spawnNodeInherit().success;
382
+ * await this.print().spawnNodeInherit().success;
370
383
  * ```
371
384
  */
372
385
  public async shellOutNode(
@@ -444,7 +457,7 @@ export class PrintableShellCommand {
444
457
  /** Equivalent to:
445
458
  *
446
459
  * ```
447
- * new Response(this.spawnBun(options).stdout);
460
+ * new Response(this.spawnBun().stdout);
448
461
  * ```
449
462
  */
450
463
  public spawnBunStdout(
@@ -463,7 +476,7 @@ export class PrintableShellCommand {
463
476
  /** Equivalent to:
464
477
  *
465
478
  * ```
466
- * await this.print().spawnBunInherit().success;
479
+ * await this.print().spawnBunInherit().success;
467
480
  * ```
468
481
  */
469
482
  public async shellOutBun(