give npm some nice error messages too

This commit is contained in:
Geoff Schmidt
2013-04-25 02:28:10 -07:00
committed by David Glasser
parent ed18c6ffb5
commit 49ddf192b3
2 changed files with 96 additions and 30 deletions

View File

@@ -11,6 +11,7 @@ var path = require('path');
var fs = require('fs');
var cleanup = require(path.join(__dirname, 'cleanup.js'));
var files = require(path.join(__dirname, 'files.js'));
var buildmessage = require('./buildmessage.js');
var _ = require('underscore');
// if a user exits meteor while we're trying to create a .npm
@@ -22,6 +23,10 @@ cleanup.onExit(function () {
});
});
// Exception used internally to gracefully bail out of a npm run if
// something goes wrong
var NpmFailure = function () {};
var meteorNpm = exports;
_.extend(exports, {
_tmpDirs: [],
@@ -87,11 +92,23 @@ _.extend(exports, {
self._createFreshNpmDirectory(
packageName, newPackageNpmDir, packageNpmDir, npmDependencies, quiet);
}
} catch (e) {
if (e instanceof NpmFailure) {
// Something happened that was out of our control, but wasn't
// exactly unexpected (eg, no such npm package, no internet
// connection.) Handle it gracefully.
return false;
}
// Some other exception -- let it propagate.
throw e;
} finally {
if (fs.existsSync(newPackageNpmDir))
files.rm_recursive(newPackageNpmDir);
self._tmpDirs = _.without(self._tmpDirs, newPackageNpmDir);
}
return true;
},
// Return true if all of a package's npm dependencies are portable
@@ -238,25 +255,27 @@ _.extend(exports, {
);
},
// Returns object with keys 'stdout', 'stderr', and 'success' (true
// for clean exit with exit code 0, else false)
_execFileSync: function(file, args, opts) {
var self = this;
if (self._printNpmCalls) // only used by test_bundler.js
process.stdout.write('cd ' + opts.cwd + ' && ' + file + ' ' + args.join(' ') + ' ... ');
return Future.wrap(function(cb) {
execFile(file, args, opts, function (err, stdout, stderr) {
if (self._printNpmCalls)
console.log('done');
var future = new Future;
var result = {stdout: stdout, stderr: stderr};
// so that we can inspect stdout/stderr in case there was an error
if (err) {
err.stdout = stdout;
err.stderr = stderr;
}
cb(err, result);
execFile(file, args, opts, function (err, stdout, stderr) {
if (self._printNpmCalls)
console.log(err ? 'failed' : 'done');
future.return({
success: ! err,
stdout: stdout,
stderr: stderr
});
})().wait();
});
return future.wait();
},
_constructPackageJson: function(packageName, newPackageNpmDir, npmDependencies) {
@@ -313,10 +332,18 @@ _.extend(exports, {
// }
// }
_installedDependenciesTree: function(dir) {
return JSON.parse(
var result =
this._execFileSync(path.join(files.get_dev_bundle(), "bin", "npm"),
["ls", "--json"],
{cwd: dir}).stdout);
{cwd: dir});
if (result.success)
return JSON.parse(result.stdout);
console.log(result.stderr);
buildmessage.error("couldn't read npm version lock information");
// Recover by returning false from updateDependencies
throw new NpmFailure;
},
_shrinkwrappedDependenciesTree: function(dir) {
var shrinkwrapFile = fs.readFileSync(path.join(dir, 'npm-shrinkwrap.json'));
@@ -367,9 +394,27 @@ _.extend(exports, {
// We don't use npm.commands.install since we couldn't
// figure out how to silence all output (specifically the
// installed tree which is printed out with `console.log`)
this._execFileSync(path.join(files.get_dev_bundle(), "bin", "npm"),
["install", installArg],
{cwd: dir});
var result =
this._execFileSync(path.join(files.get_dev_bundle(), "bin", "npm"),
["install", installArg],
{cwd: dir});
if (! result.success) {
var pkgNotFound = "404 '" + name + "' is not in the npm registry";
var versionNotFound = "version not found: " + version;
if (result.stderr.match(new RegExp(pkgNotFound))) {
buildmessage.error("there is no npm package named '" + name + "'");
} else if (result.stderr.match(new RegExp(versionNotFound))) {
buildmessage.error(name + " version " + version + " " +
"is not available in the npm registry");
} else {
console.log(result.stderr);
buildmessage.error("couldn't install npm package");
}
// Recover by returning false from updateDependencies
throw new NpmFailure;
}
},
_installFromShrinkwrap: function(dir) {
@@ -379,9 +424,17 @@ _.extend(exports, {
this._ensureConnected();
// `npm install`, which reads npm-shrinkwrap.json
this._execFileSync(path.join(files.get_dev_bundle(), "bin", "npm"),
["install"],
{cwd: dir});
var result =
this._execFileSync(path.join(files.get_dev_bundle(), "bin", "npm"),
["install"], {cwd: dir});
if (! result.success) {
console.log(result.stderr);
buildmessage.error("couldn't install npm packages from npm-shrinkwrap");
// Recover by returning false from updateDependencies
throw new NpmFailure;
}
},
// ensure we can reach http://npmjs.org before we try to install
@@ -390,8 +443,10 @@ _.extend(exports, {
try {
files.getUrl("http://registry.npmjs.org");
} catch (e) {
throw new Error(
"Can't install npm dependencies. Check your internet connection and try again.");
buildmessage.error("Can't install npm dependencies. " +
"Are you connected to the internet?");
// Recover by returning false from updateDependencies
throw new NpmFailure;
}
},
@@ -403,9 +458,17 @@ _.extend(exports, {
// (the `silent` flag isn't piped in to the call to npm.commands.ls)
// 2. In various (non-deterministic?) cases we observed the
// npm-shrinkwrap.json file not being updated
self._execFileSync(path.join(files.get_dev_bundle(), "bin", "npm"),
["shrinkwrap"],
{cwd: dir});
var result =
this._execFileSync(path.join(files.get_dev_bundle(), "bin", "npm"),
["shrinkwrap"], {cwd: dir});
if (! result.sucess) {
console.log(result.stderr);
buildmessage.error("couldn't run `npm shrinkwrap`");
// Recover by returning false from updateDependencies
throw new NpmFailure;
}
self._minimizeShrinkwrap(dir);
},

View File

@@ -916,9 +916,10 @@ _.extend(Package.prototype, {
var nodeModulesPath = null;
if (options.npmDependencies) {
meteorNpm.ensureOnlyExactVersions(options.npmDependencies);
meteorNpm.updateDependencies(name, options.npmDir,
options.npmDependencies);
if (! meteorNpm.dependenciesArePortable(options.npmDir))
var npmOk =
meteorNpm.updateDependencies(name, options.npmDir,
options.npmDependencies);
if (npmOk && ! meteorNpm.dependenciesArePortable(options.npmDir))
isPortable = false;
nodeModulesPath = path.join(options.npmDir, 'node_modules');
}
@@ -1386,6 +1387,7 @@ _.extend(Package.prototype, {
if (npmDependencies) {
var packageNpmDir =
path.resolve(path.join(self.sourceRoot, '.npm'));
var npmOk = true;
if (! options.skipNpmUpdate) {
// go through a specialized npm dependencies update process,
@@ -1393,11 +1395,12 @@ _.extend(Package.prototype, {
// (sub)dependencies. this process also runs mostly safely
// multiple times in parallel (which could happen if you have
// two apps running locally using the same package)
meteorNpm.updateDependencies(name, packageNpmDir, npmDependencies);
npmOk = meteorNpm.updateDependencies(name, packageNpmDir,
npmDependencies);
}
nodeModulesPath = path.join(packageNpmDir, 'node_modules');
if (! meteorNpm.dependenciesArePortable(packageNpmDir))
if (npmOk && ! meteorNpm.dependenciesArePortable(packageNpmDir))
isPortable = false;
}