Skip to content

Release

Release #74

Workflow file for this run

name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
dry-run:
description: "Skip release creation and npm publish (build + verify only)"
type: boolean
default: true
permissions: {}
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
CARGO_TERM_COLOR: always
jobs:
build:
name: Build (${{ matrix.platform }})
strategy:
fail-fast: false
matrix:
include:
- platform: darwin-arm64
os: macos-15
binary: claude-view
artifact: claude-view-darwin-arm64.tar.gz
- platform: darwin-x64
os: macos-15-intel
binary: claude-view
artifact: claude-view-darwin-x64.tar.gz
- platform: linux-x64
os: ubuntu-24.04
binary: claude-view
artifact: claude-view-linux-x64.tar.gz
# Windows build deferred
# - platform: win32-x64
# os: windows-latest
# binary: claude-view.exe
# artifact: claude-view-win32-x64.zip
runs-on: ${{ matrix.os }}
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
- name: Install Linux build dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libssl-dev
- name: Rust cache
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24"
- name: Verify version matches tag
if: github.ref_type == 'tag'
shell: bash
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
PKG_VERSION=$(node -p "require('./npx-cli/package.json').version")
CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) != npx-cli/package.json version ($PKG_VERSION)"
exit 1
fi
if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)"
exit 1
fi
PLUGIN_VERSION=$(node -p "require('./packages/plugin/.claude-plugin/plugin.json').version")
if [ "$TAG_VERSION" != "$PLUGIN_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) != plugin.json version ($PLUGIN_VERSION)"
exit 1
fi
echo "Version verified: $TAG_VERSION (npm + cargo + plugin)"
- name: Setup Bun
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
- name: Install frontend dependencies
run: bun install --frozen-lockfile
- name: Build frontend
run: cd apps/web && bun run build
env:
NODE_OPTIONS: "--max-old-space-size=4096"
VITE_SUPABASE_URL: ${{ vars.VITE_SUPABASE_URL }}
VITE_SUPABASE_PUBLISHABLE_KEY: ${{ vars.VITE_SUPABASE_PUBLISHABLE_KEY }}
- name: Build sidecar
run: |
cd sidecar
npm install
npm run build
- name: Build Rust binary (dist profile)
run: cargo build --profile dist -p claude-view-server --no-default-features --features dist
env:
SUPABASE_URL: ${{ vars.SUPABASE_URL }}
RELAY_URL: wss://claude-view-relay.fly.dev/ws
SHARE_WORKER_URL: https://api-share.claudeview.ai
SHARE_VIEWER_URL: https://share.claudeview.ai
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
- name: Stage sidecar
run: |
mkdir -p staging/sidecar
cp -r sidecar/dist staging/sidecar/
- name: Package (unix)
if: runner.os != 'Windows'
run: |
mkdir -p staging
cp target/dist/${{ matrix.binary }} staging/
cp -r apps/web/dist staging/
tar -czf ${{ matrix.artifact }} -C staging .
- name: Package (windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path staging
Copy-Item "target\dist\${{ matrix.binary }}" -Destination staging\
Copy-Item -Recurse apps\web\dist staging\dist
Compress-Archive -Path staging\* -DestinationPath ${{ matrix.artifact }}
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ matrix.artifact }}
path: ${{ matrix.artifact }}
retention-days: 1
verify:
name: Verify artifacts
needs: build
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Download all artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: artifacts
merge-multiple: true
- name: Contract — all expected tarballs exist and are reasonable size
shell: bash
run: |
set -euo pipefail
expected=(claude-view-darwin-arm64 claude-view-darwin-x64 claude-view-linux-x64)
fail=0
for p in "${expected[@]}"; do
f="artifacts/${p}.tar.gz"
if [[ ! -f "$f" ]]; then
echo "::error file=${f}::Missing artifact"
fail=1
continue
fi
size=$(stat -c%s "$f")
if [[ $size -lt 1000000 ]]; then
echo "::error file=${f}::Artifact only $size bytes — build silently failed"
fail=1
else
echo "OK: ${f} ($size bytes)"
fi
done
[[ $fail -eq 0 ]] || exit 1
- name: Contract — tarball internal structure matches npx-cli expectations
shell: bash
run: |
set -euo pipefail
fail=0
for tarball in artifacts/claude-view-*.tar.gz; do
tmp=$(mktemp -d)
tar -xzf "$tarball" -C "$tmp"
name=$(basename "$tarball")
if ! test -f "$tmp/claude-view"; then
echo "::error::${name}: missing binary 'claude-view'"
fail=1
elif ! test -x "$tmp/claude-view"; then
echo "::error::${name}: binary 'claude-view' not executable"
fail=1
fi
if ! test -d "$tmp/dist"; then
echo "::error::${name}: missing frontend 'dist/' directory"
fail=1
fi
if ! test -d "$tmp/sidecar/dist"; then
echo "::error::${name}: missing 'sidecar/dist/' directory"
fail=1
fi
rm -rf "$tmp"
done
[[ $fail -eq 0 ]] || exit 1
- name: Contract — linux-x64 binary runs and reports correct version
shell: bash
env:
REF_TYPE: ${{ github.ref_type }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
tmp=$(mktemp -d)
tar -xzf artifacts/claude-view-linux-x64.tar.gz -C "$tmp"
actual=$("$tmp/claude-view" --version 2>&1 | head -1 | awk '{print $NF}')
echo "Binary reports version: $actual"
if [[ "$REF_TYPE" == "tag" ]]; then
expected="${REF_NAME#v}"
if [[ "$actual" != "$expected" ]]; then
echo "::error::Binary version '$actual' != tag '$expected'"
exit 1
fi
echo "OK: binary version matches tag ($expected)"
else
echo "Not a tag push — skipping tag-vs-binary comparison"
fi
release:
name: Create Release
needs: verify
if: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.dry-run == false) }}
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: artifacts
merge-multiple: true
- name: Generate checksums
run: |
cd artifacts
sha256sum claude-view-*.tar.gz > checksums.txt
cat checksums.txt
- name: Create GitHub Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
generate_release_notes: true
files: artifacts/*
publish-npm:
name: Publish to npm
needs: release
if: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.dry-run == false) }}
runs-on: ubuntu-24.04
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Setup Bun
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
- name: Publish claude-view
run: npm publish --access public || echo "Already published, skipping"
working-directory: npx-cli
- name: Build plugin
run: cd packages/plugin && bun install && bun run build
- name: Publish @claude-view/plugin
run: npm publish --access public || echo "Already published, skipping"
working-directory: packages/plugin