monocart-coverage-reports
Version:
A code coverage tool to generate native V8 reports or Istanbul reports.
240 lines (187 loc) • 7.14 kB
JavaScript
const path = require('path');
const EC = require('eight-colors');
const { program } = require('commander');
const { foregroundChild } = require('foreground-child');
const MCR = require('./index.js');
const Util = require('./utils/util.js');
const version = require('../package.json').version;
const getRegisterPath = (filename) => {
const rel = Util.relativePath(path.resolve(__dirname, 'register', filename));
if (rel.startsWith('.')) {
return rel;
}
return `./${rel}`;
};
const getInitNodeOptions = async (cliOptions) => {
const nodeOptions = [];
if (process.env.NODE_OPTIONS) {
nodeOptions.push(process.env.NODE_OPTIONS);
}
if (cliOptions.import) {
nodeOptions.push(`--import ${cliOptions.import}`);
// for load mcr.config.ts
await import(cliOptions.import);
} else if (cliOptions.require) {
nodeOptions.push(`--require ${cliOptions.require}`);
// for load mcr.config.ts
await import(cliOptions.require);
}
return nodeOptions;
};
const getPreloadType = (nodeOptions) => {
const hasImport = nodeOptions.find((it) => it.includes('--import'));
if (hasImport) {
return '--import';
}
return '--require';
};
const checkRegisterFeature = () => {
const nv = process.versions.node;
// "module.register" added in Node.js: v20.6.0
// if (Util.cmpVersion(nv, '20.6.0') >= 0) {
// return true;
// }
// but also added in: v18.19.0
const requiredNV = '18.19.0';
if (Util.cmpVersion(nv, requiredNV) < 0) {
Util.logInfo(`The current Node.js version "${nv}" does NOT support "module.register", it requires "${requiredNV}" or higher.`);
return false;
}
// could be < 20.6.0 but just ignore it, please using latest minor version
return true;
};
const loadEnv = (cliOptions) => {
if (!cliOptions.env) {
return;
}
const envFile = cliOptions.env === true ? '.env' : cliOptions.env;
const loadEnvFile = process.loadEnvFile;
if (typeof loadEnvFile === 'function') {
loadEnvFile(envFile);
}
};
const initNodeOptions = async (cliOptions) => {
loadEnv(cliOptions);
const supportRegister = checkRegisterFeature();
if (!supportRegister) {
return;
}
const nodeOptions = await getInitNodeOptions(cliOptions);
// console.log(nodeOptions);
const preloadType = getPreloadType(nodeOptions);
// export source after
if (preloadType === '--import') {
const importPath = getRegisterPath('register.mjs');
nodeOptions.push(`--import ${importPath}`);
} else {
const requirePath = getRegisterPath('register.js');
nodeOptions.push(`--require ${requirePath}`);
}
// console.log(nodeOptions);
const nodeOptionsStr = nodeOptions.join(' ');
Util.logDebug(`node options: ${EC.cyan(nodeOptionsStr)}`);
process.env.NODE_OPTIONS = nodeOptionsStr;
};
const initNodeV8CoverageDir = (coverageOptions) => {
// dir for node v8 coverage
const nodeV8CoverageDir = Util.relativePath(path.resolve(coverageOptions.outputDir, '.v8-coverage'));
process.env.NODE_V8_COVERAGE = nodeV8CoverageDir;
// clean v8 cache before running
Util.rmSync(nodeV8CoverageDir);
// Util.logInfo(`V8 coverage dir: ${EC.cyan(nodeV8CoverageDir)}`);
return nodeV8CoverageDir;
};
const executeCommand = async (command, cliOptions) => {
Util.logInfo(`Execute: ${EC.cyan(command)}`);
// before load config
await initNodeOptions(cliOptions);
// console.log(options);
const coverageReport = MCR(cliOptions);
await coverageReport.loadConfig(cliOptions.config);
coverageReport.cleanCache();
const coverageOptions = coverageReport.options;
const nodeV8CoverageDir = initNodeV8CoverageDir(coverageOptions);
// =========================================
// onStart hook
const onStart = coverageOptions.onStart;
if (typeof onStart === 'function') {
await onStart(coverageReport);
}
// =========================================
const subprocess = foregroundChild(command, {
shell: true
}, async (code, signal) => {
// generate coverage even it is failed. code != 0
// =========================================
// onReady hook before adding coverage data.
// Sometimes, the child process has not yet finished writing the coverage data, and it needs to wait here.
const onReady = coverageOptions.onReady;
if (typeof onReady === 'function') {
await onReady(coverageReport, nodeV8CoverageDir, subprocess);
}
// =========================================
await coverageReport.addFromDir(nodeV8CoverageDir);
await coverageReport.generate();
// remove nodeV8CoverageDir
if (!Util.isDebug()) {
Util.rmSync(nodeV8CoverageDir);
}
return process.exitCode;
});
};
process.on('uncaughtException', function(err) {
Util.logError(`Process uncaughtException: ${err.message}`);
console.log(err.stack);
});
// the -- separator
const argv = [];
const subArgv = [];
let separator = false;
process.argv.forEach((it) => {
if (!separator && it === '--') {
separator = true;
}
if (separator) {
subArgv.push(it);
} else {
argv.push(it);
}
});
program
.name('mcr')
.description('CLI to generate coverage reports')
.version(version, '-v, --version', 'output the current version')
.argument('[command]', 'command to execute')
.allowUnknownOption()
.option('-c, --config <path>', 'custom config file path')
.option('-l, --logging <logging>', 'off, error, info, debug')
.option('-n, --name <name>', 'report name for title')
.option('-r, --reports <name[,name]>', 'coverage reports to use')
.option('-o, --outputDir <dir>', 'output dir for reports')
.option('-i, --inputDir <dir>', 'input dir for merging raw files')
.option('-b, --baseDir <dir>', 'base dir for normalizing path')
.option('-a, --all <dir>', 'include all files from dir')
.option('--entryFilter <pattern>', 'entry url filter')
.option('--sourceFilter <pattern>', 'source path filter')
.option('--filter <pattern>', 'the combined filter')
.option('--outputFile <path>', 'output file for v8 report')
.option('--inline', 'inline html for v8 report')
.option('--assetsPath <path>', 'assets path if not inline')
.option('--lcov', 'generate lcov.info file')
.option('--import <module>', 'preload module at startup')
.option('--require <module>', 'preload module at startup')
.option('--env [path]', 'env file (default: ".env")')
.action((_command, cliOptions) => {
const args = [].concat(program.args).concat(subArgv);
if (args[0] === '--') {
args.shift();
}
const command = args.join(' ').trim();
if (!command) {
program.outputHelp();
return;
}
executeCommand(command, cliOptions);
});
program.parse(argv);