UNPKG

12.9 kBJavaScriptView Raw
1/* eslint-disable indent, no-process-exit */
2/**
3 * Helper methods for running JSDoc on the command line.
4 *
5 * A few critical notes for anyone who works on this module:
6 *
7 * + The module should really export an instance of `cli`, and `props` should be properties of a
8 * `cli` instance.
9 *
10 * @private
11 */
12module.exports = (() => {
13 const app = require('jsdoc/app');
14 const env = require('jsdoc/env');
15 const logger = require('jsdoc/util/logger');
16 const stripBom = require('jsdoc/util/stripbom');
17 const stripJsonComments = require('strip-json-comments');
18 const Promise = require('bluebird');
19
20 const props = {
21 docs: [],
22 packageJson: null,
23 shouldExitWithError: false,
24 tmpdir: null
25 };
26
27 const FATAL_ERROR_MESSAGE = 'Exiting JSDoc because an error occurred. See the previous log ' +
28 'messages for details.';
29 const cli = {};
30
31 // TODO: docs
32 cli.setVersionInfo = () => {
33 const fs = require('fs');
34 const path = require('path');
35
36 // allow this to throw--something is really wrong if we can't read our own package file
37 const info = JSON.parse( stripBom.strip(fs.readFileSync(path.join(env.dirname, 'package.json'),
38 'utf8')) );
39
40 env.version = {
41 number: info.version,
42 revision: new Date( parseInt(info.revision, 10) ).toUTCString()
43 };
44
45 return cli;
46 };
47
48 // TODO: docs
49 cli.loadConfig = () => {
50 const _ = require('underscore');
51 const args = require('jsdoc/opts/args');
52 const Config = require('jsdoc/config');
53 let config;
54 const fs = require('jsdoc/fs');
55 const path = require('jsdoc/path');
56
57 let confPath;
58 let isFile;
59
60 const defaultOpts = {
61 destination: './out/',
62 encoding: 'utf8'
63 };
64
65 try {
66 env.opts = args.parse(env.args);
67 }
68 catch (e) {
69 console.error(`${e.message}\n`);
70 cli.printHelp().then(() => {
71 cli.exit(1);
72 });
73 }
74
75 confPath = env.opts.configure || path.join(env.dirname, 'conf.json');
76 try {
77 isFile = fs.statSync(confPath).isFile();
78 }
79 catch (e) {
80 isFile = false;
81 }
82
83 if ( !isFile && !env.opts.configure ) {
84 confPath = path.join(env.dirname, 'conf.json.EXAMPLE');
85 }
86
87 try {
88 switch ( path.extname(confPath) ) {
89 case '.js':
90 config = require( path.resolve(confPath) ) || {};
91 break;
92 case '.json':
93 case '.EXAMPLE':
94 default:
95 config = fs.readFileSync(confPath, 'utf8');
96 break;
97 }
98 env.conf = new Config(config).get();
99 }
100 catch (e) {
101 cli.exit(1, `Cannot parse the config file ${confPath}: ${e}\n${FATAL_ERROR_MESSAGE}`);
102 }
103
104 // look for options on the command line, in the config file, and in the defaults, in that order
105 env.opts = _.defaults(env.opts, env.conf.opts, defaultOpts);
106
107 return cli;
108 };
109
110 // TODO: docs
111 cli.configureLogger = () => {
112 function recoverableError() {
113 props.shouldExitWithError = true;
114 }
115
116 function fatalError() {
117 cli.exit(1);
118 }
119
120 if (env.opts.debug) {
121 logger.setLevel(logger.LEVELS.DEBUG);
122 }
123 else if (env.opts.verbose) {
124 logger.setLevel(logger.LEVELS.INFO);
125 }
126
127 if (env.opts.pedantic) {
128 logger.once('logger:warn', recoverableError);
129 logger.once('logger:error', fatalError);
130 }
131 else {
132 logger.once('logger:error', recoverableError);
133 }
134
135 logger.once('logger:fatal', fatalError);
136
137 return cli;
138 };
139
140 // TODO: docs
141 cli.logStart = () => {
142 logger.debug( cli.getVersion() );
143
144 logger.debug('Environment info: %j', {
145 env: {
146 conf: env.conf,
147 opts: env.opts
148 }
149 });
150 };
151
152 // TODO: docs
153 cli.logFinish = () => {
154 let delta;
155 let deltaSeconds;
156
157 if (env.run.finish && env.run.start) {
158 delta = env.run.finish.getTime() - env.run.start.getTime();
159 }
160
161 if (delta !== undefined) {
162 deltaSeconds = (delta / 1000).toFixed(2);
163 logger.info('Finished running in %s seconds.', deltaSeconds);
164 }
165 };
166
167 // TODO: docs
168 cli.runCommand = cb => {
169 let cmd;
170
171 const opts = env.opts;
172
173 if (opts.help) {
174 cmd = cli.printHelp;
175 }
176 else if (opts.test) {
177 cmd = cli.runTests;
178 }
179 else if (opts.version) {
180 cmd = cli.printVersion;
181 }
182 else {
183 cmd = cli.main;
184 }
185
186 cmd().then(errorCode => {
187 if (!errorCode && props.shouldExitWithError) {
188 errorCode = 1;
189 }
190 cb(errorCode);
191 });
192 };
193
194 // TODO: docs
195 cli.printHelp = () => {
196 cli.printVersion();
197 console.log( `\n${require('jsdoc/opts/args').help()}\n` );
198 console.log('Visit https://jsdoc.app/ for more information.');
199
200 return Promise.resolve(0);
201 };
202
203 // TODO: docs
204 cli.runTests = () => {
205 const path = require('jsdoc/path');
206
207 const runner = Promise.promisify(require( path.join(env.dirname, 'test/runner') ));
208
209 console.log('Running tests...');
210
211 return runner();
212 };
213
214 // TODO: docs
215 cli.getVersion = () => `JSDoc ${env.version.number} (${env.version.revision})`;
216
217 // TODO: docs
218 cli.printVersion = () => {
219 console.log( cli.getVersion() );
220
221 return Promise.resolve(0);
222 };
223
224 // TODO: docs
225 cli.main = () => {
226 cli.scanFiles();
227
228 if (env.sourceFiles.length === 0) {
229 console.log('There are no input files to process.');
230
231 return Promise.resolve(0);
232 } else {
233 return cli.createParser()
234 .parseFiles()
235 .processParseResults()
236 .then(() => {
237 env.run.finish = new Date();
238
239 return 0;
240 });
241 }
242 };
243
244 function readPackageJson(filepath) {
245 const fs = require('jsdoc/fs');
246
247 try {
248 return stripJsonComments( fs.readFileSync(filepath, 'utf8') );
249 }
250 catch (e) {
251 logger.error('Unable to read the package file "%s"', filepath);
252
253 return null;
254 }
255 }
256
257 function buildSourceList() {
258 const Readme = require('jsdoc/readme');
259
260 let packageJson;
261 let readmeHtml;
262 let sourceFile;
263 let sourceFiles = env.opts._ ? env.opts._.slice(0) : [];
264
265 if (env.conf.source && env.conf.source.include) {
266 sourceFiles = sourceFiles.concat(env.conf.source.include);
267 }
268
269 // load the user-specified package/README files, if any
270 if (env.opts.package) {
271 packageJson = readPackageJson(env.opts.package);
272 }
273 if (env.opts.readme) {
274 readmeHtml = new Readme(env.opts.readme).html;
275 }
276
277 // source files named `package.json` or `README.md` get special treatment, unless the user
278 // explicitly specified a package and/or README file
279 for (let i = 0, l = sourceFiles.length; i < l; i++) {
280 sourceFile = sourceFiles[i];
281
282 if ( !env.opts.package && /\bpackage\.json$/i.test(sourceFile) ) {
283 packageJson = readPackageJson(sourceFile);
284 sourceFiles.splice(i--, 1);
285 }
286
287 if ( !env.opts.readme && /(\bREADME|\.md)$/i.test(sourceFile) ) {
288 readmeHtml = new Readme(sourceFile).html;
289 sourceFiles.splice(i--, 1);
290 }
291 }
292
293 props.packageJson = packageJson;
294 env.opts.readme = readmeHtml;
295
296 return sourceFiles;
297 }
298
299 // TODO: docs
300 cli.scanFiles = () => {
301 const Filter = require('jsdoc/src/filter').Filter;
302
303 let filter;
304
305 env.opts._ = buildSourceList();
306
307 // are there any files to scan and parse?
308 if (env.conf.source && env.opts._.length) {
309 filter = new Filter(env.conf.source);
310
311 env.sourceFiles = app.jsdoc.scanner.scan(env.opts._,
312 (env.opts.recurse ? env.conf.recurseDepth : undefined), filter);
313 }
314
315 return cli;
316 };
317
318 function resolvePluginPaths(paths) {
319 const path = require('jsdoc/path');
320
321 const pluginPaths = [];
322
323 paths.forEach(plugin => {
324 const basename = path.basename(plugin);
325 const dirname = path.dirname(plugin);
326 const pluginPath = path.getResourcePath(dirname, basename);
327
328 if (!pluginPath) {
329 logger.error('Unable to find the plugin "%s"', plugin);
330
331 return;
332 }
333
334 pluginPaths.push( pluginPath );
335 });
336
337 return pluginPaths;
338 }
339
340 cli.createParser = () => {
341 const handlers = require('jsdoc/src/handlers');
342 const parser = require('jsdoc/src/parser');
343 const plugins = require('jsdoc/plugins');
344
345 app.jsdoc.parser = parser.createParser(env.conf.parser);
346
347 if (env.conf.plugins) {
348 env.conf.plugins = resolvePluginPaths(env.conf.plugins);
349 plugins.installPlugins(env.conf.plugins, app.jsdoc.parser);
350 }
351
352 handlers.attachTo(app.jsdoc.parser);
353
354 return cli;
355 };
356
357 cli.parseFiles = () => {
358 const augment = require('jsdoc/augment');
359 const borrow = require('jsdoc/borrow');
360 const Package = require('jsdoc/package').Package;
361
362 let docs;
363 let packageDocs;
364
365 props.docs = docs = app.jsdoc.parser.parse(env.sourceFiles, env.opts.encoding);
366
367 // If there is no package.json, just create an empty package
368 packageDocs = new Package(props.packageJson);
369 packageDocs.files = env.sourceFiles || [];
370 docs.push(packageDocs);
371
372 logger.debug('Adding inherited symbols, mixins, and interface implementations...');
373 augment.augmentAll(docs);
374 logger.debug('Adding borrowed doclets...');
375 borrow.resolveBorrows(docs);
376 logger.debug('Post-processing complete.');
377
378 app.jsdoc.parser.fireProcessingComplete(docs);
379
380 return cli;
381 };
382
383 cli.processParseResults = () => {
384 if (env.opts.explain) {
385 cli.dumpParseResults();
386
387 return Promise.resolve();
388 }
389 else {
390 cli.resolveTutorials();
391
392 return cli.generateDocs();
393 }
394 };
395
396 cli.dumpParseResults = () => {
397 console.log(require('jsdoc/util/dumper').dump(props.docs));
398
399 return cli;
400 };
401
402 cli.resolveTutorials = () => {
403 const resolver = require('jsdoc/tutorial/resolver');
404
405 if (env.opts.tutorials) {
406 resolver.load(env.opts.tutorials);
407 resolver.resolve();
408 }
409
410 return cli;
411 };
412
413 cli.generateDocs = () => {
414 const path = require('jsdoc/path');
415 const resolver = require('jsdoc/tutorial/resolver');
416 const taffy = require('taffydb').taffy;
417
418 let template;
419
420 env.opts.template = (() => {
421 const publish = env.opts.template || 'templates/default';
422 const templatePath = path.getResourcePath(publish);
423
424 // if we didn't find the template, keep the user-specified value so the error message is
425 // useful
426 return templatePath || env.opts.template;
427 })();
428
429 try {
430 template = require(`${env.opts.template}/publish`);
431 }
432 catch (e) {
433 logger.fatal(`Unable to load template: ${e.message}` || e);
434 }
435
436 // templates should include a publish.js file that exports a "publish" function
437 if (template.publish && typeof template.publish === 'function') {
438 let publishPromise;
439
440 logger.info('Generating output files...');
441 publishPromise = template.publish(
442 taffy(props.docs),
443 env.opts,
444 resolver.root
445 );
446
447 return Promise.resolve(publishPromise);
448 }
449 else {
450 logger.fatal(`${env.opts.template} does not export a "publish" function. Global "publish" functions are no longer supported.`);
451 }
452
453 return Promise.resolve();
454 };
455
456 // TODO: docs
457 cli.exit = (exitCode, message) => {
458 if (exitCode > 0 && message) {
459 console.error(message);
460 }
461 process.on('exit', () => { process.exit(exitCode); });
462 };
463
464 return cli;
465})();