alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
750 lines • 25.1 kB
TypeScript
import { Alepha, AlephaError, Async, KIND, Primitive, Static, TObject, TSchema, TString } from "alepha";
import { ConsoleColorProvider } from "alepha/logger";
import { FileSystemProvider, ShellProvider } from "alepha/system";
import * as fs from "node:fs/promises";
import { glob } from "node:fs/promises";
import { DateTimeProvider } from "alepha/datetime";
//#region ../../src/command/errors/CommandError.d.ts
declare class CommandError extends AlephaError {
readonly name = "CommandError";
}
//#endregion
//#region ../../src/command/helpers/Asker.d.ts
interface AskOptions<T extends TSchema = TString> {
/**
* Response schema expected.
*
* Recommended schemas:
* - z.text() - for free text input
* - z.number() - for numeric input
* - z.boolean() - for yes/no input (accepts "true", "false", "1", "0")
* - z.enum(["option1", "option2"]) - for predefined options
*
* You can use schema.default to provide a default value.
*
* @example
* ```ts
* ask("What is your name?", { schema: z.text({ default: "John Doe" }) })
* ```
*
* @default TString
*/
schema?: T;
/**
* Custom validation function.
* Throws an AlephaError in case of validation failure.
*/
validate?: (value: Static<T>) => void;
}
interface AskMethod {
<T extends TSchema = TString>(question: string, options?: AskOptions<T>): Promise<Static<T>>;
permission: (question: string) => Promise<boolean>;
intro: (title: string) => void;
outro: (message: string) => void;
}
/**
* Reads interactive input from the terminal using plain readline prompts.
*
* One straightforward code path: questions are printed through the logger
* and answers are read with Node's `readline`. No raw-mode cursor control,
* no ANSI framing — output stays greppable and works the same in a TTY,
* under CI, or when piped.
*/
declare class Asker {
protected readonly log: import("alepha/logger").Logger;
readonly ask: AskMethod;
protected readonly alepha: Alepha;
constructor();
protected createAskMethod(): AskMethod;
protected prompt<T extends TSchema = TString>(question: string, options: AskOptions<T>): Promise<Static<T>>;
protected createPromptInterface(): import("node:readline/promises").Interface;
}
//#endregion
//#region ../../src/command/helpers/EnvUtils.d.ts
declare class EnvUtils {
protected readonly log: import("alepha/logger").Logger;
protected readonly fs: FileSystemProvider;
/**
* Load environment variables from .env files into process.env.
*
* Variables that already exist in process.env are NOT overwritten,
* matching the standard dotenv convention where the shell environment
* takes precedence over .env file values.
*
* By default, it loads from ".env" and ".env.local".
* You can specify additional files to load, e.g. [".env", ".env.production"].
*/
loadEnv(root: string, files?: string[]): Promise<void>;
/**
* Parse environment variables from .env files without mutating process.env.
*
* Returns a merged record from all files (later files override earlier ones).
* For each file, also tries the `.local` variant (e.g. `.env.production.local`).
*/
parseEnv(root: string, files?: string[]): Promise<Record<string, string>>;
}
//#endregion
//#region ../../src/command/helpers/Runner.d.ts
type Task = {
name: string;
handler: () => any;
};
interface Timer {
name: string;
duration: string;
}
interface RunOptions {
/**
* Rename the command for logging purposes.
*/
alias?: string;
/**
* Root directory to execute the command in.
*/
root?: string;
}
interface RunnerMethod {
(cmd: string | Task | Array<string | Task>, options?: RunOptions | (() => any)): Promise<string>;
rm: (glob: string | string[], options?: RunOptions) => Promise<string>;
cp: (source: string, dest: string, options?: RunOptions) => Promise<string>;
/**
* Ends the runner and prints a summary of executed tasks.
*
* > This is automatically called at the end of command execution.
* > But can be called manually if needed to print more stuff before the command ends.
*/
end: () => void;
}
/**
* Runs CLI tasks (shell commands or functions) and logs their lifecycle.
*
* Output is intentionally plain and verbose: every task logs a
* `Starting …` / `Finished … after Ns` line through the standard logger,
* and shelled commands **stream** their stdout/stderr straight to the
* terminal (`capture: false`) so tool output — `vite build` warnings,
* Biome diagnostics, nested `alepha` subcommands — is visible live.
*/
declare class Runner {
protected readonly log: import("alepha/logger").Logger;
protected readonly dateTime: DateTimeProvider;
protected timers: Timer[];
protected readonly startTime: number;
protected readonly alepha: Alepha;
protected readonly shell: ShellProvider;
readonly run: RunnerMethod;
constructor();
/**
* Start a new command session.
*
* Retained for API compatibility (the CLI calls it before each command);
* task lifecycle is now logged statelessly, so there is nothing to reset.
*/
startCommand(_cliName: string, _commandName: string): void;
protected createRunMethod(): RunnerMethod;
protected exec(cmd: string, opts?: {
root?: string;
}): Promise<string>;
/**
* Executes one or more tasks.
*
* @param task - A single task or an array of tasks to run in parallel.
*/
protected execute(task: Task | Task[]): Promise<string>;
/**
* Prints a summary of all executed tasks and their durations.
*/
end(): void;
protected executeTask(task: Task): Promise<string>;
}
//#endregion
//#region ../../src/command/primitives/$command.d.ts
/**
* Declares a CLI command.
*
* This primitive allows you to define a command, its flags, and its handler
* within your Alepha application structure.
*/
declare const $command: {
<T extends TObject, A extends TSchema, E extends TObject>(options: CommandPrimitiveOptions<T, A, E>): CommandPrimitive<T, A, E>;
[KIND]: typeof CommandPrimitive;
};
interface CommandPrimitiveOptions<T extends TObject, A extends TSchema, E extends TObject = TObject> {
/**
* The handler function to execute when the command is matched.
*
* For parent commands with children, the handler is called when:
* - The parent command is invoked without a subcommand
* - The parent command is invoked with --help (to show available subcommands)
*/
handler: (args: CommandHandlerArgs<T, A, E>) => Async<void>;
/**
* The name of the command. If omitted, the property key is used.
*
* An empty string "" denotes the root command.
*/
name?: string;
/**
* A short description of the command, shown in the help message.
*/
description?: string;
/**
* An array of alternative names for the command.
*/
aliases?: string[];
/**
* A TypeBox object schema defining the flags for the command.
*/
flags?: T;
/**
* A TypeBox object schema defining required environment variables.
*
* Environment variables are validated before the handler runs (fail fast).
* They are displayed in the help output under "Env:" section.
*
* @example
* ```ts
* $command({
* env: z.object({
* VERCEL_TOKEN: z.text({ description: "Vercel API token" }),
* VERCEL_ORG_ID: z.text({ description: "Organization ID" }).optional(),
* }),
* handler: async ({ env }) => {
* // env.VERCEL_TOKEN is typed & guaranteed to exist
* console.log(env.VERCEL_TOKEN);
* }
* })
* ```
*/
env?: E;
/**
* An optional TypeBox schema defining the arguments for the command.
*
* @example
* args: z.text()
* my-cli command <arg1: string>
*
* args: z.text().optional()
* my-cli command [arg1: string]
*
* args: z.tuple([z.text(), z.number()])
* my-cli command <arg1: string> <arg2: number>
*
* args: z.tuple([z.text(), z.number().optional()])
* my-cli command <arg1: string> [arg2: number]
*/
args?: A;
/**
* Marks this command as the root command.
* Equivalent to setting name to an empty string "".
*/
root?: boolean;
/**
* Run this command's handler BEFORE the specified target command.
*
* Pre-hooks are not listed in help and cannot be called directly.
* They receive the same parsed flags and args as the target command.
*
* @example
* ```ts
* class BuildCommands {
* prebuild = $command({
* pre: "build",
* handler: async ({ run }) => {
* await run("cleaning dist folder...", () => fs.rm("dist"));
* }
* });
*
* build = $command({
* name: "build",
* handler: async () => { ... }
* });
* }
* ```
*/
pre?: string;
/**
* Run this command's handler AFTER the specified target command.
*
* Post-hooks are not listed in help and cannot be called directly.
* They receive the same parsed flags and args as the target command.
*
* @example
* ```ts
* class BuildCommands {
* build = $command({
* name: "build",
* handler: async () => { ... }
* });
*
* postbuild = $command({
* post: "build",
* handler: async ({ run }) => {
* await run("generating checksums...", generateChecksums);
* }
* });
* }
* ```
*/
post?: string;
/**
* If true, this command will be hidden from the help output.
*/
hide?: boolean;
/**
* Adds a `--mode, -m` flag to load environment files.
*
* When enabled:
* - Loads `.env` and `.env.local` by default
* - With `--mode production`, also loads `.env.production` and `.env.production.local`
* - The mode value is exposed in the handler as `mode: string | undefined`
*
* Set to `true` to enable with no default, or a string to set a default mode.
*
* This follows Vite's environment loading convention.
* @see https://vite.dev/guide/env-and-mode
*
* @example
* ```ts
* // No default mode
* build = $command({
* mode: true,
* handler: async ({ mode }) => {
* console.log(`Building for ${mode ?? 'development'}...`);
* }
* });
*
* // Default mode "production"
* deploy = $command({
* mode: "production",
* handler: async ({ mode }) => {
* console.log(`Deploying for ${mode}...`); // always defined
* }
* });
* ```
*
* Usage:
* - `cli build` - loads .env (mode = undefined)
* - `cli build --mode production` - loads .env and .env.production
* - `cli deploy` - loads .env and .env.production (default mode)
* - `cli deploy --mode staging` - loads .env and .env.staging
*/
mode?: boolean | string;
/**
* Child commands (subcommands) for this command.
*
* When children are defined, the command becomes a parent command that
* can be invoked with space-separated subcommands:
*
* @example
* ```ts
* class DeployCommands {
* // Subcommands
* vercel = $command({
* description: "Deploy to Vercel",
* handler: async () => { ... }
* });
*
* cloudflare = $command({
* description: "Deploy to Cloudflare",
* handler: async () => { ... }
* });
*
* // Parent command with children
* deploy = $command({
* description: "Deploy the application",
* children: [this.vercel, this.cloudflare],
* handler: async () => {
* // Called when "deploy" is invoked without subcommand
* console.log("Available: deploy vercel, deploy cloudflare");
* }
* });
* }
* ```
*
* This allows CLI usage like:
* - `cli deploy vercel` - runs the vercel subcommand
* - `cli deploy cloudflare` - runs the cloudflare subcommand
* - `cli deploy` - runs the parent handler (shows available subcommands)
* - `cli deploy --help` - shows help with all available subcommands
*/
children?: CommandPrimitive<any, any>[];
}
declare class CommandPrimitive<T extends TObject = TObject, A extends TSchema = TSchema, E extends TObject = TObject> extends Primitive<CommandPrimitiveOptions<T, A, E>> {
readonly flags: T | import("zod").ZodObject<{}, import("zod/v4/core").$strip>;
readonly env: E | import("zod").ZodObject<{}, import("zod/v4/core").$strip>;
readonly aliases: string[];
protected onInit(): void;
get name(): string;
/**
* Get the child commands (subcommands) for this command.
*/
get children(): CommandPrimitive<any, any>[];
/**
* Check if this command has child commands (is a parent command).
*/
get hasChildren(): boolean;
/**
* Find a child command by name or alias.
*/
findChild(name: string): CommandPrimitive<any, any> | undefined;
}
interface CommandHandlerArgs<T extends TObject, A extends TSchema = TSchema, E extends TObject = TObject> {
flags: Static<T>;
args: A extends TSchema ? Static<A> : Array<string>;
env: Static<E>;
run: RunnerMethod;
ask: AskMethod;
glob: typeof glob;
fs: typeof fs;
/**
* The root directory where the command is executed.
*/
root: string;
/**
* Display help for the current command.
*
* Useful for parent commands with children to show available subcommands
* when invoked without a specific subcommand.
*
* @example
* ```ts
* deploy = $command({
* children: [this.vercel, this.cloudflare],
* handler: async ({ help }) => {
* help(); // Shows available subcommands
* }
* });
* ```
*/
help: () => void;
/**
* The current execution mode (e.g., "development", "production", "staging").
*
* Use --mode flag to set this value when running the command.
*/
mode?: string;
}
//#endregion
//#region ../../src/command/providers/CliProvider.d.ts
declare const envSchema: import("zod").ZodObject<{
CLI_NAME: import("zod").ZodString;
CLI_DESCRIPTION: import("zod").ZodString;
}, import("zod/v4/core").$strip>;
declare module "alepha" {
interface Env extends Partial<Static<typeof envSchema>> {}
}
/**
* CLI provider configuration atom
*/
declare const cliOptions: import("alepha").Atom<import("zod").ZodObject<{
name: import("zod").ZodOptional<import("zod").ZodString>;
description: import("zod").ZodOptional<import("zod").ZodString>;
argv: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
}, import("zod/v4/core").$strip>, "alepha.command.cli.options">;
type CliProviderOptions = Static<typeof cliOptions.schema>;
declare module "alepha" {
interface State {
[cliOptions.key]: CliProviderOptions;
}
}
/**
* CLI provider for parsing and executing commands.
*
* Handles:
* - Command resolution (simple, nested, colon-notation)
* - Flag and argument parsing
* - Environment variable validation
* - Help generation
* - Pre/post command hooks
*
* @example
* ```typescript
* // Define a command
* class MyCommands {
* build = $command({
* name: "build",
* description: "Build the project",
* flags: z.object({ watch: z.boolean().optional() }),
* handler: async ({ flags }) => { ... }
* });
* }
*
* // CLI automatically discovers and executes commands
* const alepha = Alepha.create().with(MyCommands);
* ```
*/
declare class CliProvider {
protected readonly env: {
CLI_NAME: string;
CLI_DESCRIPTION: string;
};
protected readonly alepha: Alepha;
protected readonly log: import("alepha/logger").Logger;
protected readonly color: ConsoleColorProvider;
protected readonly runner: Runner;
protected readonly asker: Asker;
protected readonly envUtils: EnvUtils;
protected readonly options: Readonly<{
name?: string | undefined;
description?: string | undefined;
argv?: string[] | undefined;
}>;
protected get name(): string;
protected get description(): string;
protected get argv(): string[];
/**
* Global flags available to all commands.
*/
protected readonly globalFlags: {
help: {
aliases: string[];
description: string;
schema: import("zod").ZodBoolean;
};
verbose: {
aliases: string[];
description: string;
schema: import("zod").ZodBoolean;
};
};
/**
* Main entry point - resolves and executes the command from process.argv.
* This is the production execution path with full lifecycle support.
*/
protected readonly onReady: import("alepha").HookPrimitive<"ready">;
/**
* Execute a command with full lifecycle support.
*
* This is the production execution path that includes:
* - Mode-based .env file loading
* - Pre/post command hooks
* - Runner session for pretty CLI output
* - Alepha context wrapper for proper scoping
*
* @see run() for a lightweight test-only alternative
*/
protected executeCommand(command: CommandPrimitive<TObject>, argv: string[], isRootCommand: boolean): Promise<void>;
/**
* Remove consumed command path arguments from argv (keeps flags and remaining args).
*/
protected removeConsumedArgs(argv: string[], consumedArgs: string[]): string[];
/**
* Resolve a command from positional arguments.
*
* Supports:
* 1. Space-separated subcommands: `deploy vercel` -> finds deploy command, then vercel child
* 2. Colon notation (backwards compat): `deploy:vercel` -> finds command with name "deploy:vercel"
* 3. Simple commands: `build` -> finds command with name "build"
*/
protected resolveCommand(positionalArgs: string[]): {
command: CommandPrimitive<TObject> | undefined;
consumedArgs: string[];
};
/**
* Get all registered commands in the application.
*/
get commands(): CommandPrimitive<any>[];
/**
* Execute a command handler with given arguments.
*
* This is a **lightweight test helper** that directly invokes the command handler
* without the full production lifecycle. It intentionally skips:
* - Pre/post command hooks
* - Runner session (pretty CLI output)
* - Alepha context wrapper
* - .env.{mode} file loading
*
* For production execution, the `onReady` hook uses `executeCommand()` which
* provides the full lifecycle. Merging them would either make this method too
* heavy for simple testing or require many optional parameters to toggle behaviors.
*
* @example
* ```typescript
* // In tests
* const cli = alepha.inject(CliProvider);
* const cmd = alepha.inject(InitCommand);
*
* await cli.run(cmd.init, "--agent --pm=yarn");
* await cli.run(cmd.init, { argv: "--agent", root: "/project" });
* ```
*/
run<T extends TObject, A extends TSchema>(command: CommandPrimitive<T, A>, options?: string | string[] | {
argv?: string | string[];
root?: string;
}): Promise<void>;
/**
* Find a command by name or alias
*/
protected findCommand(name: string): CommandPrimitive<TObject> | undefined;
/**
* Find a top-level command by name or alias (excludes child commands)
*/
protected findTopLevelCommand(name: string): CommandPrimitive<TObject> | undefined;
/**
* Find all pre-hooks for a command (commands named `pre{commandName}`)
*/
protected findPreHooks(commandName: string): CommandPrimitive<TObject>[];
/**
* Find all post-hooks for a command (commands named `post{commandName}`)
*/
protected findPostHooks(commandName: string): CommandPrimitive<TObject>[];
/**
* Get global flags (help only, root command flags are NOT global)
*/
protected getAllGlobalFlags(): Record<string, {
aliases: string[];
description?: string;
schema: TSchema;
}>;
/**
* Read a schema's metadata (`title`, `description`, `aliases`, `alias`, …).
*
* Under zod these options live on the schema's `.meta()` registry rather than
* as direct properties (typebox), and they sit on the INNER schema — so any
* optional / nullable / default wrappers are peeled first.
*/
protected schemaMeta(schema: TSchema | undefined): Record<string, any>;
/**
* Build flag definitions (key, aliases, description, schema) from a flags
* object schema. Centralises the metadata reading so every call-site (parsing,
* arg-splitting, help) extracts aliases/descriptions the same way.
*/
protected extractFlagDefs(schema: TObject): Array<{
key: string;
aliases: string[];
description?: string;
schema: TSchema;
}>;
/**
* Parse command flags from argv using the command's flag schema
*/
protected parseCommandFlags(argv: string[], schema: TObject, options?: {
modeEnabled?: boolean;
}): Record<string, any>;
/**
* Parse and validate environment variables using the command's env schema
*/
protected parseCommandEnv(schema: TObject, commandName: string): Record<string, any>;
/**
* Parse --mode or -m flag from argv for environment file loading
*/
protected parseModeFlag(argv: string[]): string | undefined;
/**
* Load .env and .env.{mode} files into process.env
*/
protected loadModeEnv(root: string, mode: string | undefined): Promise<void>;
/**
* Low-level flag parser - extracts flag values from argv based on definitions
*/
protected parseFlags(argv: string[], flagDefs: {
key: string;
aliases: string[];
schema: TSchema;
}[], options?: {
strict?: boolean;
}): Record<string, any>;
/**
* Convert a raw flag value string into the value its schema expects.
*
* zod no longer coerces, so scalar values (number / integer / boolean) are
* cast + validated via {@link parseArgumentValue} (same path as positional
* args); object / array / record values are JSON-parsed. `schema` is expected
* to already be unwrapped of optional/nullable/default.
*/
protected castFlagValue(value: string, schema: TSchema, rawKey: string): any;
/**
* Get indices of argv elements consumed by flags (for separating args from flags)
*/
protected getFlagConsumedIndices(argv: string[], flagDefs: {
key: string;
aliases: string[];
schema: TSchema;
}[]): Set<number>;
protected parseCommandArgs(argv: string[], schema?: TSchema, isRootCommand?: boolean, flagSchema?: TObject): any;
/**
* Convert a string argument value to the appropriate type based on schema
*/
protected parseArgumentValue(value: string, schema: TSchema): any;
/**
* Generate usage string for command arguments (e.g., "<path>" or "[path]")
*/
protected generateArgsUsage(schema?: TSchema): string;
/**
* Get display type name for a schema (e.g., ": number", ": boolean")
*/
protected getTypeName(schema: TSchema): string;
/**
* Print help for a specific command or general CLI help.
*
* @param command - If provided, shows help for this specific command.
* If omitted, shows general CLI help with all commands.
*/
printHelp(command?: CommandPrimitive<any>): void;
/**
* Generate colored usage string for command arguments (for help display)
*/
protected generateColoredArgsUsage(schema?: TSchema): string;
/**
* Get the full command path (e.g., "deploy vercel" for a nested command)
*/
protected getCommandPath(command: CommandPrimitive<any>): string;
/**
* Find the parent command of a nested command
*/
protected findParentCommand(command: CommandPrimitive<any>): CommandPrimitive<any> | undefined;
/**
* Get top-level commands (commands that are not children of other commands)
*/
protected getTopLevelCommands(): CommandPrimitive<any>[];
/**
* Calculate max display length for child commands (for help alignment)
*/
protected getMaxChildCmdLength(children: CommandPrimitive<any>[]): number;
/**
* Calculate max display length for commands (for help alignment)
*/
protected getMaxCmdLength(commands: CommandPrimitive[]): number;
/**
* Calculate max display length for flags (for help alignment)
*/
protected getMaxFlagLength(flags: {
aliases: string[];
}[]): number;
/**
* Extract enum values from a schema if it represents an enum.
* Returns undefined if the schema is not an enum.
*/
protected getEnumValues(schema: TSchema): string[] | undefined;
/**
* Format flag description with enum values if applicable.
*/
protected formatFlagDescription(description: string | undefined, schema: TSchema | undefined): string;
}
//#endregion
//#region ../../src/command/index.d.ts
/**
* Declarative CLI command framework.
*
* **Features:**
* - CLI command definitions
* - Interactive CLI prompts (plain readline)
* - Command execution with streamed, verbose output
* - Environment variable utilities
* - Schema validation for CLI arguments
*
* @module alepha.command
*/
declare const AlephaCommand: import("alepha").Service<import("alepha").Module>;
declare module "alepha" {
interface StringOptions {
/**
* Additional aliases for the flags.
*
* @module alepha.command
*/
aliases?: string[];
}
}
//#endregion
export { $command, AlephaCommand, AskMethod, AskOptions, Asker, CliProvider, CliProviderOptions, CommandError, CommandHandlerArgs, CommandPrimitive, CommandPrimitiveOptions, EnvUtils, RunOptions, Runner, RunnerMethod, Task, cliOptions };
//# sourceMappingURL=index.d.ts.map