diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 3b9cf53c10..457fd91633 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -3,19 +3,14 @@ var Fiber = Npm.require('fibers'); var Future = Npm.require(path.join('fibers', 'future')); Meteor._noYieldsAllowed = function (f) { - // "Fiber" and "yield" are both in the global namespace. The yield function is - // at both "yield" and "Fiber.yield". (It's also at require('fibers').yield - // but that is because require('fibers') === Fiber.) var savedYield = Fiber.yield; Fiber.yield = function () { throw new Error("Can't call yield in a noYieldsAllowed block!"); }; - global.yield = Fiber.yield; try { return f(); } finally { Fiber.yield = savedYield; - global.yield = savedYield; } }; diff --git a/tools/catalog.js b/tools/catalog.js index c478a07c61..31df68ca5f 100644 --- a/tools/catalog.js +++ b/tools/catalog.js @@ -232,6 +232,7 @@ _.extend(CompleteCatalog.prototype, { // project root path has not been initialized, we are probably running // outside of a project, and have nothing to look at for guidance. if (opts.ignoreProjectDeps || !project.rootDir) { + console.log("ignore project deps & resolve"); return self.resolver.resolve(deps, constr, resolverOpts); } diff --git a/tools/commands.js b/tools/commands.js index 91fdee127e..c3e9e5b5bf 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -308,8 +308,9 @@ main.registerCommand({ // at the end.) if (! release.current.isCheckout() && release.current.name !== release.latestDownloaded() && - ! release.forced) + ! release.forced) { throw new main.SpringboardToLatestRelease; + } var exampleDir = path.join(__dirname, '..', 'examples'); var examples = _.reject(fs.readdirSync(exampleDir), function (e) { @@ -425,18 +426,28 @@ main.registerCommand({ return 1; } - // Unless --release was passed (meaning that either the user asked for a - // particular release, or that we _just did_ this and springboarded at - // #UpdateSpringboard), go get the latest release and switch to it. (We - // already know what the latest release is because we refreshed the catalog - // above.) + // This is the release track we'll end up on --- either because it's + // the explicitly specified (with --release) track; or because we + // didn't specify a release and it's the app's current release (if we're + // in an app dir), since non-forced updates don't change the track. + // XXX better error checking on release.current.name + // XXX add a method to release.current + var releaseTrack = release.current.getReleaseTrack(); + + // Unless --release was passed (in which case we ought to already have + // springboarded to that release), go get the latest release and switch to + // it. (We already know what the latest release is because we refreshed the + // catalog above.) Note that after springboarding, we will hit this again + // (because springboarding to a specific release does NOT set release.forced), + // but it should be a no-op next time (unless there actually was a new latest + // release in the interim). if (! release.forced) { if (! release.current || - release.current.name !== release.latestDownloaded()) { + release.current.name !== release.latestDownloaded(releaseTrack)) { // The user asked for the latest release (well, they "asked for it" by not - // passing --release). We're not currently running the latest release (we - // may have even just learned about it). #UpdateSpringboard - throw new main.SpringboardToLatestRelease; + // passing --release). We're not currently running the latest release on + // this track (we may have even just learned about it). #UpdateSpringboard + throw new main.SpringboardToLatestRelease(releaseTrack); } } @@ -449,7 +460,7 @@ main.registerCommand({ // If we're not in an app, then we're done (other than maybe printing some // stuff). if (! options.appDir) { - if (release.forced) { + if (release.forced || process.env.METEOR_SPRINGBOARD_RELEASE) { // We get here if: // 1) the user ran 'meteor update' and we found a new version // 2) the user ran 'meteor update --release xyz' (regardless of @@ -457,7 +468,9 @@ main.registerCommand({ // // In case (1), we downloaded and installed the update and then // we springboarded (at #UpdateSpringboard above), causing - // release.forced to be true. + // $METEOR_SPRINGBOARD_RELEASE to be true. + // XXX probably should have a better interface than looking directly + // at the env var here // // In case (2), we downloaded, installed, and springboarded to // the requested release in the initialization code, before the @@ -487,12 +500,6 @@ main.registerCommand({ // Otherwise, we have to upgrade the app too, if the release changed. var appRelease = project.getMeteorReleaseVersion(); if (appRelease !== null && appRelease === release.current.name) { - // Note that in this case, release.forced is true iff --release was actually - // passed on the command-line: #UpdateSpringboard can't have occured. Why? - // Well, if the user didn't specify --release but we're in an app, then - // release.current.name must have been taken from the app, and - // #UpdateSpringboard only happens if needs to try to change the app - // release, and this is the message for not needing to change the release. var maybeTheLatestRelease = release.forced ? "" : ", the latest release"; var maybeOnThisComputer = couldNotContactServer ? "\ninstalled on this computer" : ""; @@ -504,6 +511,7 @@ main.registerCommand({ // OK, let's figure out what release fits with our package constraints! +<<<<<<< HEAD // XXX this will actually be a loop over possible releases in the non-force // case // XXX better error checking on name @@ -534,76 +542,148 @@ main.registerCommand({ // XXX did we have to change some package versions? we should probably // mention that fact. // XXX error handling. - - // Find upgraders (in order) necessary to upgrade the app for the new - // release (new metadata file formats, etc, or maybe even updating renamed - // APIs). - // - // * If this is a pre-engine app with no .meteor/release file, run - // all upgraders. - // * If the app didn't have a release because it was created by a - // checkout, don't run any upgraders. - // - // 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 = []; +======= + var releaseVersionsToTry; + if (release.forced) { + releaseVersionsToTry = [release.current.getReleaseVersion()]; } 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 " + appRelease + " 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(); + // XXX clean up all this splitty stuff + var appReleaseInfo = catalog.getReleaseVersion( + appRelease.split('@')[0], appRelease.split('@')[1]); + var appOrderKey = (appReleaseInfo && appReleaseInfo.orderKey) || null; + releaseVersionsToTry = catalog.getSortedRecommendedReleaseVersions( + releaseTrack, appOrderKey); + if (!releaseVersionsToTry.length) { + // XXX make error better, and make sure that the "already there" error + // above truly does cover every other case + var maybeOnThisComputer = + couldNotContactServer ? "\ninstalled on this computer" : ""; + console.log( +"This project is already at Meteor %s, which is newer than the latest release%s.", + appRelease, maybeOnThisComputer); + return; } - - upgradersToRun = _.difference(release.current.getUpgraders(), oldUpgraders); } - // Write the release to .meteor/release. - project.writeMeteorReleaseVersion(release.current.name); - - // Now run the upgraders. - _.each(upgradersToRun, function (upgrader) { - require("./upgraders.js").runUpgrader(upgrader, options.appDir); + var solutionPackageVersions = null; + var directDependencies = project.getDirectDependencies(options.appDir); + var previousVersions = project.getIndirectDependencies(options.appDir); + var solutionReleaseVersion = _.find(releaseVersionsToTry, function (versionToTry) { + var releaseRecord = catalog.getReleaseVersion(releaseTrack, versionToTry); + if (!releaseRecord) + throw Error("missing release record?"); + var constraints = project.combinedConstraints( + directDependencies, releaseRecord.packages); + try { + solutionPackageVersions = catalog.resolveConstraints( + constraints, { previousSolution: previousVersions }); + } catch (e) { + // XXX we should make the error handling explicitly detectable, and not + // actually mention failures that are recoverable + process.stderr.write( + "XXX Update to release " + releaseTrack + + "@" + versionToTry + " impossible: " + e.message + "\n"); + return false; + } + return true; }); + if (!solutionReleaseVersion) { + // XXX put an error here when we stop doing an error on every failure above + return 1; + } + + var solutionReleaseName = releaseTrack + '@' + solutionReleaseVersion; + + // We could at this point springboard to solutionRelease (which is no newer + // than the release we are currently running), but there's no clear advantage + // to this yet. The main reason might be if we decide to delete some + // backward-compatibility code which knows how to deal with an older release, + // but if we actually do that, we can change this code to add the extra + // springboard at that time. + + + // XXX did we have to change some package versions? we should probably + // mention that fact. + + // XXX reimplement upgraders (or don't, until we need them). + // // Find upgraders (in order) necessary to upgrade the app for the new + // // release (new metadata file formats, etc, or maybe even updating renamed + // // APIs). + // // + // // * If this is a pre-engine app with no .meteor/release file, run + // // all upgraders. + // // * If the app didn't have a release because it was created by a + // // checkout, don't run any upgraders. + // // + // // 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 " + appRelease + " 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(); + // } + + // // XXX release.current needs to be replaced with solutionReleaseName + // upgradersToRun = _.difference(release.current.getUpgraders(), oldUpgraders); + // } + + // Write the new versions to .meteor/packages and .meteor/versions. + project.setDependencies(options.appDir, directDependencies.appDeps, + solutionPackageVersions); + + // Write the release to .meteor/release. + project.writeMeteorReleaseVersion(solutionReleaseName); + + // XXX redo upgrader support + // // 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); + path.basename(options.appDir), solutionReleaseName); // Print any notices relevant to this upgrade. // XXX This doesn't include package-specific notices for packages that // are included transitively (eg, packages used by app packages). var packages = project.getConstraints(); + + // var packages = project.getPackages(options.appDir); // XXX reimplement "notices" for tropohouse // warehouse.printNotices(appRelease, release.current.name, packages); }); @@ -839,6 +919,7 @@ main.registerCommand({ } }); + // Get the contents of our versions file, we will want them in order to remove // to the user what we removed. var versions = project.getVersions(); diff --git a/tools/config.js b/tools/config.js index cf3e0d9e5e..1aa02412bb 100644 --- a/tools/config.js +++ b/tools/config.js @@ -124,9 +124,7 @@ _.extend(exports, { return process.env.METEOR_PACKAGE_SERVER_URL; var host = config.getPackageServerDomain(); - console.log("XXX: currently using http, not https for package server."); - return "http://" + host; - // return addScheme(host); + return addScheme(host); }, getPackageServerDomain: function () { diff --git a/tools/fiber-helpers.js b/tools/fiber-helpers.js index 9cc396d547..9b94c4c3e8 100644 --- a/tools/fiber-helpers.js +++ b/tools/fiber-helpers.js @@ -76,3 +76,16 @@ exports.waitForOne = function (/* futures */) { return combinedFuture.wait(); }; + + +exports.noYieldsAllowed = function (f) { + var savedYield = Fiber.yield; + Fiber.yield = function () { + throw new Error("Can't call yield in a noYieldsAllowed block!"); + }; + try { + return f(); + } finally { + Fiber.yield = savedYield; + } +}; diff --git a/tools/files.js b/tools/files.js index b7cac8f490..3b712d551d 100644 --- a/tools/files.js +++ b/tools/files.js @@ -595,6 +595,14 @@ files.writeFileAtomically = function (filename, contents) { fs.renameSync(tmpFile, filename); }; +// Like fs.symlinkSync, but creates a temporay link and renames it over the +// file; this means it works even if the file already exists. +files.symlinkOverSync = function (linkText, file) { + var tmpSymlink = file + ".tmp" + utils.randomToken(); + fs.symlinkSync(linkText, tmpSymlink); + fs.renameSync(tmpSymlink, file); +}; + // Run a program synchronously and, assuming it returns success (0), // return whatever it wrote to stdout, as a string. Otherwise (if it // did not exit gracefully and return 0) return null. As node has diff --git a/tools/main.js b/tools/main.js index 8ff6787500..6d5b47f518 100644 --- a/tools/main.js +++ b/tools/main.js @@ -80,7 +80,11 @@ main.WaitForExit = function () {}; // Exception to throw from a command to exit, restart, and reinvoke // the command with the latest available (downloaded) Meteor release. -main.SpringboardToLatestRelease = function () {}; +// If track is specified, it uses the latest available in the given +// track instead of the default track. +main.SpringboardToLatestRelease = function (track) { + this.track = track; +}; // Register a command-line command. // @@ -632,6 +636,7 @@ Fiber(function () { // #ImprovingCrossVersionOptionParsing. var releaseOverride = null; + var releaseForced = false; if (_.has(rawOptions, '--release')) { if (rawOptions['--release'].length > 1) { process.stderr.write( @@ -640,6 +645,7 @@ Fiber(function () { process.exit(1); } releaseOverride = rawOptions['--release'][0]; + releaseForced = true; if (! releaseOverride) { process.stderr.write( "The --release option needs a value.\n" + @@ -650,6 +656,12 @@ Fiber(function () { } if (_.has(process.env, 'METEOR_SPRINGBOARD_RELEASE')) { // See #SpringboardEnvironmentVar + // Note that this does *NOT* cause release.forced to be true. + // release.forced should only be set when the user actually + // ran with --release, not just because (eg) they ran + // 'meteor update' and we springboarded to the latest release. + // (It's important that 'meteor update' be able to tell these + // conditions apart even after the springboard!) releaseOverride = process.env['METEOR_SPRINGBOARD_RELEASE']; } @@ -702,7 +714,7 @@ Fiber(function () { } } else { // Run outside an app dir with no --release flag. Use the latest - // release we know about. + // release we know about (in the default track). releaseName = release.latestDownloaded(); } } @@ -743,7 +755,7 @@ Fiber(function () { throw e; } - release.setCurrent(rel, /* forced */ !! releaseOverride); + release.setCurrent(rel, releaseForced); } // If we're not running the correct version of the tools for this @@ -1054,7 +1066,7 @@ commandName + ": You're not in a Meteor package directory.\n"); 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()); + var latestRelease = release.load(release.latestDownloaded(e.track)); springboard(latestRelease, latestRelease.name); // (does not return) } diff --git a/tools/project.js b/tools/project.js index dbc9f8e666..4d4edad665 100644 --- a/tools/project.js +++ b/tools/project.js @@ -100,6 +100,7 @@ var Project = function () { self._depsUpToDate = false; }; +<<<<<<< HEAD _.extend(Project.prototype, { // Set a given root directory as the project's root directory. Figure out all // relevant file paths and read in data that is independent of the constraint diff --git a/tools/release.js b/tools/release.js index 6969dd49dc..5781c159a5 100644 --- a/tools/release.js +++ b/tools/release.js @@ -37,6 +37,20 @@ _.extend(Release.prototype, { return this.name === null; }, + getReleaseTrack: function () { + var self = this; + if (! self.isProperRelease()) + throw new Error("not a proper release?"); + return self.name.split('@')[0]; + }, + + getReleaseVersion: function () { + var self = this; + if (! self.isProperRelease()) + throw new Error("not a proper release?"); + return self.name.split('@')[1]; + }, + // Return the package name for the command-line tools that this release // uses. Valid only for proper releases. getToolsPackage: function () { @@ -161,14 +175,17 @@ release.usingRightReleaseForApp = function () { // Return the name of the latest release that is downloaded and ready // for use. May not be called when running from a checkout. -release.latestDownloaded = function () { +// 'track' is optional (it defaults to the default track). +release.latestDownloaded = function (track) { if (! files.usesWarehouse()) throw new Error("called from checkout?"); // For self-test only. if (process.env.METEOR_TEST_LATEST_RELEASE) return process.env.METEOR_TEST_LATEST_RELEASE; + var defaultRelease = catalog.official.getDefaultReleaseVersion(); + if (!defaultRelease) { throw new Error("no latest release available?"); } diff --git a/tools/tests/add-package.js b/tools/tests/add-package.js index 673061280a..2f4dee4306 100644 --- a/tools/tests/add-package.js +++ b/tools/tests/add-package.js @@ -167,7 +167,6 @@ selftest.define("add packages", function () { run = s.run("--once"); run = s.run("add", "say-something@1.0.0", "--offline-catalog"); - run.match("Successfully added"); checkPackages(s, diff --git a/tools/tropohouse.js b/tools/tropohouse.js index efc73cb74c..372768584c 100644 --- a/tools/tropohouse.js +++ b/tools/tropohouse.js @@ -184,5 +184,17 @@ _.extend(exports.Tropohouse.prototype, { unipackage.saveToPath(packageDir); return true; + }, + + latestMeteorSymlink: function () { + var self = this; + var path = path.join(self.root, 'meteor'); + return fs.readlinkSync(path); + }, + + replaceLatestMeteorSymlink: function (linkText) { + var self = this; + var path = path.join(self.root, 'meteor'); + files.symlinkOverSync(linkText, path); } }); diff --git a/tools/unipackage.js b/tools/unipackage.js index d1bcd8b39d..092d708a0a 100644 --- a/tools/unipackage.js +++ b/tools/unipackage.js @@ -336,7 +336,7 @@ _.extend(Unipackage.prototype, { // recover by returning by no builds return null; } - return _.where(self.builds, { arch: chosenArch })[0]; + return _.findWhere(self.builds, { arch: chosenArch }); }, // Load this package's plugins into memory, if they haven't already diff --git a/tools/updater.js b/tools/updater.js index f0a0dd6245..fd16d44e87 100644 --- a/tools/updater.js +++ b/tools/updater.js @@ -1,31 +1,15 @@ +var path = require('path'); var inFiber = require('./fiber-helpers.js').inFiber; var files = require('./files.js'); -var warehouse = require('./warehouse.js'); +var tropohouse = require('./tropohouse.js'); var httpHelpers = require('./http-helpers.js'); var config = require('./config.js'); var release = require('./release.js'); var runLog = require('./run-log.js'); - -/** - * 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 () { - // Automated self-test support. You can set an environment variable - // to stub out the manifest fetch with a particular value, or to - // throw OfflineError. - if (process.env.METEOR_TEST_UPDATE_MANIFEST === "offline") - throw new files.OfflineError(new Error("scripted failure for tests")); - if (process.env.METEOR_TEST_UPDATE_MANIFEST) - return JSON.parse(process.env.METEOR_TEST_UPDATE_MANIFEST); - - return httpHelpers.getUrl({ - url: config.getUpdateManifestUrl(), - json: true, - useSessionHeader: true - }); -}; +var catalog = require('./catalog.js'); +var archinfo = require('./archinfo.js'); +var unipackage = require('./unipackage.js'); +var utils = require('./utils.js'); /** * Check to see if an update is available. If so, download and install @@ -42,69 +26,107 @@ exports.tryToDownloadUpdate = function (options) { if (checkInProgress) return; checkInProgress = true; - check(!!options.showBanner); + checkForUpdate(!!options.showBanner); checkInProgress = false; }; -var check = function (showBanner) { - var manifest = null; - try { - manifest = exports.getManifest(); - } catch (e) { - // Ignore error (eg, offline), but still do the "can we update this app - // with a locally available release" check. - } +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.serverCatalog.refresh(true); - if (!files.usesWarehouse()) + if (!release.isProperRelease()) return; - // XXX in the future support release channels other than stable - var manifestLatestRelease = - manifest && manifest.releases && manifest.releases.stable && - manifest.releases.stable.version; - var localLatestRelease = warehouse.latestRelease(); - if (manifestLatestRelease && manifestLatestRelease !== localLatestRelease) { - // The manifest is telling us about a release that isn't our latest - // release! First, print a banner... but only if we've never printed a - // banner for this release before. (Or, well... only if this release isn't - // the last release which has had a banner printed.) - if (manifest.releases.stable.banner && - warehouse.lastPrintedBannerRelease() !== manifestLatestRelease) { - if (showBanner) { - runLog.log(""); - runLog.log(manifest.releases.stable.banner); - runLog.log(""); - } - warehouse.writeLastPrintedBannerRelease(manifestLatestRelease); - } else { - // Already printed this banner, or maybe there is no banner. - if (showBanner) { - runLog.log("=> Meteor " + manifestLatestRelease + - " is being downloaded in the background."); - } - } - warehouse.fetchLatestRelease(); - // We should now have fetched the latest release, which *probably* is - // manifestLatestRelease. As long as it's changed from the one it was - // before we tried to fetch it, print that out. - var newLatestRelease = warehouse.latestRelease(); - if (showBanner && newLatestRelease !== localLatestRelease) { - runLog.log( - "=> Meteor " + newLatestRelease + - " is available. Update this project with 'meteor update'."); - } + var currentReleaseTrack = release.current.getTrack(); + var latestRelease = catalog.serverCatalog.getDefaultRelease( + currentReleaseTrack); + // Maybe you're on some random track with nothing recommended. That's OK. + if (!latestRelease) return; + var latestReleaseToolParts = latestRelease.tool.split('@'); + var latestReleaseToolPackage = latestReleaseToolParts[0]; + var latestReleaseToolVersion = latestReleaseToolParts[1]; + var relativeToolPath = tropohouse.default.packagePath( + latestReleaseToolPackage, latestReleaseToolVersion, true); + + var localLatestReleaseLink = tropohouse.default.latestMeteorSymlink(); + if (utils.startsWith(localLatestReleaseLink, relativeToolPath + path.sep)) { + // The latest release from the catalog is not where the ~/.meteor0/meteor + // symlink points to. Let's make sure we have that release on disk, + // and then update the symlink. + // XXX download the packages too? + tropohouse.default.maybeDownloadPackageForArchitectures( + {packageName: latestReleaseToolPackage, + version: latestReleaseToolVersion}, + [archinfo.host()]); + + var toolUnipackage = new unipackage.Unipackage; + toolUnipackage.initFromPath( + latestReleaseToolPackage, + tropohouse.default.packagePath(latestReleaseToolPackage, + latestReleaseToolVersion)); + var toolRecord = _.findWhere(toolUnipackage.toolsOnDisk, + {arch: archinfo.host()}); + // XXX maybe we shouldn't throw from this background thing + if (!toolRecord) + throw Error("latest release has no tool?"); + + console.log("XXX updating tool symlink for", + latestRelease.track + "@" + latestRelease.version); + + tropohouse.default.replaceLatestMeteorSymlink( + path.join(relativeToolPath, toolRecord.path, 'meteor')); } - // We didn't do a global update (or we're not online), but do we need to - // update this app? Specifically: is our local latest release something - // other than this app's release, and the user didn't specify a specific - // release at the command line with --release? - if (showBanner && - localLatestRelease !== release.current.name && - ! release.forced) { - runLog.log( - "=> Meteor " + localLatestRelease + - " is available. Update this project with 'meteor update'."); - } + // XXX print banners + + // var manifestLatestRelease = + // manifest && manifest.releases && manifest.releases.stable && + // manifest.releases.stable.version; + // var localLatestRelease = warehouse.latestRelease(); + // if (manifestLatestRelease && manifestLatestRelease !== localLatestRelease) { + // // The manifest is telling us about a release that isn't our latest + // // release! First, print a banner... but only if we've never printed a + // // banner for this release before. (Or, well... only if this release isn't + // // the last release which has had a banner printed.) + // if (manifest.releases.stable.banner && + // warehouse.lastPrintedBannerRelease() !== manifestLatestRelease) { + // if (showBanner) { + // runLog.log(""); + // runLog.log(manifest.releases.stable.banner); + // runLog.log(""); + // } + // warehouse.writeLastPrintedBannerRelease(manifestLatestRelease); + // } else { + // // Already printed this banner, or maybe there is no banner. + // if (showBanner) { + // runLog.log("=> Meteor " + manifestLatestRelease + + // " is being downloaded in the background."); + // } + // } + // warehouse.fetchLatestRelease(); + // // We should now have fetched the latest release, which *probably* is + // // manifestLatestRelease. As long as it's changed from the one it was + // // before we tried to fetch it, print that out. + // var newLatestRelease = warehouse.latestRelease(); + // if (showBanner && newLatestRelease !== localLatestRelease) { + // runLog.log( + // "=> Meteor " + newLatestRelease + + // " is available. Update this project with 'meteor update'."); + // } + // return; + // } + + // // We didn't do a global update (or we're not online), but do we need to + // // update this app? Specifically: is our local latest release something + // // other than this app's release, and the user didn't specify a specific + // // release at the command line with --release? + // if (showBanner && + // localLatestRelease !== release.current.name && + // ! release.forced) { + // runLog.log( + // "=> Meteor " + localLatestRelease + + // " is available. Update this project with 'meteor update'."); + // } }; diff --git a/tools/utils.js b/tools/utils.js index 135f0097ca..f25bb6ab05 100644 --- a/tools/utils.js +++ b/tools/utils.js @@ -273,4 +273,9 @@ exports.isDirectory = function (dir) { return false; } return stats.isDirectory(); + +// XXX from Underscore.String (http://epeli.github.com/underscore.string/) +exports.startsWith = function(str, starts) { + return str.length >= starts.length && + str.substring(0, starts.length) === starts; }; diff --git a/tools/warehouse.js b/tools/warehouse.js index 3217a54eed..577831ecf6 100644 --- a/tools/warehouse.js +++ b/tools/warehouse.js @@ -34,14 +34,6 @@ var fiberHelpers = require('./fiber-helpers.js'); var WAREHOUSE_URLBASE = 'https://warehouse.meteor.com'; -// Like fs.symlinkSync, but creates a temporay link and renames it over the -// file; this means it works even if the file already exists. -var symlinkOverSync = function (linkText, file) { - var tmpSymlink = file + ".tmp" + utils.randomToken(); - fs.symlinkSync(linkText, tmpSymlink); - fs.renameSync(tmpSymlink, file); -}; - var warehouse = exports; _.extend(warehouse, { // An exception meaning that you asked for a release that doesn't @@ -122,37 +114,6 @@ _.extend(warehouse, { } }, - // returns true if we updated the latest symlink - // XXX make errors prettier - fetchLatestRelease: function (options) { - options = options || {}; - var manifest = updater.getManifest(); - - // XXX in the future support release channels other than stable - var releaseName = manifest && manifest.releases && - manifest.releases.stable && manifest.releases.stable.version; - if (! releaseName) - throw new Error("no stable release found?"); - - var latestReleaseManifest = warehouse._populateWarehouseForRelease( - releaseName, !!options.showInstalling); - - // First, make sure the latest tools symlink reflects the latest installed - // release. - if (latestReleaseManifest.tools !== warehouse.latestTools()) { - symlinkOverSync(latestReleaseManifest.tools, - warehouse._latestToolsSymlinkPath()); - } - - var storedLatestRelease = warehouse.latestRelease(); - if (storedLatestRelease === releaseName) - return false; - - symlinkOverSync(releaseName + '.release.json', - warehouse._latestReleaseSymlinkPath()); - return true; - }, - packageExistsInWarehouse: function (name, version) { // A package exists if its directory exists. (We used to look for a // particular file name ("package.js") inside the directory, but since we