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