Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/scripts/update-example-dates.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

// Regex patterns defined at top level for performance
const TITLE_PATTERN = /title="([^"]+)"/;
Expand Down Expand Up @@ -97,9 +96,8 @@ async function updateExampleDates() {
console.log("Parsing repositories from MDX file...");

// Read the current MDX file
const currentDir = path.dirname(fileURLToPath(import.meta.url));
const mdxPath = path.join(
currentDir,
import.meta.dirname,
"../../app/en/resources/examples/page.mdx"
);
let content = fs.readFileSync(mdxPath, "utf8");
Expand Down
15 changes: 15 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ function getLocaleFromPathname(pathname: string): string {
return localeMatch?.[1] || "en";
}

function getMarkdownAlternatePath(pathname: string): string {
// Handle root paths
if (pathname === "/" || pathname === "") {
return "/index.md";
}
// Remove trailing slash if present, then add .md extension
const cleanPath = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
return `${cleanPath}.md`;
}

export default async function RootLayout({
children,
}: {
Expand Down Expand Up @@ -104,6 +114,11 @@ export default async function RootLayout({
<meta content="width=device-width, initial-scale=1" name="viewport" />
<link href="https://www.googletagmanager.com" rel="preconnect" />
<link href="https://www.googletagmanager.com" rel="dns-prefetch" />
<link
href={getMarkdownAlternatePath(pathname)}
rel="alternate"
type="text/markdown"
/>
</Head>
<body>
{lang !== "en" && (
Expand Down
148 changes: 148 additions & 0 deletions tests/markdown-alternate-link.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import fg from "fast-glob";
import { describe, expect, test } from "vitest";

const TIMEOUT = 30_000;

// Regex patterns at top level for performance
const APP_PREFIX_REGEX = /^app/;
const PAGE_MDX_SUFFIX_REGEX = /\/page\.mdx$/;
const PAGE_MD_SUFFIX_REGEX = /\/page\.md$/;
const MD_SUFFIX_REGEX = /\.md$/;
const MDX_SUFFIX_REGEX = /\.mdx$/;
const LOCALE_REGEX = /^app\/([a-z]{2}(?:-[A-Z]{2})?)\//;

/**
* Converts a URL pathname to the expected markdown alternate path.
* This mirrors the logic in app/layout.tsx
*/
function getMarkdownAlternatePath(pathname: string): string {
if (pathname === "/" || pathname === "") {
return "/index.md";
}
const cleanPath = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
return `${cleanPath}.md`;
}

/**
* Converts a page.mdx file path to its URL pathname.
* e.g., "app/en/guides/quickstart/page.mdx" -> "/en/guides/quickstart"
*/
function filePathToUrlPath(filePath: string): string {
return filePath
.replace(APP_PREFIX_REGEX, "")
.replace(PAGE_MDX_SUFFIX_REGEX, "")
.replace(PAGE_MD_SUFFIX_REGEX, "");
}

/**
* Converts a markdown alternate URL to the expected page file path.
* e.g., "/en/guides/quickstart.md" -> "app/en/guides/quickstart/page.mdx"
*/
function markdownUrlToFilePath(mdUrl: string): string {
const pathWithoutMd = mdUrl.replace(MD_SUFFIX_REGEX, "");
return `app${pathWithoutMd}/page.mdx`;
}

describe("Markdown alternate link", () => {
test(
"getMarkdownAlternatePath generates correct paths",
() => {
// Test root path
expect(getMarkdownAlternatePath("/")).toBe("/index.md");
expect(getMarkdownAlternatePath("")).toBe("/index.md");

// Test normal paths
expect(getMarkdownAlternatePath("/en/home")).toBe("/en/home.md");
expect(getMarkdownAlternatePath("/en/guides/quickstart")).toBe(
"/en/guides/quickstart.md"
);

// Test paths with trailing slash
expect(getMarkdownAlternatePath("/en/home/")).toBe("/en/home.md");

// Test deep nested paths
expect(
getMarkdownAlternatePath(
"/en/guides/tool-calling/call-third-party-apis"
)
).toBe("/en/guides/tool-calling/call-third-party-apis.md");
},
TIMEOUT
);

test(
"every page has a valid markdown alternate path that maps to a real page",
async () => {
// Find all page files
const pageFiles = await fg("app/**/page.{md,mdx}");
const errors: Array<{ urlPath: string; mdPath: string; reason: string }> =
[];

for (const file of pageFiles) {
const urlPath = filePathToUrlPath(file);
const mdAlternatePath = getMarkdownAlternatePath(urlPath);
const expectedPageFile = markdownUrlToFilePath(mdAlternatePath);

// Verify the page file exists
if (!existsSync(join(process.cwd(), expectedPageFile))) {
// Also check for .md extension instead of .mdx
const mdPageFile = expectedPageFile.replace(MDX_SUFFIX_REGEX, ".md");
if (!existsSync(join(process.cwd(), mdPageFile))) {
errors.push({
urlPath,
mdPath: mdAlternatePath,
reason: `Expected page file not found: ${expectedPageFile}`,
});
}
}
}

for (const error of errors) {
console.error(
`Invalid markdown alternate: ${error.urlPath} -> ${error.mdPath} (${error.reason})`
);
}

expect(errors.length).toBe(0);
},
TIMEOUT
);

test(
"markdown alternate paths are correctly formed for all locales",
async () => {
// Find all page files and group by locale
const pageFiles = await fg("app/**/page.{md,mdx}");
const locales = new Set<string>();

for (const file of pageFiles) {
const match = file.match(LOCALE_REGEX);
if (match) {
locales.add(match[1]);
}
}

// Verify each locale has pages and they all get .md extensions
expect(locales.size).toBeGreaterThan(0);

for (const locale of locales) {
const localePages = pageFiles.filter((f) =>
f.startsWith(`app/${locale}/`)
);
expect(localePages.length).toBeGreaterThan(0);

// Verify all pages in this locale get correct .md paths
for (const page of localePages) {
const urlPath = filePathToUrlPath(page);
const mdPath = getMarkdownAlternatePath(urlPath);

expect(mdPath).toMatch(MD_SUFFIX_REGEX);
expect(mdPath).toContain(`/${locale}/`);
}
}
},
TIMEOUT
);
});