TOTVSTEC/cloudbridge-cli

View on GitHub
src/cli.js

Summary

Maintainability
D
2 days
Test Coverage
'use strict';

var Cli = module.exports,
    CliHelp = cb_require('help'),
    path = require('path'),
    fs = require('fs'),
    optimist = require('optimist'),
    Q = require('q'),
    Info = cb_require('utils/info'),
    settings = require(__basedir + '/package.json'),
    TASKS = require('./tasks/task-list'),
    Updates = cb_require('utils/updates');

Cli.utils = cb_require('utils/utils');
Cli.logging = cb_require('utils/logging');

// The main entry point for the CLI
// This takes the process.argv array for arguments
// The args passed should be unfiltered.
// From here, we will filter the options/args out
// using optimist. Each command is responsible for
// parsing out the args/options in the way they need.
// This way, we can still test with passing in arguments.
Cli.run = function run(processArgv, processCwd) {
    this.cwd = processCwd || process.cwd();

    try {
        //First we parse out the args to use them.
        //Later, we will fetch the command they are trying to
        //execute, grab those options, and reparse the arguments.
        var argv = optimist(processArgv.slice(2)).argv;

        if (argv.chronometer)
            Cli.startChronometer();

        this.logging.setUpConsoleLoggingHelpers();
        Cli.attachErrorHandling();

        //Updates.checkLatestVersion();
        if (process.env.NODE_ENV !== 'test') {
            process.once('exit', function() {
                Cli.printVersionWarning();
            });
        }

        //Before taking any more steps, lets check their
        //environment and display any upgrade warnings
        Updates.doRuntimeCheck(settings.version);

        if ((argv.version || argv.v) && (!argv._.length)) {
            return Cli.version();
        }

        if (argv.verbose) {
            this.logging.logger.level = 'debug';
        }

        if (argv.help || argv.h) {
            return CliHelp.printHelpLines();
        }

        var taskSetting = Cli.tryBuildingTask(argv);
        if (!taskSetting) {
            return CliHelp.printAvailableTasks();
        }

        var booleanOptions = Cli.getBooleanOptionsForTask(taskSetting);

        argv = optimist(processArgv.slice(2)).boolean(booleanOptions).argv;

        var engine = Cli.getEngine(argv);
        var TaskModule = Cli.require_task(taskSetting.name, engine);
        var taskInstance = new TaskModule({ target: this.cwd });
        var promise = taskInstance.prepare()
            .then(() => taskInstance.run(Cli, argv));

        promise
            .catch(function(ex) {
                console.error(ex);
            })
            .done(function onFulfilled() {
                if (argv.chronometer)
                    Cli.endChronometer();

                Cli.processExit();
            },
            function onRejected() {
                console.log('Fail!');
                console.log(arguments);

                Cli.processExit(1);
            }/*,
            function onProgress(output) {
                if (output.stdout)
                    console.log(output.stdout.toString());

                if (output.stderr)
                    console.error(output.stderr.toString());
            }*/);

        return promise;
    }
    catch (ex) {
        this.logging.logger.debug('Cli.Run - Error', ex);
        return this.utils.fail(ex);
    }
};

Cli.startChronometer = function startChronometer() {
    Cli.chronometer = process.hrtime();
};

Cli.endChronometer = function endChronometer() {
    let diff = process.hrtime(Cli.chronometer),
        elapsed = (diff[0] + (diff[1] / 1e9));

    console.log('Chronometer: ' + elapsed.toFixed(3) + 'secs');
};

Cli.getBooleanOptionsForTask = function getBooleanOptionsForTask(task) {
    var availableTaskOptions = task.options;
    var booleanOptions = [];

    if (availableTaskOptions) {
        for (var key in availableTaskOptions) {
            if (typeof availableTaskOptions[key] == 'string') {
                continue;
            }
            var optionWithPipe = key;
            var optionsSplit = optionWithPipe.split('|');
            booleanOptions.push(optionsSplit[0].substring(2));
            if (optionsSplit.length == 2) {
                booleanOptions.push(optionsSplit[1].substring(1));
            }
        }
    }

    return booleanOptions;
};

Cli.require_task = function require_task(module, engine) {
    engine = engine || 'default';

    try {
        var taskModule = require('./tasks/' + engine + '/' + module);
        return taskModule;
    }
    catch (ex) {
        throw ex;
    }
};

Cli.printVersionWarning = function printVersionWarning() {
    if (Cli.npmVersion && Cli.npmVersion !== settings.version.trim()) {
        process.stdout.write('\n------------------------------------\n'.red);
        process.stdout.write('CloudBridge CLI is out of date:\n'.bold.yellow);
        process.stdout.write((' * Locally installed version: ' + settings.version + '\n').yellow);
        process.stdout.write((' * Latest version: ' + Cli.npmVersion + '\n').yellow);
        process.stdout.write((' * https://github.com/totvstec/cloudbridge-cli/blob/master/CHANGELOG.md\n').yellow);
        process.stdout.write(' * Run '.yellow + 'npm install -g cloudbridge'.bold + ' to update\n'.yellow);
        process.stdout.write('------------------------------------\n\n'.red);
        Cli.npmVersion = null;
    }
};



