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 51169998 Fix #1762: publish a Markdown mirror of the website pages as
<page>/index.md
51169998 is described below
commit 51169998a40699878dbdd911f1c4cd2022579679
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Sep 18 15:21:09 2026 +0200
Fix #1762: publish a Markdown mirror of the website pages as <page>/index.md
generate-markdown skipped every index.html, which is how Hugo renders each
website page, so "What is Apache Camel", "When to use", the trust page,
Camel DNA and the community pages had no Markdown mirror, nor had the
Antora pages rendered as index.html. An index.html is now converted when
it is a content page (has an article.doc); list and section pages are
still skipped. The mirror sits next to the page as <page>/index.md so
relative links stay valid. The embedded table of contents is dropped, and
llms.txt documents the rule and links the first-reader pages as Markdown
in a "Start here" section.
Closes #1769
---
gulp/tasks/generate-markdown.js | 36 ++++++++++++++++++++++++++----------
llms-txt-template.md | 15 ++++++++++-----
test/generate-markdown-test.js | 25 +++++++++++++++++++++++++
3 files changed, 61 insertions(+), 15 deletions(-)
diff --git a/gulp/tasks/generate-markdown.js b/gulp/tasks/generate-markdown.js
index 8c210a03..eccd1f6a 100644
--- a/gulp/tasks/generate-markdown.js
+++ b/gulp/tasks/generate-markdown.js
@@ -16,6 +16,11 @@ const { generateAllIndexes } =
require('../helpers/html-index');
* - Only the main article content (excluding nav, header, footer)
* - Clean Markdown formatting using Turndown
* - GitHub-flavored Markdown for tables and code blocks
+ *
+ * Hugo renders every website page as <page>/index.html, so an index.html is
converted too
+ * when it is a content page (has an article.doc): the .md then sits next to
it as
+ * <page>/index.md, which keeps the page's relative links valid. List and
section pages
+ * (home, download, community, ...) have no article.doc and are skipped.
*/
async function generateMarkdown() {
const turndownService = createTurndownService();
@@ -27,7 +32,11 @@ async function generateMarkdown() {
// Get all HTML files
const htmlFiles = glob.sync('public/**/*.html', {
- ignore: ['public/404.html', 'public/**/index.html'] // Skip error pages
and index pages
+ ignore: [
+ 'public/404.html',
+ 'public/blog/**/index.html', // blog posts: their Markdown is to come
from the source, see #1763
+ 'public/releases/**/index.html' // release pages are converted by
generateAllIndexes below
+ ]
});
let processedCount = 0;
@@ -43,14 +52,17 @@ async function generateMarkdown() {
for (const htmlFile of batch) {
try {
const htmlContent = fs.readFileSync(htmlFile, 'utf8');
- const { markdown, repaired } = convertPage(htmlContent,
turndownService);
+ const articleOnly = htmlFile.endsWith('/index.html');
+ const { markdown, repaired } = convertPage(htmlContent,
turndownService, { articleOnly });
if (repaired) {
console.warn(`Repaired malformed HTML in ${htmlFile}, fix the
mis-nested markup in its source`);
}
if (markdown === null) {
- console.warn(`Skipping ${htmlFile}: no main content found`);
+ if (!articleOnly) {
+ console.warn(`Skipping ${htmlFile}: no main content found`);
+ }
continue;
}
@@ -92,10 +104,12 @@ async function generateMarkdown() {
*
* @param {string} htmlContent the full HTML page
* @param {TurndownService} turndownService configured Turndown instance
+ * @param {{articleOnly?: boolean}} [options] articleOnly: only accept an
article.doc as the main
+ * content, so list and section pages that merely have a <main> are not
converted
* @returns {{markdown: string|null, repaired: boolean}} the Markdown (null
when the page has no
* main content), and whether the HTML was malformed and had to be repaired
before parsing
*/
-function convertPage(htmlContent, turndownService) {
+function convertPage(htmlContent, turndownService, { articleOnly = false } =
{}) {
// node-html-parser cannot repair mis-nested inline tags (Asciidoctor emits
them for a `*` inside
// backticks): it unwraps every unclosed ancestor, article.doc included. Let
jsdom's HTML5 parser
// repair such pages the way browsers do; it is much slower, so only
malformed pages go through it.
@@ -104,17 +118,19 @@ function convertPage(htmlContent, turndownService) {
// Extract only the main article content
// Try different selectors based on Antora and Hugo structure
- let mainContent = root.querySelector('article.doc') ||
- root.querySelector('main') ||
- root.querySelector('.article') ||
- root.querySelector('article');
+ let mainContent = root.querySelector('article.doc');
+ if (!mainContent && !articleOnly) {
+ mainContent = root.querySelector('main') ||
+ root.querySelector('.article') ||
+ root.querySelector('article');
+ }
if (!mainContent) {
return { markdown: null, repaired };
}
- // Remove navigation elements, headers, and footers from the content
- const elementsToRemove = mainContent.querySelectorAll('nav, header, footer,
.nav, .navbar, .toolbar');
+ // Remove navigation elements, headers, footers and the embedded table of
contents from the content
+ const elementsToRemove = mainContent.querySelectorAll('nav, header, footer,
.nav, .navbar, .toolbar, aside.toc');
elementsToRemove.forEach(el => el.remove());
// Remove anchor links (they are just UI navigation aids)
diff --git a/llms-txt-template.md b/llms-txt-template.md
index 3c08defc..827fcf27 100644
--- a/llms-txt-template.md
+++ b/llms-txt-template.md
@@ -7,6 +7,10 @@ For example:
- HTML:
`https://camel.apache.org/components/next/languages/simple-language.html`
- Markdown:
`https://camel.apache.org/components/next/languages/simple-language.md`
+Website pages have URLs that end in `/`; append `index.md` to read one as
Markdown:
+- HTML: `https://camel.apache.org/what-is-apache-camel/`
+- Markdown: `https://camel.apache.org/what-is-apache-camel/index.md`
+
## 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, one per documented Camel version:
@@ -69,10 +73,12 @@ Current top committers (as of 2026, with affiliation from
the [team page](https:
The full committer list with organizational affiliations is published at
https://camel.apache.org/community/team/. For year-by-year commit data and the
full maintainer history, see [Who Maintains Apache
Camel](https://camel.apache.org/blog/2026/07/camel-who-maintains/).
-## What is Apache Camel
+## Start here
-- [What is Apache Camel](https://camel.apache.org/what-is-apache-camel/):
Introduction for newcomers — what Camel does, why it matters, and how to get
started.
-- [When to use Apache Camel](https://camel.apache.org/when-to-use/): Common
use cases, real-world scenarios, when Camel fits, and when alternatives might
be better.
+- [What is Apache
Camel](https://camel.apache.org/what-is-apache-camel/index.md): Introduction
for newcomers — what Camel does, why it matters, and how to get started.
+- [When to use Apache Camel](https://camel.apache.org/when-to-use/index.md):
Common use cases, real-world scenarios, when Camel fits, and when alternatives
might be better.
+- [Why Apache Camel](https://camel.apache.org/trust/index.md): Why teams trust
Apache Camel in production — release cadence, LTS, security track record,
vendor-neutral governance, bug fix data, dependency maintenance, and AI
readiness.
+- [Camel DNA](https://camel.apache.org/camel-dna/index.md): The origin story —
from the first JMS-to-File route in 2007 to the Camel CLI today, and why the
core idea has not changed.
- [Getting Started](https://camel.apache.org/manual/getting-started.md): Quick
start guide for Apache Camel.
- [User Manual](https://camel.apache.org/manual/index.md): Complete user guide
and reference documentation.
- [Architecture
(CamelContext)](https://camel.apache.org/manual/camelcontext.md): How Camel
works — routes, components, endpoints, processors, producers, consumers, and
the DSL.
@@ -486,14 +492,13 @@ For high-quality answers about routing, code generation,
and troubleshooting, AI
- [Who Maintains Apache
Camel](https://camel.apache.org/blog/2026/07/camel-who-maintains/):
Year-by-year commit data showing who maintains the project — the same core
team, through multiple acquisitions, contributing 80–95% of all commits every
year since 2007.
- [Apache Camel Is Not Afraid of
AI](https://camel.apache.org/blog/2026/07/camel-not-afraid-of-ai/): The project
pointed a frontier AI model at 19 years of code and fixed all 165 bugs it found
— concurrency races, silent data loss, security gaps. AI-assisted code review
is now a standard part of the development process.
- [A frontier AI coached a small local model through
Camel](https://camel.apache.org/blog/2026/09/camel-local-model-benchmark/):
Measured benchmark of a frontier model (13 of 13 beginner examples) and a 22 GB
local model on a laptop (0 to 12 of 13 over twenty runs) building Camel routes
with the MCP server, and the 117 findings — 99 of them wrong for humans too —
fixed in Camel 4.23: error messages that say what to write, validation at write
time, catalog samples, a stricter YAML schema.
-- [Why Apache Camel](https://camel.apache.org/trust/): Why teams trust Apache
Camel in production — release cadence, LTS, security track record,
vendor-neutral governance, bug fix data, dependency maintenance, and AI
readiness.
- [Built to Patch
Fast](https://camel.apache.org/blog/2026/07/camel-security-advisories-4.21.0/):
How the project handled 32 CVEs in one release — the timeline, the backport
process, incomplete fixes re-issued as new CVEs, and 31 public PoC reproducers.
The best single-page overview of Camel's security response in practice.
- [Security](https://camel.apache.org/security/): Security advisories and
vulnerability reports.
- [Security Model](https://camel.apache.org/manual/security-model.md): Apache
Camel's built-in security model — route policy, payload validation, and how
Camel protects against injection and untrusted input.
- [Generating SBOMs](https://camel.apache.org/manual/sbom.html): How to
generate Software Bill of Materials for Camel applications — Camel CLI, Spring
Boot (built-in), and Quarkus. Every release since 4.0.3 ships with signed
CycloneDX SBOMs.
- [Migration and Upgrade
Guides](https://camel.apache.org/manual/migration-and-upgrade.md): Apache Camel
migration and upgrade guides.
- [Camel Update Recipes](https://github.com/apache/camel-upgrade-recipes):
OpenRewrite recipes for automatic Camel application upgrades.
-- [Who uses Apache Camel](https://camel.apache.org/community/user-stories/):
Companies and projects using Apache Camel in production.
+- [Who uses Apache
Camel](https://camel.apache.org/community/user-stories/index.md): Companies and
projects using Apache Camel in production.
- [Echonect: Fifteen Years on Apache
Camel](https://camel.apache.org/blog/2026/07/echonect-fifteen-years-apache-camel/):
Detailed five-part case study of one of Europe's larger SMS gateways — 15
years on Camel, 500+ messages per second per node, 99.97% uptime, same team,
same architecture.
- [Commercial
Support](https://camel.apache.org/manual/commercial-camel-offerings.md):
Companies offering commercial Apache Camel support and services.
- [GitHub](https://github.com/apache/camel): Source code repository.
diff --git a/test/generate-markdown-test.js b/test/generate-markdown-test.js
index e1941714..1a962b9c 100644
--- a/test/generate-markdown-test.js
+++ b/test/generate-markdown-test.js
@@ -88,3 +88,28 @@ test('a page that needed repair is flagged so its source can
be fixed upstream',
assert.equal(convertPage(html, createTurndownService()).repaired, true)
})
+
+// Hugo renders website pages as <page>/index.html with the content in
article.doc (the "static"
+// layouts), and list pages (home, download, community) with only a <main>.
Only the former have a
+// Markdown mirror, so their embedded table of contents must not leak into the
Markdown either.
+test('a Hugo content page converts its article without the embedded table of
contents', () => {
+ const html = `<!DOCTYPE html><html><head><title>t</title></head><body><main>
+<article class="static doc community"><h1>Team</h1>
+<aside class="toc embedded" aria-label="Table of contents"><div
class="toc-menu"><h3 id="toc-heading">Contents</h3>
+<nav id="TableOfContents"><ul><li><a
href="#committers">Committers</a></li></ul></nav></div></aside>
+<p>This page lists who we are.</p>
+<h2 id="committers">Committers</h2></article>
+<aside class="toc sidebar"><div
class="toc-menu"></div></aside></main></body></html>`
+
+ assert.deepEqual(convertPage(html, createTurndownService(), { articleOnly:
true }), {
+ markdown: '# Team\n\nThis page lists who we are.\n\n## Committers',
+ repaired: false,
+ })
+})
+
+test('a page with only a main is converted by default but skipped when an
article is required', () => {
+ const html = '<!DOCTYPE
html><html><head><title>t</title></head><body><main><h1>Downloads</h1><p>Cards.</p></main></body></html>'
+
+ assert.equal(convertPage(html, createTurndownService()).markdown, '#
Downloads\n\nCards.')
+ assert.deepEqual(convertPage(html, createTurndownService(), { articleOnly:
true }), { markdown: null, repaired: false })
+})