blog / src /admin /snapshot.ts
cacode's picture
Upload 434 files
96dd062 verified
import { readFileSync, readdirSync, statSync } from "node:fs";
import path from "node:path";
import matter from "gray-matter";
import {
adConfig1,
adConfig2,
announcementConfig,
backgroundWallpaper,
commentConfig,
coverImageConfig,
fontConfig,
footerConfig,
friendsConfig,
friendsPageConfig,
licenseConfig,
live2dModelConfig,
musicPlayerConfig,
navBarConfig,
profileConfig,
sakuraConfig,
sidebarLayoutConfig,
siteConfig,
sponsorConfig,
spineModelConfig,
} from "@/config";
import type {
AdminConfigBundle,
AdminImageRecord,
AdminPostRecord,
AdminSnapshot,
AdminSpecPageRecord,
} from "./types";
const PROJECT_ROOT = process.cwd();
const POSTS_DIR = path.join(PROJECT_ROOT, "src", "content", "posts");
const SPEC_DIR = path.join(PROJECT_ROOT, "src", "content", "spec");
const PUBLIC_DIR = path.join(PROJECT_ROOT, "public");
const IMAGE_EXTENSIONS = new Set([
".png",
".jpg",
".jpeg",
".webp",
".gif",
".svg",
".avif",
]);
const MARKDOWN_EXTENSIONS = new Set([".md", ".mdx"]);
function toUnixPath(value: string): string {
return value.replace(/\\/g, "/");
}
function readUtf8(filePath: string): string {
return readFileSync(filePath, "utf8");
}
function statSafe(filePath: string) {
try {
return statSync(filePath);
} catch {
return null;
}
}
function walkFiles(rootDir: string, matcher: (filePath: string) => boolean): string[] {
if (!statSafe(rootDir)?.isDirectory()) {
return [];
}
const results: string[] = [];
const stack = [rootDir];
while (stack.length > 0) {
const currentDir = stack.pop();
if (!currentDir) continue;
for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
const absolutePath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
stack.push(absolutePath);
continue;
}
if (matcher(absolutePath)) {
results.push(absolutePath);
}
}
}
return results.sort((left, right) => left.localeCompare(right));
}
function toPreviewUrl(origin: AdminImageRecord["origin"], relativeToRoot: string): string {
const encodedPath = relativeToRoot
.split("/")
.map((segment) => encodeURIComponent(segment))
.join("/");
return `/_admin/files/${origin}/${encodedPath}`;
}
function getPostRecords(): AdminPostRecord[] {
const markdownFiles = walkFiles(POSTS_DIR, (filePath) =>
MARKDOWN_EXTENSIONS.has(path.extname(filePath).toLowerCase()),
);
return markdownFiles
.map((filePath) => {
const raw = readUtf8(filePath);
const parsed = matter(raw);
const stats = statSync(filePath);
const relativePath = toUnixPath(path.relative(PROJECT_ROOT, filePath));
const relativeToPosts = toUnixPath(path.relative(POSTS_DIR, filePath));
const slug = relativeToPosts.replace(/\.(md|mdx)$/i, "");
const data = parsed.data as Record<string, unknown>;
return {
slug,
filePath: relativePath,
title: String(data.title ?? slug),
published: String(data.published ?? ""),
updated: String(data.updated ?? ""),
description: String(data.description ?? ""),
image: String(data.image ?? ""),
tags: Array.isArray(data.tags) ? data.tags.map((item) => String(item)) : [],
category: String(data.category ?? ""),
lang: String(data.lang ?? ""),
draft: Boolean(data.draft ?? false),
pinned: Boolean(data.pinned ?? false),
comment: Boolean(data.comment ?? true),
author: String(data.author ?? ""),
sourceLink: String(data.sourceLink ?? ""),
licenseName: String(data.licenseName ?? ""),
licenseUrl: String(data.licenseUrl ?? ""),
body: parsed.content,
extension: path.extname(filePath).toLowerCase() === ".mdx" ? "mdx" : "md",
excerpt: parsed.content.trim().slice(0, 180),
modifiedAt: stats.mtime.toISOString(),
};
})
.sort((left, right) => {
const pinnedDiff = Number(right.pinned) - Number(left.pinned);
if (pinnedDiff !== 0) return pinnedDiff;
return right.published.localeCompare(left.published);
});
}
function getSpecPageRecords(): AdminSpecPageRecord[] {
const markdownFiles = walkFiles(SPEC_DIR, (filePath) =>
MARKDOWN_EXTENSIONS.has(path.extname(filePath).toLowerCase()),
);
return markdownFiles.map((filePath) => ({
slug: path.basename(filePath).replace(/\.(md|mdx)$/i, ""),
filePath: toUnixPath(path.relative(PROJECT_ROOT, filePath)),
body: readUtf8(filePath),
extension: path.extname(filePath).toLowerCase() === ".mdx" ? "mdx" : "md",
modifiedAt: statSync(filePath).mtime.toISOString(),
}));
}
function getImageRecords(): AdminImageRecord[] {
const imageRoots: Array<{
dir: string;
origin: AdminImageRecord["origin"];
}> = [
{ dir: PUBLIC_DIR, origin: "public" },
{ dir: POSTS_DIR, origin: "content" },
];
return imageRoots.flatMap(({ dir, origin }) => {
return walkFiles(dir, (filePath) =>
IMAGE_EXTENSIONS.has(path.extname(filePath).toLowerCase()),
).map((filePath) => {
const stats = statSync(filePath);
const relativePath = toUnixPath(path.relative(PROJECT_ROOT, filePath));
const relativeToRoot = toUnixPath(path.relative(dir, filePath));
return {
id: relativePath,
name: path.basename(filePath),
path: relativePath,
url: toPreviewUrl(origin, relativeToRoot),
directory: toUnixPath(path.dirname(relativePath)),
origin,
size: stats.size,
updatedAt: stats.mtime.toISOString(),
sitePath: origin === "public" ? `/${relativeToRoot}` : relativeToRoot,
};
});
});
}
function getConfigBundle(): AdminConfigBundle {
return {
siteConfig,
profileConfig,
navBarConfig,
sidebarConfig: sidebarLayoutConfig,
backgroundWallpaper,
announcementConfig,
footerConfig,
adConfig: {
adConfig1,
adConfig2,
},
commentConfig,
musicConfig: musicPlayerConfig,
pioConfig: {
spineModelConfig,
live2dModelConfig,
},
sponsorConfig,
friendsConfig: {
friendsPageConfig,
friendsConfig,
},
licenseConfig,
coverImageConfig,
sakuraConfig,
fontConfig,
};
}
export function getAdminSnapshot(): AdminSnapshot {
return {
generatedAt: new Date().toISOString(),
configs: getConfigBundle(),
posts: getPostRecords(),
specPages: getSpecPageRecords(),
images: getImageRecords(),
};
}