Cli.tryBuildingTask = function tryBuildingTask(argv) {
    if (argv._.length === 0) {
        return false;
    }
    var taskName = argv._[0];

    return TASKS.getTaskWithName(taskName);
};



Cli.printCloudBridge = function printCloudBridge() {
    var w = function(s) {
        process.stdout.write(s.bold);
    };

    w("   ________                ______       _     __\n");
    w("  / ____/ /___  __  ______/ / __ )_____(_)___/ /___ ____ \n");
    w(" / /   / / __ \\/ / / / __  / __  / ___/ / __  / __ `/ _ \\\n");
    w("/ /___/ / /_/ / /_/ / /_/ / /_/ / /  / / /_/ / /_/ /  __/\n");
    w("\\____/_/\\____/\\__,_/\\__,_/_____/_/  /_/\\__,_/\\__, /\\___/ \n");
    w("                                            /____/\n");
    w("By TOTVS                                          v" + settings.version + "\n");
};

Cli.printAvailableTasks = function printAvailableTasks(argv) {
    Cli.printCloudBridge();
    process.stderr.write('\nUsage: cloudbridge task args\n\n=======================\n\n');

    if (process.argv.length > 2) {
        process.stderr.write((process.argv[2] + ' is not a valid task\n\n').bold.red);
    }

    process.stderr.write('Available tasks: '.bold);
    process.stderr.write('(use --help or -h for more info)\n\n');

    for (var i = 0; i < TASKS.length; i++) {
        var task = TASKS[i];
        if (task.summary) {
            var name = '   ' + task.name + '  ';
            var dots = '';
            while ((name + dots).length < 20) {
                dots += '.';
            }
            process.stderr.write(name.green.bold + dots.grey + '  ' + task.summary.bold + '\n');
        }
    }

    process.stderr.write('\n');
    Cli.processExit();

    return Q();
};

Cli.processExit = function processExit(code) {
    if (process.env.NODE_ENV === 'test')
        return;

    process.exit(code);

    /*
    if (Cli.cliNews && Cli.cliNews.promise) {
        Q.all([Cli.latestVersion.promise, Cli.cliNews.promise])
            .then(function() {
                process.exit(code);
            });
    }
    else {
        Cli.latestVersion.promise.then(function() {
            process.exit(code);
        });
    }
    */
};

Cli.version = function version() {
    console.log(settings.version + '\n');
};

//A little why on this reportExtras here -
//We need to access the CLI's package.json file
//for that, we need the path to be relative to this,
//not the node_module/cloudbridge-app-lib directory
Cli.reportExtras = function reportExtras(err) {
    var commandLineInfo = process.argv;
    var info = Cli.gatherInfo();
    info.command = commandLineInfo;
    return info;
};

Cli.gatherInfo = function gatherInfo() {
    var info = Info.gatherInfo();
    Info.getCloudBridgeVersion(info, process.cwd());
    Info.getCloudBridgeCliVersion(info, path.join(__dirname, '../'));
    return info;
};

Cli.handleUncaughtExceptions = function handleUncaughtExceptions(err, url) {
    console.log('An uncaught exception occurred and has been reported to CloudBridge'.error.bold);
    var errorMessage = typeof err === 'string' ? err : err.message;
    this.utils.errorHandler(errorMessage);
    process.exit(1);
};

Cli.attachErrorHandling = function attachErrorHandling() {
    this.utils.errorHandler = function errorHandler(msg, taskHelp) {
        try {
            cli.logging.logger.debug('cli.utils.errorHandler msg', msg, typeof msg);

            var stack = typeof msg == 'string' ? '' : msg.stack;
            var errorMessage = typeof msg == 'string' ? msg : msg.message;
            // console.log('stack', stack, arguments.caller);
            if (msg) {
                var info = Cli.gatherInfo();
                var cloudbridgeCliVersion = info.cloudbridge_cli;
                process.stderr.write('\n' + stack.error.bold + '\n\n');
                process.stderr.write(errorMessage.error.bold);
                process.stderr.write((' (CLI v' + cloudbridgeCliVersion + ')').error.bold + '\n');

                Info.printInfo(info);
            }
            process.stderr.write('\n');
            process.exit(1);
            return '';
        }
        catch (ex) {
            console.log('errorHandler had an error', ex);
            console.log(ex.stack);
        }
    };
};

Cli.getEngine = function getEngine(argv) {
    var engine = 'default',
        projectFile = path.join(this.cwd, 'cloudbridge.json');

    if (fs.existsSync(projectFile)) {
        var content = require(projectFile);

        if (content.engine)
            engine = content.engine;
    }
    else {
        if (argv.cordova)
            engine = 'cordova';
        else if (argv.ionic)
            engine = 'ionic';
    }

    return engine;
};


//Backwards compatability for those commands that havent been
//converted yet.
Cli.fail = function fail(err, taskHelp) {
    // var error = typeof err == 'string' ? new Error(err) : err;
    this.utils.fail(err, taskHelp);
};

Cli.require = function(name) {
    if ((name.substring(0, 1) === '/') ||
        (name.substring(0, 2) === './') ||
        (name.substring(0, 3) === '../')) {
        return require(path.join(__basedir, name));
    }

    return require(name);
};

Cli.cb_require = cb_require;