'use strict' // npm pack // Packs the specified package into a .tgz file, which can then // be installed. 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 log = require('npmlog') const move = require('move-concurrently') const npm = require('./npm') const output = require('./utils/output') const pacote = require('pacote') const pacoteOpts = require('./config/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') pack.usage = 'npm pack [[<@scope>/]...]' // if it can be installed, it can be packed. pack.completion = install.completion module.exports = pack function 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((files) => { if (!silent) { output(files.map((f) => path.relative(cwd, f)).join('\n')) } cb(null, files) }, cb) } // add to cache, then cp to the cwd 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, () => { if (mani._requested.type === 'directory') { return prepareDirectory(mani._resolved).then(() => { return packDirectory(mani, mani._resolved, target) }) } else { return pacote.tarball.toFile(pkg, target, pacoteOpts()) .then(() => target) } }) }) } 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 }) } module.exports.packDirectory = packDirectory function packDirectory (mani, dir, target) { 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, noMtime: true, gzip: true } return 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(() => move(tmpTarget, target, {Promise: BB, fs})) .then(() => lifecycle(pkg, 'postpack', dir)) .then(() => target) }) }) } 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.main.filename, '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 }