mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-21 20:06:06 +00:00
137 lines
5.6 KiB
JavaScript
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));
|