UNPKG

ionic

Version:

A tool for creating and developing Ionic Framework mobile apps.

780 lines (645 loc) • 24.1 kB
// "cordovaPlatforms": [ // "ios", // { // "android": { // "id": "android", // "locator": "https://github.com/apache/cordova-android.git" // } // } // ], // "cordovaPlugins": [ // "org.apache.cordova.device", // "org.apache.cordova.console", // "com.ionic.keyboard", // "org.apache.cordova.splashscreen", // { // "locator": "https://github.com/MobileChromeApps/cordova-crosswalk-engine.git", // "id": "org.cordova.croswalk" // }, // { // "locator": "/path/to/cloned/phonegap-facebook-plugin", // "id": "", // "variables": { // "APP_ID": "some_id", // "APP_NAME": "some_name" // } // } // ] var fs = require('fs'), path = require('path'), argv = require('optimist').argv, Q = require('q'), shelljs = require('shelljs'), Task = require('./task').Task, IonicStats = require('./stats').IonicStats, _ = require('underscore'), IonicProject = require('./project'), IonicInfo = require('./info').IonicTask; var State = module.exports; shelljs.config.silent = true; var IonicTask = function() {}; IonicTask.prototype = new Task(); State.getPackageJson = function getPackageJson() { var packageJsonPath = path.resolve('.', 'package.json'); var packageJson = null; try { packageJson = require(packageJsonPath); if (!packageJson.cordovaPlugins) { packageJson.cordovaPlugins = []; } if (!packageJson.cordovaPlatforms) { packageJson.cordovaPlatforms = []; } } catch (ex) { console.log('There was an error opening your package.json file.'); console.log(ex); Ionic.fail(ex); } return packageJson; }; State.addOrUpdatePluginToPackageJson = function addOrUpdatePluginToPackageJson(packageJson, pluginId, pluginInfo) { var existingPlugin; if (typeof pluginInfo === 'undefined') { pluginInfo = pluginId; } //We need to check cordovaPlugins //perhaps the ID already exists, or the 'id' in the object with locator exists. for (var i = 0, j = packageJson.cordovaPlugins.length; i < j; i++) { if (typeof packageJson.cordovaPlugins[i] == 'string' && packageJson.cordovaPlugins[i] == pluginId) { existingPlugin = packageJson.cordovaPlugins[i]; } else if (packageJson.cordovaPlugins[i].id == pluginId) { existingPlugin = packageJson.cordovaPlugins[i]; } } if (!existingPlugin) { console.log('Adding since there was no existingPlugin') packageJson.cordovaPlugins.push(pluginInfo); } }; State.saveState = function saveState() { console.log('Saving your Ionic app state of platforms and plugins'.blue.bold); var packageJson = State.getPackageJson(); try { State.saveExistingPlatforms(packageJson); console.log('Saved platform'.green); State.saveExistingPlugins(packageJson); console.log('Saved plugins'.green); State.savePackageJson(packageJson); console.log('Saved package.json'.green); } catch (ex) { console.log('There was an error saving your current Ionic setup'.red); console.log('Error:', ex); } }; State.saveExistingPlatforms = function saveExistingPlatforms(packageJson) { var pp = path.resolve('.', 'platforms'), platforms = [], platformPath, platformStats; try { platforms = fs.readdirSync(pp); } catch (ex) { return; } // console.log('h') platforms.forEach(function(platform) { platformPath = path.resolve('platforms', platform); platformStats = fs.statSync(platformPath); if (!platformStats.isDirectory()) { return } try { var versionPath = path.resolve(pp, platform, 'cordova', 'version'); var version = State.getPlatformVersion(versionPath); var locator = platform; //Check to see if its crosswalk if (platform === 'android' && version.indexOf('-dev') !== -1) { //Look up path for engine/cordova-android path var engineFiles = fs.readdirSync(path.resolve('engine')); var enginePath = null; engineFiles.forEach(function(engineDir) { if (engineDir.indexOf('android') !== -1) { enginePath = engineDir; } }); locator = path.resolve('engine', enginePath); } var platformExists = _.findWhere(packageJson.cordovaPlatforms, {platform: platform}); if (!platformExists) { packageJson.cordovaPlatforms.push({platform: platform, version: version, locator: locator}); } } catch (ex) { console.log('There was an error trying to save your existing state', ex); } }); }; State.saveExistingPlugins = function saveExistingPlugins(packageJson) { var pluginDir = path.resolve('.', 'plugins'); // console.log(pluginDir); var plugins = fs.readdirSync(pluginDir); var pluginId; //Lets try just relying on the fetch.json file //this file lists all plugins with where they come from, etc var fetchJson; try { fetchJson = require(path.resolve('plugins', 'fetch.json')); } catch (ex) { // Ionic.fail('There is not a fetch.json file available in your plugins folder.'); // return; } var configXmlPath = path.resolve('config.xml'); var plugin, pluginToAdd, locator; var cordovaPlugins = []; // console.log('config xml path ', configXmlPath) var configXmlData = State.getXmlData(configXmlPath); var pluginPath, pluginPathStats, pluginXmlPath, pluginXml, pluginName, featureParams, preferences, hasVariables = false, variableList = [], keyValueList = {}; if (fetchJson) { // console.log('fetchJson', fetchJson) //This will break with what we had before for (pluginId in fetchJson) { // cordovaPlugins.push(); plugin = fetchJson[pluginId]; pluginToAdd = {}; try { pluginPath = path.resolve('plugins', pluginId); pluginPathStats = fs.statSync(pluginPath); } catch (ex) { console.log(['Plugin', pluginId, 'does not exist in the plugins directory. Skipping'].join(' ').yellow); continue; } if (!pluginPathStats.isDirectory()) { console.log(['Plugin', pluginId, 'does not exist in the plugins directory. Skipping'].join(' ').yellow); continue; } // console.log('plugin.source.type', plugin.source.type); if (plugin.source.type === 'registry') { locator = pluginId; } else { if (plugin.source.type === 'local') { locator = plugin.source.path; } else { //asume its git locator = plugin.source.url; } } pluginXmlPath = path.resolve('plugins', pluginId, 'plugin.xml'); pluginXml = State.getXmlData(pluginXmlPath) pluginName = pluginXml.plugin.name; preferences = pluginXml.plugin.preference; if (preferences && preferences.length > 0) { hasVariables = true; preferences.forEach(function(preference) { variableList.push(preference.$.name); // console.log('looking at preference: ', preference) }) } hasVariables = false; variableList = []; keyValueList = {}; if (hasVariables) { // console.log('we have avariables to look at:', variableList) // var features = configXmlData.widget.feature; // console.log('features', features) featureParams = State.getPluginParameters(configXmlData, pluginName); // features.forEach(function(potFeature) { // if(potFeature.$.name == pluginName) { // feature = potFeature // } // }) // console.log('feature found:', feature); // var featureParams = feature.param; variableList.forEach(function(variable) { // console.log('Looking up variable:', variable) for (var i = 0, j = featureParams.length; i < j; i++) { if (variable == featureParams[i].$.name) { keyValueList[variable] = featureParams[i].$.value; } } }) // console.log('We have the plugin parameters with values:', keyValueList) var pluginObj = {id: pluginId, locator: locator, variables: keyValueList}; cordovaPlugins.push(pluginObj) } else if (plugin.source.type === 'git' || plugin.source.type === 'local') { cordovaPlugins.push({locator: locator, id: pluginId}); } else { cordovaPlugins.push(pluginId) } }//closes for loop for fetch.json packageJson.cordovaPlugins = cordovaPlugins } else { console.log('There was no fetch.json file available to restore plugin information from.'.red.bold); console.log('Restoring plugins from scanning the plugins folder is still being worked on.'.red.bold); } } //Closes saveExistingPlugins State.getXmlData = function getXmlData(xmlPath) { var xml2js = require('xml2js'); var xmlString = fs.readFileSync(xmlPath, { encoding: 'utf8' }); var xmlData; var parseString = xml2js.parseString; parseString(xmlString, function(err, jsonConfig) { if (err) { return self.fail('Error parsing xml file: ' + err); } try { xmlData = jsonConfig; } catch (e) { return self.fail('Error parsing ' + xmlPath + ': ' + e); } }); return xmlData } State.getPlatformVersion = function getPlatformVersion(path) { var result = shelljs.exec(['node', path].join(' '), { silent: true }); if (result.code != 0) { return 'Not installed'; } var version = result.output // console.log('Version for path:', path, version) return version.replace(/\n/g, ''); } State.addOrUpdatePlatformToPackageJson = function addOrUpdatePlatformToPackageJson(packageJson, platformId, platformInfo) { // var platformExists = _.findWhere(packageJson.cordovaPlatforms, {platform: platformId}); var existingPlatform; if (typeof platformInfo === 'undefined') { platformInfo = platformId; } for (var i = 0, j = packageJson.cordovaPlatforms.length; i < j; i++) { if (typeof packageJson.cordovaPlatforms[i] == 'string' && packageJson.cordovaPlatforms[i] == platformId) { existingPlatform = packageJson.cordovaPlatforms[i]; } else if (packageJson.cordovaPlatforms[i].id == platformId) { existingPlatform = packageJson.cordovaPlatforms[i]; existingPlatform.locator = platformInfo.locator; } } if (!existingPlatform) { packageJson.cordovaPlatforms.push(platformInfo); } // console.log('platformExists:', platformExists) // if (!platformExists && requiresLocator) { // packageJson.cordovaPlatforms.push({platform: platform, locator: locator}); // // packageJson.cordovaPlatforms.push({platform: platform, version: version, locator: locator}); // } else if (!platformExists && !requiresLocator) { // packageJson.cordovaPlatforms.push(platform); // } else { // platformExists.platform = platform // platformExists.locator = locator // } } State.savePlatform = function savePlatform(platformArgs) { // Locator may be: // Name: ios, android // Local path: ./engine/cordova-android-c0.6.1 // Http url: https://github.com/apache/cordova-android.git // console.log('platform args:', platformArgs); var locator = platformArgs._[2]; var platform = 'ios'; var packageJson = State.getPackageJson(); //Test to see if its just ios or android if (locator === 'ios' || locator === 'android') { platform = locator; State.addOrUpdatePlatformToPackageJson(packageJson, platform); return State.savePackageJson(packageJson); } platform = locator.indexOf('ios') != -1 ? 'ios' : 'android'; var platformInfo = { platform: platform, locator: locator }; State.addOrUpdatePlatformToPackageJson(packageJson, platform, platformInfo); State.savePackageJson(packageJson); } State.savePackageJson = function savePackageJson(packageJsonData) { try { var packageJsonPath = path.resolve('package.json'); fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonData, null, 2)); } catch (e) { console.log(('Error saving ' + packageJsonPath + ': ' + e).error.bold); } } State.restoreState = function restoreState() { console.log('Attempting to restore your Ionic application from package.json\n'.yellow.bold); var packageJsonPath = path.resolve('.', 'package.json') var packageJson = require(packageJsonPath) //Restore plugins first - to avoid issues with platforms adding //without variables if (!packageJson.cordovaPlugins) { packageJson.cordovaPlugins = [] } console.log('Restoring Platforms\n'.blue.bold) return State.restorePlatforms(packageJson) .then(function() { console.log('\nRestore platforms is complete\n'.green.bold) console.log('\rRestoring Plugins\n'.blue.bold) return State.restorePlugins(packageJson) }) .then(function() { console.log('Restore plugins is complete\n'.green.bold) console.log('Ionic state restore completed\n'.blue.bold) }) .catch(function(ex) { console.log('Ionic state restore failed\n'.red.bold) console.log(ex) }) } State.restorePlugins = function restorePlugins(packageJson) { var q = Q.defer(); State.processPlugin(0, packageJson, q); // q.resolve(); return q.promise; } State.restorePlatforms = function restorePlatforms(packageJson) { var q = Q.defer(); State.processPlatform(0, packageJson, q); return q.promise; } State.processPlatform = function processPlatform(index, packageJson, promise) { if (index >= packageJson.cordovaPlatforms.length) { promise.resolve(); return } try { // console.log('processing platform', index, packageJson) var platform = packageJson.cordovaPlatforms[index]; var platformCommand; if (typeof platform == 'string') { platformCommand = ['cordova platform add ', platform].join('') } else { //Here, they have either a special version, or locator for //local install or github install platformCommand = 'cordova platform add ' + platform.locator; } // var platformCommand = State.createAddRemoveStatement(platform); console.log(platformCommand); exec(platformCommand, function() { State.processPlatform(index + 1, packageJson, promise); }); } catch (ex) { console.log('An error happened processing the previous cordova plugins') console.log('Error:', ex) promise.reject(ex) } } State.createAddRemoveStatement = function createAddRemoveStatement(plugin) { // console.log('Creating add/remove statement', plugin) try { var pluginCmd = 'cordova plugin add '; if (typeof plugin === 'string') { pluginCmd += plugin; } else { pluginCmd += plugin.locator + ' '; if (plugin.variables) { Object.keys(plugin.variables).forEach(function(variable) { pluginCmd += '--variable ' + variable + '="' + plugin.variables[variable] + '" '; }); } } } catch (ex) { console.log('Failed to create add plugin statement:', ex) } // console.log('plugin cmd', pluginCmd) return pluginCmd; } State.processPlugin = function processPlugin(index, packageJson, promise) { if (index >= packageJson.cordovaPlugins.length) { promise.resolve(); return } try { // console.log('processing plugin', index, packageJson) var plugin = packageJson.cordovaPlugins[index]; var pluginCommand = State.createAddRemoveStatement(plugin); console.log(pluginCommand); exec(pluginCommand, function() { State.processPlugin(index + 1, packageJson, promise); }); } catch (ex) { console.log('An error happened processing the previous cordova plugins') console.log('Error:', ex) promise.reject(ex) } } State.removePlatform = function removePlatform(platformArgs) { // console.log('Args:', platformArgs); //Expecting - ionic platform remove [ios|android] //TODO - check if they pass multiple platforms //ionic platform remove ios android var platformToRemove = platformArgs._[2], packageJson = State.getPackageJson(), platformEntry; for (var i = 0, j = packageJson.cordovaPlatforms.length; i < j; i++) { platformEntry = packageJson.cordovaPlatforms[i]; if (typeof platformEntry === 'string' && platformEntry == platformToRemove) { packageJson.cordovaPlatforms.splice(i, 1); break; } else if (platformEntry.platform == platformToRemove) { //Its object {platform: 'android', locator: './engine/cordova-android'} packageJson.cordovaPlatforms.splice(i, 1); break; } } State.savePackageJson(packageJson); }; State.getPluginParameters = function getPluginParameters(configXmlData, pluginName) { if (!configXmlData || !configXmlData.widget || !configXmlData.widget.feature) { throw 'Invalid Config XML Data'; } var features = configXmlData.widget.feature, feature; features.forEach(function(potFeature) { if (potFeature.$.name == pluginName) { feature = potFeature } }) if (feature && feature.param) { return feature.param } return null; } // /** * Used after `ionic plugin add <id|locator>` to save the plugin * to package.json and config.xml. * @pluginArgs {Object} contains the command line args passed * from the ionic command. * { _: [ 'plugin', 'add', '../phonegap-facebook-plugin' ], variable: [ 'APP_ID=123456789', 'APP_NAME=myApplication' ], '$0': '/usr/local/bin/ionic' } */ State.savePlugin = function savePlugin(pluginArgs) { // console.log('Saveplugin:', pluginArgs); //Expects - either simple ID for plugin registry //or a local path, with or without variables //ionic plugin add org.apache.cordova.splashscreen //ionic plugin add ../phonegap-facebook-plugin --variable APP_ID="123456789" --variable APP_NAME="myApplication" //With a local file locator, we can look at the plugin.xml as it exists //If its a git resource locator, we'll have to look at fetch.json //Idea: this is run after platform add/rm is done var packageJson = State.getPackageJson(), pluginId, pluginInfo = {}, plugin; pluginId = pluginArgs._[2] //grab the org.ionic.keyboard or ./engine/cordova-android // console.log('Plugin id:', pluginId) // State.saveRemotePlugin() if (pluginId.indexOf('/') === -1) { //its just an id State.addOrUpdatePluginToPackageJson(packageJson, pluginId); return State.savePackageJson(packageJson); } //By here, we know its not a registry plugin (git/local) //Its a locator, local file path or https://github.com repo pluginInfo.locator = pluginId; pluginInfo.id = pluginId = State.getPluginFromFetchJsonByLocator(pluginInfo.locator); // console.log('going on to variables now') //If there are no variables, we just add to package, then save if (!pluginArgs.variable) { State.addOrUpdatePluginToPackageJson(packageJson, pluginId, pluginInfo); return State.savePackageJson(packageJson); } //Check and save for variables pluginInfo.variables = {}; for (var i = 0, j = pluginArgs.variable.length; i < j; i++) { //variable: [ 'APP_ID=123456789', 'APP_NAME=myApplication' ] var splits = pluginArgs.variable[i].split('='); pluginInfo.variables[splits[0]] = splits[1]; } // State.checkAndSaveConfigXml(pluginId); // console.log('Saved config.xml'); // console.log('Plugin info to save: ', pluginInfo); State.addOrUpdatePluginToPackageJson(packageJson, pluginId, pluginInfo); console.log('Save plugin to package.json completed'); //By now we assume pluginId is set, and locator might be set. return State.savePackageJson(packageJson); } State.removePlugin = function removePlugin(pluginArgs) { } State.saveXmlFile = function saveXmlFile(xmlData, xmlPath) { try { var xml2js = require('xml2js'); var xmlBuilder = new xml2js.Builder(); var configString = xmlBuilder.buildObject(xmlData); fs.writeFileSync(xmlPath, configString); } catch (ex) { console.log('Could not save your xml file to path:', xmlPath); } } State.getPluginFromFetchJsonByLocator = function getPluginFromFetchJsonByLocator(pluginLocator) { var fetchJson = require(path.resolve('plugins', 'fetch.json')), lookupId, lookupPlugin, isNotRegistyPlugin, hasUrlOrPathOfLocator, pluginId; for (lookupId in fetchJson) { lookupPlugin = fetchJson[lookupId]; isNotRegistyPlugin = lookupPlugin.source.type && lookupPlugin.source.type != 'registry'; hasUrlOrPathOfLocator = lookupPlugin.source.path == pluginLocator || lookupPlugin.source.url == pluginLocator; hasPathWithLeadingDot = lookupPlugin.source && lookupPlugin.source.replace('./', '') == pluginLocator if ((isNotRegistyPlugin && hasUrlOrPathOfLocator) || (isNotRegistyPlugin && hasPathWithLeadingDot)) { pluginId = lookupId; break; } } return pluginId; } State.checkAndSaveConfigXml = function checkAndSaveConfigXml(pluginId) { //Now we have as an array: // <param name="id" value="com.phonegap.plugins.facebookconnect"/> // <param name="installPath" value="../phonegap-facebook-plugin"/> // <param name="APP_ID" value="616451688482285"/> // <param name="APP_NAME" value="hybrid-app"/> //Check to see if platforms exist //bug with cordova is, it doesnt add the entry into //config.xml unless a platform has been added. var configXmlPath = path.resolve('config.xml'); var pluginXmlPath = path.resolve('plugins', pluginId, 'plugin.xml'); var pluginXmlData = State.getXmlData(pluginXmlPath); var configXmlData = State.getXmlData(configXmlPath); // console.log('xml data:', pluginXmlData); var pluginName = pluginXmlData.plugin.name[0], feature, param, paramName; var params = State.getPluginParameters(configXmlData, pluginName); if (!params) { //We need to add them feature = {'$': { name: pluginName }, param: [] }; if (!configXmlData.widget.feature) { configXmlData.widget.feature = []; } for (paramName in pluginInfo.variables) { //Go thru vars and add to param param = { '$': { name: paramName, value: pluginInfo.variables[paramName] } }; console.log('param:', param); feature.param.push(param); } configXmlData.widget.feature.push(feature); //Now to save config.xml file // console.log('xml data:', configXmlData); State.saveXmlFile(configXmlData, configXmlPath); } } State.resetState = function resetState() { shelljs.rm('-rf', ['platforms', 'plugins']); console.log('Removed platforms and plugins'.blue.bold); State.restoreState() .then(function() { console.log('Ionic reset state complete'.green.bold); }) } State.clearState = function clearState() { console.log('Clearing out your Ionic app of platforms, plugins, and package.json entries'.blue.bold); shelljs.rm('-rf', ['platforms', 'plugins']); var packageJson = State.getPackageJson(); packageJson.cordovaPlatforms = packageJson.cordovaPlugins = []; State.savePackageJson(packageJson); console.log('Ionic app state cleared'.green.bold); } IonicTask.prototype.run = function run(ionic) { var self = this, project, stats, projectPath; this.ionic = ionic; try { projectPath = path.resolve('ionic.project'); stats = fs.statSync(projectPath); } catch (ex) { this.ionic.fail('You cannot run any state commands on a project that is not an Ionic project.\nTry adding an ionic.project file or running ionic start to get an application to save or restore'); return; } try { project = IonicProject.load(); } catch (ex) { this.ionic.fail(ex.message); return; } // console.log('Args: ', argv._); switch (argv._[1]) { case 'save': State.saveState(); break; case 'restore': State.restoreState(); break; case 'reset': State.resetState(); break; case 'clear': State.clearState(); break; default: console.log('Please specify a command [ save | restore | reset | clear ] for the state command.'.red.bold); } IonicStats.t(); }; exports.IonicTask = IonicTask;