diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b425b32a58..c2964e6691 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -67,7 +67,7 @@ jobs: tag: ${{ steps.version.outputs.tag }} repo: ${{ steps.version.outputs.repo }} - build-cli: + build-cli-linux-win: needs: version runs-on: blacksmith-4vcpu-ubuntu-2404 if: github.repository == 'anomalyco/opencode' @@ -94,17 +94,111 @@ jobs: OPENCODE_RELEASE: ${{ needs.version.outputs.release }} GH_REPO: ${{ needs.version.outputs.repo }} GH_TOKEN: ${{ steps.committer.outputs.token }} + OPENCODE_BUILD_OS: linux,win32 + OPENCODE_SKIP_RELEASE_UPLOAD: "1" + + - uses: actions/upload-artifact@v4 + with: + name: opencode-cli-linux-win + path: packages/opencode/dist + + build-cli-darwin: + needs: version + runs-on: macos-latest + if: github.repository == 'anomalyco/opencode' + steps: + - uses: actions/checkout@v3 + with: + fetch-tags: true + + - uses: ./.github/actions/setup-bun + + - name: Setup git committer + id: committer + uses: ./.github/actions/setup-git-committer + with: + opencode-app-id: ${{ vars.OPENCODE_APP_ID }} + opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} + + - uses: apple-actions/import-codesign-certs@v2 + with: + keychain: build + p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} + p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + + - name: Resolve signing identity + run: | + CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") + CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') + if [ -z "$CERT_ID" ]; then + echo "Developer ID Application identity not found" + exit 1 + fi + echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV + + - name: Build + id: build + run: | + ./packages/opencode/script/build.ts + env: + OPENCODE_VERSION: ${{ needs.version.outputs.version }} + OPENCODE_RELEASE: ${{ needs.version.outputs.release }} + GH_REPO: ${{ needs.version.outputs.repo }} + GH_TOKEN: ${{ steps.committer.outputs.token }} + APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} + OPENCODE_BUILD_OS: darwin + OPENCODE_SKIP_RELEASE_UPLOAD: "1" + + - name: Verify darwin signatures + run: | + for file in packages/opencode/dist/opencode-darwin-*/bin/opencode; do + codesign -vvv --verify "$file" + done + + - uses: actions/upload-artifact@v4 + with: + name: opencode-cli-darwin + path: packages/opencode/dist + + build-cli-merge: + needs: + - version + - build-cli-linux-win + - build-cli-darwin + runs-on: blacksmith-4vcpu-ubuntu-2404 + if: github.repository == 'anomalyco/opencode' + steps: + - uses: actions/checkout@v3 + + - uses: ./.github/actions/setup-bun + + - name: Setup git committer + id: committer + uses: ./.github/actions/setup-git-committer + with: + opencode-app-id: ${{ vars.OPENCODE_APP_ID }} + opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} + + - uses: actions/download-artifact@v4 + with: + pattern: opencode-cli-* + path: packages/opencode/dist + merge-multiple: true + + - name: Upload CLI release assets + if: needs.version.outputs.release + run: gh release upload v${{ needs.version.outputs.version }} ./packages/opencode/dist/*.zip ./packages/opencode/dist/*.tar.gz --clobber --repo ${{ needs.version.outputs.repo }} + env: + GH_TOKEN: ${{ steps.committer.outputs.token }} - uses: actions/upload-artifact@v4 with: name: opencode-cli path: packages/opencode/dist - outputs: - version: ${{ needs.version.outputs.version }} build-tauri: needs: - - build-cli + - build-cli-merge - version continue-on-error: false strategy: @@ -248,7 +342,7 @@ jobs: build-electron: needs: - - build-cli + - build-cli-merge - version continue-on-error: false strategy: @@ -372,7 +466,7 @@ jobs: publish: needs: - version - - build-cli + - build-cli-merge - build-tauri - build-electron runs-on: blacksmith-4vcpu-ubuntu-2404 diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index a97cf32496..126719f3c7 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -59,6 +59,10 @@ console.log(`Loaded ${migrations.length} migrations`) const singleFlag = process.argv.includes("--single") const baselineFlag = process.argv.includes("--baseline") const skipInstall = process.argv.includes("--skip-install") +const skipUpload = process.argv.includes("--skip-release-upload") || process.env.OPENCODE_SKIP_RELEASE_UPLOAD === "1" +const sign = process.env.APPLE_SIGNING_IDENTITY +const entitlements = process.env.OPENCODE_CODESIGN_ENTITLEMENTS || path.join(dir, "script/entitlements.plist") +const raw = process.env.OPENCODE_BUILD_OS?.trim() const allTargets: { os: string @@ -144,6 +148,31 @@ const targets = singleFlag }) : allTargets +const os = raw + ? Array.from( + new Set( + raw + .split(",") + .map((x) => x.trim()) + .filter(Boolean), + ), + ) + : undefined + +if (os) { + const set = new Set(allTargets.map((item) => item.os)) + const bad = os.filter((item) => !set.has(item)) + if (bad.length > 0) { + throw new Error(`Invalid OPENCODE_BUILD_OS value: ${bad.join(", ")}`) + } +} + +const list = os ? targets.filter((item) => os.includes(item.os)) : targets + +if (list.length === 0) { + throw new Error("No build targets selected") +} + await $`rm -rf dist` const binaries: Record = {} @@ -151,7 +180,7 @@ if (!skipInstall) { await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}` await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parcel/watcher"]}` } -for (const item of targets) { +for (const item of list) { const name = [ pkg.name, // changing to win32 flags npm for some reason @@ -199,6 +228,24 @@ for (const item of targets) { }, }) + if (item.os === "darwin") { + if (Script.release && process.platform !== "darwin") { + throw new Error("darwin release binaries must be built on macOS runners") + } + if (Script.release && !sign) { + throw new Error("APPLE_SIGNING_IDENTITY is required for darwin release binaries") + } + if (process.platform === "darwin" && sign) { + if (!fs.existsSync(entitlements)) { + throw new Error(`Codesign entitlements file not found: ${entitlements}`) + } + const file = `dist/${name}/bin/opencode` + console.log(`codesigning ${name}`) + await $`codesign --entitlements ${entitlements} -vvvv --deep --sign ${sign} ${file} --force` + await $`codesign -vvv --verify ${file}` + } + } + // Smoke test: only run if binary is for current platform if (item.os === process.platform && item.arch === process.arch && !item.abi) { const binaryPath = `dist/${name}/bin/opencode` @@ -236,7 +283,9 @@ if (Script.release) { await $`zip -r ../../${key}.zip *`.cwd(`dist/${key}/bin`) } } - await $`gh release upload v${Script.version} ./dist/*.zip ./dist/*.tar.gz --clobber --repo ${process.env.GH_REPO}` + if (!skipUpload) { + await $`gh release upload v${Script.version} ./dist/*.zip ./dist/*.tar.gz --clobber --repo ${process.env.GH_REPO}` + } } export { binaries } diff --git a/packages/opencode/script/entitlements.plist b/packages/opencode/script/entitlements.plist new file mode 100644 index 0000000000..afa54db33b --- /dev/null +++ b/packages/opencode/script/entitlements.plist @@ -0,0 +1,16 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + +