import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const projectRoot = path.resolve(__dirname, ".."); const removableDirs = [ "dist", "Current", "tmp", ".codex-logs", ]; const removableFiles = [ ".codex-api.err.log", ".codex-api.log", ".codex-api.out.log", ".codex-server.err.log", ".codex-server.out.log", ".codex-vite.err.log", ".codex-vite.out.log", ".codex-web.log", ".tmp-map-editor-api.err.log", ".tmp-map-editor-api.log", ".tmp-map-editor-vite.err.log", ".tmp-map-editor-vite.log", ]; const backupRetentionByPrefix = new Map([ ["maps", 5], ["npcs", 5], ["tiles", 8], ["sprites", 5], ]); function toAbsolute(relativePath) { return path.join(projectRoot, relativePath); } function safeRemoveDir(relativePath, removed) { const absolutePath = toAbsolute(relativePath); if (!fs.existsSync(absolutePath)) { return; } try { fs.rmSync(absolutePath, { recursive: true, force: true }); removed.push(relativePath); } catch { // Ignore locked runtime folders so one open handle does not block the rest of cleanup. } } function safeRemoveFile(relativePath, removed) { const absolutePath = toAbsolute(relativePath); if (!fs.existsSync(absolutePath)) { return; } try { fs.rmSync(absolutePath, { force: true }); removed.push(relativePath); } catch { // Ignore locked runtime files so cleanup can continue. } } function parseBackupGroup(fileName) { const match = /^([a-z_]+)-\d{4}-\d{2}-\d{2}T.*\.json$/i.exec(fileName); return match ? match[1].toLowerCase() : null; } function pruneBackups() { const backupsDir = toAbsolute("backups"); const removed = []; if (!fs.existsSync(backupsDir)) { return removed; } const files = fs.readdirSync(backupsDir, { withFileTypes: true }) .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".json")) .map((entry) => ({ name: entry.name, path: path.join(backupsDir, entry.name), group: parseBackupGroup(entry.name), stat: fs.statSync(path.join(backupsDir, entry.name)), })); const grouped = new Map(); files.forEach((file) => { if (!file.group) { return; } if (!grouped.has(file.group)) { grouped.set(file.group, []); } grouped.get(file.group).push(file); }); grouped.forEach((groupFiles, group) => { const keepCount = backupRetentionByPrefix.get(group) ?? 3; const sorted = groupFiles.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs); sorted.slice(keepCount).forEach((file) => { try { fs.rmSync(file.path, { force: true }); removed.push(path.join("backups", file.name)); } catch { // Ignore any locked backup files. } }); }); return removed; } function main() { const removedDirs = []; const removedFiles = []; removableDirs.forEach((dir) => safeRemoveDir(dir, removedDirs)); removableFiles.forEach((file) => safeRemoveFile(file, removedFiles)); const removedBackups = pruneBackups(); const summary = { removedDirs, removedFiles, removedBackups, }; process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`); } main();