Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [0.11.7](https://github.com/decocms/parity/compare/v0.11.6...v0.11.7) (2026-06-17)

### Fixed

* **First-run UX after `npm install -g`.** Two papercuts:
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.
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.

### Known cosmetic warning

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).

## [0.11.6](https://github.com/decocms/parity/compare/v0.11.5...v0.11.6) (2026-06-17)

### Added
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ Three use cases drive the design:
## Quickstart

```bash
# Install
# Install — auto-installs Playwright Chromium on postinstall (~140 MB, one-time)
npm install -g @decocms/parity
# or run without install
npx @decocms/parity run --prod ... --cand ...

# In CI / behind a corp proxy? Skip the auto-install and do it later:
# PARITY_SKIP_PLAYWRIGHT_INSTALL=1 npm install -g @decocms/parity
# npx playwright install chromium

# First-time smoke run (~30s, no LLM needed)
parity run --prod https://oldsite.com --cand https://newsite.example.dev --preset smoke --open

Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@decocms/parity",
"version": "0.11.6",
"version": "0.11.7",
"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.",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -32,7 +32,7 @@
"bin": {
"parity": "dist/cli.js"
},
"files": ["dist", "README.md", "AGENTS.md", "CHANGELOG.md", "LICENSE"],
"files": ["dist", "scripts/postinstall.js", "README.md", "AGENTS.md", "CHANGELOG.md", "LICENSE"],
"scripts": {
"dev": "bun run --hot src/cli.ts",
"build": "bun build src/cli.ts --target=node --outfile=dist/cli.js --packages=external && bun run scripts/postbuild.ts",
Expand All @@ -44,7 +44,8 @@
"deadcode": "knip",
"lint": "biome lint .",
"fmt": "biome format --write .",
"prepublishOnly": "bun run check && bun run build"
"prepublishOnly": "bun run check && bun run build",
"postinstall": "node scripts/postinstall.js"
},
"engines": {
"node": ">=20",
Expand Down
48 changes: 48 additions & 0 deletions scripts/postinstall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env node
/**
* Best-effort Playwright Chromium install on first `npm install` of
* `@decocms/parity`. Skipped when:
* - `PARITY_SKIP_PLAYWRIGHT_INSTALL=1` is set (CI / Docker / monorepos
* that manage the browser separately).
* - The binary is already present.
*
* Failures are downgraded to a warning so a `npm install -g` that
* happens to be offline / behind a corp proxy doesn't block the whole
* install. The runtime path in `engine/browser.ts:launchBrowser` catches
* the missing-browser case and tells the user exactly what to run.
*/

if (process.env.PARITY_SKIP_PLAYWRIGHT_INSTALL === "1") {
console.log("[parity] PARITY_SKIP_PLAYWRIGHT_INSTALL=1 set — skipping Chromium install");
process.exit(0);
}

const { spawnSync } = require("node:child_process");

// Probe Playwright to find out if Chromium is already extracted. The cheap
// way: try to resolve the binary via `npx playwright install --dry-run`.
// `--dry-run` exits 0 when everything is already installed and non-zero
// when downloads are needed. We don't actually rely on the exit code; we
// always attempt `install chromium` (idempotent) and let Playwright print
// "already up to date" when there's nothing to do.
try {
console.log("[parity] Installing Playwright Chromium (one-time, ~140 MB)…");
const result = spawnSync("npx", ["--yes", "playwright", "install", "chromium"], {
stdio: "inherit",
env: process.env,
});
if (result.status === 0) {
console.log("[parity] Chromium ready.");
} else {
console.log(
"[parity] Chromium install returned a non-zero exit. " +
"If `parity run` later fails with 'Executable doesn't exist', " +
"run `npx playwright install chromium` manually.",
);
}
} catch (err) {
console.log(
`[parity] Playwright install skipped (${err && err.message ? err.message : "unknown error"}). ` +
"Run `npx playwright install chromium` before your first `parity run`.",
);
}
36 changes: 31 additions & 5 deletions src/engine/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,37 @@ export interface LaunchOptions {
}

export async function launchBrowser(opts: LaunchOptions = {}): Promise<Browser> {
return await chromium.launch({
headless: opts.headless ?? true,
slowMo: opts.slowMo ?? 0,
args: ["--disable-blink-features=AutomationControlled"],
});
try {
return await chromium.launch({
headless: opts.headless ?? true,
slowMo: opts.slowMo ?? 0,
args: ["--disable-blink-features=AutomationControlled"],
});
} catch (err) {
const msg = (err as Error).message ?? "";
// Playwright's own message starts with "Executable doesn't exist at ..."
// when the browser binary wasn't installed yet (e.g. fresh `npm install
// -g @decocms/parity` with no postinstall trigger). Replace it with a
// single clear instruction so the user doesn't have to parse a stack
// trace + Playwright's ASCII banner.
if (msg.includes("Executable doesn't exist")) {
const friendly = new Error(
[
"Playwright's Chromium binary is not installed yet. Run:",
"",
" npx playwright install chromium",
"",
"(one-time, ~140 MB download). Then re-run parity. This usually happens",
"after a fresh `npm install -g @decocms/parity`.",
].join("\n"),
);
// Preserve original error in `cause` so logs still have the full
// launcher path for debugging if needed.
(friendly as Error & { cause?: unknown }).cause = err;
throw friendly;
}
throw err;
}
}

export interface ContextOptions {
Expand Down
Loading