1- // .github/scripts/publish.mjs
2- import { readFileSync } from 'node:fs'
1+ import { readFileSync , existsSync } from 'node:fs'
32import { spawnSync } from 'node:child_process'
43import path from 'node:path'
54import { fileURLToPath } from 'node:url'
@@ -9,10 +8,14 @@ const __dirname = path.dirname(__filename)
98const repoRoot = path . resolve ( __dirname , '..' , '..' )
109
1110function 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
1821const 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' ] )
3150run ( '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+ }
3458run ( '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 ( / o t p | o n e [ - \s ] ? t i m e | E O T P / 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 ( / c a n n o t p u b l i s h o v e r | E P U B L I S H C O N F L I C T | Y o u c a n n o t p u b l i s h o v e r / 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
4089run ( 'git' , [ 'push' , '--follow-tags' ] )
90+
91+ console . log ( `[publish] Success: v${ v } ` )
0 commit comments