5b23b36261
* ci: use npm ci instead of npm install for reproducible installs Pins the node binding dependency install to the committed package-lock.json integrity hashes (OpenSSF Scorecard PinnedDependencies). npm ci installs strictly from the lockfile; npm install could resolve newer patch versions. Covers ci.yml and both release.yml node steps. * ci: hash-pin Python dev tooling in ci.yml (Scorecard #19) Replaces the unpinned 'pip install maturin pytest numpy hypothesis' with a hash-locked '--require-hashes -r' install (OpenSSF Scorecard PinnedDependencies). Two lock files are needed because numpy publishes no single release with wheels for both cp39 and cp313 (<=2.0.2 has cp39 only, >=2.1 drops cp39): ci-dev-py39.txt numpy 2.0.2 (Python 3.9, + tomli/exceptiongroup) ci-dev-py3.txt numpy 2.4.6 (Python 3.10+) The step selects the file by matrix.python-version under shell: bash. Both are generated from ci-dev.in via uv (scripts/update-lockfiles.sh, added next). * ci: hash-pin Python deps in bench.yml (Scorecard #16) Replaces the unpinned 'pip install maturin numpy pandas talipp finta' with a hash-locked '--require-hashes -r .github/requirements/bench.txt' install. bench.yml runs on a single Python version (3.11), so one lock file (generated from bench.in via uv) is sufficient. * build: add scripts/update-lockfiles.sh to regenerate all lockfiles One command refreshes every committed lockfile across languages: Cargo.lock and fuzz/Cargo.lock (cargo update), the Node binding package-lock.json, and the hash-pinned Python requirements under .github/requirements/ (uv pip compile --generate-hashes). Uses uv for the Python locks so a target Python version's hashed transitive closure can be resolved without that interpreter installed (needed for the numpy cp39/cp313 split); bootstraps uv if absent. .gitattributes pins *.sh to LF so the script stays runnable on Linux/macOS. * ci: split ci-dev requirements per Python version + Dependabot rehash Splits the single ci-dev.in into ci-dev-py39.in (numpy <2.1, the last series with cp39 wheels) and ci-dev-py3.in (3.10+), giving a 1:1 .in->.txt layout. The cap keeps Python 3.9 permanently installable and stops Dependabot from proposing 3.9-breaking numpy bumps. Adds a Dependabot pip entry on /.github/requirements so the hash-locked tooling is kept current automatically; the canonical manual refresh stays scripts/update-lockfiles.sh. Only the '# via -r' provenance lines in the .txt change; no package versions or hashes move. * ci: cache pip and npm downloads in the PR-loop jobs Adds setup-python cache: pip (ci.yml python matrix + bench.yml, keyed on the hash-locked requirements) and setup-node cache: npm (ci.yml node job, keyed on bindings/node/package-lock.json), on both the primary and retry setup steps. Scoped to jobs that actually install dependencies; the examples-smoke and clippy-bindings jobs install nothing and are left uncached. release.yml is intentionally left out: it runs only on tag push (not the PR loop) and is the publish-critical path, so no caching is added there. * docs: document hash-pinned requirements and update-lockfiles.sh Updates the lockfile-policy table: the bindings/python row no longer claims CI installs tooling unpinned, and a new .github/requirements row documents the hash-locked CI/bench tooling and the per-Python-version ci-dev split. Adds a paragraph pointing contributors at scripts/update-lockfiles.sh (uv-based, self-bootstrapping) as the canonical lockfile refresh. * docs: changelog entry for hash-pinned CI dependency installs
567 lines
22 KiB
YAML
567 lines
22 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
# Least-privilege default for the auto-injected GITHUB_TOKEN. None of the CI
|
|
# jobs write back to the repo — coverage uploads via CODECOV_TOKEN, everything
|
|
# else is build/test/lint — so a read-only token is sufficient (OpenSSF
|
|
# Scorecard: Token-Permissions).
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
RUSTFLAGS: "-D warnings"
|
|
# Network-flake resilience: retry transient registry/DNS failures at the tool
|
|
# level so a blip fetching crates.io / npm / PyPI inside any build step (cargo,
|
|
# napi, maturin, wasm-pack, npm ci, pip) retries automatically instead of
|
|
# failing the job and needing a manual re-run. Cargo treats "couldn't resolve
|
|
# host" / connect / timeout as spurious and retries with backoff; 10 attempts
|
|
# ride out a transient DNS blip on a runner. Complements the setup-action /
|
|
# cache retries (which only covered toolchain download + cache restore).
|
|
CARGO_NET_RETRY: "10"
|
|
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
|
npm_config_fetch_retries: "5"
|
|
npm_config_fetch_retry_maxtimeout: "120000"
|
|
PIP_RETRIES: "5"
|
|
PIP_DEFAULT_TIMEOUT: "120"
|
|
|
|
jobs:
|
|
rust:
|
|
name: Rust ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
components: rustfmt, clippy
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Format check
|
|
run: cargo fmt --all -- --check
|
|
|
|
- name: Clippy (workspace, all targets)
|
|
run: cargo clippy -p wickra-core -p wickra -p wickra-data -p wickra-wasm --all-targets -- -D warnings
|
|
|
|
- name: Build
|
|
run: cargo build -p wickra-core -p wickra -p wickra-data --verbose
|
|
|
|
- name: Tests (default features)
|
|
run: cargo test -p wickra-core -p wickra -p wickra-data --verbose
|
|
|
|
- name: Tests (live-binance feature)
|
|
run: cargo test -p wickra-data --features live-binance --verbose
|
|
|
|
- name: Compile benches (smoke)
|
|
run: cargo build -p wickra --benches --verbose
|
|
|
|
- name: Compile examples
|
|
# All runnable examples now live in the dedicated wickra-examples crate
|
|
# (examples/rust/src/bin/*.rs) which enables the live-binance feature
|
|
# on its wickra-data dep, so a single --bins build covers backtest,
|
|
# live_binance, fetch_btcusdt, multi_timeframe, parallel_assets and
|
|
# streaming.
|
|
run: cargo build -p wickra-examples --bins
|
|
|
|
# Syntax/parse smoke for the non-Rust examples. The Rust examples are built
|
|
# in the `rust` job above (`cargo build -p wickra-examples --bins`); the Node,
|
|
# browser-WASM and Python examples otherwise have no build gate, so a broken
|
|
# edit could land unnoticed. This is a parse-only smoke — actually running the
|
|
# examples needs the built native binding / wasm module / wheel, which the
|
|
# binding jobs provide separately.
|
|
examples-smoke:
|
|
name: Examples (syntax smoke)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Set up Node
|
|
id: setup_node
|
|
continue-on-error: true
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: "20"
|
|
|
|
- name: Wait before Node retry
|
|
if: steps.setup_node.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-node failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Node (retry)
|
|
if: steps.setup_node.outcome == 'failure'
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: "20"
|
|
|
|
- name: Set up Python
|
|
id: setup_python
|
|
continue-on-error: true
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Wait before Python retry
|
|
if: steps.setup_python.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-python failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Python (retry)
|
|
if: steps.setup_python.outcome == 'failure'
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Node examples — syntax check
|
|
run: |
|
|
shopt -s nullglob
|
|
count=0
|
|
for f in examples/node/*.js examples/wasm/*.js; do
|
|
echo "node --check $f"
|
|
node --check "$f"
|
|
count=$((count + 1))
|
|
done
|
|
echo "checked $count Node/WASM .js files"
|
|
|
|
- name: WASM demo module scripts — syntax check
|
|
# The .html demos embed an ES module; extract it and parse-check so a
|
|
# broken edit to the in-page strategy logic fails CI.
|
|
run: |
|
|
shopt -s nullglob
|
|
count=0
|
|
for f in examples/wasm/*.html; do
|
|
node -e 'const fs=require("fs");const h=fs.readFileSync(process.argv[1],"utf8");const m=h.match(/<script type="module">([\s\S]*?)<\/script>/);if(!m){console.error("no <script type=module> in "+process.argv[1]);process.exit(1);}fs.writeFileSync("module-check.mjs",m[1]);' "$f"
|
|
echo "node --check (module of) $f"
|
|
node --check module-check.mjs
|
|
count=$((count + 1))
|
|
done
|
|
rm -f module-check.mjs
|
|
echo "checked $count WASM .html module scripts"
|
|
|
|
- name: Python examples — byte-compile
|
|
run: |
|
|
shopt -s nullglob
|
|
count=0
|
|
for f in examples/python/*.py; do
|
|
echo "py_compile $f"
|
|
python -m py_compile "$f"
|
|
count=$((count + 1))
|
|
done
|
|
echo "compiled $count Python files"
|
|
|
|
# Clippy for the Python and Node bindings. These are kept out of the main
|
|
# `rust` job because PyO3 / napi build scripts need a Python interpreter and
|
|
# a Node toolchain on PATH, which the 3-OS matrix job does not provision.
|
|
# Ubuntu-only is sufficient: the lints are platform-independent.
|
|
clippy-bindings:
|
|
name: Clippy bindings
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
components: clippy
|
|
|
|
- name: Set up Python
|
|
id: setup_python
|
|
continue-on-error: true
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Wait before Python retry
|
|
if: steps.setup_python.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-python failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Python (retry)
|
|
if: steps.setup_python.outcome == 'failure'
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Set up Node
|
|
id: setup_node
|
|
continue-on-error: true
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: "20"
|
|
|
|
- name: Wait before Node retry
|
|
if: steps.setup_node.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-node failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Node (retry)
|
|
if: steps.setup_node.outcome == 'failure'
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: "20"
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Clippy (bindings, all targets)
|
|
run: cargo clippy -p wickra-node -p wickra-python --all-targets -- -D warnings
|
|
|
|
# Verify the crates still build and test on their declared minimum supported
|
|
# Rust version. The workspace pins rust-version = "1.86" — that floor is
|
|
# set by criterion 0.8.2 (the bench dev-dep), which itself rolled past the
|
|
# clap_lex 1.1.0 / edition2024 / Rust 1.85 floor and now needs 1.86; the
|
|
# earlier rayon-core 1.13.0 (1.80) and clap_lex (1.85) requirements are
|
|
# subsumed. bindings/node pins rust-version = "1.88" because napi-build
|
|
# 2.3.2 requires it (and that subsumes the older 1.77 floor needed for
|
|
# `cargo::` directives). Without this job an accidental use of a newer
|
|
# API would only surface for downstream users.
|
|
msrv:
|
|
name: ${{ matrix.name }}
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- name: MSRV workspace (Rust 1.86)
|
|
toolchain: "1.86"
|
|
packages: "-p wickra-core -p wickra -p wickra-data"
|
|
- name: MSRV node binding (Rust 1.88)
|
|
toolchain: "1.88"
|
|
packages: "-p wickra-node"
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Install Rust ${{ matrix.toolchain }}
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
toolchain: ${{ matrix.toolchain }}
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Build on MSRV
|
|
run: cargo build ${{ matrix.packages }} --verbose
|
|
|
|
- name: Test on MSRV
|
|
run: cargo test ${{ matrix.packages }} --verbose
|
|
|
|
# Code coverage for the pure-Rust crates, uploaded to Codecov.
|
|
coverage:
|
|
name: Coverage
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
components: llvm-tools-preview
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Install cargo-llvm-cov
|
|
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
|
|
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
|
with:
|
|
tool: cargo-llvm-cov
|
|
|
|
- name: Generate coverage (lcov)
|
|
run: >
|
|
cargo llvm-cov
|
|
-p wickra-core -p wickra -p wickra-data
|
|
--features wickra-data/live-binance
|
|
--lcov --output-path lcov.info
|
|
|
|
- name: Upload to Codecov
|
|
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
|
with:
|
|
files: lcov.info
|
|
fail_ci_if_error: false
|
|
env:
|
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
|
|
# Supply-chain audit: security advisories, license policy, banned crates,
|
|
# and source restrictions. Configured by deny.toml at the repo root.
|
|
supply-chain:
|
|
name: Supply-chain (cargo-deny)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: cargo-deny
|
|
uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2.0.20
|
|
with:
|
|
command: check
|
|
|
|
# Time-boxed fuzz smoke. Each target runs for ~30 s with libfuzzer; any panic
|
|
# fails the job. The goal is to catch a regression in the harness (e.g. a
|
|
# newly added indicator that panics on a particular input shape), not to
|
|
# discover novel bugs — long fuzz campaigns should be run on dedicated
|
|
# infrastructure with persistent corpora.
|
|
fuzz-smoke:
|
|
name: Fuzz (smoke)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Install nightly Rust
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
toolchain: nightly
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
with:
|
|
workspaces: fuzz
|
|
|
|
- name: Install cargo-fuzz
|
|
# Use the prebuilt binary from taiki-e/install-action instead of
|
|
# `cargo install cargo-fuzz --locked`. The latter resolves the
|
|
# version graph from cargo-fuzz's own Cargo.lock, which pins to
|
|
# rustix 0.36.5 — a version that still uses internal `rustc_*`
|
|
# attributes the modern nightly compiler rejects, so the install
|
|
# never gets off the ground. The prebuilt binary avoids the entire
|
|
# transitive-dep compile.
|
|
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
|
|
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
|
with:
|
|
tool: cargo-fuzz
|
|
|
|
- name: Fuzz csv_reader (30 s)
|
|
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu csv_reader -- -max_total_time=30
|
|
working-directory: fuzz
|
|
|
|
- name: Fuzz binance_envelope (30 s)
|
|
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu binance_envelope -- -max_total_time=30
|
|
working-directory: fuzz
|
|
|
|
- name: Fuzz indicator_update (30 s)
|
|
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu indicator_update -- -max_total_time=30
|
|
working-directory: fuzz
|
|
|
|
- name: Fuzz indicator_update_candle (30 s)
|
|
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu indicator_update_candle -- -max_total_time=30
|
|
working-directory: fuzz
|
|
|
|
- name: Fuzz tick_aggregator (30 s)
|
|
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu tick_aggregator -- -max_total_time=30
|
|
working-directory: fuzz
|
|
|
|
python:
|
|
name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
python-version: ["3.9", "3.11", "3.12", "3.13"]
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
# setup-python downloads the interpreter from the Actions tool cache /
|
|
# nodejs CDN and occasionally hangs or 5xx's on the Windows runners.
|
|
# Run it with continue-on-error, then retry once after a backoff so a
|
|
# single CDN flake does not fail the whole job (see also: GitHub
|
|
# Actions runner-images#7061).
|
|
- name: Set up Python
|
|
id: setup_python
|
|
continue-on-error: true
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
cache: pip
|
|
cache-dependency-path: .github/requirements/ci-dev-*.txt
|
|
|
|
- name: Wait before Python retry
|
|
if: steps.setup_python.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-python failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Python (retry)
|
|
if: steps.setup_python.outcome == 'failure'
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
cache: pip
|
|
cache-dependency-path: .github/requirements/ci-dev-*.txt
|
|
|
|
- name: Install Python dev dependencies
|
|
shell: bash
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
# Hash-locked dev tooling (OpenSSF Scorecard PinnedDependencies).
|
|
# Split by Python version: numpy ships no single release with wheels
|
|
# for both cp39 and cp313 (<=2.0.2 has cp39 only, >=2.1 drops cp39).
|
|
if [ "${{ matrix.python-version }}" = "3.9" ]; then
|
|
python -m pip install --require-hashes -r .github/requirements/ci-dev-py39.txt
|
|
else
|
|
python -m pip install --require-hashes -r .github/requirements/ci-dev-py3.txt
|
|
fi
|
|
|
|
- name: Build wheel
|
|
working-directory: bindings/python
|
|
run: maturin build --release --out dist
|
|
|
|
- name: Install wheel
|
|
shell: bash
|
|
working-directory: bindings/python
|
|
# --no-index forces pip to ignore PyPI; --no-deps skips re-resolving
|
|
# numpy (already installed in the previous step). Without --no-index
|
|
# pip prefers the PyPI 0.2.x wheel over our freshly built one when
|
|
# platform tags overlap (e.g. macOS arm64), so tests would run
|
|
# against the released package and miss any new symbols the PR adds.
|
|
run: python -m pip install --no-index --find-links dist --force-reinstall --no-deps wickra
|
|
|
|
- name: Run Python tests
|
|
working-directory: bindings/python
|
|
run: pytest -v
|
|
|
|
wasm:
|
|
name: WASM build
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Install Rust toolchain (with wasm target)
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
targets: wasm32-unknown-unknown
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Install wasm-pack
|
|
# jetli/wasm-pack-action@v0.4.0 with no `version:` input installs an
|
|
# old wasm-pack (~0.10.x) whose `build` subcommand does not yet accept
|
|
# `--features`, so `wasm-pack build … --features panic-hook` fails
|
|
# with "Found argument '--features' which wasn't expected". Use the
|
|
# same taiki-e prebuilt-binary installer we already use for
|
|
# cargo-llvm-cov and cargo-fuzz; it tracks the latest wasm-pack
|
|
# release, which has `--features` as a top-level flag (since 0.12).
|
|
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
|
|
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
|
with:
|
|
tool: wasm-pack
|
|
|
|
- name: Build WASM package
|
|
run: wasm-pack build bindings/wasm --target web --release --features panic-hook
|
|
|
|
- name: Run WASM tests
|
|
run: wasm-pack test --node bindings/wasm
|
|
|
|
- name: Verify generated artefacts
|
|
run: |
|
|
test -f bindings/wasm/pkg/wickra_wasm.js
|
|
test -f bindings/wasm/pkg/wickra_wasm_bg.wasm
|
|
test -f bindings/wasm/pkg/wickra_wasm.d.ts
|
|
|
|
node:
|
|
name: Node ${{ matrix.node-version }} on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
node-version: ["18", "20"]
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
# setup-node downloads Node from nodejs.org and we've seen it fail on
|
|
# Windows runners with "Attempting to download 18..." followed by a
|
|
# silent hang or curl error. Retry once after a backoff so a single
|
|
# CDN flake does not fail the whole job.
|
|
- name: Set up Node
|
|
id: setup_node
|
|
continue-on-error: true
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: ${{ matrix.node-version }}
|
|
cache: npm
|
|
cache-dependency-path: bindings/node/package-lock.json
|
|
|
|
- name: Wait before Node retry
|
|
if: steps.setup_node.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-node failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Node (retry)
|
|
if: steps.setup_node.outcome == 'failure'
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: ${{ matrix.node-version }}
|
|
cache: npm
|
|
cache-dependency-path: bindings/node/package-lock.json
|
|
|
|
- name: Install Node dependencies
|
|
working-directory: bindings/node
|
|
run: npm ci
|
|
|
|
- name: Build native module
|
|
working-directory: bindings/node
|
|
# --platform puts the target triple into the filename so the loader's
|
|
# `wickra.<target>.node` lookup finds the freshly built binary instead
|
|
# of falling back to the per-platform npm subpackage (which doesn't
|
|
# exist yet for win32-x64-msvc).
|
|
run: npx napi build --platform --release
|
|
|
|
- name: Run Node tests
|
|
working-directory: bindings/node
|
|
run: node --test __tests__/
|
|
|
|
# The cross-library benchmark has moved to a dedicated scheduled workflow
|
|
# (.github/workflows/bench.yml) — see audit finding R10. It runs nightly
|
|
# at 03:00 UTC and on-demand via `workflow_dispatch`, and is no longer on
|
|
# the every-push / every-PR critical path.
|