printable-shell-command 2.7.1 → 2.7.2
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.
|
@@ -133,7 +133,6 @@ export declare class PrintableShellCommand {
|
|
|
133
133
|
/**
|
|
134
134
|
* The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
|
|
135
135
|
*/
|
|
136
|
-
/** @ts-expect-error Type wrangling. */
|
|
137
136
|
spawn: typeof spawnType;
|
|
138
137
|
/** A wrapper for `.spawn(…)` that sets stdio to `"inherit"` (common for
|
|
139
138
|
* invoking commands from scripts whose output and interaction should be
|
|
@@ -250,7 +250,8 @@ var PrintableShellCommand = class {
|
|
|
250
250
|
/**
|
|
251
251
|
* The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
|
|
252
252
|
*/
|
|
253
|
-
|
|
253
|
+
// biome-ignore lint/suspicious/noTsIgnore: `@ts-expect-error` would end up in the published package, where it is invalid.
|
|
254
|
+
// @ts-ignore-error Wrangling types.
|
|
254
255
|
spawn = (options) => {
|
|
255
256
|
const { spawn } = process.getBuiltinModule("node:child_process");
|
|
256
257
|
const cwd = stringifyIfPath(options?.cwd);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n ChildProcessByStdio,\n ChildProcess as NodeChildProcess,\n SpawnOptions as NodeSpawnOptions,\n} from \"node:child_process\";\nimport { stderr } from \"node:process\";\nimport { Readable, Writable } from \"node:stream\";\nimport type { WriteStream } from \"node:tty\";\nimport { styleText } from \"node:util\";\nimport type {\n SpawnOptions as BunSpawnOptions,\n Subprocess as BunSubprocess,\n SpawnOptions,\n} from \"bun\";\nimport { Path, stringifyIfPath } from \"path-class\";\nimport type { SetFieldType } from \"type-fest\";\nimport type { NodeCwd, NodeWithCwd, spawnType, WithSuccess } from \"./spawn\";\n\n// TODO: does this import work?\n/**\n * @import { stdout } from \"node:process\"\n */\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\ntype StyleTextFormat = Parameters<typeof styleText>[0];\n\nconst TTY_AUTO_STYLE: StyleTextFormat = [\"gray\", \"bold\"];\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// biome-ignore lint/suspicious/noExplicitAny: This is the correct type here.\nfunction isValidArgsEntryArray(entries: any[]): entries is SingleArgument[] {\n for (const entry of entries) {\n if (isString(entry)) {\n continue;\n }\n if (entry instanceof Path) {\n continue;\n }\n return false;\n }\n return true;\n}\n\n// TODO: allow `.toString()`ables?\ntype SingleArgument = string | Path;\ntype ArgsEntry = SingleArgument | SingleArgument[];\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 {@link styleText | `styleText(\u2026)`}.\n *\n * Example usage:\n *\n * ```\n * new PrintableShellCommand(\"echo\", [\"hi\"]).print({\n * styleTextFormat: [\"green\", \"underline\"],\n * });\n * */\n styleTextFormat?: StyleTextFormat;\n}\n\nexport interface StreamPrintOptions extends PrintOptions {\n /**\n * Auto-style the text when:\n *\n * - the output stream is detected to be a TTY\n * - `styleTextFormat` is not specified.\n *\n * The current auto style is: `[\"gray\", \"bold\"]`\n */\n autoStyle?: \"tty\" | \"never\";\n // This would be a `WritableStream` (open web standard), but `WriteStream` allows us to query `.isTTY`.\n stream?: WriteStream | Writable;\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\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\"] | Path },\n> = SetFieldType<T, \"cwd\", BunCwd | undefined>;\n\nexport class PrintableShellCommand {\n #commandName: string | Path;\n constructor(\n commandName: string | Path,\n private args: Args = [],\n ) {\n if (!isString(commandName) && !(commandName instanceof Path)) {\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 (argEntry instanceof Path) {\n continue;\n }\n if (Array.isArray(argEntry) && isValidArgsEntryArray(argEntry)) {\n continue;\n }\n throw new Error(`Invalid arg entry at index: ${i}`);\n }\n }\n\n get commandName(): string {\n return stringifyIfPath(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().map(stringifyIfPath)];\n }\n\n /**\n * Convenient alias for {@link PrintableShellCommand.toFlatCommand | `.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().map(stringifyIfPath)];\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 {@link PrintableShellCommand.toCommandWithFlatArgs | `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 = stringifyIfPath(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) =>\n this.#escapeArg(stringifyIfPath(part), false, options),\n )\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 /**\n * Print the shell command to {@link stderr} (default) or a specified stream.\n *\n * By default, this will be auto-styled (as bold gray) when `.isTTY` is true\n * for the stream. `.isTTY` is populated for the {@link stderr} and\n * {@link stdout} objects. Pass `\"autoStyle\": \"never\"` or an explicit\n * `styleTextFormat` to disable this.\n *\n */\n public print(options?: StreamPrintOptions): PrintableShellCommand {\n const stream = options?.stream ?? stderr;\n // Note: we only need to modify top-level fields, so `structuredClone(\u2026)`\n // would be overkill and can only cause performance issues.\n const optionsCopy = { ...options };\n optionsCopy.styleTextFormat ??=\n options?.autoStyle !== \"never\" &&\n (stream as { isTTY?: boolean }).isTTY === true\n ? TTY_AUTO_STYLE\n : undefined;\n const writable =\n stream instanceof Writable ? stream : Writable.fromWeb(stream);\n writable.write(this.getPrintableCommand(optionsCopy));\n writable.write(\"\\n\");\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 /** @ts-expect-error Type wrangling. */\n public spawn: typeof spawnType = (options?: SpawnOptions & NodeCwd) => {\n const { spawn } = process.getBuiltinModule(\"node:child_process\");\n const cwd = stringifyIfPath(options?.cwd);\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 as object),\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 `.spawn(\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 *\n * If there is no other interaction with the shell from the calling process,\n * then it acts \"transparent\" and allows user to interact with the subprocess\n * in its stead.\n */\n public spawnTransparently(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): ChildProcessByStdio<null, null, null> & WithSuccess {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Type wrangling.\n return this.spawn({ ...options, stdio: \"inherit\" }) as any;\n }\n\n /** @deprecated: Use `.spawnTransparently(\u2026)`. */\n public spawnInherit(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): NodeChildProcess & WithSuccess {\n return this.spawnTransparently(options);\n }\n\n /**\n * A wrapper for {@link PrintableShellCommand.spawn | `.spawn(\u2026)`} that:\n *\n * - sets `detached` to `true`,\n * - sets stdio to `\"inherit\"`,\n * - calls `.unref()`, and\n * - does not wait for the process to exit.\n *\n * This is similar to starting a command int he background and disowning it (in a shell).\n *\n * The `stdio` field is left overridable. To capture `stdout` and `stderr`, connect them to output files like this:\n *\n * ```\n * import { open } from \"node:fs/promises\";\n * import { Path } from \"path-class\";\n * import { PrintableShellCommand } from \"printable-shell-command\";\n *\n * const tempDir = await Path.makeTempDir();\n * console.log(`Temp dir: ${tempDir}`);\n * const stdout = await open(tempDir.join(\"stdout.log\").path, \"a\");\n * const stderr = await open(tempDir.join(\"stderr.log\").path, \"a\");\n *\n * new PrintableShellCommand(\"echo\", [\"hi\"]).spawnDetached({\n * stdio: [\"ignore\", stdout.fd, stderr.fd],\n * });\n * ```\n *\n */\n public spawnDetached(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"detached\">>,\n ): void {\n if (options && \"detached\" in options) {\n throw new Error(\"Unexpected `detached` field.\");\n }\n const childProcess = this.spawn({\n stdio: \"ignore\",\n ...options,\n detached: true,\n });\n childProcess.unref();\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 /**\n * Convenience function for:\n *\n * .stdout(options).text()\n *\n * This can make some simple invocations easier to read and/or fit on a single line.\n */\n public text(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<string> {\n return this.stdout(options).text();\n }\n\n /**\n * Convenience function for:\n *\n * .stdout(options).json()\n *\n * This can make some simple invocations easier to read and/or fit on a single line.\n */\n public json<T>(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<T> {\n return this.stdout(options).json() as Promise<T>;\n }\n\n /** Equivalent to:\n *\n * ```\n * await this.print().spawnTransparently(\u2026).success;\n * ```\n */\n public async shellOut(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<void> {\n await this.print().spawnTransparently(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 = stringifyIfPath(options?.cwd);\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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
|
|
4
|
+
"sourcesContent": ["import type {\n ChildProcessByStdio,\n ChildProcess as NodeChildProcess,\n SpawnOptions as NodeSpawnOptions,\n} from \"node:child_process\";\nimport { stderr } from \"node:process\";\nimport { Readable, Writable } from \"node:stream\";\nimport type { WriteStream } from \"node:tty\";\nimport { styleText } from \"node:util\";\nimport type {\n SpawnOptions as BunSpawnOptions,\n Subprocess as BunSubprocess,\n SpawnOptions,\n} from \"bun\";\nimport { Path, stringifyIfPath } from \"path-class\";\nimport type { SetFieldType } from \"type-fest\";\nimport type { NodeWithCwd, spawnType, WithSuccess } from \"./spawn\";\n\n// TODO: does this import work?\n/**\n * @import { stdout } from \"node:process\"\n */\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\ntype StyleTextFormat = Parameters<typeof styleText>[0];\n\nconst TTY_AUTO_STYLE: StyleTextFormat = [\"gray\", \"bold\"];\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// biome-ignore lint/suspicious/noExplicitAny: This is the correct type here.\nfunction isValidArgsEntryArray(entries: any[]): entries is SingleArgument[] {\n for (const entry of entries) {\n if (isString(entry)) {\n continue;\n }\n if (entry instanceof Path) {\n continue;\n }\n return false;\n }\n return true;\n}\n\n// TODO: allow `.toString()`ables?\ntype SingleArgument = string | Path;\ntype ArgsEntry = SingleArgument | SingleArgument[];\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 {@link styleText | `styleText(\u2026)`}.\n *\n * Example usage:\n *\n * ```\n * new PrintableShellCommand(\"echo\", [\"hi\"]).print({\n * styleTextFormat: [\"green\", \"underline\"],\n * });\n * */\n styleTextFormat?: StyleTextFormat;\n}\n\nexport interface StreamPrintOptions extends PrintOptions {\n /**\n * Auto-style the text when:\n *\n * - the output stream is detected to be a TTY\n * - `styleTextFormat` is not specified.\n *\n * The current auto style is: `[\"gray\", \"bold\"]`\n */\n autoStyle?: \"tty\" | \"never\";\n // This would be a `WritableStream` (open web standard), but `WriteStream` allows us to query `.isTTY`.\n stream?: WriteStream | Writable;\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\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\"] | Path },\n> = SetFieldType<T, \"cwd\", BunCwd | undefined>;\n\nexport class PrintableShellCommand {\n #commandName: string | Path;\n constructor(\n commandName: string | Path,\n private args: Args = [],\n ) {\n if (!isString(commandName) && !(commandName instanceof Path)) {\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 (argEntry instanceof Path) {\n continue;\n }\n if (Array.isArray(argEntry) && isValidArgsEntryArray(argEntry)) {\n continue;\n }\n throw new Error(`Invalid arg entry at index: ${i}`);\n }\n }\n\n get commandName(): string {\n return stringifyIfPath(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().map(stringifyIfPath)];\n }\n\n /**\n * Convenient alias for {@link PrintableShellCommand.toFlatCommand | `.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().map(stringifyIfPath)];\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 {@link PrintableShellCommand.toCommandWithFlatArgs | `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 = stringifyIfPath(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) =>\n this.#escapeArg(stringifyIfPath(part), false, options),\n )\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 /**\n * Print the shell command to {@link stderr} (default) or a specified stream.\n *\n * By default, this will be auto-styled (as bold gray) when `.isTTY` is true\n * for the stream. `.isTTY` is populated for the {@link stderr} and\n * {@link stdout} objects. Pass `\"autoStyle\": \"never\"` or an explicit\n * `styleTextFormat` to disable this.\n *\n */\n public print(options?: StreamPrintOptions): PrintableShellCommand {\n const stream = options?.stream ?? stderr;\n // Note: we only need to modify top-level fields, so `structuredClone(\u2026)`\n // would be overkill and can only cause performance issues.\n const optionsCopy = { ...options };\n optionsCopy.styleTextFormat ??=\n options?.autoStyle !== \"never\" &&\n (stream as { isTTY?: boolean }).isTTY === true\n ? TTY_AUTO_STYLE\n : undefined;\n const writable =\n stream instanceof Writable ? stream : Writable.fromWeb(stream);\n writable.write(this.getPrintableCommand(optionsCopy));\n writable.write(\"\\n\");\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 // biome-ignore lint/suspicious/noTsIgnore: `@ts-expect-error` would end up in the published package, where it is invalid.\n // @ts-ignore-error Wrangling types.\n public spawn: typeof spawnType = (\n options?: Parameters<typeof spawnType>[0],\n ) => {\n const { spawn } = process.getBuiltinModule(\"node:child_process\");\n const cwd = stringifyIfPath(options?.cwd);\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 as object),\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 `.spawn(\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 *\n * If there is no other interaction with the shell from the calling process,\n * then it acts \"transparent\" and allows user to interact with the subprocess\n * in its stead.\n */\n public spawnTransparently(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): ChildProcessByStdio<null, null, null> & WithSuccess {\n if (options && \"stdio\" in options) {\n throw new Error(\"Unexpected `stdio` field.\");\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Type wrangling.\n return this.spawn({ ...options, stdio: \"inherit\" }) as any;\n }\n\n /** @deprecated: Use `.spawnTransparently(\u2026)`. */\n public spawnInherit(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): NodeChildProcess & WithSuccess {\n return this.spawnTransparently(options);\n }\n\n /**\n * A wrapper for {@link PrintableShellCommand.spawn | `.spawn(\u2026)`} that:\n *\n * - sets `detached` to `true`,\n * - sets stdio to `\"inherit\"`,\n * - calls `.unref()`, and\n * - does not wait for the process to exit.\n *\n * This is similar to starting a command int he background and disowning it (in a shell).\n *\n * The `stdio` field is left overridable. To capture `stdout` and `stderr`, connect them to output files like this:\n *\n * ```\n * import { open } from \"node:fs/promises\";\n * import { Path } from \"path-class\";\n * import { PrintableShellCommand } from \"printable-shell-command\";\n *\n * const tempDir = await Path.makeTempDir();\n * console.log(`Temp dir: ${tempDir}`);\n * const stdout = await open(tempDir.join(\"stdout.log\").path, \"a\");\n * const stderr = await open(tempDir.join(\"stderr.log\").path, \"a\");\n *\n * new PrintableShellCommand(\"echo\", [\"hi\"]).spawnDetached({\n * stdio: [\"ignore\", stdout.fd, stderr.fd],\n * });\n * ```\n *\n */\n public spawnDetached(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"detached\">>,\n ): void {\n if (options && \"detached\" in options) {\n throw new Error(\"Unexpected `detached` field.\");\n }\n const childProcess = this.spawn({\n stdio: \"ignore\",\n ...options,\n detached: true,\n });\n childProcess.unref();\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 return new Response(Readable.toWeb(subprocess.stdout));\n }\n\n /**\n * Convenience function for:\n *\n * .stdout(options).text()\n *\n * This can make some simple invocations easier to read and/or fit on a single line.\n */\n public text(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<string> {\n return this.stdout(options).text();\n }\n\n /**\n * Convenience function for:\n *\n * .stdout(options).json()\n *\n * This can make some simple invocations easier to read and/or fit on a single line.\n */\n public json<T>(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<T> {\n return this.stdout(options).json() as Promise<T>;\n }\n\n /** Equivalent to:\n *\n * ```\n * await this.print().spawnTransparently(\u2026).success;\n * ```\n */\n public async shellOut(\n options?: NodeWithCwd<Omit<NodeSpawnOptions, \"stdio\">>,\n ): Promise<void> {\n await this.print().spawnTransparently(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 = stringifyIfPath(options?.cwd);\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": ";AAKA,SAAS,cAAc;AACvB,SAAS,UAAU,gBAAgB;AAEnC,SAAS,iBAAiB;AAM1B,SAAS,MAAM,uBAAuB;AAStC,IAAM,2BAA2B;AACjC,IAAM,0BAA0B;AAChC,IAAM,iCAAiC;AAEvC,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAI3B,IAAM,iBAAkC,CAAC,QAAQ,MAAM;AAGvD,SAAS,SAAS,GAAqB;AACrC,SAAO,OAAO,MAAM;AACtB;AAGA,SAAS,sBAAsB,SAA6C;AAC1E,aAAW,SAAS,SAAS;AAC3B,QAAI,SAAS,KAAK,GAAG;AACnB;AAAA,IACF;AACA,QAAI,iBAAiB,MAAM;AACzB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAwDA,IAAM,2BAA2B,oBAAI,IAAI;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,IAAM;AAAA;AAAA,EAEH,yBAA4C,MAAM,oBAAI,IAAI,CAAC,GAAG,CAAC,CAAC;AAAA;AAS5D,IAAM,wBAAN,MAA4B;AAAA,EAEjC,YACE,aACQ,OAAa,CAAC,GACtB;AADQ;AAER,QAAI,CAAC,SAAS,WAAW,KAAK,EAAE,uBAAuB,OAAO;AAE5D,YAAM,IAAI,MAAM,iCAAiC,WAAkB;AAAA,IACrE;AACA,SAAK,eAAe;AACpB,QAAI,OAAO,SAAS,aAAa;AAC/B;AAAA,IACF;AACA,QAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AACA,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,WAAW,KAAK,CAAC;AACvB,UAAI,OAAO,aAAa,UAAU;AAChC;AAAA,MACF;AACA,UAAI,oBAAoB,MAAM;AAC5B;AAAA,MACF;AACA,UAAI,MAAM,QAAQ,QAAQ,KAAK,sBAAsB,QAAQ,GAAG;AAC9D;AAAA,MACF;AACA,YAAM,IAAI,MAAM,+BAA+B,CAAC,EAAE;AAAA,IACpD;AAAA,EACF;AAAA,EA7BA;AAAA,EA+BA,IAAI,cAAsB;AACxB,WAAO,gBAAgB,KAAK,YAAY;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcO,gBAA0B;AAC/B,WAAO,CAAC,KAAK,aAAa,GAAG,KAAK,KAAK,KAAK,EAAE,IAAI,eAAe,CAAC;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBO,SAAmB;AACxB,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBO,wBAA4C;AACjD,WAAO,CAAC,KAAK,aAAa,KAAK,KAAK,KAAK,EAAE,IAAI,eAAe,CAAC;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBO,UAA8B;AACnC,WAAO,KAAK,sBAAsB;AAAA,EACpC;AAAA,EAEA,WACE,KACA,eACA,SACQ;AACR,UAAM,gBAAgB,IAAI,IAAI,GAAG;AACjC,UAAM,yBAAyB,gBAC3B,4CACA;AACJ,QACE,SAAS,YAAY;AAAA,IAEpB,cAAiC,aAAa,sBAAsB,EAClE,OAAO,GACV;AAEA,YAAM,UAAU,IAAI,WAAW,MAAM,MAAM,EAAE,WAAW,KAAK,KAAK;AAClE,aAAO,IAAI,OAAO;AAAA,IACpB;AACA,WAAO;AAAA,EACT;AAAA,EAEA,iBAAiB,SAA+B;AAC9C,WAAO,SAAS,mBAAmB;AAAA,EACrC;AAAA,EAEA,gBAAgB,SAA+B;AAC7C,WACE,KAAK,iBAAiB,OAAO,KAC5B,SAAS,kBAAkB;AAAA,EAEhC;AAAA,EAEA,mBAAmB,SAA+B;AAChD,WAAO,qBAAqB,KAAK,gBAAgB,OAAO;AAAA,EAC1D;AAAA,EAEA,kBAAkB,SAA+B;AAC/C,YAAQ,SAAS,wBAAwB,gCAAgC;AAAA,MACvE,KAAK,YAAY;AACf,eAAO;AAAA,MACT;AAAA,MACA,KAAK,mBAAmB;AACtB,eAAO,KAAK,mBAAmB,OAAO,IAAI,KAAK,gBAAgB,OAAO;AAAA,MACxE;AAAA,MACA,KAAK,eAAe;AAClB,eAAO,KAAK,mBAAmB,OAAO;AAAA,MACxC;AAAA,MACA,KAAK,UAAU;AACb,eAAO;AAAA,MACT;AAAA,MACA;AACE,cAAM,IAAI,MAAM,0CAA0C;AAAA,IAC9D;AAAA,EACF;AAAA,EAEA,qBAAqB,SAA+B;AAClD,YAAQ,SAAS,wBAAwB,gCAAgC;AAAA,MACvE,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,eAAe;AAClB,eAAO,qBAAqB,KAAK,gBAAgB,OAAO;AAAA,MAC1D;AAAA,MACA,KAAK,UAAU;AACb,eAAO;AAAA,MACT;AAAA,MACA;AACE,cAAM,IAAI,MAAM,0CAA0C;AAAA,IAC9D;AAAA,EACF;AAAA,EAEA,uBACE,SACA,qBACQ;AACR,QAAI,wBAAwB,GAAG;AAC7B,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,8BAA8B,OAAO;AAC/C,aAAO;AAAA,IACT;AACA,WAAO,KAAK,qBAAqB,OAAO;AAAA,EAC1C;AAAA,EAEO,oBAAoB,SAAgC;AAEzD,gBAAY,CAAC;AACb,UAAM,oBAA8B,CAAC;AAErC,aAAS,IAAI,GAAG,IAAI,KAAK,KAAK,QAAQ,KAAK;AACzC,YAAM,YAAY,gBAAgB,KAAK,KAAK,CAAC,CAAC;AAE9C,UAAI,SAAS,SAAS,GAAG;AACvB,0BAAkB,KAAK,KAAK,WAAW,WAAW,OAAO,OAAO,CAAC;AAAA,MACnE,OAAO;AACL,0BAAkB;AAAA,UAChB,UACG;AAAA,YAAI,CAAC,SACJ,KAAK,WAAW,gBAAgB,IAAI,GAAG,OAAO,OAAO;AAAA,UACvD,EACC,KAAK,KAAK,kBAAkB,OAAO,CAAC;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OACF,KAAK,iBAAiB,OAAO,IAC7B,KAAK,WAAW,KAAK,aAAa,MAAM,OAAO,IAC/C,KAAK,uBAAuB,SAAS,kBAAkB,MAAM,IAC7D,kBAAkB,KAAK,KAAK,qBAAqB,OAAO,CAAC;AAC3D,QAAI,SAAS,iBAAiB;AAC5B,aAAO,UAAU,QAAQ,iBAAiB,IAAI;AAAA,IAChD;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWO,MAAM,SAAqD;AAChE,UAAM,SAAS,SAAS,UAAU;AAGlC,UAAM,cAAc,EAAE,GAAG,QAAQ;AACjC,gBAAY,oBACV,SAAS,cAAc,WACtB,OAA+B,UAAU,OACtC,iBACA;AACN,UAAM,WACJ,kBAAkB,WAAW,SAAS,SAAS,QAAQ,MAAM;AAC/D,aAAS,MAAM,KAAK,oBAAoB,WAAW,CAAC;AACpD,aAAS,MAAM,IAAI;AACnB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,QAA0B,CAC/B,YACG;AACH,UAAM,EAAE,MAAM,IAAI,QAAQ,iBAAiB,oBAAoB;AAC/D,UAAM,MAAM,gBAAgB,SAAS,GAAG;AAGxC,UAAM,aAAa,MAAM,GAAG,KAAK,QAAQ,GAAG;AAAA,MAC1C,GAAI;AAAA,MACJ;AAAA,IACF,CAAC;AAGD,WAAO,eAAe,YAAY,WAAW;AAAA,MAC3C,MAAM;AACJ,eAAO,IAAI;AAAA,UAAc,CAAC,SAAS,WACjC,KAAK;AAAA,YACH;AAAA,YACA,CAAC,aAAqD;AACpD,kBAAI,aAAa,GAAG;AAClB,wBAAQ;AAAA,cACV,OAAO;AACL,uBAAO,2CAA2C,QAAQ,EAAE;AAAA,cAC9D;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,YAAY;AAAA,IACd,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUO,mBACL,SACqD;AACrD,QAAI,WAAW,WAAW,SAAS;AACjC,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAGA,WAAO,KAAK,MAAM,EAAE,GAAG,SAAS,OAAO,UAAU,CAAC;AAAA,EACpD;AAAA;AAAA,EAGO,aACL,SACgC;AAChC,WAAO,KAAK,mBAAmB,OAAO;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BO,cACL,SACM;AACN,QAAI,WAAW,cAAc,SAAS;AACpC,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AACA,UAAM,eAAe,KAAK,MAAM;AAAA,MAC9B,OAAO;AAAA,MACP,GAAG;AAAA,MACH,UAAU;AAAA,IACZ,CAAC;AACD,iBAAa,MAAM;AAAA,EACrB;AAAA,EAEO,OACL,SACU;AACV,QAAI,WAAW,WAAW,SAAS;AACjC,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AACA,UAAM,aAAa,KAAK,MAAM;AAAA,MAC5B,GAAG;AAAA,MACH,OAAO,CAAC,UAAU,QAAQ,SAAS;AAAA,IACrC,CAAC;AAED,WAAO,IAAI,SAAS,SAAS,MAAM,WAAW,MAAM,CAAC;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASO,KACL,SACiB;AACjB,WAAO,KAAK,OAAO,OAAO,EAAE,KAAK;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASO,KACL,SACY;AACZ,WAAO,KAAK,OAAO,OAAO,EAAE,KAAK;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,SACX,SACe;AACf,UAAM,KAAK,MAAM,EAAE,mBAAmB,OAAO,EAAE;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,UAKE,SAG0D;AAC1D,QAAI,WAAW,SAAS,SAAS;AAC/B,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AACA,UAAM,EAAE,MAAM,IAAI,QAAQ,iBAAiB,KAAK;AAChD,UAAM,MAAM,gBAAgB,SAAS,GAAG;AACxC,UAAM,aAAa,MAAM;AAAA,MACvB,GAAG;AAAA,MACH;AAAA,MACA,KAAK,KAAK,OAAO;AAAA,IACnB,CAAC;AACD,WAAO,eAAe,YAAY,WAAW;AAAA,MAC3C,MAAM;AACJ,eAAO,IAAI;AAAA,UAAc,CAAC,SAAS,WACjC,KAAK,OACF,KAAK,CAAC,aAAqB;AAC1B,gBAAI,aAAa,GAAG;AAClB,sBAAQ;AAAA,YACV,OAAO;AACL;AAAA,gBACE,IAAI;AAAA,kBACF,2CAA2C,QAAQ;AAAA,gBACrD;AAAA,cACF;AAAA,YACF;AAAA,UACF,CAAC,EACA,MAAM,MAAM;AAAA,QACjB;AAAA,MACF;AAAA,MACA,YAAY;AAAA,IACd,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,iBACE,SAQA;AACA,QAAI,WAAW,WAAW,SAAS;AACjC,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AACA,WAAO,KAAK,IAAI,SAAS;AAAA,MACvB,GAAG;AAAA,MACH,OAAO,CAAC,WAAW,WAAW,SAAS;AAAA,IACzC,CAAC;AAAA,EACH;AAAA,EAEA,gBACE,SAMU;AAEV,WAAO,IAAI,SAAU,KAAK,IAAI,SAAS,OAAO,EAAU,MAAM;AAAA,EAChE;AAAA,EAEA,MAAM,aACJ,SAMe;AACf,UAAM,KAAK,MAAM,EAAE,IAAI,gBAAgB,OAAO,EAAE;AAAA,EAClD;AAAA,EAEA,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOJ,UAAU,KAAK,UAAU,KAAK,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMlC,iBAAiB,KAAK,iBAAiB,KAAK,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOhD,gBAAgB,KAAK,gBAAgB,KAAK,IAAI;AAAA,IAC9C,aAAa,KAAK,aAAa,KAAK,IAAI;AAAA,EAC1C;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
} from "bun";
|
|
15
15
|
import { Path, stringifyIfPath } from "path-class";
|
|
16
16
|
import type { SetFieldType } from "type-fest";
|
|
17
|
-
import type {
|
|
17
|
+
import type { NodeWithCwd, spawnType, WithSuccess } from "./spawn";
|
|
18
18
|
|
|
19
19
|
// TODO: does this import work?
|
|
20
20
|
/**
|
|
@@ -392,8 +392,11 @@ export class PrintableShellCommand {
|
|
|
392
392
|
/**
|
|
393
393
|
* The returned child process includes a `.success` `Promise` field, per https://github.com/oven-sh/bun/issues/8313
|
|
394
394
|
*/
|
|
395
|
-
|
|
396
|
-
|
|
395
|
+
// biome-ignore lint/suspicious/noTsIgnore: `@ts-expect-error` would end up in the published package, where it is invalid.
|
|
396
|
+
// @ts-ignore-error Wrangling types.
|
|
397
|
+
public spawn: typeof spawnType = (
|
|
398
|
+
options?: Parameters<typeof spawnType>[0],
|
|
399
|
+
) => {
|
|
397
400
|
const { spawn } = process.getBuiltinModule("node:child_process");
|
|
398
401
|
const cwd = stringifyIfPath(options?.cwd);
|
|
399
402
|
// biome-ignore lint/suspicious/noTsIgnore: We don't want linting to depend on *broken* type checking.
|
|
@@ -503,8 +506,7 @@ export class PrintableShellCommand {
|
|
|
503
506
|
stdio: ["ignore", "pipe", "inherit"],
|
|
504
507
|
});
|
|
505
508
|
|
|
506
|
-
|
|
507
|
-
return new Response(Readable.toWeb(subprocess.stdout!));
|
|
509
|
+
return new Response(Readable.toWeb(subprocess.stdout));
|
|
508
510
|
}
|
|
509
511
|
|
|
510
512
|
/**
|