mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 11:24:57 -05:00
128 lines
3.6 KiB
TypeScript
128 lines
3.6 KiB
TypeScript
import { readdirSync, readFileSync } from "node:fs";
|
|
import { join, relative } from "node:path";
|
|
import type { FileObject, PopulateParams } from "next-validate-link";
|
|
import { printErrors, scanURLs, validateFiles } from "next-validate-link";
|
|
import { remark } from "remark";
|
|
import { visit } from "unist-util-visit";
|
|
import { docsVersionSources } from "../lib/docs-version-sources.ts";
|
|
import { docsVersions, versionedDocsHref } from "../lib/docs-versions.ts";
|
|
import { scopeDocsContent } from "../lib/scope-docs-content.ts";
|
|
|
|
const routeEntries: NonNullable<PopulateParams[string]> = [];
|
|
const files: FileObject[] = [];
|
|
|
|
interface MarkdownNode {
|
|
type: string;
|
|
value?: string;
|
|
children?: MarkdownNode[];
|
|
}
|
|
|
|
function getNodeText(node: MarkdownNode): string {
|
|
if (node.value !== undefined) return node.value;
|
|
if (!node.children) return "";
|
|
return node.children.map((child) => getNodeText(child)).join("");
|
|
}
|
|
|
|
function createHeadingSlugger() {
|
|
const occurrences = new Map<string, number>();
|
|
return (value: string) => {
|
|
const base = value
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/[^\p{L}\p{N}\s_-]/gu, "")
|
|
.replace(/\s+/g, "-");
|
|
const count = occurrences.get(base) ?? 0;
|
|
occurrences.set(base, count + 1);
|
|
return count === 0 ? base : `${base}-${count}`;
|
|
};
|
|
}
|
|
|
|
function getHeadingIds(content: string) {
|
|
const slug = createHeadingSlugger();
|
|
const headings: string[] = [];
|
|
visit(remark().parse(content), "heading", (node) => {
|
|
headings.push(slug(getNodeText(node)));
|
|
});
|
|
return headings;
|
|
}
|
|
|
|
function getMdxFiles(directory: string): string[] {
|
|
const mdxFiles: string[] = [];
|
|
for (const item of readdirSync(directory, { withFileTypes: true })) {
|
|
const itemPath = join(directory, item.name);
|
|
if (item.isDirectory()) mdxFiles.push(...getMdxFiles(itemPath));
|
|
else if (item.name.endsWith(".mdx")) mdxFiles.push(itemPath);
|
|
}
|
|
return mdxFiles;
|
|
}
|
|
|
|
function normalizeTrailingSlashAnchors(content: string) {
|
|
return content.replace(/(\/docs\/[^\s"'()<>{}\]]+?)\/#([\w-]+)/g, "$1#$2");
|
|
}
|
|
|
|
for (const version of docsVersions) {
|
|
const contentDirectory = join(
|
|
"content",
|
|
docsVersionSources[version.id].contentDirectory,
|
|
);
|
|
const mdxFiles = getMdxFiles(contentDirectory);
|
|
if (mdxFiles.length === 0) {
|
|
throw new Error(
|
|
`${contentDirectory}: no MDX files found. Run sync-versions first.`,
|
|
);
|
|
}
|
|
for (const filePath of mdxFiles) {
|
|
const segments = relative(contentDirectory, filePath)
|
|
.replace(/\.mdx$/, "")
|
|
.split(/[\\/]/);
|
|
if (segments[0] === "examples") continue;
|
|
if (segments.at(-1) === "index") segments.pop();
|
|
|
|
const canonicalUrl = `/docs/${segments.join("/")}`.replace(/\/$/, "");
|
|
const url = versionedDocsHref(canonicalUrl, version);
|
|
const rawContent = readFileSync(filePath, "utf8");
|
|
const versionedContent =
|
|
version.id !== "latest"
|
|
? scopeDocsContent(rawContent, version)
|
|
: rawContent;
|
|
const content = normalizeTrailingSlashAnchors(versionedContent);
|
|
|
|
routeEntries.push({
|
|
value: {
|
|
slug: version.id !== "latest" ? [version.id, ...segments] : segments,
|
|
},
|
|
hashes: getHeadingIds(rawContent),
|
|
});
|
|
files.push({ path: filePath, content, url });
|
|
}
|
|
}
|
|
|
|
const scanned = await scanURLs({
|
|
preset: "next",
|
|
cwd: process.cwd(),
|
|
populate: {
|
|
"docs/[[...slug]]": routeEntries,
|
|
},
|
|
});
|
|
|
|
printErrors(
|
|
await validateFiles(files, {
|
|
scanned,
|
|
markdown: {
|
|
components: {
|
|
Card: { attributes: ["href"] },
|
|
Link: { attributes: ["href"] },
|
|
},
|
|
},
|
|
checkRelativePaths: "as-url",
|
|
whitelist: (url) =>
|
|
url === "/docs" ||
|
|
url === "/dashboard" ||
|
|
url === "/llms.txt" ||
|
|
url.startsWith("chrome://"),
|
|
}),
|
|
true,
|
|
);
|
|
|
|
console.log(`[validate-links] ${files.length} documents passed`);
|