@decocms/parity 0.11.6 → 0.11.9
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/CHANGELOG.md +26 -0
- package/README.md +5 -1
- package/dist/cli.js +46 -15
- package/package.json +4 -3
- package/scripts/postinstall.cjs +48 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [0.11.9](https://github.com/decocms/parity/compare/v0.11.8...v0.11.9) (2026-06-17)
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* **Re-publish to refresh `@latest` and let the Publish workflow create the GitHub release.** All the 0.11.x manual `gh release create` calls collided with the CI Publish job's `gh release create --generate-notes` step (it gets `HTTP 422: Release.tag_name already exists` and exits non-zero), which made the workflow look red even though npm publish succeeded each time. This bump has no code changes — it triggers a clean end-to-end publish so the workflow can prove green and `@latest` carries the same artifact as 0.11.8 with a fresh install signature. From now on no manual release creation — the workflow does it.
|
|
13
|
+
|
|
14
|
+
## [0.11.8](https://github.com/decocms/parity/compare/v0.11.7...v0.11.8) (2026-06-17)
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* **CI on `main` was broken across the 0.11.x patch series.** Three issues converged: (a) the new `postinstall` script in 0.11.7 used CommonJS `require()` but the package is `"type": "module"`, so every fresh `bun install --frozen-lockfile` (including the CI install step) threw `ReferenceError: require is not defined`. (b) Several lint errors carried over from PRs that used `--admin` to bypass CI (template-literal-as-string, assign-in-while-conditions in the new `plp-pagination` check, `delete attrs[name]` flagged by `noDelete`). (c) The earlier `0.11.4` HTML-compaction code in `discover-selectors`/`recover-step` triggered the same `noDelete` rule.
|
|
19
|
+
* **`postinstall.js` renamed to `postinstall.cjs`** so it runs as CommonJS regardless of the package's `"type"`. `package.json` `files` list and the `postinstall` script invocation updated accordingly.
|
|
20
|
+
* **Lint clean across the repo.** `bun run lint` returns zero errors. Loop patterns rewritten from `while ((m = re.exec(s)))` to `for (;;)` + early-break (biome's `noAssignInExpressions`). Two intentional `delete` calls on cheerio attribs get a scoped `biome-ignore` with the rationale (cheerio serializes `undefined`-valued attrs as empty strings; only `delete` actually removes them).
|
|
21
|
+
|
|
22
|
+
## [0.11.7](https://github.com/decocms/parity/compare/v0.11.6...v0.11.7) (2026-06-17)
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
* **First-run UX after `npm install -g`.** Two papercuts:
|
|
27
|
+
1. **Playwright Chromium wasn't auto-installed.** A fresh global install left users with `browserType.launch: Executable doesn't exist at ...` plus a stack trace on the first `parity run`. Now there's a `postinstall` script that runs `npx playwright install chromium` automatically (one-time, ~140 MB). Skippable via `PARITY_SKIP_PLAYWRIGHT_INSTALL=1` for CI / Docker / monorepos that manage browsers separately. Failures during postinstall (offline, corp proxy) are downgraded to a warning so the global install itself still completes.
|
|
28
|
+
2. **`launchBrowser` now catches the missing-browser error** and prints a single clear instruction (`npx playwright install chromium`) instead of Playwright's ASCII banner + stack trace. Original error is preserved on `.cause` for debugging.
|
|
29
|
+
|
|
30
|
+
### Known cosmetic warning
|
|
31
|
+
|
|
32
|
+
The `@anthropic-ai/claude-agent-sdk` peer-dep on `zod@^4.0.0` clashes with parity's `zod@^3.x`. The warning is harmless — npm nests the two versions and everything works. A migration to zod 4 is tracked separately (lots of schema API differences).
|
|
33
|
+
|
|
8
34
|
## [0.11.6](https://github.com/decocms/parity/compare/v0.11.5...v0.11.6) (2026-06-17)
|
|
9
35
|
|
|
10
36
|
### Added
|
package/README.md
CHANGED
|
@@ -26,11 +26,15 @@ Three use cases drive the design:
|
|
|
26
26
|
## Quickstart
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
# Install
|
|
29
|
+
# Install — auto-installs Playwright Chromium on postinstall (~140 MB, one-time)
|
|
30
30
|
npm install -g @decocms/parity
|
|
31
31
|
# or run without install
|
|
32
32
|
npx @decocms/parity run --prod ... --cand ...
|
|
33
33
|
|
|
34
|
+
# In CI / behind a corp proxy? Skip the auto-install and do it later:
|
|
35
|
+
# PARITY_SKIP_PLAYWRIGHT_INSTALL=1 npm install -g @decocms/parity
|
|
36
|
+
# npx playwright install chromium
|
|
37
|
+
|
|
34
38
|
# First-time smoke run (~30s, no LLM needed)
|
|
35
39
|
parity run --prod https://oldsite.com --cand https://newsite.example.dev --preset smoke --open
|
|
36
40
|
|
package/dist/cli.js
CHANGED
|
@@ -1790,11 +1790,29 @@ var VIEWPORT_PRESETS = {
|
|
|
1790
1790
|
}
|
|
1791
1791
|
};
|
|
1792
1792
|
async function launchBrowser(opts = {}) {
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1793
|
+
try {
|
|
1794
|
+
return await chromium.launch({
|
|
1795
|
+
headless: opts.headless ?? true,
|
|
1796
|
+
slowMo: opts.slowMo ?? 0,
|
|
1797
|
+
args: ["--disable-blink-features=AutomationControlled"]
|
|
1798
|
+
});
|
|
1799
|
+
} catch (err) {
|
|
1800
|
+
const msg = err.message ?? "";
|
|
1801
|
+
if (msg.includes("Executable doesn't exist")) {
|
|
1802
|
+
const friendly = new Error([
|
|
1803
|
+
"Playwright's Chromium binary is not installed yet. Run:",
|
|
1804
|
+
"",
|
|
1805
|
+
" npx playwright install chromium",
|
|
1806
|
+
"",
|
|
1807
|
+
"(one-time, ~140 MB download). Then re-run parity. This usually happens",
|
|
1808
|
+
"after a fresh `npm install -g @decocms/parity`."
|
|
1809
|
+
].join(`
|
|
1810
|
+
`));
|
|
1811
|
+
friendly.cause = err;
|
|
1812
|
+
throw friendly;
|
|
1813
|
+
}
|
|
1814
|
+
throw err;
|
|
1815
|
+
}
|
|
1798
1816
|
}
|
|
1799
1817
|
async function newContext(browser, opts) {
|
|
1800
1818
|
const baseContext = VIEWPORT_PRESETS[opts.viewport];
|
|
@@ -8638,7 +8656,7 @@ Page 2 first product: ${page2.productPaths[0] ?? "(none)"}`
|
|
|
8638
8656
|
status,
|
|
8639
8657
|
severity: "high",
|
|
8640
8658
|
durationMs: Date.now() - start,
|
|
8641
|
-
summary: issues.length === 0 ?
|
|
8659
|
+
summary: issues.length === 0 ? "PLP pagination working on both sides (tested page=2 + page=3)" : `${issues.length} pagination issue(s) — see details`,
|
|
8642
8660
|
issues,
|
|
8643
8661
|
data
|
|
8644
8662
|
};
|
|
@@ -8657,8 +8675,10 @@ async function fetchPlpProducts(url) {
|
|
|
8657
8675
|
const html = await res.text();
|
|
8658
8676
|
const paths = new Set;
|
|
8659
8677
|
const re = /href="([^"]+\/p(?:\?[^"]*|\/[^"]+|))"/gi;
|
|
8660
|
-
|
|
8661
|
-
|
|
8678
|
+
for (;; ) {
|
|
8679
|
+
const m = re.exec(html);
|
|
8680
|
+
if (!m)
|
|
8681
|
+
break;
|
|
8662
8682
|
const path = m[1];
|
|
8663
8683
|
if (!path)
|
|
8664
8684
|
continue;
|
|
@@ -8666,7 +8686,10 @@ async function fetchPlpProducts(url) {
|
|
|
8666
8686
|
paths.add(normalized);
|
|
8667
8687
|
}
|
|
8668
8688
|
const re2 = /href="([^"]+\/products\/[^"]+)"/gi;
|
|
8669
|
-
|
|
8689
|
+
for (;; ) {
|
|
8690
|
+
const m = re2.exec(html);
|
|
8691
|
+
if (!m)
|
|
8692
|
+
break;
|
|
8670
8693
|
const path = m[1];
|
|
8671
8694
|
if (path)
|
|
8672
8695
|
paths.add(path.split("?")[0].replace(/\/$/, ""));
|
|
@@ -8699,8 +8722,10 @@ async function discoverPlpFromHome(homeUrl) {
|
|
|
8699
8722
|
const html = await res.text();
|
|
8700
8723
|
const candidates = new Set;
|
|
8701
8724
|
const re = /href="([^"]+)"/gi;
|
|
8702
|
-
|
|
8703
|
-
|
|
8725
|
+
for (;; ) {
|
|
8726
|
+
const m = re.exec(html);
|
|
8727
|
+
if (!m)
|
|
8728
|
+
break;
|
|
8704
8729
|
const href = m[1];
|
|
8705
8730
|
if (!href)
|
|
8706
8731
|
continue;
|
|
@@ -11483,9 +11508,12 @@ function compactHtmlForRecovery(html, maxChars = 12000) {
|
|
|
11483
11508
|
if (attrs.class) {
|
|
11484
11509
|
const tokens = attrs.class.split(/\s+/).filter(Boolean);
|
|
11485
11510
|
const kept = tokens.filter((t) => isSemanticClassToken(t));
|
|
11486
|
-
|
|
11487
|
-
if (
|
|
11511
|
+
const joined = kept.join(" ");
|
|
11512
|
+
if (joined) {
|
|
11513
|
+
attrs.class = joined;
|
|
11514
|
+
} else {
|
|
11488
11515
|
delete attrs.class;
|
|
11516
|
+
}
|
|
11489
11517
|
}
|
|
11490
11518
|
});
|
|
11491
11519
|
const allowed = $("header, nav, main, footer, dialog, [role='dialog'], form, button, a, input, [data-buy-button], [data-checkout], [data-minicart], [data-product-list], [role='button'], [aria-label]").map((_, el) => $.html(el)).get().join(`
|
|
@@ -11772,9 +11800,12 @@ function compactHtmlForSelectors(html, maxChars = 30000) {
|
|
|
11772
11800
|
if (attrs.class) {
|
|
11773
11801
|
const tokens = attrs.class.split(/\s+/).filter(Boolean);
|
|
11774
11802
|
const kept = tokens.filter((t) => isSemanticClass(t));
|
|
11775
|
-
|
|
11776
|
-
if (
|
|
11803
|
+
const joined2 = kept.join(" ");
|
|
11804
|
+
if (joined2) {
|
|
11805
|
+
attrs.class = joined2;
|
|
11806
|
+
} else {
|
|
11777
11807
|
delete attrs.class;
|
|
11808
|
+
}
|
|
11778
11809
|
}
|
|
11779
11810
|
});
|
|
11780
11811
|
const sections = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/parity",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.9",
|
|
4
4
|
"description": "E2E parity validator for site migrations. Compares prod vs cand and reports UI, functional, SEO, visual, and Web Vitals deltas with an LLM-ranked HTML report.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"bin": {
|
|
33
33
|
"parity": "dist/cli.js"
|
|
34
34
|
},
|
|
35
|
-
"files": ["dist", "README.md", "AGENTS.md", "CHANGELOG.md", "LICENSE"],
|
|
35
|
+
"files": ["dist", "scripts/postinstall.cjs", "README.md", "AGENTS.md", "CHANGELOG.md", "LICENSE"],
|
|
36
36
|
"scripts": {
|
|
37
37
|
"dev": "bun run --hot src/cli.ts",
|
|
38
38
|
"build": "bun build src/cli.ts --target=node --outfile=dist/cli.js --packages=external && bun run scripts/postbuild.ts",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"deadcode": "knip",
|
|
45
45
|
"lint": "biome lint .",
|
|
46
46
|
"fmt": "biome format --write .",
|
|
47
|
-
"prepublishOnly": "bun run check && bun run build"
|
|
47
|
+
"prepublishOnly": "bun run check && bun run build",
|
|
48
|
+
"postinstall": "node scripts/postinstall.cjs"
|
|
48
49
|
},
|
|
49
50
|
"engines": {
|
|
50
51
|
"node": ">=20",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Best-effort Playwright Chromium install on first `npm install` of
|
|
4
|
+
* `@decocms/parity`. Skipped when:
|
|
5
|
+
* - `PARITY_SKIP_PLAYWRIGHT_INSTALL=1` is set (CI / Docker / monorepos
|
|
6
|
+
* that manage the browser separately).
|
|
7
|
+
* - The binary is already present.
|
|
8
|
+
*
|
|
9
|
+
* Failures are downgraded to a warning so a `npm install -g` that
|
|
10
|
+
* happens to be offline / behind a corp proxy doesn't block the whole
|
|
11
|
+
* install. The runtime path in `engine/browser.ts:launchBrowser` catches
|
|
12
|
+
* the missing-browser case and tells the user exactly what to run.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
if (process.env.PARITY_SKIP_PLAYWRIGHT_INSTALL === "1") {
|
|
16
|
+
console.log("[parity] PARITY_SKIP_PLAYWRIGHT_INSTALL=1 set — skipping Chromium install");
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { spawnSync } = require("node:child_process");
|
|
21
|
+
|
|
22
|
+
// Probe Playwright to find out if Chromium is already extracted. The cheap
|
|
23
|
+
// way: try to resolve the binary via `npx playwright install --dry-run`.
|
|
24
|
+
// `--dry-run` exits 0 when everything is already installed and non-zero
|
|
25
|
+
// when downloads are needed. We don't actually rely on the exit code; we
|
|
26
|
+
// always attempt `install chromium` (idempotent) and let Playwright print
|
|
27
|
+
// "already up to date" when there's nothing to do.
|
|
28
|
+
try {
|
|
29
|
+
console.log("[parity] Installing Playwright Chromium (one-time, ~140 MB)…");
|
|
30
|
+
const result = spawnSync("npx", ["--yes", "playwright", "install", "chromium"], {
|
|
31
|
+
stdio: "inherit",
|
|
32
|
+
env: process.env,
|
|
33
|
+
});
|
|
34
|
+
if (result.status === 0) {
|
|
35
|
+
console.log("[parity] Chromium ready.");
|
|
36
|
+
} else {
|
|
37
|
+
console.log(
|
|
38
|
+
"[parity] Chromium install returned a non-zero exit. " +
|
|
39
|
+
"If `parity run` later fails with 'Executable doesn't exist', " +
|
|
40
|
+
"run `npx playwright install chromium` manually.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
const reason = err?.message ?? "unknown error";
|
|
45
|
+
console.log(
|
|
46
|
+
`[parity] Playwright install skipped (${reason}). Run \`npx playwright install chromium\` before your first \`parity run\`.`,
|
|
47
|
+
);
|
|
48
|
+
}
|