/** * Documentation URL Analyzer * * Scrapes any documentation website URL to extract installation * and setup information for generating install.md files. * * Strategies: * 1. Detect known doc platforms (Mintlify, Docusaurus, GitBook, etc.) * 2. Extract code blocks from the page * 3. Find installation-related sections * 4. Parse prerequisites and dependencies */ export interface CodeBlock { language: string code: string context?: string // Text before the code block } export interface ExtractedSection { title: string content: string codeBlocks: CodeBlock[] } export interface DocsAnalysis { // Source info url: string title: string description: string // Detected platform platform: string | null // Extracted content sections: ExtractedSection[] allCodeBlocks: CodeBlock[] // Parsed installation info installCommands: Array<{ command: string packageManager?: string label?: string }> prerequisites: string[] // Raw content for AI pageContent: string // Related URLs found relatedUrls: Array<{ url: string text: string type: 'installation' | 'quickstart' | 'getting-started' | 'api' | 'other' }> } const DOC_PLATFORMS = { mintlify: ['mintlify', 'x-mintlify'], docusaurus: ['docusaurus', '__docusaurus'], gitbook: ['gitbook', 'x-gitbook'], readme: ['readme.io', 'x-readme'], vitepress: ['vitepress'], mkdocs: ['mkdocs', 'material'], sphinx: ['sphinx'], nextra: ['nextra'], } as const /** * Detect documentation platform from HTML */ function detectPlatform(html: string): string | null { const htmlLower = html.toLowerCase() for (const [platform, indicators] of Object.entries(DOC_PLATFORMS)) { if (indicators.some(ind => htmlLower.includes(ind))) { return platform } } return null } /** * Extract page title */ function extractTitle(html: string): string { // Try
patterns
const preCodePattern = /]*>[\s\S]*?]*(?:class=["'][^"']*language-(\w+)[^"']*["'])?[^>]*>([\s\S]*?)<\/code>[\s\S]*?<\/pre>/gi
let match
while ((match = preCodePattern.exec(html)) !== null) {
const language = match[1] || 'bash'
const code = decodeHtmlEntities(match[2])
.replace(/<[^>]+>/g, '') // Remove any inner HTML tags
.trim()
if (code.length > 0) {
blocks.push({ language, code })
}
}
// Match standalone blocks that look like commands
const codePattern = /]*(?:class=["'][^"']*language-(\w+)[^"']*["'])?[^>]*>([\s\S]*?)<\/code>/gi
while ((match = codePattern.exec(html)) !== null) {
const code = decodeHtmlEntities(match[2])
.replace(/<[^>]+>/g, '')
.trim()
// Only include if it looks like a command and not already captured
if (
code.length > 5 &&
code.length < 500 &&
!blocks.some(b => b.code === code) &&
/^(npm|pnpm|yarn|pip|cargo|brew|apt|curl|wget|go\s|npx|bunx)/.test(code)
) {
blocks.push({ language: 'bash', code })
}
}
return blocks
}
/**
* Decode HTML entities
*/
function decodeHtmlEntities(text: string): string {
const entities: Record = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
''': "'",
' ': ' ',
''': "'",
'/': '/',
}
let decoded = text
for (const [entity, char] of Object.entries(entities)) {
decoded = decoded.split(entity).join(char)
}
return decoded
}
/**
* Remove HTML tags and get plain text
*/
function htmlToText(html: string): string {
return html
.replace(/