Skip to content

Commit ed8c553

Browse files
committed
chore: correct publish script tag/version logic
The publish script will now never attempt to publish a package that already exists. The tag to publish to is now only figured out after determining if the package already exists on the registry instead of before. This also added a confirmation prompt with a full table of name/version/tag of everything that will be published
1 parent 97d6771 commit ed8c553

File tree

3 files changed

+77
-51
lines changed

3 files changed

+77
-51
lines changed

scripts/publish.js

+65-43
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,74 @@
11
const semver = require('semver')
22
const log = require('proc-log')
33
const pacote = require('pacote')
4+
const read = require('read')
5+
const Table = require('cli-table3')
46
const { run, git, npm, pkg: cli, spawn } = require('./util.js')
57

68
const resetdeps = () => npm('run', 'resetdeps')
79

810
const op = () => spawn('op', 'item', 'get', 'npm', '--otp', { out: true, ok: true })
911

10-
const getVersion = async (s) => {
11-
const mani = await pacote.manifest(s, { preferOnline: true }).catch(() => null)
12-
return mani?.version
13-
}
14-
const getLatestMajor = async (s) => {
15-
const pack = await pacote.packument(s, { preferOnline: true }).catch(() => null)
16-
return pack?.['dist-tags']?.latest ? semver.major(pack['dist-tags'].latest) : 0
17-
}
12+
const getWorkspaceTag = async ({ name, version }) => {
13+
const { prerelease, major } = semver.parse(version)
14+
if (prerelease.length) {
15+
return 'prerelease'
16+
}
1817

19-
const TAG = {
20-
cli: ({ version }) => `next-${semver.major(version)}`,
21-
workspace: async ({ name, version }) => {
22-
const { prerelease, major } = semver.parse(version)
23-
if (prerelease.length) {
24-
return 'prerelease'
25-
}
26-
if (major >= await getLatestMajor(name)) {
27-
return 'latest'
28-
}
29-
return 'backport'
30-
},
31-
}
18+
const pack = await pacote.packument(name, { preferOnline: true }).catch(() => null)
19+
20+
if (!pack) {
21+
// This might never happen but if we were to create a new workspace that has never
22+
// been published before it should be set to latest right away.
23+
return 'latest'
24+
}
3225

33-
const needsPublish = async ({ private, name, version }, { force, getTag }) => {
34-
if (private) {
35-
return
26+
if (major >= semver.major(pack['dist-tags'].latest)) {
27+
// if the major version we are publishing is greater than the major version
28+
// of the latest dist-tag, then this should be latest too
29+
return 'latest'
3630
}
3731

38-
const tag = await getTag({ name, version })
39-
if (force || version !== await getVersion(`${name}@${tag}`)) {
40-
return tag
32+
// Anything else is a backport
33+
return 'backport'
34+
}
35+
36+
const versionNotExists = async ({ name, version }) => {
37+
const spec = `${name}@${version}`
38+
let exists
39+
try {
40+
await pacote.manifest(spec, { preferOnline: true })
41+
exists = true // if it exists, no publish needed
42+
} catch {
43+
exists = false // otherwise its needs publishing
4144
}
45+
log.info(`${spec} exists=${exists}`)
46+
return !exists
4247
}
4348

44-
const getPublishes = async (opts) => {
45-
const publish = []
49+
const getPublishes = async ({ force }) => {
50+
const publishPackages = []
51+
52+
for (const { pkg } of await cli.mapWorkspaces({ public: true })) {
53+
if (force || await versionNotExists(pkg)) {
54+
publishPackages.push({
55+
workspace: true,
56+
name: pkg.name,
57+
version: pkg.version,
58+
tag: await getWorkspaceTag(pkg),
59+
})
60+
}
61+
}
4662

47-
for (const { name, pkg } of await cli.mapWorkspaces()) {
48-
publish.push({
49-
workspace: name,
50-
tag: await needsPublish(pkg, { ...opts, getTag: TAG.workspace }),
63+
if (force || await versionNotExists(cli)) {
64+
publishPackages.push({
65+
name: cli.name,
66+
version: cli.version,
67+
tag: `next-${semver.major(cli.version)}`,
5168
})
5269
}
5370

54-
publish.push({
55-
tag: await needsPublish(cli, { ...opts, getTag: TAG.cli }),
56-
})
57-
58-
return publish.filter(p => p.tag)
71+
return publishPackages
5972
}
6073

6174
const main = async (opts) => {
@@ -64,12 +77,21 @@ const main = async (opts) => {
6477

6578
if (!publishes.length) {
6679
throw new Error(
67-
'Nothing to publish, exiting. ' +
80+
'Nothing to publish, exiting.\n' +
6881
'All packages to publish should have their version bumped before running this script.'
6982
)
7083
}
7184

72-
log.info('publish', '\n' + publishes.map(JSON.stringify).join('\n'))
85+
const table = new Table({ head: ['name', 'version', 'tag'] })
86+
for (const publish of publishes) {
87+
table.push([publish.name, publish.version, publish.tag])
88+
}
89+
90+
const prompt = `Ready to publish the following packages:\n${table.toString()}\nOk to proceed? `
91+
const confirm = await read({ prompt, default: 'y' })
92+
if (confirm.trim().toLowerCase().charAt(0) !== 'y') {
93+
throw new Error('Aborted')
94+
}
7395

7496
await git('clean', '-fd')
7597
await resetdeps()
@@ -87,8 +109,8 @@ const main = async (opts) => {
87109
await npm('install', '-w', 'docs', '--ignore-scripts', '--no-audit', '--no-fund')
88110
await git.dirty()
89111

90-
for (const p of publishes) {
91-
const workspace = p.workspace && `--workspace=${p.workspace}`
112+
for (const publish of publishes) {
113+
const workspace = publish.workspace && `--workspace=${publish.name}`
92114
if (packOnly) {
93115
await npm(
94116
'pack',
@@ -99,7 +121,7 @@ const main = async (opts) => {
99121
await npm(
100122
'publish',
101123
workspace,
102-
`--tag=${p.tag}`,
124+
`--tag=${publish.tag}`,
103125
opts.dryRun && '--dry-run',
104126
opts.otp && `--otp=${opts.otp === 'op' ? await op() : opts.otp}`
105127
)

scripts/resetdeps.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@ const { CWD, run, pkg, fs, git, npm } = require('./util.js')
44

55
const cleanup = async () => {
66
await git('checkout', 'node_modules/')
7-
for (const { name, path, pkg: wsPkg } of await pkg.mapWorkspaces()) {
8-
if (!wsPkg.private) {
9-
// add symlinks similar to how arborist does for our production
10-
// workspaces, so they are in place before the initial install.
11-
await symlink(path, join(CWD, 'node_modules', name), 'junction')
12-
}
7+
for (const { name, path } of await pkg.mapWorkspaces({ public: true })) {
8+
// add symlinks similar to how arborist does for our production
9+
// workspaces, so they are in place before the initial install.
10+
await symlink(path, join(CWD, 'node_modules', name), 'junction')
1311
}
1412
}
1513

scripts/util.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ const EOL = '\n'
1313
const CWD = resolve(__dirname, '..')
1414

1515
const pkg = require(join(CWD, 'package.json'))
16-
pkg.mapWorkspaces = async () => {
16+
pkg.mapWorkspaces = async ({ public = false } = {}) => {
1717
const ws = []
1818
for (const [name, path] of await mapWorkspaces({ pkg })) {
19-
ws.push({ name, path, pkg: require(join(path, 'package.json')) })
19+
const pkgJson = require(join(path, 'package.json'))
20+
21+
if (public && pkgJson.private) {
22+
continue
23+
}
24+
25+
ws.push({ name, path, pkg: pkgJson })
2026
}
2127
return ws
2228
}

0 commit comments

Comments
 (0)