Skip to content

Added logic for alias recognition to VitePress#925

Open
n-boshnakov wants to merge 4 commits intogardener:masterfrom
n-boshnakov:add-alias-recognition-vitepress
Open

Added logic for alias recognition to VitePress#925
n-boshnakov wants to merge 4 commits intogardener:masterfrom
n-boshnakov:add-alias-recognition-vitepress

Conversation

@n-boshnakov
Copy link
Copy Markdown
Contributor

@n-boshnakov n-boshnakov commented Apr 3, 2026

How to categorize this PR?

/kind documentation

What this PR does / why we need it:
This PR adds alias redirect infrastructure to the website.

Which issue(s) this PR fixes:
Fixes #926

Special notes for your reviewer:

Summary by CodeRabbit

  • New Features

    • Automatic alias redirects: pages can declare alias URLs in frontmatter; build produces static redirect pages and the dev server issues runtime redirects. Aliases are normalized (trims/normalizes paths), duplicate or conflicting aliases are ignored with warnings.
  • Refactor

    • Centralized site base path and content source settings so build and dev behavior are consistent.

@n-boshnakov n-boshnakov requested a review from a team as a code owner April 3, 2026 12:39
@gardener-prow
Copy link
Copy Markdown

gardener-prow bot commented Apr 3, 2026

@n-boshnakov: The label(s) kind/documentation cannot be applied, because the repository doesn't have them.

Details

In response to this:

How to categorize this PR?

/kind documentation

What this PR does / why we need it:
This PR adds alias redirect infrastructure to the website.

Which issue(s) this PR fixes:
Fixes #

Special notes for your reviewer:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 3, 2026

Deploy Preview for gardener-docs ready!

Name Link
🔨 Latest commit 2923077
🔍 Latest deploy log https://app.netlify.com/projects/gardener-docs/deploys/69e0b712fd722c0008c3695f
😎 Deploy Preview https://deploy-preview-925--gardener-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 63 (🔴 down 5 from production)
Accessibility: 97 (no change from production)
Best Practices: 92 (no change from production)
SEO: 98 (no change from production)
PWA: 90 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@gardener-prow
Copy link
Copy Markdown

gardener-prow bot commented Apr 3, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign jordanjordanov for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@gardener-prow gardener-prow bot added do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Apr 3, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

Adds an alias-redirect feature: parses Markdown frontmatter aliases, builds a normalized alias→target map, writes static HTML redirects at build end, and provides dev-server middleware for 302 redirects. Refactors VitePress config to centralize base/srcDir, pass them into Vite config, and call redirect generation at buildEnd.

Changes

Cohort / File(s) Summary
VitePress config
/.vitepress/config.mts
Centralized siteBase and siteSrcDir; set base and srcDir from those constants; changed vite: getViteConfig()vite: getViteConfig(siteBase, siteSrcDir); added buildEnd(siteConfig: SiteConfig) to call generateAliasRedirects; added imports for alias-redirects helpers.
Alias redirect plugin
/.vitepress/plugins/alias-redirects.ts
New module exporting AliasEntry, generateAliasRedirects(siteConfig) and createAliasRedirectDevPlugin(srcDir, basePath). Scans srcDir for .md, parses frontmatter aliases, normalizes paths, deduplicates, skips collisions with canonical routes, writes safe static HTML redirect files to siteConfig.outDir in build mode, and registers dev middleware that serves 302 redirects and invalidates cache on file changes.

Sequence Diagram(s)

sequenceDiagram
    participant MF as Markdown Files
    participant Build as VitePress Build
    participant AP as Alias Plugin
    participant Dev as Dev Server
    participant FS as Output Filesystem

    rect rgba(100,150,200,0.5)
    Note over MF,AP: Parse and map aliases from source
    Build->>MF: Scan `srcDir` for .md
    Build->>AP: Request alias extraction
    AP->>AP: Read frontmatter, normalize & dedupe aliases
    end

    rect rgba(150,200,100,0.5)
    Note over Dev,AP: Dev-server redirect handling
    Dev->>AP: Incoming GET/HEAD (basePath stripped)
    AP->>AP: Lookup alias map
    AP-->>Dev: Respond 302 with Location header (if match)
    end

    rect rgba(200,150,100,0.5)
    Note over Build,FS: Static redirect generation
    Build->>AP: buildEnd(siteConfig) triggers generation
    AP->>FS: Write HTML redirect files under outDir
    FS-->>Build: Redirect files included in site output
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibble frontmatter, chase old tracks at night,

