UNPKG

@black-flag/core

Version:

A declarative framework for building fluent, deeply hierarchical command line interfaces with yargs

301 lines 16.2 kB
import type { Promisable } from 'type-fest'; import type { ConfigurationHooks } from "./types/configure.js"; import type { Arguments, NullArguments, PreExecutionContext } from "./types/program.js"; /** * @internal */ export type RunProgramParametersWithAny = [commandModulesPath: string] | [commandModulesPath: string, configurationHooks: Promisable<ConfigurationHooks<any>>] | [commandModulesPath: string, preExecutionContext: Promisable<PreExecutionContext>] | [commandModulesPath: string, argv: string | string[]] | [commandModulesPath: string, argv: string | string[], configurationHooks: Promisable<ConfigurationHooks<any>>] | [commandModulesPath: string, argv: string | string[], preExecutionContext: Promisable<PreExecutionContext>]; /** * The available call signature parameters of the {@link runProgram} function. * * The first parameter is always the required `commandModulesPath` string, * optionally followed by `argv` string/array, and then either a * {@link ConfigurationHooks} or {@link PreExecutionContext} instance (or a * promise that returns them). */ export type RunProgramParameters = RunProgramParametersWithAny | [commandModulesPath: string, configurationHooks: Promisable<ConfigurationHooks>] | [commandModulesPath: string, argv: string | string[], configurationHooks: Promisable<ConfigurationHooks>]; /** * The return type of the {@link runProgram} function. */ export type RunProgramReturnType<CustomCliArguments extends Record<string, unknown>> = Promise<NullArguments | Arguments<CustomCliArguments> | undefined>; /** * The available call signature parameters of the low-order function returned by * {@link makeRunner}. * * This is the same thing as {@link RunProgramParameters} but with the first * parameter (i.e. the `commandModulesPath` string) omitted. */ export type FactoriedRunProgramParameters = RunProgramParametersWithAny extends [infer _, ...infer Tail] ? Tail : []; /** * The options accepted by the {@link makeRunner} function. */ export type MakeRunnerOptions = { /** * @see {@link runProgram} */ commandModulesPath: string; /** * This is a special option exclusive to `makeRunner` that determines how * errors will be surfaced, which can be useful during testing. * * In production and during testing, Black Flag surfaces errors via * `process.stderr` (e.g. `console.error`), or whichever error handling * method was implemented in {@link ConfigureErrorHandlingEpilogue}. This is * the default behavior, and is what end-users will see and experience. * * However, by setting this option to `'throw'` instead of `'default'`, * exceptions that would normally cause {@link runProgram} to return * `undefined` (including framework errors) or `NullArguments` (such as * `GracefulEarlyExitError`) will instead be thrown after they are handled * by Black Flag. * * **Asserting expectations against how the CLI will actually behave in * production, which is what end-users will actually experience, should be * preferred over testing against an artificially surfaced error**. However, * surfacing errors in test cases that are failing unexpectedly can be * crucial when debugging. Discretion is advised. * * @default 'default' */ errorHandlingBehavior?: 'default' | 'throw'; /** * The {@link ConfigurationHooks} to be used by each low-order * invocation by default. Each low-order function can provide its own * {@link ConfigurationHooks} argument, which will be merged on top of * this option. A low-order function supplying a * {@link PreExecutionContext} argument instead will completely override * this option. * * Note: this option cannot be used with `preExecutionContext`. * * @see {@link runProgram} */ configurationHooks?: Promisable<ConfigurationHooks<any>>; /** * The {@link PreExecutionContext} to be used by each low-order * invocation. Each low-order function can provide its own * {@link PreExecutionContext} or {@link ConfigurationHooks} argument, * both of which which will completely override this option. * * Note: this option cannot be used with `configurationHooks`. * * @see {@link runProgram} */ preExecutionContext?: Promisable<PreExecutionContext>; } & ({ configurationHooks?: Promisable<ConfigurationHooks<any>>; preExecutionContext?: undefined; } | { preExecutionContext?: Promisable<PreExecutionContext>; configurationHooks?: undefined; }); /** * Create and return a {@link PreExecutionContext} containing fully-configured * {@link Program} instances and an {@link Executor} entry point function. * * Command auto-discovery will occur at `commandModulesPath`. An exception will * occur if no commands are loadable from the given `commandModulesPath`. * * **This function throws whenever an exception occurs**, making it not ideal as * an entry point for a CLI, but perhaps useful during testing. See * {@link runProgram} for a wrapper function that handles exceptions and sets * the exit code automatically. */ export declare function configureProgram( /** * Command auto-discovery will occur at `commandModulesPath`. An exception will * occur if no commands are loadable from the given `commandModulesPath`. * * `'file://...'`-style URLs are also accepted. */ commandModulesPath: string, configurationHooks?: Promisable<ConfigurationHooks<any>>): Promise<PreExecutionContext>; /** * A "high-order" factory function that returns a "low-order" curried * {@link runProgram} function that can be called multiple times while only * having to provide a subset of the required parameters. * * This is useful when unit/integration testing a CLI, which will likely require * multiple calls to `runProgram(...)`. * * **BE AWARE**: when an exception (e.g. bad CLI arguments) occurs in the * low-order function, `process.exitCode` is set to the appropriate value, the * {@link ConfigureErrorHandlingEpilogue} hook is triggered, and either * `NullArguments` (only if `GracefulEarlyExitError` was thrown) or `undefined` * is returned. Otherwise, upon success, `Arguments` is returned. * * In other words: **the promise returned by the low-order function will never * reject and no exception will ever be thrown.** Keep this in mind when writing * unit tests and see {@link runProgram} for more details. * * Ideally, testing for the presence of errors should be done by capturing * output sent to `process.stderr` (e.g. `console.error`)—or by interrogating * whichever error handling method was implemented in * {@link ConfigureErrorHandlingEpilogue}—since that is what end-users will see * and experience. That said, if it would be more optimal to test against an * actual thrown error, set `makeRunner`'s `errorHandlingBehavior` option to * `'throw'`. */ export declare function makeRunner<CustomCliArguments extends Record<string, unknown> = Record<string, unknown>>(options: MakeRunnerOptions): (...args: FactoriedRunProgramParameters) => RunProgramReturnType<CustomCliArguments>; /** * Invokes the dynamically imported * `configureProgram(commandModulesPath).execute()` function. * * This function is suitable for a CLI entry point since it will **never throw * or reject no matter what.** Instead, when an exception occurs, * `process.exitCode` is set to the appropriate value, the * {@link ConfigureErrorHandlingEpilogue} hook is triggered, and either * `NullArguments` (only if `GracefulEarlyExitError` was thrown) or `undefined` * is returned. * * Note: It is always safe to invoke this form of `runProgram` as many times as * desired. * * @returns `NullArguments` if `GracefulEarlyExitError` is thrown, `undefined` * if any other exception occurs, or `Arguments` otherwise. */ export declare function runProgram<CustomCliArguments extends Record<string, unknown> = Record<string, unknown>>(commandModulesPath: string): RunProgramReturnType<CustomCliArguments>; /** * Invokes the dynamically imported `configureProgram(commandModulesPath, * configurationHooks).execute()` function. * * This function is suitable for a CLI entry point since it will **never throw * or reject no matter what.** Instead, when an exception occurs, * `process.exitCode` is set to the appropriate value, the * {@link ConfigureErrorHandlingEpilogue} hook is triggered, and either * `NullArguments` (only if `GracefulEarlyExitError` was thrown) or `undefined` * is returned. * * Note: It is always safe to invoke this form of `runProgram` as many times as * desired. * * @returns `NullArguments` if `GracefulEarlyExitError` is thrown, `undefined` * if any other exception occurs, or `Arguments` otherwise. */ export declare function runProgram<CustomCliArguments extends Record<string, unknown> = Record<string, unknown>>(commandModulesPath: string, configurationHooks: Promisable<ConfigurationHooks>): RunProgramReturnType<CustomCliArguments>; /** * Invokes the dynamically imported `configureProgram(commandModulesPath, * configurationHooks).execute()` function. * * This function is suitable for a CLI entry point since it will **never throw * or reject no matter what.** Instead, when an exception occurs, * `process.exitCode` is set to the appropriate value, the * {@link ConfigureErrorHandlingEpilogue} hook is triggered, and either * `NullArguments` (only if `GracefulEarlyExitError` was thrown) or `undefined` * is returned. * * Note: It is always safe to invoke this form of `runProgram` as many times as * desired. * * @returns `NullArguments` if `GracefulEarlyExitError` is thrown, `undefined` * if any other exception occurs, or `Arguments` otherwise. */ export declare function runProgram<CustomCliArguments extends Record<string, unknown> = Record<string, unknown>>(commandModulesPath: string, configurationHooks: Promisable<ConfigurationHooks<any>>): RunProgramReturnType<CustomCliArguments>; /** * Invokes the `preExecutionContext.execute()` function. * * **WARNING: reusing the same `preExecutionContext` with multiple invocations * of `runProgram` will cause successive invocations to fail.** This is because * yargs does not support calling `yargs::parseAsync` more than once. If this is * unacceptable, do not pass `runProgram` a `preExecutionContext` property. * * This function is suitable for a CLI entry point since it will **never throw * or reject no matter what.** Instead, when an exception occurs, * `process.exitCode` is set to the appropriate value, the * {@link ConfigureErrorHandlingEpilogue} hook is triggered, and either * `NullArguments` (only if `GracefulEarlyExitError` was thrown) or `undefined` * is returned. * * @returns `NullArguments` if `GracefulEarlyExitError` is thrown, `undefined` * if any other exception occurs, or `Arguments` otherwise. */ export declare function runProgram<CustomCliArguments extends Record<string, unknown> = Record<string, unknown>>(commandModulesPath: string, preExecutionContext: Promisable<PreExecutionContext>): RunProgramReturnType<CustomCliArguments>; /** * Invokes the dynamically imported * `configureProgram(commandModulesPath).execute(argv)` function. If `argv` is a * string, `argv = argv.split(' ')` is applied first. * * This function is suitable for a CLI entry point since it will **never throw * or reject no matter what.** Instead, when an exception occurs, * `process.exitCode` is set to the appropriate value, the * {@link ConfigureErrorHandlingEpilogue} hook is triggered, and either * `NullArguments` (only if `GracefulEarlyExitError` was thrown) or `undefined` * is returned. * * Note: It is always safe to invoke this form of `runProgram` as many times as * desired. * * @returns `NullArguments` if `GracefulEarlyExitError` is thrown, `undefined` * if any other exception occurs, or `Arguments` otherwise. */ export declare function runProgram<CustomCliArguments extends Record<string, unknown> = Record<string, unknown>>(commandModulesPath: string, argv: string | string[]): RunProgramReturnType<CustomCliArguments>; /** * Invokes the dynamically imported `configureProgram(commandModulesPath, * configurationHooks).execute(argv)` function. If `argv` is a string, `argv = * argv.split(' ')` is applied first. * * This function is suitable for a CLI entry point since it will **never throw * or reject no matter what.** Instead, when an exception occurs, * `process.exitCode` is set to the appropriate value, the * {@link ConfigureErrorHandlingEpilogue} hook is triggered, and either * `NullArguments` (only if `GracefulEarlyExitError` was thrown) or `undefined` * is returned. * * Note: It is always safe to invoke this form of `runProgram` as many times as * desired. * * @returns `NullArguments` if `GracefulEarlyExitError` is thrown, `undefined` * if any other exception occurs, or `Arguments` otherwise. */ export declare function runProgram<CustomCliArguments extends Record<string, unknown> = Record<string, unknown>>(commandModulesPath: string, argv: string | string[], configurationHooks: Promisable<ConfigurationHooks>): RunProgramReturnType<CustomCliArguments>; /** * Invokes the dynamically imported `configureProgram(commandModulesPath, * configurationHooks).execute(argv)` function. If `argv` is a string, `argv = * argv.split(' ')` is applied first. * * This function is suitable for a CLI entry point since it will **never throw * or reject no matter what.** Instead, when an exception occurs, * `process.exitCode` is set to the appropriate value, the * {@link ConfigureErrorHandlingEpilogue} hook is triggered, and either * `NullArguments` (only if `GracefulEarlyExitError` was thrown) or `undefined` * is returned. * * Note: It is always safe to invoke this form of `runProgram` as many times as * desired. * * @returns `NullArguments` if `GracefulEarlyExitError` is thrown, `undefined` * if any other exception occurs, or `Arguments` otherwise. */ export declare function runProgram<CustomCliArguments extends Record<string, unknown> = Record<string, unknown>>(commandModulesPath: string, argv: string | string[], configurationHooks: Promisable<ConfigurationHooks<any>>): RunProgramReturnType<CustomCliArguments>; /** * Invokes the `preExecutionContext.execute(argv)` function. If `argv` is a * string, `argv = argv.split(' ')` is applied first. * * **WARNING: reusing the same `preExecutionContext` with multiple invocations * of `runProgram` will cause successive invocations to fail.** This is because * yargs does not support calling `yargs::parseAsync` more than once. If this is * unacceptable, do not pass `runProgram` a `preExecutionContext` property. * * This function is suitable for a CLI entry point since it will **never throw * or reject no matter what.** Instead, when an exception occurs, * `process.exitCode` is set to the appropriate value, the * {@link ConfigureErrorHandlingEpilogue} hook is triggered, and either * `NullArguments` (only if `GracefulEarlyExitError` was thrown) or `undefined` * is returned. * * @returns `NullArguments` if `GracefulEarlyExitError` is thrown, `undefined` * if any other exception occurs, or `Arguments` otherwise. */ export declare function runProgram<CustomCliArguments extends Record<string, unknown> = Record<string, unknown>>(commandModulesPath: string, argv: string | string[], preExecutionContext: Promisable<PreExecutionContext>): RunProgramReturnType<CustomCliArguments>; /** * Run the given program with the configuration given in `args`. * * This function is suitable for a CLI entry point since it will **never throw * or reject no matter what.** Instead, when an exception occurs, * `process.exitCode` is set to the appropriate value, the * {@link ConfigureErrorHandlingEpilogue} hook is triggered, and either * `NullArguments` (only if `GracefulEarlyExitError` was thrown) or `undefined` * is returned. * * @returns `NullArguments` if `GracefulEarlyExitError` is thrown, `undefined` * if any other exception occurs, or `Arguments` otherwise. */ export declare function runProgram<CustomCliArguments extends Record<string, unknown> = Record<string, unknown>>(...args: RunProgramParameters): RunProgramReturnType<CustomCliArguments>;