mocha
Version:
Classic, reliable, trusted test framework for Node.js and the browser
146 lines (119 loc) • 4.18 kB
JavaScript
/**
* Contains CLI entry point and public API for programmatic usage in Node.js.
* - Option parsing is handled by {@link module:lib/cli/parse-args}.
* - If executed via `node`, this module will run {@linkcode module:lib/cli.main main()}.
* @public
* @module lib/cli
*/
import debugModule from "debug";
import { fileURLToPath } from "node:url";
import { loadOptions } from "./options.cjs";
import { run, init } from "./commands.js";
import pc from "picocolors";
import { logSymbols } from "../utils.cjs";
import packageJson from "../../package.json" with { type: "json" };
import { getRunOptionDefinitions } from "./run-option-metadata.cjs";
const __filename = fileURLToPath(import.meta.url);
const debug = debugModule("mocha:cli:cli");
const hasFlag = (args, longName, shortName) =>
args.includes(`--${longName}`) || args.includes(`-${shortName}`);
const formatOption = (option) => {
const aliases = option.aliases.map((alias) =>
alias.length === 1 ? `-${alias}` : `--${alias}`,
);
return [`--${option.name}`].concat(aliases).join(", ");
};
const printVersion = () => {
console.log(packageJson.version);
};
const printHelp = () => {
const options = getRunOptionDefinitions()
.filter((option) => option.description)
.map(
(option) => ` ${formatOption(option).padEnd(34)} ${option.description}`,
)
.join("\n");
console.log(`Usage: mocha [spec..]
mocha init <path>
Commands:
init <path> ${init.description}
Options:
${options}
--help, -h${"".padEnd(24)} Show usage information & exit
--version, -V${"".padEnd(21)} Show version number & exit
Mocha Resources
Chat: ${packageJson.discord}
GitHub: ${packageJson.repository.url}
Docs: ${packageJson.homepage}`);
};
const fail = (msg, err) => {
debug("caught error before command handler: %O", err);
printHelp();
console.error(`\n${logSymbols.error} ${pc.red("ERROR:")} ${msg}`);
if (!msg && err) {
// Log raw error and stack when an unexpected error is encountered, to
// make debugging easier (instead of an inactionable "ERROR: null").
console.error(err);
}
process.exit(1);
};
const failRunPreparation = (err) => {
console.error(`\n${logSymbols.error} ${pc.red("ERROR:")}`, err);
process.exit(1);
};
const runCommand = async (args) => {
args.spec = args.spec || (args._ && args._.length ? args._ : ["test"]);
try {
run.normalizeRunOptions(args);
run.validateRunOptions(args);
} catch (err) {
fail(err && err.message, err);
}
try {
await run.prepareRunOptions(args);
} catch (err) {
failRunPreparation(err);
}
return run.handler(args);
};
/**
* - Accepts an `Array` of arguments
* - Modifies {@link https://nodejs.org/api/modules.html#modules_module_paths Node.js' search path} for easy loading of consumer modules
* - Sets {@linkcode https://nodejs.org/api/errors.html#errors_error_stacktracelimit Error.stackTraceLimit} to `Infinity`
* @public
* @summary Mocha's main command-line entry-point.
* @param {string[]} argv - Array of arguments to parse, or by default the lovely `process.argv.slice(2)`
* @param {object} [mochaArgs] - Object of already parsed Mocha arguments (by bin/mocha)
*/
export function main(argv = process.argv.slice(2), mochaArgs) {
debug("entered main with raw args", argv);
try {
Error.stackTraceLimit = Infinity; // configurable via --stack-trace-limit?
} catch (err) {
debug("unable to set Error.stackTraceLimit = Infinity", err);
}
try {
const args = mochaArgs ?? loadOptions(argv);
if (hasFlag(argv, "help", "h") || args.help || args.h) {
printHelp();
process.exit(0);
}
if (hasFlag(argv, "version", "V") || args.version || args.V) {
printVersion();
process.exit(0);
}
const command = (args._ && args._[0]) || argv[0];
if (command === "init") {
const commandArgs =
args._ && args._[0] === "init" ? args._.slice(1) : argv.slice(1);
return init.handler(init.parse(commandArgs));
}
return runCommand(args);
} catch (err) {
fail(err && err.message, err);
}
}
// allow direct execution
if (__filename === process.argv[1]) {
main();
}