diff --git a/lib/pack.js b/lib/pack.js index 92345d95dced4..510c4781b1edd 100644 --- a/lib/pack.js +++ b/lib/pack.js @@ -1,264 +1,58 @@ 'use strict' -// npm pack -// Packs the specified package into a .tgz file, which can then -// be installed. - -// Set this early to avoid issues with circular dependencies. -module.exports = pack - -const BB = require('bluebird') - -const cacache = require('cacache') -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 { getTarContents, logTarContents } = ('/.utils/tar-contents') +const util = require('util') 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').PassThrough -const 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 libpack = require('libnpmpack') +const npa = require('npm-package-arg') + +const npm = require('./npm.js') +const output = require('./utils/output.js') +const { getContents, logTar } = require('./utils/tar.js') -pack.usage = 'npm pack [[<@scope>/]...] [--dry-run]' +const writeFile = util.promisify(require('fs').writeFile) -// if it can be installed, it can be packed. -pack.completion = install.completion +cmd.usage = 'npm pack [[<@scope>/]...] [--dry-run]' -function pack (args, silent, cb) { - const cwd = process.cwd() +module.exports = cmd +function cmd(args, silent, cb) { if (typeof cb !== 'function') { cb = silent silent = false } + pack(args, silent) + .then(() => cb()) + .catch(cb) +} +async function pack (args, silent) { 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(logTarContents) - 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) - } + const opts = { ...npm.flatOptions } + const { unicode, jsonĀ } = opts + const tarballs = await Promise.all(args.map((arg) => pack_(arg, opts))) + if (!silent && json) { + output(JSON.stringify(tarballs, null, 2)) + } else if (!silent) { + tarballs.forEach((tar) => { + logTar(tar, { log, unicode }) }) - }) -} - -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) => getTarContents(pkg, target, filename)) -} + } -module.exports.prepareDirectory = prepareDirectory -function 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 - }) + return tarballs } -module.exports.packDirectory = packDirectory -function 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 - } +async function pack_ (arg, opts) { + const spec = npa(arg) + const manifest = await pacote.manifest(spec) + const filename = `${manifest.name}-${manifest.version}.tgz` + const tarballData = await libpack(arg) + const pkgContents = await getContents(manifest, tarballData) + const { dryRun } = opts - 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(() => getTarContents(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)) - }) - }) -} + if (!dryRun) { + await writeFile(filename, tarballData) + } -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 = packGitDep -function 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 + return pkgContents }