mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
8.3 KiB
255 lines
8.3 KiB
'use strict'
|
|
|
|
const BB = require('bluebird')
|
|
|
|
const cacache = require('cacache')
|
|
const cacheKey = require('./util/cache-key')
|
|
const fetchFromManifest = require('./fetch').fromManifest
|
|
const finished = BB.promisify(require('mississippi').finished)
|
|
const gunzip = require('./util/gunzip-maybe')
|
|
const minimatch = require('minimatch')
|
|
const normalize = require('normalize-package-data')
|
|
const optCheck = require('./util/opt-check')
|
|
const path = require('path')
|
|
const pipe = BB.promisify(require('mississippi').pipe)
|
|
const ssri = require('ssri')
|
|
const tar = require('tar-stream')
|
|
|
|
// `finalizeManifest` takes as input the various kinds of manifests that
|
|
// manifest handlers ('lib/handlers/*/manifest.js') return, and makes sure they
|
|
// are:
|
|
//
|
|
// * filled out with any required data that the handler couldn't fill in
|
|
// * formatted consistently
|
|
// * cached so we don't have to repeat this work more than necessary
|
|
//
|
|
// The biggest thing this package might do is do a full tarball extraction in
|
|
// order to find missing bits of metadata required by the npm installer. For
|
|
// example, it will fill in `_shrinkwrap`, `_integrity`, and other details that
|
|
// the plain manifest handlers would require a tarball to fill out. If a
|
|
// handler returns everything necessary, this process is skipped.
|
|
//
|
|
// If we get to the tarball phase, the corresponding tarball handler for the
|
|
// requested type will be invoked and the entire tarball will be read from the
|
|
// stream.
|
|
//
|
|
module.exports = finalizeManifest
|
|
function finalizeManifest (pkg, spec, opts) {
|
|
const key = finalKey(pkg, spec)
|
|
opts = optCheck(opts)
|
|
|
|
const cachedManifest = (opts.cache && key && !opts.preferOnline && !opts.fullMetadata)
|
|
? cacache.get.info(opts.cache, key, opts)
|
|
: BB.resolve(null)
|
|
|
|
return cachedManifest.then(cached => {
|
|
if (cached && cached.metadata.manifest) {
|
|
return new Manifest(cached.metadata.manifest)
|
|
} else {
|
|
return tarballedProps(pkg, spec, opts).then(props => {
|
|
return pkg && pkg.name
|
|
? new Manifest(pkg, props, opts.fullMetadata)
|
|
: new Manifest(props, null, opts.fullMetadata)
|
|
}).then(manifest => {
|
|
const cacheKey = key || finalKey(manifest, spec)
|
|
if (!opts.cache || !cacheKey) {
|
|
return manifest
|
|
} else {
|
|
opts.metadata = {
|
|
id: manifest._id,
|
|
manifest,
|
|
type: 'finalized-manifest'
|
|
}
|
|
return cacache.put(
|
|
opts.cache, cacheKey, '.', opts
|
|
).then(() => manifest)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
module.exports.Manifest = Manifest
|
|
function Manifest (pkg, fromTarball, fullMetadata) {
|
|
fromTarball = fromTarball || {}
|
|
if (fullMetadata) {
|
|
Object.assign(this, pkg)
|
|
}
|
|
this.name = pkg.name
|
|
this.version = pkg.version
|
|
this.engines = pkg.engines || fromTarball.engines
|
|
this.cpu = pkg.cpu || fromTarball.cpu
|
|
this.os = pkg.os || fromTarball.os
|
|
this.dependencies = pkg.dependencies || {}
|
|
this.optionalDependencies = pkg.optionalDependencies || {}
|
|
this.devDependencies = pkg.devDependencies || {}
|
|
const bundled = (
|
|
pkg.bundledDependencies ||
|
|
pkg.bundleDependencies ||
|
|
false
|
|
)
|
|
this.bundleDependencies = bundled
|
|
this.peerDependencies = pkg.peerDependencies || {}
|
|
this.deprecated = pkg.deprecated || false
|
|
|
|
// These depend entirely on each handler
|
|
this._resolved = pkg._resolved
|
|
|
|
// Not all handlers (or registries) provide these out of the box,
|
|
// and if they don't, we need to extract and read the tarball ourselves.
|
|
// These are details required by the installer.
|
|
this._integrity = pkg._integrity || fromTarball._integrity
|
|
this._shasum = pkg._shasum
|
|
this._shrinkwrap = pkg._shrinkwrap || fromTarball._shrinkwrap || null
|
|
this.bin = pkg.bin || fromTarball.bin || null
|
|
|
|
if (this.bin && Array.isArray(this.bin)) {
|
|
// Code yanked from read-package-json.
|
|
const m = (pkg.directories && pkg.directories.bin) || '.'
|
|
this.bin = this.bin.reduce((acc, mf) => {
|
|
if (mf && mf.charAt(0) !== '.') {
|
|
const f = path.basename(mf)
|
|
acc[f] = path.join(m, mf)
|
|
}
|
|
return acc
|
|
}, {})
|
|
}
|
|
|
|
this._id = null
|
|
|
|
// TODO - freezing and inextensibility pending npm changes. See test suite.
|
|
// Object.preventExtensions(this)
|
|
normalize(this)
|
|
|
|
// I don't want this why did you give it to me. Go away. 🔥🔥🔥🔥
|
|
delete this.readme
|
|
|
|
// Object.freeze(this)
|
|
}
|
|
|
|
// Some things aren't filled in by standard manifest fetching.
|
|
// If this function needs to do its work, it will grab the
|
|
// package tarball, extract it, and take whatever it needs
|
|
// from the stream.
|
|
function tarballedProps (pkg, spec, opts) {
|
|
const needsShrinkwrap = (!pkg || (
|
|
pkg._hasShrinkwrap !== false &&
|
|
!pkg._shrinkwrap
|
|
))
|
|
const needsBin = !!(!pkg || (
|
|
!pkg.bin &&
|
|
pkg.directories &&
|
|
pkg.directories.bin
|
|
))
|
|
const needsHash = !pkg || (!pkg._integrity && pkg._integrity !== false)
|
|
const needsManifest = !pkg || !pkg.name
|
|
const needsExtract = needsShrinkwrap || needsBin || needsManifest
|
|
if (!needsShrinkwrap && !needsBin && !needsHash && !needsManifest) {
|
|
return BB.resolve({})
|
|
} else {
|
|
opts = optCheck(opts)
|
|
const tarStream = fetchFromManifest(pkg, spec, opts)
|
|
const extracted = needsExtract && tar.extract()
|
|
extracted && extracted.on('entry', (h, str, next) => {
|
|
// Drain it
|
|
str.on('data', () => {}).on('end', next).on('error', next)
|
|
})
|
|
return BB.join(
|
|
needsShrinkwrap && jsonFromStream('npm-shrinkwrap.json', extracted),
|
|
needsManifest && jsonFromStream('package.json', extracted),
|
|
needsBin && getPaths(extracted),
|
|
needsHash && ssri.fromStream(tarStream, { algorithms: ['sha1'] }),
|
|
needsExtract && pipe(tarStream, gunzip(), extracted),
|
|
(sr, mani, paths, hash) => {
|
|
const extraProps = mani || {}
|
|
delete extraProps._resolved
|
|
// drain out the rest of the tarball
|
|
tarStream.unpipe()
|
|
tarStream.on('data', () => {})
|
|
// if we have directories.bin, we need to collect any matching files
|
|
// to add to bin
|
|
if (paths && paths.length) {
|
|
const dirBin = mani
|
|
? (mani && mani.directories && mani.directories.bin)
|
|
: (pkg && pkg.directories && pkg.directories.bin)
|
|
if (dirBin) {
|
|
extraProps.bin = {}
|
|
paths.forEach(filePath => {
|
|
if (minimatch(filePath, dirBin + '/**')) {
|
|
const relative = path.relative(dirBin, filePath)
|
|
if (relative && relative[0] !== '.') {
|
|
extraProps.bin[path.basename(relative)] = path.join(dirBin, relative)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
return Object.assign(extraProps, {
|
|
_shrinkwrap: sr,
|
|
_resolved: (mani && mani._resolved) ||
|
|
(pkg && pkg._resolved) ||
|
|
spec.fetchSpec,
|
|
_integrity: hash && hash.toString()
|
|
})
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
function jsonFromStream (filename, dataStream) {
|
|
return BB.fromNode(cb => {
|
|
dataStream.on('error', cb)
|
|
dataStream.on('finish', cb)
|
|
dataStream.on('entry', function handler (header, stream, next) {
|
|
const filePath = header.name.replace(/[^/]+\//, '')
|
|
if (filePath !== filename) {
|
|
next()
|
|
} else {
|
|
let data = ''
|
|
stream.on('data', d => { data += d })
|
|
stream.on('error', cb)
|
|
finished(stream).then(() => {
|
|
dataStream.removeListener('entry', handler)
|
|
try {
|
|
cb(null, JSON.parse(data))
|
|
next()
|
|
} catch (err) {
|
|
cb(err)
|
|
}
|
|
}, err => {
|
|
dataStream.removeListener('entry', handler)
|
|
cb(err)
|
|
})
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
function getPaths (dataStream) {
|
|
return BB.fromNode(cb => {
|
|
let paths = []
|
|
dataStream.on('error', cb)
|
|
dataStream.on('finish', () => cb(null, paths))
|
|
dataStream.on('entry', function handler (header, stream, next) {
|
|
const filePath = header.name.replace(/[^/]+\//, '')
|
|
stream.on('data', () => {})
|
|
paths.push(filePath)
|
|
next()
|
|
})
|
|
})
|
|
}
|
|
|
|
function finalKey (pkg, spec) {
|
|
if (pkg && pkg._uniqueResolved) {
|
|
// git packages have a unique, identifiable id, but no tar sha
|
|
return cacheKey(`${spec.type}-manifest`, pkg._uniqueResolved)
|
|
} else {
|
|
return (
|
|
pkg && pkg._integrity &&
|
|
cacheKey(
|
|
`${spec.type}-manifest`,
|
|
`${pkg._resolved}:${ssri.stringify(pkg._integrity)}`
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|