rom-weaverA ROM & Patch toolkit

Development guide

rom-weaver is a Rust workspace with a native CLI and a React webapp backed by a threaded WASI module. mise pins the Rust, Node.js, and Binaryen versions and exposes the repository's build and test tasks.

Prerequisites

Install these system tools before the first build:

  • mise, which installs and pins everything in .config/mise.toml - including Rust and Node.js, so neither is a prerequisite here
  • CMake, Clang, and a native compiler toolchain
  • WASI SDK for web builds
  • Brotli for optimized production WASM builds
  • sccache (optional) to speed up repeated Rust builds

On macOS with Homebrew:

brew install mise cmake llvm brotli sccache

WASI SDK is detected in /opt/wasi-sdk, /opt/homebrew/opt/wasi-sdk, or ~/.local/toolchains/wasi-sdk-<version>. Set WASI_SDK_PATH when it is installed elsewhere. That detection is scripts/wasm/detect-wasi-sdk.sh, and it is POSIX shell rather than Node.js because mise renders [env] before installing the tools it pins.

Clone and bootstrap

All vendored source is committed in-tree, so a plain clone is complete:

git clone https://github.com/rom-weaver/rom-weaver.git
cd rom-weaver

Trust and install the pinned toolchains, install both JavaScript dependency sets, and install the repository hooks:

mise trust
mise install
npm ci
npm ci --prefix packages/rom-weaver-webapp
npm --prefix packages/rom-weaver-webapp exec playwright -- install chromium
npm run hooks:install

Run mise tasks at any time to list the supported task entry points.

Build and run the webapp

Choose the WASM build that matches the task:

mise run wasm-check        # type-check the threaded WASI target
mise run build-wasm        # fast development build
mise run build-wasm-prod   # optimized release build with wasm-opt and Brotli

Then start the HTTPS development server:

npm run dev

The server prints its local URL. To choose another port:

npm run dev -- --port 5174

The local certificate may need a one-time browser exception. HTTPS and the server's COOP/COEP headers are required to exercise the same SharedArrayBuffer runtime used in production.

npm run dev checks the WASM artifact and rebuilds it when the Rust sources are newer. Run mise run build-wasm directly when changing the WASM toolchain or when you need to see the complete build output.

By default, build artifacts are written to packages/rom-weaver-webapp/src/wasm/, which is gitignored. To keep a separate artifact directory:

ROM_WEAVER_WASM_OUT_DIR=/path/to/artifacts mise run build-wasm

The build synchronizes the artifacts back into the webapp package when the custom directory differs from the default. The threaded target flags and WASI compiler wiring live in .cargo/config.toml and .config/mise.toml; use mise exec for ad hoc target commands:

mise exec -- cargo check -p rom-weaver-containers --target wasm32-wasip1-threads

See the WASM runtime notes for the browser OPFS and worker API.

Build and run the native CLI

cargo build -p rom-weaver-cli
cargo run -p rom-weaver-cli --bin rom-weaver -- --help

The rom-weaver-cli package contains the reusable rom_weaver_app command library plus the native CLI, WASM entrypoint, and type generator.

Test and lint

Run the complete local quality gate before submitting a change:

mise run ci

That task covers Rust formatting, Clippy, generated types, threaded-WASM guards, license inventory, workflow/shell/Dockerfile linting, Rust tests, the production WASM build, frontend linting, unit tests, browser/WASM tests, full-browser tests, webapp E2E, and the production frontend build.

The frontend lint command is the source of truth for formatting, Oxlint and Biome (including complexity limits), TypeScript, browser compatibility, unused exports, copy/paste duplication, i18n catalog integrity, and the legacy-string audit. The pre-commit hook selects these and the Rust checks from the staged paths; CI runs its lint jobs over the whole tree.

Useful narrower checks:

mise run fmt
mise run clippy
mise run test-rust
mise run typegen-check
mise run actionlint    # GitHub Actions workflows and composite actions
mise run shellcheck    # tracked shell scripts
npm test               # test the Node.js tooling
mise run hadolint      # Dockerfiles

