const fs = require("fs"); const path = require("path"); const repoRoot = path.resolve(__dirname, "..", ".."); const mealsPath = path.join(repoRoot, "data", "meals.json"); function validateThumbnail(meal, index) { if (meal.thumbnail === undefined) { return; } if ( !meal.thumbnail || typeof meal.thumbnail !== "object" || Array.isArray(meal.thumbnail) ) { throw new Error(`Meal ${index} has an invalid "thumbnail" object`); } if (meal.thumbnail.focus === undefined) { return; } const { focus } = meal.thumbnail; if (!focus || typeof focus !== "object" || Array.isArray(focus)) { throw new Error(`Meal ${index} has an invalid "thumbnail.focus" object`); } for (const axis of ["x", "y"]) { if (typeof focus[axis] !== "number" || !Number.isFinite(focus[axis])) { throw new Error( `Meal ${index} has a non-numeric thumbnail focus value for "${axis}"` ); } if (focus[axis] < 0 || focus[axis] > 1) { throw new Error( `Meal ${index} thumbnail focus "${axis}" must be between 0 and 1` ); } } } function validateMeals(meals) { if (!Array.isArray(meals)) { throw new Error("data/meals.json must contain an array"); } const ids = new Set(); for (const [index, meal] of meals.entries()) { if (!meal || typeof meal !== "object") { throw new Error(`Meal at index ${index} must be an object`); } for (const field of ["id", "title", "description"]) { if (typeof meal[field] !== "string" || meal[field].length === 0) { throw new Error(`Meal ${index} is missing required string field "${field}"`); } } if (meal.position !== undefined && typeof meal.position !== "string") { throw new Error(`Meal ${index} has a non-string "position" value`); } if (ids.has(meal.id)) { throw new Error(`Duplicate meal id "${meal.id}" found in data/meals.json`); } ids.add(meal.id); validateThumbnail(meal, index); } } function loadMeals() { const meals = JSON.parse(fs.readFileSync(mealsPath, "utf8")); validateMeals(meals); return meals; } module.exports = { loadMeals, mealsPath, repoRoot, };