@better-update/bsdiff
v0.3.2
Published
First-party bsdiff patch **producer** for better-update, written in Rust and exposed via napi-rs (N-API v3). Wraps the [`qbsdiff`](https://crates.io/crates/qbsdiff) crate (v1.4.4) to emit classic bsdiff-4.x (`BSDIFF40`) patches: an 8-byte magic, a 32-byte
Readme
@better-update/bsdiff
First-party bsdiff patch producer for better-update, written in Rust and
exposed via napi-rs (N-API v3). Wraps the qbsdiff
crate (v1.4.4) to emit classic bsdiff-4.x (BSDIFF40) patches: an 8-byte magic,
a 32-byte header, and three bzip2 streams (control/diff/extra) — exactly the
format expo-updates 56's vendored bspatch.c applies on device.
It replaces [email protected], a legacy NAN/V8 addon that segfaults under bun
(exit 133 — JSC is not V8). N-API is ABI-stable and loads under bun (same loader
mechanism as @napi-rs/keyring, already used in packages/credentials-crypto).
API
// Native binding (auto-generated index.js / index.d.ts from `napi build`):
diffSync(oldPath: string, newPath: string, outPath: string): void
diffBuffer(old: Buffer, new: Buffer): Buffer
// Pure helper shim:
import { BSDIFF40_MAGIC, hasBsdiff40Magic } from "@better-update/bsdiff/magic";diffSync mirrors the signature the CLI's BsdiffService already drives, so the
binding source can be swapped without touching the Effect port.
Build
bun run build # napi build --platform --release → host .node + index.js/.d.ts
bun run test # vitest unit tests for the pure shim (NO C compiled)
bun run test:conformance # the SHIP GATE — see below (needs build + libbz2 + C compiler)The native artifacts (*.node, index.js, index.d.ts, target/) are
git-ignored and rebuilt by napi build.
Cross-platform prebuilts (CI)
bun run build only produces the host binary. The five targets in
package.json napi.targets (darwin-arm64 + linux x64/arm64 in both gnu and
musl) need a mac runner and Linux cross-compilation, so they are built in CI by
the build-bsdiff-apple / build-bsdiff-linux jobs in
.gitlab-ci.yml. Windows and Intel macOS are
intentionally not built.
Each binary publishes as an optionalDependencies split package under
npm/<platform>/; the main package's auto-generated loader picks the right one
at runtime. The publish-cli job collates the artifacts (napi artifacts),
pins the stub versions (scripts/sync-bsdiff-versions.mjs), verifies the
invariants (bun run check:bsdiff), publishes the stubs, then ships the main
package. publish-cli runs on every @better-update/cli@* tag and always ships
the CLI; that bsdiff half only runs when bsdiff-gate finds the version is not
yet on npm.
Conformance gate — the bsdiff ship-gate
qbsdiff is format-compatible with classic bsdiff-4.x but a different
implementation than the bsdiff-node it replaces. "Same format" is a claim;
byte-identical reconstruction is only proven by feeding a real patch through the
real on-device patcher. conformance/ is that proof, codified as a reproducible
harness — and it is the gate that must be green before shipping the producer.
bun run build # first: produce the host .node addon
bun run test:conformance # == node conformance/run-conformance.mjsconformance/run-conformance.mjs (exit 0 = PASS, non-zero = do-not-ship):
- asserts
conformance/fixtures/bspatch.cmatches the pinned expo SDK-56 sha25617982b286d43583aa850c022a87db83e63b3d256b2c7822cf58d960674581d8b(the unmodified on-device patcher — tampering fails the gate); - compiles it + the thin
wrapper.cmain()— macOS:/usr/bin/clang -I/opt/homebrew/opt/bzip2/include -L/opt/homebrew/opt/bzip2/lib -lbz2(keg-onlybrew install bzip2); Linux:cc … -lbz2(apt-get install -y libbz2-dev); - produces a
BSDIFF40patch via this package'sdiffSync; - applies it with the compiled
bspatchand asserts SHA-256(out) == SHA-256(new); - negative control A — a bad-magic patch is rejected;
- negative control B — applying against the wrong base diverges from
new.
Test vectors are representative blobs, not a real hermesc
.hbc(no hermesc in this env).bspatchtreats inputs as opaque bytes, so the format-compat proof is payload-shape-independent; seeconformance/fixtures/README.mdfor the caveat and how to upgrade to a production.hbcfixture.
Why it is NOT in bun run test
The gate compiles C against libbz2. The unit run (bun run test) must never
compile C, so the harness is excluded in vitest.config.ts
(include: ["src/**/*.test.ts"], exclude: ["conformance/**", …]) and lives in
the dedicated test:conformance script + the conformance / conformance-macos
CI jobs in .gitlab-ci.yml (Linux + macOS), which build
the addon, install libbz2, and run the gate. A non-zero exit blocks the
merge/ship.
