import type { PadroneBuilder, PadroneProgram } from '../types/builder.ts'; import type { AnyPadroneCommand, PadroneCommand } from '../types/index.ts'; import type { PadroneSchema } from '../types/schema.ts'; /** * Use this type instead of `any` when you intend to fix it later * @deprecated Please replace with an actual type */ export type TODO = TCast; export type SafeString = string & {}; type IsUnknown = unknown extends T ? true : false; type IsAny = any extends T ? true : false; type IsNever = [T] extends [never] ? true : false; export type IsGeneric = IsAny extends true ? true : IsUnknown extends true ? true : IsNever extends true ? true : false; /** * Detects whether a schema has been branded as async via the `'~async'` property. * Standard Schema V1's `validate()` always types its return as `Result | Promise` * regardless of whether the schema is actually async, so we rely on an explicit brand instead. * * Use `asyncSchema(schema)` to brand a schema, or check for the `{ '~async': true }` property. */ export type IsAsyncSchema = IsAny extends true ? false : T extends { '~async': true } ? true : false; /** * Computes the new TAsync flag when a schema is added to a builder. * Once TAsync is `true`, it stays `true`. Otherwise, checks if the new schema is branded async. */ export type OrAsync = TExisting extends true ? true : IsAsyncSchema extends true ? true : false; /** * Detects whether argument meta contains interactive or optionalInteractive configuration. * When either is `true` or a `string[]`, the command requires async execution for prompting. */ export type HasInteractive = TMeta extends { interactive: true | readonly string[] } ? true : TMeta extends { optionalInteractive: true | readonly string[] } ? true : false; /** * Combines schema-level async detection with meta-level interactive detection. * Returns `true` if the existing async flag is set, the schema is branded async, or the meta has interactive fields. */ export type OrAsyncMeta = TExisting extends true ? true : HasInteractive extends true ? true : false; /** * Unwraps a result type by resolving Promises and collecting iterables into arrays. * - `AsyncIterable` → `U[]` * - `Iterable` (excluding strings) → `U[]` * - `Promise` → `Drained` (recursively unwraps) * - `T` → `T` */ export type Drained = T extends Promise ? Drained : T extends AsyncIterable ? U[] : T extends string ? T : T extends Iterable ? U[] : T; /** * A sync value augmented with Promise-like methods (.then, .catch, .finally). * Unlike a real Promise, properties of T are accessible synchronously. */ export type Thenable = T & PromiseLike & { catch: Promise['catch']; finally: Promise['finally'] }; /** * Conditionally wraps a type in Promise based on the TAsync flag. * - `true` → `Promise` * - `false` → `T & Thenable` (thenable: supports `.then()`, `.catch()`, `.finally()`, and `await`) * - `boolean` (union of true|false) → `Promise` (safe default when async-ness is uncertain) * - `any` → `T` (for generic/any typed commands like AnyPadroneCommand) */ export type MaybePromise = IsAny extends true ? T : true extends TAsync ? Promise : Thenable; type SplitString = TName extends `${infer FirstPart}${TSplitBy}${infer RestParts}` ? [FirstPart, ...SplitString] : [TName]; type JoinString = TParts extends [ infer FirstPart extends string, ...infer RestParts extends string[], ] ? RestParts extends [] ? FirstPart : `${FirstPart}${TJoinBy}${JoinString}` : TParts extends [] ? '' : TParts[number]; type SplitLastSpace = SplitString extends [...infer Init extends string[], infer Last extends string] ? Init extends [] ? [S, never] : [JoinString, Last] : [S, never]; type AnyPartExtends = [U] extends [never] ? false : U extends any ? (U extends T ? true : never) : never extends true ? true : false; export type FullCommandName = TParentName extends '' ? TName : `${TParentName} ${TName}`; /** * Generate full alias paths by combining parent path with each alias. */ type FullAliasPaths = TAliases extends [ infer First extends string, ...infer Rest extends string[], ] ? FullCommandName | FullAliasPaths : never; /** * Get all paths for a command including its primary path and all alias paths. */ type GetCommandPathsAndAliases = TCommand['~types']['path'] extends infer Path extends string ? TCommand['~types']['aliases'] extends infer Aliases extends string[] ? TCommand['~types']['parentName'] extends infer ParentName extends string ? Path | FullAliasPaths : Path : Path : never; /** * Find a direct child command in a tuple by name. * Unlike PickCommandByName, this does NOT flatten — it only checks direct children by their `name` field. * Uses indexed access (O(1) depth) instead of recursive tuple walking. */ export type FindDirectChild = Extract< TCommands[number], { '~types': { name: TName } } >; /** * Replace a command in a tuple by name, or append if not found. * Used by `.command()` override semantics: re-registering a name replaces that entry. * Uses mapped type (O(1) depth) instead of recursive tuple walking. */ export type ReplaceOrAppendCommand = HasDirectChild extends true ? ReplaceInTuple : [...TCommands, TNew]; type HasDirectChild = TName extends TCommands[number]['~types']['name'] ? true : false; type ReplaceInTuple = { [K in keyof TCommands]: TCommands[K] extends AnyPadroneCommand ? TCommands[K]['~types']['name'] extends TName ? TNew : TCommands[K] : TCommands[K]; }; /** * Utility type for extensions that add a command to a builder/program. * Replaces the boilerplate `With*` pattern used across all extension files. */ export type WithCommand = T extends { '~types': { programName: infer PN extends string; name: infer N extends string; parentName: infer PaN extends string; argsSchema: infer A extends PadroneSchema; result: infer R; commands: infer C extends [...AnyPadroneCommand[]]; async: infer AS extends boolean; context: infer CTX; contextProvided: infer CTXP; }; } ? T extends { run: any } ? PadroneProgram, any, AS, CTX, CTXP> : PadroneBuilder, any, AS, CTX, CTXP> : T; /** * Utility type for extensions that register a context-providing interceptor. * Extends `TContextProvided` with `TProvides` while preserving all other builder/program type params. */ export type WithInterceptor = T extends { '~types': { programName: infer PN extends string; name: infer N extends string; parentName: infer PaN extends string; argsSchema: infer A extends PadroneSchema; result: infer R; commands: infer C extends [...AnyPadroneCommand[]]; async: infer AS extends boolean; context: infer CTX; contextProvided: infer CTXP; }; } ? T extends { run: any } ? PadroneProgram : PadroneBuilder : T; /** * Utility type for extensions that force the builder/program into async mode. * Sets `TAsync` to `true` while preserving all other type params. */ export type WithAsync = T extends { '~types': { programName: infer PN extends string; name: infer N extends string; parentName: infer PaN extends string; argsSchema: infer A extends PadroneSchema; result: infer R; commands: infer C extends [...AnyPadroneCommand[]]; async: any; context: infer CTX; contextProvided: infer CTXP; }; } ? T extends { run: any } ? PadroneProgram : PadroneBuilder : T; export type PickCommandByName< TCommands extends AnyPadroneCommand[], TName extends string | AnyPadroneCommand, > = TName extends AnyPadroneCommand ? TName : FlattenCommands extends infer Cmd extends AnyPadroneCommand ? Cmd extends AnyPadroneCommand ? TName extends GetCommandPathsAndAliases ? Cmd : never : never : never; export type FlattenCommands = TCommands extends [] ? never : number extends TCommands['length'] ? IsAny extends true ? never : TCommands[number] : TCommands[number] extends infer Cmd extends AnyPadroneCommand ? Cmd | FlattenCommands : never; /** * Get all command paths including alias paths for all commands. */ type GetCommandPathsOrAliases = GetCommandPathsAndAliases>; /** * Find all the commands that are prefixed with a command name or alias. * This is needed to avoid matching other commands when followed by a space and another word. * For example, let's say `level1` and `level1 level2` are commands. * Then `level1 ${string}` would also match `level1 level2`, * and it would cause `level1 level2` to not show up in the autocomplete. * By excluding those cases, we can ensure autocomplete works correctly. */ type PrefixedCommands = GetCommandPathsOrAliases extends infer CommandNames ? CommandNames extends string ? AnyPartExtends, `${CommandNames} ${string}`> extends true ? never : `${CommandNames} ${string}` : never : never; /** * The possible commands are the commands that can be parsed by the program. * This includes the string that are exact matches to a command name or alias, and strings that are prefixed with a command name or alias. */ export type PossibleCommands< TCommands extends AnyPadroneCommand[], TWithPrefixed extends boolean = false, TWithObjects extends boolean = false, TWithFallback extends boolean = true, > = | GetCommandPathsOrAliases | (TWithPrefixed extends true ? PrefixedCommands : never) | (TWithObjects extends true ? FlattenCommands : never) | (TWithFallback extends true ? SafeString : never); type CommandIsUnknownable = IsGeneric extends true ? true : string extends TCommand ? true : SafeString extends TCommand ? true : false; /** * Match a string to a command by the possible commands. * This is done by recursively splitting the string by the last space, and then checking if the prefix is a valid command name or alias. * This is needed to avoid matching the top-level command when there are nested commands. */ /** * Recursively re-paths a command's children under a new parent path. * Used by `mount()` to update all nested command paths when a program is mounted as a subcommand. */ export type RepathCommands = TCommands extends [ infer First extends AnyPadroneCommand, ...infer Rest extends AnyPadroneCommand[], ] ? [RepathCommand, ...RepathCommands] : []; type RepathCommand = PadroneCommand< TCommand['~types']['name'], TNewParentName, TCommand['~types']['argsSchema'], TCommand['~types']['result'], RepathCommands>, TCommand['~types']['aliases'], TCommand['~types']['async'], TCommand['~types']['context'], TCommand['~types']['contextProvided'] >; export type PickCommandByPossibleCommands< TCommands extends AnyPadroneCommand[], TCommand extends PossibleCommands | SafeString, > = CommandIsUnknownable extends true ? FlattenCommands : TCommand extends AnyPadroneCommand ? TCommand : TCommand extends string ? TCommand extends GetCommandPathsOrAliases ? PickCommandByName : SplitLastSpace extends [infer Prefix extends string, infer Rest] ? IsNever extends true ? PickCommandByName : PickCommandByPossibleCommands : never : never;