const fs = require("node:fs"); const fsPromises = require("node:fs/promises"); const path = require("node:path"); const readline = require("node:readline/promises"); const { stdin, stdout } = require("node:process"); /** * Normalise une entrée utilisateur vers le dossier du bundle. * @param {string} input Chemin saisi par l'utilisateur. * @returns {string} Chemin absolu du bundle. */ function resolveBundlePath(input) { if (typeof input !== "string" || !input.trim()) { throw new Error("Le chemin du bundle est vide."); } const resolved = path.resolve(input); if (resolved.toLowerCase().endsWith(`${path.sep}index.md`)) { return path.dirname(resolved); } return resolved; } /** * Vérifie qu'un dossier correspond bien à un bundle Hugo. * @param {string} bundleDir Chemin absolu du bundle. */ function ensureBundleExists(bundleDir) { if (!fs.existsSync(bundleDir)) { throw new Error(`Le bundle ${bundleDir} est introuvable.`); } const stats = fs.statSync(bundleDir); if (!stats.isDirectory()) { throw new Error(`Le bundle ${bundleDir} n'est pas un dossier.`); } const indexPath = path.join(bundleDir, "index.md"); if (!fs.existsSync(indexPath)) { throw new Error(`Le bundle ${bundleDir} ne contient pas index.md.`); } } /** * Pose une question simple à l'utilisateur. * @param {string} query Texte affiché dans le terminal. * @returns {Promise} Réponse nettoyée. */ async function askQuestion(query) { const rl = readline.createInterface({ input: stdin, output: stdout }); const answer = await rl.question(query); rl.close(); return answer.trim(); } /** * Cherche le bundle modifié le plus récemment sous un répertoire racine. * @param {string} rootDir Racine à parcourir. * @returns {Promise} Chemin absolu du dernier bundle trouvé. */ async function findLatestBundle(rootDir) { let latestPath = null; let latestTime = 0; await walk(rootDir); return latestPath; /** * Parcourt récursivement l'arborescence et conserve le bundle le plus récent. * @param {string} currentDir Dossier en cours d'analyse. */ async function walk(currentDir) { const entries = await fsPromises.readdir(currentDir, { withFileTypes: true }); let hasIndex = false; for (const entry of entries) { if (entry.isFile() && entry.name === "index.md") { hasIndex = true; break; } } if (hasIndex) { const stats = await fsPromises.stat(currentDir); if (stats.mtimeMs > latestTime) { latestTime = stats.mtimeMs; latestPath = currentDir; } return; } for (const entry of entries) { if (!entry.isDirectory()) { continue; } const childDir = path.join(currentDir, entry.name); await walk(childDir); } } } /** * Résout le bundle cible à partir d'un chemin manuel ou du dernier bundle trouvé. * @param {string|null|undefined} manualPath Chemin optionnel fourni en argument. * @param {{ contentDir: string, prompts?: { confirmLatest: Function, manualPath: string } }} options Options de résolution. * @returns {Promise} Chemin absolu du bundle retenu. */ async function promptForBundlePath(manualPath, options) { let contentDir = path.resolve("content"); if (options && typeof options.contentDir === "string" && options.contentDir.trim()) { contentDir = path.resolve(options.contentDir); } const defaultPrompts = { confirmLatest(latest) { return `Use latest bundle found: ${latest}? (Y/n) `; }, manualPath: "Enter the relative path to your bundle: ", }; let prompts = defaultPrompts; if (options && options.prompts && typeof options.prompts === "object") { prompts = { confirmLatest: defaultPrompts.confirmLatest, manualPath: defaultPrompts.manualPath, }; if (typeof options.prompts.confirmLatest === "function") { prompts.confirmLatest = options.prompts.confirmLatest; } if (typeof options.prompts.manualPath === "string" && options.prompts.manualPath.trim()) { prompts.manualPath = options.prompts.manualPath; } } if (typeof manualPath === "string" && manualPath.trim()) { return resolveBundlePath(manualPath); } const latest = await findLatestBundle(contentDir); if (!latest) { throw new Error("Aucun bundle n'a été trouvé sous content/."); } const confirm = await askQuestion(prompts.confirmLatest(latest)); if (confirm.toLowerCase() === "n") { const inputPath = await askQuestion(prompts.manualPath); return resolveBundlePath(inputPath); } return latest; } module.exports = { resolveBundlePath, ensureBundleExists, askQuestion, findLatestBundle, promptForBundlePath, };