Files
fil/.github/workflows/publish.yaml
Henrik Jess Nielsen b4c07d3693
All checks were successful
Deploy fil (kreuzberg) / deploy (push) Successful in 49s
Nomad changes
2026-06-01 23:40:55 +02:00

2346 lines
87 KiB
YAML

name: Publish Release
# Action-first kreuzberg release pipeline.
#
# Orchestrates 16 alef language targets:
# crates · python · node · wasm · ruby · php · cli · homebrew · go · c-ffi
# java · csharp · elixir · dart · swift · zig · kotlin
# (R is excluded — distributed automatically by R-Universe from this repo's HEAD.)
#
# Mirrors html-to-markdown's publish.yaml composition: every registry
# interaction, every multi-platform build, and every release-asset upload
# routes through a shared kreuzberg-dev/actions composite. Repo-quirky inline
# bash is reserved for matrix expansion / artifact path stitching only.
on:
workflow_dispatch:
inputs:
tag:
description: "Release tag to build (e.g., v5.0.0-rc.1)"
required: false
type: string
ref:
description: "Git ref (branch, tag, or commit) to build; defaults to the tag"
required: false
type: string
targets:
description: "Comma-separated list of release targets, or 'all'"
required: false
type: string
default: "all"
dry_run:
description: "Prepare artifacts without publishing"
required: false
type: boolean
default: false
republish:
description: "Delete and re-create the tag on current HEAD before publishing"
required: false
type: boolean
default: false
force_republish:
description: "Force republish targets whose registry version already exists"
required: false
type: boolean
default: false
release:
types: [published]
repository_dispatch:
types: [publish-release]
permissions:
contents: write
id-token: write
concurrency:
group: ${{ github.workflow }}-${{ (github.event_name == 'workflow_dispatch' && (github.event.inputs.ref || github.event.inputs.tag)) || github.ref || github.run_id }}
cancel-in-progress: false
jobs:
prepare:
name: Prepare metadata
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.meta.outputs.tag }}
version: ${{ steps.meta.outputs.version }}
ref: ${{ steps.meta.outputs.ref }}
checkout_ref: ${{ steps.meta.outputs.checkout_ref }}
target_sha: ${{ steps.meta.outputs.target_sha }}
matrix_ref: ${{ steps.meta.outputs.matrix_ref }}
is_tag: ${{ steps.meta.outputs.is_tag }}
is_prerelease: ${{ steps.meta.outputs.is_prerelease }}
npm_tag: ${{ steps.meta.outputs.npm_tag }}
dry_run: ${{ steps.meta.outputs.dry_run }}
force_republish: ${{ steps.meta.outputs.force_republish }}
release_targets: ${{ steps.meta.outputs.release_targets }}
release_any: ${{ steps.meta.outputs.release_any }}
release_python: ${{ steps.meta.outputs.release_python }}
release_node: ${{ steps.meta.outputs.release_node }}
release_ruby: ${{ steps.meta.outputs.release_ruby }}
release_cli: ${{ steps.meta.outputs.release_cli }}
release_crates: ${{ steps.meta.outputs.release_crates }}
release_homebrew: ${{ steps.meta.outputs.release_homebrew }}
release_java: ${{ steps.meta.outputs.release_java }}
release_csharp: ${{ steps.meta.outputs.release_csharp }}
release_go: ${{ steps.meta.outputs.release_go }}
release_wasm: ${{ steps.meta.outputs.release_wasm }}
release_php: ${{ steps.meta.outputs.release_php }}
release_elixir: ${{ steps.meta.outputs.release_elixir }}
release_c_ffi: ${{ steps.meta.outputs.release_c_ffi }}
release_dart: ${{ steps.meta.outputs.release_dart }}
release_swift: ${{ steps.meta.outputs.release_swift }}
release_zig: ${{ steps.meta.outputs.release_zig }}
release_kotlin: ${{ steps.meta.outputs.release_kotlin }}
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
with:
ref: ${{ (inputs.republish == true && (inputs.ref || github.event.repository.default_branch)) || inputs.ref || inputs.tag || github.ref }}
fetch-depth: 0
- name: Retag for republish
if: ${{ inputs.republish == true || github.event.client_payload.republish == true }}
uses: kreuzberg-dev/actions/retag-for-republish@v1
with:
tag: ${{ inputs.tag || github.event.client_payload.tag }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve release metadata
id: meta
uses: kreuzberg-dev/actions/prepare-release-metadata@v1
- name: Ensure GitHub Release exists for tag
if: ${{ steps.meta.outputs.is_tag == 'true' && steps.meta.outputs.dry_run != 'true' }}
uses: kreuzberg-dev/actions/publish-github-release@v1
with:
tag: ${{ steps.meta.outputs.tag }}
target: ${{ steps.meta.outputs.target_sha }}
notes: "Release ${{ steps.meta.outputs.tag }}"
draft: "true"
prerelease: ${{ steps.meta.outputs.is_prerelease }}
validate-versions:
name: Validate language manifest versions
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
- name: Validate versions
uses: kreuzberg-dev/actions/validate-versions@v1
with:
version: ${{ needs.prepare.outputs.version }}
# ─── Registry existence checks ────────────────────────────────────────
check-pypi:
name: Check PyPI for existing version
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_python == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: pypi
package: kreuzberg
version: ${{ needs.prepare.outputs.version }}
check-npm:
name: Check npm for existing versions
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && (needs.prepare.outputs.release_node == 'true' || needs.prepare.outputs.release_wasm == 'true') }}
runs-on: ubuntu-latest
outputs:
node_exists: ${{ steps.check.outputs.exists }}
wasm_exists: ${{ steps.check.outputs.wasm_exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: npm
package: "@kreuzberg/node"
version: ${{ needs.prepare.outputs.version }}
extra-packages: |
wasm_exists=@kreuzberg/wasm
check-rubygems:
name: Check RubyGems for existing version
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_ruby == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: rubygems
package: kreuzberg
version: ${{ needs.prepare.outputs.version }}
check-maven:
name: Check Maven Central for existing version
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && (needs.prepare.outputs.release_java == 'true' || needs.prepare.outputs.release_kotlin == 'true') }}
runs-on: ubuntu-latest
outputs:
java_exists: ${{ steps.check.outputs.exists }}
kotlin_exists: ${{ steps.check.outputs.kotlin_exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: maven
package: "dev.kreuzberg:kreuzberg"
version: ${{ needs.prepare.outputs.version }}
extra-packages: |
kotlin_exists=dev.kreuzberg:kreuzberg-android
check-nuget:
name: Check NuGet for existing version
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_csharp == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: nuget
package: Kreuzberg
version: ${{ needs.prepare.outputs.version }}
check-packagist:
name: Check Packagist for existing version
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_php == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: packagist
package: kreuzberg/kreuzberg
version: ${{ needs.prepare.outputs.version }}
check-cratesio:
name: Check crates.io for existing versions
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_crates == 'true' }}
runs-on: ubuntu-latest
outputs:
kreuzberg_exists: ${{ steps.check.outputs.exists }}
tesseract_exists: ${{ steps.check.outputs.tesseract_exists }}
paddle_exists: ${{ steps.check.outputs.paddle_exists }}
cli_exists: ${{ steps.check.outputs.cli_exists }}
all_exist: ${{ steps.derive.outputs.all_exist }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: cratesio
package: kreuzberg
version: ${{ needs.prepare.outputs.version }}
extra-packages: |
tesseract_exists=kreuzberg-tesseract
paddle_exists=kreuzberg-paddle-ocr
cli_exists=kreuzberg-cli
- id: derive
# Inline: aggregate four check-registry outputs into a single boolean
# the publish-crates job can guard on. No shared action equivalent.
run: |
if [[ "${{ steps.check.outputs.exists }}" == "true" \
&& "${{ steps.check.outputs.tesseract_exists }}" == "true" \
&& "${{ steps.check.outputs.paddle_exists }}" == "true" \
&& "${{ steps.check.outputs.cli_exists }}" == "true" ]]; then
echo "all_exist=true" >> "$GITHUB_OUTPUT"
else
echo "all_exist=false" >> "$GITHUB_OUTPUT"
fi
check-hex:
name: Check Hex.pm for existing versions
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_elixir == 'true' }}
runs-on: ubuntu-latest
outputs:
elixir_exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: hex
package: kreuzberg
version: ${{ needs.prepare.outputs.version }}
check-pub:
name: Check pub.dev for existing version
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_dart == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: pub
package: kreuzberg
version: ${{ needs.prepare.outputs.version }}
check-zig:
name: Check Zig package presence
needs: prepare
if: ${{ (needs.prepare.outputs.is_tag == 'true' || needs.prepare.outputs.dry_run == 'true') && needs.prepare.outputs.release_zig == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
# `registry: github-release` (not `registry: zig`) — alef check-registry's
# zig path only verifies the release tag exists. On a real release that's
# always true, so it short-circuits `publish-zig` to skipped (regression:
# v4.x shipped without the zig tarball asset because of this). Verify
# the actual asset is present instead.
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: github-release
package: kreuzberg-zig-v${{ needs.prepare.outputs.version }}.tar.gz
version: ${{ needs.prepare.outputs.version }}
repo: ${{ github.repository }}
assets: kreuzberg-zig-v${{ needs.prepare.outputs.version }}.tar.gz
check-homebrew:
name: Check Homebrew tap for formula
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_homebrew == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: homebrew
package: kreuzberg
version: ${{ needs.prepare.outputs.version }}
tap-repo: kreuzberg-dev/homebrew-tap
check-cli:
name: Check GitHub Release for CLI assets
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_cli == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: github-release
package: kreuzberg
version: ${{ needs.prepare.outputs.version }}
repo: kreuzberg-dev/kreuzberg
tag: ${{ needs.prepare.outputs.tag }}
asset-prefix: kreuzberg-cli-
assets: |
kreuzberg-cli-aarch64-apple-darwin.tar.gz,kreuzberg-cli-aarch64-unknown-linux-gnu.tar.gz,kreuzberg-cli-aarch64-unknown-linux-musl.tar.gz,kreuzberg-cli-x86_64-unknown-linux-gnu.tar.gz,kreuzberg-cli-x86_64-unknown-linux-musl.tar.gz,kreuzberg-cli-x86_64-pc-windows-msvc.zip
check-go:
name: Check GitHub Release for Go FFI assets
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_go == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: github-release
package: kreuzberg
version: ${{ needs.prepare.outputs.version }}
repo: kreuzberg-dev/kreuzberg
tag: ${{ needs.prepare.outputs.tag }}
asset-prefix: kreuzberg-go-
check-c-ffi:
name: Check GitHub Release for C FFI assets
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_c_ffi == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: github-release
package: kreuzberg
version: ${{ needs.prepare.outputs.version }}
repo: kreuzberg-dev/kreuzberg
tag: ${{ needs.prepare.outputs.tag }}
asset-prefix: kreuzberg-ffi-
check-elixir-release:
name: Check GitHub Release for Elixir NIF assets
needs: prepare
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.release_elixir == 'true' }}
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- id: check
uses: kreuzberg-dev/actions/check-registry@v1
with:
registry: github-release
package: kreuzberg
version: ${{ needs.prepare.outputs.version }}
repo: kreuzberg-dev/kreuzberg
tag: ${{ needs.prepare.outputs.tag }}
asset-prefix: libkreuzberg_nif-
# ─── Build matrix jobs ────────────────────────────────────────────────
python-wheels:
name: Build Python wheels (${{ matrix.os }})
needs: [prepare, check-pypi]
if: ${{ needs.prepare.outputs.release_python == 'true' && (needs.check-pypi.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
env:
MACOSX_DEPLOYMENT_TARGET: "10.12"
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- name: Build wheels
uses: kreuzberg-dev/actions/build-python-wheels@v1
with:
python-version: "3.13"
package-dir: packages/python
# PyO3 abi3-py310 → one wheel per platform covers Python 3.10+.
cibw-build: "cp310-*"
upload-artifact: "false"
# AlmaLinux 8 base ships OpenSSL 1.1.1 (CentOS 7 base of manylinux2014
# ships 1.0.2k which openssl-sys 0.9.115+ rejects).
manylinux-x86_64-image: "manylinux_2_28"
manylinux-aarch64-image: "manylinux_2_28"
# manylinux_2_28 (AlmaLinux 8) ships OpenSSL 1.1.1 — manylinux2014 ships 1.0.2k
# which openssl-sys 0.9.115+ rejects. CARGO_TARGET_*_LINKER points cargo at the
# in-image gcc instead of looking for triple-prefixed binaries that aren't there.
cibw-before-build-linux: "dnf install -y openssl-devel pkgconfig && pip install maturin uv && source ~/.cargo/env"
cibw-environment: 'MATURIN_BUILD_ARGS="--compatibility manylinux_2_28" CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=gcc CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=gcc CC_x86_64_unknown_linux_gnu=gcc CC_aarch64_unknown_linux_gnu=gcc CXX_x86_64_unknown_linux_gnu=g++ CXX_aarch64_unknown_linux_gnu=g++'
# cibw's bundled rustup-init on Windows defaults host triple to gnu, and
# rust-toolchain.toml channel="1.95" resolves against that gnu host. Install
# 1.95-x86_64-pc-windows-msvc explicitly AND set RUSTUP_TOOLCHAIN env var so
# cargo bypasses the toolchain.toml override (env var > toml in rustup precedence).
cibw-before-build-windows: "rustup install 1.95-x86_64-pc-windows-msvc --profile minimal --component rust-src --component rustfmt --component clippy && rustup target add x86_64-pc-windows-msvc --toolchain 1.95-x86_64-pc-windows-msvc && pip install maturin uv && set PATH=%USERPROFILE%\\.cargo\\bin;%PATH%"
cibw-environment-windows: 'MATURIN_BUILD_ARGS="--target x86_64-pc-windows-msvc" RUSTUP_TOOLCHAIN="1.95-x86_64-pc-windows-msvc"'
- name: Upload wheels
uses: actions/upload-artifact@v7.0.1
with:
name: python-wheels-${{ matrix.os }}
path: wheelhouse/*.whl
retention-days: 14
python-sdist:
name: Build Python sdist
# The sdist compiles on the consumer's machine, so its Cargo.toml must carry
# registry version-deps, not workspace paths. build-python-sdist@v1 rewrites
# them (--require-registry) then runs maturin sdist, so the core crates must be
# on crates.io first. The status guard lets it run when publish-crates is
# skipped, blocking only on its actual failure.
needs: [prepare, check-pypi, publish-crates]
if: ${{ !cancelled() && needs.publish-crates.result != 'failure' && needs.prepare.outputs.release_python == 'true' && (needs.check-pypi.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: actions/setup-python@v6.2.0
with:
python-version: "3.13"
- uses: kreuzberg-dev/actions/build-python-sdist@v1
with:
manifest-path: crates/kreuzberg-py/Cargo.toml
output-dir: dist
- uses: actions/upload-artifact@v7.0.1
with:
name: python-sdist
path: dist/*.tar.gz
retention-days: 14
node-bindings:
name: Build Node bindings (${{ matrix.target }})
needs: [prepare, check-npm]
if: ${{ needs.prepare.outputs.release_node == 'true' && (needs.check-npm.outputs.node_exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, target: x86_64-unknown-linux-gnu }
- { os: ubuntu-24.04-arm, target: aarch64-unknown-linux-gnu }
- { os: macos-latest, target: aarch64-apple-darwin }
- { os: windows-latest, target: x86_64-pc-windows-msvc }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: ${{ matrix.target }}
- uses: kreuzberg-dev/actions/setup-node-workspace@v1
with:
node-version: "24"
- name: Build NAPI binding
uses: kreuzberg-dev/actions/build-node-napi@v1
with:
crate-dir: crates/kreuzberg-node
build-command: pnpm exec napi build --release --target ${{ matrix.target }} --platform
- uses: actions/upload-artifact@v7.0.1
with:
name: node-bindings-${{ matrix.target }}
path: crates/kreuzberg-node/*.node
retention-days: 14
wasm-bindings:
name: Build WASM package
needs: [prepare, check-npm]
if: ${{ needs.prepare.outputs.release_wasm == 'true' && (needs.check-npm.outputs.wasm_exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
# WASM cold-build of kreuzberg-wasm (no-ORT feature set) runs ~12-15 min.
# GHA cancelled this job at the ~12-min mark with "runner shutdown signal"
# in three consecutive runs (preemption, not timeout). Explicit timeout
# bounds the budget; a dedicated cache-key-prefix keeps the WASM target
# cache warm across runs and brings rebuilds under 5 min.
timeout-minutes: 90
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: wasm32-unknown-unknown
cache-key-prefix: wasm-bindings
- uses: kreuzberg-dev/actions/install-wasi-sdk@v1
- uses: kreuzberg-dev/actions/setup-node-workspace@v1
with:
node-version: "24"
- uses: kreuzberg-dev/actions/build-wasm-package@v1
with:
crate-dir: crates/kreuzberg-wasm
pack-tarball: "true"
- uses: actions/upload-artifact@v7.0.1
with:
name: wasm-bundle
path: |
crates/kreuzberg-wasm/dist/**
crates/kreuzberg-wasm/dist-node/**
crates/kreuzberg-wasm/dist-web/**
retention-days: 14
ruby-gem:
name: Build Ruby gem (${{ matrix.label }})
# build-ruby-gem@v1 rewrites workspace path-deps to registry version-deps
# (--require-registry) before compiling, so the core crates must be on
# crates.io first: order after publish-crates. The status guard lets the build
# still run when publish-crates is skipped (crates already published), blocking
# only on its actual failure.
needs: [prepare, check-rubygems, publish-crates]
if: ${{ !cancelled() && needs.publish-crates.result != 'failure' && needs.prepare.outputs.release_ruby == 'true' && (needs.check-rubygems.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
env:
RB_SYS_CARGO_PROFILE: release
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, label: linux-x86_64 }
- { os: ubuntu-24.04-arm, label: linux-aarch64 }
- { os: macos-latest, label: macos-arm64 }
# Ruby on Windows is MinGW (rb-sys → x86_64-pc-windows-gnu) but ort-sys
# only ships msvc prebuilts; the ABIs are incompatible. Skip until ORT
# publishes mingw binaries or kreuzberg moves to ort-tract on Ruby/Win.
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: ruby/setup-ruby@v1.309.0
with:
ruby-version: "3.3"
bundler-cache: false
- uses: kreuzberg-dev/actions/build-ruby-gem@v1
with:
package-dir: packages/ruby
- name: Package gem
# Inline: rake build emits the .gem under pkg/; no shared action wraps it.
working-directory: packages/ruby
run: bundle exec rake build
shell: bash
- uses: actions/upload-artifact@v7.0.1
with:
name: rubygems-${{ matrix.label }}
path: packages/ruby/pkg/*.gem
retention-days: 14
php-extension:
name: Build PHP extension (php${{ matrix.php }} ${{ matrix.platform.label }})
# build-php-extension@v1 rewrites path-deps to registry versions before
# building; the core crates must be published first. The status guard lets the
# build run when publish-crates is skipped, blocking only on its actual failure.
needs: [prepare, check-packagist, publish-crates]
if: ${{ !cancelled() && needs.publish-crates.result != 'failure' && needs.prepare.outputs.release_php == 'true' && (needs.check-packagist.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.platform.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
php: ["8.2", "8.3", "8.4"]
platform:
- { os: ubuntu-latest, label: linux-x86_64, target: x86_64-unknown-linux-gnu }
- { os: ubuntu-24.04-arm, label: linux-arm64, target: aarch64-unknown-linux-gnu }
- { os: macos-latest, label: macos-arm64, target: aarch64-apple-darwin }
- { os: windows-latest, label: windows-x86_64, target: x86_64-pc-windows-msvc }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-php@v1
with:
php-version: ${{ matrix.php }}
- uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: ${{ matrix.platform.target }}
- uses: kreuzberg-dev/actions/build-php-extension@v1
with:
crate-name: kreuzberg-php
lib-name: kreuzberg_php
php-version: ${{ matrix.php }}
php-ts: nts
- uses: kreuzberg-dev/actions/install-alef@v1
- uses: kreuzberg-dev/actions/package-php-pie@v1
with:
php-version: ${{ matrix.php }}
php-ts: nts
target: ${{ matrix.platform.target }}
windows-compiler: ${{ contains(matrix.platform.target, 'windows') && 'vs17' || '' }}
version: ${{ needs.prepare.outputs.version }}
output-dir: dist/php-package
- uses: actions/upload-artifact@v7.0.1
with:
name: php-package-${{ matrix.platform.label }}-php${{ matrix.php }}
path: |
dist/php-package/php_*.tgz
dist/php-package/php_*.tgz.sha256
dist/php-package/php_*.zip
dist/php-package/php_*.zip.sha256
retention-days: 14
cli-binaries:
name: Build CLI binary (${{ matrix.target }})
needs: [prepare, check-cli]
if: ${{ needs.prepare.outputs.release_cli == 'true' && (needs.check-cli.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, target: x86_64-unknown-linux-gnu }
- { os: ubuntu-24.04-arm, target: aarch64-unknown-linux-gnu }
- { os: macos-latest, target: aarch64-apple-darwin }
- { os: windows-latest, target: x86_64-pc-windows-msvc }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: ${{ matrix.target }}
- uses: kreuzberg-dev/actions/build-rust-cli@v1
id: build
with:
package-name: kreuzberg-cli
binary-name: kreuzberg
target: ${{ matrix.target }}
- name: Package CLI artifact
# Inline: archive layout (.tar.gz on Unix, .zip on Windows) varies per
# OS in a way no shared action covers; tiny and self-contained.
env:
BINARY_PATH: ${{ steps.build.outputs.binary-path }}
TARGET: ${{ matrix.target }}
shell: bash
run: |
STAGE="kreuzberg-cli-${TARGET}"
mkdir -p "$STAGE"
if [[ "$RUNNER_OS" == "Windows" ]]; then
cp "$BINARY_PATH" "$STAGE/kreuzberg.exe"
7z a "${STAGE}.zip" "$STAGE" >/dev/null
else
cp "$BINARY_PATH" "$STAGE/kreuzberg"
tar -czf "${STAGE}.tar.gz" "$STAGE"
fi
- uses: actions/upload-artifact@v7.0.1
with:
name: cli-${{ matrix.target }}
path: |
kreuzberg-cli-${{ matrix.target }}.tar.gz
kreuzberg-cli-${{ matrix.target }}.zip
if-no-files-found: ignore
retention-days: 14
cli-binaries-musl:
# Alpine container build: ort-sys ships no musl prebuilts, so we use Alpine's
# system onnxruntime-dev (apk) plus -C target-feature=-crt-static for cdylib.
# Each Dockerfile is single-arch (FROM alpine:3.21); arch-matched runners
# produce arch-matched binaries without qemu emulation.
name: Build CLI binary (${{ matrix.target }})
needs: [prepare, check-cli]
if: ${{ needs.prepare.outputs.release_cli == 'true' && (needs.check-cli.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, target: x86_64-unknown-linux-musl }
- { os: ubuntu-24.04-arm, target: aarch64-unknown-linux-musl }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- name: Build CLI via Alpine Docker
run: |
docker build -f docker/Dockerfile.musl-build \
--output type=local,dest=./build-output .
- name: Package CLI artifact (with bundled lib/)
env:
TARGET: ${{ matrix.target }}
shell: bash
run: |
STAGE="kreuzberg-cli-${TARGET}"
mkdir -p "$STAGE/lib"
cp build-output/kreuzberg "$STAGE/kreuzberg"
cp build-output/kreuzberg.bin "$STAGE/kreuzberg.bin"
cp -a build-output/lib/. "$STAGE/lib/"
chmod +x "$STAGE/kreuzberg"
tar -czf "${STAGE}.tar.gz" "$STAGE"
- uses: actions/upload-artifact@v7.0.1
with:
name: cli-${{ matrix.target }}
path: kreuzberg-cli-${{ matrix.target }}.tar.gz
if-no-files-found: error
retention-days: 14
go-ffi-libraries:
name: Build Go FFI library (${{ matrix.platform }})
needs: [prepare, check-go]
if: ${{ needs.prepare.outputs.release_go == 'true' && (needs.check-go.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, platform: linux-x86_64, target: x86_64-unknown-linux-gnu }
- { os: ubuntu-24.04-arm, platform: linux-aarch64, target: aarch64-unknown-linux-gnu }
- { os: macos-latest, platform: macos-arm64, target: aarch64-apple-darwin }
- { os: windows-latest, platform: windows-x86_64, target: x86_64-pc-windows-msvc }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: ${{ matrix.target }}
- uses: kreuzberg-dev/actions/build-go-ffi@v1
with:
target: ${{ matrix.target }}
crate-name: kreuzberg-ffi
lib-name: kreuzberg_ffi
header-path: crates/kreuzberg-ffi/include/kreuzberg.h
archive-name: kreuzberg-go-${{ matrix.platform }}.tar.gz
output-dir: dist/go-ffi
- uses: actions/upload-artifact@v7.0.1
with:
name: go-ffi-${{ matrix.platform }}
path: dist/go-ffi/*.tar.gz
if-no-files-found: error
retention-days: 14
c-ffi-libraries:
name: Build C FFI distribution (${{ matrix.platform }})
needs: [prepare, check-c-ffi]
if: ${{ needs.prepare.outputs.release_c_ffi == 'true' && (needs.check-c-ffi.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, platform: linux-x86_64, target: x86_64-unknown-linux-gnu }
- { os: ubuntu-24.04-arm, platform: linux-aarch64, target: aarch64-unknown-linux-gnu }
- { os: macos-latest, platform: macos-arm64, target: aarch64-apple-darwin }
- { os: windows-latest, platform: windows-x86_64, target: x86_64-pc-windows-msvc }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: ${{ matrix.target }}
- uses: kreuzberg-dev/actions/build-rust-ffi@v1
with:
crate-name: kreuzberg-ffi
target: ${{ matrix.target }}
- name: Stage C FFI distribution
# Inline: bundle library + header + LICENSE into a single tarball
# named to match upload-c-ffi-release's expected asset prefix.
env:
TARGET: ${{ matrix.target }}
PLATFORM: ${{ matrix.platform }}
shell: bash
run: |
STAGE="kreuzberg-ffi-${PLATFORM}"
mkdir -p "$STAGE/lib" "$STAGE/include"
find "target/${TARGET}/release" -maxdepth 1 -type f \
\( -name 'libkreuzberg_ffi.*' -o -name 'kreuzberg_ffi.dll' \) \
-exec cp {} "$STAGE/lib/" \;
cp crates/kreuzberg-ffi/include/kreuzberg.h "$STAGE/include/"
cp LICENSE "$STAGE/" 2>/dev/null || true
mkdir -p dist/c-ffi
tar -czf "dist/c-ffi/${STAGE}.tar.gz" "$STAGE"
- uses: actions/upload-artifact@v7.0.1
with:
name: c-ffi-${{ matrix.platform }}
path: dist/c-ffi/*.tar.gz
if-no-files-found: error
retention-days: 14
java-natives:
name: Build Java natives (${{ matrix.classifier }})
needs: [prepare, check-maven]
if: ${{ needs.prepare.outputs.release_java == 'true' && (needs.check-maven.outputs.java_exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, classifier: linux-x86_64, target: x86_64-unknown-linux-gnu }
- { os: ubuntu-24.04-arm, classifier: linux-aarch64, target: aarch64-unknown-linux-gnu }
- { os: macos-latest, classifier: macos-arm64, target: aarch64-apple-darwin }
- { os: windows-latest, classifier: windows-x86_64, target: x86_64-pc-windows-msvc }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: ${{ matrix.target }}
- uses: kreuzberg-dev/actions/build-java-natives@v1
with:
target: ${{ matrix.target }}
crate-name: kreuzberg-ffi
lib-name: kreuzberg_ffi
classifier: ${{ matrix.classifier }}
- uses: actions/upload-artifact@v7.0.1
with:
name: java-natives-${{ matrix.classifier }}
path: dist/java-natives
if-no-files-found: error
retention-days: 14
java-natives-musl:
# Alpine container build via Dockerfile.musl-ffi (system onnxruntime-dev).
name: Build Java natives (linux-musl-${{ matrix.arch }})
needs: [prepare, check-maven]
if: ${{ needs.prepare.outputs.release_java == 'true' && (needs.check-maven.outputs.java_exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, arch: x86_64, classifier: linux-musl-x86_64 }
- { os: ubuntu-24.04-arm, arch: aarch64, classifier: linux-musl-aarch64 }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- name: Build kreuzberg-ffi via Alpine Docker
run: |
docker build -f docker/Dockerfile.musl-ffi \
--output type=local,dest=./build-output .
- name: Stage native lib for Java classifier layout
env:
CLASSIFIER: ${{ matrix.classifier }}
run: |
mkdir -p "dist/java-natives/${CLASSIFIER}"
cp build-output/libkreuzberg_ffi.so "dist/java-natives/${CLASSIFIER}/libkreuzberg_ffi.so"
- uses: actions/upload-artifact@v7.0.1
with:
name: java-natives-${{ matrix.classifier }}
path: dist/java-natives
if-no-files-found: error
retention-days: 14
kotlin-android-natives:
name: Build Kotlin Android natives (${{ matrix.abi }})
needs: [prepare, check-maven]
if: ${{ needs.prepare.outputs.release_kotlin == 'true' && (needs.check-maven.outputs.kotlin_exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { abi: arm64-v8a }
- { abi: x86_64 }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: kreuzberg-dev/actions/build-android-natives@v1
with:
crate-name: kreuzberg-ffi
abis: ${{ matrix.abi }}
- uses: actions/upload-artifact@v7.0.1
with:
name: kotlin-android-${{ matrix.abi }}
path: dist/android-natives
if-no-files-found: error
retention-days: 14
csharp-natives:
name: Build C# natives (${{ matrix.rid }})
needs: [prepare, check-nuget]
if: ${{ needs.prepare.outputs.release_csharp == 'true' && (needs.check-nuget.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, rid: linux-x64, target: x86_64-unknown-linux-gnu }
- { os: ubuntu-24.04-arm, rid: linux-arm64, target: aarch64-unknown-linux-gnu }
- { os: macos-latest, rid: osx-arm64, target: aarch64-apple-darwin }
- { os: windows-latest, rid: win-x64, target: x86_64-pc-windows-msvc }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: ${{ matrix.target }}
- uses: kreuzberg-dev/actions/build-csharp-natives@v1
with:
target: ${{ matrix.target }}
crate-name: kreuzberg-ffi
lib-name: kreuzberg_ffi
rid: ${{ matrix.rid }}
- uses: actions/upload-artifact@v7.0.1
with:
name: csharp-natives-${{ matrix.rid }}
path: dist/csharp-natives
if-no-files-found: error
retention-days: 14
csharp-natives-musl:
# Alpine container build via Dockerfile.musl-ffi (system onnxruntime-dev).
name: Build C# natives (linux-musl-${{ matrix.arch }})
needs: [prepare, check-nuget]
if: ${{ needs.prepare.outputs.release_csharp == 'true' && (needs.check-nuget.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, arch: x64, rid: linux-musl-x64 }
- { os: ubuntu-24.04-arm, arch: arm64, rid: linux-musl-arm64 }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- name: Build kreuzberg-ffi via Alpine Docker
run: |
docker build -f docker/Dockerfile.musl-ffi \
--output type=local,dest=./build-output .
- name: Stage native lib for NuGet RID layout
env:
RID: ${{ matrix.rid }}
run: |
mkdir -p "dist/csharp-natives/runtimes/${RID}/native"
cp build-output/libkreuzberg_ffi.so "dist/csharp-natives/runtimes/${RID}/native/libkreuzberg_ffi.so"
- uses: actions/upload-artifact@v7.0.1
with:
name: csharp-natives-${{ matrix.rid }}
path: dist/csharp-natives
if-no-files-found: error
retention-days: 14
elixir-natives:
name: Build Elixir NIF (${{ matrix.label }})
# build-elixir-natives@v1 rewrites path-deps to registry versions before
# building (so the hex source fallback resolves); needs published crates. The
# status guard lets the build run when publish-crates is skipped, blocking only
# on its actual failure.
needs: [prepare, check-elixir-release, publish-crates]
if: ${{ !cancelled() && needs.publish-crates.result != 'failure' && needs.prepare.outputs.release_elixir == 'true' && (needs.check-elixir-release.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
nif: ["2.16", "2.17"]
include:
- os: ubuntu-latest
label: linux-x86_64
target: x86_64-unknown-linux-gnu
- os: ubuntu-24.04-arm
label: linux-aarch64
target: aarch64-unknown-linux-gnu
- os: macos-latest
label: macos-arm64
target: aarch64-apple-darwin
- os: windows-latest
label: windows-x86_64
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: erlef/setup-beam@v1.24.0
with:
elixir-version: "1.17"
otp-version: "27"
- uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: ${{ matrix.target }}
- uses: kreuzberg-dev/actions/build-elixir-natives@v1
with:
target: ${{ matrix.target }}
nif-crate-name: kreuzberg_nif
nif-crate-path: packages/elixir/native/kreuzberg_nif
package-dir: packages/elixir
nif-version: ${{ needs.prepare.outputs.version }}
nif-api-version: ${{ matrix.nif }}
- uses: actions/upload-artifact@v7.0.1
with:
name: elixir-${{ matrix.label }}-nif-${{ matrix.nif }}
path: dist/elixir-natives/*.tar.gz
if-no-files-found: error
retention-days: 14
elixir-natives-musl:
# Alpine container build via Dockerfile.musl-rustler (system onnxruntime-dev).
# Replicates RustlerPrecompiled tarball naming the build-elixir-natives action
# uses for non-musl variants: lib{nif}-v{ver}-nif-{api}-{target}.so.tar.gz + sha256.
# Like elixir-natives, needs published crates for the hex source fallback.
name: Build Elixir NIF (linux-musl-${{ matrix.arch }} nif-${{ matrix.nif }})
needs: [prepare, check-elixir-release, publish-crates]
if: ${{ !cancelled() && needs.publish-crates.result != 'failure' && needs.prepare.outputs.release_elixir == 'true' && (needs.check-elixir-release.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm]
nif: ["2.16", "2.17"]
include:
- os: ubuntu-latest
arch: x86_64
target: x86_64-unknown-linux-musl
- os: ubuntu-24.04-arm
arch: aarch64
target: aarch64-unknown-linux-musl
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- name: Build kreuzberg_nif via Alpine Docker
run: |
docker build -f docker/Dockerfile.musl-rustler \
--output type=local,dest=./build-output .
- name: Package NIF as RustlerPrecompiled tarball
env:
TARGET: ${{ matrix.target }}
NIF_API: ${{ matrix.nif }}
NIF_VERSION: ${{ needs.prepare.outputs.version }}
shell: bash
run: |
set -euo pipefail
mkdir -p dist/elixir-natives staging
cp build-output/libkreuzberg_nif.so staging/libkreuzberg_nif.so
TARBALL="libkreuzberg_nif-v${NIF_VERSION}-nif-${NIF_API}-${TARGET}.so.tar.gz"
tar -czf "dist/elixir-natives/${TARBALL}" -C staging libkreuzberg_nif.so
( cd dist/elixir-natives && sha256sum "${TARBALL}" >"${TARBALL}.sha256" )
- uses: actions/upload-artifact@v7.0.1
with:
name: elixir-musl-${{ matrix.arch }}-nif-${{ matrix.nif }}
path: dist/elixir-natives/*.tar.gz
if-no-files-found: error
retention-days: 14
dart-android-natives:
name: Build Dart Android natives (${{ matrix.abi }})
needs: [prepare, check-pub]
if: ${{ needs.prepare.outputs.release_dart == 'true' && (needs.check-pub.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { abi: arm64-v8a }
- { abi: x86_64 }
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: kreuzberg-dev/actions/build-android-natives@v1
with:
crate-name: kreuzberg-dart
abis: ${{ matrix.abi }}
- uses: actions/upload-artifact@v7.0.1
with:
name: dart-android-${{ matrix.abi }}
path: dist/android-natives
if-no-files-found: error
retention-days: 14
dart-ios-xcframework:
name: Build Dart iOS XCFramework
needs: [prepare, check-pub]
if: ${{ needs.prepare.outputs.release_dart == 'true' && (needs.check-pub.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: macos-latest
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: kreuzberg-dev/actions/build-ios-xcframework@v1
with:
crate-name: kreuzberg-dart
- uses: actions/upload-artifact@v7.0.1
with:
name: dart-ios-xcframework
path: dist/ios-xcframework
if-no-files-found: error
retention-days: 14
dart-server-natives:
name: Build Dart server native (${{ matrix.blob }})
needs: [prepare, check-pub]
if: ${{ needs.prepare.outputs.release_dart == 'true' && (needs.check-pub.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- {
os: ubuntu-latest,
target: x86_64-unknown-linux-gnu,
blob: linux-x86_64,
lib: libkreuzberg_dart.so,
}
- {
os: ubuntu-24.04-arm,
target: aarch64-unknown-linux-gnu,
blob: linux-aarch64,
lib: libkreuzberg_dart.so,
}
- {
os: macos-latest,
target: aarch64-apple-darwin,
blob: macos-arm64,
lib: libkreuzberg_dart.dylib,
}
- {
os: windows-latest,
target: x86_64-pc-windows-msvc,
blob: windows-x86_64,
lib: kreuzberg_dart.dll,
}
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
with:
target: ${{ matrix.target }}
- name: Build Dart server native
run: cargo build -p kreuzberg-dart --release --target ${{ matrix.target }}
shell: bash
- name: Stage native library
shell: bash
env:
TARGET: ${{ matrix.target }}
LIB: ${{ matrix.lib }}
run: |
mkdir -p dist/server-natives
cp "target/${TARGET}/release/${LIB}" "dist/server-natives/${LIB}"
- uses: actions/upload-artifact@v7.0.1
with:
name: dart-server-${{ matrix.blob }}
path: dist/server-natives
if-no-files-found: error
retention-days: 14
dart-package:
name: Build Dart package
needs: [prepare, check-pub]
if: ${{ needs.prepare.outputs.release_dart == 'true' && needs.check-pub.outputs.exists != 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: kreuzberg-dev/actions/build-dart-package@v1
with:
# flutter_rust_bridge.yaml lives in packages/dart/rust/, where rust_root: .
package-dir: packages/dart/rust
crate-name: kreuzberg-dart
- uses: actions/upload-artifact@v7.0.1
with:
name: dart-package
path: packages/dart
retention-days: 14
swift-artifactbundle:
name: Build Swift artifact bundle
needs: prepare
if: ${{ needs.prepare.outputs.release_swift == 'true' }}
runs-on: macos-latest
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: kreuzberg-dev/actions/build-swift-artifactbundle@v1
id: bundle
with:
crate-name: kreuzberg-swift
artifact-name: KreuzbergSwift
include-macos-x86_64: "false"
include-ios-x86_64: "false"
- uses: actions/upload-artifact@v7.0.1
with:
name: swift-artifactbundle
path: ${{ steps.bundle.outputs.bundle-zip }}
if-no-files-found: error
retention-days: 14
swift-package:
name: Build Swift package
needs: prepare
if: ${{ needs.prepare.outputs.release_swift == 'true' }}
runs-on: macos-latest
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: kreuzberg-dev/actions/build-swift-package@v1
with:
package-dir: packages/swift
crate-name: kreuzberg-swift
- uses: actions/upload-artifact@v7.0.1
with:
name: swift-package
path: packages/swift
retention-days: 14
zig-package:
name: Build Zig package (smoke check)
needs: prepare
if: ${{ needs.prepare.outputs.release_zig == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
fetch-depth: 0
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: kreuzberg-dev/actions/build-zig-package@v1
with:
ffi-crate: kreuzberg-ffi
package-dir: packages/zig
# ─── GitHub Release asset uploads ─────────────────────────────────────
upload-cli-release:
name: Upload CLI binaries to GitHub Release
needs: [prepare, cli-binaries]
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.cli-binaries.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8.0.1
with:
pattern: cli-*
path: dist/cli
merge-multiple: true
- uses: kreuzberg-dev/actions/upload-release-assets@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
working-directory: dist/cli
dry-run: ${{ needs.prepare.outputs.dry_run }}
assets: |
kreuzberg-cli-*.tar.gz
kreuzberg-cli-*.zip
upload-go-release:
name: Upload Go FFI archives to GitHub Release
needs: [prepare, go-ffi-libraries]
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.go-ffi-libraries.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8.0.1
with:
pattern: go-ffi-*
path: dist/go-ffi
merge-multiple: true
- uses: kreuzberg-dev/actions/upload-release-assets@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
working-directory: dist/go-ffi
dry-run: ${{ needs.prepare.outputs.dry_run }}
assets: kreuzberg-go-*.tar.gz
upload-c-ffi-release:
name: Upload C FFI archives to GitHub Release
needs: [prepare, c-ffi-libraries]
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.c-ffi-libraries.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8.0.1
with:
pattern: c-ffi-*
path: dist/c-ffi
merge-multiple: true
- uses: kreuzberg-dev/actions/upload-release-assets@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
working-directory: dist/c-ffi
dry-run: ${{ needs.prepare.outputs.dry_run }}
assets: kreuzberg-ffi-*.tar.gz
upload-elixir-release:
name: Upload Elixir NIF archives to GitHub Release
needs: [prepare, elixir-natives, elixir-natives-musl]
if: ${{ needs.prepare.outputs.is_tag == 'true' && (needs.elixir-natives.result == 'success' || needs.elixir-natives-musl.result == 'success') }}
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8.0.1
with:
pattern: elixir-*
path: dist/elixir
merge-multiple: true
- uses: kreuzberg-dev/actions/upload-release-assets@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
working-directory: dist/elixir
dry-run: ${{ needs.prepare.outputs.dry_run }}
assets: libkreuzberg_nif-*.tar.gz
upload-php-pie-release:
name: Upload PHP PIE archives to GitHub Release
needs: [prepare, php-extension]
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.php-extension.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8.0.1
with:
pattern: php-package-*
path: dist/php-package
merge-multiple: true
- uses: kreuzberg-dev/actions/upload-release-assets@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
working-directory: dist/php-package
dry-run: ${{ needs.prepare.outputs.dry_run }}
assets: |
php_*.tgz
php_*.tgz.sha256
php_*.zip
php_*.zip.sha256
upload-swift-bundle:
name: Upload Swift artifact bundle to GitHub Release
needs: [prepare, swift-artifactbundle]
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.swift-artifactbundle.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8.0.1
with:
name: swift-artifactbundle
path: dist/swift-bundle
- uses: kreuzberg-dev/actions/upload-release-assets@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
working-directory: dist/swift-bundle
dry-run: ${{ needs.prepare.outputs.dry_run }}
assets: "*.zip"
# Verifies every expected release asset is present on the GitHub release
# before the publish-* fan-out depends on those URLs.
verify-assets:
name: Verify release assets
needs:
- prepare
- upload-cli-release
- upload-go-release
- upload-c-ffi-release
- upload-elixir-release
- upload-php-pie-release
if: ${{ always() && needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.dry_run != 'true' && !contains(needs.*.result, 'failure') }}
runs-on: ubuntu-latest
steps:
- uses: kreuzberg-dev/actions/verify-release-assets@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
expected-assets: |
# CLI
kreuzberg-cli-*.tar.gz
kreuzberg-cli-*.zip
# Go FFI
kreuzberg-go-*.tar.gz
# C FFI
kreuzberg-ffi-*.tar.gz
# Elixir NIF
libkreuzberg_nif-*.tar.gz
# ─── Publish jobs ─────────────────────────────────────────────────────
publish-crates:
name: Publish crates.io packages
needs: [prepare, validate-versions, check-cratesio]
if: ${{ needs.prepare.outputs.release_crates == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-cratesio.outputs.all_exist != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.ref }}
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: kreuzberg-dev/actions/publish-crates@v1
with:
crates: kreuzberg-tesseract kreuzberg-paddle-ocr kreuzberg kreuzberg-cli
version: ${{ needs.prepare.outputs.version }}
dry-run: ${{ needs.prepare.outputs.dry_run }}
publish-pypi:
name: Publish to PyPI
needs: [prepare, python-wheels, python-sdist, check-pypi]
if: ${{ needs.prepare.outputs.release_python == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-pypi.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@v8.0.1
with:
pattern: python-wheels-*
path: dist
merge-multiple: true
- uses: actions/download-artifact@v8.0.1
with:
name: python-sdist
path: dist
- name: Verify Python wheel package contents
uses: kreuzberg-dev/actions/verify-package-contents@v1
with:
language: python
artifact-path: dist
package-name: kreuzberg
# Per-archive patterns don't apply uniformly: wheels carry the
# native .so/.dylib/.dll but sdist doesn't; sdist carries the
# source tree the wheels strip. The basic `python` allowlist also
# only matches wheels. strict=false reports diffs without blocking.
strict: "false"
- uses: kreuzberg-dev/actions/publish-pypi@v1
with:
packages-dir: dist
dry-run: ${{ needs.prepare.outputs.dry_run }}
publish-rubygems:
name: Publish Ruby gems
needs: [prepare, ruby-gem, check-rubygems]
if: ${{ needs.prepare.outputs.release_ruby == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-rubygems.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@v8.0.1
with:
pattern: rubygems-*
path: dist
merge-multiple: true
- uses: ruby/setup-ruby@v1.309.0
with:
ruby-version: "3.3"
bundler-cache: false
- uses: rubygems/configure-rubygems-credentials@v2.0.0
- name: Verify Ruby gem package contents
uses: kreuzberg-dev/actions/verify-package-contents@v1
with:
language: ruby
artifact-path: dist
package-name: kreuzberg
- uses: kreuzberg-dev/actions/publish-rubygems@v1
with:
gems-dir: dist
dry-run: ${{ needs.prepare.outputs.dry_run }}
publish-node:
name: Publish Node packages
needs: [prepare, node-bindings, check-npm]
if: ${{ needs.prepare.outputs.release_node == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-npm.outputs.node_exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.ref }}
submodules: recursive
- uses: actions/download-artifact@v8.0.1
with:
pattern: node-bindings-*
path: crates/kreuzberg-node/artifacts
merge-multiple: true
- uses: kreuzberg-dev/actions/setup-node-workspace@v1
with:
node-version: "24"
registry-url: "https://registry.npmjs.org/"
- name: Stage platform packages with napi
# Inline: napi artifacts → per-platform npm/ subpackages. Equivalent
# of `napi create-npm-dirs && napi artifacts`; trivially small.
working-directory: crates/kreuzberg-node
run: |
pnpm exec napi create-npm-dirs
pnpm exec napi artifacts --output-dir artifacts --npm-dir npm
shell: bash
- name: Pack platform sub-packages
# `publish-npm packages-dir` mode expects *.tgz inputs; napi only
# stages the per-platform directories. Run `npm pack` in each so
# the publish action has tarballs to upload (or dry-run).
working-directory: crates/kreuzberg-node
shell: bash
run: |
for d in npm/*/; do
[ -d "$d" ] || continue
(cd "$d" && npm pack)
done
- name: Verify Node platform sub-package contents
uses: kreuzberg-dev/actions/verify-package-contents@v1
with:
language: node-platform
artifact-path: crates/kreuzberg-node/npm
package-name: "@kreuzberg/node"
# NAPI scaffolds stub package.json dirs for every platform in
# crates/kreuzberg-node/package.json's `napi.triples.additional`,
# not just the targets we build (e.g. darwin-x64, win32-arm64-msvc
# are listed but not built — their npm/*.tgz contains no .node).
# Use strict=false so verify reports stubs without failing the job.
strict: "false"
- name: Publish platform sub-packages
uses: kreuzberg-dev/actions/publish-npm@v1
with:
packages-dir: crates/kreuzberg-node/npm
npm-tag: ${{ needs.prepare.outputs.npm_tag }}
dry-run: ${{ needs.prepare.outputs.dry_run }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish main @kreuzberg/node package
uses: kreuzberg-dev/actions/publish-npm@v1
with:
package-dir: crates/kreuzberg-node
npm-tag: ${{ needs.prepare.outputs.npm_tag }}
dry-run: ${{ needs.prepare.outputs.dry_run }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish-wasm:
name: Publish WASM package
needs: [prepare, wasm-bindings, check-npm]
if: ${{ needs.prepare.outputs.release_wasm == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-npm.outputs.wasm_exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.ref }}
submodules: recursive
- uses: actions/download-artifact@v8.0.1
with:
name: wasm-bundle
path: crates/kreuzberg-wasm
- uses: kreuzberg-dev/actions/setup-node-workspace@v1
with:
node-version: "24"
registry-url: "https://registry.npmjs.org/"
- uses: kreuzberg-dev/actions/publish-npm@v1
with:
package-dir: crates/kreuzberg-wasm
npm-tag: ${{ needs.prepare.outputs.npm_tag }}
dry-run: ${{ needs.prepare.outputs.dry_run }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish-packagist:
name: Trigger Packagist update
needs: [prepare, upload-php-pie-release, check-packagist]
if: ${{ needs.prepare.outputs.release_php == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-packagist.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
steps:
- uses: kreuzberg-dev/actions/publish-packagist@v1
with:
packagist-username: kreuzberg-dev
package-name: kreuzberg/kreuzberg
version: ${{ needs.prepare.outputs.version }}
repository-url: https://github.com/kreuzberg-dev/kreuzberg
dry-run: ${{ needs.prepare.outputs.dry_run }}
env:
PACKAGIST_API_TOKEN: ${{ secrets.PACKAGIST_API_TOKEN }}
publish-maven:
name: Publish Maven (Java) package
needs: [prepare, java-natives, java-natives-musl, check-maven]
if: ${{ needs.prepare.outputs.release_java == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-maven.outputs.java_exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.ref }}
submodules: recursive
- uses: actions/download-artifact@v8.0.1
with:
pattern: java-natives-*
path: artifacts/java-natives
merge-multiple: true
- uses: kreuzberg-dev/actions/stage-java-natives@v1
with:
resources-dir: packages/java/src/main/resources/natives
required-classifiers: >-
linux-x86_64 linux-aarch64 macos-arm64 windows-x86_64
linux-musl-x86_64 linux-musl-aarch64
lib-name: kreuzberg_ffi
- uses: actions/setup-java@v5.2.0
with:
distribution: temurin
java-version: "21"
cache: maven
server-id: central
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE
- uses: kreuzberg-dev/actions/setup-maven@v1
- uses: kreuzberg-dev/actions/publish-maven@v1
with:
pom-file: packages/java/pom.xml
maven-profile: publish
extra-args: -DskipTests
dry-run: ${{ needs.prepare.outputs.dry_run }}
env:
MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
publish-kotlin-android:
name: Publish Kotlin Android (Maven Central via Gradle)
needs: [prepare, kotlin-android-natives, check-maven]
if: ${{ needs.prepare.outputs.release_kotlin == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-maven.outputs.kotlin_exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.ref }}
submodules: recursive
- uses: actions/download-artifact@v8.0.1
with:
pattern: kotlin-android-*
path: kotlin-android-artifacts
merge-multiple: true
- name: Stage Android natives into Kotlin Android module
shell: bash
run: |
for abi in arm64-v8a x86_64; do
dest="packages/kotlin-android/src/main/jniLibs/${abi}"
mkdir -p "$dest"
src="kotlin-android-artifacts/${abi}/libkreuzberg_ffi.so"
if [[ -f "$src" ]]; then
cp -v "$src" "$dest/"
fi
done
- uses: android-actions/setup-android@v4.0.1
with:
packages: "platforms;android-36 build-tools;36.0.0"
- uses: kreuzberg-dev/actions/publish-maven-gradle@v1
with:
working-directory: packages/kotlin-android
gradle-task: publishAndReleaseToMavenCentral
dry-run: ${{ needs.prepare.outputs.dry_run }}
env:
MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
MAVEN_GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
publish-nuget:
name: Publish NuGet package
needs: [prepare, csharp-natives, csharp-natives-musl, check-nuget]
if: ${{ needs.prepare.outputs.release_csharp == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-nuget.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.ref }}
submodules: recursive
- uses: actions/download-artifact@v8.0.1
with:
pattern: csharp-natives-*
path: csharp-natives
merge-multiple: true
- name: Stage runtimes/<rid>/native into NuGet project
# Inline: NuGet expects native libs in runtimes/<rid>/native/ adjacent
# to the .csproj. Copy from artifact tree into project.
run: |
mkdir -p packages/csharp/Kreuzberg/runtimes
for dir in csharp-natives/runtimes/*/; do
rid="$(basename "$dir")"
mkdir -p "packages/csharp/Kreuzberg/runtimes/${rid}/native"
cp -v "${dir}native/"* "packages/csharp/Kreuzberg/runtimes/${rid}/native/"
done
shell: bash
- uses: actions/setup-dotnet@v5.2.0
with:
dotnet-version: "8.0.x"
- name: Pack NuGet package
# Inline: dotnet pack with the version override; one-liner.
run: |
dotnet pack packages/csharp/Kreuzberg/Kreuzberg.csproj \
-c Release \
-p:Version=${{ needs.prepare.outputs.version }} \
-o dist/nuget
shell: bash
- uses: kreuzberg-dev/actions/publish-nuget@v1
with:
packages-dir: dist/nuget
dry-run: ${{ needs.prepare.outputs.dry_run }}
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
finalize-github-release-after-uploads:
name: Finalize GitHub Release (after uploads)
needs:
- prepare
- verify-assets
- upload-cli-release
- upload-go-release
- upload-c-ffi-release
- upload-elixir-release
- upload-php-pie-release
- upload-swift-bundle
if: ${{ needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.dry_run != 'true' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: kreuzberg-dev/actions/finalize-release@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
is-prerelease: ${{ needs.prepare.outputs.is_prerelease }}
publish-hex:
name: Publish Elixir Hex package
needs: [prepare, finalize-github-release-after-uploads, check-hex]
if: ${{ needs.prepare.outputs.release_elixir == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-hex.outputs.elixir_exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.ref }}
submodules: recursive
- uses: kreuzberg-dev/actions/setup-rust@v1
- uses: erlef/setup-beam@v1.24.0
with:
elixir-version: "1.17"
otp-version: "27"
- name: Generate NIF checksum file
uses: kreuzberg-dev/actions/generate-elixir-checksums@v1
with:
github-repo: kreuzberg-dev/kreuzberg
tag: ${{ needs.prepare.outputs.tag }}
version: ${{ needs.prepare.outputs.version }}
lib-name: kreuzberg_nif
targets: x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,x86_64-unknown-linux-musl,aarch64-unknown-linux-musl,aarch64-apple-darwin,x86_64-pc-windows-msvc
output-path: packages/elixir/checksum-Elixir.Kreuzberg.Native.exs
- name: Build Elixir Hex tarball with dependency rewrite
id: build-hex
uses: kreuzberg-dev/actions/build-elixir-hex@v1
with:
package-dir: packages/elixir
nif-crate-path: packages/elixir/native/kreuzberg_nif
rewrite-native-deps: "true"
dry-run: ${{ needs.prepare.outputs.dry_run }}
- uses: kreuzberg-dev/actions/publish-hex@v1
with:
package-dir: packages/elixir
tarball-path: ${{ steps.build-hex.outputs.archive-path }}
dry-run: ${{ needs.prepare.outputs.dry_run }}
env:
HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
assemble-dart-bundle:
name: Assemble Dart package with native artifacts
needs:
[
prepare,
dart-package,
dart-android-natives,
dart-ios-xcframework,
dart-server-natives,
check-pub,
]
if: ${{ needs.prepare.outputs.release_dart == 'true' && needs.prepare.outputs.is_tag == 'true' && needs.check-pub.outputs.exists != 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8.0.1
with:
name: dart-package
path: packages/dart
- name: Download Android natives
uses: actions/download-artifact@v8.0.1
with:
pattern: dart-android-*
path: dart-android-artifacts
merge-multiple: true
- name: Download iOS XCFramework
uses: actions/download-artifact@v8.0.1
with:
name: dart-ios-xcframework
path: dart-ios-xcframework
- name: Download server natives (linux-x86_64)
uses: actions/download-artifact@v8.0.1
with:
name: dart-server-linux-x86_64
path: dart-server-artifacts/linux-x86_64
- name: Download server natives (linux-aarch64)
uses: actions/download-artifact@v8.0.1
with:
name: dart-server-linux-aarch64
path: dart-server-artifacts/linux-aarch64
- name: Download server natives (macos-arm64)
uses: actions/download-artifact@v8.0.1
with:
name: dart-server-macos-arm64
path: dart-server-artifacts/macos-arm64
- name: Download server natives (windows-x86_64)
uses: actions/download-artifact@v8.0.1
with:
name: dart-server-windows-x86_64
path: dart-server-artifacts/windows-x86_64
- name: Stage native libraries into Dart package
shell: bash
run: |
set -euo pipefail
# Android JNI libs
for abi in arm64-v8a x86_64; do
dest="packages/dart/android/src/main/jniLibs/${abi}"
mkdir -p "$dest"
src="dart-android-artifacts/${abi}/libkreuzberg_dart.so"
[[ -f "$src" ]] && cp -v "$src" "$dest/"
done
# iOS XCFramework
mkdir -p packages/dart/ios/Frameworks
for xcf in dart-ios-xcframework/*.xcframework; do
[[ -d "$xcf" ]] && cp -r "$xcf" packages/dart/ios/Frameworks/
done
# Server blobs
for entry in "linux-x86_64:libkreuzberg_dart.so" "linux-aarch64:libkreuzberg_dart.so" "macos-arm64:libkreuzberg_dart.dylib" "windows-x86_64:kreuzberg_dart.dll"; do
blob="${entry%%:*}"
lib="${entry##*:}"
dest="packages/dart/blobs/${blob}"
mkdir -p "$dest"
src="dart-server-artifacts/${blob}/${lib}"
[[ -f "$src" ]] && cp -v "$src" "$dest/"
done
- uses: actions/upload-artifact@v7.0.1
with:
name: dart-package-assembled
path: packages/dart
retention-days: 7
trigger-pubdev:
name: Trigger pub.dev publish workflow
needs: [prepare, check-pub, assemble-dart-bundle]
# pub.dev OIDC rejects tokens from `release` events. We dispatch a separate
# workflow whose `workflow_dispatch` event produces an accepted token, then
# have it download the assembled Dart package artifact from this run.
if: ${{ needs.prepare.outputs.release_dart == 'true' && needs.prepare.outputs.is_tag == 'true' && needs.prepare.outputs.dry_run != 'true' && (needs.check-pub.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Dispatch publish-pubdev workflow
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
gh workflow run publish-pubdev.yaml \
--ref ${{ github.ref_name }} \
-f run_id=${{ github.run_id }}
publish-zig:
name: Publish Zig package metadata
needs: [prepare, finalize-github-release-after-uploads, check-zig]
if: ${{ needs.prepare.outputs.release_zig == 'true' && needs.prepare.outputs.is_tag == 'true' && (needs.check-zig.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.prepare.outputs.ref }}
submodules: recursive
- uses: kreuzberg-dev/actions/publish-zig@v1
with:
working-directory: packages/zig
tag: ${{ needs.prepare.outputs.tag }}
# Override the auto-detected package-name (`.kreuzberg` from
# build.zig.zon) to match the alef-emitted test_app URL pattern
# `{crate-name}-zig-v{version}.tar.gz`. Without this, the asset
# uploads as `kreuzberg-v{version}.tar.gz` while test_apps/zig
# fetches `kreuzberg-zig-v{version}.tar.gz` → 404 + consumer
# build.zig.zon TODO hash.
package-name: kreuzberg-zig
update-release-notes: "true"
dry-run: ${{ needs.prepare.outputs.dry_run }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ─── Homebrew (formula update + bottles) ──────────────────────────────
#
# Topology mirrors html-to-markdown:
# 1. publish-homebrew-formula — update formula source URL/sha (no bottles)
# 2. homebrew-bottles — build bottle tarballs + JSON manifests
# 3. publish-homebrew-bottles — merge bottle DSL into formula and push
publish-homebrew-formula:
name: Update Homebrew formula
needs: [prepare, upload-cli-release, check-homebrew]
if: ${{ needs.prepare.outputs.release_homebrew == 'true' && needs.prepare.outputs.is_tag == 'true' && needs.check-homebrew.outputs.exists != 'true' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout source repo
uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- name: Checkout homebrew-tap
uses: actions/checkout@v6.0.2
with:
repository: kreuzberg-dev/homebrew-tap
token: ${{ secrets.HOMEBREW_TOKEN }}
path: homebrew-tap
- name: Update formula files
# Inline: bumps url/sha256/version in Formula/kreuzberg.rb. Equivalent
# of html-to-markdown's scripts/publish/update-homebrew-formula.sh.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
VERSION: ${{ needs.prepare.outputs.version }}
TAP_DIR: ${{ github.workspace }}/homebrew-tap
DRY_RUN: ${{ needs.prepare.outputs.dry_run }}
run: scripts/publish/update-homebrew-formula.sh
shell: bash
- name: Commit and push to tap
if: ${{ needs.prepare.outputs.dry_run != 'true' }}
working-directory: homebrew-tap
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TOKEN }}
run: |
git config user.name "kreuzberg-bot"
git config user.email "bot@kreuzberg.dev"
if git diff --quiet Formula/; then
echo "No formula changes; skipping commit."
exit 0
fi
git add Formula/kreuzberg.rb
git commit -m "kreuzberg ${{ needs.prepare.outputs.version }}"
git push origin HEAD
shell: bash
homebrew-bottles:
name: Build Homebrew bottle (${{ matrix.bottle_tag }})
needs: [prepare, publish-homebrew-formula]
if: ${{ needs.prepare.outputs.release_homebrew == 'true' && needs.prepare.outputs.is_tag == 'true' }}
runs-on: ${{ matrix.os }}
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- { os: macos-latest, bottle_tag: arm64_sonoma, install_brew: false }
# Intel macOS (sequoia, macos-15-intel) bottle dropped: ort-sys 2.0.0-rc.12 has no
# prebuilt for x86_64-apple-darwin. Re-add once upstream ships it or we switch to
# a no-ort feature set for Intel Macs.
- { 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/checkout@v5
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
submodules: recursive
- 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: |
kreuzberg
out-dir: ${{ github.workspace }}/bottle-json
github-repo: kreuzberg-dev/kreuzberg
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/upload-artifact@v7.0.1
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: ${{ needs.prepare.outputs.release_homebrew == 'true' && needs.prepare.outputs.is_tag == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
submodules: recursive
- name: Checkout homebrew-tap
uses: actions/checkout@v6.0.2
with:
repository: kreuzberg-dev/homebrew-tap
token: ${{ secrets.HOMEBREW_TOKEN }}
path: homebrew-tap
- uses: actions/download-artifact@v8.0.1
with:
path: ${{ github.workspace }}/bottle-json
pattern: homebrew-bottle-json-*
merge-multiple: true
- 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: |
kreuzberg
github-repo: kreuzberg-dev/kreuzberg
- name: Commit and push bottle DSL
if: ${{ needs.prepare.outputs.dry_run != 'true' }}
# Inline: tap-side commit/push of the regenerated formula. No shared
# action covers tap commits — each repo's bot identity is unique.
working-directory: homebrew-tap
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TOKEN }}
run: |
git config user.name "kreuzberg-bot"
git config user.email "bot@kreuzberg.dev"
if git diff --quiet Formula/; then
echo "No bottle DSL changes; skipping commit."
exit 0
fi
git add Formula/kreuzberg.rb
git commit -m "kreuzberg ${{ needs.prepare.outputs.version }}: add bottle DSL"
git push origin HEAD
shell: bash
# ─── Finalization ─────────────────────────────────────────────────────
release-finalize:
name: Finalize GitHub Release (post-publish validation)
needs:
- prepare
- finalize-github-release-after-uploads
- publish-crates
- publish-pypi
- publish-rubygems
- publish-node
- publish-wasm
- publish-packagist
- publish-maven
- publish-kotlin-android
- publish-nuget
- publish-hex
- trigger-pubdev
- publish-zig
- publish-homebrew-formula
- publish-homebrew-bottles
if: |
always() &&
!cancelled() &&
needs.prepare.outputs.is_tag == 'true' &&
needs.prepare.outputs.dry_run != 'true' &&
!contains(needs.*.result, 'failure')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: kreuzberg-dev/actions/finalize-release@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
is-prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' && 'true' || 'false' }}
go-module-path: packages/go/v5
announce-discord:
name: Announce release on Discord
needs:
- prepare
- publish-crates
- publish-pypi
- publish-rubygems
- publish-node
- publish-wasm
- publish-packagist
- publish-maven
- publish-kotlin-android
- publish-nuget
- publish-hex
- trigger-pubdev
- publish-zig
- publish-homebrew-formula
- publish-homebrew-bottles
- upload-cli-release
- upload-go-release
- upload-c-ffi-release
- upload-elixir-release
- upload-php-pie-release
- upload-swift-bundle
- release-finalize
if: |
always() &&
!cancelled() &&
needs.prepare.outputs.is_tag == 'true' &&
needs.prepare.outputs.dry_run != 'true' &&
needs.prepare.outputs.is_prerelease != 'true' &&
!contains(needs.*.result, 'failure')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: kreuzberg-dev/actions/announce-release-discord@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
project-name: kreuzberg