@@ -7,7 +7,9 @@ const path = require('path')
77const { runChecked } = require ( './lib/checked-command' )
88
99const ROOT = path . resolve ( __dirname , '..' )
10- const PACKAGE_NAME = JSON . parse ( fs . readFileSync ( path . join ( ROOT , 'package.json' ) , 'utf8' ) ) . name
10+ const PACKAGE_METADATA = JSON . parse ( fs . readFileSync ( path . join ( ROOT , 'package.json' ) , 'utf8' ) )
11+ const PACKAGE_NAME = PACKAGE_METADATA . name
12+ const PACKAGE_VERSION = PACKAGE_METADATA . version
1113const targets = {
1214 npmjs : { registry : 'https://registry.npmjs.org/' , access : 'public' } ,
1315 github : { registry : 'https://npm.pkg.github.com/' , access : 'restricted' }
@@ -62,6 +64,52 @@ function buildPublishArgs(name, packageName = PACKAGE_NAME, packageArtifact = '.
6264 return args
6365}
6466
67+ function allowsExistingVersion ( argv ) {
68+ return argv . includes ( '--allow-existing-version' )
69+ }
70+
71+ function isPublishedVersionCollision ( error , packageVersion = PACKAGE_VERSION ) {
72+ const evidence = error ?. evidence
73+ if ( ! evidence || evidence . command !== 'npm' || evidence . exitCode !== 1 ||
74+ ! Array . isArray ( evidence . args ) || evidence . args [ 0 ] !== 'publish' ||
75+ ! evidence . args . includes ( '--dry-run' ) ||
76+ ! evidence . args . includes ( `--registry=${ targets . npmjs . registry } ` ) ) {
77+ return false
78+ }
79+ const collisionLine = `npm error You cannot publish over the previously published versions: ${ packageVersion } .`
80+ const errorLines = String ( evidence . stderr || '' )
81+ . split ( / \r ? \n / )
82+ . map ( line => line . trim ( ) )
83+ . filter ( line => / ^ n p m e r r o r \b / i. test ( line ) )
84+ return errorLines . includes ( collisionLine ) && errorLines . every ( line =>
85+ line === collisionLine || / ^ n p m e r r o r A c o m p l e t e l o g o f t h i s r u n c a n b e f o u n d i n : / . test ( line )
86+ )
87+ }
88+
89+ function runPublishDryRun ( name , packageName , packageVersion , packageArtifact , options = { } ) {
90+ const runner = options . run || runChecked
91+ const label = `publish-dry-run:${ name } `
92+ try {
93+ return {
94+ label,
95+ ...runner ( 'npm' , buildPublishArgs ( name , packageName , packageArtifact ) , {
96+ cwd : options . cwd || ROOT ,
97+ timeoutMs : options . timeoutMs || 120000
98+ } )
99+ }
100+ } catch ( error ) {
101+ if ( options . allowExistingVersion === true && name === 'npmjs' &&
102+ isPublishedVersionCollision ( error , packageVersion ) ) {
103+ return {
104+ label,
105+ ...error . evidence ,
106+ acceptedFailure : 'PUBLISHED_VERSION_EXISTS'
107+ }
108+ }
109+ throw error
110+ }
111+ }
112+
65113function parsePackJson ( stdout ) {
66114 const text = String ( stdout || '' ) . trim ( )
67115 const match = text . match ( / ( \[ \s * \{ [ \s \S ] * \} \s * \] ) \s * $ / )
@@ -96,6 +144,7 @@ function createCandidateTarball(tempRoot) {
96144
97145function main ( argv = process . argv . slice ( 2 ) ) {
98146 const selected = readTarget ( argv )
147+ const allowExistingVersion = allowsExistingVersion ( argv )
99148 if ( ! [ 'npmjs' , 'github' , 'all' ] . includes ( selected ) ) {
100149 console . error ( `Unknown registry target: ${ selected || '(missing)' } ` )
101150 return 2
@@ -112,16 +161,20 @@ function main(argv = process.argv.slice(2)) {
112161 ...candidate . evidence
113162 } ]
114163 for ( const name of names ) {
115- evidence . push ( {
116- label : `publish-dry-run: ${ name } ` ,
117- ... runChecked ( 'npm' , buildPublishArgs ( name , PACKAGE_NAME , candidate . tarballPath ) , {
118- cwd : ROOT ,
119- timeoutMs : 120000
120- } )
121- } )
164+ evidence . push ( runPublishDryRun (
165+ name ,
166+ PACKAGE_NAME ,
167+ PACKAGE_VERSION ,
168+ candidate . tarballPath ,
169+ { allowExistingVersion }
170+ ) )
122171 }
123172 for ( const item of evidence ) {
124- console . log ( `✓ ${ item . label } : exitCode=${ item . exitCode } durationMs=${ item . durationMs } ` )
173+ if ( item . acceptedFailure ) {
174+ console . log ( `✓ ${ item . label } : acceptedFailure=${ item . acceptedFailure } originalExitCode=${ item . exitCode } durationMs=${ item . durationMs } ` )
175+ } else {
176+ console . log ( `✓ ${ item . label } : exitCode=${ item . exitCode } durationMs=${ item . durationMs } ` )
177+ }
125178 }
126179 return 0
127180 } catch ( error ) {
@@ -140,13 +193,16 @@ function main(argv = process.argv.slice(2)) {
140193if ( require . main === module ) process . exitCode = main ( )
141194
142195module . exports = {
196+ allowsExistingVersion,
143197 assertTargetSupported,
144198 buildPublishArgs,
145199 createCandidateTarball,
200+ isPublishedVersionCollision,
146201 main,
147202 packageScope,
148203 parsePackJson,
149204 readTarget,
205+ runPublishDryRun,
150206 supportedTargets,
151207 targets
152208}
0 commit comments