Skip to content

Commit 8428cae

Browse files
committed
ci(publish): improve publish script robustness
Signed-off-by: Nico Kempe <git@nicokempe.de>
1 parent 166faac commit 8428cae

File tree

1 file changed

+63
-12
lines changed

1 file changed

+63
-12
lines changed

.github/scripts/publish.mjs

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// .github/scripts/publish.mjs
2-
import { readFileSync } from 'node:fs'
1+
import { readFileSync, existsSync } from 'node:fs'
32
import { spawnSync } from 'node:child_process'
43
import path from 'node:path'
54
import { fileURLToPath } from 'node:url'
@@ -9,10 +8,14 @@ const __dirname = path.dirname(__filename)
98
const repoRoot = path.resolve(__dirname, '..', '..')
109

1110
function run(cmd, args, opts = {}) {
12-
const result = spawnSync(cmd, args, { stdio: 'inherit', cwd: repoRoot, shell: false, ...opts })
13-
if (result.status !== 0) {
14-
throw new Error(`Command failed: ${cmd} ${args.join(' ')}`)
15-
}
11+
const res = spawnSync(cmd, args, { stdio: 'inherit', cwd: repoRoot, shell: false, ...opts })
12+
if (res.status !== 0) throw new Error(`Command failed: ${cmd} ${args.join(' ')}`)
13+
}
14+
15+
function runGet(cmd, args, opts = {}) {
16+
const res = spawnSync(cmd, args, { cwd: repoRoot, shell: false, encoding: 'utf8', ...opts })
17+
if (res.status !== 0) throw new Error(`Command failed: ${cmd} ${args.join(' ')}\n${res.stderr || ''}`)
18+
return (res.stdout || '').trim()
1619
}
1720

1821
const pkgPath = path.join(repoRoot, 'package.json')
@@ -24,17 +27,65 @@ if (!/^\d{4}\.\d{1,2}\.\d+$/.test(v)) {
2427
process.exit(1)
2528
}
2629

27-
// Stage files
28-
run('git', ['add', 'CHANGELOG.md', 'package.json'])
30+
// Sanity: ensure dist exists & will be published
31+
if (!existsSync(path.join(repoRoot, 'dist'))) {
32+
console.error('dist/ not found. Did you run prepack?')
33+
process.exit(1)
34+
}
2935

30-
// Commit (no shell quoting problems here)
36+
// Show useful context
37+
try {
38+
const who = runGet('npm', ['whoami'])
39+
const reg = runGet('npm', ['config', 'get', 'registry'])
40+
console.log(`[publish] npm user: ${who}`)
41+
console.log(`[publish] registry: ${reg}`)
42+
}
43+
catch {
44+
console.error('[publish] Not logged into npm. Run `npm login` or configure an auth token.')
45+
process.exit(1)
46+
}
47+
48+
// Stage + commit
49+
run('git', ['add', 'CHANGELOG.md', 'package.json'])
3150
run('git', ['commit', '-m', `chore(release): v${v}`])
3251

33-
// Tag
52+
// Tag (fail early if tag already exists)
53+
const existingTag = runGet('git', ['tag', '-l', `v${v}`])
54+
if (existingTag === `v${v}`) {
55+
console.error(`[publish] Git tag v${v} already exists. Bump version and retry.`)
56+
process.exit(1)
57+
}
3458
run('git', ['tag', `v${v}`])
3559

36-
// Publish to npm
37-
run('npm', ['publish'])
60+
// Build publish args
61+
const publishArgs = ['publish', '--access', 'public']
62+
if (process.env.NPM_OTP && process.env.NPM_OTP.trim()) {
63+
publishArgs.push('--otp', process.env.NPM_OTP.trim())
64+
}
65+
if (process.env.NPM_TAG && process.env.NPM_TAG.trim()) {
66+
publishArgs.push('--tag', process.env.NPM_TAG.trim())
67+
}
68+
69+
// Publish (with one retry if OTP was missing)
70+
try {
71+
run('npm', publishArgs)
72+
}
73+
catch (e) {
74+
const msg = String(e?.message || '')
75+
// Common EOTP / 2FA error string
76+
if (/otp|one[-\s]?time|EOTP/i.test(msg) && !process.env.NPM_OTP) {
77+
console.error('[publish] 2FA required. Set NPM_OTP=<code> and rerun release.')
78+
process.exit(1)
79+
}
80+
// Version already exists
81+
if (/cannot publish over|EPUBLISHCONFLICT|You cannot publish over/i.test(msg)) {
82+
console.error(`[publish] Version ${v} already exists on npm. Bump version and retry.`)
83+
process.exit(1)
84+
}
85+
throw e
86+
}
3887

3988
// Push commit & tags
4089
run('git', ['push', '--follow-tags'])
90+
91+
console.log(`[publish] Success: v${v}`)

0 commit comments

Comments
 (0)