UNPKG

npm-remote-ls

Version:

Examine a package's dependency graph before you install it

123 lines (101 loc) 3.59 kB
var _ = require('lodash') var async = require('async') var semver = require('semver') var request = require('request') var once = require('once') var npa = require('npm-package-arg') // perform a recursive walk of a remote // npm package and determine its dependency // tree. function RemoteLS (opts) { var _this = this _.extend(this, { logger: console, development: true, // include dev dependencies. optional: true, // include optional dependencies. verbose: false, registry: require('registry-url')(), // URL of registry to ls. queue: async.queue(function (task, done) { _this._loadPackageJson(task, done) }, 8), tree: {}, flat: {} }, require('./config')(), opts) this.queue.pause() } RemoteLS.prototype._loadPackageJson = function (task, done) { var _this = this var name = task.name // account for scoped packages like @foo/bar var couchPackageName = name && npa(name).escapedName // wrap done so it's only called once // if done throws in _walkDependencies, it will be called again in catch below // we want to avoid "Callback was already called." from async done = once(done) request.get(this.registry.replace(/\/$/, '') + '/' + couchPackageName, {json: true}, function (err, res, obj) { if (err || (res.statusCode < 200 || res.statusCode >= 400)) { var message = res ? 'status = ' + res.statusCode : 'error = ' + err.message _this.logger.log( 'could not load ' + name + '@' + task.version + ' ' + message ) return done() } try { if (_this.verbose) console.log('loading:', name + '@' + task.version) _this._walkDependencies(task, obj, done) } catch (e) { _this.logger.log(e.message) done() } }) } RemoteLS.prototype._walkDependencies = function (task, packageJson, done) { var _this = this var version = this._guessVersion(task.version, packageJson) var dependencies = _.extend( {}, packageJson.versions[version].dependencies, this.optional ? packageJson.versions[version].optionalDependencies : {}, // show development dependencies if we're at the root, and deevelopment flag is true. (task.parent === this.tree && this.development) ? packageJson.versions[version].devDependencies : {} ) var fullName = packageJson.name + '@' + version var parent = task.parent[fullName] = {} if (_this.flat[fullName]) return done() else _this.flat[fullName] = true Object.keys(dependencies).forEach(function (name) { _this.queue.push({ name: name, version: dependencies[name], parent: parent }) }) done() } RemoteLS.prototype._guessVersion = function (versionString, packageJson) { if (versionString === 'latest') versionString = '*' var availableVersions = Object.keys(packageJson.versions) var version = semver.maxSatisfying(availableVersions, versionString, true) // check for prerelease-only versions if (!version && versionString === '*' && availableVersions.every(function (av) { return new semver.SemVer(av, true).prerelease.length })) { // just use latest then version = packageJson['dist-tags'] && packageJson['dist-tags'].latest } if (!version) throw Error('could not find a satisfactory version for string ' + versionString) else return version } RemoteLS.prototype.ls = function (name, version, callback) { var _this = this this.queue.push({ name: name, version: version, parent: this.tree }) this.queue.drain = function () { callback(_this.tree) } this.queue.resume() } module.exports = RemoteLS