From 54859e86d6bf384c13a4fd6c27d31e9f9bc71de2 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Wed, 13 Feb 2013 14:48:06 -0800 Subject: [PATCH] Fetch NPM dependencies when loading meteor release You can now be offline to use any package that's been installed --- lib/packages.js | 84 ++++++++++++++++++++++++------------------ lib/warehouse.js | 13 +++++-- tools/run-all-tests.sh | 8 ++-- 3 files changed, 61 insertions(+), 44 deletions(-) diff --git a/lib/packages.js b/lib/packages.js index d6cbb201d2..1f30c4d4af 100644 --- a/lib/packages.js +++ b/lib/packages.js @@ -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.) diff --git a/lib/warehouse.js b/lib/warehouse.js index b417fbd5d5..f4b997b330 100644 --- a/lib/warehouse.js +++ b/lib/warehouse.js @@ -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(); }); }, diff --git a/tools/run-all-tests.sh b/tools/run-all-tests.sh index 73672f913f..f4f00d0777 100755 --- a/tools/run-all-tests.sh +++ b/tools/run-all-tests.sh @@ -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