Fetch NPM dependencies when loading meteor release

You can now be offline to use any package that's been installed
This commit is contained in:
Avital Oliver
2013-02-13 14:48:06 -08:00
committed by David Glasser
parent 662754a27d
commit 54859e86d6
3 changed files with 61 additions and 44 deletions

View File

@@ -123,6 +123,38 @@ var Package = function () {
};
_.extend(Package.prototype, {
// loads a package's package.js file into memory, using
// runInThisContext. Wraps the contents of package.js in a closure,
// supplying pseudo-globals 'Package' and 'Npm'.
initFromPackageDir: function (name, dir) {
var self = this;
self.name = name;
self.source_root = dir;
self.serve_root = path.join(path.sep, 'packages', name);
if (!fs.existsSync(self.source_root))
throw new Error("The package named " + self.name + " does not exist.");
// We use string concatenation to load package.js rather than
// directly `require`ing it because that allows us to simplify the
// package API (such as supporting Package.on_use rather than
// something like Package.current().on_use)
var fullpath = path.join(self.source_root, 'package.js');
var code = fs.readFileSync(fullpath).toString();
// \n is necessary in case final line is a //-comment
var wrapped = "(function(Package,Npm){" + code + "\n})";
// See #runInThisContext
//
// XXX it'd be nice to runInNewContext so that the package
// setup code can't mess with our globals, but objects that
// come out of runInNewContext have bizarro antimatter
// prototype chains and break 'instanceof Array'. for now,
// steer clear
var func = require('vm').runInThisContext(wrapped, fullpath, true);
func(self.packageFacade, self.npmFacade);
},
// Searches:
// - $PACKAGE_DIRS (colon-separated)
// - $METEOR/packages
@@ -130,7 +162,7 @@ _.extend(Package.prototype, {
initFromLocalPackages: function (name) {
var packageDir = packages.directoryForLocalPackage(name);
if (packageDir) {
this._initFromPackageDir(name, packageDir);
this.initFromPackageDir(name, packageDir);
return true;
} else {
return false;
@@ -138,7 +170,7 @@ _.extend(Package.prototype, {
},
initFromWarehouse: function (name, version) {
this._initFromPackageDir(
this.initFromPackageDir(
name,
path.join(warehouse.getWarehouseDir(), 'packages', name, version));
},
@@ -237,44 +269,17 @@ _.extend(Package.prototype, {
//
// @param npmDependencies {Object} eg {gcd: "0.0.0", tar: "0.1.14"}
installNpmDependencies: function() {
// go through a specialized npm dependencies update process, ensuring
// we don't get new versions of any (sub)dependencies. this process
// also runs safely multiple times in parallel (which could happen if you
// have two apps running locally using the same package)
meteorNpm.updateDependencies(this.name, this.npmDir(), this.npmDependencies);
if (this.npmDependencies) {
// go through a specialized npm dependencies update process, ensuring
// we don't get new versions of any (sub)dependencies. this process
// also runs safely multiple times in parallel (which could happen if you
// have two apps running locally using the same package)
meteorNpm.updateDependencies(this.name, this.npmDir(), this.npmDependencies);
}
},
npmDir: function () {
return path.join(this.source_root, '.npm');
},
_initFromPackageDir: function (name, dir) {
var self = this;
self.name = name;
self.source_root = dir;
self.serve_root = path.join(path.sep, 'packages', name);
if (!fs.existsSync(self.source_root))
throw new Error("The package named " + self.name + " does not exist.");
// We use string concatenation to load package.js rather than
// directly `require`ing it because that allows us to simplify the
// package API (such as supporting Package.on_use rather than
// something like Package.current().on_use)
var fullpath = path.join(self.source_root, 'package.js');
var code = fs.readFileSync(fullpath).toString();
// \n is necessary in case final line is a //-comment
var wrapped = "(function(Package,Npm){" + code + "\n})";
// See #runInThisContext
//
// XXX it'd be nice to runInNewContext so that the package
// setup code can't mess with our globals, but objects that
// come out of runInNewContext have bizarro antimatter
// prototype chains and break 'instanceof Array'. for now,
// steer clear
var func = require('vm').runInThisContext(wrapped, fullpath, true);
func(self.packageFacade, self.npmFacade);
}
});
@@ -302,6 +307,13 @@ var packages = module.exports = {
return loadedPackages[name];
},
// load a package directly from a directory. don't cache.
loadFromDir: function(name, packageDir) {
var pkg = new Package;
pkg.initFromPackageDir(name, packageDir);
return pkg;
},
// get a package that represents an app. (ignore_files is optional
// and if given, it should be an array of regexps for filenames to
// ignore when scanning for source files.)

View File

@@ -144,11 +144,11 @@ var warehouse = module.exports = {
}
},
// @param packages {Object} eg {"less": "0.5.0"}
_populateWarehouseWithPackages: function(packages) {
// @param packagesToPopulate {Object} eg {"less": "0.5.0"}
_populateWarehouseWithPackages: function(packagesToPopulate) {
var Future = require('fibers/future');
var futures = [];
_.each(packages, function (version, name) {
_.each(packagesToPopulate, function (version, name) {
var packageDir = path.join(warehouse.getWarehouseDir(), 'packages', name, version);
var packageUrl = PACKAGES_URLBASE + "/packages/" + name + "/" +
name + '-' + version + ".tar.gz";
@@ -157,7 +157,7 @@ var warehouse = module.exports = {
futures.push(Future.wrap(function (cb) {
files.getUrl({url: packageUrl, encoding: null}, function (error, result) {
if (! error && result)
result = { buffer: result, packageDir: packageDir };
result = { buffer: result, packageDir: packageDir, name: name };
cb(error, result);
});
})());
@@ -173,6 +173,11 @@ var warehouse = module.exports = {
files.mkdir_p(tmpPackageDir);
files.extractTarGz(result.buffer, tmpPackageDir);
fs.renameSync(tmpPackageDir, result.packageDir);
// fetch npm dependencies
var packages = require(path.join(__dirname, "packages.js")); // load late to work around circular require
var pkg = packages.loadFromDir(result.name, result.packageDir);
pkg.installNpmDependencies();
});
},

View File

@@ -7,10 +7,6 @@ trap 'echo FAILED' EXIT
set -e -x
## Test the Meteor CLI from a checkout
./cli-test.sh
## Test the Meteor CLI from an installed engine (tests loading packages
## into the warehouse)
TMPDIR=$(mktemp -d -t meteor-installed-cli-tests)
@@ -39,6 +35,10 @@ export TEST_WAREHOUSE_DIR=$(mktemp -d -t meteor-installed-cli-tests-warehouse) #
PACKAGE_DIRS=$METEOR_DIR/tools/cli-test-packages/ $METEOR_DIR/meteor test-packages --once --release=0.0.1
## Test the Meteor CLI from a checkout. We do this last because it is least likely to fail.
./cli-test.sh
## Done
trap - EXIT
echo PASSED