printable-shell-command 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { styleText } from "node:util";
|
|
2
3
|
var DEFAULT_MAIN_INDENTATION = "";
|
|
3
4
|
var DEFAULT_ARG_INDENTATION = " ";
|
|
4
5
|
var DEFAULT_ARGUMENT_LINE_WRAPPING = "by-entry";
|
|
@@ -60,56 +61,70 @@ var PrintableShellCommand = class {
|
|
|
60
61
|
get commandName() {
|
|
61
62
|
return this.#commandName;
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
/** For use with `bun`.
|
|
65
|
+
*
|
|
66
|
+
* Usage example:
|
|
67
|
+
*
|
|
68
|
+
* ```
|
|
69
|
+
* import { PrintableShellCommand } from "printable-shell-command";
|
|
70
|
+
* import { spawn } from "bun";
|
|
71
|
+
*
|
|
72
|
+
* const command = new PrintableShellCommand( … );
|
|
73
|
+
* await spawn(command.toFlatCommand()).exited;
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
73
76
|
toFlatCommand() {
|
|
74
77
|
return [this.commandName, ...this.args.flat()];
|
|
75
78
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Convenient alias for `toFlatCommand()`.
|
|
81
|
+
*
|
|
82
|
+
* Usage example:
|
|
83
|
+
*
|
|
84
|
+
* ```
|
|
85
|
+
* import { PrintableShellCommand } from "printable-shell-command";
|
|
86
|
+
* import { spawn } from "bun";
|
|
87
|
+
*
|
|
88
|
+
* const command = new PrintableShellCommand( … );
|
|
89
|
+
* await spawn(command.forBun()).exited;
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* */
|
|
86
93
|
forBun() {
|
|
87
94
|
return this.toFlatCommand();
|
|
88
95
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
/**
|
|
97
|
+
* For use with `node:child_process`
|
|
98
|
+
*
|
|
99
|
+
* Usage example:
|
|
100
|
+
*
|
|
101
|
+
* ```
|
|
102
|
+
* import { PrintableShellCommand } from "printable-shell-command";
|
|
103
|
+
* import { spawn } from "node:child_process";
|
|
104
|
+
*
|
|
105
|
+
* const command = new PrintableShellCommand( … );
|
|
106
|
+
* const child_process = spawn(...command.toCommandWithFlatArgs()); // Note the `...`
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
*/
|
|
99
110
|
toCommandWithFlatArgs() {
|
|
100
111
|
return [this.commandName, this.args.flat()];
|
|
101
112
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
/**
|
|
114
|
+
* For use with `node:child_process`
|
|
115
|
+
*
|
|
116
|
+
* Usage example:
|
|
117
|
+
*
|
|
118
|
+
* ```
|
|
119
|
+
* import { PrintableShellCommand } from "printable-shell-command";
|
|
120
|
+
* import { spawn } from "node:child_process";
|
|
121
|
+
*
|
|
122
|
+
* const command = new PrintableShellCommand( … );
|
|
123
|
+
* const child_process = spawn(...command.forNode()); // Note the `...`
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* Convenient alias for `toCommandWithFlatArgs()`.
|
|
127
|
+
*/
|
|
113
128
|
forNode() {
|
|
114
129
|
return this.toCommandWithFlatArgs();
|
|
115
130
|
}
|
|
@@ -185,7 +200,11 @@ var PrintableShellCommand = class {
|
|
|
185
200
|
);
|
|
186
201
|
}
|
|
187
202
|
}
|
|
188
|
-
|
|
203
|
+
let text = serializedEntries.join(this.#entrySeparator(options));
|
|
204
|
+
if (options?.styleTextFormat) {
|
|
205
|
+
text = styleText(options.styleTextFormat, text);
|
|
206
|
+
}
|
|
207
|
+
return text;
|
|
189
208
|
}
|
|
190
209
|
print(options) {
|
|
191
210
|
console.log(this.getPrintableCommand(options));
|
|
@@ -228,7 +247,7 @@ var PrintableShellCommand = class {
|
|
|
228
247
|
/** Equivalent to:
|
|
229
248
|
*
|
|
230
249
|
* ```
|
|
231
|
-
*
|
|
250
|
+
* await this.print().spawnNodeInherit().success;
|
|
232
251
|
* ```
|
|
233
252
|
*/
|
|
234
253
|
async shellOutNode(options) {
|
|
@@ -283,7 +302,7 @@ var PrintableShellCommand = class {
|
|
|
283
302
|
/** Equivalent to:
|
|
284
303
|
*
|
|
285
304
|
* ```
|
|
286
|
-
*
|
|
305
|
+
* new Response(this.spawnBun(options).stdout);
|
|
287
306
|
* ```
|
|
288
307
|
*/
|
|
289
308
|
spawnBunStdout(options) {
|
|
@@ -292,7 +311,7 @@ var PrintableShellCommand = class {
|
|
|
292
311
|
/** Equivalent to:
|
|
293
312
|
*
|
|
294
313
|
* ```
|
|
295
|
-
*
|
|
314
|
+
* await this.print().spawnBunInherit().success;
|
|
296
315
|
* ```
|
|
297
316
|
*/
|
|
298
317
|
async shellOutBun(options) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n\tChildProcess as NodeChildProcess,\n\tSpawnOptions as NodeSpawnOptions,\n\tSpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple,\n\tSpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio,\n\tStdioNull as NodeStdioNull,\n\tStdioPipe as NodeStdioPipe,\n} from \"node:child_process\";\nimport type {\n\tSpawnOptions as BunSpawnOptions,\n\tSubprocess 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\treturn 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\tmainIndentation?: string; // Defaults to \"\"\n\targIndentation?: string; // Defaults to \" \"\n\t// - \"auto\": Quote only arguments that need it for safety. This tries to be\n\t// portable and safe across shells, but true safety and portability is hard\n\t// to guarantee.\n\t// - \"extra-safe\": Quote all arguments, even ones that don't need it. This is\n\t// more likely to be safe under all circumstances.\n\tquoting?: \"auto\" | \"extra-safe\";\n\t// Line wrapping to use between arguments. Defaults to `\"by-entry\"`.\n\targumentLineWrapping?:\n\t\t| \"by-entry\"\n\t\t| \"nested-by-entry\"\n\t\t| \"by-argument\"\n\t\t| \"inline\";\n}\n\n// https://mywiki.wooledge.org/BashGuide/SpecialCharacters\nconst SPECIAL_SHELL_CHARACTERS = new Set([\n\t\" \",\n\t'\"',\n\t\"'\",\n\t\"`\",\n\t\"|\",\n\t\"$\",\n\t\"*\",\n\t\"?\",\n\t\">\",\n\t\"<\",\n\t\"(\",\n\t\")\",\n\t\"[\",\n\t\"]\",\n\t\"{\",\n\t\"}\",\n\t\"&\",\n\t\"\\\\\",\n\t\";\",\n]);\n\n// https://mywiki.wooledge.org/BashGuide/SpecialCharacters\nconst SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND =\n\t// biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)\n\t(SPECIAL_SHELL_CHARACTERS as unknown as any).union(new Set([\"=\"]));\n\nexport class PrintableShellCommand {\n\t#commandName: string;\n\tconstructor(\n\t\tcommandName: string,\n\t\tprivate args: Args = [],\n\t) {\n\t\tif (!isString(commandName)) {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We want to print this, no matter what it is.\n\t\t\tthrow new Error(\"Command name is not a string:\", commandName as any);\n\t\t}\n\t\tthis.#commandName = commandName;\n\t\tif (typeof args === \"undefined\") {\n\t\t\treturn;\n\t\t}\n\t\tif (!Array.isArray(args)) {\n\t\t\tthrow new Error(\"Command arguments are not an array\");\n\t\t}\n\t\tfor (let i = 0; i < args.length; i++) {\n\t\t\tconst argEntry = args[i];\n\t\t\tif (typeof argEntry === \"string\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tArray.isArray(argEntry) &&\n\t\t\t\targEntry.length === 2 &&\n\t\t\t\tisString(argEntry[0]) &&\n\t\t\t\tisString(argEntry[1])\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthrow new Error(`Invalid arg entry at index: ${i}`);\n\t\t}\n\t}\n\n\tget commandName(): string {\n\t\treturn this.#commandName;\n\t}\n\n\t// For use with `bun`.\n\t//\n\t// Usage example:\n\t//\n\t// import { PrintableShellCommand } from \"printable-shell-command\";\n\t// import { spawn } from \"bun\";\n\t//\n\t// const command = new PrintableShellCommand(/* \u2026 */);\n\t// await spawn(command.toFlatCommand()).exited;\n\t//\n\tpublic toFlatCommand(): string[] {\n\t\treturn [this.commandName, ...this.args.flat()];\n\t}\n\n\t// Convenient alias for `toFlatCommand()`.\n\t//\n\t// Usage example:\n\t//\n\t// import { PrintableShellCommand } from \"printable-shell-command\";\n\t// import { spawn } from \"bun\";\n\t//\n\t// const command = new PrintableShellCommand(/* \u2026 */);\n\t// await spawn(command.forBun()).exited;\n\t//\n\tpublic forBun(): string[] {\n\t\treturn this.toFlatCommand();\n\t}\n\n\t// For use with `node:child_process`\n\t//\n\t// Usage example:\n\t//\n\t// import { PrintableShellCommand } from \"printable-shell-command\";\n\t// import { spawn } from \"node:child_process\";\n\t//\n\t// const command = new PrintableShellCommand(/* \u2026 */);\n\t// const child_process = spawn(...command.toCommandWithFlatArgs()); // Note the `...`\n\t//\n\tpublic toCommandWithFlatArgs(): [string, string[]] {\n\t\treturn [this.commandName, this.args.flat()];\n\t}\n\n\t// For use with `node:child_process`\n\t//\n\t// Usage example:\n\t//\n\t// import { PrintableShellCommand } from \"printable-shell-command\";\n\t// import { spawn } from \"node:child_process\";\n\t//\n\t// const command = new PrintableShellCommand(/* \u2026 */);\n\t// const child_process = spawn(...command.forNode()); // Note the `...`\n\t//\n\t// Convenient alias for `toCommandWithFlatArgs()`.\n\tpublic forNode(): [string, string[]] {\n\t\treturn this.toCommandWithFlatArgs();\n\t}\n\n\t#escapeArg(\n\t\targ: string,\n\t\tisMainCommand: boolean,\n\t\toptions: PrintOptions,\n\t): string {\n\t\tconst argCharacters = new Set(arg);\n\t\tconst specialShellCharacters = isMainCommand\n\t\t\t? SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND\n\t\t\t: SPECIAL_SHELL_CHARACTERS;\n\t\tif (\n\t\t\toptions?.quoting === \"extra-safe\" ||\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)\n\t\t\t(argCharacters as unknown as any).intersection(specialShellCharacters)\n\t\t\t\t.size > 0\n\t\t) {\n\t\t\t// Use single quote to reduce the need to escape (and therefore reduce the chance for bugs/security issues).\n\t\t\tconst escaped = arg.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"'\", \"\\\\'\");\n\t\t\treturn `'${escaped}'`;\n\t\t}\n\t\treturn arg;\n\t}\n\n\t#mainIndentation(options: PrintOptions): string {\n\t\treturn options?.mainIndentation ?? DEFAULT_MAIN_INDENTATION;\n\t}\n\n\t#argIndentation(options: PrintOptions): string {\n\t\treturn (\n\t\t\tthis.#mainIndentation(options) +\n\t\t\t(options?.argIndentation ?? DEFAULT_ARG_INDENTATION)\n\t\t);\n\t}\n\n\t#lineWrapSeparator(options: PrintOptions): string {\n\t\treturn LINE_WRAP_LINE_END + this.#argIndentation(options);\n\t}\n\n\t#argPairSeparator(options: PrintOptions): string {\n\t\tswitch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n\t\t\tcase \"by-entry\": {\n\t\t\t\treturn INLINE_SEPARATOR;\n\t\t\t}\n\t\t\tcase \"nested-by-entry\": {\n\t\t\t\treturn this.#lineWrapSeparator(options) + this.#argIndentation(options);\n\t\t\t}\n\t\t\tcase \"by-argument\": {\n\t\t\t\treturn this.#lineWrapSeparator(options);\n\t\t\t}\n\t\t\tcase \"inline\": {\n\t\t\t\treturn INLINE_SEPARATOR;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow new Error(\"Invalid argument line wrapping argument.\");\n\t\t}\n\t}\n\n\t#entrySeparator(options: PrintOptions): string {\n\t\tswitch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n\t\t\tcase \"by-entry\": {\n\t\t\t\treturn LINE_WRAP_LINE_END + this.#argIndentation(options);\n\t\t\t}\n\t\t\tcase \"nested-by-entry\": {\n\t\t\t\treturn LINE_WRAP_LINE_END + this.#argIndentation(options);\n\t\t\t}\n\t\t\tcase \"by-argument\": {\n\t\t\t\treturn LINE_WRAP_LINE_END + this.#argIndentation(options);\n\t\t\t}\n\t\t\tcase \"inline\": {\n\t\t\t\treturn INLINE_SEPARATOR;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow new Error(\"Invalid argument line wrapping argument.\");\n\t\t}\n\t}\n\n\tpublic getPrintableCommand(options?: PrintOptions): string {\n\t\t// TODO: Why in the world does TypeScript not give the `options` arg the type of `PrintOptions | undefined`???\n\t\t// biome-ignore lint/style/noParameterAssign: We want a default assignment without affecting the signature.\n\t\toptions ??= {};\n\t\tconst serializedEntries: string[] = [];\n\n\t\tserializedEntries.push(\n\t\t\tthis.#mainIndentation(options) +\n\t\t\t\tthis.#escapeArg(this.commandName, true, options),\n\t\t);\n\n\t\tfor (let i = 0; i < this.args.length; i++) {\n\t\t\tconst argsEntry = this.args[i];\n\n\t\t\tif (isString(argsEntry)) {\n\t\t\t\tserializedEntries.push(this.#escapeArg(argsEntry, false, options));\n\t\t\t} else {\n\t\t\t\tconst [part1, part2] = argsEntry;\n\t\t\t\tserializedEntries.push(\n\t\t\t\t\tthis.#escapeArg(part1, false, options) +\n\t\t\t\t\t\tthis.#argPairSeparator(options) +\n\t\t\t\t\t\tthis.#escapeArg(part2, false, options),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn serializedEntries.join(this.#entrySeparator(options));\n\t}\n\n\tpublic print(options?: PrintOptions): PrintableShellCommand {\n\t\tconsole.log(this.getPrintableCommand(options));\n\t\treturn this;\n\t}\n\n\t/**\n\t * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313\n\t */\n\tpublic spawnNode<\n\t\tStdin extends NodeStdioNull | NodeStdioPipe,\n\t\tStdout extends NodeStdioNull | NodeStdioPipe,\n\t\tStderr extends NodeStdioNull | NodeStdioPipe,\n\t>(\n\t\toptions?:\n\t\t\t| NodeSpawnOptions\n\t\t\t| NodeSpawnOptionsWithoutStdio\n\t\t\t| NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>,\n\t): // TODO: figure out how to return `ChildProcessByStdio<\u2026>` without duplicating fragile boilerplate.\n\tNodeChildProcess & { success: Promise<void> } {\n\t\tconst { spawn } = process.getBuiltinModule(\"node:child_process\");\n\t\t// @ts-ignore: The TypeScript checker has trouble reconciling the optional (i.e. potentially `undefined`) `options` with the third argument.\n\t\tconst subprocess = spawn(...this.forNode(), options) as NodeChildProcess & {\n\t\t\tsuccess: Promise<void>;\n\t\t};\n\t\tObject.defineProperty(subprocess, \"success\", {\n\t\t\tget() {\n\t\t\t\treturn new Promise<void>((resolve, reject) =>\n\t\t\t\t\tthis.addListener(\n\t\t\t\t\t\t\"exit\",\n\t\t\t\t\t\t(exitCode: number /* we only use the first arg */) => {\n\t\t\t\t\t\t\tif (exitCode === 0) {\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treject(`Command failed with non-zero exit code: ${exitCode}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t},\n\t\t\tenumerable: false,\n\t\t});\n\t\treturn subprocess;\n\t}\n\n\t/** A wrapper for `.spawnNode(\u2026)` that sets stdio to `\"inherit\"` (common for\n\t * invoking commands from scripts whose output and interaction should be\n\t * surfaced to the user). */\n\tpublic spawnNodeInherit(\n\t\toptions?: Omit<NodeSpawnOptions, \"stdio\">,\n\t): NodeChildProcess & { success: Promise<void> } {\n\t\tif (options && \"stdio\" in options) {\n\t\t\tthrow new Error(\"Unexpected `stdio` field.\");\n\t\t}\n\t\treturn this.spawnNode({ ...options, stdio: \"inherit\" });\n\t}\n\n\t/** Equivalent to:\n\t *\n\t * ```\n\t * await this.print().spawnNodeInherit().success;\n\t * ```\n\t */\n\tpublic async shellOutNode(\n\t\toptions?: Omit<NodeSpawnOptions, \"stdio\">,\n\t): Promise<void> {\n\t\tawait this.print().spawnNodeInherit(options).success;\n\t}\n\n\t/**\n\t * The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313\n\t */\n\tpublic spawnBun<\n\t\tconst In extends BunSpawnOptions.Writable = \"ignore\",\n\t\tconst Out extends BunSpawnOptions.Readable = \"pipe\",\n\t\tconst Err extends BunSpawnOptions.Readable = \"inherit\",\n\t>(\n\t\toptions?: Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, \"cmd\">,\n\t): BunSubprocess<In, Out, Err> & { success: Promise<void> } {\n\t\tif (options && \"cmd\" in options) {\n\t\t\tthrow new Error(\"Unexpected `cmd` field.\");\n\t\t}\n\t\tconst { spawn } = process.getBuiltinModule(\"bun\") as typeof import(\"bun\");\n\t\tconst subprocess = spawn({\n\t\t\t...options,\n\t\t\tcmd: this.forBun(),\n\t\t}) as BunSubprocess<In, Out, Err> & { success: Promise<void> };\n\t\tObject.defineProperty(subprocess, \"success\", {\n\t\t\tget() {\n\t\t\t\treturn new Promise<void>((resolve, reject) =>\n\t\t\t\t\tthis.exited\n\t\t\t\t\t\t.then((exitCode: number) => {\n\t\t\t\t\t\t\tif (exitCode === 0) {\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t`Command failed with non-zero exit code: ${exitCode}`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch(reject),\n\t\t\t\t);\n\t\t\t},\n\t\t\tenumerable: false,\n\t\t});\n\t\treturn subprocess;\n\t}\n\n\t/**\n\t * A wrapper for `.spawnBunInherit(\u2026)` that sets stdio to `\"inherit\"` (common\n\t * for invoking commands from scripts whose output and interaction should be\n\t * surfaced to the user).\n\t */\n\tpublic spawnBunInherit(\n\t\toptions?: Omit<\n\t\t\tOmit<\n\t\t\t\tBunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n\t\t\t\t\"cmd\"\n\t\t\t>,\n\t\t\t\"stdio\"\n\t\t>,\n\t): BunSubprocess<\"inherit\", \"inherit\", \"inherit\"> & {\n\t\tsuccess: Promise<void>;\n\t} {\n\t\tif (options && \"stdio\" in options) {\n\t\t\tthrow new Error(\"Unexpected `stdio` field.\");\n\t\t}\n\t\treturn this.spawnBun({\n\t\t\t...options,\n\t\t\tstdio: [\"inherit\", \"inherit\", \"inherit\"],\n\t\t});\n\t}\n\n\t/** Equivalent to:\n\t *\n\t * ```\n\t * new Response(this.spawnBun(options).stdout);\n\t * ```\n\t */\n\tpublic spawnBunStdout(\n\t\toptions?: Omit<\n\t\t\tOmit<\n\t\t\t\tBunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n\t\t\t\t\"cmd\"\n\t\t\t>,\n\t\t\t\"stdio\"\n\t\t>,\n\t): Response {\n\t\t// biome-ignore lint/suspicious/noExplicitAny: Avoid breaking the lib check when used without `@types/bun`.\n\t\treturn new Response((this.spawnBun(options) as any).stdout);\n\t}\n\n\t/** Equivalent to:\n\t *\n\t * ```\n\t * await this.print().spawnBunInherit().success;\n\t * ```\n\t */\n\tpublic async shellOutBun(\n\t\toptions?: Omit<\n\t\t\tOmit<\n\t\t\t\tBunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n\t\t\t\t\"cmd\"\n\t\t\t>,\n\t\t\t\"stdio\"\n\t\t>,\n\t): Promise<void> {\n\t\tawait this.print().spawnBunInherit(options).success;\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
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// 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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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "printable-shell-command",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"main": "./src/index.ts",
|
|
5
5
|
"devDependencies": {
|
|
6
|
-
"@biomejs/biome": "^2.
|
|
6
|
+
"@biomejs/biome": "^2.1.4",
|
|
7
7
|
"@cubing/dev-config": "^0.3.6",
|
|
8
|
-
"@types/bun": "^1.2.
|
|
9
|
-
"@types/node": "^24.0
|
|
8
|
+
"@types/bun": "^1.2.19",
|
|
9
|
+
"@types/node": "^24.2.0",
|
|
10
10
|
"esbuild": "^0.25.5"
|
|
11
11
|
},
|
|
12
12
|
"optionalDependencies": {
|
|
13
|
-
"@types/bun": "^1.2.
|
|
14
|
-
"@types/node": "^24.0
|
|
13
|
+
"@types/bun": "^1.2.19",
|
|
14
|
+
"@types/node": "^24.2.0"
|
|
15
15
|
},
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
ChildProcess as NodeChildProcess,
|
|
3
|
+
SpawnOptions as NodeSpawnOptions,
|
|
4
|
+
SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio,
|
|
5
|
+
SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple,
|
|
6
|
+
StdioNull as NodeStdioNull,
|
|
7
|
+
StdioPipe as NodeStdioPipe,
|
|
8
8
|
} from "node:child_process";
|
|
9
|
+
import { styleText } from "node:util";
|
|
9
10
|
import type {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
SpawnOptions as BunSpawnOptions,
|
|
12
|
+
Subprocess as BunSubprocess,
|
|
12
13
|
} from "bun";
|
|
13
14
|
|
|
14
15
|
const DEFAULT_MAIN_INDENTATION = "";
|
|
@@ -20,7 +21,7 @@ const LINE_WRAP_LINE_END = " \\\n";
|
|
|
20
21
|
|
|
21
22
|
// biome-ignore lint/suspicious/noExplicitAny: This is the correct type nere.
|
|
22
23
|
function isString(s: any): s is string {
|
|
23
|
-
|
|
24
|
+
return typeof s === "string";
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
// TODO: allow `.toString()`ables?
|
|
@@ -30,417 +31,449 @@ type ArgsEntry = SingleArgument | FlagArgumentPair;
|
|
|
30
31
|
type Args = ArgsEntry[];
|
|
31
32
|
|
|
32
33
|
export interface PrintOptions {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
/** Defaults to "" */
|
|
35
|
+
mainIndentation?: string;
|
|
36
|
+
/** Defaults to " " */
|
|
37
|
+
argIndentation?: string;
|
|
38
|
+
/**
|
|
39
|
+
* - `"auto"`: Quote only arguments that need it for safety. This tries to be
|
|
40
|
+
* portable and safe across shells, but true safety and portability is hard
|
|
41
|
+
* to guarantee.
|
|
42
|
+
* - `"extra-safe"`: Quote all arguments, even ones that don't need it. This is
|
|
43
|
+
* more likely to be safe under all circumstances.
|
|
44
|
+
*/
|
|
45
|
+
quoting?: "auto" | "extra-safe";
|
|
46
|
+
/** Line wrapping to use between arguments. Defaults to `"by-entry"`. */
|
|
47
|
+
argumentLineWrapping?:
|
|
48
|
+
| "by-entry"
|
|
49
|
+
| "nested-by-entry"
|
|
50
|
+
| "by-argument"
|
|
51
|
+
| "inline";
|
|
52
|
+
/**
|
|
53
|
+
* Style text using `node`'s [`styleText(…)`](https://nodejs.org/api/util.html#utilstyletextformat-text-options)
|
|
54
|
+
*
|
|
55
|
+
* Example usage:
|
|
56
|
+
*
|
|
57
|
+
* ```
|
|
58
|
+
* new PrintableShellCommand("echo", ["hi"]).print({
|
|
59
|
+
* styleTextFormat: ["gray", "bold"],
|
|
60
|
+
* });
|
|
61
|
+
* */
|
|
62
|
+
styleTextFormat?: Parameters<typeof styleText>[0];
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
// https://mywiki.wooledge.org/BashGuide/SpecialCharacters
|
|
50
66
|
const SPECIAL_SHELL_CHARACTERS = new Set([
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
" ",
|
|
68
|
+
'"',
|
|
69
|
+
"'",
|
|
70
|
+
"`",
|
|
71
|
+
"|",
|
|
72
|
+
"$",
|
|
73
|
+
"*",
|
|
74
|
+
"?",
|
|
75
|
+
">",
|
|
76
|
+
"<",
|
|
77
|
+
"(",
|
|
78
|
+
")",
|
|
79
|
+
"[",
|
|
80
|
+
"]",
|
|
81
|
+
"{",
|
|
82
|
+
"}",
|
|
83
|
+
"&",
|
|
84
|
+
"\\",
|
|
85
|
+
";",
|
|
70
86
|
]);
|
|
71
87
|
|
|
72
88
|
// https://mywiki.wooledge.org/BashGuide/SpecialCharacters
|
|
73
89
|
const SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND =
|
|
74
|
-
|
|
75
|
-
|
|
90
|
+
// biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)
|
|
91
|
+
(SPECIAL_SHELL_CHARACTERS as unknown as any).union(new Set(["="]));
|
|
76
92
|
|
|
77
93
|
export class PrintableShellCommand {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
94
|
+
#commandName: string;
|
|
95
|
+
constructor(
|
|
96
|
+
commandName: string,
|
|
97
|
+
private args: Args = [],
|
|
98
|
+
) {
|
|
99
|
+
if (!isString(commandName)) {
|
|
100
|
+
// biome-ignore lint/suspicious/noExplicitAny: We want to print this, no matter what it is.
|
|
101
|
+
throw new Error("Command name is not a string:", commandName as any);
|
|
102
|
+
}
|
|
103
|
+
this.#commandName = commandName;
|
|
104
|
+
if (typeof args === "undefined") {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!Array.isArray(args)) {
|
|
108
|
+
throw new Error("Command arguments are not an array");
|
|
109
|
+
}
|
|
110
|
+
for (let i = 0; i < args.length; i++) {
|
|
111
|
+
const argEntry = args[i];
|
|
112
|
+
if (typeof argEntry === "string") {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
Array.isArray(argEntry) &&
|
|
117
|
+
argEntry.length === 2 &&
|
|
118
|
+
isString(argEntry[0]) &&
|
|
119
|
+
isString(argEntry[1])
|
|
120
|
+
) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`Invalid arg entry at index: ${i}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get commandName(): string {
|
|
128
|
+
return this.#commandName;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** For use with `bun`.
|
|
132
|
+
*
|
|
133
|
+
* Usage example:
|
|
134
|
+
*
|
|
135
|
+
* ```
|
|
136
|
+
* import { PrintableShellCommand } from "printable-shell-command";
|
|
137
|
+
* import { spawn } from "bun";
|
|
138
|
+
*
|
|
139
|
+
* const command = new PrintableShellCommand( … );
|
|
140
|
+
* await spawn(command.toFlatCommand()).exited;
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
public toFlatCommand(): string[] {
|
|
144
|
+
return [this.commandName, ...this.args.flat()];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Convenient alias for `toFlatCommand()`.
|
|
149
|
+
*
|
|
150
|
+
* Usage example:
|
|
151
|
+
*
|
|
152
|
+
* ```
|
|
153
|
+
* import { PrintableShellCommand } from "printable-shell-command";
|
|
154
|
+
* import { spawn } from "bun";
|
|
155
|
+
*
|
|
156
|
+
* const command = new PrintableShellCommand( … );
|
|
157
|
+
* await spawn(command.forBun()).exited;
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* */
|
|
161
|
+
public forBun(): string[] {
|
|
162
|
+
return this.toFlatCommand();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* For use with `node:child_process`
|
|
167
|
+
*
|
|
168
|
+
* Usage example:
|
|
169
|
+
*
|
|
170
|
+
* ```
|
|
171
|
+
* import { PrintableShellCommand } from "printable-shell-command";
|
|
172
|
+
* import { spawn } from "node:child_process";
|
|
173
|
+
*
|
|
174
|
+
* const command = new PrintableShellCommand( … );
|
|
175
|
+
* const child_process = spawn(...command.toCommandWithFlatArgs()); // Note the `...`
|
|
176
|
+
* ```
|
|
177
|
+
*
|
|
178
|
+
*/
|
|
179
|
+
public toCommandWithFlatArgs(): [string, string[]] {
|
|
180
|
+
return [this.commandName, this.args.flat()];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* For use with `node:child_process`
|
|
185
|
+
*
|
|
186
|
+
* Usage example:
|
|
187
|
+
*
|
|
188
|
+
* ```
|
|
189
|
+
* import { PrintableShellCommand } from "printable-shell-command";
|
|
190
|
+
* import { spawn } from "node:child_process";
|
|
191
|
+
*
|
|
192
|
+
* const command = new PrintableShellCommand( … );
|
|
193
|
+
* const child_process = spawn(...command.forNode()); // Note the `...`
|
|
194
|
+
* ```
|
|
195
|
+
*
|
|
196
|
+
* Convenient alias for `toCommandWithFlatArgs()`.
|
|
197
|
+
*/
|
|
198
|
+
public forNode(): [string, string[]] {
|
|
199
|
+
return this.toCommandWithFlatArgs();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
#escapeArg(
|
|
203
|
+
arg: string,
|
|
204
|
+
isMainCommand: boolean,
|
|
205
|
+
options: PrintOptions,
|
|
206
|
+
): string {
|
|
207
|
+
const argCharacters = new Set(arg);
|
|
208
|
+
const specialShellCharacters = isMainCommand
|
|
209
|
+
? SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND
|
|
210
|
+
: SPECIAL_SHELL_CHARACTERS;
|
|
211
|
+
if (
|
|
212
|
+
options?.quoting === "extra-safe" ||
|
|
213
|
+
// biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)
|
|
214
|
+
(argCharacters as unknown as any).intersection(specialShellCharacters)
|
|
215
|
+
.size > 0
|
|
216
|
+
) {
|
|
217
|
+
// Use single quote to reduce the need to escape (and therefore reduce the chance for bugs/security issues).
|
|
218
|
+
const escaped = arg.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
|
|
219
|
+
return `'${escaped}'`;
|
|
220
|
+
}
|
|
221
|
+
return arg;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#mainIndentation(options: PrintOptions): string {
|
|
225
|
+
return options?.mainIndentation ?? DEFAULT_MAIN_INDENTATION;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#argIndentation(options: PrintOptions): string {
|
|
229
|
+
return (
|
|
230
|
+
this.#mainIndentation(options) +
|
|
231
|
+
(options?.argIndentation ?? DEFAULT_ARG_INDENTATION)
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
#lineWrapSeparator(options: PrintOptions): string {
|
|
236
|
+
return LINE_WRAP_LINE_END + this.#argIndentation(options);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#argPairSeparator(options: PrintOptions): string {
|
|
240
|
+
switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {
|
|
241
|
+
case "by-entry": {
|
|
242
|
+
return INLINE_SEPARATOR;
|
|
243
|
+
}
|
|
244
|
+
case "nested-by-entry": {
|
|
245
|
+
return this.#lineWrapSeparator(options) + this.#argIndentation(options);
|
|
246
|
+
}
|
|
247
|
+
case "by-argument": {
|
|
248
|
+
return this.#lineWrapSeparator(options);
|
|
249
|
+
}
|
|
250
|
+
case "inline": {
|
|
251
|
+
return INLINE_SEPARATOR;
|
|
252
|
+
}
|
|
253
|
+
default:
|
|
254
|
+
throw new Error("Invalid argument line wrapping argument.");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#entrySeparator(options: PrintOptions): string {
|
|
259
|
+
switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {
|
|
260
|
+
case "by-entry": {
|
|
261
|
+
return LINE_WRAP_LINE_END + this.#argIndentation(options);
|
|
262
|
+
}
|
|
263
|
+
case "nested-by-entry": {
|
|
264
|
+
return LINE_WRAP_LINE_END + this.#argIndentation(options);
|
|
265
|
+
}
|
|
266
|
+
case "by-argument": {
|
|
267
|
+
return LINE_WRAP_LINE_END + this.#argIndentation(options);
|
|
268
|
+
}
|
|
269
|
+
case "inline": {
|
|
270
|
+
return INLINE_SEPARATOR;
|
|
271
|
+
}
|
|
272
|
+
default:
|
|
273
|
+
throw new Error("Invalid argument line wrapping argument.");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
public getPrintableCommand(options?: PrintOptions): string {
|
|
278
|
+
// TODO: Why in the world does TypeScript not give the `options` arg the type of `PrintOptions | undefined`???
|
|
279
|
+
options ??= {};
|
|
280
|
+
const serializedEntries: string[] = [];
|
|
281
|
+
|
|
282
|
+
serializedEntries.push(
|
|
283
|
+
this.#mainIndentation(options) +
|
|
284
|
+
this.#escapeArg(this.commandName, true, options),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
for (let i = 0; i < this.args.length; i++) {
|
|
288
|
+
const argsEntry = this.args[i];
|
|
289
|
+
|
|
290
|
+
if (isString(argsEntry)) {
|
|
291
|
+
serializedEntries.push(this.#escapeArg(argsEntry, false, options));
|
|
292
|
+
} else {
|
|
293
|
+
const [part1, part2] = argsEntry;
|
|
294
|
+
serializedEntries.push(
|
|
295
|
+
this.#escapeArg(part1, false, options) +
|
|
296
|
+
this.#argPairSeparator(options) +
|
|
297
|
+
this.#escapeArg(part2, false, options),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let text = serializedEntries.join(this.#entrySeparator(options));
|
|
303
|
+
if (options?.styleTextFormat) {
|
|
304
|
+
text = styleText(options.styleTextFormat, text);
|
|
305
|
+
}
|
|
306
|
+
return text;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
public print(options?: PrintOptions): PrintableShellCommand {
|
|
310
|
+
console.log(this.getPrintableCommand(options));
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
|
|
316
|
+
*/
|
|
317
|
+
public spawnNode<
|
|
318
|
+
Stdin extends NodeStdioNull | NodeStdioPipe,
|
|
319
|
+
Stdout extends NodeStdioNull | NodeStdioPipe,
|
|
320
|
+
Stderr extends NodeStdioNull | NodeStdioPipe,
|
|
321
|
+
>(
|
|
322
|
+
options?:
|
|
323
|
+
| NodeSpawnOptions
|
|
324
|
+
| NodeSpawnOptionsWithoutStdio
|
|
325
|
+
| NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>,
|
|
326
|
+
): // TODO: figure out how to return `ChildProcessByStdio<…>` without duplicating fragile boilerplate.
|
|
327
|
+
NodeChildProcess & { success: Promise<void> } {
|
|
328
|
+
const { spawn } = process.getBuiltinModule("node:child_process");
|
|
329
|
+
// @ts-ignore: The TypeScript checker has trouble reconciling the optional (i.e. potentially `undefined`) `options` with the third argument.
|
|
330
|
+
const subprocess = spawn(...this.forNode(), options) as NodeChildProcess & {
|
|
331
|
+
success: Promise<void>;
|
|
332
|
+
};
|
|
333
|
+
Object.defineProperty(subprocess, "success", {
|
|
334
|
+
get() {
|
|
335
|
+
return new Promise<void>((resolve, reject) =>
|
|
336
|
+
this.addListener(
|
|
337
|
+
"exit",
|
|
338
|
+
(exitCode: number /* we only use the first arg */) => {
|
|
339
|
+
if (exitCode === 0) {
|
|
340
|
+
resolve();
|
|
341
|
+
} else {
|
|
342
|
+
reject(`Command failed with non-zero exit code: ${exitCode}`);
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
),
|
|
346
|
+
);
|
|
347
|
+
},
|
|
348
|
+
enumerable: false,
|
|
349
|
+
});
|
|
350
|
+
return subprocess;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** A wrapper for `.spawnNode(…)` that sets stdio to `"inherit"` (common for
|
|
354
|
+
* invoking commands from scripts whose output and interaction should be
|
|
355
|
+
* surfaced to the user). */
|
|
356
|
+
public spawnNodeInherit(
|
|
357
|
+
options?: Omit<NodeSpawnOptions, "stdio">,
|
|
358
|
+
): NodeChildProcess & { success: Promise<void> } {
|
|
359
|
+
if (options && "stdio" in options) {
|
|
360
|
+
throw new Error("Unexpected `stdio` field.");
|
|
361
|
+
}
|
|
362
|
+
return this.spawnNode({ ...options, stdio: "inherit" });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Equivalent to:
|
|
366
|
+
*
|
|
367
|
+
* ```
|
|
368
|
+
* await this.print().spawnNodeInherit().success;
|
|
369
|
+
* ```
|
|
370
|
+
*/
|
|
371
|
+
public async shellOutNode(
|
|
372
|
+
options?: Omit<NodeSpawnOptions, "stdio">,
|
|
373
|
+
): Promise<void> {
|
|
374
|
+
await this.print().spawnNodeInherit(options).success;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
|
|
379
|
+
*/
|
|
380
|
+
public spawnBun<
|
|
381
|
+
const In extends BunSpawnOptions.Writable = "ignore",
|
|
382
|
+
const Out extends BunSpawnOptions.Readable = "pipe",
|
|
383
|
+
const Err extends BunSpawnOptions.Readable = "inherit",
|
|
384
|
+
>(
|
|
385
|
+
options?: Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, "cmd">,
|
|
386
|
+
): BunSubprocess<In, Out, Err> & { success: Promise<void> } {
|
|
387
|
+
if (options && "cmd" in options) {
|
|
388
|
+
throw new Error("Unexpected `cmd` field.");
|
|
389
|
+
}
|
|
390
|
+
const { spawn } = process.getBuiltinModule("bun") as typeof import("bun");
|
|
391
|
+
const subprocess = spawn({
|
|
392
|
+
...options,
|
|
393
|
+
cmd: this.forBun(),
|
|
394
|
+
}) as BunSubprocess<In, Out, Err> & { success: Promise<void> };
|
|
395
|
+
Object.defineProperty(subprocess, "success", {
|
|
396
|
+
get() {
|
|
397
|
+
return new Promise<void>((resolve, reject) =>
|
|
398
|
+
this.exited
|
|
399
|
+
.then((exitCode: number) => {
|
|
400
|
+
if (exitCode === 0) {
|
|
401
|
+
resolve();
|
|
402
|
+
} else {
|
|
403
|
+
reject(
|
|
404
|
+
new Error(
|
|
405
|
+
`Command failed with non-zero exit code: ${exitCode}`,
|
|
406
|
+
),
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
.catch(reject),
|
|
411
|
+
);
|
|
412
|
+
},
|
|
413
|
+
enumerable: false,
|
|
414
|
+
});
|
|
415
|
+
return subprocess;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* A wrapper for `.spawnBunInherit(…)` that sets stdio to `"inherit"` (common
|
|
420
|
+
* for invoking commands from scripts whose output and interaction should be
|
|
421
|
+
* surfaced to the user).
|
|
422
|
+
*/
|
|
423
|
+
public spawnBunInherit(
|
|
424
|
+
options?: Omit<
|
|
425
|
+
Omit<
|
|
426
|
+
BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
|
|
427
|
+
"cmd"
|
|
428
|
+
>,
|
|
429
|
+
"stdio"
|
|
430
|
+
>,
|
|
431
|
+
): BunSubprocess<"inherit", "inherit", "inherit"> & {
|
|
432
|
+
success: Promise<void>;
|
|
433
|
+
} {
|
|
434
|
+
if (options && "stdio" in options) {
|
|
435
|
+
throw new Error("Unexpected `stdio` field.");
|
|
436
|
+
}
|
|
437
|
+
return this.spawnBun({
|
|
438
|
+
...options,
|
|
439
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Equivalent to:
|
|
444
|
+
*
|
|
445
|
+
* ```
|
|
446
|
+
* new Response(this.spawnBun(options).stdout);
|
|
447
|
+
* ```
|
|
448
|
+
*/
|
|
449
|
+
public spawnBunStdout(
|
|
450
|
+
options?: Omit<
|
|
451
|
+
Omit<
|
|
452
|
+
BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
|
|
453
|
+
"cmd"
|
|
454
|
+
>,
|
|
455
|
+
"stdio"
|
|
456
|
+
>,
|
|
457
|
+
): Response {
|
|
458
|
+
// biome-ignore lint/suspicious/noExplicitAny: Avoid breaking the lib check when used without `@types/bun`.
|
|
459
|
+
return new Response((this.spawnBun(options) as any).stdout);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Equivalent to:
|
|
463
|
+
*
|
|
464
|
+
* ```
|
|
465
|
+
* await this.print().spawnBunInherit().success;
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
public async shellOutBun(
|
|
469
|
+
options?: Omit<
|
|
470
|
+
Omit<
|
|
471
|
+
BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
|
|
472
|
+
"cmd"
|
|
473
|
+
>,
|
|
474
|
+
"stdio"
|
|
475
|
+
>,
|
|
476
|
+
): Promise<void> {
|
|
477
|
+
await this.print().spawnBunInherit(options).success;
|
|
478
|
+
}
|
|
446
479
|
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import type { ChildProcess as NodeChildProcess, SpawnOptions as NodeSpawnOptions, SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple, SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio, StdioNull as NodeStdioNull, StdioPipe as NodeStdioPipe } from "node:child_process";
|
|
2
|
-
import type { SpawnOptions as BunSpawnOptions, Subprocess as BunSubprocess } from "bun";
|
|
3
|
-
type SingleArgument = string;
|
|
4
|
-
type FlagArgumentPair = [string, string];
|
|
5
|
-
type ArgsEntry = SingleArgument | FlagArgumentPair;
|
|
6
|
-
type Args = ArgsEntry[];
|
|
7
|
-
export interface PrintOptions {
|
|
8
|
-
mainIndentation?: string;
|
|
9
|
-
argIndentation?: string;
|
|
10
|
-
quoting?: "auto" | "extra-safe";
|
|
11
|
-
argumentLineWrapping?: "by-entry" | "nested-by-entry" | "by-argument" | "inline";
|
|
12
|
-
}
|
|
13
|
-
export declare class PrintableShellCommand {
|
|
14
|
-
#private;
|
|
15
|
-
private args;
|
|
16
|
-
constructor(commandName: string, args?: Args);
|
|
17
|
-
get commandName(): string;
|
|
18
|
-
toFlatCommand(): string[];
|
|
19
|
-
forBun(): string[];
|
|
20
|
-
toCommandWithFlatArgs(): [string, string[]];
|
|
21
|
-
forNode(): [string, string[]];
|
|
22
|
-
getPrintableCommand(options?: PrintOptions): string;
|
|
23
|
-
print(options?: PrintOptions): PrintableShellCommand;
|
|
24
|
-
/**
|
|
25
|
-
* The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
|
|
26
|
-
*/
|
|
27
|
-
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.
|
|
28
|
-
NodeChildProcess & {
|
|
29
|
-
success: Promise<void>;
|
|
30
|
-
};
|
|
31
|
-
/** A wrapper for `.spawnNode(…)` that sets stdio to `"inherit"` (common for
|
|
32
|
-
* invoking commands from scripts whose output and interaction should be
|
|
33
|
-
* surfaced to the user). */
|
|
34
|
-
spawnNodeInherit(options?: Omit<NodeSpawnOptions, "stdio">): NodeChildProcess & {
|
|
35
|
-
success: Promise<void>;
|
|
36
|
-
};
|
|
37
|
-
/** Equivalent to:
|
|
38
|
-
*
|
|
39
|
-
* ```
|
|
40
|
-
* await this.print().spawnNodeInherit().success;
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
shellOutNode(options?: Omit<NodeSpawnOptions, "stdio">): Promise<void>;
|
|
44
|
-
/**
|
|
45
|
-
* The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
|
|
46
|
-
*/
|
|
47
|
-
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> & {
|
|
48
|
-
success: Promise<void>;
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* A wrapper for `.spawnBunInherit(…)` that sets stdio to `"inherit"` (common
|
|
52
|
-
* for invoking commands from scripts whose output and interaction should be
|
|
53
|
-
* surfaced to the user).
|
|
54
|
-
*/
|
|
55
|
-
spawnBunInherit(options?: Omit<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd">, "stdio">): BunSubprocess<"inherit", "inherit", "inherit"> & {
|
|
56
|
-
success: Promise<void>;
|
|
57
|
-
};
|
|
58
|
-
/** Equivalent to:
|
|
59
|
-
*
|
|
60
|
-
* ```
|
|
61
|
-
* new Response(this.spawnBun(options).stdout);
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
spawnBunStdout(options?: Omit<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd">, "stdio">): Response;
|
|
65
|
-
/** Equivalent to:
|
|
66
|
-
*
|
|
67
|
-
* ```
|
|
68
|
-
* await this.print().spawnBunInherit().success;
|
|
69
|
-
* ```
|
|
70
|
-
*/
|
|
71
|
-
shellOutBun(options?: Omit<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd">, "stdio">): Promise<void>;
|
|
72
|
-
}
|
|
73
|
-
export {};
|