Files
better-auth/docs/lib/docs-versions.ts

135 lines
3.8 KiB
TypeScript

export interface DocsVersion {
/**
* Display label shown in switcher (e.g. "Latest", "Beta")
*/
label: string;
/**
* Numeric version for badge rendering (e.g. "1.6").
*/
version: string;
/**
* Branch holding this version's source code (for edit-on-github links).
*/
branch: string;
/**
* URL path segment (e.g. "beta"). null = latest (no prefix).
*/
slug: string | null;
/**
* Small badge shown next to label (e.g. "beta").
*/
badge: string | null;
}
export const docsVersions: DocsVersion[] = [
{
label: "v1.7 (Beta)",
version: "1.7",
branch: "next",
slug: "beta",
badge: null,
},
{
label: "v1.6 (Latest)",
version: "1.6",
branch: "main",
slug: null,
badge: null,
},
];
/**
* The default (latest) version entry.
*/
export const latestVersion = docsVersions.find((v) => v.slug === null)!;
/**
* Find a version config by its URL slug.
*/
export function getVersionBySlug(slug: string): DocsVersion | undefined {
return docsVersions.find((v) => v.slug === slug);
}
/**
* Build a docs href for the given version.
*/
export function versionedDocsHref(path: string, version: DocsVersion): string {
if (!version.slug) return path;
// /docs/introduction -> /docs/beta/introduction
const stripped = path.replace(/^\/docs/, "");
return `/docs/${version.slug}${stripped}`;
}
/**
* Extract the current version from a pathname. Matches on a full path segment
* so `/docs/beta-tutorial/...` is not misread as the `beta` version.
*/
export function getVersionFromPathname(pathname: string): DocsVersion {
for (const v of docsVersions) {
if (!v.slug) continue;
const prefix = `/docs/${v.slug}`;
if (pathname === prefix || pathname.startsWith(`${prefix}/`)) {
return v;
}
}
return latestVersion;
}
/**
* Strip a leading `/docs/<slug>` segment from a pathname, returning the
* canonical latest-style path (`/docs/...`). Anchored to the leading
* version segment so unrelated paths are untouched.
*/
export function stripVersionPrefix(
pathname: string,
version: DocsVersion,
): string {
if (!version.slug) return pathname;
const prefix = `/docs/${version.slug}`;
if (pathname === prefix || pathname === `${prefix}/`) return "/docs";
if (pathname.startsWith(`${prefix}/`)) {
return `/docs${pathname.slice(prefix.length)}`;
}
return pathname;
}
/**
* Rewrite an absolute `/docs/...` link so it stays within the active version.
*
* - Non-`/docs` links (anchors, external, /blog, etc.) pass through untouched.
* - On latest (`slug === null`), this is a no-op.
* - Links that already target one of the currently-registered versions are
* preserved so authors can link across versions explicitly when needed.
*/
export function scopeDocsHref(
href: string | undefined,
version: DocsVersion,
): string | undefined {
if (!href || !version.slug) return href;
// Match /docs exactly, /docs/..., /docs?query, or /docs#hash.
if (!/^\/docs(?:\/|$|[?#])/.test(href)) return href;
// Strip query/hash before checking the version segment so
// `/docs/beta?foo` isn't treated as an unversioned link.
const pathOnly = href.split(/[?#]/, 1)[0];
const segment = pathOnly.split("/")[2];
if (segment && docsVersions.some((v) => v.slug === segment)) return href;
return versionedDocsHref(href, version);
}
/**
* Split a catch-all slug into its version + the remaining content slug.
*
* `["beta", "plugins", "email-otp"]` -> { version: beta, relSlug: ["plugins", "email-otp"] }
* `["introduction"]` -> { version: latest, relSlug: ["introduction"] }
*/
export function resolveVersionFromSlug(slug: string[]): {
version: DocsVersion;
relSlug: string[];
} {
const [head, ...rest] = slug;
const match = head ? getVersionBySlug(head) : undefined;
if (match) return { version: match, relSlug: rest };
return { version: latestVersion, relSlug: slug };
}