This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-website.git


The following commit(s) were added to refs/heads/main by this push:
     new a1d6c2b9 Fix #1764: generate the offline bundle list in llms.txt from 
the documented Camel versions
a1d6c2b9 is described below

commit a1d6c2b949ce4d0cfdfa6b16e072560186c289bf
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Sep 18 15:20:55 2026 +0200

    Fix #1764: generate the offline bundle list in llms.txt from the documented 
Camel versions
    
    The bundle section of llms.txt was hand-written and listed only Camel 4.18
    although a docs-4.22 bundle has existed since August. The list is now filled
    in from the versioned component directories Antora builds
    (public/components/<major.minor>.x), so it follows the branches in the
    Antora playbook. The stale note that the 4.18 bundle lacks the YAML DSL
    schema is gone, llms.txt says next has no bundle, and the README explains
    that the bundle workflow must be run when a new LTS branch is added.
    
    Closes #1768
---
 README.md                |  7 ++++++
 gulp/helpers/llms-txt.js | 57 ++++++++++++++++++++++++++++++++++++++++++++----
 llms-txt-template.md     |  6 ++---
 test/llms-txt-test.js    | 47 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 110 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index 449b3b35..f1c7618c 100644
--- a/README.md
+++ b/README.md
@@ -497,6 +497,13 @@ For example, the 4.18 bundle would be at 
`https://github.com/apache/camel-websit
 Trigger this after each Camel release. For example, when Camel `4.18.1` ships, 
run the workflow
 with version `4.18` to update the `docs-4.18` bundle with the latest 
documentation.
 
+The bundle list in [`/llms.txt`](https://camel.apache.org/llms.txt) is not 
maintained by hand:
+`gulp/helpers/llms-txt.js` fills the `<!-- offline-bundles -->` placeholder in
+`llms-txt-template.md` with one link per versioned directory under 
`public/components/`, that is
+per branch listed for the component docs in `antora-playbook-production.yml`. 
So when a new LTS
+branch is added to the playbook, run this workflow for that version too, 
otherwise `llms.txt`
+links to a bundle that does not exist yet. There is no bundle for `next`.
+
 ## Search Indexing Configuration
 
 The website uses [Algolia DocSearch](https://docsearch.algolia.com/) to 
provide site-wide search functionality. The search configuration is defined in 
[`.docsearch.config.json`](.docsearch.config.json).
diff --git a/gulp/helpers/llms-txt.js b/gulp/helpers/llms-txt.js
index 8f23afdc..0b01ca38 100644
--- a/gulp/helpers/llms-txt.js
+++ b/gulp/helpers/llms-txt.js
@@ -1,22 +1,71 @@
 const fs = require('fs');
 const path = require('path');
 
+// Placeholder in llms-txt-template.md that the generated bundle list replaces
+const BUNDLE_MARKER = '<!-- offline-bundles -->';
+const BUNDLE_DOWNLOAD_BASE = 
'https://github.com/apache/camel-website/releases/download';
+
+/**
+ * Lists the Camel versions that have versioned documentation built into the 
site, newest first.
+ * Antora writes one directory per documented branch (4.18.x, 4.22.x, ...) 
next to the unversioned
+ * `latest` and `next` aliases, so the site build itself is the source of 
truth for which offline
+ * bundles exist.
+ *
+ * @param {string} componentsDir - Directory holding the versioned component 
docs
+ * @returns {Array<string>} major.minor versions, e.g. ['4.22', '4.18']
+ */
+function bundleVersions(componentsDir = 'public/components') {
+  if (!fs.existsSync(componentsDir)) {
+    return [];
+  }
+  return fs.readdirSync(componentsDir)
+    .filter(name => /^\d+\.\d+\.x$/.test(name))
+    .map(name => name.replace(/\.x$/, ''))
+    .sort((a, b) => {
+      const [aMajor, aMinor] = a.split('.').map(Number);
+      const [bMajor, bMinor] = b.split('.').map(Number);
+      return bMajor - aMajor || bMinor - aMinor;
+    });
+}
+
+/**
+ * Fills the template with the offline bundle list.
+ *
+ * @param {string} template - Content of llms-txt-template.md
+ * @param {Array<string>} versions - Camel versions with an offline bundle, as 
returned by bundleVersions
+ * @returns {string} the llms.txt content
+ */
+function renderLlmsTxt(template, versions) {
+  if (!template.includes(BUNDLE_MARKER)) {
+    throw new Error(`llms.txt template has no ${BUNDLE_MARKER} placeholder for 
the offline bundle list`);
+  }
+  if (versions.length === 0) {
+    throw new Error('No versioned component docs found, cannot list the 
offline documentation bundles');
+  }
+  const bundleList = versions
+    .map(version => `- [Camel 
${version}](${BUNDLE_DOWNLOAD_BASE}/docs-${version}/camel-docs-${version}.zip)`)
+    .join('\n');
+  return template.replace(BUNDLE_MARKER, bundleList);
+}
+
 /**
  * Generates the /llms.txt file as per https://llmstxt.org/ specification.
  * This file helps LLMs discover and understand the structure of the 
documentation.
- * Reads from llms-txt-template.md and uses it as content.
+ * Reads from llms-txt-template.md and fills in the offline bundle list from 
the built site.
  *
  * @param {Array<string>} pages - Array of page URLs that were converted to 
markdown
  */
 function generateLlmsTxt(pages) {
   // Read the template file
   const templatePath = path.join(__dirname, '../../llms-txt-template.md');
-  let llmsTxtContent = fs.readFileSync(templatePath, 'utf8');
+  const template = fs.readFileSync(templatePath, 'utf8');
 
-  fs.writeFileSync('public/llms.txt', llmsTxtContent, 'utf8');
+  fs.writeFileSync('public/llms.txt', renderLlmsTxt(template, 
bundleVersions()), 'utf8');
   console.log('Generated /llms.txt');
 }
 
 module.exports = {
-  generateLlmsTxt
+  generateLlmsTxt,
+  bundleVersions,
+  renderLlmsTxt
 };
diff --git a/llms-txt-template.md b/llms-txt-template.md
index f5b13d97..3c08defc 100644
--- a/llms-txt-template.md
+++ b/llms-txt-template.md
@@ -9,10 +9,10 @@ For example:
 
 ## Offline documentation bundles
 
-For agents or environments with no or restricted internet access, versioned 
offline documentation bundles are available as zip archives of all Markdown 
files:
-- [Camel 
4.18](https://github.com/apache/camel-website/releases/download/docs-4.18/camel-docs-4.18.zip)
 (does not include the canonical YAML DSL JSON Schema — use the [online 
schema](https://github.com/apache/camel/blob/camel-4.18.x/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json)
 instead)
+For agents or environments with no or restricted internet access, versioned 
offline documentation bundles are available as zip archives of all Markdown 
files, one per documented Camel version:
+<!-- offline-bundles -->
 
-Download the zip matching your Camel version, unzip it locally, and read the 
files from there. Each bundle contains:
+Download the zip matching your Camel version, unzip it locally, and read the 
files from there. There is no bundle for `next` (the unreleased main branch): 
that documentation changes daily, read it online under 
`https://camel.apache.org/components/next/`. Each bundle contains:
 
 ```
 components/<version>/      — 350+ connector/component docs (Markdown)
diff --git a/test/llms-txt-test.js b/test/llms-txt-test.js
new file mode 100644
index 00000000..77753a62
--- /dev/null
+++ b/test/llms-txt-test.js
@@ -0,0 +1,47 @@
+'use strict'
+
+const assert = require('node:assert/strict')
+const fs = require('node:fs')
+const os = require('node:os')
+const path = require('node:path')
+const test = require('node:test')
+
+const { bundleVersions, renderLlmsTxt } = require('../gulp/helpers/llms-txt')
+
+function componentsDir (...names) {
+  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'llms-txt-'))
+  for (const name of names) fs.mkdirSync(path.join(dir, name))
+  return dir
+}
+
+const TEMPLATE = 'Bundles:\n<!-- offline-bundles -->\n\nUnzip it.\n'
+
+test('the built versioned component docs give the bundle versions, newest 
first', () => {
+  const dir = componentsDir('4.18.x', 'latest', 'next', '4.22.x', '4.8.x', 
'README.md')
+
+  assert.deepEqual(bundleVersions(dir), ['4.22', '4.18', '4.8'])
+})
+
+test('a missing components directory yields no versions', () => {
+  assert.deepEqual(bundleVersions(path.join(os.tmpdir(), 'llms-txt-missing')), 
[])
+})
+
+test('the placeholder becomes one download link per version', () => {
+  assert.equal(renderLlmsTxt(TEMPLATE, ['4.22', '4.18']), `Bundles:
+- [Camel 
4.22](https://github.com/apache/camel-website/releases/download/docs-4.22/camel-docs-4.22.zip)
+- [Camel 
4.18](https://github.com/apache/camel-website/releases/download/docs-4.18/camel-docs-4.18.zip)
+
+Unzip it.
+`)
+})
+
+test('rendering fails rather than publishing an empty or unfilled bundle 
list', () => {
+  assert.throws(() => renderLlmsTxt(TEMPLATE, []), /No versioned component 
docs/)
+  assert.throws(() => renderLlmsTxt('no placeholder', ['4.22']), /placeholder/)
+})
+
+test('the real template carries the placeholder', () => {
+  const template = fs.readFileSync(path.join(__dirname, 
'../llms-txt-template.md'), 'utf8')
+
+  assert.match(renderLlmsTxt(template, ['4.22']), /^- \[Camel 
4\.22\]\(.*camel-docs-4\.22\.zip\)$/m)
+})

Reply via email to