@oclif/plugin-help 2.2.3 → 3.0.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.
@@ -0,0 +1,6 @@
1
+ import { HelpBase } from '.';
2
+ export default class extends HelpBase {
3
+ showHelp(): void;
4
+ showCommandHelp(): void;
5
+ getCommandHelpForReadme(): string;
6
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ // `getHelpClass` tests require an oclif project for testing so
3
+ // it is re-using the setup here to be able to do a lookup for
4
+ // this sample help class file in tests, although it is not needed
5
+ // for @oclif/plugin-help itself.
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const _1 = require(".");
8
+ class default_1 extends _1.HelpBase {
9
+ showHelp() {
10
+ console.log('help');
11
+ }
12
+ showCommandHelp() {
13
+ console.log('command help');
14
+ }
15
+ getCommandHelpForReadme() {
16
+ return 'help for readme';
17
+ }
18
+ }
19
+ exports.default = default_1;
@@ -5,7 +5,8 @@ const __1 = require("..");
5
5
  class HelpCommand extends command_1.Command {
6
6
  async run() {
7
7
  const { flags, argv } = this.parse(HelpCommand);
8
- const help = new __1.default(this.config, { all: flags.all });
8
+ const Help = __1.getHelpClass(this.config);
9
+ const help = new Help(this.config, { all: flags.all });
9
10
  help.showHelp(argv);
10
11
  }
11
12
  }
package/lib/index.d.ts CHANGED
@@ -1,18 +1,45 @@
1
1
  import * as Config from '@oclif/config';
2
+ export { getHelpClass } from './util';
2
3
  export interface HelpOptions {
3
4
  all?: boolean;
4
5
  maxWidth: number;
5
6
  stripAnsi?: boolean;
6
7
  }
7
- export default class Help {
8
- config: Config.IConfig;
9
- opts: HelpOptions;
8
+ export declare abstract class HelpBase {
9
+ constructor(config: Config.IConfig, opts?: Partial<HelpOptions>);
10
+ protected config: Config.IConfig;
11
+ protected opts: HelpOptions;
12
+ /**
13
+ * Show help, used in multi-command CLIs
14
+ * @param args passed into your command, useful for determining which type of help to display
15
+ */
16
+ abstract showHelp(argv: string[]): void;
17
+ /**
18
+ * Show help for an individual command
19
+ * @param command
20
+ * @param topics
21
+ */
22
+ abstract showCommandHelp(command: Config.Command, topics: Config.Topic[]): void;
23
+ }
24
+ export default class Help extends HelpBase {
10
25
  render: (input: string) => string;
26
+ private get _topics();
27
+ protected get sortedCommands(): Config.Command.Plugin[];
28
+ protected get sortedTopics(): Config.Topic[];
11
29
  constructor(config: Config.IConfig, opts?: Partial<HelpOptions>);
12
30
  showHelp(argv: string[]): void;
13
- showCommandHelp(command: Config.Command, topics: Config.Topic[]): void;
14
- root(): string;
15
- topic(topic: Config.Topic): string;
16
- command(command: Config.Command): string;
17
- topics(topics: Config.Topic[]): string | undefined;
31
+ showCommandHelp(command: Config.Command): void;
32
+ protected showRootHelp(): void;
33
+ protected showTopicHelp(topic: Config.Topic): void;
34
+ protected formatRoot(): string;
35
+ protected formatCommand(command: Config.Command): string;
36
+ protected formatCommands(commands: Config.Command[]): string;
37
+ protected formatTopic(topic: Config.Topic): string;
38
+ protected formatTopics(topics: Config.Topic[]): string;
39
+ /**
40
+ * @deprecated used for readme generation
41
+ * @param {object} command The command to generate readme help for
42
+ * @return {string} the readme help string for the given command
43
+ */
44
+ protected command(command: Config.Command): string;
18
45
  }
package/lib/index.js CHANGED
@@ -9,12 +9,11 @@ const list_1 = require("./list");
9
9
  const root_1 = require("./root");
10
10
  const screen_1 = require("./screen");
11
11
  const util_1 = require("./util");
12
+ var util_2 = require("./util");
13
+ exports.getHelpClass = util_2.getHelpClass;
12
14
  const wrap = require('wrap-ansi');
13
15
  const { bold, } = chalk_1.default;
14
16
  function getHelpSubject(args) {
15
- // special case
16
- // if (['help:help', 'help:--help', '--help:help'].includes(argv.slice(0, 2).join(':'))) {
17
- // if (argv[0] === 'help') return 'help'
18
17
  for (const arg of args) {
19
18
  if (arg === '--')
20
19
  return;
@@ -25,71 +24,140 @@ function getHelpSubject(args) {
25
24
  return arg;
26
25
  }
27
26
  }
28
- class Help {
27
+ class HelpBase {
29
28
  constructor(config, opts = {}) {
30
29
  this.config = config;
31
30
  this.opts = Object.assign({ maxWidth: screen_1.stdtermwidth }, opts);
31
+ }
32
+ }
33
+ exports.HelpBase = HelpBase;
34
+ class Help extends HelpBase {
35
+ constructor(config, opts = {}) {
36
+ super(config, opts);
32
37
  this.render = util_1.template(this);
33
38
  }
34
- showHelp(argv) {
35
- let topics = this.config.topics;
39
+ /*
40
+ * _topics is to work around Config.topics mistakenly including commands that do
41
+ * not have children, as well as topics. A topic has children, either commands or other topics. When
42
+ * this is fixed upstream config.topics should return *only* topics with children,
43
+ * and this can be removed.
44
+ */
45
+ get _topics() {
46
+ return this.config.topics.filter((topic) => {
47
+ // it is assumed a topic has a child if it has children
48
+ const hasChild = this.config.topics.some(subTopic => subTopic.name.includes(`${topic.name}:`));
49
+ return hasChild;
50
+ });
51
+ }
52
+ get sortedCommands() {
53
+ let commands = this.config.commands;
54
+ commands = commands.filter(c => this.opts.all || !c.hidden);
55
+ commands = util_1.sortBy(commands, c => c.id);
56
+ commands = util_1.uniqBy(commands, c => c.id);
57
+ return commands;
58
+ }
59
+ get sortedTopics() {
60
+ let topics = this._topics;
36
61
  topics = topics.filter(t => this.opts.all || !t.hidden);
37
62
  topics = util_1.sortBy(topics, t => t.name);
38
63
  topics = util_1.uniqBy(topics, t => t.name);
64
+ return topics;
65
+ }
66
+ showHelp(argv) {
39
67
  const subject = getHelpSubject(argv);
40
- let command;
41
- if (subject) {
42
- command = this.config.findCommand(subject);
43
- }
44
- let topic;
45
- if (subject && !command) {
46
- topic = this.config.findTopic(subject);
47
- }
48
68
  if (!subject) {
49
- console.log(this.root());
50
- console.log('');
51
- if (!this.opts.all) {
52
- topics = topics.filter(t => !t.name.includes(':'));
53
- }
54
- console.log(this.topics(topics));
55
- console.log('');
56
- }
57
- else if (command) {
58
- this.showCommandHelp(command, topics);
69
+ this.showRootHelp();
70
+ return;
59
71
  }
60
- else if (topic) {
61
- const name = topic.name;
62
- const depth = name.split(':').length;
63
- topics = topics.filter(t => t.name.startsWith(name + ':') && t.name.split(':').length === depth + 1);
64
- console.log(this.topic(topic));
65
- if (topics.length > 0) {
66
- console.log(this.topics(topics));
67
- console.log('');
68
- }
72
+ const command = this.config.findCommand(subject);
73
+ if (command) {
74
+ this.showCommandHelp(command);
75
+ return;
69
76
  }
70
- else {
71
- errors_1.error(`command ${subject} not found`);
77
+ const topic = this.config.findTopic(subject);
78
+ if (topic) {
79
+ this.showTopicHelp(topic);
80
+ return;
72
81
  }
82
+ errors_1.error(`command ${subject} not found`);
73
83
  }
74
- showCommandHelp(command, topics) {
84
+ showCommandHelp(command) {
75
85
  const name = command.id;
76
86
  const depth = name.split(':').length;
77
- topics = topics.filter(t => t.name.startsWith(name + ':') && t.name.split(':').length === depth + 1);
87
+ const subTopics = this.sortedTopics.filter(t => t.name.startsWith(name + ':') && t.name.split(':').length === depth + 1);
88
+ const subCommands = this.sortedCommands.filter(c => c.id.startsWith(name + ':') && c.id.split(':').length === depth + 1);
78
89
  const title = command.description && this.render(command.description).split('\n')[0];
79
90
  if (title)
80
91
  console.log(title + '\n');
81
- console.log(this.command(command));
92
+ console.log(this.formatCommand(command));
82
93
  console.log('');
83
- if (topics.length > 0) {
84
- console.log(this.topics(topics));
94
+ if (subTopics.length > 0) {
95
+ console.log(this.formatTopics(subTopics));
96
+ console.log('');
97
+ }
98
+ if (subCommands.length > 0) {
99
+ console.log(this.formatCommands(subCommands));
100
+ console.log('');
101
+ }
102
+ }
103
+ showRootHelp() {
104
+ let rootTopics = this.sortedTopics;
105
+ let rootCommands = this.sortedCommands;
106
+ console.log(this.formatRoot());
107
+ console.log('');
108
+ if (!this.opts.all) {
109
+ rootTopics = rootTopics.filter(t => !t.name.includes(':'));
110
+ rootCommands = rootCommands.filter(c => !c.id.includes(':'));
111
+ }
112
+ if (rootTopics.length > 0) {
113
+ console.log(this.formatTopics(rootTopics));
114
+ console.log('');
115
+ }
116
+ if (rootCommands.length > 0) {
117
+ console.log(this.formatCommands(rootCommands));
118
+ console.log('');
119
+ }
120
+ }
121
+ showTopicHelp(topic) {
122
+ const name = topic.name;
123
+ const depth = name.split(':').length;
124
+ const subTopics = this.sortedTopics.filter(t => t.name.startsWith(name + ':') && t.name.split(':').length === depth + 1);
125
+ const commands = this.sortedCommands.filter(c => c.id.startsWith(name + ':') && c.id.split(':').length === depth + 1);
126
+ console.log(this.formatTopic(topic));
127
+ if (subTopics.length > 0) {
128
+ console.log(this.formatTopics(subTopics));
129
+ console.log('');
130
+ }
131
+ if (commands.length > 0) {
132
+ console.log(this.formatCommands(commands));
85
133
  console.log('');
86
134
  }
87
135
  }
88
- root() {
136
+ formatRoot() {
89
137
  const help = new root_1.default(this.config, this.opts);
90
138
  return help.root();
91
139
  }
92
- topic(topic) {
140
+ formatCommand(command) {
141
+ const help = new command_1.default(command, this.config, this.opts);
142
+ return help.generate();
143
+ }
144
+ formatCommands(commands) {
145
+ if (commands.length === 0)
146
+ return '';
147
+ const body = list_1.renderList(commands.map(c => [
148
+ c.id,
149
+ c.description && this.render(c.description.split('\n')[0]),
150
+ ]), {
151
+ spacer: '\n',
152
+ stripAnsi: this.opts.stripAnsi,
153
+ maxWidth: this.opts.maxWidth - 2,
154
+ });
155
+ return [
156
+ bold('COMMANDS'),
157
+ indent(body, 2),
158
+ ].join('\n');
159
+ }
160
+ formatTopic(topic) {
93
161
  let description = this.render(topic.description || '');
94
162
  const title = description.split('\n')[0];
95
163
  description = description.split('\n').slice(1).join('\n');
@@ -108,13 +176,9 @@ class Help {
108
176
  output = stripAnsi(output);
109
177
  return output + '\n';
110
178
  }
111
- command(command) {
112
- const help = new command_1.default(command, this.config, this.opts);
113
- return help.generate();
114
- }
115
- topics(topics) {
179
+ formatTopics(topics) {
116
180
  if (topics.length === 0)
117
- return;
181
+ return '';
118
182
  const body = list_1.renderList(topics.map(c => [
119
183
  c.name,
120
184
  c.description && this.render(c.description.split('\n')[0]),
@@ -124,12 +188,17 @@ class Help {
124
188
  maxWidth: this.opts.maxWidth - 2,
125
189
  });
126
190
  return [
127
- bold('COMMANDS'),
191
+ bold('TOPICS'),
128
192
  indent(body, 2),
129
193
  ].join('\n');
130
194
  }
195
+ /**
196
+ * @deprecated used for readme generation
197
+ * @param {object} command The command to generate readme help for
198
+ * @return {string} the readme help string for the given command
199
+ */
200
+ command(command) {
201
+ return this.formatCommand(command);
202
+ }
131
203
  }
132
204
  exports.default = Help;
133
- // function id(c: Config.Command | Config.Topic): string {
134
- // return (c as any).id || (c as any).name
135
- // }
package/lib/util.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { IConfig } from '@oclif/config';
2
+ import { HelpBase, HelpOptions } from '.';
1
3
  export declare function uniqBy<T>(arr: T[], fn: (cur: T) => any): T[];
2
4
  export declare function compact<T>(a: (T | undefined)[]): T[];
3
5
  export declare function castArray<T>(input?: T | T[]): T[];
@@ -6,3 +8,8 @@ export declare namespace sort {
6
8
  type Types = string | number | undefined | boolean;
7
9
  }
8
10
  export declare function template(context: any): (t: string) => string;
11
+ interface HelpBaseDerived {
12
+ new (config: IConfig, opts?: Partial<HelpOptions>): HelpBase;
13
+ }
14
+ export declare function getHelpClass(config: IConfig, defaultClass?: string): HelpBaseDerived;
15
+ export {};
package/lib/util.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ts_node_1 = require("@oclif/config/lib/ts-node");
3
4
  const lodashTemplate = require("lodash.template");
4
5
  function uniqBy(arr, fn) {
5
6
  return arr.filter((a, i) => {
@@ -46,3 +47,32 @@ function template(context) {
46
47
  return render;
47
48
  }
48
49
  exports.template = template;
50
+ function extractExport(config, classPath) {
51
+ const helpClassPath = ts_node_1.tsPath(config.root, classPath);
52
+ return require(helpClassPath);
53
+ }
54
+ function extractClass(exported) {
55
+ return exported && exported.default ? exported.default : exported;
56
+ }
57
+ function getHelpClass(config, defaultClass = '@oclif/plugin-help') {
58
+ const pjson = config.pjson;
59
+ const configuredClass = pjson && pjson.oclif && pjson.oclif.helpClass;
60
+ if (configuredClass) {
61
+ try {
62
+ const exported = extractExport(config, configuredClass);
63
+ return extractClass(exported);
64
+ }
65
+ catch (error) {
66
+ throw new Error(`Unable to load configured help class "${configuredClass}", failed with message:\n${error.message}`);
67
+ }
68
+ }
69
+ try {
70
+ const defaultModulePath = require.resolve(defaultClass, { paths: [config.root] });
71
+ const exported = require(defaultModulePath);
72
+ return extractClass(exported);
73
+ }
74
+ catch (error) {
75
+ throw new Error(`Could not load a help class, consider installing the @oclif/plugin-help package, failed with message:\n${error.message}`);
76
+ }
77
+ }
78
+ exports.getHelpClass = getHelpClass;
@@ -1 +1 @@
1
- {"version":"2.2.3","commands":{"help":{"id":"help","description":"display help for <%= config.bin %>","pluginName":"@oclif/plugin-help","pluginType":"core","aliases":[],"flags":{"all":{"name":"all","type":"boolean","description":"see all commands in CLI","allowNo":false}},"args":[{"name":"command","description":"command to show help for","required":false}]}}}
1
+ {"version":"3.0.0","commands":{"help":{"id":"help","description":"display help for <%= config.bin %>","pluginName":"@oclif/plugin-help","pluginType":"core","aliases":[],"flags":{"all":{"name":"all","type":"boolean","description":"see all commands in CLI","allowNo":false}},"args":[{"name":"command","description":"command to show help for","required":false}]}}}
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@oclif/plugin-help",
3
3
  "description": "standard help for oclif",
4
- "version": "2.2.3",
4
+ "version": "3.0.0",
5
5
  "author": "Jeff Dickey @jdxcode",
6
6
  "bugs": "https://github.com/oclif/plugin-help/issues",
7
7
  "dependencies": {
8
- "@oclif/command": "^1.5.13",
8
+ "@oclif/command": "^1.5.20",
9
+ "@oclif/config": "^1.15.1",
9
10
  "chalk": "^2.4.1",
10
11
  "indent-string": "^4.0.0",
11
12
  "lodash.template": "^4.4.0",
@@ -15,7 +16,6 @@
15
16
  "wrap-ansi": "^4.0.0"
16
17
  },
17
18
  "devDependencies": {
18
- "@oclif/config": "^1.13.0",
19
19
  "@oclif/dev-cli": "^1.21.0",
20
20
  "@oclif/errors": "^1.2.2",
21
21
  "@oclif/plugin-legacy": "^1.1.3",
@@ -33,8 +33,9 @@
33
33
  "eslint-config-oclif-typescript": "^0.1.0",
34
34
  "globby": "^9.0.0",
35
35
  "mocha": "^5.2.0",
36
- "ts-node": "^8.0.2",
37
- "typescript": "^3.7.2"
36
+ "sinon": "^9.0.1",
37
+ "ts-node": "^8.8.2",
38
+ "typescript": "^3.8.3"
38
39
  },
39
40
  "engines": {
40
41
  "node": ">=8.0.0"