@oclif/core 2.10.0 → 2.11.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
@@ -112,7 +112,7 @@ class Command {
112
112
  let result;
113
113
  try {
114
114
  // remove redirected env var to allow subsessions to run autoupdated client
115
- delete process.env[this.config.scopedEnvVarKey('REDIRECTED')];
115
+ this.config.scopedEnvVarKeys('REDIRECTED').map(key => delete process.env[key]);
116
116
  await this.init();
117
117
  result = await this.run();
118
118
  }
@@ -50,7 +50,18 @@ export declare class Config implements IConfig {
50
50
  runCommand<T = unknown>(id: string, argv?: string[], cachedCommand?: Command.Loadable | null): Promise<T>;
51
51
  scopedEnvVar(k: string): string | undefined;
52
52
  scopedEnvVarTrue(k: string): boolean;
53
+ /**
54
+ * this DOES NOT account for bin aliases, use scopedEnvVarKeys instead which will account for bin aliases
55
+ * @param {string} k, the unscoped key you want to get the value for
56
+ * @returns {string} returns the env var key
57
+ */
53
58
  scopedEnvVarKey(k: string): string;
59
+ /**
60
+ * gets the scoped env var keys for a given key, including bin aliases
61
+ * @param {string} k, the env key e.g. 'debug'
62
+ * @returns {string[]} e.g. ['SF_DEBUG', 'SFDX_DEBUG']
63
+ */
64
+ scopedEnvVarKeys(k: string): string[];
54
65
  findCommand(id: string, opts: {
55
66
  must: true;
56
67
  }): Command.Loadable;
@@ -333,18 +333,31 @@ class Config {
333
333
  return result;
334
334
  }
335
335
  scopedEnvVar(k) {
336
- return process.env[this.scopedEnvVarKey(k)];
336
+ return process.env[this.scopedEnvVarKeys(k).find(k => process.env[k])];
337
337
  }
338
338
  scopedEnvVarTrue(k) {
339
- const v = process.env[this.scopedEnvVarKey(k)];
339
+ const v = process.env[this.scopedEnvVarKeys(k).find(k => process.env[k])];
340
340
  return v === '1' || v === 'true';
341
341
  }
342
+ /**
343
+ * this DOES NOT account for bin aliases, use scopedEnvVarKeys instead which will account for bin aliases
344
+ * @param {string} k, the unscoped key you want to get the value for
345
+ * @returns {string} returns the env var key
346
+ */
342
347
  scopedEnvVarKey(k) {
343
348
  return [this.bin, k]
344
349
  .map(p => p.replace(/@/g, '').replace(/[/-]/g, '_'))
345
350
  .join('_')
346
351
  .toUpperCase();
347
352
  }
353
+ /**
354
+ * gets the scoped env var keys for a given key, including bin aliases
355
+ * @param {string} k, the env key e.g. 'debug'
356
+ * @returns {string[]} e.g. ['SF_DEBUG', 'SFDX_DEBUG']
357
+ */
358
+ scopedEnvVarKeys(k) {
359
+ return [this.bin, ...this.binAliases ?? []].map(alias => [alias.replace(/@/g, '').replace(/[/-]/g, '_'), k].join('_').toUpperCase());
360
+ }
348
361
  findCommand(id, opts = {}) {
349
362
  const lookupId = this.getCmdLookupId(id);
350
363
  const command = this._commands.get(lookupId);
@@ -130,6 +130,7 @@ export interface Config {
130
130
  findMatches(id: string, argv: string[]): Command.Loadable[];
131
131
  scopedEnvVar(key: string): string | undefined;
132
132
  scopedEnvVarKey(key: string): string;
133
+ scopedEnvVarKeys(key: string): string[];
133
134
  scopedEnvVarTrue(key: string): boolean;
134
135
  s3Url(key: string): string;
135
136
  s3Key(type: 'versioned' | 'unversioned', ext: '.tar.gz' | '.tar.xz', options?: Config.s3Key.Options): string;
@@ -284,7 +284,7 @@ export type BooleanFlag<T> = FlagProps & BooleanFlagProps & {
284
284
  * specifying a default of false is the same as not specifying a default
285
285
  */
286
286
  default?: FlagDefault<boolean>;
287
- parse: (input: boolean, context: Command, opts: FlagProps & BooleanFlagProps) => Promise<T>;
287
+ parse: (input: boolean, context: FlagParserContext, opts: FlagProps & BooleanFlagProps) => Promise<T>;
288
288
  };
289
289
  export type OptionFlagDefaults<T, P = CustomOptions, M = false> = FlagProps & OptionFlagProps & {
290
290
  parse: FlagParser<T, string, P>;
@@ -327,7 +327,7 @@ export type Input<TFlags extends FlagOutput, BFlags extends FlagOutput, AFlags e
327
327
  baseFlags?: FlagInput<BFlags>;
328
328
  args?: ArgInput<AFlags>;
329
329
  strict?: boolean;
330
- context?: Command;
330
+ context?: ParserContext;
331
331
  '--'?: boolean;
332
332
  };
333
333
  export type ParserInput = {
@@ -335,9 +335,12 @@ export type ParserInput = {
335
335
  flags: FlagInput<any>;
336
336
  args: ArgInput<any>;
337
337
  strict: boolean;
338
- context: Command | undefined;
338
+ context: ParserContext | undefined;
339
339
  '--'?: boolean;
340
340
  };
341
+ export type ParserContext = Command & {
342
+ token?: FlagToken | ArgToken;
343
+ };
341
344
  export type CompletionContext = {
342
345
  args?: {
343
346
  [name: string]: string;
@@ -13,16 +13,41 @@ try {
13
13
  catch {
14
14
  debug = () => { };
15
15
  }
16
- const readStdin = async () => {
17
- const { stdin, stdout } = process;
18
- debug('stdin.isTTY', stdin.isTTY);
16
+ /**
17
+ * Support reading from stdin in Node 14 and older.
18
+ *
19
+ * This generally works for Node 14 and older EXCEPT when it's being
20
+ * run from another process, in which case it will hang indefinitely. Because
21
+ * of that issue we updated this to use AbortController but since AbortController
22
+ * is only available in Node 16 and newer, we have to keep this legacy version.
23
+ *
24
+ * See these issues for more details on the hanging indefinitely bug:
25
+ * https://github.com/oclif/core/issues/330
26
+ * https://github.com/oclif/core/pull/363
27
+ *
28
+ * @returns Promise<string | null>
29
+ */
30
+ const readStdinLegacy = async () => {
31
+ const { stdin } = process;
32
+ let result;
19
33
  if (stdin.isTTY)
20
34
  return null;
35
+ result = '';
36
+ stdin.setEncoding('utf8');
37
+ for await (const chunk of stdin) {
38
+ result += chunk;
39
+ }
40
+ return result;
41
+ };
42
+ const readStdinWithTimeout = async () => {
43
+ const { stdin, stdout } = process;
21
44
  // process.stdin.isTTY is true whenever it's running in a terminal.
22
45
  // process.stdin.isTTY is undefined when it's running in a pipe, e.g. echo 'foo' | my-cli command
23
46
  // process.stdin.isTTY is undefined when it's running in a spawned process, even if there's no pipe.
24
47
  // This means that reading from stdin could hang indefinitely while waiting for a non-existent pipe.
25
48
  // Because of this, we have to set a timeout to prevent the process from hanging.
49
+ if (stdin.isTTY)
50
+ return null;
26
51
  return new Promise(resolve => {
27
52
  let result = '';
28
53
  const ac = new AbortController();
@@ -49,6 +74,13 @@ const readStdin = async () => {
49
74
  }, { once: true });
50
75
  });
51
76
  };
77
+ const readStdin = async () => {
78
+ const { stdin, version } = process;
79
+ debug('stdin.isTTY', stdin.isTTY);
80
+ const nodeMajorVersion = Number(version.split('.')[0].replace(/^v/, ''));
81
+ debug('node version', nodeMajorVersion);
82
+ return nodeMajorVersion >= 14 ? readStdinLegacy() : readStdinWithTimeout();
83
+ };
52
84
  function isNegativeNumber(input) {
53
85
  return /^-\d/g.test(input);
54
86
  }
@@ -56,7 +88,7 @@ class Parser {
56
88
  constructor(input) {
57
89
  this.input = input;
58
90
  this.raw = [];
59
- this.context = input.context || {};
91
+ this.context = input.context ?? {};
60
92
  this.argv = [...input.argv];
61
93
  this._setNames();
62
94
  this.booleanFlags = (0, util_1.pickBy)(input.flags, f => f.type === 'boolean');
@@ -180,14 +212,23 @@ class Parser {
180
212
  throw new errors_1.FlagInvalidOptionError(flag, input);
181
213
  return input;
182
214
  };
183
- const parseFlagOrThrowError = async (input, flag, token, context = {}) => {
215
+ const parseFlagOrThrowError = async (input, flag, context, token) => {
184
216
  if (!flag.parse)
185
217
  return input;
218
+ const ctx = {
219
+ ...context,
220
+ token,
221
+ error: context?.error,
222
+ exit: context?.exit,
223
+ log: context?.log,
224
+ logToStderr: context?.logToStderr,
225
+ warn: context?.warn,
226
+ };
186
227
  try {
187
228
  if (flag.type === 'boolean') {
188
- return await flag.parse(input, { ...context, token }, flag);
229
+ return await flag.parse(input, ctx, flag);
189
230
  }
190
- return await flag.parse(input, { ...context, token }, flag);
231
+ return await flag.parse(input, ctx, flag);
191
232
  }
192
233
  catch (error) {
193
234
  error.message = `Parsing --${flag.name} \n\t${error.message}\nSee more help with --help`;
@@ -202,8 +243,11 @@ class Parser {
202
243
  // user provided some input
203
244
  if (tokenLength) {
204
245
  // boolean
205
- if (fws.inputFlag.flag.type === 'boolean' && fws.tokens?.at(-1)?.input) {
206
- return { ...fws, valueFunction: async (i) => parseFlagOrThrowError(i.tokens?.at(-1)?.input !== `--no-${i.inputFlag.name}`, i.inputFlag.flag, i.tokens?.at(-1), this.context) };
246
+ if (fws.inputFlag.flag.type === 'boolean' && (0, util_1.last)(fws.tokens)?.input) {
247
+ return {
248
+ ...fws,
249
+ valueFunction: async (i) => parseFlagOrThrowError((0, util_1.last)(i.tokens)?.input !== `--no-${i.inputFlag.name}`, i.inputFlag.flag, this.context, (0, util_1.last)(i.tokens)),
250
+ };
207
251
  }
208
252
  // multiple with custom delimiter
209
253
  if (fws.inputFlag.flag.type === 'option' && fws.inputFlag.flag.delimiter && fws.inputFlag.flag.multiple) {
@@ -211,26 +255,38 @@ class Parser {
211
255
  ...fws, valueFunction: async (i) => (await Promise.all(((i.tokens ?? []).flatMap(token => token.input.split(i.inputFlag.flag.delimiter)))
212
256
  // trim, and remove surrounding doubleQuotes (which would hav been needed if the elements contain spaces)
213
257
  .map(v => v.trim().replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1'))
214
- .map(async (v) => parseFlagOrThrowError(v, i.inputFlag.flag, { ...i.tokens?.at(-1), input: v }, this.context)))).map(v => validateOptions(i.inputFlag.flag, v)),
258
+ .map(async (v) => parseFlagOrThrowError(v, i.inputFlag.flag, this.context, { ...(0, util_1.last)(i.tokens), input: v })))).map(v => validateOptions(i.inputFlag.flag, v)),
215
259
  };
216
260
  }
217
261
  // multiple in the oclif-core style
218
262
  if (fws.inputFlag.flag.type === 'option' && fws.inputFlag.flag.multiple) {
219
- return { ...fws, valueFunction: async (i) => Promise.all((fws.tokens ?? []).map(token => parseFlagOrThrowError(validateOptions(i.inputFlag.flag, token.input), i.inputFlag.flag, token, this.context))) };
263
+ return {
264
+ ...fws,
265
+ valueFunction: async (i) => Promise.all((fws.tokens ?? []).map(token => parseFlagOrThrowError(validateOptions(i.inputFlag.flag, token.input), i.inputFlag.flag, this.context, token))),
266
+ };
220
267
  }
221
268
  // simple option flag
222
269
  if (fws.inputFlag.flag.type === 'option') {
223
- return { ...fws, valueFunction: async (i) => parseFlagOrThrowError(validateOptions(i.inputFlag.flag, fws.tokens?.at(-1)?.input), i.inputFlag.flag, fws.tokens?.at(-1), this.context) };
270
+ return {
271
+ ...fws,
272
+ valueFunction: async (i) => parseFlagOrThrowError(validateOptions(i.inputFlag.flag, (0, util_1.last)(fws.tokens)?.input), i.inputFlag.flag, this.context, (0, util_1.last)(fws.tokens)),
273
+ };
224
274
  }
225
275
  }
226
276
  // no input: env flags
227
277
  if (fws.inputFlag.flag.env && process.env[fws.inputFlag.flag.env]) {
228
278
  const valueFromEnv = process.env[fws.inputFlag.flag.env];
229
279
  if (fws.inputFlag.flag.type === 'option' && valueFromEnv) {
230
- return { ...fws, valueFunction: async (i) => parseFlagOrThrowError(validateOptions(i.inputFlag.flag, valueFromEnv), i.inputFlag.flag, this.context) };
280
+ return {
281
+ ...fws,
282
+ valueFunction: async (i) => parseFlagOrThrowError(validateOptions(i.inputFlag.flag, valueFromEnv), i.inputFlag.flag, this.context),
283
+ };
231
284
  }
232
285
  if (fws.inputFlag.flag.type === 'boolean') {
233
- return { ...fws, valueFunction: async (i) => Promise.resolve((0, util_1.isTruthy)(process.env[i.inputFlag.flag.env] ?? 'false')) };
286
+ return {
287
+ ...fws,
288
+ valueFunction: async (i) => Promise.resolve((0, util_1.isTruthy)(process.env[i.inputFlag.flag.env] ?? 'false')),
289
+ };
234
290
  }
235
291
  }
236
292
  // no input, but flag has default value
package/lib/util.d.ts CHANGED
@@ -5,6 +5,7 @@ export declare function pickBy<T extends {
5
5
  } | ArrayLike<T[keyof T]>>(obj: T, fn: (i: T[keyof T]) => boolean): Partial<T>;
6
6
  export declare function compact<T>(a: (T | undefined)[]): T[];
7
7
  export declare function uniqBy<T>(arr: T[], fn: (cur: T) => any): T[];
8
+ export declare function last<T>(arr?: T[]): T | undefined;
8
9
  type SortTypes = string | number | undefined | boolean;
9
10
  export declare function sortBy<T>(arr: T[], fn: (i: T) => SortTypes | SortTypes[]): T[];
10
11
  export declare function castArray<T>(input?: T | T[]): T[];
package/lib/util.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensureArgObject = exports.requireJson = exports.isNotFalsy = exports.isTruthy = exports.fileExists = exports.dirExists = exports.capitalize = exports.sumBy = exports.maxBy = exports.isProd = exports.castArray = exports.sortBy = exports.uniqBy = exports.compact = exports.pickBy = void 0;
3
+ exports.ensureArgObject = exports.requireJson = exports.isNotFalsy = exports.isTruthy = exports.fileExists = exports.dirExists = exports.capitalize = exports.sumBy = exports.maxBy = exports.isProd = exports.castArray = exports.sortBy = exports.last = exports.uniqBy = exports.compact = exports.pickBy = void 0;
4
4
  const fs = require("fs");
5
5
  const path_1 = require("path");
6
6
  function pickBy(obj, fn) {
@@ -23,6 +23,12 @@ function uniqBy(arr, fn) {
23
23
  });
24
24
  }
25
25
  exports.uniqBy = uniqBy;
26
+ function last(arr) {
27
+ if (!arr)
28
+ return;
29
+ return arr.slice(-1)[0];
30
+ }
31
+ exports.last = last;
26
32
  function sortBy(arr, fn) {
27
33
  function compare(a, b) {
28
34
  a = a === undefined ? 0 : a;
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": "2.10.0",
4
+ "version": "2.11.0",
5
5
  "author": "Salesforce",
6
6
  "bugs": "https://github.com/oclif/core/issues",
7
7
  "dependencies": {