diff --git a/.github/workflows/offline-bundle.yml b/.github/workflows/offline-bundle.yml new file mode 100644 index 00000000000..c2c9206d98a --- /dev/null +++ b/.github/workflows/offline-bundle.yml @@ -0,0 +1,69 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Generate Offline Documentation Bundle + +on: + workflow_dispatch: + inputs: + camel_version: + description: 'Camel version to bundle (e.g. 4.18)' + required: true + type: string + +jobs: + generate-bundle: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Install dependencies + run: yarn workspaces foreach --all install + + - name: Build site + run: yarn build + env: + CAMEL_ENV: production + HUGO_OPTIONS: --buildFuture + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate versioned bundle + run: node scripts/generate-offline-bundle.js ${{ inputs.camel_version }} + + - name: Create or update GitHub Release + run: | + TAG="docs-${{ inputs.camel_version }}" + FILE="public/camel-docs-${{ inputs.camel_version }}.zip" + TITLE="Apache Camel ${{ inputs.camel_version }} — Offline Documentation" + NOTES="Offline documentation bundle for Apache Camel ${{ inputs.camel_version }}. + + Contains 350+ connector/component docs (Markdown), the user manual, the Camel Catalog (JSON metadata for all connectors/components, data formats, languages, and EIPs), and the YAML DSL canonical JSON Schema. + + Download and unzip locally for AI coding agents or environments with restricted internet access." + + # delete existing release if present so we can recreate it + gh release delete "$TAG" --yes 2>/dev/null || true + + gh release create "$TAG" "$FILE" \ + --title "$TITLE" \ + --notes "$NOTES" \ + --latest=false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/llms-txt-template.md b/llms-txt-template.md index badf76d9c4c..98dd39296d9 100644 --- a/llms-txt-template.md +++ b/llms-txt-template.md @@ -7,6 +7,30 @@ For example: - HTML: `https://camel.apache.org/components/next/languages/simple-language.html` - Markdown: `https://camel.apache.org/components/next/languages/simple-language.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: +- [Camel 4.18](https://github.com/apache/camel-website/releases/download/docs-4.18/camel-docs-4.18.zip) +- [Camel 4.14](https://github.com/apache/camel-website/releases/download/docs-4.14/camel-docs-4.14.zip) + +Download the zip matching your Camel version, unzip it locally, and read the files from there. Each bundle contains: + +``` +components// — 350+ connector/component docs (Markdown) +manual/ — user manual (Markdown) +catalog/ + components/ — 350+ connector/component metadata (JSON) + dataformats/ — data format metadata (JSON) + languages/ — expression language metadata (JSON) + models/ — EIP model metadata (JSON) + others/ — other component metadata (JSON) + schema/ + camelYamlDsl-canonical.json — YAML DSL JSON Schema +llms.txt — this file +``` + +The `catalog/` JSON files contain machine-readable metadata for every connector/component, data format, language, and EIP — parameters, types, defaults, and descriptions. The YAML DSL schema is the definitive spec for validating and generating Camel YAML routes. + ## Key facts - Apache Camel is a **library**, not a platform — it embeds in your existing Spring Boot or Quarkus application @@ -57,7 +81,7 @@ For example: - [Camel MCP Server](https://camel.apache.org/manual/camel-jbang-mcp.md): Model Context Protocol server for AI coding assistants (Claude Code, GitHub Copilot, Cursor, Gemini CLI). - [Camel LangChain4j](https://camel.apache.org/components/next/langchain4j-chat-component.md): LLM integration via LangChain4j. - [Camel OpenAI](https://camel.apache.org/components/next/openai-component.md): Native OpenAI component. -- A2A (Agent-to-Agent): Camel supports the A2A protocol for connecting AI agents to enterprise systems. +- [Camel A2A](https://camel.apache.org/components/next/a2a-component.md): Agent-to-Agent (A2A) protocol component — expose Camel routes as A2A agents or call remote A2A agents. Supports HTTP+JSON and JSONRPC bindings, OAuth/OIDC/API-key auth, and SSE streaming. ## Tooling diff --git a/scripts/generate-offline-bundle.js b/scripts/generate-offline-bundle.js new file mode 100644 index 00000000000..7de6cefe36b --- /dev/null +++ b/scripts/generate-offline-bundle.js @@ -0,0 +1,158 @@ +const fs = require('fs'); +const path = require('path'); +const { execFileSync } = require('child_process'); + +const PUBLIC_DIR = 'public'; +const CAMEL_REPO = 'apache/camel'; +const CATALOG_BASE = 'catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog'; +const YAML_SCHEMA_PATH = 'dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json'; + +const CATALOG_DIRS = ['components', 'dataformats', 'languages', 'models', 'others']; + +const VERSION_DIRS = [ + 'components' +]; + +const SHARED_DIRS = [ + 'manual' +]; + +function fetchCatalogAndSchema(version) { + const branch = `camel-${version}.x`; + const catalogDir = path.join(PUBLIC_DIR, 'catalog'); + + console.log(`\nFetching catalog and YAML DSL schema from ${CAMEL_REPO}@${branch}...`); + + // fetch YAML DSL canonical schema + const schemaDir = path.join(catalogDir, 'schema'); + fs.mkdirSync(schemaDir, { recursive: true }); + try { + const schema = execFileSync('gh', [ + 'api', `repos/${CAMEL_REPO}/contents/${YAML_SCHEMA_PATH}?ref=${branch}`, + '--jq', '.content' + ], { encoding: 'utf8' }); + fs.writeFileSync( + path.join(schemaDir, 'camelYamlDsl-canonical.json'), + Buffer.from(schema.trim(), 'base64').toString('utf8') + ); + console.log(' Fetched schema/camelYamlDsl-canonical.json'); + } catch (error) { + console.warn(` Could not fetch YAML DSL schema: ${error.message}`); + } + + // fetch catalog JSON files for each category + for (const dir of CATALOG_DIRS) { + const targetDir = path.join(catalogDir, dir); + fs.mkdirSync(targetDir, { recursive: true }); + try { + const files = execFileSync('gh', [ + 'api', `repos/${CAMEL_REPO}/contents/${CATALOG_BASE}/${dir}?ref=${branch}`, + '--jq', '.[].name' + ], { encoding: 'utf8' }).trim().split('\n').filter(f => f.endsWith('.json')); + + console.log(` Fetching catalog/${dir}/ (${files.length} files)...`); + + for (const file of files) { + try { + const content = execFileSync('gh', [ + 'api', `repos/${CAMEL_REPO}/contents/${CATALOG_BASE}/${dir}/${file}?ref=${branch}`, + '--jq', '.content' + ], { encoding: 'utf8' }); + fs.writeFileSync( + path.join(targetDir, file), + Buffer.from(content.trim(), 'base64').toString('utf8') + ); + } catch { + // skip individual file failures silently + } + } + } catch (error) { + console.warn(` Could not fetch catalog/${dir}: ${error.message}`); + } + } + + return fs.existsSync(catalogDir); +} + +function main() { + const version = process.argv[2]; + if (!version) { + console.error('Usage: node scripts/generate-offline-bundle.js '); + console.error('Example: node scripts/generate-offline-bundle.js 4.18'); + process.exit(1); + } + + const versionDir = `${version}.x`; + const bundleName = `camel-docs-${version}.zip`; + const bundlePath = path.join(PUBLIC_DIR, bundleName); + + if (!fs.existsSync(PUBLIC_DIR)) { + console.error(`Cannot generate ${bundleName}: '${PUBLIC_DIR}' directory not found`); + process.exit(1); + } + + // collect paths to include (relative to public/) + const includePaths = []; + + for (const dir of VERSION_DIRS) { + const fullPath = path.join(PUBLIC_DIR, dir, versionDir); + if (fs.existsSync(fullPath)) { + includePaths.push(`${dir}/${versionDir}/*`); + console.log(` Including ${dir}/${versionDir}/`); + } else { + console.warn(` Skipping ${dir}/${versionDir}/ (not found)`); + } + } + + for (const dir of SHARED_DIRS) { + const fullPath = path.join(PUBLIC_DIR, dir); + if (fs.existsSync(fullPath)) { + includePaths.push(`${dir}/*`); + console.log(` Including ${dir}/`); + } + } + + if (includePaths.length === 0) { + console.error(`No documentation directories found for version ${version}`); + process.exit(1); + } + + // always include llms.txt if present + if (fs.existsSync(path.join(PUBLIC_DIR, 'llms.txt'))) { + includePaths.push('llms.txt'); + } + + // fetch catalog and YAML DSL schema from the camel repo + if (fetchCatalogAndSchema(version)) { + includePaths.push('catalog/*'); + } + + // remove stale bundle + if (fs.existsSync(bundlePath)) { + fs.unlinkSync(bundlePath); + } + + // build zip: include .md files from doc dirs, plus .json from catalog, plus llms.txt + const zipArgs = ['-r', '-q', bundleName, '.']; + for (const p of includePaths) { + if (p === 'llms.txt') { + zipArgs.push('-i', 'llms.txt'); + } else if (p.startsWith('catalog/')) { + zipArgs.push('-i', `${p}.json`); + } else { + zipArgs.push('-i', `${p}.md`); + } + } + + try { + execFileSync('zip', zipArgs, { cwd: PUBLIC_DIR, stdio: 'inherit' }); + + const sizeMb = (fs.statSync(bundlePath).size / (1024 * 1024)).toFixed(1); + console.log(`\nGenerated ${bundleName} (${sizeMb} MB)`); + } catch (error) { + console.error(`Failed to generate ${bundleName}:`, error.message); + process.exit(1); + } +} + +main();