增加历史版本保存功能
This commit is contained in:
+53
-1
@@ -1,6 +1,6 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import { basename, dirname, join, relative } from "node:path";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
@@ -18,6 +18,50 @@ export const atomicWriteFile = async (path: string, content: string) => {
|
||||
await rename(tempPath, path);
|
||||
};
|
||||
|
||||
type HistoricalWriteOptions = {
|
||||
afterWrite?: () => Promise<void> | void;
|
||||
historyDir: string;
|
||||
rootDir: string;
|
||||
};
|
||||
|
||||
export const atomicWriteFileWithHistory = async (
|
||||
path: string,
|
||||
content: string,
|
||||
options: HistoricalWriteOptions,
|
||||
) => {
|
||||
const previous = await readTextFile(path);
|
||||
if (previous === content) {
|
||||
return { backupPath: null as string | null, changed: false };
|
||||
}
|
||||
|
||||
let backupPath: string | null = null;
|
||||
if (previous !== null) {
|
||||
backupPath = buildHistoryBackupPath(path, options);
|
||||
await atomicWriteFile(backupPath, previous);
|
||||
}
|
||||
|
||||
try {
|
||||
await atomicWriteFile(path, content);
|
||||
await options.afterWrite?.();
|
||||
} catch (error) {
|
||||
try {
|
||||
if (previous === null) {
|
||||
await removeFileIfExists(path);
|
||||
} else {
|
||||
await atomicWriteFile(path, previous);
|
||||
}
|
||||
} catch (rollbackError) {
|
||||
throw new AggregateError(
|
||||
[error, rollbackError],
|
||||
`write failed and rollback failed for ${path}`,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return { backupPath, changed: true };
|
||||
};
|
||||
|
||||
export const atomicWriteJson = async (path: string, value: JsonRecord | unknown[]) => {
|
||||
await atomicWriteFile(path, JSON.stringify(value, null, 2));
|
||||
};
|
||||
@@ -109,3 +153,11 @@ export const slugify = (value: string) =>
|
||||
.replace(/[^a-z0-9._-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.slice(0, 64) || "entry";
|
||||
|
||||
const buildHistoryBackupPath = (path: string, options: HistoricalWriteOptions) => {
|
||||
const relativePath = relative(options.rootDir, path);
|
||||
const scopedPath =
|
||||
relativePath && !relativePath.startsWith("..") ? relativePath : basename(path);
|
||||
const backupName = `${basename(path)}.${Date.now().toString(36)}.bak`;
|
||||
return join(options.historyDir, dirname(scopedPath), backupName);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user