@crvy/rprtr 0.2.4 → 0.3.1
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 +55 -0
- package/README.md +41 -0
- package/dist/{chunk-473CWZ4V.js → chunk-4CVHIHAJ.js} +746 -281
- package/dist/{chunk-HAFWYUNO.js → chunk-4YJL655E.js} +16 -4
- package/dist/cli.d.ts +5 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -4
- package/dist/index.css +35 -0
- package/dist/index.js +152 -105
- package/dist/reporter-artifact-ops.d.ts +0 -2
- package/dist/reporter-artifact-ops.d.ts.map +1 -1
- package/dist/reporter.cjs +32 -38
- package/dist/reporter.d.ts +2 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +27 -43
- package/dist/schemas/http.d.ts +2 -0
- package/dist/schemas/http.d.ts.map +1 -1
- package/dist/schemas.d.ts +14 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/server/app.d.ts +6 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/artifact-routes.d.ts.map +1 -1
- package/dist/server/docker-launcher.d.ts +27 -0
- package/dist/server/docker-launcher.d.ts.map +1 -0
- package/dist/server/docker-support.d.ts +88 -0
- package/dist/server/docker-support.d.ts.map +1 -0
- package/dist/server/handlers.d.ts +1 -1
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/launcher-resolver.d.ts +23 -0
- package/dist/server/launcher-resolver.d.ts.map +1 -0
- package/dist/server/playwright-config.d.ts +6 -0
- package/dist/server/playwright-config.d.ts.map +1 -1
- package/dist/server/routes-context.d.ts +6 -1
- package/dist/server/routes-context.d.ts.map +1 -1
- package/dist/server/routes.d.ts +7 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/run-controller.d.ts +19 -19
- package/dist/server/run-controller.d.ts.map +1 -1
- package/dist/server/run-launcher.d.ts +45 -0
- package/dist/server/run-launcher.d.ts.map +1 -0
- package/dist/server/run-mode.d.ts +15 -0
- package/dist/server/run-mode.d.ts.map +1 -0
- package/dist/server/server-factories.d.ts +21 -0
- package/dist/server/server-factories.d.ts.map +1 -0
- package/dist/server.cjs +921 -446
- package/dist/server.js +2 -2
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -12,13 +12,14 @@ import {
|
|
|
12
12
|
applyTestEndEvent,
|
|
13
13
|
createMutableReportState,
|
|
14
14
|
finalizeRunEvent,
|
|
15
|
+
isCI,
|
|
15
16
|
isForeignAbsolutePath,
|
|
16
17
|
resolveBaselineTargets,
|
|
17
18
|
safeParse
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-4YJL655E.js";
|
|
19
20
|
|
|
20
21
|
// src/server/app.ts
|
|
21
|
-
import { dirname as
|
|
22
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
22
23
|
import { fileURLToPath } from "url";
|
|
23
24
|
|
|
24
25
|
// src/offline-reports.ts
|
|
@@ -208,8 +209,580 @@ function mergeOfflineReportsIntoTests(existingTests, offlineReports, options = {
|
|
|
208
209
|
};
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
// src/server/docker-support.ts
|
|
213
|
+
import { spawn } from "child_process";
|
|
214
|
+
import { readFileSync } from "node:fs";
|
|
215
|
+
import { createRequire } from "node:module";
|
|
216
|
+
import { isAbsolute, join as join2, relative, sep } from "node:path";
|
|
217
|
+
import { detect } from "package-manager-detector/detect";
|
|
218
|
+
function createDockerExec() {
|
|
219
|
+
return (args) => new Promise((resolve5, reject) => {
|
|
220
|
+
const child = spawn("docker", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
221
|
+
const stdout = [];
|
|
222
|
+
const stderr = [];
|
|
223
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
224
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
225
|
+
child.on("error", reject);
|
|
226
|
+
child.on("close", (code) => {
|
|
227
|
+
resolve5({
|
|
228
|
+
exitCode: code ?? 1,
|
|
229
|
+
stdout: Buffer.concat(stdout).toString(),
|
|
230
|
+
stderr: Buffer.concat(stderr).toString()
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async function probeDockerDaemon(exec) {
|
|
236
|
+
try {
|
|
237
|
+
const result = await exec(["info"]);
|
|
238
|
+
return result.exitCode === 0;
|
|
239
|
+
} catch {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function isDockerImagePresent(exec, image) {
|
|
244
|
+
try {
|
|
245
|
+
const result = await exec(["image", "inspect", image]);
|
|
246
|
+
return result.exitCode === 0;
|
|
247
|
+
} catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function pullDockerImage(exec, image) {
|
|
252
|
+
try {
|
|
253
|
+
const result = await exec(["pull", image]);
|
|
254
|
+
return result.exitCode === 0;
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function forceRemoveContainer(exec, name) {
|
|
260
|
+
try {
|
|
261
|
+
await exec(["rm", "-f", name]);
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function resolveDockerImage(options) {
|
|
266
|
+
if (options.image !== void 0 && options.image !== "") return options.image;
|
|
267
|
+
if (options.version === null) return null;
|
|
268
|
+
return `mcr.microsoft.com/playwright:v${options.version}-noble`;
|
|
269
|
+
}
|
|
270
|
+
var CONTAINER_INVOKERS = {
|
|
271
|
+
npm: ["npx"],
|
|
272
|
+
pnpm: ["pnpm", "exec"],
|
|
273
|
+
yarn: ["yarn"],
|
|
274
|
+
bun: ["bunx"]
|
|
275
|
+
};
|
|
276
|
+
var DEFAULT_CONTAINER_COMMAND = ["npx"];
|
|
277
|
+
function resolveContainerCommand(input) {
|
|
278
|
+
if (input.command !== void 0 && input.command.length > 0) return input.command;
|
|
279
|
+
if (!input.hasCustomImage) return [...DEFAULT_CONTAINER_COMMAND];
|
|
280
|
+
if (input.detectedAgentName === void 0 || input.detectedAgentName === null) {
|
|
281
|
+
input.warn?.("Could not detect a package manager for the custom docker image; falling back to npx.");
|
|
282
|
+
return [...DEFAULT_CONTAINER_COMMAND];
|
|
283
|
+
}
|
|
284
|
+
const invoker = CONTAINER_INVOKERS[input.detectedAgentName];
|
|
285
|
+
if (invoker === void 0) {
|
|
286
|
+
input.warn?.(`Package manager "${input.detectedAgentName}" is not supported in docker mode; falling back to npx.`);
|
|
287
|
+
return [...DEFAULT_CONTAINER_COMMAND];
|
|
288
|
+
}
|
|
289
|
+
return [...invoker];
|
|
290
|
+
}
|
|
291
|
+
var detectProjectAgent = async (cwd) => {
|
|
292
|
+
try {
|
|
293
|
+
const result = await detect({ cwd, strategies: ["lockfile", "packageManager-field"] });
|
|
294
|
+
return result === null ? null : { name: result.name, agent: result.agent };
|
|
295
|
+
} catch {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
function rewriteContainerPath(path, mapping) {
|
|
300
|
+
const normalizedPath = normalizeForMatch(path);
|
|
301
|
+
const normalizedFrom = normalizeForMatch(mapping.from);
|
|
302
|
+
if (normalizedPath === normalizedFrom) return mapping.to;
|
|
303
|
+
if (normalizedPath.startsWith(`${normalizedFrom}/`)) return mapping.to + normalizedPath.slice(normalizedFrom.length);
|
|
304
|
+
return path;
|
|
305
|
+
}
|
|
306
|
+
function normalizeForMatch(p) {
|
|
307
|
+
return p.replace(/\\/g, "/").replace(/^[A-Z](?=:)/, (c) => c.toLowerCase());
|
|
308
|
+
}
|
|
309
|
+
var CONTAINER_TEST_LIST_PATH = "/tmp/crvy-rprtr-test-list.txt";
|
|
310
|
+
var REPORTER_BARE_SPECIFIER = "@crvy/rprtr";
|
|
311
|
+
var PATH_FLAGS = /* @__PURE__ */ new Set(["--config", "--reporter", "--test-list"]);
|
|
312
|
+
function rewritePlaywrightArgs(playwrightArgs, ctx, workDir, warn) {
|
|
313
|
+
const args = [];
|
|
314
|
+
const bindMounts = [];
|
|
315
|
+
for (let i = 0; i < playwrightArgs.length; i++) {
|
|
316
|
+
const flag = playwrightArgs[i];
|
|
317
|
+
if (!PATH_FLAGS.has(flag)) {
|
|
318
|
+
args.push(flag);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const value = playwrightArgs[i + 1];
|
|
322
|
+
if (value === void 0) break;
|
|
323
|
+
i += 1;
|
|
324
|
+
args.push(flag);
|
|
325
|
+
const rewritten = rewriteContainerPath(value, { from: ctx.cwd, to: workDir });
|
|
326
|
+
if (flag === "--reporter" && rewritten === value) {
|
|
327
|
+
args.push(REPORTER_BARE_SPECIFIER);
|
|
328
|
+
} else if (flag === "--test-list" && rewritten === value) {
|
|
329
|
+
args.push(CONTAINER_TEST_LIST_PATH);
|
|
330
|
+
bindMounts.push(`${value}:${CONTAINER_TEST_LIST_PATH}:ro`);
|
|
331
|
+
} else {
|
|
332
|
+
if (flag === "--config" && rewritten === value) {
|
|
333
|
+
warn(`--config "${value}" is outside the project directory and will not resolve inside the container.`);
|
|
334
|
+
}
|
|
335
|
+
args.push(rewritten);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return { args, bindMounts };
|
|
339
|
+
}
|
|
340
|
+
function resolvePlaywrightVersion(cwd) {
|
|
341
|
+
try {
|
|
342
|
+
const req = createRequire(join2(cwd, "package.json"));
|
|
343
|
+
const pkgPath = req.resolve("@playwright/test/package.json");
|
|
344
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
345
|
+
return typeof pkg === "object" && pkg !== null && "version" in pkg && typeof pkg.version === "string" ? pkg.version : null;
|
|
346
|
+
} catch {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function rewriteContainerTestDescriptors(tests, mapping) {
|
|
351
|
+
if (tests === void 0 || mapping === void 0) return tests;
|
|
352
|
+
return tests.map((d) => ({ ...d, file: rewriteContainerPath(d.file, mapping) }));
|
|
353
|
+
}
|
|
354
|
+
function testListEntry(d, file) {
|
|
355
|
+
const loc = d.column === void 0 ? `${file}:${d.line}` : `${file}:${d.line}:${d.column}`;
|
|
356
|
+
const title = d.titlePath.join(" \u203A ");
|
|
357
|
+
const prefix = d.projectName !== void 0 && d.projectName !== "" ? `[${d.projectName}] \u203A ` : "";
|
|
358
|
+
return `${prefix}${loc} \u203A ${title}`;
|
|
359
|
+
}
|
|
360
|
+
function buildTestListEntries(tests, rootDir, cwd, pathStyle = "host") {
|
|
361
|
+
const convert = (p) => pathStyle === "posix" ? p.replace(/\\/g, "/") : p;
|
|
362
|
+
if (rootDir !== void 0) {
|
|
363
|
+
return tests.map(
|
|
364
|
+
(d) => testListEntry(d, convert(isAbsolute(d.file) ? relative(rootDir, d.file) || d.file : d.file))
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
return tests.flatMap((d) => {
|
|
368
|
+
if (!isAbsolute(d.file)) return [testListEntry(d, convert(d.file))];
|
|
369
|
+
const rel = relative(cwd ?? process.cwd(), d.file);
|
|
370
|
+
const segments = pathStyle === "posix" ? rel.split(/[\\/]/) : rel.split(sep);
|
|
371
|
+
return segments.map((_, i) => testListEntry(d, convert(segments.slice(i).join(sep))));
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/server/run-controller.ts
|
|
376
|
+
import { spawn as spawn2 } from "child_process";
|
|
377
|
+
import { unlinkSync, writeFileSync } from "node:fs";
|
|
378
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
379
|
+
import { tmpdir } from "node:os";
|
|
380
|
+
import { join as join3 } from "node:path";
|
|
381
|
+
|
|
382
|
+
// src/server/run-launcher.ts
|
|
383
|
+
import { resolveCommand } from "package-manager-detector/commands";
|
|
384
|
+
import { getUserAgent } from "package-manager-detector/detect";
|
|
385
|
+
function resolvePlaywrightLaunch(cwd, playwrightArgs) {
|
|
386
|
+
const agent = getUserAgent();
|
|
387
|
+
const resolved = agent === null ? null : resolveCommand(agent, "execute-local", ["playwright", ...playwrightArgs]);
|
|
388
|
+
if (resolved !== null) return { cmd: resolved.command, args: resolved.args };
|
|
389
|
+
return { cmd: "npx", args: ["playwright", ...playwrightArgs] };
|
|
390
|
+
}
|
|
391
|
+
function buildSpawnEnv(port, baseEnv = process.env) {
|
|
392
|
+
const env = {};
|
|
393
|
+
for (const [key, value] of Object.entries(baseEnv)) {
|
|
394
|
+
if (key === "CI") continue;
|
|
395
|
+
env[key] = value;
|
|
396
|
+
}
|
|
397
|
+
env.CRVY_RPRTR_SERVER_URL = `ws://localhost:${port}`;
|
|
398
|
+
env.PLAYWRIGHT_HTML_OPEN = "never";
|
|
399
|
+
return env;
|
|
400
|
+
}
|
|
401
|
+
function createLocalLauncher(options) {
|
|
402
|
+
return {
|
|
403
|
+
mode: "local",
|
|
404
|
+
launch({ ctx, playwrightArgs }) {
|
|
405
|
+
const resolve5 = options.resolveLaunch ?? resolvePlaywrightLaunch;
|
|
406
|
+
const { cmd, args } = resolve5(ctx.cwd, playwrightArgs);
|
|
407
|
+
return { cmd, args, env: buildSpawnEnv(options.port, options.env) };
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/server/run-controller.ts
|
|
413
|
+
var STOP_GRACE_MS = 5e3;
|
|
414
|
+
var KNOWN_SIGNALS = {
|
|
415
|
+
SIGTERM: "SIGTERM",
|
|
416
|
+
SIGKILL: "SIGKILL"
|
|
417
|
+
};
|
|
418
|
+
function sharedProject(tests) {
|
|
419
|
+
const names = new Set(tests.map((t) => t.projectName ?? ""));
|
|
420
|
+
if (names.size === 1) {
|
|
421
|
+
const name = [...names][0];
|
|
422
|
+
return name === "" ? void 0 : name;
|
|
423
|
+
}
|
|
424
|
+
return void 0;
|
|
425
|
+
}
|
|
426
|
+
function gteMinor(version, major, minor) {
|
|
427
|
+
const match = /^(\d+)\.(\d+)/.exec(version.trim());
|
|
428
|
+
if (match === null) return false;
|
|
429
|
+
const maj = parseInt(match[1], 10);
|
|
430
|
+
const min = parseInt(match[2], 10);
|
|
431
|
+
if (maj !== major) return maj > major;
|
|
432
|
+
return min >= minor;
|
|
433
|
+
}
|
|
434
|
+
var defaultWriteTempFile = (content) => {
|
|
435
|
+
const path = join3(tmpdir(), `crvy-rprtr-test-list-${process.pid}-${Date.now()}.txt`);
|
|
436
|
+
writeFileSync(path, content, "utf8");
|
|
437
|
+
return path;
|
|
438
|
+
};
|
|
439
|
+
function defaultDeleteTempFile(path) {
|
|
440
|
+
try {
|
|
441
|
+
unlinkSync(path);
|
|
442
|
+
} catch {
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function resolveReporterDefault(cwd) {
|
|
446
|
+
try {
|
|
447
|
+
return createRequire2(join3(cwd, "package.json")).resolve("@crvy/rprtr");
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
return createRequire2(import.meta.url).resolve("@crvy/rprtr");
|
|
452
|
+
} catch {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
var RunController = class {
|
|
457
|
+
constructor(deps) {
|
|
458
|
+
this.deps = deps;
|
|
459
|
+
}
|
|
460
|
+
child = null;
|
|
461
|
+
sigkillTimer = null;
|
|
462
|
+
testListPath = null;
|
|
463
|
+
get isRunning() {
|
|
464
|
+
return this.child !== null;
|
|
465
|
+
}
|
|
466
|
+
supportsTestList(cwd) {
|
|
467
|
+
const getVersion = this.deps.getPlaywrightVersion ?? resolvePlaywrightVersion;
|
|
468
|
+
const version = getVersion(cwd);
|
|
469
|
+
return version !== null && gteMinor(version, 1, 56);
|
|
470
|
+
}
|
|
471
|
+
cleanupTempFile() {
|
|
472
|
+
if (this.testListPath !== null) {
|
|
473
|
+
const del = this.deps.deleteTempFile ?? defaultDeleteTempFile;
|
|
474
|
+
del(this.testListPath);
|
|
475
|
+
this.testListPath = null;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
buildPlaywrightArgs(ctx, filters, tests) {
|
|
479
|
+
const resolveReporter = this.deps.resolveReporter ?? resolveReporterDefault;
|
|
480
|
+
const reporterModule = resolveReporter(ctx.cwd);
|
|
481
|
+
const useTestList = tests !== void 0 && (tests.length > 1 || this.deps.containerPathMapping !== void 0) && this.supportsTestList(ctx.cwd);
|
|
482
|
+
const args = ["test", "--config", ctx.configFile];
|
|
483
|
+
if (reporterModule !== null) args.push("--reporter", reporterModule);
|
|
484
|
+
if (filters.update === true) args.push("--update-snapshots");
|
|
485
|
+
if (useTestList && tests !== void 0) {
|
|
486
|
+
const content = buildTestListEntries(
|
|
487
|
+
tests,
|
|
488
|
+
ctx.rootDir,
|
|
489
|
+
ctx.cwd,
|
|
490
|
+
this.deps.containerPathMapping === void 0 ? "host" : "posix"
|
|
491
|
+
).join("\n");
|
|
492
|
+
const writeTemp = this.deps.writeTempFile ?? defaultWriteTempFile;
|
|
493
|
+
this.testListPath = writeTemp(content);
|
|
494
|
+
args.push("--test-list", this.testListPath);
|
|
495
|
+
} else if (tests !== void 0 && tests.length > 0) {
|
|
496
|
+
const project = sharedProject(tests);
|
|
497
|
+
if (project !== void 0) args.push(`--project=${project}`);
|
|
498
|
+
for (const d of tests) {
|
|
499
|
+
args.push(d.column === void 0 ? `${d.file}:${d.line}` : `${d.file}:${d.line}:${d.column}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return args;
|
|
503
|
+
}
|
|
504
|
+
start(filters) {
|
|
505
|
+
const ctx = this.deps.getRunContext();
|
|
506
|
+
if (ctx === null) return { ok: false, reason: "no-config" };
|
|
507
|
+
if (this.child !== null) return { ok: false, reason: "already-running" };
|
|
508
|
+
if (filters.tests !== void 0 && filters.tests.length === 0) return { ok: false, reason: "no-tests" };
|
|
509
|
+
if (this.deps.launcher.available === false) return { ok: false, reason: "docker-unavailable" };
|
|
510
|
+
const tests = rewriteContainerTestDescriptors(filters.tests, this.deps.containerPathMapping);
|
|
511
|
+
const args = this.buildPlaywrightArgs(ctx, filters, tests);
|
|
512
|
+
const spec = this.deps.launcher.launch({ ctx, playwrightArgs: args });
|
|
513
|
+
let child;
|
|
514
|
+
try {
|
|
515
|
+
child = this.deps.spawn(spec.cmd, spec.args, { cwd: ctx.cwd, env: spec.env, stdio: "inherit" });
|
|
516
|
+
} catch (err) {
|
|
517
|
+
this.cleanupTempFile();
|
|
518
|
+
throw err;
|
|
519
|
+
}
|
|
520
|
+
this.child = child;
|
|
521
|
+
child.on("exit", (code) => {
|
|
522
|
+
this.handleChildExit(code);
|
|
523
|
+
});
|
|
524
|
+
child.on("error", () => {
|
|
525
|
+
this.handleChildExit(null);
|
|
526
|
+
});
|
|
527
|
+
this.deps.setReportRunning(true);
|
|
528
|
+
this.deps.setRunFiltered?.(filters.tests !== void 0);
|
|
529
|
+
this.deps.broadcast({ type: "run-status", data: { running: true, mode: this.deps.launcher.mode } });
|
|
530
|
+
return { ok: true };
|
|
531
|
+
}
|
|
532
|
+
stop() {
|
|
533
|
+
if (this.child === null) return { ok: false, reason: "not-running" };
|
|
534
|
+
if (this.sigkillTimer !== null) this.deps.timers.clearTimeout(this.sigkillTimer);
|
|
535
|
+
this.child.kill("SIGTERM");
|
|
536
|
+
this.sigkillTimer = this.deps.timers.setTimeout(() => {
|
|
537
|
+
if (this.child !== null) {
|
|
538
|
+
this.child.kill("SIGKILL");
|
|
539
|
+
this.deps.launcher.onForceKill?.();
|
|
540
|
+
}
|
|
541
|
+
}, STOP_GRACE_MS);
|
|
542
|
+
return { ok: true };
|
|
543
|
+
}
|
|
544
|
+
async prepareRun() {
|
|
545
|
+
const launcher = this.deps.launcher;
|
|
546
|
+
if (launcher.prepare === void 0) return { ok: true };
|
|
547
|
+
const ctx = this.deps.getRunContext();
|
|
548
|
+
if (ctx === null) return { ok: true };
|
|
549
|
+
try {
|
|
550
|
+
await launcher.prepare({
|
|
551
|
+
ctx,
|
|
552
|
+
onProgress: (phase) => {
|
|
553
|
+
this.deps.broadcast({ type: "run-status", data: { running: true, mode: launcher.mode, phase } });
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
return { ok: true };
|
|
557
|
+
} catch (error) {
|
|
558
|
+
this.deps.broadcast({ type: "run-status", data: { running: false, mode: launcher.mode } });
|
|
559
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
560
|
+
console.warn(`[RunController] run preparation failed: ${message}`);
|
|
561
|
+
return { ok: false, reason: "docker-unavailable" };
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
dispose() {
|
|
565
|
+
if (this.child === null) return;
|
|
566
|
+
if (this.sigkillTimer !== null) this.deps.timers.clearTimeout(this.sigkillTimer);
|
|
567
|
+
this.sigkillTimer = null;
|
|
568
|
+
this.child.kill("SIGKILL");
|
|
569
|
+
this.deps.launcher.onForceKill?.();
|
|
570
|
+
this.cleanupTempFile();
|
|
571
|
+
}
|
|
572
|
+
handleChildExit(code) {
|
|
573
|
+
if (this.child === null) return;
|
|
574
|
+
if (this.sigkillTimer !== null) this.deps.timers.clearTimeout(this.sigkillTimer);
|
|
575
|
+
this.sigkillTimer = null;
|
|
576
|
+
this.child = null;
|
|
577
|
+
this.cleanupTempFile();
|
|
578
|
+
if (code !== null && code !== 0) console.warn(`[RunController] playwright test exited with code ${code}`);
|
|
579
|
+
this.deps.setReportRunning(false);
|
|
580
|
+
this.deps.broadcast({ type: "run-status", data: { running: false, mode: this.deps.launcher.mode } });
|
|
581
|
+
void this.deps.saveReport?.();
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
function createRealSpawn() {
|
|
585
|
+
return (cmd, args, opts) => {
|
|
586
|
+
const cp = spawn2(cmd, args, opts);
|
|
587
|
+
return {
|
|
588
|
+
on: (event, cb) => cp.on(event, cb),
|
|
589
|
+
kill: (signal) => {
|
|
590
|
+
const sig = KNOWN_SIGNALS[signal];
|
|
591
|
+
if (sig !== void 0) cp.kill(sig);
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function createRealTimers() {
|
|
597
|
+
const pending = [];
|
|
598
|
+
return {
|
|
599
|
+
setTimeout: (fn, ms) => {
|
|
600
|
+
const id = setTimeout(fn, ms);
|
|
601
|
+
pending.push(id);
|
|
602
|
+
return id;
|
|
603
|
+
},
|
|
604
|
+
clearTimeout: () => {
|
|
605
|
+
for (const h of pending.splice(0)) clearTimeout(h);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/server/docker-launcher.ts
|
|
611
|
+
var DOCKER_WORK_DIR = "/work";
|
|
612
|
+
var DOCKER_HOST_GATEWAY = "host.docker.internal";
|
|
613
|
+
var ENV_DENYLIST = /* @__PURE__ */ new Set([
|
|
614
|
+
"CI",
|
|
615
|
+
"PLAYWRIGHT_BROWSERS_PATH",
|
|
616
|
+
"CRVY_RPRTR_SERVER_URL",
|
|
617
|
+
"CRVY_RPRTR_PORTABLE_ARTIFACTS",
|
|
618
|
+
"TZ",
|
|
619
|
+
"LANG",
|
|
620
|
+
"LC_ALL",
|
|
621
|
+
"PLAYWRIGHT_HTML_OPEN",
|
|
622
|
+
"PATH"
|
|
623
|
+
]);
|
|
624
|
+
var WINDOWS_ENV_NOISE = new Set(
|
|
625
|
+
"SYSTEMROOT COMSPEC WINDIR PATHEXT OS PROGRAMFILES PROGRAMFILES(X86) PROGRAMW6432 PROGRAMDATA ALLUSERSPROFILE PUBLIC APPDATA LOCALAPPDATA TEMP TMP USERPROFILE HOMEDRIVE HOMEPATH USERNAME PSMODULEPATH DRIVERDATA NUMBER_OF_PROCESSORS PROCESSOR_ARCHITECTURE PROCESSOR_IDENTIFIER PROCESSOR_LEVEL PROCESSOR_REVISION".split(
|
|
626
|
+
" "
|
|
627
|
+
)
|
|
628
|
+
);
|
|
629
|
+
var DockerUnavailableError = class extends Error {
|
|
630
|
+
constructor() {
|
|
631
|
+
super("Docker daemon is not available");
|
|
632
|
+
this.name = "DockerUnavailableError";
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
function defaultWarn(message) {
|
|
636
|
+
console.warn(`[crvy-rprtr] ${message}`);
|
|
637
|
+
}
|
|
638
|
+
async function detectAgentName(detect2, cwd) {
|
|
639
|
+
const detected = await (detect2 ?? detectProjectAgent)(cwd);
|
|
640
|
+
return detected?.name ?? null;
|
|
641
|
+
}
|
|
642
|
+
async function prepareDocker(state, exec, ctx, deps, onProgress) {
|
|
643
|
+
if (deps.platform === "win32" && !state.warnedWin32) {
|
|
644
|
+
state.warnedWin32 = true;
|
|
645
|
+
deps.warn(
|
|
646
|
+
"Native Windows host detected: docker run mode is experimental on this platform. For CI-identical baselines, run crvy-rprtr from WSL2 with the project stored in the WSL filesystem."
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
if (!await probeDockerDaemon(exec)) {
|
|
650
|
+
state.available = false;
|
|
651
|
+
throw new DockerUnavailableError();
|
|
652
|
+
}
|
|
653
|
+
state.available = true;
|
|
654
|
+
const image = resolveDockerImage({ image: deps.docker?.image, version: deps.getPlaywrightVersion(ctx.cwd) });
|
|
655
|
+
if (image === null) {
|
|
656
|
+
throw new Error("Could not resolve the installed @playwright/test version; set docker.image explicitly.");
|
|
657
|
+
}
|
|
658
|
+
state.image = image;
|
|
659
|
+
state.command = resolveContainerCommand({
|
|
660
|
+
command: deps.docker?.command,
|
|
661
|
+
hasCustomImage: deps.docker?.image !== void 0,
|
|
662
|
+
detectedAgentName: deps.docker?.image === void 0 ? "npm" : await detectAgentName(deps.detectAgent, ctx.cwd),
|
|
663
|
+
warn: deps.warn
|
|
664
|
+
});
|
|
665
|
+
if (!await isDockerImagePresent(exec, image)) {
|
|
666
|
+
onProgress("pulling");
|
|
667
|
+
if (!await pullDockerImage(exec, image)) {
|
|
668
|
+
throw new Error(`Failed to pull docker image: ${image}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function buildDockerRunArgs(ctx, playwrightArgs, deps) {
|
|
673
|
+
const { args: rewrittenArgs, bindMounts } = rewritePlaywrightArgs(playwrightArgs, ctx, deps.workDir, deps.warn);
|
|
674
|
+
const args = [
|
|
675
|
+
"run",
|
|
676
|
+
"--rm",
|
|
677
|
+
"--init",
|
|
678
|
+
"--name",
|
|
679
|
+
deps.containerName,
|
|
680
|
+
"--add-host",
|
|
681
|
+
`${DOCKER_HOST_GATEWAY}:host-gateway`,
|
|
682
|
+
"--ipc=host"
|
|
683
|
+
];
|
|
684
|
+
if (deps.docker?.platform !== void 0) {
|
|
685
|
+
args.push("--platform", deps.docker.platform);
|
|
686
|
+
}
|
|
687
|
+
args.push("-v", `${ctx.cwd}:${deps.workDir}:rw`, "-w", deps.workDir);
|
|
688
|
+
for (const mount of bindMounts) {
|
|
689
|
+
args.push("-v", mount);
|
|
690
|
+
}
|
|
691
|
+
args.push("-e", `CRVY_RPRTR_SERVER_URL=ws://${DOCKER_HOST_GATEWAY}:${deps.port}`);
|
|
692
|
+
args.push("-e", "CRVY_RPRTR_PORTABLE_ARTIFACTS=1", "-e", "TZ=UTC", "-e", "LANG=C.UTF-8", "-e", "LC_ALL=C.UTF-8");
|
|
693
|
+
args.push("-e", "PLAYWRIGHT_HTML_OPEN=never");
|
|
694
|
+
for (const [key, value] of Object.entries(deps.env)) {
|
|
695
|
+
const upper = key.toUpperCase();
|
|
696
|
+
if (ENV_DENYLIST.has(upper) || WINDOWS_ENV_NOISE.has(upper) || value === void 0) continue;
|
|
697
|
+
args.push("-e", key);
|
|
698
|
+
}
|
|
699
|
+
if (deps.docker?.extraArgs !== void 0) args.push(...deps.docker.extraArgs);
|
|
700
|
+
args.push(deps.image, ...deps.command, "playwright", ...rewrittenArgs);
|
|
701
|
+
return args;
|
|
702
|
+
}
|
|
703
|
+
function stripCi(env) {
|
|
704
|
+
const out = {};
|
|
705
|
+
for (const [key, value] of Object.entries(env)) {
|
|
706
|
+
if (key === "CI") continue;
|
|
707
|
+
out[key] = value;
|
|
708
|
+
}
|
|
709
|
+
return out;
|
|
710
|
+
}
|
|
711
|
+
function createState(docker) {
|
|
712
|
+
return {
|
|
713
|
+
available: void 0,
|
|
714
|
+
prepared: null,
|
|
715
|
+
image: null,
|
|
716
|
+
command: docker?.command ?? DEFAULT_CONTAINER_COMMAND,
|
|
717
|
+
warnedWin32: false
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function buildLauncher(state, deps) {
|
|
721
|
+
return {
|
|
722
|
+
mode: "docker",
|
|
723
|
+
get available() {
|
|
724
|
+
return state.available;
|
|
725
|
+
},
|
|
726
|
+
prepare({ ctx, onProgress }) {
|
|
727
|
+
state.prepared ??= prepareDocker(
|
|
728
|
+
state,
|
|
729
|
+
deps.exec,
|
|
730
|
+
ctx,
|
|
731
|
+
{
|
|
732
|
+
docker: deps.docker,
|
|
733
|
+
getPlaywrightVersion: deps.getVersion,
|
|
734
|
+
detectAgent: deps.detectAgent,
|
|
735
|
+
warn: deps.warn,
|
|
736
|
+
platform: deps.platform
|
|
737
|
+
},
|
|
738
|
+
onProgress
|
|
739
|
+
).catch((error) => {
|
|
740
|
+
state.prepared = null;
|
|
741
|
+
state.warnedWin32 = false;
|
|
742
|
+
throw error;
|
|
743
|
+
});
|
|
744
|
+
return state.prepared;
|
|
745
|
+
},
|
|
746
|
+
launch({ ctx, playwrightArgs }) {
|
|
747
|
+
const image = state.image ?? resolveDockerImage({ image: deps.docker?.image, version: deps.getVersion(ctx.cwd) });
|
|
748
|
+
if (image === null) {
|
|
749
|
+
throw new Error("Could not resolve the docker image; run prepare() first or set docker.image.");
|
|
750
|
+
}
|
|
751
|
+
const args = buildDockerRunArgs(ctx, playwrightArgs, {
|
|
752
|
+
docker: deps.docker,
|
|
753
|
+
workDir: deps.workDir,
|
|
754
|
+
containerName: deps.containerName,
|
|
755
|
+
port: deps.port,
|
|
756
|
+
env: deps.baseEnv,
|
|
757
|
+
image,
|
|
758
|
+
command: state.command,
|
|
759
|
+
warn: deps.warn
|
|
760
|
+
});
|
|
761
|
+
return { cmd: "docker", args, env: stripCi(deps.baseEnv) };
|
|
762
|
+
},
|
|
763
|
+
onForceKill() {
|
|
764
|
+
void forceRemoveContainer(deps.exec, deps.containerName);
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function createDockerLauncher(options) {
|
|
769
|
+
return buildLauncher(createState(options.docker), {
|
|
770
|
+
exec: options.exec ?? createDockerExec(),
|
|
771
|
+
workDir: options.workDir ?? DOCKER_WORK_DIR,
|
|
772
|
+
containerName: options.containerName ?? `crvy-rprtr-run-${process.pid}`,
|
|
773
|
+
baseEnv: options.env ?? process.env,
|
|
774
|
+
getVersion: options.getPlaywrightVersion ?? resolvePlaywrightVersion,
|
|
775
|
+
detectAgent: options.detectAgent,
|
|
776
|
+
warn: options.warn ?? defaultWarn,
|
|
777
|
+
platform: options.platform ?? process.platform,
|
|
778
|
+
docker: options.docker,
|
|
779
|
+
port: options.port
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
|
|
211
783
|
// src/server/handlers.ts
|
|
212
784
|
import { existsSync as existsSync2 } from "fs";
|
|
785
|
+
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
213
786
|
|
|
214
787
|
// src/server/artifact-routes.ts
|
|
215
788
|
import { existsSync } from "fs";
|
|
@@ -217,7 +790,7 @@ import { realpath } from "fs/promises";
|
|
|
217
790
|
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
218
791
|
|
|
219
792
|
// src/server/utils.ts
|
|
220
|
-
import { isAbsolute, relative, resolve, sep } from "path";
|
|
793
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve, sep as sep2 } from "path";
|
|
221
794
|
var LIVE_UPDATES_WEBSOCKET_PATH = "/";
|
|
222
795
|
function broadcastToBrowsers(wsClients, msg) {
|
|
223
796
|
const payload = JSON.stringify(msg);
|
|
@@ -231,8 +804,8 @@ function isWebSocketUpgradeRequest(req) {
|
|
|
231
804
|
function isPathWithinRoots(target, roots) {
|
|
232
805
|
const resolvedTarget = resolve(target);
|
|
233
806
|
return roots.some((root) => {
|
|
234
|
-
const rel =
|
|
235
|
-
return rel === "" || !rel.startsWith(`..${
|
|
807
|
+
const rel = relative2(resolve(root), resolvedTarget);
|
|
808
|
+
return rel === "" || !rel.startsWith(`..${sep2}`) && rel !== ".." && !isAbsolute2(rel);
|
|
236
809
|
});
|
|
237
810
|
}
|
|
238
811
|
|
|
@@ -282,11 +855,14 @@ function reporterTitlePath(test) {
|
|
|
282
855
|
return ["", projectName, testFile ?? "", ...test.titlePath, test.title];
|
|
283
856
|
}
|
|
284
857
|
function resolveBaselineSnapshotPath(routing, test, retry, imageName) {
|
|
285
|
-
const
|
|
858
|
+
const mapping = routing?.containerPathMapping;
|
|
859
|
+
const rawTestFile = test.location?.file;
|
|
860
|
+
const testFile = rawTestFile === void 0 || mapping === void 0 ? rawTestFile : rewriteContainerPath(rawTestFile, mapping);
|
|
286
861
|
const declaration = test.results?.[retry]?.visualDeclarations?.find((candidate) => candidate.visualName === imageName);
|
|
287
862
|
if (routing === void 0 || testFile === void 0 || declaration === void 0) {
|
|
288
863
|
return null;
|
|
289
864
|
}
|
|
865
|
+
const isContainerPath = mapping !== void 0 && testFile !== rawTestFile;
|
|
290
866
|
const targets = resolveBaselineTargets({
|
|
291
867
|
testFile,
|
|
292
868
|
reporterTitlePath: reporterTitlePath(test),
|
|
@@ -296,7 +872,7 @@ function resolveBaselineSnapshotPath(routing, test, retry, imageName) {
|
|
|
296
872
|
testDir: routing.playwrightTestDir ?? dirname2(testFile),
|
|
297
873
|
snapshotDir: routing.playwrightSnapshotDir ?? dirname2(testFile),
|
|
298
874
|
projectName: test.projectName ?? test.browser,
|
|
299
|
-
snapshotSuffix: process.platform,
|
|
875
|
+
snapshotSuffix: isContainerPath ? "linux" : process.platform,
|
|
300
876
|
snapshotPathTemplate: routing.playwrightSnapshotPathTemplate,
|
|
301
877
|
toHaveScreenshotPathTemplate: routing.playwrightToHaveScreenshotPathTemplate
|
|
302
878
|
},
|
|
@@ -419,7 +995,19 @@ function handleSync(ctx) {
|
|
|
419
995
|
};
|
|
420
996
|
broadcastToBrowsers(ctx.wsClients, message);
|
|
421
997
|
}
|
|
422
|
-
function
|
|
998
|
+
function applyContainerPathMapping(rawData, mapping) {
|
|
999
|
+
return {
|
|
1000
|
+
...rawData,
|
|
1001
|
+
playwrightSnapshotDir: rawData.playwrightSnapshotDir === void 0 ? void 0 : rewriteContainerPath(rawData.playwrightSnapshotDir, mapping),
|
|
1002
|
+
playwrightTestDir: rawData.playwrightTestDir === void 0 ? void 0 : rewriteContainerPath(rawData.playwrightTestDir, mapping),
|
|
1003
|
+
playwrightRootDir: rawData.playwrightRootDir === void 0 ? void 0 : rewriteContainerPath(rawData.playwrightRootDir, mapping),
|
|
1004
|
+
configFile: rawData.configFile === void 0 ? void 0 : rewriteContainerPath(rawData.configFile, mapping),
|
|
1005
|
+
cwd: rawData.cwd === void 0 ? void 0 : rewriteContainerPath(rawData.cwd, mapping)
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
function handleRegister(ctx, rawData) {
|
|
1009
|
+
const mapping = ctx.routesContext.containerPathMapping;
|
|
1010
|
+
const data = mapping === void 0 ? rawData : applyContainerPathMapping(rawData, mapping);
|
|
423
1011
|
const roots = [];
|
|
424
1012
|
if (data.playwrightSnapshotDir !== void 0 && data.playwrightSnapshotDir !== "") {
|
|
425
1013
|
roots.push(data.playwrightSnapshotDir);
|
|
@@ -447,18 +1035,61 @@ function handleRegister(ctx, data) {
|
|
|
447
1035
|
if (data.playwrightToHaveScreenshotPathTemplate !== void 0) {
|
|
448
1036
|
ctx.routesContext.approvalRouting.playwrightToHaveScreenshotPathTemplate = data.playwrightToHaveScreenshotPathTemplate;
|
|
449
1037
|
}
|
|
450
|
-
}
|
|
451
|
-
if (data.configFile !== void 0 && data.cwd !== void 0) {
|
|
452
|
-
ctx.routesContext.runContext =
|
|
453
|
-
}
|
|
454
|
-
console.log("[Server] Reporter registered with config:", {
|
|
455
|
-
playwrightSnapshotDir: data.playwrightSnapshotDir,
|
|
456
|
-
playwrightTestDir: data.playwrightTestDir
|
|
1038
|
+
}
|
|
1039
|
+
if (data.configFile !== void 0 && data.cwd !== void 0) {
|
|
1040
|
+
ctx.routesContext.runContext = buildRunContext(data.configFile, data);
|
|
1041
|
+
}
|
|
1042
|
+
console.log("[Server] Reporter registered with config:", {
|
|
1043
|
+
playwrightSnapshotDir: data.playwrightSnapshotDir,
|
|
1044
|
+
playwrightTestDir: data.playwrightTestDir,
|
|
1045
|
+
configFile: data.configFile,
|
|
1046
|
+
cwd: data.cwd
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
function buildRunContext(configFile, data) {
|
|
1050
|
+
const configDir = dirname3(configFile);
|
|
1051
|
+
return {
|
|
1052
|
+
configFile,
|
|
1053
|
+
cwd: configDir,
|
|
1054
|
+
rootDir: data.playwrightRootDir ?? (data.playwrightTestDir === void 0 ? configDir : resolve3(configDir, data.playwrightTestDir))
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// src/server/run-mode.ts
|
|
1059
|
+
async function resolveRunMode(options) {
|
|
1060
|
+
if (options.runMode === "local") return "local";
|
|
1061
|
+
if (options.runMode === "docker") return "docker";
|
|
1062
|
+
if (options.isCI) return "local";
|
|
1063
|
+
if (await options.probeDocker()) return "docker";
|
|
1064
|
+
options.warn?.(
|
|
1065
|
+
"Docker daemon unavailable \u2014 running tests locally; screenshots may differ from CI. Use --run-mode local to silence this warning."
|
|
1066
|
+
);
|
|
1067
|
+
return "local";
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// src/server/launcher-resolver.ts
|
|
1071
|
+
async function resolveRunBackend(options) {
|
|
1072
|
+
const dockerExec = createDockerExec();
|
|
1073
|
+
const resolvedRunMode = await resolveRunMode({
|
|
1074
|
+
runMode: options.runMode ?? "auto",
|
|
1075
|
+
isCI: isCI(),
|
|
1076
|
+
probeDocker: () => probeDockerDaemon(dockerExec),
|
|
1077
|
+
warn: (message) => {
|
|
1078
|
+
console.warn(`[crvy-rprtr] ${message}`);
|
|
1079
|
+
}
|
|
457
1080
|
});
|
|
1081
|
+
const launcher = resolvedRunMode === "docker" ? createDockerLauncher({ port: options.port, docker: options.docker, exec: dockerExec }) : createLocalLauncher({ port: options.port });
|
|
1082
|
+
return {
|
|
1083
|
+
launcher,
|
|
1084
|
+
routesContextOptions: {
|
|
1085
|
+
runInfo: { mode: resolvedRunMode },
|
|
1086
|
+
containerPathMapping: resolvedRunMode === "docker" ? { from: DOCKER_WORK_DIR, to: process.cwd() } : void 0
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
458
1089
|
}
|
|
459
1090
|
|
|
460
1091
|
// src/server/playwright-config.ts
|
|
461
|
-
import { join as
|
|
1092
|
+
import { join as join4, resolve as resolve4 } from "path";
|
|
462
1093
|
var CONFIG_FILES = [
|
|
463
1094
|
"playwright.config.ts",
|
|
464
1095
|
"playwright.config.mts",
|
|
@@ -470,12 +1101,15 @@ var CONFIG_FILES = [
|
|
|
470
1101
|
async function resolvePlaywrightConfig(cwd) {
|
|
471
1102
|
const matches = await Promise.all(
|
|
472
1103
|
CONFIG_FILES.map(async (file) => {
|
|
473
|
-
const candidate =
|
|
1104
|
+
const candidate = join4(cwd, file);
|
|
474
1105
|
return await fileExists(candidate) ? candidate : null;
|
|
475
1106
|
})
|
|
476
1107
|
);
|
|
477
1108
|
return matches.find((path) => path !== null) ?? null;
|
|
478
1109
|
}
|
|
1110
|
+
function resolveSeedConfigFile(option, cwd) {
|
|
1111
|
+
return option === void 0 ? resolvePlaywrightConfig(cwd) : Promise.resolve(resolve4(cwd, option));
|
|
1112
|
+
}
|
|
479
1113
|
|
|
480
1114
|
// src/server/report-persistence.ts
|
|
481
1115
|
function createDebouncedSaver(save, delayMs, setTimeoutFn = (fn, ms) => setTimeout(fn, ms), clearTimeoutFn = (handle) => {
|
|
@@ -547,14 +1181,17 @@ function createRoutesContext(reportData, staticDir, saveReport, options) {
|
|
|
547
1181
|
playwrightTestDir: options.playwrightTestDir,
|
|
548
1182
|
playwrightSnapshotDir: options.playwrightSnapshotDir,
|
|
549
1183
|
playwrightSnapshotPathTemplate: options.playwrightSnapshotPathTemplate,
|
|
550
|
-
playwrightToHaveScreenshotPathTemplate: options.playwrightToHaveScreenshotPathTemplate
|
|
1184
|
+
playwrightToHaveScreenshotPathTemplate: options.playwrightToHaveScreenshotPathTemplate,
|
|
1185
|
+
containerPathMapping: options.containerPathMapping
|
|
551
1186
|
},
|
|
552
|
-
runContext: void 0
|
|
1187
|
+
runContext: void 0,
|
|
1188
|
+
runInfo: options.runInfo,
|
|
1189
|
+
containerPathMapping: options.containerPathMapping
|
|
553
1190
|
};
|
|
554
1191
|
}
|
|
555
1192
|
|
|
556
1193
|
// src/server/routes.ts
|
|
557
|
-
import { join as
|
|
1194
|
+
import { join as join5 } from "path";
|
|
558
1195
|
|
|
559
1196
|
// src/server/run-routes.ts
|
|
560
1197
|
function handleRunRoutes(pathname, method, runController, req) {
|
|
@@ -576,6 +1213,10 @@ async function handleApiRun(runController, req) {
|
|
|
576
1213
|
if (parsed === null) {
|
|
577
1214
|
return Response.json({ ok: false, error: "Invalid request body" }, { status: 400 });
|
|
578
1215
|
}
|
|
1216
|
+
const preparation = await runController.prepareRun();
|
|
1217
|
+
if (!preparation.ok) {
|
|
1218
|
+
return Response.json({ ok: false, reason: preparation.reason }, { status: 409 });
|
|
1219
|
+
}
|
|
579
1220
|
const result = runController.start(parsed);
|
|
580
1221
|
if (result.ok) return Response.json(result);
|
|
581
1222
|
const status = result.reason === "no-tests" ? 400 : 409;
|
|
@@ -589,7 +1230,7 @@ function handleApiStop(runController) {
|
|
|
589
1230
|
|
|
590
1231
|
// src/server/routes.ts
|
|
591
1232
|
async function handleRoot(ctx) {
|
|
592
|
-
const html = await respondWithFile(
|
|
1233
|
+
const html = await respondWithFile(join5(ctx.staticDir, "index.html"), "text/html");
|
|
593
1234
|
return html ?? new Response("Not Found", { status: 404 });
|
|
594
1235
|
}
|
|
595
1236
|
async function handleAppCss() {
|
|
@@ -606,13 +1247,14 @@ async function handleSrcFiles(req) {
|
|
|
606
1247
|
function handleApiReport(ctx) {
|
|
607
1248
|
return Response.json({
|
|
608
1249
|
...ctx.reportData,
|
|
609
|
-
runEnabled: ctx.runContext !== void 0
|
|
1250
|
+
runEnabled: ctx.runContext !== void 0,
|
|
1251
|
+
runMode: ctx.runInfo?.mode
|
|
610
1252
|
});
|
|
611
1253
|
}
|
|
612
1254
|
var APPROVAL_TARGET_ERROR = "Could not resolve approval target";
|
|
613
1255
|
function actualPathFromUrl(ctx, actualUrl) {
|
|
614
1256
|
if (actualUrl.startsWith("/screenshots/")) {
|
|
615
|
-
return
|
|
1257
|
+
return join5(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length));
|
|
616
1258
|
}
|
|
617
1259
|
if (actualUrl.startsWith("/file/")) {
|
|
618
1260
|
return decodeURIComponent(actualUrl.slice("/file/".length));
|
|
@@ -734,7 +1376,7 @@ async function handleScreenshots(ctx, req) {
|
|
|
734
1376
|
}
|
|
735
1377
|
async function handleDist(ctx, req) {
|
|
736
1378
|
const path = new URL(req.url).pathname.slice("/dist/".length);
|
|
737
|
-
const filePath =
|
|
1379
|
+
const filePath = join5(ctx.staticDir, path);
|
|
738
1380
|
const contentType = filePath.endsWith(".css") ? "text/css" : filePath.endsWith(".js") ? "application/javascript" : filePath.endsWith(".svelte") ? "text/plain" : "application/octet-stream";
|
|
739
1381
|
const file = await respondWithFile(filePath, contentType);
|
|
740
1382
|
return file ?? new Response("Not Found", { status: 404 });
|
|
@@ -779,216 +1421,56 @@ function handleHttpRequest(ctx, req, runController) {
|
|
|
779
1421
|
return Promise.resolve(new Response("Not Found", { status: 404 }));
|
|
780
1422
|
}
|
|
781
1423
|
|
|
782
|
-
// src/server/
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
return { cmd: "npx", args: ["playwright", ...playwrightArgs] };
|
|
800
|
-
}
|
|
801
|
-
function descriptorLocation(d) {
|
|
802
|
-
return d.column === void 0 ? `${d.file}:${d.line}` : `${d.file}:${d.line}:${d.column}`;
|
|
803
|
-
}
|
|
804
|
-
function sharedProject(tests) {
|
|
805
|
-
const names = new Set(tests.map((t) => t.projectName ?? ""));
|
|
806
|
-
if (names.size === 1) {
|
|
807
|
-
const name = [...names][0];
|
|
808
|
-
return name === "" ? void 0 : name;
|
|
809
|
-
}
|
|
810
|
-
return void 0;
|
|
811
|
-
}
|
|
812
|
-
function gteMinor(version, major, minor) {
|
|
813
|
-
const match = /^(\d+)\.(\d+)/.exec(version.trim());
|
|
814
|
-
if (match === null) return false;
|
|
815
|
-
const maj = parseInt(match[1], 10);
|
|
816
|
-
const min = parseInt(match[2], 10);
|
|
817
|
-
if (maj !== major) return maj > major;
|
|
818
|
-
return min >= minor;
|
|
819
|
-
}
|
|
820
|
-
function resolvePlaywrightVersion(cwd) {
|
|
821
|
-
try {
|
|
822
|
-
const req = createRequire(join4(cwd, "package.json"));
|
|
823
|
-
const pkgPath = req.resolve("@playwright/test/package.json");
|
|
824
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
825
|
-
return typeof pkg === "object" && pkg !== null && "version" in pkg && typeof pkg.version === "string" ? pkg.version : null;
|
|
826
|
-
} catch {
|
|
827
|
-
return null;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
function buildTestListEntries(tests) {
|
|
831
|
-
return tests.map((d) => {
|
|
832
|
-
const loc = descriptorLocation(d);
|
|
833
|
-
const title = d.titlePath.join(" \u203A ");
|
|
834
|
-
const prefix = d.projectName !== void 0 && d.projectName !== "" ? `[${d.projectName}] \u203A ` : "";
|
|
835
|
-
return `${prefix}${loc} \u203A ${title}`;
|
|
1424
|
+
// src/server/server-factories.ts
|
|
1425
|
+
function createServerRunController(routesContext, wsClients, reportData, port, setRunFiltered, saveReport, launcher) {
|
|
1426
|
+
return new RunController({
|
|
1427
|
+
getRunContext: () => routesContext.runContext ?? null,
|
|
1428
|
+
port,
|
|
1429
|
+
broadcast: (message) => {
|
|
1430
|
+
broadcastToBrowsers(wsClients, message);
|
|
1431
|
+
},
|
|
1432
|
+
setReportRunning: (running) => {
|
|
1433
|
+
reportData.isRunning = running;
|
|
1434
|
+
},
|
|
1435
|
+
setRunFiltered,
|
|
1436
|
+
containerPathMapping: routesContext.containerPathMapping,
|
|
1437
|
+
saveReport,
|
|
1438
|
+
spawn: createRealSpawn(),
|
|
1439
|
+
timers: createRealTimers(),
|
|
1440
|
+
launcher
|
|
836
1441
|
});
|
|
837
1442
|
}
|
|
838
|
-
function
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
function defaultDeleteTempFile(path) {
|
|
844
|
-
try {
|
|
845
|
-
unlinkSync(path);
|
|
846
|
-
} catch {
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
function resolveReporterDefault(cwd) {
|
|
850
|
-
try {
|
|
851
|
-
return createRequire(join4(cwd, "package.json")).resolve("@crvy/rprtr");
|
|
852
|
-
} catch {
|
|
853
|
-
}
|
|
854
|
-
try {
|
|
855
|
-
return createRequire(import.meta.url).resolve("@crvy/rprtr");
|
|
856
|
-
} catch {
|
|
857
|
-
return null;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
function buildSpawnEnv(port) {
|
|
861
|
-
const env = {};
|
|
862
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
863
|
-
if (key === "CI") continue;
|
|
864
|
-
env[key] = value;
|
|
865
|
-
}
|
|
866
|
-
env.CRVY_RPRTR_SERVER_URL = `ws://localhost:${port}`;
|
|
867
|
-
env.PLAYWRIGHT_HTML_OPEN = "never";
|
|
868
|
-
return env;
|
|
869
|
-
}
|
|
870
|
-
var RunController = class {
|
|
871
|
-
constructor(deps) {
|
|
872
|
-
this.deps = deps;
|
|
873
|
-
}
|
|
874
|
-
child = null;
|
|
875
|
-
sigkillTimer = null;
|
|
876
|
-
testListPath = null;
|
|
877
|
-
get isRunning() {
|
|
878
|
-
return this.child !== null;
|
|
879
|
-
}
|
|
880
|
-
supportsTestList(cwd) {
|
|
881
|
-
const getVersion = this.deps.getPlaywrightVersion ?? resolvePlaywrightVersion;
|
|
882
|
-
const version = getVersion(cwd);
|
|
883
|
-
return version !== null && gteMinor(version, 1, 56);
|
|
884
|
-
}
|
|
885
|
-
cleanupTempFile() {
|
|
886
|
-
if (this.testListPath !== null) {
|
|
887
|
-
const del = this.deps.deleteTempFile ?? defaultDeleteTempFile;
|
|
888
|
-
del(this.testListPath);
|
|
889
|
-
this.testListPath = null;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
start(filters) {
|
|
893
|
-
const ctx = this.deps.getRunContext();
|
|
894
|
-
if (ctx === null) return { ok: false, reason: "no-config" };
|
|
895
|
-
if (this.child !== null) return { ok: false, reason: "already-running" };
|
|
896
|
-
if (filters.tests !== void 0 && filters.tests.length === 0) {
|
|
897
|
-
return { ok: false, reason: "no-tests" };
|
|
898
|
-
}
|
|
899
|
-
const resolveReporter = this.deps.resolveReporter ?? resolveReporterDefault;
|
|
900
|
-
const reporterModule = resolveReporter(ctx.cwd);
|
|
901
|
-
const tests = filters.tests;
|
|
902
|
-
const useTestList = tests !== void 0 && tests.length > 1 && this.supportsTestList(ctx.cwd);
|
|
903
|
-
const args = ["test", "--config", ctx.configFile];
|
|
904
|
-
if (reporterModule !== null) args.push("--reporter", reporterModule);
|
|
905
|
-
if (useTestList && tests !== void 0) {
|
|
906
|
-
const content = buildTestListEntries(tests).join("\n");
|
|
907
|
-
const writeTemp = this.deps.writeTempFile ?? defaultWriteTempFile;
|
|
908
|
-
this.testListPath = writeTemp(content);
|
|
909
|
-
args.push("--test-list", this.testListPath);
|
|
910
|
-
} else if (tests !== void 0 && tests.length > 0) {
|
|
911
|
-
const project = sharedProject(tests);
|
|
912
|
-
if (project !== void 0) args.push("--project", project);
|
|
913
|
-
for (const d of tests) args.push(descriptorLocation(d));
|
|
914
|
-
}
|
|
915
|
-
const resolveLaunch = this.deps.resolveLaunch ?? resolvePlaywrightLaunch;
|
|
916
|
-
const { cmd, args: launchArgs } = resolveLaunch(ctx.cwd, args);
|
|
917
|
-
let child;
|
|
918
|
-
try {
|
|
919
|
-
child = this.deps.spawn(cmd, launchArgs, { cwd: ctx.cwd, env: buildSpawnEnv(this.deps.port), stdio: "inherit" });
|
|
920
|
-
} catch (err) {
|
|
921
|
-
this.cleanupTempFile();
|
|
922
|
-
throw err;
|
|
923
|
-
}
|
|
924
|
-
this.child = child;
|
|
925
|
-
child.on("exit", (code) => {
|
|
926
|
-
this.handleChildExit(code);
|
|
927
|
-
});
|
|
928
|
-
child.on("error", () => {
|
|
929
|
-
this.handleChildExit(null);
|
|
930
|
-
});
|
|
931
|
-
this.deps.setReportRunning(true);
|
|
932
|
-
this.deps.setRunFiltered?.(filters.tests !== void 0);
|
|
933
|
-
this.deps.broadcast({ type: "run-status", data: { running: true } });
|
|
934
|
-
return { ok: true };
|
|
935
|
-
}
|
|
936
|
-
stop() {
|
|
937
|
-
if (this.child === null) return { ok: false, reason: "not-running" };
|
|
938
|
-
if (this.sigkillTimer !== null) this.deps.timers.clearTimeout(this.sigkillTimer);
|
|
939
|
-
this.child.kill("SIGTERM");
|
|
940
|
-
this.sigkillTimer = this.deps.timers.setTimeout(() => {
|
|
941
|
-
if (this.child !== null) this.child.kill("SIGKILL");
|
|
942
|
-
}, STOP_GRACE_MS);
|
|
943
|
-
return { ok: true };
|
|
944
|
-
}
|
|
945
|
-
dispose() {
|
|
946
|
-
if (this.child === null) return;
|
|
947
|
-
if (this.sigkillTimer !== null) this.deps.timers.clearTimeout(this.sigkillTimer);
|
|
948
|
-
this.sigkillTimer = null;
|
|
949
|
-
this.child.kill("SIGKILL");
|
|
950
|
-
this.cleanupTempFile();
|
|
951
|
-
}
|
|
952
|
-
handleChildExit(code) {
|
|
953
|
-
if (this.child === null) return;
|
|
954
|
-
if (this.sigkillTimer !== null) {
|
|
955
|
-
this.deps.timers.clearTimeout(this.sigkillTimer);
|
|
956
|
-
this.sigkillTimer = null;
|
|
957
|
-
}
|
|
958
|
-
this.child = null;
|
|
959
|
-
this.cleanupTempFile();
|
|
960
|
-
if (code !== null && code !== 0) {
|
|
961
|
-
console.warn(`[RunController] playwright test exited with code ${code}`);
|
|
962
|
-
}
|
|
963
|
-
this.deps.setReportRunning(false);
|
|
964
|
-
this.deps.broadcast({ type: "run-status", data: { running: false } });
|
|
965
|
-
void this.deps.saveReport?.();
|
|
966
|
-
}
|
|
967
|
-
};
|
|
968
|
-
function createRealSpawn() {
|
|
969
|
-
return (cmd, args, opts) => {
|
|
970
|
-
const cp = spawn(cmd, args, opts);
|
|
971
|
-
return {
|
|
972
|
-
on: (event, cb) => cp.on(event, cb),
|
|
973
|
-
kill: (signal) => {
|
|
974
|
-
const sig = KNOWN_SIGNALS[signal];
|
|
975
|
-
if (sig !== void 0) cp.kill(sig);
|
|
976
|
-
}
|
|
977
|
-
};
|
|
1443
|
+
function createCloseHandler(persistence, runController) {
|
|
1444
|
+
return async () => {
|
|
1445
|
+
await persistence.dispose();
|
|
1446
|
+
runController.dispose();
|
|
978
1447
|
};
|
|
979
1448
|
}
|
|
980
|
-
function
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1449
|
+
function createRunControllerAndHandlers(routesContext, wsClients, reportData, currentRunIds, port, persistence, launcher) {
|
|
1450
|
+
let isFilteredRun = false;
|
|
1451
|
+
const runController = createServerRunController(
|
|
1452
|
+
routesContext,
|
|
1453
|
+
wsClients,
|
|
1454
|
+
reportData,
|
|
1455
|
+
port,
|
|
1456
|
+
(filtered) => {
|
|
1457
|
+
isFilteredRun = filtered;
|
|
987
1458
|
},
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1459
|
+
persistence.saveReport,
|
|
1460
|
+
launcher
|
|
1461
|
+
);
|
|
1462
|
+
const getHandlerContext = () => ({
|
|
1463
|
+
reportData,
|
|
1464
|
+
wsClients,
|
|
1465
|
+
currentRunIds,
|
|
1466
|
+
isFilteredRun,
|
|
1467
|
+
saveReport: persistence.saveReport,
|
|
1468
|
+
scheduleReportSave: persistence.scheduleReportSave,
|
|
1469
|
+
approvalRouting: routesContext.approvalRouting,
|
|
1470
|
+
routesContext,
|
|
1471
|
+
runController
|
|
1472
|
+
});
|
|
1473
|
+
return { runController, getHandlerContext };
|
|
992
1474
|
}
|
|
993
1475
|
|
|
994
1476
|
// src/server/app.ts
|
|
@@ -1080,19 +1562,19 @@ function createWebSocketMessageHandler(getHandlerContext) {
|
|
|
1080
1562
|
};
|
|
1081
1563
|
}
|
|
1082
1564
|
async function resolveStaticDir(staticDir) {
|
|
1083
|
-
const currentDir =
|
|
1565
|
+
const currentDir = dirname4(fileURLToPath(import.meta.url));
|
|
1084
1566
|
const candidates = staticDir === void 0 ? [
|
|
1085
1567
|
currentDir,
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
] : [staticDir,
|
|
1568
|
+
join6(currentDir, "dist"),
|
|
1569
|
+
join6(currentDir, "..", "dist"),
|
|
1570
|
+
join6(currentDir, "..", "..", "dist"),
|
|
1571
|
+
join6(currentDir, ".."),
|
|
1572
|
+
join6(currentDir, "..", "..")
|
|
1573
|
+
] : [staticDir, join6(staticDir, "dist")];
|
|
1092
1574
|
const resolvedCandidates = await Promise.all(
|
|
1093
1575
|
candidates.map(async (candidate) => ({
|
|
1094
1576
|
candidate,
|
|
1095
|
-
exists: await fileExists(
|
|
1577
|
+
exists: await fileExists(join6(candidate, "index.html"))
|
|
1096
1578
|
}))
|
|
1097
1579
|
);
|
|
1098
1580
|
const resolved = resolvedCandidates.find(({ exists }) => exists);
|
|
@@ -1103,34 +1585,31 @@ async function resolveStaticDir(staticDir) {
|
|
|
1103
1585
|
}
|
|
1104
1586
|
async function resolveReportPath(reportPath) {
|
|
1105
1587
|
if (await isDirectory(reportPath)) {
|
|
1106
|
-
return { reportFile:
|
|
1588
|
+
return { reportFile: join6(reportPath, "report.json"), offlineReportDir: reportPath };
|
|
1107
1589
|
}
|
|
1108
|
-
return { reportFile: reportPath, offlineReportDir:
|
|
1590
|
+
return { reportFile: reportPath, offlineReportDir: dirname4(reportPath) };
|
|
1109
1591
|
}
|
|
1110
1592
|
async function seedRunContext(routesContext, options) {
|
|
1111
1593
|
if (routesContext.runContext !== void 0) {
|
|
1112
1594
|
return;
|
|
1113
1595
|
}
|
|
1114
|
-
const configFile = options.playwrightConfig
|
|
1596
|
+
const configFile = await resolveSeedConfigFile(options.playwrightConfig, process.cwd());
|
|
1115
1597
|
if (configFile !== null) {
|
|
1116
1598
|
routesContext.runContext = { configFile, cwd: process.cwd() };
|
|
1117
1599
|
}
|
|
1118
1600
|
}
|
|
1119
|
-
function
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
},
|
|
1129
|
-
setRunFiltered,
|
|
1130
|
-
saveReport,
|
|
1131
|
-
spawn: createRealSpawn(),
|
|
1132
|
-
timers: createRealTimers()
|
|
1601
|
+
async function setupRoutesContext(options, reportData, staticDir, saveReport, port) {
|
|
1602
|
+
const { launcher, routesContextOptions } = await resolveRunBackend({
|
|
1603
|
+
runMode: options.runMode,
|
|
1604
|
+
docker: options.docker,
|
|
1605
|
+
port
|
|
1606
|
+
});
|
|
1607
|
+
const routesContext = createRoutesContext(reportData, staticDir, saveReport, {
|
|
1608
|
+
...options,
|
|
1609
|
+
...routesContextOptions
|
|
1133
1610
|
});
|
|
1611
|
+
await seedRunContext(routesContext, options);
|
|
1612
|
+
return { routesContext, launcher };
|
|
1134
1613
|
}
|
|
1135
1614
|
async function createServerApp(options = {}) {
|
|
1136
1615
|
const port = options.port ?? 3e3;
|
|
@@ -1141,30 +1620,22 @@ async function createServerApp(options = {}) {
|
|
|
1141
1620
|
const wsClients = /* @__PURE__ */ new Set();
|
|
1142
1621
|
const currentRunIds = /* @__PURE__ */ new Set();
|
|
1143
1622
|
const persistence = createReportPersistence(reportFile, reportData);
|
|
1144
|
-
const routesContext
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1623
|
+
const { routesContext, launcher } = await setupRoutesContext(
|
|
1624
|
+
options,
|
|
1625
|
+
reportData,
|
|
1626
|
+
staticDir,
|
|
1627
|
+
persistence.saveReport,
|
|
1628
|
+
port
|
|
1629
|
+
);
|
|
1630
|
+
const { runController, getHandlerContext } = createRunControllerAndHandlers(
|
|
1148
1631
|
routesContext,
|
|
1149
1632
|
wsClients,
|
|
1150
1633
|
reportData,
|
|
1634
|
+
currentRunIds,
|
|
1151
1635
|
port,
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
},
|
|
1155
|
-
persistence.saveReport
|
|
1636
|
+
persistence,
|
|
1637
|
+
launcher
|
|
1156
1638
|
);
|
|
1157
|
-
const getHandlerContext = () => ({
|
|
1158
|
-
reportData,
|
|
1159
|
-
wsClients,
|
|
1160
|
-
currentRunIds,
|
|
1161
|
-
isFilteredRun,
|
|
1162
|
-
saveReport: persistence.saveReport,
|
|
1163
|
-
scheduleReportSave: persistence.scheduleReportSave,
|
|
1164
|
-
approvalRouting: routesContext.approvalRouting,
|
|
1165
|
-
routesContext,
|
|
1166
|
-
runController
|
|
1167
|
-
});
|
|
1168
1639
|
const handleRequest = (req) => handleHttpRequest(routesContext, req, runController);
|
|
1169
1640
|
const handleWebSocketMessage = createWebSocketMessageHandler(getHandlerContext);
|
|
1170
1641
|
await loadReport(reportFile, reportData);
|
|
@@ -1177,12 +1648,6 @@ async function createServerApp(options = {}) {
|
|
|
1177
1648
|
handleWebSocketMessage
|
|
1178
1649
|
};
|
|
1179
1650
|
}
|
|
1180
|
-
function createCloseHandler(persistence, runController) {
|
|
1181
|
-
return async () => {
|
|
1182
|
-
await persistence.dispose();
|
|
1183
|
-
runController.dispose();
|
|
1184
|
-
};
|
|
1185
|
-
}
|
|
1186
1651
|
|
|
1187
1652
|
// src/server/bun-adapter.ts
|
|
1188
1653
|
function logWebSocketError(prefix, error) {
|
|
@@ -1359,7 +1824,7 @@ function attachWebSocketServer(server, app) {
|
|
|
1359
1824
|
});
|
|
1360
1825
|
}
|
|
1361
1826
|
async function listen(server, port) {
|
|
1362
|
-
await new Promise((
|
|
1827
|
+
await new Promise((resolve5, reject) => {
|
|
1363
1828
|
const onError = (error) => {
|
|
1364
1829
|
server.off("error", onError);
|
|
1365
1830
|
reject(error);
|
|
@@ -1367,7 +1832,7 @@ async function listen(server, port) {
|
|
|
1367
1832
|
server.on("error", onError);
|
|
1368
1833
|
server.listen(port, () => {
|
|
1369
1834
|
server.off("error", onError);
|
|
1370
|
-
|
|
1835
|
+
resolve5();
|
|
1371
1836
|
});
|
|
1372
1837
|
});
|
|
1373
1838
|
}
|