From 464fbc13a60e9bfcce59d62cfea11266149836f8 Mon Sep 17 00:00:00 2001 From: Blessing Krofegha Date: Mon, 3 Mar 2025 23:21:02 +0100 Subject: [PATCH] fixing redirects issue --- notes/fix-redirects.md | 53 ++++++---- utils/fix-redirects.ts | 146 ++++++++++++++++++--------- utils/redirects.ts | 224 ++++++++++++++++++++++++----------------- 3 files changed, 259 insertions(+), 164 deletions(-) mode change 100644 => 100755 utils/fix-redirects.ts mode change 100644 => 100755 utils/redirects.ts diff --git a/notes/fix-redirects.md b/notes/fix-redirects.md index fdb47fdb1..851656b9c 100644 --- a/notes/fix-redirects.md +++ b/notes/fix-redirects.md @@ -3,23 +3,27 @@ ## Scripts overview Two scripts help maintain internal links when pages are redirected: -* `check-redirects`: Identifies links that need updating based on the `_redirects` file. -* `fix-redirects`: Automatically updates links to match `_redirects` entries. +* `check-redirects`: Identifies links that point to old URLs that have redirects defined in the `_redirects` file. +* `fix-redirects`: Automatically updates links to use the new destination URLs defined in the `_redirects` file. -## Checking for broken links +## Checking for outdated links Run the check script: ```bash -pnpm lint //OR +pnpm lint # OR pnpm check-redirects ``` -## What it does + +## What `check-redirects` does * Scans all `.mdx` files in the docs -* Compares internal links against `_redirects` file -* Reports any outdated links that need updating -* Provides a summary of total, broken, and valid links +* Reads your existing `_redirects` file (which contains mappings of old URLs to new URLs) +* Identifies internal links that match the "from" paths in your redirects file +* Reports links that should be updated to use the new destination URLs instead of relying on redirects +* Provides a summary of total pages, pages with outdated links, and valid pages + +The script does NOT check for broken links (404s) or suggest new redirects to add. It's specifically for maintaining internal link consistency by ensuring you're not using outdated URLs that require redirects. ## Example output @@ -28,36 +32,40 @@ File "builders/overview.mdx" contains outdated link "/chain/overview" - should b Summary: Total pages 🔍 - 50 -Pages broken 🚫 - 2 +Pages with outdated links 🚫 - 2 Pages OK ✅ - 48 - ``` -## Fixing broken links +## Fixing outdated links Fix links automatically: ```bash -pnpm fix //OR +pnpm fix # OR pnpm fix-redirects ``` -## What it does +## What `fix-redirects` does + +* Reads your existing `_redirects` file (which contains mappings of old URLs to new URLs) +* Scans all `.mdx` files looking for internal links +* Automatically updates links that match "from" paths to use their corresponding "to" paths +* Handles both Markdown links `[text](/path)` and HTML links `href="/path"` +* Saves the modified files with the updated links +* Reports which files were changed and which links were updated +* Provides a summary showing how many files were fixed versus unchanged -* Updates all internal links to match `_redirects` entries -* Preserves other content and formatting -* Shows which files and links were updated -* Provides a summary of changes made +This script is an automated fix tool that saves you from having to manually update all the outdated links that `check-redirects` would report. ## Example output ```bash -Fixed in "builders/overview.mdx": /chain/overview → /stack/overview +Fixed in "builders/overview.mdx": "/chain/overview" → "/stack/overview" Summary: -Total files 🔍 - 50 -Files fixed ✅ - 2 -Files skipped ⏭️ - 48 +Total pages checked 🔍 - 50 +Pages fixed ✅ - 2 +Pages unchanged ⏩ - 48 ``` ## Best practices @@ -80,4 +88,5 @@ Files skipped ⏭️ - 48 ## Common issues * Script fails: Ensure `_redirects` file exists in public folder, it should always be there! -* No broken links found: Verify `_redirects` entries are correct. \ No newline at end of file +* No outdated links found: Verify `_redirects` entries are correct or all links might already be updated. +* Links still broken after fixing: The script only updates links based on the `_redirects` file; it doesn't check if the destination URLs actually exist. \ No newline at end of file diff --git a/utils/fix-redirects.ts b/utils/fix-redirects.ts old mode 100644 new mode 100755 index 5d609c20b..772240446 --- a/utils/fix-redirects.ts +++ b/utils/fix-redirects.ts @@ -1,9 +1,12 @@ +#!/usr/bin/env node +// Save this file as utils/fix-redirects.ts + import * as fs from 'fs/promises'; import * as path from 'path'; const rootDir = path.join(process.cwd(), 'pages'); const redirectsPath = path.join(process.cwd(), 'public', '_redirects'); -const updates: string[] = []; +const fixed: string[] = []; // ANSI color codes const WHITE = '\x1b[37m'; @@ -20,80 +23,122 @@ interface Redirect { interface Summary { total: number; fixed: number; - skipped: number; + unchanged: number; } async function getRedirects(): Promise { - const content = await fs.readFile(redirectsPath, 'utf-8'); - return content.split('\n') - .filter(line => line.trim() && !line.startsWith('#')) - .map(line => { - const [from, to] = line.split(/\s+/); - return { from, to }; - }); + try { + const content = await fs.readFile(redirectsPath, 'utf-8'); + return content.split('\n') + .filter(line => line.trim() && !line.startsWith('#')) + .map(line => { + const [from, to] = line.split(/\s+/); + return { from, to }; + }); + } catch (error) { + console.error(`${YELLOW}${BOLD}Error reading redirects file:${RESET}`, error); + return []; + } } async function findMdxFiles(dir: string): Promise { const files: string[] = []; - const entries = await fs.readdir(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory() && !entry.name.startsWith('_')) { - files.push(...await findMdxFiles(fullPath)); - } else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) { - files.push(fullPath); + + try { + const entries = await fs.readdir(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory() && !entry.name.startsWith('_')) { + files.push(...await findMdxFiles(fullPath)); + } else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) { + files.push(fullPath); + } } + } catch (error) { + console.error(`${YELLOW}${BOLD}Error reading directory ${dir}:${RESET}`, error); } + return files; } async function fixFile(filePath: string, redirects: Redirect[]): Promise { - let content = await fs.readFile(filePath, 'utf-8'); - let hasChanges = false; - const relativeFilePath = path.relative(rootDir, filePath); - - redirects.forEach(redirect => { - const markdownRegex = new RegExp(`\\[([^\\]]+)\\]\\(${redirect.from}\\)`, 'g'); - const hrefRegex = new RegExp(`href="${redirect.from}"`, 'g'); - - if (content.match(markdownRegex) || content.match(hrefRegex)) { - content = content - .replace(markdownRegex, `[$1](${redirect.to})`) - .replace(hrefRegex, `href="${redirect.to}"`); - - updates.push(`${WHITE}Fixed in "${relativeFilePath}": ${YELLOW}${redirect.from}${WHITE} → ${GREEN}${redirect.to}${RESET}`); - hasChanges = true; - } - }); + try { + let content = await fs.readFile(filePath, 'utf-8'); + const relativeFilePath = path.relative(rootDir, filePath); + let fileChanged = false; + + // Fix markdown links - [text](link) + redirects.forEach(redirect => { + const markdownLinkRegex = new RegExp(`\\[([^\\]]+)\\]\\(${redirect.from.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\)`, 'g'); + if (markdownLinkRegex.test(content)) { + content = content.replace(markdownLinkRegex, `[$1](${redirect.to})`); + fixed.push(`${WHITE}Fixed in "${relativeFilePath}": ${YELLOW}"${redirect.from}"${WHITE} → ${GREEN}"${redirect.to}"${RESET}`); + fileChanged = true; + } + }); - if (hasChanges) { - await fs.writeFile(filePath, content); + // Fix HTML links - href="link" + redirects.forEach(redirect => { + const hrefRegex = new RegExp(`href="${redirect.from.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`, 'g'); + if (hrefRegex.test(content)) { + content = content.replace(hrefRegex, `href="${redirect.to}"`); + fixed.push(`${WHITE}Fixed in "${relativeFilePath}": ${YELLOW}"${redirect.from}"${WHITE} → ${GREEN}"${redirect.to}"${RESET}`); + fileChanged = true; + } + }); + + if (fileChanged) { + await fs.writeFile(filePath, content, 'utf-8'); + return true; + } + + return false; + } catch (error) { + console.error(`${YELLOW}${BOLD}Error fixing file ${filePath}:${RESET}`, error); + return false; } - - return hasChanges; } function printSummary(summary: Summary) { console.log('\nSummary:'); - console.log(`${WHITE}Total files 🔍 - ${summary.total}`); - console.log(`${GREEN}Files fixed ✅ - ${summary.fixed}`); - console.log(`${WHITE}Files skipped ⏭️ - ${summary.skipped}${RESET}`); + console.log(`${WHITE}Total pages checked 🔍 - ${summary.total}`); + console.log(`${GREEN}Pages fixed ✅ - ${summary.fixed}`); + console.log(`${WHITE}Pages unchanged ⏩ - ${summary.unchanged}${RESET}`); } async function main() { const summary: Summary = { total: 0, fixed: 0, - skipped: 0 + unchanged: 0 }; - console.log('Starting to fix redirect links...'); + console.log('Starting automatic redirect fix...'); console.log('Root directory:', rootDir); try { + // Check if directories exist + try { + await fs.access(rootDir); + } catch (error) { + console.error(`${YELLOW}${BOLD}Error: Root directory not found at ${rootDir}${RESET}`); + console.log('Current working directory:', process.cwd()); + process.exit(1); + } + + try { + await fs.access(redirectsPath); + } catch (error) { + console.error(`${YELLOW}${BOLD}Error: Redirects file not found at ${redirectsPath}${RESET}`); + process.exit(1); + } + const redirects = await getRedirects(); + console.log(`Loaded ${redirects.length} redirects from ${redirectsPath}`); + const files = await findMdxFiles(rootDir); + console.log(`Found ${files.length} MDX files to check`); summary.total = files.length; @@ -102,24 +147,25 @@ async function main() { if (wasFixed) { summary.fixed++; } else { - summary.skipped++; + summary.unchanged++; } } - if (updates.length > 0) { - console.log(`${GREEN}${BOLD}Fixed links:${RESET}`); - updates.forEach(update => console.log(update)); - printSummary(summary); + if (fixed.length > 0) { + console.log(`${GREEN}${BOLD}Fixed ${fixed.length} outdated links:${RESET}`); + fixed.forEach(message => console.log(message)); } else { - console.log(`${GREEN}No broken links found. Everything is up to date.${RESET}`); - printSummary(summary); + console.log(`${GREEN}All internal links are already up to date.${RESET}`); } + + printSummary(summary); } catch (error) { console.error(`${YELLOW}${BOLD}Error fixing redirects:${RESET}`, error); process.exit(1); } } +// Execute the script main().catch(error => { console.error(`${YELLOW}${BOLD}Error in main process:${RESET}`, error); process.exit(1); diff --git a/utils/redirects.ts b/utils/redirects.ts old mode 100644 new mode 100755 index bd2cb66b1..fc128f999 --- a/utils/redirects.ts +++ b/utils/redirects.ts @@ -1,3 +1,6 @@ +#!/usr/bin/env node +// Save this file as utils/redirects.ts + import * as fs from 'fs/promises'; import * as path from 'path'; @@ -13,123 +16,160 @@ const RESET = '\x1b[0m'; const BOLD = '\x1b[1m'; interface Redirect { - from: string; - to: string; + from: string; + to: string; } interface Summary { - total: number; - ok: number; - errors: number; + total: number; + ok: number; + errors: number; } function formatWarning(filePath: string, fromLink: string, toLink: string): string { - return `${WHITE}File "${filePath}" contains outdated link ${YELLOW}"${fromLink}"${WHITE} - should be updated to ${GREEN}"${toLink}"${RESET}`; + return `${WHITE}File "${filePath}" contains outdated link ${YELLOW}"${fromLink}"${WHITE} - should be updated to ${GREEN}"${toLink}"${RESET}`; } async function getRedirects(): Promise { - const content = await fs.readFile(redirectsPath, 'utf-8'); - return content.split('\n') - .filter(line => line.trim() && !line.startsWith('#')) - .map(line => { - const [from, to] = line.split(/\s+/); - return { from, to }; - }); + try { + const content = await fs.readFile(redirectsPath, 'utf-8'); + return content.split('\n') + .filter(line => line.trim() && !line.startsWith('#')) + .map(line => { + const [from, to] = line.split(/\s+/); + return { from, to }; + }); + } catch (error) { + console.error(`${YELLOW}${BOLD}Error reading redirects file:${RESET}`, error); + return []; + } } async function findMdxFiles(dir: string): Promise { - const files: string[] = []; - const entries = await fs.readdir(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory() && !entry.name.startsWith('_')) { - files.push(...await findMdxFiles(fullPath)); - } else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) { - files.push(fullPath); - } - } - return files; + const files: string[] = []; + + try { + const entries = await fs.readdir(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory() && !entry.name.startsWith('_')) { + files.push(...await findMdxFiles(fullPath)); + } else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) { + files.push(fullPath); + } + } + } catch (error) { + console.error(`${YELLOW}${BOLD}Error reading directory ${dir}:${RESET}`, error); + } + + return files; } function extractLinks(content: string): string[] { - const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; - const hrefRegex = /href="([^"]+)"/g; - const links: string[] = []; - - let match; - while ((match = markdownLinkRegex.exec(content)) !== null) { - if (!match[2].startsWith('http')) { - links.push(match[2]); - } - } - while ((match = hrefRegex.exec(content)) !== null) { - if (!match[1].startsWith('http')) { - links.push(match[1]); - } - } - return links; + const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; + const hrefRegex = /href="([^"]+)"/g; + const links: string[] = []; + + let match; + while ((match = markdownLinkRegex.exec(content)) !== null) { + if (!match[2].startsWith('http')) { + links.push(match[2]); + } + } + while ((match = hrefRegex.exec(content)) !== null) { + if (!match[1].startsWith('http')) { + links.push(match[1]); + } + } + return links; } async function checkFile(filePath: string, redirects: Redirect[]): Promise { - const content = await fs.readFile(filePath, 'utf-8'); - const links = extractLinks(content); - const relativeFilePath = path.relative(rootDir, filePath); - - links.forEach(link => { - const redirect = redirects.find(r => r.from === link); - if (redirect) { - warnings.push(formatWarning(relativeFilePath, link, redirect.to)); - } - }); + try { + const content = await fs.readFile(filePath, 'utf-8'); + const links = extractLinks(content); + const relativeFilePath = path.relative(rootDir, filePath); + + for (const link of links) { + const redirect = redirects.find(r => r.from === link); + if (redirect) { + warnings.push(formatWarning(relativeFilePath, link, redirect.to)); + } + } + } catch (error) { + console.error(`${YELLOW}${BOLD}Error checking file ${filePath}:${RESET}`, error); + } } function printSummary(summary: Summary) { - console.log('\nSummary:'); - console.log(`${WHITE}Total pages 🔍 - ${summary.total}`); - console.log(`${YELLOW}Pages broken 🚫 - ${summary.errors}`); - console.log(`${GREEN}Pages OK ✅ - ${summary.ok}${RESET}`); + console.log('\nSummary:'); + console.log(`${WHITE}Total pages 🔍 - ${summary.total}`); + console.log(`${YELLOW}Pages with outdated links 🚫 - ${summary.errors}`); + console.log(`${GREEN}Pages OK ✅ - ${summary.ok}${RESET}`); } async function main() { - const summary: Summary = { - total: 0, - ok: 0, - errors: 0 - }; - - console.log('Starting redirect link check...'); - console.log('Root directory:', rootDir); - - try { - const redirects = await getRedirects(); - const files = await findMdxFiles(rootDir); - - summary.total = files.length; - - for (const file of files) { - await checkFile(file, redirects); - } - - summary.errors = warnings.length; - summary.ok = summary.total - summary.errors; - - if (warnings.length > 0) { - console.log(`${YELLOW}${BOLD}Links that need updating:${RESET}`); - warnings.forEach(warning => console.log(warning)); - printSummary(summary); - process.exit(1); - } else { - console.log(`${GREEN}All internal links are up to date.${RESET}`); - printSummary(summary); - } - } catch (error) { - console.error(`${YELLOW}${BOLD}Error checking redirects:${RESET}`, error); - process.exit(1); - } + const summary: Summary = { + total: 0, + ok: 0, + errors: 0 + }; + + console.log('Starting redirect link check...'); + console.log('Root directory:', rootDir); + + try { + // Check if directories exist + try { + await fs.access(rootDir); + } catch (error) { + console.error(`${YELLOW}${BOLD}Error: Root directory not found at ${rootDir}${RESET}`); + console.log('Current working directory:', process.cwd()); + process.exit(1); + } + + try { + await fs.access(redirectsPath); + } catch (error) { + console.error(`${YELLOW}${BOLD}Error: Redirects file not found at ${redirectsPath}${RESET}`); + process.exit(1); + } + + const redirects = await getRedirects(); + console.log(`Loaded ${redirects.length} redirects from ${redirectsPath}`); + + const files = await findMdxFiles(rootDir); + console.log(`Found ${files.length} MDX files to check`); + + summary.total = files.length; + + for (const file of files) { + await checkFile(file, redirects); + } + + // Count files with errors, not total errors + const filesWithErrors = new Set(warnings.map(w => w.split('"')[1])).size; + summary.errors = filesWithErrors; + summary.ok = summary.total - filesWithErrors; + + if (warnings.length > 0) { + console.log(`${YELLOW}${BOLD}Found ${warnings.length} links that need updating:${RESET}`); + warnings.forEach(warning => console.log(warning)); + printSummary(summary); + process.exit(1); + } else { + console.log(`${GREEN}All internal links are up to date.${RESET}`); + printSummary(summary); + } + } catch (error) { + console.error(`${YELLOW}${BOLD}Error checking redirects:${RESET}`, error); + process.exit(1); + } } +// Execute the script main().catch(error => { - console.error(`${YELLOW}${BOLD}Error in main process:${RESET}`, error); - process.exit(1); + console.error(`${YELLOW}${BOLD}Error in main process:${RESET}`, error); + process.exit(1); }); \ No newline at end of file