TOTVSTEC/cloudbridge-cli

View on GitHub
src/utils/utils.js

Summary

Maintainability
C
1 day
Test Coverage
'use strict';

var fs = require('fs'),
    os = require('os'),
    path = require('path'),
    Q = require('q'),
    shelljs = require('shelljs'),
    unzip = require('unzip'),
    archiver = require('archiver'),
    request = require('request'),
    requestProgress = require('request-progress'),
    logging = require('./logging'),
    usernameSync = require('username').sync,
    ejs = require('ejs');

require('colors');

var ProgressBar = require('progress');

var Utils = module.exports;

// Utils.useMultiBar = true;

Utils.errorHandler = null;

/**
 * Utils.createArchive will zip up a subdirectory in the app directory
 *
 * Utils.createArchive(appDirectory, 'www') makes a zip file at
 * {appDirectory}/www.zip whose file structure is like www.zip/www/{assets}
 *
 * @param {string} appDirectory The app's absolute directory
 * @param {string} documentRoot Denotation of the subdirectory, e.g. 'www'
 *
 * @return {Promise}
 */
Utils.createArchive = function(appDirectory, documentRoot) {
    var q = Q.defer();
    var zipPath = path.join(appDirectory, documentRoot);

    logging.logger.debug('Now zipping contents of ' + zipPath);

    if (!fs.existsSync(zipPath)) {
        q.reject(documentRoot + ' directory cannot be found. Make sure the working directory is at the top level of an CloudBridge project.', 'upload');
    }

    var zipDestination = zipPath + ".zip";
    var zip = fs.createWriteStream(zipDestination);

    var archive = archiver('zip');
    archive.pipe(zip);

    archive.bulk([
        { expand: true, cwd: zipPath, src: ['**'] }
    ]);

    archive.finalize(function(err, bytes) {
        if (err) {
            q.reject(['Error uploading: ', err].join(''));
        }
    });

    zip.once('close', function() {
        q.resolve(zipDestination);
    });

    return q.promise;
};


Utils.fetchPackage = function fetchPackage(options) {
    if (typeof options === 'string') {
        options = {
            package: options
        };
    }

    options.group = options.group || 'totvstec';
    options.version = options.version || 'master';

    var homeDir = process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH,
        packageDir = path.join(homeDir, '.cloudbridge', 'packages', options.group, options.package),
        outputDir = path.join(packageDir, options.version),
        url = 'https://github.com/' + options.group + '/' + options.package + '/archive/' + options.version + '.zip';

    if (shelljs.test('-d', outputDir)) {
        return Q.fcall(function() {
            return outputDir;
        });
    }

    shelljs.mkdir('-p', packageDir);

    return Utils.fetchArchive(packageDir, url, false).then(function() {
        var contentDir = path.join(packageDir, options.package + '-' + options.version);

        shelljs.mv(contentDir, outputDir);

        return outputDir;
    });
};

Utils.copyPackage = function copyPackage(options) {
    var targetDir = path.join(options.project.dir, 'build', 'packages', options.package);

    shelljs.rm('-rf', targetDir);
    shelljs.mkdir('-p', targetDir);
    shelljs.cp('-Rf', path.join(options.src, '*'), targetDir);
};


Utils.fetchArchive = function fetchArchive(targetPath, archiveUrl, isGui) {
    var q = Q.defer();

    // The folder name the project will be downloaded and extracted to
    var message = ['\nDownloading:'.bold, archiveUrl].join(' ');
    logging.logger.info(message);

    var tmpFolder = os.tmpdir();
    var tempZipFilePath = path.join(tmpFolder, 'cloudbridge-' + new Date().getTime() + '.zip');

    var unzipRepo = function unzipRepo(fileName) {
        var readStream = fs.createReadStream(fileName);
        readStream.once('error', function(err) {
            logging.logger.debug(('unzipRepo readStream: ' + err).error);
            q.reject(err);
        });

        var writeStream = unzip.Extract({ path: targetPath });
        writeStream.once('close', function() {
            q.resolve();
        });
        writeStream.once('error', function(err) {
            logging.logger.debug(('unzipRepo writeStream: ' + err).error);
            q.reject(err);
        });
        readStream.pipe(writeStream);
    };

    var proxy = process.env.PROXY || process.env.http_proxy || null;
    var r = request({ url: archiveUrl, rejectUnauthorized: false, encoding: null, proxy: proxy }, function(err, res, body) {
        if (err) {
            // console.error('Error fetching:'.error.bold, archiveUrl, err);
            q.reject(err);
            return;
        }
        if (!res) {
            console.error('Invalid response:'.error.bold, archiveUrl);
            q.reject('Unable to fetch response: ' + archiveUrl);
            return;
        }
        if (res.statusCode !== 200) {
            if (res.statusCode === 404 || res.statusCode === 406) {
                console.error('Not found:'.error.bold, archiveUrl, '(' + res.statusCode + ')');
                console.error('Please verify the url and try again.'.error.bold);
            }
            else {
                console.error('Invalid response status:'.error.bold, archiveUrl, '(' + res.statusCode + ')');
            }
            q.reject(res);
            return;
        }
        try {
            fs.writeFileSync(tempZipFilePath, body);
            unzipRepo(tempZipFilePath);
        }
        catch (e) {
            logging.logger.debug('fetchArchive request write: ', e);
            q.reject(e);
        }
    });

    if (!isGui) {
        var bar = null,
            oldPercent = 0,
            p = requestProgress(r, {
                throttle: 500
            });

        r.on('response', function(res) {
            //bar = Multibar.newBar(
            bar = new ProgressBar(':percent\t:bar\t:etas', {
                complete: String.fromCharCode(9608).blue.bold,
                incomplete: String.fromCharCode(9617).gray.dim,
                width: 30,
                total: 100
            });

            p.on('progress', function(state) {
                var newPercent = Math.ceil(100 * state.percent);
                if (newPercent !== oldPercent) {
                    bar.tick(newPercent - oldPercent);
                    oldPercent = newPercent;
                }
            }).once('end', function() {
                p.removeAllListeners('progress');
                r.removeAllListeners('response');

                bar.tick(100);
                console.log();
            });
        });

        /*
        r.once('progress', function(state) {
            //bar = Multibar.newBar(
            bar = new ProgressBar(':percent\t:bar\t:etas', {
                complete: String.fromCharCode(9608).blue.bold,
                incomplete: String.fromCharCode(9608).gray.dim,
                width: 30,
                total: 100    //state.size.total
            });

            r.on('progress', function(state) {
                var newPercent = Math.ceil(100 * state.percent);
                if (newPercent !== oldPercent) {
                    bar.tick(newPercent - oldPercent);
                    oldPercent = newPercent;
                }
                //console.log(JSON.stringify(state, null, 2));

                //bar.tick(state.size.transferred);

            });
        });
        */
    }

    return q.promise;
};

