import { promises as fs } from "node:fs";
import path from "node:path";
import type { FileHistoryBackup } from "./types.js";
export type RestoreBackupOptions = {
filePath: string;
backup: FileHistoryBackup;
backupDir: string;
};
export type RestoreBackupResult = {
outcome: "restored" | "deleted" | "missing";
};
* F9 + F10 + F11 — apply a single backup entry:
*
* - `null backupFileName` → unlink the target (it didn't exist at backup
* time). Missing target is OK — desired end state is "absent".
* - non-null & backup file present → `mkdir -p` parent, `copyFile`, then
* `chmod` to preserve the recorded mode.
* - non-null & backup file absent (manually deleted, etc.) → return
* `missing` so the caller can `warn` rather than throw (F13 graceful).
*/
export async function restoreBackup(
options: RestoreBackupOptions,
): Promise<RestoreBackupResult> {
const { filePath, backup, backupDir } = options;
if (backup.backupFileName === null) {
try {
await fs.unlink(filePath);
} catch (err) {
if (!isNotFoundError(err)) throw err;
}
return { outcome: "deleted" };
}
const backupPath = path.join(backupDir, backup.backupFileName);
try {
await fs.access(backupPath);
} catch (err) {
if (isNotFoundError(err)) return { outcome: "missing" };
throw err;
}
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.copyFile(backupPath, filePath);
if (typeof backup.mode === "number" && process.platform !== "win32") {
await fs.chmod(filePath, backup.mode & 0o777);
}
return { outcome: "restored" };
}
function isNotFoundError(err: unknown): boolean {
return Boolean(
err && typeof err === "object" && (err as NodeJS.ErrnoException).code === "ENOENT",
);
}