npm --prefix packages/rom-weaver-webapp run lint    # complete frontend lint fan-out
npm --prefix packages/rom-weaver-webapp run test:unit
npm --prefix packages/rom-weaver-webapp run test:browser

Use the repository's browser-test runner instead of invoking browser Vitest directly; it isolates files and avoids browser-mode hangs in linked worktrees.

The fast end-to-end gate is also available separately:

mise run test-e2e-fast

The Mobile Safari guide owns the WebKit, nightly matrix, iOS Simulator, and real-device verification procedures.

Generated files

When Rust command types or metadata change, regenerate the TypeScript surface:

mise run typegen
mise run typegen-check

The build generates third-party attribution files from the resolved Cargo dependency graph. Public-domain and no-attribution-only licenses are omitted. To check that generation still works:

mise run licenses-check

When Clap commands or argument help changes, regenerate the man pages from those definitions (they are generated artifacts, not checked in):

mise run manpages

The webapp's sample NES ROMs, IPS patch, and ZIP bundles are reproducible from source. Generate the four public sample files in packages/rom-weaver-webapp/dist/ with:

npm --prefix packages/rom-weaver-webapp run samples:generate

Do not edit files under packages/rom-weaver-webapp/src/wasm/generated/ or docs/man/ manually.

Dependencies

.config/deny.toml owns the dependency policy - advisories, licenses, and sources. Check it with:

mise run deny             # advisories + licenses + sources
mise run deny-policy      # licenses + sources only (the gating half)
mise run deny-advisories  # advisories only
mise run machete          # unused Rust dependencies

The two halves are split because CI treats them differently. Licenses and sources gate the build - they change only when we add a dependency, so a violation is fixable in the same commit. Advisories run in the separate, non-gating security job alongside npm audit: a CVE is published against a transitive dependency with no commit of ours, and a red check on every open pull request would block unrelated work. Findings show up as job annotations and a run summary, and are still expected to get fixed.

New crates must resolve to an already-allowed license. When one does not, prefer a per-crate entry in exceptions over widening the global allow list, and write down why - a scoped entry keeps the next crate carrying that license from landing unnoticed.

bzip2 backend

bzip2 exposes two backends and picks one with #[cfg(feature = "bzip2-sys")]: the C bzip2-sys library, or the pure-Rust libbz2-rs-sys. The workspace pins bzip2 to default-features = false, features = ["bzip2-sys"], which keeps every build on the C backend.

That pin is load-bearing. Cargo unions features across the graph, and upstream qbsdiff depends on bzip2 with default features on, so libbz2-rs-sys is present in the dependency graph regardless. The workspace pin is the only thing keeping bzip2-sys selected; drop it and the bzip2 implementation underneath BDF patch output silently swaps.

libbz2-rs-sys therefore compiles but is never linked. Keep the scoped .config/deny.toml exception for its bzip2-1.0.6 license. If qbsdiff later exposes a feature that disables its default bzip2 backend, remove the extra backend and the exception together.

Linked worktrees

After creating a linked worktree, run its setup helper before building or testing:

node scripts/setup-worktree.mjs

It installs package dependencies, copies existing WASM artifacts when available, and warms the two Cargo target dirs the pre-commit hook builds into (target/hook-typegen and target/hook-wasm). That last step takes a few minutes and roughly 1.7 GB; pass --no-prime to skip it and let the first Rust commit in the worktree pay for it instead.

After verifying that a worktree has no real changes, remove it from the main checkout with:

node scripts/remove-worktree.mjs .worktrees/<name>

Project map

  • crates/ contains the Rust format libraries, shared app orchestration, and native CLI.
  • packages/rom-weaver-webapp/ contains the React app, browser workers, OPFS adapters, PWA shell, and WASM package surface.
  • scripts/ contains build, test, release, and license automation.
  • docs/ contains deployment, runtime, verification, and architecture guides.

Read ARCHITECTURE.md for the full crate graph, ownership boundaries, worker model, generated-type path, and browser runtime flow.