Skip to content
Draft
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
6 changes: 6 additions & 0 deletions .docs/site/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
.env*
!.env.example
.next
.vercel
app/docs
2 changes: 2 additions & 0 deletions .docs/site/app/global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@import "tailwindcss";
@import "@farming-labs/theme/colorful/css";
33 changes: 33 additions & 0 deletions .docs/site/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { RootProvider } from "@farming-labs/theme";
import docsConfig from "../docs.config";
import "./global.css";

const geistSans = Geist({
variable: "--fd-font-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--fd-font-mono",
subsets: ["latin"],
});

export const metadata: Metadata = {
title: {
default: "Docs",
template: docsConfig.metadata?.titleTemplate ?? "%s",
},
description: docsConfig.metadata?.description,
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased`}>
<RootProvider>{children}</RootProvider>
</body>
</html>
);
}
38 changes: 38 additions & 0 deletions .docs/site/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Link from "next/link";

const title = "Better Auth Studio";
const description = "An admin studio for Better Auth — inspect users, manage organizations, and monitor your auth system from a polished UI.";

export default function HomePage() {
return (
<main className="relative isolate min-h-dvh overflow-hidden bg-gradient-to-b from-fd-background via-fd-background to-fd-muted/25 text-fd-foreground">
<div
aria-hidden
className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(ellipse_80%_60%_at_50%_-10%,color-mix(in_oklch,var(--fd-primary)_12%,transparent),transparent)]"
/>
<div className="mx-auto flex min-h-dvh max-w-2xl flex-col justify-center px-6 py-20 sm:px-8">
<p className="text-xs font-medium uppercase tracking-[0.2em] text-fd-muted-foreground">
Documentation
</p>
<h1 className="mt-4 text-balance text-4xl font-semibold tracking-tight sm:text-5xl">{title}</h1>
<p className="mt-6 text-pretty text-lg leading-relaxed text-fd-muted-foreground">{description}</p>
<p className="mt-6 text-pretty text-base leading-relaxed text-fd-muted-foreground">
Author markdown in{" "}
<code className="rounded-md border border-fd-border bg-fd-muted/60 px-1.5 py-0.5 font-mono text-[0.9em] text-fd-foreground">
docs/
</code>
. Everything under{" "}
<span className="font-mono text-[0.95em] text-fd-foreground">/docs</span> is synced from that folder.
</p>
<div className="mt-10">
<Link
href="/docs"
className="inline-flex items-center justify-center rounded-lg bg-fd-primary px-5 py-2.5 text-sm font-medium text-fd-primary-foreground shadow-sm outline-none transition hover:opacity-95 focus-visible:ring-2 focus-visible:ring-fd-ring focus-visible:ring-offset-2 focus-visible:ring-offset-fd-background"
>
Open documentation
</Link>
</div>
</div>
</main>
);
}
54 changes: 54 additions & 0 deletions .docs/site/docs.config.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { defineDocs } from "@farming-labs/docs";
import { colorful } from "@farming-labs/theme/colorful";

export default defineDocs({
entry: "docs",
theme: colorful(),
ordering: [
{
"slug": "quickstart"
},
{
"slug": "installation"
},
{
"slug": "features",
"children": [
{
"slug": "organization-management"
}
]
},
{
"slug": "configuration",
"children": [
{
"slug": "database-adapters"
},
{
"slug": "deployment"
}
]
},
{
"slug": "guides",
"children": [
{
"slug": "watch-mode"
}
]
},
{
"slug": "self-hosting",
"children": [
{
"slug": "overview"
}
]
}
],
metadata: {
titleTemplate: "%s – Docs",
description: "Managed by @farming-labs/docs Cloud",
},
});
6 changes: 6 additions & 0 deletions .docs/site/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
12 changes: 12 additions & 0 deletions .docs/site/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { withDocs } from "@farming-labs/next/config";

const appDir = dirname(fileURLToPath(import.meta.url));
const root = join(appDir, "../..");

export default withDocs({
turbopack: {
root,
},
});
30 changes: 30 additions & 0 deletions .docs/site/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "docs-cloud-managed-runtime",
"private": true,
"packageManager": "pnpm@10.9.0",
"scripts": {
"sync:content": "node ./scripts/sync-managed-content.mjs",
"dev": "node ./scripts/sync-managed-content.mjs && next dev --turbopack",
"build": "node ./scripts/sync-managed-content.mjs && next build --turbopack",
"start": "node ./scripts/sync-managed-content.mjs && next start"
},
"dependencies": {
"@farming-labs/docs": "latest",
"@farming-labs/next": "latest",
"@farming-labs/theme": "latest",
"next": "16.2.3",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"zod": "^4.1.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.18",
"@types/mdx": "^2.0.13",
"@types/node": "^22.10.0",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3"
}
}
7 changes: 7 additions & 0 deletions .docs/site/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};

export default config;
159 changes: 159 additions & 0 deletions .docs/site/scripts/sync-managed-content.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { cp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
import { basename, dirname, extname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const runtimeRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
const repoRoot = resolve(runtimeRoot, "../..");
const authoredRoots = [
{ source: resolve(repoRoot, "docs"), target: resolve(runtimeRoot, "app/docs") },
{ source: resolve(repoRoot, "api-reference"), target: resolve(runtimeRoot, "app/docs/api") },
];
const ignoredDirectoryNames = new Set([
"node_modules",
".next",
".turbo",
".vercel",
"dist",
"build",
"coverage",
]);
const ignoredFileNames = new Set([
"bun.lock",
"jsconfig.json",
"package-lock.json",
"package.json",
"pnpm-lock.yaml",
"tsconfig.json",
"yarn.lock",
]);
const staticAssetExtensions = new Set([
".avif",
".bmp",
".csv",
".gif",
".ico",
".jpeg",
".jpg",
".json",
".mp4",
".pdf",
".png",
".svg",
".txt",
".webm",
".webp",
".zip",
]);
const codeFenceLanguageAliases = new Map([
["dotenv", "bash"],
["env", "bash"],
["shell", "bash"],
]);

function isMarkdownFile(path) {
return [".md", ".mdx"].includes(extname(path).toLowerCase());
}

function isStaticAssetFile(path) {
return staticAssetExtensions.has(extname(path).toLowerCase());
}

function shouldSkipDirectory(name) {
return name.startsWith(".") || ignoredDirectoryNames.has(name);
}

function shouldSkipFile(name) {
return name.startsWith(".") || ignoredFileNames.has(name);
}

function normalizeMarkdownContent(content) {
const fenceMarker = String.fromCharCode(96);
const codeFencePattern = new RegExp(
"(^|\\n)(" + fenceMarker + "{3,})([A-Za-z0-9_+.-]+)([^\\n" + fenceMarker + "]*)",
"g",
);

return content.replace(codeFencePattern, (match, prefix, fence, language, rest = "") => {
const normalizedLanguage = codeFenceLanguageAliases.get(language.toLowerCase());

if (!normalizedLanguage) {
return match;
}

return prefix + fence + normalizedLanguage + rest;
});
}

function targetPagePath(targetRoot, relativePath) {
const withoutExtension = relativePath.replace(/\.mdx?$/i, "");
const routeFileName = basename(withoutExtension).toLowerCase();
const isIndexPage = routeFileName === "index" || routeFileName === "page";
const targetDirectory = isIndexPage ? dirname(withoutExtension) : withoutExtension;
return join(targetRoot, targetDirectory === "." ? "" : targetDirectory, "page.mdx");
}

async function fileExists(path) {
try {
await readdir(path);
return true;
} catch {
try {
await readFile(path, "utf8");
return true;
} catch {
return false;
}
}
}

async function syncAuthoredRoot(sourceRoot, targetRoot) {
if (!(await fileExists(sourceRoot))) {
return;
}

const visit = async (currentSourceDirectory, relativeDirectory = "") => {
const entries = await readdir(currentSourceDirectory, { withFileTypes: true });

for (const entry of entries) {
if (entry.name.startsWith(".")) {
continue;
}

const sourcePath = join(currentSourceDirectory, entry.name);
const relativePath = relativeDirectory ? join(relativeDirectory, entry.name) : entry.name;

if (entry.isDirectory()) {
if (shouldSkipDirectory(entry.name)) {
continue;
}

await visit(sourcePath, relativePath);
continue;
}

if (!isMarkdownFile(entry.name)) {
if (shouldSkipFile(entry.name) || !isStaticAssetFile(entry.name)) {
continue;
}

const targetPath = join(targetRoot, relativePath);
await mkdir(dirname(targetPath), { recursive: true });
await cp(sourcePath, targetPath, { force: true });
continue;
}

const targetPath = targetPagePath(targetRoot, relativePath);
await mkdir(dirname(targetPath), { recursive: true });
await writeFile(targetPath, normalizeMarkdownContent(await readFile(sourcePath, "utf8")), "utf8");
}
};

await visit(sourceRoot);
}

await rm(resolve(runtimeRoot, "app/docs"), { recursive: true, force: true });
await mkdir(resolve(runtimeRoot, "app/docs"), { recursive: true });

for (const authoredRoot of authoredRoots) {
await syncAuthoredRoot(authoredRoot.source, authoredRoot.target);
}
42 changes: 42 additions & 0 deletions .docs/site/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
Loading
Loading