@crvy/rprtr 0.1.3 → 0.1.4
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 +23 -0
- package/README.md +8 -0
- package/dist/{chunk-F7NSIHCV.js → chunk-KLGJSEKV.js} +10 -2
- package/dist/{chunk-X2TCDD54.js → chunk-WRMHUY5A.js} +23 -7
- package/dist/cli.js +2 -2
- package/dist/index.css +3 -0
- package/dist/path-utils.d.ts +3 -0
- package/dist/path-utils.d.ts.map +1 -0
- package/dist/report-state.d.ts.map +1 -1
- package/dist/report-utils.d.ts.map +1 -1
- package/dist/reporter.cjs +46 -36
- package/dist/reporter.js +1 -1
- package/dist/server/artifact-routes.d.ts.map +1 -1
- package/dist/server.cjs +71 -49
- package/dist/server.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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.1.4] - 2026-06-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **path-utils:** Add cross-platform absolute path helpers
|
|
13
|
+
- **server:** Log diagnostic when /file URL cannot resolve on host OS
|
|
14
|
+
|
|
15
|
+
### Documentation
|
|
16
|
+
|
|
17
|
+
- **plans:** Cross-OS path handling and stale actual URL on passing rerun
|
|
18
|
+
- Document cross-OS artifact loading limitations
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **report-utils:** Route Windows-style absolute paths to /file on any host
|
|
23
|
+
- **server:** Check pre-resolve path for foreign-OS diagnostic
|
|
24
|
+
- **report-state:** Drop stale comparison actual URL on passed reruns
|
|
25
|
+
- **report-state:** Strip stale actual/diff when preserving baseline-only images
|
|
26
|
+
|
|
27
|
+
### Testing
|
|
28
|
+
|
|
29
|
+
- **path-utils:** Cover forward-slash UNC form
|
|
30
|
+
- **server-routes:** Cover baseline enrichment after failed→passed progression
|
|
8
31
|
## [0.1.3] - 2026-06-18
|
|
9
32
|
|
|
10
33
|
### Fixed
|
package/README.md
CHANGED
|
@@ -113,6 +113,14 @@ Approval routing uses the same resolver, but it reads its resolver settings from
|
|
|
113
113
|
|
|
114
114
|
When the server is running, Crvy Rprtr also refreshes the UI after report JSON or screenshot artifacts change on disk.
|
|
115
115
|
|
|
116
|
+
## Cross-OS Artifact Loading
|
|
117
|
+
|
|
118
|
+
Crvy Rprtr stores image URLs exactly as the reporter that produced them saw them. In live mode (server running during the test run), failure artifacts are referenced by absolute path under `/file/<encoded>`; in CI/offline mode, attachments are copied into `screenshots/` and referenced by relative path under `/screenshots/`.
|
|
119
|
+
|
|
120
|
+
If you generate a report on one operating system and then open it on another (for example, downloading a Windows CI runner's `report.json` onto a macOS laptop), absolute-path `/file/...` URLs cannot resolve: the file is not on your filesystem. Crvy Rprtr logs a single diagnostic line per such request and returns 404. The `/screenshots/...` and `/baseline/...` URLs remain portable because they resolve through the server's `screenshotDir` or snapshot resolver.
|
|
121
|
+
|
|
122
|
+
For fully portable artifact loading across operating systems, run the reporter in CI mode (`ci: true`) and ship the `screenshots/` directory alongside the report JSON.
|
|
123
|
+
|
|
116
124
|
## Programmatic API
|
|
117
125
|
|
|
118
126
|
```ts
|
|
@@ -11,9 +11,10 @@ import {
|
|
|
11
11
|
applyTestEndEvent,
|
|
12
12
|
createMutableReportState,
|
|
13
13
|
finalizeRunEvent,
|
|
14
|
+
isForeignAbsolutePath,
|
|
14
15
|
resolveBaselineTargets,
|
|
15
16
|
safeParse
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-WRMHUY5A.js";
|
|
17
18
|
|
|
18
19
|
// src/server/app.ts
|
|
19
20
|
import { dirname as dirname3, join as join3 } from "path";
|
|
@@ -210,14 +211,21 @@ async function realpathOrNull(path) {
|
|
|
210
211
|
}
|
|
211
212
|
async function handleFile(ctx, req) {
|
|
212
213
|
const notFound = () => new Response("Not Found", { status: 404 });
|
|
214
|
+
let rawDecoded;
|
|
213
215
|
let decodedPath;
|
|
214
216
|
try {
|
|
215
|
-
|
|
217
|
+
rawDecoded = decodeURIComponent(new URL(req.url).pathname.slice("/file/".length));
|
|
218
|
+
decodedPath = resolve2(rawDecoded);
|
|
216
219
|
} catch {
|
|
217
220
|
return notFound();
|
|
218
221
|
}
|
|
219
222
|
const realTarget = await realpathOrNull(decodedPath);
|
|
220
223
|
if (realTarget === null) {
|
|
224
|
+
if (isForeignAbsolutePath(rawDecoded, process.platform)) {
|
|
225
|
+
console.warn(
|
|
226
|
+
`[Crvy Rprtr] /file request resolved to a foreign-OS absolute path that cannot be served on ${process.platform}: ${rawDecoded}`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
221
229
|
return notFound();
|
|
222
230
|
}
|
|
223
231
|
const realRoots = (await Promise.all((ctx.artifactRoots ?? []).map((root) => realpathOrNull(resolve2(root))))).filter(
|
|
@@ -341,8 +341,26 @@ function safeParse(schema, data) {
|
|
|
341
341
|
return null;
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
// src/path-utils.ts
|
|
345
|
+
var posixAbsolutePattern = /^\//u;
|
|
346
|
+
var windowsDrivePattern = /^[A-Za-z]:[\\/]/u;
|
|
347
|
+
var windowsUncPattern = /^[\\/][\\/]/u;
|
|
348
|
+
function isPosixAbsolutePath(p) {
|
|
349
|
+
return posixAbsolutePattern.test(p);
|
|
350
|
+
}
|
|
351
|
+
function isWindowsAbsolutePath(p) {
|
|
352
|
+
return windowsDrivePattern.test(p) || windowsUncPattern.test(p);
|
|
353
|
+
}
|
|
354
|
+
function isAnyAbsolutePath(p) {
|
|
355
|
+
return isPosixAbsolutePath(p) || isWindowsAbsolutePath(p);
|
|
356
|
+
}
|
|
357
|
+
function isForeignAbsolutePath(p, hostPlatform) {
|
|
358
|
+
if (!isAnyAbsolutePath(p)) return false;
|
|
359
|
+
if (hostPlatform === "win32") return !isWindowsAbsolutePath(p);
|
|
360
|
+
return !isPosixAbsolutePath(p);
|
|
361
|
+
}
|
|
362
|
+
|
|
344
363
|
// src/report-utils.ts
|
|
345
|
-
import { isAbsolute } from "path";
|
|
346
364
|
function normalizeScreenshotsBaseUrl(baseUrl) {
|
|
347
365
|
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
348
366
|
}
|
|
@@ -387,7 +405,7 @@ function attachmentsToImages(attachments, screenshotsBaseUrl = "/screenshots/")
|
|
|
387
405
|
const role = match[2];
|
|
388
406
|
if (baseName === null || baseName === void 0 || role === null || role === void 0) continue;
|
|
389
407
|
images[baseName] ??= {};
|
|
390
|
-
const url =
|
|
408
|
+
const url = isAnyAbsolutePath(attachment.path) ? `/file/${encodeURIComponent(attachment.path)}` : `${baseUrl}${attachment.path}`;
|
|
391
409
|
const img = images[baseName];
|
|
392
410
|
if (img !== null && img !== void 0) {
|
|
393
411
|
if (role === "actual") img.actual = url;
|
|
@@ -422,9 +440,6 @@ function isCurrentArtifact(image) {
|
|
|
422
440
|
}
|
|
423
441
|
function isReusablePassingImage(image) {
|
|
424
442
|
const source = image.source ?? classifyImage(image);
|
|
425
|
-
if (source === "comparison") {
|
|
426
|
-
return image.actual !== void 0 && image.diff === void 0;
|
|
427
|
-
}
|
|
428
443
|
return source === "baseline-only";
|
|
429
444
|
}
|
|
430
445
|
function hasReusablePassingImages(images) {
|
|
@@ -445,8 +460,8 @@ function preservePreviousPassingImages(test, status, images) {
|
|
|
445
460
|
return {
|
|
446
461
|
...currentImages,
|
|
447
462
|
[name]: {
|
|
448
|
-
|
|
449
|
-
source:
|
|
463
|
+
expect: previousImage.expect,
|
|
464
|
+
source: "baseline-only"
|
|
450
465
|
}
|
|
451
466
|
};
|
|
452
467
|
}, images);
|
|
@@ -534,6 +549,7 @@ function finalizeRunEvent(state) {
|
|
|
534
549
|
}
|
|
535
550
|
|
|
536
551
|
export {
|
|
552
|
+
isForeignAbsolutePath,
|
|
537
553
|
createMutableReportState,
|
|
538
554
|
applyTestBeginEvent,
|
|
539
555
|
applyTestEndEvent,
|
package/dist/cli.js
CHANGED
package/dist/index.css
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"AAYA,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,QAAQ,GAAG,OAAO,CAIvF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report-state.d.ts","sourceRoot":"","sources":["../src/report-state.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC9D,OAAO,KAAK,EAAU,QAAQ,EAAc,MAAM,YAAY,CAAA;AAE9D,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC/B,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,YAAY,EAAE,OAAO,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,iBAAiB,CAAA;IAC7B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,QAAQ,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,KAAK,sBAAsB,GAAG,WAAW,GAAG;IAC1C,kBAAkB,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAA;CACtD,CAAA;
|
|
1
|
+
{"version":3,"file":"report-state.d.ts","sourceRoot":"","sources":["../src/report-state.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC9D,OAAO,KAAK,EAAU,QAAQ,EAAc,MAAM,YAAY,CAAA;AAE9D,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC/B,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,YAAY,EAAE,OAAO,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,iBAAiB,CAAA;IAC7B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,QAAQ,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,KAAK,sBAAsB,GAAG,WAAW,GAAG;IAC1C,kBAAkB,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAA;CACtD,CAAA;AAqDD,wBAAgB,wBAAwB,CAAC,aAAa,SAAkB,GAAG,kBAAkB,CAW5F;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa,GAAG,QAAQ,CAiB5F;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,kBAAkB,EACzB,IAAI,EAAE,sBAAsB,EAC5B,OAAO,GAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5C,kBAAkB,GAAG,IAAI,CAyC3B;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAa/G"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report-utils.d.ts","sourceRoot":"","sources":["../src/report-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"report-utils.d.ts","sourceRoot":"","sources":["../src/report-utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAMhE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAUzD;AASD,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,kBAAkB,EAAE,SAAS,qBAAqB,EAAE,GAAG,SAAS,GAC/D,SAAS,MAAM,EAAE,CAEnB;AAED,wBAAgB,sBAAsB,CACpC,kBAAkB,EAAE,SAAS,qBAAqB,EAAE,GAAG,SAAS,GAC/D,SAAS,qBAAqB,EAAE,GAAG,SAAS,CAE9C;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACvC,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CASjC;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,UAAU,EAAE,EACzB,kBAAkB,SAAkB,GACnC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAkCjC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAWrF"}
|
package/dist/reporter.cjs
CHANGED
|
@@ -36,7 +36,7 @@ __export(reporter_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(reporter_exports);
|
|
37
37
|
var import_fs = require("fs");
|
|
38
38
|
var import_promises3 = require("fs/promises");
|
|
39
|
-
var
|
|
39
|
+
var import_path4 = require("path");
|
|
40
40
|
var import_p_limit2 = __toESM(require("p-limit"), 1);
|
|
41
41
|
|
|
42
42
|
// src/ci.ts
|
|
@@ -56,16 +56,29 @@ var logError = (...args) => {
|
|
|
56
56
|
|
|
57
57
|
// src/reporter-artifact-ops.ts
|
|
58
58
|
var import_promises2 = require("fs/promises");
|
|
59
|
-
var
|
|
59
|
+
var import_path2 = require("path");
|
|
60
60
|
var import_p_limit = __toESM(require("p-limit"), 1);
|
|
61
61
|
|
|
62
62
|
// src/report-artifact.ts
|
|
63
63
|
var import_promises = require("fs/promises");
|
|
64
|
-
var
|
|
64
|
+
var import_path = require("path");
|
|
65
65
|
var import_url = require("url");
|
|
66
66
|
|
|
67
|
+
// src/path-utils.ts
|
|
68
|
+
var posixAbsolutePattern = /^\//u;
|
|
69
|
+
var windowsDrivePattern = /^[A-Za-z]:[\\/]/u;
|
|
70
|
+
var windowsUncPattern = /^[\\/][\\/]/u;
|
|
71
|
+
function isPosixAbsolutePath(p) {
|
|
72
|
+
return posixAbsolutePattern.test(p);
|
|
73
|
+
}
|
|
74
|
+
function isWindowsAbsolutePath(p) {
|
|
75
|
+
return windowsDrivePattern.test(p) || windowsUncPattern.test(p);
|
|
76
|
+
}
|
|
77
|
+
function isAnyAbsolutePath(p) {
|
|
78
|
+
return isPosixAbsolutePath(p) || isWindowsAbsolutePath(p);
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
// src/report-utils.ts
|
|
68
|
-
var import_path = require("path");
|
|
69
82
|
function normalizeScreenshotsBaseUrl(baseUrl) {
|
|
70
83
|
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
71
84
|
}
|
|
@@ -110,7 +123,7 @@ function attachmentsToImages(attachments, screenshotsBaseUrl = "/screenshots/")
|
|
|
110
123
|
const role = match[2];
|
|
111
124
|
if (baseName === null || baseName === void 0 || role === null || role === void 0) continue;
|
|
112
125
|
images[baseName] ??= {};
|
|
113
|
-
const url = (
|
|
126
|
+
const url = isAnyAbsolutePath(attachment.path) ? `/file/${encodeURIComponent(attachment.path)}` : `${baseUrl}${attachment.path}`;
|
|
114
127
|
const img = images[baseName];
|
|
115
128
|
if (img !== null && img !== void 0) {
|
|
116
129
|
if (role === "actual") img.actual = url;
|
|
@@ -145,9 +158,6 @@ function isCurrentArtifact(image) {
|
|
|
145
158
|
}
|
|
146
159
|
function isReusablePassingImage(image) {
|
|
147
160
|
const source = image.source ?? classifyImage(image);
|
|
148
|
-
if (source === "comparison") {
|
|
149
|
-
return image.actual !== void 0 && image.diff === void 0;
|
|
150
|
-
}
|
|
151
161
|
return source === "baseline-only";
|
|
152
162
|
}
|
|
153
163
|
function hasReusablePassingImages(images) {
|
|
@@ -168,8 +178,8 @@ function preservePreviousPassingImages(test, status, images) {
|
|
|
168
178
|
return {
|
|
169
179
|
...currentImages,
|
|
170
180
|
[name]: {
|
|
171
|
-
|
|
172
|
-
source:
|
|
181
|
+
expect: previousImage.expect,
|
|
182
|
+
source: "baseline-only"
|
|
173
183
|
}
|
|
174
184
|
};
|
|
175
185
|
}, images);
|
|
@@ -433,7 +443,7 @@ var import_meta = { url: require("url").pathToFileURL(__filename).href };
|
|
|
433
443
|
var DEFAULT_REPORT_HTML_PATH = "./crvy-rprtr.html";
|
|
434
444
|
var STATIC_ARTIFACT_APPROVAL_MESSAGE = "This artifact is read-only. Open it with the Crvy Rprtr server to approve screenshots.";
|
|
435
445
|
function toBrowserPath(pathValue) {
|
|
436
|
-
const normalized = pathValue.split(
|
|
446
|
+
const normalized = pathValue.split(import_path.sep).join("/");
|
|
437
447
|
if (normalized === "") return ".";
|
|
438
448
|
if (normalized.startsWith(".")) return normalized;
|
|
439
449
|
return `./${normalized}`;
|
|
@@ -469,11 +479,11 @@ function renderArtifactHtml(template, bootstrapData, stylesheet, script) {
|
|
|
469
479
|
);
|
|
470
480
|
}
|
|
471
481
|
async function getPackagedAssetPath(fileName) {
|
|
472
|
-
const currentDir = (0,
|
|
473
|
-
const candidates = [currentDir, (0,
|
|
482
|
+
const currentDir = (0, import_path.dirname)((0, import_url.fileURLToPath)(import_meta.url));
|
|
483
|
+
const candidates = [currentDir, (0, import_path.resolve)(currentDir, "../dist")];
|
|
474
484
|
const resolvedCandidates = await Promise.all(
|
|
475
485
|
candidates.map(async (candidate) => {
|
|
476
|
-
const assetPath = (0,
|
|
486
|
+
const assetPath = (0, import_path.resolve)(candidate, fileName);
|
|
477
487
|
try {
|
|
478
488
|
await (0, import_promises.access)(assetPath);
|
|
479
489
|
return assetPath;
|
|
@@ -491,7 +501,7 @@ async function getPackagedAssetPath(fileName) {
|
|
|
491
501
|
);
|
|
492
502
|
}
|
|
493
503
|
function buildStaticBootstrapData(events, screenshotDir, htmlPath) {
|
|
494
|
-
const screenshotBaseUrl = toBrowserDirPath((0,
|
|
504
|
+
const screenshotBaseUrl = toBrowserDirPath((0, import_path.relative)((0, import_path.dirname)(htmlPath), (0, import_path.resolve)(screenshotDir)));
|
|
495
505
|
const state = createMutableReportState(screenshotDir);
|
|
496
506
|
for (const event of events) {
|
|
497
507
|
switch (event.type) {
|
|
@@ -530,13 +540,13 @@ function buildStaticBootstrapData(events, screenshotDir, htmlPath) {
|
|
|
530
540
|
return parsedBootstrapData;
|
|
531
541
|
}
|
|
532
542
|
async function writeReportArtifact(options) {
|
|
533
|
-
const reportHtmlPath = (0,
|
|
543
|
+
const reportHtmlPath = (0, import_path.resolve)(options.reportHtmlPath ?? DEFAULT_REPORT_HTML_PATH);
|
|
534
544
|
const [indexHtmlPath, indexJsPath, indexCssPath] = await Promise.all([
|
|
535
545
|
getPackagedAssetPath("index.html"),
|
|
536
546
|
getPackagedAssetPath("index.js"),
|
|
537
547
|
getPackagedAssetPath("index.css")
|
|
538
548
|
]);
|
|
539
|
-
await (0, import_promises.mkdir)((0,
|
|
549
|
+
await (0, import_promises.mkdir)((0, import_path.dirname)(reportHtmlPath), { recursive: true });
|
|
540
550
|
const [template, script, stylesheet] = await Promise.all([
|
|
541
551
|
(0, import_promises.readFile)(indexHtmlPath, "utf8"),
|
|
542
552
|
(0, import_promises.readFile)(indexJsPath, "utf8"),
|
|
@@ -569,9 +579,9 @@ function sanitizeId(id) {
|
|
|
569
579
|
async function copyResolvedBaseline(safeTestId, testScreenshotDir, target, savedAttachments) {
|
|
570
580
|
const attachmentName = `${target.attachmentBaseName}-expected.png`;
|
|
571
581
|
const artifactPath = safeArtifactPath(attachmentName);
|
|
572
|
-
const destPath = (0,
|
|
582
|
+
const destPath = (0, import_path2.join)(testScreenshotDir, artifactPath);
|
|
573
583
|
try {
|
|
574
|
-
await (0, import_promises2.mkdir)((0,
|
|
584
|
+
await (0, import_promises2.mkdir)((0, import_path2.dirname)(destPath), { recursive: true });
|
|
575
585
|
await (0, import_promises2.copyFile)(target.snapshotPath, destPath);
|
|
576
586
|
savedAttachments.push({ name: attachmentName, path: `${safeTestId}/${artifactPath}`, contentType: "image/png" });
|
|
577
587
|
log(`[CrvyRprtr] Attached baseline: ${target.snapshotPath}`);
|
|
@@ -581,7 +591,7 @@ async function copyResolvedBaseline(safeTestId, testScreenshotDir, target, saved
|
|
|
581
591
|
async function saveAttachments(screenshotDir, testId, result) {
|
|
582
592
|
const savedAttachments = [];
|
|
583
593
|
const safeTestId = sanitizeId(testId);
|
|
584
|
-
const testScreenshotDir = (0,
|
|
594
|
+
const testScreenshotDir = (0, import_path2.join)(screenshotDir, safeTestId);
|
|
585
595
|
const limit = (0, import_p_limit.default)(MAX_CONCURRENT_FILE_OPS);
|
|
586
596
|
await Promise.all(
|
|
587
597
|
result.attachments.filter(
|
|
@@ -590,8 +600,8 @@ async function saveAttachments(screenshotDir, testId, result) {
|
|
|
590
600
|
(attachment) => limit(async () => {
|
|
591
601
|
try {
|
|
592
602
|
const artifactPath = safeArtifactPath(attachment.name);
|
|
593
|
-
const destPath = (0,
|
|
594
|
-
await (0, import_promises2.mkdir)((0,
|
|
603
|
+
const destPath = (0, import_path2.join)(testScreenshotDir, artifactPath);
|
|
604
|
+
await (0, import_promises2.mkdir)((0, import_path2.dirname)(destPath), { recursive: true });
|
|
595
605
|
await (0, import_promises2.copyFile)(attachment.path, destPath);
|
|
596
606
|
savedAttachments.push({
|
|
597
607
|
name: attachment.name,
|
|
@@ -721,7 +731,7 @@ function extractScreenshotDeclarations(steps) {
|
|
|
721
731
|
|
|
722
732
|
// src/snapshot-path-resolver.ts
|
|
723
733
|
var import_crypto = require("crypto");
|
|
724
|
-
var
|
|
734
|
+
var import_path3 = require("path");
|
|
725
735
|
var DEFAULT_SCREENSHOT_TEMPLATE = "{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}";
|
|
726
736
|
var WINDOWS_FILESYSTEM_FRIENDLY_LENGTH = 60;
|
|
727
737
|
function isUnsafeFilePathCharacter(character) {
|
|
@@ -756,16 +766,16 @@ function trimLongString(value, length = WINDOWS_FILESYSTEM_FRIENDLY_LENGTH) {
|
|
|
756
766
|
const end = length - middle.length - start;
|
|
757
767
|
return value.slice(0, start) + middle + value.slice(-end);
|
|
758
768
|
}
|
|
759
|
-
function sanitizeFilePathBeforeExtension(filePath, extension = (0,
|
|
769
|
+
function sanitizeFilePathBeforeExtension(filePath, extension = (0, import_path3.extname)(filePath)) {
|
|
760
770
|
const base = filePath.slice(0, filePath.length - extension.length);
|
|
761
771
|
return sanitizeForFilePath(base) + extension;
|
|
762
772
|
}
|
|
763
773
|
function addSuffixToFilePath(filePath, suffix) {
|
|
764
|
-
const extension = (0,
|
|
774
|
+
const extension = (0, import_path3.extname)(filePath);
|
|
765
775
|
return filePath.slice(0, filePath.length - extension.length) + suffix + extension;
|
|
766
776
|
}
|
|
767
777
|
function normalizedSnapshotDir(config) {
|
|
768
|
-
return (0,
|
|
778
|
+
return (0, import_path3.resolve)(config.configDir, config.snapshotDir);
|
|
769
779
|
}
|
|
770
780
|
function templateValue(template, token, value) {
|
|
771
781
|
return template.replace(
|
|
@@ -775,8 +785,8 @@ function templateValue(template, token, value) {
|
|
|
775
785
|
}
|
|
776
786
|
function applyTemplate(input, nameArgument, extension) {
|
|
777
787
|
const template = input.config.toHaveScreenshotPathTemplate ?? input.config.snapshotPathTemplate ?? DEFAULT_SCREENSHOT_TEMPLATE;
|
|
778
|
-
const relativeTestFilePath = (0,
|
|
779
|
-
const parsed = (0,
|
|
788
|
+
const relativeTestFilePath = (0, import_path3.relative)(input.config.testDir, input.testFile);
|
|
789
|
+
const parsed = (0, import_path3.parse)(relativeTestFilePath);
|
|
780
790
|
const tokens = [
|
|
781
791
|
["testDir", input.config.testDir],
|
|
782
792
|
["snapshotDir", normalizedSnapshotDir(input.config)],
|
|
@@ -794,16 +804,16 @@ function applyTemplate(input, nameArgument, extension) {
|
|
|
794
804
|
(currentTemplate, [token, value]) => templateValue(currentTemplate, token, value),
|
|
795
805
|
template
|
|
796
806
|
);
|
|
797
|
-
return (0,
|
|
807
|
+
return (0, import_path3.resolve)(input.config.configDir, snapshotPath);
|
|
798
808
|
}
|
|
799
|
-
function removeExtension(filePath, extension = (0,
|
|
809
|
+
function removeExtension(filePath, extension = (0, import_path3.extname)(filePath)) {
|
|
800
810
|
return filePath.slice(0, filePath.length - extension.length);
|
|
801
811
|
}
|
|
802
812
|
function snapshotNameParts(declaredName) {
|
|
803
|
-
const extension = (0,
|
|
813
|
+
const extension = (0, import_path3.extname)(declaredName) || ".png";
|
|
804
814
|
return {
|
|
805
815
|
extension,
|
|
806
|
-
filePath: (0,
|
|
816
|
+
filePath: (0, import_path3.extname)(declaredName) === "" ? `${declaredName}${extension}` : declaredName
|
|
807
817
|
};
|
|
808
818
|
}
|
|
809
819
|
function filePathForOccurrence(filePath, occurrenceIndex) {
|
|
@@ -827,7 +837,7 @@ function resolveStringCallTarget(input, declaration) {
|
|
|
827
837
|
function resolveArrayCallTarget(input, declaration) {
|
|
828
838
|
const { extension, filePath } = snapshotNameParts(declaration.declaredName);
|
|
829
839
|
const occurrenceFilePath = filePathForOccurrence(filePath, declaration.occurrenceIndex);
|
|
830
|
-
const nameArgument = (0,
|
|
840
|
+
const nameArgument = (0, import_path3.join)((0, import_path3.dirname)(occurrenceFilePath), (0, import_path3.basename)(occurrenceFilePath, extension));
|
|
831
841
|
return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
|
|
832
842
|
}
|
|
833
843
|
function resolveNamedTarget(input, declaration) {
|
|
@@ -869,7 +879,7 @@ function resolveTarget(input, declaration) {
|
|
|
869
879
|
case "unnamed": {
|
|
870
880
|
const anonymousFileName = anonymousName(input.reporterTitlePath, declaration.occurrenceIndex);
|
|
871
881
|
const extension = ".png";
|
|
872
|
-
const nameArgument = (0,
|
|
882
|
+
const nameArgument = (0, import_path3.join)((0, import_path3.dirname)(anonymousFileName), (0, import_path3.basename)(anonymousFileName, extension));
|
|
873
883
|
return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
|
|
874
884
|
}
|
|
875
885
|
}
|
|
@@ -921,7 +931,7 @@ var CrvyRprtr = class {
|
|
|
921
931
|
if (this.ci) this.isOfflineMode = true;
|
|
922
932
|
}
|
|
923
933
|
async onBegin(config, suite) {
|
|
924
|
-
this.configDir = config.configFile === void 0 ? config.rootDir : (0,
|
|
934
|
+
this.configDir = config.configFile === void 0 ? config.rootDir : (0, import_path4.dirname)(config.configFile);
|
|
925
935
|
log(`[CrvyRprtr] Starting run with ${suite.allTests().length} tests`);
|
|
926
936
|
await (0, import_promises3.mkdir)(this.screenshotDir, { recursive: true });
|
|
927
937
|
if (!this.ci) {
|
|
@@ -1070,7 +1080,7 @@ var CrvyRprtr = class {
|
|
|
1070
1080
|
async copyBaselinesForTargets(testId, status, resolvedTargets, savedAttachments) {
|
|
1071
1081
|
if (status !== "passed" || resolvedTargets.length === 0) return;
|
|
1072
1082
|
const safeTestId = sanitizeId(testId);
|
|
1073
|
-
const testScreenshotDir = (0,
|
|
1083
|
+
const testScreenshotDir = (0, import_path4.join)(this.screenshotDir, safeTestId);
|
|
1074
1084
|
const limit = (0, import_p_limit2.default)(5);
|
|
1075
1085
|
await Promise.all(
|
|
1076
1086
|
resolvedTargets.map(
|
package/dist/reporter.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifact-routes.d.ts","sourceRoot":"","sources":["../../src/server/artifact-routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"artifact-routes.d.ts","sourceRoot":"","sources":["../../src/server/artifact-routes.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAWhD,wBAAsB,UAAU,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyCpF;AAYD,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC,iBAAiB,CAAC,CAAA;AAE9D,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAyBf;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmCxF;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,CAQhH"}
|
package/dist/server.cjs
CHANGED
|
@@ -35,16 +35,34 @@ __export(server_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(server_exports);
|
|
36
36
|
|
|
37
37
|
// src/server/app.ts
|
|
38
|
-
var
|
|
38
|
+
var import_path7 = require("path");
|
|
39
39
|
var import_url = require("url");
|
|
40
40
|
var import_p_limit = __toESM(require("p-limit"), 1);
|
|
41
41
|
|
|
42
42
|
// src/offline-reports.ts
|
|
43
43
|
var import_promises = require("fs/promises");
|
|
44
|
-
var
|
|
44
|
+
var import_path = require("path");
|
|
45
|
+
|
|
46
|
+
// src/path-utils.ts
|
|
47
|
+
var posixAbsolutePattern = /^\//u;
|
|
48
|
+
var windowsDrivePattern = /^[A-Za-z]:[\\/]/u;
|
|
49
|
+
var windowsUncPattern = /^[\\/][\\/]/u;
|
|
50
|
+
function isPosixAbsolutePath(p) {
|
|
51
|
+
return posixAbsolutePattern.test(p);
|
|
52
|
+
}
|
|
53
|
+
function isWindowsAbsolutePath(p) {
|
|
54
|
+
return windowsDrivePattern.test(p) || windowsUncPattern.test(p);
|
|
55
|
+
}
|
|
56
|
+
function isAnyAbsolutePath(p) {
|
|
57
|
+
return isPosixAbsolutePath(p) || isWindowsAbsolutePath(p);
|
|
58
|
+
}
|
|
59
|
+
function isForeignAbsolutePath(p, hostPlatform) {
|
|
60
|
+
if (!isAnyAbsolutePath(p)) return false;
|
|
61
|
+
if (hostPlatform === "win32") return !isWindowsAbsolutePath(p);
|
|
62
|
+
return !isPosixAbsolutePath(p);
|
|
63
|
+
}
|
|
45
64
|
|
|
46
65
|
// src/report-utils.ts
|
|
47
|
-
var import_path = require("path");
|
|
48
66
|
function normalizeScreenshotsBaseUrl(baseUrl) {
|
|
49
67
|
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
50
68
|
}
|
|
@@ -89,7 +107,7 @@ function attachmentsToImages(attachments, screenshotsBaseUrl = "/screenshots/")
|
|
|
89
107
|
const role = match[2];
|
|
90
108
|
if (baseName === null || baseName === void 0 || role === null || role === void 0) continue;
|
|
91
109
|
images[baseName] ??= {};
|
|
92
|
-
const url = (
|
|
110
|
+
const url = isAnyAbsolutePath(attachment.path) ? `/file/${encodeURIComponent(attachment.path)}` : `${baseUrl}${attachment.path}`;
|
|
93
111
|
const img = images[baseName];
|
|
94
112
|
if (img !== null && img !== void 0) {
|
|
95
113
|
if (role === "actual") img.actual = url;
|
|
@@ -124,9 +142,6 @@ function isCurrentArtifact(image) {
|
|
|
124
142
|
}
|
|
125
143
|
function isReusablePassingImage(image) {
|
|
126
144
|
const source = image.source ?? classifyImage(image);
|
|
127
|
-
if (source === "comparison") {
|
|
128
|
-
return image.actual !== void 0 && image.diff === void 0;
|
|
129
|
-
}
|
|
130
145
|
return source === "baseline-only";
|
|
131
146
|
}
|
|
132
147
|
function hasReusablePassingImages(images) {
|
|
@@ -147,8 +162,8 @@ function preservePreviousPassingImages(test, status, images) {
|
|
|
147
162
|
return {
|
|
148
163
|
...currentImages,
|
|
149
164
|
[name]: {
|
|
150
|
-
|
|
151
|
-
source:
|
|
165
|
+
expect: previousImage.expect,
|
|
166
|
+
source: "baseline-only"
|
|
152
167
|
}
|
|
153
168
|
};
|
|
154
169
|
}, images);
|
|
@@ -412,7 +427,7 @@ var OFFLINE_REPORT_FILE_PATTERN = /^crvy-rprtr(?:-\d+)?\.json$/;
|
|
|
412
427
|
async function findOfflineReportPaths(searchDir) {
|
|
413
428
|
try {
|
|
414
429
|
const entries = await (0, import_promises.readdir)(searchDir);
|
|
415
|
-
return entries.filter((entry) => OFFLINE_REPORT_FILE_PATTERN.test(entry)).sort((left, right) => left.localeCompare(right)).map((entry) => (0,
|
|
430
|
+
return entries.filter((entry) => OFFLINE_REPORT_FILE_PATTERN.test(entry)).sort((left, right) => left.localeCompare(right)).map((entry) => (0, import_path.join)(searchDir, entry));
|
|
416
431
|
} catch (error) {
|
|
417
432
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
418
433
|
return [];
|
|
@@ -464,7 +479,7 @@ function mergeOfflineReportsIntoTests(existingTests, offlineReports, options = {
|
|
|
464
479
|
|
|
465
480
|
// src/server/file-utils.ts
|
|
466
481
|
var import_promises2 = require("fs/promises");
|
|
467
|
-
var
|
|
482
|
+
var import_path2 = require("path");
|
|
468
483
|
function isFileNotFound(error) {
|
|
469
484
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
470
485
|
}
|
|
@@ -505,16 +520,16 @@ async function readJsonFile(filePath) {
|
|
|
505
520
|
}
|
|
506
521
|
}
|
|
507
522
|
async function writeJsonFile(filePath, value) {
|
|
508
|
-
await (0, import_promises2.mkdir)((0,
|
|
523
|
+
await (0, import_promises2.mkdir)((0, import_path2.dirname)(filePath), { recursive: true });
|
|
509
524
|
await (0, import_promises2.writeFile)(filePath, `${JSON.stringify(value, null, 2)}
|
|
510
525
|
`);
|
|
511
526
|
}
|
|
512
527
|
async function copyFilePortable(sourcePath, destinationPath) {
|
|
513
|
-
await (0, import_promises2.mkdir)((0,
|
|
528
|
+
await (0, import_promises2.mkdir)((0, import_path2.dirname)(destinationPath), { recursive: true });
|
|
514
529
|
await (0, import_promises2.copyFile)(sourcePath, destinationPath);
|
|
515
530
|
}
|
|
516
531
|
function inferContentType(filePath) {
|
|
517
|
-
switch ((0,
|
|
532
|
+
switch ((0, import_path2.extname)(filePath).toLowerCase()) {
|
|
518
533
|
case ".css":
|
|
519
534
|
return "text/css";
|
|
520
535
|
case ".gif":
|
|
@@ -563,11 +578,11 @@ var import_fs2 = require("fs");
|
|
|
563
578
|
// src/server/artifact-routes.ts
|
|
564
579
|
var import_fs = require("fs");
|
|
565
580
|
var import_promises3 = require("fs/promises");
|
|
566
|
-
var
|
|
581
|
+
var import_path5 = require("path");
|
|
567
582
|
|
|
568
583
|
// src/snapshot-path-resolver.ts
|
|
569
584
|
var import_crypto = require("crypto");
|
|
570
|
-
var
|
|
585
|
+
var import_path3 = require("path");
|
|
571
586
|
var DEFAULT_SCREENSHOT_TEMPLATE = "{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}";
|
|
572
587
|
var WINDOWS_FILESYSTEM_FRIENDLY_LENGTH = 60;
|
|
573
588
|
function isUnsafeFilePathCharacter(character) {
|
|
@@ -602,16 +617,16 @@ function trimLongString(value, length = WINDOWS_FILESYSTEM_FRIENDLY_LENGTH) {
|
|
|
602
617
|
const end = length - middle.length - start;
|
|
603
618
|
return value.slice(0, start) + middle + value.slice(-end);
|
|
604
619
|
}
|
|
605
|
-
function sanitizeFilePathBeforeExtension(filePath, extension = (0,
|
|
620
|
+
function sanitizeFilePathBeforeExtension(filePath, extension = (0, import_path3.extname)(filePath)) {
|
|
606
621
|
const base = filePath.slice(0, filePath.length - extension.length);
|
|
607
622
|
return sanitizeForFilePath(base) + extension;
|
|
608
623
|
}
|
|
609
624
|
function addSuffixToFilePath(filePath, suffix) {
|
|
610
|
-
const extension = (0,
|
|
625
|
+
const extension = (0, import_path3.extname)(filePath);
|
|
611
626
|
return filePath.slice(0, filePath.length - extension.length) + suffix + extension;
|
|
612
627
|
}
|
|
613
628
|
function normalizedSnapshotDir(config) {
|
|
614
|
-
return (0,
|
|
629
|
+
return (0, import_path3.resolve)(config.configDir, config.snapshotDir);
|
|
615
630
|
}
|
|
616
631
|
function templateValue(template, token, value) {
|
|
617
632
|
return template.replace(
|
|
@@ -621,8 +636,8 @@ function templateValue(template, token, value) {
|
|
|
621
636
|
}
|
|
622
637
|
function applyTemplate(input, nameArgument, extension) {
|
|
623
638
|
const template = input.config.toHaveScreenshotPathTemplate ?? input.config.snapshotPathTemplate ?? DEFAULT_SCREENSHOT_TEMPLATE;
|
|
624
|
-
const relativeTestFilePath = (0,
|
|
625
|
-
const parsed = (0,
|
|
639
|
+
const relativeTestFilePath = (0, import_path3.relative)(input.config.testDir, input.testFile);
|
|
640
|
+
const parsed = (0, import_path3.parse)(relativeTestFilePath);
|
|
626
641
|
const tokens = [
|
|
627
642
|
["testDir", input.config.testDir],
|
|
628
643
|
["snapshotDir", normalizedSnapshotDir(input.config)],
|
|
@@ -640,16 +655,16 @@ function applyTemplate(input, nameArgument, extension) {
|
|
|
640
655
|
(currentTemplate, [token, value]) => templateValue(currentTemplate, token, value),
|
|
641
656
|
template
|
|
642
657
|
);
|
|
643
|
-
return (0,
|
|
658
|
+
return (0, import_path3.resolve)(input.config.configDir, snapshotPath);
|
|
644
659
|
}
|
|
645
|
-
function removeExtension(filePath, extension = (0,
|
|
660
|
+
function removeExtension(filePath, extension = (0, import_path3.extname)(filePath)) {
|
|
646
661
|
return filePath.slice(0, filePath.length - extension.length);
|
|
647
662
|
}
|
|
648
663
|
function snapshotNameParts(declaredName) {
|
|
649
|
-
const extension = (0,
|
|
664
|
+
const extension = (0, import_path3.extname)(declaredName) || ".png";
|
|
650
665
|
return {
|
|
651
666
|
extension,
|
|
652
|
-
filePath: (0,
|
|
667
|
+
filePath: (0, import_path3.extname)(declaredName) === "" ? `${declaredName}${extension}` : declaredName
|
|
653
668
|
};
|
|
654
669
|
}
|
|
655
670
|
function filePathForOccurrence(filePath, occurrenceIndex) {
|
|
@@ -673,7 +688,7 @@ function resolveStringCallTarget(input, declaration) {
|
|
|
673
688
|
function resolveArrayCallTarget(input, declaration) {
|
|
674
689
|
const { extension, filePath } = snapshotNameParts(declaration.declaredName);
|
|
675
690
|
const occurrenceFilePath = filePathForOccurrence(filePath, declaration.occurrenceIndex);
|
|
676
|
-
const nameArgument = (0,
|
|
691
|
+
const nameArgument = (0, import_path3.join)((0, import_path3.dirname)(occurrenceFilePath), (0, import_path3.basename)(occurrenceFilePath, extension));
|
|
677
692
|
return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
|
|
678
693
|
}
|
|
679
694
|
function resolveNamedTarget(input, declaration) {
|
|
@@ -711,7 +726,7 @@ function resolveTarget(input, declaration) {
|
|
|
711
726
|
case "unnamed": {
|
|
712
727
|
const anonymousFileName = anonymousName(input.reporterTitlePath, declaration.occurrenceIndex);
|
|
713
728
|
const extension = ".png";
|
|
714
|
-
const nameArgument = (0,
|
|
729
|
+
const nameArgument = (0, import_path3.join)((0, import_path3.dirname)(anonymousFileName), (0, import_path3.basename)(anonymousFileName, extension));
|
|
715
730
|
return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
|
|
716
731
|
}
|
|
717
732
|
}
|
|
@@ -724,7 +739,7 @@ function resolveBaselineTargets(input) {
|
|
|
724
739
|
}
|
|
725
740
|
|
|
726
741
|
// src/server/utils.ts
|
|
727
|
-
var
|
|
742
|
+
var import_path4 = require("path");
|
|
728
743
|
var LIVE_UPDATES_WEBSOCKET_PATH = "/";
|
|
729
744
|
function broadcastToBrowsers(wsClients, msg) {
|
|
730
745
|
const payload = JSON.stringify(msg);
|
|
@@ -736,10 +751,10 @@ function isWebSocketUpgradeRequest(req) {
|
|
|
736
751
|
return new URL(req.url).pathname === LIVE_UPDATES_WEBSOCKET_PATH && req.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
737
752
|
}
|
|
738
753
|
function isPathWithinRoots(target, roots) {
|
|
739
|
-
const resolvedTarget = (0,
|
|
754
|
+
const resolvedTarget = (0, import_path4.resolve)(target);
|
|
740
755
|
return roots.some((root) => {
|
|
741
|
-
const rel = (0,
|
|
742
|
-
return rel === "" || !rel.startsWith(`..${
|
|
756
|
+
const rel = (0, import_path4.relative)((0, import_path4.resolve)(root), resolvedTarget);
|
|
757
|
+
return rel === "" || !rel.startsWith(`..${import_path4.sep}`) && rel !== ".." && !(0, import_path4.isAbsolute)(rel);
|
|
743
758
|
});
|
|
744
759
|
}
|
|
745
760
|
|
|
@@ -753,17 +768,24 @@ async function realpathOrNull(path) {
|
|
|
753
768
|
}
|
|
754
769
|
async function handleFile(ctx, req) {
|
|
755
770
|
const notFound = () => new Response("Not Found", { status: 404 });
|
|
771
|
+
let rawDecoded;
|
|
756
772
|
let decodedPath;
|
|
757
773
|
try {
|
|
758
|
-
|
|
774
|
+
rawDecoded = decodeURIComponent(new URL(req.url).pathname.slice("/file/".length));
|
|
775
|
+
decodedPath = (0, import_path5.resolve)(rawDecoded);
|
|
759
776
|
} catch {
|
|
760
777
|
return notFound();
|
|
761
778
|
}
|
|
762
779
|
const realTarget = await realpathOrNull(decodedPath);
|
|
763
780
|
if (realTarget === null) {
|
|
781
|
+
if (isForeignAbsolutePath(rawDecoded, process.platform)) {
|
|
782
|
+
console.warn(
|
|
783
|
+
`[Crvy Rprtr] /file request resolved to a foreign-OS absolute path that cannot be served on ${process.platform}: ${rawDecoded}`
|
|
784
|
+
);
|
|
785
|
+
}
|
|
764
786
|
return notFound();
|
|
765
787
|
}
|
|
766
|
-
const realRoots = (await Promise.all((ctx.artifactRoots ?? []).map((root) => realpathOrNull((0,
|
|
788
|
+
const realRoots = (await Promise.all((ctx.artifactRoots ?? []).map((root) => realpathOrNull((0, import_path5.resolve)(root))))).filter(
|
|
767
789
|
(root) => root !== null
|
|
768
790
|
);
|
|
769
791
|
if (!isPathWithinRoots(realTarget, realRoots)) {
|
|
@@ -793,8 +815,8 @@ function resolveBaselineSnapshotPath(routing, test, retry, imageName) {
|
|
|
793
815
|
declarations: [declaration],
|
|
794
816
|
config: {
|
|
795
817
|
configDir: routing.configDir,
|
|
796
|
-
testDir: routing.playwrightTestDir ?? (0,
|
|
797
|
-
snapshotDir: routing.playwrightSnapshotDir ?? (0,
|
|
818
|
+
testDir: routing.playwrightTestDir ?? (0, import_path5.dirname)(testFile),
|
|
819
|
+
snapshotDir: routing.playwrightSnapshotDir ?? (0, import_path5.dirname)(testFile),
|
|
798
820
|
projectName: test.projectName ?? test.browser,
|
|
799
821
|
snapshotSuffix: process.platform,
|
|
800
822
|
snapshotPathTemplate: routing.playwrightSnapshotPathTemplate,
|
|
@@ -953,9 +975,9 @@ function handleRegister(ctx, data) {
|
|
|
953
975
|
}
|
|
954
976
|
|
|
955
977
|
// src/server/routes.ts
|
|
956
|
-
var
|
|
978
|
+
var import_path6 = require("path");
|
|
957
979
|
async function handleRoot(ctx) {
|
|
958
|
-
const html = await respondWithFile((0,
|
|
980
|
+
const html = await respondWithFile((0, import_path6.join)(ctx.staticDir, "index.html"), "text/html");
|
|
959
981
|
return html ?? new Response("Not Found", { status: 404 });
|
|
960
982
|
}
|
|
961
983
|
async function handleAppCss() {
|
|
@@ -975,7 +997,7 @@ function handleApiReport(ctx) {
|
|
|
975
997
|
var APPROVAL_TARGET_ERROR = "Could not resolve approval target";
|
|
976
998
|
function actualPathFromUrl(ctx, actualUrl) {
|
|
977
999
|
if (actualUrl.startsWith("/screenshots/")) {
|
|
978
|
-
return (0,
|
|
1000
|
+
return (0, import_path6.join)(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length));
|
|
979
1001
|
}
|
|
980
1002
|
if (actualUrl.startsWith("/file/")) {
|
|
981
1003
|
return decodeURIComponent(actualUrl.slice("/file/".length));
|
|
@@ -1097,7 +1119,7 @@ async function handleScreenshots(ctx, req) {
|
|
|
1097
1119
|
}
|
|
1098
1120
|
async function handleDist(ctx, req) {
|
|
1099
1121
|
const path = new URL(req.url).pathname.slice("/dist/".length);
|
|
1100
|
-
const filePath = (0,
|
|
1122
|
+
const filePath = (0, import_path6.join)(ctx.staticDir, path);
|
|
1101
1123
|
const contentType = filePath.endsWith(".css") ? "text/css" : filePath.endsWith(".js") ? "application/javascript" : filePath.endsWith(".svelte") ? "text/plain" : "application/octet-stream";
|
|
1102
1124
|
const file = await respondWithFile(filePath, contentType);
|
|
1103
1125
|
return file ?? new Response("Not Found", { status: 404 });
|
|
@@ -1260,19 +1282,19 @@ function createWebSocketMessageHandler(getHandlerContext) {
|
|
|
1260
1282
|
};
|
|
1261
1283
|
}
|
|
1262
1284
|
async function resolveStaticDir(staticDir) {
|
|
1263
|
-
const currentDir = (0,
|
|
1285
|
+
const currentDir = (0, import_path7.dirname)((0, import_url.fileURLToPath)(import_meta.url));
|
|
1264
1286
|
const candidates = staticDir === void 0 ? [
|
|
1265
1287
|
currentDir,
|
|
1266
|
-
(0,
|
|
1267
|
-
(0,
|
|
1268
|
-
(0,
|
|
1269
|
-
(0,
|
|
1270
|
-
(0,
|
|
1271
|
-
] : [staticDir, (0,
|
|
1288
|
+
(0, import_path7.join)(currentDir, "dist"),
|
|
1289
|
+
(0, import_path7.join)(currentDir, "..", "dist"),
|
|
1290
|
+
(0, import_path7.join)(currentDir, "..", "..", "dist"),
|
|
1291
|
+
(0, import_path7.join)(currentDir, ".."),
|
|
1292
|
+
(0, import_path7.join)(currentDir, "..", "..")
|
|
1293
|
+
] : [staticDir, (0, import_path7.join)(staticDir, "dist")];
|
|
1272
1294
|
const resolvedCandidates = await Promise.all(
|
|
1273
1295
|
candidates.map(async (candidate) => ({
|
|
1274
1296
|
candidate,
|
|
1275
|
-
exists: await fileExists((0,
|
|
1297
|
+
exists: await fileExists((0, import_path7.join)(candidate, "index.html"))
|
|
1276
1298
|
}))
|
|
1277
1299
|
);
|
|
1278
1300
|
const resolved = resolvedCandidates.find(({ exists }) => exists);
|
|
@@ -1283,9 +1305,9 @@ async function resolveStaticDir(staticDir) {
|
|
|
1283
1305
|
}
|
|
1284
1306
|
async function resolveReportPath(reportPath) {
|
|
1285
1307
|
if (await isDirectory(reportPath)) {
|
|
1286
|
-
return { reportFile: (0,
|
|
1308
|
+
return { reportFile: (0, import_path7.join)(reportPath, "report.json"), offlineReportDir: reportPath };
|
|
1287
1309
|
}
|
|
1288
|
-
return { reportFile: reportPath, offlineReportDir: (0,
|
|
1310
|
+
return { reportFile: reportPath, offlineReportDir: (0, import_path7.dirname)(reportPath) };
|
|
1289
1311
|
}
|
|
1290
1312
|
function createRoutesContext(reportData, staticDir, saveReport, options) {
|
|
1291
1313
|
const artifactRoots = [reportData.screenshotDir, options.outputDir, options.playwrightSnapshotDir].filter(
|
package/dist/server.js
CHANGED