refactor: unify release workflow into single release-all script

Consolidate version bumping, building, npm publishing, and git operations into a single release script that handles platform packages first. This solves the issue where optionalDependencies needed platform packages published before bun install could generate complete lockfile hashes.

Changes:
- Created cli/scripts/release-all.ts with support for --dry-run, --publish-npm, and --skip-build flags
- Removed release-it dependency and old release/publish-npm scripts
- Simplified GitHub Actions release workflow to always use --generate-notes
- Deleted obsolete release configuration files (.release-it.json, .release-it.notes.js, publish-npm.ts)
This commit is contained in:
weishu
2025-12-25 12:26:49 +08:00
parent d11bfbef86
commit dbbeedea0d
8 changed files with 118 additions and 566 deletions
-145
View File
@@ -1,145 +0,0 @@
/**
* Publish all npm packages (platform packages + main package).
*
* Usage:
* bun run scripts/publish-npm.ts # Publish all packages
* bun run scripts/publish-npm.ts --dry-run # Preview without publishing
* bun run scripts/publish-npm.ts --skip-build # Skip building binaries
*
* Prerequisites:
* - npm login (must be logged in to npm)
* - For scoped packages: npm access must be set to public
*/
import { execSync, spawnSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const projectRoot = join(__dirname, '..');
const repoRoot = join(projectRoot, '..');
const PLATFORMS = [
'darwin-arm64',
'darwin-x64',
'linux-arm64',
'linux-x64',
'win32-x64'
];
function parseArgs(): { dryRun: boolean; skipBuild: boolean } {
const args = process.argv.slice(2);
return {
dryRun: args.includes('--dry-run'),
skipBuild: args.includes('--skip-build')
};
}
function run(cmd: string, cwd: string = projectRoot): void {
console.log(`\n$ ${cmd}`);
execSync(cmd, { cwd, stdio: 'inherit' });
}
function checkNpmLogin(): boolean {
try {
const result = spawnSync('npm', ['whoami'], { encoding: 'utf-8' });
if (result.status === 0) {
console.log(`Logged in as: ${result.stdout.trim()}`);
return true;
}
} catch {
// ignore
}
return false;
}
function checkBinariesExist(): boolean {
const binaries = [
join(projectRoot, 'dist-exe', 'bun-darwin-arm64', 'hapi'),
join(projectRoot, 'dist-exe', 'bun-darwin-x64', 'hapi'),
join(projectRoot, 'dist-exe', 'bun-linux-arm64', 'hapi'),
join(projectRoot, 'dist-exe', 'bun-linux-x64', 'hapi'),
join(projectRoot, 'dist-exe', 'bun-windows-x64', 'hapi.exe')
];
for (const bin of binaries) {
if (!existsSync(bin)) {
return false;
}
}
return true;
}
function publishPackage(pkgDir: string, dryRun: boolean): void {
const cmd = dryRun
? 'npm publish --access public --dry-run'
: 'npm publish --access public';
run(cmd, pkgDir);
}
async function main(): Promise<void> {
const { dryRun, skipBuild } = parseArgs();
console.log('='.repeat(60));
console.log(dryRun ? ' DRY RUN - No packages will be published' : ' PUBLISHING PACKAGES');
console.log('='.repeat(60));
// Check npm login
console.log('\n[1/5] Checking npm login...');
if (!checkNpmLogin()) {
console.error('Error: Not logged in to npm. Run `npm login` first.');
process.exit(1);
}
// Build binaries (includes web assets)
if (!skipBuild) {
console.log('\n[2/5] Building binaries for all platforms (with web assets)...');
run('bun run build:single-exe:all', repoRoot);
} else {
console.log('\n[2/5] Skipping build (--skip-build)');
if (!checkBinariesExist()) {
console.error('Error: Binaries not found. Run without --skip-build first.');
process.exit(1);
}
}
// Prepare npm packages
console.log('\n[3/5] Preparing npm packages...');
run('bun run prepare-npm-packages');
// Publish platform packages
console.log('\n[4/5] Publishing platform packages...');
for (const platform of PLATFORMS) {
const pkgDir = join(projectRoot, 'npm', platform);
console.log(`\nPublishing @twsxtd/hapi-${platform}...`);
try {
publishPackage(pkgDir, dryRun);
} catch (error) {
console.error(`Failed to publish @twsxtd/hapi-${platform}`);
throw error;
}
}
// Publish main package
console.log('\n[5/5] Publishing main package...');
console.log('\nPublishing @twsxtd/hapi...');
publishPackage(projectRoot, dryRun);
console.log('\n' + '='.repeat(60));
console.log(dryRun ? ' DRY RUN COMPLETE' : ' ALL PACKAGES PUBLISHED SUCCESSFULLY');
console.log('='.repeat(60));
if (!dryRun) {
const pkg = await Bun.file(join(projectRoot, 'package.json')).json();
console.log(`\nVersion ${pkg.version} published!`);
console.log('\nUsers can now run:');
console.log(' npx @twsxtd/hapi');
console.log(' bunx @twsxtd/hapi');
}
}
main().catch((error) => {
console.error('\nPublish failed:', error.message);
process.exit(1);
});
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env bun
/**
* Unified release script that handles the complete release flow:
* 1. Bump version
* 2. Build binaries (with embedded web assets)
* 3. Publish platform packages first (so lockfile can resolve them)
* 4. bun install (to get complete lockfile with published packages)
* 5. Publish main package
* 6. Git commit + tag + push
*/
import { execSync } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const scriptDir = import.meta.dir;
const projectRoot = join(scriptDir, '..');
const repoRoot = join(projectRoot, '..');
// 解析参数
const args = process.argv.slice(2);
const version = args.find(arg => !arg.startsWith('--'));
const dryRun = args.includes('--dry-run');
const publishNpm = args.includes('--publish-npm'); // 只发布 npm,跳过 git 操作
const skipBuild = args.includes('--skip-build'); // 跳过构建(二进制已存在)
if (!version) {
console.error('Usage: bun run scripts/release-all.ts <version> [options]');
console.error('Options:');
console.error(' --dry-run Preview the release process');
console.error(' --publish-npm Only publish to npm, skip git operations');
console.error(' --skip-build Skip building binaries (use existing)');
console.error('Example: bun run scripts/release-all.ts 0.2.0');
process.exit(1);
}
function run(cmd: string, cwd = projectRoot): void {
console.log(`\n$ ${cmd}`);
if (!dryRun) {
execSync(cmd, { cwd, stdio: 'inherit' });
}
}
async function main(): Promise<void> {
const flags = [dryRun && 'dry-run', publishNpm && 'publish-npm', skipBuild && 'skip-build'].filter(Boolean);
console.log(`\n🚀 Starting release v${version}${flags.length ? ` (${flags.join(', ')})` : ''}\n`);
// Step 1: Update package.json version
console.log('📦 Step 1: Updating package.json version...');
const pkgPath = join(projectRoot, 'package.json');
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
const oldVersion = pkg.version;
pkg.version = version;
if (!dryRun) {
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
}
console.log(` ${oldVersion} → ${version}`);
// Step 2: Build all platform binaries (with embedded web assets)
if (!skipBuild) {
console.log('\n🔨 Step 2: Building all platform binaries with web assets...');
run('bun run build:single-exe:all', repoRoot);
} else {
console.log('\n🔨 Step 2: Skipping build (--skip-build)');
}
// Step 3: Prepare and publish platform packages
console.log('\n📤 Step 3: Publishing platform packages...');
run('bun run prepare-npm-packages');
const platforms = ['darwin-arm64', 'darwin-x64', 'linux-arm64', 'linux-x64', 'win32-x64'];
for (const platform of platforms) {
const npmDir = join(projectRoot, 'npm', platform);
run(`npm publish --access public${dryRun ? ' --dry-run' : ''}`, npmDir);
}
// Step 4: bun install to get complete lockfile
console.log('\n📥 Step 4: Updating lockfile...');
run('bun install', repoRoot);
// Step 5: Publish main package
console.log('\n📤 Step 5: Publishing main package...');
run(`npm publish --access public${dryRun ? ' --dry-run' : ''}`);
// --publish-npm 模式到此结束
if (publishNpm) {
console.log(`\n✅ Published v${version} to npm!`);
return;
}
// Step 6: Git commit + tag + push
console.log('\n📝 Step 6: Creating git commit and tag...');
run(`git add .`, repoRoot);
run(`git commit -m "Release version ${version}"`, repoRoot);
run(`git tag v${version}`, repoRoot);
run(`git push && git push --tags`, repoRoot);
console.log(`\n✅ Release v${version} completed!`);
}
main().catch(err => {
console.error('Release failed:', err);
process.exit(1);
});