From a3e1ed79ffe155017d745bb368ef319ede99dcf5 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 8 Aug 2014 10:30:22 -0700 Subject: [PATCH 1/4] Print something better than [Object object] If we ever load javascript (uniload load or package.js parse) outside of a buildmessage job and there's a syntax error, FancySyntaxError gets thrown instead of properly processed. Now, we shouldn't do that (we should only load JS inside a buildmessage job!) but our codebase isn't currently up to that standard, so at least ensure that there's some level of useful syntax error (albeit with an ugly internal stack trace attached) in this case. --- tools/buildmessage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/buildmessage.js b/tools/buildmessage.js index 863bdde618..2c5939dc2d 100644 --- a/tools/buildmessage.js +++ b/tools/buildmessage.js @@ -325,8 +325,8 @@ var exception = function (error) { // XXX this may be the wrong place to do this, but it makes syntax errors in // files loaded via unipackage.load have context. if (error instanceof files.FancySyntaxError) { - error.message = "Syntax error: " + error.message + " at " + - error.file + ":" + error.line + ":" + error.column; + error = new Error("Syntax error: " + error.message + " at " + + error.file + ":" + error.line + ":" + error.column); } throw error; } From 1f6f15dc208dcb43a79184e768b71eefa204314e Mon Sep 17 00:00:00 2001 From: Matthew Arbesfeld Date: Sun, 10 Aug 2014 17:27:22 -0700 Subject: [PATCH 2/4] Fix autoupdate in cases where Autoupdate.autoupdateVersion is set. If Autoupdate.autoupdateVersion is set outside of the Autoupdate package, we wouldn't notify the client of the new version. This patch simplifies the updating logic by only having one document per version type. --- packages/autoupdate/autoupdate_client.js | 131 +++++++++++------------ packages/autoupdate/autoupdate_server.js | 39 ++++--- tools/tests/hot-code-push.js | 17 ++- 3 files changed, 96 insertions(+), 91 deletions(-) diff --git a/packages/autoupdate/autoupdate_client.js b/packages/autoupdate/autoupdate_client.js index 79512758b8..3106c0d8a2 100644 --- a/packages/autoupdate/autoupdate_client.js +++ b/packages/autoupdate/autoupdate_client.js @@ -30,19 +30,16 @@ var autoupdateVersionRefreshable = // The collection of acceptable client versions. ClientVersions = new Meteor.Collection("meteor_autoupdate_clientVersions"); -ClientVersionsRefreshable = - new Meteor.Collection("meteor_autoupdate_clientVersions_refreshable"); Autoupdate = {}; Autoupdate.newClientAvailable = function () { - return !! ClientVersions.findOne( - { current: true, - _id: {$ne: autoupdateVersion} - }) || !! ClientVersionsRefreshable.findOne( - { current: true, - _id: {$ne: autoupdateVersionRefreshable} - }); + return !! ClientVersions.findOne({ + refreshable: false, + version: {$ne: autoupdateVersion} }) || + !! ClientVersionsRefreshable.findOne({ + refreshable: true, + version: {$ne: autoupdateVersionRefreshable} }); }; var knownToSupportCssOnLoad = false; @@ -79,73 +76,71 @@ Autoupdate._retrySubscription = function () { }, onReady: function () { if (Package.reload) { - var handle = ClientVersions.find().observeChanges({ - added: function (id, fields) { - var self = this; - if (id !== autoupdateVersion) { - if (handle) { - handle.stop(); - Package.reload.Reload._reload(); + var checkNewVersionDocument = function (id, fields) { + var self = this; + var isRefreshable = id === 'version-refreshable'; + if (isRefreshable && + fields.version !== autoupdateVersionRefreshable) { + autoupdateVersionRefreshable = fields.version; + // Switch out old css links for the new css links. Inspired by: + // https://github.com/guard/guard-livereload/blob/master/js/livereload.js#L710 + var newCss = fields.assets.allCss; + var oldLinks = []; + _.each(document.getElementsByTagName('link'), function (link) { + if (link.className === '__meteor-css__') { + oldLinks.push(link); } - } - } - }); + }); - ClientVersionsRefreshable.find().observeChanges({ - added: function (id, fields) { - if (id !== autoupdateVersionRefreshable) { - autoupdateVersionRefreshable = id; + var waitUntilCssLoads = function (link, callback) { + var executeCallback = _.once(callback); + link.onload = function () { + knownToSupportCssOnLoad = true; + executeCallback(); + }; + if (! knownToSupportCssOnLoad) { + var id = Meteor.setInterval(function () { + if (link.sheet) { + executeCallback(); + Meteor.clearInterval(id); + } + }, 50); + } + }; - // Switch out old css links for the new css links. Inspired by: - // https://github.com/guard/guard-livereload/blob/master/js/livereload.js#L710 - var newCss = fields.assets.allCss; - var oldLinks = []; - _.each(document.getElementsByTagName('link'), function (link) { - if (link.className === '__meteor-css__') { - oldLinks.push(link); - } + var attachStylesheetLink = function (newLink) { + var removeOldLinks = _.after(newCss.length, function () { + _.each(oldLinks, function (oldLink) { + oldLink.parentNode.removeChild(oldLink); + }); }); - var waitUntilCssLoads = function (link, callback) { - var executeCallback = _.once(callback); - link.onload = function () { - knownToSupportCssOnLoad = true; - executeCallback(); - }; - if (! knownToSupportCssOnLoad) { - var id = Meteor.setInterval(function () { - if (link.sheet) { - executeCallback(); - Meteor.clearInterval(id); - } - }, 50); - } - }; + document.getElementsByTagName("head").item(0).appendChild(newLink); - var attachStylesheetLink = function (newLink) { - var removeOldLinks = _.after(newCss.length, function () { - _.each(oldLinks, function (oldLink) { - oldLink.parentNode.removeChild(oldLink); - }); - }); - - document.getElementsByTagName("head").item(0).appendChild(newLink); - - waitUntilCssLoads(newLink, function () { - Meteor.setTimeout(removeOldLinks, 200); - }); - }; - - _.each(newCss, function (css) { - var newLink = document.createElement("link"); - newLink.setAttribute("rel", "stylesheet"); - newLink.setAttribute("type", "text/css"); - newLink.setAttribute("class", "__meteor-css__"); - newLink.setAttribute("href", css.url); - attachStylesheetLink(newLink); + waitUntilCssLoads(newLink, function () { + Meteor.setTimeout(removeOldLinks, 200); }); - } + }; + + _.each(newCss, function (css) { + var newLink = document.createElement("link"); + newLink.setAttribute("rel", "stylesheet"); + newLink.setAttribute("type", "text/css"); + newLink.setAttribute("class", "__meteor-css__"); + newLink.setAttribute("href", css.url); + attachStylesheetLink(newLink); + }); } + else if (! isRefreshable && + fields.version !== autoupdateVersionRefreshable && handle) { + handle.stop(); + Package.reload.Reload._reload(); + } + }; + + var handle = ClientVersions.find().observeChanges({ + added: checkNewVersionDocument, + changed: checkNewVersionDocument }); } } diff --git a/packages/autoupdate/autoupdate_server.js b/packages/autoupdate/autoupdate_server.js index a04f154325..56de4ea266 100644 --- a/packages/autoupdate/autoupdate_server.js +++ b/packages/autoupdate/autoupdate_server.js @@ -43,9 +43,6 @@ Autoupdate = {}; // The collection of acceptable client versions. ClientVersions = new Meteor.Collection("meteor_autoupdate_clientVersions", { connection: null }); -ClientVersionsRefreshable = - new Meteor.Collection("meteor_autoupdate_clientVersions_refreshable", - { connection: null }); // The client hash includes __meteor_runtime_config__, so wait until // all packages have loaded and have had a chance to populate the @@ -61,9 +58,6 @@ var startupVersion = null; // updateVersions can only be called after the server has fully loaded. var updateVersions = function (shouldReloadClientProgram) { syncQueue.runTask(function () { - var oldVersion = Autoupdate.autoupdateVersion; - var oldVersionRefreshable = Autoupdate.autoupdateVersionRefreshable; - // Step 1: load the current client program on the server and update the // hash values in __meteor_runtime_config__. if (shouldReloadClientProgram) { @@ -90,25 +84,30 @@ var updateVersions = function (shouldReloadClientProgram) { WebAppInternals.generateBoilerplate(); } - if (Autoupdate.autoupdateVersion !== oldVersion) { - if (oldVersion) { - ClientVersions.remove(oldVersion); - } - + if (! ClientVersions.findOne({_id: "version"})) { ClientVersions.insert({ - _id: Autoupdate.autoupdateVersion, - current: true + _id: "version", + refreshable: false, + version: Autoupdate.autoupdateVersion, }); + } else { + ClientVersions.update("version", { $set: { + version: Autoupdate.autoupdateVersion, + }}); } - if (Autoupdate.autoupdateVersionRefreshable !== oldVersionRefreshable) { - if (oldVersionRefreshable) { - ClientVersionsRefreshable.remove(oldVersionRefreshable); - } - ClientVersionsRefreshable.insert({ - _id: Autoupdate.autoupdateVersionRefreshable, + if (! ClientVersions.findOne({_id: "version-refreshable"})) { + ClientVersions.insert({ + _id: "version-refreshable", + version: Autoupdate.autoupdateVersionRefreshable, + refreshable: true, assets: WebAppInternals.refreshableAssets }); + } else { + ClientVersions.update("version-refreshable", { $set: { + version: Autoupdate.autoupdateVersionRefreshable, + assets: WebAppInternals.refreshableAssets + }}); } }); }; @@ -125,7 +124,7 @@ Meteor.startup(function () { Meteor.publish( "meteor_autoupdate_clientVersions", function () { - return [ ClientVersions.find(), ClientVersionsRefreshable.find() ]; + return ClientVersions.find(); }, {is_auto: true} ); diff --git a/tools/tests/hot-code-push.js b/tools/tests/hot-code-push.js index 489295a089..b2e17def48 100644 --- a/tools/tests/hot-code-push.js +++ b/tools/tests/hot-code-push.js @@ -133,15 +133,25 @@ selftest.define("javascript hot code push", function (options) { s.mkdir("server"); s.write("server/test.js", "jsVar = 'bar'"); run.match("server restarted"); + + // Setting the autoupdateVersion to a different string should also + // force the client to restart. + s.write("server/test.js", + "Package.autoupdate.Autoupdate.autoupdateVersion = 'random'"); + run.match("server restarted"); + run.match("client connected: 0"); + run.match("jsVar: undefined"); + + s.unlink("server/test.js"); + run.match("server restarted"); + s.write("client/empty.js", ""); run.match("client connected: 0"); // We should not be able to access a server variable from the client. run.match("jsVar: undefined"); - s.unlink("server/test.js"); - run.match("server restarted"); s.unlink("client/empty.js"); - run.match("client connected: 0"); + run.match("client connected: 1"); run.match("jsVar: undefined"); // Break the HTML file. This should kill the server, and print errors. @@ -169,6 +179,7 @@ selftest.define("javascript hot code push", function (options) { s.write("client/test.js", "jsVar = 'baz'"); run.match("client connected: 3"); run.match("jsVar: baz"); + s.unlink("client/test.js"); run.stop(); From c6aa178f22a294b6fef26cb578078c4270ec2d4f Mon Sep 17 00:00:00 2001 From: Matthew Arbesfeld Date: Sun, 10 Aug 2014 17:35:56 -0700 Subject: [PATCH 3/4] Fix typo in Autoupdate.newClientAvailable --- packages/autoupdate/autoupdate_client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/autoupdate/autoupdate_client.js b/packages/autoupdate/autoupdate_client.js index 3106c0d8a2..000f5a2da6 100644 --- a/packages/autoupdate/autoupdate_client.js +++ b/packages/autoupdate/autoupdate_client.js @@ -37,7 +37,7 @@ Autoupdate.newClientAvailable = function () { return !! ClientVersions.findOne({ refreshable: false, version: {$ne: autoupdateVersion} }) || - !! ClientVersionsRefreshable.findOne({ + !! ClientVersions.findOne({ refreshable: true, version: {$ne: autoupdateVersionRefreshable} }); }; From 64d939acb2566b94b55bc0c1f82b4fc6c6dc0db0 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 8 Aug 2014 11:10:37 -0700 Subject: [PATCH 4/4] Add a lot more buildmessage captures Many of these (mostly in top level commands in commands-packages.js) are not super well thought out: they use a new "doOrDie" helper to run some function in a capture and exit if there are any messages. We really need to get a little more thoughtful about the big picture of error handling (combining "build" errors, network errors, catalog errors, etc). But this at least allows the addition of more buildmessage assertions. At the very least, this ensures that if you edit a package.js in a local package while "meteor run" is running, that instead of crashing the tool it properly shows the buildmessage and lets you fix the issue. --- tools/bundler.js | 5 +- tools/catalog-base.js | 10 + tools/catalog.js | 43 +- tools/commands-packages.js | 535 +++++++++++++++--- tools/commands.js | 319 +---------- tools/compiler.js | 5 +- tools/main.js | 70 ++- tools/package-loader.js | 22 - tools/package-source.js | 1 + tools/project.js | 2 + tools/release.js | 6 + tools/run-app.js | 45 +- tools/selftest.js | 31 +- .../old/app-with-private/.meteor/versions | 8 +- .../packages/test-package/versions.json | 4 +- .../old/app-with-public/.meteor/versions | 8 +- tools/tests/old/empty-app/.meteor/versions | 8 +- tools/tests/old/test-bundler-assets.js | 22 +- tools/tests/old/test-bundler-npm.js | 26 +- tools/tests/old/test-bundler-options.js | 21 +- tools/tests/package-tests.js | 2 +- tools/tests/releases.js | 16 +- tools/tests/report-stats.js | 2 +- tools/tests/selftest-test.js | 2 +- tools/updater.js | 27 +- 25 files changed, 748 insertions(+), 492 deletions(-) diff --git a/tools/bundler.js b/tools/bundler.js index d78b940d8b..69bcd8b282 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -1648,8 +1648,6 @@ exports.bundle = function (options) { var buildOptions = options.buildOptions || {}; var appDir = project.project.rootDir; - if (! release.usingRightReleaseForApp(appDir)) - throw new Error("running wrong release for app?"); var serverArch = buildOptions.serverArch || archinfo.host(); var webArchs = buildOptions.webArchs || [ "web.browser" ]; @@ -1668,6 +1666,9 @@ exports.bundle = function (options) { var messages = buildmessage.capture({ title: "building the application" }, function () { + if (! release.usingRightReleaseForApp(appDir)) + throw new Error("running wrong release for app?"); + var packageLoader = project.project.getPackageLoader(); var downloaded = tropohouse.default.downloadMissingPackages( project.project.dependencies, { serverArch: serverArch }); diff --git a/tools/catalog-base.js b/tools/catalog-base.js index 3e787c08a4..6be67c20c9 100644 --- a/tools/catalog-base.js +++ b/tools/catalog-base.js @@ -105,6 +105,7 @@ _.extend(baseCatalog.BaseCatalog.prototype, { // the inevitable rather than slowing down normal operations) _recordOrRefresh: function (recordFinder) { var self = this; + buildmessage.assertInCapture(); var record = recordFinder(); // If we cannot find it maybe refresh. if (!record) { @@ -124,6 +125,7 @@ _.extend(baseCatalog.BaseCatalog.prototype, { // release track, or null if there is no such release track. getReleaseTrack: function (name) { var self = this; + buildmessage.assertInCapture(); self._requireInitialized(); return self._recordOrRefresh(function () { return _.findWhere(self.releaseTracks, { name: name }); @@ -138,6 +140,7 @@ _.extend(baseCatalog.BaseCatalog.prototype, { // serious refactoring. getReleaseVersion: function (track, version, notInitialized) { var self = this; + buildmessage.assertInCapture(); if (!notInitialized) self._requireInitialized(); return self._recordOrRefresh(function () { return _.findWhere(self.releaseVersions, @@ -186,6 +189,7 @@ _.extend(baseCatalog.BaseCatalog.prototype, { // package, or null if there is no such package. getPackage: function (name) { var self = this; + buildmessage.assertInCapture(); self._requireInitialized(); return self._recordOrRefresh(function () { return _.findWhere(self.packages, { name: name }); @@ -213,6 +217,7 @@ _.extend(baseCatalog.BaseCatalog.prototype, { getVersion: function (name, version) { var self = this; self._requireInitialized(); + buildmessage.assertInCapture(); var lookupVersion = function () { return _.has(self.versions, name) && @@ -250,6 +255,7 @@ _.extend(baseCatalog.BaseCatalog.prototype, { getLatestVersion: function (name) { var self = this; self._requireInitialized(); + buildmessage.assertInCapture(); var versions = self.getSortedVersions(name); if (versions.length === 0) @@ -263,6 +269,7 @@ _.extend(baseCatalog.BaseCatalog.prototype, { getBuildsForArches: function (name, version, arches) { var self = this; self._requireInitialized(); + buildmessage.assertInCapture(); var versionInfo = self.getVersion(name, version); if (! versionInfo) @@ -298,6 +305,7 @@ _.extend(baseCatalog.BaseCatalog.prototype, { getBuildWithPreciseBuildArchitectures: function (versionRecord, buildArchitectures) { var self = this; + buildmessage.assertInCapture(); self._requireInitialized(); return self._recordOrRefresh(function () { @@ -310,6 +318,7 @@ _.extend(baseCatalog.BaseCatalog.prototype, { getAllBuilds: function (name, version) { var self = this; self._requireInitialized(); + buildmessage.assertInCapture(); var versionRecord = self.getVersion(name, version); if (!versionRecord) @@ -324,6 +333,7 @@ _.extend(baseCatalog.BaseCatalog.prototype, { // version). getDefaultReleaseVersion: function (track) { var self = this; + buildmessage.assertInCapture(); self._requireInitialized(); if (!track) diff --git a/tools/catalog.js b/tools/catalog.js index 1d66a399e1..c44c1275ac 100644 --- a/tools/catalog.js +++ b/tools/catalog.js @@ -77,6 +77,7 @@ _.extend(OfficialCatalog.prototype, { // the in-progress refresh to finish. refresh: function () { var self = this; + buildmessage.assertInCapture(); self._requireInitialized(); if (self._refreshFutures) { @@ -212,6 +213,7 @@ _.extend(CompleteCatalog.prototype, { // silently ignored. initialize: function (options) { var self = this; + buildmessage.assertInCapture(); options = options || {}; @@ -259,9 +261,7 @@ _.extend(CompleteCatalog.prototype, { var self = this; opts = opts || {}; self._requireInitialized(); - // XXX for now, just doing the assertion if we have to call project - // stuff. but oh, this will be improved. - opts.ignoreProjectDeps || buildmessage.assertInCapture(); + buildmessage.assertInCapture(); // Kind of a hack, as per specification. We don't have a constraint solver // initialized yet. We are probably trying to build the constraint solver @@ -335,9 +335,12 @@ _.extend(CompleteCatalog.prototype, { // anyway. When we are using hot code push, we may be restarting the app // because of a local package change that impacts that catalog. Don't wait // on the official catalog to refresh data.json, in this case. + // - watchSet: if provided, any files read in reloading packages will be added + // to this set. refresh: function (options) { var self = this; options = options || {}; + buildmessage.assertInCapture(); // We need to limit the rate of refresh, or, at least, prevent any sort of // loops. ForceRefresh will override either one. @@ -355,7 +358,7 @@ _.extend(CompleteCatalog.prototype, { self._insertServerPackages(localData); self._recomputeEffectiveLocalPackages(); - self._addLocalPackageOverrides(); + self._addLocalPackageOverrides({watchSet: options.watchSet}); self.initialized = true; // Rebuild the resolver, since packages may have changed. self._initializeResolver(); @@ -416,8 +419,10 @@ _.extend(CompleteCatalog.prototype, { // first removing any existing packages that have the same name. // // XXX emits buildmessages. are callers expecting that? - _addLocalPackageOverrides: function () { + _addLocalPackageOverrides: function (options) { var self = this; + options = options || {}; + buildmessage.assertInCapture(); // Remove all packages from the catalog that have the same name as // a local package, along with all of their versions and builds. @@ -462,8 +467,19 @@ _.extend(CompleteCatalog.prototype, { if (buildmessage.jobHasMessages()) broken = true; }); + + if (options.watchSet) { + options.watchSet.merge(packageSource.pluginWatchSet); + _.each(packageSource.architectures, function (sourceArch) { + options.watchSet.merge(sourceArch.watchSet); + }); + } + + // Recover by ignoring, but not until after we've augmented the watchSet + // (since we want the watchSet to include files with problems that the + // user may fix!) if (broken) - return; // recover by ignoring + return; packageSources[name] = packageSource; @@ -724,6 +740,7 @@ _.extend(CompleteCatalog.prototype, { // function twice with the same `name`. addLocalPackage: function (name, directory) { var self = this; + buildmessage.assertInCapture(); self._requireInitialized(); var resolvedPath = path.resolve(directory); @@ -741,20 +758,6 @@ _.extend(CompleteCatalog.prototype, { self.refresh(); }, - // Reverse the effect of addLocalPackage. - removeLocalPackage: function (name) { - var self = this; - self._requireInitialized(); - - if (! _.has(self.localPackages, name)) - throw new Error("no such local package?"); - delete self.localPackages[name]; - - // see #CallingRefreshEveryTimeLocalPackagesChange - self._recomputeEffectiveLocalPackages(); - self.refresh(); - }, - // True if `name` is a local package (is to be loaded via // localPackageDirs or addLocalPackage rather than from the package // server) diff --git a/tools/commands-packages.js b/tools/commands-packages.js index 0927f30da9..9661984b2d 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -24,11 +24,13 @@ var compiler = require('./compiler.js'); var catalog = require('./catalog.js'); var stats = require('./stats.js'); var unipackage = require('./unipackage.js'); +var packageLoader = require('./package-loader.js'); // Returns an object with keys: // record : (a package or version record) // release : true if it is a release instead of a package. var getReleaseOrPackageRecord = function(name) { + buildmessage.assertInCapture(); // Too lazy to do string parsing. var rec = catalog.official.getPackage(name); var rel = false; @@ -68,6 +70,96 @@ var formatList = function (items) { return out; }; +// Seriously, this dies if it can't refresh. Only call it if you're sure you're +// OK that the command doesn't work while offline. +var doOrDie = exports.doOrDie = function (f) { + var ret; + var messages = buildmessage.capture(function () { + ret = f(); + }); + if (messages.hasMessages()) { + process.stderr.write(messages.formatMessages()); + throw main.ExitWithCode(1); + } + return ret; +}; + +var refreshOfficialCatalogOrDie = function () { + doOrDie(function () { + catalog.official.refresh(); + }); +}; + + +// Internal use only. Makes sure that your Meteor install is totally good to go +// (is "airplane safe"). Specifically, it: +// - Builds all local packages (including their npm dependencies) +// - Ensures that all packages in your current release are downloaded +// - Ensures that all packages used by your app (if any) are downloaded +// (It also ensures you have the dev bundle downloaded, just like every command +// in a checkout.) +// +// The use case is, for example, cloning an app from github, running this +// command, then getting on an airplane. +// +// This does NOT guarantee a *re*build of all local packages (though it will +// download any new dependencies). If you want to rebuild all local packages, +// call meteor rebuild. That said, rebuild should only be necessary if there's a +// bug in the build tool... otherwise, packages should be rebuilt whenever +// necessary! +main.registerCommand({ + name: '--get-ready' +}, function (options) { + // It is not strictly needed, but it is thematically a good idea to refresh + // the official catalog when we call get-ready, since it is an + // internet-requiring action. + refreshOfficialCatalogOrDie(); + + var loadPackages = function (packagesToLoad, loader) { + buildmessage.assertInCapture(); + loader.downloadMissingPackages(); + _.each(packagesToLoad, function (name) { + // Calling getPackage on the loader will return a unipackage object, which + // means that the package will be compiled/downloaded. That we throw the + // package variable away afterwards is immaterial. + loader.getPackage(name); + }); + }; + + var messages = buildmessage.capture({ + title: 'getting packages ready' + }, function () { + // First, build all accessible *local* packages, whether or not this app + // uses them. Use the "all packages are local" loader. + loadPackages(catalog.complete.getLocalPackageNames(), + new packageLoader.PackageLoader({versions: null})); + + // In an app? Get the list of packages used by this app. Calling getVersions + // on the project will ensureDepsUpToDate which will ensure that all builds + // of everything we need from versions have been downloaded. (Calling + // buildPackages may be redundant, but can't hurt.) + if (options.appDir) { + loadPackages(_.keys(project.getVersions()), project.getPackageLoader()); + } + + // Using a release? Get all the packages in the release. + if (release.current.isProperRelease()) { + var releasePackages = release.current.getPackages(); + loadPackages( + _.keys(releasePackages), + new packageLoader.PackageLoader({versions: releasePackages})); + } + }); + + if (messages.hasMessages()) { + process.stderr.write("\n" + messages.formatMessages()); + return 1; + }; + + console.log("You are ready!"); + return 0; +}); + /////////////////////////////////////////////////////////////////////////////// // publish a package @@ -97,12 +189,15 @@ main.registerCommand({ // Refresh the catalog, caching the remote package data on the server. We can // optimize the workflow by using this data to weed out obviously incorrect // submissions before they ever hit the wire. - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); var packageName = path.basename(options.packageDir); // Fail early if the package already exists. if (options.create) { - if (catalog.official.getPackage(packageName)) { + var packageInfo = doOrDie(function () { + return catalog.official.getPackage(packageName); + }); + if (packageInfo) { process.stderr.write("Package already exists. To create a new version of an existing "+ "package, do not use the --create flag! \n"); return 2; @@ -184,7 +279,7 @@ main.registerCommand({ // then exit with the previous error code. conn.close(); - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); return ec; }); @@ -207,9 +302,12 @@ main.registerCommand({ var versionString = all[1]; // Refresh the catalog, cacheing the remote package data on the server. - catalog.official.refresh(true); + refreshOfficialCatalogOrDie(); - if (! catalog.complete.getPackage(name)) { + var packageInfo = doOrDie(function () { + return catalog.complete.getPackage(name); + }); + if (! packageInfo) { process.stderr.write( "You can't call `meteor publish-for-arch` on package '" + name + "' without\n" + "publishing it first.\n\n" + @@ -217,7 +315,9 @@ main.registerCommand({ return 1; } - var pkgVersion = catalog.official.getVersion(name, versionString); + var pkgVersion = doOrDie(function () { + return catalog.official.getVersion(name, versionString); + }); if (! pkgVersion) { process.stderr.write( "You can't call `meteor publish-for-arch` on version " + versionString + " of\n" + @@ -308,7 +408,7 @@ main.registerCommand({ return 1; } - catalog.official.refresh(); // XXX buildmessage.capture? + refreshOfficialCatalogOrDie(); return 0; }); @@ -323,7 +423,7 @@ main.registerCommand({ }, function (options) { // Refresh the catalog, cacheing the remote package data on the server. process.stdout.write("Resyncing with package server...\n"); - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); try { var conn = packageClient.loggedInPackagesConnection(); @@ -416,7 +516,10 @@ main.registerCommand({ // authorized to publish before we do any complicated/long operations, and // before we publish its packages. if (!options['create-track']) { - var trackRecord = catalog.official.getReleaseTrack(relConf.track); + var trackRecord; + doOrDie(function () { + trackRecord = catalog.official.getReleaseTrack(relConf.track); + }); if (!trackRecord) { process.stderr.write('\n There is no release track named ' + relConf.track + '. If you are creating a new track, use the --create-track flag.\n'); @@ -650,15 +753,16 @@ main.registerCommand({ continue; var prebuilt = toPublish[name]; - var opts = { - new: !catalog.official.getPackage(name) - }; process.stdout.write("Publishing package: " + name + "\n"); var pubEC; // XXX merge with messages? messages = buildmessage.capture({ title: "publishing package " + name }, function () { + var opts = { + new: !catalog.official.getPackage(name) + }; + // If we are creating a new package, dsPS will document this for us, so // we don't need to do this here. Though, in the future, once we are // done bootstrapping package servers, we should consider having some @@ -720,7 +824,7 @@ main.registerCommand({ } // Get it back. - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); process.stdout.write("Done creating " + relConf.track + "@" + relConf.version + "!\n"); @@ -763,12 +867,18 @@ main.registerCommand({ return 1; } - catalog.official.refresh(); + // XXX this is dumb, we should be able to search even if we can't + // refresh. let's make sure to differentiate "horrible parse error while + // refreshing" from "can't connect to catalog" + refreshOfficialCatalogOrDie(); if (options.details) { var full = options.args[0].split('@'); var name = full[0]; - var allRecord = getReleaseOrPackageRecord(name); + var allRecord; + doOrDie(function () { + allRecord = getReleaseOrPackageRecord(name); + }); var record = allRecord.record; if (!record) { process.stderr.write("Unknown package or release: " + name + "\n"); @@ -779,11 +889,12 @@ main.registerCommand({ if (!allRecord.isRelease) { label = "package"; var getRelevantRecord = function (version) { - var versionRecord = - catalog.official.getVersion(name, version); - var myBuilds = _.pluck( - catalog.official.getAllBuilds(name, version), - 'buildArchitectures'); + var versionRecord = doOrDie(function () { + return catalog.official.getVersion(name, version); + }); + var myBuilds = _.pluck(doOrDie(function () { + return catalog.official.getAllBuilds(name, version); + }), 'buildArchitectures'); // Does this package only have a cross-platform build? if (myBuilds.length === 1) { var allArches = myBuilds[0].split('+'); @@ -807,12 +918,16 @@ main.registerCommand({ } else { label = "release"; if (full.length > 1) { - versionRecords = [catalog.official.getReleaseVersion(name, full[1])]; + doOrDie(function () { + versionRecords = [catalog.official.getReleaseVersion(name, full[1])]; + }); } else { versionRecords = _.map(catalog.official.getSortedRecommendedReleaseVersions(name, ""), function (v) { - return catalog.official.getReleaseVersion(name, v); + return doOrDie(function () { + return catalog.official.getReleaseVersion(name, v); + }); }); } } @@ -878,7 +993,7 @@ main.registerCommand({ search = new RegExp(options.args[0]); } catch (err) { process.stderr.write(err + "\n"); - process.exit(1); + return 1; } if (options.maintainer) { @@ -891,11 +1006,14 @@ main.registerCommand({ // you update to a new version of meteor is not that dire. selector = function (packageName, isRelease) { var record; - if (isRelease) { - record = catalog.official.getReleaseTrack(packageName); - } else { - record = catalog.official.getPackage(packageName); - } + // XXX make sure search works while offline + doOrDie(function () { + if (isRelease) { + record = catalog.official.getReleaseTrack(packageName); + } else { + record = catalog.official.getPackage(packageName); + } + }); return packageName.match(search) && !!_.findWhere(record.maintainers, {username: username}); }; @@ -907,7 +1025,9 @@ main.registerCommand({ _.each(allPackages, function (pack) { if (selector(pack, false)) { - var vr = catalog.official.getLatestVersion(pack); + var vr = doOrDie(function () { + return catalog.official.getLatestVersion(pack); + }); if (vr) { matchingPackages.push( { name: pack, description: vr.description }); @@ -916,10 +1036,13 @@ main.registerCommand({ }); _.each(allReleases, function (track) { if (selector(track, true)) { - var vr = catalog.official.getDefaultReleaseVersion(track); + var vr = doOrDie(function () { + return catalog.official.getDefaultReleaseVersion(track); + }); if (vr) { - var vrlong = - catalog.official.getReleaseVersion(track, vr.version); + var vrlong = doOrDie(function () { + return catalog.official.getReleaseVersion(track, vr.version); + }); matchingReleases.push( { name: track, description: vrlong.description }); } @@ -1040,7 +1163,9 @@ main.registerCommand({ var couldNotContactServer = false; // Refresh the catalog, cacheing the remote package data on the server. - catalog.official.refresh(true); + // XXX should be able to update even without a refresh, esp to a specific + // server + refreshOfficialCatalogOrDie(); // If you are specifying packaging individually, you probably don't want to // update the release. @@ -1051,7 +1176,7 @@ main.registerCommand({ // Some basic checks to make sure that this command is being used correctly. if (options["packages-only"] && options["patch"]) { process.stderr.write("There is no such thing as a patch update to packages."); - return 1;; + return 1; } if (!options["packages-only"]) { @@ -1079,7 +1204,9 @@ main.registerCommand({ // but it should be a no-op next time (unless there actually was a new latest // release in the interim). if (! release.forced) { - var latestRelease = release.latestDownloaded(releaseTrack); + var latestRelease = doOrDie(function () { + return release.latestDownloaded(releaseTrack); + }); // Are we on some track without ANY recommended releases at all, // and the user ran 'meteor update' without specifying a release? We // really can't do much here. @@ -1179,7 +1306,9 @@ main.registerCommand({ return 1;; } var r = appRelease.split('@'); - var record = catalog.official.getReleaseVersion(r[0], r[1]); + var record = doOrDie(function () { + return catalog.official.getReleaseVersion(r[0], r[1]); + }); var updateTo = record.patchReleaseVersion; if (!updateTo) { process.stderr.write( @@ -1188,11 +1317,15 @@ main.registerCommand({ } releaseVersionsToTry = [updateTo]; } else if (release.forced) { - releaseVersionsToTry = [release.current.getReleaseVersion()]; + doOrDie(function () { + releaseVersionsToTry = [release.current.getReleaseVersion()]; + }); } else { // XXX clean up all this splitty stuff - var appReleaseInfo = catalog.official.getReleaseVersion( - appRelease.split('@')[0], appRelease.split('@')[1]); + var appReleaseInfo = doOrDie(function () { + return catalog.official.getReleaseVersion( + appRelease.split('@')[0], appRelease.split('@')[1]); + }); var appOrderKey = (appReleaseInfo && appReleaseInfo.orderKey) || null; releaseVersionsToTry = catalog.official.getSortedRecommendedReleaseVersions( releaseTrack, appOrderKey); @@ -1220,21 +1353,35 @@ main.registerCommand({ return 1; } var solutionReleaseVersion = _.find(releaseVersionsToTry, function (versionToTry) { - var releaseRecord = catalog.complete.getReleaseVersion(releaseTrack, versionToTry); + var releaseRecord = doOrDie(function () { + return catalog.complete.getReleaseVersion(releaseTrack, versionToTry); + }); if (!releaseRecord) throw Error("missing release record?"); - var constraints = project.calculateCombinedConstraints(releaseRecord.packages); + var constraints = doOrDie(function () { + return project.calculateCombinedConstraints(releaseRecord.packages); + }); try { - solutionPackageVersions = catalog.complete.resolveConstraints( - constraints, - { previousSolution: previousVersions }, - { ignoreProjectDeps: true }); + var messages = buildmessage.capture(function () { + solutionPackageVersions = catalog.complete.resolveConstraints( + constraints, + { previousSolution: previousVersions }, + { ignoreProjectDeps: true }); + }); + if (messages.hasMessages()) { + if (process.env.METEOR_UPDATE_DEBUG) { + process.stderr.write( + "Update to release " + releaseTrack + "@" + versionToTry + + " is impossible:\n" + messages.formatMessages()); + } + return false; + } } catch (e) { - // XXX we should make the error handling explicitly detectable, and not - // actually mention failures that are recoverable - process.stderr.write( - "Update to release " + releaseTrack + - "@" + versionToTry + " impossible: " + e.message + "\n"); + if (process.env.METEOR_UPDATE_DEBUG) { + process.stderr.write( + "Update to release " + releaseTrack + + "@" + versionToTry + " impossible: " + e.message + "\n"); + } return false; } return true; @@ -1322,13 +1469,21 @@ main.registerCommand({ // Call the constraint solver. This should not fail, since we are not adding // any constraints that we didn't have before. - var newVersions = catalog.complete.resolveConstraints(allPackages, { - previousSolution: versions, - breaking: !options.minor, - upgrade: upgradePackages - }, { - ignoreProjectDeps: true + var newVersions; + var messages = buildmessage.capture(function () { + newVersions = catalog.complete.resolveConstraints(allPackages, { + previousSolution: versions, + breaking: !options.minor, + upgrade: upgradePackages + }, { + ignoreProjectDeps: true + }); }); + if (messages.hasMessages()) { + process.stderr.write("Error resolving constraints for packages:\n" + + messages.formatMessages()); + return 1; + } // Just for the sake of good messages, check to see if anything changed. if (_.isEqual(newVersions, versions)) { @@ -1378,7 +1533,8 @@ main.registerCommand({ var failed = false; // Refresh the catalog, cacheing the remote package data on the server. - catalog.official.refresh(); + // XXX ensure this works while offline + refreshOfficialCatalogOrDie(); // Read in existing package dependencies. var packages = project.getConstraints(); @@ -1405,17 +1561,19 @@ main.registerCommand({ }); _.each(constraints, function (constraint) { // Check that the package exists. - if (! catalog.complete.getPackage(constraint.name)) { - process.stderr.write(constraint.name + ": no such package\n"); - failed = true; - return; - } + doOrDie(function () { + if (! catalog.complete.getPackage(constraint.name)) { + process.stderr.write(constraint.name + ": no such package\n"); + failed = true; + return; + } + }); // If the version was specified, check that the version exists. if (constraint.version !== null) { - var versionInfo = catalog.complete.getVersion( - constraint.name, - constraint.version); + var versionInfo = doOrDie(function () { + return catalog.complete.getVersion(constraint.name, constraint.version); + }); if (! versionInfo) { process.stderr.write( constraint.name + "@" + constraint.version + ": no such version\n"); @@ -1519,7 +1677,9 @@ main.registerCommand({ process.stdout.write("\n"); _.each(constraints, function (constraint) { var version = newVersions[constraint.name]; - var versionRecord = catalog.complete.getVersion(constraint.name, version); + var versionRecord = doOrDie(function () { + return catalog.complete.getVersion(constraint.name, version); + }); if (constraint.constraintString !== null && version !== constraint.version) { process.stdout.write("Added " + constraint.name + " at version " + version + @@ -1548,7 +1708,8 @@ main.registerCommand({ // server. Technically, we don't need to do this, since it is unlikely that // new data will change our constraint solver decisions. But as a user, I // would expect this command to update the local catalog. - catalog.official.refresh(true); + // XXX what if we're offline? + refreshOfficialCatalogOrDie(); // Read in existing package dependencies. var packages = project.getConstraints(); @@ -1621,7 +1782,7 @@ main.registerCommand({ }, function (options) { // We want the most recent information. - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); var name = options.args[0]; // Yay, checking that options are correct. @@ -1637,7 +1798,10 @@ main.registerCommand({ } // Now let's get down to business! Fetching the thing. - var fullRecord = getReleaseOrPackageRecord(name); + var fullRecord; + doOrDie(function () { + fullRecord = getReleaseOrPackageRecord(name); + }); var record = fullRecord.record; if (!options.list) { @@ -1669,7 +1833,7 @@ main.registerCommand({ process.stderr.write("\n" + err + "\n"); } conn.close(); - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); } process.stdout.write("\n The maintainers for " + name + " are:\n"); @@ -1682,6 +1846,212 @@ main.registerCommand({ return 0; }); + /////////////////////////////////////////////////////////////////////////////// +// admin make-bootstrap-tarballs +/////////////////////////////////////////////////////////////////////////////// + +main.registerCommand({ + name: 'admin make-bootstrap-tarballs', + minArgs: 2, + maxArgs: 2, + hidden: true +}, function (options) { + var releaseNameAndVersion = options.args[0]; + var outputDirectory = options.args[1]; + + // In this function, we want to use the official catalog everywhere, because + // we assume that all packages have been published (along with the release + // obviously) and we want to be sure to only bundle the published versions. + catalog.official.refresh(); + + var parsed = utils.splitConstraint(releaseNameAndVersion); + if (!parsed.constraint) + throw new main.ShowUsage; + + var release = doOrDie(function () { + return catalog.official.getReleaseVersion( + parsed.package, parsed.constraint); + }); + if (!release) { + // XXX this could also mean package unknown. + process.stderr.write('Release unknown: ' + releaseNameAndVersion + '\n'); + return 1; + } + + var toolPkg = release.tool && utils.splitConstraint(release.tool); + if (! (toolPkg && toolPkg.constraint)) + throw new Error("bad tool in release: " + toolPkg); + var toolPkgBuilds = doOrDie(function () { + return catalog.official.getAllBuilds( + toolPkg.package, toolPkg.constraint); + }); + if (!toolPkgBuilds) { + // XXX this could also mean package unknown. + process.stderr.write('Tool version unknown: ' + release.tool + '\n'); + return 1; + } + if (!toolPkgBuilds.length) { + process.stderr.write('Tool version has no builds: ' + release.tool + '\n'); + return 1; + } + + // XXX check to make sure this is the three arches that we want? it's easier + // during 0.9.0 development to allow it to just decide "ok, i just want to + // build the OSX tarball" though. + var buildArches = _.pluck(toolPkgBuilds, 'buildArchitectures'); + var osArches = _.map(buildArches, function (buildArch) { + var subArches = buildArch.split('+'); + var osArches = _.filter(subArches, function (subArch) { + return subArch.substr(0, 3) === 'os.'; + }); + if (osArches.length !== 1) { + throw Error("build architecture " + buildArch + " lacks unique os.*"); + } + return osArches[0]; + }); + + process.stderr.write( + 'Building bootstrap tarballs for architectures ' + + osArches.join(', ') + '\n'); + // Before downloading anything, check that the catalog contains everything we + // need for the OSes that the tool is built for. + var messages = buildmessage.capture(function () { + _.each(osArches, function (osArch) { + _.each(release.packages, function (pkgVersion, pkgName) { + buildmessage.enterJob({ + title: "looking up " + pkgName + "@" + pkgVersion + " on " + osArch + }, function () { + if (!catalog.official.getBuildsForArches(pkgName, pkgVersion, [osArch])) { + buildmessage.error("missing build of " + pkgName + "@" + pkgVersion + + " for " + osArch); + } + }); + }); + }); + }); + + if (messages.hasMessages()) { + process.stderr.write("\n" + messages.formatMessages()); + return 1; + }; + + files.mkdir_p(outputDirectory); + + // Get a copy of the data.json. + var dataTmpdir = files.mkdtemp(); + var tmpDataJson = path.join(dataTmpdir, 'data.json'); + + var savedData = packageClient.updateServerPackageData(null, { + packageStorageFile: tmpDataJson + }).data; + if (!savedData) { + // will have already printed an error + return 2; + } + + _.each(osArches, function (osArch) { + var tmpdir = files.mkdtemp(); + // We're going to build and tar up a tropohouse in a temporary directory; we + // don't want to use any of our local packages, so we use catalog.official + // instead of catalog. + // XXX update to '.meteor' when we combine houses + var tmpTropo = new tropohouse.Tropohouse( + path.join(tmpdir, '.meteor'), catalog.official); + var messages = buildmessage.capture(function () { + buildmessage.enterJob({ + title: "downloading tool package " + toolPkg.package + "@" + + toolPkg.constraint + }, function () { + tmpTropo.maybeDownloadPackageForArchitectures({ + packageName: toolPkg.package, + version: toolPkg.constraint, + architectures: [osArch] // XXX 'web.browser' too? + }); + }); + _.each(release.packages, function (pkgVersion, pkgName) { + buildmessage.enterJob({ + title: "downloading package " + pkgName + "@" + pkgVersion + }, function () { + tmpTropo.maybeDownloadPackageForArchitectures({ + packageName: pkgName, + version: pkgVersion, + architectures: [osArch] // XXX 'web.browser' too? + }); + }); + }); + }); + if (messages.hasMessages()) { + process.stderr.write("\n" + messages.formatMessages()); + return 1; + } + + // Install the data.json file we synced earlier. + files.copyFile(tmpDataJson, config.getPackageStorage(tmpTropo)); + + // Create the top-level 'meteor' symlink, which links to the latest tool's + // meteor shell script. + var toolUnipackagePath = + tmpTropo.packagePath(toolPkg.package, toolPkg.constraint); + var toolUnipackage = new unipackage.Unipackage; + toolUnipackage.initFromPath(toolPkg.package, toolUnipackagePath); + var toolRecord = _.findWhere(toolUnipackage.toolsOnDisk, {arch: osArch}); + if (!toolRecord) + throw Error("missing tool for " + osArch); + fs.symlinkSync( + path.join( + tmpTropo.packagePath(toolPkg.package, toolPkg.constraint, true), + toolRecord.path, + 'meteor'), + path.join(tmpTropo.root, 'meteor')); + + files.createTarball( + tmpTropo.root, + path.join(outputDirectory, 'meteor-bootstrap-' + osArch + '.tar.gz')); + }); + + return 0; +}); + +// We will document how to set banners on things in a later release. +main.registerCommand({ + name: 'admin set-banners', + minArgs: 1, + maxArgs: 1, + hidden: true +}, function (options) { + var bannersFile = options.args[0]; + try { + var bannersData = fs.readFileSync(bannersFile, 'utf8'); + bannersData = JSON.parse(bannersData); + } catch (e) { + process.stderr.write("Could not parse banners file: "); + process.stderr.write(e.message + "\n"); + return 1; + } + if (!bannersData.track) { + process.stderr.write("Banners file should have a 'track' key.\n"); + return 1; + } + if (!bannersData.banners) { + process.stderr.write("Banners file should have a 'banners' key.\n"); + return 1; + } + + try { + var conn = packageClient.loggedInPackagesConnection(); + } catch (err) { + packageClient.handlePackageServerConnectionError(err); + return 1; + } + + conn.call('setBannersOnReleases', bannersData.track, + bannersData.banners); + + // Refresh afterwards. + refreshOfficialCatalogOrDie(); + return 0; +}); + main.registerCommand({ name: 'admin recommend-release', minArgs: 1, @@ -1692,7 +2062,7 @@ main.registerCommand({ }, function (options) { // We want the most recent information. - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); var release = options.args[0].split('@'); var name = release[0]; var version = release[1]; @@ -1702,7 +2072,10 @@ main.registerCommand({ } // Now let's get down to business! Fetching the thing. - var record = catalog.official.getReleaseTrack(name); + var record; + doOrDie(function () { + record = catalog.official.getReleaseTrack(name); + }); if (!record) { process.stderr.write('\n There is no release track named ' + name + '\n'); return 1; @@ -1731,7 +2104,7 @@ main.registerCommand({ process.stderr.write("\n" + err + "\n"); } conn.close(); - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); return 0; }); @@ -1744,7 +2117,7 @@ main.registerCommand({ }, function (options) { // We want the most recent information. - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); var package = options.args[0].split('@'); var name = package[0]; var version = package[1]; @@ -1755,7 +2128,9 @@ main.registerCommand({ var ecv = options.args[1]; // Now let's get down to business! Fetching the thing. - var record = catalog.official.getPackage(name); + var record = doOrDie(function () { + return catalog.official.getPackage(name); + }); if (!record) { process.stderr.write('\n There is no package named ' + name + '\n'); return 1; @@ -1780,7 +2155,7 @@ main.registerCommand({ process.stderr.write("\n" + err + "\n"); } conn.close(); - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); return 0; }); @@ -1793,12 +2168,14 @@ main.registerCommand({ }, function (options) { // We want the most recent information. - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); var name = options.args[0]; var url = options.args[1]; // Now let's get down to business! Fetching the thing. - var record = catalog.official.getPackage(name); + var record = doOrDie(function () { + return catalog.official.getPackage(name); + }); if (!record) { process.stderr.write('\n There is no package named ' + name + '\n'); return 1; @@ -1821,7 +2198,7 @@ main.registerCommand({ process.stderr.write("\n" + err + "\n"); } conn.close(); - catalog.official.refresh(); + refreshOfficialCatalogOrDie(); return 0; }); diff --git a/tools/commands.js b/tools/commands.js index 402cb3d018..cb2ad41c47 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -19,12 +19,12 @@ var httpHelpers = require('./http-helpers.js'); var archinfo = require('./archinfo.js'); var tropohouse = require('./tropohouse.js'); var packageCache = require('./package-cache.js'); -var packageLoader = require('./package-loader.js'); var PackageSource = require('./package-source.js'); var compiler = require('./compiler.js'); var catalog = require('./catalog.js'); var stats = require('./stats.js'); var unipackage = require('./unipackage.js'); +var commandsPackages = require('./commands-packages.js'); // The architecture used by Galaxy servers; it's the architecture used // by 'meteor deploy'. @@ -60,6 +60,7 @@ var hostedWithGalaxy = function (site) { // version record for that package. var getLocalPackages = function () { var ret = {}; + buildmessage.assertInCapture(); var names = catalog.complete.getAllPackageNames(); _.each(names, function (name) { @@ -71,6 +72,7 @@ var getLocalPackages = function () { return ret; }; + /////////////////////////////////////////////////////////////////////////////// // options that act like commands /////////////////////////////////////////////////////////////////////////////// @@ -138,75 +140,6 @@ main.registerCommand({ return 0; }); -// Internal use only. Makes sure that your Meteor install is totally good to go -// (is "airplane safe"). Specifically, it: -// - Builds all local packages (including their npm dependencies) -// - Ensures that all packages in your current release are downloaded -// - Ensures that all packages used by your app (if any) are downloaded -// (It also ensures you have the dev bundle downloaded, just like every command -// in a checkout.) -// -// The use case is, for example, cloning an app from github, running this -// command, then getting on an airplane. -// -// This does NOT guarantee a *re*build of all local packages (though it will -// download any new dependencies). If you want to rebuild all local packages, -// call meteor rebuild. That said, rebuild should only be necessary if there's a -// bug in the build tool... otherwise, packages should be rebuilt whenever -// necessary! -main.registerCommand({ - name: '--get-ready' -}, function (options) { - // It is not strictly needed, but it is thematically a good idea to refresh - // the official catalog when we call get-ready, since it is an - // internet-requiring action. - catalog.official.refresh(); - - var loadPackages = function (packagesToLoad, loader) { - buildmessage.assertInCapture(); - loader.downloadMissingPackages(); - _.each(packagesToLoad, function (name) { - // Calling getPackage on the loader will return a unipackage object, which - // means that the package will be compiled/downloaded. That we throw the - // package variable away afterwards is immaterial. - loader.getPackage(name); - }); - }; - - var messages = buildmessage.capture({ - title: 'getting packages ready' - }, function () { - // First, build all accessible *local* packages, whether or not this app - // uses them. Use the "all packages are local" loader. - loadPackages(catalog.complete.getLocalPackageNames(), - new packageLoader.PackageLoader({versions: null})); - - // In an app? Get the list of packages used by this app. Calling getVersions - // on the project will ensureDepsUpToDate which will ensure that all builds - // of everything we need from versions have been downloaded. (Calling - // buildPackages may be redundant, but can't hurt.) - if (options.appDir) { - loadPackages(_.keys(project.getVersions()), project.getPackageLoader()); - } - - // Using a release? Get all the packages in the release. - if (release.current.isProperRelease()) { - var releasePackages = release.current.getPackages(); - loadPackages( - _.keys(releasePackages), - new packageLoader.PackageLoader({versions: releasePackages})); - } - }); - - if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); - return 1; - }; - - console.log("You are ready!"); - return 0; -}); - /////////////////////////////////////////////////////////////////////////////// // run /////////////////////////////////////////////////////////////////////////////// @@ -343,7 +276,9 @@ main.registerCommand({ var relString; if (release.current.isCheckout()) { xn = xn.replace(/~cc~/g, "//"); - var rel = catalog.complete.getDefaultReleaseVersion(); + var rel = commandsPackages.doOrDie(function () { + return catalog.complete.getDefaultReleaseVersion(); + }); var relString = rel.track + "@" + rel.version; } else { xn = xn.replace(/~cc~/g, ""); @@ -381,10 +316,12 @@ main.registerCommand({ // (In particular, it's not sufficient to create the new app with // this version of the tools, and then stamp on the correct release // at the end.) - if (! release.current.isCheckout() && - release.current.name !== release.latestDownloaded() && - ! release.forced) { - throw new main.SpringboardToLatestRelease; + if (! release.current.isCheckout() && !release.forced) { + var needToSpringboard = commandsPackages.doOrDie(function () { + return release.current.name !== release.latestDownloaded(); + }); + if (needToSpringboard) + throw new main.SpringboardToLatestRelease; } var exampleDir = path.join(__dirname, '..', 'examples'); @@ -532,7 +469,7 @@ main.registerCommand({ process.stderr.write("Invalid architecture: " + options.architecture + "\n"); process.stderr.write( "Please use one of the following: " + VALID_ARCHITECTURES + "\n"); - process.exit(1); + return 1; } var bundleArch = options.architecture || archinfo.host(); @@ -979,7 +916,9 @@ main.registerCommand({ if (options.args.length === 0) { // Only test local packages if no package is specified. // XXX should this use the new getLocalPackageNames? - var packageList = getLocalPackages(); + var packageList = commandsPackages.doOrDie(function () { + return getLocalPackages(); + }); if (! packageList) { // Couldn't load the package list, probably because some package // has a parse error. Bail out -- this kind of sucks; we would @@ -1071,12 +1010,18 @@ main.registerCommand({ // compute them for us. This means that right now, we are testing all packages // as they work together. var tests = []; - _.each(testPackages, function(name) { - var versionRecord = catalog.complete.getLatestVersion(name); - if (versionRecord && versionRecord.testName) { - tests.push(versionRecord.testName); - } + var messages = buildmessage.capture(function () { + _.each(testPackages, function(name) { + var versionRecord = catalog.complete.getLatestVersion(name); + if (versionRecord && versionRecord.testName) { + tests.push(versionRecord.testName); + } + }); }); + if (messages.hasMessages()) { + process.stderr.write(messages.formatMessages()); + return 1; + } project.forceEditPackages(tests, 'add'); @@ -1114,10 +1059,6 @@ main.registerCommand({ }); } - _.each(localPackageNames, function (name) { - catalog.complete.removeLocalPackage(name); - }); - return ret; }); @@ -1204,212 +1145,6 @@ main.registerCommand({ return auth.whoAmICommand(options); }); -/////////////////////////////////////////////////////////////////////////////// -// admin make-bootstrap-tarballs -/////////////////////////////////////////////////////////////////////////////// - -main.registerCommand({ - name: 'admin make-bootstrap-tarballs', - minArgs: 2, - maxArgs: 2, - hidden: true, -}, function (options) { - var releaseNameAndVersion = options.args[0]; - var outputDirectory = options.args[1]; - - // In this function, we want to use the official catalog everywhere, because - // we assume that all packages have been published (along with the release - // obviously) and we want to be sure to only bundle the published versions. - catalog.official.refresh(); - - var parsed = utils.splitConstraint(releaseNameAndVersion); - if (!parsed.constraint) - throw new main.ShowUsage; - - var release = catalog.official.getReleaseVersion(parsed.package, - parsed.constraint); - if (!release) { - // XXX this could also mean package unknown. - process.stderr.write('Release unknown: ' + releaseNameAndVersion + '\n'); - return 1; - } - - var toolPkg = release.tool && utils.splitConstraint(release.tool); - if (! (toolPkg && toolPkg.constraint)) - throw new Error("bad tool in release: " + toolPkg); - var toolPkgBuilds = catalog.official.getAllBuilds( - toolPkg.package, toolPkg.constraint); - if (!toolPkgBuilds) { - // XXX this could also mean package unknown. - process.stderr.write('Tool version unknown: ' + release.tool + '\n'); - return 1; - } - if (!toolPkgBuilds.length) { - process.stderr.write('Tool version has no builds: ' + release.tool + '\n'); - return 1; - } - - // XXX check to make sure this is the three arches that we want? it's easier - // during 0.9.0 development to allow it to just decide "ok, i just want to - // build the OSX tarball" though. - var buildArches = _.pluck(toolPkgBuilds, 'buildArchitectures'); - var osArches = _.map(buildArches, function (buildArch) { - var subArches = buildArch.split('+'); - var osArches = _.filter(subArches, function (subArch) { - return subArch.substr(0, 3) === 'os.'; - }); - if (osArches.length !== 1) { - throw Error("build architecture " + buildArch + " lacks unique os.*"); - } - return osArches[0]; - }); - - process.stderr.write( - 'Building bootstrap tarballs for architectures ' + - osArches.join(', ') + '\n'); - // Before downloading anything, check that the catalog contains everything we - // need for the OSes that the tool is built for. - var messages = buildmessage.capture(function () { - _.each(osArches, function (osArch) { - _.each(release.packages, function (pkgVersion, pkgName) { - buildmessage.enterJob({ - title: "looking up " + pkgName + "@" + pkgVersion + " on " + osArch - }, function () { - if (!catalog.official.getBuildsForArches(pkgName, pkgVersion, [osArch])) { - buildmessage.error("missing build of " + pkgName + "@" + pkgVersion + - " for " + osArch); - } - }); - }); - }); - }); - - if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); - return 1; - }; - - files.mkdir_p(outputDirectory); - - // Get a copy of the data.json. - var dataTmpdir = files.mkdtemp(); - var tmpDataJson = path.join(dataTmpdir, 'data.json'); - - var savedData = packageClient.updateServerPackageData(null, { - packageStorageFile: tmpDataJson - }).data; - if (!savedData) { - // will have already printed an error - process.exit(2); - } - - _.each(osArches, function (osArch) { - var tmpdir = files.mkdtemp(); - // We're going to build and tar up a tropohouse in a temporary directory; we - // don't want to use any of our local packages, so we use catalog.official - // instead of catalog. - // XXX update to '.meteor' when we combine houses - var tmpTropo = new tropohouse.Tropohouse( - path.join(tmpdir, '.meteor'), catalog.official); - var messages = buildmessage.capture(function () { - buildmessage.enterJob({ - title: "downloading tool package " + toolPkg.package + "@" + - toolPkg.constraint - }, function () { - tmpTropo.maybeDownloadPackageForArchitectures({ - packageName: toolPkg.package, - version: toolPkg.constraint, - architectures: [osArch] // XXX 'web.browser' too? - }); - }); - _.each(release.packages, function (pkgVersion, pkgName) { - buildmessage.enterJob({ - title: "downloading package " + pkgName + "@" + pkgVersion - }, function () { - tmpTropo.maybeDownloadPackageForArchitectures({ - packageName: pkgName, - version: pkgVersion, - architectures: [osArch] // XXX 'web.browser' too? - }); - }); - }); - }); - if (messages.hasMessages()) { - process.stderr.write("\n" + messages.formatMessages()); - return 1; - } - - // Install the data.json file we synced earlier. - files.copyFile(tmpDataJson, config.getPackageStorage(tmpTropo)); - - // Create the top-level 'meteor' symlink, which links to the latest tool's - // meteor shell script. - var toolUnipackagePath = - tmpTropo.packagePath(toolPkg.package, toolPkg.constraint); - var toolUnipackage = new unipackage.Unipackage; - toolUnipackage.initFromPath(toolPkg.package, toolUnipackagePath); - var toolRecord = _.findWhere(toolUnipackage.toolsOnDisk, {arch: osArch}); - if (!toolRecord) - throw Error("missing tool for " + osArch); - fs.symlinkSync( - path.join( - tmpTropo.packagePath(toolPkg.package, toolPkg.constraint, true), - toolRecord.path, - 'meteor'), - path.join(tmpTropo.root, 'meteor')); - - files.createTarball( - tmpTropo.root, - path.join(outputDirectory, 'meteor-bootstrap-' + osArch + '.tar.gz')); - }); - - return 0; -}); - -/////////////////////////////////////////////////////////////////////////////// -// admin set-banners -/////////////////////////////////////////////////////////////////////////////// - -// We will document how to set banners on things in a later release. -main.registerCommand({ - name: 'admin set-banners', - minArgs: 1, - maxArgs: 1, - hidden: true, -}, function (options) { - var bannersFile = options.args[0]; - try { - var bannersData = fs.readFileSync(bannersFile, 'utf8'); - bannersData = JSON.parse(bannersData); - } catch (e) { - process.stderr.write("Could not parse banners file: "); - process.stderr.write(e.message + "\n"); - return 1; - } - if (!bannersData.track) { - process.stderr.write("Banners file should have a 'track' key.\n"); - return 1; - } - if (!bannersData.banners) { - process.stderr.write("Banners file should have a 'banners' key.\n"); - return 1; - } - - try { - var conn = packageClient.loggedInPackagesConnection(); - } catch (err) { - packageClient.handlePackageServerConnectionError(err); - return 1; - } - - conn.call('setBannersOnReleases', bannersData.track, - bannersData.banners); - - // Refresh afterwards. - catalog.official.refresh(); - return 0; -}); - /////////////////////////////////////////////////////////////////////////////// // self-test /////////////////////////////////////////////////////////////////////////////// diff --git a/tools/compiler.js b/tools/compiler.js index 373710b515..c34826313d 100644 --- a/tools/compiler.js +++ b/tools/compiler.js @@ -137,7 +137,7 @@ var determineBuildTimeDependencies = function (packageSource, constraintSolverOpts) { var ret = {}; constraintSolverOpts = constraintSolverOpts || {}; - constraintSolverOpts.ignoreProjectDeps || buildmessage.assertInCapture(); + buildmessage.assertInCapture(); // There are some special cases where we know that the package has no source // files, which means it can't have any interesting build-time @@ -898,6 +898,7 @@ compiler.compile = function (packageSource, options) { // so any dependencies that contains plugins have real versions in the // catalog already. Still, this seems very brittle and we should fix it. var getPluginProviders = function (versions) { + buildmessage.assertInCapture(); var result = {}; _.each(versions, function (version, name) { // Direct dependencies only create a build-order constraint if @@ -918,7 +919,7 @@ var getPluginProviders = function (versions) { compiler.getBuildOrderConstraints = function ( packageSource, constraintSolverOpts) { constraintSolverOpts = constraintSolverOpts || {}; - constraintSolverOpts.ignoreProjectDeps || buildmessage.assertInCapture(); + buildmessage.assertInCapture(); var versions = {}; // map from package name to version to true var addVersion = function (version, name) { diff --git a/tools/main.js b/tools/main.js index 1f4a4da66b..9c909f02b8 100644 --- a/tools/main.js +++ b/tools/main.js @@ -76,6 +76,14 @@ var messages = {}; // usage information. main.ShowUsage = function () {}; +// Exception to throw from a helper function inside a command which is identical +// to returning the given exit code from the command. ONLY USE THIS IN HELPERS +// THAT ARE ONLY CALLED DIRECTLY FROM COMMANDS! DON'T BE LAZY AND PUT THROW OF +// THIS IN RANDOM LIBRARY CODE! +main.ExitWithCode = function (code) { + this.code = code; +}; + // Exception to throw to skip the process.exit call. main.WaitForExit = function () {}; @@ -771,7 +779,14 @@ Fiber(function () { } else { // Run outside an app dir with no --release flag. Use the latest // release we know about (in the default track). - releaseName = release.latestDownloaded(); + var messages = buildmessage.capture(function () { + releaseName = release.latestDownloaded(); + }); + if (messages.hasMessages()) { + process.stderr.write("=> Errors while determining latest release:\n" + + messages.formatMessages()); + process.exit(1); + } } } @@ -789,7 +804,20 @@ Fiber(function () { } try { - var rel = release.load(releaseName); + var rel; + var messages = buildmessage.capture(function () { + rel = release.load(releaseName); + }); + if (messages.hasMessages()) { + // XXX The errors that trigger this are likely things like failure to + // load livedata when trying to refresh, or maybe failure to build some + // local packages, or something. They probably aren't "release doesn't + // exist"? But who knows? + process.stderr.write("=> Errors while loading release:\n" + + messages.formatMessages()); + process.exit(1); + } + } catch (e) { var name = releaseName; if (e instanceof files.OfflineError) { @@ -1115,8 +1143,15 @@ commandName + ": You're not in a Meteor project directory.\n" + // Commands that require you to be in a package directory add that package // as a local package to the catalog. Other random commands don't (but if we // see a reason for them to, we can change this rule). - catalog.complete.addLocalPackage(path.basename(options.packageDir), - options.packageDir); + messages = buildmessage.capture(function () { + catalog.complete.addLocalPackage(path.basename(options.packageDir), + options.packageDir); + }); + if (messages.hasMessages()) { + process.stderr.write("=> Errors while scanning current package:\n\n"); + process.stderr.write(messages.formatMessages()); + process.exit(1); + } } if (command.requiresRelease && ! release.current) { @@ -1148,24 +1183,35 @@ commandName + ": You're not in a Meteor project directory.\n" + var ret = command.func(options); } catch (e) { if (e === main.ShowUsage || e === main.WaitForExit || - e === main.SpringboardToLatestRelease) + e === main.SpringboardToLatestRelease || + e === main.WaitForExit) { throw new Error( "you meant 'throw new main.Foo', not 'throw main.Foo'"); - if (e instanceof main.ShowUsage) { + } else if (e instanceof main.ShowUsage) { process.stderr.write(longHelp(commandName) + "\n"); process.exit(1); - } - if (e instanceof main.SpringboardToLatestRelease) { + } else if (e instanceof main.SpringboardToLatestRelease) { // Load the latest release's metadata so that we can figure out // the tools version that it uses. We should only do this if // we know there is some latest release on this track. - var latestRelease = release.load(release.latestDownloaded(e.track)); + var latestRelease; + var messages = buildmessage.capture(function () { + latestRelease = release.load(release.latestDownloaded(e.track)); + }); + if (messages.hasMessages()) { + process.stderr.write("=> Errors while loading latest release:\n\n"); + process.stderr.write(messages.formatMessages()); + process.exit(1); + } springboard(latestRelease, latestRelease.name); // (does not return) - } - if (e instanceof main.WaitForExit) + } else if (e instanceof main.WaitForExit) { return; - throw e; + } else if (e instanceof main.ExitWithCode) { + process.exit(e.code); + } else { + throw e; + } } // Exit. (We will not get here if the command threw an exception diff --git a/tools/package-loader.js b/tools/package-loader.js index 55e76766b9..0f8871b3d8 100644 --- a/tools/package-loader.js +++ b/tools/package-loader.js @@ -58,28 +58,6 @@ _.extend(exports.PackageLoader.prototype, { name, loadPath, self.constraintSolverOpts); }, - containsPlugins: function (name) { - var self = this; - - // We don't want to ever look at the catalog in the uniload case. We - // shouldn't ever care about plugins anyway, since uniload should never - // compile real packages from source (it sorta compiles the wrapper "load" - // package, which should avoid calling this function). - if (self.uniloadDir) - throw Error("called containsPlugins for uniload?"); - - var versionRecord; - if (self.versions === null) { - versionRecord = catalog.complete.getLatestVersion(name); - } else if (_.has(self.versions, name)) { - versionRecord = catalog.complete.getVersion(name, self.versions[name]); - } else { - throw new Error("no version specified for package " + name); - } - - return versionRecord.containsPlugins; - }, - // As getPackage, but returns the path of the package that would be // loaded rather than loading the package, and does not take any // options. Returns null if the package is not available. diff --git a/tools/package-source.js b/tools/package-source.js index c125800b70..278273f3e6 100644 --- a/tools/package-source.js +++ b/tools/package-source.js @@ -406,6 +406,7 @@ _.extend(PackageSource.prototype, { // guideline for a repeatable build. initFromPackageDir: function (name, dir, options) { var self = this; + buildmessage.assertInCapture(); var isPortable = true; options = options || {}; diff --git a/tools/project.js b/tools/project.js index 13d030412f..f44546bc8e 100644 --- a/tools/project.js +++ b/tools/project.js @@ -233,6 +233,8 @@ _.extend(Project.prototype, { // getCurrentCombinedConstraints. calculateCombinedConstraints : function (releasePackages) { var self = this; + buildmessage.assertInCapture(); + var allDeps = []; // First, we process the contents of the .meteor/packages file. The // self.constraints variable is always up to date. diff --git a/tools/release.js b/tools/release.js index 59a31753b2..77decb2138 100644 --- a/tools/release.js +++ b/tools/release.js @@ -5,6 +5,7 @@ var project = require('./project.js').project; var warehouse = require('./warehouse.js'); var catalog = require('./catalog.js'); var utils = require('./utils.js'); +var buildmessage = require('./buildmessage.js'); var release = exports; @@ -91,6 +92,7 @@ _.extend(Release.prototype, { // (XXX: Or maybe just return "checkout" or something?) getCurrentToolsVersion: function () { var self = this; + buildmessage.assertInCapture(); if (release.current.name) { return self._manifest.tool; @@ -178,6 +180,7 @@ release.explicit = null; // in the current project. (taking into account release.forced and whether we're // currently running from a checkout). release.usingRightReleaseForApp = function () { + buildmessage.assertInCapture(); if (release.current === null) throw new Error("no release?"); @@ -195,6 +198,7 @@ release.usingRightReleaseForApp = function () { // for use. May not be called when running from a checkout. // 'track' is optional (it defaults to the default track). release.latestDownloaded = function (track) { + buildmessage.assertInCapture(); if (! files.usesWarehouse()) throw new Error("called from checkout?"); // For self-test only. @@ -233,6 +237,7 @@ release.latestDownloaded = function (track) { // in the world (confirmed with server). release.load = function (name, options) { options = options || {}; + buildmessage.assertInCapture(); if (! name) { return new Release({ name: null }); @@ -285,6 +290,7 @@ release.setCurrent = function (releaseObject, forced, explicit) { // XXX hack release._setCurrentForOldTest = function () { + buildmessage.assertInCapture(); if (process.env.METEOR_SPRINGBOARD_RELEASE) { release.setCurrent(release.load(process.env.METEOR_SPRINGBOARD_RELEASE), true); diff --git a/tools/run-app.js b/tools/run-app.js index e05bd152d7..003534cfc9 100644 --- a/tools/run-app.js +++ b/tools/run-app.js @@ -397,7 +397,22 @@ _.extend(AppRunner.prototype, { // tell the catalog to reload local package sources (since their // dependencies may have changed), and then we should recompute the project // constraints. - catalog.complete.refresh({ forceRefresh: true }); + // XXX the catalog refresh seems overly conservative, but who knows + var refreshWatchSet = new watch.WatchSet; + var refreshMessages = buildmessage.capture(function () { + catalog.complete.refresh({ forceRefresh: true, + watchSet: refreshWatchSet}); + }); + if (refreshMessages.hasMessages()) { + return { + outcome: 'bundle-fail', + bundleResult: { + errors: refreshMessages, + serverWatchSet: refreshWatchSet + } + }; + } + project.reload(); runLog.clearLog(); @@ -411,11 +426,22 @@ _.extend(AppRunner.prototype, { // release.current), but we still want to detect the mismatch if // you are testing packages from an app and you 'meteor update' // that app. - if (self.appDirForVersionCheck && - ! release.usingRightReleaseForApp()) { - return { outcome: 'wrong-release', - releaseNeeded: - project.getMeteorReleaseVersion() }; + if (self.appDirForVersionCheck) { + var wrongRelease; + var rightReleaseMessages = buildmessage.capture(function () { + wrongRelease = ! release.usingRightReleaseForApp(); + }); + if (rightReleaseMessages.hasMessages()) { + return { + outcome: 'bundle-fail', + bundleResult: { errors: rightReleaseMessages } + }; + } + if (wrongRelease) { + return { outcome: 'wrong-release', + releaseNeeded: project.getMeteorReleaseVersion() + }; + } } // Bundle up the app @@ -425,6 +451,7 @@ _.extend(AppRunner.prototype, { stats.recordPackages(self.appDir); }); if (statsMessages.hasMessages()) { + // XXX so this happens any time you're offline? process.stdout.write("Error talking to stats server:\n" + statsMessages.formatMessages()); // ... but continue; @@ -698,8 +725,10 @@ _.extend(AppRunner.prototype, { self.watchFuture = new Future; var watchSet = new watch.WatchSet(); - watchSet.merge(runResult.bundleResult.serverWatchSet); - watchSet.merge(runResult.bundleResult.clientWatchSet); + if (runResult.bundleResult.serverWatchSet) + watchSet.merge(runResult.bundleResult.serverWatchSet); + if (runResult.bundleResult.clientWatchSet) + watchSet.merge(runResult.bundleResult.clientWatchSet); var watcher = new watch.Watcher({ watchSet: watchSet, onChange: function () { diff --git a/tools/selftest.js b/tools/selftest.js index 1366ca6e22..f419a9a60b 100644 --- a/tools/selftest.js +++ b/tools/selftest.js @@ -84,13 +84,15 @@ var execFileSync = function (binary, args) { })().wait(); }; -var captureAndThrow = function (f) { +var doOrThrow = function (f) { + var ret; var messages = buildmessage.capture(function () { - f(); + ret = f(); }); if (messages.hasMessages()) { throw Error(messages.formatMessages()); } + return ret; }; /////////////////////////////////////////////////////////////////////////////// @@ -634,7 +636,7 @@ _.extend(Sandbox.prototype, { // build apps that contain core packages). var toolPackage, toolPackageDirectory; - captureAndThrow(function () { + doOrThrow(function () { toolPackage = getToolsPackage(); toolPackageDirectory = '.' + toolPackage.version + '.XXX++' + toolPackage.buildArchitectures(); @@ -700,22 +702,31 @@ _.extend(Sandbox.prototype, { // should be OK. var oldOffline = catalog.official.offline; catalog.official.offline = true; - catalog.official.refresh(); + doOrThrow(function () { + catalog.official.refresh(); + }); _.each( ['autopublish', 'standard-app-packages', 'insecure'], function (name) { - var versionRec = catalog.official.getLatestVersion(name); + var versionRec = doOrThrow(function () { + return catalog.official.getLatestVersion(name); + }); if (!versionRec) { catalog.official.offline = false; - catalog.official.refresh(); + doOrThrow(function () { + catalog.official.refresh(); + }); catalog.official.offline = true; - versionRec = catalog.official.getLatestVersion(name); + versionRec = doOrThrow(function () { + return catalog.official.getLatestVersion(name); + }); if (!versionRec) { throw new Error(" hack fails for " + name); } } - var buildRec = catalog.official.getAllBuilds( - name, versionRec.version)[0]; + var buildRec = doOrThrow(function () { + return catalog.official.getAllBuilds(name, versionRec.version)[0]; + }); // Insert into packages. stubCatalog.collections.packages.push({ @@ -1582,6 +1593,6 @@ _.extend(exports, { expectThrows: expectThrows, getToolsPackage: getToolsPackage, execFileSync: execFileSync, - captureAndThrow: captureAndThrow, + doOrThrow: doOrThrow, testPackageServerUrl: 'https://test-packages.meteor.com' }); diff --git a/tools/tests/old/app-with-private/.meteor/versions b/tools/tests/old/app-with-private/.meteor/versions index 6d8d7e812c..ecc60c1245 100644 --- a/tools/tests/old/app-with-private/.meteor/versions +++ b/tools/tests/old/app-with-private/.meteor/versions @@ -1,6 +1,6 @@ application-configuration@1.0.0 autopublish@1.0.0 -autoupdate@1.0.0 +autoupdate@1.0.1 binary-heap@1.0.0 blaze-tools@1.0.0 blaze@1.0.1 @@ -20,7 +20,7 @@ jquery@1.0.0 json@1.0.0 livedata@1.0.1 logging@1.0.0 -meteor@1.0.1 +meteor@1.0.2 minifiers@1.0.0 minimongo@1.0.1 mongo-livedata@1.0.0 @@ -32,10 +32,10 @@ reload@1.0.0 retry@1.0.0 routepolicy@1.0.0 session@1.0.0 -spacebars-compiler@1.0.0 +spacebars-compiler@1.0.1 spacebars@1.0.0 standard-app-packages@1.0.0 -templating@1.0.1 +templating@1.0.2 test-package@1.0.0 ui@1.0.0 underscore@1.0.0 diff --git a/tools/tests/old/app-with-private/packages/test-package/versions.json b/tools/tests/old/app-with-private/packages/test-package/versions.json index 09cd0ef7d5..aa6e7e44bf 100644 --- a/tools/tests/old/app-with-private/packages/test-package/versions.json +++ b/tools/tests/old/app-with-private/packages/test-package/versions.json @@ -2,7 +2,7 @@ "dependencies": [ [ "meteor", - "1.0.1" + "1.0.2" ], [ "underscore", @@ -15,6 +15,6 @@ {} ] ], - "toolVersion": "meteor-tool@1.0.7", + "toolVersion": "meteor-tool@1.0.8", "format": "1.0" } \ No newline at end of file diff --git a/tools/tests/old/app-with-public/.meteor/versions b/tools/tests/old/app-with-public/.meteor/versions index a91a0cadb7..c93bc952fb 100644 --- a/tools/tests/old/app-with-public/.meteor/versions +++ b/tools/tests/old/app-with-public/.meteor/versions @@ -1,6 +1,6 @@ application-configuration@1.0.0 autopublish@1.0.0 -autoupdate@1.0.0 +autoupdate@1.0.1 binary-heap@1.0.0 blaze-tools@1.0.0 blaze@1.0.1 @@ -20,7 +20,7 @@ jquery@1.0.0 json@1.0.0 livedata@1.0.1 logging@1.0.0 -meteor@1.0.1 +meteor@1.0.2 minifiers@1.0.0 minimongo@1.0.1 mongo-livedata@1.0.0 @@ -33,10 +33,10 @@ reload@1.0.0 retry@1.0.0 routepolicy@1.0.0 session@1.0.0 -spacebars-compiler@1.0.0 +spacebars-compiler@1.0.1 spacebars@1.0.0 standard-app-packages@1.0.0 -templating@1.0.1 +templating@1.0.2 ui@1.0.0 underscore@1.0.0 webapp@1.0.0 diff --git a/tools/tests/old/empty-app/.meteor/versions b/tools/tests/old/empty-app/.meteor/versions index 813cdc51ed..787bb1ae56 100644 --- a/tools/tests/old/empty-app/.meteor/versions +++ b/tools/tests/old/empty-app/.meteor/versions @@ -1,5 +1,5 @@ application-configuration@1.0.0 -autoupdate@1.0.0 +autoupdate@1.0.1 binary-heap@1.0.0 blaze-tools@1.0.0 blaze@1.0.1 @@ -18,7 +18,7 @@ jquery@1.0.0 json@1.0.0 livedata@1.0.1 logging@1.0.0 -meteor@1.0.1 +meteor@1.0.2 minifiers@1.0.0 minimongo@1.0.1 mongo-livedata@1.0.0 @@ -30,10 +30,10 @@ reload@1.0.0 retry@1.0.0 routepolicy@1.0.0 session@1.0.0 -spacebars-compiler@1.0.0 +spacebars-compiler@1.0.1 spacebars@1.0.0 standard-app-packages@1.0.0 -templating@1.0.1 +templating@1.0.2 ui@1.0.0 underscore@1.0.0 webapp@1.0.0 diff --git a/tools/tests/old/test-bundler-assets.js b/tools/tests/old/test-bundler-assets.js index e4c8d18177..70edeebeb2 100644 --- a/tools/tests/old/test-bundler-assets.js +++ b/tools/tests/old/test-bundler-assets.js @@ -9,6 +9,7 @@ var uniload = require('../../uniload.js'); var release = require('../../release.js'); var project = require('../../project.js'); var catalog = require('../../catalog.js'); +var buildmessage = require('../../buildmessage.js'); var appWithPublic = path.join(__dirname, 'app-with-public'); var appWithPrivate = path.join(__dirname, 'app-with-private'); @@ -29,11 +30,24 @@ var setAppDir = function (appDir) { files.getCurrentToolsDir(), 'packages')); } - catalog.complete.initialize({ - localPackageDirs: localPackageDirs + doOrThrow(function () { + catalog.complete.initialize({ + localPackageDirs: localPackageDirs + }); }); }; +var doOrThrow = function (f) { + var ret; + var messages = buildmessage.capture(function () { + ret = f(); + }); + if (messages.hasMessages()) { + throw Error(messages.formatMessages()); + } + return ret; +}; + // These tests make some assumptions about the structure of stars: that there // are client and server programs inside programs/. @@ -143,7 +157,9 @@ var runTest = function () { var Fiber = require('fibers'); Fiber(function () { - release._setCurrentForOldTest(); + doOrThrow(function () { + release._setCurrentForOldTest(); + }); try { runTest(); diff --git a/tools/tests/old/test-bundler-npm.js b/tools/tests/old/test-bundler-npm.js index 3ebfdfc0f2..d9cc7ae6a1 100644 --- a/tools/tests/old/test-bundler-npm.js +++ b/tools/tests/old/test-bundler-npm.js @@ -9,6 +9,7 @@ var bundler = require('../../bundler.js'); var release = require('../../release.js'); var project = require('../../project.js'); var catalog = require('../../catalog.js'); +var buildmessage = require('../../buildmessage.js'); var meteorNpm = require('../../meteor-npm.js'); var lastTmpDir = null; @@ -27,11 +28,24 @@ var setAppDir = function (appDir) { files.getCurrentToolsDir(), 'packages')); } - catalog.complete.initialize({ - localPackageDirs: localPackageDirs + doOrThrow(function () { + catalog.complete.initialize({ + localPackageDirs: localPackageDirs + }); }); }; +var doOrThrow = function (f) { + var ret; + var messages = buildmessage.capture(function () { + ret = f(); + }); + if (messages.hasMessages()) { + throw Error(messages.formatMessages()); + } + return ret; +}; + /// /// TEST PACKAGE DIR /// @@ -39,7 +53,9 @@ var tmpPackageDirContainer = tmpDir(); var testPackageDir = path.join(tmpPackageDirContainer, 'test-package'); var reloadPackages = function () { - catalog.complete.refresh(); + doOrThrow(function () { + catalog.complete.refresh(); + }); }; var updateTestPackage = function (npmDependencies) { @@ -383,7 +399,9 @@ var runTest = function () { var Fiber = require('fibers'); Fiber(function () { setAppDir(appWithPackageDir); - release._setCurrentForOldTest(); + doOrThrow(function () { + release._setCurrentForOldTest(); + }); meteorNpm._printNpmCalls = true; try { diff --git a/tools/tests/old/test-bundler-options.js b/tools/tests/old/test-bundler-options.js index 1cc05b2325..c119c2b70c 100644 --- a/tools/tests/old/test-bundler-options.js +++ b/tools/tests/old/test-bundler-options.js @@ -29,11 +29,24 @@ var setAppDir = function (appDir) { files.getCurrentToolsDir(), 'packages')); } - catalog.complete.initialize({ - localPackageDirs: localPackageDirs + doOrThrow(function () { + catalog.complete.initialize({ + localPackageDirs: localPackageDirs + }); }); }; +var doOrThrow = function (f) { + var ret; + var messages = buildmessage.capture(function () { + ret = f(); + }); + if (messages.hasMessages()) { + throw Error(messages.formatMessages()); + } + return ret; +}; + var runTest = function () { var readManifest = function (tmpOutputDir) { return JSON.parse(fs.readFileSync( @@ -143,7 +156,9 @@ var runTest = function () { var Fiber = require('fibers'); Fiber(function () { - release._setCurrentForOldTest(); + doOrThrow(function () { + release._setCurrentForOldTest(); + }); try { runTest(); diff --git a/tools/tests/package-tests.js b/tools/tests/package-tests.js index cae3e2e030..4f9c161493 100644 --- a/tools/tests/package-tests.js +++ b/tools/tests/package-tests.js @@ -440,7 +440,7 @@ selftest.define("release track defaults to METEOR-CORE", var run = s.run("publish", "--create"); run.waitSecs(20); run.matchErr("Unknown release METEOR-CORE@" + releaseVersion); - run.expectExit(8); + run.expectExit(1); }); }); diff --git a/tools/tests/releases.js b/tools/tests/releases.js index 1ee2248cc4..603c28e9f6 100644 --- a/tools/tests/releases.js +++ b/tools/tests/releases.js @@ -20,7 +20,7 @@ selftest.define("springboard", ['checkout', 'net'], function () { var run; var toolsPackage; - selftest.captureAndThrow(function() { + selftest.doOrThrow(function() { toolsPackage = selftest.getToolsPackage(); }); var toolsVersion = toolsPackage.name + '@' + @@ -125,7 +125,7 @@ selftest.define("springboard", ['checkout', 'net'], function () { // hackhackhack. If we clean up the hackhackhackhack, then this does not need // the internets. (Or, to be more specific: our warehouse code tries to fetch // the packages from the internet. If we could fool it into using local packages -// instead, or think that it alreayd has the packages, it would be ok). +// instead, or think that it already has the packages, it would be ok). selftest.define("writing versions file", ['checkout', 'net'], function () { var s = new Sandbox({ warehouse: { @@ -136,8 +136,8 @@ selftest.define("writing versions file", ['checkout', 'net'], function () { var run; var toolsPackage; - selftest.captureAndThrow(function() { - toolsPackage = selftest.getToolsPackage(); + selftest.doOrThrow(function() { + toolsPackage = selftest.getToolsPackage(); }); var toolsVersion = toolsPackage.name + '@' + toolsPackage.version; @@ -167,9 +167,7 @@ selftest.define("writing versions file", ['checkout', 'net'], function () { run.expectExit(0); versions = s.read('.meteor/versions'); if (versions) { - selftest.expectEqual( - "Versions file written with --release.", - "Versions file NOT written with --release."); + selftest.fail("Versions file written with --release."); } // Update with --release. @@ -179,9 +177,7 @@ selftest.define("writing versions file", ['checkout', 'net'], function () { // version file should exist. versions = s.read('.meteor/versions'); if (!versions) { - selftest.expectEqual( - "Versions file NOT written after update", - "Versions file written after update."); + selftest.fail("Versions file NOT written after update"); } }); diff --git a/tools/tests/report-stats.js b/tools/tests/report-stats.js index c79dcd3ceb..4123289d13 100644 --- a/tools/tests/report-stats.js +++ b/tools/tests/report-stats.js @@ -36,7 +36,7 @@ var checkMeta = function (appPackages, sessionId, useFakeRelease) { if (useFakeRelease) { var toolsPackage; - selftest.captureAndThrow(function() { + selftest.doOrThrow(function() { toolsPackage = selftest.getToolsPackage(); }); expectedUserAgentInfo.meteorReleaseTrack = diff --git a/tools/tests/selftest-test.js b/tools/tests/selftest-test.js index 928a4865e3..9fab7003ef 100644 --- a/tools/tests/selftest-test.js +++ b/tools/tests/selftest-test.js @@ -12,7 +12,7 @@ selftest.define("selftest-from-warehouse", ['checkout'], function () { var run; var toolsPackage; - selftest.captureAndThrow(function() { + selftest.doOrThrow(function() { toolsPackage = selftest.getToolsPackage(); }); var toolsVersion = toolsPackage.name + '@' + diff --git a/tools/updater.js b/tools/updater.js index ae4b13a4e0..05a52c561c 100644 --- a/tools/updater.js +++ b/tools/updater.js @@ -33,16 +33,25 @@ exports.tryToDownloadUpdate = function (options) { }; var checkForUpdate = function (showBanner) { - // XXX we should ignore errors here, right? but still do the "can we update - // this app with a locally available release" check. - catalog.official.refresh(); + var messages = buildmessage.capture(function () { + catalog.official.refresh(); - if (!release.current.isProperRelease()) + if (!release.current.isProperRelease()) + return; + + updateMeteorToolSymlink(); + + maybeShowBanners(); + }); + + if (messages.hasMessages()) { + // Ignore, since running in the background. + // XXX unfortunately the "can't refresh" message still prints :( + // XXX But maybe if it's just a "we're offline" message we should keep + // going? In case we want to present the "hey there's a locally + // available recommended release? return; - - updateMeteorToolSymlink(); - - maybeShowBanners(); + } }; var maybeShowBanners = function () { @@ -120,6 +129,8 @@ var maybeShowBanners = function () { // Update ~/.meteor/meteor to point to the tool binary from the tools of the // latest recommended release on the default release track. var updateMeteorToolSymlink = function () { + buildmessage.assertInCapture(); + // Get the latest release version of METEOR-CORE. (*Always* of the default // track, not of whatever we happen to be running: we always want the tool // symlink to go to the default track.)