From c76e7f67453fa108c4ccf58aab63db89528f1752 Mon Sep 17 00:00:00 2001 From: Jeff Dutton Date: Thu, 26 Mar 2026 08:01:39 -0400 Subject: [PATCH] fix(publish): update @next dist-tag on stable npm releases publish-with-rollback.ts now has a Phase 2 that runs npm dist-tag add @ next on all packages when UPDATE_NEXT=true (set by your publish workflow via determine-publish-tags.ts). Previously @next was only set during RC publishes; after a stable release it stayed pinned to the last RC indefinitely. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 3 + .../dev-tools/src/publish-with-rollback.ts | 82 ++++++++++++++++++- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96c7c1b..4a6dedc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- **`@next` dist-tag now updated on stable npm releases** โ€” `publish-with-rollback.ts` adds a Phase 2 that applies `npm dist-tag add @ next` to all packages when `UPDATE_NEXT=true`; set this env var from your publish workflow (e.g. via `determine-publish-tags.ts`) so that after a stable release `@next` points to it rather than staying pinned to the last RC + ### Added - Initial monorepo template setup - TypeScript configuration with strict mode diff --git a/packages/dev-tools/src/publish-with-rollback.ts b/packages/dev-tools/src/publish-with-rollback.ts index 97c1a51..967eee6 100644 --- a/packages/dev-tools/src/publish-with-rollback.ts +++ b/packages/dev-tools/src/publish-with-rollback.ts @@ -181,6 +181,31 @@ function deprecatePackage(packageName: string, version: string, dryRun: boolean) return { success: false }; } +function addDistTag(packageName: string, version: string, tag: string, dryRun: boolean): { success: boolean; dryRun?: boolean } { + const fullPackageName = packageName === UMBRELLA_PACKAGE_NAME + ? UMBRELLA_PACKAGE_NAME + : `${VIBE_AGENT_TOOLKIT_SCOPE}${packageName}`; + + log(` Adding @${tag} tag to ${fullPackageName}@${version}...`, 'blue'); + + if (dryRun) { + log(' [DRY-RUN] Skipping actual tag addition', 'yellow'); + return { success: true, dryRun: true }; + } + + const result = safeExecResult('npm', ['dist-tag', 'add', `${fullPackageName}@${version}`, tag], { + stdio: 'pipe', + }); + + if (result.success) { + log(` โœ… @${tag} tag added`, 'green'); + return { success: true }; + } + + log(` โŒ Failed to add @${tag} tag`, 'red'); + return { success: false }; +} + function rollback(dryRun: boolean): void { log('\n๐Ÿ”„ ROLLBACK: Attempting to unpublish packages...', 'yellow'); log('โ”€'.repeat(60), 'yellow'); @@ -205,6 +230,34 @@ function rollback(dryRun: boolean): void { cleanupManifest(); } +/** + * Phase 2: Update @next tag for stable releases if needed. + * + * Set UPDATE_NEXT=true in the environment (e.g. from your publish.yml via + * determine-publish-tags.ts) to trigger this after a stable release. + */ +function updateNextTag(version: string, dryRun: boolean): void { + log('\n๐Ÿ“‹ Phase 2: Updating @next tag...', 'blue'); + log('โ”€'.repeat(60), 'blue'); + + for (const pkg of PACKAGES) { + const result = addDistTag(pkg, version, 'next', dryRun); + + if (!result.success) { + log('\nโŒ Failed to add @next tag!', 'red'); + log(` Package: ${pkg}`, 'red'); + log(' All packages published but @next tag incomplete', 'red'); + rollback(dryRun); + process.exit(1); + } + } + + manifest.nextTagAdded = true; + saveManifest(); + log('\n โœ… @next tag added to all packages', 'green'); + log('\nโœ… Phase 2 complete', 'green'); +} + function main(): void { const args = process.argv.slice(2); @@ -238,20 +291,27 @@ Examples: const isStable = semver.prerelease(version) === null; const primaryTag = isStable ? 'latest' : 'next'; + const updateNext = isStable && process.env['UPDATE_NEXT'] === 'true'; manifest.version = version; manifest.primaryTag = primaryTag; saveManifest(); log('\n' + '='.repeat(60), 'blue'); - log(`๐Ÿ“ฆ Publishing vibe-agent-toolkit v${version}`, 'blue'); + log(`๐Ÿ“ฆ Publishing v${version}`, 'blue'); log(`๐Ÿท๏ธ Primary npm tag: @${primaryTag}`, 'blue'); + if (updateNext) { + log('๐Ÿท๏ธ Will also update @next tag', 'blue'); + } if (dryRun) { log('๐Ÿงช DRY-RUN MODE', 'yellow'); } log('='.repeat(60) + '\n', 'blue'); - // Publish all packages + // Phase 1: Publish all packages with primary tag + log('๐Ÿ“‹ Phase 1: Publishing packages...', 'blue'); + log('โ”€'.repeat(60), 'blue'); + for (const pkg of PACKAGES) { const result = publishPackage(pkg, version, primaryTag, dryRun); @@ -265,8 +325,24 @@ Examples: } } + log('\nโœ… Phase 1 complete - all packages published', 'green'); + + // Phase 2: For stable versions, add @next tag if requested + if (updateNext) { + updateNextTag(version, dryRun); + } + cleanupManifest(); - log('\nโœ… PUBLISH SUCCESSFUL', 'green'); + + log('\n' + '='.repeat(60), 'green'); + log('โœ… PUBLISH SUCCESSFUL', 'green'); + log(`Version: ${version}`, 'green'); + log(`Primary tag: @${primaryTag}`, 'green'); + if (manifest.nextTagAdded) { + log('@next tag: Updated', 'green'); + } + log('='.repeat(60), 'green'); + process.exit(0); }