mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Cordova refactoring and change of runner behavior
- Refactored code in tools/cordova and introduced CordovaBuilder and CordovaRunTarget classes - CordovaRunner now builds and runs the project as part of the main runner loop - Some code cleanup and ES2015 conversions
This commit is contained in:
@@ -6,8 +6,8 @@ import { ProjectContext, PlatformList } from '../project-context.js';
|
||||
import buildmessage from '../utils/buildmessage.js';
|
||||
import files from '../fs/files.js';
|
||||
|
||||
import { AVAILABLE_PLATFORMS, ensureCordovaPlatformsAreSynchronized, checkPlatformRequirements } from '../cordova/platforms.js';
|
||||
import { createCordovaProjectIfNecessary } from '../cordova/project.js';
|
||||
import * as cordova from '../cordova';
|
||||
import { CordovaProject } from '../cordova/project.js';
|
||||
|
||||
function createProjectContext(appDir) {
|
||||
const projectContext = new ProjectContext({
|
||||
@@ -42,17 +42,18 @@ main.registerCommand({
|
||||
for (platform of platformsToAdd) {
|
||||
if (_.contains(installedPlatforms, platform)) {
|
||||
buildmessage.error(`${platform}: platform is already added`);
|
||||
} else if (!_.contains(AVAILABLE_PLATFORMS, platform)) {
|
||||
} else if (!_.contains(cordova.AVAILABLE_PLATFORMS, platform)) {
|
||||
buildmessage.error(`${platform}: no such platform`);
|
||||
}
|
||||
}
|
||||
|
||||
if (buildmessage.jobHasMessages()) return;
|
||||
|
||||
const cordovaProject = createCordovaProjectIfNecessary(projectContext);
|
||||
const cordovaProject = new CordovaProject(projectContext);
|
||||
|
||||
installedPlatforms = installedPlatforms.concat(platformsToAdd)
|
||||
ensureCordovaPlatformsAreSynchronized(cordovaProject, installedPlatforms);
|
||||
const cordovaPlatforms = cordova.filterPlatforms(installedPlatforms);
|
||||
cordovaProject.ensurePlatformsAreSynchronized(cordovaPlatforms);
|
||||
|
||||
if (buildmessage.jobHasMessages()) return;
|
||||
|
||||
@@ -60,7 +61,7 @@ main.registerCommand({
|
||||
|
||||
for (platform of platformsToAdd) {
|
||||
Console.info(`${platform}: added platform`);
|
||||
checkPlatformRequirements(cordovaProject, platform);
|
||||
cordovaProject.checkPlatformRequirements(platform);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -96,10 +97,11 @@ main.registerCommand({
|
||||
|
||||
if (buildmessage.jobHasMessages()) return;
|
||||
|
||||
installedPlatforms = _.without(installedPlatforms, ...platformsToRemove);
|
||||
const cordovaProject = new CordovaProject(projectContext);
|
||||
|
||||
const cordovaProject = createCordovaProjectIfNecessary(projectContext);
|
||||
ensureCordovaPlatformsAreSynchronized(cordovaProject, installedPlatforms);
|
||||
installedPlatforms = _.without(installedPlatforms, ...platformsToRemove);
|
||||
const cordovaPlatforms = cordova.filterPlatforms(installedPlatforms);
|
||||
cordovaProject.ensurePlatformsAreSynchronized(cordovaPlatforms);
|
||||
|
||||
if (buildmessage.jobHasMessages()) return;
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ var catalog = require('../packaging/catalog/catalog.js');
|
||||
var catalogRemote = require('../packaging/catalog/catalog-remote.js');
|
||||
var isopack = require('../isobuild/isopack.js');
|
||||
var updater = require('../packaging/updater.js');
|
||||
import { filterCordovaPackages } from '../cordova/plugins.js';
|
||||
var Console = require('../console/console.js').Console;
|
||||
var projectContextModule = require('../project-context.js');
|
||||
var colonConverter = require('../utils/colon-converter.js');
|
||||
@@ -24,6 +23,8 @@ var packageMapModule = require('../packaging/package-map.js');
|
||||
var packageClient = require('../packaging/package-client.js');
|
||||
var tropohouse = require('../packaging/tropohouse.js');
|
||||
|
||||
import * as cordova from '../cordova';
|
||||
|
||||
// For each release (or package), we store a meta-record with its name,
|
||||
// maintainers, etc. This function takes in a name, figures out if
|
||||
// it is a release or a package, and fetches the correct record.
|
||||
@@ -1810,14 +1811,14 @@ main.registerCommand({
|
||||
|
||||
var exitCode = 0;
|
||||
|
||||
var filteredPackages = filterCordovaPackages(options.args);
|
||||
var pluginsToAdd = filteredPackages.plugins;
|
||||
const { plugins: pluginsToAdd, packages: packagesToAdd } =
|
||||
cordova.splitPluginsAndPackages(options.args);
|
||||
|
||||
if (pluginsToAdd.length) {
|
||||
var plugins = projectContext.cordovaPluginsFile.getPluginVersions();
|
||||
var changed = false;
|
||||
_.each(pluginsToAdd, function (pluginSpec) {
|
||||
var parts = pluginSpec.split('@');
|
||||
let plugins = projectContext.cordovaPluginsFile.getPluginVersions();
|
||||
let changed = false;
|
||||
for (pluginSpec of pluginsToAdd) {
|
||||
let parts = pluginSpec.split('@');
|
||||
if (parts.length !== 2) {
|
||||
Console.error(
|
||||
pluginSpec + ': exact version or tarball url is required');
|
||||
@@ -1831,13 +1832,11 @@ main.registerCommand({
|
||||
changed = true;
|
||||
Console.info("added cordova plugin " + parts[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
changed && projectContext.cordovaPluginsFile.write(plugins);
|
||||
}
|
||||
|
||||
var args = filteredPackages.rest;
|
||||
|
||||
if (_.isEmpty(args))
|
||||
if (_.isEmpty(packagesToAdd))
|
||||
return exitCode;
|
||||
|
||||
// Messages that we should print if we make any changes, but that don't count
|
||||
@@ -1849,7 +1848,7 @@ main.registerCommand({
|
||||
// them -- add should be an atomic operation regardless of the package
|
||||
// order.
|
||||
var messages = buildmessage.capture(function () {
|
||||
_.each(args, function (packageReq) {
|
||||
_.each(packagesToAdd, function (packageReq) {
|
||||
buildmessage.enterJob("adding package " + packageReq, function () {
|
||||
var constraint = utils.parsePackageConstraint(packageReq, {
|
||||
useBuildmessage: true
|
||||
@@ -1993,16 +1992,16 @@ main.registerCommand({
|
||||
});
|
||||
|
||||
// Special case on reserved package namespaces, such as 'cordova'
|
||||
var filteredPackages = filterCordovaPackages(options.args);
|
||||
var pluginsToRemove = filteredPackages.plugins;
|
||||
const { plugins: pluginsToRemove, packages } =
|
||||
cordova.splitPluginsAndPackages(options.args);
|
||||
|
||||
var exitCode = 0;
|
||||
let exitCode = 0;
|
||||
|
||||
// Update the plugins list
|
||||
if (pluginsToRemove.length) {
|
||||
var plugins = projectContext.cordovaPluginsFile.getPluginVersions();
|
||||
var changed = false;
|
||||
_.each(pluginsToRemove, function (pluginName) {
|
||||
let plugins = projectContext.cordovaPluginsFile.getPluginVersions();
|
||||
let changed = false;
|
||||
for (pluginName of pluginsToRemove) {
|
||||
if (/@/.test(pluginName)) {
|
||||
Console.error(pluginName + ": do not specify version constraints.");
|
||||
exitCode = 1;
|
||||
@@ -2015,21 +2014,19 @@ main.registerCommand({
|
||||
" is not in this project.");
|
||||
exitCode = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
changed && projectContext.cordovaPluginsFile.write(plugins);
|
||||
}
|
||||
|
||||
var args = filteredPackages.rest;
|
||||
|
||||
if (_.isEmpty(args))
|
||||
if (_.isEmpty(packages))
|
||||
return exitCode;
|
||||
|
||||
// For each package name specified, check if we already have it and warn the
|
||||
// user. Because removing each package is a completely atomic operation that
|
||||
// has no chance of failure, this is just a warning message, it doesn't cause
|
||||
// us to stop.
|
||||
var packagesToRemove = [];
|
||||
_.each(args, function (packageName) {
|
||||
let packagesToRemove = [];
|
||||
_.each(packages, function (packageName) {
|
||||
if (/@/.test(packageName)) {
|
||||
Console.error(packageName + ": do not specify version constraints.");
|
||||
exitCode = 1;
|
||||
|
||||
@@ -13,14 +13,15 @@ var httpHelpers = require('../utils/http-helpers.js');
|
||||
var archinfo = require('../utils/archinfo.js');
|
||||
var catalog = require('../packaging/catalog/catalog.js');
|
||||
var stats = require('../meteor-services/stats.js');
|
||||
import { platformsForTargets } from '../cordova/platforms.js';
|
||||
import { buildCordovaProject } from '../cordova/build.js';
|
||||
import { buildCordovaRunners } from '../cordova/run.js';
|
||||
var Console = require('../console/console.js').Console;
|
||||
var projectContextModule = require('../project-context.js');
|
||||
|
||||
var release = require('../packaging/release.js');
|
||||
|
||||
import * as cordova from '../cordova';
|
||||
import { CordovaProject } from '../cordova/project.js';
|
||||
import { CordovaRunner } from '../cordova/runner.js';
|
||||
import { iOSRunTarget, AndroidRunTarget } from '../cordova/run-targets.js';
|
||||
|
||||
// The architecture used by MDG's hosted servers; it's the architecture used by
|
||||
// 'meteor deploy'.
|
||||
var DEPLOY_ARCH = 'os.linux.x86_64';
|
||||
@@ -72,78 +73,77 @@ var showInvalidArchMsg = function (arch) {
|
||||
// Utility functions to parse options in run/build/test-packages commands
|
||||
|
||||
export function parseServerOptionsForRunCommand(options) {
|
||||
const serverUrl = parsePortOption(options.port);
|
||||
const parsedServerUrl = parsePortOption(options.port);
|
||||
|
||||
// XXX COMPAT WITH 0.9.2.2 -- the 'mobile-port' option is deprecated
|
||||
const mobileServerOption = options['mobile-server'] || options['mobile-port'];
|
||||
let mobileServerUrl;
|
||||
let parsedMobileServerUrl;
|
||||
if (mobileServerOption) {
|
||||
mobileServerUrl = parseMobileServerOption(mobileServerOption);
|
||||
parsedMobileServerUrl = parseMobileServerOption(mobileServerOption);
|
||||
} else {
|
||||
mobileServerUrl = mobileServerUrlForServerUrl(serverUrl,
|
||||
parsedMobileServerUrl = detectMobileServerUrl(parsedServerUrl,
|
||||
isRunOnDeviceRequested(options));
|
||||
}
|
||||
|
||||
return { serverUrl, mobileServerUrl };
|
||||
return { parsedServerUrl, parsedMobileServerUrl };
|
||||
}
|
||||
|
||||
function parsePortOption(portOption) {
|
||||
let serverUrl;
|
||||
let parsedServerUrl;
|
||||
try {
|
||||
serverUrl = utils.parseUrl(portOption);
|
||||
} catch (err) {
|
||||
parsedServerUrl = utils.parseUrl(portOption);
|
||||
} catch (error) {
|
||||
if (options.verbose) {
|
||||
Console.rawError(
|
||||
`Error while parsing --port option: ${err.stack} \n`);
|
||||
`Error while parsing --port option: ${error.stack} \n`);
|
||||
} else {
|
||||
Console.error(err.message);
|
||||
Console.error(error.message);
|
||||
}
|
||||
throw new main.ExitWithCode(1);
|
||||
}
|
||||
|
||||
if (!serverUrl.port) {
|
||||
if (!parsedServerUrl.port) {
|
||||
Console.error("--port must include a port.");
|
||||
throw new main.ExitWithCode(1);
|
||||
}
|
||||
|
||||
return serverUrl;
|
||||
return parsedServerUrl;
|
||||
}
|
||||
|
||||
function parseMobileServerOption(mobileServerOption,
|
||||
optionName = 'mobile-server') {
|
||||
let mobileServerUrl;
|
||||
let parsedMobileServerUrl;
|
||||
try {
|
||||
mobileServerUrl = utils.parseUrl(mobileServerOption, {
|
||||
protocol: 'http://'
|
||||
});
|
||||
} catch (err) {
|
||||
parsedMobileServerUrl = utils.parseUrl(mobileServerOption, {
|
||||
protocol: 'http://'});
|
||||
} catch (error) {
|
||||
if (options.verbose) {
|
||||
Console.rawError(
|
||||
`Error while parsing --${optionName} option: ${err.stack} \n`);
|
||||
`Error while parsing --${optionName} option: ${error.stack} \n`);
|
||||
} else {
|
||||
Console.error(err.message);
|
||||
Console.error(error.message);
|
||||
}
|
||||
throw new main.ExitWithCode(1);
|
||||
}
|
||||
|
||||
if (!mobileServerUrl.host) {
|
||||
Console.error(`--${optionName} must specify a hostname.`);
|
||||
if (!parsedMobileServerUrl.host) {
|
||||
Console.error(`--${optionName} must include a hostname.`);
|
||||
throw new main.ExitWithCode(1);
|
||||
}
|
||||
|
||||
return mobileServerUrl;
|
||||
return parsedMobileServerUrl;
|
||||
}
|
||||
|
||||
function mobileServerUrlForServerUrl(serverUrl, isRunOnDeviceRequested) {
|
||||
function detectMobileServerUrl(parsedServerUrl, isRunOnDeviceRequested) {
|
||||
// If we are running on a device, use the auto-detected IP
|
||||
if (isRunOnDeviceRequested) {
|
||||
let myIp;
|
||||
try {
|
||||
myIp = utils.ipAddress();
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
Console.error(
|
||||
`Error detecting IP address for mobile app to connect to:
|
||||
${err.message}
|
||||
${error.message}
|
||||
Please specify the address that the mobile app should connect
|
||||
to with --mobile-server.`);
|
||||
throw new main.ExitWithCode(1);
|
||||
@@ -151,14 +151,14 @@ to with --mobile-server.`);
|
||||
return {
|
||||
protocol: 'http://',
|
||||
host: myIp,
|
||||
port: serverUrl.port
|
||||
port: parsedServerUrl.port
|
||||
};
|
||||
} else {
|
||||
// We are running a simulator, use localhost
|
||||
return {
|
||||
protocol: 'http://',
|
||||
host: 'localhost',
|
||||
port: serverUrl.port
|
||||
port: parsedServerUrl.port
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -166,10 +166,27 @@ to with --mobile-server.`);
|
||||
// Is a run on a device requested?
|
||||
// XXX This shouldn't be hard-coded
|
||||
function isRunOnDeviceRequested(options) {
|
||||
return !!_.intersection(options.args,
|
||||
['ios-device', 'android-device']).length;
|
||||
return !_.isEmpty(_.intersection(options.args,
|
||||
['ios-device', 'android-device']));
|
||||
}
|
||||
|
||||
function parseRunTargets(targets) {
|
||||
return targets.map((target) => {
|
||||
const targetParts = target.split('-');
|
||||
const platform = targetParts[0];
|
||||
const isDevice = targetParts[1] === 'device';
|
||||
|
||||
if (platform == 'ios') {
|
||||
return new iOSRunTarget(isDevice);
|
||||
} else if (platform == 'android') {
|
||||
return new AndroidRunTarget(isDevice);
|
||||
} else {
|
||||
Console.error(`Unknown run target: ${target}`);
|
||||
throw new main.ExitWithCode(1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// options that act like commands
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -289,7 +306,7 @@ main.registerCommand(_.extend(
|
||||
function doRunCommand(options) {
|
||||
Console.setVerbose(!!options.verbose);
|
||||
|
||||
const { serverUrl, mobileServerUrl } =
|
||||
const { parsedServerUrl, parsedMobileServerUrl } =
|
||||
parseServerOptionsForRunCommand(options);
|
||||
|
||||
var projectContext = new projectContextModule.ProjectContext({
|
||||
@@ -313,47 +330,6 @@ function doRunCommand(options) {
|
||||
}
|
||||
}
|
||||
|
||||
var runners = [];
|
||||
// If additional args were specified, then also start a mobile build.
|
||||
// XXX We should defer this work until after the proxy is listening!
|
||||
// eg, move it into a CordovaBuildRunner or something.
|
||||
|
||||
if (options.args.length) {
|
||||
let cordovaProject;
|
||||
// will asynchronously start mobile emulators/devices
|
||||
try {
|
||||
Console.debug('Will compile mobile builds');
|
||||
// Run the constraint solver and build local packages.
|
||||
// XXX This code should be part of the main runner loop so that we can
|
||||
// wait on a fix, just like in the non-Cordova case! (That would also
|
||||
// move the build after the proxy listen.)
|
||||
main.captureAndExit("=> Errors while initializing project:", function () {
|
||||
projectContext.prepareProjectForBuild();
|
||||
});
|
||||
projectContext.packageMapDelta.displayOnConsole();
|
||||
|
||||
let targets = options.args;
|
||||
var platforms = platformsForTargets(targets);
|
||||
cordovaProject = buildCordovaProject(projectContext, platforms, _.extend({
|
||||
debug: !options.production
|
||||
}, options, {
|
||||
protocol: mobileServerUrl.protocol,
|
||||
host: mobileServerUrl.host,
|
||||
port: mobileServerUrl.port
|
||||
}));
|
||||
|
||||
runners = runners.concat(
|
||||
buildCordovaRunners(projectContext, cordovaProject, targets, options));
|
||||
} catch (err) {
|
||||
if (err instanceof main.ExitWithCode) {
|
||||
throw err;
|
||||
} else {
|
||||
Console.printError(err, 'Error while running for mobile platforms');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let appHost, appPort;
|
||||
if (options['app-port']) {
|
||||
var appPortMatch = options['app-port'].match(/^(?:(.+):)?([0-9]+)?$/);
|
||||
@@ -379,22 +355,30 @@ function doRunCommand(options) {
|
||||
// NOTE: this calls process.exit() when testing is done.
|
||||
if (options['test']){
|
||||
options.once = true;
|
||||
const serverUrlString = "http://" + (serverUrl.host || "localhost") +
|
||||
":" + serverUrl.port;
|
||||
const serverUrlForVelocity =
|
||||
`http://${(parsedServerUrl.host || "localhost")}:${parsedServerUrl.port}`;
|
||||
const velocity = require('../runners/run-velocity.js');
|
||||
velocity.runVelocity(serverUrlString);
|
||||
velocity.runVelocity(serverUrlForVelocity);
|
||||
}
|
||||
|
||||
let mobileServerUrlString = mobileServerUrl.protocol + mobileServerUrl.host;
|
||||
if (mobileServerUrl.port) {
|
||||
mobileServerUrlString += `:${mobileServerUrl.port}`;
|
||||
// Additional args are interpreted as run targets
|
||||
const runTargets = parseRunTargets(options.args);
|
||||
|
||||
let cordovaRunner;
|
||||
|
||||
if (!_.isEmpty(runTargets)) {
|
||||
main.captureAndExit('', 'initializing Cordova project', () => {
|
||||
const cordovaProject = new CordovaProject(projectContext);
|
||||
cordovaRunner = new CordovaRunner(cordovaProject, runTargets);
|
||||
cordovaRunner.checkPlatformsForRunTargets();
|
||||
});
|
||||
}
|
||||
|
||||
var runAll = require('../runners/run-all.js');
|
||||
return runAll.run({
|
||||
projectContext: projectContext,
|
||||
proxyPort: serverUrl.port,
|
||||
proxyHost: serverUrl.host,
|
||||
proxyPort: parsedServerUrl.port,
|
||||
proxyHost: parsedServerUrl.host,
|
||||
appPort: appPort,
|
||||
appHost: appHost,
|
||||
debugPort: options['debug-port'],
|
||||
@@ -406,9 +390,9 @@ function doRunCommand(options) {
|
||||
rootUrl: process.env.ROOT_URL,
|
||||
mongoUrl: process.env.MONGO_URL,
|
||||
oplogUrl: process.env.MONGO_OPLOG_URL,
|
||||
mobileServerUrl: mobileServerUrlString,
|
||||
mobileServerUrl: utils.formatUrl(parsedMobileServerUrl),
|
||||
once: options.once,
|
||||
extraRunners: runners
|
||||
cordovaRunner: cordovaRunner
|
||||
});
|
||||
}
|
||||
|
||||
@@ -833,41 +817,34 @@ var buildCommand = function (options) {
|
||||
options.settings = options['mobile-settings'];
|
||||
}
|
||||
|
||||
var mobilePlatforms = [];
|
||||
if (! options._serverOnly) {
|
||||
mobilePlatforms = projectContext.platformList.getCordovaPlatforms();
|
||||
}
|
||||
const appName = files.pathBasename(options.appDir);
|
||||
|
||||
if (!_.isEmpty(mobilePlatforms) && !options._serverOnly) {
|
||||
// XXX COMPAT WITH 0.9.2.2 -- the --mobile-port option is deprecated
|
||||
const mobileServerOption = options.server || options["mobile-port"];
|
||||
if (!mobileServerOption) {
|
||||
// For Cordova builds, require '--server'.
|
||||
// XXX better error message?
|
||||
Console.error(
|
||||
"Supply the server hostname and port in the --server option " +
|
||||
"for mobile app builds.");
|
||||
return 1;
|
||||
let cordovaPlatforms;
|
||||
let parsedMobileServerUrl;
|
||||
if (!options._serverOnly) {
|
||||
cordovaPlatforms = projectContext.platformList.getCordovaPlatforms();
|
||||
|
||||
if (process.platform !== 'darwin' && _.contains(cordovaPlatforms, 'ios')) {
|
||||
cordovaPlatforms = _.without(cordovaPlatforms, 'ios');
|
||||
Console.warn("Currently, it is only possible to build iOS apps on an OS X system.");
|
||||
}
|
||||
const mobileServerUrl = parseMobileServerOption(mobileServerOption,
|
||||
'server');
|
||||
|
||||
var cordovaSettings = {};
|
||||
|
||||
try {
|
||||
cordovaProject =
|
||||
buildCordovaProject(projectContext, mobilePlatforms, _.extend({},
|
||||
options, {
|
||||
protocol: mobileServerUrl.protocol,
|
||||
host: mobileServerUrl.host,
|
||||
port: mobileServerUrl.port
|
||||
}));
|
||||
} catch (err) {
|
||||
if (err instanceof main.ExitWithCode)
|
||||
throw err;
|
||||
Console.printError(err, "Error while building for mobile platforms");
|
||||
return 1;
|
||||
if (!_.isEmpty(cordovaPlatforms)) {
|
||||
// XXX COMPAT WITH 0.9.2.2 -- the --mobile-port option is deprecated
|
||||
const mobileServerOption = options.server || options["mobile-port"];
|
||||
if (!mobileServerOption) {
|
||||
// For Cordova builds, require '--server'.
|
||||
// XXX better error message?
|
||||
Console.error(
|
||||
"Supply the server hostname and port in the --server option " +
|
||||
"for mobile app builds.");
|
||||
return 1;
|
||||
}
|
||||
parsedMobileServerUrl = parseMobileServerOption(mobileServerOption,
|
||||
'server');
|
||||
}
|
||||
} else {
|
||||
cordovaPlatforms = [];
|
||||
}
|
||||
|
||||
var buildDir = projectContext.getProjectLocalDirectory('build_tar');
|
||||
@@ -875,7 +852,7 @@ var buildCommand = function (options) {
|
||||
|
||||
// Unless we're just making a tarball, warn if people try to build inside the
|
||||
// app directory.
|
||||
if (options.directory || ! _.isEmpty(mobilePlatforms)) {
|
||||
if (options.directory || ! _.isEmpty(cordovaPlatforms)) {
|
||||
var relative = files.pathRelative(options.appDir, outputPath);
|
||||
// We would like the output path to be outside the app directory, which
|
||||
// means the first step to getting there is going up a level.
|
||||
@@ -913,7 +890,7 @@ var buildCommand = function (options) {
|
||||
// is then 'meteor bundle' with no args fails if you have any local
|
||||
// packages with binary npm dependencies
|
||||
serverArch: bundleArch,
|
||||
buildMode: options.debug ? 'development' : 'production'
|
||||
buildMode: options.debug ? 'development' : 'production',
|
||||
}
|
||||
});
|
||||
if (bundleResult.errors) {
|
||||
@@ -926,56 +903,74 @@ var buildCommand = function (options) {
|
||||
files.mkdir_p(outputPath);
|
||||
|
||||
if (! options.directory) {
|
||||
try {
|
||||
var outputTar = options._serverOnly ? outputPath :
|
||||
files.pathJoin(outputPath, cordovaProject.appName + '.tar.gz');
|
||||
main.captureAndExit('', 'creating server tarball', () => {
|
||||
try {
|
||||
var outputTar = options._serverOnly ? outputPath :
|
||||
files.pathJoin(outputPath, appName + '.tar.gz');
|
||||
|
||||
files.createTarball(files.pathJoin(buildDir, 'bundle'), outputTar);
|
||||
} catch (err) {
|
||||
Console.error("Errors during tarball creation:");
|
||||
Console.error(err.message);
|
||||
files.rm_recursive(buildDir);
|
||||
return 1;
|
||||
}
|
||||
files.createTarball(files.pathJoin(buildDir, 'bundle'), outputTar);
|
||||
} catch (err) {
|
||||
buildmessage.exception(err);
|
||||
files.rm_recursive(buildDir);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Copy over the Cordova builds AFTER we bundle so that they are not included
|
||||
// in the main bundle.
|
||||
!options._serverOnly && _.each(mobilePlatforms, function (platformName) {
|
||||
var buildPath = files.pathJoin(
|
||||
projectContext.getProjectLocalDirectory('cordova-build'),
|
||||
'platforms', platformName);
|
||||
var platformPath = files.pathJoin(outputPath, platformName);
|
||||
if (!_.isEmpty(cordovaPlatforms)) {
|
||||
let cordovaProject;
|
||||
|
||||
if (platformName === 'ios') {
|
||||
if (process.platform !== 'darwin') return;
|
||||
files.cp_r(buildPath, files.pathJoin(platformPath, 'project'));
|
||||
files.writeFile(
|
||||
files.pathJoin(platformPath, 'README'),
|
||||
"This is an auto-generated XCode project for your iOS application.\n\n" +
|
||||
"Instructions for publishing your iOS app to App Store can be found at:\n" +
|
||||
"https://github.com/meteor/meteor/wiki/How-to-submit-your-iOS-app-to-App-Store\n",
|
||||
"utf8");
|
||||
} else if (platformName === 'android') {
|
||||
files.cp_r(buildPath, files.pathJoin(platformPath, 'project'));
|
||||
var apkPath = findApkPath(files.pathJoin(buildPath, 'build'), options.debug);
|
||||
files.copyFile(apkPath, files.pathJoin(platformPath, options.debug ? 'debug.apk' : 'release-unsigned.apk'));
|
||||
files.writeFile(
|
||||
files.pathJoin(platformPath, 'README'),
|
||||
"This is an auto-generated Gradle project for your Android application.\n\n" +
|
||||
"Instructions for publishing your Android app to Play Store can be found at:\n" +
|
||||
"https://github.com/meteor/meteor/wiki/How-to-submit-your-Android-app-to-Play-Store\n",
|
||||
"utf8");
|
||||
}
|
||||
});
|
||||
main.captureAndExit('', () => {
|
||||
buildmessage.enterJob({ title: "preparing Cordova project"}, () => {
|
||||
cordovaProject = new CordovaProject(projectContext, appName);
|
||||
|
||||
const plugins = cordova.pluginsFromStarManifest(
|
||||
bundleResult.starManifest);
|
||||
|
||||
cordovaProject.prepare(bundlePath, plugins,
|
||||
{ settingsFile: options.settings,
|
||||
mobileServerUrl: utils.formatUrl(parsedMobileServerUrl) });
|
||||
});
|
||||
|
||||
for (platform of cordovaPlatforms) {
|
||||
buildmessage.enterJob({ title: `building Cordova project for \
|
||||
${cordova.displayNameForPlatform(platform)}`}, () => {
|
||||
let buildOptions = [];
|
||||
if (!options.debug) buildOptions.push('--release');
|
||||
cordovaProject.build([platform], buildOptions);
|
||||
|
||||
const buildPath = files.pathJoin(
|
||||
projectContext.getProjectLocalDirectory('cordova-build'),
|
||||
'platforms', platform);
|
||||
const platformOutputPath = files.pathJoin(outputPath, platform);
|
||||
|
||||
if (platform === 'ios') {
|
||||
files.cp_r(buildPath, files.pathJoin(platformOutputPath, 'project'));
|
||||
files.writeFile(
|
||||
files.pathJoin(platformOutputPath, 'README'),
|
||||
"This is an auto-generated XCode project for your iOS application.\n\n" +
|
||||
"Instructions for publishing your iOS app to App Store can be found at:\n" +
|
||||
"https://github.com/meteor/meteor/wiki/How-to-submit-your-iOS-app-to-App-Store\n",
|
||||
"utf8");
|
||||
} else if (platform === 'android') {
|
||||
files.cp_r(buildPath, files.pathJoin(platformOutputPath, 'project'));
|
||||
const apkPath = files.pathJoin(buildPath, 'build', 'outputs', 'apk',
|
||||
options.debug ? 'android-debug.apk' : 'android-release-unsigned.apk')
|
||||
files.copyFile(apkPath, files.pathJoin(platformOutputPath, options.debug ? 'debug.apk' : 'release-unsigned.apk'));
|
||||
files.writeFile(
|
||||
files.pathJoin(platformOutputPath, 'README'),
|
||||
"This is an auto-generated Gradle project for your Android application.\n\n" +
|
||||
"Instructions for publishing your Android app to Play Store can be found at:\n" +
|
||||
"https://github.com/meteor/meteor/wiki/How-to-submit-your-Android-app-to-Play-Store\n",
|
||||
"utf8");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
files.rm_recursive(buildDir);
|
||||
};
|
||||
|
||||
var findApkPath = function (dirPath, debug) {
|
||||
return files.pathJoin(dirPath, 'outputs', 'apk', debug ? 'android-debug.apk' : 'android-release-unsigned.apk');
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// lint
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1434,7 +1429,7 @@ main.registerCommand({
|
||||
}, function (options) {
|
||||
Console.setVerbose(!!options.verbose);
|
||||
|
||||
const { serverUrl, mobileServerUrl } =
|
||||
const { parsedServerUrl, parsedMobileServerUrl } =
|
||||
parseServerOptionsForRunCommand(options);
|
||||
|
||||
// Find any packages mentioned by a path instead of a package name. We will
|
||||
@@ -1505,59 +1500,32 @@ main.registerCommand({
|
||||
// runner, once the proxy is listening. The changes we made were persisted to
|
||||
// disk, so projectContext.reset won't make us forget anything.
|
||||
|
||||
var mobileOptions = ['ios', 'ios-device', 'android', 'android-device'];
|
||||
var mobileTargets = [];
|
||||
_.each(mobileOptions, function (option) {
|
||||
if (options[option])
|
||||
mobileTargets.push(option);
|
||||
});
|
||||
const runTargets = parseRunTargets(_.intersection(
|
||||
Object.keys(options), ['ios', 'ios-device', 'android', 'android-device']));
|
||||
|
||||
if (! _.isEmpty(mobileTargets)) {
|
||||
var runners = [];
|
||||
let cordovaRunner;
|
||||
|
||||
var platforms = platformsForTargets(mobileTargets);
|
||||
projectContext.platformList.write(platforms);
|
||||
|
||||
// Run the constraint solver and build local packages.
|
||||
// XXX This code should be part of the main runner loop so that we can
|
||||
// wait on a fix, just like in the non-Cordova case! (That would also
|
||||
// move the build after the proxy listen.)
|
||||
main.captureAndExit("=> Errors while initializing project:", function () {
|
||||
projectContext.prepareProjectForBuild();
|
||||
if (!_.isEmpty(runTargets)) {
|
||||
main.captureAndExit('', 'initializing Cordova project', () => {
|
||||
const cordovaProject = new CordovaProject(projectContext);
|
||||
cordovaRunner = new CordovaRunner(cordovaProject, runTargets);
|
||||
projectContext.platformList.write(cordovaRunner.platformsForRunTargets);
|
||||
cordovaRunner.checkPlatformsForRunTargets();
|
||||
});
|
||||
// No need to display the PackageMapDelta here, since it would include all
|
||||
// of the packages!
|
||||
|
||||
try {
|
||||
const cordovaProject = buildCordovaProject(projectContext, platforms,
|
||||
_.extend({}, options, {
|
||||
debug: ! options.production
|
||||
}, {
|
||||
protocol: mobileServerUrl.protocol,
|
||||
host: mobileServerUrl.host,
|
||||
port: mobileServerUrl.port
|
||||
}));
|
||||
runners = runners.concat(buildCordovaRunners(projectContext,
|
||||
cordovaProject, mobileTargets, options));
|
||||
} catch (err) {
|
||||
if (err instanceof main.ExitWithCode) {
|
||||
throw err;
|
||||
} else {
|
||||
Console.printError(err, 'Error while testing for mobile platforms');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
options.extraRunners = runners;
|
||||
}
|
||||
|
||||
options.cordovaRunner = cordovaRunner;
|
||||
|
||||
if (options.velocity) {
|
||||
const serverUrlString = "http://" + (parsedUrl.host || "localhost") +
|
||||
":" + parsedUrl.port;
|
||||
const serverUrlForVelocity =
|
||||
`http://${(parsedServerUrl.host || "localhost")}:${parsedServerUrl.port}`;
|
||||
const velocity = require('../runners/run-velocity.js');
|
||||
velocity.runVelocity(serverUrlString);
|
||||
velocity.runVelocity(serverUrlForVelocity);
|
||||
}
|
||||
|
||||
return runTestAppForPackages(projectContext, options);
|
||||
return runTestAppForPackages(projectContext, _.extend(
|
||||
options,
|
||||
{ mobileServerUrl: utils.formatUrl(parsedMobileServerUrl) }));
|
||||
});
|
||||
|
||||
// Returns the "local-test:*" package names for the given package names (or for
|
||||
@@ -1650,11 +1618,12 @@ var runTestAppForPackages = function (projectContext, options) {
|
||||
rootUrl: process.env.ROOT_URL,
|
||||
mongoUrl: process.env.MONGO_URL,
|
||||
oplogUrl: process.env.MONGO_OPLOG_URL,
|
||||
mobileServerUrl: options.mobileServerUrl,
|
||||
once: options.once,
|
||||
recordPackageUsage: false,
|
||||
selenium: options.selenium,
|
||||
seleniumBrowser: options['selenium-browser'],
|
||||
extraRunners: options.extraRunners,
|
||||
cordovaRunner: options.cordovaRunner,
|
||||
// On the first run, we shouldn't display the delta between "no packages
|
||||
// in the temp app" and "all the packages we're testing". If we make
|
||||
// changes and reload, though, it's fine to display them.
|
||||
|
||||
44
tools/cordova/android-runner.js
vendored
44
tools/cordova/android-runner.js
vendored
@@ -1,44 +0,0 @@
|
||||
import isopackets from '../tool-env/isopackets.js'
|
||||
import files from '../fs/files.js';
|
||||
import { Console } from '../console.js';
|
||||
|
||||
import CordovaRunner from './cordova-runner.js'
|
||||
import { execFileSyncOrThrow, execFileAsyncOrThrow } from './utils.js'
|
||||
|
||||
export default class AndroidRunner extends CordovaRunner {
|
||||
constructor(projectContext, cordovaProject, isDevice, options) {
|
||||
super(projectContext, cordovaProject, options);
|
||||
this.isDevice = isDevice;
|
||||
}
|
||||
|
||||
get platform() {
|
||||
return 'android';
|
||||
}
|
||||
|
||||
get displayName() {
|
||||
return this.isDevice ? 'Android Device' : 'Android Emulator';
|
||||
}
|
||||
|
||||
checkRequirementsAndSetEnvIfNeeded() {
|
||||
const platformsDir = files.pathJoin(this.cordovaProject.projectRoot, 'platforms');
|
||||
const modulePath = files.pathJoin(platformsDir, 'android', 'cordova', 'lib', 'check_reqs');
|
||||
Promise.await(require(files.convertToOSPath(modulePath)).run());
|
||||
}
|
||||
|
||||
async run(options) {
|
||||
return this.cordovaProject.run(this.platform, this.isDevice, options)
|
||||
}
|
||||
|
||||
async tailLogs(options) {
|
||||
// Make cordova-android handle requirements and set env if needed
|
||||
this.checkRequirementsAndSetEnvIfNeeded();
|
||||
|
||||
// Clear logs
|
||||
execFileSyncOrThrow('adb', ['logcat', '-c']);
|
||||
|
||||
execFileAsyncOrThrow('adb', ['logcat'], {
|
||||
verbose: true,
|
||||
lineMapper: null
|
||||
});
|
||||
}
|
||||
}
|
||||
193
tools/cordova/build.js
vendored
193
tools/cordova/build.js
vendored
@@ -1,193 +0,0 @@
|
||||
import _ from 'underscore';
|
||||
import util from 'util';
|
||||
import { Console } from '../console.js';
|
||||
import buildmessage from '../buildmessage.js';
|
||||
import files from '../fs/files.js';
|
||||
import bundler from '../isobuild/bundler.js';
|
||||
import archinfo from '../archinfo.js';
|
||||
import release from '../packaging/release.js';
|
||||
import isopackets from '../tool-env/isopackets.js'
|
||||
|
||||
import { createCordovaProjectIfNecessary } from './project.js';
|
||||
import { AVAILABLE_PLATFORMS, ensureCordovaPlatformsAreSynchronized,
|
||||
checkCordovaPlatforms } from './platforms.js';
|
||||
import { ensureCordovaPluginsAreSynchronized } from './plugins.js';
|
||||
import { processMobileControlFile } from './mobile-control-file.js';
|
||||
|
||||
const WEB_ARCH_NAME = "web.cordova";
|
||||
|
||||
// Returns the cordovaDependencies of the Cordova arch from a star json.
|
||||
export function getCordovaDependenciesFromStar(star) {
|
||||
var cordovaProgram = _.findWhere(star.programs, { arch: WEB_ARCH_NAME });
|
||||
if (cordovaProgram) {
|
||||
return cordovaProgram.cordovaDependencies;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Build a Cordova project, creating it if necessary.
|
||||
export function buildCordovaProject(projectContext, platforms, options) {
|
||||
if (_.isEmpty(platforms)) return;
|
||||
|
||||
Console.debug('Building the Cordova project');
|
||||
|
||||
platforms = checkCordovaPlatforms(projectContext, platforms);
|
||||
|
||||
// Make sure there is a project, as all other operations depend on that
|
||||
const cordovaProject = createCordovaProjectIfNecessary(projectContext);
|
||||
|
||||
buildmessage.enterJob({ title: 'building for mobile devices' }, function () {
|
||||
const bundlePath =
|
||||
projectContext.getProjectLocalDirectory('build-cordova-temp');
|
||||
|
||||
Console.debug('Bundling the web.cordova program of the app');
|
||||
const bundle = getBundle(projectContext, bundlePath, options);
|
||||
|
||||
// Check and consume the control file
|
||||
const controlFilePath =
|
||||
files.pathJoin(projectContext.projectDir, 'mobile-config.js');
|
||||
|
||||
processMobileControlFile(
|
||||
controlFilePath,
|
||||
projectContext,
|
||||
cordovaProject,
|
||||
options.host);
|
||||
|
||||
ensureCordovaPlatformsAreSynchronized(cordovaProject,
|
||||
projectContext.platformList.getPlatforms());
|
||||
|
||||
ensureCordovaPluginsAreSynchronized(cordovaProject, getCordovaDependenciesFromStar(
|
||||
bundle.starManifest));
|
||||
|
||||
const wwwPath = files.pathJoin(cordovaProject.projectRoot, 'www');
|
||||
|
||||
Console.debug('Removing the www folder');
|
||||
files.rm_recursive(wwwPath);
|
||||
|
||||
const applicationPath = files.pathJoin(wwwPath, 'application');
|
||||
const programPath = files.pathJoin(bundlePath, 'programs', WEB_ARCH_NAME);
|
||||
|
||||
Console.debug('Writing www/application folder');
|
||||
files.mkdir_p(applicationPath);
|
||||
files.cp_r(programPath, applicationPath);
|
||||
|
||||
// Clean up the temporary bundle directory
|
||||
files.rm_recursive(bundlePath);
|
||||
|
||||
Console.debug('Writing index.html');
|
||||
|
||||
// Generate index.html
|
||||
var indexHtml = generateCordovaBoilerplate(
|
||||
projectContext, applicationPath, options);
|
||||
files.writeFile(files.pathJoin(applicationPath, 'index.html'), indexHtml, 'utf8');
|
||||
|
||||
// Write the cordova loader
|
||||
Console.debug('Writing meteor_cordova_loader');
|
||||
var loaderPath = files.pathJoin(__dirname, 'client', 'meteor_cordova_loader.js');
|
||||
var loaderCode = files.readFile(loaderPath);
|
||||
files.writeFile(files.pathJoin(wwwPath, 'meteor_cordova_loader.js'), loaderCode);
|
||||
|
||||
Console.debug('Writing a default index.html for cordova app');
|
||||
var indexPath = files.pathJoin(__dirname, 'client', 'cordova_index.html');
|
||||
var indexContent = files.readFile(indexPath);
|
||||
files.writeFile(files.pathJoin(wwwPath, 'index.html'), indexContent);
|
||||
|
||||
// Cordova Build Override feature (c)
|
||||
var buildOverridePath =
|
||||
files.pathJoin(projectContext.projectDir, 'cordova-build-override');
|
||||
|
||||
if (files.exists(buildOverridePath) &&
|
||||
files.stat(buildOverridePath).isDirectory()) {
|
||||
Console.debug('Copying over the cordova-build-override');
|
||||
files.cp_r(buildOverridePath, cordovaProject.projectRoot);
|
||||
}
|
||||
|
||||
// Run the actual build
|
||||
Console.debug('Running the build command');
|
||||
|
||||
buildmessage.enterJob({ title: 'building mobile project' }, () => {
|
||||
const buildOptions = options.debug ? [] : ['release'];
|
||||
Promise.await(cordovaProject.build({ platforms: platforms, options: buildOptions }));
|
||||
});
|
||||
});
|
||||
|
||||
Console.debug('Done building the cordova build project');
|
||||
|
||||
return cordovaProject;
|
||||
};
|
||||
|
||||
// options
|
||||
// - debug
|
||||
function getBundle(projectContext, bundlePath, options) {
|
||||
var bundleResult = bundler.bundle({
|
||||
projectContext: projectContext,
|
||||
outputPath: bundlePath,
|
||||
buildOptions: {
|
||||
minifyMode: options.debug ? 'development' : 'production',
|
||||
// XXX can we ask it not to create the server arch?
|
||||
serverArch: archinfo.host(),
|
||||
webArchs: [WEB_ARCH_NAME],
|
||||
includeDebug: !!options.debug
|
||||
}
|
||||
});
|
||||
|
||||
if (bundleResult.errors) {
|
||||
// XXX better error handling?
|
||||
throw new Error("Errors prevented bundling:\n" +
|
||||
bundleResult.errors.formatMessages());
|
||||
}
|
||||
|
||||
return bundleResult;
|
||||
};
|
||||
|
||||
function generateCordovaBoilerplate(projectContext, clientDir, options) {
|
||||
var clientJsonPath = files.convertToOSPath(files.pathJoin(clientDir, 'program.json'));
|
||||
var clientJson = JSON.parse(files.readFile(clientJsonPath, 'utf8'));
|
||||
var manifest = clientJson.manifest;
|
||||
var settings = options.settings ?
|
||||
JSON.parse(files.readFile(options.settings, 'utf8')) : {};
|
||||
var publicSettings = settings['public'];
|
||||
|
||||
var meteorRelease =
|
||||
release.current.isCheckout() ? "none" : release.current.name;
|
||||
|
||||
var configDummy = {};
|
||||
if (publicSettings) configDummy.PUBLIC_SETTINGS = publicSettings;
|
||||
|
||||
const { WebAppHashing } = isopackets.load('cordova-support')['webapp-hashing'];
|
||||
var calculatedHash =
|
||||
WebAppHashing.calculateClientHash(manifest, null, configDummy);
|
||||
|
||||
// XXX partially copied from autoupdate package
|
||||
var version = process.env.AUTOUPDATE_VERSION || calculatedHash;
|
||||
|
||||
var mobileServer = options.protocol + options.host;
|
||||
if (options.port) {
|
||||
mobileServer = mobileServer + ":" + options.port;
|
||||
}
|
||||
|
||||
var runtimeConfig = {
|
||||
meteorRelease: meteorRelease,
|
||||
ROOT_URL: mobileServer + "/",
|
||||
// XXX propagate it from options?
|
||||
ROOT_URL_PATH_PREFIX: '',
|
||||
DDP_DEFAULT_CONNECTION_URL: mobileServer,
|
||||
autoupdateVersionCordova: version,
|
||||
appId: projectContext.appIdentifier
|
||||
};
|
||||
|
||||
if (publicSettings)
|
||||
runtimeConfig.PUBLIC_SETTINGS = publicSettings;
|
||||
|
||||
const { Boilerplate } = isopackets.load('cordova-support')['boilerplate-generator'];
|
||||
var boilerplate = new Boilerplate(WEB_ARCH_NAME, manifest, {
|
||||
urlMapper: _.identity,
|
||||
pathMapper: (path) => files.convertToOSPath(files.pathJoin(clientDir, path)),
|
||||
baseDataExtension: {
|
||||
meteorRuntimeConfig: JSON.stringify(
|
||||
encodeURIComponent(JSON.stringify(runtimeConfig)))
|
||||
}
|
||||
});
|
||||
return boilerplate.toHTML();
|
||||
};
|
||||
589
tools/cordova/builder.js
vendored
Normal file
589
tools/cordova/builder.js
vendored
Normal file
@@ -0,0 +1,589 @@
|
||||
import _ from 'underscore';
|
||||
import util from 'util';
|
||||
import { Console } from '../console/console.js';
|
||||
import buildmessage from '../utils/buildmessage.js';
|
||||
import files from '../fs/files.js';
|
||||
import bundler from '../isobuild/bundler.js';
|
||||
import archinfo from '../utils/archinfo.js';
|
||||
import release from '../packaging/release.js';
|
||||
import isopackets from '../tool-env/isopackets.js';
|
||||
import utils from '../utils/utils.js';
|
||||
|
||||
import { CORDOVA_ARCH } from './index.js';
|
||||
|
||||
// Hard-coded size constants
|
||||
|
||||
const iconsIosSizes = {
|
||||
'iphone': '60x60',
|
||||
'iphone_2x': '120x120',
|
||||
'iphone_3x': '180x180',
|
||||
'ipad': '76x76',
|
||||
'ipad_2x': '152x152'
|
||||
};
|
||||
|
||||
const iconsAndroidSizes = {
|
||||
'android_ldpi': '36x36',
|
||||
'android_mdpi': '42x42',
|
||||
'android_hdpi': '72x72',
|
||||
'android_xhdpi': '96x96'
|
||||
};
|
||||
|
||||
const launchIosSizes = {
|
||||
'iphone': '320x480',
|
||||
'iphone_2x': '640x960',
|
||||
'iphone5': '640x1136',
|
||||
'iphone6': '750x1334',
|
||||
'iphone6p_portrait': '1242x2208',
|
||||
'iphone6p_landscape': '2208x1242',
|
||||
'ipad_portrait': '768x1004',
|
||||
'ipad_portrait_2x': '1536x2008',
|
||||
'ipad_landscape': '1024x748',
|
||||
'ipad_landscape_2x': '2048x1496'
|
||||
};
|
||||
|
||||
const launchAndroidSizes = {
|
||||
'android_ldpi_portrait': '320x426',
|
||||
'android_ldpi_landscape': '426x320',
|
||||
'android_mdpi_portrait': '320x470',
|
||||
'android_mdpi_landscape': '470x320',
|
||||
'android_hdpi_portrait': '480x640',
|
||||
'android_hdpi_landscape': '640x480',
|
||||
'android_xhdpi_portrait': '720x960',
|
||||
'android_xhdpi_landscape': '960x720'
|
||||
};
|
||||
|
||||
export class CordovaBuilder {
|
||||
constructor(cordovaProject, bundlePath, plugins, options) {
|
||||
this.cordovaProject = cordovaProject;
|
||||
|
||||
this.bundlePath = bundlePath;
|
||||
this.plugins = plugins;
|
||||
this.options = options;
|
||||
|
||||
this.resourcesPath = files.pathJoin(
|
||||
this.cordovaProject.projectRoot,
|
||||
'resources');
|
||||
}
|
||||
|
||||
get projectContext() {
|
||||
return this.cordovaProject.projectContext;
|
||||
}
|
||||
|
||||
start() {
|
||||
buildmessage.assertInCapture();
|
||||
|
||||
buildmessage.enterJob({ title: `preparing Cordova project` }, () => {
|
||||
this.initalizeDefaults();
|
||||
|
||||
this.processControlFile();
|
||||
|
||||
this.writeConfigXmlAndCopyResources();
|
||||
this.copyWWW();
|
||||
this.copyBuildOverride();
|
||||
|
||||
this.cordovaProject.ensurePlatformsAreSynchronized();
|
||||
this.cordovaProject.ensurePluginsAreSynchronized(this.plugins,
|
||||
this.pluginsConfiguration);
|
||||
});
|
||||
}
|
||||
|
||||
initalizeDefaults() {
|
||||
const defaultBuildNumber = (Date.now() % 1000000).toString();
|
||||
this.metadata = {
|
||||
id: 'com.id' + this.projectContext.appIdentifier,
|
||||
version: '0.0.1',
|
||||
buildNumber: defaultBuildNumber,
|
||||
name: this.cordovaProject.appName,
|
||||
description: 'New Meteor Mobile App',
|
||||
author: 'A Meteor Developer',
|
||||
email: 'n/a',
|
||||
website: 'n/a'
|
||||
};
|
||||
|
||||
// set some defaults different from the Phonegap/Cordova defaults
|
||||
this.additionalConfiguration = {
|
||||
'webviewbounce': false,
|
||||
'DisallowOverscroll': true,
|
||||
'deployment-target': '7.0'
|
||||
};
|
||||
|
||||
if (this.projectContext.packageMap.getInfo('launch-screen')) {
|
||||
this.additionalConfiguration.AutoHideSplashScreen = false;
|
||||
this.additionalConfiguration.SplashScreen = 'screen';
|
||||
this.additionalConfiguration.SplashScreenDelay = 10000;
|
||||
}
|
||||
|
||||
// Default access rules for plain Meteor-Cordova apps.
|
||||
// Rules can be extended with mobile-config API.
|
||||
// The value is `true` if the protocol or domain should be allowed,
|
||||
// 'external' if should handled externally.
|
||||
this.accessRules = {
|
||||
// Allow external calls to things like email client or maps app or a
|
||||
// phonebook app.
|
||||
'tel:*': 'external',
|
||||
'geo:*': 'external',
|
||||
'mailto:*': 'external',
|
||||
'sms:*': 'external',
|
||||
'market:*': 'external',
|
||||
|
||||
// phonegap/cordova related protocols
|
||||
// "file:" protocol is used to access first files from disk
|
||||
'file:*': true,
|
||||
'cdv:*': true,
|
||||
'gap:*': true,
|
||||
|
||||
// allow Meteor's local emulated server url - this is the url from which the
|
||||
// application loads its assets
|
||||
'http://meteor.local/*': true
|
||||
};
|
||||
|
||||
const mobileServerUrl = this.options.mobileServerUrl;
|
||||
const serverDomain = mobileServerUrl ?
|
||||
utils.parseUrl(mobileServerUrl).host : null;
|
||||
|
||||
// If the remote server domain is known, allow access to it for xhr and DDP
|
||||
// connections.
|
||||
if (serverDomain) {
|
||||
this.accessRules['*://' + serverDomain + '/*'] = true;
|
||||
// Android talks to localhost over 10.0.2.2. This config file is used for
|
||||
// multiple platforms, so any time that we say the server is on localhost we
|
||||
// should also say it is on 10.0.2.2.
|
||||
if (serverDomain === 'localhost') {
|
||||
this.accessRules['*://10.0.2.2/*'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.imagePaths = {
|
||||
icon: {},
|
||||
splash: {}
|
||||
};
|
||||
|
||||
// Defaults are Meteor meatball images located in tools/cordova/assets directory
|
||||
const assetsPath = files.pathJoin(__dirname, 'assets');
|
||||
const iconsPath = files.pathJoin(assetsPath, 'icons');
|
||||
const launchScreensPath = files.pathJoin(assetsPath, 'launchscreens');
|
||||
|
||||
const setIcon = (size, name) => {
|
||||
this.imagePaths.icon[name] = files.pathJoin(iconsPath, size + '.png');
|
||||
};
|
||||
|
||||
const setLaunchscreen = (size, name) => {
|
||||
this.imagePaths.splash[name] = files.pathJoin(launchScreensPath, size + '.png');
|
||||
};
|
||||
|
||||
_.each(iconsIosSizes, setIcon);
|
||||
_.each(iconsAndroidSizes, setIcon);
|
||||
_.each(launchIosSizes, setLaunchscreen);
|
||||
_.each(launchAndroidSizes, setLaunchscreen);
|
||||
|
||||
this.pluginsConfiguration = {};
|
||||
}
|
||||
|
||||
processControlFile() {
|
||||
const controlFilePath =
|
||||
files.pathJoin(this.projectContext.projectDir, 'mobile-config.js');
|
||||
|
||||
|
||||
if (files.exists(controlFilePath)) {
|
||||
const code = files.readFile(controlFilePath, 'utf8');
|
||||
|
||||
try {
|
||||
Console.debug('Running the mobile control file');
|
||||
files.runJavaScript(code, {
|
||||
filename: 'mobile-config.js',
|
||||
symbols: { App: App(this) }
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error('Error reading mobile-config.js:' + error.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeConfigXmlAndCopyResources() {
|
||||
const { XmlBuilder } = isopackets.load('cordova-support')['xmlbuilder'];
|
||||
|
||||
let config = XmlBuilder.create('widget');
|
||||
|
||||
// set the root attributes
|
||||
_.each({
|
||||
id: this.metadata.id,
|
||||
version: this.metadata.version,
|
||||
'android-versionCode': this.metadata.buildNumber,
|
||||
'ios-CFBundleVersion': this.metadata.buildNumber,
|
||||
xmlns: 'http://www.w3.org/ns/widgets',
|
||||
'xmlns:cdv': 'http://cordova.apache.org/ns/1.0'
|
||||
}, (value, key) => {
|
||||
if (value) {
|
||||
config.att(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
// set the metadata
|
||||
config.element('name').txt(this.metadata.name);
|
||||
config.element('description').txt(this.metadata.description);
|
||||
config.element('author', {
|
||||
href: this.metadata.website,
|
||||
email: this.metadata.email
|
||||
}).txt(this.metadata.author);
|
||||
|
||||
// set the additional configuration preferences
|
||||
_.each(this.additionalConfiguration, (value, key) => {
|
||||
config.element('preference', {
|
||||
name: key,
|
||||
value: value.toString()
|
||||
});
|
||||
});
|
||||
|
||||
// load from index.html by default
|
||||
config.element('content', { src: 'index.html' });
|
||||
|
||||
// Copy all the access rules
|
||||
_.each(this.accessRules, (rule, pattern) => {
|
||||
var opts = { origin: pattern };
|
||||
if (rule === 'external')
|
||||
opts['launch-external'] = true;
|
||||
|
||||
config.element('access', opts);
|
||||
});
|
||||
|
||||
const iosPlatformElement = config.element('platform', { name: 'ios' });
|
||||
const androidPlatformElement = config.element('platform', { name: 'android' });
|
||||
|
||||
// Prepare the resources folder
|
||||
files.rm_recursive(this.resourcesPath);
|
||||
files.mkdir_p(this.resourcesPath);
|
||||
|
||||
Console.debug('Copying resources for mobile apps');
|
||||
|
||||
// add icons and launch screens to config and copy the files on fs
|
||||
this.configureAndCopyImages(iconsIosSizes, iosPlatformElement, 'icon');
|
||||
this.configureAndCopyImages(iconsAndroidSizes, androidPlatformElement, 'icon');
|
||||
this.configureAndCopyImages(launchIosSizes, iosPlatformElement, 'splash');
|
||||
this.configureAndCopyImages(launchAndroidSizes, androidPlatformElement, 'splash');
|
||||
|
||||
Console.debug('Writing new config.xml');
|
||||
const configXmlPath = files.pathJoin(this.cordovaProject.projectRoot, 'config.xml');
|
||||
const formattedXmlConfig = config.end({ pretty: true });
|
||||
files.writeFile(configXmlPath, formattedXmlConfig, 'utf8');
|
||||
}
|
||||
|
||||
configureAndCopyImages(sizes, xmlElement, tag) {
|
||||
const imageAttributes = (name, width, height, src) => {
|
||||
const androidMatch = /android_(.?.dpi)_(landscape|portrait)/g.exec(name);
|
||||
|
||||
let attributes = {
|
||||
src: src,
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
|
||||
// XXX special case for Android
|
||||
if (androidMatch) {
|
||||
attributes.density = androidMatch[2].substr(0, 4) + '-' + androidMatch[1];
|
||||
}
|
||||
|
||||
return attributes;
|
||||
};
|
||||
|
||||
_.each(sizes, (size, name) => {
|
||||
const [width, height] = size.split('x');
|
||||
|
||||
const suppliedPath = this.imagePaths[tag][name];
|
||||
if (!suppliedPath)
|
||||
return;
|
||||
|
||||
const suppliedFilename = _.last(suppliedPath.split(files.pathSep));
|
||||
let extension = _.last(suppliedFilename.split('.'));
|
||||
|
||||
// XXX special case for 9-patch png's
|
||||
if (suppliedFilename.match(/\.9\.png$/)) {
|
||||
extension = '9.png';
|
||||
}
|
||||
|
||||
const filename = name + '.' + tag + '.' + extension;
|
||||
const src = files.pathJoin('resources', filename);
|
||||
|
||||
// copy the file to the build folder with a standardized name
|
||||
files.copyFile(
|
||||
files.pathResolve(this.projectContext.projectDir, suppliedPath),
|
||||
files.pathJoin(this.resourcesPath, filename));
|
||||
|
||||
// set it to the xml tree
|
||||
xmlElement.element(tag, imageAttributes(name, width, height, src));
|
||||
|
||||
// XXX reuse one size for other dimensions
|
||||
const dups = {
|
||||
'60x60': ['29x29', '40x40', '50x50', '57x57', '58x58'],
|
||||
'76x76': ['72x72'],
|
||||
'152x152': ['144x144'],
|
||||
'120x120': ['80x80', '100x100', '114x114'],
|
||||
'768x1004': ['768x1024'],
|
||||
'1536x2008': ['1536x2048'],
|
||||
'1024x748': ['1024x768'],
|
||||
'2048x1496': ['2048x1536']
|
||||
}[size];
|
||||
|
||||
// just use the same image
|
||||
_.each(dups, (size) => {
|
||||
const [width, height] = size.split('x');
|
||||
// XXX this is fine to not supply a name since it is always iOS, but
|
||||
// this is a hack right now.
|
||||
xmlElement.element(tag, imageAttributes('n/a', width, height, src));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
copyWWW() {
|
||||
const wwwPath = files.pathJoin(this.cordovaProject.projectRoot, 'www');
|
||||
|
||||
// Remove existing www
|
||||
files.rm_recursive(wwwPath);
|
||||
|
||||
// Create www and www/application directories
|
||||
const applicationPath = files.pathJoin(wwwPath, 'application');
|
||||
files.mkdir_p(applicationPath);
|
||||
|
||||
// Copy Cordova arch program from bundle to www/application
|
||||
const programPath = files.pathJoin(this.bundlePath, 'programs', CORDOVA_ARCH);
|
||||
files.cp_r(programPath, applicationPath);
|
||||
|
||||
const bootstrapPage = this.generateBootstrapPage(applicationPath);
|
||||
files.writeFile(files.pathJoin(applicationPath, 'index.html'),
|
||||
bootstrapPage, 'utf8');
|
||||
|
||||
files.copyFile(
|
||||
files.pathJoin(__dirname, 'client', 'meteor_cordova_loader.js'),
|
||||
files.pathJoin(wwwPath, 'meteor_cordova_loader.js'));
|
||||
files.copyFile(
|
||||
files.pathJoin(__dirname, 'client', 'cordova_index.html'),
|
||||
files.pathJoin(wwwPath, 'index.html'));
|
||||
}
|
||||
|
||||
generateBootstrapPage(applicationPath) {
|
||||
const programJsonPath = files.convertToOSPath(
|
||||
files.pathJoin(applicationPath, 'program.json'));
|
||||
const programJson = JSON.parse(files.readFile(programJsonPath, 'utf8'));
|
||||
const manifest = programJson.manifest;
|
||||
|
||||
const settingsFile = this.options.settingsFile;
|
||||
const settings = settingsFile ?
|
||||
JSON.parse(files.readFile(settingsFile, 'utf8')) : {};
|
||||
const publicSettings = settings['public'];
|
||||
|
||||
const meteorRelease =
|
||||
release.current.isCheckout() ? "none" : release.current.name;
|
||||
|
||||
let configDummy = {};
|
||||
if (publicSettings) {
|
||||
configDummy.PUBLIC_SETTINGS = publicSettings;
|
||||
}
|
||||
|
||||
const { WebAppHashing } = isopackets.load('cordova-support')['webapp-hashing'];
|
||||
const calculatedHash =
|
||||
WebAppHashing.calculateClientHash(manifest, null, configDummy);
|
||||
|
||||
// XXX partially copied from autoupdate package
|
||||
const version = process.env.AUTOUPDATE_VERSION || calculatedHash;
|
||||
|
||||
const mobileServerUrl = this.options.mobileServerUrl;
|
||||
|
||||
const runtimeConfig = {
|
||||
meteorRelease: meteorRelease,
|
||||
ROOT_URL: mobileServerUrl + "/",
|
||||
// XXX propagate it from this.options?
|
||||
ROOT_URL_PATH_PREFIX: '',
|
||||
DDP_DEFAULT_CONNECTION_URL: mobileServerUrl,
|
||||
autoupdateVersionCordova: version,
|
||||
appId: this.projectContext.appIdentifier
|
||||
};
|
||||
|
||||
if (publicSettings)
|
||||
runtimeConfig.PUBLIC_SETTINGS = publicSettings;
|
||||
|
||||
const { Boilerplate } = isopackets.load('cordova-support')['boilerplate-generator'];
|
||||
const boilerplate = new Boilerplate(CORDOVA_ARCH, manifest, {
|
||||
urlMapper: _.identity,
|
||||
pathMapper: (path) => files.convertToOSPath(
|
||||
files.pathJoin(applicationPath, path)),
|
||||
baseDataExtension: {
|
||||
meteorRuntimeConfig: JSON.stringify(
|
||||
encodeURIComponent(JSON.stringify(runtimeConfig)))
|
||||
}
|
||||
});
|
||||
|
||||
return boilerplate.toHTML();
|
||||
}
|
||||
|
||||
copyBuildOverride() {
|
||||
const buildOverridePath =
|
||||
files.pathJoin(this.projectContext.projectDir, 'cordova-build-override');
|
||||
|
||||
if (files.exists(buildOverridePath) &&
|
||||
files.stat(buildOverridePath).isDirectory()) {
|
||||
Console.debug('Copying over the cordova-build-override directory');
|
||||
files.cp_r(buildOverridePath, this.cordovaProject.projectRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function App(builder) {
|
||||
/**
|
||||
* @namespace App
|
||||
* @global
|
||||
* @summary The App configuration object in mobile-config.js
|
||||
*/
|
||||
return {
|
||||
/**
|
||||
* @summary Set your mobile app's core configuration information.
|
||||
* @param {Object} options
|
||||
* @param {String} [options.id,version,name,description,author,email,website]
|
||||
* Each of the options correspond to a key in the app's core configuration
|
||||
* as described in the [PhoneGap documentation](http://docs.phonegap.com/en/3.5.0/config_ref_index.md.html#The%20config.xml%20File_core_configuration_elements).
|
||||
* @memberOf App
|
||||
*/
|
||||
info: function (options) {
|
||||
// check that every key is meaningful
|
||||
_.each(options, function (value, key) {
|
||||
if (!_.has(builder.metadata, key))
|
||||
throw new Error("Unknown key in App.info configuration: " + key);
|
||||
});
|
||||
|
||||
_.extend(builder.metadata, options);
|
||||
},
|
||||
/**
|
||||
* @summary Add a preference for your build as described in the
|
||||
* [PhoneGap documentation](http://docs.phonegap.com/en/3.5.0/config_ref_index.md.html#The%20config.xml%20File_global_preferences).
|
||||
* @param {String} name A preference name supported by Phonegap's
|
||||
* `config.xml`.
|
||||
* @param {String} value The value for that preference.
|
||||
* @memberOf App
|
||||
*/
|
||||
setPreference: function (key, value) {
|
||||
builder.additionalConfiguration[key] = value;
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Set the build-time configuration for a Phonegap plugin.
|
||||
* @param {String} pluginName The identifier of the plugin you want to
|
||||
* configure.
|
||||
* @param {Object} config A set of key-value pairs which will be passed
|
||||
* at build-time to configure the specified plugin.
|
||||
* @memberOf App
|
||||
*/
|
||||
configurePlugin: function (pluginName, config) {
|
||||
builder.pluginsConfiguration[pluginName] = config;
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Set the icons for your mobile app.
|
||||
* @param {Object} icons An Object where the keys are different
|
||||
* devices and screen sizes, and values are image paths
|
||||
* relative to the project root directory.
|
||||
*
|
||||
* Valid key values:
|
||||
* - `iphone`
|
||||
* - `iphone_2x`
|
||||
* - `iphone_3x`
|
||||
* - `ipad`
|
||||
* - `ipad_2x`
|
||||
* - `android_ldpi`
|
||||
* - `android_mdpi`
|
||||
* - `android_hdpi`
|
||||
* - `android_xhdpi`
|
||||
* @memberOf App
|
||||
*/
|
||||
icons: function (icons) {
|
||||
var validDevices =
|
||||
_.keys(iconsIosSizes).concat(_.keys(iconsAndroidSizes));
|
||||
_.each(icons, function (value, key) {
|
||||
if (!_.include(validDevices, key))
|
||||
throw new Error(key + ": unknown key in App.icons configuration.");
|
||||
});
|
||||
_.extend(builder.imagePaths.icon, icons);
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Set the launch screen images for your mobile app.
|
||||
* @param {Object} launchScreens A dictionary where keys are different
|
||||
* devices, screen sizes, and orientations, and the values are image paths
|
||||
* relative to the project root directory.
|
||||
*
|
||||
* For Android, launch screen images should
|
||||
* be special "Nine-patch" image files that specify how they should be
|
||||
* stretched. See the [Android docs](https://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch).
|
||||
*
|
||||
* Valid key values:
|
||||
* - `iphone`
|
||||
* - `iphone_2x`
|
||||
* - `iphone5`
|
||||
* - `iphone6`
|
||||
* - `iphone6p_portrait`
|
||||
* - `iphone6p_landscape`
|
||||
* - `ipad_portrait`
|
||||
* - `ipad_portrait_2x`
|
||||
* - `ipad_landscape`
|
||||
* - `ipad_landscape_2x`
|
||||
* - `android_ldpi_portrait`
|
||||
* - `android_ldpi_landscape`
|
||||
* - `android_mdpi_portrait`
|
||||
* - `android_mdpi_landscape`
|
||||
* - `android_hdpi_portrait`
|
||||
* - `android_hdpi_landscape`
|
||||
* - `android_xhdpi_portrait`
|
||||
* - `android_xhdpi_landscape`
|
||||
*
|
||||
* @memberOf App
|
||||
*/
|
||||
launchScreens: function (launchScreens) {
|
||||
var validDevices =
|
||||
_.keys(launchIosSizes).concat(_.keys(launchAndroidSizes));
|
||||
|
||||
_.each(launchScreens, function (value, key) {
|
||||
if (!_.include(validDevices, key))
|
||||
throw new Error(key + ": unknown key in App.launchScreens configuration.");
|
||||
});
|
||||
_.extend(builder.imagePaths.splash, launchScreens);
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Set a new access rule based on origin domain for your app.
|
||||
* By default your application has a limited list of servers it can contact.
|
||||
* Use this method to extend this list.
|
||||
*
|
||||
* Default access rules:
|
||||
*
|
||||
* - `tel:*`, `geo:*`, `mailto:*`, `sms:*`, `market:*` are allowed and
|
||||
* launch externally (phone app, or an email client on Android)
|
||||
* - `gap:*`, `cdv:*`, `file:` are allowed (protocols required to access
|
||||
* local file-system)
|
||||
* - `http://meteor.local/*` is allowed (a domain Meteor uses to access
|
||||
* app's assets)
|
||||
* - The domain of the server passed to the build process (or local ip
|
||||
* address in the development mode) is used to be able to contact the
|
||||
* Meteor app server.
|
||||
*
|
||||
* Read more about domain patterns in [Cordova
|
||||
* docs](http://cordova.apache.org/docs/en/4.0.0/guide_appdev_whitelist_index.md.html).
|
||||
*
|
||||
* Starting with Meteor 1.0.4 access rule for all domains and protocols
|
||||
* (`<access origin="*"/>`) is no longer set by default due to
|
||||
* [certain kind of possible
|
||||
* attacks](http://cordova.apache.org/announcements/2014/08/04/android-351.html).
|
||||
*
|
||||
* @param {String} domainRule The pattern defining affected domains or URLs.
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} options.launchExternal Set to true if the matching URL
|
||||
* should be handled externally (e.g. phone app or email client on Android).
|
||||
* @memberOf App
|
||||
*/
|
||||
accessRule: function (domainRule, options) {
|
||||
options = options || {};
|
||||
options.launchExternal = !!options.launchExternal;
|
||||
if (options.launchExternal) {
|
||||
builder.accessRules[domainRule] = 'external';
|
||||
} else {
|
||||
builder.accessRules[domainRule] = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
64
tools/cordova/cordova-runner.js
vendored
64
tools/cordova/cordova-runner.js
vendored
@@ -1,64 +0,0 @@
|
||||
import _ from 'underscore';
|
||||
import { Console } from '../console.js';
|
||||
|
||||
// This is a runner, that we pass to Runner (run-all.js)
|
||||
export default class CordovaRunner {
|
||||
constructor(projectContext, cordovaProject, options) {
|
||||
this.projectContext = projectContext;
|
||||
this.cordovaProject = cordovaProject;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
get title() {
|
||||
return `app on ${this.displayName}`;
|
||||
}
|
||||
|
||||
prestart() {
|
||||
// OAuth2 packages don't work so well with any mobile platform except the iOS
|
||||
// simulator. Print a warning and direct users to the wiki page for help. (We
|
||||
// do this now instead of in start() so we don't have to worry about
|
||||
// projectContext being asynchronously reset.)
|
||||
if (!(this.platform === "ios" && this.isDevice) &&
|
||||
this.projectContext.packageMap.getInfo('oauth2')) {
|
||||
Console.warn();
|
||||
Console.labelWarn(
|
||||
"It looks like you are using OAuth2 login in your app. " +
|
||||
"Meteor's OAuth2 implementation does not currently work with " +
|
||||
"mobile apps in local development mode, except in the iOS " +
|
||||
"simulator. You can run the iOS simulator with 'meteor run ios'. " +
|
||||
"For additional workarounds, see " +
|
||||
Console.url(
|
||||
"https://github.com/meteor/meteor/wiki/" +
|
||||
"OAuth-for-mobile-Meteor-clients."));
|
||||
}
|
||||
|
||||
// If we are targeting the remote devices, warn about ports and same network
|
||||
if (this.isDevice) {
|
||||
Console.warn();
|
||||
Console.labelWarn(
|
||||
"You are testing your app on a remote device. " +
|
||||
"For the mobile app to be able to connect to the local server, make " +
|
||||
"sure your device is on the same network, and that the network " +
|
||||
"configuration allows clients to talk to each other " +
|
||||
"(no client isolation).");
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
Console.debug('Running Cordova for target', this.displayName);
|
||||
|
||||
try {
|
||||
Promise.await(this.run(this.options));
|
||||
} catch (err) {
|
||||
Console.error(`${this.displayName}: failed to start the app.`,
|
||||
err.message);
|
||||
}
|
||||
|
||||
try {
|
||||
Promise.await(this.tailLogs(this.options));
|
||||
} catch (err) {
|
||||
Console.error(`${this.displayName}: failed to tail logs.`,
|
||||
err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
tools/cordova/index.js
vendored
Normal file
43
tools/cordova/index.js
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import _ from 'underscore';
|
||||
|
||||
export const CORDOVA_ARCH = "web.cordova";
|
||||
|
||||
export const AVAILABLE_PLATFORMS = ['ios', 'android'];
|
||||
|
||||
const PLATFORM_TO_DISPLAY_NAME_MAP = {
|
||||
'ios': 'iOS',
|
||||
'android': 'Android'
|
||||
};
|
||||
|
||||
export function displayNameForPlatform(platform) {
|
||||
return PLATFORM_TO_DISPLAY_NAME_MAP[platform] || platform;
|
||||
};
|
||||
|
||||
export function filterPlatforms(platforms) {
|
||||
return _.intersection(platforms, AVAILABLE_PLATFORMS);
|
||||
}
|
||||
|
||||
export function splitPluginsAndPackages(packages) {
|
||||
let result = {
|
||||
plugins: [],
|
||||
packages: []
|
||||
};
|
||||
|
||||
for (package of packages) {
|
||||
const [namespace, ...rest] = package.split(':');
|
||||
if (namespace === 'cordova') {
|
||||
const name = rest.join(':');
|
||||
result.plugins.push(name);
|
||||
} else {
|
||||
result.packages.push(package);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the cordovaDependencies of the Cordova arch from a star manifest.
|
||||
export function pluginsFromStarManifest(star) {
|
||||
var cordovaProgram = _.findWhere(star.programs, { arch: CORDOVA_ARCH });
|
||||
return cordovaProgram ? cordovaProgram.cordovaDependencies : {};
|
||||
}
|
||||
86
tools/cordova/ios-runner.js
vendored
86
tools/cordova/ios-runner.js
vendored
@@ -1,86 +0,0 @@
|
||||
import _ from 'underscore';
|
||||
import chalk from 'chalk';
|
||||
import { Console } from '../console.js';
|
||||
import files from '../fs/files.js';
|
||||
import isopackets from '../tool-env/isopackets.js'
|
||||
|
||||
import CordovaRunner from './cordova-runner.js'
|
||||
import { execFileSyncOrThrow, execFileAsyncOrThrow } from './utils.js'
|
||||
|
||||
export default class iOSRunner extends CordovaRunner {
|
||||
constructor(projectContext, cordovaProject, isDevice, options) {
|
||||
super(projectContext, cordovaProject, options);
|
||||
this.isDevice = isDevice;
|
||||
}
|
||||
|
||||
get platform() {
|
||||
return 'ios';
|
||||
}
|
||||
|
||||
get displayName() {
|
||||
return this.isDevice ? 'iOS Device' : 'iOS Simulator';
|
||||
}
|
||||
|
||||
async run(options = {}) {
|
||||
// ios-deploy is super buggy, so we just open xcode and let the user
|
||||
// start the app themselves.
|
||||
if (this.isDevice) {
|
||||
openInXcode(files.pathJoin(this.cordovaProject.projectRoot, 'platforms', 'ios'));
|
||||
} else {
|
||||
const cordovaBinPath = files.convertToOSPath(
|
||||
files.pathJoin(files.getCurrentToolsDir(),
|
||||
'packages/cordova/.npm/package/node_modules/.bin'));
|
||||
return this.cordovaProject.run(this.platform, this.isDevice,
|
||||
_.extend(options, { extraPaths: [cordovaBinPath] }));
|
||||
}
|
||||
}
|
||||
|
||||
async tailLogs(options) {
|
||||
var logFilePath =
|
||||
files.pathJoin(this.cordovaProject.projectRoot, 'platforms', 'ios', 'cordova', 'console.log');
|
||||
Console.debug('Printing logs for ios emulator, tailing file', logFilePath);
|
||||
|
||||
// overwrite the file so we don't have to print the old logs
|
||||
files.writeFile(logFilePath, '');
|
||||
// print the log file
|
||||
execFileAsyncOrThrow('tail', ['-f', logFilePath], {
|
||||
verbose: true,
|
||||
lineMapper: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function openInXcode(projectDir) {
|
||||
// XXX this is buggy if your app directory is under something with a space,
|
||||
// because the this.projectRoot part is not quoted for sh!
|
||||
args = ['-c', 'open ' +
|
||||
'"' + projectDir.replace(/"/g, "\\\"") + '"/*.xcodeproj'];
|
||||
|
||||
try {
|
||||
execFileSyncOrThrow('sh', args);
|
||||
} catch (err) {
|
||||
Console.error();
|
||||
Console.error(chalk.green("Could not open your project in Xcode."));
|
||||
Console.error(chalk.green("Try running again with the --verbose option."));
|
||||
Console.error(
|
||||
chalk.green("Instructions for running your app on an iOS device: ") +
|
||||
Console.url(
|
||||
"https://github.com/meteor/meteor/wiki/" +
|
||||
"How-to-run-your-app-on-an-iOS-device")
|
||||
);
|
||||
Console.error();
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
Console.info();
|
||||
Console.info(
|
||||
chalk.green(
|
||||
"Your project has been opened in Xcode so that you can run your " +
|
||||
"app on an iOS device. For further instructions, visit this " +
|
||||
"wiki page: ") +
|
||||
Console.url(
|
||||
"https://github.com/meteor/meteor/wiki/" +
|
||||
"How-to-run-your-app-on-an-iOS-device"
|
||||
));
|
||||
Console.info();
|
||||
}
|
||||
441
tools/cordova/mobile-control-file.js
vendored
441
tools/cordova/mobile-control-file.js
vendored
@@ -1,441 +0,0 @@
|
||||
import _ from 'underscore';
|
||||
import { Console } from '../console.js';
|
||||
import files from '../fs/files.js';
|
||||
import isopackets from '../tool-env/isopackets.js'
|
||||
|
||||
// Hard-coded constants
|
||||
var iconIosSizes = {
|
||||
'iphone': '60x60',
|
||||
'iphone_2x': '120x120',
|
||||
'iphone_3x': '180x180',
|
||||
'ipad': '76x76',
|
||||
'ipad_2x': '152x152'
|
||||
};
|
||||
|
||||
var iconAndroidSizes = {
|
||||
'android_ldpi': '36x36',
|
||||
'android_mdpi': '42x42',
|
||||
'android_hdpi': '72x72',
|
||||
'android_xhdpi': '96x96'
|
||||
};
|
||||
|
||||
var launchIosSizes = {
|
||||
'iphone': '320x480',
|
||||
'iphone_2x': '640x960',
|
||||
'iphone5': '640x1136',
|
||||
'iphone6': '750x1334',
|
||||
'iphone6p_portrait': '1242x2208',
|
||||
'iphone6p_landscape': '2208x1242',
|
||||
'ipad_portrait': '768x1004',
|
||||
'ipad_portrait_2x': '1536x2008',
|
||||
'ipad_landscape': '1024x748',
|
||||
'ipad_landscape_2x': '2048x1496'
|
||||
};
|
||||
|
||||
var launchAndroidSizes = {
|
||||
'android_ldpi_portrait': '320x426',
|
||||
'android_ldpi_landscape': '426x320',
|
||||
'android_mdpi_portrait': '320x470',
|
||||
'android_mdpi_landscape': '470x320',
|
||||
'android_hdpi_portrait': '480x640',
|
||||
'android_hdpi_landscape': '640x480',
|
||||
'android_xhdpi_portrait': '720x960',
|
||||
'android_xhdpi_landscape': '960x720'
|
||||
};
|
||||
|
||||
// Given the mobile control file converts it to the Phongep/Cordova project's
|
||||
// config.xml file and copies the necessary files (icons and launch screens) to
|
||||
// the correct build location. Replaces all the old resources.
|
||||
export function processMobileControlFile(controlFilePath, projectContext, cordovaProject, serverDomain) {
|
||||
Console.debug('Processing the mobile control file');
|
||||
|
||||
// clean up the previous settings and resources
|
||||
files.rm_recursive(files.pathJoin(cordovaProject.projectRoot, 'resources'));
|
||||
|
||||
var code = '';
|
||||
|
||||
if (files.exists(controlFilePath)) {
|
||||
// read the file if it exists
|
||||
code = files.readFile(controlFilePath, 'utf8');
|
||||
}
|
||||
|
||||
var defaultBuildNumber = (Date.now() % 1000000).toString();
|
||||
var metadata = {
|
||||
id: 'com.id' + projectContext.appIdentifier,
|
||||
version: '0.0.1',
|
||||
buildNumber: defaultBuildNumber,
|
||||
name: cordovaProject.appName,
|
||||
description: 'New Meteor Mobile App',
|
||||
author: 'A Meteor Developer',
|
||||
email: 'n/a',
|
||||
website: 'n/a'
|
||||
};
|
||||
|
||||
// set some defaults different from the Phonegap/Cordova defaults
|
||||
var additionalConfiguration = {
|
||||
'webviewbounce': false,
|
||||
'DisallowOverscroll': true,
|
||||
'deployment-target': '7.0'
|
||||
};
|
||||
|
||||
if (projectContext.packageMap.getInfo('launch-screen')) {
|
||||
additionalConfiguration.AutoHideSplashScreen = false;
|
||||
additionalConfiguration.SplashScreen = 'screen';
|
||||
additionalConfiguration.SplashScreenDelay = 10000;
|
||||
}
|
||||
|
||||
// Defaults are Meteor meatball images located in tools/cordova/assets directory
|
||||
var assetsPath = files.pathJoin(__dirname, 'assets');
|
||||
var iconsPath = files.pathJoin(assetsPath, 'icons');
|
||||
var launchscreensPath = files.pathJoin(assetsPath, 'launchscreens');
|
||||
var imagePaths = {
|
||||
icon: {},
|
||||
splash: {}
|
||||
};
|
||||
|
||||
// Default access rules for plain Meteor-Cordova apps.
|
||||
// Rules can be extended with mobile-config API described below.
|
||||
// The value is `true` if the protocol or domain should be allowed,
|
||||
// 'external' if should handled externally.
|
||||
var accessRules = {
|
||||
// Allow external calls to things like email client or maps app or a
|
||||
// phonebook app.
|
||||
'tel:*': 'external',
|
||||
'geo:*': 'external',
|
||||
'mailto:*': 'external',
|
||||
'sms:*': 'external',
|
||||
'market:*': 'external',
|
||||
|
||||
// phonegap/cordova related protocols
|
||||
// "file:" protocol is used to access first files from disk
|
||||
'file:*': true,
|
||||
'cdv:*': true,
|
||||
'gap:*': true,
|
||||
|
||||
// allow Meteor's local emulated server url - this is the url from which the
|
||||
// application loads its assets
|
||||
'http://meteor.local/*': true
|
||||
};
|
||||
|
||||
// If the remote server domain is known, allow access to it for xhr and DDP
|
||||
// connections.
|
||||
if (serverDomain) {
|
||||
accessRules['*://' + serverDomain + '/*'] = true;
|
||||
// Android talks to localhost over 10.0.2.2. This config file is used for
|
||||
// multiple platforms, so any time that we say the server is on localhost we
|
||||
// should also say it is on 10.0.2.2.
|
||||
if (serverDomain === 'localhost') {
|
||||
accessRules['*://10.0.2.2/*'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
var setIcon = function (size, name) {
|
||||
imagePaths.icon[name] = files.pathJoin(iconsPath, size + '.png');
|
||||
};
|
||||
var setLaunch = function (size, name) {
|
||||
imagePaths.splash[name] = files.pathJoin(launchscreensPath, size + '.png');
|
||||
};
|
||||
|
||||
_.each(iconIosSizes, setIcon);
|
||||
_.each(iconAndroidSizes, setIcon);
|
||||
_.each(launchIosSizes, setLaunch);
|
||||
_.each(launchAndroidSizes, setLaunch);
|
||||
|
||||
/**
|
||||
* @namespace App
|
||||
* @global
|
||||
* @summary The App configuration object in mobile-config.js
|
||||
*/
|
||||
var App = {
|
||||
/**
|
||||
* @summary Set your mobile app's core configuration information.
|
||||
* @param {Object} options
|
||||
* @param {String} [options.id,version,name,description,author,email,website]
|
||||
* Each of the options correspond to a key in the app's core configuration
|
||||
* as described in the [PhoneGap documentation](http://docs.phonegap.com/en/3.5.0/config_ref_index.md.html#The%20config.xml%20File_core_configuration_elements).
|
||||
* @memberOf App
|
||||
*/
|
||||
info: function (options) {
|
||||
// check that every key is meaningful
|
||||
_.each(options, function (value, key) {
|
||||
if (!_.has(metadata, key))
|
||||
throw new Error("Unknown key in App.info configuration: " + key);
|
||||
});
|
||||
|
||||
_.extend(metadata, options);
|
||||
},
|
||||
/**
|
||||
* @summary Add a preference for your build as described in the
|
||||
* [PhoneGap documentation](http://docs.phonegap.com/en/3.5.0/config_ref_index.md.html#The%20config.xml%20File_global_preferences).
|
||||
* @param {String} name A preference name supported by Phonegap's
|
||||
* `config.xml`.
|
||||
* @param {String} value The value for that preference.
|
||||
* @memberOf App
|
||||
*/
|
||||
setPreference: function (key, value) {
|
||||
additionalConfiguration[key] = value;
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Set the build-time configuration for a Phonegap plugin.
|
||||
* @param {String} pluginName The identifier of the plugin you want to
|
||||
* configure.
|
||||
* @param {Object} config A set of key-value pairs which will be passed
|
||||
* at build-time to configure the specified plugin.
|
||||
* @memberOf App
|
||||
*/
|
||||
configurePlugin: function (pluginName, config) {
|
||||
pluginsConfiguration[pluginName] = config;
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Set the icons for your mobile app.
|
||||
* @param {Object} icons An Object where the keys are different
|
||||
* devices and screen sizes, and values are image paths
|
||||
* relative to the project root directory.
|
||||
*
|
||||
* Valid key values:
|
||||
* - `iphone`
|
||||
* - `iphone_2x`
|
||||
* - `iphone_3x`
|
||||
* - `ipad`
|
||||
* - `ipad_2x`
|
||||
* - `android_ldpi`
|
||||
* - `android_mdpi`
|
||||
* - `android_hdpi`
|
||||
* - `android_xhdpi`
|
||||
* @memberOf App
|
||||
*/
|
||||
icons: function (icons) {
|
||||
var validDevices =
|
||||
_.keys(iconIosSizes).concat(_.keys(iconAndroidSizes));
|
||||
_.each(icons, function (value, key) {
|
||||
if (!_.include(validDevices, key))
|
||||
throw new Error(key + ": unknown key in App.icons configuration.");
|
||||
});
|
||||
_.extend(imagePaths.icon, icons);
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Set the launch screen images for your mobile app.
|
||||
* @param {Object} launchScreens A dictionary where keys are different
|
||||
* devices, screen sizes, and orientations, and the values are image paths
|
||||
* relative to the project root directory.
|
||||
*
|
||||
* For Android, launch screen images should
|
||||
* be special "Nine-patch" image files that specify how they should be
|
||||
* stretched. See the [Android docs](https://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch).
|
||||
*
|
||||
* Valid key values:
|
||||
* - `iphone`
|
||||
* - `iphone_2x`
|
||||
* - `iphone5`
|
||||
* - `iphone6`
|
||||
* - `iphone6p_portrait`
|
||||
* - `iphone6p_landscape`
|
||||
* - `ipad_portrait`
|
||||
* - `ipad_portrait_2x`
|
||||
* - `ipad_landscape`
|
||||
* - `ipad_landscape_2x`
|
||||
* - `android_ldpi_portrait`
|
||||
* - `android_ldpi_landscape`
|
||||
* - `android_mdpi_portrait`
|
||||
* - `android_mdpi_landscape`
|
||||
* - `android_hdpi_portrait`
|
||||
* - `android_hdpi_landscape`
|
||||
* - `android_xhdpi_portrait`
|
||||
* - `android_xhdpi_landscape`
|
||||
*
|
||||
* @memberOf App
|
||||
*/
|
||||
launchScreens: function (launchScreens) {
|
||||
var validDevices =
|
||||
_.keys(launchIosSizes).concat(_.keys(launchAndroidSizes));
|
||||
|
||||
_.each(launchScreens, function (value, key) {
|
||||
if (!_.include(validDevices, key))
|
||||
throw new Error(key + ": unknown key in App.launchScreens configuration.");
|
||||
});
|
||||
_.extend(imagePaths.splash, launchScreens);
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Set a new access rule based on origin domain for your app.
|
||||
* By default your application has a limited list of servers it can contact.
|
||||
* Use this method to extend this list.
|
||||
*
|
||||
* Default access rules:
|
||||
*
|
||||
* - `tel:*`, `geo:*`, `mailto:*`, `sms:*`, `market:*` are allowed and
|
||||
* launch externally (phone app, or an email client on Android)
|
||||
* - `gap:*`, `cdv:*`, `file:` are allowed (protocols required to access
|
||||
* local file-system)
|
||||
* - `http://meteor.local/*` is allowed (a domain Meteor uses to access
|
||||
* app's assets)
|
||||
* - The domain of the server passed to the build process (or local ip
|
||||
* address in the development mode) is used to be able to contact the
|
||||
* Meteor app server.
|
||||
*
|
||||
* Read more about domain patterns in [Cordova
|
||||
* docs](http://cordova.apache.org/docs/en/4.0.0/guide_appdev_whitelist_index.md.html).
|
||||
*
|
||||
* Starting with Meteor 1.0.4 access rule for all domains and protocols
|
||||
* (`<access origin="*"/>`) is no longer set by default due to
|
||||
* [certain kind of possible
|
||||
* attacks](http://cordova.apache.org/announcements/2014/08/04/android-351.html).
|
||||
*
|
||||
* @param {String} domainRule The pattern defining affected domains or URLs.
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} options.launchExternal Set to true if the matching URL
|
||||
* should be handled externally (e.g. phone app or email client on Android).
|
||||
* @memberOf App
|
||||
*/
|
||||
accessRule: function (domainRule, options) {
|
||||
options = options || {};
|
||||
options.launchExternal = !!options.launchExternal;
|
||||
if (options.launchExternal) {
|
||||
accessRules[domainRule] = 'external';
|
||||
} else {
|
||||
accessRules[domainRule] = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
Console.debug('Running the mobile control file');
|
||||
files.runJavaScript(code, {
|
||||
filename: 'mobile-config.js',
|
||||
symbols: { App: App }
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error('Error reading mobile-config.js:' + err.stack);
|
||||
}
|
||||
|
||||
const { XmlBuilder } = isopackets.load('cordova-support')['xmlbuilder'];
|
||||
var config = XmlBuilder.create('widget');
|
||||
|
||||
_.each({
|
||||
id: metadata.id,
|
||||
version: metadata.version,
|
||||
'android-versionCode': metadata.buildNumber,
|
||||
'ios-CFBundleVersion': metadata.buildNumber,
|
||||
xmlns: 'http://www.w3.org/ns/widgets',
|
||||
'xmlns:cdv': 'http://cordova.apache.org/ns/1.0'
|
||||
}, function (val, key) {
|
||||
config.att(key, val);
|
||||
});
|
||||
|
||||
// set all the metadata
|
||||
config.ele('name').txt(metadata.name);
|
||||
config.ele('description').txt(metadata.description);
|
||||
config.ele('author', {
|
||||
href: metadata.website,
|
||||
email: metadata.email
|
||||
}).txt(metadata.author);
|
||||
|
||||
// set the additional configuration preferences
|
||||
_.each(additionalConfiguration, function (value, key) {
|
||||
config.ele('preference', {
|
||||
name: key,
|
||||
value: value.toString()
|
||||
});
|
||||
});
|
||||
|
||||
// load from index.html by default
|
||||
config.ele('content', { src: 'index.html' });
|
||||
|
||||
// Copy all the access rules
|
||||
_.each(accessRules, function (rule, pattern) {
|
||||
var opts = { origin: pattern };
|
||||
if (rule === 'external')
|
||||
opts['launch-external'] = true;
|
||||
|
||||
config.ele('access', opts);
|
||||
});
|
||||
|
||||
var iosPlatform = config.ele('platform', { name: 'ios' });
|
||||
var androidPlatform = config.ele('platform', { name: 'android' });
|
||||
|
||||
// Prepare the resources folder
|
||||
var resourcesPath = files.pathJoin(cordovaProject.projectRoot, 'resources');
|
||||
files.rm_recursive(resourcesPath);
|
||||
files.mkdir_p(resourcesPath);
|
||||
|
||||
Console.debug('Copying resources for mobile apps');
|
||||
|
||||
var imageXmlRec = function (name, width, height, src) {
|
||||
var androidMatch = /android_(.?.dpi)_(landscape|portrait)/g.exec(name);
|
||||
var xmlRec = {
|
||||
src: src,
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
|
||||
// XXX special case for Android
|
||||
if (androidMatch)
|
||||
xmlRec.density = androidMatch[2].substr(0, 4) + '-' + androidMatch[1];
|
||||
|
||||
return xmlRec;
|
||||
};
|
||||
var setImages = function (sizes, xmlEle, tag) {
|
||||
_.each(sizes, function (size, name) {
|
||||
var width = size.split('x')[0];
|
||||
var height = size.split('x')[1];
|
||||
|
||||
var suppliedPath = imagePaths[tag][name];
|
||||
if (!suppliedPath)
|
||||
return;
|
||||
|
||||
var suppliedFilename = _.last(suppliedPath.split(files.pathSep));
|
||||
var extension = _.last(suppliedFilename.split('.'));
|
||||
|
||||
// XXX special case for 9-patch png's
|
||||
if (suppliedFilename.match(/\.9\.png$/)) {
|
||||
extension = '9.png';
|
||||
}
|
||||
|
||||
var fileName = name + '.' + tag + '.' + extension;
|
||||
var src = files.pathJoin('resources', fileName);
|
||||
|
||||
// copy the file to the build folder with a standardized name
|
||||
files.copyFile(files.pathResolve(projectContext.projectDir, suppliedPath),
|
||||
files.pathJoin(resourcesPath, fileName));
|
||||
|
||||
// set it to the xml tree
|
||||
xmlEle.ele(tag, imageXmlRec(name, width, height, src));
|
||||
|
||||
// XXX reuse one size for other dimensions
|
||||
var dups = {
|
||||
'60x60': ['29x29', '40x40', '50x50', '57x57', '58x58'],
|
||||
'76x76': ['72x72'],
|
||||
'152x152': ['144x144'],
|
||||
'120x120': ['80x80', '100x100', '114x114'],
|
||||
'768x1004': ['768x1024'],
|
||||
'1536x2008': ['1536x2048'],
|
||||
'1024x748': ['1024x768'],
|
||||
'2048x1496': ['2048x1536']
|
||||
}[size];
|
||||
|
||||
// just use the same image
|
||||
_.each(dups, function (size) {
|
||||
width = size.split('x')[0];
|
||||
height = size.split('x')[1];
|
||||
// XXX this is fine to not supply a name since it is always iOS, but
|
||||
// this is a hack right now.
|
||||
xmlEle.ele(tag, imageXmlRec('n/a', width, height, src));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// add icons and launch screens to config and copy the files on fs
|
||||
setImages(iconIosSizes, iosPlatform, 'icon');
|
||||
setImages(iconAndroidSizes, androidPlatform, 'icon');
|
||||
setImages(launchIosSizes, iosPlatform, 'splash');
|
||||
setImages(launchAndroidSizes, androidPlatform, 'splash');
|
||||
|
||||
var formattedXmlConfig = config.end({ pretty: true });
|
||||
var configPath = files.pathJoin(cordovaProject.projectRoot, 'config.xml');
|
||||
|
||||
Console.debug('Writing new config.xml');
|
||||
files.writeFile(configPath, formattedXmlConfig, 'utf8');
|
||||
};
|
||||
117
tools/cordova/platforms.js
vendored
117
tools/cordova/platforms.js
vendored
@@ -1,117 +0,0 @@
|
||||
import _ from 'underscore';
|
||||
import chalk from 'chalk';
|
||||
import main from '../cli/main.js';
|
||||
import { Console } from '../console.js';
|
||||
import { ProjectContext, PlatformList } from '../project-context.js';
|
||||
import buildmessage from '../buildmessage.js';
|
||||
|
||||
export const AVAILABLE_PLATFORMS = PlatformList.DEFAULT_PLATFORMS.concat(
|
||||
['android', 'ios']);
|
||||
|
||||
const PLATFORM_TO_DISPLAY_NAME_MAP = {
|
||||
'ios': 'iOS',
|
||||
'android': 'Android'
|
||||
};
|
||||
|
||||
export function displayNameForPlatform(platform) {
|
||||
return PLATFORM_TO_DISPLAY_NAME_MAP[platform] || platform;
|
||||
};
|
||||
|
||||
export function platformsForTargets(targets) {
|
||||
targets = _.uniq(targets);
|
||||
|
||||
var platforms = [];
|
||||
// Find the platforms that correspond to the targets
|
||||
// ie. ["ios", "android", "ios-device"] will produce ["ios", "android"]
|
||||
_.each(targets, function (targetName) {
|
||||
var platform = targetName.split('-')[0];
|
||||
if (!_.contains(platforms, platform)) {
|
||||
platforms.push(platform);
|
||||
}
|
||||
});
|
||||
|
||||
return platforms;
|
||||
};
|
||||
|
||||
// Ensures that the Cordova platforms are synchronized with the app-level
|
||||
// platforms.
|
||||
export function ensureCordovaPlatformsAreSynchronized(cordovaProject, platforms) {
|
||||
// Filter out the default platforms, leaving the Cordova platforms
|
||||
platforms = _.difference(platforms, PlatformList.DEFAULT_PLATFORMS);
|
||||
const installedPlatforms = cordovaProject.getInstalledPlatforms();
|
||||
|
||||
for (platform of platforms) {
|
||||
if (_.contains(installedPlatforms, platform)) continue;
|
||||
|
||||
buildmessage.enterJob(`Adding platform: ${platform}`, () => {
|
||||
Promise.await(cordovaProject.addPlatform(platform));
|
||||
});
|
||||
}
|
||||
|
||||
for (platform of installedPlatforms) {
|
||||
if (!_.contains(platforms, platform) &&
|
||||
_.contains(AVAILABLE_PLATFORMS, platform)) {
|
||||
buildmessage.enterJob(`Removing platform: ${platform}`, () => {
|
||||
Promise.await(cordovaProject.removePlatform(platform));
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function checkPlatformRequirements(cordovaProject, platform) {
|
||||
const requirements = Promise.await(cordovaProject.checkRequirements([platform]));
|
||||
let platformRequirements = requirements[platform];
|
||||
|
||||
if (!platformRequirements) {
|
||||
Console.warn("Could not check platform requirements");
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't use ios-deploy, but open Xcode to run on a device instead
|
||||
platformRequirements = _.reject(platformRequirements, requirement => requirement.id === 'ios-deploy');
|
||||
|
||||
const satisifed = _.every(platformRequirements, requirement => requirement.installed);
|
||||
|
||||
if (!satisifed) {
|
||||
Console.info(`Make sure all installation requirements are satisfied
|
||||
before running or building for ${displayNameForPlatform(platform)}:`);
|
||||
for (requirement of platformRequirements) {
|
||||
const name = requirement.name;
|
||||
if (requirement.installed) {
|
||||
Console.success(name);
|
||||
} else {
|
||||
const reason = requirement.metadata && requirement.metadata.reason;
|
||||
if (reason) {
|
||||
Console.failInfo(`${name}: ${reason}`);
|
||||
} else {
|
||||
Console.failInfo(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return satisifed;
|
||||
}
|
||||
|
||||
// Filter out unsupported Cordova platforms, and exit if platform hasn't been
|
||||
// added to the project yet
|
||||
export function checkCordovaPlatforms(projectContext, platforms) {
|
||||
var cordovaPlatformsInProject = projectContext.platformList.getCordovaPlatforms();
|
||||
return _.filter(platforms, function (platform) {
|
||||
var inProject = _.contains(cordovaPlatformsInProject, 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;
|
||||
}
|
||||
|
||||
if (!inProject) {
|
||||
Console.warn("Please add the " + displayNameForPlatform(platform) +
|
||||
" platform to your project first.");
|
||||
Console.info("Run: " + Console.command("meteor add-platform " + platform));
|
||||
throw new main.ExitWithCode(2);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
107
tools/cordova/plugins.js
vendored
107
tools/cordova/plugins.js
vendored
@@ -1,107 +0,0 @@
|
||||
import _ from 'underscore';
|
||||
import { Console } from '../console.js';
|
||||
import buildmessage from '../buildmessage.js';
|
||||
import files from '../fs/files.js';
|
||||
import utils from '../utils/utils.js';
|
||||
|
||||
// packages - list of strings
|
||||
export function filterCordovaPackages(packages) {
|
||||
// We hard-code the 'cordova' namespace
|
||||
var ret = {
|
||||
rest: [],
|
||||
plugins: []
|
||||
};
|
||||
|
||||
_.each(packages, function (p) {
|
||||
var namespace = p.split(':')[0];
|
||||
var name = p.split(':').slice(1).join(':');
|
||||
if (namespace === 'cordova') {
|
||||
ret.plugins.push(name);
|
||||
} else {
|
||||
ret.rest.push(p); // leave it the same
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Ensures that the Cordova plugins are synchronized with the app-level
|
||||
// plugins.
|
||||
export function ensureCordovaPluginsAreSynchronized(cordovaProject, plugins,
|
||||
pluginsConfiguration = {}) {
|
||||
Console.debug('Ensuring that the Cordova plugins are synchronized with the app-level plugins', plugins);
|
||||
|
||||
var installedPlugins = Promise.await(cordovaProject.getInstalledPlugins());
|
||||
|
||||
// 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.
|
||||
var shouldReinstallPlugins = 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 local path.
|
||||
var pluginsFromLocalPath = {};
|
||||
_.each(plugins, function (version, name) {
|
||||
// Check if plugin is installed from local path
|
||||
let pluginFromLocalPath = utils.isUrlWithFileScheme(version);
|
||||
if (pluginFromLocalPath) {
|
||||
pluginsFromLocalPath[name] = version;
|
||||
}
|
||||
|
||||
// XXX there is a hack here that never updates a package if you are
|
||||
// trying to install it from a URL, because we can't determine if
|
||||
// it's the right version or not
|
||||
if (!_.has(installedPlugins, name) ||
|
||||
(installedPlugins[name] !== version && !pluginFromLocalPath)) {
|
||||
// The version of the plugin has changed, or we do not contain a plugin.
|
||||
shouldReinstallPlugins = 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(installedPlugins, function (version, name) {
|
||||
if (!_.has(plugins, name)) {
|
||||
shouldReinstallPlugins = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldReinstallPlugins || !_.isEmpty(pluginsFromLocalPath)) {
|
||||
buildmessage.enterJob({ title: "installing Cordova plugins"}, function () {
|
||||
installedPlugins = Promise.await(cordovaProject.getInstalledPlugins());
|
||||
|
||||
if (shouldReinstallPlugins) {
|
||||
cordovaProject.removePlugins(installedPlugins);
|
||||
} else {
|
||||
cordovaProject.removePlugins(pluginsFromLocalPath);
|
||||
}
|
||||
|
||||
// Now install necessary plugins.
|
||||
var pluginsInstalled, pluginsToInstall;
|
||||
|
||||
if (shouldReinstallPlugins) {
|
||||
pluginsInstalled = 0;
|
||||
pluginsToInstall = plugins;
|
||||
} else {
|
||||
pluginsInstalled = _.size(installedPlugins);
|
||||
pluginsToInstall = pluginsFromLocalPath;
|
||||
}
|
||||
|
||||
var pluginsCount = _.size(plugins);
|
||||
|
||||
buildmessage.reportProgress({ current: 0, end: pluginsCount });
|
||||
_.each(pluginsToInstall, function (version, name) {
|
||||
Promise.await(cordovaProject.addPlugin(name, version, pluginsConfiguration[name]));
|
||||
|
||||
buildmessage.reportProgress({
|
||||
current: ++pluginsInstalled,
|
||||
end: pluginsCount
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
420
tools/cordova/project.js
vendored
420
tools/cordova/project.js
vendored
@@ -1,12 +1,20 @@
|
||||
import _ from 'underscore';
|
||||
import util from 'util';
|
||||
import path from 'path';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import isopackets from '../tool-env/isopackets.js'
|
||||
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 { AVAILABLE_PLATFORMS, displayNameForPlatform } from './index.js';
|
||||
import { CordovaBuilder } from './builder.js';
|
||||
|
||||
function loadDependenciesFromCordovaPackageIfNeeded() {
|
||||
if (typeof Cordova !== 'undefined') return;
|
||||
|
||||
@@ -15,104 +23,214 @@ function loadDependenciesFromCordovaPackageIfNeeded() {
|
||||
|
||||
events.on('results', logIfVerbose);
|
||||
events.on('log', logIfVerbose);
|
||||
events.on('warn', console.warn);
|
||||
events.on('warn', log);
|
||||
events.on('verbose', logIfVerbose);
|
||||
}
|
||||
|
||||
const logIfVerbose = (...args) => {
|
||||
function logIfVerbose(...args) {
|
||||
if (Console.verbose) {
|
||||
console.log(args);
|
||||
log(...args);
|
||||
}
|
||||
};
|
||||
|
||||
// Creates a Cordova project if necessary.
|
||||
export function createCordovaProjectIfNecessary(projectContext) {
|
||||
const cordovaPath = projectContext.getProjectLocalDirectory('cordova-build');
|
||||
const appName = files.pathBasename(projectContext.projectDir);
|
||||
const cordovaProject = new CordovaProject(cordovaPath, appName);
|
||||
function log(...args) {
|
||||
Console.rawInfo(`%% ${util.format.apply(null, args)}\n`);
|
||||
}
|
||||
|
||||
if (!files.exists(cordovaPath)) {
|
||||
Console.debug('Cordova project doesn\'t exist, creating one');
|
||||
files.mkdir_p(files.pathDirname(cordovaPath));
|
||||
Promise.await(cordovaProject.create());
|
||||
}
|
||||
|
||||
return cordovaProject;
|
||||
};
|
||||
|
||||
export default class CordovaProject {
|
||||
constructor(projectRoot, appName) {
|
||||
export class CordovaProject {
|
||||
constructor(projectContext, appName = files.pathBasename(projectContext.projectDir)) {
|
||||
loadDependenciesFromCordovaPackageIfNeeded();
|
||||
|
||||
this.projectRoot = projectRoot;
|
||||
this.projectContext = projectContext;
|
||||
|
||||
this.projectRoot = projectContext.getProjectLocalDirectory('cordova-build');
|
||||
this.appName = appName;
|
||||
|
||||
this.pluginsDir = files.pathJoin(this.projectRoot, 'plugins');
|
||||
this.localPluginsDir = files.pathJoin(this.projectRoot, 'local-plugins');
|
||||
this.tarballPluginsLockPath = files.pathJoin(this.projectRoot, 'cordova-tarball-plugins.json');
|
||||
|
||||
this.createIfNeeded();
|
||||
}
|
||||
|
||||
async create() {
|
||||
// Cordova app identifiers have to look like Java namespaces.
|
||||
// Change weird characters (especially hyphens) into underscores.
|
||||
const appId = 'com.meteor.userapps.' + this.appName.replace(/[^a-zA-Z\d_$.]/g, '_');
|
||||
return await cordova.raw.create(files.convertToOSPath(this.projectRoot), appId, this.appName);
|
||||
// Creating
|
||||
|
||||
createIfNeeded() {
|
||||
buildmessage.assertInCapture();
|
||||
|
||||
if (!files.exists(this.projectRoot)) {
|
||||
buildmessage.enterJob({ title: "creating Cordova project" }, () => {
|
||||
files.mkdir_p(files.pathDirname(this.projectRoot));
|
||||
// Cordova app identifiers have to look like Java namespaces.
|
||||
// Change weird characters (especially hyphens) into underscores.
|
||||
const appId = 'com.meteor.userapps.' + this.appName.replace(/[^a-zA-Z\d_$.]/g, '_');
|
||||
|
||||
// Don't set cwd to project root in runCommands because it doesn't exist yet
|
||||
this.runCommands(async () => {
|
||||
await cordova.raw.create(files.convertToOSPath(this.projectRoot), appId, this.appName);
|
||||
}, this.defaultEnvWithPathsAdded(), null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
chdirToProjectRoot() {
|
||||
process.chdir(files.convertToOSPath(this.projectRoot));
|
||||
// Preparing
|
||||
|
||||
prepare(bundlePath, plugins, options = {}) {
|
||||
assert(bundlePath);
|
||||
assert(plugins);
|
||||
|
||||
Console.debug('Preparing Cordova project');
|
||||
|
||||
const builder = new CordovaBuilder(this, bundlePath, plugins, options);
|
||||
builder.start();
|
||||
}
|
||||
|
||||
get defaultOptions() {
|
||||
return { silent: !Console.verbose, verbose: Console.verbose };
|
||||
// Building
|
||||
|
||||
build(platforms = this.installedPlatforms, options = [], extraPaths) {
|
||||
const env = this.defaultEnvWithPathsAdded(...extraPaths);
|
||||
const commandOptions = _.extend(this.defaultOptions,
|
||||
{ platforms: platforms, options: options });
|
||||
|
||||
Console.debug('Building Cordova project', commandOptions);
|
||||
|
||||
this.runCommands(async () => {
|
||||
await cordova.raw.build(commandOptions);
|
||||
});
|
||||
}
|
||||
|
||||
env(...extraPaths) {
|
||||
let paths = (this.defaultPaths || []);
|
||||
paths.unshift(...extraPaths);
|
||||
const env = files.currentEnvWithPathsAdded(...paths);
|
||||
return env;
|
||||
}
|
||||
// Running
|
||||
|
||||
get defaultPaths() {
|
||||
const nodeBinDir = files.getCurrentNodeBinDir();
|
||||
return [nodeBinDir];
|
||||
async run(platform, isDevice, options = [], extraPaths) {
|
||||
const env = this.defaultEnvWithPathsAdded(...extraPaths);
|
||||
const commandOptions = _.extend(this.defaultOptions,
|
||||
{ platforms: [platform], options: options });
|
||||
|
||||
Console.debug('Running Cordova project', commandOptions);
|
||||
|
||||
|
||||
this.runCommands(async () => {
|
||||
if (isDevice) {
|
||||
await cordova.raw.run(commandOptions);
|
||||
} else {
|
||||
await cordova.raw.emulate(commandOptions);
|
||||
}
|
||||
}, env);
|
||||
}
|
||||
|
||||
// Platforms
|
||||
|
||||
async checkRequirements(platforms = null) {
|
||||
this.chdirToProjectRoot();
|
||||
superspawn.setEnv(this.env());
|
||||
return await cordova.raw.requirements(platforms, this.defaultOptions);
|
||||
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.installedPlatforms;
|
||||
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(
|
||||
async () => {
|
||||
return await cordova.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: ${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(`Make sure all installation requirements are satisfied \
|
||||
before running or building for ${displayNameForPlatform(platform)}:`);
|
||||
for (requirement of requirements) {
|
||||
const name = requirement.name;
|
||||
if (requirement.installed) {
|
||||
Console.success(name);
|
||||
} else {
|
||||
const reason = requirement.metadata && requirement.metadata.reason;
|
||||
if (reason) {
|
||||
Console.failInfo(`${name}: ${reason}`);
|
||||
} else {
|
||||
Console.failInfo(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return satisfied;
|
||||
}
|
||||
|
||||
getInstalledPlatforms() {
|
||||
get installedPlatforms() {
|
||||
return cordova_util.listPlatforms(files.convertToOSPath(this.projectRoot));
|
||||
}
|
||||
|
||||
async addPlatform(platform) {
|
||||
this.chdirToProjectRoot();
|
||||
superspawn.setEnv(this.env());
|
||||
return await cordova.raw.platform('add', platform, this.defaultOptions);
|
||||
updatePlatforms(platforms = this.installedPlatforms) {
|
||||
this.runCommands(async () => {
|
||||
await cordova.raw.platform('update', platforms, this.defaultOptions);
|
||||
});
|
||||
}
|
||||
|
||||
async removePlatform(platform) {
|
||||
this.chdirToProjectRoot();
|
||||
superspawn.setEnv(this.env());
|
||||
return await cordova.raw.platform('rm', platform, this.defaultOptions);
|
||||
addPlatform(platform) {
|
||||
this.runCommands(async () => {
|
||||
await cordova.raw.platform('add', platform, this.defaultOptions);
|
||||
});
|
||||
}
|
||||
|
||||
removePlatform(platform) {
|
||||
this.runCommands(async () => {
|
||||
await cordova.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) {
|
||||
const installedPlatforms = this.installedPlatforms;
|
||||
|
||||
for (platform of platforms) {
|
||||
if (_.contains(installedPlatforms, platform)) continue;
|
||||
|
||||
this.addPlatform(platform);
|
||||
}
|
||||
|
||||
for (platform of installedPlatforms) {
|
||||
if (!_.contains(platforms, platform) &&
|
||||
_.contains(AVAILABLE_PLATFORMS, platform)) {
|
||||
this.removePlatform(platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Plugins
|
||||
|
||||
getInstalledPlugins() {
|
||||
let pluginInfoProvider = new PluginInfoProvider();
|
||||
return _.object(_.map(pluginInfoProvider.getAllWithinSearchPath(files.convertToOSPath(this.pluginsDir)), plugin => {
|
||||
get installedPlugins() {
|
||||
const pluginInfoProvider = new PluginInfoProvider();
|
||||
const plugins = pluginInfoProvider.getAllWithinSearchPath(
|
||||
files.convertToOSPath(this.pluginsDir));
|
||||
return _.object(plugins.map(plugin => {
|
||||
return [ plugin.id, plugin.version ];
|
||||
}));
|
||||
}
|
||||
|
||||
async addPlugin(name, version, config) {
|
||||
addPlugin(name, version, config) {
|
||||
let pluginTarget;
|
||||
if (version && utils.isUrlWithSha(version)) {
|
||||
pluginTarget = files.convertToOSPath(this.fetchCordovaPluginFromShaUrl(version, name));
|
||||
@@ -123,6 +241,8 @@ export default class CordovaProject {
|
||||
pluginTarget = version ? `${name}@${version}` : name;
|
||||
}
|
||||
|
||||
Console.debug('Adding a Cordova plugin', pluginTarget);
|
||||
|
||||
let additionalArgs = [];
|
||||
_.each(config || {}, (value, variable) => {
|
||||
additionalArgs.push('--variable');
|
||||
@@ -130,44 +250,42 @@ export default class CordovaProject {
|
||||
});
|
||||
pluginTarget.concat(additionalArgs)
|
||||
|
||||
this.chdirToProjectRoot();
|
||||
superspawn.setEnv(this.env());
|
||||
return await cordova.raw.plugin('add', pluginTarget, this.defaultOptions);
|
||||
this.runCommands(async () => {
|
||||
await cordova.raw.plugin('add', pluginTarget, this.defaultOptions);
|
||||
});
|
||||
|
||||
if (utils.isUrlWithSha(version)) {
|
||||
Console.debug('Adding plugin to the tarball plugins lock', name);
|
||||
let lock = this.getTarballPluginsLock(this.projectRoot);
|
||||
lock[name] = version;
|
||||
this.writeTarballPluginsLock(this.projectRoot, lock);
|
||||
}
|
||||
}
|
||||
|
||||
async removePlugin(plugin, isFromTarballUrl = false) {
|
||||
verboseLog('Removing a plugin', name);
|
||||
removePlugin(plugin, isFromTarballUrl = false) {
|
||||
Console.debug('Removing a Cordova plugin', plugin);
|
||||
|
||||
this.chdirToProjectRoot();
|
||||
superspawn.setEnv(this.env());
|
||||
await cordova.raw.plugin('rm', plugin, this.defaultOptions);
|
||||
this.runCommands(async () => {
|
||||
await cordova.raw.plugin('rm', plugin, this.defaultOptions);
|
||||
});
|
||||
|
||||
if (isFromTarballUrl) {
|
||||
Console.debug('Removing plugin from the tarball plugins lock', name);
|
||||
Console.debug('Removing plugin from the tarball plugins lock', plugin);
|
||||
// also remove from tarball-url-based plugins lock
|
||||
var lock = getTarballPluginsLock(this.projectRoot);
|
||||
let lock = getTarballPluginsLock(this.projectRoot);
|
||||
delete lock[name];
|
||||
writeTarballPluginsLock(this.projectRoot, lock);
|
||||
}
|
||||
}
|
||||
|
||||
async removePlugins(pluginsToRemove) {
|
||||
Console.debug('Removing plugins', pluginsToRemove);
|
||||
removePlugins(pluginsToRemove) {
|
||||
Console.debug('Removing Cordova plugins', pluginsToRemove);
|
||||
|
||||
// Loop through all of the plugins to remove and remove them one by one until
|
||||
// we have deleted proper amount of plugins. It's necessary to loop because
|
||||
// we might have dependencies between plugins.
|
||||
while (pluginsToRemove.length > 0) {
|
||||
await Promise.all(_.map(pluginsToRemove, (version, name) => {
|
||||
removePlugin(name, utils.isUrlWithSha(version));
|
||||
}));
|
||||
let installedPlugins = await this.installedPlugins();
|
||||
if (_.isEmpty(pluginsToRemove)) return;
|
||||
|
||||
uninstalledPlugins = _.difference(
|
||||
Object.keys(pluginsToRemove), Object.keys(installedPlugins)
|
||||
);
|
||||
plugins = _.omit(pluginsToRemove, uninstalledPlugins);
|
||||
};
|
||||
this.runCommands(async () => {
|
||||
await cordova.raw.plugin('rm', Object.keys(pluginsToRemove), this.defaultOptions);
|
||||
});
|
||||
}
|
||||
|
||||
getTarballPluginsLock() {
|
||||
@@ -205,7 +323,7 @@ export default class CordovaProject {
|
||||
}
|
||||
|
||||
fetchCordovaPluginFromShaUrl(urlWithSha, pluginName) {
|
||||
Console.debug('Fetching a tarball from url:', urlWithSha);
|
||||
Console.debug('Fetching a Cordova plugin tarball from url:', urlWithSha);
|
||||
var pluginPath = files.pathJoin(this.localPluginsDir, pluginName);
|
||||
|
||||
var pluginTarball = buildmessage.enterJob("downloading Cordova plugin", () => {
|
||||
@@ -246,34 +364,140 @@ export default class CordovaProject {
|
||||
getCordovaLocalPluginPath(pluginPath) {
|
||||
pluginPath = pluginPath.substr("file://".length);
|
||||
if (utils.isPathRelative(pluginPath)) {
|
||||
return path.relative(this.projectRoot, path.resolve(projectDir, pluginPath));
|
||||
return path.relative(
|
||||
this.projectRoot,
|
||||
path.resolve(this.projectContext.projectDir, pluginPath));
|
||||
} else {
|
||||
return pluginPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the project
|
||||
async build(options) {
|
||||
this.chdirToProjectRoot();
|
||||
// Ensures that the Cordova plugins are synchronized with the app-level
|
||||
// plugins.
|
||||
ensurePluginsAreSynchronized(plugins, pluginsConfiguration = {}) {
|
||||
buildmessage.assertInCapture();
|
||||
|
||||
superspawn.setEnv(this.env(...options.extraPaths));
|
||||
options = _.extend(this.defaultOptions, options);
|
||||
Console.debug('Ensuring Cordova plugins are synchronized', plugins,
|
||||
pluginsConfiguration);
|
||||
|
||||
return await cordova.raw.build(options);
|
||||
var installedPlugins = this.installedPlugins;
|
||||
|
||||
// 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.
|
||||
var shouldReinstallPlugins = 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 local path.
|
||||
var pluginsFromLocalPath = {};
|
||||
_.each(plugins, (version, name) => {
|
||||
// Check if plugin is installed from local path
|
||||
let pluginFromLocalPath = utils.isUrlWithFileScheme(version);
|
||||
if (pluginFromLocalPath) {
|
||||
pluginsFromLocalPath[name] = version;
|
||||
}
|
||||
|
||||
// XXX there is a hack here that never updates a package if you are
|
||||
// trying to install it from a URL, because we can't determine if
|
||||
// it's the right version or not
|
||||
if (!_.has(installedPlugins, name) ||
|
||||
(installedPlugins[name] !== version && !pluginFromLocalPath)) {
|
||||
// The version of the plugin has changed, or we do not contain a plugin.
|
||||
shouldReinstallPlugins = 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(installedPlugins, (version, name) => {
|
||||
if (!_.has(plugins, name)) {
|
||||
shouldReinstallPlugins = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldReinstallPlugins || !_.isEmpty(pluginsFromLocalPath)) {
|
||||
buildmessage.enterJob({ title: "installing Cordova plugins"}, () => {
|
||||
installedPlugins = this.installedPlugins;
|
||||
|
||||
if (shouldReinstallPlugins) {
|
||||
this.removePlugins(installedPlugins);
|
||||
} else {
|
||||
this.removePlugins(pluginsFromLocalPath);
|
||||
}
|
||||
|
||||
// Now install necessary plugins.
|
||||
var pluginsInstalled, pluginsToInstall;
|
||||
|
||||
if (shouldReinstallPlugins) {
|
||||
pluginsInstalled = 0;
|
||||
pluginsToInstall = plugins;
|
||||
} else {
|
||||
pluginsInstalled = _.size(installedPlugins);
|
||||
pluginsToInstall = pluginsFromLocalPath;
|
||||
}
|
||||
|
||||
var pluginsCount = _.size(plugins);
|
||||
|
||||
buildmessage.reportProgress({ current: 0, end: pluginsCount });
|
||||
_.each(pluginsToInstall, (version, name) => {
|
||||
this.addPlugin(name, version, pluginsConfiguration[name]);
|
||||
|
||||
buildmessage.reportProgress({
|
||||
current: ++pluginsInstalled,
|
||||
end: pluginsCount
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Cordova commands support
|
||||
|
||||
get defaultOptions() {
|
||||
return { silent: !Console.verbose, verbose: Console.verbose };
|
||||
}
|
||||
|
||||
// Run the project
|
||||
async run(platform, isDevice, options) {
|
||||
this.chdirToProjectRoot();
|
||||
defaultEnvWithPathsAdded(...extraPaths) {
|
||||
let paths = (this.defaultPaths || []);
|
||||
paths.unshift(...extraPaths);
|
||||
const env = files.currentEnvWithPathsAdded(...paths);
|
||||
return env;
|
||||
}
|
||||
|
||||
superspawn.setEnv(this.env(...options.extraPaths));
|
||||
options = _.extend(this.defaultOptions, options,
|
||||
{ platforms: [platform] });
|
||||
get defaultPaths() {
|
||||
const nodeBinDir = files.getCurrentNodeBinDir();
|
||||
return [nodeBinDir];
|
||||
}
|
||||
|
||||
if (isDevice) {
|
||||
return await cordova.raw.run(options);
|
||||
} else {
|
||||
return await cordova.raw.emulate(options);
|
||||
runCommands(asyncFunc, env = this.defaultEnvWithPathsAdded(),
|
||||
cwd = this.projectRoot) {
|
||||
const oldCwd = process.cwd();
|
||||
if (cwd) {
|
||||
process.chdir(files.convertToOSPath(cwd));
|
||||
}
|
||||
|
||||
superspawn.setEnv(env);
|
||||
|
||||
try {
|
||||
return Promise.await(asyncFunc());
|
||||
} catch (error) {
|
||||
if (error instanceof CordovaError) {
|
||||
Console.error(`cordova: ${error.message}`);
|
||||
Console.error(chalk.green("Try running again with the --verbose option \
|
||||
to help diagnose the issue."));
|
||||
throw new main.ExitWithCode(1);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
} finally {
|
||||
if (oldCwd) {
|
||||
process.chdir(oldCwd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
92
tools/cordova/run-targets.js
vendored
Normal file
92
tools/cordova/run-targets.js
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
import _ from 'underscore';
|
||||
import chalk from 'chalk';
|
||||
import child_process from 'child_process';
|
||||
|
||||
import runLog from '../runners/run-log.js';
|
||||
import { Console } from '../console/console.js';
|
||||
import files from '../fs/files.js';
|
||||
|
||||
export class CordovaRunTarget {
|
||||
get title() {
|
||||
return `app on ${this.displayName}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class iOSRunTarget extends CordovaRunTarget {
|
||||
constructor(isDevice) {
|
||||
super();
|
||||
this.platform = 'ios';
|
||||
this.isDevice = isDevice;
|
||||
}
|
||||
|
||||
get displayName() {
|
||||
return this.isDevice ? "iOS Device" : "iOS Simulator";
|
||||
}
|
||||
|
||||
async start(cordovaProject) {
|
||||
// ios-deploy is super buggy, so we just open Xcode and let the user
|
||||
// start the app themselves.
|
||||
if (this.isDevice) {
|
||||
openXcodeProject(files.pathJoin(cordovaProject.projectRoot,
|
||||
'platforms', 'ios', `${cordovaProject.appName}.xcodeproj`));
|
||||
} else {
|
||||
// Add the cordova package npm bin path so Cordova can find ios-sim
|
||||
const cordovaBinPath = files.convertToOSPath(
|
||||
files.pathJoin(files.getCurrentToolsDir(),
|
||||
'packages/cordova/.npm/package/node_modules/.bin'));
|
||||
|
||||
await cordovaProject.run(this.platform, this.isDevice, undefined,
|
||||
[cordovaBinPath]);
|
||||
|
||||
// Bring iOS Simulator to front
|
||||
child_process.spawn('osascript', ['-e',
|
||||
'tell application "System Events" \
|
||||
to set frontmost of process "iOS Simulator" to true']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openXcodeProject(projectPath) {
|
||||
child_process.execFile('open', [projectPath], undefined,
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
Console.error();
|
||||
Console.error(chalk.green(`Failed to open your project in Xcode:
|
||||
${error.message}`));
|
||||
Console.error(
|
||||
chalk.green("Instructions for running your app on an iOS device: ") +
|
||||
Console.url("https://github.com/meteor/meteor/wiki/" +
|
||||
"How-to-run-your-app-on-an-iOS-device")
|
||||
);
|
||||
Console.error();
|
||||
} else {
|
||||
Console.info();
|
||||
Console.info(
|
||||
chalk.green(
|
||||
"Your project has been opened in Xcode so that you can run your " +
|
||||
"app on an iOS device. For further instructions, visit this " +
|
||||
"wiki page: ") +
|
||||
Console.url(
|
||||
"https://github.com/meteor/meteor/wiki/" +
|
||||
"How-to-run-your-app-on-an-iOS-device"
|
||||
));
|
||||
Console.info();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export class AndroidRunTarget extends CordovaRunTarget {
|
||||
constructor(isDevice) {
|
||||
super();
|
||||
this.platform = 'android';
|
||||
this.isDevice = isDevice;
|
||||
}
|
||||
|
||||
get displayName() {
|
||||
return this.isDevice ? "Android Device" : "Android Emulator";
|
||||
}
|
||||
|
||||
async start(cordovaProject) {
|
||||
await cordovaProject.run(this.platform, this.isDevice);
|
||||
}
|
||||
}
|
||||
23
tools/cordova/run.js
vendored
23
tools/cordova/run.js
vendored
@@ -1,23 +0,0 @@
|
||||
import _ from 'underscore';
|
||||
import { Console } from '../console.js';
|
||||
import files from '../fs/files.js';
|
||||
import isopackets from '../tool-env/isopackets.js'
|
||||
|
||||
import iOSRunner from './ios-runner.js';
|
||||
import AndroidRunner from './android-runner.js';
|
||||
|
||||
export function buildCordovaRunners(projectContext, cordovaProject, targets, options) {
|
||||
return _.map(targets, (target) => {
|
||||
let targetParts = target.split('-');
|
||||
const platform = targetParts[0];
|
||||
const isDevice = targetParts[1] === 'device';
|
||||
|
||||
if (platform == 'ios') {
|
||||
return new iOSRunner(projectContext, cordovaProject, isDevice, options);
|
||||
} else if (platform == 'android') {
|
||||
return new AndroidRunner(projectContext, cordovaProject, isDevice, options);
|
||||
} else {
|
||||
throw new Error(`Unknown platform: ${platform}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
112
tools/cordova/runner.js
vendored
Normal file
112
tools/cordova/runner.js
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
import _ from 'underscore';
|
||||
import buildmessage from '../utils/buildmessage.js';
|
||||
import runLog from '../runners/run-log.js';
|
||||
import { Console } from '../console/console.js';
|
||||
import main from '../cli/main.js';
|
||||
|
||||
import { displayNameForPlatform, prepareProjectForBuild } from './index.js';
|
||||
|
||||
export class CordovaRunner {
|
||||
constructor(cordovaProject, runTargets) {
|
||||
this.cordovaProject = cordovaProject;
|
||||
this.runTargets = runTargets;
|
||||
}
|
||||
|
||||
get projectContext() {
|
||||
return this.cordovaProject.projectContext;
|
||||
}
|
||||
|
||||
get platformsForRunTargets() {
|
||||
return _.uniq(this.runTargets.map((runTarget) => runTarget.platform));
|
||||
}
|
||||
|
||||
checkPlatformsForRunTargets() {
|
||||
this.cordovaProject.ensurePlatformsAreSynchronized();
|
||||
|
||||
let satisfied = true;
|
||||
const messages = buildmessage.capture(
|
||||
{ title: `checking platform requirements` }, () => {
|
||||
for (platform of this.platformsForRunTargets) {
|
||||
satisfied =
|
||||
this.cordovaProject.checkPlatformRequirements(platform) &&
|
||||
satisfied;
|
||||
}
|
||||
});
|
||||
|
||||
if (messages.hasMessages()) {
|
||||
Console.printMessages(messages);
|
||||
throw new main.ExitWithCode(1);
|
||||
} else if (!satisfied) {
|
||||
throw new main.ExitWithCode(1);
|
||||
};
|
||||
}
|
||||
|
||||
printWarningsIfNeeded() {
|
||||
// OAuth2 packages don't work so well with any mobile platform except the iOS
|
||||
// simulator. Print a warning and direct users to the wiki page for help.
|
||||
if (this.projectContext.packageMap.getInfo('oauth2')) {
|
||||
Console.warn();
|
||||
Console.labelWarn(
|
||||
"It looks like you are using OAuth2 login in your app. " +
|
||||
"Meteor's OAuth2 implementation does not currently work with " +
|
||||
"mobile apps in local development mode, except in the iOS " +
|
||||
"simulator. You can run the iOS simulator with 'meteor run ios'. " +
|
||||
"For additional workarounds, see " +
|
||||
Console.url(
|
||||
"https://github.com/meteor/meteor/wiki/" +
|
||||
"OAuth-for-mobile-Meteor-clients."));
|
||||
}
|
||||
|
||||
// If we are targeting the remote devices, warn about ports and same network
|
||||
if (_.findWhere(this.runTargets, { isDevice: true })) {
|
||||
Console.warn();
|
||||
Console.labelWarn(
|
||||
"You are testing your app on a remote device. " +
|
||||
"For the mobile app to be able to connect to the local server, make " +
|
||||
"sure your device is on the same network, and that the network " +
|
||||
"configuration allows clients to talk to each other " +
|
||||
"(no client isolation).");
|
||||
}
|
||||
}
|
||||
|
||||
prepareProject(bundlePath, plugins, options) {
|
||||
this.cordovaProject.prepare(bundlePath, plugins, options);
|
||||
}
|
||||
|
||||
build() {
|
||||
buildmessage.assertInCapture();
|
||||
|
||||
buildmessage.enterJob(
|
||||
{ title: `building Cordova project for platforms: \
|
||||
${this.platformsForRunTargets}` },
|
||||
() => {
|
||||
this.cordovaProject.build(this.platformsForRunTargets, this.options);
|
||||
});
|
||||
}
|
||||
|
||||
startRunTargets() {
|
||||
buildmessage.assertInCapture();
|
||||
|
||||
for (runTarget of this.runTargets) {
|
||||
buildmessage.enterJob(
|
||||
{ title: `starting ${runTarget.title}` },
|
||||
() => {
|
||||
// Do not await the returned promise
|
||||
runTarget.start(this.cordovaProject);
|
||||
|
||||
if (!buildmessage.jobHasMessages()) {
|
||||
runLog.log(`Started ${runTarget.title}.`, { arrow: true });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
havePlatformsChanged() {
|
||||
return false;
|
||||
}
|
||||
|
||||
havePluginsChanged() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
45
tools/cordova/utils.js
vendored
45
tools/cordova/utils.js
vendored
@@ -1,45 +0,0 @@
|
||||
import _ from 'underscore';
|
||||
import files from '../fs/files.js';
|
||||
import { Console } from '../console.js';
|
||||
import { execFileAsync, execFileSync } from '../utils/utils.js';
|
||||
|
||||
export function execFileAsyncOrThrow(file, args, opts, cb) {
|
||||
Console.debug('Running asynchronously: ', file, args);
|
||||
|
||||
if (_.isFunction(opts)) {
|
||||
cb = opts;
|
||||
opts = undefined;
|
||||
}
|
||||
|
||||
var p = execFileAsync(file, args, opts);
|
||||
p.on('close', function (code) {
|
||||
var err = null;
|
||||
if (code)
|
||||
err = new Error(file + ' ' + args.join(' ') +
|
||||
' exited with non-zero code: ' + code + '. Use -v for' +
|
||||
' more logs.');
|
||||
|
||||
if (cb) cb(err, code);
|
||||
else if (err) throw err;
|
||||
});
|
||||
};
|
||||
|
||||
export function execFileSyncOrThrow(file, args, opts) {
|
||||
Console.debug('Running synchronously: ', file, args);
|
||||
|
||||
var childProcess = execFileSync(file, args, opts);
|
||||
if (!childProcess.success) {
|
||||
// XXX Include args
|
||||
var message = 'Error running ' + file;
|
||||
if (childProcess.stderr) {
|
||||
message = message + "\n" + childProcess.stderr + "\n";
|
||||
}
|
||||
if (childProcess.stdout) {
|
||||
message = message + "\n" + childProcess.stdout + "\n";
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return childProcess;
|
||||
};
|
||||
@@ -21,7 +21,7 @@ class Runner {
|
||||
appPort,
|
||||
banner,
|
||||
disableOplog,
|
||||
extraRunners,
|
||||
cordovaRunner,
|
||||
mongoUrl,
|
||||
onFailure,
|
||||
oplogUrl,
|
||||
@@ -60,8 +60,6 @@ class Runner {
|
||||
self.rootUrl = 'http://localhost:' + listenPort + '/';
|
||||
}
|
||||
|
||||
self.extraRunners = extraRunners ? extraRunners.slice(0) : [];
|
||||
|
||||
self.proxy = new Proxy({
|
||||
listenPort,
|
||||
listenHost: proxyHost,
|
||||
@@ -99,6 +97,7 @@ class Runner {
|
||||
rootUrl: self.rootUrl,
|
||||
proxy: self.proxy,
|
||||
noRestartBanner: self.quiet,
|
||||
cordovaRunner: cordovaRunner
|
||||
});
|
||||
|
||||
self.selenium = null;
|
||||
@@ -114,11 +113,6 @@ class Runner {
|
||||
start() {
|
||||
const self = this;
|
||||
|
||||
// XXX: Include all runners, and merge parallel-launch patch
|
||||
_.each(self.extraRunners, function (runner) {
|
||||
runner && runner.prestart && runner.prestart();
|
||||
});
|
||||
|
||||
self.proxy.start();
|
||||
|
||||
// print the banner only once we've successfully bound the port
|
||||
@@ -133,17 +127,6 @@ class Runner {
|
||||
self.updater.start();
|
||||
}
|
||||
|
||||
_.forEach(self.extraRunners, function (extraRunner) {
|
||||
if (! self.stopped) {
|
||||
const title = extraRunner.title;
|
||||
buildmessage.enterJob({ title: "starting " + title }, function () {
|
||||
extraRunner.start();
|
||||
});
|
||||
if (! self.quiet && ! self.stopped)
|
||||
runLog.log("Started " + title + ".", { arrow: true });
|
||||
}
|
||||
});
|
||||
|
||||
if (! self.stopped) {
|
||||
buildmessage.enterJob({ title: "starting your app" }, function () {
|
||||
self.appRunner.start();
|
||||
@@ -204,9 +187,6 @@ class Runner {
|
||||
self.proxy.stop();
|
||||
self.updater.stop();
|
||||
self.mongoRunner && self.mongoRunner.stop();
|
||||
_.forEach(self.extraRunners, function (extraRunner) {
|
||||
extraRunner.stop();
|
||||
});
|
||||
self.appRunner.stop();
|
||||
self.selenium && self.selenium.stop();
|
||||
// XXX does calling this 'finish' still make sense now that runLog is a
|
||||
|
||||
@@ -8,11 +8,12 @@ var bundler = require('../isobuild/bundler.js');
|
||||
var buildmessage = require('../utils/buildmessage.js');
|
||||
var runLog = require('./run-log.js');
|
||||
var stats = require('../meteor-services/stats.js');
|
||||
import { getCordovaDependenciesFromStar } from '../cordova/build.js';
|
||||
var Console = require('../console/console.js').Console;
|
||||
var catalog = require('../packaging/catalog/catalog.js');
|
||||
var Profile = require('../tool-env/profile.js').Profile;
|
||||
var release = require('../packaging/release.js');
|
||||
import * as cordova from '../cordova';
|
||||
import { CordovaBuilder } from '../cordova/builder.js';
|
||||
|
||||
// Parse out s as if it were a bash command line.
|
||||
var bashParse = function (s) {
|
||||
@@ -351,6 +352,7 @@ var AppRunner = function (options) {
|
||||
self.buildOptions = options.buildOptions;
|
||||
self.rootUrl = options.rootUrl;
|
||||
self.mobileServerUrl = options.mobileServerUrl;
|
||||
self.cordovaRunner = options.cordovaRunner;
|
||||
self.settingsFile = options.settingsFile;
|
||||
self.debugPort = options.debugPort;
|
||||
self.proxy = options.proxy;
|
||||
@@ -363,14 +365,6 @@ var AppRunner = function (options) {
|
||||
self.omitPackageMapDeltaDisplayOnFirstRun =
|
||||
options.omitPackageMapDeltaDisplayOnFirstRun;
|
||||
|
||||
// Keep track of the app's Cordova plugins and platforms. If the set
|
||||
// of plugins or platforms changes from one run to the next, we just
|
||||
// exit, because we don't yet have a way to, for example, get the new
|
||||
// plugins to the mobile clients or stop a running client on a
|
||||
// platform that has been removed.
|
||||
self.cordovaPlugins = null;
|
||||
self.cordovaPlatforms = null;
|
||||
|
||||
self.fiber = null;
|
||||
self.startFuture = null;
|
||||
self.runFuture = null;
|
||||
@@ -449,7 +443,7 @@ _.extend(AppRunner.prototype, {
|
||||
_runOnce: function (options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
var firstRun = options.firstRun;
|
||||
const firstRun = options.firstRun;
|
||||
|
||||
Console.enableProgressDisplay(true);
|
||||
|
||||
@@ -620,32 +614,6 @@ _.extend(AppRunner.prototype, {
|
||||
};
|
||||
}
|
||||
|
||||
firstRun = false;
|
||||
|
||||
var platforms = self.projectContext.platformList.getCordovaPlatforms();
|
||||
platforms.sort();
|
||||
if (self.cordovaPlatforms &&
|
||||
! _.isEqual(self.cordovaPlatforms, platforms)) {
|
||||
return {
|
||||
outcome: 'outdated-cordova-platforms'
|
||||
};
|
||||
}
|
||||
// XXX This is racy --- we should get this from the pre-runner build, not
|
||||
// from the first runner build.
|
||||
self.cordovaPlatforms = platforms;
|
||||
|
||||
var plugins = getCordovaDependenciesFromStar(
|
||||
bundleResult.starManifest);
|
||||
|
||||
if (self.cordovaPlugins && ! _.isEqual(self.cordovaPlugins, plugins)) {
|
||||
return {
|
||||
outcome: 'outdated-cordova-plugins'
|
||||
};
|
||||
}
|
||||
// XXX This is racy --- we should get this from the pre-runner build, not
|
||||
// from the first runner build.
|
||||
self.cordovaPlugins = plugins;
|
||||
|
||||
var serverWatchSet = bundleResult.serverWatchSet;
|
||||
serverWatchSet.merge(settingsWatchSet);
|
||||
|
||||
@@ -659,6 +627,41 @@ _.extend(AppRunner.prototype, {
|
||||
serverWatchSet = combinedWatchSetForBundleResult(bundleResult);
|
||||
}
|
||||
|
||||
const cordovaRunner = self.cordovaRunner;
|
||||
if (cordovaRunner) {
|
||||
if (firstRun) {
|
||||
const plugins = cordova.pluginsFromStarManifest(bundleResult.starManifest);
|
||||
const { settingsFile, mobileServerUrl } = self;
|
||||
const messages = buildmessage.capture(() => {
|
||||
cordovaRunner.prepareProject(bundlePath, plugins,
|
||||
{ settingsFile, mobileServerUrl });
|
||||
cordovaRunner.printWarningsIfNeeded();
|
||||
cordovaRunner.startRunTargets();
|
||||
});
|
||||
|
||||
if (messages.hasMessages()) {
|
||||
return {
|
||||
outcome: 'bundle-fail',
|
||||
errors: messages,
|
||||
watchSet: combinedWatchSetForBundleResult(bundleResult)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// If the set of Cordova of platforms or plugins changes from one run
|
||||
// to the next, we just exit, because we don't yet have a way to,
|
||||
// for example, get the new plugins to the mobile clients or stop a
|
||||
// running client on a platform that has been removed.
|
||||
|
||||
if (cordovaRunner.havePlatformsChanged()) {
|
||||
return { outcome: 'outdated-cordova-platforms' };
|
||||
}
|
||||
|
||||
if (cordovaRunner.havePluginsChanged()) {
|
||||
return { outcome: 'outdated-cordova-plugins' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Atomically (1) see if we've been stop()'d, (2) if not, create a
|
||||
// future that can be used to stop() us once we start running.
|
||||
if (self.exitFuture)
|
||||
@@ -700,7 +703,7 @@ _.extend(AppRunner.prototype, {
|
||||
});
|
||||
|
||||
// Empty self._beforeStartFutures and await its elements.
|
||||
if (options.firstRun && self._beforeStartFuture) {
|
||||
if (firstRun && self._beforeStartFuture) {
|
||||
var stopped = self._beforeStartFuture.wait();
|
||||
if (stopped) {
|
||||
return true;
|
||||
|
||||
6
tools/tests/cordova-platforms.js
vendored
6
tools/tests/cordova-platforms.js
vendored
@@ -13,7 +13,7 @@ selftest.define("add cordova platforms", ["cordova"], function () {
|
||||
run = s.run("run", "android");
|
||||
run.matchErr("Please add the Android platform to your project first");
|
||||
run.match("meteor add-platform android");
|
||||
run.expectExit(2);
|
||||
run.expectExit(1);
|
||||
|
||||
run = s.run("install-sdk", "android");
|
||||
run.waitSecs(90); // Big downloads
|
||||
@@ -26,12 +26,12 @@ selftest.define("add cordova platforms", ["cordova"], function () {
|
||||
|
||||
run = s.run("remove-platform", "foo");
|
||||
run.matchErr("foo: platform is not");
|
||||
run.expectExit(0);
|
||||
run.expectExit(1);
|
||||
|
||||
run = s.run("remove-platform", "android");
|
||||
run.match("removed");
|
||||
run = s.run("run", "android");
|
||||
run.matchErr("Please add the Android platform to your project first");
|
||||
run.match("meteor add-platform android");
|
||||
run.expectExit(2);
|
||||
run.expectExit(1);
|
||||
});
|
||||
|
||||
4
tools/tests/cordova-run.js
vendored
4
tools/tests/cordova-run.js
vendored
@@ -1,4 +1,4 @@
|
||||
import selftest from '../selftest.js';
|
||||
import selftest from '../tool-testing/selftest.js';
|
||||
import utils from '../utils/utils.js';
|
||||
import { parseServerOptionsForRunCommand } from '../cli/commands-cordova.js';
|
||||
|
||||
@@ -34,7 +34,7 @@ selftest.define('get mobile server argument for meteor run', ['cordova'], functi
|
||||
}).mobileServerUrl, { host: utils.ipAddress(), port: "3000", protocol: "http://" });
|
||||
|
||||
// meteor run -p example.com:3000 --mobile-server 4000 => error, mobile
|
||||
// server must specify a hostname
|
||||
// server must include a hostname
|
||||
selftest.expectThrows(() => {
|
||||
parseServerOptionsForRunCommand({
|
||||
port: "example.com:3000",
|
||||
|
||||
@@ -20,7 +20,7 @@ var utils = exports;
|
||||
// undefined} or something like that.
|
||||
//
|
||||
// 'defaults' is an optional object with 'host', 'port', and 'protocol' keys.
|
||||
var parseUrl = function (str, defaults) {
|
||||
exports.parseUrl = function (str, defaults) {
|
||||
// XXX factor this out into a {type: host/port}?
|
||||
|
||||
defaults = defaults || {};
|
||||
@@ -52,7 +52,16 @@ var parseUrl = function (str, defaults) {
|
||||
};
|
||||
};
|
||||
|
||||
var ipAddress = function () {
|
||||
// 'defaults' is an optional object with 'host', 'port', and 'protocol' keys.
|
||||
exports.formatUrl = function (url, defaults) {
|
||||
let string = url.protocol + url.host;
|
||||
if (url.port) {
|
||||
string += `:${url.port}`;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
exports.ipAddress = function () {
|
||||
let defaultRoute;
|
||||
// netroute is not available on Windows
|
||||
if (false) {
|
||||
@@ -100,9 +109,6 @@ exports.hasScheme = function (str) {
|
||||
return !! str.match(/^[A-Za-z][A-Za-z0-9+-\.]*\:\/\//);
|
||||
};
|
||||
|
||||
exports.parseUrl = parseUrl;
|
||||
|
||||
exports.ipAddress = ipAddress;
|
||||
|
||||
exports.hasScheme = function (str) {
|
||||
return !! str.match(/^[A-Za-z][A-Za-z0-9+-\.]*\:\/\//);
|
||||
|
||||
Reference in New Issue
Block a user