Files
typikon/.forgejo/workflows/ci.yml
T
funkpower bccf5c4ad8 clone by hand; the runner has no node
Every run died at the first step: "node: executable file not found in $PATH".
actions/checkout is a JavaScript action, and the runner executes it with node
*inside the job container* — which here is a Rust image that has never carried
one. actions/cache and actions/upload-artifact are the same.

The usual answer is an image carrying node as well, which for a Rust project
means installing the toolchain on every run. Cloning by hand is four lines and
keeps the lean image, and the repository is public so it needs no credential.

release.yml collapses to a single job for the same reason. Build and publish were
split across four, which meant handing wheels between them through
upload-artifact — another JS action. Everything a release needs fits in one job,
so nothing has to be handed over at all.
2026-09-18 12:50:56 -04:00

75 lines
3.1 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: ['**']
tags-ignore: ['**']
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
- name: Rust tests
run: cargo test
# 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
- 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