@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.
- package/CHANGELOG.md +15 -0
- package/dist/cli.js +68 -26
- 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
|
|
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
|
-
|
|
1803
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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 (
|
|
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
|
|
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 =
|
|
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.
|
|
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",
|