{"size": 8038, "ext": "js", "max_stars_repo_path": "src/commands/plugin-manager.command/view.command/view-package.js", "max_stars_repo_name": "CalvinVon/dalao-proxy", "max_stars_repo_stars_event_min_datetime": "2018-11-01T16:40:40.000Z", "max_stars_repo_stars_event_max_datetime": "2022-02-11T07:55:00.000Z", "max_issues_repo_path": "src/commands/plugin-manager.command/view.command/view-package.js", "max_issues_repo_name": "CalvinVon/dalao-proxy", "max_issues_repo_issues_event_min_datetime": "2019-05-06T03:46:34.000Z", "max_issues_repo_issues_event_max_datetime": "2022-02-27T20:18:29.000Z", "max_forks_repo_path": "src/commands/plugin-manager.command/view.command/view-package.js", "max_forks_repo_name": "CalvinVon/dalao-proxy", "max_forks_repo_forks_event_min_datetime": "2019-04-15T01:35:21.000Z", "max_forks_repo_forks_event_max_datetime": "2021-12-24T02:05:19.000Z"}
{"max_stars_count": 38, "max_issues_count": 10, "max_forks_count": 6, "avg_line_length": 27.7172413793, "max_line_length": 114, "alphanum_fraction": 0.5197810401}
const { spawn, execSync } = require('child_process');
const Table = require('cli-table3');
const chalk = require('chalk');
const Spinner = require('cli-spinner').Spinner;
const request = require('request');
const path = require('path');

const { CommandContext } = require('../../../../src');
const { Plugin } = require('../../../plugin');
const { analysisPlugin } = require('../list.command/list-plugin');
const { isDebugMode } = require('../../../utils');

/**
 * Fetch package infomation though `npm`
 * @param {String} packageName
 * @param {Object} options
 * @param {Function} callback
 */
function fetchPackageInfo(packageName, options, callback) {

    if (isDebugMode()) {
        const { indexPath } = Plugin.resolvePluginPaths(packageName);
        callback(null, require(path.join(indexPath, '../package.json')));
    }
    else {
        const npmOptions = [
            'view',
            packageName,
            'name', 'keywords', 'maintainers', 'versions', 'time', 'dist.unpackedSize',
            '--json',
        ];

        if (options.registry) {
            npmOptions.push('--registry', options.registry);
        }

        runNpmCommand(npmOptions, {
            stdio: 'pipe',
            shell: true,
            env: process.env
        }, (err, data) => {
            if (err) return callback(err);

            const detail = JSON.parse(data);
            if (detail.error) {
                return callback(detail.error);
            }

            const latestVersion = detail.versions.pop();
            const size = detail['dist.unpackedSize'];
            callback(null, {
                id: detail.name,
                keywords: detail.keywords,
                latestVersion,
                latestPublish: detail.time[latestVersion],
                size: size ? Number(size / 1000).toFixed(2) + 'kB' : null,
                maintainers: detail.maintainers
            });
        });
    }
};

/**
 * Install package locally but does not cause changes to the `package.json` or `package-lock.json` files
 * @param {String} packageName
 * @param {Object} options
 * @param {Function} callback
 */
function installPkgTemporarily(packageName, options, callback) {
    const npmOptions = [
        'install',
        packageName,
        '-no-save',
        '--no-package-lock',
        '--ignore-scripts',
        '--no-bin-links',
        '--no-optional',
        '--no-audit'
    ];

    if (options.registry) {
        npmOptions.push('--registry', options.registry);
    }

    runNpmCommand(npmOptions, {
        stdio: 'pipe',
        shell: true,
        env: process.env,
        cwd: process.cwd()
    }, (err) => {
        if (err) return callback(err);

        const plugin = new Plugin(packageName, new CommandContext());
        const pluginDetail = analysisPlugin(plugin);

        callback(null, pluginDetail);
    });
};


function removePkg(packageName) {
    execSync('npm uninstall ' + packageName, {
        cwd: process.cwd(),
        env: process.env
    });
}


/**
 * Run npm command wrapper function
 * @param {Array} args npm command args
 * @param {Object} options child_process options
 * @param {Function} callback
 */
