@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 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
- return await chromium.launch({
1794
- headless: opts.headless ?? true,
1795
- slowMo: opts.slowMo ?? 0,
1796
- args: ["--disable-blink-features=AutomationControlled"]
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 ? `PLP pagination working on both sides (tested page=2 + page=3)` : `${issues.length} pagination issue(s) — see details`,
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
- let m;
8661
- while (m = re.exec(html)) {
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
- while (m = re2.exec(html)) {
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
- let m;
8703
- while (m = re.exec(html)) {
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
- attrs.class = kept.join(" ");
11487
- if (!attrs.class)
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
- attrs.class = kept.join(" ");
11776
- if (!attrs.class)
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.6",
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
+ }