Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a7d22fa
docs: Add User Guide section
dishaprakash Mar 5, 2026
a9c315c
rebase
dishaprakash Mar 5, 2026
50e37bb
docs: Add reference and other docs
dishaprakash Mar 5, 2026
43f2b69
Update docs/en/reference/faq.md
dishaprakash Mar 5, 2026
8d97bab
chore: Add workflows to deploy to cloudflare
dishaprakash Mar 6, 2026
8f0ea15
use sticky comment
dishaprakash Mar 6, 2026
6e1c0c3
remove prs from pull_request_target and add second hugo config
dishaprakash Mar 7, 2026
a2eb7be
add header
dishaprakash Mar 7, 2026
8a58ffa
docs: implement deep re-routing, pagination, and migration banner
dishaprakash Mar 7, 2026
4a5432e
Apply suggestion from @gemini-code-assist[bot]
dishaprakash Mar 7, 2026
d45d9b6
gemini review changes
dishaprakash Mar 7, 2026
80822be
minor fix
dishaprakash Mar 7, 2026
c654670
docs: Optimize llms.txt and llms-full.txt
dishaprakash Mar 7, 2026
315df49
add documentation
dishaprakash Mar 8, 2026
3833b43
update integration directory structure
dishaprakash Mar 8, 2026
a48e3dd
docs: Add page standardization to external source integrations
dishaprakash Mar 8, 2026
435acc6
add documentation and ci check
dishaprakash Mar 8, 2026
40dd323
docs: Add page standardization to external tool integrations
dishaprakash Mar 8, 2026
e74b609
docs: integrate Pagefind search, Google Analytics, and feedback buttons
dishaprakash Mar 9, 2026
06ae36b
Update .hugo/layouts/partials/search-input.html
dishaprakash Mar 9, 2026
9ab6076
cleanup
dishaprakash Mar 9, 2026
922dcf4
Merge branch 'documentation-reorg' into google-analytics
dishaprakash Mar 12, 2026
9fd9356
Update lint-docs-source-page.sh
dishaprakash Mar 12, 2026
9b31a01
Update lint-docs-tool-page.sh
dishaprakash Mar 12, 2026
e9d01e2
Update DEVELOPER.md
dishaprakash Mar 12, 2026
42e31a3
Update deploy_previous_version_docs.yaml
dishaprakash Mar 12, 2026
1912d38
Update deploy_previous_version_docs_to_cf.yaml
dishaprakash Mar 12, 2026
f848c58
Update deploy_versioned_docs.yaml
dishaprakash Mar 12, 2026
cd80e83
Update deploy_versioned_docs_to_cf.yaml
dishaprakash Mar 12, 2026
fca742d
Update docs_preview_deploy_cf.yaml
dishaprakash Mar 12, 2026
38fb103
Update _index.md
dishaprakash Mar 12, 2026
f114119
Update cockroachdb-list-schemas.md
dishaprakash Mar 12, 2026
e218290
Update lint-docs-tool-page.sh
dishaprakash Mar 12, 2026
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
117 changes: 117 additions & 0 deletions .ci/lint-docs-source-page.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/bin/bash
set -e

python3 - << 'EOF'
import os
import re
import sys
from pathlib import Path

integration_dir = Path("./docs/en/integrations")
if not integration_dir.exists():
print("Info: Directory './docs/en/integrations' not found. Skipping linting.")
sys.exit(0)

ALLOWED_ORDER = [
"About",
"Available Tools",
"Requirements",
"Example",
"Reference",
"Advanced Usage",
"Troubleshooting",
"Additional Resources"
]
REQUIRED = {"About", "Example", "Reference"}

# Regex to catch any variation of the list-tools shortcode, including parameters
SHORTCODE_PATTERN = r"\{\{<\s*list-tools.*?>\}\}"

has_errors = False

# Find all _index.md files inside the subdirectories of integrations/
for filepath in integration_dir.rglob("_index.md"):
# Skip the top-level integrations/_index.md if it exists
if filepath.parent == integration_dir:
continue

with open(filepath, "r", encoding="utf-8") as f:
content = f.read()

# Separate YAML frontmatter from the markdown body
match = re.match(r'^\s*---\s*\n(.*?)\n---\s*(.*)', content, re.DOTALL)
if match:
frontmatter = match.group(1)
body = match.group(2)
else:
frontmatter = ""
body = content

# If the file has no markdown content (metadata placeholder only), skip it entirely
if not body.strip():
continue

file_errors = False

