mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-18 11:32:15 -05:00
fix(docs): match documentation sources to release versions (#11868)
This commit is contained in:
@@ -10,11 +10,12 @@ import type { PageTreesByVersion } from "./provider";
|
||||
import { DocsProvider } from "./provider";
|
||||
import { DocsShell } from "./shell";
|
||||
|
||||
const [latestVersion, version16] = docsVersions;
|
||||
const [betaVersion, latestVersion, version16] = docsVersions;
|
||||
|
||||
const pageTreesByVersion = {
|
||||
latest: getSourceFor(latestVersion.id).getPageTree(),
|
||||
"1.6": getSourceFor(version16.id).getPageTree(),
|
||||
beta: getSourceFor(betaVersion.id).getPageTree(),
|
||||
} satisfies PageTreesByVersion;
|
||||
|
||||
function getAvailablePaths(version: DocsVersion) {
|
||||
@@ -27,6 +28,7 @@ function getAvailablePaths(version: DocsVersion) {
|
||||
const versionAvailability = {
|
||||
latest: getAvailablePaths(latestVersion),
|
||||
"1.6": getAvailablePaths(version16),
|
||||
beta: getAvailablePaths(betaVersion),
|
||||
} satisfies VersionAvailability;
|
||||
const resolvedDocsVersions = loadDocsVersions();
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
|
||||
@@ -49,6 +49,7 @@ function useDismissablePopover(
|
||||
|
||||
function getVersionTitle(version: DocsVersion) {
|
||||
if (version.id === "latest") return "Latest Version";
|
||||
if (version.id === "beta") return "Beta Version";
|
||||
return `Version ${version.id}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"latest": {
|
||||
"contentDirectory": "docs",
|
||||
"contentDirectory": "_generated/docs/latest",
|
||||
"editBranch": "main",
|
||||
"commitSha": null
|
||||
},
|
||||
@@ -8,5 +8,10 @@
|
||||
"contentDirectory": "_generated/docs/v1-6",
|
||||
"editBranch": "v1.6.x",
|
||||
"commitSha": "c11bfa667f9f260092c3d4c3798fe50d74b79718"
|
||||
},
|
||||
"beta": {
|
||||
"contentDirectory": "_generated/docs/beta",
|
||||
"editBranch": "next",
|
||||
"commitSha": "be781d5fd26aa4719f5f4fa3ebd767736d4883b1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,29 @@ import {
|
||||
getVersionFromPathname,
|
||||
getVersionTargetHref,
|
||||
resolveVersionFromSlug,
|
||||
scopeDocsHref,
|
||||
versionedDocsHref,
|
||||
} from "./docs-versions";
|
||||
|
||||
const [latestVersion, version16] = docsVersions;
|
||||
const [betaVersion, latestVersion, version16] = docsVersions;
|
||||
|
||||
const availability: VersionAvailability = {
|
||||
latest: [],
|
||||
"1.6": ["/docs/introduction", "/docs/plugins/scim"],
|
||||
beta: ["/docs/introduction", "/docs/plugins/scim"],
|
||||
};
|
||||
|
||||
describe("documentation versions", () => {
|
||||
it("lists beta before stable while keeping stable as the default", () => {
|
||||
expect(docsVersions.map((version) => version.id)).toEqual([
|
||||
"beta",
|
||||
"latest",
|
||||
"1.6",
|
||||
]);
|
||||
expect(getVersionFromPathname("/docs/introduction").id).toBe("latest");
|
||||
expect(resolveVersionFromSlug([]).version.id).toBe("latest");
|
||||
});
|
||||
|
||||
it("targets maintenance branches for documentation edits", () => {
|
||||
expect(docsVersionSources[version16.id].editBranch).toBe("v1.6.x");
|
||||
});
|
||||
@@ -28,6 +40,51 @@ describe("documentation versions", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("pins beta documentation to next and keeps its release line separate", () => {
|
||||
expect(docsVersionSources[betaVersion.id].editBranch).toBe("next");
|
||||
expect(docsVersionSources[betaVersion.id].commitSha).toMatch(
|
||||
/^[0-9a-f]{40}$/,
|
||||
);
|
||||
expect(betaVersion.releaseLine).toBe("1.8");
|
||||
});
|
||||
|
||||
it("resolves beta pages and scopes their links", () => {
|
||||
expect(resolveVersionFromSlug(["beta", "plugins", "scim"])).toEqual({
|
||||
version: betaVersion,
|
||||
relSlug: ["plugins", "scim"],
|
||||
});
|
||||
expect(getVersionFromPathname("/docs/beta/plugins/scim")).toBe(betaVersion);
|
||||
expect(getVersionFromPathname("/docs/beta-tutorial")).toBe(latestVersion);
|
||||
expect(scopeDocsHref("/docs/plugins/scim", betaVersion)).toBe(
|
||||
"/docs/beta/plugins/scim",
|
||||
);
|
||||
expect(scopeDocsHref("/docs/beta/plugins/scim", betaVersion)).toBe(
|
||||
"/docs/beta/plugins/scim",
|
||||
);
|
||||
expect(scopeDocsHref("/docs/1.6/introduction", betaVersion)).toBe(
|
||||
"/docs/1.6/introduction",
|
||||
);
|
||||
});
|
||||
|
||||
it("switches between beta and stable without losing the current page", () => {
|
||||
expect(
|
||||
getVersionTargetHref(
|
||||
"/docs/beta/plugins/scim",
|
||||
betaVersion,
|
||||
latestVersion,
|
||||
{ ...availability, latest: ["/docs/plugins/scim"] },
|
||||
),
|
||||
).toBe("/docs/plugins/scim");
|
||||
expect(
|
||||
getVersionTargetHref(
|
||||
"/docs/plugins/scim",
|
||||
latestVersion,
|
||||
betaVersion,
|
||||
availability,
|
||||
),
|
||||
).toBe("/docs/beta/plugins/scim");
|
||||
});
|
||||
|
||||
it("resolves archived version slugs", () => {
|
||||
expect(resolveVersionFromSlug(["1.6", "plugins", "scim"])).toEqual({
|
||||
version: version16,
|
||||
|
||||
@@ -18,6 +18,12 @@ export interface DocsVersionConfig {
|
||||
}
|
||||
|
||||
export const docsVersions = [
|
||||
{
|
||||
label: "v1.8 (Beta)",
|
||||
releaseLine: "1.8",
|
||||
id: "beta",
|
||||
badge: null,
|
||||
},
|
||||
{
|
||||
label: "v1.7 (Latest)",
|
||||
releaseLine: "1.7",
|
||||
@@ -44,7 +50,7 @@ const docsRootPathPattern = /^\/docs\/?([?#].*)?$/;
|
||||
/**
|
||||
* The default (latest) version entry.
|
||||
*/
|
||||
export const latestVersion = docsVersions[0];
|
||||
export const latestVersion = docsVersions[1];
|
||||
|
||||
/**
|
||||
* Find a version config by its URL slug.
|
||||
|
||||
@@ -29,6 +29,7 @@ describe("LLM routes", () => {
|
||||
|
||||
expect(content).toContain("https://better-auth.com/docs/llms.txt");
|
||||
expect(content).toContain("https://better-auth.com/docs/1.6/llms.txt");
|
||||
expect(content).toContain("https://better-auth.com/docs/beta/llms.txt");
|
||||
expect(content).toContain("https://mcp.better-auth.com/mcp");
|
||||
expect(content).not.toContain("/llms.txt/docs/");
|
||||
});
|
||||
@@ -61,6 +62,9 @@ describe("LLM routes", () => {
|
||||
"/docs/1.6/plugins/scim.md",
|
||||
);
|
||||
expect(getLegacyMarkdownTarget(["1.6"])).toBe("/docs/1.6/llms.txt");
|
||||
expect(getLegacyMarkdownTarget(["beta", "plugins", "scim"])).toBe(
|
||||
"/docs/beta/plugins/scim.md",
|
||||
);
|
||||
expect(getLegacyMarkdownTarget(["docs", "introduction.txt"])).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { docsVersions } from "./docs-versions";
|
||||
import { scopeMarkdownLinks } from "./markdown-links";
|
||||
|
||||
const version16 = docsVersions[1];
|
||||
const version16 = docsVersions[2];
|
||||
|
||||
describe("versioned Markdown links", () => {
|
||||
it("scopes unversioned links without duplicating version prefixes", () => {
|
||||
|
||||
+9
-1
@@ -1,7 +1,7 @@
|
||||
import { loader } from "fumadocs-core/source";
|
||||
import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons";
|
||||
import { toFumadocsSource } from "fumadocs-mdx/runtime/server";
|
||||
import { blogCollection, docs, docsV16 } from "@/.source/server";
|
||||
import { blogCollection, docs, docsBeta, docsV16 } from "@/.source/server";
|
||||
import type { DocsVersionId } from "./docs-versions";
|
||||
import { pageTreePlugin } from "./page-tree";
|
||||
|
||||
@@ -21,9 +21,17 @@ export const sourceV16 = loader({
|
||||
plugins: docsPlugins,
|
||||
});
|
||||
|
||||
export const sourceBeta = loader({
|
||||
baseUrl: "/docs/beta",
|
||||
source: docsBeta.toFumadocsSource(),
|
||||
pageTree: { noRef: true },
|
||||
plugins: docsPlugins,
|
||||
});
|
||||
|
||||
const docsSources = {
|
||||
latest: source,
|
||||
"1.6": sourceV16,
|
||||
beta: sourceBeta,
|
||||
} satisfies Record<DocsVersionId, typeof source>;
|
||||
|
||||
export function getSourceFor(versionId: DocsVersionId) {
|
||||
|
||||
@@ -24,10 +24,21 @@ const releaseVersionsPath = join(
|
||||
"docs",
|
||||
"release-versions.json",
|
||||
);
|
||||
const localReleaseVersion = await readPackageVersion(
|
||||
join(repositoryRoot, packagePath),
|
||||
);
|
||||
const localVersion = docsVersions.find((version) =>
|
||||
localReleaseVersion.startsWith(`${version.releaseLine}.`),
|
||||
);
|
||||
if (!localVersion) {
|
||||
throw new Error(
|
||||
`No documentation version configured for ${localReleaseVersion}`,
|
||||
);
|
||||
}
|
||||
const versionsToSync = docsVersions.flatMap((version) => {
|
||||
const source = docsVersionSources[version.id];
|
||||
if (source.commitSha === null) return [];
|
||||
return [{ ...version, ...source, commitSha: source.commitSha }];
|
||||
if (version.id === localVersion.id) return [];
|
||||
return [{ ...version, ...source }];
|
||||
});
|
||||
|
||||
function git(args: string[], cwd?: string) {
|
||||
@@ -57,7 +68,7 @@ async function readPackageVersion(path: string) {
|
||||
}
|
||||
|
||||
async function prepareWorktree(version: (typeof versionsToSync)[number]) {
|
||||
const { commitSha } = version;
|
||||
const revision = `refs/docs-versions/${version.id.replaceAll(".", "-")}`;
|
||||
const checkoutDirectory = resolve(
|
||||
tempRoot,
|
||||
"worktrees",
|
||||
@@ -71,7 +82,7 @@ async function prepareWorktree(version: (typeof versionsToSync)[number]) {
|
||||
"--detach",
|
||||
"--no-checkout",
|
||||
checkoutDirectory,
|
||||
commitSha,
|
||||
revision,
|
||||
]);
|
||||
await git(["-C", checkoutDirectory, "sparse-checkout", "init", "--cone"]);
|
||||
await git([
|
||||
@@ -88,19 +99,22 @@ async function prepareWorktree(version: (typeof versionsToSync)[number]) {
|
||||
"checkout",
|
||||
"--quiet",
|
||||
"--detach",
|
||||
commitSha,
|
||||
revision,
|
||||
]);
|
||||
return { checkoutDirectory, commitSha, version };
|
||||
return { checkoutDirectory, version };
|
||||
}
|
||||
|
||||
async function syncVersion({
|
||||
checkoutDirectory,
|
||||
commitSha,
|
||||
version,
|
||||
}: Awaited<ReturnType<typeof prepareWorktree>>) {
|
||||
const destination = join(docsRoot, "content", version.contentDirectory);
|
||||
const sourceRevision =
|
||||
checkoutDirectory === repositoryRoot
|
||||
? "local"
|
||||
: (version.commitSha?.slice(0, 9) ?? "HEAD");
|
||||
console.log(
|
||||
`[sync-versions] ${version.editBranch}@${commitSha.slice(0, 9)}:${remotePath} → ${destination}`,
|
||||
`[sync-versions] ${version.editBranch}@${sourceRevision}:${remotePath} → ${destination}`,
|
||||
);
|
||||
const sourcePath = join(checkoutDirectory, remotePath);
|
||||
try {
|
||||
@@ -112,12 +126,17 @@ async function syncVersion({
|
||||
);
|
||||
}
|
||||
|
||||
await rm(destination, { recursive: true, force: true });
|
||||
await cp(sourcePath, destination, { recursive: true });
|
||||
await writeFile(join(destination, ".gitkeep"), "");
|
||||
const releaseVersion = await readPackageVersion(
|
||||
join(checkoutDirectory, packagePath),
|
||||
);
|
||||
if (!releaseVersion.startsWith(`${version.releaseLine}.`)) {
|
||||
throw new Error(
|
||||
`Invalid release version for ${version.id}: expected ${version.releaseLine}.x, received ${releaseVersion}`,
|
||||
);
|
||||
}
|
||||
await rm(destination, { recursive: true, force: true });
|
||||
await cp(sourcePath, destination, { recursive: true });
|
||||
await writeFile(join(destination, ".gitkeep"), "");
|
||||
return [version.id, releaseVersion] as const;
|
||||
}
|
||||
|
||||
@@ -137,11 +156,16 @@ try {
|
||||
"origin",
|
||||
...versionsToSync.map(
|
||||
(version) =>
|
||||
`+${version.commitSha}:refs/docs-versions/${version.id.replaceAll(".", "-")}`,
|
||||
`+${version.commitSha ?? version.editBranch}:refs/docs-versions/${version.id.replaceAll(".", "-")}`,
|
||||
),
|
||||
]);
|
||||
|
||||
const worktrees = [];
|
||||
const worktrees = [
|
||||
{
|
||||
checkoutDirectory: repositoryRoot,
|
||||
version: { ...localVersion, ...docsVersionSources[localVersion.id] },
|
||||
},
|
||||
];
|
||||
for (const version of versionsToSync) {
|
||||
worktrees.push(await prepareWorktree(version));
|
||||
}
|
||||
@@ -155,17 +179,11 @@ try {
|
||||
"One or more documentation versions failed to sync",
|
||||
);
|
||||
}
|
||||
const latestVersion = docsVersions.find((version) => version.id === "latest");
|
||||
if (!latestVersion) throw new Error("Missing latest documentation version");
|
||||
const releaseVersions = Object.fromEntries([
|
||||
[
|
||||
latestVersion.id,
|
||||
await readPackageVersion(join(repositoryRoot, packagePath)),
|
||||
],
|
||||
...results.flatMap((result) =>
|
||||
const releaseVersions = Object.fromEntries(
|
||||
results.flatMap((result) =>
|
||||
result.status === "fulfilled" ? [result.value] : [],
|
||||
),
|
||||
]);
|
||||
);
|
||||
await writeFile(
|
||||
releaseVersionsPath,
|
||||
`${JSON.stringify(releaseVersions, null, 2)}\n`,
|
||||
|
||||
+27
-2
@@ -12,6 +12,12 @@ import {
|
||||
remarkAutoTypeTable,
|
||||
} from "fumadocs-typescript";
|
||||
import * as z from "zod";
|
||||
import packageMetadata from "../packages/better-auth/package.json" with {
|
||||
type: "json",
|
||||
};
|
||||
import { docsVersionSources } from "./lib/docs-version-sources";
|
||||
import type { DocsVersion } from "./lib/docs-versions";
|
||||
import { docsVersions } from "./lib/docs-versions";
|
||||
|
||||
const docsPageSchema = pageSchema.extend({
|
||||
sidebarBadge: z.string().min(1).optional(),
|
||||
@@ -22,8 +28,16 @@ const processedMarkdownOptions = {
|
||||
mdxAsPlaceholder: ["APIMethod"],
|
||||
} satisfies LLMsOptions;
|
||||
|
||||
function getContentDirectory(version: DocsVersion) {
|
||||
return packageMetadata.version.startsWith(`${version.releaseLine}.`)
|
||||
? "./content/docs"
|
||||
: `./content/${docsVersionSources[version.id].contentDirectory}`;
|
||||
}
|
||||
|
||||
const [betaVersion, latestVersion, version16] = docsVersions;
|
||||
|
||||
export const docs = defineDocs({
|
||||
dir: "./content/docs",
|
||||
dir: getContentDirectory(latestVersion),
|
||||
docs: {
|
||||
schema: docsPageSchema,
|
||||
postprocess: {
|
||||
@@ -34,7 +48,18 @@ export const docs = defineDocs({
|
||||
});
|
||||
|
||||
export const docsV16 = defineDocs({
|
||||
dir: "./content/_generated/docs/v1-6",
|
||||
dir: getContentDirectory(version16),
|
||||
docs: {
|
||||
schema: docsPageSchema,
|
||||
postprocess: {
|
||||
includeProcessedMarkdown: processedMarkdownOptions,
|
||||
},
|
||||
async: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const docsBeta = defineDocs({
|
||||
dir: getContentDirectory(betaVersion),
|
||||
docs: {
|
||||
schema: docsPageSchema,
|
||||
postprocess: {
|
||||
|
||||
Reference in New Issue
Block a user