summaryrefslogtreecommitdiff
path: root/deps/npm/lib/install
diff options
context:
space:
mode:
Diffstat (limited to 'deps/npm/lib/install')
-rw-r--r--deps/npm/lib/install/action/extract-worker.js4
-rw-r--r--deps/npm/lib/install/action/extract.js32
-rw-r--r--deps/npm/lib/install/action/fetch.js2
-rw-r--r--deps/npm/lib/install/actions.js2
-rw-r--r--deps/npm/lib/install/audit.js272
-rw-r--r--deps/npm/lib/install/copy-tree.js17
-rw-r--r--deps/npm/lib/install/decompose-actions.js79
-rw-r--r--deps/npm/lib/install/deps.js130
-rw-r--r--deps/npm/lib/install/diff-trees.js42
-rw-r--r--deps/npm/lib/install/get-requested.js4
-rw-r--r--deps/npm/lib/install/has-modern-meta.js20
-rw-r--r--deps/npm/lib/install/inflate-shrinkwrap.js81
-rw-r--r--deps/npm/lib/install/is-only-optional.js3
-rw-r--r--deps/npm/lib/install/read-shrinkwrap.js68
-rw-r--r--deps/npm/lib/install/save.js12
15 files changed, 606 insertions, 162 deletions
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/action/fetch.js b/deps/npm/lib/install/action/fetch.js
index a4d760fe82..5ad34e29dd 100644
--- a/deps/npm/lib/install/action/fetch.js
+++ b/deps/npm/lib/install/action/fetch.js
@@ -12,5 +12,5 @@ function fetch (staging, pkg, log, next) {
log.silly('fetch', packageId(pkg))
const opts = pacoteOpts({integrity: pkg.package._integrity})
return finished(pacote.tarball.stream(pkg.package._requested, opts))
- .then(() => next(), next)
+ .then(() => next(), next)
}
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/audit.js b/deps/npm/lib/install/audit.js
new file mode 100644
index 0000000000..4be59ca7c5
--- /dev/null
+++ b/deps/npm/lib/install/audit.js
@@ -0,0 +1,272 @@
+'use strict'
+exports.generate = generate
+exports.generateFromInstall = generateFromInstall
+exports.submitForInstallReport = submitForInstallReport
+exports.submitForFullReport = submitForFullReport
+exports.printInstallReport = printInstallReport
+exports.printFullReport = printFullReport
+
+const Bluebird = require('bluebird')
+const auditReport = require('npm-audit-report')
+const treeToShrinkwrap = require('../shrinkwrap.js').treeToShrinkwrap
+const packageId = require('../utils/package-id.js')
+const output = require('../utils/output.js')
+const npm = require('../npm.js')
+const qw = require('qw')
+const registryFetch = require('npm-registry-fetch')
+const zlib = require('zlib')
+const gzip = Bluebird.promisify(zlib.gzip)
+const log = require('npmlog')
+const perf = require('../utils/perf.js')
+const url = require('url')
+const npa = require('npm-package-arg')
+const uuid = require('uuid')
+const ssri = require('ssri')
+const cloneDeep = require('lodash.clonedeep')
+const pacoteOpts = require('../config/pacote.js')
+
+// used when scrubbing module names/specifiers
+const runId = uuid.v4()
+
+function submitForInstallReport (auditData) {
+ const cfg = npm.config // avoid the no-dynamic-lookups test
+ const scopedRegistries = cfg.keys.filter(_ => /:registry$/.test(_)).map(_ => cfg.get(_))
+ perf.emit('time', 'audit compress')
+ // TODO: registryFetch will be adding native support for `Content-Encoding: gzip` at which point
+ // we'll pass in something like `gzip: true` and not need to JSON stringify, gzip or headers.
+ return gzip(JSON.stringify(auditData)).then(body => {
+ perf.emit('timeEnd', 'audit compress')
+ log.info('audit', 'Submitting payload of ' + body.length + 'bytes')
+ scopedRegistries.forEach(reg => {
+ // we don't care about the response so destroy the stream if we can, or leave it flowing
+ // so it can eventually finish and clean up after itself
+ fetchAudit(url.resolve(reg, '/-/npm/v1/security/audits/quick'))
+ .then(_ => {
+ _.body.on('error', () => {})
+ if (_.body.destroy) {
+ _.body.destroy()
+ } else {
+ _.body.resume()
+ }
+ }, _ => {})
+ })
+ perf.emit('time', 'audit submit')
+ return fetchAudit('/-/npm/v1/security/audits/quick', body).then(response => {
+ perf.emit('timeEnd', 'audit submit')
+ perf.emit('time', 'audit body')
+ return response.json()
+ }).then(result => {
+ perf.emit('timeEnd', 'audit body')
+ return result
+ })
+ })
+}
+
+function submitForFullReport (auditData) {
+ perf.emit('time', 'audit compress')
+ // TODO: registryFetch will be adding native support for `Content-Encoding: gzip` at which point
+ // we'll pass in something like `gzip: true` and not need to JSON stringify, gzip or headers.
+ return gzip(JSON.stringify(auditData)).then(body => {
+ perf.emit('timeEnd', 'audit compress')
+ log.info('audit', 'Submitting payload of ' + body.length + ' bytes')
+ perf.emit('time', 'audit submit')
+ return fetchAudit('/-/npm/v1/security/audits', body).then(response => {
+ perf.emit('timeEnd', 'audit submit')
+ perf.emit('time', 'audit body')
+ return response.json()
+ }).then(result => {
+ perf.emit('timeEnd', 'audit body')
+ result.runId = runId
+ return result
+ })
+ })
+}
+
+function fetchAudit (href, body) {
+ const opts = pacoteOpts()
+ return registryFetch(href, {
+ method: 'POST',
+ headers: { 'Content-Encoding': 'gzip', 'Content-Type': 'application/json' },
+ config: npm.config,
+ npmSession: opts.npmSession,
+ projectScope: npm.projectScope,
+ log: log,
+ body: body
+ })
+}
+
+function printInstallReport (auditResult) {
+ return auditReport(auditResult, {
+ reporter: 'install',
+ withColor: npm.color,
+ withUnicode: npm.config.get('unicode')
+ }).then(result => output(result.report))
+}
+
+function printFullReport (auditResult) {
+ return auditReport(auditResult, {
+ log: output,
+ reporter: npm.config.get('json') ? 'json' : 'detail',
+ withColor: npm.color,
+ withUnicode: npm.config.get('unicode')
+ }).then(result => output(result.report))
+}
+
+function generate (shrinkwrap, requires, diffs, install, remove) {
+ const sw = cloneDeep(shrinkwrap)
+ delete sw.lockfileVersion
+ sw.requires = scrubRequires(requires)
+ scrubDeps(sw.dependencies)
+
+ // sw.diffs = diffs || {}
+ sw.install = (install || []).map(scrubArg)
+ sw.remove = (remove || []).map(scrubArg)
+ return generateMetadata().then((md) => {
+ sw.metadata = md
+ return sw
+ })
+}
+
+const scrubKeys = qw`version`
+const deleteKeys = qw`from resolved`
+
+function scrubDeps (deps) {
+ if (!deps) return
+ Object.keys(deps).forEach(name => {
+ if (!shouldScrubName(name) && !shouldScrubSpec(name, deps[name].version)) return
+ const value = deps[name]
+ delete deps[name]
+ deps[scrub(name)] = value
+ })
+ Object.keys(deps).forEach(name => {
+ for (let toScrub of scrubKeys) {
+ if (!deps[name][toScrub]) continue
+ deps[name][toScrub] = scrubSpec(name, deps[name][toScrub])
+ }
+ for (let toDelete of deleteKeys) delete deps[name][toDelete]
+
+ scrubRequires(deps[name].requires)
+ scrubDeps(deps[name].dependencies)
+ })
+}
+
+function scrubRequires (reqs) {
+ if (!reqs) return reqs
+ Object.keys(reqs).forEach(name => {
+ const spec = reqs[name]
+ if (shouldScrubName(name) || shouldScrubSpec(name, spec)) {
+ delete reqs[name]
+ reqs[scrub(name)] = scrubSpec(name, spec)
+ } else {
+ reqs[name] = scrubSpec(name, spec)
+ }
+ })
+ return reqs
+}
+
+function getScope (name) {
+ if (name[0] === '@') return name.slice(0, name.indexOf('/'))
+}
+
+function shouldScrubName (name) {
+ const scope = getScope(name)
+ const cfg = npm.config // avoid the no-dynamic-lookups test
+ return Boolean(scope && cfg.get(scope + ':registry'))
+}
+function shouldScrubSpec (name, spec) {
+ const req = npa.resolve(name, spec)
+ return !req.registry
+}
+
+function scrubArg (arg) {
+ const req = npa(arg)
+ let name = req.name
+ if (shouldScrubName(name) || shouldScrubSpec(name, req.rawSpec)) {
+ name = scrubName(name)
+ }
+ const spec = scrubSpec(req.name, req.rawSpec)
+ return name + '@' + spec
+}
+
+function scrubName (name) {
+ return shouldScrubName(name) ? scrub(name) : name
+}
+
+function scrubSpec (name, spec) {
+ const req = npa.resolve(name, spec)
+ if (req.registry) return spec
+ if (req.type === 'git') {
+ return 'git+ssh://' + scrub(spec)
+ } else if (req.type === 'remote') {
+ return 'https://' + scrub(spec)
+ } else if (req.type === 'directory') {
+ return 'file:' + scrub(spec)
+ } else if (req.type === 'file') {
+ return 'file:' + scrub(spec) + '.tar'
+ } else {
+ return scrub(spec)
+ }
+}
+
+module.exports.scrub = scrub
+function scrub (value, rid) {
+ return ssri.fromData((rid || runId) + ' ' + value, {algorithms: ['sha256']}).hexDigest()
+}
+
+function generateMetadata () {
+ const meta = {}
+ meta.npm_version = npm.version
+ meta.node_version = process.version
+ meta.platform = process.platform
+ meta.node_env = process.env.NODE_ENV
+
+ return Promise.resolve(meta)
+}
+/*
+ const head = path.resolve(npm.prefix, '.git/HEAD')
+ return readFile(head, 'utf8').then((head) => {
+ if (!head.match(/^ref: /)) {
+ meta.commit_hash = head.trim()
+ return
+ }
+ const headFile = head.replace(/^ref: /, '').trim()
+ meta.branch = headFile.replace(/^refs[/]heads[/]/, '')
+ return readFile(path.resolve(npm.prefix, '.git', headFile), 'utf8')
+ }).then((commitHash) => {
+ meta.commit_hash = commitHash.trim()
+ const proc = spawn('git', qw`diff --quiet --exit-code package.json package-lock.json`, {cwd: npm.prefix, stdio: 'ignore'})
+ return new Promise((resolve, reject) => {
+ proc.once('error', reject)
+ proc.on('exit', (code, signal) => {
+ if (signal == null) meta.state = code === 0 ? 'clean' : 'dirty'
+ resolve()
+ })
+ })
+ }).then(() => meta, () => meta)
+*/
+
+function generateFromInstall (tree, diffs, install, remove) {
+ const requires = {}
+ tree.requires.forEach((pkg) => {
+ requires[pkg.package.name] = tree.package.dependencies[pkg.package.name] || tree.package.devDependencies[pkg.package.name] || pkg.package.version
+ })
+
+ const auditInstall = (install || []).filter((a) => a.name).map(packageId)
+ const auditRemove = (remove || []).filter((a) => a.name).map(packageId)
+ const auditDiffs = {}
+ diffs.forEach((action) => {
+ const mutation = action[0]
+ const child = action[1]
+ if (mutation !== 'add' && mutation !== 'update' && mutation !== 'remove') return
+ if (!auditDiffs[mutation]) auditDiffs[mutation] = []
+ if (mutation === 'add') {
+ auditDiffs[mutation].push({location: child.location})
+ } else if (mutation === 'update') {
+ auditDiffs[mutation].push({location: child.location, previous: packageId(child.oldPkg)})
+ } else if (mutation === 'remove') {
+ auditDiffs[mutation].push({previous: packageId(child)})
+ }
+ })
+
+ return generate(treeToShrinkwrap(tree), requires, auditDiffs, auditInstall, auditRemove)
+}
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/decompose-actions.js b/deps/npm/lib/install/decompose-actions.js
index 57dc7cd687..ba08e6e768 100644
--- a/deps/npm/lib/install/decompose-actions.js
+++ b/deps/npm/lib/install/decompose-actions.js
@@ -1,72 +1,79 @@
'use strict'
var validate = require('aproba')
-var asyncMap = require('slide').asyncMap
var npm = require('../npm.js')
module.exports = function (differences, decomposed, next) {
validate('AAF', arguments)
- asyncMap(differences, function (action, done) {
+ differences.forEach((action) => {
var cmd = action[0]
var pkg = action[1]
switch (cmd) {
case 'add':
- addSteps(decomposed, pkg, done)
+ addSteps(decomposed, pkg)
break
case 'update':
- updateSteps(decomposed, pkg, done)
+ updateSteps(decomposed, pkg)
break
case 'move':
- moveSteps(decomposed, pkg, done)
+ moveSteps(decomposed, pkg)
break
case 'remove':
- removeSteps(decomposed, pkg, done)
+ removeSteps(decomposed, pkg)
break
default:
- defaultSteps(decomposed, cmd, pkg, done)
+ defaultSteps(decomposed, cmd, pkg)
}
- }, next)
+ })
+ next()
+}
+
+function addAction (decomposed, action, pkg) {
+ if (decomposed.some((_) => _[0] === action && _[1] === pkg)) return
+ decomposed.push([action, pkg])
}
-function addSteps (decomposed, pkg, done) {
+function addSteps (decomposed, pkg) {
+ if (pkg.fromBundle) {
+ // make sure our source module exists to extract ourselves from
+ // if we're installing our source module anyway, the duplication
+ // of these steps will be elided by `addAction` automatically
+ addAction(decomposed, 'fetch', pkg.fromBundle)
+ addAction(decomposed, 'extract', pkg.fromBundle)
+ }
if (!pkg.fromBundle && !pkg.isLink) {
- decomposed.push(['fetch', pkg])
- decomposed.push(['extract', pkg])
+ addAction(decomposed, 'fetch', pkg)
+ addAction(decomposed, 'extract', pkg)
}
if (!pkg.fromBundle || npm.config.get('rebuild-bundle')) {
- decomposed.push(['preinstall', pkg])
- decomposed.push(['build', pkg])
- decomposed.push(['install', pkg])
- decomposed.push(['postinstall', pkg])
+ addAction(decomposed, 'preinstall', pkg)
+ addAction(decomposed, 'build', pkg)
+ addAction(decomposed, 'install', pkg)
+ addAction(decomposed, 'postinstall', pkg)
}
if (!pkg.fromBundle || !pkg.isLink) {
- decomposed.push(['finalize', pkg])
+ addAction(decomposed, 'finalize', pkg)
}
- decomposed.push(['refresh-package-json', pkg])
- done()
+ addAction(decomposed, 'refresh-package-json', pkg)
}
-function updateSteps (decomposed, pkg, done) {
- removeSteps(decomposed, pkg.oldPkg, () => {
- addSteps(decomposed, pkg, done)
- })
+function updateSteps (decomposed, pkg) {
+ removeSteps(decomposed, pkg.oldPkg)
+ addSteps(decomposed, pkg)
}
-function removeSteps (decomposed, pkg, done) {
- decomposed.push(['unbuild', pkg])
- decomposed.push(['remove', pkg])
- done()
+function removeSteps (decomposed, pkg) {
+ addAction(decomposed, 'unbuild', pkg)
+ addAction(decomposed, 'remove', pkg)
}
-function moveSteps (decomposed, pkg, done) {
- decomposed.push(['move', pkg])
- decomposed.push(['build', pkg])
- decomposed.push(['install', pkg])
- decomposed.push(['postinstall', pkg])
- decomposed.push(['refresh-package-json', pkg])
- done()
+function moveSteps (decomposed, pkg) {
+ addAction(decomposed, 'move', pkg)
+ addAction(decomposed, 'build', pkg)
+ addAction(decomposed, 'install', pkg)
+ addAction(decomposed, 'postinstall', pkg)
+ addAction(decomposed, 'refresh-package-json', pkg)
}
-function defaultSteps (decomposed, cmd, pkg, done) {
- decomposed.push([cmd, pkg])
- done()
+function defaultSteps (decomposed, cmd, pkg) {
+ addAction(decomposed, cmd, pkg)
}
diff --git a/deps/npm/lib/install/deps.js b/deps/npm/lib/install/deps.js
index 93c4adffd7..c36265093b 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.
@@ -50,6 +51,12 @@ function doesChildVersionMatch (child, requested, requestor) {
return path.relative(child.realpath, requested.fetchSpec) === ''
}
+ if (requested.type === 'git' && child.fromShrinkwrap) {
+ const fromSw = child.package._from ? npa(child.package._from) : child.fromShrinkwrap
+ fromSw.name = requested.name // we're only checking specifiers here
+ if (fromSw.toString() === requested.toString()) return true
+ }
+
if (!registryTypes[requested.type]) {
var childReq = child.package._requested
if (childReq) {
@@ -65,7 +72,7 @@ function doesChildVersionMatch (child, requested, requestor) {
// You'll see this scenario happen with at least tags and git dependencies.
// Some buggy clients will write spaces into the module name part of a _from.
if (child.package._from) {
- var fromReq = npa.resolve(moduleName(child), child.package._from.replace(new RegExp('^\s*' + moduleName(child) + '\s*@'), ''))
+ var fromReq = npa.resolve(moduleName(child), child.package._from.replace(new RegExp('^\\s*' + moduleName(child) + '\\s*@'), ''))
if (fromReq.rawSpec === requested.rawSpec) return true
if (fromReq.type === requested.type && fromReq.saveSpec && fromReq.saveSpec === requested.saveSpec) return true
}
@@ -78,8 +85,8 @@ function doesChildVersionMatch (child, requested, requestor) {
}
}
-function childDependencySpecifier (tree, name, spec) {
- return npa.resolve(name, spec, packageRelativePath(tree))
+function childDependencySpecifier (tree, name, spec, where) {
+ return npa.resolve(name, spec, where || packageRelativePath(tree))
}
exports.computeMetadata = computeMetadata
@@ -104,14 +111,13 @@ function computeMetadata (tree, seen) {
resolveWithExistingModule(child, tree)
return true
}
- return
}
const deps = tree.package.dependencies || {}
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) {
@@ -186,15 +192,14 @@ function packageRelativePath (tree) {
var requested = tree.package._requested || {}
var isLocal = requested.type === 'directory' || requested.type === 'file'
return isLocal ? requested.fetchSpec
- : (tree.isLink || tree.isInLink) && !preserveSymlinks() ? tree.realpath
- : tree.path
+ : (tree.isLink || tree.isInLink) && !preserveSymlinks() ? tree.realpath
+ : tree.path
}
function matchingDep (tree, name) {
if (!tree || !tree.package) return
if (tree.package.dependencies && tree.package.dependencies[name]) return tree.package.dependencies[name]
if (tree.package.devDependencies && tree.package.devDependencies[name]) return tree.package.devDependencies[name]
- return
}
exports.getAllMetadata = function (args, tree, where, next) {
@@ -261,6 +266,7 @@ exports.loadRequestedDeps = function (args, tree, saveToDependencies, log, next)
delete tree.package[saveType][childName]
}
}
+ if (child.save === 'optionalDependencies') tree.package.dependencies[childName] = child.saveSpec
}
// For things the user asked to install, that aren't a dependency (or
@@ -282,10 +288,12 @@ function computeVersionSpec (tree, child) {
validate('OO', arguments)
var requested
var childReq = child.package._requested
- if (childReq && (isNotEmpty(childReq.saveSpec) || (isNotEmpty(childReq.rawSpec) && isNotEmpty(childReq.fetchSpec)))) {
+ if (child.isLink) {
+ requested = npa.resolve(child.package.name, 'file:' + child.realpath, getTop(tree).path)
+ } else if (childReq && (isNotEmpty(childReq.saveSpec) || (isNotEmpty(childReq.rawSpec) && isNotEmpty(childReq.fetchSpec)))) {
requested = child.package._requested
} else if (child.package._from) {
- requested = npa(child.package._from)
+ requested = npa(child.package._from, tree.path)
} else {
requested = npa.resolve(child.package.name, child.package.version)
}
@@ -299,7 +307,7 @@ function computeVersionSpec (tree, child) {
}
return rangeDescriptor + version
} else if (requested.type === 'directory' || requested.type === 'file') {
- return 'file:' + unixFormatPath(path.relative(tree.path, requested.fetchSpec))
+ return 'file:' + unixFormatPath(path.relative(getTop(tree).path, requested.fetchSpec))
} else {
return requested.saveSpec || requested.rawSpec
}
@@ -332,9 +340,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)
@@ -369,8 +389,22 @@ function andForEachChild (load, next) {
function isDepOptional (tree, name, pkg) {
if (pkg.package && pkg.package._optional) return true
- if (!tree.package.optionalDependencies) return false
- if (tree.package.optionalDependencies[name] != null) return true
+ const optDeps = tree.package.optionalDependencies
+ if (optDeps && optDeps[name] != null) return true
+
+ const devDeps = tree.package.devDependencies
+ if (devDeps && devDeps[name] != null) {
+ const includeDev = npm.config.get('dev') ||
+ (!/^prod(uction)?$/.test(npm.config.get('only')) && !npm.config.get('production')) ||
+ /^dev(elopment)?$/.test(npm.config.get('only')) ||
+ /^dev(elopment)?$/.test(npm.config.get('also'))
+ return !includeDev
+ }
+ const prodDeps = tree.package.dependencies
+ if (prodDeps && prodDeps[name] != null) {
+ const includeProd = !/^dev(elopment)?$/.test(npm.config.get('only'))
+ return !includeProd
+ }
return false
}
@@ -461,12 +495,6 @@ function loadDeps (tree, log, next) {
if (!tree.package.dependencies) tree.package.dependencies = {}
asyncMap(Object.keys(tree.package.dependencies), function (dep, done) {
var version = tree.package.dependencies[dep]
- if (tree.package.optionalDependencies &&
- tree.package.optionalDependencies[dep] &&
- !npm.config.get('optional')) {
- return done()
- }
-
addDependency(dep, version, tree, log.newGroup('loadDep:' + dep), andHandleOptionalErrors(log, tree, dep, done))
}, andForEachChild(loadDeps, andFinishTracker(log, next)))
}
@@ -481,7 +509,7 @@ exports.loadDevDeps = function (tree, log, next) {
if (tree.package.dependencies[dep]) return done()
var logGroup = log.newGroup('loadDevDep:' + dep)
- addDependency(dep, tree.package.devDependencies[dep], tree, logGroup, done)
+ addDependency(dep, tree.package.devDependencies[dep], tree, logGroup, andHandleOptionalErrors(log, tree, dep, done))
}, andForEachChild(loadDeps, andFinishTracker(log, next)))
}
@@ -519,14 +547,14 @@ function addDependency (name, versionSpec, tree, log, done) {
try {
var req = childDependencySpecifier(tree, name, versionSpec)
if (tree.swRequires && tree.swRequires[name]) {
- var swReq = childDependencySpecifier(tree, name, tree.swRequires[name])
+ var swReq = childDependencySpecifier(tree, name, tree.swRequires[name], tree.package._where)
}
} catch (err) {
return done(err)
}
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 +562,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 +650,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 +667,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 +755,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 +812,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..346846fdc0 100644
--- a/deps/npm/lib/install/diff-trees.js
+++ b/deps/npm/lib/install/diff-trees.js
@@ -8,6 +8,7 @@ var log = require('npmlog')
var path = require('path')
var ssri = require('ssri')
var moduleName = require('../utils/module-name.js')
+var isOnlyOptional = require('./is-only-optional.js')
// we don't use get-requested because we're operating on files on disk, and
// we don't want to extropolate from what _should_ be there.
@@ -50,7 +51,7 @@ function pkgIntegrity (pkg) {
if (Object.keys(integrity).length === 0) return
return integrity
} catch (ex) {
- return
+
}
}
@@ -70,6 +71,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
@@ -162,6 +166,11 @@ var sortActions = module.exports.sortActions = function (differences) {
sorted.unshift(action)
}
+ // safety net, anything excluded above gets tacked on the end
+ differences.forEach((_) => {
+ if (sorted.indexOf(_) === -1) sorted.push(_)
+ })
+
return sorted
}
@@ -213,9 +222,8 @@ var diffTrees = module.exports._diffTrees = function (oldTree, newTree) {
pkg.fromPath = toMv.pkg.path
setAction(differences, 'move', pkg)
delete toRemove[toMv.flatname]
- // we don't generate add actions for things found in links (which already exist on disk) or
- // for bundled modules (which will be installed when we install their parent)
- } else if (!(pkg.isInLink && pkg.fromBundle)) {
+ // we don't generate add actions for things found in links (which already exist on disk)
+ } else if (!pkg.isInLink || !(pkg.fromBundle && pkg.fromBundle.isLink)) {
setAction(differences, 'add', pkg)
}
}
@@ -227,18 +235,26 @@ var diffTrees = module.exports._diffTrees = function (oldTree, newTree) {
.map((flatname) => toRemove[flatname])
.forEach((pkg) => setAction(differences, 'remove', pkg))
+ return filterActions(differences)
+}
+
+function filterActions (differences) {
+ const includeOpt = npm.config.get('optional')
const includeDev = npm.config.get('dev') ||
(!/^prod(uction)?$/.test(npm.config.get('only')) && !npm.config.get('production')) ||
/^dev(elopment)?$/.test(npm.config.get('only')) ||
/^dev(elopment)?$/.test(npm.config.get('also'))
const includeProd = !/^dev(elopment)?$/.test(npm.config.get('only'))
- if (!includeProd || !includeDev) {
- log.silly('diff-trees', 'filtering actions:', 'includeDev', includeDev, 'includeProd', includeProd)
- differences = differences.filter((diff) => {
- const pkg = diff[1]
- const pkgIsOnlyDev = isOnlyDev(pkg)
- return (!includeProd && pkgIsOnlyDev) || (includeDev && pkgIsOnlyDev) || (includeProd && !pkgIsOnlyDev)
- })
- }
- return differences
+ if (includeProd && includeDev && includeOpt) return differences
+
+ log.silly('diff-trees', 'filtering actions:', 'includeDev', includeDev, 'includeProd', includeProd, 'includeOpt', includeOpt)
+ return differences.filter((diff) => {
+ const pkg = diff[1]
+ const pkgIsOnlyDev = isOnlyDev(pkg)
+ const pkgIsOnlyOpt = isOnlyOptional(pkg)
+ if (!includeProd && pkgIsOnlyDev) return true
+ if (includeDev && pkgIsOnlyDev) return true
+ if (includeProd && !pkgIsOnlyDev && (includeOpt || !pkgIsOnlyOpt)) return true
+ return false
+ })
}
diff --git a/deps/npm/lib/install/get-requested.js b/deps/npm/lib/install/get-requested.js
index f6c44d1463..ab410ffc9b 100644
--- a/deps/npm/lib/install/get-requested.js
+++ b/deps/npm/lib/install/get-requested.js
@@ -2,9 +2,9 @@
const npa = require('npm-package-arg')
const moduleName = require('../utils/module-name.js')
-module.exports = function (child) {
+module.exports = function (child, reqBy) {
if (!child.requiredBy.length) return
- const reqBy = child.requiredBy[0]
+ if (!reqBy) reqBy = child.requiredBy[0]
const deps = reqBy.package.dependencies || {}
const devDeps = reqBy.package.devDependencies || {}
const name = moduleName(child)
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..bf801d0d31
--- /dev/null
+++ b/deps/npm/lib/install/has-modern-meta.js
@@ -0,0 +1,20 @@
+'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)
+ const version = npa.resolve(moduleName(child), child.package.version)
+ return child.isTop ||
+ isLink(child) ||
+ child.fromBundle || child.package._inBundle ||
+ child.package._integrity || child.package._shasum ||
+ (resolved && resolved.type === 'git') || (version && version.type === 'git')
+}
diff --git a/deps/npm/lib/install/inflate-shrinkwrap.js b/deps/npm/lib/install/inflate-shrinkwrap.js
index 43ac9136f0..bf1ab70657 100644
--- a/deps/npm/lib/install/inflate-shrinkwrap.js
+++ b/deps/npm/lib/install/inflate-shrinkwrap.js
@@ -14,6 +14,9 @@ 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')
+const npa = require('npm-package-arg')
module.exports = function (tree, sw, opts, finishInflating) {
if (!fetchPackageMetadata) {
@@ -66,11 +69,43 @@ function normalizePackageDataNoErrors (pkg) {
}
}
+function quotemeta (str) {
+ return str.replace(/([^A-Za-z_0-9/])/g, '\\$1')
+}
+
+function tarballToVersion (name, tb) {
+ const registry = quotemeta(npm.config.get('registry'))
+ .replace(/https?:/, 'https?:')
+ .replace(/([^/])$/, '$1/')
+ let matchRegTarball
+ if (name) {
+ const nameMatch = quotemeta(name)
+ matchRegTarball = new RegExp(`^${registry}${nameMatch}/-/${nameMatch}-(.*)[.]tgz$`)
+ } else {
+ matchRegTarball = new RegExp(`^${registry}(.*)?/-/\\1-(.*)[.]tgz$`)
+ }
+ const match = tb.match(matchRegTarball)
+ if (!match) return
+ return match[2] || match[1]
+}
+
function inflatableChild (onDiskChild, name, topPath, tree, sw, requested, opts) {
validate('OSSOOOO|ZSSOOOO', arguments)
- if (onDiskChild && childIsEquivalent(sw, requested, onDiskChild)) {
+ const usesIntegrity = (
+ requested.registry ||
+ requested.type === 'remote' ||
+ requested.type === 'file'
+ )
+ const regTarball = tarballToVersion(name, sw.version)
+ if (regTarball) {
+ sw.resolved = sw.version
+ sw.version = regTarball
+ }
+ if (sw.requires) Object.keys(sw.requires).map(_ => { sw.requires[_] = tarballToVersion(_, sw.requires[_]) || sw.requires[_] })
+ const modernLink = requested.type === 'directory' && !sw.from
+ if (hasModernMeta(onDiskChild) && childIsEquivalent(sw, requested, onDiskChild)) {
// The version on disk matches the shrinkwrap entry.
- if (!onDiskChild.fromShrinkwrap) onDiskChild.fromShrinkwrap = true
+ if (!onDiskChild.fromShrinkwrap) onDiskChild.fromShrinkwrap = requested
onDiskChild.package._requested = requested
onDiskChild.package._spec = requested.rawSpec
onDiskChild.package._where = topPath
@@ -88,7 +123,7 @@ function inflatableChild (onDiskChild, name, topPath, tree, sw, requested, opts)
onDiskChild.swRequires = sw.requires
tree.children.push(onDiskChild)
return BB.resolve(onDiskChild)
- } else if ((sw.version && sw.integrity) || sw.bundled) {
+ } else if ((sw.version && (sw.integrity || !usesIntegrity) && (requested.type !== 'directory' || modernLink)) || sw.bundled) {
// The shrinkwrap entry has an integrity field. We can fake a pkg to get
// the installer to do a content-address fetch from the cache, if possible.
return BB.resolve(makeFakeChild(name, topPath, tree, sw, requested))
@@ -100,13 +135,18 @@ function inflatableChild (onDiskChild, name, topPath, tree, sw, requested, opts)
}
}
+function isGit (sw) {
+ const version = npa.resolve(sw.name, sw.version)
+ return (version && version.type === 'git')
+}
+
function makeFakeChild (name, topPath, tree, sw, requested) {
const from = sw.from || requested.raw
const pkg = {
name: name,
version: sw.version,
_id: name + '@' + sw.version,
- _resolved: adaptResolved(requested, sw.resolved),
+ _resolved: sw.resolved || (isGit(sw) && sw.version),
_requested: requested,
_optional: sw.optional,
_development: sw.dev,
@@ -127,15 +167,15 @@ function makeFakeChild (name, topPath, tree, sw, requested) {
}
const child = createChild({
package: pkg,
- loaded: true,
+ loaded: false,
parent: tree,
children: [],
- fromShrinkwrap: true,
+ fromShrinkwrap: requested,
fakeChild: sw,
fromBundle: sw.bundled ? tree.fromBundle || tree : null,
path: childPath(tree.path, pkg),
- realpath: childPath(tree.realpath, pkg),
- location: tree.location + '/' + pkg.name,
+ realpath: requested.type === 'directory' ? requested.fetchSpec : childPath(tree.realpath, pkg),
+ location: (tree.location === '/' ? '' : tree.location + '/') + pkg.name,
isLink: requested.type === 'directory',
isInLink: tree.isLink,
swRequires: sw.requires
@@ -144,23 +184,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
@@ -178,7 +201,7 @@ function fetchChild (topPath, tree, sw, requested) {
path: childPath(tree.path, pkg),
realpath: isLink ? requested.fetchSpec : childPath(tree.realpath, pkg),
children: pkg._bundled || [],
- location: tree.location + '/' + pkg.name,
+ location: (tree.location === '/' ? '' : tree.location + '/') + pkg.name,
fromBundle: null,
isLink: isLink,
isInLink: tree.isLink,
@@ -196,7 +219,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/is-only-optional.js b/deps/npm/lib/install/is-only-optional.js
index 7366e9abe1..72d6f065e6 100644
--- a/deps/npm/lib/install/is-only-optional.js
+++ b/deps/npm/lib/install/is-only-optional.js
@@ -11,8 +11,9 @@ function isOptional (node, seen) {
return false
}
seen.add(node)
-
+ const swOptional = node.fromShrinkwrap && node.package._optional
return node.requiredBy.every(function (req) {
+ if (req.fakeChild && swOptional) return true
return isOptDep(req, node.package.name) || isOptional(req, seen)
})
}
diff --git a/deps/npm/lib/install/read-shrinkwrap.js b/deps/npm/lib/install/read-shrinkwrap.js
index 45e883caa2..7074678011 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,59 @@ 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.`)
+ log.warn('conflict', 'To make this happen automatically on git rebase/merge, consider using the npm-merge-driver:')
+ log.warn('conflict', '$ npx npm-merge-driver install -g')
+ 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..8bafcbfc6b 100644
--- a/deps/npm/lib/install/save.js
+++ b/deps/npm/lib/install/save.js
@@ -1,8 +1,8 @@
'use strict'
-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 +10,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')
@@ -44,9 +45,9 @@ exports.saveShrinkwrap = saveShrinkwrap
function saveShrinkwrap (tree, next) {
validate('OF', arguments)
if (!npm.config.get('shrinkwrap') || !npm.config.get('package-lock')) {
- next()
+ return next()
}
- createShrinkwrap(tree, {silent: false}, next)
+ require('../shrinkwrap.js').createShrinkwrap(tree, {silent: false}, next)
}
function savePackageJson (tree, next) {
@@ -60,7 +61,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 +124,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()