Add optional site archive backups
This commit is contained in:
+27
-5
@@ -33,6 +33,7 @@ function resolveCmsPath(value) {
|
||||
|
||||
let projectConfig = await loadProjectConfig();
|
||||
const repoRoot = resolveCmsPath(process.env.SITE_ROOT || projectConfig.siteRoot || "../NODEDC_SITE");
|
||||
const siteBackupRoot = join(repoRoot, ".cms-backups");
|
||||
const pagePath = join(repoRoot, "content", "pages", "home.json");
|
||||
const knowledgePath = join(repoRoot, "content", "knowledge.json");
|
||||
const blockTemplatesPath = join(repoRoot, "content", "block-templates", "home.json");
|
||||
@@ -106,7 +107,7 @@ const MEDIA_EXTENSIONS = new Set([
|
||||
".woff",
|
||||
".woff2",
|
||||
]);
|
||||
const BROWSER_EXCLUDED_DIRS = new Set([".git", ".trash", "node_modules"]);
|
||||
const BROWSER_EXCLUDED_DIRS = new Set([".cms-backups", ".git", ".trash", "node_modules"]);
|
||||
const REFERENCE_TEXT_EXTENSIONS = new Set([
|
||||
".css",
|
||||
".html",
|
||||
@@ -1649,12 +1650,28 @@ async function clearSiteWorkspace() {
|
||||
await mkdir(repoRoot, { recursive: true });
|
||||
const entries = await readdir(repoRoot, { withFileTypes: true }).catch(() => []);
|
||||
for (const entry of entries) {
|
||||
if (entry.name === ".git") continue;
|
||||
if (entry.name === ".git" || entry.name === ".cms-backups") continue;
|
||||
await rm(join(repoRoot, entry.name), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function unpackSiteArchive({ fileName, fileBuffer }) {
|
||||
async function saveSiteArchiveBackup({ fileName, fileBuffer }) {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const sourceBase = String(fileName || "site.zip").replace(/\.zip$/i, "");
|
||||
const backupName = `${timestamp}-${sanitizePathSegment(sourceBase)}.zip`;
|
||||
const absolutePath = join(siteBackupRoot, backupName);
|
||||
await mkdir(siteBackupRoot, { recursive: true });
|
||||
await writeFile(absolutePath, fileBuffer);
|
||||
return {
|
||||
saved: true,
|
||||
fileName: backupName,
|
||||
path: `.cms-backups/${backupName}`,
|
||||
bytes: fileBuffer.length,
|
||||
label: formatProjectSize(fileBuffer.length),
|
||||
};
|
||||
}
|
||||
|
||||
async function unpackSiteArchive({ fileName, fileBuffer }, { keepBackup = false } = {}) {
|
||||
if (!fileName.toLowerCase().endsWith(".zip")) {
|
||||
throw new Error("Пока поддержан архив сайта в формате .zip");
|
||||
}
|
||||
@@ -1674,6 +1691,9 @@ async function unpackSiteArchive({ fileName, fileBuffer }) {
|
||||
throw new Error(`Архив сайта неполный. Отсутствует: ${missingRequired.join(", ")}`);
|
||||
}
|
||||
|
||||
const backup = keepBackup
|
||||
? await saveSiteArchiveBackup({ fileName, fileBuffer })
|
||||
: { saved: false, fileName: "", path: "", bytes: 0, label: "0 МБ" };
|
||||
await clearSiteWorkspace();
|
||||
|
||||
let files = 0;
|
||||
@@ -1689,7 +1709,7 @@ async function unpackSiteArchive({ fileName, fileBuffer }) {
|
||||
}
|
||||
|
||||
if (!relativePath) continue;
|
||||
if (pathSegments(relativePath).some((segment) => segment === ".git" || segment === "node_modules")) continue;
|
||||
if (pathSegments(relativePath).some((segment) => BROWSER_EXCLUDED_DIRS.has(segment))) continue;
|
||||
|
||||
const targetPath = join(repoRoot, relativePath);
|
||||
const resolvedTarget = resolve(targetPath);
|
||||
@@ -1720,6 +1740,7 @@ async function unpackSiteArchive({ fileName, fileBuffer }) {
|
||||
files,
|
||||
bytes,
|
||||
label: formatProjectSize(bytes),
|
||||
backup,
|
||||
site,
|
||||
size: await calculateProjectSize(),
|
||||
};
|
||||
@@ -2845,7 +2866,8 @@ const server = createServer(async (req, res) => {
|
||||
return;
|
||||
}
|
||||
const upload = parseMultipartUpload(req, await readBodyBuffer(req));
|
||||
const result = await unpackSiteArchive(upload);
|
||||
const keepBackup = ["1", "true", "yes"].includes(String(url.searchParams.get("backup") || "").toLowerCase());
|
||||
const result = await unpackSiteArchive(upload, { keepBackup });
|
||||
sendJson(res, 200, result);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user