#!/usr/bin/env node
/*!
 * Copyright 2019 acrazing <joking.young@gmail.com>. All rights reserved.
 * @since 2019-07-17 18:45:32
 */
import { __awaiter } from "tslib";
import chalk from 'chalk';
import fs from 'fs-extra';
import * as G from 'glob';
// import ora from 'ora';
import path from 'path';
import yargs from 'yargs';
import { parseDependencyTree } from '../parser';
import { defaultOptions, isEmpty, parseCircular, parseWarnings, prettyCircular, prettyTree, prettyWarning, simpleResolver, } from '../utils';
function main() {
    return __awaiter(this, void 0, void 0, function* () {
        const argv = yield yargs
            .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: defaultOptions.extensions.filter(Boolean).join(','),
        })
            .option('js', {
            type: 'string',
            desc: 'comma separated extensions indicate the file is js like',
            default: defaultOptions.js.join(','),
        })
            .option('include', {
            type: 'string',
            desc: 'included filenames regexp in string, default includes all files',
            default: defaultOptions.include.source,
        })
            .option('exclude', {
            type: 'string',
            desc: 'excluded filenames regexp in string, set as empty string to include all files',
            default: 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: 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.terminalWidth(), 100)).argv;
        const files = argv.files;
        if (files.length === 0) {
            yargs.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 = ora('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.relative(context, target);
                    break;
                case 'end':
                    ended += 1;
                    break;
            }
            if (argv.progress) {
                // o.text = `[${ended}/${total}] Analyzing ${current}...`;
                // o.render();
            }
        }
        console.log('current: ', current);
        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,
        };
        console.log('options: ', options, files);
        parseDependencyTree(files, options)
            .then((tree) => __awaiter(this, void 0, void 0, function* () {
            // write tree to file
            fs.writeFile('tree-ts.json', JSON.stringify(tree, null, 2));
            if (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) => simpleResolver(options.context, path.join(options.context, name), options.extensions).then((id) => (id ? path.relative(options.context, id) : name))));
            const circulars = parseCircular(tree, argv.skipDynamicImports === 'circular');
            if (argv.output) {
                yield fs.outputJSON(argv.output, { entries, tree, circulars }, { spaces: 2 });
            }
            if (argv.tree) {
                console.log(chalk.bold('• Dependencies Tree'));
                console.log(prettyTree(tree, entries));
                console.log('');
            }
            if (argv.circular) {
                console.log(chalk.bold[circulars.length === 0 ? 'green' : 'red']('• Circular Dependencies'));
                if (circulars.length === 0) {
                    console.log(chalk.bold.green('  ✅ Congratulations, no circular dependency was found in your project.'));
                }
                else {
                    console.log(prettyCircular(circulars));
                }
                console.log('');
            }
            if (argv.warning) {
                console.log(chalk.bold.yellow('• Warnings'));
                console.log(prettyWarning(parseWarnings(tree)));
                console.log('');
            }
            if (argv.detectUnusedFilesFrom) {
                const allFiles = yield G.glob(argv.detectUnusedFilesFrom);
                const shortAllFiles = allFiles.map((v) => path.relative(context, v));
                const unusedFiles = shortAllFiles.filter((v) => !(v in tree)).sort();
                console.log(chalk.bold.cyan('• Unused files'));
                if (unusedFiles.length === 0) {
                    console.log(chalk.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