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)
+})