@guanghechen/commander 4.7.7 → 4.7.9
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/CHANGELOG.md +14 -0
- package/README.md +7 -0
- package/lib/cjs/browser.cjs +2767 -1444
- package/lib/cjs/node.cjs +2767 -1444
- package/lib/esm/browser.mjs +2768 -1444
- package/lib/esm/node.mjs +2768 -1444
- package/lib/types/browser.d.ts +191 -125
- package/lib/types/node.d.ts +191 -125
- package/package.json +1 -1
package/lib/cjs/node.cjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
var promises = require('node:fs/promises');
|
|
4
4
|
var path = require('node:path');
|
|
5
|
-
var env = require('@guanghechen/env');
|
|
6
5
|
var reporter = require('@guanghechen/reporter');
|
|
6
|
+
var env = require('@guanghechen/env');
|
|
7
7
|
var fs = require('node:fs');
|
|
8
8
|
|
|
9
9
|
const WINDOWS_DRIVE_ABSOLUTE_REGEX = /^[a-zA-Z]:[\\/]/;
|
|
@@ -94,608 +94,1164 @@ function createNodeCommandRuntime() {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
const TERMINAL_STYLE = {
|
|
98
|
-
bold: '\x1b[1m',
|
|
99
|
-
italic: '\x1b[3m',
|
|
100
|
-
underline: '\x1b[4m',
|
|
101
|
-
cyan: '\x1b[36m',
|
|
102
|
-
dim: '\x1b[2m',
|
|
103
|
-
reset: '\x1b[0m',
|
|
104
|
-
};
|
|
105
|
-
function styleText(text, ...styles) {
|
|
106
|
-
return `${styles.join('')}${text}${TERMINAL_STYLE.reset}`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const BUILTIN_LOG_LEVELS = ['debug', 'info', 'hint', 'warn', 'error'];
|
|
110
|
-
function resolveReporterLogLevel(raw) {
|
|
111
|
-
const normalized = raw.trim().toLowerCase();
|
|
112
|
-
return BUILTIN_LOG_LEVELS.find(level => level === normalized);
|
|
113
|
-
}
|
|
114
|
-
function setReporterLevel(ctx, level) {
|
|
115
|
-
const reporter = ctx.reporter;
|
|
116
|
-
reporter?.setLevel?.(level);
|
|
117
|
-
}
|
|
118
|
-
function setReporterFlight(ctx, flight) {
|
|
119
|
-
const reporter = ctx.reporter;
|
|
120
|
-
reporter?.setFlight?.(flight);
|
|
121
|
-
}
|
|
122
|
-
const logLevelOption = {
|
|
123
|
-
long: 'logLevel',
|
|
124
|
-
type: 'string',
|
|
125
|
-
args: 'required',
|
|
126
|
-
desc: 'Set log level',
|
|
127
|
-
default: 'info',
|
|
128
|
-
choices: [...BUILTIN_LOG_LEVELS],
|
|
129
|
-
coerce: (raw) => {
|
|
130
|
-
const level = resolveReporterLogLevel(raw);
|
|
131
|
-
if (level === undefined) {
|
|
132
|
-
throw new Error(`Invalid log level: ${raw}`);
|
|
133
|
-
}
|
|
134
|
-
return level;
|
|
135
|
-
},
|
|
136
|
-
apply: (value, ctx) => {
|
|
137
|
-
setReporterLevel(ctx, value);
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
const logDateOption = {
|
|
141
|
-
long: 'logDate',
|
|
142
|
-
type: 'boolean',
|
|
143
|
-
args: 'none',
|
|
144
|
-
desc: 'Enable log timestamp',
|
|
145
|
-
default: true,
|
|
146
|
-
apply: (value, ctx) => {
|
|
147
|
-
setReporterFlight(ctx, { date: Boolean(value) });
|
|
148
|
-
},
|
|
149
|
-
};
|
|
150
|
-
const logColorfulOption = {
|
|
151
|
-
long: 'logColorful',
|
|
152
|
-
type: 'boolean',
|
|
153
|
-
args: 'none',
|
|
154
|
-
desc: 'Enable colorful log output',
|
|
155
|
-
default: true,
|
|
156
|
-
apply: (value, ctx) => {
|
|
157
|
-
setReporterFlight(ctx, { color: Boolean(value) });
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
const silentOption = {
|
|
161
|
-
long: 'silent',
|
|
162
|
-
type: 'boolean',
|
|
163
|
-
args: 'none',
|
|
164
|
-
desc: 'Suppress non-error output',
|
|
165
|
-
default: false,
|
|
166
|
-
apply: (value, ctx) => {
|
|
167
|
-
if (value) {
|
|
168
|
-
setReporterLevel(ctx, 'error');
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
};
|
|
172
|
-
|
|
173
97
|
class CommanderError extends Error {
|
|
174
98
|
kind;
|
|
175
99
|
commandPath;
|
|
176
|
-
|
|
100
|
+
meta;
|
|
101
|
+
constructor(kind, message, commandPath, meta) {
|
|
177
102
|
super(message);
|
|
178
103
|
this.name = 'CommanderError';
|
|
179
104
|
this.kind = kind;
|
|
180
105
|
this.commandPath = commandPath;
|
|
106
|
+
this.meta = meta;
|
|
107
|
+
}
|
|
108
|
+
withIssue(issue) {
|
|
109
|
+
return this.withIssues([issue]);
|
|
110
|
+
}
|
|
111
|
+
withIssues(issues) {
|
|
112
|
+
if (issues.length === 0) {
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
const nextMeta = {
|
|
116
|
+
commandPath: this.meta?.commandPath ?? this.commandPath,
|
|
117
|
+
token: this.meta?.token,
|
|
118
|
+
option: this.meta?.option,
|
|
119
|
+
argument: this.meta?.argument,
|
|
120
|
+
issues: [...(this.meta?.issues ?? []), ...issues],
|
|
121
|
+
};
|
|
122
|
+
return new CommanderError(this.kind, this.message, this.commandPath, nextMeta);
|
|
181
123
|
}
|
|
182
124
|
format() {
|
|
125
|
+
const issues = this.meta?.issues ?? [];
|
|
126
|
+
if (issues.length > 0) {
|
|
127
|
+
const primary = issues.find(issue => issue.kind === 'error') ?? issues[0];
|
|
128
|
+
const lines = [`Error: ${primary.reason.message}`];
|
|
129
|
+
for (const issue of issues) {
|
|
130
|
+
if (issue.kind !== 'hint')
|
|
131
|
+
continue;
|
|
132
|
+
const message = issue.reason.message.startsWith('Hint:')
|
|
133
|
+
? issue.reason.message
|
|
134
|
+
: `Hint: ${issue.reason.message}`;
|
|
135
|
+
lines.push(message);
|
|
136
|
+
}
|
|
137
|
+
lines.push(`Run "${this.commandPath} --help" for usage.`);
|
|
138
|
+
return lines.join('\n');
|
|
139
|
+
}
|
|
183
140
|
return `Error: ${this.message}\nRun "${this.commandPath} --help" for usage.`;
|
|
184
141
|
}
|
|
185
142
|
}
|
|
186
143
|
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const PRESET_SELECTOR_DELIMITER = ':';
|
|
192
|
-
function kebabToCamelCase(str) {
|
|
193
|
-
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
194
|
-
}
|
|
195
|
-
function camelToKebabCase$1(str) {
|
|
196
|
-
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
197
|
-
}
|
|
198
|
-
const ANSI_ESCAPE_REGEX = new RegExp(String.raw `\\x1B\\[[0-?]*[ -/]*[@-~]`, 'g');
|
|
199
|
-
const DECIMAL_INTEGER_REGEX = /^\d(?:_?\d)*$/;
|
|
200
|
-
const DECIMAL_FRACTION_REGEX = /^\d(?:_?\d)*$/;
|
|
201
|
-
const DECIMAL_EXPONENT_REGEX = /^[eE][+-]?\d(?:_?\d)*$/;
|
|
202
|
-
const BINARY_LITERAL_REGEX = /^0[bB][01](?:_?[01])*$/;
|
|
203
|
-
const OCTAL_LITERAL_REGEX = /^0[oO][0-7](?:_?[0-7])*$/;
|
|
204
|
-
const HEX_LITERAL_REGEX = /^0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*$/;
|
|
205
|
-
const PRESET_PROFILE_NAME_REGEX = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
206
|
-
const PRESET_VARIANT_NAME_REGEX = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
207
|
-
function stripAnsi(value) {
|
|
208
|
-
return value.replace(ANSI_ESCAPE_REGEX, '');
|
|
209
|
-
}
|
|
210
|
-
function isCombiningMark(codePoint) {
|
|
211
|
-
return ((codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
212
|
-
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
213
|
-
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
214
|
-
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
215
|
-
(codePoint >= 0xfe20 && codePoint <= 0xfe2f));
|
|
216
|
-
}
|
|
217
|
-
function isWideCodePoint(codePoint) {
|
|
218
|
-
if (codePoint < 0x1100) {
|
|
219
|
-
return false;
|
|
144
|
+
async function runCommandAction(params) {
|
|
145
|
+
const { action, actionParams, commandPath } = params;
|
|
146
|
+
if (action === undefined) {
|
|
147
|
+
return;
|
|
220
148
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
(
|
|
226
|
-
|
|
227
|
-
(codePoint >= 0xa960 && codePoint <= 0xa97c) ||
|
|
228
|
-
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
229
|
-
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
230
|
-
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
231
|
-
(codePoint >= 0xfe30 && codePoint <= 0xfe6b) ||
|
|
232
|
-
(codePoint >= 0xff01 && codePoint <= 0xff60) ||
|
|
233
|
-
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
234
|
-
(codePoint >= 0x1b000 && codePoint <= 0x1b001) ||
|
|
235
|
-
(codePoint >= 0x1f200 && codePoint <= 0x1f251) ||
|
|
236
|
-
(codePoint >= 0x20000 && codePoint <= 0x3fffd));
|
|
237
|
-
}
|
|
238
|
-
function getDisplayWidth(value) {
|
|
239
|
-
const normalized = stripAnsi(value).normalize('NFC');
|
|
240
|
-
let width = 0;
|
|
241
|
-
for (const char of normalized) {
|
|
242
|
-
const codePoint = char.codePointAt(0);
|
|
243
|
-
if (codePoint === undefined || isCombiningMark(codePoint)) {
|
|
244
|
-
continue;
|
|
149
|
+
try {
|
|
150
|
+
await action(actionParams);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
if (err instanceof CommanderError) {
|
|
154
|
+
throw err;
|
|
245
155
|
}
|
|
246
|
-
|
|
156
|
+
const issue = {
|
|
157
|
+
kind: 'error',
|
|
158
|
+
stage: 'run',
|
|
159
|
+
scope: 'action',
|
|
160
|
+
reason: {
|
|
161
|
+
code: 'action_failed',
|
|
162
|
+
message: err instanceof Error ? err.message : 'action failed',
|
|
163
|
+
details: err instanceof Error
|
|
164
|
+
? {
|
|
165
|
+
errorName: err.name,
|
|
166
|
+
errorMessage: err.message,
|
|
167
|
+
}
|
|
168
|
+
: {
|
|
169
|
+
errorValue: String(err),
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
throw new CommanderError('ActionFailed', err instanceof Error ? err.message : 'action failed', commandPath).withIssue(issue);
|
|
247
174
|
}
|
|
248
|
-
return width;
|
|
249
175
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
176
|
+
|
|
177
|
+
const BUILTIN_HELP_OPTION = {
|
|
178
|
+
long: 'help',
|
|
179
|
+
type: 'boolean',
|
|
180
|
+
args: 'none',
|
|
181
|
+
desc: 'Show help information',
|
|
182
|
+
};
|
|
183
|
+
const BUILTIN_VERSION_OPTION = {
|
|
184
|
+
long: 'version',
|
|
185
|
+
type: 'boolean',
|
|
186
|
+
args: 'none',
|
|
187
|
+
desc: 'Show version number',
|
|
188
|
+
};
|
|
189
|
+
function errorKindToIssueScope(kind) {
|
|
190
|
+
switch (kind) {
|
|
191
|
+
case 'UnknownSubcommand':
|
|
192
|
+
return 'command';
|
|
193
|
+
case 'MissingRequiredArgument':
|
|
194
|
+
case 'TooManyArguments':
|
|
195
|
+
case 'UnexpectedArgument':
|
|
196
|
+
return 'argument';
|
|
197
|
+
case 'ActionFailed':
|
|
198
|
+
return 'action';
|
|
199
|
+
case 'ConfigurationError':
|
|
200
|
+
return 'runtime';
|
|
201
|
+
default:
|
|
202
|
+
return 'option';
|
|
254
203
|
}
|
|
255
|
-
return value + ' '.repeat(targetWidth - width);
|
|
256
204
|
}
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
205
|
+
function createBuiltinOptionState(enabled) {
|
|
206
|
+
return {
|
|
207
|
+
version: enabled,
|
|
208
|
+
color: enabled,
|
|
209
|
+
devmode: enabled,
|
|
210
|
+
logLevel: enabled,
|
|
211
|
+
silent: enabled,
|
|
212
|
+
logDate: enabled,
|
|
213
|
+
logColorful: enabled,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function normalizeBuiltinConfig(builtin) {
|
|
217
|
+
const resolved = {
|
|
218
|
+
option: createBuiltinOptionState(true),
|
|
219
|
+
};
|
|
220
|
+
if (builtin === undefined) {
|
|
221
|
+
return resolved;
|
|
272
222
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
223
|
+
if (builtin === true) {
|
|
224
|
+
return {
|
|
225
|
+
option: createBuiltinOptionState(true),
|
|
226
|
+
};
|
|
277
227
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return false;
|
|
228
|
+
if (builtin === false) {
|
|
229
|
+
return {
|
|
230
|
+
option: createBuiltinOptionState(false),
|
|
231
|
+
};
|
|
283
232
|
}
|
|
284
|
-
if (
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
return false;
|
|
233
|
+
if (builtin.option !== undefined) {
|
|
234
|
+
if (builtin.option === false) {
|
|
235
|
+
resolved.option = createBuiltinOptionState(false);
|
|
288
236
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
237
|
+
else if (builtin.option === true) {
|
|
238
|
+
resolved.option = createBuiltinOptionState(true);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
if (builtin.option.version !== undefined)
|
|
242
|
+
resolved.option.version = builtin.option.version;
|
|
243
|
+
if (builtin.option.color !== undefined)
|
|
244
|
+
resolved.option.color = builtin.option.color;
|
|
245
|
+
if (builtin.option.devmode !== undefined)
|
|
246
|
+
resolved.option.devmode = builtin.option.devmode;
|
|
247
|
+
if (builtin.option.logLevel !== undefined) {
|
|
248
|
+
resolved.option.logLevel = builtin.option.logLevel;
|
|
249
|
+
}
|
|
250
|
+
if (builtin.option.silent !== undefined)
|
|
251
|
+
resolved.option.silent = builtin.option.silent;
|
|
252
|
+
if (builtin.option.logDate !== undefined)
|
|
253
|
+
resolved.option.logDate = builtin.option.logDate;
|
|
254
|
+
if (builtin.option.logColorful !== undefined) {
|
|
255
|
+
resolved.option.logColorful = builtin.option.logColorful;
|
|
256
|
+
}
|
|
294
257
|
}
|
|
295
|
-
return intPart.length > 0 || fracPart.length > 0;
|
|
296
258
|
}
|
|
297
|
-
return
|
|
259
|
+
return resolved;
|
|
298
260
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
261
|
+
|
|
262
|
+
function createCommandContext(params) {
|
|
263
|
+
const { leafCommand, chain, cmds, envs, reporter } = params;
|
|
264
|
+
const envSnapshot = { ...envs };
|
|
265
|
+
return {
|
|
266
|
+
cmd: leafCommand,
|
|
267
|
+
chain,
|
|
268
|
+
envs: envSnapshot,
|
|
269
|
+
controls: { help: false, version: false },
|
|
270
|
+
sources: {
|
|
271
|
+
preset: {
|
|
272
|
+
state: 'none',
|
|
273
|
+
argv: [],
|
|
274
|
+
envs: {},
|
|
275
|
+
},
|
|
276
|
+
user: {
|
|
277
|
+
cmds: [...cmds],
|
|
278
|
+
argv: [],
|
|
279
|
+
envs: envSnapshot,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
reporter,
|
|
283
|
+
};
|
|
309
284
|
}
|
|
310
|
-
function
|
|
311
|
-
return
|
|
285
|
+
function freezeInputSources(sources) {
|
|
286
|
+
return Object.freeze({
|
|
287
|
+
preset: Object.freeze({
|
|
288
|
+
state: sources.preset.state,
|
|
289
|
+
argv: Object.freeze([...sources.preset.argv]),
|
|
290
|
+
envs: Object.freeze({ ...sources.preset.envs }),
|
|
291
|
+
meta: sources.preset.meta === undefined ? undefined : Object.freeze({ ...sources.preset.meta }),
|
|
292
|
+
}),
|
|
293
|
+
user: Object.freeze({
|
|
294
|
+
cmds: Object.freeze([...sources.user.cmds]),
|
|
295
|
+
argv: Object.freeze([...sources.user.argv]),
|
|
296
|
+
envs: Object.freeze({ ...sources.user.envs }),
|
|
297
|
+
}),
|
|
298
|
+
});
|
|
312
299
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
300
|
+
|
|
301
|
+
function buildHelpSubcommands(params) {
|
|
302
|
+
const { entries, getDescription } = params;
|
|
303
|
+
return entries.map(entry => ({
|
|
304
|
+
name: entry.name,
|
|
305
|
+
aliases: entry.aliases,
|
|
306
|
+
desc: getDescription(entry.command),
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
function buildCompletionMeta(params) {
|
|
310
|
+
const { name, desc, mergedOptions, arguments_, supportsBuiltinVersion, builtinHelpOption, builtinVersionOption, subcommands, resolveSubcommandMeta, } = params;
|
|
311
|
+
const optionMap = new Map();
|
|
312
|
+
for (const option of mergedOptions) {
|
|
313
|
+
optionMap.set(option.long, option);
|
|
314
|
+
}
|
|
315
|
+
optionMap.set('help', builtinHelpOption);
|
|
316
|
+
if (supportsBuiltinVersion) {
|
|
317
|
+
optionMap.set('version', builtinVersionOption);
|
|
318
|
+
}
|
|
319
|
+
const options = Array.from(optionMap.values()).map(option => ({
|
|
320
|
+
long: option.long,
|
|
321
|
+
short: option.short,
|
|
322
|
+
desc: option.desc,
|
|
323
|
+
type: option.type,
|
|
324
|
+
args: option.args,
|
|
325
|
+
choices: option.choices?.map(choice => String(choice)),
|
|
326
|
+
}));
|
|
327
|
+
const argumentsMeta = arguments_.map(argument => ({
|
|
328
|
+
name: argument.name,
|
|
329
|
+
kind: argument.kind,
|
|
330
|
+
type: argument.type,
|
|
331
|
+
choices: argument.type === 'choice' ? argument.choices?.map(choice => String(choice)) : undefined,
|
|
332
|
+
}));
|
|
333
|
+
return {
|
|
334
|
+
name,
|
|
335
|
+
desc,
|
|
336
|
+
aliases: [],
|
|
337
|
+
options,
|
|
338
|
+
arguments: argumentsMeta,
|
|
339
|
+
subcommands: subcommands.map(entry => {
|
|
340
|
+
const subMeta = resolveSubcommandMeta(entry.command);
|
|
341
|
+
return {
|
|
342
|
+
...subMeta,
|
|
343
|
+
name: entry.name,
|
|
344
|
+
aliases: entry.aliases,
|
|
345
|
+
};
|
|
346
|
+
}),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function resolvePresetFileAbsolutePath(params) {
|
|
351
|
+
const { runtime, filepath, baseDirectory } = params;
|
|
352
|
+
if (runtime.isAbsolute(filepath)) {
|
|
353
|
+
return filepath;
|
|
322
354
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
355
|
+
return runtime.resolve(baseDirectory ?? runtime.cwd(), filepath);
|
|
356
|
+
}
|
|
357
|
+
async function readPresetFile(params) {
|
|
358
|
+
const { runtime, file, commandPath } = params;
|
|
359
|
+
try {
|
|
360
|
+
const stats = await runtime.stat(file.absolutePath);
|
|
361
|
+
if (stats.isDirectory()) {
|
|
362
|
+
throw new Error('target is a directory');
|
|
363
|
+
}
|
|
364
|
+
return await runtime.readFile(file.absolutePath);
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
const ioError = error;
|
|
368
|
+
if (!file.explicit && ioError.code === 'ENOENT') {
|
|
369
|
+
return undefined;
|
|
329
370
|
}
|
|
330
|
-
|
|
371
|
+
throw new CommanderError('ConfigurationError', `failed to read preset file "${file.displayPath}": ${error.message}`, commandPath);
|
|
331
372
|
}
|
|
332
|
-
return prev[right.length];
|
|
333
373
|
}
|
|
334
|
-
function
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (namePart.includes('_')) {
|
|
339
|
-
throw new CommanderError('InvalidOptionFormat', `invalid option "${arg}": use '-' instead of '_'`, commandPath);
|
|
374
|
+
function parsePresetEnvsContent(params) {
|
|
375
|
+
const { content, file, commandPath } = params;
|
|
376
|
+
try {
|
|
377
|
+
return env.parse(content);
|
|
340
378
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
throw new CommanderError('InvalidNegativeOption', `invalid negative option syntax "${arg}"`, commandPath);
|
|
379
|
+
catch (error) {
|
|
380
|
+
throw new CommanderError('ConfigurationError', `failed to parse preset env file "${file.displayPath}": ${error.message}`, commandPath);
|
|
344
381
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
382
|
+
}
|
|
383
|
+
async function buildPresetProfileInputs(params) {
|
|
384
|
+
const { runtime, commandPath, resolvedProfile, validatePresetOptionTokens } = params;
|
|
385
|
+
const presetArgv = [];
|
|
386
|
+
if (resolvedProfile !== undefined && resolvedProfile.optsArgv.length > 0) {
|
|
387
|
+
validatePresetOptionTokens(resolvedProfile.optsArgv, resolvedProfile.optsSourceLabel, commandPath);
|
|
388
|
+
presetArgv.push(...resolvedProfile.optsArgv);
|
|
389
|
+
}
|
|
390
|
+
const presetEnvs = {};
|
|
391
|
+
if (resolvedProfile !== undefined) {
|
|
392
|
+
if (resolvedProfile.profileEnvFileSource !== undefined) {
|
|
393
|
+
const content = await readPresetFile({
|
|
394
|
+
runtime,
|
|
395
|
+
file: resolvedProfile.profileEnvFileSource,
|
|
396
|
+
commandPath,
|
|
397
|
+
});
|
|
398
|
+
if (content !== undefined) {
|
|
399
|
+
Object.assign(presetEnvs, parsePresetEnvsContent({
|
|
400
|
+
content,
|
|
401
|
+
file: resolvedProfile.profileEnvFileSource,
|
|
402
|
+
commandPath,
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
Object.assign(presetEnvs, resolvedProfile.profileInlineEnvs);
|
|
407
|
+
if (resolvedProfile.variantEnvFileSource !== undefined) {
|
|
408
|
+
const content = await readPresetFile({
|
|
409
|
+
runtime,
|
|
410
|
+
file: resolvedProfile.variantEnvFileSource,
|
|
411
|
+
commandPath,
|
|
412
|
+
});
|
|
413
|
+
if (content !== undefined) {
|
|
414
|
+
Object.assign(presetEnvs, parsePresetEnvsContent({
|
|
415
|
+
content,
|
|
416
|
+
file: resolvedProfile.variantEnvFileSource,
|
|
417
|
+
commandPath,
|
|
418
|
+
}));
|
|
419
|
+
}
|
|
351
420
|
}
|
|
352
|
-
|
|
353
|
-
return {
|
|
354
|
-
original: arg,
|
|
355
|
-
resolved: `--${camelName}=false`,
|
|
356
|
-
name: camelName,
|
|
357
|
-
type: 'long',
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
if (!LONG_OPTION_REGEX.test(lowerName)) {
|
|
361
|
-
throw new CommanderError('InvalidOptionFormat', `invalid option format "${arg}"`, commandPath);
|
|
421
|
+
Object.assign(presetEnvs, resolvedProfile.variantInlineEnvs);
|
|
362
422
|
}
|
|
363
|
-
|
|
364
|
-
return {
|
|
365
|
-
original: arg,
|
|
366
|
-
resolved: `--${camelName}${valuePart}`,
|
|
367
|
-
name: camelName,
|
|
368
|
-
type: 'long',
|
|
369
|
-
};
|
|
423
|
+
return { presetArgv, presetEnvs };
|
|
370
424
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
const flags = arg.slice(1);
|
|
376
|
-
return flags.split('').map(flag => ({
|
|
377
|
-
original: `-${flag}`,
|
|
378
|
-
resolved: `-${flag}`,
|
|
379
|
-
name: flag,
|
|
380
|
-
type: 'short',
|
|
381
|
-
}));
|
|
425
|
+
|
|
426
|
+
function getExitCode(error) {
|
|
427
|
+
return error.kind === 'ActionFailed' ? 1 : 2;
|
|
382
428
|
}
|
|
383
|
-
function
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
429
|
+
function renderTermination(params) {
|
|
430
|
+
const { termination, argv, envs, route, controlScan, findCommandByPath, resolveHelpCommand, resolveHelpColor, formatHelpForDisplay, print, } = params;
|
|
431
|
+
if (termination.kind === 'version') {
|
|
432
|
+
print(termination.version);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const routeResult = route(argv);
|
|
436
|
+
const leafCommand = routeResult.chain[routeResult.chain.length - 1];
|
|
437
|
+
const controlScanResult = controlScan(routeResult.remaining, leafCommand);
|
|
438
|
+
const helpCommand = findCommandByPath(termination.targetCommandPath) ??
|
|
439
|
+
resolveHelpCommand(leafCommand, controlScanResult.helpTarget);
|
|
440
|
+
const helpColor = resolveHelpColor(helpCommand, controlScanResult.remaining, envs);
|
|
441
|
+
print(formatHelpForDisplay(helpCommand, helpColor));
|
|
442
|
+
}
|
|
443
|
+
function handleRunOutcome(params) {
|
|
444
|
+
const { outcome, argv, envs, route, controlScan, findCommandByPath, resolveHelpCommand, resolveHelpColor, formatHelpForDisplay, print, printError, exit, normalizeControlRunError, } = params;
|
|
445
|
+
if (outcome.kind === 'parsed') {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (outcome.kind === 'terminated') {
|
|
449
|
+
try {
|
|
450
|
+
renderTermination({
|
|
451
|
+
termination: outcome.termination,
|
|
452
|
+
argv,
|
|
453
|
+
envs,
|
|
454
|
+
route,
|
|
455
|
+
controlScan,
|
|
456
|
+
findCommandByPath,
|
|
457
|
+
resolveHelpCommand,
|
|
458
|
+
resolveHelpColor,
|
|
459
|
+
formatHelpForDisplay,
|
|
460
|
+
print,
|
|
461
|
+
});
|
|
462
|
+
return;
|
|
399
463
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
464
|
+
catch (error) {
|
|
465
|
+
if (error instanceof CommanderError) {
|
|
466
|
+
const normalizedError = normalizeControlRunError(error);
|
|
467
|
+
printError(normalizedError.format());
|
|
468
|
+
exit(getExitCode(normalizedError));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
throw error;
|
|
403
472
|
}
|
|
404
|
-
optionTokens.push({
|
|
405
|
-
original: arg,
|
|
406
|
-
resolved: arg,
|
|
407
|
-
name: '',
|
|
408
|
-
type: 'none',
|
|
409
|
-
});
|
|
410
473
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const BUILTIN_HELP_OPTION = {
|
|
414
|
-
long: 'help',
|
|
415
|
-
type: 'boolean',
|
|
416
|
-
args: 'none',
|
|
417
|
-
desc: 'Show help information',
|
|
418
|
-
};
|
|
419
|
-
const BUILTIN_VERSION_OPTION = {
|
|
420
|
-
long: 'version',
|
|
421
|
-
type: 'boolean',
|
|
422
|
-
args: 'none',
|
|
423
|
-
desc: 'Show version number',
|
|
424
|
-
};
|
|
425
|
-
const BUILTIN_COLOR_OPTION = {
|
|
426
|
-
long: 'color',
|
|
427
|
-
type: 'boolean',
|
|
428
|
-
args: 'none',
|
|
429
|
-
desc: 'Enable colored help output',
|
|
430
|
-
default: true,
|
|
431
|
-
};
|
|
432
|
-
function createBuiltinOptionState(enabled) {
|
|
433
|
-
return {
|
|
434
|
-
version: enabled,
|
|
435
|
-
color: enabled,
|
|
436
|
-
logLevel: enabled,
|
|
437
|
-
silent: enabled,
|
|
438
|
-
logDate: enabled,
|
|
439
|
-
logColorful: enabled,
|
|
440
|
-
};
|
|
474
|
+
printError(outcome.error.format());
|
|
475
|
+
exit(getExitCode(outcome.error));
|
|
441
476
|
}
|
|
442
|
-
function
|
|
443
|
-
|
|
477
|
+
function unwrapParseOutcome(params) {
|
|
478
|
+
const { outcome, commandPath } = params;
|
|
479
|
+
if (outcome.kind === 'parsed') {
|
|
480
|
+
return outcome.parseResult;
|
|
481
|
+
}
|
|
482
|
+
if (outcome.kind === 'terminated') {
|
|
483
|
+
throw new CommanderError('ConfigurationError', 'internal invariant violation: parse mode must not produce termination', commandPath);
|
|
484
|
+
}
|
|
485
|
+
throw outcome.error;
|
|
444
486
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
487
|
+
|
|
488
|
+
function validateOptionConfig(params) {
|
|
489
|
+
const { opt, commandPath } = params;
|
|
490
|
+
if (opt.long === 'help' ||
|
|
491
|
+
opt.long === 'version' ||
|
|
492
|
+
opt.long === 'devmode' ||
|
|
493
|
+
opt.long === 'logLevel') {
|
|
494
|
+
throw new CommanderError('ConfigurationError', `option long name "${opt.long}" is reserved`, commandPath);
|
|
451
495
|
}
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
option: createBuiltinOptionState(true),
|
|
455
|
-
};
|
|
496
|
+
if (opt.type === 'boolean' && opt.args !== 'none') {
|
|
497
|
+
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, commandPath);
|
|
456
498
|
}
|
|
457
|
-
if (
|
|
458
|
-
|
|
459
|
-
option: createBuiltinOptionState(false),
|
|
460
|
-
};
|
|
499
|
+
if ((opt.type === 'string' || opt.type === 'number') && opt.args === 'none') {
|
|
500
|
+
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required', 'optional', or 'variadic'`, commandPath);
|
|
461
501
|
}
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
resolved.option = createBuiltinOptionState(false);
|
|
465
|
-
}
|
|
466
|
-
else if (builtin.option === true) {
|
|
467
|
-
resolved.option = createBuiltinOptionState(true);
|
|
468
|
-
}
|
|
469
|
-
else {
|
|
470
|
-
if (builtin.option.version !== undefined)
|
|
471
|
-
resolved.option.version = builtin.option.version;
|
|
472
|
-
if (builtin.option.color !== undefined)
|
|
473
|
-
resolved.option.color = builtin.option.color;
|
|
474
|
-
if (builtin.option.logLevel !== undefined) {
|
|
475
|
-
resolved.option.logLevel = builtin.option.logLevel;
|
|
476
|
-
}
|
|
477
|
-
if (builtin.option.silent !== undefined)
|
|
478
|
-
resolved.option.silent = builtin.option.silent;
|
|
479
|
-
if (builtin.option.logDate !== undefined)
|
|
480
|
-
resolved.option.logDate = builtin.option.logDate;
|
|
481
|
-
if (builtin.option.logColorful !== undefined) {
|
|
482
|
-
resolved.option.logColorful = builtin.option.logColorful;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
502
|
+
if (opt.type === 'number' && opt.args === 'optional') {
|
|
503
|
+
throw new CommanderError('ConfigurationError', `number option "--${opt.long}" does not support args: 'optional'`, commandPath);
|
|
485
504
|
}
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
class Command {
|
|
489
|
-
#name;
|
|
490
|
-
#desc;
|
|
491
|
-
#version;
|
|
492
|
-
#builtinConfig;
|
|
493
|
-
#builtin;
|
|
494
|
-
#presetConfig;
|
|
495
|
-
#reporter;
|
|
496
|
-
#runtime;
|
|
497
|
-
#parent;
|
|
498
|
-
#options = [];
|
|
499
|
-
#arguments = [];
|
|
500
|
-
#examples = [];
|
|
501
|
-
#subcommandsList = [];
|
|
502
|
-
#subcommandsMap = new Map();
|
|
503
|
-
#action = undefined;
|
|
504
|
-
constructor(config) {
|
|
505
|
-
this.#name = config.name ?? '';
|
|
506
|
-
this.#desc = config.desc;
|
|
507
|
-
this.#version = config.version;
|
|
508
|
-
this.#builtinConfig = config.builtin;
|
|
509
|
-
this.#builtin = normalizeBuiltinConfig(config.builtin);
|
|
510
|
-
this.#presetConfig = config.preset;
|
|
511
|
-
this.#reporter = config.reporter;
|
|
512
|
-
this.#runtime = config.runtime ?? getDefaultCommandRuntime();
|
|
505
|
+
if (opt.long.startsWith('no')) {
|
|
506
|
+
throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, commandPath);
|
|
513
507
|
}
|
|
514
|
-
|
|
515
|
-
|
|
508
|
+
if (!/^[a-z][a-zA-Z0-9]*$/.test(opt.long)) {
|
|
509
|
+
throw new CommanderError('ConfigurationError', `option long name must be camelCase: "${opt.long}"`, commandPath);
|
|
516
510
|
}
|
|
517
|
-
|
|
518
|
-
|
|
511
|
+
if (opt.short !== undefined && opt.short.length !== 1) {
|
|
512
|
+
throw new CommanderError('ConfigurationError', `option short name must be a single character: "${opt.short}"`, commandPath);
|
|
519
513
|
}
|
|
520
|
-
|
|
521
|
-
|
|
514
|
+
if (opt.required && opt.default !== undefined) {
|
|
515
|
+
throw new CommanderError('ConfigurationError', `option "--${opt.long}" cannot be both required and have a default value`, commandPath);
|
|
522
516
|
}
|
|
523
|
-
|
|
524
|
-
|
|
517
|
+
if (opt.type === 'boolean' && opt.required) {
|
|
518
|
+
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" cannot be required`, commandPath);
|
|
525
519
|
}
|
|
526
|
-
|
|
527
|
-
|
|
520
|
+
if (opt.required && opt.args !== 'required') {
|
|
521
|
+
throw new CommanderError('ConfigurationError', `required option "--${opt.long}" must use args: 'required'`, commandPath);
|
|
528
522
|
}
|
|
529
|
-
|
|
530
|
-
|
|
523
|
+
}
|
|
524
|
+
function checkOptionUniqueness(params) {
|
|
525
|
+
const { opt, options, commandPath } = params;
|
|
526
|
+
if (options.some(o => o.long === opt.long)) {
|
|
527
|
+
throw new CommanderError('OptionConflict', `option "--${opt.long}" is already defined`, commandPath);
|
|
531
528
|
}
|
|
532
|
-
|
|
533
|
-
|
|
529
|
+
if (opt.short && options.some(o => o.short === opt.short)) {
|
|
530
|
+
throw new CommanderError('OptionConflict', `short option "-${opt.short}" is already defined`, commandPath);
|
|
534
531
|
}
|
|
535
|
-
|
|
536
|
-
|
|
532
|
+
}
|
|
533
|
+
function validateArgumentConfig(params) {
|
|
534
|
+
const { arg, arguments_, commandPath } = params;
|
|
535
|
+
if (arg.kind !== 'required' &&
|
|
536
|
+
arg.kind !== 'optional' &&
|
|
537
|
+
arg.kind !== 'variadic' &&
|
|
538
|
+
arg.kind !== 'some') {
|
|
539
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" must specify a valid kind`, commandPath);
|
|
537
540
|
}
|
|
538
|
-
|
|
539
|
-
|
|
541
|
+
if (arg.type !== 'string' && arg.type !== 'choice') {
|
|
542
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" must specify a valid type`, commandPath);
|
|
540
543
|
}
|
|
541
|
-
|
|
542
|
-
|
|
544
|
+
if (arg.default !== undefined && arg.kind !== 'optional') {
|
|
545
|
+
throw new CommanderError('ConfigurationError', `only optional argument "${arg.name}" can have a default value`, commandPath);
|
|
543
546
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
this.#checkOptionUniqueness(opt);
|
|
547
|
-
this.#options.push(opt);
|
|
548
|
-
return this;
|
|
547
|
+
if (arg.type === 'string' && arg.choices !== undefined) {
|
|
548
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" of type "string" cannot declare choices`, commandPath);
|
|
549
549
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
550
|
+
if (arg.type === 'choice') {
|
|
551
|
+
if (!Array.isArray(arg.choices) || arg.choices.length === 0) {
|
|
552
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" of type "choice" must declare a non-empty choices array`, commandPath);
|
|
553
|
+
}
|
|
554
|
+
if (arg.choices.some(choice => typeof choice !== 'string')) {
|
|
555
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" choices must be string[]`, commandPath);
|
|
556
|
+
}
|
|
554
557
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
return this;
|
|
558
|
+
if (arg.default !== undefined) {
|
|
559
|
+
validateArgumentDefaultValue({ arg, commandPath });
|
|
558
560
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
561
|
+
if (arg.kind === 'variadic' || arg.kind === 'some') {
|
|
562
|
+
if (arguments_.some(a => a.kind === 'variadic' || a.kind === 'some')) {
|
|
563
|
+
throw new CommanderError('ConfigurationError', 'only one variadic/some argument is allowed', commandPath);
|
|
564
|
+
}
|
|
562
565
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
+
if (arguments_.length > 0) {
|
|
567
|
+
const last = arguments_[arguments_.length - 1];
|
|
568
|
+
if (last.kind === 'variadic' || last.kind === 'some') {
|
|
569
|
+
throw new CommanderError('ConfigurationError', 'variadic/some argument must be the last argument', commandPath);
|
|
566
570
|
}
|
|
567
|
-
|
|
568
|
-
|
|
571
|
+
}
|
|
572
|
+
if (arg.kind === 'required') {
|
|
573
|
+
const hasOptional = arguments_.some(a => a.kind === 'optional' || a.kind === 'variadic' || a.kind === 'some');
|
|
574
|
+
if (hasOptional) {
|
|
575
|
+
throw new CommanderError('ConfigurationError', `required argument "${arg.name}" cannot come after optional/variadic/some arguments`, commandPath);
|
|
569
576
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function validateArgumentDefaultValue(params) {
|
|
580
|
+
const { arg, commandPath } = params;
|
|
581
|
+
if (typeof arg.default !== 'string') {
|
|
582
|
+
throw new CommanderError('ConfigurationError', `default value for argument "${arg.name}" must match type "${arg.type}"`, commandPath);
|
|
583
|
+
}
|
|
584
|
+
if (arg.type === 'choice') {
|
|
585
|
+
const choices = arg.choices ?? [];
|
|
586
|
+
if (!choices.includes(arg.default)) {
|
|
587
|
+
throw new CommanderError('ConfigurationError', `default value for argument "${arg.name}" must be one of declared choices`, commandPath);
|
|
573
588
|
}
|
|
574
|
-
const existing = this.#subcommandsList.find(e => e.command === cmd);
|
|
575
|
-
if (existing) {
|
|
576
|
-
if (existing.name === name || existing.aliases.includes(name)) {
|
|
577
|
-
return this;
|
|
578
|
-
}
|
|
579
|
-
existing.aliases.push(name);
|
|
580
|
-
this.#subcommandsMap.set(name, cmd);
|
|
581
|
-
}
|
|
582
|
-
else {
|
|
583
|
-
cmd.#name = name;
|
|
584
|
-
cmd.#parent = this;
|
|
585
|
-
this.#subcommandsList.push({ name, aliases: [], command: cmd });
|
|
586
|
-
this.#subcommandsMap.set(name, cmd);
|
|
587
|
-
}
|
|
588
|
-
return this;
|
|
589
589
|
}
|
|
590
|
-
|
|
590
|
+
}
|
|
591
|
+
function normalizeExample(params) {
|
|
592
|
+
const { example, commandPath } = params;
|
|
593
|
+
const title = example.title.trim();
|
|
594
|
+
const usage = example.usage.trim();
|
|
595
|
+
const desc = example.desc.trim();
|
|
596
|
+
if (!title) {
|
|
597
|
+
throw new CommanderError('ConfigurationError', 'example title cannot be empty', commandPath);
|
|
598
|
+
}
|
|
599
|
+
if (!usage) {
|
|
600
|
+
throw new CommanderError('ConfigurationError', 'example usage cannot be empty', commandPath);
|
|
601
|
+
}
|
|
602
|
+
if (!desc) {
|
|
603
|
+
throw new CommanderError('ConfigurationError', 'example description cannot be empty', commandPath);
|
|
604
|
+
}
|
|
605
|
+
return { title, usage, desc };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
class CommandKernel {
|
|
609
|
+
#port;
|
|
610
|
+
#diagnostics;
|
|
611
|
+
#contextAdapter;
|
|
612
|
+
constructor(params) {
|
|
613
|
+
this.#port = params.port;
|
|
614
|
+
this.#diagnostics = params.diagnostics;
|
|
615
|
+
this.#contextAdapter = params.contextAdapter;
|
|
616
|
+
}
|
|
617
|
+
async execute(params, mode) {
|
|
591
618
|
const { argv, envs, reporter } = params;
|
|
592
619
|
try {
|
|
593
|
-
const routeResult = this.#route(argv);
|
|
620
|
+
const routeResult = this.#port.route(argv);
|
|
594
621
|
const { chain } = routeResult;
|
|
595
622
|
const leafCommand = chain[chain.length - 1];
|
|
596
|
-
|
|
623
|
+
let ctx = this.#port.createContext({
|
|
597
624
|
chain,
|
|
598
625
|
cmds: routeResult.cmds,
|
|
599
626
|
envs,
|
|
600
627
|
reporter,
|
|
601
628
|
});
|
|
602
|
-
const controlScanResult = this.#controlScan(routeResult.remaining, leafCommand);
|
|
603
|
-
ctx
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
629
|
+
const controlScanResult = this.#port.controlScan(routeResult.remaining, leafCommand);
|
|
630
|
+
ctx = this.#contextAdapter.applyControlScan(ctx, controlScanResult);
|
|
631
|
+
if (mode === 'run') {
|
|
632
|
+
const termination = this.#port.controlRun(leafCommand, controlScanResult);
|
|
633
|
+
if (termination !== undefined) {
|
|
634
|
+
ctx.sources.preset.state = 'skipped';
|
|
635
|
+
return {
|
|
636
|
+
kind: 'terminated',
|
|
637
|
+
termination,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
610
640
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
641
|
+
let presetResult;
|
|
642
|
+
try {
|
|
643
|
+
presetResult = await this.#port.preset(controlScanResult.remaining, ctx);
|
|
644
|
+
}
|
|
645
|
+
catch (err) {
|
|
646
|
+
if (err instanceof CommanderError) {
|
|
647
|
+
throw this.#diagnostics.withErrorIssue(err, {
|
|
648
|
+
stage: 'preset',
|
|
649
|
+
scope: this.#port.issueScopeFromErrorKind(err.kind),
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
throw err;
|
|
614
653
|
}
|
|
615
|
-
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
654
|
+
ctx = this.#contextAdapter.applyPresetResult(ctx, presetResult);
|
|
655
|
+
const sourceSegments = presetResult.segments;
|
|
656
|
+
let optionTokens;
|
|
657
|
+
let restArgs;
|
|
658
|
+
try {
|
|
659
|
+
const tokenizeResult = this.#port.tokenize(sourceSegments, this.#port.getCommandPath(leafCommand));
|
|
660
|
+
optionTokens = tokenizeResult.optionTokens;
|
|
661
|
+
restArgs = tokenizeResult.restArgs;
|
|
662
|
+
}
|
|
663
|
+
catch (err) {
|
|
664
|
+
if (err instanceof CommanderError) {
|
|
665
|
+
const enriched = this.#diagnostics.withErrorIssue(err, {
|
|
666
|
+
stage: 'tokenize',
|
|
667
|
+
scope: this.#port.issueScopeFromErrorKind(err.kind),
|
|
668
|
+
});
|
|
669
|
+
throw this.#diagnostics.withPresetInjectedHint(enriched, sourceSegments);
|
|
670
|
+
}
|
|
671
|
+
throw err;
|
|
631
672
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
673
|
+
let optionPolicyMap;
|
|
674
|
+
try {
|
|
675
|
+
optionPolicyMap = this.#port.buildOptionPolicyMap(chain);
|
|
676
|
+
}
|
|
677
|
+
catch (err) {
|
|
678
|
+
if (err instanceof CommanderError) {
|
|
679
|
+
const enriched = this.#diagnostics.withErrorIssue(err, {
|
|
680
|
+
stage: 'builtin-resolve',
|
|
681
|
+
scope: this.#port.issueScopeFromErrorKind(err.kind),
|
|
682
|
+
});
|
|
683
|
+
throw this.#diagnostics.withPresetInjectedHint(enriched, sourceSegments);
|
|
684
|
+
}
|
|
685
|
+
throw err;
|
|
635
686
|
}
|
|
636
|
-
|
|
637
|
-
|
|
687
|
+
let resolveResult;
|
|
688
|
+
try {
|
|
689
|
+
resolveResult = this.#port.resolve(chain, optionTokens, optionPolicyMap);
|
|
690
|
+
}
|
|
691
|
+
catch (err) {
|
|
692
|
+
if (err instanceof CommanderError) {
|
|
693
|
+
const enriched = this.#diagnostics.withErrorIssue(err, {
|
|
694
|
+
stage: 'resolve',
|
|
695
|
+
scope: this.#port.issueScopeFromErrorKind(err.kind),
|
|
696
|
+
});
|
|
697
|
+
throw this.#diagnostics.withPresetInjectedHint(enriched, sourceSegments);
|
|
698
|
+
}
|
|
699
|
+
throw err;
|
|
700
|
+
}
|
|
701
|
+
let parseResult;
|
|
702
|
+
try {
|
|
703
|
+
parseResult = this.#port.parse(chain, resolveResult, optionPolicyMap, ctx, restArgs);
|
|
704
|
+
}
|
|
705
|
+
catch (err) {
|
|
706
|
+
if (err instanceof CommanderError) {
|
|
707
|
+
const optionConflictSource = this.#diagnostics.resolveOptionConflictSourceAttribution(err, sourceSegments);
|
|
708
|
+
const optionConflictPreset = this.#diagnostics.resolveOptionConflictPresetAttribution(err, sourceSegments, optionConflictSource);
|
|
709
|
+
const enriched = this.#diagnostics.withErrorIssue(err, {
|
|
710
|
+
stage: 'parse',
|
|
711
|
+
scope: this.#port.issueScopeFromErrorKind(err.kind),
|
|
712
|
+
source: optionConflictSource,
|
|
713
|
+
preset: optionConflictPreset,
|
|
714
|
+
});
|
|
715
|
+
throw this.#diagnostics.withPresetInjectedHint(enriched, sourceSegments);
|
|
716
|
+
}
|
|
717
|
+
throw err;
|
|
718
|
+
}
|
|
719
|
+
if (mode === 'run') {
|
|
720
|
+
try {
|
|
721
|
+
await this.#port.run({
|
|
722
|
+
leafCommand,
|
|
723
|
+
parseResult,
|
|
724
|
+
presetResult,
|
|
725
|
+
ctx,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
catch (err) {
|
|
729
|
+
if (err instanceof CommanderError) {
|
|
730
|
+
throw this.#diagnostics.withErrorIssue(err, {
|
|
731
|
+
stage: 'run',
|
|
732
|
+
scope: this.#port.issueScopeFromErrorKind(err.kind),
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
throw err;
|
|
736
|
+
}
|
|
638
737
|
}
|
|
738
|
+
return {
|
|
739
|
+
kind: 'parsed',
|
|
740
|
+
parseResult,
|
|
741
|
+
};
|
|
639
742
|
}
|
|
640
743
|
catch (err) {
|
|
641
744
|
if (err instanceof CommanderError) {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
745
|
+
return {
|
|
746
|
+
kind: 'failed',
|
|
747
|
+
error: this.#diagnostics.normalizeCommanderError(err, {
|
|
748
|
+
fallbackStage: mode === 'run' ? 'run' : 'parse',
|
|
749
|
+
fallbackScope: this.#port.issueScopeFromErrorKind(err.kind),
|
|
750
|
+
}),
|
|
751
|
+
};
|
|
645
752
|
}
|
|
646
753
|
throw err;
|
|
647
754
|
}
|
|
648
755
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
class CommandContextAdapter {
|
|
759
|
+
applyControlScan(ctx, controlScanResult) {
|
|
760
|
+
return {
|
|
761
|
+
...ctx,
|
|
762
|
+
controls: controlScanResult.controls,
|
|
763
|
+
sources: {
|
|
764
|
+
...ctx.sources,
|
|
765
|
+
user: {
|
|
766
|
+
...ctx.sources.user,
|
|
767
|
+
argv: [...controlScanResult.remaining],
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
applyPresetResult(ctx, presetResult) {
|
|
773
|
+
return {
|
|
774
|
+
...ctx,
|
|
775
|
+
sources: presetResult.sources,
|
|
776
|
+
envs: presetResult.envs,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
toActionParams(parseResult) {
|
|
780
|
+
return {
|
|
781
|
+
ctx: parseResult.ctx,
|
|
782
|
+
builtin: parseResult.builtin,
|
|
783
|
+
opts: parseResult.opts,
|
|
784
|
+
args: parseResult.args,
|
|
785
|
+
rawArgs: parseResult.rawArgs,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
class CommandHintAttributor {
|
|
791
|
+
withPresetInjectedHint(error, sourceSegments) {
|
|
792
|
+
const presetSegments = sourceSegments.filter(segment => segment.source === 'preset');
|
|
793
|
+
if (presetSegments.length === 0) {
|
|
794
|
+
return error;
|
|
795
|
+
}
|
|
796
|
+
if (error.kind === 'ConfigurationError') {
|
|
797
|
+
return error;
|
|
798
|
+
}
|
|
799
|
+
let nextError = error;
|
|
800
|
+
const primaryIssue = nextError.meta?.issues.find(issue => issue.kind === 'error');
|
|
801
|
+
const conflictSources = primaryIssue?.reason.code === 'option_conflict'
|
|
802
|
+
? this.#inferOptionConflictSources(primaryIssue.reason.message, sourceSegments)
|
|
803
|
+
: undefined;
|
|
804
|
+
const hasMixedConflictAttribution = primaryIssue?.source?.related?.includes('user') === true &&
|
|
805
|
+
primaryIssue.source.related.includes('preset');
|
|
806
|
+
const isMixedConflict = hasMixedConflictAttribution ||
|
|
807
|
+
(conflictSources?.has('user') === true && conflictSources.has('preset'));
|
|
808
|
+
if (isMixedConflict &&
|
|
809
|
+
primaryIssue?.reason.code === 'option_conflict' &&
|
|
810
|
+
!nextError.meta?.issues.some(issue => issue.kind === 'hint' && issue.reason.code === 'mixed_source_conflict')) {
|
|
811
|
+
const mixedHint = {
|
|
812
|
+
kind: 'hint',
|
|
813
|
+
stage: primaryIssue.stage,
|
|
814
|
+
originStage: primaryIssue.originStage,
|
|
815
|
+
scope: 'option',
|
|
816
|
+
source: {
|
|
817
|
+
related: ['user', 'preset'],
|
|
818
|
+
},
|
|
819
|
+
reason: {
|
|
820
|
+
code: 'mixed_source_conflict',
|
|
821
|
+
message: 'option conflict involves both user input and preset-injected tokens',
|
|
822
|
+
},
|
|
823
|
+
preset: this.#resolveOptionConflictPresetByMessage(primaryIssue.reason.message, sourceSegments, { related: ['user', 'preset'] }),
|
|
824
|
+
};
|
|
825
|
+
nextError = nextError.withIssue(mixedHint);
|
|
826
|
+
}
|
|
827
|
+
const shouldAttachPresetTokenHint = primaryIssue?.source?.primary === 'preset' || isMixedConflict;
|
|
828
|
+
if (!shouldAttachPresetTokenHint) {
|
|
829
|
+
return nextError;
|
|
830
|
+
}
|
|
831
|
+
if (nextError.meta?.issues.some(issue => issue.kind === 'hint' && issue.reason.code === 'preset_token_injected')) {
|
|
832
|
+
return nextError;
|
|
833
|
+
}
|
|
834
|
+
const firstSegment = presetSegments[0];
|
|
835
|
+
const moreCount = presetSegments.length - 1;
|
|
836
|
+
const moreText = moreCount > 0 ? ` (+${moreCount} more)` : '';
|
|
837
|
+
const currentPrimaryIssue = nextError.meta?.issues.find(issue => issue.kind === 'error');
|
|
838
|
+
const hint = {
|
|
839
|
+
kind: 'hint',
|
|
840
|
+
stage: currentPrimaryIssue?.stage ?? 'parse',
|
|
841
|
+
originStage: 'preset',
|
|
842
|
+
scope: 'preset',
|
|
843
|
+
source: { primary: 'preset' },
|
|
844
|
+
reason: {
|
|
845
|
+
code: 'preset_token_injected',
|
|
846
|
+
message: `token ${JSON.stringify(firstSegment.value)} was injected from preset profile opts${moreText}`,
|
|
847
|
+
},
|
|
848
|
+
preset: firstSegment.preset,
|
|
849
|
+
};
|
|
850
|
+
return nextError.withIssue(hint);
|
|
851
|
+
}
|
|
852
|
+
resolveOptionConflictSourceAttribution(error, sourceSegments) {
|
|
853
|
+
if (error.kind !== 'OptionConflict') {
|
|
854
|
+
return undefined;
|
|
855
|
+
}
|
|
856
|
+
const sources = this.#inferOptionConflictSources(error.message, sourceSegments);
|
|
857
|
+
if (sources.has('user') && sources.has('preset')) {
|
|
858
|
+
return {
|
|
859
|
+
related: ['user', 'preset'],
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
if (sources.has('preset')) {
|
|
863
|
+
return { primary: 'preset' };
|
|
864
|
+
}
|
|
865
|
+
if (sources.has('user')) {
|
|
866
|
+
return { primary: 'user' };
|
|
867
|
+
}
|
|
868
|
+
return undefined;
|
|
869
|
+
}
|
|
870
|
+
resolveOptionConflictPresetAttribution(error, sourceSegments, source) {
|
|
871
|
+
if (error.kind !== 'OptionConflict') {
|
|
872
|
+
return undefined;
|
|
873
|
+
}
|
|
874
|
+
return this.#resolveOptionConflictPresetByMessage(error.message, sourceSegments, source);
|
|
875
|
+
}
|
|
876
|
+
#resolveOptionConflictPresetByMessage(message, sourceSegments, source) {
|
|
877
|
+
const relevantSegments = this.#collectOptionConflictSegments(message, sourceSegments);
|
|
878
|
+
const relevantPresetSegment = relevantSegments.find(segment => segment.source === 'preset' && segment.preset !== undefined);
|
|
879
|
+
if (relevantPresetSegment?.preset !== undefined) {
|
|
880
|
+
return { ...relevantPresetSegment.preset };
|
|
881
|
+
}
|
|
882
|
+
const hasPresetSource = source?.primary === 'preset' || source?.related?.includes('preset') === true;
|
|
883
|
+
if (!hasPresetSource) {
|
|
884
|
+
return undefined;
|
|
885
|
+
}
|
|
886
|
+
const fallbackPresetSegment = sourceSegments.find(segment => segment.source === 'preset' && segment.preset !== undefined);
|
|
887
|
+
return fallbackPresetSegment?.preset === undefined
|
|
888
|
+
? undefined
|
|
889
|
+
: { ...fallbackPresetSegment.preset };
|
|
890
|
+
}
|
|
891
|
+
#inferOptionConflictSources(message, sourceSegments) {
|
|
892
|
+
const relevantSegments = this.#collectOptionConflictSegments(message, sourceSegments);
|
|
893
|
+
const sources = new Set();
|
|
894
|
+
for (const segment of relevantSegments) {
|
|
895
|
+
sources.add(segment.source);
|
|
896
|
+
}
|
|
897
|
+
return sources;
|
|
898
|
+
}
|
|
899
|
+
#collectOptionConflictSegments(message, sourceSegments) {
|
|
900
|
+
const matchedLongs = Array.from(message.matchAll(/"(--[a-z][a-z0-9]*(?:-[a-z0-9]+)*)"/g), match => match[1]);
|
|
901
|
+
const matchedShorts = Array.from(message.matchAll(/"(-[A-Za-z0-9])"/g), match => match[1]);
|
|
902
|
+
const optionLiterals = new Set([...matchedLongs, ...matchedShorts]);
|
|
903
|
+
const optionSegments = sourceSegments.filter(segment => segment.value.startsWith('-'));
|
|
904
|
+
const relevantSegments = optionLiterals.size === 0
|
|
905
|
+
? optionSegments
|
|
906
|
+
: optionSegments.filter(segment => {
|
|
907
|
+
for (const literal of optionLiterals) {
|
|
908
|
+
if (segment.value === literal || segment.value.startsWith(`${literal}=`)) {
|
|
909
|
+
return true;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return false;
|
|
913
|
+
});
|
|
914
|
+
return relevantSegments;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const COMMAND_ERROR_ISSUE_CODES = [
|
|
919
|
+
'invalid_option_format',
|
|
920
|
+
'invalid_negative_option',
|
|
921
|
+
'negative_option_with_value',
|
|
922
|
+
'negative_option_type',
|
|
923
|
+
'unknown_option',
|
|
924
|
+
'unknown_subcommand',
|
|
925
|
+
'unexpected_argument',
|
|
926
|
+
'missing_value',
|
|
927
|
+
'invalid_type',
|
|
928
|
+
'unsupported_short_syntax',
|
|
929
|
+
'option_conflict',
|
|
930
|
+
'missing_required',
|
|
931
|
+
'invalid_choice',
|
|
932
|
+
'invalid_boolean_value',
|
|
933
|
+
'missing_required_argument',
|
|
934
|
+
'too_many_arguments',
|
|
935
|
+
'configuration_error',
|
|
936
|
+
'action_failed',
|
|
937
|
+
];
|
|
938
|
+
const COMMAND_HINT_ISSUE_CODES = [
|
|
939
|
+
'preset_token_injected',
|
|
940
|
+
'mixed_source_conflict',
|
|
941
|
+
'did_you_mean_subcommand',
|
|
942
|
+
'command_does_not_accept_positional_arguments',
|
|
943
|
+
];
|
|
944
|
+
const ERROR_ISSUE_CODE_SET = new Set(COMMAND_ERROR_ISSUE_CODES);
|
|
945
|
+
const HINT_ISSUE_CODE_SET = new Set(COMMAND_HINT_ISSUE_CODES);
|
|
946
|
+
function isErrorIssueCode(code) {
|
|
947
|
+
return ERROR_ISSUE_CODE_SET.has(code);
|
|
948
|
+
}
|
|
949
|
+
function isHintIssueCode(code) {
|
|
950
|
+
return HINT_ISSUE_CODE_SET.has(code);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function errorKindToIssueCode(kind) {
|
|
954
|
+
switch (kind) {
|
|
955
|
+
case 'InvalidOptionFormat':
|
|
956
|
+
return 'invalid_option_format';
|
|
957
|
+
case 'InvalidNegativeOption':
|
|
958
|
+
return 'invalid_negative_option';
|
|
959
|
+
case 'NegativeOptionWithValue':
|
|
960
|
+
return 'negative_option_with_value';
|
|
961
|
+
case 'NegativeOptionType':
|
|
962
|
+
return 'negative_option_type';
|
|
963
|
+
case 'UnknownOption':
|
|
964
|
+
return 'unknown_option';
|
|
965
|
+
case 'UnknownSubcommand':
|
|
966
|
+
return 'unknown_subcommand';
|
|
967
|
+
case 'UnexpectedArgument':
|
|
968
|
+
return 'unexpected_argument';
|
|
969
|
+
case 'MissingValue':
|
|
970
|
+
return 'missing_value';
|
|
971
|
+
case 'InvalidType':
|
|
972
|
+
return 'invalid_type';
|
|
973
|
+
case 'UnsupportedShortSyntax':
|
|
974
|
+
return 'unsupported_short_syntax';
|
|
975
|
+
case 'OptionConflict':
|
|
976
|
+
return 'option_conflict';
|
|
977
|
+
case 'MissingRequired':
|
|
978
|
+
return 'missing_required';
|
|
979
|
+
case 'InvalidChoice':
|
|
980
|
+
return 'invalid_choice';
|
|
981
|
+
case 'InvalidBooleanValue':
|
|
982
|
+
return 'invalid_boolean_value';
|
|
983
|
+
case 'MissingRequiredArgument':
|
|
984
|
+
return 'missing_required_argument';
|
|
985
|
+
case 'TooManyArguments':
|
|
986
|
+
return 'too_many_arguments';
|
|
987
|
+
case 'ActionFailed':
|
|
988
|
+
return 'action_failed';
|
|
989
|
+
case 'ConfigurationError':
|
|
990
|
+
return 'configuration_error';
|
|
991
|
+
default:
|
|
992
|
+
return 'configuration_error';
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
class CommandIssueNormalizer {
|
|
996
|
+
withErrorIssue(error, params) {
|
|
997
|
+
if (error.meta?.issues.some(existing => existing.kind === 'error')) {
|
|
998
|
+
return error;
|
|
999
|
+
}
|
|
1000
|
+
return error.withIssue(this.#buildErrorIssue(error, params));
|
|
1001
|
+
}
|
|
1002
|
+
normalizeCommanderError(error, options) {
|
|
1003
|
+
const issues = error.meta?.issues ?? [];
|
|
1004
|
+
const normalizedIssues = this.#normalizeIssues(issues, {
|
|
1005
|
+
error,
|
|
1006
|
+
fallbackStage: options.fallbackStage,
|
|
1007
|
+
fallbackScope: options.fallbackScope,
|
|
659
1008
|
});
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
1009
|
+
const nextMeta = {
|
|
1010
|
+
commandPath: error.meta?.commandPath ?? error.commandPath,
|
|
1011
|
+
token: error.meta?.token,
|
|
1012
|
+
option: error.meta?.option,
|
|
1013
|
+
argument: error.meta?.argument,
|
|
1014
|
+
issues: normalizedIssues,
|
|
1015
|
+
};
|
|
1016
|
+
return new CommanderError(error.kind, error.message, error.commandPath, nextMeta);
|
|
1017
|
+
}
|
|
1018
|
+
#buildErrorIssue(error, params) {
|
|
1019
|
+
const { stage, scope, token, source, preset, originStage, details } = params;
|
|
1020
|
+
const tokenSource = token === undefined
|
|
1021
|
+
? undefined
|
|
1022
|
+
: {
|
|
1023
|
+
primary: token.source,
|
|
1024
|
+
};
|
|
1025
|
+
const defaultSource = error.kind === 'MissingRequiredArgument' || error.kind === 'UnknownSubcommand'
|
|
1026
|
+
? { primary: 'user' }
|
|
1027
|
+
: undefined;
|
|
1028
|
+
const issueSource = source ?? tokenSource ?? defaultSource;
|
|
1029
|
+
const issuePreset = preset ?? token?.preset;
|
|
1030
|
+
const presetSource = issueSource?.primary === 'preset' || issueSource?.related?.includes('preset');
|
|
1031
|
+
const resolvedOriginStage = originStage ?? (presetSource && stage !== 'preset' ? 'preset' : undefined);
|
|
1032
|
+
return {
|
|
1033
|
+
kind: 'error',
|
|
1034
|
+
stage,
|
|
1035
|
+
originStage: resolvedOriginStage,
|
|
1036
|
+
source: issueSource,
|
|
1037
|
+
scope,
|
|
1038
|
+
reason: {
|
|
1039
|
+
code: errorKindToIssueCode(error.kind),
|
|
1040
|
+
message: error.message,
|
|
1041
|
+
details,
|
|
1042
|
+
},
|
|
1043
|
+
preset: issuePreset,
|
|
1044
|
+
};
|
|
671
1045
|
}
|
|
672
|
-
|
|
673
|
-
|
|
1046
|
+
#normalizeIssues(issues, options) {
|
|
1047
|
+
const normalized = issues
|
|
1048
|
+
.map(issue => this.#normalizeIssue(issue))
|
|
1049
|
+
.filter((issue) => issue !== undefined);
|
|
1050
|
+
const primaryError = normalized.find(issue => issue.kind === 'error');
|
|
1051
|
+
const hints = normalized.filter(issue => issue.kind === 'hint');
|
|
1052
|
+
if (primaryError === undefined) {
|
|
1053
|
+
const fallbackError = this.#buildErrorIssue(options.error, {
|
|
1054
|
+
stage: options.fallbackStage,
|
|
1055
|
+
scope: options.fallbackScope,
|
|
1056
|
+
});
|
|
1057
|
+
return [fallbackError, ...hints];
|
|
1058
|
+
}
|
|
1059
|
+
return [primaryError, ...hints];
|
|
1060
|
+
}
|
|
1061
|
+
#normalizeIssue(issue) {
|
|
1062
|
+
let source = this.#normalizeIssueSource(issue.source);
|
|
1063
|
+
let preset = this.#normalizePresetIssueMeta(issue.preset);
|
|
1064
|
+
const reasonCode = issue.reason.code;
|
|
1065
|
+
if (!this.#hasPresetSource(source)) {
|
|
1066
|
+
preset = undefined;
|
|
1067
|
+
}
|
|
1068
|
+
if (source?.primary === 'preset' && !this.#hasPresetLocator(preset)) {
|
|
1069
|
+
source = this.#dropPresetPrimarySource(source);
|
|
1070
|
+
if (!this.#hasPresetSource(source)) {
|
|
1071
|
+
preset = undefined;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (issue.kind === 'error') {
|
|
1075
|
+
const normalizedCode = isErrorIssueCode(reasonCode)
|
|
1076
|
+
? reasonCode
|
|
1077
|
+
: 'configuration_error';
|
|
1078
|
+
const normalizedReason = normalizedCode === reasonCode
|
|
1079
|
+
? issue.reason
|
|
1080
|
+
: {
|
|
1081
|
+
code: normalizedCode,
|
|
1082
|
+
message: `invalid error issue code "${reasonCode}"`,
|
|
1083
|
+
details: {
|
|
1084
|
+
...(issue.reason.details ?? {}),
|
|
1085
|
+
invalidIssueCode: reasonCode,
|
|
1086
|
+
},
|
|
1087
|
+
};
|
|
1088
|
+
return {
|
|
1089
|
+
...issue,
|
|
1090
|
+
source,
|
|
1091
|
+
preset,
|
|
1092
|
+
reason: normalizedReason,
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
if (!isHintIssueCode(reasonCode)) {
|
|
1096
|
+
return undefined;
|
|
1097
|
+
}
|
|
1098
|
+
return {
|
|
1099
|
+
...issue,
|
|
1100
|
+
source,
|
|
1101
|
+
preset,
|
|
1102
|
+
};
|
|
674
1103
|
}
|
|
675
|
-
#
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
if (!this.#shouldRenderStyledHelp(color)) {
|
|
679
|
-
return this.#renderHelpPlain(helpData);
|
|
1104
|
+
#normalizeIssueSource(source) {
|
|
1105
|
+
if (source === undefined) {
|
|
1106
|
+
return undefined;
|
|
680
1107
|
}
|
|
681
|
-
|
|
1108
|
+
const related = source.related === undefined ? undefined : Array.from(new Set(source.related));
|
|
1109
|
+
const primary = source.primary;
|
|
1110
|
+
if (primary !== undefined && related !== undefined && !related.includes(primary)) {
|
|
1111
|
+
related.push(primary);
|
|
1112
|
+
}
|
|
1113
|
+
if (primary === undefined && (related === undefined || related.length === 0)) {
|
|
1114
|
+
return undefined;
|
|
1115
|
+
}
|
|
1116
|
+
return {
|
|
1117
|
+
primary,
|
|
1118
|
+
related,
|
|
1119
|
+
};
|
|
682
1120
|
}
|
|
683
|
-
#
|
|
684
|
-
|
|
1121
|
+
#dropPresetPrimarySource(source) {
|
|
1122
|
+
const related = source.related?.filter(item => item !== 'preset');
|
|
1123
|
+
if (related === undefined || related.length === 0) {
|
|
1124
|
+
return undefined;
|
|
1125
|
+
}
|
|
1126
|
+
return {
|
|
1127
|
+
related,
|
|
1128
|
+
};
|
|
685
1129
|
}
|
|
686
|
-
#
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
if (this.#supportsBuiltinVersion()) {
|
|
690
|
-
allOptions.push(BUILTIN_VERSION_OPTION);
|
|
1130
|
+
#normalizePresetIssueMeta(preset) {
|
|
1131
|
+
if (preset === undefined) {
|
|
1132
|
+
return undefined;
|
|
691
1133
|
}
|
|
692
|
-
const
|
|
693
|
-
|
|
1134
|
+
const nextPreset = {
|
|
1135
|
+
file: preset.file,
|
|
1136
|
+
profile: preset.profile,
|
|
1137
|
+
variant: preset.variant,
|
|
1138
|
+
optionKey: preset.optionKey,
|
|
1139
|
+
};
|
|
1140
|
+
const hasAnyValue = nextPreset.file !== undefined ||
|
|
1141
|
+
nextPreset.profile !== undefined ||
|
|
1142
|
+
nextPreset.variant !== undefined ||
|
|
1143
|
+
nextPreset.optionKey !== undefined;
|
|
1144
|
+
return hasAnyValue ? nextPreset : undefined;
|
|
1145
|
+
}
|
|
1146
|
+
#hasPresetLocator(preset) {
|
|
1147
|
+
return (preset?.file !== undefined || preset?.profile !== undefined || preset?.variant !== undefined);
|
|
1148
|
+
}
|
|
1149
|
+
#hasPresetSource(source) {
|
|
1150
|
+
return source?.primary === 'preset' || source?.related?.includes('preset') === true;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
class CommandDiagnosticsEngine {
|
|
1155
|
+
#issueNormalizer = new CommandIssueNormalizer();
|
|
1156
|
+
#hintAttributor = new CommandHintAttributor();
|
|
1157
|
+
withErrorIssue(error, params) {
|
|
1158
|
+
return this.#issueNormalizer.withErrorIssue(error, params);
|
|
1159
|
+
}
|
|
1160
|
+
withPresetInjectedHint(error, sourceSegments) {
|
|
1161
|
+
return this.#hintAttributor.withPresetInjectedHint(error, sourceSegments);
|
|
1162
|
+
}
|
|
1163
|
+
normalizeCommanderError(error, options) {
|
|
1164
|
+
return this.#issueNormalizer.normalizeCommanderError(error, options);
|
|
1165
|
+
}
|
|
1166
|
+
resolveOptionConflictSourceAttribution(error, sourceSegments) {
|
|
1167
|
+
return this.#hintAttributor.resolveOptionConflictSourceAttribution(error, sourceSegments);
|
|
1168
|
+
}
|
|
1169
|
+
resolveOptionConflictPresetAttribution(error, sourceSegments, source) {
|
|
1170
|
+
return this.#hintAttributor.resolveOptionConflictPresetAttribution(error, sourceSegments, source);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const TERMINAL_STYLE = {
|
|
1175
|
+
bold: '\x1b[1m',
|
|
1176
|
+
italic: '\x1b[3m',
|
|
1177
|
+
underline: '\x1b[4m',
|
|
1178
|
+
cyan: '\x1b[36m',
|
|
1179
|
+
dim: '\x1b[2m',
|
|
1180
|
+
reset: '\x1b[0m',
|
|
1181
|
+
};
|
|
1182
|
+
function styleText(text, ...styles) {
|
|
1183
|
+
return `${styles.join('')}${text}${TERMINAL_STYLE.reset}`;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const ANSI_ESCAPE_REGEX = new RegExp(String.raw `\x1B\[[0-?]*[ -/]*[@-~]`, 'g');
|
|
1187
|
+
function camelToKebabCase$4(str) {
|
|
1188
|
+
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
1189
|
+
}
|
|
1190
|
+
function isNoColorEnabled$1(envs) {
|
|
1191
|
+
return envs['NO_COLOR'] !== undefined;
|
|
1192
|
+
}
|
|
1193
|
+
function stripAnsi(value) {
|
|
1194
|
+
return value.replace(ANSI_ESCAPE_REGEX, '');
|
|
1195
|
+
}
|
|
1196
|
+
function isCombiningMark(codePoint) {
|
|
1197
|
+
return ((codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
1198
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
1199
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
1200
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
1201
|
+
(codePoint >= 0xfe20 && codePoint <= 0xfe2f));
|
|
1202
|
+
}
|
|
1203
|
+
function isWideCodePoint(codePoint) {
|
|
1204
|
+
if (codePoint < 0x1100) {
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
return (codePoint <= 0x115f ||
|
|
1208
|
+
codePoint === 0x2329 ||
|
|
1209
|
+
codePoint === 0x232a ||
|
|
1210
|
+
(codePoint >= 0x2e80 && codePoint <= 0x3247 && codePoint !== 0x303f) ||
|
|
1211
|
+
(codePoint >= 0x3250 && codePoint <= 0x4dbf) ||
|
|
1212
|
+
(codePoint >= 0x4e00 && codePoint <= 0xa4c6) ||
|
|
1213
|
+
(codePoint >= 0xa960 && codePoint <= 0xa97c) ||
|
|
1214
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
1215
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
1216
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
1217
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6b) ||
|
|
1218
|
+
(codePoint >= 0xff01 && codePoint <= 0xff60) ||
|
|
1219
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
1220
|
+
(codePoint >= 0x1b000 && codePoint <= 0x1b001) ||
|
|
1221
|
+
(codePoint >= 0x1f200 && codePoint <= 0x1f251) ||
|
|
1222
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd));
|
|
1223
|
+
}
|
|
1224
|
+
function getDisplayWidth(value) {
|
|
1225
|
+
const normalized = stripAnsi(value).normalize('NFC');
|
|
1226
|
+
let width = 0;
|
|
1227
|
+
for (const char of normalized) {
|
|
1228
|
+
const codePoint = char.codePointAt(0);
|
|
1229
|
+
if (codePoint === undefined || isCombiningMark(codePoint)) {
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
width += isWideCodePoint(codePoint) ? 2 : 1;
|
|
1233
|
+
}
|
|
1234
|
+
return width;
|
|
1235
|
+
}
|
|
1236
|
+
function padDisplayEnd(value, targetWidth) {
|
|
1237
|
+
const width = getDisplayWidth(value);
|
|
1238
|
+
if (width >= targetWidth) {
|
|
1239
|
+
return value;
|
|
1240
|
+
}
|
|
1241
|
+
return value + ' '.repeat(targetWidth - width);
|
|
1242
|
+
}
|
|
1243
|
+
class CommandHelpRenderer {
|
|
1244
|
+
buildHelpData(params) {
|
|
1245
|
+
const allOptions = [...params.options, params.builtinHelpOption];
|
|
1246
|
+
if (params.supportsBuiltinVersion) {
|
|
1247
|
+
allOptions.push(params.builtinVersionOption);
|
|
1248
|
+
}
|
|
1249
|
+
let usage = `Usage: ${params.commandPath}`;
|
|
694
1250
|
if (allOptions.length > 0)
|
|
695
1251
|
usage += ' [options]';
|
|
696
|
-
if (
|
|
1252
|
+
if (params.subcommands.length > 0)
|
|
697
1253
|
usage += ' [command]';
|
|
698
|
-
for (const arg of
|
|
1254
|
+
for (const arg of params.arguments) {
|
|
699
1255
|
if (arg.kind === 'required') {
|
|
700
1256
|
usage += ` <${arg.name}>`;
|
|
701
1257
|
}
|
|
@@ -710,7 +1266,7 @@ class Command {
|
|
|
710
1266
|
}
|
|
711
1267
|
}
|
|
712
1268
|
const argumentsLines = [];
|
|
713
|
-
for (const arg of
|
|
1269
|
+
for (const arg of params.arguments) {
|
|
714
1270
|
const sig = arg.kind === 'required'
|
|
715
1271
|
? `<${arg.name}>`
|
|
716
1272
|
: arg.kind === 'optional'
|
|
@@ -746,11 +1302,11 @@ class Command {
|
|
|
746
1302
|
if (rankA !== rankB) {
|
|
747
1303
|
return rankA - rankB;
|
|
748
1304
|
}
|
|
749
|
-
return camelToKebabCase$
|
|
1305
|
+
return camelToKebabCase$4(a.long).localeCompare(camelToKebabCase$4(b.long));
|
|
750
1306
|
});
|
|
751
1307
|
const options = [];
|
|
752
1308
|
for (const opt of sortedOptions) {
|
|
753
|
-
const kebabLong = camelToKebabCase$
|
|
1309
|
+
const kebabLong = camelToKebabCase$4(opt.long);
|
|
754
1310
|
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
755
1311
|
sig += `--${kebabLong}`;
|
|
756
1312
|
if (opt.args === 'optional') {
|
|
@@ -769,24 +1325,24 @@ class Command {
|
|
|
769
1325
|
options.push({ sig, desc });
|
|
770
1326
|
}
|
|
771
1327
|
const commands = [];
|
|
772
|
-
if (
|
|
1328
|
+
if (params.subcommands.length > 0) {
|
|
773
1329
|
commands.push({ name: 'help', desc: 'Show help for a command' });
|
|
774
1330
|
}
|
|
775
|
-
const sortedSubcommands = [...
|
|
1331
|
+
const sortedSubcommands = [...params.subcommands].sort((a, b) => a.name.localeCompare(b.name));
|
|
776
1332
|
for (const entry of sortedSubcommands) {
|
|
777
1333
|
let name = entry.name;
|
|
778
1334
|
if (entry.aliases.length > 0) {
|
|
779
1335
|
name += `, ${entry.aliases.join(', ')}`;
|
|
780
1336
|
}
|
|
781
|
-
commands.push({ name, desc: entry.
|
|
1337
|
+
commands.push({ name, desc: entry.desc });
|
|
782
1338
|
}
|
|
783
|
-
const examples =
|
|
1339
|
+
const examples = params.examples.map(example => ({
|
|
784
1340
|
title: example.title,
|
|
785
|
-
usage: commandPath ? `${commandPath} ${example.usage}` : example.usage,
|
|
1341
|
+
usage: params.commandPath ? `${params.commandPath} ${example.usage}` : example.usage,
|
|
786
1342
|
desc: example.desc,
|
|
787
1343
|
}));
|
|
788
1344
|
return {
|
|
789
|
-
desc:
|
|
1345
|
+
desc: params.desc,
|
|
790
1346
|
usage,
|
|
791
1347
|
arguments: argumentsLines,
|
|
792
1348
|
options,
|
|
@@ -794,6 +1350,51 @@ class Command {
|
|
|
794
1350
|
examples,
|
|
795
1351
|
};
|
|
796
1352
|
}
|
|
1353
|
+
formatHelp(helpData) {
|
|
1354
|
+
return this.#renderHelpPlain(helpData);
|
|
1355
|
+
}
|
|
1356
|
+
formatHelpForDisplay(helpData, color) {
|
|
1357
|
+
if (!this.#shouldRenderStyledHelp(color)) {
|
|
1358
|
+
return this.#renderHelpPlain(helpData);
|
|
1359
|
+
}
|
|
1360
|
+
return this.#renderHelpTerminal(helpData);
|
|
1361
|
+
}
|
|
1362
|
+
resolveHelpColorFromTailArgv(params) {
|
|
1363
|
+
const colorOption = params.options.find(opt => opt.long === 'color');
|
|
1364
|
+
let color = !isNoColorEnabled$1(params.envs);
|
|
1365
|
+
if (!colorOption || colorOption.type !== 'boolean' || colorOption.args !== 'none') {
|
|
1366
|
+
return color;
|
|
1367
|
+
}
|
|
1368
|
+
const separatorIndex = params.tailArgv.indexOf('--');
|
|
1369
|
+
const scanTokens = separatorIndex === -1 ? params.tailArgv : params.tailArgv.slice(0, separatorIndex);
|
|
1370
|
+
for (const token of scanTokens) {
|
|
1371
|
+
if (token === '--color') {
|
|
1372
|
+
color = true;
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
if (token === '--no-color') {
|
|
1376
|
+
color = false;
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
if (!token.startsWith('--color=')) {
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
1382
|
+
const value = token.slice('--color='.length);
|
|
1383
|
+
if (value === 'true') {
|
|
1384
|
+
color = true;
|
|
1385
|
+
}
|
|
1386
|
+
else if (value === 'false') {
|
|
1387
|
+
color = false;
|
|
1388
|
+
}
|
|
1389
|
+
else {
|
|
1390
|
+
throw new CommanderError('InvalidBooleanValue', `invalid value "${value}" for boolean option "--color". Use "true" or "false"`, params.commandPath);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
return color;
|
|
1394
|
+
}
|
|
1395
|
+
#shouldRenderStyledHelp(color) {
|
|
1396
|
+
return color && process.stdout.isTTY === true;
|
|
1397
|
+
}
|
|
797
1398
|
#renderHelpPlain(helpData) {
|
|
798
1399
|
const lines = [];
|
|
799
1400
|
const labelWidth = this.#getHelpLabelWidth(helpData);
|
|
@@ -888,243 +1489,287 @@ class Command {
|
|
|
888
1489
|
const outputLabel = styleLabel ? styleLabel(paddedLabel) : paddedLabel;
|
|
889
1490
|
return ` ${outputLabel} ${desc}`;
|
|
890
1491
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const DECIMAL_INTEGER_REGEX = /^\d(?:_?\d)*$/;
|
|
1495
|
+
const DECIMAL_FRACTION_REGEX = /^\d(?:_?\d)*$/;
|
|
1496
|
+
const DECIMAL_EXPONENT_REGEX = /^[eE][+-]?\d(?:_?\d)*$/;
|
|
1497
|
+
const BINARY_LITERAL_REGEX = /^0[bB][01](?:_?[01])*$/;
|
|
1498
|
+
const OCTAL_LITERAL_REGEX = /^0[oO][0-7](?:_?[0-7])*$/;
|
|
1499
|
+
const HEX_LITERAL_REGEX = /^0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*$/;
|
|
1500
|
+
function camelToKebabCase$3(str) {
|
|
1501
|
+
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
1502
|
+
}
|
|
1503
|
+
function isNoColorEnabled(envs) {
|
|
1504
|
+
return envs['NO_COLOR'] !== undefined;
|
|
1505
|
+
}
|
|
1506
|
+
function isValidPrimitiveNumberLiteral(rawValue) {
|
|
1507
|
+
if (rawValue.trim() !== rawValue || rawValue.length === 0) {
|
|
1508
|
+
return false;
|
|
1509
|
+
}
|
|
1510
|
+
if (rawValue === 'NaN' || rawValue === 'Infinity' || rawValue === '-Infinity') {
|
|
1511
|
+
return false;
|
|
1512
|
+
}
|
|
1513
|
+
if (BINARY_LITERAL_REGEX.test(rawValue)) {
|
|
1514
|
+
return true;
|
|
1515
|
+
}
|
|
1516
|
+
if (OCTAL_LITERAL_REGEX.test(rawValue)) {
|
|
1517
|
+
return true;
|
|
1518
|
+
}
|
|
1519
|
+
if (HEX_LITERAL_REGEX.test(rawValue)) {
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1522
|
+
const sign = rawValue[0] === '+' || rawValue[0] === '-' ? rawValue[0] : '';
|
|
1523
|
+
const body = sign ? rawValue.slice(1) : rawValue;
|
|
1524
|
+
if (body.length === 0) {
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
const expIndex = body.search(/[eE]/);
|
|
1528
|
+
const basePart = expIndex === -1 ? body : body.slice(0, expIndex);
|
|
1529
|
+
const expPart = expIndex === -1 ? '' : body.slice(expIndex);
|
|
1530
|
+
if (expPart && !DECIMAL_EXPONENT_REGEX.test(expPart)) {
|
|
1531
|
+
return false;
|
|
1532
|
+
}
|
|
1533
|
+
if (basePart.includes('.')) {
|
|
1534
|
+
const decimalParts = basePart.split('.');
|
|
1535
|
+
if (decimalParts.length !== 2) {
|
|
1536
|
+
return false;
|
|
912
1537
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
choices: arg.type === 'choice' ? arg.choices?.map(choice => String(choice)) : undefined,
|
|
919
|
-
});
|
|
1538
|
+
const [intPart, fracPart] = decimalParts;
|
|
1539
|
+
const intOk = intPart.length === 0 || DECIMAL_INTEGER_REGEX.test(intPart);
|
|
1540
|
+
const fracOk = fracPart.length === 0 || DECIMAL_FRACTION_REGEX.test(fracPart);
|
|
1541
|
+
if (!intOk || !fracOk) {
|
|
1542
|
+
return false;
|
|
920
1543
|
}
|
|
921
|
-
return
|
|
922
|
-
name: this.#name,
|
|
923
|
-
desc: this.#desc,
|
|
924
|
-
aliases: [],
|
|
925
|
-
options,
|
|
926
|
-
arguments: argumentsMeta,
|
|
927
|
-
subcommands: this.#subcommandsList.map(entry => {
|
|
928
|
-
const subMeta = entry.command.getCompletionMeta();
|
|
929
|
-
return {
|
|
930
|
-
...subMeta,
|
|
931
|
-
name: entry.name,
|
|
932
|
-
aliases: entry.aliases,
|
|
933
|
-
};
|
|
934
|
-
}),
|
|
935
|
-
};
|
|
1544
|
+
return intPart.length > 0 || fracPart.length > 0;
|
|
936
1545
|
}
|
|
937
|
-
|
|
938
|
-
|
|
1546
|
+
return DECIMAL_INTEGER_REGEX.test(basePart);
|
|
1547
|
+
}
|
|
1548
|
+
function parsePrimitiveNumber(rawValue) {
|
|
1549
|
+
if (!isValidPrimitiveNumberLiteral(rawValue)) {
|
|
1550
|
+
return undefined;
|
|
939
1551
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
let idx = 0;
|
|
945
|
-
while (idx < argv.length) {
|
|
946
|
-
const token = argv[idx];
|
|
947
|
-
if (token.startsWith('-'))
|
|
948
|
-
break;
|
|
949
|
-
const entry = current.#findSubcommandEntry(token);
|
|
950
|
-
if (!entry)
|
|
951
|
-
break;
|
|
952
|
-
current = entry.command;
|
|
953
|
-
cmds.push(token);
|
|
954
|
-
chain.push(current);
|
|
955
|
-
idx += 1;
|
|
956
|
-
}
|
|
957
|
-
return { chain, remaining: argv.slice(idx), cmds };
|
|
1552
|
+
const normalized = rawValue.replaceAll('_', '');
|
|
1553
|
+
const value = Number(normalized);
|
|
1554
|
+
if (!Number.isFinite(value)) {
|
|
1555
|
+
return undefined;
|
|
958
1556
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1557
|
+
return value;
|
|
1558
|
+
}
|
|
1559
|
+
class CommandOptionParser {
|
|
1560
|
+
parseOptions(params) {
|
|
1561
|
+
const { tokens, allOptions, envs, commandPath } = params;
|
|
1562
|
+
const opts = {};
|
|
1563
|
+
const explicitOptionLongs = new Set();
|
|
1564
|
+
let sawColorToken = false;
|
|
1565
|
+
for (const opt of allOptions) {
|
|
1566
|
+
if (opt.default !== undefined) {
|
|
1567
|
+
opts[opt.long] = opt.default;
|
|
1568
|
+
}
|
|
1569
|
+
else if (opt.type === 'boolean' && opt.args === 'none') {
|
|
1570
|
+
opts[opt.long] = false;
|
|
1571
|
+
}
|
|
1572
|
+
else if (opt.args === 'variadic') {
|
|
1573
|
+
opts[opt.long] = [];
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
const optionByLong = new Map();
|
|
1577
|
+
const optionByShort = new Map();
|
|
1578
|
+
for (const opt of allOptions) {
|
|
1579
|
+
optionByLong.set(opt.long, opt);
|
|
1580
|
+
if (opt.short) {
|
|
1581
|
+
optionByShort.set(opt.short, opt);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
let i = 0;
|
|
1585
|
+
while (i < tokens.length) {
|
|
1586
|
+
const token = tokens[i];
|
|
1587
|
+
const opt = token.type === 'long' ? optionByLong.get(token.name) : optionByShort.get(token.name);
|
|
1588
|
+
if (!opt) {
|
|
1589
|
+
i += 1;
|
|
1590
|
+
continue;
|
|
1591
|
+
}
|
|
1592
|
+
explicitOptionLongs.add(opt.long);
|
|
1593
|
+
if (opt.long === 'color') {
|
|
1594
|
+
sawColorToken = true;
|
|
1595
|
+
}
|
|
1596
|
+
const isNegativeToken = token.original.toLowerCase().startsWith('--no-');
|
|
1597
|
+
if (isNegativeToken && !(opt.type === 'boolean' && opt.args === 'none')) {
|
|
1598
|
+
throw new CommanderError('NegativeOptionType', `"--no-${camelToKebabCase$3(opt.long)}" can only be used with boolean options`, commandPath);
|
|
1599
|
+
}
|
|
1600
|
+
if (opt.type === 'boolean' && opt.args === 'none') {
|
|
1601
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
1602
|
+
if (eqIdx !== -1) {
|
|
1603
|
+
const value = token.resolved.slice(eqIdx + 1);
|
|
1604
|
+
if (value === 'true') {
|
|
1605
|
+
opts[opt.long] = true;
|
|
1606
|
+
}
|
|
1607
|
+
else if (value === 'false') {
|
|
1608
|
+
opts[opt.long] = false;
|
|
1609
|
+
}
|
|
1610
|
+
else {
|
|
1611
|
+
throw new CommanderError('InvalidBooleanValue', `invalid value "${value}" for boolean option "--${camelToKebabCase$3(opt.long)}". Use "true" or "false"`, commandPath);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
else {
|
|
1615
|
+
opts[opt.long] = true;
|
|
1616
|
+
}
|
|
1617
|
+
i += 1;
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
if (opt.args === 'required') {
|
|
1621
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
1622
|
+
let rawValue;
|
|
1623
|
+
if (eqIdx !== -1) {
|
|
1624
|
+
rawValue = token.resolved.slice(eqIdx + 1);
|
|
1625
|
+
}
|
|
1626
|
+
else if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1627
|
+
rawValue = tokens[i + 1].original;
|
|
1628
|
+
i += 1;
|
|
1629
|
+
}
|
|
1630
|
+
else {
|
|
1631
|
+
throw new CommanderError('MissingValue', `option "--${camelToKebabCase$3(opt.long)}" requires a value`, commandPath);
|
|
1632
|
+
}
|
|
1633
|
+
opts[opt.long] = this.#convertValue(opt, rawValue, commandPath);
|
|
1634
|
+
i += 1;
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
if (opt.args === 'optional') {
|
|
1638
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
1639
|
+
if (eqIdx !== -1) {
|
|
1640
|
+
opts[opt.long] = this.#convertValue(opt, token.resolved.slice(eqIdx + 1), commandPath);
|
|
1641
|
+
i += 1;
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1645
|
+
opts[opt.long] = this.#convertValue(opt, tokens[i + 1].original, commandPath);
|
|
1646
|
+
i += 1;
|
|
1647
|
+
}
|
|
1648
|
+
else {
|
|
1649
|
+
opts[opt.long] = undefined;
|
|
1650
|
+
}
|
|
1651
|
+
i += 1;
|
|
980
1652
|
continue;
|
|
981
1653
|
}
|
|
982
|
-
if (
|
|
983
|
-
|
|
1654
|
+
if (opt.args === 'variadic') {
|
|
1655
|
+
const values = Array.isArray(opts[opt.long]) ? opts[opt.long] : [];
|
|
1656
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
1657
|
+
if (eqIdx !== -1) {
|
|
1658
|
+
values.push(this.#convertValue(opt, token.resolved.slice(eqIdx + 1), commandPath));
|
|
1659
|
+
}
|
|
1660
|
+
else {
|
|
1661
|
+
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1662
|
+
i += 1;
|
|
1663
|
+
values.push(this.#convertValue(opt, tokens[i].original, commandPath));
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
opts[opt.long] = values;
|
|
1667
|
+
i += 1;
|
|
984
1668
|
continue;
|
|
985
1669
|
}
|
|
986
|
-
|
|
1670
|
+
i += 1;
|
|
1671
|
+
}
|
|
1672
|
+
for (const opt of allOptions) {
|
|
1673
|
+
if (opt.required && !Object.prototype.hasOwnProperty.call(opts, opt.long)) {
|
|
1674
|
+
throw new CommanderError('MissingRequired', `missing required option "--${camelToKebabCase$3(opt.long)}"`, commandPath);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
for (const opt of allOptions) {
|
|
1678
|
+
if (opt.choices && opts[opt.long] !== undefined) {
|
|
1679
|
+
const value = opts[opt.long];
|
|
1680
|
+
const values = Array.isArray(value) ? value : [value];
|
|
1681
|
+
const choices = opt.choices;
|
|
1682
|
+
for (const v of values) {
|
|
1683
|
+
if (!choices.includes(v)) {
|
|
1684
|
+
throw new CommanderError('InvalidChoice', `invalid value "${v}" for option "--${camelToKebabCase$3(opt.long)}". Allowed: ${opt.choices.join(', ')}`, commandPath);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
if (isNoColorEnabled(envs) && !sawColorToken && opts['color'] === true) {
|
|
1690
|
+
opts['color'] = false;
|
|
987
1691
|
}
|
|
988
|
-
const remaining = separatorIndex === -1
|
|
989
|
-
? remainingBeforeSeparator
|
|
990
|
-
: [...remainingBeforeSeparator, '--', ...afterSeparator];
|
|
991
|
-
return {
|
|
992
|
-
controls,
|
|
993
|
-
remaining,
|
|
994
|
-
helpTarget,
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
#createContext(params) {
|
|
998
|
-
const { chain, cmds, envs, reporter: reporter$1 } = params;
|
|
999
|
-
const leafCommand = chain[chain.length - 1];
|
|
1000
|
-
const envSnapshot = { ...envs };
|
|
1001
1692
|
return {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
envs: envSnapshot,
|
|
1005
|
-
controls: { help: false, version: false },
|
|
1006
|
-
sources: {
|
|
1007
|
-
preset: {
|
|
1008
|
-
argv: [],
|
|
1009
|
-
envs: {},
|
|
1010
|
-
},
|
|
1011
|
-
user: {
|
|
1012
|
-
cmds: [...cmds],
|
|
1013
|
-
argv: [],
|
|
1014
|
-
envs: envSnapshot,
|
|
1015
|
-
},
|
|
1016
|
-
},
|
|
1017
|
-
reporter: reporter$1 ?? this.#reporter ?? new reporter.Reporter(),
|
|
1693
|
+
opts,
|
|
1694
|
+
explicitOptionLongs,
|
|
1018
1695
|
};
|
|
1019
1696
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1697
|
+
applyBuiltinDevmodeLogLevel(params) {
|
|
1698
|
+
const { opts, explicitOptionLongs, builtinOption, hasUserOption } = params;
|
|
1699
|
+
const hasBuiltinDevmode = builtinOption.devmode;
|
|
1700
|
+
const hasBuiltinLogLevel = builtinOption.logLevel && !hasUserOption('logLevel');
|
|
1701
|
+
if (!hasBuiltinDevmode || !hasBuiltinLogLevel) {
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
if (opts['devmode'] !== true) {
|
|
1705
|
+
return;
|
|
1023
1706
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
return leafCommand;
|
|
1707
|
+
if (explicitOptionLongs.has('logLevel')) {
|
|
1708
|
+
return;
|
|
1027
1709
|
}
|
|
1028
|
-
|
|
1710
|
+
opts['logLevel'] = 'debug';
|
|
1029
1711
|
}
|
|
1030
|
-
|
|
1031
|
-
const
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
const afterSeparator = separatorIndex === -1 ? [] : controlTailArgv.slice(separatorIndex + 1);
|
|
1035
|
-
const profileScanResult = this.#scanPresetProfileDirectives(beforeSeparator, commandPath);
|
|
1036
|
-
const cleanArgv = separatorIndex === -1
|
|
1037
|
-
? profileScanResult.cleanArgv
|
|
1038
|
-
: [...profileScanResult.cleanArgv, '--', ...afterSeparator];
|
|
1039
|
-
const commandChain = ctx.chain;
|
|
1040
|
-
const commandPresetFile = this.#resolveCommandPresetFileFromChain(commandChain);
|
|
1041
|
-
const effectivePresetFile = profileScanResult.presetFile ?? commandPresetFile;
|
|
1042
|
-
const commandPresetProfile = this.#resolveCommandPresetProfileFromChain(commandChain);
|
|
1043
|
-
const useCommandPresetProfile = profileScanResult.presetProfile === undefined && commandPresetProfile !== undefined;
|
|
1044
|
-
if (useCommandPresetProfile) {
|
|
1045
|
-
this.#assertPresetProfileSelectorValue(commandPresetProfile, 'command.preset.profile', commandPath);
|
|
1046
|
-
}
|
|
1047
|
-
const effectivePresetProfile = profileScanResult.presetProfile ?? commandPresetProfile;
|
|
1048
|
-
const effectivePresetProfileSourceName = profileScanResult.presetProfile !== undefined
|
|
1049
|
-
? PRESET_PROFILE_FLAG
|
|
1050
|
-
: commandPresetProfile !== undefined
|
|
1051
|
-
? 'command.preset.profile'
|
|
1052
|
-
: undefined;
|
|
1053
|
-
if (effectivePresetFile === undefined && useCommandPresetProfile) {
|
|
1054
|
-
throw new CommanderError('ConfigurationError', 'cannot use "command.preset.profile" without "command.preset.file" or "--preset-file"', commandPath);
|
|
1055
|
-
}
|
|
1056
|
-
const resolvedProfile = await this.#resolvePresetProfile({
|
|
1057
|
-
presetFile: effectivePresetFile,
|
|
1058
|
-
presetProfile: effectivePresetProfile,
|
|
1059
|
-
presetProfileSourceName: effectivePresetProfileSourceName,
|
|
1060
|
-
commandPath,
|
|
1061
|
-
});
|
|
1062
|
-
const userSources = {
|
|
1063
|
-
cmds: [...ctx.sources.user.cmds],
|
|
1064
|
-
argv: [...cleanArgv],
|
|
1065
|
-
envs: { ...ctx.sources.user.envs },
|
|
1712
|
+
resolveBuiltinParsedOptions(params) {
|
|
1713
|
+
const { opts, builtinOption, hasUserOption } = params;
|
|
1714
|
+
const builtin = {
|
|
1715
|
+
devmode: builtinOption.devmode ? Boolean(opts['devmode']) : false,
|
|
1066
1716
|
};
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
this.#validatePresetOptionTokens(resolvedProfile.optsArgv, resolvedProfile.optsSourceLabel, commandPath);
|
|
1070
|
-
this.#assertPresetOptionFragments(resolvedProfile.optsArgv, resolvedProfile.optsSourceLabel, commandChain, optionPolicyMap);
|
|
1071
|
-
presetArgv.push(...resolvedProfile.optsArgv);
|
|
1072
|
-
}
|
|
1073
|
-
const presetEnvs = {};
|
|
1074
|
-
if (resolvedProfile !== undefined) {
|
|
1075
|
-
if (resolvedProfile.profileEnvFileSource !== undefined) {
|
|
1076
|
-
const content = await this.#readPresetFile(resolvedProfile.profileEnvFileSource, commandPath);
|
|
1077
|
-
if (content !== undefined) {
|
|
1078
|
-
const parsed = this.#parsePresetEnvsContent(content, resolvedProfile.profileEnvFileSource, commandPath);
|
|
1079
|
-
Object.assign(presetEnvs, parsed);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
Object.assign(presetEnvs, resolvedProfile.profileInlineEnvs);
|
|
1083
|
-
if (resolvedProfile.variantEnvFileSource !== undefined) {
|
|
1084
|
-
const content = await this.#readPresetFile(resolvedProfile.variantEnvFileSource, commandPath);
|
|
1085
|
-
if (content !== undefined) {
|
|
1086
|
-
const parsed = this.#parsePresetEnvsContent(content, resolvedProfile.variantEnvFileSource, commandPath);
|
|
1087
|
-
Object.assign(presetEnvs, parsed);
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
Object.assign(presetEnvs, resolvedProfile.variantInlineEnvs);
|
|
1717
|
+
if (builtinOption.color && !hasUserOption('color')) {
|
|
1718
|
+
builtin.color = Boolean(opts['color']);
|
|
1091
1719
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
envs: presetEnvs,
|
|
1097
|
-
},
|
|
1098
|
-
};
|
|
1099
|
-
const envs = { ...sources.user.envs, ...sources.preset.envs };
|
|
1100
|
-
const tailArgv = [...sources.preset.argv, ...sources.user.argv];
|
|
1101
|
-
return { tailArgv, envs, sources };
|
|
1102
|
-
}
|
|
1103
|
-
#resolveCommandPresetFileFromChain(chain) {
|
|
1104
|
-
for (let index = chain.length - 1; index >= 0; index -= 1) {
|
|
1105
|
-
const preset = chain[index].#presetConfig;
|
|
1106
|
-
if (preset?.file !== undefined) {
|
|
1107
|
-
return preset.file;
|
|
1720
|
+
if (builtinOption.logLevel && !hasUserOption('logLevel')) {
|
|
1721
|
+
const logLevel = opts['logLevel'];
|
|
1722
|
+
if (typeof logLevel === 'string') {
|
|
1723
|
+
builtin.logLevel = logLevel;
|
|
1108
1724
|
}
|
|
1109
1725
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
#resolveCommandPresetProfileFromChain(chain) {
|
|
1113
|
-
for (let index = chain.length - 1; index >= 0; index -= 1) {
|
|
1114
|
-
const preset = chain[index].#presetConfig;
|
|
1115
|
-
if (preset?.profile !== undefined) {
|
|
1116
|
-
return preset.profile;
|
|
1117
|
-
}
|
|
1726
|
+
if (builtinOption.silent && !hasUserOption('silent')) {
|
|
1727
|
+
builtin.silent = Boolean(opts['silent']);
|
|
1118
1728
|
}
|
|
1119
|
-
|
|
1729
|
+
if (builtinOption.logDate && !hasUserOption('logDate')) {
|
|
1730
|
+
builtin.logDate = Boolean(opts['logDate']);
|
|
1731
|
+
}
|
|
1732
|
+
if (builtinOption.logColorful && !hasUserOption('logColorful')) {
|
|
1733
|
+
builtin.logColorful = Boolean(opts['logColorful']);
|
|
1734
|
+
}
|
|
1735
|
+
return builtin;
|
|
1120
1736
|
}
|
|
1121
|
-
#
|
|
1122
|
-
if (
|
|
1123
|
-
return
|
|
1737
|
+
#convertValue(opt, rawValue, commandPath) {
|
|
1738
|
+
if (opt.coerce) {
|
|
1739
|
+
return opt.coerce(rawValue);
|
|
1740
|
+
}
|
|
1741
|
+
if (opt.type === 'number') {
|
|
1742
|
+
const num = parsePrimitiveNumber(rawValue);
|
|
1743
|
+
if (num === undefined) {
|
|
1744
|
+
throw new CommanderError('InvalidType', `invalid number "${rawValue}" for option "--${camelToKebabCase$3(opt.long)}"`, commandPath);
|
|
1745
|
+
}
|
|
1746
|
+
return num;
|
|
1124
1747
|
}
|
|
1125
|
-
return
|
|
1748
|
+
return rawValue;
|
|
1126
1749
|
}
|
|
1127
|
-
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
const PRESET_FILE_FLAG = '--preset-file';
|
|
1753
|
+
const PRESET_PROFILE_FLAG = '--preset-profile';
|
|
1754
|
+
const PRESET_SELECTOR_DELIMITER = ':';
|
|
1755
|
+
const PRESET_PROFILE_NAME_REGEX = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1756
|
+
const PRESET_VARIANT_NAME_REGEX = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1757
|
+
function kebabToCamelCase$1(str) {
|
|
1758
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
1759
|
+
}
|
|
1760
|
+
function camelToKebabCase$2(str) {
|
|
1761
|
+
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
1762
|
+
}
|
|
1763
|
+
class CommandPresetProfileParser {
|
|
1764
|
+
#resolvePresetFileAbsolutePath;
|
|
1765
|
+
#resolvePath;
|
|
1766
|
+
#readPresetFile;
|
|
1767
|
+
constructor(params) {
|
|
1768
|
+
this.#resolvePresetFileAbsolutePath = params.resolvePresetFileAbsolutePath;
|
|
1769
|
+
this.#resolvePath = params.resolvePath;
|
|
1770
|
+
this.#readPresetFile = params.readPresetFile;
|
|
1771
|
+
}
|
|
1772
|
+
async resolvePresetProfile(params) {
|
|
1128
1773
|
const { presetFile, presetProfile, presetProfileSourceName, commandPath } = params;
|
|
1129
1774
|
if (presetFile === undefined) {
|
|
1130
1775
|
if (presetProfile !== undefined) {
|
|
@@ -1142,11 +1787,14 @@ class Command {
|
|
|
1142
1787
|
return undefined;
|
|
1143
1788
|
}
|
|
1144
1789
|
const manifest = this.#parsePresetProfileManifest(content, profileFile.displayPath, commandPath);
|
|
1145
|
-
const resolvedProfileSelector =
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1790
|
+
const resolvedProfileSelector = this.#resolvePresetProfileSelector({
|
|
1791
|
+
presetProfile,
|
|
1792
|
+
presetProfileSourceName,
|
|
1793
|
+
manifest,
|
|
1794
|
+
commandPath,
|
|
1795
|
+
presetFileDisplayPath: profileFile.displayPath,
|
|
1796
|
+
});
|
|
1797
|
+
const { profileName: resolvedProfileName, variantName: explicitVariantName } = this.#parsePresetProfileSelector(resolvedProfileSelector.selector, resolvedProfileSelector.sourceName, commandPath);
|
|
1150
1798
|
const profile = manifest.profiles[resolvedProfileName];
|
|
1151
1799
|
if (profile === undefined) {
|
|
1152
1800
|
throw new CommanderError('ConfigurationError', `unknown preset profile "${resolvedProfileName}" in "${profileFile.displayPath}"`, commandPath);
|
|
@@ -1167,9 +1815,9 @@ class Command {
|
|
|
1167
1815
|
: `${resolvedProfileName}${PRESET_SELECTOR_DELIMITER}${selectedVariantName}`;
|
|
1168
1816
|
const mergedOpts = { ...(profile.opts ?? {}), ...(selectedVariant?.opts ?? {}) };
|
|
1169
1817
|
const optsArgv = this.#buildPresetArgvFromProfileOptions(mergedOpts, profileSelectorLabel, commandPath);
|
|
1170
|
-
const profileInlineEnvs = this.#normalizePresetProfileEnvs(profile.envs
|
|
1171
|
-
const variantInlineEnvs = this.#normalizePresetProfileEnvs(selectedVariant?.envs
|
|
1172
|
-
const profileDir = this.#
|
|
1818
|
+
const profileInlineEnvs = this.#normalizePresetProfileEnvs(profile.envs);
|
|
1819
|
+
const variantInlineEnvs = this.#normalizePresetProfileEnvs(selectedVariant?.envs);
|
|
1820
|
+
const profileDir = this.#resolvePath(profileFile.absolutePath, '..');
|
|
1173
1821
|
let profileEnvFileSource;
|
|
1174
1822
|
if (profile.envFile !== undefined) {
|
|
1175
1823
|
profileEnvFileSource = {
|
|
@@ -1191,19 +1839,95 @@ class Command {
|
|
|
1191
1839
|
variantName: selectedVariantName,
|
|
1192
1840
|
optsArgv,
|
|
1193
1841
|
optsSourceLabel: `${profileFile.displayPath}#${profileSelectorLabel}.opts`,
|
|
1842
|
+
issueMeta: {
|
|
1843
|
+
file: profileFile.displayPath,
|
|
1844
|
+
profile: resolvedProfileName,
|
|
1845
|
+
variant: selectedVariantName,
|
|
1846
|
+
},
|
|
1194
1847
|
profileInlineEnvs,
|
|
1195
1848
|
variantInlineEnvs,
|
|
1196
1849
|
profileEnvFileSource,
|
|
1197
1850
|
variantEnvFileSource,
|
|
1198
1851
|
};
|
|
1199
1852
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1853
|
+
assertPresetProfileSelectorValue(selector, sourceName, commandPath) {
|
|
1854
|
+
void this.#parsePresetProfileSelector(selector, sourceName, commandPath);
|
|
1855
|
+
}
|
|
1856
|
+
validatePresetOptionTokens(tokens, filepath, commandPath) {
|
|
1857
|
+
if (tokens.length === 0) {
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
if (!tokens[0].startsWith('-')) {
|
|
1861
|
+
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": bare token "${tokens[0]}" cannot appear before any option token`, commandPath);
|
|
1862
|
+
}
|
|
1863
|
+
for (const token of tokens) {
|
|
1864
|
+
if (token === '--') {
|
|
1865
|
+
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": "--" is not allowed`, commandPath);
|
|
1866
|
+
}
|
|
1867
|
+
if (token === 'help' || token === '--help' || token === '--version') {
|
|
1868
|
+
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": control token "${token}" is not allowed`, commandPath);
|
|
1869
|
+
}
|
|
1870
|
+
if (token === PRESET_FILE_FLAG ||
|
|
1871
|
+
token.startsWith(`${PRESET_FILE_FLAG}=`) ||
|
|
1872
|
+
token === PRESET_PROFILE_FLAG ||
|
|
1873
|
+
token.startsWith(`${PRESET_PROFILE_FLAG}=`)) {
|
|
1874
|
+
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": preset directive "${token}" is not allowed`, commandPath);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
#resolvePresetProfileSelector(params) {
|
|
1879
|
+
const { presetProfile, presetProfileSourceName, manifest, commandPath, presetFileDisplayPath } = params;
|
|
1880
|
+
if (presetProfile !== undefined) {
|
|
1881
|
+
return {
|
|
1882
|
+
selector: presetProfile,
|
|
1883
|
+
sourceName: presetProfileSourceName ?? PRESET_PROFILE_FLAG,
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
const commandPathSuffixProfile = this.#resolveProfileNameByCommandPathSuffix({
|
|
1887
|
+
commandPath,
|
|
1888
|
+
profiles: manifest.profiles,
|
|
1889
|
+
});
|
|
1890
|
+
if (commandPathSuffixProfile !== undefined) {
|
|
1891
|
+
return {
|
|
1892
|
+
selector: commandPathSuffixProfile,
|
|
1893
|
+
sourceName: 'commandPath.suffix',
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
if (manifest.defaults?.profile !== undefined) {
|
|
1897
|
+
return {
|
|
1898
|
+
selector: manifest.defaults.profile,
|
|
1899
|
+
sourceName: 'defaults.profile',
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
if (manifest.profiles.default !== undefined) {
|
|
1903
|
+
return {
|
|
1904
|
+
selector: 'default',
|
|
1905
|
+
sourceName: 'profiles["default"]',
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
throw new CommanderError('ConfigurationError', `missing profile for preset file "${presetFileDisplayPath}": provide "${PRESET_PROFILE_FLAG}", define command-path suffix profile, defaults.profile, or profile "default"`, commandPath);
|
|
1909
|
+
}
|
|
1910
|
+
#resolveProfileNameByCommandPathSuffix(params) {
|
|
1911
|
+
const { commandPath, profiles } = params;
|
|
1912
|
+
const commandNames = commandPath
|
|
1913
|
+
.split(' ')
|
|
1914
|
+
.map(value => value.trim())
|
|
1915
|
+
.filter(Boolean);
|
|
1916
|
+
for (let index = 0; index < commandNames.length; index += 1) {
|
|
1917
|
+
const profileName = commandNames.slice(index).join('.');
|
|
1918
|
+
if (profiles[profileName] !== undefined) {
|
|
1919
|
+
return profileName;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
return undefined;
|
|
1923
|
+
}
|
|
1924
|
+
#parsePresetProfileManifest(content, filepath, commandPath) {
|
|
1925
|
+
let parsed;
|
|
1926
|
+
try {
|
|
1927
|
+
parsed = JSON.parse(content);
|
|
1928
|
+
}
|
|
1929
|
+
catch (error) {
|
|
1930
|
+
throw new CommanderError('ConfigurationError', `failed to parse preset file "${filepath}": ${error.message}`, commandPath);
|
|
1207
1931
|
}
|
|
1208
1932
|
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
1209
1933
|
throw new CommanderError('ConfigurationError', `invalid preset file "${filepath}": root must be an object`, commandPath);
|
|
@@ -1223,7 +1947,7 @@ class Command {
|
|
|
1223
1947
|
if (typeof defaultsRecord.profile !== 'string') {
|
|
1224
1948
|
throw new CommanderError('ConfigurationError', `invalid preset file "${filepath}": "defaults.profile" must be a string`, commandPath);
|
|
1225
1949
|
}
|
|
1226
|
-
this
|
|
1950
|
+
this.assertPresetProfileSelectorValue(defaultsRecord.profile, 'defaults.profile', commandPath);
|
|
1227
1951
|
}
|
|
1228
1952
|
defaults = { profile: defaultsRecord.profile };
|
|
1229
1953
|
}
|
|
@@ -1379,7 +2103,7 @@ class Command {
|
|
|
1379
2103
|
}
|
|
1380
2104
|
throw new CommanderError('ConfigurationError', `${valueLabel} must be boolean|string|number|(string|number)[]`, commandPath);
|
|
1381
2105
|
}
|
|
1382
|
-
#normalizePresetProfileEnvs(envs
|
|
2106
|
+
#normalizePresetProfileEnvs(envs) {
|
|
1383
2107
|
return envs === undefined ? {} : { ...envs };
|
|
1384
2108
|
}
|
|
1385
2109
|
#normalizePresetOptionName(rawName, profileName, commandPath) {
|
|
@@ -1396,7 +2120,7 @@ class Command {
|
|
|
1396
2120
|
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(lowered)) {
|
|
1397
2121
|
throw new CommanderError('ConfigurationError', `invalid option name "${rawName}" in preset profile "${profileName}"`, commandPath);
|
|
1398
2122
|
}
|
|
1399
|
-
return kebabToCamelCase(lowered);
|
|
2123
|
+
return kebabToCamelCase$1(lowered);
|
|
1400
2124
|
}
|
|
1401
2125
|
if (!/^[a-z][a-zA-Z0-9]*$/.test(stripped)) {
|
|
1402
2126
|
throw new CommanderError('ConfigurationError', `invalid option name "${rawName}" in preset profile "${profileName}"`, commandPath);
|
|
@@ -1407,7 +2131,7 @@ class Command {
|
|
|
1407
2131
|
const argv = [];
|
|
1408
2132
|
for (const [rawName, rawValue] of Object.entries(opts)) {
|
|
1409
2133
|
const optionName = this.#normalizePresetOptionName(rawName, profileName, commandPath);
|
|
1410
|
-
const kebabName = camelToKebabCase$
|
|
2134
|
+
const kebabName = camelToKebabCase$2(optionName);
|
|
1411
2135
|
const positiveFlag = `--${kebabName}`;
|
|
1412
2136
|
const negativeFlag = `--no-${kebabName}`;
|
|
1413
2137
|
if (typeof rawValue === 'boolean') {
|
|
@@ -1429,80 +2153,6 @@ class Command {
|
|
|
1429
2153
|
}
|
|
1430
2154
|
return argv;
|
|
1431
2155
|
}
|
|
1432
|
-
#scanPresetProfileDirectives(argv, commandPath) {
|
|
1433
|
-
const cleanArgv = [];
|
|
1434
|
-
let presetFile;
|
|
1435
|
-
let presetProfile;
|
|
1436
|
-
const assignDirective = (flag, value) => {
|
|
1437
|
-
if (flag === PRESET_FILE_FLAG) {
|
|
1438
|
-
presetFile = value;
|
|
1439
|
-
}
|
|
1440
|
-
else {
|
|
1441
|
-
this.#assertPresetProfileSelectorValue(value, PRESET_PROFILE_FLAG, commandPath);
|
|
1442
|
-
presetProfile = value;
|
|
1443
|
-
}
|
|
1444
|
-
};
|
|
1445
|
-
let index = 0;
|
|
1446
|
-
while (index < argv.length) {
|
|
1447
|
-
const token = argv[index];
|
|
1448
|
-
if (token === PRESET_FILE_FLAG || token === PRESET_PROFILE_FLAG) {
|
|
1449
|
-
const value = argv[index + 1];
|
|
1450
|
-
if (value === undefined || value.length === 0) {
|
|
1451
|
-
throw new CommanderError('ConfigurationError', `missing value for "${token}"`, commandPath);
|
|
1452
|
-
}
|
|
1453
|
-
assignDirective(token, value);
|
|
1454
|
-
index += 2;
|
|
1455
|
-
continue;
|
|
1456
|
-
}
|
|
1457
|
-
if (token.startsWith(`${PRESET_FILE_FLAG}=`)) {
|
|
1458
|
-
const value = token.slice(PRESET_FILE_FLAG.length + 1);
|
|
1459
|
-
if (value.length === 0) {
|
|
1460
|
-
throw new CommanderError('ConfigurationError', `missing value for "${PRESET_FILE_FLAG}"`, commandPath);
|
|
1461
|
-
}
|
|
1462
|
-
assignDirective(PRESET_FILE_FLAG, value);
|
|
1463
|
-
index += 1;
|
|
1464
|
-
continue;
|
|
1465
|
-
}
|
|
1466
|
-
if (token.startsWith(`${PRESET_PROFILE_FLAG}=`)) {
|
|
1467
|
-
const value = token.slice(PRESET_PROFILE_FLAG.length + 1);
|
|
1468
|
-
if (value.length === 0) {
|
|
1469
|
-
throw new CommanderError('ConfigurationError', `missing value for "${PRESET_PROFILE_FLAG}"`, commandPath);
|
|
1470
|
-
}
|
|
1471
|
-
assignDirective(PRESET_PROFILE_FLAG, value);
|
|
1472
|
-
index += 1;
|
|
1473
|
-
continue;
|
|
1474
|
-
}
|
|
1475
|
-
cleanArgv.push(token);
|
|
1476
|
-
index += 1;
|
|
1477
|
-
}
|
|
1478
|
-
return { cleanArgv, presetFile, presetProfile };
|
|
1479
|
-
}
|
|
1480
|
-
#assertPresetOptionFragments(tokens, filepath, chain, optionPolicyMap) {
|
|
1481
|
-
if (tokens.length === 0) {
|
|
1482
|
-
return;
|
|
1483
|
-
}
|
|
1484
|
-
const commandPath = chain[chain.length - 1].#getCommandPath();
|
|
1485
|
-
try {
|
|
1486
|
-
const { optionTokens, restArgs } = tokenize(tokens, commandPath);
|
|
1487
|
-
void restArgs;
|
|
1488
|
-
const { argTokens } = this.#resolve(chain, optionTokens, optionPolicyMap);
|
|
1489
|
-
if (argTokens.length > 0) {
|
|
1490
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": token "${argTokens[0].original}" cannot be resolved as an option fragment`, commandPath);
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
catch (error) {
|
|
1494
|
-
if (error instanceof CommanderError) {
|
|
1495
|
-
if (error.kind === 'ConfigurationError') {
|
|
1496
|
-
throw error;
|
|
1497
|
-
}
|
|
1498
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": ${error.message}`, commandPath);
|
|
1499
|
-
}
|
|
1500
|
-
throw error;
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
#assertPresetProfileSelectorValue(selector, sourceName, commandPath) {
|
|
1504
|
-
void this.#parsePresetProfileSelector(selector, sourceName, commandPath);
|
|
1505
|
-
}
|
|
1506
2156
|
#parsePresetProfileSelector(selector, sourceName, commandPath) {
|
|
1507
2157
|
const normalizedSelector = selector.trim();
|
|
1508
2158
|
const separatorIndex = normalizedSelector.indexOf(PRESET_SELECTOR_DELIMITER);
|
|
@@ -1534,713 +2184,1387 @@ class Command {
|
|
|
1534
2184
|
}
|
|
1535
2185
|
throw new CommanderError('ConfigurationError', `invalid variant name for "${sourceName}": "${variantName}" (must match ${PRESET_VARIANT_NAME_REGEX.source})`, commandPath);
|
|
1536
2186
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
const BUILTIN_LOG_LEVELS = ['debug', 'info', 'hint', 'warn', 'error'];
|
|
2190
|
+
function resolveReporterLogLevel(raw) {
|
|
2191
|
+
const normalized = raw.trim().toLowerCase();
|
|
2192
|
+
return BUILTIN_LOG_LEVELS.find(level => level === normalized);
|
|
2193
|
+
}
|
|
2194
|
+
function setReporterLevel(ctx, level) {
|
|
2195
|
+
const reporter = ctx.reporter;
|
|
2196
|
+
reporter?.setLevel?.(level);
|
|
2197
|
+
}
|
|
2198
|
+
function setReporterFlight(ctx, flight) {
|
|
2199
|
+
const reporter = ctx.reporter;
|
|
2200
|
+
reporter?.setFlight?.(flight);
|
|
2201
|
+
}
|
|
2202
|
+
const devmodeOption = {
|
|
2203
|
+
long: 'devmode',
|
|
2204
|
+
type: 'boolean',
|
|
2205
|
+
args: 'none',
|
|
2206
|
+
desc: 'Enable development mode',
|
|
2207
|
+
default: false,
|
|
2208
|
+
};
|
|
2209
|
+
const logLevelOption = {
|
|
2210
|
+
long: 'logLevel',
|
|
2211
|
+
type: 'string',
|
|
2212
|
+
args: 'required',
|
|
2213
|
+
desc: 'Set log level',
|
|
2214
|
+
default: 'info',
|
|
2215
|
+
choices: [...BUILTIN_LOG_LEVELS],
|
|
2216
|
+
coerce: (raw) => {
|
|
2217
|
+
const level = resolveReporterLogLevel(raw);
|
|
2218
|
+
if (level === undefined) {
|
|
2219
|
+
throw new Error(`Invalid log level: ${raw}`);
|
|
1544
2220
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
2221
|
+
return level;
|
|
2222
|
+
},
|
|
2223
|
+
apply: (value, ctx) => {
|
|
2224
|
+
setReporterLevel(ctx, value);
|
|
2225
|
+
},
|
|
2226
|
+
};
|
|
2227
|
+
const logDateOption = {
|
|
2228
|
+
long: 'logDate',
|
|
2229
|
+
type: 'boolean',
|
|
2230
|
+
args: 'none',
|
|
2231
|
+
desc: 'Enable log timestamp',
|
|
2232
|
+
default: true,
|
|
2233
|
+
apply: (value, ctx) => {
|
|
2234
|
+
setReporterFlight(ctx, { date: Boolean(value) });
|
|
2235
|
+
},
|
|
2236
|
+
};
|
|
2237
|
+
const logColorfulOption = {
|
|
2238
|
+
long: 'logColorful',
|
|
2239
|
+
type: 'boolean',
|
|
2240
|
+
args: 'none',
|
|
2241
|
+
desc: 'Enable colorful log output',
|
|
2242
|
+
default: true,
|
|
2243
|
+
apply: (value, ctx) => {
|
|
2244
|
+
setReporterFlight(ctx, { color: Boolean(value) });
|
|
2245
|
+
},
|
|
2246
|
+
};
|
|
2247
|
+
const silentOption = {
|
|
2248
|
+
long: 'silent',
|
|
2249
|
+
type: 'boolean',
|
|
2250
|
+
args: 'none',
|
|
2251
|
+
desc: 'Suppress non-error output',
|
|
2252
|
+
default: false,
|
|
2253
|
+
apply: (value, ctx) => {
|
|
2254
|
+
if (value) {
|
|
2255
|
+
setReporterLevel(ctx, 'error');
|
|
1551
2256
|
}
|
|
2257
|
+
},
|
|
2258
|
+
};
|
|
2259
|
+
|
|
2260
|
+
const BUILTIN_COLOR_OPTION = {
|
|
2261
|
+
long: 'color',
|
|
2262
|
+
type: 'boolean',
|
|
2263
|
+
args: 'none',
|
|
2264
|
+
desc: 'Enable colored help output',
|
|
2265
|
+
default: true,
|
|
2266
|
+
};
|
|
2267
|
+
function resolveOptionPolicy(params) {
|
|
2268
|
+
const { builtinOption, localOptions } = params;
|
|
2269
|
+
const optionMap = new Map();
|
|
2270
|
+
const hasUserOption = (long) => localOptions.some(option => option.long === long);
|
|
2271
|
+
if (builtinOption.color && !hasUserOption('color')) {
|
|
2272
|
+
optionMap.set('color', BUILTIN_COLOR_OPTION);
|
|
1552
2273
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
return env.parse(content);
|
|
1556
|
-
}
|
|
1557
|
-
catch (error) {
|
|
1558
|
-
throw new CommanderError('ConfigurationError', `failed to parse preset env file "${file.displayPath}": ${error.message}`, commandPath);
|
|
1559
|
-
}
|
|
2274
|
+
if (builtinOption.devmode) {
|
|
2275
|
+
optionMap.set('devmode', devmodeOption);
|
|
1560
2276
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
2277
|
+
if (builtinOption.logLevel && !hasUserOption('logLevel')) {
|
|
2278
|
+
optionMap.set('logLevel', logLevelOption);
|
|
2279
|
+
}
|
|
2280
|
+
if (builtinOption.silent && !hasUserOption('silent')) {
|
|
2281
|
+
optionMap.set('silent', silentOption);
|
|
2282
|
+
}
|
|
2283
|
+
if (builtinOption.logDate && !hasUserOption('logDate')) {
|
|
2284
|
+
optionMap.set('logDate', logDateOption);
|
|
2285
|
+
}
|
|
2286
|
+
if (builtinOption.logColorful && !hasUserOption('logColorful')) {
|
|
2287
|
+
optionMap.set('logColorful', logColorfulOption);
|
|
2288
|
+
}
|
|
2289
|
+
for (const option of localOptions) {
|
|
2290
|
+
optionMap.set(option.long, option);
|
|
2291
|
+
}
|
|
2292
|
+
return {
|
|
2293
|
+
mergedOptions: Array.from(optionMap.values()),
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
function buildOptionPolicyMap(params) {
|
|
2297
|
+
const { chain, resolveOptionPolicy } = params;
|
|
2298
|
+
const optionPolicyMap = new Map();
|
|
2299
|
+
for (const command of chain) {
|
|
2300
|
+
optionPolicyMap.set(command, resolveOptionPolicy(command));
|
|
2301
|
+
}
|
|
2302
|
+
return optionPolicyMap;
|
|
2303
|
+
}
|
|
2304
|
+
function mustGetOptionPolicy(params) {
|
|
2305
|
+
const { optionPolicyMap, command, resolveOptionPolicy } = params;
|
|
2306
|
+
const policy = optionPolicyMap.get(command) ?? resolveOptionPolicy(command);
|
|
2307
|
+
optionPolicyMap.set(command, policy);
|
|
2308
|
+
return policy;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
function scanControl(params) {
|
|
2312
|
+
const { tailArgv, supportsBuiltinVersion } = params;
|
|
2313
|
+
const controls = { help: false, version: false };
|
|
2314
|
+
const separatorIndex = tailArgv.indexOf('--');
|
|
2315
|
+
const beforeSeparator = separatorIndex === -1 ? tailArgv : tailArgv.slice(0, separatorIndex);
|
|
2316
|
+
const afterSeparator = separatorIndex === -1 ? [] : tailArgv.slice(separatorIndex + 1);
|
|
2317
|
+
let helpTarget;
|
|
2318
|
+
let scanStartIndex = 0;
|
|
2319
|
+
if (beforeSeparator[0] === 'help') {
|
|
2320
|
+
controls.help = true;
|
|
2321
|
+
scanStartIndex = 1;
|
|
2322
|
+
const candidate = beforeSeparator[1];
|
|
2323
|
+
if (candidate !== undefined && !candidate.startsWith('-')) {
|
|
2324
|
+
helpTarget = candidate;
|
|
2325
|
+
scanStartIndex = 2;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
const remainingBeforeSeparator = [];
|
|
2329
|
+
for (let index = scanStartIndex; index < beforeSeparator.length; index += 1) {
|
|
2330
|
+
const token = beforeSeparator[index];
|
|
2331
|
+
if (token === '--help') {
|
|
2332
|
+
controls.help = true;
|
|
2333
|
+
continue;
|
|
1564
2334
|
}
|
|
1565
|
-
if (
|
|
1566
|
-
|
|
2335
|
+
if (token === '--version' && supportsBuiltinVersion) {
|
|
2336
|
+
controls.version = true;
|
|
2337
|
+
continue;
|
|
1567
2338
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
2339
|
+
remainingBeforeSeparator.push(token);
|
|
2340
|
+
}
|
|
2341
|
+
return {
|
|
2342
|
+
controls,
|
|
2343
|
+
remaining: separatorIndex === -1
|
|
2344
|
+
? remainingBeforeSeparator
|
|
2345
|
+
: [...remainingBeforeSeparator, '--', ...afterSeparator],
|
|
2346
|
+
helpTarget,
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
function runControl(params) {
|
|
2350
|
+
const { leafCommand, controlScanResult, resolveHelpCommand, getCommandPath, getCommandVersion } = params;
|
|
2351
|
+
if (controlScanResult.controls.help) {
|
|
2352
|
+
const helpCommand = resolveHelpCommand(leafCommand, controlScanResult.helpTarget);
|
|
2353
|
+
return {
|
|
2354
|
+
kind: 'help',
|
|
2355
|
+
targetCommandPath: getCommandPath(helpCommand),
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
if (controlScanResult.controls.version) {
|
|
2359
|
+
const version = getCommandVersion(leafCommand);
|
|
2360
|
+
if (version !== undefined) {
|
|
2361
|
+
return {
|
|
2362
|
+
kind: 'version',
|
|
2363
|
+
targetCommandPath: getCommandPath(leafCommand),
|
|
2364
|
+
version,
|
|
2365
|
+
};
|
|
1581
2366
|
}
|
|
1582
2367
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
}
|
|
1605
|
-
return { consumedTokens, argTokens };
|
|
1606
|
-
}
|
|
1607
|
-
#shift(tokens, shadowed, allOptions) {
|
|
1608
|
-
const effectiveOptions = allOptions.filter(o => !shadowed.has(o.long));
|
|
1609
|
-
const optionByLong = new Map();
|
|
1610
|
-
const optionByShort = new Map();
|
|
1611
|
-
for (const opt of effectiveOptions) {
|
|
1612
|
-
optionByLong.set(opt.long, opt);
|
|
1613
|
-
if (opt.short) {
|
|
1614
|
-
optionByShort.set(opt.short, opt);
|
|
1615
|
-
}
|
|
2368
|
+
return undefined;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
function resolveStage(params) {
|
|
2372
|
+
const { chain, tokens, optionPolicyMap, mustGetOptionPolicy, getLocalOptions, getCommandPath, withUnknownOptionIssue, } = params;
|
|
2373
|
+
try {
|
|
2374
|
+
return resolveTokensByChain({
|
|
2375
|
+
chain,
|
|
2376
|
+
tokens,
|
|
2377
|
+
optionPolicyMap,
|
|
2378
|
+
mustGetOptionPolicy,
|
|
2379
|
+
getLocalOptions,
|
|
2380
|
+
getCommandPath,
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
catch (error) {
|
|
2384
|
+
if (error instanceof CommanderError && error.kind === 'UnknownOption') {
|
|
2385
|
+
const unresolvedToken = error.meta?.token === undefined
|
|
2386
|
+
? undefined
|
|
2387
|
+
: tokens.find(token => token.original === error.meta?.token);
|
|
2388
|
+
throw withUnknownOptionIssue(error, unresolvedToken);
|
|
1616
2389
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
2390
|
+
throw error;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
function shiftTokens(params) {
|
|
2394
|
+
const { tokens, shadowed, allOptions } = params;
|
|
2395
|
+
const effectiveOptions = allOptions.filter(option => !shadowed.has(option.long));
|
|
2396
|
+
const optionByLong = new Map();
|
|
2397
|
+
const optionByShort = new Map();
|
|
2398
|
+
for (const option of effectiveOptions) {
|
|
2399
|
+
optionByLong.set(option.long, option);
|
|
2400
|
+
if (option.short) {
|
|
2401
|
+
optionByShort.set(option.short, option);
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
const consumed = [];
|
|
2405
|
+
const remaining = [];
|
|
2406
|
+
let index = 0;
|
|
2407
|
+
while (index < tokens.length) {
|
|
2408
|
+
const token = tokens[index];
|
|
2409
|
+
if (token.type === 'long') {
|
|
2410
|
+
const option = optionByLong.get(token.name);
|
|
2411
|
+
if (option !== undefined) {
|
|
2412
|
+
consumed.push(token);
|
|
2413
|
+
if (option.args === 'required') {
|
|
2414
|
+
if (!token.resolved.includes('=') && index + 1 < tokens.length) {
|
|
2415
|
+
index += 1;
|
|
2416
|
+
consumed.push(tokens[index]);
|
|
1631
2417
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
2418
|
+
}
|
|
2419
|
+
else if (option.args === 'optional') {
|
|
2420
|
+
if (!token.resolved.includes('=') &&
|
|
2421
|
+
index + 1 < tokens.length &&
|
|
2422
|
+
tokens[index + 1].type === 'none') {
|
|
2423
|
+
index += 1;
|
|
2424
|
+
consumed.push(tokens[index]);
|
|
1639
2425
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
2426
|
+
}
|
|
2427
|
+
else if (option.args === 'variadic' && !token.resolved.includes('=')) {
|
|
2428
|
+
while (index + 1 < tokens.length && tokens[index + 1].type === 'none') {
|
|
2429
|
+
index += 1;
|
|
2430
|
+
consumed.push(tokens[index]);
|
|
1647
2431
|
}
|
|
1648
|
-
i += 1;
|
|
1649
|
-
continue;
|
|
1650
2432
|
}
|
|
1651
|
-
|
|
1652
|
-
i += 1;
|
|
2433
|
+
index += 1;
|
|
1653
2434
|
continue;
|
|
1654
2435
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
i += 1;
|
|
1668
|
-
consumed.push(tokens[i]);
|
|
1669
|
-
}
|
|
2436
|
+
remaining.push(token);
|
|
2437
|
+
index += 1;
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
if (token.type === 'short') {
|
|
2441
|
+
const option = optionByShort.get(token.name);
|
|
2442
|
+
if (option !== undefined) {
|
|
2443
|
+
consumed.push(token);
|
|
2444
|
+
if (option.args === 'required' || option.args === 'optional') {
|
|
2445
|
+
if (index + 1 < tokens.length && tokens[index + 1].type === 'none') {
|
|
2446
|
+
index += 1;
|
|
2447
|
+
consumed.push(tokens[index]);
|
|
1670
2448
|
}
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
2449
|
+
}
|
|
2450
|
+
else if (option.args === 'variadic') {
|
|
2451
|
+
while (index + 1 < tokens.length && tokens[index + 1].type === 'none') {
|
|
2452
|
+
index += 1;
|
|
2453
|
+
consumed.push(tokens[index]);
|
|
1676
2454
|
}
|
|
1677
|
-
i += 1;
|
|
1678
|
-
continue;
|
|
1679
2455
|
}
|
|
1680
|
-
|
|
1681
|
-
i += 1;
|
|
2456
|
+
index += 1;
|
|
1682
2457
|
continue;
|
|
1683
2458
|
}
|
|
1684
2459
|
remaining.push(token);
|
|
1685
|
-
|
|
2460
|
+
index += 1;
|
|
2461
|
+
continue;
|
|
1686
2462
|
}
|
|
1687
|
-
|
|
2463
|
+
remaining.push(token);
|
|
2464
|
+
index += 1;
|
|
1688
2465
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
2466
|
+
return { consumed, remaining };
|
|
2467
|
+
}
|
|
2468
|
+
function resolveTokensByChain(params) {
|
|
2469
|
+
const { chain, tokens, optionPolicyMap, mustGetOptionPolicy, getLocalOptions, getCommandPath } = params;
|
|
2470
|
+
const consumedTokens = new Map();
|
|
2471
|
+
let remaining = [...tokens];
|
|
2472
|
+
const shadowed = new Set();
|
|
2473
|
+
for (let index = chain.length - 1; index >= 0; index -= 1) {
|
|
2474
|
+
const command = chain[index];
|
|
2475
|
+
const policy = mustGetOptionPolicy(optionPolicyMap, command);
|
|
2476
|
+
const result = shiftTokens({
|
|
2477
|
+
tokens: remaining,
|
|
2478
|
+
shadowed,
|
|
2479
|
+
allOptions: policy.mergedOptions,
|
|
2480
|
+
});
|
|
2481
|
+
consumedTokens.set(command, result.consumed);
|
|
2482
|
+
remaining = result.remaining;
|
|
2483
|
+
for (const option of getLocalOptions(command)) {
|
|
2484
|
+
shadowed.add(option.long);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
const leafCommand = chain[chain.length - 1];
|
|
2488
|
+
const leafCommandPath = getCommandPath(leafCommand);
|
|
2489
|
+
const argTokens = [];
|
|
2490
|
+
for (const token of remaining) {
|
|
2491
|
+
if (token.type !== 'none') {
|
|
2492
|
+
throw new CommanderError('UnknownOption', `unknown option "${token.original}" for command "${leafCommandPath}"`, leafCommandPath, {
|
|
2493
|
+
commandPath: leafCommandPath,
|
|
2494
|
+
token: token.original,
|
|
2495
|
+
issues: [],
|
|
2496
|
+
});
|
|
1704
2497
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
2498
|
+
argTokens.push(token);
|
|
2499
|
+
}
|
|
2500
|
+
return { consumedTokens, argTokens };
|
|
2501
|
+
}
|
|
2502
|
+
function validateMergedShortOptions(params) {
|
|
2503
|
+
const { chain, optionPolicyMap, mustGetOptionPolicy, rootCommandPath } = params;
|
|
2504
|
+
const mergedByLong = new Map();
|
|
2505
|
+
for (const command of chain) {
|
|
2506
|
+
const policy = mustGetOptionPolicy(optionPolicyMap, command);
|
|
2507
|
+
for (const option of policy.mergedOptions) {
|
|
2508
|
+
mergedByLong.set(option.long, option);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
const shortMap = new Map();
|
|
2512
|
+
for (const option of mergedByLong.values()) {
|
|
2513
|
+
if (!option.short) {
|
|
2514
|
+
continue;
|
|
1711
2515
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
sources: this.#freezeInputSources(ctx.sources),
|
|
1718
|
-
};
|
|
1719
|
-
return { ctx: parseCtx, opts: leafLocalOpts, args, rawArgs };
|
|
2516
|
+
const existingLong = shortMap.get(option.short);
|
|
2517
|
+
if (existingLong !== undefined && existingLong !== option.long) {
|
|
2518
|
+
throw new CommanderError('OptionConflict', `short option "-${option.short}" conflicts with "--${existingLong}"`, rootCommandPath);
|
|
2519
|
+
}
|
|
2520
|
+
shortMap.set(option.short, option.long);
|
|
1720
2521
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
function camelToKebabCase$1(str) {
|
|
2525
|
+
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
2526
|
+
}
|
|
2527
|
+
function normalizeSubcommandNameForDistance(name) {
|
|
2528
|
+
return camelToKebabCase$1(name).toLowerCase();
|
|
2529
|
+
}
|
|
2530
|
+
function levenshteinDistance(left, right) {
|
|
2531
|
+
if (left === right) {
|
|
2532
|
+
return 0;
|
|
2533
|
+
}
|
|
2534
|
+
if (left.length === 0) {
|
|
2535
|
+
return right.length;
|
|
2536
|
+
}
|
|
2537
|
+
if (right.length === 0) {
|
|
2538
|
+
return left.length;
|
|
2539
|
+
}
|
|
2540
|
+
let prev = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
2541
|
+
for (let row = 0; row < left.length; row += 1) {
|
|
2542
|
+
const current = [row + 1];
|
|
2543
|
+
for (let col = 0; col < right.length; col += 1) {
|
|
2544
|
+
const substitutionCost = left[row] === right[col] ? 0 : 1;
|
|
2545
|
+
current[col + 1] = Math.min(current[col] + 1, prev[col + 1] + 1, prev[col] + substitutionCost);
|
|
1734
2546
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
2547
|
+
prev = current;
|
|
2548
|
+
}
|
|
2549
|
+
return prev[right.length];
|
|
2550
|
+
}
|
|
2551
|
+
function convertArgumentValue(params) {
|
|
2552
|
+
const { def, raw, commandPath } = params;
|
|
2553
|
+
let value;
|
|
2554
|
+
if (def.coerce) {
|
|
2555
|
+
try {
|
|
2556
|
+
value = def.coerce(raw);
|
|
1742
2557
|
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
i += 1;
|
|
1776
|
-
continue;
|
|
1777
|
-
}
|
|
1778
|
-
if (opt.args === 'required') {
|
|
1779
|
-
const eqIdx = token.resolved.indexOf('=');
|
|
1780
|
-
let rawValue;
|
|
1781
|
-
if (eqIdx !== -1) {
|
|
1782
|
-
rawValue = token.resolved.slice(eqIdx + 1);
|
|
1783
|
-
}
|
|
1784
|
-
else if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1785
|
-
rawValue = tokens[i + 1].original;
|
|
1786
|
-
i += 1;
|
|
1787
|
-
}
|
|
1788
|
-
else {
|
|
1789
|
-
throw new CommanderError('MissingValue', `option "--${camelToKebabCase$1(opt.long)}" requires a value`, this.#getCommandPath());
|
|
1790
|
-
}
|
|
1791
|
-
opts[opt.long] = this.#convertValue(opt, rawValue);
|
|
1792
|
-
i += 1;
|
|
1793
|
-
continue;
|
|
1794
|
-
}
|
|
1795
|
-
if (opt.args === 'optional') {
|
|
1796
|
-
const eqIdx = token.resolved.indexOf('=');
|
|
1797
|
-
if (eqIdx !== -1) {
|
|
1798
|
-
opts[opt.long] = this.#convertValue(opt, token.resolved.slice(eqIdx + 1));
|
|
1799
|
-
i += 1;
|
|
1800
|
-
continue;
|
|
1801
|
-
}
|
|
1802
|
-
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1803
|
-
opts[opt.long] = this.#convertValue(opt, tokens[i + 1].original);
|
|
1804
|
-
i += 1;
|
|
1805
|
-
}
|
|
1806
|
-
else {
|
|
1807
|
-
opts[opt.long] = undefined;
|
|
1808
|
-
}
|
|
1809
|
-
i += 1;
|
|
1810
|
-
continue;
|
|
2558
|
+
catch {
|
|
2559
|
+
throw new CommanderError('InvalidType', `invalid value "${raw}" for argument "${def.name}"`, commandPath);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
else {
|
|
2563
|
+
value = raw;
|
|
2564
|
+
}
|
|
2565
|
+
if (typeof value !== 'string') {
|
|
2566
|
+
throw new CommanderError('InvalidType', `invalid value for argument "${def.name}": expected ${def.type}`, commandPath);
|
|
2567
|
+
}
|
|
2568
|
+
if (def.type === 'choice') {
|
|
2569
|
+
const choices = def.choices ?? [];
|
|
2570
|
+
if (!choices.includes(value)) {
|
|
2571
|
+
throw new CommanderError('InvalidChoice', `invalid value "${value}" for argument "${def.name}". Allowed: ${choices
|
|
2572
|
+
.map(choice => JSON.stringify(choice))
|
|
2573
|
+
.join(', ')}`, commandPath);
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
return value;
|
|
2577
|
+
}
|
|
2578
|
+
function parseArguments(params) {
|
|
2579
|
+
const { argumentDefs, rawArgs, commandPath } = params;
|
|
2580
|
+
const args = {};
|
|
2581
|
+
if (argumentDefs.length === 0 && rawArgs.length > 0) {
|
|
2582
|
+
throw new CommanderError('UnexpectedArgument', `unexpected argument "${rawArgs[0]}"`, commandPath);
|
|
2583
|
+
}
|
|
2584
|
+
const missing = [];
|
|
2585
|
+
let remaining = rawArgs.length;
|
|
2586
|
+
for (const def of argumentDefs) {
|
|
2587
|
+
if (def.kind === 'required') {
|
|
2588
|
+
if (remaining === 0) {
|
|
2589
|
+
missing.push(def.name);
|
|
1811
2590
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
const eqIdx = token.resolved.indexOf('=');
|
|
1815
|
-
if (eqIdx !== -1) {
|
|
1816
|
-
values.push(this.#convertValue(opt, token.resolved.slice(eqIdx + 1)));
|
|
1817
|
-
}
|
|
1818
|
-
else {
|
|
1819
|
-
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1820
|
-
i += 1;
|
|
1821
|
-
values.push(this.#convertValue(opt, tokens[i].original));
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
opts[opt.long] = values;
|
|
1825
|
-
i += 1;
|
|
1826
|
-
continue;
|
|
2591
|
+
else {
|
|
2592
|
+
remaining -= 1;
|
|
1827
2593
|
}
|
|
1828
|
-
|
|
2594
|
+
continue;
|
|
1829
2595
|
}
|
|
1830
|
-
|
|
1831
|
-
if (
|
|
1832
|
-
|
|
2596
|
+
if (def.kind === 'optional') {
|
|
2597
|
+
if (remaining > 0) {
|
|
2598
|
+
remaining -= 1;
|
|
1833
2599
|
}
|
|
2600
|
+
continue;
|
|
1834
2601
|
}
|
|
1835
|
-
|
|
1836
|
-
if (
|
|
1837
|
-
|
|
1838
|
-
const values = Array.isArray(value) ? value : [value];
|
|
1839
|
-
const choices = opt.choices;
|
|
1840
|
-
for (const v of values) {
|
|
1841
|
-
if (!choices.includes(v)) {
|
|
1842
|
-
throw new CommanderError('InvalidChoice', `invalid value "${v}" for option "--${camelToKebabCase$1(opt.long)}". Allowed: ${opt.choices.join(', ')}`, this.#getCommandPath());
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
2602
|
+
if (def.kind === 'some') {
|
|
2603
|
+
if (remaining === 0) {
|
|
2604
|
+
missing.push(def.name);
|
|
1845
2605
|
}
|
|
2606
|
+
remaining = 0;
|
|
2607
|
+
continue;
|
|
1846
2608
|
}
|
|
1847
|
-
|
|
1848
|
-
opts['color'] = false;
|
|
1849
|
-
}
|
|
1850
|
-
return opts;
|
|
2609
|
+
remaining = 0;
|
|
1851
2610
|
}
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
return opt.coerce(rawValue);
|
|
1855
|
-
}
|
|
1856
|
-
if (opt.type === 'number') {
|
|
1857
|
-
const num = parsePrimitiveNumber(rawValue);
|
|
1858
|
-
if (num === undefined) {
|
|
1859
|
-
throw new CommanderError('InvalidType', `invalid number "${rawValue}" for option "--${camelToKebabCase$1(opt.long)}"`, this.#getCommandPath());
|
|
1860
|
-
}
|
|
1861
|
-
return num;
|
|
1862
|
-
}
|
|
1863
|
-
return rawValue;
|
|
2611
|
+
if (missing.length > 0) {
|
|
2612
|
+
throw new CommanderError('MissingRequiredArgument', `missing required argument(s): ${missing.join(', ')}`, commandPath);
|
|
1864
2613
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
let remaining = rawArgs.length;
|
|
1873
|
-
for (const def of argumentDefs) {
|
|
1874
|
-
if (def.kind === 'required') {
|
|
1875
|
-
if (remaining === 0) {
|
|
1876
|
-
missing.push(def.name);
|
|
1877
|
-
}
|
|
1878
|
-
else {
|
|
1879
|
-
remaining -= 1;
|
|
1880
|
-
}
|
|
1881
|
-
continue;
|
|
1882
|
-
}
|
|
1883
|
-
if (def.kind === 'optional') {
|
|
1884
|
-
if (remaining > 0) {
|
|
1885
|
-
remaining -= 1;
|
|
1886
|
-
}
|
|
1887
|
-
continue;
|
|
1888
|
-
}
|
|
1889
|
-
if (def.kind === 'some') {
|
|
1890
|
-
if (remaining === 0) {
|
|
1891
|
-
missing.push(def.name);
|
|
1892
|
-
}
|
|
1893
|
-
remaining = 0;
|
|
1894
|
-
continue;
|
|
1895
|
-
}
|
|
1896
|
-
remaining = 0;
|
|
2614
|
+
let index = 0;
|
|
2615
|
+
for (const def of argumentDefs) {
|
|
2616
|
+
if (def.kind === 'variadic' || def.kind === 'some') {
|
|
2617
|
+
const rest = rawArgs.slice(index);
|
|
2618
|
+
args[def.name] = rest.map(raw => convertArgumentValue({ def, raw, commandPath }));
|
|
2619
|
+
index = rawArgs.length;
|
|
2620
|
+
break;
|
|
1897
2621
|
}
|
|
1898
|
-
if (
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
for (const def of argumentDefs) {
|
|
1903
|
-
if (def.kind === 'variadic') {
|
|
1904
|
-
const rest = rawArgs.slice(index);
|
|
1905
|
-
args[def.name] = rest.map(raw => this.#convertArgument(def, raw));
|
|
1906
|
-
index = rawArgs.length;
|
|
1907
|
-
break;
|
|
1908
|
-
}
|
|
1909
|
-
if (def.kind === 'some') {
|
|
1910
|
-
const rest = rawArgs.slice(index);
|
|
1911
|
-
args[def.name] = rest.map(raw => this.#convertArgument(def, raw));
|
|
1912
|
-
index = rawArgs.length;
|
|
1913
|
-
break;
|
|
1914
|
-
}
|
|
1915
|
-
if (def.kind === 'optional') {
|
|
1916
|
-
const raw = rawArgs[index];
|
|
1917
|
-
if (raw === undefined) {
|
|
1918
|
-
args[def.name] = def.default ?? undefined;
|
|
1919
|
-
continue;
|
|
1920
|
-
}
|
|
1921
|
-
args[def.name] = this.#convertArgument(def, raw);
|
|
1922
|
-
index += 1;
|
|
2622
|
+
if (def.kind === 'optional') {
|
|
2623
|
+
const raw = rawArgs[index];
|
|
2624
|
+
if (raw === undefined) {
|
|
2625
|
+
args[def.name] = def.default ?? undefined;
|
|
1923
2626
|
continue;
|
|
1924
2627
|
}
|
|
1925
|
-
|
|
1926
|
-
args[def.name] = this.#convertArgument(def, raw);
|
|
2628
|
+
args[def.name] = convertArgumentValue({ def, raw, commandPath });
|
|
1927
2629
|
index += 1;
|
|
2630
|
+
continue;
|
|
1928
2631
|
}
|
|
1929
|
-
const
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
}
|
|
1933
|
-
return { args, rawArgs };
|
|
2632
|
+
const raw = rawArgs[index];
|
|
2633
|
+
args[def.name] = convertArgumentValue({ def, raw, commandPath });
|
|
2634
|
+
index += 1;
|
|
1934
2635
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
2636
|
+
const hasRestArgument = argumentDefs.some(def => def.kind === 'variadic' || def.kind === 'some');
|
|
2637
|
+
if (!hasRestArgument && index < rawArgs.length) {
|
|
2638
|
+
throw new CommanderError('TooManyArguments', `too many arguments: expected ${argumentDefs.length}, got ${rawArgs.length}`, commandPath);
|
|
2639
|
+
}
|
|
2640
|
+
return { args, rawArgs };
|
|
2641
|
+
}
|
|
2642
|
+
function resolveDidYouMeanSubcommandName(token, subcommands) {
|
|
2643
|
+
const source = normalizeSubcommandNameForDistance(token);
|
|
2644
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
2645
|
+
let bestName;
|
|
2646
|
+
let isUniqueBest = false;
|
|
2647
|
+
for (const entry of subcommands) {
|
|
2648
|
+
const target = normalizeSubcommandNameForDistance(entry.name);
|
|
2649
|
+
const distance = levenshteinDistance(source, target);
|
|
2650
|
+
if (distance < minDistance) {
|
|
2651
|
+
minDistance = distance;
|
|
2652
|
+
bestName = entry.name;
|
|
2653
|
+
isUniqueBest = true;
|
|
2654
|
+
}
|
|
2655
|
+
else if (distance === minDistance) {
|
|
2656
|
+
isUniqueBest = false;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
if (minDistance <= 2 && isUniqueBest) {
|
|
2660
|
+
return bestName;
|
|
2661
|
+
}
|
|
2662
|
+
return undefined;
|
|
2663
|
+
}
|
|
2664
|
+
function assertUnknownSubcommand(params) {
|
|
2665
|
+
const { userTailArgv, subcommands, hasArguments, commandPath, withDiagnosticsIssue } = params;
|
|
2666
|
+
if (subcommands.length === 0) {
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
const token = userTailArgv[0];
|
|
2670
|
+
if (token === undefined || token.startsWith('-') || token === 'help') {
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
const matched = subcommands.some(entry => entry.name === token || entry.aliases.includes(token));
|
|
2674
|
+
if (matched) {
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
let error = new CommanderError('UnknownSubcommand', `unknown subcommand "${token}" for command "${commandPath}"`, commandPath);
|
|
2678
|
+
error = withDiagnosticsIssue(error);
|
|
2679
|
+
if (!hasArguments) {
|
|
2680
|
+
const hint = {
|
|
2681
|
+
kind: 'hint',
|
|
2682
|
+
stage: 'parse',
|
|
2683
|
+
scope: 'command',
|
|
2684
|
+
reason: {
|
|
2685
|
+
code: 'command_does_not_accept_positional_arguments',
|
|
2686
|
+
message: `command "${commandPath}" does not accept positional arguments`,
|
|
2687
|
+
},
|
|
2688
|
+
};
|
|
2689
|
+
error = error.withIssue(hint);
|
|
2690
|
+
}
|
|
2691
|
+
const candidate = resolveDidYouMeanSubcommandName(token, subcommands);
|
|
2692
|
+
if (candidate !== undefined) {
|
|
2693
|
+
const hint = {
|
|
2694
|
+
kind: 'hint',
|
|
2695
|
+
stage: 'parse',
|
|
2696
|
+
scope: 'command',
|
|
2697
|
+
reason: {
|
|
2698
|
+
code: 'did_you_mean_subcommand',
|
|
2699
|
+
message: `did you mean "${candidate}"?`,
|
|
2700
|
+
details: { candidate },
|
|
2701
|
+
},
|
|
2702
|
+
};
|
|
2703
|
+
error = error.withIssue(hint);
|
|
2704
|
+
}
|
|
2705
|
+
throw error;
|
|
2706
|
+
}
|
|
2707
|
+
function collectRawArguments(params) {
|
|
2708
|
+
const { argTokens, restArgs } = params;
|
|
2709
|
+
return [...argTokens.map(token => token.original), ...restArgs];
|
|
2710
|
+
}
|
|
2711
|
+
function parseStage(params) {
|
|
2712
|
+
const { chain, resolveResult, optionPolicyMap, ctx, restArgs, rootCommandPath, mustGetOptionPolicy, parseOptions, applyBuiltinDevmodeLogLevel, resolveBuiltinParsedOptions, applyOptionCallbacks, getLocalOptions, getSubcommands, getArguments, getCommandPath, withUnknownSubcommandIssue, freezeInputSources, } = params;
|
|
2713
|
+
const { consumedTokens, argTokens } = resolveResult;
|
|
2714
|
+
const leafCommand = chain[chain.length - 1];
|
|
2715
|
+
validateMergedShortOptions({
|
|
2716
|
+
chain,
|
|
2717
|
+
optionPolicyMap,
|
|
2718
|
+
mustGetOptionPolicy: (map, command) => mustGetOptionPolicy(map, command),
|
|
2719
|
+
rootCommandPath,
|
|
2720
|
+
});
|
|
2721
|
+
const optsMap = new Map();
|
|
2722
|
+
const builtinMap = new Map();
|
|
2723
|
+
for (const command of chain) {
|
|
2724
|
+
const policy = mustGetOptionPolicy(optionPolicyMap, command);
|
|
2725
|
+
const tokens = consumedTokens.get(command) ?? [];
|
|
2726
|
+
const commandPath = getCommandPath(command);
|
|
2727
|
+
const parseOptionsResult = parseOptions({
|
|
2728
|
+
command,
|
|
2729
|
+
tokens,
|
|
2730
|
+
allOptions: policy.mergedOptions,
|
|
2731
|
+
envs: ctx.envs,
|
|
2732
|
+
commandPath,
|
|
2733
|
+
});
|
|
2734
|
+
const opts = parseOptionsResult.opts;
|
|
2735
|
+
applyBuiltinDevmodeLogLevel({
|
|
2736
|
+
command,
|
|
2737
|
+
opts,
|
|
2738
|
+
explicitOptionLongs: parseOptionsResult.explicitOptionLongs,
|
|
2739
|
+
});
|
|
2740
|
+
optsMap.set(command, opts);
|
|
2741
|
+
builtinMap.set(command, resolveBuiltinParsedOptions({ command, opts }));
|
|
2742
|
+
applyOptionCallbacks({
|
|
2743
|
+
command,
|
|
2744
|
+
opts,
|
|
2745
|
+
allOptions: policy.mergedOptions,
|
|
2746
|
+
ctx,
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
const leafLocalOpts = {};
|
|
2750
|
+
const leafParsedOpts = optsMap.get(leafCommand) ?? {};
|
|
2751
|
+
for (const option of getLocalOptions(leafCommand)) {
|
|
2752
|
+
if (Object.prototype.hasOwnProperty.call(leafParsedOpts, option.long)) {
|
|
2753
|
+
leafLocalOpts[option.long] = leafParsedOpts[option.long];
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
const leafCommandPath = getCommandPath(leafCommand);
|
|
2757
|
+
const leafArguments = getArguments(leafCommand);
|
|
2758
|
+
assertUnknownSubcommand({
|
|
2759
|
+
userTailArgv: ctx.sources.user.argv,
|
|
2760
|
+
subcommands: getSubcommands(leafCommand),
|
|
2761
|
+
hasArguments: leafArguments.length > 0,
|
|
2762
|
+
commandPath: leafCommandPath,
|
|
2763
|
+
withDiagnosticsIssue: withUnknownSubcommandIssue,
|
|
2764
|
+
});
|
|
2765
|
+
const rawArgStrings = collectRawArguments({ argTokens, restArgs });
|
|
2766
|
+
const { args, rawArgs } = parseArguments({
|
|
2767
|
+
argumentDefs: leafArguments,
|
|
2768
|
+
rawArgs: rawArgStrings,
|
|
2769
|
+
commandPath: leafCommandPath,
|
|
2770
|
+
});
|
|
2771
|
+
const parseCtx = {
|
|
2772
|
+
...ctx,
|
|
2773
|
+
sources: freezeInputSources(ctx.sources),
|
|
2774
|
+
};
|
|
2775
|
+
return {
|
|
2776
|
+
ctx: parseCtx,
|
|
2777
|
+
builtin: builtinMap.get(leafCommand) ?? { devmode: false },
|
|
2778
|
+
opts: leafLocalOpts,
|
|
2779
|
+
args,
|
|
2780
|
+
rawArgs,
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
function resolvePresetConfigFromChain(params) {
|
|
2785
|
+
const { chain, getPresetConfig } = params;
|
|
2786
|
+
let presetFile;
|
|
2787
|
+
let presetProfile;
|
|
2788
|
+
for (let index = chain.length - 1; index >= 0; index -= 1) {
|
|
2789
|
+
const presetConfig = getPresetConfig(chain[index]);
|
|
2790
|
+
if (presetFile === undefined && presetConfig?.file !== undefined) {
|
|
2791
|
+
presetFile = presetConfig.file;
|
|
1947
2792
|
}
|
|
1948
|
-
if (
|
|
1949
|
-
|
|
2793
|
+
if (presetProfile === undefined && presetConfig?.profile !== undefined) {
|
|
2794
|
+
presetProfile = presetConfig.profile;
|
|
1950
2795
|
}
|
|
1951
|
-
if (
|
|
1952
|
-
|
|
1953
|
-
if (!choices.includes(value)) {
|
|
1954
|
-
throw new CommanderError('InvalidChoice', `invalid value "${value}" for argument "${def.name}". Allowed: ${choices
|
|
1955
|
-
.map(choice => JSON.stringify(choice))
|
|
1956
|
-
.join(', ')}`, this.#getCommandPath());
|
|
1957
|
-
}
|
|
2796
|
+
if (presetFile !== undefined && presetProfile !== undefined) {
|
|
2797
|
+
break;
|
|
1958
2798
|
}
|
|
1959
|
-
return value;
|
|
1960
2799
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
if (
|
|
2800
|
+
return { presetFile, presetProfile };
|
|
2801
|
+
}
|
|
2802
|
+
function scanPresetDirectives(params) {
|
|
2803
|
+
const { argv, commandPath, presetFileFlag, presetProfileFlag, assertPresetProfileSelectorValue } = params;
|
|
2804
|
+
const cleanArgv = [];
|
|
2805
|
+
let presetFile;
|
|
2806
|
+
let presetProfile;
|
|
2807
|
+
const assignDirective = (flag, value) => {
|
|
2808
|
+
if (flag === presetFileFlag) {
|
|
2809
|
+
presetFile = value;
|
|
1970
2810
|
return;
|
|
1971
2811
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2812
|
+
assertPresetProfileSelectorValue(value, presetProfileFlag, commandPath);
|
|
2813
|
+
presetProfile = value;
|
|
2814
|
+
};
|
|
2815
|
+
let index = 0;
|
|
2816
|
+
while (index < argv.length) {
|
|
2817
|
+
const token = argv[index];
|
|
2818
|
+
if (token === presetFileFlag || token === presetProfileFlag) {
|
|
2819
|
+
const value = argv[index + 1];
|
|
2820
|
+
if (value === undefined || value.length === 0) {
|
|
2821
|
+
throw new CommanderError('ConfigurationError', `missing value for "${token}"`, commandPath);
|
|
2822
|
+
}
|
|
2823
|
+
assignDirective(token, value);
|
|
2824
|
+
index += 2;
|
|
2825
|
+
continue;
|
|
1979
2826
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
const source = normalizeSubcommandNameForDistance(token);
|
|
1985
|
-
let minDistance = Number.POSITIVE_INFINITY;
|
|
1986
|
-
let bestName;
|
|
1987
|
-
let isUniqueBest = false;
|
|
1988
|
-
for (const entry of this.#subcommandsList) {
|
|
1989
|
-
const target = normalizeSubcommandNameForDistance(entry.name);
|
|
1990
|
-
const distance = levenshteinDistance(source, target);
|
|
1991
|
-
if (distance < minDistance) {
|
|
1992
|
-
minDistance = distance;
|
|
1993
|
-
bestName = entry.name;
|
|
1994
|
-
isUniqueBest = true;
|
|
1995
|
-
}
|
|
1996
|
-
else if (distance === minDistance) {
|
|
1997
|
-
isUniqueBest = false;
|
|
2827
|
+
if (token.startsWith(`${presetFileFlag}=`)) {
|
|
2828
|
+
const value = token.slice(presetFileFlag.length + 1);
|
|
2829
|
+
if (value.length === 0) {
|
|
2830
|
+
throw new CommanderError('ConfigurationError', `missing value for "${presetFileFlag}"`, commandPath);
|
|
1998
2831
|
}
|
|
2832
|
+
assignDirective(presetFileFlag, value);
|
|
2833
|
+
index += 1;
|
|
2834
|
+
continue;
|
|
1999
2835
|
}
|
|
2000
|
-
if (
|
|
2001
|
-
|
|
2836
|
+
if (token.startsWith(`${presetProfileFlag}=`)) {
|
|
2837
|
+
const value = token.slice(presetProfileFlag.length + 1);
|
|
2838
|
+
if (value.length === 0) {
|
|
2839
|
+
throw new CommanderError('ConfigurationError', `missing value for "${presetProfileFlag}"`, commandPath);
|
|
2840
|
+
}
|
|
2841
|
+
assignDirective(presetProfileFlag, value);
|
|
2842
|
+
index += 1;
|
|
2843
|
+
continue;
|
|
2002
2844
|
}
|
|
2845
|
+
cleanArgv.push(token);
|
|
2846
|
+
index += 1;
|
|
2847
|
+
}
|
|
2848
|
+
return { cleanArgv, presetFile, presetProfile };
|
|
2849
|
+
}
|
|
2850
|
+
function buildPresetSources(params) {
|
|
2851
|
+
const { userCmds, userArgv, userEnvs, presetArgv, presetEnvs, presetMeta, presetResolvedEnvFile, } = params;
|
|
2852
|
+
const presetSourceMeta = presetMeta === undefined
|
|
2853
|
+
? undefined
|
|
2854
|
+
: {
|
|
2855
|
+
applied: true,
|
|
2856
|
+
file: presetMeta.file,
|
|
2857
|
+
profile: presetMeta.profile,
|
|
2858
|
+
variant: presetMeta.variant,
|
|
2859
|
+
...(presetResolvedEnvFile === undefined
|
|
2860
|
+
? {}
|
|
2861
|
+
: {
|
|
2862
|
+
resolvedEnvFile: presetResolvedEnvFile,
|
|
2863
|
+
}),
|
|
2864
|
+
};
|
|
2865
|
+
const presetState = presetSourceMeta === undefined ? 'none' : 'applied';
|
|
2866
|
+
const sources = {
|
|
2867
|
+
user: {
|
|
2868
|
+
cmds: [...userCmds],
|
|
2869
|
+
argv: [...userArgv],
|
|
2870
|
+
envs: { ...userEnvs },
|
|
2871
|
+
},
|
|
2872
|
+
preset: {
|
|
2873
|
+
state: presetState,
|
|
2874
|
+
argv: [...presetArgv],
|
|
2875
|
+
envs: { ...presetEnvs },
|
|
2876
|
+
meta: presetSourceMeta === undefined ? undefined : { ...presetSourceMeta },
|
|
2877
|
+
},
|
|
2878
|
+
};
|
|
2879
|
+
const envs = { ...sources.user.envs, ...sources.preset.envs };
|
|
2880
|
+
const tailArgv = [...sources.preset.argv, ...sources.user.argv];
|
|
2881
|
+
const segments = [
|
|
2882
|
+
...sources.preset.argv.map(value => ({
|
|
2883
|
+
value,
|
|
2884
|
+
source: 'preset',
|
|
2885
|
+
preset: sources.preset.meta === undefined
|
|
2886
|
+
? undefined
|
|
2887
|
+
: {
|
|
2888
|
+
file: sources.preset.meta.file,
|
|
2889
|
+
profile: sources.preset.meta.profile,
|
|
2890
|
+
variant: sources.preset.meta.variant,
|
|
2891
|
+
},
|
|
2892
|
+
})),
|
|
2893
|
+
...sources.user.argv.map(value => ({ value, source: 'user' })),
|
|
2894
|
+
];
|
|
2895
|
+
return {
|
|
2896
|
+
sources,
|
|
2897
|
+
tailArgv,
|
|
2898
|
+
envs,
|
|
2899
|
+
segments,
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2902
|
+
async function resolvePresetStage(params) {
|
|
2903
|
+
const { controlTailArgv, chain, commandPath, presetFileFlag, presetProfileFlag, getPresetConfig, assertPresetProfileSelectorValue, resolvePresetProfile, } = params;
|
|
2904
|
+
const separatorIndex = controlTailArgv.indexOf('--');
|
|
2905
|
+
const beforeSeparator = separatorIndex === -1 ? controlTailArgv : controlTailArgv.slice(0, separatorIndex);
|
|
2906
|
+
const afterSeparator = separatorIndex === -1 ? [] : controlTailArgv.slice(separatorIndex + 1);
|
|
2907
|
+
const profileScanResult = scanPresetDirectives({
|
|
2908
|
+
argv: beforeSeparator,
|
|
2909
|
+
commandPath,
|
|
2910
|
+
presetFileFlag,
|
|
2911
|
+
presetProfileFlag,
|
|
2912
|
+
assertPresetProfileSelectorValue,
|
|
2913
|
+
});
|
|
2914
|
+
const cleanArgv = separatorIndex === -1
|
|
2915
|
+
? profileScanResult.cleanArgv
|
|
2916
|
+
: [...profileScanResult.cleanArgv, '--', ...afterSeparator];
|
|
2917
|
+
const resolvedCommandPresetConfig = resolvePresetConfigFromChain({
|
|
2918
|
+
chain,
|
|
2919
|
+
getPresetConfig,
|
|
2920
|
+
});
|
|
2921
|
+
const commandPresetFile = resolvedCommandPresetConfig.presetFile;
|
|
2922
|
+
const effectivePresetFile = profileScanResult.presetFile ?? commandPresetFile;
|
|
2923
|
+
const commandPresetProfile = resolvedCommandPresetConfig.presetProfile;
|
|
2924
|
+
const useCommandPresetProfile = profileScanResult.presetProfile === undefined && commandPresetProfile !== undefined;
|
|
2925
|
+
if (useCommandPresetProfile) {
|
|
2926
|
+
assertPresetProfileSelectorValue(commandPresetProfile, 'command.preset.profile', commandPath);
|
|
2927
|
+
}
|
|
2928
|
+
const effectivePresetProfile = profileScanResult.presetProfile ?? commandPresetProfile;
|
|
2929
|
+
const effectivePresetProfileSourceName = profileScanResult.presetProfile !== undefined
|
|
2930
|
+
? presetProfileFlag
|
|
2931
|
+
: commandPresetProfile !== undefined
|
|
2932
|
+
? 'command.preset.profile'
|
|
2933
|
+
: undefined;
|
|
2934
|
+
if (effectivePresetFile === undefined && useCommandPresetProfile) {
|
|
2935
|
+
throw new CommanderError('ConfigurationError', 'cannot use "command.preset.profile" without "command.preset.file" or "--preset-file"', commandPath);
|
|
2936
|
+
}
|
|
2937
|
+
const resolvedProfile = await resolvePresetProfile({
|
|
2938
|
+
presetFile: effectivePresetFile,
|
|
2939
|
+
presetProfile: effectivePresetProfile,
|
|
2940
|
+
presetProfileSourceName: effectivePresetProfileSourceName,
|
|
2941
|
+
commandPath,
|
|
2942
|
+
});
|
|
2943
|
+
return { cleanArgv, resolvedProfile };
|
|
2944
|
+
}
|
|
2945
|
+
async function runPresetStage(params) {
|
|
2946
|
+
const { controlTailArgv, chain, commandPath, presetFileFlag, presetProfileFlag, runtime, userCmds, userEnvs, getPresetConfig, assertPresetProfileSelectorValue, resolvePresetProfile, validatePresetOptionTokens, } = params;
|
|
2947
|
+
const { cleanArgv, resolvedProfile } = await resolvePresetStage({
|
|
2948
|
+
controlTailArgv,
|
|
2949
|
+
chain,
|
|
2950
|
+
commandPath,
|
|
2951
|
+
presetFileFlag,
|
|
2952
|
+
presetProfileFlag,
|
|
2953
|
+
getPresetConfig,
|
|
2954
|
+
assertPresetProfileSelectorValue,
|
|
2955
|
+
resolvePresetProfile,
|
|
2956
|
+
});
|
|
2957
|
+
const { presetArgv, presetEnvs } = await buildPresetProfileInputs({
|
|
2958
|
+
runtime,
|
|
2959
|
+
commandPath,
|
|
2960
|
+
resolvedProfile,
|
|
2961
|
+
validatePresetOptionTokens,
|
|
2962
|
+
});
|
|
2963
|
+
const { tailArgv, envs, segments, sources } = buildPresetSources({
|
|
2964
|
+
userCmds,
|
|
2965
|
+
userArgv: cleanArgv,
|
|
2966
|
+
userEnvs,
|
|
2967
|
+
presetArgv,
|
|
2968
|
+
presetEnvs,
|
|
2969
|
+
presetMeta: resolvedProfile?.issueMeta,
|
|
2970
|
+
presetResolvedEnvFile: resolvePresetEnvFilePath(resolvedProfile),
|
|
2971
|
+
});
|
|
2972
|
+
return { tailArgv, envs, segments, sources };
|
|
2973
|
+
}
|
|
2974
|
+
function resolvePresetEnvFilePath(resolvedProfile) {
|
|
2975
|
+
if (!resolvedProfile) {
|
|
2003
2976
|
return undefined;
|
|
2004
2977
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2978
|
+
return (resolvedProfile.variantEnvFileSource?.absolutePath ??
|
|
2979
|
+
resolvedProfile.profileEnvFileSource?.absolutePath);
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
function findSubcommandEntry(entries, token) {
|
|
2983
|
+
return entries.find(entry => entry.name === token || entry.aliases.includes(token));
|
|
2984
|
+
}
|
|
2985
|
+
function routeCommandChain(params) {
|
|
2986
|
+
const { root, argv, getSubcommandEntries } = params;
|
|
2987
|
+
const chain = [root];
|
|
2988
|
+
const cmds = [];
|
|
2989
|
+
let current = root;
|
|
2990
|
+
let index = 0;
|
|
2991
|
+
while (index < argv.length) {
|
|
2992
|
+
const token = argv[index];
|
|
2993
|
+
if (token.startsWith('-')) {
|
|
2994
|
+
break;
|
|
2995
|
+
}
|
|
2996
|
+
const entry = findSubcommandEntry(getSubcommandEntries(current), token);
|
|
2997
|
+
if (entry === undefined) {
|
|
2998
|
+
break;
|
|
2999
|
+
}
|
|
3000
|
+
current = entry.command;
|
|
3001
|
+
cmds.push(token);
|
|
3002
|
+
chain.push(current);
|
|
3003
|
+
index += 1;
|
|
2007
3004
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
3005
|
+
return {
|
|
3006
|
+
chain,
|
|
3007
|
+
cmds,
|
|
3008
|
+
remaining: argv.slice(index),
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
3011
|
+
function resolveHelpCommand(params) {
|
|
3012
|
+
const { leafCommand, helpTarget, getSubcommandEntries } = params;
|
|
3013
|
+
if (helpTarget === undefined) {
|
|
3014
|
+
return leafCommand;
|
|
2010
3015
|
}
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
const hasUserLogLevel = this.#hasUserOption('logLevel');
|
|
2015
|
-
const hasUserSilent = this.#hasUserOption('silent');
|
|
2016
|
-
const hasUserLogDate = this.#hasUserOption('logDate');
|
|
2017
|
-
const hasUserLogColorful = this.#hasUserOption('logColorful');
|
|
2018
|
-
if (this.#builtin.option.color && !hasUserColor) {
|
|
2019
|
-
optionMap.set('color', BUILTIN_COLOR_OPTION);
|
|
2020
|
-
}
|
|
2021
|
-
if (this.#builtin.option.logLevel && !hasUserLogLevel) {
|
|
2022
|
-
optionMap.set('logLevel', logLevelOption);
|
|
2023
|
-
}
|
|
2024
|
-
if (this.#builtin.option.silent && !hasUserSilent) {
|
|
2025
|
-
optionMap.set('silent', silentOption);
|
|
2026
|
-
}
|
|
2027
|
-
if (this.#builtin.option.logDate && !hasUserLogDate) {
|
|
2028
|
-
optionMap.set('logDate', logDateOption);
|
|
2029
|
-
}
|
|
2030
|
-
if (this.#builtin.option.logColorful && !hasUserLogColorful) {
|
|
2031
|
-
optionMap.set('logColorful', logColorfulOption);
|
|
2032
|
-
}
|
|
2033
|
-
for (const opt of this.#options) {
|
|
2034
|
-
optionMap.set(opt.long, opt);
|
|
2035
|
-
}
|
|
2036
|
-
return {
|
|
2037
|
-
mergedOptions: Array.from(optionMap.values()),
|
|
2038
|
-
};
|
|
3016
|
+
const entry = findSubcommandEntry(getSubcommandEntries(leafCommand), helpTarget);
|
|
3017
|
+
if (entry === undefined) {
|
|
3018
|
+
return leafCommand;
|
|
2039
3019
|
}
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
3020
|
+
return entry.command;
|
|
3021
|
+
}
|
|
3022
|
+
function findCommandByPath(params) {
|
|
3023
|
+
const { root, commandPath, getCommandName, getSubcommandEntries } = params;
|
|
3024
|
+
if (!commandPath) {
|
|
3025
|
+
return root;
|
|
3026
|
+
}
|
|
3027
|
+
const segments = commandPath.split(' ').filter(Boolean);
|
|
3028
|
+
if (segments.length === 0) {
|
|
3029
|
+
return root;
|
|
3030
|
+
}
|
|
3031
|
+
let startIndex = 0;
|
|
3032
|
+
const rootName = getCommandName(root);
|
|
3033
|
+
if (rootName !== undefined && segments[0] === rootName) {
|
|
3034
|
+
startIndex = 1;
|
|
3035
|
+
}
|
|
3036
|
+
let current = root;
|
|
3037
|
+
for (const segment of segments.slice(startIndex)) {
|
|
3038
|
+
if (current === undefined) {
|
|
3039
|
+
return undefined;
|
|
2044
3040
|
}
|
|
2045
|
-
|
|
3041
|
+
const entry = findSubcommandEntry(getSubcommandEntries(current), segment);
|
|
3042
|
+
current = entry?.command;
|
|
2046
3043
|
}
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
3044
|
+
return current;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
async function runStage(params) {
|
|
3048
|
+
const { leafCommand, actionParams, tailArgv, envs, hasAction, runAction, hasSubcommands, renderHelpForDisplay, print, getCommandPath, } = params;
|
|
3049
|
+
if (hasAction(leafCommand)) {
|
|
3050
|
+
await runAction(leafCommand, actionParams);
|
|
3051
|
+
return;
|
|
3052
|
+
}
|
|
3053
|
+
if (hasSubcommands(leafCommand)) {
|
|
3054
|
+
print(renderHelpForDisplay(leafCommand, {
|
|
3055
|
+
tailArgv,
|
|
3056
|
+
envs,
|
|
3057
|
+
}));
|
|
3058
|
+
return;
|
|
2051
3059
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
}
|
|
2068
|
-
shortMap.set(opt.short, opt.long);
|
|
2069
|
-
}
|
|
3060
|
+
throw new CommanderError('ConfigurationError', `no action defined for command "${getCommandPath(leafCommand)}"`, getCommandPath(leafCommand));
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
const LONG_OPTION_REGEX = /^--[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
3064
|
+
const NEGATIVE_OPTION_REGEX = /^--no-[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
3065
|
+
function kebabToCamelCase(str) {
|
|
3066
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
3067
|
+
}
|
|
3068
|
+
function tokenizeLongOption(segment, commandPath) {
|
|
3069
|
+
const arg = segment.value;
|
|
3070
|
+
const eqIdx = arg.indexOf('=');
|
|
3071
|
+
const namePart = eqIdx !== -1 ? arg.slice(0, eqIdx) : arg;
|
|
3072
|
+
const valuePart = eqIdx !== -1 ? arg.slice(eqIdx) : '';
|
|
3073
|
+
if (namePart.includes('_')) {
|
|
3074
|
+
throw new CommanderError('InvalidOptionFormat', `invalid option "${arg}": use '-' instead of '_'`, commandPath);
|
|
2070
3075
|
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
}
|
|
2075
|
-
if (opt.type === 'boolean' && opt.args !== 'none') {
|
|
2076
|
-
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, this.#getCommandPath());
|
|
2077
|
-
}
|
|
2078
|
-
if ((opt.type === 'string' || opt.type === 'number') && opt.args === 'none') {
|
|
2079
|
-
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required', 'optional', or 'variadic'`, this.#getCommandPath());
|
|
2080
|
-
}
|
|
2081
|
-
if (opt.type === 'number' && opt.args === 'optional') {
|
|
2082
|
-
throw new CommanderError('ConfigurationError', `number option "--${opt.long}" does not support args: 'optional'`, this.#getCommandPath());
|
|
2083
|
-
}
|
|
2084
|
-
if (opt.long.startsWith('no')) {
|
|
2085
|
-
throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, this.#getCommandPath());
|
|
2086
|
-
}
|
|
2087
|
-
if (!/^[a-z][a-zA-Z0-9]*$/.test(opt.long)) {
|
|
2088
|
-
throw new CommanderError('ConfigurationError', `option long name must be camelCase: "${opt.long}"`, this.#getCommandPath());
|
|
2089
|
-
}
|
|
2090
|
-
if (opt.short !== undefined && opt.short.length !== 1) {
|
|
2091
|
-
throw new CommanderError('ConfigurationError', `option short name must be a single character: "${opt.short}"`, this.#getCommandPath());
|
|
2092
|
-
}
|
|
2093
|
-
if (opt.required && opt.default !== undefined) {
|
|
2094
|
-
throw new CommanderError('ConfigurationError', `option "--${opt.long}" cannot be both required and have a default value`, this.#getCommandPath());
|
|
2095
|
-
}
|
|
2096
|
-
if (opt.type === 'boolean' && opt.required) {
|
|
2097
|
-
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" cannot be required`, this.#getCommandPath());
|
|
2098
|
-
}
|
|
2099
|
-
if (opt.required && opt.args !== 'required') {
|
|
2100
|
-
throw new CommanderError('ConfigurationError', `required option "--${opt.long}" must use args: 'required'`, this.#getCommandPath());
|
|
2101
|
-
}
|
|
3076
|
+
const lowerName = namePart.toLowerCase();
|
|
3077
|
+
if (lowerName === '--no' || lowerName === '--no-') {
|
|
3078
|
+
throw new CommanderError('InvalidNegativeOption', `invalid negative option syntax "${arg}"`, commandPath);
|
|
2102
3079
|
}
|
|
2103
|
-
|
|
2104
|
-
if (
|
|
2105
|
-
throw new CommanderError('
|
|
3080
|
+
if (lowerName.startsWith('--no-')) {
|
|
3081
|
+
if (valuePart !== '') {
|
|
3082
|
+
throw new CommanderError('NegativeOptionWithValue', `"${namePart}" does not accept a value`, commandPath);
|
|
2106
3083
|
}
|
|
2107
|
-
if (
|
|
2108
|
-
throw new CommanderError('
|
|
3084
|
+
if (!NEGATIVE_OPTION_REGEX.test(lowerName)) {
|
|
3085
|
+
throw new CommanderError('InvalidOptionFormat', `invalid option format "${arg}"`, commandPath);
|
|
2109
3086
|
}
|
|
3087
|
+
const camelName = kebabToCamelCase(lowerName.slice(5));
|
|
3088
|
+
return {
|
|
3089
|
+
original: arg,
|
|
3090
|
+
resolved: `--${camelName}=false`,
|
|
3091
|
+
name: camelName,
|
|
3092
|
+
type: 'long',
|
|
3093
|
+
source: segment.source,
|
|
3094
|
+
preset: segment.preset,
|
|
3095
|
+
};
|
|
2110
3096
|
}
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
3097
|
+
if (!LONG_OPTION_REGEX.test(lowerName)) {
|
|
3098
|
+
throw new CommanderError('InvalidOptionFormat', `invalid option format "${arg}"`, commandPath);
|
|
3099
|
+
}
|
|
3100
|
+
const camelName = kebabToCamelCase(lowerName.slice(2));
|
|
3101
|
+
return {
|
|
3102
|
+
original: arg,
|
|
3103
|
+
resolved: `--${camelName}${valuePart}`,
|
|
3104
|
+
name: camelName,
|
|
3105
|
+
type: 'long',
|
|
3106
|
+
source: segment.source,
|
|
3107
|
+
preset: segment.preset,
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
function tokenizeShortOptions(segment, commandPath) {
|
|
3111
|
+
const arg = segment.value;
|
|
3112
|
+
if (arg.includes('=')) {
|
|
3113
|
+
throw new CommanderError('UnsupportedShortSyntax', `"${arg}" is not supported. Use "-${arg[1]} ${arg.slice(3)}" instead`, commandPath);
|
|
3114
|
+
}
|
|
3115
|
+
return arg
|
|
3116
|
+
.slice(1)
|
|
3117
|
+
.split('')
|
|
3118
|
+
.map(flag => ({
|
|
3119
|
+
original: `-${flag}`,
|
|
3120
|
+
resolved: `-${flag}`,
|
|
3121
|
+
name: flag,
|
|
3122
|
+
type: 'short',
|
|
3123
|
+
source: segment.source,
|
|
3124
|
+
preset: segment.preset,
|
|
3125
|
+
}));
|
|
3126
|
+
}
|
|
3127
|
+
function tokenizeArgv(segments, commandPath) {
|
|
3128
|
+
const optionTokens = [];
|
|
3129
|
+
const restArgs = [];
|
|
3130
|
+
let passThrough = false;
|
|
3131
|
+
for (const segment of segments) {
|
|
3132
|
+
const arg = segment.value;
|
|
3133
|
+
if (arg === '--') {
|
|
3134
|
+
passThrough = true;
|
|
3135
|
+
continue;
|
|
2117
3136
|
}
|
|
2118
|
-
if (
|
|
2119
|
-
|
|
3137
|
+
if (passThrough) {
|
|
3138
|
+
restArgs.push(segment.value);
|
|
3139
|
+
continue;
|
|
2120
3140
|
}
|
|
2121
|
-
if (arg.
|
|
2122
|
-
|
|
3141
|
+
if (arg.startsWith('--')) {
|
|
3142
|
+
optionTokens.push(tokenizeLongOption(segment, commandPath));
|
|
3143
|
+
continue;
|
|
2123
3144
|
}
|
|
2124
|
-
if (arg.
|
|
2125
|
-
|
|
3145
|
+
if (arg.startsWith('-') && arg.length > 1) {
|
|
3146
|
+
optionTokens.push(...tokenizeShortOptions(segment, commandPath));
|
|
3147
|
+
continue;
|
|
2126
3148
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
3149
|
+
optionTokens.push({
|
|
3150
|
+
original: arg,
|
|
3151
|
+
resolved: arg,
|
|
3152
|
+
name: '',
|
|
3153
|
+
type: 'none',
|
|
3154
|
+
source: segment.source,
|
|
3155
|
+
preset: segment.preset,
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
return { optionTokens, restArgs };
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
class Command {
|
|
3162
|
+
#name;
|
|
3163
|
+
#desc;
|
|
3164
|
+
#version;
|
|
3165
|
+
#builtinConfig;
|
|
3166
|
+
#builtin;
|
|
3167
|
+
#presetConfig;
|
|
3168
|
+
#reporter;
|
|
3169
|
+
#runtime;
|
|
3170
|
+
#contextAdapter = new CommandContextAdapter();
|
|
3171
|
+
#diagnostics = new CommandDiagnosticsEngine();
|
|
3172
|
+
#helpRenderer = new CommandHelpRenderer();
|
|
3173
|
+
#optionParser = new CommandOptionParser();
|
|
3174
|
+
#presetProfileParser;
|
|
3175
|
+
#kernel;
|
|
3176
|
+
#parent;
|
|
3177
|
+
#options = [];
|
|
3178
|
+
#arguments = [];
|
|
3179
|
+
#examples = [];
|
|
3180
|
+
#subcommandsList = [];
|
|
3181
|
+
#subcommandsMap = new Map();
|
|
3182
|
+
#action = undefined;
|
|
3183
|
+
constructor(config) {
|
|
3184
|
+
this.#name = config.name ?? '';
|
|
3185
|
+
this.#desc = config.desc;
|
|
3186
|
+
this.#version = config.version;
|
|
3187
|
+
this.#builtinConfig = config.builtin;
|
|
3188
|
+
this.#builtin = normalizeBuiltinConfig(config.builtin);
|
|
3189
|
+
this.#presetConfig = config.preset;
|
|
3190
|
+
this.#reporter = config.reporter;
|
|
3191
|
+
this.#runtime = config.runtime ?? getDefaultCommandRuntime();
|
|
3192
|
+
this.#presetProfileParser = new CommandPresetProfileParser({
|
|
3193
|
+
resolvePresetFileAbsolutePath: (filepath, baseDirectory) => resolvePresetFileAbsolutePath({
|
|
3194
|
+
runtime: this.#runtime,
|
|
3195
|
+
filepath,
|
|
3196
|
+
baseDirectory,
|
|
3197
|
+
}),
|
|
3198
|
+
resolvePath: (...paths) => this.#runtime.resolve(...paths),
|
|
3199
|
+
readPresetFile: async (file, commandPath) => readPresetFile({ runtime: this.#runtime, file, commandPath }),
|
|
3200
|
+
});
|
|
3201
|
+
this.#kernel = new CommandKernel({
|
|
3202
|
+
port: {
|
|
3203
|
+
route: argv => this.#route(argv),
|
|
3204
|
+
createContext: params => this.#createContext(params),
|
|
3205
|
+
controlScan: (tailArgv, leafCommand) => this.#controlScan(tailArgv, leafCommand),
|
|
3206
|
+
controlRun: (leafCommand, controlScanResult) => this.#controlRun(leafCommand, controlScanResult),
|
|
3207
|
+
preset: async (tailArgv, ctx) => this.#preset(tailArgv, ctx),
|
|
3208
|
+
tokenize: (segments, commandPath) => tokenizeArgv(segments, commandPath),
|
|
3209
|
+
getCommandPath: command => command.#getCommandPath(),
|
|
3210
|
+
buildOptionPolicyMap: chain => this.#buildOptionPolicyMap(chain),
|
|
3211
|
+
resolve: (chain, optionTokens, optionPolicyMap) => this.#resolve(chain, optionTokens, optionPolicyMap),
|
|
3212
|
+
parse: (chain, resolveResult, optionPolicyMap, ctx, restArgs) => this.#parse(chain, resolveResult, optionPolicyMap, ctx, restArgs),
|
|
3213
|
+
run: async ({ leafCommand, parseResult, presetResult, ctx }) => runStage({
|
|
3214
|
+
leafCommand,
|
|
3215
|
+
actionParams: this.#contextAdapter.toActionParams(parseResult),
|
|
3216
|
+
tailArgv: presetResult.tailArgv,
|
|
3217
|
+
envs: ctx.envs,
|
|
3218
|
+
hasAction: command => command.#action !== undefined,
|
|
3219
|
+
runAction: async (command, actionParams) => runCommandAction({
|
|
3220
|
+
action: command.#action,
|
|
3221
|
+
actionParams,
|
|
3222
|
+
commandPath: command.#getCommandPath(),
|
|
3223
|
+
}),
|
|
3224
|
+
hasSubcommands: command => command.#subcommandsList.length > 0,
|
|
3225
|
+
renderHelpForDisplay: (command, options) => {
|
|
3226
|
+
const helpColor = command.#resolveHelpColorFromTailArgv(options.tailArgv, options.envs);
|
|
3227
|
+
return command.#formatHelpForDisplay({ color: helpColor });
|
|
3228
|
+
},
|
|
3229
|
+
print: content => {
|
|
3230
|
+
console.log(content);
|
|
3231
|
+
},
|
|
3232
|
+
getCommandPath: command => command.#getCommandPath(),
|
|
3233
|
+
}),
|
|
3234
|
+
issueScopeFromErrorKind: kind => errorKindToIssueScope(kind),
|
|
3235
|
+
},
|
|
3236
|
+
diagnostics: this.#diagnostics,
|
|
3237
|
+
contextAdapter: this.#contextAdapter,
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
get name() {
|
|
3241
|
+
return this.#name || undefined;
|
|
3242
|
+
}
|
|
3243
|
+
get description() {
|
|
3244
|
+
return this.#desc;
|
|
3245
|
+
}
|
|
3246
|
+
get version() {
|
|
3247
|
+
return this.#version;
|
|
3248
|
+
}
|
|
3249
|
+
get builtin() {
|
|
3250
|
+
return this.#builtinConfig;
|
|
3251
|
+
}
|
|
3252
|
+
get preset() {
|
|
3253
|
+
return this.#presetConfig === undefined ? undefined : { ...this.#presetConfig };
|
|
3254
|
+
}
|
|
3255
|
+
get parent() {
|
|
3256
|
+
return this.#parent;
|
|
3257
|
+
}
|
|
3258
|
+
get options() {
|
|
3259
|
+
return [...this.#options];
|
|
3260
|
+
}
|
|
3261
|
+
get arguments() {
|
|
3262
|
+
return [...this.#arguments];
|
|
3263
|
+
}
|
|
3264
|
+
get examples() {
|
|
3265
|
+
return this.#examples.map(example => ({ ...example }));
|
|
3266
|
+
}
|
|
3267
|
+
get subcommands() {
|
|
3268
|
+
return new Map(this.#subcommandsMap);
|
|
3269
|
+
}
|
|
3270
|
+
option(opt) {
|
|
3271
|
+
const commandPath = this.#getCommandPath();
|
|
3272
|
+
validateOptionConfig({ opt, commandPath });
|
|
3273
|
+
checkOptionUniqueness({ opt, options: this.#options, commandPath });
|
|
3274
|
+
this.#options.push(opt);
|
|
3275
|
+
return this;
|
|
3276
|
+
}
|
|
3277
|
+
argument(arg) {
|
|
3278
|
+
validateArgumentConfig({
|
|
3279
|
+
arg: arg,
|
|
3280
|
+
arguments_: this.#arguments,
|
|
3281
|
+
commandPath: this.#getCommandPath(),
|
|
3282
|
+
});
|
|
3283
|
+
this.#arguments.push(arg);
|
|
3284
|
+
return this;
|
|
3285
|
+
}
|
|
3286
|
+
action(fn) {
|
|
3287
|
+
this.#action = fn;
|
|
3288
|
+
return this;
|
|
3289
|
+
}
|
|
3290
|
+
example(title, usage, desc) {
|
|
3291
|
+
this.#examples.push(normalizeExample({
|
|
3292
|
+
example: { title, usage, desc },
|
|
3293
|
+
commandPath: this.#getCommandPath(),
|
|
3294
|
+
}));
|
|
3295
|
+
return this;
|
|
3296
|
+
}
|
|
3297
|
+
subcommand(name, cmd) {
|
|
3298
|
+
if (name === 'help') {
|
|
3299
|
+
throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name', this.#getCommandPath());
|
|
2134
3300
|
}
|
|
2135
|
-
if (
|
|
2136
|
-
this.#
|
|
3301
|
+
if (cmd.#parent && cmd.#parent !== this) {
|
|
3302
|
+
throw new CommanderError('ConfigurationError', `command "${cmd.#name}" already has a parent`, this.#getCommandPath());
|
|
2137
3303
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
}
|
|
3304
|
+
const occupied = this.#subcommandsMap.get(name);
|
|
3305
|
+
if (occupied && occupied !== cmd) {
|
|
3306
|
+
throw new CommanderError('ConfigurationError', `subcommand name/alias "${name}" conflicts with an existing command`, this.#getCommandPath());
|
|
2142
3307
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
if (
|
|
2146
|
-
|
|
3308
|
+
const existing = this.#subcommandsList.find(e => e.command === cmd);
|
|
3309
|
+
if (existing) {
|
|
3310
|
+
if (existing.name === name || existing.aliases.includes(name)) {
|
|
3311
|
+
return this;
|
|
2147
3312
|
}
|
|
3313
|
+
existing.aliases.push(name);
|
|
3314
|
+
this.#subcommandsMap.set(name, cmd);
|
|
2148
3315
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
3316
|
+
else {
|
|
3317
|
+
cmd.#name = name;
|
|
3318
|
+
cmd.#parent = this;
|
|
3319
|
+
this.#subcommandsList.push({ name, aliases: [], command: cmd });
|
|
3320
|
+
this.#subcommandsMap.set(name, cmd);
|
|
2154
3321
|
}
|
|
3322
|
+
return this;
|
|
2155
3323
|
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
3324
|
+
async run(params) {
|
|
3325
|
+
const outcome = await this.#execute(params, 'run');
|
|
3326
|
+
handleRunOutcome({
|
|
3327
|
+
outcome,
|
|
3328
|
+
argv: params.argv,
|
|
3329
|
+
envs: params.envs,
|
|
3330
|
+
route: argv => this.#route(argv),
|
|
3331
|
+
controlScan: (tailArgv, leafCommand) => this.#controlScan(tailArgv, leafCommand),
|
|
3332
|
+
findCommandByPath: commandPath => this.#findCommandByPath(commandPath),
|
|
3333
|
+
resolveHelpCommand: (leafCommand, helpTarget) => this.#resolveHelpCommand(leafCommand, helpTarget),
|
|
3334
|
+
resolveHelpColor: (command, tailArgv, envs) => command.#resolveHelpColorFromTailArgv(tailArgv, envs),
|
|
3335
|
+
formatHelpForDisplay: (command, color) => command.#formatHelpForDisplay({ color }),
|
|
3336
|
+
print: content => {
|
|
3337
|
+
console.log(content);
|
|
3338
|
+
},
|
|
3339
|
+
printError: content => {
|
|
3340
|
+
console.error(content);
|
|
3341
|
+
},
|
|
3342
|
+
exit: code => this.#exit(code),
|
|
3343
|
+
normalizeControlRunError: error => {
|
|
3344
|
+
const enrichedError = this.#diagnostics.withErrorIssue(error, {
|
|
3345
|
+
stage: 'control-run',
|
|
3346
|
+
scope: 'control',
|
|
3347
|
+
});
|
|
3348
|
+
return this.#diagnostics.normalizeCommanderError(enrichedError, {
|
|
3349
|
+
fallbackStage: 'control-run',
|
|
3350
|
+
fallbackScope: 'control',
|
|
3351
|
+
});
|
|
3352
|
+
},
|
|
3353
|
+
});
|
|
2166
3354
|
}
|
|
2167
|
-
#
|
|
2168
|
-
|
|
2169
|
-
const usage = example.usage.trim();
|
|
2170
|
-
const desc = example.desc.trim();
|
|
2171
|
-
if (!title) {
|
|
2172
|
-
throw new CommanderError('ConfigurationError', 'example title cannot be empty', this.#getCommandPath());
|
|
2173
|
-
}
|
|
2174
|
-
if (!usage) {
|
|
2175
|
-
throw new CommanderError('ConfigurationError', 'example usage cannot be empty', this.#getCommandPath());
|
|
2176
|
-
}
|
|
2177
|
-
if (!desc) {
|
|
2178
|
-
throw new CommanderError('ConfigurationError', 'example description cannot be empty', this.#getCommandPath());
|
|
2179
|
-
}
|
|
2180
|
-
return { title, usage, desc };
|
|
3355
|
+
#exit(code) {
|
|
3356
|
+
process.exit(code);
|
|
2181
3357
|
}
|
|
2182
|
-
async
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
}
|
|
2188
|
-
catch (err) {
|
|
2189
|
-
if (err instanceof Error) {
|
|
2190
|
-
console.error(`Error: ${err.message}`);
|
|
2191
|
-
}
|
|
2192
|
-
else {
|
|
2193
|
-
console.error('Error: action failed');
|
|
2194
|
-
}
|
|
2195
|
-
process.exit(1);
|
|
2196
|
-
}
|
|
3358
|
+
async parse(params) {
|
|
3359
|
+
const outcome = await this.#execute(params, 'parse');
|
|
3360
|
+
return unwrapParseOutcome({
|
|
3361
|
+
outcome,
|
|
3362
|
+
commandPath: this.#getCommandPath(),
|
|
3363
|
+
});
|
|
2197
3364
|
}
|
|
2198
|
-
#
|
|
2199
|
-
|
|
2200
|
-
let color = !isNoColorEnabled(envs);
|
|
2201
|
-
if (!colorOption || colorOption.type !== 'boolean' || colorOption.args !== 'none') {
|
|
2202
|
-
return color;
|
|
2203
|
-
}
|
|
2204
|
-
const separatorIndex = tailArgv.indexOf('--');
|
|
2205
|
-
const scanTokens = separatorIndex === -1 ? tailArgv : tailArgv.slice(0, separatorIndex);
|
|
2206
|
-
for (const token of scanTokens) {
|
|
2207
|
-
if (token === '--color') {
|
|
2208
|
-
color = true;
|
|
2209
|
-
continue;
|
|
2210
|
-
}
|
|
2211
|
-
if (token === '--no-color') {
|
|
2212
|
-
color = false;
|
|
2213
|
-
continue;
|
|
2214
|
-
}
|
|
2215
|
-
if (!token.startsWith('--color=')) {
|
|
2216
|
-
continue;
|
|
2217
|
-
}
|
|
2218
|
-
const value = token.slice('--color='.length);
|
|
2219
|
-
if (value === 'true') {
|
|
2220
|
-
color = true;
|
|
2221
|
-
}
|
|
2222
|
-
else if (value === 'false') {
|
|
2223
|
-
color = false;
|
|
2224
|
-
}
|
|
2225
|
-
else {
|
|
2226
|
-
throw new CommanderError('InvalidBooleanValue', `invalid value "${value}" for boolean option "--color". Use "true" or "false"`, this.#getCommandPath());
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
return color;
|
|
3365
|
+
async #execute(params, mode) {
|
|
3366
|
+
return this.#kernel.execute(params, mode);
|
|
2230
3367
|
}
|
|
2231
|
-
#
|
|
2232
|
-
return
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
3368
|
+
#controlRun(leafCommand, controlScanResult) {
|
|
3369
|
+
return runControl({
|
|
3370
|
+
leafCommand,
|
|
3371
|
+
controlScanResult,
|
|
3372
|
+
resolveHelpCommand: (leaf, helpTarget) => this.#resolveHelpCommand(leaf, helpTarget),
|
|
3373
|
+
getCommandPath: command => command.#getCommandPath(),
|
|
3374
|
+
getCommandVersion: command => command.#version,
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
3377
|
+
#findCommandByPath(commandPath) {
|
|
3378
|
+
return findCommandByPath({
|
|
3379
|
+
root: this,
|
|
3380
|
+
commandPath,
|
|
3381
|
+
getCommandName: command => command.#name || undefined,
|
|
3382
|
+
getSubcommandEntries: command => command.#subcommandsList,
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
formatHelp() {
|
|
3386
|
+
return this.#helpRenderer.formatHelp(this.#buildHelpData());
|
|
3387
|
+
}
|
|
3388
|
+
#formatHelpForDisplay(params = {}) {
|
|
3389
|
+
const { color = true } = params;
|
|
3390
|
+
return this.#helpRenderer.formatHelpForDisplay(this.#buildHelpData(), color);
|
|
3391
|
+
}
|
|
3392
|
+
#buildHelpData() {
|
|
3393
|
+
const subcommands = buildHelpSubcommands({
|
|
3394
|
+
entries: this.#subcommandsList,
|
|
3395
|
+
getDescription: command => command.#desc,
|
|
3396
|
+
});
|
|
3397
|
+
return this.#helpRenderer.buildHelpData({
|
|
3398
|
+
desc: this.#desc,
|
|
3399
|
+
commandPath: this.#getCommandPath(),
|
|
3400
|
+
arguments: this.#arguments,
|
|
3401
|
+
options: this.#resolveOptionPolicy().mergedOptions,
|
|
3402
|
+
supportsBuiltinVersion: this.#supportsBuiltinVersion(),
|
|
3403
|
+
subcommands,
|
|
3404
|
+
examples: this.#examples,
|
|
3405
|
+
builtinHelpOption: BUILTIN_HELP_OPTION,
|
|
3406
|
+
builtinVersionOption: BUILTIN_VERSION_OPTION,
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3409
|
+
getCompletionMeta() {
|
|
3410
|
+
return buildCompletionMeta({
|
|
3411
|
+
name: this.#name,
|
|
3412
|
+
desc: this.#desc,
|
|
3413
|
+
mergedOptions: this.#resolveOptionPolicy().mergedOptions,
|
|
3414
|
+
arguments_: this.#arguments,
|
|
3415
|
+
supportsBuiltinVersion: this.#supportsBuiltinVersion(),
|
|
3416
|
+
builtinHelpOption: BUILTIN_HELP_OPTION,
|
|
3417
|
+
builtinVersionOption: BUILTIN_VERSION_OPTION,
|
|
3418
|
+
subcommands: this.#subcommandsList,
|
|
3419
|
+
resolveSubcommandMeta: command => command.getCompletionMeta(),
|
|
3420
|
+
});
|
|
3421
|
+
}
|
|
3422
|
+
#route(argv) {
|
|
3423
|
+
return routeCommandChain({
|
|
3424
|
+
root: this,
|
|
3425
|
+
argv,
|
|
3426
|
+
getSubcommandEntries: command => command.#subcommandsList,
|
|
3427
|
+
});
|
|
3428
|
+
}
|
|
3429
|
+
#controlScan(tailArgv, leafCommand) {
|
|
3430
|
+
return scanControl({
|
|
3431
|
+
tailArgv,
|
|
3432
|
+
supportsBuiltinVersion: leafCommand.#supportsBuiltinVersion(),
|
|
3433
|
+
});
|
|
3434
|
+
}
|
|
3435
|
+
#createContext(params) {
|
|
3436
|
+
const { chain, cmds, envs, reporter: reporter$1 } = params;
|
|
3437
|
+
const leafCommand = chain[chain.length - 1];
|
|
3438
|
+
return createCommandContext({
|
|
3439
|
+
leafCommand,
|
|
3440
|
+
chain,
|
|
3441
|
+
cmds,
|
|
3442
|
+
envs,
|
|
3443
|
+
reporter: reporter$1 ?? this.#reporter ?? new reporter.Reporter(),
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
#resolveHelpCommand(leafCommand, helpTarget) {
|
|
3447
|
+
return resolveHelpCommand({
|
|
3448
|
+
leafCommand,
|
|
3449
|
+
helpTarget,
|
|
3450
|
+
getSubcommandEntries: command => command.#subcommandsList,
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
async #preset(controlTailArgv, ctx) {
|
|
3454
|
+
const commandPath = ctx.chain[ctx.chain.length - 1].#getCommandPath();
|
|
3455
|
+
return runPresetStage({
|
|
3456
|
+
controlTailArgv,
|
|
3457
|
+
chain: ctx.chain,
|
|
3458
|
+
commandPath,
|
|
3459
|
+
presetFileFlag: PRESET_FILE_FLAG,
|
|
3460
|
+
presetProfileFlag: PRESET_PROFILE_FLAG,
|
|
3461
|
+
runtime: this.#runtime,
|
|
3462
|
+
userCmds: ctx.sources.user.cmds,
|
|
3463
|
+
userEnvs: ctx.sources.user.envs,
|
|
3464
|
+
getPresetConfig: command => command.#presetConfig,
|
|
3465
|
+
assertPresetProfileSelectorValue: (value, sourceName, path) => this.#presetProfileParser.assertPresetProfileSelectorValue(value, sourceName, path),
|
|
3466
|
+
resolvePresetProfile: params => this.#presetProfileParser.resolvePresetProfile(params),
|
|
3467
|
+
validatePresetOptionTokens: (tokens, filepath, path) => this.#presetProfileParser.validatePresetOptionTokens(tokens, filepath, path),
|
|
3468
|
+
});
|
|
3469
|
+
}
|
|
3470
|
+
#resolve(chain, tokens, optionPolicyMap) {
|
|
3471
|
+
return resolveStage({
|
|
3472
|
+
chain,
|
|
3473
|
+
tokens,
|
|
3474
|
+
optionPolicyMap,
|
|
3475
|
+
mustGetOptionPolicy: (map, command) => this.#mustGetOptionPolicy(map, command),
|
|
3476
|
+
getLocalOptions: command => command.#options,
|
|
3477
|
+
getCommandPath: command => command.#getCommandPath(),
|
|
3478
|
+
withUnknownOptionIssue: (error, token) => this.#diagnostics.withErrorIssue(error, {
|
|
3479
|
+
stage: 'resolve',
|
|
3480
|
+
scope: 'option',
|
|
3481
|
+
token,
|
|
2236
3482
|
}),
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
3483
|
+
});
|
|
3484
|
+
}
|
|
3485
|
+
#parse(chain, resolveResult, optionPolicyMap, ctx, restArgs) {
|
|
3486
|
+
return parseStage({
|
|
3487
|
+
chain,
|
|
3488
|
+
resolveResult,
|
|
3489
|
+
optionPolicyMap,
|
|
3490
|
+
ctx,
|
|
3491
|
+
restArgs,
|
|
3492
|
+
rootCommandPath: this.#getCommandPath(),
|
|
3493
|
+
mustGetOptionPolicy: (map, command) => this.#mustGetOptionPolicy(map, command),
|
|
3494
|
+
parseOptions: ({ command, tokens, allOptions, envs, commandPath }) => command.#optionParser.parseOptions({
|
|
3495
|
+
tokens,
|
|
3496
|
+
allOptions,
|
|
3497
|
+
envs,
|
|
3498
|
+
commandPath,
|
|
2241
3499
|
}),
|
|
3500
|
+
applyBuiltinDevmodeLogLevel: ({ command, opts, explicitOptionLongs }) => {
|
|
3501
|
+
command.#optionParser.applyBuiltinDevmodeLogLevel({
|
|
3502
|
+
opts,
|
|
3503
|
+
explicitOptionLongs,
|
|
3504
|
+
builtinOption: command.#builtin.option,
|
|
3505
|
+
hasUserOption: long => command.#hasUserOption(long),
|
|
3506
|
+
});
|
|
3507
|
+
},
|
|
3508
|
+
resolveBuiltinParsedOptions: ({ command, opts }) => command.#optionParser.resolveBuiltinParsedOptions({
|
|
3509
|
+
opts,
|
|
3510
|
+
builtinOption: command.#builtin.option,
|
|
3511
|
+
hasUserOption: long => command.#hasUserOption(long),
|
|
3512
|
+
}),
|
|
3513
|
+
applyOptionCallbacks: ({ opts, allOptions, ctx }) => {
|
|
3514
|
+
for (const option of allOptions) {
|
|
3515
|
+
if (option.apply && opts[option.long] !== undefined) {
|
|
3516
|
+
option.apply(opts[option.long], ctx);
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
},
|
|
3520
|
+
getLocalOptions: command => command.#options,
|
|
3521
|
+
getSubcommands: command => command.#subcommandsList,
|
|
3522
|
+
getArguments: command => command.#arguments,
|
|
3523
|
+
getCommandPath: command => command.#getCommandPath(),
|
|
3524
|
+
withUnknownSubcommandIssue: error => this.#diagnostics.withErrorIssue(error, {
|
|
3525
|
+
stage: 'parse',
|
|
3526
|
+
scope: 'command',
|
|
3527
|
+
source: { primary: 'user' },
|
|
3528
|
+
}),
|
|
3529
|
+
freezeInputSources: sources => this.#freezeInputSources(sources),
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
#hasUserOption(long) {
|
|
3533
|
+
return this.#options.some(option => option.long === long);
|
|
3534
|
+
}
|
|
3535
|
+
#supportsBuiltinVersion() {
|
|
3536
|
+
return this.#version !== undefined && this.#builtin.option.version;
|
|
3537
|
+
}
|
|
3538
|
+
#resolveOptionPolicy() {
|
|
3539
|
+
return resolveOptionPolicy({
|
|
3540
|
+
builtinOption: this.#builtin.option,
|
|
3541
|
+
localOptions: this.#options,
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3544
|
+
#buildOptionPolicyMap(chain) {
|
|
3545
|
+
return buildOptionPolicyMap({
|
|
3546
|
+
chain,
|
|
3547
|
+
resolveOptionPolicy: command => command.#resolveOptionPolicy(),
|
|
2242
3548
|
});
|
|
2243
3549
|
}
|
|
3550
|
+
#mustGetOptionPolicy(optionPolicyMap, cmd) {
|
|
3551
|
+
return mustGetOptionPolicy({
|
|
3552
|
+
optionPolicyMap,
|
|
3553
|
+
command: cmd,
|
|
3554
|
+
resolveOptionPolicy: command => command.#resolveOptionPolicy(),
|
|
3555
|
+
});
|
|
3556
|
+
}
|
|
3557
|
+
#resolveHelpColorFromTailArgv(tailArgv, envs, policy = this.#resolveOptionPolicy()) {
|
|
3558
|
+
return this.#helpRenderer.resolveHelpColorFromTailArgv({
|
|
3559
|
+
tailArgv,
|
|
3560
|
+
envs,
|
|
3561
|
+
options: policy.mergedOptions,
|
|
3562
|
+
commandPath: this.#getCommandPath(),
|
|
3563
|
+
});
|
|
3564
|
+
}
|
|
3565
|
+
#freezeInputSources(sources) {
|
|
3566
|
+
return freezeInputSources(sources);
|
|
3567
|
+
}
|
|
2244
3568
|
#getCommandPath() {
|
|
2245
3569
|
const parts = [];
|
|
2246
3570
|
let current = this;
|
|
@@ -3123,6 +4447,5 @@ exports.isIpv4 = isIpv4;
|
|
|
3123
4447
|
exports.isIpv6 = isIpv6;
|
|
3124
4448
|
exports.logColorfulOption = logColorfulOption;
|
|
3125
4449
|
exports.logDateOption = logDateOption;
|
|
3126
|
-
exports.logLevelOption = logLevelOption;
|
|
3127
4450
|
exports.setDefaultCommandRuntime = setDefaultCommandRuntime;
|
|
3128
4451
|
exports.silentOption = silentOption;
|