Files
meteor/tools/updater.js

187 lines
6.8 KiB
JavaScript

var path = require('path');
var fs = require('fs');
var _ = require('underscore');
var inFiber = require('./fiber-helpers.js').inFiber;
var files = require('./files.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');
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
* it before returning.
*
* options: showBanner
*/
var checkInProgress = false;
exports.tryToDownloadUpdate = function (options) {
options = options || {};
// Don't run more than one check simultaneously. It should be
// harmless but having two downloads happening simultaneously (and
// two sets of messages being printed) would be confusing.
if (checkInProgress)
return;
checkInProgress = true;
checkForUpdate(!!options.showBanner);
checkInProgress = false;
};
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();
if (!release.current.isProperRelease())
return;
updateMeteorToolSymlink();
maybeShowBanners();
};
var maybeShowBanners = function () {
var releaseData = release.current.getCatalogReleaseData();
var banner = releaseData.banner;
if (banner) {
var bannersShown = {};
try {
bannersShown = JSON.parse(
fs.readFileSync(config.getBannersShownFilename()));
} catch (e) {
// ... ignore
}
var shouldShowBanner = false;
if (_.has(bannersShown, release.current.name)) {
// XXX use EJSON so that we can just have Dates
var lastShown = new Date(bannersShown[release.current.name]);
var bannerUpdated = banner.lastUpdated ?
new Date(banner.lastUpdated) : new Date;
// XXX should the default really be "once ever" and not eg "once a week"?
if (lastShown < bannerUpdated) {
shouldShowBanner = true;
}
} else {
shouldShowBanner = true;
}
if (shouldShowBanner) {
// This banner is new; print it!
runLog.log("");
runLog.log(banner.text);
runLog.log("");
bannersShown[release.current.name] = new Date;
// XXX ick slightly racy
fs.writeFileSync(config.getBannersShownFilename(),
JSON.stringify(bannersShown, null, 2));
return;
}
}
// We now consider printing some simpler banners, if this isn't the latest
// release. But if the user specified a release manually with --release, we
// don't bother: we only want to tell users about ways to update *their app*.
if (release.forced)
return;
// Didn't print a banner? Maybe we have a patch release to recommend.
var track = release.current.getReleaseTrack();
var patchReleaseVersion = releaseData.patchReleaseVersion;
if (patchReleaseVersion) {
runLog.log("=> A patch (" +
utils.displayRelease(track, patchReleaseVersion) +
") for your current release is available!");
runLog.log(" Update this project now with 'meteor update --patch'.");
return;
}
// There's no patch (so no urgent exclamation!) but there may be something
// worth mentioning.
// XXX maybe run constraint solver to change the message depending on whether
// or not it will actually work?
var currentReleaseOrderKey = releaseData.orderKey || null;
var futureReleases = catalog.official.getSortedRecommendedReleaseVersions(
track, currentReleaseOrderKey);
if (futureReleases.length) {
runLog.log(
"=> " + utils.displayRelease(track, futureReleases[0]) +
" is available. Update this project with 'meteor update'.");
return;
}
};
// Update ~/.meteor0/meteor to point to the tool binary from the tools of the
// latest recommended release on the default release track.
var updateMeteorToolSymlink = function () {
// 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.)
var latestReleaseVersion = catalog.official.getDefaultReleaseVersion();
// Maybe you're on some random track with nothing recommended. That's OK.
if (!latestReleaseVersion)
return;
var latestRelease = catalog.official.getReleaseVersion(
latestReleaseVersion.track, latestReleaseVersion.version);
if (!latestRelease)
throw Error("latest release doesn't exist?");
if (!latestRelease.tool)
throw Error("latest release doesn't have a tool?");
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.
try {
tropohouse.default.maybeDownloadPackageForArchitectures(
{packageName: latestReleaseToolPackage,
version: latestReleaseToolVersion},
[archinfo.host()]);
_.each(latestRelease.packages, function (version, packageName) {
tropohouse.default.maybeDownloadPackageForArchitectures(
{ packageName: packageName, version: version },
['browser', archinfo.host()]);
});
} catch (err) {
console.log("Could not download latest release:",
latestRelease.track + "@" + latestRelease.version);
// Return, since we are running in the background.
return;
}
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
// counter: this is super weird and should never ever happen.
if (!toolRecord)
throw Error("latest release has no tool?");
console.log("XXX updating tool symlink for",
latestReleaseVersion.track + "@" + latestReleaseVersion.version);
tropohouse.default.replaceLatestMeteorSymlink(
path.join(relativeToolPath, toolRecord.path, 'meteor'));
}
};