Skip to content

Commit 076420c

Browse files
wraithgarruyadorno
authored andcommitted
feat(publish): add workspace support
Errors will make things stop altogether, dunno if we want to bikeshed that here or not PR-URL: #3231 Credit: @wraithgar Close: #3231 Reviewed-by: @ruyadorno
1 parent 9c46a0a commit 076420c

File tree

5 files changed

+274
-46
lines changed

5 files changed

+274
-46
lines changed

docs/content/commands/npm-publish.md

+7
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ by specifying a different default registry or using a
4747
actually publishing to the registry. Reports the details of what would
4848
have been published.
4949
50+
* `[--workspaces]`: Enables workspace context while publishing. All
51+
workspace packages will be published.
52+
53+
* `[--workspace]`: Enables workspaces context and limits results to only
54+
those specified by this config item. Only the packages in the
55+
workspaces given will be published.
56+
5057
The publish will fail if the package name and version combination already
5158
exists in the specified registry.
5259

lib/publish.js

+50-26
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@ const pacote = require('pacote')
88
const npa = require('npm-package-arg')
99
const npmFetch = require('npm-registry-fetch')
1010

11-
const flatten = require('./utils/config/flatten.js')
1211
const otplease = require('./utils/otplease.js')
1312
const { getContents, logTar } = require('./utils/tar.js')
13+
const getWorkspaces = require('./workspaces/get-workspaces.js')
14+
15+
// for historical reasons, publishConfig in package.json can contain ANY config
16+
// keys that npm supports in .npmrc files and elsewhere. We *may* want to
17+
// revisit this at some point, and have a minimal set that's a SemVer-major
18+
// change that ought to get a RFC written on it.
19+
const flatten = require('./utils/config/flatten.js')
1420

15-
// this is the only case in the CLI where we use the old full slow
16-
// 'read-package-json' module, because we want to pull in all the
17-
// defaults and metadata, like git sha's and default scripts and all that.
21+
// this is the only case in the CLI where we want to use the old full slow
22+
// 'read-package-json' module, because we want to pull in all the defaults and
23+
// metadata, like git sha's and default scripts and all that.
1824
const readJson = util.promisify(require('read-package-json'))
1925

2026
const BaseCommand = require('./base-command.js')
@@ -30,7 +36,7 @@ class Publish extends BaseCommand {
3036

3137
/* istanbul ignore next - see test/lib/load-all-commands.js */
3238
static get params () {
33-
return ['tag', 'access', 'dry-run']
39+
return ['tag', 'access', 'dry-run', 'workspace', 'workspaces']
3440
}
3541

3642
/* istanbul ignore next - see test/lib/load-all-commands.js */
@@ -44,6 +50,10 @@ class Publish extends BaseCommand {
4450
this.publish(args).then(() => cb()).catch(cb)
4551
}
4652

53+
execWorkspaces (args, filters, cb) {
54+
this.publishWorkspaces(args, filters).then(() => cb()).catch(cb)
55+
}
56+
4757
async publish (args) {
4858
if (args.length === 0)
4959
args = ['.']
@@ -56,6 +66,7 @@ class Publish extends BaseCommand {
5666
const dryRun = this.npm.config.get('dry-run')
5767
const json = this.npm.config.get('json')
5868
const defaultTag = this.npm.config.get('tag')
69+
const silent = log.level === 'silent'
5970

6071
if (semver.validRange(defaultTag))
6172
throw new Error('Tag name must not be a valid SemVer range: ' + defaultTag.trim())
@@ -68,7 +79,7 @@ class Publish extends BaseCommand {
6879
let manifest = await this.getManifest(spec, opts)
6980

7081
if (manifest.publishConfig)
71-
Object.assign(opts, this.publishConfigToOpts(manifest.publishConfig))
82+
flatten(manifest.publishConfig, opts)
7283

7384
// only run scripts for directory type publishes
7485
if (spec.type === 'directory') {
@@ -77,7 +88,7 @@ class Publish extends BaseCommand {
7788
path: spec.fetchSpec,
7889
stdio: 'inherit',
7990
pkg: manifest,
80-
banner: log.level !== 'silent',
91+
banner: !silent,
8192
})
8293
}
8394

@@ -89,7 +100,7 @@ class Publish extends BaseCommand {
89100
// note that publishConfig might have changed as well!
90101
manifest = await this.getManifest(spec, opts)
91102
if (manifest.publishConfig)
92-
Object.assign(opts, this.publishConfigToOpts(manifest.publishConfig))
103+
flatten(manifest.publishConfig, opts)
93104

94105
// note that logTar calls npmlog.notice(), so if we ARE in silent mode,
95106
// this will do nothing, but we still want it in the debuglog if it fails.
@@ -114,44 +125,57 @@ class Publish extends BaseCommand {
114125
path: spec.fetchSpec,
115126
stdio: 'inherit',
116127
pkg: manifest,
117-
banner: log.level !== 'silent',
128+
banner: !silent,
118129
})
119130

120131
await runScript({
121132
event: 'postpublish',
122133
path: spec.fetchSpec,
123134
stdio: 'inherit',
124135
pkg: manifest,
125-
banner: log.level !== 'silent',
136+
banner: !silent,
126137
})
127138
}
128139

129-
const silent = log.level === 'silent'
130-
if (!silent && json)
131-
this.npm.output(JSON.stringify(pkgContents, null, 2))
132-
else if (!silent)
133-
this.npm.output(`+ ${pkgContents.id}`)
140+
if (!this.workspaces) {
141+
if (!silent && json)
142+
this.npm.output(JSON.stringify(pkgContents, null, 2))
143+
else if (!silent)
144+
this.npm.output(`+ ${pkgContents.id}`)
145+
}
134146

135147
return pkgContents
136148
}
137149

150+
async publishWorkspaces (args, filters) {
151+
// Suppresses JSON output in publish() so we can handle it here
152+
this.workspaces = true
153+
154+
const results = {}
155+
const json = this.npm.config.get('json')
156+
const silent = log.level === 'silent'
157+
const workspaces =
158+
await getWorkspaces(filters, { path: this.npm.localPrefix })
159+
for (const [name, workspace] of workspaces.entries()) {
160+
const pkgContents = await this.publish([workspace])
161+
// This needs to be in-line w/ the rest of the output that non-JSON
162+
// publish generates
163+
if (!silent && !json)
164+
this.npm.output(`+ ${pkgContents.id}`)
165+
else
166+
results[name] = pkgContents
167+
}
168+
169+
if (!silent && json)
170+
this.npm.output(JSON.stringify(results, null, 2))
171+
}
172+
138173
// if it's a directory, read it from the file system
139174
// otherwise, get the full metadata from whatever it is
140175
getManifest (spec, opts) {
141176
if (spec.type === 'directory')
142177
return readJson(`${spec.fetchSpec}/package.json`)
143178
return pacote.manifest(spec, { ...opts, fullMetadata: true })
144179
}
145-
146-
// for historical reasons, publishConfig in package.json can contain
147-
// ANY config keys that npm supports in .npmrc files and elsewhere.
148-
// We *may* want to revisit this at some point, and have a minimal set
149-
// that's a SemVer-major change that ought to get a RFC written on it.
150-
publishConfigToOpts (publishConfig) {
151-
// create a new object that inherits from the config stack
152-
// then squash the css-case into camelCase opts, like we do
153-
// this is Object.assign()'ed onto the base npm.flatOptions
154-
return flatten(publishConfig, {})
155-
}
156180
}
157181
module.exports = Publish

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

