123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- /**
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
- */
-
- var fs = require('fs-extra');
- var url = require('url');
- var semver = require('semver');
- var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
- var CordovaError = require('cordova-common').CordovaError;
- var events = require('cordova-common').events;
- var metadata = require('./util/metadata');
- var path = require('path');
- var pluginSpec = require('../cordova/plugin/plugin_spec_parser');
- var fetch = require('cordova-fetch');
- var cordovaUtil = require('../cordova/util');
-
- var projectRoot;
-
- // Cache of PluginInfo objects for plugins in search path.
- var localPlugins = null;
-
- // possible options: link, subdir, git_ref, client, expected_id
- // Returns a promise.
- module.exports = fetchPlugin;
- function fetchPlugin (plugin_src, plugins_dir, options) {
- // Ensure the containing directory exists.
- fs.ensureDirSync(plugins_dir);
- options = options || {};
- options.subdir = options.subdir || '.';
- options.searchpath = options.searchpath || [];
- if (typeof options.searchpath === 'string') {
- options.searchpath = options.searchpath.split(path.delimiter);
- }
-
- var pluginInfoProvider = options.pluginInfoProvider || new PluginInfoProvider();
-
- // clone from git repository
- // @todo Use 'url.URL' constructor instead since 'url.parse' was deprecated since v11.0.0
- var uri = url.parse(plugin_src); // eslint-disable-line
-
- // If the hash exists, it has the form from npm: http://foo.com/bar#git-ref[:subdir]
- // git-ref can be a commit SHA, a tag, or a branch
- // NB: No leading or trailing slash on the subdir.
- if (uri.hash) {
- var result = uri.hash.match(/^#([^:]*)(?::\/?(.*?)\/?)?$/);
- if (result) {
- if (result[1]) { options.git_ref = result[1]; }
- if (result[2]) { options.subdir = result[2]; }
- }
- }
- return Promise.resolve().then(function () {
- var plugin_dir = cordovaUtil.fixRelativePath(path.join(plugin_src, options.subdir));
- return Promise.resolve().then(function () {
- // check if it is a local path
- if (fs.existsSync(plugin_dir)) {
- if (!fs.existsSync(path.join(plugin_dir, 'package.json'))) {
- return Promise.reject(new CordovaError('Invalid Plugin! ' + plugin_dir + ' needs a valid package.json'));
- }
-
- 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;
- }
- return fetch(path.resolve(plugin_dir), projectRoot, options)
- .then(function (directory) {
- return {
- pinfo: pluginInfoProvider.get(directory),
- fetchJsonSource: {
- type: 'local',
- path: directory
- }
- };
- }).catch(function (error) {
- // something went wrong with cordova-fetch
- return Promise.reject(new CordovaError(error.message));
- });
- }
- // If there is no such local path or it's a git URL, it's a plugin id or id@versionspec.
- // First look for it in the local search path (if provided).
- var pinfo = findLocalPlugin(plugin_src, options.searchpath, pluginInfoProvider);
- if (pinfo) {
- events.emit('verbose', 'Found ' + plugin_src + ' at ' + pinfo.dir);
- return {
- pinfo: pinfo,
- fetchJsonSource: {
- type: 'local',
- path: pinfo.dir
- }
- };
- } else if (options.noregistry) {
- return Promise.reject(new CordovaError(
- 'Plugin ' + plugin_src + ' not found locally. ' +
- 'Note, plugin registry was disabled by --noregistry flag.'
- ));
- }
- // If not found in local search path, fetch from the registry.
- var parsedSpec = pluginSpec.parse(plugin_src);
- var P;
- var skipCopyingPlugin;
- plugin_dir = path.join(plugins_dir, parsedSpec.id);
- // if the plugin has already been fetched, use it.
- if (fs.existsSync(plugin_dir)) {
- P = Promise.resolve(plugin_dir);
- skipCopyingPlugin = true;
- } else {
- // use cordova-fetch
- 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);
- skipCopyingPlugin = false;
- }
- return P
- .catch(function (error) {
- var message = 'Failed to fetch plugin ' + plugin_src + ' via registry.' +
- '\nProbably this is either a connection problem, or plugin spec is incorrect.' +
- '\nCheck your connection and plugin name/version/URL.' +
- '\n' + error;
- return Promise.reject(new CordovaError(message));
- })
- .then(function (dir) {
- return {
- pinfo: pluginInfoProvider.get(dir),
- fetchJsonSource: {
- type: 'registry',
- id: plugin_src
- },
- skipCopyingPlugin: skipCopyingPlugin
- };
- });
- }).then(function (result) {
- options.plugin_src_dir = result.pinfo.dir;
- var P;
- if (result.skipCopyingPlugin) {
- P = Promise.resolve(options.plugin_src_dir);
- } else {
- P = Promise.resolve(copyPlugin(result.pinfo, plugins_dir, options.link && result.fetchJsonSource.type === 'local'));
- }
- return P.then(function (dir) {
- result.dest = dir;
- return result;
- });
- });
- }).then(function (result) {
- checkID(options.expected_id, result.pinfo);
- var data = { source: result.fetchJsonSource };
- data.is_top_level = options.is_top_level;
- data.variables = options.variables || {};
- metadata.save_fetch_metadata(plugins_dir, result.pinfo.id, data);
- return result.dest;
- });
- }
-
- // Helper function for checking expected plugin IDs against reality.
- function checkID (expectedIdAndVersion, pinfo) {
- if (!expectedIdAndVersion) return;
-
- var parsedSpec = pluginSpec.parse(expectedIdAndVersion);
-
- if (parsedSpec.id !== pinfo.id) {
- throw new Error('Expected plugin to have ID "' + parsedSpec.id + '" but got "' + pinfo.id + '".');
- }
-
- if (parsedSpec.version && !semver.satisfies(pinfo.version, parsedSpec.version)) {
- throw new Error('Expected plugin ' + pinfo.id + ' to satisfy version "' + parsedSpec.version + '" but got "' + pinfo.version + '".');
- }
- }
-
- // Note, there is no cache invalidation logic for local plugins.
- // As of this writing loadLocalPlugins() is never called with different
- // search paths and such case would not be handled properly.
- function loadLocalPlugins (searchpath, pluginInfoProvider) {
- if (localPlugins) {
- // localPlugins already populated, nothing to do.
- // just in case, make sure it was loaded with the same search path
- if (localPlugins.searchpath.join(path.delimiter) !== searchpath.join(path.delimiter)) {
- var msg =
- 'loadLocalPlugins called twice with different search paths.' +
- 'Support for this is not implemented. Using previously cached path.';
- events.emit('warn', msg);
- }
- return;
- }
-
- // Populate localPlugins object.
- localPlugins = {};
- localPlugins.searchpath = searchpath;
- localPlugins.plugins = {};
-
- searchpath.forEach(function (dir) {
- var ps = pluginInfoProvider.getAllWithinSearchPath(dir);
- ps.forEach(function (p) {
- var versions = localPlugins.plugins[p.id] || [];
- versions.push(p);
- localPlugins.plugins[p.id] = versions;
- });
- });
- }
-
- // If a plugin is fund in local search path, return a PluginInfo for it.
- // Ignore plugins that don't satisfy the required version spec.
- // If several versions are present in search path, return the latest.
- // Examples of accepted plugin_src strings:
- // org.apache.cordova.file
- // org.apache.cordova.file@>=1.2.0
- function findLocalPlugin (plugin_src, searchpath, pluginInfoProvider) {
- loadLocalPlugins(searchpath, pluginInfoProvider);
- var parsedSpec = pluginSpec.parse(plugin_src);
- var versionspec = parsedSpec.version || '*';
-
- var latest = null;
- var versions = localPlugins.plugins[parsedSpec.id];
-
- if (!versions) return null;
-
- versions.forEach(function (pinfo) {
- // Ignore versions that don't satisfy the the requested version range.
- // Ignore -dev suffix because latest semver versions doesn't handle it properly (CB-9421)
- if (!semver.satisfies(pinfo.version.replace(/-dev$/, ''), versionspec)) {
- return;
- }
- if (!latest) {
- latest = pinfo;
- return;
- }
- if (semver.gt(pinfo.version, latest.version)) {
- latest = pinfo;
- }
- });
- return latest;
- }
-
- // Copy or link a plugin from plugin_dir to plugins_dir/plugin_id.
- // if alternative ID of plugin exists in plugins_dir/plugin_id, skip copying
- function copyPlugin (pinfo, plugins_dir, link) {
- var plugin_dir = pinfo.dir;
- var dest = path.join(plugins_dir, pinfo.id);
-
- fs.removeSync(dest);
-
- if (!link && dest.indexOf(path.resolve(plugin_dir) + path.sep) === 0) {
- events.emit('verbose', 'Copy plugin destination is child of src. Forcing --link mode.');
- link = true;
- }
-
- if (link) {
- var isRelativePath = plugin_dir.charAt(1) !== ':' && plugin_dir.charAt(0) !== path.sep;
- var fixedPath = isRelativePath ? path.join(path.relative(plugins_dir, process.env.PWD || process.cwd()), plugin_dir) : plugin_dir;
- events.emit('verbose', 'Linking "' + dest + '" => "' + fixedPath + '"');
- fs.symlinkSync(fixedPath, dest, 'junction');
- } else {
- events.emit('verbose', 'Copying plugin "' + plugin_dir + '" => "' + dest + '"');
- fs.copySync(plugin_dir, dest, { dereference: true });
- }
- return dest;
- }
|