@oclif/core 1.5.3 → 1.6.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/CHANGELOG.md +7 -0
- package/lib/config/config.d.ts +54 -3
- package/lib/config/config.js +223 -92
- package/lib/config/plugin.js +4 -3
- package/lib/config/util.d.ts +22 -0
- package/lib/config/util.js +60 -4
- package/lib/help/index.d.ts +1 -2
- package/lib/help/index.js +12 -9
- package/lib/help/util.d.ts +1 -0
- package/lib/help/util.js +16 -10
- package/lib/interfaces/config.d.ts +4 -0
- package/lib/interfaces/hooks.d.ts +9 -0
- package/lib/interfaces/pjson.d.ts +2 -0
- package/lib/main.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [1.6.0](https://github.com/oclif/core/compare/v1.5.3...v1.6.0) (2022-03-14)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* POC for allowing flexible command taxonomy ([#376](https://github.com/oclif/core/issues/376)) ([c47c6c6](https://github.com/oclif/core/commit/c47c6c6fb689a92f66d40aacfa146d885f08d962))
|
|
11
|
+
|
|
5
12
|
### [1.5.3](https://github.com/oclif/core/compare/v1.5.2...v1.5.3) (2022-03-09)
|
|
6
13
|
|
|
7
14
|
|
package/lib/config/config.d.ts
CHANGED
|
@@ -29,10 +29,13 @@ export declare class Config implements IConfig {
|
|
|
29
29
|
binPath?: string;
|
|
30
30
|
valid: boolean;
|
|
31
31
|
topicSeparator: ':' | ' ';
|
|
32
|
+
flexibleTaxonomy: boolean;
|
|
32
33
|
protected warned: boolean;
|
|
33
|
-
private
|
|
34
|
-
private
|
|
35
|
-
private
|
|
34
|
+
private commandPermutations;
|
|
35
|
+
private topicPermutations;
|
|
36
|
+
private _commands;
|
|
37
|
+
private _topics;
|
|
38
|
+
private _commandIDs;
|
|
36
39
|
constructor(options: Options);
|
|
37
40
|
static load(opts?: LoadOptions): Promise<IConfig | Config>;
|
|
38
41
|
load(): Promise<void>;
|
|
@@ -56,6 +59,30 @@ export declare class Config implements IConfig {
|
|
|
56
59
|
findTopic(id: string, opts?: {
|
|
57
60
|
must: boolean;
|
|
58
61
|
}): Topic | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Find all command ids that include the provided command id.
|
|
64
|
+
*
|
|
65
|
+
* For example, if the command ids are:
|
|
66
|
+
* - foo:bar:baz
|
|
67
|
+
* - one:two:three
|
|
68
|
+
*
|
|
69
|
+
* `bar` would return `foo:bar:baz`
|
|
70
|
+
*
|
|
71
|
+
* @param partialCmdId string
|
|
72
|
+
* @param argv string[] process.argv containing the flags and arguments provided by the user
|
|
73
|
+
* @returns string[]
|
|
74
|
+
*/
|
|
75
|
+
findMatches(partialCmdId: string, argv: string[]): Command.Plugin[];
|
|
76
|
+
/**
|
|
77
|
+
* Returns an array of all commands. If flexible taxonomy is enabled then all permutations will be appended to the array.
|
|
78
|
+
* @returns Command.Plugin[]
|
|
79
|
+
*/
|
|
80
|
+
getAllCommands(): Command.Plugin[];
|
|
81
|
+
/**
|
|
82
|
+
* Returns an array of all command ids. If flexible taxonomy is enabled then all permutations will be appended to the array.
|
|
83
|
+
* @returns string[]
|
|
84
|
+
*/
|
|
85
|
+
getAllCommandIDs(): string[];
|
|
59
86
|
get commands(): Command.Plugin[];
|
|
60
87
|
get commandIDs(): string[];
|
|
61
88
|
get topics(): Topic[];
|
|
@@ -78,5 +105,29 @@ export declare class Config implements IConfig {
|
|
|
78
105
|
detail: string;
|
|
79
106
|
}, scope?: string): void;
|
|
80
107
|
protected get isProd(): boolean;
|
|
108
|
+
private getCmdLookupId;
|
|
109
|
+
private getTopicLookupId;
|
|
110
|
+
private loadCommands;
|
|
111
|
+
private loadTopics;
|
|
112
|
+
/**
|
|
113
|
+
* This method is responsible for locating the correct plugin to use for a named command id
|
|
114
|
+
* It searches the {Config} registered commands to match either the raw command id or the command alias
|
|
115
|
+
* It is possible that more than one command will be found. This is due the ability of two distinct plugins to
|
|
116
|
+
* create the same command or command alias.
|
|
117
|
+
*
|
|
118
|
+
* In the case of more than one found command, the function will select the command based on the order in which
|
|
119
|
+
* the plugin is included in the package.json `oclif.plugins` list. The command that occurs first in the list
|
|
120
|
+
* is selected as the command to run.
|
|
121
|
+
*
|
|
122
|
+
* Commands can also be present from either an install or a link. When a command is one of these and a core plugin
|
|
123
|
+
* is present, this function defers to the core plugin.
|
|
124
|
+
*
|
|
125
|
+
* If there is not a core plugin command present, this function will return the first
|
|
126
|
+
* plugin as discovered (will not change the order)
|
|
127
|
+
*
|
|
128
|
+
* @param commands commands to determine the priority of
|
|
129
|
+
* @returns command instance {Command.Plugin} or undefined
|
|
130
|
+
*/
|
|
131
|
+
private determinePriority;
|
|
81
132
|
}
|
|
82
133
|
export declare function toCached(c: Command.Class, plugin?: IPlugin): Promise<Command>;
|
package/lib/config/config.js
CHANGED
|
@@ -7,11 +7,11 @@ const os = require("os");
|
|
|
7
7
|
const path = require("path");
|
|
8
8
|
const url_1 = require("url");
|
|
9
9
|
const util_1 = require("util");
|
|
10
|
-
const util_2 = require("./util");
|
|
11
10
|
const Plugin = require("./plugin");
|
|
12
|
-
const
|
|
13
|
-
const
|
|
11
|
+
const util_2 = require("./util");
|
|
12
|
+
const util_3 = require("../util");
|
|
14
13
|
const module_loader_1 = require("../module-loader");
|
|
14
|
+
const util_4 = require("../help/util");
|
|
15
15
|
// eslint-disable-next-line new-cap
|
|
16
16
|
const debug = (0, util_2.Debug)();
|
|
17
17
|
const _pjson = require('../../package.json');
|
|
@@ -23,6 +23,36 @@ const WSL = require('is-wsl');
|
|
|
23
23
|
function isConfig(o) {
|
|
24
24
|
return o && Boolean(o._base);
|
|
25
25
|
}
|
|
26
|
+
class Permutations extends Map {
|
|
27
|
+
constructor() {
|
|
28
|
+
super(...arguments);
|
|
29
|
+
this.validPermutations = new Map();
|
|
30
|
+
}
|
|
31
|
+
add(permutation, commandId) {
|
|
32
|
+
this.validPermutations.set(permutation, commandId);
|
|
33
|
+
for (const id of (0, util_2.collectUsableIds)([permutation])) {
|
|
34
|
+
if (this.has(id)) {
|
|
35
|
+
this.set(id, this.get(id).add(commandId));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.set(id, new Set([commandId]));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
get(key) {
|
|
43
|
+
var _a;
|
|
44
|
+
return (_a = super.get(key)) !== null && _a !== void 0 ? _a : new Set();
|
|
45
|
+
}
|
|
46
|
+
getValid(key) {
|
|
47
|
+
return this.validPermutations.get(key);
|
|
48
|
+
}
|
|
49
|
+
getAllValid() {
|
|
50
|
+
return [...this.validPermutations.keys()];
|
|
51
|
+
}
|
|
52
|
+
hasValid(key) {
|
|
53
|
+
return this.validPermutations.has(key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
26
56
|
class Config {
|
|
27
57
|
// eslint-disable-next-line no-useless-constructor
|
|
28
58
|
constructor(options) {
|
|
@@ -32,6 +62,10 @@ class Config {
|
|
|
32
62
|
this.plugins = [];
|
|
33
63
|
this.topicSeparator = ':';
|
|
34
64
|
this.warned = false;
|
|
65
|
+
this.commandPermutations = new Permutations();
|
|
66
|
+
this.topicPermutations = new Permutations();
|
|
67
|
+
this._commands = new Map();
|
|
68
|
+
this._topics = new Map();
|
|
35
69
|
}
|
|
36
70
|
static async load(opts = (module.parent && module.parent.parent && module.parent.parent.filename) || __dirname) {
|
|
37
71
|
// Handle the case when a file URL string is passed in such as 'import.meta.url'; covert to file path.
|
|
@@ -62,6 +96,7 @@ class Config {
|
|
|
62
96
|
this.windows = this.platform === 'win32';
|
|
63
97
|
this.bin = this.pjson.oclif.bin || this.name;
|
|
64
98
|
this.dirname = this.pjson.oclif.dirname || this.name;
|
|
99
|
+
this.flexibleTaxonomy = this.pjson.oclif.flexibleTaxonomy || false;
|
|
65
100
|
// currently, only colons or spaces are valid separators
|
|
66
101
|
if (this.pjson.oclif.topicSeparator && [':', ' '].includes(this.pjson.oclif.topicSeparator))
|
|
67
102
|
this.topicSeparator = this.pjson.oclif.topicSeparator;
|
|
@@ -104,6 +139,10 @@ class Config {
|
|
|
104
139
|
await this.loadUserPlugins();
|
|
105
140
|
await this.loadDevPlugins();
|
|
106
141
|
await this.loadCorePlugins();
|
|
142
|
+
for (const plugin of this.plugins) {
|
|
143
|
+
this.loadCommands(plugin);
|
|
144
|
+
this.loadTopics(plugin);
|
|
145
|
+
}
|
|
107
146
|
debug('config done');
|
|
108
147
|
}
|
|
109
148
|
async loadCorePlugins() {
|
|
@@ -129,7 +168,7 @@ class Config {
|
|
|
129
168
|
try {
|
|
130
169
|
const userPJSONPath = path.join(this.dataDir, 'package.json');
|
|
131
170
|
debug('reading user plugins pjson %s', userPJSONPath);
|
|
132
|
-
const pjson = await (0,
|
|
171
|
+
const pjson = await (0, util_2.loadJSON)(userPJSONPath);
|
|
133
172
|
this.userPJSON = pjson;
|
|
134
173
|
if (!pjson.oclif)
|
|
135
174
|
pjson.oclif = { schema: 1 };
|
|
@@ -214,7 +253,10 @@ class Config {
|
|
|
214
253
|
debug('runCommand %s %o', id, argv);
|
|
215
254
|
const c = cachedCommand || this.findCommand(id);
|
|
216
255
|
if (!c) {
|
|
217
|
-
const
|
|
256
|
+
const matches = this.flexibleTaxonomy ? this.findMatches(id, argv) : [];
|
|
257
|
+
const hookResult = this.flexibleTaxonomy && matches.length > 0 ?
|
|
258
|
+
await this.runHook('command_incomplete', { id, argv, matches }) :
|
|
259
|
+
await this.runHook('command_not_found', { id, argv });
|
|
218
260
|
if (hookResult.successes[0]) {
|
|
219
261
|
const cmdResult = hookResult.successes[0].result;
|
|
220
262
|
return cmdResult;
|
|
@@ -240,106 +282,79 @@ class Config {
|
|
|
240
282
|
.join('_')
|
|
241
283
|
.toUpperCase();
|
|
242
284
|
}
|
|
243
|
-
/**
|
|
244
|
-
* This function is responsible for locating the correct plugin to use for a named command id
|
|
245
|
-
* It searches the {Config} registered commands to match either the raw command id or the command alias
|
|
246
|
-
* It is possible that more than one command will be found. This is due the ability of two distinct plugins to
|
|
247
|
-
* create the same command or command alias.
|
|
248
|
-
*
|
|
249
|
-
* In the case of more than one found command, the function will select the command based on the order in which
|
|
250
|
-
* the plugin is included in the package.json `oclif.plugins` list. The command that occurs first in the list
|
|
251
|
-
* is selected as the command to run.
|
|
252
|
-
*
|
|
253
|
-
* Commands can also be present from either an install or a link. When a command is one of these and a core plugin
|
|
254
|
-
* is present, this function defers to the core plugin.
|
|
255
|
-
*
|
|
256
|
-
* If there is not a core plugin command present, this function will return the first
|
|
257
|
-
* plugin as discovered (will not change the order)
|
|
258
|
-
* @param id raw command id or command alias
|
|
259
|
-
* @param opts options to control if the command must be found
|
|
260
|
-
* @returns command instance {Command.Plugin} or undefined
|
|
261
|
-
*/
|
|
262
285
|
findCommand(id, opts = {}) {
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
if (opts.must &&
|
|
266
|
-
(0, errors_1.error)(`command ${
|
|
267
|
-
|
|
268
|
-
return commands[0];
|
|
269
|
-
// more than one command found across available plugins
|
|
270
|
-
const oclifPlugins = (_b = (_a = this.pjson.oclif) === null || _a === void 0 ? void 0 : _a.plugins) !== null && _b !== void 0 ? _b : [];
|
|
271
|
-
const commandPlugins = commands.sort((a, b) => {
|
|
272
|
-
var _a, _b;
|
|
273
|
-
const pluginAliasA = (_a = a.pluginAlias) !== null && _a !== void 0 ? _a : 'A-Cannot-Find-This';
|
|
274
|
-
const pluginAliasB = (_b = b.pluginAlias) !== null && _b !== void 0 ? _b : 'B-Cannot-Find-This';
|
|
275
|
-
const aIndex = oclifPlugins.indexOf(pluginAliasA);
|
|
276
|
-
const bIndex = oclifPlugins.indexOf(pluginAliasB);
|
|
277
|
-
// When both plugin types are 'core' plugins sort based on index
|
|
278
|
-
if (a.pluginType === 'core' && b.pluginType === 'core') {
|
|
279
|
-
// If b appears first in the pjson.plugins sort it first
|
|
280
|
-
return aIndex - bIndex;
|
|
281
|
-
}
|
|
282
|
-
// if b is a core plugin and a is not sort b first
|
|
283
|
-
if (b.pluginType === 'core' && a.pluginType !== 'core') {
|
|
284
|
-
return 1;
|
|
285
|
-
}
|
|
286
|
-
// if a is a core plugin and b is not sort a first
|
|
287
|
-
if (a.pluginType === 'core' && b.pluginType !== 'core') {
|
|
288
|
-
return -1;
|
|
289
|
-
}
|
|
290
|
-
// neither plugin is core, so do not change the order
|
|
291
|
-
return 0;
|
|
292
|
-
});
|
|
293
|
-
return commandPlugins[0];
|
|
286
|
+
const lookupId = this.getCmdLookupId(id);
|
|
287
|
+
const command = this._commands.get(lookupId);
|
|
288
|
+
if (opts.must && !command)
|
|
289
|
+
(0, errors_1.error)(`command ${lookupId} not found`);
|
|
290
|
+
return command;
|
|
294
291
|
}
|
|
295
292
|
findTopic(name, opts = {}) {
|
|
296
|
-
const
|
|
293
|
+
const lookupId = this.getTopicLookupId(name);
|
|
294
|
+
const topic = this._topics.get(lookupId);
|
|
297
295
|
if (topic)
|
|
298
296
|
return topic;
|
|
299
297
|
if (opts.must)
|
|
300
298
|
throw new Error(`topic ${name} not found`);
|
|
301
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Find all command ids that include the provided command id.
|
|
302
|
+
*
|
|
303
|
+
* For example, if the command ids are:
|
|
304
|
+
* - foo:bar:baz
|
|
305
|
+
* - one:two:three
|
|
306
|
+
*
|
|
307
|
+
* `bar` would return `foo:bar:baz`
|
|
308
|
+
*
|
|
309
|
+
* @param partialCmdId string
|
|
310
|
+
* @param argv string[] process.argv containing the flags and arguments provided by the user
|
|
311
|
+
* @returns string[]
|
|
312
|
+
*/
|
|
313
|
+
findMatches(partialCmdId, argv) {
|
|
314
|
+
const flags = argv.filter(arg => !(0, util_4.getHelpFlagAdditions)(this).includes(arg) && arg.startsWith('-')).map(a => a.replace(/-/g, ''));
|
|
315
|
+
const possibleMatches = [...this.commandPermutations.get(partialCmdId)].map(k => this._commands.get(k));
|
|
316
|
+
const matches = possibleMatches.filter(command => {
|
|
317
|
+
const cmdFlags = Object.entries(command.flags).flatMap(([flag, def]) => {
|
|
318
|
+
return def.char ? [def.char, flag] : [flag];
|
|
319
|
+
});
|
|
320
|
+
// A command is a match if the provided flags belong to the full command
|
|
321
|
+
return flags.every(f => cmdFlags.includes(f));
|
|
322
|
+
});
|
|
323
|
+
return matches;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Returns an array of all commands. If flexible taxonomy is enabled then all permutations will be appended to the array.
|
|
327
|
+
* @returns Command.Plugin[]
|
|
328
|
+
*/
|
|
329
|
+
getAllCommands() {
|
|
330
|
+
const commands = [...this._commands.values()];
|
|
331
|
+
const validPermutations = [...this.commandPermutations.getAllValid()];
|
|
332
|
+
for (const permutation of validPermutations) {
|
|
333
|
+
if (!this._commands.has(permutation)) {
|
|
334
|
+
const cmd = this._commands.get(this.getCmdLookupId(permutation));
|
|
335
|
+
commands.push({ ...cmd, id: permutation });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return commands;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Returns an array of all command ids. If flexible taxonomy is enabled then all permutations will be appended to the array.
|
|
342
|
+
* @returns string[]
|
|
343
|
+
*/
|
|
344
|
+
getAllCommandIDs() {
|
|
345
|
+
return this.getAllCommands().map(c => c.id);
|
|
346
|
+
}
|
|
302
347
|
get commands() {
|
|
303
|
-
|
|
304
|
-
return this._commands;
|
|
305
|
-
this._commands = (0, util_3.flatMap)(this.plugins, p => p.commands);
|
|
306
|
-
return this._commands;
|
|
348
|
+
return [...this._commands.values()];
|
|
307
349
|
}
|
|
308
350
|
get commandIDs() {
|
|
309
351
|
if (this._commandIDs)
|
|
310
352
|
return this._commandIDs;
|
|
311
|
-
|
|
312
|
-
this._commandIDs = (0, util_3.uniq)(ids);
|
|
353
|
+
this._commandIDs = this.commands.map(c => c.id);
|
|
313
354
|
return this._commandIDs;
|
|
314
355
|
}
|
|
315
356
|
get topics() {
|
|
316
|
-
|
|
317
|
-
return this._topics;
|
|
318
|
-
const topics = [];
|
|
319
|
-
for (const plugin of this.plugins) {
|
|
320
|
-
for (const topic of (0, util_3.compact)(plugin.topics)) {
|
|
321
|
-
const existing = topics.find(t => t.name === topic.name);
|
|
322
|
-
if (existing) {
|
|
323
|
-
existing.description = topic.description || existing.description;
|
|
324
|
-
existing.hidden = existing.hidden || topic.hidden;
|
|
325
|
-
}
|
|
326
|
-
else
|
|
327
|
-
topics.push(topic);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
// add missing topics
|
|
331
|
-
for (const c of this.commands.filter(c => !c.hidden)) {
|
|
332
|
-
const parts = c.id.split(':');
|
|
333
|
-
while (parts.length > 0) {
|
|
334
|
-
const name = parts.join(':');
|
|
335
|
-
if (name && !topics.find(t => t.name === name)) {
|
|
336
|
-
topics.push({ name, description: c.summary || c.description });
|
|
337
|
-
}
|
|
338
|
-
parts.pop();
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
this._topics = topics;
|
|
342
|
-
return this._topics;
|
|
357
|
+
return [...this._topics.values()];
|
|
343
358
|
}
|
|
344
359
|
s3Key(type, ext, options = {}) {
|
|
345
360
|
var _a;
|
|
@@ -444,7 +459,7 @@ class Config {
|
|
|
444
459
|
if (err instanceof Error) {
|
|
445
460
|
const modifiedErr = err;
|
|
446
461
|
modifiedErr.name = `${err.name} Plugin: ${this.name}`;
|
|
447
|
-
modifiedErr.detail = (0,
|
|
462
|
+
modifiedErr.detail = (0, util_2.compact)([
|
|
448
463
|
err.detail,
|
|
449
464
|
`module: ${this._base}`,
|
|
450
465
|
scope && `task: ${scope}`,
|
|
@@ -458,7 +473,7 @@ class Config {
|
|
|
458
473
|
// err is an object
|
|
459
474
|
process.emitWarning('Config.warn expected either a string or Error, but instead received an object');
|
|
460
475
|
err.name = `${err.name} Plugin: ${this.name}`;
|
|
461
|
-
err.detail = (0,
|
|
476
|
+
err.detail = (0, util_2.compact)([
|
|
462
477
|
err.detail,
|
|
463
478
|
`module: ${this._base}`,
|
|
464
479
|
scope && `task: ${scope}`,
|
|
@@ -469,7 +484,123 @@ class Config {
|
|
|
469
484
|
process.emitWarning(JSON.stringify(err));
|
|
470
485
|
}
|
|
471
486
|
get isProd() {
|
|
472
|
-
return (0,
|
|
487
|
+
return (0, util_3.isProd)();
|
|
488
|
+
}
|
|
489
|
+
getCmdLookupId(id) {
|
|
490
|
+
if (this._commands.has(id))
|
|
491
|
+
return id;
|
|
492
|
+
if (this.commandPermutations.hasValid(id))
|
|
493
|
+
return this.commandPermutations.getValid(id);
|
|
494
|
+
return id;
|
|
495
|
+
}
|
|
496
|
+
getTopicLookupId(id) {
|
|
497
|
+
if (this._topics.has(id))
|
|
498
|
+
return id;
|
|
499
|
+
if (this.topicPermutations.hasValid(id))
|
|
500
|
+
return this.topicPermutations.getValid(id);
|
|
501
|
+
return id;
|
|
502
|
+
}
|
|
503
|
+
loadCommands(plugin) {
|
|
504
|
+
var _a;
|
|
505
|
+
for (const command of plugin.commands) {
|
|
506
|
+
if (this._commands.has(command.id)) {
|
|
507
|
+
const prioritizedCommand = this.determinePriority([this._commands.get(command.id), command]);
|
|
508
|
+
this._commands.set(prioritizedCommand.id, prioritizedCommand);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
this._commands.set(command.id, command);
|
|
512
|
+
}
|
|
513
|
+
const permutations = this.flexibleTaxonomy ? (0, util_2.getCommandIdPermutations)(command.id) : [command.id];
|
|
514
|
+
for (const permutation of permutations) {
|
|
515
|
+
this.commandPermutations.add(permutation, command.id);
|
|
516
|
+
}
|
|
517
|
+
for (const alias of (_a = command.aliases) !== null && _a !== void 0 ? _a : []) {
|
|
518
|
+
if (this._commands.has(alias)) {
|
|
519
|
+
const prioritizedCommand = this.determinePriority([this._commands.get(alias), command]);
|
|
520
|
+
this._commands.set(prioritizedCommand.id, prioritizedCommand);
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
this._commands.set(alias, command);
|
|
524
|
+
}
|
|
525
|
+
const aliasPermutations = this.flexibleTaxonomy ? (0, util_2.getCommandIdPermutations)(alias) : [alias];
|
|
526
|
+
for (const permutation of aliasPermutations) {
|
|
527
|
+
this.commandPermutations.add(permutation, command.id);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
loadTopics(plugin) {
|
|
533
|
+
for (const topic of (0, util_2.compact)(plugin.topics)) {
|
|
534
|
+
const existing = this._topics.get(topic.name);
|
|
535
|
+
if (existing) {
|
|
536
|
+
existing.description = topic.description || existing.description;
|
|
537
|
+
existing.hidden = existing.hidden || topic.hidden;
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
this._topics.set(topic.name, topic);
|
|
541
|
+
}
|
|
542
|
+
const permutations = this.flexibleTaxonomy ? (0, util_2.getCommandIdPermutations)(topic.name) : [topic.name];
|
|
543
|
+
for (const permutation of permutations) {
|
|
544
|
+
this.topicPermutations.add(permutation, topic.name);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Add missing topics for displaying help when partial commands are entered.
|
|
548
|
+
for (const c of plugin.commands.filter(c => !c.hidden)) {
|
|
549
|
+
const parts = c.id.split(':');
|
|
550
|
+
while (parts.length > 0) {
|
|
551
|
+
const name = parts.join(':');
|
|
552
|
+
if (name && !this._topics.has(name)) {
|
|
553
|
+
this._topics.set(name, { name, description: c.summary || c.description });
|
|
554
|
+
}
|
|
555
|
+
parts.pop();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* This method is responsible for locating the correct plugin to use for a named command id
|
|
561
|
+
* It searches the {Config} registered commands to match either the raw command id or the command alias
|
|
562
|
+
* It is possible that more than one command will be found. This is due the ability of two distinct plugins to
|
|
563
|
+
* create the same command or command alias.
|
|
564
|
+
*
|
|
565
|
+
* In the case of more than one found command, the function will select the command based on the order in which
|
|
566
|
+
* the plugin is included in the package.json `oclif.plugins` list. The command that occurs first in the list
|
|
567
|
+
* is selected as the command to run.
|
|
568
|
+
*
|
|
569
|
+
* Commands can also be present from either an install or a link. When a command is one of these and a core plugin
|
|
570
|
+
* is present, this function defers to the core plugin.
|
|
571
|
+
*
|
|
572
|
+
* If there is not a core plugin command present, this function will return the first
|
|
573
|
+
* plugin as discovered (will not change the order)
|
|
574
|
+
*
|
|
575
|
+
* @param commands commands to determine the priority of
|
|
576
|
+
* @returns command instance {Command.Plugin} or undefined
|
|
577
|
+
*/
|
|
578
|
+
determinePriority(commands) {
|
|
579
|
+
var _a, _b;
|
|
580
|
+
const oclifPlugins = (_b = (_a = this.pjson.oclif) === null || _a === void 0 ? void 0 : _a.plugins) !== null && _b !== void 0 ? _b : [];
|
|
581
|
+
const commandPlugins = commands.sort((a, b) => {
|
|
582
|
+
var _a, _b;
|
|
583
|
+
const pluginAliasA = (_a = a.pluginAlias) !== null && _a !== void 0 ? _a : 'A-Cannot-Find-This';
|
|
584
|
+
const pluginAliasB = (_b = b.pluginAlias) !== null && _b !== void 0 ? _b : 'B-Cannot-Find-This';
|
|
585
|
+
const aIndex = oclifPlugins.indexOf(pluginAliasA);
|
|
586
|
+
const bIndex = oclifPlugins.indexOf(pluginAliasB);
|
|
587
|
+
// When both plugin types are 'core' plugins sort based on index
|
|
588
|
+
if (a.pluginType === 'core' && b.pluginType === 'core') {
|
|
589
|
+
// If b appears first in the pjson.plugins sort it first
|
|
590
|
+
return aIndex - bIndex;
|
|
591
|
+
}
|
|
592
|
+
// if b is a core plugin and a is not sort b first
|
|
593
|
+
if (b.pluginType === 'core' && a.pluginType !== 'core') {
|
|
594
|
+
return 1;
|
|
595
|
+
}
|
|
596
|
+
// if a is a core plugin and b is not sort a first
|
|
597
|
+
if (a.pluginType === 'core' && b.pluginType !== 'core') {
|
|
598
|
+
return -1;
|
|
599
|
+
}
|
|
600
|
+
// neither plugin is core, so do not change the order
|
|
601
|
+
return 0;
|
|
602
|
+
});
|
|
603
|
+
return commandPlugins[0];
|
|
473
604
|
}
|
|
474
605
|
}
|
|
475
606
|
exports.Config = Config;
|
package/lib/config/plugin.js
CHANGED
|
@@ -126,9 +126,10 @@ class Plugin {
|
|
|
126
126
|
}
|
|
127
127
|
this.hooks = (0, util_3.mapValues)(this.pjson.oclif.hooks || {}, i => Array.isArray(i) ? i : [i]);
|
|
128
128
|
this.manifest = await this._manifest(Boolean(this.options.ignoreManifest), Boolean(this.options.errorOnManifestCreate));
|
|
129
|
-
this.commands = Object
|
|
130
|
-
.
|
|
131
|
-
|
|
129
|
+
this.commands = Object
|
|
130
|
+
.entries(this.manifest.commands)
|
|
131
|
+
.map(([id, c]) => ({ ...c, pluginAlias: this.alias, pluginType: this.type, load: async () => this.findCommand(id, { must: true }) }))
|
|
132
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
132
133
|
}
|
|
133
134
|
get topics() {
|
|
134
135
|
return topicsToArray(this.pjson.oclif.topics || {});
|
package/lib/config/util.d.ts
CHANGED
|
@@ -12,3 +12,25 @@ export declare function loadJSON(path: string): Promise<any>;
|
|
|
12
12
|
export declare function compact<T>(a: (T | undefined)[]): T[];
|
|
13
13
|
export declare function uniq<T>(arr: T[]): T[];
|
|
14
14
|
export declare function Debug(...scope: string[]): (..._: any) => void;
|
|
15
|
+
export declare function getPermutations(arr: string[]): Array<string[]>;
|
|
16
|
+
export declare function getCommandIdPermutations(commandId: string): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Return an array of ids that represent all the usable combinations that a user could enter.
|
|
19
|
+
*
|
|
20
|
+
* For example, if the command ids are:
|
|
21
|
+
* - foo:bar:baz
|
|
22
|
+
* - one:two:three
|
|
23
|
+
* Then the usable ids would be:
|
|
24
|
+
* - foo
|
|
25
|
+
* - foo:bar
|
|
26
|
+
* - foo:bar:baz
|
|
27
|
+
* - one
|
|
28
|
+
* - one:two
|
|
29
|
+
* - one:two:three
|
|
30
|
+
*
|
|
31
|
+
* This allows us to determine which parts of the argv array belong to the command id whenever the topicSeparator is a space.
|
|
32
|
+
*
|
|
33
|
+
* @param commandIds string[]
|
|
34
|
+
* @returns string[]
|
|
35
|
+
*/
|
|
36
|
+
export declare function collectUsableIds(commandIds: string[]): string[];
|
package/lib/config/util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Debug = exports.uniq = exports.compact = exports.loadJSON = exports.resolvePackage = exports.exists = exports.mapValues = exports.flatMap = void 0;
|
|
3
|
+
exports.collectUsableIds = exports.getCommandIdPermutations = exports.getPermutations = exports.Debug = exports.uniq = exports.compact = exports.loadJSON = exports.resolvePackage = exports.exists = exports.mapValues = exports.flatMap = void 0;
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const debug = require('debug');
|
|
6
6
|
function flatMap(arr, fn) {
|
|
@@ -46,9 +46,7 @@ function compact(a) {
|
|
|
46
46
|
}
|
|
47
47
|
exports.compact = compact;
|
|
48
48
|
function uniq(arr) {
|
|
49
|
-
return arr.
|
|
50
|
-
return !arr.find((b, j) => j > i && b === a);
|
|
51
|
-
});
|
|
49
|
+
return [...new Set(arr)].sort();
|
|
52
50
|
}
|
|
53
51
|
exports.uniq = uniq;
|
|
54
52
|
function displayWarnings() {
|
|
@@ -69,3 +67,61 @@ function Debug(...scope) {
|
|
|
69
67
|
return (...args) => d(...args);
|
|
70
68
|
}
|
|
71
69
|
exports.Debug = Debug;
|
|
70
|
+
// Adapted from https://github.com/angus-c/just/blob/master/packages/array-permutations/index.js
|
|
71
|
+
function getPermutations(arr) {
|
|
72
|
+
if (arr.length === 0)
|
|
73
|
+
return [];
|
|
74
|
+
if (arr.length === 1)
|
|
75
|
+
return [arr];
|
|
76
|
+
const output = [];
|
|
77
|
+
const partialPermutations = getPermutations(arr.slice(1));
|
|
78
|
+
const first = arr[0];
|
|
79
|
+
for (let i = 0, len = partialPermutations.length; i < len; i++) {
|
|
80
|
+
const partial = partialPermutations[i];
|
|
81
|
+
for (let j = 0, len2 = partial.length; j <= len2; j++) {
|
|
82
|
+
const start = partial.slice(0, j);
|
|
83
|
+
const end = partial.slice(j);
|
|
84
|
+
const merged = start.concat(first, end);
|
|
85
|
+
output.push(merged);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return output;
|
|
89
|
+
}
|
|
90
|
+
exports.getPermutations = getPermutations;
|
|
91
|
+
function getCommandIdPermutations(commandId) {
|
|
92
|
+
return getPermutations(commandId.split(':')).flatMap(c => c.join(':'));
|
|
93
|
+
}
|
|
94
|
+
exports.getCommandIdPermutations = getCommandIdPermutations;
|
|
95
|
+
/**
|
|
96
|
+
* Return an array of ids that represent all the usable combinations that a user could enter.
|
|
97
|
+
*
|
|
98
|
+
* For example, if the command ids are:
|
|
99
|
+
* - foo:bar:baz
|
|
100
|
+
* - one:two:three
|
|
101
|
+
* Then the usable ids would be:
|
|
102
|
+
* - foo
|
|
103
|
+
* - foo:bar
|
|
104
|
+
* - foo:bar:baz
|
|
105
|
+
* - one
|
|
106
|
+
* - one:two
|
|
107
|
+
* - one:two:three
|
|
108
|
+
*
|
|
109
|
+
* This allows us to determine which parts of the argv array belong to the command id whenever the topicSeparator is a space.
|
|
110
|
+
*
|
|
111
|
+
* @param commandIds string[]
|
|
112
|
+
* @returns string[]
|
|
113
|
+
*/
|
|
114
|
+
function collectUsableIds(commandIds) {
|
|
115
|
+
const usuableIds = [];
|
|
116
|
+
for (const id of commandIds) {
|
|
117
|
+
const parts = id.split(':');
|
|
118
|
+
while (parts.length > 0) {
|
|
119
|
+
const name = parts.join(':');
|
|
120
|
+
if (name)
|
|
121
|
+
usuableIds.push(name);
|
|
122
|
+
parts.pop();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return uniq(usuableIds).sort();
|
|
126
|
+
}
|
|
127
|
+
exports.collectUsableIds = collectUsableIds;
|
package/lib/help/index.d.ts
CHANGED
|
@@ -2,8 +2,7 @@ import * as Interfaces from '../interfaces';
|
|
|
2
2
|
import CommandHelp from './command';
|
|
3
3
|
import { HelpFormatter } from './formatter';
|
|
4
4
|
export { CommandHelp } from './command';
|
|
5
|
-
export { standardizeIDFromArgv, loadHelpClass } from './util';
|
|
6
|
-
export declare function getHelpFlagAdditions(config: Interfaces.Config): string[];
|
|
5
|
+
export { standardizeIDFromArgv, loadHelpClass, getHelpFlagAdditions } from './util';
|
|
7
6
|
export declare abstract class HelpBase extends HelpFormatter {
|
|
8
7
|
constructor(config: Interfaces.Config, opts?: Partial<Interfaces.HelpOptions>);
|
|
9
8
|
/**
|
package/lib/help/index.js
CHANGED
|
@@ -15,16 +15,10 @@ Object.defineProperty(exports, "CommandHelp", { enumerable: true, get: function
|
|
|
15
15
|
var util_3 = require("./util");
|
|
16
16
|
Object.defineProperty(exports, "standardizeIDFromArgv", { enumerable: true, get: function () { return util_3.standardizeIDFromArgv; } });
|
|
17
17
|
Object.defineProperty(exports, "loadHelpClass", { enumerable: true, get: function () { return util_3.loadHelpClass; } });
|
|
18
|
-
|
|
19
|
-
function getHelpFlagAdditions(config) {
|
|
20
|
-
var _a;
|
|
21
|
-
const additionalHelpFlags = (_a = config.pjson.oclif.additionalHelpFlags) !== null && _a !== void 0 ? _a : [];
|
|
22
|
-
return [...new Set([...helpFlags, ...additionalHelpFlags]).values()];
|
|
23
|
-
}
|
|
24
|
-
exports.getHelpFlagAdditions = getHelpFlagAdditions;
|
|
18
|
+
Object.defineProperty(exports, "getHelpFlagAdditions", { enumerable: true, get: function () { return util_3.getHelpFlagAdditions; } });
|
|
25
19
|
function getHelpSubject(args, config) {
|
|
26
20
|
// for each help flag that starts with '--' create a new flag with same name sans '--'
|
|
27
|
-
const mergedHelpFlags = getHelpFlagAdditions(config);
|
|
21
|
+
const mergedHelpFlags = (0, util_2.getHelpFlagAdditions)(config);
|
|
28
22
|
for (const arg of args) {
|
|
29
23
|
if (arg === '--')
|
|
30
24
|
return;
|
|
@@ -76,7 +70,8 @@ class Help extends HelpBase {
|
|
|
76
70
|
return topics;
|
|
77
71
|
}
|
|
78
72
|
async showHelp(argv) {
|
|
79
|
-
|
|
73
|
+
const originalArgv = argv.slice(1);
|
|
74
|
+
argv = argv.filter(arg => !(0, util_2.getHelpFlagAdditions)(this.config).includes(arg));
|
|
80
75
|
if (this.config.topicSeparator !== ':')
|
|
81
76
|
argv = (0, util_2.standardizeIDFromArgv)(argv, this.config);
|
|
82
77
|
const subject = getHelpSubject(argv, this.config);
|
|
@@ -105,6 +100,14 @@ class Help extends HelpBase {
|
|
|
105
100
|
await this.showTopicHelp(topic);
|
|
106
101
|
return;
|
|
107
102
|
}
|
|
103
|
+
if (this.config.flexibleTaxonomy) {
|
|
104
|
+
const matches = this.config.findMatches(subject, originalArgv);
|
|
105
|
+
if (matches.length > 0) {
|
|
106
|
+
const result = await this.config.runHook('command_incomplete', { id: subject, argv: originalArgv, matches });
|
|
107
|
+
if (result.successes.length > 0)
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
108
111
|
(0, errors_1.error)(`Command ${subject} not found.`);
|
|
109
112
|
}
|
|
110
113
|
async getDynamicCommand(cmdName) {
|
package/lib/help/util.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ export declare function template(context: any): (t: string) => string;
|
|
|
8
8
|
export declare function toStandardizedId(commandID: string, config: IConfig): string;
|
|
9
9
|
export declare function toConfiguredId(commandID: string, config: IConfig): string;
|
|
10
10
|
export declare function standardizeIDFromArgv(argv: string[], config: IConfig): string[];
|
|
11
|
+
export declare function getHelpFlagAdditions(config: IConfig): string[];
|
|
11
12
|
export {};
|
package/lib/help/util.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.standardizeIDFromArgv = exports.toConfiguredId = exports.toStandardizedId = exports.template = exports.loadHelpClass = void 0;
|
|
3
|
+
exports.getHelpFlagAdditions = exports.standardizeIDFromArgv = exports.toConfiguredId = exports.toStandardizedId = exports.template = exports.loadHelpClass = void 0;
|
|
4
4
|
const ejs = require("ejs");
|
|
5
5
|
const _1 = require(".");
|
|
6
6
|
const module_loader_1 = require("../module-loader");
|
|
7
|
+
const util_1 = require("../config/util");
|
|
7
8
|
function extractClass(exported) {
|
|
8
9
|
return exported && exported.default ? exported.default : exported;
|
|
9
10
|
}
|
|
@@ -32,22 +33,20 @@ exports.template = template;
|
|
|
32
33
|
function collateSpacedCmdIDFromArgs(argv, config) {
|
|
33
34
|
if (argv.length === 1)
|
|
34
35
|
return argv;
|
|
35
|
-
const ids =
|
|
36
|
+
const ids = (0, util_1.collectUsableIds)(config.commandIDs);
|
|
36
37
|
const findId = (argv) => {
|
|
37
38
|
const final = [];
|
|
38
|
-
const idPresent = (id) => ids.
|
|
39
|
+
const idPresent = (id) => ids.includes(id);
|
|
39
40
|
const isFlag = (s) => s.startsWith('-');
|
|
40
41
|
const isArgWithValue = (s) => s.includes('=');
|
|
41
42
|
const finalizeId = (s) => s ? [...final, s].join(':') : final.join(':');
|
|
42
43
|
const hasSubCommandsWithArgs = () => {
|
|
43
44
|
const id = finalizeId();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*/
|
|
50
|
-
const subCommands = config.commands.filter(c => (c.id).startsWith(id) || c.aliases.some(a => a.startsWith(id)));
|
|
45
|
+
if (!id)
|
|
46
|
+
return false;
|
|
47
|
+
// Get a list of sub commands for the current command id. A command is returned as a subcommand if the `id` starts with the current command id.
|
|
48
|
+
// e.g. `foo:bar` is a subcommand of `foo`
|
|
49
|
+
const subCommands = config.commands.filter(c => (c.id).startsWith(id));
|
|
51
50
|
return Boolean(subCommands.find(cmd => { var _a; return cmd.strict === false || ((_a = cmd.args) === null || _a === void 0 ? void 0 : _a.length) > 0; }));
|
|
52
51
|
};
|
|
53
52
|
for (const arg of argv) {
|
|
@@ -88,3 +87,10 @@ function standardizeIDFromArgv(argv, config) {
|
|
|
88
87
|
return argv;
|
|
89
88
|
}
|
|
90
89
|
exports.standardizeIDFromArgv = standardizeIDFromArgv;
|
|
90
|
+
function getHelpFlagAdditions(config) {
|
|
91
|
+
var _a;
|
|
92
|
+
const helpFlags = ['--help'];
|
|
93
|
+
const additionalHelpFlags = (_a = config.pjson.oclif.additionalHelpFlags) !== null && _a !== void 0 ? _a : [];
|
|
94
|
+
return [...new Set([...helpFlags, ...additionalHelpFlags]).values()];
|
|
95
|
+
}
|
|
96
|
+
exports.getHelpFlagAdditions = getHelpFlagAdditions;
|
|
@@ -86,6 +86,7 @@ export interface Config {
|
|
|
86
86
|
plugins: Plugin[];
|
|
87
87
|
binPath?: string;
|
|
88
88
|
valid: boolean;
|
|
89
|
+
flexibleTaxonomy?: boolean;
|
|
89
90
|
topicSeparator: ':' | ' ';
|
|
90
91
|
readonly commands: Command.Plugin[];
|
|
91
92
|
readonly topics: Topic[];
|
|
@@ -93,6 +94,8 @@ export interface Config {
|
|
|
93
94
|
runCommand<T = unknown>(id: string, argv?: string[]): Promise<T>;
|
|
94
95
|
runCommand<T = unknown>(id: string, argv?: string[], cachedCommand?: Command.Plugin): Promise<T>;
|
|
95
96
|
runHook<T extends keyof Hooks>(event: T, opts: Hooks[T]['options'], timeout?: number): Promise<Hook.Result<Hooks[T]['return']>>;
|
|
97
|
+
getAllCommandIDs(): string[];
|
|
98
|
+
getAllCommands(): Command.Plugin[];
|
|
96
99
|
findCommand(id: string, opts: {
|
|
97
100
|
must: true;
|
|
98
101
|
}): Command.Plugin;
|
|
@@ -105,6 +108,7 @@ export interface Config {
|
|
|
105
108
|
findTopic(id: string, opts?: {
|
|
106
109
|
must: boolean;
|
|
107
110
|
}): Topic | undefined;
|
|
111
|
+
findMatches(id: string, argv: string[]): Command.Plugin[];
|
|
108
112
|
scopedEnvVar(key: string): string | undefined;
|
|
109
113
|
scopedEnvVarKey(key: string): string;
|
|
110
114
|
scopedEnvVarTrue(key: string): boolean;
|
|
@@ -50,6 +50,14 @@ export interface Hooks {
|
|
|
50
50
|
};
|
|
51
51
|
return: unknown;
|
|
52
52
|
};
|
|
53
|
+
'command_incomplete': {
|
|
54
|
+
options: {
|
|
55
|
+
id: string;
|
|
56
|
+
argv: string[];
|
|
57
|
+
matches: Command.Plugin[];
|
|
58
|
+
};
|
|
59
|
+
return: unknown;
|
|
60
|
+
};
|
|
53
61
|
'plugins:preinstall': {
|
|
54
62
|
options: {
|
|
55
63
|
plugin: {
|
|
@@ -75,6 +83,7 @@ export declare namespace Hook {
|
|
|
75
83
|
type Preupdate = Hook<'preupdate'>;
|
|
76
84
|
type Update = Hook<'update'>;
|
|
77
85
|
type CommandNotFound = Hook<'command_not_found'>;
|
|
86
|
+
type CommandIncomplete = Hook<'command_incomplete'>;
|
|
78
87
|
interface Context {
|
|
79
88
|
config: Config;
|
|
80
89
|
exit(code?: number): void;
|
|
@@ -16,6 +16,7 @@ export declare namespace PJSON {
|
|
|
16
16
|
schema?: number;
|
|
17
17
|
description?: string;
|
|
18
18
|
topicSeparator?: ':' | ' ';
|
|
19
|
+
flexibleTaxonomy?: boolean;
|
|
19
20
|
hooks?: {
|
|
20
21
|
[name: string]: (string | string[]);
|
|
21
22
|
};
|
|
@@ -78,6 +79,7 @@ export declare namespace PJSON {
|
|
|
78
79
|
npmRegistry?: string;
|
|
79
80
|
scope?: string;
|
|
80
81
|
dirname?: string;
|
|
82
|
+
flexibleTaxonomy?: boolean;
|
|
81
83
|
};
|
|
82
84
|
}
|
|
83
85
|
interface User extends PJSON {
|
package/lib/main.js
CHANGED
|
@@ -62,7 +62,7 @@ async function run(argv = process.argv.slice(2), options) {
|
|
|
62
62
|
// find & run command
|
|
63
63
|
const cmd = config.findCommand(id);
|
|
64
64
|
if (!cmd) {
|
|
65
|
-
const topic = config.findTopic(id);
|
|
65
|
+
const topic = config.flexibleTaxonomy ? null : config.findTopic(id);
|
|
66
66
|
if (topic)
|
|
67
67
|
return config.runCommand('help', [id]);
|
|
68
68
|
if (config.pjson.oclif.default) {
|