import type { Command } from "commander"; import type { CommandGroupEntry } from "./register-command-groups.js"; export type NamedCommandDescriptor = { name: string; description: string; hasSubcommands: boolean; }; export type CommandGroupDescriptorSpec = { commandNames: readonly string[]; register: TRegister; }; export type ImportedCommandGroupDefinition = { commandNames: readonly string[]; loadModule: () => Promise; register: (module: TModule, args: TRegisterArgs) => Promise | void; }; export type ResolvedCommandGroupEntry = { placeholders: TDescriptor[]; register: TRegister; }; function buildDescriptorIndex( descriptors: readonly TDescriptor[], ): Map { return new Map(descriptors.map((descriptor) => [descriptor.name, descriptor])); } export function resolveCommandGroupEntries( descriptors: readonly TDescriptor[], specs: readonly CommandGroupDescriptorSpec[], ): ResolvedCommandGroupEntry[] { const descriptorsByName = buildDescriptorIndex(descriptors); return specs.map((spec) => ({ placeholders: spec.commandNames.map((name) => { const descriptor = descriptorsByName.get(name); if (!descriptor) { throw new Error(`Unknown command descriptor: ${name}`); } return descriptor; }), register: spec.register, })); } export function buildCommandGroupEntries( descriptors: readonly TDescriptor[], specs: readonly CommandGroupDescriptorSpec[], mapRegister: (register: TRegister) => CommandGroupEntry["register"], ): CommandGroupEntry[] { return resolveCommandGroupEntries(descriptors, specs).map((entry) => ({ placeholders: entry.placeholders, register: mapRegister(entry.register), })); } export function defineImportedCommandGroupSpec( commandNames: readonly string[], loadModule: () => Promise, register: (module: TModule, args: TRegisterArgs) => Promise | void, ): CommandGroupDescriptorSpec<(args: TRegisterArgs) => Promise> { return { commandNames, register: async (args: TRegisterArgs) => { const module = await loadModule(); await register(module, args); }, }; } export function defineImportedCommandGroupSpecs( definitions: readonly ImportedCommandGroupDefinition[], ): CommandGroupDescriptorSpec<(args: TRegisterArgs) => Promise>[] { return definitions.map((definition) => defineImportedCommandGroupSpec( definition.commandNames, definition.loadModule, definition.register, ), ); } type ProgramCommandRegistrar = (program: Command) => Promise | void; type AnyImportedProgramCommandGroupDefinition = { commandNames: readonly string[]; loadModule: () => Promise>; exportName: string; }; export type ImportedProgramCommandGroupDefinition< TModule extends Record, TKey extends keyof TModule & string, > = { commandNames: readonly string[]; loadModule: () => Promise; exportName: TKey; }; export function defineImportedProgramCommandGroupSpec< TModule extends Record, TKey extends keyof TModule & string, >( definition: ImportedProgramCommandGroupDefinition, ): CommandGroupDescriptorSpec<(program: Command) => Promise> { return defineImportedCommandGroupSpec( definition.commandNames, definition.loadModule, (module, program: Command) => module[definition.exportName](program), ); } export function defineImportedProgramCommandGroupSpecs( definitions: readonly AnyImportedProgramCommandGroupDefinition[], ): CommandGroupDescriptorSpec<(program: Command) => Promise>[] { return definitions.map((definition) => ({ commandNames: definition.commandNames, register: async (program: Command) => { const module = await definition.loadModule(); const register = module[definition.exportName]; if (typeof register !== "function") { throw new Error(`Missing program command registrar: ${definition.exportName}`); } await register(program); }, })); }