diff --git a/docs/content/commands/npm-pack.md b/docs/content/commands/npm-pack.md index cc6b669efb1ef..de3eeb731a9e3 100644 --- a/docs/content/commands/npm-pack.md +++ b/docs/content/commands/npm-pack.md @@ -10,6 +10,25 @@ description: Create a tarball from a package npm pack [[<@scope>/]...] [--dry-run] ``` +### Configuration + +#### dry-run + +Do everything that pack usually does without actually packing anything. +That is, report on what would have gone into the tarball, but nothing +else. + +#### workspaces + +Enables workspaces context while creating tarballs. Tarballs for each +workspaces will be generated. + +#### workspace + +Enables workspaces context and limits results to only those specified by +this config item. Tarballs will only be generated for the packages +named in the workspaces given here. + ### Description For anything that's installable (that is, a package folder, tarball, @@ -23,10 +42,6 @@ overwritten the second time. If no arguments are supplied, then npm packs the current package folder. -The `--dry-run` argument will do everything that pack usually does without -actually packing anything. That is, it reports on what would have gone -into the tarball, but nothing else. - ### See Also * [npm-packlist package](http://npm.im/npm-packlist) diff --git a/lib/pack.js b/lib/pack.js index 92a2fbd7ac33c..8e61efabb36e4 100644 --- a/lib/pack.js +++ b/lib/pack.js @@ -3,6 +3,7 @@ const log = require('npmlog') const pacote = require('pacote') const libpack = require('libnpmpack') const npa = require('npm-package-arg') +const getWorkspaces = require('./workspaces/get-workspaces.js') const { getContents, logTar } = require('./utils/tar.js') @@ -23,7 +24,7 @@ class Pack extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { - return ['dry-run'] + return ['dry-run', 'workspace', 'workspaces'] } /* istanbul ignore next - see test/lib/load-all-commands.js */ @@ -35,6 +36,10 @@ class Pack extends BaseCommand { this.pack(args).then(() => cb()).catch(cb) } + execWorkspaces (args, filters, cb) { + this.packWorkspaces(args, filters).then(() => cb()).catch(cb) + } + async pack (args) { if (args.length === 0) args = ['.'] @@ -62,5 +67,21 @@ class Pack extends BaseCommand { this.npm.output(tar.filename.replace(/^@/, '').replace(/\//, '-')) } } + + async packWorkspaces (args, filters) { + // If they either ask for nothing, or explicitly include '.' in the args, + // we effectively translate that into each workspace requested + + const useWorkspaces = args.length === 0 || args.includes('.') + + if (!useWorkspaces) { + this.npm.log.warn('Ignoring workspaces for specified package(s)') + return this.pack(args) + } + + const workspaces = + await getWorkspaces(filters, { path: this.npm.localPrefix }) + return this.pack([...workspaces.values(), ...args.filter(a => a !== '.')]) + } } module.exports = Pack diff --git a/lib/view.js b/lib/view.js index fb280f0d58248..91b32e2fd38fa 100644 --- a/lib/view.js +++ b/lib/view.js @@ -151,7 +151,7 @@ class View extends BaseCommand { const local = /^\.@/.test(pkg) || pkg === '.' if (!local) { - this.npm.log.warn('Ignoring workspaces for remote package') + this.npm.log.warn('Ignoring workspaces for specified package(s)') return this.view([pkg, ...args]) } let wholePackument = false diff --git a/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js b/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js index 19beaaa85ea48..6bd38772ee368 100644 --- a/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js +++ b/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js @@ -625,7 +625,7 @@ All commands: npm pack [[<@scope>/]...] Options: - [--dry-run] + [--dry-run] [-w|--workspace [-w|--workspace ...]] [-ws|--workspaces] Run "npm help pack" for more info diff --git a/tap-snapshots/test-lib-view.js-TAP.test.js b/tap-snapshots/test-lib-view.js-TAP.test.js index 02810e31a5087..1cdf356356af9 100644 --- a/tap-snapshots/test-lib-view.js-TAP.test.js +++ b/tap-snapshots/test-lib-view.js-TAP.test.js @@ -450,7 +450,7 @@ dist-tags: ` exports[`test/lib/view.js TAP workspaces remote package name > must match snapshot 1`] = ` -Ignoring workspaces for remote package +Ignoring workspaces for specified package(s) ` exports[`test/lib/view.js TAP workspaces remote package name > must match snapshot 2`] = ` diff --git a/test/fixtures/mock-npm.js b/test/fixtures/mock-npm.js index c47758111fd40..01f482bde291b 100644 --- a/test/fixtures/mock-npm.js +++ b/test/fixtures/mock-npm.js @@ -2,10 +2,24 @@ // npm.config You still need a separate flatOptions but this is the first step // to eventually just using npm itself +const mockLog = { + clearProgress: () => {}, + disableProgress: () => {}, + enableProgress: () => {}, + http: () => {}, + info: () => {}, + levels: [], + notice: () => {}, + pause: () => {}, + silly: () => {}, + verbose: () => {}, + warn: () => {}, +} const mockNpm = (base = {}) => { const config = base.config || {} const flatOptions = base.flatOptions || {} return { + log: mockLog, ...base, flatOptions, config: { diff --git a/test/lib/pack.js b/test/lib/pack.js index a5163acfdc49b..4bcfe8ae3fd9c 100644 --- a/test/lib/pack.js +++ b/test/lib/pack.js @@ -1,6 +1,7 @@ const t = require('tap') const requireInject = require('require-inject') const mockNpm = require('../fixtures/mock-npm') +const pacote = require('pacote') const OUTPUT = [] const output = (...msg) => OUTPUT.push(msg) @@ -11,6 +12,16 @@ const libnpmpack = async (spec, opts) => { return '' } +const mockPacote = { + manifest: (spec) => { + if (spec.type === 'directory') + return pacote.manifest(spec) + return { + name: spec.name || 'test-package', + version: spec.version || '1.0.0-test', + } + }, +} t.afterEach(cb => { OUTPUT.length = 0 @@ -152,3 +163,95 @@ t.test('should log pack contents', (t) => { t.end() }) }) + +t.test('workspaces', (t) => { + const testDir = t.testdir({ + 'package.json': JSON.stringify({ + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + }, null, 2), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.0.0', + }), + }, + }) + const Pack = requireInject('../../lib/pack.js', { + libnpmpack, + pacote: mockPacote, + npmlog: { + notice: () => {}, + showProgress: () => {}, + clearProgress: () => {}, + }, + }) + const npm = mockNpm({ + localPrefix: testDir, + config: { + unicode: false, + json: false, + 'dry-run': false, + }, + output, + }) + const pack = new Pack(npm) + + t.test('all workspaces', (t) => { + pack.execWorkspaces([], [], er => { + if (er) + throw er + + t.strictSame(OUTPUT, [ + ['workspace-a-1.0.0.tgz'], + ['workspace-b-1.0.0.tgz'], + ]) + t.end() + }) + }) + + t.test('all workspaces, `.` first arg', (t) => { + pack.execWorkspaces(['.'], [], er => { + if (er) + throw er + + t.strictSame(OUTPUT, [ + ['workspace-a-1.0.0.tgz'], + ['workspace-b-1.0.0.tgz'], + ]) + t.end() + }) + }) + + t.test('one workspace', (t) => { + pack.execWorkspaces([], ['workspace-a'], er => { + if (er) + throw er + + t.strictSame(OUTPUT, [ + ['workspace-a-1.0.0.tgz'], + ]) + t.end() + }) + }) + + t.test('specific package', (t) => { + pack.execWorkspaces(['abbrev'], [], er => { + if (er) + throw er + + t.strictSame(OUTPUT, [ + ['abbrev-1.0.0-test.tgz'], + ]) + t.end() + }) + }) + t.end() +})