cli-api 0.1.2 → 0.2.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 +22 -25
- package/dist/index.d.mts +394 -0
- package/dist/index.mjs +3 -0
- package/dist/interfaces-COq24bNI.mjs +391 -0
- package/dist/run-C903J5ca.mjs +1137 -0
- package/package.json +37 -37
- package/.hgignore +0 -12
- package/Makefile +0 -14
- package/babel.config.json +0 -33
- package/dist/cjs/index.js +0 -588
- package/dist/cjs/index.js.map +0 -1
- package/dist/es/index.mjs +0 -578
- package/dist/es/index.mjs.map +0 -1
- package/dist/types/app-help.d.ts +0 -3
- package/dist/types/commands/command-help.d.ts +0 -2
- package/dist/types/commands/version.d.ts +0 -2
- package/dist/types/constants.d.ts +0 -4
- package/dist/types/index.d.ts +0 -3
- package/dist/types/interfaces.d.ts +0 -79
- package/dist/types/options.d.ts +0 -7
- package/dist/types/print-command-help.d.ts +0 -2
- package/dist/types/run.d.ts +0 -2
- package/dist/types/utils.d.ts +0 -17
- package/rollup.config.js +0 -44
- package/tsconfig.json +0 -32
|
@@ -0,0 +1,1137 @@
|
|
|
1
|
+
import { a as getExecuteHandler, c as createChalk, i as OptType, n as Command, o as hasSubCommands, r as ExecutionContext, s as isExecutable } from "./interfaces-COq24bNI.mjs";
|
|
2
|
+
import Path from "path";
|
|
3
|
+
import stringWidth from "string-width";
|
|
4
|
+
import FileSys from "fs";
|
|
5
|
+
|
|
6
|
+
//#region src/constants.ts
|
|
7
|
+
const EMPTY_ARRAY = Object.freeze([]);
|
|
8
|
+
const EMPTY_OBJECT = Object.freeze(Object.create({ __proto__: null }));
|
|
9
|
+
const TRUE_VALUES = new Set([
|
|
10
|
+
"y",
|
|
11
|
+
"yes",
|
|
12
|
+
"t",
|
|
13
|
+
"true",
|
|
14
|
+
"1",
|
|
15
|
+
"on"
|
|
16
|
+
]);
|
|
17
|
+
const FALSE_VALUES = new Set([
|
|
18
|
+
"n",
|
|
19
|
+
"no",
|
|
20
|
+
"f",
|
|
21
|
+
"false",
|
|
22
|
+
"0",
|
|
23
|
+
"off"
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/utils.ts
|
|
28
|
+
function print(str = "") {
|
|
29
|
+
return process.stdout.write(str);
|
|
30
|
+
}
|
|
31
|
+
function printLn(...args) {
|
|
32
|
+
console.log(...args);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Semantic categories for user-facing CLI errors.
|
|
36
|
+
*/
|
|
37
|
+
let ErrorCategory = /* @__PURE__ */ function(ErrorCategory$1) {
|
|
38
|
+
ErrorCategory$1["InvalidArg"] = "invalid-arg";
|
|
39
|
+
ErrorCategory$1["Misconfig"] = "misconfig";
|
|
40
|
+
ErrorCategory$1["Internal"] = "internal";
|
|
41
|
+
return ErrorCategory$1;
|
|
42
|
+
}({});
|
|
43
|
+
const ERROR_PRESENTATION = {
|
|
44
|
+
[ErrorCategory.InvalidArg]: {
|
|
45
|
+
code: 2,
|
|
46
|
+
color: "#D73737"
|
|
47
|
+
},
|
|
48
|
+
[ErrorCategory.Misconfig]: {
|
|
49
|
+
code: 254,
|
|
50
|
+
color: "#B854D4"
|
|
51
|
+
},
|
|
52
|
+
[ErrorCategory.Internal]: {
|
|
53
|
+
code: 253,
|
|
54
|
+
color: "#6684E1"
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
function blockError(str, style, chalk) {
|
|
58
|
+
const lines = str.split("\n");
|
|
59
|
+
const width = Math.max(...lines.map((l) => stringWidth(l))) + 4;
|
|
60
|
+
const colorize = chalk.bgHex(ERROR_PRESENTATION[style].color).hex("#FEFBEC");
|
|
61
|
+
printLn(colorize(space(width)));
|
|
62
|
+
for (const line of lines) {
|
|
63
|
+
const txt = ` ${line}`;
|
|
64
|
+
printLn(colorize(txt + space(width, txt)));
|
|
65
|
+
}
|
|
66
|
+
printLn(colorize(space(width)));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Creates a structured CLI error.
|
|
70
|
+
*
|
|
71
|
+
* @param message The error text that should be rendered.
|
|
72
|
+
* @param type The semantic error category used to determine presentation and exit code.
|
|
73
|
+
* @returns A structured CLI error object.
|
|
74
|
+
*/
|
|
75
|
+
function createError(message, type) {
|
|
76
|
+
return {
|
|
77
|
+
message,
|
|
78
|
+
type
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Gets the process exit code associated with a given CLI error.
|
|
83
|
+
*
|
|
84
|
+
* @param error The structured CLI error to map to a process exit code.
|
|
85
|
+
* @returns The default exit code used for that error type.
|
|
86
|
+
*/
|
|
87
|
+
function getErrorExitCode(error) {
|
|
88
|
+
return ERROR_PRESENTATION[error.type].code;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Prints a user-facing CLI error block.
|
|
92
|
+
*
|
|
93
|
+
* @param error The structured CLI error to render.
|
|
94
|
+
* @returns Nothing.
|
|
95
|
+
*/
|
|
96
|
+
function printError(error, chalk) {
|
|
97
|
+
blockError(error.message, error.type, chalk);
|
|
98
|
+
}
|
|
99
|
+
function toArray(x) {
|
|
100
|
+
if (!x) return EMPTY_ARRAY;
|
|
101
|
+
if (Array.isArray(x)) return x;
|
|
102
|
+
return [x];
|
|
103
|
+
}
|
|
104
|
+
function resolve(x) {
|
|
105
|
+
return typeof x === "function" ? x() : x;
|
|
106
|
+
}
|
|
107
|
+
function toBool(str) {
|
|
108
|
+
if (typeof str === "boolean") return str;
|
|
109
|
+
str = String(str).trim().toLowerCase();
|
|
110
|
+
if (TRUE_VALUES.has(str)) return true;
|
|
111
|
+
if (FALSE_VALUES.has(str)) return false;
|
|
112
|
+
throw new Error(`Could not cast "${str}" to boolean`);
|
|
113
|
+
}
|
|
114
|
+
function space(len, str) {
|
|
115
|
+
if (str) len -= stringWidth(str);
|
|
116
|
+
return len > 0 ? " ".repeat(len) : "";
|
|
117
|
+
}
|
|
118
|
+
function getTerminalWidth() {
|
|
119
|
+
return process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
120
|
+
}
|
|
121
|
+
function wrapText(text, width) {
|
|
122
|
+
if (width <= 0) return text.split("\n");
|
|
123
|
+
const wrappedLines = [];
|
|
124
|
+
for (const rawLine of text.split("\n")) {
|
|
125
|
+
if (rawLine.trim().length === 0) {
|
|
126
|
+
wrappedLines.push("");
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const words = rawLine.trim().split(/\s+/);
|
|
130
|
+
let currentLine = "";
|
|
131
|
+
for (const word of words) {
|
|
132
|
+
const candidate = currentLine.length === 0 ? word : `${currentLine} ${word}`;
|
|
133
|
+
if (currentLine.length > 0 && stringWidth(candidate) > width) {
|
|
134
|
+
wrappedLines.push(currentLine);
|
|
135
|
+
currentLine = word;
|
|
136
|
+
} else currentLine = candidate;
|
|
137
|
+
}
|
|
138
|
+
if (currentLine.length > 0) wrappedLines.push(currentLine);
|
|
139
|
+
}
|
|
140
|
+
return wrappedLines;
|
|
141
|
+
}
|
|
142
|
+
function getProcName(app) {
|
|
143
|
+
const bin = app._bin;
|
|
144
|
+
if (bin != null) return bin;
|
|
145
|
+
const relPath = Path.relative(process.cwd(), process.argv[1]);
|
|
146
|
+
return `${Path.basename(process.argv[0])} ${relPath.length < process.argv[1].length ? relPath : process.argv[1]}`;
|
|
147
|
+
}
|
|
148
|
+
function includes(needle, haystack) {
|
|
149
|
+
if (!haystack) return false;
|
|
150
|
+
if (Array.isArray(haystack)) return haystack.includes(needle);
|
|
151
|
+
return needle === haystack;
|
|
152
|
+
}
|
|
153
|
+
function statSync(path) {
|
|
154
|
+
try {
|
|
155
|
+
return FileSys.lstatSync(path);
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function sortBy(arr, cmp) {
|
|
161
|
+
const collator = new Intl.Collator();
|
|
162
|
+
const values = arr.map(cmp);
|
|
163
|
+
const keys = Array.from(arr.keys());
|
|
164
|
+
keys.sort((a, b) => collator.compare(values[a], values[b]));
|
|
165
|
+
return keys.map((i) => arr[i]);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/commands/version.ts
|
|
170
|
+
const versionCommand = new Command("version").describe("Displays current version").run(async (_, __, context) => {
|
|
171
|
+
printLn(context.app._version);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/options.ts
|
|
176
|
+
/**
|
|
177
|
+
* Error thrown when argument parsing encounters an unknown option name.
|
|
178
|
+
*
|
|
179
|
+
* @param option The unrecognized option token, including its leading dashes.
|
|
180
|
+
* @returns A parser error whose message matches the CLI's unknown-option wording.
|
|
181
|
+
*/
|
|
182
|
+
var UnknownOptionError = class extends Error {
|
|
183
|
+
option;
|
|
184
|
+
constructor(option) {
|
|
185
|
+
super(`option ${option} not recognized`);
|
|
186
|
+
this.option = option;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
function getEnumValues(item) {
|
|
190
|
+
if (Array.isArray(item.type)) return item.type;
|
|
191
|
+
if (item.type === OptType.ENUM) return item.enumValues;
|
|
192
|
+
}
|
|
193
|
+
function getOptionImplicitValue(opt) {
|
|
194
|
+
if (opt.valueIfSet !== void 0) return resolve(opt.valueIfSet);
|
|
195
|
+
if (opt.type === OptType.BOOL) return true;
|
|
196
|
+
return !resolve(opt.defaultValue);
|
|
197
|
+
}
|
|
198
|
+
function getOptionNoPrefixValue(opt) {
|
|
199
|
+
if (opt.valueIfNoPrefix !== void 0) return resolve(opt.valueIfNoPrefix);
|
|
200
|
+
if (opt.type === OptType.BOOL) return false;
|
|
201
|
+
throw new Error(`\`${getOptName(opt)}\` option must define valueIfNoPrefix when noPrefix is enabled`);
|
|
202
|
+
}
|
|
203
|
+
function isRepeatable(value) {
|
|
204
|
+
return value === true || typeof value === "number";
|
|
205
|
+
}
|
|
206
|
+
function getMaxRepeatCount(value) {
|
|
207
|
+
if (value === true || value === false || value === void 0) return;
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
function getMinRequiredCount(value) {
|
|
211
|
+
if (value === true) return 1;
|
|
212
|
+
if (typeof value === "number") return value;
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
function assertValidCount(name, value, { allowZero = false } = {}) {
|
|
216
|
+
if (!Number.isInteger(value) || value < 0 || !allowZero && value === 0) throw new Error(`${name} must be ${allowZero ? "a non-negative" : "a positive"} integer`);
|
|
217
|
+
}
|
|
218
|
+
function validatePositionalDefinitions(cmd) {
|
|
219
|
+
if (!cmd.positonals?.length) return;
|
|
220
|
+
let encounteredOptionalPositional = false;
|
|
221
|
+
for (let i = 0; i < cmd.positonals.length; ++i) {
|
|
222
|
+
const arg = cmd.positonals[i];
|
|
223
|
+
const repeatable = isRepeatable(arg.repeatable);
|
|
224
|
+
const minRequired = getMinRequiredCount(arg.required);
|
|
225
|
+
const maxRepeatCount = getMaxRepeatCount(arg.repeatable);
|
|
226
|
+
if (typeof arg.required === "number") {
|
|
227
|
+
assertValidCount(`"${arg.name}" argument required count`, arg.required, { allowZero: true });
|
|
228
|
+
if (!repeatable) throw new Error(`"${arg.name}" argument cannot use a numeric required count unless it is repeatable`);
|
|
229
|
+
}
|
|
230
|
+
if (typeof arg.repeatable === "number") assertValidCount(`"${arg.name}" argument repeatable count`, arg.repeatable);
|
|
231
|
+
if (repeatable && i < cmd.positonals.length - 1) throw new Error("Only the last argument can be repeatable");
|
|
232
|
+
if (maxRepeatCount !== void 0 && minRequired > maxRepeatCount) throw new Error(`"${arg.name}" argument requires at least ${minRequired} values but allows at most ${maxRepeatCount}`);
|
|
233
|
+
if (encounteredOptionalPositional && minRequired > 0) throw new Error("Required arguments cannot come after optional arguments");
|
|
234
|
+
if (minRequired === 0) encounteredOptionalPositional = true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function pushRepeatableValue(target, value, itemName, maxCount, kind) {
|
|
238
|
+
if (maxCount !== void 0 && target.length >= maxCount) throw new Error(`"${itemName}" ${kind} allows at most ${maxCount} value${maxCount === 1 ? "" : "s"}`);
|
|
239
|
+
target.push(value);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Formats an option definition for display in help output.
|
|
243
|
+
*
|
|
244
|
+
* @param opt The option metadata to render.
|
|
245
|
+
* @returns A tuple containing the formatted flag label and its description text.
|
|
246
|
+
*/
|
|
247
|
+
function formatOption(opt, chalk) {
|
|
248
|
+
const aliases = [];
|
|
249
|
+
if (opt.alias) if (Array.isArray(opt.alias)) aliases.push(...opt.alias);
|
|
250
|
+
else aliases.push(opt.alias);
|
|
251
|
+
let flags = [...aliases.map((a) => chalk.green(a.length === 1 ? `-${a}` : `--${a}`)), chalk.green(`--${opt.name}`)].join(", ");
|
|
252
|
+
if (!opt.alias && opt.name.length > 1) flags = ` ${flags}`;
|
|
253
|
+
if (opt.type !== OptType.BOOL) {
|
|
254
|
+
const valuePlaceholder = chalk.magenta(getValuePlaceholder(opt));
|
|
255
|
+
flags += opt.valueNotRequired ? `${chalk.grey("[")}=${valuePlaceholder}${chalk.grey("]")}` : `=${valuePlaceholder}`;
|
|
256
|
+
}
|
|
257
|
+
if (opt.noPrefix) flags += `, ${chalk.green(`--no-${opt.name}`)}`;
|
|
258
|
+
let desc = opt.description ?? "";
|
|
259
|
+
let defaultValueText = opt.defaultValueText;
|
|
260
|
+
if (defaultValueText === void 0 && opt.defaultValue !== void 0) defaultValueText = JSON.stringify(resolve(opt.defaultValue));
|
|
261
|
+
if (defaultValueText !== void 0) desc += chalk.yellow(` [default: ${defaultValueText}]`);
|
|
262
|
+
const enumValues = getEnumValues(opt);
|
|
263
|
+
if (enumValues?.length) desc += ` [possible values: ${enumValues.join(", ")}]`;
|
|
264
|
+
return [flags, desc];
|
|
265
|
+
}
|
|
266
|
+
function getValuePlaceholder(opt) {
|
|
267
|
+
if (opt.valuePlaceholder !== void 0) return opt.valuePlaceholder;
|
|
268
|
+
const enumValues = getEnumValues(opt);
|
|
269
|
+
if (enumValues !== void 0) return enumValues.join("|").toUpperCase();
|
|
270
|
+
else if (opt.type == OptType.BOOL) return JSON.stringify(!resolve(opt.defaultValue));
|
|
271
|
+
else if (opt.type === OptType.INT || opt.type === OptType.FLOAT) return "#";
|
|
272
|
+
else if (opt.type === OptType.INPUT_FILE || opt.type === OptType.OUTPUT_FILE) return "FILE";
|
|
273
|
+
else if (opt.type === OptType.INPUT_DIRECTORY || opt.type === OptType.OUTPUT_DIRECTORY || opt.type === OptType.EMPTY_DIRECTORY) return "DIR";
|
|
274
|
+
else return opt.name.toUpperCase();
|
|
275
|
+
}
|
|
276
|
+
function sortOptions(options) {
|
|
277
|
+
return sortBy(options, (option) => option.name);
|
|
278
|
+
}
|
|
279
|
+
function getOptions(cmd) {
|
|
280
|
+
return sortOptions(toArray(cmd.options));
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Validates a command's static option and positional configuration before parsing argv.
|
|
284
|
+
*
|
|
285
|
+
* @param cmd The command definition to validate.
|
|
286
|
+
* @returns Nothing. Throws when the command configuration is internally inconsistent.
|
|
287
|
+
*/
|
|
288
|
+
function validateCommandConfig(cmd) {
|
|
289
|
+
validatePositionalDefinitions(cmd);
|
|
290
|
+
const seenOptionTokens = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const opt of getOptions(cmd)) {
|
|
292
|
+
if (typeof opt.repeatable === "number") assertValidCount(`\`${opt.name}\` option repeatable count`, opt.repeatable);
|
|
293
|
+
if (opt.noPrefix && !opt.valueNotRequired) throw new Error(`\`${getOptName(opt)}\` option cannot enable noPrefix unless valueNotRequired is enabled`);
|
|
294
|
+
if (opt.type === OptType.ENUM && !getEnumValues(opt)?.length) throw new Error(`\`${getOptName(opt)}\` option must define enumValues when using OptType.ENUM`);
|
|
295
|
+
if (opt.noPrefix && opt.type !== OptType.BOOL && opt.valueIfNoPrefix === void 0) throw new Error(`\`${getOptName(opt)}\` option must define valueIfNoPrefix when noPrefix is enabled`);
|
|
296
|
+
const tokens = new Set([opt.name]);
|
|
297
|
+
for (const alias of toArray(opt.alias)) tokens.add(alias);
|
|
298
|
+
if (opt.noPrefix) tokens.add(`no-${opt.name}`);
|
|
299
|
+
for (const token of tokens) {
|
|
300
|
+
const existing = seenOptionTokens.get(token);
|
|
301
|
+
if (existing !== void 0) {
|
|
302
|
+
const currentName = token.startsWith("no-") ? `--${token}` : getOptName(opt);
|
|
303
|
+
throw new Error(`Option token \`${currentName}\` collides with \`${existing}\``);
|
|
304
|
+
}
|
|
305
|
+
const displayName = token.startsWith("no-") ? `--${token}` : token.length === 1 ? `-${token}` : `--${token}`;
|
|
306
|
+
seenOptionTokens.set(token, displayName);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function parseArgs(cmd, argv) {
|
|
311
|
+
const args = [];
|
|
312
|
+
const opts = Object.create(null);
|
|
313
|
+
let parseFlags = true;
|
|
314
|
+
const allOptions = getOptions(cmd);
|
|
315
|
+
validateCommandConfig(cmd);
|
|
316
|
+
for (const opt of allOptions) if (isRepeatable(opt.repeatable)) {
|
|
317
|
+
const k = opt.key ?? opt.name;
|
|
318
|
+
if (opts[k] === void 0) opts[k] = [];
|
|
319
|
+
}
|
|
320
|
+
if (cmd.positonals?.length) for (let i = 0; i < cmd.positonals.length; ++i) {
|
|
321
|
+
const a = cmd.positonals[i];
|
|
322
|
+
if (isRepeatable(a.repeatable)) {
|
|
323
|
+
const k = a.key ?? a.name;
|
|
324
|
+
if (k && opts[k] === void 0) opts[k] = [];
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const findOpt = (name) => {
|
|
328
|
+
const option = allOptions.find((o) => o.name === name || includes(name, o.alias));
|
|
329
|
+
if (option !== void 0) return {
|
|
330
|
+
opt: option,
|
|
331
|
+
negated: false
|
|
332
|
+
};
|
|
333
|
+
const negatedOption = allOptions.find((o) => o.noPrefix && `no-${o.name}` === name);
|
|
334
|
+
if (negatedOption !== void 0) return {
|
|
335
|
+
opt: negatedOption,
|
|
336
|
+
negated: true
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
const getUnknownShortOption = (cluster) => {
|
|
340
|
+
for (let j = 0; j < cluster.length; ++j) {
|
|
341
|
+
const ch = cluster[j];
|
|
342
|
+
if (!findOpt(ch)) return ch;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
let argIdx = 0;
|
|
346
|
+
for (let i = 0; i < argv.length; ++i) {
|
|
347
|
+
let token = argv[i];
|
|
348
|
+
if (parseFlags && token === "--") {
|
|
349
|
+
parseFlags = false;
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (parseFlags && token.length >= 2 && token.startsWith("-")) {
|
|
353
|
+
let inlineValue;
|
|
354
|
+
if (token.startsWith("--")) {
|
|
355
|
+
if (token.includes("=")) {
|
|
356
|
+
const [left, right] = token.split("=", 2);
|
|
357
|
+
token = left;
|
|
358
|
+
inlineValue = right;
|
|
359
|
+
}
|
|
360
|
+
const name = token.slice(2);
|
|
361
|
+
const match = findOpt(name);
|
|
362
|
+
if (!match) throw new UnknownOptionError(`--${name}`);
|
|
363
|
+
const { opt, negated } = match;
|
|
364
|
+
if (negated && inlineValue !== void 0) throw new Error(`Option \`--no-${opt.name}\` does not take a value`);
|
|
365
|
+
let value = inlineValue;
|
|
366
|
+
if (negated) value = getOptionNoPrefixValue(opt);
|
|
367
|
+
else if (value === void 0) if (opt.valueNotRequired) value = getOptionImplicitValue(opt);
|
|
368
|
+
else if (i < argv.length - 1) value = argv[++i];
|
|
369
|
+
else throw new Error(`Missing required value for option \`${token}\``);
|
|
370
|
+
if (opt.type != null) value = coerceType(value, opt.type, `option \`${token}\``, getEnumValues(opt));
|
|
371
|
+
const k = opt.key ?? opt.name;
|
|
372
|
+
if (isRepeatable(opt.repeatable)) pushRepeatableValue(opts[k], value, opt.name, getMaxRepeatCount(opt.repeatable), "option");
|
|
373
|
+
else opts[k] = value;
|
|
374
|
+
} else {
|
|
375
|
+
const clusterText = token.slice(1);
|
|
376
|
+
const equalIndex = clusterText.indexOf("=");
|
|
377
|
+
const cluster = equalIndex === -1 ? clusterText : clusterText.slice(0, equalIndex);
|
|
378
|
+
const hasInlineAssignment = equalIndex !== -1;
|
|
379
|
+
inlineValue = equalIndex === -1 ? void 0 : clusterText.slice(equalIndex + 1);
|
|
380
|
+
if (hasInlineAssignment) {
|
|
381
|
+
const unknownOption = getUnknownShortOption(cluster);
|
|
382
|
+
if (unknownOption !== void 0) throw new UnknownOptionError(`-${unknownOption}`);
|
|
383
|
+
}
|
|
384
|
+
let j = 0;
|
|
385
|
+
while (j < cluster.length) {
|
|
386
|
+
const ch = cluster[j];
|
|
387
|
+
const match = findOpt(ch);
|
|
388
|
+
if (!match) throw new UnknownOptionError(`-${ch}`);
|
|
389
|
+
const { opt } = match;
|
|
390
|
+
if (opt.valueNotRequired) {
|
|
391
|
+
const k$1 = opt.key ?? opt.name;
|
|
392
|
+
const v = getOptionImplicitValue(opt);
|
|
393
|
+
if (isRepeatable(opt.repeatable)) pushRepeatableValue(opts[k$1], v, opt.name, getMaxRepeatCount(opt.repeatable), "option");
|
|
394
|
+
else opts[k$1] = v;
|
|
395
|
+
j += 1;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
let value;
|
|
399
|
+
const remainder = cluster.slice(j + 1);
|
|
400
|
+
if (remainder.length && !hasInlineAssignment) value = remainder;
|
|
401
|
+
else if (remainder.length && hasInlineAssignment) throw new Error(`Missing required value for option \`-${ch}\``);
|
|
402
|
+
else if (inlineValue !== void 0 && remainder.length === 0) value = inlineValue;
|
|
403
|
+
else if (i < argv.length - 1) value = argv[++i];
|
|
404
|
+
else throw new Error(`Missing required value for option "-${ch}"`);
|
|
405
|
+
if (opt.type != null) value = coerceType(value, opt.type, `option \`-${ch}\``, getEnumValues(opt));
|
|
406
|
+
const k = opt.key ?? opt.name;
|
|
407
|
+
if (isRepeatable(opt.repeatable)) pushRepeatableValue(opts[k], value, opt.name, getMaxRepeatCount(opt.repeatable), "option");
|
|
408
|
+
else opts[k] = value;
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
let value = token;
|
|
414
|
+
const def = cmd.positonals?.[argIdx];
|
|
415
|
+
if (def) {
|
|
416
|
+
if (def.type != null) value = coerceType(value, def.type, `argument \`${def.name}\``, getEnumValues(def));
|
|
417
|
+
const k = def.key ?? def.name;
|
|
418
|
+
if (isRepeatable(def.repeatable)) {
|
|
419
|
+
const arr = opts[k] ??= [];
|
|
420
|
+
pushRepeatableValue(arr, value, def.name, getMaxRepeatCount(def.repeatable), "argument");
|
|
421
|
+
for (i = i + 1; i < argv.length; ++i) {
|
|
422
|
+
let v = argv[i];
|
|
423
|
+
if (parseFlags && v === "--") {
|
|
424
|
+
parseFlags = false;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (parseFlags && v.startsWith("-")) {
|
|
428
|
+
i -= 1;
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
if (def.type != null) v = coerceType(v, def.type, `argument \`${def.name}\``, getEnumValues(def));
|
|
432
|
+
pushRepeatableValue(arr, v, def.name, getMaxRepeatCount(def.repeatable), "argument");
|
|
433
|
+
}
|
|
434
|
+
argIdx = isRepeatable(def.repeatable) && cmd.positonals ? cmd.positonals.length : argIdx;
|
|
435
|
+
args.push(...arr);
|
|
436
|
+
continue;
|
|
437
|
+
} else opts[k] = value;
|
|
438
|
+
}
|
|
439
|
+
args.push(value);
|
|
440
|
+
++argIdx;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (allOptions.length) for (const opt of allOptions) {
|
|
444
|
+
const k = opt.key ?? opt.name;
|
|
445
|
+
if (opts[k] === void 0) {
|
|
446
|
+
if (opt.defaultValue !== void 0) opts[k] = resolve(opt.defaultValue);
|
|
447
|
+
else if (opt.required) throw new Error(`\`${getOptName(opt)}\` option is required`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (cmd.positonals?.length) for (let i = 0; i < cmd.positonals.length; ++i) {
|
|
451
|
+
const a = cmd.positonals[i];
|
|
452
|
+
const minRequired = getMinRequiredCount(a.required);
|
|
453
|
+
if (minRequired > 0 && argIdx <= i && !isRepeatable(a.repeatable)) throw new Error(`\`${a.name}\` argument is required`);
|
|
454
|
+
const k = a.key ?? a.name;
|
|
455
|
+
if (isRepeatable(a.repeatable) && (opts[k]?.length ?? 0) < minRequired) throw new Error(`\`${a.name}\` argument requires at least ${minRequired} value${minRequired === 1 ? "" : "s"}`);
|
|
456
|
+
if (k && opts[k] === void 0 && a.defaultValue !== void 0) opts[k] = resolve(a.defaultValue);
|
|
457
|
+
}
|
|
458
|
+
return [args, opts];
|
|
459
|
+
}
|
|
460
|
+
function coerceType(value, type, itemName, enumValues) {
|
|
461
|
+
const normalizedEnumValue = () => {
|
|
462
|
+
const normalized = String(value).trim().toLowerCase();
|
|
463
|
+
const allowedValues = enumValues ?? (Array.isArray(type) ? type : void 0);
|
|
464
|
+
if (allowedValues !== void 0 && !allowedValues.includes(normalized)) {
|
|
465
|
+
const itemText = itemName ? ` for ${itemName}` : "";
|
|
466
|
+
throw new Error(`Invalid value "${value}"${itemText} (expected one of: ${allowedValues.join(", ")})`);
|
|
467
|
+
}
|
|
468
|
+
return normalized;
|
|
469
|
+
};
|
|
470
|
+
if (Array.isArray(type)) return normalizedEnumValue();
|
|
471
|
+
switch (type) {
|
|
472
|
+
case OptType.BOOL: return toBool(value);
|
|
473
|
+
case OptType.INT: return Math.trunc(Number(value));
|
|
474
|
+
case OptType.FLOAT: return Number(value);
|
|
475
|
+
case OptType.ENUM: return normalizedEnumValue();
|
|
476
|
+
case OptType.STRING: return String(value);
|
|
477
|
+
case OptType.INPUT_FILE: {
|
|
478
|
+
if (value === "-") return "/dev/stdin";
|
|
479
|
+
const file = Path.normalize(value);
|
|
480
|
+
const fullPath = Path.resolve(file);
|
|
481
|
+
const stat = statSync(file);
|
|
482
|
+
if (!stat) throw new Error(`File ${createChalk().underline(fullPath)} does not exist`);
|
|
483
|
+
if (!stat.isFile()) throw new Error(`${createChalk().underline(fullPath)} is not a file`);
|
|
484
|
+
try {
|
|
485
|
+
FileSys.accessSync(file, FileSys.constants.R_OK);
|
|
486
|
+
} catch (err) {
|
|
487
|
+
throw new Error(`${createChalk().underline(fullPath)} is not readable`);
|
|
488
|
+
}
|
|
489
|
+
return file;
|
|
490
|
+
}
|
|
491
|
+
case OptType.INPUT_DIRECTORY: {
|
|
492
|
+
const dir = Path.normalize(value);
|
|
493
|
+
FileSys.accessSync(dir, FileSys.constants.X_OK);
|
|
494
|
+
return dir;
|
|
495
|
+
}
|
|
496
|
+
case OptType.OUTPUT_FILE: {
|
|
497
|
+
if (value === "-") return "/dev/stdout";
|
|
498
|
+
const file = Path.normalize(value);
|
|
499
|
+
const stat = statSync(file);
|
|
500
|
+
if (stat) {
|
|
501
|
+
if (!stat.isFile()) throw new Error(`'${file}' is not a file`);
|
|
502
|
+
FileSys.accessSync(file, FileSys.constants.W_OK);
|
|
503
|
+
} else FileSys.accessSync(Path.dirname(file), FileSys.constants.W_OK);
|
|
504
|
+
return file;
|
|
505
|
+
}
|
|
506
|
+
case OptType.OUTPUT_DIRECTORY:
|
|
507
|
+
FileSys.accessSync(value, FileSys.constants.W_OK);
|
|
508
|
+
return Path.normalize(value);
|
|
509
|
+
case OptType.EMPTY_DIRECTORY: {
|
|
510
|
+
const dir = Path.normalize(value);
|
|
511
|
+
let files = [];
|
|
512
|
+
try {
|
|
513
|
+
files = FileSys.readdirSync(dir);
|
|
514
|
+
} catch (err) {
|
|
515
|
+
if (err?.code === "ENOENT") FileSys.accessSync(Path.dirname(dir), FileSys.constants.W_OK);
|
|
516
|
+
else throw err;
|
|
517
|
+
}
|
|
518
|
+
if (files.length) throw new Error(`${createChalk().underline(dir)} is not empty`);
|
|
519
|
+
return dir;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return value;
|
|
523
|
+
}
|
|
524
|
+
function getOptName(opt) {
|
|
525
|
+
return (opt.name.length > 1 ? "--" : "-") + opt.name;
|
|
526
|
+
}
|
|
527
|
+
function findSubCommand(name, subCommands) {
|
|
528
|
+
const cmdName = String(name).trim().replace(/^-{1,2}/, "").toLowerCase();
|
|
529
|
+
return subCommands.find((c) => c.name === cmdName || includes(cmdName, c.alias));
|
|
530
|
+
}
|
|
531
|
+
function getCommand(path, subCommands) {
|
|
532
|
+
if (!path.length) throw new Error("Command path is required.");
|
|
533
|
+
let current = subCommands;
|
|
534
|
+
let command;
|
|
535
|
+
const resolvedPath = [];
|
|
536
|
+
for (let i = 0; i < path.length; ++i) {
|
|
537
|
+
const segment = path[i];
|
|
538
|
+
const next = findSubCommand(segment, current);
|
|
539
|
+
if (next === void 0) throw new Error(`Command "${segment}" does not exist.`);
|
|
540
|
+
command = next;
|
|
541
|
+
resolvedPath.push(next.name);
|
|
542
|
+
if (i < path.length - 1) {
|
|
543
|
+
if (!hasSubCommands(next)) throw new Error(`Command "${resolvedPath.join(" ")}" does not have subCommands.`);
|
|
544
|
+
current = next.subCommands;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
command,
|
|
549
|
+
path: resolvedPath
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region src/global-options.ts
|
|
555
|
+
const HELP_OPTION = {
|
|
556
|
+
name: "help",
|
|
557
|
+
alias: "h",
|
|
558
|
+
description: "Show help text",
|
|
559
|
+
type: OptType.BOOL,
|
|
560
|
+
valueNotRequired: true,
|
|
561
|
+
valueIfSet: true
|
|
562
|
+
};
|
|
563
|
+
const COLOR_OPTION = {
|
|
564
|
+
name: "color",
|
|
565
|
+
description: "Control ANSI color output.",
|
|
566
|
+
type: OptType.ENUM,
|
|
567
|
+
enumValues: [
|
|
568
|
+
"always",
|
|
569
|
+
"never",
|
|
570
|
+
"auto"
|
|
571
|
+
],
|
|
572
|
+
valuePlaceholder: "WHEN",
|
|
573
|
+
valueNotRequired: true,
|
|
574
|
+
valueIfSet: "always",
|
|
575
|
+
noPrefix: true,
|
|
576
|
+
valueIfNoPrefix: "never",
|
|
577
|
+
defaultValue: "auto"
|
|
578
|
+
};
|
|
579
|
+
function getGlobalOptions(app) {
|
|
580
|
+
return sortOptions([
|
|
581
|
+
HELP_OPTION,
|
|
582
|
+
COLOR_OPTION,
|
|
583
|
+
...app._globalOptions ?? []
|
|
584
|
+
]);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
//#endregion
|
|
588
|
+
//#region src/app-help.ts
|
|
589
|
+
function shouldWrapHelpEntry$1(label, description, labelWidth) {
|
|
590
|
+
if (!description) return false;
|
|
591
|
+
const terminalWidth = getTerminalWidth();
|
|
592
|
+
const inlineIndent = labelWidth + 4;
|
|
593
|
+
const inlineDescriptionLines = wrapText(description, Math.max(terminalWidth - inlineIndent, 1));
|
|
594
|
+
return description.includes("\n") || inlineDescriptionLines.length > 1 || labelWidth + 4 + stringWidth(description) > terminalWidth;
|
|
595
|
+
}
|
|
596
|
+
function printHelpEntry$1(label, description, labelWidth, forceWrap = false) {
|
|
597
|
+
print(` ${label}`);
|
|
598
|
+
if (!description) {
|
|
599
|
+
printLn();
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
if (!(forceWrap || shouldWrapHelpEntry$1(label, description, labelWidth))) {
|
|
603
|
+
printLn(`${space(labelWidth + 2, label)}${description}`);
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
printLn();
|
|
607
|
+
const terminalWidth = getTerminalWidth();
|
|
608
|
+
const descriptionIndent = " ".repeat(10);
|
|
609
|
+
const wrappedDescription = wrapText(description, Math.max(terminalWidth - descriptionIndent.length, 1));
|
|
610
|
+
for (const line of wrappedDescription) printLn(line.length ? `${descriptionIndent}${line}` : "");
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
function printOptionEntries$1(entries) {
|
|
614
|
+
const width = Math.max(...entries.map((line) => stringWidth(line[0])));
|
|
615
|
+
const forceWrap = entries.some(([label, description]) => shouldWrapHelpEntry$1(label, description, width));
|
|
616
|
+
entries.forEach(([label, description], index) => {
|
|
617
|
+
if (printHelpEntry$1(label, description, width, forceWrap) && index < entries.length - 1) printLn();
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
function printHelp(context, commands) {
|
|
621
|
+
const app = context.app;
|
|
622
|
+
const chalk = context.chalk;
|
|
623
|
+
print(chalk.green(app.name));
|
|
624
|
+
const { _author: author, _version: version } = app;
|
|
625
|
+
if (version) print(` ver. ${chalk.yellow(version)}`);
|
|
626
|
+
if (author) print(` by ${chalk.cyan(author)}`);
|
|
627
|
+
printLn();
|
|
628
|
+
if (app.description) {
|
|
629
|
+
printLn();
|
|
630
|
+
printLn(app.description);
|
|
631
|
+
}
|
|
632
|
+
printLn();
|
|
633
|
+
printLn(chalk.yellow("Usage:"));
|
|
634
|
+
print(` ${chalk.cyan(getProcName(app))}`);
|
|
635
|
+
if (hasSubCommands(app)) print(` ${chalk.gray("[")}--GLOBAL-OPTIONS${chalk.gray("]")} ${chalk.gray("<")}COMMAND${chalk.gray(">")}`);
|
|
636
|
+
printLn("\n");
|
|
637
|
+
if (commands.length) printAvailableCommands(commands, "Commands:", chalk);
|
|
638
|
+
const globalOptions = getGlobalOptions(app);
|
|
639
|
+
if (globalOptions.length) {
|
|
640
|
+
printLn();
|
|
641
|
+
printLn(chalk.yellow("Global Options:"));
|
|
642
|
+
printOptionEntries$1(globalOptions.map((option) => formatOption(option, chalk)));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function printAvailableCommands(commands, title, chalk) {
|
|
646
|
+
if (!commands.length) return;
|
|
647
|
+
printLn(chalk.yellow(title));
|
|
648
|
+
const width = Math.max(...commands.map((c) => stringWidth(c.name))) + 2;
|
|
649
|
+
for (const cmd of commands) printHelpEntry$1(chalk.green(cmd.name), cmd.description, width);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
//#endregion
|
|
653
|
+
//#region src/print-command-help.ts
|
|
654
|
+
function shouldWrapHelpEntry(label, description, labelWidth) {
|
|
655
|
+
if (!description) return false;
|
|
656
|
+
const terminalWidth = getTerminalWidth();
|
|
657
|
+
const inlineIndent = labelWidth + 4;
|
|
658
|
+
const inlineDescriptionLines = wrapText(description, Math.max(terminalWidth - inlineIndent, 1));
|
|
659
|
+
return description.includes("\n") || inlineDescriptionLines.length > 1 || labelWidth + 4 + stringWidth(description) > terminalWidth;
|
|
660
|
+
}
|
|
661
|
+
function printHelpEntry(label, description, labelWidth, forceWrap = false) {
|
|
662
|
+
print(` ${label}`);
|
|
663
|
+
if (!description) {
|
|
664
|
+
printLn();
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
if (!(forceWrap || shouldWrapHelpEntry(label, description, labelWidth))) {
|
|
668
|
+
printLn(`${space(labelWidth + 2, label)}${description}`);
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
printLn();
|
|
672
|
+
const terminalWidth = getTerminalWidth();
|
|
673
|
+
const descriptionIndent = " ".repeat(10);
|
|
674
|
+
const wrappedDescription = wrapText(description, Math.max(terminalWidth - descriptionIndent.length, 1));
|
|
675
|
+
for (const line of wrappedDescription) printLn(line.length ? `${descriptionIndent}${line}` : "");
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
function printOptionEntries(entries) {
|
|
679
|
+
const width = Math.max(...entries.map((line) => stringWidth(line[0])));
|
|
680
|
+
const forceWrap = entries.some(([label, description]) => shouldWrapHelpEntry(label, description, width));
|
|
681
|
+
entries.forEach(([label, description], index) => {
|
|
682
|
+
if (printHelpEntry(label, description, width, forceWrap) && index < entries.length - 1) printLn();
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
function getCommandLabel(context, path) {
|
|
686
|
+
const proc = context.chalk.cyan(getProcName(context.app));
|
|
687
|
+
if (!path.length) return proc;
|
|
688
|
+
return `${proc} ${path.join(" ")}`;
|
|
689
|
+
}
|
|
690
|
+
function formatUsageOption(opt, chalk) {
|
|
691
|
+
const optionName = chalk.green(getOptName(opt));
|
|
692
|
+
if (opt.type === OptType.BOOL) return optionName;
|
|
693
|
+
const valuePlaceholder = chalk.magenta(getValuePlaceholder(opt));
|
|
694
|
+
if (opt.valueNotRequired) return `${optionName}${chalk.grey("[")}=${valuePlaceholder}${chalk.grey("]")}`;
|
|
695
|
+
return `${optionName}=${valuePlaceholder}`;
|
|
696
|
+
}
|
|
697
|
+
function formatUsageArgument(arg, chalk) {
|
|
698
|
+
const argumentName = chalk.magenta(arg.repeatable ? `${arg.name}...` : arg.name);
|
|
699
|
+
return `${chalk.grey(arg.required ? "<" : "[")}${argumentName}${chalk.grey(arg.required ? ">" : "]")}`;
|
|
700
|
+
}
|
|
701
|
+
function printCommandHelp(context, cmd, path = []) {
|
|
702
|
+
const app = context.app;
|
|
703
|
+
const chalk = context.chalk;
|
|
704
|
+
if (cmd.description) {
|
|
705
|
+
printLn(cmd.description);
|
|
706
|
+
printLn();
|
|
707
|
+
}
|
|
708
|
+
if (cmd.longDescription) {
|
|
709
|
+
printLn(cmd.longDescription);
|
|
710
|
+
printLn();
|
|
711
|
+
}
|
|
712
|
+
if (cmd === app) {
|
|
713
|
+
const author = app._author;
|
|
714
|
+
if (author) {
|
|
715
|
+
printLn(`Author: ${author}`);
|
|
716
|
+
printLn();
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
printLn(chalk.yellow("Usage:"));
|
|
720
|
+
print(` ${getCommandLabel(context, path)}`);
|
|
721
|
+
if (hasSubCommands(cmd)) print(` ${chalk.gray("<")}command${chalk.gray(">")}`);
|
|
722
|
+
if (isExecutable(cmd)) {
|
|
723
|
+
const allOptions = getOptions(cmd);
|
|
724
|
+
if (allOptions.length) {
|
|
725
|
+
let otherOptions = 0;
|
|
726
|
+
for (const opt of allOptions) if (opt.required) print(` ${formatUsageOption(opt, chalk)}`);
|
|
727
|
+
else ++otherOptions;
|
|
728
|
+
if (otherOptions) print(` ${chalk.gray("[")}${chalk.magenta("--options")}${chalk.gray("]")}`);
|
|
729
|
+
}
|
|
730
|
+
if (cmd.positonals?.length) {
|
|
731
|
+
print(` ${chalk.grey("[")}--${chalk.grey("]")}`);
|
|
732
|
+
for (const arg of cmd.positonals) print(` ${formatUsageArgument(arg, chalk)}`);
|
|
733
|
+
}
|
|
734
|
+
} else if (!hasSubCommands(cmd)) print(` ${chalk.gray("[options] [arguments]")}`);
|
|
735
|
+
printLn();
|
|
736
|
+
if (isExecutable(cmd)) {
|
|
737
|
+
const allOptions = getOptions(cmd);
|
|
738
|
+
if (allOptions.length) {
|
|
739
|
+
printLn(chalk.yellow("\nOptions:"));
|
|
740
|
+
printOptionEntries(allOptions.map((option) => formatOption(option, chalk)));
|
|
741
|
+
}
|
|
742
|
+
if (cmd.positonals?.length) {
|
|
743
|
+
printLn(chalk.yellow("\nArguments:"));
|
|
744
|
+
const width = Math.max(...cmd.positonals.map((arg) => stringWidth(arg.name)));
|
|
745
|
+
for (const arg of cmd.positonals) printHelpEntry(chalk.green(arg.name), arg.description, width);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (hasSubCommands(cmd)) {
|
|
749
|
+
printLn();
|
|
750
|
+
printAvailableCommands(cmd.subCommands, "Sub-commands:", chalk);
|
|
751
|
+
}
|
|
752
|
+
const globalOptions = getGlobalOptions(app);
|
|
753
|
+
if (globalOptions.length) {
|
|
754
|
+
printLn();
|
|
755
|
+
printLn(chalk.yellow("Global Options:"));
|
|
756
|
+
printOptionEntries(globalOptions.map((option) => formatOption(option, chalk)));
|
|
757
|
+
}
|
|
758
|
+
if (cmd.alias) {
|
|
759
|
+
const aliases = toArray(cmd.alias);
|
|
760
|
+
printLn(chalk.yellow(`\nAlias${aliases.length !== 1 ? "es" : ""}: `) + aliases.join(chalk.gray(", ")));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
//#endregion
|
|
765
|
+
//#region src/commands/command-help.ts
|
|
766
|
+
const helpCommand = new Command("help").describe("Displays help for a command").arg("command", {
|
|
767
|
+
description: "The command path.",
|
|
768
|
+
repeatable: true
|
|
769
|
+
}).run(async (commandPath, _, context) => {
|
|
770
|
+
const app = context.app;
|
|
771
|
+
const rootCommands = [
|
|
772
|
+
...app.subCommands !== void 0 ? sortBy(app.subCommands, (c) => c.name) : [],
|
|
773
|
+
versionCommand,
|
|
774
|
+
helpCommand
|
|
775
|
+
];
|
|
776
|
+
if (commandPath.length) {
|
|
777
|
+
const { command, path } = getCommand(commandPath, rootCommands);
|
|
778
|
+
printCommandHelp(context, command, path);
|
|
779
|
+
} else if (app.subCommands !== void 0) printHelp(context, rootCommands);
|
|
780
|
+
else printCommandHelp(context, app, []);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
//#endregion
|
|
784
|
+
//#region src/run.ts
|
|
785
|
+
function normalizeAppAsLeafCommand(app) {
|
|
786
|
+
const handler = app.handler;
|
|
787
|
+
if (handler === void 0) throw new Error("Command is not executable.");
|
|
788
|
+
return {
|
|
789
|
+
name: app.name,
|
|
790
|
+
...app.alias !== void 0 ? { alias: app.alias } : {},
|
|
791
|
+
...app.description !== void 0 ? { description: app.description } : {},
|
|
792
|
+
...app.longDescription !== void 0 ? { longDescription: app.longDescription } : {},
|
|
793
|
+
...app.options !== void 0 ? { options: [...app.options] } : {},
|
|
794
|
+
...app.positonals !== void 0 ? { positonals: [...app.positonals] } : {},
|
|
795
|
+
execute: handler
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
function normalizeLeafCommand(cmd) {
|
|
799
|
+
const handler = getExecuteHandler(cmd);
|
|
800
|
+
if (handler === void 0) throw new Error("Command is not executable.");
|
|
801
|
+
return {
|
|
802
|
+
name: cmd.name,
|
|
803
|
+
...cmd.alias !== void 0 ? { alias: cmd.alias } : {},
|
|
804
|
+
...cmd.description !== void 0 ? { description: cmd.description } : {},
|
|
805
|
+
...cmd.longDescription !== void 0 ? { longDescription: cmd.longDescription } : {},
|
|
806
|
+
...cmd.options !== void 0 ? { options: [...cmd.options] } : {},
|
|
807
|
+
...cmd.positonals !== void 0 ? { positonals: [...cmd.positonals] } : {},
|
|
808
|
+
execute: handler
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function formatConfigError(error) {
|
|
812
|
+
return `Config Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
813
|
+
}
|
|
814
|
+
function mergeCommandOptions(app, cmd) {
|
|
815
|
+
const normalized = normalizeLeafCommand(cmd);
|
|
816
|
+
return {
|
|
817
|
+
...normalized,
|
|
818
|
+
options: [...getGlobalOptions(app), ...normalized.options ?? []]
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
function isVersionFlag(arg) {
|
|
822
|
+
return arg === "--version";
|
|
823
|
+
}
|
|
824
|
+
function getHelpValidationCommand(cmd) {
|
|
825
|
+
return {
|
|
826
|
+
...cmd,
|
|
827
|
+
options: cmd.options?.map((opt) => ({
|
|
828
|
+
...opt,
|
|
829
|
+
required: false
|
|
830
|
+
})),
|
|
831
|
+
positonals: cmd.positonals?.map((arg) => ({
|
|
832
|
+
...arg,
|
|
833
|
+
required: false
|
|
834
|
+
}))
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function getRootCommands(app) {
|
|
838
|
+
return [
|
|
839
|
+
...app.subCommands !== void 0 ? sortBy(app.subCommands, (c) => c.name) : [],
|
|
840
|
+
versionCommand,
|
|
841
|
+
helpCommand
|
|
842
|
+
];
|
|
843
|
+
}
|
|
844
|
+
function createExecutionContext(app, opts, path = []) {
|
|
845
|
+
return new ExecutionContext(app, opts?.color ?? "auto", path);
|
|
846
|
+
}
|
|
847
|
+
function createGlobalParseCommand(app) {
|
|
848
|
+
return {
|
|
849
|
+
name: app.name,
|
|
850
|
+
options: getGlobalOptions(app),
|
|
851
|
+
positonals: [{
|
|
852
|
+
name: "argv",
|
|
853
|
+
key: "argv",
|
|
854
|
+
repeatable: true
|
|
855
|
+
}],
|
|
856
|
+
execute() {}
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
function parseGlobalOptions(app, argv) {
|
|
860
|
+
try {
|
|
861
|
+
const [, opts] = parseArgs(getHelpValidationCommand(createGlobalParseCommand(app)), argv);
|
|
862
|
+
return { opts };
|
|
863
|
+
} catch (err) {
|
|
864
|
+
if (err instanceof UnknownOptionError) return {};
|
|
865
|
+
const error = createError(err instanceof Error ? err.message : String(err), ErrorCategory.InvalidArg);
|
|
866
|
+
return { result: {
|
|
867
|
+
code: getErrorExitCode(error),
|
|
868
|
+
error
|
|
869
|
+
} };
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function getOptionTokenConsumption(argv, index, rawOptions) {
|
|
873
|
+
const token = argv[index];
|
|
874
|
+
if (token === "--") return argv.length - index;
|
|
875
|
+
if (!token.startsWith("-") || token === "-") return 0;
|
|
876
|
+
const options = rawOptions;
|
|
877
|
+
const matchLong = (name) => options.find((opt) => opt.name === name || opt.noPrefix && `no-${opt.name}` === name || opt.alias !== void 0 && (Array.isArray(opt.alias) ? opt.alias.includes(name) : opt.alias === name));
|
|
878
|
+
if (token.startsWith("--")) {
|
|
879
|
+
const [left] = token.split("=", 1);
|
|
880
|
+
const option = matchLong(left.slice(2));
|
|
881
|
+
if (option === void 0) return 0;
|
|
882
|
+
if (left !== token || option.valueNotRequired) return 1;
|
|
883
|
+
return index < argv.length - 1 ? 2 : 1;
|
|
884
|
+
}
|
|
885
|
+
const clusterText = token.slice(1);
|
|
886
|
+
const equalIndex = clusterText.indexOf("=");
|
|
887
|
+
const cluster = equalIndex === -1 ? clusterText : clusterText.slice(0, equalIndex);
|
|
888
|
+
for (let i = 0; i < cluster.length; ++i) {
|
|
889
|
+
const option = options.find((opt) => opt.name === cluster[i] || (Array.isArray(opt.alias) ? opt.alias.includes(cluster[i]) : opt.alias === cluster[i]));
|
|
890
|
+
if (option === void 0) return 0;
|
|
891
|
+
if (!option.valueNotRequired) {
|
|
892
|
+
if (i < cluster.length - 1 || equalIndex !== -1) return 1;
|
|
893
|
+
return index < argv.length - 1 ? 2 : 1;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return 1;
|
|
897
|
+
}
|
|
898
|
+
function resolveCommandWithGlobalOptions(argv, subCommands, globalOptions) {
|
|
899
|
+
const path = [];
|
|
900
|
+
const commandIndexes = /* @__PURE__ */ new Set();
|
|
901
|
+
let command;
|
|
902
|
+
let current = subCommands;
|
|
903
|
+
let index = 0;
|
|
904
|
+
while (index < argv.length) {
|
|
905
|
+
const token = argv[index];
|
|
906
|
+
const consumed = getOptionTokenConsumption(argv, index, globalOptions);
|
|
907
|
+
if (consumed > 0) {
|
|
908
|
+
index += consumed;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
const candidate = findSubCommand(token, current);
|
|
912
|
+
if (candidate === void 0) break;
|
|
913
|
+
command = candidate;
|
|
914
|
+
commandIndexes.add(index);
|
|
915
|
+
path.push(candidate.name);
|
|
916
|
+
index += 1;
|
|
917
|
+
if (!hasSubCommands(candidate)) break;
|
|
918
|
+
current = candidate.subCommands;
|
|
919
|
+
}
|
|
920
|
+
return {
|
|
921
|
+
command,
|
|
922
|
+
path,
|
|
923
|
+
remainingArgv: argv.filter((_, argvIndex) => !commandIndexes.has(argvIndex))
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
function getFirstNonGlobalToken(argv, globalOptions) {
|
|
927
|
+
for (let index = 0; index < argv.length;) {
|
|
928
|
+
const consumed = getOptionTokenConsumption(argv, index, globalOptions);
|
|
929
|
+
if (consumed > 0) {
|
|
930
|
+
index += consumed;
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
return argv[index];
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
function unknownCommandResult(app, commandName) {
|
|
937
|
+
const error = createError(`${getProcName(app)}: unknown command '${commandName}'`, ErrorCategory.InvalidArg);
|
|
938
|
+
return {
|
|
939
|
+
code: getErrorExitCode(error),
|
|
940
|
+
error
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
async function executeLeaf(app, cmd, rawArgs, path) {
|
|
944
|
+
try {
|
|
945
|
+
validateCommandConfig(mergeCommandOptions(app, cmd));
|
|
946
|
+
} catch (err) {
|
|
947
|
+
const error = createError(formatConfigError(err), ErrorCategory.Misconfig);
|
|
948
|
+
return {
|
|
949
|
+
result: {
|
|
950
|
+
code: getErrorExitCode(error),
|
|
951
|
+
error
|
|
952
|
+
},
|
|
953
|
+
context: createExecutionContext(app, void 0, path)
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
let args;
|
|
957
|
+
let opts;
|
|
958
|
+
try {
|
|
959
|
+
const parseableCommand = mergeCommandOptions(app, cmd);
|
|
960
|
+
const [, provisionalOpts] = parseArgs(getHelpValidationCommand(parseableCommand), rawArgs);
|
|
961
|
+
const provisionalContext = createExecutionContext(app, provisionalOpts, path);
|
|
962
|
+
if (provisionalOpts.help) {
|
|
963
|
+
if (cmd === app && !hasSubCommands(app)) {
|
|
964
|
+
printHelp(provisionalContext, getRootCommands(app));
|
|
965
|
+
return {
|
|
966
|
+
result: { code: 0 },
|
|
967
|
+
context: provisionalContext
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
printCommandHelp(provisionalContext, cmd, path);
|
|
971
|
+
return {
|
|
972
|
+
result: { code: 0 },
|
|
973
|
+
context: provisionalContext
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
[args, opts] = parseArgs(parseableCommand, rawArgs);
|
|
977
|
+
} catch (err) {
|
|
978
|
+
if (err instanceof UnknownOptionError) {
|
|
979
|
+
const error$1 = createError(`${getProcName(app)}: ${err.message}`, ErrorCategory.InvalidArg);
|
|
980
|
+
return {
|
|
981
|
+
result: {
|
|
982
|
+
code: getErrorExitCode(error$1),
|
|
983
|
+
error: error$1
|
|
984
|
+
},
|
|
985
|
+
context: createExecutionContext(app, void 0, path)
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
const error = createError(err instanceof Error ? err.message : String(err), ErrorCategory.InvalidArg);
|
|
989
|
+
return {
|
|
990
|
+
result: {
|
|
991
|
+
code: getErrorExitCode(error),
|
|
992
|
+
error
|
|
993
|
+
},
|
|
994
|
+
context: createExecutionContext(app, void 0, path)
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
const handler = getExecuteHandler(cmd);
|
|
998
|
+
if (handler === void 0) {
|
|
999
|
+
const error = createError("Command is not executable.", ErrorCategory.Internal);
|
|
1000
|
+
return {
|
|
1001
|
+
result: {
|
|
1002
|
+
code: getErrorExitCode(error),
|
|
1003
|
+
error
|
|
1004
|
+
},
|
|
1005
|
+
context: createExecutionContext(app, opts, path)
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
try {
|
|
1009
|
+
const context = createExecutionContext(app, opts, path);
|
|
1010
|
+
const code = await Promise.resolve(handler(opts, args, context));
|
|
1011
|
+
if (code === void 0) return {
|
|
1012
|
+
result: { code: null },
|
|
1013
|
+
context
|
|
1014
|
+
};
|
|
1015
|
+
return {
|
|
1016
|
+
result: { code },
|
|
1017
|
+
context
|
|
1018
|
+
};
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
const error = createError(String(err?.stack ?? err), ErrorCategory.Internal);
|
|
1021
|
+
return {
|
|
1022
|
+
result: {
|
|
1023
|
+
code: getErrorExitCode(error),
|
|
1024
|
+
error
|
|
1025
|
+
},
|
|
1026
|
+
context: createExecutionContext(app, opts, path)
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
async function executeAppDetailed(app, argv = process.argv.slice(2)) {
|
|
1031
|
+
const rootCommands = getRootCommands(app);
|
|
1032
|
+
const globalOptions = getGlobalOptions(app);
|
|
1033
|
+
const rootContext = createExecutionContext(app);
|
|
1034
|
+
if (argv.length === 0) {
|
|
1035
|
+
if (isExecutable(app)) return executeLeaf(app, normalizeAppAsLeafCommand(app), [], []);
|
|
1036
|
+
printHelp(rootContext, rootCommands);
|
|
1037
|
+
return {
|
|
1038
|
+
result: { code: 0 },
|
|
1039
|
+
context: rootContext
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
if (isExecutable(app) && !hasSubCommands(app)) {
|
|
1043
|
+
if (isVersionFlag(argv[0])) {
|
|
1044
|
+
printLn(app._version);
|
|
1045
|
+
return {
|
|
1046
|
+
result: { code: 0 },
|
|
1047
|
+
context: rootContext
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
const globalParse = parseGlobalOptions(app, argv);
|
|
1051
|
+
if (globalParse.result !== void 0) return {
|
|
1052
|
+
result: globalParse.result,
|
|
1053
|
+
context: rootContext
|
|
1054
|
+
};
|
|
1055
|
+
if (globalParse.opts?.help) return executeLeaf(app, normalizeAppAsLeafCommand(app), argv, []);
|
|
1056
|
+
const builtin = findSubCommand(argv[0], rootCommands);
|
|
1057
|
+
if (builtin && isExecutable(builtin)) return executeLeaf(app, builtin, argv.slice(1), [builtin.name]);
|
|
1058
|
+
return executeLeaf(app, normalizeAppAsLeafCommand(app), argv, []);
|
|
1059
|
+
}
|
|
1060
|
+
const resolved = resolveCommandWithGlobalOptions(argv, rootCommands, globalOptions);
|
|
1061
|
+
if (!resolved.command) {
|
|
1062
|
+
const globalParse = parseGlobalOptions(app, argv);
|
|
1063
|
+
if (globalParse.result !== void 0) return {
|
|
1064
|
+
result: globalParse.result,
|
|
1065
|
+
context: rootContext
|
|
1066
|
+
};
|
|
1067
|
+
const globalOpts = globalParse.opts ?? {};
|
|
1068
|
+
const context = createExecutionContext(app, globalOpts);
|
|
1069
|
+
const firstNonGlobalToken = getFirstNonGlobalToken(argv, globalOptions);
|
|
1070
|
+
if (firstNonGlobalToken === void 0) {
|
|
1071
|
+
if (globalOpts.help || !hasSubCommands(app)) {
|
|
1072
|
+
if (isExecutable(app)) return executeLeaf(app, normalizeAppAsLeafCommand(app), argv, []);
|
|
1073
|
+
printHelp(context, rootCommands);
|
|
1074
|
+
return {
|
|
1075
|
+
result: { code: 0 },
|
|
1076
|
+
context
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
printHelp(context, rootCommands);
|
|
1080
|
+
return {
|
|
1081
|
+
result: { code: 0 },
|
|
1082
|
+
context
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
return {
|
|
1086
|
+
result: unknownCommandResult(app, firstNonGlobalToken),
|
|
1087
|
+
context
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
if (hasSubCommands(resolved.command)) {
|
|
1091
|
+
const globalParse = parseGlobalOptions(app, resolved.remainingArgv);
|
|
1092
|
+
if (globalParse.result !== void 0) return {
|
|
1093
|
+
result: globalParse.result,
|
|
1094
|
+
context: rootContext
|
|
1095
|
+
};
|
|
1096
|
+
const branchOpts = globalParse.opts ?? {};
|
|
1097
|
+
const context = createExecutionContext(app, branchOpts, resolved.path);
|
|
1098
|
+
const firstNonGlobalToken = getFirstNonGlobalToken(resolved.remainingArgv, globalOptions);
|
|
1099
|
+
if (!resolved.remainingArgv.length || branchOpts.help && firstNonGlobalToken === void 0) {
|
|
1100
|
+
printCommandHelp(context, resolved.command, resolved.path);
|
|
1101
|
+
return {
|
|
1102
|
+
result: { code: 0 },
|
|
1103
|
+
context
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
return {
|
|
1107
|
+
result: unknownCommandResult(app, firstNonGlobalToken ?? resolved.remainingArgv[0]),
|
|
1108
|
+
context
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
if (!isExecutable(resolved.command)) {
|
|
1112
|
+
const error = createError("Command is not executable.", ErrorCategory.Internal);
|
|
1113
|
+
return {
|
|
1114
|
+
result: {
|
|
1115
|
+
code: getErrorExitCode(error),
|
|
1116
|
+
error
|
|
1117
|
+
},
|
|
1118
|
+
context: createExecutionContext(app, void 0, resolved.path)
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
return executeLeaf(app, resolved.command, resolved.remainingArgv, resolved.path);
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Parses CLI arguments, prints any resulting error, and executes the matching command.
|
|
1125
|
+
*
|
|
1126
|
+
* @param app The app to execute.
|
|
1127
|
+
* @param argv The raw CLI arguments to parse. Defaults to `process.argv.slice(2)`.
|
|
1128
|
+
* @returns The process exit code for the CLI invocation.
|
|
1129
|
+
*/
|
|
1130
|
+
async function executeApp(app, argv = process.argv.slice(2)) {
|
|
1131
|
+
const { result, context } = await executeAppDetailed(app, argv);
|
|
1132
|
+
if (result.error !== void 0) printError(result.error, context.chalk);
|
|
1133
|
+
return result.code ?? 0;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
//#endregion
|
|
1137
|
+
export { executeApp };
|