opencroc 1.6.0 → 1.6.2
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/dist/cli/index.js +328 -49
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -4206,12 +4206,17 @@ function registerAgentRoutes(app, office) {
|
|
|
4206
4206
|
duration: result.duration
|
|
4207
4207
|
};
|
|
4208
4208
|
});
|
|
4209
|
-
app.post("/api/run-tests", async (
|
|
4209
|
+
app.post("/api/run-tests", async (req, reply) => {
|
|
4210
4210
|
if (office.isRunning()) {
|
|
4211
4211
|
reply.code(409).send({ error: "A task is already running" });
|
|
4212
4212
|
return;
|
|
4213
4213
|
}
|
|
4214
|
-
|
|
4214
|
+
const mode = req.body?.mode;
|
|
4215
|
+
if (mode && !["auto", "reuse", "managed"].includes(mode)) {
|
|
4216
|
+
reply.code(400).send({ error: "Invalid mode. Valid values: auto, reuse, managed" });
|
|
4217
|
+
return;
|
|
4218
|
+
}
|
|
4219
|
+
office.runTests({ mode }).catch(() => {
|
|
4215
4220
|
});
|
|
4216
4221
|
return { ok: true, message: "Test execution started" };
|
|
4217
4222
|
});
|
|
@@ -4268,6 +4273,264 @@ var init_agents = __esm({
|
|
|
4268
4273
|
}
|
|
4269
4274
|
});
|
|
4270
4275
|
|
|
4276
|
+
// src/execution/coordinator.ts
|
|
4277
|
+
var coordinator_exports = {};
|
|
4278
|
+
__export(coordinator_exports, {
|
|
4279
|
+
createExecutionCoordinator: () => createExecutionCoordinator
|
|
4280
|
+
});
|
|
4281
|
+
import { execSync as nodeExecSync } from "child_process";
|
|
4282
|
+
function parseMetrics(output) {
|
|
4283
|
+
const metrics = { passed: 0, failed: 0, skipped: 0, timedOut: 0 };
|
|
4284
|
+
const passedMatch = output.match(/(\d+)\s+passed/);
|
|
4285
|
+
const failedMatch = output.match(/(\d+)\s+failed/);
|
|
4286
|
+
const skippedMatch = output.match(/(\d+)\s+skipped/);
|
|
4287
|
+
const timedOutMatch = output.match(/(\d+)\s+timed?\s*out/i);
|
|
4288
|
+
if (passedMatch) metrics.passed = parseInt(passedMatch[1], 10);
|
|
4289
|
+
if (failedMatch) metrics.failed = parseInt(failedMatch[1], 10);
|
|
4290
|
+
if (skippedMatch) metrics.skipped = parseInt(skippedMatch[1], 10);
|
|
4291
|
+
if (timedOutMatch) metrics.timedOut = parseInt(timedOutMatch[1], 10);
|
|
4292
|
+
return metrics;
|
|
4293
|
+
}
|
|
4294
|
+
function getFailureLines(output) {
|
|
4295
|
+
return output.split(/\r?\n/).filter((line) => /fail|error|timeout/i.test(line)).slice(0, 5);
|
|
4296
|
+
}
|
|
4297
|
+
function createExecutionCoordinator(deps = {}) {
|
|
4298
|
+
const execSync = deps.execSync ?? nodeExecSync;
|
|
4299
|
+
const categorizeFailure2 = deps.categorizeFailure;
|
|
4300
|
+
return {
|
|
4301
|
+
async run(request) {
|
|
4302
|
+
const mode = request.mode ?? "auto";
|
|
4303
|
+
const timeoutMs = request.timeoutMs ?? 3e5;
|
|
4304
|
+
const command = `npx playwright test ${request.testFiles.map((file) => `"${file}"`).join(" ")} --reporter=line 2>&1`;
|
|
4305
|
+
let output;
|
|
4306
|
+
try {
|
|
4307
|
+
output = String(execSync(command, {
|
|
4308
|
+
cwd: request.cwd,
|
|
4309
|
+
encoding: "utf-8",
|
|
4310
|
+
timeout: timeoutMs,
|
|
4311
|
+
stdio: "pipe"
|
|
4312
|
+
}));
|
|
4313
|
+
} catch (err) {
|
|
4314
|
+
const execErr = err;
|
|
4315
|
+
output = `${execErr.stdout || ""}
|
|
4316
|
+
${execErr.stderr || ""}`;
|
|
4317
|
+
}
|
|
4318
|
+
output = output.trim();
|
|
4319
|
+
const metrics = parseMetrics(output);
|
|
4320
|
+
if (metrics.passed === 0 && metrics.failed === 0 && metrics.skipped === 0 && metrics.timedOut === 0) {
|
|
4321
|
+
metrics.failed = request.testFiles.length;
|
|
4322
|
+
}
|
|
4323
|
+
const failureHints = getFailureLines(output).map((line) => {
|
|
4324
|
+
const analyzed = categorizeFailure2 ? categorizeFailure2(line) : { category: "unknown", confidence: 0.5 };
|
|
4325
|
+
return {
|
|
4326
|
+
line,
|
|
4327
|
+
category: analyzed.category,
|
|
4328
|
+
confidence: analyzed.confidence
|
|
4329
|
+
};
|
|
4330
|
+
});
|
|
4331
|
+
return {
|
|
4332
|
+
mode,
|
|
4333
|
+
metrics,
|
|
4334
|
+
output,
|
|
4335
|
+
failureHints
|
|
4336
|
+
};
|
|
4337
|
+
}
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
var init_coordinator = __esm({
|
|
4341
|
+
"src/execution/coordinator.ts"() {
|
|
4342
|
+
"use strict";
|
|
4343
|
+
init_esm_shims();
|
|
4344
|
+
}
|
|
4345
|
+
});
|
|
4346
|
+
|
|
4347
|
+
// src/runtime/resilient-fetch.ts
|
|
4348
|
+
function sleep(ms) {
|
|
4349
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
4350
|
+
}
|
|
4351
|
+
async function waitForBackend(baseUrl, options = {}) {
|
|
4352
|
+
const { timeoutMs = 3e4, intervalMs = 1e3, healthPath = "/health" } = options;
|
|
4353
|
+
const healthUrl = new URL(healthPath, baseUrl).href;
|
|
4354
|
+
const start = Date.now();
|
|
4355
|
+
while (Date.now() - start < timeoutMs) {
|
|
4356
|
+
try {
|
|
4357
|
+
const resp = await fetch(healthUrl, { method: "GET", signal: AbortSignal.timeout(3e3) });
|
|
4358
|
+
if (resp.ok) return;
|
|
4359
|
+
} catch {
|
|
4360
|
+
}
|
|
4361
|
+
await sleep(intervalMs);
|
|
4362
|
+
}
|
|
4363
|
+
throw new Error(`Backend not ready: ${healthUrl} timed out after ${timeoutMs}ms`);
|
|
4364
|
+
}
|
|
4365
|
+
var init_resilient_fetch = __esm({
|
|
4366
|
+
"src/runtime/resilient-fetch.ts"() {
|
|
4367
|
+
"use strict";
|
|
4368
|
+
init_esm_shims();
|
|
4369
|
+
}
|
|
4370
|
+
});
|
|
4371
|
+
|
|
4372
|
+
// src/execution/backend-manager.ts
|
|
4373
|
+
var backend_manager_exports = {};
|
|
4374
|
+
__export(backend_manager_exports, {
|
|
4375
|
+
createBackendManager: () => createBackendManager
|
|
4376
|
+
});
|
|
4377
|
+
import { resolve as resolve8 } from "path";
|
|
4378
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
4379
|
+
function normalizeHealthUrl(server, baseURL) {
|
|
4380
|
+
if (server?.healthUrl) return server.healthUrl;
|
|
4381
|
+
if (baseURL) return new URL("/health", baseURL).href;
|
|
4382
|
+
return "http://localhost:3000/health";
|
|
4383
|
+
}
|
|
4384
|
+
function splitHealthUrl(healthUrl) {
|
|
4385
|
+
const parsed = new URL(healthUrl);
|
|
4386
|
+
const baseUrl = `${parsed.protocol}//${parsed.host}`;
|
|
4387
|
+
const healthPath = `${parsed.pathname}${parsed.search}`;
|
|
4388
|
+
return { baseUrl, healthPath };
|
|
4389
|
+
}
|
|
4390
|
+
async function waitReady(waitForBackend2, healthUrl, timeoutMs, intervalMs) {
|
|
4391
|
+
const { baseUrl, healthPath } = splitHealthUrl(healthUrl);
|
|
4392
|
+
await waitForBackend2(baseUrl, {
|
|
4393
|
+
timeoutMs,
|
|
4394
|
+
intervalMs,
|
|
4395
|
+
healthPath
|
|
4396
|
+
});
|
|
4397
|
+
}
|
|
4398
|
+
function createBackendManager(deps = {}) {
|
|
4399
|
+
const waitForBackend2 = deps.waitForBackend ?? waitForBackend;
|
|
4400
|
+
const spawn = deps.spawn ?? nodeSpawn;
|
|
4401
|
+
return {
|
|
4402
|
+
async ensureReady(request) {
|
|
4403
|
+
const server = request.server ?? {};
|
|
4404
|
+
const healthUrl = normalizeHealthUrl(server, request.baseURL);
|
|
4405
|
+
const quickTimeoutMs = 1500;
|
|
4406
|
+
const startTimeoutMs = server.startTimeoutMs ?? 3e4;
|
|
4407
|
+
const pollIntervalMs = server.pollIntervalMs ?? 800;
|
|
4408
|
+
const tryReuse = request.mode !== "managed" || server.reuseExisting !== false;
|
|
4409
|
+
if (tryReuse) {
|
|
4410
|
+
try {
|
|
4411
|
+
await waitReady(waitForBackend2, healthUrl, quickTimeoutMs, 300);
|
|
4412
|
+
return {
|
|
4413
|
+
mode: request.mode,
|
|
4414
|
+
status: "reused",
|
|
4415
|
+
healthUrl,
|
|
4416
|
+
cleanup: async () => {
|
|
4417
|
+
}
|
|
4418
|
+
};
|
|
4419
|
+
} catch (err) {
|
|
4420
|
+
void err;
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
if (request.mode === "reuse") {
|
|
4424
|
+
throw new Error(`HEALTH_FAIL: backend is not reachable at ${healthUrl} in reuse mode`);
|
|
4425
|
+
}
|
|
4426
|
+
if (!server.command) {
|
|
4427
|
+
if (request.mode === "auto") {
|
|
4428
|
+
return {
|
|
4429
|
+
mode: request.mode,
|
|
4430
|
+
status: "skipped",
|
|
4431
|
+
healthUrl,
|
|
4432
|
+
cleanup: async () => {
|
|
4433
|
+
}
|
|
4434
|
+
};
|
|
4435
|
+
}
|
|
4436
|
+
throw new Error("BOOT_CONFIG_MISSING: runtime.server.command is required for managed mode");
|
|
4437
|
+
}
|
|
4438
|
+
const child = spawn(server.command, server.args ?? [], {
|
|
4439
|
+
cwd: resolve8(request.cwd, server.cwd ?? "."),
|
|
4440
|
+
shell: true,
|
|
4441
|
+
stdio: "pipe",
|
|
4442
|
+
env: process.env
|
|
4443
|
+
});
|
|
4444
|
+
try {
|
|
4445
|
+
await waitReady(waitForBackend2, healthUrl, startTimeoutMs, pollIntervalMs);
|
|
4446
|
+
} catch {
|
|
4447
|
+
if (child.exitCode === null) child.kill();
|
|
4448
|
+
throw new Error(`BOOT_TIMEOUT: backend did not become healthy at ${healthUrl}`);
|
|
4449
|
+
}
|
|
4450
|
+
return {
|
|
4451
|
+
mode: request.mode,
|
|
4452
|
+
status: "started",
|
|
4453
|
+
healthUrl,
|
|
4454
|
+
cleanup: async () => {
|
|
4455
|
+
if (child.exitCode === null) child.kill();
|
|
4456
|
+
}
|
|
4457
|
+
};
|
|
4458
|
+
}
|
|
4459
|
+
};
|
|
4460
|
+
}
|
|
4461
|
+
var init_backend_manager = __esm({
|
|
4462
|
+
"src/execution/backend-manager.ts"() {
|
|
4463
|
+
"use strict";
|
|
4464
|
+
init_esm_shims();
|
|
4465
|
+
init_resilient_fetch();
|
|
4466
|
+
}
|
|
4467
|
+
});
|
|
4468
|
+
|
|
4469
|
+
// src/execution/runtime-bootstrap.ts
|
|
4470
|
+
var runtime_bootstrap_exports = {};
|
|
4471
|
+
__export(runtime_bootstrap_exports, {
|
|
4472
|
+
createRuntimeBootstrap: () => createRuntimeBootstrap
|
|
4473
|
+
});
|
|
4474
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
4475
|
+
import { dirname as dirname5, join as join13 } from "path";
|
|
4476
|
+
function ensureFile(filePath, content, force) {
|
|
4477
|
+
if (existsSync15(filePath) && !force) {
|
|
4478
|
+
return false;
|
|
4479
|
+
}
|
|
4480
|
+
mkdirSync10(dirname5(filePath), { recursive: true });
|
|
4481
|
+
writeFileSync10(filePath, content, "utf-8");
|
|
4482
|
+
return true;
|
|
4483
|
+
}
|
|
4484
|
+
function createRuntimeBootstrap(config) {
|
|
4485
|
+
return {
|
|
4486
|
+
async ensure(request) {
|
|
4487
|
+
const force = request.force ?? false;
|
|
4488
|
+
const files = [
|
|
4489
|
+
{
|
|
4490
|
+
name: "playwright.config.ts",
|
|
4491
|
+
content: generatePlaywrightConfig(config)
|
|
4492
|
+
},
|
|
4493
|
+
{
|
|
4494
|
+
name: "global-setup.ts",
|
|
4495
|
+
content: generateGlobalSetup(config)
|
|
4496
|
+
},
|
|
4497
|
+
{
|
|
4498
|
+
name: "global-teardown.ts",
|
|
4499
|
+
content: generateGlobalTeardown(config)
|
|
4500
|
+
}
|
|
4501
|
+
];
|
|
4502
|
+
if (request.hasAuth) {
|
|
4503
|
+
files.push({
|
|
4504
|
+
name: "auth.setup.ts",
|
|
4505
|
+
content: generateAuthSetup(config)
|
|
4506
|
+
});
|
|
4507
|
+
}
|
|
4508
|
+
const writtenFiles = [];
|
|
4509
|
+
const skippedFiles = [];
|
|
4510
|
+
for (const file of files) {
|
|
4511
|
+
const filePath = join13(request.cwd, file.name);
|
|
4512
|
+
const written = ensureFile(filePath, file.content, force);
|
|
4513
|
+
if (written) writtenFiles.push(file.name);
|
|
4514
|
+
else skippedFiles.push(file.name);
|
|
4515
|
+
}
|
|
4516
|
+
return {
|
|
4517
|
+
writtenFiles,
|
|
4518
|
+
skippedFiles
|
|
4519
|
+
};
|
|
4520
|
+
}
|
|
4521
|
+
};
|
|
4522
|
+
}
|
|
4523
|
+
var init_runtime_bootstrap = __esm({
|
|
4524
|
+
"src/execution/runtime-bootstrap.ts"() {
|
|
4525
|
+
"use strict";
|
|
4526
|
+
init_esm_shims();
|
|
4527
|
+
init_playwright_config_generator();
|
|
4528
|
+
init_global_setup_generator();
|
|
4529
|
+
init_global_teardown_generator();
|
|
4530
|
+
init_auth_setup_generator();
|
|
4531
|
+
}
|
|
4532
|
+
});
|
|
4533
|
+
|
|
4271
4534
|
// src/server/croc-office.ts
|
|
4272
4535
|
var DEFAULT_AGENTS, CrocOffice;
|
|
4273
4536
|
var init_croc_office = __esm({
|
|
@@ -4408,13 +4671,13 @@ var init_croc_office = __esm({
|
|
|
4408
4671
|
const fullResult = await pipeline.run(["scan", "er-diagram", "api-chain", "plan", "codegen"]);
|
|
4409
4672
|
this.lastPipelineResult = fullResult;
|
|
4410
4673
|
this.lastGeneratedFiles = fullResult.generatedFiles;
|
|
4411
|
-
const { writeFileSync:
|
|
4412
|
-
const { dirname:
|
|
4674
|
+
const { writeFileSync: writeFileSync11, mkdirSync: mkdirSync11 } = await import("fs");
|
|
4675
|
+
const { dirname: dirname7 } = await import("path");
|
|
4413
4676
|
let filesWritten = 0;
|
|
4414
4677
|
for (const file of fullResult.generatedFiles) {
|
|
4415
4678
|
const fullPath = resolvePath(this.cwd, file.filePath);
|
|
4416
|
-
|
|
4417
|
-
|
|
4679
|
+
mkdirSync11(dirname7(fullPath), { recursive: true });
|
|
4680
|
+
writeFileSync11(fullPath, file.content, "utf-8");
|
|
4418
4681
|
filesWritten++;
|
|
4419
4682
|
}
|
|
4420
4683
|
this.updateNodeStatus("controller", "passed");
|
|
@@ -4483,58 +4746,66 @@ var init_croc_office = __esm({
|
|
|
4483
4746
|
return this.lastReports;
|
|
4484
4747
|
}
|
|
4485
4748
|
/** Run generated tests with Playwright */
|
|
4486
|
-
async runTests() {
|
|
4749
|
+
async runTests(options = {}) {
|
|
4487
4750
|
if (this.running) return { ok: false, task: "execute", duration: 0, error: "Another task is running" };
|
|
4488
4751
|
if (this.lastGeneratedFiles.length === 0) {
|
|
4489
4752
|
return { ok: false, task: "execute", duration: 0, error: "No test files \u2014 run Pipeline first" };
|
|
4490
4753
|
}
|
|
4491
4754
|
this.running = true;
|
|
4492
4755
|
const start = Date.now();
|
|
4756
|
+
let cleanupBackend = null;
|
|
4493
4757
|
try {
|
|
4494
4758
|
const { resolve: resolvePath } = await import("path");
|
|
4495
|
-
const {
|
|
4496
|
-
const {
|
|
4497
|
-
const
|
|
4759
|
+
const { existsSync: existsSync17 } = await import("fs");
|
|
4760
|
+
const { createExecutionCoordinator: createExecutionCoordinator2 } = await Promise.resolve().then(() => (init_coordinator(), coordinator_exports));
|
|
4761
|
+
const { createBackendManager: createBackendManager2 } = await Promise.resolve().then(() => (init_backend_manager(), backend_manager_exports));
|
|
4762
|
+
const { createRuntimeBootstrap: createRuntimeBootstrap2 } = await Promise.resolve().then(() => (init_runtime_bootstrap(), runtime_bootstrap_exports));
|
|
4763
|
+
const { categorizeFailure: categorizeFailure2 } = await Promise.resolve().then(() => (init_self_healing(), self_healing_exports));
|
|
4764
|
+
const testFiles = this.lastGeneratedFiles.map((f) => resolvePath(this.cwd, f.filePath)).filter((f) => existsSync17(f));
|
|
4498
4765
|
if (testFiles.length === 0) {
|
|
4499
4766
|
this.log("\u26A0\uFE0F No test files found on disk", "warn");
|
|
4500
4767
|
return { ok: false, task: "execute", duration: Date.now() - start, error: "No test files found on disk" };
|
|
4501
4768
|
}
|
|
4502
|
-
|
|
4503
|
-
this.
|
|
4504
|
-
this.
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
}
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4769
|
+
const mode = options.mode ?? "auto";
|
|
4770
|
+
this.updateAgent("tester-croc", { status: "working", currentTask: `Running ${testFiles.length} test files (${mode})...`, progress: 0 });
|
|
4771
|
+
this.log(`\u{1F9EA} \u6D4B\u8BD5\u9CC4 is running ${testFiles.length} Playwright tests (${mode})...`);
|
|
4772
|
+
const runtimeBootstrap = createRuntimeBootstrap2(this.config);
|
|
4773
|
+
const runtimeResult = await runtimeBootstrap.ensure({
|
|
4774
|
+
cwd: this.cwd,
|
|
4775
|
+
hasAuth: !!this.config.runtime?.auth?.loginUrl
|
|
4776
|
+
});
|
|
4777
|
+
if (runtimeResult.writtenFiles.length > 0) {
|
|
4778
|
+
this.log(`\u{1F9E9} Runtime assets prepared: ${runtimeResult.writtenFiles.join(", ")}`);
|
|
4779
|
+
}
|
|
4780
|
+
const backendManager = createBackendManager2();
|
|
4781
|
+
const backendReady = await backendManager.ensureReady({
|
|
4782
|
+
mode,
|
|
4783
|
+
cwd: this.cwd,
|
|
4784
|
+
server: this.config.runtime?.server,
|
|
4785
|
+
baseURL: this.config.playwright?.baseURL
|
|
4786
|
+
});
|
|
4787
|
+
cleanupBackend = backendReady.cleanup;
|
|
4788
|
+
if (backendReady.status === "started") {
|
|
4789
|
+
this.log(`\u{1F680} Managed backend started (${backendReady.healthUrl})`);
|
|
4790
|
+
} else if (backendReady.status === "reused") {
|
|
4791
|
+
this.log(`\u{1F501} Reusing backend (${backendReady.healthUrl})`);
|
|
4516
4792
|
}
|
|
4517
|
-
|
|
4518
|
-
const
|
|
4519
|
-
const
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
if (skippedMatch) metrics.skipped = parseInt(skippedMatch[1], 10);
|
|
4526
|
-
if (timedOutMatch) metrics.timedOut = parseInt(timedOutMatch[1], 10);
|
|
4793
|
+
this.updateAgent("healer-croc", { status: "thinking", currentTask: "Monitoring test run...", progress: 0 });
|
|
4794
|
+
const coordinator = createExecutionCoordinator2({ categorizeFailure: categorizeFailure2 });
|
|
4795
|
+
const execResult = await coordinator.run({
|
|
4796
|
+
cwd: this.cwd,
|
|
4797
|
+
testFiles,
|
|
4798
|
+
mode
|
|
4799
|
+
});
|
|
4800
|
+
const metrics = execResult.metrics;
|
|
4527
4801
|
this.lastExecutionMetrics = metrics;
|
|
4528
4802
|
const total = metrics.passed + metrics.failed + metrics.skipped + metrics.timedOut;
|
|
4529
4803
|
if (metrics.failed > 0) {
|
|
4530
4804
|
this.updateAgent("tester-croc", { status: "error", currentTask: `${metrics.failed} tests failed`, progress: 100 });
|
|
4531
4805
|
this.updateAgent("healer-croc", { status: "working", currentTask: `Analyzing ${metrics.failed} failures...`, progress: 50 });
|
|
4532
4806
|
this.log(`\u274C Tests: ${metrics.passed} passed, ${metrics.failed} failed, ${metrics.skipped} skipped`, "warn");
|
|
4533
|
-
const
|
|
4534
|
-
|
|
4535
|
-
for (const line of failLines) {
|
|
4536
|
-
const cat = categorizeFailure2(line);
|
|
4537
|
-
this.log(` \u{1F50D} ${cat.category} (${Math.round(cat.confidence * 100)}%): ${line.substring(0, 100)}`, "warn");
|
|
4807
|
+
for (const hint of execResult.failureHints) {
|
|
4808
|
+
this.log(` \u{1F50D} ${hint.category} (${Math.round(hint.confidence * 100)}%): ${hint.line.substring(0, 100)}`, "warn");
|
|
4538
4809
|
}
|
|
4539
4810
|
this.updateAgent("healer-croc", { status: "done", currentTask: "Failure analysis done", progress: 100 });
|
|
4540
4811
|
} else {
|
|
@@ -4552,6 +4823,14 @@ var init_croc_office = __esm({
|
|
|
4552
4823
|
this.log(`\u274C Test execution failed: ${err}`, "error");
|
|
4553
4824
|
return { ok: false, task: "execute", duration: Date.now() - start, error: String(err) };
|
|
4554
4825
|
} finally {
|
|
4826
|
+
if (cleanupBackend) {
|
|
4827
|
+
try {
|
|
4828
|
+
await cleanupBackend();
|
|
4829
|
+
this.log("\u{1F9F9} Managed backend stopped");
|
|
4830
|
+
} catch (err) {
|
|
4831
|
+
this.log(`\u26A0\uFE0F Backend cleanup failed: ${err}`, "warn");
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4555
4834
|
this.running = false;
|
|
4556
4835
|
}
|
|
4557
4836
|
}
|
|
@@ -4571,12 +4850,12 @@ var init_croc_office = __esm({
|
|
|
4571
4850
|
const reports = generateReports2(this.lastPipelineResult, formats);
|
|
4572
4851
|
this.lastReports = reports;
|
|
4573
4852
|
const { resolve: resolvePath } = await import("path");
|
|
4574
|
-
const { writeFileSync:
|
|
4853
|
+
const { writeFileSync: writeFileSync11, mkdirSync: mkdirSync11 } = await import("fs");
|
|
4575
4854
|
const outDir = resolvePath(this.cwd, this.config.outDir || "./opencroc-output");
|
|
4576
|
-
|
|
4855
|
+
mkdirSync11(outDir, { recursive: true });
|
|
4577
4856
|
for (const report2 of reports) {
|
|
4578
4857
|
const fullPath = resolvePath(outDir, report2.filename);
|
|
4579
|
-
|
|
4858
|
+
writeFileSync11(fullPath, report2.content, "utf-8");
|
|
4580
4859
|
this.log(`\u{1F4C4} Generated ${report2.format} report: ${report2.filename}`);
|
|
4581
4860
|
}
|
|
4582
4861
|
this.updateAgent("reporter-croc", { status: "done", currentTask: `${reports.length} reports generated`, progress: 100 });
|
|
@@ -4821,13 +5100,13 @@ import Fastify from "fastify";
|
|
|
4821
5100
|
import fastifyStatic from "@fastify/static";
|
|
4822
5101
|
import fastifyWebsocket from "@fastify/websocket";
|
|
4823
5102
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4824
|
-
import { dirname as
|
|
4825
|
-
import { existsSync as
|
|
5103
|
+
import { dirname as dirname6, join as join14, resolve as resolve9 } from "path";
|
|
5104
|
+
import { existsSync as existsSync16 } from "fs";
|
|
4826
5105
|
async function startServer(opts) {
|
|
4827
5106
|
const app = Fastify({ logger: false });
|
|
4828
5107
|
await app.register(fastifyWebsocket);
|
|
4829
|
-
const webDir =
|
|
4830
|
-
if (
|
|
5108
|
+
const webDir = resolve9(__dirname2, "../web");
|
|
5109
|
+
if (existsSync16(webDir)) {
|
|
4831
5110
|
await app.register(fastifyStatic, {
|
|
4832
5111
|
root: webDir,
|
|
4833
5112
|
prefix: "/",
|
|
@@ -4848,8 +5127,8 @@ async function startServer(opts) {
|
|
|
4848
5127
|
reply.code(404).send({ error: "Not found" });
|
|
4849
5128
|
return;
|
|
4850
5129
|
}
|
|
4851
|
-
const indexPath =
|
|
4852
|
-
if (
|
|
5130
|
+
const indexPath = join14(webDir, "index.html");
|
|
5131
|
+
if (existsSync16(indexPath)) {
|
|
4853
5132
|
reply.sendFile("index.html");
|
|
4854
5133
|
} else {
|
|
4855
5134
|
reply.code(200).header("content-type", "text/html").send(getEmbeddedHtml());
|
|
@@ -5000,7 +5279,7 @@ var init_server = __esm({
|
|
|
5000
5279
|
init_agents();
|
|
5001
5280
|
init_croc_office();
|
|
5002
5281
|
__filename2 = fileURLToPath2(import.meta.url);
|
|
5003
|
-
__dirname2 =
|
|
5282
|
+
__dirname2 = dirname6(__filename2);
|
|
5004
5283
|
}
|
|
5005
5284
|
});
|
|
5006
5285
|
|