Files
LangBot/skills/scripts/e2e/workspace-repository-contracts.mjs
T
2026-07-19 22:46:01 +08:00

137 lines
5.6 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from "node:child_process";
import { access, mkdir, writeFile } from "node:fs/promises";
import { basename, delimiter, dirname, join, resolve } from "node:path";
import { env } from "node:process";
import {
ensureEvidence,
evidencePaths,
exitCode,
loadEnvFiles,
localIsoWithOffset,
resolveLangBotRepo,
writeResult,
} from "./lib/langbot-e2e.mjs";
await loadEnvFiles();
const caseId = env.LBS_CASE_ID || "workspace-repository-contracts";
const paths = evidencePaths(caseId);
await ensureEvidence(paths);
const startedAt = new Date();
const logsDir = join(paths.evidenceDir, "repository-contracts");
await mkdir(logsDir, { recursive: true });
const result = {
source: "automation",
case_id: caseId,
run_id: paths.runId,
started_at: startedAt.toISOString(),
started_at_local: localIsoWithOffset(startedAt),
finished_at: "",
finished_at_local: "",
status: "fail",
reason: "",
repositories: [],
evidence: {
contracts_dir: logsDir,
automation_result_json: paths.automationResultJson,
result_json: paths.resultJson,
},
evidence_collected: ["filesystem", "metrics"],
};
function run(command, args, { cwd, processEnv, timeoutMs = 900_000 }) {
return new Promise((resolvePromise) => {
const started = Date.now();
const child = spawn(command, args, { cwd, env: processEnv, stdio: ["ignore", "pipe", "pipe"] });
let stdout = "";
let stderr = "";
let timedOut = false;
const timer = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
}, timeoutMs);
child.stdout.on("data", (chunk) => { stdout += chunk; });
child.stderr.on("data", (chunk) => { stderr += chunk; });
child.on("error", (error) => {
clearTimeout(timer);
resolvePromise({ status: null, stdout, stderr, error, timedOut, durationMs: Date.now() - started });
});
child.on("close", (status) => {
clearTimeout(timer);
resolvePromise({ status, stdout, stderr, error: null, timedOut, durationMs: Date.now() - started });
});
});
}
try {
const langbotRepo = await resolveLangBotRepo();
const workspaceRoot = resolve(env.LANGBOT_WORKSPACE_ROOT || dirname(langbotRepo));
const sdkRepo = resolve(env.LANGBOT_PLUGIN_SDK_REPO || join(workspaceRoot, "langbot-plugin-sdk"));
const python = join(langbotRepo, ".venv", "bin", "python");
await access(python);
await access(join(sdkRepo, "src", "langbot_plugin"));
const processEnv = {
...env,
PYTHONPATH: [join(sdkRepo, "src"), env.PYTHONPATH].filter(Boolean).join(delimiter),
};
const specs = [
["agent-runner", "langbot-agent-runner", ["-m", "pytest", "tests", "-q"]],
["control-plane", "langbot-agent-control-plane", ["-m", "pytest", "tests", "-q"]],
["longterm-memory", "langbot-longterm-memory", ["-m", "pytest", "tests", "-q"]],
["parser", "langbot-parser", ["-m", "pytest", "tests", "-q"]],
["rag", "langbot-rag", ["-m", "pytest", "tests", "-q"]],
["skill-authoring", "langbot-skill-authoring", ["-m", "pytest", "tests", "-q"]],
["local-agent", "langbot-local-agent", ["-m", "pytest", "tests", "-q"]],
["langbot-agent-provider", "LangBot", ["-m", "pytest", "tests/unit_tests/agent", "tests/unit_tests/provider", "-q"]],
["skills-cli", "LangBot/skills", ["--test", "test/lbs-cli.test.ts"]],
["plugin-sdk-runtime", "langbot-plugin-sdk", ["-m", "pytest", "tests", "-q", "--ignore=tests/packaging/test_installed_cli_blackbox.py"]],
["plugin-sdk-packaging", "langbot-plugin-sdk", ["-m", "pytest", "tests/packaging/test_installed_cli_blackbox.py", "-q"]],
];
for (const [id, directory, args] of specs) {
const cwd = resolve(workspaceRoot, directory);
const command = id === "skills-cli" ? env.LANGBOT_NODE || "node" : python;
const execution = await run(command, args, { cwd, processEnv });
const stdoutPath = join(logsDir, `${id}.stdout.log`);
const stderrPath = join(logsDir, `${id}.stderr.log`);
await writeFile(stdoutPath, execution.stdout, "utf8");
await writeFile(stderrPath, execution.stderr, "utf8");
const combined = `${execution.stdout}\n${execution.stderr}`;
const packagingNetworkIssue = id === "plugin-sdk-packaging"
&& execution.status !== 0
&& /hatchling|PyPI|TLS|SSL|network|timed out|Temporary failure|connection|EOF/i.test(combined);
const status = execution.status === 0 ? "pass" : packagingNetworkIssue ? "env_issue" : "fail";
result.repositories.push({
id,
repository: basename(cwd),
status,
exit_status: execution.status,
timed_out: execution.timedOut,
duration_ms: execution.durationMs,
stdout_log: stdoutPath,
stderr_log: stderrPath,
reason: execution.error?.message || (execution.timedOut ? "Test command timed out." : packagingNetworkIssue ? "Packaging blackbox could not fetch its isolated build dependency." : ""),
});
}
const productFailures = result.repositories.filter((item) => item.status === "fail");
const envIssues = result.repositories.filter((item) => item.status === "env_issue");
result.status = productFailures.length === 0 ? "pass" : "fail";
result.reason = productFailures.length === 0
? `All repository product contracts passed; ${envIssues.length} packaging environment issue(s) were classified separately.`
: `${productFailures.length} repository product contract group(s) failed.`;
} catch (error) {
result.status = "env_issue";
result.reason = error.message;
} finally {
const finishedAt = new Date();
result.finished_at = finishedAt.toISOString();
result.finished_at_local = localIsoWithOffset(finishedAt);
await writeResult(paths, result);
console.log(JSON.stringify(result, null, 2));
}
process.exit(exitCode(result.status));