@crvy/rprtr 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/dist/{chunk-RFZGJSL3.js → chunk-473CWZ4V.js} +110 -13
- package/dist/cli.js +1 -1
- package/dist/index.js +3 -3
- package/dist/server/app.d.ts +2 -1
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/handlers.d.ts +3 -0
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/report-persistence.d.ts +27 -0
- package/dist/server/report-persistence.d.ts.map +1 -0
- package/dist/server/run-controller.d.ts +2 -0
- package/dist/server/run-controller.d.ts.map +1 -1
- package/dist/server/shutdown.d.ts +17 -0
- package/dist/server/shutdown.d.ts.map +1 -0
- package/dist/server.cjs +110 -13
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
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.2.3] - 2026-07-03
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **server:** Persist report on interrupted runs and mid-run mutations
|
|
8
13
|
## [0.2.2] - 2026-07-03
|
|
9
14
|
|
|
10
15
|
### Miscellaneous
|
|
@@ -350,6 +350,7 @@ function handleArtifactRoute(ctx, req, pathname) {
|
|
|
350
350
|
function handleTestBegin(ctx, data) {
|
|
351
351
|
const test = applyTestBeginEvent(ctx, data);
|
|
352
352
|
ctx.reportData.isRunning = true;
|
|
353
|
+
ctx.scheduleReportSave();
|
|
353
354
|
console.log(` \u25B6 [${test.browser ?? "?"}] ${test.title}`);
|
|
354
355
|
const message = { type: "test-begin", data: test };
|
|
355
356
|
broadcastToBrowsers(ctx.wsClients, message);
|
|
@@ -388,6 +389,7 @@ function handleTestEnd(ctx, data) {
|
|
|
388
389
|
const errNote = data.error !== null && data.error !== void 0 ? `
|
|
389
390
|
Error: ${data.error}` : "";
|
|
390
391
|
console.log(` ${icon} [${test.browser}] ${test.title}${dur}${diffNote}${errNote}`);
|
|
392
|
+
ctx.scheduleReportSave();
|
|
391
393
|
const message = { type: "test-update", data: test };
|
|
392
394
|
broadcastToBrowsers(ctx.wsClients, message);
|
|
393
395
|
}
|
|
@@ -475,6 +477,61 @@ async function resolvePlaywrightConfig(cwd) {
|
|
|
475
477
|
return matches.find((path) => path !== null) ?? null;
|
|
476
478
|
}
|
|
477
479
|
|
|
480
|
+
// src/server/report-persistence.ts
|
|
481
|
+
function createDebouncedSaver(save, delayMs, setTimeoutFn = (fn, ms) => setTimeout(fn, ms), clearTimeoutFn = (handle) => {
|
|
482
|
+
if (handle !== null) clearTimeout(handle);
|
|
483
|
+
}) {
|
|
484
|
+
let timer = null;
|
|
485
|
+
let dirty = false;
|
|
486
|
+
let inFlight = null;
|
|
487
|
+
const runSave = () => {
|
|
488
|
+
dirty = false;
|
|
489
|
+
inFlight = save();
|
|
490
|
+
return inFlight.finally(() => {
|
|
491
|
+
inFlight = null;
|
|
492
|
+
});
|
|
493
|
+
};
|
|
494
|
+
return {
|
|
495
|
+
schedule() {
|
|
496
|
+
dirty = true;
|
|
497
|
+
if (timer !== null) clearTimeoutFn(timer);
|
|
498
|
+
timer = setTimeoutFn(() => {
|
|
499
|
+
timer = null;
|
|
500
|
+
void runSave();
|
|
501
|
+
}, delayMs);
|
|
502
|
+
},
|
|
503
|
+
async flush() {
|
|
504
|
+
if (timer !== null) {
|
|
505
|
+
clearTimeoutFn(timer);
|
|
506
|
+
timer = null;
|
|
507
|
+
}
|
|
508
|
+
if (inFlight !== null) {
|
|
509
|
+
await inFlight;
|
|
510
|
+
}
|
|
511
|
+
if (dirty) {
|
|
512
|
+
await runSave();
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
cancel() {
|
|
516
|
+
if (timer !== null) {
|
|
517
|
+
clearTimeoutFn(timer);
|
|
518
|
+
timer = null;
|
|
519
|
+
}
|
|
520
|
+
dirty = false;
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
function createReportPersistence(reportFile, reportData) {
|
|
525
|
+
const reportSaver = createDebouncedSaver(() => writeJsonFile(reportFile, reportData), 250);
|
|
526
|
+
return {
|
|
527
|
+
saveReport: () => reportSaver.flush(),
|
|
528
|
+
scheduleReportSave: () => {
|
|
529
|
+
reportSaver.schedule();
|
|
530
|
+
},
|
|
531
|
+
dispose: () => reportSaver.flush()
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
478
535
|
// src/server/routes-context.ts
|
|
479
536
|
function createRoutesContext(reportData, staticDir, saveReport, options) {
|
|
480
537
|
const artifactRoots = [reportData.screenshotDir, options.outputDir, options.playwrightSnapshotDir].filter(
|
|
@@ -905,15 +962,14 @@ var RunController = class {
|
|
|
905
962
|
}
|
|
906
963
|
this.deps.setReportRunning(false);
|
|
907
964
|
this.deps.broadcast({ type: "run-status", data: { running: false } });
|
|
965
|
+
void this.deps.saveReport?.();
|
|
908
966
|
}
|
|
909
967
|
};
|
|
910
968
|
function createRealSpawn() {
|
|
911
969
|
return (cmd, args, opts) => {
|
|
912
970
|
const cp = spawn(cmd, args, opts);
|
|
913
971
|
return {
|
|
914
|
-
on: (event, cb) =>
|
|
915
|
-
cp.on(event, cb);
|
|
916
|
-
},
|
|
972
|
+
on: (event, cb) => cp.on(event, cb),
|
|
917
973
|
kill: (signal) => {
|
|
918
974
|
const sig = KNOWN_SIGNALS[signal];
|
|
919
975
|
if (sig !== void 0) cp.kill(sig);
|
|
@@ -1060,7 +1116,7 @@ async function seedRunContext(routesContext, options) {
|
|
|
1060
1116
|
routesContext.runContext = { configFile, cwd: process.cwd() };
|
|
1061
1117
|
}
|
|
1062
1118
|
}
|
|
1063
|
-
function createServerRunController(routesContext, wsClients, reportData, port, setRunFiltered) {
|
|
1119
|
+
function createServerRunController(routesContext, wsClients, reportData, port, setRunFiltered, saveReport) {
|
|
1064
1120
|
return new RunController({
|
|
1065
1121
|
getRunContext: () => routesContext.runContext ?? null,
|
|
1066
1122
|
port,
|
|
@@ -1071,6 +1127,7 @@ function createServerRunController(routesContext, wsClients, reportData, port, s
|
|
|
1071
1127
|
reportData.isRunning = running;
|
|
1072
1128
|
},
|
|
1073
1129
|
setRunFiltered,
|
|
1130
|
+
saveReport,
|
|
1074
1131
|
spawn: createRealSpawn(),
|
|
1075
1132
|
timers: createRealTimers()
|
|
1076
1133
|
});
|
|
@@ -1083,19 +1140,27 @@ async function createServerApp(options = {}) {
|
|
|
1083
1140
|
const staticDir = await resolveStaticDir(options.staticDir);
|
|
1084
1141
|
const wsClients = /* @__PURE__ */ new Set();
|
|
1085
1142
|
const currentRunIds = /* @__PURE__ */ new Set();
|
|
1086
|
-
const
|
|
1087
|
-
const routesContext = createRoutesContext(reportData, staticDir, saveReport, options);
|
|
1143
|
+
const persistence = createReportPersistence(reportFile, reportData);
|
|
1144
|
+
const routesContext = createRoutesContext(reportData, staticDir, persistence.saveReport, options);
|
|
1088
1145
|
await seedRunContext(routesContext, options);
|
|
1089
1146
|
let isFilteredRun = false;
|
|
1090
|
-
const runController = createServerRunController(
|
|
1091
|
-
|
|
1092
|
-
|
|
1147
|
+
const runController = createServerRunController(
|
|
1148
|
+
routesContext,
|
|
1149
|
+
wsClients,
|
|
1150
|
+
reportData,
|
|
1151
|
+
port,
|
|
1152
|
+
(filtered) => {
|
|
1153
|
+
isFilteredRun = filtered;
|
|
1154
|
+
},
|
|
1155
|
+
persistence.saveReport
|
|
1156
|
+
);
|
|
1093
1157
|
const getHandlerContext = () => ({
|
|
1094
1158
|
reportData,
|
|
1095
1159
|
wsClients,
|
|
1096
1160
|
currentRunIds,
|
|
1097
1161
|
isFilteredRun,
|
|
1098
|
-
saveReport,
|
|
1162
|
+
saveReport: persistence.saveReport,
|
|
1163
|
+
scheduleReportSave: persistence.scheduleReportSave,
|
|
1099
1164
|
approvalRouting: routesContext.approvalRouting,
|
|
1100
1165
|
routesContext,
|
|
1101
1166
|
runController
|
|
@@ -1107,13 +1172,17 @@ async function createServerApp(options = {}) {
|
|
|
1107
1172
|
return {
|
|
1108
1173
|
port,
|
|
1109
1174
|
wsClients,
|
|
1110
|
-
close: ()
|
|
1111
|
-
runController.dispose();
|
|
1112
|
-
},
|
|
1175
|
+
close: createCloseHandler(persistence, runController),
|
|
1113
1176
|
handleRequest,
|
|
1114
1177
|
handleWebSocketMessage
|
|
1115
1178
|
};
|
|
1116
1179
|
}
|
|
1180
|
+
function createCloseHandler(persistence, runController) {
|
|
1181
|
+
return async () => {
|
|
1182
|
+
await persistence.dispose();
|
|
1183
|
+
runController.dispose();
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1117
1186
|
|
|
1118
1187
|
// src/server/bun-adapter.ts
|
|
1119
1188
|
function logWebSocketError(prefix, error) {
|
|
@@ -1311,9 +1380,37 @@ async function startNodeServer(app) {
|
|
|
1311
1380
|
console.log(`Crvy Rprtr started at http://localhost:${app.port}`);
|
|
1312
1381
|
}
|
|
1313
1382
|
|
|
1383
|
+
// src/server/shutdown.ts
|
|
1384
|
+
var EXIT_CODES = {
|
|
1385
|
+
SIGINT: 130,
|
|
1386
|
+
SIGTERM: 143,
|
|
1387
|
+
SIGHUP: 129
|
|
1388
|
+
};
|
|
1389
|
+
function createSignalShutdown(deps) {
|
|
1390
|
+
let shuttingDown = false;
|
|
1391
|
+
return async function shutdown(signal) {
|
|
1392
|
+
if (shuttingDown) {
|
|
1393
|
+
deps.exit(EXIT_CODES[signal] ?? 1);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
shuttingDown = true;
|
|
1397
|
+
try {
|
|
1398
|
+
await deps.close();
|
|
1399
|
+
} catch {
|
|
1400
|
+
}
|
|
1401
|
+
deps.exit(EXIT_CODES[signal] ?? 1);
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1314
1405
|
// src/server.ts
|
|
1315
1406
|
async function startServer(options = {}) {
|
|
1316
1407
|
const app = await createServerApp(options);
|
|
1408
|
+
const shutdown = createSignalShutdown({ close: () => app.close(), exit: (code) => process.exit(code) });
|
|
1409
|
+
const onSignal = (signal) => {
|
|
1410
|
+
void shutdown(signal);
|
|
1411
|
+
};
|
|
1412
|
+
process.on("SIGINT", onSignal);
|
|
1413
|
+
process.on("SIGTERM", onSignal);
|
|
1317
1414
|
if (typeof Bun !== "undefined" && typeof Bun.serve === "function") {
|
|
1318
1415
|
startBunServer(app);
|
|
1319
1416
|
return;
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -20092,10 +20092,10 @@ function recalcSuiteStatuses(root10, testPath) {
|
|
|
20092
20092
|
for (const parentPath of ancestorPaths) {
|
|
20093
20093
|
const parentSuite = getSuiteByPath(root10, parentPath);
|
|
20094
20094
|
if (parentSuite && !isTest(parentSuite)) {
|
|
20095
|
-
parentSuite.status = getChildrenArray(parentSuite.children).map(({ status }) => status).reduce(calcStatus);
|
|
20095
|
+
parentSuite.status = getChildrenArray(parentSuite.children).map(({ status }) => status).reduce(calcStatus, void 0);
|
|
20096
20096
|
}
|
|
20097
20097
|
}
|
|
20098
|
-
root10.status = getChildrenArray(root10.children).map(({ status }) => status).reduce(calcStatus);
|
|
20098
|
+
root10.status = getChildrenArray(root10.children).map(({ status }) => status).reduce(calcStatus, void 0);
|
|
20099
20099
|
}
|
|
20100
20100
|
function recalcAllSuiteStatuses(suite) {
|
|
20101
20101
|
for (const child2 of getChildrenArray(suite.children)) {
|
|
@@ -20103,7 +20103,7 @@ function recalcAllSuiteStatuses(suite) {
|
|
|
20103
20103
|
recalcAllSuiteStatuses(child2);
|
|
20104
20104
|
}
|
|
20105
20105
|
}
|
|
20106
|
-
suite.status = getChildrenArray(suite.children).map(({ status }) => status).reduce(calcStatus);
|
|
20106
|
+
suite.status = getChildrenArray(suite.children).map(({ status }) => status).reduce(calcStatus, void 0);
|
|
20107
20107
|
}
|
|
20108
20108
|
function markTestsPending(node) {
|
|
20109
20109
|
if (isTest(node)) {
|
package/dist/server/app.d.ts
CHANGED
|
@@ -26,7 +26,8 @@ export interface ServerOptions {
|
|
|
26
26
|
export interface ServerApp {
|
|
27
27
|
port: number;
|
|
28
28
|
wsClients: Set<RuntimeWebSocket>;
|
|
29
|
-
|
|
29
|
+
/** Flushes pending report writes and disposes the run controller. */
|
|
30
|
+
close: () => Promise<void>;
|
|
30
31
|
handleRequest: (req: Request) => Promise<Response>;
|
|
31
32
|
handleWebSocketMessage: (message: string) => Promise<void>;
|
|
32
33
|
}
|
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":"AA+BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/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;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,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,qEAAqE;IACrE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,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;AAuKD,wBAAsB,eAAe,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CAgDrF"}
|
|
@@ -16,6 +16,9 @@ export interface HandlerContext {
|
|
|
16
16
|
currentRunIds: Set<string>;
|
|
17
17
|
isFilteredRun: boolean;
|
|
18
18
|
saveReport: () => Promise<void>;
|
|
19
|
+
/** Debounced persistence; used after in-memory mutations so a crash or
|
|
20
|
+
* interrupted run (no `run-end`) still leaves the latest state on disk. */
|
|
21
|
+
scheduleReportSave: () => void;
|
|
19
22
|
approvalRouting?: ApprovalRouting;
|
|
20
23
|
routesContext: RoutesContext;
|
|
21
24
|
runController: RunController;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/server/handlers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC7E,OAAO,KAAK,EAA0B,QAAQ,EAAE,MAAM,aAAa,CAAA;AACnE,OAAO,EAA+B,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACxF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE;QACV,SAAS,EAAE,OAAO,CAAA;QAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAC/B,QAAQ,EAAE,MAAM,EAAE,CAAA;QAClB,YAAY,EAAE,OAAO,CAAA;QACrB,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAChC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAG1B,aAAa,EAAE,OAAO,CAAA;IACtB,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;CAC7B;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/server/handlers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC7E,OAAO,KAAK,EAA0B,QAAQ,EAAE,MAAM,aAAa,CAAA;AACnE,OAAO,EAA+B,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACxF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE;QACV,SAAS,EAAE,OAAO,CAAA;QAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAC/B,QAAQ,EAAE,MAAM,EAAE,CAAA;QAClB,YAAY,EAAE,OAAO,CAAA;QACrB,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAChC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAG1B,aAAa,EAAE,OAAO,CAAA;IACtB,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B;+EAC2E;IAC3E,kBAAkB,EAAE,MAAM,IAAI,CAAA;IAC9B,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;CAC7B;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAO9E;AAwBD,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAkB1E;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE;IAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAA;CAAE,GAChD,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAgB,aAAa,IAAI,IAAI,CAIpC;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAWpD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAyC5E"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type TimerHandle = ReturnType<typeof setTimeout>;
|
|
2
|
+
type SetTimer = (fn: () => void, ms?: number) => TimerHandle;
|
|
3
|
+
type ClearTimer = (handle: TimerHandle | null) => void;
|
|
4
|
+
export interface DebouncedSaver {
|
|
5
|
+
/** Schedules a save after the debounce window. Re-arms on each call. */
|
|
6
|
+
schedule(): void;
|
|
7
|
+
/** Cancels any pending timer; awaits an in-flight save; saves immediately if dirty. */
|
|
8
|
+
flush(): Promise<void>;
|
|
9
|
+
/** Cancels any pending timer and clears the dirty flag without saving. */
|
|
10
|
+
cancel(): void;
|
|
11
|
+
}
|
|
12
|
+
export interface ReportPersistence {
|
|
13
|
+
/** Flushes immediately; used by run-end, approvals, child exit, and signals. */
|
|
14
|
+
saveReport: () => Promise<void>;
|
|
15
|
+
/** Debounced; used after frequent in-memory mutations (test-begin/test-end). */
|
|
16
|
+
scheduleReportSave: () => void;
|
|
17
|
+
/** Flushes any pending debounced write; used during teardown. */
|
|
18
|
+
dispose: () => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export declare function createDebouncedSaver(save: () => Promise<void>, delayMs: number, setTimeoutFn?: SetTimer, clearTimeoutFn?: ClearTimer): DebouncedSaver;
|
|
21
|
+
/** Builds the debounced report-persistence facade shared by handlers, the run
|
|
22
|
+
* controller, and shutdown. `scheduleReportSave` coalesces frequent mutations;
|
|
23
|
+
* `saveReport` flushes immediately so an interrupted run (Ctrl+C before run-end,
|
|
24
|
+
* killed child, server restart) still lands its results on disk. */
|
|
25
|
+
export declare function createReportPersistence<T>(reportFile: string, reportData: T): ReportPersistence;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=report-persistence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-persistence.d.ts","sourceRoot":"","sources":["../../src/server/report-persistence.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;AAChD,KAAK,QAAQ,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,KAAK,WAAW,CAAA;AAC5D,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;AAEtD,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,QAAQ,IAAI,IAAI,CAAA;IAChB,uFAAuF;IACvF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,0EAA0E;IAC1E,MAAM,IAAI,IAAI,CAAA;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,gFAAgF;IAChF,kBAAkB,EAAE,MAAM,IAAI,CAAA;IAC9B,iEAAiE;IACjE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACzB,OAAO,EAAE,MAAM,EACf,YAAY,GAAE,QAAyC,EACvD,cAAc,GAAE,UAEf,GACA,cAAc,CA0ChB;AAED;;;oEAGoE;AACpE,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,GAAG,iBAAiB,CAS/F"}
|
|
@@ -34,6 +34,8 @@ export interface RunControllerDeps {
|
|
|
34
34
|
setReportRunning(running: boolean): void;
|
|
35
35
|
/** Records whether the in-progress run is filtered, so run-end can preserve unrelated tests. */
|
|
36
36
|
setRunFiltered?(filtered: boolean): void;
|
|
37
|
+
/** Flushes pending report writes on child exit so an interrupted run still persists. */
|
|
38
|
+
saveReport?: () => Promise<void>;
|
|
37
39
|
spawn: SpawnLike;
|
|
38
40
|
timers: {
|
|
39
41
|
setTimeout: (fn: () => void, ms?: number) => unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-controller.d.ts","sourceRoot":"","sources":["../../src/server/run-controller.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"run-controller.d.ts","sourceRoot":"","sources":["../../src/server/run-controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graceful shutdown handler for process signals (SIGINT/SIGTERM).
|
|
3
|
+
*
|
|
4
|
+
* On the first signal: flushes the report (via `close`) so the latest in-memory
|
|
5
|
+
* state lands on disk, then exits. This is the key path that prevents data loss
|
|
6
|
+
* when the user Ctrl+Cs the reporter server while a run is in progress or a
|
|
7
|
+
* debounced save is pending.
|
|
8
|
+
*
|
|
9
|
+
* On a second signal: exits immediately so a stuck flush can always be
|
|
10
|
+
* force-killed by the user.
|
|
11
|
+
*/
|
|
12
|
+
export interface ShutdownDeps {
|
|
13
|
+
close: () => Promise<void>;
|
|
14
|
+
exit: (code?: number) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function createSignalShutdown(deps: ShutdownDeps): (signal: NodeJS.Signals) => Promise<void>;
|
|
17
|
+
//# sourceMappingURL=shutdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../src/server/shutdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B;AAQD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAgBlG"}
|
package/dist/server.cjs
CHANGED
|
@@ -954,6 +954,7 @@ function handleArtifactRoute(ctx, req, pathname) {
|
|
|
954
954
|
function handleTestBegin(ctx, data) {
|
|
955
955
|
const test = applyTestBeginEvent(ctx, data);
|
|
956
956
|
ctx.reportData.isRunning = true;
|
|
957
|
+
ctx.scheduleReportSave();
|
|
957
958
|
console.log(` \u25B6 [${test.browser ?? "?"}] ${test.title}`);
|
|
958
959
|
const message = { type: "test-begin", data: test };
|
|
959
960
|
broadcastToBrowsers(ctx.wsClients, message);
|
|
@@ -992,6 +993,7 @@ function handleTestEnd(ctx, data) {
|
|
|
992
993
|
const errNote = data.error !== null && data.error !== void 0 ? `
|
|
993
994
|
Error: ${data.error}` : "";
|
|
994
995
|
console.log(` ${icon} [${test.browser}] ${test.title}${dur}${diffNote}${errNote}`);
|
|
996
|
+
ctx.scheduleReportSave();
|
|
995
997
|
const message = { type: "test-update", data: test };
|
|
996
998
|
broadcastToBrowsers(ctx.wsClients, message);
|
|
997
999
|
}
|
|
@@ -1079,6 +1081,61 @@ async function resolvePlaywrightConfig(cwd) {
|
|
|
1079
1081
|
return matches.find((path) => path !== null) ?? null;
|
|
1080
1082
|
}
|
|
1081
1083
|
|
|
1084
|
+
// src/server/report-persistence.ts
|
|
1085
|
+
function createDebouncedSaver(save, delayMs, setTimeoutFn = (fn, ms) => setTimeout(fn, ms), clearTimeoutFn = (handle) => {
|
|
1086
|
+
if (handle !== null) clearTimeout(handle);
|
|
1087
|
+
}) {
|
|
1088
|
+
let timer = null;
|
|
1089
|
+
let dirty = false;
|
|
1090
|
+
let inFlight = null;
|
|
1091
|
+
const runSave = () => {
|
|
1092
|
+
dirty = false;
|
|
1093
|
+
inFlight = save();
|
|
1094
|
+
return inFlight.finally(() => {
|
|
1095
|
+
inFlight = null;
|
|
1096
|
+
});
|
|
1097
|
+
};
|
|
1098
|
+
return {
|
|
1099
|
+
schedule() {
|
|
1100
|
+
dirty = true;
|
|
1101
|
+
if (timer !== null) clearTimeoutFn(timer);
|
|
1102
|
+
timer = setTimeoutFn(() => {
|
|
1103
|
+
timer = null;
|
|
1104
|
+
void runSave();
|
|
1105
|
+
}, delayMs);
|
|
1106
|
+
},
|
|
1107
|
+
async flush() {
|
|
1108
|
+
if (timer !== null) {
|
|
1109
|
+
clearTimeoutFn(timer);
|
|
1110
|
+
timer = null;
|
|
1111
|
+
}
|
|
1112
|
+
if (inFlight !== null) {
|
|
1113
|
+
await inFlight;
|
|
1114
|
+
}
|
|
1115
|
+
if (dirty) {
|
|
1116
|
+
await runSave();
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
1119
|
+
cancel() {
|
|
1120
|
+
if (timer !== null) {
|
|
1121
|
+
clearTimeoutFn(timer);
|
|
1122
|
+
timer = null;
|
|
1123
|
+
}
|
|
1124
|
+
dirty = false;
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function createReportPersistence(reportFile, reportData) {
|
|
1129
|
+
const reportSaver = createDebouncedSaver(() => writeJsonFile(reportFile, reportData), 250);
|
|
1130
|
+
return {
|
|
1131
|
+
saveReport: () => reportSaver.flush(),
|
|
1132
|
+
scheduleReportSave: () => {
|
|
1133
|
+
reportSaver.schedule();
|
|
1134
|
+
},
|
|
1135
|
+
dispose: () => reportSaver.flush()
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1082
1139
|
// src/server/routes-context.ts
|
|
1083
1140
|
function createRoutesContext(reportData, staticDir, saveReport, options) {
|
|
1084
1141
|
const artifactRoots = [reportData.screenshotDir, options.outputDir, options.playwrightSnapshotDir].filter(
|
|
@@ -1510,15 +1567,14 @@ var RunController = class {
|
|
|
1510
1567
|
}
|
|
1511
1568
|
this.deps.setReportRunning(false);
|
|
1512
1569
|
this.deps.broadcast({ type: "run-status", data: { running: false } });
|
|
1570
|
+
void this.deps.saveReport?.();
|
|
1513
1571
|
}
|
|
1514
1572
|
};
|
|
1515
1573
|
function createRealSpawn() {
|
|
1516
1574
|
return (cmd, args, opts) => {
|
|
1517
1575
|
const cp = (0, import_child_process.spawn)(cmd, args, opts);
|
|
1518
1576
|
return {
|
|
1519
|
-
on: (event, cb) =>
|
|
1520
|
-
cp.on(event, cb);
|
|
1521
|
-
},
|
|
1577
|
+
on: (event, cb) => cp.on(event, cb),
|
|
1522
1578
|
kill: (signal) => {
|
|
1523
1579
|
const sig = KNOWN_SIGNALS[signal];
|
|
1524
1580
|
if (sig !== void 0) cp.kill(sig);
|
|
@@ -1666,7 +1722,7 @@ async function seedRunContext(routesContext, options) {
|
|
|
1666
1722
|
routesContext.runContext = { configFile, cwd: process.cwd() };
|
|
1667
1723
|
}
|
|
1668
1724
|
}
|
|
1669
|
-
function createServerRunController(routesContext, wsClients, reportData, port, setRunFiltered) {
|
|
1725
|
+
function createServerRunController(routesContext, wsClients, reportData, port, setRunFiltered, saveReport) {
|
|
1670
1726
|
return new RunController({
|
|
1671
1727
|
getRunContext: () => routesContext.runContext ?? null,
|
|
1672
1728
|
port,
|
|
@@ -1677,6 +1733,7 @@ function createServerRunController(routesContext, wsClients, reportData, port, s
|
|
|
1677
1733
|
reportData.isRunning = running;
|
|
1678
1734
|
},
|
|
1679
1735
|
setRunFiltered,
|
|
1736
|
+
saveReport,
|
|
1680
1737
|
spawn: createRealSpawn(),
|
|
1681
1738
|
timers: createRealTimers()
|
|
1682
1739
|
});
|
|
@@ -1689,19 +1746,27 @@ async function createServerApp(options = {}) {
|
|
|
1689
1746
|
const staticDir = await resolveStaticDir(options.staticDir);
|
|
1690
1747
|
const wsClients = /* @__PURE__ */ new Set();
|
|
1691
1748
|
const currentRunIds = /* @__PURE__ */ new Set();
|
|
1692
|
-
const
|
|
1693
|
-
const routesContext = createRoutesContext(reportData, staticDir, saveReport, options);
|
|
1749
|
+
const persistence = createReportPersistence(reportFile, reportData);
|
|
1750
|
+
const routesContext = createRoutesContext(reportData, staticDir, persistence.saveReport, options);
|
|
1694
1751
|
await seedRunContext(routesContext, options);
|
|
1695
1752
|
let isFilteredRun = false;
|
|
1696
|
-
const runController = createServerRunController(
|
|
1697
|
-
|
|
1698
|
-
|
|
1753
|
+
const runController = createServerRunController(
|
|
1754
|
+
routesContext,
|
|
1755
|
+
wsClients,
|
|
1756
|
+
reportData,
|
|
1757
|
+
port,
|
|
1758
|
+
(filtered) => {
|
|
1759
|
+
isFilteredRun = filtered;
|
|
1760
|
+
},
|
|
1761
|
+
persistence.saveReport
|
|
1762
|
+
);
|
|
1699
1763
|
const getHandlerContext = () => ({
|
|
1700
1764
|
reportData,
|
|
1701
1765
|
wsClients,
|
|
1702
1766
|
currentRunIds,
|
|
1703
1767
|
isFilteredRun,
|
|
1704
|
-
saveReport,
|
|
1768
|
+
saveReport: persistence.saveReport,
|
|
1769
|
+
scheduleReportSave: persistence.scheduleReportSave,
|
|
1705
1770
|
approvalRouting: routesContext.approvalRouting,
|
|
1706
1771
|
routesContext,
|
|
1707
1772
|
runController
|
|
@@ -1713,13 +1778,17 @@ async function createServerApp(options = {}) {
|
|
|
1713
1778
|
return {
|
|
1714
1779
|
port,
|
|
1715
1780
|
wsClients,
|
|
1716
|
-
close: ()
|
|
1717
|
-
runController.dispose();
|
|
1718
|
-
},
|
|
1781
|
+
close: createCloseHandler(persistence, runController),
|
|
1719
1782
|
handleRequest,
|
|
1720
1783
|
handleWebSocketMessage
|
|
1721
1784
|
};
|
|
1722
1785
|
}
|
|
1786
|
+
function createCloseHandler(persistence, runController) {
|
|
1787
|
+
return async () => {
|
|
1788
|
+
await persistence.dispose();
|
|
1789
|
+
runController.dispose();
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1723
1792
|
|
|
1724
1793
|
// src/server/bun-adapter.ts
|
|
1725
1794
|
function logWebSocketError(prefix, error) {
|
|
@@ -1917,9 +1986,37 @@ async function startNodeServer(app) {
|
|
|
1917
1986
|
console.log(`Crvy Rprtr started at http://localhost:${app.port}`);
|
|
1918
1987
|
}
|
|
1919
1988
|
|
|
1989
|
+
// src/server/shutdown.ts
|
|
1990
|
+
var EXIT_CODES = {
|
|
1991
|
+
SIGINT: 130,
|
|
1992
|
+
SIGTERM: 143,
|
|
1993
|
+
SIGHUP: 129
|
|
1994
|
+
};
|
|
1995
|
+
function createSignalShutdown(deps) {
|
|
1996
|
+
let shuttingDown = false;
|
|
1997
|
+
return async function shutdown(signal) {
|
|
1998
|
+
if (shuttingDown) {
|
|
1999
|
+
deps.exit(EXIT_CODES[signal] ?? 1);
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
shuttingDown = true;
|
|
2003
|
+
try {
|
|
2004
|
+
await deps.close();
|
|
2005
|
+
} catch {
|
|
2006
|
+
}
|
|
2007
|
+
deps.exit(EXIT_CODES[signal] ?? 1);
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
|
|
1920
2011
|
// src/server.ts
|
|
1921
2012
|
async function startServer(options = {}) {
|
|
1922
2013
|
const app = await createServerApp(options);
|
|
2014
|
+
const shutdown = createSignalShutdown({ close: () => app.close(), exit: (code) => process.exit(code) });
|
|
2015
|
+
const onSignal = (signal) => {
|
|
2016
|
+
void shutdown(signal);
|
|
2017
|
+
};
|
|
2018
|
+
process.on("SIGINT", onSignal);
|
|
2019
|
+
process.on("SIGTERM", onSignal);
|
|
1923
2020
|
if (typeof Bun !== "undefined" && typeof Bun.serve === "function") {
|
|
1924
2021
|
startBunServer(app);
|
|
1925
2022
|
return;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAKrE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,wBAAsB,WAAW,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB5E"}
|
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvy/rprtr",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Playwright reporter with visual regression UI for comparing and approving screenshot tests",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"crvy",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"p-limit": "^7.3.0",
|
|
79
|
-
"package-manager-detector": "^1.
|
|
79
|
+
"package-manager-detector": "^1.6.0",
|
|
80
80
|
"ws": "^8.18.3",
|
|
81
81
|
"zod": "^4.3.6"
|
|
82
82
|
},
|