Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ruyadorno authored and wraithgar committed May 27, 2021
1 parent 64b13dd commit 4a4fbe3
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 1 deletion.
22 changes: 21 additions & 1 deletion lib/publish.js
Expand Up @@ -7,6 +7,7 @@ const runScript = require('@npmcli/run-script')
const pacote = require('pacote')
const npa = require('npm-package-arg')
const npmFetch = require('npm-registry-fetch')
const chalk = require('chalk')

const otplease = require('./utils/otplease.js')
const { getContents, logTar } = require('./utils/tar.js')
Expand Down Expand Up @@ -154,10 +155,29 @@ class Publish extends BaseCommand {
const results = {}
const json = this.npm.config.get('json')
const silent = log.level === 'silent'
const noop = a => a
const color = this.npm.color ? chalk : { green: noop, bold: noop }
const workspaces =
await getWorkspaces(filters, { path: this.npm.localPrefix })

for (const [name, workspace] of workspaces.entries()) {
const pkgContents = await this.publish([workspace])
let pkgContents
try {
pkgContents = await this.publish([workspace])
} catch (err) {
if (err.code === 'EPRIVATE') {

This comment has been minimized.

Copy link
@tschaub

tschaub Feb 3, 2024

I'm seeing ENEEDAUTH when trying the setup described in #3268 when trying to publish scoped packages to a custom registry. The workspace with "private": true is not scoped, and the error thrown has the message "This command requires you to be logged in to https://registry.npmjs.org/." I've opened #7199 with details.

log.warn(
'publish',
`Skipping workspace ${
color.green(name)
}, marked as ${
color.bold('private')
}`
)
continue
}
throw err
}
// This needs to be in-line w/ the rest of the output that non-JSON
// publish generates
if (!silent && !json)
Expand Down
34 changes: 34 additions & 0 deletions tap-snapshots/test/lib/publish.js.test.cjs
Expand Up @@ -5,6 +5,40 @@
* Make sure to inspect the output below. Do not ignore changes!
*/
'use strict'
exports[`test/lib/publish.js TAP private workspaces colorless > should output all publishes 1`] = `
Array [
"+ @npmcli/b@1.0.0",
]
`

exports[`test/lib/publish.js TAP private workspaces colorless > should publish all non-private workspaces 1`] = `
Array [
Object {
"_id": "@npmcli/b@1.0.0",
"name": "@npmcli/b",
"readme": "ERROR: No README data found!",
"version": "1.0.0",
},
]
`

exports[`test/lib/publish.js TAP private workspaces with color > should output all publishes 1`] = `
Array [
"+ @npmcli/b@1.0.0",
]
`

exports[`test/lib/publish.js TAP private workspaces with color > should publish all non-private workspaces 1`] = `
Array [
Object {
"_id": "@npmcli/b@1.0.0",
"name": "@npmcli/b",
"readme": "ERROR: No README data found!",
"version": "1.0.0",
},
]
`

exports[`test/lib/publish.js TAP shows usage with wrong set of arguments > should print usage 1`] = `
Error:
Usage: npm publish
Expand Down
145 changes: 145 additions & 0 deletions test/lib/publish.js
Expand Up @@ -617,3 +617,148 @@ t.test('workspaces', (t) => {
})
t.end()
})

t.test('private workspaces', (t) => {
const testDir = t.testdir({
'package.json': JSON.stringify({
name: 'workspaces-project',
version: '1.0.0',
workspaces: ['packages/*'],
}),
packages: {
a: {
'package.json': JSON.stringify({
name: '@npmcli/a',
version: '1.0.0',
private: true,
}),
},
b: {
'package.json': JSON.stringify({
name: '@npmcli/b',
version: '1.0.0',
}),
},
},
})

const publishes = []
const outputs = []
t.beforeEach(() => {
npm.config.set('json', false)
outputs.length = 0
publishes.length = 0
})
const mocks = {
'../../lib/utils/tar.js': {
getContents: (manifest) => ({
id: manifest._id,
}),
logTar: () => {},
},
libnpmpublish: {
publish: (manifest, tarballData, opts) => {
if (manifest.private) {
throw Object.assign(
new Error('private pkg'),
{ code: 'EPRIVATE' }
)
}
publishes.push(manifest)
},
},
}
const npm = mockNpm({
output: (o) => {
outputs.push(o)
},
})
npm.localPrefix = testDir
npm.config.getCredentialsByURI = (uri) => {
return { token: 'some.registry.token' }
}

t.test('with color', t => {
const Publish = t.mock('../../lib/publish.js', {
...mocks,
npmlog: {
notice () {},
verbose () {},
warn (title, msg) {
t.equal(title, 'publish', 'should use publish warn title')
t.match(
msg,
'Skipping workspace \u001b[32m@npmcli/a\u001b[39m, marked as \u001b[1mprivate\u001b[22m',
'should display skip private workspace warn msg'
)
},
},
})
const publish = new Publish(npm)

npm.color = true
publish.execWorkspaces([], [], (err) => {
t.notOk(err)
t.matchSnapshot(publishes, 'should publish all non-private workspaces')
t.matchSnapshot(outputs, 'should output all publishes')
npm.color = false
t.end()
})
})

t.test('colorless', t => {
const Publish = t.mock('../../lib/publish.js', {
...mocks,
npmlog: {
notice () {},
verbose () {},
warn (title, msg) {
t.equal(title, 'publish', 'should use publish warn title')
t.equal(
msg,
'Skipping workspace @npmcli/a, marked as private',
'should display skip private workspace warn msg'
)
},
},
})
const publish = new Publish(npm)

publish.execWorkspaces([], [], (err) => {
t.notOk(err)
t.matchSnapshot(publishes, 'should publish all non-private workspaces')
t.matchSnapshot(outputs, 'should output all publishes')
t.end()
})
})

t.test('unexpected error', t => {
const Publish = t.mock('../../lib/publish.js', {
...mocks,
libnpmpublish: {
publish: (manifest, tarballData, opts) => {
if (manifest.private)
throw new Error('ERR')

publishes.push(manifest)
},
},
npmlog: {
notice () {},
verbose () {},
},
})
const publish = new Publish(npm)

publish.execWorkspaces([], [], (err) => {
t.match(
err,
/ERR/,
'should throw unexpected error'
)
t.end()
})
})

t.end()
})

0 comments on commit 4a4fbe3

Please sign in to comment.