| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 | 'use strict'// npm pack <pkg>// Packs the specified package into a .tgz file, which can then// be installed.// Set this early to avoid issues with circular dependencies.module.exports = packconst BB = require('bluebird')const byteSize = require('byte-size')const cacache = require('cacache')const columnify = require('columnify')const cp = require('child_process')const deprCheck = require('./utils/depr-check')const fpm = require('./fetch-package-metadata')const fs = require('graceful-fs')const install = require('./install')const lifecycle = BB.promisify(require('./utils/lifecycle'))const log = require('npmlog')const move = require('move-concurrently')const npm = require('./npm')const npmConfig = require('./config/figgy-config.js')const output = require('./utils/output')const pacote = require('pacote')const path = require('path')const PassThrough = require('stream').PassThroughconst pathIsInside = require('path-is-inside')const pipe = BB.promisify(require('mississippi').pipe)const prepublishWarning = require('./utils/warn-deprecated')('prepublish-on-install')const pinflight = require('promise-inflight')const readJson = BB.promisify(require('read-package-json'))const tar = require('tar')const packlist = require('npm-packlist')const ssri = require('ssri')pack.usage = 'npm pack [[<@scope>/]<pkg>...] [--dry-run]'// if it can be installed, it can be packed.pack.completion = install.completionfunction pack (args, silent, cb) {  const cwd = process.cwd()  if (typeof cb !== 'function') {    cb = silent    silent = false  }  if (args.length === 0) args = ['.']  BB.all(    args.map((arg) => pack_(arg, cwd))  ).then((tarballs) => {    if (!silent && npm.config.get('json')) {      output(JSON.stringify(tarballs, null, 2))    } else if (!silent) {      tarballs.forEach(logContents)      output(tarballs.map((f) => path.relative(cwd, f.filename)).join('\n'))    }    return tarballs  }).nodeify(cb)}function pack_ (pkg, dir) {  return BB.fromNode((cb) => fpm(pkg, dir, cb)).then((mani) => {    let name = mani.name[0] === '@'    // scoped packages get special treatment      ? mani.name.substr(1).replace(/\//g, '-')      : mani.name    const target = `${name}-${mani.version}.tgz`    return pinflight(target, () => {      const dryRun = npm.config.get('dry-run')      if (mani._requested.type === 'directory') {        return prepareDirectory(mani._resolved)          .then(() => {            return packDirectory(mani, mani._resolved, target, target, true, dryRun)          })      } else if (dryRun) {        log.verbose('pack', '--dry-run mode enabled. Skipping write.')        return cacache.tmp.withTmp(npm.tmp, {tmpPrefix: 'packing'}, (tmp) => {          const tmpTarget = path.join(tmp, path.basename(target))          return packFromPackage(pkg, tmpTarget, target)        })      } else {        return packFromPackage(pkg, target, target)      }    })  })}function packFromPackage (arg, target, filename) {  const opts = npmConfig()  return pacote.tarball.toFile(arg, target, opts)    .then(() => cacache.tmp.withTmp(npm.tmp, {tmpPrefix: 'unpacking'}, (tmp) => {      const tmpTarget = path.join(tmp, filename)      return pacote.extract(arg, tmpTarget, opts)        .then(() => readJson(path.join(tmpTarget, 'package.json')))    }))    .then((pkg) => getContents(pkg, target, filename))}module.exports.prepareDirectory = prepareDirectoryfunction prepareDirectory (dir) {  return readJson(path.join(dir, 'package.json')).then((pkg) => {    if (!pkg.name) {      throw new Error('package.json requires a "name" field')    }    if (!pkg.version) {      throw new Error('package.json requires a valid "version" field')    }    if (!pathIsInside(dir, npm.tmp)) {      if (pkg.scripts && pkg.scripts.prepublish) {        prepublishWarning([          'As of npm@5, `prepublish` scripts are deprecated.',          'Use `prepare` for build steps and `prepublishOnly` for upload-only.',          'See the deprecation note in `npm help scripts` for more information.'        ])      }      if (npm.config.get('ignore-prepublish')) {        return lifecycle(pkg, 'prepare', dir).then(() => pkg)      } else {        return lifecycle(pkg, 'prepublish', dir).then(() => {          return lifecycle(pkg, 'prepare', dir)        }).then(() => pkg)      }    }    return pkg  })}module.exports.packDirectory = packDirectoryfunction packDirectory (mani, dir, target, filename, logIt, dryRun) {  deprCheck(mani)  return readJson(path.join(dir, 'package.json')).then((pkg) => {    return lifecycle(pkg, 'prepack', dir)  }).then(() => {    return readJson(path.join(dir, 'package.json'))  }).then((pkg) => {    return cacache.tmp.withTmp(npm.tmp, {tmpPrefix: 'packing'}, (tmp) => {      const tmpTarget = path.join(tmp, path.basename(target))      const tarOpt = {        file: tmpTarget,        cwd: dir,        prefix: 'package/',        portable: true,        // Provide a specific date in the 1980s for the benefit of zip,        // which is confounded by files dated at the Unix epoch 0.        mtime: new Date('1985-10-26T08:15:00.000Z'),        gzip: true      }      return BB.resolve(packlist({ path: dir }))      // NOTE: node-tar does some Magic Stuff depending on prefixes for files      //       specifically with @ signs, so we just neutralize that one      //       and any such future "features" by prepending `./`        .then((files) => tar.create(tarOpt, files.map((f) => `./${f}`)))        .then(() => getContents(pkg, tmpTarget, filename, logIt))        // thread the content info through        .tap(() => {          if (dryRun) {            log.verbose('pack', '--dry-run mode enabled. Skipping write.')          } else {            return move(tmpTarget, target, {Promise: BB, fs})          }        })        .tap(() => lifecycle(pkg, 'postpack', dir))    })  })}module.exports.logContents = logContentsfunction logContents (tarball) {  log.notice('')  log.notice('', `${npm.config.get('unicode') ? '📦 ' : 'package:'} ${tarball.name}@${tarball.version}`)  log.notice('=== Tarball Contents ===')  if (tarball.files.length) {    log.notice('', columnify(tarball.files.map((f) => {      const bytes = byteSize(f.size)      return {path: f.path, size: `${bytes.value}${bytes.unit}`}    }), {      include: ['size', 'path'],      showHeaders: false    }))  }  if (tarball.bundled.length) {    log.notice('=== Bundled Dependencies ===')    tarball.bundled.forEach((name) => log.notice('', name))  }  log.notice('=== Tarball Details ===')  log.notice('', columnify([    {name: 'name:', value: tarball.name},    {name: 'version:', value: tarball.version},    tarball.filename && {name: 'filename:', value: tarball.filename},    {name: 'package size:', value: byteSize(tarball.size)},    {name: 'unpacked size:', value: byteSize(tarball.unpackedSize)},    {name: 'shasum:', value: tarball.shasum},    {      name: 'integrity:',      value: tarball.integrity.toString().substr(0, 20) + '[...]' + tarball.integrity.toString().substr(80)},    tarball.bundled.length && {name: 'bundled deps:', value: tarball.bundled.length},    tarball.bundled.length && {name: 'bundled files:', value: tarball.entryCount - tarball.files.length},    tarball.bundled.length && {name: 'own files:', value: tarball.files.length},    {name: 'total files:', value: tarball.entryCount}  ].filter((x) => x), {    include: ['name', 'value'],    showHeaders: false  }))  log.notice('', '')}module.exports.getContents = getContentsfunction getContents (pkg, target, filename, silent) {  const bundledWanted = new Set(    pkg.bundleDependencies ||    pkg.bundledDependencies ||    []  )  const files = []  const bundled = new Set()  let totalEntries = 0  let totalEntrySize = 0  return tar.t({    file: target,    onentry (entry) {      totalEntries++      totalEntrySize += entry.size      const p = entry.path      if (p.startsWith('package/node_modules/')) {        const name = p.match(/^package\/node_modules\/((?:@[^/]+\/)?[^/]+)/)[1]        if (bundledWanted.has(name)) {          bundled.add(name)        }      } else {        files.push({          path: entry.path.replace(/^package\//, ''),          size: entry.size,          mode: entry.mode        })      }    },    strip: 1  })    .then(() => BB.all([      BB.fromNode((cb) => fs.stat(target, cb)),      ssri.fromStream(fs.createReadStream(target), {        algorithms: ['sha1', 'sha512']      })    ]))    .then(([stat, integrity]) => {      const shasum = integrity['sha1'][0].hexDigest()      return {        id: pkg._id,        name: pkg.name,        version: pkg.version,        from: pkg._from,        size: stat.size,        unpackedSize: totalEntrySize,        shasum,        integrity: ssri.parse(integrity['sha512'][0]),        filename,        files,        entryCount: totalEntries,        bundled: Array.from(bundled)      }    })}const PASSTHROUGH_OPTS = [  'always-auth',  'auth-type',  'ca',  'cafile',  'cert',  'git',  'local-address',  'maxsockets',  'offline',  'prefer-offline',  'prefer-online',  'proxy',  'https-proxy',  'registry',  'send-metrics',  'sso-poll-frequency',  'sso-type',  'strict-ssl']module.exports.packGitDep = packGitDepfunction packGitDep (manifest, dir) {  const stream = new PassThrough()  readJson(path.join(dir, 'package.json')).then((pkg) => {    if (pkg.scripts && pkg.scripts.prepare) {      log.verbose('prepareGitDep', `${manifest._spec}: installing devDeps and running prepare script.`)      const cliArgs = PASSTHROUGH_OPTS.reduce((acc, opt) => {        if (npm.config.get(opt, 'cli') != null) {          acc.push(`--${opt}=${npm.config.get(opt)}`)        }        return acc      }, [])      const child = cp.spawn(process.env.NODE || process.execPath, [        require.resolve('../bin/npm-cli.js'),        'install',        '--dev',        '--prod',        '--ignore-prepublish',        '--no-progress',        '--no-save'      ].concat(cliArgs), {        cwd: dir,        env: process.env      })      let errData = []      let errDataLen = 0      let outData = []      let outDataLen = 0      child.stdout.on('data', (data) => {        outData.push(data)        outDataLen += data.length        log.gauge.pulse('preparing git package')      })      child.stderr.on('data', (data) => {        errData.push(data)        errDataLen += data.length        log.gauge.pulse('preparing git package')      })      return BB.fromNode((cb) => {        child.on('error', cb)        child.on('exit', (code, signal) => {          if (code > 0) {            const err = new Error(`${signal}: npm exited with code ${code} while attempting to build ${manifest._requested}. Clone the repository manually and run 'npm install' in it for more information.`)            err.code = code            err.signal = signal            cb(err)          } else {            cb()          }        })      }).then(() => {        if (outDataLen > 0) log.silly('prepareGitDep', '1>', Buffer.concat(outData, outDataLen).toString())        if (errDataLen > 0) log.silly('prepareGitDep', '2>', Buffer.concat(errData, errDataLen).toString())      }, (err) => {        if (outDataLen > 0) log.error('prepareGitDep', '1>', Buffer.concat(outData, outDataLen).toString())        if (errDataLen > 0) log.error('prepareGitDep', '2>', Buffer.concat(errData, errDataLen).toString())        throw err      })    }  }).then(() => {    return readJson(path.join(dir, 'package.json'))  }).then((pkg) => {    return cacache.tmp.withTmp(npm.tmp, {      tmpPrefix: 'pacote-packing'    }, (tmp) => {      const tmpTar = path.join(tmp, 'package.tgz')      return packDirectory(manifest, dir, tmpTar).then(() => {        return pipe(fs.createReadStream(tmpTar), stream)      })    })  }).catch((err) => stream.emit('error', err))  return stream}
 |