diff --git a/Dockerfile b/Dockerfile index 2ec633711fc3..51a36ac3fea6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,7 +51,7 @@ ARG DOCS_URL="https://docs.docker.com" ENV HUGO_CACHEDIR="/tmp/hugo_cache" RUN --mount=type=cache,target=/tmp/hugo_cache \ hugo --gc --minify -e $HUGO_ENV -b $DOCS_URL -RUN ./hack/flatten-markdown.sh public +RUN ./hack/flatten-and-resolve.js public # lint lints markdown files FROM ghcr.io/igorshubovych/markdownlint-cli:v0.45.0 AS lint diff --git a/hack/flatten-and-resolve.js b/hack/flatten-and-resolve.js new file mode 100755 index 000000000000..04532d343eb5 --- /dev/null +++ b/hack/flatten-and-resolve.js @@ -0,0 +1,236 @@ +#!/usr/bin/env node + +/** + * Flattens markdown directory structure and resolves all links to absolute paths. + * + * This script: + * 1. Moves index.md files up one level (ai/model-runner/index.md -> ai/model-runner.md) + * 2. Fixes _index.md and index.md references in links + * 3. Strips /manuals/ prefix from paths (Hugo config removes this) + * 4. Resolves all relative links to absolute HTML paths for RAG ingestion + * + * Usage: node flatten-and-resolve.js [public-dir] + */ + +const fs = require('fs'); +const path = require('path'); + +const PUBLIC_DIR = path.resolve(process.argv[2] || 'public'); + +if (!fs.existsSync(PUBLIC_DIR)) { + console.error(`Error: Directory ${PUBLIC_DIR} does not exist`); + process.exit(1); +} + +/** + * Recursively find all files matching a predicate + */ +function findFiles(dir, predicate) { + const results = []; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + results.push(...findFiles(fullPath, predicate)); + } else if (entry.isFile() && predicate(entry.name)) { + results.push(fullPath); + } + } + + return results; +} + +/** + * Step 1: Flatten index.md files + * Move path/to/section/index.md -> path/to/section.md + * Before moving, rewrite sibling links (e.g., "get-started.md" -> "section/get-started.md") + */ +function flattenIndexFiles() { + const indexFiles = findFiles(PUBLIC_DIR, name => name === 'index.md'); + let count = 0; + + for (const file of indexFiles) { + // Skip root index.md + if (file === path.join(PUBLIC_DIR, 'index.md')) { + continue; + } + + const dir = path.dirname(file); + const dirname = path.basename(dir); + + // Read content and fix sibling links + let content = fs.readFileSync(file, 'utf8'); + + // Rewrite relative links that don't start with /, ../, or http + // These are sibling files that will become children after flattening + content = content.replace( + /\[([^\]]+)\]\(([a-zA-Z0-9][^):]*)\)/g, + (match, text, link) => { + // Skip if it's a URL or starts with special chars + if (link.startsWith('http://') || link.startsWith('https://') || + link.startsWith('#')) { + return match; + } + return `[${text}](${dirname}/${link})`; + } + ); + + // Also fix reference-style links + content = content.replace( + /^\[([^\]]+)\]:\s+([a-zA-Z0-9][^: ]*\.md)$/gm, + (match, ref, link) => `[${ref}]: ${dirname}/${link}` + ); + + fs.writeFileSync(file, content, 'utf8'); + + // Move file up one level + const parentDir = path.dirname(dir); + const newPath = path.join(parentDir, `${dirname}.md`); + fs.renameSync(file, newPath); + + count++; + } + + console.log(`Flattened ${count} index.md files`); + return count; +} + +/** + * Step 2: Fix _index.md and index.md references in all files + * Also strip /manuals/ prefix from paths + */ +function fixIndexReferences() { + const mdFiles = findFiles(PUBLIC_DIR, name => name.endsWith('.md')); + let count = 0; + + for (const file of mdFiles) { + const dir = path.dirname(file); + const dirname = path.basename(dir); + const parentDir = path.dirname(dir); + const parentDirname = path.basename(parentDir); + + let content = fs.readFileSync(file, 'utf8'); + const original = content; + + // Fix path/_index.md or path/index.md -> path.md + content = content.replace(/([a-zA-Z0-9_/-]+)\/_?index\.md/g, '$1.md'); + + // Fix bare _index.md or index.md -> ../dirname.md + content = content.replace(/_?index\.md/g, `../${dirname}.md`); + + // Fix ../_index.md that became ...md -> ../../parentdirname.md + if (parentDir !== PUBLIC_DIR) { + content = content.replace(/\.\.\.md/g, `../../${parentDirname}.md`); + } + + // Strip /manuals/ prefix (both /manuals/ and manuals/) + content = content.replace(/\/?manuals\//g, '/'); + + if (content !== original) { + fs.writeFileSync(file, content, 'utf8'); + count++; + } + } + + console.log(`Fixed _index.md references in ${count} files`); + return count; +} + +/** + * Step 3: Resolve all relative links to absolute HTML paths + */ +function resolveLinks() { + const mdFiles = findFiles(PUBLIC_DIR, name => name.endsWith('.md')); + let count = 0; + + for (const file of mdFiles) { + let content = fs.readFileSync(file, 'utf8'); + const original = content; + + // Process inline links: [text](path) + content = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, link) => { + const resolved = resolveLinkPath(link, file); + return `[${text}](${resolved})`; + }); + + // Process reference-style links: [ref]: path + content = content.replace(/^\[([^\]]+)\]:\s+(.+)$/gm, (match, ref, link) => { + const resolved = resolveLinkPath(link, file); + return `[${ref}]: ${resolved}`; + }); + + if (content !== original) { + fs.writeFileSync(file, content, 'utf8'); + count++; + } + } + + console.log(`Resolved links in ${count} files`); + return count; +} + +/** + * Resolve a link path to absolute HTML format + */ +function resolveLinkPath(linkPath, currentFile) { + // Skip external URLs and mailto + if (linkPath.startsWith('http://') || linkPath.startsWith('https://') || + linkPath.startsWith('mailto:')) { + return linkPath; + } + + // Skip same-page anchors + if (linkPath.startsWith('#')) { + return linkPath; + } + + // Split path and anchor + const hashIndex = linkPath.indexOf('#'); + const pathPart = hashIndex >= 0 ? linkPath.substring(0, hashIndex) : linkPath; + const anchorPart = hashIndex >= 0 ? linkPath.substring(hashIndex) : ''; + + if (!pathPart) { + // Just an anchor + return linkPath; + } + + // Handle absolute paths - just convert to HTML format + if (pathPart.startsWith('/')) { + return toHtmlPath(pathPart) + anchorPart; + } + + // Resolve relative path to absolute + const currentDir = path.dirname(currentFile); + const absolutePath = path.resolve(currentDir, pathPart); + const relativePath = path.relative(PUBLIC_DIR, absolutePath); + + // Convert to URL path (forward slashes) + const urlPath = '/' + relativePath.split(path.sep).join('/'); + + return toHtmlPath(urlPath) + anchorPart; +} + +/** + * Convert a path to HTML format (strip .md, add trailing /) + */ +function toHtmlPath(urlPath) { + if (urlPath.endsWith('.md')) { + return urlPath.slice(0, -3) + '/'; + } + return urlPath; +} + +// Main execution +console.log('Starting markdown flattening and link resolution...'); +console.log(''); + +const flattenCount = flattenIndexFiles(); +const fixCount = fixIndexReferences(); +const resolveCount = resolveLinks(); + +console.log(''); +console.log('Done!'); +console.log(`- Flattened: ${flattenCount} files`); +console.log(`- Fixed references: ${fixCount} files`); +console.log(`- Resolved links: ${resolveCount} files`); diff --git a/hack/flatten-markdown.sh b/hack/flatten-markdown.sh deleted file mode 100755 index d055dc9bdb34..000000000000 --- a/hack/flatten-markdown.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -set -e - -# Flatten markdown files from public/path/to/page/index.md to public/path/to/page.md -# This makes markdown output links work correctly - -PUBLIC_DIR="${1:-public}" - -[ -d "$PUBLIC_DIR" ] || { echo "Error: Directory $PUBLIC_DIR does not exist"; exit 1; } - -find "$PUBLIC_DIR" -type f -name 'index.md' | while read -r file; do - # Skip the root index.md - [ "$file" = "$PUBLIC_DIR/index.md" ] && continue - - # Get the directory containing index.md - dir="${file%/*}" - - # Move index.md to parent directory with directory name - mv "$file" "${dir%/*}/${dir##*/}.md" -done - -echo "Flattened markdown files in $PUBLIC_DIR" diff --git a/layouts/_default/api.markdown.md b/layouts/_default/api.markdown.md index ff3bef069369..012b44430c7f 100644 --- a/layouts/_default/api.markdown.md +++ b/layouts/_default/api.markdown.md @@ -1,38 +1,5 @@ ---- -title: {{ .Title }} -url: {{ .Permalink }} -{{- range .Ancestors }} - {{- if and (not .IsHome) .Permalink }} -parent: - title: {{ .Title }} - url: {{ .Permalink }} - {{- break }} - {{- end }} -{{- end }} -{{- if .Ancestors }} -breadcrumbs: -{{- range .Ancestors.Reverse }} - {{- if and (not .IsHome) .Permalink }} - - title: {{ .Title }} - url: {{ .Permalink }} - {{- end }} -{{- end }} - - title: {{ .Title }} - url: {{ .Permalink }} -{{- end }} -{{- with .NextInSection }} -next: - title: {{ .Title }} - url: {{ .Permalink }} -{{- end }} -{{- with .PrevInSection }} -prev: - title: {{ .Title }} - url: {{ .Permalink }} -{{- end }} -{{- $specURL := urls.Parse (printf "/%s%s.yaml" .File.Dir .File.ContentBaseName) }} -openapi_spec: {{ $specURL.String | absURL }} ---- +{{- $specURL := urls.Parse (printf "/%s%s.yaml" .File.Dir .File.ContentBaseName) -}} +# {{ .Title }} {{ .Content }} diff --git a/layouts/_default/cli.markdown.md b/layouts/_default/cli.markdown.md index 99cfd9ecc06e..7c03cb1f0b77 100644 --- a/layouts/_default/cli.markdown.md +++ b/layouts/_default/cli.markdown.md @@ -4,39 +4,7 @@ {{- else }} {{- $data = index site.Data .Params.datafile }} {{- end -}} ---- -title: {{ .Title }} -url: {{ .Permalink }} -{{- range .Ancestors }} - {{- if and (not .IsHome) .Permalink }} -parent: - title: {{ .Title }} - url: {{ .Permalink }} - {{- break }} - {{- end }} -{{- end }} -{{- if .Ancestors }} -breadcrumbs: -{{- range .Ancestors.Reverse }} - {{- if and (not .IsHome) .Permalink }} - - title: {{ .Title }} - url: {{ .Permalink }} - {{- end }} -{{- end }} - - title: {{ .Title }} - url: {{ .Permalink }} -{{- end }} -{{- with .NextInSection }} -next: - title: {{ .Title }} - url: {{ .Permalink }} -{{- end }} -{{- with .PrevInSection }} -prev: - title: {{ .Title }} - url: {{ .Permalink }} -{{- end }} ---- +# {{ .Title }} {{ with $data.short }}**Description:** {{ . }}{{ end }} diff --git a/layouts/_default/list.markdown.md b/layouts/_default/list.markdown.md index ab2747bc38c9..54f307137674 100644 --- a/layouts/_default/list.markdown.md +++ b/layouts/_default/list.markdown.md @@ -1,36 +1,3 @@ ---- -title: {{ .Title }} -url: {{ .Permalink }} -{{- range .Ancestors }} - {{- if and (not .IsHome) .Permalink }} -parent: - title: {{ .Title }} - url: {{ .Permalink }} - {{- break }} - {{- end }} -{{- end }} -{{- if .Ancestors }} -breadcrumbs: -{{- range .Ancestors.Reverse }} - {{- if and (not .IsHome) .Permalink }} - - title: {{ .Title }} - url: {{ .Permalink }} - {{- end }} -{{- end }} - - title: {{ .Title }} - url: {{ .Permalink }} -{{- end }} -{{- $children := where .Pages "Permalink" "ne" "" }} -{{- if $children }} -children: -{{- range $children }} - - title: {{ .Title }} - url: {{ .Permalink }} - {{- with .Description }} - description: {{ . }} - {{- end }} -{{- end }} -{{- end }} ---- +# {{ .Title }} {{ .RenderShortcodes }} diff --git a/layouts/_default/single.markdown.md b/layouts/_default/single.markdown.md index 40c1778180d4..54f307137674 100644 --- a/layouts/_default/single.markdown.md +++ b/layouts/_default/single.markdown.md @@ -1,35 +1,3 @@ ---- -title: {{ .Title }} -url: {{ .Permalink }} -{{- range .Ancestors }} - {{- if and (not .IsHome) .Permalink }} -parent: - title: {{ .Title }} - url: {{ .Permalink }} - {{- break }} - {{- end }} -{{- end }} -{{- if .Ancestors }} -breadcrumbs: -{{- range .Ancestors.Reverse }} - {{- if and (not .IsHome) .Permalink }} - - title: {{ .Title }} - url: {{ .Permalink }} - {{- end }} -{{- end }} - - title: {{ .Title }} - url: {{ .Permalink }} -{{- end }} -{{- with .NextInSection }} -next: - title: {{ .Title }} - url: {{ .Permalink }} -{{- end }} -{{- with .PrevInSection }} -prev: - title: {{ .Title }} - url: {{ .Permalink }} -{{- end }} ---- +# {{ .Title }} {{ .RenderShortcodes }} diff --git a/layouts/samples/single.markdown.md b/layouts/samples/single.markdown.md new file mode 100644 index 000000000000..800197369fa0 --- /dev/null +++ b/layouts/samples/single.markdown.md @@ -0,0 +1,9 @@ +# {{ .Title }} + +| Name | Description | +|------|-------------| +{{- range site.Data.samples.samples }} +{{- if in .services $.Params.service }} +| [{{ .title }}]({{ .url }}) | {{ chomp .description }} | +{{- end }} +{{- end }} diff --git a/netlify.toml b/netlify.toml index 6cee9928c01b..cb07b6343c93 100644 --- a/netlify.toml +++ b/netlify.toml @@ -10,4 +10,4 @@ HUGO_ENVIRONMENT = "preview" SECRETS_SCAN_OMIT_PATHS = "public/contribute/file-conventions/index.html" [context.deploy-preview] -command = "hugo --gc --minify -b $DEPLOY_PRIME_URL && ./hack/flatten-markdown.sh && npx pagefind@v1.3.0" +command = "hugo --gc --minify -b $DEPLOY_PRIME_URL && ./hack/flatten-and-resolve.js && npx pagefind@v1.3.0"