printable-shell-command 2.0.0 → 2.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,6 +1,8 @@
|
|
|
1
|
-
import type { ChildProcess as NodeChildProcess, SpawnOptions as NodeSpawnOptions, SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio, SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple, StdioNull as NodeStdioNull, StdioPipe as NodeStdioPipe } from "node:child_process";
|
|
1
|
+
import type { ChildProcess as NodeChildProcess, SpawnOptions as NodeSpawnOptions, SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio, SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple, StdioNull as NodeStdioNull, StdioPipe as NodeStdioPipe, ProcessEnvOptions } from "node:child_process";
|
|
2
2
|
import { styleText } from "node:util";
|
|
3
|
-
import type { SpawnOptions as BunSpawnOptions, Subprocess as BunSubprocess } from "bun";
|
|
3
|
+
import type { SpawnOptions as BunSpawnOptions, Subprocess as BunSubprocess, SpawnOptions } from "bun";
|
|
4
|
+
import { Path } from "path-class";
|
|
5
|
+
import type { SetFieldType } from "type-fest";
|
|
4
6
|
type SingleArgument = string;
|
|
5
7
|
type FlagArgumentGroup = string[];
|
|
6
8
|
type ArgsEntry = SingleArgument | FlagArgumentGroup;
|
|
@@ -34,6 +36,14 @@ export interface PrintOptions {
|
|
|
34
36
|
* */
|
|
35
37
|
styleTextFormat?: Parameters<typeof styleText>[0];
|
|
36
38
|
}
|
|
39
|
+
type NodeCwd = ProcessEnvOptions["cwd"] | Path;
|
|
40
|
+
type NodeWithCwd<T extends {
|
|
41
|
+
cwd?: ProcessEnvOptions["cwd"];
|
|
42
|
+
}> = SetFieldType<T, "cwd", NodeCwd | undefined>;
|
|
43
|
+
type BunCwd = SpawnOptions.OptionsObject<any, any, any>["cwd"] | Path;
|
|
44
|
+
type BunWithCwd<T extends {
|
|
45
|
+
cwd?: SpawnOptions.OptionsObject<any, any, any>["cwd"];
|
|
46
|
+
}> = SetFieldType<T, "cwd", BunCwd | undefined>;
|
|
37
47
|
export declare class PrintableShellCommand {
|
|
38
48
|
#private;
|
|
39
49
|
private args;
|
|
@@ -103,24 +113,24 @@ export declare class PrintableShellCommand {
|
|
|
103
113
|
/**
|
|
104
114
|
* The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
|
|
105
115
|
*/
|
|
106
|
-
spawn<Stdin extends NodeStdioNull | NodeStdioPipe, Stdout extends NodeStdioNull | NodeStdioPipe, Stderr extends NodeStdioNull | NodeStdioPipe>(options?: NodeSpawnOptions | NodeSpawnOptionsWithoutStdio | NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr
|
|
116
|
+
spawn<Stdin extends NodeStdioNull | NodeStdioPipe, Stdout extends NodeStdioNull | NodeStdioPipe, Stderr extends NodeStdioNull | NodeStdioPipe>(options?: NodeWithCwd<NodeSpawnOptions> | NodeWithCwd<NodeSpawnOptionsWithoutStdio> | NodeWithCwd<NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>>): // TODO: figure out how to return `ChildProcessByStdio<…>` without duplicating fragile boilerplate.
|
|
107
117
|
NodeChildProcess & {
|
|
108
118
|
success: Promise<void>;
|
|
109
119
|
};
|
|
110
120
|
/** A wrapper for `.spawnNode(…)` that sets stdio to `"inherit"` (common for
|
|
111
121
|
* invoking commands from scripts whose output and interaction should be
|
|
112
122
|
* surfaced to the user). */
|
|
113
|
-
spawnInherit(options?: Omit<NodeSpawnOptions, "stdio"
|
|
123
|
+
spawnInherit(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): NodeChildProcess & {
|
|
114
124
|
success: Promise<void>;
|
|
115
125
|
};
|
|
116
|
-
stdout(options?: Omit<NodeSpawnOptions, "stdio"
|
|
126
|
+
stdout(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): Response;
|
|
117
127
|
/** Equivalent to:
|
|
118
128
|
*
|
|
119
129
|
* ```
|
|
120
130
|
* await this.print().spawnInherit(…).success;
|
|
121
131
|
* ```
|
|
122
132
|
*/
|
|
123
|
-
shellOut(options?: Omit<NodeSpawnOptions, "stdio"
|
|
133
|
+
shellOut(options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>): Promise<void>;
|
|
124
134
|
bun: {
|
|
125
135
|
/** Equivalent to:
|
|
126
136
|
*
|
|
@@ -128,7 +138,7 @@ export declare class PrintableShellCommand {
|
|
|
128
138
|
* await this.print().bun.spawnBunInherit(…).success;
|
|
129
139
|
* ```
|
|
130
140
|
*/
|
|
131
|
-
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"
|
|
141
|
+
spawnBun: <const In extends BunSpawnOptions.Writable = "ignore", const Out extends BunSpawnOptions.Readable = "pipe", const Err extends BunSpawnOptions.Readable = "inherit">(options?: BunWithCwd<Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, "cmd">>) => BunSubprocess<In, Out, Err> & {
|
|
132
142
|
success: Promise<void>;
|
|
133
143
|
};
|
|
134
144
|
/**
|
|
@@ -136,7 +146,7 @@ export declare class PrintableShellCommand {
|
|
|
136
146
|
* for invoking commands from scripts whose output and interaction should be
|
|
137
147
|
* surfaced to the user).
|
|
138
148
|
*/
|
|
139
|
-
spawnBunInherit: (options?:
|
|
149
|
+
spawnBunInherit: (options?: BunWithCwd<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd" | "stdio">>) => BunSubprocess<"inherit", "inherit", "inherit"> & {
|
|
140
150
|
success: Promise<void>;
|
|
141
151
|
};
|
|
142
152
|
/** Equivalent to:
|
|
@@ -145,8 +155,8 @@ export declare class PrintableShellCommand {
|
|
|
145
155
|
* new Response(this.bun.spawnBun(…).stdout);
|
|
146
156
|
* ```
|
|
147
157
|
*/
|
|
148
|
-
spawnBunStdout: (options?:
|
|
149
|
-
shellOutBun: (options?:
|
|
158
|
+
spawnBunStdout: (options?: BunWithCwd<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd" | "stdio">>) => Response;
|
|
159
|
+
shellOutBun: (options?: BunWithCwd<Omit<BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">, "cmd" | "stdio">>) => Promise<void>;
|
|
150
160
|
};
|
|
151
161
|
}
|
|
152
162
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Readable } from "node:stream";
|
|
3
3
|
import { styleText } from "node:util";
|
|
4
|
+
import { Path } from "path-class";
|
|
4
5
|
var DEFAULT_MAIN_INDENTATION = "";
|
|
5
6
|
var DEFAULT_ARG_INDENTATION = " ";
|
|
6
7
|
var DEFAULT_ARGUMENT_LINE_WRAPPING = "by-entry";
|
|
@@ -226,7 +227,18 @@ var PrintableShellCommand = class {
|
|
|
226
227
|
*/
|
|
227
228
|
spawn(options) {
|
|
228
229
|
const { spawn } = process.getBuiltinModule("node:child_process");
|
|
229
|
-
const
|
|
230
|
+
const cwd = (() => {
|
|
231
|
+
if (typeof options?.cwd !== "undefined") {
|
|
232
|
+
if (options.cwd instanceof Path) {
|
|
233
|
+
return options.cwd.toString();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return options?.cwd;
|
|
237
|
+
})();
|
|
238
|
+
const subprocess = spawn(...this.forNode(), {
|
|
239
|
+
...options,
|
|
240
|
+
cwd
|
|
241
|
+
});
|
|
230
242
|
Object.defineProperty(subprocess, "success", {
|
|
231
243
|
get() {
|
|
232
244
|
return new Promise(
|
|
@@ -282,8 +294,17 @@ var PrintableShellCommand = class {
|
|
|
282
294
|
throw new Error("Unexpected `cmd` field.");
|
|
283
295
|
}
|
|
284
296
|
const { spawn } = process.getBuiltinModule("bun");
|
|
297
|
+
const cwd = (() => {
|
|
298
|
+
if (typeof options?.cwd !== "undefined") {
|
|
299
|
+
if (options.cwd instanceof Path) {
|
|
300
|
+
return options.cwd.toString();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return options?.cwd;
|
|
304
|
+
})();
|
|
285
305
|
const subprocess = spawn({
|
|
286
306
|
...options,
|
|
307
|
+
cwd,
|
|
287
308
|
cmd: this.forBun()
|
|
288
309
|
});
|
|
289
310
|
Object.defineProperty(subprocess, "success", {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n ChildProcess as NodeChildProcess,\n SpawnOptions as NodeSpawnOptions,\n SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio,\n SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple,\n StdioNull as NodeStdioNull,\n StdioPipe as NodeStdioPipe,\n} from \"node:child_process\";\nimport { Readable } from \"node:stream\";\nimport { styleText } from \"node:util\";\nimport type {\n SpawnOptions as BunSpawnOptions,\n Subprocess as BunSubprocess,\n} from \"bun\";\n\nconst DEFAULT_MAIN_INDENTATION = \"\";\nconst DEFAULT_ARG_INDENTATION = \" \";\nconst DEFAULT_ARGUMENT_LINE_WRAPPING = \"by-entry\";\n\nconst INLINE_SEPARATOR = \" \";\nconst LINE_WRAP_LINE_END = \" \\\\\\n\";\n\n// biome-ignore lint/suspicious/noExplicitAny: This is the correct type nere.\nfunction isString(s: any): s is string {\n return typeof s === \"string\";\n}\n// biome-ignore lint/suspicious/noExplicitAny: This is the correct type nere.\nfunction isStringArray(entries: any[]): entries is string[] {\n for (const entry of entries) {\n if (!isString(entry)) {\n return false;\n }\n }\n return true;\n}\n\n// TODO: allow `.toString()`ables?\ntype SingleArgument = string;\ntype FlagArgumentGroup = string[];\ntype ArgsEntry = SingleArgument | FlagArgumentGroup;\ntype Args = ArgsEntry[];\n\nexport interface PrintOptions {\n /** Defaults to \"\" */\n mainIndentation?: string;\n /** Defaults to \" \" */\n argIndentation?: string;\n /**\n * - `\"auto\"`: Quote only arguments that need it for safety. This tries to be\n * portable and safe across shells, but true safety and portability is hard\n * to guarantee.\n * - `\"extra-safe\"`: Quote all arguments, even ones that don't need it. This is\n * more likely to be safe under all circumstances.\n */\n quoting?: \"auto\" | \"extra-safe\";\n /** Line wrapping to use between arguments. Defaults to `\"by-entry\"`. */\n argumentLineWrapping?:\n | \"by-entry\"\n | \"nested-by-entry\"\n | \"by-argument\"\n | \"inline\";\n /** Include the first arg (or first arg group) on the same line as the command, regardless of the `argumentLineWrapping` setting. */\n skipLineWrapBeforeFirstArg?: true | false;\n /**\n * Style text using `node`'s [`styleText(\u2026)`](https://nodejs.org/api/util.html#utilstyletextformat-text-options)\n *\n * Example usage:\n *\n * ```\n * new PrintableShellCommand(\"echo\", [\"hi\"]).print({\n * styleTextFormat: [\"gray\", \"bold\"],\n * });\n * */\n styleTextFormat?: Parameters<typeof styleText>[0];\n}\n\n// https://mywiki.wooledge.org/BashGuide/SpecialCharacters\nconst SPECIAL_SHELL_CHARACTERS = new Set([\n \" \",\n '\"',\n \"'\",\n \"`\",\n \"|\",\n \"$\",\n \"*\",\n \"?\",\n \">\",\n \"<\",\n \"(\",\n \")\",\n \"[\",\n \"]\",\n \"{\",\n \"}\",\n \"&\",\n \"\\\\\",\n \";\",\n \"#\",\n]);\n\n// https://mywiki.wooledge.org/BashGuide/SpecialCharacters\nconst SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND =\n // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)\n (SPECIAL_SHELL_CHARACTERS as unknown as any).union(new Set([\"=\"]));\n\nexport class PrintableShellCommand {\n #commandName: string;\n constructor(\n commandName: string,\n private args: Args = [],\n ) {\n if (!isString(commandName)) {\n // biome-ignore lint/suspicious/noExplicitAny: We want to print this, no matter what it is.\n throw new Error(\"Command name is not a string:\", commandName as any);\n }\n this.#commandName = commandName;\n if (typeof args === \"undefined\") {\n return;\n }\n if (!Array.isArray(args)) {\n throw new Error(\"Command arguments are not an array\");\n }\n for (let i = 0; i < args.length; i++) {\n const argEntry = args[i];\n if (typeof argEntry === \"string\") {\n continue;\n }\n if (Array.isArray(argEntry) && isStringArray(argEntry)) {\n continue;\n }\n throw new Error(`Invalid arg entry at index: ${i}`);\n }\n }\n\n get commandName(): string {\n return this.#commandName;\n }\n\n /** For use with `bun`.\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"bun\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * await spawn(command.toFlatCommand()).exited;\n * ```\n */\n public toFlatCommand(): string[] {\n return [this.commandName, ...this.args.flat()];\n }\n\n /**\n * Convenient alias for `toFlatCommand()`.\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"bun\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * await spawn(command.forBun()).exited;\n * ```\n *\n * */\n public forBun(): string[] {\n return this.toFlatCommand();\n }\n\n /**\n * For use with `node:child_process`\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"node:child_process\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * const child_process = spawn(...command.toCommandWithFlatArgs()); // Note the `...`\n * ```\n *\n */\n public toCommandWithFlatArgs(): [string, string[]] {\n return [this.commandName, this.args.flat()];\n }\n\n /**\n * For use with `node:child_process`\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"node:child_process\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * const child_process = spawn(...command.forNode()); // Note the `...`\n * ```\n *\n * Convenient alias for `toCommandWithFlatArgs()`.\n */\n public forNode(): [string, string[]] {\n return this.toCommandWithFlatArgs();\n }\n\n #escapeArg(\n arg: string,\n isMainCommand: boolean,\n options: PrintOptions,\n ): string {\n const argCharacters = new Set(arg);\n const specialShellCharacters = isMainCommand\n ? SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND\n : SPECIAL_SHELL_CHARACTERS;\n if (\n options?.quoting === \"extra-safe\" ||\n // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)\n (argCharacters as unknown as any).intersection(specialShellCharacters)\n .size > 0\n ) {\n // Use single quote to reduce the need to escape (and therefore reduce the chance for bugs/security issues).\n const escaped = arg.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"'\", \"\\\\'\");\n return `'${escaped}'`;\n }\n return arg;\n }\n\n #mainIndentation(options: PrintOptions): string {\n return options?.mainIndentation ?? DEFAULT_MAIN_INDENTATION;\n }\n\n #argIndentation(options: PrintOptions): string {\n return (\n this.#mainIndentation(options) +\n (options?.argIndentation ?? DEFAULT_ARG_INDENTATION)\n );\n }\n\n #lineWrapSeparator(options: PrintOptions): string {\n return LINE_WRAP_LINE_END + this.#argIndentation(options);\n }\n\n #argPairSeparator(options: PrintOptions): string {\n switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n case \"by-entry\": {\n return INLINE_SEPARATOR;\n }\n case \"nested-by-entry\": {\n return this.#lineWrapSeparator(options) + this.#argIndentation(options);\n }\n case \"by-argument\": {\n return this.#lineWrapSeparator(options);\n }\n case \"inline\": {\n return INLINE_SEPARATOR;\n }\n default:\n throw new Error(\"Invalid argument line wrapping argument.\");\n }\n }\n\n #intraEntrySeparator(options: PrintOptions): string {\n switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n case \"by-entry\":\n case \"nested-by-entry\":\n case \"by-argument\": {\n return LINE_WRAP_LINE_END + this.#argIndentation(options);\n }\n case \"inline\": {\n return INLINE_SEPARATOR;\n }\n default:\n throw new Error(\"Invalid argument line wrapping argument.\");\n }\n }\n\n #separatorAfterCommand(\n options: PrintOptions,\n numFollowingEntries: number,\n ): string {\n if (numFollowingEntries === 0) {\n return \"\";\n }\n if (options.skipLineWrapBeforeFirstArg ?? false) {\n return INLINE_SEPARATOR;\n }\n return this.#intraEntrySeparator(options);\n }\n\n public getPrintableCommand(options?: PrintOptions): string {\n // TODO: Why in the world does TypeScript not give the `options` arg the type of `PrintOptions | undefined`???\n options ??= {};\n const serializedEntries: string[] = [];\n\n for (let i = 0; i < this.args.length; i++) {\n const argsEntry = this.args[i];\n\n if (isString(argsEntry)) {\n serializedEntries.push(this.#escapeArg(argsEntry, false, options));\n } else {\n serializedEntries.push(\n argsEntry\n .map((part) => this.#escapeArg(part, false, options))\n .join(this.#argPairSeparator(options)),\n );\n }\n }\n\n let text =\n this.#mainIndentation(options) +\n this.#escapeArg(this.commandName, true, options) +\n this.#separatorAfterCommand(options, serializedEntries.length) +\n serializedEntries.join(this.#intraEntrySeparator(options));\n if (options?.styleTextFormat) {\n text = styleText(options.styleTextFormat, text);\n }\n return text;\n }\n\n public print(options?: PrintOptions): PrintableShellCommand {\n console.log(this.getPrintableCommand(options));\n return this;\n }\n\n /**\n * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313\n */\n public spawn<\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 // biome-ignore lint/suspicious/noTsIgnore: We don't want linting to depend on *broken* type checking.\n // @ts-ignore: The TypeScript checker has trouble reconciling the optional (i.e. potentially `undefined`) `options` with the third argument.\n const subprocess = spawn(...this.forNode(), 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 spawnInherit(\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.spawn({ ...options, stdio: \"inherit\" });\n }\n\n public stdout(options?: Omit<NodeSpawnOptions, \"stdio\">): Response {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n const subprocess = this.spawn({\n ...options,\n stdio: [\"ignore\", \"pipe\", \"inherit\"],\n });\n\n // biome-ignore lint/style/noNonNullAssertion: dude\n return new Response(Readable.toWeb(subprocess.stdout!));\n }\n\n /** Equivalent to:\n *\n * ```\n * await this.print().spawnInherit(\u2026).success;\n * ```\n */\n public async shellOut(\n options?: Omit<NodeSpawnOptions, \"stdio\">,\n ): Promise<void> {\n await this.print().spawnInherit(options).success;\n }\n\n /**\n * The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313\n */\n #spawnBun<\n const In extends BunSpawnOptions.Writable = \"ignore\",\n const Out extends BunSpawnOptions.Readable = \"pipe\",\n const Err extends BunSpawnOptions.Readable = \"inherit\",\n >(\n options?: 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 #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.bun.spawnBun({\n ...options,\n stdio: [\"inherit\", \"inherit\", \"inherit\"],\n });\n }\n\n #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.bun.spawnBun(options) as any).stdout);\n }\n\n 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().bun.spawnBunInherit(options).success;\n }\n\n bun = {\n /** Equivalent to:\n *\n * ```\n * await this.print().bun.spawnBunInherit(\u2026).success;\n * ```\n */\n spawnBun: this.#spawnBun.bind(this),\n /**\n * A wrapper for `.spawnBunInherit(\u2026)` that sets stdio to `\"inherit\"` (common\n * for invoking commands from scripts whose output and interaction should be\n * surfaced to the user).\n */\n spawnBunInherit: this.#spawnBunInherit.bind(this),\n /** Equivalent to:\n *\n * ```\n * new Response(this.bun.spawnBun(\u2026).stdout);\n * ```\n */\n spawnBunStdout: this.#spawnBunStdout.bind(this),\n shellOutBun: this.#shellOutBun.bind(this),\n };\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["import type {\n ChildProcess as NodeChildProcess,\n SpawnOptions as NodeSpawnOptions,\n SpawnOptionsWithoutStdio as NodeSpawnOptionsWithoutStdio,\n SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple,\n StdioNull as NodeStdioNull,\n StdioPipe as NodeStdioPipe,\n ProcessEnvOptions,\n} from \"node:child_process\";\nimport { Readable } from \"node:stream\";\nimport { styleText } from \"node:util\";\nimport type {\n SpawnOptions as BunSpawnOptions,\n Subprocess as BunSubprocess,\n SpawnOptions,\n} from \"bun\";\nimport { Path } from \"path-class\";\nimport type { SetFieldType } from \"type-fest\";\n\nconst DEFAULT_MAIN_INDENTATION = \"\";\nconst DEFAULT_ARG_INDENTATION = \" \";\nconst DEFAULT_ARGUMENT_LINE_WRAPPING = \"by-entry\";\n\nconst INLINE_SEPARATOR = \" \";\nconst LINE_WRAP_LINE_END = \" \\\\\\n\";\n\n// biome-ignore lint/suspicious/noExplicitAny: This is the correct type nere.\nfunction isString(s: any): s is string {\n return typeof s === \"string\";\n}\n// biome-ignore lint/suspicious/noExplicitAny: This is the correct type nere.\nfunction isStringArray(entries: any[]): entries is string[] {\n for (const entry of entries) {\n if (!isString(entry)) {\n return false;\n }\n }\n return true;\n}\n\n// TODO: allow `.toString()`ables?\ntype SingleArgument = string;\ntype FlagArgumentGroup = string[];\ntype ArgsEntry = SingleArgument | FlagArgumentGroup;\ntype Args = ArgsEntry[];\n\nexport interface PrintOptions {\n /** Defaults to \"\" */\n mainIndentation?: string;\n /** Defaults to \" \" */\n argIndentation?: string;\n /**\n * - `\"auto\"`: Quote only arguments that need it for safety. This tries to be\n * portable and safe across shells, but true safety and portability is hard\n * to guarantee.\n * - `\"extra-safe\"`: Quote all arguments, even ones that don't need it. This is\n * more likely to be safe under all circumstances.\n */\n quoting?: \"auto\" | \"extra-safe\";\n /** Line wrapping to use between arguments. Defaults to `\"by-entry\"`. */\n argumentLineWrapping?:\n | \"by-entry\"\n | \"nested-by-entry\"\n | \"by-argument\"\n | \"inline\";\n /** Include the first arg (or first arg group) on the same line as the command, regardless of the `argumentLineWrapping` setting. */\n skipLineWrapBeforeFirstArg?: true | false;\n /**\n * Style text using `node`'s [`styleText(\u2026)`](https://nodejs.org/api/util.html#utilstyletextformat-text-options)\n *\n * Example usage:\n *\n * ```\n * new PrintableShellCommand(\"echo\", [\"hi\"]).print({\n * styleTextFormat: [\"gray\", \"bold\"],\n * });\n * */\n styleTextFormat?: Parameters<typeof styleText>[0];\n}\n\n// https://mywiki.wooledge.org/BashGuide/SpecialCharacters\nconst SPECIAL_SHELL_CHARACTERS = new Set([\n \" \",\n '\"',\n \"'\",\n \"`\",\n \"|\",\n \"$\",\n \"*\",\n \"?\",\n \">\",\n \"<\",\n \"(\",\n \")\",\n \"[\",\n \"]\",\n \"{\",\n \"}\",\n \"&\",\n \"\\\\\",\n \";\",\n \"#\",\n]);\n\n// https://mywiki.wooledge.org/BashGuide/SpecialCharacters\nconst SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND =\n // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)\n (SPECIAL_SHELL_CHARACTERS as unknown as any).union(new Set([\"=\"]));\n\ntype NodeCwd = ProcessEnvOptions[\"cwd\"] | Path;\ntype NodeWithCwd<T extends { cwd?: ProcessEnvOptions[\"cwd\"] }> = SetFieldType<\n T,\n \"cwd\",\n NodeCwd | undefined\n>;\n\n// biome-ignore lint/suspicious/noExplicitAny: Just matching\ntype BunCwd = SpawnOptions.OptionsObject<any, any, any>[\"cwd\"] | Path;\ntype BunWithCwd<\n // biome-ignore lint/suspicious/noExplicitAny: Just matching\n T extends { cwd?: SpawnOptions.OptionsObject<any, any, any>[\"cwd\"] },\n> = SetFieldType<T, \"cwd\", BunCwd | undefined>;\n\nexport class PrintableShellCommand {\n #commandName: string;\n constructor(\n commandName: string,\n private args: Args = [],\n ) {\n if (!isString(commandName)) {\n // biome-ignore lint/suspicious/noExplicitAny: We want to print this, no matter what it is.\n throw new Error(\"Command name is not a string:\", commandName as any);\n }\n this.#commandName = commandName;\n if (typeof args === \"undefined\") {\n return;\n }\n if (!Array.isArray(args)) {\n throw new Error(\"Command arguments are not an array\");\n }\n for (let i = 0; i < args.length; i++) {\n const argEntry = args[i];\n if (typeof argEntry === \"string\") {\n continue;\n }\n if (Array.isArray(argEntry) && isStringArray(argEntry)) {\n continue;\n }\n throw new Error(`Invalid arg entry at index: ${i}`);\n }\n }\n\n get commandName(): string {\n return this.#commandName;\n }\n\n /** For use with `bun`.\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"bun\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * await spawn(command.toFlatCommand()).exited;\n * ```\n */\n public toFlatCommand(): string[] {\n return [this.commandName, ...this.args.flat()];\n }\n\n /**\n * Convenient alias for `toFlatCommand()`.\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"bun\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * await spawn(command.forBun()).exited;\n * ```\n *\n * */\n public forBun(): string[] {\n return this.toFlatCommand();\n }\n\n /**\n * For use with `node:child_process`\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"node:child_process\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * const child_process = spawn(...command.toCommandWithFlatArgs()); // Note the `...`\n * ```\n *\n */\n public toCommandWithFlatArgs(): [string, string[]] {\n return [this.commandName, this.args.flat()];\n }\n\n /**\n * For use with `node:child_process`\n *\n * Usage example:\n *\n * ```\n * import { PrintableShellCommand } from \"printable-shell-command\";\n * import { spawn } from \"node:child_process\";\n *\n * const command = new PrintableShellCommand( \u2026 );\n * const child_process = spawn(...command.forNode()); // Note the `...`\n * ```\n *\n * Convenient alias for `toCommandWithFlatArgs()`.\n */\n public forNode(): [string, string[]] {\n return this.toCommandWithFlatArgs();\n }\n\n #escapeArg(\n arg: string,\n isMainCommand: boolean,\n options: PrintOptions,\n ): string {\n const argCharacters = new Set(arg);\n const specialShellCharacters = isMainCommand\n ? SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND\n : SPECIAL_SHELL_CHARACTERS;\n if (\n options?.quoting === \"extra-safe\" ||\n // biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)\n (argCharacters as unknown as any).intersection(specialShellCharacters)\n .size > 0\n ) {\n // Use single quote to reduce the need to escape (and therefore reduce the chance for bugs/security issues).\n const escaped = arg.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"'\", \"\\\\'\");\n return `'${escaped}'`;\n }\n return arg;\n }\n\n #mainIndentation(options: PrintOptions): string {\n return options?.mainIndentation ?? DEFAULT_MAIN_INDENTATION;\n }\n\n #argIndentation(options: PrintOptions): string {\n return (\n this.#mainIndentation(options) +\n (options?.argIndentation ?? DEFAULT_ARG_INDENTATION)\n );\n }\n\n #lineWrapSeparator(options: PrintOptions): string {\n return LINE_WRAP_LINE_END + this.#argIndentation(options);\n }\n\n #argPairSeparator(options: PrintOptions): string {\n switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n case \"by-entry\": {\n return INLINE_SEPARATOR;\n }\n case \"nested-by-entry\": {\n return this.#lineWrapSeparator(options) + this.#argIndentation(options);\n }\n case \"by-argument\": {\n return this.#lineWrapSeparator(options);\n }\n case \"inline\": {\n return INLINE_SEPARATOR;\n }\n default:\n throw new Error(\"Invalid argument line wrapping argument.\");\n }\n }\n\n #intraEntrySeparator(options: PrintOptions): string {\n switch (options?.argumentLineWrapping ?? DEFAULT_ARGUMENT_LINE_WRAPPING) {\n case \"by-entry\":\n case \"nested-by-entry\":\n case \"by-argument\": {\n return LINE_WRAP_LINE_END + this.#argIndentation(options);\n }\n case \"inline\": {\n return INLINE_SEPARATOR;\n }\n default:\n throw new Error(\"Invalid argument line wrapping argument.\");\n }\n }\n\n #separatorAfterCommand(\n options: PrintOptions,\n numFollowingEntries: number,\n ): string {\n if (numFollowingEntries === 0) {\n return \"\";\n }\n if (options.skipLineWrapBeforeFirstArg ?? false) {\n return INLINE_SEPARATOR;\n }\n return this.#intraEntrySeparator(options);\n }\n\n public getPrintableCommand(options?: PrintOptions): string {\n // TODO: Why in the world does TypeScript not give the `options` arg the type of `PrintOptions | undefined`???\n options ??= {};\n const serializedEntries: string[] = [];\n\n for (let i = 0; i < this.args.length; i++) {\n const argsEntry = this.args[i];\n\n if (isString(argsEntry)) {\n serializedEntries.push(this.#escapeArg(argsEntry, false, options));\n } else {\n serializedEntries.push(\n argsEntry\n .map((part) => this.#escapeArg(part, false, options))\n .join(this.#argPairSeparator(options)),\n );\n }\n }\n\n let text =\n this.#mainIndentation(options) +\n this.#escapeArg(this.commandName, true, options) +\n this.#separatorAfterCommand(options, serializedEntries.length) +\n serializedEntries.join(this.#intraEntrySeparator(options));\n if (options?.styleTextFormat) {\n text = styleText(options.styleTextFormat, text);\n }\n return text;\n }\n\n public print(options?: PrintOptions): PrintableShellCommand {\n console.log(this.getPrintableCommand(options));\n return this;\n }\n\n /**\n * The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313\n */\n public spawn<\n Stdin extends NodeStdioNull | NodeStdioPipe,\n Stdout extends NodeStdioNull | NodeStdioPipe,\n Stderr extends NodeStdioNull | NodeStdioPipe,\n >(\n options?:\n | NodeWithCwd<NodeSpawnOptions>\n | NodeWithCwd<NodeSpawnOptionsWithoutStdio>\n | NodeWithCwd<NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>>,\n ): // TODO: figure out how to return `ChildProcessByStdio<\u2026>` without duplicating fragile boilerplate.\n NodeChildProcess & { success: Promise<void> } {\n const { spawn } = process.getBuiltinModule(\"node:child_process\");\n const cwd = (() => {\n if (typeof options?.cwd !== \"undefined\") {\n if (options.cwd instanceof Path) {\n return options.cwd.toString();\n }\n }\n return options?.cwd;\n })();\n // biome-ignore lint/suspicious/noTsIgnore: We don't want linting to depend on *broken* type checking.\n // @ts-ignore: The TypeScript checker has trouble reconciling the optional (i.e. potentially `undefined`) `options` with the third argument.\n const subprocess = spawn(...this.forNode(), {\n ...options,\n cwd,\n }) as NodeChildProcess & {\n success: Promise<void>;\n };\n Object.defineProperty(subprocess, \"success\", {\n get() {\n return new Promise<void>((resolve, reject) =>\n this.addListener(\n \"exit\",\n (exitCode: number /* we only use the first arg */) => {\n if (exitCode === 0) {\n resolve();\n } else {\n reject(`Command failed with non-zero exit code: ${exitCode}`);\n }\n },\n ),\n );\n },\n enumerable: false,\n });\n return subprocess;\n }\n\n /** A wrapper for `.spawnNode(\u2026)` that sets stdio to `\"inherit\"` (common for\n * invoking commands from scripts whose output and interaction should be\n * surfaced to the user). */\n public spawnInherit(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): NodeChildProcess & { success: Promise<void> } {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n return this.spawn({ ...options, stdio: \"inherit\" });\n }\n\n public stdout(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Response {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n const subprocess = this.spawn({\n ...options,\n stdio: [\"ignore\", \"pipe\", \"inherit\"],\n });\n\n // biome-ignore lint/style/noNonNullAssertion: dude\n return new Response(Readable.toWeb(subprocess.stdout!));\n }\n\n /** Equivalent to:\n *\n * ```\n * await this.print().spawnInherit(\u2026).success;\n * ```\n */\n public async shellOut(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<void> {\n await this.print().spawnInherit(options).success;\n }\n\n /**\n * The returned subprocess includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313\n */\n #spawnBun<\n const In extends BunSpawnOptions.Writable = \"ignore\",\n const Out extends BunSpawnOptions.Readable = \"pipe\",\n const Err extends BunSpawnOptions.Readable = \"inherit\",\n >(\n options?: BunWithCwd<\n Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, \"cmd\">\n >,\n ): BunSubprocess<In, Out, Err> & { success: Promise<void> } {\n if (options && \"cmd\" in options) {\n throw new Error(\"Unexpected `cmd` field.\");\n }\n const { spawn } = process.getBuiltinModule(\"bun\") as typeof import(\"bun\");\n const cwd = (() => {\n if (typeof options?.cwd !== \"undefined\") {\n if (options.cwd instanceof Path) {\n return options.cwd.toString();\n }\n }\n return options?.cwd;\n })();\n const subprocess = spawn({\n ...options,\n cwd,\n cmd: this.forBun(),\n }) as BunSubprocess<In, Out, Err> & { success: Promise<void> };\n Object.defineProperty(subprocess, \"success\", {\n get() {\n return new Promise<void>((resolve, reject) =>\n this.exited\n .then((exitCode: number) => {\n if (exitCode === 0) {\n resolve();\n } else {\n reject(\n new Error(\n `Command failed with non-zero exit code: ${exitCode}`,\n ),\n );\n }\n })\n .catch(reject),\n );\n },\n enumerable: false,\n });\n return subprocess;\n }\n\n #spawnBunInherit(\n options?: BunWithCwd<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\" | \"stdio\"\n >\n >,\n ): BunSubprocess<\"inherit\", \"inherit\", \"inherit\"> & {\n success: Promise<void>;\n } {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n return this.bun.spawnBun({\n ...options,\n stdio: [\"inherit\", \"inherit\", \"inherit\"],\n });\n }\n\n #spawnBunStdout(\n options?: BunWithCwd<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\" | \"stdio\"\n >\n >,\n ): Response {\n // biome-ignore lint/suspicious/noExplicitAny: Avoid breaking the lib check when used without `@types/bun`.\n return new Response((this.bun.spawnBun(options) as any).stdout);\n }\n\n async #shellOutBun(\n options?: BunWithCwd<\n Omit<\n BunSpawnOptions.OptionsObject<\"inherit\", \"inherit\", \"inherit\">,\n \"cmd\" | \"stdio\"\n >\n >,\n ): Promise<void> {\n await this.print().bun.spawnBunInherit(options).success;\n }\n\n bun = {\n /** Equivalent to:\n *\n * ```\n * await this.print().bun.spawnBunInherit(\u2026).success;\n * ```\n */\n spawnBun: this.#spawnBun.bind(this),\n /**\n * A wrapper for `.spawnBunInherit(\u2026)` that sets stdio to `\"inherit\"` (common\n * for invoking commands from scripts whose output and interaction should be\n * surfaced to the user).\n */\n spawnBunInherit: this.#spawnBunInherit.bind(this),\n /** Equivalent to:\n *\n * ```\n * new Response(this.bun.spawnBun(\u2026).stdout);\n * ```\n */\n spawnBunStdout: this.#spawnBunStdout.bind(this),\n shellOutBun: this.#shellOutBun.bind(this),\n };\n}\n"],
|
|
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": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"main": "./src/index.ts",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@biomejs/biome": "^2.1.4",
|
|
@@ -27,5 +27,9 @@
|
|
|
27
27
|
"type": "module",
|
|
28
28
|
"scripts": {
|
|
29
29
|
"prepublishOnly": "make prepublishOnly"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"path-class": "^0.3.1",
|
|
33
|
+
"type-fest": "^5.1.0"
|
|
30
34
|
}
|
|
31
35
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,13 +5,17 @@ import type {
|
|
|
5
5
|
SpawnOptionsWithStdioTuple as NodeSpawnOptionsWithStdioTuple,
|
|
6
6
|
StdioNull as NodeStdioNull,
|
|
7
7
|
StdioPipe as NodeStdioPipe,
|
|
8
|
+
ProcessEnvOptions,
|
|
8
9
|
} from "node:child_process";
|
|
9
10
|
import { Readable } from "node:stream";
|
|
10
11
|
import { styleText } from "node:util";
|
|
11
12
|
import type {
|
|
12
13
|
SpawnOptions as BunSpawnOptions,
|
|
13
14
|
Subprocess as BunSubprocess,
|
|
15
|
+
SpawnOptions,
|
|
14
16
|
} from "bun";
|
|
17
|
+
import { Path } from "path-class";
|
|
18
|
+
import type { SetFieldType } from "type-fest";
|
|
15
19
|
|
|
16
20
|
const DEFAULT_MAIN_INDENTATION = "";
|
|
17
21
|
const DEFAULT_ARG_INDENTATION = " ";
|
|
@@ -103,6 +107,20 @@ const SPECIAL_SHELL_CHARACTERS_FOR_MAIN_COMMAND =
|
|
|
103
107
|
// biome-ignore lint/suspicious/noExplicitAny: Workaround to make this package easier to use in a project that otherwise only uses ES2022.)
|
|
104
108
|
(SPECIAL_SHELL_CHARACTERS as unknown as any).union(new Set(["="]));
|
|
105
109
|
|
|
110
|
+
type NodeCwd = ProcessEnvOptions["cwd"] | Path;
|
|
111
|
+
type NodeWithCwd<T extends { cwd?: ProcessEnvOptions["cwd"] }> = SetFieldType<
|
|
112
|
+
T,
|
|
113
|
+
"cwd",
|
|
114
|
+
NodeCwd | undefined
|
|
115
|
+
>;
|
|
116
|
+
|
|
117
|
+
// biome-ignore lint/suspicious/noExplicitAny: Just matching
|
|
118
|
+
type BunCwd = SpawnOptions.OptionsObject<any, any, any>["cwd"] | Path;
|
|
119
|
+
type BunWithCwd<
|
|
120
|
+
// biome-ignore lint/suspicious/noExplicitAny: Just matching
|
|
121
|
+
T extends { cwd?: SpawnOptions.OptionsObject<any, any, any>["cwd"] },
|
|
122
|
+
> = SetFieldType<T, "cwd", BunCwd | undefined>;
|
|
123
|
+
|
|
106
124
|
export class PrintableShellCommand {
|
|
107
125
|
#commandName: string;
|
|
108
126
|
constructor(
|
|
@@ -335,15 +353,26 @@ export class PrintableShellCommand {
|
|
|
335
353
|
Stderr extends NodeStdioNull | NodeStdioPipe,
|
|
336
354
|
>(
|
|
337
355
|
options?:
|
|
338
|
-
| NodeSpawnOptions
|
|
339
|
-
| NodeSpawnOptionsWithoutStdio
|
|
340
|
-
| NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr
|
|
356
|
+
| NodeWithCwd<NodeSpawnOptions>
|
|
357
|
+
| NodeWithCwd<NodeSpawnOptionsWithoutStdio>
|
|
358
|
+
| NodeWithCwd<NodeSpawnOptionsWithStdioTuple<Stdin, Stdout, Stderr>>,
|
|
341
359
|
): // TODO: figure out how to return `ChildProcessByStdio<…>` without duplicating fragile boilerplate.
|
|
342
360
|
NodeChildProcess & { success: Promise<void> } {
|
|
343
361
|
const { spawn } = process.getBuiltinModule("node:child_process");
|
|
362
|
+
const cwd = (() => {
|
|
363
|
+
if (typeof options?.cwd !== "undefined") {
|
|
364
|
+
if (options.cwd instanceof Path) {
|
|
365
|
+
return options.cwd.toString();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return options?.cwd;
|
|
369
|
+
})();
|
|
344
370
|
// biome-ignore lint/suspicious/noTsIgnore: We don't want linting to depend on *broken* type checking.
|
|
345
371
|
// @ts-ignore: The TypeScript checker has trouble reconciling the optional (i.e. potentially `undefined`) `options` with the third argument.
|
|
346
|
-
const subprocess = spawn(...this.forNode(),
|
|
372
|
+
const subprocess = spawn(...this.forNode(), {
|
|
373
|
+
...options,
|
|
374
|
+
cwd,
|
|
375
|
+
}) as NodeChildProcess & {
|
|
347
376
|
success: Promise<void>;
|
|
348
377
|
};
|
|
349
378
|
Object.defineProperty(subprocess, "success", {
|
|
@@ -370,7 +399,7 @@ export class PrintableShellCommand {
|
|
|
370
399
|
* invoking commands from scripts whose output and interaction should be
|
|
371
400
|
* surfaced to the user). */
|
|
372
401
|
public spawnInherit(
|
|
373
|
-
options?: Omit<NodeSpawnOptions, "stdio"
|
|
402
|
+
options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
|
|
374
403
|
): NodeChildProcess & { success: Promise<void> } {
|
|
375
404
|
if (options && "stdio" in options) {
|
|
376
405
|
throw new Error("Unexpected `stdio` field.");
|
|
@@ -378,7 +407,9 @@ export class PrintableShellCommand {
|
|
|
378
407
|
return this.spawn({ ...options, stdio: "inherit" });
|
|
379
408
|
}
|
|
380
409
|
|
|
381
|
-
public stdout(
|
|
410
|
+
public stdout(
|
|
411
|
+
options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
|
|
412
|
+
): Response {
|
|
382
413
|
if (options && "stdio" in options) {
|
|
383
414
|
throw new Error("Unexpected `stdio` field.");
|
|
384
415
|
}
|
|
@@ -398,7 +429,7 @@ export class PrintableShellCommand {
|
|
|
398
429
|
* ```
|
|
399
430
|
*/
|
|
400
431
|
public async shellOut(
|
|
401
|
-
options?: Omit<NodeSpawnOptions, "stdio"
|
|
432
|
+
options?: NodeWithCwd<Omit<NodeSpawnOptions, "stdio">>,
|
|
402
433
|
): Promise<void> {
|
|
403
434
|
await this.print().spawnInherit(options).success;
|
|
404
435
|
}
|
|
@@ -411,14 +442,25 @@ export class PrintableShellCommand {
|
|
|
411
442
|
const Out extends BunSpawnOptions.Readable = "pipe",
|
|
412
443
|
const Err extends BunSpawnOptions.Readable = "inherit",
|
|
413
444
|
>(
|
|
414
|
-
options?:
|
|
445
|
+
options?: BunWithCwd<
|
|
446
|
+
Omit<BunSpawnOptions.OptionsObject<In, Out, Err>, "cmd">
|
|
447
|
+
>,
|
|
415
448
|
): BunSubprocess<In, Out, Err> & { success: Promise<void> } {
|
|
416
449
|
if (options && "cmd" in options) {
|
|
417
450
|
throw new Error("Unexpected `cmd` field.");
|
|
418
451
|
}
|
|
419
452
|
const { spawn } = process.getBuiltinModule("bun") as typeof import("bun");
|
|
453
|
+
const cwd = (() => {
|
|
454
|
+
if (typeof options?.cwd !== "undefined") {
|
|
455
|
+
if (options.cwd instanceof Path) {
|
|
456
|
+
return options.cwd.toString();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return options?.cwd;
|
|
460
|
+
})();
|
|
420
461
|
const subprocess = spawn({
|
|
421
462
|
...options,
|
|
463
|
+
cwd,
|
|
422
464
|
cmd: this.forBun(),
|
|
423
465
|
}) as BunSubprocess<In, Out, Err> & { success: Promise<void> };
|
|
424
466
|
Object.defineProperty(subprocess, "success", {
|
|
@@ -445,12 +487,11 @@ export class PrintableShellCommand {
|
|
|
445
487
|
}
|
|
446
488
|
|
|
447
489
|
#spawnBunInherit(
|
|
448
|
-
options?:
|
|
490
|
+
options?: BunWithCwd<
|
|
449
491
|
Omit<
|
|
450
492
|
BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
|
|
451
|
-
"cmd"
|
|
452
|
-
|
|
453
|
-
"stdio"
|
|
493
|
+
"cmd" | "stdio"
|
|
494
|
+
>
|
|
454
495
|
>,
|
|
455
496
|
): BunSubprocess<"inherit", "inherit", "inherit"> & {
|
|
456
497
|
success: Promise<void>;
|
|
@@ -465,12 +506,11 @@ export class PrintableShellCommand {
|
|
|
465
506
|
}
|
|
466
507
|
|
|
467
508
|
#spawnBunStdout(
|
|
468
|
-
options?:
|
|
509
|
+
options?: BunWithCwd<
|
|
469
510
|
Omit<
|
|
470
511
|
BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
|
|
471
|
-
"cmd"
|
|
472
|
-
|
|
473
|
-
"stdio"
|
|
512
|
+
"cmd" | "stdio"
|
|
513
|
+
>
|
|
474
514
|
>,
|
|
475
515
|
): Response {
|
|
476
516
|
// biome-ignore lint/suspicious/noExplicitAny: Avoid breaking the lib check when used without `@types/bun`.
|
|
@@ -478,12 +518,11 @@ export class PrintableShellCommand {
|
|
|
478
518
|
}
|
|
479
519
|
|
|
480
520
|
async #shellOutBun(
|
|
481
|
-
options?:
|
|
521
|
+
options?: BunWithCwd<
|
|
482
522
|
Omit<
|
|
483
523
|
BunSpawnOptions.OptionsObject<"inherit", "inherit", "inherit">,
|
|
484
|
-
"cmd"
|
|
485
|
-
|
|
486
|
-
"stdio"
|
|
524
|
+
"cmd" | "stdio"
|
|
525
|
+
>
|
|
487
526
|
>,
|
|
488
527
|
): Promise<void> {
|
|
489
528
|
await this.print().bun.spawnBunInherit(options).success;
|