|
| 1 | +// Generates /llms.txt following https://llmstxt.org/ so that AI agents can |
| 2 | +// discover the documentation without scraping HTML. |
| 3 | +// |
| 4 | +// For every component, only the version that 'stable' points to is indexed |
| 5 | +// (the latest release, or nightly when building without release branches). |
| 6 | +// Each entry carries the published page URL, the page description and a link |
| 7 | +// to the AsciiDoc source on GitHub, so agents can both read a page and find |
| 8 | +// the file to change. |
| 9 | +// |
| 10 | +// Useful links: |
| 11 | +// Extensions: https://docs.antora.org/antora/latest/extend/extensions/ |
| 12 | +// Types of events: https://docs.antora.org/antora/latest/extend/generator-events-reference/ |
| 13 | +module.exports.register = function () { |
| 14 | + this.once('beforePublish', ({ playbook, contentCatalog, siteCatalog }) => { |
| 15 | + const siteUrl = (playbook.site.url || '').replace(/\/$/, '') |
| 16 | + const lines = [ |
| 17 | + '# Stackable Data Platform Documentation', |
| 18 | + '', |
| 19 | + '> Documentation for the Stackable Data Platform (SDP), a curated selection of', |
| 20 | + '> open source data apps like Apache Kafka, Apache Druid, Apache Trino and', |
| 21 | + '> Apache Spark, all working together seamlessly on Kubernetes.', |
| 22 | + '', |
| 23 | + 'Machine-readable release, component and CRD data is available from the', |
| 24 | + 'Stackable Hub: https://hub.stackable.tech/llms.txt', |
| 25 | + '', |
| 26 | + 'Every entry below links the published page and, in parentheses, its AsciiDoc', |
| 27 | + 'source on GitHub. To suggest a change to a page, edit that source file.', |
| 28 | + ] |
| 29 | + |
| 30 | + for (const component of contentCatalog.getComponents()) { |
| 31 | + const version = component.latest |
| 32 | + const pages = contentCatalog |
| 33 | + .findBy({ component: component.name, version: version.version, family: 'page' }) |
| 34 | + .filter((page) => page.out) |
| 35 | + .sort((a, b) => a.pub.url.localeCompare(b.pub.url)) |
| 36 | + const byModule = new Map() |
| 37 | + for (const page of pages) { |
| 38 | + const module = page.src.module |
| 39 | + if (!byModule.has(module)) byModule.set(module, []) |
| 40 | + byModule.get(module).push(page) |
| 41 | + } |
| 42 | + for (const [module, modulePages] of byModule) { |
| 43 | + lines.push('', `## ${component.title}: ${module === 'ROOT' ? 'general' : module}`, '') |
| 44 | + for (const page of modulePages) { |
| 45 | + const title = (page.asciidoc && page.asciidoc.doctitle ? page.asciidoc.doctitle : page.src.stem) |
| 46 | + .replace(/<[^>]+>/g, '') |
| 47 | + const description = page.asciidoc && page.asciidoc.attributes['description'] |
| 48 | + const source = sourceUrl(page.src) |
| 49 | + let entry = `- [${title}](${siteUrl}${page.pub.url})` |
| 50 | + if (description) entry += `: ${description.replace(/\s+/g, ' ').trim()}` |
| 51 | + if (source) entry += ` (source: ${source})` |
| 52 | + lines.push(entry) |
| 53 | + } |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + siteCatalog.addFile({ |
| 58 | + contents: Buffer.from(lines.join('\n') + '\n', 'utf8'), |
| 59 | + out: { path: 'llms.txt' }, |
| 60 | + }) |
| 61 | + }) |
| 62 | +} |
| 63 | + |
| 64 | +function sourceUrl (src) { |
| 65 | + const origin = src.origin |
| 66 | + if (!origin || !origin.url || !origin.url.startsWith('https://github.com/')) return undefined |
| 67 | + const repo = origin.url.replace(/\.git$/, '') |
| 68 | + const start = origin.startPath ? `${origin.startPath}/` : '' |
| 69 | + return `${repo}/blob/${origin.refname}/${start}${src.path}` |
| 70 | +} |
0 commit comments