UNPKG

dax

Version:

Cross platform shell tools inspired by zx.

677 lines 28.9 kB
import * as dntShim from "../../../../../../_dnt.shims.js"; import { Path } from "../../../path/0.3.2/mod.js"; import { Buffer } from "../../../../@std/io/0.225.3/buffer.js"; import type { CommandHandler } from "./commandHandler.js"; import { Box, type Delay, LoggerTreeBox } from "./common.js"; import type { Signal } from "./signal.js"; import { ByteRingBuffer } from "./byteRingBuffer.js"; import { type ErrorTailOptions, PipedBuffer, type Reader, type ShellPipeReaderKind, type ShellPipeWriterKind, type TailDisplayOptions } from "./pipes.js"; import { type ShellOptionsState } from "./shell.js"; import { StreamFds } from "./shell.js"; type BufferStdio = "inherit" | "null" | "streamed" | Buffer; /** Identifies one of the standard output streams of a command, or both * of them when combined. */ export type StreamKind = "stdout" | "stderr" | "combined"; declare class Deferred<T> { #private; constructor(create: () => T | Promise<T>); create(): T | Promise<T>; } interface ShellPipeWriterKindWithOptions { kind: ShellPipeWriterKind; options?: dntShim.StreamPipeOptions; } export interface CommandBuilderStateCommand { text: string; fds: StreamFds | undefined; } interface CommandBuilderState { command: Readonly<CommandBuilderStateCommand> | undefined; stdin: "inherit" | "null" | Box<Reader | dntShim.ReadableStream<Uint8Array> | "consumed"> | Deferred<dntShim.ReadableStream<Uint8Array> | Reader>; combinedStdoutStderr: boolean; stdout: ShellPipeWriterKindWithOptions; stderr: ShellPipeWriterKindWithOptions; tailDisplay: false | TailDisplayOptions; errorTail: false | ErrorTailOptions; noThrow: boolean | number[]; env: Record<string, string | undefined>; commands: Record<string, CommandHandler>; cwd: string | undefined; clearEnv: boolean; exportEnv: boolean; printCommand: boolean; printCommandLogger: LoggerTreeBox; timeout: number | undefined; signal: KillSignal | undefined; encoding: string | undefined; shellOptions: Partial<ShellOptionsState>; beforeCommand: BeforeCommandCallback[] | undefined; beforeCommandSync: BeforeCommandSyncCallback[] | undefined; } /** Synchronous variant of {@link BeforeCommandCallback}. Receives the current * builder and returns a (possibly modified) builder synchronously, or returns * nothing for a no-op. * * Unlike {@link BeforeCommandCallback}, this runs even on the `.spawn()` path * since no async work is involved. */ export type BeforeCommandSyncCallback = (builder: CommandBuilder) => CommandBuilder | void; /** Callback invoked before a command is spawned. * * Receives the current builder and may return a (possibly modified) builder * to use for the spawn. Returning the builder unchanged — or returning * nothing — is a no-op. * * The builder passed to the callback is wrapped in a Proxy that hides its * `.then`, so it isn't detected as a thenable by the JavaScript Promise * machinery. That makes it safe to `return builder.env(...)` from an `async` * function without the runtime recursively unwrapping the thenable and * triggering a spawn. Methods like `.env(...)` that return a new builder * return another such wrapped Proxy. * * The return type is loose because TypeScript infers the async return type * by following `PromiseLike` (the same logic the runtime uses), so e.g. * `async b => b.env(...)` is inferred as `Promise<CommandResult>`. */ export type BeforeCommandCallback = (builder: CommandBuilder) => CommandBuilder | Promise<CommandBuilder> | Promise<CommandResult> | void | Promise<void>; /** @internal */ export declare const getRegisteredCommandNamesSymbol: unique symbol; /** @internal */ export declare const setCommandTextStateSymbol: unique symbol; /** * Underlying builder API for executing commands. * * This is what `$` uses to execute commands. Using this provides * a way to provide a raw text command or an array of arguments. * * Command builders are immutable where each method call creates * a new command builder. * * ```ts * const builder = new CommandBuilder() * .cwd("./src") * .command("echo $MY_VAR"); * * // outputs 5 * console.log(await builder.env("MY_VAR", "5").text()); * // outputs 6 * console.log(await builder.env("MY_VAR", "6").text()); * ``` */ export declare class CommandBuilder implements PromiseLike<CommandResult> { #private; /** Spawns the command and returns a promise resolving to the command result. * * Allows awaiting a `CommandBuilder` directly without calling `.spawn()`. */ then<TResult1 = CommandResult, TResult2 = never>(onfulfilled?: ((value: CommandResult) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): PromiseLike<TResult1 | TResult2>; /** * Explicit way to spawn a command. * * This is an alias for awaiting the command builder or calling `.then(...)` */ spawn(): CommandChild; /** * Register a command. */ registerCommand(command: string, handleFn: CommandHandler): CommandBuilder; /** * Register multilple commands. */ registerCommands(commands: Record<string, CommandHandler>): CommandBuilder; /** * Unregister a command. */ unregisterCommand(command: string): CommandBuilder; /** Sets the raw command to execute. */ command(command: string | string[]): CommandBuilder; /** The command should not throw for the provided non-zero exit codes. */ noThrow(exclusionExitCode: number, ...additional: number[]): CommandBuilder; /** The command should not throw when it fails or times out. */ noThrow(value?: boolean): CommandBuilder; /** Registers a callback that runs just before each command is spawned. * * The callback receives the current builder and may return a (possibly * modified) builder to use for the spawn. Useful for asynchronously * resolving values such as auth tokens or env vars. * * ```ts * $`./build.sh`.beforeCommand(async (builder) => { * return builder.env("AUTH_TOKEN", await getAccessToken()); * }); * ``` * * Multiple `.beforeCommand(...)` calls compose: each registered callback runs * in the order it was added, with the builder produced by the previous one. * * The builder passed to the callback is in a special "passthrough" mode so * its `.then(...)` resolves with the builder itself (instead of spawning) — * this is what makes `return builder.env(...)` from an `async` function * safe. If you construct a fresh `new CommandBuilder()` inside the callback, * return it as `{ commandBuilder: ... }` to avoid an accidental spawn. * * Hooks are only resolved when the builder is awaited (or `.then()` is * called). Calling `.spawn()` on a builder with registered hooks throws. */ beforeCommand(callback: BeforeCommandCallback): CommandBuilder; /** Synchronous variant of {@link beforeCommand}. Unlike `.beforeCommand`, * sync hooks also run on the `.spawn()` path, so they're suitable when you * need streaming via `.spawn().stdout()` etc. * * The callback must return synchronously (`CommandBuilder` or nothing). The * thenable-unwrapping concern doesn't apply here because the return value * never passes through Promise machinery — `return builder.env(...)` is just * a function return. * * ```ts * const child = $`./build.sh` * .beforeCommandSync((builder) => builder.env("BUILD_ID", crypto.randomUUID())) * .spawn(); * ``` * * Sync hooks always run before async hooks during a single resolution pass. */ beforeCommandSync(callback: BeforeCommandSyncCallback): CommandBuilder; /** Sets the signal that will be passed to all commands * created with this command builder. * * Accepts a `KillSignal` or a standard `AbortSignal`. When an * `AbortSignal` is provided it is internally bridged to a * `KillSignal` that sends `SIGTERM` on abort. */ signal(signal: KillSignal | AbortSignal): CommandBuilder; /** * Whether to capture a combined buffer of both stdout and stderr. * * This will set both stdout and stderr to "piped" if not already "piped" * or "inheritPiped". */ captureCombined(value?: boolean): CommandBuilder; /** * Sets the stdin to use for the command. * * @remarks If multiple launches of a command occurs, then stdin will only be * read from the first consumed reader or readable stream and error otherwise. * For this reason, if you are setting stdin to something other than "inherit" or * "null", then it's recommended to set this each time you spawn a command. */ stdin(reader: ShellPipeReaderKind): CommandBuilder; /** * Sets the stdin string to use for a command. * * @remarks See the remarks on stdin. The same applies here. */ stdinText(text: string): CommandBuilder; /** Set the stdout kind. */ stdout(kind: ShellPipeWriterKind): CommandBuilder; /** Set the stdout to pipe to the provided WritableStream with optional pipe options. */ stdout(kind: dntShim.WritableStream<Uint8Array>, options?: dntShim.StreamPipeOptions): CommandBuilder; /** Set the stderr kind. */ stderr(kind: ShellPipeWriterKind): CommandBuilder; /** Set the stderr to pipe to the provided WritableStream with optional pipe options. */ stderr(kind: dntShim.WritableStream<Uint8Array>, options?: dntShim.StreamPipeOptions): CommandBuilder; /** * Enables Docker-style partial scrolling for any `"inherit"` or * `"inheritPiped"` output: only the last few lines are shown in a * static region at the bottom of the terminal while the command * runs. When both stdout and stderr are being displayed, they * interleave into a single shared scrolling region with one header. * * On success the region is cleared (header promotes to scrollback); * on error the retained tail is flushed above so the user can see * what happened. Has no effect on non-TTY output or on streams * routed elsewhere (piped, null, WritableStream, Path, etc.). * * Pass an options object to customize the visible line count or header. * * @example * ```ts * await $`./build.sh`.tailDisplay(); * await $`./build.sh`.tailDisplay({ maxLines: 2, header: false }); * await $`./build.sh`.tailDisplay({ * maxLines: "50%", * header: ({ command }) => `building ${command}...`, * }); * ``` */ tailDisplay(value?: boolean): CommandBuilder; /** Enables Docker-style partial scrolling with the provided options. */ tailDisplay(options: TailDisplayOptions): CommandBuilder; /** * Silently retains the trailing N bytes of stdout and/or stderr and * appends them to the thrown `Error.message` if the command exits with * a non-zero code. Targets the case where the stream is going somewhere * the user can't see — piped to another command, redirected to a file, * sent to a `WritableStream`, or discarded with `"null"` — so they * still get a glimpse of what was written when the command fails. * * Streams routed to the terminal (`"inherit"` / `"inheritPiped"`) are * skipped: the user already watched those bytes scroll by, so adding * them to the error message would just be noise. * * Has no effect when the command succeeds (the buffer is discarded) or * when `.noThrow()` swallows the failure. * * @example * ```ts * // when stdout is being captured for the return value (.text(), * // .json(), .bytes(), etc.), errorTail surfaces it in the thrown * // error if the command fails — without it the captured bytes vanish * // along with the result you never got. * const output = await $`./build.sh`.errorTail().text(); * const output = await $`./build.sh`.errorTail({ maxBytes: 16 * 1024 }).text(); * ``` */ errorTail(value?: boolean): CommandBuilder; /** Enables errorTail capture with the provided options. */ errorTail(options: ErrorTailOptions): CommandBuilder; /** Pipes the current command to the provided command returning the * provided command builder. When chaining, it's important to call this * after you are done configuring the current command or else you will * start modifying the provided command instead. * * @example * ```ts * const lineCount = await $`echo 1 && echo 2` * .pipe($`wc -l`) * .text(); * ``` */ pipe(builder: CommandBuilder): CommandBuilder; /** Sets multiple environment variables to use at the same time via an object literal. */ env(items: Record<string, string | undefined>): CommandBuilder; /** Sets a single environment variable to use. */ env(name: string, value: string | undefined): CommandBuilder; /** Sets the current working directory to use when executing this command. */ cwd(dirPath: string | URL | Path): CommandBuilder; /** * Exports the environment of the command to the executing process. * * So for example, changing the directory in a command or exporting * an environment variable will actually change the environment * of the executing process. * * ```ts * await $`cd src && export SOME_VALUE=5`; * console.log(process.env["SOME_VALUE"]); // 5 * console.log(process.cwd()); // will be in the src directory * ``` */ exportEnv(value?: boolean): CommandBuilder; /** * Clear environmental variables from parent process. * * Doesn't guarantee that only `env` variables are present, as the OS may * set environmental variables for processes. */ clearEnv(value?: boolean): CommandBuilder; /** * Prints the command text before executing the command. * * For example: * * ```ts * const text = "example"; * await $`echo ${text}`.printCommand(); * ``` * * Outputs: * * ``` * > echo example * example * ``` */ printCommand(value?: boolean): CommandBuilder; /** * Mutates the command builder to change the logger used * for `printCommand()`. */ setPrintCommandLogger(logger: (...args: any[]) => void): void; /** * Ensures stdout and stderr are piped if they have the default behaviour or are inherited. * * ```ts * // ensure both stdout and stderr is not logged to the console * await $`echo 1`.quiet(); * // ensure stdout is not logged to the console * await $`echo 1`.quiet("stdout"); * // ensure stderr is not logged to the console * await $`echo 1`.quiet("stderr"); * ``` */ quiet(kind?: StreamKind | "both"): CommandBuilder; /** * Specifies a timeout for the command. The command will exit with * exit code `124` (timeout) if it times out. * * Note that when using `.noThrow()` this won't cause an error to * be thrown when timing out. */ timeout(delay: Delay | undefined): CommandBuilder; /** * Sets stdout as quiet, spawns the command, and gets stdout as a Uint8Array. * * Shorthand for: * * ```ts * const data = (await $`command`.quiet("stdout")).stdoutBytes; * ``` */ bytes(kind?: StreamKind): Promise<Uint8Array>; /** * Sets the text decoder encoding to use for decoding stdout and stderr. * * This can be useful when the command output uses a specific character encoding * that differs from the default UTF-8 encoding. */ encoding(encoding?: string): CommandBuilder; /** * Sets whether pipefail is enabled. When enabled, a pipeline's exit code * is the rightmost non-zero exit code, or 0 if all commands succeed. * * ```ts * // without pipefail (default): exit code is from the last command (grep) * const code1 = await $`false | grep foo`.noThrow().code(); // 1 (grep's exit code) * * // with pipefail: exit code is from the first failing command * const code2 = await $`false | grep foo`.pipefail().noThrow().code(); // 1 (false's exit code) * ``` */ pipefail(value?: boolean): CommandBuilder; /** * Sets whether nullglob is enabled. When enabled, a glob pattern that * matches no files expands to nothing (empty) rather than being passed * through literally. * * Note: This also disables failglob since nullglob and failglob are * mutually exclusive behaviors. * * ```ts * // without nullglob (default): passes pattern literally * await $`echo *.nonexistent`; // outputs "*.nonexistent" * * // with nullglob: expands to nothing * await $`echo *.nonexistent`.nullglob(); // outputs empty line * ``` */ nullglob(value?: boolean): CommandBuilder; /** * Sets whether failglob is enabled. When enabled, a glob pattern * that matches no files causes an error. When disabled (the default), * unmatched patterns are passed through literally. * * ```ts * // without failglob (default): passes pattern literally * await $`echo *.nonexistent`; // outputs "*.nonexistent" * * // with failglob: throws error if no matches * await $`echo *.nonexistent`.failglob(); // Error: glob: no matches found * ``` */ failglob(value?: boolean): CommandBuilder; /** * Sets whether globstar is enabled. When enabled (the default), the pattern `**` * used in a pathname expansion context will match all files and zero or more * directories and subdirectories. When disabled, `**` is treated as `*`. * * ```ts * // with globstar (default): ** matches recursively * await $`echo **\/*.ts`; // matches all .ts files in all subdirectories * * // without globstar: ** is treated as * * await $`echo **\/*.ts`.globstar(false); // matches only .ts files one level deep * ``` */ globstar(value?: boolean): CommandBuilder; /** * Sets whether questionGlob is enabled. When enabled, `?` matches any * single character in glob patterns. When disabled (the default), `?` is * treated literally. * * This option is only available via the builder API, not via `shopt` or `set`. * * ```ts * // without questionGlob (default): ? is literal * await $`echo a?c`; // outputs "a?c" * * // with questionGlob: ? matches any single character * await $`echo a?c`.questionGlob(); // matches files like "abc", "axc", etc. * ``` */ questionGlob(value?: boolean): CommandBuilder; /** * Sets the provided stream (stdout by default) as quiet, spawns the command, and gets the stream as a string without the last newline. * Can be used to get stdout, stderr, or both. * * Shorthand for: * * ```ts * const data = (await $`command`.quiet("stdout")).stdout.replace(/\r?\n$/, ""); * ``` */ text(kind?: StreamKind): Promise<string>; /** * Gets the text as an array of lines. * * Lines are split the same way as {@link CommandBuilder.linesIter} (matches * [Rust's `str::lines`](https://doc.rust-lang.org/std/primitive.str.html#method.lines)): * line terminators are not included, a trailing blank line caused by a final * line ending is excluded, and empty output yields no lines. */ lines(kind?: StreamKind): Promise<string[]>; /** * Streams the command's output and iterates over its lines without * buffering everything into memory. * * Lines are split at `\n` or `\r\n`. Line terminators are not included. * A trailing blank line caused by a final line ending is excluded (matches * [Rust's `str::lines`](https://doc.rust-lang.org/std/primitive.str.html#method.lines)). * * If iteration is abandoned before the end of the output is reached, the * child process is killed. * * ```ts * for await (const line of $`cat big.txt`.linesIter()) { * console.log(line); * } * ``` */ linesIter(kind?: Exclude<StreamKind, "combined">): AsyncIterableIterator<string>; /** * Sets stream (stdout by default) as quiet, spawns the command, and gets stream as JSON. * * Shorthand for: * * ```ts * const data = (await $`command`.quiet("stdout")).stdoutJson; * ``` */ json<TResult = any>(kind?: Exclude<StreamKind, "combined">): Promise<TResult>; /** * Helper to get the exit code without throwing on non-zero exit codes. * * Shorthand for: * * ```ts * const code = (await $`command`.noThrow()).code; * ``` */ code(): Promise<number>; /** @internal */ [getRegisteredCommandNamesSymbol](): string[]; /** @internal */ [setCommandTextStateSymbol](textState: CommandBuilderStateCommand): CommandBuilder; } /** A spawned command. Awaiting it resolves to a `CommandResult` and it * exposes hooks to read piped output streams or send signals to the * underlying child process. */ export declare class CommandChild extends Promise<CommandResult> { #private; /** @internal */ constructor(executor: (resolve: (value: CommandResult) => void, reject: (reason?: any) => void) => void, options?: { pipedStdoutBuffer: PipedBuffer | undefined; pipedStderrBuffer: PipedBuffer | undefined; killSignalController: KillController | undefined; }); /** Send a signal to the executing command's child process. Note that SIGTERM, * SIGKILL, SIGABRT, SIGQUIT, SIGINT, or SIGSTOP will cause the entire command * to be considered "aborted" and if part of a command runs after this has occurred * it will return a 124 exit code. Other signals will just be forwarded to the command. * * Defaults to "SIGTERM". */ kill(signal?: Signal): void; /** Returns a `ReadableStream` of the running child's stdout. Requires the * stdout to have been set to `"piped"`. May only be consumed once. */ stdout(): dntShim.ReadableStream<Uint8Array>; /** Returns a `ReadableStream` of the running child's stderr. Requires the * stderr to have been set to `"piped"`. May only be consumed once. */ stderr(): dntShim.ReadableStream<Uint8Array>; } export declare function parseAndSpawnCommand(state: CommandBuilderState, callerStack?: string): CommandChild; /** Error thrown when a command exits with a non-zero code (and `.noThrow()` * is not set). Exposes structured properties (`exitCode`, `stdout`, `stderr`) * so callers can inspect the failure programmatically. The human-readable * `message` — including any captured error-tail output — is built lazily on * first access so the formatting cost is skipped when the error is caught * and only its properties are used. */ export declare class ShellError extends Error { #private; readonly name = "ShellError"; /** The process exit code. */ readonly exitCode: number; /** Whether the command was killed because its timeout elapsed. */ readonly timedOut: boolean; /** Whether the command was aborted via its kill signal. */ readonly aborted: boolean; /** Captured trailing stdout text from the error-tail ring buffer. * Empty string when stdout was not captured (e.g. routed to the * terminal, or errorTail disabled for stdout). */ readonly stdout: string; /** Captured trailing stderr text from the error-tail ring buffer. * Empty string when stderr was not captured. */ readonly stderr: string; /** @internal */ constructor(options: { exitCode: number; timedOut: boolean; aborted: boolean; stdoutRing: ByteRingBuffer | undefined; stderrRing: ByteRingBuffer | undefined; combinedRing: ByteRingBuffer | undefined; }); get message(): string; set message(value: string); } /** Result of running a command. */ export declare class CommandResult { #private; /** The exit code. */ readonly code: number; /** @internal */ constructor(code: number, stdout: BufferStdio, stderr: BufferStdio, combined: Buffer | undefined, encoding: string | undefined); /** Raw decoded stdout text. */ get stdout(): string; /** * Stdout text as JSON. * * @remarks Will throw if it can't be parsed as JSON. */ get stdoutJson(): any; /** Raw stdout bytes. */ get stdoutBytes(): Uint8Array; /** Raw decoded stdout text. */ get stderr(): string; /** * Stderr text as JSON. * * @remarks Will throw if it can't be parsed as JSON. */ get stderrJson(): any; /** Raw stderr bytes. */ get stderrBytes(): Uint8Array; /** Raw combined stdout and stderr text. */ get combined(): string; /** Raw combined stdout and stderr bytes. */ get combinedBytes(): Uint8Array; } /** Escapes a single argument for safe interpolation into a shell command. */ export declare function escapeArg(arg: string): string; /** Wraps a value so that it is interpolated into a template literal command * without being shell-escaped. Use {@link rawArg} to construct one. */ export declare class RawArg<T> { #private; /** Creates a new `RawArg` wrapping the provided value. */ constructor(value: T); /** The wrapped value that will be used as-is during template substitution. */ get value(): T; } /** Creates a `RawArg` wrapping the provided value so it bypasses shell * argument escaping when interpolated into a template literal command. */ export declare function rawArg<T>(arg: T): RawArg<T>; interface KillSignalState { abortedCode: number | undefined; listeners: ((signal: Signal) => void)[]; } /** Similar to an AbortController, but for sending signals to commands. */ export declare class KillController { #private; constructor(); /** The associated `KillSignal` to attach to commands. */ get signal(): KillSignal; /** Send a signal to the downstream child process. Note that SIGTERM, * SIGKILL, SIGABRT, SIGQUIT, SIGINT, or SIGSTOP will cause all the commands * to be considered "aborted" and will return a 124 exit code, while other * signals will just be forwarded to the commands. */ kill(signal?: Signal): void; } /** Listener for when a KillSignal is killed. */ export type KillSignalListener = (signal: Signal) => void; /** Similar to `AbortSignal`, but for OS signals. * * A `KillSignal` is considered aborted if its controller * receives SIGTERM, SIGKILL, SIGABRT, SIGQUIT, SIGINT, or SIGSTOP. * * These can be created via a `KillController`. */ export declare class KillSignal { #private; /** @internal */ constructor(symbol: Symbol, state: KillSignalState); /** Returns if the command signal has ever received a SIGTERM, * SIGKILL, SIGABRT, SIGQUIT, SIGINT, or SIGSTOP */ get aborted(): boolean; /** Gets the exit code to use if aborted. */ get abortedExitCode(): number | undefined; /** * Causes the provided kill signal to be triggered when this * signal receives a signal. */ linkChild(killSignal: KillSignal): { unsubscribe(): void; }; /** Registers a listener that will be invoked whenever a signal is sent. */ addListener(listener: KillSignalListener): void; /** Removes a previously registered listener. */ removeListener(listener: KillSignalListener): void; } export declare function getSignalAbortCode(signal: Signal): number | undefined; export declare function template(strings: TemplateStringsArray, exprs: TemplateExpr[]): CommandBuilderStateCommand; export declare function templateRaw(strings: TemplateStringsArray, exprs: TemplateExpr[]): CommandBuilderStateCommand; /** A template expression that can be substituted into a command outside of * an input/output redirect context. */ export type NonRedirectTemplateExpr = string | number | boolean | Path | Uint8Array | CommandResult | RawArg<NonRedirectTemplateExpr> | { toString(): string; catch?: never; }; /** Any value that may be interpolated into a `$` template literal command. */ export type TemplateExpr = NonRedirectTemplateExpr | NonRedirectTemplateExpr[] | dntShim.ReadableStream<Uint8Array> | { [readable: symbol]: () => dntShim.ReadableStream<Uint8Array>; } | (() => dntShim.ReadableStream<Uint8Array>) | { [writable: symbol]: () => dntShim.WritableStream<Uint8Array>; } | dntShim.WritableStream<Uint8Array> | (() => dntShim.WritableStream<Uint8Array>); export {}; //# sourceMappingURL=command.d.ts.map