@oclif/core 3.19.1 → 3.19.2-qa.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.
@@ -18,6 +18,7 @@ export declare class Config implements IConfig {
18
18
  errlog: string;
19
19
  flexibleTaxonomy: boolean;
20
20
  home: string;
21
+ isSingleCommandCLI: boolean;
21
22
  name: string;
22
23
  npmRegistry?: string;
23
24
  nsisCustomization?: string;
@@ -98,6 +98,7 @@ class Config {
98
98
  errlog;
99
99
  flexibleTaxonomy;
100
100
  home;
101
+ isSingleCommandCLI = false;
101
102
  name;
102
103
  npmRegistry;
103
104
  nsisCustomization;
@@ -328,6 +329,10 @@ class Config {
328
329
  ...(s3.templates && s3.templates.vanilla),
329
330
  },
330
331
  };
332
+ this.isSingleCommandCLI = Boolean(this.pjson.oclif.default ||
333
+ (typeof this.pjson.oclif.commands !== 'string' &&
334
+ this.pjson.oclif.commands?.strategy === 'single' &&
335
+ this.pjson.oclif.commands?.target));
331
336
  await this.loadPluginsAndCommands();
332
337
  debug('config done');
333
338
  marker?.addDetails({
@@ -29,6 +29,8 @@ export declare class Plugin implements IPlugin {
29
29
  protected warned: boolean;
30
30
  _base: string;
31
31
  protected _debug: (..._: any) => void;
32
+ private commandCache;
33
+ private commandDiscoveryOpts;
32
34
  private flexibleTaxonomy;
33
35
  constructor(options: PluginOptions);
34
36
  get topics(): Topic[];
@@ -42,6 +44,9 @@ export declare class Plugin implements IPlugin {
42
44
  private _manifest;
43
45
  private addErrorScope;
44
46
  private getCommandIDs;
47
+ private getCommandIdsFromPattern;
48
+ private getCommandIdsFromTarget;
45
49
  private getCommandsDir;
50
+ private loadCommandsFromTarget;
46
51
  private warn;
47
52
  }
@@ -10,6 +10,7 @@ const node_util_1 = require("node:util");
10
10
  const errors_1 = require("../errors");
11
11
  const module_loader_1 = require("../module-loader");
12
12
  const performance_1 = require("../performance");
13
+ const symbols_1 = require("../symbols");
13
14
  const cache_command_1 = require("../util/cache-command");
14
15
  const find_root_1 = require("../util/find-root");
15
16
  const fs_1 = require("../util/fs");
@@ -30,7 +31,7 @@ function topicsToArray(input, base) {
30
31
  });
31
32
  }
32
33
  const cachedCommandCanBeUsed = (manifest, id) => Boolean(manifest?.commands[id] && 'isESM' in manifest.commands[id] && 'relativePath' in manifest.commands[id]);
33
- const search = (cmd) => {
34
+ const searchForCommandClass = (cmd) => {
34
35
  if (typeof cmd.run === 'function')
35
36
  return cmd;
36
37
  if (cmd.default && cmd.default.run)
@@ -47,9 +48,24 @@ function processCommandIds(files) {
47
48
  const topics = p.dir.split('/');
48
49
  const command = p.name !== 'index' && p.name;
49
50
  const id = [...topics, command].filter(Boolean).join(':');
50
- return id === '' ? '.' : id;
51
+ return id === '' ? symbols_1.SINGLE_COMMAND_CLI_SYMBOL : id;
51
52
  });
52
53
  }
54
+ function determineCommandDiscoveryOptions(commandDiscovery, defaultCmdId) {
55
+ if (!commandDiscovery)
56
+ return;
57
+ if (typeof commandDiscovery === 'string' && defaultCmdId) {
58
+ return { strategy: 'single', target: commandDiscovery };
59
+ }
60
+ if (typeof commandDiscovery === 'string') {
61
+ return { globPatterns: GLOB_PATTERNS, strategy: 'pattern', target: commandDiscovery };
62
+ }
63
+ if (!commandDiscovery.target)
64
+ throw new errors_1.CLIError('`oclif.commandDiscovery.target` is required.');
65
+ if (!commandDiscovery.strategy)
66
+ throw new errors_1.CLIError('`oclif.commandDiscovery.strategy` is required.');
67
+ return commandDiscovery;
68
+ }
53
69
  class Plugin {
54
70
  options;
55
71
  alias;
@@ -76,6 +92,8 @@ class Plugin {
76
92
  _base = `${_pjson.name}@${_pjson.version}`;
77
93
  // eslint-disable-next-line new-cap
78
94
  _debug = (0, util_2.Debug)();
95
+ commandCache;
96
+ commandDiscoveryOpts;
79
97
  flexibleTaxonomy;
80
98
  constructor(options) {
81
99
  this.options = options;
@@ -89,32 +107,43 @@ class Plugin {
89
107
  plugin: this.name,
90
108
  });
91
109
  const fetch = async () => {
92
- const commandsDir = await this.getCommandsDir();
93
- if (!commandsDir)
94
- return;
95
- let module;
96
- let isESM;
97
- let filePath;
98
- try {
99
- ;
100
- ({ filePath, isESM, module } = cachedCommandCanBeUsed(this.manifest, id)
101
- ? await (0, module_loader_1.loadWithDataFromManifest)(this.manifest.commands[id], this.root)
102
- : await (0, module_loader_1.loadWithData)(this, (0, node_path_1.join)(commandsDir ?? this.pjson.oclif.commands, ...id.split(':'))));
103
- this._debug(isESM ? '(import)' : '(require)', filePath);
110
+ if (this.commandDiscoveryOpts?.strategy === 'pattern') {
111
+ const commandsDir = await this.getCommandsDir();
112
+ if (!commandsDir)
113
+ return;
114
+ let module;
115
+ let isESM;
116
+ let filePath;
117
+ try {
118
+ ;
119
+ ({ filePath, isESM, module } = cachedCommandCanBeUsed(this.manifest, id)
120
+ ? await (0, module_loader_1.loadWithDataFromManifest)(this.manifest.commands[id], this.root)
121
+ : await (0, module_loader_1.loadWithData)(this, (0, node_path_1.join)(commandsDir ?? this.pjson.oclif.commands, ...id.split(':'))));
122
+ this._debug(isESM ? '(import)' : '(require)', filePath);
123
+ }
124
+ catch (error) {
125
+ if (!opts.must && error.code === 'MODULE_NOT_FOUND')
126
+ return;
127
+ throw error;
128
+ }
129
+ const cmd = searchForCommandClass(module);
130
+ if (!cmd)
131
+ return;
132
+ cmd.id = id;
133
+ cmd.plugin = this;
134
+ cmd.isESM = isESM;
135
+ cmd.relativePath = (0, node_path_1.relative)(this.root, filePath || '').split(node_path_1.sep);
136
+ return cmd;
104
137
  }
105
- catch (error) {
106
- if (!opts.must && error.code === 'MODULE_NOT_FOUND')
138
+ if (this.commandDiscoveryOpts?.strategy === 'single' || this.commandDiscoveryOpts?.strategy === 'explicit') {
139
+ const commandCache = await this.loadCommandsFromTarget();
140
+ const cmd = commandCache?.[id];
141
+ if (!cmd)
107
142
  return;
108
- throw error;
143
+ cmd.id = id;
144
+ cmd.plugin = this;
145
+ return cmd;
109
146
  }
110
- const cmd = search(module);
111
- if (!cmd)
112
- return;
113
- cmd.id = id;
114
- cmd.plugin = this;
115
- cmd.isESM = isESM;
116
- cmd.relativePath = (0, node_path_1.relative)(this.root, filePath || '').split(node_path_1.sep);
117
- return cmd;
118
147
  };
119
148
  const cmd = await fetch();
120
149
  if (!cmd && opts.must)
@@ -154,6 +183,8 @@ class Plugin {
154
183
  this.pjson.oclif = this.pjson['cli-engine'] || {};
155
184
  }
156
185
  this.hooks = Object.fromEntries(Object.entries(this.pjson.oclif.hooks ?? {}).map(([k, v]) => [k, (0, util_1.castArray)(v)]));
186
+ this.commandDiscoveryOpts = determineCommandDiscoveryOptions(this.pjson.oclif?.commands, this.pjson.oclif?.default);
187
+ this._debug('command discovery options', this.commandDiscoveryOpts);
157
188
  this.manifest = await this._manifest();
158
189
  this.commands = Object.entries(this.manifest.commands)
159
190
  .map(([id, c]) => ({
@@ -205,7 +236,13 @@ class Plugin {
205
236
  const manifest = {
206
237
  commands: (await Promise.all(this.commandIDs.map(async (id) => {
207
238
  try {
208
- const cached = await (0, cache_command_1.cacheCommand)(await this.findCommand(id, { must: true }), this, respectNoCacheDefault);
239
+ const found = await this.findCommand(id, { must: true });
240
+ const cached = await (0, cache_command_1.cacheCommand)(found, this, respectNoCacheDefault);
241
+ // Ensure that id is set to the id being processed
242
+ // This is necessary because the id is set by findCommand but if there
243
+ // are multiple instances of a Command, then the id will be set to the
244
+ // last one found.
245
+ cached.id = id;
209
246
  if (this.flexibleTaxonomy) {
210
247
  const permutations = (0, util_2.getCommandIdPermutations)(id);
211
248
  const aliasPermutations = cached.aliases.flatMap((a) => (0, util_2.getCommandIdPermutations)(a));
@@ -246,24 +283,66 @@ class Plugin {
246
283
  return err;
247
284
  }
248
285
  async getCommandIDs() {
249
- const commandsDir = await this.getCommandsDir();
250
- if (!commandsDir)
251
- return [];
252
286
  const marker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, `plugin.getCommandIDs#${this.name}`, { plugin: this.name });
253
- this._debug(`loading IDs from ${commandsDir}`);
254
- const files = await (0, globby_1.default)(GLOB_PATTERNS, { cwd: commandsDir });
255
- const ids = processCommandIds(files);
287
+ let ids;
288
+ switch (this.commandDiscoveryOpts?.strategy) {
289
+ case 'explicit': {
290
+ ids = (await this.getCommandIdsFromTarget()) ?? [];
291
+ break;
292
+ }
293
+ case 'pattern': {
294
+ ids = await this.getCommandIdsFromPattern();
295
+ break;
296
+ }
297
+ case 'single': {
298
+ ids = (await this.getCommandIdsFromTarget()) ?? [];
299
+ break;
300
+ }
301
+ default: {
302
+ ids = [];
303
+ }
304
+ }
256
305
  this._debug('found commands', ids);
257
306
  marker?.addDetails({ count: ids.length });
258
307
  marker?.stop();
259
308
  return ids;
260
309
  }
310
+ async getCommandIdsFromPattern() {
311
+ const commandsDir = await this.getCommandsDir();
312
+ if (!commandsDir)
313
+ return [];
314
+ this._debug(`loading IDs from ${commandsDir}`);
315
+ const files = await (0, globby_1.default)(this.commandDiscoveryOpts?.globPatterns ?? GLOB_PATTERNS, { cwd: commandsDir });
316
+ return processCommandIds(files);
317
+ }
318
+ async getCommandIdsFromTarget() {
319
+ const commandsFromExport = await this.loadCommandsFromTarget();
320
+ if (commandsFromExport) {
321
+ return Object.keys(commandsFromExport);
322
+ }
323
+ }
261
324
  async getCommandsDir() {
262
325
  if (this.commandsDir)
263
326
  return this.commandsDir;
264
- this.commandsDir = await (0, ts_node_1.tsPath)(this.root, this.pjson.oclif.commands, this);
327
+ this.commandsDir = await (0, ts_node_1.tsPath)(this.root, this.commandDiscoveryOpts?.target, this);
265
328
  return this.commandsDir;
266
329
  }
330
+ async loadCommandsFromTarget() {
331
+ if (this.commandCache)
332
+ return this.commandCache;
333
+ if (this.commandDiscoveryOpts?.strategy === 'explicit' && this.commandDiscoveryOpts.target) {
334
+ const filePath = await (0, ts_node_1.tsPath)(this.root, this.commandDiscoveryOpts.target, this);
335
+ const module = await (0, module_loader_1.load)(this, filePath);
336
+ this.commandCache = module.default ?? {};
337
+ return this.commandCache;
338
+ }
339
+ if (this.commandDiscoveryOpts?.strategy === 'single' && this.commandDiscoveryOpts.target) {
340
+ const filePath = await (0, ts_node_1.tsPath)(this.root, this.commandDiscoveryOpts?.target ?? this.root, this);
341
+ const module = await (0, module_loader_1.load)(this, filePath);
342
+ this.commandCache = { [symbols_1.SINGLE_COMMAND_CLI_SYMBOL]: searchForCommandClass(module) };
343
+ return this.commandCache;
344
+ }
345
+ }
267
346
  warn(err, scope) {
268
347
  if (this.warned)
269
348
  return;
@@ -77,7 +77,7 @@ class DocOpts {
77
77
  return new DocOpts(cmd).toString();
78
78
  }
79
79
  toString() {
80
- const opts = this.cmd.id === '.' || this.cmd.id === '' ? [] : ['<%= command.id %>'];
80
+ const opts = ['<%= command.id %>'];
81
81
  if (this.cmd.args) {
82
82
  const a = Object.values((0, ensure_arg_object_1.ensureArgObject)(this.cmd.args)).map((arg) => arg.required ? arg.name.toUpperCase() : `[${arg.name.toUpperCase()}]`) || [];
83
83
  opts.push(...a);
package/lib/help/index.js CHANGED
@@ -10,6 +10,7 @@ const theme_1 = require("../cli-ux/theme");
10
10
  const write_1 = __importDefault(require("../cli-ux/write"));
11
11
  const errors_1 = require("../errors");
12
12
  const module_loader_1 = require("../module-loader");
13
+ const symbols_1 = require("../symbols");
13
14
  const cache_default_value_1 = require("../util/cache-default-value");
14
15
  const ids_1 = require("../util/ids");
15
16
  const util_1 = require("../util/util");
@@ -189,8 +190,8 @@ class Help extends HelpBase {
189
190
  argv = (0, util_2.standardizeIDFromArgv)(argv, this.config);
190
191
  const subject = getHelpSubject(argv, this.config);
191
192
  if (!subject) {
192
- if (this.config.pjson.oclif.default) {
193
- const rootCmd = this.config.findCommand(this.config.pjson.oclif.default);
193
+ if (this.config.isSingleCommandCLI) {
194
+ const rootCmd = this.config.findCommand(symbols_1.SINGLE_COMMAND_CLI_SYMBOL);
194
195
  if (rootCmd) {
195
196
  await this.showCommandHelp(rootCmd);
196
197
  return;
@@ -201,6 +202,12 @@ class Help extends HelpBase {
201
202
  }
202
203
  const command = this.config.findCommand(subject);
203
204
  if (command) {
205
+ if (command.id === symbols_1.SINGLE_COMMAND_CLI_SYMBOL) {
206
+ // If the command is the root command of a single command CLI,
207
+ // then set the command id to an empty string to prevent the
208
+ // the SINGLE_COMMAND_CLI_SYMBOL from being displayed in the help output.
209
+ command.id = '';
210
+ }
204
211
  if (command.hasDynamicHelp && command.pluginType !== 'jit') {
205
212
  const loaded = await command.load();
206
213
  for (const [name, flag] of Object.entries(loaded.flags ?? {})) {
@@ -96,6 +96,7 @@ export interface Config {
96
96
  * example: /home/myuser
97
97
  */
98
98
  readonly home: string;
99
+ readonly isSingleCommandCLI: boolean;
99
100
  readonly name: string;
100
101
  /**
101
102
  * npm registry to use for installing plugins
@@ -16,6 +16,45 @@ export interface PJSON {
16
16
  };
17
17
  version: string;
18
18
  }
19
+ export type CommandDiscovery = {
20
+ /**
21
+ * The strategy to use for loading commands.
22
+ *
23
+ * - `pattern` will use glob patterns to find command files in the specified `directory`.
24
+ * - `explicit` will use `import` (or `require` for CJS) to load the commands from the
25
+ * specified `file`.
26
+ * - `single` will use the `target` which should export a command class. This is for CLIs that
27
+ * only have a single command.
28
+ *
29
+ * In both cases, the `oclif.manifest.json` file will be used to find the commands if it exists.
30
+ */
31
+ strategy: 'pattern' | 'explicit' | 'single';
32
+ /**
33
+ * If the `strategy` is `pattern`, this is the **directory** to use to find command files.
34
+ *
35
+ * If the `strategy` is `explicit`, this is the **file** that default exports the commands.
36
+ * - This export must be the default export and an object with keys that are the command names
37
+ * and values that are the command classes.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * // in src/commands.ts
42
+ * import Hello from './commands/hello/index.js'
43
+ * import HelloWorld from './commands/hello/world.js'
44
+ *
45
+ * export default {
46
+ * hello: Hello,
47
+ * 'hello:world': HelloWorld,
48
+ * }
49
+ * ```
50
+ */
51
+ target: string;
52
+ /**
53
+ * The glob patterns to use to find command files when no `oclif.manifest.json` is present.
54
+ * This is only used when `strategy` is `pattern`.
55
+ */
56
+ globPatterns?: string[];
57
+ };
19
58
  export declare namespace PJSON {
20
59
  interface Plugin extends PJSON {
21
60
  name: string;
@@ -25,7 +64,13 @@ export declare namespace PJSON {
25
64
  aliases?: {
26
65
  [name: string]: null | string;
27
66
  };
28
- commands?: string;
67
+ commands?: string | CommandDiscovery;
68
+ /**
69
+ * Default command id when no command is found. This is used to support single command CLIs.
70
+ * Only supported value is "."
71
+ *
72
+ * @deprecated Use `commands.strategy: 'single'` instead.
73
+ */
29
74
  default?: string;
30
75
  description?: string;
31
76
  devPlugins?: string[];
package/lib/main.js CHANGED
@@ -6,9 +6,10 @@ const cli_ux_1 = require("./cli-ux");
6
6
  const config_1 = require("./config");
7
7
  const help_1 = require("./help");
8
8
  const performance_1 = require("./performance");
9
+ const symbols_1 = require("./symbols");
9
10
  const debug = require('debug')('oclif:main');
10
11
  const helpAddition = (argv, config) => {
11
- if (argv.length === 0 && !config.pjson.oclif.default)
12
+ if (argv.length === 0 && !config.isSingleCommandCLI)
12
13
  return true;
13
14
  const mergedHelpFlags = (0, help_1.getHelpFlagAdditions)(config);
14
15
  for (const arg of argv) {
@@ -47,7 +48,11 @@ async function run(argv, options) {
47
48
  options = (0, node_url_1.fileURLToPath)(options);
48
49
  }
49
50
  const config = await config_1.Config.load(options ?? require.main?.filename ?? __dirname);
50
- let [id, ...argvSlice] = (0, help_1.normalizeArgv)(config, argv);
51
+ // If this is a single command CLI, then insert the SINGLE_COMMAND_CLI_SYMBOL into the argv array to serve as the command id.
52
+ if (config.isSingleCommandCLI) {
53
+ argv = [symbols_1.SINGLE_COMMAND_CLI_SYMBOL, ...argv];
54
+ }
55
+ const [id, ...argvSlice] = (0, help_1.normalizeArgv)(config, argv);
51
56
  // run init hook
52
57
  await config.runHook('init', { argv: argvSlice, id });
53
58
  // display version if applicable
@@ -70,17 +75,8 @@ async function run(argv, options) {
70
75
  const topic = config.flexibleTaxonomy ? null : config.findTopic(id);
71
76
  if (topic)
72
77
  return config.runCommand('help', [id]);
73
- if (config.pjson.oclif.default) {
74
- id = config.pjson.oclif.default;
75
- argvSlice = argv;
76
- }
77
78
  }
78
79
  initMarker?.stop();
79
- // If the the default command is '.' (signifying that the CLI is a single command CLI) and '.' is provided
80
- // as an argument, we need to add back the '.' to argv since it was stripped out earlier as part of the
81
- // command id.
82
- if (config.pjson.oclif.default === '.' && id === '.' && argv[0] === '.')
83
- argvSlice = ['.', ...argvSlice];
84
80
  try {
85
81
  return await config.runCommand(id, argvSlice, cmd);
86
82
  }
@@ -0,0 +1 @@
1
+ export declare const SINGLE_COMMAND_CLI_SYMBOL: string;
package/lib/symbols.js ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SINGLE_COMMAND_CLI_SYMBOL = void 0;
4
+ exports.SINGLE_COMMAND_CLI_SYMBOL = Symbol('SINGLE_COMMAND_CLI').toString();
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": "3.19.1",
4
+ "version": "3.19.2-qa.0",
5
5
  "author": "Salesforce",
6
6
  "bugs": "https://github.com/oclif/core/issues",
7
7
  "dependencies": {