mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-30 13:36:45 +02:00
111 lines
3.3 KiB
JavaScript
111 lines
3.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { execFileSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
const DEFAULT_DELETION_THRESHOLD = 200;
|
|
const REQUIRED_ROOT_FILES = ["package.json", "pnpm-lock.yaml", ".gitignore"];
|
|
|
|
function parseBooleanEnv(value) {
|
|
return ["1", "true", "yes", "on"].includes(value?.trim().toLowerCase() ?? "");
|
|
}
|
|
|
|
function parsePositiveInteger(value, fallback) {
|
|
if (!value) {
|
|
return fallback;
|
|
}
|
|
const parsed = Number.parseInt(value, 10);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
}
|
|
|
|
export function parseGitShortStatus(raw) {
|
|
return raw
|
|
.split(/\r?\n/u)
|
|
.map((line) => line.trimEnd())
|
|
.filter(Boolean)
|
|
.map((line) => {
|
|
const status = line.slice(0, 2);
|
|
const rawPath = line.slice(3);
|
|
return {
|
|
line,
|
|
path: rawPath.includes(" -> ") ? (rawPath.split(" -> ").at(-1) ?? rawPath) : rawPath,
|
|
status,
|
|
trackedDeletion: status.includes("D") && status !== "??",
|
|
};
|
|
});
|
|
}
|
|
|
|
export function evaluateTestboxSyncSanity({
|
|
cwd,
|
|
statusRaw,
|
|
exists = fs.existsSync,
|
|
deletionThreshold = DEFAULT_DELETION_THRESHOLD,
|
|
allowMassDeletions = false,
|
|
}) {
|
|
const missingRootFiles = REQUIRED_ROOT_FILES.filter((file) => !exists(path.join(cwd, file)));
|
|
const statusEntries = parseGitShortStatus(statusRaw);
|
|
const trackedDeletions = statusEntries.filter((entry) => entry.trackedDeletion);
|
|
const problems = [];
|
|
|
|
if (missingRootFiles.length > 0) {
|
|
problems.push(`missing required root files: ${missingRootFiles.join(", ")}`);
|
|
}
|
|
if (!allowMassDeletions && trackedDeletions.length >= deletionThreshold) {
|
|
const examples = trackedDeletions
|
|
.slice(0, 8)
|
|
.map((entry) => entry.path)
|
|
.join(", ");
|
|
problems.push(
|
|
`remote git status has ${trackedDeletions.length} tracked deletions (threshold ${deletionThreshold}); examples: ${examples}`,
|
|
);
|
|
}
|
|
|
|
return {
|
|
ok: problems.length === 0,
|
|
missingRootFiles,
|
|
problems,
|
|
statusEntryCount: statusEntries.length,
|
|
trackedDeletionCount: trackedDeletions.length,
|
|
};
|
|
}
|
|
|
|
function git(args, cwd) {
|
|
return execFileSync("git", args, { cwd, encoding: "utf8" });
|
|
}
|
|
|
|
export function runTestboxSyncSanity({
|
|
cwd = process.cwd(),
|
|
env = process.env,
|
|
stdout = process.stdout,
|
|
stderr = process.stderr,
|
|
} = {}) {
|
|
const root = git(["rev-parse", "--show-toplevel"], cwd).trim();
|
|
const statusRaw = git(["status", "--short", "--untracked-files=all"], root);
|
|
const result = evaluateTestboxSyncSanity({
|
|
cwd: root,
|
|
statusRaw,
|
|
deletionThreshold: parsePositiveInteger(
|
|
env.OPENCLAW_TESTBOX_DELETION_THRESHOLD,
|
|
DEFAULT_DELETION_THRESHOLD,
|
|
),
|
|
allowMassDeletions: parseBooleanEnv(env.OPENCLAW_TESTBOX_ALLOW_MASS_DELETIONS),
|
|
});
|
|
|
|
if (!result.ok) {
|
|
stderr.write(`Testbox sync sanity failed:\n- ${result.problems.join("\n- ")}\n`);
|
|
stderr.write("Warm a fresh box or rerun from a clean repo root before spending a gate.\n");
|
|
return 1;
|
|
}
|
|
|
|
stdout.write(
|
|
`Testbox sync sanity ok: ${result.statusEntryCount} changed entries, ${result.trackedDeletionCount} tracked deletions.\n`,
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
process.exitCode = runTestboxSyncSanity();
|
|
}
|