@oclif/core 3.19.4 → 3.19.5-dev.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/cache.d.ts CHANGED
@@ -2,15 +2,23 @@ import { PJSON, Plugin } from './interfaces';
2
2
  type CacheContents = {
3
3
  rootPlugin: Plugin;
4
4
  exitCodes: PJSON.Plugin['oclif']['exitCodes'];
5
+ '@oclif/core': OclifCoreInfo;
5
6
  };
6
7
  type ValueOf<T> = T[keyof T];
8
+ type OclifCoreInfo = {
9
+ name: string;
10
+ version: string;
11
+ };
7
12
  /**
8
13
  * A simple cache for storing values that need to be accessed globally.
9
14
  */
10
15
  export default class Cache extends Map<keyof CacheContents, ValueOf<CacheContents>> {
11
16
  static instance: Cache;
17
+ constructor();
12
18
  static getInstance(): Cache;
19
+ get(key: '@oclif/core'): OclifCoreInfo;
13
20
  get(key: 'rootPlugin'): Plugin | undefined;
14
21
  get(key: 'exitCodes'): PJSON.Plugin['oclif']['exitCodes'] | undefined;
22
+ private getOclifCoreMeta;
15
23
  }
16
24
  export {};
package/lib/cache.js CHANGED
@@ -1,10 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_fs_1 = require("node:fs");
4
+ const node_path_1 = require("node:path");
3
5
  /**
4
6
  * A simple cache for storing values that need to be accessed globally.
5
7
  */
6
8
  class Cache extends Map {
7
9
  static instance;
10
+ constructor() {
11
+ super();
12
+ this.set('@oclif/core', this.getOclifCoreMeta());
13
+ }
8
14
  static getInstance() {
9
15
  if (!Cache.instance) {
10
16
  Cache.instance = new Cache();
@@ -14,5 +20,22 @@ class Cache extends Map {
14
20
  get(key) {
15
21
  return super.get(key);
16
22
  }
23
+ getOclifCoreMeta() {
24
+ try {
25
+ // eslint-disable-next-line node/no-extraneous-require
26
+ return { name: '@oclif/core', version: require('@oclif/core/package.json').version };
27
+ }
28
+ catch {
29
+ try {
30
+ return {
31
+ name: '@oclif/core',
32
+ version: JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, '..', 'package.json'), 'utf8')),
33
+ };
34
+ }
35
+ catch {
36
+ return { name: '@oclif/core', version: 'unknown' };
37
+ }
38
+ }
39
+ }
17
40
  }
18
41
  exports.default = Cache;
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.config = exports.Config = void 0;
7
- const fs_1 = require("../util/fs");
7
+ const cache_1 = __importDefault(require("../cache"));
8
8
  const simple_1 = __importDefault(require("./action/simple"));
9
9
  const spinner_1 = __importDefault(require("./action/spinner"));
10
10
  const g = global;
@@ -35,7 +35,8 @@ class Config {
35
35
  }
36
36
  exports.Config = Config;
