cornhsu-parity 1.1.0 → 2.0.0

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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -31
  3. package/bin/parity.js +18 -11
  4. package/package.json +8 -8
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 許彧銘 Hsu Yu-Ming (Cornhsu)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  ## GitHub Action
12
12
 
13
13
  ```yaml
14
- - uses: HSU-YU-MING/cornhsu-parity@v1 # @v1 tracks the latest 1.x (interfaces frozen since 1.0)
14
+ - uses: HSU-YU-MING/cornhsu-parity@v2 # @v2 tracks the latest 2.x (interfaces frozen within a major)
15
15
  with:
16
16
  config: parity.config.json # optional, defaults to parity.config.json
17
17
  target: /pricing # optional, check only this route (omit for all)
@@ -63,13 +63,13 @@ PR comment, updating the same comment rather than adding new ones.
63
63
 
64
64
  | | |
65
65
  |---|---|
66
- | Releases | 27 on NuGet (v0.1.0 → v1.1.0). Pushing a tag publishes via OIDC Trusted Publishing — zero long-lived keys in the repo |
66
+ | Releases | 28 on NuGet (v0.1.0 → v2.0.0). Pushing a tag publishes via OIDC Trusted Publishing — zero long-lived keys in the repo |
67
67
  | Compared properties | Size, padding, spacing, typography, color (CIEDE2000 ΔE), relative position — **absolute coordinates are deliberately excluded** (they are guaranteed false positives in a flexible layout) |
68
68
  | Design sources | 4: the Figma API, a rendering snapshot, an image + annotations (pixel sampling — any tool that exports a PNG works), or JSON |
69
69
  | Implementation sources | The web (including **shadow DOM, same-origin iframes, and multiple responsive breakpoints**) plus **Electron** (attached to the live window over CDP) |
70
- | Tests | **208**, covering the CIEDE2000 reference data set (Sharma), match disambiguation, position false-positive guards, image sampling, and real-browser capture regressions |
70
+ | Tests | **805**, covering the CIEDE2000 reference data set (Sharma), match disambiguation, position false-positive guards, image sampling, and real-browser capture regressions |
71
71
  | Proven in CI | The GitHub Action is validated by **real PRs in an external repo**: blocking the PR, commenting automatically (updating in place), and baseline regression gating |
72
- | Proven in production | **All 21 pages of cornhsu.com are gated by Parity itself** — dogfooding has already found and fixed three flaky root causes (most recently a scroll-triggered entrance animation; see 0.11.1) |
72
+ | Proven in production | **All 26 pages of cornhsu.com are gated by Parity itself** — dogfooding has already found and fixed three flaky root causes (most recently a scroll-triggered entrance animation; see 0.11.1) |
73
73
 
74
74
  ## Install
75
75
 
@@ -86,13 +86,15 @@ dotnet tool install -g Cornhsu.Parity
86
86
  > The npm build is the same self-contained native executable; you only download the one for
87
87
  > your platform (win32-x64 / win32-arm64 / linux-x64 / linux-arm64 / darwin-x64 / darwin-arm64).
88
88
  > Playwright normally bundles its own copy of Node; the npm build strips that out and uses the
89
- > Node you already have, which saves about 88 MB per platform package.
89
+ > Node you already have, which saves at least 88 MB per platform package. That Node has to be
90
+ > **20 or newer** — the minimum of the bundled Playwright driver (`engines.node` says so).
90
91
 
91
92
  ## Quick start
92
93
 
93
94
  ```sh
94
95
  parity init # Generate a parity.config.json template
95
- parity install-browser # First run only: download Playwright Chromium
96
+ parity install-browser # Download Playwright Chromium: on first run, and again after an
97
+ # upgrade if check says Chromium is not installed
96
98
  export FIGMA_TOKEN=... # Only the file_content:read scope is needed
97
99
  parity check # Compare; writes a report and sets the exit code
98
100
  parity report # Re-render the Markdown report from an existing report.json
@@ -105,6 +107,11 @@ parity lint # Design lint: are the design file's values inside th
105
107
  # design-token set? (design only, no implementation)
106
108
  parity check --reverse # Reverse view, for a designer redrawing an existing page:
107
109
  # how does my draft differ from what ships? (never gates)
