From 8ade1fe696095ea04e5f691ae1a9ac01ae0feb00 Mon Sep 17 00:00:00 2001 From: Cheese Date: Tue, 15 Sep 2026 16:59:24 -0400 Subject: [PATCH 1/3] feat: add TiDB Cloud Filesystem navigation and routing --- README.md | 18 +++ gatsby-config.js | 2 +- gatsby/__tests__/filesystem-header.test.ts | 54 ++++++++ gatsby/__tests__/filesystem-routing.test.ts | 129 ++++++++++++++++++ gatsby/link-resolver/config.ts | 16 +++ gatsby/toc-namespace/index.ts | 7 + gatsby/url-resolver/config.ts | 12 ++ locale/en/translation.json | 1 + locale/ja/translation.json | 1 + locale/zh/translation.json | 1 + .../Layout/Header/HeaderNavConfigData.tsx | 9 ++ src/shared/interface.ts | 2 + 12 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 gatsby/__tests__/filesystem-header.test.ts create mode 100644 gatsby/__tests__/filesystem-routing.test.ts diff --git a/README.md b/README.md index 74eac42c..34574283 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,24 @@ In order to debug algolia searches, you need to provide two additional environme Put them in `.env.development` to make them take effect. (Ref: ) +## Filesystem Documentation + +TiDB Cloud Filesystem appears after TiDB Cloud Lake in the Product menu. Its +English documentation is published at `/tidbcloudfs/` with its own +sidebar and Preview badge. + +The content comes from `pingcap/docs` on the stable TiDB documentation branch +(`release-8.5` in `docs/docs.json`), using `tidb-cloud-filesystem/` and +`TOC-tidb-cloud-filesystem.md`. It shares the existing `tidb` staging source; +it does not require a separate repository entry in `docs/docs.json`. The +source pages must reach `docs-staging` before a website deployment can publish +them. The menu links to English without changing the selected Cloud database +plan or implying that translated Filesystem pages exist. + +`gatsby/__tests__/filesystem-routing.test.ts` checks published URLs, sidebar +selection, TOC membership, and links to the existing AI command reference. +`gatsby/__tests__/filesystem-header.test.ts` checks menu order and selection. + ## Workflow Because of most of our text data stored in GitHub. It's needed to apply a GitHub API token in development **when you are prompted for `rate-limiting`**. diff --git a/gatsby-config.js b/gatsby-config.js index 73f8db87..59aeaf20 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -93,7 +93,7 @@ module.exports = { { matchPath: `/:lang?/(${Object.keys(docs.docs).join( "|" - )}|developer|best-practices|api|ai|releases|tidbcloudlake)/(.*)`, + )}|developer|best-practices|api|ai|releases|tidbcloudlake|tidbcloudfs)/(.*)`, getLanguageFromPath: true, }, { diff --git a/gatsby/__tests__/filesystem-header.test.ts b/gatsby/__tests__/filesystem-header.test.ts new file mode 100644 index 00000000..7866cac6 --- /dev/null +++ b/gatsby/__tests__/filesystem-header.test.ts @@ -0,0 +1,54 @@ +jest.mock("shared/interface", () => require("../../src/shared/interface"), { + virtual: true, +}); +jest.mock("shared/useCloudPlan", () => ({ CLOUD_MODE_KEY: "cloud-mode" }), { + virtual: true, +}); +jest.mock("components/Badge/PreviewBadge", () => () => null, { virtual: true }); +jest.mock("media/icons/cloud-03.svg", () => () => null, { virtual: true }); +jest.mock("media/icons/layers-three-01.svg", () => () => null, { + virtual: true, +}); + +import { generateNavConfig } from "../../src/components/Layout/Header/HeaderNavConfigData"; +import { getSelectedNavItem } from "../../src/components/Layout/Header/getSelectedNavItem"; +import { CloudPlan, TOCNamespace } from "../../src/shared/interface"; + +describe("Filesystem product menu", () => { + it("appears immediately after Lake and links to English documentation", () => { + const nav = generateNavConfig( + (key) => key, + CloudPlan.Starter, + "prod", + "zh" + ); + const product = nav[0]; + if (product.type !== "group") throw new Error("Missing Product menu"); + const cloud = product.children[0]; + if (cloud.type !== "group") throw new Error("Missing Cloud products"); + const lakeIndex = cloud.children.findIndex( + (item) => item.type === "item" && item.to === "/tidbcloudlake" + ); + expect(lakeIndex).toBeGreaterThanOrEqual(0); + const filesystem = cloud.children[lakeIndex + 1]; + expect(filesystem).toMatchObject({ + type: "item", + label: "navbar.tidbCloudFilesystem", + to: "/tidbcloudfs", + isI18n: false, + }); + if (filesystem.type !== "item") throw new Error("Missing Filesystem item"); + expect(filesystem.endIcon).toBeTruthy(); + expect(getSelectedNavItem(nav, TOCNamespace.TiDBCloudFilesystem)).toBe( + filesystem + ); + expect(filesystem.onClick).toBeUndefined(); + }); + + it("does not add Filesystem to the archived documentation site", () => { + const nav = generateNavConfig((key) => key, null, "archive", "en"); + expect( + getSelectedNavItem(nav, TOCNamespace.TiDBCloudFilesystem) + ).toBeNull(); + }); +}); diff --git a/gatsby/__tests__/filesystem-routing.test.ts b/gatsby/__tests__/filesystem-routing.test.ts new file mode 100644 index 00000000..f887e81c --- /dev/null +++ b/gatsby/__tests__/filesystem-routing.test.ts @@ -0,0 +1,129 @@ +import CONFIG from "../../docs/docs.json"; +import { TOCNamespace, TOCNamespaceSlugMap } from "../../src/shared/interface"; +import { resolveMarkdownLink } from "../link-resolver"; +import { generateConfig, generateNavTOCPath } from "../path"; +import { mdxAstToToc } from "../toc"; +import { filterNodesByToc, getFilesFromTocs } from "../toc-filter"; +import { getTOCNamespace } from "../toc-namespace"; +import { calculateFileUrl } from "../url-resolver"; + +const source = `en/tidb/${CONFIG.docs.tidb.stable}`; +const tocSlug = `${source}/TOC-tidb-cloud-filesystem`; +const tocAST = [ + { + type: "list", + ordered: false, + children: [ + ["Introduction", "/tidb-cloud-filesystem/_index.md"], + ["Quick Start", "/tidb-cloud-filesystem/filesystem-quick-start.md"], + ["CLI Commands", "/ai/ti/reference/ti-filesystem.md"], + ].map(([label, url]) => ({ + type: "listItem", + children: [ + { + type: "paragraph", + children: [ + { type: "link", url, children: [{ type: "text", value: label }] }, + ], + }, + ], + })), + }, +]; + +describe("Filesystem product routing", () => { + it.each([ + ["_index", "/tidbcloudfs"], + ["filesystem-quick-start", "/tidbcloudfs/filesystem-quick-start"], + ["guides/filesystem-mount", "/tidbcloudfs/filesystem-mount"], + ])("publishes %s with its own namespace and TOC", (name, expected) => { + const slug = `${source}/tidb-cloud-filesystem/${name}`; + expect(calculateFileUrl(slug, true)).toBe(expected); + const namespace = getTOCNamespace(slug); + expect(namespace).toBe(TOCNamespace.TiDBCloudFilesystem); + expect( + generateNavTOCPath( + generateConfig(slug).config, + TOCNamespaceSlugMap[namespace!] + ) + ).toBe(tocSlug); + }); + + it("does not take over other TiDB versions or AI documentation", () => { + expect( + getTOCNamespace("en/tidb/master/tidb-cloud-filesystem/filesystem-mount") + ).toBe(TOCNamespace.TiDB); + expect( + calculateFileUrl( + "en/tidb/master/tidb-cloud-filesystem/filesystem-mount", + true + ) + ).toBe("/tidb/dev/filesystem-mount"); + expect(getTOCNamespace(`${source}/ai/ti/reference/ti-filesystem`)).toBe( + TOCNamespace.AI + ); + expect( + calculateFileUrl(`${source}/ai/ti/reference/ti-filesystem`, true) + ).toBe("/ai/ti-filesystem"); + }); + + it.each([ + ["/tidb-cloud-filesystem/_index", "/ai", "/tidbcloudfs"], + [ + "/tidb-cloud-filesystem/filesystem-mount#finish-safely", + "/ai/ti-quick-start", + "/tidbcloudfs/filesystem-mount#finish-safely", + ], + [ + "/tidb-cloud-filesystem/filesystem-mount", + "/zh/ai", + "/tidbcloudfs/filesystem-mount", + ], + ["/ai/ti/reference/ti-filesystem", "/tidbcloudfs", "/ai/ti-filesystem"], + [ + "/tidb-cloud/manage-api-keys", + "/tidbcloudfs", + "/tidbcloud/manage-api-keys", + ], + [ + "filesystem-mount#finish-safely", + "/tidbcloudfs/filesystem-quick-start", + "/tidbcloudfs/filesystem-mount#finish-safely", + ], + ])("resolves %s from %s", (link, current, expected) => { + expect(resolveMarkdownLink(link, current)).toBe(expected); + }); + + it("builds pages from the Filesystem TOC and keeps cross-product links", async () => { + const nav = mdxAstToToc(tocAST as any, tocSlug); + expect(nav.map((item) => item.link)).toEqual([ + "/tidbcloudfs", + "/tidbcloudfs/filesystem-quick-start", + "/ai/ti-filesystem", + ]); + const graphql = jest.fn().mockResolvedValue({ + data: { + allMdx: { + nodes: [ + { + id: "filesystem-toc", + slug: tocSlug, + mdxAST: { children: tocAST }, + parent: { relativePath: `${tocSlug}.md` }, + }, + ], + }, + }, + }); + const { tocFilesMap, tocNamesByFileMap } = await getFilesFromTocs(graphql); + const nodes = ["filesystem-quick-start", "unlisted-page"].map((name) => { + const slug = `${source}/tidb-cloud-filesystem/${name}`; + return { name, slug, pathConfig: generateConfig(slug).config }; + }); + const included = filterNodesByToc(nodes, tocFilesMap, tocNamesByFileMap); + expect(included.map((node) => node.name)).toEqual([ + "filesystem-quick-start", + ]); + expect(included[0].tocNames).toEqual(["TOC-tidb-cloud-filesystem"]); + }); +}); diff --git a/gatsby/link-resolver/config.ts b/gatsby/link-resolver/config.ts index e89ba417..15184afc 100644 --- a/gatsby/link-resolver/config.ts +++ b/gatsby/link-resolver/config.ts @@ -11,6 +11,15 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { languages: ["en", "zh", "ja"], linkMappings: [ + // Filesystem documentation is currently published in English only. + { + linkPattern: "/tidb-cloud-filesystem/{...folders}/_index", + targetPattern: "/tidbcloudfs/{folders}", + }, + { + linkPattern: "/tidb-cloud-filesystem/{...folders}/{docname}", + targetPattern: "/tidbcloudfs/{docname}", + }, { linkPattern: "/releases/_index", targetPattern: "/{curLang}/releases/tidb-self-managed", @@ -109,6 +118,13 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { linkPattern: "/{...any}/{docname}", targetPattern: "/{lang}/tidbcloudlake/{docname}", }, + // Relative Filesystem links stay in the product namespace. Explicit AI + // and other namespace links are handled by the rules above. + { + pathPattern: "/{lang}/tidbcloudfs/{...any}", + linkPattern: "/{...folders}/{docname}", + targetPattern: "/tidbcloudfs/{docname}", + }, // Rule 4: developer, best-practices, api, ai namespace in tidb folder // Current page: /{lang}/{namespace}/{...any} // Link: /{...any}/{docname} -> /{lang}/{namespace}/{docname} diff --git a/gatsby/toc-namespace/index.ts b/gatsby/toc-namespace/index.ts index 31fdcec4..5e4b67c4 100644 --- a/gatsby/toc-namespace/index.ts +++ b/gatsby/toc-namespace/index.ts @@ -24,6 +24,13 @@ export interface NamespaceRule { * Add new rules here to extend namespace matching logic */ const SHARED_NAMESPACE_RULES: NamespaceRule[] = [ + { + namespace: TOCNamespace.TiDBCloudFilesystem, + repo: Repo.tidb, + branch: CONFIG.docs.tidb.stable, + folder: "tidb-cloud-filesystem", + minRestLength: 1, + }, { namespace: TOCNamespace.AI, repo: Repo.tidb, diff --git a/gatsby/url-resolver/config.ts b/gatsby/url-resolver/config.ts index 2b8e12d7..18acec3f 100644 --- a/gatsby/url-resolver/config.ts +++ b/gatsby/url-resolver/config.ts @@ -14,6 +14,18 @@ export const defaultUrlResolverConfig: UrlResolverConfig = { trailingSlash: "never", pathMappings: [ + // Filesystem has its own product URL but shares the stable docs source. + { + sourcePattern: `/{lang}/tidb/${CONFIG.docs.tidb.stable}/tidb-cloud-filesystem/{...folders}/{filename}`, + targetPattern: "/{lang}/tidbcloudfs/{filename}", + filenameTransform: { + ignoreIf: ["_index"], + conditionalTarget: { + keepIf: ["_index"], + keepTargetPattern: "/{lang}/tidbcloudfs/{folders}", + }, + }, + }, // tidbcloud dedicated _index // /en/tidbcloud/master/tidb-cloud/dedicated/_index.md -> /en/tidbcloud/dedicated/ { diff --git a/locale/en/translation.json b/locale/en/translation.json index 2cf23afd..83e9e686 100644 --- a/locale/en/translation.json +++ b/locale/en/translation.json @@ -36,6 +36,7 @@ "tidbCloudPremium": "TiDB Cloud Premium", "tidbCloudDedicated": "TiDB Cloud Dedicated", "tidbCloudLake": "TiDB Cloud Lake", + "tidbCloudFilesystem": "TiDB Cloud Filesystem", "tidbShortTerm": "TiDB", "tidbOnKubernetes": "TiDB on Kubernetes", "tidbCloudReleases": "TiDB Cloud Releases", diff --git a/locale/ja/translation.json b/locale/ja/translation.json index f91b5f4f..2afa7c33 100644 --- a/locale/ja/translation.json +++ b/locale/ja/translation.json @@ -36,6 +36,7 @@ "tidbCloudPremium": "TiDB Cloud Premium", "tidbCloudDedicated": "TiDB Cloud Dedicated", "tidbCloudLake": "TiDB Cloud Lake", + "tidbCloudFilesystem": "TiDB Cloud Filesystem", "tidbShortTerm": "TiDB", "tidbOnKubernetes": "TiDB on Kubernetes", "tidbCloudReleases": "TiDB Cloud リリース", diff --git a/locale/zh/translation.json b/locale/zh/translation.json index 684466e6..356506c9 100644 --- a/locale/zh/translation.json +++ b/locale/zh/translation.json @@ -34,6 +34,7 @@ "tidbCloudPremium": "TiDB Cloud Premium", "tidbCloudDedicated": "TiDB Cloud Dedicated", "tidbCloudLake": "TiDB Cloud Lake", + "tidbCloudFilesystem": "TiDB Cloud Filesystem", "tidbShortTerm": "TiDB", "tidbOnKubernetes": "TiDB on Kubernetes", "tidbCloudReleases": "TiDB Cloud 发布记录", diff --git a/src/components/Layout/Header/HeaderNavConfigData.tsx b/src/components/Layout/Header/HeaderNavConfigData.tsx index 6a0b1e32..2a9267d2 100644 --- a/src/components/Layout/Header/HeaderNavConfigData.tsx +++ b/src/components/Layout/Header/HeaderNavConfigData.tsx @@ -93,6 +93,15 @@ const getDefaultNavConfig = ( } }, }, + { + type: "item", + label: t("navbar.tidbCloudFilesystem"), + endIcon: , + to: "/tidbcloudfs", + isI18n: false, + selected: (namespace) => + namespace === TOCNamespace.TiDBCloudFilesystem, + }, ], }, { diff --git a/src/shared/interface.ts b/src/shared/interface.ts index 5f6171b2..92728d49 100644 --- a/src/shared/interface.ts +++ b/src/shared/interface.ts @@ -17,6 +17,7 @@ export enum TOCNamespace { TiDB = "tidb", TiDBCloud = "tidb-cloud", TiDBCloudLake = "tidb-cloud-lake", + TiDBCloudFilesystem = "tidb-cloud-filesystem", TiDBInKubernetes = "tidb-in-kubernetes", AI = "ai", Develop = "develop", @@ -35,6 +36,7 @@ export const TOCNamespaceSlugMap: Record = { [TOCNamespace.TiDB]: "", [TOCNamespace.TiDBCloud]: "", [TOCNamespace.TiDBCloudLake]: "tidb-cloud-lake", + [TOCNamespace.TiDBCloudFilesystem]: "tidb-cloud-filesystem", [TOCNamespace.TiDBInKubernetes]: "", [TOCNamespace.AI]: "ai", [TOCNamespace.Develop]: "develop", From 4e9da18f7be8154169326ec8f21cc050ff549b84 Mon Sep 17 00:00:00 2001 From: Cheese Date: Tue, 15 Sep 2026 23:22:00 -0400 Subject: [PATCH 2/3] fix: use tidbcloud-filesystem documentation URLs --- README.md | 2 +- gatsby-config.js | 2 +- gatsby/__tests__/filesystem-header.test.ts | 2 +- gatsby/__tests__/filesystem-routing.test.ts | 28 +++++++++++-------- gatsby/link-resolver/config.ts | 8 +++--- gatsby/url-resolver/config.ts | 4 +-- .../Layout/Header/HeaderNavConfigData.tsx | 2 +- 7 files changed, 26 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 34574283..ec758597 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Put them in `.env.development` to make them take effect. (Ref: { expect(filesystem).toMatchObject({ type: "item", label: "navbar.tidbCloudFilesystem", - to: "/tidbcloudfs", + to: "/tidbcloud-filesystem", isI18n: false, }); if (filesystem.type !== "item") throw new Error("Missing Filesystem item"); diff --git a/gatsby/__tests__/filesystem-routing.test.ts b/gatsby/__tests__/filesystem-routing.test.ts index f887e81c..9222ff1f 100644 --- a/gatsby/__tests__/filesystem-routing.test.ts +++ b/gatsby/__tests__/filesystem-routing.test.ts @@ -33,9 +33,9 @@ const tocAST = [ describe("Filesystem product routing", () => { it.each([ - ["_index", "/tidbcloudfs"], - ["filesystem-quick-start", "/tidbcloudfs/filesystem-quick-start"], - ["guides/filesystem-mount", "/tidbcloudfs/filesystem-mount"], + ["_index", "/tidbcloud-filesystem"], + ["filesystem-quick-start", "/tidbcloud-filesystem/filesystem-quick-start"], + ["guides/filesystem-mount", "/tidbcloud-filesystem/filesystem-mount"], ])("publishes %s with its own namespace and TOC", (name, expected) => { const slug = `${source}/tidb-cloud-filesystem/${name}`; expect(calculateFileUrl(slug, true)).toBe(expected); @@ -68,27 +68,31 @@ describe("Filesystem product routing", () => { }); it.each([ - ["/tidb-cloud-filesystem/_index", "/ai", "/tidbcloudfs"], + ["/tidb-cloud-filesystem/_index", "/ai", "/tidbcloud-filesystem"], [ "/tidb-cloud-filesystem/filesystem-mount#finish-safely", "/ai/ti-quick-start", - "/tidbcloudfs/filesystem-mount#finish-safely", + "/tidbcloud-filesystem/filesystem-mount#finish-safely", ], [ "/tidb-cloud-filesystem/filesystem-mount", "/zh/ai", - "/tidbcloudfs/filesystem-mount", + "/tidbcloud-filesystem/filesystem-mount", + ], + [ + "/ai/ti/reference/ti-filesystem", + "/tidbcloud-filesystem", + "/ai/ti-filesystem", ], - ["/ai/ti/reference/ti-filesystem", "/tidbcloudfs", "/ai/ti-filesystem"], [ "/tidb-cloud/manage-api-keys", - "/tidbcloudfs", + "/tidbcloud-filesystem", "/tidbcloud/manage-api-keys", ], [ "filesystem-mount#finish-safely", - "/tidbcloudfs/filesystem-quick-start", - "/tidbcloudfs/filesystem-mount#finish-safely", + "/tidbcloud-filesystem/filesystem-quick-start", + "/tidbcloud-filesystem/filesystem-mount#finish-safely", ], ])("resolves %s from %s", (link, current, expected) => { expect(resolveMarkdownLink(link, current)).toBe(expected); @@ -97,8 +101,8 @@ describe("Filesystem product routing", () => { it("builds pages from the Filesystem TOC and keeps cross-product links", async () => { const nav = mdxAstToToc(tocAST as any, tocSlug); expect(nav.map((item) => item.link)).toEqual([ - "/tidbcloudfs", - "/tidbcloudfs/filesystem-quick-start", + "/tidbcloud-filesystem", + "/tidbcloud-filesystem/filesystem-quick-start", "/ai/ti-filesystem", ]); const graphql = jest.fn().mockResolvedValue({ diff --git a/gatsby/link-resolver/config.ts b/gatsby/link-resolver/config.ts index 15184afc..c1c4f21f 100644 --- a/gatsby/link-resolver/config.ts +++ b/gatsby/link-resolver/config.ts @@ -14,11 +14,11 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { // Filesystem documentation is currently published in English only. { linkPattern: "/tidb-cloud-filesystem/{...folders}/_index", - targetPattern: "/tidbcloudfs/{folders}", + targetPattern: "/tidbcloud-filesystem/{folders}", }, { linkPattern: "/tidb-cloud-filesystem/{...folders}/{docname}", - targetPattern: "/tidbcloudfs/{docname}", + targetPattern: "/tidbcloud-filesystem/{docname}", }, { linkPattern: "/releases/_index", @@ -121,9 +121,9 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { // Relative Filesystem links stay in the product namespace. Explicit AI // and other namespace links are handled by the rules above. { - pathPattern: "/{lang}/tidbcloudfs/{...any}", + pathPattern: "/{lang}/tidbcloud-filesystem/{...any}", linkPattern: "/{...folders}/{docname}", - targetPattern: "/tidbcloudfs/{docname}", + targetPattern: "/tidbcloud-filesystem/{docname}", }, // Rule 4: developer, best-practices, api, ai namespace in tidb folder // Current page: /{lang}/{namespace}/{...any} diff --git a/gatsby/url-resolver/config.ts b/gatsby/url-resolver/config.ts index 18acec3f..865f19c3 100644 --- a/gatsby/url-resolver/config.ts +++ b/gatsby/url-resolver/config.ts @@ -17,12 +17,12 @@ export const defaultUrlResolverConfig: UrlResolverConfig = { // Filesystem has its own product URL but shares the stable docs source. { sourcePattern: `/{lang}/tidb/${CONFIG.docs.tidb.stable}/tidb-cloud-filesystem/{...folders}/{filename}`, - targetPattern: "/{lang}/tidbcloudfs/{filename}", + targetPattern: "/{lang}/tidbcloud-filesystem/{filename}", filenameTransform: { ignoreIf: ["_index"], conditionalTarget: { keepIf: ["_index"], - keepTargetPattern: "/{lang}/tidbcloudfs/{folders}", + keepTargetPattern: "/{lang}/tidbcloud-filesystem/{folders}", }, }, }, diff --git a/src/components/Layout/Header/HeaderNavConfigData.tsx b/src/components/Layout/Header/HeaderNavConfigData.tsx index 2a9267d2..ade5916d 100644 --- a/src/components/Layout/Header/HeaderNavConfigData.tsx +++ b/src/components/Layout/Header/HeaderNavConfigData.tsx @@ -97,7 +97,7 @@ const getDefaultNavConfig = ( type: "item", label: t("navbar.tidbCloudFilesystem"), endIcon: , - to: "/tidbcloudfs", + to: "/tidbcloud-filesystem", isI18n: false, selected: (namespace) => namespace === TOCNamespace.TiDBCloudFilesystem, From a91e25d11a43ac410398c14481d12490caf14e45 Mon Sep 17 00:00:00 2001 From: qiancai Date: Wed, 16 Sep 2026 12:31:02 +0800 Subject: [PATCH 3/3] fix(filesystem): align routing with staging source Treat TiDB Cloud Filesystem as an independent docs-staging repo and map its master content to the product URL namespace. Keep English-only link behavior, dedicated TOC selection, header translation targets, architecture docs, and regression coverage aligned. --- README.md | 18 +- gatsby/URL_MAPPING_ARCHITECTURE.md | 192 +++++++++++++++--- gatsby/__tests__/filesystem-routing.test.ts | 16 +- gatsby/__tests__/toc-namespace.test.ts | 14 ++ .../__tests__/link-resolver.test.ts | 28 +++ gatsby/link-resolver/config.ts | 17 +- gatsby/path/index.ts | 24 ++- gatsby/toc-namespace/index.ts | 5 +- .../__tests__/url-resolver.test.ts | 40 ++++ gatsby/url-resolver/config.ts | 18 +- src/components/Layout/Header/index.tsx | 2 + src/shared/interface.ts | 1 + src/shared/utils/index.ts | 25 ++- 13 files changed, 333 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index ec758597..b0138ff4 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,17 @@ TiDB Cloud Filesystem appears after TiDB Cloud Lake in the Product menu. Its English documentation is published at `/tidbcloud-filesystem/` with its own sidebar and Preview badge. -The content comes from `pingcap/docs` on the stable TiDB documentation branch -(`release-8.5` in `docs/docs.json`), using `tidb-cloud-filesystem/` and -`TOC-tidb-cloud-filesystem.md`. It shares the existing `tidb` staging source; -it does not require a separate repository entry in `docs/docs.json`. The -source pages must reach `docs-staging` before a website deployment can publish -them. The menu links to English without changing the selected Cloud database -plan or implying that translated Filesystem pages exist. +The source content comes from `pingcap/docs` and is published by +`pingcap/docs-staging` under +`markdown-pages/en/tidb-cloud-filesystem/master/`. The staging tree contains +`tidb-cloud-filesystem/` and `TOC-tidb-cloud-filesystem.md`, and requires a +separate `tidb-cloud-filesystem` entry in the `pingcap/docs-staging` +`docs.json`, similar to TiDB Cloud Lake. After the staging submodule is +updated, that file is available in this checkout as `docs/docs.json`. The +source pages and staging configuration must reach `docs-staging` before a +website deployment can publish them. The menu links to English without +changing the selected Cloud database plan or implying that translated +Filesystem pages exist. `gatsby/__tests__/filesystem-routing.test.ts` checks published URLs, sidebar selection, TOC membership, and links to the existing AI command reference. diff --git a/gatsby/URL_MAPPING_ARCHITECTURE.md b/gatsby/URL_MAPPING_ARCHITECTURE.md index 421e91f7..aaf2bbe4 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` @@ -172,7 +189,37 @@ The following sections describe the effects of each configuration rule in order Rules are evaluated in order; the first matching rule wins. -### Rule 1: TiDBCloud Dedicated Index +### Rule 1: TiDB Cloud Filesystem Namespace + +**Effect**: Maps TiDB Cloud Filesystem pages from their independent staging tree to the English `/tidbcloud-filesystem` namespace. + +**Source Patterns**: + +- `/{lang}/tidb-cloud-filesystem/{branch}/tidb-cloud-filesystem/{...folders}/{filename}` +- `/{lang}/tidb-cloud-filesystem/{branch}/{...folders}/{filename}` + +**Target Pattern**: + +- For `_index`: `/{lang}/tidbcloud-filesystem/{folders}` (keeps folder structure) +- For other files: `/{lang}/tidbcloud-filesystem/{filename}` (flattens folder structure) + +**Filename Transform**: + +- `ignoreIf: ["_index"]` +- `conditionalTarget.keepIf: ["_index"]` + +**Example**: + +- Source: `en/tidb-cloud-filesystem/master/tidb-cloud-filesystem/_index.md` +- Target: `/tidbcloud-filesystem` +- Source: `en/tidb-cloud-filesystem/master/tidb-cloud-filesystem/filesystem-quick-start.md` +- Target: `/tidbcloud-filesystem/filesystem-quick-start` + +**Use Case**: TiDB Cloud Filesystem is sourced from its own English-only docs tree while using a product URL that omits the hyphen between `tidb` and `cloud`. + +--- + +### Rule 2: TiDBCloud Dedicated Index **Effect**: Maps TiDBCloud dedicated `_index.md` files to the TiDBCloud root URL. @@ -183,6 +230,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) @@ -190,7 +238,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 2: TiDBCloud Releases Index +### Rule 3: TiDBCloud Releases Index **Effect**: Maps TiDBCloud releases `_index.md` to the releases namespace. @@ -201,6 +249,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` @@ -208,7 +257,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 3: TiDB Releases Index (Stable) +### Rule 4: TiDB Releases Index (Stable) **Effect**: Maps the stable TiDB releases `_index.md` file to the shared releases namespace. @@ -219,6 +268,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` @@ -226,7 +276,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 4: TiDB-in-Kubernetes Releases Index +### Rule 5: TiDB-in-Kubernetes Releases Index **Effect**: Maps TiDB-in-Kubernetes releases `_index.md` to the releases namespace. @@ -237,6 +287,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` @@ -244,21 +295,24 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 5: TiDBCloud with Prefix +### Rule 6: TiDBCloud with Prefix **Effect**: Maps TiDBCloud pages with prefixes (dedicated, starter, essential) to TiDBCloud URLs. **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` @@ -268,23 +322,26 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 6: Developer Namespace +### Rule 7: Developer Namespace **Effect**: Maps stable TiDB pages under the `develop` folder (published as `developer`) to the shared `/developer` namespace. **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` @@ -294,23 +351,26 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 7: Best-Practices/API/AI Namespace +### Rule 8: Best-Practices/API/AI Namespace **Effect**: Maps stable TiDB pages under `best-practices`, `api`, and `ai` to their corresponding shared namespaces. **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` @@ -320,21 +380,24 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 8: TiDB Cloud Lake Namespace +### Rule 9: TiDB Cloud Lake Namespace **Effect**: Maps TiDB Cloud Lake pages to the `/tidbcloudlake` namespace. **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` @@ -346,7 +409,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 9: TiDB Index Pages with Folders +### Rule 10: TiDB Index Pages with Folders **Effect**: Maps TiDB `_index.md` pages to URLs that keep their folder path, preventing multiple `_index.md` files from collapsing to the same `/tidb/{branch}` URL. @@ -357,6 +420,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` @@ -366,9 +430,9 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 10: TiDB with Branch Alias +### Rule 11: 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 +441,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,7 +457,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 11: TiDB-in-Kubernetes Release Notes from Main +### Rule 12: 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. @@ -400,6 +466,7 @@ 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` @@ -409,9 +476,9 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 12: TiDB-in-Kubernetes with Branch Alias +### Rule 13: 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}` @@ -420,11 +487,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` @@ -434,7 +503,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 13: Fallback Rule +### Rule 14: Fallback Rule **Effect**: Generic fallback for any remaining paths. @@ -445,6 +514,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` @@ -456,7 +526,34 @@ Rules are evaluated in order; the first matching rule wins. Rules are evaluated in order; the first matching rule wins. -### Rule 1: Releases Index Links +### Rule 1: TiDB Cloud Filesystem Links (Direct Mapping) + +**Effect**: Resolves Filesystem source links to the English `/tidbcloud-filesystem` namespace, regardless of the current page language. + +**Link Patterns**: + +- `/tidb-cloud-filesystem/{...folders}/_index` +- `/tidb-cloud-filesystem/{...folders}/{docname}` + +**Target Patterns**: + +- `/tidbcloud-filesystem/{folders}` for `_index` +- `/tidbcloud-filesystem/{docname}` for article pages + +**Example**: + +- Link: `/tidb-cloud-filesystem/_index` +- Current Page: `/zh/tidb/stable/overview` +- Result: `/tidbcloud-filesystem` +- Link: `/tidb-cloud-filesystem/guides/filesystem-mount` +- Current Page: Any page +- Result: `/tidbcloud-filesystem/filesystem-mount` + +**Use Case**: Filesystem is currently published in English only, so cross-product links must not inherit `/zh` or `/ja`. + +--- + +### Rule 2: Releases Index Links **Effect**: Resolves `/releases/_index` links to TiDB self-managed releases page. @@ -465,6 +562,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) @@ -473,7 +571,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 2: TiDB Cloud Releases Index Links +### Rule 3: TiDB Cloud Releases Index Links **Effect**: Resolves `/tidb-cloud/releases/_index` links to TiDB Cloud releases page. @@ -482,6 +580,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` @@ -490,7 +589,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 3: TiDB-in-Kubernetes Releases Index Links (Path-Based) +### Rule 4: TiDB-in-Kubernetes Releases Index Links (Path-Based) **Effect**: Resolves `/tidb-in-kubernetes/releases/_index` links from TiDB-in-Kubernetes pages. @@ -501,6 +600,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` @@ -509,7 +609,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 4: Links from TiDB Releases Landing Page (Path-Based) +### Rule 5: Links from TiDB Releases Landing Page (Path-Based) **Effect**: Resolves `/releases/*` links from the releases landing page to TiDB stable branch URLs. @@ -520,6 +620,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) @@ -528,7 +629,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 5: Links from TiDB Operator Releases Landing Page (Path-Based, /releases/*) +### Rule 6: 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. @@ -539,6 +640,7 @@ 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) @@ -547,7 +649,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 6: TiDB-in-Kubernetes Main TOC Release Links (Path-Based) +### Rule 7: 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. @@ -558,6 +660,7 @@ Rules are evaluated in order; the first matching rule wins. **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` @@ -566,7 +669,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 7: Namespace Index Links (Direct Mapping) +### Rule 8: 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,11 +680,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` @@ -596,7 +701,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 8: Namespace Links (Direct Mapping) +### Rule 9: 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`). @@ -607,11 +712,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` @@ -623,7 +730,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 9: TiDBCloud Page Links (Path-Based) +### Rule 10: TiDBCloud Page Links (Path-Based) **Effect**: Resolves relative links from TiDBCloud pages to TiDBCloud URLs. @@ -634,6 +741,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` @@ -645,7 +753,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 10: TiDB Cloud Lake Page Links (Path-Based) +### Rule 11: TiDB Cloud Lake Page Links (Path-Based) **Effect**: Resolves relative links from TiDB Cloud Lake pages to `/tidbcloudlake/*` URLs. @@ -656,6 +764,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` @@ -664,7 +773,27 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 11: Developer/Best-Practices/API/AI Namespace Page Links (Path-Based) +### Rule 12: TiDB Cloud Filesystem Page Links (Path-Based) + +**Effect**: Resolves relative links from Filesystem pages to English `/tidbcloud-filesystem/*` URLs. + +**Path Pattern**: `/{lang}/tidbcloud-filesystem/{...any}` + +**Link Pattern**: `/{...folders}/{docname}` + +**Target Pattern**: `/tidbcloud-filesystem/{docname}` + +**Example**: + +- Current Page: `/tidbcloud-filesystem/filesystem-quick-start` +- Link: `/guides/filesystem-mount` +- Result: `/tidbcloud-filesystem/filesystem-mount` + +**Use Case**: Relative Filesystem links stay in the English product namespace; explicit namespace links such as `/ai/*` are handled by earlier direct rules. + +--- + +### Rule 13: Developer/Best-Practices/API/AI Namespace Page Links (Path-Based) **Effect**: Resolves relative links from namespace pages to TiDB stable branch URLs. @@ -677,6 +806,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` @@ -688,7 +818,7 @@ Rules are evaluated in order; the first matching rule wins. --- -### Rule 12: TiDB/TiDB-in-Kubernetes Page Links (Path-Based) +### Rule 14: TiDB/TiDB-in-Kubernetes Page Links (Path-Based) **Effect**: Resolves relative links from TiDB or TiDB-in-Kubernetes pages, preserving branch/version. @@ -697,10 +827,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` @@ -724,7 +856,7 @@ The URL mapping system provides: 1. **Consistent URL Structure**: Source files are mapped to clean, SEO-friendly URLs 2. **Context-Aware Link Resolution**: Links are resolved based on the current page's context -3. **Namespace Support**: Special namespaces (`developer`, `best-practices`, `api`, `ai`, `tidbcloudlake`) have their own URL structure +3. **Namespace Support**: Special namespaces (`developer`, `best-practices`, `api`, `ai`, `tidbcloudlake`, `tidbcloud-filesystem`) have their own URL structure 4. **Branch Aliasing**: Internal branch names are transformed to user-friendly versions 5. **Default Language Omission**: Default language (`en`) is omitted from URLs for cleaner paths 6. **TOC-Driven Build**: Only files referenced in TOC files are built, reducing build size diff --git a/gatsby/__tests__/filesystem-routing.test.ts b/gatsby/__tests__/filesystem-routing.test.ts index 9222ff1f..413da0e6 100644 --- a/gatsby/__tests__/filesystem-routing.test.ts +++ b/gatsby/__tests__/filesystem-routing.test.ts @@ -7,7 +7,8 @@ import { filterNodesByToc, getFilesFromTocs } from "../toc-filter"; import { getTOCNamespace } from "../toc-namespace"; import { calculateFileUrl } from "../url-resolver"; -const source = `en/tidb/${CONFIG.docs.tidb.stable}`; +const source = "en/tidb-cloud-filesystem/master"; +const stableTidbSource = `en/tidb/${CONFIG.docs.tidb.stable}`; const tocSlug = `${source}/TOC-tidb-cloud-filesystem`; const tocAST = [ { @@ -49,7 +50,7 @@ describe("Filesystem product routing", () => { ).toBe(tocSlug); }); - it("does not take over other TiDB versions or AI documentation", () => { + it("does not take over TiDB or AI documentation", () => { expect( getTOCNamespace("en/tidb/master/tidb-cloud-filesystem/filesystem-mount") ).toBe(TOCNamespace.TiDB); @@ -59,11 +60,14 @@ describe("Filesystem product routing", () => { true ) ).toBe("/tidb/dev/filesystem-mount"); - expect(getTOCNamespace(`${source}/ai/ti/reference/ti-filesystem`)).toBe( - TOCNamespace.AI - ); expect( - calculateFileUrl(`${source}/ai/ti/reference/ti-filesystem`, true) + getTOCNamespace(`${stableTidbSource}/ai/ti/reference/ti-filesystem`) + ).toBe(TOCNamespace.AI); + expect( + calculateFileUrl( + `${stableTidbSource}/ai/ti/reference/ti-filesystem`, + true + ) ).toBe("/ai/ti-filesystem"); }); diff --git a/gatsby/__tests__/toc-namespace.test.ts b/gatsby/__tests__/toc-namespace.test.ts index 02da2f16..bd447439 100644 --- a/gatsby/__tests__/toc-namespace.test.ts +++ b/gatsby/__tests__/toc-namespace.test.ts @@ -20,6 +20,20 @@ describe("getTOCNamespace", () => { ); }); + it("maps TiDB Cloud Filesystem docs to the Filesystem namespace", () => { + expect( + getTOCNamespace( + "en/tidb-cloud-filesystem/master/tidb-cloud-filesystem/filesystem-quick-start" + ) + ).toBe(TOCNamespace.TiDBCloudFilesystem); + }); + + it("maps the root-level Filesystem index to the Filesystem namespace", () => { + expect(getTOCNamespace("en/tidb-cloud-filesystem/master/_index")).toBe( + TOCNamespace.TiDBCloudFilesystem + ); + }); + it("keeps other TiDB stable docs in the TiDB namespace", () => { expect(getTOCNamespace("en/tidb/release-8.5/alert-rules")).toBe( TOCNamespace.TiDB diff --git a/gatsby/link-resolver/__tests__/link-resolver.test.ts b/gatsby/link-resolver/__tests__/link-resolver.test.ts index 62732b2f..66003df8 100644 --- a/gatsby/link-resolver/__tests__/link-resolver.test.ts +++ b/gatsby/link-resolver/__tests__/link-resolver.test.ts @@ -182,6 +182,14 @@ describe("resolveMarkdownLink", () => { expect(result).toBe("/tidbcloudlake"); }); + it("should resolve tidb-cloud-filesystem/_index links to the English filesystem root", () => { + const result = resolveMarkdownLink( + "/tidb-cloud-filesystem/_index", + "/zh/tidb/stable/alert-rules" + ); + expect(result).toBe("/tidbcloud-filesystem"); + }); + it("should resolve best-practices namespace links (en - default language omitted)", () => { const result = resolveMarkdownLink( "/best-practices/optimization/query-optimization", @@ -584,6 +592,26 @@ describe("resolveMarkdownLink", () => { }); }); + describe("linkMappingsByPath - tidbcloud-filesystem pages", () => { + it("should resolve nested relative links in the English filesystem namespace", () => { + const result = resolveMarkdownLink( + "guides/filesystem-mount#finish-safely", + "/tidbcloud-filesystem/filesystem-quick-start" + ); + expect(result).toBe( + "/tidbcloud-filesystem/filesystem-mount#finish-safely" + ); + }); + + it("should keep explicit AI links in the AI namespace", () => { + const result = resolveMarkdownLink( + "/ai/ti/reference/ti-filesystem", + "/tidbcloud-filesystem" + ); + expect(result).toBe("/ai/ti-filesystem"); + }); + }); + describe("linkMappingsByPath - tidb pages with branch", () => { it("should resolve links from tidb pages with stable branch", () => { const result = resolveMarkdownLink( diff --git a/gatsby/link-resolver/config.ts b/gatsby/link-resolver/config.ts index c1c4f21f..73d1950e 100644 --- a/gatsby/link-resolver/config.ts +++ b/gatsby/link-resolver/config.ts @@ -11,7 +11,8 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { languages: ["en", "zh", "ja"], linkMappings: [ - // Filesystem documentation is currently published in English only. + // Filesystem documentation is currently published in English only, so + // direct links intentionally omit the current page language. { linkPattern: "/tidb-cloud-filesystem/{...folders}/_index", targetPattern: "/tidbcloud-filesystem/{folders}", @@ -55,7 +56,7 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { linkPattern: "/releases/{docname}", targetPattern: "/{lang}/tidb-in-kubernetes/stable/{docname}", }, - // Rule 1: Links starting with specific namespaces (direct link mapping) + // Links starting with specific namespaces (direct link mapping) // Special handling for namespace index links: // /develop/_index -> /developer // /best-practices/_index -> /best-practices @@ -102,7 +103,7 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { develop: "developer", }, }, - // Rule 2: tidbcloud with prefix pages (path-based mapping) + // tidbcloud with prefix pages (path-based mapping) // Current page: /{lang}/tidbcloud/{...any} // Link: /{...any}/{docname} -> /{lang}/tidbcloud/{docname} { @@ -110,7 +111,7 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { linkPattern: "/{...any}/{docname}", targetPattern: "/{lang}/tidbcloud/{docname}", }, - // Rule 3: tidbcloudlake pages (path-based mapping) + // tidbcloudlake pages (path-based mapping) // Current page: /{lang}/tidbcloudlake/{...any} // Link: /{...any}/{docname} -> /{lang}/tidbcloudlake/{docname} { @@ -118,14 +119,14 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { linkPattern: "/{...any}/{docname}", targetPattern: "/{lang}/tidbcloudlake/{docname}", }, - // Relative Filesystem links stay in the product namespace. Explicit AI - // and other namespace links are handled by the rules above. + // Relative Filesystem links stay in the English product namespace. + // Explicit AI and other namespace links are handled by the rules above. { pathPattern: "/{lang}/tidbcloud-filesystem/{...any}", linkPattern: "/{...folders}/{docname}", targetPattern: "/tidbcloud-filesystem/{docname}", }, - // Rule 4: developer, best-practices, api, ai namespace in tidb folder + // developer, best-practices, api, ai namespace in tidb folder // Current page: /{lang}/{namespace}/{...any} // Link: /{...any}/{docname} -> /{lang}/{namespace}/{docname} { @@ -136,7 +137,7 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { linkPattern: "/{...any}/{docname}", targetPattern: "/{lang}/tidb/stable/{docname}", }, - // Rule 4: versioned docs with branch pages (path-based mapping) + // Versioned docs with branch pages (path-based mapping) // Current page: /{lang}/{repo}/{branch}/{...any} (branch is already aliased, e.g., "stable", "v8.5") // Link: /{...any}/{docname} -> /{lang}/{repo}/{branch}/{docname} { diff --git a/gatsby/path/index.ts b/gatsby/path/index.ts index 96ebaafb..725ae33c 100644 --- a/gatsby/path/index.ts +++ b/gatsby/path/index.ts @@ -6,6 +6,15 @@ import { } from "../../src/shared/interface"; import CONFIG from "../../docs/docs.json"; +type DocsConfigByRepo = Record< + string, + { + languages: Record; + } +>; + +const DOCS_CONFIG = CONFIG.docs as unknown as DocsConfigByRepo; + // @deprecated, use calculateFileUrl instead export function generateUrl(filename: string, config: PathConfig) { const lang = config.locale === Locale.en ? "" : `/${config.locale}`; @@ -97,15 +106,16 @@ function branchToVersion(repo: Repo, branch: string) { case Repo.tidbcloud: case Repo.tidbcloudlake: + case Repo.tidbcloudfilesystem: return null; } } -export const AllVersion = Object.keys(CONFIG.docs).reduce((acc, val) => { +export const AllVersion = Object.keys(DOCS_CONFIG).reduce((acc, val) => { const repo = val as Repo; - acc[repo] = Object.keys(CONFIG.docs[repo].languages).reduce((acc, val) => { + acc[repo] = Object.keys(DOCS_CONFIG[repo].languages).reduce((acc, val) => { const locale = val as Locale.en; - acc[locale] = CONFIG.docs[repo].languages[locale].versions.map((v) => + acc[locale] = DOCS_CONFIG[repo].languages[locale].versions.map((v) => branchToVersion(repo, v) ); return acc; @@ -114,10 +124,14 @@ export const AllVersion = Object.keys(CONFIG.docs).reduce((acc, val) => { }, {} as Record>); export function getRepo(config: PathConfig) { - const { languages } = CONFIG.docs[config.repo]; + const repoConfig = DOCS_CONFIG[config.repo]; + if (!repoConfig) { + throw new Error(`no config for repo ${config.repo}`); + } + const { languages } = repoConfig; if (config.locale in languages) { - return languages[config.locale as Locale.en].repo; + return languages[config.locale].repo; } throw new Error(`no ${config.locale} in repo ${config.repo}`); diff --git a/gatsby/toc-namespace/index.ts b/gatsby/toc-namespace/index.ts index 5e4b67c4..adfbd24b 100644 --- a/gatsby/toc-namespace/index.ts +++ b/gatsby/toc-namespace/index.ts @@ -26,10 +26,7 @@ export interface NamespaceRule { const SHARED_NAMESPACE_RULES: NamespaceRule[] = [ { namespace: TOCNamespace.TiDBCloudFilesystem, - repo: Repo.tidb, - branch: CONFIG.docs.tidb.stable, - folder: "tidb-cloud-filesystem", - minRestLength: 1, + repo: Repo.tidbcloudfilesystem, }, { namespace: TOCNamespace.AI, diff --git a/gatsby/url-resolver/__tests__/url-resolver.test.ts b/gatsby/url-resolver/__tests__/url-resolver.test.ts index 05c2b1e0..d6d702f9 100644 --- a/gatsby/url-resolver/__tests__/url-resolver.test.ts +++ b/gatsby/url-resolver/__tests__/url-resolver.test.ts @@ -322,6 +322,33 @@ describe("calculateFileUrl", () => { expect(url).toBe("/en/tidbcloudlake/dashboards/"); }); + it("should resolve tidb cloud filesystem _index", () => { + const absolutePath = path.join( + sourceBasePath, + "en/tidb-cloud-filesystem/master/tidb-cloud-filesystem/_index.md" + ); + const url = calculateFileUrlWithConfig(absolutePath, testConfig); + expect(url).toBe("/en/tidbcloud-filesystem"); + }); + + it("should resolve tidb cloud filesystem guide pages", () => { + const absolutePath = path.join( + sourceBasePath, + "en/tidb-cloud-filesystem/master/tidb-cloud-filesystem/guides/filesystem-mount.md" + ); + const url = calculateFileUrlWithConfig(absolutePath, testConfig); + expect(url).toBe("/en/tidbcloud-filesystem/filesystem-mount/"); + }); + + it("should resolve tidb cloud filesystem root _index", () => { + const absolutePath = path.join( + sourceBasePath, + "en/tidb-cloud-filesystem/master/_index.md" + ); + const url = calculateFileUrlWithConfig(absolutePath, testConfig); + expect(url).toBe("/en/tidbcloud-filesystem"); + }); + it("should resolve releases folder", () => { const absolutePath = path.join( sourceBasePath, @@ -595,6 +622,19 @@ describe("calculateFileUrl with defaultLanguage: 'en'", () => { expect(url).toBe("/tidbcloudlake/dashboards"); }); + it("should omit /en/ prefix for English tidb cloud filesystem files", () => { + const absolutePath = path.join( + sourceBasePath, + "en/tidb-cloud-filesystem/master/tidb-cloud-filesystem/filesystem-quick-start.md" + ); + const url = calculateFileUrlWithConfig( + absolutePath, + configWithDefaultLang, + true + ); + expect(url).toBe("/tidbcloud-filesystem/filesystem-quick-start"); + }); + it("should omit /en/ prefix for English release branch files", () => { const absolutePath = path.join( sourceBasePath, diff --git a/gatsby/url-resolver/config.ts b/gatsby/url-resolver/config.ts index 865f19c3..0c21aadf 100644 --- a/gatsby/url-resolver/config.ts +++ b/gatsby/url-resolver/config.ts @@ -14,9 +14,23 @@ export const defaultUrlResolverConfig: UrlResolverConfig = { trailingSlash: "never", pathMappings: [ - // Filesystem has its own product URL but shares the stable docs source. + // TiDB Cloud Filesystem is sourced from its own docs tree and published + // under the tidbcloud-filesystem namespace. { - sourcePattern: `/{lang}/tidb/${CONFIG.docs.tidb.stable}/tidb-cloud-filesystem/{...folders}/{filename}`, + sourcePattern: + "/{lang}/tidb-cloud-filesystem/{branch}/tidb-cloud-filesystem/{...folders}/{filename}", + targetPattern: "/{lang}/tidbcloud-filesystem/{filename}", + filenameTransform: { + ignoreIf: ["_index"], + conditionalTarget: { + keepIf: ["_index"], + keepTargetPattern: "/{lang}/tidbcloud-filesystem/{folders}", + }, + }, + }, + { + sourcePattern: + "/{lang}/tidb-cloud-filesystem/{branch}/{...folders}/{filename}", targetPattern: "/{lang}/tidbcloud-filesystem/{filename}", filenameTransform: { ignoreIf: ["_index"], diff --git a/src/components/Layout/Header/index.tsx b/src/components/Layout/Header/index.tsx index 4000bb81..e58d7c3a 100644 --- a/src/components/Layout/Header/index.tsx +++ b/src/components/Layout/Header/index.tsx @@ -416,6 +416,8 @@ const HeaderBanner = (props: HeaderProps) => { ? `/tidbcloud/${name}` : namespace === TOCNamespace.TiDBCloudLake ? `/tidbcloudlake/${name}` + : namespace === TOCNamespace.TiDBCloudFilesystem + ? `/tidbcloud-filesystem/${name}` : `/${props.pathConfig?.repo}/${ props.pathConfig?.version || "stable" }/${name}`; diff --git a/src/shared/interface.ts b/src/shared/interface.ts index 92728d49..e6fcde9f 100644 --- a/src/shared/interface.ts +++ b/src/shared/interface.ts @@ -53,6 +53,7 @@ export enum Repo { operator = "tidb-in-kubernetes", tidbcloud = "tidbcloud", tidbcloudlake = "tidb-cloud-lake", + tidbcloudfilesystem = "tidb-cloud-filesystem", } export enum Locale { diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts index 65e412a0..82e6c8e5 100644 --- a/src/shared/utils/index.ts +++ b/src/shared/utils/index.ts @@ -33,6 +33,15 @@ import { TiDBCloudBanner, } from "components/Icons/LearingPathIcon"; +type DocsConfigByRepo = Record< + string, + { + languages: Record; + } +>; + +const DOCS_CONFIG = CONFIG.docs as unknown as DocsConfigByRepo; + export function generateDocsHomeUrl(lang?: string) { switch (lang) { case "ja": @@ -159,10 +168,14 @@ export function calcPDFUrl(config: PathConfig) { } export function getRepoFromPathCfg(config: PathConfig) { - const { languages } = CONFIG.docs[config.repo]; + const repoConfig = DOCS_CONFIG[config.repo]; + if (!repoConfig) { + throw new Error(`no config for repo ${config.repo}`); + } + const { languages } = repoConfig; if (config.locale in languages) { - return languages[config.locale as Locale.en].repo; + return languages[config.locale].repo; } throw new Error(`no ${config.locale} in repo ${config.repo}`); @@ -199,15 +212,17 @@ function branchToVersion(repo: Repo, branch: string) { return branch.replace("release-", "v"); case Repo.tidbcloud: + case Repo.tidbcloudlake: + case Repo.tidbcloudfilesystem: return null; } } -export const AllVersion = Object.keys(CONFIG.docs).reduce((acc, val) => { +export const AllVersion = Object.keys(DOCS_CONFIG).reduce((acc, val) => { const repo = val as Repo; - acc[repo] = Object.keys(CONFIG.docs[repo].languages).reduce((acc, val) => { + acc[repo] = Object.keys(DOCS_CONFIG[repo].languages).reduce((acc, val) => { const locale = val as Locale.en; - acc[locale] = CONFIG.docs[repo].languages[locale].versions.map((v) => + acc[locale] = DOCS_CONFIG[repo].languages[locale].versions.map((v) => branchToVersion(repo, v) ); return acc;