mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
- Update CI workflow to package release artifacts with correct naming (hapi-* prefix) - Generate SHA256 checksums for all artifacts - Auto-update homebrew-tap repository on release - Add Installation section to README with Homebrew, npm, and binary options - Simplify .release-it.json config as CI now handles release process - Add update-homebrew-formula script to update formula in homebrew-tap - Add release-artifacts directory to .gitignore Users can now install via: brew install tiann/tap/hapi
232 lines
7.5 KiB
TypeScript
232 lines
7.5 KiB
TypeScript
/**
|
|
* Update the Homebrew formula for hapi.
|
|
*
|
|
* This script:
|
|
* 1. Reads checksums from release-artifacts/checksums.txt
|
|
* 2. Generates an updated hapi.rb formula
|
|
* 3. Optionally clones the tap repo, commits, and pushes
|
|
*
|
|
* Usage:
|
|
* # Generate formula locally (for review)
|
|
* bun run scripts/update-homebrew-formula.ts --version 0.1.0
|
|
*
|
|
* # Generate and push to tap repository
|
|
* bun run scripts/update-homebrew-formula.ts --version 0.1.0 --push
|
|
*
|
|
* Environment:
|
|
* HOMEBREW_TAP_REPO - Git URL of the tap repository (default: https://github.com/tiann/homebrew-tap.git)
|
|
*/
|
|
|
|
import { execSync } from 'node:child_process';
|
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { tmpdir } from 'node:os';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const projectRoot = join(__dirname, '..');
|
|
|
|
interface PlatformSha {
|
|
darwinArm64: string;
|
|
darwinX64: string;
|
|
linuxArm64: string;
|
|
linuxX64: string;
|
|
}
|
|
|
|
function parseChecksums(checksumsPath: string): PlatformSha {
|
|
const content = readFileSync(checksumsPath, 'utf-8');
|
|
const lines = content.trim().split('\n');
|
|
|
|
const shas: Partial<PlatformSha> = {};
|
|
|
|
for (const line of lines) {
|
|
const [sha, filename] = line.split(' ');
|
|
if (!sha || !filename) continue;
|
|
|
|
if (filename.includes('darwin-arm64')) shas.darwinArm64 = sha;
|
|
else if (filename.includes('darwin-x64')) shas.darwinX64 = sha;
|
|
else if (filename.includes('linux-arm64')) shas.linuxArm64 = sha;
|
|
else if (filename.includes('linux-x64')) shas.linuxX64 = sha;
|
|
}
|
|
|
|
const missing: string[] = [];
|
|
if (!shas.darwinArm64) missing.push('darwin-arm64');
|
|
if (!shas.darwinX64) missing.push('darwin-x64');
|
|
if (!shas.linuxArm64) missing.push('linux-arm64');
|
|
if (!shas.linuxX64) missing.push('linux-x64');
|
|
|
|
if (missing.length > 0) {
|
|
throw new Error(`Missing SHA256 checksums for: ${missing.join(', ')}`);
|
|
}
|
|
|
|
return shas as PlatformSha;
|
|
}
|
|
|
|
function generateFormula(version: string, shas: PlatformSha): string {
|
|
return `# typed: false
|
|
# frozen_string_literal: true
|
|
|
|
class Hapi < Formula
|
|
desc "App for agentic coding - access coding agent anywhere"
|
|
homepage "https://github.com/tiann/hapi"
|
|
version "${version}"
|
|
license "MIT"
|
|
|
|
on_macos do
|
|
if Hardware::CPU.arm?
|
|
url "https://github.com/tiann/hapi/releases/download/v#{version}/hapi-darwin-arm64.tar.gz"
|
|
sha256 "${shas.darwinArm64}"
|
|
else
|
|
url "https://github.com/tiann/hapi/releases/download/v#{version}/hapi-darwin-x64.tar.gz"
|
|
sha256 "${shas.darwinX64}"
|
|
end
|
|
end
|
|
|
|
on_linux do
|
|
if Hardware::CPU.arm?
|
|
url "https://github.com/tiann/hapi/releases/download/v#{version}/hapi-linux-arm64.tar.gz"
|
|
sha256 "${shas.linuxArm64}"
|
|
else
|
|
url "https://github.com/tiann/hapi/releases/download/v#{version}/hapi-linux-x64.tar.gz"
|
|
sha256 "${shas.linuxX64}"
|
|
end
|
|
end
|
|
|
|
def install
|
|
bin.install "hapi"
|
|
end
|
|
|
|
test do
|
|
assert_match version.to_s, shell_output("#{bin}/hapi --version")
|
|
end
|
|
end
|
|
`;
|
|
}
|
|
|
|
function printUsage(): void {
|
|
console.log(`Usage:
|
|
bun run scripts/update-homebrew-formula.ts --version <version> [--push]
|
|
|
|
Options:
|
|
--version <version> Version to update to (required)
|
|
--push Clone tap repo, commit and push changes
|
|
--help Show this help message
|
|
|
|
Examples:
|
|
# Generate formula locally
|
|
bun run scripts/update-homebrew-formula.ts --version 0.1.0
|
|
|
|
# Push to tap repository
|
|
bun run scripts/update-homebrew-formula.ts --version 0.1.0 --push
|
|
`);
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.includes('--help') || args.includes('-h')) {
|
|
printUsage();
|
|
process.exit(0);
|
|
}
|
|
|
|
const versionIdx = args.indexOf('--version');
|
|
if (versionIdx === -1 || !args[versionIdx + 1]) {
|
|
console.error('Error: --version is required\n');
|
|
printUsage();
|
|
process.exit(1);
|
|
}
|
|
|
|
const version = args[versionIdx + 1];
|
|
const shouldPush = args.includes('--push');
|
|
const tapRepo = process.env.HOMEBREW_TAP_REPO || 'https://github.com/tiann/homebrew-tap.git';
|
|
const checksumsPath = join(projectRoot, 'release-artifacts', 'checksums.txt');
|
|
|
|
if (!existsSync(checksumsPath)) {
|
|
console.error(`Error: Checksums file not found: ${checksumsPath}`);
|
|
console.error('This file is generated by the CI workflow during release.');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`Generating Homebrew formula for v${version}...\n`);
|
|
|
|
// Parse checksums
|
|
const shas = parseChecksums(checksumsPath);
|
|
console.log('SHA256 checksums:');
|
|
console.log(` darwin-arm64: ${shas.darwinArm64}`);
|
|
console.log(` darwin-x64: ${shas.darwinX64}`);
|
|
console.log(` linux-arm64: ${shas.linuxArm64}`);
|
|
console.log(` linux-x64: ${shas.linuxX64}\n`);
|
|
|
|
// Generate formula content
|
|
const formulaContent = generateFormula(version, shas);
|
|
|
|
if (!shouldPush) {
|
|
// Just output the formula locally
|
|
const localFormulaDir = join(projectRoot, 'release-artifacts', 'Formula');
|
|
mkdirSync(localFormulaDir, { recursive: true });
|
|
|
|
const localFormulaPath = join(localFormulaDir, 'hapi.rb');
|
|
writeFileSync(localFormulaPath, formulaContent);
|
|
|
|
console.log(`Formula generated: ${localFormulaPath}\n`);
|
|
console.log('To push to the tap repository, run with --push flag.');
|
|
console.log(`Or manually copy to your homebrew-tap repo's Formula/ directory.`);
|
|
return;
|
|
}
|
|
|
|
// Clone and push to tap repository
|
|
const githubToken = process.env.GITHUB_TOKEN;
|
|
let cloneUrl = tapRepo;
|
|
|
|
// Use token-authenticated URL in CI
|
|
if (githubToken && tapRepo.includes('github.com')) {
|
|
cloneUrl = tapRepo.replace('https://github.com/', `https://x-access-token:${githubToken}@github.com/`);
|
|
}
|
|
|
|
console.log(`Cloning ${tapRepo}...`);
|
|
|
|
const tempDir = join(tmpdir(), `homebrew-tap-${Date.now()}`);
|
|
mkdirSync(tempDir, { recursive: true });
|
|
|
|
try {
|
|
execSync(`git clone --depth 1 "${cloneUrl}" "${tempDir}"`, { stdio: 'pipe' });
|
|
|
|
// Ensure Formula directory exists
|
|
const formulaDir = join(tempDir, 'Formula');
|
|
mkdirSync(formulaDir, { recursive: true });
|
|
|
|
// Write formula
|
|
const formulaPath = join(formulaDir, 'hapi.rb');
|
|
writeFileSync(formulaPath, formulaContent);
|
|
console.log(`Updated: ${formulaPath}`);
|
|
|
|
// Configure git user for CI
|
|
if (githubToken) {
|
|
execSync('git config user.name "github-actions[bot]"', { cwd: tempDir, stdio: 'pipe' });
|
|
execSync('git config user.email "github-actions[bot]@users.noreply.github.com"', { cwd: tempDir, stdio: 'pipe' });
|
|
}
|
|
|
|
// Commit and push
|
|
execSync('git add Formula/hapi.rb', { cwd: tempDir, stdio: 'pipe' });
|
|
|
|
try {
|
|
execSync(`git commit -m "Update hapi to v${version}"`, { cwd: tempDir, stdio: 'pipe' });
|
|
execSync('git push origin main', { cwd: tempDir, stdio: 'pipe' });
|
|
console.log(`\nSuccessfully pushed hapi v${version} to homebrew-tap`);
|
|
} catch {
|
|
console.log('\nNo changes to commit (formula already up to date)');
|
|
}
|
|
|
|
console.log('\nUsers can now install via:');
|
|
console.log(' brew install tiann/tap/hapi');
|
|
} finally {
|
|
// Cleanup
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('Error:', error.message || error);
|
|
process.exit(1);
|
|
});
|