Skip to content

Commit 4a4fbe3

Browse files
ruyadornowraithgar
authored andcommitted
fix(publish): skip private workspaces
Allow users to publish all workspaces with `npm publish --ws` while also skipping any workspace that might have been intentionally marked as private, using `"private": true` in its package.json file. Fixes: #3268 PR-URL: #3285 Credit: @ruyadorno Close: #3285 Reviewed-by: @wraithgar
1 parent 64b13dd commit 4a4fbe3

File tree

3 files changed

+200
-1
lines changed

3 files changed

+200
-1
lines changed

lib/publish.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const runScript = require('@npmcli/run-script')
77
const pacote = require('pacote')
88
const npa = require('npm-package-arg')
99
const npmFetch = require('npm-registry-fetch')
10+
const chalk = require('chalk')
1011

1112
const otplease = require('./utils/otplease.js')
1213
const { getContents, logTar } = require('./utils/tar.js')
@@ -154,10 +155,29 @@ class Publish extends BaseCommand {
154155
const results = {}
155156
const json = this.npm.config.get('json')
156157
const silent = log.level === 'silent'
158+
const noop = a => a
159+
const color = this.npm.color ? chalk : { green: noop, bold: noop }
157160
const workspaces =
158161
await getWorkspaces(filters, { path: this.npm.localPrefix })
162+
159163
for (const [name, workspace] of workspaces.entries()) {
160-
const pkgContents = await this.publish([workspace])
164+
let pkgContents
165+
try {
166+
pkgContents = await this.publish([workspace])
167+
} catch (err) {
168+
if (err.code === 'EPRIVATE') {
169+
log.warn(
170+
'publish',
171+
`Skipping workspace ${
172+
color.green(name)
173+
}, marked as ${
174+
color.bold('private')
175+
}`
176+
)
177+
continue
178+
}
179+
throw err
180+
}
161181
// This needs to be in-line w/ the rest of the output that non-JSON
162182
// publish generates
163183
if (!silent && !json)

tap-snapshots/test/lib/publish.js.test.cjs

+34
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,40 @@
55
* Make sure to inspect the output below. Do not ignore changes!
66
*/
77
'use strict'
8+
exports[`test/lib/publish.js TAP private workspaces colorless > should output all publishes 1`] = `
9+
Array [
10+
"+ @npmcli/[email protected]",
11+
]
12+
`
13+
14+
exports[`test/lib/publish.js TAP private workspaces colorless > should publish all non-private workspaces 1`] = `
15+
Array [
16+
Object {
17+
"_id": "@npmcli/[email protected]",
18+
"name": "@npmcli/b",
19+
"readme": "ERROR: No README data found!",
20+
"version": "1.0.0",
21+
},
22+
]
23+
`
24+
25+
exports[`test/lib/publish.js TAP private workspaces with color > should output all publishes 1`] = `
26+
Array [
27+
"+ @npmcli/[email protected]",
28+
]
29+
`
30+
31+
exports[`test/lib/publish.js TAP private workspaces with color > should publish all non-private workspaces 1`] = `
32+
Array [
33+
Object {
34+
"_id": "@npmcli/[email protected]",
35+
"name": "@npmcli/b",
36+
"readme": "ERROR: No README data found!",
37+
"version": "1.0.0",
38+
},
39+
]
40+
`
41+
842
exports[`test/lib/publish.js TAP shows usage with wrong set of arguments > should print usage 1`] = `
943
Error:
1044
Usage: npm publish

test/lib/publish.js

+145
Original file line numberDiff line numberDiff line change
@@ -617,3 +617,148 @@ t.test('workspaces', (t) => {
617617
})
618618
t.end()
619619
})
620+
621+
t.test('private workspaces', (t) => {
622+
const testDir = t.testdir({
623+
'package.json': JSON.stringify({
624+
name: 'workspaces-project',
625+
version: '1.0.0',
626+
workspaces: ['packages/*'],
627+
}),
628+
packages: {
629+
a: {
630+
'package.json': JSON.stringify({
631+
name: '@npmcli/a',
632+
version: '1.0.0',
633+
private: true,
634+
}),
635+
},
636+
b: {
637+
'package.json': JSON.stringify({
638+
name: '@npmcli/b',
639+
version: '1.0.0',
640+
}),
641+
},
642+
},
643+
})
644+
645+
const publishes = []
646+
const outputs = []
647+
t.beforeEach(() => {
648+
npm.config.set('json', false)
649+
outputs.length = 0
650+
publishes.length = 0
651+
})
652+
const mocks = {
653+
'../../lib/utils/tar.js': {
654+
getContents: (manifest) => ({
655+
id: manifest._id,
656+
}),
657+
logTar: () => {},
658+
},
659+
libnpmpublish: {
660+
publish: (manifest, tarballData, opts) => {
661+
if (manifest.private) {
662+
throw Object.assign(
663+
new Error('private pkg'),
664+
{ code: 'EPRIVATE' }
665+
)
666+
}
667+
publishes.push(manifest)
668+
},
669+
},
670+
}
671+
const npm = mockNpm({
672+
output: (o) => {
673+
outputs.push(o)
674+
},
675+
})
676+
npm.localPrefix = testDir
677+
npm.config.getCredentialsByURI = (uri) => {
678+
return { token: 'some.registry.token' }
679+
}
680+
681+
t.test('with color', t => {
682+
const Publish = t.mock('../../lib/publish.js', {
683+
...mocks,
684+
npmlog: {
685+
notice () {},
686+
verbose () {},
687+
warn (title, msg) {
688+
t.equal(title, 'publish', 'should use publish warn title')
689+
t.match(
690+
msg,
691+
'Skipping workspace \u001b[32m@npmcli/a\u001b[39m, marked as \u001b[1mprivate\u001b[22m',
692+
'should display skip private workspace warn msg'
693+
)
694+
},
695+
},
696+
})
697+
const publish = new Publish(npm)
698+
699+
npm.color = true
700+
publish.execWorkspaces([], [], (err) => {
701+
t.notOk(err)
702+
t.matchSnapshot(publishes, 'should publish all non-private workspaces')
703+
t.matchSnapshot(outputs, 'should output all publishes')
704+
npm.color = false
705+
t.end()
706+
})
707+
})
708+
709+
t.test('colorless', t => {
710+
const Publish = t.mock('../../lib/publish.js', {
711+
...mocks,
712+
npmlog: {
713+
notice () {},
714+
verbose () {},
715+
warn (title, msg) {
716+
t.equal(title, 'publish', 'should use publish warn title')
717+
t.equal(
718+
msg,
719+
'Skipping workspace @npmcli/a, marked as private',
720+
'should display skip private workspace warn msg'
721+
)
722+
},
723+
},
724+
})
725+
const publish = new Publish(npm)
726+
727+
publish.execWorkspaces([], [], (err) => {
728+
t.notOk(err)
729+
t.matchSnapshot(publishes, 'should publish all non-private workspaces')
730+
t.matchSnapshot(outputs, 'should output all publishes')
731+
t.end()
732+
})
733+
})
734+
735+
t.test('unexpected error', t => {
736+
const Publish = t.mock('../../lib/publish.js', {
737+
...mocks,
738+
libnpmpublish: {
739+
publish: (manifest, tarballData, opts) => {
740+
if (manifest.private)
741+
throw new Error('ERR')
742+
743+
publishes.push(manifest)
744+
},
745+
},
746+
npmlog: {
747+
notice () {},
748+
verbose () {},
749+
},
750+
})
751+
const publish = new Publish(npm)
752+
753+
publish.execWorkspaces([], [], (err) => {
754+
t.match(
755+
err,
756+
/ERR/,
757+
'should throw unexpected error'
758+
)
759+
t.end()
760+
})
761+
})
762+
763+
t.end()
764+
})

0 commit comments

Comments
 (0)