37
37
  function fetch() {
38
- const major = (0, fs_1.requireJson)(__dirname, '..', '..', 'package.json').version.split('.')[0];
38
+ const core = cache_1.default.getInstance().get('@oclif/core');
39
+ const major = core?.version.split('.')[0] || 'unknown';
39
40
  if (globals[major])
40
41
  return globals[major];
41
42
  globals[major] = new Config();
package/lib/command.js CHANGED
@@ -30,16 +30,16 @@ exports.Command = void 0;
30
30
  const chalk_1 = __importDefault(require("chalk"));
31
31
  const node_url_1 = require("node:url");
32
32
  const node_util_1 = require("node:util");
33
+ const cache_1 = __importDefault(require("./cache"));
33
34
  const cli_ux_1 = require("./cli-ux");
34
35
  const config_1 = require("./config");
35
36
  const Errors = __importStar(require("./errors"));
36
37
  const util_1 = require("./help/util");
37
38
  const Parser = __importStar(require("./parser"));
38
39
  const aggregate_flags_1 = require("./util/aggregate-flags");
39
- const fs_1 = require("./util/fs");
40
40
  const ids_1 = require("./util/ids");
41
41
  const util_2 = require("./util/util");
42
- const pjson = (0, fs_1.requireJson)(__dirname, '..', 'package.json');
42
+ const pjson = cache_1.default.getInstance().get('@oclif/core');
43
43
  /**
44
44
  * swallows stdout epipe errors
45
45
  * this occurs when stdout closes such as when piping to head
@@ -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;
@@ -48,7 +48,7 @@ const ts_node_1 = require("./ts-node");
48
48
  const util_3 = require("./util");
49
49
  // eslint-disable-next-line new-cap
50
50
  const debug = (0, util_3.Debug)();
51
- const _pjson = (0, fs_1.requireJson)(__dirname, '..', '..', 'package.json');
51
+ const _pjson = cache_1.default.getInstance().get('@oclif/core');
52
52
  const BASE = `${_pjson.name}@${_pjson.version}`;
53
53
  function channelFromVersion(version) {
54
54
  const m = version.match(/[^-]+(?:-([^.]+))?/);
@@ -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({
@@ -482,14 +487,15 @@ class Config {
482
487
  };
483
488
  const hooks = p.hooks[event] || [];
484
489
  for (const hook of hooks) {
485
- const marker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, `config.runHook#${p.name}(${hook})`);
490
+ const marker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, `config.runHook#${p.name}(${hook.target})`);
486
491
  try {
487
492
  /* eslint-disable no-await-in-loop */
488
- const { filePath, isESM, module } = await (0, module_loader_1.loadWithData)(p, await (0, ts_node_1.tsPath)(p.root, hook, p));
493
+ const { filePath, isESM, module } = await (0, module_loader_1.loadWithData)(p, await (0, ts_node_1.tsPath)(p.root, hook.target, p));
489
494
  debug('start', isESM ? '(import)' : '(require)', filePath);
495
+ const hookFn = module[hook.identifier] ?? search(module);
490
496
  const result = timeout
491
- ? await withTimeout(timeout, search(module).call(context, { ...opts, config: this, context }))
492
- : await search(module).call(context, { ...opts, config: this, context });
497
+ ? await withTimeout(timeout, hookFn.call(context, { ...opts, config: this, context }))
498
+ : await hookFn.call(context, { ...opts, config: this, context });
493
499
  final.successes.push({ plugin: p, result });
494
500
  if (p.name === '@oclif/plugin-legacy' && event === 'init') {
495
501
  this.insertLegacyPlugins(result);
@@ -511,7 +517,7 @@ class Config {
511
517
  }
512
518
  marker?.addDetails({
513
519
  event,
514
- hook,
520
+ hook: hook.target,
515
521
  plugin: p.name,
516
522
  });
517
523
  marker?.stop();
@@ -1,6 +1,6 @@
1
1
  import { Command } from '../command';
2
2
  import { Manifest } from '../interfaces/manifest';
3
- import { PJSON } from '../interfaces/pjson';
3
+ import { HookOptions, PJSON } from '../interfaces/pjson';
4
4
  import { Plugin as IPlugin, PluginOptions } from '../interfaces/plugin';
5
5
  import { Topic } from '../interfaces/topic';
6
6
  export declare class Plugin implements IPlugin {
@@ -13,7 +13,7 @@ export declare class Plugin implements IPlugin {
13
13
  commandsDir: string | undefined;
14
14
  hasManifest: boolean;
15
15
  hooks: {
16
- [k: string]: string[];
16
+ [key: string]: HookOptions[];
17
17
  };
18
18
  isRoot: boolean;
19
19
  manifest: Manifest;
@@ -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
  }
@@ -7,16 +7,18 @@ exports.Plugin = void 0;
7
7
  const globby_1 = __importDefault(require("globby"));
8
8
  const node_path_1 = require("node:path");
9
9
  const node_util_1 = require("node:util");
10
+ const cache_1 = __importDefault(require("../cache"));
10
11
  const errors_1 = require("../errors");
11
12
  const module_loader_1 = require("../module-loader");
12
13
  const performance_1 = require("../performance");
14
+ const symbols_1 = require("../symbols");
13
15
  const cache_command_1 = require("../util/cache-command");
14
16
  const find_root_1 = require("../util/find-root");
15
17
  const fs_1 = require("../util/fs");
16
18
  const util_1 = require("../util/util");
17
19
  const ts_node_1 = require("./ts-node");
18
20
  const util_2 = require("./util");
19
- const _pjson = (0, fs_1.requireJson)(__dirname, '..', '..', 'package.json');
21
+ const _pjson = cache_1.default.getInstance().get('@oclif/core');
20
22
  function topicsToArray(input, base) {
21
23
  if (!input)
22
24
  return [];
@@ -30,7 +32,7 @@ function topicsToArray(input, base) {
30
32
  });
31
33
  }
32
34
  const cachedCommandCanBeUsed = (manifest, id) => Boolean(manifest?.commands[id] && 'isESM' in manifest.commands[id] && 'relativePath' in manifest.commands[id]);
33
- const search = (cmd) => {
35
+ const searchForCommandClass = (cmd) => {
34
36
  if (typeof cmd.run === 'function')
35
37
  return cmd;
36
38
  if (cmd.default && cmd.default.run)
@@ -47,9 +49,34 @@ function processCommandIds(files) {
47
49
  const topics = p.dir.split('/');
48
50
  const command = p.name !== 'index' && p.name;
49
51
  const id = [...topics, command].filter(Boolean).join(':');
50
- return id === '' ? '.' : id;
52
+ return id === '' ? symbols_1.SINGLE_COMMAND_CLI_SYMBOL : id;
51
53
  });
52
54
  }
55
+ function determineCommandDiscoveryOptions(commandDiscovery, defaultCmdId) {
56
+ if (!commandDiscovery)
57
+ return;
58
+ if (typeof commandDiscovery === 'string' && defaultCmdId) {
59
+ return { strategy: 'single', target: commandDiscovery };
60
+ }
61
+ if (typeof commandDiscovery === 'string') {
62
+ return { globPatterns: GLOB_PATTERNS, strategy: 'pattern', target: commandDiscovery };
63
+ }
64
+ if (!commandDiscovery.target)
65
+ throw new errors_1.CLIError('`oclif.commandDiscovery.target` is required.');
66
+ if (!commandDiscovery.strategy)
67
+ throw new errors_1.CLIError('`oclif.commandDiscovery.strategy` is required.');
68
+ if (commandDiscovery.strategy === 'explicit' && !commandDiscovery.identifier) {
69
+ commandDiscovery.identifier = 'default';
70
+ }
71
+ return commandDiscovery;
72
+ }
73
+ function determineHookOptions(hook) {
74
+ if (typeof hook === 'string')
75
+ return { identifier: 'default', target: hook };
76
+ if (!hook.identifier)
77
+ return { ...hook, identifier: 'default' };
78
+ return hook;
79
+ }
53
80
  class Plugin {
54
81
  options;
55
82
  alias;
@@ -76,6 +103,8 @@ class Plugin {
76
103
  _base = `${_pjson.name}@${_pjson.version}`;
77
104
  // eslint-disable-next-line new-cap
78
105
  _debug = (0, util_2.Debug)();
106
+ commandCache;
107
+ commandDiscoveryOpts;
79
108
  flexibleTaxonomy;
80
109
  constructor(options) {
81
110
  this.options = options;
@@ -89,32 +118,43 @@ class Plugin {
89
118
  plugin: this.name,
90
119
  });
91
120
  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);
121
+ if (this.commandDiscoveryOpts?.strategy === 'pattern') {
122
+ const commandsDir = await this.getCommandsDir();
123
+ if (!commandsDir)
124
+ return;
125
+ let module;
126
+ let isESM;
127
+ let filePath;
128
+ try {
129
+ ;
130
+ ({ filePath, isESM, module } = cachedCommandCanBeUsed(this.manifest, id)
131
+ ? await (0, module_loader_1.loadWithDataFromManifest)(this.manifest.commands[id], this.root)
132
+ : await (0, module_loader_1.loadWithData)(this, (0, node_path_1.join)(commandsDir ?? this.pjson.oclif.commands, ...id.split(':'))));
133
+ this._debug(isESM ? '(import)' : '(require)', filePath);
134
+ }
135
+ catch (error) {
136
+ if (!opts.must && error.code === 'MODULE_NOT_FOUND')
137
+ return;
138
+ throw error;
139
+ }
140
+ const cmd = searchForCommandClass(module);
141
+ if (!cmd)
142
+ return;
143
+ cmd.id = id;
144
+ cmd.plugin = this;
145
+ cmd.isESM = isESM;
146
+ cmd.relativePath = (0, node_path_1.relative)(this.root, filePath || '').split(node_path_1.sep);
147
+ return cmd;
104
148
  }
105
- catch (error) {
106
- if (!opts.must && error.code === 'MODULE_NOT_FOUND')
149
+ if (this.commandDiscoveryOpts?.strategy === 'single' || this.commandDiscoveryOpts?.strategy === 'explicit') {
150
+ const commandCache = await this.loadCommandsFromTarget();
151
+ const cmd = commandCache?.[id];
152
+ if (!cmd)
107
153
  return;
108
- throw error;
154
+ cmd.id = id;
155
+ cmd.plugin = this;
156
+ return cmd;
109
157
  }
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
158
  };
119
159
  const cmd = await fetch();
120
160
  if (!cmd && opts.must)
@@ -153,7 +193,12 @@ class Plugin {
153
193
  else {
154
194
  this.pjson.oclif = this.pjson['cli-engine'] || {};
155
195
  }
156
- this.hooks = Object.fromEntries(Object.entries(this.pjson.oclif.hooks ?? {}).map(([k, v]) => [k, (0, util_1.castArray)(v)]));
196
+ this.hooks = Object.fromEntries(Object.entries(this.pjson.oclif.hooks ?? {}).map(([k, v]) => [
197
+ k,
198
+ (0, util_1.castArray)(v).map((v) => determineHookOptions(v)),
199
+ ]));
200
+ this.commandDiscoveryOpts = determineCommandDiscoveryOptions(this.pjson.oclif?.commands, this.pjson.oclif?.default);
201
+ this._debug('command discovery options', this.commandDiscoveryOpts);
157
202
  this.manifest = await this._manifest();
158
203
  this.commands = Object.entries(this.manifest.commands)
159
204
  .map(([id, c]) => ({
@@ -205,7 +250,13 @@ class Plugin {
205
250
  const manifest = {
206
251
  commands: (await Promise.all(this.commandIDs.map(async (id) => {
207
252
  try {
208
- const cached = await (0, cache_command_1.cacheCommand)(await this.findCommand(id, { must: true }), this, respectNoCacheDefault);
253
+ const found = await this.findCommand(id, { must: true });
254
+ const cached = await (0, cache_command_1.cacheCommand)(found, this, respectNoCacheDefault);
255
+ // Ensure that id is set to the id being processed
256
+ // This is necessary because the id is set by findCommand but if there
257
+ // are multiple instances of a Command, then the id will be set to the
258
+ // last one found.
259
+ cached.id = id;
209
260
  if (this.flexibleTaxonomy) {
210
261
  const permutations = (0, util_2.getCommandIdPermutations)(id);
211
262
  const aliasPermutations = cached.aliases.flatMap((a) => (0, util_2.getCommandIdPermutations)(a));
@@ -246,24 +297,66 @@ class Plugin {
246
297
  return err;
247
298
  }
248
299
  async getCommandIDs() {
249
- const commandsDir = await this.getCommandsDir();
250
- if (!commandsDir)
251
- return [];
252
300
  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);
301
+ let ids;
302
+ switch (this.commandDiscoveryOpts?.strategy) {
303
+ case 'explicit': {
304
+ ids = (await this.getCommandIdsFromTarget()) ?? [];
305
+ break;
306
+ }
307
+ case 'pattern': {
308
+ ids = await this.getCommandIdsFromPattern();
309
+ break;
310
+ }
311
+ case 'single': {
312
+ ids = (await this.getCommandIdsFromTarget()) ?? [];
313
+ break;
314
+ }
315
+ default: {
316
+ ids = [];
317
+ }
318
+ }
256
319
  this._debug('found commands', ids);
257
320
  marker?.addDetails({ count: ids.length });
258
321
  marker?.stop();
259
322
  return ids;
260
323
  }
324
+ async getCommandIdsFromPattern() {
325
+ const commandsDir = await this.getCommandsDir();
326
+ if (!commandsDir)
327
+ return [];
328
+ this._debug(`loading IDs from ${commandsDir}`);
329
+ const files = await (0, globby_1.default)(this.commandDiscoveryOpts?.globPatterns ?? GLOB_PATTERNS, { cwd: commandsDir });
330
+ return processCommandIds(files);
331
+ }
332
+ async getCommandIdsFromTarget() {
333
+ const commandsFromExport = await this.loadCommandsFromTarget();
334
+ if (commandsFromExport) {
335
+ return Object.keys(commandsFromExport);
336
+ }
337
+ }
261
338
  async getCommandsDir() {
262
339
  if (this.commandsDir)
263
340
  return this.commandsDir;
264
- this.commandsDir = await (0, ts_node_1.tsPath)(this.root, this.pjson.oclif.commands, this);
341
+ this.commandsDir = await (0, ts_node_1.tsPath)(this.root, this.commandDiscoveryOpts?.target, this);
265
342
  return this.commandsDir;
266
343
  }
344
+ async loadCommandsFromTarget() {
345
+ if (this.commandCache)
346
+ return this.commandCache;
347
+ if (this.commandDiscoveryOpts?.strategy === 'explicit' && this.commandDiscoveryOpts.target) {
348
+ const filePath = await (0, ts_node_1.tsPath)(this.root, this.commandDiscoveryOpts.target, this);
349
+ const module = await (0, module_loader_1.load)(this, filePath);
350
+ this.commandCache = module[this.commandDiscoveryOpts?.identifier ?? 'default'] ?? {};
351
+ return this.commandCache;
352
+ }
353
+ if (this.commandDiscoveryOpts?.strategy === 'single' && this.commandDiscoveryOpts.target) {
354
+ const filePath = await (0, ts_node_1.tsPath)(this.root, this.commandDiscoveryOpts?.target ?? this.root, this);
355
+ const module = await (0, module_loader_1.load)(this, filePath);
356
+ this.commandCache = { [symbols_1.SINGLE_COMMAND_CLI_SYMBOL]: searchForCommandClass(module) };
357
+ return this.commandCache;
358
+ }
359
+ }
267
360
  warn(err, scope) {
268
361
  if (this.warned)
269
362
  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,84 @@ 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 `target`.
24
+ * - `explicit` will use `import` (or `require` for CJS) to load the commands from the
25
+ * specified `target`.
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 exports the commands.
36
+ * - This export must be an object with keys that are the command names and values that are the command classes.
37
+ * - Unless `identifier` is specified, the default export will be used.
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
+ /**
58
+ * The name of the export to used when loading the command object from the `target` file. Only
59
+ * used when `strategy` is `explicit`. Defaults to `default`.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // in src/commands.ts
64
+ * import Hello from './commands/hello/index.js'
65
+ * import HelloWorld from './commands/hello/world.js'
66
+ *
67
+ * export const MY_COMMANDS = {
68
+ * hello: Hello,
69
+ * 'hello:world': HelloWorld,
70
+ * }
71
+ * ```
72
+ *
73
+ * In the package.json:
74
+ * ```json
75
+ * {
76
+ * "oclif": {
77
+ * "commands": {
78
+ * "strategy": "explicit",
79
+ * "target": "./dist/index.js",
80
+ * "identifier": "MY_COMMANDS"
81
+ * }
82
+ * }
83
+ * ```
84
+ */
85
+ identifier?: string;
86
+ };
87
+ export type HookOptions = {
88
+ /**
89
+ * The file path containing hook.
90
+ */
91
+ target: string;
92
+ /**
93
+ * The name of the export to use when loading the hook function from the `target` file. Defaults to `default`.
94
+ */
95
+ identifier: string;
96
+ };
19
97
  export declare namespace PJSON {
20
98
  interface Plugin extends PJSON {
21
99
  name: string;
@@ -25,7 +103,13 @@ export declare namespace PJSON {
25
103
  aliases?: {
26
104
  [name: string]: null | string;
27
105
  };
28
- commands?: string;
106
+ commands?: string | CommandDiscovery;
107
+ /**
108
+ * Default command id when no command is found. This is used to support single command CLIs.
109
+ * Only supported value is "."
110
+ *
111
+ * @deprecated Use `commands.strategy: 'single'` instead.
112
+ */
29
113
  default?: string;
30
114
  description?: string;
31
115
  devPlugins?: string[];
@@ -42,7 +126,7 @@ export declare namespace PJSON {
42
126
  helpClass?: string;
43
127
  helpOptions?: HelpOptions;
44
128
  hooks?: {
45
- [name: string]: string | string[];
129
+ [name: string]: string | string[] | HookOptions | HookOptions[];
46
130
  };
47
131
  jitPlugins?: Record<string, string>;
48
132
  macos?: {
@@ -1,5 +1,5 @@
1
1
  import { Command } from '../command';
2
- import { PJSON } from './pjson';
2
+ import { HookOptions, PJSON } from './pjson';
3
3
  import { Topic } from './topic';
4
4
  export interface PluginOptions {
5
5
  children?: Plugin[];
@@ -44,7 +44,7 @@ export interface Plugin {
44
44
  }): Promise<Command.Class> | undefined;
45
45
  readonly hasManifest: boolean;
46
46
  hooks: {
47
- [k: string]: string[];
47
+ [key: string]: HookOptions[];
48
48
  };
49
49
  /**
50
50
  * True if the plugin is the root plugin.
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/lib/util/fs.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export declare function requireJson<T>(...pathParts: string[]): T;
2
1
  /**
3
2
  * Parser for Args.directory and Flags.directory. Checks that the provided path
4
3
  * exists and is a directory.
package/lib/util/fs.js CHANGED
@@ -1,13 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.existsSync = exports.safeReadJson = exports.readJsonSync = exports.readJson = exports.fileExists = exports.dirExists = exports.requireJson = void 0;
3
+ exports.existsSync = exports.safeReadJson = exports.readJsonSync = exports.readJson = exports.fileExists = exports.dirExists = void 0;
4
4
  const node_fs_1 = require("node:fs");
5
5
  const promises_1 = require("node:fs/promises");
6
- const node_path_1 = require("node:path");
7
- function requireJson(...pathParts) {
8
- return JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(...pathParts), 'utf8'));
9
- }
10
- exports.requireJson = requireJson;
11
6
  /**
12
7
  * Parser for Args.directory and Flags.directory. Checks that the provided path
13
8
  * exists and is a directory.
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.4",
4
+ "version": "3.19.5-dev.0",
5
5
  "author": "Salesforce",
6
6
  "bugs": "https://github.com/oclif/core/issues",
7
7
  "dependencies": {
@@ -114,7 +114,6 @@
114
114
  "access": "public"
115
115
  },
116
116
  "scripts": {
117
- "build:dev": "shx rm -rf lib && tsc --sourceMap",
118
117
  "build": "shx rm -rf lib && tsc",
119
118
  "commitlint": "commitlint",
120
119
  "compile": "tsc",
@@ -127,9 +126,8 @@
127
126
  "test:circular-deps": "madge lib/ -c",
128
127
  "test:debug": "nyc mocha --debug-brk --inspect \"test/**/*.test.ts\"",
129
128
  "test:integration": "mocha --forbid-only \"test/**/*.integration.ts\" --parallel --timeout 1200000",
130
- "test:esm-cjs": "cross-env DEBUG=integration:* ts-node test/integration/esm-cjs.ts",
129
+ "test:interoperability": "cross-env DEBUG=integration:* ts-node test/integration/interop.ts",
131
130
  "test:perf": "ts-node test/perf/parser.perf.ts",
132
- "test:dev": "nyc mocha \"test/**/*.test.ts\"",
133
131
  "test": "nyc mocha --forbid-only \"test/**/*.test.ts\""
134
132
  },
135
133
  "types": "lib/index.d.ts"