diff options
Diffstat (limited to 'deps/npm/lib/pack.js')
-rw-r--r-- | deps/npm/lib/pack.js | 170 |
1 files changed, 151 insertions, 19 deletions
diff --git a/deps/npm/lib/pack.js b/deps/npm/lib/pack.js index f6a0eff805..93c21ad559 100644 --- a/deps/npm/lib/pack.js +++ b/deps/npm/lib/pack.js @@ -6,7 +6,9 @@ const 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') @@ -28,6 +30,7 @@ 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>...]' @@ -46,35 +49,64 @@ function pack (args, silent, cb) { BB.all( args.map((arg) => pack_(arg, cwd)) - ).then((files) => { - if (!silent) { - output(files.map((f) => path.relative(cwd, f)).join('\n')) + ).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')) } - cb(null, files) - }, cb) + return tarballs + }).nodeify(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 + ? 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) + return cacache.tmp.withTmp(npm.tmp, {tmpPrefix: 'packing'}, (tmp) => { + const tmpTarget = path.join(tmp, path.basename(target)) + return prepareDirectory(mani._resolved) + .then(() => { + return packDirectory(mani, mani._resolved, tmpTarget, target, true) + }) + .tap(() => { + if (npm.config.get('dry-run')) { + log.verbose('pack', '--dry-run mode enabled. Skipping write.') + } else { + return move(tmpTarget, target, {Promise: BB, fs}) + } + }) + }) + } else if (npm.config.get('dry-run')) { + 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 pacote.tarball.toFile(pkg, target, pacoteOpts()) - .then(() => target) + return packFromPackage(pkg, target, target) } }) }) } +function packFromPackage (arg, target, filename) { + const opts = pacoteOpts() + return pacote.tarball.toFile(arg, target, pacoteOpts()) + .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 = prepareDirectory function prepareDirectory (dir) { return readJson(path.join(dir, 'package.json')).then((pkg) => { @@ -105,7 +137,7 @@ function prepareDirectory (dir) { } module.exports.packDirectory = packDirectory -function packDirectory (mani, dir, target) { +function packDirectory (mani, dir, target, filename, logIt) { deprCheck(mani) return readJson(path.join(dir, 'package.json')).then((pkg) => { return lifecycle(pkg, 'prepack', dir) @@ -120,22 +152,122 @@ function packDirectory (mani, dir, target) { cwd: dir, prefix: 'package/', portable: true, - noMtime: 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 packlist({ path: dir }) + 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(() => move(tmpTarget, target, {Promise: BB, fs})) - .then(() => lifecycle(pkg, 'postpack', dir)) - .then(() => target) + .then(() => getContents(pkg, tmpTarget, filename, logIt)) + // thread the content info through + .tap(() => move(tmpTarget, target, {Promise: BB, fs})) + .tap(() => lifecycle(pkg, 'postpack', dir)) }) }) } +module.exports.logContents = logContents +function 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 = getContents +function 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', @@ -170,7 +302,7 @@ function packGitDep (manifest, dir) { return acc }, []) const child = cp.spawn(process.env.NODE || process.execPath, [ - require.main.filename, + require.resolve('../bin/npm-cli.js'), 'install', '--dev', '--prod', |