@crvy/rprtr 0.3.2 → 0.3.3
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 +19 -0
- package/README.md +43 -1
- package/dist/chunk-7JUICMVX.js +62 -0
- package/dist/{chunk-C5SVPK2O.js → chunk-NXW25MU3.js} +100 -44
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +30 -10
- package/dist/fontconfig.d.ts +43 -0
- package/dist/fontconfig.d.ts.map +1 -0
- package/dist/index.css +4 -0
- package/dist/rendering.cjs +114 -0
- package/dist/rendering.d.ts +61 -0
- package/dist/rendering.d.ts.map +1 -0
- package/dist/rendering.js +30 -0
- package/dist/server/app.d.ts +9 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/docker-env.d.ts +7 -0
- package/dist/server/docker-env.d.ts.map +1 -0
- package/dist/server/docker-launcher.d.ts +11 -0
- package/dist/server/docker-launcher.d.ts.map +1 -1
- package/dist/server/fontconfig.d.ts +22 -0
- package/dist/server/fontconfig.d.ts.map +1 -0
- package/dist/server/launcher-resolver.d.ts +6 -0
- package/dist/server/launcher-resolver.d.ts.map +1 -1
- package/dist/server/run-launcher.d.ts +13 -2
- package/dist/server/run-launcher.d.ts.map +1 -1
- package/dist/server.cjs +181 -69
- package/dist/server.js +2 -1
- package/package.json +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.3] - 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **rendering:** Pin grayscale text antialiasing in every run mode
|
|
13
|
+
|
|
14
|
+
### Documentation
|
|
15
|
+
|
|
16
|
+
- **examples:** Add component-testing example for playwright stories + gallery
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **ci:** Green lint and docker smoke after grayscale antialiasing
|
|
21
|
+
- **docker:** Stop forwarding host HOME and Windows-path env into the container
|
|
22
|
+
- **ci:** Install example deps in publish and enforce formatting checks
|
|
23
|
+
|
|
24
|
+
### Miscellaneous
|
|
25
|
+
|
|
26
|
+
- **ci:** Pin bun to 1.4 across workflows and engines
|
|
8
27
|
## [0.3.2] - 2026-08-25
|
|
9
28
|
|
|
10
29
|
### Fixed
|
package/README.md
CHANGED
|
@@ -90,6 +90,10 @@ When the server can resolve a Playwright config (via `--config` or auto-discover
|
|
|
90
90
|
3. **In the browser:** The UI shows all screenshot tests with side-by-side, swap, slide, and blend diff views.
|
|
91
91
|
4. **Approving changes:** Start the UI server and click "Approve" or "Approve All" to accept a new screenshot as the baseline. Approval uses the same exact Playwright-aware resolver as passed-baseline display, including default layouts, unnamed screenshots, duplicate names, and custom templates when the running server was started with matching resolver options. Those approval-routing options are read from the server startup path, not from reporter options. If the server starts without explicit resolver overrides, approval falls back to the server defaults instead. If Crvy Rprtr cannot determine exactly one target path, it leaves the image unresolved instead of guessing.
|
|
92
92
|
|
|
93
|
+
## Component Testing
|
|
94
|
+
|
|
95
|
+
Crvy Rprtr works with [Playwright Component Testing](https://playwright.dev/docs/test-components) (the stories + gallery model, Playwright ≥ 1.62) out of the box — component tests are regular Playwright tests, so live reporting, baseline display, diffs, approval, and Docker mode all work unchanged. See the complete, annotated example in [examples/component-testing](./examples/component-testing).
|
|
96
|
+
|
|
93
97
|
## Offline Mode
|
|
94
98
|
|
|
95
99
|
When the server isn't running during tests, the reporter automatically falls back to offline mode:
|
|
@@ -143,8 +147,9 @@ The server still runs on your host; only `playwright test` executes in the conta
|
|
|
143
147
|
| `--run-mode <mode>` | `local`, `docker`, or `auto` (default: `auto`) |
|
|
144
148
|
| `--docker-image <image>` | Custom image. With a custom image, the container-side package manager is auto-detected from your lockfile (`npx` / `pnpm exec` / `yarn` / `bunx`); the image must contain it |
|
|
145
149
|
| `--docker-platform <platform>` | `linux/amd64` or `linux/arm64` (default: host architecture) |
|
|
150
|
+
| `--font-rendering <mode>` | Text antialiasing for runs in either mode: `grayscale` (default, deterministic) or `inherit` (see [Text antialiasing](#text-antialiasing)) |
|
|
146
151
|
|
|
147
|
-
Programmatic equivalents: `startServer({ runMode: 'docker', docker: { image, platform, command, extraArgs } })`. `docker.command` overrides the container-side invocation verbatim (e.g. `['pnpm', 'exec', 'playwright']`); `docker.extraArgs` appends raw `docker run` flags.
|
|
152
|
+
Programmatic equivalents: `startServer({ runMode: 'docker', fontRendering: 'grayscale', docker: { image, platform, command, extraArgs } })`. `docker.command` overrides the container-side invocation verbatim (e.g. `['pnpm', 'exec', 'playwright']`); `docker.extraArgs` appends raw `docker run` flags.
|
|
148
153
|
|
|
149
154
|
### Windows
|
|
150
155
|
|
|
@@ -161,8 +166,45 @@ Notes:
|
|
|
161
166
|
- Baselines are **not** architecture-specific for typical DOM/text pages: amd64 and arm64 variants of the same image render bit-identically in practice (verified: 100/103 tests byte-identical between an amd64 CI runner and Apple Silicon). Use the native architecture on every host — do **not** pin `--docker-platform` to force amd64 emulation on Apple Silicon (Rosetta/QEMU is slower and less stable, and buys nothing). Residual risk: canvas 2D / complex SVG / WebGL content can show tiny cross-arch anti-aliasing diffs; handle per-test with `maxDiffPixels`.
|
|
162
167
|
- If your `playwright.config.ts` uses `webServer`, that server now starts inside the container: it must bind `0.0.0.0`, and hosts it references must resolve inside the container.
|
|
163
168
|
- Timezone and locale are pinned (`TZ=UTC`, `LANG=C.UTF-8`, `LC_ALL=C.UTF-8`) so date/number rendering in screenshots is stable; override via `docker.extraArgs` if you need a different locale under test.
|
|
169
|
+
- Text antialiasing is pinned too: a fontconfig drop-in mounted at `/etc/fonts/conf.d/99-crvy-rprtr-grayscale.conf` switches Chromium to grayscale AA, so screenshots no longer depend on whether the image enables subpixel (LCD) rendering. Opt out with `fontRendering: 'inherit'` (see [Text antialiasing](#text-antialiasing)).
|
|
164
170
|
- The "Run & update baselines" button (▶↻) regenerates baselines inside the container, keeping generation and verification in the same image.
|
|
165
171
|
|
|
172
|
+
### Text antialiasing
|
|
173
|
+
|
|
174
|
+
Chromium on Linux takes its text AA mode from **fontconfig**, so screenshots change with the environment rather than with the page: a system Chromium passed via `executablePath`, an agent-provided binary or a second image renders the same text with colored subpixel fringes on every glyph. Glyph positions stay identical, so the diff is invisible by eye and fatal to the comparator — colored fringes on ~3% of a text-heavy page, channel deltas up to 100.
|
|
175
|
+
|
|
176
|
+
crvy-rprtr therefore pins grayscale AA in **both** run modes, with no configuration:
|
|
177
|
+
|
|
178
|
+
| run mode | mechanism |
|
|
179
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
180
|
+
| docker | fontconfig drop-in mounted at `/etc/fonts/conf.d/99-crvy-rprtr-grayscale.conf` |
|
|
181
|
+
| local | `FONTCONFIG_FILE` exported to the spawned `playwright test` process (a generated root config that includes the system one, then forces `rgba=none`); every browser it launches inherits it |
|
|
182
|
+
|
|
183
|
+
Both were verified to produce byte-identical screenshots, so runs can be mixed freely. On macOS and Windows the local-mode override is skipped: fontconfig does not drive text rendering there (macOS has had no subpixel AA since Mojave, Windows uses DirectWrite).
|
|
184
|
+
|
|
185
|
+
Turn it off with `fontRendering: 'inherit'` — as `startServer({ fontRendering: 'inherit' })` or `crvy-rprtr --font-rendering inherit` — when faithful "what a desktop user sees" text matters more than determinism. `docker: { fontRendering: 'inherit' }` still works and takes precedence in docker mode.
|
|
186
|
+
|
|
187
|
+
#### Runs that crvy-rprtr does not launch
|
|
188
|
+
|
|
189
|
+
Plain `npx playwright test` in CI is not covered by the above — it does not go through the launcher. Match it from the Playwright config:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { defineConfig } from '@playwright/test'
|
|
193
|
+
import { deterministicLaunchOptions } from '@crvy/rprtr/rendering'
|
|
194
|
+
|
|
195
|
+
export default defineConfig({
|
|
196
|
+
use: { launchOptions: deterministicLaunchOptions() },
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The helper writes the same generated fontconfig root config and merges `FONTCONFIG_FILE` into `launchOptions.env`, keeping the inherited environment and the caller's own entries. It applies to every browser (`deterministicLaunchOptions(base, { fontRendering: 'inherit' })` opts out), needs no file committed to the repo, and produces screenshots byte-identical to both run modes. Outside Linux it returns the options unchanged.
|
|
201
|
+
|
|
202
|
+
`deterministicChromiumLaunchOptions()`, which adds `--disable-lcd-text` instead, remains available for configs that cannot set browser env vars; it is byte-identical for Chromium but Chromium-only, since Firefox rejects unknown command-line flags.
|
|
203
|
+
|
|
204
|
+
Firefox and WebKit need no equivalent: the Playwright builds never rasterize with subpixel AA — forcing `rgba=rgb` through fontconfig leaves their screenshots byte-identical — so a multi-browser suite is fully covered.
|
|
205
|
+
|
|
206
|
+
Baselines captured with subpixel AA have to be regenerated once after adopting any of this. Full investigation: [docs/text-antialiasing-determinism.md](docs/text-antialiasing-determinism.md).
|
|
207
|
+
|
|
166
208
|
## Programmatic API
|
|
167
209
|
|
|
168
210
|
```ts
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/fontconfig.ts
|
|
2
|
+
import { existsSync, mkdirSync, renameSync, writeFileSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
var GRAYSCALE_MATCH = ` <match target="font">
|
|
6
|
+
<edit name="rgba" mode="assign"><const>none</const></edit>
|
|
7
|
+
</match>`;
|
|
8
|
+
var HEADER = `<?xml version="1.0"?>
|
|
9
|
+
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
|
|
10
|
+
<!-- Written by crvy-rprtr: pins grayscale text antialiasing for screenshot determinism. -->`;
|
|
11
|
+
var GRAYSCALE_FONTCONFIG_XML = `${HEADER}
|
|
12
|
+
<fontconfig>
|
|
13
|
+
${GRAYSCALE_MATCH}
|
|
14
|
+
</fontconfig>
|
|
15
|
+
`;
|
|
16
|
+
function grayscaleRootFontconfigXml(systemConfigPath) {
|
|
17
|
+
return `${HEADER}
|
|
18
|
+
<fontconfig>
|
|
19
|
+
<include ignore_missing="no">${escapeXml(systemConfigPath)}</include>
|
|
20
|
+
${GRAYSCALE_MATCH}
|
|
21
|
+
</fontconfig>
|
|
22
|
+
`;
|
|
23
|
+
}
|
|
24
|
+
function escapeXml(value) {
|
|
25
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
26
|
+
}
|
|
27
|
+
var SYSTEM_FONTCONFIG_PATHS = ["/etc/fonts/fonts.conf", "/usr/local/etc/fonts/fonts.conf"];
|
|
28
|
+
function rootFontconfigPath() {
|
|
29
|
+
return join(tmpdir(), "crvy-rprtr", "fonts.conf");
|
|
30
|
+
}
|
|
31
|
+
function resolveSystemFontconfig(env = process.env, exists = existsSync) {
|
|
32
|
+
const ours = rootFontconfigPath();
|
|
33
|
+
const candidates = [
|
|
34
|
+
env.FONTCONFIG_FILE,
|
|
35
|
+
env.FONTCONFIG_PATH === void 0 ? void 0 : join(env.FONTCONFIG_PATH, "fonts.conf"),
|
|
36
|
+
...SYSTEM_FONTCONFIG_PATHS
|
|
37
|
+
];
|
|
38
|
+
for (const candidate of candidates) {
|
|
39
|
+
if (candidate === void 0 || candidate === ours) continue;
|
|
40
|
+
if (exists(candidate)) return candidate;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function ensureRootFontconfig(systemConfigPath) {
|
|
45
|
+
const path = rootFontconfigPath();
|
|
46
|
+
mkdirSync(join(tmpdir(), "crvy-rprtr"), { recursive: true });
|
|
47
|
+
const staging = `${path}.${process.pid}.tmp`;
|
|
48
|
+
writeFileSync(staging, grayscaleRootFontconfigXml(systemConfigPath), "utf8");
|
|
49
|
+
renameSync(staging, path);
|
|
50
|
+
return path;
|
|
51
|
+
}
|
|
52
|
+
function grayscaleFontconfigEnv(baseEnv = process.env, options = {}) {
|
|
53
|
+
if ((options.platform ?? process.platform) !== "linux") return null;
|
|
54
|
+
const systemConfig = resolveSystemFontconfig(baseEnv, options.exists);
|
|
55
|
+
if (systemConfig === null) return null;
|
|
56
|
+
return { FONTCONFIG_FILE: (options.writeConfig ?? ensureRootFontconfig)(systemConfig) };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
GRAYSCALE_FONTCONFIG_XML,
|
|
61
|
+
grayscaleFontconfigEnv
|
|
62
|
+
};
|
|
@@ -17,9 +17,13 @@ import {
|
|
|
17
17
|
resolveBaselineTargets,
|
|
18
18
|
safeParse
|
|
19
19
|
} from "./chunk-7YLKL3SL.js";
|
|
20
|
+
import {
|
|
21
|
+
GRAYSCALE_FONTCONFIG_XML,
|
|
22
|
+
grayscaleFontconfigEnv
|
|
23
|
+
} from "./chunk-7JUICMVX.js";
|
|
20
24
|
|
|
21
25
|
// src/server/app.ts
|
|
22
|
-
import { dirname as dirname4, join as
|
|
26
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
23
27
|
import { fileURLToPath } from "url";
|
|
24
28
|
|
|
25
29
|
// src/offline-reports.ts
|
|
@@ -209,6 +213,47 @@ function mergeOfflineReportsIntoTests(existingTests, offlineReports, options = {
|
|
|
209
213
|
};
|
|
210
214
|
}
|
|
211
215
|
|
|
216
|
+
// src/server/docker-env.ts
|
|
217
|
+
var ENV_DENYLIST = /* @__PURE__ */ new Set([
|
|
218
|
+
"CI",
|
|
219
|
+
"PLAYWRIGHT_BROWSERS_PATH",
|
|
220
|
+
"CRVY_RPRTR_SERVER_URL",
|
|
221
|
+
"CRVY_RPRTR_PORTABLE_ARTIFACTS",
|
|
222
|
+
"TZ",
|
|
223
|
+
// The container must use its own home: a forwarded host HOME makes NSS (browser cert
|
|
224
|
+
// store init) and npm resolve their state under it, and a Windows-style value like
|
|
225
|
+
// `C:\Users\dev` is a *relative* path on Linux, so those tools would create a literal
|
|
226
|
+
// `C:\Users\dev/.local/share/pki/nssdb` tree under the container CWD — i.e. inside the
|
|
227
|
+
// bind-mounted project.
|
|
228
|
+
"HOME",
|
|
229
|
+
"NPM_CONFIG_CACHE",
|
|
230
|
+
"LANG",
|
|
231
|
+
"LC_ALL",
|
|
232
|
+
"PLAYWRIGHT_HTML_OPEN",
|
|
233
|
+
"PATH",
|
|
234
|
+
// Host paths that do not exist in the container: fontconfig resolves them to nothing and
|
|
235
|
+
// the browser ends up with no font directories at all (blank text). The container gets its
|
|
236
|
+
// AA from the mounted drop-in instead.
|
|
237
|
+
"FONTCONFIG_FILE",
|
|
238
|
+
"FONTCONFIG_PATH"
|
|
239
|
+
]);
|
|
240
|
+
var WINDOWS_ENV_NOISE = new Set(
|
|
241
|
+
"SYSTEMROOT COMSPEC WINDIR PATHEXT OS PROGRAMFILES PROGRAMFILES(X86) PROGRAMW6432 PROGRAMDATA ALLUSERSPROFILE PUBLIC APPDATA LOCALAPPDATA TEMP TMP USERPROFILE HOMEDRIVE HOMEPATH USERNAME PSMODULEPATH DRIVERDATA NUMBER_OF_PROCESSORS PROCESSOR_ARCHITECTURE PROCESSOR_LEVEL PROCESSOR_REVISION".split(
|
|
242
|
+
" "
|
|
243
|
+
)
|
|
244
|
+
);
|
|
245
|
+
var WINDOWS_PATH_VALUE = /^(?:[A-Za-z]:[\\/]|\\\\)/;
|
|
246
|
+
function collectForwardedEnvNames(env, platform) {
|
|
247
|
+
const names = [];
|
|
248
|
+
for (const [key, value] of Object.entries(env)) {
|
|
249
|
+
const upper = key.toUpperCase();
|
|
250
|
+
if (ENV_DENYLIST.has(upper) || WINDOWS_ENV_NOISE.has(upper) || value === void 0) continue;
|
|
251
|
+
if (platform === "win32" && WINDOWS_PATH_VALUE.test(value)) continue;
|
|
252
|
+
names.push(key);
|
|
253
|
+
}
|
|
254
|
+
return names;
|
|
255
|
+
}
|
|
256
|
+
|
|
212
257
|
// src/server/docker-support.ts
|
|
213
258
|
import { spawn } from "child_process";
|
|
214
259
|
import { readFileSync } from "node:fs";
|
|
@@ -372,12 +417,27 @@ function buildTestListEntries(tests, rootDir, cwd, pathStyle = "host") {
|
|
|
372
417
|
});
|
|
373
418
|
}
|
|
374
419
|
|
|
420
|
+
// src/server/fontconfig.ts
|
|
421
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
422
|
+
import { tmpdir } from "os";
|
|
423
|
+
import { join as join3 } from "path";
|
|
424
|
+
var CONTAINER_FONTCONFIG_PATH = "/etc/fonts/conf.d/99-crvy-rprtr-grayscale.conf";
|
|
425
|
+
function hostFontconfigPath() {
|
|
426
|
+
return join3(tmpdir(), "crvy-rprtr", "99-crvy-rprtr-grayscale.conf");
|
|
427
|
+
}
|
|
428
|
+
function ensureGrayscaleFontconfig() {
|
|
429
|
+
const path = hostFontconfigPath();
|
|
430
|
+
mkdirSync(join3(tmpdir(), "crvy-rprtr"), { recursive: true });
|
|
431
|
+
writeFileSync(path, GRAYSCALE_FONTCONFIG_XML, "utf8");
|
|
432
|
+
return path;
|
|
433
|
+
}
|
|
434
|
+
|
|
375
435
|
// src/server/run-controller.ts
|
|
376
436
|
import { spawn as spawn2 } from "child_process";
|
|
377
|
-
import { unlinkSync, writeFileSync } from "node:fs";
|
|
437
|
+
import { unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
378
438
|
import { createRequire as createRequire2 } from "node:module";
|
|
379
|
-
import { tmpdir } from "node:os";
|
|
380
|
-
import { join as
|
|
439
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
440
|
+
import { join as join4 } from "node:path";
|
|
381
441
|
|
|
382
442
|
// src/server/run-launcher.ts
|
|
383
443
|
import { resolveCommand } from "package-manager-detector/commands";
|
|
@@ -388,7 +448,7 @@ function resolvePlaywrightLaunch(cwd, playwrightArgs) {
|
|
|
388
448
|
if (resolved !== null) return { cmd: resolved.command, args: resolved.args };
|
|
389
449
|
return { cmd: "npx", args: ["playwright", ...playwrightArgs] };
|
|
390
450
|
}
|
|
391
|
-
function buildSpawnEnv(port, baseEnv = process.env) {
|
|
451
|
+
function buildSpawnEnv(port, baseEnv = process.env, options = {}) {
|
|
392
452
|
const env = {};
|
|
393
453
|
for (const [key, value] of Object.entries(baseEnv)) {
|
|
394
454
|
if (key === "CI") continue;
|
|
@@ -396,6 +456,10 @@ function buildSpawnEnv(port, baseEnv = process.env) {
|
|
|
396
456
|
}
|
|
397
457
|
env.CRVY_RPRTR_SERVER_URL = `ws://localhost:${port}`;
|
|
398
458
|
env.PLAYWRIGHT_HTML_OPEN = "never";
|
|
459
|
+
const { fontRendering = "grayscale", ...fontconfigOptions } = options;
|
|
460
|
+
if (fontRendering !== "inherit") {
|
|
461
|
+
Object.assign(env, grayscaleFontconfigEnv(baseEnv, fontconfigOptions) ?? {});
|
|
462
|
+
}
|
|
399
463
|
return env;
|
|
400
464
|
}
|
|
401
465
|
function createLocalLauncher(options) {
|
|
@@ -404,7 +468,8 @@ function createLocalLauncher(options) {
|
|
|
404
468
|
launch({ ctx, playwrightArgs }) {
|
|
405
469
|
const resolve5 = options.resolveLaunch ?? resolvePlaywrightLaunch;
|
|
406
470
|
const { cmd, args } = resolve5(ctx.cwd, playwrightArgs);
|
|
407
|
-
|
|
471
|
+
const { port, resolveLaunch: _resolveLaunch, env: baseEnv, ...spawnEnvOptions } = options;
|
|
472
|
+
return { cmd, args, env: buildSpawnEnv(port, baseEnv, spawnEnvOptions) };
|
|
408
473
|
}
|
|
409
474
|
};
|
|
410
475
|
}
|
|
@@ -432,8 +497,8 @@ function gteMinor(version, major, minor) {
|
|
|
432
497
|
return min >= minor;
|
|
433
498
|
}
|
|
434
499
|
var defaultWriteTempFile = (content) => {
|
|
435
|
-
const path =
|
|
436
|
-
|
|
500
|
+
const path = join4(tmpdir2(), `crvy-rprtr-test-list-${process.pid}-${Date.now()}.txt`);
|
|
501
|
+
writeFileSync2(path, content, "utf8");
|
|
437
502
|
return path;
|
|
438
503
|
};
|
|
439
504
|
function defaultDeleteTempFile(path) {
|
|
@@ -444,7 +509,7 @@ function defaultDeleteTempFile(path) {
|
|
|
444
509
|
}
|
|
445
510
|
function resolveReporterDefault(cwd) {
|
|
446
511
|
try {
|
|
447
|
-
return createRequire2(
|
|
512
|
+
return createRequire2(join4(cwd, "package.json")).resolve("@crvy/rprtr");
|
|
448
513
|
} catch {
|
|
449
514
|
}
|
|
450
515
|
try {
|
|
@@ -610,22 +675,6 @@ function createRealTimers() {
|
|
|
610
675
|
// src/server/docker-launcher.ts
|
|
611
676
|
var DOCKER_WORK_DIR = "/work";
|
|
612
677
|
var DOCKER_HOST_GATEWAY = "host.docker.internal";
|
|
613
|
-
var ENV_DENYLIST = /* @__PURE__ */ new Set([
|
|
614
|
-
"CI",
|
|
615
|
-
"PLAYWRIGHT_BROWSERS_PATH",
|
|
616
|
-
"CRVY_RPRTR_SERVER_URL",
|
|
617
|
-
"CRVY_RPRTR_PORTABLE_ARTIFACTS",
|
|
618
|
-
"TZ",
|
|
619
|
-
"LANG",
|
|
620
|
-
"LC_ALL",
|
|
621
|
-
"PLAYWRIGHT_HTML_OPEN",
|
|
622
|
-
"PATH"
|
|
623
|
-
]);
|
|
624
|
-
var WINDOWS_ENV_NOISE = new Set(
|
|
625
|
-
"SYSTEMROOT COMSPEC WINDIR PATHEXT OS PROGRAMFILES PROGRAMFILES(X86) PROGRAMW6432 PROGRAMDATA ALLUSERSPROFILE PUBLIC APPDATA LOCALAPPDATA TEMP TMP USERPROFILE HOMEDRIVE HOMEPATH USERNAME PSMODULEPATH DRIVERDATA NUMBER_OF_PROCESSORS PROCESSOR_ARCHITECTURE PROCESSOR_IDENTIFIER PROCESSOR_LEVEL PROCESSOR_REVISION".split(
|
|
626
|
-
" "
|
|
627
|
-
)
|
|
628
|
-
);
|
|
629
678
|
var DockerUnavailableError = class extends Error {
|
|
630
679
|
constructor() {
|
|
631
680
|
super("Docker daemon is not available");
|
|
@@ -691,9 +740,10 @@ function buildDockerRunArgs(ctx, playwrightArgs, deps) {
|
|
|
691
740
|
args.push("-e", `CRVY_RPRTR_SERVER_URL=ws://${DOCKER_HOST_GATEWAY}:${deps.port}`);
|
|
692
741
|
args.push("-e", "CRVY_RPRTR_PORTABLE_ARTIFACTS=1", "-e", "TZ=UTC", "-e", "LANG=C.UTF-8", "-e", "LC_ALL=C.UTF-8");
|
|
693
742
|
args.push("-e", "PLAYWRIGHT_HTML_OPEN=never");
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
743
|
+
if (deps.docker?.fontRendering !== "inherit") {
|
|
744
|
+
args.push("-v", `${ensureGrayscaleFontconfig()}:${CONTAINER_FONTCONFIG_PATH}:ro`);
|
|
745
|
+
}
|
|
746
|
+
for (const key of collectForwardedEnvNames(deps.env, deps.platform)) {
|
|
697
747
|
args.push("-e", key);
|
|
698
748
|
}
|
|
699
749
|
if (deps.docker?.extraArgs !== void 0) args.push(...deps.docker.extraArgs);
|
|
@@ -756,7 +806,8 @@ function buildLauncher(state, deps) {
|
|
|
756
806
|
env: deps.baseEnv,
|
|
757
807
|
image,
|
|
758
808
|
command: state.command,
|
|
759
|
-
warn: deps.warn
|
|
809
|
+
warn: deps.warn,
|
|
810
|
+
platform: deps.platform
|
|
760
811
|
});
|
|
761
812
|
return { cmd: "docker", args, env: stripCi(deps.baseEnv) };
|
|
762
813
|
},
|
|
@@ -1078,7 +1129,11 @@ async function resolveRunBackend(options) {
|
|
|
1078
1129
|
console.warn(`[crvy-rprtr] ${message}`);
|
|
1079
1130
|
}
|
|
1080
1131
|
});
|
|
1081
|
-
const launcher = resolvedRunMode === "docker" ? createDockerLauncher({
|
|
1132
|
+
const launcher = resolvedRunMode === "docker" ? createDockerLauncher({
|
|
1133
|
+
port: options.port,
|
|
1134
|
+
docker: { fontRendering: options.fontRendering, ...options.docker },
|
|
1135
|
+
exec: dockerExec
|
|
1136
|
+
}) : createLocalLauncher({ port: options.port, fontRendering: options.fontRendering });
|
|
1082
1137
|
return {
|
|
1083
1138
|
launcher,
|
|
1084
1139
|
routesContextOptions: {
|
|
@@ -1089,7 +1144,7 @@ async function resolveRunBackend(options) {
|
|
|
1089
1144
|
}
|
|
1090
1145
|
|
|
1091
1146
|
// src/server/playwright-config.ts
|
|
1092
|
-
import { join as
|
|
1147
|
+
import { join as join5, resolve as resolve4 } from "path";
|
|
1093
1148
|
var CONFIG_FILES = [
|
|
1094
1149
|
"playwright.config.ts",
|
|
1095
1150
|
"playwright.config.mts",
|
|
@@ -1101,7 +1156,7 @@ var CONFIG_FILES = [
|
|
|
1101
1156
|
async function resolvePlaywrightConfig(cwd) {
|
|
1102
1157
|
const matches = await Promise.all(
|
|
1103
1158
|
CONFIG_FILES.map(async (file) => {
|
|
1104
|
-
const candidate =
|
|
1159
|
+
const candidate = join5(cwd, file);
|
|
1105
1160
|
return await fileExists(candidate) ? candidate : null;
|
|
1106
1161
|
})
|
|
1107
1162
|
);
|
|
@@ -1191,7 +1246,7 @@ function createRoutesContext(reportData, staticDir, saveReport, options) {
|
|
|
1191
1246
|
}
|
|
1192
1247
|
|
|
1193
1248
|
// src/server/routes.ts
|
|
1194
|
-
import { join as
|
|
1249
|
+
import { join as join6 } from "path";
|
|
1195
1250
|
|
|
1196
1251
|
// src/server/run-routes.ts
|
|
1197
1252
|
function handleRunRoutes(pathname, method, runController, req) {
|
|
@@ -1230,7 +1285,7 @@ function handleApiStop(runController) {
|
|
|
1230
1285
|
|
|
1231
1286
|
// src/server/routes.ts
|
|
1232
1287
|
async function handleRoot(ctx) {
|
|
1233
|
-
const html = await respondWithFile(
|
|
1288
|
+
const html = await respondWithFile(join6(ctx.staticDir, "index.html"), "text/html");
|
|
1234
1289
|
return html ?? new Response("Not Found", { status: 404 });
|
|
1235
1290
|
}
|
|
1236
1291
|
async function handleAppCss() {
|
|
@@ -1254,7 +1309,7 @@ function handleApiReport(ctx) {
|
|
|
1254
1309
|
var APPROVAL_TARGET_ERROR = "Could not resolve approval target";
|
|
1255
1310
|
function actualPathFromUrl(ctx, actualUrl) {
|
|
1256
1311
|
if (actualUrl.startsWith("/screenshots/")) {
|
|
1257
|
-
return
|
|
1312
|
+
return join6(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length));
|
|
1258
1313
|
}
|
|
1259
1314
|
if (actualUrl.startsWith("/file/")) {
|
|
1260
1315
|
return decodeURIComponent(actualUrl.slice("/file/".length));
|
|
@@ -1376,7 +1431,7 @@ async function handleScreenshots(ctx, req) {
|
|
|
1376
1431
|
}
|
|
1377
1432
|
async function handleDist(ctx, req) {
|
|
1378
1433
|
const path = new URL(req.url).pathname.slice("/dist/".length);
|
|
1379
|
-
const filePath =
|
|
1434
|
+
const filePath = join6(ctx.staticDir, path);
|
|
1380
1435
|
const contentType = filePath.endsWith(".css") ? "text/css" : filePath.endsWith(".js") ? "application/javascript" : filePath.endsWith(".svelte") ? "text/plain" : "application/octet-stream";
|
|
1381
1436
|
const file = await respondWithFile(filePath, contentType);
|
|
1382
1437
|
return file ?? new Response("Not Found", { status: 404 });
|
|
@@ -1565,16 +1620,16 @@ async function resolveStaticDir(staticDir) {
|
|
|
1565
1620
|
const currentDir = dirname4(fileURLToPath(import.meta.url));
|
|
1566
1621
|
const candidates = staticDir === void 0 ? [
|
|
1567
1622
|
currentDir,
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
] : [staticDir,
|
|
1623
|
+
join7(currentDir, "dist"),
|
|
1624
|
+
join7(currentDir, "..", "dist"),
|
|
1625
|
+
join7(currentDir, "..", "..", "dist"),
|
|
1626
|
+
join7(currentDir, ".."),
|
|
1627
|
+
join7(currentDir, "..", "..")
|
|
1628
|
+
] : [staticDir, join7(staticDir, "dist")];
|
|
1574
1629
|
const resolvedCandidates = await Promise.all(
|
|
1575
1630
|
candidates.map(async (candidate) => ({
|
|
1576
1631
|
candidate,
|
|
1577
|
-
exists: await fileExists(
|
|
1632
|
+
exists: await fileExists(join7(candidate, "index.html"))
|
|
1578
1633
|
}))
|
|
1579
1634
|
);
|
|
1580
1635
|
const resolved = resolvedCandidates.find(({ exists }) => exists);
|
|
@@ -1585,7 +1640,7 @@ async function resolveStaticDir(staticDir) {
|
|
|
1585
1640
|
}
|
|
1586
1641
|
async function resolveReportPath(reportPath) {
|
|
1587
1642
|
if (await isDirectory(reportPath)) {
|
|
1588
|
-
return { reportFile:
|
|
1643
|
+
return { reportFile: join7(reportPath, "report.json"), offlineReportDir: reportPath };
|
|
1589
1644
|
}
|
|
1590
1645
|
return { reportFile: reportPath, offlineReportDir: dirname4(reportPath) };
|
|
1591
1646
|
}
|
|
@@ -1602,6 +1657,7 @@ async function setupRoutesContext(options, reportData, staticDir, saveReport, po
|
|
|
1602
1657
|
const { launcher, routesContextOptions } = await resolveRunBackend({
|
|
1603
1658
|
runMode: options.runMode,
|
|
1604
1659
|
docker: options.docker,
|
|
1660
|
+
fontRendering: options.fontRendering,
|
|
1605
1661
|
port
|
|
1606
1662
|
});
|
|
1607
1663
|
const routesContext = createRoutesContext(reportData, staticDir, saveReport, {
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import type { FontRendering } from './rendering.ts';
|
|
2
3
|
import { type ServerOptions } from './server.ts';
|
|
3
4
|
import type { DockerOptions } from './server/docker-launcher.ts';
|
|
4
5
|
import type { RunMode } from './server/run-mode.ts';
|
|
@@ -9,8 +10,9 @@ interface ResolvedCliOptions extends ServerOptions {
|
|
|
9
10
|
outputDir: string;
|
|
10
11
|
runMode?: RunMode;
|
|
11
12
|
docker?: DockerOptions;
|
|
13
|
+
fontRendering?: FontRendering;
|
|
12
14
|
}
|
|
13
|
-
export declare const HELP_TEXT = "Usage: crvy-rprtr [artifact-dir] [options]\n\nStart the @crvy/rprtr visual regression report UI server.\n\nArguments:\n artifact-dir Directory of downloaded CI test artifacts. When set,\n --report-path defaults to <dir>/report.json and\n --screenshot-dir defaults to <dir>/screenshots.\n\nOptions:\n -p, --port <number> Server port (default: 3000)\n -s, --screenshot-dir <dir> Screenshot artifact directory (default: ./screenshots)\n -r, --report-path <path> Path to report.json (default: ./report.json)\n -o, --output-dir <dir> Playwright test output directory (default: ./test-results)\n -c, --config <path> Path to playwright.config.ts, used for approval routing\n --run-mode <mode> Test run backend: local, docker, or auto (default: auto)\n --docker-image <image> Docker image for docker mode (default: official Playwright image matching the installed @playwright/test version)\n --docker-platform <platform> Container platform: linux/amd64 or linux/arm64 (default: host architecture)\n -h, --help Show this help message\n";
|
|
15
|
+
export declare const HELP_TEXT = "Usage: crvy-rprtr [artifact-dir] [options]\n\nStart the @crvy/rprtr visual regression report UI server.\n\nArguments:\n artifact-dir Directory of downloaded CI test artifacts. When set,\n --report-path defaults to <dir>/report.json and\n --screenshot-dir defaults to <dir>/screenshots.\n\nOptions:\n -p, --port <number> Server port (default: 3000)\n -s, --screenshot-dir <dir> Screenshot artifact directory (default: ./screenshots)\n -r, --report-path <path> Path to report.json (default: ./report.json)\n -o, --output-dir <dir> Playwright test output directory (default: ./test-results)\n -c, --config <path> Path to playwright.config.ts, used for approval routing\n --run-mode <mode> Test run backend: local, docker, or auto (default: auto)\n --docker-image <image> Docker image for docker mode (default: official Playwright image matching the installed @playwright/test version)\n --docker-platform <platform> Container platform: linux/amd64 or linux/arm64 (default: host architecture)\n --font-rendering <mode> Text antialiasing for runs: grayscale (default, deterministic) or inherit (the environment's own rendering)\n -h, --help Show this help message\n";
|
|
14
16
|
export declare function printHelp(): void;
|
|
15
17
|
export declare function wantsHelp(args: string[]): boolean;
|
|
16
18
|
export declare function resolveCliOptions(args: string[]): ResolvedCliOptions;
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAKA,OAAO,EAAe,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAOnD,UAAU,kBAAmB,SAAQ,aAAa;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAe,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAOnD,UAAU,kBAAmB,SAAQ,aAAa;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,aAAa,CAAC,EAAE,aAAa,CAAA;CAC9B;AAED,eAAO,MAAM,SAAS,izCAoBrB,CAAA;AAED,wBAAgB,SAAS,IAAI,IAAI,CAEhC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAEjD;AA0BD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,kBAAkB,CA6CpE"}
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
startServer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-NXW25MU3.js";
|
|
5
5
|
import "./chunk-7YLKL3SL.js";
|
|
6
|
+
import "./chunk-7JUICMVX.js";
|
|
6
7
|
|
|
7
8
|
// src/cli.ts
|
|
8
9
|
import { join } from "path";
|
|
@@ -50,6 +51,7 @@ Options:
|
|
|
50
51
|
--run-mode <mode> Test run backend: local, docker, or auto (default: auto)
|
|
51
52
|
--docker-image <image> Docker image for docker mode (default: official Playwright image matching the installed @playwright/test version)
|
|
52
53
|
--docker-platform <platform> Container platform: linux/amd64 or linux/arm64 (default: host architecture)
|
|
54
|
+
--font-rendering <mode> Text antialiasing for runs: grayscale (default, deterministic) or inherit (the environment's own rendering)
|
|
53
55
|
-h, --help Show this help message
|
|
54
56
|
`;
|
|
55
57
|
function printHelp() {
|
|
@@ -58,6 +60,27 @@ function printHelp() {
|
|
|
58
60
|
function wantsHelp(args) {
|
|
59
61
|
return args.includes("--help") || args.includes("-h");
|
|
60
62
|
}
|
|
63
|
+
function parseRunMode(value) {
|
|
64
|
+
if (value === void 0) return void 0;
|
|
65
|
+
if (value !== "local" && value !== "docker" && value !== "auto") {
|
|
66
|
+
throw new TypeError(`Invalid --run-mode: ${value} (expected local, docker, or auto)`);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
function parseDockerPlatform(value) {
|
|
71
|
+
if (value === void 0) return void 0;
|
|
72
|
+
if (value !== "linux/amd64" && value !== "linux/arm64") {
|
|
73
|
+
throw new TypeError(`Invalid --docker-platform: ${value} (expected linux/amd64 or linux/arm64)`);
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
function parseFontRendering(value) {
|
|
78
|
+
if (value === void 0) return void 0;
|
|
79
|
+
if (value !== "grayscale" && value !== "inherit") {
|
|
80
|
+
throw new TypeError(`Invalid --font-rendering: ${value} (expected grayscale or inherit)`);
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
61
84
|
function resolveCliOptions(args) {
|
|
62
85
|
const { values, positionals } = parseArgs({
|
|
63
86
|
args,
|
|
@@ -70,7 +93,8 @@ function resolveCliOptions(args) {
|
|
|
70
93
|
config: { type: "string", short: "c" },
|
|
71
94
|
"run-mode": { type: "string" },
|
|
72
95
|
"docker-image": { type: "string" },
|
|
73
|
-
"docker-platform": { type: "string" }
|
|
96
|
+
"docker-platform": { type: "string" },
|
|
97
|
+
"font-rendering": { type: "string" }
|
|
74
98
|
}
|
|
75
99
|
});
|
|
76
100
|
if (positionals.length > 1) {
|
|
@@ -79,14 +103,9 @@ function resolveCliOptions(args) {
|
|
|
79
103
|
const artifactDir = positionals[0];
|
|
80
104
|
const reportPath = values["report-path"] ?? (artifactDir === void 0 ? DEFAULT_REPORT_PATH : join(artifactDir, "report.json"));
|
|
81
105
|
const screenshotDir = values["screenshot-dir"] ?? (artifactDir === void 0 ? DEFAULT_SCREENSHOT_DIR : join(artifactDir, "screenshots"));
|
|
82
|
-
const runMode = values["run-mode"];
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
const dockerPlatform = values["docker-platform"];
|
|
87
|
-
if (dockerPlatform !== void 0 && dockerPlatform !== "linux/amd64" && dockerPlatform !== "linux/arm64") {
|
|
88
|
-
throw new TypeError(`Invalid --docker-platform: ${dockerPlatform} (expected linux/amd64 or linux/arm64)`);
|
|
89
|
-
}
|
|
106
|
+
const runMode = parseRunMode(values["run-mode"]);
|
|
107
|
+
const dockerPlatform = parseDockerPlatform(values["docker-platform"]);
|
|
108
|
+
const fontRendering = parseFontRendering(values["font-rendering"]);
|
|
90
109
|
const docker = {};
|
|
91
110
|
if (values["docker-image"] !== void 0) docker.image = values["docker-image"];
|
|
92
111
|
if (dockerPlatform !== void 0) docker.platform = dockerPlatform;
|
|
@@ -98,6 +117,7 @@ function resolveCliOptions(args) {
|
|
|
98
117
|
outputDir: values["output-dir"] ?? DEFAULT_OUTPUT_DIR,
|
|
99
118
|
playwrightConfig: values.config,
|
|
100
119
|
...runMode === void 0 ? {} : { runMode },
|
|
120
|
+
...fontRendering === void 0 ? {} : { fontRendering },
|
|
101
121
|
...hasDockerOptions ? { docker } : {}
|
|
102
122
|
};
|
|
103
123
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop-in form, for `conf.d`: the rule alone, with the surrounding config supplied by the
|
|
3
|
+
* distro. Docker run mode mounts this at {@link CONTAINER_FONTCONFIG_PATH}.
|
|
4
|
+
*/
|
|
5
|
+
export declare const GRAYSCALE_FONTCONFIG_XML = "<?xml version=\"1.0\"?>\n<!DOCTYPE fontconfig SYSTEM \"urn:fontconfig:fonts.dtd\">\n<!-- Written by crvy-rprtr: pins grayscale text antialiasing for screenshot determinism. -->\n<fontconfig>\n <match target=\"font\">\n <edit name=\"rgba\" mode=\"assign\"><const>none</const></edit>\n </match>\n</fontconfig>\n";
|
|
6
|
+
/**
|
|
7
|
+
* Root-config form, for `FONTCONFIG_FILE`: the same rule, plus an include of the system
|
|
8
|
+
* config it replaces. The include is not optional — `FONTCONFIG_FILE` replaces the root
|
|
9
|
+
* config outright, and a root config with no font directories leaves the browser with no
|
|
10
|
+
* fonts at all (verified: a page of text rendered completely blank, total ink 0).
|
|
11
|
+
*/
|
|
12
|
+
export declare function grayscaleRootFontconfigXml(systemConfigPath: string): string;
|
|
13
|
+
/** Generated root config; stable path so repeated runs reuse one file. */
|
|
14
|
+
export declare function rootFontconfigPath(): string;
|
|
15
|
+
/**
|
|
16
|
+
* The root config our generated one must include: the caller's own `FONTCONFIG_FILE` /
|
|
17
|
+
* `FONTCONFIG_PATH` when they set one (their rules survive, ours are appended after), else the
|
|
18
|
+
* distro default. Returns null when nothing exists to include — the caller must then leave the
|
|
19
|
+
* environment alone rather than break font lookup.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveSystemFontconfig(env?: Record<string, string | undefined>, exists?: (path: string) => boolean): string | null;
|
|
22
|
+
/** Writes the generated root config and returns its path. Rename keeps concurrent Playwright workers from reading a half-written file. */
|
|
23
|
+
export declare function ensureRootFontconfig(systemConfigPath: string): string;
|
|
24
|
+
export interface GrayscaleFontconfigEnvOptions {
|
|
25
|
+
/** Injectable seams for tests; default to the real platform and filesystem. */
|
|
26
|
+
platform?: NodeJS.Platform;
|
|
27
|
+
exists?: (path: string) => boolean;
|
|
28
|
+
writeConfig?: (systemConfigPath: string) => string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The `FONTCONFIG_FILE` override that pins grayscale AA for **every** browser that reads
|
|
32
|
+
* fontconfig, or null when it does not apply.
|
|
33
|
+
*
|
|
34
|
+
* Why the environment and not `--disable-lcd-text`: browser args reach Chromium only, and
|
|
35
|
+
* Firefox rejects unknown flags. The env var reaches whatever binary Playwright launches,
|
|
36
|
+
* including a system Chromium passed via `executablePath`. Verified byte-identical to
|
|
37
|
+
* `--disable-lcd-text` in `mcr.microsoft.com/playwright:v1.59.0-noble`.
|
|
38
|
+
*
|
|
39
|
+
* Null on non-Linux (macOS has had no subpixel AA since Mojave, Windows uses DirectWrite) and
|
|
40
|
+
* when no system config exists to include — see {@link grayscaleRootFontconfigXml}.
|
|
41
|
+
*/
|
|
42
|
+
export declare function grayscaleFontconfigEnv(baseEnv?: Record<string, string | undefined>, options?: GrayscaleFontconfigEnvOptions): Record<string, string> | null;
|
|
43
|
+
//# sourceMappingURL=fontconfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fontconfig.d.ts","sourceRoot":"","sources":["../src/fontconfig.ts"],"names":[],"mappings":"AAmBA;;;GAGG;AACH,eAAO,MAAM,wBAAwB,+TAIpC,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAO3E;AASD,0EAA0E;AAC1E,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,MAAM,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,GAC7C,MAAM,GAAG,IAAI,CAcf;AAED,0IAA0I;AAC1I,wBAAgB,oBAAoB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAOrE;AAED,MAAM,WAAW,6BAA6B;IAC5C,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IAC1B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAClC,WAAW,CAAC,EAAE,CAAC,gBAAgB,EAAE,MAAM,KAAK,MAAM,CAAA;CACnD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACzD,OAAO,GAAE,6BAAkC,GAC1C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAK/B"}
|
package/dist/index.css
CHANGED
|
@@ -847,6 +847,10 @@
|
|
|
847
847
|
.outline-accent {
|
|
848
848
|
outline-color: var(--color-accent);
|
|
849
849
|
}
|
|
850
|
+
.blur {
|
|
851
|
+
--tw-blur: blur(8px);
|
|
852
|
+
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
|
853
|
+
}
|
|
850
854
|
.grayscale {
|
|
851
855
|
--tw-grayscale: grayscale(100%);
|
|
852
856
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|