# 1. Check Frontmatter Title
title_source = frontmatter if frontmatter else content
title_match = re.search(r"^title:\s*[\"']?(.*?)[\"']?\s*$", title_source, re.MULTILINE)
if not title_match or not title_match.group(1).strip().endswith("Source"):
found_title = title_match.group(1) if title_match else "None"
print(f"[{filepath}] Error: Frontmatter title must end with 'Source'. Found: '{found_title}'")
file_errors = True

# 2. Check Shortcode Placement ONLY IF "Available Tools" heading is present
tools_section_match = re.search(r"^##\s+Available Tools\s*(.*?)(?=^##\s|\Z)", body, re.MULTILINE | re.DOTALL)
if tools_section_match:
if not re.search(SHORTCODE_PATTERN, tools_section_match.group(1)):
print(f"[{filepath}] Error: The list-tools shortcode must be placed under the '## Available Tools' heading.")
file_errors = True
else:
# Prevent edge case where shortcode is used but the heading was forgotten
if re.search(SHORTCODE_PATTERN, body):
print(f"[{filepath}] Error: A list-tools shortcode was found, but the '## Available Tools' heading is missing.")
file_errors = True

# 3. Strip code blocks from body to avoid linting example markdown headings
clean_body = re.sub(r"```.*?```", "", body, flags=re.DOTALL)

# 4. Check H1 Headings
if re.search(r"^#\s+\w+", clean_body, re.MULTILINE):
print(f"[{filepath}] Error: H1 headings (#) are forbidden in the body.")
file_errors = True

# 5. Check H2 Headings
h2s = re.findall(r"^##\s+(.*)", clean_body, re.MULTILINE)
h2s = [h2.strip() for h2 in h2s]

# Missing Required
missing = REQUIRED - set(h2s)
if missing:
print(f"[{filepath}] Error: Missing required H2 headings: {missing}")
file_errors = True

# Unauthorized Headings
unauthorized = set(h2s) - set(ALLOWED_ORDER)
if unauthorized:
print(f"[{filepath}] Error: Unauthorized H2 headings found: {unauthorized}")
file_errors = True

# Strict Ordering
filtered_h2s = [h for h in h2s if h in ALLOWED_ORDER]
expected_order = [h for h in ALLOWED_ORDER if h in h2s]
if filtered_h2s != expected_order:
print(f"[{filepath}] Error: Headings are out of order.")
print(f" Expected: {expected_order}")
print(f" Found: {filtered_h2s}")
file_errors = True

if file_errors:
has_errors = True

if has_errors:
print("Linting failed. Please fix the structure errors above.")
sys.exit(1)
else:
print("Success: All Source pages passed structure validation.")
EOF
118 changes: 118 additions & 0 deletions .ci/lint-docs-tool-page.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/bin/bash
set -e

python3 - << 'EOF'
import os
import re
import sys
from pathlib import Path

integration_dir = Path("./docs/en/integrations")
if not integration_dir.exists():
print("Info: Directory './docs/en/integrations' not found. Skipping linting.")
sys.exit(0)

ALLOWED_ORDER = [
"About",
"Compatible Sources",
"Requirements",
"Parameters",
"Example",
"Output Format",
"Reference",
"Advanced Usage",
"Troubleshooting",
"Additional Resources"
]
REQUIRED = {"About", "Example"}

# Regex to catch any variation of the compatible-sources shortcode
SHORTCODE_PATTERN = r"\{\{<\s*compatible-sources.*?>\}\}"

has_errors = False

# Find all .md files, excluding _index.md (which are Source pages)
for filepath in integration_dir.rglob("*.md"):
if filepath.name == "_index.md":
continue

with open(filepath, "r", encoding="utf-8") as f:
content = f.read()

# Separate YAML frontmatter from the markdown body
match = re.match(r'^\s*---\s*\n(.*?)\n---\s*(.*)', content, re.DOTALL)
if match:
frontmatter = match.group(1)
body = match.group(2)
else:
frontmatter = ""
body = content

# If the file has no markdown content (metadata placeholder only), skip it entirely
if not body.strip():
continue

file_errors = False

# 1. Check Frontmatter Title
title_source = frontmatter if frontmatter else content
title_match = re.search(r"^title:\s*[\"']?(.*?)[\"']?\s*$", title_source, re.MULTILINE)
if not title_match or not title_match.group(1).strip().endswith("Tool"):
found_title = title_match.group(1) if title_match else "None"
print(f"[{filepath}] Error: Frontmatter title must end with 'Tool'. Found: '{found_title}'")
file_errors = True

