chore(wasm): merge prep β main fixes, pinact, per-image error tolerance, test parity#604
Merged
chore(wasm): merge prep β main fixes, pinact, per-image error tolerance, test parity#604
Conversation
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* 0.18.8 * update
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
β¦ty] (#528) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6. - [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md) - [Commits](moxystudio/node-cross-spawn@v7.0.3...v7.0.6) --- updated-dependencies: - dependency-name: cross-spawn dependency-version: 7.0.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.2 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](micromatch/micromatch@4.0.2...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-version: 4.0.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* fix: update * fix: greenkeep * fix: use latest
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.7 to 7.5.13. - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](isaacs/node-tar@v7.5.7...v7.5.13) --- updated-dependencies: - dependency-name: tar dependency-version: 7.5.13 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
chore(deps): bump tar from 7.5.7 to 7.5.13
β¦nerability chore(deps): update dependency brace-expansion to v5 [security]
chore(deps): update dependency puppeteer to v24.42.0
chore(deps): update dependency @babel/preset-env to v7.29.3
chore(deps): update pnpm to v10.33.2
security: green keep
chore(deps): update dependency tar-fs to v3.1.2
chore(deps): update dependency ava to v8
* test: remove mistakenly committed .only from cli test Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: fix __dirname not defined in ESM perf test Use process.cwd() instead of __dirname so the test file works regardless of CommonJS/ESM module loading. Other tests in this file already rely on the default cwd. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: use deleteAsync from del v8 (default export removed) del v8 dropped the default export and now exposes deleteAsync/deleteSync as named exports. The babel-compiled CJS was calling _del.default(...), which is undefined, crashing the CLI whenever -U triggered the cleanup path. This was masked by a stray test.serial.only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses three minimatch ReDoS advisories: - GHSA-23c5-xmqv-rm74 (CVE-2026-27904) β nested *() extglobs - GHSA-7r86-cg39-jmmj (CVE-2026-27903) β matchOne() backtracking - GHSA-3ppc-4f35-3m26 (CVE-2026-26996) β repeated wildcards Replace the duplicate/partial minimatch overrides with per-major-line selectors that pin to the first patched release in each line. After this, the lockfile resolves minimatch to 3.1.5 and 10.2.5 (both fully patched) instead of the vulnerable 9.0.5. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦version bump - Adopts main's pnpm.overrides (minimatch security, tar 7.x, etc.) - Keeps wasm-test job alongside the legacy puppeteer test job - Pulls main's src/index.js -U restore (legacy JS still ships from root) - Top-level version 0.18.6 β 0.18.16 - Keeps wasm-side onlyBuiltDependencies (cli-spinner + puppeteer) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap each rayon-thread image into a `ImageOutcome::{Ok,Failed}` rather
than propagating a single Err out of the closure. On read or decode
failure: log to stderr, fire `compare-event{type:"fail"}` for live
progress, and fold into `failedItems` so callers (CLI, EventEmitter
consumers) see a normal end-of-run result.
Matches classic reg-cli's tolerance β one corrupt PNG must not abort a
batch of 1000 images.
Adds tempfile dev-dep + 3 unit tests:
- corrupt PNG β failedItems, neighbour passes
- non-image extensions stay silently filtered
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cli.test.mjs (+6): - -T 0.00 / -T 1.00 thresholdRate boundaries - -S 0 / -S 10000000 thresholdPixel boundaries - identical dirs β passedItems all populated - actual empty β all baselines surface as deletedItems - corrupt PNG β failedItems entry, sibling still passes, exit 1 - non-image files (.md, .json) silently skipped library.test.mjs (+2): - reg-suit drop-in: every event + CompareOutput field processor.ts consumes (start/compare/complete/error + failed/new/deleted/passed) - compare() does NOT fire `error` on single-image decode failure; bad.png β failedItems + 'fail' compare-event, run completes js/reg.wasm rebuilt against the updated reg_core (per-image error tolerance) so CI exercises the new behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two CI failures on PR #604: - `test` job: pnpm-lock.yaml didn't list @pyroscope/nodejs (we adopted main's lock during the merge), so --frozen-lockfile rejected the package.json that still had it. @pyroscope is only used by the wasm wrapper (`js/`); it doesn't belong in the root JS reg-cli's deps. - `wasm-test` job: pnpm/action-setup with `version: 10.26.2` conflicted with root package.json's `packageManager: pnpm@10.33.2+...`. Drop the explicit version and let action-setup read packageManager. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
bokuweb
added a commit
that referenced
this pull request
May 3, 2026
* init * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * 0.0.0-experimental0 * perf: improve perf * update * fix: image diff * fix: wasm * fix: remove console.log * fix * fix * fix * fix * fix (#539) * fix * fix: diff ext (#540) * updatev wazm * feat: otel * Fix 2s sleep in tracing shutdown; add JS-bridge spans (#579) Two related changes surfaced by a trace comparison between the classic JS reg-cli and this Wasm build. 1. Remove the `setTimeout(resolve, 2000)` after `sdk.shutdown()` in `js/tracing.ts`. `sdk.shutdown()` already awaits exporter flush; the extra sleep was a paranoia workaround that dominated wall-clock when OTEL is on: 20 images Γ 1280Γ720, OTEL on: before: Wasm 2.53s (JS 0.88s) after: Wasm 0.49s (JS 0.81s) β Wasm is now faster 2. Add JS-bridge spans so the invisible time between `main()` and Rust `reg_cli_main` is visible. New spans (worker -> parent via postMessage, parent converts to OTel): main.init_tracing main.new_entry_worker main.new_thread_worker (Γ N) main.process_trace_and_spans main.run_total entry.wasm_compile entry.wasm_instantiate entry.wasi_start entry.init_tracing_rust entry.wasm_main entry.read_report_string entry.collect_rust_traces entry.worker_total worker.wasm_compile worker.wasm_instantiate worker.wasi_start worker.wasi_thread_start worker.thread_total Before this, the ~180ms between Node startup and Rust `reg_cli_main` looked like a mystery gap in Jaeger. Now each stage is attributable. 3. Fix bare `postMessage(...)` in `js/worker.ts`. `postMessage` is not a global inside Node's `worker_threads`; use `parentPort?.postMessage(...)` to match `js/entry.ts`. This was silently not working or emitting an async error that aborted thread-spawn under some conditions. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add bench/ harness; bump image-diff-rs to 0.1.0 (#580) * Add bench/ harness; bump image-diff-rs to 0.1.0 ## Changes - **Cargo.toml (workspace)**: add `[profile.release]` (lto / codegen-units=1 / opt-level=3 / panic=abort / strip) for smaller + faster wasm binary - **crates/reg_core/Cargo.toml**: pin `image-diff-rs` to tag `0.1.0` (https://github.com/bokuweb/image-diff-rs/releases/tag/0.1.0 β adds internal tracing spans for decode / compare / encode) - **bench/**: JS vs Wasm trace-comparison benchmark harness - `generate.sh` β ImageMagick-based fixture generator (20 pairs Γ 1280Γ720, 5 mutation types: shift / hue / badge / rect / stripe) - `run.sh` β run both `dist/cli.js` and `js/dist/cli.mjs` against the same fixture, capturing OTLP traces - `export-traces.sh` β pull latest JS and Wasm traces from Jaeger and write `out/traces.json` - `viz/index.html` β single-trace waterfall viewer (per-service) - `viz/compare.html` β **aligned-timescale stacked view** (JS on top, Wasm on bottom, shared x-axis) - `README.md` β mutation matrix and usage - `RESULTS.md` β measurement results with honest caveats (PNG vs WebP encode, reg.json file write, etc.) ## Measured (macOS Apple Silicon / Node v20 / warm median) | workload | JS | Wasm | ratio | |---|---:|---:|---:| | 20 Γ 1280Γ720 | 0.59 s | 0.42 s | 1.40Γ | | 1 Γ 1280Γ720 | 0.28 s | 0.24 s | 1.18Γ | | 1 Γ 3840Γ2160 | 1.29 s | 0.69 s | 1.87Γ | | identical Γ 20 | 0.30 s | 0.29 s | ~equal | Caveats included in `bench/RESULTS.md`: - JS writes diff images as PNG; Wasm writes as WebP (WebP encode is cheaper) - JS writes `reg.json` to file; Wasm returns it as a string - Concurrency default 4 for both; md5 / byte short-circuit for identical both Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix pathdiff panic; add --diffFormat flag; update RESULTS Three fixes that fell out of "why is the JS vs Wasm benchmark not apples-to-apples": ## 1. `pathdiff::diff_paths` no longer panics on mixed paths `resolve_dir(base, target)` previously called `.expect("should resolve relative path.")` which panicked whenever `base` and `target` were a mix of absolute and relative paths (e.g. absolute `--report` + relative fixture directory). Now: - Both-same-kind β pathdiff as before - Mixed β canonicalize both to absolute via `current_dir()` and retry - Still no match β return `target` unchanged instead of panicking Added 4 unit tests covering both-relative, both-absolute, and both mixed directions. ## 2. `--diffFormat {webp,png}` CLI flag Depends on image-diff-rs PR #34 (bokuweb/image-diff-rs#34) which adds `EncodeFormat`. Plumbed through: - `reg_core::Options` gets `diff_image_format: Option<DiffImageFormat>` - `DiffImageFormat::{Webp, Png}` as a user-facing mirror - File extension of written diff images + HTML report follows the choice - `reg_cli`'s `main.rs` exposes `--diffFormat {webp,png}` via clap Pointed `reg_core`'s `image-diff-rs` dep at the PR branch temporarily; will move back to a tag once image-diff-rs #34 is merged and released. ## 3. bench/RESULTS.md β corrected numbers Replaced the earlier WebP-vs-PNG confounded numbers with apples-to-apples PNG-vs-PNG measurement: | workload | JS (PNG) | Wasm `--diffFormat png` | ratio | |---|---:|---:|---:| | 20 Γ 1280Γ720 | 0.71 s | 0.46 s | **1.54Γ faster** | | 1 Γ 3840Γ2160 (4K) | 0.92 s | 0.42 s | **2.19Γ faster** | | identical Γ 20 (skip) | 0.30 s | 0.29 s | ~equal | Also documents all the setup caveats surfaced during the investigation (WASI preopen sandbox, pathdiff panic, postMessage form, 2s sleep, etc.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Extend benchmark to 100 images β gap widens to 1.98Γ faster Added 100 Γ 1280Γ720 row to the apples-to-apples and default tables. At 100 images the Wasm advantage grows from 1.54Γ β 1.98Γ compared to JS PNG (and 1.31Γ β 1.54Γ in default WebP mode). Consistent with the hypothesis that startup cost amortizes and per-image compute stays flat β the more work to do, the more the Rust core wins. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Bump image-diff-rs to 0.1.1 (EncodeFormat released) image-diff-rs#34 merged and tagged as 0.1.1, so we can drop the branch pin and use a proper tag. - Before: branch = "add-png-encode-option" - After: tag = "0.1.1" No code changes; still smoke-tested with `--diffFormat png`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(otel): drop stale packageManager field that breaks yarn install The root package.json carried a `packageManager: "pnpm@10.26.2+..."` field (introduced via the wasm branch's tooling migration), but CI still installs the outer project with yarn and the repository still ships a `yarn.lock`. Corepack/yarn 1.x refuses to install when the declared packageManager doesn't match the invoked tool: error This project's package.json defines "packageManager": "yarn@pnpm@10.26.2". However the current global version of Yarn is 1.22.22. The simplest fix that restores CI without churn is to drop the field. The inner `js/` workspace (Wasm bridge) uses its own pnpm + lockfile and is unaffected by removing the root-level declaration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(otel): migrate root project from yarn to pnpm Matches the `packageManager: pnpm@10.26.2` declared in package.json. - `.github/workflows/ci.yml`: use `pnpm/action-setup@v4` + `pnpm install --frozen-lockfile`, bump `actions/checkout`/`setup-node` to v4, Node 20 - `package.json`: replace `resolutions` (yarn glob syntax) with `pnpm.overrides` (flat, pnpm native). Same 21 pinned versions, no behaviour change. - `pnpm-lock.yaml`: added (generated by `pnpm install`) - `yarn.lock`: removed (no longer authoritative) - `decls/deps.js`: add flow declarations for third-party modules that .flowconfig's `[ignore] node_modules` hides from flow's resolver. The nine "Cannot resolve module" errors that surface after a clean install are all third-party imports (`cli-spinner`, `meow`, `lodash`, `chalk`, etc.); this restores `pnpm run flow` to pass. The inner `js/` workspace (Wasm bridge) already uses its own pnpm install and is unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(otel): restore packageManager field so pnpm/action-setup works An earlier commit removed the `packageManager` field as a workaround for yarn, but pnpm/action-setup@v4 reads exactly this field to pick the pnpm version. Restore it now that CI is on pnpm. Fixes: "Error: No pnpm version is specified." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(otel): pin pnpm version in action-setup, drop outer packageManager - Specify `version: 10.26.2` directly on pnpm/action-setup@v4 instead of relying on the outer `packageManager` field. The outer field was conflicting with the inner `yarn install` inside `report/ui/` because yarn 1.22 walks up the directory tree when checking the corepack packageManager constraint. - Remove `packageManager` from the root package.json so yarn inside the cloned `report/ui` (which has its own yarn.lock and needs Yarn 1.22) doesn't refuse to install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(otel): allow puppeteer postinstall so test:screenshot finds Chrome pnpm 10 blocks postinstall scripts by default (via `onlyBuiltDependencies`). puppeteer 13.7 downloads Chromium in its postinstall; without it `test/screenshot.js` fails with: Error: Could not find expected browser (chrome) locally. Run `npm install` to download the correct Chromium revision (982053). Add `puppeteer` to `pnpm.onlyBuiltDependencies` so its install.js runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viz: collapsible parent spans in compare.html + index.html Click the label row of a span that has children to toggle the sub-tree. Rows with children show βΎ (expanded) or βΈ (collapsed); leaves show no arrow. New "collapse all" / "expand all" buttons in the toolbar operate on the union of parent-span IDs across both traces so the same state applies to every lane. Useful for the 100-image waterfall where `parallel_image_diff` or `diff_single_image` otherwise push all the interesting one-liners (boot / cleanup / threadpool) off-screen. Implementation notes: - Shared `collapsed: Set<spanId>` state at the module level - `flatten()` (compare.html) / `walk()` (index.html) skip descendants of collapsed ids, and tag each row with `hasChildren` - Invisible full-width hit rect per collapsible row so the entire label area is clickable, not just the arrow glyph - `pointer-events: none` on the text/badge inside so the underlying hit rect catches the click reliably Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Narrow the WASI sandbox: single-ancestor preopen + env allowlist (#581) Before this, entry.ts / worker.ts set `preopens: { './': './' }` and `env: env as Record<string, string>`. That means any Wasm code that takes control of the pipeline (e.g. via an image-decoder CVE β libpng, libwebp, libjpeg have a long history of RCE-class ones) could read anything under the invocation cwd (`.npmrc`, `.env`, `node_modules`, ...) and see every host env var (`NPM_TOKEN`, `AWS_*`, ...). This change introduces `computeWasiSandbox(argv)` in `js/utils.ts` and plugs it into both `entry.ts` and `worker.ts`: - **preopen** β the narrowest single ancestor directory that contains every path the run actually touches: `actualDir`, `expectedDir`, `diffDir`, and the parents of `--report` / `--json`. In a typical invocation inside a repo the sandbox shrinks from "all of CWD" to "just the reg-cli fixture folder", i.e. the Wasm side no longer sees sibling files like `.env` or `~/.config/...` - **env** β allowlist only OTel-related variables (`OTEL_ENABLED`, `OTEL_DEBUG`, `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_SERVICE_NAME`, β¦). Everything else (AWS_*, NPM_TOKEN, CI secrets) stops at the Wasm boundary ## Why only a single ancestor preopen? Originally this PR tried to register one preopen per directory (actual / expected / diff / report-parent / json-parent). Writes to the first preopen worked; the other 4 silently became invisible to Rust. Instrumenting `@tybys/wasm-util` showed that WASI-side all 3-5 preopens were correctly installed (fd 3, 4, 5, ...) but the Wasm side only ever issued `fd_prestat_get(3)` and never queried fd 4+, which smells like an enumeration bug in `wasm32-wasip1-threads` libstd. Falling back to the common ancestor avoids the bug while still giving a real defense-in-depth win. When the upstream issue is fixed we can swap back to per-directory preopens. ## Not in scope (explicit follow-ups) - Symlinks / FIFOs inside the preopened directory are still resolved by the host. An attacker who can plant files under `./diff/` could still exfiltrate via an evil symlink. - `fs: fs as IFs` still hands `@tybys/wasm-util`'s WASI unrestricted Node `fs`. We constrain *the paths Wasm is allowed to name*, not what the host fs layer underneath would do on Wasm's behalf. - stdout / stderr inheritance. If the host redirects them to a socket (`reg-cli > >(nc ...)`) Wasm output leaves the box through the host's pipe, not through a WASI syscall. ## Also bumps `rust-toolchain.toml` nightly-2024-08-24 β 2025-01-01 Build fix: dependencies now require `edition2024` which the old nightly cannot stabilise. No behavioural change. ## Test plan - [x] 20-image fixture: 16 diff images produced, report.html written - [x] 100-image fixture: same (timings within noise of pre-change) - [x] `SECRET_TOKEN=x NPM_TOKEN=y AWS_ACCESS_KEY_ID=z reg-cli ...` β none of those are forwarded into Wasm (confirmed via logged `sandbox.env` during bring-up) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * CLI compat with classic reg-cli: exit codes, short aliases, -I/-E/-U (#583) Makes `@bokuweb/reg-cli-wasm`'s CLI a drop-in replacement for `reg-cli` for the most common CI use-cases. Closes the first tier of compat gaps between the JS classic CLI and the Wasm build; `--from`, `--junit`, `-D`, `-X` and the per-file library events remain for a follow-up PR. ## Before - `node js/dist/cli.mjs ... ` exited 0 regardless of diffs β CI didn't detect failures - Only long-form flags (`--json`, `--report`, ...) worked; `-J`, `-R`, `-M`, `-T`, `-S`, `-C`, `-A` all failed with "unexpected argument" - No progress / summary lines on stdout - `-U`, `-I`, `-E` were not recognised at all - Worker `error` events were re-thrown outside the EventEmitter, so library users couldn't `.on('error', ...)` them Swapping classic reg-cli β the Wasm build was therefore a script change, not a drop-in replacement. ## After ### Rust / clap (`crates/reg_cli/src/main.rs`) - Add POSIX short aliases to every existing option: `-R/--report`, `-J/--json`, `-M/--matchingThreshold`, `-T/--thresholdRate`, `-S/--thresholdPixel`, `-P/--urlPrefix`, `-C/--concurrency`, `-A/--enableAntialias` - `-A` now also accepts a bare form (`num_args = 0..=1` + `default_missing_value = "true"`) so `-A` alone means `-A=true`, matching meow's boolean semantics. - `--diffFormat` unchanged (long-only, matching no-short policy for wasm-only flag). ### CLI wrapper (`js/cli.ts`) Rewritten as a thin front-end around `run()` that handles everything the Wasm side doesn't (yet) know about: - Proper arg parsing with Node's built-in `util.parseArgs` β no extra dep; short aliases listed there too as a safety net. - `--help` / `-h` short-circuit prints the help text (clap help is reachable via the Wasm too but is cosmetically different). - `-U / --update` β after complete, copy `actualItems` over `expectedDir` with `mkdir(recursive)` + `copyFile`. - `-I / --ignoreChange` β suppresses non-zero exit. - `-E / --extendedErrors` β treats new/deleted counts as failures. - `-D / --customDiffMessage` β trailing message printed on diff, defaulting to classic's "Inspect your code changes, re-run with `-U` to update them.". - Per-file `β pass / β change / β append / β delete` log lines + summary. - `process.exitCode = 1` on failure (honouring `-I`). ### Library (`js/index.ts`) The two `worker.on('error', e => { throw new Error(e.message) })` hooks are now `emitter.emit('error', e)`, matching classic reg-cli's EventEmitter surface. Library users can now: const emitter = compare({ ... }); emitter.on('error', (err) => { /* handle */ }); emitter.on('complete', (data) => { /* ... */ }); instead of needing a global `uncaughtException` handler. ### Also: `rust-toolchain.toml` nightly-2024-08-24 β 2025-01-01 Transitive dep now requires `edition2024` which the old nightly cannot stabilise. No behavioural change. ## Test plan - [x] 20 images: all short aliases (`-R -J -M -C`) parsed, 16 PNG diffs produced, exit 1. - [x] `-I` flag: exit 0 even with diffs. - [x] `-E` with no diffs: exit 0 (no escalation triggered). - [x] `-E` with new images: exit 1. - [x] `-U`: `expected/` is updated to match `actual/`, exit 0. - [x] `--help`: prints usage and exits 0. - [x] Missing positional args: clear error + exit 1. ## Not in this PR (follow-up) - `-F / --from` (JSON β report without re-running diff) - `--junit` output - `-X / --additionalDetection` - Library `start` / `compare` (per-file) / `update` events - `reg.json` / `junit.xml` written to disk by default - Default `--diffFormat png` (to fully match classic's output filenames) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * CLI compat (phase B): library events + --junit + update via compare() Follow-up to #583 (phase A). Library surface now matches classic reg-cli. - run() / compare() emit 'start', 'compare' (per-file synthesised), 'update', 'complete', 'error' - compare(input) handles update / junitReport as side effects after the Wasm run, instead of forwarding them to clap (which rejected them) - Translates threshold -> thresholdRate alias - New js/junit.ts writes minimal JUnit XML matching classic's schema - CLI --junit <path> flag wires the writer on complete Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * CLI compat (phase C): default PNG output + always persist reg.json Follow-up to #583 and #584. Closes the last first-class compat gap with classic reg-cli: the JSON/HTML report now references PNG diff images by default and `reg.json` is always written to disk. ## What changes for users ### Default `diffFormat` = `png` Before: Wasm defaulted to WebP, so `failedItems` / `diffItems` in the JSON contained `*.webp` entries and the HTML report loaded WebP images. That's a real behaviour break for downstream consumers of reg-cli's JSON (reg-suit, reg-notify-*, custom bots). After: both `cli.ts` and `compare()` default `--diffFormat` to `png` when the caller hasn't specified it. Users who want WebP can still opt in with `--diffFormat webp` or `{ diffFormat: 'webp' }`. ### `reg.json` is always persisted Before: the Wasm side returned the JSON report as a string through the Worker `complete` message, but nobody wrote it. CIs that read `./reg.json` after running reg-cli saw nothing. After: both `cli.ts` and `compare()` write the report to the `--json` path (defaulting to `./reg.json`) immediately before emitting the external `complete` event. If the write fails, the library emits `'error'` instead of `'complete'`. ## Implementation - `js/cli.ts`: reads `--json` / `-J` (default `./reg.json`), reads `--diffFormat` (default `png`). Pushes the effective values to the Wasm argv and writes `reg.json` itself after complete. - `js/index.ts`: same defaults applied inside `compare()` before building the Wasm argv; `writeRegJson()` extracted and exported so `cli.ts` reuses the exact same persistence code path. - The previous fast-path in `compare()` that returned the inner emitter unchanged when no side effects were needed is gone, since writing `reg.json` is now an unconditional side effect. Cheap β one `writeFile` of ~KB JSON. ## Test plan - [x] CLI: diff images in diff dir are `*.png`, `reg.json` exists, `reg.json` contains `*.png` entries in `diffItems`. - [x] CLI explicit `--diffFormat webp`: diff images are `*.webp`, JSON references `*.webp`. - [x] Library `compare({ ... })` with no `json`: `./reg.json` written, diffItems end in `.png`. - [x] Library `compare({ json: '/path/out.json' })`: written to /path/out.json, parent dir created if missing. - [x] Writing to an unwritable path fires `error`, not `complete`. ## Still not in scope - `-F / --from` (JSON β HTML without running diff) - `-X / --additionalDetection` Both have limited blast radius (few users rely on them) and can land in a follow-up without blocking migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * CLI compat (phase D): -F/--from, -X/--additionalDetection, Rust-side junit + reg.json writes Closes the remaining CLI gaps with classic reg-cli: - `-F, --from <reg.json>`: new `reg_core::run_from_json()` re-renders the HTML report from an existing reg.json without running any image comparison, and the CLI makes positional dirs optional in this mode. - `-X, --additionalDetection <none|client>`: plumbs `Options.enable_client_additional_detection` into `ReportInput`, flipping the HTML report's `ximgdiffConfig.enabled`. The report template's worker_url is finally wired ("./worker.js" instead of the leftover "TODO:"). - `--junit <path>`: moved JUnit XML generation into `reg_core` (`build_junit_xml`) and the `run()` / `run_from_json()` entry points now write it themselves. `js/junit.ts` is deleted; the JS side no longer writes reg.json or junit.xml (both artefacts land inside the WASI preopened root instead of via host-side fs). JS side: - `computeWasiSandbox` now also covers `--junit` and `--from` parents when deciding the common-ancestor preopen root. - `cli.ts`: new `-F/--from`, `-X/--additionalDetection`, `--junit` flags forwarded to Wasm; drops the duplicate reg.json / junit writes. - `compare()` in `index.ts`: remaps `junitReport` β `--junit`, translates the historical `enableClientAdditionalDetection: true` to `additionalDetection: "client"`, drops the JS json/junit writes, and now only keeps the `update` shim (file-copy still needs host fs). Smoke-tested end to end against `js/sample/` with both diff + `-F` regeneration; reg.json / report.html / junit.xml now all come from Rust. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * CLI compat (phase E): byte-for-byte junit + Rust unit tests + JS integration tests ## Fix: junit XML is now byte-identical to classic reg-cli Phase D (#586) shipped a simplified junit shape (`<testsuites>` bare, `classname="reg-cli"`, `message="changed|new|deleted"`) that diverged from what classic reg-cli produces via xmlbuilder2 β which is exactly what downstream CI parsers and `test/cli.test.mjs` snapshot-check. Fixed to the classic schema: - `<?xml version="1.0"?>` (no `encoding` attr) - `<testsuites name="reg-cli tests" tests=N failures=M>` (attrs on BOTH testsuites and testsuite) - `<testcase name="...">` (no `classname`) - `<failure message="failed"/>` for failedItems, `"newItem"`/`"deletedItem"` for new/deleted ONLY when `-E/--extendedErrors` is set - Without `-E`, new/deleted items are counted as passed testcases (classic behaviour) To make that last branch possible, `extended_errors` is now plumbed through `Options` β `reg_cli` clap (`-E/--extendedErrors`) β `compare()` library forwarding β `build_junit_xml`. The JS CLI wrapper still owns exit-code semantics for `-E`, but the flag is now dual-forwarded so Rust can also see it. ## New tests Rust (`cargo test -p reg_core --lib`, 6 new tests): - single failure - passed + failed mix - new/deleted with and without extended_errors (both branches) - XML attribute escaping (&, <, >, ") - empty report β self-closing `<testsuite/>` JS (`node --test`, 19 tests across two files, no new deps): - `js/test/cli.test.mjs` β spawns `node dist/cli.mjs`, asserts exit codes (`-I`, `-E`), reg.json schema, JUnit XML **byte-for-byte match** to classic, `-R`, `-X client`, `-F` (regenerate from reg.json, verifies source reg.json is immutable and no diff dir is recreated), stdout formatting, `-D` custom trailer. - `js/test/library.test.mjs` β `compare()` EventEmitter lifecycle (`start`β`compare(xN)`β`complete`), JUnit + reg.json via Rust, `update: true` file copy + `update` event, `additionalDetection: 'client'` and the legacy `enableClientAdditionalDetection: true` alias. Notable compatibility finding documented in the library test header: `wasm32-wasip1-threads` libstd's prestat enumeration only honours the first path segment of a preopen name. `computeWasiSandbox` collapses every touched dir to a single common ancestor, so when all positional dirs live under one scratch (update mode), that scratch must itself be one segment deep at the repo root. The library test scaffolding uses `.libtest-<pid>-<n>-<rand>` for exactly that reason. ## CI New `wasm-test` job runs `cargo test -p reg_core --lib`, then builds the js dist (using the committed `js/reg.wasm`, no wasi-sdk needed) and runs `pnpm --filter ./js test`. The classic `test` job is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(wasm-test): build report-ui before cargo test `reg_core` embeds the report UI bundle via include_str!: let js = include_str!("../../../report/ui/dist/report.js"); let css = include_str!("../../../report/ui/dist/style.css"); So `cargo test -p reg_core --lib` fails at macro expansion unless `report/ui/dist/` has been populated. Run `scripts/build-ui.sh v0.3.0` (same version the root `build:report` npm script uses) before the cargo test step, and enable corepack so the yarn that script invokes is available. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * CLI compat (phase F): -X client assets, -U semantics, favicon, --version, small-set concurrency Closes the remaining classic-reg-cli gaps from the phase-E audit. One notable item (N9, live per-file `compare` events during the diff loop) is deferred as a follow-up β it requires plumbing a stderr-parsing event channel between Rust/Wasm and the worker and is intrusive enough to deserve its own change. ## B1 β `-X client` now emits the browser-side assets `ximgdiffConfig.enabled=true` alone is a no-op without a worker script and wasm binary next to the HTML report. Matching classic reg-cli (src/report.js:158-163): - New `js/ximgdiff.ts` concatenates `worker_pre.js` (mustache-rendered with the wasm URL) + the report-ui `worker.js` + the x-img-diff-js loader and writes `<report-dir>/worker.js` + `detector.wasm` (the x-img-diff-js wasm binary, renamed). - `js/cli.ts` and the library's `compare()` both invoke it when `-X client` is paired with `-R`. - `x-img-diff-js` is now a runtime dep of `@bokuweb/reg-cli-wasm`. - unbuild hook in `build.config.ts` stages `worker_pre.js` and the report-ui worker into `dist/shared/` at build time; `writeXimgdiffAssets` takes an explicit `distDir` from the entry module's `dir()` so chunk splitting doesn't break asset lookup. - `compare()` lazy-loads ximgdiff (dynamic import) to keep the cold path for users who never pass `-X client`. ## B2 β `-U/--update` now matches classic's prune-then-selective-copy Classic (`src/index.js:134-146`): 1. rm from expected: deletedItems βͺ failedItems (so stale baselines don't linger and overwrites are clean). 2. copy actualβexpected: newItems βͺ failedItems. 3. passedItems untouched (preserves mtime, keeps git status clean). Previously we copied every `actualItems` entry, which (a) never pruned deleted baselines (they accumulated in expected/ forever) and (b) needlessly rewrote unchanged files, breaking reg-suit workflows that rely on a clean tree. Implemented in both `js/cli.ts` and `js/index.ts`. ## N2 β Force concurrency=1 when < 20 images Classic (`src/index.js:77`) short-circuits the per-image parallelism for small sets because the ProcessAdaptor spin-up dominates. In our Rust implementation the equivalent is the rayon thread-pool + cross-thread span propagation. Apply the same < 20 heuristic in `crates/reg_core/src/lib.rs`. ## N3 β Favicon data URL in HTML report Classic embeds the PNG as a data URL so the generated HTML is self-contained. `reg_core` now `include_bytes!`es `report/assets/favicon_{success,failure}.png`, base64-encodes at render time, and fills the existing `{{&faviconData}}` template slot. Switches on the report status (Danger β failure favicon, Success β success). ## N4 β `--version` reads from package.json Was `reg-cli-wasm\n` placeholder. Now uses `createRequire` to read the adjacent `package.json` so reg-suit's `reg-cli --version` probe works. ## Tests - `cargo test -p reg_core --lib` β 10 passing. - `pnpm --filter ./js test` β 24 passing. New cases: - `--version` prints semver-shaped string - HTML report embeds a favicon data URL - `-X client` writes worker.js (>10KB) + detector.wasm (>100KB) - `-U` prunes deleted baselines, preserves passed-file mtime - library test: `-U` prunes stale baseline via `compare()` ## Notable deferrals - **N9** (live `compare` events): reg-suit / spinners currently see all per-file events fire synchronously just before `complete`, not as each diff completes. Fixing this needs a WASI-stderr event channel (`REG_CLI_EVT:type:path` lines captured by `printErr` and forwarded via `parentPort.postMessage`). Non-trivial and deserves its own PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * CLI compat (phase G): live compare events + .expect("TODO:") cleanup ## N9 β Live per-file `compare` events during the diff loop Classic reg-cli's `ProcessAdaptor` fires `emitter.emit('compare', ...)` as each image completes, so spinners/progress bars animate. Phase E's wasm port synthesised all events in one burst *after* `complete`, with a comment admitting the limitation. Fixed by streaming them through a tagged WASI stderr channel: - `crates/reg_core/src/lib.rs` emits `emit_progress(kind, path)` which writes `__REG_CLI_EVT__\t{"type":"β¦","path":"β¦"}\n` to stderr. Fired from: - `find_images` post-detection for `new` / `delete` items. - inside `par_iter` immediately after the pixel-diff completes and threshold classification runs (classification was moved into the closure so events fire on whichever rayon thread did the work, not serialised after collect). - New `js/progress.ts` (`createPrintErrHook`) parses those lines out of the WASI stderr stream, forwarding progress events to the caller and passing everything else through to `console.error` so real diagnostics still reach users. Piggybacks on `@tybys/wasm-util`'s `StandardOutput.write` already being line-buffered. - `js/entry.ts` + `js/worker.ts` each install `printErr` on their WASI instance and forward events via `parentPort.postMessage({ cmd: 'compare-event', event })`. - `js/index.ts` `run()` handles `compare-event` messages from both the main entry worker AND every spawned rayon thread worker, emitting them live on the outer EventEmitter. - The old post-complete batched emission is removed β `emitter.on('compare', β¦)` now sees events live, same as classic. New library test `compare() emits compare events LIVE, before complete` asserts the ordering so we don't silently regress. ## M1 / M2 β Drop the `.expect("TODO:")` and the stale input-validation TODO `create_dir_for_json_report` previously `.expect`ed on `url::Url::join` failures, which would crash with the useless panic message `"TODO:"` if the `--urlPrefix` value ever produced an unjoinable URL (unlikely after clap parses it as `url::Url`, but still). Swapped for a graceful fallback to the relative path + a `tracing::warn!` log, matching classic reg-cli's silent-fallthrough behaviour. The adjacent `// TODO: please validate input on cli input.` comment is removed as the edit subsumes it. ## Tests - `cargo test -p reg_core --lib` β 10/10. - `pnpm --filter ./js test` β 25/25 (one new: live-event ordering). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * CLI compat (phase H): replace glob::glob with direct walker to fix silent find_images==[] for multi-segment preopens ## The bug When `computeWasiSandbox` registered a preopen whose mapped path had more than one `/`-separated segment, `find_images` returned ZERO results β silent data loss: reg.json has every list empty, exit code 0, HTML says "success". CI goes green while no images were compared. Reproduction: preopen | find_images result ---------------------------|------------------- ./repro (1seg) | β images found ./repro/inner (2seg) | β empty set ./repro/a/b/c (3seg) | β empty set Who hit it: - `reg-cli packages/app/screenshots/actual β¦` (monorepos) π₯ - `reg-cli /Users/alice/project/screens/actual β¦` (absolute paths) π₯ - `reg-cli screens/actual β¦` (flat) β ## Root cause (properly diagnosed this time) Not wasi-sdk. Not @tybys/wasm-util. Not Rust toolchain. All three were tested (wasm-util 0.9.0 β 0.10.1, Rust nightly-2025-01-01 β nightly-2026-04-18, Node 20 β 22) with no change. The actual culprit: **the `glob` crate**. For pattern `deep/a/b/actual/**/*`, glob walks from cwd opening each intermediate directory (`.` β `deep` β `deep/a` β `deep/a/b` β `deep/a/b/actual`). Under our WASI sandbox the preopen IS `./deep/a/b` β so `.`, `deep`, and `deep/a` are OUTSIDE the sandbox. `read_dir(.)` fails with EBADF, glob silently swallows the error (`.flatten()` in our filter code also contributed), and the iterator produces zero items. Verified by instrumenting entry.ts's WASI imports: direct `std::fs::read_dir("deep/a/b/actual")` SUCCEEDS via `path_open(fd=3, "actual")`. Only glob's walk-from-cwd strategy fails. ## The fix Replace `glob::glob` in `find_images` with a direct recursive `std::fs::read_dir(root)` walker (`walk_images`). Starts AT the sandboxed directory instead of traversing to it. No intermediate `read_dir(".")` calls. Multi-segment preopens now Just Work with the original narrowest-ancestor sandbox β no widening, no tradeoffs, no warnings. Also drops the unused `glob` and `path-clean` crates from `crates/reg_core/Cargo.toml`. ## Tests `js/test/cli.test.mjs`: - `multi-segment positional dirs still discover images` β deep path (`.phase-h-deep/<pid>/nested/level/...`), asserts `actualItems` is populated despite the preopen being 4+ segments deep. - `multi-segment positional dirs: nested subdirs still discover images` β image at `actual/sub/a.png` under a multi-segment preopen, verifies both recursion AND the actual_dir-relative path in reg.json. Full JS: 27/27 passing. Rust: 10/10 unchanged. ## Why this PR is better than the earlier "truncate to first segment" attempt The first revision of this PR shipped a JS-side workaround that truncated multi-segment preopens to their first segment (widening the sandbox β `./packages/app/screens` β `./packages`, or even `/Users/alice/β¦` β `/Users`). That avoided the bug but exposed strictly more of the host than needed. The direct walker approach fixes the ACTUAL bug: `find_images` now works correctly against the narrowest-ancestor preopen, and monorepo / absolute path users get the same sandbox privileges as flat-layout users. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * CLI compat (phase I): reg-suit drop-in β strip ignoreChange + enableCliAdditionalDetection before forwarding ## The bug reg-viz/reg-suit's `packages/reg-suit-core/src/processor.ts:105-116` invokes `compare({β¦})` with two keys the Wasm port's Rust clap layer does not recognise: compare({ actualDir, expectedDir, diffDir, json, report, update: false, ignoreChange: true, // β not a Rust clap flag urlPrefix: '', β¦ enableCliAdditionalDetection: true, // β not a Rust clap flag enableClientAdditionalDetection: β¦, β¦ }) `compare()` was forwarding unknown keys verbatim as `--foo value` pairs. When reg-suit calls us, Rust clap aborts with `error: unexpected argument '--ignoreChange' found` before the diff loop even runs. ## The fix Add both to `CLI_ONLY_KEYS` in `js/index.ts`. Semantically both are no-ops at the EventEmitter layer: - `ignoreChange` only governs classic reg-cli's process exit code; the library never needed it. - `enableCliAdditionalDetection` was classic's flag for running an extra CLI-side x-img-diff pass during the diff. Our Wasm pipeline already produces the final pass/fail classification, so toggling it changes nothing. (The *client*-side variant is handled separately via `additionalDetection: 'client'` / the legacy `enableClientAdditionalDetection: true` alias.) Both keys also added to the `CompareInput` TS type so TypeScript users can pass them without a `// @ts-ignore` dance. ## Tests New `js/test/library.test.mjs` case `reg-suit compat: compare() accepts reg-suit-shaped options without aborting` β constructs the exact option bag reg-suit's `processor.ts` passes and verifies `complete` fires cleanly with the expected reg.json. If someone later removes a key from `CLI_ONLY_KEYS`, this catches it. Full JS: 28/28 passing. Rust: 10/10 unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Potential fix for pull request finding 'CodeQL / Workflow does not contain permissions' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * ci: pin GitHub Actions to SHAs via pinact Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * reg_core: tolerate per-image read/decode failures Wrap each rayon-thread image into a `ImageOutcome::{Ok,Failed}` rather than propagating a single Err out of the closure. On read or decode failure: log to stderr, fire `compare-event{type:"fail"}` for live progress, and fold into `failedItems` so callers (CLI, EventEmitter consumers) see a normal end-of-run result. Matches classic reg-cli's tolerance β one corrupt PNG must not abort a batch of 1000 images. Adds tempfile dev-dep + 3 unit tests: - corrupt PNG β failedItems, neighbour passes - non-image extensions stay silently filtered Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * js tests: backfill JS-branch parity + corrupt-image edge cases cli.test.mjs (+6): - -T 0.00 / -T 1.00 thresholdRate boundaries - -S 0 / -S 10000000 thresholdPixel boundaries - identical dirs β passedItems all populated - actual empty β all baselines surface as deletedItems - corrupt PNG β failedItems entry, sibling still passes, exit 1 - non-image files (.md, .json) silently skipped library.test.mjs (+2): - reg-suit drop-in: every event + CompareOutput field processor.ts consumes (start/compare/complete/error + failed/new/deleted/passed) - compare() does NOT fire `error` on single-image decode failure; bad.png β failedItems + 'fail' compare-event, run completes js/reg.wasm rebuilt against the updated reg_core (per-image error tolerance) so CI exercises the new behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: drop @pyroscope/nodejs from root deps + remove pnpm version conflict Two CI failures on PR #604: - `test` job: pnpm-lock.yaml didn't list @pyroscope/nodejs (we adopted main's lock during the merge), so --frozen-lockfile rejected the package.json that still had it. @pyroscope is only used by the wasm wrapper (`js/`); it doesn't belong in the root JS reg-cli's deps. - `wasm-test` job: pnpm/action-setup with `version: 10.26.2` conflicted with root package.json's `packageManager: pnpm@10.33.2+...`. Drop the explicit version and let action-setup read packageManager. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Remove legacy JS implementation, tests, and dependencies The Wasm-backed reg-cli (under js/ and crates/) is now the single source of truth β its 12 cargo tests + 38 node:test cases cover classic reg.json/junit schema parity, every reg-suit processor.ts option/event/CompareOutput field, and per-image read/decode failure tolerance. Keeping the legacy JS impl alongside it doubled the CI matrix and the dep surface (puppeteer, babel, ava, flowβ¦) for no incremental coverage. Removed ------- - src/, test/, decls/, sample/, resource/ β legacy JS source/tests/fixtures - .babelrc, .flowconfig β legacy build/lint config - .travis.yml, appveyor.yml, docker-compose.yml β legacy CI/dev infra - fixture.html, index.html, reg.json β legacy generated artefacts - report/{index.html,worker.js,sample/,diff/} β legacy demo HTML - root package.json deps (babel, ava, puppeteer, flow-bin, img-diff-js, x-img-diff-js, lodash, etc.) and the pnpm.overrides scaffolding that existed solely to neutralise transitive vulns in those legacy deps Kept (consumed by the Wasm pipeline) ------------------------------------ - template/{template.html,worker_pre.js} β `include_str!` from reg_core - report/assets/favicon_{success,failure}.png β `include_bytes!` from reg_core - scripts/{build-ui.sh,build-wasm.sh} β wasm + report-ui build entry points - crates/, js/ β the actual implementation - bench/, docs/ β benchmarks + user docs CI -- - Drop the legacy `test` job (puppeteer screenshot + ava). Rename the former `wasm-test` to `test`; it remains the only required check. Root package.json is now `private: true` and minimal. The publishable package lives under js/ as `@bokuweb/reg-cli-wasm` (renaming back to `reg-cli` is a separate decision). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Collapse js/ into the repo root so the published package == the repo The Wasm wrapper used to live under `js/` while the legacy JS impl sat at the root. After #605 dropped the legacy impl the dual layout was no longer load-bearing, just confusing β `package.json` at the root was a stub `private: true` shim while the real package.json that publishes to npm lived one directory deeper. Moves (preserving git history via `git mv`) ------------------------------------------- - js/{build.config.ts, cli.ts, entry.ts, index.ts, progress.ts, proxy.{js,ts}, tracing.ts, tsconfig.json, utils.ts, worker.ts, ximgdiff.ts, reg.wasm} β root - js/sample/ β sample/ - js/test/ β test/ - js/package.json β root package.json (overwrites the stub; keeps the top-level `packageManager` field and adds a `repository` block) - js/pnpm-lock.yaml β root pnpm-lock.yaml Path adjustments ---------------- - build.config.ts: `repoRoot = resolve(here, '..')` β just `here`. - test/{cli,library}.test.mjs: REPO/SAMPLE_REL/CLI/DIST/TMP_ROOT_* drop the leading `js/` segment. - scripts/build-wasm.sh: copies to `./reg.wasm` (was `js/reg.wasm`). - .github/workflows/ci.yml: drop `working-directory: js` from install/ build/test steps; rename them. - .gitignore: collapse the workspace path. Verification ------------ - `pnpm install --frozen-lockfile` β - `pnpm build` β (dist regenerated; `reg-cli-wasm.*` chunks identical byte-for-byte to pre-collapse) - `pnpm test` β 38 / 38 β - `cargo +stable test -p reg_core --lib --locked` β 12 / 12 β - `npm pack --dry-run` β publishable as `@bokuweb/reg-cli-wasm@0.0.0-experimental6` (32 files, 926 kB) - end-to-end: `node ./dist/cli.mjs ./sample/actual ./sample/expected /tmp/diff -I` exits 0 with classic per-file output. The package still publishes as `@bokuweb/reg-cli-wasm`. Renaming back to `reg-cli` is the next step once a publish dry-run lands cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Move TS sources back into src/ β keep root limited to package metadata Reverts the visual flatness from the previous collapse. Having a dozen .ts files at the repo root next to package.json / Cargo.toml / README.md was hard to scan; the npm convention is `src/` for the TypeScript sources and the root for package metadata + build config. Moved (history-preserving git mv): cli.ts, entry.ts, index.ts, progress.ts, proxy.{js,ts}, tracing.ts, utils.ts, worker.ts, ximgdiff.ts β src/ Build config: - build.config.ts entries β ./src/{index,cli,worker,entry}.ts - tsconfig.json `include` β ./src/**/*.ts Stays at root: package.json, pnpm-lock.yaml, build.config.ts, tsconfig.json, reg.wasm, sample/, test/, scripts/, crates/, template/, report/. Verified: pnpm build β identical tarball (`shasum 0d8db1e5...`, 926.6 kB, 32 files) as the flat layout. pnpm test β 38/38. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * scripts: cross-platform wasm build + one-shot release prep build-wasm.sh - Add Linux support (x86_64-linux, arm64-linux). Was macOS-only. - Switch to OS+arch case statement; fail loudly on unsupported triples rather than silently building for the wrong target. - Auto-install the rustup target (wasm32-wasip1-threads) when rustup is present β saves a "first build" footgun. - `set -euo pipefail` (was `set -e`). - Comments on what wasi-sdk + SYSROOT are actually for. scripts/release.sh (new) - One-shot publish prep. Chains: build-ui β build-wasm β pnpm install β pnpm build β npm pack [--dry-run]. - `--pack` writes the .tgz; default is dry-run. - SKIP_UI=1 / SKIP_WASM=1 escape hatches for iterating locally. - REPORT_UI_TAG env var (defaults to v0.3.0, matching CI). package.json scripts: build:wasm β bash ./scripts/build-wasm.sh release:prep β bash ./scripts/release.sh (dry-run pack) release:pack β bash ./scripts/release.sh --pack (writes .tgz) Verified locally: `SKIP_UI=1 SKIP_WASM=1 bash scripts/release.sh` produces a 926.6 kB tarball, 32 files. Full chain (no skips) tested separately via `bash scripts/build-wasm.sh` β wasi-sdk download + cargo build --release succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop the last legacy bits: bench/, empty .npmignore, flow-only prettier opts bench/ Was JS-vs-Wasm trace comparison infrastructure (bench/run.sh runs `dist/cli.js` AND `js/dist/cli.mjs` against the same fixtures). With the legacy JS impl gone there's no second runner to compare against β the only path the script tests is one that no longer exists. Delete the directory rather than leave a broken benchmark. .npmignore 0-byte file. The `files: ["dist"]` whitelist in package.json is what actually decides what ships, so .npmignore was redundant *and* empty. .prettierrc Drop `parser: "flow"` (no flow files left β TS files use the TypeScript parser, auto-selected by extension) and the long-deprecated `jsxBracketSameLine` (renamed to `bracketSameLine` in prettier 2.4). Verified end-to-end with the BUILT tarball: $ bash scripts/release.sh --pack β bokuweb-reg-cli-wasm-0.0.0-experimental6.tgz (926.6 kB, 32 files) $ npm install /path/to/.tgz # in a scratch project $ node run.mjs # mirrors reg-suit/processor.ts:18 β 'start' fires, 'compare' fires per file, 'complete' arrives with failedItems / newItems / deletedItems / passedItems all arrays, no 'error' event. Drop-in compat confirmed. $ node node_modules/.../dist/cli.mjs ./actual ./expected ./diff -I β writes diff/sample0.png, exits 0. cargo test -p reg_core --lib --locked β 12 / 12 β pnpm test β 38 / 38 β Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings the
wasmbranch closer to mergeable state.maininto wasm β pulls minimatch ReDoS overrides, the-Urestore, version bump to 0.18.16, and other security bumps. Conflict resolution kept wasm's structure (root JS code intact,js/+crates/additions preserved); main's pnpm overrides + onlyBuiltDependencies merged.pinact runβ every action is now hash-pinned with version comments.crates/reg_core/src/lib.rs: a corrupt PNG or unreadable file no longer aborts the whole batch withErr(CompareError). Each failure is logged to stderr, fired as acompare-event{type:"fail"}for live progress, and folded intofailedItemsβ matching classic reg-cli's tolerance (it forks per image, so individual decode failures never sank the run).js/reg.wasmso CI exercises the new behaviour (Cargo.lock pinned to image 0.25.5 / icu 2.0.0 to stay compatible with the project's nightly-2025-01-01 toolchain).test/cli.test.mjsβ adds boundary tests for-T/-S, identical-dirs, all-deleted, plus corrupt-PNG and non-image-file edge cases.library.test.mjs) pins every option/event/CompareOutput field thatreg-suit/processor.tsconsumes β if wasm reg-cli stops being a drop-in for reg-suit, this test fires first.Test plan
cargo +stable test -p reg_core --libβ 12 passed (3 new inper_image_failure_tests)cd js && pnpm testβ 38 passed (8 new across cli + library)sh ./scripts/build-wasm.shsucceeds;js/reg.wasmregeneratedπ€ Generated with Claude Code