Utils.preprocessOptions = function preprocessOptions(options) {
    var result = {};

    result.appDirectory = options.appDirectory;
    result.targetPath = options.targetPath || null;
    result.template = options.template || 'blank';
    result.packageName = options.packageName || null;
    // result.appName = options.appName || '';

    if (!options.appName) {
        var appNameSplit = options.appDirectory.split('/');
        appNameSplit = appNameSplit[appNameSplit.length - 1].split('\\');
        options.appName = appNameSplit[appNameSplit.length - 1];
    }
    else {
        result.appName = options.appName;
    }

    return result;
};

Utils.preprocessCliOptions = function preprocessCliOptions(argv) {
    try {
        var options = {};

        //      0     1
        //cloudbridge start facebooker
        //Grab the app's relative directory name
        options.appDirectory = argv._[1];

        // Grab the name of the app from -a or  --app. Defaults to appDirectory if none provided
        options.appName = argv.appname || argv['app-name'] || argv.a;
        if (!options.appName) {
            var appNameSplit = options.appDirectory.split('/');
            appNameSplit = appNameSplit[appNameSplit.length - 1].split('\\');
            options.appName = appNameSplit[appNameSplit.length - 1];
        }

        // get a package name, like com.cloudbridge.myapp
        options.packageName = argv.id || argv.i;

        if (!options.packageName) {
            var appname = options.appName.replace(/[\W_]/igm, '').toLowerCase(),
                username = usernameSync().toLowerCase();

            options.packageName = 'com.' + username + '.' + appname;
        }

        // start project template can come from cmd line args -t, --template, or the 3rd arg, and defaults to base
        options.template = (argv.template || argv.t || argv._[2] || 'base');

        // figure out the full path
        options.targetPath = Utils.getProjectDirectory(options);

        return options;
    }
    catch (ex) {
        logging.logger.debug('An error occrured processing the CLI arguments', ex);
        Utils.fail('There was an error parsing out options from the Command Line');
    }
};

Utils.getProjectDirectory = function getProjectDirectory(options) {
    return path.resolve(options.appDirectory);
};

Utils.fail = function fail(msg, taskHelp) {
    try {
        logging.logger.debug('Utils.fail', msg, taskHelp);
        logging.logger.debug('Utils.fail stack', msg.stack);

        //If an error handler is set, use it. Otherwise, just print basic info.
        if (Utils.errorHandler) {
            logging.logger.debug('Utils.errorHandler is set, calling that now');
            return Utils.errorHandler(msg, taskHelp);
        }

        logging.logger.error('An error occurred in CloudBridge App Lib and no error handler was set.');
        logging.logger.error(msg);
        process.exit(1);
        return '';
    }
    catch (ex) {
        logging.logger.debug('Utils.fail: ', ex);
    }
};

Utils.gulpInstalledGlobally = function gulpInstalledGlobally() {
    var result = shelljs.exec('gulp -v', { silent: true });

    if (result.code !== 0) {
        return false;
    }

    return true;
};

Utils.copyTemplate = function copyTemplate(origin, to, data, extensions) {
    var files = fs.readdirSync(origin);

    extensions = extensions || /\.(prw|js|ts|coffe|css|less|scss|html|md|json|xml|java)/;

    for (var i = 0; i < files.length; i++) {
        var current = path.join(origin, files[i]);
        var target = path.join(to, files[i]);
        var stat = fs.statSync(current);

        if (stat.isDirectory()) {
            shelljs.mkdir('-p', target);

            Utils.copyTemplate(current, target, data, extensions);
        }
        else {
            var ext = path.extname(current);
            var content = fs.readFileSync(current);

            if ((ext === '') || (ext.match(extensions))) {
                try {
                    var parsed = ejs.render(content.toString(), data);
                    content = parsed;
                }
                catch (error) {
                    console.error("Eror on parsing '" + target + "'", error);
                }
            }

            fs.writeFileSync(target, content);

            /*
            fs.writeFile(target, content, function(err) {
                if (err) {
                    console.error(err);
                }
            });
            */
        }
    }
};