diff --git a/.github/workflows/build-artifacts.yml b/.github/workflows/build-artifacts.yml new file mode 100644 index 0000000000..0d78a131fb --- /dev/null +++ b/.github/workflows/build-artifacts.yml @@ -0,0 +1,152 @@ +# .github/workflows/build-artifacts.yml +name: Desktop build artifacts + +# Dispatchable unsigned desktop build for an external release conductor. +# Builds the Electron app for all four platforms at a pinned ref with a +# caller-supplied version, uploads each installer as a short-lived run +# artifact, and emits a digests.json artifact (a flat JSON object mapping +# each artifact filename to its sha256 hex string) that the conductor reads +# to verify hashes before signing. Creates no release, references no secrets. +# macOS output is intentionally UNSIGNED: forge.config.ts only activates +# osxSign/osxNotarize when the Apple env vars are present, and this workflow +# never sets them (no macos-signing-setup). The conductor signs downstream. + +on: + workflow_dispatch: + inputs: + ref: + description: "Git ref or SHA to build" + type: string + required: true + version: + description: "Version string to stamp into the app" + type: string + required: true + +permissions: + contents: read + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + # `artifact` is the pinned upload-artifact name the downstream release + # conductor consumes; the contained filenames keep the existing stable + # alias names so the cask/updater/landing stay compatible. Do not + # rename either without updating the conductor. + - os: macos-latest + platform: darwin-arm64 + artifact: mac-arm64 + - os: macos-15-intel + platform: darwin-x64 + artifact: mac-x64 + - os: windows-latest + platform: win32-x64 + artifact: win + - os: ubuntu-latest + platform: linux-x64 + artifact: linux + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: frontend/package-lock.json + # The daemon is compiled by build-daemon.mjs during premake, so the Go + # toolchain must be present and pinned on every runner. + - uses: actions/setup-go@v5 + with: + go-version-file: backend/go.mod + cache-dependency-path: backend/go.sum + - run: npm ci + # Same stamping mechanism as frontend-nightly.yml: rewrite package.json + # to the caller's version, dropping any +build metadata. + - name: Stamp version + shell: bash + env: + BUILD_VERSION: ${{ inputs.version }} + run: | + node -e "const v=process.env.BUILD_VERSION.split('+')[0]; const fs=require('fs'); const p=require('./package.json'); p.version=v; fs.writeFileSync('./package.json', JSON.stringify(p,null,'\t')+'\n');" + - name: Build (unsigned, no publish) + shell: bash + run: npm run make + - name: Collect artifact and compute digest + shell: bash + env: + PLATFORM: ${{ matrix.platform }} + run: | + mkdir -p dist-artifact + case "$PLATFORM" in + darwin-*) + # maker-zip output: out/make/zip/darwin//-.zip + src="$(find out/make/zip/darwin -name '*.zip' | head -n1)" + name="agent-orchestrator-$PLATFORM.zip" + ;; + win32-x64) + # NSIS (electron-builder) output: out/make/ Setup .exe + src="$(find out/make -maxdepth 1 -name '*Setup*.exe' | head -n1)" + name="agent-orchestrator-win32-x64.exe" + ;; + linux-x64) + # AppImage (electron-builder) output: out/make/-.AppImage + src="$(find out/make -maxdepth 1 -name '*.AppImage' | head -n1)" + name="agent-orchestrator-linux-x64.AppImage" + ;; + *) + echo "unknown platform: $PLATFORM" >&2 + exit 1 + ;; + esac + if [ -z "$src" ]; then echo "no build output found for $PLATFORM" >&2; exit 1; fi + cp "$src" "dist-artifact/$name" + hash="$(openssl dgst -sha256 "dist-artifact/$name" | awk '{print $NF}')" + # Pinned digest contract: a flat filename -> sha256 hex map, merged + # into one digests.json by the digests job below. + jq -n --arg f "$name" --arg h "$hash" \ + '{($f): $h}' > "dist-artifact/$PLATFORM.digest.json" + echo "$PLATFORM: $name sha256=$hash" >> "$GITHUB_STEP_SUMMARY" + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: frontend/dist-artifact/agent-orchestrator-${{ matrix.platform }}.* + retention-days: 1 + if-no-files-found: error + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digest-${{ matrix.platform }} + path: frontend/dist-artifact/${{ matrix.platform }}.digest.json + retention-days: 1 + if-no-files-found: error + + # Merge the per-platform digest fragments into one digests.json artifact so + # a downstream consumer reads every expected hash from a single file. + digests: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + pattern: digest-* + merge-multiple: true + - name: Merge digests + shell: bash + run: | + jq -s 'add' ./*.digest.json > digests.json + { echo '### Artifact digests'; echo '```json'; cat digests.json; echo '```'; } >> "$GITHUB_STEP_SUMMARY" + - uses: actions/upload-artifact@v4 + with: + name: digests + path: digests.json + retention-days: 1 + if-no-files-found: error