mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
407 lines
10 KiB
JavaScript
407 lines
10 KiB
JavaScript
/*
|
|
Copyright 2011 the JSDoc Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
/* eslint-disable indent, no-process-exit */
|
|
const _ = require('lodash');
|
|
const { config, Dependencies } = require('@jsdoc/core');
|
|
const { Dictionary } = require('@jsdoc/tag');
|
|
const Engine = require('@jsdoc/cli');
|
|
const { EventBus, log } = require('@jsdoc/util');
|
|
const fs = require('fs');
|
|
const { sync: glob } = require('fast-glob');
|
|
const { Package, resolveBorrows } = require('@jsdoc/doclet');
|
|
const path = require('path');
|
|
const stripBom = require('strip-bom');
|
|
const stripJsonComments = require('strip-json-comments');
|
|
const { taffy } = require('@jsdoc/salty');
|
|
|
|
/**
|
|
* Helper methods for running JSDoc on the command line.
|
|
*
|
|
* @private
|
|
*/
|
|
module.exports = (() => {
|
|
const props = {
|
|
docs: [],
|
|
packageJson: null,
|
|
shouldExitWithError: false,
|
|
shouldPrintHelp: false,
|
|
tmpdir: null,
|
|
};
|
|
|
|
const bus = new EventBus('jsdoc');
|
|
const cli = {};
|
|
const dependencies = new Dependencies();
|
|
const engine = new Engine();
|
|
const FATAL_ERROR_MESSAGE =
|
|
'Exiting JSDoc because an error occurred. See the previous log messages for details.';
|
|
const LOG_LEVELS = Engine.LOG_LEVELS;
|
|
|
|
cli.setEnv = (env) => {
|
|
dependencies.registerValue('env', env);
|
|
|
|
return cli;
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.setVersionInfo = () => {
|
|
const env = dependencies.get('env');
|
|
|
|
const packageJsonPath = path.join(require.main.path, 'package.json');
|
|
// allow this to throw--something is really wrong if we can't read our own package file
|
|
const info = JSON.parse(stripBom(fs.readFileSync(packageJsonPath, 'utf8')));
|
|
const revision = new Date(parseInt(info.revision, 10));
|
|
|
|
env.version = {
|
|
number: info.version,
|
|
revision: revision.toUTCString(),
|
|
};
|
|
|
|
engine.version = env.version.number;
|
|
engine.revision = revision;
|
|
|
|
return cli;
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.loadConfig = () => {
|
|
const env = dependencies.get('env');
|
|
|
|
try {
|
|
env.opts = engine.parseFlags(env.args);
|
|
} catch (e) {
|
|
props.shouldPrintHelp = true;
|
|
cli.exit(1, `${e.message}\n`);
|
|
|
|
return cli;
|
|
}
|
|
|
|
try {
|
|
env.conf = config.loadSync(env.opts.configure).config;
|
|
} catch (e) {
|
|
cli.exit(1, `Cannot parse the config file: ${e}\n${FATAL_ERROR_MESSAGE}`);
|
|
|
|
return cli;
|
|
}
|
|
|
|
// Look for options on the command line, then in the config.
|
|
env.opts = _.defaults(env.opts, env.conf.opts);
|
|
// Now that we're done loading and merging things, register dependencies.
|
|
dependencies.registerValue('config', env.conf);
|
|
dependencies.registerValue('options', env.opts);
|
|
dependencies.registerSingletonFactory('tags', () =>
|
|
Dictionary.fromConfig(dependencies.get('env'))
|
|
);
|
|
|
|
return cli;
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.configureLogger = () => {
|
|
const options = dependencies.get('options');
|
|
|
|
function recoverableError() {
|
|
props.shouldExitWithError = true;
|
|
}
|
|
|
|
function fatalError() {
|
|
cli.exit(1);
|
|
}
|
|
|
|
if (options.test) {
|
|
engine.logLevel = LOG_LEVELS.SILENT;
|
|
} else {
|
|
if (options.debug) {
|
|
engine.logLevel = LOG_LEVELS.DEBUG;
|
|
} else if (options.verbose) {
|
|
engine.logLevel = LOG_LEVELS.INFO;
|
|
}
|
|
|
|
if (options.pedantic) {
|
|
bus.once('logger:warn', recoverableError);
|
|
bus.once('logger:error', fatalError);
|
|
} else {
|
|
bus.once('logger:error', recoverableError);
|
|
}
|
|
|
|
bus.once('logger:fatal', fatalError);
|
|
}
|
|
|
|
return cli;
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.logStart = () => {
|
|
log.debug(engine.versionDetails);
|
|
log.debug('Environment info: %j', {
|
|
env: {
|
|
conf: dependencies.get('config'),
|
|
opts: dependencies.get('options'),
|
|
},
|
|
});
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.logFinish = () => {
|
|
let delta;
|
|
let deltaSeconds;
|
|
const env = dependencies.get('env');
|
|
|
|
if (env.run.finish && env.run.start) {
|
|
delta = env.run.finish.getTime() - env.run.start.getTime();
|
|
deltaSeconds = (delta / 1000).toFixed(2);
|
|
log.info(`Finished running in ${deltaSeconds} seconds.`);
|
|
}
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.runCommand = () => {
|
|
let cmd;
|
|
const options = dependencies.get('options');
|
|
|
|
// If we already need to exit with an error, don't do any more work.
|
|
if (props.shouldExitWithError) {
|
|
cmd = () => Promise.resolve(0);
|
|
} else if (options.help) {
|
|
cmd = cli.printHelp;
|
|
} else if (options.test) {
|
|
cmd = cli.runTests;
|
|
} else if (options.version) {
|
|
cmd = cli.printVersion;
|
|
} else {
|
|
cmd = cli.main;
|
|
}
|
|
|
|
return cmd().then((errorCode) => {
|
|
if (!errorCode && props.shouldExitWithError) {
|
|
errorCode = 1;
|
|
}
|
|
|
|
cli.logFinish();
|
|
cli.exit(errorCode || 0);
|
|
});
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.printHelp = () => {
|
|
cli.printVersion();
|
|
console.log(engine.help({ maxLength: process.stdout.columns }));
|
|
|
|
return Promise.resolve(0);
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.runTests = async () => {
|
|
const result = await require('./test')(dependencies);
|
|
|
|
return result.overallStatus === 'failed' ? 1 : 0;
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.printVersion = () => {
|
|
console.log(engine.versionDetails);
|
|
|
|
return Promise.resolve(0);
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.main = () => {
|
|
const env = dependencies.get('env');
|
|
|
|
cli.scanFiles();
|
|
|
|
if (env.sourceFiles.length === 0) {
|
|
console.log('There are no input files to process.');
|
|
|
|
return Promise.resolve(0);
|
|
} else {
|
|
return cli
|
|
.createParser()
|
|
.parseFiles()
|
|
.processParseResults()
|
|
.then(() => {
|
|
env.run.finish = new Date();
|
|
|
|
return 0;
|
|
});
|
|
}
|
|
};
|
|
|
|
function readPackageJson(filepath) {
|
|
try {
|
|
return stripJsonComments(fs.readFileSync(filepath, 'utf8'));
|
|
} catch (e) {
|
|
log.error(`Unable to read the package file ${filepath}`);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function buildSourceList() {
|
|
const conf = dependencies.get('config');
|
|
const options = dependencies.get('options');
|
|
let packageJson;
|
|
let sourceFiles = options._ ? options._.slice() : [];
|
|
|
|
if (conf.sourceFiles) {
|
|
sourceFiles = sourceFiles.concat(conf.sourceFiles);
|
|
}
|
|
|
|
// load the user-specified package file, if any
|
|
if (options.package) {
|
|
packageJson = readPackageJson(options.package);
|
|
props.packageJson = packageJson;
|
|
}
|
|
|
|
// Resolve the path to the README.
|
|
if (options.readme) {
|
|
options.readme = path.resolve(options.readme);
|
|
}
|
|
|
|
return sourceFiles;
|
|
}
|
|
|
|
// TODO: docs
|
|
cli.scanFiles = () => {
|
|
const env = dependencies.get('env');
|
|
const options = dependencies.get('options');
|
|
|
|
options._ = buildSourceList();
|
|
if (options._.length) {
|
|
env.sourceFiles = glob(options._, {
|
|
absolute: true,
|
|
onlyFiles: true,
|
|
});
|
|
}
|
|
|
|
return cli;
|
|
};
|
|
|
|
cli.createParser = () => {
|
|
const { createParser, handlers } = require('@jsdoc/parse');
|
|
const { plugins } = require('@jsdoc/core');
|
|
|
|
const conf = dependencies.get('config');
|
|
|
|
props.parser = createParser(dependencies);
|
|
|
|
if (conf.plugins) {
|
|
plugins.installPlugins(conf.plugins, props.parser, dependencies);
|
|
}
|
|
|
|
handlers.attachTo(props.parser);
|
|
|
|
return cli;
|
|
};
|
|
|
|
cli.parseFiles = () => {
|
|
const { augmentAll } = require('@jsdoc/doclet').augment;
|
|
|
|
let docs;
|
|
const env = dependencies.get('env');
|
|
const options = dependencies.get('options');
|
|
let packageDocs;
|
|
|
|
props.docs = docs = props.parser.parse(env.sourceFiles, options.encoding);
|
|
|
|
// If there is no package.json, just create an empty package
|
|
packageDocs = new Package(props.packageJson);
|
|
packageDocs.files = env.sourceFiles || [];
|
|
docs.push(packageDocs);
|
|
|
|
log.debug('Adding inherited symbols, mixins, and interface implementations...');
|
|
augmentAll(docs);
|
|
log.debug('Adding borrowed doclets...');
|
|
resolveBorrows(docs);
|
|
log.debug('Post-processing complete.');
|
|
|
|
props.parser.fireProcessingComplete(docs);
|
|
|
|
return cli;
|
|
};
|
|
|
|
cli.processParseResults = () => {
|
|
const options = dependencies.get('options');
|
|
|
|
if (options.explain) {
|
|
cli.dumpParseResults();
|
|
|
|
return Promise.resolve();
|
|
} else {
|
|
return cli.generateDocs();
|
|
}
|
|
};
|
|
|
|
cli.dumpParseResults = () => {
|
|
console.log(JSON.stringify(props.docs, null, 4));
|
|
|
|
return cli;
|
|
};
|
|
|
|
cli.generateDocs = () => {
|
|
let message;
|
|
const options = dependencies.get('options');
|
|
let template;
|
|
|
|
options.template = options.template || path.join(__dirname, 'templates', 'default');
|
|
|
|
try {
|
|
// TODO: Just look for a `publish` function in the specified module, not a `publish.js`
|
|
// file _and_ a `publish` function.
|
|
template = require(`${options.template}/publish`);
|
|
} catch (e) {
|
|
log.fatal(`Unable to load template: ${e.message}` || e);
|
|
}
|
|
|
|
// templates should include a publish.js file that exports a "publish" function
|
|
if (template.publish && typeof template.publish === 'function') {
|
|
let publishPromise;
|
|
|
|
log.info('Generating output files...');
|
|
publishPromise = template.publish(taffy(props.docs), dependencies);
|
|
|
|
return Promise.resolve(publishPromise);
|
|
} else {
|
|
message =
|
|
`${options.template} does not export a "publish" function. ` +
|
|
'Global "publish" functions are no longer supported.';
|
|
log.fatal(message);
|
|
|
|
return Promise.reject(new Error(message));
|
|
}
|
|
};
|
|
|
|
// TODO: docs
|
|
cli.exit = (exitCode, message) => {
|
|
if (exitCode > 0) {
|
|
props.shouldExitWithError = true;
|
|
|
|
if (message) {
|
|
console.error(message);
|
|
}
|
|
}
|
|
|
|
process.on('exit', () => {
|
|
if (props.shouldPrintHelp) {
|
|
cli.printHelp();
|
|
}
|
|
|
|
process.exit(exitCode);
|
|
});
|
|
};
|
|
|
|
return cli;
|
|
})();
|