110
+ parity bug-report # (experimental) Collect version / OS / Node / Chromium info and print
111
+ # it with a pre-filled GitHub issue link. Parity itself connects to nothing;
112
+ # opening the link sends the masked report to github.com (it rides in
113
+ # the URL). No issue until you submit the form. --config adds a
114
+ # structural summary without URLs, routes, keys, tokens or paths
108
115
  ```
109
116
 
110
117
  When working inside this repo:
@@ -125,24 +132,27 @@ serious-severity gaps — which is exactly the behaviour you want from a CI gate
125
132
  "figmaFileKey": "abcd1234",
126
133
  "designToken": "env:FIGMA_TOKEN",
127
134
  "mapFile": "parity.map.json", // manual fallback mapping (layer name → selector)
128
- "tokensFile": "tokens.json", // optional: design tokens (name → value);
135
+ "tokensFile": "tokens.json", // optional: design tokens, a flat object of string values
136
+ // ({"brand": "#2563EB", "space-4": "16px"}; // comments ok);
129
137
  // suggested fixes will name the matching token
130
138
  "targets": [
131
139
  { "route": "/", "frame": "10:2", "url": "http://localhost:8080/" }
132
140
  ],
133
141
  "compare": { "position": "relative" }, // relative = compare relative position (default); none = skip position
134
- "tolerances": { "sizePx": 2, "spacingPx": 2, "colorDeltaE": 2.0, "positionPx": 4 },
142
+ "tolerances": { "sizePx": 2, "spacingPx": 2, "colorDeltaE": 2.0, "fontSizePx": 0.5, "positionPx": 4 },
135
143
  "ignore": ["[data-parity-ignore]"],
136
144
  "gate": {
137
- "failOn": ["critical", "serious"],
138
- "minMatchRate": 0 // optional: FAIL outright below this match rate (0–1); 0 = no threshold
145
+ "failOn": ["critical", "serious"], // any of minor / medium / serious / critical, one per string
146
+ "minMatchRate": 0 // optional: below this match rate (0–1) → exit 3, see below; 0 = no threshold
139
147
  }
140
148
  }
141
149
  ```
142
150
 
143
151
  > Besides gaps, the gate also validates **match integrity**: zero matches (or zero design nodes)
144
- > is always a GATE FAIL — with nothing matched there are no gaps to catch, so a silent PASS
145
- > would be a false pass, usually caused by a wrong url or frame. `--baseline` mode is not exempt.
152
+ > always fails, with exit code 3 (`MATCH INTEGRITY TOO LOW`) rather than 1 — with nothing
153
+ > matched there are no gaps to catch, so a silent PASS would be a false pass, usually caused by
154
+ > a wrong url or frame. A match rate below `gate.minMatchRate` fails the same way, with exit
155
+ > code 3. `--baseline` mode is not exempt; `--reverse` never gates and always exits 0.
146
156
 
147
157
  ## Targets: a web page or an Electron desktop app
148
158
 
@@ -209,15 +219,20 @@ Only the things that should hold no matter how the layout flows:
209
219
  as references (TEXT/HUG boxes are never references, TEXT is never a target, and Y is honestly
210
220
  skipped when everything above is text), so line-height drift in a flowing layout does not
211
221
  produce false positives. Turn it off with `compare.position: "none"`.
212
- - **Typography**: size, weight, line-height and letter-spacing are compared exactly;
213
- font-family is a **soft gap** and never gates.
222
+ - **Typography**: size and letter-spacing are compared as numbers within `tolerances.fontSizePx`,
223
+ line-height within `tolerances.sizePx`; weight has no tolerance — any difference is a gap
224
+ (under 200 apart is medium, 200 or more is serious). font-family is a **soft gap**: it is
225
+ reported (severity `minor`, marked soft) but costs no fidelity points and **never gates** —
226
+ not even when `failOn` includes `minor`, and `--baseline` mode does not count it as new.
214
227
  - **Color**: thresholded on CIEDE2000 (ΔE), not hex equality. Parsing covers modern syntax
215
228
  (`rgb(37 99 235 / .5)`, `color(srgb …)`, `oklch()`, `color(display-p3 …)`).
216
229
  - **Absolute x/y is deliberately never compared**: it legitimately differs in a flexible layout,
217
230
  so comparing it means false positives, which means losing trust.
