UNPKG

github-action-readme-generator

Version:

The docs generator for GitHub Actions. Auto-syncs action.yml to README.md with 8 sections: inputs, outputs, usage, badges, branding & more. Works as CLI or GitHub Action.

87 lines 3.58 kB
import LogTask from '../logtask/index.js'; /** * Converts a header text to a GitHub-compatible anchor link. * @param {string} text - The header text to convert. * @returns {string} The anchor link. */ function headerToAnchor(text) { return text .toLowerCase() .replaceAll(/[^\w\s-]/g, '') // Remove special characters except hyphens .replaceAll(/\s+/g, '-') // Replace spaces with hyphens .replaceAll(/-+/g, '-') // Collapse multiple hyphens .replaceAll(/^-|-$/g, ''); // Remove leading/trailing hyphens } /** * Extracts headers from markdown content, excluding those in code blocks. * @param {string} content - The markdown content. * @returns {Array<{level: number, text: string}>} Array of header objects. */ function extractHeaders(content) { const headers = []; const lines = content.split('\n'); let inCodeBlock = false; for (const line of lines) { // Track code block state if (line.trim().startsWith('```')) { inCodeBlock = !inCodeBlock; continue; } // Skip if inside code block if (inCodeBlock) { continue; } // Match markdown headers (## Header) const headerMatch = /^(#{2,6})\s+(.+)$/.exec(line); if (headerMatch) { const level = headerMatch[1].length; let text = headerMatch[2].trim(); // Remove inline images and other markdown formatting from header text text = text.replaceAll(/<img[^>]*>/g, '').trim(); // Remove markdown links but keep the text text = text.replaceAll(/\[([^\]]+)]\([^)]+\)/g, '$1'); if (text) { headers.push({ level, text }); } } } return headers; } export default function updateContents(sectionToken, inputs) { const log = new LogTask(sectionToken); log.start(); const content = []; const readmeContent = inputs.readmeEditor.getReadmeContent(); // Extract headers from README const headers = extractHeaders(readmeContent); // Filter out the title (h1) and contents section itself const tocHeaders = headers.filter((h) => h.level >= 2 && !h.text.toLowerCase().includes('contents')); if (tocHeaders.length === 0) { log.info('No headers found for table of contents'); } else { log.info(`Generating table of contents with ${tocHeaders.length} entries`); // Find minimum header level for proper indentation const minLevel = Math.min(...tocHeaders.map((h) => h.level)); // Track anchor occurrences to disambiguate duplicates like GitHub does const anchorCounts = new Map(); // Generate TOC entries content.push('## Table of Contents', ''); for (const header of tocHeaders) { const indent = ' '.repeat(header.level - minLevel); const baseAnchor = headerToAnchor(header.text); // Track occurrences and compute unique anchor const count = anchorCounts.get(baseAnchor) ?? 0; anchorCounts.set(baseAnchor, count + 1); // First occurrence uses base anchor, subsequent ones get -1, -2, etc. const anchor = count === 0 ? baseAnchor : `${baseAnchor}-${count}`; content.push(`${indent}- [${header.text}](#${anchor})`); } } inputs.readmeEditor.updateSection(sectionToken, content); log.success(); const ret = {}; ret[sectionToken] = content.join('\n'); return ret; } //# sourceMappingURL=update-contents.js.map