Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
.idea/
# Dependencies
node_modules/

# Antora build output and cache
build/
.cache/
antora.log

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Lockfile — yarn.lock is the committed source of truth
package-lock.json

# OS
.DS_Store
node_modules
build
Thumbs.db

# Editors / IDEs
.idea/
.vscode/
*.swp
*~
29 changes: 29 additions & 0 deletions antora-playbook.local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
site:
title: TigerGraph Documentation
url: https://www.tigergraph.com/docs
start_page: savanna:overview:index.adoc

content:
sources:
- url: .
branches: HEAD
start_paths: [modules/savanna, modules/cloud]

antora:
extensions:
- ./lib/markdown-for-llm.js
- ./lib/llms-txt.js

asciidoc:
extensions:
- '@asciidoctor/tabs'

output:
dir: ./build/site

# Local UI development: clone antora-ui as a sibling repo, run `gulp build` there,
# then build or preview cloud-docs with `npm run build:local` or `npm run dev`.
ui:
bundle:
url: ../antora-ui/public/_
snapshot: true
18 changes: 14 additions & 4 deletions antora-playbook.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
site:
title: "Cloud Doc Test"
start_page: "savanna:overview:index.adoc"
title: TigerGraph Documentation
url: https://www.tigergraph.com/docs
start_page: savanna:overview:index.adoc

content:
sources:
- url: .
branches: HEAD
start_paths: [modules/savanna, modules/cloud]

antora:
extensions:
- ./lib/markdown-for-llm.js
- ./lib/llms-txt.js

asciidoc:
extensions:
- '@asciidoctor/tabs'

output:
dir: ./build/site

ui:
bundle:
url: https://github.com/tigergraph/antora-ui/blob/main/build/ui-bundle.zip?raw=true
snapshot: true
url: https://github.com/tigergraph/antora-ui/blob/main/build/ui-bundle-cloud.zip?raw=true
snapshot: true
4 changes: 2 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ const { reload: livereload } = process.env.LIVERELOAD === 'true' ? require('gulp
const { series, src, watch } = require('gulp')
const yaml = require('js-yaml')

const playbookFilename = 'antora-playbook.yml'
const playbookFilename = process.env.PLAYBOOK || 'antora-playbook.yml'
const playbook = yaml.load(fs.readFileSync(playbookFilename, 'utf8'))
const outputDir = (playbook.output || {}).dir || './build/site'
const serverConfig = { name: 'Preview Site', livereload, port: 5000, root: outputDir }
const serverConfig = { name: 'Preview Site', livereload, port: Number(process.env.PORT) || 5000, root: outputDir }
const antoraArgs = ['--playbook', playbookFilename]
const watchPatterns = playbook.content.sources.filter((source) => !source.url.includes(':')).reduce((accum, source) => {
accum.push(`${source.url}/${source.start_path ? source.start_path + '/' : ''}antora.yml`)
Expand Down
25 changes: 25 additions & 0 deletions lib/llm-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict'

/**
* Shared helpers for LLM-oriented Antora extensions.
*/

function stripTags (html) {
return String(html || '')
.replace(/<[^>]+>/g, '')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&nbsp;/g, ' ')
.replace(/\s+/g, ' ')
.trim()
}

function htmlToMdUrl (url) {
if (typeof url !== 'string') return url
return url.replace(/\.html(?=[#?]|$)/, '.md')
}

module.exports = { stripTags, htmlToMdUrl }
128 changes: 128 additions & 0 deletions lib/llms-txt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
'use strict'

/**
* Antora extension: generate /llms.txt from the site navigation model.
*
* Inspired by Couchbase's llms-txt.js, but adapted for this dual HTML+Markdown
* site: navigation links point at the generated `.md` twins rather than HTML,
* and we walk Antora's navigationCatalog directly (no separate nav-data JSON).
*
* Spec: https://llmstxt.org/
*/

const { htmlToMdUrl, stripTags } = require('./llm-utils')

function absoluteUrl (siteUrl, path) {
if (!path) return path
if (/^https?:\/\//i.test(path)) return htmlToMdUrl(path)
const base = (siteUrl || '').replace(/\/$/, '')
const rel = path.startsWith('/') ? path : `/${path}`
return htmlToMdUrl(base ? `${base}${rel}` : rel)
}

function navLabel (item) {
return stripTags(item.content) || stripTags(item.title) || 'Untitled'
}

function renderNavItems (items, siteUrl, lines, depth) {
if (!items || !items.length) return

for (const item of items) {
const label = navLabel(item)
const indent = ' '.repeat(depth)
const hasChildren = item.items && item.items.length

if (item.url && item.urlType !== 'fragment') {
lines.push(`${indent}- [${label}](${absoluteUrl(siteUrl, item.url)})`)
if (hasChildren) renderNavItems(item.items, siteUrl, lines, depth + 1)
continue
}

// Section heading without its own page
if (depth === 0 && hasChildren) {
lines.push('')
lines.push(`### ${label}`)
lines.push('')
renderNavItems(item.items, siteUrl, lines, 0)
continue
}

if (label) lines.push(`${indent}- ${label}`)
if (hasChildren) renderNavItems(item.items, siteUrl, lines, depth + 1)
}
}

function buildLlmsTxt (playbook, contentCatalog, navigationCatalog) {
const siteTitle = (playbook.site && playbook.site.title) || 'TigerGraph Documentation'
const siteUrl = (playbook.site && playbook.site.url) || ''
const lines = []

lines.push(`# ${siteTitle}`)
lines.push('')
lines.push(
'> TigerGraph developer documentation for Savanna (cloud-native graph database) and Cloud Classic. ' +
'Prefer the Markdown (`.md`) links below when loading pages into an LLM or coding agent.'
)
lines.push('')
lines.push(`- [HTML site](${siteUrl || '/'})`)
lines.push('')

const components = contentCatalog.getComponents().slice().sort((a, b) => {
// Prefer Savanna first, then alphabetical
if (a.name === 'savanna') return -1
if (b.name === 'savanna') return 1
return a.name.localeCompare(b.name)
})

for (const component of components) {
// Latest / only version for each component (both currently use version "main")
const version = component.latest || component.versions[0]
if (!version) continue

const navTrees = navigationCatalog.getNavigation(component.name, version.version) || []
if (!navTrees.length) continue

const versionLabel =
version.displayVersion && version.displayVersion !== 'default'
? ` (${version.displayVersion})`
: version.version && version.version !== 'main'
? ` (${version.version})`
: ''

lines.push(`## ${version.title}${versionLabel}`)
lines.push('')

for (const tree of navTrees) {
renderNavItems(tree.items || [tree], siteUrl, lines, 0)
}

lines.push('')
}

return `${lines.join('\n').replace(/\n{3,}/g, '\n\n').trim()}\n`
}

module.exports.register = function () {
let snapshot

this.on('navigationBuilt', ({ playbook, contentCatalog, navigationCatalog }) => {
snapshot = { playbook, contentCatalog, navigationCatalog }
})

this.on('beforePublish', ({ siteCatalog }) => {
if (!snapshot) return

const contents = Buffer.from(
buildLlmsTxt(snapshot.playbook, snapshot.contentCatalog, snapshot.navigationCatalog)
)

siteCatalog.addFile({
contents,
mediaType: 'text/plain',
out: { path: 'llms.txt' },
path: 'llms.txt',
pub: { url: '/llms.txt' },
src: { stem: 'llms' },
})
})
}
Loading