218
231
 
219
- > When the design source is Figma, layer names in the report (both the Markdown and the local UI)
220
- > **link back to that node in Figma** — a designer clicks once and lands on the layer.
232
+ > When the design source is Figma, layer names in the report (the Markdown written by
233
+ > `check --md` or re-rendered by `parity report`, and the local UI) **link back to that node in
234
+ > Figma** — a designer clicks once and lands on the layer. When `designFile` is set it wins over
235
+ > `figmaFileKey`, so a snapshot- or JSON-based report carries no Figma links.
221
236
 
222
237
  ## Two workflows for designers
223
238
 
@@ -245,7 +260,9 @@ frame's size**, so a 375-wide phone frame makes your media queries fire naturall
245
260
  ```
246
261
 
247
262
  `route` is only a label in the report, so pick something recognizable. You only need `width` /
248
- `height` on a target when the frame's width is not the viewport width you want to test.
263
+ `height` on a target when the frame's size is not the viewport you want to test. Each one
264
+ overrides its own axis: `width` alone keeps the default height of 800, `height` alone keeps the
265
+ frame's width.
249
266
 
250
267
  ## Shadow DOM / iframes
251
268
 
@@ -262,7 +279,8 @@ unaffected and works as usual).
262
279
 
263
280
  1. **Automatic text anchoring**: design TEXT content ↔ page text (matched only when unique;
264
281
  when several elements share the text, the layer name disambiguates — and it still refuses to force a match)
265
- 2. **Layer name ↔ id / class / aria-label**: `CTA Button` matches `class="cta-button"` automatically
282
+ 2. **Layer name ↔ id / class / aria-label** (falling back to `data-testid` when there is no
283
+ aria-label): `CTA Button` matches `class="cta-button"` automatically
266
284
  3. **Container inference**: an unmatched container is inferred from the lowest common ancestor of
267
285
  its already-matched descendants (pure structure — no guessing)
268
286
  4. **Manual fallback**: add `data-parity="Layer Name"` in your HTML, or write
@@ -286,11 +304,57 @@ src/Parity.Engine/ The engine; FidelityEngine is the only entry point
286
304
  DesignSources/ IDesignSource + Figma (REST + local cache) / Json
287
305
  ImplementationSources/ IImplementationSource + Web (Playwright)
288
306
  Comparison/ Normalizer / Matcher / DiffEngine / ColorDelta (CIEDE2000)
289
- src/Parity.Cli/ The dotnet tool shell: parity check / init / install-browser
307
+ src/Parity.Report/ The report contract: report.json types, ReportWire, the fidelity score,
308
+ baseline comparison — no package dependencies, so no Playwright
309
+ src/Parity.Cli/ The dotnet tool shell: parity check / init / install-browser,
310
+ the local report UI (parity serve) and parity push (experimental)
311
+ src/Parity.Storage/ Baseline history (EF Core + SQLite), behind parity baseline
312
+ src/Parity.Server/ Team dashboard shell (ASP.NET Core) — not published to NuGet
313
+ src/Parity.Server.Data/ Its database (EF Core + SQLite, migrations from day one)
314
+ web/ The dashboard front end (React + Vite + TypeScript); the build
315
+ output lands in src/Parity.Server/wwwroot
290
316
  tests/Parity.Tests/ Unit tests (including the CIEDE2000 reference data)
291
317
  samples/demo/ Offline demo: a deliberately broken page + a design JSON
292
318
  ```
293
319
 
