Skip to content

chore(deps): relock test_apps/rust to html-to-markdown-rs 3.12.0 #355

chore(deps): relock test_apps/rust to html-to-markdown-rs 3.12.0

chore(deps): relock test_apps/rust to html-to-markdown-rs 3.12.0 #355

Workflow file for this run

name: CI E2E
on:
push:
branches: [main]
paths:
- "crates/**"
- "packages/**"
- "e2e/**"
- "fixtures/**"
- "alef.toml"
- "tools/**"
- "scripts/**"
- "Cargo.toml"
- "Cargo.lock"
- "Taskfile.yaml"
- ".task/**"
- "pyproject.toml"
- "uv.lock"
- "uv.toml"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "package.json"
- "Gemfile"
- "Gemfile.lock"
- "composer.json"
- "composer.lock"
- "go.mod"
- "go.sum"
- "rust-toolchain.toml"
- ".cargo/config.toml"
- ".github/workflows/ci-e2e.yaml"
pull_request:
branches: [main]
paths:
- "crates/**"
- "packages/**"
- "e2e/**"
- "fixtures/**"
- "alef.toml"
- "tools/**"
- "scripts/**"
- "Cargo.toml"
- "Cargo.lock"
- "Taskfile.yaml"
- ".task/**"
- "pyproject.toml"
- "uv.lock"
- "uv.toml"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "package.json"
- "Gemfile"
- "Gemfile.lock"
- "composer.json"
- "composer.lock"
- "go.mod"
- "go.sum"
- "rust-toolchain.toml"
- ".cargo/config.toml"
- ".github/workflows/ci-e2e.yaml"
workflow_dispatch: {}
concurrency:
group: ci-e2e-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
BUILD_PROFILE: "ci"
GO_VERSION: "1.26.0"
GO_TOOLCHAIN: "go1.26.0"
GOLANGCI_LINT_VERSION: "latest"
permissions:
contents: read
jobs:
changes:
name: "Detect Changes"
runs-on: ubuntu-24.04-arm
outputs:
core: ${{ steps.filter.outputs.core }}
rust: ${{ steps.filter.outputs.rust }}
ffi: ${{ steps.filter.outputs.ffi }}
python: ${{ steps.filter.outputs.python }}
node: ${{ steps.filter.outputs.node }}
ruby: ${{ steps.filter.outputs.ruby }}
php: ${{ steps.filter.outputs.php }}
go: ${{ steps.filter.outputs.go }}
java: ${{ steps.filter.outputs.java }}
elixir: ${{ steps.filter.outputs.elixir }}
r: ${{ steps.filter.outputs.r }}
wasm: ${{ steps.filter.outputs.wasm }}
csharp: ${{ steps.filter.outputs.csharp }}
kotlin: ${{ steps.filter.outputs.kotlin }}
swift: ${{ steps.filter.outputs.swift }}
dart: ${{ steps.filter.outputs.dart }}
zig: ${{ steps.filter.outputs.zig }}
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Detect changes
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4
id: filter
with:
filters: |
core:
- 'crates/html-to-markdown/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- '.cargo/config.toml'
- 'fixtures/**'
- 'tools/e2e-generator/**'
rust:
- 'crates/html-to-markdown/**'
- 'crates/html-to-markdown-cli/**'
- 'e2e/rust/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rustfmt.toml'
ffi:
- 'crates/html-to-markdown-ffi/**'
- 'crates/html-to-markdown/**'
- 'e2e/c/**'
- 'Cargo.toml'
- 'Cargo.lock'
python:
- 'crates/html-to-markdown-py/**'
- 'packages/python/**'
- 'e2e/python/**'
- 'pyproject.toml'
- 'uv.lock'
- 'uv.toml'
- 'fixtures/**'
node:
- 'crates/html-to-markdown-node/**'
- 'e2e/node/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'fixtures/**'
ruby:
- 'packages/ruby/**'
- 'e2e/ruby/**'
- 'Gemfile'
- 'Gemfile.lock'
- 'fixtures/**'
php:
- 'crates/html-to-markdown-php/**'
- 'packages/php-ext/**'
- 'e2e/php/**'
- 'composer.json'
- 'composer.lock'
- 'fixtures/**'
go:
- 'packages/go/**'
- 'e2e/go/**'
- 'crates/html-to-markdown-ffi/**'
- 'go.mod'
- 'go.sum'
- 'fixtures/**'
java:
- 'packages/java/**'
- 'e2e/java/**'
- 'crates/html-to-markdown-ffi/**'
- 'fixtures/**'
elixir:
- 'packages/elixir/**'
- 'e2e/elixir/**'
- 'crates/html-to-markdown-ffi/**'
- 'fixtures/**'
r:
- 'packages/r/**'
- 'e2e/r/**'
- 'fixtures/**'
wasm:
- 'crates/html-to-markdown-wasm/**'
- 'crates/html-to-markdown-wasm-wasi/**'
- 'e2e/wasm/**'
- 'fixtures/**'
csharp:
- 'packages/csharp/**'
- 'e2e/csharp/**'
- 'crates/html-to-markdown-ffi/**'
- 'fixtures/**'
kotlin:
- 'packages/kotlin-android/**'
- 'crates/html-to-markdown-ffi/**'
- 'fixtures/**'
swift:
- 'packages/swift/**'
- 'e2e/swift_e2e/**'
- 'fixtures/**'
dart:
- 'packages/dart/**'
- 'e2e/dart/**'
- 'fixtures/**'
zig:
- 'packages/zig/**'
- 'e2e/zig/**'
- 'crates/html-to-markdown-ffi/**'
- 'fixtures/**'
# ~keep e2e/ is entirely generated from fixtures/ (html-to-markdown audit #40).
# Nothing previously regenerated it in CI and diffed the result, so a fixture or
# generator change could drift from the checked-in e2e suite indefinitely.
e2e-freshness:
name: "Verify: e2e freshness"
runs-on: ubuntu-24.04-arm
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: e2e-freshness
# ~keep alef formats the Elixir suite with `mix format` during generation. Without mix on
# PATH it degrades to a WARN and emits unformatted output, which the drift check below then
# reports as staleness that has nothing to do with fixtures/ — the whole job failed on
# e2e/elixir/**.exs for that reason alone. Only the formatter is needed here, so no hex,
# rebar or dependency install. Every other language's formatter is already on the runner
# image or installed by the steps below.
- name: Setup Elixir
uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1
with:
elixir-version: "1.19"
otp-version: "28.1"
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Install alef
uses: xberg-io/actions/install-alef@v1
# ~keep `alef e2e generate` formats every suite it emits by shelling out to poly. With
# poly absent this job failed all 16 languages with "poly not found on PATH" before the
# drift check below ever ran, so the gate could not report on the drift it exists to catch.
- name: Install poly
uses: Goldziher/poly@v0
- name: Regenerate e2e suites
run: task e2e:generate
shell: bash
- name: Fail if generated e2e/ drifted from committed output
# ~keep `git diff` reports only tracked paths, so a regeneration that ADDS a new suite
# left the gate at exit 0 — it could not fail on the defect it exists to catch. Use
# `git status --porcelain -uall`, which reports additions, modifications and deletions.
run: |
drift="$(git status --porcelain --untracked-files=all -- e2e/)"
if [ -n "$drift" ]; then
echo "::error::e2e/ is stale — regenerating from fixtures/ changed the tree. Run 'task e2e:generate' and commit the result."
printf '%s\n' "$drift"
git diff -- e2e/
exit 1
fi
shell: bash
# ~keep The FFI crate is the shared cross-compile base for the C, Go, Java, and
# C# publish legs, all of which build it for macos-15-intel (darwin x64) and
# windows-11-arm (windows arm64) too. Those two targets were previously only
# ever compiled at publish time — cover them here so a cross-compile break
# surfaces on the commit that causes it, not at tag time.
build-ffi:
needs: [changes]
name: "Build: FFI (${{ matrix.runner }})"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.ffi == 'true'
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
runner:
- ubuntu-latest
- ubuntu-24.04-arm
- macos-latest
- macos-15-intel
- windows-latest
- windows-11-arm
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Free disk space
if: startsWith(matrix.runner, 'ubuntu')
uses: xberg-io/actions/free-disk-space-linux@v1
with:
show-initial: "false"
show-final: "true"
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: ffi-${{ matrix.runner }}
use-sccache: false
- name: Build html-to-markdown-ffi (release, Unix)
if: "!startsWith(matrix.runner, 'windows')"
run: cargo build --release --locked -p html-to-markdown-ffi
shell: bash
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: "0"
RUST_BACKTRACE: short
- name: Build html-to-markdown-ffi (debug, Windows)
if: startsWith(matrix.runner, 'windows')
run: cargo build --locked -p html-to-markdown-ffi
shell: bash
env:
CARGO_TERM_COLOR: always
# ~keep `test -f` alone only proves build.rs wrote *a* header — it says
# nothing about whether the freshly-generated header matches the one
# committed to git, so a stale checked-in header could never fail this
# step. Diff against git to actually assert freshness.
- name: Verify header is fresh
shell: bash
run: |
HEADER="crates/html-to-markdown-ffi/include/html_to_markdown.h"
test -f "$HEADER"
git diff --exit-code -- "$HEADER" || {
echo "::error::$HEADER is stale — cbindgen regenerated a different header than the one committed. Run 'cargo build -p html-to-markdown-ffi' and commit the result."
exit 1
}
echo "Header verified fresh: $HEADER"
- name: Upload FFI artifacts
if: "!startsWith(matrix.runner, 'windows')"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ffi-${{ matrix.runner }}
path: |
target/release/libhtml_to_markdown_ffi.*
target/release/html_to_markdown_ffi.*
crates/html-to-markdown-ffi/include/html_to_markdown.h
retention-days: 7
if-no-files-found: warn
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
build-python:
needs: [changes]
name: "Build: Python (${{ matrix.os }})"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.python == 'true'
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python: ["3.10", "3.12", "3.14"]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: ${{ matrix.python }}
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
components: rustfmt, clippy, llvm-tools-preview
cache-key-prefix: python-${{ matrix.os }}-${{ matrix.python }}
- name: Install Python Dependencies
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4
with:
timeout_minutes: 5
max_attempts: 3
retry_wait_seconds: 30
command: |
if [[ "${{ runner.os }}" == "Windows" ]] && [[ -d ".venv" ]]; then
echo "Removing existing .venv directory on Windows"
rm -rf .venv
fi
uv sync --all-extras --no-install-project --no-install-workspace
shell: bash
- name: Build Python Bindings
run: |
uv pip install maturin
cd packages/python && uv run maturin develop --release
shell: bash
- name: Build CLI binary
run: cargo build --release --locked -p html-to-markdown-cli
shell: bash
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
build-node:
needs: [changes]
name: "Build: Node (${{ matrix.os }}, ${{ matrix.runtime }})"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.node == 'true'
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runtime: [node, bun]
exclude:
- os: windows-latest
runtime: bun
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: node-${{ matrix.os }}-${{ matrix.runtime }}
- name: Setup Node.js workspace
if: matrix.runtime == 'node'
uses: xberg-io/actions/setup-node-workspace@v1
- name: Setup Bun
if: matrix.runtime == 'bun'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest
- name: Build NAPI-RS Bindings (Node.js)
if: matrix.runtime == 'node'
uses: xberg-io/actions/build-node-napi@v1
with:
crate-dir: crates/html-to-markdown-node
- name: Install workspace dependencies (Bun)
if: matrix.runtime == 'bun'
run: bun install
shell: bash
- name: Build NAPI-RS Bindings (Bun)
if: matrix.runtime == 'bun'
working-directory: crates/html-to-markdown-node
run: bun run build
shell: bash
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
# ~keep The musl platform packages are cross-compiled through zig and were only ever built at
# publish time, where both legs failed and skipped the whole npm publish. This mirrors the
# publish job's steps so a broken cross-compile surfaces on the commit that causes it.
build-node-musl:
needs: [changes]
name: "Build: Node (${{ matrix.target }}, zig cross-compile)"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.node == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
target: [x86_64-unknown-linux-musl, aarch64-unknown-linux-musl]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
target: ${{ matrix.target }}
cache-key-prefix: node-musl-${{ matrix.target }}
configure-musl-cc: "false"
- name: Setup Node.js workspace
uses: xberg-io/actions/setup-node-workspace@v1
with:
node-version: "24"
- name: Setup Zig
uses: xberg-io/actions/setup-zig@v1
with:
version: "0.16.0"
- name: Install cargo-zigbuild
shell: bash
run: command -v cargo-zigbuild || cargo install --locked cargo-zigbuild
- name: Build native module
shell: bash
working-directory: crates/html-to-markdown-node
run: |
set -euo pipefail
rm -rf npm
pnpm exec napi create-npm-dirs
pnpm exec napi build --platform --release --target "${{ matrix.target }}" -x \
--js index.js --dts index.d.ts
pnpm exec napi artifacts --output-dir . --npm-dir npm
- name: Verify the platform package holds a native module
shell: bash
working-directory: crates/html-to-markdown-node
run: |
set -euo pipefail
node_file=$(find npm -maxdepth 2 -name "*.node" -type f | head -1)
if [ -z "${node_file}" ]; then
echo "::error::No .node file found under npm/ for ${{ matrix.target }}"
find npm -maxdepth 2 -type f
exit 1
fi
file "${node_file}"
echo "Verified: $(basename "$(dirname "${node_file}")") contains $(basename "${node_file}")"
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
# ~keep aarch64-unknown-linux-gnu is cross-compiled through the docker-based
# @napi-rs/cross-toolchain (napi build --use-napi-cross), a different code path
# than the host-native builds above and the zig cross-compile below. It was only
# ever exercised at publish time (same publish-only-leg class as the musl job
# above) — mirror it here so a broken cross-toolchain surfaces on the commit
# that causes it instead of at tag time.
build-node-linux-arm64-gnu:
needs: [changes]
name: "Build: Node (aarch64-unknown-linux-gnu, napi-cross)"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.node == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
target: aarch64-unknown-linux-gnu
cache-key-prefix: node-linux-arm64-gnu
- name: Setup Node.js workspace
uses: xberg-io/actions/setup-node-workspace@v1
with:
node-version: "24"
- name: Build native module (napi-cross, docker toolchain)
uses: xberg-io/actions/build-node-napi@v1
with:
crate-dir: crates/html-to-markdown-node
target: aarch64-unknown-linux-gnu
use-napi-cross: true
install-deps: "false"
- name: Verify the platform package holds a native module
shell: bash
working-directory: crates/html-to-markdown-node
run: |
set -euo pipefail
node_file=$(find npm -maxdepth 2 -name "*.node" -type f | head -1)
if [ -z "${node_file}" ]; then
echo "::error::No .node file found under npm/ for aarch64-unknown-linux-gnu"
find npm -maxdepth 2 -type f
exit 1
fi
file "${node_file}"
echo "Verified: $(basename "$(dirname "${node_file}")") contains $(basename "${node_file}")"
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
build-ruby:
needs: [changes]
name: "Build: Ruby (${{ matrix.os }}, ruby-${{ matrix.ruby }})"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.ruby == 'true'
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
ruby: ["3.2", "3.3"]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: ruby-${{ matrix.os }}-${{ matrix.ruby }}
- name: Setup Ruby (Unix)
if: runner.os != 'Windows'
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1
with:
ruby-version: ${{ matrix.ruby }}
bundler: "4.0.3"
bundler-cache: false
working-directory: packages/ruby
- name: Setup Ruby (Windows)
if: runner.os == 'Windows'
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1
with:
ruby-version: ${{ matrix.ruby }}
bundler: "4.0.3"
bundler-cache: false
working-directory: packages/ruby
windows-toolchain: UCRT64
- name: Build CLI binary
uses: xberg-io/actions/build-rust-cli@v1
with:
package-name: html-to-markdown-cli
binary-name: html-to-markdown
- name: Build Ruby extension
uses: xberg-io/actions/build-ruby-gem@v1
with:
# Non-release e2e CI builds the gem against the local workspace path-dep;
# skip the registry rewrite that hard-fails until core is on crates.io. ~keep
rewrite-native-deps: "false"
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
# ~keep Publish builds the PHP extension across linux-x86_64, linux-arm64,
# macos-arm64, and windows-x86_64 for every supported PHP version (16 legs
# total), but until now only ubuntu/php8.4 was ever exercised before tag
# time. Mirror the per-platform build (one php version, not the full 4x4
# matrix) so a total per-platform failure surfaces in CI instead of at
# publish. Cross-PHP-version coverage stays publish-only — see the audit.
build-php:
needs: [changes]
name: "Build: PHP (${{ matrix.platform.label }})"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.php == 'true'
runs-on: ${{ matrix.platform.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
platform:
- { os: ubuntu-latest, label: linux-x86_64 }
- { os: ubuntu-24.04-arm, label: linux-arm64 }
- { os: macos-latest, label: macos-arm64 }
- { os: windows-latest, label: windows-x86_64 }
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: "8.4"
extensions: ctype, dom, json, libxml, mbstring, tokenizer, xml, xmlwriter
tools: composer:2.9.8
coverage: none
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: php-${{ matrix.platform.label }}
- name: Capture php-config path
run: scripts/ci/php/set-php-config.sh
shell: bash
- name: Install root Composer dependencies
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0
with:
dependency-versions: locked
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GITHUB_TOKEN }}"}}'
- name: Install PHP package Composer dependencies
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0
with:
dependency-versions: locked
working-directory: crates/html-to-markdown-php/src
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GITHUB_TOKEN }}"}}'
- name: Build PHP extension
id: build-php-extension
uses: xberg-io/actions/build-php-extension@v1
with:
# Non-release e2e CI builds the extension against the local workspace path-dep;
# skip the registry rewrite that hard-fails until core is on crates.io. ~keep
rewrite-native-deps: "false"
crate-name: html-to-markdown-php
lib-name: html_to_markdown_php
- name: Upload PHP extension artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: php-extension-${{ matrix.platform.label }}
path: ${{ steps.build-php-extension.outputs.extension-path }}
retention-days: 7
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
build-csharp:
needs: [changes]
name: "Build: C# (${{ matrix.os }})"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.csharp == 'true'
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: csharp-${{ matrix.os }}
- name: Setup .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5
with:
dotnet-version: "10.0.x"
- name: Build FFI library (Unix)
if: runner.os != 'Windows'
run: cargo build --release --locked -p html-to-markdown-ffi
shell: bash
- name: Build FFI library (Windows)
if: runner.os == 'Windows'
run: cargo build --locked -p html-to-markdown-ffi
shell: bash
- name: Restore C# dependencies
run: dotnet restore packages/csharp/
shell: bash
- name: Build C# package
run: dotnet build packages/csharp/ -c Release
shell: bash
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
build-java:
needs: [changes]
name: "Build: Java (${{ matrix.os }})"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.java == 'true'
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
java: ["25"]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: java-${{ matrix.os }}
- name: Test Java Panama FFI bindings
uses: xberg-io/actions/test-java-ffi@v1
with:
ffi-crate-name: html-to-markdown-ffi
ffi-lib-name: html_to_markdown_ffi
java-version: ${{ matrix.java }}
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
build-wasm:
needs: [changes]
name: "Build: WASM"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.wasm == 'true'
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
target: wasm32-unknown-unknown
use-sccache: false
cache-key-prefix: wasm
- name: Ensure wasm target installed
run: scripts/common/ensure-wasm-target.sh
shell: bash
- name: Install wasm-pack
run: scripts/common/install-wasm-pack.sh
shell: bash
- name: Setup Node workspace
uses: xberg-io/actions/setup-node-workspace@v1
- name: Build WASM (all targets)
uses: xberg-io/actions/build-wasm-package@v1
with:
crate-dir: crates/html-to-markdown-wasm
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
test-python:
needs: [build-python]
name: "Test: Python (${{ matrix.os }}, py-${{ matrix.python }})"
if: always() && !cancelled() && needs.build-python.result != 'skipped'
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python: ["3.10", "3.12", "3.14"]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: ${{ matrix.python }}
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
components: rustfmt, clippy, llvm-tools-preview
- name: Install Python Dependencies
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4
with:
timeout_minutes: 5
max_attempts: 3
retry_wait_seconds: 30
command: |
if [[ "${{ runner.os }}" == "Windows" ]] && [[ -d ".venv" ]]; then
echo "Removing existing .venv directory on Windows"
rm -rf .venv
fi
uv sync --all-extras --no-install-project --no-install-workspace
shell: bash
- name: Build Python Bindings
run: |
uv pip install maturin
cd packages/python && uv run maturin develop --release
shell: bash
- name: Build CLI binary
run: cargo build --release --locked -p html-to-markdown-cli
shell: bash
- name: Install alef
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests
run: alef test --e2e --lang python
shell: bash
test-node:
needs: [build-node]
name: "Test: Node (${{ matrix.os }}, ${{ matrix.runtime }})"
if: always() && !cancelled() && needs.build-node.result != 'skipped'
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runtime: [node, bun]
exclude:
- os: windows-latest
runtime: bun
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
- name: Setup Node.js workspace
if: matrix.runtime == 'node'
uses: xberg-io/actions/setup-node-workspace@v1
- name: Setup Bun
if: matrix.runtime == 'bun'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest
- name: Build NAPI-RS Bindings (Node.js)
if: matrix.runtime == 'node'
uses: xberg-io/actions/build-node-napi@v1
with:
crate-dir: crates/html-to-markdown-node
- name: Install workspace dependencies (Bun)
if: matrix.runtime == 'bun'
run: bun install
shell: bash
- name: Build NAPI-RS Bindings (Bun)
if: matrix.runtime == 'bun'
working-directory: crates/html-to-markdown-node
run: bun run build
shell: bash
- name: Install alef
if: matrix.runtime == 'node'
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests (Node.js only)
if: matrix.runtime == 'node'
run: alef test --e2e --lang node
shell: bash
test-ruby:
needs: [build-ruby]
name: "Test: Ruby (${{ matrix.os }}, ruby-${{ matrix.ruby }})"
if: always() && !cancelled() && needs.build-ruby.result != 'skipped'
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
ruby: ["3.2", "3.3"]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
- name: Setup Ruby (Unix)
if: runner.os != 'Windows'
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1
with:
ruby-version: ${{ matrix.ruby }}
bundler: "4.0.3"
bundler-cache: false
working-directory: packages/ruby
- name: Setup Ruby (Windows)
if: runner.os == 'Windows'
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1
with:
ruby-version: ${{ matrix.ruby }}
bundler: "4.0.3"
bundler-cache: false
working-directory: packages/ruby
windows-toolchain: UCRT64
- name: Build CLI binary
uses: xberg-io/actions/build-rust-cli@v1
with:
package-name: html-to-markdown-cli
binary-name: html-to-markdown
- name: Build Ruby extension
uses: xberg-io/actions/build-ruby-gem@v1
with:
# Non-release e2e CI builds the gem against the local workspace path-dep;
# skip the registry rewrite that hard-fails until core is on crates.io. ~keep
rewrite-native-deps: "false"
- name: Run Rubocop (Ubuntu/ruby-3.3 only)
if: runner.os != 'Windows' && matrix.os == 'ubuntu-latest' && matrix.ruby == '3.3'
run: ./scripts/ci/ruby/run-rubocop.sh
shell: bash
- name: Validate RBS signatures (Ubuntu/ruby-3.3 only)
if: runner.os != 'Windows' && matrix.os == 'ubuntu-latest' && matrix.ruby == '3.3'
run: ./scripts/ci/ruby/run-rbs-validate.sh
shell: bash
- name: Run Steep type checking (Ubuntu/ruby-3.3 only)
if: runner.os != 'Windows' && matrix.os == 'ubuntu-latest' && matrix.ruby == '3.3'
working-directory: packages/ruby
run: ../../scripts/ci/ruby/run-steep.sh
shell: bash
- name: Run Ruby specs (Unix)
if: runner.os != 'Windows'
working-directory: packages/ruby
run: ../../scripts/ci/ruby/run-rspec-unix.sh
shell: bash
- name: Run Ruby specs (Windows)
if: runner.os == 'Windows'
working-directory: packages/ruby
shell: pwsh
run: ../../scripts/ci/ruby/run-rspec-windows.ps1
- name: Install Task
if: runner.os != 'Windows'
uses: xberg-io/actions/install-task@v1
- name: Install alef
if: runner.os != 'Windows'
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests (Unix only)
if: runner.os != 'Windows'
run: alef test --e2e --lang ruby
shell: bash
test-php:
needs: [build-php]
name: "Test: PHP"
if: always() && !cancelled() && needs.build-php.result != 'skipped'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: "8.4"
extensions: ctype, dom, json, libxml, mbstring, tokenizer, xml, xmlwriter
tools: composer:2.9.8
coverage: none
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
- name: Capture php-config path
run: scripts/ci/php/set-php-config.sh
shell: bash
- name: Install root Composer dependencies
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0
with:
dependency-versions: locked
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GITHUB_TOKEN }}"}}'
- name: Install PHP package Composer dependencies
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0
with:
dependency-versions: locked
working-directory: crates/html-to-markdown-php/src
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GITHUB_TOKEN }}"}}'
- name: Build PHP extension
id: build-php-extension
uses: xberg-io/actions/build-php-extension@v1
with:
# Non-release e2e CI builds the extension against the local workspace path-dep;
# skip the registry rewrite that hard-fails until core is on crates.io. ~keep
rewrite-native-deps: "false"
crate-name: html-to-markdown-php
lib-name: html_to_markdown_php
- name: Run PHP tests
run: scripts/ci/php/run-php-tests.sh
shell: bash
env:
EXTENSION_PATH: ${{ steps.build-php-extension.outputs.extension-path }}
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Install alef
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests
run: alef test --e2e --lang php
shell: bash
env:
EXTENSION_PATH: ${{ steps.build-php-extension.outputs.extension-path }}
test-csharp:
needs: [build-csharp, build-ffi, changes]
name: "Test: C# (${{ matrix.os }})"
if: |
always() && !cancelled() && needs.build-csharp.result != 'skipped' &&
(github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.csharp == 'true' ||
needs.changes.outputs.ffi == 'true' ||
needs.changes.outputs.core == 'true')
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: csharp-test-${{ matrix.os }}
- name: Setup .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5
with:
dotnet-version: "10.0.x"
- name: Build FFI library (Unix)
if: runner.os != 'Windows'
run: cargo build --release --locked -p html-to-markdown-ffi
shell: bash
- name: Build FFI library (Windows)
if: runner.os == 'Windows'
run: cargo build --locked -p html-to-markdown-ffi
shell: bash
- name: Restore C# dependencies
run: dotnet restore packages/csharp/
shell: bash
- name: Build C# package
run: dotnet build packages/csharp/ -c Release
shell: bash
- name: Install alef
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests (Unix)
if: runner.os != 'Windows'
run: alef test --e2e --lang csharp
shell: bash
env:
LD_LIBRARY_PATH: ${{ github.workspace }}/target/release:${{ github.workspace }}/target/debug
DYLD_LIBRARY_PATH: ${{ github.workspace }}/target/release:${{ github.workspace }}/target/debug
- name: Run E2E tests (Windows)
if: runner.os == 'Windows'
run: |
export PATH="${{ github.workspace }}/target/release:${{ github.workspace }}/target/debug:$PATH"
alef test --e2e --lang csharp
shell: bash
- name: Cleanup Rust cache
if: always()
uses: xberg-io/actions/cleanup-rust-cache@v1
test-go:
needs: [build-ffi, changes]
name: "Test: Go"
if: |
always() && !cancelled() && needs.build-ffi.result != 'skipped' &&
(github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.go == 'true' ||
needs.changes.outputs.ffi == 'true' ||
needs.changes.outputs.core == 'true')
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
- name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Build FFI library
run: cargo build --release --locked -p html-to-markdown-ffi
shell: bash
- name: Detect Go modules
id: set-modules
shell: bash
run: scripts/ci/go/detect-go-modules.sh
- name: Install golangci-lint
if: steps.set-modules.outputs.modules != '[]'
env:
GOTOOLCHAIN: ${{ env.GO_TOOLCHAIN }}
run: scripts/ci/go/install-golangci-lint.sh
shell: bash
- name: Run golangci-lint (all modules)
if: steps.set-modules.outputs.modules != '[]'
shell: bash
run: |
for module in $(echo '${{ steps.set-modules.outputs.modules }}' | jq -r '.[]'); do
echo "=== Linting $module ==="
(cd "$module" && "${{ github.workspace }}/scripts/ci/go/run-golangci-lint.sh")
done
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Install alef
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests
run: alef test --e2e --lang go
shell: bash
test-java:
needs: [build-java]
name: "Test: Java (${{ matrix.os }})"
if: always() && !cancelled() && needs.build-java.result != 'skipped'
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
java: ["25"]
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
- name: Test Java Panama FFI bindings
uses: xberg-io/actions/test-java-ffi@v1
with:
ffi-crate-name: html-to-markdown-ffi
ffi-lib-name: html_to_markdown_ffi
java-version: ${{ matrix.java }}
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Install alef
if: matrix.os != 'windows-latest'
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests (Linux + macOS)
if: matrix.os != 'windows-latest'
run: alef test --e2e --lang java
shell: bash
test-elixir:
needs: [build-ffi, changes]
name: "Test: Elixir"
if: |
always() && !cancelled() && needs.build-ffi.result != 'skipped' &&
(github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.elixir == 'true' ||
needs.changes.outputs.ffi == 'true' ||
needs.changes.outputs.core == 'true')
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Elixir
uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1
with:
elixir-version: "1.19"
otp-version: "28.1"
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
- name: Install Hex/Rebar
run: scripts/ci/elixir/install-hex-rebar.sh
shell: bash
- name: Install dependencies
working-directory: packages/elixir
run: ../../scripts/ci/elixir/install-deps.sh
shell: bash
- name: Run tests
working-directory: packages/elixir
run: ../../scripts/ci/elixir/run-tests.sh
shell: bash
- name: Credo lint
working-directory: packages/elixir
run: ../../scripts/ci/elixir/run-credo.sh
shell: bash
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Install alef
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests
run: alef test --e2e --lang elixir
shell: bash
test-r:
needs: [changes]
name: "Test: R"
if: |
always() && !cancelled() &&
(github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.r == 'true' ||
needs.changes.outputs.core == 'true')
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup R
uses: xberg-io/actions/setup-r@v1
with:
install-deps-script: scripts/ci/r/install-deps.sh
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
- name: Run tests
working-directory: packages/r
run: ../../scripts/ci/r/run-tests.sh
shell: bash
- name: Run lintr
working-directory: packages/r
run: ../../scripts/ci/r/run-lintr.sh
shell: bash
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Install alef
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests
run: alef test --e2e --lang r
shell: bash
test-c-ffi:
needs: [build-ffi, changes]
name: "Test: C FFI (${{ matrix.runner }})"
if: |
always() && !cancelled() && needs.build-ffi.result != 'skipped' &&
(github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.ffi == 'true' ||
needs.changes.outputs.core == 'true')
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
runner:
- ubuntu-latest
- ubuntu-24.04-arm
- macos-latest
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: c-ffi-${{ matrix.runner }}
use-sccache: false
- name: Build html-to-markdown-ffi
shell: bash
run: cargo build --release --locked -p html-to-markdown-ffi
- name: Run C e2e tests
shell: bash
env:
LD_LIBRARY_PATH: ${{ github.workspace }}/target/release
DYLD_LIBRARY_PATH: ${{ github.workspace }}/target/release
run: cd e2e/c && make test
- name: Verify header is fresh
shell: bash
run: |
HEADER="crates/html-to-markdown-ffi/include/html_to_markdown.h"
test -f "$HEADER"
git diff --exit-code -- "$HEADER" || {
echo "::error::$HEADER is stale — cbindgen regenerated a different header than the one committed. Run 'cargo build -p html-to-markdown-ffi' and commit the result."
exit 1
}
echo "Header verified fresh: $HEADER"
- name: Verify pkg-config output
shell: bash
run: |
PC_DIR="$(pwd)/target/release/build"
PC_FILE=$(find "$PC_DIR" -name 'html-to-markdown.pc' -path '*/html-to-markdown-ffi-*/out/*' 2>/dev/null | head -1)
if [ -z "$PC_FILE" ]; then
echo "Warning: html-to-markdown.pc not found in build output"
find "$PC_DIR" -name '*.pc' 2>/dev/null || echo "No .pc files found"
else
echo "Found pkg-config file: $PC_FILE"
cat "$PC_FILE"
fi
test-c-ffi-windows:
needs: [build-ffi, changes]
name: "Test: C FFI (windows-latest)"
if: |
always() && !cancelled() && needs.build-ffi.result != 'skipped' &&
(github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.ffi == 'true' ||
needs.changes.outputs.core == 'true')
runs-on: windows-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: c-ffi-windows
use-sccache: false
- name: Build html-to-markdown-ffi
shell: bash
run: cargo build --locked -p html-to-markdown-ffi
- name: Verify header is fresh
shell: bash
run: |
HEADER="crates/html-to-markdown-ffi/include/html_to_markdown.h"
test -f "$HEADER"
git diff --exit-code -- "$HEADER" || {
echo "::error::$HEADER is stale — cbindgen regenerated a different header than the one committed. Run 'cargo build -p html-to-markdown-ffi' and commit the result."
exit 1
}
echo "Header verified fresh on Windows: $HEADER"
test-wasm:
needs: [build-wasm]
name: "Test: WASM"
if: always() && !cancelled() && needs.build-wasm.result != 'skipped'
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
target: wasm32-unknown-unknown
use-sccache: false
- name: Ensure wasm target installed
run: scripts/common/ensure-wasm-target.sh
shell: bash
- name: Install wasm-pack
run: scripts/common/install-wasm-pack.sh
shell: bash
- name: Setup Node workspace
uses: xberg-io/actions/setup-node-workspace@v1
- name: Build WASM (all targets)
uses: xberg-io/actions/build-wasm-package@v1
with:
crate-dir: crates/html-to-markdown-wasm
- name: Test WASM bundle
working-directory: crates/html-to-markdown-wasm
run: ../../scripts/ci/wasm/test-wasm-bundle.sh
shell: bash
- name: Run Rust WASM tests
working-directory: crates/html-to-markdown-wasm
run: ../../scripts/ci/wasm/test-wasm-rust.sh
shell: bash
build-kotlin-android:
needs: [changes]
name: "Build: Kotlin Android"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.ffi == 'true' ||
needs.changes.outputs.kotlin == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
targets: aarch64-linux-android,x86_64-linux-android
cache-key-prefix: kotlin-android
- name: Setup Android SDK/NDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4
# ~keep Bundle the JNI crate (libhtm_jni.so) that the Kotlin bridge loads via
# System.loadLibrary("htm_jni") — NOT the plain C-FFI crate, whose lib*_ffi.so
# lacks the JNI entry points (html-to-markdown#446). This mirrors the exact
# crate/lib pair the publish workflow's kotlin-android-natives job builds, so a
# regression in the JNI crate surfaces here instead of only at publish time.
- name: Build Android native libraries
uses: xberg-io/actions/build-android-natives@v1
with:
crate-name: html-to-markdown-rs-jni
lib-name: htm_jni
abis: arm64-v8a,x86_64
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
# ~keep Kotlin/Android ships to Maven Central but was never compiled or tested in
# CI (html-to-markdown audit #38) — build-kotlin-android above only cross-compiles
# the native .so, it never invokes Gradle/Kotlin compilation. `gradle test` here
# compiles the Kotlin facade against the host JVM and runs the e2e fixture suite
# via a host-platform JNI build (see packages/kotlin-android/build.gradle.kts'
# buildHostJni/copyHostJni tasks) — no Android emulator required.
test-kotlin-android:
needs: [changes]
name: "Test: Kotlin Android (host JVM)"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.ffi == 'true' ||
needs.changes.outputs.kotlin == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
id: checkout
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: kotlin-android-test
- name: Setup Android SDK/NDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4
- name: Setup JDK
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
java-version: "17"
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Install alef
uses: xberg-io/actions/install-alef@v1
- name: Compile and test Kotlin Android binding (host JVM)
run: alef test --e2e --lang kotlin_android
shell: bash
- name: Cleanup Rust cache
if: always() && steps.checkout.outcome == 'success'
uses: xberg-io/actions/cleanup-rust-cache@v1
test-swift:
needs: [changes]
name: "Test: Swift"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.swift == 'true'
runs-on: macos-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: swift
- name: Setup Swift
uses: xberg-io/actions/setup-swift@v1
with:
version: "6.0"
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: "8.3"
- name: Install alef
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests
run: alef test --e2e --lang swift
shell: bash
- name: Cleanup Rust cache
if: always()
uses: xberg-io/actions/cleanup-rust-cache@v1
test-dart:
needs: [changes]
name: "Test: Dart"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.dart == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: dart
- name: Setup Dart
uses: dart-lang/setup-dart@7654d458321ee25acccccfdb86cd48bd95768ff1 # v1
- name: Install alef
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests
run: alef test --e2e --lang dart
shell: bash
- name: Cleanup Rust cache
if: always()
uses: xberg-io/actions/cleanup-rust-cache@v1
test-zig:
needs: [changes]
name: "Test: Zig"
if: |
github.event_name == 'workflow_dispatch' ||
needs.changes.outputs.core == 'true' ||
needs.changes.outputs.ffi == 'true' ||
needs.changes.outputs.zig == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Install Task
uses: xberg-io/actions/install-task@v1
- name: Setup Rust
uses: xberg-io/actions/setup-rust@v1
with:
cache-key-prefix: zig
- name: Setup Zig
uses: xberg-io/actions/setup-zig@v1
with:
version: "0.16.0"
- name: Install alef
uses: xberg-io/actions/install-alef@v1
- name: Run E2E tests
run: alef test --e2e --lang zig
shell: bash
- name: Cleanup Rust cache
if: always()
uses: xberg-io/actions/cleanup-rust-cache@v1