From 2c0191c1ddd5e1b624e7bb51aeb730532862a317 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Tue, 7 Jul 2026 15:10:14 -0400 Subject: [PATCH 1/3] ci: add label-gated signed macOS test build for PRs Add .github/workflows/pr-build-installer.yml. Applying the `build-installer` label to a same-repo PR packages apps/code as a signed (unnotarized) arm64 macOS .dmg/.zip, uploads it as a run artifact, and posts a sticky PR comment linking to the download. Rebuilds on each push while the label is present. Generated-By: PostHog Code Task-Id: 95ef6c10-a78d-4099-8606-789050d9b3e2 --- .github/workflows/pr-build-installer.yml | 158 +++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 .github/workflows/pr-build-installer.yml diff --git a/.github/workflows/pr-build-installer.yml b/.github/workflows/pr-build-installer.yml new file mode 100644 index 0000000000..70bb09353d --- /dev/null +++ b/.github/workflows/pr-build-installer.yml @@ -0,0 +1,158 @@ +name: PR Build Installer + +# On-demand, signed macOS test builds for a pull request. Add the +# `build-installer` label to package apps/code and get a downloadable .dmg/.zip +# attached to the run, with a sticky comment on the PR linking to it. Testers +# download the artifact and run the app locally. +# +# This deliberately does NOT publish a GitHub release or notarize the build. It +# signs (so the app launches after a right-click → Open) but skips notarization, +# which lives in the real release pipeline (code-release.yml, on v* tags). +# +# Gating: only same-repo PRs run — fork PRs never receive the signing/AWS +# secrets, so building them would red spuriously. + +on: + pull_request: + types: [opened, synchronize, reopened, labeled] + +concurrency: + group: pr-build-installer-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + build-macos: + # Run on a same-repo PR that carries the `build-installer` label (either + # just added, or already present on a push). + if: >- + github.event.pull_request.head.repo.full_name == github.repository && + ( + (github.event.action == 'labeled' && github.event.label.name == 'build-installer') || + (github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'build-installer')) + ) + runs-on: macos-15 + timeout-minutes: 45 + permissions: + id-token: write + contents: read + env: + NODE_OPTIONS: "--max-old-space-size=8192" + NODE_ENV: production + npm_config_arch: arm64 + npm_config_platform: darwin + VITE_POSTHOG_API_KEY: ${{ secrets.VITE_POSTHOG_API_KEY }} + VITE_POSTHOG_API_HOST: ${{ secrets.VITE_POSTHOG_API_HOST }} + POSTHOG_SOURCEMAP_API_KEY: ${{ secrets.POSTHOG_SOURCEMAP_API_KEY }} + POSTHOG_ENV_ID: ${{ secrets.POSTHOG_ENV_ID }} + POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} + CSC_LINK: ${{ secrets.APPLE_CODESIGN_CERT_BASE64 }} + CSC_KEY_PASSWORD: ${{ secrets.APPLE_CODESIGN_CERT_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + # Sign but do not notarize: a signed-but-unnotarized app launches for a + # tester after a right-click → Open. Notarization runs only in the tag + # release (code-release.yml). + SKIP_NOTARIZE: "1" + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1 + with: + role-to-assume: ${{ secrets.AWS_TWIG_APP_ASSETS_ROLE_ARN }} + aws-region: ${{ secrets.AWS_TWIG_APP_ASSETS_REGION }} + mask-aws-account-id: true + unset-current-credentials: true + + - name: Download BerkeleyMono fonts from S3 + run: aws s3 cp s3://${{ secrets.AWS_TWIG_APP_ASSETS_BUCKET }}/fonts/BerkeleyMono/ apps/code/assets/fonts/BerkeleyMono/ --recursive + + - name: Build workspace packages + run: | + pnpm --filter @posthog/electron-trpc run build + pnpm --filter @posthog/platform run build + pnpm --filter @posthog/shared run build + pnpm --filter @posthog/git run build + pnpm --filter @posthog/enricher run build + pnpm --filter @posthog/agent run build + + # build/Assets.car is gitignored; regenerate it so the packaged app ships + # the liquid-glass icon (the script falls back to .icns if actool fails). + - name: Compile macOS liquid-glass icon + working-directory: apps/code + run: bash scripts/compile-glass-icon.sh + + - name: Build app + working-directory: apps/code + run: | + pnpm exec electron-vite build + pnpm exec electron-builder build --mac --arm64 --publish never --config electron-builder.ts + + - name: Upload installer artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: code-macos-arm64 + path: | + apps/code/out/*.dmg + apps/code/out/*-mac.zip + retention-days: 7 + if-no-files-found: error + + comment: + # Post the download link once the build finishes. Skipped when build-macos + # is skipped (no label / fork), since `needs` requires its success. + needs: build-macos + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + issues: write + pull-requests: write + steps: + - name: Post download link on the PR + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + set -euo pipefail + MARKER="" + RUN_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + { + echo "$MARKER" + echo "### 🖥️ macOS test build (Apple Silicon)" + echo "" + echo "A signed macOS build for this PR is ready to download and run." + echo "" + echo "**[⬇️ Open this run and download the \`code-macos-arm64\` artifact](${RUN_URL}#artifacts)** (Apple Silicon / M-series)." + echo "" + echo "Unzip it, then open the \`.dmg\` (or the \`.app\` inside the \`-mac.zip\`)." + echo "" + echo "> ℹ️ The build is **signed but not notarized**, so macOS may warn on first launch." + echo "> Right-click the app → **Open**, or run \`xattr -dr com.apple.quarantine \"PostHog Code.app\"\`." + echo "" + echo "Built from ${GITHUB_SHA} · rebuilds on each push while the \`build-installer\` label is present. Downloading requires being signed in to GitHub with repo access." + } > comment-body.md + + COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --paginate \ + --jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" | head -n1 || true) + + if [ -n "${COMMENT_ID}" ]; then + gh api --method PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" -F body=@comment-body.md + else + gh api --method POST "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" -F body=@comment-body.md + fi From 3dc6ee09749e5f73842b46069730ec2e57afa114 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Tue, 7 Jul 2026 15:48:36 -0400 Subject: [PATCH 2/3] ci: update PR build comment on failure instead of leaving it stale Run the comment job on build success OR failure (skipping only when build-macos itself was skipped), and write a "build failed" update when the packaging job fails. Previously the comment job needed build-macos success, so a failed re-run left the prior "ready" comment pointing at artifacts from a different commit. Generated-By: PostHog Code Task-Id: 95ef6c10-a78d-4099-8606-789050d9b3e2 --- .github/workflows/pr-build-installer.yml | 53 ++++++++++++++++-------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/.github/workflows/pr-build-installer.yml b/.github/workflows/pr-build-installer.yml index 70bb09353d..96ea44c4d1 100644 --- a/.github/workflows/pr-build-installer.yml +++ b/.github/workflows/pr-build-installer.yml @@ -114,9 +114,12 @@ jobs: if-no-files-found: error comment: - # Post the download link once the build finishes. Skipped when build-macos - # is skipped (no label / fork), since `needs` requires its success. + # Update the sticky comment once the build finishes, on success OR failure, + # so a failed re-run replaces a prior "ready" comment instead of leaving it + # pointing at stale artifacts. Skipped only when build-macos itself was + # skipped (no label / fork), where there is nothing to report. needs: build-macos + if: ${{ !cancelled() && (needs.build-macos.result == 'success' || needs.build-macos.result == 'failure') }} runs-on: ubuntu-latest timeout-minutes: 5 permissions: @@ -124,29 +127,43 @@ jobs: issues: write pull-requests: write steps: - - name: Post download link on the PR + - name: Post build status on the PR env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} + BUILD_RESULT: ${{ needs.build-macos.result }} run: | set -euo pipefail MARKER="" RUN_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - { - echo "$MARKER" - echo "### 🖥️ macOS test build (Apple Silicon)" - echo "" - echo "A signed macOS build for this PR is ready to download and run." - echo "" - echo "**[⬇️ Open this run and download the \`code-macos-arm64\` artifact](${RUN_URL}#artifacts)** (Apple Silicon / M-series)." - echo "" - echo "Unzip it, then open the \`.dmg\` (or the \`.app\` inside the \`-mac.zip\`)." - echo "" - echo "> ℹ️ The build is **signed but not notarized**, so macOS may warn on first launch." - echo "> Right-click the app → **Open**, or run \`xattr -dr com.apple.quarantine \"PostHog Code.app\"\`." - echo "" - echo "Built from ${GITHUB_SHA} · rebuilds on each push while the \`build-installer\` label is present. Downloading requires being signed in to GitHub with repo access." - } > comment-body.md + if [ "${BUILD_RESULT}" = "success" ]; then + { + echo "$MARKER" + echo "### 🖥️ macOS test build (Apple Silicon)" + echo "" + echo "A signed macOS build for this PR is ready to download and run." + echo "" + echo "**[⬇️ Open this run and download the \`code-macos-arm64\` artifact](${RUN_URL}#artifacts)** (Apple Silicon / M-series)." + echo "" + echo "Unzip it, then open the \`.dmg\` (or the \`.app\` inside the \`-mac.zip\`)." + echo "" + echo "> ℹ️ The build is **signed but not notarized**, so macOS may warn on first launch." + echo "> Right-click the app → **Open**, or run \`xattr -dr com.apple.quarantine \"PostHog Code.app\"\`." + echo "" + echo "Built from ${GITHUB_SHA} · rebuilds on each push while the \`build-installer\` label is present. Downloading requires being signed in to GitHub with repo access." + } > comment-body.md + else + { + echo "$MARKER" + echo "### 🖥️ macOS test build failed" + echo "" + echo "The macOS build for this PR **failed**, so there is no downloadable artifact for the latest commit." + echo "" + echo "**[🔎 View the failed run](${RUN_URL})** for logs." + echo "" + echo "Failed on ${GITHUB_SHA} · rebuilds on each push while the \`build-installer\` label is present." + } > comment-body.md + fi COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --paginate \ --jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" | head -n1 || true) From ed68c0958892cea990a8f90dbc409307beb69705 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Tue, 7 Jul 2026 15:49:47 -0400 Subject: [PATCH 3/3] ci: drop unused contents:read from PR build comment job The comment job only calls the GitHub API to post/update a PR comment; it never checks out or reads repository files. Remove contents:read to follow least privilege. Generated-By: PostHog Code Task-Id: 95ef6c10-a78d-4099-8606-789050d9b3e2 --- .github/workflows/pr-build-installer.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr-build-installer.yml b/.github/workflows/pr-build-installer.yml index 96ea44c4d1..20c9099125 100644 --- a/.github/workflows/pr-build-installer.yml +++ b/.github/workflows/pr-build-installer.yml @@ -123,7 +123,6 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 permissions: - contents: read issues: write pull-requests: write steps: