@crvy/rprtr 0.0.8 → 0.0.9
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 +5 -0
- package/dist/{chunk-XU5VQ3FZ.js → chunk-TS64TROX.js} +17 -138
- package/dist/cli.js +1 -1
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server.cjs +33 -154
- package/dist/server.js +1 -1
- package/package.json +1 -1
- package/dist/server/report-watch.d.ts +0 -9
- package/dist/server/report-watch.d.ts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,11 @@ 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.0.9] - 2026-06-05
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Remove file watcher that interfered with live updates
|
|
8
13
|
## [0.0.8] - 2026-06-05
|
|
9
14
|
|
|
10
15
|
### Fixed
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-EJRLZZ22.js";
|
|
15
15
|
|
|
16
16
|
// src/server/app.ts
|
|
17
|
-
import { dirname as dirname3, join as
|
|
17
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
18
18
|
import { fileURLToPath } from "url";
|
|
19
19
|
import pLimit from "p-limit";
|
|
20
20
|
|
|
@@ -210,123 +210,15 @@ function handleSync(ctx) {
|
|
|
210
210
|
broadcastToBrowsers(ctx.wsClients, { type: "sync", data: ctx.reportData });
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
// src/server/report-watch.ts
|
|
214
|
-
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
215
|
-
import { join as join2 } from "path";
|
|
216
|
-
var OFFLINE_REPORT_FILE_PATTERN2 = /^crvy-rprtr(?:-\d+)?\.json$/;
|
|
217
|
-
function createDebouncedRefresh(reload, delayMs = 50) {
|
|
218
|
-
let timer = null;
|
|
219
|
-
return () => {
|
|
220
|
-
if (timer !== null) {
|
|
221
|
-
clearTimeout(timer);
|
|
222
|
-
}
|
|
223
|
-
timer = setTimeout(() => {
|
|
224
|
-
timer = null;
|
|
225
|
-
void reload();
|
|
226
|
-
}, delayMs);
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
function isFileNotFound2(error) {
|
|
230
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
231
|
-
}
|
|
232
|
-
async function describeFile(filePath, label) {
|
|
233
|
-
try {
|
|
234
|
-
const fileStats = await stat2(filePath);
|
|
235
|
-
return `${label}:${fileStats.size}:${fileStats.mtimeMs}`;
|
|
236
|
-
} catch (error) {
|
|
237
|
-
if (isFileNotFound2(error)) {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
throw error;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
async function createOfflineFingerprint(offlineReportDir) {
|
|
244
|
-
if (!await isDirectory(offlineReportDir)) {
|
|
245
|
-
return "offline:missing";
|
|
246
|
-
}
|
|
247
|
-
try {
|
|
248
|
-
const entries = await readdir2(offlineReportDir, { withFileTypes: true });
|
|
249
|
-
const relevantEntries = entries.filter(
|
|
250
|
-
(entry) => entry.isFile() && (entry.name === "report.json" || OFFLINE_REPORT_FILE_PATTERN2.test(entry.name))
|
|
251
|
-
).sort((left, right) => left.name.localeCompare(right.name));
|
|
252
|
-
const parts = await Promise.all(
|
|
253
|
-
relevantEntries.map((entry) => describeFile(join2(offlineReportDir, entry.name), entry.name))
|
|
254
|
-
);
|
|
255
|
-
return `offline:${parts.filter((part) => part !== null).join("|")}`;
|
|
256
|
-
} catch (error) {
|
|
257
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
258
|
-
console.warn(`[Server] Unable to scan ${offlineReportDir}: ${errorMsg}`);
|
|
259
|
-
return "offline:error";
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
async function createScreenshotFingerprint(screenshotDir, relativePath = "") {
|
|
263
|
-
const directoryPath = relativePath === "" ? screenshotDir : join2(screenshotDir, relativePath);
|
|
264
|
-
if (!await isDirectory(directoryPath)) {
|
|
265
|
-
return relativePath === "" ? ["screenshots:missing"] : [];
|
|
266
|
-
}
|
|
267
|
-
try {
|
|
268
|
-
const entries = await readdir2(directoryPath, { withFileTypes: true });
|
|
269
|
-
const parts = await Promise.all(
|
|
270
|
-
entries.sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
271
|
-
const entryRelativePath = relativePath === "" ? entry.name : join2(relativePath, entry.name);
|
|
272
|
-
if (entry.isDirectory()) {
|
|
273
|
-
return createScreenshotFingerprint(screenshotDir, entryRelativePath);
|
|
274
|
-
}
|
|
275
|
-
return describeFile(join2(screenshotDir, entryRelativePath), entryRelativePath);
|
|
276
|
-
})
|
|
277
|
-
);
|
|
278
|
-
return parts.flat().filter((part) => part !== null);
|
|
279
|
-
} catch (error) {
|
|
280
|
-
if (isFileNotFound2(error)) {
|
|
281
|
-
return relativePath === "" ? ["screenshots:missing"] : [];
|
|
282
|
-
}
|
|
283
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
284
|
-
console.warn(`[Server] Unable to scan ${directoryPath}: ${errorMsg}`);
|
|
285
|
-
return relativePath === "" ? ["screenshots:error"] : [];
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
async function createArtifactsFingerprint(options) {
|
|
289
|
-
const [offlineFingerprint, screenshotFingerprint] = await Promise.all([
|
|
290
|
-
createOfflineFingerprint(options.offlineReportDir),
|
|
291
|
-
createScreenshotFingerprint(options.screenshotDir)
|
|
292
|
-
]);
|
|
293
|
-
return `${offlineFingerprint}::${screenshotFingerprint.join("|")}`;
|
|
294
|
-
}
|
|
295
|
-
async function watchReportArtifacts(options) {
|
|
296
|
-
let fingerprint = await createArtifactsFingerprint(options);
|
|
297
|
-
let isPolling = false;
|
|
298
|
-
const interval = setInterval(() => {
|
|
299
|
-
if (isPolling) {
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
isPolling = true;
|
|
303
|
-
void createArtifactsFingerprint(options).then((nextFingerprint) => {
|
|
304
|
-
if (nextFingerprint === fingerprint) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
fingerprint = nextFingerprint;
|
|
308
|
-
options.scheduleRefresh();
|
|
309
|
-
}).catch((error) => {
|
|
310
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
311
|
-
console.warn(`[Server] Artifact refresh poll failed: ${errorMsg}`);
|
|
312
|
-
}).finally(() => {
|
|
313
|
-
isPolling = false;
|
|
314
|
-
});
|
|
315
|
-
}, 100);
|
|
316
|
-
return () => {
|
|
317
|
-
clearInterval(interval);
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
213
|
// src/server/routes.ts
|
|
322
214
|
import { existsSync } from "fs";
|
|
323
|
-
import { dirname as dirname2, join as
|
|
215
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
324
216
|
var LIVE_UPDATES_WEBSOCKET_PATH = "/";
|
|
325
217
|
function isWebSocketUpgradeRequest(req) {
|
|
326
218
|
return new URL(req.url).pathname === LIVE_UPDATES_WEBSOCKET_PATH && req.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
327
219
|
}
|
|
328
220
|
async function handleRoot(ctx) {
|
|
329
|
-
const html = await respondWithFile(
|
|
221
|
+
const html = await respondWithFile(join2(ctx.staticDir, "index.html"), "text/html");
|
|
330
222
|
return html ?? new Response("Not Found", { status: 404 });
|
|
331
223
|
}
|
|
332
224
|
async function handleAppCss() {
|
|
@@ -345,7 +237,7 @@ function handleApiReport(ctx) {
|
|
|
345
237
|
}
|
|
346
238
|
var APPROVAL_TARGET_ERROR = "Could not resolve approval target";
|
|
347
239
|
function actualPathFromUrl(ctx, actualUrl) {
|
|
348
|
-
return actualUrl.startsWith("/screenshots/") ?
|
|
240
|
+
return actualUrl.startsWith("/screenshots/") ? join2(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length)) : actualUrl;
|
|
349
241
|
}
|
|
350
242
|
function reporterTitlePath(test) {
|
|
351
243
|
const testFile = test.location?.file;
|
|
@@ -489,7 +381,7 @@ async function handleScreenshots(ctx, req) {
|
|
|
489
381
|
}
|
|
490
382
|
async function handleDist(ctx, req) {
|
|
491
383
|
const path = new URL(req.url).pathname.slice("/dist/".length);
|
|
492
|
-
const filePath =
|
|
384
|
+
const filePath = join2(ctx.staticDir, path);
|
|
493
385
|
const contentType = filePath.endsWith(".css") ? "text/css" : filePath.endsWith(".js") ? "application/javascript" : filePath.endsWith(".svelte") ? "text/plain" : "application/octet-stream";
|
|
494
386
|
const file = await respondWithFile(filePath, contentType);
|
|
495
387
|
return file ?? new Response("Not Found", { status: 404 });
|
|
@@ -592,10 +484,6 @@ async function loadOfflineReports(offlineReportDir, reportData) {
|
|
|
592
484
|
screenshotsBaseUrl: "/screenshots/"
|
|
593
485
|
});
|
|
594
486
|
}
|
|
595
|
-
function resetReloadableReportData(reportData) {
|
|
596
|
-
reportData.tests = {};
|
|
597
|
-
reportData.isUpdateMode = false;
|
|
598
|
-
}
|
|
599
487
|
async function handleParsedWebSocketMessage(ctx, msg) {
|
|
600
488
|
switch (msg.type) {
|
|
601
489
|
case "test-begin": {
|
|
@@ -647,16 +535,16 @@ async function resolveStaticDir(staticDir) {
|
|
|
647
535
|
const currentDir = dirname3(fileURLToPath(import.meta.url));
|
|
648
536
|
const candidates = staticDir === void 0 ? [
|
|
649
537
|
currentDir,
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
] : [staticDir,
|
|
538
|
+
join3(currentDir, "dist"),
|
|
539
|
+
join3(currentDir, "..", "dist"),
|
|
540
|
+
join3(currentDir, "..", "..", "dist"),
|
|
541
|
+
join3(currentDir, ".."),
|
|
542
|
+
join3(currentDir, "..", "..")
|
|
543
|
+
] : [staticDir, join3(staticDir, "dist")];
|
|
656
544
|
const resolvedCandidates = await Promise.all(
|
|
657
545
|
candidates.map(async (candidate) => ({
|
|
658
546
|
candidate,
|
|
659
|
-
exists: await fileExists(
|
|
547
|
+
exists: await fileExists(join3(candidate, "index.html"))
|
|
660
548
|
}))
|
|
661
549
|
);
|
|
662
550
|
const resolved = resolvedCandidates.find(({ exists }) => exists);
|
|
@@ -667,7 +555,7 @@ async function resolveStaticDir(staticDir) {
|
|
|
667
555
|
}
|
|
668
556
|
async function resolveReportPath(reportPath) {
|
|
669
557
|
if (await isDirectory(reportPath)) {
|
|
670
|
-
return { reportFile:
|
|
558
|
+
return { reportFile: join3(reportPath, "report.json"), offlineReportDir: reportPath };
|
|
671
559
|
}
|
|
672
560
|
return { reportFile: reportPath, offlineReportDir: dirname3(reportPath) };
|
|
673
561
|
}
|
|
@@ -700,22 +588,13 @@ async function createServerApp(options = {}) {
|
|
|
700
588
|
const getHandlerContext = () => createHandlerContext(reportData, wsClients, currentRunIds, saveReport);
|
|
701
589
|
const handleRequest = (req) => handleHttpRequest(routesContext, req);
|
|
702
590
|
const handleWebSocketMessage = createWebSocketMessageHandler(getHandlerContext);
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
await loadReport(reportFile, reportData);
|
|
706
|
-
await loadOfflineReports(offlineReportDir, reportData);
|
|
707
|
-
broadcastToBrowsers(wsClients, { type: "sync", data: reportData });
|
|
708
|
-
};
|
|
709
|
-
await reloadFromDisk();
|
|
710
|
-
const close = await watchReportArtifacts({
|
|
711
|
-
offlineReportDir,
|
|
712
|
-
screenshotDir: reportData.screenshotDir,
|
|
713
|
-
scheduleRefresh: createDebouncedRefresh(reloadFromDisk)
|
|
714
|
-
});
|
|
591
|
+
await loadReport(reportFile, reportData);
|
|
592
|
+
await loadOfflineReports(offlineReportDir, reportData);
|
|
715
593
|
return {
|
|
716
594
|
port,
|
|
717
595
|
wsClients,
|
|
718
|
-
close
|
|
596
|
+
close: () => {
|
|
597
|
+
},
|
|
719
598
|
handleRequest,
|
|
720
599
|
handleWebSocketMessage
|
|
721
600
|
};
|
package/dist/cli.js
CHANGED
package/dist/server/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,8BAA8B,CAAC,EAAE,MAAM,CAAA;IACvC,sCAAsC,CAAC,EAAE,MAAM,CAAA;CAChD;AAUD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAChC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClD,sBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3D;AA8LD,wBAAsB,eAAe,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CA6BrF"}
|
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
|
|
38
|
+
var import_path5 = require("path");
|
|
39
39
|
var import_url = require("url");
|
|
40
40
|
var import_p_limit = __toESM(require("p-limit"), 1);
|
|
41
41
|
|
|
@@ -562,121 +562,13 @@ function handleSync(ctx) {
|
|
|
562
562
|
broadcastToBrowsers(ctx.wsClients, { type: "sync", data: ctx.reportData });
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
-
// src/server/report-watch.ts
|
|
566
|
-
var import_promises3 = require("fs/promises");
|
|
567
|
-
var import_path3 = require("path");
|
|
568
|
-
var OFFLINE_REPORT_FILE_PATTERN2 = /^crvy-rprtr(?:-\d+)?\.json$/;
|
|
569
|
-
function createDebouncedRefresh(reload, delayMs = 50) {
|
|
570
|
-
let timer = null;
|
|
571
|
-
return () => {
|
|
572
|
-
if (timer !== null) {
|
|
573
|
-
clearTimeout(timer);
|
|
574
|
-
}
|
|
575
|
-
timer = setTimeout(() => {
|
|
576
|
-
timer = null;
|
|
577
|
-
void reload();
|
|
578
|
-
}, delayMs);
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
function isFileNotFound2(error) {
|
|
582
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
583
|
-
}
|
|
584
|
-
async function describeFile(filePath, label) {
|
|
585
|
-
try {
|
|
586
|
-
const fileStats = await (0, import_promises3.stat)(filePath);
|
|
587
|
-
return `${label}:${fileStats.size}:${fileStats.mtimeMs}`;
|
|
588
|
-
} catch (error) {
|
|
589
|
-
if (isFileNotFound2(error)) {
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
592
|
-
throw error;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
async function createOfflineFingerprint(offlineReportDir) {
|
|
596
|
-
if (!await isDirectory(offlineReportDir)) {
|
|
597
|
-
return "offline:missing";
|
|
598
|
-
}
|
|
599
|
-
try {
|
|
600
|
-
const entries = await (0, import_promises3.readdir)(offlineReportDir, { withFileTypes: true });
|
|
601
|
-
const relevantEntries = entries.filter(
|
|
602
|
-
(entry) => entry.isFile() && (entry.name === "report.json" || OFFLINE_REPORT_FILE_PATTERN2.test(entry.name))
|
|
603
|
-
).sort((left, right) => left.name.localeCompare(right.name));
|
|
604
|
-
const parts = await Promise.all(
|
|
605
|
-
relevantEntries.map((entry) => describeFile((0, import_path3.join)(offlineReportDir, entry.name), entry.name))
|
|
606
|
-
);
|
|
607
|
-
return `offline:${parts.filter((part) => part !== null).join("|")}`;
|
|
608
|
-
} catch (error) {
|
|
609
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
610
|
-
console.warn(`[Server] Unable to scan ${offlineReportDir}: ${errorMsg}`);
|
|
611
|
-
return "offline:error";
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
async function createScreenshotFingerprint(screenshotDir, relativePath = "") {
|
|
615
|
-
const directoryPath = relativePath === "" ? screenshotDir : (0, import_path3.join)(screenshotDir, relativePath);
|
|
616
|
-
if (!await isDirectory(directoryPath)) {
|
|
617
|
-
return relativePath === "" ? ["screenshots:missing"] : [];
|
|
618
|
-
}
|
|
619
|
-
try {
|
|
620
|
-
const entries = await (0, import_promises3.readdir)(directoryPath, { withFileTypes: true });
|
|
621
|
-
const parts = await Promise.all(
|
|
622
|
-
entries.sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
623
|
-
const entryRelativePath = relativePath === "" ? entry.name : (0, import_path3.join)(relativePath, entry.name);
|
|
624
|
-
if (entry.isDirectory()) {
|
|
625
|
-
return createScreenshotFingerprint(screenshotDir, entryRelativePath);
|
|
626
|
-
}
|
|
627
|
-
return describeFile((0, import_path3.join)(screenshotDir, entryRelativePath), entryRelativePath);
|
|
628
|
-
})
|
|
629
|
-
);
|
|
630
|
-
return parts.flat().filter((part) => part !== null);
|
|
631
|
-
} catch (error) {
|
|
632
|
-
if (isFileNotFound2(error)) {
|
|
633
|
-
return relativePath === "" ? ["screenshots:missing"] : [];
|
|
634
|
-
}
|
|
635
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
636
|
-
console.warn(`[Server] Unable to scan ${directoryPath}: ${errorMsg}`);
|
|
637
|
-
return relativePath === "" ? ["screenshots:error"] : [];
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
async function createArtifactsFingerprint(options) {
|
|
641
|
-
const [offlineFingerprint, screenshotFingerprint] = await Promise.all([
|
|
642
|
-
createOfflineFingerprint(options.offlineReportDir),
|
|
643
|
-
createScreenshotFingerprint(options.screenshotDir)
|
|
644
|
-
]);
|
|
645
|
-
return `${offlineFingerprint}::${screenshotFingerprint.join("|")}`;
|
|
646
|
-
}
|
|
647
|
-
async function watchReportArtifacts(options) {
|
|
648
|
-
let fingerprint = await createArtifactsFingerprint(options);
|
|
649
|
-
let isPolling = false;
|
|
650
|
-
const interval = setInterval(() => {
|
|
651
|
-
if (isPolling) {
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
isPolling = true;
|
|
655
|
-
void createArtifactsFingerprint(options).then((nextFingerprint) => {
|
|
656
|
-
if (nextFingerprint === fingerprint) {
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
fingerprint = nextFingerprint;
|
|
660
|
-
options.scheduleRefresh();
|
|
661
|
-
}).catch((error) => {
|
|
662
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
663
|
-
console.warn(`[Server] Artifact refresh poll failed: ${errorMsg}`);
|
|
664
|
-
}).finally(() => {
|
|
665
|
-
isPolling = false;
|
|
666
|
-
});
|
|
667
|
-
}, 100);
|
|
668
|
-
return () => {
|
|
669
|
-
clearInterval(interval);
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
|
|
673
565
|
// src/server/routes.ts
|
|
674
566
|
var import_fs = require("fs");
|
|
675
|
-
var
|
|
567
|
+
var import_path4 = require("path");
|
|
676
568
|
|
|
677
569
|
// src/snapshot-path-resolver.ts
|
|
678
570
|
var import_crypto = require("crypto");
|
|
679
|
-
var
|
|
571
|
+
var import_path3 = require("path");
|
|
680
572
|
var DEFAULT_SCREENSHOT_TEMPLATE = "{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}";
|
|
681
573
|
var WINDOWS_FILESYSTEM_FRIENDLY_LENGTH = 60;
|
|
682
574
|
function isUnsafeFilePathCharacter(character) {
|
|
@@ -711,16 +603,16 @@ function trimLongString(value, length = WINDOWS_FILESYSTEM_FRIENDLY_LENGTH) {
|
|
|
711
603
|
const end = length - middle.length - start;
|
|
712
604
|
return value.slice(0, start) + middle + value.slice(-end);
|
|
713
605
|
}
|
|
714
|
-
function sanitizeFilePathBeforeExtension(filePath, extension = (0,
|
|
606
|
+
function sanitizeFilePathBeforeExtension(filePath, extension = (0, import_path3.extname)(filePath)) {
|
|
715
607
|
const base = filePath.slice(0, filePath.length - extension.length);
|
|
716
608
|
return sanitizeForFilePath(base) + extension;
|
|
717
609
|
}
|
|
718
610
|
function addSuffixToFilePath(filePath, suffix) {
|
|
719
|
-
const extension = (0,
|
|
611
|
+
const extension = (0, import_path3.extname)(filePath);
|
|
720
612
|
return filePath.slice(0, filePath.length - extension.length) + suffix + extension;
|
|
721
613
|
}
|
|
722
614
|
function normalizedSnapshotDir(config) {
|
|
723
|
-
return (0,
|
|
615
|
+
return (0, import_path3.resolve)(config.configDir, config.snapshotDir);
|
|
724
616
|
}
|
|
725
617
|
function templateValue(template, token, value) {
|
|
726
618
|
return template.replace(
|
|
@@ -730,8 +622,8 @@ function templateValue(template, token, value) {
|
|
|
730
622
|
}
|
|
731
623
|
function applyTemplate(input, nameArgument, extension) {
|
|
732
624
|
const template = input.config.toHaveScreenshotPathTemplate ?? input.config.snapshotPathTemplate ?? DEFAULT_SCREENSHOT_TEMPLATE;
|
|
733
|
-
const relativeTestFilePath = (0,
|
|
734
|
-
const parsed = (0,
|
|
625
|
+
const relativeTestFilePath = (0, import_path3.relative)(input.config.testDir, input.testFile);
|
|
626
|
+
const parsed = (0, import_path3.parse)(relativeTestFilePath);
|
|
735
627
|
const tokens = [
|
|
736
628
|
["testDir", input.config.testDir],
|
|
737
629
|
["snapshotDir", normalizedSnapshotDir(input.config)],
|
|
@@ -749,16 +641,16 @@ function applyTemplate(input, nameArgument, extension) {
|
|
|
749
641
|
(currentTemplate, [token, value]) => templateValue(currentTemplate, token, value),
|
|
750
642
|
template
|
|
751
643
|
);
|
|
752
|
-
return (0,
|
|
644
|
+
return (0, import_path3.resolve)(input.config.configDir, snapshotPath);
|
|
753
645
|
}
|
|
754
|
-
function removeExtension(filePath, extension = (0,
|
|
646
|
+
function removeExtension(filePath, extension = (0, import_path3.extname)(filePath)) {
|
|
755
647
|
return filePath.slice(0, filePath.length - extension.length);
|
|
756
648
|
}
|
|
757
649
|
function snapshotNameParts(declaredName) {
|
|
758
|
-
const extension = (0,
|
|
650
|
+
const extension = (0, import_path3.extname)(declaredName) || ".png";
|
|
759
651
|
return {
|
|
760
652
|
extension,
|
|
761
|
-
filePath: (0,
|
|
653
|
+
filePath: (0, import_path3.extname)(declaredName) === "" ? `${declaredName}${extension}` : declaredName
|
|
762
654
|
};
|
|
763
655
|
}
|
|
764
656
|
function filePathForOccurrence(filePath, occurrenceIndex) {
|
|
@@ -782,7 +674,7 @@ function resolveStringCallTarget(input, declaration) {
|
|
|
782
674
|
function resolveArrayCallTarget(input, declaration) {
|
|
783
675
|
const { extension, filePath } = snapshotNameParts(declaration.declaredName);
|
|
784
676
|
const occurrenceFilePath = filePathForOccurrence(filePath, declaration.occurrenceIndex);
|
|
785
|
-
const nameArgument = (0,
|
|
677
|
+
const nameArgument = (0, import_path3.join)((0, import_path3.dirname)(occurrenceFilePath), (0, import_path3.basename)(occurrenceFilePath, extension));
|
|
786
678
|
return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
|
|
787
679
|
}
|
|
788
680
|
function resolveNamedTarget(input, declaration) {
|
|
@@ -818,7 +710,7 @@ function resolveTarget(input, declaration) {
|
|
|
818
710
|
case "unnamed": {
|
|
819
711
|
const anonymousFileName = anonymousName(input.reporterTitlePath, declaration.occurrenceIndex);
|
|
820
712
|
const extension = ".png";
|
|
821
|
-
const nameArgument = (0,
|
|
713
|
+
const nameArgument = (0, import_path3.join)((0, import_path3.dirname)(anonymousFileName), (0, import_path3.basename)(anonymousFileName, extension));
|
|
822
714
|
return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
|
|
823
715
|
}
|
|
824
716
|
}
|
|
@@ -836,7 +728,7 @@ function isWebSocketUpgradeRequest(req) {
|
|
|
836
728
|
return new URL(req.url).pathname === LIVE_UPDATES_WEBSOCKET_PATH && req.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
837
729
|
}
|
|
838
730
|
async function handleRoot(ctx) {
|
|
839
|
-
const html = await respondWithFile((0,
|
|
731
|
+
const html = await respondWithFile((0, import_path4.join)(ctx.staticDir, "index.html"), "text/html");
|
|
840
732
|
return html ?? new Response("Not Found", { status: 404 });
|
|
841
733
|
}
|
|
842
734
|
async function handleAppCss() {
|
|
@@ -855,7 +747,7 @@ function handleApiReport(ctx) {
|
|
|
855
747
|
}
|
|
856
748
|
var APPROVAL_TARGET_ERROR = "Could not resolve approval target";
|
|
857
749
|
function actualPathFromUrl(ctx, actualUrl) {
|
|
858
|
-
return actualUrl.startsWith("/screenshots/") ? (0,
|
|
750
|
+
return actualUrl.startsWith("/screenshots/") ? (0, import_path4.join)(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length)) : actualUrl;
|
|
859
751
|
}
|
|
860
752
|
function reporterTitlePath(test) {
|
|
861
753
|
const testFile = test.location?.file;
|
|
@@ -873,8 +765,8 @@ function resolveApprovalTarget(ctx, test, retry, imageName) {
|
|
|
873
765
|
declarations: [declaration],
|
|
874
766
|
config: {
|
|
875
767
|
configDir: ctx.approvalRouting.configDir,
|
|
876
|
-
testDir: ctx.approvalRouting.playwrightTestDir ?? (0,
|
|
877
|
-
snapshotDir: ctx.approvalRouting.playwrightSnapshotDir ?? (0,
|
|
768
|
+
testDir: ctx.approvalRouting.playwrightTestDir ?? (0, import_path4.dirname)(testFile),
|
|
769
|
+
snapshotDir: ctx.approvalRouting.playwrightSnapshotDir ?? (0, import_path4.dirname)(testFile),
|
|
878
770
|
projectName: test.browser,
|
|
879
771
|
snapshotSuffix: process.platform,
|
|
880
772
|
snapshotPathTemplate: ctx.approvalRouting.playwrightSnapshotPathTemplate,
|
|
@@ -999,7 +891,7 @@ async function handleScreenshots(ctx, req) {
|
|
|
999
891
|
}
|
|
1000
892
|
async function handleDist(ctx, req) {
|
|
1001
893
|
const path = new URL(req.url).pathname.slice("/dist/".length);
|
|
1002
|
-
const filePath = (0,
|
|
894
|
+
const filePath = (0, import_path4.join)(ctx.staticDir, path);
|
|
1003
895
|
const contentType = filePath.endsWith(".css") ? "text/css" : filePath.endsWith(".js") ? "application/javascript" : filePath.endsWith(".svelte") ? "text/plain" : "application/octet-stream";
|
|
1004
896
|
const file = await respondWithFile(filePath, contentType);
|
|
1005
897
|
return file ?? new Response("Not Found", { status: 404 });
|
|
@@ -1103,10 +995,6 @@ async function loadOfflineReports(offlineReportDir, reportData) {
|
|
|
1103
995
|
screenshotsBaseUrl: "/screenshots/"
|
|
1104
996
|
});
|
|
1105
997
|
}
|
|
1106
|
-
function resetReloadableReportData(reportData) {
|
|
1107
|
-
reportData.tests = {};
|
|
1108
|
-
reportData.isUpdateMode = false;
|
|
1109
|
-
}
|
|
1110
998
|
async function handleParsedWebSocketMessage(ctx, msg) {
|
|
1111
999
|
switch (msg.type) {
|
|
1112
1000
|
case "test-begin": {
|
|
@@ -1155,19 +1043,19 @@ function createWebSocketMessageHandler(getHandlerContext) {
|
|
|
1155
1043
|
};
|
|
1156
1044
|
}
|
|
1157
1045
|
async function resolveStaticDir(staticDir) {
|
|
1158
|
-
const currentDir = (0,
|
|
1046
|
+
const currentDir = (0, import_path5.dirname)((0, import_url.fileURLToPath)(import_meta.url));
|
|
1159
1047
|
const candidates = staticDir === void 0 ? [
|
|
1160
1048
|
currentDir,
|
|
1161
|
-
(0,
|
|
1162
|
-
(0,
|
|
1163
|
-
(0,
|
|
1164
|
-
(0,
|
|
1165
|
-
(0,
|
|
1166
|
-
] : [staticDir, (0,
|
|
1049
|
+
(0, import_path5.join)(currentDir, "dist"),
|
|
1050
|
+
(0, import_path5.join)(currentDir, "..", "dist"),
|
|
1051
|
+
(0, import_path5.join)(currentDir, "..", "..", "dist"),
|
|
1052
|
+
(0, import_path5.join)(currentDir, ".."),
|
|
1053
|
+
(0, import_path5.join)(currentDir, "..", "..")
|
|
1054
|
+
] : [staticDir, (0, import_path5.join)(staticDir, "dist")];
|
|
1167
1055
|
const resolvedCandidates = await Promise.all(
|
|
1168
1056
|
candidates.map(async (candidate) => ({
|
|
1169
1057
|
candidate,
|
|
1170
|
-
exists: await fileExists((0,
|
|
1058
|
+
exists: await fileExists((0, import_path5.join)(candidate, "index.html"))
|
|
1171
1059
|
}))
|
|
1172
1060
|
);
|
|
1173
1061
|
const resolved = resolvedCandidates.find(({ exists }) => exists);
|
|
@@ -1178,9 +1066,9 @@ async function resolveStaticDir(staticDir) {
|
|
|
1178
1066
|
}
|
|
1179
1067
|
async function resolveReportPath(reportPath) {
|
|
1180
1068
|
if (await isDirectory(reportPath)) {
|
|
1181
|
-
return { reportFile: (0,
|
|
1069
|
+
return { reportFile: (0, import_path5.join)(reportPath, "report.json"), offlineReportDir: reportPath };
|
|
1182
1070
|
}
|
|
1183
|
-
return { reportFile: reportPath, offlineReportDir: (0,
|
|
1071
|
+
return { reportFile: reportPath, offlineReportDir: (0, import_path5.dirname)(reportPath) };
|
|
1184
1072
|
}
|
|
1185
1073
|
function createRoutesContext(reportData, staticDir, saveReport, options) {
|
|
1186
1074
|
return {
|
|
@@ -1211,22 +1099,13 @@ async function createServerApp(options = {}) {
|
|
|
1211
1099
|
const getHandlerContext = () => createHandlerContext(reportData, wsClients, currentRunIds, saveReport);
|
|
1212
1100
|
const handleRequest = (req) => handleHttpRequest(routesContext, req);
|
|
1213
1101
|
const handleWebSocketMessage = createWebSocketMessageHandler(getHandlerContext);
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
await loadReport(reportFile, reportData);
|
|
1217
|
-
await loadOfflineReports(offlineReportDir, reportData);
|
|
1218
|
-
broadcastToBrowsers(wsClients, { type: "sync", data: reportData });
|
|
1219
|
-
};
|
|
1220
|
-
await reloadFromDisk();
|
|
1221
|
-
const close = await watchReportArtifacts({
|
|
1222
|
-
offlineReportDir,
|
|
1223
|
-
screenshotDir: reportData.screenshotDir,
|
|
1224
|
-
scheduleRefresh: createDebouncedRefresh(reloadFromDisk)
|
|
1225
|
-
});
|
|
1102
|
+
await loadReport(reportFile, reportData);
|
|
1103
|
+
await loadOfflineReports(offlineReportDir, reportData);
|
|
1226
1104
|
return {
|
|
1227
1105
|
port,
|
|
1228
1106
|
wsClients,
|
|
1229
|
-
close
|
|
1107
|
+
close: () => {
|
|
1108
|
+
},
|
|
1230
1109
|
handleRequest,
|
|
1231
1110
|
handleWebSocketMessage
|
|
1232
1111
|
};
|
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare function createDebouncedRefresh(reload: () => Promise<void>, delayMs?: number): () => void;
|
|
2
|
-
interface ReportWatchOptions {
|
|
3
|
-
offlineReportDir: string;
|
|
4
|
-
screenshotDir: string;
|
|
5
|
-
scheduleRefresh: () => void;
|
|
6
|
-
}
|
|
7
|
-
export declare function watchReportArtifacts(options: ReportWatchOptions): Promise<() => void>;
|
|
8
|
-
export {};
|
|
9
|
-
//# sourceMappingURL=report-watch.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"report-watch.d.ts","sourceRoot":"","sources":["../../src/server/report-watch.ts"],"names":[],"mappings":"AAOA,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,SAAK,GAAG,MAAM,IAAI,CAa5F;AAED,UAAU,kBAAkB;IAC1B,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,eAAe,EAAE,MAAM,IAAI,CAAA;CAC5B;AAsFD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAgC3F"}
|