import * as fs from "node:fs"; export interface ArtifactCheckInput { outputPath?: string; outputDir?: string; minimumSizeBytes?: number; commandStartedAt?: number; copiedArtifact?: { sourcePath: string; targetPath: string; }; popupError?: string | null; } export interface ArtifactCheckResult { ok: boolean; blocked: boolean; reasons: string[]; checks: { fileExists: boolean; folderNotEmpty: boolean; minimumSize: boolean; modifiedAfterStart: boolean; copiedHashMatch: boolean; popupClear: boolean; }; } function safeExists(p?: string): boolean { try { return !!p && fs.existsSync(p); } catch { return false; } } function hashFile(p: string): string { const data = fs.readFileSync(p); const crypto = require("node:crypto"); return crypto.createHash("sha256").update(data).digest("hex"); } export function validateArtifact(input: ArtifactCheckInput): ArtifactCheckResult { const reasons: string[] = []; const minimum = input.minimumSizeBytes ?? 1; const fileExists = input.outputPath ? safeExists(input.outputPath) : true; if (!fileExists) reasons.push("Output file missing."); let folderNotEmpty = true; if (!input.outputDir && input.outputPath) { try { const parent = require("node:path").dirname(input.outputPath); folderNotEmpty = fs.existsSync(parent) && fs.readdirSync(parent).length > 0; } catch { folderNotEmpty = false; } if (!folderNotEmpty) reasons.push("Output folder empty after command success."); } if (input.outputDir) { try { folderNotEmpty = fs.existsSync(input.outputDir) && fs.readdirSync(input.outputDir).length > 0; } catch { folderNotEmpty = false; } if (!folderNotEmpty) reasons.push("Output folder empty after command success."); } let minimumSize = true; if (input.outputPath && fileExists) { try { minimumSize = fs.statSync(input.outputPath).size > minimum; } catch { minimumSize = false; } if (!minimumSize) reasons.push("Output file size below minimum."); } let modifiedAfterStart = true; if (input.outputPath && fileExists && input.commandStartedAt) { try { modifiedAfterStart = fs.statSync(input.outputPath).mtimeMs >= input.commandStartedAt; } catch { modifiedAfterStart = false; } if (!modifiedAfterStart) reasons.push("Output file not modified after command start."); } let copiedHashMatch = true; if (input.copiedArtifact) { const { sourcePath, targetPath } = input.copiedArtifact; try { if (safeExists(sourcePath) && safeExists(targetPath)) { const sourceText = fs.readFileSync(sourcePath); const targetText = fs.readFileSync(targetPath); copiedHashMatch = sourceText.equals(targetText); } else { copiedHashMatch = false; } } catch { copiedHashMatch = false; } if (!copiedHashMatch) reasons.push("Copied artifact hash mismatch."); } const popupClear = !input.popupError; if (!popupClear) reasons.push(`Popup/error modal detected: ${input.popupError}`); const ok = reasons.length === 0; return { ok, blocked: !ok, reasons, checks: { fileExists, folderNotEmpty, minimumSize, modifiedAfterStart, copiedHashMatch, popupClear, }, }; }