/** * Template rendering utilities for awesome-opencode */ const fs = require('fs'); const path = require('path'); const COMPAT_BADGES = { v2: 'V2', v1: 'V1 only', unknown: 'unverified' }; /** * Read the README template * @returns {string} Template content */ function readTemplate() { const templatePath = path.join(__dirname, '../../templates/README.template.md'); return fs.readFileSync(templatePath, 'utf8'); } /** * Replace a placeholder in the template * @param {string} template - Template content * @param {string} placeholder - Placeholder name (without braces) * @param {string} content - Content to insert * @returns {string} Updated template */ function replacePlaceholder(template, placeholder, content) { const pattern = new RegExp(`\\{\\{${placeholder}\\}\\}`, 'g'); return template.replace(pattern, () => content); } /** * Escape text for use inside HTML (entries are untrusted contributor text). * @param {string} text * @returns {string} */ function escapeHtml(text) { return String(text) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } /** * Escape text for a markdown table cell. * @param {string} text * @returns {string} */ function escapeCell(text) { return String(text).replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/\r?\n/g, ' '); } /** * Parse a URL, or null when it is not one. * @param {string} url * @returns {URL|null} */ function parseUrl(url) { try { return new URL(url); } catch { return null; } } /** * True for npm package pages (they duplicate the npm link, so they are not shown as docs). * @param {string} url * @returns {boolean} */ function isNpmPage(url) { const u = parseUrl(url); return Boolean(u && (u.hostname === 'www.npmjs.com' || u.hostname === 'npmjs.com') && u.pathname.startsWith('/package/')); } /** * URL of the entry's code, including the monorepo subfolder when set. * @param {object} entry - Parsed YAML data * @returns {string} */ function sourceUrl(entry) { return entry.path ? `${entry.repo.replace(/\/+$/, '')}/tree/HEAD/${entry.path}` : entry.repo; } /** * Generate HTML for a single entry * @param {object} entry - Parsed YAML data * @param {string} category - Category directory name * @returns {string} HTML string for the details element */ function generateEntryHtml(entry, category) { const url = parseUrl(entry.repo); const isGist = Boolean(url && url.hostname === 'gist.github.com'); const isDiscussion = Boolean(url && url.hostname === 'github.com' && /^\/[^/]+\/[^/]+\/discussions\//.test(url.pathname)); // Determine link text based on URL type first let linkText = '🔗 View Repository'; if (isGist) { linkText = '🔗 View Gist'; } else if (isDiscussion) { linkText = '🔗 View Discussion'; } // Extract owner/repo from URL for star badge (only for non-gist, non-discussion GitHub repos) const repoMatch = url && url.hostname === 'github.com' ? url.pathname.match(/^\/([^/]+)\/([^/]+)/) : null; let summaryContent = `${escapeHtml(entry.name)}`; // Add star badge if it's a GitHub repo (not a gist or discussion) if (repoMatch && !isGist && !isDiscussion) { const owner = repoMatch[1]; const repo = repoMatch[2].replace(/\.git$/, '').replace(/\/$/, ''); const starBadge = `https://badgen.net/github/stars/${owner}/${repo}`; summaryContent += ` `; } // Compatibility badge: always for plugins, otherwise only when the entry uses the OpenCode API const compat = entry.compat || (category === 'plugins' ? 'unknown' : 'n/a'); if (COMPAT_BADGES[compat] && (category === 'plugins' || compat !== 'unknown')) { summaryContent += ` ${COMPAT_BADGES[compat]}`; } if (entry.also && entry.also.includes('mcp-server')) { summaryContent += ' MCP'; } summaryContent += ` - ${escapeHtml(entry.tagline)}`; const links = [`${linkText}`]; if (entry.npm) { links.push(`📦 npm`); } if (entry.homepage && !isNpmPage(entry.homepage)) { links.push(`📖 Docs`); } return `
${summaryContent}
${escapeHtml(entry.description)}

${links.join('  â€¢  ')}
`; } /** * Write the final README * @param {string} content - Generated README content */ function writeReadme(content) { const readmePath = path.join(__dirname, '../../README.md'); fs.writeFileSync(readmePath, content, 'utf8'); } module.exports = { escapeCell, escapeHtml, isNpmPage, readTemplate, replacePlaceholder, generateEntryHtml, sourceUrl, writeReadme };