UNPKG

ionic

Version:

A tool for creating and developing Ionic Framework mobile apps.

563 lines (472 loc) 15.7 kB
var Cli = module.exports, colors = require('colors'), IonicAppLib = require('ionic-app-lib'), ConfigXml = IonicAppLib.configXml, IonicOpbeat = IonicAppLib.opbeat, Info = IonicAppLib.info, Ionitron = require('./ionic/ionitron'), optimist = require('optimist'), path = require('path'), settings = require('../package.json'), Tasks = require('./tasks/cliTasks'), Utils = IonicAppLib.utils, Q = require('q'); Cli.Tasks = TASKS = Tasks; Cli.IONIC_DASH = 'https://apps.ionic.io'; //Cli.IONIC_DASH = 'http://localhost:8000'; Cli.IONIC_API = '/api/v1/'; Cli.PRIVATE_PATH = '.ionic'; // Cli.dev = function dev() { // if (settings.version.contains('dev') || settings.version.contains('beta') || settings.version.contains('alpha')) { // return true; // } // return false; // }; // 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) { 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; Cli.setUpConsoleLoggingHelpers(); Cli.attachErrorHandling(); Cli.checkLatestVersion(); process.on('exit', function(){ Cli.printVersionWarning(); }); //Before taking any more steps, lets check their //environment and display any upgrade warnings Info.checkRuntime(); if(argv.ionitron) { var lang = argv.es ? 'es' : 'en'; return Ionitron.print(lang); } if (argv.verbose) { IonicAppLib.events.on('verbose', console.log); } if( (argv.version || argv.v) && !argv._.length) { return Cli.version(); } if(argv.help || argv.h) { return Cli.printHelpLines(); } if(argv['stats-opt-out']) { var IonicStore = require('./ionic/store').IonicStore; var ionicConfig = new IonicStore('ionic.config'); ionicConfig.set('statsOptOut', true); ionicConfig.save(); console.log('Successful stats opt-out'); return; } var taskSetting = Cli.tryBuildingTask(argv); if(!taskSetting) { return Cli.printAvailableTasks(); } var booleanOptions = Cli.getBooleanOptionsForTask(taskSetting); argv = optimist(processArgv.slice(2)).boolean(booleanOptions).argv; var taskModule = Cli.lookupTask(taskSetting.module); var taskInstance = new taskModule(); var promise = taskInstance.run(Cli, argv); return promise; } catch (ex) { IonicAppLib.events.emit('verbose', 'Cli.Run - Error', ex); IonicAppLib.events.emit('verbose', 'Cli.run stack - ', ex.stack); return Utils.fail(ex); } }; 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.setUpConsoleLoggingHelpers = function setUpConsoleLoggingHelpers() { IonicAppLib.events.on('log', console.log); colors.setTheme({ silly: 'rainbow', input: 'grey', small: 'grey', verbose: 'cyan', prompt: 'grey', info: 'white', data: 'grey', help: 'cyan', warn: 'yellow', debug: 'blue', error: 'red' }); var consoleInfo = console.info; console.info = function() { if (arguments.length === 1 && !arguments[0]) return; var msg = ''; for (var n in arguments) { msg += arguments[n] + ' '; } consoleInfo.call(console, msg.blue.bold); }; var consoleError = console.error; console.error = function() { if (arguments.length === 1 && !arguments[0]) return; var msg = ' ✗'; for (var n in arguments) { msg += ' ' + arguments[n]; } consoleError.call(console, msg.red.bold); }; console.success = function() { if (arguments.length === 1 && !arguments[0]) return; var msg = ' ✓'; for (var n in arguments) { msg += ' ' + arguments[n]; } console.log(msg.green.bold); }; }; Cli.lookupTask = function lookupTask(module) { try { var taskModule = require(module).IonicTask; 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('Ionic 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/driftyco/ionic-cli/blob/master/CHANGELOG.md\n').yellow ); process.stdout.write( ' * Run '.yellow + 'npm install -g ionic'.bold + ' to update\n'.yellow ); process.stdout.write('------------------------------------\n\n'.red); Cli.npmVersion = null; } }; Cli.checkLatestVersion = function checkLatestVersion() { Cli.latestVersion = Q.defer(); try { if (settings.version.indexOf('beta') > -1) { // don't bother checking if its a beta Cli.latestVersion.resolve(); return; } // stay silent if it errors var IonicStore = require('./ionic/store').IonicStore; var ionicConfig = new IonicStore('ionic.config'); var versionCheck = ionicConfig.get('versionCheck'); if (versionCheck && ((versionCheck + 86400000) > Date.now() )) { // we've recently checked for the latest version, so don't bother again Cli.latestVersion.resolve(); return; } var proxy = process.env.PROXY || process.env.http_proxy || null; var request = require('request'); request({ url: 'http://registry.npmjs.org/ionic/latest', proxy: proxy }, function(err, res, body) { try { Cli.npmVersion = JSON.parse(body).version; ionicConfig.set('versionCheck', Date.now()); ionicConfig.save(); } catch(e) {} Cli.latestVersion.resolve(); }); } catch (e) { Cli.latestVersion.resolve(); } return Cli.latestVersion.promise; }; Cli.tryBuildingTask = function tryBuildingTask(argv) { if (argv._.length === 0) { return false; } var taskName = argv._[0]; return Cli.getTaskWithName(taskName); }; Cli.getTaskWithName = function getTaskWithName(name) { for (var i = 0; i < TASKS.length; i++) { var t = TASKS[i]; if(t.name === name) { return t; } if (t.alt) { for(var j = 0; j < t.alt.length; j++) { var alt = t.alt[j]; if (alt === name) { return t; } } } } }; Cli.printIonic = function printIonic() { var w = function(s) { process.stdout.write(s); }; w(' _ _ \n'); w(' (_) (_) \n'); w(' _ ___ _ __ _ ___ \n'); w(' | |/ _ \\| \'_ \\| |/ __|\n'); w(' | | (_) | | | | | (__ \n'); w(' |_|\\___/|_| |_|_|\\___| CLI v'+ settings.version + '\n'); }; Cli.printAvailableTasks = function printAvailableTasks(argv) { Cli.printIonic(); process.stderr.write('\nUsage: ionic 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(1); }; Cli.printHelpLines = function printHelpLines() { Cli.printIonic(); process.stderr.write('\n=======================\n'); for (var i = 0; i < TASKS.length; i++) { var task = TASKS[i]; if (task.summary) { Cli.printUsage(task); } } process.stderr.write('\n'); Cli.processExit(1); }; Cli.printUsage = function printUsage(d) { var w = function(s) { process.stdout.write(s); }; w('\n'); var rightColumn = 41; var dots = ''; var indent = ''; var x, arg; var taskArgs = d.title; for(arg in d.args) { taskArgs += ' ' + arg; } w(taskArgs.green.bold); while( (taskArgs + dots).length < rightColumn + 1) { dots += '.'; } w(' ' + dots.grey + ' '); if(d.summary) { w(d.summary.bold); } for(arg in d.args) { if( !d.args[arg] ) continue; indent = ''; w('\n'); while(indent.length < rightColumn) { indent += ' '; } w( (indent + ' ' + arg + ' ').bold ); var argDescs = d.args[arg].split('\n'); var argIndent = indent + ' '; for(x=0; x<arg.length + 1; x++) { argIndent += ' '; } for(x=0; x<argDescs.length; x++) { if(x===0) { w( argDescs[x].bold ); } else { w( '\n' + argIndent + argDescs[x].bold ); } } } indent = ''; while(indent.length < d.name.length + 1) { indent += ' '; } var optIndent = indent; while(optIndent.length < rightColumn + 4) { optIndent += ' '; } for(var opt in d.options) { w('\n'); dots = ''; var optLine = indent + '[' + opt + '] '; w(optLine.yellow.bold); if(d.options[opt]) { while( (dots.length + optLine.length - 2) < rightColumn) { dots += '.'; } w(dots.grey + ' '); var taskOpt = d.options[opt], optDescs; if (typeof taskOpt == 'string') { optDescs = taskOpt.split('\n'); } else { optDescs = taskOpt.title.split('\n'); } for(x=0; x<optDescs.length; x++) { if(x===0) { w( optDescs[x].bold ); } else { w( '\n' + optIndent + optDescs[x].bold ); } } } } w('\n'); }; Cli.processExit = function processExit(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'); }; Cli.printNewsUpdates = function printNewsUpdates(skipNewsCheck) { if(typeof skipNewsCheck == 'undefined') { skipNewsCheck = true; } var q = Cli.cliNews = Q.defer(); var proxy = process.env.PROXY || null; var IonicStore = require('./ionic/store').IonicStore; var ionicConfig = new IonicStore('ionic.config'); var request = require('request'); var monthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; var d = new Date(); var downloadUrl = 'http://code.ionicframework.com/content/cli-message.json'; request({ url: downloadUrl, proxy: proxy }, function(err, res, html) { if(!err && res && res.statusCode === 200) { try { var newsId = ionicConfig.get('newsId'); var messagesJson = {}; messagesJson = JSON.parse(html); if(skipNewsCheck || typeof newsId == 'undefined' || newsId != messagesJson.id) { ionicConfig.set('newsId', messagesJson.id) ionicConfig.save() } else { q.resolve(); return q.promise; } process.stdout.write('+---------------------------------------------------------+\n'.green); var monthMessage = ['+ New Ionic Updates for ', monthNames[d.getMonth()], ' ', d.getFullYear(), '\n+\n'].join('').green; process.stdout.write(monthMessage); for(var i = 0, j = messagesJson.list.length; i < j; i++) { var entry = messagesJson.list[i]; var entryMessage = ['+ ', entry.name, '\n', '+ ', entry.action.yellow, '\n+\n'].join(''); process.stdout.write(entryMessage); } process.stdout.write('+---------------------------------------------------------+\n'.green); } catch(ex) { console.log('ex', ex.stack); q.reject('Error occurred in downloading the CLI messages:', ex) Utils.fail(ex); q.reject(ex); return } q.resolve(messagesJson); } else { process.stdout.write('Unable to fetch', err, res.statusCode); q.reject(res); } }); return q.promise; }; //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/ionic-app-lib directory Cli.reportExtras = function reportExtras(err) { var commandLineInfo = process.argv; // if (Cli.dev()) { // console.log('An error occurred and was reported to Opbeat', err); // console.log(err.stack); // } var info = Cli.gatherInfo(); info.command = commandLineInfo; return info; }; Cli.gatherInfo = function gatherInfo() { var info = Info.gatherInfo(); Info.getIonicVersion(info, process.cwd()); Info.getIonicCliVersion(info, path.join(__dirname, '../')); return info; }; Cli.handleUncaughtExceptions = function handleUncaughtExceptions(err, url) { console.log('An uncaught exception occurred and has been reported to Ionic'.error.bold); var errorMessage = typeof err === 'string' ? err : err.message; Utils.errorHandler(errorMessage); process.exit(1); }; Cli.attachErrorHandling = function attachErrorHandling() { Utils.errorHandler = function errorHandler(msg, taskHelp) { try { IonicAppLib.events.emit('verbose', '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 ionicCliVersion = info.ionic_cli; process.stderr.write('\n' + stack.error.bold + '\n\n'); process.stderr.write(errorMessage.error.bold); process.stderr.write( (' (CLI v' + ionicCliVersion + ')').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); } }; var options = IonicOpbeat.cliClientOptions; options.prerequestMethod = Cli.reportExtras; var opbeat = IonicOpbeat.createCliClient(options); opbeat.handleUncaughtExceptions(Cli.handleUncaughtExceptions); }; //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; Utils.fail(err, taskHelp); }; Cli.getContentSrc = function getContentSrc() { return Utils.getContentSrc(process.cwd()); }; Cli.setConfigXml = function setConfigXml(settings) { return ConfigXml.setConfigXml(process.cwd(), settings); };