I map each alias, set the path aright,
Dev sends a nudge, build plants a sign,
Visitors wander, then find the right line,
A tiny hop, and every link’s polite.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main change: adding alias recognition logic to VitePress, which matches the primary objective of implementing alias redirect infrastructure.
Description check ✅ Passed The description follows the template structure with categorization, explanation of purpose, and linked issue reference, though implementation details are minimal.
Linked Issues check ✅ Passed The PR implements alias redirect infrastructure that directly addresses issue #926's requirement for users to be automatically redirected when visiting old URLs with aliases.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing alias redirect functionality: VitePress config integration, plugin implementation, and supporting infrastructure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.vitepress/config.mts (1)

222-299: Consider adding type annotations for Vite server APIs to improve IDE support.

The dev redirect plugin logic is sound with good caching, proper cache invalidation on file changes, and correct middleware ordering (calls next() when no redirect is found, allowing other handlers to process the request). Error handling gracefully falls through to the next middleware.

The any types for server, req, and res reduce type safety. Vite and Node.js provide standard type exports that should be used:

🛠️ Optional: Add type imports for better IDE support
+import type { ViteDevServer } from 'vite'
+import type { IncomingMessage, ServerResponse } from 'node:http'

 function createAliasRedirectDevPlugin(srcDir: string, basePath: string) {
   // ...
   return {
     name: 'vitepress-aliases-dev-redirects',
     apply: 'serve',
-    configureServer(server: any) {
+    configureServer(server: ViteDevServer) {
       // ...
-      server.middlewares.use((req: any, res: any, next: () => void) => {
+      server.middlewares.use((req: IncomingMessage, res: ServerResponse, next: () => void) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.vitepress/config.mts around lines 222 - 299, The plugin uses untyped any
for Vite server and HTTP req/res; add proper type annotations to improve IDE
support: import ViteDevServer from 'vite' and use it for the
configureServer(server) parameter in createAliasRedirectDevPlugin, and annotate
the middleware handler parameters (req, res, next) with appropriate Node types
(http.IncomingMessage / http.ServerResponse) or Connect types so IDEs can
resolve properties like url and method; update signatures for
configureServer(server: ViteDevServer) and the middleware callback inside
configureServer (and refreshOnMarkdownChange/loadAliases references remain the
same) and adjust any watcher/middleware usages to match the typed APIs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.vitepress/config.mts:
- Around line 28-45: createRedirectHtml currently embeds targetPath via
JSON.stringify directly into a <script>, which allows breaking out if targetPath
contains "</script>" (XSS). Fix by creating a script-safe string (e.g., let
safeScript = JSON.stringify(targetPath).replace(/<\/script>/gi,
'<\\/script>').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029')) and
use safeScript inside the window.location.replace(...) call while keeping the
HTML-escaped escapedTarget for the meta/link/body; update createRedirectHtml to
use this safeScript instead of JSON.stringify(targetPath).

---

Nitpick comments:
In @.vitepress/config.mts:
- Around line 222-299: The plugin uses untyped any for Vite server and HTTP
req/res; add proper type annotations to improve IDE support: import
ViteDevServer from 'vite' and use it for the configureServer(server) parameter
in createAliasRedirectDevPlugin, and annotate the middleware handler parameters
(req, res, next) with appropriate Node types (http.IncomingMessage /
http.ServerResponse) or Connect types so IDEs can resolve properties like url
and method; update signatures for configureServer(server: ViteDevServer) and the
middleware callback inside configureServer (and
refreshOnMarkdownChange/loadAliases references remain the same) and adjust any
watcher/middleware usages to match the typed APIs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a327d31b-3090-459b-9604-9d6490fc7a17

📥 Commits

Reviewing files that changed from the base of the PR and between 70d3f6e and cab8b8a.

📒 Files selected for processing (1)
  • .vitepress/config.mts

Comment thread .vitepress/config.mts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
.vitepress/plugins/alias-redirects.ts (1)

11-21: ⚠️ Potential issue | 🔴 Critical

Make the inline redirect script safe for </script> sequences.

JSON.stringify() is not enough inside a <script> tag. If a page path or configured base ever contains </script>, the generated redirect page becomes executable markup.

🔒 Proposed fix
 const createRedirectHtml = (targetPath: string) => {
   const escapedTarget = escapeHtml(targetPath)
+  const safeScriptTarget = JSON.stringify(targetPath)
+    .replace(/</g, '\\u003c')
+    .replace(/\u2028/g, '\\u2028')
+    .replace(/\u2029/g, '\\u2029')

   return `<!doctype html>
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <title>Redirecting...</title>
     <meta http-equiv="refresh" content="0; url=${escapedTarget}">
     <link rel="canonical" href="${escapedTarget}">
-    <script>window.location.replace(${JSON.stringify(targetPath)})</script>
+    <script>window.location.replace(${safeScriptTarget})</script>
   </head>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.vitepress/plugins/alias-redirects.ts around lines 11 - 21, The inline
redirect script in createRedirectHtml is unsafe because
JSON.stringify(targetPath) alone can still allow a closing "</script>" sequence
to break out of the script tag; update createRedirectHtml to sanitize the string
used inside the script by escaping any "</script>" occurrences (e.g. replace
"</script>" with "<\/script>") after JSON.stringify(targetPath) (and likewise
ensure escapedTarget used in attributes is properly HTML-escaped), so the
generated script and markup cannot be terminated by a malicious path value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.vitepress/plugins/alias-redirects.ts:
- Around line 98-107: Reject aliases containing dot-segments before joining them
into outDir: in toRedirectOutputPath, after computing normalizedAliasPath check
for any ".." path segment (e.g., normalizedAliasPath === '/..' or
normalizedAliasPath.includes('/..') or by splitting on '/' and detecting '..')
and return null (or otherwise refuse to create a redirect) if found to prevent
path traversal; apply the same validation to the other path-joining code in this
file that builds redirect output paths (the second occurrence around the other
redirect helper) so no alias can escape siteConfig.outDir.

---

Duplicate comments:
In @.vitepress/plugins/alias-redirects.ts:
- Around line 11-21: The inline redirect script in createRedirectHtml is unsafe
because JSON.stringify(targetPath) alone can still allow a closing "</script>"
sequence to break out of the script tag; update createRedirectHtml to sanitize
the string used inside the script by escaping any "</script>" occurrences (e.g.
replace "</script>" with "<\/script>") after JSON.stringify(targetPath) (and
likewise ensure escapedTarget used in attributes is properly HTML-escaped), so
the generated script and markup cannot be terminated by a malicious path value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 62231597-0a4f-477b-88b4-66e7b226caf4

📥 Commits

Reviewing files that changed from the base of the PR and between cab8b8a and c654461.

📒 Files selected for processing (2)
  • .vitepress/config.mts
  • .vitepress/plugins/alias-redirects.ts

Comment thread .vitepress/plugins/alias-redirects.ts Outdated
Comment thread .vitepress/plugins/alias-redirects.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
.vitepress/plugins/alias-redirects.ts (1)

57-60: ⚠️ Potential issue | 🟠 Major

Reject . segments too.

path.join() normalizes the joined path, including . segments, so aliases like /./ or /docs/./faq/ still resolve inside outDir and can overwrite the root or a real page. The current helper only blocks ... (nodejs.org)

🛡️ Proposed fix
 function hasDotSegment(urlPath: string): boolean {
-  if (urlPath === '/..' || urlPath.includes('/../')) return true
-  return urlPath.split('/').some((segment) => segment === '..')
+  return urlPath.split('/').some((segment) => segment === '.' || segment === '..')
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.vitepress/plugins/alias-redirects.ts around lines 57 - 60, The helper
hasDotSegment currently only rejects '..' segments; update it to also reject
single-dot segments so paths containing '/./', '/.' or '/docs/./faq' are
considered invalid. Locate the hasDotSegment function and change its logic to
check segments for segment === '.' || segment === '..' (e.g., split on '/' and
test for either '.' or '..'), ensuring edge cases like a trailing '/.' or
occurrences anywhere in the path are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.vitepress/plugins/alias-redirects.ts:
- Around line 57-60: The helper hasDotSegment currently only rejects '..'
segments; update it to also reject single-dot segments so paths containing
'/./', '/.' or '/docs/./faq' are considered invalid. Locate the hasDotSegment
function and change its logic to check segments for segment === '.' || segment
=== '..' (e.g., split on '/' and test for either '.' or '..'), ensuring edge
cases like a trailing '/.' or occurrences anywhere in the path are caught.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a662704-9272-4c50-995b-1820e31a34f8

📥 Commits

Reviewing files that changed from the base of the PR and between c654461 and f68d715.

📒 Files selected for processing (1)
  • .vitepress/plugins/alias-redirects.ts

@klocke-io
Copy link
Copy Markdown
Member

@n-boshnakov maybe we could print a list of the aliases created :)
would also make it easier for testing.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.vitepress/plugins/alias-redirects.ts:
- Around line 265-271: The loadAliases function stores a promise in
cachedAliases that can stay rejected and block future loads; update loadAliases
so that when you call collectAliasEntries(srcDir, basePath) you attach a .catch
handler that clears cachedAliases (set to undefined) before rethrowing the
error, ensuring a subsequent call retries; reference the loadAliases function,
the cachedAliases variable and collectAliasEntries call when implementing this
change.
- Around line 135-153: collectMarkdownFiles relies on the nondeterministic order
of fs.readdir which makes alias conflict resolution flaky; to fix, make the
traversal deterministic by sorting the directory entries before processing
(e.g., sort the entries array by entry.name with a consistent comparator) so
that collectMarkdownFiles always returns the same ordering and results (or
alternatively sort the final results array before returning); update the code
around the fs.readdir call and the entries handling to use the stable sort so
duplicate alias winners are deterministic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82c59f92-b9ea-4b77-8703-b73d3d941934

📥 Commits

Reviewing files that changed from the base of the PR and between f68d715 and 2923077.

📒 Files selected for processing (2)
  • .vitepress/config.mts
  • .vitepress/plugins/alias-redirects.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .vitepress/config.mts

Comment on lines +135 to +153
async function collectMarkdownFiles(rootDir: string): Promise<string[]> {
const results: string[] = []
const entries = await fs.readdir(rootDir, { withFileTypes: true })

for (const entry of entries) {
const absolutePath = path.join(rootDir, entry.name)

if (entry.isDirectory()) {
results.push(...await collectMarkdownFiles(absolutePath))
continue
}

if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) {
results.push(absolutePath)
}
}

return results
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Sort the markdown scan before resolving duplicate aliases.

Line 137 relies on fs.readdir() order, but that order is not guaranteed. Because Lines 194-197 keep the first conflicting mapping, the winning alias target can vary across platforms and builds.

Proposed fix
 async function collectMarkdownFiles(rootDir: string): Promise<string[]> {
   const results: string[] = []
-  const entries = await fs.readdir(rootDir, { withFileTypes: true })
+  const entries = (await fs.readdir(rootDir, { withFileTypes: true }))
+    .sort((a, b) => a.name.localeCompare(b.name))
 
   for (const entry of entries) {
     const absolutePath = path.join(rootDir, entry.name)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.vitepress/plugins/alias-redirects.ts around lines 135 - 153,
collectMarkdownFiles relies on the nondeterministic order of fs.readdir which
makes alias conflict resolution flaky; to fix, make the traversal deterministic
by sorting the directory entries before processing (e.g., sort the entries array
by entry.name with a consistent comparator) so that collectMarkdownFiles always
returns the same ordering and results (or alternatively sort the final results
array before returning); update the code around the fs.readdir call and the
entries handling to use the stable sort so duplicate alias winners are
deterministic.

Comment on lines +265 to +271
const loadAliases = () => {
if (!cachedAliases) {
cachedAliases = collectAliasEntries(srcDir, basePath)
}

return cachedAliases
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clear failed alias loads from the dev cache.

If collectAliasEntries() rejects once, cachedAliases stays as a rejected promise and every later request reuses the same failure until a watcher event invalidates it. Resetting the cache on rejection lets the next request retry.

Proposed fix
   const loadAliases = () => {
     if (!cachedAliases) {
-      cachedAliases = collectAliasEntries(srcDir, basePath)
+      cachedAliases = collectAliasEntries(srcDir, basePath).catch((error) => {
+        cachedAliases = null
+        throw error
+      })
     }
 
     return cachedAliases
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.vitepress/plugins/alias-redirects.ts around lines 265 - 271, The
loadAliases function stores a promise in cachedAliases that can stay rejected
and block future loads; update loadAliases so that when you call
collectAliasEntries(srcDir, basePath) you attach a .catch handler that clears
cachedAliases (set to undefined) before rethrowing the error, ensuring a
subsequent call retries; reference the loadAliases function, the cachedAliases
variable and collectAliasEntries call when implementing this change.

@gardener-prow gardener-prow bot added cla: no Indicates the PR's author has not signed the cla-assistant.io CLA. cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. and removed cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. cla: no Indicates the PR's author has not signed the cla-assistant.io CLA. labels Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Aliases do not function correctly

2 participants