UNPKG

dpdm-fast

Version:

Analyze circular dependencies in your JavaScript/TypeScript projects with Rust.

230 lines 9.68 kB
#!/usr/bin/env node "use strict"; /*! * Copyright 2019 acrazing <joking.young@gmail.com>. All rights reserved. * @since 2019-07-17 18:45:32 */ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const chalk_1 = tslib_1.__importDefault(require("chalk")); const fs_extra_1 = tslib_1.__importDefault(require("fs-extra")); const G = tslib_1.__importStar(require("glob")); const ora_1 = tslib_1.__importDefault(require("ora")); const path_1 = tslib_1.__importDefault(require("path")); const yargs_1 = tslib_1.__importDefault(require("yargs")); const parser_1 = require("../parser"); const utils_1 = require("../utils"); function main() { return tslib_1.__awaiter(this, void 0, void 0, function* () { const argv = yield yargs_1.default .strict() .usage('$0 [options] <files...>', "Analyze the files' dependencies.", (yargs) => { return yargs.positional('files', { description: 'The file paths or globs', demandOption: true, type: 'string', array: true, }); }) .option('context', { type: 'string', desc: 'the context directory to shorten path, default is current directory', }) .option('extensions', { alias: 'ext', type: 'string', desc: 'comma separated extensions to resolve', default: utils_1.defaultOptions.extensions.filter(Boolean).join(','), }) .option('js', { type: 'string', desc: 'comma separated extensions indicate the file is js like', default: utils_1.defaultOptions.js.join(','), }) .option('include', { type: 'string', desc: 'included filenames regexp in string, default includes all files', default: utils_1.defaultOptions.include.source, }) .option('exclude', { type: 'string', desc: 'excluded filenames regexp in string, set as empty string to include all files', default: utils_1.defaultOptions.exclude.source, }) .option('output', { alias: 'o', type: 'string', desc: 'output json to file', }) .option('tree', { type: 'boolean', desc: 'print tree to stdout', default: true, }) .option('circular', { type: 'boolean', desc: 'print circular to stdout', default: true, }) .option('warning', { type: 'boolean', desc: 'print warning to stdout', default: true, }) .option('tsconfig', { type: 'string', desc: 'the tsconfig path, which is used for resolve path alias, default is tsconfig.json if it exists in context directory', }) .option('transform', { type: 'boolean', desc: 'transform typescript modules to javascript before analyze, it allows you to omit types dependency in typescript', default: utils_1.defaultOptions.transform, alias: 'T', }) .option('exit-code', { type: 'string', desc: 'exit with specified code, the value format is CASE:CODE, `circular` is the only supported CASE, ' + 'CODE should be a integer between 0 and 128. ' + 'For example: `dpdm --exit-code circular:1` the program will exit with code 1 if circular dependency found.', }) .option('progress', { type: 'boolean', desc: 'show progress bar', default: process.stdout.isTTY && !process.env.CI, }) .option('detect-unused-files-from', { type: 'string', desc: 'this file is a glob, used for finding unused files.', }) .option('skip-dynamic-imports', { type: 'string', choices: ['tree', 'circular'], desc: 'Skip parse import(...) statement.', }) .alias('h', 'help') .wrap(Math.min(yargs_1.default.terminalWidth(), 100)).argv; const files = argv.files; if (files.length === 0) { yargs_1.default.showHelp(); console.log('\nMissing entry file'); process.exit(1); } const exitCases = new Set(['circular']); const exitCodes = []; if (argv['exit-code']) { argv['exit-code'].split(',').forEach((c) => { const [label, code] = c.split(':'); if (!code || !isFinite(+code)) { throw new TypeError(`exit code should be a number`); } if (!exitCases.has(label)) { throw new TypeError(`unsupported exit case "${label}"`); } exitCodes.push([label, +code]); }); } const o = (0, ora_1.default)('Start analyzing dependencies...').start(); let total = 0; let ended = 0; let current = ''; const context = argv.context || process.cwd(); function onProgress(event, target) { switch (event) { case 'start': total += 1; current = path_1.default.relative(context, target); break; case 'end': ended += 1; break; } if (argv.progress) { o.text = `[${ended}/${total}] Analyzing ${current}...`; o.render(); } } const options = { context, extensions: argv.extensions.split(','), js: argv.js.split(','), include: new RegExp(argv.include || '.*'), exclude: new RegExp(argv.exclude || '$.'), tsconfig: argv.tsconfig, transform: argv.transform, skipDynamicImports: argv.skipDynamicImports === 'tree', onProgress, }; (0, parser_1.parseDependencyTree)(files, options) .then((tree) => tslib_1.__awaiter(this, void 0, void 0, function* () { if ((0, utils_1.isEmpty)(tree)) { throw new Error(`No entry files were matched.`); } o.succeed(`[${ended}/${total}] Analyze done!`); const entriesDeep = yield Promise.all(files.map((g) => G.glob(g))); const entries = yield Promise.all(Array() .concat(...entriesDeep) .map((name) => (0, utils_1.simpleResolver)(options.context, path_1.default.join(options.context, name), options.extensions).then((id) => (id ? path_1.default.relative(options.context, id) : name)))); const circulars = (0, utils_1.parseCircular)(tree, argv.skipDynamicImports === 'circular'); if (argv.output) { yield fs_extra_1.default.outputJSON(argv.output, { entries, tree, circulars }, { spaces: 2 }); } if (argv.tree) { console.log(chalk_1.default.bold('• Dependencies Tree')); console.log((0, utils_1.prettyTree)(tree, entries)); console.log(''); } if (argv.circular) { console.log(chalk_1.default.bold[circulars.length === 0 ? 'green' : 'red']('• Circular Dependencies')); if (circulars.length === 0) { console.log(chalk_1.default.bold.green(' ✅ Congratulations, no circular dependency was found in your project.')); } else { console.log((0, utils_1.prettyCircular)(circulars)); } console.log(''); } if (argv.warning) { console.log(chalk_1.default.bold.yellow('• Warnings')); console.log((0, utils_1.prettyWarning)((0, utils_1.parseWarnings)(tree))); console.log(''); } if (argv.detectUnusedFilesFrom) { const allFiles = yield G.glob(argv.detectUnusedFilesFrom); const shortAllFiles = allFiles.map((v) => path_1.default.relative(context, v)); const unusedFiles = shortAllFiles.filter((v) => !(v in tree)).sort(); console.log(chalk_1.default.bold.cyan('• Unused files')); if (unusedFiles.length === 0) { console.log(chalk_1.default.bold.green(' ✅ Congratulations, no unused file was found in your project. (total: ' + allFiles.length + ', used: ' + Object.keys(tree).length + ')')); } else { const len = unusedFiles.length.toString().length; unusedFiles.forEach((f, i) => { console.log('%s) %s', i.toString().padStart(len, '0'), f); }); } } for (const [label, code] of exitCodes) { switch (label) { case 'circular': if (circulars.length > 0) { process.exit(code); } } } })) .catch((e) => { o.fail(); console.error(e.stack || e); process.exit(1); }); }); } main().catch((e) => { console.error(e); process.exit(1); }); //# sourceMappingURL=dpdm.js.map