Skip to content

Commit

Permalink
eliminate process.exit from release.js and warehouse.js
Browse files Browse the repository at this point in the history
  • Loading branch information
gschmidt committed Jan 8, 2014
1 parent a1decc4 commit 8bded5c
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 94 deletions.
78 changes: 56 additions & 22 deletions tools/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,10 @@ 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()) {
var desiredRelease = release.forced ? release.current.name :
release.latestDownloaded();
if (release.current.name !== desiredRelease)
throw new main.SpringboardToRelease(desiredRelease); // does not return
}
if (! release.current.isCheckout() &&
release.current.name !== release.latestDownloaded() &&
! release.forced)
throw new main.SpringboardToLatestRelease;

var appPath;
if (options.args.length === 1)
Expand Down Expand Up @@ -315,15 +313,15 @@ main.registerCommand({
// The user asked for the latest release (well, they "asked for
// it" by not passing --release). We just downloaded a new
// release, so springboard to it. (Or, we were run in app with
// no release, so springboard to the lastest release we know
// no release, so springboard to the latest release we know
// about, whether we just download it or not.)
// #UpdateSpringboard
//
// (We used to springboard only if the tools version actually
// changed between the old and new releases. Now we do it
// unconditionally, because it's not a big deal to do it and it
// eliminates the complexity of the current release changing.)
throw new main.SpringboardToRelease(release.latestDownloaded());
throw new main.SpringboardToLatestRelease;
}
}

