From 909aaca09d5aa37595a265beaf31b43e4be90419 Mon Sep 17 00:00:00 2001 From: weishu Date: Mon, 27 Jul 2026 12:59:02 +0800 Subject: [PATCH] fix(cli): gate main package release on platform packages being live (#1187) - prepare-npm-packages: exit(1) instead of warn-and-continue when a platform binary is missing, so a broken build aborts the release - release-all: after publishing platform packages, poll npm view for every @twsxtd/hapi- package until it matches the release version (10min timeout, 15s interval) before publishing the main package Fixes #1149 --- cli/scripts/prepare-npm-packages.ts | 6 +-- cli/scripts/release-all.ts | 60 ++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/cli/scripts/prepare-npm-packages.ts b/cli/scripts/prepare-npm-packages.ts index 528a72c8..cd7c54f9 100644 --- a/cli/scripts/prepare-npm-packages.ts +++ b/cli/scripts/prepare-npm-packages.ts @@ -179,9 +179,9 @@ async function preparePlatform( const destBin = join(binDir, platform.binName); if (!existsSync(srcBin)) { - console.warn(`Warning: Binary not found: ${srcBin}`); - console.warn(` Run 'bun run build:exe:all' first to build binaries.`); - return; + console.error(`Error: Binary not found: ${srcBin}`); + console.error(` Run 'bun run build:exe:all' first to build binaries.`); + process.exit(1); } copyFileSync(srcBin, destBin); diff --git a/cli/scripts/release-all.ts b/cli/scripts/release-all.ts index a1697f28..5700dc26 100644 --- a/cli/scripts/release-all.ts +++ b/cli/scripts/release-all.ts @@ -4,9 +4,10 @@ * 1. Bump version * 2. Build binaries (with embedded web assets) * 3. Publish platform packages first (so lockfile can resolve them) - * 4. Publish main package - * 5. bun install --lockfile-only --os=* --cpu=* (to lock all platform packages) - * 6. Git commit + tag + push + * 4. Verify all platform packages are live on npm + * 5. Publish main package + * 6. bun install --lockfile-only --os=* --cpu=* (to lock all platform packages) + * 7. Git commit + tag + push */ import { execSync } from 'node:child_process'; @@ -58,6 +59,37 @@ function updateBuildInfoVersion(nextVersion: string): void { } } +async function waitForPlatformPackages(platforms: string[], expectedVersion: string): Promise { + const timeoutMs = 10 * 60 * 1000; + const intervalMs = 15_000; + const deadline = Date.now() + timeoutMs; + const pending = new Set(platforms.map(platform => `@twsxtd/hapi-${platform}`)); + + while (pending.size > 0) { + for (const name of [...pending]) { + try { + const published = execSync(`npm view ${name} version`, { encoding: 'utf-8' }).trim(); + if (published === expectedVersion) { + console.log(` āœ“ ${name}@${expectedVersion} is live on npm`); + pending.delete(name); + } + } catch { + // Not published yet, keep waiting + } + } + if (pending.size === 0) { + return; + } + if (Date.now() >= deadline) { + console.error(`āŒ Timed out waiting for platform packages on npm: ${[...pending].join(', ')}`); + console.error(' The main package was NOT published. Publish the missing platform packages and re-run with --publish-npm.'); + process.exit(1); + } + console.log(` ā³ Waiting for: ${[...pending].join(', ')} (retry in ${intervalMs / 1000}s)...`); + await new Promise(resolve => setTimeout(resolve, intervalMs)); + } +} + async function runWithTimeoutRetry(cmd: string, cwd = projectRoot): Promise { const timeoutCmd = `timeout 60s ${cmd}`; while (true) { @@ -130,8 +162,18 @@ async function main(): Promise { run(`npm publish --access public${dryRun ? ' --dry-run' : ''}`, npmDir); } - // Step 4: Publish main package - console.log('\nšŸ“¤ Step 4: Publishing main package...'); + // Step 4: Verify all platform packages are live on npm before publishing the main package. + // The main package pins platform packages via optionalDependencies, so publishing it first + // would let users install a version whose platform binary does not exist yet. + if (dryRun) { + console.log('\nšŸ” Step 4: Skipping platform package verification (dry-run)'); + } else { + console.log('\nšŸ” Step 4: Verifying platform packages are live on npm...'); + await waitForPlatformPackages(platforms, version); + } + + // Step 5: Publish main package + console.log('\nšŸ“¤ Step 5: Publishing main package...'); const mainNpmDir = join(projectRoot, 'npm', 'main'); run(`npm publish --access public${dryRun ? ' --dry-run' : ''}`, mainNpmDir); @@ -141,12 +183,12 @@ async function main(): Promise { return; } - // Step 5: bun install to get complete lockfile - console.log('\nšŸ“„ Step 5: Updating lockfile for all platform packages...'); + // Step 6: bun install to get complete lockfile + console.log('\nšŸ“„ Step 6: Updating lockfile for all platform packages...'); await runWithTimeoutRetry('bun install --lockfile-only --os=* --cpu=*', repoRoot); - // Step 6: Git commit + tag + push - console.log('\nšŸ“ Step 6: Creating git commit and tag...'); + // Step 7: Git commit + tag + push + console.log('\nšŸ“ Step 7: Creating git commit and tag...'); run(`git add .`, repoRoot); run(`git commit -m "Release version ${version}"`, repoRoot); run(`git tag v${version}`, repoRoot);