mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
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:
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"git": {
|
||||
"commitMessage": "Release version ${version}",
|
||||
"tagName": "v${version}",
|
||||
"push": true,
|
||||
"requireCleanWorkingDir": true,
|
||||
"requireBranch": "main",
|
||||
"addUntrackedFiles": false
|
||||
},
|
||||
"github": {
|
||||
"release": false
|
||||
},
|
||||
"npm": {
|
||||
"publish": false
|
||||
},
|
||||
"hooks": {
|
||||
"after:bump": "node .release-it.notes.js ${latestTag} ${version} > ../RELEASE_NOTES.md && git add ../RELEASE_NOTES.md"
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
/**
|
||||
* Generate release notes using Claude Code by analyzing git commits
|
||||
* Usage: node scripts/generate-release-notes.js <from-tag> <to-version>
|
||||
*/
|
||||
|
||||
const [,, fromTag, toVersion] = process.argv;
|
||||
|
||||
if (!fromTag || !toVersion) {
|
||||
console.error('Usage: node scripts/generate-release-notes.js <from-tag> <to-version>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function generateReleaseNotes() {
|
||||
try {
|
||||
// Get commit range for the release
|
||||
const commitRange = fromTag === 'null' || !fromTag ? '--all' : `${fromTag}..HEAD`;
|
||||
|
||||
// Get git log for the commits
|
||||
let gitLog;
|
||||
try {
|
||||
gitLog = execSync(
|
||||
`git log ${commitRange} --pretty=format:"%h - %s (%an, %ar)" --no-merges`,
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
} catch (error) {
|
||||
// Fallback to recent commits if tag doesn't exist
|
||||
console.error(`Tag ${fromTag} not found, using recent commits instead`);
|
||||
gitLog = execSync(
|
||||
`git log -10 --pretty=format:"%h - %s (%an, %ar)" --no-merges`,
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
}
|
||||
|
||||
if (!gitLog.trim()) {
|
||||
console.error('No commits found for release notes generation');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create a prompt for Claude to analyze commits and generate release notes
|
||||
const prompt = `Please analyze these git commits and generate professional release notes for version ${toVersion} of the Happy CLI tool (a Claude Code session sharing CLI).
|
||||
|
||||
Git commits:
|
||||
${gitLog}
|
||||
|
||||
Please format the output as markdown with:
|
||||
- A brief summary of the release
|
||||
- Organized sections for:
|
||||
- 🚀 New Features
|
||||
- 🐛 Bug Fixes
|
||||
- ♻️ Refactoring
|
||||
- 🔧 Other Changes
|
||||
- Use bullet points for each change
|
||||
- Keep descriptions concise but informative
|
||||
- Focus on user-facing changes
|
||||
- New line after each section
|
||||
|
||||
Do not include any preamble or explanations, just return the markdown release notes.`;
|
||||
|
||||
// Call Claude Code to generate release notes
|
||||
console.error('Generating release notes with Claude Code...');
|
||||
const releaseNotes = execSync(
|
||||
`claude --print "${prompt}"`,
|
||||
{
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
maxBuffer: 1024 * 1024 * 10 // 10MB buffer
|
||||
}
|
||||
);
|
||||
|
||||
// Output release notes to stdout for release-it to use
|
||||
console.log(releaseNotes.trim());
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating release notes:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
generateReleaseNotes();
|
||||
+1
-5
@@ -39,8 +39,6 @@
|
||||
"build:exe:allinone:all": "bun run scripts/build-executable.ts --with-web-assets --all",
|
||||
"prepare-npm-packages": "bun run scripts/prepare-npm-packages.ts",
|
||||
"prepack": "bun run prepare-npm-packages",
|
||||
"publish-npm": "bun run scripts/publish-npm.ts",
|
||||
"publish-npm:dry-run": "bun run scripts/publish-npm.ts --dry-run",
|
||||
"tools:unpack": "bun run scripts/unpack-tools.ts",
|
||||
"update-homebrew-formula": "bun run scripts/update-homebrew-formula.ts",
|
||||
"test": "bun run tools:unpack && vitest run",
|
||||
@@ -48,8 +46,7 @@
|
||||
"dev": "tsx src/index.ts",
|
||||
"dev:local-server": "tsx --env-file .env.dev-local-server src/index.ts",
|
||||
"dev:integration-test-env": "tsx --env-file .env.integration-test src/index.ts",
|
||||
"release": "release-it",
|
||||
"release:dry-run": "release-it --dry-run"
|
||||
"release-all": "bun run scripts/release-all.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.22.0",
|
||||
@@ -78,7 +75,6 @@
|
||||
"dotenv": "^16.6.1",
|
||||
"eslint": "^9",
|
||||
"eslint-config-prettier": "^10",
|
||||
"release-it": "^19.2.1",
|
||||
"shx": "^0.3.3",
|
||||
"ts-node": "^10",
|
||||
"tsx": "^4.20.6",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user