import fs from 'node:fs/promises'; import path from 'node:path'; const GLOB_CHARS = /[*?]/; const DOCS_FILE = /\.mdx?$/i; const REGEX_SPECIAL = /[.+^${}()|\\[\]]/; const SKIPPED_DIRS = new Set(['.git', 'node_modules']); function toPosix(filePath: string): string { return filePath.split(path.sep).join('/'); } function normalizeArg(arg: string): string { return toPosix(arg).replace(/^\.\//, ''); } function globToRegExp(pattern: string): RegExp { let source = ''; let i = 0; while (i < pattern.length) { const char = pattern[i]!; if (char === '*') { if (pattern[i + 1] === '*') { if (pattern[i + 2] === '/') { source += '(?:[^/]+/)*'; i += 3; } else { source += '.*'; i += 2; } } else { source += '[^/]*'; i += 1; } } else if (char === '?') { source += '[^/]'; i += 1; } else { source += REGEX_SPECIAL.test(char) ? `\\${char}` : char; i += 1; } } return new RegExp(`^${source}$`); } async function isFile(filePath: string): Promise { try { return (await fs.stat(filePath)).isFile(); } catch { return false; } } async function walk(baseDir: string, dir = ''): Promise { const entries = await fs.readdir(path.join(baseDir, dir), { withFileTypes: true }); const files: string[] = []; for (const entry of entries) { if (SKIPPED_DIRS.has(entry.name)) continue; const rel = dir ? `${dir}/${entry.name}` : entry.name; if (entry.isDirectory()) { files.push(...(await walk(baseDir, rel))); } else { files.push(rel); } } return files; } export async function resolveExplicitFiles(baseDir: string, args: string[]): Promise { let allFiles: string[] | null = null; const matched: string[] = []; const unmatched: string[] = []; for (const arg of args) { const pattern = normalizeArg(arg); if (GLOB_CHARS.test(pattern)) { allFiles = allFiles ?? (await walk(baseDir)); const regex = globToRegExp(pattern); const hits = allFiles.filter((file) => DOCS_FILE.test(file) && regex.test(file)).sort(); if (hits.length === 0) { unmatched.push(arg); } else { matched.push(...hits); } } else if (await isFile(path.join(baseDir, pattern))) { matched.push(pattern); } else { unmatched.push(arg); } } if (unmatched.length > 0) { throw new Error( `No matching files for: ${unmatched.join(', ')} (globs match .md and .mdx files)` ); } return [...new Set(matched)]; }