@crvy/rprtr 0.3.1 → 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/dist/server.cjs CHANGED
@@ -35,7 +35,7 @@ __export(server_exports, {
35
35
  module.exports = __toCommonJS(server_exports);
36
36
 
37
37
  // src/server/app.ts
38
- var import_path9 = require("path");
38
+ var import_path11 = require("path");
39
39
  var import_url = require("url");
40
40
 
41
41
  // src/offline-reports.ts
@@ -659,6 +659,47 @@ function mergeOfflineReportsIntoTests(existingTests, offlineReports, options = {
659
659
  };
660
660
  }
661
661
 
662
+ // src/server/docker-env.ts
663
+ var ENV_DENYLIST = /* @__PURE__ */ new Set([
664
+ "CI",
665
+ "PLAYWRIGHT_BROWSERS_PATH",
666
+ "CRVY_RPRTR_SERVER_URL",
667
+ "CRVY_RPRTR_PORTABLE_ARTIFACTS",
668
+ "TZ",
669
+ // The container must use its own home: a forwarded host HOME makes NSS (browser cert
670
+ // store init) and npm resolve their state under it, and a Windows-style value like
671
+ // `C:\Users\dev` is a *relative* path on Linux, so those tools would create a literal
672
+ // `C:\Users\dev/.local/share/pki/nssdb` tree under the container CWD — i.e. inside the
673
+ // bind-mounted project.
674
+ "HOME",
675
+ "NPM_CONFIG_CACHE",
676
+ "LANG",
677
+ "LC_ALL",
678
+ "PLAYWRIGHT_HTML_OPEN",
679
+ "PATH",
680
+ // Host paths that do not exist in the container: fontconfig resolves them to nothing and
681
+ // the browser ends up with no font directories at all (blank text). The container gets its
682
+ // AA from the mounted drop-in instead.
683
+ "FONTCONFIG_FILE",
684
+ "FONTCONFIG_PATH"
685
+ ]);
686
+ var WINDOWS_ENV_NOISE = new Set(
687
+ "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(
688
+ " "
689
+ )
690
+ );
691
+ var WINDOWS_PATH_VALUE = /^(?:[A-Za-z]:[\\/]|\\\\)/;
692
+ function collectForwardedEnvNames(env, platform) {
693
+ const names = [];
694
+ for (const [key, value] of Object.entries(env)) {
695
+ const upper = key.toUpperCase();
696
+ if (ENV_DENYLIST.has(upper) || WINDOWS_ENV_NOISE.has(upper) || value === void 0) continue;
697
+ if (platform === "win32" && WINDOWS_PATH_VALUE.test(value)) continue;
698
+ names.push(key);
699
+ }
700
+ return names;
701
+ }
702
+
662
703
  // src/server/docker-support.ts
663
704
  var import_child_process = require("child_process");
664
705
  var import_node_fs = require("node:fs");
@@ -822,6 +863,81 @@ function buildTestListEntries(tests, rootDir, cwd, pathStyle = "host") {
822
863
  });
823
864
  }
824
865
 
