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