#!/usr/bin/env node import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { execSync } from 'child_process'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Get the project root const PROJECT_ROOT = path.join(__dirname, '..'); const APP_DIR = path.join(PROJECT_ROOT, 'app'); // Import utilities import { discoverPages, generateSitemapXml, analyzeInternalLinks } from '../lib/seo-utils.js'; import { defaultConfig as defaultLinkConfig, calculatePageScore, getKeywordsForTarget, getAnchorTexts, isExcludedRoute, getMaxLinksForPage, getMinWordsBetweenLinks, findKeywordsInContent } from '../lib/internal-link-config.js'; // Import readline only when needed let readline; /** * Display colored output in console */ function colorize(text, color) { const colors = { reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m' }; return `${colors[color]}${text}${colors.reset}`; } /** * Display section header */ function displayHeader(title) { console.log(`\n${colorize('='.repeat(50), 'cyan')}`); console.log(colorize(` ${title}`, 'cyan')); console.log(colorize('='.repeat(50), 'cyan')); } /** * Display success message */ function displaySuccess(message) { console.log(colorize(`āœ“ ${message}`, 'green')); } /** * Display error message */ function displayError(message) { console.log(colorize(`āœ— ${message}`, 'red')); } /** * Display warning message */ function displayWarning(message) { console.log(colorize(`⚠ ${message}`, 'yellow')); } /** * Display info message */ function displayInfo(message) { console.log(colorize(`ℹ ${message}`, 'blue')); } /** * Show help information */ function showHelp() { displayHeader('SEO Internal Link Tool - Help'); console.log(colorize('Usage:', 'white')); console.log(' node seo-internal-link-tool.js [options]'); console.log(''); console.log(colorize('Commands:', 'white')); console.log(' sitemap Generate sitemap from React pages'); console.log(' analyze Analyze internal links'); console.log(' optimize Optimize internal links'); console.log(' report Generate comprehensive SEO report'); console.log(' json-ld Generate JSON-LD structured data'); console.log(' interactive Start interactive mode'); console.log(''); console.log(colorize('Options:', 'white')); console.log(' --output Output file path'); console.log(' --format Output format (json, html, csv, markdown)'); console.log(' --verbose Enable verbose output'); console.log(' --help, -h Show help information'); console.log(''); console.log(colorize('Examples:', 'white')); console.log(' node seo-internal-link-tool.js sitemap'); console.log(' node seo-internal-link-tool.js analyze --output report.json'); console.log(' node seo-internal-link-tool.js interactive'); } /** * Read file content with error handling */ function readFileContent(filePath) { try { return fs.readFileSync(filePath, 'utf8'); } catch (error) { return ''; } } /** * Extract content from page file */ function extractPageContent(page) { return readFileContent(page.filePath); } /** * Generate JSON-LD structured data for a page */ function generatePageJsonLD(page, content, linkConfig = defaultLinkConfig) { const businessConfig = { name: linkConfig.businessName, websiteUrl: linkConfig.websiteUrl, description: extractPageDescription(content) || `${linkConfig.businessName} - ${page.title}` }; const jsonLD = { '@context': 'https://schema.org', '@type': getSchemaTypeForRoute(page.route), name: page.title, description: businessConfig.description, url: `${businessConfig.websiteUrl}${page.url}`, datePublished: page.lastModified.toISOString(), dateModified: page.lastModified.toISOString(), publisher: { '@type': 'Organization', name: businessConfig.name, url: businessConfig.websiteUrl, } }; // Add route-specific structured data if (page.route === '') { jsonLD['@type'] = 'WebSite'; jsonLD.about = { '@type': 'LocalBusiness', name: businessConfig.name, description: businessConfig.description, url: businessConfig.websiteUrl, }; } else if (page.route.includes('service') || page.route.includes('repair')) { jsonLD['@type'] = 'Service'; jsonLD.serviceType = page.title; jsonLD.provider = { '@type': 'LocalBusiness', name: businessConfig.name, url: businessConfig.websiteUrl, }; } else if (page.route.includes('contact-us')) { jsonLD['@type'] = 'ContactPage'; } else if (page.route.includes('about')) { jsonLD['@type'] = 'AboutPage'; } return jsonLD; } /** * Extract page description from content */ function extractPageDescription(content) { const descMatch = content.match(/description:\s*['"`]([^'"`]+)['"`]/); return descMatch ? descMatch[1] : null; } /** * Get schema type based on route */ function getSchemaTypeForRoute(route) { if (route === '') return 'WebSite'; if (route.includes('service') || route.includes('repair')) return 'Service'; if (route.includes('contact-us')) return 'ContactPage'; if (route.includes('about')) return 'AboutPage'; if (route.includes('product') || route.includes('vending')) return 'Product'; return 'WebPage'; } /** * Generate sitemap from React pages */ async function runSitemapGeneration(options) { displayHeader('Generating Sitemap from React Pages'); try { // Discover pages displayInfo('Discovering React pages...'); const pages = discoverPages(APP_DIR); displaySuccess(`Found ${pages.length} pages`); // Generate sitemap XML displayInfo('Generating sitemap XML...'); const sitemapXml = generateSitemapXml(pages); // Output results if (options.output) { fs.writeFileSync(options.output, sitemapXml); displaySuccess(`Sitemap saved to: ${options.output}`); } else { console.log(colorize('\nGenerated Sitemap:', 'white')); console.log(sitemapXml); } // Also save to conventional location const sitemapPath = path.join(PROJECT_ROOT, 'out', 'sitemap.xml'); fs.writeFileSync(sitemapPath, sitemapXml); displaySuccess(`Default sitemap location: ${sitemapPath}`); } catch (error) { displayError(`Failed to generate sitemap: ${error.message}`); throw error; } } /** * Analyze internal links */ async function runLinkAnalysis(options) { displayHeader('Analyzing Internal Links'); try { // Discover pages displayInfo('Discovering React pages...'); const pages = discoverPages(APP_DIR); displaySuccess(`Found ${pages.length} pages`); // Extract content from each page displayInfo('Extracting page content...'); const contentByRoute = {}; for (const page of pages) { contentByRoute[page.route] = extractPageContent(page); } // Analyze internal links displayInfo('Analyzing internal links...'); const analysis = analyzeInternalLinks(pages, contentByRoute); // Display results displayLinkAnalysisResults(analysis, options); // Save results if output file specified if (options.output) { saveAnalysisResults(analysis, options.output, options.format); displaySuccess(`Analysis saved to: ${options.output}`); } } catch (error) { displayError(`Failed to analyze links: ${error.message}`); throw error; } } /** * Display link analysis results */ function displayLinkAnalysisResults(analysis, options) { console.log(colorize(`\nšŸ“Š Link Analysis Summary`, 'white')); console.log(colorize(`Total Pages: ${analysis.totalPages}`, 'cyan')); console.log(colorize(`Internal Links: ${analysis.internalLinks}`, 'cyan')); console.log(colorize(`Average Link Density: ${analysis.averageLinkDensity.toFixed(2)} links per page`, 'cyan')); if (analysis.orphanedPages.length > 0) { displayWarning(`Orphaned Pages: ${analysis.orphanedPages.length}`); analysis.orphanedPages.forEach(page => { console.log(colorize(` - ${page}`, 'yellow')); }); } else { displaySuccess('No orphaned pages found'); } if (analysis.brokenLinks.length > 0) { displayError(`Broken Links: ${analysis.brokenLinks.length}`); analysis.brokenLinks.forEach(link => { console.log(colorize(` - ${link}`, 'red')); }); } else { displaySuccess('No broken links found'); } // Page types breakdown console.log(colorize(`\nšŸ“‹ Page Types:`, 'white')); for (const [type, count] of Object.entries(analysis.pageTypes)) { console.log(colorize(` ${type}: ${count} pages`, 'blue')); } // Pages with issues if (analysis.pagesWithIssues.length > 0) { displayWarning(`Pages with Issues: ${analysis.pagesWithIssues.length}`); analysis.pagesWithIssues.forEach(page => { console.log(colorize(` - ${page.url}:`, 'yellow')); page.issues.forEach(issue => { console.log(colorize(` * ${issue}`, 'yellow')); }); }); } } /** * Save analysis results */ function saveAnalysisResults(results, outputPath, format) { const output = { timestamp: new Date().toISOString(), summary: { totalPages: results.totalPages, internalLinks: results.internalLinks, orphanedPages: results.orphanedPages.length, brokenLinks: results.brokenLinks.length, averageLinkDensity: results.averageLinkDensity }, details: results }; let content; switch (format) { case 'json': content = JSON.stringify(output, null, 2); break; case 'csv': content = convertToCSV(results); break; case 'markdown': content = convertToMarkdown(results); break; case 'html': content = convertToHTML(results); break; default: content = JSON.stringify(output, null, 2); } fs.writeFileSync(outputPath, content); } /** * Convert analysis results to CSV */ function convertToCSV(results) { let csv = 'Page,Route,Links,Issues\n'; // Add pages with issues results.pagesWithIssues.forEach(page => { csv += `"${page.url}","${page.route}","0","${page.issues.join('; ')}"\n`; }); // Add pages without issues const allPages = Array.from(new Set([ ...results.orphanedPages, ...results.brokenLinks.map(link => link.split(' -> ')[0]) ])); allPages.forEach(page => { csv += `"${page}","${page}","0","No issues"\n`; }); return csv; } /** * Convert analysis results to Markdown */ function convertToMarkdown(results) { let md = '# SEO Link Analysis Report\n\n'; md += `*Generated: ${new Date().toISOString()}*\n\n`; md += '## Summary\n\n'; md += `- Total Pages: ${results.totalPages}\n`; md += `- Internal Links: ${results.internalLinks}\n`; md += `- Orphaned Pages: ${results.orphanedPages.length}\n`; md += `- Broken Links: ${results.brokenLinks.length}\n`; md += `- Average Link Density: ${results.averageLinkDensity.toFixed(2)}\n\n`; md += '## Page Types\n\n'; for (const [type, count] of Object.entries(results.pageTypes)) { md += `- ${type}: ${count} pages\n`; } md += '\n'; if (results.orphanedPages.length > 0) { md += '## Orphaned Pages\n\n'; results.orphanedPages.forEach(page => { md += `- ${page}\n`; }); md += '\n'; } if (results.brokenLinks.length > 0) { md += '## Broken Links\n\n'; results.brokenLinks.forEach(link => { md += `- ${link}\n`; }); md += '\n'; } if (results.pagesWithIssues.length > 0) { md += '## Pages with Issues\n\n'; results.pagesWithIssues.forEach(page => { md += `### ${page.url}\n`; page.issues.forEach(issue => { md += `- ${issue}\n`; }); md += '\n'; }); } return md; } /** * Convert analysis results to HTML */ function convertToHTML(results) { return ` SEO Link Analysis Report

