@gunshi/bone 0.27.6 → 0.28.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/lib/index.d.ts +25 -0
- package/lib/index.js +114 -33
- package/package.json +5 -5
package/lib/index.d.ts
CHANGED
|
@@ -1068,6 +1068,15 @@ interface CommandContext<G extends GunshiParamsConstraint = DefaultGunshiParams>
|
|
|
1068
1068
|
* The command call mode is `entry` when the command is executed as an entry command, and `subCommand` when the command is executed as a sub-command.
|
|
1069
1069
|
*/
|
|
1070
1070
|
callMode: CommandCallMode;
|
|
1071
|
+
/**
|
|
1072
|
+
* The path of nested sub-commands that were resolved to reach the current command.
|
|
1073
|
+
*
|
|
1074
|
+
* For example, if the user runs `git remote add`, `commandPath` would be `['remote', 'add']`.
|
|
1075
|
+
* For the entry command, this is an empty array.
|
|
1076
|
+
*
|
|
1077
|
+
* @since v0.28.0
|
|
1078
|
+
*/
|
|
1079
|
+
commandPath: string[];
|
|
1071
1080
|
/**
|
|
1072
1081
|
* Whether to convert the camel-case style argument name to kebab-case.
|
|
1073
1082
|
* This context value is set from {@linkcode Command.toKebab} option.
|
|
@@ -1209,6 +1218,15 @@ interface Command<G extends GunshiParamsConstraint = DefaultGunshiParams> {
|
|
|
1209
1218
|
* @since v0.27.0
|
|
1210
1219
|
*/
|
|
1211
1220
|
rendering?: RenderingOptions<G>;
|
|
1221
|
+
/**
|
|
1222
|
+
* Nested sub-commands for this command.
|
|
1223
|
+
*
|
|
1224
|
+
* Allows building command trees like `git remote add`.
|
|
1225
|
+
* Each key is the sub-command name, and the value is a command or lazy command.
|
|
1226
|
+
*
|
|
1227
|
+
* @since v0.28.0
|
|
1228
|
+
*/
|
|
1229
|
+
subCommands?: Record<string, SubCommandable> | Map<string, SubCommandable>;
|
|
1212
1230
|
}
|
|
1213
1231
|
/**
|
|
1214
1232
|
* Lazy command interface.
|
|
@@ -1285,6 +1303,13 @@ interface SubCommandable {
|
|
|
1285
1303
|
* see {@link LazyCommand.commandName}
|
|
1286
1304
|
*/
|
|
1287
1305
|
commandName?: string;
|
|
1306
|
+
/**
|
|
1307
|
+
* Nested sub-commands for this command.
|
|
1308
|
+
*
|
|
1309
|
+
* @see {@link Command.subCommands}
|
|
1310
|
+
* @since v0.28.0
|
|
1311
|
+
*/
|
|
1312
|
+
subCommands?: Record<string, any> | Map<string, any>;
|
|
1288
1313
|
/**
|
|
1289
1314
|
* Index signature to allow additional properties
|
|
1290
1315
|
*/
|
package/lib/index.js
CHANGED
|
@@ -555,7 +555,8 @@ async function resolveLazyCommand(cmd, name, needRunResolving = false) {
|
|
|
555
555
|
args: cmd.args,
|
|
556
556
|
examples: cmd.examples,
|
|
557
557
|
internal: cmd.internal,
|
|
558
|
-
entry: cmd.entry
|
|
558
|
+
entry: cmd.entry,
|
|
559
|
+
subCommands: cmd.subCommands
|
|
559
560
|
};
|
|
560
561
|
if ("resource" in cmd && cmd.resource) baseCommand.resource = cmd.resource;
|
|
561
562
|
command = Object.assign(create(), baseCommand);
|
|
@@ -571,6 +572,7 @@ async function resolveLazyCommand(cmd, name, needRunResolving = false) {
|
|
|
571
572
|
command.examples = loaded.examples;
|
|
572
573
|
command.internal = loaded.internal;
|
|
573
574
|
command.entry = loaded.entry;
|
|
575
|
+
command.subCommands = loaded.subCommands || cmd.subCommands;
|
|
574
576
|
if ("resource" in loaded && loaded.resource) command.resource = loaded.resource;
|
|
575
577
|
} else throw new TypeError(`Cannot resolve command: ${cmd.name || name}`);
|
|
576
578
|
}
|
|
@@ -596,6 +598,24 @@ function log(...args) {
|
|
|
596
598
|
console.log(...args);
|
|
597
599
|
}
|
|
598
600
|
/**
|
|
601
|
+
* Get the sub-commands of a command as a normalized Map.
|
|
602
|
+
*
|
|
603
|
+
* @param cmd - A command or lazy command
|
|
604
|
+
* @returns A Map of sub-commands, or undefined if the command has no sub-commands.
|
|
605
|
+
*/
|
|
606
|
+
function getCommandSubCommands(cmd) {
|
|
607
|
+
const subCommands = isLazyCommand(cmd) ? cmd.subCommands : typeof cmd === "object" ? cmd.subCommands : void 0;
|
|
608
|
+
if (!subCommands) return;
|
|
609
|
+
if (subCommands instanceof Map) return subCommands.size > 0 ? subCommands : void 0;
|
|
610
|
+
if (typeof subCommands === "object") {
|
|
611
|
+
const entries = Object.entries(subCommands);
|
|
612
|
+
if (entries.length === 0) return;
|
|
613
|
+
const map = /* @__PURE__ */ new Map();
|
|
614
|
+
for (const [name, cmd] of entries) map.set(name, cmd);
|
|
615
|
+
return map;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
599
619
|
* Deep freeze an object, making it immutable.
|
|
600
620
|
*
|
|
601
621
|
* @param obj - The object to freeze
|
|
@@ -635,7 +655,7 @@ function deepFreeze(obj, ignores = []) {
|
|
|
635
655
|
* @param param - A {@link CommandContextParams | parameters} to create a command context.
|
|
636
656
|
* @returns A {@link CommandContext | command context}, which is readonly.
|
|
637
657
|
*/
|
|
638
|
-
async function createCommandContext({ args = {}, explicit = {}, values = {}, positionals = [], rest = [], argv = [], tokens = [], command = {}, extensions = {}, cliOptions = {}, callMode = "entry", omitted = false, validationError = void 0 }) {
|
|
658
|
+
async function createCommandContext({ args = {}, explicit = {}, values = {}, positionals = [], rest = [], argv = [], tokens = [], command = {}, extensions = {}, cliOptions = {}, callMode = "entry", commandPath = [], omitted = false, validationError = void 0 }) {
|
|
639
659
|
/**
|
|
640
660
|
* normailize the options schema and values, to avoid prototype pollution
|
|
641
661
|
*/
|
|
@@ -664,6 +684,7 @@ async function createCommandContext({ args = {}, explicit = {}, values = {}, pos
|
|
|
664
684
|
description: command.description,
|
|
665
685
|
omitted,
|
|
666
686
|
callMode,
|
|
687
|
+
commandPath,
|
|
667
688
|
env,
|
|
668
689
|
args: _args,
|
|
669
690
|
explicit,
|
|
@@ -881,16 +902,18 @@ async function cliCore(argv, entry, options, plugins) {
|
|
|
881
902
|
const resolvedPlugins = await applyPlugins(pluginContext, [...plugins, ...options.plugins || []]);
|
|
882
903
|
const cliOptions = normalizeCliOptions(options, decorators, pluginContext);
|
|
883
904
|
const tokens = parseArgs(argv);
|
|
884
|
-
const
|
|
885
|
-
const { commandName: name, command, callMode } =
|
|
905
|
+
const resolved = resolveCommandTree(tokens, entry, cliOptions);
|
|
906
|
+
const { commandName: name, command, callMode, commandPath, depth, levelSubCommands } = resolved;
|
|
886
907
|
if (!command) throw new Error(`Command not found: ${name || ""}`);
|
|
887
908
|
const args = resolveArguments(pluginContext, getCommandArgs(command));
|
|
909
|
+
const skipPositional = depth > 0 ? depth - 1 : -1;
|
|
888
910
|
const { explicit, values, positionals, rest, error } = resolveArgs(args, tokens, {
|
|
889
911
|
shortGrouping: true,
|
|
890
912
|
toKebab: command.toKebab,
|
|
891
|
-
skipPositional
|
|
913
|
+
skipPositional
|
|
892
914
|
});
|
|
893
|
-
const omitted =
|
|
915
|
+
const omitted = resolved.omitted;
|
|
916
|
+
if (levelSubCommands) cliOptions.subCommands = levelSubCommands;
|
|
894
917
|
const resolvedCommand = isLazyCommand(command) ? await resolveLazyCommand(command, name, true) : command;
|
|
895
918
|
return await executeCommand(resolvedCommand, await createCommandContext({
|
|
896
919
|
args,
|
|
@@ -902,6 +925,7 @@ async function cliCore(argv, entry, options, plugins) {
|
|
|
902
925
|
tokens,
|
|
903
926
|
omitted,
|
|
904
927
|
callMode,
|
|
928
|
+
commandPath,
|
|
905
929
|
command: resolvedCommand,
|
|
906
930
|
extensions: getPluginExtensions(resolvedPlugins),
|
|
907
931
|
validationError: error,
|
|
@@ -938,9 +962,12 @@ function createInitialSubCommands(options, entryCmd) {
|
|
|
938
962
|
const subCommands = new Map(options.subCommands instanceof Map ? options.subCommands : []);
|
|
939
963
|
if (!(options.subCommands instanceof Map) && isObject(options.subCommands)) for (const [name, cmd] of Object.entries(options.subCommands)) subCommands.set(name, cmd);
|
|
940
964
|
if (hasSubCommands) {
|
|
941
|
-
if (isLazyCommand(entryCmd)
|
|
942
|
-
entryCmd
|
|
943
|
-
subCommands.set(resolveEntryName(
|
|
965
|
+
if (isLazyCommand(entryCmd)) {
|
|
966
|
+
const entryCopy = Object.assign((...args) => entryCmd(...args), entryCmd, { entry: true });
|
|
967
|
+
subCommands.set(resolveEntryName(entryCopy), entryCopy);
|
|
968
|
+
} else if (typeof entryCmd === "object") {
|
|
969
|
+
const entryCopy = Object.assign(create(), entryCmd, { entry: true });
|
|
970
|
+
subCommands.set(resolveEntryName(entryCopy), entryCopy);
|
|
944
971
|
} else if (typeof entryCmd === "function") {
|
|
945
972
|
const name = entryCmd.name || ANONYMOUS_COMMAND_NAME;
|
|
946
973
|
subCommands.set(name, {
|
|
@@ -960,48 +987,102 @@ function normalizeCliOptions(options, decorators, pluginContext) {
|
|
|
960
987
|
if (resolvedOptions.renderValidationErrors === void 0) resolvedOptions.renderValidationErrors = decorators.getValidationErrorsRenderer();
|
|
961
988
|
return resolvedOptions;
|
|
962
989
|
}
|
|
963
|
-
function
|
|
964
|
-
|
|
965
|
-
return firstToken && firstToken.kind === "positional" && firstToken.index === 0 && firstToken.value ? firstToken.value : "";
|
|
990
|
+
function getPositionalTokens(tokens) {
|
|
991
|
+
return tokens.filter((t) => t.kind === "positional").map((t) => t.value).filter((v) => !!v);
|
|
966
992
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
function doResolveCommand() {
|
|
993
|
+
function resolveCommandTree(tokens, entry, options) {
|
|
994
|
+
const positionals = getPositionalTokens(tokens);
|
|
995
|
+
function resolveAsEntry() {
|
|
971
996
|
if (typeof entry === "function") if ("commandName" in entry && entry.commandName) return {
|
|
972
997
|
commandName: entry.commandName,
|
|
973
998
|
command: entry,
|
|
974
|
-
callMode: "entry"
|
|
999
|
+
callMode: "entry",
|
|
1000
|
+
commandPath: [],
|
|
1001
|
+
depth: 0,
|
|
1002
|
+
omitted: options.subCommands.size > 0 && !positionals[0],
|
|
1003
|
+
levelSubCommands: options.subCommands.size > 0 ? options.subCommands : void 0
|
|
975
1004
|
};
|
|
976
1005
|
else return {
|
|
977
1006
|
command: {
|
|
978
1007
|
run: entry,
|
|
979
1008
|
entry: true
|
|
980
1009
|
},
|
|
981
|
-
callMode: "entry"
|
|
1010
|
+
callMode: "entry",
|
|
1011
|
+
commandPath: [],
|
|
1012
|
+
depth: 0,
|
|
1013
|
+
omitted: options.subCommands.size > 0 && !positionals[0],
|
|
1014
|
+
levelSubCommands: options.subCommands.size > 0 ? options.subCommands : void 0
|
|
982
1015
|
};
|
|
983
1016
|
else if (typeof entry === "object") return {
|
|
984
1017
|
commandName: resolveEntryName(entry),
|
|
985
1018
|
command: entry,
|
|
986
|
-
callMode: "entry"
|
|
1019
|
+
callMode: "entry",
|
|
1020
|
+
commandPath: [],
|
|
1021
|
+
depth: 0,
|
|
1022
|
+
omitted: options.subCommands.size > 0 && !positionals[0],
|
|
1023
|
+
levelSubCommands: options.subCommands.size > 0 ? options.subCommands : void 0
|
|
987
1024
|
};
|
|
988
|
-
else return
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
return {
|
|
995
|
-
commandName: sub,
|
|
996
|
-
callMode: "unexpected"
|
|
1025
|
+
else return {
|
|
1026
|
+
callMode: "unexpected",
|
|
1027
|
+
commandPath: [],
|
|
1028
|
+
depth: 0,
|
|
1029
|
+
omitted: false,
|
|
1030
|
+
levelSubCommands: void 0
|
|
997
1031
|
};
|
|
998
1032
|
}
|
|
999
|
-
if (
|
|
1000
|
-
|
|
1033
|
+
if (positionals.length === 0 || options.subCommands.size === 0) return resolveAsEntry();
|
|
1034
|
+
let currentSubCommands = options.subCommands;
|
|
1035
|
+
let resolvedCommand;
|
|
1036
|
+
let resolvedName;
|
|
1037
|
+
const commandPath = [];
|
|
1038
|
+
let depth = 0;
|
|
1039
|
+
for (let i = 0; i < positionals.length; i++) {
|
|
1040
|
+
const token = positionals[i];
|
|
1041
|
+
const cmd = currentSubCommands.get(token);
|
|
1042
|
+
if (cmd == null) {
|
|
1043
|
+
if (depth === 0) {
|
|
1044
|
+
if (options.fallbackToEntry) return resolveAsEntry();
|
|
1045
|
+
return {
|
|
1046
|
+
commandName: token,
|
|
1047
|
+
callMode: "unexpected",
|
|
1048
|
+
commandPath: [],
|
|
1049
|
+
depth: 0,
|
|
1050
|
+
omitted: false,
|
|
1051
|
+
levelSubCommands: void 0
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
let resolved = cmd;
|
|
1057
|
+
if (typeof cmd === "function" && cmd.commandName == null) resolved = Object.assign((...args) => cmd(...args), cmd, { commandName: token });
|
|
1058
|
+
else if (typeof cmd === "object" && cmd.name == null) resolved = Object.assign(create(), cmd, { name: token });
|
|
1059
|
+
resolvedCommand = resolved;
|
|
1060
|
+
resolvedName = token;
|
|
1061
|
+
commandPath.push(token);
|
|
1062
|
+
depth++;
|
|
1063
|
+
const nestedSubCommands = getCommandSubCommands(cmd);
|
|
1064
|
+
if (nestedSubCommands && nestedSubCommands.size > 0) currentSubCommands = nestedSubCommands;
|
|
1065
|
+
else break;
|
|
1066
|
+
}
|
|
1067
|
+
if (!resolvedCommand) return resolveAsEntry();
|
|
1068
|
+
const resolvedSubCommands = getCommandSubCommands(resolvedCommand);
|
|
1069
|
+
const omitted = resolvedSubCommands != null && resolvedSubCommands.size > 0;
|
|
1070
|
+
let levelSubCommands;
|
|
1071
|
+
if (omitted && resolvedSubCommands) {
|
|
1072
|
+
levelSubCommands = new Map(resolvedSubCommands);
|
|
1073
|
+
let entryCopy;
|
|
1074
|
+
if (typeof resolvedCommand === "function") entryCopy = Object.assign((...args) => resolvedCommand(...args), resolvedCommand, { entry: true });
|
|
1075
|
+
else entryCopy = Object.assign(create(), resolvedCommand, { entry: true });
|
|
1076
|
+
levelSubCommands.set(resolvedName || resolveEntryName(entryCopy), entryCopy);
|
|
1077
|
+
}
|
|
1001
1078
|
return {
|
|
1002
|
-
commandName:
|
|
1003
|
-
command:
|
|
1004
|
-
callMode: "subCommand"
|
|
1079
|
+
commandName: resolvedName,
|
|
1080
|
+
command: resolvedCommand,
|
|
1081
|
+
callMode: depth > 0 ? "subCommand" : "entry",
|
|
1082
|
+
commandPath,
|
|
1083
|
+
depth,
|
|
1084
|
+
omitted,
|
|
1085
|
+
levelSubCommands
|
|
1005
1086
|
};
|
|
1006
1087
|
}
|
|
1007
1088
|
function resolveEntryName(entry) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gunshi/bone",
|
|
3
3
|
"description": "gunshi minimum",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.28.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "kazuya kawaguchi",
|
|
7
7
|
"email": "kawakazu80@gmail.com"
|
|
@@ -56,10 +56,10 @@
|
|
|
56
56
|
"jsr-exports-lint": "^0.4.1",
|
|
57
57
|
"publint": "^0.3.16",
|
|
58
58
|
"tsdown": "0.15.12",
|
|
59
|
-
"@gunshi/definition": "0.
|
|
60
|
-
"@gunshi/plugin-global": "0.
|
|
61
|
-
"@gunshi/plugin-renderer": "0.
|
|
62
|
-
"gunshi": "0.
|
|
59
|
+
"@gunshi/definition": "0.28.0",
|
|
60
|
+
"@gunshi/plugin-global": "0.28.0",
|
|
61
|
+
"@gunshi/plugin-renderer": "0.28.0",
|
|
62
|
+
"gunshi": "0.28.0"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsdown",
|