feat(studio): add nitro cleanup tool for fake and broken bundles
CI / check (push) Failing after 1m22s
CI / preflight (push) Skipped
CI / deploy (push) Skipped

This commit is contained in:
openhands committed 2026-09-15 20:54:10 +02:00
1 parent ce93b92a81
commit 694f87d98c
6 files changed
+869

No files matched your search

@@ -0,0 +1,70 @@
import { apiError, apiOk } from "@/lib/api";
import { withAdmin } from "@/lib/api-handler";
import { PERMS } from "@/lib/permissions";
import { logAudit } from "@/lib/services/audit";
import { clearFurniImportCache } from "@/lib/services/furni-import-cache";
import {
deleteNitroCleanupFiles,
scanFakeBrokenNitros,
} from "@/lib/services/nitro-cleanup";
/** Scan all nitro directories for fake (orphaned) and broken (unparseable) .nitro files. */
export const GET = withAdmin({ permission: PERMS.ASSETS_IMPORT }, async () => {
try {
const scan = await scanFakeBrokenNitros();
return apiOk({
fake: scan.fake,
broken: scan.broken,
total: scan.total,
});
} catch (err) {
return apiError(`Nitro scan failed: ${(err as Error).message}`, 500);
}
});
/** Delete the given .nitro files from every nitro directory (primary + mirrors). */
export const DELETE = withAdmin(
{ permission: PERMS.ASSETS_IMPORT },
async (request, ctx) => {
let body: { fileNames?: unknown };
try {
body = await request.json();
} catch {
return apiError("Invalid JSON body", 400);
}
const fileNames = Array.isArray(body?.fileNames)
? body.fileNames.filter(
(name): name is string => typeof name === "string",
)
: [];
if (fileNames.length === 0) {
return apiError("fileNames[] is required", 400);
}
try {
const result = await deleteNitroCleanupFiles(fileNames);
// The deleted .nitro presence is cached by the Studio — drop it so
// the furniture list reflects the removal immediately.
clearFurniImportCache();
if (result.deleted > 0) {
logAudit({
userId: ctx.session.user.id,
action: "nitro_cleanup_delete",
target: "NitroFiles",
after: {
deleted: result.deleted,
copiesRemoved: result.copiesRemoved,
fileNames,
errors: result.errors.length > 0 ? result.errors : undefined,
},
});
}
return apiOk({
deleted: result.deleted,
copiesRemoved: result.copiesRemoved,
errors: result.errors.length > 0 ? result.errors : undefined,
});
} catch (err) {
return apiError(`Nitro cleanup failed: ${(err as Error).message}`, 500);
}
},
);