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//native into NuGet project # Inline: NuGet expects native libs in runtimes//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