Source: lib/commands/install.js

/*!
 * Copyright (C) 2010-2015 by Revolution Analytics Inc.
 *
 * This program is licensed to you under the terms of Version 2.0 of the
 * Apache License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more
 * details.
 */

'use strict';

var path     = require('path'),
    fs       = require('fs'),
    deployr  = require('deployr'),
    request  = require('request'),
    opn      = require('opn'),
    shell    = require('shelljs'),
    Download = require('download'),
    progress = require('download-status'),
    spinner  = require('char-spinner'),
    LangType = require('../util/lang-type'),
    di       = require('../../di'),
    conf     = di.config,
    // --- color aliases ---
    ul       = di.chalk.underline,
    dim      = di.chalk.dim,
    red      = di.chalk.red,
    gray     = di.chalk.gray,
    green    = di.chalk.green,
    yellow   = di.chalk.yellow,
    magenta  = di.chalk.magenta;

/**
 * Commands for installing and deploying starter examples.
 * @mixin
 * @alias commands/install
 */   
var install = exports;

/** 
 * Usage for the _di install *_ commands which allow you install and deploy
 * starter examples.
 * 
 * - di install example
 * - di install example <example name>
 */
install.usage = [
    'The `di install` command installs pre-built DeployR examples locally',
    '',
    'Example usages:',
    'di install example',
    'di install example <example name>'
];

/**
 * Installs a starter example.
 *
 * @param {String} exName - The example to install.
 * @param {Function} callback - Continuation to pass control when complete.
 */
