Files
2026-05-27 23:29:53 +01:00

5.8 KiB

errors

(no submodules)

Purpose

The errors module defines the complete error taxonomy for @suaveplan/cli. It provides a stable, machine-readable code registry (CLI_CODES) and a strongly-typed error class (CliError) so that every failure surface in the CLI framework is discriminable at runtime without string-matching on messages. Callers can switch exhaustively on err.code, log structured records with stable identifiers, and forward typed errors across process boundaries without losing fidelity.

Features

  • Three sub-namespaces — CLI.PARSE.* covers argument/option parsing failures; CLI.COMMAND.* covers command-dispatch failures; CLI.APP.* covers top-level application lifecycle failures.
  • Nine stable error codes — each code is a dot-delimited string constant produced by defineCodes() from @suaveplan/error, guaranteeing uniqueness and structural consistency across the codebase.
  • CliCode union type — narrows the full set of valid codes for use in generic SuaveplanError<CliCode> type parameters and exhaustive switch statements.
  • CliError class — extends SuaveplanError with a public constructor, cause chaining, and structured context attachment for diagnostic metadata.
  • Zero runtime overhead — the CLI_CODES object is as const, so all string values are inlined at the call site by the TypeScript compiler.

Usage: Basic Example

import { CliError, CLI_CODES } from "@suaveplan/cli/errors/errors";

function assertKnownOption(flag: string, known: Set<string>): void {
    if (!known.has(flag)) {
        throw new CliError(
            CLI_CODES.PARSE.UNKNOWN_OPTION,
            "cli.parse.unknown_option"
        );
    }
}

Usage: Advanced Example

import { CliError, CLI_CODES } from "@suaveplan/cli/errors/errors";
import { SuaveplanError } from "@suaveplan/error";

async function runApp(signal: AbortSignal): Promise<void> {
    try {
        await doWork(signal);
    } catch (err) {
        if (signal.aborted) {
            throw new CliError(CLI_CODES.APP.ABORTED, "cli.app.aborted", {
                cause: err instanceof Error ? err : undefined,
                context: { reason: signal.reason },
            });
        }
        throw new CliError(CLI_CODES.APP.RUN_FAILED, "cli.app.run_failed", {
            cause: err instanceof Error ? err : undefined,
        });
    }
}

function handleError(err: unknown): void {
    if (err instanceof CliError) {
        // Narrow on the stable code — no message-string matching needed.
        switch (err.code) {
            case CLI_CODES.APP.ABORTED:
                process.exitCode = 130; // standard SIGINT exit code
                break;
            case CLI_CODES.PARSE.UNKNOWN_OPTION:
                console.error(`Unknown option: ${String(err.context?.["option"])}`);
                process.exitCode = 1;
                break;
            default:
                console.error(err.message);
                process.exitCode = 1;
        }
    }
}

API Reference

CLI_CODES

const CLI_CODES: {
    PARSE: {
        UNKNOWN_OPTION: "CLI.PARSE.UNKNOWN_OPTION";
        MISSING_VALUE: "CLI.PARSE.MISSING_VALUE";
        INVALID_VALUE: "CLI.PARSE.INVALID_VALUE";
        MISSING_ARGUMENT: "CLI.PARSE.MISSING_ARGUMENT";
        MISSING_REQUIRED_OPTION: "CLI.PARSE.MISSING_REQUIRED_OPTION";
    };
    COMMAND: {
        NO_ACTION: "CLI.COMMAND.NO_ACTION";
        UNKNOWN_SUBCOMMAND: "CLI.COMMAND.UNKNOWN_SUBCOMMAND";
    };
    APP: {
        RUN_FAILED: "CLI.APP.RUN_FAILED";
        ABORTED: "CLI.APP.ABORTED";
    };
}

Immutable registry of all error codes used by @suaveplan/cli. Each value is a dot-delimited string in the form <NAMESPACE>.<SUBCATEGORY>.<NAME>.

CliCode

type CliCode =
    | "CLI.PARSE.UNKNOWN_OPTION"
    | "CLI.PARSE.MISSING_VALUE"
    | "CLI.PARSE.INVALID_VALUE"
    | "CLI.PARSE.MISSING_ARGUMENT"
    | "CLI.PARSE.MISSING_REQUIRED_OPTION"
    | "CLI.COMMAND.NO_ACTION"
    | "CLI.COMMAND.UNKNOWN_SUBCOMMAND"
    | "CLI.APP.RUN_FAILED"
    | "CLI.APP.ABORTED";

Union of all values in CLI_CODES. Use as the type parameter for SuaveplanError<CliCode> or in exhaustive switch statements.

CliError

class CliError extends SuaveplanError<CliCode> {
    public constructor(
        code: CliCode,
        messageKey: string,
        options?: SuaveplanErrorOptions
    ): CliError;
}

Typed error for all @suaveplan/cli failures.

  • code — one of the CliCode values; accessible as err.code via the base class.
  • messageKey — i18n message key passed to the base class for message resolver integration.
  • options.cause — optional upstream Error for cause chaining; accessible as err.cause.
  • options.context — optional Record<string, unknown> with structured diagnostic metadata; accessible as err.context.

Throws: nothing — this is an Error constructor.

Implementation Notes

CLI_CODES is built from three separate defineCodes() calls rather than one flat call so that each sub-namespace is isolated. This mirrors the pattern established in @suaveplan/readline and keeps future code-set additions within a namespace non-breaking for callers who already destructure CLI_CODES.PARSE.

The CliError constructor comment (biome-ignore) documents why the constructor is not "useless" from Biome's perspective: the base SuaveplanError constructor is protected, so widening it to public here is a meaningful override even though the body only calls super(). This is the standard pattern across all @suaveplan/* packages.

No runtime dependencies beyond @suaveplan/error are introduced. The defineCodes helper produces plain string literals, so the entire module tree-shakes to near zero bytes when errors are not thrown on a given code path.