cornhsu-parity 2.2.0 → 2.3.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.
- package/README.md +135 -12
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -63,11 +63,11 @@ PR comment, updating the same comment rather than adding new ones.
|
|
|
63
63
|
|
|
64
64
|
| | |
|
|
65
65
|
|---|---|
|
|
66
|
-
| Releases |
|
|
66
|
+
| Releases | 31 on NuGet (v0.1.0 → v2.3.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 | **
|
|
70
|
+
| Tests | **1443**, 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
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
|
|
|
@@ -111,10 +111,11 @@ up for someone, to pass on as it is. The full description is in
|
|
|
111
111
|
Development builds (any version starting with `0.0.0-`) never send.
|
|
112
112
|
- **What is sent — counts and ranges only:** the tool version, the OS, whether it runs in CI, the fidelity
|
|
113
113
|
score, whether the gate failed, the number of pages, the kind of design source, the design and matched
|
|
114
|
-
node totals, gap counts per severity,
|
|
115
|
-
|
|
114
|
+
node totals, gap counts per severity, for each measured property how many gaps and how large, in ranges,
|
|
115
|
+
how many design nodes could not be matched per reason, and, if `compare.extras` is on, how many extra
|
|
116
|
+
elements were found. There is no ID unless the user adds one to the config.
|
|
116
117
|
- **Never sent:** URLs, routes, page or layer names, selectors, file names or paths, screenshots, colour
|
|
117
|
-
values, design content, tokens, or repository, user or machine names. The request carries no User-Agent
|
|
118
|
+
values, design content, page text, image contents, tokens, or repository, user or machine names. The request carries no User-Agent
|
|
118
119
|
or cookies, and the collector stores no IP address.
|
|
119
120
|
- **Where and why:** `stats.cornhsu.com`, for academic research on how far real implementations drift from
|
|
120
121
|
their designs; only aggregate results are published, and each record is kept for at most 3 years
|
|
@@ -179,11 +180,21 @@ serious-severity gaps — which is exactly the behaviour you want from a CI gate
|
|
|
179
180
|
"targets": [
|
|
180
181
|
{ "route": "/", "frame": "10:2", "url": "http://localhost:8080/" }
|
|
181
182
|
],
|
|
182
|
-
"compare": {
|
|
183
|
+
"compare": {
|
|
184
|
+
"position": "relative", // relative = compare relative position (default); none = skip position
|
|
185
|
+
"extras": "none", // optional: "list" = also list page elements the design has no node for;
|
|
186
|
+
// listed only, never gates (see "Elements the design does not have")
|
|
187
|
+
"text": "soft", // optional: text content. soft = report it, never gate (default);
|
|
188
|
+
// gate = serious gap; none = do not compare
|
|
189
|
+
"images": "none" // optional: "soft" = compare image content against a reference image:
|
|
190
|
+
// a snapshot baseline, designImage, or the exported Figma frame
|
|
191
|
+
},
|
|
183
192
|
"tolerances": { "sizePx": 2, "spacingPx": 2, "colorDeltaE": 2.0, "fontSizePx": 0.5, "positionPx": 4 },
|
|
184
193
|
"ignore": ["[data-parity-ignore]"],
|
|
185
194
|
"gate": {
|
|
186
195
|
"failOn": ["critical", "serious"], // any of minor / medium / serious / critical, one per string
|
|
196
|
+
"failOnUnmatched": [], // optional: unmatched reasons that fail the gate (exit 1), e.g.
|
|
197
|
+
// ["text-not-found"], or ["*"] for any; [] = list only (default), see below
|
|
187
198
|
"minMatchRate": 0 // optional: below this match rate (0–1) → exit 3, see below; 0 = no threshold
|
|
188
199
|
},
|
|
189
200
|
"stats": { "share": true } // optional: false turns anonymous usage statistics off;
|
|
@@ -197,6 +208,40 @@ serious-severity gaps — which is exactly the behaviour you want from a CI gate
|
|
|
197
208
|
> a wrong url or frame. A match rate below `gate.minMatchRate` fails the same way, with exit
|
|
198
209
|
> code 3. `--baseline` mode is not exempt; `--reverse` never gates and always exits 0.
|
|
199
210
|
|
|
211
|
+
### Gating on missing elements: `gate.failOnUnmatched`
|
|
212
|
+
|
|
213
|
+
By default an unmatched design node is only **listed** (`unmatched` in the report) — a page that
|
|
214
|
+
lost a badge still passes. `gate.failOnUnmatched` lets you fail the gate on chosen
|
|
215
|
+
[unmatched reasons](#report-vocabulary-for-anyone-parsing-reportjson) instead: exit 1 (GATE FAIL),
|
|
216
|
+
and the reason line names the layers it blocked, e.g.
|
|
217
|
+
`/: 1 unmatched design node blocked by gate.failOnUnmatched (text-not-found: cta-label)`.
|
|
218
|
+
|
|
219
|
+
- `["text-not-found"]` — the targeted choice: fail when a design text node's text appears **nowhere on
|
|
220
|
+
the page** (the element is gone, or its copy was changed). Other unmatched nodes stay listed only.
|
|
221
|
+
- `["*"]` — fail on every unmatched node, whatever the reason. (`minMatchRate: 1` also fails on any
|
|
222
|
+
unmatched node, but as exit 3 — "results are not trustworthy" — not as a GATE FAIL.)
|
|
223
|
+
- Reasons are matched case-insensitively; a misspelled reason is a config error at load (exit 2),
|
|
224
|
+
because a reason that never matches would silently disable the gate.
|
|
225
|
+
- Match integrity still comes first: zero matches remain exit 3.
|
|
226
|
+
- Applied by `check`, `parity report` and `parity push` (both recompute the verdict from the config),
|
|
227
|
+
and `parity serve`. **Not applied by `check --baseline`** while a baseline exists — regression mode
|
|
228
|
+
only gates new or worsened gaps; with no baseline yet it falls back to the normal gate, this
|
|
229
|
+
condition included.
|
|
230
|
+
|
|
231
|
+
What `text-not-found` means exactly: the design text is not a substring of the page text Parity can
|
|
232
|
+
see. The comparison ignores case, all whitespace, and invisible characters such as soft hyphens, and
|
|
233
|
+
treats curly quotes as straight ones, so a manual line break in the design or a different quote style
|
|
234
|
+
is not a missing text. That page text is the rendered text of the page
|
|
235
|
+
(`innerText`, so `text-transform` and text split across elements like `Hello <b>world</b>` count),
|
|
236
|
+
plus what `innerText` misses: open shadow roots, same-origin iframes, `<input>`/`<textarea>`
|
|
237
|
+
values and placeholders, `::before`/`::after` content, `<img alt>`. Hidden text (`display: none`,
|
|
238
|
+
`visibility: hidden`) and password values do not count. Text Parity cannot reach — cross-origin
|
|
239
|
+
iframes, closed shadow roots, text drawn on a `<canvas>` or baked into an image without `alt` — counts as
|
|
240
|
+
not found, so `text-not-found` means "not found anywhere Parity can see". A text that is present but
|
|
241
|
+
cannot be pinned to one element (several identical texts, or split across elements) stays
|
|
242
|
+
`ambiguous-or-missing-text`. The page text is used for this decision only: it is never written to
|
|
243
|
+
`report.json`, snapshots, statistics or bug reports.
|
|
244
|
+
|
|
200
245
|
## Targets: a web page or an Electron desktop app
|
|
201
246
|
|
|
202
247
|
`target.url` decides what the implementation side is; the commands and the report are identical:
|
|
@@ -272,6 +317,45 @@ Only the things that should hold no matter how the layout flows:
|
|
|
272
317
|
- **Absolute x/y is deliberately never compared**: it legitimately differs in a flexible layout,
|
|
273
318
|
so comparing it means false positives, which means losing trust.
|
|
274
319
|
|
|
320
|
+
These are all **soft gaps** too, like font-family: reported, but they cost no fidelity points, never
|
|
321
|
+
gate and do not count as new in `--baseline` mode. They start soft on purpose, until real sites show
|
|
322
|
+
how noisy each one is.
|
|
323
|
+
|
|
324
|
+
- **Text content** (`text`): a layer matched by name whose text differs. Case, whitespace, invisible
|
|
325
|
+
characters and quote style are ignored. When the element has children, the design text only has to
|
|
326
|
+
appear somewhere inside. Layers you matched by hand are skipped (design systems often use placeholder
|
|
327
|
+
text), and so are snapshot baselines (dynamic content changes between runs). `compare.text: "gate"`
|
|
328
|
+
makes it a serious gap, snapshot baselines included; `"none"` turns it off. **Both texts go into
|
|
329
|
+
`report.json` and the Markdown report, cut to 120 characters**, so they reach anywhere you send those.
|
|
330
|
+
- **Child order** (`childOrder`): children of an auto-layout container in a different order along its
|
|
331
|
+
main axis. Compared on the real boxes, so CSS `order` and `row-reverse` count correctly. In a wrapping
|
|
332
|
+
container the reading order is compared, so breaking onto a new row at a different place is not a gap.
|
|
333
|
+
Absolutely positioned children are skipped.
|
|
334
|
+
- **Container alignment** (`mainAxisOffset`, `crossAxisOffset`): the distance from a child to its
|
|
335
|
+
auto-layout parent's edge. Only where that distance should be fixed: both sizes explicitly FIXED, no
|
|
336
|
+
text nodes, and no padding or size gap that already explains the shift. The main axis is compared for
|
|
337
|
+
the first child under `MIN`, `CENTER` or `MAX`. Order and alignment follow `compare.position`, and are
|
|
338
|
+
skipped when a target's viewport width differs from the design, because the layout may reflow.
|
|
339
|
+
- **Line count** (`lineCount`): how many lines a text takes, measured from the real line boxes. This is
|
|
340
|
+
the root cause when a wrapped subtitle pushes everything below it down. The design side is box height
|
|
341
|
+
over line height; fixed-height text boxes are skipped.
|
|
342
|
+
- **Style**: `fontStyle`, `textDecoration`, `textTransform`, `textAlign`, `opacity`, `borderWidth`,
|
|
343
|
+
`borderColor`, `shadow`, `shadowColor`, `gradient`. Only what the design states is compared: Figma nodes
|
|
344
|
+
always state it, and a hand-written design JSON states what it sets (`text.fontStyle`, `opacity`,
|
|
345
|
+
`borderWidth`, `shadows`, `gradient` and so on). What cannot be seen does not count: invisible shadows, transparent borders, a border the same color as the
|
|
346
|
+
fill, and a text transform that changes nothing on screen. Opacity is the element's own, not accumulated from its
|
|
347
|
+
ancestors. Gradients compare their kind, direction and end colors, not the stops in between; a direction
|
|
348
|
+
that depends on the box, like `to top right`, is not compared.
|
|
349
|
+
- **Image content** (`image`, off by default): `"compare": { "images": "soft" }` crops each `img`, `svg`,
|
|
350
|
+
`picture` and background image out of a full-page screenshot and compares it with the same box in the
|
|
351
|
+
reference image: the screenshot next to a `parity snapshot` baseline, `designImage`, or the frame exported
|
|
352
|
+
from Figma. Recompressing or rescaling an image does not count. It takes one extra screenshot per page.
|
|
353
|
+
With Figma it also spends API quota, one export per frame, kept in `.parity/cache` until you pass
|
|
354
|
+
`--refresh`. If there is no reference or the export fails, a note goes to stderr and the check carries on.
|
|
355
|
+
|
|
356
|
+
A snapshot taken by an older version has none of the style values, so nothing new appears until you run
|
|
357
|
+
`parity snapshot` again.
|
|
358
|
+
|
|
275
359
|
> When the design source is Figma, layer names in the report (the Markdown written by
|
|
276
360
|
> `check --md` or re-rendered by `parity report`, and the local UI) **link back to that node in
|
|
277
361
|
> Figma** — a designer clicks once and lands on the layer. When `designFile` is set it wins over
|
|
@@ -328,7 +412,41 @@ unaffected and works as usual).
|
|
|
328
412
|
its already-matched descendants (pure structure — no guessing)
|
|
329
413
|
4. **Manual fallback**: add `data-parity="Layer Name"` in your HTML, or write
|
|
330
414
|
`{ "Layer Name": "CSS selector" }` in `parity.map.json`
|
|
331
|
-
5. Still no match → **honestly listed as unmatched**, so you only fill in what genuinely needs a human
|
|
415
|
+
5. Still no match → **honestly listed as unmatched**, so you only fill in what genuinely needs a human.
|
|
416
|
+
A text node whose text is nowhere on the page is reported as `text-not-found` (the element is
|
|
417
|
+
missing), not as an ambiguity — and `gate.failOnUnmatched` can fail the gate on it
|
|
418
|
+
|
|
419
|
+
## Elements the design does not have (`compare.extras`)
|
|
420
|
+
|
|
421
|
+
Matching is anchored on the design side, so by default an element the design never had — say an
|
|
422
|
+
800×200 banner across the top of the page — produces no gap at all and the check passes without a
|
|
423
|
+
word. `"compare": { "extras": "list" }` adds a list of such elements to the report. **It only
|
|
424
|
+
lists**: the gate, the exit code, the fidelity score, the `summary` counts and `--baseline` are exactly
|
|
425
|
+
what they would be without it. The anonymous statistics gain one number, how many such elements were
|
|
426
|
+
found, and never which ones. The default is `"none"`; any value
|
|
427
|
+
other than `none` / `list` is a config error (exit 2) — it is a string, not a boolean, so a typo
|
|
428
|
+
like `"false"` fails loudly.
|
|
429
|
+
|
|
430
|
+
An element on the page is listed when all of these hold:
|
|
431
|
+
|
|
432
|
+
- it is not matched, and it is not an ancestor of a matched element (those are layout containers);
|
|
433
|
+
- it is not inside a matched design **leaf** (how a button or an image is built inside is an
|
|
434
|
+
implementation detail), nor inside a matched element none of whose design children matched
|
|
435
|
+
(those children are already in the unmatched list — listing the page side too would report the
|
|
436
|
+
same gap twice);
|
|
437
|
+
- it is visible: it has text of its own, is an `img` or `svg`, or has a background that is not
|
|
438
|
+
fully transparent;
|
|
439
|
+
- at least **64×64 = 4,096 px²** of it lies inside the viewport the page was rendered at (by
|
|
440
|
+
default the design frame's size). The threshold is deliberately conservative — icons, hairlines
|
|
441
|
+
and a single short word stay below it — and not configurable in this first version.
|
|
442
|
+
|
|
443
|
+
Nested elements are listed once, at the outermost one that qualifies. `ignore` selectors apply as
|
|
444
|
+
usual, and `check --reverse` never computes the list. `check` prints the list and `--md` / `parity
|
|
445
|
+
report` add a table when there is something to show; the local report UI and the dashboard do not
|
|
446
|
+
show it yet (the dashboard keeps it as part of the raw report). Known limits: the capture does not
|
|
447
|
+
record borders, shadows, `opacity` or clipping, so **an element that is visible only through its
|
|
448
|
+
border is not listed**, while one hidden by `opacity: 0`, by an ancestor's `overflow: hidden` or
|
|
449
|
+
under another element can be.
|
|
332
450
|
|
|
333
451
|
## Architecture
|
|
334
452
|
|
|
@@ -490,8 +608,8 @@ Behaviour:
|
|
|
490
608
|
"Report a misjudgment" link to the issue form — a link only, nothing is sent) and keeps
|
|
491
609
|
updating that same comment instead of adding new ones — so PMs and reviewers see it without
|
|
492
610
|
ever touching the tool.
|
|
493
|
-
- **Blocks the PR**: gaps beyond `gate.failOn`
|
|
494
|
-
first, then the gate is enforced.**)
|
|
611
|
+
- **Blocks the PR**: gaps beyond `gate.failOn` (or unmatched nodes whose reason is in
|
|
612
|
+
`gate.failOnUnmatched`) → exit 1 → red PR. (**The comment is posted first, then the gate is enforced.**)
|
|
495
613
|
- **Artifacts**: `report.json` plus the Markdown report are uploaded for download (artifact
|
|
496
614
|
name `parity-report` unless you set `artifact-name`; `report.json` and `parity-report.md` at its
|
|
497
615
|
root). `report.json` is picked up from `.parity/report.json` next to the config file — where
|
|
@@ -526,8 +644,9 @@ Action inputs: `config` / `target` / `working-directory` / `version` / `figma-to
|
|
|
526
644
|
|
|
527
645
|
- **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.
|
|
528
646
|
- `severity` — **closed set** (spelling and membership frozen): `none` / `minor` / `medium` / `serious` / `critical`. `status`: `mismatch` / `missing`.
|
|
529
|
-
- `unmatched[].reason` — **open set**: parsers must tolerate unknown values; known spellings never change. Currently: `no-anchor`, `ambiguous-or-missing-text`, `randomized-id`, `size-implausible`.
|
|
647
|
+
- `unmatched[].reason` — **open set**: parsers must tolerate unknown values; known spellings never change. Currently: `no-anchor`, `ambiguous-or-missing-text`, `text-not-found`, `randomized-id`, `size-implausible`.
|
|
530
648
|
- `nodes[].matchedBy` — **open set**, same rule. Currently: `selector`, `explicit`, `auto-text`, `auto-name`, `auto-container`.
|
|
649
|
+
- `extras` (per report, `compare.extras`) — `null` when the list is off (the default, `check --reverse`, and every report written before the field existed); otherwise `{ "total": n, "items": [{ "selector", "tag", "box" }] }`, where `total: 0` with empty `items` means "on, nothing found". `items` is sorted largest first and capped at 20 per page; `total` counts them all. Items carry no element text. Never affects the gate or the score.
|
|
531
650
|
- `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.
|
|
532
651
|
|
|
533
652
|
## Regression gating: baselines (M5)
|
|
@@ -550,6 +669,7 @@ parity baseline list # Browse snapshot history (the score column is the fide
|
|
|
550
669
|
|
|
551
670
|
- **New** (absent from the baseline, present now) or **worsened** (higher severity) → exit 1, PR blocked
|
|
552
671
|
- **Fixed** (in the baseline, gone now) is listed as encouragement; **unchanged** existing gaps do not gate
|
|
672
|
+
- `gate.failOnUnmatched` is not applied in this mode (once a baseline exists); unmatched nodes are listed as usual
|
|
553
673
|
- Ideal for gradual adoption: record reality with `baseline save`, then run `check --baseline` in
|
|
554
674
|
CI, and all the team has to commit to is "do not make fidelity worse"
|
|
555
675
|
|
|
@@ -605,10 +725,13 @@ for, how long it is kept — is **<https://stats.cornhsu.com/>**.
|
|
|
605
725
|
| `data.designSource` | `figma`, `snapshot` (a file written by `parity snapshot`), `file` (any other design JSON) or `image` |
|
|
606
726
|
| `data.designNodes`, `data.matched` | Design nodes and matched nodes, summed over all pages |
|
|
607
727
|
| `data.severity` | Gap counts per severity: `critical`, `serious`, `medium`, `minor` |
|
|
608
|
-
| `data.props` | For each property with at least one gap: how many gaps, how many were `missing`, and how many fall in each range of `abs(delta)` — px for lengths (`offsetX`/`offsetY`, size, padding, `itemSpacing`, `cornerRadius`, `fontSize`, `lineHeight`, `letterSpacing`: <1, 1–2, 2–4, 4–8, 8–16, 16–32, ≥32), ΔE for `color
|
|
728
|
+
| `data.props` | For each property with at least one gap: how many gaps, how many were `missing`, and how many fall in each range of `abs(delta)` — px for lengths (`offsetX`/`offsetY`, size, padding, `itemSpacing`, `cornerRadius`, `fontSize`, `lineHeight`, `letterSpacing`, `borderWidth`, `shadow`, `mainAxisOffset`, `crossAxisOffset`: <1, 1–2, 2–4, 4–8, 8–16, 16–32, ≥32), ΔE for `color`, `background`, `borderColor`, `shadowColor`, `gradient` (<1, 1–2, 2–5, 5–10, 10–20, ≥20), weight steps for `fontWeight` (<100, 100–200, 200–300, ≥300), a 0–1 difference for `opacity` and `image` (<0.05, 0.05–0.1, 0.1–0.25, 0.25–0.5, ≥0.5), and lines for `lineCount` (1, 2, 3–4, 5 or more). `fontFamily`, `text`, `childOrder`, `fontStyle`, `textDecoration`, `textTransform`, `textAlign` have no ranges, only counts: never the text, the layer names or the values |
|
|
729
|
+
| `data.unmatchedBy` | Design nodes that could not be matched, counted per reason: `no-anchor`, `ambiguous-or-missing-text`, `text-not-found`, `randomized-id`, `size-implausible`, or `other` for any reason this version does not list. Only reasons with at least one node; `{}` when everything matched. Never the layer names |
|
|
730
|
+
| `data.extras` | How many extra page elements `compare.extras` found, summed over all pages (the total, before the 20-per-page cap). Only present when the list is on. Never their selectors, tags or boxes |
|
|
609
731
|
|
|
610
732
|
**Never sent:** URLs, routes, page or layer names, selectors, file names or paths, screenshots, colour
|
|
611
|
-
values, design content,
|
|
733
|
+
values, design content, text from the page or the design, image contents or image hashes, tokens,
|
|
734
|
+
repository names or IDs, user names, machine names. The request has no
|
|
612
735
|
User-Agent and no cookies. The collector does not store IP addresses or the time a record arrived (only
|
|
613
736
|
its date); Cloudflare, which hosts the collector, sees the connection address at its edge, as any network
|
|
614
737
|
provider does.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cornhsu-parity",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.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",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"node": ">=20"
|
|
35
35
|
},
|
|
36
36
|
"optionalDependencies": {
|
|
37
|
-
"@cornhsu/parity-win32-x64": "2.
|
|
38
|
-
"@cornhsu/parity-win32-arm64": "2.
|
|
39
|
-
"@cornhsu/parity-linux-x64": "2.
|
|
40
|
-
"@cornhsu/parity-linux-arm64": "2.
|
|
41
|
-
"@cornhsu/parity-darwin-x64": "2.
|
|
42
|
-
"@cornhsu/parity-darwin-arm64": "2.
|
|
37
|
+
"@cornhsu/parity-win32-x64": "2.3.0",
|
|
38
|
+
"@cornhsu/parity-win32-arm64": "2.3.0",
|
|
39
|
+
"@cornhsu/parity-linux-x64": "2.3.0",
|
|
40
|
+
"@cornhsu/parity-linux-arm64": "2.3.0",
|
|
41
|
+
"@cornhsu/parity-darwin-x64": "2.3.0",
|
|
42
|
+
"@cornhsu/parity-darwin-arm64": "2.3.0"
|
|
43
43
|
}
|
|
44
44
|
}
|