Expand Down Expand Up @@ -387,9 +385,6 @@ main.registerCommand({
return;
}

// Write the release to .meteor/release.
project.writeMeteorReleaseVersion(options.appDir, release.current.name);

// Find upgraders (in order) necessary to upgrade the app for the new
// release (new metadata file formats, etc, or maybe even updating renamed
// APIs).
Expand All @@ -398,21 +393,60 @@ main.registerCommand({
// all upgraders.
// * If the app didn't have a release because it was created by a
// checkout, don't run any upgraders.
if (appRelease !== "none") {
// NB! This call to release.load() may have to fetch the release
// from the server. If so, it will print progress messages and
// even kill the program if it doesn't get what it wants!
var oldUpgraders =
appRelease === null ? [] : release.load(appRelease).getUpgraders();
var upgraders = _.difference(release.current.getUpgraders(),
oldUpgraders);
_.each(upgraders, function (upgrader) {
require("./upgraders.js").runUpgrader(upgrader, options.appDir);
});
//
// We're going to need the list of upgraders from the old release
// (the release the app was using previously) to decide what
// upgraders to run. It's possible that we don't have it downloaded
// yet (if they checked out the project and immediately ran 'meteor
// update --release foo'), so it's important to do this before we
// actually update the project.
var upgradersToRun;
if (appRelease === "none") {
upgradersToRun = [];
} else {
var oldUpgraders;

if (appRelease === null) {
oldUpgraders = [];
} else {
try {
var oldRelease = release.load(appRelease);
} catch (e) {
if (e instanceof files.OfflineError) {
process.stderr.write(
"You need to be online to do this. Please check your internet connection.\n");
return 1;
}
if (e instanceof warehouse.NoSuchReleaseError) {
// In this situation it's tempting to just print a warning and
// skip the updaters, but I can't figure out how to explain
// what's happening to the user, so let's just do this.
process.stderr.write(
"This project says that it uses version " + name + " of Meteor, but you\n" +
"don't have that version of Meteor installed and the Meteor update servers\n" +
"don't have it either. Please edit the .meteor/release file in the project\n" +
"project and change it to a valid Meteor release.\n");
return 1;
}
throw e;
}
oldUpgraders = oldRelease.getUpgraders();
}

upgradersToRun = _.difference(release.current.getUpgraders(), oldUpgraders);
}

// Write the release to .meteor/release.
project.writeMeteorReleaseVersion(options.appDir, release.current.name);

// Now run the upgraders.
_.each(upgradersToRun, function (upgrader) {
require("./upgraders.js").runUpgrader(upgrader, options.appDir);
});

// This is the right spot to do any other changes we need to the app in
// order to update it for the new release.

console.log("%s: updated to Meteor %s.",
path.basename(options.appDir), release.current.name);

Expand Down
2 changes: 1 addition & 1 deletion tools/http-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ _.extend(exports, {
//
// (This has gone through a few refactors and it might be possible
// to fully roll it into httpHelpers.request() at this point.)
getUrl: function (urlOrOptions, callback) {
getUrl: function (urlOrOptions) {
try {
var result = httpHelpers.request(urlOrOptions);
} catch (e) {
Expand Down
84 changes: 56 additions & 28 deletions tools/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,8 @@ main.ShowUsage = function () {};
main.WaitForExit = function () {};

// Exception to throw from a command to exit, restart, and reinvoke
// the command with a different Meteor release.
main.SpringboardToRelease = function (releaseName) {
if (! releaseName)
throw new Error("didn't specify a release?");
this.releaseName = releaseName;
};
// the command with the latest available (downloaded) Meteor release.
main.SpringboardToLatestRelease = function () {};

// Register a command-line command.
//
Expand Down Expand Up @@ -130,7 +126,11 @@ main.SpringboardToRelease = function (releaseName) {
// create an app with a checkout (so that it has no release), and
// then run that app with released Meteor. Normally this just prints
// an error saying that you have to pick a release, but you can
// disable that by setting this flag to false.
// disable that by setting this flag to false. Even if you set this
// flag, we will still *attempt* to run the correct Meteor release
// just like we always do; it's just that in that one case, instead
// of bailing out with an error we will run your command with
// release.current === null.
// - raw: if true, option parsing is completely skipped (including
// --release and --help). To be activated the command will need to be
// literally the first argument, and it will need to do its own option
Expand Down Expand Up @@ -158,12 +158,12 @@ main.SpringboardToRelease = function (releaseName) {
// main.ShowUsage'. This will print usage info for the command and
// exit with status 1.
// - If you have started (for example) a subprocess or worker fiber
// and want to wait until it's finished to exit, throw
// main.WaitForExit. This will skip the call to process.exit and the
// and want to wait until it's finished to exit, 'throw new
// main.WaitForExit'. This will skip the call to process.exit and the
// program will keep running until node thinks that everything is
// done.
// - To quit, restart, and rerun the command with a different Meteor
// release, 'throw new mainSpringboardToRelease(releaseName)'.
// - To quit, restart, and rerun the command with a latest available
// (downloaded) Meteor release, 'throw new main.SpringboardToLatestRelease'.
//
// Commands should never call process.exit()! They should instead
// return an appropriate value. Not all commands obey that yet, but
Expand Down Expand Up @@ -537,10 +537,44 @@ Fiber(function () {
}

if (releaseName !== undefined) {
release.setCurrent(release.load(releaseName, {
packageDirs: packageDirs,
forApp: !! appDir
}), /* forced */ !! releaseOverride);
try {
var rel = release.load(releaseName, {
packageDirs: packageDirs
});
} catch (e) {
var name = releaseName;
if (e instanceof files.OfflineError) {
if (appDir) {
process.stderr.write(
"Sorry, this project uses Meteor " + name + ", which is not installed and\n"+
"could not be downloaded. Please check to make sure that you are online.\n");
} else {
process.stderr.write(
"Sorry, Meteor " + name + " is not installed and could not be downloaded.\n"+
"Please check to make sure that you are online.\n");
}
process.exit(1);
}

if (e instanceof warehouse.NoSuchReleaseError) {
if (releaseOverride) {
process.stderr.write(releaseVersion + ": unknown release.\n");
} else if (appDir) {
process.stderr.write(
"Problem! This project says that it uses version " + name + " of Meteor,\n" +
"but you don't have that version of Meteor installed and the Meteor update\n" +
"servers don't have it either. Please edit the .meteor/release file in the\n" +
"project and change it to a valid Meteor release.\n");
} else {
throw new Error("can't load latest release?");
}
process.exit(1);
}

throw e;
}

release.setCurrent(rel, /* forced */ !! releaseOverride);
}

// If we're not running the correct version of the tools for this
Expand Down Expand Up @@ -814,25 +848,19 @@ 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.SpringboardToRelease)
e === main.SpringboardToLatestRelease)
throw new Error(
"you meant 'throw new main.Foo', not 'throw main.Foo'");
if (e instanceof main.ShowUsage) {
process.stderr.write(longHelp(commandName) + "\n");
process.exit(1);
}
if (e instanceof main.SpringboardToRelease) {
// First we need to load the other release's metadata so that we
// can figure out the tools version that it uses. This could
// load the release from the network (in which case it will
// print progress messages and possibly even kill the program if
// something goes wrong!) But it won't do that if you only
// specify releases that are already downloaded in the
// warehouse, which is what you'll most likely be doing.
var otherRelease = release.load(e.releaseName);

// Good to go! Now a function call that doesn't return:
springboard(otherRelease.getToolsVersion(), otherRelease.name);
if (e instanceof main.SpringboardToLatestRelease) {
// Load the latest release's metadata so that we can figure out
// the tools version that it uses.
var latestRelease = release.load(release.latestDownloaded());
springboard(latestRelease.getToolsVersion(), latestRelease.name);
// (does not return)
}
if (e instanceof main.WaitForExit)
return;
Expand Down
37 changes: 12 additions & 25 deletions tools/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,22 @@ release.latestDownloaded = function () {
// may be called as many times as you like.
//
// This will fetch the release from the server if it isn't cached
// locally. If that happens it will print progress messages. (And if
// that fails, it will kill the process! XXX return gracefully)
// locally. If that happens it will print progress messages.
//
// Arguments:
// - name: release name to use. Or pass 'null' to use a checkout
// - options:
// - packageDirs: array of extra paths to search when searching for
// packages. They are searched in order and take precedence over
// anything in the release.
// - forApp: cosmetic effect only. If set, change the messages to
// reflect that we're downloading the release because an app needs
// it.
// - quiet: if the release has to be downloaded, don't print
// progress messages.
//
// Throws:
// - files.OfflineError if it was not possible to load the
// release because it's not locally cached and we're not online.
// - warehouse.NoSuchReleaseError if no release called 'name' exists
// in the world (confirmed with server).
//
// XXX packageDirs really should not be part of Release, and
// override() really should not be a method on a global singleton
Expand All @@ -172,26 +176,9 @@ release.load = function (name, options) {
}

// Go download the release if necessary.
var manifest;
try {
// XXX may print an error and kill the process if it doesn't get
// what it wants
manifest =
warehouse.ensureReleaseExistsAndReturnManifest(name);
} catch (e) {
if (! (e instanceof files.OfflineError))
throw e;
if (options.forApp) {
process.stderr.write(
"Sorry, this project uses Meteor " + version + ", which is not installed and\n"+
"could not be downloaded. Please check to make sure that you are online.\n");
} else {
process.stderr.write(
"Sorry, Meteor " + version + " is not installed and could not be downloaded.\n"+
"Please check to make sure that you are online.\n");
}
process.exit(1);
}
// (can throw files.OfflineError or warehouse.NoSuchReleaseError)
var manifest =
warehouse.ensureReleaseExistsAndReturnManifest(name, options.quiet);

return new Release({
name: name,
Expand Down
5 changes: 3 additions & 2 deletions tools/updater.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ if (testingUpdater)
config.setTestingUpdater(true);

/**
* Downloads the current manifest file and returns it via a callback (or
* null on error)
* Downloads the current manifest file and returns it. Throws
* files.OfflineError if we are offline, or throws some other
* exception if the server turned down our request.
*/
exports.getManifest = function () {
return httpHelpers.getUrl({
Expand Down
Loading

0 comments on commit 8bded5c

Please sign in to comment.