You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ka...@apache.org on 2014/05/02 20:33:27 UTC
[08/24] Split out cordova-lib: move cordova-cli files
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/metadata/wp8_parser.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/metadata/wp8_parser.js b/cordova-lib/src/cordova/metadata/wp8_parser.js
new file mode 100644
index 0000000..b5f0326
--- /dev/null
+++ b/cordova-lib/src/cordova/metadata/wp8_parser.js
@@ -0,0 +1,287 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+var fs = require('fs'),
+ path = require('path'),
+ et = require('elementtree'),
+ util = require('../util'),
+ events = require('../events'),
+ shell = require('shelljs'),
+ child_process = require('child_process'),
+ Q = require('q'),
+ ConfigParser = require('../ConfigParser'),
+ CordovaError = require('../CordovaError'),
+ xml = require('../xml-helpers'),
+ config = require('../config'),
+ hooker = require('../hooker');
+
+module.exports = function wp8_parser(project) {
+ try {
+ // TODO : Check that it's not a wp8 project?
+ var csproj_file = fs.readdirSync(project).filter(function(e) { return e.match(/\.csproj$/i); })[0];
+ if (!csproj_file) throw new CordovaError('No .csproj file in "'+project+'"');
+ this.wp8_proj_dir = project;
+ this.csproj_path = path.join(this.wp8_proj_dir, csproj_file);
+ this.sln_path = path.join(this.wp8_proj_dir, csproj_file.replace(/\.csproj/, '.sln'));
+ } catch(e) {
+ throw new CordovaError('The provided path "' + project + '" is not a Windows Phone 8 project. ' + e);
+ }
+ this.manifest_path = path.join(this.wp8_proj_dir, 'Properties', 'WMAppManifest.xml');
+};
+
+// Returns a promise.
+module.exports.check_requirements = function(project_root) {
+ events.emit('log', 'Checking wp8 requirements...');
+ var lib_path = path.join(util.libDirectory, 'wp', 'cordova', require('../../platforms').wp8.version, 'wp8');
+ var custom_path = config.has_custom_path(project_root, 'wp8');
+ if (custom_path) {
+ lib_path = path.join(custom_path, 'wp8');
+ }
+ var command = '"' + path.join(lib_path, 'bin', 'check_reqs') + '"';
+ events.emit('verbose', 'Running "' + command + '" (output to follow)');
+ var d = Q.defer();
+ child_process.exec(command, function(err, output, stderr) {
+ events.emit('verbose', output);
+ if (err) {
+ d.reject(new CordovaError('Requirements check failed: ' + output + stderr));
+ } else {
+ d.resolve();
+ }
+ });
+ return d.promise;
+};
+
+module.exports.prototype = {
+ update_from_config:function(config) {
+ //check config parser
+ if (config instanceof ConfigParser) {
+ } else throw new Error('update_from_config requires a ConfigParser object');
+
+ //Get manifest file
+ var manifest = xml.parseElementtreeSync(this.manifest_path);
+
+ //Update app version
+ var version = config.version();
+ manifest.find('.//App').attrib.Version = version;
+
+ // Update app name by editing app title in Properties\WMAppManifest.xml
+ var name = config.name();
+ var prev_name = manifest.find('.//App[@Title]')['attrib']['Title'];
+ if(prev_name != name) {
+ //console.log("Updating app name from " + prev_name + " to " + name);
+ manifest.find('.//App').attrib.Title = name;
+ manifest.find('.//App').attrib.Publisher = name + " Publisher";
+ manifest.find('.//App').attrib.Author = name + " Author";
+ manifest.find('.//PrimaryToken').attrib.TokenID = name;
+ //update name of sln and csproj.
+ name = name.replace(/(\.\s|\s\.|\s+|\.+)/g, '_'); //make it a ligitamate name
+ prev_name = prev_name.replace(/(\.\s|\s\.|\s+|\.+)/g, '_');
+ // TODO: might return .sln.user? (generated file)
+ var sln_name = fs.readdirSync(this.wp8_proj_dir).filter(function(e) { return e.match(/\.sln$/i); })[0];
+ var sln_path = path.join(this.wp8_proj_dir, sln_name);
+ var sln_file = fs.readFileSync(sln_path, 'utf-8');
+ var name_regex = new RegExp(prev_name, "g");
+ fs.writeFileSync(sln_path, sln_file.replace(name_regex, name), 'utf-8');
+ shell.mv('-f', this.csproj_path, path.join(this.wp8_proj_dir, name + '.csproj'));
+ this.csproj_path = path.join(this.wp8_proj_dir, name + '.csproj');
+ shell.mv('-f', sln_path, path.join(this.wp8_proj_dir, name + '.sln'));
+ this.sln_path = path.join(this.wp8_proj_dir, name + '.sln');
+ }
+
+ // Update package name by changing:
+ /* - CordovaAppProj.csproj
+ * - MainPage.xaml
+ * - MainPage.xaml.cs
+ * - App.xaml
+ * - App.xaml.cs
+ */
+ var pkg = config.packageName();
+ var csproj = xml.parseElementtreeSync(this.csproj_path);
+ prev_name = csproj.find('.//RootNamespace').text;
+ if(prev_name != pkg) {
+ //console.log("Updating package name from " + prev_name + " to " + pkg);
+ //CordovaAppProj.csproj
+ csproj.find('.//RootNamespace').text = pkg;
+ csproj.find('.//AssemblyName').text = pkg;
+ csproj.find('.//XapFilename').text = pkg + '.xap';
+ csproj.find('.//SilverlightAppEntry').text = pkg + '.App';
+ fs.writeFileSync(this.csproj_path, csproj.write({indent: 4}), 'utf-8');
+ //MainPage.xaml
+ var mainPageXAML = xml.parseElementtreeSync(path.join(this.wp8_proj_dir, 'MainPage.xaml'));
+ mainPageXAML.getroot().attrib['x:Class'] = pkg + '.MainPage';
+ fs.writeFileSync(path.join(this.wp8_proj_dir, 'MainPage.xaml'), mainPageXAML.write({indent: 4}), 'utf-8');
+ //MainPage.xaml.cs
+ var mainPageCS = fs.readFileSync(path.join(this.wp8_proj_dir, 'MainPage.xaml.cs'), 'utf-8');
+ var namespaceRegEx = new RegExp('namespace ' + prev_name);
+ fs.writeFileSync(path.join(this.wp8_proj_dir, 'MainPage.xaml.cs'), mainPageCS.replace(namespaceRegEx, 'namespace ' + pkg), 'utf-8');
+ //App.xaml
+ var appXAML = xml.parseElementtreeSync(path.join(this.wp8_proj_dir, 'App.xaml'));
+ appXAML.getroot().attrib['x:Class'] = pkg + '.App';
+ fs.writeFileSync(path.join(this.wp8_proj_dir, 'App.xaml'), appXAML.write({indent: 4}), 'utf-8');
+ //App.xaml.cs
+ var appCS = fs.readFileSync(path.join(this.wp8_proj_dir, 'App.xaml.cs'), 'utf-8');
+ fs.writeFileSync(path.join(this.wp8_proj_dir, 'App.xaml.cs'), appCS.replace(namespaceRegEx, 'namespace ' + pkg), 'utf-8');
+ }
+
+ //Write out manifest
+ fs.writeFileSync(this.manifest_path, manifest.write({indent: 4}), 'utf-8');
+
+ // Update icons
+ var icons = config.getIcons('wp8');
+ var platformRoot = this.wp8_proj_dir;
+ var appRoot = util.isCordova(platformRoot);
+
+ // icons, that should be added to platform
+ // @param dest {string} Path to copy icon to, relative to platform root
+ var platformIcons = [
+ {dest: "ApplicationIcon.png", width: 99, height: 99},
+ {dest: "Background.png", width: 159, height: 159},
+ ];
+
+ platformIcons.forEach(function (item) {
+ icon = icons.getIconBySize(item.width, item.height) || icons.getDefault();
+ if (icon){
+ var src = path.join(appRoot, icon.src),
+ dest = path.join(platformRoot, item.dest);
+ events.emit('verbose', 'Copying icon from ' + src + ' to ' + dest);
+ shell.cp('-f', src, dest);
+ }
+ });
+
+ },
+ // Returns the platform-specific www directory.
+ www_dir:function() {
+ return path.join(this.wp8_proj_dir, 'www');
+ },
+ config_xml:function() {
+ return path.join(this.wp8_proj_dir, 'config.xml');
+ },
+ // copy files from merges directory to actual www dir
+ copy_merges:function(merges_sub_path) {
+ var merges_path = path.join(util.appDir(util.isCordova(this.wp8_proj_dir)), 'merges', merges_sub_path);
+ if (fs.existsSync(merges_path)) {
+ var overrides = path.join(merges_path, '*');
+ shell.cp('-rf', overrides, this.www_dir());
+ }
+ },
+
+ // Used for creating platform_www in projects created by older versions.
+ cordovajs_path:function(libDir) {
+ var jsPath = path.join(libDir, '..', 'common', 'www', 'cordova.js');
+ return path.resolve(jsPath);
+ },
+
+ // Replace the www dir with contents of platform_www and app www and updates the csproj file.
+ update_www:function() {
+ var projectRoot = util.isCordova(this.wp8_proj_dir);
+ var app_www = util.projectWww(projectRoot);
+ var platform_www = path.join(this.wp8_proj_dir, 'platform_www');
+
+ // Clear the www dir
+ shell.rm('-rf', this.www_dir());
+ shell.mkdir(this.www_dir());
+ // Copy over all app www assets
+ shell.cp('-rf', path.join(app_www, '*'), this.www_dir());
+
+ // Copy all files from merges directories - wp generic first, then wp8 specific.
+ this.copy_merges('wp');
+ this.copy_merges('wp8');
+
+ // Copy over stock platform www assets (cordova.js)
+ shell.cp('-rf', path.join(platform_www, '*'), this.www_dir());
+ },
+
+ // updates the csproj file to explicitly list all www content.
+ update_csproj:function() {
+ var csproj_xml = xml.parseElementtreeSync(this.csproj_path);
+ // remove any previous references to the www files
+ var item_groups = csproj_xml.findall('ItemGroup');
+ for (var i = 0, l = item_groups.length; i < l; i++) {
+ var group = item_groups[i];
+ var files = group.findall('Content');
+ for (var j = 0, k = files.length; j < k; j++) {
+ var file = files[j];
+ if (file.attrib.Include.substr(0, 3) == 'www') {
+ // remove file reference
+ group.remove(0, file);
+ // remove ItemGroup if empty
+ var new_group = group.findall('Content');
+ if(new_group.length < 1) {
+ csproj_xml.getroot().remove(0, group);
+ }
+ }
+ }
+ }
+
+ // now add all www references back in from the root www folder
+ var www_files = this.folder_contents('www', this.www_dir());
+ for(file in www_files) {
+ var item = new et.Element('ItemGroup');
+ var content = new et.Element('Content');
+ content.attrib.Include = www_files[file];
+ item.append(content);
+ csproj_xml.getroot().append(item);
+ }
+ // save file
+ fs.writeFileSync(this.csproj_path, csproj_xml.write({indent:4}), 'utf-8');
+ },
+ // Returns an array of all the files in the given directory with relative paths
+ // - name : the name of the top level directory (i.e all files will start with this in their path)
+ // - dir : the directory whos contents will be listed under 'name' directory
+ folder_contents:function(name, dir) {
+ var results = [];
+ var folder_dir = fs.readdirSync(dir);
+ for(item in folder_dir) {
+ var stat = fs.statSync(path.join(dir, folder_dir[item]));
+
+ if(stat.isDirectory()) {
+ var sub_dir = this.folder_contents(path.join(name, folder_dir[item]), path.join(dir, folder_dir[item]));
+ //Add all subfolder item paths
+ for(sub_item in sub_dir) {
+ results.push(sub_dir[sub_item]);
+ }
+ }
+ else if(stat.isFile()) {
+ results.push(path.join(name, folder_dir[item]));
+ }
+ // else { it is a FIFO, or a Socket or something ... }
+ }
+ return results;
+ },
+
+ // calls the nessesary functions to update the wp8 project
+ // Returns a promise.
+ update_project:function(cfg) {
+ try {
+ this.update_from_config(cfg);
+ } catch(e) {
+ return Q.reject(e);
+ }
+
+ // trigger an event in case anyone needs to modify the contents of the www folder before we package it.
+ var that = this;
+ var projectRoot = util.isCordova(process.cwd());
+
+ var hooks = new hooker(projectRoot);
+ return hooks.fire('pre_package', { wwwPath:this.www_dir(), platforms: ['wp8'] })
+ .then(function() {
+ that.update_csproj();
+ util.deleteSvnFolders(that.www_dir());
+ });
+ }
+};
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/platform.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/platform.js b/cordova-lib/src/cordova/platform.js
new file mode 100644
index 0000000..bd20780
--- /dev/null
+++ b/cordova-lib/src/cordova/platform.js
@@ -0,0 +1,399 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+var config = require('./config'),
+ cordova = require('../cordova'),
+ cordova_util = require('./util'),
+ ConfigParser = require('./ConfigParser'),
+ util = require('util'),
+ fs = require('fs'),
+ os = require('os'),
+ path = require('path'),
+ hooker = require('./hooker'),
+ events = require('./events'),
+ lazy_load = require('./lazy_load'),
+ CordovaError = require('./CordovaError'),
+ Q = require('q'),
+ platforms = require('../platforms'),
+ superspawn = require('./superspawn'),
+ semver = require('semver'),
+ shell = require('shelljs');
+
+function getVersionFromScript(script, defaultValue) {
+ var versionPromise = Q(defaultValue);
+ if (fs.existsSync(script)) {
+ versionPromise = superspawn.spawn(script);
+ } else {
+ /* if you are here, it's probably because you're in Jasmine: fix your existsSync stub */
+ versionPromise = Q(defaultValue);
+ }
+ return versionPromise;
+}
+
+function add(hooks, projectRoot, targets, opts) {
+ var xml = cordova_util.projectConfig(projectRoot);
+ var cfg = new ConfigParser(xml);
+ if (!targets || !targets.length) {
+ return Q.reject(new CordovaError('No platform specified. Please specify a platform to add. See "platform list".'));
+ }
+ var config_json = config.read(projectRoot);
+ var platformsDir = path.join(projectRoot, 'platforms');
+
+ // The "platforms" dir is safe to delete, it's almost equivalent to
+ // cordova platfrom rm <list of all platforms>
+ if ( !fs.existsSync(platformsDir)) {
+ shell.mkdir('-p', platformsDir);
+ }
+
+ return hooks.fire('before_platform_add', opts)
+ .then(function() {
+ return targets.reduce(function(soFar, t) {
+ return soFar.then(function() {
+ return lazy_load.based_on_config(projectRoot, t)
+ .then(function(libDir) {
+ var template = config_json.lib && config_json.lib[t] && config_json.lib[t].template || null;
+ var copts = null;
+ if ('spawnoutput' in opts) {
+ copts = opts.spawnoutput;
+ }
+ return call_into_create(t, projectRoot, cfg, libDir, template, copts);
+ }, function(err) {
+ throw new CordovaError('Unable to fetch platform ' + t + ': ' + err);
+ });
+ });
+ }, Q());
+ })
+ .then(function() {
+ return hooks.fire('after_platform_add', opts);
+ });
+};
+
+function remove(hooks, projectRoot, targets, opts) {
+ if (!targets || !targets.length) {
+ return Q.reject(new CordovaError('No platform[s] specified. Please specify platform[s] to remove. See "platform list".'));
+ }
+ return hooks.fire('before_platform_rm', opts)
+ .then(function() {
+ targets.forEach(function(target) {
+ shell.rm('-rf', path.join(projectRoot, 'platforms', target));
+ var plugins_json = path.join(projectRoot, 'plugins', target + '.json');
+ if (fs.existsSync(plugins_json)) shell.rm(plugins_json);
+ });
+ }).then(function() {
+ return hooks.fire('after_platform_rm', opts);
+ });
+}
+
+function update(hooks, projectRoot, targets, opts) {
+ // Shell out to the update script provided by the named platform.
+ if (!targets || !targets.length) {
+ return Q.reject(new CordovaError('No platform specified. Please specify a platform to update. See "platform list".'));
+ } else if (targets.length > 1) {
+ return Q.reject(new CordovaError('Platform update can only be executed on one platform at a time.'));
+ } else {
+ var plat = targets[0];
+ var platformPath = path.join(projectRoot, 'platforms', plat);
+ var installed_platforms = cordova_util.listPlatforms(projectRoot);
+ if (installed_platforms.indexOf(plat) < 0) {
+ return Q.reject(new CordovaError('Platform "' + plat + '" is not installed. See "platform list".'));
+ }
+
+ function copyCordovaJs() {
+ var parser = new platforms[plat].parser(platformPath);
+ var platform_www = path.join(platformPath, 'platform_www');
+ shell.mkdir('-p', platform_www);
+ shell.cp('-f', path.join(parser.www_dir(), 'cordova.js'), path.join(platform_www, 'cordova.js'));
+ }
+
+ // First, lazy_load the latest version.
+ return hooks.fire('before_platform_update', opts)
+ .then(function() {
+ return lazy_load.based_on_config(projectRoot, plat);
+ }).then(function(libDir) {
+ // Call the platform's update script.
+ var script = path.join(libDir, 'bin', 'update');
+ return superspawn.spawn(script, [platformPath], { stdio: 'inherit' })
+ .then(function() {
+ // Copy the new cordova.js from www -> platform_www.
+ copyCordovaJs();
+ // Leave it to the update script to log out "updated to v FOO".
+ });
+ });
+ }
+}
+
+function check(hooks, projectRoot) {
+ var platformsText = [],
+ platforms_on_fs = cordova_util.listPlatforms(projectRoot),
+ scratch = path.join(os.tmpdir(), "cordova-platform-check-"+Date.now()),
+ listeners = events._events;
+ events._events = {};
+ var result = Q.defer();
+ cordova.raw.create(scratch)
+ .then(function () {
+ var h = new hooker(scratch);
+ // Acquire the version number of each platform we have installed, and output that too.
+ Q.all(platforms_on_fs.map(function(p) {
+ var d = Q.defer();
+ add(h, scratch, [p], {spawnoutput: {stdio: 'ignore'}})
+ .then(function() {
+ var d_avail = Q.defer(),
+ d_cur = Q.defer();
+ getVersionFromScript(path.join(scratch, 'platforms', p, 'cordova', 'version'), null)
+ .then(function(avail) {
+ if (!avail) {
+ /* Platform version script was silent, we can't work with this */
+ d_avail.resolve('');
+ } else {
+ d_avail.resolve(avail);
+ }
+ })
+ .catch(function () {
+ /* Platform version script failed, we can't work with this */
+ d_avail.resolve('');
+ });
+ getVersionFromScript(path.join(projectRoot, 'platforms', p, 'cordova', 'version'), null)
+ .catch(function () {
+ d_cur.resolve('broken');
+ }).then(function(v) {
+ d_cur.resolve(v || '');
+ });
+ Q.all([d_avail.promise, d_cur.promise]).spread(function (avail, v) {
+ var m;
+ if (avail && (!v || v == 'broken' || semver.gt(avail, v))) {
+ m = p + ' @ ' + (v || 'unknown') + ' could be updated to: ' + avail;
+ platformsText.push(m);
+ }
+ d.resolve(m);
+ })
+ .catch(function () {
+ return '?';
+ })
+ .done();
+ })
+ .catch(function () {
+ /* If a platform doesn't install, then we can't realistically suggest updating */
+ d.resolve();
+ });
+ return d.promise;
+ })).then(function() {
+ var results = '';
+ events._events = listeners;
+ shell.rm('-rf', scratch);
+ if (platformsText) {
+ results = platformsText.filter(function (p) {return !!p}).sort().join('\n');
+ }
+ if (!results) {
+ results = 'All platforms are up-to-date.';
+ }
+ events.emit('results', results);
+ result.resolve();
+ })
+ .done();
+ }).catch(function (){
+ events._events = listeners;
+ shell.rm('-rf', scratch);
+ })
+ .done();
+ return result.promise;
+}
+
+function list(hooks, projectRoot) {
+ var platforms_on_fs = cordova_util.listPlatforms(projectRoot);
+ return hooks.fire('before_platform_ls')
+ .then(function() {
+ // Acquire the version number of each platform we have installed, and output that too.
+ return Q.all(platforms_on_fs.map(function(p) {
+ return getVersionFromScript(path.join(projectRoot, 'platforms', p, 'cordova', 'version'), null)
+ .then(function(v) {
+ if (!v) return p;
+ return p + ' ' + v;
+ }, function(v) {
+ return p + ' broken';
+ });
+ }));
+ }).then(function(platformsText) {
+ var results = 'Installed platforms: ' + platformsText.sort().join(', ') + '\n';
+ var available = Object.getOwnPropertyNames(platforms).filter(function(p) {
+ var platform = platforms[p] || {},
+ hostos = platform.hostos || null;
+ if (!hostos)
+ return true;
+ if (hostos.indexOf('*') >= 0)
+ return true;
+ if (hostos.indexOf(process.platform) >= 0)
+ return true;
+ return false;
+ });
+
+ available = available.filter(function(p) {
+ return platforms_on_fs.indexOf(p) < 0; // Only those not already installed.
+ });
+ results += 'Available platforms: ' + available.sort().join(', ');
+
+ events.emit('results', results);
+ }).then(function() {
+ return hooks.fire('after_platform_ls');
+ });
+}
+
+// Returns a promise.
+module.exports = function platform(command, targets) {
+ var projectRoot = cordova_util.cdProjectRoot();
+
+ var hooks = new hooker(projectRoot);
+
+ if (arguments.length === 0) command = 'ls';
+ if (targets) {
+ if (!(targets instanceof Array)) targets = [targets];
+ var err;
+ targets.forEach(function(t) {
+ if (!(t in platforms)) {
+ err = new CordovaError('Platform "' + t + '" not recognized as a core cordova platform. See "platform list".');
+ }
+ });
+ if (err) return Q.reject(err);
+ } else {
+ if (command == 'add' || command == 'rm') {
+ return Q.reject(new CordovaError('You need to qualify `add` or `remove` with one or more platforms!'));
+ }
+ }
+
+ var opts = {
+ platforms:targets
+ };
+ switch(command) {
+ case 'add':
+ return add(hooks, projectRoot, targets, opts);
+ case 'rm':
+ case 'remove':
+ return remove(hooks, projectRoot, targets, opts);
+ case 'update':
+ case 'up':
+ return update(hooks, projectRoot, targets, opts);
+ case 'check':
+ return check(hooks, projectRoot);
+ case 'ls':
+ case 'list':
+ default:
+ return list(hooks, projectRoot);
+ }
+};
+
+/**
+ * Check Platform Support.
+ *
+ * - {String} `name` of the platform to test.
+ * - Returns a promise, which shows any errors.
+ *
+ */
+
+function supports(project_root, name) {
+ // required parameters
+ if (!name) return Q.reject(new CordovaError('requires a platform name parameter'));
+
+ // check if platform exists
+ var platform = platforms[name];
+ if (!platform) {
+ return Q.reject(new CordovaError(util.format('"%s" platform does not exist', name)));
+ }
+
+ // look up platform meta-data parser
+ var platformParser = platforms[name].parser;
+ if (!platformParser) {
+ return Q.reject(new Error(util.format('"%s" platform parser does not exist', name)));
+ }
+
+ // check for platform support
+ return platformParser.check_requirements(project_root);
+};
+
+// Expose the platform parsers on top of this command
+for (var p in platforms) {
+ module.exports[p] = platforms[p];
+}
+function createOverrides(projectRoot, target) {
+ shell.mkdir('-p', path.join(cordova_util.appDir(projectRoot), 'merges', target));
+};
+
+// Returns a promise.
+function call_into_create(target, projectRoot, cfg, libDir, template_dir, opts) {
+ var output = path.join(projectRoot, 'platforms', target);
+
+ // Check if output directory already exists.
+ if (fs.existsSync(output)) {
+ return Q.reject(new CordovaError('Platform ' + target + ' already added'));
+ } else {
+ // Make sure we have minimum requirements to work with specified platform
+ events.emit('verbose', 'Checking if platform "' + target + '" passes minimum requirements...');
+ /* XXX this is calling the public symbol so that Jasmine Spy can attack it */
+ return module.exports.supports(projectRoot, target)
+ .then(function() {
+ events.emit('log', 'Creating ' + target + ' project...');
+ var bin = path.join(libDir, 'bin', 'create');
+ var args = [];
+ if (target == 'android') {
+ var platformVersion = fs.readFileSync(path.join(libDir, 'VERSION'), 'UTF-8').trim();
+ if (semver.gt(platformVersion, '3.3.0')) {
+ args.push('--cli');
+ }
+ } else if (target == 'ios') {
+ var platformVersion = fs.readFileSync(path.join(libDir, 'CordovaLib', 'VERSION'), 'UTF-8').trim();
+ args.push('--arc');
+ if (semver.gt(platformVersion, '3.3.0')) {
+ args.push('--cli');
+ }
+ }
+
+ var pkg = cfg.packageName().replace(/[^\w.]/g,'_');
+ var name = cfg.name();
+ args.push(output, pkg, name);
+ if (template_dir) {
+ args.push(template_dir);
+ }
+ return superspawn.spawn(bin, args, opts || { stdio: 'inherit' })
+ .then(function() {
+ return require('../cordova').raw.prepare(target);
+ })
+ .then(function() {
+ createOverrides(projectRoot, target);
+ // Install all currently installed plugins into this new platform.
+ var plugins_dir = path.join(projectRoot, 'plugins');
+ var plugins = cordova_util.findPlugins(plugins_dir);
+ var parser = new platforms[target].parser(output);
+ if (!plugins) return Q();
+
+ var plugman = require('plugman');
+ // Install them serially.
+ return plugins.reduce(function(soFar, plugin) {
+ return soFar.then(function() {
+ events.emit('verbose', 'Installing plugin "' + plugin + '" following successful platform add of ' + target);
+ return plugman.raw.install(target, output, path.basename(plugin), plugins_dir);
+ });
+ }, Q());
+ });
+ });
+ }
+}
+
+module.exports.add = add;
+module.exports.remove = remove;
+module.exports.update = update;
+module.exports.check = check;
+module.exports.list = list;
+module.exports.supports = supports;
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/platforms.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/platforms.js b/cordova-lib/src/cordova/platforms.js
new file mode 100644
index 0000000..5bb82f1
--- /dev/null
+++ b/cordova-lib/src/cordova/platforms.js
@@ -0,0 +1,88 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+module.exports = {
+ 'ios' : {
+ hostos : ['darwin'],
+ parser : './src/metadata/ios_parser',
+ url : 'https://git-wip-us.apache.org/repos/asf?p=cordova-ios.git',
+ version: '3.4.1'
+ },
+ 'android' : {
+ parser : './src/metadata/android_parser',
+ url : 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git',
+ version: '3.4.0'
+ },
+ 'ubuntu' : {
+ hostos : ['linux'],
+ parser : './src/metadata/ubuntu_parser',
+ url : 'https://git-wip-us.apache.org/repos/asf?p=cordova-ubuntu.git',
+ version: '3.4.0'
+ },
+ 'amazon-fireos' : {
+ parser : './src/metadata/amazon_fireos_parser',
+ url : 'https://git-wip-us.apache.org/repos/asf?p=cordova-amazon-fireos.git',
+ version: '3.4.0'
+ },
+ 'wp7' : {
+ hostos : ['win32'],
+ parser : './src/metadata/wp7_parser',
+ url : 'https://git-wip-us.apache.org/repos/asf?p=cordova-wp8.git',
+ version: '3.4.0',
+ subdirectory: 'wp7'
+ },
+ 'wp8' : {
+ hostos : ['win32'],
+ parser : './src/metadata/wp8_parser',
+ url : 'https://git-wip-us.apache.org/repos/asf?p=cordova-wp8.git',
+ version: '3.4.0',
+ subdirectory: 'wp8'
+ },
+ 'blackberry10' : {
+ parser : './src/metadata/blackberry10_parser',
+ url : 'https://git-wip-us.apache.org/repos/asf?p=cordova-blackberry.git',
+ version: '3.4.0',
+ subdirectory: 'blackberry10'
+ },
+ 'www':{
+ hostos : [],
+ url : 'https://git-wip-us.apache.org/repos/asf?p=cordova-app-hello-world.git',
+ version: '3.4.0'
+ },
+ 'firefoxos':{
+ parser: './src/metadata/firefoxos_parser',
+ url : 'https://git-wip-us.apache.org/repos/asf?p=cordova-firefoxos.git',
+ version: '3.4.0'
+ },
+ 'windows8':{
+ hostos : ['win32'],
+ parser: './src/metadata/windows8_parser',
+ url : 'https://git-wip-us.apache.org/repos/asf?p=cordova-windows.git',
+ version: '3.4.0',
+ subdirectory: 'windows8'
+ }
+};
+
+var addModuleProperty = require('./src/util').addModuleProperty;
+Object.keys(module.exports).forEach(function(key) {
+ var obj = module.exports[key];
+ if (obj.parser) {
+ addModuleProperty(module, 'parser', obj.parser, false, obj);
+ }
+});
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/plugin.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/plugin.js b/cordova-lib/src/cordova/plugin.js
new file mode 100644
index 0000000..f0e01f7
--- /dev/null
+++ b/cordova-lib/src/cordova/plugin.js
@@ -0,0 +1,210 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+// Returns a promise.
+module.exports = function plugin(command, targets, opts) {
+ var cordova_util = require('./util'),
+ path = require('path'),
+ hooker = require('./hooker'),
+ config = require('./config'),
+ Q = require('q'),
+ CordovaError = require('./CordovaError'),
+ events = require('./events');
+
+ var projectRoot = cordova_util.cdProjectRoot(),
+ err;
+
+ // Dance with all the possible call signatures we've come up over the time. They can be:
+ // 1. plugin() -> list the plugins
+ // 2. plugin(command, Array of targets, maybe opts object)
+ // 3. plugin(command, target1, target2, target3 ... )
+ // The targets are not really targets, they can be a mixture of plugins and options to be passed to plugman.
+
+ command = command || 'ls';
+ targets = targets || [];
+ opts = opts || {};
+ if ( opts.length ) {
+ // This is the case with multiple targes as separate arguments and opts is not opts but another target.
+ targets = Array.prototype.slice.call(arguments, 1);
+ opts = {};
+ }
+ if ( !Array.isArray(targets) ) {
+ // This means we had a single target given as string.
+ targets = [targets];
+ }
+ opts.options = [];
+ opts.plugins = [];
+
+ var hooks = new hooker(projectRoot);
+ var platformList = cordova_util.listPlatforms(projectRoot);
+
+ // Massage plugin name(s) / path(s)
+ var pluginPath, plugins;
+ pluginPath = path.join(projectRoot, 'plugins');
+ plugins = cordova_util.findPlugins(pluginPath);
+ if (!targets || !targets.length) {
+ if (command == 'add' || command == 'rm') {
+ return Q.reject(new CordovaError('You need to qualify `add` or `remove` with one or more plugins!'));
+ } else {
+ targets = [];
+ }
+ }
+
+ //Split targets between plugins and options
+ //Assume everything after a token with a '-' is an option
+ var i;
+ for (i = 0; i < targets.length; i++) {
+ if (targets[i].match(/^-/)) {
+ opts.options = targets.slice(i);
+ break;
+ } else {
+ opts.plugins.push(targets[i]);
+ }
+ }
+
+ switch(command) {
+ case 'add':
+ if (!targets || !targets.length) {
+ return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to add. See "plugin search".'));
+ }
+
+ var config_json = config(projectRoot, {});
+ var searchPath = config_json.plugin_search_path || [];
+ if (typeof opts.searchpath == 'string') {
+ searchPath = opts.searchpath.split(path.delimiter).concat(searchPath);
+ } else if (opts.searchpath) {
+ searchPath = opts.searchpath.concat(searchPath);
+ }
+ // Blank it out to appease unit tests.
+ if (searchPath.length === 0) {
+ searchPath = undefined;
+ }
+
+ return hooks.fire('before_plugin_add', opts)
+ .then(function() {
+ return opts.plugins.reduce(function(soFar, target) {
+ var pluginsDir = path.join(projectRoot, 'plugins');
+ return soFar.then(function() {
+ if (target[target.length - 1] == path.sep) {
+ target = target.substring(0, target.length - 1);
+ }
+
+ // Fetch the plugin first.
+ events.emit('verbose', 'Calling plugman.fetch on plugin "' + target + '"');
+ var plugman = require('plugman');
+ return plugman.raw.fetch(target, pluginsDir, { searchpath: searchPath});
+ })
+ .then(function(dir) {
+ // Iterate (in serial!) over all platforms in the project and install the plugin.
+ return platformList.reduce(function(soFar, platform) {
+ return soFar.then(function() {
+ var platforms = require('../platforms');
+ var platformRoot = path.join(projectRoot, 'platforms', platform),
+ parser = new platforms[platform].parser(platformRoot),
+ options = {
+ cli_variables: {},
+ searchpath: searchPath
+ },
+ tokens,
+ key,
+ i;
+ //parse variables into cli_variables
+ for (i=0; i< opts.options.length; i++) {
+ if (opts.options[i] === "--variable" && typeof opts.options[++i] === "string") {
+ tokens = opts.options[i].split('=');
+ key = tokens.shift().toUpperCase();
+ if (/^[\w-_]+$/.test(key)) {
+ options.cli_variables[key] = tokens.join('=');
+ }
+ }
+ }
+
+ events.emit('verbose', 'Calling plugman.install on plugin "' + dir + '" for platform "' + platform + '" with options "' + JSON.stringify(options) + '"');
+ return plugman.raw.install(platform, platformRoot, path.basename(dir), pluginsDir, options);
+ });
+ }, Q());
+ });
+ }, Q()); // end Q.all
+ }).then(function() {
+ return hooks.fire('after_plugin_add', opts);
+ });
+ break;
+ case 'rm':
+ case 'remove':
+ if (!targets || !targets.length) {
+ return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to remove. See "plugin list".'));
+ }
+ return hooks.fire('before_plugin_rm', opts)
+ .then(function() {
+ return opts.plugins.reduce(function(soFar, target) {
+ // Check if we have the plugin.
+ if (plugins.indexOf(target) < 0) {
+ return Q.reject(new CordovaError('Plugin "' + target + '" is not present in the project. See "plugin list".'));
+ }
+
+ var targetPath = path.join(pluginPath, target);
+ // Iterate over all installed platforms and uninstall.
+ // If this is a web-only or dependency-only plugin, then
+ // there may be nothing to do here except remove the
+ // reference from the platform's plugin config JSON.
+ var plugman = require('plugman');
+ return platformList.reduce(function(soFar, platform) {
+ return soFar.then(function() {
+ var platformRoot = path.join(projectRoot, 'platforms', platform);
+ var platforms = require('../platforms');
+ var parser = new platforms[platform].parser(platformRoot);
+ events.emit('verbose', 'Calling plugman.uninstall on plugin "' + target + '" for platform "' + platform + '"');
+ return plugman.raw.uninstall.uninstallPlatform(platform, platformRoot, target, path.join(projectRoot, 'plugins'));
+ });
+ }, Q())
+ .then(function() {
+ return plugman.raw.uninstall.uninstallPlugin(target, path.join(projectRoot, 'plugins'));
+ });
+ }, Q());
+ }).then(function() {
+ return hooks.fire('after_plugin_rm', opts);
+ });
+ break;
+ case 'search':
+ return hooks.fire('before_plugin_search')
+ .then(function() {
+ var plugman = require('plugman');
+ return plugman.raw.search(opts.plugins);
+ }).then(function(plugins) {
+ for(var plugin in plugins) {
+ events.emit('results', plugins[plugin].name, '-', plugins[plugin].description || 'no description provided');
+ }
+ }).then(function() {
+ return hooks.fire('after_plugin_search');
+ });
+ break;
+ case 'ls':
+ case 'list':
+ default:
+ return hooks.fire('before_plugin_ls')
+ .then(function() {
+ events.emit('results', (plugins.length ? plugins : 'No plugins added. Use `cordova plugin add <plugin>`.'));
+ return hooks.fire('after_plugin_ls')
+ .then(function() {
+ return plugins;
+ });
+ });
+ break;
+ }
+};
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/plugin_parser.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/plugin_parser.js b/cordova-lib/src/cordova/plugin_parser.js
new file mode 100644
index 0000000..d3099c0
--- /dev/null
+++ b/cordova-lib/src/cordova/plugin_parser.js
@@ -0,0 +1,30 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+var xml = require('./xml-helpers'),
+ fs = require('fs');
+
+function plugin_parser(xmlPath) {
+ this.path = xmlPath;
+ this.doc = xml.parseElementtreeSync(xmlPath);
+ this.platforms = this.doc.findall('platform').map(function(p) {
+ return p.attrib.name;
+ });
+}
+
+module.exports = plugin_parser;
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/prepare.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/prepare.js b/cordova-lib/src/cordova/prepare.js
new file mode 100644
index 0000000..eef0123
--- /dev/null
+++ b/cordova-lib/src/cordova/prepare.js
@@ -0,0 +1,187 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+var cordova_util = require('./util'),
+ ConfigParser = require('./ConfigParser'),
+ path = require('path'),
+ platforms = require('../platforms'),
+ platform = require('./platform'),
+ fs = require('fs'),
+ shell = require('shelljs'),
+ et = require('elementtree'),
+ hooker = require('./hooker'),
+ lazy_load = require('./lazy_load'),
+ events = require('./events'),
+ Q = require('q'),
+ plugman = require('plugman'),
+ util = require('util');
+
+// Returns a promise.
+exports = module.exports = function prepare(options) {
+ var projectRoot = cordova_util.cdProjectRoot();
+
+ if (!options) {
+ options = {
+ verbose: false,
+ platforms: [],
+ options: []
+ };
+ }
+
+ options = cordova_util.preProcessOptions(options);
+
+ var xml = cordova_util.projectConfig(projectRoot);
+ var paths = options.platforms.map(function(p) {
+ var platform_path = path.join(projectRoot, 'platforms', p);
+ var parser = (new platforms[p].parser(platform_path));
+ return parser.www_dir();
+ });
+ options.paths = paths;
+
+ var hooks = new hooker(projectRoot);
+ return hooks.fire('before_prepare', options)
+ .then(function() {
+ var cfg = new ConfigParser(xml);
+
+ // Iterate over each added platform
+ return Q.all(options.platforms.map(function(platform) {
+ var platformPath = path.join(projectRoot, 'platforms', platform);
+ return lazy_load.based_on_config(projectRoot, platform)
+ .then(function(libDir) {
+ var parser = new platforms[platform].parser(platformPath),
+ defaults_xml_path = path.join(platformPath, "cordova", "defaults.xml");
+ //If defaults.xml is present, overwrite platform config.xml with it
+ //Otherwise save whatever is there as defaults so it can be restored
+ //or copy project config into platform if none exists
+ if (fs.existsSync(defaults_xml_path)) {
+ shell.cp("-f", defaults_xml_path, parser.config_xml());
+ events.emit('verbose', 'Generating config.xml from defaults for platform "' + platform + '"');
+ } else {
+ if(fs.existsSync(parser.config_xml())){
+ shell.cp("-f", parser.config_xml(), defaults_xml_path);
+ }else{
+ shell.cp("-f",xml,parser.config_xml());
+ }
+ }
+
+ var stagingPath = path.join(platformPath, '.staging');
+ if (fs.existsSync(stagingPath)) {
+ events.emit('log', 'Deleting now-obsolete intermediate directory: ' + stagingPath);
+ shell.rm('-rf', stagingPath);
+ }
+
+ var platform_www = path.join(platformPath, 'platform_www');
+ // Create platfom_www if project was created with older version.
+ if (!fs.existsSync(platform_www)) {
+ shell.mkdir(platform_www);
+ shell.cp(parser.cordovajs_path(libDir), path.join(platform_www, 'cordova.js'));
+ }
+
+ // Replace the existing web assets with the app master versions
+ parser.update_www();
+
+ // Call plugman --prepare for this platform. sets up js-modules appropriately.
+ var plugins_dir = path.join(projectRoot, 'plugins');
+ events.emit('verbose', 'Calling plugman.prepare for platform "' + platform + '"');
+ plugman.prepare(platformPath, platform, plugins_dir);
+
+ // Make sure that config changes for each existing plugin is in place
+ var munger = new plugman.config_changes.PlatformMunger(platform, platformPath, plugins_dir);
+ munger.reapply_global_munge();
+ munger.save_all();
+
+ // Update platform config.xml based on top level config.xml
+ var platform_cfg = new ConfigParser(parser.config_xml());
+ exports._mergeXml(cfg.doc.getroot(), platform_cfg.doc.getroot(), platform, true);
+ platform_cfg.write();
+
+ return parser.update_project(cfg);
+ });
+ })).then(function() {
+ return hooks.fire('after_prepare', options);
+ });
+ });
+};
+
+var BLACKLIST = ["platform"];
+var SINGLETONS = ["content", "author"];
+function mergeXml(src, dest, platform, clobber) {
+ if (BLACKLIST.indexOf(src.tag) === -1) {
+ //Handle attributes
+ Object.getOwnPropertyNames(src.attrib).forEach(function (attribute) {
+ if (clobber || !dest.attrib[attribute]) {
+ dest.attrib[attribute] = src.attrib[attribute];
+ }
+ });
+ //Handle text
+ if (src.text && (clobber || !dest.text)) {
+ dest.text = src.text;
+ }
+ //Handle platform
+ if (platform) {
+ src.findall('platform[@name="' + platform + '"]').forEach(function (platformElement) {
+ platformElement.getchildren().forEach(mergeChild);
+ });
+ }
+
+ //Handle children
+ src.getchildren().forEach(mergeChild);
+
+ function mergeChild (srcChild) {
+ var srcTag = srcChild.tag,
+ destChild = new et.Element(srcTag),
+ foundChild,
+ query = srcTag + "",
+ shouldMerge = true;
+
+ if (BLACKLIST.indexOf(srcTag) === -1) {
+ if (SINGLETONS.indexOf(srcTag) !== -1) {
+ foundChild = dest.find(query);
+ if (foundChild) {
+ destChild = foundChild;
+ dest.remove(0, destChild);
+ }
+ } else {
+ //Check for an exact match and if you find one don't add
+ Object.getOwnPropertyNames(srcChild.attrib).forEach(function (attribute) {
+ query += "[@" + attribute + '="' + srcChild.attrib[attribute] + '"]';
+ });
+ foundChild = dest.find(query);
+ if (foundChild && textMatch(srcChild, foundChild)) {
+ destChild = foundChild;
+ dest.remove(0, destChild);
+ shouldMerge = false;
+ }
+ }
+
+ mergeXml(srcChild, destChild, platform, clobber && shouldMerge);
+ dest.append(destChild);
+ }
+ }
+
+ function textMatch(elm1, elm2) {
+ var text1 = elm1.text ? elm1.text.replace(/\s+/, "") : "",
+ text2 = elm2.text ? elm2.text.replace(/\s+/, "") : "";
+ return (text1 === "" || text1 === text2);
+ }
+ }
+}
+
+// Expose for testing.
+exports._mergeXml = mergeXml;
+
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/run.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/run.js b/cordova-lib/src/cordova/run.js
new file mode 100644
index 0000000..5de3031
--- /dev/null
+++ b/cordova-lib/src/cordova/run.js
@@ -0,0 +1,48 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+/*global require: true, module: true, process: true*/
+/*jslint sloppy: true, white: true, newcap: true */
+
+var cordova_util = require('./util'),
+ path = require('path'),
+ hooker = require('./hooker'),
+ superspawn = require('./superspawn'),
+ Q = require('q');
+
+// Returns a promise.
+module.exports = function run(options) {
+ var projectRoot = cordova_util.cdProjectRoot(),
+ options = cordova_util.preProcessOptions(options);
+
+ var hooks = new hooker(projectRoot);
+ return hooks.fire('before_run', options)
+ .then(function() {
+ // Run a prepare first, then shell out to run
+ return require('../cordova').raw.prepare(options.platforms)
+ }).then(function() {
+ // Deploy in parallel (output gets intermixed though...)
+ return Q.all(options.platforms.map(function(platform) {
+ var cmd = path.join(projectRoot, 'platforms', platform, 'cordova', 'run');
+ return superspawn.spawn(cmd, options.options, { printCommand: true, stdio: 'inherit' });
+ }));
+ }).then(function() {
+ return hooks.fire('after_run', options);
+ });
+};
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/serve.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/serve.js b/cordova-lib/src/cordova/serve.js
new file mode 100644
index 0000000..bd17fa3
--- /dev/null
+++ b/cordova-lib/src/cordova/serve.js
@@ -0,0 +1,224 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+var cordova_util = require('./util'),
+ crypto = require('crypto'),
+ path = require('path'),
+ shell = require('shelljs'),
+ platforms = require('../platforms'),
+ ConfigParser = require('./ConfigParser'),
+ hooker = require('./hooker'),
+ fs = require('fs'),
+ http = require("http"),
+ url = require("url"),
+ mime = require("mime"),
+ zlib = require("zlib");
+
+function launchServer(projectRoot, port) {
+ var server = http.createServer(function(request, response) {
+ function do404() {
+ console.log('404 ' + request.url);
+ response.writeHead(404, {"Content-Type": "text/plain"});
+ response.write("404 Not Found\n");
+ response.end();
+ }
+ function do302(where) {
+ console.log('302 ' + request.url);
+ response.setHeader("Location", where);
+ response.writeHead(302, {"Content-Type": "text/plain"});
+ response.end();
+ }
+ function doRoot() {
+ response.writeHead(200, {"Content-Type": "text/html"});
+ var config = new ConfigParser(cordova_util.projectConfig(projectRoot));
+ response.write("<html><head><title>"+config.name()+"</title></head><body>");
+ response.write("<table border cellspacing=0><thead><caption><h3>Package Metadata</h3></caption></thead><tbody>");
+ for (var c in {"name": true, "packageName": true, "version": true}) {
+ response.write("<tr><th>"+c+"</th><td>"+config[c]()+"</td></tr>");
+ }
+ response.write("</tbody></table>");
+ response.write("<h3>Platforms</h3><ul>");
+ var installed_platforms = cordova_util.listPlatforms(projectRoot);
+ for (var p in platforms) {
+ if (installed_platforms.indexOf(p) >= 0) {
+ response.write("<li><a href='"+p+"/'>"+p+"</a></li>\n");
+ } else {
+ response.write("<li><em>"+p+"</em></li>\n");
+ }
+ }
+ response.write("</ul>");
+ response.write("<h3>Plugins</h3><ul>");
+ var pluginPath = path.join(projectRoot, 'plugins');
+ var plugins = cordova_util.findPlugins(pluginPath);
+ for (var p in plugins) {
+ response.write("<li>"+plugins[p]+"</li>\n");
+ }
+ response.write("</ul>");
+ response.write("</body></html>");
+ response.end();
+ }
+ var urlPath = url.parse(request.url).pathname;
+ var firstSegment = /\/(.*?)\//.exec(urlPath);
+ if (!firstSegment) {
+ return doRoot();
+ }
+ var platformId = firstSegment[1];
+ if (!platforms[platformId]) {
+ return do404();
+ }
+ // Strip the platform out of the path.
+ urlPath = urlPath.slice(platformId.length + 1);
+
+ try {
+ var parser = new platforms[platformId].parser(path.join(projectRoot, 'platforms', platformId));
+ } catch (e) {
+ return do404();
+ }
+ var filePath = null;
+
+ if (urlPath == '/config.xml') {
+ filePath = parser.config_xml();
+ } else if (urlPath == '/project.json') {
+ processAddRequest(request, response, platformId, projectRoot);
+ return;
+ } else if (/^\/www\//.test(urlPath)) {
+ filePath = path.join(parser.www_dir(), urlPath.slice(5));
+ } else if (/^\/+[^\/]*$/.test(urlPath)) {
+ return do302("/" + platformId + "/www/");
+ } else {
+ return do404();
+ }
+
+ fs.exists(filePath, function(exists) {
+ if (exists) {
+ if (fs.statSync(filePath).isDirectory()) {
+ index = path.join(filePath, "index.html");
+ try {
+ if (fs.statSync(index)) {
+ filePath = index;
+ }
+ } catch (e) {}
+ }
+ if (fs.statSync(filePath).isDirectory()) {
+ if (!/\/$/.test(urlPath)) {
+ return do302("/" + platformId + urlPath + "/");
+ }
+ console.log('200 ' + request.url);
+ response.writeHead(200, {"Content-Type": "text/html"});
+ response.write("<html><head><title>Directory listing of "+ urlPath + "</title></head>");
+ response.write("<h3>Items in this directory</h3>");
+ var items = fs.readdirSync(filePath);
+ response.write("<ul>");
+ for (var i in items) {
+ var file = items[i];
+ if (file) {
+ response.write('<li><a href="'+file+'">'+file+'</a></li>\n');
+ }
+ }
+ response.write("</ul>");
+ response.end();
+ } else {
+ var mimeType = mime.lookup(filePath);
+ var respHeaders = {
+ 'Content-Type': mimeType
+ };
+ var readStream = fs.createReadStream(filePath);
+
+ var acceptEncoding = request.headers['accept-encoding'] || '';
+ if (acceptEncoding.match(/\bgzip\b/)) {
+ respHeaders['content-encoding'] = 'gzip';
+ readStream = readStream.pipe(zlib.createGzip());
+ } else if (acceptEncoding.match(/\bdeflate\b/)) {
+ respHeaders['content-encoding'] = 'deflate';
+ readStream = readStream.pipe(zlib.createDeflate());
+ }
+ console.log('200 ' + request.url);
+ response.writeHead(200, respHeaders);
+ readStream.pipe(response);
+ }
+ } else {
+ return do404();
+ }
+ });
+ }).on('listening', function () {
+ console.log("Static file server running on port " + port + " (i.e. http://localhost:" + port + ")\nCTRL + C to shut down");
+ }).on('error', function (e) {
+ if (e && e.toString().indexOf('EADDRINUSE') !== -1) {
+ port++;
+ server.listen(port);
+ } else {
+ console.log("An error occured starting static file server: " + e);
+ }
+ }).listen(port);
+ return server;
+}
+
+function calculateMd5(fileName) {
+ var BUF_LENGTH = 64*1024,
+ buf = new Buffer(BUF_LENGTH),
+ bytesRead = BUF_LENGTH,
+ pos = 0,
+ fdr = fs.openSync(fileName, 'r');
+
+ try {
+ var md5sum = crypto.createHash('md5');
+ while (bytesRead === BUF_LENGTH) {
+ bytesRead = fs.readSync(fdr, buf, 0, BUF_LENGTH, pos);
+ pos += bytesRead;
+ md5sum.update(buf.slice(0, bytesRead));
+ }
+ } finally {
+ fs.closeSync(fdr);
+ }
+ return md5sum.digest('hex');
+}
+
+function processAddRequest(request, response, platformId, projectRoot) {
+ var parser = new platforms[platformId].parser(path.join(projectRoot, 'platforms', platformId));
+ var wwwDir = parser.www_dir();
+ var payload = {
+ 'configPath': '/' + platformId + '/config.xml',
+ 'wwwPath': '/' + platformId + '/www',
+ 'wwwFileList': shell.find(wwwDir)
+ .filter(function(a) { return !fs.statSync(a).isDirectory() && !/(^\.)|(\/\.)/.test(a) })
+ .map(function(a) { return {'path': a.slice(wwwDir.length), 'etag': '' + calculateMd5(a)}; })
+ };
+ console.log('200 ' + request.url);
+ response.writeHead(200, {
+ 'Content-Type': 'application/json',
+ 'Cache-Control': 'no-cache'
+ });
+ response.write(JSON.stringify(payload));
+ response.end();
+}
+
+module.exports = function server(port) {
+ var projectRoot = cordova_util.cdProjectRoot();
+ port = +port || 8000;
+
+ var hooks = new hooker(projectRoot);
+ return hooks.fire('before_serve')
+ .then(function() {
+ // Run a prepare first!
+ return require('../cordova').raw.prepare([]);
+ }).then(function() {
+ launchServer(projectRoot, port);
+ return hooks.fire('after_serve');
+ });
+};
+
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/superspawn.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/superspawn.js b/cordova-lib/src/cordova/superspawn.js
new file mode 100644
index 0000000..60c9fdc
--- /dev/null
+++ b/cordova-lib/src/cordova/superspawn.js
@@ -0,0 +1,139 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var child_process = require('child_process');
+var fs = require('fs');
+var path = require('path');
+var _ = require('underscore');
+var Q = require('q');
+var shell = require('shelljs');
+var events = require('./events');
+var iswin32 = process.platform == 'win32';
+
+// On Windows, spawn() for batch files requires absolute path & having the extension.
+function resolveWindowsExe(cmd) {
+ var winExtensions = ['.exe', '.cmd', '.bat', '.js', '.vbs'];
+ function isValidExe(c) {
+ return winExtensions.indexOf(path.extname(c)) !== -1 && fs.existsSync(c);
+ }
+ if (isValidExe(cmd)) {
+ return cmd;
+ }
+ cmd = shell.which(cmd) || cmd;
+ if (!isValidExe(cmd)) {
+ winExtensions.some(function(ext) {
+ if (fs.existsSync(cmd + ext)) {
+ cmd = cmd + ext;
+ return true;
+ }
+ });
+ }
+ return cmd;
+}
+
+function maybeQuote(a) {
+ if (a.indexOf(' ') != -1) {
+ a = '"' + a + '"';
+ }
+ return a;
+}
+
+// opts:
+// printCommand: Whether to log the command (default: false)
+// stdio: 'default' is to capture output and returning it as a string to success (same as exec)
+// 'ignore' means don't bother capturing it
+// 'inherit' means pipe the input & output. This is required for anything that prompts.
+// env: Map of extra environment variables.
+// cwd: Working directory for the command.
+// Returns a promise that succeeds only for return code = 0.
+exports.spawn = function(cmd, args, opts) {
+ args = args || [];
+ opts = opts || {};
+ var spawnOpts = {};
+ var d = Q.defer();
+
+ if (iswin32) {
+ cmd = resolveWindowsExe(cmd);
+ // If we couldn't find the file, likely we'll end up failing,
+ // but for things like "del", cmd will do the trick.
+ if (path.extname(cmd) != '.exe' && cmd.indexOf(' ') != -1) {
+ // We need to use /s to ensure that spaces are parsed properly with cmd spawned content
+ args = [['/s', '/c', '"'+[cmd].concat(args).map(function(a){if (/^[^"].* .*[^"]/.test(a)) return '"'+a+'"'; return a;}).join(" ")+'"'].join(" ")];
+ cmd = 'cmd';
+ spawnOpts.windowsVerbatimArguments = true;
+ } else if (!fs.existsSync(cmd)) {
+ // We need to use /s to ensure that spaces are parsed properly with cmd spawned content
+ args = ['/s', '/c', cmd].concat(args);
+ }
+ }
+
+ if (opts.stdio == 'ignore') {
+ spawnOpts.stdio = 'ignore';
+ } else if (opts.stdio == 'inherit') {
+ spawnOpts.stdio = 'inherit';
+ }
+ if (opts.cwd) {
+ spawnOpts.cwd = opts.cwd;
+ }
+ if (opts.env) {
+ spawnOpts.env = _.extend(_.extend({}, process.env), opts.env);
+ }
+
+ events.emit(opts.printCommand ? 'log' : 'verbose', 'Running command: ' + maybeQuote(cmd) + ' ' + args.map(maybeQuote).join(' '));
+
+ var child = child_process.spawn(cmd, args, spawnOpts);
+ var capturedOut = '';
+ var capturedErr = '';
+
+ if (child.stdout) {
+ child.stdout.setEncoding('utf8');
+ child.stdout.on('data', function(data) {
+ capturedOut += data;
+ });
+
+ child.stderr.setEncoding('utf8');
+ child.stderr.on('data', function(data) {
+ capturedErr += data;
+ });
+ }
+
+ child.on('close', whenDone);
+ child.on('error', whenDone);
+ function whenDone(arg) {
+ child.removeListener('close', whenDone);
+ child.removeListener('error', whenDone);
+ var code = typeof arg == 'number' ? arg : arg && arg.code;
+
+ events.emit('verbose', 'Command finished with error code ' + code + ': ' + cmd + ' ' + args);
+ if (code === 0) {
+ d.resolve(capturedOut.trim());
+ } else {
+ var errMsg = cmd + ': Command failed with exit code ' + code;
+ if (capturedErr) {
+ errMsg += ' Error output:\n' + capturedErr.trim();
+ }
+ var err = new Error(errMsg);
+ err.code = code;
+ d.reject(err);
+ }
+ }
+
+ return d.promise;
+};
+
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/util.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/util.js b/cordova-lib/src/cordova/util.js
new file mode 100644
index 0000000..2d3b12a
--- /dev/null
+++ b/cordova-lib/src/cordova/util.js
@@ -0,0 +1,214 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+var fs = require('fs'),
+ path = require('path'),
+ CordovaError = require('./CordovaError'),
+ shell = require('shelljs');
+
+// Global configuration paths
+var HOME = process.env[(process.platform.slice(0, 3) == 'win') ? 'USERPROFILE' : 'HOME'];
+var global_config_path = path.join(HOME, '.cordova');
+var lib_path = path.join(global_config_path, 'lib');
+shell.mkdir('-p', lib_path);
+
+function isRootDir(dir) {
+ if (fs.existsSync(path.join(dir, 'www'))) {
+ if (fs.existsSync(path.join(dir, 'config.xml'))) {
+ // For sure is.
+ if (fs.existsSync(path.join(dir, 'platforms'))) {
+ return 2;
+ } else {
+ return 1;
+ }
+ }
+ // Might be (or may be under platforms/).
+ if (fs.existsSync(path.join(dir, 'www', 'config.xml'))) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+exports = module.exports = {
+ globalConfig:global_config_path,
+ libDirectory:lib_path,
+ // Runs up the directory chain looking for a .cordova directory.
+ // IF it is found we are in a Cordova project.
+ // Omit argument to use CWD.
+ isCordova: function isCordova(dir) {
+ if (!dir) {
+ // Prefer PWD over cwd so that symlinked dirs within your PWD work correctly (CB-5687).
+ var pwd = process.env.PWD;
+ var cwd = process.cwd();
+ if (pwd && pwd != cwd) {
+ return this.isCordova(pwd) || this.isCordova(cwd);
+ }
+ return this.isCordova(cwd);
+ }
+ var bestReturnValueSoFar = false;
+ for (var i = 0; i < 1000; ++i) {
+ var result = isRootDir(dir);
+ if (result === 2) {
+ return dir;
+ }
+ if (result === 1) {
+ bestReturnValueSoFar = dir;
+ }
+ var parentDir = path.normalize(path.join(dir, '..'));
+ // Detect fs root.
+ if (parentDir == dir) {
+ return bestReturnValueSoFar;
+ }
+ dir = parentDir;
+ }
+ console.error('Hit an unhandled case in util.isCordova');
+ return false;
+ },
+ // Cd to project root dir and return its path. Throw CordovaError if not in a Corodva project.
+ cdProjectRoot: function() {
+ var projectRoot = this.isCordova();
+ if (!projectRoot) {
+ throw new CordovaError('Current working directory is not a Cordova-based project.');
+ }
+ process.chdir(projectRoot);
+ return projectRoot;
+ },
+ // Recursively deletes .svn folders from a target path
+ deleteSvnFolders:function(dir) {
+ var contents = fs.readdirSync(dir);
+ contents.forEach(function(entry) {
+ var fullpath = path.join(dir, entry);
+ if (fs.statSync(fullpath).isDirectory()) {
+ if (entry == '.svn') {
+ shell.rm('-rf', fullpath);
+ } else module.exports.deleteSvnFolders(fullpath);
+ }
+ });
+ },
+ listPlatforms:function(project_dir) {
+ var core_platforms = require('../platforms');
+ var platforms_dir = path.join(project_dir, 'platforms');
+ if ( !fs.existsSync(platforms_dir)) {
+ return [];
+ }
+ var subdirs = fs.readdirSync(platforms_dir);
+ return subdirs.filter(function(p) {
+ return Object.keys(core_platforms).indexOf(p) > -1;
+ });
+ },
+ // list the directories in the path, ignoring any files
+ findPlugins:function(pluginPath) {
+ var plugins = [],
+ stats;
+
+ if (fs.existsSync(pluginPath)) {
+ plugins = fs.readdirSync(pluginPath).filter(function (fileName) {
+ stats = fs.statSync(path.join(pluginPath, fileName));
+ return fileName != '.svn' && fileName != 'CVS' && stats.isDirectory();
+ });
+ }
+
+ return plugins;
+ },
+ appDir: function(projectDir) {
+ return projectDir;
+ },
+ projectWww: function(projectDir) {
+ return path.join(projectDir, 'www');
+ },
+ projectConfig: function(projectDir) {
+ var rootPath = path.join(projectDir, 'config.xml');
+ var wwwPath = path.join(projectDir, 'www', 'config.xml');
+ if (fs.existsSync(rootPath)) {
+ return rootPath;
+ } else if (fs.existsSync(wwwPath)) {
+ return wwwPath;
+ }
+ return rootPath;
+ },
+ preProcessOptions: function (inputOptions) {
+ /**
+ * Current Desired Arguments
+ * options: {verbose: boolean, platforms: [String], options: [String]}
+ * Accepted Arguments
+ * platformList: [String] -- assume just a list of platforms
+ * platform: String -- assume this is a platform
+ */
+ var result = inputOptions || {};
+ if (Array.isArray(inputOptions)) {
+ result = { platforms: inputOptions };
+ } else if (typeof inputOptions === 'string') {
+ result = { platforms: [inputOptions] };
+ }
+ result.verbose = result.verbose || false;
+ result.platforms = result.platforms || [];
+ result.options = result.options || [];
+
+ var projectRoot = this.isCordova();
+
+ if (!projectRoot) {
+ throw new CordovaError('Current working directory is not a Cordova-based project.');
+ }
+ var projectPlatforms = this.listPlatforms(projectRoot);
+ if (projectPlatforms.length === 0) {
+ throw new CordovaError('No platforms added to this project. Please use `cordova platform add <platform>`.');
+ }
+ if (result.platforms.length === 0) {
+ result.platforms = projectPlatforms;
+ }
+
+ return result;
+ }
+};
+
+// opt_wrap is a boolean: True means that a callback-based wrapper for the promise-based function
+// should be created.
+function addModuleProperty(module, symbol, modulePath, opt_wrap, opt_obj) {
+ var val = null;
+ if (opt_wrap) {
+ module.exports[symbol] = function() {
+ val = val || module.require(modulePath);
+ if (arguments.length && typeof arguments[arguments.length - 1] === 'function') {
+ // If args exist and the last one is a function, it's the callback.
+ var args = Array.prototype.slice.call(arguments);
+ var cb = args.pop();
+ val.apply(module.exports, args).done(function(result) {cb(undefined, result)}, cb);
+ } else {
+ val.apply(module.exports, arguments).done(null, function(err) { throw err; });
+ }
+ };
+ } else {
+ Object.defineProperty(opt_obj || module.exports, symbol, {
+ get : function() { return val = val || module.require(modulePath); },
+ set : function(v) { val = v; }
+ });
+ }
+
+ // Add the module.raw.foo as well.
+ if(module.exports.raw) {
+ Object.defineProperty(module.exports.raw, symbol, {
+ get : function() { return val = val || module.require(modulePath); },
+ set : function(v) { val = v; }
+ });
+ }
+}
+
+addModuleProperty(module, 'plugin_parser', './plugin_parser');
+
+exports.addModuleProperty = addModuleProperty;
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/xml-helpers.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/xml-helpers.js b/cordova-lib/src/cordova/xml-helpers.js
new file mode 100644
index 0000000..70ec96b
--- /dev/null
+++ b/cordova-lib/src/cordova/xml-helpers.js
@@ -0,0 +1,171 @@
+/*
+ *
+ * Copyright 2013 Anis Kadri
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * contains XML utility functions, some of which are specific to elementtree
+ */
+
+var fs = require('fs')
+ , path = require('path')
+ , et = require('elementtree');
+
+module.exports = {
+ // Returns a promise.
+ moveProjFile: function(origFile, projPath) {
+ var src = path.resolve(projPath, origFile)
+ , dest = src.replace('.orig', '');
+
+ var d = Q.defer();
+ fs.createReadStream(src)
+ .pipe(fs.createWriteStream(dest))
+ .on('close', d.resolve)
+ .on('error', d.reject);
+ return d.promise;
+ },
+
+ // compare two et.XML nodes, see if they match
+ // compares tagName, text, attributes and children (recursively)
+ equalNodes: function(one, two) {
+ if (one.tag != two.tag) {
+ return false;
+ } else if (one.text.trim() != two.text.trim()) {
+ return false;
+ } else if (one._children.length != two._children.length) {
+ return false;
+ }
+
+ var oneAttribKeys = Object.keys(one.attrib),
+ twoAttribKeys = Object.keys(two.attrib),
+ i = 0, attribName;
+
+ if (oneAttribKeys.length != twoAttribKeys.length) {
+ return false;
+ }
+
+ for (i; i < oneAttribKeys.length; i++) {
+ attribName = oneAttribKeys[i];
+
+ if (one.attrib[attribName] != two.attrib[attribName]) {
+ return false;
+ }
+ }
+
+ for (i; i < one._children.length; i++) {
+ if (!module.exports.equalNodes(one._children[i], two._children[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ // adds node to doc at selector
+ graftXML: function(doc, nodes, selector) {
+ var parent = resolveParent(doc, selector);
+ if (!parent) return false;
+
+ nodes.forEach(function (node) {
+ // check if child is unique first
+ if (uniqueChild(node, parent)) {
+ parent.append(node);
+ }
+ });
+
+ return true;
+ },
+
+ // removes node from doc at selector
+ pruneXML: function(doc, nodes, selector) {
+ var parent = resolveParent(doc, selector);
+ if (!parent) return false;
+
+ nodes.forEach(function (node) {
+ var matchingKid = null;
+ if ((matchingKid = findChild(node, parent)) != null) {
+ // stupid elementtree takes an index argument it doesn't use
+ // and does not conform to the python lib
+ parent.remove(0, matchingKid);
+ }
+ });
+
+ return true;
+ },
+
+ parseElementtreeSync: function (filename) {
+ var contents = fs.readFileSync(filename, 'utf-8');
+ if(contents) {
+ contents = contents.substring(contents.indexOf("<")); //Windows is the BOM
+ }
+ return new et.ElementTree(et.XML(contents));
+ }
+};
+
+function findChild(node, parent) {
+ var matchingKids = parent.findall(node.tag)
+ , i, j;
+
+ for (i = 0, j = matchingKids.length ; i < j ; i++) {
+ if (module.exports.equalNodes(node, matchingKids[i])) {
+ return matchingKids[i];
+ }
+ }
+ return null;
+}
+
+function uniqueChild(node, parent) {
+ var matchingKids = parent.findall(node.tag)
+ , i = 0;
+
+ if (matchingKids.length == 0) {
+ return true;
+ } else {
+ for (i; i < matchingKids.length; i++) {
+ if (module.exports.equalNodes(node, matchingKids[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+var ROOT = /^\/([^\/]*)/,
+ ABSOLUTE = /^\/([^\/]*)\/(.*)/;
+function resolveParent(doc, selector) {
+ var parent, tagName, subSelector;
+
+ // handle absolute selector (which elementtree doesn't like)
+ if (ROOT.test(selector)) {
+ tagName = selector.match(ROOT)[1];
+ // test for wildcard "any-tag" root selector
+ if (tagName == '*' || tagName === doc._root.tag) {
+ parent = doc._root;
+
+ // could be an absolute path, but not selecting the root
+ if (ABSOLUTE.test(selector)) {
+ subSelector = selector.match(ABSOLUTE)[2];
+ parent = parent.find(subSelector)
+ }
+ } else {
+ return false;
+ }
+ } else {
+ parent = doc.find(selector)
+ }
+ return parent;
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/templates/config.xml
----------------------------------------------------------------------
diff --git a/cordova-lib/templates/config.xml b/cordova-lib/templates/config.xml
new file mode 100644
index 0000000..4a9a4d8
--- /dev/null
+++ b/cordova-lib/templates/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns = "http://www.w3.org/ns/widgets"
+ xmlns:cdv = "http://cordova.apache.org/ns/1.0"
+ id = "io.cordova.hellocordova"
+ version = "0.0.1">
+ <name>Hello Cordova</name>
+
+ <description>
+ A sample Apache Cordova application that responds to the deviceready event.
+ </description>
+
+ <author href="http://cordova.io" email="dev@cordova.apache.org">
+ Apache Cordova Team
+ </author>
+
+ <content src="index.html" />
+
+ <access origin="*" />
+</widget>