+103
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,109 @@ npm publish [<folder>]
1515
1616
Options:
1717
[--tag <tag>] [--access <restricted|public>] [--dry-run]
18+
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
19+
[-ws|--workspaces]
1820
1921
Run "npm help publish" for more info
2022
`
23+
24+
exports[`test/lib/publish.js TAP workspaces all workspaces > should output all publishes 1`] = `
25+
Array [
26+
27+
28+
]
29+
`
30+
31+
exports[`test/lib/publish.js TAP workspaces all workspaces > should publish all workspaces 1`] = `
32+
Array [
33+
Object {
34+
35+
"name": "workspace-a",
36+
"readme": "ERROR: No README data found!",
37+
"repository": Object {
38+
"type": "git",
39+
"url": "http://repo.workspace-a/",
40+
},
41+
"version": "1.2.3-a",
42+
},
43+
Object {
44+
45+
"bugs": Object {
46+
"url": "https://github.com/npm/workspace-b/issues",
47+
},
48+
"homepage": "https://github.com/npm/workspace-b#readme",
49+
"name": "workspace-b",
50+
"readme": "ERROR: No README data found!",
51+
"repository": Object {
52+
"type": "git",
53+
"url": "git+https://github.com/npm/workspace-b.git",
54+
},
55+
"version": "1.2.3-n",
56+
},
57+
]
58+
`
59+
60+
exports[`test/lib/publish.js TAP workspaces json > should output all publishes as json 1`] = `
61+
Array [
62+
String(
63+
{
64+
"workspace-a": {
65+
66+
},
67+
"workspace-b": {
68+
69+
}
70+
}
71+
),
72+
]
73+
`
74+
75+
exports[`test/lib/publish.js TAP workspaces json > should publish all workspaces 1`] = `
76+
Array [
77+
Object {
78+
79+
"name": "workspace-a",
80+
"readme": "ERROR: No README data found!",
81+
"repository": Object {
82+
"type": "git",
83+
"url": "http://repo.workspace-a/",
84+
},
85+
"version": "1.2.3-a",
86+
},
87+
Object {
88+
89+
"bugs": Object {
90+
"url": "https://github.com/npm/workspace-b/issues",
91+
},
92+
"homepage": "https://github.com/npm/workspace-b#readme",
93+
"name": "workspace-b",
94+
"readme": "ERROR: No README data found!",
95+
"repository": Object {
96+
"type": "git",
97+
"url": "git+https://github.com/npm/workspace-b.git",
98+
},
99+
"version": "1.2.3-n",
100+
},
101+
]
102+
`
103+
104+
exports[`test/lib/publish.js TAP workspaces one workspace > should output one publish 1`] = `
105+
Array [
106+
107+
]
108+
`
109+
110+
exports[`test/lib/publish.js TAP workspaces one workspace > should publish given workspace 1`] = `
111+
Array [
112+
Object {
113+
114+
"name": "workspace-a",
115+
"readme": "ERROR: No README data found!",
116+
"repository": Object {
117+
"type": "git",
118+
"url": "http://repo.workspace-a/",
119+
},
120+
"version": "1.2.3-a",
121+
},
122+
]
123+
`

tap-snapshots/test/lib/utils/npm-usage.js.test.cjs

+2
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,8 @@ All commands:
697697
698698
Options:
699699
[--tag <tag>] [--access <restricted|public>] [--dry-run]
700+
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
701+
[-ws|--workspaces]
700702
701703
Run "npm help publish" for more info
702704

0 commit comments

Comments
 (0)