173 lines
5.2 KiB
JavaScript
173 lines
5.2 KiB
JavaScript
/**
|
|
* Documentation Link Fixer
|
|
*
|
|
* Reads the audit report generated by audit-doc-links.js and applies fixes
|
|
* to broken links where a single match was found.
|
|
*
|
|
* Usage:
|
|
* node tools/fix-doc-links.js # Dry-run (preview changes)
|
|
* node tools/fix-doc-links.js --apply # Apply changes
|
|
*/
|
|
|
|
const { readFileSync, writeFileSync, existsSync } = require('node:fs');
|
|
const { resolve, relative } = require('node:path');
|
|
|
|
const REPORT_PATH = resolve(__dirname, '.link-audit-report.json');
|
|
|
|
// Colors for console output
|
|
const colors = {
|
|
reset: '\u001B[0m',
|
|
red: '\u001B[31m',
|
|
green: '\u001B[32m',
|
|
yellow: '\u001B[33m',
|
|
cyan: '\u001B[36m',
|
|
dim: '\u001B[2m',
|
|
};
|
|
|
|
/**
|
|
* Load the audit report.
|
|
*/
|
|
function loadReport() {
|
|
if (!existsSync(REPORT_PATH)) {
|
|
console.error(`\n ${colors.red}Error:${colors.reset} No audit report found.`);
|
|
console.error(` Run 'node tools/audit-doc-links.js' first.\n`);
|
|
process.exit(1);
|
|
}
|
|
|
|
try {
|
|
const content = readFileSync(REPORT_PATH, 'utf-8');
|
|
return JSON.parse(content);
|
|
} catch (error) {
|
|
console.error(`\n ${colors.red}Error:${colors.reset} Failed to parse audit report.`);
|
|
console.error(` ${error.message}\n`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply a fix to a file by replacing the original link with the suggested fix.
|
|
*/
|
|
function applyFix(filePath, originalLink, suggestedFix) {
|
|
const content = readFileSync(filePath, 'utf-8');
|
|
|
|
// Create the replacement pattern - we need to match the exact link in markdown syntax
|
|
// Original might have anchor, so we need to handle that
|
|
const originalWithoutAnchor = originalLink.split('#')[0];
|
|
const suggestedWithoutAnchor = suggestedFix.split('#')[0];
|
|
|
|
// Also preserve any anchor from the original if the fix doesn't include one
|
|
let finalFix = suggestedFix;
|
|
if (originalLink.includes('#') && !suggestedFix.includes('#')) {
|
|
const anchor = originalLink.slice(originalLink.indexOf('#'));
|
|
finalFix = suggestedFix + anchor;
|
|
}
|
|
|
|
// Replace the link - be careful to only replace inside markdown link syntax
|
|
const linkPattern = new RegExp(`\\]\\(${escapeRegex(originalLink)}\\)`, 'g');
|
|
|
|
const newContent = content.replace(linkPattern, `](${finalFix})`);
|
|
|
|
if (newContent === content) {
|
|
return false; // No change made
|
|
}
|
|
|
|
writeFileSync(filePath, newContent);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Escape special regex characters in a string.
|
|
*/
|
|
function escapeRegex(string) {
|
|
return string.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
}
|
|
|
|
/**
|
|
* Main function.
|
|
*/
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
const applyChanges = args.includes('--apply');
|
|
|
|
console.log('\n Documentation Link Fixer');
|
|
console.log(' ========================\n');
|
|
|
|
if (applyChanges) {
|
|
console.log(` ${colors.green}APPLYING CHANGES${colors.reset}\n`);
|
|
} else {
|
|
console.log(` ${colors.yellow}DRY RUN${colors.reset} - No files will be modified.`);
|
|
console.log(` Use --apply to apply changes.\n`);
|
|
}
|
|
|
|
const report = loadReport();
|
|
|
|
if (report.autoFixable.length === 0) {
|
|
console.log(` ${colors.green}✓${colors.reset} No auto-fixable links found.\n`);
|
|
process.exit(0);
|
|
}
|
|
|
|
console.log(` Found ${report.autoFixable.length} auto-fixable link(s).\n`);
|
|
|
|
const fixed = [];
|
|
const failed = [];
|
|
|
|
// Group fixes by file for efficiency
|
|
const byFile = {};
|
|
for (const item of report.autoFixable) {
|
|
if (!byFile[item.sourceFileAbsolute]) {
|
|
byFile[item.sourceFileAbsolute] = [];
|
|
}
|
|
byFile[item.sourceFileAbsolute].push(item);
|
|
}
|
|
|
|
for (const [filePath, items] of Object.entries(byFile)) {
|
|
const displayPath = relative(process.cwd(), filePath);
|
|
console.log(` ${colors.cyan}${displayPath}${colors.reset}`);
|
|
|
|
for (const item of items) {
|
|
console.log(` Line ${item.line}:`);
|
|
console.log(` ${colors.dim}Old:${colors.reset} ${item.originalLink}`);
|
|
console.log(` ${colors.dim}New:${colors.reset} ${item.suggestedFix}`);
|
|
|
|
if (applyChanges) {
|
|
try {
|
|
const success = applyFix(filePath, item.originalLink, item.suggestedFix);
|
|
if (success) {
|
|
console.log(` ${colors.green}✓ Fixed${colors.reset}`);
|
|
fixed.push(item);
|
|
} else {
|
|
console.log(` ${colors.yellow}⚠ No match found (may have been fixed already)${colors.reset}`);
|
|
}
|
|
} catch (error) {
|
|
console.log(` ${colors.red}✗ Error: ${error.message}${colors.reset}`);
|
|
failed.push({ ...item, error: error.message });
|
|
}
|
|
}
|
|
console.log();
|
|
}
|
|
}
|
|
|
|
// Summary
|
|
console.log(' ' + '─'.repeat(50));
|
|
console.log('\n SUMMARY\n');
|
|
|
|
if (applyChanges) {
|
|
console.log(` Fixed: ${colors.green}${fixed.length}${colors.reset}`);
|
|
if (failed.length > 0) {
|
|
console.log(` Failed: ${colors.red}${failed.length}${colors.reset}`);
|
|
}
|
|
console.log(`\n Run 'node tools/audit-doc-links.js' to verify remaining issues.\n`);
|
|
} else {
|
|
console.log(` Would fix: ${colors.yellow}${report.autoFixable.length}${colors.reset} link(s)`);
|
|
console.log(`\n To apply these fixes, run:`);
|
|
console.log(` node tools/fix-doc-links.js --apply\n`);
|
|
}
|
|
|
|
process.exit(failed.length > 0 ? 1 : 0);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('Error:', error.message);
|
|
process.exit(1);
|
|
});
|