You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by st...@apache.org on 2016/05/09 21:18:36 UTC
[1/2] cordova-lib git commit: CB-9858 merging initial fetch work for
plugin and platform fetching
Repository: cordova-lib
Updated Branches:
refs/heads/master b21bcc390 -> 6025a5f23
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-lib/src/cordova/plugin.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/plugin.js b/cordova-lib/src/cordova/plugin.js
index fd75458..74f1cca 100644
--- a/cordova-lib/src/cordova/plugin.js
+++ b/cordova-lib/src/cordova/plugin.js
@@ -1,828 +1,832 @@
-/**
- 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'),
- path = require('path'),
- semver = require('semver'),
- config = require('./config'),
- Q = require('q'),
- CordovaError = require('cordova-common').CordovaError,
- ConfigParser = require('cordova-common').ConfigParser,
- fs = require('fs'),
- shell = require('shelljs'),
- PluginInfoProvider = require('cordova-common').PluginInfoProvider,
- plugman = require('../plugman/plugman'),
- pluginMapper = require('cordova-registry-mapper').newToOld,
- pluginSpec = require('./plugin_spec_parser'),
- events = require('cordova-common').events,
- metadata = require('../plugman/util/metadata'),
- registry = require('../plugman/registry/registry'),
- chainMap = require('../util/promise-util').Q_chainmap,
- pkgJson = require('../../package.json'),
- opener = require('opener');
-
-// For upper bounds in cordovaDependencies
-var UPPER_BOUND_REGEX = /^<\d+\.\d+\.\d+$/;
-
-// Returns a promise.
-module.exports = function plugin(command, targets, opts) {
- // CB-10519 wrap function code into promise so throwing error
- // would result in promise rejection instead of uncaught exception
- return Q().then(function () {
- var projectRoot = cordova_util.cdProjectRoot();
-
- // 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 targets 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.options || [];
- opts.plugins = [];
-
- // TODO: Otherwise HooksRunner will be Object instead of function when run from tests - investigate why
- var HooksRunner = require('../hooks/HooksRunner');
- var hooksRunner = new HooksRunner(projectRoot);
- var config_json = config.read(projectRoot);
- var platformList = cordova_util.listPlatforms(projectRoot);
-
- // Massage plugin name(s) / path(s)
- var pluginPath = path.join(projectRoot, 'plugins');
- var plugins = cordova_util.findPlugins(pluginPath);
- if (!targets || !targets.length) {
- if (command == 'add' || command == 'rm') {
- return Q.reject(new CordovaError('You need to qualify `'+cordova_util.binname+' plugin add` or `'+cordova_util.binname+' plugin 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]);
- }
- }
-
+/**
+ 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'),
+ path = require('path'),
+ semver = require('semver'),
+ config = require('./config'),
+ Q = require('q'),
+ CordovaError = require('cordova-common').CordovaError,
+ ConfigParser = require('cordova-common').ConfigParser,
+ fs = require('fs'),
+ shell = require('shelljs'),
+ PluginInfoProvider = require('cordova-common').PluginInfoProvider,
+ plugman = require('../plugman/plugman'),
+ pluginMapper = require('cordova-registry-mapper').newToOld,
+ pluginSpec = require('./plugin_spec_parser'),
+ events = require('cordova-common').events,
+ metadata = require('../plugman/util/metadata'),
+ registry = require('../plugman/registry/registry'),
+ chainMap = require('../util/promise-util').Q_chainmap,
+ pkgJson = require('../../package.json'),
+ opener = require('opener');
+
+// For upper bounds in cordovaDependencies
+var UPPER_BOUND_REGEX = /^<\d+\.\d+\.\d+$/;
+
+// Returns a promise.
+module.exports = function plugin(command, targets, opts) {
+ // CB-10519 wrap function code into promise so throwing error
+ // would result in promise rejection instead of uncaught exception
+ return Q().then(function () {
+ var projectRoot = cordova_util.cdProjectRoot();
+
+ // 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 targets 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.options || [];
+ opts.plugins = [];
+
+ // TODO: Otherwise HooksRunner will be Object instead of function when run from tests - investigate why
+ var HooksRunner = require('../hooks/HooksRunner');
+ var hooksRunner = new HooksRunner(projectRoot);
+ var config_json = config.read(projectRoot);
+ var platformList = cordova_util.listPlatforms(projectRoot);
+
+ // Massage plugin name(s) / path(s)
+ var pluginPath = path.join(projectRoot, 'plugins');
+ var plugins = cordova_util.findPlugins(pluginPath);
+ if (!targets || !targets.length) {
+ if (command == 'add' || command == 'rm') {
+ return Q.reject(new CordovaError('You need to qualify `'+cordova_util.binname+' plugin add` or `'+cordova_util.binname+' plugin 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]);
+ }
+ }
+
// Assume we don't need to run prepare by default
var shouldRunPrepare = false;
- switch(command) {
- case 'add':
- if (!targets || !targets.length) {
- return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to add. See `'+cordova_util.binname+' plugin search`.'));
- }
-
- var xml = cordova_util.projectConfig(projectRoot);
- var cfg = new ConfigParser(xml);
- 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;
- }
-
- opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
- return hooksRunner.fire('before_plugin_add', opts)
- .then(function() {
- var pluginInfoProvider = new PluginInfoProvider();
- return opts.plugins.reduce(function(soFar, target) {
- return soFar.then(function() {
- if (target[target.length - 1] == path.sep) {
- target = target.substring(0, target.length - 1);
- }
-
- // Fetch the plugin first.
- var fetchOptions = {
- searchpath: searchPath,
- noregistry: opts.noregistry,
- nohooks: opts.nohooks,
- link: opts.link,
- pluginInfoProvider: pluginInfoProvider,
- variables: opts.cli_variables,
- is_top_level: true
- };
-
- return determinePluginTarget(projectRoot, cfg, target, fetchOptions)
- .then(function(resolvedTarget) {
- target = resolvedTarget;
- events.emit('verbose', 'Calling plugman.fetch on plugin "' + target + '"');
- return plugman.raw.fetch(target, pluginPath, fetchOptions);
- })
- .then(function (directory) {
- return pluginInfoProvider.get(directory);
- });
- })
- .then(function(pluginInfo) {
- // Validate top-level required variables
- var pluginVariables = pluginInfo.getPreferences();
- var missingVariables = Object.keys(pluginVariables)
- .filter(function (variableName) {
- // discard variables with default value
- return !(pluginVariables[variableName] || opts.cli_variables[variableName]);
- });
-
- if (missingVariables.length) {
- events.emit('verbose', 'Removing ' + pluginInfo.dir + ' due to installation failure');
- shell.rm('-rf', pluginInfo.dir);
- var msg = 'Variable(s) missing (use: --variable ' + missingVariables.join('=value --variable ') + '=value).';
- return Q.reject(new CordovaError(msg));
- }
-
- // Iterate (in serial!) over all platforms in the project and install the plugin.
- return chainMap(platformList, function (platform) {
- var platformRoot = path.join(projectRoot, 'platforms', platform),
- options = {
- cli_variables: opts.cli_variables || {},
- browserify: opts.browserify || false,
- searchpath: searchPath,
- noregistry: opts.noregistry,
- link: opts.link,
- pluginInfoProvider: pluginInfoProvider,
- // Set up platform to install asset files/js modules to <platform>/platform_www dir
- // instead of <platform>/www. This is required since on each prepare platform's www dir is changed
- // and files from 'platform_www' merged into 'www'. Thus we need to persist these
- // files platform_www directory, so they'll be applied to www on each prepare.
- usePlatformWww: true,
- nohooks: opts.nohooks,
- force: opts.force
- };
-
- events.emit('verbose', 'Calling plugman.install on plugin "' + pluginInfo.dir + '" for platform "' + platform);
+ switch(command) {
+ case 'add':
+ if (!targets || !targets.length) {
+ return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to add. See `'+cordova_util.binname+' plugin search`.'));
+ }
+
+ var xml = cordova_util.projectConfig(projectRoot);
+ var cfg = new ConfigParser(xml);
+ 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;
+ }
+
+ opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
+ return hooksRunner.fire('before_plugin_add', opts)
+ .then(function() {
+ var pluginInfoProvider = new PluginInfoProvider();
+ return opts.plugins.reduce(function(soFar, target) {
+ return soFar.then(function() {
+ if (target[target.length - 1] == path.sep) {
+ target = target.substring(0, target.length - 1);
+ }
+
+ // Fetch the plugin first.
+ var fetchOptions = {
+ searchpath: searchPath,
+ noregistry: opts.noregistry,
+ fetch: opts.fetch || false,
+ save: opts.save,
+ nohooks: opts.nohooks,
+ link: opts.link,
+ pluginInfoProvider: pluginInfoProvider,
+ variables: opts.cli_variables,
+ is_top_level: true
+ };
+
+ return determinePluginTarget(projectRoot, cfg, target, fetchOptions)
+ .then(function(resolvedTarget) {
+ target = resolvedTarget;
+ events.emit('verbose', 'Calling plugman.fetch on plugin "' + target + '"');
+ return plugman.raw.fetch(target, pluginPath, fetchOptions);
+ })
+ .then(function (directory) {
+ return pluginInfoProvider.get(directory);
+ });
+ })
+ .then(function(pluginInfo) {
+ // Validate top-level required variables
+ var pluginVariables = pluginInfo.getPreferences();
+ var missingVariables = Object.keys(pluginVariables)
+ .filter(function (variableName) {
+ // discard variables with default value
+ return !(pluginVariables[variableName] || opts.cli_variables[variableName]);
+ });
+
+ if (missingVariables.length) {
+ events.emit('verbose', 'Removing ' + pluginInfo.dir + ' due to installation failure');
+ shell.rm('-rf', pluginInfo.dir);
+ var msg = 'Variable(s) missing (use: --variable ' + missingVariables.join('=value --variable ') + '=value).';
+ return Q.reject(new CordovaError(msg));
+ }
+
+ // Iterate (in serial!) over all platforms in the project and install the plugin.
+ return chainMap(platformList, function (platform) {
+ var platformRoot = path.join(projectRoot, 'platforms', platform),
+ options = {
+ cli_variables: opts.cli_variables || {},
+ browserify: opts.browserify || false,
+ fetch: opts.fetch || false,
+ save: opts.save,
+ searchpath: searchPath,
+ noregistry: opts.noregistry,
+ link: opts.link,
+ pluginInfoProvider: pluginInfoProvider,
+ // Set up platform to install asset files/js modules to <platform>/platform_www dir
+ // instead of <platform>/www. This is required since on each prepare platform's www dir is changed
+ // and files from 'platform_www' merged into 'www'. Thus we need to persist these
+ // files platform_www directory, so they'll be applied to www on each prepare.
+ usePlatformWww: true,
+ nohooks: opts.nohooks,
+ force: opts.force
+ };
+
+ events.emit('verbose', 'Calling plugman.install on plugin "' + pluginInfo.dir + '" for platform "' + platform);
return plugman.raw.install(platform, platformRoot, path.basename(pluginInfo.dir), pluginPath, options)
.then(function (didPrepare) {
// If platform does not returned anything we'll need
// to trigger a prepare after all plugins installed
if (!didPrepare) shouldRunPrepare = true;
});
- })
- .thenResolve(pluginInfo);
- })
- .then(function(pluginInfo){
- // save to config.xml
- if(saveToConfigXmlOn(config_json, opts)){
- var src = parseSource(target, opts);
- var attributes = {
- name: pluginInfo.id
- };
-
- if (src) {
- attributes.spec = src;
- } else {
- var ver = '~' + pluginInfo.version;
- // Scoped packages need to have the package-spec along with the version
- var parsedSpec = pluginSpec.parse(target);
- if (parsedSpec.scope) {
- attributes.spec = parsedSpec.package + '@' + ver;
- } else {
- attributes.spec = ver;
- }
- }
-
- xml = cordova_util.projectConfig(projectRoot);
- cfg = new ConfigParser(xml);
- cfg.removePlugin(pluginInfo.id);
- cfg.addPlugin(attributes, opts.cli_variables);
- cfg.write();
-
- events.emit('results', 'Saved plugin info for "' + pluginInfo.id + '" to config.xml');
- }
- });
+ })
+ .thenResolve(pluginInfo);
+ })
+ .then(function(pluginInfo){
+ // save to config.xml
+ if(saveToConfigXmlOn(config_json, opts)){
+ var src = parseSource(target, opts);
+ var attributes = {
+ name: pluginInfo.id
+ };
+
+ if (src) {
+ attributes.spec = src;
+ } else {
+ var ver = '~' + pluginInfo.version;
+ // Scoped packages need to have the package-spec along with the version
+ var parsedSpec = pluginSpec.parse(target);
+ if (parsedSpec.scope) {
+ attributes.spec = parsedSpec.package + '@' + ver;
+ } else {
+ attributes.spec = ver;
+ }
+ }
+
+ xml = cordova_util.projectConfig(projectRoot);
+ cfg = new ConfigParser(xml);
+ cfg.removePlugin(pluginInfo.id);
+ cfg.addPlugin(attributes, opts.cli_variables);
+ cfg.write();
+
+ events.emit('results', 'Saved plugin info for "' + pluginInfo.id + '" to config.xml');
+ }
+ });
}, Q());
- }).then(function() {
+ }).then(function() {
// CB-11022 We do not need to run prepare after plugin install until shouldRunPrepare flag is set to true
if (!shouldRunPrepare) {
return Q();
}
- // Need to require right here instead of doing this at the beginning of file
- // otherwise tests are failing without any real reason.
- return require('./prepare').preparePlatforms(platformList, projectRoot, opts);
- }).then(function() {
- opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
- return hooksRunner.fire('after_plugin_add', opts);
- });
- case 'rm':
- case 'remove':
- if (!targets || !targets.length) {
- return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to remove. See `'+cordova_util.binname+' plugin list`.'));
- }
-
- opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
- return hooksRunner.fire('before_plugin_rm', opts)
- .then(function() {
- return opts.plugins.reduce(function(soFar, target) {
- var validatedPluginId = validatePluginId(target, plugins);
- if (!validatedPluginId) {
- return Q.reject(new CordovaError('Plugin "' + target + '" is not present in the project. See `' + cordova_util.binname + ' plugin list`.'));
- }
- target = validatedPluginId;
-
- // 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.
- return platformList.reduce(function(soFar, platform) {
- return soFar.then(function() {
- var platformRoot = path.join(projectRoot, 'platforms', platform);
- events.emit('verbose', 'Calling plugman.uninstall on plugin "' + target + '" for platform "' + platform + '"');
+ // Need to require right here instead of doing this at the beginning of file
+ // otherwise tests are failing without any real reason.
+ return require('./prepare').preparePlatforms(platformList, projectRoot, opts);
+ }).then(function() {
+ opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
+ return hooksRunner.fire('after_plugin_add', opts);
+ });
+ case 'rm':
+ case 'remove':
+ if (!targets || !targets.length) {
+ return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to remove. See `'+cordova_util.binname+' plugin list`.'));
+ }
+
+ opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
+ return hooksRunner.fire('before_plugin_rm', opts)
+ .then(function() {
+ return opts.plugins.reduce(function(soFar, target) {
+ var validatedPluginId = validatePluginId(target, plugins);
+ if (!validatedPluginId) {
+ return Q.reject(new CordovaError('Plugin "' + target + '" is not present in the project. See `' + cordova_util.binname + ' plugin list`.'));
+ }
+ target = validatedPluginId;
+
+ // 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.
+ return platformList.reduce(function(soFar, platform) {
+ return soFar.then(function() {
+ var platformRoot = path.join(projectRoot, 'platforms', platform);
+ events.emit('verbose', 'Calling plugman.uninstall on plugin "' + target + '" for platform "' + platform + '"');
return plugman.raw.uninstall.uninstallPlatform(platform, platformRoot, target, pluginPath)
.then(function (didPrepare) {
// If platform does not returned anything we'll need
// to trigger a prepare after all plugins installed
if (!didPrepare) shouldRunPrepare = true;
});
- });
- }, Q())
- .then(function() {
- // TODO: Should only uninstallPlugin when no platforms have it.
- return plugman.raw.uninstall.uninstallPlugin(target, pluginPath);
- }).then(function(){
- //remove plugin from config.xml
- if(saveToConfigXmlOn(config_json, opts)){
- var configPath = cordova_util.projectConfig(projectRoot);
- if(fs.existsSync(configPath)){//should not happen with real life but needed for tests
- var configXml = new ConfigParser(configPath);
- configXml.removePlugin(target);
- configXml.write();
- events.emit('results', 'config.xml entry for ' +target+ ' is removed');
- }
- }
- })
- .then(function(){
- // Remove plugin from fetch.json
- events.emit('verbose', 'Removing plugin ' + target + ' from fetch.json');
- metadata.remove_fetch_metadata(pluginPath, target);
- });
- }, Q());
- }).then(function () {
+ });
+ }, Q())
+ .then(function() {
+ // TODO: Should only uninstallPlugin when no platforms have it.
+ return plugman.raw.uninstall.uninstallPlugin(target, pluginPath, opts);
+ }).then(function(){
+ //remove plugin from config.xml
+ if(saveToConfigXmlOn(config_json, opts)){
+ var configPath = cordova_util.projectConfig(projectRoot);
+ if(fs.existsSync(configPath)){//should not happen with real life but needed for tests
+ var configXml = new ConfigParser(configPath);
+ configXml.removePlugin(target);
+ configXml.write();
+ events.emit('results', 'config.xml entry for ' +target+ ' is removed');
+ }
+ }
+ })
+ .then(function(){
+ // Remove plugin from fetch.json
+ events.emit('verbose', 'Removing plugin ' + target + ' from fetch.json');
+ metadata.remove_fetch_metadata(pluginPath, target);
+ });
+ }, Q());
+ }).then(function () {
// CB-11022 We do not need to run prepare after plugin install until shouldRunPrepare flag is set to true
if (!shouldRunPrepare) {
return Q();
}
- return require('./prepare').preparePlatforms(platformList, projectRoot, opts);
- }).then(function() {
- opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
- return hooksRunner.fire('after_plugin_rm', opts);
- });
- case 'search':
- return hooksRunner.fire('before_plugin_search', opts)
- .then(function() {
- var link = 'http://cordova.apache.org/plugins/';
- if (opts.plugins.length > 0) {
- var keywords = (opts.plugins).join(' ');
- var query = link + '?q=' + encodeURI(keywords);
- opener(query);
- }
- else {
- opener(link);
- }
-
- return Q.resolve();
- }).then(function() {
- return hooksRunner.fire('after_plugin_search', opts);
- });
- case 'save':
- // save the versions/folders/git-urls of currently installed plugins into config.xml
- return save(projectRoot, opts);
- default:
- return list(projectRoot, hooksRunner);
- }
- });
-};
-
-function determinePluginTarget(projectRoot, cfg, target, fetchOptions) {
- var parsedSpec = pluginSpec.parse(target);
-
- var id = parsedSpec.package || target;
-
- // CB-10975 We need to resolve relative path to plugin dir from app's root before checking whether if it exists
- var maybeDir = cordova_util.fixRelativePath(id);
- if (parsedSpec.version || cordova_util.isUrl(id) || cordova_util.isDirectory(maybeDir)) {
- return Q(target);
- }
-
- // If no version is specified, retrieve the version (or source) from config.xml
- events.emit('verbose', 'No version specified, retrieving version from config.xml');
- var ver = getVersionFromConfigFile(id, cfg);
-
- if (cordova_util.isUrl(ver) || cordova_util.isDirectory(ver) || pluginSpec.parse(ver).scope) {
- return Q(ver);
- }
-
- // If version exists in config.xml, use that
- if (ver) {
- return Q(id + '@' + ver);
- }
-
- // If no version is given at all and we are fetching from npm, we
- // can attempt to use the Cordova dependencies the plugin lists in
- // their package.json
- var shouldUseNpmInfo = !fetchOptions.searchpath && !fetchOptions.noregistry;
-
- if(shouldUseNpmInfo) {
- events.emit('verbose', 'No version given in config.xml, attempting to use plugin engine info');
- }
-
- return (shouldUseNpmInfo ? registry.info([id]) : Q({}))
- .then(function(pluginInfo) {
- return getFetchVersion(projectRoot, pluginInfo, pkgJson.version);
- })
- .then(function(fetchVersion) {
- return fetchVersion ? (id + '@' + fetchVersion) : target;
- });
-}
-
-// Exporting for testing purposes
-module.exports.getFetchVersion = getFetchVersion;
-
-function validatePluginId(pluginId, installedPlugins) {
- if (installedPlugins.indexOf(pluginId) >= 0) {
- return pluginId;
- }
-
- var oldStylePluginId = pluginMapper[pluginId];
- if (oldStylePluginId) {
- events.emit('log', 'Plugin "' + pluginId + '" is not present in the project. Converting value to "' + oldStylePluginId + '" and trying again.');
- return installedPlugins.indexOf(oldStylePluginId) >= 0 ? oldStylePluginId : null;
- }
-
- if (pluginId.indexOf('cordova-plugin-') < 0) {
- return validatePluginId('cordova-plugin-' + pluginId, installedPlugins);
- }
-}
-
-function save(projectRoot, opts){
- var xml = cordova_util.projectConfig(projectRoot);
- var cfg = new ConfigParser(xml);
-
- // First, remove all pre-existing plugins from config.xml
- cfg.getPluginIdList().forEach(function(plugin){
- cfg.removePlugin(plugin);
- });
-
- // Then, save top-level plugins and their sources
- var jsonFile = path.join(projectRoot, 'plugins', 'fetch.json');
- var plugins;
- try {
- // It might be the case that fetch.json file is not yet existent.
- // for example: when we have never ran the command 'cordova plugin add foo' on the project
- // in that case, there's nothing to do except bubble up the error
- plugins = JSON.parse(fs.readFileSync(jsonFile, 'utf-8'));
- } catch (err) {
- return Q.reject(err.message);
- }
-
- Object.keys(plugins).forEach(function(pluginName){
- var plugin = plugins[pluginName];
- var pluginSource = plugin.source;
-
- // If not a top-level plugin, skip it, don't save it to config.xml
- if(!plugin.is_top_level){
- return;
- }
-
- var attribs = {name: pluginName};
- var spec = getSpec(pluginSource, projectRoot, pluginName);
- if (spec) {
- attribs.spec = spec;
- }
-
- var variables = getPluginVariables(plugin.variables);
- cfg.addPlugin(attribs, variables);
- });
- cfg.write();
-
- return Q.resolve();
-}
-
-function getPluginVariables(variables){
- var result = [];
- if(!variables){
- return result;
- }
-
- Object.keys(variables).forEach(function(pluginVar){
- result.push({name: pluginVar, value: variables[pluginVar]});
- });
-
- return result;
-}
-
-function getVersionFromConfigFile(plugin, cfg){
- var parsedSpec = pluginSpec.parse(plugin);
- var pluginEntry = cfg.getPlugin(parsedSpec.id);
- if (!pluginEntry && !parsedSpec.scope) {
- // If the provided plugin id is in the new format (e.g. cordova-plugin-camera), it might be stored in config.xml
- // under the old format (e.g. org.apache.cordova.camera), so check for that.
- var oldStylePluginId = pluginMapper[parsedSpec.id];
- if (oldStylePluginId) {
- pluginEntry = cfg.getPlugin(oldStylePluginId);
- }
- }
- return pluginEntry && pluginEntry.spec;
-}
-
-function list(projectRoot, hooksRunner, opts) {
- var pluginsList = [];
- return hooksRunner.fire('before_plugin_ls', opts)
- .then(function() {
- return getInstalledPlugins(projectRoot);
- })
- .then(function(plugins) {
- if (plugins.length === 0) {
- events.emit('results', 'No plugins added. Use `'+cordova_util.binname+' plugin add <plugin>`.');
- return;
- }
- var pluginsDict = {};
- var lines = [];
- var txt, p;
- for (var i=0; i<plugins.length; i++) {
- p = plugins[i];
- pluginsDict[p.id] = p;
- pluginsList.push(p.id);
- txt = p.id + ' ' + p.version + ' "' + (p.name || p.description) + '"';
- lines.push(txt);
- }
- // Add warnings for deps with wrong versions.
- for (var id in pluginsDict) {
- p = pluginsDict[id];
- for (var depId in p.deps) {
- var dep = pluginsDict[depId];
- //events.emit('results', p.deps[depId].version);
- //events.emit('results', dep != null);
- if (!dep) {
- txt = 'WARNING, missing dependency: plugin ' + id +
- ' depends on ' + depId +
- ' but it is not installed';
- lines.push(txt);
- } else if (!semver.satisfies(dep.version, p.deps[depId].version)) {
- txt = 'WARNING, broken dependency: plugin ' + id +
- ' depends on ' + depId + ' ' + p.deps[depId].version +
- ' but installed version is ' + dep.version;
- lines.push(txt);
- }
- }
- }
- events.emit('results', lines.join('\n'));
- })
- .then(function() {
- return hooksRunner.fire('after_plugin_ls', opts);
- })
- .then(function() {
- return pluginsList;
- });
-}
-
-function getInstalledPlugins(projectRoot) {
- var pluginsDir = path.join(projectRoot, 'plugins');
- // TODO: This should list based off of platform.json, not directories within plugins/
- var pluginInfoProvider = new PluginInfoProvider();
- return pluginInfoProvider.getAllWithinSearchPath(pluginsDir);
-}
-
-function saveToConfigXmlOn(config_json, options){
- options = options || {};
- var autosave = config_json.auto_save_plugins || false;
- return autosave || options.save;
-}
-
-function parseSource(target, opts) {
- var url = require('url');
- var uri = url.parse(target);
- if (uri.protocol && uri.protocol != 'file:' && uri.protocol[1] != ':' && !target.match(/^\w+:\\/)) {
- return target;
- } else {
- var plugin_dir = cordova_util.fixRelativePath(path.join(target, (opts.subdir || '.')));
- if (fs.existsSync(plugin_dir)) {
- return target;
- }
- }
- return null;
-}
-
-function getSpec(pluginSource, projectRoot, pluginName) {
- if (pluginSource.hasOwnProperty('url') || pluginSource.hasOwnProperty('path')) {
- return pluginSource.url || pluginSource.path;
- }
-
- var version = null;
- var scopedPackage = null;
- if (pluginSource.hasOwnProperty('id')) {
- // Note that currently version is only saved here if it was explicitly specified when the plugin was added.
- var parsedSpec = pluginSpec.parse(pluginSource.id);
- version = parsedSpec.version;
- if (version) {
- version = versionString(version);
- }
-
- if (parsedSpec.scope) {
- scopedPackage = parsedSpec.package;
- }
- }
-
- if (!version) {
- // Fallback on getting version from the plugin folder, if it's there
- var pluginInfoProvider = new PluginInfoProvider();
- var dir = path.join(projectRoot, 'plugins', pluginName);
-
- try {
- // pluginInfoProvider.get() will throw if directory does not exist.
- var pluginInfo = pluginInfoProvider.get(dir);
- if (pluginInfo) {
- version = versionString(pluginInfo.version);
- }
- } catch (err) {
- }
- }
-
- if (scopedPackage) {
- version = scopedPackage + '@' + version;
- }
-
- return version;
-}
-
-function versionString(version) {
- var validVersion = semver.valid(version, true);
- if (validVersion) {
- return '~' + validVersion;
- }
-
- if (semver.validRange(version, true)) {
- // Return what we were passed rather than the result of the validRange() call, as that call makes modifications
- // we don't want, like converting '^1.2.3' to '>=1.2.3-0 <2.0.0-0'
- return version;
- }
-
- return null;
-}
-
-/**
- * Gets the version of a plugin that should be fetched for a given project based
- * on the plugin's engine information from NPM and the platforms/plugins installed
- * in the project. The cordovaDependencies object in the package.json's engines
- * entry takes the form of an object that maps plugin versions to a series of
- * constraints and semver ranges. For example:
- *
- * { plugin-version: { constraint: semver-range, ...}, ...}
- *
- * Constraint can be a plugin, platform, or cordova version. Plugin-version
- * can be either a single version (e.g. 3.0.0) or an upper bound (e.g. <3.0.0)
- *
- * @param {string} projectRoot The path to the root directory of the project
- * @param {object} pluginInfo The NPM info of the plugin to be fetched (e.g. the
- * result of calling `registry.info()`)
- * @param {string} cordovaVersion The semver version of cordova-lib
- *
- * @return {Promise} A promise that will resolve to either a string
- * if there is a version of the plugin that this
- * project satisfies or null if there is not
- */
-function getFetchVersion(projectRoot, pluginInfo, cordovaVersion) {
- // Figure out the project requirements
- if (pluginInfo.engines && pluginInfo.engines.cordovaDependencies) {
- var pluginList = getInstalledPlugins(projectRoot);
- var pluginMap = {};
-
- pluginList.forEach(function(plugin) {
- pluginMap[plugin.id] = plugin.version;
- });
-
- return cordova_util.getInstalledPlatformsWithVersions(projectRoot)
- .then(function(platformVersions) {
- return determinePluginVersionToFetch(
- pluginInfo,
- pluginMap,
- platformVersions,
- cordovaVersion);
- });
- } else {
- // If we have no engine, we want to fall back to the default behavior
- events.emit('verbose', 'No plugin engine info found or not using registry, falling back to latest version');
- return Q(null);
- }
-}
-
-function findVersion(versions, version) {
- var cleanedVersion = semver.clean(version);
- for(var i = 0; i < versions.length; i++) {
- if(semver.clean(versions[i]) === cleanedVersion) {
- return versions[i];
- }
- }
- return null;
-}
-
-/*
- * The engine entry maps plugin versions to constraints like so:
- * {
- * '1.0.0' : { 'cordova': '<5.0.0' },
- * '<2.0.0': {
- * 'cordova': '>=5.0.0',
- * 'cordova-ios': '~5.0.0',
- * 'cordova-plugin-camera': '~5.0.0'
- * },
- * '3.0.0' : { 'cordova-ios': '>5.0.0' }
- * }
- *
- * See cordova-spec/plugin_fetch.spec.js for test cases and examples
- */
-function determinePluginVersionToFetch(pluginInfo, pluginMap, platformMap, cordovaVersion) {
- var allVersions = pluginInfo.versions;
- var engine = pluginInfo.engines.cordovaDependencies;
- var name = pluginInfo.name;
-
- // Filters out pre-release versions
- var latest = semver.maxSatisfying(allVersions, '>=0.0.0');
-
- var versions = [];
- var upperBound = null;
- var upperBoundRange = null;
- var upperBoundExists = false;
-
- for(var version in engine) {
- if(semver.valid(semver.clean(version)) && !semver.gt(version, latest)) {
- versions.push(version);
- } else {
- // Check if this is an upperbound; validRange() handles whitespace
- var cleanedRange = semver.validRange(version);
- if(cleanedRange && UPPER_BOUND_REGEX.exec(cleanedRange)) {
- upperBoundExists = true;
- // We only care about the highest upper bound that our project does not support
- if(getFailedRequirements(engine[version], pluginMap, platformMap, cordovaVersion).length !== 0) {
- var maxMatchingUpperBound = cleanedRange.substring(1);
- if (maxMatchingUpperBound && (!upperBound || semver.gt(maxMatchingUpperBound, upperBound))) {
- upperBound = maxMatchingUpperBound;
- upperBoundRange = version;
- }
- }
- } else {
- events.emit('verbose', 'Ignoring invalid version in ' + name + ' cordovaDependencies: ' + version + ' (must be a single version <= latest or an upper bound)');
- }
- }
- }
-
- // If there were no valid requirements, we fall back to old behavior
- if(!upperBoundExists && versions.length === 0) {
- events.emit('verbose', 'Ignoring ' + name + ' cordovaDependencies entry because it did not contain any valid plugin version entries');
- return null;
- }
-
- // Handle the lower end of versions by giving them a satisfied engine
- if(!findVersion(versions, '0.0.0')) {
- versions.push('0.0.0');
- engine['0.0.0'] = {};
- }
-
- // Add an entry after the upper bound to handle the versions above the
- // upper bound but below the next entry. For example: 0.0.0, <1.0.0, 2.0.0
- // needs a 1.0.0 entry that has the same engine as 0.0.0
- if(upperBound && !findVersion(versions, upperBound) && !semver.gt(upperBound, latest)) {
- versions.push(upperBound);
- var below = semver.maxSatisfying(versions, upperBoundRange);
-
- // Get the original entry without trimmed whitespace
- below = below ? findVersion(versions, below) : null;
- engine[upperBound] = below ? engine[below] : {};
- }
-
- // Sort in descending order; we want to start at latest and work back
- versions.sort(semver.rcompare);
-
- for(var i = 0; i < versions.length; i++) {
- if(upperBound && semver.lt(versions[i], upperBound)) {
- // Because we sorted in desc. order, if the upper bound we found
- // applies to this version (and thus the ones below) we can just
- // quit
- break;
- }
-
- var range = i? ('>=' + versions[i] + ' <' + versions[i-1]) : ('>=' + versions[i]);
- var maxMatchingVersion = semver.maxSatisfying(allVersions, range);
-
- if (maxMatchingVersion && getFailedRequirements(engine[versions[i]], pluginMap, platformMap, cordovaVersion).length === 0) {
-
- // Because we sorted in descending order, we can stop searching once
- // we hit a satisfied constraint
- if (maxMatchingVersion !== latest) {
- var failedReqs = getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion);
-
- // Warn the user that we are not fetching latest
- listUnmetRequirements(name, failedReqs);
- events.emit('warn', 'Fetching highest version of ' + name + ' that this project supports: ' + maxMatchingVersion + ' (latest is ' + latest + ')');
- }
- return maxMatchingVersion;
- }
- }
-
- // No version of the plugin is satisfied. In this case, we fall back to
- // fetching the latest version, but also output a warning
- var latestFailedReqs = versions.length > 0 ? getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion) : [];
-
- // If the upper bound is greater than latest, we need to combine its engine
- // requirements with latest to print out in the warning
- if(upperBound && semver.satisfies(latest, upperBoundRange)) {
- var upperFailedReqs = getFailedRequirements(engine[upperBoundRange], pluginMap, platformMap, cordovaVersion);
- upperFailedReqs.forEach(function(failedReq) {
- for(var i = 0; i < latestFailedReqs.length; i++) {
- if(latestFailedReqs[i].dependency === failedReq.dependency) {
- // Not going to overcomplicate things and actually merge the ranges
- latestFailedReqs[i].required += ' AND ' + failedReq.required;
- return;
- }
- }
-
- // There is no req to merge it with
- latestFailedReqs.push(failedReq);
- });
- }
-
- listUnmetRequirements(name, latestFailedReqs);
- events.emit('warn', 'Current project does not satisfy the engine requirements specified by any version of ' + name + '. Fetching latest version of plugin anyway (may be incompatible)');
-
- // No constraints were satisfied
- return null;
-}
-
-
-function getFailedRequirements(reqs, pluginMap, platformMap, cordovaVersion) {
- var failed = [];
-
- for (var req in reqs) {
- if(reqs.hasOwnProperty(req) && typeof req === 'string' && semver.validRange(reqs[req])) {
- var badInstalledVersion = null;
- var trimmedReq = req.trim();
-
- if(pluginMap[trimmedReq] && !semver.satisfies(pluginMap[trimmedReq], reqs[req])) {
- badInstalledVersion = pluginMap[req];
- } else if(trimmedReq === 'cordova' && !semver.satisfies(cordovaVersion, reqs[req])) {
- badInstalledVersion = cordovaVersion;
- } else if(trimmedReq.indexOf('cordova-') === 0) {
- // Might be a platform constraint
- var platform = trimmedReq.substring(8);
- if(platformMap[platform] && !semver.satisfies(platformMap[platform], reqs[req])) {
- badInstalledVersion = platformMap[platform];
- }
- }
-
- if(badInstalledVersion) {
- failed.push({
- dependency: trimmedReq,
- installed: badInstalledVersion.trim(),
- required: reqs[req].trim()
- });
- }
- } else {
- events.emit('verbose', 'Ignoring invalid plugin dependency constraint ' + req + ':' + reqs[req]);
- }
- }
-
- return failed;
-}
-
-function listUnmetRequirements(name, failedRequirements) {
- events.emit('warn', 'Unmet project requirements for latest version of ' + name + ':');
-
- failedRequirements.forEach(function(req) {
- events.emit('warn', ' ' + req.dependency + ' (' + req.installed + ' installed, ' + req.required + ' required)');
- });
-}
+ return require('./prepare').preparePlatforms(platformList, projectRoot, opts);
+ }).then(function() {
+ opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
+ return hooksRunner.fire('after_plugin_rm', opts);
+ });
+ case 'search':
+ return hooksRunner.fire('before_plugin_search', opts)
+ .then(function() {
+ var link = 'http://cordova.apache.org/plugins/';
+ if (opts.plugins.length > 0) {
+ var keywords = (opts.plugins).join(' ');
+ var query = link + '?q=' + encodeURI(keywords);
+ opener(query);
+ }
+ else {
+ opener(link);
+ }
+
+ return Q.resolve();
+ }).then(function() {
+ return hooksRunner.fire('after_plugin_search', opts);
+ });
+ case 'save':
+ // save the versions/folders/git-urls of currently installed plugins into config.xml
+ return save(projectRoot, opts);
+ default:
+ return list(projectRoot, hooksRunner);
+ }
+ });
+};
+
+function determinePluginTarget(projectRoot, cfg, target, fetchOptions) {
+ var parsedSpec = pluginSpec.parse(target);
+
+ var id = parsedSpec.package || target;
+
+ // CB-10975 We need to resolve relative path to plugin dir from app's root before checking whether if it exists
+ var maybeDir = cordova_util.fixRelativePath(id);
+ if (parsedSpec.version || cordova_util.isUrl(id) || cordova_util.isDirectory(maybeDir)) {
+ return Q(target);
+ }
+
+ // If no version is specified, retrieve the version (or source) from config.xml
+ events.emit('verbose', 'No version specified, retrieving version from config.xml');
+ var ver = getVersionFromConfigFile(id, cfg);
+
+ if (cordova_util.isUrl(ver) || cordova_util.isDirectory(ver) || pluginSpec.parse(ver).scope) {
+ return Q(ver);
+ }
+
+ // If version exists in config.xml, use that
+ if (ver) {
+ return Q(id + '@' + ver);
+ }
+
+ // If no version is given at all and we are fetching from npm, we
+ // can attempt to use the Cordova dependencies the plugin lists in
+ // their package.json
+ var shouldUseNpmInfo = !fetchOptions.searchpath && !fetchOptions.noregistry;
+
+ if(shouldUseNpmInfo) {
+ events.emit('verbose', 'No version given in config.xml, attempting to use plugin engine info');
+ }
+
+ return (shouldUseNpmInfo ? registry.info([id]) : Q({}))
+ .then(function(pluginInfo) {
+ return getFetchVersion(projectRoot, pluginInfo, pkgJson.version);
+ })
+ .then(function(fetchVersion) {
+ return fetchVersion ? (id + '@' + fetchVersion) : target;
+ });
+}
+
+// Exporting for testing purposes
+module.exports.getFetchVersion = getFetchVersion;
+
+function validatePluginId(pluginId, installedPlugins) {
+ if (installedPlugins.indexOf(pluginId) >= 0) {
+ return pluginId;
+ }
+
+ var oldStylePluginId = pluginMapper[pluginId];
+ if (oldStylePluginId) {
+ events.emit('log', 'Plugin "' + pluginId + '" is not present in the project. Converting value to "' + oldStylePluginId + '" and trying again.');
+ return installedPlugins.indexOf(oldStylePluginId) >= 0 ? oldStylePluginId : null;
+ }
+
+ if (pluginId.indexOf('cordova-plugin-') < 0) {
+ return validatePluginId('cordova-plugin-' + pluginId, installedPlugins);
+ }
+}
+
+function save(projectRoot, opts){
+ var xml = cordova_util.projectConfig(projectRoot);
+ var cfg = new ConfigParser(xml);
+
+ // First, remove all pre-existing plugins from config.xml
+ cfg.getPluginIdList().forEach(function(plugin){
+ cfg.removePlugin(plugin);
+ });
+
+ // Then, save top-level plugins and their sources
+ var jsonFile = path.join(projectRoot, 'plugins', 'fetch.json');
+ var plugins;
+ try {
+ // It might be the case that fetch.json file is not yet existent.
+ // for example: when we have never ran the command 'cordova plugin add foo' on the project
+ // in that case, there's nothing to do except bubble up the error
+ plugins = JSON.parse(fs.readFileSync(jsonFile, 'utf-8'));
+ } catch (err) {
+ return Q.reject(err.message);
+ }
+
+ Object.keys(plugins).forEach(function(pluginName){
+ var plugin = plugins[pluginName];
+ var pluginSource = plugin.source;
+
+ // If not a top-level plugin, skip it, don't save it to config.xml
+ if(!plugin.is_top_level){
+ return;
+ }
+
+ var attribs = {name: pluginName};
+ var spec = getSpec(pluginSource, projectRoot, pluginName);
+ if (spec) {
+ attribs.spec = spec;
+ }
+
+ var variables = getPluginVariables(plugin.variables);
+ cfg.addPlugin(attribs, variables);
+ });
+ cfg.write();
+
+ return Q.resolve();
+}
+
+function getPluginVariables(variables){
+ var result = [];
+ if(!variables){
+ return result;
+ }
+
+ Object.keys(variables).forEach(function(pluginVar){
+ result.push({name: pluginVar, value: variables[pluginVar]});
+ });
+
+ return result;
+}
+
+function getVersionFromConfigFile(plugin, cfg){
+ var parsedSpec = pluginSpec.parse(plugin);
+ var pluginEntry = cfg.getPlugin(parsedSpec.id);
+ if (!pluginEntry && !parsedSpec.scope) {
+ // If the provided plugin id is in the new format (e.g. cordova-plugin-camera), it might be stored in config.xml
+ // under the old format (e.g. org.apache.cordova.camera), so check for that.
+ var oldStylePluginId = pluginMapper[parsedSpec.id];
+ if (oldStylePluginId) {
+ pluginEntry = cfg.getPlugin(oldStylePluginId);
+ }
+ }
+ return pluginEntry && pluginEntry.spec;
+}
+
+function list(projectRoot, hooksRunner, opts) {
+ var pluginsList = [];
+ return hooksRunner.fire('before_plugin_ls', opts)
+ .then(function() {
+ return getInstalledPlugins(projectRoot);
+ })
+ .then(function(plugins) {
+ if (plugins.length === 0) {
+ events.emit('results', 'No plugins added. Use `'+cordova_util.binname+' plugin add <plugin>`.');
+ return;
+ }
+ var pluginsDict = {};
+ var lines = [];
+ var txt, p;
+ for (var i=0; i<plugins.length; i++) {
+ p = plugins[i];
+ pluginsDict[p.id] = p;
+ pluginsList.push(p.id);
+ txt = p.id + ' ' + p.version + ' "' + (p.name || p.description) + '"';
+ lines.push(txt);
+ }
+ // Add warnings for deps with wrong versions.
+ for (var id in pluginsDict) {
+ p = pluginsDict[id];
+ for (var depId in p.deps) {
+ var dep = pluginsDict[depId];
+ //events.emit('results', p.deps[depId].version);
+ //events.emit('results', dep != null);
+ if (!dep) {
+ txt = 'WARNING, missing dependency: plugin ' + id +
+ ' depends on ' + depId +
+ ' but it is not installed';
+ lines.push(txt);
+ } else if (!semver.satisfies(dep.version, p.deps[depId].version)) {
+ txt = 'WARNING, broken dependency: plugin ' + id +
+ ' depends on ' + depId + ' ' + p.deps[depId].version +
+ ' but installed version is ' + dep.version;
+ lines.push(txt);
+ }
+ }
+ }
+ events.emit('results', lines.join('\n'));
+ })
+ .then(function() {
+ return hooksRunner.fire('after_plugin_ls', opts);
+ })
+ .then(function() {
+ return pluginsList;
+ });
+}
+
+function getInstalledPlugins(projectRoot) {
+ var pluginsDir = path.join(projectRoot, 'plugins');
+ // TODO: This should list based off of platform.json, not directories within plugins/
+ var pluginInfoProvider = new PluginInfoProvider();
+ return pluginInfoProvider.getAllWithinSearchPath(pluginsDir);
+}
+
+function saveToConfigXmlOn(config_json, options){
+ options = options || {};
+ var autosave = config_json.auto_save_plugins || false;
+ return autosave || options.save;
+}
+
+function parseSource(target, opts) {
+ var url = require('url');
+ var uri = url.parse(target);
+ if (uri.protocol && uri.protocol != 'file:' && uri.protocol[1] != ':' && !target.match(/^\w+:\\/)) {
+ return target;
+ } else {
+ var plugin_dir = cordova_util.fixRelativePath(path.join(target, (opts.subdir || '.')));
+ if (fs.existsSync(plugin_dir)) {
+ return target;
+ }
+ }
+ return null;
+}
+
+function getSpec(pluginSource, projectRoot, pluginName) {
+ if (pluginSource.hasOwnProperty('url') || pluginSource.hasOwnProperty('path')) {
+ return pluginSource.url || pluginSource.path;
+ }
+
+ var version = null;
+ var scopedPackage = null;
+ if (pluginSource.hasOwnProperty('id')) {
+ // Note that currently version is only saved here if it was explicitly specified when the plugin was added.
+ var parsedSpec = pluginSpec.parse(pluginSource.id);
+ version = parsedSpec.version;
+ if (version) {
+ version = versionString(version);
+ }
+
+ if (parsedSpec.scope) {
+ scopedPackage = parsedSpec.package;
+ }
+ }
+
+ if (!version) {
+ // Fallback on getting version from the plugin folder, if it's there
+ var pluginInfoProvider = new PluginInfoProvider();
+ var dir = path.join(projectRoot, 'plugins', pluginName);
+
+ try {
+ // pluginInfoProvider.get() will throw if directory does not exist.
+ var pluginInfo = pluginInfoProvider.get(dir);
+ if (pluginInfo) {
+ version = versionString(pluginInfo.version);
+ }
+ } catch (err) {
+ }
+ }
+
+ if (scopedPackage) {
+ version = scopedPackage + '@' + version;
+ }
+
+ return version;
+}
+
+function versionString(version) {
+ var validVersion = semver.valid(version, true);
+ if (validVersion) {
+ return '~' + validVersion;
+ }
+
+ if (semver.validRange(version, true)) {
+ // Return what we were passed rather than the result of the validRange() call, as that call makes modifications
+ // we don't want, like converting '^1.2.3' to '>=1.2.3-0 <2.0.0-0'
+ return version;
+ }
+
+ return null;
+}
+
+/**
+ * Gets the version of a plugin that should be fetched for a given project based
+ * on the plugin's engine information from NPM and the platforms/plugins installed
+ * in the project. The cordovaDependencies object in the package.json's engines
+ * entry takes the form of an object that maps plugin versions to a series of
+ * constraints and semver ranges. For example:
+ *
+ * { plugin-version: { constraint: semver-range, ...}, ...}
+ *
+ * Constraint can be a plugin, platform, or cordova version. Plugin-version
+ * can be either a single version (e.g. 3.0.0) or an upper bound (e.g. <3.0.0)
+ *
+ * @param {string} projectRoot The path to the root directory of the project
+ * @param {object} pluginInfo The NPM info of the plugin to be fetched (e.g. the
+ * result of calling `registry.info()`)
+ * @param {string} cordovaVersion The semver version of cordova-lib
+ *
+ * @return {Promise} A promise that will resolve to either a string
+ * if there is a version of the plugin that this
+ * project satisfies or null if there is not
+ */
+function getFetchVersion(projectRoot, pluginInfo, cordovaVersion) {
+ // Figure out the project requirements
+ if (pluginInfo.engines && pluginInfo.engines.cordovaDependencies) {
+ var pluginList = getInstalledPlugins(projectRoot);
+ var pluginMap = {};
+
+ pluginList.forEach(function(plugin) {
+ pluginMap[plugin.id] = plugin.version;
+ });
+
+ return cordova_util.getInstalledPlatformsWithVersions(projectRoot)
+ .then(function(platformVersions) {
+ return determinePluginVersionToFetch(
+ pluginInfo,
+ pluginMap,
+ platformVersions,
+ cordovaVersion);
+ });
+ } else {
+ // If we have no engine, we want to fall back to the default behavior
+ events.emit('verbose', 'No plugin engine info found or not using registry, falling back to latest version');
+ return Q(null);
+ }
+}
+
+function findVersion(versions, version) {
+ var cleanedVersion = semver.clean(version);
+ for(var i = 0; i < versions.length; i++) {
+ if(semver.clean(versions[i]) === cleanedVersion) {
+ return versions[i];
+ }
+ }
+ return null;
+}
+
+/*
+ * The engine entry maps plugin versions to constraints like so:
+ * {
+ * '1.0.0' : { 'cordova': '<5.0.0' },
+ * '<2.0.0': {
+ * 'cordova': '>=5.0.0',
+ * 'cordova-ios': '~5.0.0',
+ * 'cordova-plugin-camera': '~5.0.0'
+ * },
+ * '3.0.0' : { 'cordova-ios': '>5.0.0' }
+ * }
+ *
+ * See cordova-spec/plugin_fetch.spec.js for test cases and examples
+ */
+function determinePluginVersionToFetch(pluginInfo, pluginMap, platformMap, cordovaVersion) {
+ var allVersions = pluginInfo.versions;
+ var engine = pluginInfo.engines.cordovaDependencies;
+ var name = pluginInfo.name;
+
+ // Filters out pre-release versions
+ var latest = semver.maxSatisfying(allVersions, '>=0.0.0');
+
+ var versions = [];
+ var upperBound = null;
+ var upperBoundRange = null;
+ var upperBoundExists = false;
+
+ for(var version in engine) {
+ if(semver.valid(semver.clean(version)) && !semver.gt(version, latest)) {
+ versions.push(version);
+ } else {
+ // Check if this is an upperbound; validRange() handles whitespace
+ var cleanedRange = semver.validRange(version);
+ if(cleanedRange && UPPER_BOUND_REGEX.exec(cleanedRange)) {
+ upperBoundExists = true;
+ // We only care about the highest upper bound that our project does not support
+ if(getFailedRequirements(engine[version], pluginMap, platformMap, cordovaVersion).length !== 0) {
+ var maxMatchingUpperBound = cleanedRange.substring(1);
+ if (maxMatchingUpperBound && (!upperBound || semver.gt(maxMatchingUpperBound, upperBound))) {
+ upperBound = maxMatchingUpperBound;
+ upperBoundRange = version;
+ }
+ }
+ } else {
+ events.emit('verbose', 'Ignoring invalid version in ' + name + ' cordovaDependencies: ' + version + ' (must be a single version <= latest or an upper bound)');
+ }
+ }
+ }
+
+ // If there were no valid requirements, we fall back to old behavior
+ if(!upperBoundExists && versions.length === 0) {
+ events.emit('verbose', 'Ignoring ' + name + ' cordovaDependencies entry because it did not contain any valid plugin version entries');
+ return null;
+ }
+
+ // Handle the lower end of versions by giving them a satisfied engine
+ if(!findVersion(versions, '0.0.0')) {
+ versions.push('0.0.0');
+ engine['0.0.0'] = {};
+ }
+
+ // Add an entry after the upper bound to handle the versions above the
+ // upper bound but below the next entry. For example: 0.0.0, <1.0.0, 2.0.0
+ // needs a 1.0.0 entry that has the same engine as 0.0.0
+ if(upperBound && !findVersion(versions, upperBound) && !semver.gt(upperBound, latest)) {
+ versions.push(upperBound);
+ var below = semver.maxSatisfying(versions, upperBoundRange);
+
+ // Get the original entry without trimmed whitespace
+ below = below ? findVersion(versions, below) : null;
+ engine[upperBound] = below ? engine[below] : {};
+ }
+
+ // Sort in descending order; we want to start at latest and work back
+ versions.sort(semver.rcompare);
+
+ for(var i = 0; i < versions.length; i++) {
+ if(upperBound && semver.lt(versions[i], upperBound)) {
+ // Because we sorted in desc. order, if the upper bound we found
+ // applies to this version (and thus the ones below) we can just
+ // quit
+ break;
+ }
+
+ var range = i? ('>=' + versions[i] + ' <' + versions[i-1]) : ('>=' + versions[i]);
+ var maxMatchingVersion = semver.maxSatisfying(allVersions, range);
+
+ if (maxMatchingVersion && getFailedRequirements(engine[versions[i]], pluginMap, platformMap, cordovaVersion).length === 0) {
+
+ // Because we sorted in descending order, we can stop searching once
+ // we hit a satisfied constraint
+ if (maxMatchingVersion !== latest) {
+ var failedReqs = getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion);
+
+ // Warn the user that we are not fetching latest
+ listUnmetRequirements(name, failedReqs);
+ events.emit('warn', 'Fetching highest version of ' + name + ' that this project supports: ' + maxMatchingVersion + ' (latest is ' + latest + ')');
+ }
+ return maxMatchingVersion;
+ }
+ }
+
+ // No version of the plugin is satisfied. In this case, we fall back to
+ // fetching the latest version, but also output a warning
+ var latestFailedReqs = versions.length > 0 ? getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion) : [];
+
+ // If the upper bound is greater than latest, we need to combine its engine
+ // requirements with latest to print out in the warning
+ if(upperBound && semver.satisfies(latest, upperBoundRange)) {
+ var upperFailedReqs = getFailedRequirements(engine[upperBoundRange], pluginMap, platformMap, cordovaVersion);
+ upperFailedReqs.forEach(function(failedReq) {
+ for(var i = 0; i < latestFailedReqs.length; i++) {
+ if(latestFailedReqs[i].dependency === failedReq.dependency) {
+ // Not going to overcomplicate things and actually merge the ranges
+ latestFailedReqs[i].required += ' AND ' + failedReq.required;
+ return;
+ }
+ }
+
+ // There is no req to merge it with
+ latestFailedReqs.push(failedReq);
+ });
+ }
+
+ listUnmetRequirements(name, latestFailedReqs);
+ events.emit('warn', 'Current project does not satisfy the engine requirements specified by any version of ' + name + '. Fetching latest version of plugin anyway (may be incompatible)');
+
+ // No constraints were satisfied
+ return null;
+}
+
+
+function getFailedRequirements(reqs, pluginMap, platformMap, cordovaVersion) {
+ var failed = [];
+
+ for (var req in reqs) {
+ if(reqs.hasOwnProperty(req) && typeof req === 'string' && semver.validRange(reqs[req])) {
+ var badInstalledVersion = null;
+ var trimmedReq = req.trim();
+
+ if(pluginMap[trimmedReq] && !semver.satisfies(pluginMap[trimmedReq], reqs[req])) {
+ badInstalledVersion = pluginMap[req];
+ } else if(trimmedReq === 'cordova' && !semver.satisfies(cordovaVersion, reqs[req])) {
+ badInstalledVersion = cordovaVersion;
+ } else if(trimmedReq.indexOf('cordova-') === 0) {
+ // Might be a platform constraint
+ var platform = trimmedReq.substring(8);
+ if(platformMap[platform] && !semver.satisfies(platformMap[platform], reqs[req])) {
+ badInstalledVersion = platformMap[platform];
+ }
+ }
+
+ if(badInstalledVersion) {
+ failed.push({
+ dependency: trimmedReq,
+ installed: badInstalledVersion.trim(),
+ required: reqs[req].trim()
+ });
+ }
+ } else {
+ events.emit('verbose', 'Ignoring invalid plugin dependency constraint ' + req + ':' + reqs[req]);
+ }
+ }
+
+ return failed;
+}
+
+function listUnmetRequirements(name, failedRequirements) {
+ events.emit('warn', 'Unmet project requirements for latest version of ' + name + ':');
+
+ failedRequirements.forEach(function(req) {
+ events.emit('warn', ' ' + req.dependency + ' (' + req.installed + ' installed, ' + req.required + ' required)');
+ });
+}
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-lib/src/plugman/fetch.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/fetch.js b/cordova-lib/src/plugman/fetch.js
index 9583688..6db2723 100644
--- a/cordova-lib/src/plugman/fetch.js
+++ b/cordova-lib/src/plugman/fetch.js
@@ -32,8 +32,9 @@ var shell = require('shelljs'),
registry = require('./registry/registry'),
pluginMappernto = require('cordova-registry-mapper').newToOld,
pluginMapperotn = require('cordova-registry-mapper').oldToNew,
- pluginSpec = require('../cordova/plugin_spec_parser');
-var cordovaUtil = require('../cordova/util');
+ pluginSpec = require('../cordova/plugin_spec_parser'),
+ fetch = require('cordova-fetch'),
+ cordovaUtil = require('../cordova/util');
// Cache of PluginInfo objects for plugins in search path.
var localPlugins = null;
@@ -44,7 +45,6 @@ module.exports = fetchPlugin;
function fetchPlugin(plugin_src, plugins_dir, options) {
// Ensure the containing directory exists.
shell.mkdir('-p', plugins_dir);
-
options = options || {};
options.subdir = options.subdir || '.';
options.searchpath = options.searchpath || [];
@@ -67,16 +67,26 @@ function fetchPlugin(plugin_src, plugins_dir, options) {
options.git_ref = result[1];
if (result[2])
options.subdir = result[2];
+ //if --fetch was used, throw error for subdirectories
+ if (result[2] && options.fetch) {
+ return Q.reject(new CordovaError('--fetch does not support subdirectories'));
+ }
// Recurse and exit with the new options and truncated URL.
var new_dir = plugin_src.substring(0, plugin_src.indexOf('#'));
- return fetchPlugin(new_dir, plugins_dir, options);
+
+ //skip the return if user asked for --fetch
+ //cordova-fetch doesn't need to strip out git-ref
+ if(!options.fetch) {
+ return fetchPlugin(new_dir, plugins_dir, options);
+ }
}
}
return Q.when().then(function() {
- // If it looks like a network URL, git clone it.
- if ( uri.protocol && uri.protocol != 'file:' && uri.protocol[1] != ':' && !plugin_src.match(/^\w+:\\/)) {
+ // If it looks like a network URL, git clone it
+ // skip git cloning if user passed in --fetch flag
+ if ( uri.protocol && uri.protocol != 'file:' && uri.protocol[1] != ':' && !plugin_src.match(/^\w+:\\/) && !options.fetch) {
events.emit('log', 'Fetching plugin "' + plugin_src + '" via git clone');
if (options.link) {
events.emit('log', '--link is not supported for git URLs and will be ignored');
@@ -158,7 +168,18 @@ function fetchPlugin(plugin_src, plugins_dir, options) {
if (newID) {
events.emit('warn', 'Notice: ' + parsedSpec.id + ' has been automatically converted to ' + newID + ' to be fetched from npm. This is due to our old plugins registry shutting down.');
}
- P = registry.fetch([plugin_src]);
+ //use cordova-fetch if --fetch was passed in
+ if(options.fetch) {
+ var projectRoot = path.join(plugins_dir, '..');
+ //Plugman projects need to go up two directories to reach project root.
+ //Plugman projects have an options.projectRoot variable
+ if(options.projectRoot) {
+ projectRoot = options.projectRoot;
+ }
+ P = fetch(plugin_src, projectRoot, options);
+ } else {
+ P = registry.fetch([plugin_src]);
+ }
skipCopyingPlugin = false;
}
}
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-lib/src/plugman/plugman.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/plugman.js b/cordova-lib/src/plugman/plugman.js
index 2627b38..4265bc6 100644
--- a/cordova-lib/src/plugman/plugman.js
+++ b/cordova-lib/src/plugman/plugman.js
@@ -104,9 +104,12 @@ plugman.commands = {
var opts = {
subdir: '.',
cli_variables: cli_variables,
+ fetch: cli_opts.fetch || false,
+ save: cli_opts.save || false,
www_dir: cli_opts.www,
searchpath: cli_opts.searchpath,
- link: cli_opts.link
+ link: cli_opts.link,
+ projectRoot: cli_opts.project
};
var p = Q();
cli_opts.plugin.forEach(function (pluginSrc) {
@@ -128,8 +131,14 @@ plugman.commands = {
var p = Q();
cli_opts.plugin.forEach(function (pluginSrc) {
+ var opts = {
+ www_dir: cli_opts.www,
+ save: cli_opts.save || false,
+ fetch: cli_opts.fetch || false,
+ projectRoot: cli_opts.project
+ };
p = p.then(function () {
- return plugman.raw.uninstall(cli_opts.platform, cli_opts.project, pluginSrc, cli_opts.plugins_dir, { www_dir: cli_opts.www });
+ return plugman.raw.uninstall(cli_opts.platform, cli_opts.project, pluginSrc, cli_opts.plugins_dir, opts);
});
});
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-lib/src/plugman/uninstall.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/uninstall.js b/cordova-lib/src/plugman/uninstall.js
index 87bcdca..c987d49 100644
--- a/cordova-lib/src/plugman/uninstall.js
+++ b/cordova-lib/src/plugman/uninstall.js
@@ -34,6 +34,7 @@ var path = require('path'),
HooksRunner = require('../hooks/HooksRunner'),
cordovaUtil = require('../cordova/util'),
pluginMapper = require('cordova-registry-mapper').oldToNew,
+ npmUninstall = require('cordova-fetch').uninstall,
pluginSpec = require('../cordova/plugin_spec_parser');
var superspawn = require('cordova-common').superspawn;
@@ -115,15 +116,31 @@ module.exports.uninstallPlugin = function(id, plugins_dir, options) {
return Q();
}
+ /*
+ * Deletes plugin from plugins directory and
+ * node_modules directory if --fetch was supplied.
+ *
+ * @param {String} id the id of the plugin being removed
+ *
+ * @return {Promise||Error} Returns a empty promise or a promise of doing the npm uninstall
+ */
var doDelete = function(id) {
var plugin_dir = path.join(plugins_dir, id);
if ( !fs.existsSync(plugin_dir) ) {
events.emit('verbose', 'Plugin "'+ id +'" already removed ('+ plugin_dir +')');
return Q();
}
-
+
shell.rm('-rf', plugin_dir);
events.emit('verbose', 'Deleted "'+ id +'"');
+
+ if(options.fetch) {
+ //remove plugin from node_modules directory
+ return npmUninstall(id, options.projectRoot, options);
+ }
+
+ return Q();
+
};
// We've now lost the metadata for the plugins that have been uninstalled, so we can't use that info.
@@ -201,7 +218,7 @@ module.exports.uninstallPlugin = function(id, plugins_dir, options) {
});
});
- var i, plugin_id, msg;
+ var i, plugin_id, msg, delArray = [];
for(i in toDelete) {
plugin_id = toDelete[i];
@@ -221,11 +238,11 @@ module.exports.uninstallPlugin = function(id, plugins_dir, options) {
}
}
}
-
- doDelete(plugin_id);
+ //create an array of promises
+ delArray.push(doDelete(plugin_id));
}
-
- return Q();
+ //return promise.all
+ return Q.all(delArray);
};
// possible options: cli_variables, www_dir, is_top_level
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org
[2/2] cordova-lib git commit: CB-9858 merging initial fetch work for
plugin and platform fetching
Posted by st...@apache.org.
CB-9858 merging initial fetch work for plugin and platform fetching
Project: http://git-wip-us.apache.org/repos/asf/cordova-lib/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-lib/commit/6025a5f2
Tree: http://git-wip-us.apache.org/repos/asf/cordova-lib/tree/6025a5f2
Diff: http://git-wip-us.apache.org/repos/asf/cordova-lib/diff/6025a5f2
Branch: refs/heads/master
Commit: 6025a5f23c00284fb3416d8042ff5d738bf03db3
Parents: b21bcc3
Author: Steve Gill <st...@gmail.com>
Authored: Mon May 9 14:18:21 2016 -0700
Committer: Steve Gill <st...@gmail.com>
Committed: Mon May 9 14:18:21 2016 -0700
----------------------------------------------------------------------
.gitignore | 1 +
.travis.yml | 2 +
appveyor.yml | 2 +
cordova-fetch/.jshintrc | 12 +
cordova-fetch/README.md | 36 +
cordova-fetch/RELEASENOTES.md | 22 +
cordova-fetch/index.js | 236 ++++
cordova-fetch/package.json | 43 +
cordova-fetch/spec/fetch.spec.js | 300 +++++
cordova-fetch/spec/helpers.js | 16 +
cordova-fetch/spec/support/jasmine.json | 11 +
cordova-fetch/spec/testpkg.json | 11 +
cordova-lib/spec-cordova/helpers.js | 3 +
cordova-lib/spec-cordova/platform.spec.js | 108 ++
cordova-lib/src/cordova/platform.js | 31 +-
cordova-lib/src/cordova/plugin.js | 1608 ++++++++++++------------
cordova-lib/src/plugman/fetch.js | 35 +-
cordova-lib/src/plugman/plugman.js | 13 +-
cordova-lib/src/plugman/uninstall.js | 29 +-
19 files changed, 1700 insertions(+), 819 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index ea05522..dae2aca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ src/plugman/defaults.json
cordova-lib/src/plugman/defaults.json
cordova-lib/spec-plugman/plugins/recursivePlug/demo/fetch.json
cordova-lib/spec-plugman/plugins/recursivePlug/demo/test-recursive
+cordova-fetch/test/temp
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index bb404a1..e9f15ee 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,8 +12,10 @@ install:
- npm link ../cordova-js
- npm link ../cordova-common
- npm link ../cordova-serve
+ - npm link ../cordova-fetch
- npm install
script:
- "(cd ../cordova-common && npm test)"
+ - "(cd ../cordova-common && npm test)"
- "npm run ci"
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/appveyor.yml
----------------------------------------------------------------------
diff --git a/appveyor.yml b/appveyor.yml
index 36e2b26..dc26ed4 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -7,6 +7,7 @@ install:
- npm link ../cordova-js
- npm link ../cordova-common
- npm link ../cordova-serve
+ - npm link ../cordova-fetch
- npm install
build: off
@@ -15,4 +16,5 @@ test_script:
- node --version
- npm --version
- "cd ../cordova-common && npm test"
+ - "cd ../cordova-fetch && npm test"
- "cd ../cordova-lib && npm test"
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/.jshintrc
----------------------------------------------------------------------
diff --git a/cordova-fetch/.jshintrc b/cordova-fetch/.jshintrc
new file mode 100644
index 0000000..62edb5c
--- /dev/null
+++ b/cordova-fetch/.jshintrc
@@ -0,0 +1,12 @@
+{
+ "node": true,
+ "bitwise": true,
+ "undef": true,
+ "trailing": true,
+ "quotmark": true,
+ "indent": 4,
+ "unused": "vars",
+ "latedef": "nofunc",
+ "-W030": false,
+ "jasmine": true
+}
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/README.md
----------------------------------------------------------------------
diff --git a/cordova-fetch/README.md b/cordova-fetch/README.md
new file mode 100644
index 0000000..5cc75e9
--- /dev/null
+++ b/cordova-fetch/README.md
@@ -0,0 +1,36 @@
+<!--
+#
+# 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.
+#
+-->
+
+# cordova-fetch
+
+This module is used for fetching modules from npm and gitURLs. It fetches the modules via `npm install`.
+
+Usage:
+```
+var fetch = require('cordova-fetch');
+
+fetch(spec, dest, opts);
+```
+
+`spec` can be a string containg a npm `packageID` or a `git URL`.
+`dest` is string of the directory location you wish to `npm install` these modules.
+`opts` is an Object of options cordova fetch handles. Currently, fetch only support the `save` option.
+ eg. `{'save':true}`
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/RELEASENOTES.md
----------------------------------------------------------------------
diff --git a/cordova-fetch/RELEASENOTES.md b/cordova-fetch/RELEASENOTES.md
new file mode 100644
index 0000000..305cdc6
--- /dev/null
+++ b/cordova-fetch/RELEASENOTES.md
@@ -0,0 +1,22 @@
+<!--
+#
+# 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.
+#
+-->
+# Cordova-fetch Release Notes
+
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/index.js
----------------------------------------------------------------------
diff --git a/cordova-fetch/index.js b/cordova-fetch/index.js
new file mode 100644
index 0000000..0e3c3fe
--- /dev/null
+++ b/cordova-fetch/index.js
@@ -0,0 +1,236 @@
+/**
+ 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 Q = require('q');
+var shell = require('shelljs');
+var superspawn = require('cordova-common').superspawn;
+var events = require('cordova-common').events;
+var depls = require('dependency-ls');
+var path = require('path');
+var fs = require('fs');
+var CordovaError = require('cordova-common').CordovaError;
+var isUrl = require('is-url');
+
+/*
+ * A function that npm installs a module from npm or a git url
+ *
+ * @param {String} target the packageID or git url
+ * @param {String} dest destination of where to install the module
+ * @param {Object} opts [opts={save:true}] options to pass to fetch module
+ *
+ * @return {String|Promise} Returns string of the absolute path to the installed module.
+ *
+ */
+module.exports = function(target, dest, opts) {
+ var fetchArgs = ['install'];
+ opts = opts || {};
+ var tree1;
+
+ //check if npm is installed
+ return isNpmInstalled()
+ .then(function() {
+ if(dest && target) {
+ //add target to fetchArgs Array
+ fetchArgs.push(target);
+
+ //append node_modules to dest if it doesn't come included
+ if (path.basename(dest) !== 'node_modules') {
+ dest = path.resolve(path.join(dest, 'node_modules'));
+ }
+
+ //create dest if it doesn't exist
+ if(!fs.existsSync(dest)) {
+ shell.mkdir('-p', dest);
+ }
+
+ } else return Q.reject(new CordovaError('Need to supply a target and destination'));
+
+ //set the directory where npm install will be run
+ opts.cwd = dest;
+
+ //if user added --save flag, pass it to npm install command
+ if(opts.save) {
+ events.emit('verbose', 'saving');
+ fetchArgs.push('--save');
+ }
+
+
+ //Grab json object of installed modules before npm install
+ return depls(dest);
+ })
+ .then(function(depTree) {
+ tree1 = depTree;
+
+ //install new module
+ return superspawn.spawn('npm', fetchArgs, opts);
+ })
+ .then(function(output) {
+ //Grab object of installed modules after npm install
+ return depls(dest);
+ })
+ .then(function(depTree2) {
+ var tree2 = depTree2;
+
+ //getJsonDiff will fail if the module already exists in node_modules.
+ //Need to use trimID in that case.
+ //This could happen on a platform update.
+ var id = getJsonDiff(tree1, tree2) || trimID(target);
+
+ return getPath(id, dest);
+ })
+ .fail(function(err){
+ return Q.reject(new CordovaError(err));
+ });
+};
+
+
+/*
+ * Takes two JSON objects and returns the key of the new property as a string.
+ * If a module already exists in node_modules, the diff will be blank.
+ * cordova-fetch will use trimID in that case.
+ *
+ * @param {Object} obj1 json object representing installed modules before latest npm install
+ * @param {Object} obj2 json object representing installed modules after latest npm install
+ *
+ * @return {String} String containing the key value of the difference between the two objects
+ *
+ */
+function getJsonDiff(obj1, obj2) {
+ var result = '';
+
+ //regex to filter out peer dependency warnings from result
+ var re = /UNMET PEER DEPENDENCY/;
+
+ for (var key in obj2) {
+ //if it isn't a unmet peer dependency, continue
+ if (key.search(re) === -1) {
+ if(obj2[key] != obj1[key]) result = key;
+ }
+ }
+ return result;
+}
+
+/*
+ * Takes the specified target and returns the moduleID
+ * If the git repoName is different than moduleID, then the
+ * output from this function will be incorrect. This is the
+ * backup way to get ID. getJsonDiff is the preferred way to
+ * get the moduleID of the installed module.
+ *
+ * @param {String} target target that was passed into cordova-fetch.
+ * can be moduleID, moduleID@version or gitURL
+ *
+ * @return {String} ID moduleID without version.
+ */
+function trimID(target) {
+ var parts;
+
+ //If GITURL, set target to repo name
+ if (isUrl(target)) {
+ var re = /.*\/(.*).git/;
+ parts = target.match(re);
+ target = parts[1];
+ }
+
+ //strip away everything after '@'
+ if(target.indexOf('@') != -1) {
+ parts = target.split('@');
+ target = parts[0];
+ }
+
+ return target;
+}
+
+/*
+ * Takes the moduleID and destination and returns an absolute path to the module
+ *
+ * @param {String} id the packageID
+ * @param {String} dest destination of where to fetch the modules
+ *
+ * @return {String|Error} Returns the absolute url for the module or throws a error
+ *
+ */
+
+function getPath(id, dest) {
+ var finalDest = path.resolve(path.join(dest, id));
+
+ //Sanity check it exists
+ if(fs.existsSync(finalDest)){
+ return finalDest;
+ } else return Q.reject(new CordovaError('Failed to get absolute path to installed module'));
+}
+
+
+/*
+ * Checks to see if npm is installed on the users system
+ * @return {Promise|Error} Returns true or a cordova error.
+ */
+
+function isNpmInstalled() {
+ if(!shell.which('npm')) {
+ return Q.reject(new CordovaError('"npm" command line tool is not installed: make sure it is accessible on your PATH.'));
+ }
+ return Q();
+}
+
+/*
+ * A function that deletes the target from node_modules and runs npm uninstall
+ *
+ * @param {String} target the packageID
+ * @param {String} dest destination of where to uninstall the module from
+ * @param {Object} opts [opts={save:true}] options to pass to npm uninstall
+ *
+ * @return {Promise|Error} Returns a promise with the npm uninstall output or an error.
+ *
+ */
+module.exports.uninstall = function(target, dest, opts) {
+ var fetchArgs = ['uninstall'];
+ opts = opts || {};
+
+ //check if npm is installed on the system
+ return isNpmInstalled()
+ .then(function() {
+ if(dest && target) {
+ //add target to fetchArgs Array
+ fetchArgs.push(target);
+ } else return Q.reject(new CordovaError('Need to supply a target and destination'));
+
+ //set the directory where npm uninstall will be run
+ opts.cwd = dest;
+
+ //if user added --save flag, pass it to npm uninstall command
+ if(opts.save) {
+ fetchArgs.push('--save');
+ }
+
+ //run npm uninstall, this will remove dependency
+ //from package.json if --save was used.
+ return superspawn.spawn('npm', fetchArgs, opts);
+ })
+ .then(function(res) {
+ var pluginDest = path.join(dest, 'node_modules', target);
+ if(fs.existsSync(pluginDest)) {
+ shell.rm('-rf', pluginDest);
+ }
+ return res;
+ })
+ .fail(function(err) {
+ return Q.reject(new CordovaError(err));
+ });
+};
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/package.json
----------------------------------------------------------------------
diff --git a/cordova-fetch/package.json b/cordova-fetch/package.json
new file mode 100644
index 0000000..2e7e330
--- /dev/null
+++ b/cordova-fetch/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "cordova-fetch",
+ "version": "1.0.0-dev",
+ "description": "Apache Cordova fetch module. Fetches from git and npm.",
+ "main": "index.js",
+ "repository": {
+ "type": "git",
+ "url": "git://git-wip-us.apache.org/repos/asf/cordova-lib.git"
+ },
+ "keywords": [
+ "cordova",
+ "fetch",
+ "apache",
+ "ecosystem:cordova",
+ "cordova:tool"
+ ],
+ "author": "Apache Software Foundation",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://issues.apache.org/jira/browse/CB",
+ "email": "dev@cordova.apache.org"
+ },
+ "dependencies": {
+ "cordova-common": "^1.0.0",
+ "dependency-ls": "^1.0.0",
+ "is-url": "^1.2.1",
+ "q": "^1.4.1",
+ "shelljs": "^0.7.0"
+ },
+ "devDependencies": {
+ "jasmine": "^2.4.1",
+ "jshint": "^2.8.0"
+ },
+ "scripts": {
+ "test": "npm run jshint && npm run jasmine",
+ "jshint": "jshint index.js spec/fetch.spec.js",
+ "jasmine": "jasmine spec/fetch.spec.js"
+ },
+ "engines": {
+ "node": ">= 0.12.0",
+ "npm": ">= 2.5.1"
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/spec/fetch.spec.js
----------------------------------------------------------------------
diff --git a/cordova-fetch/spec/fetch.spec.js b/cordova-fetch/spec/fetch.spec.js
new file mode 100644
index 0000000..2f6513a
--- /dev/null
+++ b/cordova-fetch/spec/fetch.spec.js
@@ -0,0 +1,300 @@
+/**
+ 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 fetch = require('../index.js');
+var uninstall = require('../index.js').uninstall;
+var shell = require('shelljs');
+var path = require('path');
+var fs = require('fs');
+var helpers = require('./helpers.js');
+
+describe('platform fetch/uninstall tests via npm & git', function () {
+
+ var tmpDir = helpers.tmpDir('plat_fetch');
+ var opts = {};
+
+ beforeEach(function() {
+ process.chdir(tmpDir);
+ });
+
+ afterEach(function() {
+ process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
+ shell.rm('-rf', tmpDir);
+ });
+
+ it('should fetch and uninstall a cordova platform via npm & git', function(done) {
+
+ fetch('cordova-android', tmpDir, opts)
+ .then(function(result) {
+ var pkgJSON = require(path.join(result,'package.json'));
+ expect(result).toBeDefined();
+ expect(fs.existsSync(result)).toBe(true);
+ expect(pkgJSON.name).toBe('cordova-android');
+
+ return uninstall('cordova-android', tmpDir, opts);
+ })
+ .then(function() {
+ expect(fs.existsSync(path.join(tmpDir,'node_modules', 'cordova-android'))).toBe(false);
+
+ return fetch('https://github.com/apache/cordova-ios.git', tmpDir, opts);
+ })
+ .then(function(result) {
+ var pkgJSON = require(path.join(result,'package.json'));
+ expect(result).toBeDefined();
+ expect(fs.existsSync(result)).toBe(true);
+ expect(pkgJSON.name).toBe('cordova-ios');
+
+ return uninstall('cordova-ios', tmpDir, opts);
+ })
+ .then(function() {
+ expect(fs.existsSync(path.join(tmpDir,'node_modules', 'cordova-ios'))).toBe(false);
+ })
+ .fail(function(err) {
+ console.error(err);
+ expect(err).toBeUndefined();
+ })
+ .fin(done);
+ }, 60000);
+});
+
+describe('platform fetch/uninstall test via npm & git tags with --save', function () {
+
+ var tmpDir = helpers.tmpDir('plat_fetch_save');
+ var opts = {'save':true};
+
+ beforeEach(function() {
+ //copy package.json from spec directory to tmpDir
+ shell.cp('spec/testpkg.json', path.join(tmpDir,'package.json'));
+ process.chdir(tmpDir);
+ });
+
+ afterEach(function() {
+ process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
+ shell.rm('-rf', tmpDir);
+ });
+
+ it('should fetch and uninstall a cordova platform via npm & git tags/branches', function(done) {
+ fetch('cordova-android@5.1.1', tmpDir, opts)
+ .then(function(result) {
+ var pkgJSON = require(path.join(result,'package.json'));
+ expect(result).toBeDefined();
+ expect(fs.existsSync(result)).toBe(true);
+ expect(pkgJSON.name).toBe('cordova-android');
+ expect(pkgJSON.version).toBe('5.1.1');
+
+ var rootPJ = require(path.join(tmpDir,'package.json'));
+ expect(rootPJ.dependencies['cordova-android']).toBe('^5.1.1');
+
+ return uninstall('cordova-android', tmpDir, opts);
+ })
+ .then(function() {
+ var rootPJ = JSON.parse(fs.readFileSync(path.join(tmpDir,'package.json'), 'utf8'));
+ expect(Object.keys(rootPJ.dependencies).length).toBe(0);
+ expect(fs.existsSync(path.join(tmpDir,'node_modules', 'cordova-android'))).toBe(false);
+
+ return fetch('https://github.com/apache/cordova-ios.git#rel/4.1.1', tmpDir, opts);
+ })
+ .then(function(result) {
+ var pkgJSON = require(path.join(result,'package.json'));
+ expect(result).toBeDefined();
+ expect(fs.existsSync(result)).toBe(true);
+ expect(pkgJSON.name).toBe('cordova-ios');
+ expect(pkgJSON.version).toBe('4.1.1');
+
+ var rootPJ = JSON.parse(fs.readFileSync(path.join(tmpDir,'package.json'), 'utf8'));
+ expect(rootPJ.dependencies['cordova-ios']).toBe('git+https://github.com/apache/cordova-ios.git#rel/4.1.1');
+
+ return uninstall('cordova-ios', tmpDir, opts);
+ })
+ .then(function() {
+ var rootPJ = JSON.parse(fs.readFileSync(path.join(tmpDir,'package.json'), 'utf8'));
+ expect(Object.keys(rootPJ.dependencies).length).toBe(0);
+ expect(fs.existsSync(path.join(tmpDir,'node_modules', 'cordova-ios'))).toBe(false);
+
+ return fetch('https://github.com/apache/cordova-android.git#4.1.x', tmpDir, opts);
+ })
+ .then(function(result) {
+ var pkgJSON = JSON.parse(fs.readFileSync(path.join(result,'package.json'), 'utf8'));
+ expect(result).toBeDefined();
+ expect(fs.existsSync(result)).toBe(true);
+ expect(pkgJSON.name).toBe('cordova-android');
+ expect(pkgJSON.version).toBe('4.1.1');
+
+ var rootPJ = JSON.parse(fs.readFileSync(path.join(tmpDir,'package.json'), 'utf8'));
+ expect(rootPJ.dependencies['cordova-android']).toBe('git+https://github.com/apache/cordova-android.git#4.1.x');
+
+ return uninstall('cordova-android', tmpDir, opts);
+ })
+ .fail(function(err) {
+ console.error(err);
+ expect(err).toBeUndefined();
+ })
+ .fin(done);
+ }, 60000);
+});
+
+describe('plugin fetch/uninstall test with --save', function () {
+
+ var tmpDir = helpers.tmpDir('plug_fetch_save');
+ var opts = {'save':true};
+
+ beforeEach(function() {
+ //copy package.json from spec directory to tmpDir
+ shell.cp('spec/testpkg.json', path.join(tmpDir,'package.json'));
+ process.chdir(tmpDir);
+ });
+
+ afterEach(function() {
+ process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
+ shell.rm('-rf', tmpDir);
+ });
+
+ it('should fetch and uninstall a cordova plugin via git commit sha', function(done) {
+ fetch('https://github.com/apache/cordova-plugin-contacts.git#7db612115755c2be73a98dda76ff4c5fd9d8a575', tmpDir, opts)
+ .then(function(result) {
+ var pkgJSON = require(path.join(result,'package.json'));
+ expect(result).toBeDefined();
+ expect(fs.existsSync(result)).toBe(true);
+ expect(pkgJSON.name).toBe('cordova-plugin-contacts');
+ expect(pkgJSON.version).toBe('2.0.2-dev');
+
+ var rootPJ = require(path.join(tmpDir,'package.json'));
+ expect(rootPJ.dependencies['cordova-plugin-contacts']).toBe('git+https://github.com/apache/cordova-plugin-contacts.git#7db612115755c2be73a98dda76ff4c5fd9d8a575');
+
+ return uninstall('cordova-plugin-contacts', tmpDir, opts);
+ })
+ .then(function() {
+ var rootPJ = JSON.parse(fs.readFileSync(path.join(tmpDir,'package.json'), 'utf8'));
+ expect(Object.keys(rootPJ.dependencies).length).toBe(0);
+ expect(fs.existsSync(path.join(tmpDir,'node_modules', 'cordova-plugin-contacts'))).toBe(false);
+ })
+ .fail(function(err) {
+ console.error(err);
+ expect(err).toBeUndefined();
+ })
+ .fin(done);
+ }, 30000);
+});
+
+describe('test trimID method for npm and git', function () {
+
+ var tmpDir = helpers.tmpDir('plug_trimID');
+ var opts = {};
+
+ beforeEach(function() {
+ process.chdir(tmpDir);
+ });
+
+ afterEach(function() {
+ process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
+ shell.rm('-rf', tmpDir);
+ });
+
+ it('should fetch the same cordova plugin twice in a row', function(done) {
+ fetch('cordova-plugin-device', tmpDir, opts)
+ .then(function(result) {
+ var pkgJSON = require(path.join(result,'package.json'));
+ expect(result).toBeDefined();
+ expect(fs.existsSync(result)).toBe(true);
+ expect(pkgJSON.name).toBe('cordova-plugin-device');
+
+ return fetch('https://github.com/apache/cordova-plugin-media.git', tmpDir, opts);
+ })
+ .then(function(result) {
+ var pkgJSON = require(path.join(result,'package.json'));
+ expect(result).toBeDefined();
+ expect(fs.existsSync(result)).toBe(true);
+ expect(pkgJSON.name).toBe('cordova-plugin-media');
+
+ //refetch to trigger trimID
+ return fetch('cordova-plugin-device', tmpDir, opts);
+
+ })
+ .then(function(result) {
+ expect(result).toBeDefined();
+ expect(fs.existsSync(result)).toBe(true);
+
+ //refetch to trigger trimID
+ return fetch('https://github.com/apache/cordova-plugin-media.git', tmpDir, opts);
+ })
+ .then(function(result) {
+ expect(result).toBeDefined();
+ expect(fs.existsSync(result)).toBe(true);
+ })
+ .fail(function(err) {
+ console.error(err);
+ expect(err).toBeUndefined();
+ })
+ .fin(done);
+ }, 30000);
+});
+
+describe('fetch failure with unknown module', function () {
+
+ var tmpDir = helpers.tmpDir('fetch_fails_npm');
+ var opts = {};
+
+ beforeEach(function() {
+ process.chdir(tmpDir);
+ });
+
+ afterEach(function() {
+ process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
+ shell.rm('-rf', tmpDir);
+ });
+
+ it('should fail fetching a module that does not exist on npm', function(done) {
+ fetch('NOTAMODULE', tmpDir, opts)
+ .then(function(result) {
+ console.log('This should fail and it should not be seen');
+ })
+ .fail(function(err) {
+ expect(err.message.code).toBe(1);
+ expect(err).toBeDefined();
+ })
+ .fin(done);
+ }, 30000);
+});
+
+describe('fetch failure with git subdirectory', function () {
+
+ var tmpDir = helpers.tmpDir('fetch_fails_subdirectory');
+ var opts = {};
+
+ beforeEach(function() {
+ process.chdir(tmpDir);
+ });
+
+ afterEach(function() {
+ process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
+ shell.rm('-rf', tmpDir);
+ });
+
+ it('should fail fetching a giturl which contains a subdirectory', function(done) {
+ fetch('https://github.com/apache/cordova-plugins.git#:keyboard', tmpDir, opts)
+ .then(function(result) {
+ console.log('This should fail and it should not be seen');
+ })
+ .fail(function(err) {
+ expect(err.message.code).toBe(1);
+ expect(err).toBeDefined();
+ })
+ .fin(done);
+ }, 30000);
+});
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/spec/helpers.js
----------------------------------------------------------------------
diff --git a/cordova-fetch/spec/helpers.js b/cordova-fetch/spec/helpers.js
new file mode 100644
index 0000000..ee3f57b
--- /dev/null
+++ b/cordova-fetch/spec/helpers.js
@@ -0,0 +1,16 @@
+var path = require('path'),
+ fs = require('fs'),
+ shell = require('shelljs'),
+ os = require('os');
+
+module.exports.tmpDir = function (subdir) {
+ var dir = path.join(os.tmpdir(), 'e2e-test');
+ if (subdir) {
+ dir = path.join(dir, subdir);
+ }
+ if(fs.existsSync(dir)) {
+ shell.rm('-rf', dir);
+ }
+ shell.mkdir('-p', dir);
+ return dir;
+};
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/spec/support/jasmine.json
----------------------------------------------------------------------
diff --git a/cordova-fetch/spec/support/jasmine.json b/cordova-fetch/spec/support/jasmine.json
new file mode 100644
index 0000000..3ea3166
--- /dev/null
+++ b/cordova-fetch/spec/support/jasmine.json
@@ -0,0 +1,11 @@
+{
+ "spec_dir": "spec",
+ "spec_files": [
+ "**/*[sS]pec.js"
+ ],
+ "helpers": [
+ "helpers/**/*.js"
+ ],
+ "stopSpecOnExpectationFailure": false,
+ "random": false
+}
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/spec/testpkg.json
----------------------------------------------------------------------
diff --git a/cordova-fetch/spec/testpkg.json b/cordova-fetch/spec/testpkg.json
new file mode 100644
index 0000000..94e7f64
--- /dev/null
+++ b/cordova-fetch/spec/testpkg.json
@@ -0,0 +1,11 @@
+{
+ "name": "test",
+ "version": "1.0.0",
+ "description": "",
+ "main": "fetch.spec.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-lib/spec-cordova/helpers.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/helpers.js b/cordova-lib/spec-cordova/helpers.js
index a5d23fd..4cc1e2c 100644
--- a/cordova-lib/spec-cordova/helpers.js
+++ b/cordova-lib/spec-cordova/helpers.js
@@ -40,6 +40,9 @@ module.exports.tmpDir = function (subdir) {
if (subdir) {
dir = path.join(dir, subdir);
}
+ if(fs.existsSync(dir)) {
+ shell.rm('-rf', dir);
+ }
shell.mkdir('-p', dir);
return dir;
};
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-lib/spec-cordova/platform.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/platform.spec.js b/cordova-lib/spec-cordova/platform.spec.js
index a35216f..a0bd104 100644
--- a/cordova-lib/spec-cordova/platform.spec.js
+++ b/cordova-lib/spec-cordova/platform.spec.js
@@ -248,3 +248,111 @@ describe('platform add plugin rm end-to-end', function () {
.fin(done);
}, 20000);
});
+
+describe('platform add and remove --fetch', function () {
+
+ var tmpDir = helpers.tmpDir('plat_add_remove_fetch_test');
+ var project = path.join(tmpDir, 'helloFetch');
+ var platformsDir = path.join(project, 'platforms');
+ var nodeModulesDir = path.join(project, 'node_modules');
+
+ beforeEach(function() {
+ process.chdir(tmpDir);
+ });
+
+ afterEach(function() {
+ process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
+ shell.rm('-rf', tmpDir);
+ });
+
+ it('should add and remove platform from node_modules directory', function(done) {
+
+ cordova.raw.create('helloFetch')
+ .then(function() {
+ process.chdir(project);
+ return cordova.raw.platform('add', 'ios', {'fetch':true});
+ })
+ .then(function() {
+ expect(path.join(nodeModulesDir, 'cordova-ios')).toExist();
+ expect(path.join(platformsDir, 'ios')).toExist();
+ return cordova.raw.platform('add', 'android', {'fetch':true});
+ })
+ .then(function() {
+ expect(path.join(nodeModulesDir, 'cordova-android')).toExist();
+ expect(path.join(platformsDir, 'android')).toExist();
+ //Tests finish before this command finishes resolving
+ //return cordova.raw.platform('rm', 'ios', {'fetch':true});
+ })
+ .then(function() {
+ //expect(path.join(nodeModulesDir, 'cordova-ios')).not.toExist();
+ //expect(path.join(platformsDir, 'ios')).not.toExist();
+ //Tests finish before this command finishes resolving
+ //return cordova.raw.platform('rm', 'android', {'fetch':true});
+ })
+ .then(function() {
+ //expect(path.join(nodeModulesDir, 'cordova-android')).not.toExist();
+ //expect(path.join(platformsDir, 'android')).not.toExist();
+ })
+ .fail(function(err) {
+ console.error(err);
+ expect(err).toBeUndefined();
+ })
+ .fin(done);
+ }, 40000);
+});
+
+describe('plugin add and rm end-to-end --fetch', function () {
+
+ var tmpDir = helpers.tmpDir('plugin_rm_fetch_test');
+ var project = path.join(tmpDir, 'hello3');
+ var pluginsDir = path.join(project, 'plugins');
+
+ beforeEach(function() {
+ process.chdir(tmpDir);
+ });
+
+ afterEach(function() {
+ process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
+ shell.rm('-rf', tmpDir);
+ });
+
+ it('should remove dependency when removing parent plugin', function(done) {
+
+ cordova.raw.create('hello3')
+ .then(function() {
+ process.chdir(project);
+ return cordova.raw.platform('add', 'ios', {'fetch': true});
+ })
+ .then(function() {
+ return cordova.raw.plugin('add', 'cordova-plugin-media', {'fetch': true});
+ })
+ .then(function() {
+ expect(path.join(pluginsDir, 'cordova-plugin-media')).toExist();
+ expect(path.join(pluginsDir, 'cordova-plugin-file')).toExist();
+ expect(path.join(pluginsDir, 'cordova-plugin-compat')).toExist();
+ expect(path.join(project, 'node_modules', 'cordova-plugin-media')).toExist();
+ expect(path.join(project, 'node_modules', 'cordova-plugin-file')).toExist();
+ expect(path.join(project, 'node_modules', 'cordova-plugin-compat')).toExist();
+ return cordova.raw.platform('add', 'android', {'fetch':true});
+ })
+ .then(function() {
+ expect(path.join(pluginsDir, 'cordova-plugin-media')).toExist();
+ expect(path.join(pluginsDir, 'cordova-plugin-file')).toExist();
+ return cordova.raw.plugin('rm', 'cordova-plugin-media', {'fetch':true});
+ })
+ .then(function() {
+ expect(path.join(pluginsDir, 'cordova-plugin-media')).not.toExist();
+ expect(path.join(pluginsDir, 'cordova-plugin-file')).not.toExist();
+ expect(path.join(pluginsDir, 'cordova-plugin-compat')).not.toExist();
+ //These don't work yet due to the tests finishing before the promise resolves.
+ //expect(path.join(project, 'node_modules', 'cordova-plugin-media')).not.toExist();
+ //expect(path.join(project, 'node_modules', 'cordova-plugin-file')).not.toExist();
+ //expect(path.join(project, 'node_modules', 'cordova-plugin-compat')).not.toExist();
+ })
+ .fail(function(err) {
+ console.error(err);
+ expect(err).toBeUndefined();
+ })
+ .fin(done);
+ }, 60000);
+});
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-lib/src/cordova/platform.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/platform.js b/cordova-lib/src/cordova/platform.js
index db6f53c..cdfeb85 100644
--- a/cordova-lib/src/cordova/platform.js
+++ b/cordova-lib/src/cordova/platform.js
@@ -37,6 +37,8 @@ var config = require('./config'),
shell = require('shelljs'),
_ = require('underscore'),
PlatformJson = require('cordova-common').PlatformJson,
+ fetch = require('cordova-fetch'),
+ npmUninstall = require('cordova-fetch').uninstall,
platformMetadata = require('./platform_metadata');
// Expose the platform parsers on top of this command
@@ -257,6 +259,21 @@ function getSpecString(spec) {
function downloadPlatform(projectRoot, platform, version, opts) {
var target = version ? (platform + '@' + version) : platform;
return Q().then(function() {
+ if (opts.fetch) {
+ //append cordova to platform
+ if(platform in platforms) {
+ target = 'cordova-'+target;
+ }
+
+ //gitURLs don't supply a platform, it equals null
+ if(!platform) {
+ target = version;
+ }
+
+ events.emit('log', 'Using cordova-fetch for '+ target);
+ return fetch(target, projectRoot, opts);
+ }
+
if (cordova_util.isUrl(version)) {
events.emit('log', 'git cloning: ' + version);
var parts = version.split('#');
@@ -356,8 +373,8 @@ function remove(hooksRunner, projectRoot, targets, opts) {
events.emit('log', 'Removing ' + target + ' from config.xml file ...');
cfg.removeEngine(platformName);
cfg.write();
- });
- }
+ });
+ }
}).then(function() {
// Remove targets from platforms.json
targets.forEach(function(target) {
@@ -365,6 +382,16 @@ function remove(hooksRunner, projectRoot, targets, opts) {
platformMetadata.remove(projectRoot, target);
});
}).then(function() {
+ //Remove from node_modules if it exists and --fetch was used
+ if(opts.fetch) {
+ targets.forEach(function(target) {
+ if(target in platforms) {
+ target = 'cordova-'+target;
+ }
+ return npmUninstall(target, projectRoot, opts);
+ });
+ }
+ }).then(function() {
return hooksRunner.fire('after_platform_rm', opts);
});
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org