Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
80 changes: 46 additions & 34 deletions .ci/lint-docs-source-page.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,27 @@ python3 - << 'EOF'
MCP TOOLBOX: SOURCE PAGE LINTER
===============================
This script enforces a standardized structure for integration Source pages
(_index.md files). It ensures users can predictably find
information across all database integrations.
(source.md files). It ensures users can predictably find connection details
and configurations across all database integrations.

Note: The structural _index.md folder wrappers are intentionally ignored
by this script as they should only contain YAML frontmatter.

MAINTENANCE GUIDE:
------------------
1. TO ADD A NEW HEADING:
Add the exact heading text to the 'ALLOWED_ORDER' list in the desired
sequence.

2. TO MAKE A HEADING MANDATORY:
Add the heading text to the 'REQUIRED' set.
2. TO MAKE A HEADING MANDATORY/OPTIONAL:
Add or remove the heading text in the 'REQUIRED' set.

3. TO IGNORE NEW CONTENT TYPES:
Update the regex in the 'clean_body' variable (Step 3) to strip out
Update the regex in the 'clean_body' variable to strip out
Markdown before linting.

4. SCOPE:
This script targets ONLY the top-level _index.md of an integration.
This script only targets docs/en/integrations/**/source.md.
"""

import os
Expand All @@ -43,6 +46,8 @@ ALLOWED_ORDER = [
"Additional Resources"
]
REQUIRED = {"About", "Example", "Reference"}

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

Expand All @@ -52,10 +57,12 @@ if not integration_dir.exists():
sys.exit(0)

has_errors = False
source_pages_found = 0

for filepath in integration_dir.rglob("_index.md"):
if filepath.parent == integration_dir:
continue
# ONLY scan files specifically named "source.md"
for filepath in integration_dir.rglob("source.md"):
source_pages_found += 1
file_errors = False

if filepath.parent.parent != integration_dir:
continue
Expand All @@ -67,30 +74,30 @@ for filepath in integration_dir.rglob("_index.md"):
if match:
frontmatter, body = match.group(1), match.group(2)
else:
frontmatter, body = "", content

if not body.strip():
print(f"[{filepath}] Error: Missing or invalid YAML frontmatter.")
has_errors = True
continue

file_errors = False
# 1. Check for linkTitle: "Source" in frontmatter
link_title_match = re.search(r"^linkTitle:\s*[\"']?(.*?)[\"']?\s*$", frontmatter, re.MULTILINE)
if not link_title_match or link_title_match.group(1).strip() != "Source":
print(f"[{filepath}] Error: Frontmatter must contain exactly linkTitle: \"Source\".")
file_errors = True

# 1. Frontmatter Title Check
title_match = re.search(r"^title:\s*[\"']?(.*?)[\"']?\s*$", frontmatter if frontmatter else content, re.MULTILINE)
if not title_match or not title_match.group(1).strip().endswith("Source"):
print(f"[{filepath}] Error: Title must end with 'Source'.")
# 2. Check for weight: 1 in frontmatter
weight_match = re.search(r"^weight:\s*[\"']?(\d+)[\"']?\s*$", frontmatter, re.MULTILINE)
if not weight_match or weight_match.group(1).strip() != "1":
print(f"[{filepath}] Error: Frontmatter must contain exactly weight: 1.")
file_errors = True

# 2. Shortcode Placement Check
tools_section = re.search(r"^##\s+Available Tools\s*(.*?)(?=^##\s|\Z)", body, re.MULTILINE | re.DOTALL)
if tools_section:
if not re.search(SHORTCODE_PATTERN, tools_section.group(1)):
print(f"[{filepath}] Error: {{< list-tools >}} must be under '## Available Tools'.")
# 3. Check Shortcode Placement & Available Tools Section (Only if 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
elif re.search(SHORTCODE_PATTERN, body):
print(f"[{filepath}] Error: {{< list-tools >}} found, but '## Available Tools' heading is missing.")
file_errors = True

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

if re.search(r"^#\s+\w+", clean_body, re.MULTILINE):
Expand All @@ -99,9 +106,10 @@ for filepath in integration_dir.rglob("_index.md"):

h2s = [h.strip() for h in re.findall(r"^##\s+(.*)", clean_body, re.MULTILINE)]

# 4. Required & Unauthorized Check
if missing := (REQUIRED - set(h2s)):
print(f"[{filepath}] Error: Missing required H2s: {missing}")
# Missing Required Headings
missing = REQUIRED - set(h2s)
if missing:
print(f"[{filepath}] Error: Missing required H2 headings: {missing}")
file_errors = True

if unauthorized := (set(h2s) - set(ALLOWED_ORDER)):
Expand All @@ -115,9 +123,13 @@ for filepath in integration_dir.rglob("_index.md"):

if file_errors: has_errors = True

if has_errors:
print("Linting failed. Fix structure errors above.")
# Handle final output based on what was found
if source_pages_found == 0:
print("Info: No 'source.md' files found in integrations. Passing gracefully.")
sys.exit(0)
elif has_errors:
print(f"\nLinting failed. Please fix the structure errors in the {source_pages_found} 'source.md' file(s) above.")
sys.exit(1)
print("Success: Source pages validated.")
sys.exit(0)
EOF
else:
print(f"Success: {source_pages_found} 'source.md' file(s) passed structure validation.")
EOF
1 change: 0 additions & 1 deletion .ci/lint-docs-tool-page.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ tools_pages_found = 0
# Specifically target the tools directories
for filepath in integration_dir.rglob("tools/*.md"):
tools_pages_found += 1

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

Expand Down
2 changes: 1 addition & 1 deletion .github/header-checker-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ sourceFileExtensions:
- 'yml'
ignoreFiles:
- 'docs/en/documentation/**/*.go'
- 'docs/en/samples-hub/**/*'
- 'docs/en/samples/**/*'
- '**/*oracle*'
13 changes: 13 additions & 0 deletions .hugo/assets/scss/_styles_project.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
/* ==========================================================================
PROJECT SCSS HUB
Custom theme overrides and UI components.
========================================================================== */

@import 'components/typography';
@import 'components/layout';
@import 'components/header';
@import 'components/sidebar';
@import 'components/callouts';
@import 'components/secondary_nav';
@import 'components/search';

@import 'td/code-dark';

// Make tabs scrollable horizontally instead of wrapping
Expand Down
26 changes: 26 additions & 0 deletions .hugo/assets/scss/components/_callouts.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* ==========================================================================
CALLOUTS & NOTICES
Styling for alerts, tips, and admonition blocks.
========================================================================== */

.td-content .alert code,
.td-content .notice code,
.td-content .admonition code {
background-color: #ffffff !important;
color: rgba(32, 33, 36, 0.95) !important;
padding: 0.2rem 0.4rem !important;
border-radius: 4px !important;
border: 1px solid rgba(0, 0, 0, 0.05) !important;
font-weight: 500 !important;
}

html[data-bs-theme="dark"] .td-content .alert code,
body.dark .td-content .alert code,
html[data-bs-theme="dark"] .td-content .notice code,
body.dark .td-content .notice code,
html[data-bs-theme="dark"] .td-content .admonition code,
body.dark .td-content .admonition code {
background-color: rgba(255, 255, 255, 0.15) !important;
color: #ffffff !important;
border-color: rgba(255, 255, 255, 0.1) !important;
}
Loading
Loading