Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions .github/workflows/prerelease.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: V5 prerelease candidate

# Called through the existing Release entry dispatched on next. Preparing never publishes.
on:
workflow_call:
inputs:
version:
type: string
required: true
source_revision:
type: string
required: true
publish:
type: boolean
default: false

permissions:
contents: read

concurrency:
group: v5-prerelease-${{ inputs.version }}
cancel-in-progress: false

env:
RELEASE_CHANNEL: prerelease
HACK_PRERELEASE_VERSION: ${{ inputs.version }}
HACK_PRERELEASE_SOURCE_REVISION: ${{ inputs.source_revision }}
HACK_PRERELEASE_PUBLISH: ${{ inputs.publish }}

jobs:
plan:
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
checks: read
statuses: read
deployments: read
outputs:
version: ${{ steps.plan.outputs.version }}
source_revision: ${{ steps.plan.outputs.source_revision }}
archive: ${{ steps.plan.outputs.archive }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
persist-credentials: false
- uses: oven-sh/setup-bun@v1
with:
bun-version: "1.4.2"
- name: Validate immutable plan
id: plan
run: bun scripts/prerelease-plan.ts plan
- name: Verify publication prerequisites before scheduling approval
if: ${{ inputs.publish }}
env:
GH_TOKEN: ${{ github.token }}
run: bun scripts/prerelease-plan.ts verify

build:
needs: plan
runs-on: macos-15
timeout-minutes: 60
permissions:
contents: read
env:
HACK_PRERELEASE_BUNDLE: ${{ runner.temp }}/native-candidate
HACK_PRERELEASE_OUTPUT: ${{ runner.temp }}/prerelease-assets
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.plan.outputs.source_revision }}
persist-credentials: false
- uses: oven-sh/setup-bun@v1
with:
bun-version: "1.4.2"
- uses: dtolnay/rust-toolchain@1.97.1
with:
targets: aarch64-unknown-linux-musl
- uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4
with:
version: "2026.9.12"
install_args: zig
add_shims_to_path: false
cache: false
env: false
- run: bun install --frozen-lockfile
- name: Build and verify complete native bundle
run: mise exec zig -- scripts/build-native-candidate.sh "$HACK_PRERELEASE_BUNDLE" "--version=$HACK_PRERELEASE_VERSION"
- name: Package verified payload and outer checksum
run: bun scripts/prerelease-plan.ts package
- name: Retain reviewable artifacts
uses: actions/upload-artifact@v4
with:
name: v5-prerelease-${{ needs.plan.outputs.version }}
path: ${{ runner.temp }}/prerelease-assets/*
if-no-files-found: error
retention-days: 14

publish:
if: ${{ inputs.publish }}
needs: [plan, build]
runs-on: ubuntu-latest
timeout-minutes: 10
# The plan checks that this already exists with real required-reviewer protection.
environment: v5-prerelease
permissions:
contents: write
actions: read
checks: read
statuses: read
deployments: read
env:
HACK_PRERELEASE_OUTPUT: ${{ runner.temp }}/prerelease-assets
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.plan.outputs.source_revision }}
persist-credentials: false
- uses: oven-sh/setup-bun@v1
with:
bun-version: "1.4.2"
- uses: actions/download-artifact@v4
with:
name: v5-prerelease-${{ needs.plan.outputs.version }}
path: ${{ runner.temp }}/prerelease-assets
- name: Recheck exact head, CI, human approval and tag absence; publish once
run: bun scripts/prerelease-plan.ts publish
78 changes: 57 additions & 21 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,49 @@ on:
tag:
description: 'Release tag (e.g., v0.1.0)'
required: false
channel:
description: Release channel
type: choice
options: [stable, prerelease]
default: stable
prerelease_version:
description: 'Explicit v5 candidate version (5.0.0-next.N)'
type: string
required: false
source_revision:
description: 'Full approved next commit SHA for the candidate'
type: string
required: false
publish_prerelease:
description: 'Request publication after exact-head CI and environment review (default prepares artifacts only)'
type: boolean
default: false
push:
tags:
- "v*"
- "!v*-*"
- "!v*+*"

permissions:
contents: read

jobs:
prerelease:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.channel == 'prerelease' }}
uses: ./.github/workflows/prerelease.yml
permissions:
contents: write
actions: read
checks: read
statuses: read
deployments: read
with:
version: ${{ inputs.prerelease_version }}
source_revision: ${{ inputs.source_revision }}
publish: ${{ inputs.publish_prerelease }}

create-release:
if: ${{ github.event_name == 'push' || inputs.channel == 'stable' }}
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
Expand All @@ -29,22 +66,10 @@ jobs:
bun-version: "1.4.2"
- name: Resolve version
id: version
run: |
TARGET_TAG="${{ github.event.inputs.tag }}"
if [ -z "$TARGET_TAG" ]; then
TARGET_TAG="$GITHUB_REF_NAME"
fi
if [ -z "$TARGET_TAG" ]; then
echo "Missing tag. Provide workflow input 'tag' or run on a tag ref."
exit 1
fi
VERSION="$(bun -e "const pkg = await Bun.file('package.json').json(); console.log(pkg.version)")"
if [ "v$VERSION" != "$TARGET_TAG" ]; then
echo "Tag $TARGET_TAG does not match package.json version $VERSION."
exit 1
fi
echo "tag=$TARGET_TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
env:
RELEASE_CHANNEL: ${{ inputs.channel || 'stable' }}
RELEASE_TAG_INPUT: ${{ inputs.tag }}
run: bun scripts/prerelease-plan.ts stable
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
Expand Down Expand Up @@ -75,6 +100,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.create-release.outputs.tag }}
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
Expand All @@ -83,10 +110,14 @@ jobs:
run: bun install
- name: Build release artifacts
if: ${{ !matrix.skip_tests }}
run: bun run build:release --version="${{ needs.create-release.outputs.version }}"
env:
RELEASE_VERSION: ${{ needs.create-release.outputs.version }}
run: bun run build:release "--version=$RELEASE_VERSION"
- name: Build release artifacts (skip tests)
if: ${{ matrix.skip_tests }}
run: bun run build:release --version="${{ needs.create-release.outputs.version }}" --skip-tests
env:
RELEASE_VERSION: ${{ needs.create-release.outputs.version }}
run: bun run build:release "--version=$RELEASE_VERSION" --skip-tests
- name: Upload tarball
env:
GH_TOKEN: ${{ github.token }}
Expand Down Expand Up @@ -146,6 +177,7 @@ jobs:
done

update-homebrew-tap:
if: ${{ github.event_name == 'push' || inputs.channel == 'stable' }}
needs: [create-release, build]
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
Expand Down Expand Up @@ -175,13 +207,17 @@ jobs:
- name: Render formula
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.create-release.outputs.tag }}
RELEASE_VERSION: ${{ needs.create-release.outputs.version }}
run: |
bun run scripts/update-homebrew-tap.ts \
--tag="${{ needs.create-release.outputs.tag }}" \
--version="${{ needs.create-release.outputs.version }}" \
--tag="$RELEASE_TAG" \
--version="$RELEASE_VERSION" \
--tap-dir=homebrew-tap
- name: Commit and push tap update
working-directory: homebrew-tap
env:
RELEASE_TAG: ${{ needs.create-release.outputs.tag }}
run: |
set -euo pipefail
git add Formula/hack.rb
Expand All @@ -191,5 +227,5 @@ jobs:
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "build(hack): update formula to ${{ needs.create-release.outputs.tag }}"
git commit -m "build(hack): update formula to $RELEASE_TAG"
git push origin HEAD:main
Loading
Loading