diff options
Diffstat (limited to 'deps/npm/lib')
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 + } +} |