From 968cbedb8a7a2a8ebb0852ac3bc40b5ea247cba0 Mon Sep 17 00:00:00 2001 From: li Date: Tue, 10 Mar 2026 22:26:14 +0800 Subject: [PATCH] ci: migrate binaries to GitHub Release + Actions CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add .github/workflows/build-binaries.yml (Go cross-compile → preset release) - installer.ts: download from GitHub Release, fallback to local bin/ - package.json: exclude binaries from npm (16.3MB → 161KB) - .gitignore: stop tracking bin/codeagent-wrapper-* - git rm --cached 6 pre-compiled binaries --- .github/workflows/build-binaries.yml | 74 ++++++++++++++++++++++++++++ .gitignore | 6 +-- package.json | 2 +- src/utils/installer.ts | 60 +++++++++++++++++++--- 4 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/build-binaries.yml diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml new file mode 100644 index 0000000..29e962f --- /dev/null +++ b/.github/workflows/build-binaries.yml @@ -0,0 +1,74 @@ +name: Build codeagent-wrapper binaries + +on: + push: + paths: + - 'codeagent-wrapper/**' + workflow_dispatch: + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.21' + + - name: Cross-compile binaries + working-directory: codeagent-wrapper + run: | + set -e + mkdir -p ../artifacts + + declare -a targets=( + "darwin:amd64:" + "darwin:arm64:" + "linux:amd64:" + "linux:arm64:" + "windows:amd64:.exe" + "windows:arm64:.exe" + ) + + for target in "${targets[@]}"; do + IFS=':' read -r os arch ext <<< "$target" + output="codeagent-wrapper-${os}-${arch}${ext}" + echo "::group::Building ${output}" + GOOS=$os GOARCH=$arch go build -ldflags="-s -w" -o "../artifacts/${output}" . + echo "::endgroup::" + done + + echo "### Build artifacts" >> $GITHUB_STEP_SUMMARY + ls -lh ../artifacts/ | awk 'NR>1{printf "- `%s` (%s)\n", $NF, $5}' >> $GITHUB_STEP_SUMMARY + + - name: Delete existing preset release + run: gh release delete preset --yes --cleanup-tag 2>/dev/null || true + env: + GH_TOKEN: ${{ github.token }} + + - name: Create preset release + uses: softprops/action-gh-release@v2 + with: + tag_name: preset + name: "Pre-built Binaries (codeagent-wrapper)" + body: | + Pre-compiled `codeagent-wrapper` binaries for all supported platforms. + Automatically downloaded during `npx ccg-workflow` installation. + + | File | Platform | Architecture | + |------|----------|-------------| + | `codeagent-wrapper-darwin-amd64` | macOS | Intel x64 | + | `codeagent-wrapper-darwin-arm64` | macOS | Apple Silicon | + | `codeagent-wrapper-linux-amd64` | Linux | x64 | + | `codeagent-wrapper-linux-arm64` | Linux | ARM64 | + | `codeagent-wrapper-windows-amd64.exe` | Windows | x64 | + | `codeagent-wrapper-windows-arm64.exe` | Windows | ARM64 | + + > This release is automatically updated when `codeagent-wrapper/` source changes. + files: artifacts/* + prerelease: true + make_latest: false diff --git a/.gitignore b/.gitignore index 7eeb427..03a96ce 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,10 @@ codeagent-wrapper/codeagent-wrapper codeagent-wrapper/*.exe *.exe - -# 但是允许 bin/ 目录下的预编译二进制文件 -!bin/*.exe dist/ + +# Pre-compiled binaries (hosted on GitHub Release, not tracked in git) +bin/codeagent-wrapper-* node_modules/ # Python diff --git a/package.json b/package.json index b2f66bf..2226718 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "module": "dist/index.mjs", "types": "dist/index.d.mts", "files": [ - "bin", + "bin/ccg.mjs", "dist", "templates/commands/analyze.md", "templates/commands/backend.md", diff --git a/src/utils/installer.ts b/src/utils/installer.ts index 7d96a41..a2fe5cb 100644 --- a/src/utils/installer.ts +++ b/src/utils/installer.ts @@ -7,6 +7,11 @@ import { parse as parseToml, stringify as stringifyToml } from 'smol-toml' import { backupClaudeCodeConfig, buildMcpServerConfig, fixWindowsMcpConfig, mergeMcpServers, readClaudeCodeConfig, writeClaudeCodeConfig } from './mcp' import { isWindows } from './platform' +// GitHub Release download config +const GITHUB_REPO = 'fengshao1227/ccg-workflow' +const RELEASE_TAG = 'preset' +const BINARY_DOWNLOAD_URL = `https://github.com/${GITHUB_REPO}/releases/download/${RELEASE_TAG}` + const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -24,6 +29,31 @@ function findPackageRoot(startDir: string): string { const PACKAGE_ROOT = findPackageRoot(__dirname) +/** + * Download codeagent-wrapper binary from GitHub Release. + * Uses the fixed `preset` tag release for pre-compiled binaries. + * Supports redirect-following (GitHub releases redirect to CDN). + */ +async function downloadBinaryFromRelease(binaryName: string, destPath: string): Promise { + const url = `${BINARY_DOWNLOAD_URL}/${binaryName}` + + // Use dynamic import for https to follow redirects manually (Node.js fetch follows automatically) + const response = await fetch(url, { redirect: 'follow' }) + if (!response.ok) { + return false + } + + const buffer = Buffer.from(await response.arrayBuffer()) + await fs.writeFile(destPath, buffer) + + // Set executable permission on Unix-like systems + if (process.platform !== 'win32') { + await fs.chmod(destPath, 0o755) + } + + return true +} + // All available commands (20 total after adding spec commands) const ALL_COMMANDS = [ 'workflow', // 完整6阶段开发工作流 @@ -813,16 +843,30 @@ ${workflow.description} return result } - const srcBinary = join(PACKAGE_ROOT, 'bin', binaryName) const destBinary = join(binDir, platform === 'win32' ? 'codeagent-wrapper.exe' : 'codeagent-wrapper') - if (await fs.pathExists(srcBinary)) { - await fs.copy(srcBinary, destBinary) - // Set executable permission on Unix-like systems - if (platform !== 'win32') { - await fs.chmod(destBinary, 0o755) - } + // Strategy 1: Download from GitHub Release (preferred) + let installed = false + try { + installed = await downloadBinaryFromRelease(binaryName, destBinary) + } + catch { + // Download failed, will try fallback + } + // Strategy 2: Fallback to local bin/ (development or offline) + if (!installed) { + const srcBinary = join(PACKAGE_ROOT, 'bin', binaryName) + if (await fs.pathExists(srcBinary)) { + await fs.copy(srcBinary, destBinary) + if (platform !== 'win32') { + await fs.chmod(destBinary, 0o755) + } + installed = true + } + } + + if (installed) { // Verify installation by running --version try { const { execSync } = await import('node:child_process') @@ -836,7 +880,7 @@ ${workflow.description} } } else { - result.errors.push(`Binary not found in package: ${binaryName}`) + result.errors.push(`Failed to obtain binary: ${binaryName} (download failed, no local fallback)`) result.success = false } }