| /** |
| 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)) { |
| //strip away .git and everything that follows |
| var strippedTarget = target.split('.git'); |
| var re = /.*\/(.*)/; |
| //Grabs everything in url after last `/` |
| parts = strippedTarget[0].match(re); |
| |
| target = parts[1]; |
| } |
| |
| //If local path exists, set target to final directory |
| if (fs.existsSync(target)) { |
| target = path.basename(target); |
| } |
| |
| //strip away everything after '@' |
| //also support scoped packages |
| if(target.indexOf('@') != -1) { |
| parts = target.split('@'); |
| if (parts.length > 1 && parts[0] === '') { |
| //scoped package |
| target = '@' + parts[1]; |
| } else { |
| 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)); |
| }); |
| }; |