Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | 5x 5x 37x 29x 29x 49x 15x 23x 11x 11x 6x 4x 45x 13x 13x 13x 13x 3x 22x 22x 37x 29x 13x 4x 18x 18x 7x 7x 7x 7x 2x 16x 16x 15x 29x 29x 5x | const DEBUG_FETCH = process.env.DEBUG_FETCH === '1' || process.env.DEBUG_FETCH === 'true';
const API_PREFIX_RE = /^[•\-*.\s📌📡🚀]*\s*/i;
function isCompleteApiLabel(label) {
return /^Complete\s+API\b/i.test((label || '').replace(API_PREFIX_RE, '').trim());
}
function findCompleteApiInAst(ast, ctx) {
Iif (!ast || !Array.isArray(ast.children)) return null;
for (const n of ast.children) {
if (n.type === 'paragraph') {
for (const ch of (n.children || [])) {
if (ch.type !== 'link' || !ch.url) continue;
const label = (ch.children || []).map(c => c.value || '').join('').trim();
if (!isCompleteApiLabel(label)) continue;
const desc = (n.children || []).filter(c => c.type === 'text').map(c => c.value).join(' ').trim();
return { title: label || 'Complete API', link: ch.url, description: ctx.strip(desc) };
}
}
if (n.type === 'list') {
for (const li of (n.children || [])) {
const flat = ctx.parseReadme.flattenNodeText(li || '').replace(/\r?\n/g, ' ');
for (const m of String(flat).matchAll(/\[([^\]]+)\]\(([^)]+)\)/ig)) {
if (isCompleteApiLabel((m[1] || '').trim())) {
return { title: (m[1] || '').trim(), link: (m[2] || '').trim(), description: '' };
}
}
}
}
}
return null;
}
function findCompleteApiInText(readmeText) {
for (const line of readmeText.split(/\r?\n/)) {
if (/^\s*#{1,6}\s/.test(line)) continue;
for (const m of line.matchAll(/\[([^\]]+)\]\(([^)]+)\)/ig)) {
if (isCompleteApiLabel((m[1] || '').trim())) {
return { title: (m[1] || '').trim(), link: (m[2] || '').trim(), description: '' };
}
}
}
return null;
}
function findAnyApiLink(readmeText, toRawGithub) {
for (const m of readmeText.matchAll(/\[([^\]]*)\]\((https?:\/\/[^)\s]+|\.?\/?[^)\s]+)\)/ig)) {
const label = (m[1] || '').trim();
const url = (m[2] || '').trim();
Iif (!url) continue;
if (/api/i.test(label) || /api\.(?:md|html)$/i.test(url) || /src\/(?:main\/)?docs\/.+api/i.test(url)) {
return { title: label || 'API Documentation', link: toRawGithub(url), description: '' };
}
}
const rawAny = readmeText.match(/https?:\/\/raw\.githubusercontent\.com\/keglev\/[^/]+\/(?:main|master)\/(.+api.+\.(?:md|html))/i);
if (rawAny) return { title: 'API Documentation', link: toRawGithub(rawAny[0]), description: '' };
return null;
}
/**
* Finds the first "Complete API" or any API-related link in the README.
* Search order: AST paragraph nodes → AST list items → raw text scan.
* "Complete API" is the canonical label used in project READMEs for the full API reference.
*
* @param {object} ast - Parsed README AST
* @param {string} readmeText - Raw README string (fallback when AST scan finds nothing)
* @param {object} ctx - Shared context: { toRawGithub, parseReadme, strip }
* @returns {{ title: string, link: string, description: string }|null}
*/
function extractApiDocumentation(ast, readmeText, ctx) {
try {
return findCompleteApiInAst(ast, ctx) || findCompleteApiInText(readmeText) || findAnyApiLink(readmeText, ctx.toRawGithub);
} catch (e) {
if (DEBUG_FETCH) console.log('extractApiDocumentation error', e && e.message);
return null;
}
}
module.exports = { extractApiDocumentation };
|