import { spawnSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; export const normalizeRepoPath = (value) => value.split(path.sep).join("/"); const repoRoot = path.resolve(process.cwd()); export function normalizeTrackedRepoPath(value) { const normalizedValue = typeof value === "string" ? value : String(value ?? ""); const repoRelative = path.isAbsolute(normalizedValue) ? path.relative(repoRoot, path.resolve(normalizedValue)) : normalizedValue; if (path.isAbsolute(repoRelative) || repoRelative.startsWith("..") || repoRelative === "") { return normalizeRepoPath(normalizedValue); } return normalizeRepoPath(repoRelative); } export function readJsonFile(filePath) { return JSON.parse(fs.readFileSync(filePath, "utf8")); } export function tryReadJsonFile(filePath, fallback) { try { return readJsonFile(filePath); } catch { return fallback; } } export function writeJsonFile(filePath, value) { fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`); } export function runVitestJsonReport({ config, reportPath = "", prefix = "openclaw-vitest-report", }) { const resolvedReportPath = reportPath || path.join(os.tmpdir(), `${prefix}-${Date.now()}.json`); if (!(reportPath && fs.existsSync(resolvedReportPath))) { const run = spawnSync( "pnpm", ["vitest", "run", "--config", config, "--reporter=json", "--outputFile", resolvedReportPath], { stdio: "inherit", env: process.env, }, ); if (run.status !== 0) { process.exit(run.status ?? 1); } } return resolvedReportPath; } export function collectVitestFileDurations(report, normalizeFile = (value) => value) { return (report.testResults ?? []) .map((result) => { const file = typeof result.name === "string" ? normalizeFile(result.name) : ""; const start = typeof result.startTime === "number" ? result.startTime : 0; const end = typeof result.endTime === "number" ? result.endTime : 0; const testCount = Array.isArray(result.assertionResults) ? result.assertionResults.length : 0; return { file, durationMs: Math.max(0, end - start), testCount, }; }) .filter((entry) => entry.file.length > 0 && entry.durationMs > 0); }