Files
meme-api/scripts/generate-manifest.mjs

111 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.join(__dirname, "..");
const sourceDir = path.join(repoRoot, "原图");
const publicMeme = path.join(repoRoot, "public", "meme");
const outFile = path.join(repoRoot, "public", "memes.json");
const NUMBERED_ASSET_RE = /^(\d+)\.(webp|gif)$/i;
/**
* Mirror srcDir into destDir without fs.cpSync (avoids native crashes on huge trees / some Node+Win builds).
*/
function copyTreeIterative(srcDir, destDir) {
fs.mkdirSync(destDir, { recursive: true });
/** @type {Array<[string, string]>} */
const stack = [[srcDir, destDir]];
let files = 0;
while (stack.length) {
const [src, dest] = stack.pop();
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const ent of entries) {
const s = path.join(src, ent.name);
const d = path.join(dest, ent.name);
if (ent.isDirectory()) {
fs.mkdirSync(d, { recursive: true });
stack.push([s, d]);
} else if (ent.isFile()) {
fs.copyFileSync(s, d);
files += 1;
}
}
}
return files;
}
function syncSourceToPublic() {
console.log("build: mirroring 原图/ -> public/meme/ ...");
if (!fs.existsSync(sourceDir)) {
console.warn("原图/ not found — 请先运行 convert_to_webp.py或保留 public/meme");
return false;
}
fs.mkdirSync(path.dirname(publicMeme), { recursive: true });
if (fs.existsSync(publicMeme)) {
fs.rmSync(publicMeme, { recursive: true, force: true });
}
fs.mkdirSync(publicMeme, { recursive: true });
const n = copyTreeIterative(sourceDir, publicMeme);
console.log(`synced 原图/ -> public/meme/ (${n} files copied)`);
return true;
}
/** @param {string} dirPath */
function listNumberedAssets(dirPath) {
if (!fs.existsSync(dirPath)) return [];
return fs
.readdirSync(dirPath)
.filter((f) => NUMBERED_ASSET_RE.test(f))
.sort((a, b) => {
const na = parseInt(a.replace(NUMBERED_ASSET_RE, "$1"), 10);
const nb = parseInt(b.replace(NUMBERED_ASSET_RE, "$1"), 10);
return na - nb;
});
}
function main() {
syncSourceToPublic();
const scanRoot = fs.existsSync(publicMeme) ? publicMeme : sourceDir;
/** @type {{ generatedAt: string; categories: Array<{ id: string; name: string; items: Array<{ file: string; path: string }> }> }} */
const payload = {
generatedAt: new Date().toISOString(),
categories: [],
};
if (fs.existsSync(scanRoot)) {
const dirs = fs.readdirSync(scanRoot, { withFileTypes: true });
for (const ent of dirs) {
if (!ent.isDirectory()) continue;
const id = ent.name;
const dirPath = path.join(scanRoot, id);
const files = listNumberedAssets(dirPath);
if (!files.length) continue;
payload.categories.push({
id,
name: id,
items: files.map((file) => ({
file,
path: `${id}/${file}`,
})),
});
}
}
payload.categories.sort((a, b) => a.id.localeCompare(b.id, "zh-Hans-CN"));
fs.mkdirSync(path.dirname(outFile), { recursive: true });
fs.writeFileSync(outFile, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
const n = payload.categories.reduce((acc, c) => acc + c.items.length, 0);
console.log(`manifest: ${payload.categories.length} categories, ${n} assets (webp/gif)`);
}
try {
main();
} catch (err) {
console.error("generate-manifest failed:", err);
process.exitCode = 1;
}