install.example = function(exName, callback) {    
    var exConfig = {};

    //
    // Allows arbitrary amount of arguments
    //
    if (arguments.length) {
        var args = Array.prototype.slice.call([arguments], 0)[0];
        callback = args[arguments.length - 1];
        exName = args[0] || null;
    }
    
    function listExamples() {
        var interval   = spinner(),
            separator  = di.prompt.separator,
            jsList     = [],
            javaList   = [],
            dotNETList = [];

        request.get({
                url: conf.get('git').repos,
                headers: {
                    'User-Agent': 'Awesome-Octocat-App'
                }
            },
            function(err, res, body) {
                var found = false, choices = [];

                clearInterval(interval);

                if (err || res.statusCode != 200) {
                    return callback(err);
                }                

                try {
                    JSON.parse(body).forEach(function(repo, i) {
                        var name = repo.name;
                        if (name.indexOf('example') > -1) {
                            found = name === exName ? true : found;
                            switch (LangType.parse(name)) {
                                case LangType.JS:
                                    jsList.push({ name: '  ' + name });
                                    break;

                                case LangType.JAVA:
                                    javaList.push('  ' + name);
                                    break;

                                case LangType.DOTNET:
                                    dotNETList.push('  ' + name);
                                    break;
                            }
                        }
                    });

                    if (found) {
                        console.log('Installing example ' + ul(exName) + '\n');
                        installExample(exName, di.noop);
                        return;
                    }                    

                    if (jsList.length > 0) {
                        choices.push(separator('JavaScript Examples '));
                        choices.push(jsList);
                    }

                    if (javaList.length > 0) {
                        choices.push(separator());
                        choices.push(separator('Java Examples '));
                        choices.push(javaList);
                    }

                    if (dotNETList.length > 0) {
                        choices.push(separator('.NET Examples '));
                        choices.push(dotNETList);
                    }

                    choices.push(separator());
                    choices.push({
                        name: 'Take me back home!',
                        value: {
                            method: 'home'
                        }
                    });

                    di.prompt.inquirer([{
                        name: 'example',
                        type: 'list',
                        message: 'What example would you like to install?',
                        choices: di._.flatten(choices)
                    }], function(answer) {
                        if (!answer.example.method) {
                            installExample(answer.example.trim(), di.noop);
                        } else {
                            di.home();
                        }
                    }.bind(di));

                } catch (err) {
                    return callback(new Error(chalk.bold(
                        'A problem occurred installing the example from Github.' +
                        '\nUnable to parse response: not valid JSON.'
                    )));
                }
            }); 
    }

    function installExample(example) {        
        var download = new Download({
                extract: true,
                strip: 1,
                mode: '755'
            })
            .get(conf.get('git').example.replace('{{example}}', example))
            .dest(example)
            .use(progress());

        download.run(function(err, files, stream) {
            if (err) { callback(new Error(err)); }

            console.log(green.bold('✓ download complete.\n'));
            console.log(ul(example));

            var installer;

            switch (LangType.parse(example)) {
                case LangType.JS:
                    installer = function(next) {
                        console.log(yellow('\nResolving npm dependencies, this might take a while...\n'));
                        di.spawnCommand('npm', ['install', '--production', '--silent'])
                            .on('error', next)
                            .on('exit', next);
                    };
                    break;

                case LangType.JAVA:
                case LangType.DOTNET:
                    installer = function(next) { next(); }
                    break;
            }

            // be in the example dir for installation and running
            shell.cd(example);

            installer(function() {
                fs.exists(path.resolve('di-config.json'), function(exists) {
                    if (exists) {
                        agent();

                        deployr.io('/r/user/about')
                            .error(function() {
                                var end = !conf.get('endpoint') ?
                                    ' First identify your DeployR server endpoint.' : '';
                                console.log(yellow('\nAuthentication required to install.' + end + '\n'));

                                di.commands.login(function(err) {
                                    if (err) { return callback(err); }

                                    installation();                                    
                                }); 
                            })
                            .end(function() { installation(); });
                    } else { 
                        // no `di-config` file given, just the launch example
                        console.log('no di-config.json');
                        run();
                    }
                });
            });
        });

        function installation() {
            var config  = require(path.resolve('di-config.json')) || {},
                repos   = (config['app-install'] || {}).repository || [],
                dirs    = di._.uniq(repos, 'directory'),
                last    = dirs.length,
                success = 0;            

            exConfig = {
                example: example,
                repos: repos,
                auth: (config['app-run'] || {}).requireAuthentication || false,
                tutorial: (config['app-run'] || {}).tutorial || {}
            };

            if (repos.length > 0) {
                console.log(yellow('\nInstalling example analytics dependencies onto DeployR...\n'));
                agent();

                repos.forEach(function(item, index) {
                    console.log((index + 1) + '. ' +
                        ul(item.file.filename) + ' in directory ' +
                        ul(item.file.directory || 'root'));
                });
                console.log('');

                //
                // create directories for the dependencies, when done start the
                // upload/install of the analytic dependencies into DeployR
                //
                dirs.forEach(function(item) {
                    deployr.io('/r/repository/directory/create')
                        .data({ directory: item.file.directory })
                        .end(function() { success++; })
                        .ensure(function() {
                            if (success === last) { analytics(repos); }
                         });
                });
            } else { // othwise no dependencies just run the example            
                candidateToRun();
            }
        }

        function analytics(repos) {
            var last    = repos.length,
                success = 0,
                retry   = [];

            repos.forEach(function(item) {
                var file = item.file,
                    perm = item.permissions;                    

                deployr.io('/r/repository/file/upload')
                    .data({
                        filename: file.filename,
                        directory: file.directory || 'root',
                        restricted: perm.restricted || null,
                        shared: perm.shared || true,
                        published: perm.published || true,
                        newversion: true,
                        newversionmsg: 'DeployR CLI (examples) upload.'
                    })
                    .attach(path.resolve('analytics', file.filename))
                    .error(function(err) { callback(err); })
                    .end(function(res) {
                        var file = res.get('repository').file;

                        console.log(green.bold('✓ upload complete.\n'));
                        console.log(ul(file.filename) +
                            ' uploaded to directory ' +
                            ul(file.directory) +
                            ' for ' + dim(conf.get('username') + '@' + conf.get('endpoint')) +
                            '\n');
                        success++;
                    })
                    .ensure(function() {
                        if (success === last) {
                            console.log(green.bold('✓ installation complete.\n'));
                            candidateToRun();
                        }
                    });
            }); // foreach
        }

    } // end installExample

    function candidateToRun() {
        di.prompt.inquirer([{
            name: 'run',
            type: 'confirm',
            message: 'Would you like to run the example:'
        }], function(result) {
            if (result.run) {
                if (exConfig.auth) {
                    console.log('\nThis example requires ' +
                        magenta(conf.get('username')) +
                        '\'s password to run.\n');

                    verifyPassword(targets);
                } else {
                    targets();
                }
            }
        });
    }

    function verifyPassword(cb) {
        di.prompt.inquirer([{
            name: 'password',
            type: 'password',
            message: 'Password:',
            validate: function(input) {
                return input.length > 0 || 'Please enter a valid password.';
            }
        }], function(answer) {
            // confirm password
            di.prompt.inquirer([{
                name: 'password',
                type: 'password',
                message: 'Verify Password:'
            }], function(confirm) {
                if (answer.password === confirm.password) {
                    cb(confirm.password);
                } else {
                    console.log(red('>>') + ' Passwords do not match.\n');
                    verifyPassword(cb);
                }
            });
        });
    }

    function targets(pw) {
        var separator = di.prompt.separator;

        di.clearScreen();

        // tutorial type examples
        if (exConfig.tutorial.topics) {
            var choices = [separator()];

            choices.push({
                name: 'Example Documentation',
                value: {
                    help: exConfig.tutorial.help,
                }
            });

            exConfig.tutorial.topics.forEach(function(obj) {
                choices.push({
                    name: obj.topic,
                    value: {
                        menu: obj.menu
                    }
                });
            });

            choices.push(separator());

            choices.push({
                name: 'Take me back home!',
                value: 'home'
            });

            di.prompt.inquirer([{
                name: 'topic',
                type: 'list',
                message: 'Examples',
                choices: choices
            }], function(answer) {
                if (answer.topic === 'home') {
                    di.home();
                } else if (answer.topic.help) {
                    opn(answer.topic.help);
                    targets(pw);
                } else {
                    tutorialChoice(answer.topic.menu, pw);
                }
            });
        } else { // standalone example apps
            run({
                pw: pw
            });
        }
    }

    function tutorialChoice(menu, pw) {
        var separator = di.prompt.separator,
            choices   = [separator()].concat(menu.map(function(obj) {
                return {
                    name: obj.item,
                    value: {
                        test: obj.args
                    }
                };
            }));

        di.prompt.inquirer([{
            name: 'item',
            type: 'list',
            message: 'Examples to run?',
            choices: choices.concat([
                separator(), {
                    name: 'Take me back',
                    value: 'back'
                }
            ])
        }], function(answer) {
            if (answer.item === 'back') {
                return targets(pw);
            }
            run({
                pw: pw,
                tutorial: {
                    menu: menu,
                    example: answer.item.test
                }
            });
        });
    }

    function run(optArgs) {
        var args    = [],
            options = [],
            sep     = dim(ul('                                                           ')),
            cmd;

        optArgs = optArgs || {};        

        switch (LangType.parse(exConfig.example)) {
            case LangType.JS:
                cmd = 'npm';
                args = ['start'];

                process.env.endpoint = conf.get('endpoint');
                process.env.username = conf.get('username');
                process.env.password = optArgs.pw || '';
                process.env.testmod  = (optArgs.tutorial || {}).example;
                options = { env: process.env };

                break;

            case LangType.JAVA:
                cmd = (process.platform === 'win32' ? 'gradlew.bat' : './gradlew');
                args = [
                    'run',
                    '-Pendpoint=' + conf.get('endpoint') + '/deployr',
                    '-Pusername=' + conf.get('username'),
                    '-Ppassword=' + optArgs.pw || '',
                    '-DtestClass=' + (optArgs.tutorial || {}).example
                ];

                break;

            case LangType.DOTNET:
                break;
        }

        di.clearScreen();
        
        console.log(sep);

        di.spawnCommand(cmd, args, options)
            .on('error', function(err) {
                callback(err);
            })
            .on('close', function(err) {
                console.log(sep + '\n');
                di.prompt.inquirer([{
                    name: 'post',
                    type: 'list',
                    message: 'What do you want to do?',
                    choices: [{
                        name: 'Take me back',
                        value: 'back'
                    }, {
                        name: 'Run more examples',
                        value: 'more'
                    }]
                }], function(answer) {
                    di.clearScreen();

                    if (answer.post === 'back') {
                        tutorialChoice(optArgs.tutorial.menu, optArgs.pw);
                    } else {
                        targets(optArgs.pw);
                    }
                });
            });
    }

    //
    // Start to example installation workflow
    //
    listExamples();
};

/**
 * Output usage information describing commands for installing and deploying 
 * starter examples.
 */
install.example.usage = [
    'The `di install` command installs pre-built DeployR examples locally',
    '',
    'Example usages:',
    'di install example',
    'di install example <example name>'
];

function agent() {
    deployr.configure({
        host: conf.get('endpoint'),
        cookies: ['JSESSIONID=' + conf.get('cookie')],
        sticky: true
    });
}