From cb16be87d06e9cee34201877ac6fbf5fa4b5cc6c Mon Sep 17 00:00:00 2001 From: Matthew Arbesfeld Date: Wed, 6 Aug 2014 11:08:59 -0700 Subject: [PATCH] Wip --- tools/commands.js | 343 +++++++--------------------------------------- 1 file changed, 46 insertions(+), 297 deletions(-) diff --git a/tools/commands.js b/tools/commands.js index 2d96b2f514..253ba361dc 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -25,13 +25,7 @@ var compiler = require('./compiler.js'); var catalog = require('./catalog.js'); var stats = require('./stats.js'); var unipackage = require('./unipackage.js'); - -var getLoadedPackages = _.once(function () { - var uniload = require('./uniload.js'); - return uniload.load({ - packages: [ 'boilerplate-generator' ] - }); -}); +var cordova = require('./commands-cordova.js'); // The architecture used by Galaxy servers; it's the architecture used // by 'meteor deploy'. @@ -237,6 +231,7 @@ main.registerCommand({ main.registerCommand({ name: 'run', requiresApp: true, + maxArgs: Infinity, options: { port: { type: String, short: "p", default: '3000' }, 'app-port': { type: String }, @@ -272,6 +267,49 @@ main.registerCommand({ var proxyHost = portMatch[1] || null; var proxyPort = parseInt(portMatch[2]); + // If additional args were specified, then also start a mobile build. + if (options.args) { + var supportedPlatforms = ['ios', 'android']; + var requestedPlatforms = []; + + var localDir = path.join(options.appDir, '.meteor', 'local'); + var cordovaPath = path.join(localDir, 'cordova-build'); + var bundleDir = path.join(localDir, 'bundle-tar'); + + // Find the required platforms. ie. "ios android ios-device" will produce + // ["ios", "android"] + _.each(options.args, function (platformName) { + var platform = platformName.split('-')[0]; + if (_.has(supportedPlatforms, platform) && + ! _.has(requestedPlatforms, platform)) { + requestedPlatforms.push(platform); + } + }); + + cordova.ensureCordovaPlatforms(requestedPlatforms, cordovaPath); + + var cordovaSettings = null; + if (options.settings) { + cordovaSettings = + JSON.parse(fs.readFileSync(options.settings, "utf8")).cordova; + } + + var cordovaOptions = { + platforms: requestedPlatforms, + host: proxyHost, + port: proxyPort, + appName: path.basename(options.appDir), + settings: cordovaSettings + }; + + cordova.ensureCordovaProject(cordovaOptions, cordovaPath, bundleDir); + + _.each(options.args, function (platformName) { + // XXX check for repeats + execCordovaOnPlatform(platformName, cordovaPath); + }); + } + var appHost, appPort; if (options['app-port']) { var appPortMatch = options['app-port'].match(/^(?:(.+):)?([0-9]+)?$/); @@ -529,7 +567,7 @@ main.registerCommand({ // XXX output, to stderr, the name of the file written to (for human // comfort, especially since we might change the name) - // XXX name the root directory in the bundle based on the basename + // XXX name the root directory in t he bundle based on the basename // of the file, not a constant 'bundle' (a bit obnoxious for // machines, but worth it for humans) @@ -1533,292 +1571,3 @@ main.registerCommand({ if (options['delete']) process.stdout.write('delete\n'); }); - - -/////////////////////////////////////////////////////////////////////////////// -// cordova -/////////////////////////////////////////////////////////////////////////////// - -var generateCordovaBoilerplate = function (clientDir, options) { - var clientJsonPath = path.join(clientDir, 'program.json'); - var clientJson = JSON.parse(fs.readFileSync(clientJsonPath, 'utf8')); - - var manifest = _.map(clientJson.manifest, function (item) { - if (item.type === 'js') - return _.extend(item, { url: ('/js' + item.url) }); - return item; - }); - - var meteorRelease = project.getMeteorReleaseVersion(); - var Boilerplate = getLoadedPackages()['boilerplate-generator'].Boilerplate; - var boilerplate = new Boilerplate('client.cordova', manifest, { - urlMapper: function (url) { return url ? url.substr(1) : ''; }, - pathMapper: function (p) { return path.join(clientDir, p); }, - baseDataExtension: { - includeCordova: true, - meteorRuntimeConfig: JSON.stringify({ - meteorRelease: meteorRelease, - ROOT_URL: 'http://' + options.host + ':' + options.port + '/', - // XXX propagate it from options? - ROOT_URL_PATH_PREFIX: '', - DDP_DEFAULT_CONNECTION_URL: 'http://' + options.host + ':' + options.port - }) - } - }); - return boilerplate.toHTML(); -}; - - -var fetchCordovaPluginFromShaUrl = - function (urlWithSha, localPluginsDir, pluginName) { - var pluginPath = path.join(localPluginsDir, pluginName); - var pluginTarballPath = pluginPath + '.tgz'; - var curlProcess = - execFileSync('curl', ['-L', urlWithSha, '-o', pluginTarballPath]); - - if (! curlProcess.success) - throw new Error("Failed to fetch the tarball from " + urlWithSha + ": " + - curlProcess.stderr); - - files.mkdir_p(pluginPath); - var tarProcess = execFileSync('tar', - ['xf', pluginTarballPath, '-C', pluginPath, '--strip-components=1']); - if (! tarProcess.success) - throw new Error("Failed to untar the tarball from " + urlWithSha + ": " + - tarProcess.stderr); - files.rm_recursive(pluginTarballPath); - return pluginPath; -}; - -// Creates a Cordova project if necessary and makes sure added Cordova -// platforms and Cordova plugins are up to date with the project's -// definition. -var ensureCordovaProject = function (options, projectPath, bundlePath) { - var bundler = require(path.join(__dirname, 'bundler.js')); - var loader = project.getPackageLoader(); - - var clientArchName = 'client.cordova'; - - var bundleResult = bundler.bundle({ - outputPath: bundlePath, - buildOptions: { - minify: false, // XXX ! options.debug, - arch: archinfo.host(), - clientArchs: [clientArchName] - } - }); - - if (bundleResult.errors) { - throw new Error("Errors prevented bundling:\n" + - bundleResult.errors.formatMessages()); - } - - var programPath = path.join(bundlePath, 'programs'); - var localPluginsPath = path.join(projectPath, 'local-plugins'); - var newSettings = options.cordovaSettings; - - if (! fs.existsSync(projectPath)) { - execFileSync('cordova', ['create', path.basename(projectPath), - 'com.meteor.' + options.appName, - options.appName.replace(/\s/g, '')], - { cwd: path.dirname(projectPath) }); - - // XXX a hack as platforms management is not implemented yet - var platform = options.platform || "firefoxos"; - execFileSync('cordova', ['platforms', 'add', platform], { cwd: projectPath }); - - - // create a folder for storing local plugins - // XXX cache them there - files.mkdir_p(localPluginsPath); - } - - var oldSettings = {}; - try { - fs.readFileSync(path.join(projectPath, 'cordova-settings.json'), "utf8"); - } catch(err) { - if (err.code !== "ENOENT") - throw err; - } - - var wwwPath = path.join(projectPath, "www"); - - var cordovaProgramPath = path.join(programPath, clientArchName); - var cordovaProgramAppPath = path.join(cordovaProgramPath, 'app'); - - // XXX hack, copy files from app folder one level up - if (fs.existsSync(cordovaProgramAppPath)) { - files.cp_r(cordovaProgramAppPath, cordovaProgramPath); - files.rm_recursive(cordovaProgramAppPath); - } - - // rewrite the www folder - files.rm_recursive(wwwPath); - files.cp_r(cordovaProgramPath, wwwPath); - - // clean up the temporary bundle directory - files.rm_recursive(bundlePath); - - // generate index.html - var indexHtml = generateCordovaBoilerplate(wwwPath, options); - fs.writeFileSync(path.join(wwwPath, 'index.html'), indexHtml, 'utf8'); - - var loaderPath = path.join(__dirname, 'client', 'meteor_cordova_loader.js'); - var loaderCode = fs.readFileSync(loaderPath); - fs.writeFileSync(path.join(wwwPath, 'meteor_cordova_loader.js'), loaderCode); - - // XXX get platforms from project file -// _.each(platforms, function (platform) { -// execFileSync('cordova', ['platform', 'add', platform], { cwd: projectPath }); -// }); - - // Compare the state of plugins in the Cordova project and the required by the - // Meteor project. - // XXX compare the latest used sha's with the currently required sha's for - // plugins fetched from a github/tarball url. - var pluginsOutput = execFileSync('cordova', ['plugin', 'list'], - { cwd: projectPath }).stdout; - - var installedPlugins = {}; - // Check if there are any plugins - if (! pluginsOutput.match(/No plugins added/)) { - _.each(pluginsOutput.split('\n'), function (line) { - line = line.trim(); - if (line === '') - return; - var plugin = line.split(' ')[0]; - var version = line.split(' ')[1]; - installedPlugins[plugin] = version; - }); - } - - var requiredPlugins = bundleResult.starManifest.cordovaDependencies; - // XXX the project-level cordova plugins deps override the package-level ones - _.extend(requiredPlugins, project.cordovaPlugins); - - _.each(requiredPlugins, function (version, name) { - // no-op if this plugin is already installed - if (_.has(installedPlugins, name) - && installedPlugins[name] === version - && _.isEqual(oldSettings[name], newSettings[name])) - return; - - if (_.has(installedPlugins, name)) - execFileSync('cordova', ['plugin', 'rm', name], { cwd: projectPath }); - - // XXX do something different for plugins fetched from a url. - var pluginInstallCommand = version ? name + '@' + version : name; - - if (version && utils.isUrlWithSha(version)) { - pluginInstallCommand = - fetchCordovaPluginFromShaUrl(version, localPluginsPath, name); - } - - var additionalArgs = []; - if (newSettings[name]) { - if (! _.isObject(newSettings[name])) - throw new Error("Meteor.settings.cordova." + name + " is expected to be an object"); - _.each(newSettings[name], function (value, variable) { - additionalArgs.push("--variable"); - additionalArgs.push(variable + "=" + JSON.stringify(value)); - }); - } - - var execRes = execFileSync('cordova', - ['plugin', 'add', pluginInstallCommand].concat(additionalArgs), { cwd: projectPath }); - if (! execRes.success) - throw new Error("Failed to install plugin " + name + ": " + execRes.stderr); - }); - - _.each(installedPlugins, function (version, name) { - if (! _.has(requiredPlugins, name)) - execFileSync('cordova', ['plugin', 'rm', name], { cwd: projectPath }); - }); - - execFileSync('cordova', ['build'], { cwd: projectPath }); -}; - -main.registerCommand({ - name: 'cordova', - minArgs: 1, - maxArgs: 10, - requiresApp: true, - options: { - settings: { type: String }, - port: { type: String, short: 'p', default: '3000' }, - host: { type: String, short: 'h', default: 'localhost' }, - platform: { type: String, default: 'firefoxos' }, - verbose: { type: Boolean, short: 'v', default: false } - }, -}, function (options) { - var localDir = path.join(options.appDir, '.meteor', 'local'); - var cordovaPath = path.join(localDir, 'cordova-build'); - var bundleDir = path.join(localDir, 'bundle-tar'); - var appName = path.basename(options.appDir); - - var cordovaCommand = options.args[0]; - var cordovaArgs = options.args.slice(1); - var cordovaSettings = {}; - - if (options.settings) { - cordovaSettings = JSON.parse(fs.readFileSync(options.settings, "utf8")).cordova; - } - - if (cordovaCommand === 'plugin' || cordovaCommand === 'plugins') { - var pluginsCommand = cordovaArgs[0]; - var pluginsArgs = cordovaArgs.slice(1); - var plugins = _.map(pluginsArgs, function (str) { return str.split('@')[0]; }); - - if (pluginsCommand === 'add') { - var pluginsHash = _.object(_.map(pluginsArgs, function (str) { - return str.split('@'); - })); - - // check that every plugin is specifying either an exact constraint or a - // tarball url with sha - utils.ensureOnlyExactVersions(pluginsHash); - - project.addCordovaPlugins(pluginsHash); - console.log("=> Added", plugins.join(' ')); - return 0; - } else if (pluginsCommand === 'remove' || pluginsCommand === 'rm') { - project.removeCordovaPlugins(pluginsArgs); - console.log("=> Removed", plugins.join(' ')); - return 0; - } - - options.verbose = true; - } - - var projectOptions = _.pick(options, 'port', 'host', 'platform'); - projectOptions.appName = appName; - projectOptions.cordovaSettings = cordovaSettings; - - // XXX in Android simulators you can't access localhost and the correct way is - // to use "10.0.2.2" instead. - if (cordovaCommand === 'emulate' && cordovaArgs[0] === 'android' && - options.host === 'localhost') - projectOptions.host = '10.0.2.2'; - - if (_.contains(['emulate', 'build', 'prepare', 'compile', 'serve', 'create'], cordovaCommand)) { - try { - ensureCordovaProject(projectOptions, cordovaPath, bundleDir); - } catch (e) { - process.stderr.write('Errors preventing the Cordova project from build:\n'); - process.stderr.write(e.stack); - return 1; - } - } - - // XXX error if not a Cordova project - var cordovaProcess = execFileSync('cordova', options.args, { cwd: cordovaPath }); - if (cordovaProcess.success) { - if (options.verbose) - console.log(cordovaProcess.stdout); - return 0; - } else { - process.stderr.write(cordovaProcess.stderr); - return 1; - } -}); -