# Packaging contract (Python + npm)
This document is the release contract for issue #692: every compatible
`djvu-rs` crate release should produce version-matched Python wheels and an
npm WebAssembly package, with smoke tests against the *installed* artifacts.
## Version policy
| Rust crates (`djvu-rs` + workspace) | root `Cargo.toml` / release-please |
| Python package `djvu-rs` on PyPI | `djvu-py/Cargo.toml` via maturin (`pyproject.toml` uses `dynamic = ["version"]`) |
| npm package `djvu-rs` | `package.json` generated by `make wasm` from the crate version |
`scripts/check_package_versions.sh` fails the packaging workflow when the
Python crate version or release-please manifest drifts from the root crate.
After `make wasm`, the same script with `CHECK_NPM_PACKAGE=1` verifies the
generated npm `package.json`.
## Python (`djvu-py`)
### Supported wheels
Release CI builds CPython wheels for:
- manylinux (x86_64, aarch64)
- musllinux (x86_64, aarch64)
- macOS (x86_64, arm64)
- Windows (x64)
Documented Python versions: **3.9–3.13** (`requires-python = ">=3.9"`).
### Source distribution
The sdist builds with [maturin](https://www.maturin.rs/) and needs:
- a Rust toolchain (MSRV from root `Cargo.toml` `rust-version`)
- a CPython interpreter matching the target
- network access to crates.io on first build
```sh
pip install djvu-rs # preferred once published
# or from an sdist / checkout:
pip install ./djvu-py # requires Rust + maturin
```
### Stable surface
Exposed today: open / `from_bytes`, page metadata, render (including region /
progressive / coarse), text extraction, typed exceptions, zero-copy buffer
views, and document export — `to_pdf` / `write_pdf`, `to_epub` / `write_epub`,
`to_cbz` / `write_cbz`, `to_tiff` / `write_tiff`. Each `to_*` returns bytes;
each `write_*` streams to a file and holds one page at a time.
**Not exposed in the Python bindings** (use the Rust crate / CLI instead):
- encode / `PageEncoder`
- document mutation (`DjVuDocumentMut`)
### Typed exceptions
| `djvu_rs.Error` | Base class for binding errors |
| `djvu_rs.DecodeError` | Parse / decode / render failures |
| `djvu_rs.IoError` | Filesystem failures from `Document.open` and the `write_*` exporters |
| `djvu_rs.ExportError` | PDF / EPUB / CBZ / TIFF conversion failures |
| `djvu_rs.PageIndexError` | Out-of-range `Document.page` (also an `IndexError`) |
### Smoke tests
```sh
pip install dist/djvu_rs-*.whl
python scripts/smoke_python_wheel.py \
--fixture tests/fixtures/boy.djvu \
--expect-version "$VERSION" \
--repo-root "$PWD"
```
The smoke script refuses to load `djvu-py` sources from the checkout.
## npm (WebAssembly)
Build the dual scalar + `simd128` package:
```sh
make wasm # → examples/wasm/pkg
# or: OUT=target/npm-pkg make wasm
```
The package includes TypeScript declarations (`djvu_rs.d.ts`) and both wasm
artifacts under `scalar/` and `simd128/`. Runtime selection uses
`WebAssembly.validate()` on a SIMD probe.
### Smoke tests
```sh
make wasm
npm pack --pack-destination target/npm-dist --workspace=false
# from examples/wasm/pkg:
cd examples/wasm/pkg && npm pack --pack-destination ../../../target/npm-dist
node scripts/smoke_npm_package.mjs \
--package target/npm-dist/djvu-rs-*.tgz \
--fixture tests/fixtures/boy.djvu \
--expect-version "$VERSION" \
--bundler
node scripts/smoke_npm_browser.mjs \
--package examples/wasm/pkg \
--fixture tests/fixtures/boy.djvu
```
## Release workflow
`.github/workflows/publish-packages.yml` runs on version tags (`v*`) and:
1. Checks package versions
2. Builds the full Python wheel + sdist matrix
3. Builds the npm package (`make wasm`) and packs a tarball
4. Runs install-time smokes (Python wheel, Node, bundler, browser)
5. Writes `SHA256SUMS` and GitHub artifact attestations
6. Publishes to PyPI / npm **only if** the corresponding secrets are configured
and the gate job succeeded
If any platform wheel, the sdist, the npm tarball, or a smoke test fails, the
gate fails and publish jobs do not run — an incomplete set is never advertised
as released.
### Required secrets / trust
| `PYPI_API_TOKEN` or PyPI trusted publishing (`id-token`) | Upload wheels + sdist |
| `NPM_TOKEN` | `npm publish` |
Until those are configured, tag builds still produce attested artifacts and
checksums as workflow artifacts for manual verification.
## Related docs
- [`RELEASING.md`](../RELEASING.md) — crates.io tag flow
- [`djvu-py/README.md`](../djvu-py/README.md) — Python quick start
- [`examples/wasm/README.md`](../examples/wasm/README.md) — wasm demo + npm layout