Skip to content

gate release publish to tags, dry-run on branch #596

gate release publish to tags, dry-run on branch

gate release publish to tags, dry-run on branch #596

Workflow file for this run

name: Release PostHog Code
on:
push:
tags:
- "v*"
branches:
# Temporary: full pre-merge dry run of the release pipeline on this branch.
# Builds, signs and verifies every platform but does NOT create or publish a
# GitHub release (all release steps are gated to tags below). Remove this
# entry before merging.
- refactor/electron-vite
workflow_dispatch:
concurrency:
# Per-ref so a branch dry run never serializes behind or cancels a tag release.
group: code-release-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') }}
jobs:
prepare-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Get app token
id: app-token
uses: getsentry/action-github-app-token@d4b5da6c5e37703f8c3b3e43abb5705b46e159cc # v3.0.0
with:
app_id: ${{ secrets.GH_APP_ARRAY_RELEASER_APP_ID }}
private_key: ${{ secrets.GH_APP_ARRAY_RELEASER_PRIVATE_KEY }}
- name: Extract version from tag
id: version
run: |
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT"
- name: Create draft release if absent
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_VERSION: ${{ steps.version.outputs.version }}
run: |
# Pre-create a single draft release so every parallel publish job uploads
# into the same release. Without this, parallel jobs each create-if-missing
# at once, producing multiple releases for one tag and scattering assets
# (the orphans get left as drafts).
if gh release view "v$APP_VERSION" --repo PostHog/code >/dev/null 2>&1; then
echo "Release v$APP_VERSION already exists — reusing it"
else
gh release create "v$APP_VERSION" --repo PostHog/code --draft \
--title "v$APP_VERSION" --notes "Release v$APP_VERSION (build in progress)"
fi
publish-macos:
needs: [prepare-release]
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
runner: macos-15
- arch: x64
runner: macos-15-intel
runs-on: ${{ matrix.runner }}
permissions:
id-token: write
contents: write
env:
NODE_OPTIONS: "--max-old-space-size=8192"
NODE_ENV: production
npm_config_arch: ${{ matrix.arch }}
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_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
steps:
- name: Get app token
id: app-token
uses: getsentry/action-github-app-token@d4b5da6c5e37703f8c3b3e43abb5705b46e159cc # v3.0.0
with:
app_id: ${{ secrets.GH_APP_ARRAY_RELEASER_APP_ID }}
private_key: ${{ secrets.GH_APP_ARRAY_RELEASER_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
persist-credentials: false
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: "pnpm"
- name: Resolve version
id: version
run: |
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
VERSION="${GITHUB_REF#refs/tags/v}"
else
VERSION="$(jq -r .version apps/code/package.json)"
fi
echo "Version: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Set version in package.json
env:
APP_VERSION: ${{ steps.version.outputs.version }}
run: |
jq --arg v "$APP_VERSION" '.version = $v' apps/code/package.json > tmp.json && mv tmp.json apps/code/package.json
echo "Set apps/code/package.json version to $APP_VERSION"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.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 electron-trpc package
run: pnpm --filter @posthog/electron-trpc run build
- name: Build platform package
run: pnpm --filter @posthog/platform run build
- name: Build shared package
run: pnpm --filter @posthog/shared run build
- name: Build git package
run: pnpm --filter @posthog/git run build
- name: Build enricher package
run: pnpm --filter @posthog/enricher run build
- name: Build agent package
run: pnpm --filter @posthog/agent run build
- name: Build release artifacts
env:
APP_VERSION: ${{ steps.version.outputs.version }}
working-directory: apps/code
run: |
pnpm exec electron-vite build
if [[ "${{ matrix.arch }}" == "arm64" ]]; then
pnpm exec electron-builder build --mac --arm64 --publish never --config electron-builder.ts
else
pnpm exec electron-builder build --mac --x64 --publish never --config electron-builder.ts
fi
- name: Verify package
run: |
if [[ "${{ matrix.arch }}" == "arm64" ]]; then
APP_BUNDLE="apps/code/out/mac-arm64/PostHog Code.app"
else
APP_BUNDLE="apps/code/out/mac/PostHog Code.app"
fi
RESOURCES="$APP_BUNDLE/Contents/Resources"
UNPACKED="$RESOURCES/app.asar.unpacked/node_modules"
if [[ ! -f "$RESOURCES/app-update.yml" ]]; then
echo "FAIL: app-update.yml missing at $RESOURCES/app-update.yml"
exit 1
fi
echo "OK: app-update.yml"
for mod in node-pty better-sqlite3 "@parcel/watcher"; do
if [[ ! -d "$UNPACKED/$mod" ]]; then
echo "FAIL: $mod missing in app.asar.unpacked/node_modules"
exit 1
fi
echo "OK: $mod"
done
for bin in claude-cli codex-acp; do
if [[ ! -d "$RESOURCES/app.asar.unpacked/.vite/build/$bin" ]]; then
echo "FAIL: $bin missing in bundled binaries"
exit 1
fi
echo "OK: $bin"
done
- name: Install Playwright
run: pnpm --filter code exec playwright install
- name: Smoke test packaged app
env:
CI: true
E2E_APP_ARCH: ${{ matrix.arch }}
run: pnpm --filter code exec playwright test --config=tests/e2e/playwright.config.ts tests/e2e/tests/smoke.spec.ts
- name: Upload Playwright report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: failure()
with:
name: release-playwright-report-macos-${{ matrix.arch }}
path: apps/code/playwright-report/
retention-days: 7
- name: Upload release artifacts
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_VERSION: ${{ steps.version.outputs.version }}
run: |
gh release upload "v$APP_VERSION" --repo PostHog/code --clobber \
apps/code/out/*.dmg \
apps/code/out/*-mac.zip \
apps/code/out/*.blockmap
- name: Upload build artifacts (dry run)
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: code-macos-${{ matrix.arch }}
path: |
apps/code/out/*.dmg
apps/code/out/*-mac.zip
apps/code/out/*.blockmap
apps/code/out/latest-mac.yml
retention-days: 7
- name: Upload mac manifest artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mac-manifest-${{ matrix.arch }}
path: apps/code/out/latest-mac.yml
retention-days: 1
publish-windows:
needs: [prepare-release]
runs-on: windows-2022
permissions:
id-token: write
contents: write
env:
NODE_OPTIONS: "--max-old-space-size=8192"
NODE_ENV: production
VITE_POSTHOG_API_KEY: ${{ secrets.VITE_POSTHOG_API_KEY }}
VITE_POSTHOG_API_HOST: ${{ secrets.VITE_POSTHOG_API_HOST }}
steps:
- name: Get app token
id: app-token
uses: getsentry/action-github-app-token@d4b5da6c5e37703f8c3b3e43abb5705b46e159cc # v3.0.0
with:
app_id: ${{ secrets.GH_APP_ARRAY_RELEASER_APP_ID }}
private_key: ${{ secrets.GH_APP_ARRAY_RELEASER_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
persist-credentials: false
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: "pnpm"
- name: Resolve version
id: version
shell: pwsh
env:
GITHUB_REF: ${{ github.ref }}
run: |
if ("$env:GITHUB_REF" -like "refs/tags/v*") {
$version = "$env:GITHUB_REF" -replace "refs/tags/v", ""
} else {
$version = (Get-Content apps/code/package.json -Raw | ConvertFrom-Json).version
}
echo "Version: $version"
echo "version=$version" >> $env:GITHUB_OUTPUT
- name: Set version in package.json
shell: pwsh
env:
APP_VERSION: ${{ steps.version.outputs.version }}
run: |
$pkg = Get-Content apps/code/package.json -Raw | ConvertFrom-Json
$pkg.version = "$env:APP_VERSION"
$pkg | ConvertTo-Json -Depth 10 | Set-Content apps/code/package.json
echo "Set apps/code/package.json version to $env:APP_VERSION"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build electron-trpc package
run: pnpm --filter @posthog/electron-trpc run build
- name: Build platform package
run: pnpm --filter @posthog/platform run build
- name: Build shared package
run: pnpm --filter @posthog/shared run build
- name: Build git package
run: pnpm --filter @posthog/git run build
- name: Build enricher package
run: pnpm --filter @posthog/enricher run build
- name: Build agent package
run: pnpm --filter @posthog/agent run build
- name: Build release artifacts
env:
APP_VERSION: ${{ steps.version.outputs.version }}
working-directory: apps/code
run: |
pnpm exec electron-vite build
pnpm exec electron-builder build --win --x64 --publish never --config electron-builder.ts
- name: Upload release artifacts
if: startsWith(github.ref, 'refs/tags/v')
shell: pwsh
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_VERSION: ${{ steps.version.outputs.version }}
run: |
$out = "apps/code/out"
$sq = Join-Path $out "squirrel-windows"
$items = @()
# NSIS installer + electron-updater metadata live in the output root.
$items += Get-ChildItem $out -File -Filter "*.exe"
$items += Get-ChildItem $out -File -Filter "latest.yml"
$items += Get-ChildItem $out -File -Filter "*.blockmap"
# Squirrel.Windows artifacts (nupkg + RELEASES + Setup) land in out/squirrel-windows.
if (Test-Path $sq) {
$items += Get-ChildItem $sq -File -Filter "*.nupkg"
$items += Get-ChildItem $sq -File -Filter "RELEASES"
$items += Get-ChildItem $sq -File -Filter "*.exe"
}
$files = $items | Select-Object -ExpandProperty FullName
gh release upload "v$env:APP_VERSION" --repo PostHog/code --clobber @files
- name: Upload build artifacts (dry run)
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: code-windows
path: |
apps/code/out/*.exe
apps/code/out/latest.yml
apps/code/out/*.blockmap
apps/code/out/squirrel-windows/**
retention-days: 7
if-no-files-found: ignore
publish-linux:
needs: [prepare-release]
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
arch: x64
- runner: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.runner }}
permissions:
id-token: write
contents: write
env:
NODE_OPTIONS: "--max-old-space-size=8192"
NODE_ENV: production
VITE_POSTHOG_API_KEY: ${{ secrets.VITE_POSTHOG_API_KEY }}
VITE_POSTHOG_API_HOST: ${{ secrets.VITE_POSTHOG_API_HOST }}
steps:
- name: Get app token
id: app-token
uses: getsentry/action-github-app-token@d4b5da6c5e37703f8c3b3e43abb5705b46e159cc # v3
with:
app_id: ${{ secrets.GH_APP_ARRAY_RELEASER_APP_ID }}
private_key: ${{ secrets.GH_APP_ARRAY_RELEASER_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
persist-credentials: false
- name: Install Linux packaging tooling
# squashfs-tools/zsync/libfuse2t64: AppImage. fakeroot: maker-deb.
# rpm: maker-rpm (provides rpmbuild).
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
squashfs-tools zsync libfuse2t64 fakeroot rpm
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
cache: "pnpm"
- name: Resolve version
id: version
run: |
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
VERSION="${GITHUB_REF#refs/tags/v}"
else
VERSION="$(jq -r .version apps/code/package.json)"
fi
echo "Version: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Set version in package.json
env:
APP_VERSION: ${{ steps.version.outputs.version }}
run: |
jq --arg v "$APP_VERSION" '.version = $v' apps/code/package.json > tmp.json && mv tmp.json apps/code/package.json
echo "Set apps/code/package.json version to $APP_VERSION"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build electron-trpc package
run: pnpm --filter @posthog/electron-trpc run build
- name: Build platform package
run: pnpm --filter @posthog/platform run build
- name: Build shared package
run: pnpm --filter @posthog/shared run build
- name: Build git package
run: pnpm --filter @posthog/git run build
- name: Build enricher package
run: pnpm --filter @posthog/enricher run build
- name: Build agent package
run: pnpm --filter @posthog/agent run build
- name: Build release artifacts
env:
APP_VERSION: ${{ steps.version.outputs.version }}
working-directory: apps/code
run: |
pnpm exec electron-vite build
if [[ "${{ matrix.arch }}" == "arm64" ]]; then
pnpm exec electron-builder build --linux --arm64 --publish never --config electron-builder.ts
else
pnpm exec electron-builder build --linux --x64 --publish never --config electron-builder.ts
fi
- name: Upload release artifacts
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_VERSION: ${{ steps.version.outputs.version }}
run: |
gh release upload "v$APP_VERSION" --repo PostHog/code --clobber \
apps/code/out/*.AppImage \
apps/code/out/*.deb \
apps/code/out/*.rpm
- name: Upload build artifacts (dry run)
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: code-linux-${{ matrix.arch }}
path: |
apps/code/out/*.AppImage
apps/code/out/*.deb
apps/code/out/*.rpm
retention-days: 7
finalize-release:
needs: [publish-macos, publish-windows, publish-linux]
# macOS is the primary platform: publish the release as long as macOS built
# successfully, even if Windows or Linux failed. always() is required so this
# job isn't skipped when a non-macOS publish job fails.
if: always() && needs.publish-macos.result == 'success' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Get app token
id: app-token
uses: getsentry/action-github-app-token@d4b5da6c5e37703f8c3b3e43abb5705b46e159cc # v3.0.0
with:
app_id: ${{ secrets.GH_APP_ARRAY_RELEASER_APP_ID }}
private_key: ${{ secrets.GH_APP_ARRAY_RELEASER_PRIVATE_KEY }}
- name: Extract version from tag
id: version
run: |
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT"
- name: Checkout merge script
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
sparse-checkout: |
apps/code/scripts/merge-mac-manifests.mjs
sparse-checkout-cone-mode: false
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
- name: Install yaml dependency
run: npm install --prefix apps/code yaml
- name: Download arm64 mac manifest
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: mac-manifest-arm64
path: /tmp/mac-manifests/arm64
- name: Download x64 mac manifest
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: mac-manifest-x64
path: /tmp/mac-manifests/x64
- name: Merge mac manifests
run: |
node apps/code/scripts/merge-mac-manifests.mjs \
/tmp/mac-manifests/arm64/latest-mac.yml \
/tmp/mac-manifests/x64/latest-mac.yml \
/tmp/latest-mac.yml
- name: Upload merged mac manifest
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_VERSION: ${{ steps.version.outputs.version }}
run: |
gh release upload "v$APP_VERSION" --repo PostHog/code --clobber /tmp/latest-mac.yml
- name: Publish GitHub release
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_VERSION: ${{ steps.version.outputs.version }}
run: |
gh api repos/PostHog/code/releases/generate-notes -f tag_name="v$APP_VERSION" --jq '.body' > /tmp/release-notes.md
gh release edit "v$APP_VERSION" --repo PostHog/code --draft=false --notes-file /tmp/release-notes.md