320
+ There are three shells, and **the engine does not know that any of them exist**:
321
+
322
+ 1. **CLI** (`Parity.Cli`) — `parity check` in a terminal or in CI. This is the only shell
323
+ published as a package (NuGet / npm / the GitHub Action).
324
+ 2. **Local report UI** (`parity serve`) — a zero-build SPA bound to 127.0.0.1, for the
325
+ person who is actually fixing the gaps.
326
+ 3. **Team dashboard** (`Parity.Server` + `web/`) — somewhere the reports can accumulate, so
327
+ that trends exist and so that people who never touch the tool have a place to look. You
328
+ deploy it yourself; it is not on NuGet, and it is **still in development** (see M7 below).
329
+
330
+ > **Where the dashboard keeps its data.** The web host and the admin commands (`create-project`,
331
+ > `create-invite`) find the SQLite database the same way: `ConnectionStrings:parity` (from
332
+ > `appsettings.json` or the `ConnectionStrings__parity` environment variable), then
333
+ > `PARITY_SERVER_DB` (a file path), then `parity-server.db` in the working directory. This is the
334
+ > reverse of .NET's usual "environment beats appsettings": a connection string in
335
+ > `appsettings.json` wins over `PARITY_SERVER_DB` — to override it from the environment, set
336
+ > `ConnectionStrings__parity`. The reason: an explicit connection string is the more specific
337
+ > setting, and the integration tests inject one that a developer's `PARITY_SERVER_DB` must not
338
+ > redirect.
339
+
340
+ ### The iron rule: the cloud never runs a browser
341
+
342
+ `Parity.Server`'s only endpoint that takes in scan results is `POST /api/runs` (its other
343
+ write endpoints only manage sign-in, invites, members, tokens and run deletion), and what it
344
+ accepts is **a report that has already finished running**. There is no "give me a URL and I will go scan it"
345
+ endpoint, and there will not be one: **scanning always happens on your own machine or in your
346
+ CI**, where the page, the credentials and the localhost port already are.
347
+
348
+ This is not an implementation detail — it is the security claim of the whole design. It is why
349
+ the dashboard has **no SSRF surface at all**: it is never handed a URL to fetch. And it is
350
+ enforced structurally rather than by policy — the server projects do not reference the engine
351
+ at all, only `Parity.Report` (the report types, which have no package dependencies), so neither
352
+ Playwright nor its driver is anywhere in the server's build output. A test walks the server's
353
+ whole project-reference graph and turns CI red if any project in it references Playwright or
354
+ the engine; another turns it red the moment server code references a Playwright type or the
355
+ engine's capture side (`FidelityEngine`, `IImplementationSource`, or anything in
356
+ `ImplementationSources.Web`, which is the code that opens Chromium).
357
+
294
358
  ## Local report UI (M3)
295
359
 
