| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 | 'use strict'const BB = require('bluebird')const chain = require('slide').chainconst 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')const iferr = require('iferr')const isOnlyOptional = require('./install/is-only-optional.js')const isOnlyDev = require('./install/is-only-dev.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 path = require('path')const readPackageTree = BB.promisify(require('read-package-tree'))const ssri = require('ssri')const stringifyPackage = require('stringify-package')const validate = require('aproba')const writeFileAtomic = require('write-file-atomic')const unixFormatPath = require('./utils/unix-format-path.js')const isRegistry = require('./utils/is-registry.js')const { chown } = require('fs')const inferOwner = require('infer-owner')const selfOwner = {  uid: process.getuid && process.getuid(),  gid: process.getgid && process.getgid()}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 = shrinkwrapexports.treeToShrinkwrap = treeToShrinkwrapfunction shrinkwrap (args, silent, cb) {  if (typeof cb !== 'function') {    cb = silent    silent = false  }  if (args.length) {    log.warn('shrinkwrap', "doesn't take positional args")  }  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 readFile(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 = createShrinkwrapfunction 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 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  if (tree.children.length) {    pkginfo.requires = true    shrinkwrapDeps(pkginfo.dependencies = {}, tree, tree)  }  return pkginfo}function shrinkwrapDeps (deps, top, tree, seen) {  validate('OOO', [deps, top, tree])  if (!seen) seen = new Set()  if (seen.has(tree)) return  seen.add(tree)  sortModules(tree.children).forEach(function (child) {    var childIsOnlyDev = isOnlyDev(child)    var pkginfo = deps[moduleName(child)] = {}    var requested = getRequested(child) || child.package._requested || {}    var linked = child.isLink || child.isInLink    pkginfo.version = childVersion(top, child, requested)    if (requested.type === 'git' && child.package._from) {      pkginfo.from = child.package._from    }    if (child.fromBundle && !linked) {      pkginfo.bundled = true    } else {      if (isRegistry(requested)) {        pkginfo.resolved = child.package._resolved      }      // no integrity for git deps as integrity hashes are based on the      // tarball and we can't (yet) create consistent tarballs from a stable      // source.      if (requested.type !== 'git') {        pkginfo.integrity = child.package._integrity || undefined        if (!pkginfo.integrity && child.package._shasum) {          pkginfo.integrity = ssri.fromHex(child.package._shasum, 'sha1')        }      }    }    if (childIsOnlyDev) pkginfo.dev = true    if (isOnlyOptional(child)) pkginfo.optional = true    if (child.requires.length) {      pkginfo.requires = {}      sortModules(child.requires).forEach((required) => {        var requested = getRequested(required, child) || required.package._requested || {}        pkginfo.requires[moduleName(required)] = childRequested(top, required, requested)      })    }    // iterate into children on non-links and links contained within the top level package    if (child.children.length) {      pkginfo.dependencies = {}      shrinkwrapDeps(pkginfo.dependencies, top, child, seen)    }  })}function sortModules (modules) {  // sort modules with the locale-agnostic Unicode sort  var sortedModuleNames = modules.map(moduleName).sort()  return modules.sort((a, b) => (    sortedModuleNames.indexOf(moduleName(a)) - sortedModuleNames.indexOf(moduleName(b))  ))}function childVersion (top, child, req) {  if (req.type === 'directory' || req.type === 'file') {    return 'file:' + unixFormatPath(path.relative(top.path, child.package._resolved || req.fetchSpec))  } else if (!isRegistry(req) && !child.fromBundle) {    return child.package._resolved || req.saveSpec || req.rawSpec  } else if (req.type === 'alias') {    return `npm:${child.package.name}@${child.package.version}`  } else {    return child.package.version  }}function childRequested (top, child, requested) {  if (requested.type === 'directory' || requested.type === 'file') {    return 'file:' + unixFormatPath(path.relative(top.path, child.package._resolved || requested.fetchSpec))  } else if (requested.type === 'git' && child.package._from) {    return child.package._from  } else if (!isRegistry(requested) && !child.fromBundle) {    return child.package._resolved || requested.saveSpec || requested.rawSpec  } else if (requested.type === 'tag') {    // tags are not ranges we can match against, so we invent a "reasonable"    // one based on what we actually installed.    return npm.config.get('save-prefix') + child.package.version  } else if (requested.saveSpec || requested.rawSpec) {    return requested.saveSpec || requested.rawSpec  } else if (child.package._from || (child.package._requested && child.package._requested.rawSpec)) {    return child.package._from.replace(/^@?[^@]+@/, '') || child.package._requested.rawSpec  } else {    return child.package.version  }}function shrinkwrap_ (dir, pkginfo, opts, cb) {  save(dir, pkginfo, opts, cb)}function save (dir, pkginfo, opts, cb) {  // copy the keys over in a well defined order  // because javascript objects serialize arbitrarily  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,          newline: pkg && pkg.newline        }      )      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.`)        cb(null, pkginfo)      } else {        inferOwner(info.path).then(owner => {          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.`)            }            if (selfOwner.uid === 0 && (selfOwner.uid !== owner.uid || selfOwner.gid !== owner.gid)) {              chown(info.path, owner.uid, owner.gid, er => cb(er, pkginfo))            } else {              cb(null, pkginfo)            }          })        })      }    }  ).then((file) => {  }, cb)}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',    '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    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 readFile(    file, 'utf8'  ).then((data) => {    const format = npm.config.get('format-package-lock') !== false    const indent = format ? detectIndent(data).indent : 0    const newline = format ? detectNewline(data) : 0    return {      path: file,      raw: data,      indent,      newline    }  }).catch({code: 'ENOENT'}, () => {})}
 |