auto-mode 1.0.1 → 2.0.1
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/dist/{classify-edit-B10-ua5i.js → classify-edit-DXP7XLGH.js} +10 -10
- package/dist/cli.js +2810 -73
- package/dist/index.d.ts +47 -18
- package/dist/index.js +13 -1
- package/dist/parse-action-request-neI2tNwd.js +5378 -0
- package/dist/{parse-action-request-BfkmQxEC.js → schemas-Dc2424NY.js} +83 -5238
- package/dist/start-judge-server-CCSZbTwM.js +169 -0
- package/dist/start-judge-session-B_DFN8Ji.js +2 -0
- package/dist/start-judge-session-DjPX0007.js +37956 -0
- package/package.json +5 -3
- package/policy/containment.md +3 -3
- package/policy/judge.md +5 -12
- package/policy/classifier.md +0 -282
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,2570 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as
|
|
3
|
-
import {
|
|
2
|
+
import { C as pickJudgeLane, E as updateJSONFile, S as CLI_LIMITS, T as resolveXDGDir, _ as findCheckout, a as resolveSessionScopePath, b as loadConfig, c as splitGitCommand, d as loadCheckoutFacts, f as writeActionDiagnostic, g as isUnresolvedWord, h as resolveShellPath, i as classifyAction, l as loadPolicy, m as collectShellCommands, n as readRuntime, o as loadSessionScope, p as toRepositorySlug, s as EMPTY_SESSION_SCOPE, t as parseActionRequest, u as resolveShippedPolicyDir, v as findGitEntry, w as M, x as runCapped, y as DEFAULT_SCOPE_SOURCES } from "./parse-action-request-neI2tNwd.js";
|
|
3
|
+
import { a as literal, d as string, i as discriminatedUnion, r as boolean, s as number, u as strictObject } from "./schemas-Dc2424NY.js";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
5
|
import { chmod, mkdir, open, readFile, realpath, stat } from "node:fs/promises";
|
|
5
6
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
|
-
import {
|
|
7
|
+
import { setTimeout } from "node:timers/promises";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { constants, existsSync, statSync } from "node:fs";
|
|
7
10
|
import { text } from "node:stream/consumers";
|
|
8
|
-
import {
|
|
11
|
+
import { connect } from "node:net";
|
|
12
|
+
//#region node_modules/.bun/gunshi@1.0.0-rc.1/node_modules/gunshi/lib/utils-D-MmF6sM.js
|
|
13
|
+
/**
|
|
14
|
+
* Entry point of utils.
|
|
15
|
+
*
|
|
16
|
+
* Note that this entry point is used by gunshi to import utility functions.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
22
|
+
* @license MIT
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Convert a string to kebab-case.
|
|
26
|
+
*
|
|
27
|
+
* @param str - A string to convert
|
|
28
|
+
* @returns Converted string into kebab-case.
|
|
29
|
+
*/
|
|
30
|
+
function kebabnize(str) {
|
|
31
|
+
return str.replace(/[A-Z]/g, (match, offset) => (offset > 0 ? "-" : "") + match.toLowerCase());
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Format allowed argument choices for validation error values and fallback messages.
|
|
35
|
+
*
|
|
36
|
+
* @param choices - Choice values to format
|
|
37
|
+
* @returns JSON-quoted choices joined with `, `.
|
|
38
|
+
*/
|
|
39
|
+
function formatChoices(choices) {
|
|
40
|
+
return choices.map((value) => JSON.stringify(value)).join(", ");
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region node_modules/.bun/gunshi@1.0.0-rc.1/node_modules/gunshi/lib/utils.js
|
|
44
|
+
/**
|
|
45
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
46
|
+
* @license MIT
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* Check if the given command is a {@link LazyCommand}.
|
|
50
|
+
*
|
|
51
|
+
* @param cmd - A command to check
|
|
52
|
+
* @returns `true` if the command is a {@link LazyCommand}, otherwise `false`
|
|
53
|
+
*
|
|
54
|
+
* A lazy command is identified by its `commandName` property. The property may be
|
|
55
|
+
* `undefined` when the lazy command has no definition name.
|
|
56
|
+
*/
|
|
57
|
+
function isLazyCommand(cmd) {
|
|
58
|
+
return typeof cmd === "function" && "commandName" in cmd;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a lazy command to a {@link Command}.
|
|
62
|
+
*
|
|
63
|
+
* If the loader returns a command, the properties of the loaded command take precedence,
|
|
64
|
+
* and the properties that it does not define fall back to the definition that is given to `lazy`.
|
|
65
|
+
*
|
|
66
|
+
* @param cmd - A {@link Commandable} or {@link LazyCommand} to resolve
|
|
67
|
+
* @param name - Optional name of the command, if not provided, it will use the name from the command itself.
|
|
68
|
+
* @param needRunResolving - Whether to run the resolving function of the lazy command.
|
|
69
|
+
* @returns A resolved {@link Command}
|
|
70
|
+
*/
|
|
71
|
+
async function resolveLazyCommand(cmd, name, needRunResolving = false) {
|
|
72
|
+
let command;
|
|
73
|
+
if (isLazyCommand(cmd)) {
|
|
74
|
+
const baseCommand = {
|
|
75
|
+
name: cmd.commandName,
|
|
76
|
+
description: cmd.description,
|
|
77
|
+
args: cmd.args,
|
|
78
|
+
examples: cmd.examples,
|
|
79
|
+
internal: cmd.internal,
|
|
80
|
+
entry: cmd.entry,
|
|
81
|
+
toKebab: cmd.toKebab,
|
|
82
|
+
rendering: cmd.rendering,
|
|
83
|
+
subCommands: cmd.subCommands
|
|
84
|
+
};
|
|
85
|
+
if ("resource" in cmd && cmd.resource) baseCommand.resource = cmd.resource;
|
|
86
|
+
command = Object.assign(create(), baseCommand);
|
|
87
|
+
if (needRunResolving) {
|
|
88
|
+
const loaded = await cmd();
|
|
89
|
+
if (typeof loaded === "function") command.run = loaded;
|
|
90
|
+
else if (typeof loaded === "object") {
|
|
91
|
+
if (loaded.run == null) throw new TypeError(`'run' is required in command: ${cmd.name || name}`);
|
|
92
|
+
/**
|
|
93
|
+
* NOTE(kazupon): the loaded command wins, and the definition that is given to `lazy` is the fallback
|
|
94
|
+
* for what the loaded command does not define. A loader that returns `{ run }` keeps the whole definition,
|
|
95
|
+
* like a loader that returns the runner itself.
|
|
96
|
+
*/
|
|
97
|
+
command.run = loaded.run;
|
|
98
|
+
command.name = loaded.name ?? cmd.commandName;
|
|
99
|
+
command.description = loaded.description ?? cmd.description;
|
|
100
|
+
command.args = loaded.args ?? cmd.args;
|
|
101
|
+
command.examples = loaded.examples ?? cmd.examples;
|
|
102
|
+
command.internal = loaded.internal ?? cmd.internal;
|
|
103
|
+
command.entry = loaded.entry ?? cmd.entry;
|
|
104
|
+
command.toKebab = loaded.toKebab ?? cmd.toKebab;
|
|
105
|
+
command.rendering = loaded.rendering ?? cmd.rendering;
|
|
106
|
+
command.subCommands = loaded.subCommands || cmd.subCommands;
|
|
107
|
+
if ("resource" in loaded && loaded.resource) command.resource = loaded.resource;
|
|
108
|
+
} else throw new TypeError(`Cannot resolve command: ${cmd.name || name}`);
|
|
109
|
+
}
|
|
110
|
+
} else command = Object.assign(create(), cmd);
|
|
111
|
+
if (command.name == null && name) command.name = name;
|
|
112
|
+
return deepFreeze(command);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create an object with the specified prototype. A shorthand for `Object.create`.
|
|
116
|
+
*
|
|
117
|
+
* @param obj - An object to use as the prototype for the new object. If `null`, it will create an object with no prototype.
|
|
118
|
+
* @returns A new object with the specified prototype
|
|
119
|
+
*/
|
|
120
|
+
function create(obj = null) {
|
|
121
|
+
return Object.create(obj);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Log a message to the console.
|
|
125
|
+
*
|
|
126
|
+
* @param args - Arguments to log
|
|
127
|
+
*/
|
|
128
|
+
function log(...args) {
|
|
129
|
+
console.log(...args);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get the sub-commands of a command as a normalized Map.
|
|
133
|
+
*
|
|
134
|
+
* @param cmd - A command or lazy command
|
|
135
|
+
* @returns A Map of sub-commands, or undefined if the command has no sub-commands.
|
|
136
|
+
*/
|
|
137
|
+
function getCommandSubCommands(cmd) {
|
|
138
|
+
const subCommands = isLazyCommand(cmd) ? cmd.subCommands : typeof cmd === "object" ? cmd.subCommands : void 0;
|
|
139
|
+
if (!subCommands) return;
|
|
140
|
+
if (subCommands instanceof Map) return subCommands.size > 0 ? subCommands : void 0;
|
|
141
|
+
if (typeof subCommands === "object") {
|
|
142
|
+
const entries = Object.entries(subCommands);
|
|
143
|
+
if (entries.length === 0) return;
|
|
144
|
+
const map = /* @__PURE__ */ new Map();
|
|
145
|
+
for (const [name, cmd] of entries) map.set(name, cmd);
|
|
146
|
+
return map;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Deep freeze an object, making it immutable.
|
|
151
|
+
*
|
|
152
|
+
* @param obj - The object to freeze
|
|
153
|
+
* @param ignores - Properties to ignore during freezing
|
|
154
|
+
* @returns A frozen object
|
|
155
|
+
*/
|
|
156
|
+
function deepFreeze(obj, ignores = []) {
|
|
157
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
158
|
+
for (const key of Object.keys(obj)) {
|
|
159
|
+
const value = obj[key];
|
|
160
|
+
if (ignores.includes(key)) continue;
|
|
161
|
+
if (typeof value === "object" && value !== null) deepFreeze(value, ignores);
|
|
162
|
+
}
|
|
163
|
+
return Object.freeze(obj);
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region node_modules/.bun/gunshi@1.0.0-rc.1/node_modules/gunshi/lib/context-CSecf6R5.js
|
|
167
|
+
/**
|
|
168
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
169
|
+
* @license MIT
|
|
170
|
+
*/
|
|
171
|
+
const ANONYMOUS_COMMAND_NAME = "(anonymous)";
|
|
172
|
+
/**
|
|
173
|
+
* A no-operation function.
|
|
174
|
+
*/
|
|
175
|
+
const NOOP = () => {};
|
|
176
|
+
const CLI_OPTIONS_DEFAULT = {
|
|
177
|
+
name: void 0,
|
|
178
|
+
description: void 0,
|
|
179
|
+
version: void 0,
|
|
180
|
+
cwd: void 0,
|
|
181
|
+
usageSilent: false,
|
|
182
|
+
subCommands: void 0,
|
|
183
|
+
leftMargin: 2,
|
|
184
|
+
middleMargin: 10,
|
|
185
|
+
usageOptionType: false,
|
|
186
|
+
usageOptionValue: true,
|
|
187
|
+
strict: false,
|
|
188
|
+
renderHeader: void 0,
|
|
189
|
+
renderUsage: void 0,
|
|
190
|
+
renderValidationErrors: void 0,
|
|
191
|
+
plugins: void 0,
|
|
192
|
+
fallbackToEntry: false
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* The entry for gunshi context.
|
|
196
|
+
* This module is exported for the purpose of testing the command.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```js
|
|
200
|
+
* import { createCommandContext } from 'gunshi/context'
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* @module
|
|
204
|
+
*/
|
|
205
|
+
/**
|
|
206
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
207
|
+
* @license MIT
|
|
208
|
+
*/
|
|
209
|
+
/**
|
|
210
|
+
* Create a command context.
|
|
211
|
+
*
|
|
212
|
+
* @param param - A {@link CommandContextParams | parameters} to create a command context.
|
|
213
|
+
* @returns A {@link CommandContext | command context}, which is readonly.
|
|
214
|
+
*/
|
|
215
|
+
async function createCommandContext({ args = {}, explicit = {}, values = {}, positionals = [], rest = [], argv = [], tokens = [], command = {}, extensions = {}, cliOptions = {}, callMode = "entry", commandPath = [], omitted = false, validationError }) {
|
|
216
|
+
/**
|
|
217
|
+
* normailize the options schema and values, to avoid prototype pollution
|
|
218
|
+
*/
|
|
219
|
+
const _args = Object.entries(args).reduce((acc, [key, value]) => {
|
|
220
|
+
acc[key] = Object.assign(create(), value);
|
|
221
|
+
return acc;
|
|
222
|
+
}, create());
|
|
223
|
+
/**
|
|
224
|
+
* setup the environment
|
|
225
|
+
*/
|
|
226
|
+
const env = Object.assign(create(), CLI_OPTIONS_DEFAULT, cliOptions);
|
|
227
|
+
/**
|
|
228
|
+
* apply Command definition's rendering option with highest priority
|
|
229
|
+
*/
|
|
230
|
+
if (command.rendering) {
|
|
231
|
+
const { header, usage, validationErrors } = command.rendering;
|
|
232
|
+
if (header !== void 0) env.renderHeader = header;
|
|
233
|
+
if (usage !== void 0) env.renderUsage = usage;
|
|
234
|
+
if (validationErrors !== void 0) env.renderValidationErrors = validationErrors;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* create the command context
|
|
238
|
+
*/
|
|
239
|
+
const core = Object.assign(create(), {
|
|
240
|
+
name: getCommandName(command),
|
|
241
|
+
description: command.description,
|
|
242
|
+
omitted,
|
|
243
|
+
callMode,
|
|
244
|
+
commandPath,
|
|
245
|
+
env,
|
|
246
|
+
args: _args,
|
|
247
|
+
explicit,
|
|
248
|
+
values,
|
|
249
|
+
positionals,
|
|
250
|
+
rest,
|
|
251
|
+
_: argv,
|
|
252
|
+
tokens,
|
|
253
|
+
toKebab: command.toKebab,
|
|
254
|
+
log: cliOptions.usageSilent ? NOOP : log,
|
|
255
|
+
validationError
|
|
256
|
+
});
|
|
257
|
+
/**
|
|
258
|
+
* extend the command context with extensions
|
|
259
|
+
*/
|
|
260
|
+
if (Object.keys(extensions).length > 0) {
|
|
261
|
+
const ext = create(null);
|
|
262
|
+
Object.defineProperty(core, "extensions", {
|
|
263
|
+
value: ext,
|
|
264
|
+
writable: false,
|
|
265
|
+
enumerable: true,
|
|
266
|
+
configurable: true
|
|
267
|
+
});
|
|
268
|
+
for (const [key, extension] of Object.entries(extensions)) {
|
|
269
|
+
ext[key] = await extension.factory(core, command);
|
|
270
|
+
if (extension.onFactory) await extension.onFactory(core, command);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return deepFreeze(core, ["extensions", "entryCommand"]);
|
|
274
|
+
}
|
|
275
|
+
function getCommandName(cmd) {
|
|
276
|
+
if (isLazyCommand(cmd)) return cmd.commandName || cmd.name || "(anonymous)";
|
|
277
|
+
else if (typeof cmd === "object") return cmd.name || "(anonymous)";
|
|
278
|
+
else return ANONYMOUS_COMMAND_NAME;
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region node_modules/.bun/gunshi@1.0.0-rc.1/node_modules/gunshi/lib/resolver-CAlgxe_R-3pz9PVFu.js
|
|
282
|
+
/**
|
|
283
|
+
* forked from `nodejs/node` (`pkgjs/parseargs`)
|
|
284
|
+
* repository url: https://github.com/nodejs/node (https://github.com/pkgjs/parseargs)
|
|
285
|
+
* code url: https://github.com/nodejs/node/blob/main/lib/internal/util/parse_args/parse_args.js
|
|
286
|
+
*
|
|
287
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
288
|
+
* @license MIT
|
|
289
|
+
*/
|
|
290
|
+
const HYPHEN_CHAR = "-";
|
|
291
|
+
const HYPHEN_CODE = HYPHEN_CHAR.codePointAt(0);
|
|
292
|
+
const EQUAL_CHAR = "=";
|
|
293
|
+
const EQUAL_CODE = EQUAL_CHAR.codePointAt(0);
|
|
294
|
+
const TERMINATOR = "--";
|
|
295
|
+
const SHORT_OPTION_PREFIX = HYPHEN_CHAR;
|
|
296
|
+
const LONG_OPTION_PREFIX = "--";
|
|
297
|
+
/**
|
|
298
|
+
* Parse command line arguments.
|
|
299
|
+
*
|
|
300
|
+
* @param args - command line arguments
|
|
301
|
+
* @param options - parse options, about details see {@link ParserOptions}
|
|
302
|
+
* @returns Argument tokens.
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```js
|
|
306
|
+
* import { parseArgs } from 'args-tokens' // for Node.js and Bun
|
|
307
|
+
* // import { parseArgs } from 'jsr:@kazupon/args-tokens' // for Deno
|
|
308
|
+
*
|
|
309
|
+
* const tokens = parseArgs(['--foo', 'bar', '-x', '--bar=baz'])
|
|
310
|
+
* // do something with using tokens
|
|
311
|
+
* // ...
|
|
312
|
+
* console.log('tokens:', tokens)
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
function parseArgs(args, options = {}) {
|
|
316
|
+
const { allowCompatible = false } = options;
|
|
317
|
+
const tokens = [];
|
|
318
|
+
const remainings = [...args];
|
|
319
|
+
let index = -1;
|
|
320
|
+
let groupCount = 0;
|
|
321
|
+
let hasShortGroupValue = false;
|
|
322
|
+
let shortGroupValueInline = true;
|
|
323
|
+
while (remainings.length > 0) {
|
|
324
|
+
const arg = remainings.shift();
|
|
325
|
+
if (arg == void 0) break;
|
|
326
|
+
if (groupCount > 0) groupCount--;
|
|
327
|
+
else index++;
|
|
328
|
+
if (arg === TERMINATOR) {
|
|
329
|
+
tokens.push({
|
|
330
|
+
kind: "option-terminator",
|
|
331
|
+
index
|
|
332
|
+
});
|
|
333
|
+
const mapped = remainings.map((arg) => {
|
|
334
|
+
return {
|
|
335
|
+
kind: "positional",
|
|
336
|
+
index: ++index,
|
|
337
|
+
value: arg
|
|
338
|
+
};
|
|
339
|
+
});
|
|
340
|
+
tokens.push(...mapped);
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
if (isShortOption(arg)) {
|
|
344
|
+
const shortOption = arg.charAt(1);
|
|
345
|
+
let value;
|
|
346
|
+
let inlineValue;
|
|
347
|
+
if (groupCount) {
|
|
348
|
+
tokens.push({
|
|
349
|
+
kind: "option",
|
|
350
|
+
name: shortOption,
|
|
351
|
+
rawName: arg,
|
|
352
|
+
index,
|
|
353
|
+
value,
|
|
354
|
+
inlineValue
|
|
355
|
+
});
|
|
356
|
+
if (groupCount === 1 && hasShortGroupValue) {
|
|
357
|
+
value = remainings.shift();
|
|
358
|
+
inlineValue = shortGroupValueInline;
|
|
359
|
+
hasShortGroupValue = false;
|
|
360
|
+
tokens.push({
|
|
361
|
+
kind: "option",
|
|
362
|
+
index,
|
|
363
|
+
value,
|
|
364
|
+
inlineValue
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
} else tokens.push({
|
|
368
|
+
kind: "option",
|
|
369
|
+
name: shortOption,
|
|
370
|
+
rawName: arg,
|
|
371
|
+
index,
|
|
372
|
+
value,
|
|
373
|
+
inlineValue
|
|
374
|
+
});
|
|
375
|
+
if (value != null) ++index;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (isShortOptionGroup(arg)) {
|
|
379
|
+
const expanded = [];
|
|
380
|
+
let separated = false;
|
|
381
|
+
let dashed = false;
|
|
382
|
+
let shortValue = "";
|
|
383
|
+
for (let i = 1; i < arg.length; i++) {
|
|
384
|
+
const shortableOption = arg.charAt(i);
|
|
385
|
+
if (separated) shortValue += shortableOption;
|
|
386
|
+
else if (!allowCompatible && shortableOption.codePointAt(0) === EQUAL_CODE && expanded.length > 0) separated = true;
|
|
387
|
+
else if (!allowCompatible && shortableOption.codePointAt(0) === HYPHEN_CODE) {
|
|
388
|
+
separated = true;
|
|
389
|
+
dashed = true;
|
|
390
|
+
shortValue += shortableOption;
|
|
391
|
+
} else expanded.push(`${SHORT_OPTION_PREFIX}${shortableOption}`);
|
|
392
|
+
}
|
|
393
|
+
hasShortGroupValue = separated;
|
|
394
|
+
shortGroupValueInline = !dashed;
|
|
395
|
+
if (separated) expanded.push(shortValue);
|
|
396
|
+
remainings.unshift(...expanded);
|
|
397
|
+
groupCount = expanded.length;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (isLongOption(arg)) {
|
|
401
|
+
const longOption = arg.slice(2);
|
|
402
|
+
tokens.push({
|
|
403
|
+
kind: "option",
|
|
404
|
+
name: longOption,
|
|
405
|
+
rawName: arg,
|
|
406
|
+
index,
|
|
407
|
+
value: void 0,
|
|
408
|
+
inlineValue: void 0
|
|
409
|
+
});
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (isLongOptionAndValue(arg)) {
|
|
413
|
+
const equalIndex = arg.indexOf(EQUAL_CHAR);
|
|
414
|
+
const longOption = arg.slice(2, equalIndex);
|
|
415
|
+
const value = arg.slice(equalIndex + 1);
|
|
416
|
+
tokens.push({
|
|
417
|
+
kind: "option",
|
|
418
|
+
name: longOption,
|
|
419
|
+
rawName: `${LONG_OPTION_PREFIX}${longOption}`,
|
|
420
|
+
index,
|
|
421
|
+
value,
|
|
422
|
+
inlineValue: true
|
|
423
|
+
});
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
tokens.push({
|
|
427
|
+
kind: "positional",
|
|
428
|
+
index,
|
|
429
|
+
value: arg
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return tokens;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Check if `arg` is a short option (e.g. `-f`).
|
|
436
|
+
*
|
|
437
|
+
* @param arg - An argument to check
|
|
438
|
+
* @returns Whether `arg` is a short option.
|
|
439
|
+
*/
|
|
440
|
+
function isShortOption(arg) {
|
|
441
|
+
return arg.length === 2 && arg.codePointAt(0) === HYPHEN_CODE && arg.codePointAt(1) !== HYPHEN_CODE;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Check if `arg` is a short option group (e.g. `-abc`).
|
|
445
|
+
*
|
|
446
|
+
* @param arg - An argument to check
|
|
447
|
+
* @returns Whether `arg` is a short option group.
|
|
448
|
+
*/
|
|
449
|
+
function isShortOptionGroup(arg) {
|
|
450
|
+
if (arg.length <= 2) return false;
|
|
451
|
+
if (arg.codePointAt(0) !== HYPHEN_CODE) return false;
|
|
452
|
+
if (arg.codePointAt(1) === HYPHEN_CODE) return false;
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Check if `arg` is a long option (e.g. `--foo`).
|
|
457
|
+
*
|
|
458
|
+
* @param arg - An argument to check
|
|
459
|
+
* @returns Whether `arg` is a long option.
|
|
460
|
+
*/
|
|
461
|
+
function isLongOption(arg) {
|
|
462
|
+
return hasLongOptionPrefix(arg) && !arg.includes(EQUAL_CHAR, 3);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Check if `arg` is a long option with value (e.g. `--foo=bar`).
|
|
466
|
+
*
|
|
467
|
+
* @param arg - An argument to check
|
|
468
|
+
* @returns Whether `arg` is a long option.
|
|
469
|
+
*/
|
|
470
|
+
function isLongOptionAndValue(arg) {
|
|
471
|
+
return hasLongOptionPrefix(arg) && arg.includes(EQUAL_CHAR, 3);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Check if `arg` starts with the long option prefix `--` and has more after it, such as `--foo`.
|
|
475
|
+
* The option terminator `--` alone does not.
|
|
476
|
+
*
|
|
477
|
+
* @param arg - An argument to check
|
|
478
|
+
* @returns Whether `arg` has the long option prefix and more after it.
|
|
479
|
+
*/
|
|
480
|
+
function hasLongOptionPrefix(arg) {
|
|
481
|
+
return arg.startsWith(LONG_OPTION_PREFIX) && arg.length > 2;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Internal definitions shared by the modules of `args-tokens`.
|
|
485
|
+
*
|
|
486
|
+
* This module is not an entry point: it is not in the `exports` of `package.json` and `jsr.json`,
|
|
487
|
+
* so what it exports is not part of the public API.
|
|
488
|
+
*/
|
|
489
|
+
/**
|
|
490
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
491
|
+
* @license MIT
|
|
492
|
+
*/
|
|
493
|
+
/**
|
|
494
|
+
* Brand of a `parse` function that has no side effects, set by `string()`, `number()`,
|
|
495
|
+
* `integer()`, `float()`, `choice()` and `positional()` without a parser, so that the resolver may
|
|
496
|
+
* call it with a value that the option was not given, to check the value before suggesting it.
|
|
497
|
+
*
|
|
498
|
+
* The brand is looked up in the global symbol registry with `Symbol.for`, so it stays identical
|
|
499
|
+
* across bundled copies of `args-tokens`, where the resolver and the combinators may come from
|
|
500
|
+
* different copies.
|
|
501
|
+
*/
|
|
502
|
+
const PURE_PARSE = Symbol.for("args-tokens.pureParse");
|
|
503
|
+
/**
|
|
504
|
+
* Entry point of argument options resolver.
|
|
505
|
+
*
|
|
506
|
+
* @module
|
|
507
|
+
*/
|
|
508
|
+
/**
|
|
509
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
510
|
+
* @license MIT
|
|
511
|
+
*/
|
|
512
|
+
/**
|
|
513
|
+
* Machine-readable error codes for {@link ArgsValidationError}.
|
|
514
|
+
*
|
|
515
|
+
* Each code identifies a validation failure category and is also suitable as an
|
|
516
|
+
* i18n resource key for localized rendering.
|
|
517
|
+
*
|
|
518
|
+
* {@link resolveArgs} and `parse()` do not report `unknownOption`. An option that is not in the
|
|
519
|
+
* schema takes the argument after it as its value, unless that argument is an option, and the value
|
|
520
|
+
* is dropped: with no `foo` in the schema, `--foo bar` gives no positional argument.
|
|
521
|
+
*/
|
|
522
|
+
const ArgsValidationErrorKeys = {
|
|
523
|
+
requiredOption: "err:arg:required-option",
|
|
524
|
+
requiredPositional: "err:arg:required-positional",
|
|
525
|
+
invalidType: "err:arg:invalid-type",
|
|
526
|
+
invalidChoice: "err:arg:invalid-choice",
|
|
527
|
+
customParse: "err:arg:custom-parse",
|
|
528
|
+
unknownOption: "err:arg:unknown-option",
|
|
529
|
+
unexpectedValue: "err:arg:unexpected-value",
|
|
530
|
+
missingValue: "err:arg:missing-value",
|
|
531
|
+
conflict: "err:arg:conflict",
|
|
532
|
+
invalidDefault: "err:arg:invalid-default"
|
|
533
|
+
};
|
|
534
|
+
/**
|
|
535
|
+
* Brand that marks {@link ArgsValidationError} instances.
|
|
536
|
+
*
|
|
537
|
+
* The brand is looked up in the global symbol registry with `Symbol.for`, so it stays
|
|
538
|
+
* identical across bundled copies of this module and across realms. It lets
|
|
539
|
+
* {@link isArgsValidationError} recognize errors created by another copy of `args-tokens`,
|
|
540
|
+
* where `instanceof` cannot match.
|
|
541
|
+
*/
|
|
542
|
+
const ARGS_VALIDATION_ERROR_BRAND = Symbol.for("args-tokens.ArgsValidationError");
|
|
543
|
+
/**
|
|
544
|
+
* An error that contains structured metadata for argument validation failures.
|
|
545
|
+
*
|
|
546
|
+
* The `message` remains the English fallback message. Renderers can use `code`
|
|
547
|
+
* and `values` to localize the error, falling back to `message` when localization
|
|
548
|
+
* is unavailable.
|
|
549
|
+
*
|
|
550
|
+
* Each instance carries a non-enumerable brand keyed by
|
|
551
|
+
* `Symbol.for('args-tokens.ArgsValidationError')`, which {@link isArgsValidationError}
|
|
552
|
+
* uses to recognize instances created by another bundled copy of `args-tokens`.
|
|
553
|
+
*/
|
|
554
|
+
var ArgsValidationError = class extends Error {
|
|
555
|
+
/**
|
|
556
|
+
* Machine-readable error code for this validation failure.
|
|
557
|
+
*
|
|
558
|
+
* This code can also be used as an i18n resource key.
|
|
559
|
+
*/
|
|
560
|
+
code;
|
|
561
|
+
/**
|
|
562
|
+
* Interpolation values for `code`.
|
|
563
|
+
*/
|
|
564
|
+
values;
|
|
565
|
+
/**
|
|
566
|
+
* Create an `ArgsValidationError` instance.
|
|
567
|
+
*
|
|
568
|
+
* @param message - fallback error message
|
|
569
|
+
* @param options - structured validation metadata
|
|
570
|
+
*/
|
|
571
|
+
constructor(message, options = {}) {
|
|
572
|
+
super(message, { cause: options.cause });
|
|
573
|
+
this.name = "ArgsValidationError";
|
|
574
|
+
this.code = options.code;
|
|
575
|
+
this.values = options.values ?? {};
|
|
576
|
+
Object.defineProperty(this, ARGS_VALIDATION_ERROR_BRAND, {
|
|
577
|
+
value: true,
|
|
578
|
+
enumerable: false,
|
|
579
|
+
writable: false,
|
|
580
|
+
configurable: false
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
/**
|
|
585
|
+
* Check whether the given value is an {@link ArgsValidationError}.
|
|
586
|
+
*
|
|
587
|
+
* This guard also recognizes errors created by another bundled copy of `args-tokens`
|
|
588
|
+
* (0.29.0 or later), where `instanceof` does not match. Such an error must have an own brand
|
|
589
|
+
* keyed by `Symbol.for('args-tokens.ArgsValidationError')` set to `true`, and a `values` object.
|
|
590
|
+
* The guard does not rely on `error.name`, so subclasses such as {@link ArgResolveError} that
|
|
591
|
+
* override `name` are still recognized.
|
|
592
|
+
*
|
|
593
|
+
* The guard narrows to `ArgsValidationError` only. Across bundled copies,
|
|
594
|
+
* `instanceof ArgResolveError` still does not match.
|
|
595
|
+
*
|
|
596
|
+
* @param error - value to check
|
|
597
|
+
* @returns `true` when the value is an `ArgsValidationError`
|
|
598
|
+
*/
|
|
599
|
+
function isArgsValidationError$1(error) {
|
|
600
|
+
if (error instanceof ArgsValidationError) return true;
|
|
601
|
+
if (typeof error !== "object" || error === null) return false;
|
|
602
|
+
if (!Object.hasOwn(error, ARGS_VALIDATION_ERROR_BRAND) || error[ARGS_VALIDATION_ERROR_BRAND] !== true) return false;
|
|
603
|
+
const values = error.values;
|
|
604
|
+
return typeof values === "object" && values !== null;
|
|
605
|
+
}
|
|
606
|
+
const SKIP_POSITIONAL_DEFAULT = -1;
|
|
607
|
+
const ARG_TYPES = /* @__PURE__ */ new Set([
|
|
608
|
+
"string",
|
|
609
|
+
"boolean",
|
|
610
|
+
"number",
|
|
611
|
+
"enum",
|
|
612
|
+
"positional",
|
|
613
|
+
"custom"
|
|
614
|
+
]);
|
|
615
|
+
/**
|
|
616
|
+
* Resolve command line arguments.
|
|
617
|
+
*
|
|
618
|
+
* @typeParam A - {@link Args | Arguments}, which is an object that defines the command line arguments.
|
|
619
|
+
*
|
|
620
|
+
* @param args - An arguments that contains {@link ArgSchema | arguments schema}.
|
|
621
|
+
* @param tokens - An array of {@link ArgToken | tokens}.
|
|
622
|
+
* @param resolveArgs - An arguments that contains {@link ResolveArgs | resolve arguments}.
|
|
623
|
+
* @returns An object that contains the values of the arguments, positional arguments, rest arguments, {@link AggregateError | validation errors}, and explicit provision status.
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```typescript
|
|
627
|
+
* // passed tokens: --port 3000
|
|
628
|
+
*
|
|
629
|
+
* const { values, explicit } = resolveArgs({
|
|
630
|
+
* port: {
|
|
631
|
+
* type: 'number',
|
|
632
|
+
* default: 8080
|
|
633
|
+
* },
|
|
634
|
+
* host: {
|
|
635
|
+
* type: 'string',
|
|
636
|
+
* default: 'localhost'
|
|
637
|
+
* }
|
|
638
|
+
* }, parsedTokens)
|
|
639
|
+
*
|
|
640
|
+
* values.port // 3000
|
|
641
|
+
* values.host // 'localhost'
|
|
642
|
+
*
|
|
643
|
+
* explicit.port // true (explicitly provided)
|
|
644
|
+
* explicit.host // false (not provided, fallback to default)
|
|
645
|
+
* ```
|
|
646
|
+
*/
|
|
647
|
+
function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKIP_POSITIONAL_DEFAULT, toKebab = false } = {}) {
|
|
648
|
+
const skipPositionalIndex = typeof skipPositional === "number" ? Math.max(skipPositional, SKIP_POSITIONAL_DEFAULT) : SKIP_POSITIONAL_DEFAULT;
|
|
649
|
+
const rest = [];
|
|
650
|
+
const optionTokens = [];
|
|
651
|
+
const positionalTokens = [];
|
|
652
|
+
const argEntries = Object.entries(args);
|
|
653
|
+
let currentLongOption;
|
|
654
|
+
let currentShortOption;
|
|
655
|
+
const expandableShortOptions = [];
|
|
656
|
+
function getOptionName(rawArg, schema) {
|
|
657
|
+
return toKebab || schema.toKebab ? kebabnize(rawArg) : rawArg;
|
|
658
|
+
}
|
|
659
|
+
function checkLongTokenName(option, schema, token) {
|
|
660
|
+
if (token.rawName == void 0 || !hasLongOptionPrefix(token.rawName)) return false;
|
|
661
|
+
return token.name === option || isNegatedToken(token, option, schema);
|
|
662
|
+
}
|
|
663
|
+
function toShortValue() {
|
|
664
|
+
if (expandableShortOptions.length === 0) return;
|
|
665
|
+
else {
|
|
666
|
+
let value = "";
|
|
667
|
+
for (let i = 0; i < expandableShortOptions.length; i++) value += expandableShortOptions[i].name ?? "";
|
|
668
|
+
expandableShortOptions.length = 0;
|
|
669
|
+
return value;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function applyLongOptionValue(value) {
|
|
673
|
+
if (currentLongOption) {
|
|
674
|
+
currentLongOption.value = value;
|
|
675
|
+
optionTokens.push({ ...currentLongOption });
|
|
676
|
+
currentLongOption = void 0;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function applyShortOptionValue(value) {
|
|
680
|
+
if (currentShortOption) {
|
|
681
|
+
currentShortOption.value = value ?? toShortValue();
|
|
682
|
+
optionTokens.push({ ...currentShortOption });
|
|
683
|
+
currentShortOption = void 0;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* analyze phase to resolve value
|
|
688
|
+
* separate tokens into positionals, long and short options, after that resolve values
|
|
689
|
+
*/
|
|
690
|
+
const schemas = Object.values(args);
|
|
691
|
+
const booleanLongOptionNames = /* @__PURE__ */ new Set();
|
|
692
|
+
for (let i = 0; i < argEntries.length; i++) {
|
|
693
|
+
const entry = argEntries[i];
|
|
694
|
+
const rawArg = entry[0];
|
|
695
|
+
const schema = entry[1];
|
|
696
|
+
if (typeof schema.parse !== "function") {
|
|
697
|
+
if (schema.type === "custom") throw new TypeError(`argument '${getOptionName(rawArg, schema)}' should have a 'parse' function`);
|
|
698
|
+
if (schema.type === "enum" && !Array.isArray(schema.choices)) throw new TypeError(`argument '${getOptionName(rawArg, schema)}' should have a 'choices' array`);
|
|
699
|
+
if (!ARG_TYPES.has(schema.type)) throw new Error(`Unsupported argument type '${schema.type}' for option '${getOptionName(rawArg, schema)}'`);
|
|
700
|
+
}
|
|
701
|
+
if (schema.type !== "boolean") continue;
|
|
702
|
+
const arg = toKebab || schema.toKebab ? kebabnize(rawArg) : rawArg;
|
|
703
|
+
booleanLongOptionNames.add(arg);
|
|
704
|
+
if (schema.negatable === true) booleanLongOptionNames.add(`no-${arg}`);
|
|
705
|
+
}
|
|
706
|
+
let terminated = false;
|
|
707
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
708
|
+
const token = tokens[i];
|
|
709
|
+
if (token.kind === "positional") {
|
|
710
|
+
if (terminated) {
|
|
711
|
+
rest.push(token.value);
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
if (currentShortOption) {
|
|
715
|
+
if (schemas.find((schema) => schema.short === currentShortOption.name && schema.type === "boolean") || expandableShortOptions.length > 0) {
|
|
716
|
+
positionalTokens.push({ ...token });
|
|
717
|
+
applyShortOptionValue();
|
|
718
|
+
} else applyShortOptionValue(token.value);
|
|
719
|
+
} else if (currentLongOption) {
|
|
720
|
+
if (booleanLongOptionNames.has(currentLongOption.name)) {
|
|
721
|
+
positionalTokens.push({ ...token });
|
|
722
|
+
applyLongOptionValue();
|
|
723
|
+
} else applyLongOptionValue(token.value);
|
|
724
|
+
} else positionalTokens.push({ ...token });
|
|
725
|
+
} else if (token.kind === "option") {
|
|
726
|
+
if (token.rawName) {
|
|
727
|
+
if (token.rawName.startsWith("--")) {
|
|
728
|
+
applyLongOptionValue();
|
|
729
|
+
applyShortOptionValue();
|
|
730
|
+
if (token.inlineValue) optionTokens.push({ ...token });
|
|
731
|
+
else currentLongOption = { ...token };
|
|
732
|
+
} else if (isShortOption(token.rawName)) {
|
|
733
|
+
if (currentShortOption) {
|
|
734
|
+
if (currentShortOption.index === token.index) {
|
|
735
|
+
if (shortGrouping) {
|
|
736
|
+
currentShortOption.value = token.value;
|
|
737
|
+
optionTokens.push({ ...currentShortOption });
|
|
738
|
+
currentShortOption = { ...token };
|
|
739
|
+
} else expandableShortOptions.push({ ...token });
|
|
740
|
+
} else {
|
|
741
|
+
currentShortOption.value = toShortValue();
|
|
742
|
+
optionTokens.push({ ...currentShortOption });
|
|
743
|
+
currentShortOption = { ...token };
|
|
744
|
+
}
|
|
745
|
+
applyLongOptionValue();
|
|
746
|
+
} else {
|
|
747
|
+
currentShortOption = { ...token };
|
|
748
|
+
applyLongOptionValue();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
} else {
|
|
752
|
+
if (currentShortOption && currentShortOption.index == token.index) {
|
|
753
|
+
const letters = toShortValue();
|
|
754
|
+
currentShortOption.value = letters === void 0 ? token.value : `${letters}${token.inlineValue ? "=" : ""}${token.value ?? ""}`;
|
|
755
|
+
currentShortOption.inlineValue = token.inlineValue;
|
|
756
|
+
optionTokens.push({ ...currentShortOption });
|
|
757
|
+
currentShortOption = void 0;
|
|
758
|
+
}
|
|
759
|
+
applyLongOptionValue();
|
|
760
|
+
}
|
|
761
|
+
} else {
|
|
762
|
+
if (token.kind === "option-terminator") terminated = true;
|
|
763
|
+
applyLongOptionValue();
|
|
764
|
+
applyShortOptionValue();
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* check if the last long or short option is not resolved
|
|
769
|
+
*/
|
|
770
|
+
applyLongOptionValue();
|
|
771
|
+
applyShortOptionValue();
|
|
772
|
+
/**
|
|
773
|
+
* resolve values
|
|
774
|
+
*/
|
|
775
|
+
const values = Object.create(null);
|
|
776
|
+
const errors = [];
|
|
777
|
+
const explicit = Object.create(null);
|
|
778
|
+
const actualInputNames = /* @__PURE__ */ new Map();
|
|
779
|
+
let requiredPositionalsAfter;
|
|
780
|
+
const positionalItemCount = tokens.filter((token) => token.kind === "positional").length;
|
|
781
|
+
function getPositionalSkipIndex() {
|
|
782
|
+
return Math.min(skipPositionalIndex, positionalItemCount);
|
|
783
|
+
}
|
|
784
|
+
function getRequiredPositionalsAfter(rawArg) {
|
|
785
|
+
requiredPositionalsAfter ??= createRequiredPositionalsAfter(argEntries);
|
|
786
|
+
return requiredPositionalsAfter[rawArg] ?? 0;
|
|
787
|
+
}
|
|
788
|
+
let positionalsCount = 0;
|
|
789
|
+
for (let argIndex = 0; argIndex < argEntries.length; argIndex++) {
|
|
790
|
+
const entry = argEntries[argIndex];
|
|
791
|
+
const rawArg = entry[0];
|
|
792
|
+
const schema = entry[1];
|
|
793
|
+
const arg = getOptionName(rawArg, schema);
|
|
794
|
+
explicit[rawArg] = false;
|
|
795
|
+
if (schema.type === "positional") {
|
|
796
|
+
if (skipPositionalIndex > SKIP_POSITIONAL_DEFAULT) while (positionalsCount <= getPositionalSkipIndex()) positionalsCount++;
|
|
797
|
+
if (schema.multiple) {
|
|
798
|
+
const availablePositionals = Math.max(positionalTokens.length - positionalsCount, 0);
|
|
799
|
+
const positionalsToConsume = availablePositionals > 0 ? Math.max(availablePositionals - getRequiredPositionalsAfter(rawArg), 0) : 0;
|
|
800
|
+
if (positionalsToConsume > 0) {
|
|
801
|
+
const endPositionals = positionalsCount + positionalsToConsume;
|
|
802
|
+
if (typeof schema.parse === "function") {
|
|
803
|
+
const parsed = [];
|
|
804
|
+
for (let i = positionalsCount; i < endPositionals; i++) {
|
|
805
|
+
const p = positionalTokens[i];
|
|
806
|
+
const [parsedValue, error] = parseSchemaValue(p.value, rawArg, arg, schema);
|
|
807
|
+
if (error) errors.push(error);
|
|
808
|
+
else parsed.push(parsedValue);
|
|
809
|
+
}
|
|
810
|
+
values[rawArg] = parsed;
|
|
811
|
+
} else {
|
|
812
|
+
const valuesArray = [];
|
|
813
|
+
for (let i = positionalsCount; i < endPositionals; i++) valuesArray.push(positionalTokens[i].value);
|
|
814
|
+
values[rawArg] = valuesArray;
|
|
815
|
+
}
|
|
816
|
+
positionalsCount = endPositionals;
|
|
817
|
+
explicit[rawArg] = true;
|
|
818
|
+
} else if (schema.required) errors.push(createRequireError(rawArg, arg, schema));
|
|
819
|
+
else if (hasDefault(schema)) values[rawArg] = getDefaultValue(schema);
|
|
820
|
+
} else {
|
|
821
|
+
const positional = positionalTokens[positionalsCount];
|
|
822
|
+
if (shouldRequireMissingSinglePositional(schema)) {
|
|
823
|
+
if (positional != null) {
|
|
824
|
+
resolveSinglePositionalValue(values, errors, rawArg, arg, schema, positional);
|
|
825
|
+
explicit[rawArg] = true;
|
|
826
|
+
positionalsCount++;
|
|
827
|
+
} else errors.push(createRequireError(rawArg, arg, schema));
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (positional != null) {
|
|
831
|
+
const requiredPositionals = getRequiredPositionalsAfter(rawArg);
|
|
832
|
+
if (Math.max(positionalTokens.length - positionalsCount, 0) > requiredPositionals) {
|
|
833
|
+
resolveSinglePositionalValue(values, errors, rawArg, arg, schema, positional);
|
|
834
|
+
explicit[rawArg] = true;
|
|
835
|
+
positionalsCount++;
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (hasDefault(schema)) values[rawArg] = schema.default;
|
|
840
|
+
}
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (schema.required) {
|
|
844
|
+
if (!optionTokens.find((token) => {
|
|
845
|
+
return schema.short && token.name === schema.short && token.rawName != void 0 && isShortOption(token.rawName) || checkLongTokenName(arg, schema, token);
|
|
846
|
+
})) {
|
|
847
|
+
errors.push(createRequireError(rawArg, arg, schema));
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
let resolved = false;
|
|
852
|
+
for (let i = 0; i < optionTokens.length; i++) {
|
|
853
|
+
const token = optionTokens[i];
|
|
854
|
+
if (checkLongTokenName(arg, schema, token) || schema.short === token.name && token.rawName != void 0 && isShortOption(token.rawName)) {
|
|
855
|
+
explicit[rawArg] = true;
|
|
856
|
+
const rawName = token.rawName;
|
|
857
|
+
const actualInputName = isShortOption(rawName) ? `-${token.name}` : rawName;
|
|
858
|
+
actualInputNames.set(rawArg, actualInputName);
|
|
859
|
+
const missing = schema.type !== "boolean" && token.value === void 0;
|
|
860
|
+
if (!missing) {
|
|
861
|
+
const invalid = validateRequire(token, rawArg, arg, schema);
|
|
862
|
+
if (invalid) {
|
|
863
|
+
errors.push(invalid);
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const [parsedValue, error] = missing ? [void 0, createMissingValueError(rawArg, arg, schema, findOptionLikeNextArgument(tokens, optionTokens, i, argEntries, toKebab))] : parse$1(token, rawArg, arg, schema);
|
|
868
|
+
if (error) errors.push(error);
|
|
869
|
+
else {
|
|
870
|
+
resolved = true;
|
|
871
|
+
if (schema.multiple) {
|
|
872
|
+
values[rawArg] ||= [];
|
|
873
|
+
values[rawArg].push(parsedValue);
|
|
874
|
+
} else values[rawArg] = parsedValue;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (!resolved && schema.default != null) {
|
|
879
|
+
if (hasDefaultOutsideChoices(schema)) errors.push(createDefaultChoiceError(rawArg, arg, schema));
|
|
880
|
+
else values[rawArg] = getDefaultValue(schema);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const conflictErrors = checkConflicts(args, explicit, toKebab, actualInputNames);
|
|
884
|
+
errors.push(...conflictErrors);
|
|
885
|
+
return {
|
|
886
|
+
values,
|
|
887
|
+
positionals: positionalTokens.map((token) => token.value),
|
|
888
|
+
rest,
|
|
889
|
+
error: errors.length > 0 ? new AggregateError(errors) : void 0,
|
|
890
|
+
explicit
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Check whether an option token is the negated form (`--no-<option>`) of a negatable boolean option.
|
|
895
|
+
*
|
|
896
|
+
* The negated name is always `no-` followed by the full option name, so an option named
|
|
897
|
+
* `no-cache` is negated only by `--no-no-cache`, and `--no-cache` is the option itself.
|
|
898
|
+
*
|
|
899
|
+
* @param token - The option token
|
|
900
|
+
* @param option - The option name used on the command line (after `toKebab` conversion)
|
|
901
|
+
* @param schema - The argument schema
|
|
902
|
+
* @returns Whether the token negates the option.
|
|
903
|
+
*/
|
|
904
|
+
function isNegatedToken(token, option, schema) {
|
|
905
|
+
return schema.type === "boolean" && schema.negatable === true && token.name === `no-${option}`;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Resolve the value of an option token that matches the schema.
|
|
909
|
+
*
|
|
910
|
+
* A token of a non-boolean option always has a value here: `resolveArgs()` reports an option
|
|
911
|
+
* given without a value before calling this function.
|
|
912
|
+
*
|
|
913
|
+
* @param token - The option token
|
|
914
|
+
* @param rawArg - The argument key in the schema
|
|
915
|
+
* @param option - The option name used on the command line
|
|
916
|
+
* @param schema - The argument schema
|
|
917
|
+
* @returns The resolved value, or a validation error
|
|
918
|
+
*/
|
|
919
|
+
function parse$1(token, rawArg, option, schema) {
|
|
920
|
+
if (schema.type === "enum" && schema.choices && !schema.choices.includes(token.value)) return [void 0, createChoiceError(rawArg, option, schema, token.value)];
|
|
921
|
+
if (typeof schema.parse === "function") {
|
|
922
|
+
if (schema.type === "boolean") {
|
|
923
|
+
const [boolValue, error] = resolveBooleanValue(token, rawArg, option, schema);
|
|
924
|
+
if (error) return [void 0, error];
|
|
925
|
+
return parseSchemaValue(String(boolValue), rawArg, option, schema);
|
|
926
|
+
}
|
|
927
|
+
return parseSchemaValue(token.value, rawArg, option, schema);
|
|
928
|
+
}
|
|
929
|
+
switch (schema.type) {
|
|
930
|
+
case "string": return typeof token.value === "string" ? [token.value, void 0] : [void 0, createTypeError(rawArg, option, schema, token.value)];
|
|
931
|
+
case "boolean": return resolveBooleanValue(token, rawArg, option, schema);
|
|
932
|
+
case "number":
|
|
933
|
+
if (typeof token.value !== "string" || !isNumeric(token.value)) return [void 0, createTypeError(rawArg, option, schema, token.value)];
|
|
934
|
+
return [+token.value, void 0];
|
|
935
|
+
case "enum": return [token.value, void 0];
|
|
936
|
+
case "custom": throw new TypeError(`argument '${option}' should have a 'parse' function`);
|
|
937
|
+
default: throw new Error(`Unsupported argument type '${schema.type}' for option '${option}'`);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Resolve the value of a boolean option token.
|
|
942
|
+
*
|
|
943
|
+
* `--flag` is `true` and `--no-flag` is `false`. An explicit inline value (`--flag=true`,
|
|
944
|
+
* `--flag=false`, `-f=false`) is read instead; any other inline value is a type error.
|
|
945
|
+
* The negated form does not take a value, so `--no-flag=<value>` is an error.
|
|
946
|
+
*
|
|
947
|
+
* @param token - The option token
|
|
948
|
+
* @param rawArg - The argument key in the schema
|
|
949
|
+
* @param option - The option name used on the command line
|
|
950
|
+
* @param schema - The argument schema
|
|
951
|
+
* @returns The resolved boolean, or a validation error
|
|
952
|
+
*/
|
|
953
|
+
function resolveBooleanValue(token, rawArg, option, schema) {
|
|
954
|
+
const negated = isNegatedToken(token, option, schema);
|
|
955
|
+
if (!token.inlineValue) return [!negated, void 0];
|
|
956
|
+
if (negated) return [void 0, createUnexpectedValueError(rawArg, option, schema, token)];
|
|
957
|
+
if (token.value !== "true" && token.value !== "false") return [void 0, createTypeError(rawArg, option, schema, token.value)];
|
|
958
|
+
return [token.value === "true", void 0];
|
|
959
|
+
}
|
|
960
|
+
function parseSchemaValue(value, rawArg, option, schema) {
|
|
961
|
+
try {
|
|
962
|
+
return [schema.parse(value), void 0];
|
|
963
|
+
} catch (error) {
|
|
964
|
+
return [void 0, createCustomParseError(error, rawArg, option, schema, value)];
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function createRequireError(rawArg, option, schema) {
|
|
968
|
+
return new ArgResolveError(schema.type === "positional" ? `Positional argument '${option}' is required` : `Optional argument ${createOptionDisplayName(option, schema)} is required`, option, "required", schema, {
|
|
969
|
+
code: schema.type === "positional" ? ArgsValidationErrorKeys.requiredPositional : ArgsValidationErrorKeys.requiredOption,
|
|
970
|
+
values: schema.type === "positional" ? {
|
|
971
|
+
displayName: createArgumentDisplayName(option, schema),
|
|
972
|
+
name: rawArg
|
|
973
|
+
} : {
|
|
974
|
+
displayName: createOptionDisplayName(option, schema),
|
|
975
|
+
name: rawArg
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
function resolveSinglePositionalValue(values, errors, rawArg, option, schema, positional) {
|
|
980
|
+
if (typeof schema.parse === "function") {
|
|
981
|
+
const [parsedValue, error] = parseSchemaValue(positional.value, rawArg, option, schema);
|
|
982
|
+
if (error) errors.push(error);
|
|
983
|
+
else values[rawArg] = parsedValue;
|
|
984
|
+
} else values[rawArg] = positional.value;
|
|
985
|
+
}
|
|
986
|
+
function hasDefault(schema) {
|
|
987
|
+
return schema.default != null;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Get the value that the default of an argument gives.
|
|
991
|
+
*
|
|
992
|
+
* The value of a `multiple` argument is an array, so its default becomes the only element of an
|
|
993
|
+
* array. A default that is an array already, which only untyped code can give, is used as is.
|
|
994
|
+
*
|
|
995
|
+
* @param schema - The argument schema
|
|
996
|
+
* @returns The value that the default gives
|
|
997
|
+
*/
|
|
998
|
+
function getDefaultValue(schema) {
|
|
999
|
+
const value = schema.default;
|
|
1000
|
+
return schema.multiple && !Array.isArray(value) ? [value] : value;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Check whether the default of an `enum` option is not one of its `choices`.
|
|
1004
|
+
*
|
|
1005
|
+
* The default does not go through `parse`, so with a `parse` function of your own, or a `map()`
|
|
1006
|
+
* transform, it is a value that the function returns, which need not be one of the choices, and it
|
|
1007
|
+
* is not checked. The `parse` function of `choice()` returns the value as is, so its default is
|
|
1008
|
+
* checked: among the schemas that the combinators make, it is the only `enum` schema whose `parse`
|
|
1009
|
+
* has the {@link PURE_PARSE} brand. An `enum` schema written by hand with another branded `parse`
|
|
1010
|
+
* would be checked too.
|
|
1011
|
+
*
|
|
1012
|
+
* Only a string, number or boolean default, as the type of `default` allows, is checked against an
|
|
1013
|
+
* array of choices. Anything else, which only untyped code can give, is used as before.
|
|
1014
|
+
*
|
|
1015
|
+
* @param schema - The argument schema
|
|
1016
|
+
* @returns Whether the default is not one of the choices
|
|
1017
|
+
*/
|
|
1018
|
+
function hasDefaultOutsideChoices(schema) {
|
|
1019
|
+
const defaultType = typeof schema.default;
|
|
1020
|
+
if (schema.type !== "enum" || !Array.isArray(schema.choices) || defaultType !== "string" && defaultType !== "number" && defaultType !== "boolean" || schema.choices.includes(schema.default)) return false;
|
|
1021
|
+
const { parse } = schema;
|
|
1022
|
+
if (typeof parse !== "function") return true;
|
|
1023
|
+
try {
|
|
1024
|
+
return hasPureParseBrand(parse);
|
|
1025
|
+
} catch {
|
|
1026
|
+
return false;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
function shouldRequireMissingSinglePositional(schema) {
|
|
1030
|
+
if (schema.required === true) return true;
|
|
1031
|
+
if (schema.required === false) return false;
|
|
1032
|
+
return !hasDefault(schema);
|
|
1033
|
+
}
|
|
1034
|
+
function getRequiredPositionalInputCount(schema) {
|
|
1035
|
+
if (schema.type !== "positional") return 0;
|
|
1036
|
+
if (schema.multiple) return schema.required === true ? 1 : 0;
|
|
1037
|
+
return shouldRequireMissingSinglePositional(schema) ? 1 : 0;
|
|
1038
|
+
}
|
|
1039
|
+
function createRequiredPositionalsAfter(argEntries) {
|
|
1040
|
+
const requiredPositionalsAfter = Object.create(null);
|
|
1041
|
+
let minimumRequiredPositionals = 0;
|
|
1042
|
+
for (let i = argEntries.length - 1; i >= 0; i--) {
|
|
1043
|
+
const [rawArg, schema] = argEntries[i];
|
|
1044
|
+
if (schema.type !== "positional") continue;
|
|
1045
|
+
requiredPositionalsAfter[rawArg] = minimumRequiredPositionals;
|
|
1046
|
+
minimumRequiredPositionals += getRequiredPositionalInputCount(schema);
|
|
1047
|
+
}
|
|
1048
|
+
return requiredPositionalsAfter;
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* An error that occurs when resolving arguments.
|
|
1052
|
+
*
|
|
1053
|
+
* It is not thrown: when an argument is not valid, {@link resolveArgs} returns it in the `errors`
|
|
1054
|
+
* of the `AggregateError` in `error`, and so does `parse()`.
|
|
1055
|
+
*/
|
|
1056
|
+
var ArgResolveError = class extends ArgsValidationError {
|
|
1057
|
+
/**
|
|
1058
|
+
* The name of the argument on the command line, without the leading dashes: the schema key, in
|
|
1059
|
+
* kebab-case with `toKebab`, such as `input-file` for `inputFile`, whether the option is given in
|
|
1060
|
+
* the long form, as a short option or in the negated form. For a conflict, it is the name of the
|
|
1061
|
+
* argument whose `conflicts` names the other one (the first in the schema when both name each
|
|
1062
|
+
* other). `values.name` has the schema key.
|
|
1063
|
+
*/
|
|
1064
|
+
name;
|
|
1065
|
+
schema;
|
|
1066
|
+
type;
|
|
1067
|
+
/**
|
|
1068
|
+
* Create an `ArgResolveError` instance.
|
|
1069
|
+
*
|
|
1070
|
+
* @param message - the error message
|
|
1071
|
+
* @param name - the name of the argument on the command line, in kebab-case with `toKebab`
|
|
1072
|
+
* @param type - the type of the error: 'type', 'required', or 'conflict'
|
|
1073
|
+
* @param schema - the argument schema that caused the error
|
|
1074
|
+
* @param options - structured validation metadata
|
|
1075
|
+
*/
|
|
1076
|
+
constructor(message, name, type, schema, options = {}) {
|
|
1077
|
+
super(message, options);
|
|
1078
|
+
this.name = name;
|
|
1079
|
+
this.type = type;
|
|
1080
|
+
this.schema = schema;
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
function validateRequire(token, rawArg, option, schema) {
|
|
1084
|
+
if (schema.required && schema.type !== "boolean" && !token.value) return createRequireError(rawArg, option, schema);
|
|
1085
|
+
}
|
|
1086
|
+
function isNumeric(str) {
|
|
1087
|
+
return str.trim() !== "" && !isNaN(str);
|
|
1088
|
+
}
|
|
1089
|
+
function createTypeError(rawArg, option, schema, actual, expected = schema.type) {
|
|
1090
|
+
return new ArgResolveError(`Optional argument ${createOptionDisplayName(option, schema)} should be '${expected}'`, option, "type", schema, {
|
|
1091
|
+
code: ArgsValidationErrorKeys.invalidType,
|
|
1092
|
+
values: {
|
|
1093
|
+
displayName: createOptionDisplayName(option, schema),
|
|
1094
|
+
name: rawArg,
|
|
1095
|
+
expected,
|
|
1096
|
+
...actual != null ? { actual } : {}
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Create the error for an option that is given without a value.
|
|
1102
|
+
*
|
|
1103
|
+
* `expected` names what the option takes: its type, or for a `custom` type its `metavar` (for
|
|
1104
|
+
* example `'integer'` for the `integer()` combinator). An `enum` also gets its choices. When the
|
|
1105
|
+
* argument after the option may be a value that starts with `-`, and the option may take it (see
|
|
1106
|
+
* `acceptsSuggestedValue()`), the error suggests the long form with `=`, which passes such a value in
|
|
1107
|
+
* every mode of the tokenizer.
|
|
1108
|
+
*
|
|
1109
|
+
* @param rawArg - The argument key in the schema
|
|
1110
|
+
* @param option - The option name used on the command line
|
|
1111
|
+
* @param schema - The argument schema
|
|
1112
|
+
* @param next - The argument after the option, when it may be a value that starts with `-`
|
|
1113
|
+
* @returns The validation error
|
|
1114
|
+
*/
|
|
1115
|
+
function createMissingValueError(rawArg, option, schema, next) {
|
|
1116
|
+
const displayName = createOptionDisplayName(option, schema);
|
|
1117
|
+
const suggestion = next === void 0 || !acceptsSuggestedValue(schema, next) ? void 0 : `--${option}=${next}`;
|
|
1118
|
+
const hint = suggestion === void 0 ? "" : ` (to pass '${next}' as its value, write '${suggestion}')`;
|
|
1119
|
+
const choices = schema.choices ?? [];
|
|
1120
|
+
return new ArgResolveError(`Optional argument ${displayName} requires a value${hint}`, option, "type", schema, {
|
|
1121
|
+
code: ArgsValidationErrorKeys.missingValue,
|
|
1122
|
+
values: {
|
|
1123
|
+
displayName,
|
|
1124
|
+
name: rawArg,
|
|
1125
|
+
expected: schema.type === "custom" ? schema.metavar ?? schema.type : schema.type,
|
|
1126
|
+
...schema.type === "enum" ? {
|
|
1127
|
+
choices: formatChoices(choices),
|
|
1128
|
+
choiceValues: [...choices]
|
|
1129
|
+
} : {},
|
|
1130
|
+
...suggestion === void 0 ? {} : {
|
|
1131
|
+
next,
|
|
1132
|
+
suggestion
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Find the argument right after an option that is given without a value, when it may be a value
|
|
1139
|
+
* that starts with `-`.
|
|
1140
|
+
*
|
|
1141
|
+
* The option must end its own argument (with `shortGrouping`, `-pv` gives `-p` no value because of
|
|
1142
|
+
* `-v`), and the next argument must be read as options that are not all defined, such as `-5` or
|
|
1143
|
+
* `--foo`. Without `shortGrouping`, a group is read as its first option, which takes the other
|
|
1144
|
+
* letters as its value, so `-p5` is defined when `-p` is. The argument is rebuilt from its tokens.
|
|
1145
|
+
* Without `allowCompatible`, a short option written with a value, such as `-x=1`, `-x=` or `-x-1`,
|
|
1146
|
+
* has a value, and is not suggested.
|
|
1147
|
+
*
|
|
1148
|
+
* @param tokens - The tokens given to `resolveArgs()`
|
|
1149
|
+
* @param optionTokens - The option tokens that `resolveArgs()` resolves
|
|
1150
|
+
* @param position - The position of the option given without a value in `optionTokens`
|
|
1151
|
+
* @param argEntries - The argument schemas
|
|
1152
|
+
* @param toKebab - Whether every option name is converted to kebab-case
|
|
1153
|
+
* @returns The next argument rebuilt from its tokens, or `undefined` when there is nothing to
|
|
1154
|
+
* suggest
|
|
1155
|
+
*/
|
|
1156
|
+
function findOptionLikeNextArgument(tokens, optionTokens, position, argEntries, toKebab) {
|
|
1157
|
+
const token = optionTokens[position];
|
|
1158
|
+
for (let i = position + 1; i < optionTokens.length; i++) if (optionTokens[i].index === token.index) return;
|
|
1159
|
+
const nextArg = tokens.filter((t) => t.index === token.index + 1);
|
|
1160
|
+
if (nextArg.length === 0 || nextArg.some((t) => t.kind !== "option" || t.rawName == null)) return;
|
|
1161
|
+
let text;
|
|
1162
|
+
let known;
|
|
1163
|
+
if (nextArg.length === 1 && nextArg[0].rawName.startsWith("--")) {
|
|
1164
|
+
const [option] = nextArg;
|
|
1165
|
+
text = option.inlineValue ? `${option.rawName}=${option.value}` : option.rawName;
|
|
1166
|
+
known = createKnownOptionNames(argEntries, toKebab).long.has(option.name);
|
|
1167
|
+
} else if (nextArg.every((t) => isShortOption(t.rawName) && t.value === void 0)) {
|
|
1168
|
+
text = `-${nextArg.map((t) => t.name).join("")}`;
|
|
1169
|
+
const { short } = createKnownOptionNames(argEntries, toKebab);
|
|
1170
|
+
known = optionTokens.every((t) => t.index !== token.index + 1 || short.has(t.name));
|
|
1171
|
+
} else return;
|
|
1172
|
+
return known ? void 0 : text;
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Collect the names of the defined options: the long names, with the negated form of negatable
|
|
1176
|
+
* booleans, and the short names.
|
|
1177
|
+
*
|
|
1178
|
+
* @param argEntries - The argument schemas
|
|
1179
|
+
* @param toKebab - Whether every option name is converted to kebab-case
|
|
1180
|
+
* @returns The long and short option names
|
|
1181
|
+
*/
|
|
1182
|
+
function createKnownOptionNames(argEntries, toKebab) {
|
|
1183
|
+
const long = /* @__PURE__ */ new Set();
|
|
1184
|
+
const short = /* @__PURE__ */ new Set();
|
|
1185
|
+
for (const [rawArg, schema] of argEntries) {
|
|
1186
|
+
if (schema.type === "positional") continue;
|
|
1187
|
+
const name = toKebab || schema.toKebab ? kebabnize(rawArg) : rawArg;
|
|
1188
|
+
long.add(name);
|
|
1189
|
+
if (schema.type === "boolean" && schema.negatable === true) long.add(`no-${name}`);
|
|
1190
|
+
if (schema.short) short.add(schema.short);
|
|
1191
|
+
}
|
|
1192
|
+
return {
|
|
1193
|
+
long,
|
|
1194
|
+
short
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Check whether the argument after an option given without a value may be suggested as its value.
|
|
1199
|
+
*
|
|
1200
|
+
* The value of a `number` option must be numeric, as the error expects a number, and the value of
|
|
1201
|
+
* an `enum` option with `choices` must be one of them, which is checked before any `parse`
|
|
1202
|
+
* function. A `parse` function with its own {@link PURE_PARSE} brand is then called with the
|
|
1203
|
+
* value, which must not throw. Any other `parse` function may have side effects, so it is not
|
|
1204
|
+
* called: a `number` option or an `enum` option with `choices` is then checked only as above, and
|
|
1205
|
+
* any other option is not suggested.
|
|
1206
|
+
*
|
|
1207
|
+
* @param schema - The argument schema
|
|
1208
|
+
* @param value - The argument after the option
|
|
1209
|
+
* @returns Whether the value may be suggested
|
|
1210
|
+
*/
|
|
1211
|
+
function acceptsSuggestedValue(schema, value) {
|
|
1212
|
+
if (schema.type === "number" && !isNumeric(value)) return false;
|
|
1213
|
+
if (schema.type === "enum" && schema.choices && !schema.choices.includes(value)) return false;
|
|
1214
|
+
const { parse } = schema;
|
|
1215
|
+
if (typeof parse !== "function") return true;
|
|
1216
|
+
try {
|
|
1217
|
+
if (!hasPureParseBrand(parse)) return schema.type === "number" || schema.type === "enum" && schema.choices !== void 0;
|
|
1218
|
+
parse(value);
|
|
1219
|
+
return true;
|
|
1220
|
+
} catch {
|
|
1221
|
+
return false;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Check whether a `parse` function has its own {@link PURE_PARSE} brand.
|
|
1226
|
+
*
|
|
1227
|
+
* Only an own brand counts, as for `isArgsValidationError()`. Reading it may throw, for example for
|
|
1228
|
+
* a revoked proxy, so call this function in `try`.
|
|
1229
|
+
*
|
|
1230
|
+
* @param parse - The `parse` function of a schema
|
|
1231
|
+
* @returns Whether the function has no side effects
|
|
1232
|
+
*/
|
|
1233
|
+
function hasPureParseBrand(parse) {
|
|
1234
|
+
return Object.hasOwn(parse, PURE_PARSE) && parse[PURE_PARSE] === true;
|
|
1235
|
+
}
|
|
1236
|
+
function createUnexpectedValueError(rawArg, option, schema, token) {
|
|
1237
|
+
const rawName = token.rawName;
|
|
1238
|
+
const displayName = `'${rawName}'`;
|
|
1239
|
+
return new ArgResolveError(`Optional argument ${displayName} does not take a value`, option, "type", schema, {
|
|
1240
|
+
code: ArgsValidationErrorKeys.unexpectedValue,
|
|
1241
|
+
values: {
|
|
1242
|
+
displayName,
|
|
1243
|
+
name: rawArg,
|
|
1244
|
+
rawName,
|
|
1245
|
+
...token.value != null ? { actual: token.value } : {}
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
function createChoiceError(rawArg, option, schema, actual) {
|
|
1250
|
+
const choices = schema.choices ?? [];
|
|
1251
|
+
return new ArgResolveError(`Optional argument ${createOptionDisplayName(option, schema)} should be chosen from '${schema.type}' [${formatChoices(choices)}] values`, option, "type", schema, {
|
|
1252
|
+
code: ArgsValidationErrorKeys.invalidChoice,
|
|
1253
|
+
values: {
|
|
1254
|
+
displayName: createOptionDisplayName(option, schema),
|
|
1255
|
+
name: rawArg,
|
|
1256
|
+
expected: schema.type,
|
|
1257
|
+
choices: formatChoices(choices),
|
|
1258
|
+
choiceValues: [...choices],
|
|
1259
|
+
...actual != null ? { actual } : {}
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Create the error for the default of an `enum` option that is not one of its `choices`.
|
|
1265
|
+
*
|
|
1266
|
+
* Its `values` have the same keys as those of `err:arg:invalid-choice`, with the default as
|
|
1267
|
+
* `actual`.
|
|
1268
|
+
*
|
|
1269
|
+
* @param rawArg - The argument key in the schema
|
|
1270
|
+
* @param option - The option name used on the command line
|
|
1271
|
+
* @param schema - The argument schema
|
|
1272
|
+
* @returns The error
|
|
1273
|
+
*/
|
|
1274
|
+
function createDefaultChoiceError(rawArg, option, schema) {
|
|
1275
|
+
const choices = schema.choices ?? [];
|
|
1276
|
+
const displayName = createOptionDisplayName(option, schema);
|
|
1277
|
+
return new ArgResolveError(`Optional argument ${displayName} has the default ${typeof schema.default === "string" ? JSON.stringify(schema.default) : String(schema.default)}, which is not one of '${schema.type}' [${formatChoices(choices)}] values`, option, "type", schema, {
|
|
1278
|
+
code: ArgsValidationErrorKeys.invalidDefault,
|
|
1279
|
+
values: {
|
|
1280
|
+
displayName,
|
|
1281
|
+
name: rawArg,
|
|
1282
|
+
expected: schema.type,
|
|
1283
|
+
choices: formatChoices(choices),
|
|
1284
|
+
choiceValues: [...choices],
|
|
1285
|
+
actual: schema.default
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
function createCustomParseError(error, rawArg, option, schema, value) {
|
|
1290
|
+
const reused = reuseValidationError(error, rawArg, option, schema, value);
|
|
1291
|
+
if (reused) return reused;
|
|
1292
|
+
const reason = getErrorReason(error);
|
|
1293
|
+
return new ArgsValidationError(reason, {
|
|
1294
|
+
code: ArgsValidationErrorKeys.customParse,
|
|
1295
|
+
values: {
|
|
1296
|
+
displayName: createArgumentDisplayName(option, schema),
|
|
1297
|
+
name: rawArg,
|
|
1298
|
+
reason
|
|
1299
|
+
},
|
|
1300
|
+
cause: error
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Reuse an {@link ArgsValidationError} thrown from a custom `parse` function, filling in missing values.
|
|
1305
|
+
*
|
|
1306
|
+
* @param error - The value thrown from `parse`
|
|
1307
|
+
* @param rawArg - The argument key in the schema
|
|
1308
|
+
* @param option - The option name used on the command line
|
|
1309
|
+
* @param schema - The argument schema
|
|
1310
|
+
* @param value - The raw input value
|
|
1311
|
+
* @returns The same error when it can be reused, otherwise `undefined` so the caller wraps it.
|
|
1312
|
+
* Inspecting or updating the thrown value can throw, for example when its `values` object is frozen
|
|
1313
|
+
* or an accessor throws; such values are not reused, so a custom `parse` cannot make `resolveArgs` throw this way.
|
|
1314
|
+
*/
|
|
1315
|
+
function reuseValidationError(error, rawArg, option, schema, value) {
|
|
1316
|
+
try {
|
|
1317
|
+
if (isArgsValidationError$1(error)) {
|
|
1318
|
+
augmentValidationError(error, rawArg, option, schema, value);
|
|
1319
|
+
return error;
|
|
1320
|
+
}
|
|
1321
|
+
} catch {}
|
|
1322
|
+
}
|
|
1323
|
+
function augmentValidationError(error, rawArg, option, schema, value) {
|
|
1324
|
+
const values = error.values;
|
|
1325
|
+
values.name ??= rawArg;
|
|
1326
|
+
values.displayName ??= createArgumentDisplayName(option, schema);
|
|
1327
|
+
if ((error.code === ArgsValidationErrorKeys.invalidType || error.code === ArgsValidationErrorKeys.invalidChoice) && values.actual == null) values.actual = value;
|
|
1328
|
+
}
|
|
1329
|
+
function getErrorReason(error) {
|
|
1330
|
+
return error instanceof Error ? error.message : String(error);
|
|
1331
|
+
}
|
|
1332
|
+
function createArgumentDisplayName(option, schema) {
|
|
1333
|
+
return schema.type === "positional" ? `'${option}'` : createOptionDisplayName(option, schema);
|
|
1334
|
+
}
|
|
1335
|
+
function createOptionDisplayName(option, schema) {
|
|
1336
|
+
return `'--${option}'${schema.short ? ` or '-${schema.short}'` : ""}`;
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Create the error for two arguments that conflict.
|
|
1340
|
+
*
|
|
1341
|
+
* An option is shown as it was written, such as `'-p'` or `'--no-color'`, and a positional argument
|
|
1342
|
+
* by its name, such as `'file'`, as in the message. The message starts with "Positional argument"
|
|
1343
|
+
* when `schema` is a positional argument, and with "Optional argument" otherwise.
|
|
1344
|
+
*
|
|
1345
|
+
* @param rawArg - The argument key of the argument whose `conflicts` names the other one
|
|
1346
|
+
* @param option - The name of that argument used on the command line
|
|
1347
|
+
* @param schema - The argument schema of that argument
|
|
1348
|
+
* @param actualName - That option as it was written, or the name of that positional argument
|
|
1349
|
+
* @param conflictingArg - The argument key of the other argument
|
|
1350
|
+
* @param conflictingActualName - The other option as it was written, or the name of the other
|
|
1351
|
+
* positional argument
|
|
1352
|
+
* @returns The validation error
|
|
1353
|
+
*/
|
|
1354
|
+
function createConflictError(rawArg, option, schema, actualName, conflictingArg, conflictingActualName) {
|
|
1355
|
+
const displayName = `'${actualName}'`;
|
|
1356
|
+
const conflictDisplayName = `'${conflictingActualName}'`;
|
|
1357
|
+
return new ArgResolveError(`${schema.type === "positional" ? "Positional" : "Optional"} argument ${displayName} conflicts with ${conflictDisplayName}`, option, "conflict", schema, {
|
|
1358
|
+
code: ArgsValidationErrorKeys.conflict,
|
|
1359
|
+
values: {
|
|
1360
|
+
displayName,
|
|
1361
|
+
name: rawArg,
|
|
1362
|
+
conflictDisplayName,
|
|
1363
|
+
conflictName: conflictingArg
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
function checkConflicts(args, explicit, toKebab, actualInputNames) {
|
|
1368
|
+
for (const rawArg in args) {
|
|
1369
|
+
const schema = args[rawArg];
|
|
1370
|
+
if (!explicit[rawArg]) continue;
|
|
1371
|
+
if (!schema.conflicts) continue;
|
|
1372
|
+
const conflicts = Array.isArray(schema.conflicts) ? schema.conflicts : [schema.conflicts];
|
|
1373
|
+
for (let i = 0; i < conflicts.length; i++) {
|
|
1374
|
+
const conflictingArg = conflicts[i];
|
|
1375
|
+
if (!explicit[conflictingArg]) continue;
|
|
1376
|
+
const arg = toKebab || schema.toKebab ? kebabnize(rawArg) : rawArg;
|
|
1377
|
+
const conflictingArgKebab = toKebab || args[conflictingArg]?.toKebab ? kebabnize(conflictingArg) : conflictingArg;
|
|
1378
|
+
return [createConflictError(rawArg, arg, schema, schema.type === "positional" ? arg : actualInputNames.get(rawArg) || `--${arg}`, conflictingArg, args[conflictingArg]?.type === "positional" ? conflictingArgKebab : actualInputNames.get(conflictingArg) || `--${conflictingArgKebab}`)];
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
return [];
|
|
1382
|
+
}
|
|
1383
|
+
//#endregion
|
|
1384
|
+
//#region node_modules/.bun/gunshi@1.0.0-rc.1/node_modules/gunshi/lib/error-pbHb3QAL.js
|
|
1385
|
+
/**
|
|
1386
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
1387
|
+
* @license MIT
|
|
1388
|
+
*/
|
|
1389
|
+
/**
|
|
1390
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
1391
|
+
* @license MIT
|
|
1392
|
+
*/
|
|
1393
|
+
/**
|
|
1394
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
1395
|
+
* @license MIT
|
|
1396
|
+
*/
|
|
1397
|
+
/**
|
|
1398
|
+
* Command not found error resource keys.
|
|
1399
|
+
*/
|
|
1400
|
+
const CommandNotFoundErrorKeys = { notFound: "err:cmd:not-found" };
|
|
1401
|
+
/**
|
|
1402
|
+
* Command resolution error resource keys.
|
|
1403
|
+
*
|
|
1404
|
+
* These errors describe an ambiguity or inconsistency while choosing a command from arguments.
|
|
1405
|
+
* They are kept separate from {@link CommandNotFoundError} so consumers can distinguish a typo
|
|
1406
|
+
* from an input whose command interpretation is not unique.
|
|
1407
|
+
*/
|
|
1408
|
+
const CommandResolutionErrorKeys = {
|
|
1409
|
+
ambiguous: "err:cmd:ambiguous",
|
|
1410
|
+
inconsistentOptions: "err:cmd:inconsistent-options",
|
|
1411
|
+
lazySchemaMismatch: "err:cmd:lazy-schema-mismatch"
|
|
1412
|
+
};
|
|
1413
|
+
/**
|
|
1414
|
+
* Brand that marks {@link CommandNotFoundError} instances.
|
|
1415
|
+
*
|
|
1416
|
+
* The brand is looked up in the global symbol registry with `Symbol.for`, so it stays
|
|
1417
|
+
* identical across bundled copies of gunshi (`gunshi`, `@gunshi/plugin`, `@gunshi/bone`)
|
|
1418
|
+
* and across realms. It lets {@link isCommandNotFoundError} recognize errors created by
|
|
1419
|
+
* another copy, where `instanceof` cannot match.
|
|
1420
|
+
*/
|
|
1421
|
+
const COMMAND_NOT_FOUND_ERROR_BRAND = Symbol.for("gunshi.CommandNotFoundError");
|
|
1422
|
+
const COMMAND_RESOLUTION_ERROR_BRAND = Symbol.for("gunshi.CommandResolutionError");
|
|
1423
|
+
/**
|
|
1424
|
+
* Error raised when a command cannot be resolved.
|
|
1425
|
+
*
|
|
1426
|
+
* Each instance carries a non-enumerable brand keyed by
|
|
1427
|
+
* `Symbol.for('gunshi.CommandNotFoundError')`, so that {@link isCommandNotFoundError}
|
|
1428
|
+
* recognizes it even when it was created by another bundled copy of gunshi.
|
|
1429
|
+
*/
|
|
1430
|
+
var CommandNotFoundError = class extends Error {
|
|
1431
|
+
code;
|
|
1432
|
+
values;
|
|
1433
|
+
commandName;
|
|
1434
|
+
candidates;
|
|
1435
|
+
commandPath;
|
|
1436
|
+
/**
|
|
1437
|
+
* Create a command-not-found error.
|
|
1438
|
+
*
|
|
1439
|
+
* @param message - Fallback error message
|
|
1440
|
+
* @param options - Command-not-found metadata
|
|
1441
|
+
*/
|
|
1442
|
+
constructor(message, options) {
|
|
1443
|
+
super(message, { cause: options.cause });
|
|
1444
|
+
this.name = "CommandNotFoundError";
|
|
1445
|
+
this.code = options.code;
|
|
1446
|
+
this.values = options.values || {};
|
|
1447
|
+
this.commandName = options.commandName;
|
|
1448
|
+
this.candidates = options.candidates || [];
|
|
1449
|
+
this.commandPath = options.commandPath || [];
|
|
1450
|
+
Object.defineProperty(this, COMMAND_NOT_FOUND_ERROR_BRAND, {
|
|
1451
|
+
value: true,
|
|
1452
|
+
enumerable: false,
|
|
1453
|
+
writable: false,
|
|
1454
|
+
configurable: false
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
/**
|
|
1459
|
+
* Error raised when command routing cannot produce one consistent command.
|
|
1460
|
+
*
|
|
1461
|
+
* The own registry brand is the cross-bundle contract. Unlike the legacy command-not-found
|
|
1462
|
+
* guard, the new guard deliberately has no name-based fallback because this error type is new and
|
|
1463
|
+
* must not classify arbitrary errors from older versions.
|
|
1464
|
+
*/
|
|
1465
|
+
var CommandResolutionError = class extends Error {
|
|
1466
|
+
code;
|
|
1467
|
+
values;
|
|
1468
|
+
commandPath;
|
|
1469
|
+
candidatePaths;
|
|
1470
|
+
/**
|
|
1471
|
+
* Create a command resolution error with structured routing metadata.
|
|
1472
|
+
*
|
|
1473
|
+
* @param message - Human-readable fallback message
|
|
1474
|
+
* @param options - Error code and command-routing metadata
|
|
1475
|
+
*/
|
|
1476
|
+
constructor(message, options = {}) {
|
|
1477
|
+
super(message, { cause: options.cause });
|
|
1478
|
+
this.name = "CommandResolutionError";
|
|
1479
|
+
this.code = options.code;
|
|
1480
|
+
this.values = options.values || {};
|
|
1481
|
+
this.commandPath = options.commandPath || [];
|
|
1482
|
+
this.candidatePaths = options.candidatePaths || [];
|
|
1483
|
+
Object.defineProperty(this, COMMAND_RESOLUTION_ERROR_BRAND, {
|
|
1484
|
+
value: true,
|
|
1485
|
+
enumerable: false,
|
|
1486
|
+
writable: false,
|
|
1487
|
+
configurable: false
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
/**
|
|
1492
|
+
* Check whether an error is a {@link CommandNotFoundError}.
|
|
1493
|
+
*
|
|
1494
|
+
* `instanceof` alone is not enough: `@gunshi/plugin` is bundled with its own copy of this
|
|
1495
|
+
* class (`noExternal: ['gunshi/plugin']`), so an error thrown by `gunshi` is never an instance
|
|
1496
|
+
* of the class a plugin imports. Errors from another copy are recognized through the
|
|
1497
|
+
* `Symbol.for('gunshi.CommandNotFoundError')` brand, which does not depend on `error.name`.
|
|
1498
|
+
*
|
|
1499
|
+
* @param error - An unknown error
|
|
1500
|
+
* @returns `true` if the error is a {@link CommandNotFoundError}
|
|
1501
|
+
*/
|
|
1502
|
+
function isCommandNotFoundError(error) {
|
|
1503
|
+
if (error instanceof CommandNotFoundError) return true;
|
|
1504
|
+
if (!isRecord(error) || !hasCommandNotFoundErrorShape(error)) return false;
|
|
1505
|
+
if (Object.hasOwn(error, COMMAND_NOT_FOUND_ERROR_BRAND) && error[COMMAND_NOT_FOUND_ERROR_BRAND] === true) return true;
|
|
1506
|
+
return error instanceof Error && error.name === "CommandNotFoundError";
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Check whether an error is a {@link CommandResolutionError}.
|
|
1510
|
+
*
|
|
1511
|
+
* @param error - An unknown error
|
|
1512
|
+
* @returns `true` when the error has the command-resolution brand and shape
|
|
1513
|
+
*/
|
|
1514
|
+
function isCommandResolutionError(error) {
|
|
1515
|
+
if (error instanceof CommandResolutionError) return true;
|
|
1516
|
+
if (!isRecord(error) || !hasCommandResolutionErrorShape(error)) return false;
|
|
1517
|
+
return Object.hasOwn(error, COMMAND_RESOLUTION_ERROR_BRAND) && error[COMMAND_RESOLUTION_ERROR_BRAND] === true;
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Check whether an error is an {@link ArgsValidationError}.
|
|
1521
|
+
*
|
|
1522
|
+
* Prefer this over the `args-tokens` guard of the same name. Both recognize errors from another
|
|
1523
|
+
* bundled copy through the `Symbol.for('args-tokens.ArgsValidationError')` brand that
|
|
1524
|
+
* `args-tokens` 0.29.0 or later sets, including subclasses such as `ArgResolveError` that
|
|
1525
|
+
* override `name` with the argument name. This guard additionally:
|
|
1526
|
+
*
|
|
1527
|
+
* - recognizes direct `ArgsValidationError` instances from copies bundling `args-tokens`
|
|
1528
|
+
* older than 0.29.0, which do not set the brand
|
|
1529
|
+
* - checks that `code` and `values` of an error from another copy have the expected types
|
|
1530
|
+
*
|
|
1531
|
+
* The guard narrows only to {@link ArgsValidationError}: across copies,
|
|
1532
|
+
* `instanceof ArgResolveError` still fails, so do not rely on `type` or `schema` for such errors.
|
|
1533
|
+
*
|
|
1534
|
+
* @param error - An unknown error
|
|
1535
|
+
* @returns `true` if the error is an {@link ArgsValidationError}
|
|
1536
|
+
*/
|
|
1537
|
+
function isArgsValidationError(error) {
|
|
1538
|
+
if (error instanceof ArgsValidationError) return true;
|
|
1539
|
+
if (!isRecord(error) || !hasArgsValidationErrorShape(error)) return false;
|
|
1540
|
+
if (isArgsValidationError$1(error)) return true;
|
|
1541
|
+
return error instanceof Error && error.name === "ArgsValidationError";
|
|
1542
|
+
}
|
|
1543
|
+
function isRecord(value) {
|
|
1544
|
+
return typeof value === "object" && value !== null;
|
|
1545
|
+
}
|
|
1546
|
+
function isStringArray(value) {
|
|
1547
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Check whether `code` is an own or inherited property that is a string or `undefined`.
|
|
1551
|
+
*
|
|
1552
|
+
* Constructors always assign `code`, even when the option is omitted.
|
|
1553
|
+
*
|
|
1554
|
+
* @param error - An error from another copy
|
|
1555
|
+
* @returns `true` if `code` has the expected type
|
|
1556
|
+
*/
|
|
1557
|
+
function hasOptionalStringCode(error) {
|
|
1558
|
+
return "code" in error && (error.code === void 0 || typeof error.code === "string");
|
|
1559
|
+
}
|
|
1560
|
+
function hasCommandNotFoundErrorShape(error) {
|
|
1561
|
+
return hasOptionalStringCode(error) && isRecord(error.values) && typeof error.commandName === "string" && isStringArray(error.candidates) && isStringArray(error.commandPath);
|
|
1562
|
+
}
|
|
1563
|
+
function hasCommandResolutionErrorShape(error) {
|
|
1564
|
+
return hasOptionalStringCode(error) && isRecord(error.values) && isStringArray(error.commandPath) && Array.isArray(error.candidatePaths) && error.candidatePaths.every((path) => isStringArray(path));
|
|
1565
|
+
}
|
|
1566
|
+
function hasArgsValidationErrorShape(error) {
|
|
1567
|
+
return hasOptionalStringCode(error) && isRecord(error.values);
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Check whether validation errors should be handled before version, help, or command execution.
|
|
1571
|
+
*
|
|
1572
|
+
* @param error - An aggregate validation error
|
|
1573
|
+
* @returns `true` if the validation error must be handled with priority
|
|
1574
|
+
*/
|
|
1575
|
+
function hasPriorityValidationError(error) {
|
|
1576
|
+
return error?.errors.some((error) => isCommandNotFoundError(error) || isCommandResolutionError(error) || isArgsValidationError(error) && error.code === ArgsValidationErrorKeys.unknownOption) ?? false;
|
|
1577
|
+
}
|
|
1578
|
+
//#endregion
|
|
1579
|
+
//#region node_modules/.bun/gunshi@1.0.0-rc.1/node_modules/gunshi/lib/core-EDPnjwxe.js
|
|
1580
|
+
/**
|
|
1581
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
1582
|
+
* @license MIT
|
|
1583
|
+
*/
|
|
1584
|
+
const EMPTY_RENDERER = () => Promise.resolve("");
|
|
1585
|
+
/**
|
|
1586
|
+
* Factory function for creating a decorators manager.
|
|
1587
|
+
*
|
|
1588
|
+
* @returns A new decorators manager instance
|
|
1589
|
+
*/
|
|
1590
|
+
function createDecorators() {
|
|
1591
|
+
/**
|
|
1592
|
+
* private states
|
|
1593
|
+
*/
|
|
1594
|
+
const headerDecorators = [];
|
|
1595
|
+
const usageDecorators = [];
|
|
1596
|
+
const validationDecorators = [];
|
|
1597
|
+
const commandDecorators = [];
|
|
1598
|
+
/**
|
|
1599
|
+
* helper function for building renderers
|
|
1600
|
+
*/
|
|
1601
|
+
function buildRenderer(decorators, defaultRenderer) {
|
|
1602
|
+
if (decorators.length === 0) return defaultRenderer;
|
|
1603
|
+
let renderer = defaultRenderer;
|
|
1604
|
+
for (const decorator of decorators) {
|
|
1605
|
+
const previousRenderer = renderer;
|
|
1606
|
+
renderer = (ctx) => decorator(previousRenderer, ctx);
|
|
1607
|
+
}
|
|
1608
|
+
return renderer;
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* public interfaces
|
|
1612
|
+
*/
|
|
1613
|
+
return Object.freeze({
|
|
1614
|
+
addHeaderDecorator(decorator) {
|
|
1615
|
+
headerDecorators.push(decorator);
|
|
1616
|
+
},
|
|
1617
|
+
addUsageDecorator(decorator) {
|
|
1618
|
+
usageDecorators.push(decorator);
|
|
1619
|
+
},
|
|
1620
|
+
addValidationErrorsDecorator(decorator) {
|
|
1621
|
+
validationDecorators.push(decorator);
|
|
1622
|
+
},
|
|
1623
|
+
addCommandDecorator(decorator) {
|
|
1624
|
+
commandDecorators.push(decorator);
|
|
1625
|
+
},
|
|
1626
|
+
get commandDecorators() {
|
|
1627
|
+
return [...commandDecorators];
|
|
1628
|
+
},
|
|
1629
|
+
getHeaderRenderer() {
|
|
1630
|
+
return buildRenderer(headerDecorators, EMPTY_RENDERER);
|
|
1631
|
+
},
|
|
1632
|
+
getUsageRenderer() {
|
|
1633
|
+
return buildRenderer(usageDecorators, EMPTY_RENDERER);
|
|
1634
|
+
},
|
|
1635
|
+
getValidationErrorsRenderer() {
|
|
1636
|
+
if (validationDecorators.length === 0) return EMPTY_RENDERER;
|
|
1637
|
+
let renderer = EMPTY_RENDERER;
|
|
1638
|
+
for (const decorator of validationDecorators) {
|
|
1639
|
+
const previousRenderer = renderer;
|
|
1640
|
+
renderer = (ctx, error) => decorator(previousRenderer, ctx, error);
|
|
1641
|
+
}
|
|
1642
|
+
return renderer;
|
|
1643
|
+
}
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
1648
|
+
* @license MIT
|
|
1649
|
+
*/
|
|
1650
|
+
/**
|
|
1651
|
+
* Factory function for creating a plugin context.
|
|
1652
|
+
*
|
|
1653
|
+
* @typeParam G - A type extending {@linkcode GunshiParams} to specify the shape of command parameters.
|
|
1654
|
+
*
|
|
1655
|
+
* @param decorators - A {@linkcode Decorators} instance.
|
|
1656
|
+
* @param initialSubCommands - Initial sub commands map.
|
|
1657
|
+
* @returns A new {@linkcode PluginContext} instance.
|
|
1658
|
+
*/
|
|
1659
|
+
function createPluginContext(decorators, initialSubCommands) {
|
|
1660
|
+
/**
|
|
1661
|
+
* private states
|
|
1662
|
+
*/
|
|
1663
|
+
const globalOptions = /* @__PURE__ */ new Map();
|
|
1664
|
+
const subCommands = new Map(initialSubCommands || []);
|
|
1665
|
+
/**
|
|
1666
|
+
* public interfaces
|
|
1667
|
+
*/
|
|
1668
|
+
return Object.freeze({
|
|
1669
|
+
get globalOptions() {
|
|
1670
|
+
return new Map(globalOptions);
|
|
1671
|
+
},
|
|
1672
|
+
addGlobalOption(name, schema) {
|
|
1673
|
+
if (!name) throw new Error("Option name must be a non-empty string");
|
|
1674
|
+
if (globalOptions.has(name)) throw new Error(`Global option '${name}' is already registered`);
|
|
1675
|
+
/**
|
|
1676
|
+
* NOTE(kazupon): a short name belongs to one option. The global options of a CLI come from
|
|
1677
|
+
* plugins that know nothing of each other, so two of them may reach for the same letter, and
|
|
1678
|
+
* the parser would then match both and give neither the value that was typed. The one that
|
|
1679
|
+
* asked first keeps the letter, and the other keeps its long name — the same way a global
|
|
1680
|
+
* option gives its short name up to an argument of the command that claims it.
|
|
1681
|
+
*/
|
|
1682
|
+
const taken = schema.short && findGlobalOptionByShort(globalOptions, schema.short);
|
|
1683
|
+
if (taken) {
|
|
1684
|
+
console.warn(`Short name '-${schema.short}' of the global option '${name}' is already used by '${taken}'. Global option '${name}' is registered without it.`);
|
|
1685
|
+
globalOptions.set(name, {
|
|
1686
|
+
...schema,
|
|
1687
|
+
short: void 0
|
|
1688
|
+
});
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
globalOptions.set(name, schema);
|
|
1692
|
+
},
|
|
1693
|
+
get subCommands() {
|
|
1694
|
+
return new Map(subCommands);
|
|
1695
|
+
},
|
|
1696
|
+
addCommand(name, command) {
|
|
1697
|
+
if (!name) throw new Error("Command name must be a non-empty string");
|
|
1698
|
+
if (subCommands.has(name)) throw new Error(`Command '${name}' is already registered`);
|
|
1699
|
+
subCommands.set(name, command);
|
|
1700
|
+
},
|
|
1701
|
+
hasCommand(name) {
|
|
1702
|
+
return subCommands.has(name);
|
|
1703
|
+
},
|
|
1704
|
+
decorateHeaderRenderer(decorator) {
|
|
1705
|
+
decorators.addHeaderDecorator(decorator);
|
|
1706
|
+
},
|
|
1707
|
+
decorateUsageRenderer(decorator) {
|
|
1708
|
+
decorators.addUsageDecorator(decorator);
|
|
1709
|
+
},
|
|
1710
|
+
decorateValidationErrorsRenderer(decorator) {
|
|
1711
|
+
decorators.addValidationErrorsDecorator(decorator);
|
|
1712
|
+
},
|
|
1713
|
+
decorateCommand(decorator) {
|
|
1714
|
+
decorators.addCommandDecorator(decorator);
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Find the global option that already uses a short name.
|
|
1720
|
+
*
|
|
1721
|
+
* @param globalOptions - The global options registered so far
|
|
1722
|
+
* @param short - A short name, without the leading dash
|
|
1723
|
+
* @returns The name of the global option that uses it, or `undefined`
|
|
1724
|
+
*/
|
|
1725
|
+
function findGlobalOptionByShort(globalOptions, short) {
|
|
1726
|
+
for (const [name, schema] of globalOptions) if (schema.short === short) return name;
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
1730
|
+
* @license MIT
|
|
1731
|
+
*/
|
|
1732
|
+
/**
|
|
1733
|
+
* Resolve plugin dependencies using topological sort
|
|
1734
|
+
*
|
|
1735
|
+
* @param plugins - Array of plugins to resolve
|
|
1736
|
+
* @returns Array of plugins sorted by dependencies
|
|
1737
|
+
*/
|
|
1738
|
+
function resolveDependencies(plugins) {
|
|
1739
|
+
const sorted = [];
|
|
1740
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1741
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1742
|
+
const pluginMap = /* @__PURE__ */ new Map();
|
|
1743
|
+
for (const plugin of plugins) {
|
|
1744
|
+
if (typeof plugin.id !== "string" || plugin.id.length === 0) throw new Error("Plugin id must be a non-empty string");
|
|
1745
|
+
if (pluginMap.has(plugin.id)) console.warn(`Duplicate plugin id detected: \`${plugin.id}\``);
|
|
1746
|
+
pluginMap.set(plugin.id, plugin);
|
|
1747
|
+
}
|
|
1748
|
+
function visit(plugin) {
|
|
1749
|
+
if (typeof plugin.id !== "string" || plugin.id.length === 0) throw new Error("Plugin id must be a non-empty string");
|
|
1750
|
+
if (visited.has(plugin.id)) return;
|
|
1751
|
+
if (visiting.has(plugin.id)) throw new Error(`Circular dependency detected: \`${[...visiting].join(` -> `) + " -> " + plugin.id}\``);
|
|
1752
|
+
visiting.add(plugin.id);
|
|
1753
|
+
const deps = plugin.dependencies || [];
|
|
1754
|
+
for (const dep of deps) {
|
|
1755
|
+
const depId = typeof dep === "string" ? dep : dep.id;
|
|
1756
|
+
const isOptional = typeof dep === "string" ? false : dep.optional || false;
|
|
1757
|
+
const depPlugin = pluginMap.get(depId);
|
|
1758
|
+
if (!depPlugin && !isOptional) throw new Error(`Missing required dependency: \`${depId}\` on \`${plugin.id}\``);
|
|
1759
|
+
if (depPlugin) visit(depPlugin);
|
|
1760
|
+
}
|
|
1761
|
+
visiting.delete(plugin.id);
|
|
1762
|
+
visited.add(plugin.id);
|
|
1763
|
+
sorted.push(plugin);
|
|
1764
|
+
}
|
|
1765
|
+
for (const plugin of plugins) visit(plugin);
|
|
1766
|
+
return sorted;
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
1770
|
+
* @license MIT
|
|
1771
|
+
*/
|
|
1772
|
+
/**
|
|
1773
|
+
* Resolve the arguments of a command, the way gunshi parses and renders them.
|
|
1774
|
+
*
|
|
1775
|
+
* The global options belong to no command definition, so they are merged into the arguments of the
|
|
1776
|
+
* command that runs. An argument of the command shadows the global option of the same name, and it
|
|
1777
|
+
* shadows the same way by short name: a global option gives up its short name to an argument of the
|
|
1778
|
+
* command that claims the same letter, and keeps its long name.
|
|
1779
|
+
*
|
|
1780
|
+
* NOTE(kazupon): this is not exported from the entry points. `@gunshi/plugin-completion` completes
|
|
1781
|
+
* the arguments the same way with the copy in `@gunshi/shared`, and the tests of that copy pin it
|
|
1782
|
+
* to the results of this one, so a change here has to be made there too.
|
|
1783
|
+
*
|
|
1784
|
+
* @param globalOptions - The global options that plugins registered with `addGlobalOption`
|
|
1785
|
+
* @param args - The arguments that the command declares
|
|
1786
|
+
* @returns The merged arguments
|
|
1787
|
+
*/
|
|
1788
|
+
function resolveCommandArgs(globalOptions, args) {
|
|
1789
|
+
return Object.assign(create(), resolveGlobalOptions(globalOptions, args), args);
|
|
1790
|
+
}
|
|
1791
|
+
function resolveGlobalOptions(globalOptions, args) {
|
|
1792
|
+
if (!globalOptions) return;
|
|
1793
|
+
const shortNames = /* @__PURE__ */ new Set();
|
|
1794
|
+
for (const schema of Object.values(args || {})) if (schema.type !== "positional" && schema.short) shortNames.add(schema.short);
|
|
1795
|
+
const resolved = create();
|
|
1796
|
+
for (const [name, schema] of globalOptions)
|
|
1797
|
+
/**
|
|
1798
|
+
* NOTE(kazupon): a copy, because the schema is the one that the plugin registered, which every
|
|
1799
|
+
* command of the CLI shares. Only this command gives up the short name.
|
|
1800
|
+
*/
|
|
1801
|
+
resolved[name] = schema.short && shortNames.has(schema.short) ? {
|
|
1802
|
+
...schema,
|
|
1803
|
+
short: void 0
|
|
1804
|
+
} : schema;
|
|
1805
|
+
return resolved;
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
1809
|
+
* @license MIT
|
|
1810
|
+
*/
|
|
1811
|
+
/**
|
|
1812
|
+
* Run the command.
|
|
1813
|
+
*
|
|
1814
|
+
* @typeParam G - A type extending {@linkcode GunshiParams} to specify the shape of command and cli options.
|
|
1815
|
+
*
|
|
1816
|
+
* @param argv - Command line arguments
|
|
1817
|
+
* @param entry - A {@link Command | entry command}, an {@link CommandRunner | inline command runner}, or a {@link LazyCommand | lazily-loaded command}
|
|
1818
|
+
* @param options - A {@link CliOptions | CLI options}
|
|
1819
|
+
* @param plugins - An array of {@link Plugin | plugins} to be applied
|
|
1820
|
+
* @param usageOnly - Whether to render the usage of the command instead of running it, which is what `generate` of `gunshi/generator` asks for
|
|
1821
|
+
* @returns A rendered usage or undefined. if you will use {@link CliOptions.usageSilent} option, it will return rendered usage string.
|
|
1822
|
+
*/
|
|
1823
|
+
async function cliCore(argv, entry, options, plugins, usageOnly = false) {
|
|
1824
|
+
const decorators = createDecorators();
|
|
1825
|
+
/**
|
|
1826
|
+
* NOTE(kazupon): the decorator goes on before any plugin, which makes it the outermost one, so
|
|
1827
|
+
* that the usage is rendered whatever the command declares and the runner is never reached.
|
|
1828
|
+
*/
|
|
1829
|
+
if (usageOnly) decorators.addCommandDecorator(usageOnlyDecorator());
|
|
1830
|
+
const entryCommand = createEntryCommand(entry);
|
|
1831
|
+
const initialSubCommands = createInitialSubCommands(options, entryCommand);
|
|
1832
|
+
const pluginContext = createPluginContext(decorators, initialSubCommands);
|
|
1833
|
+
const resolvedPlugins = await applyPlugins(pluginContext, [...plugins, ...options.plugins || []]);
|
|
1834
|
+
const cliOptions = normalizeCliOptions(options, decorators, pluginContext, entryCommand);
|
|
1835
|
+
/**
|
|
1836
|
+
* NOTE(kazupon): a CLI whose user declares no sub-commands has no command tree of its own,
|
|
1837
|
+
* so a positional argument must not become a command name just because a plugin added a command.
|
|
1838
|
+
* The user can still opt out with an explicit `fallbackToEntry: false`.
|
|
1839
|
+
*/
|
|
1840
|
+
if (initialSubCommands.size === 0 && cliOptions.subCommands.size > 0 && options.fallbackToEntry === void 0) cliOptions.fallbackToEntry = true;
|
|
1841
|
+
const tokens = parseArgs(argv);
|
|
1842
|
+
const selection = resolveCommandSelection(tokens, entry, cliOptions, pluginContext.globalOptions);
|
|
1843
|
+
const resolved = selection.context;
|
|
1844
|
+
const { commandName: name, command, callMode, commandPath, depth, levelSubCommands } = resolved;
|
|
1845
|
+
let targetCommand = command;
|
|
1846
|
+
let targetCommandName = name;
|
|
1847
|
+
let targetCallMode = callMode;
|
|
1848
|
+
let targetCommandPath = commandPath;
|
|
1849
|
+
let targetDepth = depth;
|
|
1850
|
+
let targetOmitted = resolved.omitted;
|
|
1851
|
+
let targetLevelSubCommands = levelSubCommands;
|
|
1852
|
+
const additionalValidationErrors = selection.error ? [selection.error] : [];
|
|
1853
|
+
if (!targetCommand) {
|
|
1854
|
+
if (!resolved.parentCommand || !resolved.unresolvedCommandName) throw new Error(`Command not found: ${name || ""}`);
|
|
1855
|
+
targetCommand = resolved.parentCommand;
|
|
1856
|
+
targetCommandName = resolved.parentCommandName;
|
|
1857
|
+
targetCommandPath = resolved.parentCommandPath || [];
|
|
1858
|
+
targetDepth = targetCommandPath.length;
|
|
1859
|
+
targetCallMode = targetDepth > 0 ? "subCommand" : "entry";
|
|
1860
|
+
targetOmitted = false;
|
|
1861
|
+
targetLevelSubCommands = resolved.parentSubCommands;
|
|
1862
|
+
additionalValidationErrors.push(createCommandNotFoundError(resolved));
|
|
1863
|
+
}
|
|
1864
|
+
const routingOptions = Object.assign(create(), cliOptions, { subCommands: new Map(cliOptions.subCommands) });
|
|
1865
|
+
if (targetLevelSubCommands) cliOptions.subCommands = targetLevelSubCommands;
|
|
1866
|
+
const resolvedCommand = isLazyCommand(targetCommand) ? await resolveLazyCommand(targetCommand, targetCommandName, true) : targetCommand;
|
|
1867
|
+
if (selection.error == null && isLazyCommand(targetCommand)) {
|
|
1868
|
+
const loaded = resolveCommandTreeFromPositionals(getRoutingPositionals(tokens, pluginContext.globalOptions, resolvedCommand), entry, routingOptions);
|
|
1869
|
+
if (!sameCommandPath(loaded.commandPath, targetCommandPath)) additionalValidationErrors.push(createLazySchemaMismatchError(targetCommandPath, loaded.commandPath));
|
|
1870
|
+
}
|
|
1871
|
+
const commandArgs = getCommandArgs(resolvedCommand);
|
|
1872
|
+
const args = resolveCommandArgs(pluginContext.globalOptions, commandArgs);
|
|
1873
|
+
/**
|
|
1874
|
+
* NOTE(kazupon): a plugin cannot tell on its own whether the global option it registered survived
|
|
1875
|
+
* the merge, so the core says which ones are in effect for this command (#745).
|
|
1876
|
+
*/
|
|
1877
|
+
cliOptions.globalOptions = resolveEffectiveGlobalOptions(pluginContext.globalOptions, commandArgs, args);
|
|
1878
|
+
const skipPositional = resolveSkipPositional(tokens, pluginContext.globalOptions, resolvedCommand, targetDepth);
|
|
1879
|
+
const diagnostic = selection.diagnostic === true;
|
|
1880
|
+
const { explicit, values, positionals, rest, error } = diagnostic ? {
|
|
1881
|
+
explicit: create(),
|
|
1882
|
+
values: create(),
|
|
1883
|
+
positionals: [],
|
|
1884
|
+
rest: [],
|
|
1885
|
+
error: void 0
|
|
1886
|
+
} : resolveArgs(args, tokens, {
|
|
1887
|
+
shortGrouping: true,
|
|
1888
|
+
toKebab: resolvedCommand.toKebab,
|
|
1889
|
+
skipPositional
|
|
1890
|
+
});
|
|
1891
|
+
const validationError = mergeValidationErrors(error, [...additionalValidationErrors, ...cliOptions.strict && !diagnostic ? createUnknownOptionErrors(findUnknownOptions(args, tokens, { toKebab: resolvedCommand.toKebab })) : []]);
|
|
1892
|
+
return await executeCommand(resolvedCommand, await createCommandContext({
|
|
1893
|
+
args,
|
|
1894
|
+
explicit,
|
|
1895
|
+
values,
|
|
1896
|
+
positionals,
|
|
1897
|
+
rest,
|
|
1898
|
+
argv,
|
|
1899
|
+
tokens,
|
|
1900
|
+
omitted: targetOmitted,
|
|
1901
|
+
callMode: targetCallMode,
|
|
1902
|
+
commandPath: targetCommandPath,
|
|
1903
|
+
command: resolvedCommand,
|
|
1904
|
+
extensions: getPluginExtensions(resolvedPlugins),
|
|
1905
|
+
validationError,
|
|
1906
|
+
cliOptions
|
|
1907
|
+
}), decorators.commandDecorators);
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Create the decorator that renders the usage of the command instead of running it.
|
|
1911
|
+
*
|
|
1912
|
+
* NOTE(kazupon): `generate()` of `gunshi/generator` asks the core for a usage. It used to ask by
|
|
1913
|
+
* adding `-h` to the arguments, which made the answer depend on what the command calls its own
|
|
1914
|
+
* arguments: a command that declares one named `help` takes the schema of the global option, and
|
|
1915
|
+
* the usage was never rendered. A decorator is not an argument, so no command can take it away.
|
|
1916
|
+
*
|
|
1917
|
+
* @returns A {@linkcode CommandDecorator | command decorator} that renders the usage
|
|
1918
|
+
*/
|
|
1919
|
+
function usageOnlyDecorator() {
|
|
1920
|
+
return () => async (ctx) => {
|
|
1921
|
+
if (hasPriorityValidationError(ctx.validationError)) throw ctx.validationError;
|
|
1922
|
+
const buf = [];
|
|
1923
|
+
if (ctx.env.renderHeader != null) {
|
|
1924
|
+
const header = await ctx.env.renderHeader(ctx);
|
|
1925
|
+
if (header) buf.push(header);
|
|
1926
|
+
}
|
|
1927
|
+
if (ctx.env.renderUsage == null) return;
|
|
1928
|
+
const usage = await ctx.env.renderUsage(ctx);
|
|
1929
|
+
if (!usage) return;
|
|
1930
|
+
buf.push(usage);
|
|
1931
|
+
return buf.join("\n");
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
async function applyPlugins(pluginContext, plugins) {
|
|
1935
|
+
const sortedPlugins = resolveDependencies(plugins);
|
|
1936
|
+
for (const plugin of sortedPlugins) try {
|
|
1937
|
+
/**
|
|
1938
|
+
* NOTE(kazupon):
|
|
1939
|
+
* strictly `Args` are not required for plugin installation.
|
|
1940
|
+
* because the strictly `Args` required by each plugin are unknown,
|
|
1941
|
+
* and the plugin side can not know what the user will specify.
|
|
1942
|
+
*/
|
|
1943
|
+
await plugin(pluginContext);
|
|
1944
|
+
} catch (error) {
|
|
1945
|
+
/**
|
|
1946
|
+
* NOTE(kazupon): a plugin that cannot be installed leaves the CLI without the options, the
|
|
1947
|
+
* commands and the decorators it was written to add, and the plugins behind it are not
|
|
1948
|
+
* installed either, while their context extensions are handed to the command all the same.
|
|
1949
|
+
* `resolveDependencies()` already ends the run for a circular or a missing dependency, so a
|
|
1950
|
+
* `setup` that throws ends it too, naming the plugin rather than leaving the failure to
|
|
1951
|
+
* surface somewhere else.
|
|
1952
|
+
*/
|
|
1953
|
+
throw new Error(`Failed to install the plugin \`${plugin.id}\``, { cause: error });
|
|
1954
|
+
}
|
|
1955
|
+
return sortedPlugins;
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Resolve the global options that are in effect for the command being executed.
|
|
1959
|
+
*
|
|
1960
|
+
* A global option is in effect unless the command declares an argument of its own under the same
|
|
1961
|
+
* name, which replaces it. The schema is taken from the merged arguments, so it is the one in effect.
|
|
1962
|
+
*
|
|
1963
|
+
* @param globalOptions - The global options that plugins registered with `addGlobalOption`
|
|
1964
|
+
* @param commandArgs - The arguments that the command declares
|
|
1965
|
+
* @param args - The merged arguments
|
|
1966
|
+
* @returns The global options in effect, keyed by name
|
|
1967
|
+
*/
|
|
1968
|
+
function resolveEffectiveGlobalOptions(globalOptions, commandArgs, args) {
|
|
1969
|
+
const effective = /* @__PURE__ */ new Map();
|
|
1970
|
+
for (const name of globalOptions.keys()) if (!Object.hasOwn(commandArgs, name)) effective.set(name, args[name]);
|
|
1971
|
+
return effective;
|
|
1972
|
+
}
|
|
1973
|
+
function getCommandArgs(cmd) {
|
|
1974
|
+
if (isLazyCommand(cmd)) return cmd.args || create();
|
|
1975
|
+
else if (typeof cmd === "object") return cmd.args || create();
|
|
1976
|
+
else return create();
|
|
1977
|
+
}
|
|
1978
|
+
const NEGATABLE_OPTION_PREFIX = "no-";
|
|
1979
|
+
function findUnknownOptions(args, tokens, options) {
|
|
1980
|
+
const knownLongOptions = /* @__PURE__ */ new Set();
|
|
1981
|
+
const knownShortOptions = /* @__PURE__ */ new Set();
|
|
1982
|
+
const knownNegatableOptions = /* @__PURE__ */ new Set();
|
|
1983
|
+
const knownLongOptionCandidates = /* @__PURE__ */ new Set();
|
|
1984
|
+
const longOptionCandidates = [];
|
|
1985
|
+
function addLongOptionCandidate(name) {
|
|
1986
|
+
const candidate = `--${name}`;
|
|
1987
|
+
if (knownLongOptionCandidates.has(candidate)) return;
|
|
1988
|
+
knownLongOptionCandidates.add(candidate);
|
|
1989
|
+
longOptionCandidates.push(candidate);
|
|
1990
|
+
}
|
|
1991
|
+
for (const [name, schema] of Object.entries(args)) {
|
|
1992
|
+
if (schema.type === "positional") continue;
|
|
1993
|
+
const optionName = resolveOptionName(name, schema, options);
|
|
1994
|
+
knownLongOptions.add(optionName);
|
|
1995
|
+
/**
|
|
1996
|
+
* NOTE(kazupon): a hidden option is still accepted, so it stays a known option. It is left out
|
|
1997
|
+
* of the candidates, which are what `@gunshi/plugin-suggestion` offers as "Did you mean".
|
|
1998
|
+
*/
|
|
1999
|
+
if (!schema.hidden) addLongOptionCandidate(optionName);
|
|
2000
|
+
if (schema.short) knownShortOptions.add(schema.short);
|
|
2001
|
+
if (schema.type === "boolean" && schema.negatable) {
|
|
2002
|
+
const negatableOptionName = `${NEGATABLE_OPTION_PREFIX}${optionName}`;
|
|
2003
|
+
knownNegatableOptions.add(negatableOptionName);
|
|
2004
|
+
if (!schema.hidden) addLongOptionCandidate(negatableOptionName);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
const unknownOptions = [];
|
|
2008
|
+
for (const token of tokens) {
|
|
2009
|
+
if (token.kind === "option-terminator") break;
|
|
2010
|
+
if (token.kind !== "option" || !token.name || !token.rawName) continue;
|
|
2011
|
+
const isLongOption = token.rawName.startsWith("--");
|
|
2012
|
+
if (isLongOption) {
|
|
2013
|
+
if (knownLongOptions.has(token.name) || knownNegatableOptions.has(token.name)) continue;
|
|
2014
|
+
} else if (knownShortOptions.has(token.name)) continue;
|
|
2015
|
+
unknownOptions.push({
|
|
2016
|
+
rawName: token.rawName,
|
|
2017
|
+
name: token.name,
|
|
2018
|
+
candidates: isLongOption ? longOptionCandidates : []
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
return unknownOptions;
|
|
2022
|
+
}
|
|
2023
|
+
function resolveOptionName(name, schema, options) {
|
|
2024
|
+
return options.toKebab || schema.toKebab ? kebabnize(name) : name;
|
|
2025
|
+
}
|
|
2026
|
+
function createUnknownOptionErrors(unknownOptions) {
|
|
2027
|
+
return unknownOptions.map(({ rawName, name, candidates }) => {
|
|
2028
|
+
return new ArgsValidationError(`Unknown option: ${rawName}`, {
|
|
2029
|
+
code: ArgsValidationErrorKeys.unknownOption,
|
|
2030
|
+
values: {
|
|
2031
|
+
rawName,
|
|
2032
|
+
name,
|
|
2033
|
+
candidates
|
|
2034
|
+
}
|
|
2035
|
+
});
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
function mergeValidationErrors(error, additionalErrors) {
|
|
2039
|
+
if (additionalErrors.length === 0) return error;
|
|
2040
|
+
return new AggregateError(error ? [...error.errors, ...additionalErrors] : additionalErrors, error?.message || additionalErrors[0]?.message);
|
|
2041
|
+
}
|
|
2042
|
+
const isObject = (val) => val !== null && typeof val === "object";
|
|
2043
|
+
/**
|
|
2044
|
+
* Create the entry command that sub-commands and plugins can see.
|
|
2045
|
+
* The user-provided entry is copied, so that marking it with `entry: true` does not mutate it.
|
|
2046
|
+
*
|
|
2047
|
+
* @param entryCmd - The entry that is passed to `cli`
|
|
2048
|
+
* @returns The entry command marked with `entry: true`, or `undefined` if the entry is not a command
|
|
2049
|
+
*/
|
|
2050
|
+
function createEntryCommand(entryCmd) {
|
|
2051
|
+
if (isLazyCommand(entryCmd)) return Object.assign((...args) => entryCmd(...args), entryCmd, { entry: true });
|
|
2052
|
+
else if (typeof entryCmd === "object") return Object.assign(create(), entryCmd, { entry: true });
|
|
2053
|
+
else if (typeof entryCmd === "function") return {
|
|
2054
|
+
run: entryCmd,
|
|
2055
|
+
name: entryCmd.name || "(anonymous)",
|
|
2056
|
+
entry: true
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
function createInitialSubCommands(options, entryCommand) {
|
|
2060
|
+
const hasSubCommands = options.subCommands ? options.subCommands instanceof Map ? options.subCommands.size > 0 : isObject(options.subCommands) && Object.keys(options.subCommands).length > 0 : false;
|
|
2061
|
+
const subCommands = new Map(options.subCommands instanceof Map ? options.subCommands : []);
|
|
2062
|
+
if (!(options.subCommands instanceof Map) && isObject(options.subCommands)) for (const [name, cmd] of Object.entries(options.subCommands)) subCommands.set(name, cmd);
|
|
2063
|
+
if (hasSubCommands && entryCommand) subCommands.set(resolveEntryName(entryCommand), entryCommand);
|
|
2064
|
+
return subCommands;
|
|
2065
|
+
}
|
|
2066
|
+
function normalizeCliOptions(options, decorators, pluginContext, entryCommand) {
|
|
2067
|
+
const subCommands = new Map(pluginContext.subCommands);
|
|
2068
|
+
/**
|
|
2069
|
+
* NOTE(kazupon): the entry command is part of `subCommands` only when the user passes sub-commands,
|
|
2070
|
+
* so it is exposed on its own for the plugins that add commands to a CLI without sub-commands.
|
|
2071
|
+
*/
|
|
2072
|
+
const resolvedOptions = Object.assign(create(), CLI_OPTIONS_DEFAULT, options, {
|
|
2073
|
+
subCommands,
|
|
2074
|
+
entryCommand
|
|
2075
|
+
});
|
|
2076
|
+
if (resolvedOptions.renderHeader === void 0) resolvedOptions.renderHeader = decorators.getHeaderRenderer();
|
|
2077
|
+
if (resolvedOptions.renderUsage === void 0) resolvedOptions.renderUsage = decorators.getUsageRenderer();
|
|
2078
|
+
if (resolvedOptions.renderValidationErrors === void 0) resolvedOptions.renderValidationErrors = decorators.getValidationErrorsRenderer();
|
|
2079
|
+
return resolvedOptions;
|
|
2080
|
+
}
|
|
2081
|
+
function getPositionalTokens(tokens) {
|
|
2082
|
+
const positionals = [];
|
|
2083
|
+
for (const token of tokens) {
|
|
2084
|
+
if (token.kind === "option-terminator") break;
|
|
2085
|
+
if (token.kind === "positional" && token.value) positionals.push(token.value);
|
|
2086
|
+
}
|
|
2087
|
+
return positionals;
|
|
2088
|
+
}
|
|
2089
|
+
function resolveCommandTree(tokens, entry, options) {
|
|
2090
|
+
return resolveCommandTreeFromPositionals(getPositionalTokens(tokens), entry, options);
|
|
2091
|
+
}
|
|
2092
|
+
function resolveCommandTreeFromPositionals(positionals, entry, options) {
|
|
2093
|
+
function resolveAsEntry() {
|
|
2094
|
+
if (typeof entry === "function") {
|
|
2095
|
+
if (isLazyCommand(entry)) return {
|
|
2096
|
+
commandName: resolveEntryName(entry),
|
|
2097
|
+
command: entry,
|
|
2098
|
+
callMode: "entry",
|
|
2099
|
+
commandPath: [],
|
|
2100
|
+
depth: 0,
|
|
2101
|
+
omitted: options.subCommands.size > 0 && !positionals[0],
|
|
2102
|
+
levelSubCommands: options.subCommands.size > 0 ? options.subCommands : void 0
|
|
2103
|
+
};
|
|
2104
|
+
else return {
|
|
2105
|
+
command: {
|
|
2106
|
+
run: entry,
|
|
2107
|
+
entry: true
|
|
2108
|
+
},
|
|
2109
|
+
callMode: "entry",
|
|
2110
|
+
commandPath: [],
|
|
2111
|
+
depth: 0,
|
|
2112
|
+
omitted: options.subCommands.size > 0 && !positionals[0],
|
|
2113
|
+
levelSubCommands: options.subCommands.size > 0 ? options.subCommands : void 0
|
|
2114
|
+
};
|
|
2115
|
+
} else if (typeof entry === "object") return {
|
|
2116
|
+
commandName: resolveEntryName(entry),
|
|
2117
|
+
command: entry,
|
|
2118
|
+
callMode: "entry",
|
|
2119
|
+
commandPath: [],
|
|
2120
|
+
depth: 0,
|
|
2121
|
+
omitted: options.subCommands.size > 0 && !positionals[0],
|
|
2122
|
+
levelSubCommands: options.subCommands.size > 0 ? options.subCommands : void 0
|
|
2123
|
+
};
|
|
2124
|
+
else return {
|
|
2125
|
+
callMode: "unexpected",
|
|
2126
|
+
commandPath: [],
|
|
2127
|
+
depth: 0,
|
|
2128
|
+
omitted: false,
|
|
2129
|
+
levelSubCommands: void 0
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
if (positionals.length === 0 || options.subCommands.size === 0) return resolveAsEntry();
|
|
2133
|
+
let currentSubCommands = options.subCommands;
|
|
2134
|
+
let resolvedCommand;
|
|
2135
|
+
let resolvedName;
|
|
2136
|
+
const commandPath = [];
|
|
2137
|
+
let depth = 0;
|
|
2138
|
+
for (let i = 0; i < positionals.length; i++) {
|
|
2139
|
+
const token = positionals[i];
|
|
2140
|
+
const cmd = currentSubCommands.get(token);
|
|
2141
|
+
if (cmd == null) {
|
|
2142
|
+
if (depth === 0) {
|
|
2143
|
+
if (options.fallbackToEntry) return resolveAsEntry();
|
|
2144
|
+
const parent = resolveAsEntry();
|
|
2145
|
+
return {
|
|
2146
|
+
commandName: token,
|
|
2147
|
+
callMode: "unexpected",
|
|
2148
|
+
commandPath: [],
|
|
2149
|
+
depth: 0,
|
|
2150
|
+
omitted: false,
|
|
2151
|
+
levelSubCommands: void 0,
|
|
2152
|
+
unresolvedCommandName: token,
|
|
2153
|
+
parentCommand: parent.command,
|
|
2154
|
+
parentCommandName: parent.commandName,
|
|
2155
|
+
parentCommandPath: [],
|
|
2156
|
+
parentSubCommands: options.subCommands
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
return {
|
|
2160
|
+
commandName: token,
|
|
2161
|
+
callMode: "unexpected",
|
|
2162
|
+
commandPath: [...commandPath],
|
|
2163
|
+
depth,
|
|
2164
|
+
omitted: false,
|
|
2165
|
+
levelSubCommands: void 0,
|
|
2166
|
+
unresolvedCommandName: token,
|
|
2167
|
+
parentCommand: resolvedCommand,
|
|
2168
|
+
parentCommandName: resolvedName,
|
|
2169
|
+
parentCommandPath: [...commandPath],
|
|
2170
|
+
parentSubCommands: currentSubCommands
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
let resolved = cmd;
|
|
2174
|
+
if (typeof cmd === "function" && cmd.commandName == null) resolved = Object.assign((...args) => cmd(...args), cmd, { commandName: token });
|
|
2175
|
+
else if (typeof cmd === "object" && cmd.name == null) resolved = Object.assign(create(), cmd, { name: token });
|
|
2176
|
+
resolvedCommand = resolved;
|
|
2177
|
+
resolvedName = token;
|
|
2178
|
+
commandPath.push(token);
|
|
2179
|
+
depth++;
|
|
2180
|
+
const nestedSubCommands = getCommandSubCommands(cmd);
|
|
2181
|
+
if (nestedSubCommands && nestedSubCommands.size > 0) currentSubCommands = nestedSubCommands;
|
|
2182
|
+
else break;
|
|
2183
|
+
}
|
|
2184
|
+
if (!resolvedCommand) return resolveAsEntry();
|
|
2185
|
+
const resolvedSubCommands = getCommandSubCommands(resolvedCommand);
|
|
2186
|
+
const omitted = resolvedSubCommands != null && resolvedSubCommands.size > 0;
|
|
2187
|
+
let levelSubCommands;
|
|
2188
|
+
if (omitted && resolvedSubCommands) {
|
|
2189
|
+
levelSubCommands = new Map(resolvedSubCommands);
|
|
2190
|
+
let entryCopy;
|
|
2191
|
+
if (typeof resolvedCommand === "function") entryCopy = Object.assign((...args) => resolvedCommand(...args), resolvedCommand, { entry: true });
|
|
2192
|
+
else entryCopy = Object.assign(create(), resolvedCommand, { entry: true });
|
|
2193
|
+
levelSubCommands.set(resolvedName || resolveEntryName(entryCopy), entryCopy);
|
|
2194
|
+
}
|
|
2195
|
+
return {
|
|
2196
|
+
commandName: resolvedName,
|
|
2197
|
+
command: resolvedCommand,
|
|
2198
|
+
callMode: depth > 0 ? "subCommand" : "entry",
|
|
2199
|
+
commandPath,
|
|
2200
|
+
depth,
|
|
2201
|
+
omitted,
|
|
2202
|
+
levelSubCommands
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Select a command while accounting for option values that args-tokens removes during resolution.
|
|
2207
|
+
*
|
|
2208
|
+
* The routing pass deliberately projects each candidate's schemas down to the metadata needed to
|
|
2209
|
+
* classify tokens. It never copies `parse`, defaults, required checks, or other user code. A
|
|
2210
|
+
* candidate is accepted only when routing its own declaration reaches the same Map-key path in the
|
|
2211
|
+
* declaration tree.
|
|
2212
|
+
*
|
|
2213
|
+
* @param tokens - Tokenized command-line arguments
|
|
2214
|
+
* @param entry - Entry command definition or runner
|
|
2215
|
+
* @param options - Internal CLI options with the declared sub-command tree
|
|
2216
|
+
* @param globalOptions - Global option schemas used during candidate classification
|
|
2217
|
+
* @returns The selected command context or a diagnostic resolution error
|
|
2218
|
+
*/
|
|
2219
|
+
function resolveCommandSelection(tokens, entry, options, globalOptions) {
|
|
2220
|
+
const rawPositionals = getPositionalTokens(tokens);
|
|
2221
|
+
const legacy = resolveCommandTree(tokens, entry, options);
|
|
2222
|
+
if (!tokens.some((token) => token.kind === "option")) return { context: legacy };
|
|
2223
|
+
const candidates = collectRoutingCandidates(rawPositionals, options.subCommands);
|
|
2224
|
+
const evaluations = candidates.map((candidate) => evaluateRoutingCandidate(candidate));
|
|
2225
|
+
const validExplicit = evaluations.filter((evaluation) => evaluation.result.command != null && evaluation.result.unresolvedCommandName == null && sameCommandPath(evaluation.result.commandPath, evaluation.path) && evaluation.path.length > 0);
|
|
2226
|
+
if (legacy.command != null && legacy.unresolvedCommandName == null && legacy.commandPath.length > 0) {
|
|
2227
|
+
const legacyMatch = validExplicit.find((evaluation) => sameCommandPath(evaluation.path, legacy.commandPath));
|
|
2228
|
+
if (legacyMatch) return { context: legacyMatch.result };
|
|
2229
|
+
}
|
|
2230
|
+
if (validExplicit.length === 1) return { context: validExplicit[0].result };
|
|
2231
|
+
if (validExplicit.length > 1) return createCommandResolutionResult(options, entry, CommandResolutionErrorKeys.ambiguous, validExplicit.map((evaluation) => evaluation.path));
|
|
2232
|
+
const entryEvaluation = evaluateRoutingCandidate({
|
|
2233
|
+
path: [],
|
|
2234
|
+
command: getEntryRoutingCommand(entry, options)
|
|
2235
|
+
});
|
|
2236
|
+
if (entryEvaluation.result.command != null && entryEvaluation.result.unresolvedCommandName == null && entryEvaluation.result.commandPath.length === 0) return { context: entryEvaluation.result };
|
|
2237
|
+
const failures = evaluations.filter((evaluation) => {
|
|
2238
|
+
const { result } = evaluation;
|
|
2239
|
+
if (!result.unresolvedCommandName) return false;
|
|
2240
|
+
const parentPath = result.parentCommandPath || [];
|
|
2241
|
+
return isPathPrefix(evaluation.path, parentPath);
|
|
2242
|
+
}).map((evaluation) => evaluation.result);
|
|
2243
|
+
if (entryEvaluation.result.unresolvedCommandName) failures.push(entryEvaluation.result);
|
|
2244
|
+
const deepestFailures = selectDeepestFailures(failures);
|
|
2245
|
+
if (deepestFailures.length === 1) return { context: deepestFailures[0] };
|
|
2246
|
+
if (deepestFailures.length > 1) {
|
|
2247
|
+
const paths = deepestFailures.map((result) => result.parentCommandPath || []);
|
|
2248
|
+
return createCommandResolutionResult(options, entry, CommandResolutionErrorKeys.ambiguous, paths);
|
|
2249
|
+
}
|
|
2250
|
+
return createCommandResolutionResult(options, entry, CommandResolutionErrorKeys.inconsistentOptions, candidates.map((candidate) => candidate.path));
|
|
2251
|
+
function evaluateRoutingCandidate(candidate) {
|
|
2252
|
+
const positionals = getRoutingPositionals(tokens, globalOptions, candidate.command);
|
|
2253
|
+
return {
|
|
2254
|
+
...candidate,
|
|
2255
|
+
positionals,
|
|
2256
|
+
result: resolveCommandTreeFromPositionals(positionals, entry, options)
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
function collectRoutingCandidates(rawPositionals, root) {
|
|
2261
|
+
const candidates = [];
|
|
2262
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2263
|
+
function collect(commands, offset, parentPath) {
|
|
2264
|
+
for (let index = offset; index < rawPositionals.length; index++) {
|
|
2265
|
+
const name = rawPositionals[index];
|
|
2266
|
+
const command = commands.get(name);
|
|
2267
|
+
if (command == null) continue;
|
|
2268
|
+
const path = [...parentPath, name];
|
|
2269
|
+
const key = JSON.stringify(path);
|
|
2270
|
+
const normalized = normalizeCommandReference(command, name);
|
|
2271
|
+
if (!seen.has(key)) {
|
|
2272
|
+
seen.add(key);
|
|
2273
|
+
candidates.push({
|
|
2274
|
+
path,
|
|
2275
|
+
command: normalized
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
const nested = getCommandSubCommands(normalized);
|
|
2279
|
+
if (nested && nested.size > 0) collect(nested, index + 1, path);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
collect(root, 0, []);
|
|
2283
|
+
return candidates;
|
|
2284
|
+
}
|
|
2285
|
+
function normalizeCommandReference(command, name) {
|
|
2286
|
+
if (typeof command === "function" && command.commandName == null) return Object.assign((...args) => command(...args), command, { commandName: name });
|
|
2287
|
+
if (typeof command === "object" && command.name == null) return Object.assign(create(), command, { name });
|
|
2288
|
+
return command;
|
|
2289
|
+
}
|
|
2290
|
+
function getEntryRoutingCommand(entry, options) {
|
|
2291
|
+
if (options.entryCommand) return options.entryCommand;
|
|
2292
|
+
if (typeof entry === "function" && !isLazyCommand(entry)) return {
|
|
2293
|
+
run: entry,
|
|
2294
|
+
entry: true
|
|
2295
|
+
};
|
|
2296
|
+
return entry;
|
|
2297
|
+
}
|
|
2298
|
+
function getRoutingPositionals(tokens, globalOptions, command) {
|
|
2299
|
+
/**
|
|
2300
|
+
* NOTE(kazupon): an empty argument is never a command name. `getPositionalTokens()` skips it when it
|
|
2301
|
+
* collects the candidates, and the routing of each candidate must skip it too; otherwise the routing
|
|
2302
|
+
* looks for a sub-command named `''`, and fails with `inconsistent-options` (#781).
|
|
2303
|
+
*/
|
|
2304
|
+
return readPositionals(tokens, globalOptions, command).filter((value) => value !== "");
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Read the positional arguments as `resolveArgs()` reads them for a command, empty ones included.
|
|
2308
|
+
*
|
|
2309
|
+
* The schemas are projected down to the metadata that classifies the tokens, so no code of the user,
|
|
2310
|
+
* such as `parse`, runs.
|
|
2311
|
+
*
|
|
2312
|
+
* @param tokens - Tokenized command-line arguments
|
|
2313
|
+
* @param globalOptions - Global option schemas
|
|
2314
|
+
* @param command - The command whose arguments classify the tokens
|
|
2315
|
+
* @returns The positional arguments, in the order that `skipPositional` of args-tokens counts them
|
|
2316
|
+
*/
|
|
2317
|
+
function readPositionals(tokens, globalOptions, command) {
|
|
2318
|
+
const effective = resolveCommandArgs(globalOptions, getCommandArgs(command));
|
|
2319
|
+
const routingArgs = create();
|
|
2320
|
+
for (const [name, schema] of Object.entries(effective)) {
|
|
2321
|
+
if (schema.type === "positional") continue;
|
|
2322
|
+
routingArgs[name] = {
|
|
2323
|
+
type: schema.type === "boolean" ? "boolean" : "string",
|
|
2324
|
+
short: schema.short,
|
|
2325
|
+
negatable: schema.negatable,
|
|
2326
|
+
toKebab: schema.toKebab
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
return resolveArgs(routingArgs, tokens, {
|
|
2330
|
+
shortGrouping: true,
|
|
2331
|
+
toKebab: command.toKebab
|
|
2332
|
+
}).positionals;
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Resolve `skipPositional` of args-tokens: the index of the last positional argument to skip.
|
|
2336
|
+
*
|
|
2337
|
+
* NOTE(kazupon): the command names are the first positional arguments that are not empty, and the
|
|
2338
|
+
* last one is at `depth - 1` only when no empty argument comes before it. `skipPositional` counts every
|
|
2339
|
+
* positional argument, the empty ones too, so `app '' build x` gave `build` to the first positional
|
|
2340
|
+
* argument of `build` (#793). The index is found in the reading of `resolveArgs()`, in which the values
|
|
2341
|
+
* of the options are already taken out, so that an empty value of an option is not counted.
|
|
2342
|
+
*
|
|
2343
|
+
* @param tokens - Tokenized command-line arguments
|
|
2344
|
+
* @param globalOptions - Global option schemas
|
|
2345
|
+
* @param command - The command whose arguments are resolved
|
|
2346
|
+
* @param depth - The number of command names
|
|
2347
|
+
* @returns The index of the last command name among the positional arguments, or `-1` for the entry
|
|
2348
|
+
*/
|
|
2349
|
+
function resolveSkipPositional(tokens, globalOptions, command, depth) {
|
|
2350
|
+
if (depth === 0) return -1;
|
|
2351
|
+
if (!tokens.some((token) => token.kind === "positional" && token.value === "")) return depth - 1;
|
|
2352
|
+
let names = 0;
|
|
2353
|
+
for (const [index, value] of readPositionals(tokens, globalOptions, command).entries()) {
|
|
2354
|
+
if (value === "") continue;
|
|
2355
|
+
names++;
|
|
2356
|
+
if (names === depth) return index;
|
|
2357
|
+
}
|
|
2358
|
+
return depth - 1;
|
|
2359
|
+
}
|
|
2360
|
+
function sameCommandPath(left, right) {
|
|
2361
|
+
return left.length === right.length && left.every((name, index) => name === right[index]);
|
|
2362
|
+
}
|
|
2363
|
+
function isPathPrefix(prefix, path) {
|
|
2364
|
+
return prefix.length <= path.length && prefix.every((name, index) => name === path[index]);
|
|
2365
|
+
}
|
|
2366
|
+
function selectDeepestFailures(failures) {
|
|
2367
|
+
const unique = /* @__PURE__ */ new Map();
|
|
2368
|
+
for (const failure of failures) {
|
|
2369
|
+
const path = failure.parentCommandPath || [];
|
|
2370
|
+
const key = JSON.stringify(path);
|
|
2371
|
+
unique.set(key, failure);
|
|
2372
|
+
}
|
|
2373
|
+
const deepest = Math.max(-1, ...[...unique.values()].map((failure) => (failure.parentCommandPath || []).length));
|
|
2374
|
+
return [...unique.values()].filter((failure) => (failure.parentCommandPath || []).length === deepest);
|
|
2375
|
+
}
|
|
2376
|
+
function createCommandResolutionResult(options, entry, code, candidatePaths) {
|
|
2377
|
+
const normalizedPaths = candidatePaths.map((path) => [...path]);
|
|
2378
|
+
const commandPath = commonCommandPath(normalizedPaths);
|
|
2379
|
+
const parent = resolveCommandTreeFromPositionals(commandPath, entry, options);
|
|
2380
|
+
const command = createDiagnosticCommand(parent.command, commandPath.at(-1));
|
|
2381
|
+
const context = {
|
|
2382
|
+
...parent,
|
|
2383
|
+
command,
|
|
2384
|
+
commandPath,
|
|
2385
|
+
depth: commandPath.length,
|
|
2386
|
+
commandName: parent.commandName || commandPath.at(-1),
|
|
2387
|
+
callMode: commandPath.length > 0 ? "subCommand" : "entry",
|
|
2388
|
+
omitted: false
|
|
2389
|
+
};
|
|
2390
|
+
const formattedPaths = normalizedPaths.map((path) => path.join(" ")).join(", ");
|
|
2391
|
+
const formattedPath = commandPath.join(" ");
|
|
2392
|
+
const values = {
|
|
2393
|
+
commandPath: formattedPath,
|
|
2394
|
+
candidatePaths: formattedPaths,
|
|
2395
|
+
candidates: formattedPaths
|
|
2396
|
+
};
|
|
2397
|
+
return {
|
|
2398
|
+
context,
|
|
2399
|
+
error: new CommandResolutionError(createCommandResolutionMessage(code, formattedPath, formattedPaths), {
|
|
2400
|
+
code,
|
|
2401
|
+
values,
|
|
2402
|
+
commandPath,
|
|
2403
|
+
candidatePaths: normalizedPaths
|
|
2404
|
+
}),
|
|
2405
|
+
diagnostic: true
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
function commonCommandPath(paths) {
|
|
2409
|
+
if (paths.length === 0) return [];
|
|
2410
|
+
const common = [...paths[0]];
|
|
2411
|
+
for (const path of paths.slice(1)) {
|
|
2412
|
+
let length = 0;
|
|
2413
|
+
while (length < common.length && length < path.length && common[length] === path[length]) length++;
|
|
2414
|
+
common.length = length;
|
|
2415
|
+
}
|
|
2416
|
+
return common;
|
|
2417
|
+
}
|
|
2418
|
+
function createDiagnosticCommand(command, name) {
|
|
2419
|
+
if (typeof command === "function") return Object.assign(create(), command, {
|
|
2420
|
+
name: command.commandName || name,
|
|
2421
|
+
run: void 0
|
|
2422
|
+
});
|
|
2423
|
+
return Object.assign(create(), command || {}, {
|
|
2424
|
+
name: command?.name || name,
|
|
2425
|
+
run: void 0
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
function createCommandResolutionMessage(code, commandPath, candidatePaths) {
|
|
2429
|
+
switch (code) {
|
|
2430
|
+
case CommandResolutionErrorKeys.ambiguous: return `Ambiguous command resolution. Move options after the command name: ${candidatePaths}`;
|
|
2431
|
+
case CommandResolutionErrorKeys.lazySchemaMismatch: return `Lazy command schema mismatch at ${commandPath}. Declare leading options in the lazy definition or move options after the command name.`;
|
|
2432
|
+
default: return "The options do not identify a consistent command. Move options after the command name.";
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
function createLazySchemaMismatchError(commandPath, loadedPath) {
|
|
2436
|
+
const expected = [...commandPath];
|
|
2437
|
+
const actual = [...loadedPath];
|
|
2438
|
+
return new CommandResolutionError(`Lazy command schema mismatch at ${expected.join(" ")}. Declare leading options in the lazy definition or move options after the command name.`, {
|
|
2439
|
+
code: CommandResolutionErrorKeys.lazySchemaMismatch,
|
|
2440
|
+
values: {
|
|
2441
|
+
commandPath: expected.join(" "),
|
|
2442
|
+
loadedCommandPath: actual.join(" ")
|
|
2443
|
+
},
|
|
2444
|
+
commandPath: expected,
|
|
2445
|
+
candidatePaths: [expected, actual]
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
function createCommandNotFoundError(resolved) {
|
|
2449
|
+
const commandName = resolved.unresolvedCommandName || resolved.commandName || "";
|
|
2450
|
+
return new CommandNotFoundError(`Command not found: ${commandName}`, {
|
|
2451
|
+
code: CommandNotFoundErrorKeys.notFound,
|
|
2452
|
+
values: { commandName },
|
|
2453
|
+
commandName,
|
|
2454
|
+
candidates: [...resolved.parentSubCommands?.keys() || []],
|
|
2455
|
+
commandPath: resolved.parentCommandPath || []
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
function resolveEntryName(entry) {
|
|
2459
|
+
return isLazyCommand(entry) ? entry.commandName || "(anonymous)" : entry.name || "(anonymous)";
|
|
2460
|
+
}
|
|
2461
|
+
function getPluginExtensions(plugins) {
|
|
2462
|
+
const extensions = create();
|
|
2463
|
+
for (const plugin of plugins) if (plugin.extension) {
|
|
2464
|
+
const key = plugin.id;
|
|
2465
|
+
if (extensions[key]) console.warn(`Plugin "${key}" is already installed. Ignore it for command context extending.`);
|
|
2466
|
+
else extensions[key] = plugin.extension;
|
|
2467
|
+
}
|
|
2468
|
+
return extensions;
|
|
2469
|
+
}
|
|
2470
|
+
async function executeCommand(cmd, ctx, decorators) {
|
|
2471
|
+
const commandRunner = cmd.run || NOOP;
|
|
2472
|
+
const baseRunner = (ctx) => {
|
|
2473
|
+
if (hasPriorityValidationError(ctx.validationError)) throw ctx.validationError;
|
|
2474
|
+
return commandRunner(ctx);
|
|
2475
|
+
};
|
|
2476
|
+
const decoratedRunner = decorators.reduceRight((runner, decorator) => decorator(runner), baseRunner);
|
|
2477
|
+
try {
|
|
2478
|
+
if (ctx.env.onBeforeCommand) await ctx.env.onBeforeCommand(ctx);
|
|
2479
|
+
const result = await decoratedRunner(ctx);
|
|
2480
|
+
if (ctx.env.onAfterCommand) await ctx.env.onAfterCommand(ctx, result);
|
|
2481
|
+
return typeof result === "string" ? result : void 0;
|
|
2482
|
+
} catch (error) {
|
|
2483
|
+
if (ctx.env.onErrorCommand) try {
|
|
2484
|
+
await ctx.env.onErrorCommand(ctx, error);
|
|
2485
|
+
} catch (hookError) {
|
|
2486
|
+
console.error("Error in onErrorCommand hook:", hookError);
|
|
2487
|
+
}
|
|
2488
|
+
throw error;
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
//#endregion
|
|
2492
|
+
//#region node_modules/.bun/gunshi@1.0.0-rc.1/node_modules/gunshi/lib/bone.js
|
|
2493
|
+
/**
|
|
2494
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
2495
|
+
* @license MIT
|
|
2496
|
+
*/
|
|
2497
|
+
/**
|
|
2498
|
+
* Run the command.
|
|
2499
|
+
*
|
|
2500
|
+
* @typeParam G - A type extending {@linkcode GunshiParams} to specify the shape of command and cli options.
|
|
2501
|
+
*
|
|
2502
|
+
* @param args - Command line arguments
|
|
2503
|
+
* @param entry - A {@link Command | entry command}, an {@link CommandRunner | inline command runner}, or a {@link LazyCommand | lazily-loaded command}
|
|
2504
|
+
* @param options - A {@link CliOptions | CLI options}
|
|
2505
|
+
* @returns A rendered usage or undefined. if you will use {@linkcode CliOptions.usageSilent} option, it will return rendered usage string.
|
|
2506
|
+
*/
|
|
2507
|
+
async function cli(args, entry, options = {}) {
|
|
2508
|
+
return cliCore(args, entry, options, []);
|
|
2509
|
+
}
|
|
2510
|
+
/**
|
|
2511
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
2512
|
+
* @license MIT
|
|
2513
|
+
*/
|
|
2514
|
+
//#endregion
|
|
2515
|
+
//#region node_modules/.bun/gunshi@1.0.0-rc.1/node_modules/gunshi/lib/definition.js
|
|
2516
|
+
/**
|
|
2517
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
2518
|
+
* @license MIT
|
|
2519
|
+
*/
|
|
2520
|
+
/**
|
|
2521
|
+
* Define a {@link Command | command}.
|
|
2522
|
+
*
|
|
2523
|
+
* @typeParam G - A {@link GunshiParamsConstraint} type
|
|
2524
|
+
*
|
|
2525
|
+
* @param definition - A {@link Command | command} definition
|
|
2526
|
+
* @returns A defined {@link Command | command}
|
|
2527
|
+
*/
|
|
2528
|
+
function define(definition) {
|
|
2529
|
+
return definition;
|
|
2530
|
+
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Define a {@link LazyCommand | lazy command} with or without definition.
|
|
2533
|
+
*
|
|
2534
|
+
* @param loader - A {@link CommandLoader | command loader} function that returns a command definition
|
|
2535
|
+
* @param definition - An optional {@link Command | command} definition. Its `name` may be omitted,
|
|
2536
|
+
* including when the lazy command is used as the CLI entry command.
|
|
2537
|
+
* @returns A {@link LazyCommand | lazy command} that can be executed later
|
|
2538
|
+
*/
|
|
2539
|
+
function lazy(loader, definition) {
|
|
2540
|
+
let lazyCommand = loader;
|
|
2541
|
+
if ((definition != null || !("commandName" in lazyCommand)) && !Object.isExtensible(lazyCommand)) lazyCommand = Object.assign(() => loader(), loader);
|
|
2542
|
+
if (definition == null && !("commandName" in lazyCommand)) lazyCommand.commandName = void 0;
|
|
2543
|
+
if (definition != null) {
|
|
2544
|
+
lazyCommand.commandName = definition.name;
|
|
2545
|
+
lazyCommand.description = definition.description;
|
|
2546
|
+
lazyCommand.args = definition.args;
|
|
2547
|
+
lazyCommand.examples = definition.examples;
|
|
2548
|
+
lazyCommand.internal = definition.internal;
|
|
2549
|
+
lazyCommand.entry = definition.entry;
|
|
2550
|
+
lazyCommand.toKebab = definition.toKebab;
|
|
2551
|
+
lazyCommand.rendering = definition.rendering;
|
|
2552
|
+
if (definition.subCommands) lazyCommand.subCommands = definition.subCommands;
|
|
2553
|
+
if ("resource" in definition) lazyCommand.resource = definition.resource;
|
|
2554
|
+
}
|
|
2555
|
+
return lazyCommand;
|
|
2556
|
+
}
|
|
2557
|
+
//#endregion
|
|
2558
|
+
//#region node_modules/.bun/gunshi@1.0.0-rc.1/node_modules/gunshi/lib/plugin-Fea3gCnT.js
|
|
2559
|
+
/**
|
|
2560
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
2561
|
+
* @license MIT
|
|
2562
|
+
*/
|
|
2563
|
+
/**
|
|
2564
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
2565
|
+
* @license MIT
|
|
2566
|
+
*/
|
|
2567
|
+
//#endregion
|
|
9
2568
|
//#region src/capture/resolve-capture-dir.ts
|
|
10
2569
|
function resolveCaptureDir(capture, host) {
|
|
11
2570
|
return capture.dir ?? join(resolveXDGDir(host, "state"), "captures");
|
|
@@ -73,7 +2632,6 @@ async function writeOwnerFile(path, text, flag) {
|
|
|
73
2632
|
//#endregion
|
|
74
2633
|
//#region src/config/build-jev-only-config.ts
|
|
75
2634
|
function buildJevOnlyConfig(config) {
|
|
76
|
-
if (config.provider.protocol !== "system-one") return null;
|
|
77
2635
|
const judge = config.judge === void 0 || config.judge === null ? config.judge : {
|
|
78
2636
|
...config.judge,
|
|
79
2637
|
timeoutMs: Math.min(config.judge.timeoutMs, CLI_LIMITS.judgeMs)
|
|
@@ -95,6 +2653,117 @@ function resolveShippedModDir(startDir = import.meta.dirname) {
|
|
|
95
2653
|
return modDir;
|
|
96
2654
|
}
|
|
97
2655
|
//#endregion
|
|
2656
|
+
//#region src/process/start-detached-process.ts
|
|
2657
|
+
function startDetachedProcess(file, args, env) {
|
|
2658
|
+
const child = spawn(file, args, {
|
|
2659
|
+
detached: true,
|
|
2660
|
+
stdio: "ignore",
|
|
2661
|
+
env
|
|
2662
|
+
});
|
|
2663
|
+
child.on("error", () => {});
|
|
2664
|
+
child.unref();
|
|
2665
|
+
}
|
|
2666
|
+
//#endregion
|
|
2667
|
+
//#region src/judge/judge-server-response-schema.ts
|
|
2668
|
+
const judgeServerResponseSchema = discriminatedUnion("kind", [strictObject({
|
|
2669
|
+
kind: literal("reply"),
|
|
2670
|
+
text: string(),
|
|
2671
|
+
model: string(),
|
|
2672
|
+
safetyStops: number().nullable(),
|
|
2673
|
+
fallback: boolean(),
|
|
2674
|
+
warm: boolean().nullable()
|
|
2675
|
+
}), strictObject({
|
|
2676
|
+
kind: literal("failure"),
|
|
2677
|
+
aborted: boolean(),
|
|
2678
|
+
message: string()
|
|
2679
|
+
})]);
|
|
2680
|
+
//#endregion
|
|
2681
|
+
//#region src/judge/make-judge-server-session.ts
|
|
2682
|
+
function makeJudgeServerSession(target) {
|
|
2683
|
+
return {
|
|
2684
|
+
review: async (model, request, signal) => {
|
|
2685
|
+
const answer = await sendLine(await tryConnectOrStartServer(target, signal), `${JSON.stringify({
|
|
2686
|
+
model,
|
|
2687
|
+
timeoutMs: target.timeoutMs,
|
|
2688
|
+
...request
|
|
2689
|
+
})}\n`, signal);
|
|
2690
|
+
const response = judgeServerResponseSchema.parse(JSON.parse(answer));
|
|
2691
|
+
if (response.kind === "failure") throw response.aborted ? new DOMException(response.message, "AbortError") : new Error(response.message);
|
|
2692
|
+
const { kind: _kind, ...reply } = response;
|
|
2693
|
+
return reply;
|
|
2694
|
+
},
|
|
2695
|
+
stop: () => {}
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
const START_WAIT_MS = 1e4;
|
|
2699
|
+
const START_POLL_MS = 25;
|
|
2700
|
+
async function tryConnectOrStartServer(target, signal) {
|
|
2701
|
+
const existing = await tryConnect(target.socketPath);
|
|
2702
|
+
if (existing !== null) return existing;
|
|
2703
|
+
const [file, ...args] = target.command;
|
|
2704
|
+
startDetachedProcess(file, [
|
|
2705
|
+
...args,
|
|
2706
|
+
"judge-server",
|
|
2707
|
+
target.socketPath
|
|
2708
|
+
], target.env);
|
|
2709
|
+
const deadline = Date.now() + START_WAIT_MS;
|
|
2710
|
+
while (Date.now() < deadline) {
|
|
2711
|
+
await setTimeout(START_POLL_MS, void 0, { signal });
|
|
2712
|
+
const socket = await tryConnect(target.socketPath);
|
|
2713
|
+
if (socket !== null) return socket;
|
|
2714
|
+
}
|
|
2715
|
+
throw new Error("the judge server did not start");
|
|
2716
|
+
}
|
|
2717
|
+
function tryConnect(socketPath) {
|
|
2718
|
+
return new Promise((resolve) => {
|
|
2719
|
+
const socket = connect(socketPath);
|
|
2720
|
+
socket.once("connect", () => {
|
|
2721
|
+
socket.off("error", onError);
|
|
2722
|
+
resolve(socket);
|
|
2723
|
+
});
|
|
2724
|
+
const onError = () => {
|
|
2725
|
+
resolve(null);
|
|
2726
|
+
};
|
|
2727
|
+
socket.once("error", onError);
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
function sendLine(socket, line, signal) {
|
|
2731
|
+
return new Promise((resolve, reject) => {
|
|
2732
|
+
const chunks = [];
|
|
2733
|
+
const onAbort = () => {
|
|
2734
|
+
socket.destroy();
|
|
2735
|
+
reject(new DOMException("Judge review aborted", "AbortError"));
|
|
2736
|
+
};
|
|
2737
|
+
if (signal.aborted) {
|
|
2738
|
+
onAbort();
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2742
|
+
socket.on("data", (chunk) => {
|
|
2743
|
+
chunks.push(chunk);
|
|
2744
|
+
});
|
|
2745
|
+
socket.once("error", (error) => {
|
|
2746
|
+
signal.removeEventListener("abort", onAbort);
|
|
2747
|
+
reject(error);
|
|
2748
|
+
});
|
|
2749
|
+
socket.once("end", () => {
|
|
2750
|
+
signal.removeEventListener("abort", onAbort);
|
|
2751
|
+
socket.destroy();
|
|
2752
|
+
resolve(Buffer.concat(chunks).toString("utf8").trim());
|
|
2753
|
+
});
|
|
2754
|
+
socket.write(line);
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
//#endregion
|
|
2758
|
+
//#region src/judge/resolve-judge-socket-path.ts
|
|
2759
|
+
function resolveJudgeSocketPath(host, sessionID, entry) {
|
|
2760
|
+
const runtime = host.env["XDG_RUNTIME_DIR"];
|
|
2761
|
+
const dir = runtime === void 0 || runtime === "" ? join(resolveXDGDir(host, "state"), "judge") : join(runtime, "auto-mode");
|
|
2762
|
+
const build = statSync(entry, { throwIfNoEntry: false });
|
|
2763
|
+
const name = createHash("sha256").update(`${sessionID}\0${entry}\0${String(build?.mtimeMs ?? 0)}`).digest("hex").slice(0, 24);
|
|
2764
|
+
return join(dir, `judge-${name}.sock`);
|
|
2765
|
+
}
|
|
2766
|
+
//#endregion
|
|
98
2767
|
//#region src/scope/read-pull-request.ts
|
|
99
2768
|
const LOOKUP_TIMEOUT_MS = 5e3;
|
|
100
2769
|
const MAX_LOOKUP_OUTPUT_BYTES = 65536;
|
|
@@ -345,11 +3014,11 @@ async function isBranchCreatedSince(commonDir, branch, since) {
|
|
|
345
3014
|
}
|
|
346
3015
|
//#endregion
|
|
347
3016
|
//#region src/record-scope.ts
|
|
348
|
-
async function recordScope(request, config,
|
|
349
|
-
const host =
|
|
3017
|
+
async function recordScope(request, config, runtime) {
|
|
3018
|
+
const host = runtime.host;
|
|
350
3019
|
if (!Object.values(config.scopeSources ?? DEFAULT_SCOPE_SOURCES).some((source) => source.kind === "session")) return EMPTY_SESSION_SCOPE;
|
|
351
3020
|
return await updateSessionScope(request, {
|
|
352
|
-
now:
|
|
3021
|
+
now: runtime.now(),
|
|
353
3022
|
stateDir: resolveXDGDir(host, "state"),
|
|
354
3023
|
home: host.home,
|
|
355
3024
|
env: host.env,
|
|
@@ -419,6 +3088,7 @@ Usage:
|
|
|
419
3088
|
auto-mode print-prompt Print the system prompt the classifier receives
|
|
420
3089
|
auto-mode record Read a finished Bash call on stdin, add what it created to the session's scope
|
|
421
3090
|
auto-mode mod-path Print the directory of the Claude Code mod this package ships
|
|
3091
|
+
auto-mode judge-server <socket> Serve one agent session's judge reviews on a unix socket
|
|
422
3092
|
|
|
423
3093
|
Options:
|
|
424
3094
|
--classifier <path> Use this framework file instead of the shipped one
|
|
@@ -428,78 +3098,147 @@ Options:
|
|
|
428
3098
|
--jev-only With run: require Jev and cap its API timeout at 5 seconds
|
|
429
3099
|
--evaluation-deadline <unix-ms> With --jev-only: share the helper and API deadline
|
|
430
3100
|
`;
|
|
3101
|
+
const CLI_ARGS = {
|
|
3102
|
+
classifier: { type: "string" },
|
|
3103
|
+
rules: { type: "string" },
|
|
3104
|
+
explain: { type: "boolean" },
|
|
3105
|
+
"local-only": { type: "boolean" },
|
|
3106
|
+
"jev-only": { type: "boolean" },
|
|
3107
|
+
"evaluation-deadline": { type: "string" },
|
|
3108
|
+
help: {
|
|
3109
|
+
type: "boolean",
|
|
3110
|
+
short: "h"
|
|
3111
|
+
}
|
|
3112
|
+
};
|
|
3113
|
+
const COMMANDS = /* @__PURE__ */ new Map([
|
|
3114
|
+
["run", (io, values) => runClassification(io, values)],
|
|
3115
|
+
["print-prompt", (io, values) => printPrompt(io, values)],
|
|
3116
|
+
["record", (io) => runRecord(io)],
|
|
3117
|
+
["mod-path", (io) => printModPath(io)],
|
|
3118
|
+
["judge-server", (io, _values, positionals) => runJudgeServer(io, positionals[0])]
|
|
3119
|
+
]);
|
|
431
3120
|
async function runCLI(argv, io) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
3121
|
+
let exitCode = 0;
|
|
3122
|
+
const subCommands = Object.fromEntries([...COMMANDS].map(([name, runCommand]) => [name, lazy(() => async (ctx) => {
|
|
3123
|
+
exitCode = ctx.values.help === true ? printUsage(io.stdout) : await runCommand(io, ctx.values, [...ctx.positionals.slice(ctx.commandPath.length), ...ctx.rest]);
|
|
3124
|
+
}, {
|
|
3125
|
+
name,
|
|
3126
|
+
args: CLI_ARGS
|
|
3127
|
+
})]));
|
|
3128
|
+
const entry = define({
|
|
3129
|
+
name: "auto-mode",
|
|
3130
|
+
args: CLI_ARGS,
|
|
3131
|
+
run: async (ctx) => {
|
|
3132
|
+
exitCode = await runCommandAfterTerminator(io, ctx.values, ctx.rest);
|
|
3133
|
+
}
|
|
3134
|
+
});
|
|
3135
|
+
try {
|
|
3136
|
+
await cli([...argv], entry, {
|
|
3137
|
+
subCommands,
|
|
3138
|
+
strict: true,
|
|
3139
|
+
onBeforeCommand: requireValidCommandLine
|
|
3140
|
+
});
|
|
3141
|
+
} catch (error) {
|
|
3142
|
+
if (!isUsageError(error)) throw error;
|
|
3143
|
+
io.stderr.write(`auto-mode: ${formatUsageError(error)}\n\n${USAGE}`);
|
|
435
3144
|
return 2;
|
|
436
3145
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
3146
|
+
return exitCode;
|
|
3147
|
+
}
|
|
3148
|
+
function runCommandAfterTerminator(io, values, rest) {
|
|
3149
|
+
const [command, ...positionals] = rest;
|
|
3150
|
+
if (values.help === true || command === void 0) return Promise.resolve(printUsage(io.stdout));
|
|
3151
|
+
const runCommand = COMMANDS.get(command);
|
|
3152
|
+
if (runCommand === void 0) {
|
|
3153
|
+
io.stderr.write(`auto-mode: unknown command '${command}'\n\n${USAGE}`);
|
|
3154
|
+
return Promise.resolve(2);
|
|
441
3155
|
}
|
|
442
|
-
|
|
443
|
-
|
|
3156
|
+
return runCommand(io, values, positionals);
|
|
3157
|
+
}
|
|
3158
|
+
function printUsage(stdout) {
|
|
3159
|
+
stdout.write(USAGE);
|
|
3160
|
+
return 0;
|
|
3161
|
+
}
|
|
3162
|
+
async function runClassification(io, values) {
|
|
3163
|
+
try {
|
|
3164
|
+
return await run(io, {
|
|
3165
|
+
explain: values.explain === true,
|
|
3166
|
+
localOnly: values["local-only"] === true,
|
|
3167
|
+
jevOnly: values["jev-only"] === true,
|
|
3168
|
+
deadline: values["evaluation-deadline"]
|
|
3169
|
+
});
|
|
444
3170
|
} catch {
|
|
445
3171
|
io.stderr.write("auto-mode: configuration unreadable; no verdict\n");
|
|
446
3172
|
return 0;
|
|
447
3173
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
3174
|
+
}
|
|
3175
|
+
async function printPrompt(io, values) {
|
|
3176
|
+
const config = await loadConfig(void 0, io.runtime.host);
|
|
3177
|
+
printWarnings(io.stderr, config.warnings);
|
|
3178
|
+
const prompt = await loadPolicy({
|
|
3179
|
+
classifierPath: values.classifier ?? config.classifierPath,
|
|
3180
|
+
rulesPath: values.rules ?? config.rulesPath
|
|
3181
|
+
}, "decision.md");
|
|
3182
|
+
io.stdout.write(`${prompt}\n`);
|
|
3183
|
+
return 0;
|
|
3184
|
+
}
|
|
3185
|
+
function printModPath(io) {
|
|
3186
|
+
try {
|
|
461
3187
|
io.stdout.write(`${resolveShippedModDir()}\n`);
|
|
462
|
-
return 0;
|
|
3188
|
+
return Promise.resolve(0);
|
|
463
3189
|
} catch (error) {
|
|
464
3190
|
io.stderr.write(`auto-mode: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
465
|
-
return 1;
|
|
3191
|
+
return Promise.resolve(1);
|
|
466
3192
|
}
|
|
467
|
-
io.stderr.write(`auto-mode: unknown command '${command}'\n\n${USAGE}`);
|
|
468
|
-
return 2;
|
|
469
3193
|
}
|
|
470
|
-
function
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
3194
|
+
function requireValidCommandLine(ctx) {
|
|
3195
|
+
if (ctx.validationError !== void 0) throw ctx.validationError;
|
|
3196
|
+
}
|
|
3197
|
+
function isUsageError(error) {
|
|
3198
|
+
return error instanceof AggregateError && error.errors.every((reason) => isArgsValidationError(reason) || isCommandNotFoundError(reason) || isCommandResolutionError(reason));
|
|
3199
|
+
}
|
|
3200
|
+
function formatUsageError(error) {
|
|
3201
|
+
return error.errors.map((reason) => {
|
|
3202
|
+
if (isCommandNotFoundError(reason)) return `unknown command '${reason.commandName}'`;
|
|
3203
|
+
return reason instanceof Error ? reason.message : String(reason);
|
|
3204
|
+
}).join("\n");
|
|
3205
|
+
}
|
|
3206
|
+
function buildJudgeServerSession(io, config, sessionID) {
|
|
3207
|
+
const judge = config.judge ?? null;
|
|
3208
|
+
if (io.judgeServerCommand === void 0 || judge === null || pickJudgeLane(judge).transport !== "agent-sdk") return;
|
|
3209
|
+
const command = io.judgeServerCommand;
|
|
3210
|
+
const entry = command[1] ?? command[0];
|
|
3211
|
+
return makeJudgeServerSession({
|
|
3212
|
+
socketPath: resolveJudgeSocketPath(io.runtime.host, sessionID, entry),
|
|
3213
|
+
command: io.judgeServerCommand,
|
|
3214
|
+
env: io.runtime.host.env,
|
|
3215
|
+
timeoutMs: judge.timeoutMs
|
|
3216
|
+
});
|
|
3217
|
+
}
|
|
3218
|
+
async function runJudgeServer(io, socketPath) {
|
|
3219
|
+
if (socketPath === void 0) {
|
|
3220
|
+
io.stderr.write(`auto-mode: judge-server needs a socket path\n\n${USAGE}`);
|
|
3221
|
+
return 2;
|
|
490
3222
|
}
|
|
3223
|
+
await (await (await import("./start-judge-server-CCSZbTwM.js")).startJudgeServer({
|
|
3224
|
+
socketPath,
|
|
3225
|
+
env: io.runtime.host.env,
|
|
3226
|
+
idleMs: JUDGE_IDLE_MS
|
|
3227
|
+
}))?.closed;
|
|
3228
|
+
return 0;
|
|
491
3229
|
}
|
|
492
|
-
|
|
3230
|
+
const JUDGE_IDLE_MS = 9e5;
|
|
3231
|
+
async function run(io, options) {
|
|
493
3232
|
const raw = await io.stdin();
|
|
494
3233
|
let body;
|
|
495
3234
|
try {
|
|
496
3235
|
body = JSON.parse(raw);
|
|
497
3236
|
} catch {
|
|
498
|
-
return printNote(io.stderr, explain, "stdin is not JSON, so no verdict");
|
|
3237
|
+
return printNote(io.stderr, options.explain, "stdin is not JSON, so no verdict");
|
|
499
3238
|
}
|
|
500
3239
|
const request = parseActionRequest(body);
|
|
501
|
-
if (request === null) return printNote(io.stderr, explain, "not an action request this classifier judges, so no verdict");
|
|
502
|
-
const host = io.host;
|
|
3240
|
+
if (request === null) return printNote(io.stderr, options.explain, "not an action request this classifier judges, so no verdict");
|
|
3241
|
+
const host = io.runtime.host;
|
|
503
3242
|
let loaded;
|
|
504
3243
|
try {
|
|
505
3244
|
loaded = await loadConfig(void 0, host);
|
|
@@ -508,29 +3247,26 @@ async function run(io, explain, localOnly, jevOnly, deadline) {
|
|
|
508
3247
|
return printNote(io.stderr, true, "configuration unreadable; no verdict");
|
|
509
3248
|
}
|
|
510
3249
|
printWarnings(io.stderr, loaded.warnings);
|
|
511
|
-
const config = jevOnly ? buildJevOnlyConfig(loaded) : loaded;
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
return printNote(io.stderr, true, "Jev-only evaluation requires system-one; no verdict");
|
|
515
|
-
}
|
|
516
|
-
const deadlineAt = deadline === void 0 ? void 0 : Number(deadline);
|
|
517
|
-
if (deadlineAt !== void 0 && (!jevOnly || !Number.isSafeInteger(deadlineAt) || deadlineAt <= 0)) {
|
|
3250
|
+
const config = options.jevOnly ? buildJevOnlyConfig(loaded) : loaded;
|
|
3251
|
+
const deadlineAt = options.deadline === void 0 ? void 0 : Number(options.deadline);
|
|
3252
|
+
if (deadlineAt !== void 0 && (!options.jevOnly || !Number.isSafeInteger(deadlineAt) || deadlineAt <= 0)) {
|
|
518
3253
|
await writeActionFailure(request, host, io.stderr);
|
|
519
3254
|
return printNote(io.stderr, true, "invalid Jev evaluation deadline; no verdict");
|
|
520
3255
|
}
|
|
521
3256
|
const controller = new AbortController();
|
|
522
|
-
const unsubscribe = jevOnly ? io.subscribeToStopSignals(() => {
|
|
3257
|
+
const unsubscribe = options.jevOnly ? io.subscribeToStopSignals(() => {
|
|
523
3258
|
controller.abort();
|
|
524
3259
|
}) : null;
|
|
525
3260
|
try {
|
|
526
3261
|
const outcome = await classifyAction(request, config, {
|
|
527
|
-
localOnly,
|
|
3262
|
+
localOnly: options.localOnly,
|
|
528
3263
|
deadlineAt,
|
|
529
3264
|
signal: controller.signal,
|
|
530
|
-
|
|
3265
|
+
runtime: io.runtime,
|
|
531
3266
|
stderr: io.stderr,
|
|
532
3267
|
budget: true,
|
|
533
|
-
actionLog: true
|
|
3268
|
+
actionLog: true,
|
|
3269
|
+
judgeSession: buildJudgeServerSession(io, config, request.sessionID)
|
|
534
3270
|
});
|
|
535
3271
|
if (outcome.verdict !== null) printVerdict(io.stdout, outcome.verdict);
|
|
536
3272
|
if (config.capture?.enabled === true) {
|
|
@@ -544,7 +3280,7 @@ async function run(io, explain, localOnly, jevOnly, deadline) {
|
|
|
544
3280
|
});
|
|
545
3281
|
printCaptureNote(io.stderr, captured);
|
|
546
3282
|
}
|
|
547
|
-
return printNote(io.stderr, explain || outcome.unavailable === true, outcome.note);
|
|
3283
|
+
return printNote(io.stderr, options.explain || outcome.unavailable === true, outcome.note);
|
|
548
3284
|
} finally {
|
|
549
3285
|
unsubscribe?.();
|
|
550
3286
|
}
|
|
@@ -574,7 +3310,7 @@ async function runRecord(io) {
|
|
|
574
3310
|
const request = parseScopeRecordRequest(body);
|
|
575
3311
|
if (request === null) return 0;
|
|
576
3312
|
try {
|
|
577
|
-
await recordScope(request, await loadConfig(void 0, io.host),
|
|
3313
|
+
await recordScope(request, await loadConfig(void 0, io.runtime.host), io.runtime);
|
|
578
3314
|
} catch {
|
|
579
3315
|
io.stderr.write("auto-mode: session scope unavailable\n");
|
|
580
3316
|
}
|
|
@@ -586,7 +3322,7 @@ process.exitCode = await runCLI(process.argv.slice(2), {
|
|
|
586
3322
|
stdin: () => text(process.stdin),
|
|
587
3323
|
stdout: process.stdout,
|
|
588
3324
|
stderr: process.stderr,
|
|
589
|
-
|
|
3325
|
+
runtime: readRuntime(),
|
|
590
3326
|
subscribeToStopSignals: (onStop) => {
|
|
591
3327
|
process.on("SIGTERM", onStop);
|
|
592
3328
|
process.on("SIGINT", onStop);
|
|
@@ -594,7 +3330,8 @@ process.exitCode = await runCLI(process.argv.slice(2), {
|
|
|
594
3330
|
process.off("SIGTERM", onStop);
|
|
595
3331
|
process.off("SIGINT", onStop);
|
|
596
3332
|
};
|
|
597
|
-
}
|
|
3333
|
+
},
|
|
3334
|
+
judgeServerCommand: [process.execPath, import.meta.filename]
|
|
598
3335
|
});
|
|
599
3336
|
//#endregion
|
|
600
3337
|
export {};
|