296
360
  ```sh
@@ -321,12 +385,12 @@ jobs:
321
385
  contents: read
322
386
  pull-requests: write # lets the action post the fidelity report as a PR comment
323
387
  steps:
324
- - uses: actions/checkout@v4
388
+ - uses: actions/checkout@v7
325
389
  # Start your site (or deploy to a preview URL and point the config's url at it)
326
390
  - run: |
327
391
  npm ci && npm run build
328
392
  npm run preview & # e.g. serving on localhost:8080
329
- - uses: HSU-YU-MING/cornhsu-parity@v1 # @v1 tracks the latest 1.x
393
+ - uses: HSU-YU-MING/cornhsu-parity@v2 # @v2 tracks the latest 2.x
330
394
  with:
331
395
  config: parity.config.json
332
396
  figma-token: ${{ secrets.FIGMA_TOKEN }} # omit when the design source is a local JSON file
@@ -334,25 +398,46 @@ jobs:
334
398
 
335
399
  Behaviour:
336
400
 
337
- - **PR comment**: posts a fidelity report (score + gap table + **suggested fixes**) and keeps
401
+ - **PR comment**: posts a fidelity report (score + gap table + **suggested fixes**, ending with a
402
+ "Report a misjudgment" link to the issue form — a link only, nothing is sent) and keeps
338
403
  updating that same comment instead of adding new ones — so PMs and reviewers see it without
339
404
  ever touching the tool.
340
405
  - **Blocks the PR**: gaps beyond `gate.failOn` → exit 1 → red PR. (**The comment is posted
341
406
  first, then the gate is enforced.**)
342
- - **Artifacts**: `report.json` plus the Markdown report are uploaded for download.
407
+ - **Artifacts**: `report.json` plus the Markdown report are uploaded for download (artifact
408
+ name `parity-report` unless you set `artifact-name`; `report.json` and `parity-report.md` at its
409
+ root). `report.json` is picked up from `.parity/report.json` next to the config file — where
410
+ `check` writes it — even when `config` points into a subdirectory.
411
+ - **Running it more than once per workflow run**: prefer a **matrix** (one job per site or config), and
412
+ give each call its own `artifact-name` (e.g. `parity-report-${{ matrix.site }}`). Each name gets its
413
+ own artifact and keeps its own PR comment; two calls with the same name collide on the artifact upload.
414
+ Two calls in **one job** work only while the first one passes: when its gate fails, the job stops at
415
+ that call's last step and the second call never runs (no report, no comment, no artifact for it).
343
416
 
344
417
  Action inputs: `config` / `target` / `working-directory` / `version` / `figma-token` /
345
- `baseline` (regression mode) / `comment` (turn off the PR comment) / `upload-report`.
418
+ `baseline` (regression mode) / `comment` (turn off the PR comment) / `upload-report` /
419
+ `artifact-name` (default `parity-report`; letters, digits, `.`, `_`, `-`, at most 100 characters, not starting with `-` and not only dots).
346
420
 
347
421
  > The action installs the tool with `dotnet tool install -g Cornhsu.Parity` (published on
348
- > nuget.org). This repo's own `.github/workflows/ci.yml` instead **builds from source** and runs
349
- > the offline demo as a self-check, so it does not depend on a published release.
422
+ > nuget.org) — the latest 2.x when `version` is omitted, so `@v2` never jumps to a future 3.x,
423
+ > or exactly the `version` you pass. **Within one job the tool is installed once per call into the same
424
+ > global location, and a later call cannot install a lower version than an earlier one** (`dotnet tool`
425
+ > refuses to downgrade) — e.g. a first call without `version` installs the latest 2.x, and a second call
426
+ > with `version: 2.0.0` fails. Give every call in a job the same `version`, or use a matrix. This repo's own `.github/workflows/ci.yml` instead **builds from
427
+ > source** and runs the offline demo as a self-check, so it does not depend on a published release.
428
+ >
429
+ > **Upgrading from `@v1`?** `v1` stays at 1.1.0, whose action installs the newest release of *any*
430
+ > major when `version` is omitted — so an unpinned `@v1` workflow already runs 2.x. Switch to `@v2` and
431
+ > work through the "upgrade required" list in the CHANGELOG's 2.0.0 entry, or set `version: 1.1.0` to
432
+ > stay on 1.x for now.
350
433
 
351
434
  ## Report vocabulary (for anyone parsing `report.json`)
352
435
 
436
+ - **Unknown keys** — consumers must ignore keys they do not recognize. Within 2.x, `report.json` only *gains* fields; existing keys are never removed, renamed or given a different type (that would be 3.0). Closed sets below never gain members within 2.x either.
353
437
  - `severity` — **closed set** (spelling and membership frozen): `none` / `minor` / `medium` / `serious` / `critical`. `status`: `mismatch` / `missing`.
354
438
  - `unmatched[].reason` — **open set**: parsers must tolerate unknown values; known spellings never change. Currently: `no-anchor`, `ambiguous-or-missing-text`, `randomized-id`, `size-implausible`.
355
439
  - `nodes[].matchedBy` — **open set**, same rule. Currently: `selector`, `explicit`, `auto-text`, `auto-name`, `auto-container`.
440
+ - `direction` (top level, since 2.0.0) — **closed set**: `forward` (a normal `check`) / `reverse` (`check --reverse`: expected and actual swapped, never gated). Always written; files without it read as `forward`. Readers accept exactly the lowercase strings `"forward"` and `"reverse"` — any other value (other casing, a number, `null`) is an error, not a guess. `parity report` does not gate a reverse report, and `parity push` refuses one.
356
441
 
357
442
  ## Regression gating: baselines (M5)
358
443
 
@@ -387,7 +472,17 @@ file is released immediately), and the engine's `BaselineComparer` is a pure, un
387
472
  - [x] **M3** Local report UI (`parity serve --watch`, Kestrel bound to 127.0.0.1) + `parity map` interactive matching
388
473
  - [x] **M4** GitHub Action: a reusable composite action (`action.yml`) + this repo's own CI (build / test / offline-demo self-check)
389
474
  - [x] **M5** EF Core + SQLite baselines and history (regression gating + score trend) + `ImageDesignSource` (image + annotations + pixel sampling) + `parity snapshot` (freeze the current state as the baseline)
390
- - [ ] **M6** (optional) Cloud shell: public-URL scanning + SSRF protection
475
+ - [ ] **M6** (optional) Cloud shell: public-URL scanning + SSRF protection — **deliberately
476
+ declined** (2026-07-18). This is *not* what M7 is: M7 never scans anything. The reasoning is
477
+ kept in [ROADMAP.md](ROADMAP.md).
478
+ - [ ] **M7** Team dashboard shell (`Parity.Server` + `Parity.Server.Data` + `web/`) — **in
479
+ progress, not yet released.** `parity push` (**experimental**: the server is not publicly deployed
480
+ yet, so the command and its server protocol may change in a 2.x minor release and join the interface
481
+ freeze once the server ships) sends a finished `report.json` to a server you host yourself; the dashboard renders the gaps as an engineering blueprint drawn from the
482
+ measured coordinates (no screenshot upload), an overview and score trend per project and
483
+ route, and accounts / project roles / invite links. Its own internal M1–M4.6 are done; its
484
+ M5 (real external deployment: TLS, a domain, a hosted database) is not. **It receives
485
+ reports, it never scans** — see the iron rule above.
391
486
 
392
487
  > Open work, known blind spots and the next priorities are in [ROADMAP.md](ROADMAP.md);
393
488
  > version history is in [CHANGELOG.md](CHANGELOG.md).
@@ -396,8 +491,12 @@ file is released immediately), and the engine's `BaselineComparer` is a pure, un
396
491
 
397
492
  - The Figma token comes from an environment variable (`env:FIGMA_TOKEN`); it never reaches a log
398
493
  and never goes in a URL (it is sent as the `X-Figma-Token` header).
399
- - Fetched frames are cached in `.parity/cache` (gitignored), so re-runs do not hit Figma and can
400
- compare offline.
494
+ - Fetched frames are cached in `.parity/cache` (add `.parity/` to your `.gitignore`), so re-runs do
495
+ not hit Figma and can compare offline.
496
+ - The local report UI binds 127.0.0.1 only, and additionally rejects unexpected `Host` headers
497
+ (DNS rebinding) and cross-origin `POST`s (CSRF).
498
+ - **The team dashboard never scans anything** — it only receives finished reports, so it has no
499
+ SSRF surface. See [the iron rule](#the-iron-rule-the-cloud-never-runs-a-browser).
401
500
 
402
501
  ## See also
403
502
 
@@ -413,8 +512,12 @@ same author, built on the same philosophy: numeric checks that gate CI. Parity a
413
512
  implementation match the design?**" (fidelity); XamlContrast answers "**can anyone actually see
414
513
  what we built?**" (WCAG contrast, scanned statically from XAML source). One guards fidelity, the
415
514
  other readability — and its static color resolution is also the groundwork for the expected values
416
- of this project's future `WpfImplementationSource` desktop adapter.
515
+ of this project's future `WpfImplementationSource` desktop adapter (see §4.5 of the
516
+ [project plan](docs/Parity專案規畫書.md), in Traditional Chinese).
417
517
 
418
518
  ## License
419
519
 
420
- MIT
520
+ MIT. The NuGet and npm packages also bundle third-party components (Playwright, ImageSharp,
521
+ SQLite and others; the npm packages also include the .NET runtime). Their licenses are listed in
522
+ [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md), which ships inside the NuGet package and every npm
523
+ platform package.
package/bin/parity.js CHANGED
@@ -17,24 +17,31 @@
17
17
  const { spawnSync } = require("node:child_process");
18
18
  const path = require("node:path");
19
19
 
20
- // 平台 → 子套件名 / 執行檔名
20
+ // 平台 → 子套件名 / 執行檔名。必須與 npm/prepare.mjs 的 TARGETS 一一對應
21
+ // (RepoInvariantsTests 會比對;1.1.0 漏了兩個 arm64,平台包有發佈、npx 卻說不支援)
21
22
  const TARGETS = {
22
23
  "win32 x64": { pkg: "@cornhsu/parity-win32-x64", bin: "parity.exe" },
24
+ "win32 arm64": { pkg: "@cornhsu/parity-win32-arm64", bin: "parity.exe" },
23
25
  "linux x64": { pkg: "@cornhsu/parity-linux-x64", bin: "parity" },
26
+ "linux arm64": { pkg: "@cornhsu/parity-linux-arm64", bin: "parity" },
24
27
  "darwin x64": { pkg: "@cornhsu/parity-darwin-x64", bin: "parity" },
25
28
  "darwin arm64": { pkg: "@cornhsu/parity-darwin-arm64", bin: "parity" },
26
29
  };
27
30
 
31
+ // 啟動腳本自己的錯誤一律 exit 2:CLI 的契約是 0 通過 / 1 落差超 gate / 2 執行錯誤 / 3 可信度不足,
32
+ // 回 1 會被 CI 讀成「設計還原度不合格」。訊息用英文,與 CLI 本體一致(0.12.0 起)。
33
+ const EXECUTION_ERROR = 2;
34
+
28
35
  const key = `${process.platform} ${process.arch}`;
29
36
  const target = TARGETS[key];
30
37
 
31
38
  if (!target) {
32
39
  console.error(
33
- `Cornhsu.Parity 沒有 ${key} 的預先建置版本。\n` +
34
- `目前支援:${Object.keys(TARGETS).join("、")}\n` +
35
- `其他平台請改用 .NET 版本:dotnet tool install -g Cornhsu.Parity`
40
+ `cornhsu-parity: there is no prebuilt build for ${key}.\n` +
41
+ `Supported: ${Object.keys(TARGETS).join(", ")}\n` +
42
+ `On other platforms use the .NET tool instead: dotnet tool install -g Cornhsu.Parity`
36
43
  );
37
- process.exit(1);
44
+ process.exit(EXECUTION_ERROR);
38
45
  }
39
46
 
40
47
  let binary;
@@ -45,11 +52,11 @@ try {
45
52
  binary = path.join(path.dirname(manifest), "bin", target.bin);
46
53
  } catch {
47
54
  console.error(
48
- `找不到平台套件 ${target.pkg}。\n` +
49
- `可能是安裝時加了 --no-optional,或安裝過程中斷。\n` +
50
- `請重新安裝:npm install cornhsu-parity`
55
+ `cornhsu-parity: the platform package ${target.pkg} is not installed.\n` +
56
+ `It is an optional dependency, so the install may have skipped it (--no-optional / --omit=optional) or been interrupted.\n` +
57
+ `Reinstall: npm install cornhsu-parity`
51
58
  );
52
- process.exit(1);
59
+ process.exit(EXECUTION_ERROR);
53
60
  }
54
61
 
55
62
  const result = spawnSync(binary, process.argv.slice(2), {
@@ -62,8 +69,8 @@ const result = spawnSync(binary, process.argv.slice(2), {
62
69
  });
63
70
 
64
71
  if (result.error) {
65
- console.error(`無法執行 ${binary}:${result.error.message}`);
66
- process.exit(1);
72
+ console.error(`cornhsu-parity: could not run ${binary}: ${result.error.message}`);
73
+ process.exit(EXECUTION_ERROR);
67
74
  }
68
75
 
69
76
  // 被訊號中斷時 status 會是 null;轉成慣例的 128+signal,別讓 CI 誤判為成功
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cornhsu-parity",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "Property-Level Design QA that can gate your CI. Compares real numeric values from your Figma file against the rendered UI and fails the PR when the implementation drifts from the design.",
5
5
  "keywords": [
6
6
  "figma",
@@ -31,14 +31,14 @@
31
31
  "README.md"
32
32
  ],
33
33
  "engines": {
34
- "node": ">=18"
34
+ "node": ">=20"
35
35
  },
36
36
  "optionalDependencies": {
37
- "@cornhsu/parity-win32-x64": "1.1.0",
38
- "@cornhsu/parity-win32-arm64": "1.1.0",
39
- "@cornhsu/parity-linux-x64": "1.1.0",
40
- "@cornhsu/parity-linux-arm64": "1.1.0",
41
- "@cornhsu/parity-darwin-x64": "1.1.0",
42
- "@cornhsu/parity-darwin-arm64": "1.1.0"
37
+ "@cornhsu/parity-win32-x64": "2.0.0",
38
+ "@cornhsu/parity-win32-arm64": "2.0.0",
39
+ "@cornhsu/parity-linux-x64": "2.0.0",
40
+ "@cornhsu/parity-linux-arm64": "2.0.0",
41
+ "@cornhsu/parity-darwin-x64": "2.0.0",
42
+ "@cornhsu/parity-darwin-arm64": "2.0.0"
43
43
  }
44
44
  }