@oclif/core 4.8.4 → 4.10.0

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/lib/command.js CHANGED
@@ -295,7 +295,7 @@ class Command {
295
295
  }
296
296
  warnIfCommandDeprecated() {
297
297
  const [id] = (0, util_1.normalizeArgv)(this.config);
298
- if (this.ctor.deprecateAliases && this.ctor.aliases.includes(id)) {
298
+ if (this.ctor.deprecateAliases && [...this.ctor.aliases, ...this.ctor.hiddenAliases].includes(id)) {
299
299
  const cmdName = (0, ids_1.toConfiguredId)(this.ctor.id, this.config);
300
300
  const aliasName = (0, ids_1.toConfiguredId)(id, this.config);
301
301
  this.warn((0, util_1.formatCommandDeprecationWarning)(aliasName, { to: cmdName }));
@@ -57,8 +57,9 @@ class CommandHelp extends formatter_1.HelpFormatter {
57
57
  if (args.filter((a) => a.description).length === 0)
58
58
  return;
59
59
  return args.map((a) => {
60
- // Add ellipsis to indicate that the argument takes multiple values if strict is false
61
- let name = this.command.strict === false ? `${a.name.toUpperCase()}...` : a.name.toUpperCase();
60
+ // Add ellipsis for variadic args, or for all args when strict is false (backward compat)
61
+ const suffix = a.multiple ? '...' : this.command.strict === false ? '...' : '';
62
+ let name = `${a.name.toUpperCase()}${suffix}`;
62
63
  name = a.required ? `${name}` : `[${name}]`;
63
64
  let description = a.description || '';
64
65
  if (a.default)
@@ -100,11 +100,12 @@ class DocOpts {
100
100
  toString() {
101
101
  const opts = ['<%= command.id %>'];
102
102
  if (this.cmd.args) {
103
- // If strict is false, add ellipsis to indicate that the argument takes multiple values
104
- const suffix = this.cmd.strict === false ? '...' : '';
105
103
  const a = Object.values((0, ensure_arg_object_1.ensureArgObject)(this.cmd.args))
106
104
  .filter((arg) => !arg.hidden)
107
- .map((arg) => arg.required ? `${arg.name.toUpperCase()}${suffix}` : `[${arg.name.toUpperCase()}${suffix}]`) || [];
105
+ .map((arg) => {
106
+ const suffix = arg.multiple ? '...' : this.cmd.strict === false ? '...' : '';
107
+ return arg.required ? `${arg.name.toUpperCase()}${suffix}` : `[${arg.name.toUpperCase()}${suffix}]`;
108
+ }) || [];
108
109
  opts.push(...a);
109
110
  }
110
111
  try {
package/lib/help/index.js CHANGED
@@ -174,8 +174,8 @@ class Help extends HelpBase {
174
174
  ? `${(0, util_2.formatCommandDeprecationWarning)((0, ids_1.toConfiguredId)(name, this.config), command.deprecationOptions)}\n`
175
175
  : `This command is in ${state}.\n`);
176
176
  }
177
- if (command.deprecateAliases && command.aliases.includes(name)) {
178
- const actualCmd = this.config.commands.find((c) => c.aliases.includes(name));
177
+ if (command.deprecateAliases && [...(command.aliases ?? []), ...(command.hiddenAliases ?? [])].includes(name)) {
178
+ const actualCmd = this.config.commands.find((c) => [...(c.aliases ?? []), ...(c.hiddenAliases ?? [])].includes(name));
179
179
  const actualCmdName = actualCmd ? (0, ids_1.toConfiguredId)(actualCmd.id, this.config) : '';
180
180
  const opts = { ...command.deprecationOptions, ...(actualCmd ? { to: actualCmdName } : {}) };
181
181
  this.log(`${(0, util_2.formatCommandDeprecationWarning)((0, ids_1.toConfiguredId)(name, this.config), opts)}\n`);
@@ -192,6 +192,11 @@ export type ArgProps = {
192
192
  * If true, the flag will be required.
193
193
  */
194
194
  required?: boolean;
195
+ /**
196
+ * If true, the arg accepts multiple values. Only one arg per command can have multiple: true.
197
+ * All args after a variadic arg must be required.
198
+ */
199
+ multiple?: boolean;
195
200
  options?: string[];
196
201
  ignoreStdin?: boolean;
197
202
  /**
@@ -245,6 +250,20 @@ export type Arg<T, P = CustomOptions> = ArgProps & {
245
250
  parse: ArgParser<T, P>;
246
251
  };
247
252
  export type ArgDefinition<T, P = CustomOptions> = {
253
+ (options: P & {
254
+ multiple: true;
255
+ } & ({
256
+ required: true;
257
+ } | {
258
+ default: ArgDefault<T[]>;
259
+ }) & Omit<Partial<Arg<T, P>>, 'default'> & {
260
+ default?: ArgDefault<T[] | undefined>;
261
+ }): Arg<T[], P>;
262
+ (options: P & {
263
+ multiple: true;
264
+ } & Omit<Partial<Arg<T, P>>, 'default'> & {
265
+ default?: ArgDefault<T[] | undefined>;
266
+ }): Arg<T[] | undefined, P>;
248
267
  (options: P & ({
249
268
  required: true;
250
269
  } | {
@@ -16,8 +16,9 @@ export declare class CLIParseError extends CLIError {
16
16
  }
17
17
  export declare class InvalidArgsSpecError extends CLIParseError {
18
18
  args: ArgInput;
19
- constructor({ args, exit, parse }: CLIParseErrorOptions & {
19
+ constructor({ args, exit, parse, reason }: CLIParseErrorOptions & {
20
20
  args: ArgInput;
21
+ reason?: string;
21
22
  });
22
23
  }
23
24
  export declare class RequiredArgsError extends CLIParseError {
@@ -23,8 +23,11 @@ class CLIParseError extends errors_1.CLIError {
23
23
  exports.CLIParseError = CLIParseError;
24
24
  class InvalidArgsSpecError extends CLIParseError {
25
25
  args;
26
- constructor({ args, exit, parse }) {
26
+ constructor({ args, exit, parse, reason }) {
27
27
  let message = 'Invalid argument spec';
28
+ if (reason) {
29
+ message += `: ${reason}`;
30
+ }
28
31
  const namedArgs = Object.values(args).filter((a) => a.name);
29
32
  if (namedArgs.length > 0) {
30
33
  const list = (0, list_1.default)(namedArgs.map((a) => [`${a.name} (${a.required ? 'required' : 'optional'})`, a.description]));
@@ -201,43 +201,93 @@ class Parser {
201
201
  const tokens = this._argTokens;
202
202
  let stdinRead = false;
203
203
  const ctx = this.context;
204
- for (const [name, arg] of Object.entries(this.input.args)) {
205
- const token = tokens.find((t) => t.arg === name);
206
- ctx.token = token;
207
- if (token) {
208
- if (arg.options && !arg.options.includes(token.input)) {
209
- throw new errors_1.ArgInvalidOptionError(arg, token.input);
210
- }
211
- const parsed = await arg.parse(token.input, ctx, arg);
212
- argv.push(parsed);
213
- args[token.arg] = parsed;
204
+ const parseArgInput = async (name, arg, input) => {
205
+ if (arg.options && !arg.options.includes(input)) {
206
+ throw new errors_1.ArgInvalidOptionError(arg, input);
214
207
  }
215
- else if (!arg.ignoreStdin && !stdinRead) {
216
- let stdin = await (0, exports.readStdin)();
217
- if (stdin) {
218
- stdin = stdin.trim();
219
- const parsed = await arg.parse(stdin, ctx, arg);
220
- argv.push(parsed);
221
- args[name] = parsed;
222
- }
223
- stdinRead = true;
208
+ ctx.token = { arg: name, input, type: 'arg' };
209
+ const parsed = await arg.parse(input, ctx, arg);
210
+ argv.push(parsed);
211
+ args[name] = parsed;
212
+ };
213
+ const applyDefault = async (name, arg) => {
214
+ if (args[name] !== undefined)
215
+ return;
216
+ if (!arg.default && arg.default !== false)
217
+ return;
218
+ const value = typeof arg.default === 'function' ? await arg.default() : arg.default;
219
+ if (Array.isArray(value)) {
220
+ for (const v of value)
221
+ argv.push(v);
222
+ }
223
+ else {
224
+ argv.push(value);
224
225
  }
225
- if (!args[name] && (arg.default || arg.default === false)) {
226
- if (typeof arg.default === 'function') {
227
- const f = await arg.default();
228
- argv.push(f);
229
- args[name] = f;
226
+ args[name] = value;
227
+ };
228
+ const tryStdin = async (name, arg) => {
229
+ if (arg.ignoreStdin || stdinRead)
230
+ return;
231
+ let stdin = await (0, exports.readStdin)();
232
+ stdinRead = true;
233
+ if (!stdin)
234
+ return;
235
+ stdin = stdin.trim();
236
+ await parseArgInput(name, arg, stdin);
237
+ };
238
+ const argEntries = Object.entries(this.input.args);
239
+ const variadicIndex = argEntries.findIndex(([, arg]) => arg.multiple);
240
+ if (variadicIndex === -1) {
241
+ for (const [name, arg] of argEntries) {
242
+ const token = tokens.find((t) => t.arg === name);
243
+ ctx.token = token;
244
+ await (token ? parseArgInput(name, arg, token.input) : tryStdin(name, arg));
245
+ await applyDefault(name, arg);
246
+ }
247
+ // Append unmatched tokens to argv (for strict: false)
248
+ for (const token of tokens) {
249
+ if (args[token.arg] !== undefined)
250
+ continue;
251
+ argv.push(token.input);
252
+ }
253
+ }
254
+ else {
255
+ const tokenInputs = tokens.map((t) => t.input);
256
+ // Consume pre-variadic args from the front
257
+ for (let i = 0; i < variadicIndex; i++) {
258
+ const [name, arg] = argEntries[i];
259
+ const input = tokenInputs.shift();
260
+ await (input === undefined ? tryStdin(name, arg) : parseArgInput(name, arg, input));
261
+ await applyDefault(name, arg);
262
+ }
263
+ // Consume post-variadic args from the back
264
+ const postVariadicParsing = [];
265
+ for (let i = argEntries.length - 1; i > variadicIndex; i--) {
266
+ const [name, arg] = argEntries[i];
267
+ const input = tokenInputs.pop();
268
+ if (input !== undefined) {
269
+ postVariadicParsing.unshift({ arg, input, name });
230
270
  }
231
- else {
232
- argv.push(arg.default);
233
- args[name] = arg.default;
271
+ }
272
+ // Everything remaining goes to the variadic arg
273
+ const [variadicName, variadicArg] = argEntries[variadicIndex];
274
+ if (tokenInputs.length > 0) {
275
+ const parsedValues = [];
276
+ for (const input of tokenInputs) {
277
+ await parseArgInput(variadicName, variadicArg, input);
278
+ parsedValues.push(args[variadicName]);
234
279
  }
280
+ args[variadicName] = parsedValues;
281
+ }
282
+ for (const { arg, input, name } of postVariadicParsing) {
283
+ await parseArgInput(name, arg, input);
284
+ }
285
+ // Apply defaults for any args that didn't receive values
286
+ await applyDefault(variadicName, variadicArg);
287
+ for (let i = variadicIndex + 1; i < argEntries.length; i++) {
288
+ const [name, arg] = argEntries[i];
289
+ await applyDefault(name, arg);
235
290
  }
236
- }
237
- for (const token of tokens) {
238
- if (args[token.arg] !== undefined)
239
- continue;
240
- argv.push(token.input);
241
291
  }
242
292
  return { args, argv };
243
293
  }
@@ -6,6 +6,41 @@ const errors_1 = require("./errors");
6
6
  async function validate(parse) {
7
7
  let cachedResolvedFlags;
8
8
  function validateArgs() {
9
+ // Validate variadic arg constraints (definition-time checks)
10
+ const argEntries = Object.entries(parse.input.args);
11
+ const variadicIndex = argEntries.findIndex(([, arg]) => arg.multiple);
12
+ if (variadicIndex !== -1) {
13
+ // Only one variadic arg is allowed
14
+ const secondVariadic = argEntries.findIndex(([, arg], i) => i > variadicIndex && arg.multiple);
15
+ if (secondVariadic !== -1) {
16
+ throw new errors_1.InvalidArgsSpecError({
17
+ args: parse.input.args,
18
+ parse,
19
+ reason: 'only one variadic arg (multiple: true) is allowed',
20
+ });
21
+ }
22
+ // All args before a variadic arg must be required (otherwise assignment is ambiguous)
23
+ for (let i = 0; i < variadicIndex; i++) {
24
+ if (!argEntries[i][1].required) {
25
+ throw new errors_1.InvalidArgsSpecError({
26
+ args: parse.input.args,
27
+ parse,
28
+ reason: `args before a variadic arg must be required, but "${argEntries[i][0]}" is optional`,
29
+ });
30
+ }
31
+ }
32
+ // All args after a variadic arg must be required (otherwise assignment is ambiguous)
33
+ for (let i = variadicIndex + 1; i < argEntries.length; i++) {
34
+ if (!argEntries[i][1].required) {
35
+ throw new errors_1.InvalidArgsSpecError({
36
+ args: parse.input.args,
37
+ parse,
38
+ reason: `args after a variadic arg must be required, but "${argEntries[i][0]}" is optional`,
39
+ });
40
+ }
41
+ }
42
+ }
43
+ const variadicArgFound = variadicIndex !== -1;
9
44
  if (parse.output.nonExistentFlags?.length > 0) {
10
45
  throw new errors_1.NonExistentFlagsError({
11
46
  flags: parse.output.nonExistentFlags,
@@ -13,7 +48,8 @@ async function validate(parse) {
13
48
  });
14
49
  }
15
50
  const maxArgs = Object.keys(parse.input.args).length;
16
- if (parse.input.strict && parse.output.argv.length > maxArgs) {
51
+ const hasVariadicArg = Object.values(parse.input.args).some((arg) => arg.multiple);
52
+ if (parse.input.strict && !hasVariadicArg && parse.output.argv.length > maxArgs) {
17
53
  const extras = parse.output.argv.slice(maxArgs);
18
54
  throw new errors_1.UnexpectedArgsError({
19
55
  args: extras,
@@ -26,9 +62,11 @@ async function validate(parse) {
26
62
  if (!arg.required) {
27
63
  hasOptional = true;
28
64
  }
29
- else if (hasOptional) {
65
+ else if (hasOptional && !variadicArgFound) {
30
66
  // (required arg) check whether an optional has occurred before
31
67
  // optionals should follow required, not before
68
+ // Skip this check when a variadic arg is present, since the variadic
69
+ // validation above already enforces that args after a variadic are required
32
70
  throw new errors_1.InvalidArgsSpecError({
33
71
  args: parse.input.args,
34
72
  parse,
@@ -60,6 +60,7 @@ async function cacheArgs(cmdArgs, respectNoCacheDefault) {
60
60
  default: await (0, cache_default_value_1.cacheDefaultValue)(arg, respectNoCacheDefault),
61
61
  description: arg.description,
62
62
  hidden: arg.hidden,
63
+ multiple: arg.multiple,
63
64
  name,
64
65
  noCacheDefault: arg.noCacheDefault,
65
66
  options: arg.options,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oclif/core",
3
3
  "description": "base library for oclif CLIs",
4
- "version": "4.8.4",
4
+ "version": "4.10.0",
5
5
  "author": "Salesforce",
6
6
  "bugs": "https://github.com/oclif/core/issues",
7
7
  "dependencies": {