feat(release): unify product versioning #9
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| pull_request: | |
| paths: | |
| - ".github/workflows/release.yml" | |
| - ".github/scripts/**" | |
| - "apps/headless/Dockerfile.linux" | |
| - "apps/headless/build.sh" | |
| - "apps/headless/build-linux.sh" | |
| - "apps/headless/install-linux.sh" | |
| - "apps/headless/Package.swift" | |
| - "apps/headless/Sources/HeadlessProtocol/ProductVersion.swift" | |
| - "apps/headless/VERSION" | |
| - "apps/headless/VersionSupport/**" | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: Build and verify release packages without publishing | |
| required: true | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: read | |
| jobs: | |
| version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| publish: ${{ steps.version.outputs.publish }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - id: version | |
| shell: bash | |
| env: | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then | |
| version="${GITHUB_REF_NAME#v}" | |
| if ! printf '%s\n' "$version" | grep -Eq -f apps/headless/VersionSupport/semver-pattern.txt; then | |
| echo "Release tag must be a semantic version prefixed with v: $GITHUB_REF_NAME" >&2 | |
| exit 64 | |
| fi | |
| expected="$(tr -d '[:space:]' < apps/headless/VERSION)" | |
| if [[ "$version" != "$expected" ]]; then | |
| echo "Release tag $version does not match apps/headless/VERSION $expected" >&2 | |
| exit 64 | |
| fi | |
| echo "publish=true" >> "$GITHUB_OUTPUT" | |
| else | |
| if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" && "$DRY_RUN" != "true" ]]; then | |
| echo "manual release runs must use dry_run=true; publish by pushing a v* tag" >&2 | |
| exit 64 | |
| fi | |
| version="0.0.${GITHUB_RUN_NUMBER}" | |
| echo "publish=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| macos: | |
| needs: version | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install FFmpeg | |
| run: brew install ffmpeg | |
| - name: Build | |
| env: | |
| HEADLESS_VERSION: ${{ needs.version.outputs.version }} | |
| run: ./apps/headless/build.sh | |
| - name: Unit tests | |
| env: | |
| HEADLESS_VERSION: ${{ needs.version.outputs.version }} | |
| run: ./apps/headless/test.sh | |
| - name: E2E | |
| run: zsh ./apps/headless/Tests/macos-e2e.sh | |
| - name: Package | |
| env: | |
| VERSION: ${{ needs.version.outputs.version }} | |
| run: | | |
| set -eu | |
| cd apps/headless | |
| ditto -c -k --keepParent Headless.app "Headless-${VERSION}-macos.zip" | |
| test -s "Headless-${VERSION}-macos.zip" | |
| unzip -t "Headless-${VERSION}-macos.zip" | |
| unzip -Z1 "Headless-${VERSION}-macos.zip" | grep -qx 'Headless.app/Contents/MacOS/Headless' | |
| unzip -Z1 "Headless-${VERSION}-macos.zip" | grep -qx 'Headless.app/Contents/Resources/bin/headless' | |
| unzip -Z1 "Headless-${VERSION}-macos.zip" | grep -qx 'Headless.app/Contents/Resources/bin/headless-mcp' | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: macos | |
| path: apps/headless/Headless-${{ needs.version.outputs.version }}-macos.zip | |
| linux-amd64: | |
| needs: version | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Build | |
| env: | |
| HEADLESS_LINUX_PLATFORM: linux/amd64 | |
| HEADLESS_VERSION: ${{ needs.version.outputs.version }} | |
| run: ./apps/headless/build-linux.sh | |
| - name: E2E | |
| run: ./apps/headless/Tests/linux-docker.sh | |
| - name: Package | |
| env: | |
| VERSION: ${{ needs.version.outputs.version }} | |
| run: | | |
| set -eu | |
| cp apps/headless/build/headless-linux-amd64.tar.gz \ | |
| "apps/headless/build/headless-${VERSION}-linux-amd64.tar.gz" | |
| archive="apps/headless/build/headless-${VERSION}-linux-amd64.tar.gz" | |
| test -s "$archive" | |
| tar -tzf "$archive" > archive-contents.txt | |
| for expected in headless headless-host headless-mcp install-linux.sh Headless_HeadlessProtocol.resources/AgentRuntime.js; do | |
| grep -qx "$expected" archive-contents.txt | |
| done | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: linux-amd64 | |
| path: apps/headless/build/headless-${{ needs.version.outputs.version }}-linux-amd64.tar.gz | |
| linux-arm64: | |
| needs: version | |
| runs-on: ubuntu-24.04-arm | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Build | |
| env: | |
| HEADLESS_LINUX_PLATFORM: linux/arm64 | |
| HEADLESS_VERSION: ${{ needs.version.outputs.version }} | |
| run: ./apps/headless/build-linux.sh | |
| - name: E2E | |
| run: ./apps/headless/Tests/linux-docker.sh | |
| - name: Package | |
| env: | |
| VERSION: ${{ needs.version.outputs.version }} | |
| run: | | |
| set -eu | |
| cp apps/headless/build/headless-linux-arm64.tar.gz \ | |
| "apps/headless/build/headless-${VERSION}-linux-arm64.tar.gz" | |
| archive="apps/headless/build/headless-${VERSION}-linux-arm64.tar.gz" | |
| test -s "$archive" | |
| tar -tzf "$archive" > archive-contents.txt | |
| for expected in headless headless-host headless-mcp install-linux.sh Headless_HeadlessProtocol.resources/AgentRuntime.js; do | |
| grep -qx "$expected" archive-contents.txt | |
| done | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: linux-arm64 | |
| path: apps/headless/build/headless-${{ needs.version.outputs.version }}-linux-arm64.tar.gz | |
| publish: | |
| needs: [version, macos, linux-amd64, linux-arm64] | |
| if: >- | |
| github.event_name == 'push' && | |
| startsWith(github.ref, 'refs/tags/v') && | |
| needs.version.outputs.publish == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| path: dist | |
| merge-multiple: true | |
| - name: Generate and verify checksums | |
| run: ./.github/scripts/release-checksums.sh "${{ needs.version.outputs.version }}" dist | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: Headless ${{ needs.version.outputs.version }} | |
| generate_release_notes: true | |
| body: | | |
| ## Downloads | |
| | File | Platform | | |
| | --- | --- | | |
| | `Headless-${{ needs.version.outputs.version }}-macos.zip` | macOS (ad-hoc signed `.app`) | | |
| | `headless-${{ needs.version.outputs.version }}-linux-amd64.tar.gz` | Linux x86_64 | | |
| | `headless-${{ needs.version.outputs.version }}-linux-arm64.tar.gz` | Linux arm64 | | |
| ### Notes | |
| - **macOS:** Unzip and run `Headless.app`. Gatekeeper may warn (ad-hoc signature; notarization not included yet). CLI: `Headless.app/Contents/Resources/bin/headless`. | |
| - **Linux tarball:** Does not bundle Chromium. Install with `./install-linux.sh` after ensuring non-Snap Chromium and FFmpeg are available. The Docker production image remains the self-contained runtime (not published by this release). | |
| files: | | |
| dist/Headless-${{ needs.version.outputs.version }}-macos.zip | |
| dist/headless-${{ needs.version.outputs.version }}-linux-amd64.tar.gz | |
| dist/headless-${{ needs.version.outputs.version }}-linux-arm64.tar.gz | |
| dist/SHA256SUMS | |
| fail_on_unmatched_files: true |