@oh-my-pi/pi-utils 11.10.0 → 11.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/src/cli.ts +432 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-utils",
|
|
3
|
-
"version": "11.10.
|
|
3
|
+
"version": "11.10.2",
|
|
4
4
|
"description": "Shared utilities for pi packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./src/index.ts",
|
|
11
11
|
"import": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./cli": {
|
|
14
|
+
"types": "./src/cli.ts",
|
|
15
|
+
"import": "./src/cli.ts"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"files": [
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CLI framework — drop-in replacement for the subset of @oclif/core
|
|
3
|
+
* actually used by the coding agent. Provides `Command`, `Args`, `Flags`,
|
|
4
|
+
* and a `run()` entry point with explicit command registration.
|
|
5
|
+
*
|
|
6
|
+
* Design goals:
|
|
7
|
+
* - Zero dependencies beyond node builtins
|
|
8
|
+
* - No filesystem scanning, no manifest files, no plugin loading
|
|
9
|
+
* - Lazy command imports (only the invoked command is loaded)
|
|
10
|
+
* - Typed `this.parse()` output matching oclif's API shape
|
|
11
|
+
*/
|
|
12
|
+
import { parseArgs as nodeParseArgs } from "node:util";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Flag & Arg descriptors
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export interface FlagDescriptor<K extends "string" | "boolean" | "integer" = "string" | "boolean" | "integer"> {
|
|
19
|
+
kind: K;
|
|
20
|
+
description?: string;
|
|
21
|
+
char?: string;
|
|
22
|
+
default?: unknown;
|
|
23
|
+
multiple?: boolean;
|
|
24
|
+
options?: readonly string[];
|
|
25
|
+
required?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ArgDescriptor {
|
|
29
|
+
kind: "string";
|
|
30
|
+
description?: string;
|
|
31
|
+
required?: boolean;
|
|
32
|
+
multiple?: boolean;
|
|
33
|
+
options?: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface FlagInput {
|
|
37
|
+
description?: string;
|
|
38
|
+
char?: string;
|
|
39
|
+
default?: unknown;
|
|
40
|
+
multiple?: boolean;
|
|
41
|
+
options?: readonly string[];
|
|
42
|
+
required?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ArgInput {
|
|
46
|
+
description?: string;
|
|
47
|
+
required?: boolean;
|
|
48
|
+
multiple?: boolean;
|
|
49
|
+
options?: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Builders that match the `Flags.*()` / `Args.*()` API from oclif. */
|
|
53
|
+
export const Flags = {
|
|
54
|
+
string<T extends FlagInput>(opts?: T): FlagDescriptor<"string"> & T {
|
|
55
|
+
return { kind: "string" as const, ...opts } as FlagDescriptor<"string"> & T;
|
|
56
|
+
},
|
|
57
|
+
boolean<T extends FlagInput>(opts?: T): FlagDescriptor<"boolean"> & T {
|
|
58
|
+
return { kind: "boolean" as const, ...opts } as FlagDescriptor<"boolean"> & T;
|
|
59
|
+
},
|
|
60
|
+
integer<T extends FlagInput & { default?: number }>(opts?: T): FlagDescriptor<"integer"> & T {
|
|
61
|
+
return { kind: "integer" as const, ...opts } as FlagDescriptor<"integer"> & T;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const Args = {
|
|
66
|
+
string<T extends ArgInput>(opts?: T): ArgDescriptor & T {
|
|
67
|
+
return { kind: "string" as const, ...opts } as ArgDescriptor & T;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Parse result types — mirrors oclif's typed output from this.parse()
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
type FlagValue<D extends FlagDescriptor> = D["kind"] extends "boolean"
|
|
76
|
+
? D extends { default: boolean }
|
|
77
|
+
? boolean
|
|
78
|
+
: boolean | undefined
|
|
79
|
+
: D["kind"] extends "integer"
|
|
80
|
+
? D extends { default: number }
|
|
81
|
+
? number
|
|
82
|
+
: number | undefined
|
|
83
|
+
: D extends { multiple: true }
|
|
84
|
+
? string[] | undefined
|
|
85
|
+
: string | undefined;
|
|
86
|
+
|
|
87
|
+
type ArgValue<D extends ArgDescriptor> = D extends { multiple: true } ? string[] | undefined : string | undefined;
|
|
88
|
+
|
|
89
|
+
type FlagValues<T extends Record<string, FlagDescriptor>> = { [K in keyof T]: FlagValue<T[K]> };
|
|
90
|
+
type ArgValues<T extends Record<string, ArgDescriptor>> = { [K in keyof T]: ArgValue<T[K]> };
|
|
91
|
+
|
|
92
|
+
export interface ParseOutput<
|
|
93
|
+
F extends Record<string, FlagDescriptor> = Record<string, FlagDescriptor>,
|
|
94
|
+
A extends Record<string, ArgDescriptor> = Record<string, ArgDescriptor>,
|
|
95
|
+
> {
|
|
96
|
+
flags: FlagValues<F>;
|
|
97
|
+
args: ArgValues<A>;
|
|
98
|
+
argv: string[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Command base class
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
export interface CommandCtor {
|
|
106
|
+
new (argv: string[], config: CliConfig): Command;
|
|
107
|
+
description?: string;
|
|
108
|
+
hidden?: boolean;
|
|
109
|
+
strict?: boolean;
|
|
110
|
+
aliases?: string[];
|
|
111
|
+
examples?: string[];
|
|
112
|
+
flags?: Record<string, FlagDescriptor>;
|
|
113
|
+
args?: Record<string, ArgDescriptor>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Configuration passed to every command instance and help renderers. */
|
|
117
|
+
export interface CliConfig {
|
|
118
|
+
bin: string;
|
|
119
|
+
version: string;
|
|
120
|
+
/** All registered commands keyed by their canonical name. */
|
|
121
|
+
commands: Map<string, CommandCtor>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Minimal Command base matching the oclif surface we use. */
|
|
125
|
+
export abstract class Command {
|
|
126
|
+
argv: string[];
|
|
127
|
+
config: CliConfig;
|
|
128
|
+
|
|
129
|
+
constructor(argv: string[], config: CliConfig) {
|
|
130
|
+
this.argv = argv;
|
|
131
|
+
this.config = config;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
abstract run(): Promise<void>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Parse argv against the static `flags` and `args` declared on the
|
|
138
|
+
* concrete command class. Returns a typed `{ flags, args, argv }` object.
|
|
139
|
+
*/
|
|
140
|
+
async parse<C extends CommandCtor>(
|
|
141
|
+
_Cmd: C,
|
|
142
|
+
): Promise<
|
|
143
|
+
ParseOutput<
|
|
144
|
+
NonNullable<C["flags"]> extends Record<string, FlagDescriptor>
|
|
145
|
+
? NonNullable<C["flags"]>
|
|
146
|
+
: Record<string, FlagDescriptor>,
|
|
147
|
+
NonNullable<C["args"]> extends Record<string, ArgDescriptor>
|
|
148
|
+
? NonNullable<C["args"]>
|
|
149
|
+
: Record<string, ArgDescriptor>
|
|
150
|
+
>
|
|
151
|
+
> {
|
|
152
|
+
const Cmd = _Cmd as CommandCtor;
|
|
153
|
+
const flagDefs = (Cmd.flags ?? {}) as Record<string, FlagDescriptor>;
|
|
154
|
+
const argDefs = (Cmd.args ?? {}) as Record<string, ArgDescriptor>;
|
|
155
|
+
const strict = Cmd.strict !== false;
|
|
156
|
+
|
|
157
|
+
// Build node:util parseArgs options from flag descriptors
|
|
158
|
+
const options: Record<
|
|
159
|
+
string,
|
|
160
|
+
{ type: "string" | "boolean"; short?: string; multiple?: boolean; default?: string | boolean }
|
|
161
|
+
> = {};
|
|
162
|
+
for (const [name, desc] of Object.entries(flagDefs)) {
|
|
163
|
+
const opt: (typeof options)[string] = {
|
|
164
|
+
type: desc.kind === "boolean" ? "boolean" : "string",
|
|
165
|
+
};
|
|
166
|
+
if (desc.char) opt.short = desc.char;
|
|
167
|
+
if (desc.multiple) opt.multiple = true;
|
|
168
|
+
if (desc.default !== undefined) {
|
|
169
|
+
opt.default = desc.kind === "boolean" ? Boolean(desc.default) : String(desc.default);
|
|
170
|
+
}
|
|
171
|
+
options[name] = opt;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// strict=false when command declares args (positionals must pass through)
|
|
175
|
+
// or when the command itself opts out
|
|
176
|
+
const { values: rawValues, positionals } = nodeParseArgs({
|
|
177
|
+
args: this.argv,
|
|
178
|
+
options,
|
|
179
|
+
allowPositionals: true,
|
|
180
|
+
strict,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Convert raw values to proper types and validate
|
|
184
|
+
const flags: Record<string, unknown> = {};
|
|
185
|
+
for (const [name, desc] of Object.entries(flagDefs)) {
|
|
186
|
+
const raw = rawValues[name];
|
|
187
|
+
if (desc.kind === "integer") {
|
|
188
|
+
if (raw === undefined || typeof raw === "boolean") {
|
|
189
|
+
flags[name] = desc.default ?? undefined;
|
|
190
|
+
} else {
|
|
191
|
+
const n = Number.parseInt(raw as string, 10);
|
|
192
|
+
if (Number.isNaN(n)) {
|
|
193
|
+
throw new Error(`Expected integer for --${name}, got "${raw}"`);
|
|
194
|
+
}
|
|
195
|
+
flags[name] = n;
|
|
196
|
+
}
|
|
197
|
+
} else if (desc.kind === "boolean") {
|
|
198
|
+
flags[name] =
|
|
199
|
+
raw !== undefined ? Boolean(raw) : desc.default !== undefined ? Boolean(desc.default) : undefined;
|
|
200
|
+
} else {
|
|
201
|
+
// string
|
|
202
|
+
const val = raw !== undefined && typeof raw !== "boolean" ? raw : (desc.default ?? undefined);
|
|
203
|
+
// Validate options constraint
|
|
204
|
+
if (val !== undefined && desc.options && !Array.isArray(val)) {
|
|
205
|
+
if (!desc.options.includes(val as string)) {
|
|
206
|
+
throw new Error(`Expected --${name} to be one of: ${[...desc.options].join(", ")}; got "${val}"`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
flags[name] = val;
|
|
210
|
+
}
|
|
211
|
+
// Validate required
|
|
212
|
+
if (desc.required && flags[name] === undefined) {
|
|
213
|
+
throw new Error(`Missing required flag: --${name}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Map positionals to named args in declaration order and validate
|
|
218
|
+
const args: Record<string, unknown> = {};
|
|
219
|
+
let posIdx = 0;
|
|
220
|
+
for (const [argName, desc] of Object.entries(argDefs)) {
|
|
221
|
+
if (desc.multiple) {
|
|
222
|
+
const val = positionals.slice(posIdx);
|
|
223
|
+
args[argName] = val.length > 0 ? val : undefined;
|
|
224
|
+
posIdx = positionals.length;
|
|
225
|
+
} else {
|
|
226
|
+
const val = positionals[posIdx];
|
|
227
|
+
args[argName] = val;
|
|
228
|
+
posIdx++;
|
|
229
|
+
}
|
|
230
|
+
// Validate required
|
|
231
|
+
if (desc.required && args[argName] === undefined) {
|
|
232
|
+
throw new Error(`Missing required argument: ${argName}`);
|
|
233
|
+
}
|
|
234
|
+
// Validate options constraint
|
|
235
|
+
const argVal = args[argName];
|
|
236
|
+
if (argVal !== undefined && desc.options && typeof argVal === "string") {
|
|
237
|
+
if (!desc.options.includes(argVal)) {
|
|
238
|
+
throw new Error(`Expected ${argName} to be one of: ${[...desc.options].join(", ")}; got "${argVal}"`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { flags, args, argv: positionals } as never;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Help rendering
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
/** Render full root help: header, default command details, subcommand list. */
|
|
252
|
+
export function renderRootHelp(config: CliConfig): void {
|
|
253
|
+
const { bin, version, commands } = config;
|
|
254
|
+
const lines: string[] = [];
|
|
255
|
+
lines.push(`${bin} v${version}\n`);
|
|
256
|
+
lines.push("USAGE");
|
|
257
|
+
lines.push(` $ ${bin} [COMMAND]\n`);
|
|
258
|
+
|
|
259
|
+
// Show the default command's flags/args/examples inline.
|
|
260
|
+
// The default command is the one marked hidden (it's the implicit entry point).
|
|
261
|
+
const defaultCmd = [...commands.values()].find(C => C.hidden);
|
|
262
|
+
if (defaultCmd) {
|
|
263
|
+
renderCommandBody(lines, defaultCmd);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// List visible subcommands
|
|
267
|
+
const visible = [...commands.entries()].filter(([, C]) => !C.hidden);
|
|
268
|
+
if (visible.length > 0) {
|
|
269
|
+
lines.push("COMMANDS");
|
|
270
|
+
const maxLen = Math.max(...visible.map(([n]) => n.length));
|
|
271
|
+
for (const [name, C] of visible.sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
272
|
+
lines.push(` ${name.padEnd(maxLen + 2)}${C.description ?? ""}`);
|
|
273
|
+
}
|
|
274
|
+
lines.push("");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
process.stdout.write(lines.join("\n"));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Render help for a single command. */
|
|
281
|
+
export function renderCommandHelp(bin: string, id: string, Cmd: CommandCtor): void {
|
|
282
|
+
const lines: string[] = [];
|
|
283
|
+
if (Cmd.description) lines.push(`${Cmd.description}\n`);
|
|
284
|
+
lines.push("USAGE");
|
|
285
|
+
const argNames = Object.keys(Cmd.args ?? {});
|
|
286
|
+
const argStr = argNames.length > 0 ? ` ${argNames.map(n => `[${n.toUpperCase()}]`).join(" ")}` : "";
|
|
287
|
+
const hasFlags = Object.keys(Cmd.flags ?? {}).length > 0;
|
|
288
|
+
lines.push(` $ ${bin} ${id}${argStr}${hasFlags ? " [FLAGS]" : ""}\n`);
|
|
289
|
+
renderCommandBody(lines, Cmd);
|
|
290
|
+
process.stdout.write(lines.join("\n"));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function renderCommandBody(lines: string[], Cmd: CommandCtor): void {
|
|
294
|
+
const argDefs = Cmd.args ?? {};
|
|
295
|
+
const flagDefs = Cmd.flags ?? {};
|
|
296
|
+
|
|
297
|
+
// Arguments
|
|
298
|
+
const argEntries = Object.entries(argDefs);
|
|
299
|
+
if (argEntries.length > 0) {
|
|
300
|
+
lines.push("ARGUMENTS");
|
|
301
|
+
const maxLen = Math.max(...argEntries.map(([n]) => n.length));
|
|
302
|
+
for (const [name, desc] of argEntries) {
|
|
303
|
+
const parts = [name.toUpperCase().padEnd(maxLen + 2)];
|
|
304
|
+
if (desc.description) parts.push(desc.description);
|
|
305
|
+
if (desc.options) parts.push(`(${[...desc.options].join("|")})`);
|
|
306
|
+
lines.push(` ${parts.join(" ")}`);
|
|
307
|
+
}
|
|
308
|
+
lines.push("");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Flags
|
|
312
|
+
const flagEntries = Object.entries(flagDefs);
|
|
313
|
+
if (flagEntries.length > 0) {
|
|
314
|
+
lines.push("FLAGS");
|
|
315
|
+
const formatted: [string, string][] = [];
|
|
316
|
+
for (const [name, desc] of flagEntries) {
|
|
317
|
+
const charPart = desc.char ? `-${desc.char}, ` : " ";
|
|
318
|
+
const namePart = `--${name}`;
|
|
319
|
+
const typePart = desc.kind === "boolean" ? "" : desc.kind === "integer" ? "=<int>" : "=<value>";
|
|
320
|
+
formatted.push([` ${charPart}${namePart}${typePart}`, desc.description ?? ""]);
|
|
321
|
+
}
|
|
322
|
+
const maxLeft = Math.max(...formatted.map(([l]) => l.length));
|
|
323
|
+
for (const [left, right] of formatted) {
|
|
324
|
+
lines.push(`${left.padEnd(maxLeft + 2)}${right}`);
|
|
325
|
+
}
|
|
326
|
+
lines.push("");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Examples
|
|
330
|
+
if (Cmd.examples && Cmd.examples.length > 0) {
|
|
331
|
+
lines.push("EXAMPLES");
|
|
332
|
+
for (const ex of Cmd.examples) {
|
|
333
|
+
for (const line of ex.split("\n")) {
|
|
334
|
+
lines.push(` ${line}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
lines.push("");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// CLI entry point
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
/** A lazily-loaded command: canonical name, loader, and optional aliases. */
|
|
346
|
+
export interface CommandEntry {
|
|
347
|
+
name: string;
|
|
348
|
+
load: () => Promise<CommandCtor>;
|
|
349
|
+
aliases?: string[];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export interface RunOptions {
|
|
353
|
+
bin: string;
|
|
354
|
+
version: string;
|
|
355
|
+
argv: string[];
|
|
356
|
+
commands: CommandEntry[];
|
|
357
|
+
/** Custom help renderer. Receives fully-populated config. */
|
|
358
|
+
help?: (config: CliConfig) => Promise<void> | void;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Find a command entry by exact name or alias. */
|
|
362
|
+
function findEntry(commands: CommandEntry[], id: string): CommandEntry | undefined {
|
|
363
|
+
return commands.find(e => e.name === id) ?? commands.find(e => e.aliases?.includes(id));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Main entry point — replaces `run()` from @oclif/core.
|
|
368
|
+
*
|
|
369
|
+
* Each command is explicitly registered with a lazy loader.
|
|
370
|
+
* No filesystem scanning, no plugin system, no package.json reading.
|
|
371
|
+
*/
|
|
372
|
+
export async function run(opts: RunOptions): Promise<void> {
|
|
373
|
+
const { bin, version, argv } = opts;
|
|
374
|
+
|
|
375
|
+
const commandId = argv[0] ?? "";
|
|
376
|
+
const commandArgv = argv.slice(1);
|
|
377
|
+
|
|
378
|
+
// Top-level help
|
|
379
|
+
if (commandId === "--help" || commandId === "-h" || commandId === "help" || commandId === "") {
|
|
380
|
+
const config = await loadAllCommands(opts);
|
|
381
|
+
if (opts.help) {
|
|
382
|
+
await opts.help(config);
|
|
383
|
+
} else {
|
|
384
|
+
renderRootHelp(config);
|
|
385
|
+
}
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Version
|
|
390
|
+
if (commandId === "--version" || commandId === "-v") {
|
|
391
|
+
process.stdout.write(`${bin}/${version}\n`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Per-command help
|
|
396
|
+
if (commandArgv.includes("--help") || commandArgv.includes("-h")) {
|
|
397
|
+
const config = await loadAllCommands(opts);
|
|
398
|
+
// Resolve aliases for help too
|
|
399
|
+
const entry = findEntry(opts.commands, commandId);
|
|
400
|
+
const Cmd = entry ? config.commands.get(entry.name) : undefined;
|
|
401
|
+
if (Cmd) {
|
|
402
|
+
renderCommandHelp(bin, entry!.name, Cmd);
|
|
403
|
+
} else {
|
|
404
|
+
process.stderr.write(`Unknown command: ${commandId}\n`);
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Find command by name or alias
|
|
410
|
+
const entry = findEntry(opts.commands, commandId);
|
|
411
|
+
|
|
412
|
+
if (!entry) {
|
|
413
|
+
process.stderr.write(`Error: command ${commandId} not found\n`);
|
|
414
|
+
process.exitCode = 1;
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const Cmd = await entry.load();
|
|
419
|
+
const config: CliConfig = { bin, version, commands: new Map([[entry.name, Cmd]]) };
|
|
420
|
+
const instance = new Cmd(commandArgv, config);
|
|
421
|
+
await instance.run();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Resolve all command loaders for help/alias display. */
|
|
425
|
+
async function loadAllCommands(opts: RunOptions): Promise<CliConfig> {
|
|
426
|
+
const commands = new Map<string, CommandCtor>();
|
|
427
|
+
const loaded = await Promise.all(opts.commands.map(async e => [e.name, await e.load()] as const));
|
|
428
|
+
for (const [name, Cmd] of loaded) {
|
|
429
|
+
commands.set(name, Cmd);
|
|
430
|
+
}
|
|
431
|
+
return { bin: opts.bin, version: opts.version, commands };
|
|
432
|
+
}
|