# 2. Check Shortcode Placement
sources_section_match = re.search(r"^##\s+Compatible Sources\s*(.*?)(?=^##\s|\Z)", body, re.MULTILINE | re.DOTALL)
if sources_section_match:
if not re.search(SHORTCODE_PATTERN, sources_section_match.group(1)):
print(f"[{filepath}] Error: The compatible-sources shortcode must be placed under the '## Compatible Sources' heading.")
file_errors = True
else:
# Prevent edge case where shortcode is used but the heading was forgotten
if re.search(SHORTCODE_PATTERN, body):
print(f"[{filepath}] Error: A compatible-sources shortcode was found, but the '## Compatible Sources' heading is missing.")
file_errors = True

# 3. Strip code blocks from body to avoid linting example markdown headings
clean_body = re.sub(r"```.*?```", "", body, flags=re.DOTALL)

# 4. Check H1 Headings
if re.search(r"^#\s+\w+", clean_body, re.MULTILINE):
print(f"[{filepath}] Error: H1 headings (#) are forbidden in the body.")
file_errors = True

# 5. Check H2 Headings
h2s = re.findall(r"^##\s+(.*)", clean_body, re.MULTILINE)
h2s = [h2.strip() for h2 in h2s]

# Missing Required
missing = REQUIRED - set(h2s)
if missing:
print(f"[{filepath}] Error: Missing required H2 headings: {missing}")
file_errors = True

# Unauthorized Headings
unauthorized = set(h2s) - set(ALLOWED_ORDER)
if unauthorized:
print(f"[{filepath}] Error: Unauthorized H2 headings found: {unauthorized}")
file_errors = True

# Strict Ordering
filtered_h2s = [h for h in h2s if h in ALLOWED_ORDER]
expected_order = [h for h in ALLOWED_ORDER if h in h2s]
if filtered_h2s != expected_order:
print(f"[{filepath}] Error: Headings are out of order.")
print(f" Expected: {expected_order}")
print(f" Found: {filtered_h2s}")
file_errors = True

if file_errors:
has_errors = True

if has_errors:
print("Linting failed for Tool pages. Please fix the structure errors above.")
sys.exit(1)
else:
print("Success: All Tool pages passed structure validation.")
EOF
42 changes: 42 additions & 0 deletions .github/workflows/cloudflare_sync.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2026 Google LLC
#
# Licensed 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: "Sync v1 docsite to Cloudflare"

concurrency:
group: cloudflare-sync
cancel-in-progress: true

on:
workflow_run:
workflows: ["CF: Deploy Dev Docs", "CF: Deploy Versioned Docs", "CF: Deploy Previous Version Docs"]
types: [completed]

jobs:
deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: actions/checkout@v4
with:
ref: 'cloudflare-pages'
- name: Cleanup
run: |
rm -rf .git
- name: Cloudflare Deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy . --project-name=toolbox-docs --branch=main
3 changes: 3 additions & 0 deletions .github/workflows/deploy_dev_docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ jobs:
HUGO_BASEURL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/dev
HUGO_RELATIVEURLS: false

- name: Build Pagefind Search Index
run: npx pagefind --site public

- name: Create Staging Directory
run: |
mkdir staging
Expand Down
90 changes: 90 additions & 0 deletions .github/workflows/deploy_dev_docs_to_cf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Copyright 2026 Google LLC
#
# Licensed 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: "CF: Deploy Dev Docs"

permissions:
contents: write

on:
push:
branches:
- main
paths:
- 'docs/**'
- '.github/workflows/docs*_cf.yaml'
- '.github/workflows/deploy*_cf.yaml'
- '.hugo/**'

# Allow triggering manually.
workflow_dispatch:

jobs:
deploy:
runs-on: ubuntu-24.04
defaults:
run:
working-directory: .hugo
# This shared concurrency group ensures only one docs deployment runs at a time.
concurrency:
group: cf-docs-update
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
submodules: recursive

- name: Setup Hugo
uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3
with:
hugo-version: "0.145.0"
extended: true

- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: "22"

- name: Cache dependencies
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- run: npm ci
- run: hugo --minify --config hugo.cloudflare.toml
env:
HUGO_BASEURL: https://mcp-toolbox.dev/dev/
HUGO_RELATIVEURLS: false

- name: Build Pagefind Search Index
run: npx pagefind --site public

- name: Create Staging Directory
run: |
mkdir staging
mv public staging/dev
mv staging/dev/releases.releases staging/releases.releases

- name: Push to Cloudflare Branch
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./.hugo/staging
publish_branch: cloudflare-pages
keep_files: true
commit_message: "deploy: ${{ github.event.head_commit.message }}"
Loading
Loading