From fe2117e8c47e3c34cfbddb364d2d18dd9a999d9c Mon Sep 17 00:00:00 2001 From: Suhaha Date: Wed, 29 Jul 2026 15:11:19 +0800 Subject: [PATCH 1/4] feat(link-resolver): update release links to point to stable URLs for tidb-in-kubernetes --- gatsby/URL_MAPPING_ARCHITECTURE.md | 94 ++++++++++++++++--- .../__tests__/link-resolver.test.ts | 22 +++-- gatsby/link-resolver/config.ts | 4 +- .../__tests__/url-resolver.test.ts | 34 +++++++ gatsby/url-resolver/config.ts | 10 +- 5 files changed, 143 insertions(+), 21 deletions(-) diff --git a/gatsby/URL_MAPPING_ARCHITECTURE.md b/gatsby/URL_MAPPING_ARCHITECTURE.md index 113fc8a6..e3093604 100644 --- a/gatsby/URL_MAPPING_ARCHITECTURE.md +++ b/gatsby/URL_MAPPING_ARCHITECTURE.md @@ -3,6 +3,7 @@ ## Overview This document describes how the project handles URL mapping across three key areas: + 1. **Page URL Mapping**: Converting source file paths to published page URLs during build 2. **TOC Mapping**: Resolving links in TOC (Table of Contents) files 3. **Article Link Mapping**: Transforming internal links within markdown articles @@ -16,12 +17,14 @@ The system uses two core resolvers (`url-resolver` and `link-resolver`) that wor **Location**: `gatsby/create-pages/create-docs.ts` **Process**: + 1. Gatsby queries all MDX files from the GraphQL data layer 2. For each file, `calculateFileUrl()` from `url-resolver` converts the source path to a published URL 3. `getTOCNamespace()` from `toc-namespace` determines the page's TOC namespace for navigation/context 4. The resolved URL is used to create the Gatsby page with `createPage()` **Example**: + ```typescript // Source file: docs/markdown-pages/en/tidb/master/alert-rules.md // Slug: "en/tidb/master/alert-rules" @@ -31,6 +34,7 @@ const path = calculateFileUrl(node.slug, true); ``` **Key Points**: + - Uses `url-resolver` to transform source paths to URLs - Default language (`en`) is omitted from URLs (`omitDefaultLanguage: true`) - Only files referenced in TOC files are built (filtered by `filterNodesByToc`) @@ -40,6 +44,7 @@ const path = calculateFileUrl(node.slug, true); **Location**: `gatsby/toc.ts` and `gatsby/toc-filter.ts` **Process**: + 1. Gatsby queries all TOC files (files matching `/TOC.*md$/`) 2. For each TOC file, `mdxAstToToc()` parses the markdown AST 3. Links within TOC are resolved using `resolveMarkdownLink()` from `link-resolver` @@ -48,16 +53,21 @@ const path = calculateFileUrl(node.slug, true); - Generate navigation menus for pages **Example**: + ```typescript // TOC file: docs/markdown-pages/en/tidb/stable/TOC.md // Contains link: [Getting Started](/develop/getting-started) // TOC path: "/en/tidb/stable" (resolved from TOC file slug) -const resolvedLink = resolveMarkdownLink("/develop/getting-started", "/en/tidb/stable"); +const resolvedLink = resolveMarkdownLink( + "/develop/getting-started", + "/en/tidb/stable" +); // Result: "/developer/getting-started" // Used in navigation menu ``` **Key Points**: + - Uses `link-resolver` to resolve links in TOC files - TOC links are resolved relative to the TOC file's own URL - Resolved links are used to build a whitelist of files to include in the build @@ -67,12 +77,14 @@ const resolvedLink = resolveMarkdownLink("/develop/getting-started", "/en/tidb/s **Location**: `gatsby/plugin/content/index.ts` **Process**: + 1. During markdown processing, Gatsby's MDX plugin processes each article 2. For each link in the markdown AST, `resolveMarkdownLink()` resolves the link path 3. The resolved link is converted to a Gatsby `` component 4. External links (`http://`, `https://`) are kept as-is with `target="_blank"` **Example**: + ```typescript // Article: docs/markdown-pages/en/tidb/stable/overview.md // Contains link: [Upgrade Guide](/upgrade/upgrade-tidb-using-tiup) @@ -86,6 +98,7 @@ const resolvedPath = resolveMarkdownLink( ``` **Key Points**: + - Uses `link-resolver` to resolve links based on current page context - Links are resolved relative to the current article's URL - Hash fragments (`#section`) are preserved automatically @@ -134,21 +147,25 @@ Final HTML/JSX **Scenario**: Building a TiDB article with links 1. **Source File**: `docs/markdown-pages/en/tidb/master/alert-rules.md` + - Contains link: `[Vector Search](/develop/vector-search)` 2. **Page URL Resolution** (`create-docs.ts`): + ```typescript const pageUrl = calculateFileUrl("en/tidb/master/alert-rules", true); // Result: "/tidb/dev/alert-rules" ``` 3. **TOC Processing** (`toc-filter.ts`): + - TOC file: `en/tidb/stable/TOC.md` - Contains link to `alert-rules` - Link resolved: `/tidb/dev/alert-rules` - File added to whitelist: `en/tidb/stable -> Set(["alert-rules"])` 4. **Page Creation** (`create-docs.ts`): + - File matches TOC whitelist → page is created - Page URL: `/tidb/dev/alert-rules` - Namespace: `TOCNamespace.TiDB` @@ -157,7 +174,7 @@ Final HTML/JSX - Current page URL: `/en/tidb/dev/alert-rules` - Link `/develop/vector-search` resolved: ```typescript - resolveMarkdownLink("/develop/vector-search", "/en/tidb/dev/alert-rules") + resolveMarkdownLink("/develop/vector-search", "/en/tidb/dev/alert-rules"); // Result: "/developer/vector-search" ``` - Rendered as: `Vector Search` @@ -183,6 +200,7 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `filename = "_index"` **Example**: + - Source: `en/tidbcloud/master/tidb-cloud/dedicated/_index.md` - Target: `/tidbcloud` (or `/en/tidbcloud` if default language not omitted) @@ -201,6 +219,7 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `filename = "_index"` **Example**: + - Source: `en/tidbcloud/master/tidb-cloud/releases/_index.md` - Target: `/releases/tidb-cloud` @@ -219,6 +238,7 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `filename = "_index"` **Example**: + - Source: `en/tidb/release-8.5/releases/_index.md` - Target: `/releases/tidb-self-managed` @@ -237,6 +257,7 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `filename = "_index"` **Example**: + - Source: `en/tidb-in-kubernetes/main/releases/_index.md` - Target: `/releases/tidb-operator` @@ -251,14 +272,17 @@ Rules are evaluated in order; the first matching rule wins. **Source Pattern**: `/{lang}/tidbcloud/{branch}/tidb-cloud/{...prefixes}/{filename}` **Target Pattern**: + - For `_index`: `/{lang}/tidbcloud/{prefixes}` (keeps prefixes) - For other files: `/{lang}/tidbcloud/{filename}` (removes prefixes) **Filename Transform**: + - `ignoreIf: ["_index"]` - Filename removed from URL for non-index files - `conditionalTarget.keepIf: ["_index"]` - Uses alternative pattern for `_index` files **Example**: + - Source: `en/tidbcloud/master/tidb-cloud/dedicated/starter/_index.md` - Target: `/tidbcloud/dedicated/starter` - Source: `en/tidbcloud/master/tidb-cloud/dedicated/starter/getting-started.md` @@ -275,16 +299,19 @@ Rules are evaluated in order; the first matching rule wins. **Source Pattern**: `/{lang}/tidb/{stable}/{folder}/{...folders}/{filename}` **Target Pattern**: + - For `_index`: `/{lang}/developer/{folders}` (keeps folder structure) - For other files: `/{lang}/developer/{filename}` (flattens folder structure) **Conditions**: `folder = ["develop"]` **Filename Transform**: + - `ignoreIf: ["_index"]` - `conditionalTarget.keepIf: ["_index"]` **Example**: + - Source: `en/tidb/release-8.5/develop/subfolder/_index.md` - Target: `/developer/subfolder` - Source: `en/tidb/release-8.5/develop/subfolder/vector-search.md` @@ -301,16 +328,19 @@ Rules are evaluated in order; the first matching rule wins. **Source Pattern**: `/{lang}/tidb/{stable}/{folder}/{...folders}/{filename}` **Target Pattern**: + - For `_index`: `/{lang}/{folder}/{folders}` (keeps folder structure) - For other files: `/{lang}/{folder}/{filename}` (flattens folder structure) **Conditions**: `folder = ["best-practices", "api", "ai"]` **Filename Transform**: + - `ignoreIf: ["_index"]` - `conditionalTarget.keepIf: ["_index"]` **Example**: + - Source: `en/tidb/release-8.5/ai/subfolder/_index.md` - Target: `/ai/subfolder` - Source: `en/tidb/release-8.5/api/overview.md` @@ -327,14 +357,17 @@ Rules are evaluated in order; the first matching rule wins. **Source Pattern**: `/{lang}/tidb-cloud-lake/{branch}/{...folders}/{filename}` **Target Pattern**: + - For `_index`: `/{lang}/tidbcloudlake/{folders}` (keeps folder structure) - For other files: `/{lang}/tidbcloudlake/{filename}` (flattens folder structure) **Filename Transform**: + - `ignoreIf: ["_index"]` - `conditionalTarget.keepIf: ["_index"]` **Example**: + - Source: `en/tidb-cloud-lake/master/_index.md` - Target: `/tidbcloudlake` - Source: `en/tidb-cloud-lake/master/tidb-cloud-lake/_index.md` @@ -357,6 +390,7 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `filename = "_index"` **Example**: + - Source: `en/tidb/master/develop/_index.md` - Target: `/tidb/dev/develop` - Source: `en/tidb/master/releases/_index.md` @@ -368,7 +402,7 @@ Rules are evaluated in order; the first matching rule wins. ### Rule 10: TiDB with Branch Alias -**Effect**: Maps TiDB pages with branch aliasing (master → dev, release-* → v*). +**Effect**: Maps TiDB pages with branch aliasing (master → dev, release-_ → v_). **Source Pattern**: `/{lang}/tidb/{branch}/{...folders}/{filename}` @@ -377,11 +411,13 @@ Rules are evaluated in order; the first matching rule wins. **Filename Transform**: `ignoreIf: ["_index", "_docHome"]` **Alias Mapping** (`branch-alias-tidb`): + - `master` → `dev` - `{stable}` → `stable` (exact match) - `release-*` → `v*` (wildcard pattern) **Example**: + - Source: `en/tidb/master/alert-rules.md` - Target: `/tidb/dev/alert-rules` - Source: `en/tidb/release-8.5/alert-rules.md` @@ -391,9 +427,28 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 11: TiDB-in-Kubernetes with Branch Alias +### Rule 11: TiDB-in-Kubernetes Release Notes from Main + +**Effect**: Publishes TiDB-in-Kubernetes release notes from `main` at stable URLs so they override the copies from the configured stable release branch. + +**Source Pattern**: `/{lang}/tidb-in-kubernetes/main/releases/{filename}` + +**Target Pattern**: `/{lang}/tidb-in-kubernetes/stable/{filename}` + +**Example**: + +- Source: `en/tidb-in-kubernetes/main/releases/release-2.0.0.md` +- Target: `/tidb-in-kubernetes/stable/release-2.0.0` +- Source: `zh/tidb-in-kubernetes/main/releases/release-2.0.0.md` +- Target: `/zh/tidb-in-kubernetes/stable/release-2.0.0` + +**Use Case**: Release notes are maintained on `main`, but their canonical published URLs must resolve under `stable`. The earlier releases-index rule continues to map `_index.md` to `/releases/tidb-operator`. + +--- + +### Rule 12: TiDB-in-Kubernetes with Branch Alias -**Effect**: Maps TiDB-in-Kubernetes pages with branch aliasing (main → dev, release-* → v*). +**Effect**: Maps TiDB-in-Kubernetes pages with branch aliasing (main → dev, release-_ → v_). **Source Pattern**: `/{lang}/tidb-in-kubernetes/{branch}/{...folders}/{filename}` @@ -402,11 +457,13 @@ Rules are evaluated in order; the first matching rule wins. **Filename Transform**: `ignoreIf: ["_index", "_docHome"]` **Alias Mapping** (`branch-alias-tidb-in-kubernetes`): + - `main` → `dev` - `{stable}` → `stable` (exact match) - `release-*` → `v*` (wildcard pattern) **Example**: + - Source: `en/tidb-in-kubernetes/main/deploy/deploy-tidb-on-kubernetes.md` - Target: `/tidb-in-kubernetes/dev/deploy-tidb-on-kubernetes` - Source: `en/tidb-in-kubernetes/release-1.6/deploy/deploy-tidb-on-kubernetes.md` @@ -416,7 +473,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 12: Fallback Rule +### Rule 13: Fallback Rule **Effect**: Generic fallback for any remaining paths. @@ -427,6 +484,7 @@ Rules are evaluated in order; the first matching rule wins. **Filename Transform**: `ignoreIf: ["_index", "_docHome"]` **Example**: + - Source: `en/dm/release-5.3/migration/migrate-data.md` - Target: `/en/dm/migrate-data` @@ -447,6 +505,7 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{curLang}/releases/tidb-self-managed` **Example**: + - Link: `/releases/_index` - Current Page: Any page - Result: `/releases/tidb-self-managed` (or `/en/releases/tidb-self-managed` if default language not omitted) @@ -464,6 +523,7 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{curLang}/releases/tidb-cloud` **Example**: + - Link: `/tidb-cloud/releases/_index` - Current Page: Any page - Result: `/releases/tidb-cloud` @@ -483,6 +543,7 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{curLang}/releases/tidb-operator` **Example**: + - Current Page: `/tidb-in-kubernetes/stable/deploy` - Link: `/tidb-in-kubernetes/releases/_index` - Result: `/releases/tidb-operator` @@ -502,6 +563,7 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{lang}/tidb/stable/{docname}` **Example**: + - Current Page: `/releases/tidb-self-managed` - Link: `/releases/release-8.5.4` - Result: `/tidb/stable/release-8.5.4` (or `/en/tidb/stable/release-8.5.4` if default language not omitted) @@ -510,22 +572,23 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 5: Links from TiDB Operator Releases Landing Page (Path-Based, /releases/*) +### Rule 5: Links from TiDB Operator Releases Landing Page (Path-Based, /releases/\*) -**Effect**: Resolves `/releases/*` links from the operator releases landing page to TiDB-in-Kubernetes `dev` branch URLs. +**Effect**: Resolves `/releases/*` links from the operator releases landing page to TiDB-in-Kubernetes `stable` URLs. **Path Pattern**: `/{lang}/releases/tidb-operator/{...any}` **Link Pattern**: `/releases/{docname}` -**Target Pattern**: `/{lang}/tidb-in-kubernetes/dev/{docname}` +**Target Pattern**: `/{lang}/tidb-in-kubernetes/stable/{docname}` **Example**: + - Current Page: `/releases/tidb-operator` - Link: `/releases/release-2.0.0` -- Result: `/tidb-in-kubernetes/dev/release-2.0.0` (or `/en/tidb-in-kubernetes/dev/release-2.0.0` if default language not omitted) +- Result: `/tidb-in-kubernetes/stable/release-2.0.0` (or `/en/tidb-in-kubernetes/stable/release-2.0.0` if default language not omitted) -**Use Case**: The operator releases landing page is under `/releases/`, but the actual release notes pages are published under `/tidb-in-kubernetes/dev/*` (`main` is published as `dev`). +**Use Case**: The operator releases landing page is under `/releases/`, while release notes from `main` are published under `/tidb-in-kubernetes/stable/*` to override the copies from the stable release branch. --- @@ -540,11 +603,13 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `namespace = ["tidb-cloud", "tidb-cloud-lake", "develop", "best-practices", "api", "ai"]` **Namespace Transform**: + - `tidb-cloud` → `tidbcloud` - `tidb-cloud-lake` → `tidbcloudlake` - `develop` → `developer` **Example**: + - Link: `/develop/_index` - Current Page: Any page - Result: `/developer` @@ -570,11 +635,13 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `namespace = ["tidb-cloud", "tidb-cloud-lake", "develop", "best-practices", "api", "ai"]` **Namespace Transform**: + - `tidb-cloud` → `tidbcloud` - `tidb-cloud-lake` → `tidbcloudlake` - `develop` → `developer` **Example**: + - Link: `/develop/vector-search` - Current Page: Any page - Result: `/developer/vector-search` @@ -597,6 +664,7 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{lang}/tidbcloud/{docname}` **Example**: + - Current Page: `/tidbcloud/dedicated` - Link: `/getting-started` - Result: `/tidbcloud/getting-started` @@ -619,6 +687,7 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{lang}/tidbcloudlake/{docname}` **Example**: + - Current Page: `/tidbcloudlake` - Link: `/guides/dashboards` - Result: `/tidbcloudlake/dashboards` @@ -640,6 +709,7 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{lang}/tidb/stable/{docname}` **Example**: + - Current Page: `/developer/overview` - Link: `/vector-search` - Result: `/tidb/stable/vector-search` @@ -660,10 +730,12 @@ Rules are evaluated in order; the first matching rule wins. **Path Conditions**: `repo = ["tidb", "tidb-in-kubernetes"]` **Link Pattern / Target Pattern**: + - Index links: `/{...folders}/_index` → `/{lang}/{repo}/{branch}/{folders}` - Other links: `/{...any}/{docname}` → `/{lang}/{repo}/{branch}/{docname}` **Example**: + - Current Page: `/tidb/stable/upgrade` - Link: `/upgrade-tidb-using-tiup` - Result: `/tidb/stable/upgrade-tidb-using-tiup` diff --git a/gatsby/link-resolver/__tests__/link-resolver.test.ts b/gatsby/link-resolver/__tests__/link-resolver.test.ts index 9ed75d9e..80c1c9d0 100644 --- a/gatsby/link-resolver/__tests__/link-resolver.test.ts +++ b/gatsby/link-resolver/__tests__/link-resolver.test.ts @@ -355,7 +355,7 @@ describe("resolveMarkdownLink", () => { "/releases/release-2.0.0", "/en/releases/tidb-operator" ); - expect(result).toBe("/tidb-in-kubernetes/dev/release-2.0.0"); + expect(result).toBe("/tidb-in-kubernetes/stable/release-2.0.0"); }); it("should resolve /releases/* links from releases/tidb-operator page (en - currentPageUrl without language prefix)", () => { @@ -363,7 +363,7 @@ describe("resolveMarkdownLink", () => { "/releases/release-2.0.0", "/releases/tidb-operator" ); - expect(result).toBe("/tidb-in-kubernetes/dev/release-2.0.0"); + expect(result).toBe("/tidb-in-kubernetes/stable/release-2.0.0"); }); it("should resolve /releases/* links from releases/tidb-operator page (en - default language omitted)", () => { @@ -371,7 +371,7 @@ describe("resolveMarkdownLink", () => { "/release-2.0.0", "/en/releases/tidb-operator" ); - expect(result).toBe("/tidb-in-kubernetes/dev/release-2.0.0"); + expect(result).toBe("/tidb-in-kubernetes/stable/release-2.0.0"); }); it("should resolve /releases/* links from releases/tidb-operator page (en - currentPageUrl without language prefix)", () => { @@ -379,7 +379,7 @@ describe("resolveMarkdownLink", () => { "/release-2.0.0", "/releases/tidb-operator" ); - expect(result).toBe("/tidb-in-kubernetes/dev/release-2.0.0"); + expect(result).toBe("/tidb-in-kubernetes/stable/release-2.0.0"); }); it("should resolve /releases/* links from releases/tidb-operator page (zh - language prefix included)", () => { @@ -387,7 +387,7 @@ describe("resolveMarkdownLink", () => { "/releases/release-2.0.0", "/zh/releases/tidb-operator" ); - expect(result).toBe("/zh/tidb-in-kubernetes/dev/release-2.0.0"); + expect(result).toBe("/zh/tidb-in-kubernetes/stable/release-2.0.0"); }); it("should resolve /releases/* links from releases/tidb-operator page (zh - language prefix included)", () => { @@ -395,7 +395,17 @@ describe("resolveMarkdownLink", () => { "/release-2.0.0", "/zh/releases/tidb-operator" ); - expect(result).toBe("/zh/tidb-in-kubernetes/dev/release-2.0.0"); + expect(result).toBe("/zh/tidb-in-kubernetes/stable/release-2.0.0"); + }); + + it("should resolve operator release links without a leading slash and preserve the hash", () => { + const result = resolveMarkdownLink( + "releases/release-2.0.0#upgrade", + "/ja/releases/tidb-operator" + ); + expect(result).toBe( + "/ja/tidb-in-kubernetes/stable/release-2.0.0#upgrade" + ); }); it("should resolve releases namespace links (en - matches Rule 4, not Rule 1)", () => { diff --git a/gatsby/link-resolver/config.ts b/gatsby/link-resolver/config.ts index aa7b0e38..c2bc230d 100644 --- a/gatsby/link-resolver/config.ts +++ b/gatsby/link-resolver/config.ts @@ -33,11 +33,11 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { targetPattern: "/{lang}/tidb/stable/{docname}", }, // Current page: /{lang}/releases/tidb-operator - // Link: /releases/{docname} -> /{lang}/tidb-in-kubernetes/dev/{docname} + // Link: /releases/{docname} -> /{lang}/tidb-in-kubernetes/stable/{docname} { pathPattern: "/{lang}/releases/tidb-operator", linkPattern: "/{...any}/{docname}", - targetPattern: "/{lang}/tidb-in-kubernetes/dev/{docname}", + targetPattern: "/{lang}/tidb-in-kubernetes/stable/{docname}", }, // Rule 1: Links starting with specific namespaces (direct link mapping) // Special handling for namespace index links: diff --git a/gatsby/url-resolver/__tests__/url-resolver.test.ts b/gatsby/url-resolver/__tests__/url-resolver.test.ts index 7bd3deeb..eadd5245 100644 --- a/gatsby/url-resolver/__tests__/url-resolver.test.ts +++ b/gatsby/url-resolver/__tests__/url-resolver.test.ts @@ -351,6 +351,40 @@ describe("calculateFileUrl", () => { expect(url).toBe("/en/tidb/v8.1/releases"); }); + it.each([ + ["en", "/en/tidb-in-kubernetes/stable/release-2.0.0/"], + ["zh", "/zh/tidb-in-kubernetes/stable/release-2.0.0/"], + ["ja", "/ja/tidb-in-kubernetes/stable/release-2.0.0/"], + ])( + "should map %s tidb-in-kubernetes release notes from main to stable", + (lang, expected) => { + const absolutePath = path.join( + sourceBasePath, + `${lang}/tidb-in-kubernetes/main/releases/release-2.0.0.md` + ); + const url = calculateFileUrlWithConfig(absolutePath, testConfig); + expect(url).toBe(expected); + } + ); + + it("should keep the tidb-in-kubernetes releases index mapping", () => { + const absolutePath = path.join( + sourceBasePath, + "en/tidb-in-kubernetes/main/releases/_index.md" + ); + const url = calculateFileUrlWithConfig(absolutePath, testConfig); + expect(url).toBe("/en/releases/tidb-operator/"); + }); + + it("should continue mapping non-release pages from main to dev", () => { + const absolutePath = path.join( + sourceBasePath, + "en/tidb-in-kubernetes/main/deploy/deploy-tidb-on-kubernetes.md" + ); + const url = calculateFileUrlWithConfig(absolutePath, testConfig); + expect(url).toBe("/en/tidb-in-kubernetes/dev/deploy-tidb-on-kubernetes/"); + }); + it("should resolve releases folder zh", () => { const absolutePath = path.join( sourceBasePath, diff --git a/gatsby/url-resolver/config.ts b/gatsby/url-resolver/config.ts index ab3c4100..2b8e12d7 100644 --- a/gatsby/url-resolver/config.ts +++ b/gatsby/url-resolver/config.ts @@ -107,8 +107,7 @@ export const defaultUrlResolverConfig: UrlResolverConfig = { // When filename = "_index": /en/tidb-cloud-lake/master/{folders}/_index.md -> /en/tidbcloudlake/{folders} // When filename != "_index": /en/tidb-cloud-lake/master/{folders}/{filename}.md -> /en/tidbcloudlake/{filename} { - sourcePattern: - "/{lang}/tidb-cloud-lake/{branch}/{...folders}/{filename}", + sourcePattern: "/{lang}/tidb-cloud-lake/{branch}/{...folders}/{filename}", targetPattern: "/{lang}/tidbcloudlake/{filename}", filenameTransform: { ignoreIf: ["_index"], @@ -140,6 +139,13 @@ export const defaultUrlResolverConfig: UrlResolverConfig = { ignoreIf: ["_index", "_docHome"], }, }, + // tidb-in-kubernetes release notes from main + // Release notes from main override the copies from the stable release branch. + // /en/tidb-in-kubernetes/main/releases/{filename} -> /en/tidb-in-kubernetes/stable/{filename} + { + sourcePattern: "/{lang}/tidb-in-kubernetes/main/releases/{filename}", + targetPattern: "/{lang}/tidb-in-kubernetes/stable/{filename}", + }, // tidb-in-kubernetes with branch and optional folders // /en/tidb-in-kubernetes/main/{...folders}/{filename} -> /en/tidb-in-kubernetes/stable/{filename} // /en/tidb-in-kubernetes/release-1.6/{...folders}/{filename} -> /en/tidb-in-kubernetes/v1.6/{filename} From 5a5f098e31b2bc531d8f4c1c4033d67db1acc8e1 Mon Sep 17 00:00:00 2001 From: Suhaha Date: Wed, 29 Jul 2026 15:14:52 +0800 Subject: [PATCH 2/4] fix(url-resolver): format sourcePattern for better readability --- gatsby/URL_MAPPING_ARCHITECTURE.md | 64 +++--------------------------- gatsby/url-resolver/config.ts | 3 +- 2 files changed, 7 insertions(+), 60 deletions(-) diff --git a/gatsby/URL_MAPPING_ARCHITECTURE.md b/gatsby/URL_MAPPING_ARCHITECTURE.md index e3093604..cc5c2a80 100644 --- a/gatsby/URL_MAPPING_ARCHITECTURE.md +++ b/gatsby/URL_MAPPING_ARCHITECTURE.md @@ -3,7 +3,6 @@ ## Overview This document describes how the project handles URL mapping across three key areas: - 1. **Page URL Mapping**: Converting source file paths to published page URLs during build 2. **TOC Mapping**: Resolving links in TOC (Table of Contents) files 3. **Article Link Mapping**: Transforming internal links within markdown articles @@ -17,14 +16,12 @@ The system uses two core resolvers (`url-resolver` and `link-resolver`) that wor **Location**: `gatsby/create-pages/create-docs.ts` **Process**: - 1. Gatsby queries all MDX files from the GraphQL data layer 2. For each file, `calculateFileUrl()` from `url-resolver` converts the source path to a published URL 3. `getTOCNamespace()` from `toc-namespace` determines the page's TOC namespace for navigation/context 4. The resolved URL is used to create the Gatsby page with `createPage()` **Example**: - ```typescript // Source file: docs/markdown-pages/en/tidb/master/alert-rules.md // Slug: "en/tidb/master/alert-rules" @@ -34,7 +31,6 @@ const path = calculateFileUrl(node.slug, true); ``` **Key Points**: - - Uses `url-resolver` to transform source paths to URLs - Default language (`en`) is omitted from URLs (`omitDefaultLanguage: true`) - Only files referenced in TOC files are built (filtered by `filterNodesByToc`) @@ -44,7 +40,6 @@ const path = calculateFileUrl(node.slug, true); **Location**: `gatsby/toc.ts` and `gatsby/toc-filter.ts` **Process**: - 1. Gatsby queries all TOC files (files matching `/TOC.*md$/`) 2. For each TOC file, `mdxAstToToc()` parses the markdown AST 3. Links within TOC are resolved using `resolveMarkdownLink()` from `link-resolver` @@ -53,21 +48,16 @@ const path = calculateFileUrl(node.slug, true); - Generate navigation menus for pages **Example**: - ```typescript // TOC file: docs/markdown-pages/en/tidb/stable/TOC.md // Contains link: [Getting Started](/develop/getting-started) // TOC path: "/en/tidb/stable" (resolved from TOC file slug) -const resolvedLink = resolveMarkdownLink( - "/develop/getting-started", - "/en/tidb/stable" -); +const resolvedLink = resolveMarkdownLink("/develop/getting-started", "/en/tidb/stable"); // Result: "/developer/getting-started" // Used in navigation menu ``` **Key Points**: - - Uses `link-resolver` to resolve links in TOC files - TOC links are resolved relative to the TOC file's own URL - Resolved links are used to build a whitelist of files to include in the build @@ -77,14 +67,12 @@ const resolvedLink = resolveMarkdownLink( **Location**: `gatsby/plugin/content/index.ts` **Process**: - 1. During markdown processing, Gatsby's MDX plugin processes each article 2. For each link in the markdown AST, `resolveMarkdownLink()` resolves the link path 3. The resolved link is converted to a Gatsby `` component 4. External links (`http://`, `https://`) are kept as-is with `target="_blank"` **Example**: - ```typescript // Article: docs/markdown-pages/en/tidb/stable/overview.md // Contains link: [Upgrade Guide](/upgrade/upgrade-tidb-using-tiup) @@ -98,7 +86,6 @@ const resolvedPath = resolveMarkdownLink( ``` **Key Points**: - - Uses `link-resolver` to resolve links based on current page context - Links are resolved relative to the current article's URL - Hash fragments (`#section`) are preserved automatically @@ -147,25 +134,21 @@ Final HTML/JSX **Scenario**: Building a TiDB article with links 1. **Source File**: `docs/markdown-pages/en/tidb/master/alert-rules.md` - - Contains link: `[Vector Search](/develop/vector-search)` 2. **Page URL Resolution** (`create-docs.ts`): - ```typescript const pageUrl = calculateFileUrl("en/tidb/master/alert-rules", true); // Result: "/tidb/dev/alert-rules" ``` 3. **TOC Processing** (`toc-filter.ts`): - - TOC file: `en/tidb/stable/TOC.md` - Contains link to `alert-rules` - Link resolved: `/tidb/dev/alert-rules` - File added to whitelist: `en/tidb/stable -> Set(["alert-rules"])` 4. **Page Creation** (`create-docs.ts`): - - File matches TOC whitelist → page is created - Page URL: `/tidb/dev/alert-rules` - Namespace: `TOCNamespace.TiDB` @@ -174,7 +157,7 @@ Final HTML/JSX - Current page URL: `/en/tidb/dev/alert-rules` - Link `/develop/vector-search` resolved: ```typescript - resolveMarkdownLink("/develop/vector-search", "/en/tidb/dev/alert-rules"); + resolveMarkdownLink("/develop/vector-search", "/en/tidb/dev/alert-rules") // Result: "/developer/vector-search" ``` - Rendered as: `Vector Search` @@ -200,7 +183,6 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `filename = "_index"` **Example**: - - Source: `en/tidbcloud/master/tidb-cloud/dedicated/_index.md` - Target: `/tidbcloud` (or `/en/tidbcloud` if default language not omitted) @@ -219,7 +201,6 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `filename = "_index"` **Example**: - - Source: `en/tidbcloud/master/tidb-cloud/releases/_index.md` - Target: `/releases/tidb-cloud` @@ -238,7 +219,6 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `filename = "_index"` **Example**: - - Source: `en/tidb/release-8.5/releases/_index.md` - Target: `/releases/tidb-self-managed` @@ -257,7 +237,6 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `filename = "_index"` **Example**: - - Source: `en/tidb-in-kubernetes/main/releases/_index.md` - Target: `/releases/tidb-operator` @@ -272,17 +251,14 @@ Rules are evaluated in order; the first matching rule wins. **Source Pattern**: `/{lang}/tidbcloud/{branch}/tidb-cloud/{...prefixes}/{filename}` **Target Pattern**: - - For `_index`: `/{lang}/tidbcloud/{prefixes}` (keeps prefixes) - For other files: `/{lang}/tidbcloud/{filename}` (removes prefixes) **Filename Transform**: - - `ignoreIf: ["_index"]` - Filename removed from URL for non-index files - `conditionalTarget.keepIf: ["_index"]` - Uses alternative pattern for `_index` files **Example**: - - Source: `en/tidbcloud/master/tidb-cloud/dedicated/starter/_index.md` - Target: `/tidbcloud/dedicated/starter` - Source: `en/tidbcloud/master/tidb-cloud/dedicated/starter/getting-started.md` @@ -299,19 +275,16 @@ Rules are evaluated in order; the first matching rule wins. **Source Pattern**: `/{lang}/tidb/{stable}/{folder}/{...folders}/{filename}` **Target Pattern**: - - For `_index`: `/{lang}/developer/{folders}` (keeps folder structure) - For other files: `/{lang}/developer/{filename}` (flattens folder structure) **Conditions**: `folder = ["develop"]` **Filename Transform**: - - `ignoreIf: ["_index"]` - `conditionalTarget.keepIf: ["_index"]` **Example**: - - Source: `en/tidb/release-8.5/develop/subfolder/_index.md` - Target: `/developer/subfolder` - Source: `en/tidb/release-8.5/develop/subfolder/vector-search.md` @@ -328,19 +301,16 @@ Rules are evaluated in order; the first matching rule wins. **Source Pattern**: `/{lang}/tidb/{stable}/{folder}/{...folders}/{filename}` **Target Pattern**: - - For `_index`: `/{lang}/{folder}/{folders}` (keeps folder structure) - For other files: `/{lang}/{folder}/{filename}` (flattens folder structure) **Conditions**: `folder = ["best-practices", "api", "ai"]` **Filename Transform**: - - `ignoreIf: ["_index"]` - `conditionalTarget.keepIf: ["_index"]` **Example**: - - Source: `en/tidb/release-8.5/ai/subfolder/_index.md` - Target: `/ai/subfolder` - Source: `en/tidb/release-8.5/api/overview.md` @@ -357,17 +327,14 @@ Rules are evaluated in order; the first matching rule wins. **Source Pattern**: `/{lang}/tidb-cloud-lake/{branch}/{...folders}/{filename}` **Target Pattern**: - - For `_index`: `/{lang}/tidbcloudlake/{folders}` (keeps folder structure) - For other files: `/{lang}/tidbcloudlake/{filename}` (flattens folder structure) **Filename Transform**: - - `ignoreIf: ["_index"]` - `conditionalTarget.keepIf: ["_index"]` **Example**: - - Source: `en/tidb-cloud-lake/master/_index.md` - Target: `/tidbcloudlake` - Source: `en/tidb-cloud-lake/master/tidb-cloud-lake/_index.md` @@ -390,7 +357,6 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `filename = "_index"` **Example**: - - Source: `en/tidb/master/develop/_index.md` - Target: `/tidb/dev/develop` - Source: `en/tidb/master/releases/_index.md` @@ -402,7 +368,7 @@ Rules are evaluated in order; the first matching rule wins. ### Rule 10: TiDB with Branch Alias -**Effect**: Maps TiDB pages with branch aliasing (master → dev, release-_ → v_). +**Effect**: Maps TiDB pages with branch aliasing (master → dev, release-* → v*). **Source Pattern**: `/{lang}/tidb/{branch}/{...folders}/{filename}` @@ -411,13 +377,11 @@ Rules are evaluated in order; the first matching rule wins. **Filename Transform**: `ignoreIf: ["_index", "_docHome"]` **Alias Mapping** (`branch-alias-tidb`): - - `master` → `dev` - `{stable}` → `stable` (exact match) - `release-*` → `v*` (wildcard pattern) **Example**: - - Source: `en/tidb/master/alert-rules.md` - Target: `/tidb/dev/alert-rules` - Source: `en/tidb/release-8.5/alert-rules.md` @@ -436,7 +400,6 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{lang}/tidb-in-kubernetes/stable/{filename}` **Example**: - - Source: `en/tidb-in-kubernetes/main/releases/release-2.0.0.md` - Target: `/tidb-in-kubernetes/stable/release-2.0.0` - Source: `zh/tidb-in-kubernetes/main/releases/release-2.0.0.md` @@ -448,7 +411,7 @@ Rules are evaluated in order; the first matching rule wins. ### Rule 12: TiDB-in-Kubernetes with Branch Alias -**Effect**: Maps TiDB-in-Kubernetes pages with branch aliasing (main → dev, release-_ → v_). +**Effect**: Maps TiDB-in-Kubernetes pages with branch aliasing (main → dev, release-* → v*). **Source Pattern**: `/{lang}/tidb-in-kubernetes/{branch}/{...folders}/{filename}` @@ -457,13 +420,11 @@ Rules are evaluated in order; the first matching rule wins. **Filename Transform**: `ignoreIf: ["_index", "_docHome"]` **Alias Mapping** (`branch-alias-tidb-in-kubernetes`): - - `main` → `dev` - `{stable}` → `stable` (exact match) - `release-*` → `v*` (wildcard pattern) **Example**: - - Source: `en/tidb-in-kubernetes/main/deploy/deploy-tidb-on-kubernetes.md` - Target: `/tidb-in-kubernetes/dev/deploy-tidb-on-kubernetes` - Source: `en/tidb-in-kubernetes/release-1.6/deploy/deploy-tidb-on-kubernetes.md` @@ -484,7 +445,6 @@ Rules are evaluated in order; the first matching rule wins. **Filename Transform**: `ignoreIf: ["_index", "_docHome"]` **Example**: - - Source: `en/dm/release-5.3/migration/migrate-data.md` - Target: `/en/dm/migrate-data` @@ -505,7 +465,6 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{curLang}/releases/tidb-self-managed` **Example**: - - Link: `/releases/_index` - Current Page: Any page - Result: `/releases/tidb-self-managed` (or `/en/releases/tidb-self-managed` if default language not omitted) @@ -523,7 +482,6 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{curLang}/releases/tidb-cloud` **Example**: - - Link: `/tidb-cloud/releases/_index` - Current Page: Any page - Result: `/releases/tidb-cloud` @@ -543,7 +501,6 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{curLang}/releases/tidb-operator` **Example**: - - Current Page: `/tidb-in-kubernetes/stable/deploy` - Link: `/tidb-in-kubernetes/releases/_index` - Result: `/releases/tidb-operator` @@ -563,7 +520,6 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{lang}/tidb/stable/{docname}` **Example**: - - Current Page: `/releases/tidb-self-managed` - Link: `/releases/release-8.5.4` - Result: `/tidb/stable/release-8.5.4` (or `/en/tidb/stable/release-8.5.4` if default language not omitted) @@ -572,7 +528,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 5: Links from TiDB Operator Releases Landing Page (Path-Based, /releases/\*) +### Rule 5: Links from TiDB Operator Releases Landing Page (Path-Based, /releases/*) **Effect**: Resolves `/releases/*` links from the operator releases landing page to TiDB-in-Kubernetes `stable` URLs. @@ -583,7 +539,6 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{lang}/tidb-in-kubernetes/stable/{docname}` **Example**: - - Current Page: `/releases/tidb-operator` - Link: `/releases/release-2.0.0` - Result: `/tidb-in-kubernetes/stable/release-2.0.0` (or `/en/tidb-in-kubernetes/stable/release-2.0.0` if default language not omitted) @@ -603,13 +558,11 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `namespace = ["tidb-cloud", "tidb-cloud-lake", "develop", "best-practices", "api", "ai"]` **Namespace Transform**: - - `tidb-cloud` → `tidbcloud` - `tidb-cloud-lake` → `tidbcloudlake` - `develop` → `developer` **Example**: - - Link: `/develop/_index` - Current Page: Any page - Result: `/developer` @@ -635,13 +588,11 @@ Rules are evaluated in order; the first matching rule wins. **Conditions**: `namespace = ["tidb-cloud", "tidb-cloud-lake", "develop", "best-practices", "api", "ai"]` **Namespace Transform**: - - `tidb-cloud` → `tidbcloud` - `tidb-cloud-lake` → `tidbcloudlake` - `develop` → `developer` **Example**: - - Link: `/develop/vector-search` - Current Page: Any page - Result: `/developer/vector-search` @@ -664,7 +615,6 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{lang}/tidbcloud/{docname}` **Example**: - - Current Page: `/tidbcloud/dedicated` - Link: `/getting-started` - Result: `/tidbcloud/getting-started` @@ -687,7 +637,6 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{lang}/tidbcloudlake/{docname}` **Example**: - - Current Page: `/tidbcloudlake` - Link: `/guides/dashboards` - Result: `/tidbcloudlake/dashboards` @@ -709,7 +658,6 @@ Rules are evaluated in order; the first matching rule wins. **Target Pattern**: `/{lang}/tidb/stable/{docname}` **Example**: - - Current Page: `/developer/overview` - Link: `/vector-search` - Result: `/tidb/stable/vector-search` @@ -730,12 +678,10 @@ Rules are evaluated in order; the first matching rule wins. **Path Conditions**: `repo = ["tidb", "tidb-in-kubernetes"]` **Link Pattern / Target Pattern**: - - Index links: `/{...folders}/_index` → `/{lang}/{repo}/{branch}/{folders}` - Other links: `/{...any}/{docname}` → `/{lang}/{repo}/{branch}/{docname}` **Example**: - - Current Page: `/tidb/stable/upgrade` - Link: `/upgrade-tidb-using-tiup` - Result: `/tidb/stable/upgrade-tidb-using-tiup` diff --git a/gatsby/url-resolver/config.ts b/gatsby/url-resolver/config.ts index 2b8e12d7..6055e394 100644 --- a/gatsby/url-resolver/config.ts +++ b/gatsby/url-resolver/config.ts @@ -107,7 +107,8 @@ export const defaultUrlResolverConfig: UrlResolverConfig = { // When filename = "_index": /en/tidb-cloud-lake/master/{folders}/_index.md -> /en/tidbcloudlake/{folders} // When filename != "_index": /en/tidb-cloud-lake/master/{folders}/{filename}.md -> /en/tidbcloudlake/{filename} { - sourcePattern: "/{lang}/tidb-cloud-lake/{branch}/{...folders}/{filename}", + sourcePattern: + "/{lang}/tidb-cloud-lake/{branch}/{...folders}/{filename}", targetPattern: "/{lang}/tidbcloudlake/{filename}", filenameTransform: { ignoreIf: ["_index"], From cd616b7df5fa0b7e9eac05bbcea3997f3bb3ee8f Mon Sep 17 00:00:00 2001 From: Suhaha Date: Wed, 29 Jul 2026 15:17:18 +0800 Subject: [PATCH 3/4] feat(url-resolver): add test for stable URL mapping of release notes --- .../__tests__/url-resolver.test.ts | 20 +++++++++++++++++++ gatsby/url-resolver/config.ts | 3 +-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/gatsby/url-resolver/__tests__/url-resolver.test.ts b/gatsby/url-resolver/__tests__/url-resolver.test.ts index eadd5245..05c2b1e0 100644 --- a/gatsby/url-resolver/__tests__/url-resolver.test.ts +++ b/gatsby/url-resolver/__tests__/url-resolver.test.ts @@ -376,6 +376,26 @@ describe("calculateFileUrl", () => { expect(url).toBe("/en/releases/tidb-operator/"); }); + it("should map main and stable-branch release notes to the same stable URL", () => { + const mainUrl = calculateFileUrlWithConfig( + path.join( + sourceBasePath, + "en/tidb-in-kubernetes/main/releases/release-2.0.0.md" + ), + testConfig + ); + const stableBranchUrl = calculateFileUrlWithConfig( + path.join( + sourceBasePath, + "en/tidb-in-kubernetes/release-1.6/releases/release-2.0.0.md" + ), + testConfig + ); + + expect(mainUrl).toBe("/en/tidb-in-kubernetes/stable/release-2.0.0/"); + expect(stableBranchUrl).toBe(mainUrl); + }); + it("should continue mapping non-release pages from main to dev", () => { const absolutePath = path.join( sourceBasePath, diff --git a/gatsby/url-resolver/config.ts b/gatsby/url-resolver/config.ts index 6055e394..2b8e12d7 100644 --- a/gatsby/url-resolver/config.ts +++ b/gatsby/url-resolver/config.ts @@ -107,8 +107,7 @@ export const defaultUrlResolverConfig: UrlResolverConfig = { // When filename = "_index": /en/tidb-cloud-lake/master/{folders}/_index.md -> /en/tidbcloudlake/{folders} // When filename != "_index": /en/tidb-cloud-lake/master/{folders}/{filename}.md -> /en/tidbcloudlake/{filename} { - sourcePattern: - "/{lang}/tidb-cloud-lake/{branch}/{...folders}/{filename}", + sourcePattern: "/{lang}/tidb-cloud-lake/{branch}/{...folders}/{filename}", targetPattern: "/{lang}/tidbcloudlake/{filename}", filenameTransform: { ignoreIf: ["_index"], From 25bafc32c2b2a4f933a28d0c1ff385e76ee5de05 Mon Sep 17 00:00:00 2001 From: Suhaha Date: Wed, 29 Jul 2026 17:45:47 +0800 Subject: [PATCH 4/4] feat(toc): enhance TiDB-in-Kubernetes TOC release links to resolve to stable URLs --- gatsby/URL_MAPPING_ARCHITECTURE.md | 31 +++++++++--- gatsby/__tests__/get-files-from-tocs.test.ts | 26 ++++++++++ gatsby/__tests__/toc.test.ts | 50 +++++++++++++++++++ .../__tests__/link-resolver.test.ts | 39 +++++++++++++++ gatsby/link-resolver/config.ts | 7 +++ 5 files changed, 147 insertions(+), 6 deletions(-) diff --git a/gatsby/URL_MAPPING_ARCHITECTURE.md b/gatsby/URL_MAPPING_ARCHITECTURE.md index cc5c2a80..421e91f7 100644 --- a/gatsby/URL_MAPPING_ARCHITECTURE.md +++ b/gatsby/URL_MAPPING_ARCHITECTURE.md @@ -547,7 +547,26 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 6: Namespace Index Links (Direct Mapping) +### Rule 6: TiDB-in-Kubernetes Main TOC Release Links (Path-Based) + +**Effect**: Resolves release-note links from the `main` TiDB-in-Kubernetes TOC to the stable URLs that publish the corresponding `main` release-note files. + +**Path Pattern**: `/{lang}/tidb-in-kubernetes/dev/TOC-tidb-operator-releases` + +**Link Pattern**: `/releases/{docname}` + +**Target Pattern**: `/{lang}/tidb-in-kubernetes/stable/{docname}` + +**Example**: +- Current TOC: `/tidb-in-kubernetes/dev/TOC-tidb-operator-releases` +- Link: `/releases/release-2.0.0` +- Result: `/tidb-in-kubernetes/stable/release-2.0.0` + +**Use Case**: `TOC-tidb-operator-releases.md` from `main` is resolved under the `dev` branch alias, but its `releases/release-*.md` entries are published under `/stable/*`. Other TOC links continue to resolve under `dev`, and versioned TOCs continue to preserve their version. + +--- + +### Rule 7: Namespace Index Links (Direct Mapping) **Effect**: Resolves namespace index links (ending with `/_index`) to namespace URLs (published as `/developer`, `/best-practices`, `/api`, `/ai`, `/tidbcloud`, `/tidbcloudlake`). @@ -577,7 +596,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 7: Namespace Links (Direct Mapping) +### Rule 8: Namespace Links (Direct Mapping) **Effect**: Resolves namespace links (`develop`, `best-practices`, `api`, `ai`, `tidb-cloud`, `tidb-cloud-lake`) to namespace URLs (published as `/developer`, `/best-practices`, `/api`, `/ai`, `/tidbcloud`, `/tidbcloudlake`). @@ -604,7 +623,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 8: TiDBCloud Page Links (Path-Based) +### Rule 9: TiDBCloud Page Links (Path-Based) **Effect**: Resolves relative links from TiDBCloud pages to TiDBCloud URLs. @@ -626,7 +645,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 9: TiDB Cloud Lake Page Links (Path-Based) +### Rule 10: TiDB Cloud Lake Page Links (Path-Based) **Effect**: Resolves relative links from TiDB Cloud Lake pages to `/tidbcloudlake/*` URLs. @@ -645,7 +664,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 10: Developer/Best-Practices/API/AI Namespace Page Links (Path-Based) +### Rule 11: Developer/Best-Practices/API/AI Namespace Page Links (Path-Based) **Effect**: Resolves relative links from namespace pages to TiDB stable branch URLs. @@ -669,7 +688,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 11: TiDB/TiDB-in-Kubernetes Page Links (Path-Based) +### Rule 12: TiDB/TiDB-in-Kubernetes Page Links (Path-Based) **Effect**: Resolves relative links from TiDB or TiDB-in-Kubernetes pages, preserving branch/version. diff --git a/gatsby/__tests__/get-files-from-tocs.test.ts b/gatsby/__tests__/get-files-from-tocs.test.ts index 3cbb4d2d..01a9d013 100644 --- a/gatsby/__tests__/get-files-from-tocs.test.ts +++ b/gatsby/__tests__/get-files-from-tocs.test.ts @@ -110,6 +110,32 @@ describe("getFilesFromTocs TOC selection rules", () => { ]); }); + it("tidb-in-kubernetes main reads the operator releases TOC", async () => { + const { getFilesFromTocs } = require("../toc-filter"); + + const graphql = jest.fn().mockResolvedValue({ + data: { + allMdx: { + nodes: [ + makeNode( + "en/tidb-in-kubernetes/main/TOC", + "docs/markdown-pages/en/tidb-in-kubernetes/main/TOC.md" + ), + makeNode( + "en/tidb-in-kubernetes/main/TOC-tidb-operator-releases", + "docs/markdown-pages/en/tidb-in-kubernetes/main/TOC-tidb-operator-releases.md" + ), + ], + }, + }, + }); + + const { tocFilesMap } = await getFilesFromTocs(graphql); + expect(new Set(tocFilesMap.get("en/tidb-in-kubernetes/dev")!)).toEqual( + new Set(["toc-only", "operator-releases-only"]) + ); + }); + it("tidbcloud always reads all TOCs under tidbcloud directory", async () => { const { getFilesFromTocs } = require("../toc-filter"); diff --git a/gatsby/__tests__/toc.test.ts b/gatsby/__tests__/toc.test.ts index db3e9544..6adf0bf1 100644 --- a/gatsby/__tests__/toc.test.ts +++ b/gatsby/__tests__/toc.test.ts @@ -1,4 +1,5 @@ import { mdxAstToToc } from "../toc"; +import { extractFilesFromToc } from "../toc-filter"; describe("mdxAstToToc tag query parsing", () => { it("does not emit a bogus ?undefined query when the image URL has no query string", () => { @@ -100,3 +101,52 @@ describe("mdxAstToToc tag query parsing", () => { }); }); }); + +describe("mdxAstToToc TiDB Operator releases navigation", () => { + it("maps main release TOC links to stable URLs and preserves TOC membership", () => { + const toc = mdxAstToToc( + [ + { + type: "list", + children: [ + { + type: "listItem", + children: [ + { + type: "paragraph", + children: [{ type: "text", value: "v2.0" }], + }, + { + type: "list", + children: [ + { + type: "listItem", + children: [ + { + type: "paragraph", + children: [ + { + type: "link", + url: "releases/release-2.0.0.md", + children: [{ type: "text", value: "2.0 GA" }], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ] as any, + "en/tidb-in-kubernetes/main/TOC-tidb-operator-releases" + ); + + expect(toc[0].children?.[0].link).toBe( + "/tidb-in-kubernetes/stable/release-2.0.0" + ); + expect(extractFilesFromToc(toc)).toEqual(["release-2.0.0"]); + }); +}); diff --git a/gatsby/link-resolver/__tests__/link-resolver.test.ts b/gatsby/link-resolver/__tests__/link-resolver.test.ts index 80c1c9d0..62732b2f 100644 --- a/gatsby/link-resolver/__tests__/link-resolver.test.ts +++ b/gatsby/link-resolver/__tests__/link-resolver.test.ts @@ -408,6 +408,45 @@ describe("resolveMarkdownLink", () => { ); }); + it.each([ + ["en", "/tidb-in-kubernetes/stable/release-2.0.0"], + ["zh", "/zh/tidb-in-kubernetes/stable/release-2.0.0"], + ["ja", "/ja/tidb-in-kubernetes/stable/release-2.0.0"], + ])( + "should resolve %s release links from the main tidb-in-kubernetes TOC to stable", + (lang, expected) => { + const result = resolveMarkdownLink( + "/releases/release-2.0.0", + `/${lang}/tidb-in-kubernetes/dev/TOC-tidb-operator-releases` + ); + expect(result).toBe(expected); + } + ); + + it("should resolve TOC release links without a leading slash and preserve the hash", () => { + const result = resolveMarkdownLink( + "releases/release-2.0.0#upgrade", + "/tidb-in-kubernetes/dev/TOC-tidb-operator-releases" + ); + expect(result).toBe("/tidb-in-kubernetes/stable/release-2.0.0#upgrade"); + }); + + it("should keep non-release links from the main tidb-in-kubernetes TOC on dev", () => { + const result = resolveMarkdownLink( + "/deploy/deploy-tidb-on-kubernetes", + "/en/tidb-in-kubernetes/dev/TOC-tidb-operator-releases" + ); + expect(result).toBe("/tidb-in-kubernetes/dev/deploy-tidb-on-kubernetes"); + }); + + it("should preserve versioned tidb-in-kubernetes TOC release links", () => { + const result = resolveMarkdownLink( + "/releases/release-2.0.0", + "/zh/tidb-in-kubernetes/v2.0/TOC" + ); + expect(result).toBe("/zh/tidb-in-kubernetes/v2.0/release-2.0.0"); + }); + it("should resolve releases namespace links (en - matches Rule 4, not Rule 1)", () => { const result = resolveMarkdownLink( "/releases/v8.5/release-notes", diff --git a/gatsby/link-resolver/config.ts b/gatsby/link-resolver/config.ts index c2bc230d..e89ba417 100644 --- a/gatsby/link-resolver/config.ts +++ b/gatsby/link-resolver/config.ts @@ -39,6 +39,13 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { linkPattern: "/{...any}/{docname}", targetPattern: "/{lang}/tidb-in-kubernetes/stable/{docname}", }, + // Current TOC: /{lang}/tidb-in-kubernetes/dev/TOC-tidb-operator-releases + // Link: /releases/{docname} -> /{lang}/tidb-in-kubernetes/stable/{docname} + { + pathPattern: "/{lang}/tidb-in-kubernetes/dev/TOC-tidb-operator-releases", + linkPattern: "/releases/{docname}", + targetPattern: "/{lang}/tidb-in-kubernetes/stable/{docname}", + }, // Rule 1: Links starting with specific namespaces (direct link mapping) // Special handling for namespace index links: // /develop/_index -> /developer