name: Publish
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Git tag to publish (e.g. v0.4.6)"
required: true
type: string
dry_run:
description: "Dry run — build everything but skip registry publishes"
required: false
default: false
type: boolean
force_republish:
description: "Force publish even if version already exists"
required: false
default: false
type: boolean
targets:
description: "Comma-separated targets (empty = all). Values: crates,cli,homebrew"
required: false
default: ""
type: string
repository_dispatch:
types: [publish-release]
concurrency:
group: publish-${{ github.event.inputs.tag || github.ref_name || github.run_id }}
cancel-in-progress: false
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
prepare:
name: Prepare release
runs-on: runner-medium
outputs:
tag: ${{ steps.meta.outputs.tag }}
version: ${{ steps.meta.outputs.version }}
checkout_ref: ${{ steps.meta.outputs.checkout_ref }}
dry_run: ${{ steps.meta.outputs.dry_run }}
force_republish: ${{ steps.meta.outputs.force_republish }}
release_crates: ${{ steps.meta.outputs.release_crates }}
release_cli: ${{ steps.meta.outputs.release_cli }}
release_homebrew: ${{ steps.meta.outputs.release_homebrew }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Prepare release metadata
id: meta
uses: kreuzberg-dev/actions/prepare-release-metadata@v1
with:
available-targets: crates,cli,homebrew
alef-version: main
validate-versions:
name: Validate versions
needs: prepare
runs-on: runner-medium
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- name: Validate manifest versions
uses: kreuzberg-dev/actions/validate-versions@v1
with:
version: ${{ needs.prepare.outputs.version }}
alef-version: main
check-cratesio:
name: Check crates.io
needs: prepare
if: needs.prepare.outputs.release_crates == 'true'
runs-on: runner-medium
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- name: Check crates.io for alef
id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: cratesio
package: alef
version: ${{ needs.prepare.outputs.version }}
alef-version: main
check-github-release:
name: Check GitHub release
needs: prepare
if: needs.prepare.outputs.release_cli == 'true'
runs-on: runner-medium
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- name: Check GitHub release assets
id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: github-release
package: alef
version: ${{ needs.prepare.outputs.version }}
tag: ${{ needs.prepare.outputs.tag }}
asset-prefix: alef-
repo: kreuzberg-dev/alef
alef-version: main
check-homebrew:
name: Check Homebrew tap
needs: prepare
if: needs.prepare.outputs.release_homebrew == 'true'
runs-on: runner-medium
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- name: Check Homebrew formula
id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: homebrew
package: alef
version: ${{ needs.prepare.outputs.version }}
tap-repo: kreuzberg-dev/homebrew-tap
alef-version: main
build-cli:
name: Build CLI (${{ matrix.label }})
needs: [prepare, validate-versions, check-github-release]
if: |
always() &&
needs.prepare.outputs.release_cli == 'true' &&
needs.validate-versions.result == 'success' &&
(needs.check-github-release.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true')
strategy:
fail-fast: false
matrix:
include:
- label: linux-x86_64
runner: runner-medium
target: x86_64-unknown-linux-gnu
- label: linux-aarch64
runner: runner-medium-arm64
target: aarch64-unknown-linux-gnu
- label: macos-arm64
runner: macos-latest
target: aarch64-apple-darwin
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- name: Setup Rust
uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: ${{ matrix.target }}
cache-key-prefix: publish-cli-${{ matrix.label }}
install-llvm-cov: "false"
macos-dynamic-lookup: "false"
disable-cache: ${{ runner.os == 'Windows' && 'true' || 'false' }}
- name: Build CLI
id: build
uses: kreuzberg-dev/actions/build-rust-cli@v1
with:
package-name: alef
binary-name: alef
target: ${{ matrix.target }}
- name: Create release archive
id: archive
shell: bash
run: |
set -euo pipefail
ARCHIVE_DIR="alef-${{ matrix.target }}"
mkdir -p "${ARCHIVE_DIR}"
cp "${{ steps.build.outputs.binary-path }}" "${ARCHIVE_DIR}/"
if [[ "${{ runner.os }}" == "Windows" ]]; then
ARCHIVE_NAME="alef-${{ matrix.target }}.zip"
7z a "${ARCHIVE_NAME}" "${ARCHIVE_DIR}"
else
ARCHIVE_NAME="alef-${{ matrix.target }}.tar.gz"
tar -czf "${ARCHIVE_NAME}" "${ARCHIVE_DIR}"
fi
echo "path=${ARCHIVE_NAME}" >> "$GITHUB_OUTPUT"
echo "name=${ARCHIVE_NAME}" >> "$GITHUB_OUTPUT"
- name: Upload CLI artifact
uses: actions/upload-artifact@v7
with:
name: cli-${{ matrix.label }}
path: ${{ steps.archive.outputs.path }}
retention-days: 14
publish-homebrew-formula:
name: Update Homebrew formula
needs: [prepare, validate-versions, check-homebrew, publish-crates]
if: |
always() &&
needs.prepare.outputs.release_homebrew == 'true' &&
needs.prepare.outputs.dry_run != 'true' &&
needs.validate-versions.result == 'success' &&
(needs.check-homebrew.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') &&
needs.publish-crates.result != 'failure'
runs-on: runner-medium
steps:
- uses: actions/create-github-app-token@v3
id: tap-token
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
owner: kreuzberg-dev
repositories: homebrew-tap
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- uses: actions/checkout@v7
with:
repository: kreuzberg-dev/homebrew-tap
token: ${{ steps.tap-token.outputs.token }}
path: homebrew-tap
- name: Update formula
env:
TAG: ${{ needs.prepare.outputs.tag }}
VERSION: ${{ needs.prepare.outputs.version }}
TAP_DIR: ${{ github.workspace }}/homebrew-tap
run: scripts/publish/update-homebrew-formula.sh
- name: Commit and push formula
working-directory: homebrew-tap
env:
GH_TOKEN: ${{ steps.tap-token.outputs.token }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if git diff --quiet Formula/alef.rb; then
echo "No formula changes; skipping commit."
exit 0
fi
git add Formula/alef.rb
git commit -m "alef ${{ needs.prepare.outputs.version }}"
git push origin HEAD
homebrew-bottles:
name: Build Homebrew bottle (${{ matrix.bottle_tag }})
needs: [prepare, publish-homebrew-formula]
if: |
always() &&
needs.prepare.outputs.release_homebrew == 'true' &&
needs.prepare.outputs.dry_run != 'true' &&
needs.publish-homebrew-formula.result == 'success'
runs-on: ${{ matrix.os }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
bottle_tag: arm64_sequoia
install_brew: false
- os: macos-15-intel
bottle_tag: sequoia
install_brew: false
- os: ubuntu-latest
bottle_tag: x86_64_linux
install_brew: true
- os: ubuntu-24.04-arm
bottle_tag: arm64_linux
install_brew: true
steps:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
owner: kreuzberg-dev
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- name: Install Homebrew (Linux)
if: ${{ matrix.install_brew }}
uses: kreuzberg-dev/actions/install-homebrew-linux@v1
- name: Build bottles
uses: kreuzberg-dev/actions/homebrew-build-bottles@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
version: ${{ needs.prepare.outputs.version }}
tap: kreuzberg-dev/homebrew-tap
formulas: alef
out-dir: ${{ github.workspace }}/bottle-json
github-repo: kreuzberg-dev/alef
token: ${{ steps.app-token.outputs.token }}
- name: Upload bottle JSON manifests
uses: actions/upload-artifact@v7
with:
name: homebrew-bottle-json-${{ matrix.bottle_tag }}
path: ${{ github.workspace }}/bottle-json/*.json
if-no-files-found: error
retention-days: 14
publish-homebrew-bottles:
name: Merge Homebrew bottle DSL
needs: [prepare, publish-homebrew-formula, homebrew-bottles]
if: |
always() &&
needs.prepare.outputs.release_homebrew == 'true' &&
needs.prepare.outputs.dry_run != 'true' &&
needs.publish-homebrew-formula.result == 'success' &&
needs.homebrew-bottles.result == 'success'
runs-on: runner-medium
steps:
- uses: actions/create-github-app-token@v3
id: tap-token
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
owner: kreuzberg-dev
repositories: homebrew-tap
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- uses: actions/checkout@v7
with:
repository: kreuzberg-dev/homebrew-tap
token: ${{ steps.tap-token.outputs.token }}
path: homebrew-tap
- uses: actions/download-artifact@v8
with:
pattern: homebrew-bottle-json-*
path: ${{ github.workspace }}/bottle-json
merge-multiple: true
- name: Merge bottle DSL into formula
uses: kreuzberg-dev/actions/homebrew-merge-bottles@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
version: ${{ needs.prepare.outputs.version }}
tap-dir: ${{ github.workspace }}/homebrew-tap
json-dir: ${{ github.workspace }}/bottle-json
formulas: alef
github-repo: kreuzberg-dev/alef
- name: Commit and push bottle DSL
working-directory: homebrew-tap
env:
GH_TOKEN: ${{ steps.tap-token.outputs.token }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if git diff --quiet Formula/alef.rb; then
echo "No bottle DSL changes; skipping commit."
exit 0
fi
git add Formula/alef.rb
git commit -m "alef ${{ needs.prepare.outputs.version }}: add bottle DSL"
git push origin HEAD
upload-release-assets:
name: Upload release assets
needs: [prepare, validate-versions, check-github-release, build-cli]
if: |
always() &&
needs.prepare.outputs.release_cli == 'true' &&
needs.validate-versions.result == 'success' &&
needs.build-cli.result == 'success' &&
(needs.check-github-release.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true')
runs-on: runner-medium
steps:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
owner: kreuzberg-dev
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- name: Download all CLI artifacts
uses: actions/download-artifact@v8
with:
pattern: cli-*
path: dist/cli
merge-multiple: true
- name: Upload CLI artifacts to release
uses: kreuzberg-dev/actions/publish-github-release@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
title: ${{ needs.prepare.outputs.tag }}
artifacts: "dist/cli/*"
generate-notes: "false"
draft: "false"
dry-run: ${{ needs.prepare.outputs.dry_run }}
token: ${{ steps.app-token.outputs.token }}
publish-crates:
name: Publish crates.io
needs: [prepare, validate-versions, check-cratesio]
if: |
always() &&
needs.prepare.outputs.release_crates == 'true' &&
needs.validate-versions.result == 'success' &&
(needs.check-cratesio.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true')
runs-on: runner-medium
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- name: Setup Rust
uses: kreuzberg-dev/actions/setup-rust@v1
with:
install-llvm-cov: "false"
- name: Publish to crates.io
uses: kreuzberg-dev/actions/publish-crates@v1
with:
crates: alef
version: ${{ needs.prepare.outputs.version }}
dry-run: ${{ needs.prepare.outputs.dry_run }}
finalize:
name: Finalize release
needs: [prepare, upload-release-assets, publish-crates, publish-homebrew-bottles]
if: always()
runs-on: runner-medium
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- name: Publish summary
if: always()
run: |
{
echo "## Release ${{ needs.prepare.outputs.tag }}"
echo ""
echo "| Registry | Status |"
echo "|----------|--------|"
} >> "$GITHUB_STEP_SUMMARY"
report() {
local name="$1" result="$2"
case "$result" in
success) echo "| $name | :white_check_mark: Published |" ;;
skipped) echo "| $name | :fast_forward: Skipped |" ;;
*) echo "| $name | :x: $result |" ;;
esac
}
{
report "crates.io" "${{ needs.publish-crates.result }}"
report "GitHub Release" "${{ needs.upload-release-assets.result }}"
report "Homebrew" "${{ needs.publish-homebrew-bottles.result }}"
} >> "$GITHUB_STEP_SUMMARY"