866
+ // src/server/fontconfig.ts
867
+ var import_fs2 = require("fs");
868
+ var import_os2 = require("os");
869
+ var import_path4 = require("path");
870
+
871
+ // src/fontconfig.ts
872
+ var import_fs = require("fs");
873
+ var import_os = require("os");
874
+ var import_path3 = require("path");
875
+ var GRAYSCALE_MATCH = ` <match target="font">
876
+ <edit name="rgba" mode="assign"><const>none</const></edit>
877
+ </match>`;
878
+ var HEADER = `<?xml version="1.0"?>
879
+ <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
880
+ <!-- Written by crvy-rprtr: pins grayscale text antialiasing for screenshot determinism. -->`;
881
+ var GRAYSCALE_FONTCONFIG_XML = `${HEADER}
882
+ <fontconfig>
883
+ ${GRAYSCALE_MATCH}
884
+ </fontconfig>
885
+ `;
886
+ function grayscaleRootFontconfigXml(systemConfigPath) {
887
+ return `${HEADER}
888
+ <fontconfig>
889
+ <include ignore_missing="no">${escapeXml(systemConfigPath)}</include>
890
+ ${GRAYSCALE_MATCH}
891
+ </fontconfig>
892
+ `;
893
+ }
894
+ function escapeXml(value) {
895
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
896
+ }
897
+ var SYSTEM_FONTCONFIG_PATHS = ["/etc/fonts/fonts.conf", "/usr/local/etc/fonts/fonts.conf"];
898
+ function rootFontconfigPath() {
899
+ return (0, import_path3.join)((0, import_os.tmpdir)(), "crvy-rprtr", "fonts.conf");
900
+ }
901
+ function resolveSystemFontconfig(env = process.env, exists = import_fs.existsSync) {
902
+ const ours = rootFontconfigPath();
903
+ const candidates = [
904
+ env.FONTCONFIG_FILE,
905
+ env.FONTCONFIG_PATH === void 0 ? void 0 : (0, import_path3.join)(env.FONTCONFIG_PATH, "fonts.conf"),
906
+ ...SYSTEM_FONTCONFIG_PATHS
907
+ ];
908
+ for (const candidate of candidates) {
909
+ if (candidate === void 0 || candidate === ours) continue;
910
+ if (exists(candidate)) return candidate;
911
+ }
912
+ return null;
913
+ }
914
+ function ensureRootFontconfig(systemConfigPath) {
915
+ const path = rootFontconfigPath();
916
+ (0, import_fs.mkdirSync)((0, import_path3.join)((0, import_os.tmpdir)(), "crvy-rprtr"), { recursive: true });
917
+ const staging = `${path}.${process.pid}.tmp`;
918
+ (0, import_fs.writeFileSync)(staging, grayscaleRootFontconfigXml(systemConfigPath), "utf8");
919
+ (0, import_fs.renameSync)(staging, path);
920
+ return path;
921
+ }
922
+ function grayscaleFontconfigEnv(baseEnv = process.env, options = {}) {
923
+ if ((options.platform ?? process.platform) !== "linux") return null;
924
+ const systemConfig = resolveSystemFontconfig(baseEnv, options.exists);
925
+ if (systemConfig === null) return null;
926
+ return { FONTCONFIG_FILE: (options.writeConfig ?? ensureRootFontconfig)(systemConfig) };
927
+ }
928
+
929
+ // src/server/fontconfig.ts
930
+ var CONTAINER_FONTCONFIG_PATH = "/etc/fonts/conf.d/99-crvy-rprtr-grayscale.conf";
931
+ function hostFontconfigPath() {
932
+ return (0, import_path4.join)((0, import_os2.tmpdir)(), "crvy-rprtr", "99-crvy-rprtr-grayscale.conf");
933
+ }
934
+ function ensureGrayscaleFontconfig() {
935
+ const path = hostFontconfigPath();
936
+ (0, import_fs2.mkdirSync)((0, import_path4.join)((0, import_os2.tmpdir)(), "crvy-rprtr"), { recursive: true });
937
+ (0, import_fs2.writeFileSync)(path, GRAYSCALE_FONTCONFIG_XML, "utf8");
938
+ return path;
939
+ }
940
+
825
941
  // src/server/run-controller.ts
826
942
  var import_child_process2 = require("child_process");
827
943
  var import_node_fs2 = require("node:fs");
@@ -838,7 +954,7 @@ function resolvePlaywrightLaunch(cwd, playwrightArgs) {
838
954
  if (resolved !== null) return { cmd: resolved.command, args: resolved.args };
839
955
  return { cmd: "npx", args: ["playwright", ...playwrightArgs] };
840
956
  }
