aboutsummaryrefslogtreecommitdiff
path: root/deps/npm/lib
diff options
context:
space:
mode:
Diffstat (limited to 'deps/npm/lib')
-rw-r--r--deps/npm/lib/cache/add-named.js27
-rw-r--r--deps/npm/lib/config/cmd-list.js3
-rw-r--r--deps/npm/lib/config/defaults.js4
-rw-r--r--deps/npm/lib/doctor.js109
-rw-r--r--deps/npm/lib/doctor/check-files-permission.js55
-rw-r--r--deps/npm/lib/doctor/check-ping.js13
-rw-r--r--deps/npm/lib/doctor/checksum-cached-files.js62
-rw-r--r--deps/npm/lib/doctor/get-git-path.js13
-rw-r--r--deps/npm/lib/doctor/get-latest-nodejs-version.js26
-rw-r--r--deps/npm/lib/doctor/get-latest-npm-version.js13
-rw-r--r--deps/npm/lib/fetch-package-metadata.js32
-rw-r--r--deps/npm/lib/install.js8
-rw-r--r--deps/npm/lib/install/deps.js8
-rw-r--r--deps/npm/lib/install/diff-trees.js30
-rw-r--r--deps/npm/lib/install/save.js6
-rw-r--r--deps/npm/lib/npm.js3
-rw-r--r--deps/npm/lib/outdated.js7
-rw-r--r--deps/npm/lib/prune.js86
-rw-r--r--deps/npm/lib/uninstall.js3
-rw-r--r--deps/npm/lib/utils/ansi-trim.js7
-rw-r--r--deps/npm/lib/utils/error-handler.js5
-rw-r--r--deps/npm/lib/utils/metrics-launch.js39
-rw-r--r--deps/npm/lib/utils/metrics.js73
-rw-r--r--deps/npm/lib/utils/pick-manifest-from-registry-metadata.js26
24 files changed, 545 insertions, 113 deletions
diff --git a/deps/npm/lib/cache/add-named.js b/deps/npm/lib/cache/add-named.js
index f0e5e2c33a..3a48b42e7f 100644
--- a/deps/npm/lib/cache/add-named.js
+++ b/deps/npm/lib/cache/add-named.js
@@ -14,6 +14,7 @@ var cachedPackageRoot = require('./cached-package-root.js')
var mapToRegistry = require('../utils/map-to-registry.js')
var pulseTillDone = require('../utils/pulse-till-done.js')
var packageId = require('../utils/package-id.js')
+var pickManifestFromRegistryMetadata = require('../utils/pick-manifest-from-registry-metadata.js')
module.exports = addNamed
@@ -252,28 +253,10 @@ function addNameRange (name, range, data, cb) {
log.silly('addNameRange', 'versions'
, [data.name, Object.keys(data.versions || {})])
- // if the tagged version satisfies, then use that.
- var tagged = data['dist-tags'][npm.config.get('tag')]
- if (tagged &&
- data.versions[tagged] &&
- semver.satisfies(tagged, range, true)) {
- return addNamed(name, tagged, data.versions[tagged], cb)
- }
-
- // find the max satisfying version.
- var versions = Object.keys(data.versions || {})
- var ms = semver.maxSatisfying(versions, range, true)
- if (!ms) {
- if (range === '*' && versions.length) {
- return addNameTag(name, 'latest', data, cb)
- } else {
- return cb(installTargetsError(range, data))
- }
- }
-
- // if we don't have a registry connection, try to see if
- // there's a cached copy that will be ok.
- addNamed(name, ms, data.versions[ms], cb)
+ var versions = Object.keys(data.versions).filter(function (v) { return semver.valid(v) })
+ var picked = pickManifestFromRegistryMetadata(range, npm.config.get('tag'), versions, data)
+ if (picked) return addNamed(name, picked.resolvedTo, picked.manifest, cb)
+ return cb(installTargetsError(range, data))
}
}
diff --git a/deps/npm/lib/config/cmd-list.js b/deps/npm/lib/config/cmd-list.js
index eb79e9df06..a1abe80fb0 100644
--- a/deps/npm/lib/config/cmd-list.js
+++ b/deps/npm/lib/config/cmd-list.js
@@ -98,7 +98,8 @@ var cmdList = [
'start',
'restart',
'run-script',
- 'completion'
+ 'completion',
+ 'doctor'
]
var plumbing = [
diff --git a/deps/npm/lib/config/defaults.js b/deps/npm/lib/config/defaults.js
index 05a06a4e20..81ead08b6f 100644
--- a/deps/npm/lib/config/defaults.js
+++ b/deps/npm/lib/config/defaults.js
@@ -167,6 +167,7 @@ Object.defineProperty(exports, 'defaults', {get: function () {
long: false,
maxsockets: 50,
message: '%s',
+ 'metrics-registry': 'https://registry.npmjs.org/',
'node-version': process.version,
'onload-script': false,
only: null,
@@ -196,6 +197,7 @@ Object.defineProperty(exports, 'defaults', {get: function () {
searchopts: '',
searchexclude: null,
searchstaleness: 15 * 60,
+ 'send-metrics': false,
shell: osenv.shell(),
shrinkwrap: true,
'sign-git-tag': false,
@@ -279,6 +281,7 @@ exports.types = {
long: Boolean,
maxsockets: Number,
message: String,
+ 'metrics-registry': String,
'node-version': [null, semver],
'onload-script': [null, String],
only: [null, 'dev', 'development', 'prod', 'production'],
@@ -303,6 +306,7 @@ exports.types = {
searchopts: String,
searchexclude: [null, String],
searchstaleness: Number,
+ 'send-metrics': Boolean,
shell: String,
shrinkwrap: Boolean,
'sign-git-tag': Boolean,
diff --git a/deps/npm/lib/doctor.js b/deps/npm/lib/doctor.js
new file mode 100644
index 0000000000..f2037ab86c
--- /dev/null
+++ b/deps/npm/lib/doctor.js
@@ -0,0 +1,109 @@
+var path = require('path')
+var chain = require('slide').chain
+var table = require('text-table')
+var color = require('ansicolors')
+var styles = require('ansistyles')
+var semver = require('semver')
+var npm = require('./npm.js')
+var log = require('npmlog')
+var ansiTrim = require('./utils/ansi-trim.js')
+var output = require('./utils/output.js')
+var defaultRegistry = require('./config/defaults.js').defaults.registry
+var checkPing = require('./doctor/check-ping.js')
+var getGitPath = require('./doctor/get-git-path.js')
+var checksumCachedFiles = require('./doctor/checksum-cached-files.js')
+var checkFilesPermission = require('./doctor/check-files-permission.js')
+var getLatestNodejsVersion = require('./doctor/get-latest-nodejs-version.js')
+var getLatestNpmVersion = require('./doctor/get-latest-npm-version')
+var globalNodeModules = path.join(npm.config.globalPrefix, 'lib', 'node_modules')
+var localNodeModules = path.join(npm.config.localPrefix, 'node_modules')
+
+module.exports = doctor
+
+doctor.usage = 'npm doctor'
+
+function doctor (args, silent, cb) {
+ args = args || {}
+ if (typeof cb !== 'function') {
+ cb = silent
+ silent = false
+ }
+
+ var actionsToRun = [
+ [checkPing],
+ [getLatestNpmVersion],
+ [getLatestNodejsVersion, args['node-url']],
+ [getGitPath],
+ [checkFilesPermission, npm.cache, 6],
+ [checkFilesPermission, globalNodeModules, 4],
+ [checkFilesPermission, localNodeModules, 6],
+ [checksumCachedFiles]
+ ]
+
+ log.info('doctor', 'Running checkup')
+ chain(actionsToRun, function (stderr, stdout) {
+ if (stderr && stderr.message !== 'not found: git') return cb(stderr)
+ var outHead = ['Check', 'Value', 'Recommendation']
+ var list = makePretty(stdout)
+ var outBody = list
+
+ if (npm.color) {
+ outHead = outHead.map(function (item) {
+ return styles.underline(item)
+ })
+ outBody = outBody.map(function (item) {
+ if (item[2]) {
+ item[0] = color.red(item[0])
+ item[2] = color.magenta(item[2])
+ }
+ return item
+ })
+ }
+
+ var outTable = [outHead].concat(outBody)
+ var tableOpts = {
+ stringLength: function (s) { return ansiTrim(s).length }
+ }
+
+ if (!silent) output(table(outTable, tableOpts))
+
+ cb(null, list)
+ })
+}
+
+function makePretty (p) {
+ var ping = p[0] ? 'ok' : 'notOk'
+ var npmLTS = p[1]
+ var nodeLTS = p[2].replace('v', '')
+ var whichGit = p[3] || 'not installed'
+ var readbleCaches = p[4] ? 'ok' : 'notOk'
+ var executableGlobalModules = p[5] ? 'ok' : 'notOk'
+ var executableLocalModules = p[6] ? 'ok' : 'notOk'
+ var checksumCachedFiles = p[7] ? 'ok' : 'notOk'
+ var npmV = npm.version
+ var nodeV = process.version.replace('v', '')
+ var registry = npm.config.get('registry')
+ var list = [
+ ['npm ping', ping],
+ ['npm -v', 'v' + npmV],
+ ['node -v', 'v' + nodeV],
+ ['npm config get registry', registry],
+ ['which git', whichGit],
+ ['Perms check on cached files', readbleCaches],
+ ['Perms check on global node_modules', executableGlobalModules],
+ ['Perms check on local node_modules', executableLocalModules],
+ ['Checksum cached files', checksumCachedFiles]
+ ]
+
+ if (ping !== 'ok') list[0][2] = 'Check your internet connection'
+ if (!semver.satisfies(npmV, '>=' + npmLTS)) list[1][2] = 'Use npm v' + npmLTS
+ if (!semver.satisfies(nodeV, '>=' + nodeLTS)) list[2][2] = 'Use node v' + nodeLTS
+ if (registry !== defaultRegistry) list[3][2] = 'Try `npm config set registry ' + defaultRegistry
+ if (whichGit === 'not installed') list[4][2] = 'Install git and ensure it\'s in your PATH.'
+ if (readbleCaches !== 'ok') list[5][2] = 'Check the permissions of your files in ' + npm.config.get('cache')
+ if (executableGlobalModules !== 'ok') list[6][2] = globalNodeModules + ' must be readable and writable by the current user.'
+ if (executableLocalModules !== 'ok') list[7][2] = localNodeModules + ' must be readable and writable by the current user.'
+ if (checksumCachedFiles !== 'ok') list[8][2] = 'You have some broken packages in your cache.'
+
+ return list
+}
diff --git a/deps/npm/lib/doctor/check-files-permission.js b/deps/npm/lib/doctor/check-files-permission.js
new file mode 100644
index 0000000000..ac3bb40bc6
--- /dev/null
+++ b/deps/npm/lib/doctor/check-files-permission.js
@@ -0,0 +1,55 @@
+var fs = require('fs')
+var path = require('path')
+var getUid = require('uid-number')
+var chain = require('slide').chain
+var log = require('npmlog')
+var npm = require('../npm.js')
+var fileCompletion = require('../utils/completion/file-completion.js')
+
+function checkFilesPermission (root, mask, cb) {
+ if (process.platform === 'win32') return cb(null, true)
+ getUid(npm.config.get('user'), npm.config.get('group'), function (e, uid, gid) {
+ if (e) {
+ tracker.finish()
+ tracker.warn('checkFilePermissions', 'Error looking up user and group:', e)
+ return cb(e)
+ }
+ var tracker = log.newItem('checkFilePermissions', 1)
+ tracker.info('checkFilePermissions', 'Building file list of ' + root)
+ fileCompletion(root, '.', Infinity, function (e, files) {
+ if (e) {
+ tracker.warn('checkFilePermissions', 'Error building file list:', e)
+ tracker.finish()
+ return cb(e)
+ }
+ tracker.addWork(files.length)
+ tracker.completeWork(1)
+ chain(files.map(andCheckFile), function (er) {
+ tracker.finish()
+ cb(null, !er)
+ })
+ function andCheckFile (f) {
+ return [checkFile, f]
+ }
+ function checkFile (f, next) {
+ var file = path.join(root, f)
+ tracker.silly('checkFilePermissions', f)
+ fs.stat(file, function (e, stat) {
+ tracker.completeWork(1)
+ if (e) return next(e)
+ if (!stat.isFile()) return next()
+ var mode = stat.mode
+ var isGroup = stat.gid ? stat.gid === gid : true
+ var isUser = stat.uid ? stat.uid === uid : true
+ if ((mode & parseInt('000' + mask, 8))) return next()
+ if ((isGroup && mode & parseInt('00' + mask + '0', 8))) return next()
+ if ((isUser && mode & parseInt('0' + mask + '00', 8))) return next()
+ tracker.error('checkFilePermissions', 'Missing permissions on (' + isGroup + ', ' + isUser + ', ' + mode + ')', file)
+ return next(new Error('Missing permissions for ' + file))
+ })
+ }
+ })
+ })
+}
+
+module.exports = checkFilesPermission
diff --git a/deps/npm/lib/doctor/check-ping.js b/deps/npm/lib/doctor/check-ping.js
new file mode 100644
index 0000000000..99e4ea2ba2
--- /dev/null
+++ b/deps/npm/lib/doctor/check-ping.js
@@ -0,0 +1,13 @@
+var log = require('npmlog')
+var ping = require('../ping.js')
+
+function checkPing (cb) {
+ var tracker = log.newItem('checkPing', 1)
+ tracker.info('checkPing', 'Pinging registry')
+ ping({}, true, function (err, pong) {
+ tracker.finish()
+ cb(err, pong)
+ })
+}
+
+module.exports = checkPing
diff --git a/deps/npm/lib/doctor/checksum-cached-files.js b/deps/npm/lib/doctor/checksum-cached-files.js
new file mode 100644
index 0000000000..d50c326852
--- /dev/null
+++ b/deps/npm/lib/doctor/checksum-cached-files.js
@@ -0,0 +1,62 @@
+var crypto = require('crypto')
+var fs = require('fs')
+var path = require('path')
+var chain = require('slide').chain
+var log = require('npmlog')
+var npm = require('../npm')
+var fileCompletion = require('../utils/completion/file-completion.js')
+
+function checksum (str) {
+ return crypto
+ .createHash('sha1')
+ .update(str, 'utf8')
+ .digest('hex')
+}
+
+function checksumCachedFiles (cb) {
+ var tracker = log.newItem('checksumCachedFiles', 1)
+ tracker.info('checksumCachedFiles', 'Building file list of ' + npm.cache)
+ fileCompletion(npm.cache, '.', Infinity, function (e, files) {
+ if (e) {
+ tracker.finish()
+ return cb(e)
+ }
+ tracker.addWork(files.length)
+ tracker.completeWork(1)
+ chain(files.map(andChecksumFile), function (er) {
+ tracker.finish()
+ cb(null, !er)
+ })
+ function andChecksumFile (f) {
+ return [function (next) { process.nextTick(function () { checksumFile(f, next) }) }]
+ }
+ function checksumFile (f, next) {
+ var file = path.join(npm.cache, f)
+ tracker.silly('checksumFile', f)
+ if (!/.tgz$/.test(file)) {
+ tracker.completeWork(1)
+ return next()
+ }
+ fs.readFile(file, function (err, tgz) {
+ tracker.completeWork(1)
+ if (err) return next(err)
+ try {
+ var pkgJSON = fs.readFileSync(path.join(path.dirname(file), 'package/package.json'))
+ } catch (e) {
+ return next() // no package.json in cche is ok
+ }
+ try {
+ var pkg = JSON.parse(pkgJSON)
+ var shasum = (pkg.dist && pkg.dist.shasum) || pkg._shasum
+ var actual = checksum(tgz)
+ if (actual !== shasum) return next(new Error('Checksum mismatch on ' + file + ', expected: ' + shasum + ', got: ' + shasum))
+ return next()
+ } catch (e) {
+ return next(new Error('Error parsing JSON in ' + file + ': ' + e))
+ }
+ })
+ }
+ })
+}
+
+module.exports = checksumCachedFiles
diff --git a/deps/npm/lib/doctor/get-git-path.js b/deps/npm/lib/doctor/get-git-path.js
new file mode 100644
index 0000000000..5b00e9d54e
--- /dev/null
+++ b/deps/npm/lib/doctor/get-git-path.js
@@ -0,0 +1,13 @@
+var log = require('npmlog')
+var which = require('which')
+
+function getGitPath (cb) {
+ var tracker = log.newItem('getGitPath', 1)
+ tracker.info('getGitPath', 'Finding git in your PATH')
+ which('git', function (err, path) {
+ tracker.finish()
+ cb(err, path)
+ })
+}
+
+module.exports = getGitPath
diff --git a/deps/npm/lib/doctor/get-latest-nodejs-version.js b/deps/npm/lib/doctor/get-latest-nodejs-version.js
new file mode 100644
index 0000000000..a21229692f
--- /dev/null
+++ b/deps/npm/lib/doctor/get-latest-nodejs-version.js
@@ -0,0 +1,26 @@
+var log = require('npmlog')
+var request = require('request')
+
+function getLatestNodejsVersion (url, cb) {
+ var tracker = log.newItem('getLatestNodejsVersion', 1)
+ tracker.info('getLatestNodejsVersion', 'Getting Node.js release information')
+ var version = ''
+ url = url || 'https://nodejs.org/dist/index.json'
+ request(url, function (e, res, index) {
+ tracker.finish()
+ if (e) return cb(e)
+ if (res.statusCode !== 200) {
+ return cb(new Error('Status not 200, ' + res.statusCode))
+ }
+ try {
+ JSON.parse(index).forEach(function (item) {
+ if (item.lts && item.version > version) version = item.version
+ })
+ cb(null, version)
+ } catch (e) {
+ cb(e)
+ }
+ })
+}
+
+module.exports = getLatestNodejsVersion
diff --git a/deps/npm/lib/doctor/get-latest-npm-version.js b/deps/npm/lib/doctor/get-latest-npm-version.js
new file mode 100644
index 0000000000..4df0fee0fa
--- /dev/null
+++ b/deps/npm/lib/doctor/get-latest-npm-version.js
@@ -0,0 +1,13 @@
+var log = require('npmlog')
+var fetchPackageMetadata = require('../fetch-package-metadata')
+
+function getLatestNpmVersion (cb) {
+ var tracker = log.newItem('getLatestNpmVersion', 1)
+ tracker.info('getLatestNpmVersion', 'Getting npm package information')
+ fetchPackageMetadata('npm@latest', '.', function (e, d) {
+ tracker.finish()
+ cb(e, d.version)
+ })
+}
+
+module.exports = getLatestNpmVersion
diff --git a/deps/npm/lib/fetch-package-metadata.js b/deps/npm/lib/fetch-package-metadata.js
index 85322304d5..bd6e47e17c 100644
--- a/deps/npm/lib/fetch-package-metadata.js
+++ b/deps/npm/lib/fetch-package-metadata.js
@@ -26,6 +26,7 @@ var getCacheStat = require('./cache/get-stat.js')
var unpack = require('./utils/tar.js').unpack
var pulseTillDone = require('./utils/pulse-till-done.js')
var parseJSON = require('./utils/parse-json.js')
+var pickManifestFromRegistryMetadata = require('./utils/pick-manifest-from-registry-metadata.js')
function andLogAndFinish (spec, tracker, done) {
validate('SF', [spec, done])
@@ -113,7 +114,7 @@ function fetchNamedPackageData (dep, next) {
} else {
npm.registry.get(url, {auth: auth}, pulseTillDone('fetchMetadata', iferr(next, pickVersionFromRegistryDocument)))
}
- function returnAndAddMetadata (pkg) {
+ function thenAddMetadata (pkg) {
pkg._from = dep.raw
pkg._resolved = pkg.dist.tarball
pkg._shasum = pkg.dist.shasum
@@ -130,35 +131,14 @@ function fetchNamedPackageData (dep, next) {
'You should delete or re-publish the invalid versions.', pkg.name, invalidVersions.join(', '))
}
- versions = versions.filter(function (v) { return semver.valid(v) }).sort(semver.rcompare)
+ versions = versions.filter(function (v) { return semver.valid(v) })
if (dep.type === 'tag') {
var tagVersion = pkg['dist-tags'][dep.spec]
- if (pkg.versions[tagVersion]) return returnAndAddMetadata(pkg.versions[tagVersion])
+ if (pkg.versions[tagVersion]) return thenAddMetadata(pkg.versions[tagVersion])
} else {
- var latestVersion = pkg['dist-tags'][npm.config.get('tag')] || versions[0]
-
- // Find the the most recent version less than or equal
- // to latestVersion that satisfies our spec
- for (var ii = 0; ii < versions.length; ++ii) {
- if (semver.gt(versions[ii], latestVersion)) continue
- if (semver.satisfies(versions[ii], dep.spec)) {
- return returnAndAddMetadata(pkg.versions[versions[ii]])
- }
- }
-
- // Failing that, try finding the most recent version that matches
- // our spec
- for (var jj = 0; jj < versions.length; ++jj) {
- if (semver.satisfies(versions[jj], dep.spec)) {
- return returnAndAddMetadata(pkg.versions[versions[jj]])
- }
- }
-
- // Failing THAT, if the range was '*' uses latestVersion
- if (dep.spec === '*') {
- return returnAndAddMetadata(pkg.versions[latestVersion])
- }
+ var picked = pickManifestFromRegistryMetadata(dep.spec, npm.config.get('tag'), versions, pkg)
+ if (picked) return thenAddMetadata(picked.manifest)
}
// We didn't manage to find a compatible version
diff --git a/deps/npm/lib/install.js b/deps/npm/lib/install.js
index 0cf6a2b2d6..1b0601d9f4 100644
--- a/deps/npm/lib/install.js
+++ b/deps/npm/lib/install.js
@@ -114,6 +114,7 @@ var unlock = locker.unlock
var ls = require('./ls.js')
var parseJSON = require('./utils/parse-json.js')
var output = require('./utils/output.js')
+var saveMetrics = require('./utils/metrics.js').save
// install specific libraries
var copyTree = require('./install/copy-tree.js')
@@ -216,9 +217,14 @@ function Installer (where, dryrun, args) {
}
Installer.prototype = {}
-Installer.prototype.run = function (cb) {
+Installer.prototype.run = function (_cb) {
validate('F', arguments)
+ var cb = function (err) {
+ saveMetrics(!err)
+ return _cb.apply(this, arguments)
+ }
+
// FIXME: This is bad and I should feel bad.
// lib/install needs to have some way of sharing _limited_
// state with the things it calls. Passing the object is too
diff --git a/deps/npm/lib/install/deps.js b/deps/npm/lib/install/deps.js
index bdbd817007..a31976a50d 100644
--- a/deps/npm/lib/install/deps.js
+++ b/deps/npm/lib/install/deps.js
@@ -188,9 +188,13 @@ function addRequiredDep (tree, child, cb) {
}
exports.removeObsoleteDep = removeObsoleteDep
-function removeObsoleteDep (child) {
+function removeObsoleteDep (child, log) {
if (child.removed) return
child.removed = true
+ if (log) {
+ log.silly('removeObsoleteDep', 'removing ' + packageId(child) +
+ ' from the tree as its been replaced by a newer version or is no longer required')
+ }
// remove from physical tree
if (child.parent) {
child.parent.children = child.parent.children.filter(function (pchild) { return pchild !== child })
@@ -199,7 +203,7 @@ function removeObsoleteDep (child) {
var requires = child.requires || []
requires.forEach(function (requirement) {
requirement.requiredBy = requirement.requiredBy.filter(function (reqBy) { return reqBy !== child })
- if (requirement.requiredBy.length === 0) removeObsoleteDep(requirement)
+ if (requirement.requiredBy.length === 0) removeObsoleteDep(requirement, log)
})
}
diff --git a/deps/npm/lib/install/diff-trees.js b/deps/npm/lib/install/diff-trees.js
index db4fb3ce31..1429c71dcb 100644
--- a/deps/npm/lib/install/diff-trees.js
+++ b/deps/npm/lib/install/diff-trees.js
@@ -23,7 +23,7 @@ function pkgAreEquiv (aa, bb) {
return false
}
-function getNameAndVersion (pkg) {
+function getUniqueId (pkg) {
var versionspec = pkg._shasum
if (!versionspec && nonRegistrySource(pkg)) {
@@ -113,21 +113,25 @@ var sortActions = module.exports.sortActions = function (differences) {
return sorted
}
+function setAction (differences, action, pkg) {
+ differences.push([action, pkg])
+}
+
var diffTrees = module.exports._diffTrees = function (oldTree, newTree) {
validate('OO', arguments)
var differences = []
var flatOldTree = flattenTree(oldTree)
var flatNewTree = flattenTree(newTree)
var toRemove = {}
- var toRemoveByNameAndVer = {}
+ var toRemoveByUniqueId = {}
// find differences
Object.keys(flatOldTree).forEach(function (flatname) {
if (flatNewTree[flatname]) return
var pkg = flatOldTree[flatname]
toRemove[flatname] = pkg
- var namever = getNameAndVersion(pkg.package)
- if (!toRemoveByNameAndVer[namever]) toRemoveByNameAndVer[namever] = []
- toRemoveByNameAndVer[namever].push(flatname)
+ var pkgunique = getUniqueId(pkg.package)
+ if (!toRemoveByUniqueId[pkgunique]) toRemoveByUniqueId[pkgunique] = []
+ toRemoveByUniqueId[pkgunique].push(flatname)
})
Object.keys(flatNewTree).forEach(function (path) {
var pkg = flatNewTree[path]
@@ -138,21 +142,21 @@ var diffTrees = module.exports._diffTrees = function (oldTree, newTree) {
if (pkg.oldPkg) {
if (!pkg.userRequired && pkgAreEquiv(pkg.oldPkg.package, pkg.package)) return
if (!pkg.isInLink && (isLink(pkg.oldPkg) || isLink(pkg))) {
- differences.push(['update-linked', pkg])
+ setAction(differences, 'update-linked', pkg)
} else {
- differences.push(['update', pkg])
+ setAction(differences, 'update', pkg)
}
} else {
- var vername = getNameAndVersion(pkg.package)
- var removing = toRemoveByNameAndVer[vername] && toRemoveByNameAndVer[vername].length
+ var vername = getUniqueId(pkg.package)
+ var removing = toRemoveByUniqueId[vername] && toRemoveByUniqueId[vername].length
var bundlesOrFromBundle = pkg.fromBundle || pkg.package.bundleDependencies
if (removing && !bundlesOrFromBundle) {
- var flatname = toRemoveByNameAndVer[vername].shift()
+ var flatname = toRemoveByUniqueId[vername].shift()
pkg.fromPath = toRemove[flatname].path
- differences.push(['move', pkg])
+ setAction(differences, 'move', pkg)
delete toRemove[flatname]
} else {
- differences.push(['add', pkg])
+ setAction(differences, 'add', pkg)
}
}
})
@@ -160,7 +164,7 @@ var diffTrees = module.exports._diffTrees = function (oldTree, newTree) {
.keys(toRemove)
.map(function (path) { return toRemove[path] })
.forEach(function (pkg) {
- differences.push(['remove', pkg])
+ setAction(differences, 'remove', pkg)
})
return differences
}
diff --git a/deps/npm/lib/install/save.js b/deps/npm/lib/install/save.js
index d5c97bfaa2..18028a3c26 100644
--- a/deps/npm/lib/install/save.js
+++ b/deps/npm/lib/install/save.js
@@ -167,11 +167,7 @@ function computeVersionSpec (child) {
pathname: requested.spec
})
} else {
- return url.format({
- protocol: 'file',
- slashes: false,
- pathname: relativePath
- })
+ return 'file:' + relativePath
}
} else if (requested.type === 'hosted') {
return requested.spec
diff --git a/deps/npm/lib/npm.js b/deps/npm/lib/npm.js
index 05749b3fd2..d0af64b195 100644
--- a/deps/npm/lib/npm.js
+++ b/deps/npm/lib/npm.js
@@ -35,6 +35,7 @@
var cmdList = require('./config/cmd-list').cmdList
var plumbing = require('./config/cmd-list').plumbing
var output = require('./utils/output.js')
+ var startMetrics = require('./utils/metrics.js').start
npm.config = {
loaded: false,
@@ -308,6 +309,8 @@
// go ahead and spin up the registry client.
npm.registry = new CachingRegClient(npm.config)
+ startMetrics()
+
return cb(null, npm)
})
})
diff --git a/deps/npm/lib/outdated.js b/deps/npm/lib/outdated.js
index 9f6a8b75eb..a4a0d15abb 100644
--- a/deps/npm/lib/outdated.js
+++ b/deps/npm/lib/outdated.js
@@ -41,6 +41,7 @@ var isExtraneous = require('./install/is-extraneous.js')
var recalculateMetadata = require('./install/deps.js').recalculateMetadata
var moduleName = require('./utils/module-name.js')
var output = require('./utils/output.js')
+var ansiTrim = require('./utils/ansi-trim')
function uniqName (item) {
return item[0].path + '|' + item[1] + '|' + item[7]
@@ -149,12 +150,6 @@ function makePretty (p) {
return columns
}
-function ansiTrim (str) {
- var r = new RegExp('\x1b(?:\\[(?:\\d+[ABCDEFGJKSTm]|\\d+;\\d+[Hfm]|' +
- '\\d+;\\d+;\\d+m|6n|s|u|\\?25[lh])|\\w)', 'g')
- return str.replace(r, '')
-}
-
function makeParseable (list) {
return list.map(function (p) {
var dep = p[0]
diff --git a/deps/npm/lib/prune.js b/deps/npm/lib/prune.js
index 46373742f5..6d103fc508 100644
--- a/deps/npm/lib/prune.js
+++ b/deps/npm/lib/prune.js
@@ -1,54 +1,66 @@
// prune extraneous packages.
module.exports = prune
+module.exports.Pruner = Pruner
prune.usage = 'npm prune [[<@scope>/]<pkg>...] [--production]'
-var readInstalled = require('read-installed')
var npm = require('./npm.js')
-var path = require('path')
-var readJson = require('read-package-json')
var log = require('npmlog')
+var util = require('util')
+var moduleName = require('./utils/module-name.js')
+var Installer = require('./install.js').Installer
+var isExtraneous = require('./install/is-extraneous.js')
+var isDev = require('./install/is-dev-dep.js')
+var removeDeps = require('./install/deps.js').removeDeps
+var loadExtraneous = require('./install/deps.js').loadExtraneous
+var chain = require('slide').chain
prune.completion = require('./utils/completion/installed-deep.js')
function prune (args, cb) {
- // check if is a valid package.json file
- var jsonFile = path.resolve(npm.dir, '..', 'package.json')
- readJson(jsonFile, log.warn, function (er) {
- if (er) return cb(er)
- next()
- })
-
- function next () {
- var opt = {
- depth: npm.config.get('depth'),
- dev: !npm.config.get('production') || npm.config.get('dev')
- }
- readInstalled(npm.prefix, opt, function (er, data) {
- if (er) return cb(er)
- prune_(args, data, cb)
- })
- }
+ var dryrun = !!npm.config.get('dry-run')
+ new Pruner('.', dryrun, args).run(cb)
}
-function prune_ (args, data, cb) {
- npm.commands.unbuild(prunables(args, data, []), cb)
+function Pruner (where, dryrun, args) {
+ Installer.call(this, where, dryrun, args)
}
+util.inherits(Pruner, Installer)
+
+Pruner.prototype.loadAllDepsIntoIdealTree = function (cb) {
+ log.silly('uninstall', 'loadAllDepsIntoIdealtree')
+
+ var cg = this.progress.loadAllDepsIntoIdealTree
+ var steps = []
-function prunables (args, data, seen) {
- var deps = data.dependencies || {}
- return Object.keys(deps).map(function (d) {
- if (typeof deps[d] !== 'object' || seen.indexOf(deps[d]) !== -1) return null
- seen.push(deps[d])
- if (deps[d].extraneous && (args.length === 0 || args.indexOf(d) !== -1)) {
- var extra = deps[d]
- delete deps[d]
- return extra.path
- }
- return prunables(args, deps[d], seen)
- }).filter(function (d) { return d !== null })
- .reduce(function FLAT (l, r) {
- return l.concat(Array.isArray(r) ? r.reduce(FLAT, []) : r)
- }, [])
+ var self = this
+ var excludeDev = npm.config.get('production') || /^prod(uction)?$/.test(npm.config.get('only'))
+ function shouldPrune (child) {
+ if (isExtraneous(child)) return true
+ if (!excludeDev) return false
+ var childName = moduleName(child)
+ var isChildDev = function (parent) { return isDev(parent, childName) }
+ if (child.requiredBy.every(isChildDev)) return true
+ }
+ function getModuleName (child) {
+ // wrapping because moduleName doesn't like extra args and we're called
+ // from map.
+ return moduleName(child)
+ }
+ function matchesArg (name) {
+ return self.args.length === 0 || self.args.indexOf(name) !== -1
+ }
+ function nameObj (name) {
+ return {name: name}
+ }
+ var toPrune = this.currentTree.children.filter(shouldPrune).map(getModuleName).filter(matchesArg).map(nameObj)
+
+ steps.push(
+ [removeDeps, toPrune, this.idealTree, null, cg.newGroup('removeDeps')],
+ [loadExtraneous, this.idealTree, cg.newGroup('loadExtraneous')])
+ chain(steps, cb)
}
+
+Pruner.prototype.runPreinstallTopLevelLifecycles = function (cb) { cb() }
+Pruner.prototype.runPostinstallTopLevelLifecycles = function (cb) { cb() }
diff --git a/deps/npm/lib/uninstall.js b/deps/npm/lib/uninstall.js
index 9a05e6697f..2176048843 100644
--- a/deps/npm/lib/uninstall.js
+++ b/deps/npm/lib/uninstall.js
@@ -74,3 +74,6 @@ Uninstaller.prototype.loadAllDepsIntoIdealTree = function (cb) {
[loadExtraneous, this.idealTree, cg.newGroup('loadExtraneous')])
chain(steps, cb)
}
+
+Uninstaller.prototype.runPreinstallTopLevelLifecycles = function (cb) { cb() }
+Uninstaller.prototype.runPostinstallTopLevelLifecycles = function (cb) { cb() }
diff --git a/deps/npm/lib/utils/ansi-trim.js b/deps/npm/lib/utils/ansi-trim.js
new file mode 100644
index 0000000000..7f9a6c30ec
--- /dev/null
+++ b/deps/npm/lib/utils/ansi-trim.js
@@ -0,0 +1,7 @@
+function ansiTrim (str) {
+ var r = new RegExp('\x1b(?:\\[(?:\\d+[ABCDEFGJKSTm]|\\d+;\\d+[Hfm]|' +
+ '\\d+;\\d+;\\d+m|6n|s|u|\\?25[lh])|\\w)', 'g')
+ return str.replace(r, '')
+}
+
+module.exports = ansiTrim
diff --git a/deps/npm/lib/utils/error-handler.js b/deps/npm/lib/utils/error-handler.js
index 6b2bf1c72d..f3cfc3c5ea 100644
--- a/deps/npm/lib/utils/error-handler.js
+++ b/deps/npm/lib/utils/error-handler.js
@@ -13,10 +13,15 @@ var rollbacks = npm.rollbacks
var chain = require('slide').chain
var writeStreamAtomic = require('fs-write-stream-atomic')
var errorMessage = require('./error-message.js')
+var stopMetrics = require('./metrics.js').stop
process.on('exit', function (code) {
log.disableProgress()
if (!npm.config || !npm.config.loaded) return
+
+ // kill any outstanding stats reporter if it hasn't finished yet
+ stopMetrics()
+
if (code) itWorked = false
if (itWorked) log.info('ok')
else {
diff --git a/deps/npm/lib/utils/metrics-launch.js b/deps/npm/lib/utils/metrics-launch.js
new file mode 100644
index 0000000000..821f8bc7e4
--- /dev/null
+++ b/deps/npm/lib/utils/metrics-launch.js
@@ -0,0 +1,39 @@
+'use strict'
+module.exports = launchSendMetrics
+var fs = require('graceful-fs')
+var child_process = require('child_process')
+
+if (require.main === module) main()
+
+function launchSendMetrics () {
+ var path = require('path')
+ var npm = require('../npm.js')
+ try {
+ if (!npm.config.get('send-metrics')) return
+ var cliMetrics = path.join(npm.config.get('cache'), 'anonymous-cli-metrics.json')
+ var targetRegistry = npm.config.get('metrics-registry')
+ fs.statSync(cliMetrics)
+ return runInBackground(__filename, [cliMetrics, targetRegistry])
+ } catch (ex) {
+ // if the metrics file doesn't exist, don't run
+ }
+}
+
+function runInBackground (js, args, opts) {
+ if (!args) args = []
+ args.unshift(js)
+ if (!opts) opts = {}
+ opts.stdio = 'ignore'
+ opts.detached = true
+ var child = child_process.spawn(process.execPath, args, opts)
+ child.unref()
+ return child
+}
+
+function main () {
+ var sendMetrics = require('./metrics.js').send
+ var metricsFile = process.argv[2]
+ var metricsRegistry = process.argv[3]
+
+ sendMetrics(metricsFile, metricsRegistry)
+}
diff --git a/deps/npm/lib/utils/metrics.js b/deps/npm/lib/utils/metrics.js
new file mode 100644
index 0000000000..c51136e78c
--- /dev/null
+++ b/deps/npm/lib/utils/metrics.js
@@ -0,0 +1,73 @@
+'use strict'
+exports.start = startMetrics
+exports.stop = stopMetrics
+exports.save = saveMetrics
+exports.send = sendMetrics
+
+var fs = require('fs')
+var path = require('path')
+var npm = require('../npm.js')
+var uuid = require('uuid')
+
+var inMetrics = false
+
+function startMetrics () {
+ if (inMetrics) return
+ // loaded on demand to avoid any recursive deps when `./metrics-launch` requires us.
+ var metricsLaunch = require('./metrics-launch.js')
+ npm.metricsProcess = metricsLaunch()
+}
+
+function stopMetrics () {
+ if (inMetrics) return
+ if (npm.metricsProcess) npm.metricsProcess.kill('SIGKILL')
+}
+
+function saveMetrics (itWorked) {
+ if (inMetrics) return
+ // If the metrics reporter hasn't managed to PUT yet then kill it so that it doesn't
+ // step on our updating the anonymous-cli-metrics json
+ stopMetrics()
+ var metricsFile = path.join(npm.config.get('cache'), 'anonymous-cli-metrics.json')
+ var metrics
+ try {
+ metrics = JSON.parse(fs.readFileSync(metricsFile))
+ metrics.metrics.to = new Date().toISOString()
+ if (itWorked) {
+ ++metrics.metrics.successfulInstalls
+ } else {
+ ++metrics.metrics.failedInstalls
+ }
+ } catch (ex) {
+ metrics = {
+ metricId: uuid.v4(),
+ metrics: {
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ successfulInstalls: itWorked ? 1 : 0,
+ failedInstalls: itWorked ? 0 : 1
+ }
+ }
+ }
+ try {
+ fs.writeFileSync(metricsFile, JSON.stringify(metrics))
+ } catch (ex) {
+ // we couldn't write the error metrics file, um, well, oh well.
+ }
+}
+
+function sendMetrics (metricsFile, metricsRegistry) {
+ inMetrics = true
+ var cliMetrics = JSON.parse(fs.readFileSync(metricsFile))
+ npm.load({}, function (err) {
+ if (err) return
+ npm.registry.config.retry.retries = 0
+ npm.registry.sendAnonymousCLIMetrics(metricsRegistry, cliMetrics, function (err) {
+ if (err) {
+ fs.writeFileSync(path.join(path.dirname(metricsFile), 'last-send-metrics-error.txt'), err.stack)
+ } else {
+ fs.unlinkSync(metricsFile)
+ }
+ })
+ })
+}
diff --git a/deps/npm/lib/utils/pick-manifest-from-registry-metadata.js b/deps/npm/lib/utils/pick-manifest-from-registry-metadata.js
new file mode 100644
index 0000000000..e2c0d2e5aa
--- /dev/null
+++ b/deps/npm/lib/utils/pick-manifest-from-registry-metadata.js
@@ -0,0 +1,26 @@
+'use strict'
+module.exports = pickManifestFromRegistryMetadata
+
+var log = require('npmlog')
+var semver = require('semver')
+
+function pickManifestFromRegistryMetadata (spec, tag, versions, metadata) {
+ log.silly('pickManifestFromRegistryMetadata', 'spec', spec, 'tag', tag, 'versions', versions)
+
+ // if the tagged version satisfies, then use that.
+ var tagged = metadata['dist-tags'][tag]
+ if (tagged &&
+ metadata.versions[tagged] &&
+ semver.satisfies(tagged, spec, true)) {
+ return {resolvedTo: tag, manifest: metadata.versions[tagged]}
+ }
+ // find the max satisfying version.
+ var ms = semver.maxSatisfying(versions, spec, true)
+ if (ms) {
+ return {resolvedTo: ms, manifest: metadata.versions[ms]}
+ } else if (spec === '*' && versions.length && tagged && metadata.versions[tagged]) {
+ return {resolvedTo: tag, manifest: metadata.versions[tagged]}
+ } else {
+ return
+ }
+}