-
Notifications
You must be signed in to change notification settings - Fork 0
Contributing
git clone https://github.com/Abdulkader-Safi/s-img.git
cd s-img
npm install
npm run check # tsc strict + guards + tests. The one to run before a commit.Node 22.18+. Bun is optional, and only needed for the parity check.
npm run check # everything CI runs. Run this before pushing.
npm run build # tsc to dist/, ESM
npm test # node --test
npm run test:bun # the Bun half of the Node/Bun parity claim
npm run check:guards # the two boundary rules
npm run check:size # the core bundle against its 150 KB budget
npm run bench:preview # the preview path's numbers, on a generated 12MP JPEG
npm run example # the runnable tourCI runs check, build, the Bun smoke test and the size gate on every PR. All four must be
green.
features/index.md is the plan: one file per feature, ordered by milestone so every
dependency lands before the thing that needs it. Each feature file states what it does, the
edge cases, and the acceptance criteria — written before the code.
One feature, one branch, one PR.
When the spec and reality disagree, one of them changes on purpose. Sometimes the spec wins and the code is wrong. Sometimes the implementation turns out to be right and the spec gets corrected. What does not happen is a silent deviation — if the code does something the spec did not say, the spec says so afterwards, with the reason.
TDD, and it is not decorative: write the test, watch it fail, then write the code. A test that has never failed is a test you have no evidence about. Several bugs in this repo were found because a test that "obviously" worked did not fail when the code was deliberately broken.
Not from this library's encoder. Testing a decoder against your own encoder proves they agree with each other, which is not the same as either being correct — they can be wrong together, symmetrically, forever.
test/fixtures/*/generate.sh regenerates them. Where a fixture asserts something ImageMagick
cannot write (EXIF orientation on a file with no EXIF, say), it is hand-built and then
validated by an independent reader — ImageMagick or exifr — rather than trusted.
The primary quality tool here, and the standard for a PR. It has found a real bug on essentially every branch — including in tests that were already passing, which is the point.
The workflow is manual and crude and works: break the code on purpose, run the tests, see if anything notices.
# change `<=` to `<`, delete a line, swap a `+` for a `-`, then:
npm testIf nothing fails, the test suite has a hole. Either kill the mutant with a new test, or — if it genuinely changes no behaviour — prove it is equivalent and document it in place, next to the code, with the reason. A survivor nobody explained and a survivor that is fine look identical six months later.
Real examples from this repo:
- A reduced IDCT's cosine table was wrong and 15 passing tests did not care, because "the preview roughly resembles the photo" is not a statement about arithmetic.
-
encodePngnever failed on a malformed image: a 2-byte buffer for a 4×4 produced a valid 72-byte PNG of garbage. - The size check did not catch the exact regression it was written to catch.
If a test asserts that something is absent — a count is zero, no metadata survived, the WASM is not in the bundle — it needs a companion test proving the detector works at all. Otherwise it passes just as happily against a broken harness, forever.
npm run check:guards enforces two rules mechanically:
-
No
node:imports undersrc/core/. The core is portable;io/is the only place that touches a filesystem. - No
anyin the emitted.d.ts.
Each guard has a test that plants a violation and watches the guard fail. A guard nobody has watched fail is a comment — and this one did pass silently on its first attempt, because stripping string literals deleted the import specifiers it was hunting for.
150 KB min+gzip for the core, enforced in CI. Currently 23.2 KB.
Adding a runtime dependency is a real decision, not a shrug. Zero deps is the premise, not
a preference: the artifact has to stay small enough for Obsidian Sync to carry, and every
dependency is a transitive tree and a size surprise. Reach for node: builtins first —
node:zlib instead of a bundled inflate saves ~45 KB, a third of the whole budget.
-
src/core/codecs/yourformat.ts, exportingprobe,decode,encode. - A row in the
CODECStable indispatch.ts, listing the options it accepts. - Magic bytes in
formats.ts. - Fixtures generated by ImageMagick, with a
generate.sh. - Tests: round-trip, the odd dimensions, the malformed input, the edge cases.
- Mutation-test it.
- Check
npm run check:size— you have a budget.
The codec never imports a transform, and never imports node: anything.
Explain why, not what — the diff already says what. If a decision was non-obvious, or a spec was deviated from, or mutation testing found something, that belongs in the message. Someone reading it in a year has the same questions you have now.
Do not add a Co-Authored-By trailer for AI tooling.
Pure TypeScript image editing for Node and Bun. Core: 23.2 KB min+gzip, replacing a 7 MB ImageMagick bundle.
Using it
Working on it