website-builde / lib /validator.ts
Daviddolor's picture
Constrain admin table to plain HTML (no missing shadcn import), fix install-timeout silently passing as success
fdae3ee
Raw
History Blame Contribute Delete
3.65 kB
import { spawn } from "child_process";
import fs from "fs";
import path from "path";
export interface ValidationResult {
success: boolean;
installLog: string;
buildLog: string;
error?: string;
duration: number; // milliseconds
}
/**
* Run a command with timeout and capture stdout/stderr
*/
function runCommand(
cmd: string,
args: string[],
cwd: string,
timeout: number = 300000 // 5 minutes default
): Promise<{ stdout: string; stderr: string; code: number | null }> {
return new Promise((resolve) => {
const child = spawn(cmd, args, { cwd, shell: true });
let stdout = "";
let stderr = "";
child.stdout.on("data", (data) => {
stdout += data.toString();
});
child.stderr.on("data", (data) => {
stderr += data.toString();
});
const timer = setTimeout(() => {
child.kill("SIGTERM");
resolve({
stdout,
stderr: stderr + "\n[ERROR] Command timed out after " + timeout + "ms",
code: null,
});
}, timeout);
child.on("close", (code) => {
clearTimeout(timer);
resolve({ stdout, stderr, code });
});
child.on("error", (err) => {
clearTimeout(timer);
resolve({
stdout,
stderr: stderr + "\n" + err.message,
code: null,
});
});
});
}
export async function validateProject(
projectDir: string,
options: { installTimeout?: number; buildTimeout?: number } = {}
): Promise<ValidationResult> {
const start = Date.now();
const installTimeout = options.installTimeout || 300000;
const buildTimeout = options.buildTimeout || 300000;
// Check if project directory exists
if (!fs.existsSync(projectDir)) {
return {
success: false,
installLog: "",
buildLog: "",
error: `Project directory not found: ${projectDir}`,
duration: 0,
};
}
// Check for package.json
const pkgPath = path.join(projectDir, "package.json");
if (!fs.existsSync(pkgPath)) {
return {
success: false,
installLog: "",
buildLog: "",
error: "No package.json found in project directory",
duration: 0,
};
}
// Step 1: npm install
const installResult = await runCommand(
"npm",
["install", "--no-audit", "--no-fund", "--loglevel=error", "--include=dev"],
projectDir,
installTimeout
);
const installLog = installResult.stdout + "\n" + installResult.stderr;
if (installResult.code !== 0) {
return {
success: false,
installLog,
buildLog: "",
error: `npm install failed with code ${installResult.code}`,
duration: Date.now() - start,
};
}
// Step 2: npm run build
const buildResult = await runCommand(
"npm",
["run", "build"],
projectDir,
buildTimeout
);
const buildLog = buildResult.stdout + "\n" + buildResult.stderr;
if (buildResult.code !== 0 && buildResult.code !== null) {
return {
success: false,
installLog,
buildLog,
error: `npm run build failed with code ${buildResult.code}`,
duration: Date.now() - start,
};
}
return {
success: true,
installLog,
buildLog,
duration: Date.now() - start,
};
}
/**
* Validate the project and write a JSON report inside the project directory
*/
export async function validateAndSave(
projectDir: string,
options?: { installTimeout?: number; buildTimeout?: number }
): Promise<ValidationResult> {
const result = await validateProject(projectDir, options);
const reportPath = path.join(projectDir, ".validation.json");
fs.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
return result;
}