SEO Link Analysis Report

Generated: ${new Date().toISOString()}

Summary

Total Pages: ${results.totalPages}

Internal Links: ${results.internalLinks}

Orphaned Pages: ${results.orphanedPages.length}

Broken Links: ${results.brokenLinks.length}

Average Link Density: ${results.averageLinkDensity.toFixed(2)}

${results.orphanedPages.length > 0 ? `

Orphaned Pages

    ${results.orphanedPages.map(page => `
  • ${page}
  • `).join('')}
` : ''} ${results.brokenLinks.length > 0 ? `

Broken Links

    ${results.brokenLinks.map(link => `
  • ${link}
  • `).join('')}
` : ''} `; } /** * Optimize internal links */ async function runLinkOptimization(options) { displayHeader('Optimizing Internal Links'); try { // Load configuration displayInfo('Loading configuration...'); const linkConfig = defaultLinkConfig; // Discover pages displayInfo('Discovering React pages...'); const pages = discoverPages(APP_DIR); displaySuccess(`Found ${pages.length} pages`); // Extract content from each page displayInfo('Extracting page content...'); const contentByRoute = {}; for (const page of pages) { contentByRoute[page.route] = extractPageContent(page); } // Optimize links page by page let totalLinksAdded = 0; const optimizationResults = []; for (const page of pages) { if (isExcludedRoute(page.route, linkConfig)) { continue; } const maxLinks = getMaxLinksForPage(page.route, linkConfig); const minWordsBetween = getMinWordsBetweenLinks(page.route, linkConfig); const content = contentByRoute[page.route]; // Calculate SEO score const internalLinks = extractInternalLinksFromContent(content); const seoScore = calculatePageScore(page.route, content, internalLinks, linkConfig); // Find keywords to link const keywordsToLink = findKeywordsInContent(content, linkConfig); const linksAdded = addOptimalLinks(content, keywordsToLink, pages, linkConfig); if (linksAdded > 0) { totalLinksAdded += linksAdded; optimizationResults.push({ page: page.route, linksAdded, seoScore: seoScore, keywordsFound: keywordsToLink.length }); displaySuccess(`${page.route}: Added ${linksAdded} link(s)`); } } displaySuccess(`Total links added: ${totalLinksAdded}`); // Save results if (options.output) { const results = { timestamp: new Date().toISOString(), totalLinksAdded, pagesOptimized: optimizationResults.length, details: optimizationResults }; fs.writeFileSync(options.output, JSON.stringify(results, null, 2)); displaySuccess(`Optimization results saved to: ${options.output}`); } } catch (error) { displayError(`Failed to optimize links: ${error.message}`); throw error; } } /** * Extract internal links from content */ function extractInternalLinksFromContent(content) { const links = []; const linkRegex = /]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g; let match; while ((match = linkRegex.exec(content)) !== null) { if (isInternalLink(match[1])) { links.push({ to: match[1], text: match[2].trim() }); } } return links; } /** * Check if link is internal */ function isInternalLink(href) { return !href.startsWith('http') && !href.startsWith('mailto:') && !href.startsWith('tel:'); } /** * Add optimal links to content */ function addOptimalLinks(content, keywords, pages, config) { let linksAdded = 0; const linkPositions = []; for (const keyword of keywords) { if (linksAdded >= config.linkDensity.maxLinksPerPage) { break; } // Find keyword in content const keywordRegex = new RegExp(`\\b${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'gi'); let match; while ((match = keywordRegex.exec(content)) !== null) { if (linksAdded >= config.linkDensity.maxLinksPerPage) { break; } // Check distance from other links const tooClose = linkPositions.some(pos => Math.abs(pos - match.index) < config.linkDensity.minWordsBetweenLinks * 5 ); if (tooClose) continue; // Add link const targetPage = pages.find(p => p.title.toLowerCase().includes(keyword.toLowerCase()) || p.route.toLowerCase().includes(keyword.toLowerCase()) ); if (targetPage && !isExcludedRoute(targetPage.route, config)) { const anchorTexts = getAnchorTexts(targetPage.route, config); const anchorText = anchorTexts[0] || keyword; // Insert link (simplified - in real implementation, would need to properly handle HTML) content = insertLink(content, match.index, match.index + match[0].length, targetPage.url, anchorText); linkPositions.push(match.index); linksAdded++; break; // Only link first occurrence per keyword } } } return linksAdded; } /** * Insert link into content */ function insertLink(content, startIndex, endIndex, url, anchorText) { const before = content.substring(0, startIndex); const after = content.substring(endIndex); const link = `${anchorText}`; return before + link + after; } /** * Generate SEO report */ async function runSEOReport(options) { displayHeader('Generating SEO Report'); try { // Discover pages displayInfo('Discovering React pages...'); const pages = discoverPages(APP_DIR); displaySuccess(`Found ${pages.length} pages`); // Extract content from each page displayInfo('Extracting page content...'); const contentByRoute = {}; const pageScores = []; for (const page of pages) { contentByRoute[page.route] = extractPageContent(page); // Calculate SEO score const internalLinks = extractInternalLinksFromContent(contentByRoute[page.route]); const seoScore = calculatePageScore(page.route, contentByRoute[page.route], internalLinks); pageScores.push({ page: page.route, url: page.url, title: page.title, priority: page.priority, score: seoScore, internalLinks: internalLinks.length, lastModified: page.lastModified.toISOString() }); } // Sort pages by score pageScores.sort((a, b) => b.score - a.score); // Display report displaySEOResults(pageScores, options); // Save results if (options.output) { const report = { timestamp: new Date().toISOString(), totalPages: pages.length, averageScore: pageScores.reduce((sum, p) => sum + p.score, 0) / pageScores.length, pages: pageScores }; let content; switch (options.format) { case 'json': content = JSON.stringify(report, null, 2); break; case 'markdown': content = generateMarkdownReport(report); break; case 'html': content = generateHTMLReport(report); break; default: content = JSON.stringify(report, null, 2); } fs.writeFileSync(options.output, content); displaySuccess(`Report saved to: ${options.output}`); } } catch (error) { displayError(`Failed to generate report: ${error.message}`); throw error; } } /** * Display SEO results */ function displaySEOResults(pageScores, options) { console.log(colorize(`\nšŸ“Š SEO Analysis Summary`, 'white')); console.log(colorize(`Total Pages Analyzed: ${pageScores.length}`, 'cyan')); const averageScore = pageScores.reduce((sum, p) => sum + p.score, 0) / pageScores.length; console.log(colorize(`Average SEO Score: ${averageScore.toFixed(1)}/100`, 'cyan')); // Top performing pages console.log(colorize(`\nšŸ† Top Performing Pages:`, 'white')); const topPages = pageScores.slice(0, 5); topPages.forEach((page, index) => { const scoreColor = page.score >= 80 ? 'green' : page.score >= 60 ? 'yellow' : 'red'; console.log(colorize(`${index + 1}. ${page.title} (${page.route}) - Score: ${page.score.toFixed(1)}`, scoreColor)); }); // Pages needing improvement const poorPages = pageScores.filter(p => p.score < 60); if (poorPages.length > 0) { console.log(colorize(`\n⚠ Pages Needing Improvement:`, 'yellow')); poorPages.slice(0, 3).forEach(page => { console.log(colorize(`- ${page.title} (${page.route}) - Score: ${page.score.toFixed(1)}`, 'red')); }); } } /** * Generate markdown report */ function generateMarkdownReport(report) { let md = '# SEO Report\n\n'; md += `*Generated: ${report.timestamp}*\n\n`; md += '## Summary\n\n'; md += `- Total Pages: ${report.totalPages}\n`; md += `- Average Score: ${report.averageScore.toFixed(1)}/100\n\n`; md += '## Page Scores\n\n'; report.pages.forEach((page, index) => { const status = page.score >= 80 ? 'Excellent' : page.score >= 60 ? 'Good' : 'Needs Improvement'; const statusColor = page.score >= 80 ? 'green' : page.score >= 60 ? 'yellow' : 'red'; md += `${index + 1}. **${page.title}** (${page.route})\n`; md += ` - Score: ${page.score.toFixed(1)}/100 (${status})\n`; md += ` - Internal Links: ${page.internalLinks}\n`; md += ` - Priority: ${page.priority}\n`; md += ` - Last Modified: ${new Date(page.lastModified).toLocaleDateString()}\n\n`; }); return md; } /** * Generate HTML report */ function generateHTMLReport(report) { return ` SEO Report

SEO Report

Generated: ${report.timestamp}

Summary

Total Pages: ${report.totalPages}

Average Score: ${report.averageScore.toFixed(1)}/100

Page Scores

${report.pages.map((page, index) => { const status = page.score >= 80 ? 'excellent' : page.score >= 60 ? 'good' : 'poor'; return `

${index + 1}. ${page.title} (${page.route})

${page.score.toFixed(1)}/100

Internal Links: ${page.internalLinks}

Priority: ${page.priority}

Last Modified: ${new Date(page.lastModified).toLocaleDateString()}

`; }).join('')} `; } /** * Generate JSON-LD structured data */ async function runJsonLDGeneration(options) { displayHeader('Generating JSON-LD Structured Data'); try { // Discover pages displayInfo('Discovering React pages...'); const pages = discoverPages(APP_DIR); displaySuccess(`Found ${pages.length} pages`); // Generate JSON-LD for each page const jsonLDData = {}; for (const page of pages) { const content = extractPageContent(page); const jsonLD = generatePageJsonLD(page, content); jsonLDData[page.route] = jsonLD; if (options.verbose) { displaySuccess(`Generated JSON-LD for: ${page.route}`); } } // Output results if (options.output) { fs.writeFileSync(options.output, JSON.stringify(jsonLDData, null, 2)); displaySuccess(`JSON-LD data saved to: ${options.output}`); } else { console.log(colorize('\nGenerated JSON-LD Structured Data:', 'white')); console.log(JSON.stringify(jsonLDData, null, 2)); } // Also save individual files (skip dynamic routes to avoid filesystem issues) const outputDir = path.join(PROJECT_ROOT, 'public', 'json-ld'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } for (const [route, jsonLD] of Object.entries(jsonLDData)) { // Skip dynamic routes if (route.includes(':') || route.includes('...')) { continue; } let filename; if (route === '') { filename = 'home.jsonld'; } else { filename = `${route.replace(/\//g, '_')}.jsonld`; } const filepath = path.join(outputDir, filename); fs.writeFileSync(filepath, JSON.stringify(jsonLD, null, 2)); } displaySuccess(`Individual JSON-LD files saved to: ${outputDir}`); } catch (error) { displayError(`Failed to generate JSON-LD: ${error.message}`); throw error; } } /** * Start interactive mode */ async function startInteractiveMode(options) { displayHeader('SEO Internal Link Tool - Interactive Mode'); displayInfo('Type "help" for available commands or "exit" to quit'); // Import readline only when needed try { readline = await import('readline'); } catch (error) { displayError('Interactive mode requires Node.js readline module'); process.exit(1); } const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const question = (query) => new Promise(resolve => rl.question(query, resolve)); while (true) { const command = await question('\nseo-tool> '); if (command.toLowerCase() === 'exit' || command.toLowerCase() === 'quit') { break; } if (command.toLowerCase() === 'help') { showHelp(); continue; } const args = command.split(' '); const cmd = args[0]; const cmdOptions = { output: null, format: 'console', verbose: false }; // Parse options for (let i = 1; i < args.length; i++) { const arg = args[i]; if (arg === '--output' && i + 1 < args.length) { cmdOptions.output = args[i + 1]; i++; } else if (arg === '--format' && i + 1 < args.length) { cmdOptions.format = args[i + 1]; i++; } else if (arg === '--verbose' || arg === '-v') { cmdOptions.verbose = true; } } try { switch (cmd) { case 'sitemap': await runSitemapGeneration(cmdOptions); break; case 'analyze': await runLinkAnalysis(cmdOptions); break; case 'optimize': await runLinkOptimization(cmdOptions); break; case 'report': await runSEOReport(cmdOptions); break; case 'json-ld': await runJsonLDGeneration(cmdOptions); break; default: displayError(`Unknown command: ${cmd}`); showHelp(); } } catch (error) { displayError(`Error: ${error.message}`); } } rl.close(); displaySuccess('Goodbye!'); } /** * Main CLI dispatcher */ function main() { const args = process.argv.slice(2); if (args.length === 0 || args.includes('--help') || args.includes('-h')) { showHelp(); return; } const command = args[0]; const options = { output: null, format: 'console', verbose: false }; // Parse options for (let i = 1; i < args.length; i++) { const arg = args[i]; if (arg === '--output' && i + 1 < args.length) { options.output = args[i + 1]; i++; } else if (arg === '--format' && i + 1 < args.length) { options.format = args[i + 1]; i++; } else if (arg === '--verbose' || arg === '-v') { options.verbose = true; } } // Check if we need to import readline for interactive mode if (command === 'interactive') { try { readline = require('readline'); } catch (error) { displayError('Interactive mode requires Node.js readline module'); process.exit(1); } } switch (command) { case 'sitemap': executeCommand(runSitemapGeneration, options); break; case 'analyze': executeCommand(runLinkAnalysis, options); break; case 'optimize': executeCommand(runLinkOptimization, options); break; case 'report': executeCommand(runSEOReport, options); break; case 'json-ld': executeCommand(runJsonLDGeneration, options); break; case 'interactive': startInteractiveMode(options); break; default: displayError(`Unknown command: ${command}`); console.log(''); showHelp(); process.exit(1); } } /** * Execute function and handle errors */ async function executeCommand(commandFn, options) { try { await commandFn(options); } catch (error) { displayError(`Error: ${error.message}`); if (options.verbose) { console.error(error); } process.exit(1); } } // Export functions for external use export { main, executeCommand, displayHeader, displaySuccess, displayError, displayWarning, displayInfo, runSitemapGeneration, runLinkAnalysis, runLinkOptimization, runSEOReport, runJsonLDGeneration }; // Run if called directly if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith('seo-internal-link-tool.js')) { main(); }