@gunshi/bone 0.26.3
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/LICENSE +20 -0
- package/README.md +67 -0
- package/lib/index.d.ts +699 -0
- package/lib/index.js +847 -0
- package/package.json +69 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
//#region ../../node_modules/.pnpm/args-tokens@0.20.1/node_modules/args-tokens/lib/parser-Dr4iAGaX.js
|
|
2
|
+
const HYPHEN_CHAR = "-";
|
|
3
|
+
const HYPHEN_CODE = HYPHEN_CHAR.codePointAt(0);
|
|
4
|
+
const EQUAL_CHAR = "=";
|
|
5
|
+
const EQUAL_CODE = EQUAL_CHAR.codePointAt(0);
|
|
6
|
+
const TERMINATOR = "--";
|
|
7
|
+
const SHORT_OPTION_PREFIX = HYPHEN_CHAR;
|
|
8
|
+
const LONG_OPTION_PREFIX = "--";
|
|
9
|
+
/**
|
|
10
|
+
* Parse command line arguments.
|
|
11
|
+
* @example
|
|
12
|
+
* ```js
|
|
13
|
+
* import { parseArgs } from 'args-tokens' // for Node.js and Bun
|
|
14
|
+
* // import { parseArgs } from 'jsr:@kazupon/args-tokens' // for Deno
|
|
15
|
+
*
|
|
16
|
+
* const tokens = parseArgs(['--foo', 'bar', '-x', '--bar=baz'])
|
|
17
|
+
* // do something with using tokens
|
|
18
|
+
* // ...
|
|
19
|
+
* console.log('tokens:', tokens)
|
|
20
|
+
* ```
|
|
21
|
+
* @param args command line arguments
|
|
22
|
+
* @param options parse options
|
|
23
|
+
* @returns Argument tokens.
|
|
24
|
+
*/
|
|
25
|
+
function parseArgs(args, options = {}) {
|
|
26
|
+
const { allowCompatible = false } = options;
|
|
27
|
+
const tokens = [];
|
|
28
|
+
const remainings = [...args];
|
|
29
|
+
let index = -1;
|
|
30
|
+
let groupCount = 0;
|
|
31
|
+
let hasShortValueSeparator = false;
|
|
32
|
+
while (remainings.length > 0) {
|
|
33
|
+
const arg = remainings.shift();
|
|
34
|
+
if (arg == void 0) break;
|
|
35
|
+
const nextArg = remainings[0];
|
|
36
|
+
if (groupCount > 0) groupCount--;
|
|
37
|
+
else index++;
|
|
38
|
+
if (arg === TERMINATOR) {
|
|
39
|
+
tokens.push({
|
|
40
|
+
kind: "option-terminator",
|
|
41
|
+
index
|
|
42
|
+
});
|
|
43
|
+
const mapped = remainings.map((arg$1) => {
|
|
44
|
+
return {
|
|
45
|
+
kind: "positional",
|
|
46
|
+
index: ++index,
|
|
47
|
+
value: arg$1
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
tokens.push(...mapped);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
if (isShortOption(arg)) {
|
|
54
|
+
const shortOption = arg.charAt(1);
|
|
55
|
+
let value;
|
|
56
|
+
let inlineValue;
|
|
57
|
+
if (groupCount) {
|
|
58
|
+
tokens.push({
|
|
59
|
+
kind: "option",
|
|
60
|
+
name: shortOption,
|
|
61
|
+
rawName: arg,
|
|
62
|
+
index,
|
|
63
|
+
value,
|
|
64
|
+
inlineValue
|
|
65
|
+
});
|
|
66
|
+
if (groupCount === 1 && hasOptionValue(nextArg)) {
|
|
67
|
+
value = remainings.shift();
|
|
68
|
+
if (hasShortValueSeparator) {
|
|
69
|
+
inlineValue = true;
|
|
70
|
+
hasShortValueSeparator = false;
|
|
71
|
+
}
|
|
72
|
+
tokens.push({
|
|
73
|
+
kind: "option",
|
|
74
|
+
index,
|
|
75
|
+
value,
|
|
76
|
+
inlineValue
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
} else tokens.push({
|
|
80
|
+
kind: "option",
|
|
81
|
+
name: shortOption,
|
|
82
|
+
rawName: arg,
|
|
83
|
+
index,
|
|
84
|
+
value,
|
|
85
|
+
inlineValue
|
|
86
|
+
});
|
|
87
|
+
if (value != null) ++index;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (isShortOptionGroup(arg)) {
|
|
91
|
+
const expanded = [];
|
|
92
|
+
let shortValue = "";
|
|
93
|
+
for (let i = 1; i < arg.length; i++) {
|
|
94
|
+
const shortableOption = arg.charAt(i);
|
|
95
|
+
if (hasShortValueSeparator) shortValue += shortableOption;
|
|
96
|
+
else if (!allowCompatible && shortableOption.codePointAt(0) === EQUAL_CODE) hasShortValueSeparator = true;
|
|
97
|
+
else expanded.push(`${SHORT_OPTION_PREFIX}${shortableOption}`);
|
|
98
|
+
}
|
|
99
|
+
if (shortValue) expanded.push(shortValue);
|
|
100
|
+
remainings.unshift(...expanded);
|
|
101
|
+
groupCount = expanded.length;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (isLongOption(arg)) {
|
|
105
|
+
const longOption = arg.slice(2);
|
|
106
|
+
tokens.push({
|
|
107
|
+
kind: "option",
|
|
108
|
+
name: longOption,
|
|
109
|
+
rawName: arg,
|
|
110
|
+
index,
|
|
111
|
+
value: void 0,
|
|
112
|
+
inlineValue: void 0
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (isLongOptionAndValue(arg)) {
|
|
117
|
+
const equalIndex = arg.indexOf(EQUAL_CHAR);
|
|
118
|
+
const longOption = arg.slice(2, equalIndex);
|
|
119
|
+
const value = arg.slice(equalIndex + 1);
|
|
120
|
+
tokens.push({
|
|
121
|
+
kind: "option",
|
|
122
|
+
name: longOption,
|
|
123
|
+
rawName: `${LONG_OPTION_PREFIX}${longOption}`,
|
|
124
|
+
index,
|
|
125
|
+
value,
|
|
126
|
+
inlineValue: true
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
tokens.push({
|
|
131
|
+
kind: "positional",
|
|
132
|
+
index,
|
|
133
|
+
value: arg
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return tokens;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if `arg` is a short option (e.g. `-f`).
|
|
140
|
+
* @param arg the argument to check
|
|
141
|
+
* @returns whether `arg` is a short option.
|
|
142
|
+
*/
|
|
143
|
+
function isShortOption(arg) {
|
|
144
|
+
return arg.length === 2 && arg.codePointAt(0) === HYPHEN_CODE && arg.codePointAt(1) !== HYPHEN_CODE;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Check if `arg` is a short option group (e.g. `-abc`).
|
|
148
|
+
* @param arg the argument to check
|
|
149
|
+
* @returns whether `arg` is a short option group.
|
|
150
|
+
*/
|
|
151
|
+
function isShortOptionGroup(arg) {
|
|
152
|
+
if (arg.length <= 2) return false;
|
|
153
|
+
if (arg.codePointAt(0) !== HYPHEN_CODE) return false;
|
|
154
|
+
if (arg.codePointAt(1) === HYPHEN_CODE) return false;
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Check if `arg` is a long option (e.g. `--foo`).
|
|
159
|
+
* @param arg the argument to check
|
|
160
|
+
* @returns whether `arg` is a long option.
|
|
161
|
+
*/
|
|
162
|
+
function isLongOption(arg) {
|
|
163
|
+
return hasLongOptionPrefix(arg) && !arg.includes(EQUAL_CHAR, 3);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Check if `arg` is a long option with value (e.g. `--foo=bar`).
|
|
167
|
+
* @param arg the argument to check
|
|
168
|
+
* @returns whether `arg` is a long option.
|
|
169
|
+
*/
|
|
170
|
+
function isLongOptionAndValue(arg) {
|
|
171
|
+
return hasLongOptionPrefix(arg) && arg.includes(EQUAL_CHAR, 3);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if `arg` is a long option prefix (e.g. `--`).
|
|
175
|
+
* @param arg the argument to check
|
|
176
|
+
* @returns whether `arg` is a long option prefix.
|
|
177
|
+
*/
|
|
178
|
+
function hasLongOptionPrefix(arg) {
|
|
179
|
+
return arg.length > 2 && ~arg.indexOf(LONG_OPTION_PREFIX);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Check if a `value` is an option value.
|
|
183
|
+
* @param value a value to check
|
|
184
|
+
* @returns whether a `value` is an option value.
|
|
185
|
+
*/
|
|
186
|
+
function hasOptionValue(value) {
|
|
187
|
+
return !(value == null) && value.codePointAt(0) !== HYPHEN_CODE;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region ../../node_modules/.pnpm/args-tokens@0.20.1/node_modules/args-tokens/lib/utils-N7UlhLbz.js
|
|
192
|
+
/**
|
|
193
|
+
* Entry point of utils.
|
|
194
|
+
*
|
|
195
|
+
* Note that this entry point is used by gunshi to import utility functions.
|
|
196
|
+
*
|
|
197
|
+
* @module
|
|
198
|
+
*/
|
|
199
|
+
/**
|
|
200
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
201
|
+
* @license MIT
|
|
202
|
+
*/
|
|
203
|
+
function kebabnize(str) {
|
|
204
|
+
return str.replace(/[A-Z]/g, (match, offset) => (offset > 0 ? "-" : "") + match.toLowerCase());
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region ../../node_modules/.pnpm/args-tokens@0.20.1/node_modules/args-tokens/lib/resolver-Q4k2fgTW.js
|
|
209
|
+
const SKIP_POSITIONAL_DEFAULT = -1;
|
|
210
|
+
/**
|
|
211
|
+
* Resolve command line arguments.
|
|
212
|
+
* @param args - An arguments that contains {@link ArgSchema | arguments schema}.
|
|
213
|
+
* @param tokens - An array of {@link ArgToken | tokens}.
|
|
214
|
+
* @param resolveArgs - An arguments that contains {@link ResolveArgs | resolve arguments}.
|
|
215
|
+
* @returns An object that contains the values of the arguments, positional arguments, rest arguments, and {@link AggregateError | validation errors}.
|
|
216
|
+
*/
|
|
217
|
+
function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKIP_POSITIONAL_DEFAULT, toKebab = false } = {}) {
|
|
218
|
+
const skipPositionalIndex = typeof skipPositional === "number" ? Math.max(skipPositional, SKIP_POSITIONAL_DEFAULT) : SKIP_POSITIONAL_DEFAULT;
|
|
219
|
+
const rest = [];
|
|
220
|
+
const optionTokens = [];
|
|
221
|
+
const positionalTokens = [];
|
|
222
|
+
let currentLongOption;
|
|
223
|
+
let currentShortOption;
|
|
224
|
+
const expandableShortOptions = [];
|
|
225
|
+
function toShortValue() {
|
|
226
|
+
if (expandableShortOptions.length === 0) return void 0;
|
|
227
|
+
else {
|
|
228
|
+
const value = expandableShortOptions.map((token) => token.name).join("");
|
|
229
|
+
expandableShortOptions.length = 0;
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function applyLongOptionValue(value = void 0) {
|
|
234
|
+
if (currentLongOption) {
|
|
235
|
+
currentLongOption.value = value;
|
|
236
|
+
optionTokens.push({ ...currentLongOption });
|
|
237
|
+
currentLongOption = void 0;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function applyShortOptionValue(value = void 0) {
|
|
241
|
+
if (currentShortOption) {
|
|
242
|
+
currentShortOption.value = value || toShortValue();
|
|
243
|
+
optionTokens.push({ ...currentShortOption });
|
|
244
|
+
currentShortOption = void 0;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* analyze phase to resolve value
|
|
249
|
+
* separate tokens into positionals, long and short options, after that resolve values
|
|
250
|
+
*/
|
|
251
|
+
const schemas = Object.values(args);
|
|
252
|
+
let terminated = false;
|
|
253
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
254
|
+
const token = tokens[i];
|
|
255
|
+
if (token.kind === "positional") {
|
|
256
|
+
if (terminated && token.value) {
|
|
257
|
+
rest.push(token.value);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (currentShortOption) {
|
|
261
|
+
const found = schemas.find((schema) => schema.short === currentShortOption.name && schema.type === "boolean");
|
|
262
|
+
if (found) positionalTokens.push({ ...token });
|
|
263
|
+
} else if (currentLongOption) {
|
|
264
|
+
const found = args[currentLongOption.name]?.type === "boolean";
|
|
265
|
+
if (found) positionalTokens.push({ ...token });
|
|
266
|
+
} else positionalTokens.push({ ...token });
|
|
267
|
+
applyLongOptionValue(token.value);
|
|
268
|
+
applyShortOptionValue(token.value);
|
|
269
|
+
} else if (token.kind === "option") if (token.rawName) {
|
|
270
|
+
if (hasLongOptionPrefix(token.rawName)) {
|
|
271
|
+
applyLongOptionValue();
|
|
272
|
+
if (token.inlineValue) optionTokens.push({ ...token });
|
|
273
|
+
else currentLongOption = { ...token };
|
|
274
|
+
applyShortOptionValue();
|
|
275
|
+
} else if (isShortOption(token.rawName)) if (currentShortOption) {
|
|
276
|
+
if (currentShortOption.index === token.index) if (shortGrouping) {
|
|
277
|
+
currentShortOption.value = token.value;
|
|
278
|
+
optionTokens.push({ ...currentShortOption });
|
|
279
|
+
currentShortOption = { ...token };
|
|
280
|
+
} else expandableShortOptions.push({ ...token });
|
|
281
|
+
else {
|
|
282
|
+
currentShortOption.value = toShortValue();
|
|
283
|
+
optionTokens.push({ ...currentShortOption });
|
|
284
|
+
currentShortOption = { ...token };
|
|
285
|
+
}
|
|
286
|
+
applyLongOptionValue();
|
|
287
|
+
} else {
|
|
288
|
+
currentShortOption = { ...token };
|
|
289
|
+
applyLongOptionValue();
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
if (currentShortOption && currentShortOption.index == token.index && token.inlineValue) {
|
|
293
|
+
currentShortOption.value = token.value;
|
|
294
|
+
optionTokens.push({ ...currentShortOption });
|
|
295
|
+
currentShortOption = void 0;
|
|
296
|
+
}
|
|
297
|
+
applyLongOptionValue();
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
if (token.kind === "option-terminator") terminated = true;
|
|
301
|
+
applyLongOptionValue();
|
|
302
|
+
applyShortOptionValue();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* check if the last long or short option is not resolved
|
|
307
|
+
*/
|
|
308
|
+
applyLongOptionValue();
|
|
309
|
+
applyShortOptionValue();
|
|
310
|
+
/**
|
|
311
|
+
* resolve values
|
|
312
|
+
*/
|
|
313
|
+
const values = Object.create(null);
|
|
314
|
+
const errors = [];
|
|
315
|
+
function checkTokenName(option, schema, token) {
|
|
316
|
+
return token.name === (schema.type === "boolean" ? schema.negatable && token.name?.startsWith("no-") ? `no-${option}` : option : option);
|
|
317
|
+
}
|
|
318
|
+
const positionalItemCount = tokens.filter((token) => token.kind === "positional").length;
|
|
319
|
+
function getPositionalSkipIndex() {
|
|
320
|
+
return Math.min(skipPositionalIndex, positionalItemCount);
|
|
321
|
+
}
|
|
322
|
+
let positionalsCount = 0;
|
|
323
|
+
for (const [rawArg, schema] of Object.entries(args)) {
|
|
324
|
+
const arg = toKebab || schema.toKebab ? kebabnize(rawArg) : rawArg;
|
|
325
|
+
if (schema.required) {
|
|
326
|
+
const found = optionTokens.find((token) => {
|
|
327
|
+
return schema.short && token.name === schema.short || token.rawName && hasLongOptionPrefix(token.rawName) && token.name === arg;
|
|
328
|
+
});
|
|
329
|
+
if (!found) {
|
|
330
|
+
errors.push(createRequireError(arg, schema));
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (schema.type === "positional") {
|
|
335
|
+
if (skipPositionalIndex > SKIP_POSITIONAL_DEFAULT) while (positionalsCount <= getPositionalSkipIndex()) positionalsCount++;
|
|
336
|
+
const positional = positionalTokens[positionalsCount];
|
|
337
|
+
if (positional != null) values[rawArg] = positional.value;
|
|
338
|
+
else errors.push(createRequireError(arg, schema));
|
|
339
|
+
positionalsCount++;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
for (let i = 0; i < optionTokens.length; i++) {
|
|
343
|
+
const token = optionTokens[i];
|
|
344
|
+
if (checkTokenName(arg, schema, token) && token.rawName != void 0 && hasLongOptionPrefix(token.rawName) || schema.short === token.name && token.rawName != void 0 && isShortOption(token.rawName)) {
|
|
345
|
+
const invalid = validateRequire(token, arg, schema);
|
|
346
|
+
if (invalid) {
|
|
347
|
+
errors.push(invalid);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (schema.type === "boolean") token.value = void 0;
|
|
351
|
+
const [parsedValue, error] = parse(token, arg, schema);
|
|
352
|
+
if (error) errors.push(error);
|
|
353
|
+
else if (schema.multiple) {
|
|
354
|
+
values[rawArg] ||= [];
|
|
355
|
+
values[rawArg].push(parsedValue);
|
|
356
|
+
} else values[rawArg] = parsedValue;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (values[rawArg] == null && schema.default != null) values[rawArg] = schema.default;
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
values,
|
|
363
|
+
positionals: positionalTokens.map((token) => token.value),
|
|
364
|
+
rest,
|
|
365
|
+
error: errors.length > 0 ? new AggregateError(errors) : void 0
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function parse(token, option, schema) {
|
|
369
|
+
switch (schema.type) {
|
|
370
|
+
case "string": return typeof token.value === "string" ? [token.value || schema.default, void 0] : [void 0, createTypeError(option, schema)];
|
|
371
|
+
case "boolean": return token.value ? [token.value || schema.default, void 0] : [!(schema.negatable && token.name.startsWith("no-")), void 0];
|
|
372
|
+
case "number": {
|
|
373
|
+
if (!isNumeric(token.value)) return [void 0, createTypeError(option, schema)];
|
|
374
|
+
return token.value ? [+token.value, void 0] : [+(schema.default || ""), void 0];
|
|
375
|
+
}
|
|
376
|
+
case "enum": {
|
|
377
|
+
if (schema.choices && !schema.choices.includes(token.value)) return [void 0, new ArgResolveError(`Optional argument '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}should be chosen from '${schema.type}' [${schema.choices.map((c) => JSON.stringify(c)).join(", ")}] values`, option, "type", schema)];
|
|
378
|
+
return [token.value || schema.default, void 0];
|
|
379
|
+
}
|
|
380
|
+
case "custom": {
|
|
381
|
+
if (typeof schema.parse !== "function") throw new TypeError(`argument '${option}' should have a 'parse' function`);
|
|
382
|
+
try {
|
|
383
|
+
return [schema.parse(token.value || String(schema.default || "")), void 0];
|
|
384
|
+
} catch (error) {
|
|
385
|
+
return [void 0, error];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
default: throw new Error(`Unsupported argument type '${schema.type}' for option '${option}'`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function createRequireError(option, schema) {
|
|
392
|
+
const message = schema.type === "positional" ? `Positional argument '${option}' is required` : `Optional argument '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}is required`;
|
|
393
|
+
return new ArgResolveError(message, option, "required", schema);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* An error that occurs when resolving arguments.
|
|
397
|
+
* This error is thrown when the argument is not valid.
|
|
398
|
+
*/
|
|
399
|
+
var ArgResolveError = class extends Error {
|
|
400
|
+
name;
|
|
401
|
+
schema;
|
|
402
|
+
type;
|
|
403
|
+
constructor(message, name, type, schema) {
|
|
404
|
+
super(message);
|
|
405
|
+
this.name = name;
|
|
406
|
+
this.type = type;
|
|
407
|
+
this.schema = schema;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
function validateRequire(token, option, schema) {
|
|
411
|
+
if (schema.required && schema.type !== "boolean" && !token.value) return createRequireError(option, schema);
|
|
412
|
+
}
|
|
413
|
+
function isNumeric(str) {
|
|
414
|
+
return str.trim() !== "" && !isNaN(str);
|
|
415
|
+
}
|
|
416
|
+
function createTypeError(option, schema) {
|
|
417
|
+
return new ArgResolveError(`Optional argument '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}should be '${schema.type}'`, option, "type", schema);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region ../gunshi/src/constants.ts
|
|
422
|
+
const ANONYMOUS_COMMAND_NAME = "(anonymous)";
|
|
423
|
+
const NOOP = () => {};
|
|
424
|
+
const COMMAND_OPTIONS_DEFAULT = {
|
|
425
|
+
name: void 0,
|
|
426
|
+
description: void 0,
|
|
427
|
+
version: void 0,
|
|
428
|
+
cwd: void 0,
|
|
429
|
+
usageSilent: false,
|
|
430
|
+
subCommands: void 0,
|
|
431
|
+
leftMargin: 2,
|
|
432
|
+
middleMargin: 10,
|
|
433
|
+
usageOptionType: false,
|
|
434
|
+
usageOptionValue: true,
|
|
435
|
+
renderHeader: void 0,
|
|
436
|
+
renderUsage: void 0,
|
|
437
|
+
renderValidationErrors: void 0,
|
|
438
|
+
plugins: void 0
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region ../gunshi/src/utils.ts
|
|
443
|
+
function isLazyCommand(cmd) {
|
|
444
|
+
return typeof cmd === "function" && "commandName" in cmd && !!cmd.commandName;
|
|
445
|
+
}
|
|
446
|
+
async function resolveLazyCommand(cmd, name, needRunResolving = false) {
|
|
447
|
+
let command;
|
|
448
|
+
if (isLazyCommand(cmd)) {
|
|
449
|
+
const baseCommand = {
|
|
450
|
+
name: cmd.commandName,
|
|
451
|
+
description: cmd.description,
|
|
452
|
+
args: cmd.args,
|
|
453
|
+
examples: cmd.examples
|
|
454
|
+
};
|
|
455
|
+
if ("resource" in cmd && cmd.resource) baseCommand.resource = cmd.resource;
|
|
456
|
+
command = Object.assign(create(), baseCommand);
|
|
457
|
+
if (needRunResolving) {
|
|
458
|
+
const loaded = await cmd();
|
|
459
|
+
if (typeof loaded === "function") command.run = loaded;
|
|
460
|
+
else if (typeof loaded === "object") {
|
|
461
|
+
if (loaded.run == null) throw new TypeError(`'run' is required in command: ${cmd.name || name}`);
|
|
462
|
+
command.run = loaded.run;
|
|
463
|
+
command.name = loaded.name;
|
|
464
|
+
command.description = loaded.description;
|
|
465
|
+
command.args = loaded.args;
|
|
466
|
+
command.examples = loaded.examples;
|
|
467
|
+
if ("resource" in loaded && loaded.resource) command.resource = loaded.resource;
|
|
468
|
+
} else throw new TypeError(`Cannot resolve command: ${cmd.name || name}`);
|
|
469
|
+
}
|
|
470
|
+
} else command = Object.assign(create(), cmd);
|
|
471
|
+
if (command.name == null && name) command.name = name;
|
|
472
|
+
return deepFreeze(command);
|
|
473
|
+
}
|
|
474
|
+
function create(obj = null) {
|
|
475
|
+
return Object.create(obj);
|
|
476
|
+
}
|
|
477
|
+
function log(...args) {
|
|
478
|
+
console.log(...args);
|
|
479
|
+
}
|
|
480
|
+
function deepFreeze(obj, ignores = []) {
|
|
481
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
482
|
+
for (const key of Object.keys(obj)) {
|
|
483
|
+
const value = obj[key];
|
|
484
|
+
if (ignores.includes(key)) continue;
|
|
485
|
+
if (typeof value === "object" && value !== null) deepFreeze(value, ignores);
|
|
486
|
+
}
|
|
487
|
+
return Object.freeze(obj);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region ../gunshi/src/context.ts
|
|
492
|
+
/**
|
|
493
|
+
* Create a {@link CommandContext | command context}
|
|
494
|
+
* @param param A {@link CommandContextParams | parameters} to create a {@link CommandContext | command context}
|
|
495
|
+
* @returns A {@link CommandContext | command context}, which is readonly
|
|
496
|
+
*/
|
|
497
|
+
async function createCommandContext({ args, values, positionals, rest, argv, tokens, command, extensions = {}, cliOptions, callMode = "entry", omitted = false, validationError }) {
|
|
498
|
+
/**
|
|
499
|
+
* normailize the options schema and values, to avoid prototype pollution
|
|
500
|
+
*/
|
|
501
|
+
const _args = Object.entries(args).reduce((acc, [key, value]) => {
|
|
502
|
+
acc[key] = Object.assign(create(), value);
|
|
503
|
+
return acc;
|
|
504
|
+
}, create());
|
|
505
|
+
/**
|
|
506
|
+
* setup the environment
|
|
507
|
+
*/
|
|
508
|
+
const env = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, cliOptions);
|
|
509
|
+
/**
|
|
510
|
+
* create the command context
|
|
511
|
+
*/
|
|
512
|
+
const core = Object.assign(create(), {
|
|
513
|
+
name: getCommandName(command),
|
|
514
|
+
description: command.description,
|
|
515
|
+
omitted,
|
|
516
|
+
callMode,
|
|
517
|
+
env,
|
|
518
|
+
args: _args,
|
|
519
|
+
values,
|
|
520
|
+
positionals,
|
|
521
|
+
rest,
|
|
522
|
+
_: argv,
|
|
523
|
+
tokens,
|
|
524
|
+
toKebab: command.toKebab,
|
|
525
|
+
log: cliOptions.usageSilent ? NOOP : log,
|
|
526
|
+
validationError
|
|
527
|
+
});
|
|
528
|
+
/**
|
|
529
|
+
* extend the command context with extensions
|
|
530
|
+
*/
|
|
531
|
+
if (Object.keys(extensions).length > 0) {
|
|
532
|
+
const ext = create(null);
|
|
533
|
+
Object.defineProperty(core, "extensions", {
|
|
534
|
+
value: ext,
|
|
535
|
+
writable: false,
|
|
536
|
+
enumerable: true,
|
|
537
|
+
configurable: true
|
|
538
|
+
});
|
|
539
|
+
for (const [key, extension] of Object.entries(extensions)) {
|
|
540
|
+
ext[key] = await extension.factory(core, command);
|
|
541
|
+
if (extension.onFactory) await extension.onFactory(core, command);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const ctx = deepFreeze(core, ["extensions"]);
|
|
545
|
+
return ctx;
|
|
546
|
+
}
|
|
547
|
+
function getCommandName(cmd) {
|
|
548
|
+
if (isLazyCommand(cmd)) return cmd.commandName || cmd.name || ANONYMOUS_COMMAND_NAME;
|
|
549
|
+
else if (typeof cmd === "object") return cmd.name || ANONYMOUS_COMMAND_NAME;
|
|
550
|
+
else return ANONYMOUS_COMMAND_NAME;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region ../gunshi/src/decorators.ts
|
|
555
|
+
const EMPTY_RENDERER = async () => "";
|
|
556
|
+
/**
|
|
557
|
+
* Factory function for creating a decorators manager.
|
|
558
|
+
* @returns A new decorators manager instance
|
|
559
|
+
*/
|
|
560
|
+
function createDecorators() {
|
|
561
|
+
/**
|
|
562
|
+
* private states
|
|
563
|
+
*/
|
|
564
|
+
const headerDecorators = [];
|
|
565
|
+
const usageDecorators = [];
|
|
566
|
+
const validationDecorators = [];
|
|
567
|
+
const commandDecorators = [];
|
|
568
|
+
/**
|
|
569
|
+
* helper function for building renderers
|
|
570
|
+
*/
|
|
571
|
+
function buildRenderer(decorators, defaultRenderer) {
|
|
572
|
+
if (decorators.length === 0) return defaultRenderer;
|
|
573
|
+
let renderer = defaultRenderer;
|
|
574
|
+
for (const decorator of decorators) {
|
|
575
|
+
const previousRenderer = renderer;
|
|
576
|
+
renderer = (ctx) => decorator(previousRenderer, ctx);
|
|
577
|
+
}
|
|
578
|
+
return renderer;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* public interfaces
|
|
582
|
+
*/
|
|
583
|
+
return Object.freeze({
|
|
584
|
+
addHeaderDecorator(decorator) {
|
|
585
|
+
headerDecorators.push(decorator);
|
|
586
|
+
},
|
|
587
|
+
addUsageDecorator(decorator) {
|
|
588
|
+
usageDecorators.push(decorator);
|
|
589
|
+
},
|
|
590
|
+
addValidationErrorsDecorator(decorator) {
|
|
591
|
+
validationDecorators.push(decorator);
|
|
592
|
+
},
|
|
593
|
+
addCommandDecorator(decorator) {
|
|
594
|
+
commandDecorators.push(decorator);
|
|
595
|
+
},
|
|
596
|
+
get commandDecorators() {
|
|
597
|
+
return [...commandDecorators];
|
|
598
|
+
},
|
|
599
|
+
getHeaderRenderer() {
|
|
600
|
+
return buildRenderer(headerDecorators, EMPTY_RENDERER);
|
|
601
|
+
},
|
|
602
|
+
getUsageRenderer() {
|
|
603
|
+
return buildRenderer(usageDecorators, EMPTY_RENDERER);
|
|
604
|
+
},
|
|
605
|
+
getValidationErrorsRenderer() {
|
|
606
|
+
if (validationDecorators.length === 0) return EMPTY_RENDERER;
|
|
607
|
+
let renderer = EMPTY_RENDERER;
|
|
608
|
+
for (const decorator of validationDecorators) {
|
|
609
|
+
const previousRenderer = renderer;
|
|
610
|
+
renderer = (ctx, error) => decorator(previousRenderer, ctx, error);
|
|
611
|
+
}
|
|
612
|
+
return renderer;
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region ../gunshi/src/plugin/context.ts
|
|
619
|
+
/**
|
|
620
|
+
* Factory function for creating a plugin context.
|
|
621
|
+
* @param decorators - A {@link Decorators} instance.
|
|
622
|
+
* @returns A new {@link PluginContext} instance.
|
|
623
|
+
*/
|
|
624
|
+
function createPluginContext(decorators) {
|
|
625
|
+
/**
|
|
626
|
+
* private states
|
|
627
|
+
*/
|
|
628
|
+
const globalOptions = new Map();
|
|
629
|
+
/**
|
|
630
|
+
* public interfaces
|
|
631
|
+
*/
|
|
632
|
+
return Object.freeze({
|
|
633
|
+
get globalOptions() {
|
|
634
|
+
return new Map(globalOptions);
|
|
635
|
+
},
|
|
636
|
+
addGlobalOption(name, schema) {
|
|
637
|
+
if (!name) throw new Error("Option name must be a non-empty string");
|
|
638
|
+
if (globalOptions.has(name)) throw new Error(`Global option '${name}' is already registered`);
|
|
639
|
+
globalOptions.set(name, schema);
|
|
640
|
+
},
|
|
641
|
+
decorateHeaderRenderer(decorator) {
|
|
642
|
+
decorators.addHeaderDecorator(decorator);
|
|
643
|
+
},
|
|
644
|
+
decorateUsageRenderer(decorator) {
|
|
645
|
+
decorators.addUsageDecorator(decorator);
|
|
646
|
+
},
|
|
647
|
+
decorateValidationErrorsRenderer(decorator) {
|
|
648
|
+
decorators.addValidationErrorsDecorator(decorator);
|
|
649
|
+
},
|
|
650
|
+
decorateCommand(decorator) {
|
|
651
|
+
decorators.addCommandDecorator(decorator);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
//#endregion
|
|
657
|
+
//#region ../gunshi/src/plugin/dependency.ts
|
|
658
|
+
/**
|
|
659
|
+
* Resolve plugin dependencies using topological sort
|
|
660
|
+
* @param plugins - Array of plugins to resolve
|
|
661
|
+
* @returns Array of plugins sorted by dependencies
|
|
662
|
+
* @throws Error if circular dependency is detected or required dependency is missing
|
|
663
|
+
*/
|
|
664
|
+
function resolveDependencies(plugins) {
|
|
665
|
+
const sorted = [];
|
|
666
|
+
const visited = new Set();
|
|
667
|
+
const visiting = new Set();
|
|
668
|
+
const pluginMap = new Map();
|
|
669
|
+
for (const plugin of plugins) if (plugin.id) {
|
|
670
|
+
if (pluginMap.has(plugin.id)) console.warn(`Duplicate plugin id detected: \`${plugin.id}\``);
|
|
671
|
+
pluginMap.set(plugin.id, plugin);
|
|
672
|
+
}
|
|
673
|
+
function visit(plugin) {
|
|
674
|
+
if (!plugin.id) return;
|
|
675
|
+
if (visited.has(plugin.id)) return;
|
|
676
|
+
if (visiting.has(plugin.id)) throw new Error(`Circular dependency detected: \`${[...visiting].join(` -> `) + " -> " + plugin.id}\``);
|
|
677
|
+
visiting.add(plugin.id);
|
|
678
|
+
const deps = plugin.dependencies || [];
|
|
679
|
+
for (const dep of deps) {
|
|
680
|
+
const depId = typeof dep === "string" ? dep : dep.id;
|
|
681
|
+
const isOptional = typeof dep === "string" ? false : dep.optional || false;
|
|
682
|
+
const depPlugin = pluginMap.get(depId);
|
|
683
|
+
if (!depPlugin && !isOptional) throw new Error(`Missing required dependency: \`${depId}\` on \`${plugin.id}\``);
|
|
684
|
+
if (depPlugin) visit(depPlugin);
|
|
685
|
+
}
|
|
686
|
+
visiting.delete(plugin.id);
|
|
687
|
+
visited.add(plugin.id);
|
|
688
|
+
sorted.push(plugin);
|
|
689
|
+
}
|
|
690
|
+
for (const plugin of plugins) visit(plugin);
|
|
691
|
+
return sorted;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region ../gunshi/src/cli/core.ts
|
|
696
|
+
async function cliCore(argv, entry, options, plugins) {
|
|
697
|
+
const decorators = createDecorators();
|
|
698
|
+
const pluginContext = createPluginContext(decorators);
|
|
699
|
+
const resolvedPlugins = await applyPlugins(pluginContext, [...plugins, ...options.plugins || []]);
|
|
700
|
+
const cliOptions = normalizeCliOptions(options, entry, decorators);
|
|
701
|
+
const tokens = parseArgs(argv);
|
|
702
|
+
const subCommand = getSubCommand(tokens);
|
|
703
|
+
const { commandName: name, command, callMode } = await resolveCommand(subCommand, entry, cliOptions);
|
|
704
|
+
if (!command) throw new Error(`Command not found: ${name || ""}`);
|
|
705
|
+
const args = resolveArguments(pluginContext, getCommandArgs(command));
|
|
706
|
+
const { values, positionals, rest, error } = resolveArgs(args, tokens, {
|
|
707
|
+
shortGrouping: true,
|
|
708
|
+
toKebab: command.toKebab,
|
|
709
|
+
skipPositional: cliOptions.subCommands.size > 0 ? 0 : -1
|
|
710
|
+
});
|
|
711
|
+
const omitted = !subCommand;
|
|
712
|
+
const commandContext = await createCommandContext({
|
|
713
|
+
args,
|
|
714
|
+
values,
|
|
715
|
+
positionals,
|
|
716
|
+
rest,
|
|
717
|
+
argv,
|
|
718
|
+
tokens,
|
|
719
|
+
omitted,
|
|
720
|
+
callMode,
|
|
721
|
+
command,
|
|
722
|
+
extensions: getPluginExtensions(resolvedPlugins),
|
|
723
|
+
validationError: error,
|
|
724
|
+
cliOptions
|
|
725
|
+
});
|
|
726
|
+
return await executeCommand(command, commandContext, name || "", decorators.commandDecorators);
|
|
727
|
+
}
|
|
728
|
+
async function applyPlugins(pluginContext, plugins) {
|
|
729
|
+
const sortedPlugins = resolveDependencies(plugins);
|
|
730
|
+
try {
|
|
731
|
+
for (const plugin of sortedPlugins)
|
|
732
|
+
/**
|
|
733
|
+
* NOTE(kazupon):
|
|
734
|
+
* strictly `Args` are not required for plugin installation.
|
|
735
|
+
* because the strictly `Args` required by each plugin are unknown,
|
|
736
|
+
* and the plugin side can not know what the user will specify.
|
|
737
|
+
*/
|
|
738
|
+
await plugin(pluginContext);
|
|
739
|
+
} catch (error) {
|
|
740
|
+
console.error("Error loading plugin:", error.message);
|
|
741
|
+
}
|
|
742
|
+
return sortedPlugins;
|
|
743
|
+
}
|
|
744
|
+
function getCommandArgs(cmd) {
|
|
745
|
+
if (isLazyCommand(cmd)) return cmd.args || create();
|
|
746
|
+
else if (typeof cmd === "object") return cmd.args || create();
|
|
747
|
+
else return create();
|
|
748
|
+
}
|
|
749
|
+
function resolveArguments(pluginContext, args) {
|
|
750
|
+
return Object.assign(create(), Object.fromEntries(pluginContext.globalOptions), args);
|
|
751
|
+
}
|
|
752
|
+
function normalizeCliOptions(options, entry, decorators) {
|
|
753
|
+
const subCommands = new Map(options.subCommands);
|
|
754
|
+
if (options.subCommands) {
|
|
755
|
+
if (isLazyCommand(entry)) subCommands.set(entry.commandName, entry);
|
|
756
|
+
else if (typeof entry === "object" && entry.name) subCommands.set(entry.name, entry);
|
|
757
|
+
}
|
|
758
|
+
const resolvedOptions = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, options, { subCommands });
|
|
759
|
+
if (resolvedOptions.renderHeader === void 0) resolvedOptions.renderHeader = decorators.getHeaderRenderer();
|
|
760
|
+
if (resolvedOptions.renderUsage === void 0) resolvedOptions.renderUsage = decorators.getUsageRenderer();
|
|
761
|
+
if (resolvedOptions.renderValidationErrors === void 0) resolvedOptions.renderValidationErrors = decorators.getValidationErrorsRenderer();
|
|
762
|
+
return resolvedOptions;
|
|
763
|
+
}
|
|
764
|
+
function getSubCommand(tokens) {
|
|
765
|
+
const firstToken = tokens[0];
|
|
766
|
+
return firstToken && firstToken.kind === "positional" && firstToken.index === 0 && firstToken.value ? firstToken.value : "";
|
|
767
|
+
}
|
|
768
|
+
const CANNOT_RESOLVE_COMMAND = { callMode: "unexpected" };
|
|
769
|
+
async function resolveCommand(sub, entry, options) {
|
|
770
|
+
const omitted = !sub;
|
|
771
|
+
async function doResolveCommand() {
|
|
772
|
+
if (typeof entry === "function") if ("commandName" in entry && entry.commandName) return {
|
|
773
|
+
commandName: entry.commandName,
|
|
774
|
+
command: entry,
|
|
775
|
+
callMode: "entry"
|
|
776
|
+
};
|
|
777
|
+
else return {
|
|
778
|
+
command: { run: entry },
|
|
779
|
+
callMode: "entry"
|
|
780
|
+
};
|
|
781
|
+
else if (typeof entry === "object") return {
|
|
782
|
+
commandName: resolveEntryName(entry),
|
|
783
|
+
command: entry,
|
|
784
|
+
callMode: "entry"
|
|
785
|
+
};
|
|
786
|
+
else return CANNOT_RESOLVE_COMMAND;
|
|
787
|
+
}
|
|
788
|
+
if (omitted || options.subCommands?.size === 0) return doResolveCommand();
|
|
789
|
+
const cmd = options.subCommands?.get(sub);
|
|
790
|
+
if (cmd == null) return {
|
|
791
|
+
commandName: sub,
|
|
792
|
+
callMode: "unexpected"
|
|
793
|
+
};
|
|
794
|
+
if (isLazyCommand(cmd) && cmd.commandName == null) cmd.commandName = sub;
|
|
795
|
+
else if (typeof cmd === "object" && cmd.name == null) cmd.name = sub;
|
|
796
|
+
return {
|
|
797
|
+
commandName: sub,
|
|
798
|
+
command: cmd,
|
|
799
|
+
callMode: "subCommand"
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
function resolveEntryName(entry) {
|
|
803
|
+
return entry.name || ANONYMOUS_COMMAND_NAME;
|
|
804
|
+
}
|
|
805
|
+
function getPluginExtensions(plugins) {
|
|
806
|
+
const extensions = create();
|
|
807
|
+
for (const plugin of plugins) if (plugin.extension) {
|
|
808
|
+
const key = plugin.id;
|
|
809
|
+
if (extensions[key]) console.warn(`Plugin "${key}" is already installed. ignore it for command context extending.`);
|
|
810
|
+
else extensions[key] = plugin.extension;
|
|
811
|
+
}
|
|
812
|
+
return extensions;
|
|
813
|
+
}
|
|
814
|
+
async function executeCommand(cmd, ctx, name, decorators) {
|
|
815
|
+
const resolved = isLazyCommand(cmd) ? await resolveLazyCommand(cmd, name, true) : cmd;
|
|
816
|
+
const baseRunner = resolved.run || NOOP;
|
|
817
|
+
const decoratedRunner = decorators.reduceRight((runner, decorator) => decorator(runner), baseRunner);
|
|
818
|
+
try {
|
|
819
|
+
if (ctx.env.onBeforeCommand) await ctx.env.onBeforeCommand(ctx);
|
|
820
|
+
const result = await decoratedRunner(ctx);
|
|
821
|
+
if (ctx.env.onAfterCommand) await ctx.env.onAfterCommand(ctx, result);
|
|
822
|
+
return typeof result === "string" ? result : void 0;
|
|
823
|
+
} catch (error) {
|
|
824
|
+
if (ctx.env.onErrorCommand) try {
|
|
825
|
+
await ctx.env.onErrorCommand(ctx, error);
|
|
826
|
+
} catch (hookError) {
|
|
827
|
+
console.error("Error in onErrorCommand hook:", hookError);
|
|
828
|
+
}
|
|
829
|
+
throw error;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
//#endregion
|
|
834
|
+
//#region ../gunshi/src/cli/bone.ts
|
|
835
|
+
/**
|
|
836
|
+
* Run the command.
|
|
837
|
+
* @param args Command line arguments
|
|
838
|
+
* @param entry A {@link Command | entry command}, an {@link CommandRunner | inline command runner}, or a {@link LazyCommand | lazily-loaded command}
|
|
839
|
+
* @param options A {@link CliOptions | CLI options}
|
|
840
|
+
* @returns A rendered usage or undefined. if you will use {@link CliOptions.usageSilent} option, it will return rendered usage string.
|
|
841
|
+
*/
|
|
842
|
+
async function cli(argv, entry, options = {}) {
|
|
843
|
+
return cliCore(argv, entry, options, []);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
//#endregion
|
|
847
|
+
export { cli };
|