1
Files
2025/tools/lib/bundles.js

164 lines
4.7 KiB
JavaScript

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<string>} 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<string|null>} 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<string>} 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,
};