import type { App, ArgMeta, ArgsOption, CommandContext, Exec, InternalArgMeta, InternalArgToken, Lib, Module, Pkg, PrimitiveArgType, Sys, } from "./argMeta"; import { normalizePrimitiveArgType } from "./argMeta"; import { assertUniqueDependencies, type DependencyInstanceMap, injectDependencies } from "./dependencyBuilder"; import { COMMAND_META, type CommandCls, type TargetMeta, type TargetOption } from "./targetMeta"; import type { DependencyCls, DependencyKey } from "./types"; type PrimitiveValue = T extends StringConstructor ? string : T extends NumberConstructor ? number : boolean; type MaybeNullable = Option extends { nullable: true } ? Value | null : Value; type AddArg = [ ...Params, MaybeNullable, Option>, ]; type AddInternalArg = [...Params, Token["_value"]]; type AddInternalArgs = Tokens extends readonly [ infer Head extends InternalArgToken, ...infer Rest extends InternalArgToken[], ] ? AddInternalArgs, Rest> : Params; type ContextFromToken = Token["_value"] extends App ? { app: App } : Token["_value"] extends Lib ? { lib: Lib } : Token["_value"] extends Sys ? { sys: Sys } : Token["_value"] extends Pkg ? { pkg: Pkg } : Token["_value"] extends Module ? { module: Module } : Token["_value"] extends Exec ? { exec: Exec } : object; type AddInternalContext = Tokens extends readonly [ infer Head extends InternalArgToken, ...infer Rest extends InternalArgToken[], ] ? AddInternalContext, Rest> : Context; type CommandHandler = ( this: DependencyInstanceMap, ...args: Params ) => unknown | Promise; class TargetBuilder { readonly #args: (ArgMeta | InternalArgMeta)[]; constructor( private readonly targetOption: TargetOption, args: (ArgMeta | InternalArgMeta)[] = [], ) { this.#args = args; } arg = ArgsOption>( name: string, type: Type, argsOption: Option = {} as Option, ): TargetBuilder, Context> { return new TargetBuilder, Context>(this.targetOption, [ ...this.#args, { name, argsOption: { ...argsOption, type: normalizePrimitiveArgType(type) }, key: "", idx: this.#args.length, type: "Argument", } as ArgMeta, ]); } option = ArgsOption>( name: string, type: Type, argsOption: Option = {} as Option, ): TargetBuilder, Context> { return new TargetBuilder, Context>(this.targetOption, [ ...this.#args, { name, argsOption: { ...argsOption, type: normalizePrimitiveArgType(type) }, key: "", idx: this.#args.length, type: "Option", } as ArgMeta, ]); } with( ...tokens: Tokens ): TargetBuilder, AddInternalContext> { return new TargetBuilder, AddInternalContext>( this.targetOption, [ ...this.#args, ...tokens.map( (token, offset) => ({ key: "", idx: this.#args.length + offset, type: token.type, }) satisfies InternalArgMeta, ), ], ); } exec(handler: CommandHandler) { return { args: this.#args, handler: handler as TargetMeta["handler"], targetOption: this.targetOption, }; } } type TargetDefinition = ReturnType["exec"]>; type CommandBuilderContext = { public: (targetOption?: Omit) => TargetBuilder; cloud: (targetOption?: Omit) => TargetBuilder; dev: (targetOption?: Omit) => TargetBuilder; arg: = ArgsOption>( name: string, type: Type, argsOption?: Option, ) => ArgMeta; option: = ArgsOption>( name: string, type: Type, argsOption?: Option, ) => ArgMeta; }; type CommandBuilder = ( context: CommandBuilderContext, ) => Record; const createTarget = (type: TargetOption["type"]) => (targetOption: Omit = {}) => new TargetBuilder({ runsOnWorkspaceRoot: true, ...targetOption, type }); const createContext = (): CommandBuilderContext => ({ public: createTarget("public"), cloud: createTarget("cloud"), dev: createTarget("dev"), arg: (name, type, argsOption) => ({ name, argsOption: { ...(argsOption ?? {}), type: normalizePrimitiveArgType(type) }, key: "", idx: -1, type: "Argument", }) as ArgMeta, option: (name, type, argsOption) => ({ name, argsOption: { ...(argsOption ?? {}), type: normalizePrimitiveArgType(type) }, key: "", idx: -1, type: "Option", }) as ArgMeta, }); const buildCommandMeta = (definitions: Record) => { const commandMeta = new Map(); for (const [key, definition] of Object.entries(definitions)) { commandMeta.set(key, { key, args: definition.args.map((arg) => ({ ...arg, key })), handler: definition.handler, targetOption: definition.targetOption, }); } return commandMeta; }; export function command( refName: RefName, deps: Deps, builder: CommandBuilder, ): CommandCls, DependencyKey>; export function command( refName: RefName, builder: CommandBuilder<[]>, ): CommandCls, DependencyKey>; export function command( refName: RefName, depsOrBuilder: Deps | CommandBuilder<[]>, builder?: CommandBuilder, ) { const deps = (Array.isArray(depsOrBuilder) ? depsOrBuilder : []) as unknown as Deps; const commandBuilder = (Array.isArray(depsOrBuilder) ? builder : depsOrBuilder) as CommandBuilder; assertUniqueDependencies(deps); const commandMeta = buildCommandMeta(commandBuilder(createContext())); class CommandBase { static readonly refName = refName; static readonly dependencyKind = "command"; static readonly dependencyKey = `${refName}Command` as const; static readonly [COMMAND_META] = commandMeta; constructor() { injectDependencies(this, deps); } } return CommandBase as unknown as CommandCls, DependencyKey>; }