Replace entire repo content with correct code from /root/shahikitchen-website/
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Sync ftp-images -> public/images and report changes.
|
||||
* Usage: node scripts/sync-ftp-images.mjs [--dry-run]
|
||||
*
|
||||
* Mapping:
|
||||
* ftp-images/bilder-bp/ -> public/images/dishes/ (menu uses /images/dishes/...)
|
||||
* ftp-images/<other>/ -> public/images/<other>/
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import crypto from "node:crypto";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = path.resolve(__dirname, "..");
|
||||
const DRY_RUN = process.argv.includes("--dry-run");
|
||||
const FTP_ROOT = path.join(ROOT, "ftp-images");
|
||||
const PUBLIC_IMAGES = path.join(ROOT, "public", "images");
|
||||
const LOG_FILE = path.join(ROOT, "sync-images-log.txt");
|
||||
const RESULT_FILE = path.join(ROOT, "sync-result.json");
|
||||
|
||||
/** FTP subfolder name -> public/images subfolder name */
|
||||
const DEST_ALIASES = {
|
||||
"bilder-bp": "dishes",
|
||||
};
|
||||
|
||||
const IMAGE_EXT = new Set([".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".avif"]);
|
||||
|
||||
const logLines = [];
|
||||
const log = (...args) => {
|
||||
const line = args.map(String).join(" ");
|
||||
logLines.push(line);
|
||||
console.log(...args);
|
||||
};
|
||||
|
||||
function hashFile(filePath) {
|
||||
const data = fs.readFileSync(filePath);
|
||||
return crypto.createHash("md5").update(data).digest("hex");
|
||||
}
|
||||
|
||||
function walk(dir) {
|
||||
const out = [];
|
||||
if (!fs.existsSync(dir)) return out;
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) out.push(...walk(full));
|
||||
else if (entry.isFile()) out.push(full);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function rel(from, to) {
|
||||
return path.relative(from, to).split(path.sep).join("/");
|
||||
}
|
||||
|
||||
function discoverSources() {
|
||||
if (!fs.existsSync(FTP_ROOT)) return [];
|
||||
const pairs = [];
|
||||
for (const entry of fs.readdirSync(FTP_ROOT, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const destName = DEST_ALIASES[entry.name] ?? entry.name;
|
||||
pairs.push({
|
||||
src: path.join(FTP_ROOT, entry.name),
|
||||
dest: path.join(PUBLIC_IMAGES, destName),
|
||||
srcLabel: `ftp-images/${entry.name}`,
|
||||
destLabel: `public/images/${destName}`,
|
||||
});
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
function syncPair({ src, dest, srcLabel, destLabel }) {
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
|
||||
const srcFiles = walk(src).filter((f) => IMAGE_EXT.has(path.extname(f).toLowerCase()));
|
||||
const destFiles = walk(dest).filter((f) => IMAGE_EXT.has(path.extname(f).toLowerCase()));
|
||||
const srcSet = new Set(srcFiles.map((f) => rel(src, f)));
|
||||
|
||||
const copied = [];
|
||||
const updated = [];
|
||||
const unchanged = [];
|
||||
const removed = [];
|
||||
|
||||
for (const file of srcFiles) {
|
||||
const relPath = rel(src, file);
|
||||
const target = path.join(dest, relPath);
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||
|
||||
if (!fs.existsSync(target)) {
|
||||
if (!DRY_RUN) fs.copyFileSync(file, target);
|
||||
copied.push(relPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hashFile(file) !== hashFile(target)) {
|
||||
if (!DRY_RUN) fs.copyFileSync(file, target);
|
||||
updated.push(relPath);
|
||||
} else {
|
||||
unchanged.push(relPath);
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of destFiles) {
|
||||
const relPath = rel(dest, file);
|
||||
if (!srcSet.has(relPath)) {
|
||||
if (!DRY_RUN) fs.unlinkSync(file);
|
||||
removed.push(relPath);
|
||||
}
|
||||
}
|
||||
|
||||
log(`\n=== ${srcLabel} -> ${destLabel} ===`);
|
||||
log(` new: ${copied.length} updated: ${updated.length} unchanged: ${unchanged.length} removed: ${removed.length}`);
|
||||
for (const f of copied) log(` + ${f}`);
|
||||
for (const f of updated) log(` ~ ${f}`);
|
||||
for (const f of removed) log(` - ${f}`);
|
||||
|
||||
return { copied, updated, unchanged, removed, src: srcLabel, dest: destLabel };
|
||||
}
|
||||
|
||||
function checkMenuReferences() {
|
||||
const menuFile = path.join(ROOT, "infrastructure", "menu", "static-menu-data.ts");
|
||||
if (!fs.existsSync(menuFile)) {
|
||||
log("\nMenu file not found; skipping reference check.");
|
||||
return { unique: [], missing: [] };
|
||||
}
|
||||
|
||||
const text = fs.readFileSync(menuFile, "utf8");
|
||||
const refs = [...text.matchAll(/["'`](\/images\/[^"'`]+)["'`]/g)].map((m) => m[1]);
|
||||
const unique = [...new Set(refs)];
|
||||
const missing = unique.filter((ref) => !fs.existsSync(path.join(ROOT, "public", ref.replace(/^\//, ""))));
|
||||
|
||||
log(`\n=== Menu references: ${unique.length} unique, ${missing.length} missing ===`);
|
||||
for (const m of missing) log(` ! ${m}`);
|
||||
return { unique, missing };
|
||||
}
|
||||
|
||||
function writeOutputs(result) {
|
||||
if (!DRY_RUN) {
|
||||
fs.writeFileSync(LOG_FILE, logLines.join("\n") + "\n");
|
||||
fs.writeFileSync(RESULT_FILE, JSON.stringify(result, null, 2) + "\n");
|
||||
log(`\nWrote ${path.relative(ROOT, LOG_FILE)}`);
|
||||
log(`Wrote ${path.relative(ROOT, RESULT_FILE)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(FTP_ROOT)) {
|
||||
console.error(`ftp-images not found at ${FTP_ROOT}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log(DRY_RUN ? "DRY RUN\n" : "Syncing...\n");
|
||||
|
||||
const pairs = discoverSources();
|
||||
if (pairs.length === 0) {
|
||||
log("No subfolders found in ftp-images/");
|
||||
writeOutputs({ totals: { copied: 0, updated: 0, unchanged: 0, removed: 0 }, pairs: [], menu: { unique: [], missing: [] } });
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const pairResults = [];
|
||||
const totals = { copied: 0, updated: 0, unchanged: 0, removed: 0 };
|
||||
|
||||
for (const pair of pairs) {
|
||||
const result = syncPair(pair);
|
||||
pairResults.push(result);
|
||||
totals.copied += result.copied.length;
|
||||
totals.updated += result.updated.length;
|
||||
totals.unchanged += result.unchanged.length;
|
||||
totals.removed += result.removed.length;
|
||||
}
|
||||
|
||||
log(`\n=== TOTALS: ${totals.copied} new, ${totals.updated} updated, ${totals.unchanged} unchanged, ${totals.removed} removed ===`);
|
||||
|
||||
const menu = checkMenuReferences();
|
||||
writeOutputs({ dryRun: DRY_RUN, totals, pairs: pairResults, menu });
|
||||
|
||||
if (menu.missing.length) process.exitCode = 1;
|
||||
Reference in New Issue
Block a user