@decocms/parity 0.11.9 → 0.11.11

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 (3) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cli.js +68 -26
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ 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.11](https://github.com/decocms/parity/compare/v0.11.10...v0.11.11) (2026-06-17)
9
+
10
+ ### Fixed
11
+
12
+ * **Runtime auto-install was incomplete in 0.11.10.** The 0.11.10 install path ran `npx --yes playwright install chromium` and reported success, but Playwright 1.49+ launches `chromium-headless-shell` for `headless: true` — that's a *separate* binary download. The retry still failed with `Executable doesn't exist at .../chrome-headless-shell` and the user saw the friendly fallback message even though we tried to fix it. Live-reproduced against bagaggio.
13
+ * **Now installs both `chromium` AND `chromium-headless-shell`** so `parity run` works headless without manual intervention.
14
+ * **Uses the bundled Playwright CLI, not `npx --yes playwright`.** `npx --yes` may fetch a *different* Playwright version into its cache, and the binaries it downloads may not match the version `parity` actually launches — so even a successful install can leave the retry failing. We now resolve `playwright/cli.js` via `createRequire` against the local `node_modules` and spawn `node <local-cli> install chromium chromium-headless-shell` — guaranteed version-matched. Falls back to `npx` only if the local resolution fails.
15
+ * **Validated locally end-to-end**: cleared `~/Library/Caches/ms-playwright`, ran `parity audit`, both binaries downloaded (`chromium-1217` + `chromium_headless_shell-1217`), audit completed successfully — no manual `npx playwright install` needed.
16
+
17
+ ## [0.11.10](https://github.com/decocms/parity/compare/v0.11.9...v0.11.10) (2026-06-17)
18
+
19
+ ### Fixed
20
+
21
+ * **Runtime auto-install of Chromium when `postinstall` didn't run.** The 0.11.7 `postinstall` hook works when npm runs it — but plenty of installs skip lifecycle scripts (`npm config set ignore-scripts true`, `npm 11+` global-install default, monorepos with `--ignore-scripts`). Users saw a friendly "run `npx playwright install chromium`" message but still had to run a second command before `parity` worked. Now `launchBrowser` catches the missing-browser error and runs `npx --yes playwright install chromium` inline (with stdio inherited so the download progress is visible), then retries the launch once. The original "you must run X" path is preserved via `PARITY_SKIP_PLAYWRIGHT_INSTALL=1` for CI / Docker / monorepos that want explicit control. Net effect: a fresh `npm i -g @decocms/parity && parity run …` works in one command even when lifecycle scripts are disabled.
22
+
8
23
  ## [0.11.9](https://github.com/decocms/parity/compare/v0.11.8...v0.11.9) (2026-06-17)
9
24
 
10
25
  ### Fixed
package/dist/cli.js CHANGED
@@ -1618,6 +1618,9 @@ function countBy(arr, pick) {
1618
1618
  }
1619
1619
 
1620
1620
  // src/engine/browser.ts
1621
+ import { spawnSync } from "node:child_process";
1622
+ import { createRequire as createRequire2 } from "node:module";
1623
+ import { dirname, resolve as resolvePath } from "node:path";
1621
1624
  import { chromium, devices } from "playwright";
1622
1625
 
1623
1626
  // src/engine/carousel-stabilizer.ts
@@ -1790,30 +1793,69 @@ var VIEWPORT_PRESETS = {
1790
1793
  }
1791
1794
  };
1792
1795
  async function launchBrowser(opts = {}) {
1796
+ const doLaunch = () => chromium.launch({
1797
+ headless: opts.headless ?? true,
1798
+ slowMo: opts.slowMo ?? 0,
1799
+ args: ["--disable-blink-features=AutomationControlled"]
1800
+ });
1793
1801
  try {
1794
- return await chromium.launch({
1795
- headless: opts.headless ?? true,
1796
- slowMo: opts.slowMo ?? 0,
1797
- args: ["--disable-blink-features=AutomationControlled"]
1798
- });
1802
+ return await doLaunch();
1799
1803
  } catch (err) {
1800
1804
  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;
1805
+ if (!msg.includes("Executable doesn't exist"))
1806
+ throw err;
1807
+ if (process.env.PARITY_SKIP_PLAYWRIGHT_INSTALL === "1") {
1808
+ throw missingBrowserError(err);
1813
1809
  }
1814
- throw err;
1810
+ console.log("");
1811
+ console.log(" ⚠ Playwright's Chromium binary is not installed yet — downloading now (~140 MB, one-time)…");
1812
+ console.log(" Set PARITY_SKIP_PLAYWRIGHT_INSTALL=1 to disable this auto-install.");
1813
+ console.log("");
1814
+ const installRc = installChromiumSync();
1815
+ if (installRc !== 0)
1816
+ throw missingBrowserError(err);
1817
+ console.log(" ✓ Chromium ready. Continuing the run.");
1818
+ console.log("");
1819
+ try {
1820
+ return await doLaunch();
1821
+ } catch (retryErr) {
1822
+ throw missingBrowserError(retryErr);
1823
+ }
1824
+ }
1825
+ }
1826
+ function installChromiumSync() {
1827
+ const localCli = resolveLocalPlaywrightCli();
1828
+ const cmd = localCli ? { command: process.execPath, args: [localCli, "install", "chromium", "chromium-headless-shell"] } : { command: "npx", args: ["--yes", "playwright", "install", "chromium", "chromium-headless-shell"] };
1829
+ const result = spawnSync(cmd.command, cmd.args, {
1830
+ stdio: "inherit",
1831
+ env: process.env
1832
+ });
1833
+ return result.status ?? 1;
1834
+ }
1835
+ function resolveLocalPlaywrightCli() {
1836
+ try {
1837
+ const req = createRequire2(import.meta.url);
1838
+ const pkgJsonPath = req.resolve("playwright/package.json");
1839
+ const pkg = req("playwright/package.json");
1840
+ const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.playwright;
1841
+ if (!binEntry)
1842
+ return null;
1843
+ return resolvePath(dirname(pkgJsonPath), binEntry);
1844
+ } catch {
1845
+ return null;
1815
1846
  }
1816
1847
  }
1848
+ function missingBrowserError(cause) {
1849
+ const original = cause instanceof Error ? cause.message : String(cause);
1850
+ return new Error([
1851
+ "Playwright's Chromium binary is not installed.",
1852
+ "Run: npx playwright install chromium chromium-headless-shell",
1853
+ "Or unset PARITY_SKIP_PLAYWRIGHT_INSTALL and rerun `parity` to auto-install.",
1854
+ "",
1855
+ `Original error: ${original}`
1856
+ ].join(`
1857
+ `));
1858
+ }
1817
1859
  async function newContext(browser, opts) {
1818
1860
  const baseContext = VIEWPORT_PRESETS[opts.viewport];
1819
1861
  const ctx = await browser.newContext({
@@ -10242,7 +10284,7 @@ async function visualSemanticDiff(input) {
10242
10284
  // src/checks/lib/parity-cache.ts
10243
10285
  import { createHash } from "node:crypto";
10244
10286
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
10245
- import { dirname, join as join6 } from "node:path";
10287
+ import { dirname as dirname2, join as join6 } from "node:path";
10246
10288
  function hashScreenshotPair(prodPath, candPath, promptVersion) {
10247
10289
  const h = createHash("sha256");
10248
10290
  h.update(readFileSync6(prodPath));
@@ -10271,7 +10313,7 @@ function readCache(cacheDir) {
10271
10313
  }
10272
10314
  function writeCache(cacheDir, cache) {
10273
10315
  const file = cacheFilePath(cacheDir);
10274
- mkdirSync3(dirname(file), { recursive: true });
10316
+ mkdirSync3(dirname2(file), { recursive: true });
10275
10317
  writeFileSync6(file, JSON.stringify(cache, null, 2));
10276
10318
  }
10277
10319
  function getCacheEntry(cache, hash2, currentPromptVersion) {
@@ -12033,7 +12075,7 @@ async function resolveSearchTerms(url, html, opts = {}) {
12033
12075
 
12034
12076
  // src/learned/repo.ts
12035
12077
  import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync, writeFileSync as writeFileSync9 } from "node:fs";
12036
- import { dirname as dirname2 } from "node:path";
12078
+ import { dirname as dirname3 } from "node:path";
12037
12079
  import { z as z2 } from "zod";
12038
12080
  var SelectorKey = z2.enum([
12039
12081
  "categoryLink",
@@ -12098,7 +12140,7 @@ function loadLearned(path = DEFAULT_PATH) {
12098
12140
  }
12099
12141
  }
12100
12142
  function saveLearned(lib, path = DEFAULT_PATH) {
12101
- const dir = dirname2(path);
12143
+ const dir = dirname3(path);
12102
12144
  if (dir && dir !== "." && !existsSync10(dir))
12103
12145
  mkdirSync7(dir, { recursive: true });
12104
12146
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
@@ -15742,7 +15784,7 @@ function analyzeHeatmapBuffer(data, width, height, opts = {}) {
15742
15784
 
15743
15785
  // src/diff/section-bundle.ts
15744
15786
  import { writeFileSync as writeFileSync11 } from "node:fs";
15745
- import { basename as basename2, dirname as dirname3, join as join13, relative as relative3 } from "node:path";
15787
+ import { basename as basename2, dirname as dirname4, join as join13, relative as relative3 } from "node:path";
15746
15788
 
15747
15789
  // src/engine/computed-styles.ts
15748
15790
  var SECTION_STYLE_KEYS = [
@@ -16020,7 +16062,7 @@ function renderMarkdownBundle(input, deltas) {
16020
16062
  }
16021
16063
  function relativeOrAbs(outDir, absPath) {
16022
16064
  try {
16023
- if (dirname3(absPath) === outDir)
16065
+ if (dirname4(absPath) === outDir)
16024
16066
  return basename2(absPath);
16025
16067
  return relative3(outDir, absPath);
16026
16068
  } catch {
@@ -19980,11 +20022,11 @@ function printSummary6(vitals, cache) {
19980
20022
 
19981
20023
  // src/util/version.ts
19982
20024
  import { readFileSync as readFileSync18 } from "node:fs";
19983
- import { dirname as dirname4, resolve as resolve3 } from "node:path";
20025
+ import { dirname as dirname5, resolve as resolve3 } from "node:path";
19984
20026
  import { fileURLToPath } from "node:url";
19985
20027
  function getPackageVersion() {
19986
20028
  try {
19987
- const here = dirname4(fileURLToPath(import.meta.url));
20029
+ const here = dirname5(fileURLToPath(import.meta.url));
19988
20030
  for (const up of ["..", "../..", "../../.."]) {
19989
20031
  try {
19990
20032
  const pkgPath = resolve3(here, up, "package.json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/parity",
3
- "version": "0.11.9",
3
+ "version": "0.11.11",
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",