mirror of
https://git.pumpin.online/funkpower/typikon.git
synced 2026-10-22 18:57:13 +00:00
WebAssembly rather than a native Node addon: one artifact serves Node, Deno, Bun, a browser and anything built on them, where napi would have meant a prebuilt binary per platform and a source build for whoever the matrix missed. The engine is pure computation and the menologion is already compiled in, so there is nothing for it to give up. A day comes back as a plain object, not a handle. A wasm class would have meant a .free() that every caller eventually forgets, and this way JSON.stringify works and the thing spreads into state. Two serializer defaults had to be overridden to keep the promise that it is the same document --json emits: maps as objects, and an absent field as null rather than undefined, which stringify would otherwise drop entirely. The TypeScript is hand-written, because the generated types for a value crossing as JsValue are `any`, and a binding that types its whole return as `any` is a binding TypeScript cannot check. tsc --strict is a release gate now, the way mypy --strict already is, and the check includes three lines that must fail to compile. The one place this differs from every other binding: no remembered default jurisdiction, because a browser has nowhere to keep one. It is called out in the README, in the types and in docs/versioning.md. The test resolves typikon through a staged node_modules rather than a relative path, so what it exercises is the exports map, and it compares a year of days field by field against the command line for four jurisdictions.
122 lines
5.7 KiB
YAML
122 lines
5.7 KiB
YAML
name: ci
|
|
|
|
# Deliberately not triggered by pull_request.
|
|
#
|
|
# The forge this runs on is public and takes pull requests from strangers, and a
|
|
# runner executes whatever the workflow checks out. Triggering on pull_request
|
|
# would mean a stranger's first contribution runs code on the runner host before
|
|
# anyone has read it. Triggering only on push means the runner only ever executes
|
|
# code that someone with push rights put there.
|
|
#
|
|
# The cost is that a pull request gets no automatic check. Run it by pushing the
|
|
# branch yourself once you have read the diff.
|
|
on:
|
|
push:
|
|
# Branches only. A tag push does not match a branch filter, so release.yml
|
|
# owns tags and this owns everything else.
|
|
branches: ['**']
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: docker
|
|
container:
|
|
image: rust:1.98-bookworm
|
|
steps:
|
|
# Not actions/checkout. That is a JavaScript action: the runner executes it
|
|
# with `node` *inside this container*, and a Rust image carries no node, so
|
|
# the job dies at the first step with "node: executable file not found in
|
|
# $PATH". actions/cache and actions/upload-artifact are the same. Using an
|
|
# image that also carries node would mean installing Rust on every run;
|
|
# cloning by hand costs four lines and keeps the lean image.
|
|
- name: Checkout
|
|
run: |
|
|
git init -q .
|
|
git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
|
|
git fetch -q --depth 1 origin "$GITHUB_SHA"
|
|
git checkout -q FETCH_HEAD
|
|
|
|
# No `cargo fmt --check`. The tables in this crate are aligned by hand —
|
|
# MONTHS, JURISDICTIONS — and rustfmt wants one entry per line, which loses
|
|
# the shape that makes them readable. A gate that fights the house style is
|
|
# a gate that gets ignored.
|
|
- name: Lint
|
|
run: |
|
|
rustup component add clippy
|
|
cargo clippy --all-targets -- -D warnings
|
|
# The binding code only exists under the feature, so a bare clippy
|
|
# run never looks at src/python.rs.
|
|
cargo clippy --features python --all-targets -- -D warnings
|
|
cargo clippy --features capi --all-targets -- -D warnings
|
|
rustup target add wasm32-unknown-unknown
|
|
cargo clippy --lib --features wasm --target wasm32-unknown-unknown -- -D warnings
|
|
|
|
- name: Rust tests
|
|
run: |
|
|
cargo test
|
|
cargo test --features capi
|
|
|
|
# The header and the LuaJIT cdef are checked against the library by the
|
|
# Rust tests; this checks that a C compiler agrees, which they cannot.
|
|
- name: C ABI
|
|
run: |
|
|
apt-get update && apt-get install -y --no-install-recommends gcc luajit
|
|
cargo build --release --features capi
|
|
cc -Wall -Wextra -Werror -Iinclude examples/c/demo.c \
|
|
-Ltarget/release -ltypikon -o /tmp/demo
|
|
LD_LIBRARY_PATH=target/release /tmp/demo rocor
|
|
LD_LIBRARY_PATH=target/release /tmp/demo --list
|
|
TYPIKON_LIB=target/release/libtypikon.so \
|
|
luajit examples/lua/statusline.lua rocor
|
|
|
|
# The CLI and the bindings are the same engine, so they have to agree. This
|
|
# is the check that caught the bindings resolving their own default
|
|
# jurisdiction while the CLI read the configured one.
|
|
- name: Python bindings
|
|
run: |
|
|
apt-get update && apt-get install -y --no-install-recommends python3 python3-venv python3-dev
|
|
python3 -m venv /tmp/venv
|
|
/tmp/venv/bin/pip install --quiet maturin pytest mypy
|
|
cargo build --release
|
|
/tmp/venv/bin/maturin build --release --out dist
|
|
/tmp/venv/bin/pip install --quiet --no-index --find-links dist typikon
|
|
/tmp/venv/bin/python -m pytest tests/python -q
|
|
# The bindings resolve the default jurisdiction themselves, and once
|
|
# read only the config file while the CLI read the environment first.
|
|
# The two then answered for different churches on the same machine.
|
|
cli=$(TYPIKON_JURISDICTION=rocor ./target/release/typikon \
|
|
--date 2026-03-18 --format tsv --fields jurisdiction | tail -1)
|
|
py=$(TYPIKON_JURISDICTION=rocor /tmp/venv/bin/python -c "import typikon, datetime; print(typikon.describe(datetime.date(2026, 3, 18)).jurisdiction)")
|
|
test "$cli" = "$py" || { echo "cli=$cli python=$py"; exit 1; }
|
|
|
|
- name: Type stubs
|
|
run: |
|
|
cat > /tmp/typecheck.py <<'EOF'
|
|
import datetime, typikon
|
|
d: typikon.Day = typikon.describe(datetime.date(2026, 4, 12))
|
|
day: datetime.date = d.date
|
|
feast: str | None = d.feast
|
|
n: int = d.days_to_pascha
|
|
EOF
|
|
/tmp/venv/bin/python -m mypy --strict /tmp/typecheck.py
|
|
|
|
# The npm package is the same engine again, so it has to agree with the
|
|
# command line again. The test compares a year of days field by field
|
|
# for four jurisdictions, and type-checks the published .d.ts the way the
|
|
# Python bindings are type-checked.
|
|
- name: JavaScript package
|
|
run: |
|
|
apt-get update && apt-get install -y --no-install-recommends nodejs npm
|
|
version=$(awk '/^name = "wasm-bindgen"$/{getline; gsub(/[",]/,""); print $3; exit}' Cargo.lock)
|
|
curl -sSfL "https://github.com/rustwasm/wasm-bindgen/releases/download/$version/wasm-bindgen-$version-x86_64-unknown-linux-musl.tar.gz" \
|
|
| tar xz -C /tmp
|
|
export PATH="/tmp/wasm-bindgen-$version-x86_64-unknown-linux-musl:$PATH"
|
|
npm install --global typescript@5
|
|
cargo build --release
|
|
npm/build.sh
|
|
node npm/test.mjs
|
|
|
|
- name: README examples
|
|
run: |
|
|
cargo build --release
|
|
python3 tests/readme-examples.py
|