function runNpmCommand(args, options, callback) {
    let data = '', errorData = '';

    const cmd = spawn('npm', args, options);
    cmd.stdout.on('data', d => data += d);
    cmd.stdout.on('end', () => {
        callback(null, data);
    })

    cmd.stderr.on('data', d => errorData += d);
    cmd.stderr.on('end', () => {
        console.error(errorData);
        errorData && callback(errorData);
    });

    cmd.on('exit', () => {
        cmd.kill();
    });
}


/**
 * Request plugin information and ouput in a table in the CLI
 * @param {String} packageName
 * @param {Array<Plugin>} installedPlugins
 * @param {Object} options
 */
function displayViewPlugin(packageName, installedPlugins, options) {
    const isInstalled = installedPlugins.filter(plugin => plugin.id === packageName)[0];
    const spinner = new Spinner('Requesting from the npm package registry ... %s');
    spinner.start();
    fetchPackageInfo(packageName, options, (err, packageDetail) => {
        if (err) return exitProgram(err);
        console.clear();

        fetchPkgMonthlyDownloadCount(packageName, (err, count) => {
            spinner.stop(true);
            packageDetail.lastMonDownload = count;

            if (isInstalled) {
                packageDetail.installed = true;
                packageDetail.currentVersion = isInstalled.meta.version;
            }

            displayDetailTable({
                package: packageDetail,
            });

            spinner.setSpinnerTitle('Analyze basic information for plugin installation ... %s');
            spinner.start();

            if (isInstalled) {
                spinner.stop(true);

                displayDetailTable({
                    plugin: analysisPlugin(isInstalled)
                });
                exitProgram();
            }
            else {
                installPkgTemporarily(packageName, options, (err, pluginDetail) => {
                    spinner.stop(true);
                    if (err) return exitProgram(err);


                    displayDetailTable({
                        plugin: pluginDetail
                    });

                    removePkg(packageName);
                    exitProgram();
                });
            }

        });

    });
}

function exitProgram(err) {
    if (err) {
        console.error(chalk.red(err.summary));
    }
    process.exit(0);
}


// Display
function displayDetailTable({ package, plugin }) {
    const tableStyle = {
        chars: {
            'top': '─'
            , 'top-mid': ''
            , 'top-left': ' '
            , 'top-right': ' '
            , 'bottom': ' '
            , 'bottom-mid': ''
            , 'bottom-left': ' '
            , 'bottom-right': ' '
            , 'left': ' '
            , 'left-mid': ' '
            , 'mid': '─'
            , 'mid-mid': ''
            , 'right': ' '
            , 'right-mid': ' '
            , 'middle': ' '
        },
        style: {
            compact: true,
        }
    }

    Array(
        [package, 'Package Infomation'],
        [plugin, 'Installation Information As A Plugin', value => { delete value.instance; delete value.enabled }]
    ).forEach(([data, header, processor = new Function]) => {
        if (data) {
            processor(data);
            console.log(chalk.red('\n  ' + header));
            const packInfoTable = new Table(tableStyle);

            Object.keys(data).forEach(key => {
                let displayString;
                if (key === 'defaultConfig') {
                    displayString = JSON.stringify(data[key], null, 4) || '-';
                }
                else {
                    displayString = format(data[key]);
                }
                packInfoTable.push({
                    [key]: displayString
                });
            });

            console.log(packInfoTable.toString() + '\n');
        }
    });


    function format(value) {
        if (typeof value === 'string') {
            return value || '';
        }
        else if (Array.isArray(value)) {
            return value.join('\n') || '-';
        }
        else if (typeof value === 'object') {
            return Object.keys(value || {}).map(key => `${key}: ${format(value[key])}`).join('\n') + '\n' || '-';
        }
        else {
            return value || '-';
        }
    }
}


/**
 * Fetch monthly download count
 * @param {String} packageName
 * @param {Function} callback
 */
function fetchPkgMonthlyDownloadCount(packageName, callback) {
    request(`https://api.npmjs.org/downloads/point/last-month/${packageName}`, (err, res) => {
        if (err) return callback(err);

        const { error, downloads } = JSON.parse(res.body) || {};
        callback(error, downloads);
    })
}

module.exports = {
    fetchPackageInfo,
    installPkgTemporarily,
    removePkg,
    displayViewPlugin,
};