@kidd-cli/core 0.3.0 → 0.5.0
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/README.md +23 -8
- package/dist/{config-D8e5qxLp.js → config-BiEi8RG2.js} +2 -2
- package/dist/{config-D8e5qxLp.js.map → config-BiEi8RG2.js.map} +1 -1
- package/dist/{create-store-OHdkm_Yt.js → create-store-CGeHrTcl.js} +2 -2
- package/dist/{create-store-OHdkm_Yt.js.map → create-store-CGeHrTcl.js.map} +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +265 -95
- package/dist/index.js.map +1 -1
- package/dist/lib/config.js +2 -2
- package/dist/lib/format.d.ts +73 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +20 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/logger.d.ts +1 -1
- package/dist/lib/logger.js +10 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/project.d.ts +1 -1
- package/dist/lib/project.js +1 -1
- package/dist/lib/store.d.ts +1 -1
- package/dist/lib/store.js +2 -2
- package/dist/{logger-9j49T5da.d.ts → logger-Bm-LRSeQ.d.ts} +17 -1
- package/dist/logger-Bm-LRSeQ.d.ts.map +1 -0
- package/dist/middleware/auth.d.ts +15 -3
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +48 -9
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/http.d.ts +1 -1
- package/dist/middleware/http.js +1 -1
- package/dist/middleware/icons.d.ts +119 -0
- package/dist/middleware/icons.d.ts.map +1 -0
- package/dist/middleware/icons.js +824 -0
- package/dist/middleware/icons.js.map +1 -0
- package/dist/{middleware-BWnPSRWR.js → middleware-BewRXb2G.js} +1 -1
- package/dist/{middleware-BWnPSRWR.js.map → middleware-BewRXb2G.js.map} +1 -1
- package/dist/{project-D0g84bZY.js → project-CoWHMVc8.js} +1 -1
- package/dist/{project-D0g84bZY.js.map → project-CoWHMVc8.js.map} +1 -1
- package/dist/tally-ioa20iGw.js +220 -0
- package/dist/tally-ioa20iGw.js.map +1 -0
- package/dist/{types-D-BxshYM.d.ts → types-Boe_1EjY.d.ts} +1 -1
- package/dist/{types-D-BxshYM.d.ts.map → types-Boe_1EjY.d.ts.map} +1 -1
- package/dist/types-Cp8_uIil.d.ts +160 -0
- package/dist/types-Cp8_uIil.d.ts.map +1 -0
- package/dist/{types-CTvrsrnD.d.ts → types-s-yUj9Zj.d.ts} +104 -44
- package/dist/types-s-yUj9Zj.d.ts.map +1 -0
- package/package.json +12 -3
- package/dist/logger-9j49T5da.d.ts.map +0 -1
- package/dist/types-CTvrsrnD.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import "./tally-ioa20iGw.js";
|
|
1
2
|
import { createCliLogger } from "./lib/logger.js";
|
|
2
|
-
import { n as decorateContext, t as middleware } from "./middleware-
|
|
3
|
-
import "./project-
|
|
4
|
-
import { t as createConfigClient } from "./config-
|
|
3
|
+
import { n as decorateContext, t as middleware } from "./middleware-BewRXb2G.js";
|
|
4
|
+
import "./project-CoWHMVc8.js";
|
|
5
|
+
import { t as createConfigClient } from "./config-BiEi8RG2.js";
|
|
5
6
|
import { basename, extname, join, resolve } from "node:path";
|
|
6
7
|
import { loadConfig } from "@kidd-cli/config/loader";
|
|
7
8
|
import { P, attemptAsync, err, isPlainObject, isString, match, ok } from "@kidd-cli/utils/fp";
|
|
8
9
|
import yargs from "yargs";
|
|
10
|
+
import { z } from "zod";
|
|
9
11
|
import * as clack from "@clack/prompts";
|
|
12
|
+
import pc from "picocolors";
|
|
13
|
+
import { match as match$1 } from "ts-pattern";
|
|
10
14
|
import { TAG, hasTag, withTag } from "@kidd-cli/utils/tag";
|
|
11
15
|
import { jsonStringify } from "@kidd-cli/utils/json";
|
|
12
16
|
import { readdir } from "node:fs/promises";
|
|
13
17
|
import { formatZodIssues } from "@kidd-cli/utils/validate";
|
|
14
|
-
import { match as match$1 } from "ts-pattern";
|
|
15
18
|
import { defineConfig } from "@kidd-cli/config";
|
|
16
19
|
//#region src/context/error.ts
|
|
17
20
|
/**
|
|
@@ -75,40 +78,26 @@ function createContextErrorData(message, options) {
|
|
|
75
78
|
}, "ContextError");
|
|
76
79
|
}
|
|
77
80
|
//#endregion
|
|
78
|
-
//#region src/context/
|
|
81
|
+
//#region src/context/format.ts
|
|
79
82
|
/**
|
|
80
|
-
* Create the
|
|
83
|
+
* Create the pure string formatter methods for a context.
|
|
81
84
|
*
|
|
82
85
|
* @private
|
|
83
|
-
* @
|
|
84
|
-
* @returns An Output instance backed by the given stream.
|
|
86
|
+
* @returns A Format instance with json and table formatters.
|
|
85
87
|
*/
|
|
86
|
-
function
|
|
87
|
-
return {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
raw(content) {
|
|
92
|
-
stream.write(content);
|
|
88
|
+
function createContextFormat() {
|
|
89
|
+
return Object.freeze({
|
|
90
|
+
json(data) {
|
|
91
|
+
const [, json] = jsonStringify(data, { pretty: true });
|
|
92
|
+
return `${json}\n`;
|
|
93
93
|
},
|
|
94
|
-
table(rows
|
|
95
|
-
if (
|
|
96
|
-
const [, json] = jsonStringify(rows, { pretty: true });
|
|
97
|
-
stream.write(`${json}\n`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (rows.length === 0) return;
|
|
94
|
+
table(rows) {
|
|
95
|
+
if (rows.length === 0) return "";
|
|
101
96
|
const [firstRow] = rows;
|
|
102
|
-
if (!firstRow) return;
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
write(data, options) {
|
|
106
|
-
if (options && options.json || typeof data === "object" && data !== null) {
|
|
107
|
-
const [, json] = jsonStringify(data, { pretty: true });
|
|
108
|
-
stream.write(`${json}\n`);
|
|
109
|
-
} else stream.write(`${String(data)}\n`);
|
|
97
|
+
if (!firstRow) return "";
|
|
98
|
+
return formatTable(rows, Object.keys(firstRow));
|
|
110
99
|
}
|
|
111
|
-
};
|
|
100
|
+
});
|
|
112
101
|
}
|
|
113
102
|
/**
|
|
114
103
|
* Format an unknown value as a string for table cell display.
|
|
@@ -167,16 +156,16 @@ function computeColumnWidths(rows, keys) {
|
|
|
167
156
|
});
|
|
168
157
|
}
|
|
169
158
|
/**
|
|
170
|
-
*
|
|
159
|
+
* Format a table (header, separator, rows) as a string.
|
|
171
160
|
*
|
|
172
161
|
* @private
|
|
173
|
-
* @param stream - The writable stream.
|
|
174
162
|
* @param rows - The data rows.
|
|
175
163
|
* @param keys - The column keys.
|
|
164
|
+
* @returns The formatted table string.
|
|
176
165
|
*/
|
|
177
|
-
function
|
|
166
|
+
function formatTable(rows, keys) {
|
|
178
167
|
const widths = computeColumnWidths(rows, keys);
|
|
179
|
-
|
|
168
|
+
return `${[
|
|
180
169
|
createTableHeader({
|
|
181
170
|
keys,
|
|
182
171
|
widths
|
|
@@ -187,8 +176,7 @@ function writeTableToStream(stream, rows, keys) {
|
|
|
187
176
|
row,
|
|
188
177
|
widths
|
|
189
178
|
}))
|
|
190
|
-
].join("\n")
|
|
191
|
-
stream.write(`${content}\n`);
|
|
179
|
+
].join("\n")}\n`;
|
|
192
180
|
}
|
|
193
181
|
//#endregion
|
|
194
182
|
//#region src/context/prompts.ts
|
|
@@ -269,7 +257,7 @@ function createMemoryStore() {
|
|
|
269
257
|
/**
|
|
270
258
|
* Create the {@link Context} object threaded through middleware and command handlers.
|
|
271
259
|
*
|
|
272
|
-
* Assembles logger, spinner,
|
|
260
|
+
* Assembles logger, spinner, format, store, prompts, and meta from
|
|
273
261
|
* the provided options into a single immutable context. Each sub-system is
|
|
274
262
|
* constructed via its own factory so this function remains a lean orchestrator.
|
|
275
263
|
*
|
|
@@ -279,7 +267,7 @@ function createMemoryStore() {
|
|
|
279
267
|
function createContext(options) {
|
|
280
268
|
const ctxLogger = options.logger ?? createCliLogger();
|
|
281
269
|
const ctxSpinner = clack.spinner();
|
|
282
|
-
const
|
|
270
|
+
const ctxFormat = createContextFormat();
|
|
283
271
|
const ctxStore = createMemoryStore();
|
|
284
272
|
const ctxPrompts = createContextPrompts();
|
|
285
273
|
const ctxMeta = {
|
|
@@ -289,13 +277,14 @@ function createContext(options) {
|
|
|
289
277
|
};
|
|
290
278
|
return {
|
|
291
279
|
args: options.args,
|
|
280
|
+
colors: Object.freeze({ ...pc }),
|
|
292
281
|
config: options.config,
|
|
293
282
|
fail(message, failOptions) {
|
|
294
283
|
throw createContextError(message, failOptions);
|
|
295
284
|
},
|
|
285
|
+
format: ctxFormat,
|
|
296
286
|
logger: ctxLogger,
|
|
297
287
|
meta: ctxMeta,
|
|
298
|
-
output: ctxOutput,
|
|
299
288
|
prompts: ctxPrompts,
|
|
300
289
|
spinner: ctxSpinner,
|
|
301
290
|
store: ctxStore
|
|
@@ -317,17 +306,7 @@ const INDEX_NAME = "index";
|
|
|
317
306
|
*/
|
|
318
307
|
async function autoload(options) {
|
|
319
308
|
const dir = resolveDir(options);
|
|
320
|
-
|
|
321
|
-
const fileEntries = entries.filter(isCommandFile);
|
|
322
|
-
const dirEntries = entries.filter(isCommandDir);
|
|
323
|
-
const fileResults = await Promise.all(fileEntries.map(async (entry) => {
|
|
324
|
-
const cmd = await importCommand(join(dir, entry.name));
|
|
325
|
-
if (!cmd) return;
|
|
326
|
-
return [deriveCommandName(entry), cmd];
|
|
327
|
-
}));
|
|
328
|
-
const dirResults = await Promise.all(dirEntries.map((entry) => buildDirCommand(join(dir, entry.name))));
|
|
329
|
-
const validPairs = [...fileResults, ...dirResults].filter((pair) => pair !== void 0);
|
|
330
|
-
return Object.fromEntries(validPairs);
|
|
309
|
+
return buildCommandMapFromEntries(dir, await readdir(dir, { withFileTypes: true }));
|
|
331
310
|
}
|
|
332
311
|
/**
|
|
333
312
|
* Resolve the target directory from autoload options.
|
|
@@ -354,7 +333,7 @@ function resolveDir(options) {
|
|
|
354
333
|
async function buildDirCommand(dir) {
|
|
355
334
|
const name = basename(dir);
|
|
356
335
|
const dirEntries = await readdir(dir, { withFileTypes: true });
|
|
357
|
-
const subCommands = await
|
|
336
|
+
const subCommands = await buildCommandMapFromEntries(dir, dirEntries);
|
|
358
337
|
const indexFile = findIndexInEntries(dirEntries);
|
|
359
338
|
if (indexFile) {
|
|
360
339
|
const parentCommand = await importCommand(join(dir, indexFile.name));
|
|
@@ -367,14 +346,17 @@ async function buildDirCommand(dir) {
|
|
|
367
346
|
return [name, withTag({ commands: subCommands }, "Command")];
|
|
368
347
|
}
|
|
369
348
|
/**
|
|
370
|
-
* Build
|
|
349
|
+
* Build a CommandMap from pre-read directory entries.
|
|
350
|
+
*
|
|
351
|
+
* Shared by both `autoload` and `buildDirCommand` to avoid duplicating
|
|
352
|
+
* the file/dir fan-out and result-filtering logic.
|
|
371
353
|
*
|
|
372
354
|
* @private
|
|
373
|
-
* @param dir - Absolute path to the directory.
|
|
374
|
-
* @param entries - Pre-read directory entries.
|
|
355
|
+
* @param dir - Absolute path to the directory the entries belong to.
|
|
356
|
+
* @param entries - Pre-read directory entries for that directory.
|
|
375
357
|
* @returns A CommandMap built from the entries.
|
|
376
358
|
*/
|
|
377
|
-
async function
|
|
359
|
+
async function buildCommandMapFromEntries(dir, entries) {
|
|
378
360
|
const fileEntries = entries.filter(isCommandFile);
|
|
379
361
|
const dirEntries = entries.filter(isCommandDir);
|
|
380
362
|
const fileResults = await Promise.all(fileEntries.map(async (entry) => {
|
|
@@ -460,6 +442,46 @@ function isCommandDir(entry) {
|
|
|
460
442
|
return !entry.name.startsWith("_") && !entry.name.startsWith(".");
|
|
461
443
|
}
|
|
462
444
|
//#endregion
|
|
445
|
+
//#region src/command.ts
|
|
446
|
+
/**
|
|
447
|
+
* Check whether a value is a structured {@link CommandsConfig} object.
|
|
448
|
+
*
|
|
449
|
+
* Discriminates from a plain `CommandMap` by checking for the `order` (array)
|
|
450
|
+
* or `path` (string) keys — neither can appear on a valid `CommandMap` whose
|
|
451
|
+
* values are tagged `Command` objects.
|
|
452
|
+
*
|
|
453
|
+
* @param value - The value to test.
|
|
454
|
+
* @returns `true` when `value` is a `CommandsConfig`.
|
|
455
|
+
*/
|
|
456
|
+
function isCommandsConfig(value) {
|
|
457
|
+
if (typeof value !== "object" || value === null || value instanceof Promise) return false;
|
|
458
|
+
return "order" in value && Array.isArray(value.order) || "path" in value && typeof value.path === "string";
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Define a CLI command with typed args, config, and handler.
|
|
462
|
+
*
|
|
463
|
+
* The `const TMiddleware` generic preserves the middleware tuple as a literal type,
|
|
464
|
+
* enabling TypeScript to extract and intersect `Variables` from each middleware
|
|
465
|
+
* element onto the handler's `ctx` type.
|
|
466
|
+
*
|
|
467
|
+
* When `def.commands` is a structured {@link CommandsConfig}, the factory
|
|
468
|
+
* normalizes it into flat `commands` and `order` fields on the output
|
|
469
|
+
* `Command` object so downstream consumers never need to handle the grouped form.
|
|
470
|
+
*
|
|
471
|
+
* @param def - Command definition including description, args schema, middleware, and handler.
|
|
472
|
+
* @returns A resolved Command object for registration in the command map.
|
|
473
|
+
*/
|
|
474
|
+
function command(def) {
|
|
475
|
+
return match(def.commands).when(isCommandsConfig, (cfg) => {
|
|
476
|
+
const { order, commands: innerCommands } = cfg;
|
|
477
|
+
return withTag({
|
|
478
|
+
...def,
|
|
479
|
+
commands: innerCommands,
|
|
480
|
+
order
|
|
481
|
+
}, "Command");
|
|
482
|
+
}).otherwise(() => withTag({ ...def }, "Command"));
|
|
483
|
+
}
|
|
484
|
+
//#endregion
|
|
463
485
|
//#region src/runtime/args/zod.ts
|
|
464
486
|
/**
|
|
465
487
|
* Type guard that checks whether a value is a zod object schema.
|
|
@@ -700,6 +722,44 @@ function yargsArgDefToOption(def) {
|
|
|
700
722
|
};
|
|
701
723
|
}
|
|
702
724
|
//#endregion
|
|
725
|
+
//#region src/runtime/sort-commands.ts
|
|
726
|
+
/**
|
|
727
|
+
* Validate that every name in the order array exists in the provided command names.
|
|
728
|
+
*
|
|
729
|
+
* @param params - The order array and available command names to validate against.
|
|
730
|
+
* @returns A Result tuple — `[null, void]` on success or `[Error, null]` on failure.
|
|
731
|
+
*/
|
|
732
|
+
function validateCommandOrder(params) {
|
|
733
|
+
const { order, commandNames } = params;
|
|
734
|
+
const seen = /* @__PURE__ */ new Set();
|
|
735
|
+
const duplicates = order.filter((name) => {
|
|
736
|
+
if (seen.has(name)) return true;
|
|
737
|
+
seen.add(name);
|
|
738
|
+
return false;
|
|
739
|
+
});
|
|
740
|
+
if (duplicates.length > 0) return err(`Invalid command order: duplicate command(s) ${[...new Set(duplicates)].map((n) => `"${n}"`).join(", ")}`);
|
|
741
|
+
const nameSet = new Set(commandNames);
|
|
742
|
+
const invalid = order.filter((name) => !nameSet.has(name));
|
|
743
|
+
if (invalid.length > 0) return err(`Invalid command order: unknown command(s) ${invalid.map((n) => `"${n}"`).join(", ")}`);
|
|
744
|
+
return ok();
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Sort command entries with ordered names first (in specified order),
|
|
748
|
+
* remaining names alphabetically.
|
|
749
|
+
*
|
|
750
|
+
* @param params - The command entries and optional order array.
|
|
751
|
+
* @returns Sorted array of `[name, Command]` entries.
|
|
752
|
+
*/
|
|
753
|
+
function sortCommandEntries(params) {
|
|
754
|
+
const { entries, order } = params;
|
|
755
|
+
if (!order || order.length === 0) return [...entries].toSorted(([a], [b]) => a.localeCompare(b));
|
|
756
|
+
const entryMap = new Map(entries);
|
|
757
|
+
const ordered = order.filter((name) => entryMap.has(name)).map((name) => [name, entryMap.get(name)]);
|
|
758
|
+
const orderedSet = new Set(order);
|
|
759
|
+
const remaining = entries.filter(([name]) => !orderedSet.has(name)).toSorted(([a], [b]) => a.localeCompare(b));
|
|
760
|
+
return [...ordered, ...remaining];
|
|
761
|
+
}
|
|
762
|
+
//#endregion
|
|
703
763
|
//#region src/runtime/register.ts
|
|
704
764
|
/**
|
|
705
765
|
* Type guard that checks whether a value is a Command object.
|
|
@@ -714,22 +774,36 @@ function isCommand(value) {
|
|
|
714
774
|
* Register all commands from a CommandMap on a yargs instance.
|
|
715
775
|
*
|
|
716
776
|
* Iterates over the command map, filters for valid Command objects,
|
|
717
|
-
*
|
|
718
|
-
* the provided yargs Argv instance.
|
|
777
|
+
* validates the order array, sorts entries, and recursively registers
|
|
778
|
+
* each command (including subcommands) on the provided yargs Argv instance.
|
|
719
779
|
*
|
|
720
780
|
* @param options - Registration options including the command map, yargs instance, and resolution ref.
|
|
721
781
|
*/
|
|
722
782
|
function registerCommands(options) {
|
|
723
|
-
const { instance, commands, resolved, parentPath } = options;
|
|
783
|
+
const { instance, commands, resolved, parentPath, order, errorRef } = options;
|
|
724
784
|
const commandEntries = Object.entries(commands).filter((pair) => isCommand(pair[1]));
|
|
725
|
-
|
|
785
|
+
if (order && order.length > 0) {
|
|
786
|
+
const [validationError] = validateCommandOrder({
|
|
787
|
+
commandNames: commandEntries.map(([name]) => name),
|
|
788
|
+
order
|
|
789
|
+
});
|
|
790
|
+
if (validationError && errorRef) {
|
|
791
|
+
errorRef.error = validationError;
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
sortCommandEntries({
|
|
796
|
+
entries: commandEntries,
|
|
797
|
+
order
|
|
798
|
+
}).map(([name, entry]) => registerResolvedCommand({
|
|
726
799
|
builder: instance,
|
|
727
800
|
cmd: entry,
|
|
801
|
+
errorRef,
|
|
728
802
|
instance,
|
|
729
803
|
name,
|
|
730
804
|
parentPath,
|
|
731
805
|
resolved
|
|
732
|
-
});
|
|
806
|
+
}));
|
|
733
807
|
}
|
|
734
808
|
/**
|
|
735
809
|
* Register a single resolved command (and its subcommands) with yargs.
|
|
@@ -742,20 +816,34 @@ function registerCommands(options) {
|
|
|
742
816
|
* @param options - Command registration context.
|
|
743
817
|
*/
|
|
744
818
|
function registerResolvedCommand(options) {
|
|
745
|
-
const { instance, name, cmd, resolved, parentPath } = options;
|
|
819
|
+
const { instance, name, cmd, resolved, parentPath, errorRef } = options;
|
|
746
820
|
const description = cmd.description ?? "";
|
|
747
821
|
instance.command(name, description, (builder) => {
|
|
748
822
|
registerCommandArgs(builder, cmd.args);
|
|
749
823
|
if (cmd.commands) {
|
|
750
824
|
const subCommands = Object.entries(cmd.commands).filter((pair) => isCommand(pair[1]));
|
|
751
|
-
|
|
825
|
+
if (cmd.order && cmd.order.length > 0) {
|
|
826
|
+
const [validationError] = validateCommandOrder({
|
|
827
|
+
commandNames: subCommands.map(([n]) => n),
|
|
828
|
+
order: cmd.order
|
|
829
|
+
});
|
|
830
|
+
if (validationError && errorRef) {
|
|
831
|
+
errorRef.error = validationError;
|
|
832
|
+
return builder;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
sortCommandEntries({
|
|
836
|
+
entries: subCommands,
|
|
837
|
+
order: cmd.order
|
|
838
|
+
}).map(([subName, subEntry]) => registerResolvedCommand({
|
|
752
839
|
builder,
|
|
753
840
|
cmd: subEntry,
|
|
841
|
+
errorRef,
|
|
754
842
|
instance: builder,
|
|
755
843
|
name: subName,
|
|
756
844
|
parentPath: [...parentPath, name],
|
|
757
845
|
resolved
|
|
758
|
-
});
|
|
846
|
+
}));
|
|
759
847
|
if (cmd.handler) builder.demandCommand(0);
|
|
760
848
|
else builder.demandCommand(1, "You must specify a subcommand.");
|
|
761
849
|
}
|
|
@@ -890,31 +978,46 @@ const ARGV_SLICE_START = 2;
|
|
|
890
978
|
async function cli(options) {
|
|
891
979
|
const logger = createCliLogger();
|
|
892
980
|
const [uncaughtError, result] = await attemptAsync(async () => {
|
|
893
|
-
const
|
|
981
|
+
const [versionError, version] = resolveVersion(options.version);
|
|
982
|
+
if (versionError) return versionError;
|
|
983
|
+
const program = yargs(process.argv.slice(ARGV_SLICE_START)).scriptName(options.name).version(version).strict().help().option("cwd", {
|
|
894
984
|
describe: "Set the working directory",
|
|
895
985
|
global: true,
|
|
896
986
|
type: "string"
|
|
897
987
|
});
|
|
898
988
|
if (options.description) program.usage(options.description);
|
|
989
|
+
const footer = extractFooter(options.help);
|
|
990
|
+
if (footer) program.epilogue(footer);
|
|
899
991
|
const resolved = { ref: void 0 };
|
|
900
|
-
const
|
|
901
|
-
|
|
992
|
+
const errorRef = { error: void 0 };
|
|
993
|
+
const resolvedCmds = await resolveCommands(options.commands);
|
|
994
|
+
if (resolvedCmds) {
|
|
902
995
|
registerCommands({
|
|
903
|
-
commands,
|
|
996
|
+
commands: resolvedCmds.commands,
|
|
997
|
+
errorRef,
|
|
904
998
|
instance: program,
|
|
999
|
+
order: resolvedCmds.order,
|
|
905
1000
|
parentPath: [],
|
|
906
1001
|
resolved
|
|
907
1002
|
});
|
|
908
|
-
|
|
1003
|
+
if (errorRef.error) return errorRef.error;
|
|
909
1004
|
}
|
|
910
1005
|
const argv = await program.parseAsync();
|
|
911
1006
|
applyCwd(argv);
|
|
912
|
-
if (!resolved.ref)
|
|
1007
|
+
if (!resolved.ref) {
|
|
1008
|
+
showNoCommandHelp({
|
|
1009
|
+
argv,
|
|
1010
|
+
commands: resolvedCmds,
|
|
1011
|
+
help: options.help,
|
|
1012
|
+
program
|
|
1013
|
+
});
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
913
1016
|
const [runtimeError, runtime] = await createRuntime({
|
|
914
1017
|
config: options.config,
|
|
915
1018
|
middleware: options.middleware,
|
|
916
1019
|
name: options.name,
|
|
917
|
-
version
|
|
1020
|
+
version
|
|
918
1021
|
});
|
|
919
1022
|
if (runtimeError) return runtimeError;
|
|
920
1023
|
const [executeError] = await runtime.execute({
|
|
@@ -932,23 +1035,64 @@ async function cli(options) {
|
|
|
932
1035
|
}
|
|
933
1036
|
if (result) exitOnError(result, logger);
|
|
934
1037
|
}
|
|
1038
|
+
const VERSION_ERROR = /* @__PURE__ */ new Error("No CLI version available. Either pass `version` to cli() or build with the kidd bundler.");
|
|
1039
|
+
const VersionSchema = z.string().trim().min(1);
|
|
1040
|
+
/**
|
|
1041
|
+
* Resolve the CLI version from an explicit value or the compile-time constant.
|
|
1042
|
+
*
|
|
1043
|
+
* Resolution order:
|
|
1044
|
+
* 1. Explicit version string passed to `cli()`
|
|
1045
|
+
* 2. `__KIDD_VERSION__` injected by the kidd bundler at build time
|
|
1046
|
+
*
|
|
1047
|
+
* Returns an error when neither source provides a non-empty version.
|
|
1048
|
+
*
|
|
1049
|
+
* @private
|
|
1050
|
+
* @param explicit - The version string from `CliOptions.version`, if provided.
|
|
1051
|
+
* @returns A Result tuple with the resolved version string or an Error.
|
|
1052
|
+
*/
|
|
1053
|
+
function resolveVersion(explicit) {
|
|
1054
|
+
if (explicit !== void 0) {
|
|
1055
|
+
const parsed = VersionSchema.safeParse(explicit);
|
|
1056
|
+
if (parsed.success) return ok(parsed.data);
|
|
1057
|
+
return err(VERSION_ERROR);
|
|
1058
|
+
}
|
|
1059
|
+
if (typeof __KIDD_VERSION__ === "string") {
|
|
1060
|
+
const parsed = VersionSchema.safeParse(__KIDD_VERSION__);
|
|
1061
|
+
if (parsed.success) return ok(parsed.data);
|
|
1062
|
+
}
|
|
1063
|
+
return err(VERSION_ERROR);
|
|
1064
|
+
}
|
|
935
1065
|
/**
|
|
936
|
-
* Resolve the commands option to a
|
|
1066
|
+
* Resolve the commands option to a {@link ResolvedCommands}.
|
|
937
1067
|
*
|
|
938
1068
|
* Accepts a directory string (triggers autoload), a static CommandMap,
|
|
939
|
-
* a Promise<CommandMap
|
|
1069
|
+
* a Promise<CommandMap>, a structured {@link CommandsConfig},
|
|
940
1070
|
* or undefined (loads `kidd.config.ts` and autoloads from its `commands` field,
|
|
941
1071
|
* falling back to `'./commands'`).
|
|
942
1072
|
*
|
|
943
1073
|
* @private
|
|
944
1074
|
* @param commands - The commands option from CliOptions.
|
|
945
|
-
* @returns
|
|
1075
|
+
* @returns Resolved commands with optional order, or undefined.
|
|
946
1076
|
*/
|
|
947
1077
|
async function resolveCommands(commands) {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1078
|
+
return match(commands).when(isString, async (dir) => ({ commands: await autoload({ dir }) })).with(P.instanceOf(Promise), async (p) => ({ commands: await p })).when(isCommandsConfig, (cfg) => resolveCommandsConfig(cfg)).when(isPlainObject, (cmds) => ({ commands: cmds })).otherwise(() => resolveCommandsFromConfig());
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Resolve a structured {@link CommandsConfig} into flat commands and order.
|
|
1082
|
+
*
|
|
1083
|
+
* When `path` is provided, autoloads from that directory. Otherwise uses the
|
|
1084
|
+
* inline `commands` map (resolved if it is a promise).
|
|
1085
|
+
*
|
|
1086
|
+
* @private
|
|
1087
|
+
* @param config - The structured commands configuration.
|
|
1088
|
+
* @returns Resolved commands with optional order.
|
|
1089
|
+
*/
|
|
1090
|
+
async function resolveCommandsConfig(config) {
|
|
1091
|
+
const { order, path, commands: innerCommands } = config;
|
|
1092
|
+
return {
|
|
1093
|
+
commands: await match(innerCommands).when(() => isString(path), async () => autoload({ dir: path })).with(P.instanceOf(Promise), async (p) => p).when(isPlainObject, (cmds) => cmds).otherwise(() => ({})),
|
|
1094
|
+
order
|
|
1095
|
+
};
|
|
952
1096
|
}
|
|
953
1097
|
/**
|
|
954
1098
|
* Load `kidd.config.ts` and autoload commands from its `commands` field.
|
|
@@ -962,8 +1106,8 @@ async function resolveCommands(commands) {
|
|
|
962
1106
|
async function resolveCommandsFromConfig() {
|
|
963
1107
|
const DEFAULT_COMMANDS_DIR = "./commands";
|
|
964
1108
|
const [configError, configResult] = await loadConfig();
|
|
965
|
-
if (configError || !configResult) return autoload({ dir: DEFAULT_COMMANDS_DIR });
|
|
966
|
-
return autoload({ dir: configResult.config.commands ?? DEFAULT_COMMANDS_DIR });
|
|
1109
|
+
if (configError || !configResult) return { commands: await autoload({ dir: DEFAULT_COMMANDS_DIR }) };
|
|
1110
|
+
return { commands: await autoload({ dir: configResult.config.commands ?? DEFAULT_COMMANDS_DIR }) };
|
|
967
1111
|
}
|
|
968
1112
|
/**
|
|
969
1113
|
* Change the process working directory when `--cwd` is provided.
|
|
@@ -978,6 +1122,47 @@ function applyCwd(argv) {
|
|
|
978
1122
|
if (isString(argv.cwd)) process.chdir(resolve(argv.cwd));
|
|
979
1123
|
}
|
|
980
1124
|
/**
|
|
1125
|
+
* Show help output when no command was matched.
|
|
1126
|
+
*
|
|
1127
|
+
* Prints the header (if configured) above the yargs help text. Skipped when
|
|
1128
|
+
* `--help` was explicitly passed, since yargs already handles that case.
|
|
1129
|
+
*
|
|
1130
|
+
* @private
|
|
1131
|
+
* @param params - The argv, commands, help options, and yargs program instance.
|
|
1132
|
+
*/
|
|
1133
|
+
function showNoCommandHelp({ argv, commands, help, program }) {
|
|
1134
|
+
if (!commands) return;
|
|
1135
|
+
if (argv.help) return;
|
|
1136
|
+
const header = extractHeader(help);
|
|
1137
|
+
if (header) {
|
|
1138
|
+
console.log(header);
|
|
1139
|
+
console.log();
|
|
1140
|
+
}
|
|
1141
|
+
program.showHelp("log");
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Extract the header string from help options.
|
|
1145
|
+
*
|
|
1146
|
+
* @private
|
|
1147
|
+
* @param help - The help options, possibly undefined.
|
|
1148
|
+
* @returns The header string or undefined.
|
|
1149
|
+
*/
|
|
1150
|
+
function extractHeader(help) {
|
|
1151
|
+
if (!help) return;
|
|
1152
|
+
return help.header;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Extract the footer string from help options.
|
|
1156
|
+
*
|
|
1157
|
+
* @private
|
|
1158
|
+
* @param help - The help options, possibly undefined.
|
|
1159
|
+
* @returns The footer string or undefined.
|
|
1160
|
+
*/
|
|
1161
|
+
function extractFooter(help) {
|
|
1162
|
+
if (!help) return;
|
|
1163
|
+
return help.footer;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
981
1166
|
* Handle a CLI error by logging the message and exiting with the appropriate code.
|
|
982
1167
|
*
|
|
983
1168
|
* ContextErrors carry a custom exit code; all other errors exit with code 1.
|
|
@@ -1001,21 +1186,6 @@ function exitOnError(error, logger) {
|
|
|
1001
1186
|
process.exit(info.exitCode);
|
|
1002
1187
|
}
|
|
1003
1188
|
//#endregion
|
|
1004
|
-
//#region src/command.ts
|
|
1005
|
-
/**
|
|
1006
|
-
* Define a CLI command with typed args, config, and handler.
|
|
1007
|
-
*
|
|
1008
|
-
* The `const TMiddleware` generic preserves the middleware tuple as a literal type,
|
|
1009
|
-
* enabling TypeScript to extract and intersect `Variables` from each middleware
|
|
1010
|
-
* element onto the handler's `ctx` type.
|
|
1011
|
-
*
|
|
1012
|
-
* @param def - Command definition including description, args schema, middleware, and handler.
|
|
1013
|
-
* @returns A resolved Command object for registration in the command map.
|
|
1014
|
-
*/
|
|
1015
|
-
function command(def) {
|
|
1016
|
-
return withTag({ ...def }, "Command");
|
|
1017
|
-
}
|
|
1018
|
-
//#endregion
|
|
1019
1189
|
//#region src/compose.ts
|
|
1020
1190
|
/**
|
|
1021
1191
|
* Middleware combinator that merges multiple middleware into one.
|