@jsdoc/cli
Advanced tools
Comparing version 0.3.9 to 0.3.10
@@ -17,86 +17,15 @@ /* | ||
import EventEmitter from 'node:events'; | ||
import { Api, config as jsdocConfig } from '@jsdoc/core'; | ||
import { getLogFunctions } from '@jsdoc/util'; | ||
import _ from 'lodash'; | ||
import ow from 'ow'; | ||
import yargs from 'yargs-parser'; | ||
import flags from './flags.js'; | ||
import { flags, parseFlags } from './flags.js'; | ||
import help from './help.js'; | ||
import { LEVELS, Logger } from './logger.js'; | ||
function validateChoice(flagInfo, choices, values) { | ||
let flagNames = flagInfo.alias ? `-${flagInfo.alias}/` : ''; | ||
const FATAL_ERROR_MESSAGE = | ||
'Exiting JSDoc because an error occurred. See the previous log messages for details.'; | ||
flagNames += `--${flagInfo.name}`; | ||
for (let value of values) { | ||
if (!choices.includes(value)) { | ||
throw new TypeError(`The flag ${flagNames} accepts only these values: ${choices.join(', ')}`); | ||
} | ||
} | ||
} | ||
/** | ||
* `KNOWN_FLAGS` is a set of all known flag names, including the long and short forms. | ||
* | ||
* `YARGS_FLAGS` is details about the known command-line flags, but in a form that `yargs-parser` | ||
* understands. (That form is relatively hard to read, so we build this object from the more | ||
* readable `flags` object.) | ||
* | ||
* @private | ||
*/ | ||
const { KNOWN_FLAGS, YARGS_FLAGS } = (() => { | ||
const names = new Set(); | ||
const opts = { | ||
alias: {}, | ||
array: [], | ||
boolean: [], | ||
coerce: {}, | ||
narg: {}, | ||
normalize: [], | ||
}; | ||
// `_` contains unparsed arguments. | ||
names.add('_'); | ||
Object.keys(flags).forEach((flag) => { | ||
const value = flags[flag]; | ||
names.add(flag); | ||
if (value.alias) { | ||
names.add(value.alias); | ||
opts.alias[flag] = [value.alias]; | ||
} | ||
if (value.array) { | ||
opts.array.push(flag); | ||
} | ||
if (value.boolean) { | ||
opts.boolean.push(flag); | ||
} | ||
if (value.coerce) { | ||
opts.coerce[flag] = value.coerce; | ||
} | ||
if (value.normalize) { | ||
opts.normalize.push(flag); | ||
} | ||
if (value.requiresArg) { | ||
opts.narg[flag] = 1; | ||
} | ||
}); | ||
return { | ||
KNOWN_FLAGS: names, | ||
YARGS_FLAGS: opts, | ||
}; | ||
})(); | ||
/** | ||
* CLI engine for JSDoc. | ||
@@ -133,3 +62,5 @@ * | ||
this.emitter = opts.emitter ?? new EventEmitter(); | ||
this.api = opts.api ?? new Api({ emitter: opts.emitter }); | ||
this.emitter = this.api.emitter; | ||
this.env = this.api.env; | ||
this.flags = []; | ||
@@ -141,13 +72,108 @@ this.log = opts.log ?? getLogFunctions(this.emitter); | ||
}); | ||
// TODO: Make these private when `cli.js` no longer needs them. | ||
this.shouldExitWithError = false; | ||
this.shouldPrintHelp = false; | ||
// Support the format used by `Env`. | ||
// TODO: Make the formats consistent. | ||
if (_.isObject(opts.version)) { | ||
this.env.version = opts.version; | ||
this.version = opts.version.number; | ||
this.revision = new Date(opts.version.revision); | ||
this.env.version.revision = opts.version.revision; | ||
} else { | ||
this.version = opts.version; | ||
this.env.version = {}; | ||
this.version = this.env.version.number = opts.version; | ||
this.revision = opts.revision; | ||
this.env.version.revision = opts.revision?.toUTCString(); | ||
} | ||
} | ||
configureLogger() { | ||
const fatalError = () => { | ||
this.exit(1); | ||
}; | ||
const { LOG_LEVELS } = Engine; | ||
const { options } = this.env; | ||
const recoverableError = () => { | ||
this.shouldExitWithError = true; | ||
}; | ||
if (options.test) { | ||
this.logLevel = LOG_LEVELS.SILENT; | ||
} else { | ||
if (options.debug) { | ||
this.logLevel = LOG_LEVELS.DEBUG; | ||
} else if (options.verbose) { | ||
this.logLevel = LOG_LEVELS.INFO; | ||
} | ||
if (options.pedantic) { | ||
this.emitter.once('logger:warn', recoverableError); | ||
this.emitter.once('logger:error', fatalError); | ||
} else { | ||
this.emitter.once('logger:error', recoverableError); | ||
} | ||
this.emitter.once('logger:fatal', fatalError); | ||
} | ||
} | ||
dumpParseResults(docletStore) { | ||
let doclets; | ||
const { options } = this.env; | ||
if (options.debug || options.verbose) { | ||
doclets = docletStore.allDoclets; | ||
} else { | ||
doclets = docletStore.doclets; | ||
} | ||
console.log(JSON.stringify(Array.from(doclets), null, 2)); | ||
} | ||
exit(exitCode, message) { | ||
ow(exitCode, ow.number); | ||
ow(message, ow.optional.string); | ||
if (exitCode > 0) { | ||
this.shouldExitWithError = true; | ||
process.on('exit', () => { | ||
if (message) { | ||
console.error(message); | ||
} | ||
}); | ||
} | ||
process.on('exit', () => { | ||
if (this.shouldPrintHelp) { | ||
this.printHelp(); | ||
} | ||
// eslint-disable-next-line no-process-exit | ||
process.exit(exitCode); | ||
}); | ||
} | ||
async generate() { | ||
let docletStore; | ||
const { api, env } = this; | ||
await api.findSourceFiles(); | ||
if (env.sourceFiles.length === 0) { | ||
console.log('There are no input files to process.'); | ||
} else { | ||
docletStore = await api.parseSourceFiles(); | ||
if (env.options.explain) { | ||
this.dumpParseResults(docletStore); | ||
} else { | ||
await api.generateDocs(docletStore); | ||
} | ||
} | ||
env.run.finish = new Date(); | ||
return 0; | ||
} | ||
/** | ||
@@ -174,2 +200,3 @@ * Get help text for JSDoc. | ||
// TODO: Add a typedef for this. | ||
/** | ||
@@ -182,3 +209,38 @@ * Details about the command-line flags that JSDoc recognizes. | ||
// TODO: Add details about the directory and filenames that this method looks for. | ||
/** | ||
* Parses command-line flags; loads the JSDoc configuration file; and adds configuration details | ||
* to the JSDoc environment. | ||
* | ||
* For details about supported command-line flags, see the value of the | ||
* {@link module:@jsdoc/cli#knownFlags} property. | ||
* | ||
* @returns {Promise<undefined>} A promise that is fulfilled after the configuration is loaded. | ||
*/ | ||
loadConfig() { | ||
const { env } = this; | ||
try { | ||
env.opts = _.defaults({}, this.parseFlags(env.args), env.opts); | ||
} catch (e) { | ||
this.shouldPrintHelp = true; | ||
this.exit(1, `${e.message}\n`); | ||
return Promise.reject(e); | ||
} | ||
// TODO: Await the promise and use try-catch. | ||
return jsdocConfig.load(env.opts.configure).then( | ||
(conf) => { | ||
env.conf = conf.config; | ||
// Look for options on the command line, then in the config. | ||
env.opts = _.defaults(env.opts, env.conf.opts); | ||
}, | ||
(e) => { | ||
this.exit(1, `Cannot parse the config file: ${e}\n${FATAL_ERROR_MESSAGE}`); | ||
} | ||
); | ||
} | ||
/** | ||
* The log level to use. Messages are logged only if they are at or above this level. | ||
@@ -203,46 +265,22 @@ * Must be an enumerated value of {@link module:@jsdoc/cli.LOG_LEVELS}. | ||
* @param {Array<string>} cliFlags - The command-line flags to parse. | ||
* @returns {Object} The name and value for each flag. The `_` property contains all arguments | ||
* other than flags and flag values. | ||
* @returns {Object<string, *>} The long name and value for each flag. The `_` property contains | ||
* all arguments other than flags and flag values. | ||
*/ | ||
parseFlags(cliFlags) { | ||
ow(cliFlags, ow.array); | ||
this.flags = parseFlags(cliFlags); | ||
let normalizedFlags; | ||
let parsed; | ||
let parsedFlags; | ||
let parsedFlagNames; | ||
return this.flags; | ||
} | ||
normalizedFlags = Object.keys(flags); | ||
parsed = yargs.detailed(cliFlags, YARGS_FLAGS); | ||
if (parsed.error) { | ||
throw parsed.error; | ||
} | ||
parsedFlags = parsed.argv; | ||
parsedFlagNames = new Set(Object.keys(parsedFlags)); | ||
printHelp() { | ||
this.printVersion(); | ||
console.log(this.help({ maxLength: process.stdout.columns })); | ||
// Check all parsed flags for unknown flag names. | ||
for (let flag of parsedFlagNames) { | ||
if (!KNOWN_FLAGS.has(flag)) { | ||
throw new TypeError( | ||
'Unknown command-line option: ' + (flag.length === 1 ? `-${flag}` : `--${flag}`) | ||
); | ||
} | ||
} | ||
return Promise.resolve(0); | ||
} | ||
// Validate the values of known flags. | ||
for (let flag of normalizedFlags) { | ||
if (parsedFlags[flag] && flags[flag].choices) { | ||
let flagInfo = { | ||
name: flag, | ||
alias: flags[flag].alias, | ||
}; | ||
printVersion() { | ||
console.log(this.versionDetails); | ||
validateChoice(flagInfo, flags[flag].choices, parsedFlags[flag]); | ||
} | ||
} | ||
// Only keep the long name of each flag. | ||
this.flags = _.pick(parsedFlags, normalizedFlags.concat(['_'])); | ||
return this.flags; | ||
return Promise.resolve(0); | ||
} | ||
@@ -249,0 +287,0 @@ |
127
lib/flags.js
@@ -19,2 +19,5 @@ /* | ||
import { cast } from '@jsdoc/util'; | ||
import _ from 'lodash'; | ||
import ow from 'ow'; | ||
import yargs from 'yargs-parser'; | ||
@@ -27,3 +30,3 @@ // TODO: Document the format of this object, then update the docs for `Engine`. | ||
*/ | ||
export default { | ||
export const flags = { | ||
access: { | ||
@@ -121,1 +124,123 @@ alias: 'a', | ||
}; | ||
/** | ||
* `KNOWN_FLAGS` is a set of all known flag names, including the long and short forms. | ||
* | ||
* `YARGS_FLAGS` is details about the known command-line flags, but in a form that `yargs-parser` | ||
* understands. (That form is relatively hard to read, so we build this object from the more | ||
* readable `flags` object.) | ||
* | ||
* @private | ||
*/ | ||
const { KNOWN_FLAGS, YARGS_FLAGS } = (() => { | ||
const names = new Set(); | ||
const opts = { | ||
alias: {}, | ||
array: [], | ||
boolean: [], | ||
coerce: {}, | ||
narg: {}, | ||
normalize: [], | ||
}; | ||
// `_` contains unparsed arguments. | ||
names.add('_'); | ||
Object.keys(flags).forEach((flag) => { | ||
const value = flags[flag]; | ||
names.add(flag); | ||
if (value.alias) { | ||
names.add(value.alias); | ||
opts.alias[flag] = [value.alias]; | ||
} | ||
if (value.array) { | ||
opts.array.push(flag); | ||
} | ||
if (value.boolean) { | ||
opts.boolean.push(flag); | ||
} | ||
if (value.coerce) { | ||
opts.coerce[flag] = value.coerce; | ||
} | ||
if (value.normalize) { | ||
opts.normalize.push(flag); | ||
} | ||
if (value.requiresArg) { | ||
opts.narg[flag] = 1; | ||
} | ||
}); | ||
return { | ||
KNOWN_FLAGS: names, | ||
YARGS_FLAGS: opts, | ||
}; | ||
})(); | ||
function validateChoice(flagInfo, choices, values) { | ||
let flagNames = flagInfo.alias ? `-${flagInfo.alias}/` : ''; | ||
flagNames += `--${flagInfo.name}`; | ||
for (let value of values) { | ||
if (!choices.includes(value)) { | ||
throw new TypeError(`The flag ${flagNames} accepts only these values: ${choices.join(', ')}`); | ||
} | ||
} | ||
} | ||
/** | ||
* Parse an array of command-line flags (also known as "options"). | ||
* | ||
* Use the instance's `flags` property to retrieve the parsed flags later. | ||
* | ||
* @param {Array<string>} cliFlags - The command-line flags to parse. | ||
* @returns {Object<string, *>} The long name and value for each flag. The `_` property contains | ||
* all arguments other than flags and flag values. | ||
*/ | ||
export function parseFlags(cliFlags) { | ||
ow(cliFlags, ow.array); | ||
let normalizedFlags; | ||
let parsed; | ||
let parsedFlags; | ||
let parsedFlagNames; | ||
normalizedFlags = Object.keys(flags); | ||
parsed = yargs.detailed(cliFlags, YARGS_FLAGS); | ||
if (parsed.error) { | ||
throw parsed.error; | ||
} | ||
parsedFlags = parsed.argv; | ||
parsedFlagNames = new Set(Object.keys(parsedFlags)); | ||
// Check all parsed flags for unknown flag names. | ||
for (let flag of parsedFlagNames) { | ||
if (!KNOWN_FLAGS.has(flag)) { | ||
throw new TypeError( | ||
'Unknown command-line option: ' + (flag.length === 1 ? `-${flag}` : `--${flag}`) | ||
); | ||
} | ||
} | ||
// Validate the values of known flags. | ||
for (let flag of normalizedFlags) { | ||
if (parsedFlags[flag] && flags[flag].choices) { | ||
let flagInfo = { | ||
name: flag, | ||
alias: flags[flag].alias, | ||
}; | ||
validateChoice(flagInfo, flags[flag].choices, parsedFlags[flag]); | ||
} | ||
} | ||
// Only keep the long name of each flag. | ||
return _.pick(parsedFlags, normalizedFlags.concat(['_'])); | ||
} |
@@ -16,3 +16,3 @@ /* | ||
*/ | ||
import flags from './flags.js'; | ||
import { flags } from './flags.js'; | ||
@@ -19,0 +19,0 @@ function padLeft(str, length) { |
{ | ||
"name": "@jsdoc/cli", | ||
"version": "0.3.9", | ||
"version": "0.3.10", | ||
"description": "Command-line tool for JSDoc.", | ||
@@ -34,4 +34,4 @@ "keywords": [ | ||
"dependencies": { | ||
"@jsdoc/core": "^0.5.7", | ||
"@jsdoc/util": "^0.3.2", | ||
"@jsdoc/core": "^0.5.8", | ||
"@jsdoc/util": "^0.3.3", | ||
"lodash": "^4.17.21", | ||
@@ -45,3 +45,3 @@ "ow": "^1.1.1", | ||
}, | ||
"gitHead": "db20d510665ac4ca9a363c08ea95e4ae88c09c4a" | ||
"gitHead": "7942901ff766e74d5d800ae5c708ab72cf89cefe" | ||
} |
36248
755
Updated@jsdoc/core@^0.5.8
Updated@jsdoc/util@^0.3.3