feat: extend crash recovery for pre-built bundle artifacts

This commit is contained in:
SnapOtter
2026-06-13 16:29:50 +08:00
parent a4ac7cf7d6
commit d1e9536cff
2 changed files with 76 additions and 0 deletions
+34
View File
@@ -378,6 +378,40 @@ export function recoverInterruptedInstalls(): void {
}
}
// 6. Delete staging-{bundleId}/ directories (incomplete extraction)
try {
const aiEntries = readdirSync(AI_DIR, { withFileTypes: true });
for (const entry of aiEntries) {
if (entry.isDirectory() && entry.name.startsWith("staging-")) {
const stagingPath = join(AI_DIR, entry.name);
rmSync(stagingPath, { recursive: true, force: true });
console.info(`[feature-status] Deleted orphaned ${entry.name}/`);
}
}
} catch {
// AI_DIR may not exist yet
}
// 7. Clean up staging/ download directory (partial downloads, orphaned tars)
const downloadStaging = join(AI_DIR, "staging");
if (existsSync(downloadStaging)) {
try {
const files = readdirSync(downloadStaging);
for (const file of files) {
const filePath = join(downloadStaging, file);
if (file.endsWith(".partial") || file.endsWith(".meta")) {
unlinkSync(filePath);
console.info(`[feature-status] Deleted stale download file: ${file}`);
} else if (file.endsWith(".tar.gz")) {
unlinkSync(filePath);
console.info(`[feature-status] Deleted orphaned archive: ${file}`);
}
}
} catch {
// best-effort
}
}
invalidateCache();
}
@@ -438,6 +438,48 @@ describe("Crash recovery - recoverInterruptedInstalls", () => {
mod.recoverInterruptedInstalls();
expect(mod.isFeatureInstalled("ocr")).toBe(false);
});
it("deletes staging-{bundleId}/ directories", () => {
const staging = join(aiDir, "staging-ocr");
mkdirSync(staging, { recursive: true });
writeFileSync(join(staging, "somefile"), "data");
mod.recoverInterruptedInstalls();
expect(existsSync(staging)).toBe(false);
});
it("deletes multiple staging directories", () => {
mkdirSync(join(aiDir, "staging-ocr"), { recursive: true });
mkdirSync(join(aiDir, "staging-upscale-enhance"), { recursive: true });
mod.recoverInterruptedInstalls();
expect(existsSync(join(aiDir, "staging-ocr"))).toBe(false);
expect(existsSync(join(aiDir, "staging-upscale-enhance"))).toBe(false);
});
it("does NOT delete non-staging directories", () => {
const venvDir = join(aiDir, "venv");
mkdirSync(venvDir, { recursive: true });
writeFileSync(join(venvDir, "file"), "data");
mod.recoverInterruptedInstalls();
expect(existsSync(venvDir)).toBe(true);
});
it("deletes stale download files in staging/", () => {
const staging = join(aiDir, "staging");
mkdirSync(staging, { recursive: true });
writeFileSync(join(staging, "bundle.tar.gz.partial"), "partial");
writeFileSync(join(staging, "bundle.tar.gz.meta"), '{"bytesDownloaded":0}');
mod.recoverInterruptedInstalls();
expect(existsSync(join(staging, "bundle.tar.gz.partial"))).toBe(false);
expect(existsSync(join(staging, "bundle.tar.gz.meta"))).toBe(false);
});
it("deletes orphaned .tar.gz in staging when bundle not installed", () => {
const staging = join(aiDir, "staging");
mkdirSync(staging, { recursive: true });
writeFileSync(join(staging, "background-removal-amd64-gpu.tar.gz"), "tar-data");
mod.recoverInterruptedInstalls();
expect(existsSync(join(staging, "background-removal-amd64-gpu.tar.gz"))).toBe(false);
});
});
describe("Composite state - getFeatureStates", () => {