import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const TOOLS_CONFIG = path.resolve(__dirname, '../src/js/config/tools.ts'); const PAGES_DIR = path.resolve(__dirname, '../src/pages'); const PARTIAL_PATH = path.resolve( __dirname, '../src/partials/tool-links-static.html' ); function escapeHtml(value) { return value .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function unescapeTs(value) { return value.replace(/\\'/g, "'").replace(/\\\\/g, '\\'); } function parseCategories(source) { const headers = [ ...source.matchAll(/name:\s*'((?:[^'\\]|\\.)*)',\s*\n\s*tools:\s*\[/g), ].map((m) => ({ name: unescapeTs(m[1]), index: m.index })); const toolMatches = [ ...source.matchAll( /\{[^{}]*?href:\s*import\.meta\.env\.BASE_URL\s*\+\s*'((?:[^'\\]|\\.)*)'[^{}]*?\}/gs ), ]; const categories = headers.map((h) => ({ name: h.name, tools: [] })); for (const match of toolMatches) { const block = match[0]; const hrefFile = unescapeTs(match[1]); const nameMatch = block.match(/name:\s*'((?:[^'\\]|\\.)*)'/s); const subtitleMatch = block.match(/subtitle:\s*\n?\s*'((?:[^'\\]|\\.)*)'/s); if (!nameMatch) continue; let categoryIndex = -1; for (let i = 0; i < headers.length; i++) { if (headers[i].index < match.index) categoryIndex = i; } if (categoryIndex === -1) continue; categories[categoryIndex].tools.push({ slug: hrefFile.replace(/\.html$/, ''), name: unescapeTs(nameMatch[1]), subtitle: subtitleMatch ? unescapeTs(subtitleMatch[1]) : '', }); } return categories.filter((c) => c.tools.length > 0); } function generate() { const source = fs.readFileSync(TOOLS_CONFIG, 'utf-8'); const categories = parseCategories(source); const existingPages = new Set( fs .readdirSync(PAGES_DIR) .filter((f) => f.endsWith('.html')) .map((f) => f.replace(/\.html$/, '')) ); const seen = new Set(); const lines = []; let toolCount = 0; for (const category of categories) { const tools = category.tools.filter( (t) => existingPages.has(t.slug) && !seen.has(t.slug) ); if (tools.length === 0) continue; for (const t of tools) seen.add(t.slug); lines.push( `

${escapeHtml(category.name)}

` ); for (const tool of tools) { toolCount++; lines.push( `` + `

${escapeHtml(tool.name)}

` + `

${escapeHtml(tool.subtitle)}

` + `
` ); } } if (toolCount < 50) { console.error( `generate-static-tool-links: only ${toolCount} tools parsed from tools.ts — parser is likely out of sync with the config format` ); process.exit(1); } fs.writeFileSync(PARTIAL_PATH, lines.join('\n') + '\n'); console.log( `Static tool links: ${toolCount} tools in ${categories.length} categories -> src/partials/tool-links-static.html` ); } generate();