UNPKG

9.32 kBJavaScriptView Raw
1"use strict";
2// tslint:disable no-implicit-dependencies
3Object.defineProperty(exports, "__esModule", { value: true });
4const command_1 = require("@oclif/command");
5const Config = require("@oclif/config");
6const plugin_help_1 = require("@oclif/plugin-help");
7const fs = require("fs-extra");
8const _ = require("lodash");
9const path = require("path");
10const url_1 = require("url");
11const util_1 = require("../util");
12const normalize = require('normalize-package-data');
13const columns = parseInt(process.env.COLUMNS, 10) || 120;
14function slugify(input) {
15 return _.kebabCase(input.trim().replace(/:/g, '')).replace(/[^a-zA-Z0-9\- ]/g, '');
16}
17class Readme extends command_1.Command {
18 async run() {
19 const { flags } = this.parse(Readme);
20 const config = await Config.load({ root: process.cwd(), devPlugins: false, userPlugins: false });
21 try {
22 // @ts-ignore
23 let p = require.resolve('@oclif/plugin-legacy', { paths: [process.cwd()] });
24 let plugin = new Config.Plugin({ root: p, type: 'core' });
25 await plugin.load();
26 config.plugins.push(plugin);
27 }
28 catch (_a) { }
29 await config.runHook('init', { id: 'readme', argv: this.argv });
30 let readme = await fs.readFile('README.md', 'utf8');
31 let commands = config.commands;
32 commands = commands.filter(c => !c.hidden);
33 commands = commands.filter(c => c.pluginType === 'core');
34 this.debug('commands:', commands.map(c => c.id).length);
35 commands = util_1.uniqBy(commands, c => c.id);
36 commands = util_1.sortBy(commands, c => c.id);
37 readme = this.replaceTag(readme, 'usage', this.usage(config));
38 readme = this.replaceTag(readme, 'commands', flags.multi ? this.multiCommands(config, commands, flags.dir) : this.commands(config, commands));
39 readme = this.replaceTag(readme, 'toc', this.toc(config, readme));
40 readme = readme.trimRight();
41 readme += '\n';
42 await fs.outputFile('README.md', readme);
43 }
44 replaceTag(readme, tag, body) {
45 if (readme.includes(`<!-- ${tag} -->`)) {
46 if (readme.includes(`<!-- ${tag}stop -->`)) {
47 readme = readme.replace(new RegExp(`<!-- ${tag} -->(.|\n)*<!-- ${tag}stop -->`, 'm'), `<!-- ${tag} -->`);
48 }
49 this.log(`replacing <!-- ${tag} --> in README.md`);
50 }
51 return readme.replace(`<!-- ${tag} -->`, `<!-- ${tag} -->\n${body}\n<!-- ${tag}stop -->`);
52 }
53 toc(__, readme) {
54 return readme.split('\n').filter(l => l.startsWith('# '))
55 .map(l => l.slice(2))
56 .map(l => `* [${l}](#${slugify(l)})`)
57 .join('\n');
58 }
59 usage(config) {
60 return [
61 `\`\`\`sh-session
62$ npm install -g ${config.name}
63$ ${config.bin} COMMAND
64running command...
65$ ${config.bin} (-v|--version|version)
66${config.name}/${process.env.OCLIF_NEXT_VERSION || config.version} ${process.platform}-${process.arch} node-v${process.versions.node}
67$ ${config.bin} --help [COMMAND]
68USAGE
69 $ ${config.bin} COMMAND
70...
71\`\`\`\n`,
72 ].join('\n').trim();
73 }
74 multiCommands(config, commands, dir) {
75 let topics = config.topics;
76 topics = topics.filter(t => !t.hidden && !t.name.includes(':'));
77 topics = topics.filter(t => commands.find(c => c.id.startsWith(t.name)));
78 topics = util_1.sortBy(topics, t => t.name);
79 topics = util_1.uniqBy(topics, t => t.name);
80 for (let topic of topics) {
81 this.createTopicFile(path.join('.', dir, topic.name.replace(/:/g, '/') + '.md'), config, topic, commands.filter(c => c.id === topic.name || c.id.startsWith(topic.name + ':')));
82 }
83 return [
84 '# Command Topics\n',
85 ...topics.map(t => {
86 return util_1.compact([
87 `* [\`${config.bin} ${t.name}\`](${dir}/${t.name.replace(/:/g, '/')}.md)`,
88 util_1.template({ config })(t.description || '').trim().split('\n')[0]
89 ]).join(' - ');
90 }),
91 ].join('\n').trim() + '\n';
92 }
93 createTopicFile(file, config, topic, commands) {
94 const bin = `\`${config.bin} ${topic.name}\``;
95 let doc = [
96 bin,
97 '='.repeat(bin.length),
98 '',
99 util_1.template({ config })(topic.description || '').trim(),
100 '',
101 this.commands(config, commands),
102 ].join('\n').trim() + '\n';
103 fs.outputFileSync(file, doc);
104 }
105 commands(config, commands) {
106 return [
107 ...commands.map(c => {
108 let usage = this.commandUsage(c);
109 return `* [\`${config.bin} ${usage}\`](#${slugify(`${config.bin}-${usage}`)})`;
110 }),
111 '',
112 ...commands.map(c => this.renderCommand(config, c)).map(s => s.trim() + '\n'),
113 ].join('\n').trim();
114 }
115 renderCommand(config, c) {
116 this.debug('rendering command', c.id);
117 let title = util_1.template({ config })(c.description || '').trim().split('\n')[0];
118 const help = new plugin_help_1.default(config, { stripAnsi: true, maxWidth: columns });
119 const header = () => `## \`${config.bin} ${this.commandUsage(c)}\``;
120 return util_1.compact([
121 header(),
122 title,
123 '```\n' + help.command(c).trim() + '\n```',
124 this.commandCode(config, c),
125 ]).join('\n\n');
126 }
127 commandCode(config, c) {
128 let pluginName = c.pluginName;
129 if (!pluginName)
130 return;
131 let plugin = config.plugins.find(p => p.name === c.pluginName);
132 if (!plugin)
133 return;
134 const repo = this.repo(plugin);
135 if (!repo)
136 return;
137 let label = plugin.name;
138 let version = plugin.version;
139 let commandPath = this.commandPath(plugin, c);
140 if (!commandPath)
141 return;
142 if (config.name === plugin.name) {
143 label = commandPath;
144 version = process.env.OCLIF_NEXT_VERSION || version;
145 }
146 const template = plugin.pjson.oclif.repositoryPrefix || '<%- repo %>/blob/v<%- version %>/<%- commandPath %>';
147 return `_See code: [${label}](${_.template(template)({ repo, version, commandPath, config, c })})_`;
148 }
149 repo(plugin) {
150 const pjson = Object.assign({}, plugin.pjson);
151 normalize(pjson);
152 let repo = pjson.repository && pjson.repository.url;
153 if (!repo)
154 return;
155 let url = new url_1.URL(repo);
156 if (!['github.com', 'gitlab.com'].includes(url.hostname))
157 return;
158 return `https://${url.hostname}${url.pathname.replace(/\.git$/, '')}`;
159 }
160 /**
161 * fetches the path to a command
162 */
163 commandPath(plugin, c) {
164 let commandsDir = plugin.pjson.oclif.commands;
165 if (!commandsDir)
166 return;
167 let p = path.join(plugin.root, commandsDir, ...c.id.split(':'));
168 const libRegex = new RegExp('^lib' + (path.sep === '\\' ? '\\\\' : path.sep));
169 if (fs.pathExistsSync(path.join(p, 'index.js'))) {
170 p = path.join(p, 'index.js');
171 }
172 else if (fs.pathExistsSync(p + '.js')) {
173 p = p + '.js';
174 }
175 else if (plugin.pjson.devDependencies.typescript) {
176 // check if non-compiled scripts are available
177 let base = p.replace(plugin.root + path.sep, '');
178 p = path.join(plugin.root, base.replace(libRegex, 'src' + path.sep));
179 if (fs.pathExistsSync(path.join(p, 'index.ts'))) {
180 p = path.join(p, 'index.ts');
181 }
182 else if (fs.pathExistsSync(p + '.ts')) {
183 p = p + '.ts';
184 }
185 else
186 return;
187 }
188 else
189 return;
190 p = p.replace(plugin.root + path.sep, '');
191 if (plugin.pjson.devDependencies.typescript) {
192 p = p.replace(libRegex, 'src' + path.sep);
193 p = p.replace(/\.js$/, '.ts');
194 }
195 return p;
196 }
197 commandUsage(command) {
198 const arg = (arg) => {
199 let name = arg.name.toUpperCase();
200 if (arg.required)
201 return `${name}`;
202 return `[${name}]`;
203 };
204 const defaultUsage = () => {
205 // const flags = Object.entries(command.flags)
206 // .filter(([, v]) => !v.hidden)
207 return util_1.compact([
208 command.id,
209 command.args.filter(a => !a.hidden).map(a => arg(a)).join(' '),
210 ]).join(' ');
211 };
212 let usages = util_1.castArray(command.usage);
213 return usages.length === 0 ? defaultUsage() : usages[0];
214 }
215}
216Readme.description = `adds commands to README.md in current directory
217The readme must have any of the following tags inside of it for it to be replaced or else it will do nothing:
218# Usage
219<!-- usage -->
220# Commands
221<!-- commands -->
222
223Customize the code URL prefix by setting oclif.repositoryPrefix in package.json.
224`;
225Readme.flags = {
226 dir: command_1.flags.string({ description: 'output directory for multi docs', default: 'docs', required: true }),
227 multi: command_1.flags.boolean({ description: 'create a different markdown page for each topic' })
228};
229exports.default = Readme;