import * as fs from "node:fs"; import * as path from "node:path"; export type AnalysisArtifactPaths = { analysisDir: string; resultDir: string; legacyFiles: string[]; }; function assertNumericId(name: string, value: number): void { if (!Number.isSafeInteger(value) || value <= 0) { throw new Error(`${name} must be a positive safe integer`); } } export function getAnalysisArtifactPaths( taskId: number, resultId: number, dataDir = process.env.DATA_DIR || "/data", ): AnalysisArtifactPaths { assertNumericId("taskId", taskId); assertNumericId("resultId", resultId); const tasksDir = path.resolve(dataDir, "tasks"); const taskDir = path.resolve(tasksDir, String(taskId)); const analysisDir = path.resolve(taskDir, "analysis"); const resultDir = path.resolve(analysisDir, String(resultId)); if (path.dirname(taskDir) !== tasksDir || path.dirname(resultDir) !== analysisDir) { throw new Error("analysis artifact path escaped its fixed root"); } return { analysisDir, resultDir, legacyFiles: [ path.join(analysisDir, `heatmap_${resultId}.bin`), path.join(analysisDir, `deviation_${resultId}.bin`), ], }; } export async function cleanupAnalysisArtifacts( taskId: number, resultId: number, dataDir = process.env.DATA_DIR || "/data", ): Promise { const paths = getAnalysisArtifactPaths(taskId, resultId, dataDir); await fs.promises.rm(paths.resultDir, { recursive: true, force: true }); let entries: string[] = []; try { entries = await fs.promises.readdir(paths.analysisDir); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } const assemblyPrefix = `assembly_vertex_${resultId}_`; const assemblyFiles = entries .filter((name) => name.startsWith(assemblyPrefix) && name.endsWith(".bin")) .map((name) => path.join(paths.analysisDir, name)); await Promise.all( [...paths.legacyFiles, ...assemblyFiles].map((file) => fs.promises.rm(file, { force: true }), ), ); }