mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
751 lines
26 KiB
JavaScript
751 lines
26 KiB
JavaScript
import _ from 'underscore';
|
|
import util from 'util';
|
|
import assert from 'assert';
|
|
import chalk from 'chalk';
|
|
import semver from 'semver';
|
|
|
|
import files from '../fs/files.js';
|
|
import utils from '../utils/utils.js';
|
|
import { Console } from '../console/console.js';
|
|
import buildmessage from '../utils/buildmessage.js';
|
|
import main from '../cli/main.js';
|
|
import httpHelpers from '../utils/http-helpers.js';
|
|
import { execFileSync, execFileAsync } from '../utils/processes.js';
|
|
|
|
import './protect-string-proto.js'; // must always come before 'cordova-lib'
|
|
import { cordova as cordova_lib, events as cordova_events, CordovaError }
|
|
from 'cordova-lib';
|
|
import cordova_util from 'cordova-lib/src/cordova/util.js';
|
|
import superspawn from 'cordova-common/src/superspawn.js';
|
|
import PluginInfoProvider from 'cordova-common/src/PluginInfo/PluginInfoProvider.js';
|
|
|
|
import { CORDOVA_PLATFORMS, CORDOVA_PLATFORM_VERSIONS, displayNameForPlatform, displayNamesForPlatforms,
|
|
newPluginId, convertPluginVersions, convertToGitUrl } from './index.js';
|
|
import { CordovaBuilder } from './builder.js';
|
|
|
|
cordova_events.on('verbose', logIfVerbose);
|
|
cordova_events.on('log', logIfVerbose);
|
|
cordova_events.on('info', logIfVerbose);
|
|
cordova_events.on('warn', log);
|
|
cordova_events.on('error', log);
|
|
|
|
cordova_events.on('results', logIfVerbose);
|
|
|
|
function logIfVerbose(...args) {
|
|
if (Console.verbose) {
|
|
log(...args);
|
|
}
|
|
};
|
|
|
|
function log(...args) {
|
|
Console.rawInfo(`%% ${util.format.apply(null, args)}\n`);
|
|
}
|
|
|
|
// We pin platform versions ourselves instead of relying on cordova-lib
|
|
// so we we can update them independently (e.g. use Cordova iOS 4.0.1
|
|
// with Cordova 5.4.1)
|
|
const pinnedPlatformVersions = CORDOVA_PLATFORM_VERSIONS;
|
|
|
|
// We pin plugin versions to make sure we do not install versions that are
|
|
// incompatible with the current platform versions.
|
|
// Versions are taken from cordova-lib's package.json and should be updated
|
|
// when we update to a newer version of cordova-lib.
|
|
const pinnedPluginVersions = {
|
|
"cordova-plugin-battery-status": "1.2.0",
|
|
"cordova-plugin-camera": "2.3.0",
|
|
"cordova-plugin-console": "1.0.4",
|
|
"cordova-plugin-contacts": "2.2.0",
|
|
"cordova-plugin-device": "1.1.3",
|
|
"cordova-plugin-device-motion": "1.2.2",
|
|
"cordova-plugin-device-orientation": "1.0.4",
|
|
"cordova-plugin-dialogs": "1.3.0",
|
|
"cordova-plugin-file": "4.3.0",
|
|
"cordova-plugin-file-transfer": "1.6.0",
|
|
"cordova-plugin-geolocation": "2.3.0",
|
|
"cordova-plugin-globalization": "1.0.4",
|
|
"cordova-plugin-inappbrowser": "1.5.0",
|
|
"cordova-plugin-legacy-whitelist": "1.1.1",
|
|
"cordova-plugin-media": "2.4.0",
|
|
"cordova-plugin-media-capture": "1.4.0",
|
|
"cordova-plugin-network-information": "1.3.0",
|
|
"cordova-plugin-splashscreen": "4.0.0",
|
|
"cordova-plugin-statusbar": "2.2.0",
|
|
"cordova-plugin-test-framework": "1.1.3",
|
|
"cordova-plugin-vibration": "2.1.2",
|
|
"cordova-plugin-whitelist": "1.3.0",
|
|
"cordova-plugin-wkwebview-engine": "1.1.0"
|
|
}
|
|
|
|
export class CordovaProject {
|
|
constructor(projectContext, options = {}) {
|
|
|
|
this.projectContext = projectContext;
|
|
|
|
this.projectRoot = projectContext.getProjectLocalDirectory('cordova-build');
|
|
this.options = options;
|
|
|
|
this.pluginsDir = files.pathJoin(this.projectRoot, 'plugins');
|
|
|
|
this.createIfNeeded();
|
|
}
|
|
|
|
createIfNeeded() {
|
|
buildmessage.assertInJob();
|
|
|
|
// Check if we have an existing Cordova project directory with outdated
|
|
// platforms. In that case, we remove the whole directory to avoid issues.
|
|
if (files.exists(this.projectRoot)) {
|
|
const installedPlatforms = this.listInstalledPlatforms();
|
|
|
|
const outdated = _.some(pinnedPlatformVersions, (pinnedVersion, platform) => {
|
|
// If the platform is not installed, it cannot be outdated
|
|
if (!_.contains(installedPlatforms, platform)) {
|
|
return false;
|
|
}
|
|
|
|
const installedVersion = this.installedVersionForPlatform(platform);
|
|
// If we cannot establish the installed version, we consider it outdated
|
|
if (!installedVersion) {
|
|
return true;
|
|
}
|
|
|
|
return semver.lt(installedVersion, pinnedVersion);
|
|
});
|
|
|
|
if (outdated) {
|
|
Console.debug(`Removing Cordova project directory to avoid issues with
|
|
outdated platforms`);
|
|
// Remove Cordova project directory to start afresh
|
|
// and avoid a broken project
|
|
files.rm_recursive(this.projectRoot);
|
|
}
|
|
}
|
|
|
|
if (!files.exists(this.projectRoot)) {
|
|
// We create a temporary directory with a generated config.xml
|
|
// to use as a template for creating the Cordova project
|
|
// This way, we are not dependent on the contents of
|
|
// cordova-app-hello-world but we base our initial project state on
|
|
// our own defaults and optionally a mobile-config.js
|
|
|
|
const templatePath = files.mkdtemp('cordova-template-');
|
|
|
|
// If we don't create an empty hooks directory, cordova-lib will attempt
|
|
// to install one from a hardcoded path to cordova-app-hello-world
|
|
files.mkdir_p(files.pathJoin(templatePath, 'hooks'));
|
|
|
|
// If we don't create an empty www directory, cordova-lib will get
|
|
// confused
|
|
files.mkdir_p(files.pathJoin(templatePath, 'www'));
|
|
|
|
const builder = new CordovaBuilder(this.projectContext, templatePath,
|
|
{ mobileServerUrl, settingsFile } = this.options);
|
|
|
|
builder.processControlFile();
|
|
|
|
if (buildmessage.jobHasMessages()) {
|
|
return;
|
|
}
|
|
|
|
// Don't copy resources (they will be copied as part of the prepare)
|
|
builder.writeConfigXmlAndCopyResources(false);
|
|
|
|
// Create the Cordova project root directory
|
|
files.mkdir_p(files.pathDirname(this.projectRoot));
|
|
|
|
const config = {
|
|
lib: {
|
|
www: {
|
|
url: files.convertToOSPath(templatePath),
|
|
template: true
|
|
}
|
|
}
|
|
};
|
|
|
|
// Don't set cwd to project root in runCommands because it doesn't
|
|
// exist yet
|
|
this.runCommands('creating Cordova project', async () => {
|
|
// No need to pass in appName and appId because these are set from
|
|
// the generated config.xml
|
|
await cordova_lib.raw.create(files.convertToOSPath(this.projectRoot),
|
|
undefined, undefined, config);
|
|
}, undefined, null);
|
|
}
|
|
}
|
|
|
|
// Preparing
|
|
|
|
prepareFromAppBundle(bundlePath, pluginVersions) {
|
|
assert(bundlePath);
|
|
assert(pluginVersions);
|
|
|
|
buildmessage.assertInJob();
|
|
|
|
Console.debug('Preparing Cordova project from app bundle');
|
|
|
|
const builder = new CordovaBuilder(this.projectContext, this.projectRoot,
|
|
{ mobileServerUrl, settingsFile } = this.options);
|
|
|
|
builder.processControlFile();
|
|
|
|
if (buildmessage.jobHasMessages()) {
|
|
return;
|
|
}
|
|
|
|
builder.writeConfigXmlAndCopyResources();
|
|
builder.copyWWW(bundlePath);
|
|
|
|
this.ensurePlatformsAreSynchronized();
|
|
this.ensurePluginsAreSynchronized(pluginVersions,
|
|
builder.pluginsConfiguration);
|
|
|
|
// Temporary workaround for Cordova iOS bug until
|
|
// https://issues.apache.org/jira/browse/CB-10885 is fixed
|
|
const iosBuildExtrasPath =
|
|
files.pathJoin(
|
|
this.projectRoot,
|
|
'platforms/ios/cordova/build-extras.xcconfig');
|
|
|
|
if (files.exists(iosBuildExtrasPath)) {
|
|
files.writeFile(
|
|
iosBuildExtrasPath,
|
|
'LD_RUNPATH_SEARCH_PATHS = @executable_path/Frameworks;');
|
|
}
|
|
|
|
builder.copyBuildOverride();
|
|
}
|
|
|
|
prepareForPlatform(platform) {
|
|
assert(platform);
|
|
|
|
// Temporary workaround for Cordova iOS bug until
|
|
// https://issues.apache.org/jira/browse/CB-11731 has been released
|
|
delete require.cache[files.pathJoin(this.projectRoot,
|
|
'platforms/ios/cordova/lib/configMunger.js')];
|
|
delete require.cache[files.pathJoin(this.projectRoot,
|
|
'platforms/ios/cordova/lib/prepare.js')];
|
|
|
|
const commandOptions = _.extend(this.defaultOptions,
|
|
{ platforms: [platform] });
|
|
|
|
this.runCommands(`preparing Cordova project for platform \
|
|
${displayNameForPlatform(platform)}`, async () => {
|
|
await cordova_lib.raw.prepare(commandOptions);
|
|
});
|
|
}
|
|
|
|
// Building (includes prepare)
|
|
|
|
buildForPlatform(platform, options = {}, extraPaths) {
|
|
assert(platform);
|
|
|
|
const commandOptions = _.extend(this.defaultOptions,
|
|
{ platforms: [platform], options: options });
|
|
|
|
this.runCommands(`building Cordova app for platform \
|
|
${displayNameForPlatform(platform)}`, async () => {
|
|
await cordova_lib.raw.build(commandOptions);
|
|
});
|
|
}
|
|
|
|
// Running
|
|
|
|
async run(platform, isDevice, options = [], extraPaths = []) {
|
|
options.push(isDevice ? '--device' : '--emulator');
|
|
|
|
let env = this.defaultEnvWithPathsAdded(...extraPaths);
|
|
|
|
let command = files.convertToOSPath(files.pathJoin(
|
|
this.projectRoot, 'platforms', platform, 'cordova', 'run'));
|
|
|
|
this.runCommands(`running Cordova app for platform \
|
|
${displayNameForPlatform(platform)} with options ${options}`,
|
|
execFileAsync(command, options, {
|
|
env: env,
|
|
cwd: this.projectRoot,
|
|
stdio: Console.verbose ? 'inherit' : 'pipe',
|
|
waitForClose: false
|
|
}), null, null);
|
|
}
|
|
|
|
// Platforms
|
|
|
|
// Checks to see if the requirements for building and running on the
|
|
// specified Cordova platform are satisfied, printing
|
|
// installation instructions when needed.
|
|
checkPlatformRequirements(platform) {
|
|
if (platform === 'ios' && process.platform !== 'darwin') {
|
|
Console.warn("Currently, it is only possible to build iOS apps \
|
|
on an OS X system.");
|
|
return false;
|
|
}
|
|
|
|
const installedPlatforms = this.listInstalledPlatforms();
|
|
|
|
const inProject = _.contains(installedPlatforms, platform);
|
|
if (!inProject) {
|
|
Console.warn(`Please add the ${displayNameForPlatform(platform)} \
|
|
platform to your project first.`);
|
|
Console.info(`Run: ${Console.command(`meteor add-platform ${platform}`)}`);
|
|
return false;
|
|
}
|
|
|
|
const allRequirements = this.runCommands(`checking Cordova \
|
|
requirements for platform ${displayNameForPlatform(platform)}`,
|
|
async () => {
|
|
return await cordova_lib.raw.requirements([platform],
|
|
this.defaultOptions);
|
|
});
|
|
let requirements = allRequirements && allRequirements[platform];
|
|
if (!requirements) {
|
|
Console.error(`Failed to check requirements for platform \
|
|
${displayNameForPlatform(platform)}`);
|
|
return false;
|
|
} else if (requirements instanceof CordovaError) {
|
|
Console.error(`Cordova error: ${requirements.message}`);
|
|
return false;
|
|
}
|
|
|
|
// We don't use ios-deploy, but open Xcode to run on a device instead
|
|
requirements = _.reject(requirements,
|
|
requirement => requirement.id === 'ios-deploy');
|
|
|
|
const satisfied = _.every(requirements,
|
|
requirement => requirement.installed);
|
|
|
|
if (!satisfied) {
|
|
Console.info();
|
|
Console.info(`Your system does not yet seem to fulfill all requirements \
|
|
to build apps for ${displayNameForPlatform(platform)}.`);
|
|
|
|
Console.info();
|
|
Console.info("Please follow the installation instructions in the mobile guide:");
|
|
Console.info(Console.url("http://guide.meteor.com/mobile.html#installing-prerequisites"));
|
|
|
|
Console.info();
|
|
|
|
Console.info("Status of the individual requirements:");
|
|
for (requirement of requirements) {
|
|
const name = requirement.name;
|
|
if (requirement.installed) {
|
|
Console.success(name, "installed");
|
|
} else {
|
|
const reason = requirement.metadata && requirement.metadata.reason;
|
|
if (reason) {
|
|
Console.failInfo(`${name}: ${reason}`);
|
|
} else {
|
|
Console.failInfo(name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return satisfied;
|
|
}
|
|
|
|
listInstalledPlatforms() {
|
|
return cordova_util.listPlatforms(files.convertToOSPath(this.projectRoot));
|
|
}
|
|
|
|
installedVersionForPlatform(platform) {
|
|
const command = files.convertToOSPath(files.pathJoin(
|
|
this.projectRoot, 'platforms', platform, 'cordova', 'version'));
|
|
// Make sure the command exists before trying to execute it
|
|
if (files.exists(command)) {
|
|
return this.runCommands(
|
|
`getting installed version for platform ${platform} in Cordova project`,
|
|
execFileSync(command, {
|
|
env: this.defaultEnvWithPathsAdded(),
|
|
cwd: this.projectRoot}), null, null);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
updatePlatforms(platforms = this.listInstalledPlatforms()) {
|
|
this.runCommands(`updating Cordova project for platforms \
|
|
${displayNamesForPlatforms(platforms)}`, async () => {
|
|
await cordova_lib.raw.platform('update', platforms, this.defaultOptions);
|
|
});
|
|
}
|
|
|
|
addPlatform(platform) {
|
|
this.runCommands(`adding platform ${displayNameForPlatform(platform)} \
|
|
to Cordova project`, async () => {
|
|
let version = pinnedPlatformVersions[platform];
|
|
let platformSpec = version ? `${platform}@${version}` : platform;
|
|
await cordova_lib.raw.platform('add', platformSpec, this.defaultOptions);
|
|
});
|
|
}
|
|
|
|
removePlatform(platform) {
|
|
this.runCommands(`removing platform ${displayNameForPlatform(platform)} \
|
|
from Cordova project`, async () => {
|
|
await cordova_lib.raw.platform('rm', platform, this.defaultOptions);
|
|
});
|
|
}
|
|
|
|
get cordovaPlatformsInApp() {
|
|
return this.projectContext.platformList.getCordovaPlatforms();
|
|
}
|
|
|
|
// Ensures that the Cordova platforms are synchronized with the app-level
|
|
// platforms.
|
|
ensurePlatformsAreSynchronized(platforms = this.cordovaPlatformsInApp) {
|
|
buildmessage.assertInCapture();
|
|
|
|
const installedPlatforms = this.listInstalledPlatforms();
|
|
|
|
for (platform of platforms) {
|
|
if (_.contains(installedPlatforms, platform)) {
|
|
continue;
|
|
}
|
|
|
|
this.addPlatform(platform);
|
|
}
|
|
|
|
for (platform of installedPlatforms) {
|
|
if (!_.contains(platforms, platform) &&
|
|
_.contains(CORDOVA_PLATFORMS, platform)) {
|
|
this.removePlatform(platform);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Plugins
|
|
|
|
// Because PluginInfoProvider reads in the plugin versions from
|
|
// their plugin.xml, that only gives us the declared version and doesn't
|
|
// tell us if plugins have been fetched from a Git SHA URL or a local path.
|
|
// So we overwrite the declared versions with versions from
|
|
// listFetchedPluginVersions that do contain this information.
|
|
listInstalledPluginVersions() {
|
|
const pluginInfoProvider = new PluginInfoProvider();
|
|
const installedPluginVersions = pluginInfoProvider.getAllWithinSearchPath(
|
|
files.convertToOSPath(this.pluginsDir));
|
|
const fetchedPluginVersions = this.listFetchedPluginVersions();
|
|
return _.object(installedPluginVersions.map(pluginInfo => {
|
|
const id = pluginInfo.id;
|
|
const version = fetchedPluginVersions[id] || pluginInfo.version;
|
|
return [id, version];
|
|
}));
|
|
}
|
|
|
|
// There is no Cordova function to get the fetched plugin versions, so we
|
|
// have to read in fetch.json (a file managed by plugman, a semi-independent
|
|
// part of cordova-lib) and parse the format ourselves into a version
|
|
// string suitable to be passed to targetForPlugin.
|
|
// Note that a plugin can be fetched but not installed, so that's why we
|
|
// still need a separate listInstalledPluginVersions.
|
|
listFetchedPluginVersions() {
|
|
const fetchJsonPath = files.pathJoin(this.pluginsDir, 'fetch.json');
|
|
|
|
if (!files.exists(fetchJsonPath)) {
|
|
return {};
|
|
}
|
|
|
|
const fetchedPluginsMetadata = JSON.parse(files.readFile(
|
|
fetchJsonPath, 'utf8'));
|
|
return _.object(_.map(fetchedPluginsMetadata, (metadata, id) => {
|
|
const source = metadata.source;
|
|
let version;
|
|
if (source.type === 'registry') {
|
|
version = source.id.split('@')[1];
|
|
} else if (source.type === 'git') {
|
|
version = `${source.url}#${source.ref}`;
|
|
} else if (source.type === 'local') {
|
|
version = `file://${source.path}`;
|
|
}
|
|
return [id, version];
|
|
}));
|
|
}
|
|
|
|
// Construct a target suitable for 'cordova plugin add' from an id and
|
|
// version, converting or resolving a URL or path where needed.
|
|
targetForPlugin(id, version) {
|
|
assert(id);
|
|
assert(version);
|
|
|
|
buildmessage.assertInJob();
|
|
|
|
if (utils.isUrlWithSha(version)) {
|
|
return convertToGitUrl(version);
|
|
} else if (utils.isUrlWithFileScheme(version)) {
|
|
// Strip file:// and resolve the path relative to the cordova-build
|
|
// directory
|
|
const pluginPath = this.resolveLocalPluginPath(version);
|
|
// We need to check if the directory exists ourselves because Cordova
|
|
// will try to install from npm (and fail with an unhelpful error message)
|
|
// if the directory is not found
|
|
const stat = files.statOrNull(pluginPath);
|
|
if (!(stat && stat.isDirectory())) {
|
|
buildmessage.error(`Couldn't find local directory \
|
|
'${files.convertToOSPath(pluginPath)}' \
|
|
(while attempting to install plugin ${id}).`);
|
|
return null;
|
|
}
|
|
return files.convertToOSPath(pluginPath);
|
|
} else {
|
|
return `${id}@${version}`;
|
|
}
|
|
}
|
|
|
|
// Strips file:// and resolves the path relative to the cordova-build
|
|
// directory
|
|
resolveLocalPluginPath(pluginPath) {
|
|
pluginPath = pluginPath.substr("file://".length);
|
|
if (utils.isPathRelative(pluginPath)) {
|
|
return files.pathResolve(this.projectContext.projectDir, pluginPath);
|
|
} else {
|
|
return pluginPath;
|
|
}
|
|
}
|
|
|
|
addPlugin(id, version, config = {}) {
|
|
const target = this.targetForPlugin(id, version);
|
|
if (target) {
|
|
const commandOptions = _.extend(this.defaultOptions,
|
|
{ cli_variables: config, link: utils.isUrlWithFileScheme(version) });
|
|
|
|
this.runCommands(`adding plugin ${target} \
|
|
to Cordova project`, async () => {
|
|
await cordova_lib.raw.plugin('add', [target], commandOptions);
|
|
});
|
|
}
|
|
}
|
|
|
|
// plugins is an array of plugin IDs.
|
|
removePlugins(plugins) {
|
|
if (_.isEmpty(plugins)) {
|
|
return;
|
|
}
|
|
|
|
this.runCommands(`removing plugins ${plugins} \
|
|
from Cordova project`, async () => {
|
|
await cordova_lib.raw.plugin('rm', plugins, this.defaultOptions);
|
|
});
|
|
}
|
|
|
|
// Ensures that the Cordova plugins are synchronized with the app-level
|
|
// plugins.
|
|
ensurePluginsAreSynchronized(pluginVersions, pluginsConfiguration = {}) {
|
|
assert(pluginVersions);
|
|
|
|
buildmessage.assertInCapture();
|
|
|
|
buildmessage.enterJob({ title: "installing Cordova plugins"}, () => {
|
|
// Cordova plugin IDs have changed as part of moving to npm.
|
|
// We convert old plugin IDs to new IDs in the 1.2.0-cordova-changes
|
|
// upgrader and when adding plugins, but packages may still depend on
|
|
// the old IDs.
|
|
// To avoid attempts at duplicate installation, we check for old IDs here
|
|
// and convert them to new IDs when needed. We also convert old-style GitHub
|
|
// tarball URLs to new Git URLs, and check if other Git URLs contain a
|
|
// SHA reference.
|
|
pluginVersions = convertPluginVersions(pluginVersions);
|
|
|
|
// To ensure we do not attempt to install plugin versions incompatible
|
|
// with the current platform versions, we compare them against a list of
|
|
// pinned versions and adjust them if necessary.
|
|
this.ensurePinnedPluginVersions(pluginVersions);
|
|
|
|
if (buildmessage.jobHasMessages()) {
|
|
return;
|
|
}
|
|
|
|
// Also, we warn if any App.configurePlugin calls in mobile-config.js
|
|
// need to be updated (and in the meantime we take care of the
|
|
// conversion of the plugin configuration to the new ID).
|
|
pluginsConfiguration = _.object(_.map(pluginsConfiguration, (config, id) => {
|
|
const newId = newPluginId(id);
|
|
if (newId) {
|
|
Console.warn();
|
|
Console.labelWarn(`Cordova plugin ${id} has been renamed to ${newId} \
|
|
as part of moving to npm. Please change the App.configurePlugin call in \
|
|
mobile-config.js accordingly.`);
|
|
return [newId, config];
|
|
} else {
|
|
return [id, config];
|
|
}
|
|
}));
|
|
|
|
const installedPluginVersions =
|
|
convertPluginVersions(this.listInstalledPluginVersions());
|
|
|
|
// Due to the dependency structure of Cordova plugins, it is impossible to
|
|
// upgrade the version on an individual Cordova plugin. Instead, whenever
|
|
// a new Cordova plugin is added or removed, or its version is changed,
|
|
// we just reinstall all of the plugins.
|
|
let shouldReinstallAllPlugins = false;
|
|
|
|
// Iterate through all of the plugins and find if any of them have a new
|
|
// version. Additionally, check if we have plugins installed from a local
|
|
// path.
|
|
const pluginsFromLocalPath = {};
|
|
_.each(pluginVersions, (version, id) => {
|
|
// Check if plugin is installed from a local path.
|
|
const isPluginFromLocalPath = utils.isUrlWithFileScheme(version);
|
|
|
|
if (isPluginFromLocalPath) {
|
|
pluginsFromLocalPath[id] = version;
|
|
} else {
|
|
if (!_.has(installedPluginVersions, id) ||
|
|
installedPluginVersions[id] !== version) {
|
|
// We do not have the plugin installed or the version has changed.
|
|
shouldReinstallAllPlugins = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!_.isEmpty(pluginsFromLocalPath)) {
|
|
Console.debug('Reinstalling Cordova plugins added from the local path');
|
|
}
|
|
|
|
// Check to see if we have any installed plugins that are not in the
|
|
// current set of plugins.
|
|
_.each(installedPluginVersions, (version, id) => {
|
|
if (!_.has(pluginVersions, id)) {
|
|
shouldReinstallAllPlugins = true;
|
|
}
|
|
});
|
|
|
|
// We either reinstall all plugins or only those fetched from a local
|
|
// path.
|
|
if (shouldReinstallAllPlugins || !_.isEmpty(pluginsFromLocalPath)) {
|
|
let pluginsToRemove;
|
|
if (shouldReinstallAllPlugins) {
|
|
pluginsToRemove = Object.keys(installedPluginVersions);
|
|
} else {
|
|
// Only try to remove plugins that are currently installed.
|
|
pluginsToRemove = _.intersection(
|
|
Object.keys(pluginsFromLocalPath),
|
|
Object.keys(installedPluginVersions));
|
|
}
|
|
|
|
this.removePlugins(pluginsToRemove);
|
|
|
|
// Now install the necessary plugins.
|
|
if (shouldReinstallAllPlugins) {
|
|
pluginVersionsToInstall = pluginVersions;
|
|
} else {
|
|
pluginVersionsToInstall = pluginsFromLocalPath;
|
|
}
|
|
|
|
const pluginsToInstallCount = _.size(pluginVersionsToInstall);
|
|
let installedPluginsCount = 0;
|
|
|
|
buildmessage.reportProgress({ current: 0, end: pluginsToInstallCount });
|
|
_.each(pluginVersionsToInstall, (version, id) => {
|
|
this.addPlugin(id, version, pluginsConfiguration[id]);
|
|
|
|
buildmessage.reportProgress({
|
|
current: ++installedPluginsCount,
|
|
end: pluginsToInstallCount
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
ensurePinnedPluginVersions(pluginVersions) {
|
|
assert(pluginVersions);
|
|
|
|
_.each(pluginVersions, (version, id) => {
|
|
// Skip plugin specs that are not actual versions
|
|
if (utils.isUrlWithSha(version) || utils.isUrlWithFileScheme(version)) {
|
|
return;
|
|
}
|
|
|
|
const pinnedVersion = pinnedPluginVersions[id];
|
|
|
|
if (pinnedVersion && semver.lt(version, pinnedVersion)) {
|
|
Console.labelWarn(`Attempting to install plugin ${id}@${version}, but \
|
|
it should have a minimum version of ${pinnedVersion} to ensure compatibility \
|
|
with the current platform versions. Installing the minimum version for \
|
|
convenience, but you should adjust your dependencies.`);
|
|
pluginVersions[id] = pinnedVersion;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Cordova commands support
|
|
|
|
get defaultOptions() {
|
|
return { silent: !Console.verbose, verbose: Console.verbose };
|
|
}
|
|
|
|
defaultEnvWithPathsAdded(...extraPaths) {
|
|
let paths = (this.defaultPaths || []);
|
|
paths.unshift(...extraPaths);
|
|
const env = files.currentEnvWithPathsAdded(...paths);
|
|
return env;
|
|
}
|
|
|
|
get defaultPaths() {
|
|
const nodeBinDir = files.getCurrentNodeBinDir();
|
|
|
|
// Add the ios-sim bin path so Cordova can find it
|
|
const iosSimBinPath =
|
|
files.pathJoin(files.getDevBundle(),
|
|
'lib/node_modules/ios-sim/bin');
|
|
|
|
return [nodeBinDir, iosSimBinPath];
|
|
}
|
|
|
|
runCommands(title, promiseOrAsyncFunction, env = this.defaultEnvWithPathsAdded(),
|
|
cwd = this.projectRoot) {
|
|
// Capitalize title for debug output
|
|
Console.debug(title[0].toUpperCase() + title.slice(1));
|
|
|
|
const oldCwd = process.cwd();
|
|
if (cwd) {
|
|
process.chdir(files.convertToOSPath(cwd));
|
|
}
|
|
|
|
const oldEnv = process.env;
|
|
if (env) {
|
|
// this preserves case insensitivity for PATH on windows
|
|
Object.keys(env).forEach(key => {
|
|
process.env[key] = env[key];
|
|
});
|
|
}
|
|
|
|
try {
|
|
const promise = (typeof promiseOrAsyncFunction === 'function') ?
|
|
promiseOrAsyncFunction() : promiseOrAsyncFunction;
|
|
return Promise.await(promise);
|
|
} catch (error) {
|
|
Console.arrowError('Errors executing Cordova commands:');
|
|
Console.error();
|
|
const consoleOptions = Console.options({ indent: 3 });
|
|
Console.error(`While ${title}:`, consoleOptions);
|
|
|
|
if (error instanceof CordovaError) {
|
|
// Only print the message for errors thrown by cordova-lib, because
|
|
// these are meant for end-user consumption.
|
|
// But warn that they may not completely apply to our situation.
|
|
// (We do print the stack trace if we are in verbose mode.)
|
|
const errorMessage = Console.verbose ? (error.stack || error.message) :
|
|
error.message;
|
|
Console.error(`Cordova error: ${errorMessage}`, consoleOptions);
|
|
Console.error(chalk.green(`(If the error message contains suggestions \
|
|
for a fix, note that this may not apply to the Meteor integration. You can try \
|
|
running again with the --verbose option to help diagnose the issue.)`),
|
|
consoleOptions);
|
|
} else {
|
|
// Print stack trace for other errors by default, because the message
|
|
// usually does not give us enough information to know what is going on
|
|
const errorMessage = error && error.stack || error;
|
|
Console.error(errorMessage, consoleOptions);
|
|
};
|
|
throw new main.ExitWithCode(1);
|
|
} finally {
|
|
if (cwd && oldCwd) {
|
|
process.chdir(oldCwd);
|
|
}
|
|
if (env && oldEnv) {
|
|
process.env = oldEnv;
|
|
}
|
|
}
|
|
}
|
|
}
|