diff options
Diffstat (limited to 'deps/npm/lib')
29 files changed, 461 insertions, 168 deletions
diff --git a/deps/npm/lib/auth/legacy.js b/deps/npm/lib/auth/legacy.js index 92bf44c119..08de61bff0 100644 --- a/deps/npm/lib/auth/legacy.js +++ b/deps/npm/lib/auth/legacy.js @@ -6,52 +6,74 @@ const npm = require('../npm.js') const output = require('../utils/output.js') const pacoteOpts = require('../config/pacote') const fetchOpts = require('../config/fetch-opts') +const opener = require('opener') -module.exports.login = function login (creds, registry, scope, cb) { - let username = creds.username || '' - let password = creds.password || '' - let email = creds.email || '' - const auth = {} - if (npm.config.get('otp')) auth.otp = npm.config.get('otp') +const openerPromise = (url) => new Promise((resolve, reject) => { + opener(url, { command: npm.config.get('browser') }, (er) => er ? reject(er) : resolve()) +}) - return read.username('Username:', username, {log: log}).then((u) => { - username = u - return read.password('Password: ', password) +const loginPrompter = (creds) => { + const opts = { log: log } + return read.username('Username:', creds.username, opts).then((u) => { + creds.username = u + return read.password('Password:', creds.password) }).then((p) => { - password = p - return read.email('Email: (this IS public) ', email, {log: log}) + creds.password = p + return read.email('Email: (this IS public) ', creds.email, opts) }).then((e) => { - email = e - return profile.login(username, password, {registry: registry, auth: auth}).catch((err) => { - if (err.code === 'EOTP') throw err - return profile.adduser(username, email, password, { - registry: registry, - opts: fetchOpts.fromPacote(pacoteOpts()) - }) - }).catch((err) => { - if (err.code === 'EOTP' && !auth.otp) { - return read.otp('Authenticator provided OTP:').then((otp) => { - auth.otp = otp - return profile.login(username, password, {registry: registry, auth: auth}) - }) - } else { - throw err - } + creds.email = e + return creds + }) +} + +module.exports.login = (creds, registry, scope, cb) => { + const conf = { + log: log, + creds: creds, + registry: registry, + auth: { + otp: npm.config.get('otp') + }, + scope: scope, + opts: fetchOpts.fromPacote(pacoteOpts()) + } + login(conf).then((newCreds) => cb(null, newCreds)).catch(cb) +} + +function login (conf) { + return profile.login(openerPromise, loginPrompter, conf) + .catch((err) => { + if (err.code === 'EOTP') throw err + const u = conf.creds.username + const p = conf.creds.password + const e = conf.creds.email + if (!(u && p && e)) throw err + return profile.adduserCouch(u, e, p, conf) + }) + .catch((err) => { + if (err.code !== 'EOTP') throw err + return read.otp('Authenticator provided OTP:').then((otp) => { + conf.auth.otp = otp + const u = conf.creds.username + const p = conf.creds.password + return profile.loginCouch(u, p, conf) }) }).then((result) => { const newCreds = {} if (result && result.token) { newCreds.token = result.token } else { - newCreds.username = username - newCreds.password = password - newCreds.email = email + newCreds.username = conf.creds.username + newCreds.password = conf.creds.password + newCreds.email = conf.creds.email newCreds.alwaysAuth = npm.config.get('always-auth') } - log.info('adduser', 'Authorized user %s', username) - const scopeMessage = scope ? ' to scope ' + scope : '' - output('Logged in as %s%s on %s.', username, scopeMessage, registry) - cb(null, newCreds) - }).catch(cb) + const usermsg = conf.creds.username ? ' user ' + conf.creds.username : '' + conf.log.info('login', 'Authorized' + usermsg) + const scopeMessage = conf.scope ? ' to scope ' + conf.scope : '' + const userout = conf.creds.username ? ' as ' + conf.creds.username : '' + output('Logged in%s%s on %s.', userout, scopeMessage, conf.registry) + return newCreds + }) } diff --git a/deps/npm/lib/ci.js b/deps/npm/lib/ci.js new file mode 100644 index 0000000000..6a3183d977 --- /dev/null +++ b/deps/npm/lib/ci.js @@ -0,0 +1,37 @@ +'use strict' + +const Installer = require('libcipm') +const lifecycleOpts = require('./config/lifecycle.js') +const npm = require('./npm.js') +const npmlog = require('npmlog') +const pacoteOpts = require('./config/pacote.js') + +ci.usage = 'npm ci' + +ci.completion = (cb) => cb(null, []) + +Installer.CipmConfig.impl(npm.config, { + get: npm.config.get, + set: npm.config.set, + toLifecycle (moreOpts) { + return lifecycleOpts(moreOpts) + }, + toPacote (moreOpts) { + return pacoteOpts(moreOpts) + } +}) + +module.exports = ci +function ci (args, cb) { + return new Installer({ + config: npm.config, + log: npmlog + }) + .run() + .then( + (details) => console.error(`added ${details.pkgCount} packages in ${ + details.runTime / 1000 + }s`) + ) + .then(() => cb(), cb) +} diff --git a/deps/npm/lib/config/cmd-list.js b/deps/npm/lib/config/cmd-list.js index 49c445a4f0..1f618cdb9a 100644 --- a/deps/npm/lib/config/cmd-list.js +++ b/deps/npm/lib/config/cmd-list.js @@ -22,6 +22,7 @@ var affordances = { 'la': 'ls', 'll': 'ls', 'verison': 'version', + 'ic': 'ci', 'isntall': 'install', 'dist-tags': 'dist-tag', 'apihelp': 'help', @@ -46,6 +47,7 @@ var affordances = { // these are filenames in . var cmdList = [ + 'ci', 'install', 'install-test', 'uninstall', diff --git a/deps/npm/lib/config/core.js b/deps/npm/lib/config/core.js index 50cf4772e7..54a74bb847 100644 --- a/deps/npm/lib/config/core.js +++ b/deps/npm/lib/config/core.js @@ -331,7 +331,10 @@ Conf.prototype.parse = function (content, file) { Conf.prototype.add = function (data, marker) { try { Object.keys(data).forEach(function (k) { - data[k] = parseField(data[k], k) + const newKey = envReplace(k) + const newField = parseField(data[k], newKey) + delete data[k] + data[newKey] = newField }) } catch (e) { this.emit('error', e) diff --git a/deps/npm/lib/config/defaults.js b/deps/npm/lib/config/defaults.js index c049f213fa..865805eb27 100644 --- a/deps/npm/lib/config/defaults.js +++ b/deps/npm/lib/config/defaults.js @@ -130,7 +130,7 @@ Object.defineProperty(exports, 'defaults', {get: function () { cidr: null, - color: true, + color: process.env.NO_COLOR == null, depth: Infinity, description: true, dev: false, @@ -193,6 +193,7 @@ Object.defineProperty(exports, 'defaults', {get: function () { 'progress': !process.env.TRAVIS && !process.env.CI, proxy: null, 'https-proxy': null, + 'no-proxy': null, 'user-agent': 'npm/{npm-version} ' + 'node/{node-version} ' + '{platform} ' + @@ -312,6 +313,7 @@ exports.types = { 'metrics-registry': [null, String], 'node-options': [null, String], 'node-version': [null, semver], + 'no-proxy': [null, String, Array], offline: Boolean, 'onload-script': [null, String], only: [null, 'dev', 'development', 'prod', 'production'], diff --git a/deps/npm/lib/config/pacote.js b/deps/npm/lib/config/pacote.js index ec43178c77..0793d3a6fc 100644 --- a/deps/npm/lib/config/pacote.js +++ b/deps/npm/lib/config/pacote.js @@ -37,6 +37,7 @@ function pacoteOpts (moreOpts) { preferOnline: npm.config.get('prefer-online') || npm.config.get('cache-max') <= 0, projectScope: npm.projectScope, proxy: npm.config.get('https-proxy') || npm.config.get('proxy'), + noProxy: npm.config.get('no-proxy'), refer: npm.registry.refer, registry: npm.config.get('registry'), retry: { diff --git a/deps/npm/lib/dedupe.js b/deps/npm/lib/dedupe.js index 71e60619c4..c763499f6b 100644 --- a/deps/npm/lib/dedupe.js +++ b/deps/npm/lib/dedupe.js @@ -142,7 +142,7 @@ function hoistChildren_ (tree, diff, seen, next) { [andComputeMetadata(tree)] ], done) } - var hoistTo = earliestInstallable(tree, tree.parent, child.package) + var hoistTo = earliestInstallable(tree, tree.parent, child.package, log) if (hoistTo) { move(child, hoistTo, diff) chain([ diff --git a/deps/npm/lib/install.js b/deps/npm/lib/install.js index 42906f2394..7a7d75c908 100644 --- a/deps/npm/lib/install.js +++ b/deps/npm/lib/install.js @@ -220,6 +220,8 @@ function Installer (where, dryrun, args, opts) { this.noPackageJsonOk = !!args.length this.topLevelLifecycles = !args.length + this.autoPrune = npm.config.get('package-lock') + const dev = npm.config.get('dev') const only = npm.config.get('only') const onlyProd = /^prod(uction)?$/.test(only) @@ -436,8 +438,8 @@ Installer.prototype.pruneIdealTree = function (cb) { // if our lock file didn't have the requires field and there // are any fake children then forgo pruning until we have more info. if (!this.idealTree.hasRequiresFromLock && this.idealTree.children.some((n) => n.fakeChild)) return cb() - var toPrune = this.idealTree.children - .filter(isExtraneous) + const toPrune = this.idealTree.children + .filter((child) => isExtraneous(child) && (this.autoPrune || child.removing)) .map((n) => ({name: moduleName(n)})) return removeExtraneous(toPrune, this.idealTree, cb) } @@ -692,27 +694,19 @@ Installer.prototype.readLocalPackageData = function (cb) { Installer.prototype.cloneCurrentTreeToIdealTree = function (cb) { validate('F', arguments) log.silly('install', 'cloneCurrentTreeToIdealTree') - this.idealTree = copyTree(this.currentTree, (child) => { - // Filter out any children we didn't install ourselves. They need to be - // reinstalled in order for things to be correct. - return child.isTop || isLink(child) || ( - child.package && - child.package._resolved && - (child.package._integrity || child.package._shasum) - ) - }) + + this.idealTree = copyTree(this.currentTree) this.idealTree.warnings = [] cb() } -function isLink (child) { - return child.isLink || (child.parent && isLink(child.parent)) -} - Installer.prototype.loadShrinkwrap = function (cb) { validate('F', arguments) log.silly('install', 'loadShrinkwrap') - readShrinkwrap.andInflate(this.idealTree, cb) + readShrinkwrap.andInflate(this.idealTree, iferr(cb, () => { + computeMetadata(this.idealTree) + cb() + })) } Installer.prototype.getInstalledModules = function () { @@ -774,6 +768,9 @@ Installer.prototype.printInstalledForHuman = function (diffs, cb) { var added = 0 var updated = 0 var moved = 0 + // Count the number of contributors to packages added, tracking + // contributors we've seen, so we can produce a running unique count. + var contributors = new Set() diffs.forEach(function (action) { var mutation = action[0] var pkg = action[1] @@ -784,6 +781,26 @@ Installer.prototype.printInstalledForHuman = function (diffs, cb) { ++moved } else if (mutation === 'add') { ++added + // Count contributors to added packages. Start by combining `author` + // and `contributors` data into a single array of contributor-people + // for this package. + var people = [] + var meta = pkg.package + if (meta.author) people.push(meta.author) + if (meta.contributors && Array.isArray(meta.contributors)) { + people = people.concat(meta.contributors) + } + // Make sure a normalized string for every person behind this + // package is in `contributors`. + people.forEach(function (person) { + // Ignore errors from malformed `author` and `contributors`. + try { + var normalized = normalizePerson(person) + } catch (error) { + return + } + if (!contributors.has(normalized)) contributors.add(normalized) + }) } else if (mutation === 'update' || mutation === 'update-linked') { ++updated } @@ -795,7 +812,11 @@ Installer.prototype.printInstalledForHuman = function (diffs, cb) { }).join('\n') + '\n' } var actions = [] - if (added) actions.push('added ' + packages(added)) + if (added) { + var action = 'added ' + packages(added) + if (contributors.size) action += from(contributors.size) + actions.push(action) + } if (removed) actions.push('removed ' + packages(removed)) if (updated) actions.push('updated ' + packages(updated)) if (moved) actions.push('moved ' + packages(moved)) @@ -815,6 +836,23 @@ Installer.prototype.printInstalledForHuman = function (diffs, cb) { function packages (num) { return num + ' package' + (num > 1 ? 's' : '') } + + function from (num) { + return ' from ' + num + ' contributor' + (num > 1 ? 's' : '') + } + + // Values of `author` and elements of `contributors` in `package.json` + // files can be e-mail style strings or Objects with `name`, `email, + // and `url` String properties. Convert Objects to Strings so that + // we can efficiently keep a set of contributors we have already seen. + function normalizePerson (argument) { + if (typeof argument === 'string') return argument + var returned = '' + if (argument.name) returned += argument.name + if (argument.email) returned += ' <' + argument.email + '>' + if (argument.url) returned += ' (' + argument.email + ')' + return returned + } } Installer.prototype.printInstalledForJSON = function (diffs, cb) { diff --git a/deps/npm/lib/install/action/extract-worker.js b/deps/npm/lib/install/action/extract-worker.js index 24508c7804..2b082b4a57 100644 --- a/deps/npm/lib/install/action/extract-worker.js +++ b/deps/npm/lib/install/action/extract-worker.js @@ -10,9 +10,9 @@ module.exports = (args, cb) => { const spec = parsed[0] const extractTo = parsed[1] const opts = parsed[2] - if (!opts.log && opts.loglevel) { + if (!opts.log) { opts.log = npmlog - opts.log.level = opts.loglevel } + opts.log.level = opts.loglevel || opts.log.level BB.resolve(extract(spec, extractTo, opts)).nodeify(cb) } diff --git a/deps/npm/lib/install/action/extract.js b/deps/npm/lib/install/action/extract.js index 6b827f36ea..e8d7a6c4f6 100644 --- a/deps/npm/lib/install/action/extract.js +++ b/deps/npm/lib/install/action/extract.js @@ -4,9 +4,7 @@ const BB = require('bluebird') const stat = BB.promisify(require('graceful-fs').stat) const gentlyRm = BB.promisify(require('../../utils/gently-rm.js')) -const log = require('npmlog') const mkdirp = BB.promisify(require('mkdirp')) -const moduleName = require('../../utils/module-name.js') const moduleStagingPath = require('../module-staging-path.js') const move = require('../../utils/move.js') const npa = require('npm-package-arg') @@ -59,12 +57,11 @@ function extract (staging, pkg, log) { pacoteOpts = require('../../config/pacote') } const opts = pacoteOpts({ - integrity: pkg.package._integrity + integrity: pkg.package._integrity, + resolved: pkg.package._resolved }) const args = [ - pkg.package._resolved - ? npa.resolve(pkg.package.name, pkg.package._resolved) - : pkg.package._requested, + pkg.package._requested, extractTo, opts ] @@ -112,18 +109,6 @@ function readBundled (pkg, staging, extractTo) { }, {concurrency: 10}) } -function getTree (pkg) { - while (pkg.parent) pkg = pkg.parent - return pkg -} - -function warn (pkg, code, msg) { - const tree = getTree(pkg) - const err = new Error(msg) - err.code = code - tree.warnings.push(err) -} - function stageBundledModule (bundler, child, staging, parentPath) { const stageFrom = path.join(parentPath, 'node_modules', child.package.name) const stageTo = moduleStagingPath(staging, child) @@ -146,15 +131,6 @@ function finishModule (bundler, child, stageTo, stageFrom) { return move(stageFrom, stageTo) }) } else { - return stat(stageFrom).then(() => { - const bundlerId = packageId(bundler) - if (!getTree(bundler).warnings.some((w) => { - return w.code === 'EBUNDLEOVERRIDE' - })) { - warn(bundler, 'EBUNDLEOVERRIDE', `${bundlerId} had bundled packages that do not match the required version(s). They have been replaced with non-bundled versions.`) - } - log.verbose('bundle', `EBUNDLEOVERRIDE: Replacing ${bundlerId}'s bundled version of ${moduleName(child)} with ${packageId(child)}.`) - return gentlyRm(stageFrom) - }, () => {}) + return stat(stageFrom).then(() => gentlyRm(stageFrom), () => {}) } } diff --git a/deps/npm/lib/install/actions.js b/deps/npm/lib/install/actions.js index 9608a943a5..a34d03ffe2 100644 --- a/deps/npm/lib/install/actions.js +++ b/deps/npm/lib/install/actions.js @@ -118,7 +118,7 @@ function doParallel (type, staging, actionsToRun, log, next) { } return acc }, []) - log.silly('doParallel', type + ' ' + actionsToRun.length) + log.silly('doParallel', type + ' ' + acts.length) time(log) if (!acts.length) { return next() } return withInit(actions[type], () => { diff --git a/deps/npm/lib/install/copy-tree.js b/deps/npm/lib/install/copy-tree.js index a5b558cf59..2bf7064f33 100644 --- a/deps/npm/lib/install/copy-tree.js +++ b/deps/npm/lib/install/copy-tree.js @@ -1,27 +1,26 @@ 'use strict' var createNode = require('./node.js').create -module.exports = function (tree, filter) { - return copyTree(tree, {}, filter) +module.exports = function (tree) { + return copyTree(tree, {}) } -function copyTree (tree, cache, filter) { - if (filter && !filter(tree)) { return null } +function copyTree (tree, cache) { if (cache[tree.path]) { return cache[tree.path] } var newTree = cache[tree.path] = createNode(Object.assign({}, tree)) - copyModuleList(newTree, 'children', cache, filter) + copyModuleList(newTree, 'children', cache) newTree.children.forEach(function (child) { child.parent = newTree }) - copyModuleList(newTree, 'requires', cache, filter) - copyModuleList(newTree, 'requiredBy', cache, filter) + copyModuleList(newTree, 'requires', cache) + copyModuleList(newTree, 'requiredBy', cache) return newTree } -function copyModuleList (tree, key, cache, filter) { +function copyModuleList (tree, key, cache) { var newList = [] if (tree[key]) { tree[key].forEach(function (child) { - const copy = copyTree(child, cache, filter) + const copy = copyTree(child, cache) if (copy) { newList.push(copy) } diff --git a/deps/npm/lib/install/deps.js b/deps/npm/lib/install/deps.js index 93c4adffd7..54cc5258fa 100644 --- a/deps/npm/lib/install/deps.js +++ b/deps/npm/lib/install/deps.js @@ -33,6 +33,7 @@ var getSaveType = require('./save.js').getSaveType var unixFormatPath = require('../utils/unix-format-path.js') var isExtraneous = require('./is-extraneous.js') var isRegistry = require('../utils/is-registry.js') +var hasModernMeta = require('./has-modern-meta.js') // The export functions in this module mutate a dependency tree, adding // items to them. @@ -111,7 +112,7 @@ function computeMetadata (tree, seen) { const reqs = tree.swRequires || {} for (let name of Object.keys(deps)) { if (findChild(name, deps[name])) continue - if (findChild(name, reqs[name])) continue + if (name in reqs && findChild(name, reqs[name])) continue tree.missingDeps[name] = deps[name] } if (tree.isTop) { @@ -332,9 +333,21 @@ exports.removeDeps = function (args, tree, saveToDependencies, next) { parent.requires = parent.requires.filter((child) => child !== pkgToRemove) } pkgToRemove.requiredBy = pkgToRemove.requiredBy.filter((parent) => parent !== tree) + flagAsRemoving(pkgToRemove) } next() } + +function flagAsRemoving (toRemove, seen) { + if (!seen) seen = new Set() + if (seen.has(toRemove)) return + seen.add(toRemove) + toRemove.removing = true + toRemove.requires.forEach((required) => { + flagAsRemoving(required, seen) + }) +} + exports.removeExtraneous = function (args, tree, next) { for (let pkg of args) { var pkgName = moduleName(pkg) @@ -526,7 +539,7 @@ function addDependency (name, versionSpec, tree, log, done) { } var child = findRequirement(tree, name, req) if (!child && swReq) child = findRequirement(tree, name, swReq) - if (child) { + if (hasModernMeta(child)) { resolveWithExistingModule(child, tree) if (child.package._shrinkwrap === undefined) { readShrinkwrap.andInflate(child, function (er) { next(er, child, log) }) @@ -534,12 +547,42 @@ function addDependency (name, versionSpec, tree, log, done) { next(null, child, log) } } else { + if (child) { + if (req.registry) { + req = childDependencySpecifier(tree, name, child.package.version) + } + if (child.fromBundle) reportBundleOverride(child, log) + removeObsoleteDep(child, log) + } fetchPackageMetadata(req, packageRelativePath(tree), {tracker: log.newItem('fetchMetadata')}, iferr(next, function (pkg) { resolveWithNewModule(pkg, tree, log, next) })) } } +function getTop (pkg) { + const seen = new Set() + while (pkg.parent && !seen.has(pkg.parent)) { + pkg = pkg.parent + seen.add(pkg) + } + return pkg +} + +function reportBundleOverride (child, log) { + const code = 'EBUNDLEOVERRIDE' + const top = getTop(child.fromBundle) + const bundlerId = packageId(child.fromBundle) + if (!top.warnings.some((w) => { + return w.code === code + })) { + const err = new Error(`${bundlerId} had bundled packages that do not match the required version(s). They have been replaced with non-bundled versions.`) + err.code = code + top.warnings.push(err) + } + if (log) log.verbose('bundle', `${code}: Replacing ${bundlerId}'s bundled version of ${moduleName(child)} with ${packageId(child)}.`) +} + function resolveWithExistingModule (child, tree) { validate('OO', arguments) addRequiredDep(tree, child) @@ -592,7 +635,7 @@ function resolveWithNewModule (pkg, tree, log, next) { return isInstallable(pkg, (err) => { let installable = !err addBundled(pkg, (bundleErr) => { - var parent = earliestInstallable(tree, tree, pkg) || tree + var parent = earliestInstallable(tree, tree, pkg, log) || tree var isLink = pkg._requested.type === 'directory' var child = createChild({ package: pkg, @@ -609,7 +652,10 @@ function resolveWithNewModule (pkg, tree, log, next) { var hasBundled = child.children.length var replaced = replaceModuleByName(parent, 'children', child) - if (replaced) removeObsoleteDep(replaced) + if (replaced) { + if (replaced.fromBundle) reportBundleOverride(replaced, log) + removeObsoleteDep(replaced) + } addRequiredDep(tree, child) child.location = flatNameFromTree(child) @@ -694,12 +740,25 @@ function preserveSymlinks () { // Find the highest level in the tree that we can install this module in. // If the module isn't installed above us yet, that'd be the very top. // If it is, then it's the level below where its installed. -var earliestInstallable = exports.earliestInstallable = function (requiredBy, tree, pkg) { - validate('OOO', arguments) +var earliestInstallable = exports.earliestInstallable = function (requiredBy, tree, pkg, log) { + validate('OOOO', arguments) + function undeletedModuleMatches (child) { return !child.removed && moduleName(child) === pkg.name } - if (tree.children.some(undeletedModuleMatches)) return null + const undeletedMatches = tree.children.filter(undeletedModuleMatches) + if (undeletedMatches.length) { + // if there's a conflict with another child AT THE SAME level then we're replacing it, so + // mark it as removed and continue with resolution normally. + if (tree === requiredBy) { + undeletedMatches.forEach((pkg) => { + if (pkg.fromBundle) reportBundleOverride(pkg, log) + removeObsoleteDep(pkg, log) + }) + } else { + return null + } + } // If any of the children of this tree have conflicting // binaries then we need to decline to install this package here. @@ -738,5 +797,5 @@ var earliestInstallable = exports.earliestInstallable = function (requiredBy, tr if (!preserveSymlinks() && /^[.][.][\\/]/.test(path.relative(tree.parent.realpath, tree.realpath))) return tree - return (earliestInstallable(requiredBy, tree.parent, pkg) || tree) + return (earliestInstallable(requiredBy, tree.parent, pkg, log) || tree) } diff --git a/deps/npm/lib/install/diff-trees.js b/deps/npm/lib/install/diff-trees.js index 4316f351cc..06e6b77a91 100644 --- a/deps/npm/lib/install/diff-trees.js +++ b/deps/npm/lib/install/diff-trees.js @@ -70,6 +70,9 @@ function sriMatch (aa, bb) { function pkgAreEquiv (aa, bb) { // coming in we know they share a path… + // if one is inside a link and the other is not, then they are not equivalent + // this happens when we're replacing a linked dep with a non-linked version + if (aa.isInLink !== bb.isInLink) return false // if they share package metadata _identity_, they're the same thing if (aa.package === bb.package) return true // if they share integrity information, they're the same thing diff --git a/deps/npm/lib/install/has-modern-meta.js b/deps/npm/lib/install/has-modern-meta.js new file mode 100644 index 0000000000..382e74c70c --- /dev/null +++ b/deps/npm/lib/install/has-modern-meta.js @@ -0,0 +1,19 @@ +'use strict' +module.exports = hasModernMeta + +const npa = require('npm-package-arg') +const moduleName = require('../utils/module-name.js') + +function isLink (child) { + return child.isLink || (child.parent && isLink(child.parent)) +} + +function hasModernMeta (child) { + if (!child) return false + const resolved = child.package._resolved && npa.resolve(moduleName(child), child.package._resolved) + return child.isTop || isLink(child) || ( + child.package && + resolved && + (child.package._integrity || child.package._shasum || resolved.type === 'git') + ) +} diff --git a/deps/npm/lib/install/inflate-shrinkwrap.js b/deps/npm/lib/install/inflate-shrinkwrap.js index 43ac9136f0..e596ca94ab 100644 --- a/deps/npm/lib/install/inflate-shrinkwrap.js +++ b/deps/npm/lib/install/inflate-shrinkwrap.js @@ -14,6 +14,8 @@ const realizeShrinkwrapSpecifier = require('./realize-shrinkwrap-specifier.js') const validate = require('aproba') const path = require('path') const isRegistry = require('../utils/is-registry.js') +const hasModernMeta = require('./has-modern-meta.js') +const ssri = require('ssri') module.exports = function (tree, sw, opts, finishInflating) { if (!fetchPackageMetadata) { @@ -68,7 +70,7 @@ function normalizePackageDataNoErrors (pkg) { function inflatableChild (onDiskChild, name, topPath, tree, sw, requested, opts) { validate('OSSOOOO|ZSSOOOO', arguments) - if (onDiskChild && childIsEquivalent(sw, requested, onDiskChild)) { + if (hasModernMeta(onDiskChild) && childIsEquivalent(sw, requested, onDiskChild)) { // The version on disk matches the shrinkwrap entry. if (!onDiskChild.fromShrinkwrap) onDiskChild.fromShrinkwrap = true onDiskChild.package._requested = requested @@ -106,7 +108,7 @@ function makeFakeChild (name, topPath, tree, sw, requested) { name: name, version: sw.version, _id: name + '@' + sw.version, - _resolved: adaptResolved(requested, sw.resolved), + _resolved: sw.resolved, _requested: requested, _optional: sw.optional, _development: sw.dev, @@ -144,23 +146,6 @@ function makeFakeChild (name, topPath, tree, sw, requested) { return child } -function adaptResolved (requested, resolved) { - const registry = requested.scope - ? npm.config.get(`${requested.scope}:registry`) || npm.config.get('registry') - : npm.config.get('registry') - if (!isRegistry(requested) || (resolved && resolved.indexOf(registry) === 0)) { - // Nothing to worry about here. Pass it through. - return resolved - } else { - // We could fast-path for registry.npmjs.org here, but if we do, it - // would end up getting written back to the `resolved` field. By always - // returning `null` for other registries, `pacote.extract()` will take - // care of any required metadata fetches internally, without altering - // the tree we're going to write out to shrinkwrap/lockfile. - return null - } -} - function fetchChild (topPath, tree, sw, requested) { return fetchPackageMetadata(requested, topPath).then((pkg) => { pkg._from = sw.from || requested.raw @@ -196,7 +181,11 @@ function fetchChild (topPath, tree, sw, requested) { function childIsEquivalent (sw, requested, child) { if (!child) return false if (child.fromShrinkwrap) return true - if (sw.integrity && child.package._integrity === sw.integrity) return true + if ( + sw.integrity && + child.package._integrity && + ssri.parse(sw.integrity).match(child.package._integrity) + ) return true if (child.isLink && requested.type === 'directory') return path.relative(child.realpath, requested.fetchSpec) === '' if (sw.resolved) return child.package._resolved === sw.resolved diff --git a/deps/npm/lib/install/read-shrinkwrap.js b/deps/npm/lib/install/read-shrinkwrap.js index 45e883caa2..a48a4aea00 100644 --- a/deps/npm/lib/install/read-shrinkwrap.js +++ b/deps/npm/lib/install/read-shrinkwrap.js @@ -25,14 +25,7 @@ function readShrinkwrap (child, next) { log.warn('read-shrinkwrap', 'Ignoring package-lock.json because there is already an npm-shrinkwrap.json. Please use only one of the two.') } const name = shrinkwrap ? 'npm-shrinkwrap.json' : 'package-lock.json' - let parsed = null - if (shrinkwrap || lockfile) { - try { - parsed = parseJSON(shrinkwrap || lockfile) - } catch (ex) { - throw ex - } - } + const parsed = parsePkgLock(shrinkwrap || lockfile, name) if (parsed && parsed.lockfileVersion !== PKGLOCK_VERSION) { log.warn('read-shrinkwrap', `This version of npm is compatible with lockfileVersion@${PKGLOCK_VERSION}, but ${name} was generated for lockfileVersion@${parsed.lockfileVersion || 0}. I'll try to do my best with it!`) } @@ -43,7 +36,8 @@ function readShrinkwrap (child, next) { function maybeReadFile (name, child) { return readFileAsync( - path.join(child.path, name) + path.join(child.path, name), + 'utf8' ).catch({code: 'ENOENT'}, () => null) } @@ -56,3 +50,57 @@ module.exports.andInflate = function (child, next) { } })) } + +const PARENT_RE = /\|{7,}/g +const OURS_RE = /\<{7,}/g +const THEIRS_RE = /\={7,}/g +const END_RE = /\>{7,}/g + +module.exports._isDiff = isDiff +function isDiff (str) { + return str.match(OURS_RE) && str.match(THEIRS_RE) && str.match(END_RE) +} + +module.exports._parsePkgLock = parsePkgLock +function parsePkgLock (str, filename) { + if (!str) { return null } + try { + return parseJSON(str) + } catch (e) { + if (isDiff(str)) { + log.warn('conflict', `A git conflict was detected in ${filename}. Attempting to auto-resolve.`) + const pieces = str.split(/[\n\r]+/g).reduce((acc, line) => { + if (line.match(PARENT_RE)) acc.state = 'parent' + else if (line.match(OURS_RE)) acc.state = 'ours' + else if (line.match(THEIRS_RE)) acc.state = 'theirs' + else if (line.match(END_RE)) acc.state = 'top' + else { + if (acc.state === 'top' || acc.state === 'ours') acc.ours += line + if (acc.state === 'top' || acc.state === 'theirs') acc.theirs += line + if (acc.state === 'top' || acc.state === 'parent') acc.parent += line + } + return acc + }, { + state: 'top', + ours: '', + theirs: '', + parent: '' + }) + try { + const ours = parseJSON(pieces.ours) + const theirs = parseJSON(pieces.theirs) + return reconcileLockfiles(ours, theirs) + } catch (_e) { + log.error('conflict', `Automatic conflict resolution failed. Please manually resolve conflicts in ${filename} and try again.`) + log.silly('conflict', `Error during resolution: ${_e}`) + throw e + } + } else { + throw e + } + } +} + +function reconcileLockfiles (parent, ours, theirs) { + return Object.assign({}, ours, theirs) +} diff --git a/deps/npm/lib/install/save.js b/deps/npm/lib/install/save.js index f0c61f555d..3f62643b80 100644 --- a/deps/npm/lib/install/save.js +++ b/deps/npm/lib/install/save.js @@ -3,6 +3,7 @@ const createShrinkwrap = require('../shrinkwrap.js').createShrinkwrap const deepSortObject = require('../utils/deep-sort-object.js') const detectIndent = require('detect-indent') +const detectNewline = require('detect-newline') const fs = require('graceful-fs') const iferr = require('iferr') const log = require('npmlog') @@ -10,6 +11,7 @@ const moduleName = require('../utils/module-name.js') const npm = require('../npm.js') const parseJSON = require('../utils/parse-json.js') const path = require('path') +const stringifyPackage = require('../utils/stringify-package') const validate = require('aproba') const without = require('lodash.without') const writeFileAtomic = require('write-file-atomic') @@ -60,7 +62,8 @@ function savePackageJson (tree, next) { // don't use readJson, because we don't want to do all the other // tricky npm-specific stuff that's in there. fs.readFile(saveTarget, 'utf8', iferr(next, function (packagejson) { - const indent = detectIndent(packagejson).indent || ' ' + const indent = detectIndent(packagejson).indent + const newline = detectNewline(packagejson) try { tree.package = parseJSON(packagejson) } catch (ex) { @@ -122,7 +125,7 @@ function savePackageJson (tree, next) { tree.package.bundleDependencies = deepSortObject(bundle) } - var json = JSON.stringify(tree.package, null, indent) + '\n' + var json = stringifyPackage(tree.package, indent, newline) if (json === packagejson) { log.verbose('shrinkwrap', 'skipping write for package.json because there were no changes.') next() diff --git a/deps/npm/lib/pack.js b/deps/npm/lib/pack.js index f6a0eff805..b6a08d8650 100644 --- a/deps/npm/lib/pack.js +++ b/deps/npm/lib/pack.js @@ -120,7 +120,9 @@ function packDirectory (mani, dir, target) { cwd: dir, prefix: 'package/', portable: true, - noMtime: true, + // Provide a specific date in the 1980s for the benefit of zip, + // which is confounded by files dated at the Unix epoch 0. + mtime: new Date('1985-10-26T08:15:00.000Z'), gzip: true } @@ -170,7 +172,7 @@ function packGitDep (manifest, dir) { return acc }, []) const child = cp.spawn(process.env.NODE || process.execPath, [ - require.main.filename, + require.resolve('../bin/npm-cli.js'), 'install', '--dev', '--prod', diff --git a/deps/npm/lib/profile.js b/deps/npm/lib/profile.js index 587a26ca8b..016e898157 100644 --- a/deps/npm/lib/profile.js +++ b/deps/npm/lib/profile.js @@ -82,7 +82,18 @@ function config () { registry: npm.config.get('registry'), otp: npm.config.get('otp') } - conf.auth = npm.config.getCredentialsByURI(conf.registry) + const creds = npm.config.getCredentialsByURI(conf.registry) + if (creds.token) { + conf.auth = {token: creds.token} + } else if (creds.username) { + conf.auth = {basic: {username: creds.username, password: creds.password}} + } else if (creds.auth) { + const auth = Buffer.from(creds.auth, 'base64').toString().split(':', 2) + conf.auth = {basic: {username: auth[0], password: auth[1]}} + } else { + conf.auth = {} + } + if (conf.otp) conf.auth.otp = conf.otp return conf } @@ -155,12 +166,17 @@ function set (args) { return Promise.reject(Error(`"${prop}" is not a property we can set. Valid properties are: ` + writableProfileKeys.join(', '))) } return Bluebird.try(() => { - if (prop !== 'password') return - return readUserInfo.password('Current password: ').then((current) => { - return readPasswords().then((newpassword) => { - value = {old: current, new: newpassword} + if (prop === 'password') { + return readUserInfo.password('Current password: ').then((current) => { + return readPasswords().then((newpassword) => { + value = {old: current, new: newpassword} + }) }) - }) + } else if (prop === 'email') { + return readUserInfo.password('Password: ').then((current) => { + return {password: current, email: value} + }) + } function readPasswords () { return readUserInfo.password('New password: ').then((password1) => { return readUserInfo.password(' Again: ').then((password2) => { diff --git a/deps/npm/lib/prune.js b/deps/npm/lib/prune.js index 4ac8139576..010e471e4b 100644 --- a/deps/npm/lib/prune.js +++ b/deps/npm/lib/prune.js @@ -26,6 +26,7 @@ function prune (args, cb) { function Pruner (where, dryrun, args) { Installer.call(this, where, dryrun, args) + this.autoPrune = true } util.inherits(Pruner, Installer) @@ -64,3 +65,4 @@ Pruner.prototype.loadAllDepsIntoIdealTree = function (cb) { Pruner.prototype.runPreinstallTopLevelLifecycles = function (cb) { cb() } Pruner.prototype.runPostinstallTopLevelLifecycles = function (cb) { cb() } +Pruner.prototype.saveToDependencies = function (cb) { cb() } diff --git a/deps/npm/lib/shrinkwrap.js b/deps/npm/lib/shrinkwrap.js index ddfff2c681..603056ec94 100644 --- a/deps/npm/lib/shrinkwrap.js +++ b/deps/npm/lib/shrinkwrap.js @@ -4,6 +4,7 @@ const BB = require('bluebird') const chain = require('slide').chain const detectIndent = require('detect-indent') +const detectNewline = require('detect-newline') const readFile = BB.promisify(require('graceful-fs').readFile) const getRequested = require('./install/get-requested.js') const id = require('./install/deps.js') @@ -18,6 +19,7 @@ const npm = require('./npm.js') const path = require('path') const readPackageTree = BB.promisify(require('read-package-tree')) const ssri = require('ssri') +const stringifyPackage = require('./utils/stringify-package') const validate = require('aproba') const writeFileAtomic = require('write-file-atomic') const unixFormatPath = require('./utils/unix-format-path.js') @@ -179,11 +181,12 @@ function save (dir, pkginfo, opts, cb) { { path: path.resolve(dir, opts.defaultFile || PKGLOCK), data: '{}', - indent: (pkg && pkg.indent) || 2 + indent: pkg && pkg.indent, + newline: pkg && pkg.newline } ) - const updated = updateLockfileMetadata(pkginfo, pkg && pkg.data) - const swdata = JSON.stringify(updated, null, info.indent) + '\n' + const updated = updateLockfileMetadata(pkginfo, pkg && JSON.parse(pkg.raw)) + const swdata = stringifyPackage(updated, info.indent, info.newline) if (swdata === info.raw) { // skip writing if file is identical log.verbose('shrinkwrap', `skipping write for ${path.basename(info.path)} because there were no changes.`) @@ -244,9 +247,8 @@ function checkPackageFile (dir, name) { return { path: file, raw: data, - data: JSON.parse(data), - indent: detectIndent(data).indent || 2 + indent: detectIndent(data).indent, + newline: detectNewline(data) } }).catch({code: 'ENOENT'}, () => {}) } - diff --git a/deps/npm/lib/token.js b/deps/npm/lib/token.js index 2a3b65e6ad..350582782d 100644 --- a/deps/npm/lib/token.js +++ b/deps/npm/lib/token.js @@ -13,6 +13,8 @@ const pulseTillDone = require('./utils/pulse-till-done.js') module.exports = token +token._validateCIDRList = validateCIDRList + token.usage = 'npm token list\n' + 'npm token revoke <tokenKey>\n' + @@ -81,7 +83,17 @@ function config () { registry: npm.config.get('registry'), otp: npm.config.get('otp') } - conf.auth = npm.config.getCredentialsByURI(conf.registry) + const creds = npm.config.getCredentialsByURI(conf.registry) + if (creds.token) { + conf.auth = {token: creds.token} + } else if (creds.username) { + conf.auth = {basic: {username: creds.username, password: creds.password}} + } else if (creds.auth) { + const auth = Buffer.from(creds.auth, 'base64').toString().split(':', 2) + conf.auth = {basic: {username: auth[0], password: auth[1]}} + } else { + conf.auth = {} + } if (conf.otp) conf.auth.otp = conf.otp return conf } @@ -149,8 +161,14 @@ function rm (args) { } }) return Bluebird.map(toRemove, (key) => { - progress.info('token', 'removing', key) - profile.removeToken(key, conf).then(() => profile.completeWork(1)) + return profile.removeToken(key, conf).catch((ex) => { + if (ex.code !== 'EOTP') throw ex + log.info('token', 'failed because revoking this token requires OTP') + return readUserInfo.otp('Authenticator provided OTP:').then((otp) => { + conf.auth.otp = otp + return profile.removeToken(key, conf) + }) + }) }) })).then(() => { if (conf.json) { @@ -205,7 +223,8 @@ function validateCIDR (cidr) { } function validateCIDRList (cidrs) { - const list = Array.isArray(cidrs) ? cidrs : cidrs ? cidrs.split(/,\s*/) : [] + const maybeList = cidrs ? (Array.isArray(cidrs) ? cidrs : [cidrs]) : [] + const list = maybeList.length === 1 ? maybeList[0].split(/,\s*/) : maybeList list.forEach(validateCIDR) return list } diff --git a/deps/npm/lib/utils/error-message.js b/deps/npm/lib/utils/error-message.js index 85504f5edc..cd31d7d714 100644 --- a/deps/npm/lib/utils/error-message.js +++ b/deps/npm/lib/utils/error-message.js @@ -23,8 +23,17 @@ function errorMessage (er) { case 'EACCES': case 'EPERM': short.push(['', er]) - detail.push(['', ['\nPlease try running this command again as root/Administrator.' - ].join('\n')]) + detail.push([ + '', + [ + '\nThe operation was rejected by your operating system.', + (process.platform === 'win32' + ? 'It\'s possible that the file was already in use (by a text editor or antivirus),\nor that you lack permissions to access it.' + : 'It is likely you do not have the permissions to access this file as the current user'), + '\nIf you believe this might be a permissions issue, please double-check the', + 'permissions of the file and its containing directories, or try running', + 'the command again as root/Administrator (though this is not recommended).' + ].join('\n')]) break case 'ELIFECYCLE': @@ -52,6 +61,25 @@ function errorMessage (er) { break case 'EJSONPARSE': + const path = require('path') + // Check whether we ran into a conflict in our own package.json + if (er.file === path.join(npm.prefix, 'package.json')) { + const isDiff = require('../install/read-shrinkwrap.js')._isDiff + const txt = require('fs').readFileSync(er.file, 'utf8') + if (isDiff(txt)) { + detail.push([ + '', + [ + 'Merge conflict detected in your package.json.', + '', + 'Please resolve the package.json conflict and retry the command:', + '', + `$ ${process.argv.join(' ')}` + ].join('\n') + ]) + break + } + } short.push(['', er.message]) short.push(['', 'File: ' + er.file]) detail.push([ diff --git a/deps/npm/lib/utils/parse-json.js b/deps/npm/lib/utils/parse-json.js index 5c0b959a0d..c4149d282d 100644 --- a/deps/npm/lib/utils/parse-json.js +++ b/deps/npm/lib/utils/parse-json.js @@ -1,6 +1,7 @@ 'use strict' +var parseJsonWithErrors = require('json-parse-better-errors') var parseJSON = module.exports = function (content) { - return JSON.parse(stripBOM(content)) + return parseJsonWithErrors(stripBOM(content)) } parseJSON.noExceptions = function (content) { diff --git a/deps/npm/lib/utils/stringify-package.js b/deps/npm/lib/utils/stringify-package.js new file mode 100644 index 0000000000..0cc9de0a36 --- /dev/null +++ b/deps/npm/lib/utils/stringify-package.js @@ -0,0 +1,17 @@ +'use strict' + +module.exports = stringifyPackage + +const DEFAULT_INDENT = 2 +const CRLF = '\r\n' +const LF = '\n' + +function stringifyPackage (data, indent, newline) { + const json = JSON.stringify(data, null, indent || DEFAULT_INDENT) + + if (newline === CRLF) { + return json.replace(/\n/g, CRLF) + CRLF + } + + return json + LF +} diff --git a/deps/npm/lib/utils/unsupported.js b/deps/npm/lib/utils/unsupported.js index b586d035ce..15fa7396d0 100644 --- a/deps/npm/lib/utils/unsupported.js +++ b/deps/npm/lib/utils/unsupported.js @@ -5,8 +5,7 @@ var supportedNode = [ {ver: '6', min: '6.0.0'}, {ver: '7', min: '7.0.0'}, {ver: '8', min: '8.0.0'}, - {ver: '9', min: '9.0.0'}, - {ver: '10', min: '10.0.0'} + {ver: '9', min: '9.0.0'} ] var knownBroken = '<4.7.0' @@ -26,7 +25,7 @@ exports.checkForBrokenNode = function () { supportedNode.forEach(function (rel) { if (semver.satisfies(nodejs.version, rel.ver)) { console.error('Node.js ' + rel.ver + " is supported but the specific version you're running has") - console.error(`a bug known to break npm. Please update to at least ${rel.min} to use this`) + console.error('a bug known to break npm. Please update to at least ' + rel.min + ' to use this') console.error('version of npm. You can find the latest release of Node.js at https://nodejs.org/') process.exit(1) } diff --git a/deps/npm/lib/version.js b/deps/npm/lib/version.js index edcd664f2a..23880b61a5 100644 --- a/deps/npm/lib/version.js +++ b/deps/npm/lib/version.js @@ -4,6 +4,7 @@ const BB = require('bluebird') const assert = require('assert') const chain = require('slide').chain const detectIndent = require('detect-indent') +const detectNewline = require('detect-newline') const fs = require('graceful-fs') const readFile = BB.promisify(require('graceful-fs').readFile) const git = require('./utils/git.js') @@ -14,6 +15,7 @@ const output = require('./utils/output.js') const parseJSON = require('./utils/parse-json.js') const path = require('path') const semver = require('semver') +const stringifyPackage = require('./utils/stringify-package') const writeFileAtomic = require('write-file-atomic') version.usage = 'npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]' + @@ -33,7 +35,7 @@ function version (args, silent, cb_) { } if (args.length > 1) return cb_(version.usage) - readPackage(function (er, data, indent) { + readPackage(function (er, data, indent, newline) { if (!args.length) return dump(data, cb_) if (er) { @@ -115,14 +117,16 @@ function readPackage (cb) { fs.readFile(packagePath, 'utf8', function (er, data) { if (er) return cb(new Error(er)) var indent + var newline try { - indent = detectIndent(data).indent || ' ' + indent = detectIndent(data).indent + newline = detectNewline(data) data = JSON.parse(data) } catch (e) { er = e data = null } - cb(er, data, indent) + cb(er, data, indent, newline) }) } @@ -132,10 +136,10 @@ function updatePackage (newVersion, silent, cb_) { cb_(er) } - readPackage(function (er, data, indent) { + readPackage(function (er, data, indent, newline) { if (er) return cb(new Error(er)) data.version = newVersion - write(data, 'package.json', indent, cb) + write(data, 'package.json', indent, newline, cb) }) } @@ -168,15 +172,17 @@ function updateShrinkwrap (newVersion, cb) { const file = shrinkwrap ? SHRINKWRAP : PKGLOCK let data let indent + let newline try { data = parseJSON(shrinkwrap || lockfile) - indent = detectIndent(shrinkwrap || lockfile).indent || ' ' + indent = detectIndent(shrinkwrap || lockfile).indent + newline = detectNewline(shrinkwrap || lockfile) } catch (err) { log.error('version', `Bad ${file} data.`) return cb(err) } data.version = newVersion - write(data, file, indent, (err) => { + write(data, file, indent, newline, (err) => { if (err) { log.error('version', `Failed to update version in ${file}`) return cb(err) @@ -321,14 +327,14 @@ function addLocalFile (file, options, ignoreFailure) { : p } -function write (data, file, indent, cb) { +function write (data, file, indent, newline, cb) { assert(data && typeof data === 'object', 'must pass data to version write') assert(typeof file === 'string', 'must pass filename to write to version write') log.verbose('version.write', 'data', data, 'to', file) writeFileAtomic( path.join(npm.localPrefix, file), - new Buffer(JSON.stringify(data, null, indent || 2) + '\n'), + new Buffer(stringifyPackage(data, indent, newline)), cb ) } diff --git a/deps/npm/lib/xmas.js b/deps/npm/lib/xmas.js index 25535533e1..65c0c131ab 100644 --- a/deps/npm/lib/xmas.js +++ b/deps/npm/lib/xmas.js @@ -48,7 +48,7 @@ module.exports = function (args, cb) { w('\n\n') log.heading = '' log.addLevel('npm', 100000, log.headingStyle) - log.npm('loves you', 'Happy Xmas, Noders!') + log.npm('loves you', 'Happy Xmas, JavaScripters!') cb() } var dg = false |