Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
273 lines
7.4 KiB
JavaScript
273 lines
7.4 KiB
JavaScript
import { spawn } from "node:child_process";
|
|
import {
|
|
createWriteStream,
|
|
mkdirSync,
|
|
readFileSync,
|
|
writeFileSync
|
|
} from "node:fs";
|
|
import readline from "node:readline";
|
|
import { dirname, join, relative, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import {
|
|
formatFailureExcerpt,
|
|
formatSlowestLanes
|
|
} from "./run-validation-lanes.mjs";
|
|
import { repositoryCheckDefinitions } from "./repository-checks.mjs";
|
|
|
|
const scriptDirectory = dirname(fileURLToPath(import.meta.url));
|
|
const workspaceRoot = join(scriptDirectory, "..", "..");
|
|
const pnpmCommand = resolvePnpmCommand();
|
|
const verbose = process.argv.includes("--verbose");
|
|
const failureLinesPerTask = readPositiveIntegerOption("--tail-lines", 120);
|
|
const tmpRoot = join(workspaceRoot, ".tmp", "check-full-runs");
|
|
|
|
const phases = [
|
|
{
|
|
title: "Preparation",
|
|
tasks: [{ label: "builtin-apps", script: "generate:builtin-apps" }]
|
|
},
|
|
{
|
|
title: "Preflight checks",
|
|
tasks: repositoryCheckDefinitions.map(({ key, script }) => ({
|
|
label: key,
|
|
script
|
|
}))
|
|
},
|
|
{
|
|
title: "Validation checks",
|
|
tasks: [
|
|
{ label: "lint:ts", script: "lint:ts" },
|
|
{ label: "lint:go", script: "lint:go:prepared" },
|
|
{ label: "typecheck", script: "typecheck" },
|
|
{ label: "test:ts", script: "test:ts" },
|
|
{ label: "test:go", script: "test:go:prepared" }
|
|
]
|
|
}
|
|
];
|
|
|
|
export async function main() {
|
|
const runId = new Date().toISOString().replace(/[:.]/gu, "-");
|
|
const runDirectory = join(tmpRoot, runId);
|
|
mkdirSync(runDirectory, { recursive: true });
|
|
|
|
const startedAt = Date.now();
|
|
const results = [];
|
|
let failed = false;
|
|
|
|
for (const phase of phases) {
|
|
console.log(`\n==> ${phase.title}`);
|
|
const phaseStartedAt = Date.now();
|
|
const phaseResults = await Promise.all(
|
|
phase.tasks.map((task) => runTask(task, phase.title, runDirectory))
|
|
);
|
|
results.push(...phaseResults);
|
|
const phaseDurationMs = Date.now() - phaseStartedAt;
|
|
const failures = phaseResults.filter((result) => result.exitCode !== 0);
|
|
|
|
if (failures.length > 0) {
|
|
console.error(
|
|
`${phase.title} failed ${failures.length}/${phase.tasks.length} task(s) in ${formatDuration(phaseDurationMs)}`
|
|
);
|
|
printFailureSummary(failures, runDirectory);
|
|
failed = true;
|
|
break;
|
|
}
|
|
|
|
console.log(
|
|
`${phase.title} passed ${phase.tasks.length} task(s) in ${formatDuration(phaseDurationMs)}`
|
|
);
|
|
}
|
|
|
|
const durationMs = Date.now() - startedAt;
|
|
const summary = {
|
|
durationMs,
|
|
failureLinesPerTask,
|
|
runDirectory,
|
|
startedAt: new Date(startedAt).toISOString(),
|
|
verbose,
|
|
results
|
|
};
|
|
writeFileSync(
|
|
join(runDirectory, "summary.json"),
|
|
`${JSON.stringify(summary, null, 2)}\n`
|
|
);
|
|
mkdirSync(tmpRoot, { recursive: true });
|
|
writeFileSync(
|
|
join(tmpRoot, "latest.json"),
|
|
`${JSON.stringify(summary, null, 2)}\n`
|
|
);
|
|
|
|
if (failed) {
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
|
|
console.log(
|
|
`\ncheck:full passed ${results.length} task(s) in ${formatDuration(durationMs)}`
|
|
);
|
|
const slowest = formatSlowestLanes(results);
|
|
console.log(
|
|
`slowest tasks: ${slowest} (details: ${relative(workspaceRoot, join(tmpRoot, "latest.json"))})`
|
|
);
|
|
}
|
|
|
|
function runTask(task, phase, runDirectory) {
|
|
const logPath = join(runDirectory, `${sanitizeFileName(task.label)}.log`);
|
|
const logPathRelative = relative(workspaceRoot, logPath);
|
|
const logStream = createWriteStream(logPath, { flags: "w" });
|
|
const startedAt = Date.now();
|
|
const [command, ...prefixArgs] = pnpmCommand;
|
|
const args = [...prefixArgs, "run", task.script];
|
|
|
|
if (verbose) {
|
|
console.log(`[${task.label}] starting`);
|
|
}
|
|
logStream.write(`$ ${formatCommand([command, ...args])}\n\n`);
|
|
|
|
return new Promise((resolveResult) => {
|
|
let settled = false;
|
|
const child = spawn(command, args, {
|
|
cwd: workspaceRoot,
|
|
stdio: ["ignore", "pipe", "pipe"]
|
|
});
|
|
|
|
child.stdout.on("data", (chunk) => logStream.write(chunk));
|
|
child.stderr.on("data", (chunk) => logStream.write(chunk));
|
|
if (verbose) {
|
|
pipeWithPrefix(child.stdout, task.label, process.stdout);
|
|
pipeWithPrefix(child.stderr, task.label, process.stderr);
|
|
}
|
|
|
|
child.on("error", (error) => {
|
|
if (settled) {
|
|
return;
|
|
}
|
|
settled = true;
|
|
logStream.write(`\n[runner] ${error.message}\n`);
|
|
finish(1);
|
|
});
|
|
|
|
child.on("close", (code) => {
|
|
if (settled) {
|
|
return;
|
|
}
|
|
settled = true;
|
|
finish(typeof code === "number" ? code : 1);
|
|
});
|
|
|
|
function finish(exitCode) {
|
|
const result = {
|
|
durationMs: Date.now() - startedAt,
|
|
exitCode,
|
|
label: task.label,
|
|
logPath,
|
|
logPathRelative,
|
|
phase,
|
|
script: task.script
|
|
};
|
|
logStream.end(() => {
|
|
if (verbose) {
|
|
console.log(
|
|
`[${task.label}] ${exitCode === 0 ? "passed" : "failed"} ${formatDuration(result.durationMs)}`
|
|
);
|
|
} else if (exitCode !== 0) {
|
|
printTaskFailure(result);
|
|
}
|
|
resolveResult(result);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function printFailureSummary(failures, runDirectory) {
|
|
console.error(
|
|
`failed tasks: ${failures.map((failure) => failure.label).join(", ")}`
|
|
);
|
|
console.error(
|
|
`\nfull logs: ${relative(workspaceRoot, runDirectory)}\nRerun with live output: pnpm check:full -- --verbose`
|
|
);
|
|
}
|
|
|
|
function printTaskFailure(failure) {
|
|
const excerpt = formatFailureExcerpt(
|
|
readFileSync(failure.logPath, "utf8"),
|
|
failureLinesPerTask
|
|
);
|
|
const label = excerpt.truncated
|
|
? `failure excerpt last ${failureLinesPerTask} lines`
|
|
: "failure output";
|
|
console.error(
|
|
`\n[${failure.label}] failed in ${formatDuration(failure.durationMs)}`
|
|
);
|
|
console.error(`--- ${label} (full log: ${failure.logPathRelative}) ---`);
|
|
console.error(excerpt.text);
|
|
}
|
|
|
|
function resolvePnpmCommand() {
|
|
const fallback = [process.platform === "win32" ? "pnpm.cmd" : "pnpm"];
|
|
try {
|
|
const packageJson = JSON.parse(
|
|
readFileSync(join(workspaceRoot, "package.json"), "utf8")
|
|
);
|
|
const match = /^pnpm@(.+)$/u.exec(String(packageJson.packageManager ?? ""));
|
|
if (!match) {
|
|
return fallback;
|
|
}
|
|
return [
|
|
process.platform === "win32" ? "corepack.cmd" : "corepack",
|
|
`pnpm@${match[1]}`
|
|
];
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function readPositiveIntegerOption(name, defaultValue) {
|
|
const index = process.argv.indexOf(name);
|
|
if (index === -1) {
|
|
return defaultValue;
|
|
}
|
|
const parsed = Number.parseInt(process.argv[index + 1] ?? "", 10);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultValue;
|
|
}
|
|
|
|
function formatCommand(command) {
|
|
return command.map(shellQuote).join(" ");
|
|
}
|
|
|
|
function shellQuote(value) {
|
|
if (/^[A-Za-z0-9_./:=@+-]+$/u.test(value)) {
|
|
return value;
|
|
}
|
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
}
|
|
|
|
function sanitizeFileName(value) {
|
|
return value.replace(/[^A-Za-z0-9_.-]+/gu, "-");
|
|
}
|
|
|
|
function formatDuration(durationMs) {
|
|
return `${(durationMs / 1000).toFixed(1)}s`;
|
|
}
|
|
|
|
function pipeWithPrefix(stream, label, output) {
|
|
const rl = readline.createInterface({ input: stream });
|
|
rl.on("line", (line) => output.write(`[${label}] ${line}\n`));
|
|
}
|
|
|
|
function isMainModule() {
|
|
return (
|
|
process.argv[1] &&
|
|
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
);
|
|
}
|
|
|
|
if (isMainModule()) {
|
|
main().catch((error) => {
|
|
console.error(
|
|
error instanceof Error ? (error.stack ?? error.message) : error
|
|
);
|
|
process.exitCode = 1;
|
|
});
|
|
}
|