docs: fix broken links utility + uncaught broken links (#12637)

* fix broken links

* update broken links utility

* add missing payment evens

* generate llms

* fix segment link
This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-05-28 17:13:27 +03:00
1 parent 9866baa852
commit 40e73c6ea2
64 files changed
+151813 -150840

No files matched your search

@@ -18,28 +18,99 @@ import { MD_LINK_REGEX } from "./constants.js"
function getErrorMessage({
link,
file,
additionalMessage = "",
}: {
link: string
file: VFile
additionalMessage?: string
}): string {
return `Broken link found! ${link} linked in ${file.history[0]}`
return `Broken link found! ${link} linked in ${file.history[0]}${
additionalMessage ? `: ${additionalMessage}` : ""
}`
}
function checkLocalLinkExists({
link,
file,
currentPageFilePath,
rootBasePath = { default: "app" },
slugChanges = [],
}: {
link: string
file: VFile
currentPageFilePath: string
rootBasePath?: BrokenLinkCheckerOptions["rootBasePath"]
slugChanges?: SlugChange[]
}) {
let fileToCheck = link
let basePath = currentPageFilePath
if (!fileToCheck.endsWith("page.mdx")) {
// if the link doesn't end with page.mdx, add it
// find route base path
let routeBasePath = rootBasePath.overrides
? Object.entries(rootBasePath.overrides).find(([key]) =>
fileToCheck.startsWith(key)
)?.[1]
: rootBasePath.default
if (routeBasePath === undefined) {
routeBasePath = rootBasePath.default
}
fileToCheck = `${routeBasePath}${fileToCheck}/page.mdx`.replace(
/\/{2,}/g,
"/"
)
basePath = file.cwd
}
// remove leading slash if present
fileToCheck = fileToCheck.replace(/^\//, "")
// get absolute path of the URL
const linkedFilePath = path
.resolve(currentPageFilePath, link)
.replace(/#.*$/, "")
const linkedFilePath = path.resolve(basePath, fileToCheck).replace(/#.*$/, "")
// console.log(link, currentPageFilePath, basePath, fileToCheck, linkedFilePath)
// check if the file exists
if (!existsSync(linkedFilePath)) {
if (existsSync(linkedFilePath)) {
return
}
// maybe check in slugs
if (!slugChanges.length) {
throw new Error(
getErrorMessage({
link,
file,
})
)
}
checkSlugExists({
slugChanges,
pathToCheck: link,
link,
file,
baseProjectPath: file.cwd,
})
}
function checkSlugExists({
slugChanges,
pathToCheck,
link,
file,
baseProjectPath,
}: {
slugChanges: SlugChange[]
pathToCheck: string
baseProjectPath: string
link: string
file: VFile
}) {
const slugChange = slugChanges.find(
(change) => change.newSlug === pathToCheck
)
if (
!slugChange ||
!mdxPageExists(path.join(baseProjectPath, slugChange.origSlug))
) {
throw new Error(
getErrorMessage({
link,
@@ -156,18 +227,20 @@ function checkLink({
// try to remove hash
const hashIndex = link.lastIndexOf("#")
const likeWithoutHash = hashIndex !== -1 ? link.substring(0, hashIndex) : link
if (likeWithoutHash.match(/page\.mdx?$/)) {
if (likeWithoutHash.match(/page\.mdx?$/) || likeWithoutHash.startsWith("/")) {
checkLocalLinkExists({
link: likeWithoutHash,
file,
currentPageFilePath,
slugChanges: options.generatedSlugs,
rootBasePath: options.rootBasePath,
})
return
}
const parsedLink = parseCrossProjectLink(likeWithoutHash)
if (!parsedLink || !Object.hasOwn(options.crossProjects, parsedLink.area)) {
if (!parsedLink) {
if (MD_LINK_REGEX.test(link)) {
// try fixing MDX links
let linkMatches
@@ -192,10 +265,33 @@ function checkLink({
}
}
return
} else if (!Object.hasOwn(options.crossProjects, parsedLink.area)) {
throw new Error(
getErrorMessage({
link,
file,
additionalMessage: `Unknown project area: ${parsedLink.area}`,
})
)
}
const projectOptions = options.crossProjects[parsedLink.area]
if (parsedLink.path.length && !parsedLink.path.startsWith("/")) {
throw new Error(
getErrorMessage({
link,
file,
additionalMessage: `Cross project paths must start with a slash.`,
})
)
}
if (projectOptions.skipSlugValidation) {
// if slug validation is skipped, just check if the file exists
return
}
const isReferenceLink =
parsedLink.path.startsWith("/references") &&
parsedLink.path !== "/references-overview"
@@ -210,41 +306,40 @@ function checkLink({
return
}
// file doesn't exist, check if slugs are enabled and generated
if (!projectOptions.generatedSlugs) {
throw new Error(
getErrorMessage({
link,
file,
})
)
}
checkSlugExists({
slugChanges: projectOptions.generatedSlugs,
pathToCheck: parsedLink.path,
link,
file,
baseProjectPath: projectOptions.projectPath,
})
}
function loadSlugs(projectPath: string): SlugChange[] {
// file doesn't exist, check if slugs are enabled and generated
const generatedSlugsPath = path.join(
projectOptions.projectPath,
projectPath,
"generated",
"slug-changes.mjs"
)
if (!projectOptions.hasGeneratedSlugs || !existsSync(generatedSlugsPath)) {
throw new Error(
getErrorMessage({
link,
file,
})
)
if (!existsSync(generatedSlugsPath)) {
throw new Error(`Cross project ${projectPath} has no generated slugs file.`)
}
// get slugs from file
const generatedSlugContent = readFileSync(generatedSlugsPath, "utf-8")
const slugChanges: SlugChange[] = JSON.parse(
return JSON.parse(
generatedSlugContent.substring(generatedSlugContent.indexOf("["))
)
const slugChange = slugChanges.find(
(change) => change.newSlug === parsedLink.path
)
if (
!slugChange ||
!mdxPageExists(path.join(projectOptions.projectPath, slugChange.origSlug))
) {
throw new Error(
getErrorMessage({
link,
file,
})
)
}
) as SlugChange[]
}
const allowedComponentNames = [
@@ -261,6 +356,22 @@ export function brokenLinkCheckerPlugin(
return async (tree, file) => {
const { visit } = await import("unist-util-visit")
// load slug changes for the current project
if (options.hasGeneratedSlugs) {
options.generatedSlugs = loadSlugs(file.cwd)
}
// load slug changes for cross projects
if (options.crossProjects) {
for (const project of Object.values(options.crossProjects)) {
if (!project.hasGeneratedSlugs) {
continue
}
project.generatedSlugs = loadSlugs(project.projectPath)
}
}
const currentPageFilePath = file.history[0].replace(
`/${path.basename(file.history[0])}`,
""
+11
View File
@@ -1,4 +1,5 @@
import { Node } from "unist-builder"
import { SlugChange } from "./build-scripts.js"
export interface UnistNode extends Node {
type: string
@@ -128,11 +129,21 @@ export declare type CrossProjectLinksOptions = {
}
export declare type BrokenLinkCheckerOptions = {
rootBasePath?: {
default: string
overrides?: {
[k: string]: string
}
}
hasGeneratedSlugs?: boolean
generatedSlugs?: SlugChange[]
crossProjects: {
[k: string]: {
projectPath: string
contentPath?: string
hasGeneratedSlugs?: boolean
generatedSlugs?: SlugChange[]
skipSlugValidation?: boolean
}
}
}