Files
rednote-tool-studio/lib/cleanup.ts
T
2026-09-17 21:54:33 +08:00

44 lines
1.9 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
import { CLEANUP_INTERVAL_MS, DEFAULT_IDLE_HOURS, PROJECTS_ROOT } from "./config";
import { isBuildActive } from "./build";
import type { ProjectMeta } from "./types";
let schedulerStarted = false;
export function isProjectIdle(lastAccessAt: string, idleHours: number, now = Date.now()) {
const timestamp = Date.parse(lastAccessAt);
return Number.isFinite(timestamp) && timestamp <= now - idleHours * 60 * 60 * 1000;
}
export async function cleanupIdleNodeModules(idleHours = DEFAULT_IDLE_HOURS) {
await fs.mkdir(PROJECTS_ROOT, { recursive: true });
const cutoff = Date.now() - idleHours * 60 * 60 * 1000;
const cleaned: string[] = [];
for (const entry of await fs.readdir(PROJECTS_ROOT, { withFileTypes: true })) {
if (!entry.isDirectory() || isBuildActive(entry.name)) continue;
const root = path.join(PROJECTS_ROOT, entry.name);
try {
const metaPath = path.join(root, "project.json");
const meta: ProjectMeta = JSON.parse(await fs.readFile(metaPath, "utf8"));
if (!isProjectIdle(meta.lastAccessAt, idleHours, Date.now())) continue;
const modules = path.join(root, "workspace", "node_modules");
const stat = await fs.lstat(modules).catch(() => null);
if (!stat?.isDirectory() || stat.isSymbolicLink()) continue;
await fs.rm(modules, { recursive: true, force: true });
meta.nodeModulesCleanedAt = new Date().toISOString();
await fs.writeFile(metaPath, JSON.stringify(meta, null, 2));
cleaned.push(entry.name);
} catch { /* one broken project must not stop maintenance */ }
}
return { cleaned, idleHours, checkedAt: new Date().toISOString() };
}
export function startCleanupScheduler() {
if (schedulerStarted || process.env.NODE_ENV === "test") return;
schedulerStarted = true;
const timer = setInterval(() => { void cleanupIdleNodeModules(); }, CLEANUP_INTERVAL_MS);
timer.unref();
void cleanupIdleNodeModules();
}