Files
2026-07-21 16:50:58 +02:00

116 lines
4.2 KiB
JavaScript
Raw Permalink 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.
/**
* Assets IO — lecture des métadonnées PNG, copie des fichiers ingrédients
* et branding vers `apps/web/public`, sélection d'une cover recette.
*
* Aucun parsing : ces fonctions touchent le système de fichiers. Le parsing
* bentext / sprite vit dans `parse-bentext.mjs` et `sprites.mjs`.
*/
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
/** Fichiers dingrédients copiés tels quels vers `apps/web/public`. */
const INGREDIENT_ASSET_FILES = ["ingredients.png", "ingredient-sprites.csv", "ingredient-unknown.png"];
/** Fichiers de branding copiés tels quels vers `apps/web/public/branding`. */
export const BRANDING_FILES = ["onigiri-logo.png", "og.jpg"];
export const PATHS = {
repoRoot,
recipesDir: path.join(repoRoot, "ressources/bentext"),
outDir: path.join(repoRoot, "ressources/generated"),
webPublicDir: path.join(repoRoot, "apps/web/public/recipes"),
webCoverDir: path.join(repoRoot, "apps/web/public/recipe-covers"),
webBrandingDir: path.join(repoRoot, "apps/web/public/branding"),
webPublicRoot: path.join(repoRoot, "apps/web/public"),
publicAssetsDir: path.join(repoRoot, "ressources/public"),
placeholderCoversDir: path.join(repoRoot, "ressources/recipe-placeholder-covers"),
/** Grille des tuiles : coordonnées et alias listés dans ce fichier (plus dAPI). */
ingredientSpritesBentextPath: path.join(repoRoot, "ressources/public/ingredient-sprites.bentext"),
ingredientsPngPath: path.join(repoRoot, "ressources/public/ingredients.png")
};
const COVER_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp"];
const readPngDimensions = async (filePath) => {
try {
const buf = await fs.readFile(filePath);
if (buf.length < 24 || buf[0] !== 0x89) return null;
const width = buf.readUInt32BE(16);
const height = buf.readUInt32BE(20);
return { width, height };
} catch {
return null;
}
};
const copyIngredientAssetsToWeb = async () => {
await fs.mkdir(PATHS.webPublicRoot, { recursive: true });
for (const name of INGREDIENT_ASSET_FILES) {
const src = path.join(PATHS.publicAssetsDir, name);
const dest = path.join(PATHS.webPublicRoot, name);
try {
await fs.copyFile(src, dest);
} catch (error) {
if (error && error.code === "ENOENT") continue;
throw error;
}
}
};
const copyBrandingAssets = async () => {
await fs.mkdir(PATHS.webBrandingDir, { recursive: true });
for (const name of BRANDING_FILES) {
const src = path.join(PATHS.publicAssetsDir, name);
const dest = path.join(PATHS.webBrandingDir, name);
try {
await fs.copyFile(src, dest);
} catch (error) {
if (error && error.code === "ENOENT") {
console.warn(`Branding asset missing, skip: ${name}`);
continue;
}
throw error;
}
}
};
/**
* Sélectionne une cover pour `slug` :
* 1. Image HD dans `ressources/recipe-placeholder-covers/<slug>.png`.
* 2. Sinon, image dans `ressources/public/<slug>.<ext>`.
* Copie le fichier dans `apps/web/public/recipe-covers/` et retourne lURL publique.
*/
const pickCoverImage = async (slug) => {
await fs.mkdir(PATHS.webCoverDir, { recursive: true });
/* Priorité aux visuels HD du dépôt ; sinon ressources/public (override legacy). */
const placeholderPng = path.join(PATHS.placeholderCoversDir, `${slug}.png`);
try {
await fs.access(placeholderPng);
const publicName = `${slug}.png`;
const target = path.join(PATHS.webCoverDir, publicName);
await fs.copyFile(placeholderPng, target);
return `/recipe-covers/${publicName}`;
} catch {
// continue
}
for (const extension of COVER_EXTENSIONS) {
const candidate = path.join(PATHS.publicAssetsDir, `${slug}${extension}`);
try {
await fs.access(candidate);
const publicName = `${slug}${extension}`;
const target = path.join(PATHS.webCoverDir, publicName);
await fs.copyFile(candidate, target);
return `/recipe-covers/${publicName}`;
} catch {
// continue
}
}
return null;
};
export { readPngDimensions, copyIngredientAssetsToWeb, copyBrandingAssets, pickCoverImage };