reg-cli 0.18.16 → 0.19.0-rc0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -46
- package/dist/cli.cjs +221 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +221 -0
- package/dist/entry.cjs +119 -0
- package/dist/entry.d.cts +15 -0
- package/dist/entry.d.mts +15 -0
- package/dist/entry.mjs +118 -0
- package/dist/index.cjs +212 -0
- package/dist/index.d.cts +52 -0
- package/dist/index.d.mts +52 -0
- package/dist/index.mjs +206 -0
- package/dist/progress-BB7D-xke.mjs +28 -0
- package/dist/progress-UOiKHaTY.cjs +33 -0
- package/dist/reg.wasm +0 -0
- package/dist/rolldown-runtime-D_mwlA32.cjs +43 -0
- package/{report/ui/dist/worker.js → dist/shared/report-worker.js} +859 -247
- package/dist/tracing-7HO8hac9.mjs +378 -0
- package/dist/tracing-DlLL541f.cjs +445 -0
- package/dist/worker.cjs +143 -0
- package/dist/worker.d.cts +1 -0
- package/dist/worker.d.mts +1 -0
- package/dist/worker.mjs +142 -0
- package/dist/ximgdiff-DzkRRnkW.mjs +41 -0
- package/dist/ximgdiff-wmW1BH1H.cjs +41 -0
- package/package.json +61 -112
- package/dist/.keep +0 -0
- package/dist/cli.js +0 -214
- package/dist/diff.js +0 -80
- package/dist/icon.js +0 -12
- package/dist/image-finder.js +0 -25
- package/dist/index.js +0 -200
- package/dist/log.js +0 -21
- package/dist/process-adaptor.js +0 -40
- package/dist/report.js +0 -170
- package/dist/tracing.js +0 -169
- package/report/assets/favicon_failure.png +0 -0
- package/report/assets/favicon_success.png +0 -0
- package/report/sample/actual/sample.png +0 -0
- package/report/sample/diff/sample.png +0 -0
- package/report/sample/expected/sample.png +0 -0
- package/report/ui/dist/report.js +0 -123
- package/report/ui/dist/style.css +0 -1
- package/template/template.html +0 -20
- /package/{template → dist/shared}/worker_pre.js +0 -0
package/README.md
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
|
-
](https://img.shields.io/npm/dm/reg-cli.svg)
|
|
3
|
+
[](https://github.com/reg-viz/reg-cli/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/reg-cli)
|
|
5
|
+
[](https://www.npmjs.com/package/reg-cli)
|
|
7
6
|
|
|
8
|
-
> Visual regression
|
|
7
|
+
> Visual regression testing CLI with HTML reporter — **Wasm-backed**. Drop-in compatible with classic `reg-cli`'s flags, `reg.json` schema, JUnit XML output, and the `compare()` EventEmitter API used by [reg-suit](https://github.com/reg-viz/reg-suit).
|
|
8
|
+
|
|
9
|
+
The diff engine is now **Rust → WebAssembly (WASI threads)** instead of pure JS, giving 1.1×–2.9× wall-clock speedups (see [Performance](#performance) below) while keeping the user-facing surface bit-for-bit compatible.
|
|
9
10
|
|
|
10
11
|
## Table of Contents
|
|
11
12
|
|
|
12
13
|
- [Installation](#installation)
|
|
13
14
|
- [Usage](#usage)
|
|
15
|
+
- [Performance](#performance)
|
|
16
|
+
- [Architecture](#architecture)
|
|
17
|
+
- [Building from source](#building-from-source)
|
|
14
18
|
- [Test](#test)
|
|
15
19
|
- [Contribute](#contribute)
|
|
16
20
|
- [License](#license)
|
|
@@ -19,11 +23,9 @@
|
|
|
19
23
|
|
|
20
24
|
### Requirements
|
|
21
25
|
|
|
22
|
-
- Node.js
|
|
23
|
-
|
|
24
|
-
`reg-cli` support Node.js v18+
|
|
26
|
+
- Node.js v20+
|
|
25
27
|
|
|
26
|
-
```
|
|
28
|
+
```sh
|
|
27
29
|
$ npm i -D reg-cli
|
|
28
30
|
```
|
|
29
31
|
|
|
@@ -31,65 +33,173 @@ $ npm i -D reg-cli
|
|
|
31
33
|
|
|
32
34
|
### CLI
|
|
33
35
|
|
|
34
|
-
```
|
|
36
|
+
```sh
|
|
35
37
|
$ reg-cli /path/to/actual-dir /path/to/expected-dir /path/to/diff-dir -R ./report.html
|
|
36
38
|
```
|
|
37
39
|
|
|
38
40
|
#### Options
|
|
39
41
|
|
|
40
|
-
* `-U`, `--update` Update expected images.(Copy
|
|
41
|
-
* `-R`, `--report` Output
|
|
42
|
-
* `-J`, `--json`
|
|
42
|
+
* `-U`, `--update` Update expected images. (Copy `actual` images to `expected` images.)
|
|
43
|
+
* `-R`, `--report` Output HTML report to specified path.
|
|
44
|
+
* `-J`, `--json` JSON report path. If omitted: `./reg.json`.
|
|
45
|
+
* `--junit` JUnit XML report path.
|
|
43
46
|
* `-I`, `--ignoreChange` If true, error will not be thrown when image change detected.
|
|
44
47
|
* `-E`, `--extendedErrors` If true, also added/deleted images will throw an error.
|
|
45
|
-
* `-P`, `--urlPrefix` Add prefix to all image src.
|
|
46
|
-
* `-M`, `--matchingThreshold` Matching threshold, ranges from 0 to 1. Smaller values make the comparison more sensitive. 0 by default.
|
|
47
|
-
* `-T`, `--thresholdRate` Rate threshold for detecting change. When the difference ratio of the image is larger than the set rate
|
|
48
|
-
* `-S`, `--thresholdPixel` Pixel threshold for detecting change. When the difference pixel
|
|
49
|
-
* `-C`, `--concurrency` How many
|
|
50
|
-
* `-A`, `--enableAntialias` Enable antialias.
|
|
51
|
-
*
|
|
52
|
-
* `-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
* `-P`, `--urlPrefix` Add prefix to all image src in `reg.json`.
|
|
49
|
+
* `-M`, `--matchingThreshold` Matching threshold, ranges from 0 to 1. Smaller values make the comparison more sensitive. 0 by default. Tunes the YIQ pixel-difference threshold inside the diff lib.
|
|
50
|
+
* `-T`, `--thresholdRate` Rate threshold for detecting change. When the difference ratio of the image is larger than the set rate, change is reported. Applied after `matchingThreshold`. 0 by default.
|
|
51
|
+
* `-S`, `--thresholdPixel` Pixel threshold for detecting change. When the difference pixel count is larger than the set value, change is reported. This value takes precedence over `thresholdRate`. Applied after `matchingThreshold`. 0 by default.
|
|
52
|
+
* `-C`, `--concurrency` How many threads run the per-image diff in parallel. Default: 4. The Wasm version uses Rayon inside the WASI thread pool; below 20 images we fall back to single-threaded to avoid spin-up cost (matches classic reg-cli).
|
|
53
|
+
* `-A`, `--enableAntialias` Enable antialias-tolerant comparison. Off by default.
|
|
54
|
+
* `--diffFormat` Output diff image format: `webp` (default) or `png`. Use `png` for byte-for-byte parity with classic reg-cli's diff images.
|
|
55
|
+
* `-X`, `--additionalDetection` Enable additional difference detection (highly experimental). Select `none` (default) or `client` for the in-browser second-pass detector.
|
|
56
|
+
* `-F`, `--from` Generate report from an existing `reg.json` instead of running the comparison.
|
|
57
|
+
* `-D`, `--diffMessage` Custom diff message printed when a comparison fails.
|
|
58
|
+
|
|
59
|
+
### HTML report
|
|
60
|
+
|
|
61
|
+
If `-R` is set, an HTML report is written to the specified path.
|
|
57
62
|
https://reg-viz.github.io/reg-cli/
|
|
58
63
|
|
|
59
|
-

|
|
65
|
+

|
|
66
|
+

|
|
62
67
|
|
|
63
|
-
###
|
|
68
|
+
### From JSON
|
|
64
69
|
|
|
65
|
-
If `-F`
|
|
70
|
+
If `-F` is set, only the report is rendered — no image comparison runs.
|
|
66
71
|
|
|
67
|
-
```
|
|
68
|
-
reg-cli
|
|
72
|
+
```sh
|
|
73
|
+
$ reg-cli -F ./sample/reg.json -R ./sample/index.html
|
|
69
74
|
```
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
JSON format:
|
|
77
|
+
|
|
78
|
+
```json
|
|
73
79
|
{
|
|
74
80
|
"failedItems": ["sample.png"],
|
|
75
|
-
"newItems":[],
|
|
76
|
-
"deletedItems":[],
|
|
77
|
-
"passedItems":[],
|
|
78
|
-
"expectedItems":["sample.png"],
|
|
79
|
-
"actualItems":["sample.png"],
|
|
80
|
-
"diffItems":["sample.png"],
|
|
81
|
-
"actualDir":"./actual",
|
|
82
|
-
"expectedDir":"./expected",
|
|
83
|
-
"diffDir":"./diff"
|
|
81
|
+
"newItems": [],
|
|
82
|
+
"deletedItems": [],
|
|
83
|
+
"passedItems": [],
|
|
84
|
+
"expectedItems": ["sample.png"],
|
|
85
|
+
"actualItems": ["sample.png"],
|
|
86
|
+
"diffItems": ["sample.png"],
|
|
87
|
+
"actualDir": "./actual",
|
|
88
|
+
"expectedDir": "./expected",
|
|
89
|
+
"diffDir": "./diff"
|
|
84
90
|
}
|
|
85
91
|
```
|
|
86
92
|
|
|
93
|
+
### Library
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
import { compare } from 'reg-cli';
|
|
97
|
+
|
|
98
|
+
const emitter = compare({
|
|
99
|
+
actualDir: './actual',
|
|
100
|
+
expectedDir: './expected',
|
|
101
|
+
diffDir: './diff',
|
|
102
|
+
json: './reg.json',
|
|
103
|
+
report: './report.html',
|
|
104
|
+
threshold: 0,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
emitter.on('start', () => console.log('start'));
|
|
108
|
+
emitter.on('compare', ({ type, path }) => console.log(type, path));
|
|
109
|
+
emitter.on('error', (e) => console.error(e));
|
|
110
|
+
emitter.on('complete', (data) => {
|
|
111
|
+
console.log(data.failedItems, data.newItems, data.deletedItems, data.passedItems);
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The full option set, event surface, and `CompareOutput` shape match what [`reg-suit`'s `processor.ts`](https://github.com/reg-viz/reg-suit/blob/main/packages/reg-suit-core/src/processor.ts) expects — a regression test in this repo locks that in (`test/library.test.mjs`).
|
|
116
|
+
|
|
117
|
+
## Performance
|
|
118
|
+
|
|
119
|
+
Apples-to-apples vs `reg-cli@0.18.16` (last legacy JS release), `--diffFormat png` on both sides, 5 timed runs after 1 warmup, median wall-clock on macOS (Apple Silicon) / Node v20.19.0:
|
|
120
|
+
|
|
121
|
+
| Workload | JS reg-cli@0.18.16 | reg-cli@0.19.0-rc0 | Wasm speedup |
|
|
122
|
+
|---|---:|---:|---:|
|
|
123
|
+
| 20 × 1280×720 | 0.56 s | 0.49 s | **1.14×** |
|
|
124
|
+
| 100 × 1280×720 | 1.94 s | 1.44 s | **1.35×** |
|
|
125
|
+
| 1 × 3840×2160 (4K) | 1.89 s | 0.66 s | **2.86×** |
|
|
126
|
+
|
|
127
|
+
The gap widens with image size and count: small fixtures are dominated by JS startup, but per-image compute is where the Rust + Rayon path shines. Wasm also has lower run-to-run variance than the JS version (±5% vs ±10% at 4K).
|
|
128
|
+
|
|
129
|
+
The default `--diffFormat` is **webp**, which is a few % slower than PNG (the encoder is heavier) but produces ~5× smaller diff artefacts. Pass `--diffFormat png` for parity with classic reg-cli.
|
|
130
|
+
|
|
131
|
+
## Architecture
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
135
|
+
│ Node.js host (src/index.ts, src/cli.ts, src/entry.ts) │
|
|
136
|
+
│ │
|
|
137
|
+
│ • CLI argv parsing, EventEmitter API (`compare()`) │
|
|
138
|
+
│ • -U (update mode), -F (re-render from reg.json), │
|
|
139
|
+
│ -X client asset staging, -P urlPrefix application │
|
|
140
|
+
│ • Spawns the WASM entry as a worker_thread, then │
|
|
141
|
+
│ additional thread workers per Rayon thread spawn │
|
|
142
|
+
└──────────────────────────┬──────────────────────────────────┘
|
|
143
|
+
│ worker_threads + WASI preopens
|
|
144
|
+
▼
|
|
145
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
146
|
+
│ Wasm32-WASIp1-threads bundle (reg.wasm, ~2.5 MB) │
|
|
147
|
+
│ Compiled from `crates/reg_cli` + `crates/reg_core` │
|
|
148
|
+
│ │
|
|
149
|
+
│ • clap CLI layer (crates/reg_cli/src/main.rs) │
|
|
150
|
+
│ • Image walker (crates/reg_core/src/dir.rs) — walks the │
|
|
151
|
+
│ actual/expected dirs, intersects, classifies new/del. │
|
|
152
|
+
│ • Per-image diff in a Rayon thread pool │
|
|
153
|
+
│ (image-diff-rs → pixelmatch-rs port) │
|
|
154
|
+
│ • Per-file errors are logged + folded into failedItems │
|
|
155
|
+
│ rather than aborting the run (matches classic reg-cli's │
|
|
156
|
+
│ fork-per-image tolerance). │
|
|
157
|
+
│ • reg.json + JUnit XML + HTML report writers │
|
|
158
|
+
│ (templates: template/template.html, report/assets/*) │
|
|
159
|
+
└─────────────────────────────────────────────────────────────┘
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Why Wasm instead of native?
|
|
163
|
+
- **Portable** — the same `reg.wasm` runs on Linux, macOS, Windows. No prebuilds, no node-gyp.
|
|
164
|
+
- **Sandboxed** — file I/O is constrained to WASI preopens declared from the JS host's positional dirs. A misbehaving image can't escape into your filesystem.
|
|
165
|
+
- **Threading** — `wasm32-wasip1-threads` exposes `pthread`, so Rayon's `par_iter` works inside the sandbox; image diffs run in parallel across CPU cores.
|
|
166
|
+
|
|
167
|
+
The `compare-event` channel from Rust to JS is implemented as a stderr-tagged line protocol (`__REG_CLI_EVT__\t{...}`) parsed by `src/progress.ts` and re-emitted on the EventEmitter. That's how live per-file `compare` events fire before `complete`.
|
|
168
|
+
|
|
169
|
+
## Building from source
|
|
170
|
+
|
|
171
|
+
`reg.wasm` is committed so most contributors don't need to install the Rust + wasi-sdk toolchain. To rebuild it (and the report-ui assets that `reg_core` embeds via `include_str!`):
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
# 1. Build report-ui (clones reg-cli-report-ui at v0.5.0, builds with pnpm)
|
|
175
|
+
sh ./scripts/build-ui.sh v0.5.0
|
|
176
|
+
|
|
177
|
+
# 2. Build reg.wasm (downloads wasi-sdk on first run, then cargo build --release)
|
|
178
|
+
bash ./scripts/build-wasm.sh
|
|
179
|
+
# or: pnpm build:wasm
|
|
180
|
+
|
|
181
|
+
# 3. Bundle everything into dist/
|
|
182
|
+
pnpm build
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
One-shot publish prep (the same chain plus `npm pack`):
|
|
186
|
+
|
|
187
|
+
```sh
|
|
188
|
+
pnpm release:prep # → npm pack --dry-run
|
|
189
|
+
pnpm release:pack # → writes the .tgz
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`scripts/build-wasm.sh` works on macOS (arm64 / x86_64) and Linux (x86_64 / arm64). It auto-installs the `wasm32-wasip1-threads` rustup target if `rustup` is available.
|
|
193
|
+
|
|
87
194
|
## Test
|
|
88
195
|
|
|
89
196
|
```sh
|
|
90
|
-
$
|
|
197
|
+
$ pnpm test # → 50 node:test cases (CLI + library)
|
|
198
|
+
$ cargo test -p reg_core --lib --locked # → 12 Rust unit tests (Linux / macOS)
|
|
91
199
|
```
|
|
92
200
|
|
|
201
|
+
CI runs both the JS tests and `cargo test` on Ubuntu and macOS. **Windows is not in the matrix:** Node's built-in WASI runtime returns `EINVAL` when the wasm bin writes to preopened relative paths (`reg.json` / `report.html` / `diff/*.png`) — a bug in Node's WASI path translation, not in `reg.wasm` itself. Until that's fixed upstream, Windows users should run reg-cli under WSL or git-bash; the wasm bin itself is OS-independent.
|
|
202
|
+
|
|
93
203
|
## Contribute
|
|
94
204
|
|
|
95
205
|
PRs welcome.
|
|
@@ -107,4 +217,3 @@ The above copyright notice and this permission notice shall be included in all c
|
|
|
107
217
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
108
218
|
|
|
109
219
|

|
|
110
|
-
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require("./rolldown-runtime-D_mwlA32.cjs");
|
|
3
|
+
const require_index = require("./index.cjs");
|
|
4
|
+
const require_ximgdiff = require("./ximgdiff-wmW1BH1H.cjs");
|
|
5
|
+
let node_fs_promises = require("node:fs/promises");
|
|
6
|
+
let node_path = require("node:path");
|
|
7
|
+
let node_module = require("node:module");
|
|
8
|
+
let node_util = require("node:util");
|
|
9
|
+
//#region src/cli.ts
|
|
10
|
+
const HELP = `
|
|
11
|
+
Usage
|
|
12
|
+
$ reg-cli /path/to/actual-dir /path/to/expected-dir /path/to/diff-dir
|
|
13
|
+
Options
|
|
14
|
+
-U, --update Update expected images (copy actual → expected).
|
|
15
|
+
-R, --report Output html report to specified path.
|
|
16
|
+
-J, --json Output json report to specified path (default ./reg.json).
|
|
17
|
+
-I, --ignoreChange Exit 0 even when image changes are detected.
|
|
18
|
+
-E, --extendedErrors Also treat added/deleted images as failures.
|
|
19
|
+
-F, --from Render HTML report from an existing reg.json (no diff).
|
|
20
|
+
-X, --additionalDetection "none" | "client" — enable browser-side second-pass detection.
|
|
21
|
+
-P, --urlPrefix Prefix for image src in html report.
|
|
22
|
+
-M, --matchingThreshold YIQ threshold (0-1). Default 0.
|
|
23
|
+
-T, --thresholdRate Ratio of pixels that may differ before failing.
|
|
24
|
+
-S, --thresholdPixel Absolute pixel count that may differ before failing.
|
|
25
|
+
-C, --concurrency Parallel worker count. Default 4.
|
|
26
|
+
-A, --enableAntialias Count anti-aliased pixels as different.
|
|
27
|
+
-D, --customDiffMessage Trailing message printed on diff.
|
|
28
|
+
--junit Path to write a JUnit XML test report.
|
|
29
|
+
--diffFormat webp (default) | png
|
|
30
|
+
`;
|
|
31
|
+
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
32
|
+
process.stdout.write(HELP);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
if (process.argv.includes("--version")) {
|
|
36
|
+
try {
|
|
37
|
+
const pkg = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("../package.json");
|
|
38
|
+
process.stdout.write(`${pkg.version ?? "unknown"}\n`);
|
|
39
|
+
} catch {
|
|
40
|
+
process.stdout.write("unknown\n");
|
|
41
|
+
}
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = (0, node_util.parseArgs)({
|
|
47
|
+
args: process.argv.slice(2),
|
|
48
|
+
options: {
|
|
49
|
+
update: {
|
|
50
|
+
type: "boolean",
|
|
51
|
+
short: "U"
|
|
52
|
+
},
|
|
53
|
+
json: {
|
|
54
|
+
type: "string",
|
|
55
|
+
short: "J"
|
|
56
|
+
},
|
|
57
|
+
ignoreChange: {
|
|
58
|
+
type: "boolean",
|
|
59
|
+
short: "I"
|
|
60
|
+
},
|
|
61
|
+
extendedErrors: {
|
|
62
|
+
type: "boolean",
|
|
63
|
+
short: "E"
|
|
64
|
+
},
|
|
65
|
+
report: {
|
|
66
|
+
type: "string",
|
|
67
|
+
short: "R"
|
|
68
|
+
},
|
|
69
|
+
urlPrefix: {
|
|
70
|
+
type: "string",
|
|
71
|
+
short: "P"
|
|
72
|
+
},
|
|
73
|
+
matchingThreshold: {
|
|
74
|
+
type: "string",
|
|
75
|
+
short: "M"
|
|
76
|
+
},
|
|
77
|
+
thresholdRate: {
|
|
78
|
+
type: "string",
|
|
79
|
+
short: "T"
|
|
80
|
+
},
|
|
81
|
+
thresholdPixel: {
|
|
82
|
+
type: "string",
|
|
83
|
+
short: "S"
|
|
84
|
+
},
|
|
85
|
+
concurrency: {
|
|
86
|
+
type: "string",
|
|
87
|
+
short: "C"
|
|
88
|
+
},
|
|
89
|
+
enableAntialias: {
|
|
90
|
+
type: "boolean",
|
|
91
|
+
short: "A"
|
|
92
|
+
},
|
|
93
|
+
customDiffMessage: {
|
|
94
|
+
type: "string",
|
|
95
|
+
short: "D"
|
|
96
|
+
},
|
|
97
|
+
diffFormat: { type: "string" },
|
|
98
|
+
junit: { type: "string" },
|
|
99
|
+
from: {
|
|
100
|
+
type: "string",
|
|
101
|
+
short: "F"
|
|
102
|
+
},
|
|
103
|
+
additionalDetection: {
|
|
104
|
+
type: "string",
|
|
105
|
+
short: "X"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
allowPositionals: true
|
|
109
|
+
});
|
|
110
|
+
} catch (err) {
|
|
111
|
+
process.stderr.write(`reg-cli: ${err.message}\n`);
|
|
112
|
+
process.stderr.write(HELP);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
const { values, positionals } = parsed;
|
|
116
|
+
const [actualDir, expectedDir, diffDir] = positionals;
|
|
117
|
+
const fromPath = typeof values.from === "string" ? values.from : void 0;
|
|
118
|
+
if (!fromPath && (!actualDir || !expectedDir || !diffDir)) {
|
|
119
|
+
process.stderr.write("reg-cli: please specify actual, expected and diff directories.\n");
|
|
120
|
+
process.stderr.write(HELP);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
const update = !!values.update;
|
|
124
|
+
const ignoreChange = !!values.ignoreChange;
|
|
125
|
+
const extendedErrors = !!values.extendedErrors;
|
|
126
|
+
const diffFormat = typeof values.diffFormat === "string" ? values.diffFormat : "png";
|
|
127
|
+
const jsonPath = typeof values.json === "string" ? values.json : "./reg.json";
|
|
128
|
+
const customDiffMessage = typeof values.customDiffMessage === "string" ? values.customDiffMessage : `\nInspect your code changes, re-run with \`-U\` to update them. `;
|
|
129
|
+
const wasmArgv = ["--"];
|
|
130
|
+
if (actualDir) wasmArgv.push(actualDir);
|
|
131
|
+
if (expectedDir) wasmArgv.push(expectedDir);
|
|
132
|
+
if (diffDir) wasmArgv.push(diffDir);
|
|
133
|
+
const pushFlag = (name, v) => {
|
|
134
|
+
if (v == null || v === false) return;
|
|
135
|
+
if (v === true) wasmArgv.push(`--${name}`);
|
|
136
|
+
else wasmArgv.push(`--${name}`, String(v));
|
|
137
|
+
};
|
|
138
|
+
pushFlag("report", values.report);
|
|
139
|
+
pushFlag("json", jsonPath);
|
|
140
|
+
pushFlag("junit", values.junit);
|
|
141
|
+
pushFlag("extendedErrors", values.extendedErrors);
|
|
142
|
+
pushFlag("from", fromPath);
|
|
143
|
+
pushFlag("additionalDetection", values.additionalDetection);
|
|
144
|
+
pushFlag("matchingThreshold", values.matchingThreshold);
|
|
145
|
+
pushFlag("thresholdRate", values.thresholdRate);
|
|
146
|
+
pushFlag("thresholdPixel", values.thresholdPixel);
|
|
147
|
+
pushFlag("urlPrefix", values.urlPrefix);
|
|
148
|
+
pushFlag("concurrency", values.concurrency);
|
|
149
|
+
pushFlag("enableAntialias", values.enableAntialias);
|
|
150
|
+
pushFlag("diffFormat", diffFormat);
|
|
151
|
+
const CHECK = "✔";
|
|
152
|
+
const CROSS = "✘";
|
|
153
|
+
const PLUS = "✚";
|
|
154
|
+
const MINUS = "−";
|
|
155
|
+
const formatPath = (p) => actualDir ? (0, node_path.join)(actualDir, p) : p;
|
|
156
|
+
const emitter = require_index.run(wasmArgv);
|
|
157
|
+
emitter.once("complete", async (data) => {
|
|
158
|
+
const failed = data.failedItems ?? [];
|
|
159
|
+
const passed = data.passedItems ?? [];
|
|
160
|
+
const added = data.newItems ?? [];
|
|
161
|
+
const deleted = data.deletedItems ?? [];
|
|
162
|
+
if (values.additionalDetection === "client" && typeof values.report === "string") try {
|
|
163
|
+
await require_ximgdiff.writeXimgdiffAssets({
|
|
164
|
+
reportPath: values.report,
|
|
165
|
+
urlPrefix: typeof values.urlPrefix === "string" ? values.urlPrefix : "",
|
|
166
|
+
distDir: require_index.dir()
|
|
167
|
+
});
|
|
168
|
+
} catch (e) {
|
|
169
|
+
process.stderr.write(`reg-cli: failed to write ximgdiff assets — ${e.message}\n`);
|
|
170
|
+
process.exitCode = 1;
|
|
171
|
+
}
|
|
172
|
+
for (const img of passed) process.stdout.write(`${CHECK} pass ${formatPath(img)}\n`);
|
|
173
|
+
for (const img of added) process.stdout.write(`${PLUS} append ${formatPath(img)}\n`);
|
|
174
|
+
for (const img of deleted) process.stdout.write(`${MINUS} delete ${formatPath(img)}\n`);
|
|
175
|
+
for (const img of failed) process.stdout.write(`${CROSS} change ${formatPath(img)}\n`);
|
|
176
|
+
process.stdout.write("\n");
|
|
177
|
+
if (failed.length) process.stdout.write(`${CROSS} ${failed.length} file(s) changed.\n`);
|
|
178
|
+
if (deleted.length) process.stdout.write(`${MINUS} ${deleted.length} file(s) deleted.\n`);
|
|
179
|
+
if (added.length) process.stdout.write(`${PLUS} ${added.length} file(s) appended.\n`);
|
|
180
|
+
if (passed.length) process.stdout.write(`${CHECK} ${passed.length} file(s) passed.\n`);
|
|
181
|
+
if (update) {
|
|
182
|
+
if (!actualDir || !expectedDir) {
|
|
183
|
+
process.stderr.write(`reg-cli: --update requires actual/expected dirs (incompatible with --from).\n`);
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
await updateExpected(actualDir, expectedDir, {
|
|
189
|
+
newItems: added,
|
|
190
|
+
failedItems: failed,
|
|
191
|
+
deletedItems: deleted
|
|
192
|
+
});
|
|
193
|
+
process.stdout.write("✨ your expected images are updated ✨\n");
|
|
194
|
+
} catch (e) {
|
|
195
|
+
process.stderr.write(`reg-cli: failed to update expected — ${e.message}\n`);
|
|
196
|
+
process.exitCode = 1;
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (failed.length > 0 || extendedErrors && (added.length > 0 || deleted.length > 0)) {
|
|
201
|
+
process.stdout.write(`${customDiffMessage}\n`);
|
|
202
|
+
if (!ignoreChange) process.exitCode = 1;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
emitter.once("error", (err) => {
|
|
206
|
+
process.stderr.write(`reg-cli: ${err?.message ?? String(err)}\n`);
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
});
|
|
209
|
+
async function updateExpected(actualDir, expectedDir, items) {
|
|
210
|
+
const { rm } = await import("node:fs/promises");
|
|
211
|
+
const toRemove = [...items.deletedItems, ...items.failedItems];
|
|
212
|
+
for (const img of toRemove) await rm((0, node_path.join)(expectedDir, img), { force: true });
|
|
213
|
+
const toCopy = [...items.newItems, ...items.failedItems];
|
|
214
|
+
for (const img of toCopy) {
|
|
215
|
+
const src = (0, node_path.join)(actualDir, img);
|
|
216
|
+
const dst = (0, node_path.join)(expectedDir, img);
|
|
217
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(dst), { recursive: true });
|
|
218
|
+
await (0, node_fs_promises.copyFile)(src, dst);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|