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 = "Befter";
const description = "A lightweight JavaScript library for managing hooks — register, invoke, update, and sequence them with full control over execution order.";

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>
);
}
19 changes: 19 additions & 0 deletions .docs/site/docs.config.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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"
}
],
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"
]
}
19 changes: 19 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Docs Maintenance Guide
Use this file as the handoff checklist for future edits to this documentation PR.
## Source Layout
- The docs source lives in `docs/`.
- `docs.json` is the Docs Cloud configuration for publishing, previews, and content roots.
- The managed runtime lives in `.docs/site`; edit authored markdown at the repo root instead of generated runtime pages under `.docs/site/app/docs`.
- Keep every page grounded in README content, package metadata, source exports, CLI help, environment examples, or existing docs.
## Docs Routes
- /docs - Introduction
- /docs/installation - Installation
- /docs/quickstart - Quickstart
## Editing Guidelines
- Prefer reader-facing setup, usage, and troubleshooting notes over source inventories.
- Do not add commands, flags, environment variables, routes, imports, or framework names unless they are present in the repository.
- If you add or rename a page, keep its frontmatter title and description accurate and make sure the navigation ordering still includes it.
- Avoid analyzer language such as generated from, source evidence, implementation map, source surface, or detected in files.
## Verification
- Build the docs site with `cd .docs/site && pnpm install && pnpm build` before handing off a docs PR.
- Open `/docs` and at least one generated leaf page to confirm the sidebar and page content match the PR.
Loading
Loading