diff options
Diffstat (limited to 'deps/npm/lib/shrinkwrap.js')
-rw-r--r-- | deps/npm/lib/shrinkwrap.js | 264 |
1 files changed, 178 insertions, 86 deletions
diff --git a/deps/npm/lib/shrinkwrap.js b/deps/npm/lib/shrinkwrap.js index 5e12f0bd81..75fe0dd95d 100644 --- a/deps/npm/lib/shrinkwrap.js +++ b/deps/npm/lib/shrinkwrap.js @@ -1,30 +1,39 @@ -// emit JSON describing versions of all packages currently installed (for later -// use with shrinkwrap install) +'use strict' -module.exports = exports = shrinkwrap +const BB = require('bluebird') -var path = require('path') -var log = require('npmlog') -var writeFileAtomic = require('write-file-atomic') -var iferr = require('iferr') -var readPackageJson = require('read-package-json') -var readPackageTree = require('read-package-tree') -var validate = require('aproba') -var chain = require('slide').chain -var npm = require('./npm.js') -var recalculateMetadata = require('./install/deps.js').recalculateMetadata -var validatePeerDeps = require('./install/deps.js').validatePeerDeps -var isExtraneous = require('./install/is-extraneous.js') -var packageId = require('./utils/package-id.js') -var moduleName = require('./utils/module-name.js') -var output = require('./utils/output.js') -var lifecycle = require('./utils/lifecycle.js') -var isDevDep = require('./install/is-dev-dep.js') -var isProdDep = require('./install/is-prod-dep.js') -var isOptDep = require('./install/is-opt-dep.js') +const chain = require('slide').chain +const detectIndent = require('detect-indent') +const fs = BB.promisifyAll(require('graceful-fs')) +const getRequested = require('./install/get-requested.js') +const id = require('./install/deps.js') +const iferr = require('iferr') +const isDevDep = require('./install/is-dev-dep.js') +const isExtraneous = require('./install/is-extraneous.js') +const isOptDep = require('./install/is-opt-dep.js') +const isProdDep = require('./install/is-prod-dep.js') +const lifecycle = require('./utils/lifecycle.js') +const log = require('npmlog') +const moduleName = require('./utils/module-name.js') +const move = require('move-concurrently') +const npm = require('./npm.js') +const packageId = require('./utils/package-id.js') +const path = require('path') +const pkgSri = require('./utils/package-integrity.js') +const readPackageTree = BB.promisify(require('read-package-tree')) +const ssri = require('ssri') +const validate = require('aproba') +const writeFileAtomic = require('write-file-atomic') +const PKGLOCK = 'package-lock.json' +const SHRINKWRAP = 'npm-shrinkwrap.json' +const PKGLOCK_VERSION = npm.lockfileVersion + +// emit JSON describing versions of all packages currently installed (for later +// use with shrinkwrap install) shrinkwrap.usage = 'npm shrinkwrap' +module.exports = exports = shrinkwrap function shrinkwrap (args, silent, cb) { if (typeof cb !== 'function') { cb = silent @@ -35,56 +44,64 @@ function shrinkwrap (args, silent, cb) { log.warn('shrinkwrap', "doesn't take positional args") } - var packagePath = path.join(npm.localPrefix, 'package.json') - var prod = npm.config.get('production') || /^prod/.test(npm.config.get('only')) - - readPackageJson(packagePath, iferr(cb, function (pkg) { - createShrinkwrap(npm.localPrefix, pkg, !prod, silent, cb) - })) + move( + path.resolve(npm.prefix, PKGLOCK), + path.resolve(npm.prefix, SHRINKWRAP), + { Promise: BB } + ).then(() => { + log.notice('', `${PKGLOCK} has been renamed to ${SHRINKWRAP}. ${SHRINKWRAP} will be used for future installations.`) + return fs.readFileAsync(path.resolve(npm.prefix, SHRINKWRAP)).then((d) => { + return JSON.parse(d) + }) + }, (err) => { + if (err.code !== 'ENOENT') { + throw err + } else { + return readPackageTree(npm.localPrefix).then( + id.computeMetadata + ).then((tree) => { + return BB.fromNode((cb) => { + createShrinkwrap(tree, { + silent, + defaultFile: SHRINKWRAP + }, cb) + }) + }) + } + }).then((data) => cb(null, data), cb) } module.exports.createShrinkwrap = createShrinkwrap -function createShrinkwrap (dir, pkg, dev, silent, cb) { - lifecycle(pkg, 'preshrinkwrap', dir, function () { - readPackageTree(dir, andRecalculateMetadata(iferr(cb, function (tree) { - var pkginfo = treeToShrinkwrap(tree, dev) - - chain([ - [lifecycle, tree.package, 'shrinkwrap', dir], - [shrinkwrap_, pkginfo, silent], - [lifecycle, tree.package, 'postshrinkwrap', dir] - ], iferr(cb, function (data) { - cb(null, data[0]) - })) - }))) +function createShrinkwrap (tree, opts, cb) { + opts = opts || {} + lifecycle(tree.package, 'preshrinkwrap', tree.path, function () { + const pkginfo = treeToShrinkwrap(tree) + chain([ + [lifecycle, tree.package, 'shrinkwrap', tree.path], + [shrinkwrap_, tree.path, pkginfo, opts], + [lifecycle, tree.package, 'postshrinkwrap', tree.path] + ], iferr(cb, function (data) { + cb(null, pkginfo) + })) }) } -function andRecalculateMetadata (next) { - validate('F', arguments) - return function (er, tree) { - validate('EO', arguments) - if (er) return next(er) - recalculateMetadata(tree, log, next) - } -} - -function treeToShrinkwrap (tree, dev) { - validate('OB', arguments) +function treeToShrinkwrap (tree) { + validate('O', arguments) var pkginfo = {} if (tree.package.name) pkginfo.name = tree.package.name if (tree.package.version) pkginfo.version = tree.package.version var problems = [] if (tree.children.length) { - shrinkwrapDeps(dev, problems, pkginfo.dependencies = {}, tree) + shrinkwrapDeps(problems, pkginfo.dependencies = {}, tree, tree) } if (problems.length) pkginfo.problems = problems return pkginfo } -function shrinkwrapDeps (dev, problems, deps, tree, seen) { - validate('BAOO', [dev, problems, deps, tree]) +function shrinkwrapDeps (problems, deps, top, tree, seen) { + validate('AOOO', [problems, deps, top, tree]) if (!seen) seen = {} if (seen[tree.path]) return seen[tree.path] = true @@ -100,58 +117,133 @@ function shrinkwrapDeps (dev, problems, deps, tree, seen) { }) tree.children.sort(function (aa, bb) { return moduleName(aa).localeCompare(moduleName(bb)) }).forEach(function (child) { var childIsOnlyDev = isOnlyDev(child) - if (!dev && childIsOnlyDev) { - log.warn('shrinkwrap', 'Excluding devDependency: %s', child.location) + if (child.package._injectedFromShrinkwrap) { + deps[moduleName(child)] = child.package._injectedFromShrinkwrap return } var pkginfo = deps[moduleName(child)] = {} - pkginfo.version = child.package.version - pkginfo.from = child.package._from - pkginfo.resolved = child.package._resolved - if (dev && childIsOnlyDev) pkginfo.dev = true + var req = child.package._requested || getRequested(child) + if (req.type === 'directory' || req.type === 'file') { + pkginfo.version = 'file:' + path.relative(top.path, child.package._resolved || req.fetchSpec) + } else if (!req.registry && !child.fromBundle) { + pkginfo.version = child.package._resolved || req.saveSpec || req.rawSpec + } else { + pkginfo.version = child.package.version + } + if (child.fromBundle || child.isInLink) { + pkginfo.bundled = true + } else { + if (req.registry) { + pkginfo.resolved = child.package._resolved + } + // no integrity for git deps as integirty hashes are based on the + // tarball and we can't (yet) create consistent tarballs from a stable + // source. + if (req.type !== 'git') { + pkginfo.integrity = child.package._integrity + if (!pkginfo.integrity && child.package._shasum) { + pkginfo.integrity = ssri.fromHex(child.package._shasum, 'sha1') + } + } + } + if (childIsOnlyDev) pkginfo.dev = true if (isOptional(child)) pkginfo.optional = true if (isExtraneous(child)) { problems.push('extraneous: ' + child.package._id + ' ' + child.path) } - validatePeerDeps(child, function (tree, pkgname, version) { + id.validatePeerDeps(child, function (tree, pkgname, version) { problems.push('peer invalid: ' + pkgname + '@' + version + ', required by ' + child.package._id) }) if (child.children.length) { - shrinkwrapDeps(dev, problems, pkginfo.dependencies = {}, child, seen) + pkginfo.dependencies = {} + shrinkwrapDeps(problems, pkginfo.dependencies, top, child, seen) } }) } -function shrinkwrap_ (pkginfo, silent, cb) { - if (pkginfo.problems) { - return cb(new Error('Problems were encountered\n' + - 'Please correct and try again.\n' + - pkginfo.problems.join('\n'))) - } - - save(pkginfo, silent, cb) +function shrinkwrap_ (dir, pkginfo, opts, cb) { + save(dir, pkginfo, opts, cb) } -function save (pkginfo, silent, cb) { +function save (dir, pkginfo, opts, cb) { // copy the keys over in a well defined order // because javascript objects serialize arbitrarily - var swdata - try { - swdata = JSON.stringify(pkginfo, null, 2) + '\n' - } catch (er) { - log.error('shrinkwrap', 'Error converting package info to json') - return cb(er) - } - - var file = path.resolve(npm.prefix, 'npm-shrinkwrap.json') + BB.join( + checkPackageFile(dir, SHRINKWRAP), + checkPackageFile(dir, PKGLOCK), + checkPackageFile(dir, 'package.json'), + (shrinkwrap, lockfile, pkg) => { + const info = ( + shrinkwrap || + lockfile || + { + path: path.resolve(dir, opts.defaultFile || PKGLOCK), + data: '{}', + indent: (pkg && pkg.indent) || 2 + } + ) + const updated = updateLockfileMetadata(pkginfo, pkg && pkg.data) + const swdata = JSON.stringify(updated, null, info.indent) + '\n' + writeFileAtomic(info.path, swdata, (err) => { + if (err) return cb(err) + if (opts.silent) return cb(null, pkginfo) + if (!shrinkwrap && !lockfile) { + log.notice('', `created a lockfile as ${path.basename(info.path)}. You should commit this file.`) + } + cb(null, pkginfo) + }) + } + ).then((file) => { + }, cb) +} - writeFileAtomic(file, swdata, function (er) { - if (er) return cb(er) - if (silent) return cb(null, pkginfo) - output('wrote npm-shrinkwrap.json') - cb(null, pkginfo) +function updateLockfileMetadata (pkginfo, pkgJson) { + // This is a lot of work just to make sure the extra metadata fields are + // between version and dependencies fields, without affecting any other stuff + const newPkg = {} + let metainfoWritten = false + const metainfo = new Set([ + 'lockfileVersion', + 'packageIntegrity', + 'preserveSymlinks' + ]) + Object.keys(pkginfo).forEach((k) => { + if (k === 'dependencies') { + writeMetainfo(newPkg) + } + if (!metainfo.has(k)) { + newPkg[k] = pkginfo[k] + } + if (k === 'version') { + writeMetainfo(newPkg) + } }) + if (!metainfoWritten) { + writeMetainfo(newPkg) + } + function writeMetainfo (pkginfo) { + pkginfo.lockfileVersion = PKGLOCK_VERSION + pkginfo.packageIntegrity = pkgJson && pkgSri.hash(pkgJson) + if (process.env.NODE_PRESERVE_SYMLINKS) { + pkginfo.preserveSymlinks = process.env.NODE_PRESERVE_SYMLINKS + } + metainfoWritten = true + } + return newPkg +} + +function checkPackageFile (dir, name) { + const file = path.resolve(dir, name) + return fs.readFileAsync( + file, 'utf8' + ).then((data) => { + return { + path: file, + data: JSON.parse(data), + indent: detectIndent(data).indent || 2 + } + }).catch({code: 'ENOENT'}, () => {}) } // Returns true if the module `node` is only required direcctly as a dev |