841
- function buildSpawnEnv(port, baseEnv = process.env) {
957
+ function buildSpawnEnv(port, baseEnv = process.env, options = {}) {
842
958
  const env = {};
843
959
  for (const [key, value] of Object.entries(baseEnv)) {
844
960
  if (key === "CI") continue;
@@ -846,6 +962,10 @@ function buildSpawnEnv(port, baseEnv = process.env) {
846
962
  }
847
963
  env.CRVY_RPRTR_SERVER_URL = `ws://localhost:${port}`;
848
964
  env.PLAYWRIGHT_HTML_OPEN = "never";
965
+ const { fontRendering = "grayscale", ...fontconfigOptions } = options;
966
+ if (fontRendering !== "inherit") {
967
+ Object.assign(env, grayscaleFontconfigEnv(baseEnv, fontconfigOptions) ?? {});
968
+ }
849
969
  return env;
850
970
  }
851
971
  function createLocalLauncher(options) {
@@ -854,7 +974,8 @@ function createLocalLauncher(options) {
854
974
  launch({ ctx, playwrightArgs }) {
855
975
  const resolve6 = options.resolveLaunch ?? resolvePlaywrightLaunch;
856
976
  const { cmd, args } = resolve6(ctx.cwd, playwrightArgs);
857
- return { cmd, args, env: buildSpawnEnv(options.port, options.env) };
977
+ const { port, resolveLaunch: _resolveLaunch, env: baseEnv, ...spawnEnvOptions } = options;
978
+ return { cmd, args, env: buildSpawnEnv(port, baseEnv, spawnEnvOptions) };
858
979
  }
859
980
  };
860
981
  }
@@ -1061,22 +1182,6 @@ function createRealTimers() {
1061
1182
  // src/server/docker-launcher.ts
1062
1183
  var DOCKER_WORK_DIR = "/work";
1063
1184
  var DOCKER_HOST_GATEWAY = "host.docker.internal";
1064
- var ENV_DENYLIST = /* @__PURE__ */ new Set([
1065
- "CI",
1066
- "PLAYWRIGHT_BROWSERS_PATH",
1067
- "CRVY_RPRTR_SERVER_URL",
1068
- "CRVY_RPRTR_PORTABLE_ARTIFACTS",
1069
- "TZ",
1070
- "LANG",
1071
- "LC_ALL",
1072
- "PLAYWRIGHT_HTML_OPEN",
1073
- "PATH"
1074
- ]);
1075
- var WINDOWS_ENV_NOISE = new Set(
1076
- "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(
1077
- " "
1078
- )
1079
- );
1080
1185
  var DockerUnavailableError = class extends Error {
1081
1186
  constructor() {
1082
1187
  super("Docker daemon is not available");
@@ -1142,9 +1247,10 @@ function buildDockerRunArgs(ctx, playwrightArgs, deps) {
1142
1247
  args.push("-e", `CRVY_RPRTR_SERVER_URL=ws://${DOCKER_HOST_GATEWAY}:${deps.port}`);
1143
1248
  args.push("-e", "CRVY_RPRTR_PORTABLE_ARTIFACTS=1", "-e", "TZ=UTC", "-e", "LANG=C.UTF-8", "-e", "LC_ALL=C.UTF-8");
1144
1249
  args.push("-e", "PLAYWRIGHT_HTML_OPEN=never");
1145
- for (const [key, value] of Object.entries(deps.env)) {
1146
- const upper = key.toUpperCase();
1147
- if (ENV_DENYLIST.has(upper) || WINDOWS_ENV_NOISE.has(upper) || value === void 0) continue;
1250
+ if (deps.docker?.fontRendering !== "inherit") {
1251
+ args.push("-v", `${ensureGrayscaleFontconfig()}:${CONTAINER_FONTCONFIG_PATH}:ro`);
1252
+ }
1253
+ for (const key of collectForwardedEnvNames(deps.env, deps.platform)) {
1148
1254
  args.push("-e", key);
1149
1255
  }
1150
1256
  if (deps.docker?.extraArgs !== void 0) args.push(...deps.docker.extraArgs);
@@ -1207,7 +1313,8 @@ function buildLauncher(state, deps) {
1207
1313
  env: deps.baseEnv,
1208
1314
  image,
1209
1315
  command: state.command,
1210
- warn: deps.warn
1316
+ warn: deps.warn,
1317
+ platform: deps.platform
1211
1318
  });
1212
1319
  return { cmd: "docker", args, env: stripCi(deps.baseEnv) };
1213
1320
  },
@@ -1232,19 +1339,20 @@ function createDockerLauncher(options) {
1232
1339
  }
1233
1340
 
1234
1341
  // src/server/handlers.ts
1235
- var import_fs2 = require("fs");
1236
- var import_path6 = require("path");
1342
+ var import_fs4 = require("fs");
1343
+ var import_path8 = require("path");
1237
1344
 
1238
1345
  // src/server/artifact-routes.ts
1239
- var import_fs = require("fs");
1346
+ var import_fs3 = require("fs");
1240
1347
  var import_promises3 = require("fs/promises");
1241
- var import_path5 = require("path");
1348
+ var import_path7 = require("path");
1242
1349
 
1243
1350
  // src/snapshot-path-resolver.ts
1244
1351
  var import_crypto = require("crypto");
1245
- var import_path3 = require("path");
1352
+ var import_path5 = require("path");
1246
1353
  var DEFAULT_SCREENSHOT_TEMPLATE = "{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}";
1247
1354
  var WINDOWS_FILESYSTEM_FRIENDLY_LENGTH = 60;
1355
+ var PLAYWRIGHT_SNAPSHOT_NAME_LIMIT = 100;
1248
1356
  function isUnsafeFilePathCharacter(character) {
1249
1357
  const codePoint = character.codePointAt(0);
1250
1358
  return codePoint !== void 0 && (codePoint <= 44 || codePoint >= 46 && codePoint <= 47 || codePoint >= 58 && codePoint <= 64 || codePoint >= 91 && codePoint <= 96 || codePoint >= 123 && codePoint <= 127);
@@ -1277,16 +1385,16 @@ function trimLongString(value, length = WINDOWS_FILESYSTEM_FRIENDLY_LENGTH) {
1277
1385
  const end = length - middle.length - start;
1278
1386
  return value.slice(0, start) + middle + value.slice(-end);
1279
1387
  }
1280
- function sanitizeFilePathBeforeExtension(filePath, extension = (0, import_path3.extname)(filePath)) {
1388
+ function sanitizeFilePathBeforeExtension(filePath, extension = (0, import_path5.extname)(filePath)) {
1281
1389
  const base = filePath.slice(0, filePath.length - extension.length);
1282
1390
  return sanitizeForFilePath(base) + extension;
1283
1391
  }
1284
1392
  function addSuffixToFilePath(filePath, suffix) {
1285
- const extension = (0, import_path3.extname)(filePath);
1393
+ const extension = (0, import_path5.extname)(filePath);
1286
1394
  return filePath.slice(0, filePath.length - extension.length) + suffix + extension;
1287
1395
  }
1288
1396
  function normalizedSnapshotDir(config) {
1289
- return (0, import_path3.resolve)(config.configDir, config.snapshotDir);
1397
+ return (0, import_path5.resolve)(config.configDir, config.snapshotDir);
1290
1398
  }
1291
1399
  function templateValue(template, token, value) {
1292
1400
  return template.replace(
@@ -1296,8 +1404,8 @@ function templateValue(template, token, value) {
1296
1404
  }
1297
1405
  function applyTemplate(input, nameArgument, extension) {
1298
1406
  const template = input.config.toHaveScreenshotPathTemplate ?? input.config.snapshotPathTemplate ?? DEFAULT_SCREENSHOT_TEMPLATE;
1299
- const relativeTestFilePath = (0, import_path3.relative)(input.config.testDir, input.testFile);
1300
- const parsed = (0, import_path3.parse)(relativeTestFilePath);
1407
+ const relativeTestFilePath = (0, import_path5.relative)(input.config.testDir, input.testFile);
1408
+ const parsed = (0, import_path5.parse)(relativeTestFilePath);
1301
1409
  const tokens = [
1302
1410
  ["testDir", input.config.testDir],
1303
1411
  ["snapshotDir", normalizedSnapshotDir(input.config)],
@@ -1315,16 +1423,16 @@ function applyTemplate(input, nameArgument, extension) {
1315
1423
  (currentTemplate, [token, value]) => templateValue(currentTemplate, token, value),
1316
1424
  template
1317
1425
  );
1318
- return (0, import_path3.resolve)(input.config.configDir, snapshotPath);
1426
+ return (0, import_path5.resolve)(input.config.configDir, snapshotPath);
1319
1427
  }
1320
- function removeExtension(filePath, extension = (0, import_path3.extname)(filePath)) {
1428
+ function removeExtension(filePath, extension = (0, import_path5.extname)(filePath)) {
1321
1429
  return filePath.slice(0, filePath.length - extension.length);
1322
1430
  }
1323
1431
  function snapshotNameParts(declaredName) {
1324
- const extension = (0, import_path3.extname)(declaredName) || ".png";
1432
+ const extension = (0, import_path5.extname)(declaredName) || ".png";
1325
1433
  return {
1326
1434
  extension,
1327
- filePath: (0, import_path3.extname)(declaredName) === "" ? `${declaredName}${extension}` : declaredName
1435
+ filePath: (0, import_path5.extname)(declaredName) === "" ? `${declaredName}${extension}` : declaredName
1328
1436
  };
1329
1437
  }
1330
1438
  function filePathForOccurrence(filePath, occurrenceIndex) {
@@ -1348,7 +1456,7 @@ function resolveStringCallTarget(input, declaration) {
1348
1456
  function resolveArrayCallTarget(input, declaration) {
1349
1457
  const { extension, filePath } = snapshotNameParts(declaration.declaredName);
1350
1458
  const occurrenceFilePath = filePathForOccurrence(filePath, declaration.occurrenceIndex);
1351
- const nameArgument = (0, import_path3.join)((0, import_path3.dirname)(occurrenceFilePath), (0, import_path3.basename)(occurrenceFilePath, extension));
1459
+ const nameArgument = (0, import_path5.join)((0, import_path5.dirname)(occurrenceFilePath), (0, import_path5.basename)(occurrenceFilePath, extension));
1352
1460
  return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
1353
1461
  }
1354
1462
  function resolveNamedTarget(input, declaration) {
@@ -1374,7 +1482,11 @@ function reporterTitlesWithoutProjectAndFile(reporterTitlePath2) {
1374
1482
  return reporterTitlePath2.slice(3).filter((part) => part !== "");
1375
1483
  }
1376
1484
  function anonymousNameFromTitles(titles, occurrenceIndex) {
1377
- return sanitizeFilePathBeforeExtension(trimLongString(`${titles.join(" ")} ${occurrenceIndex}.png`), ".png");
1485
+ const fullTitleWithoutSpec = `${titles.join(" ")} ${occurrenceIndex}`;
1486
+ return sanitizeFilePathBeforeExtension(
1487
+ `${trimLongString(fullTitleWithoutSpec, PLAYWRIGHT_SNAPSHOT_NAME_LIMIT)}.png`,
1488
+ ".png"
1489
+ );
1378
1490
  }
1379
1491
  function anonymousName(reporterTitlePath2, occurrenceIndex) {
1380
1492
  return anonymousNameFromTitles(reporterTitlesWithoutProjectAndFile(reporterTitlePath2), occurrenceIndex);
@@ -1386,7 +1498,7 @@ function resolveTarget(input, declaration) {
1386
1498
  case "unnamed": {
1387
1499
  const anonymousFileName = anonymousName(input.reporterTitlePath, declaration.occurrenceIndex);
1388
1500
  const extension = ".png";
1389
- const nameArgument = (0, import_path3.join)((0, import_path3.dirname)(anonymousFileName), (0, import_path3.basename)(anonymousFileName, extension));
1501
+ const nameArgument = (0, import_path5.join)((0, import_path5.dirname)(anonymousFileName), (0, import_path5.basename)(anonymousFileName, extension));
1390
1502
  return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
1391
1503
  }
1392
1504
  }
@@ -1399,7 +1511,7 @@ function resolveBaselineTargets(input) {
1399
1511
  }
1400
1512
 
1401
1513
  // src/server/utils.ts
1402
- var import_path4 = require("path");
1514
+ var import_path6 = require("path");
1403
1515
  var LIVE_UPDATES_WEBSOCKET_PATH = "/";
1404
1516
  function broadcastToBrowsers(wsClients, msg) {
1405
1517
  const payload = JSON.stringify(msg);
@@ -1411,10 +1523,10 @@ function isWebSocketUpgradeRequest(req) {
1411
1523
  return new URL(req.url).pathname === LIVE_UPDATES_WEBSOCKET_PATH && req.headers.get("upgrade")?.toLowerCase() === "websocket";
1412
1524
  }
1413
1525
  function isPathWithinRoots(target, roots) {
1414
- const resolvedTarget = (0, import_path4.resolve)(target);
1526
+ const resolvedTarget = (0, import_path6.resolve)(target);
1415
1527
  return roots.some((root) => {
1416
- const rel = (0, import_path4.relative)((0, import_path4.resolve)(root), resolvedTarget);
1417
- return rel === "" || !rel.startsWith(`..${import_path4.sep}`) && rel !== ".." && !(0, import_path4.isAbsolute)(rel);
1528
+ const rel = (0, import_path6.relative)((0, import_path6.resolve)(root), resolvedTarget);
1529
+ return rel === "" || !rel.startsWith(`..${import_path6.sep}`) && rel !== ".." && !(0, import_path6.isAbsolute)(rel);
1418
1530
  });
1419
1531
  }
1420
1532
 
@@ -1432,7 +1544,7 @@ async function handleFile(ctx, req) {
1432
1544
  let decodedPath;
1433
1545
  try {
1434
1546
  rawDecoded = decodeURIComponent(new URL(req.url).pathname.slice("/file/".length));
1435
- decodedPath = (0, import_path5.resolve)(rawDecoded);
1547
+ decodedPath = (0, import_path7.resolve)(rawDecoded);
1436
1548
  } catch {
1437
1549
  return notFound();
1438
1550
  }
@@ -1445,7 +1557,7 @@ async function handleFile(ctx, req) {
1445
1557
  }
1446
1558
  return notFound();
1447
1559
  }
1448
- const realRoots = (await Promise.all((ctx.artifactRoots ?? []).map((root) => realpathOrNull((0, import_path5.resolve)(root))))).filter(
1560
+ const realRoots = (await Promise.all((ctx.artifactRoots ?? []).map((root) => realpathOrNull((0, import_path7.resolve)(root))))).filter(
1449
1561
  (root) => root !== null
1450
1562
  );
1451
1563
  if (!isPathWithinRoots(realTarget, realRoots)) {
@@ -1478,14 +1590,14 @@ function resolveBaselineSnapshotPath(routing, test, retry, imageName) {
1478
1590
  declarations: [declaration],
1479
1591
  config: {
1480
1592
  configDir: routing.configDir,
1481
- testDir: routing.playwrightTestDir ?? (0, import_path5.dirname)(testFile),
1482
- snapshotDir: routing.playwrightSnapshotDir ?? (0, import_path5.dirname)(testFile),
1593
+ testDir: routing.playwrightTestDir ?? (0, import_path7.dirname)(testFile),
1594
+ snapshotDir: routing.playwrightSnapshotDir ?? (0, import_path7.dirname)(testFile),
1483
1595
  projectName: test.projectName ?? test.browser,
1484
1596
  snapshotSuffix: isContainerPath ? "linux" : process.platform,
1485
1597
  snapshotPathTemplate: routing.playwrightSnapshotPathTemplate,
1486
1598
  toHaveScreenshotPathTemplate: routing.playwrightToHaveScreenshotPathTemplate
1487
1599
  },
1488
- snapshotPathExists: import_fs.existsSync
1600
+ snapshotPathExists: import_fs3.existsSync
1489
1601
  });
1490
1602
  return targets.length === 1 ? targets[0]?.snapshotPath ?? null : null;
1491
1603
  }
@@ -1551,7 +1663,7 @@ function enrichDeclaredBaselines(ctx, test) {
1551
1663
  continue;
1552
1664
  }
1553
1665
  const snapshotPath = resolveBaselineSnapshotPath(ctx.approvalRouting, test, retry, visualName);
1554
- if (snapshotPath === null || !(0, import_fs2.existsSync)(snapshotPath)) {
1666
+ if (snapshotPath === null || !(0, import_fs4.existsSync)(snapshotPath)) {
1555
1667
  continue;
1556
1668
  }
1557
1669
  image.expect = `/baseline/${encodeURIComponent(test.id)}/${retry}/${encodeURIComponent(visualName)}`;
@@ -1656,11 +1768,11 @@ function handleRegister(ctx, rawData) {
1656
1768
  });
1657
1769
  }
1658
1770
  function buildRunContext(configFile, data) {
1659
- const configDir = (0, import_path6.dirname)(configFile);
1771
+ const configDir = (0, import_path8.dirname)(configFile);
1660
1772
  return {
1661
1773
  configFile,
1662
1774
  cwd: configDir,
1663
- rootDir: data.playwrightRootDir ?? (data.playwrightTestDir === void 0 ? configDir : (0, import_path6.resolve)(configDir, data.playwrightTestDir))
1775
+ rootDir: data.playwrightRootDir ?? (data.playwrightTestDir === void 0 ? configDir : (0, import_path8.resolve)(configDir, data.playwrightTestDir))
1664
1776
  };
1665
1777
  }
1666
1778
 
@@ -1693,7 +1805,11 @@ async function resolveRunBackend(options) {
1693
1805
  console.warn(`[crvy-rprtr] ${message}`);
1694
1806
  }
1695
1807
  });
1696
- const launcher = resolvedRunMode === "docker" ? createDockerLauncher({ port: options.port, docker: options.docker, exec: dockerExec }) : createLocalLauncher({ port: options.port });
1808
+ const launcher = resolvedRunMode === "docker" ? createDockerLauncher({
1809
+ port: options.port,
1810
+ docker: { fontRendering: options.fontRendering, ...options.docker },
1811
+ exec: dockerExec
1812
+ }) : createLocalLauncher({ port: options.port, fontRendering: options.fontRendering });
1697
1813
  return {
1698
1814
  launcher,
1699
1815
  routesContextOptions: {
@@ -1704,7 +1820,7 @@ async function resolveRunBackend(options) {
1704
1820
  }
1705
1821
 
1706
1822
  // src/server/playwright-config.ts
1707
- var import_path7 = require("path");
1823
+ var import_path9 = require("path");
1708
1824
  var CONFIG_FILES = [
1709
1825
  "playwright.config.ts",
1710
1826
  "playwright.config.mts",
@@ -1716,14 +1832,14 @@ var CONFIG_FILES = [
1716
1832
  async function resolvePlaywrightConfig(cwd) {
1717
1833
  const matches = await Promise.all(
1718
1834
  CONFIG_FILES.map(async (file) => {
1719
- const candidate = (0, import_path7.join)(cwd, file);
1835
+ const candidate = (0, import_path9.join)(cwd, file);
1720
1836
  return await fileExists(candidate) ? candidate : null;
1721
1837
  })
1722
1838
  );
1723
1839
  return matches.find((path) => path !== null) ?? null;
1724
1840
  }
1725
1841
  function resolveSeedConfigFile(option, cwd) {
1726
- return option === void 0 ? resolvePlaywrightConfig(cwd) : Promise.resolve((0, import_path7.resolve)(cwd, option));
1842
+ return option === void 0 ? resolvePlaywrightConfig(cwd) : Promise.resolve((0, import_path9.resolve)(cwd, option));
1727
1843
  }
1728
1844
 
1729
1845
  // src/server/report-persistence.ts
@@ -1806,7 +1922,7 @@ function createRoutesContext(reportData, staticDir, saveReport, options) {
1806
1922
  }
1807
1923
 
1808
1924
  // src/server/routes.ts
1809
- var import_path8 = require("path");
1925
+ var import_path10 = require("path");
1810
1926
 
1811
1927
  // src/server/run-routes.ts
1812
1928
  function handleRunRoutes(pathname, method, runController, req) {
@@ -1845,7 +1961,7 @@ function handleApiStop(runController) {
1845
1961
 
1846
1962
  // src/server/routes.ts
1847
1963
  async function handleRoot(ctx) {
1848
- const html = await respondWithFile((0, import_path8.join)(ctx.staticDir, "index.html"), "text/html");
1964
+ const html = await respondWithFile((0, import_path10.join)(ctx.staticDir, "index.html"), "text/html");
1849
1965
  return html ?? new Response("Not Found", { status: 404 });
1850
1966
  }
1851
1967
  async function handleAppCss() {
@@ -1869,7 +1985,7 @@ function handleApiReport(ctx) {
1869
1985
  var APPROVAL_TARGET_ERROR = "Could not resolve approval target";
1870
1986
  function actualPathFromUrl(ctx, actualUrl) {
1871
1987
  if (actualUrl.startsWith("/screenshots/")) {
1872
- return (0, import_path8.join)(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length));
1988
+ return (0, import_path10.join)(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length));
1873
1989
  }
1874
1990
  if (actualUrl.startsWith("/file/")) {
1875
1991
  return decodeURIComponent(actualUrl.slice("/file/".length));
@@ -1991,7 +2107,7 @@ async function handleScreenshots(ctx, req) {
1991
2107
  }
1992
2108
  async function handleDist(ctx, req) {
1993
2109
  const path = new URL(req.url).pathname.slice("/dist/".length);
1994
- const filePath = (0, import_path8.join)(ctx.staticDir, path);
2110
+ const filePath = (0, import_path10.join)(ctx.staticDir, path);
1995
2111
  const contentType = filePath.endsWith(".css") ? "text/css" : filePath.endsWith(".js") ? "application/javascript" : filePath.endsWith(".svelte") ? "text/plain" : "application/octet-stream";
1996
2112
  const file = await respondWithFile(filePath, contentType);
1997
2113
  return file ?? new Response("Not Found", { status: 404 });
@@ -2178,19 +2294,19 @@ function createWebSocketMessageHandler(getHandlerContext) {
2178
2294
  };
2179
2295
  }
2180
2296
  async function resolveStaticDir(staticDir) {
2181
- const currentDir = (0, import_path9.dirname)((0, import_url.fileURLToPath)(import_meta2.url));
2297
+ const currentDir = (0, import_path11.dirname)((0, import_url.fileURLToPath)(import_meta2.url));
2182
2298
  const candidates = staticDir === void 0 ? [
2183
2299
  currentDir,
2184
- (0, import_path9.join)(currentDir, "dist"),
2185
- (0, import_path9.join)(currentDir, "..", "dist"),
2186
- (0, import_path9.join)(currentDir, "..", "..", "dist"),
2187
- (0, import_path9.join)(currentDir, ".."),
2188
- (0, import_path9.join)(currentDir, "..", "..")
2189
- ] : [staticDir, (0, import_path9.join)(staticDir, "dist")];
2300
+ (0, import_path11.join)(currentDir, "dist"),
2301
+ (0, import_path11.join)(currentDir, "..", "dist"),
2302
+ (0, import_path11.join)(currentDir, "..", "..", "dist"),
2303
+ (0, import_path11.join)(currentDir, ".."),
2304
+ (0, import_path11.join)(currentDir, "..", "..")
2305
+ ] : [staticDir, (0, import_path11.join)(staticDir, "dist")];
2190
2306
  const resolvedCandidates = await Promise.all(
2191
2307
  candidates.map(async (candidate) => ({
2192
2308
  candidate,
2193
- exists: await fileExists((0, import_path9.join)(candidate, "index.html"))
2309
+ exists: await fileExists((0, import_path11.join)(candidate, "index.html"))
2194
2310
  }))
2195
2311
  );
2196
2312
  const resolved = resolvedCandidates.find(({ exists }) => exists);
@@ -2201,9 +2317,9 @@ async function resolveStaticDir(staticDir) {
2201
2317
  }
2202
2318
  async function resolveReportPath(reportPath) {
2203
2319
  if (await isDirectory(reportPath)) {
2204
- return { reportFile: (0, import_path9.join)(reportPath, "report.json"), offlineReportDir: reportPath };
2320
+ return { reportFile: (0, import_path11.join)(reportPath, "report.json"), offlineReportDir: reportPath };
2205
2321
  }
2206
- return { reportFile: reportPath, offlineReportDir: (0, import_path9.dirname)(reportPath) };
2322
+ return { reportFile: reportPath, offlineReportDir: (0, import_path11.dirname)(reportPath) };
2207
2323
  }
2208
2324
  async function seedRunContext(routesContext, options) {
2209
2325
  if (routesContext.runContext !== void 0) {
@@ -2218,6 +2334,7 @@ async function setupRoutesContext(options, reportData, staticDir, saveReport, po
2218
2334
  const { launcher, routesContextOptions } = await resolveRunBackend({
2219
2335
  runMode: options.runMode,
2220
2336
  docker: options.docker,
2337
+ fontRendering: options.fontRendering,
2221
2338
  port
2222
2339
  });
2223
2340
  const routesContext = createRoutesContext(reportData, staticDir, saveReport, {
package/dist/server.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  startServer
3
- } from "./chunk-4CVHIHAJ.js";
4
- import "./chunk-4YJL655E.js";
3
+ } from "./chunk-NXW25MU3.js";
4
+ import "./chunk-7YLKL3SL.js";
5
+ import "./chunk-7JUICMVX.js";
5
6
  export {
6
7
  startServer
7
8
  };
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot-path-resolver.d.ts","sourceRoot":"","sources":["../src/snapshot-path-resolver.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAM5F,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC,QAAQ,CAAC,4BAA4B,CAAC,EAAE,MAAM,CAAA;CAC/C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7C,QAAQ,CAAC,YAAY,EAAE,SAAS,qBAAqB,EAAE,CAAA;IACvD,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAA;IACvC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAA;CAChE;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAeD,iBAAS,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAsBlD;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAqC,GAAG,MAAM,CAW1F;AAED,iBAAS,+BAA+B,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAoB,GAAG,MAAM,CAGhG;AAED,iBAAS,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrE;AAsID,wBAAgB,6BAA6B,CAC3C,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,eAAe,EAAE,MAAM,GACtB,MAAM,GAAG,IAAI,CAGf;AAqBD,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,SAAS,qBAAqB,EAAE,EAC9C,iBAAiB,EAAE,SAAS,MAAM,EAAE,GACnC,qBAAqB,EAAE,CASzB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,EAAE,CAK7F;AAED,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,+BAA+B,EAAE,mBAAmB,EAAE,CAAA"}
1
+ {"version":3,"file":"snapshot-path-resolver.d.ts","sourceRoot":"","sources":["../src/snapshot-path-resolver.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAU5F,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC,QAAQ,CAAC,4BAA4B,CAAC,EAAE,MAAM,CAAA;CAC/C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7C,QAAQ,CAAC,YAAY,EAAE,SAAS,qBAAqB,EAAE,CAAA;IACvD,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAA;IACvC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAA;CAChE;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAeD,iBAAS,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAsBlD;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAqC,GAAG,MAAM,CAW1F;AAED,iBAAS,+BAA+B,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAoB,GAAG,MAAM,CAGhG;AAED,iBAAS,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrE;AA0ID,wBAAgB,6BAA6B,CAC3C,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,eAAe,EAAE,MAAM,GACtB,MAAM,GAAG,IAAI,CAGf;AAqBD,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,SAAS,qBAAqB,EAAE,EAC9C,iBAAiB,EAAE,SAAS,MAAM,EAAE,GACnC,qBAAqB,EAAE,CASzB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,EAAE,CAK7F;AAED,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,+BAA+B,EAAE,mBAAmB,EAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crvy/rprtr",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Playwright reporter with visual regression UI for comparing and approving screenshot tests",
5
5
  "keywords": [
6
6
  "crvy",
@@ -44,6 +44,12 @@
44
44
  "require": "./dist/server.cjs",
45
45
  "default": "./dist/server.js"
46
46
  },
47
+ "./rendering": {
48
+ "types": "./dist/rendering.d.ts",
49
+ "import": "./dist/rendering.js",
50
+ "require": "./dist/rendering.cjs",
51
+ "default": "./dist/rendering.js"
52
+ },
47
53
  "./types": {
48
54
  "types": "./dist/types.d.ts"
49
55
  }
@@ -69,6 +75,7 @@
69
75
  "check": "./scripts/check.sh",
70
76
  "check:staged": "./scripts/check.sh --staged",
71
77
  "example": "cd examples/playwright && bunx playwright test",
78
+ "example:ct": "cd examples/component-testing && bunx playwright test",
72
79
  "changelog:preview": "git-cliff --dry-run",
73
80
  "changelog:generate": "git-cliff -o CHANGELOG.md",
74
81
  "prepublishOnly": "bun run build && bunx publint",
@@ -83,7 +90,7 @@
83
90
  "devDependencies": {
84
91
  "@playwright/test": "1.59.0",
85
92
  "@tailwindcss/postcss": "^4.2.2",
86
- "@types/bun": "latest",
93
+ "@types/bun": "^1.4.0",
87
94
  "@types/ws": "^8.18.1",
88
95
  "esbuild": "^0.27.4",
89
96
  "esbuild-svelte": "^0.9.4",
@@ -102,7 +109,7 @@
102
109
  "@playwright/test": ">=1.40"
103
110
  },
104
111
  "engines": {
105
- "bun": ">=1.0.0",
112
+ "bun": ">=1.4.0",
106
113
  "node": ">=22"
107
114
  }
108
115
  }