@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 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 saveReport = () => writeJsonFile(reportFile, reportData);
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(routesContext, wsClients, reportData, port, (filtered) => {
1091
- isFilteredRun = filtered;
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startServer
4
- } from "./chunk-RFZGJSL3.js";
4
+ } from "./chunk-473CWZ4V.js";
5
5
  import "./chunk-HAFWYUNO.js";
6
6
 
7
7
  // src/cli.ts
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)) {
@@ -26,7 +26,8 @@ export interface ServerOptions {
26
26
  export interface ServerApp {
27
27
  port: number;
28
28
  wsClients: Set<RuntimeWebSocket>;
29
- close: () => void;
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AA8BA,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,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;AAqKD,wBAAsB,eAAe,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CA8CrF"}
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,CAM9E;AAwBD,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAiB1E;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"}
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 saveReport = () => writeJsonFile(reportFile, reportData);
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(routesContext, wsClients, reportData, port, (filtered) => {
1697
- isFilteredRun = filtered;
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;
@@ -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;AAIrE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,wBAAsB,WAAW,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAS5E"}
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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startServer
3
- } from "./chunk-RFZGJSL3.js";
3
+ } from "./chunk-473CWZ4V.js";
4
4
  import "./chunk-HAFWYUNO.js";
5
5
  export {
6
6
  startServer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crvy/rprtr",
3
- "version": "0.2.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.7.0",
79
+ "package-manager-detector": "^1.6.0",
80
80
  "ws": "^8.18.3",
81
81
  "zod": "^4.3.6"
82
82
  },