@@ -223,34 +223,219 @@ function publishStable(repoDir, modeConfig, dryRun) {
223223
224224function publishTest ( repoDir , modeConfig , dryRun ) {
225225 const preid = modeConfig . preid || 'test' ;
226- const originalScripts = disableVersionScripts ( repoDir ) ;
227- try {
228- run ( `npm version prerelease --preid ${ preid } --no-git-tag-version` , repoDir , dryRun ) ;
229- } finally {
230- restoreVersionScripts ( repoDir , originalScripts ) ;
231- }
232-
233226 const pkg = getPackageJson ( repoDir ) ;
234227 const name = pkg ? pkg . name : null ;
235- let version = getPackageVersion ( repoDir ) ;
236- let attempts = 0 ;
237- const maxAttempts = 5 ;
238-
239- if ( ! dryRun && name ) {
240- while ( attempts < maxAttempts && packageVersionExists ( name , version , repoDir ) ) {
241- console . log ( `Version ${ version } already published. Bumping prerelease...` ) ;
242- const retryOriginalScripts = disableVersionScripts ( repoDir ) ;
243- try {
244- run ( `npm version prerelease --preid ${ preid } --no-git-tag-version` , repoDir , dryRun ) ;
245- } finally {
246- restoreVersionScripts ( repoDir , retryOriginalScripts ) ;
228+ let localVersion = pkg ? pkg . version : null ;
229+
230+ console . log ( `Local package.json version: ${ localVersion } ` ) ;
231+
232+ // Get the latest @test version from npm FIRST to understand what's already published
233+ let latestTestVersion = null ;
234+ if ( name ) {
235+ try {
236+ const result = runQuiet ( `npm view ${ name } @${ preid } version` , repoDir ) ;
237+ if ( result && result . trim ( ) ) {
238+ latestTestVersion = result . trim ( ) ;
239+ console . log ( `Latest published @${ preid } version: ${ latestTestVersion } ` ) ;
240+ }
241+ } catch ( err ) {
242+ // No @test version published yet, that's fine
243+ console . log ( `No @${ preid } version published yet` ) ;
244+ }
245+ }
246+
247+ // Get the latest stable version from npm
248+ let latestStableVersion = null ;
249+ if ( name ) {
250+ try {
251+ const result = runQuiet ( `npm view ${ name } @latest version` , repoDir ) ;
252+ if ( result && result . trim ( ) ) {
253+ latestStableVersion = result . trim ( ) ;
254+ console . log ( `Latest published @latest version: ${ latestStableVersion } ` ) ;
247255 }
248- version = getPackageVersion ( repoDir ) ;
249- attempts += 1 ;
256+ } catch ( err ) {
257+ console . log ( `No @latest version published yet` ) ;
250258 }
259+ }
251260
252- if ( attempts === maxAttempts && packageVersionExists ( name , version , repoDir ) ) {
253- throw new Error ( `Unable to find an unpublished prerelease version after ${ maxAttempts } attempts.` ) ;
261+ let version ;
262+
263+ // Strategy: Always compute baseVersion from the latest published @test version first
264+ // This ensures we're incrementing from what npm sees, not what our local checkout has
265+ if ( latestTestVersion ) {
266+ // Extract the base version and counter from the latest published @test version
267+ // Match pattern 1: X.Y.Z-preid.N (standard test version with counter)
268+ // Match pattern 2: X.Y.Z (just a version, no test suffix yet)
269+ const regexPattern = `^(\\d+\\.\\d+\\.\\d+)(?:.*)?-${ preid . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) } \\.(\\d+)$` ;
270+ console . log ( `[DEBUG] Testing regex: ${ regexPattern } ` ) ;
271+ console . log ( `[DEBUG] Against version: ${ latestTestVersion } ` ) ;
272+ const counterMatch = latestTestVersion . match ( new RegExp ( regexPattern ) ) ;
273+ console . log ( `[DEBUG] Regex match result:` , counterMatch ) ;
274+
275+ if ( counterMatch && counterMatch [ 1 ] ) {
276+ // Found a version with -test.N pattern
277+ const publishedBaseVersion = counterMatch [ 1 ] ;
278+ const publishedCounter = parseInt ( counterMatch [ 2 ] , 10 ) ;
279+ console . log ( `Base version from published @${ preid } : ${ publishedBaseVersion } , counter: ${ publishedCounter } ` ) ;
280+
281+ // Check if the base version from @test matches the latest stable
282+ // If yes, we need to bump to the next patch version for the test
283+ if ( latestStableVersion && publishedBaseVersion === latestStableVersion ) {
284+ console . log ( `Base version ${ publishedBaseVersion } matches latest stable. Bumping to next patch for test...` ) ;
285+ // Increment patch version and start at -test.0
286+ const versionParts = publishedBaseVersion . split ( '.' ) ;
287+ if ( versionParts . length >= 3 ) {
288+ versionParts [ 2 ] = String ( parseInt ( versionParts [ 2 ] , 10 ) + 1 ) ;
289+ const newBaseVersion = versionParts . join ( '.' ) ;
290+ version = `${ newBaseVersion } -${ preid } .0` ;
291+ console . log ( `Bumping to ${ version } ...` ) ;
292+ } else {
293+ // Fallback to normal bump if version format is unexpected
294+ console . log ( `Unexpected version format. Doing normal prerelease bump...` ) ;
295+ const originalScripts = disableVersionScripts ( repoDir ) ;
296+ try {
297+ run ( `npm version prerelease --preid ${ preid } --no-git-tag-version` , repoDir , dryRun ) ;
298+ } finally {
299+ restoreVersionScripts ( repoDir , originalScripts ) ;
300+ }
301+ version = getPackageVersion ( repoDir ) ;
302+ }
303+
304+ // Update package.json manually with this version
305+ if ( version && ! dryRun ) {
306+ const pkgPath = path . join ( repoDir , 'package.json' ) ;
307+ const pkgData = readJson ( pkgPath ) ;
308+ pkgData . version = version ;
309+ fs . writeFileSync ( pkgPath , JSON . stringify ( pkgData , null , 2 ) + '\n' ) ;
310+ }
311+ } else {
312+ // Base version doesn't match stable, increment the test counter
313+ const nextCounter = parseInt ( counterMatch [ 2 ] , 10 ) + 1 ;
314+ version = `${ publishedBaseVersion } -${ preid } .${ nextCounter } ` ;
315+ console . log ( `Latest @${ preid } is ${ latestTestVersion } . Incrementing to ${ version } ...` ) ;
316+
317+ // Update package.json manually with this version
318+ const pkgPath = path . join ( repoDir , 'package.json' ) ;
319+ const pkgData = readJson ( pkgPath ) ;
320+ pkgData . version = version ;
321+ fs . writeFileSync ( pkgPath , JSON . stringify ( pkgData , null , 2 ) + '\n' ) ;
322+ }
323+ } else {
324+ // Couldn't find exact -test.N pattern, but we do have a @test version
325+ // Extract just the X.Y.Z base from latestTestVersion
326+ const baseVersionMatch = latestTestVersion . match ( / ^ ( \d + \. \d + \. \d + ) / ) ;
327+ if ( baseVersionMatch && baseVersionMatch [ 1 ] ) {
328+ const publishedBaseVersion = baseVersionMatch [ 1 ] ;
329+ console . log ( `Found @${ preid } version but no -${ preid } .N pattern. Extracted base: ${ publishedBaseVersion } ` ) ;
330+
331+ // Check if this base version matches the latest stable
332+ if ( latestStableVersion && publishedBaseVersion === latestStableVersion ) {
333+ console . log ( `Base version ${ publishedBaseVersion } matches latest stable. Bumping to next patch for test...` ) ;
334+ const versionParts = publishedBaseVersion . split ( '.' ) ;
335+ if ( versionParts . length >= 3 ) {
336+ versionParts [ 2 ] = String ( parseInt ( versionParts [ 2 ] , 10 ) + 1 ) ;
337+ const newBaseVersion = versionParts . join ( '.' ) ;
338+ version = `${ newBaseVersion } -${ preid } .0` ;
339+ console . log ( `Bumping to ${ version } ...` ) ;
340+ } else {
341+ console . log ( `Unexpected version format. Doing normal prerelease bump...` ) ;
342+ const originalScripts = disableVersionScripts ( repoDir ) ;
343+ try {
344+ run ( `npm version prerelease --preid ${ preid } --no-git-tag-version` , repoDir , dryRun ) ;
345+ } finally {
346+ restoreVersionScripts ( repoDir , originalScripts ) ;
347+ }
348+ version = getPackageVersion ( repoDir ) ;
349+ }
350+
351+ // Update package.json manually with this version
352+ if ( version && ! dryRun ) {
353+ const pkgPath = path . join ( repoDir , 'package.json' ) ;
354+ const pkgData = readJson ( pkgPath ) ;
355+ pkgData . version = version ;
356+ fs . writeFileSync ( pkgPath , JSON . stringify ( pkgData , null , 2 ) + '\n' ) ;
357+ }
358+ } else {
359+ // Base version doesn't match stable, use this version as starting point for -test
360+ version = `${ publishedBaseVersion } -${ preid } .0` ;
361+ console . log ( `Base version ${ publishedBaseVersion } differs from stable. Starting test version at ${ version } ...` ) ;
362+
363+ // Update package.json manually with this version
364+ if ( ! dryRun ) {
365+ const pkgPath = path . join ( repoDir , 'package.json' ) ;
366+ const pkgData = readJson ( pkgPath ) ;
367+ pkgData . version = version ;
368+ fs . writeFileSync ( pkgPath , JSON . stringify ( pkgData , null , 2 ) + '\n' ) ;
369+ }
370+ }
371+ } else {
372+ // Couldn't parse base version at all, do normal prerelease bump
373+ console . log ( `Found @${ preid } (${ latestTestVersion } ) but couldn't parse base version. Doing normal prerelease bump...` ) ;
374+ const originalScripts = disableVersionScripts ( repoDir ) ;
375+ try {
376+ run ( `npm version prerelease --preid ${ preid } --no-git-tag-version` , repoDir , dryRun ) ;
377+ } finally {
378+ restoreVersionScripts ( repoDir , originalScripts ) ;
379+ }
380+ version = getPackageVersion ( repoDir ) ;
381+ if ( dryRun ) {
382+ console . log ( `[dry-run simulation] Prerelease version would be: ${ version } ` ) ;
383+ } else {
384+ console . log ( `After prerelease bump: ${ version } ` ) ;
385+ }
386+ }
387+ }
388+ } else {
389+ // No existing @test version, but we can be smart about versioning
390+ console . log ( `No @${ preid } version found. Determining strategy for first test release...` ) ;
391+
392+ // Extract base version from local version (just X.Y.Z, strip any pre-release identifiers)
393+ const localBaseMatch = localVersion . match ( / ^ ( \d + \. \d + \. \d + ) / ) ;
394+ const localBaseVersion = localBaseMatch ? localBaseMatch [ 1 ] : localVersion ;
395+ console . log ( `Local base version: ${ localBaseVersion } ` ) ;
396+
397+ if ( latestStableVersion && localBaseVersion === latestStableVersion ) {
398+ // Local version base matches latest stable, so bump patch and start at -test.0
399+ console . log ( `Local base ${ localBaseVersion } matches latest stable. Bumping to next patch for test...` ) ;
400+ const versionParts = localBaseVersion . split ( '.' ) ;
401+ if ( versionParts . length >= 3 ) {
402+ versionParts [ 2 ] = String ( parseInt ( versionParts [ 2 ] , 10 ) + 1 ) ;
403+ const newBaseVersion = versionParts . join ( '.' ) ;
404+ version = `${ newBaseVersion } -${ preid } .0` ;
405+ console . log ( `Using ${ version } for test release...` ) ;
406+
407+ if ( ! dryRun ) {
408+ const pkgPath = path . join ( repoDir , 'package.json' ) ;
409+ const pkgData = readJson ( pkgPath ) ;
410+ pkgData . version = version ;
411+ fs . writeFileSync ( pkgPath , JSON . stringify ( pkgData , null , 2 ) + '\n' ) ;
412+ }
413+ } else {
414+ // Fallback to normal prerelease bump if format is unexpected
415+ console . log ( `Unexpected version format. Doing normal prerelease bump...` ) ;
416+ const originalScripts = disableVersionScripts ( repoDir ) ;
417+ try {
418+ run ( `npm version prerelease --preid ${ preid } --no-git-tag-version` , repoDir , dryRun ) ;
419+ } finally {
420+ restoreVersionScripts ( repoDir , originalScripts ) ;
421+ }
422+ version = getPackageVersion ( repoDir ) ;
423+ }
424+ } else {
425+ // Local version is newer than stable (or no stable exists), use it with -test.0
426+ version = `${ localBaseVersion } -${ preid } .0` ;
427+ console . log ( `Local base ${ localBaseVersion } is ahead of stable. Starting test at ${ version } ...` ) ;
428+
429+ if ( ! dryRun ) {
430+ const pkgPath = path . join ( repoDir , 'package.json' ) ;
431+ const pkgData = readJson ( pkgPath ) ;
432+ pkgData . version = version ;
433+ fs . writeFileSync ( pkgPath , JSON . stringify ( pkgData , null , 2 ) + '\n' ) ;
434+ }
435+ }
436+
437+ if ( dryRun ) {
438+ console . log ( `[dry-run simulation] Test version would be: ${ version } ` ) ;
254439 }
255440 }
256441
@@ -325,9 +510,12 @@ function main() {
325510 continue ;
326511 }
327512
328- const { behind, ahead } = getAheadBehind ( repoDir , branch ) ;
329- if ( behind > 0 ) {
330- throw new Error ( `Local branch behind origin/${ branch } . Pull first.` ) ;
513+ // Skip ahead/behind check in dry-run since git commands don't actually execute
514+ if ( ! dryRun ) {
515+ const { behind, ahead } = getAheadBehind ( repoDir , branch ) ;
516+ if ( behind > 0 ) {
517+ throw new Error ( `Local branch behind origin/${ branch } . Pull first.` ) ;
518+ }
331519 }
332520
333521 const pkg = getPackageJson ( repoDir ) ;
0 commit comments