import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const DOCS_DIR = path.resolve(__dirname, '../docs'); // Non-recursive scan of DOCS_DIR function getDocsFiles(dir) { const files = fs.readdirSync(dir); const mdFiles = []; files.forEach(file => { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); if (!stat.isDirectory() && file.endsWith('.md')) { mdFiles.push(filePath); } }); return mdFiles; } const files = getDocsFiles(DOCS_DIR); let hasErrors = false; console.log('šŸ” Scanning markdown files for formatting issues...\n'); files.forEach(file => { const content = fs.readFileSync(file, 'utf-8'); const lines = content.split('\n'); const relativePath = path.relative(process.cwd(), file); lines.forEach((line, index) => { const lineNum = index + 1; let errors = []; // Check 1: Starred links must be bolded // Pattern: * ⭐ [Link] -> Bad // Pattern: * ⭐ **[Link] -> Good // Only applies to list items starting with * or - if (/^\s*[*+-]\s+⭐/.test(line)) { // It's a starred list item. // Check if the text immediately following "⭐ " starts with "**" // We look for the star, then optional spaces, then ensure "**" follows. if (!/⭐\s*\*\*/.test(line)) { errors.push('Starred item not bolded (expected * ⭐ **Link**)'); } } // Check 2: Space between ] ( if (/\]\s+\(http/.test(line)) { errors.push('Space between bracket and parenthesis in link'); } // Check 3: Missing closing bracket ] // Pattern: [Text(http... // We look for [ followed by (http without ] in between. if (/\[[^\]]*\(http/.test(line)) { errors.push('Possible missing closing bracket "]"'); } // Check 4: Missing closing parenthesis ) // Pattern: [Text](http... where it ends without ) // We look for "](http..." followed by space or end of line, but NOT ending with ) // regex: \]\(http[^)]*($|\s) matches "](http://url" at EOL or "](http://url " const missingParenMatch = line.match(/\]\((http[^)]+?)($|\s)/); if (missingParenMatch) { errors.push(`Possible broken link (missing closing parenthesis or trailing space): ${missingParenMatch[1]}`); } // Check 5: Double parenthesis in link // specific pattern: ](url)) // This is often valid if inside parenthesis: (See [Link](url)) // We only flag if parentheses are UNBALANCED in the line. if (/\]\([^)]+\)\)/.test(line)) { const openParens = (line.match(/\(/g) || []).length; const closeParens = (line.match(/\)/g) || []).length; if (closeParens > openParens) { errors.push('Double closing parenthesis in link (Unbalanced)'); } } // Check 6: Double spaces // We want to avoid double spaces in the text, but ignore leading indentation. // We trim start of line to ignore indentation, then check for " ". const trimmedLine = line.trimStart(); if (trimmedLine.includes(' ')) { errors.push('Double space detected'); } // Check 7: Broken Bold Syntax // Pattern: ** Text**, **Text **, or ** Text ** // We temporarily replace inline code to avoid false positives const boldLine = line.replace(/`[^`]+`/g, 'PLACEHOLDER'); if (boldLine.includes('**')) { const parts = boldLine.split('**'); // Check odd segments (inside the stars) for (let i = 1; i < parts.length; i += 2) { // Ensure we have a closing pair on this line if (i + 1 < parts.length) { const text = parts[i]; if (text.length > 0 && (/^\s/.test(text) || /\s$/.test(text))) { errors.push(`Broken bold syntax (leading/trailing space) in "**${text}**"`); } } } } if (errors.length > 0) { hasErrors = true; errors.forEach(err => { console.log(`${relativePath}:${lineNum} - ${err}`); console.log(` Line: ${line.trim()}`); }); } }); }); if (!hasErrors) { console.log('āœ… No formatting issues found.'); } else { // console.log('\nāŒ Issues found. Please review.'); process.exit(1); }