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