const fs = require("fs/promises"); const path = require("path"); async function collectMarkdownFiles(rootDir, { skipIndex = true } = {}) { const entries = await fs.readdir(rootDir, { withFileTypes: true }); const files = []; for (const entry of entries) { const fullPath = path.join(rootDir, entry.name); if (entry.isDirectory()) { const nested = await collectMarkdownFiles(fullPath, { skipIndex }); files.push(...nested); continue; } if (!entry.isFile()) continue; if (!entry.name.toLowerCase().endsWith(".md")) continue; if (skipIndex && entry.name === "_index.md") continue; files.push(fullPath); } return files; } async function collectSectionIndexDirs(rootDir) { const sections = new Set(); async function walk(dir) { let entries; try { entries = await fs.readdir(dir, { withFileTypes: true }); } catch (error) { console.error(`Skipping section scan for ${dir}: ${error.message}`); return; } let hasIndex = false; for (const entry of entries) { if (entry.isFile() && entry.name.toLowerCase() === "_index.md") { hasIndex = true; break; } } if (hasIndex) { sections.add(path.resolve(dir)); } for (const entry of entries) { if (entry.isDirectory()) { await walk(path.join(dir, entry.name)); } } } await walk(rootDir); return sections; } async function resolveMarkdownTargets(inputs, { rootDir = process.cwd(), skipIndex = true } = {}) { if (!inputs || inputs.length === 0) { return collectMarkdownFiles(rootDir, { skipIndex }); } const targets = new Set(); for (const input of inputs) { const resolved = path.resolve(input); try { const stat = await fs.stat(resolved); if (stat.isDirectory()) { const nested = await collectMarkdownFiles(resolved, { skipIndex }); nested.forEach((file) => targets.add(file)); continue; } if (stat.isFile()) { const lower = resolved.toLowerCase(); if (!lower.endsWith(".md")) continue; if (skipIndex && path.basename(resolved) === "_index.md") continue; targets.add(resolved); } } catch (error) { console.error(`Skipping ${input}: ${error.message}`); } } return Array.from(targets); } /** * Collecte tous les fichiers correspondant a une liste d'extensions. * @param {string} rootDir Racine a parcourir. * @param {string[]} extensions Extensions attendues, avec le point. * @param {{ skipDirs?: string[] }} options Options de parcours. * @returns {Promise} Fichiers trouves, tries par chemin. */ async function collectFilesByExtensions(rootDir, extensions, options = {}) { const normalizedExtensions = new Set(); for (const extension of extensions) { if (typeof extension !== "string") { continue; } const candidate = extension.trim().toLowerCase(); if (!candidate) { continue; } normalizedExtensions.add(candidate); } if (normalizedExtensions.size === 0) { return []; } const skipDirs = new Set([".git", "node_modules"]); if (Array.isArray(options.skipDirs)) { for (const directoryName of options.skipDirs) { if (typeof directoryName !== "string") { continue; } const candidate = directoryName.trim(); if (!candidate) { continue; } skipDirs.add(candidate); } } const files = []; await walk(rootDir); files.sort((a, b) => a.localeCompare(b)); return files; async function walk(currentDir) { const entries = await fs.readdir(currentDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(currentDir, entry.name); if (entry.isDirectory()) { if (skipDirs.has(entry.name)) { continue; } await walk(fullPath); continue; } if (!entry.isFile()) { continue; } const extension = path.extname(entry.name).toLowerCase(); if (!normalizedExtensions.has(extension)) { continue; } files.push(fullPath); } } } async function collectBundles(rootDir) { const bundles = []; await walk(rootDir, rootDir, bundles); bundles.sort((a, b) => a.relativePath.localeCompare(b.relativePath)); return bundles; } async function walk(rootDir, currentDir, bucket) { let entries; try { entries = await fs.readdir(currentDir, { withFileTypes: true }); } catch (error) { console.warn(`⚠️ Lecture impossible de ${currentDir}: ${error.message}`); return; } let hasIndex = false; for (const entry of entries) { if (entry.isFile() && entry.name === "index.md") { hasIndex = true; break; } } if (hasIndex) { const relative = path.relative(rootDir, currentDir); const parts = relative.split(path.sep).filter(Boolean); const slug = parts[parts.length - 1] || path.basename(currentDir); bucket.push({ dir: currentDir, indexPath: path.join(currentDir, "index.md"), relativePath: parts.join("/"), parts, slug, }); } for (const entry of entries) { if (!entry.isDirectory()) continue; if (entry.name === ".git" || entry.name === "node_modules") continue; await walk(rootDir, path.join(currentDir, entry.name), bucket); } } module.exports = { collectMarkdownFiles, collectSectionIndexDirs, resolveMarkdownTargets, collectFilesByExtensions, collectBundles, };