Skip to content

Commit f6db795

Browse files
committed
feat(workflows): add Homebrew tap update workflow and Node.js script for version management
1 parent 5ed1539 commit f6db795

2 files changed

Lines changed: 382 additions & 0 deletions

File tree

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
#!/usr/bin/env node
2+
3+
const { execSync } = require("child_process");
4+
const { readFileSync, writeFileSync, existsSync } = require("fs");
5+
const { join } = require("path");
6+
const { createHash } = require("crypto");
7+
8+
/**
9+
* Update Homebrew formula with new version and checksums
10+
* @param {string} version - Version string (e.g., "0.1.0")
11+
*/
12+
async function updateHomebrew(version) {
13+
console.log(`🍺 Updating Homebrew formula to v${version}...`);
14+
15+
// Validate version format
16+
if (!/^\d+\.\d+\.\d+$/.test(version)) {
17+
console.error("❌ Invalid version format. Expected: x.y.z (e.g., 1.0.0)");
18+
process.exit(1);
19+
}
20+
21+
const projectRoot = process.cwd();
22+
const homebrewPath = join(projectRoot, "homebrew-tap");
23+
const formulaPath = join(homebrewPath, "Formula", "occtx.rb");
24+
25+
// Check if homebrew-tap directory exists
26+
if (!existsSync(homebrewPath)) {
27+
console.error("❌ homebrew-tap directory not found. Please clone your homebrew-tap repository first.");
28+
console.error(" git clone https://github.com/hungthai1401/homebrew-tap.git");
29+
process.exit(1);
30+
}
31+
32+
// Check if formula file exists
33+
if (!existsSync(formulaPath)) {
34+
console.error("❌ Formula file not found:", formulaPath);
35+
process.exit(1);
36+
}
37+
38+
// Define platforms and their binary names
39+
const platforms = [
40+
{ os: "macos", arch: "aarch64", name: "macOS ARM64 (Apple Silicon)" },
41+
{ os: "macos", arch: "x86_64", name: "macOS x86_64 (Intel)" },
42+
{ os: "linux", arch: "aarch64", name: "Linux ARM64" },
43+
{ os: "linux", arch: "x86_64", name: "Linux x86_64" },
44+
];
45+
46+
const checksums = {};
47+
48+
// Download and calculate checksums for all platforms
49+
for (const { os, arch, name } of platforms) {
50+
const binaryName = `occtx-${os}-${arch}`;
51+
const url = `https://github.com/hungthai1401/occtx/releases/download/v${version}/${binaryName}`;
52+
console.log(`📥 Calculating checksum for ${name}...`);
53+
54+
try {
55+
// Download file temporarily
56+
const tempFile = `/tmp/${binaryName}`;
57+
execSync(`curl -L -s -o ${tempFile} "${url}"`, { stdio: 'pipe' });
58+
59+
// Check if download was successful
60+
if (!existsSync(tempFile)) {
61+
throw new Error(`Failed to download ${url}`);
62+
}
63+
64+
// Calculate SHA256
65+
const buffer = readFileSync(tempFile);
66+
const hash = createHash("sha256").update(buffer).digest("hex");
67+
checksums[`${os}-${arch}`] = hash;
68+
69+
// Clean up
70+
execSync(`rm -f ${tempFile}`);
71+
console.log(`✅ ${name}: ${hash}`);
72+
} catch (error) {
73+
console.error(`❌ Failed to download ${name}:`, error.message);
74+
console.error(` URL: ${url}`);
75+
process.exit(1);
76+
}
77+
}
78+
79+
// Read current formula
80+
console.log("📝 Reading current formula...");
81+
const formula = readFileSync(formulaPath, "utf8");
82+
83+
// Update formula content
84+
console.log("🔄 Updating formula content...");
85+
let updatedFormula = formula;
86+
87+
// Update version
88+
updatedFormula = updatedFormula.replace(
89+
/version "[^"]+"/g,
90+
`version "${version}"`
91+
);
92+
93+
// Update URLs for each platform
94+
updatedFormula = updatedFormula.replace(
95+
/url "https:\/\/github\.com\/hungthai1401\/occtx\/releases\/download\/v[^\/]+\/occtx-macos-aarch64"/g,
96+
`url "https://github.com/hungthai1401/occtx/releases/download/v${version}/occtx-macos-aarch64"`
97+
);
98+
99+
updatedFormula = updatedFormula.replace(
100+
/url "https:\/\/github\.com\/hungthai1401\/occtx\/releases\/download\/v[^\/]+\/occtx-macos-x86_64"/g,
101+
`url "https://github.com/hungthai1401/occtx/releases/download/v${version}/occtx-macos-x86_64"`
102+
);
103+
104+
updatedFormula = updatedFormula.replace(
105+
/url "https:\/\/github\.com\/hungthai1401\/occtx\/releases\/download\/v[^\/]+\/occtx-linux-aarch64"/g,
106+
`url "https://github.com/hungthai1401/occtx/releases/download/v${version}/occtx-linux-aarch64"`
107+
);
108+
109+
updatedFormula = updatedFormula.replace(
110+
/url "https:\/\/github\.com\/hungthai1401\/occtx\/releases\/download\/v[^\/]+\/occtx-linux-x86_64"/g,
111+
`url "https://github.com/hungthai1401/occtx/releases/download/v${version}/occtx-linux-x86_64"`
112+
);
113+
114+
// Update SHA256 checksums using a more precise approach
115+
console.log("🔐 Updating SHA256 checksums...");
116+
117+
// Split content into lines for precise replacement
118+
const lines = updatedFormula.split('\n');
119+
let inMacosSection = false;
120+
let inLinuxSection = false;
121+
let inArmBlock = false;
122+
123+
for (let i = 0; i < lines.length; i++) {
124+
const line = lines[i];
125+
126+
// Track which section we're in
127+
if (line.includes('on_macos do')) {
128+
inMacosSection = true;
129+
inLinuxSection = false;
130+
} else if (line.includes('on_linux do')) {
131+
inLinuxSection = true;
132+
inMacosSection = false;
133+
} else if (line.trim() === 'end' && (inMacosSection || inLinuxSection)) {
134+
// Check if this is the end of the platform section (not just an if block)
135+
let foundPlatformBlock = false;
136+
for (let j = Math.max(0, i - 10); j < i; j++) {
137+
if (lines[j].includes('on_macos do') || lines[j].includes('on_linux do')) {
138+
foundPlatformBlock = true;
139+
break;
140+
}
141+
}
142+
if (foundPlatformBlock && !line.includes(' ')) { // Top-level end
143+
inMacosSection = false;
144+
inLinuxSection = false;
145+
}
146+
}
147+
148+
// Track ARM vs x86 blocks
149+
if (line.includes('if Hardware::CPU.arm?')) {
150+
inArmBlock = true;
151+
} else if (line.includes('else') && (inMacosSection || inLinuxSection)) {
152+
inArmBlock = false;
153+
}
154+
155+
// Update SHA256 hashes
156+
if (line.includes('sha256') && line.includes('"')) {
157+
if (inMacosSection && inArmBlock) {
158+
lines[i] = line.replace(/sha256 "[^"]*"/, `sha256 "${checksums['macos-aarch64']}"`);
159+
} else if (inMacosSection && !inArmBlock) {
160+
lines[i] = line.replace(/sha256 "[^"]*"/, `sha256 "${checksums['macos-x86_64']}"`);
161+
} else if (inLinuxSection && inArmBlock) {
162+
lines[i] = line.replace(/sha256 "[^"]*"/, `sha256 "${checksums['linux-aarch64']}"`);
163+
} else if (inLinuxSection && !inArmBlock) {
164+
lines[i] = line.replace(/sha256 "[^"]*"/, `sha256 "${checksums['linux-x86_64']}"`);
165+
}
166+
}
167+
}
168+
169+
updatedFormula = lines.join('\n');
170+
171+
// Write updated formula
172+
writeFileSync(formulaPath, updatedFormula);
173+
console.log("✅ Updated Formula/occtx.rb");
174+
175+
// Show the updated content
176+
console.log("\n📄 Updated formula content:");
177+
console.log("=" .repeat(50));
178+
console.log(updatedFormula);
179+
console.log("=" .repeat(50));
180+
181+
// Commit and push to homebrew-tap
182+
console.log("\n🚀 Committing and pushing changes...");
183+
try {
184+
// Configure git if needed
185+
try {
186+
execSync('git config user.name', { cwd: homebrewPath, stdio: 'pipe' });
187+
} catch {
188+
execSync('git config user.name "GitHub Actions"', { cwd: homebrewPath });
189+
execSync('git config user.email "actions@github.com"', { cwd: homebrewPath });
190+
}
191+
192+
// Add changes
193+
execSync("git add .", { cwd: homebrewPath });
194+
195+
// Check if there are changes to commit
196+
try {
197+
execSync("git diff --cached --exit-code", { cwd: homebrewPath, stdio: 'pipe' });
198+
console.log("ℹ️ No changes to commit");
199+
return;
200+
} catch {
201+
// There are changes, continue with commit
202+
}
203+
204+
// Commit with detailed message
205+
const commitMessage = `Update occtx to version ${version}
206+
207+
- Version: ${version}
208+
- macOS ARM64: ${checksums['macos-aarch64']}
209+
- macOS x86_64: ${checksums['macos-x86_64']}
210+
- Linux ARM64: ${checksums['linux-aarch64']}
211+
- Linux x86_64: ${checksums['linux-x86_64']}
212+
- Release: https://github.com/hungthai1401/occtx/releases/tag/v${version}
213+
214+
Auto-updated by GitHub Actions`;
215+
216+
execSync(`git commit -m "${commitMessage}"`, { cwd: homebrewPath });
217+
execSync("git push origin main", { cwd: homebrewPath });
218+
console.log("✅ Pushed to homebrew-tap repository");
219+
} catch (error) {
220+
console.error("❌ Failed to update homebrew-tap:", error.message);
221+
process.exit(1);
222+
}
223+
224+
console.log(`\n🍺 Homebrew formula updated to v${version} successfully! 🎉`);
225+
226+
// Summary
227+
console.log("\n📊 Summary:");
228+
console.log(`Version: ${version}`);
229+
console.log(`macOS ARM64 SHA: ${checksums['macos-aarch64']}`);
230+
console.log(`macOS x86_64 SHA: ${checksums['macos-x86_64']}`);
231+
console.log(`Linux ARM64 SHA: ${checksums['linux-aarch64']}`);
232+
console.log(`Linux x86_64 SHA: ${checksums['linux-x86_64']}`);
233+
}
234+
235+
// Export for use in other scripts
236+
module.exports = { updateHomebrew };
237+
238+
// Allow direct execution
239+
if (require.main === module) {
240+
const version = process.argv[2] || process.env.OCCTX_VERSION;
241+
if (!version) {
242+
console.error("❌ Version is required!");
243+
console.error("");
244+
console.error("Usage:");
245+
console.error(" node .github/workflows/scripts/update-homebrew.js <version>");
246+
console.error(" OCCTX_VERSION=1.0.0 node .github/workflows/scripts/update-homebrew.js");
247+
console.error("");
248+
console.error("Examples:");
249+
console.error(" node .github/workflows/scripts/update-homebrew.js 1.0.0");
250+
console.error(" OCCTX_VERSION=1.0.0 node .github/workflows/scripts/update-homebrew.js");
251+
process.exit(1);
252+
}
253+
254+
updateHomebrew(version).catch((error) => {
255+
console.error("❌ Script failed:", error.message);
256+
process.exit(1);
257+
});
258+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
name: Update Homebrew Tap
2+
3+
on:
4+
release:
5+
types: [published]
6+
workflow_dispatch:
7+
inputs:
8+
version:
9+
description: 'Version to update (e.g., 1.0.0)'
10+
required: true
11+
type: string
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
update-homebrew-tap:
18+
name: Update Homebrew Tap
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout main repository
22+
uses: actions/checkout@v4
23+
24+
- name: Extract or set version
25+
id: version
26+
run: |
27+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
28+
VERSION="${{ github.event.inputs.version }}"
29+
TAG="v$VERSION"
30+
echo "🔧 Manual trigger with version: $VERSION"
31+
else
32+
VERSION="${GITHUB_REF#refs/tags/v}"
33+
TAG="${GITHUB_REF#refs/tags/}"
34+
echo "🚀 Release trigger with version: $VERSION"
35+
fi
36+
37+
echo "version=$VERSION" >> $GITHUB_OUTPUT
38+
echo "tag=$TAG" >> $GITHUB_OUTPUT
39+
40+
echo "Version: $VERSION"
41+
echo "Tag: $TAG"
42+
43+
- name: Set up Node.js
44+
uses: actions/setup-node@v4
45+
with:
46+
node-version: '18'
47+
48+
- name: Update Homebrew formula using Node.js script
49+
run: |
50+
# Configure git for the script
51+
git config --global user.name "github-actions[bot]"
52+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
53+
54+
# Clone homebrew-tap repository with authentication
55+
git clone https://${{ secrets.GITHUB_TOKEN }}@github.com/hungthai1401/homebrew-tap.git homebrew-tap
56+
57+
# Run the Node.js update script
58+
echo "🚀 Updating Homebrew formula..."
59+
node .github/workflows/scripts/update-homebrew.js ${{ steps.version.outputs.version }}
60+
env:
61+
OCCTX_VERSION: ${{ steps.version.outputs.version }}
62+
63+
- name: Verify formula was updated
64+
run: |
65+
echo "🔍 Verifying formula update was successful..."
66+
67+
if [ -f "homebrew-tap/Formula/occtx.rb" ]; then
68+
echo "✅ Formula file exists"
69+
70+
# Check if version was updated
71+
if grep -q "version \"${{ steps.version.outputs.version }}\"" homebrew-tap/Formula/occtx.rb; then
72+
echo "✅ Version updated successfully"
73+
else
74+
echo "❌ Version was not updated"
75+
exit 1
76+
fi
77+
78+
# Basic syntax check if Ruby is available
79+
if command -v ruby >/dev/null 2>&1; then
80+
echo "🔍 Running Ruby syntax check..."
81+
if ruby -c homebrew-tap/Formula/occtx.rb 2>/dev/null; then
82+
echo "✅ Ruby syntax check passed"
83+
else
84+
echo "❌ Ruby syntax check failed"
85+
echo "📄 Formula content for debugging:"
86+
cat homebrew-tap/Formula/occtx.rb
87+
exit 1
88+
fi
89+
else
90+
echo "⚠️ Ruby not available, skipping syntax check"
91+
echo "📝 Note: Formula syntax will be validated when users install via Homebrew"
92+
echo "🔍 Basic file validation..."
93+
94+
# Basic validation without Ruby
95+
if grep -q "class Occtx < Formula" homebrew-tap/Formula/occtx.rb; then
96+
echo "✅ Formula class definition found"
97+
else
98+
echo "❌ Formula class definition not found"
99+
exit 1
100+
fi
101+
102+
if grep -q "def install" homebrew-tap/Formula/occtx.rb; then
103+
echo "✅ Install method found"
104+
else
105+
echo "❌ Install method not found"
106+
exit 1
107+
fi
108+
109+
if grep -q "sha256" homebrew-tap/Formula/occtx.rb; then
110+
echo "✅ SHA256 hashes found"
111+
else
112+
echo "❌ SHA256 hashes not found"
113+
exit 1
114+
fi
115+
116+
echo "✅ Basic formula validation passed"
117+
fi
118+
119+
echo "📄 Final formula content:"
120+
cat homebrew-tap/Formula/occtx.rb
121+
else
122+
echo "❌ Formula file not found"
123+
exit 1
124+
fi

0 commit comments

Comments
 (0)