99 lines
3.3 KiB
JavaScript
99 lines
3.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
import { spawnSync } from "node:child_process";
|
|
import { capture, commandExists, run } from "./lib/exec.mjs";
|
|
|
|
const root = process.cwd();
|
|
const requiredRootFiles = [
|
|
"CLAUDE.md",
|
|
"product/milestones.json",
|
|
"ItWorx.KnowledgeHub.slnx",
|
|
"src/frontend/package.json"
|
|
];
|
|
|
|
const missingRoot = requiredRootFiles.filter((entry) => !fs.existsSync(path.join(root, entry)));
|
|
if (missingRoot.length > 0) {
|
|
console.error("Run this command from the repository root. Missing:", missingRoot.join(", "));
|
|
process.exit(1);
|
|
}
|
|
|
|
const checks = [];
|
|
|
|
async function check(name, command, args, predicate, required = true) {
|
|
if (!commandExists(command)) {
|
|
checks.push({ name, value: "missing", ok: false, required });
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const value = await capture(command, args);
|
|
checks.push({ name, value: value.split(/\r?\n/)[0], ok: predicate(value), required });
|
|
} catch (error) {
|
|
checks.push({ name, value: `error: ${error.message}`, ok: false, required });
|
|
}
|
|
}
|
|
|
|
await check("Git", "git", ["--version"], (value) => /git version/i.test(value));
|
|
await check("Node.js", "node", ["--version"], (value) => {
|
|
const major = Number(value.match(/v?(\d+)/)?.[1] ?? 0);
|
|
return major >= 24;
|
|
});
|
|
await check("Corepack", "corepack", ["--version"], (value) => value.length > 0);
|
|
await check("pnpm", "corepack", ["pnpm", "--version"], (value) => {
|
|
const major = Number(value.match(/(\d+)/)?.[1] ?? 0);
|
|
return major >= 11;
|
|
});
|
|
await check(".NET SDK", "dotnet", ["--version"], (value) => value.trim().startsWith("10."));
|
|
function findWorkingPython() {
|
|
for (const candidate of ["python3", "python"]) {
|
|
if (!commandExists(candidate)) {
|
|
continue;
|
|
}
|
|
const probe = spawnSync(candidate, ["--version"], { stdio: "ignore", shell: false });
|
|
if (probe.status === 0) {
|
|
return candidate;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const python = findWorkingPython();
|
|
if (python) {
|
|
await check("Python", python, ["--version"], (value) => {
|
|
const match = value.match(/Python\s+(\d+)\.(\d+)/i);
|
|
return Boolean(match && Number(match[1]) >= 3 && Number(match[2]) >= 11);
|
|
});
|
|
} else {
|
|
checks.push({ name: "Python", value: "missing or non-working", ok: false, required: true });
|
|
}
|
|
await check("Docker", "docker", ["--version"], (value) => /Docker version/i.test(value));
|
|
if (commandExists("docker")) {
|
|
try {
|
|
const compose = await capture("docker", ["compose", "version"]);
|
|
checks.push({ name: "Docker Compose", value: compose, ok: /Compose version\s+v?\d+\./i.test(compose), required: true });
|
|
} catch (error) {
|
|
checks.push({ name: "Docker Compose", value: `error: ${error.message}`, ok: false, required: true });
|
|
}
|
|
}
|
|
|
|
const width = Math.max(...checks.map((entry) => entry.name.length), 10);
|
|
for (const entry of checks) {
|
|
const marker = entry.ok ? "OK " : entry.required ? "ERR" : "WARN";
|
|
console.log(`${marker} ${entry.name.padEnd(width)} ${entry.value}`);
|
|
}
|
|
|
|
const failures = checks.filter((entry) => entry.required && !entry.ok);
|
|
if (failures.length > 0) {
|
|
console.error("\nRequired toolchain checks failed.");
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!fs.existsSync(path.join(root, "pnpm-lock.yaml"))) {
|
|
console.warn("\nWARN pnpm-lock.yaml is not present yet. M0 must generate and commit it.");
|
|
}
|
|
|
|
console.log("\nToolchain baseline is available.");
|