tra-analysis/website/node_modules/npm/lib/install/inflate-shrinkwrap.js
2019-01-06 13:14:45 -06:00

234 lines
8.0 KiB
JavaScript

'use strict'
const BB = require('bluebird')
let addBundled
const childPath = require('../utils/child-path.js')
const createChild = require('./node.js').create
let fetchPackageMetadata
const inflateBundled = require('./inflate-bundled.js')
const moduleName = require('../utils/module-name.js')
const normalizePackageData = require('normalize-package-data')
const npm = require('../npm.js')
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) {
fetchPackageMetadata = BB.promisify(require('../fetch-package-metadata.js'))
addBundled = BB.promisify(fetchPackageMetadata.addBundled)
}
if (arguments.length === 3) {
finishInflating = opts
opts = {}
}
if (!npm.config.get('shrinkwrap') || !npm.config.get('package-lock')) {
return finishInflating()
}
tree.loaded = false
tree.hasRequiresFromLock = sw.requires
return inflateShrinkwrap(tree.path, tree, sw.dependencies, opts).then(
() => finishInflating(),
finishInflating
)
}
function inflateShrinkwrap (topPath, tree, swdeps, opts) {
if (!swdeps) return Promise.resolve()
if (!opts) opts = {}
const onDisk = {}
tree.children.forEach((child) => {
onDisk[moduleName(child)] = child
})
tree.children = []
return BB.each(Object.keys(swdeps), (name) => {
const sw = swdeps[name]
const dependencies = sw.dependencies || {}
const requested = realizeShrinkwrapSpecifier(name, sw, topPath)
return inflatableChild(
onDisk[name], name, topPath, tree, sw, requested, opts
).then((child) => {
child.hasRequiresFromLock = tree.hasRequiresFromLock
return inflateShrinkwrap(topPath, child, dependencies)
})
})
}
function normalizePackageDataNoErrors (pkg) {
try {
normalizePackageData(pkg)
} catch (ex) {
// don't care
}
}
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)
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 = requested
onDiskChild.package._requested = requested
onDiskChild.package._spec = requested.rawSpec
onDiskChild.package._where = topPath
onDiskChild.package._optional = sw.optional
onDiskChild.package._development = sw.dev
onDiskChild.package._inBundle = sw.bundled
onDiskChild.fromBundle = (sw.bundled || onDiskChild.package._inBundle) ? tree.fromBundle || tree : null
if (!onDiskChild.package._args) onDiskChild.package._args = []
onDiskChild.package._args.push([String(requested), topPath])
// non-npm registries can and will return unnormalized data, plus
// even the npm registry may have package data normalized with older
// normalization rules. This ensures we get package data in a consistent,
// stable format.
normalizePackageDataNoErrors(onDiskChild.package)
onDiskChild.swRequires = sw.requires
tree.children.push(onDiskChild)
return BB.resolve(onDiskChild)
} 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))
} else {
// It's not on disk, and we can't just look it up by address -- do a full
// fpm/inflate bundle pass. For registry deps, this will go straight to the
// tarball URL, as if it were a remote tarball dep.
return fetchChild(topPath, tree, sw, requested)
}
}
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: sw.resolved || (isGit(sw) && sw.version),
_requested: requested,
_optional: sw.optional,
_development: sw.dev,
_inBundle: sw.bundled,
_integrity: sw.integrity,
_from: from,
_spec: requested.rawSpec,
_where: topPath,
_args: [[requested.toString(), topPath]],
dependencies: sw.requires
}
if (!sw.bundled) {
const bundleDependencies = Object.keys(sw.dependencies || {}).filter((d) => sw.dependencies[d].bundled)
if (bundleDependencies.length === 0) {
pkg.bundleDependencies = bundleDependencies
}
}
const child = createChild({
package: pkg,
loaded: false,
parent: tree,
children: [],
fromShrinkwrap: requested,
fakeChild: sw,
fromBundle: sw.bundled ? tree.fromBundle || tree : null,
path: childPath(tree.path, pkg),
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
})
tree.children.push(child)
return child
}
function fetchChild (topPath, tree, sw, requested) {
return fetchPackageMetadata(requested, topPath).then((pkg) => {
pkg._from = sw.from || requested.raw
pkg._optional = sw.optional
pkg._development = sw.dev
pkg._inBundle = false
return addBundled(pkg).then(() => pkg)
}).then((pkg) => {
var isLink = pkg._requested.type === 'directory'
const child = createChild({
package: pkg,
loaded: false,
parent: tree,
fromShrinkwrap: requested,
path: childPath(tree.path, pkg),
realpath: isLink ? requested.fetchSpec : childPath(tree.realpath, pkg),
children: pkg._bundled || [],
location: (tree.location === '/' ? '' : tree.location + '/') + pkg.name,
fromBundle: null,
isLink: isLink,
isInLink: tree.isLink,
swRequires: sw.requires
})
tree.children.push(child)
if (pkg._bundled) {
delete pkg._bundled
inflateBundled(child, child, child.children)
}
return child
})
}
function childIsEquivalent (sw, requested, child) {
if (!child) return false
if (child.fromShrinkwrap) 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
if (!isRegistry(requested) && sw.from) return child.package._from === sw.from
if (!isRegistry(requested) && child.package._resolved) return sw.version === child.package._resolved
return child.package.version === sw.version
}