From 6e0f41c258c67dca6a4d9bb1df776eb58eda17de Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 09:17:17 +0530 Subject: [PATCH 01/38] feat: remove Next.js deps, add Vite + Nitro stack, delete app router - Remove next, remark-attr, zod, react-device-detect from deps - Add vite, nitro, react-router-dom, gray-matter, minisearch, satori - Add fumadocs remark/rehype plugins (remark-gfm, remark-frontmatter, etc.) - Delete src/app/ (Next.js App Router), source.config.ts, next.config.mjs - Replace react-device-detect with navigator.platform check Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/next.config.mjs | 10 -- packages/chronicle/package.json | 23 ++-- packages/chronicle/source.config.ts | 51 -------- .../chronicle/src/app/[[...slug]]/layout.tsx | 15 --- .../chronicle/src/app/[[...slug]]/page.tsx | 106 ---------------- .../chronicle/src/app/api/apis-proxy/route.ts | 59 --------- .../chronicle/src/app/api/health/route.ts | 3 - .../chronicle/src/app/api/search/route.ts | 90 -------------- .../app/apis/[[...slug]]/layout.module.css | 22 ---- .../src/app/apis/[[...slug]]/layout.tsx | 26 ---- .../src/app/apis/[[...slug]]/page.tsx | 117 ------------------ packages/chronicle/src/app/layout.tsx | 57 --------- .../chronicle/src/app/llms-full.txt/route.ts | 18 --- packages/chronicle/src/app/llms.txt/route.ts | 15 --- packages/chronicle/src/app/og/route.tsx | 62 ---------- packages/chronicle/src/app/providers.tsx | 8 -- packages/chronicle/src/app/robots.ts | 10 -- packages/chronicle/src/app/sitemap.ts | 29 ----- .../chronicle/src/components/ui/search.tsx | 4 +- 19 files changed, 16 insertions(+), 709 deletions(-) delete mode 100644 packages/chronicle/next.config.mjs delete mode 100644 packages/chronicle/source.config.ts delete mode 100644 packages/chronicle/src/app/[[...slug]]/layout.tsx delete mode 100644 packages/chronicle/src/app/[[...slug]]/page.tsx delete mode 100644 packages/chronicle/src/app/api/apis-proxy/route.ts delete mode 100644 packages/chronicle/src/app/api/health/route.ts delete mode 100644 packages/chronicle/src/app/api/search/route.ts delete mode 100644 packages/chronicle/src/app/apis/[[...slug]]/layout.module.css delete mode 100644 packages/chronicle/src/app/apis/[[...slug]]/layout.tsx delete mode 100644 packages/chronicle/src/app/apis/[[...slug]]/page.tsx delete mode 100644 packages/chronicle/src/app/layout.tsx delete mode 100644 packages/chronicle/src/app/llms-full.txt/route.ts delete mode 100644 packages/chronicle/src/app/llms.txt/route.ts delete mode 100644 packages/chronicle/src/app/og/route.tsx delete mode 100644 packages/chronicle/src/app/providers.tsx delete mode 100644 packages/chronicle/src/app/robots.ts delete mode 100644 packages/chronicle/src/app/sitemap.ts diff --git a/packages/chronicle/next.config.mjs b/packages/chronicle/next.config.mjs deleted file mode 100644 index 4b50b92..0000000 --- a/packages/chronicle/next.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { createMDX } from 'fumadocs-mdx/next' - -const withMDX = createMDX() - -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, -} - -export default withMDX(nextConfig) diff --git a/packages/chronicle/package.json b/packages/chronicle/package.json index 6f508a7..dcdd3f4 100644 --- a/packages/chronicle/package.json +++ b/packages/chronicle/package.json @@ -9,8 +9,6 @@ "dist", "src", "templates", - "next.config.mjs", - "source.config.ts", "tsconfig.json" ], "bin": { @@ -38,26 +36,33 @@ "@codemirror/view": "^6.39.14", "@heroicons/react": "^2.2.0", "@raystack/apsara": "^0.56.0", - "@types/unist": "^3.0.3", + "@shikijs/rehype": "^4.0.2", + "@vitejs/plugin-react": "^6.0.1", "chalk": "^5.6.2", "class-variance-authority": "^0.7.1", "codemirror": "^6.0.2", "commander": "^14.0.2", "fumadocs-core": "16.6.15", "fumadocs-mdx": "^14.2.6", + "glob": "^11.0.0", + "gray-matter": "^4.0.3", "lodash": "^4.17.23", "mermaid": "^11.13.0", - "next": "16.1.6", + "minisearch": "^7.2.0", + "nitro": "latest", + "openapi-types": "^12.1.3", "react": "^19.0.0", - "react-device-detect": "^2.2.3", "react-dom": "^19.0.0", - "remark-attr": "^0.11.1", + "react-router-dom": "^7.13.1", "remark-directive": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.1", + "remark-mdx-frontmatter": "^5.2.0", + "satori": "^0.25.0", "slugify": "^1.6.6", "unified": "^11.0.5", "unist-util-visit": "^5.1.0", - "openapi-types": "^12.1.3", - "yaml": "^2.8.2", - "zod": "^4.3.6" + "vite": "^8.0.0", + "yaml": "^2.8.2" } } \ No newline at end of file diff --git a/packages/chronicle/source.config.ts b/packages/chronicle/source.config.ts deleted file mode 100644 index eb8b3ec..0000000 --- a/packages/chronicle/source.config.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { defineDocs, defineConfig, frontmatterSchema } from 'fumadocs-mdx/config' -import { z } from 'zod' -import remarkDirective from 'remark-directive' -import { remarkDirectiveAdmonition, remarkMdxMermaid } from 'fumadocs-core/mdx-plugins' -import remarkUnusedDirectives from './src/lib/remark-unused-directives' - -const contentDir = process.env.CHRONICLE_CONTENT_DIR || './content' - -export const docs = defineDocs({ - dir: contentDir, - docs: { - schema: frontmatterSchema.extend({ - order: z.number().optional(), - lastModified: z.string().optional(), - }), - postprocess: { - includeProcessedMarkdown: true, - }, - files: ['**/*.mdx', '**/*.md', '!**/node_modules/**'], - }, -}) - -export default defineConfig({ - mdxOptions: { - remarkPlugins: [ - remarkDirective, - [ - remarkDirectiveAdmonition, - { - tags: { - CalloutContainer: 'Callout', - CalloutTitle: 'CalloutTitle', - CalloutDescription: 'CalloutDescription', - }, - types: { - note: 'accent', - tip: 'accent', - info: 'accent', - warn: 'attention', - warning: 'attention', - danger: 'alert', - caution: 'alert', - success: 'success', - }, - }, - ], - remarkUnusedDirectives, - remarkMdxMermaid, - ], - }, -}) diff --git a/packages/chronicle/src/app/[[...slug]]/layout.tsx b/packages/chronicle/src/app/[[...slug]]/layout.tsx deleted file mode 100644 index 17b40bf..0000000 --- a/packages/chronicle/src/app/[[...slug]]/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { loadConfig } from '@/lib/config' -import { buildPageTree } from '@/lib/source' -import { getTheme } from '@/themes/registry' - -export default function DocsLayout({ children }: { children: React.ReactNode }) { - const config = loadConfig() - const tree = buildPageTree() - const { Layout, className } = getTheme(config.theme?.name) - - return ( - - {children} - - ) -} diff --git a/packages/chronicle/src/app/[[...slug]]/page.tsx b/packages/chronicle/src/app/[[...slug]]/page.tsx deleted file mode 100644 index c40a615..0000000 --- a/packages/chronicle/src/app/[[...slug]]/page.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import type { Metadata, ResolvingMetadata } from 'next' -import { notFound } from 'next/navigation' -import type { MDXContent } from 'mdx/types' -import { loadConfig } from '@/lib/config' -import { source, buildPageTree } from '@/lib/source' -import { getTheme } from '@/themes/registry' -import { mdxComponents } from '@/components/mdx' - -interface PageProps { - params: Promise<{ slug?: string[] }> -} - -interface PageData { - title: string - description?: string - body: MDXContent - toc: { title: string; url: string; depth: number }[] -} - -export async function generateMetadata( - { params }: PageProps, - parent: ResolvingMetadata, -): Promise { - const { slug } = await params - const page = source.getPage(slug) - if (!page) return {} - const config = loadConfig() - const data = page.data as PageData - const parentMetadata = await parent - - const metadata: Metadata = { - title: data.title, - description: data.description, - } - - if (config.url) { - const ogParams = new URLSearchParams({ title: data.title }) - if (data.description) ogParams.set('description', data.description) - metadata.openGraph = { - ...parentMetadata.openGraph, - title: data.title, - description: data.description, - images: [{ url: `/og?${ogParams.toString()}`, width: 1200, height: 630 }], - } - metadata.twitter = { - ...parentMetadata.twitter, - title: data.title, - description: data.description, - } - } - - return metadata -} - -export default async function DocsPage({ params }: PageProps) { - const { slug } = await params - const config = loadConfig() - - const page = source.getPage(slug) - - if (!page) { - notFound() - } - - const { Page } = getTheme(config.theme?.name) - - const data = page.data as PageData - const MDXBody = data.body - - const tree = buildPageTree() - - const pageUrl = config.url ? `${config.url}/${(slug ?? []).join('/')}` : undefined - - return ( - <> - - , - toc: data.toc ?? [], - }} - config={config} - tree={tree} - /> - - ) -} - -export function generateStaticParams() { - return source.getPages().map((page) => ({ - slug: page.slugs, - })) -} diff --git a/packages/chronicle/src/app/api/apis-proxy/route.ts b/packages/chronicle/src/app/api/apis-proxy/route.ts deleted file mode 100644 index 5ceeb1b..0000000 --- a/packages/chronicle/src/app/api/apis-proxy/route.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { loadConfig } from "@/lib/config"; -import { loadApiSpecs } from "@/lib/openapi"; - -export async function POST(request: NextRequest) { - const { specName, method, path, headers, body } = await request.json(); - - if (!specName || !method || !path) { - return NextResponse.json( - { error: "Missing specName, method, or path" }, - { status: 400 }, - ); - } - - const config = loadConfig(); - const specs = loadApiSpecs(config.api ?? []); - const spec = specs.find((s) => s.name === specName); - - if (!spec) { - return NextResponse.json( - { error: `Unknown spec: ${specName}` }, - { status: 404 }, - ); - } - - const url = spec.server.url + path; - - try { - const response = await fetch(url, { - method, - headers, - body: body ? JSON.stringify(body) : undefined, - }); - - const contentType = response.headers.get("content-type") ?? ""; - const responseBody = contentType.includes("application/json") - ? await response.json() - : await response.text(); - - return NextResponse.json({ - status: response.status, - statusText: response.statusText, - body: responseBody, - }, { status: response.status }); - } catch (error) { - const message = - error instanceof Error - ? `${error.message}${error.cause ? `: ${(error.cause as Error).message}` : ""}` - : "Request failed"; - return NextResponse.json( - { - status: 502, - statusText: "Bad Gateway", - body: `Could not reach ${url}\n${message}`, - }, - { status: 502 }, - ); - } -} diff --git a/packages/chronicle/src/app/api/health/route.ts b/packages/chronicle/src/app/api/health/route.ts deleted file mode 100644 index 5ebc71e..0000000 --- a/packages/chronicle/src/app/api/health/route.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function GET() { - return Response.json({ status: 'ok' }) -} diff --git a/packages/chronicle/src/app/api/search/route.ts b/packages/chronicle/src/app/api/search/route.ts deleted file mode 100644 index 0286227..0000000 --- a/packages/chronicle/src/app/api/search/route.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { source } from '@/lib/source' -import { createSearchAPI, type AdvancedIndex } from 'fumadocs-core/search/server' -import type { StructuredData } from 'fumadocs-core/mdx-plugins' -import type { OpenAPIV3 } from 'openapi-types' -import { loadConfig } from '@/lib/config' -import { loadApiSpecs, type ApiSpec } from '@/lib/openapi' -import { getSpecSlug } from '@/lib/api-routes' - -interface PageData { - title?: string - description?: string - structuredData?: StructuredData - load?: () => Promise<{ structuredData?: StructuredData }> -} - -const HTTP_METHODS = ['get', 'post', 'put', 'delete', 'patch'] as const -type HttpMethod = (typeof HTTP_METHODS)[number] - -function getParamNames(op: OpenAPIV3.OperationObject): string[] { - const params = (op.parameters as OpenAPIV3.ParameterObject[] | undefined) ?? [] - return params.map((p) => p.name) -} - -function buildStructuredData(op: OpenAPIV3.OperationObject, method: string, pathStr: string) { - return { - headings: [{ id: op.operationId!, content: `${method.toUpperCase()} ${pathStr}` }], - contents: [{ heading: op.operationId!, content: `${method.toUpperCase()} ${[op.description, ...getParamNames(op)].filter(Boolean).join(' ')}` }], - } -} - -function operationToIndex(specSlug: string, pathStr: string, method: HttpMethod, op: OpenAPIV3.OperationObject): AdvancedIndex { - const url = `/apis/${specSlug}/${encodeURIComponent(op.operationId!)}` - return { - id: url, - url, - title: `${method.toUpperCase()} ${op.summary ?? op.operationId!}`, - description: op.description ?? '', - structuredData: buildStructuredData(op, method, pathStr), - } -} - -function pathEntryToIndexes(specSlug: string) { - return ([pathStr, pathItem]: [string, OpenAPIV3.PathItemObject | undefined]): AdvancedIndex[] => { - if (!pathItem) return [] - const hasOp = (m: HttpMethod) => !!pathItem[m]?.operationId - const toIndex = (m: HttpMethod) => operationToIndex(specSlug, pathStr, m, pathItem[m]!) - return HTTP_METHODS.filter(hasOp).map(toIndex) - } -} - -function specToIndexes(spec: ApiSpec): AdvancedIndex[] { - const specSlug = getSpecSlug(spec) - return Object.entries(spec.document.paths ?? {}).flatMap(pathEntryToIndexes(specSlug)) -} - -function buildApiIndexes(): AdvancedIndex[] { - const config = loadConfig() - if (!config.api?.length) return [] - return loadApiSpecs(config.api).flatMap(specToIndexes) -} - -export const { GET } = createSearchAPI('advanced', { - indexes: async (): Promise => { - const pages = source.getPages() - const indexes = await Promise.all( - pages.map(async (page): Promise => { - const data = page.data as PageData - let structuredData: StructuredData | undefined = data.structuredData - - if (!structuredData && data.load) { - try { - const loaded = await data.load() - structuredData = loaded.structuredData - } catch (error) { - console.error(`Failed to load structured data for ${page.url}:`, error) - } - } - - return { - id: page.url, - url: page.url, - title: data.title ?? '', - description: data.description ?? '', - structuredData: structuredData ?? { headings: [], contents: [] }, - } - }) - ) - return [...indexes, ...buildApiIndexes()] - }, -}) diff --git a/packages/chronicle/src/app/apis/[[...slug]]/layout.module.css b/packages/chronicle/src/app/apis/[[...slug]]/layout.module.css deleted file mode 100644 index a3c8ffe..0000000 --- a/packages/chronicle/src/app/apis/[[...slug]]/layout.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.layout { - height: 100vh; - overflow: hidden; -} - -.body { - overflow: hidden; -} - -.sidebar { - height: 100%; -} - -.content { - height: 100%; - overflow-y: auto; - padding-right: 0; -} - -.hiddenSearch { - display: none; -} diff --git a/packages/chronicle/src/app/apis/[[...slug]]/layout.tsx b/packages/chronicle/src/app/apis/[[...slug]]/layout.tsx deleted file mode 100644 index 98c18cf..0000000 --- a/packages/chronicle/src/app/apis/[[...slug]]/layout.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { cx } from 'class-variance-authority' -import { loadConfig } from '@/lib/config' -import { loadApiSpecs } from '@/lib/openapi' -import { buildApiPageTree } from '@/lib/api-routes' -import { getTheme } from '@/themes/registry' -import { Search } from '@/components/ui/search' -import styles from './layout.module.css' - -export default function ApiLayout({ children }: { children: React.ReactNode }) { - const config = loadConfig() - const { Layout, className } = getTheme(config.theme?.name) - const specs = loadApiSpecs(config.api ?? []) - const tree = buildApiPageTree(specs) - - return ( - - - {children} - - ) -} diff --git a/packages/chronicle/src/app/apis/[[...slug]]/page.tsx b/packages/chronicle/src/app/apis/[[...slug]]/page.tsx deleted file mode 100644 index 8c9c831..0000000 --- a/packages/chronicle/src/app/apis/[[...slug]]/page.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import type { Metadata, ResolvingMetadata } from 'next' -import { notFound } from 'next/navigation' -import type { OpenAPIV3 } from 'openapi-types' -import { Flex, Headline, Text } from '@raystack/apsara' -import { loadConfig } from '@/lib/config' -import { loadApiSpecs } from '@/lib/openapi' -import { buildApiRoutes, findApiOperation } from '@/lib/api-routes' -import { EndpointPage } from '@/components/api' - -interface PageProps { - params: Promise<{ slug?: string[] }> -} - -export async function generateMetadata( - { params }: PageProps, - parent: ResolvingMetadata, -): Promise { - const { slug } = await params - const config = loadConfig() - const specs = loadApiSpecs(config.api ?? []) - const parentMetadata = await parent - - if (!slug || slug.length === 0) { - const apiDescription = `API documentation for ${config.title}` - const metadata: Metadata = { - title: 'API Reference', - description: apiDescription, - } - if (config.url) { - metadata.openGraph = { - ...parentMetadata.openGraph, - title: 'API Reference', - description: apiDescription, - images: [{ url: `/og?title=${encodeURIComponent('API Reference')}&description=${encodeURIComponent(apiDescription)}`, width: 1200, height: 630 }], - } - metadata.twitter = { - ...parentMetadata.twitter, - title: 'API Reference', - description: apiDescription, - } - } - return metadata - } - - const match = findApiOperation(specs, slug) - if (!match) return {} - - const operation = match.operation as OpenAPIV3.OperationObject - const title = operation.summary ?? `${match.method.toUpperCase()} ${match.path}` - const description = operation.description - - const metadata: Metadata = { title, description } - - if (config.url) { - const ogParams = new URLSearchParams({ title }) - if (description) ogParams.set('description', description) - metadata.openGraph = { - ...parentMetadata.openGraph, - title, - description, - images: [{ url: `/og?${ogParams.toString()}`, width: 1200, height: 630 }], - } - metadata.twitter = { - ...parentMetadata.twitter, - title, - description, - } - } - - return metadata -} - -export default async function ApiPage({ params }: PageProps) { - const { slug } = await params - const config = loadConfig() - const specs = loadApiSpecs(config.api ?? []) - - if (!slug || slug.length === 0) { - return - } - - const match = findApiOperation(specs, slug) - if (!match) notFound() - - return ( - - ) -} - -function ApiLanding({ specs }: { specs: { name: string; document: OpenAPIV3.Document }[] }) { - return ( - - API Reference - {specs.map((spec) => ( - - {spec.name} - {spec.document.info.description && ( - {spec.document.info.description} - )} - - ))} - - ) -} - -export function generateStaticParams() { - const config = loadConfig() - const specs = loadApiSpecs(config.api ?? []) - return [{ slug: [] }, ...buildApiRoutes(specs)] -} diff --git a/packages/chronicle/src/app/layout.tsx b/packages/chronicle/src/app/layout.tsx deleted file mode 100644 index c71ed16..0000000 --- a/packages/chronicle/src/app/layout.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import '@raystack/apsara/normalize.css' -import '@raystack/apsara/style.css' -import type { Metadata } from 'next' -import { loadConfig } from '@/lib/config' -import { Providers } from './providers' - -const config = loadConfig() - -export const metadata: Metadata = { - title: { - default: config.title, - template: `%s | ${config.title}`, - }, - description: config.description, - ...(config.url && { - metadataBase: new URL(config.url), - openGraph: { - title: config.title, - description: config.description, - url: config.url, - siteName: config.title, - type: 'website', - images: [{ url: '/og?title=' + encodeURIComponent(config.title), width: 1200, height: 630 }], - }, - twitter: { - card: 'summary_large_image', - title: config.title, - description: config.description, - images: ['/og?title=' + encodeURIComponent(config.title)], - }, - }), -} - -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - - {config.url && ( - - )} - {children} - - - ) -} diff --git a/packages/chronicle/src/app/llms-full.txt/route.ts b/packages/chronicle/src/app/llms-full.txt/route.ts deleted file mode 100644 index beb9c70..0000000 --- a/packages/chronicle/src/app/llms-full.txt/route.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { source } from '@/lib/source' -import { loadConfig } from '@/lib/config' -import { getLLMText } from '@/lib/get-llm-text' - -export const revalidate = false - -export async function GET() { - const config = loadConfig() - - if (!config.llms?.enabled) { - return new Response('Not Found', { status: 404 }) - } - - const scan = source.getPages().map(getLLMText) - const scanned = await Promise.all(scan) - - return new Response(scanned.join('\n\n')) -} diff --git a/packages/chronicle/src/app/llms.txt/route.ts b/packages/chronicle/src/app/llms.txt/route.ts deleted file mode 100644 index 664c6cf..0000000 --- a/packages/chronicle/src/app/llms.txt/route.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { source } from '@/lib/source' -import { loadConfig } from '@/lib/config' -import { llms } from 'fumadocs-core/source' - -export const revalidate = false - -export function GET() { - const config = loadConfig() - - if (!config.llms?.enabled) { - return new Response('Not Found', { status: 404 }) - } - - return new Response(llms(source).index()) -} diff --git a/packages/chronicle/src/app/og/route.tsx b/packages/chronicle/src/app/og/route.tsx deleted file mode 100644 index cef9a6f..0000000 --- a/packages/chronicle/src/app/og/route.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { ImageResponse } from 'next/og' -import type { NextRequest } from 'next/server' -import { loadConfig } from '@/lib/config' - -export async function GET(request: NextRequest) { - const { searchParams } = request.nextUrl - const title = searchParams.get('title') ?? loadConfig().title - const description = searchParams.get('description') ?? '' - const siteName = loadConfig().title - - return new ImageResponse( - ( -
-
- {siteName} -
-
- {title} -
- {description && ( -
- {description} -
- )} -
- ), - { - width: 1200, - height: 630, - } - ) -} diff --git a/packages/chronicle/src/app/providers.tsx b/packages/chronicle/src/app/providers.tsx deleted file mode 100644 index 389b797..0000000 --- a/packages/chronicle/src/app/providers.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client' - -import { ThemeProvider } from '@raystack/apsara' -import type { ReactNode } from 'react' - -export function Providers({ children }: { children: ReactNode }) { - return {children} -} diff --git a/packages/chronicle/src/app/robots.ts b/packages/chronicle/src/app/robots.ts deleted file mode 100644 index 9b43e68..0000000 --- a/packages/chronicle/src/app/robots.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { MetadataRoute } from 'next' -import { loadConfig } from '@/lib/config' - -export default function robots(): MetadataRoute.Robots { - const config = loadConfig() - return { - rules: { userAgent: '*', allow: '/' }, - ...(config.url && { sitemap: `${config.url}/sitemap.xml` }), - } -} diff --git a/packages/chronicle/src/app/sitemap.ts b/packages/chronicle/src/app/sitemap.ts deleted file mode 100644 index 4da7cdf..0000000 --- a/packages/chronicle/src/app/sitemap.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { MetadataRoute } from 'next' -import { loadConfig } from '@/lib/config' -import { source } from '@/lib/source' -import { loadApiSpecs } from '@/lib/openapi' -import { buildApiRoutes } from '@/lib/api-routes' - -export default function sitemap(): MetadataRoute.Sitemap { - const config = loadConfig() - if (!config.url) return [] - - const baseUrl = config.url.replace(/\/$/, '') - - const docPages = source.getPages().map((page) => ({ - url: `${baseUrl}/${page.slugs.join('/')}`, - ...(page.data.lastModified && { lastModified: new Date(page.data.lastModified) }), - })) - - const apiPages = config.api?.length - ? buildApiRoutes(loadApiSpecs(config.api)).map((route) => ({ - url: `${baseUrl}/apis/${route.slug.join('/')}`, - })) - : [] - - return [ - { url: baseUrl }, - ...docPages, - ...apiPages, - ] -} diff --git a/packages/chronicle/src/components/ui/search.tsx b/packages/chronicle/src/components/ui/search.tsx index fb56aaa..543d353 100644 --- a/packages/chronicle/src/components/ui/search.tsx +++ b/packages/chronicle/src/components/ui/search.tsx @@ -7,7 +7,6 @@ import { cx } from "class-variance-authority"; import { useDocsSearch } from "fumadocs-core/search/client"; import type { SortedResult } from "fumadocs-core/search"; import { DocumentIcon, HashtagIcon } from "@heroicons/react/24/outline"; -import { isMacOs } from "react-device-detect"; import { MethodBadge } from "@/components/api/method-badge"; import styles from "./search.module.css"; @@ -15,7 +14,8 @@ function SearchShortcutKey({ className }: { className?: string }) { const [key, setKey] = useState("⌘"); useEffect(() => { - setKey(isMacOs ? "⌘" : "Ctrl"); + const isMac = navigator.platform?.toUpperCase().includes("MAC"); + setKey(isMac ? "⌘" : "Ctrl"); }, []); return ( From eab908fa80b78e421881d674343bf91b1d3a9979 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 10:13:00 +0530 Subject: [PATCH 02/38] feat: add Vite + Nitro server structure, pages, and source rewrite - src/server/: Nitro route handlers (api/, routes/), SSR catch-all, vite-config.ts with Nitro + fumadocs-mdx/vite (index: false) - src/server/api/: health, search, specs, apis-proxy - src/server/routes/: og, robots.txt, sitemap.xml, llms.txt, [...] SSR - src/pages/: DocsLayout, DocsPage, ApiLayout, ApiPage, NotFound - src/lib/source.ts: rewrite with fs/promises + gray-matter + fumadocs-core loader() - src/lib/head.tsx, page-context.tsx: SSR meta and React context - src/types/globals.d.ts: Vite build-time constant types - Add nitro, h3, remark-parse to deps - Add .gitignore and lint script to chronicle package Co-Authored-By: Claude Opus 4.6 (1M context) --- bun.lock | 271 ++++++++++++++++-- packages/chronicle/.gitignore | 3 + packages/chronicle/biome.json | 2 +- packages/chronicle/package.json | 5 +- packages/chronicle/src/lib/head.tsx | 49 ++++ packages/chronicle/src/lib/page-context.tsx | 120 ++++++++ packages/chronicle/src/lib/source.ts | 175 ++++++++--- .../chronicle/src/pages/ApiLayout.module.css | 22 ++ packages/chronicle/src/pages/ApiLayout.tsx | 33 +++ packages/chronicle/src/pages/ApiPage.tsx | 73 +++++ packages/chronicle/src/pages/DocsLayout.tsx | 18 ++ packages/chronicle/src/pages/DocsPage.tsx | 43 +++ packages/chronicle/src/pages/NotFound.tsx | 17 ++ packages/chronicle/src/server/App.tsx | 67 +++++ .../chronicle/src/server/api/apis-proxy.ts | 69 +++++ packages/chronicle/src/server/api/health.ts | 5 + packages/chronicle/src/server/api/search.ts | 170 +++++++++++ packages/chronicle/src/server/api/specs.ts | 9 + .../src/server/build-search-index.ts | 117 ++++++++ .../chronicle/src/server/entry-client.tsx | 84 ++++++ .../chronicle/src/server/entry-server.tsx | 35 +++ packages/chronicle/src/server/index.html | 12 + packages/chronicle/src/server/routes/[...].ts | 64 +++++ .../chronicle/src/server/routes/llms.txt.ts | 61 ++++ packages/chronicle/src/server/routes/og.tsx | 75 +++++ .../chronicle/src/server/routes/robots.txt.ts | 11 + .../src/server/routes/sitemap.xml.ts | 39 +++ .../chronicle/src/server/utils/safe-path.ts | 17 ++ packages/chronicle/src/server/vite-config.ts | 68 +++++ packages/chronicle/src/types/globals.d.ts | 3 + 30 files changed, 1672 insertions(+), 65 deletions(-) create mode 100644 packages/chronicle/.gitignore create mode 100644 packages/chronicle/src/lib/head.tsx create mode 100644 packages/chronicle/src/lib/page-context.tsx create mode 100644 packages/chronicle/src/pages/ApiLayout.module.css create mode 100644 packages/chronicle/src/pages/ApiLayout.tsx create mode 100644 packages/chronicle/src/pages/ApiPage.tsx create mode 100644 packages/chronicle/src/pages/DocsLayout.tsx create mode 100644 packages/chronicle/src/pages/DocsPage.tsx create mode 100644 packages/chronicle/src/pages/NotFound.tsx create mode 100644 packages/chronicle/src/server/App.tsx create mode 100644 packages/chronicle/src/server/api/apis-proxy.ts create mode 100644 packages/chronicle/src/server/api/health.ts create mode 100644 packages/chronicle/src/server/api/search.ts create mode 100644 packages/chronicle/src/server/api/specs.ts create mode 100644 packages/chronicle/src/server/build-search-index.ts create mode 100644 packages/chronicle/src/server/entry-client.tsx create mode 100644 packages/chronicle/src/server/entry-server.tsx create mode 100644 packages/chronicle/src/server/index.html create mode 100644 packages/chronicle/src/server/routes/[...].ts create mode 100644 packages/chronicle/src/server/routes/llms.txt.ts create mode 100644 packages/chronicle/src/server/routes/og.tsx create mode 100644 packages/chronicle/src/server/routes/robots.txt.ts create mode 100644 packages/chronicle/src/server/routes/sitemap.xml.ts create mode 100644 packages/chronicle/src/server/utils/safe-path.ts create mode 100644 packages/chronicle/src/server/vite-config.ts create mode 100644 packages/chronicle/src/types/globals.d.ts diff --git a/bun.lock b/bun.lock index f1b0c41..8ae2b20 100644 --- a/bun.lock +++ b/bun.lock @@ -18,26 +18,36 @@ "@codemirror/view": "^6.39.14", "@heroicons/react": "^2.2.0", "@raystack/apsara": "^0.56.0", - "@types/unist": "^3.0.3", + "@shikijs/rehype": "^4.0.2", + "@vitejs/plugin-react": "^6.0.1", "chalk": "^5.6.2", "class-variance-authority": "^0.7.1", "codemirror": "^6.0.2", "commander": "^14.0.2", "fumadocs-core": "16.6.15", "fumadocs-mdx": "^14.2.6", + "glob": "^11.0.0", + "gray-matter": "^4.0.3", + "h3": "^2.0.1-rc.16", "lodash": "^4.17.23", "mermaid": "^11.13.0", - "next": "16.1.6", + "minisearch": "^7.2.0", + "nitro": "latest", + "openapi-types": "^12.1.3", "react": "^19.0.0", - "react-device-detect": "^2.2.3", "react-dom": "^19.0.0", - "remark-attr": "^0.11.1", + "react-router-dom": "^7.13.1", "remark-directive": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.1", + "remark-mdx-frontmatter": "^5.2.0", + "remark-parse": "^11.0.0", + "satori": "^0.25.0", "slugify": "^1.6.6", "unified": "^11.0.5", "unist-util-visit": "^5.1.0", + "vite": "^8.0.0", "yaml": "^2.8.2", - "zod": "^4.3.6", }, "devDependencies": { "@biomejs/biome": "^2.3.13", @@ -48,7 +58,6 @@ "@types/react": "^19.2.10", "@types/react-dom": "^19.2.3", "@types/semver": "^7.7.1", - "openapi-types": "^12.1.3", "semver": "^7.7.4", "typescript": "5.9.3", }, @@ -113,8 +122,12 @@ "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], + "@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], + "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], @@ -235,6 +248,8 @@ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="], + "@lezer/common": ["@lezer/common@1.5.1", "", {}, "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw=="], "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="], @@ -249,6 +264,8 @@ "@mermaid-js/parser": ["@mermaid-js/parser@1.0.1", "", { "dependencies": { "langium": "^4.0.0" } }, "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + "@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="], "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="], @@ -269,6 +286,8 @@ "@orama/orama": ["@orama/orama@3.1.18", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="], + "@oxc-project/types": ["@oxc-project/types@0.120.0", "", {}, "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], @@ -397,6 +416,38 @@ "@raystack/tools-config": ["@raystack/tools-config@0.56.0", "", { "peerDependencies": { "@biomejs/biome": ">=2.0.0" }, "optionalPeers": ["@biomejs/biome"] }, "sha512-Vh4Ei++2g0hycTFmCF5vjp8VIUasl9TUiQRNv517otYJDY49/C6gqjDt137NDrBRPyguJ+CL+SPY4sRt7UxQxQ=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.10", "", { "os": "android", "cpu": "arm64" }, "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10", "", { "os": "linux", "cpu": "arm" }, "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "x64" }, "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.10", "", { "os": "linux", "cpu": "x64" }, "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.10", "", { "os": "none", "cpu": "arm64" }, "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.10", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.10", "", { "os": "win32", "cpu": "x64" }, "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], + "@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="], "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="], @@ -417,6 +468,8 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + "@shuding/opentype.js": ["@shuding/opentype.js@1.4.0-beta.0", "", { "dependencies": { "fflate": "^0.7.3", "string.prototype.codepointat": "^0.2.1" }, "bin": { "ot": "bin/ot" } }, "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], @@ -431,6 +484,8 @@ "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.18", "", {}, "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], @@ -529,6 +584,8 @@ "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -541,8 +598,16 @@ "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="], + "brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + + "camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="], + "caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -579,7 +644,7 @@ "color-convert": ["color-convert@3.1.3", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg=="], - "color-name": ["color-name@2.1.0", "", {}, "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="], + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], "color-string": ["color-string@2.1.4", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg=="], @@ -591,10 +656,28 @@ "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "crossws": ["crossws@0.4.4", "", { "peerDependencies": { "srvx": ">=0.7.1" }, "optionalPeers": ["srvx"] }, "sha512-w6c4OdpRNnudVmcgr7brb/+/HmYjMQvYToO/oTrprTwxRUiom3LYWU1PMWuD006okbUWpII1Ea9/+kwpUfmyRg=="], + + "css-background-parser": ["css-background-parser@0.1.0", "", {}, "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="], + + "css-box-shadow": ["css-box-shadow@1.0.0-3", "", {}, "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="], + + "css-color-keywords": ["css-color-keywords@1.0.0", "", {}, "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="], + + "css-gradient-parser": ["css-gradient-parser@0.0.17", "", {}, "sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg=="], + + "css-to-react-native": ["css-to-react-native@3.2.0", "", { "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", "postcss-value-parser": "^4.0.2" } }, "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="], @@ -675,6 +758,8 @@ "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], + "db0": ["db0@0.3.4", "", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], @@ -691,14 +776,22 @@ "dompurify": ["dompurify@3.3.2", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ=="], + "emoji-regex-xs": ["emoji-regex-xs@2.0.1", "", {}, "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g=="], + + "env-runner": ["env-runner@0.1.6", "", { "dependencies": { "crossws": "^0.4.4", "httpxy": "^0.3.1", "srvx": "^0.11.9" }, "peerDependencies": { "miniflare": "^4.0.0" }, "optionalPeers": ["miniflare"], "bin": { "env-runner": "dist/cli.mjs" } }, "sha512-fSb7X1zdda8k6611a6/SdSQpDe7a/bqMz2UWdbHjk9YWzpUR4/fn9YtE/hqgGQ2nhvVN0zUtcL1SRMKwIsDbAA=="], + "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], "esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="], "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], "estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="], @@ -717,8 +810,20 @@ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], + + "fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "fflate": ["fflate@0.7.4", "", {}, "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "fumadocs-core": ["fumadocs-core@16.6.15", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.8.1", "@orama/orama": "^3.1.18", "@shikijs/rehype": "^4.0.2", "@shikijs/transformers": "^4.0.2", "estree-util-value-to-estree": "^3.5.0", "github-slugger": "^2.0.0", "hast-util-to-estree": "^3.1.3", "hast-util-to-jsx-runtime": "^2.3.6", "image-size": "^2.0.2", "mdast-util-mdx": "^3.0.0", "mdast-util-to-markdown": "^2.1.2", "negotiator": "^1.0.0", "npm-to-yarn": "^3.0.1", "path-to-regexp": "^8.3.0", "remark": "^15.0.1", "remark-gfm": "^4.0.1", "remark-rehype": "^11.1.2", "scroll-into-view-if-needed": "^3.1.0", "shiki": "^4.0.2", "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3" }, "peerDependencies": { "@mdx-js/mdx": "*", "@mixedbread/sdk": "^0.46.0", "@orama/core": "1.x.x", "@oramacloud/client": "2.x.x", "@tanstack/react-router": "1.x.x", "@types/estree-jsx": "*", "@types/hast": "*", "@types/mdast": "*", "@types/react": "*", "algoliasearch": "5.x.x", "flexsearch": "*", "lucide-react": "*", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "7.x.x", "waku": "^0.26.0 || ^0.27.0 || ^1.0.0", "zod": "4.x.x" }, "optionalPeers": ["@mdx-js/mdx", "@mixedbread/sdk", "@orama/core", "@oramacloud/client", "@tanstack/react-router", "@types/estree-jsx", "@types/hast", "@types/mdast", "@types/react", "algoliasearch", "flexsearch", "lucide-react", "next", "react", "react-dom", "react-router", "waku", "zod"] }, "sha512-N6gbXicmaylWeaEFu9vpw25dZK29rPPjalrcIqDRgDklCFkxHn0fsagDMZiSjFBn4RfWRErL6mYmu24WSwosew=="], "fumadocs-mdx": ["fumadocs-mdx@14.2.7", "", { "dependencies": { "@mdx-js/mdx": "^3.1.1", "@standard-schema/spec": "^1.1.0", "chokidar": "^5.0.0", "esbuild": "^0.27.3", "estree-util-value-to-estree": "^3.5.0", "js-yaml": "^4.1.1", "mdast-util-to-markdown": "^2.1.2", "picocolors": "^1.1.1", "picomatch": "^4.0.3", "remark-mdx": "^3.1.1", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3", "zod": "^4.3.6" }, "peerDependencies": { "@fumadocs/mdx-remote": "^1.4.0", "@types/mdast": "*", "@types/mdx": "*", "@types/react": "*", "fumadocs-core": "^15.0.0 || ^16.0.0", "mdast-util-directive": "*", "mdast-util-mdx-jsx": "*", "next": "^15.3.0 || ^16.0.0", "react": "*", "vite": "6.x.x || 7.x.x" }, "optionalPeers": ["@fumadocs/mdx-remote", "@types/mdast", "@types/mdx", "@types/react", "mdast-util-directive", "mdast-util-mdx-jsx", "next", "react", "vite"], "bin": { "fumadocs-mdx": "dist/bin.js" } }, "sha512-Q2W79F7wpwhq4HoYPw9GnMpf5ZmpdU7YzZND7EWwwOiWddfyzPh6EH/z7MFhhdhsTqRh9/kwwsu9XslWDRRPsg=="], @@ -727,6 +832,12 @@ "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], + + "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], + + "h3": ["h3@2.0.1-rc.18", "", { "dependencies": { "rou3": "^0.8.1", "srvx": "^0.11.12" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"], "bin": { "h3": "bin/h3.mjs" } }, "sha512-2EdYEOIJwZHfhfdxvqZsmmUz4tgwzQSuzre+l50j+voHJV4m7j3zw2lYLgHoyfkCF9EAZcaH4ea0zH/hgcs9Yg=="], + "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], "hast-util-to-estree": ["hast-util-to-estree@3.1.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="], @@ -739,10 +850,14 @@ "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], - "html-element-attributes": ["html-element-attributes@2.3.0", "", {}, "sha512-RJv2v3BBaYSc0ODHwT0sqWI+2lFs6DATBvCRnW20BDmULxoAWvfT6r28uL8LcW1a9/eqUl+1DccUOJzw00qVXQ=="], + "hex-rgb": ["hex-rgb@4.3.0", "", {}, "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw=="], + + "hookable": ["hookable@6.1.0", "", {}, "sha512-ZoKZSJgu8voGK2geJS+6YtYjvIzu9AOM/KZXsBxr83uhLL++e9pEv/dlgwgy3dvHg06kTz6JOh1hk3C8Ceiymw=="], "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + "httpxy": ["httpxy@0.3.1", "", {}, "sha512-XjG/CEoofEisMrnFr0D6U6xOZ4mRfnwcYQ9qvvnT4lvnX8BoeA3x3WofB75D+vZwpaobFVkBIHrZzoK40w8XSw=="], + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "image-size": ["image-size@2.0.2", "", { "bin": { "image-size": "bin/image-size.js" } }, "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w=="], @@ -757,11 +872,15 @@ "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], - "is-whitespace-character": ["is-whitespace-character@1.0.4", "", {}, "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="], "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], @@ -769,30 +888,60 @@ "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + "langium": ["langium@4.2.1", "", { "dependencies": { "chevrotain": "~11.1.1", "chevrotain-allstar": "~0.3.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.1.0" } }, "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ=="], "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "linebreak": ["linebreak@1.1.0", "", { "dependencies": { "base64-js": "0.0.8", "unicode-trie": "^2.0.0" } }, "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ=="], + "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], "lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="], "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + "lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="], + "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], - "md-attr-parser": ["md-attr-parser@1.3.0", "", {}, "sha512-KTVlfU5Oxo/6kd0YZ2mLP3eWJj+5vzh5mBCxLo3yGl1fzHIgxmtadbE9tHb7TbUBi3XZbl+P0xKeGmakat135w=="], - "mdast-util-directive": ["mdast-util-directive@3.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q=="], "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + "mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="], + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], @@ -829,6 +978,8 @@ "micromark-extension-directive": ["micromark-extension-directive@4.0.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "parse-entities": "^4.0.0" } }, "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg=="], + "micromark-extension-frontmatter": ["micromark-extension-frontmatter@2.0.0", "", { "dependencies": { "fault": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg=="], + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], @@ -895,6 +1046,12 @@ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + "minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "minisearch": ["minisearch@7.2.0", "", {}, "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg=="], + "mlly": ["mlly@1.8.1", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -905,20 +1062,40 @@ "next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="], + "nf3": ["nf3@0.3.13", "", {}, "sha512-drDt0yl4d/yUhlpD0GzzqahSpA5eUNeIfFq0/aoZb0UlPY0ZwP4u1EfREVvZrYdEnJ3OU9Le9TrzbvWgEkkeKw=="], + + "nitro": ["nitro@3.0.260311-beta", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.4", "db0": "^0.3.4", "env-runner": "^0.1.6", "h3": "^2.0.1-rc.16", "hookable": "^6.0.1", "nf3": "^0.3.11", "ocache": "^0.1.2", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "rolldown": "^1.0.0-rc.8", "srvx": "^0.11.9", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.6" }, "peerDependencies": { "dotenv": "*", "giget": "*", "jiti": "^2.6.1", "rollup": "^4.59.0", "vite": "^7 || ^8 || >=8.0.0-0", "xml2js": "^0.6.2", "zephyr-agent": "^0.1.15" }, "optionalPeers": ["dotenv", "giget", "jiti", "rollup", "vite", "xml2js", "zephyr-agent"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-0o0fJ9LUh4WKUqJNX012jyieUOtMCnadkNDWr0mHzdraoHpJP/1CGNefjRyZyMXSpoJfwoWdNEZu2iGf35TUvQ=="], + "npm-to-yarn": ["npm-to-yarn@3.0.1", "", {}, "sha512-tt6PvKu4WyzPwWUzy/hvPFqn+uwXO0K1ZHka8az3NnrhWJDmSqI8ncWq0fkL0k/lmmi5tAC11FXwXuh0rFbt1A=="], + "ocache": ["ocache@0.1.4", "", { "dependencies": { "ohash": "^2.0.11" } }, "sha512-e7geNdWjxSnvsSgvLuPvgKgu7ubM10ZmTPOgpr7mz2BXYtvjMKTiLhjFi/gWU8chkuP6hNkZBsa9LzOusyaqkQ=="], + + "ofetch": ["ofetch@2.0.0-alpha.3", "", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="], + + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + "pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], + + "parse-css-color": ["parse-css-color@0.2.1", "", { "dependencies": { "color-name": "^1.1.4", "hex-rgb": "^4.1.0" } }, "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg=="], + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], @@ -933,7 +1110,9 @@ "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], - "postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], "prism-react-renderer": ["prism-react-renderer@2.4.1", "", { "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": ">=16.0.0" } }, "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig=="], @@ -945,14 +1124,16 @@ "react-day-picker": ["react-day-picker@9.13.2", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-IMPiXfXVIAuR5Yk58DDPBC8QKClrhdXV+Tr/alBrwrHUw0qDDYB1m5zPNuTnnPIr/gmJ4ChMxmtqPdxm8+R4Eg=="], - "react-device-detect": ["react-device-detect@2.2.3", "", { "dependencies": { "ua-parser-js": "^1.0.33" }, "peerDependencies": { "react": ">= 0.14.0", "react-dom": ">= 0.14.0" } }, "sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw=="], - "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], + "react-router": ["react-router@7.13.1", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA=="], + + "react-router-dom": ["react-router-dom@7.13.1", "", { "dependencies": { "react-router": "7.13.1" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw=="], + "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], @@ -975,16 +1156,16 @@ "remark": ["remark@15.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A=="], - "remark-attr": ["remark-attr@0.11.1", "", { "dependencies": { "html-element-attributes": "^2.0.0", "is-whitespace-character": "^1.0.4", "md-attr-parser": "^1.3.0", "remark-footnotes": "^1.0.0" } }, "sha512-NnzURvBJ52c58L3AohBk5qSTBPXdMKsf5+w9L0YNhi/HCtv7ZA9dNRz5NsPIxLtIHhoZIjTqQsMzoyggyPVvkQ=="], - "remark-directive": ["remark-directive@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-directive": "^3.0.0", "micromark-extension-directive": "^4.0.0", "unified": "^11.0.0" } }, "sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA=="], - "remark-footnotes": ["remark-footnotes@1.0.0", "", {}, "sha512-X9Ncj4cj3/CIvLI2Z9IobHtVi8FVdUrdJkCNaL9kdX8ohfsi18DXHsCVd/A7ssARBdccdDb5ODnt62WuEWaM/g=="], + "remark-frontmatter": ["remark-frontmatter@5.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-frontmatter": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0", "unified": "^11.0.0" } }, "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ=="], "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], + "remark-mdx-frontmatter": ["remark-mdx-frontmatter@5.2.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "estree-util-value-to-estree": "^3.0.0", "toml": "^3.0.0", "unified": "^11.0.0", "unist-util-mdx-define": "^1.0.0", "yaml": "^2.0.0" } }, "sha512-U/hjUYTkQqNjjMRYyilJgLXSPF65qbLPdoESOkXyrwz2tVyhAnm4GUKhfXqOOS9W34M3545xEMq+aMpHgVjEeQ=="], + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], @@ -995,22 +1176,38 @@ "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], + "rolldown": ["rolldown@1.0.0-rc.10", "", { "dependencies": { "@oxc-project/types": "=0.120.0", "@rolldown/pluginutils": "1.0.0-rc.10" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.10", "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", "@rolldown/binding-darwin-x64": "1.0.0-rc.10", "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA=="], + + "rou3": ["rou3@0.8.1", "", {}, "sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA=="], + "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "satori": ["satori@0.25.0", "", { "dependencies": { "@shuding/opentype.js": "1.4.0-beta.0", "css-background-parser": "^0.1.0", "css-box-shadow": "1.0.0-3", "css-gradient-parser": "^0.0.17", "css-to-react-native": "^3.0.0", "emoji-regex-xs": "^2.0.1", "escape-html": "^1.0.3", "linebreak": "^1.1.0", "parse-css-color": "^0.2.1", "postcss-value-parser": "^4.2.0", "yoga-layout": "^3.2.1" } }, "sha512-utINfLxrYrmSnLvxFT4ZwgwWa8KOjrz7ans32V5wItgHVmzESl/9i33nE38uG0miycab8hUqQtDlOpqrIpB/iw=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], + "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="], "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], @@ -1021,8 +1218,16 @@ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "srvx": ["srvx@0.11.12", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-AQfrGqntqVPXgP03pvBDN1KyevHC+KmYVqb8vVf4N+aomQqdhaZxjvoVp+AOm4u6x+GgNQY3MVzAUIn+TqwkOA=="], + + "string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + "strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="], + "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], @@ -1033,10 +1238,14 @@ "stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="], + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="], + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], @@ -1047,16 +1256,20 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="], - "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], + + "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + "unist-util-mdx-define": ["unist-util-mdx-define@1.1.2", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-9ncH7i7TN5Xn7/tzX5bE3rXgz1X/u877gYVAUB3mLeTKYJmQHmqKTDBi6BTGXV7AeolBCI9ErcVsOt2qryoD0g=="], + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], "unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="], @@ -1069,6 +1282,8 @@ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "unstorage": ["unstorage@2.0.0-alpha.7", "", { "peerDependencies": { "@azure/app-configuration": "^1.11.0", "@azure/cosmos": "^4.9.1", "@azure/data-tables": "^13.3.2", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.31.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.13.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.36.2", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.9.3", "lru-cache": "^11.2.6", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-ELPztchk2zgFJnakyodVY3vJWGW9jy//keJ32IOJVGUMyaPydwcA1FtVvWqT0TNRch9H+cMNEGllfVFfScImog=="], + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], @@ -1081,6 +1296,8 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + "vite": ["vite@8.0.1", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.10", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw=="], + "vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], "vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="], @@ -1095,8 +1312,12 @@ "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], @@ -1181,6 +1402,10 @@ "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + "color-convert/color-name": ["color-name@2.1.0", "", {}, "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="], + + "color-string/color-name": ["color-name@2.1.0", "", {}, "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="], + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], @@ -1189,16 +1414,24 @@ "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "radix-ui/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.10", "", {}, "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg=="], + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + + "gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], } } diff --git a/packages/chronicle/.gitignore b/packages/chronicle/.gitignore new file mode 100644 index 0000000..8e27122 --- /dev/null +++ b/packages/chronicle/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.source diff --git a/packages/chronicle/biome.json b/packages/chronicle/biome.json index 22b44af..29c5617 100644 --- a/packages/chronicle/biome.json +++ b/packages/chronicle/biome.json @@ -1,3 +1,3 @@ { - "extends": ["@raystack/tools-config/biome.json"] + "extends": ["@raystack/tools-config/biome"] } diff --git a/packages/chronicle/package.json b/packages/chronicle/package.json index dcdd3f4..f0cbba7 100644 --- a/packages/chronicle/package.json +++ b/packages/chronicle/package.json @@ -15,7 +15,8 @@ "chronicle": "./bin/chronicle.js" }, "scripts": { - "build:cli": "bun build-cli.ts" + "build:cli": "bun build-cli.ts", + "lint": "biome lint src/" }, "devDependencies": { "@biomejs/biome": "^2.3.13", @@ -46,6 +47,7 @@ "fumadocs-mdx": "^14.2.6", "glob": "^11.0.0", "gray-matter": "^4.0.3", + "h3": "^2.0.1-rc.16", "lodash": "^4.17.23", "mermaid": "^11.13.0", "minisearch": "^7.2.0", @@ -55,6 +57,7 @@ "react-dom": "^19.0.0", "react-router-dom": "^7.13.1", "remark-directive": "^4.0.0", + "remark-parse": "^11.0.0", "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", "remark-mdx-frontmatter": "^5.2.0", diff --git a/packages/chronicle/src/lib/head.tsx b/packages/chronicle/src/lib/head.tsx new file mode 100644 index 0000000..74b0621 --- /dev/null +++ b/packages/chronicle/src/lib/head.tsx @@ -0,0 +1,49 @@ +import type { ChronicleConfig } from '@/types'; + +export interface HeadProps { + title: string; + description?: string; + config: ChronicleConfig; + jsonLd?: Record; +} + +export function Head({ title, description, config, jsonLd }: HeadProps) { + const fullTitle = `${title} | ${config.title}`; + const ogParams = new URLSearchParams({ title }); + if (description) ogParams.set('description', description); + + return ( + <> + {fullTitle} + {description && } + + {config.url && ( + <> + + {description && ( + + )} + + + + + + + + + {description && ( + + )} + + + )} + + {jsonLd && ( + + + diff --git a/packages/chronicle/src/server/routes/[...].ts b/packages/chronicle/src/server/routes/[...].ts new file mode 100644 index 0000000..9ec87d7 --- /dev/null +++ b/packages/chronicle/src/server/routes/[...].ts @@ -0,0 +1,64 @@ +import { defineHandler } from 'nitro'; +import React from 'react'; +import { mdxComponents } from '@/components/mdx'; +import { loadConfig } from '@/lib/config'; +import { loadApiSpecs } from '@/lib/openapi'; +import { buildPageTree, getPage, loadPageComponent } from '@/lib/source'; +import { render } from '../entry-server'; + +export default defineHandler(async event => { + const pathname = event.url.pathname; + const slug = + pathname === '/' ? [] : pathname.slice(1).split('/').filter(Boolean); + + const config = loadConfig(); + const apiSpecs = config.api?.length ? loadApiSpecs(config.api) : []; + + const [tree, sourcePage] = await Promise.all([ + buildPageTree(), + getPage(slug) + ]); + + let pageData = null; + const embeddedData: Record = { + config, + tree, + slug, + frontmatter: null, + filePath: null + }; + + if (sourcePage) { + const component = await loadPageComponent(sourcePage); + pageData = { + slug, + frontmatter: sourcePage.frontmatter, + content: component + ? React.createElement(component, { components: mdxComponents }) + : null + }; + embeddedData.frontmatter = sourcePage.frontmatter; + embeddedData.filePath = sourcePage.filePath; + } + + const appHtml = render(event.url.href, { config, tree, page: pageData, apiSpecs }); + + const safeJson = JSON.stringify(embeddedData).replace(/window.__PAGE_DATA__ = ${safeJson}`; + + const html = ` + + + + + ${dataScript} + + +
${appHtml}
+ + +`; + + event.res.headers.set('Content-Type', 'text/html'); + return html; +}); diff --git a/packages/chronicle/src/server/routes/llms.txt.ts b/packages/chronicle/src/server/routes/llms.txt.ts new file mode 100644 index 0000000..21fc3ef --- /dev/null +++ b/packages/chronicle/src/server/routes/llms.txt.ts @@ -0,0 +1,61 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import matter from 'gray-matter'; +import { defineHandler, HTTPError } from 'nitro'; +import { loadConfig } from '@/lib/config'; + +function getContentDir(): string { + return __CHRONICLE_CONTENT_DIR__ || path.join(process.cwd(), 'content'); +} + +async function scanPages(): Promise<{ title: string; url: string }[]> { + const contentDir = getContentDir(); + const pages: { title: string; url: string }[] = []; + + async function scan(dir: string, prefix: string[] = []) { + try { + const entries = await fs.readdir(dir, { withFileTypes: true }); + for (const entry of entries) { + if (entry.name.startsWith('.') || entry.name === 'node_modules') + continue; + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + await scan(fullPath, [...prefix, entry.name]); + continue; + } + + if (!entry.name.endsWith('.mdx') && !entry.name.endsWith('.md')) + continue; + + const raw = await fs.readFile(fullPath, 'utf-8'); + const { data: fm } = matter(raw); + const baseName = entry.name.replace(/\.(mdx|md)$/, ''); + const slugs = baseName === 'index' ? prefix : [...prefix, baseName]; + const url = slugs.length === 0 ? '/' : `/${slugs.join('/')}`; + + pages.push({ title: fm.title ?? baseName, url }); + } + } catch { + /* directory not readable */ + } + } + + await scan(contentDir); + return pages; +} + +export default defineHandler(async event => { + const config = loadConfig(); + + if (!config.llms?.enabled) { + throw new HTTPError({ status: 404, message: 'Not Found' }); + } + + const pages = await scanPages(); + const index = pages.map(p => `- [${p.title}](${p.url})`).join('\n'); + const body = `# ${config.title}\n\n${config.description ?? ''}\n\n${index}`; + + event.res.headers.set('Content-Type', 'text/plain'); + return body; +}); diff --git a/packages/chronicle/src/server/routes/og.tsx b/packages/chronicle/src/server/routes/og.tsx new file mode 100644 index 0000000..2fb15ca --- /dev/null +++ b/packages/chronicle/src/server/routes/og.tsx @@ -0,0 +1,75 @@ +import { defineHandler } from 'nitro'; +import React from 'react'; +import satori from 'satori'; +import { loadConfig } from '@/lib/config'; + +let fontData: ArrayBuffer | null = null; + +async function loadFont(): Promise { + if (fontData) return fontData; + + try { + const response = await fetch( + 'https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hiA.woff2' + ); + fontData = await response.arrayBuffer(); + } catch { + fontData = new ArrayBuffer(0); + } + + return fontData; +} + +export default defineHandler(async event => { + const config = loadConfig(); + const title = event.url.searchParams.get('title') ?? config.title; + const description = event.url.searchParams.get('description') ?? ''; + const siteName = config.title; + + const font = await loadFont(); + + const svg = await satori( +
+
+ {siteName} +
+
+ {title} +
+ {description && ( +
+ {description} +
+ )} +
, + { + width: 1200, + height: 630, + fonts: [ + { name: 'Inter', data: font, weight: 400, style: 'normal' as const }, + ], + }, + ); + + event.res.headers.set('Content-Type', 'image/svg+xml'); + event.res.headers.set('Cache-Control', 'public, max-age=86400'); + return svg; +}); diff --git a/packages/chronicle/src/server/routes/robots.txt.ts b/packages/chronicle/src/server/routes/robots.txt.ts new file mode 100644 index 0000000..31c06d0 --- /dev/null +++ b/packages/chronicle/src/server/routes/robots.txt.ts @@ -0,0 +1,11 @@ +import { defineHandler } from 'nitro'; +import { loadConfig } from '@/lib/config'; + +export default defineHandler(event => { + const config = loadConfig(); + const sitemap = config.url ? `\nSitemap: ${config.url}/sitemap.xml` : ''; + const body = `User-agent: *\nAllow: /${sitemap}`; + + event.res.headers.set('Content-Type', 'text/plain'); + return body; +}); diff --git a/packages/chronicle/src/server/routes/sitemap.xml.ts b/packages/chronicle/src/server/routes/sitemap.xml.ts new file mode 100644 index 0000000..62c9009 --- /dev/null +++ b/packages/chronicle/src/server/routes/sitemap.xml.ts @@ -0,0 +1,39 @@ +import { defineHandler } from 'nitro'; +import { buildApiRoutes } from '@/lib/api-routes'; +import { loadConfig } from '@/lib/config'; +import { loadApiSpecs } from '@/lib/openapi'; +import { getPages } from '@/lib/source'; + +export default defineHandler(async event => { + const config = loadConfig(); + + if (!config.url) { + event.res.headers.set('Content-Type', 'application/xml'); + return ''; + } + + const baseUrl = config.url.replace(/\/$/, ''); + + const pages = await getPages(); + const docPages = pages.map(page => { + const lastmod = page.frontmatter.lastModified + ? `${new Date(page.frontmatter.lastModified).toISOString()}` + : ''; + return `${baseUrl}/${page.slugs.join('/')}${lastmod}`; + }); + + const apiPages = config.api?.length + ? buildApiRoutes(loadApiSpecs(config.api)).map( + route => `${baseUrl}/apis/${route.slug.join('/')}` + ) + : []; + + const xml = ` + +${baseUrl} +${[...docPages, ...apiPages].join('\n')} +`; + + event.res.headers.set('Content-Type', 'application/xml'); + return xml; +}); diff --git a/packages/chronicle/src/server/utils/safe-path.ts b/packages/chronicle/src/server/utils/safe-path.ts new file mode 100644 index 0000000..4d3867d --- /dev/null +++ b/packages/chronicle/src/server/utils/safe-path.ts @@ -0,0 +1,17 @@ +import path from 'node:path'; + +/** + * Resolve a URL path within a base directory, preventing path traversal. + * Returns null if the resolved path escapes the base directory. + */ +export function safePath(baseDir: string, urlPath: string): string | null { + const decoded = decodeURIComponent(urlPath.split('?')[0]); + const resolved = path.resolve(baseDir, '.' + decoded); + if ( + !resolved.startsWith(path.resolve(baseDir) + path.sep) && + resolved !== path.resolve(baseDir) + ) { + return null; + } + return resolved; +} diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts new file mode 100644 index 0000000..a8a50e8 --- /dev/null +++ b/packages/chronicle/src/server/vite-config.ts @@ -0,0 +1,68 @@ +import react from '@vitejs/plugin-react'; +import mdx from 'fumadocs-mdx/vite'; +import { nitro } from 'nitro/vite'; +import path from 'node:path'; +import { type InlineConfig } from 'vite'; + +export interface ViteConfigOptions { + root: string; + contentDir: string; + preset?: string; +} + +export async function createViteConfig( + options: ViteConfigOptions +): Promise { + const { root, contentDir, preset } = options; + + return { + root, + configFile: false, + plugins: [ + nitro({ + serverDir: path.resolve(root, 'src/server'), + ...(preset && { preset }) + }), + mdx({}, { index: false }), + react() + ], + resolve: { + alias: { + '@': path.resolve(root, 'src'), + '@content': contentDir + }, + dedupe: [ + 'react', + 'react-dom', + 'react/jsx-runtime', + 'react/jsx-dev-runtime' + ] + }, + server: { + fs: { + allow: [root, contentDir] + } + }, + define: { + __CHRONICLE_CONTENT_DIR__: JSON.stringify(contentDir), + __CHRONICLE_PROJECT_ROOT__: JSON.stringify(path.resolve(contentDir, '..')) + }, + css: { + modules: { + localsConvention: 'camelCase' + } + }, + ssr: { + noExternal: ['@raystack/apsara', 'fumadocs-core'] + }, + environments: { + client: { + build: { + rollupOptions: { + input: path.resolve(root, 'src/server/entry-client.tsx') + } + } + } + } + }; +} diff --git a/packages/chronicle/src/types/globals.d.ts b/packages/chronicle/src/types/globals.d.ts new file mode 100644 index 0000000..32b9cd4 --- /dev/null +++ b/packages/chronicle/src/types/globals.d.ts @@ -0,0 +1,3 @@ +// Vite build-time constants (injected via define in vite-config.ts) +declare const __CHRONICLE_CONTENT_DIR__: string +declare const __CHRONICLE_PROJECT_ROOT__: string From ef25f7bb1371465fd8b238d4608be5d93dff5991 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 10:22:06 +0530 Subject: [PATCH 03/38] feat: rewrite CLI commands for Vite/Nitro, remove Next.js scaffolding - CLI dev/build/start/serve: use Vite programmatic API (no child process) - CLI init: remove .chronicle/ scaffolding, .next from gitignore - Remove process.ts (attachLifecycleHandlers), resolveNextCli - Strip scaffold.ts to detectPackageManager() + getChronicleVersion() - lib/config.ts: replace env vars with __CHRONICLE_* build-time constants - cli/utils/config.ts: remove env var fallback from resolveContentDir - tsconfig.json: remove .source paths and source.config.ts include Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/cli/commands/build.ts | 54 +++--- packages/chronicle/src/cli/commands/dev.ts | 50 ++--- packages/chronicle/src/cli/commands/init.ts | 181 ++++++++++--------- packages/chronicle/src/cli/commands/serve.ts | 88 ++++----- packages/chronicle/src/cli/commands/start.ts | 49 ++--- packages/chronicle/src/cli/index.ts | 28 +-- packages/chronicle/src/cli/utils/config.ts | 51 +++--- packages/chronicle/src/cli/utils/index.ts | 6 +- packages/chronicle/src/cli/utils/process.ts | 7 - packages/chronicle/src/cli/utils/resolve.ts | 10 +- packages/chronicle/src/cli/utils/scaffold.ts | 145 ++------------- packages/chronicle/src/lib/config.ts | 60 +++--- packages/chronicle/src/lib/source.ts | 52 ++++-- packages/chronicle/tsconfig.json | 5 +- 14 files changed, 325 insertions(+), 461 deletions(-) delete mode 100644 packages/chronicle/src/cli/utils/process.ts diff --git a/packages/chronicle/src/cli/commands/build.ts b/packages/chronicle/src/cli/commands/build.ts index 7ff457c..f027ab5 100644 --- a/packages/chronicle/src/cli/commands/build.ts +++ b/packages/chronicle/src/cli/commands/build.ts @@ -1,38 +1,30 @@ -import { Command } from 'commander' -import { spawn } from 'child_process' -import path from 'path' -import fs from 'fs' -import chalk from 'chalk' -import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils' +import chalk from 'chalk'; +import { Command } from 'commander'; +import { resolveContentDir } from '@/cli/utils/config'; +import { PACKAGE_ROOT } from '@/cli/utils/resolve'; export const buildCommand = new Command('build') .description('Build for production') - .action(() => { - const scaffoldPath = path.join(process.cwd(), '.chronicle') - if (!fs.existsSync(scaffoldPath)) { - console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.')) - process.exit(1) - } + .option('-c, --content ', 'Content directory') + .option( + '--preset ', + 'Deploy preset (vercel, cloudflare, node-server)' + ) + .action(async options => { + const contentDir = resolveContentDir(options.content); - let nextCli: string - try { - nextCli = resolveNextCli() - } catch { - console.log(chalk.red('Error: Next.js CLI not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.')) - process.exit(1) - } + console.log(chalk.cyan('Building for production...')); - console.log(chalk.cyan('Building for production...')) + const { build } = await import('vite'); + const { createViteConfig } = await import('@/server/vite-config'); - const child = spawn(process.execPath, [nextCli, 'build'], { - stdio: 'inherit', - cwd: scaffoldPath, - env: { - ...process.env, - CHRONICLE_PROJECT_ROOT: process.cwd(), - CHRONICLE_CONTENT_DIR: './content', - }, - }) + const config = await createViteConfig({ + root: PACKAGE_ROOT, + contentDir, + preset: options.preset + }); - attachLifecycleHandlers(child) - }) + await build(config); + + console.log(chalk.green('Build complete')); + }); diff --git a/packages/chronicle/src/cli/commands/dev.ts b/packages/chronicle/src/cli/commands/dev.ts index 84c6110..0b819f3 100644 --- a/packages/chronicle/src/cli/commands/dev.ts +++ b/packages/chronicle/src/cli/commands/dev.ts @@ -1,39 +1,27 @@ -import { Command } from 'commander' -import { spawn } from 'child_process' -import path from 'path' -import fs from 'fs' -import chalk from 'chalk' -import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils' +import chalk from 'chalk'; +import { Command } from 'commander'; +import { resolveContentDir } from '@/cli/utils/config'; +import { PACKAGE_ROOT } from '@/cli/utils/resolve'; export const devCommand = new Command('dev') .description('Start development server') .option('-p, --port ', 'Port number', '3000') - .action((options) => { - const scaffoldPath = path.join(process.cwd(), '.chronicle') - if (!fs.existsSync(scaffoldPath)) { - console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.')) - process.exit(1) - } + .option('-c, --content ', 'Content directory') + .action(async options => { + const contentDir = resolveContentDir(options.content); + const port = parseInt(options.port, 10); - let nextCli: string - try { - nextCli = resolveNextCli() - } catch { - console.log(chalk.red('Error: Next.js CLI not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.')) - process.exit(1) - } + console.log(chalk.cyan('Starting dev server...')); - console.log(chalk.cyan('Starting dev server...')) + const { createServer } = await import('vite'); + const { createViteConfig } = await import('@/server/vite-config'); - const child = spawn(process.execPath, [nextCli, 'dev', '-p', options.port], { - stdio: 'inherit', - cwd: scaffoldPath, - env: { - ...process.env, - CHRONICLE_PROJECT_ROOT: process.cwd(), - CHRONICLE_CONTENT_DIR: './content', - }, - }) + const config = await createViteConfig({ root: PACKAGE_ROOT, contentDir }); + const server = await createServer({ + ...config, + server: { ...config.server, port } + }); - attachLifecycleHandlers(child) - }) + await server.listen(); + server.printUrls(); + }); diff --git a/packages/chronicle/src/cli/commands/init.ts b/packages/chronicle/src/cli/commands/init.ts index f79ce1e..60e0e3d 100644 --- a/packages/chronicle/src/cli/commands/init.ts +++ b/packages/chronicle/src/cli/commands/init.ts @@ -1,20 +1,22 @@ -import { Command } from 'commander' -import { execSync } from 'child_process' -import fs from 'fs' -import path from 'path' -import chalk from 'chalk' -import { stringify } from 'yaml' -import type { ChronicleConfig } from '@/types' -import { loadCLIConfig, scaffoldDir, detectPackageManager, getChronicleVersion } from '@/cli/utils' - +import { execSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import chalk from 'chalk'; +import { Command } from 'commander'; +import { stringify } from 'yaml'; +import { + detectPackageManager, + getChronicleVersion +} from '@/cli/utils/scaffold'; +import type { ChronicleConfig } from '@/types'; function createConfig(): ChronicleConfig { return { title: 'My Documentation', description: 'Documentation powered by Chronicle', theme: { name: 'default' }, - search: { enabled: true, placeholder: 'Search documentation...' }, - } + search: { enabled: true, placeholder: 'Search documentation...' } + }; } function createPackageJson(name: string): Record { @@ -25,19 +27,19 @@ function createPackageJson(name: string): Record { scripts: { dev: 'chronicle dev', build: 'chronicle build', - start: 'chronicle start', + start: 'chronicle start' }, dependencies: { - '@raystack/chronicle': `^${getChronicleVersion()}`, + '@raystack/chronicle': `^${getChronicleVersion()}` }, devDependencies: { '@raystack/tools-config': '0.56.0', 'openapi-types': '^12.1.3', typescript: '5.9.3', '@types/react': '^19.2.10', - '@types/node': '^25.1.0', - }, - } + '@types/node': '^25.1.0' + } + }; } const sampleMdx = `--- @@ -49,115 +51,126 @@ order: 1 # Welcome This is your documentation home page. -` +`; export const initCommand = new Command('init') .description('Initialize a new Chronicle project') .option('-c, --content ', 'Content directory name', 'content') - .action((options) => { - const projectDir = process.cwd() - const dirName = path.basename(projectDir) || 'docs' - const contentDir = path.join(projectDir, options.content) + .action(options => { + const projectDir = process.cwd(); + const dirName = path.basename(projectDir) || 'docs'; + const contentDir = path.join(projectDir, options.content); - // Create content directory if it doesn't exist if (!fs.existsSync(contentDir)) { - fs.mkdirSync(contentDir, { recursive: true }) - console.log(chalk.green('✓'), 'Created', contentDir) + fs.mkdirSync(contentDir, { recursive: true }); + console.log(chalk.green('\u2713'), 'Created', contentDir); } - // Create or update package.json in project root - const packageJsonPath = path.join(projectDir, 'package.json') + const packageJsonPath = path.join(projectDir, 'package.json'); if (!fs.existsSync(packageJsonPath)) { - fs.writeFileSync(packageJsonPath, JSON.stringify(createPackageJson(dirName), null, 2) + '\n') - console.log(chalk.green('✓'), 'Created', packageJsonPath) + fs.writeFileSync( + packageJsonPath, + `${JSON.stringify(createPackageJson(dirName), null, 2)}\n` + ); + console.log(chalk.green('\u2713'), 'Created', packageJsonPath); } else { - const existing = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) - const template = createPackageJson(dirName) - let updated = false + const existing = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + const template = createPackageJson(dirName); + let updated = false; - // Set type to module if (existing.type !== 'module') { - existing.type = 'module' - updated = true + existing.type = 'module'; + updated = true; } - // Merge missing scripts - if (!existing.scripts) existing.scripts = {} - for (const [key, value] of Object.entries(template.scripts as Record)) { + if (!existing.scripts) existing.scripts = {}; + for (const [key, value] of Object.entries( + template.scripts as Record + )) { if (!existing.scripts[key]) { - existing.scripts[key] = value - updated = true + existing.scripts[key] = value; + updated = true; } } - // Merge missing dependencies - if (!existing.dependencies) existing.dependencies = {} - for (const [key, value] of Object.entries(template.dependencies as Record)) { + if (!existing.dependencies) existing.dependencies = {}; + for (const [key, value] of Object.entries( + template.dependencies as Record + )) { if (!existing.dependencies[key]) { - existing.dependencies[key] = value - updated = true + existing.dependencies[key] = value; + updated = true; } } - // Merge missing devDependencies - if (!existing.devDependencies) existing.devDependencies = {} - for (const [key, value] of Object.entries(template.devDependencies as Record)) { + if (!existing.devDependencies) existing.devDependencies = {}; + for (const [key, value] of Object.entries( + template.devDependencies as Record + )) { if (!existing.devDependencies[key]) { - existing.devDependencies[key] = value - updated = true + existing.devDependencies[key] = value; + updated = true; } } if (updated) { - fs.writeFileSync(packageJsonPath, JSON.stringify(existing, null, 2) + '\n') - console.log(chalk.green('✓'), 'Updated', packageJsonPath, 'with missing scripts/deps') + fs.writeFileSync( + packageJsonPath, + `${JSON.stringify(existing, null, 2)}\n` + ); + console.log(chalk.green('\u2713'), 'Updated', packageJsonPath); } else { - console.log(chalk.yellow('⚠'), packageJsonPath, 'already has all required entries') + console.log( + chalk.yellow('\u26a0'), + packageJsonPath, + 'already has all required entries' + ); } } - // Create chronicle.yaml in project root - const configPath = path.join(projectDir, 'chronicle.yaml') + const configPath = path.join(projectDir, 'chronicle.yaml'); if (!fs.existsSync(configPath)) { - fs.writeFileSync(configPath, stringify(createConfig())) - console.log(chalk.green('✓'), 'Created', configPath) + fs.writeFileSync(configPath, stringify(createConfig())); + console.log(chalk.green('\u2713'), 'Created', configPath); } else { - console.log(chalk.yellow('⚠'), configPath, 'already exists') + console.log(chalk.yellow('\u26a0'), configPath, 'already exists'); } - // Create sample index.mdx only if content dir is empty - const contentFiles = fs.readdirSync(contentDir) + const contentFiles = fs.readdirSync(contentDir); if (contentFiles.length === 0) { - const indexPath = path.join(contentDir, 'index.mdx') - fs.writeFileSync(indexPath, sampleMdx) - console.log(chalk.green('✓'), 'Created', indexPath) + const indexPath = path.join(contentDir, 'index.mdx'); + fs.writeFileSync(indexPath, sampleMdx); + console.log(chalk.green('\u2713'), 'Created', indexPath); } - // Add entries to .gitignore - const gitignorePath = path.join(projectDir, '.gitignore') - const gitignoreEntries = ['.chronicle', 'node_modules', '.next'] + const gitignorePath = path.join(projectDir, '.gitignore'); + const gitignoreEntries = ['node_modules', 'dist', '.output']; if (fs.existsSync(gitignorePath)) { - const existing = fs.readFileSync(gitignorePath, 'utf-8') - const missing = gitignoreEntries.filter(e => !existing.includes(e)) + const existing = fs.readFileSync(gitignorePath, 'utf-8'); + const missing = gitignoreEntries.filter(e => !existing.includes(e)); if (missing.length > 0) { - fs.appendFileSync(gitignorePath, `\n${missing.join('\n')}\n`) - console.log(chalk.green('✓'), 'Added', missing.join(', '), 'to .gitignore') + fs.appendFileSync(gitignorePath, `\n${missing.join('\n')}\n`); + console.log( + chalk.green('\u2713'), + 'Added', + missing.join(', '), + 'to .gitignore' + ); } } else { - fs.writeFileSync(gitignorePath, `${gitignoreEntries.join('\n')}\n`) - console.log(chalk.green('✓'), 'Created .gitignore') + fs.writeFileSync(gitignorePath, `${gitignoreEntries.join('\n')}\n`); + console.log(chalk.green('\u2713'), 'Created .gitignore'); } - // Install dependencies - const pm = detectPackageManager() - console.log(chalk.cyan(`\nInstalling dependencies with ${pm}...`)) - execSync(`${pm} install`, { cwd: projectDir, stdio: 'inherit' }) - - // Scaffold .chronicle/ directory - loadCLIConfig(contentDir) - scaffoldDir(contentDir) - - const runCmd = pm === 'npm' ? 'npx' : pm === 'bun' ? 'bunx' : `${pm} dlx` - console.log(chalk.green('\n✓ Chronicle initialized!')) - console.log('\nRun', chalk.cyan(`${runCmd} chronicle dev`), 'to start development server') - }) + const pm = detectPackageManager(); + console.log(chalk.cyan(`\nInstalling dependencies with ${pm}...`)); + execSync(`${pm} install`, { cwd: projectDir, stdio: 'inherit' }); + + const runCmd = pm === 'npm' ? 'npx' : pm === 'bun' ? 'bunx' : `${pm} dlx`; + console.log(chalk.green('\n\u2713 Chronicle initialized!')); + console.log( + '\nRun', + chalk.cyan(`${runCmd} chronicle dev`), + 'to start development server' + ); + }); diff --git a/packages/chronicle/src/cli/commands/serve.ts b/packages/chronicle/src/cli/commands/serve.ts index 77de8b5..20d2ca7 100644 --- a/packages/chronicle/src/cli/commands/serve.ts +++ b/packages/chronicle/src/cli/commands/serve.ts @@ -1,59 +1,37 @@ -import { Command } from 'commander' -import { spawn } from 'child_process' -import path from 'path' -import fs from 'fs' -import chalk from 'chalk' -import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils' +import chalk from 'chalk'; +import { Command } from 'commander'; +import { resolveContentDir } from '@/cli/utils/config'; +import { PACKAGE_ROOT } from '@/cli/utils/resolve'; export const serveCommand = new Command('serve') .description('Build and start production server') .option('-p, --port ', 'Port number', '3000') - .action((options) => { - const scaffoldPath = path.join(process.cwd(), '.chronicle') - if (!fs.existsSync(scaffoldPath)) { - console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.')) - process.exit(1) - } - - let nextCli: string - try { - nextCli = resolveNextCli() - } catch { - console.log(chalk.red('Error: Next.js CLI not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.')) - process.exit(1) - } - - const env = { - ...process.env, - CHRONICLE_PROJECT_ROOT: process.cwd(), - CHRONICLE_CONTENT_DIR: './content', - } - - console.log(chalk.cyan('Building for production...')) - - const buildChild = spawn(process.execPath, [nextCli, 'build'], { - stdio: 'inherit', - cwd: scaffoldPath, - env, - }) - - process.once('SIGINT', () => buildChild.kill('SIGINT')) - process.once('SIGTERM', () => buildChild.kill('SIGTERM')) - - buildChild.on('close', (code) => { - if (code !== 0) { - console.log(chalk.red('Build failed')) - process.exit(code ?? 1) - } - - console.log(chalk.cyan('Starting production server...')) - - const startChild = spawn(process.execPath, [nextCli, 'start', '-p', options.port], { - stdio: 'inherit', - cwd: scaffoldPath, - env, - }) - - attachLifecycleHandlers(startChild) - }) - }) + .option('-c, --content ', 'Content directory') + .option( + '--preset ', + 'Deploy preset (vercel, cloudflare, node-server)' + ) + .action(async options => { + const contentDir = resolveContentDir(options.content); + const port = parseInt(options.port, 10); + + const { build, preview } = await import('vite'); + const { createViteConfig } = await import('@/server/vite-config'); + + const config = await createViteConfig({ + root: PACKAGE_ROOT, + contentDir, + preset: options.preset + }); + + console.log(chalk.cyan('Building for production...')); + await build(config); + + console.log(chalk.cyan('Starting production server...')); + const server = await preview({ + ...config, + preview: { port } + }); + + server.printUrls(); + }); diff --git a/packages/chronicle/src/cli/commands/start.ts b/packages/chronicle/src/cli/commands/start.ts index f29d376..ff14e7e 100644 --- a/packages/chronicle/src/cli/commands/start.ts +++ b/packages/chronicle/src/cli/commands/start.ts @@ -1,39 +1,26 @@ -import { Command } from 'commander' -import { spawn } from 'child_process' -import path from 'path' -import fs from 'fs' -import chalk from 'chalk' -import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils' +import chalk from 'chalk'; +import { Command } from 'commander'; +import { resolveContentDir } from '@/cli/utils/config'; +import { PACKAGE_ROOT } from '@/cli/utils/resolve'; export const startCommand = new Command('start') .description('Start production server') .option('-p, --port ', 'Port number', '3000') - .action((options) => { - const scaffoldPath = path.join(process.cwd(), '.chronicle') - if (!fs.existsSync(scaffoldPath)) { - console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.')) - process.exit(1) - } + .option('-c, --content ', 'Content directory') + .action(async options => { + const contentDir = resolveContentDir(options.content); + const port = parseInt(options.port, 10); - let nextCli: string - try { - nextCli = resolveNextCli() - } catch { - console.log(chalk.red('Error: Next.js CLI not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.')) - process.exit(1) - } + console.log(chalk.cyan('Starting production server...')); - console.log(chalk.cyan('Starting production server...')) + const { preview } = await import('vite'); + const { createViteConfig } = await import('@/server/vite-config'); - const child = spawn(process.execPath, [nextCli, 'start', '-p', options.port], { - stdio: 'inherit', - cwd: scaffoldPath, - env: { - ...process.env, - CHRONICLE_PROJECT_ROOT: process.cwd(), - CHRONICLE_CONTENT_DIR: './content', - }, - }) + const config = await createViteConfig({ root: PACKAGE_ROOT, contentDir }); + const server = await preview({ + ...config, + preview: { port } + }); - attachLifecycleHandlers(child) - }) + server.printUrls(); + }); diff --git a/packages/chronicle/src/cli/index.ts b/packages/chronicle/src/cli/index.ts index 2c5ba37..ad89726 100644 --- a/packages/chronicle/src/cli/index.ts +++ b/packages/chronicle/src/cli/index.ts @@ -1,21 +1,21 @@ -import { Command } from 'commander' -import { initCommand } from './commands/init' -import { devCommand } from './commands/dev' -import { buildCommand } from './commands/build' -import { startCommand } from './commands/start' -import { serveCommand } from './commands/serve' +import { Command } from 'commander'; +import { buildCommand } from './commands/build'; +import { devCommand } from './commands/dev'; +import { initCommand } from './commands/init'; +import { serveCommand } from './commands/serve'; +import { startCommand } from './commands/start'; -const program = new Command() +const program = new Command(); program .name('chronicle') .description('Config-driven documentation framework') - .version('0.1.0') + .version('0.1.0'); -program.addCommand(initCommand) -program.addCommand(devCommand) -program.addCommand(buildCommand) -program.addCommand(startCommand) -program.addCommand(serveCommand) +program.addCommand(initCommand); +program.addCommand(devCommand); +program.addCommand(buildCommand); +program.addCommand(startCommand); +program.addCommand(serveCommand); -program.parse() +program.parse(); diff --git a/packages/chronicle/src/cli/utils/config.ts b/packages/chronicle/src/cli/utils/config.ts index 3bc20d4..eba3ea1 100644 --- a/packages/chronicle/src/cli/utils/config.ts +++ b/packages/chronicle/src/cli/utils/config.ts @@ -1,43 +1,42 @@ -import fs from 'fs' -import path from 'path' -import { parse } from 'yaml' -import chalk from 'chalk' -import type { ChronicleConfig } from '@/types' +import fs from 'node:fs'; +import path from 'node:path'; +import chalk from 'chalk'; +import { parse } from 'yaml'; +import type { ChronicleConfig } from '@/types'; export interface CLIConfig { - config: ChronicleConfig - configPath: string - contentDir: string + config: ChronicleConfig; + configPath: string; + contentDir: string; } export function resolveContentDir(contentFlag?: string): string { - if (contentFlag) return path.resolve(contentFlag) - if (process.env.CHRONICLE_CONTENT_DIR) return path.resolve(process.env.CHRONICLE_CONTENT_DIR) - return path.resolve('content') + if (contentFlag) return path.resolve(contentFlag); + return path.resolve('content'); } function resolveConfigPath(contentDir: string): string | null { - const cwdPath = path.join(process.cwd(), 'chronicle.yaml') - if (fs.existsSync(cwdPath)) return cwdPath - const contentPath = path.join(contentDir, 'chronicle.yaml') - if (fs.existsSync(contentPath)) return contentPath - return null + const cwdPath = path.join(process.cwd(), 'chronicle.yaml'); + if (fs.existsSync(cwdPath)) return cwdPath; + const contentPath = path.join(contentDir, 'chronicle.yaml'); + if (fs.existsSync(contentPath)) return contentPath; + return null; } export function loadCLIConfig(contentDir: string): CLIConfig { - const configPath = resolveConfigPath(contentDir) + const configPath = resolveConfigPath(contentDir); if (!configPath) { - console.log(chalk.red(`Error: chronicle.yaml not found in '${process.cwd()}' or '${contentDir}'`)) - console.log(chalk.gray(`Run 'chronicle init' to create one`)) - process.exit(1) + console.log( + chalk.red( + `Error: chronicle.yaml not found in '${process.cwd()}' or '${contentDir}'` + ) + ); + console.log(chalk.gray("Run 'chronicle init' to create one")); + process.exit(1); } - const config = parse(fs.readFileSync(configPath, 'utf-8')) as ChronicleConfig + const config = parse(fs.readFileSync(configPath, 'utf-8')) as ChronicleConfig; - return { - config, - configPath, - contentDir, - } + return { config, configPath, contentDir }; } diff --git a/packages/chronicle/src/cli/utils/index.ts b/packages/chronicle/src/cli/utils/index.ts index 7b2a393..9db18b5 100644 --- a/packages/chronicle/src/cli/utils/index.ts +++ b/packages/chronicle/src/cli/utils/index.ts @@ -1,3 +1,3 @@ -export * from './config' -export * from './process' -export * from './scaffold' +export * from './config'; +export * from './resolve'; +export * from './scaffold'; diff --git a/packages/chronicle/src/cli/utils/process.ts b/packages/chronicle/src/cli/utils/process.ts deleted file mode 100644 index eda3e09..0000000 --- a/packages/chronicle/src/cli/utils/process.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { ChildProcess } from 'child_process' - -export function attachLifecycleHandlers(child: ChildProcess) { - child.on('close', (code) => process.exit(code ?? 0)) - process.on('SIGINT', () => child.kill('SIGINT')) - process.on('SIGTERM', () => child.kill('SIGTERM')) -} diff --git a/packages/chronicle/src/cli/utils/resolve.ts b/packages/chronicle/src/cli/utils/resolve.ts index 5178c73..3a4eac3 100644 --- a/packages/chronicle/src/cli/utils/resolve.ts +++ b/packages/chronicle/src/cli/utils/resolve.ts @@ -1,6 +1,10 @@ -import path from 'path' -import { fileURLToPath } from 'url' +import path from 'path'; +import { fileURLToPath } from 'url'; // After bundling: dist/cli/index.js → ../.. = package root // After install: node_modules/@raystack/chronicle/dist/cli/index.js → ../.. = package root -export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..') +export const PACKAGE_ROOT = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '..', + '..' +); diff --git a/packages/chronicle/src/cli/utils/scaffold.ts b/packages/chronicle/src/cli/utils/scaffold.ts index ed64f27..273478d 100644 --- a/packages/chronicle/src/cli/utils/scaffold.ts +++ b/packages/chronicle/src/cli/utils/scaffold.ts @@ -1,137 +1,24 @@ -import { execSync } from 'child_process' -import { createRequire } from 'module' -import fs from 'fs' -import path from 'path' -import chalk from 'chalk' -import { PACKAGE_ROOT } from './resolve' - -const COPY_FILES = ['src', 'source.config.ts', 'tsconfig.json'] - -function copyRecursive(src: string, dest: string) { - const stat = fs.statSync(src) - if (stat.isDirectory()) { - fs.mkdirSync(dest, { recursive: true }) - for (const entry of fs.readdirSync(src)) { - copyRecursive(path.join(src, entry), path.join(dest, entry)) - } - } else { - fs.copyFileSync(src, dest) - } -} - -function ensureRemoved(targetPath: string) { - try { - fs.lstatSync(targetPath) - fs.rmSync(targetPath, { recursive: true, force: true }) - } catch { - // nothing exists, proceed - } -} +import fs from 'node:fs'; +import path from 'node:path'; +import { PACKAGE_ROOT } from './resolve'; export function detectPackageManager(): string { if (process.env.npm_config_user_agent) { - return process.env.npm_config_user_agent.split('/')[0] - } - const cwd = process.cwd() - if (fs.existsSync(path.join(cwd, 'bun.lock')) || fs.existsSync(path.join(cwd, 'bun.lockb'))) return 'bun' - if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm' - if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn' - return 'npm' -} - -function generateNextConfig(scaffoldPath: string) { - const config = `import { createMDX } from 'fumadocs-mdx/next' - -const withMDX = createMDX() - -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, -} - -export default withMDX(nextConfig) -` - fs.writeFileSync(path.join(scaffoldPath, 'next.config.mjs'), config) -} - -function createPackageJson(): Record { - return { - name: 'chronicle-docs', - private: true, - dependencies: { - '@raystack/chronicle': `^${getChronicleVersion()}`, - }, - devDependencies: { - '@raystack/tools-config': '0.56.0', - 'openapi-types': '^12.1.3', - typescript: '5.9.3', - '@types/react': '^19.2.10', - '@types/node': '^25.1.0', - }, - } -} - -function ensureDeps() { - const cwd = process.cwd() - const cwdPkgJson = path.join(cwd, 'package.json') - const cwdNodeModules = path.join(cwd, 'node_modules') - - if (fs.existsSync(cwdPkgJson) && fs.existsSync(cwdNodeModules)) { - // Case 1: existing project with deps installed - return - } - - // Case 2: no package.json — create in cwd and install - if (!fs.existsSync(cwdPkgJson)) { - fs.writeFileSync(cwdPkgJson, JSON.stringify(createPackageJson(), null, 2) + '\n') - } - - if (!fs.existsSync(cwdNodeModules)) { - const pm = detectPackageManager() - console.log(chalk.cyan(`Installing dependencies with ${pm}...`)) - execSync(`${pm} install`, { cwd, stdio: 'inherit' }) + return process.env.npm_config_user_agent.split('/')[0]; } + const cwd = process.cwd(); + if ( + fs.existsSync(path.join(cwd, 'bun.lock')) || + fs.existsSync(path.join(cwd, 'bun.lockb')) + ) + return 'bun'; + if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm'; + if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn'; + return 'npm'; } export function getChronicleVersion(): string { - const pkgPath = path.join(PACKAGE_ROOT, 'package.json') - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) - return pkg.version -} - -export function resolveNextCli(): string { - const chronicleRequire = createRequire(path.join(PACKAGE_ROOT, 'package.json')) - return chronicleRequire.resolve('next/dist/bin/next') -} - -export function scaffoldDir(contentDir: string): string { - const scaffoldPath = path.join(process.cwd(), '.chronicle') - - // Create .chronicle/ if not exists - if (!fs.existsSync(scaffoldPath)) { - fs.mkdirSync(scaffoldPath, { recursive: true }) - } - - // Copy package files - for (const name of COPY_FILES) { - const src = path.join(PACKAGE_ROOT, name) - const dest = path.join(scaffoldPath, name) - ensureRemoved(dest) - copyRecursive(src, dest) - } - - // Generate next.config.mjs - generateNextConfig(scaffoldPath) - - // Symlink content dir - const contentLink = path.join(scaffoldPath, 'content') - ensureRemoved(contentLink) - fs.symlinkSync(path.resolve(contentDir), contentLink) - - // Ensure dependencies are available - ensureDeps() - - console.log(chalk.gray(`Scaffold: ${scaffoldPath}`)) - - return scaffoldPath + const pkgPath = path.join(PACKAGE_ROOT, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + return pkg.version; } diff --git a/packages/chronicle/src/lib/config.ts b/packages/chronicle/src/lib/config.ts index aa77280..fe548e7 100644 --- a/packages/chronicle/src/lib/config.ts +++ b/packages/chronicle/src/lib/config.ts @@ -1,56 +1,58 @@ -import fs from 'fs' -import path from 'path' -import { parse } from 'yaml' -import type { ChronicleConfig } from '@/types' +import fs from 'node:fs'; +import path from 'node:path'; +import { parse } from 'yaml'; +import type { ChronicleConfig } from '@/types'; -const CONFIG_FILE = 'chronicle.yaml' +const CONFIG_FILE = 'chronicle.yaml'; const defaultConfig: ChronicleConfig = { title: 'Documentation', theme: { name: 'default' }, - search: { enabled: true, placeholder: 'Search...' }, -} + search: { enabled: true, placeholder: 'Search...' } +}; function resolveConfigPath(): string | null { - // Check project root via env var - const projectRoot = process.env.CHRONICLE_PROJECT_ROOT - if (projectRoot) { - const rootPath = path.join(projectRoot, CONFIG_FILE) - if (fs.existsSync(rootPath)) return rootPath - } - // Check cwd - const cwdPath = path.join(process.cwd(), CONFIG_FILE) - if (fs.existsSync(cwdPath)) return cwdPath - // Check content dir - const contentDir = process.env.CHRONICLE_CONTENT_DIR + const projectRoot = + typeof __CHRONICLE_PROJECT_ROOT__ !== 'undefined' + ? __CHRONICLE_PROJECT_ROOT__ + : process.cwd(); + + const rootPath = path.join(projectRoot, CONFIG_FILE); + if (fs.existsSync(rootPath)) return rootPath; + + const contentDir = + typeof __CHRONICLE_CONTENT_DIR__ !== 'undefined' + ? __CHRONICLE_CONTENT_DIR__ + : undefined; if (contentDir) { - const contentPath = path.join(contentDir, CONFIG_FILE) - if (fs.existsSync(contentPath)) return contentPath + const contentPath = path.join(contentDir, CONFIG_FILE); + if (fs.existsSync(contentPath)) return contentPath; } - return null + + return null; } export function loadConfig(): ChronicleConfig { - const configPath = resolveConfigPath() + const configPath = resolveConfigPath(); if (!configPath) { - return defaultConfig + return defaultConfig; } - const raw = fs.readFileSync(configPath, 'utf-8') - const userConfig = parse(raw) as Partial + const raw = fs.readFileSync(configPath, 'utf-8'); + const userConfig = parse(raw) as Partial; return { ...defaultConfig, ...userConfig, theme: { name: userConfig.theme?.name ?? defaultConfig.theme!.name, - colors: { ...defaultConfig.theme?.colors, ...userConfig.theme?.colors }, + colors: { ...defaultConfig.theme?.colors, ...userConfig.theme?.colors } }, search: { ...defaultConfig.search, ...userConfig.search }, footer: userConfig.footer, api: userConfig.api, llms: { enabled: false, ...userConfig.llms }, - analytics: { enabled: false, ...userConfig.analytics }, - } -} \ No newline at end of file + analytics: { enabled: false, ...userConfig.analytics } + }; +} diff --git a/packages/chronicle/src/lib/source.ts b/packages/chronicle/src/lib/source.ts index 065c4fd..e92a325 100644 --- a/packages/chronicle/src/lib/source.ts +++ b/packages/chronicle/src/lib/source.ts @@ -1,9 +1,9 @@ import fs from 'node:fs/promises'; import path from 'node:path'; -import matter from 'gray-matter'; import { loader } from 'fumadocs-core/source'; -import type { Frontmatter, PageTree, PageTreeItem } from '@/types'; +import matter from 'gray-matter'; import type { MDXContent } from 'mdx/types'; +import type { Frontmatter, PageTree, PageTreeItem } from '@/types'; export interface SourcePage { url: string; @@ -17,13 +17,18 @@ function getContentDir(): string { } async function scanFiles(contentDir: string) { - const files: { type: 'page' | 'meta'; path: string; data: Record }[] = []; + const files: { + type: 'page' | 'meta'; + path: string; + data: Record; + }[] = []; async function scan(dir: string, prefix: string[] = []) { try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { - if (entry.name.startsWith('.') || entry.name === 'node_modules') continue; + if (entry.name.startsWith('.') || entry.name === 'node_modules') + continue; const fullPath = path.join(dir, entry.name); const relativePath = [...prefix, entry.name].join('/'); @@ -35,14 +40,22 @@ async function scanFiles(contentDir: string) { if (entry.name.endsWith('.mdx') || entry.name.endsWith('.md')) { const raw = await fs.readFile(fullPath, 'utf-8'); const { data } = matter(raw); - files.push({ type: 'page', path: relativePath, data: { ...data, _absolutePath: fullPath } }); + files.push({ + type: 'page', + path: relativePath, + data: { ...data, _absolutePath: fullPath } + }); } else if (entry.name === 'meta.json' || entry.name === 'meta.yaml') { const raw = await fs.readFile(fullPath, 'utf-8'); - const data = entry.name.endsWith('.json') ? JSON.parse(raw) : matter(raw).data; + const data = entry.name.endsWith('.json') + ? JSON.parse(raw) + : matter(raw).data; files.push({ type: 'meta', path: relativePath, data }); } } - } catch { /* directory not readable */ } + } catch { + /* directory not readable */ + } } await scan(contentDir); @@ -58,7 +71,7 @@ async function getSource() { const files = await scanFiles(contentDir); cachedSource = loader({ source: { files }, - baseUrl: '/', + baseUrl: '/' }); return cachedSource; } @@ -79,12 +92,15 @@ export async function getPages(): Promise { slugs: page.slugs, filePath: (data._absolutePath as string) ?? '', frontmatter: { - title: (data.title as string) ?? page.slugs[page.slugs.length - 1] ?? 'Untitled', + title: + (data.title as string) ?? + page.slugs[page.slugs.length - 1] ?? + 'Untitled', description: data.description as string | undefined, order: data.order as number | undefined, icon: data.icon as string | undefined, - lastModified: data.lastModified as string | undefined, - }, + lastModified: data.lastModified as string | undefined + } }; }); @@ -97,7 +113,9 @@ export async function getPage(slug?: string[]): Promise { return pages.find(p => p.url === targetUrl) ?? null; } -export async function loadPageComponent(page: SourcePage): Promise { +export async function loadPageComponent( + page: SourcePage +): Promise { if (!page.filePath) return null; const mod = await import(/* @vite-ignore */ page.filePath); return mod.default; @@ -116,7 +134,7 @@ export async function buildPageTree(): Promise { type: 'page', name: (data.title as string) ?? page.slugs.join('/') ?? 'Untitled', url: page.url, - order: (data.order as number | undefined) ?? (isIndex ? 0 : undefined), + order: (data.order as number | undefined) ?? (isIndex ? 0 : undefined) }; if (page.slugs.length > 1) { @@ -131,7 +149,11 @@ export async function buildPageTree(): Promise { } const sortByOrder = (items: PageTreeItem[]) => - items.sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER)); + items.sort( + (a, b) => + (a.order ?? Number.MAX_SAFE_INTEGER) - + (b.order ?? Number.MAX_SAFE_INTEGER) + ); const children: PageTreeItem[] = sortByOrder(rootPages); @@ -144,7 +166,7 @@ export async function buildPageTree(): Promise { type: 'folder', name: `${folder.charAt(0).toUpperCase()}${folder.slice(1)}`, order: folderOrder, - children: sorted, + children: sorted }); } diff --git a/packages/chronicle/tsconfig.json b/packages/chronicle/tsconfig.json index b21ec05..50ac79e 100644 --- a/packages/chronicle/tsconfig.json +++ b/packages/chronicle/tsconfig.json @@ -21,10 +21,9 @@ "lib": ["ES2022", "DOM", "DOM.Iterable"], "moduleResolution": "bundler", "paths": { - "@/*": ["./src/*"], - "@/.source/*": ["./.source/*"] + "@/*": ["./src/*"] } }, - "include": ["src", ".source", "source.config.ts"], + "include": ["src"], "exclude": ["node_modules", "dist"] } From 4c4066becce85a996c3d4863e93cff831ce8ef6a Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 10:26:18 +0530 Subject: [PATCH 04/38] feat: replace Next.js imports with React Router MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - next/link → react-router-dom Link - next/navigation usePathname → useLocation - next/navigation useRouter → useNavigate - next/image → native img tag - next/font/google → removed (font.ts deleted) - Remove 'use client' directives (not needed with Vite) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../chronicle/src/components/mdx/image.tsx | 39 +-- .../chronicle/src/components/mdx/link.tsx | 33 +- .../chronicle/src/components/ui/search.tsx | 114 ++++--- .../chronicle/src/themes/default/Layout.tsx | 111 ++++--- .../chronicle/src/themes/default/Page.tsx | 18 +- packages/chronicle/src/themes/default/Toc.tsx | 58 ++-- packages/chronicle/src/themes/default/font.ts | 6 - .../chronicle/src/themes/default/index.ts | 16 +- .../chronicle/src/themes/paper/ChapterNav.tsx | 101 +++--- .../src/themes/paper/Layout.module.css | 2 +- .../chronicle/src/themes/paper/Layout.tsx | 36 ++- .../src/themes/paper/Page.module.css | 15 +- packages/chronicle/src/themes/paper/Page.tsx | 115 ++++--- .../src/themes/paper/ReadingProgress.tsx | 299 ++++++++++-------- packages/chronicle/src/themes/paper/index.ts | 10 +- packages/chronicle/src/themes/registry.ts | 14 +- 16 files changed, 535 insertions(+), 452 deletions(-) delete mode 100644 packages/chronicle/src/themes/default/font.ts diff --git a/packages/chronicle/src/components/mdx/image.tsx b/packages/chronicle/src/components/mdx/image.tsx index fcf511d..0f7aa80 100644 --- a/packages/chronicle/src/components/mdx/image.tsx +++ b/packages/chronicle/src/components/mdx/image.tsx @@ -1,38 +1,9 @@ -'use client' +import type { ComponentProps } from 'react'; -import NextImage from 'next/image' -import type { ComponentProps } from 'react' +type ImageProps = ComponentProps<'img'>; -type ImageProps = Omit, 'src'> & { - src?: string - width?: number | string - height?: number | string -} - -export function Image({ src, alt, width, height, ...props }: ImageProps) { - if (!src || typeof src !== 'string') return null - - const isExternal = src.startsWith('http://') || src.startsWith('https://') - - if (isExternal) { - return ( - // eslint-disable-next-line @next/next/no-img-element - {alt - ) - } +export function Image({ src, alt, ...props }: ImageProps) { + if (!src) return null; - return ( - - ) + return {alt; } diff --git a/packages/chronicle/src/components/mdx/link.tsx b/packages/chronicle/src/components/mdx/link.tsx index 8193d6f..20ad0a1 100644 --- a/packages/chronicle/src/components/mdx/link.tsx +++ b/packages/chronicle/src/components/mdx/link.tsx @@ -1,38 +1,41 @@ -'use client' +import { Link as ApsaraLink } from '@raystack/apsara'; +import type { ComponentProps } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; -import NextLink from 'next/link' -import { Link as ApsaraLink } from '@raystack/apsara' -import type { ComponentProps } from 'react' - -type LinkProps = ComponentProps<'a'> +type LinkProps = ComponentProps<'a'>; export function Link({ href, children, ...props }: LinkProps) { if (!href) { - return {children} + return {children}; } - const isExternal = href.startsWith('http://') || href.startsWith('https://') - const isAnchor = href.startsWith('#') + const isExternal = href.startsWith('http://') || href.startsWith('https://'); + const isAnchor = href.startsWith('#'); if (isAnchor) { return ( {children} - ) + ); } if (isExternal) { return ( - + {children} - ) + ); } return ( - + {children} - - ) + + ); } diff --git a/packages/chronicle/src/components/ui/search.tsx b/packages/chronicle/src/components/ui/search.tsx index 543d353..44c86ac 100644 --- a/packages/chronicle/src/components/ui/search.tsx +++ b/packages/chronicle/src/components/ui/search.tsx @@ -1,21 +1,19 @@ -"use client"; - -import { useState, useEffect, useCallback } from "react"; -import { useRouter } from "next/navigation"; -import { Button, Command, Dialog, Text } from "@raystack/apsara"; -import { cx } from "class-variance-authority"; -import { useDocsSearch } from "fumadocs-core/search/client"; -import type { SortedResult } from "fumadocs-core/search"; -import { DocumentIcon, HashtagIcon } from "@heroicons/react/24/outline"; -import { MethodBadge } from "@/components/api/method-badge"; -import styles from "./search.module.css"; +import { DocumentIcon, HashtagIcon } from '@heroicons/react/24/outline'; +import { Button, Command, Dialog, Text } from '@raystack/apsara'; +import { cx } from 'class-variance-authority'; +import type { SortedResult } from 'fumadocs-core/search'; +import { useDocsSearch } from 'fumadocs-core/search/client'; +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { MethodBadge } from '@/components/api/method-badge'; +import styles from './search.module.css'; function SearchShortcutKey({ className }: { className?: string }) { - const [key, setKey] = useState("⌘"); + const [key, setKey] = useState('⌘'); useEffect(() => { - const isMac = navigator.platform?.toUpperCase().includes("MAC"); - setKey(isMac ? "⌘" : "Ctrl"); + const isMac = navigator.platform?.toUpperCase().includes('MAC'); + setKey(isMac ? '⌘' : 'Ctrl'); }, []); return ( @@ -26,50 +24,50 @@ function SearchShortcutKey({ className }: { className?: string }) { } interface SearchProps { - className?: string + className?: string; } export function Search({ className }: SearchProps) { const [open, setOpen] = useState(false); - const router = useRouter(); + const navigate = useNavigate(); const { search, setSearch, query } = useDocsSearch({ - type: "fetch", - api: "/api/search", + type: 'fetch', + api: '/api/search', delayMs: 100, - allowEmpty: true, + allowEmpty: true }); const onSelect = useCallback( (url: string) => { setOpen(false); - router.push(url); + navigate(url); }, - [router], + [navigate] ); useEffect(() => { const down = (e: KeyboardEvent) => { - if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); - setOpen((open) => !open); + setOpen(open => !open); } }; - document.addEventListener("keydown", down); - return () => document.removeEventListener("keydown", down); + document.addEventListener('keydown', down); + return () => document.removeEventListener('keydown', down); }, []); const results = deduplicateByUrl( - query.data === "empty" ? [] : (query.data ?? []), + query.data === 'empty' ? [] : (query.data ?? []) ); return ( <> )} {next ? ( - + - + ) : ( - )} @@ -83,25 +102,25 @@ export function Page({ page, config, tree }: ThemePageProps) { {i === crumbs.length - 1 ? ( {crumb.label} ) : ( - + {crumb.label} - + )} ))} - - {config.search?.enabled && } + + {config.search?.enabled && ( + + )}
-
- {page.content} -
+
{page.content}
- ) + ); } diff --git a/packages/chronicle/src/themes/paper/ReadingProgress.tsx b/packages/chronicle/src/themes/paper/ReadingProgress.tsx index 6f887ab..a3f32b3 100644 --- a/packages/chronicle/src/themes/paper/ReadingProgress.tsx +++ b/packages/chronicle/src/themes/paper/ReadingProgress.tsx @@ -1,46 +1,46 @@ -'use client' +'use client'; -import { cx } from 'class-variance-authority' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import type { TocItem } from '@/types' -import styles from './ReadingProgress.module.css' +import { cx } from 'class-variance-authority'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import type { TocItem } from '@/types'; +import styles from './ReadingProgress.module.css'; interface Heading { - title: string - level: number - id: string - url: string - yPosition: number + title: string; + level: number; + id: string; + url: string; + yPosition: number; } -const ARTICLE_SELECTOR = '[data-article-content]' -const TICK_HEIGHT = 20 -const NAV_HEIGHT = 60 +const ARTICLE_SELECTOR = '[data-article-content]'; +const TICK_HEIGHT = 20; +const NAV_HEIGHT = 60; function calculateTickBounds(containerHeight: number) { - const numTicks = Math.floor(containerHeight / TICK_HEIGHT) + 1 - const maxPosition = (numTicks - 1) * TICK_HEIGHT - return { numTicks, maxPosition } + const numTicks = Math.floor(containerHeight / TICK_HEIGHT) + 1; + const maxPosition = (numTicks - 1) * TICK_HEIGHT; + return { numTicks, maxPosition }; } function snapToTick(value: number, maxPosition: number): number { - const snapped = Math.round(value / TICK_HEIGHT) * TICK_HEIGHT - return Math.max(0, Math.min(snapped, maxPosition)) + const snapped = Math.round(value / TICK_HEIGHT) * TICK_HEIGHT; + return Math.max(0, Math.min(snapped, maxPosition)); } function resolveOverlaps(headings: Heading[], maxPosition: number): Heading[] { - if (headings.length <= 1) return headings + if (headings.length <= 1) return headings; - const resolved: Heading[] = [] - let lastUsedPos = -TICK_HEIGHT + const resolved: Heading[] = []; + let lastUsedPos = -TICK_HEIGHT; for (const heading of headings) { - let newPos = heading.yPosition + let newPos = heading.yPosition; if (newPos <= lastUsedPos) { - newPos = lastUsedPos + TICK_HEIGHT + newPos = lastUsedPos + TICK_HEIGHT; } - resolved.push({ ...heading, yPosition: newPos }) - lastUsedPos = newPos + resolved.push({ ...heading, yPosition: newPos }); + lastUsedPos = newPos; } // Backward pass: clamp-and-shift to prevent overlapping positions @@ -48,193 +48,212 @@ function resolveOverlaps(headings: Heading[], maxPosition: number): Heading[] { const maxAllowed = i === resolved.length - 1 ? maxPosition - : resolved[i + 1].yPosition - TICK_HEIGHT + : resolved[i + 1].yPosition - TICK_HEIGHT; - const clampedPos = Math.max(0, maxAllowed) + const clampedPos = Math.max(0, maxAllowed); if (resolved[i].yPosition > clampedPos) { - resolved[i] = { ...resolved[i], yPosition: clampedPos } + resolved[i] = { ...resolved[i], yPosition: clampedPos }; for (let j = i - 1; j >= 0; j--) { - const upperBound = resolved[j + 1].yPosition - TICK_HEIGHT + const upperBound = resolved[j + 1].yPosition - TICK_HEIGHT; if (resolved[j].yPosition > upperBound) { - resolved[j] = { ...resolved[j], yPosition: Math.max(0, upperBound) } + resolved[j] = { ...resolved[j], yPosition: Math.max(0, upperBound) }; } else { - break + break; } } } } - return resolved + return resolved; } interface ReadingProgressProps { - items: TocItem[] + items: TocItem[]; } export function ReadingProgress({ items }: ReadingProgressProps) { - const [headings, setHeadings] = useState([]) - const [containerHeight, setContainerHeight] = useState(0) - const [ready, setReady] = useState(false) - const [isScrollable, setIsScrollable] = useState(true) - const containerRef = useRef(null) - const scrollMarkerRef = useRef(null) - const scrollPosRef = useRef(0) + const [headings, setHeadings] = useState([]); + const [containerHeight, setContainerHeight] = useState(0); + const [ready, setReady] = useState(false); + const [isScrollable, setIsScrollable] = useState(true); + const containerRef = useRef(null); + const scrollMarkerRef = useRef(null); + const scrollPosRef = useRef(0); const { numTicks, maxPosition } = useMemo( () => calculateTickBounds(containerHeight), [containerHeight] - ) + ); const recalcHeadings = useCallback(() => { - const article = document.querySelector(ARTICLE_SELECTOR) - const container = containerRef.current - if (!article || !container || !items.length) return + const article = document.querySelector(ARTICLE_SELECTOR); + const container = containerRef.current; + if (!article || !container || !items.length) return; - const articleBox = article.getBoundingClientRect() - const containerBox = container.getBoundingClientRect() - const articleTop = articleBox.top + window.scrollY + const articleBox = article.getBoundingClientRect(); + const containerBox = container.getBoundingClientRect(); + const articleTop = articleBox.top + window.scrollY; - const hasScroll = articleBox.height > window.innerHeight - setIsScrollable(hasScroll) - setContainerHeight(containerBox.height) + const hasScroll = articleBox.height > window.innerHeight; + setIsScrollable(hasScroll); + setContainerHeight(containerBox.height); - const { maxPosition: maxPos } = calculateTickBounds(containerBox.height) + const { maxPosition: maxPos } = calculateTickBounds(containerBox.height); const mapped = items - .map((tocItem) => { - const id = tocItem.url.startsWith('#') ? tocItem.url.slice(1) : tocItem.url - const node = document.getElementById(id) - if (!node) return null - - const { top } = node.getBoundingClientRect() - const headingPosInArticle = top + window.scrollY - articleTop - const progress = headingPosInArticle / articleBox.height - const rawY = progress * maxPos - const yPos = snapToTick(rawY, maxPos) + .map(tocItem => { + const id = tocItem.url.startsWith('#') + ? tocItem.url.slice(1) + : tocItem.url; + const node = document.getElementById(id); + if (!node) return null; + + const { top } = node.getBoundingClientRect(); + const headingPosInArticle = top + window.scrollY - articleTop; + const progress = headingPosInArticle / articleBox.height; + const rawY = progress * maxPos; + const yPos = snapToTick(rawY, maxPos); return { title: tocItem.title, level: tocItem.depth, id, url: tocItem.url, - yPosition: yPos, - } + yPosition: yPos + }; }) - .filter((item): item is Heading => item !== null) + .filter((item): item is Heading => item !== null); - const resolvedItems = resolveOverlaps(mapped, maxPos) - setHeadings(resolvedItems) - }, [items]) + const resolvedItems = resolveOverlaps(mapped, maxPos); + setHeadings(resolvedItems); + }, [items]); // Imperative DOM updates to avoid React re-render on every scroll event const handleScroll = useCallback(() => { - const article = document.querySelector(ARTICLE_SELECTOR) - const container = containerRef.current - const scrollMarker = scrollMarkerRef.current - if (!article || !container || !scrollMarker) return + const article = document.querySelector(ARTICLE_SELECTOR); + const container = containerRef.current; + const scrollMarker = scrollMarkerRef.current; + if (!article || !container || !scrollMarker) return; - const { top, height } = article.getBoundingClientRect() - const { height: cHeight } = container.getBoundingClientRect() - const viewportHeight = window.innerHeight - const { maxPosition: maxPos } = calculateTickBounds(cHeight) + const { top, height } = article.getBoundingClientRect(); + const { height: cHeight } = container.getBoundingClientRect(); + const viewportHeight = window.innerHeight; + const { maxPosition: maxPos } = calculateTickBounds(cHeight); - let newScrollPos: number + let newScrollPos: number; if (top > 0) { - newScrollPos = 0 + newScrollPos = 0; } else { - const scrolled = Math.abs(top) - const scrollRange = height - viewportHeight - const progress = scrollRange > 0 ? Math.min(1, scrolled / scrollRange) : 0 - const rawPos = progress * maxPos - newScrollPos = snapToTick(rawPos, maxPos) + const scrolled = Math.abs(top); + const scrollRange = height - viewportHeight; + const progress = + scrollRange > 0 ? Math.min(1, scrolled / scrollRange) : 0; + const rawPos = progress * maxPos; + newScrollPos = snapToTick(rawPos, maxPos); } - const prevScrollPos = scrollPosRef.current + const prevScrollPos = scrollPosRef.current; if (newScrollPos !== prevScrollPos) { - scrollPosRef.current = newScrollPos - scrollMarker.style.top = `${newScrollPos}px` + scrollPosRef.current = newScrollPos; + scrollMarker.style.top = `${newScrollPos}px`; - const textEl = scrollMarker.querySelector('[data-scroll-text]') + const textEl = scrollMarker.querySelector('[data-scroll-text]'); if (textEl) { - textEl.textContent = (maxPos > 0 ? newScrollPos / maxPos : 0).toFixed(2) + textEl.textContent = (maxPos > 0 ? newScrollPos / maxPos : 0).toFixed( + 2 + ); } - const tickLines = container.querySelectorAll('[data-tick-line]') - tickLines.forEach((tick) => { - const tickPos = Number(tick.getAttribute('data-tick-pos')) + const tickLines = container.querySelectorAll('[data-tick-line]'); + tickLines.forEach(tick => { + const tickPos = Number(tick.getAttribute('data-tick-pos')); if (tickPos < newScrollPos) { - tick.classList.remove(styles.tickLineAfter) - tick.classList.add(styles.tickLineBefore) + tick.classList.remove(styles.tickLineAfter); + tick.classList.add(styles.tickLineBefore); } else { - tick.classList.remove(styles.tickLineBefore) - tick.classList.add(styles.tickLineAfter) + tick.classList.remove(styles.tickLineBefore); + tick.classList.add(styles.tickLineAfter); } - }) + }); } - }, []) + }, []); useEffect(() => { - recalcHeadings() - handleScroll() - setReady(true) + recalcHeadings(); + handleScroll(); + setReady(true); - const article = document.querySelector(ARTICLE_SELECTOR) - let ro: ResizeObserver | undefined + const article = document.querySelector(ARTICLE_SELECTOR); + let ro: ResizeObserver | undefined; if (article) { ro = new ResizeObserver(() => { - recalcHeadings() - handleScroll() - }) - ro.observe(article) + recalcHeadings(); + handleScroll(); + }); + ro.observe(article); } - window.addEventListener('resize', recalcHeadings) - window.addEventListener('scroll', handleScroll, { passive: true }) + window.addEventListener('resize', recalcHeadings); + window.addEventListener('scroll', handleScroll, { passive: true }); return () => { - ro?.disconnect() - window.removeEventListener('resize', recalcHeadings) - window.removeEventListener('scroll', handleScroll) - } - }, [recalcHeadings, handleScroll]) + ro?.disconnect(); + window.removeEventListener('resize', recalcHeadings); + window.removeEventListener('scroll', handleScroll); + }; + }, [recalcHeadings, handleScroll]); const scrollToTick = (y: number): void => { - const article = document.querySelector(ARTICLE_SELECTOR) - if (!article || maxPosition === 0) return + const article = document.querySelector(ARTICLE_SELECTOR); + if (!article || maxPosition === 0) return; - const articleBox = article.getBoundingClientRect() - const articleTop = articleBox.top + window.scrollY - const progress = y / maxPosition - const articlePos = progress * articleBox.height - const targetScroll = articleTop + articlePos - NAV_HEIGHT + const articleBox = article.getBoundingClientRect(); + const articleTop = articleBox.top + window.scrollY; + const progress = y / maxPosition; + const articlePos = progress * articleBox.height; + const targetScroll = articleTop + articlePos - NAV_HEIGHT; - window.scrollTo({ top: Math.max(0, targetScroll), behavior: 'smooth' }) - } + window.scrollTo({ top: Math.max(0, targetScroll), behavior: 'smooth' }); + }; const scrollToHeading = (id: string): void => { - const element = document.getElementById(id) - if (!element) return + const element = document.getElementById(id); + if (!element) return; - const elementTop = element.getBoundingClientRect().top + window.scrollY - window.scrollTo({ top: Math.max(0, elementTop - NAV_HEIGHT), behavior: 'smooth' }) - } + const elementTop = element.getBoundingClientRect().top + window.scrollY; + window.scrollTo({ + top: Math.max(0, elementTop - NAV_HEIGHT), + behavior: 'smooth' + }); + }; const ticks = useMemo( () => Array.from({ length: numTicks }, (_, i) => i * TICK_HEIGHT), [numTicks] - ) + ); if (!isScrollable || ticks.length < 2) { - return
+ return
; } return (
{ticks.map((y, i) => ( -
-
-
scrollToTick(y)} /> +
+
+
scrollToTick(y)} + />
))} @@ -246,16 +265,16 @@ export function ReadingProgress({ items }: ReadingProgressProps) { top: `${h.yPosition - 6}px`, right: '24px', zIndex: h.level < 4 ? 10 : 0, - transitionDelay: `${50 * i}ms`, + transitionDelay: `${50 * i}ms` }} > { - e.preventDefault() - e.stopPropagation() - scrollToHeading(h.id) + onClick={e => { + e.preventDefault(); + e.stopPropagation(); + scrollToHeading(h.id); }} > {h.title} @@ -270,7 +289,7 @@ export function ReadingProgress({ items }: ReadingProgressProps) { className={styles.connectingLine} style={{ top: `${h.yPosition}px`, - width: `${Math.max(4, (3 - h.level) * 4 + 12)}px`, + width: `${Math.max(4, (3 - h.level) * 4 + 12)}px` }} /> ))} @@ -279,7 +298,9 @@ export function ReadingProgress({ items }: ReadingProgressProps) { ref={scrollMarkerRef} className={cx( styles.scrollMarkerContainer, - ready ? styles.scrollMarkerContainerReady : styles.scrollMarkerContainerNotReady + ready + ? styles.scrollMarkerContainerReady + : styles.scrollMarkerContainerNotReady )} style={{ top: '0px' }} > @@ -290,5 +311,5 @@ export function ReadingProgress({ items }: ReadingProgressProps) {
- ) + ); } diff --git a/packages/chronicle/src/themes/paper/index.ts b/packages/chronicle/src/themes/paper/index.ts index 0229a4d..65543a5 100644 --- a/packages/chronicle/src/themes/paper/index.ts +++ b/packages/chronicle/src/themes/paper/index.ts @@ -1,8 +1,8 @@ -import { Layout } from './Layout' -import { Page } from './Page' -import type { Theme } from '@/types' +import type { Theme } from '@/types'; +import { Layout } from './Layout'; +import { Page } from './Page'; export const paperTheme: Theme = { Layout, - Page, -} + Page +}; diff --git a/packages/chronicle/src/themes/registry.ts b/packages/chronicle/src/themes/registry.ts index ec48d76..25c2db2 100644 --- a/packages/chronicle/src/themes/registry.ts +++ b/packages/chronicle/src/themes/registry.ts @@ -1,14 +1,14 @@ -import type { Theme } from '@/types' -import { defaultTheme } from './default' -import { paperTheme } from './paper' +import type { Theme } from '@/types'; +import { defaultTheme } from './default'; +import { paperTheme } from './paper'; const themes: Record = { default: defaultTheme, - paper: paperTheme, -} + paper: paperTheme +}; export function getTheme(name?: string): Theme { - if (!name || !themes[name]) return defaultTheme + if (!name || !themes[name]) return defaultTheme; - return themes[name] + return themes[name]; } From 14736e40993a67f094e04eb06141fdc2f15d2192 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 12:28:30 +0530 Subject: [PATCH 05/38] feat: symlink content into package root, fix SSR + client hydration - Symlink user's content dir to PACKAGE_ROOT/.content/ before starting - Vite root stays as package root, content accessible via symlink - SSR: use renderToPipeableStream for Suspense support - Client: import MDX via /.content/ symlink path for hydration - loadPageComponent uses symlink path for Vite module resolution - openapi.ts: async with fs/promises, resolve specs from project root - scaffold.ts: fully async (detectPackageManager, getChronicleVersion, linkContent) - build-cli.ts: externalize all dependencies - Add __CHRONICLE_PACKAGE_ROOT__ build-time constant - Graceful fallback when API spec files are missing - Add data-article-content attribute to default theme Page Co-Authored-By: Claude Opus 4.6 (1M context) --- bun.lock | 12 +-- packages/chronicle/build-cli.ts | 9 +- packages/chronicle/package.json | 4 +- packages/chronicle/src/cli/commands/build.ts | 5 +- packages/chronicle/src/cli/commands/dev.ts | 7 +- packages/chronicle/src/cli/commands/init.ts | 12 +-- packages/chronicle/src/cli/commands/serve.ts | 5 +- packages/chronicle/src/cli/commands/start.ts | 4 +- packages/chronicle/src/cli/utils/scaffold.ts | 37 ++++++--- .../chronicle/src/components/mdx/link.tsx | 2 +- .../chronicle/src/components/ui/search.tsx | 2 +- packages/chronicle/src/lib/openapi.ts | 16 ++-- packages/chronicle/src/lib/page-context.tsx | 2 +- packages/chronicle/src/lib/source.ts | 5 +- packages/chronicle/src/server/App.tsx | 2 +- .../chronicle/src/server/api/apis-proxy.ts | 2 +- packages/chronicle/src/server/api/search.ts | 6 +- packages/chronicle/src/server/api/specs.ts | 4 +- .../src/server/build-search-index.ts | 6 +- .../chronicle/src/server/entry-client.tsx | 82 ++++++++----------- .../chronicle/src/server/entry-server.tsx | 44 ++++++---- packages/chronicle/src/server/routes/[...].ts | 6 +- .../src/server/routes/sitemap.xml.ts | 2 +- packages/chronicle/src/server/vite-config.ts | 26 +++--- .../chronicle/src/themes/default/Layout.tsx | 2 +- .../chronicle/src/themes/default/Page.tsx | 2 +- .../chronicle/src/themes/paper/ChapterNav.tsx | 2 +- packages/chronicle/src/themes/paper/Page.tsx | 2 +- packages/chronicle/src/types/globals.d.ts | 1 + 29 files changed, 174 insertions(+), 137 deletions(-) diff --git a/bun.lock b/bun.lock index 8ae2b20..5042c56 100644 --- a/bun.lock +++ b/bun.lock @@ -17,7 +17,7 @@ "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.39.14", "@heroicons/react": "^2.2.0", - "@raystack/apsara": "^0.56.0", + "@raystack/apsara": "0.55.1", "@shikijs/rehype": "^4.0.2", "@vitejs/plugin-react": "^6.0.1", "chalk": "^5.6.2", @@ -36,7 +36,7 @@ "openapi-types": "^12.1.3", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router-dom": "^7.13.1", + "react-router": "^7.13.1", "remark-directive": "^4.0.0", "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", @@ -410,7 +410,7 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], - "@raystack/apsara": ["@raystack/apsara@0.56.2", "", { "dependencies": { "@ariakit/react": "^0.4.16", "@radix-ui/react-icons": "^1.3.2", "@tanstack/match-sorter-utils": "^8.8.4", "@tanstack/react-table": "^8.9.2", "@tanstack/react-virtual": "^3.13.13", "@tanstack/table-core": "^8.9.2", "class-variance-authority": "^0.7.1", "cmdk": "^1.1.1", "color": "^5.0.0", "dayjs": "^1.11.11", "prism-react-renderer": "^2.4.1", "radix-ui": "^1.4.2", "react-day-picker": "^9.6.7", "sonner": "^2.0.6" }, "peerDependencies": { "@types/react": "^18 || ^19", "react": "^18 || ^19", "react-dom": "^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-kAZh+ir2TdXH1s3a/smWFz8Ebhl3kXPzQp8YEKbxUDipw0//jO6QC1R2reZF5FWKZefLmLW0INMw373W9I6cjg=="], + "@raystack/apsara": ["@raystack/apsara@0.55.1", "", { "dependencies": { "@ariakit/react": "^0.4.16", "@radix-ui/react-icons": "^1.3.2", "@tanstack/match-sorter-utils": "^8.8.4", "@tanstack/react-table": "^8.9.2", "@tanstack/table-core": "^8.9.2", "class-variance-authority": "^0.7.1", "cmdk": "^1.1.1", "color": "^5.0.0", "dayjs": "^1.11.11", "prism-react-renderer": "^2.4.1", "radix-ui": "^1.4.2", "react-day-picker": "^9.6.7", "sonner": "^2.0.6" }, "peerDependencies": { "@types/react": "^18 || ^19", "react": "^18 || ^19", "react-dom": "^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-/7ek/ylQT3GBoxJVd6CMQRmqRO6rHpjXRuSoI6DctrUTEXx3G+lbtovwxJJsKTzf9f2+sIakozhi3W0rQIfi4w=="], "@raystack/chronicle": ["@raystack/chronicle@workspace:packages/chronicle"], @@ -478,12 +478,8 @@ "@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="], - "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.18", "", { "dependencies": { "@tanstack/virtual-core": "3.13.18" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A=="], - "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], - "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.18", "", {}, "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], @@ -1132,8 +1128,6 @@ "react-router": ["react-router@7.13.1", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA=="], - "react-router-dom": ["react-router-dom@7.13.1", "", { "dependencies": { "react-router": "7.13.1" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw=="], - "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], diff --git a/packages/chronicle/build-cli.ts b/packages/chronicle/build-cli.ts index 2f0a6f7..8d0142e 100644 --- a/packages/chronicle/build-cli.ts +++ b/packages/chronicle/build-cli.ts @@ -1,13 +1,14 @@ -import path from 'path' +import pkg from './package.json'; const result = await Bun.build({ entrypoints: ['src/cli/index.ts'], outdir: 'dist/cli', target: 'node', format: 'esm', -}) + external: Object.keys(pkg.dependencies), +}); if (!result.success) { - for (const log of result.logs) console.error(log) - process.exit(1) + for (const log of result.logs) console.error(log); + process.exit(1); } diff --git a/packages/chronicle/package.json b/packages/chronicle/package.json index f0cbba7..a23682e 100644 --- a/packages/chronicle/package.json +++ b/packages/chronicle/package.json @@ -36,7 +36,7 @@ "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.39.14", "@heroicons/react": "^2.2.0", - "@raystack/apsara": "^0.56.0", + "@raystack/apsara": "0.55.1", "@shikijs/rehype": "^4.0.2", "@vitejs/plugin-react": "^6.0.1", "chalk": "^5.6.2", @@ -55,7 +55,7 @@ "openapi-types": "^12.1.3", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router-dom": "^7.13.1", + "react-router": "^7.13.1", "remark-directive": "^4.0.0", "remark-parse": "^11.0.0", "remark-frontmatter": "^5.0.0", diff --git a/packages/chronicle/src/cli/commands/build.ts b/packages/chronicle/src/cli/commands/build.ts index f027ab5..34e6984 100644 --- a/packages/chronicle/src/cli/commands/build.ts +++ b/packages/chronicle/src/cli/commands/build.ts @@ -2,6 +2,7 @@ import chalk from 'chalk'; import { Command } from 'commander'; import { resolveContentDir } from '@/cli/utils/config'; import { PACKAGE_ROOT } from '@/cli/utils/resolve'; +import { linkContent } from '@/cli/utils/scaffold'; export const buildCommand = new Command('build') .description('Build for production') @@ -12,6 +13,7 @@ export const buildCommand = new Command('build') ) .action(async options => { const contentDir = resolveContentDir(options.content); + await linkContent(contentDir); console.log(chalk.cyan('Building for production...')); @@ -19,7 +21,8 @@ export const buildCommand = new Command('build') const { createViteConfig } = await import('@/server/vite-config'); const config = await createViteConfig({ - root: PACKAGE_ROOT, + packageRoot: PACKAGE_ROOT, + projectRoot: process.cwd(), contentDir, preset: options.preset }); diff --git a/packages/chronicle/src/cli/commands/dev.ts b/packages/chronicle/src/cli/commands/dev.ts index 0b819f3..82bbbe2 100644 --- a/packages/chronicle/src/cli/commands/dev.ts +++ b/packages/chronicle/src/cli/commands/dev.ts @@ -1,7 +1,10 @@ +import fs from 'node:fs'; +import path from 'node:path'; import chalk from 'chalk'; import { Command } from 'commander'; import { resolveContentDir } from '@/cli/utils/config'; import { PACKAGE_ROOT } from '@/cli/utils/resolve'; +import { linkContent } from '@/cli/utils/scaffold'; export const devCommand = new Command('dev') .description('Start development server') @@ -11,12 +14,14 @@ export const devCommand = new Command('dev') const contentDir = resolveContentDir(options.content); const port = parseInt(options.port, 10); + await linkContent(contentDir); + console.log(chalk.cyan('Starting dev server...')); const { createServer } = await import('vite'); const { createViteConfig } = await import('@/server/vite-config'); - const config = await createViteConfig({ root: PACKAGE_ROOT, contentDir }); + const config = await createViteConfig({ packageRoot: PACKAGE_ROOT, projectRoot: process.cwd(), contentDir }); const server = await createServer({ ...config, server: { ...config.server, port } diff --git a/packages/chronicle/src/cli/commands/init.ts b/packages/chronicle/src/cli/commands/init.ts index 60e0e3d..008625a 100644 --- a/packages/chronicle/src/cli/commands/init.ts +++ b/packages/chronicle/src/cli/commands/init.ts @@ -19,7 +19,7 @@ function createConfig(): ChronicleConfig { }; } -function createPackageJson(name: string): Record { +async function createPackageJson(name: string): Promise> { return { name, private: true, @@ -30,7 +30,7 @@ function createPackageJson(name: string): Record { start: 'chronicle start' }, dependencies: { - '@raystack/chronicle': `^${getChronicleVersion()}` + '@raystack/chronicle': `^${await getChronicleVersion()}` }, devDependencies: { '@raystack/tools-config': '0.56.0', @@ -56,7 +56,7 @@ This is your documentation home page. export const initCommand = new Command('init') .description('Initialize a new Chronicle project') .option('-c, --content ', 'Content directory name', 'content') - .action(options => { + .action(async options => { const projectDir = process.cwd(); const dirName = path.basename(projectDir) || 'docs'; const contentDir = path.join(projectDir, options.content); @@ -70,12 +70,12 @@ export const initCommand = new Command('init') if (!fs.existsSync(packageJsonPath)) { fs.writeFileSync( packageJsonPath, - `${JSON.stringify(createPackageJson(dirName), null, 2)}\n` + `${JSON.stringify(await createPackageJson(dirName), null, 2)}\n` ); console.log(chalk.green('\u2713'), 'Created', packageJsonPath); } else { const existing = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); - const template = createPackageJson(dirName); + const template = await createPackageJson(dirName); let updated = false; if (existing.type !== 'module') { @@ -162,7 +162,7 @@ export const initCommand = new Command('init') console.log(chalk.green('\u2713'), 'Created .gitignore'); } - const pm = detectPackageManager(); + const pm = await detectPackageManager(); console.log(chalk.cyan(`\nInstalling dependencies with ${pm}...`)); execSync(`${pm} install`, { cwd: projectDir, stdio: 'inherit' }); diff --git a/packages/chronicle/src/cli/commands/serve.ts b/packages/chronicle/src/cli/commands/serve.ts index 20d2ca7..65cfd33 100644 --- a/packages/chronicle/src/cli/commands/serve.ts +++ b/packages/chronicle/src/cli/commands/serve.ts @@ -2,6 +2,7 @@ import chalk from 'chalk'; import { Command } from 'commander'; import { resolveContentDir } from '@/cli/utils/config'; import { PACKAGE_ROOT } from '@/cli/utils/resolve'; +import { linkContent } from '@/cli/utils/scaffold'; export const serveCommand = new Command('serve') .description('Build and start production server') @@ -14,12 +15,14 @@ export const serveCommand = new Command('serve') .action(async options => { const contentDir = resolveContentDir(options.content); const port = parseInt(options.port, 10); + await linkContent(contentDir); const { build, preview } = await import('vite'); const { createViteConfig } = await import('@/server/vite-config'); const config = await createViteConfig({ - root: PACKAGE_ROOT, + packageRoot: PACKAGE_ROOT, + projectRoot: process.cwd(), contentDir, preset: options.preset }); diff --git a/packages/chronicle/src/cli/commands/start.ts b/packages/chronicle/src/cli/commands/start.ts index ff14e7e..ed0ee85 100644 --- a/packages/chronicle/src/cli/commands/start.ts +++ b/packages/chronicle/src/cli/commands/start.ts @@ -2,6 +2,7 @@ import chalk from 'chalk'; import { Command } from 'commander'; import { resolveContentDir } from '@/cli/utils/config'; import { PACKAGE_ROOT } from '@/cli/utils/resolve'; +import { linkContent } from '@/cli/utils/scaffold'; export const startCommand = new Command('start') .description('Start production server') @@ -10,13 +11,14 @@ export const startCommand = new Command('start') .action(async options => { const contentDir = resolveContentDir(options.content); const port = parseInt(options.port, 10); + await linkContent(contentDir); console.log(chalk.cyan('Starting production server...')); const { preview } = await import('vite'); const { createViteConfig } = await import('@/server/vite-config'); - const config = await createViteConfig({ root: PACKAGE_ROOT, contentDir }); + const config = await createViteConfig({ packageRoot: PACKAGE_ROOT, projectRoot: process.cwd(), contentDir }); const server = await preview({ ...config, preview: { port } diff --git a/packages/chronicle/src/cli/utils/scaffold.ts b/packages/chronicle/src/cli/utils/scaffold.ts index 273478d..d51171a 100644 --- a/packages/chronicle/src/cli/utils/scaffold.ts +++ b/packages/chronicle/src/cli/utils/scaffold.ts @@ -1,24 +1,39 @@ -import fs from 'node:fs'; +import fs from 'node:fs/promises'; import path from 'node:path'; import { PACKAGE_ROOT } from './resolve'; -export function detectPackageManager(): string { +export async function detectPackageManager(): Promise { if (process.env.npm_config_user_agent) { return process.env.npm_config_user_agent.split('/')[0]; } const cwd = process.cwd(); - if ( - fs.existsSync(path.join(cwd, 'bun.lock')) || - fs.existsSync(path.join(cwd, 'bun.lockb')) - ) + const exists = async (p: string) => + fs.access(p).then(() => true).catch(() => false); + + if (await exists(path.join(cwd, 'bun.lock')) || await exists(path.join(cwd, 'bun.lockb'))) return 'bun'; - if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm'; - if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn'; + if (await exists(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm'; + if (await exists(path.join(cwd, 'yarn.lock'))) return 'yarn'; return 'npm'; } -export function getChronicleVersion(): string { +export async function getChronicleVersion(): Promise { const pkgPath = path.join(PACKAGE_ROOT, 'package.json'); - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); - return pkg.version; + const raw = await fs.readFile(pkgPath, 'utf-8'); + return JSON.parse(raw).version; +} + +export async function linkContent(contentDir: string): Promise { + const linkPath = path.join(PACKAGE_ROOT, '.content'); + const target = path.resolve(contentDir); + + try { + const existing = await fs.readlink(linkPath); + if (existing === target) return; + await fs.unlink(linkPath); + } catch { + // link doesn't exist + } + + await fs.symlink(target, linkPath); } diff --git a/packages/chronicle/src/components/mdx/link.tsx b/packages/chronicle/src/components/mdx/link.tsx index 20ad0a1..18f30d6 100644 --- a/packages/chronicle/src/components/mdx/link.tsx +++ b/packages/chronicle/src/components/mdx/link.tsx @@ -1,6 +1,6 @@ import { Link as ApsaraLink } from '@raystack/apsara'; import type { ComponentProps } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; +import { Link as RouterLink } from 'react-router'; type LinkProps = ComponentProps<'a'>; diff --git a/packages/chronicle/src/components/ui/search.tsx b/packages/chronicle/src/components/ui/search.tsx index 44c86ac..293893d 100644 --- a/packages/chronicle/src/components/ui/search.tsx +++ b/packages/chronicle/src/components/ui/search.tsx @@ -4,7 +4,7 @@ import { cx } from 'class-variance-authority'; import type { SortedResult } from 'fumadocs-core/search'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { useCallback, useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router'; import { MethodBadge } from '@/components/api/method-badge'; import styles from './search.module.css'; diff --git a/packages/chronicle/src/lib/openapi.ts b/packages/chronicle/src/lib/openapi.ts index 4e12086..0d8a85c 100644 --- a/packages/chronicle/src/lib/openapi.ts +++ b/packages/chronicle/src/lib/openapi.ts @@ -1,5 +1,5 @@ -import fs from 'fs' -import path from 'path' +import fs from 'node:fs/promises' +import path from 'node:path' import { parse as parseYaml } from 'yaml' import type { OpenAPIV2, OpenAPIV3 } from 'openapi-types' import type { ApiConfig, ApiServerConfig, ApiAuthConfig } from '@/types/config' @@ -17,14 +17,14 @@ export interface ApiSpec { export type { SchemaField } from './schema' export { flattenSchema } from './schema' -export function loadApiSpecs(apiConfigs: ApiConfig[]): ApiSpec[] { - const contentDir = process.env.CHRONICLE_CONTENT_DIR ?? process.cwd() - return apiConfigs.map((config) => loadApiSpec(config, contentDir)) +export async function loadApiSpecs(apiConfigs: ApiConfig[]): Promise { + const projectRoot = typeof __CHRONICLE_PROJECT_ROOT__ !== 'undefined' ? __CHRONICLE_PROJECT_ROOT__ : process.cwd() + return Promise.all(apiConfigs.map((config) => loadApiSpec(config, projectRoot))) } -export function loadApiSpec(config: ApiConfig, contentDir: string): ApiSpec { - const specPath = path.resolve(contentDir, config.spec) - const raw = fs.readFileSync(specPath, 'utf-8') +export async function loadApiSpec(config: ApiConfig, projectRoot: string): Promise { + const specPath = path.resolve(projectRoot, config.spec) + const raw = await fs.readFile(specPath, 'utf-8') const isYaml = specPath.endsWith('.yaml') || specPath.endsWith('.yml') const doc = (isYaml ? parseYaml(raw) : JSON.parse(raw)) as OpenAPIV2.Document | OpenAPIV3.Document diff --git a/packages/chronicle/src/lib/page-context.tsx b/packages/chronicle/src/lib/page-context.tsx index 800cd4e..1bf86e9 100644 --- a/packages/chronicle/src/lib/page-context.tsx +++ b/packages/chronicle/src/lib/page-context.tsx @@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router'; import { mdxComponents } from '@/components/mdx'; import type { ApiSpec } from '@/lib/openapi'; import { buildPageTree, getPage, loadPageComponent } from '@/lib/source'; diff --git a/packages/chronicle/src/lib/source.ts b/packages/chronicle/src/lib/source.ts index e92a325..c032c2f 100644 --- a/packages/chronicle/src/lib/source.ts +++ b/packages/chronicle/src/lib/source.ts @@ -117,7 +117,10 @@ export async function loadPageComponent( page: SourcePage ): Promise { if (!page.filePath) return null; - const mod = await import(/* @vite-ignore */ page.filePath); + const contentDir = getContentDir(); + const relativePath = path.relative(contentDir, page.filePath); + const symlinkPath = path.join(__CHRONICLE_PACKAGE_ROOT__, '.content', relativePath); + const mod = await import(/* @vite-ignore */ symlinkPath); return mod.default; } diff --git a/packages/chronicle/src/server/App.tsx b/packages/chronicle/src/server/App.tsx index 33a8e17..2f7b349 100644 --- a/packages/chronicle/src/server/App.tsx +++ b/packages/chronicle/src/server/App.tsx @@ -1,7 +1,7 @@ import '@raystack/apsara/normalize.css'; import '@raystack/apsara/style.css'; import { ThemeProvider } from '@raystack/apsara'; -import { useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router'; import { Head } from '@/lib/head'; import { usePageContext } from '@/lib/page-context'; import { ApiLayout } from '@/pages/ApiLayout'; diff --git a/packages/chronicle/src/server/api/apis-proxy.ts b/packages/chronicle/src/server/api/apis-proxy.ts index 6cb5518..83e8642 100644 --- a/packages/chronicle/src/server/api/apis-proxy.ts +++ b/packages/chronicle/src/server/api/apis-proxy.ts @@ -26,7 +26,7 @@ export default defineHandler(async event => { } const config = loadConfig(); - const specs = loadApiSpecs(config.api ?? []); + const specs = await loadApiSpecs(config.api ?? []); const spec = specs.find(s => s.name === specName); if (!spec) { diff --git a/packages/chronicle/src/server/api/search.ts b/packages/chronicle/src/server/api/search.ts index 0ef4a42..deb249f 100644 --- a/packages/chronicle/src/server/api/search.ts +++ b/packages/chronicle/src/server/api/search.ts @@ -90,12 +90,12 @@ async function scanContent(): Promise { return docs; } -function buildApiDocs(): SearchDocument[] { +async function buildApiDocs(): Promise { const config = loadConfig(); if (!config.api?.length) return []; const docs: SearchDocument[] = []; - const specs = loadApiSpecs(config.api); + const specs = await loadApiSpecs(config.api); for (const spec of specs) { const specSlug = getSpecSlug(spec); @@ -126,7 +126,7 @@ async function loadDocuments(): Promise { const [contentDocs, apiDocs] = await Promise.all([ scanContent(), - Promise.resolve(buildApiDocs()) + buildApiDocs() ]); return [...contentDocs, ...apiDocs]; } diff --git a/packages/chronicle/src/server/api/specs.ts b/packages/chronicle/src/server/api/specs.ts index 61858e7..3d4f1f7 100644 --- a/packages/chronicle/src/server/api/specs.ts +++ b/packages/chronicle/src/server/api/specs.ts @@ -2,8 +2,8 @@ import { defineHandler } from 'nitro'; import { loadConfig } from '@/lib/config'; import { loadApiSpecs } from '@/lib/openapi'; -export default defineHandler(() => { +export default defineHandler(async () => { const config = loadConfig(); - const specs = config.api?.length ? loadApiSpecs(config.api) : []; + const specs = config.api?.length ? await loadApiSpecs(config.api) : []; return specs; }); diff --git a/packages/chronicle/src/server/build-search-index.ts b/packages/chronicle/src/server/build-search-index.ts index 2fa0a78..4196a78 100644 --- a/packages/chronicle/src/server/build-search-index.ts +++ b/packages/chronicle/src/server/build-search-index.ts @@ -73,12 +73,12 @@ async function scanContent(contentDir: string): Promise { return docs; } -function buildApiDocs(): SearchDocument[] { +async function buildApiDocs(): Promise { const config = loadConfig(); if (!config.api?.length) return []; const docs: SearchDocument[] = []; - const specs = loadApiSpecs(config.api); + const specs = await loadApiSpecs(config.api); for (const spec of specs) { const specSlug = getSpecSlug(spec); @@ -106,7 +106,7 @@ function buildApiDocs(): SearchDocument[] { export async function generateSearchIndex(contentDir: string, outDir: string) { const [contentDocs, apiDocs] = await Promise.all([ scanContent(contentDir), - Promise.resolve(buildApiDocs()) + buildApiDocs() ]); const documents = [...contentDocs, ...apiDocs]; diff --git a/packages/chronicle/src/server/entry-client.tsx b/packages/chronicle/src/server/entry-client.tsx index 76fb7e0..a07fa05 100644 --- a/packages/chronicle/src/server/entry-client.tsx +++ b/packages/chronicle/src/server/entry-client.tsx @@ -1,67 +1,43 @@ -import type { ReactNode } from 'react'; +import '@vitejs/plugin-react/preamble'; import React from 'react'; import { hydrateRoot } from 'react-dom/client'; -import { BrowserRouter } from 'react-router-dom'; +import { BrowserRouter } from 'react-router'; import { mdxComponents } from '@/components/mdx'; -import type { ApiSpec } from '@/lib/openapi'; import { PageProvider } from '@/lib/page-context'; -import { buildPageTree, getPage, loadPageComponent } from '@/lib/source'; import type { ChronicleConfig, Frontmatter, PageTree } from '@/types'; +import type { ApiSpec } from '@/lib/openapi'; +import type { ReactNode } from 'react'; import { App } from './App'; interface EmbeddedData { config: ChronicleConfig; tree: PageTree; slug: string[]; - frontmatter: { title: string; description?: string; order?: number }; + frontmatter: Frontmatter; filePath: string; } async function hydrate() { try { - const embedded: EmbeddedData | undefined = (window as unknown as { __PAGE_DATA__?: EmbeddedData }).__PAGE_DATA__; + const embedded = ( + window as unknown as { __PAGE_DATA__?: EmbeddedData } + ).__PAGE_DATA__; - let config: ChronicleConfig = { title: 'Documentation' }; - let tree: PageTree = { name: 'root', children: [] }; - let page: { slug: string[]; frontmatter: Frontmatter; content: ReactNode } | null = - null; - let apiSpecs: ApiSpec[] = []; + const config: ChronicleConfig = embedded?.config ?? { + title: 'Documentation' + }; + const tree: PageTree = embedded?.tree ?? { name: 'root', children: [] }; + const isApiPage = + window.location.pathname.startsWith('/apis') && !!config.api?.length; + const apiSpecs: ApiSpec[] = isApiPage + ? await fetch('/api/specs') + .then(r => r.json()) + .catch(() => []) + : []; - if (embedded) { - config = embedded.config; - tree = embedded.tree; - - // Fetch API specs if on /apis route - const isApiRoute = window.location.pathname.startsWith('/apis'); - if (isApiRoute && config.api?.length) { - try { - const res = await fetch('/api/specs'); - apiSpecs = await res.json(); - } catch { - /* will load on demand */ - } - } - - const sourcePage = await getPage(embedded.slug); - if (sourcePage) { - const component = await loadPageComponent(sourcePage); - page = { - slug: embedded.slug, - frontmatter: embedded.frontmatter, - content: component - ? React.createElement(component, { components: mdxComponents }) - : null - }; - } else { - page = { - slug: embedded.slug, - frontmatter: embedded.frontmatter, - content: null - }; - } - } else { - tree = await buildPageTree(); - } + const page = embedded?.filePath + ? await loadPage(embedded) + : null; hydrateRoot( document.getElementById('root') as HTMLElement, @@ -81,4 +57,18 @@ async function hydrate() { } } +async function loadPage( + embedded: EmbeddedData +): Promise<{ slug: string[]; frontmatter: Frontmatter; content: ReactNode }> { + const contentDir = __CHRONICLE_CONTENT_DIR__; + const relativePath = embedded.filePath + .replace(contentDir, '') + .replace(/^\//, ''); + const mod = await import(/* @vite-ignore */ `/.content/${relativePath}`); + const content = mod.default + ? React.createElement(mod.default, { components: mdxComponents }) + : null; + return { slug: embedded.slug, frontmatter: embedded.frontmatter, content }; +} + hydrate(); diff --git a/packages/chronicle/src/server/entry-server.tsx b/packages/chronicle/src/server/entry-server.tsx index 342c36c..c0e555c 100644 --- a/packages/chronicle/src/server/entry-server.tsx +++ b/packages/chronicle/src/server/entry-server.tsx @@ -1,6 +1,7 @@ +import { PassThrough } from 'node:stream'; import type { ReactNode } from 'react'; -import { renderToString } from 'react-dom/server'; -import { StaticRouter } from 'react-router-dom'; +import { renderToPipeableStream } from 'react-dom/server'; +import { StaticRouter } from 'react-router'; import type { ApiSpec } from '@/lib/openapi'; import { PageProvider } from '@/lib/page-context'; import type { ChronicleConfig, Frontmatter, PageTree } from '@/types'; @@ -17,19 +18,32 @@ export interface SSRData { apiSpecs: ApiSpec[]; } -export function render(url: string, data: SSRData): string { +export function render(url: string, data: SSRData): Promise { const pathname = new URL(url, 'http://localhost').pathname; - return renderToString( - - - - - - ); + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + const { pipe } = renderToPipeableStream( + + + + + , + { + onAllReady() { + const passthrough = new PassThrough(); + passthrough.on('data', (chunk: Buffer) => chunks.push(chunk)); + passthrough.on('end', () => resolve(Buffer.concat(chunks).toString())); + passthrough.on('error', reject); + pipe(passthrough); + }, + onError: reject, + } + ); + }); } diff --git a/packages/chronicle/src/server/routes/[...].ts b/packages/chronicle/src/server/routes/[...].ts index 9ec87d7..851ad72 100644 --- a/packages/chronicle/src/server/routes/[...].ts +++ b/packages/chronicle/src/server/routes/[...].ts @@ -12,7 +12,9 @@ export default defineHandler(async event => { pathname === '/' ? [] : pathname.slice(1).split('/').filter(Boolean); const config = loadConfig(); - const apiSpecs = config.api?.length ? loadApiSpecs(config.api) : []; + const apiSpecs = config.api?.length + ? await loadApiSpecs(config.api).catch(() => []) + : []; const [tree, sourcePage] = await Promise.all([ buildPageTree(), @@ -41,7 +43,7 @@ export default defineHandler(async event => { embeddedData.filePath = sourcePage.filePath; } - const appHtml = render(event.url.href, { config, tree, page: pageData, apiSpecs }); + const appHtml = await render(event.url.href, { config, tree, page: pageData, apiSpecs }); const safeJson = JSON.stringify(embeddedData).replace(/window.__PAGE_DATA__ = ${safeJson}`; diff --git a/packages/chronicle/src/server/routes/sitemap.xml.ts b/packages/chronicle/src/server/routes/sitemap.xml.ts index 62c9009..a961ce3 100644 --- a/packages/chronicle/src/server/routes/sitemap.xml.ts +++ b/packages/chronicle/src/server/routes/sitemap.xml.ts @@ -23,7 +23,7 @@ export default defineHandler(async event => { }); const apiPages = config.api?.length - ? buildApiRoutes(loadApiSpecs(config.api)).map( + ? buildApiRoutes(await loadApiSpecs(config.api)).map( route => `${baseUrl}/apis/${route.slug.join('/')}` ) : []; diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts index a8a50e8..ac6eb74 100644 --- a/packages/chronicle/src/server/vite-config.ts +++ b/packages/chronicle/src/server/vite-config.ts @@ -5,7 +5,8 @@ import path from 'node:path'; import { type InlineConfig } from 'vite'; export interface ViteConfigOptions { - root: string; + packageRoot: string; + projectRoot: string; contentDir: string; preset?: string; } @@ -13,24 +14,26 @@ export interface ViteConfigOptions { export async function createViteConfig( options: ViteConfigOptions ): Promise { - const { root, contentDir, preset } = options; + const { packageRoot, projectRoot, contentDir, preset } = options; return { - root, + root: packageRoot, configFile: false, plugins: [ nitro({ - serverDir: path.resolve(root, 'src/server'), - ...(preset && { preset }) + serverDir: path.resolve(packageRoot, 'src/server'), + ...(preset && { preset }), + noExternals: true, }), mdx({}, { index: false }), react() ], resolve: { alias: { - '@': path.resolve(root, 'src'), - '@content': contentDir + '@': path.resolve(packageRoot, 'src'), + '@content': path.resolve(packageRoot, '.content') }, + conditions: ['module-sync', 'import', 'node'], dedupe: [ 'react', 'react-dom', @@ -40,12 +43,13 @@ export async function createViteConfig( }, server: { fs: { - allow: [root, contentDir] + allow: [packageRoot, projectRoot, contentDir] } }, define: { __CHRONICLE_CONTENT_DIR__: JSON.stringify(contentDir), - __CHRONICLE_PROJECT_ROOT__: JSON.stringify(path.resolve(contentDir, '..')) + __CHRONICLE_PROJECT_ROOT__: JSON.stringify(projectRoot), + __CHRONICLE_PACKAGE_ROOT__: JSON.stringify(packageRoot) }, css: { modules: { @@ -53,13 +57,13 @@ export async function createViteConfig( } }, ssr: { - noExternal: ['@raystack/apsara', 'fumadocs-core'] + noExternal: ['@raystack/apsara', 'dayjs', 'fumadocs-core'] }, environments: { client: { build: { rollupOptions: { - input: path.resolve(root, 'src/server/entry-client.tsx') + input: path.resolve(packageRoot, 'src/server/entry-client.tsx') } } } diff --git a/packages/chronicle/src/themes/default/Layout.tsx b/packages/chronicle/src/themes/default/Layout.tsx index 401f7dd..b24f885 100644 --- a/packages/chronicle/src/themes/default/Layout.tsx +++ b/packages/chronicle/src/themes/default/Layout.tsx @@ -9,7 +9,7 @@ import { } from '@raystack/apsara'; import { cx } from 'class-variance-authority'; import { useEffect, useMemo, useRef } from 'react'; -import { Link as RouterLink, useLocation } from 'react-router-dom'; +import { Link as RouterLink, useLocation } from 'react-router'; import { MethodBadge } from '@/components/api/method-badge'; import { ClientThemeSwitcher } from '@/components/ui/client-theme-switcher'; import { Footer } from '@/components/ui/footer'; diff --git a/packages/chronicle/src/themes/default/Page.tsx b/packages/chronicle/src/themes/default/Page.tsx index 6a7f452..247acf9 100644 --- a/packages/chronicle/src/themes/default/Page.tsx +++ b/packages/chronicle/src/themes/default/Page.tsx @@ -9,7 +9,7 @@ import { Toc } from './Toc'; export function Page({ page, tree }: ThemePageProps) { return ( -
+
{page.content}
diff --git a/packages/chronicle/src/themes/paper/ChapterNav.tsx b/packages/chronicle/src/themes/paper/ChapterNav.tsx index 393da0c..730bb42 100644 --- a/packages/chronicle/src/themes/paper/ChapterNav.tsx +++ b/packages/chronicle/src/themes/paper/ChapterNav.tsx @@ -1,4 +1,4 @@ -import { Link as RouterLink, useLocation } from 'react-router-dom'; +import { Link as RouterLink, useLocation } from 'react-router'; import { MethodBadge } from '@/components/api/method-badge'; import type { PageTree, PageTreeItem } from '@/types'; import styles from './ChapterNav.module.css'; diff --git a/packages/chronicle/src/themes/paper/Page.tsx b/packages/chronicle/src/themes/paper/Page.tsx index 011be62..ab3d82a 100644 --- a/packages/chronicle/src/themes/paper/Page.tsx +++ b/packages/chronicle/src/themes/paper/Page.tsx @@ -1,7 +1,7 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'; import { Flex } from '@raystack/apsara'; import { useMemo } from 'react'; -import { Link as RouterLink, useLocation } from 'react-router-dom'; +import { Link as RouterLink, useLocation } from 'react-router'; import { Search } from '@/components/ui/search'; import type { PageTreeItem, ThemePageProps } from '@/types'; import styles from './Page.module.css'; diff --git a/packages/chronicle/src/types/globals.d.ts b/packages/chronicle/src/types/globals.d.ts index 32b9cd4..d4c3fe8 100644 --- a/packages/chronicle/src/types/globals.d.ts +++ b/packages/chronicle/src/types/globals.d.ts @@ -1,3 +1,4 @@ // Vite build-time constants (injected via define in vite-config.ts) declare const __CHRONICLE_CONTENT_DIR__: string declare const __CHRONICLE_PROJECT_ROOT__: string +declare const __CHRONICLE_PACKAGE_ROOT__: string From 0264c7e5d2aea11d8f31bc7ea2178457b9d03d5e Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 12:29:50 +0530 Subject: [PATCH 06/38] feat: simplify init command to content + config only Remove package.json creation, dependency installation, and detectPackageManager/getChronicleVersion usage from init. Init now only creates content dir, chronicle.yaml, sample MDX, and .gitignore. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/cli/commands/init.ts | 127 ++------------------ 1 file changed, 10 insertions(+), 117 deletions(-) diff --git a/packages/chronicle/src/cli/commands/init.ts b/packages/chronicle/src/cli/commands/init.ts index 008625a..d72734d 100644 --- a/packages/chronicle/src/cli/commands/init.ts +++ b/packages/chronicle/src/cli/commands/init.ts @@ -1,46 +1,16 @@ -import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import chalk from 'chalk'; import { Command } from 'commander'; import { stringify } from 'yaml'; -import { - detectPackageManager, - getChronicleVersion -} from '@/cli/utils/scaffold'; import type { ChronicleConfig } from '@/types'; -function createConfig(): ChronicleConfig { - return { - title: 'My Documentation', - description: 'Documentation powered by Chronicle', - theme: { name: 'default' }, - search: { enabled: true, placeholder: 'Search documentation...' } - }; -} - -async function createPackageJson(name: string): Promise> { - return { - name, - private: true, - type: 'module', - scripts: { - dev: 'chronicle dev', - build: 'chronicle build', - start: 'chronicle start' - }, - dependencies: { - '@raystack/chronicle': `^${await getChronicleVersion()}` - }, - devDependencies: { - '@raystack/tools-config': '0.56.0', - 'openapi-types': '^12.1.3', - typescript: '5.9.3', - '@types/react': '^19.2.10', - '@types/node': '^25.1.0' - } - }; -} +const defaultConfig: ChronicleConfig = { + title: 'My Documentation', + description: 'Documentation powered by Chronicle', + theme: { name: 'default' }, + search: { enabled: true, placeholder: 'Search documentation...' } +}; const sampleMdx = `--- title: Welcome @@ -56,9 +26,8 @@ This is your documentation home page. export const initCommand = new Command('init') .description('Initialize a new Chronicle project') .option('-c, --content ', 'Content directory name', 'content') - .action(async options => { + .action(options => { const projectDir = process.cwd(); - const dirName = path.basename(projectDir) || 'docs'; const contentDir = path.join(projectDir, options.content); if (!fs.existsSync(contentDir)) { @@ -66,71 +35,9 @@ export const initCommand = new Command('init') console.log(chalk.green('\u2713'), 'Created', contentDir); } - const packageJsonPath = path.join(projectDir, 'package.json'); - if (!fs.existsSync(packageJsonPath)) { - fs.writeFileSync( - packageJsonPath, - `${JSON.stringify(await createPackageJson(dirName), null, 2)}\n` - ); - console.log(chalk.green('\u2713'), 'Created', packageJsonPath); - } else { - const existing = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); - const template = await createPackageJson(dirName); - let updated = false; - - if (existing.type !== 'module') { - existing.type = 'module'; - updated = true; - } - - if (!existing.scripts) existing.scripts = {}; - for (const [key, value] of Object.entries( - template.scripts as Record - )) { - if (!existing.scripts[key]) { - existing.scripts[key] = value; - updated = true; - } - } - - if (!existing.dependencies) existing.dependencies = {}; - for (const [key, value] of Object.entries( - template.dependencies as Record - )) { - if (!existing.dependencies[key]) { - existing.dependencies[key] = value; - updated = true; - } - } - - if (!existing.devDependencies) existing.devDependencies = {}; - for (const [key, value] of Object.entries( - template.devDependencies as Record - )) { - if (!existing.devDependencies[key]) { - existing.devDependencies[key] = value; - updated = true; - } - } - - if (updated) { - fs.writeFileSync( - packageJsonPath, - `${JSON.stringify(existing, null, 2)}\n` - ); - console.log(chalk.green('\u2713'), 'Updated', packageJsonPath); - } else { - console.log( - chalk.yellow('\u26a0'), - packageJsonPath, - 'already has all required entries' - ); - } - } - const configPath = path.join(projectDir, 'chronicle.yaml'); if (!fs.existsSync(configPath)) { - fs.writeFileSync(configPath, stringify(createConfig())); + fs.writeFileSync(configPath, stringify(defaultConfig)); console.log(chalk.green('\u2713'), 'Created', configPath); } else { console.log(chalk.yellow('\u26a0'), configPath, 'already exists'); @@ -150,27 +57,13 @@ export const initCommand = new Command('init') const missing = gitignoreEntries.filter(e => !existing.includes(e)); if (missing.length > 0) { fs.appendFileSync(gitignorePath, `\n${missing.join('\n')}\n`); - console.log( - chalk.green('\u2713'), - 'Added', - missing.join(', '), - 'to .gitignore' - ); + console.log(chalk.green('\u2713'), 'Added', missing.join(', '), 'to .gitignore'); } } else { fs.writeFileSync(gitignorePath, `${gitignoreEntries.join('\n')}\n`); console.log(chalk.green('\u2713'), 'Created .gitignore'); } - const pm = await detectPackageManager(); - console.log(chalk.cyan(`\nInstalling dependencies with ${pm}...`)); - execSync(`${pm} install`, { cwd: projectDir, stdio: 'inherit' }); - - const runCmd = pm === 'npm' ? 'npx' : pm === 'bun' ? 'bunx' : `${pm} dlx`; console.log(chalk.green('\n\u2713 Chronicle initialized!')); - console.log( - '\nRun', - chalk.cyan(`${runCmd} chronicle dev`), - 'to start development server' - ); + console.log('\nRun', chalk.cyan('chronicle dev'), 'to start development server'); }); From 90c6e53651957bea49fd01acaf0acf1b82520e46 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 12:30:30 +0530 Subject: [PATCH 07/38] chore: remove unused detectPackageManager and getChronicleVersion Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/cli/utils/scaffold.ts | 21 -------------------- 1 file changed, 21 deletions(-) diff --git a/packages/chronicle/src/cli/utils/scaffold.ts b/packages/chronicle/src/cli/utils/scaffold.ts index d51171a..a27af8b 100644 --- a/packages/chronicle/src/cli/utils/scaffold.ts +++ b/packages/chronicle/src/cli/utils/scaffold.ts @@ -2,27 +2,6 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import { PACKAGE_ROOT } from './resolve'; -export async function detectPackageManager(): Promise { - if (process.env.npm_config_user_agent) { - return process.env.npm_config_user_agent.split('/')[0]; - } - const cwd = process.cwd(); - const exists = async (p: string) => - fs.access(p).then(() => true).catch(() => false); - - if (await exists(path.join(cwd, 'bun.lock')) || await exists(path.join(cwd, 'bun.lockb'))) - return 'bun'; - if (await exists(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm'; - if (await exists(path.join(cwd, 'yarn.lock'))) return 'yarn'; - return 'npm'; -} - -export async function getChronicleVersion(): Promise { - const pkgPath = path.join(PACKAGE_ROOT, 'package.json'); - const raw = await fs.readFile(pkgPath, 'utf-8'); - return JSON.parse(raw).version; -} - export async function linkContent(contentDir: string): Promise { const linkPath = path.join(PACKAGE_ROOT, '.content'); const target = path.resolve(contentDir); From 8234b09ac03fcdc373dd8699631bc41603a52c06 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 12:38:05 +0530 Subject: [PATCH 08/38] feat: server-side page API, remove source.ts from client bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add /api/page/:slug endpoint — returns frontmatter + rendered contentHtml - page-context.tsx: fetch from /api/page on client navigation instead of importing source.ts (removes fs/gray-matter from client bundle) - entry-client.tsx: use SSR content HTML for initial hydration - Extract renderToHtml utility (shared by catch-all route and page API) - Fixes buffer/node:fs/promises client warnings Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/lib/page-context.tsx | 52 +++++++------------ .../src/server/api/page/[...slug].ts | 22 ++++++++ .../chronicle/src/server/entry-client.tsx | 30 ++++------- .../src/server/utils/render-to-html.ts | 19 +++++++ 4 files changed, 72 insertions(+), 51 deletions(-) create mode 100644 packages/chronicle/src/server/api/page/[...slug].ts create mode 100644 packages/chronicle/src/server/utils/render-to-html.ts diff --git a/packages/chronicle/src/lib/page-context.tsx b/packages/chronicle/src/lib/page-context.tsx index 1bf86e9..08117c4 100644 --- a/packages/chronicle/src/lib/page-context.tsx +++ b/packages/chronicle/src/lib/page-context.tsx @@ -6,9 +6,7 @@ import React, { useState } from 'react'; import { useLocation } from 'react-router'; -import { mdxComponents } from '@/components/mdx'; import type { ApiSpec } from '@/lib/openapi'; -import { buildPageTree, getPage, loadPageComponent } from '@/lib/source'; import type { ChronicleConfig, Frontmatter, PageTree } from '@/types'; interface PageData { @@ -56,7 +54,7 @@ export function PageProvider({ children }: PageProviderProps) { const { pathname } = useLocation(); - const [tree, setTree] = useState(initialTree); + const [tree] = useState(initialTree); const [page, setPage] = useState(initialPage); const [apiSpecs, setApiSpecs] = useState(initialApiSpecs); const [currentPath, setCurrentPath] = useState(pathname); @@ -65,49 +63,39 @@ export function PageProvider({ if (pathname === currentPath) return; setCurrentPath(pathname); - let cancelled = false; + const cancelled = { current: false }; if (pathname.startsWith('/apis')) { - // Fetch API specs if not already loaded if (apiSpecs.length === 0) { fetch('/api/specs') .then(res => res.json()) .then(specs => { - if (!cancelled) setApiSpecs(specs); + if (!cancelled.current) setApiSpecs(specs); }) .catch(() => {}); } - return () => { - cancelled = true; - }; + return () => { cancelled.current = true; }; } - async function load() { - const slug = - pathname === '/' ? [] : pathname.slice(1).split('/').filter(Boolean); + const slug = pathname === '/' + ? [] + : pathname.slice(1).split('/').filter(Boolean); - const [sourcePage, newTree] = await Promise.all([ - getPage(slug), - buildPageTree() - ]); - if (cancelled || !sourcePage) return; + const apiPath = slug.length === 0 ? '/api/page' : `/api/page/${slug.join('/')}`; - const component = await loadPageComponent(sourcePage); - if (cancelled) return; + fetch(apiPath) + .then(res => res.json()) + .then((data: { frontmatter: Frontmatter; contentHtml: string }) => { + if (cancelled.current) return; + setPage({ + slug, + frontmatter: data.frontmatter, + content:
, + }); + }) + .catch(() => {}); - setTree(newTree); - setPage({ - slug, - frontmatter: sourcePage.frontmatter, - content: component - ? React.createElement(component, { components: mdxComponents }) - : null - }); - } - load(); - return () => { - cancelled = true; - }; + return () => { cancelled.current = true; }; }, [pathname]); return ( diff --git a/packages/chronicle/src/server/api/page/[...slug].ts b/packages/chronicle/src/server/api/page/[...slug].ts new file mode 100644 index 0000000..4d9069a --- /dev/null +++ b/packages/chronicle/src/server/api/page/[...slug].ts @@ -0,0 +1,22 @@ +import React from 'react'; +import { defineHandler, HTTPError } from 'nitro'; +import { mdxComponents } from '@/components/mdx'; +import { getPage, loadPageComponent } from '@/lib/source'; +import { renderToHtml } from '../../utils/render-to-html'; + +export default defineHandler(async event => { + const slugParam = event.context.params?.slug ?? ''; + const slug = slugParam ? slugParam.split('/') : []; + const page = await getPage(slug); + + if (!page) { + throw new HTTPError({ status: 404, message: 'Page not found' }); + } + + const component = await loadPageComponent(page); + const contentHtml = component + ? await renderToHtml(React.createElement(component, { components: mdxComponents })) + : ''; + + return { frontmatter: page.frontmatter, contentHtml }; +}); diff --git a/packages/chronicle/src/server/entry-client.tsx b/packages/chronicle/src/server/entry-client.tsx index a07fa05..ea5005e 100644 --- a/packages/chronicle/src/server/entry-client.tsx +++ b/packages/chronicle/src/server/entry-client.tsx @@ -2,11 +2,9 @@ import '@vitejs/plugin-react/preamble'; import React from 'react'; import { hydrateRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router'; -import { mdxComponents } from '@/components/mdx'; import { PageProvider } from '@/lib/page-context'; import type { ChronicleConfig, Frontmatter, PageTree } from '@/types'; import type { ApiSpec } from '@/lib/openapi'; -import type { ReactNode } from 'react'; import { App } from './App'; interface EmbeddedData { @@ -14,9 +12,13 @@ interface EmbeddedData { tree: PageTree; slug: string[]; frontmatter: Frontmatter; - filePath: string; + contentHtml: string; } +// Capture SSR content before hydration +const ssrContentHtml = + document.querySelector('[data-article-content]')?.innerHTML ?? ''; + async function hydrate() { try { const embedded = ( @@ -35,8 +37,12 @@ async function hydrate() { .catch(() => []) : []; - const page = embedded?.filePath - ? await loadPage(embedded) + const page = embedded?.frontmatter + ? { + slug: embedded.slug, + frontmatter: embedded.frontmatter, + content:
, + } : null; hydrateRoot( @@ -57,18 +63,4 @@ async function hydrate() { } } -async function loadPage( - embedded: EmbeddedData -): Promise<{ slug: string[]; frontmatter: Frontmatter; content: ReactNode }> { - const contentDir = __CHRONICLE_CONTENT_DIR__; - const relativePath = embedded.filePath - .replace(contentDir, '') - .replace(/^\//, ''); - const mod = await import(/* @vite-ignore */ `/.content/${relativePath}`); - const content = mod.default - ? React.createElement(mod.default, { components: mdxComponents }) - : null; - return { slug: embedded.slug, frontmatter: embedded.frontmatter, content }; -} - hydrate(); diff --git a/packages/chronicle/src/server/utils/render-to-html.ts b/packages/chronicle/src/server/utils/render-to-html.ts new file mode 100644 index 0000000..09fff74 --- /dev/null +++ b/packages/chronicle/src/server/utils/render-to-html.ts @@ -0,0 +1,19 @@ +import { PassThrough } from 'node:stream'; +import type { ReactElement } from 'react'; +import { renderToPipeableStream } from 'react-dom/server'; + +export function renderToHtml(element: ReactElement): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + const { pipe } = renderToPipeableStream(element, { + onAllReady() { + const passthrough = new PassThrough(); + passthrough.on('data', (chunk: Buffer) => chunks.push(chunk)); + passthrough.on('end', () => resolve(Buffer.concat(chunks).toString())); + passthrough.on('error', reject); + pipe(passthrough); + }, + onError: reject, + }); + }); +} From 50ecc6de2392929b4d9f29ee01debb034b6e1635 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 13:07:41 +0530 Subject: [PATCH 09/38] feat: MDX import on client, /api/page endpoint, fix duplicate React - Client imports MDX via /.content/ symlink (matches SSR React tree) - /api/page/:slug returns frontmatter + relativePath for client navigation - page-context.tsx: fetch /api/page then import MDX (no source.ts on client) - entry-client.tsx: import MDX for initial hydration - Remove render-to-html utility (page API returns path, not HTML) - Embed relativePath instead of filePath in __PAGE_DATA__ - Add .content to .gitignore - Remove cacheDir and React alias hacks Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/.gitignore | 1 + packages/chronicle/src/lib/page-context.tsx | 20 +++++++++----- .../src/server/api/page/[...slug].ts | 14 ++++------ .../chronicle/src/server/entry-client.tsx | 26 +++++++++++-------- packages/chronicle/src/server/routes/[...].ts | 5 ++-- .../src/server/utils/render-to-html.ts | 19 -------------- packages/chronicle/src/server/vite-config.ts | 5 ++-- 7 files changed, 40 insertions(+), 50 deletions(-) delete mode 100644 packages/chronicle/src/server/utils/render-to-html.ts diff --git a/packages/chronicle/.gitignore b/packages/chronicle/.gitignore index 8e27122..38d9526 100644 --- a/packages/chronicle/.gitignore +++ b/packages/chronicle/.gitignore @@ -1,3 +1,4 @@ node_modules dist .source +.content diff --git a/packages/chronicle/src/lib/page-context.tsx b/packages/chronicle/src/lib/page-context.tsx index 08117c4..8f159d1 100644 --- a/packages/chronicle/src/lib/page-context.tsx +++ b/packages/chronicle/src/lib/page-context.tsx @@ -6,6 +6,7 @@ import React, { useState } from 'react'; import { useLocation } from 'react-router'; +import { mdxComponents } from '@/components/mdx'; import type { ApiSpec } from '@/lib/openapi'; import type { ChronicleConfig, Frontmatter, PageTree } from '@/types'; @@ -46,6 +47,13 @@ interface PageProviderProps { children: ReactNode; } +async function loadMdxComponent(relativePath: string): Promise { + const mod = await import(/* @vite-ignore */ `/.content/${relativePath}`); + return mod.default + ? React.createElement(mod.default, { components: mdxComponents }) + : null; +} + export function PageProvider({ initialConfig, initialTree, @@ -81,17 +89,15 @@ export function PageProvider({ ? [] : pathname.slice(1).split('/').filter(Boolean); - const apiPath = slug.length === 0 ? '/api/page' : `/api/page/${slug.join('/')}`; + const apiPath = slug.length === 0 ? '/api/page/' : `/api/page/${slug.join('/')}`; fetch(apiPath) .then(res => res.json()) - .then((data: { frontmatter: Frontmatter; contentHtml: string }) => { + .then(async (data: { frontmatter: Frontmatter; relativePath: string }) => { + if (cancelled.current) return; + const content = await loadMdxComponent(data.relativePath); if (cancelled.current) return; - setPage({ - slug, - frontmatter: data.frontmatter, - content:
, - }); + setPage({ slug, frontmatter: data.frontmatter, content }); }) .catch(() => {}); diff --git a/packages/chronicle/src/server/api/page/[...slug].ts b/packages/chronicle/src/server/api/page/[...slug].ts index 4d9069a..e867dff 100644 --- a/packages/chronicle/src/server/api/page/[...slug].ts +++ b/packages/chronicle/src/server/api/page/[...slug].ts @@ -1,8 +1,6 @@ -import React from 'react'; +import path from 'node:path'; import { defineHandler, HTTPError } from 'nitro'; -import { mdxComponents } from '@/components/mdx'; -import { getPage, loadPageComponent } from '@/lib/source'; -import { renderToHtml } from '../../utils/render-to-html'; +import { getPage } from '@/lib/source'; export default defineHandler(async event => { const slugParam = event.context.params?.slug ?? ''; @@ -13,10 +11,8 @@ export default defineHandler(async event => { throw new HTTPError({ status: 404, message: 'Page not found' }); } - const component = await loadPageComponent(page); - const contentHtml = component - ? await renderToHtml(React.createElement(component, { components: mdxComponents })) - : ''; + const contentDir = __CHRONICLE_CONTENT_DIR__; + const relativePath = path.relative(contentDir, page.filePath); - return { frontmatter: page.frontmatter, contentHtml }; + return { frontmatter: page.frontmatter, relativePath }; }); diff --git a/packages/chronicle/src/server/entry-client.tsx b/packages/chronicle/src/server/entry-client.tsx index ea5005e..42e281b 100644 --- a/packages/chronicle/src/server/entry-client.tsx +++ b/packages/chronicle/src/server/entry-client.tsx @@ -2,9 +2,11 @@ import '@vitejs/plugin-react/preamble'; import React from 'react'; import { hydrateRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router'; +import { mdxComponents } from '@/components/mdx'; import { PageProvider } from '@/lib/page-context'; import type { ChronicleConfig, Frontmatter, PageTree } from '@/types'; import type { ApiSpec } from '@/lib/openapi'; +import type { ReactNode } from 'react'; import { App } from './App'; interface EmbeddedData { @@ -12,13 +14,9 @@ interface EmbeddedData { tree: PageTree; slug: string[]; frontmatter: Frontmatter; - contentHtml: string; + relativePath: string; } -// Capture SSR content before hydration -const ssrContentHtml = - document.querySelector('[data-article-content]')?.innerHTML ?? ''; - async function hydrate() { try { const embedded = ( @@ -37,12 +35,8 @@ async function hydrate() { .catch(() => []) : []; - const page = embedded?.frontmatter - ? { - slug: embedded.slug, - frontmatter: embedded.frontmatter, - content:
, - } + const page = embedded?.relativePath + ? await loadPage(embedded) : null; hydrateRoot( @@ -63,4 +57,14 @@ async function hydrate() { } } +async function loadPage( + embedded: EmbeddedData +): Promise<{ slug: string[]; frontmatter: Frontmatter; content: ReactNode }> { + const mod = await import(/* @vite-ignore */ `/.content/${embedded.relativePath}`); + const content = mod.default + ? React.createElement(mod.default, { components: mdxComponents }) + : null; + return { slug: embedded.slug, frontmatter: embedded.frontmatter, content }; +} + hydrate(); diff --git a/packages/chronicle/src/server/routes/[...].ts b/packages/chronicle/src/server/routes/[...].ts index 851ad72..f0990dc 100644 --- a/packages/chronicle/src/server/routes/[...].ts +++ b/packages/chronicle/src/server/routes/[...].ts @@ -1,3 +1,4 @@ +import path from 'node:path'; import { defineHandler } from 'nitro'; import React from 'react'; import { mdxComponents } from '@/components/mdx'; @@ -27,7 +28,7 @@ export default defineHandler(async event => { tree, slug, frontmatter: null, - filePath: null + relativePath: null }; if (sourcePage) { @@ -40,7 +41,7 @@ export default defineHandler(async event => { : null }; embeddedData.frontmatter = sourcePage.frontmatter; - embeddedData.filePath = sourcePage.filePath; + embeddedData.relativePath = path.relative(__CHRONICLE_CONTENT_DIR__, sourcePage.filePath); } const appHtml = await render(event.url.href, { config, tree, page: pageData, apiSpecs }); diff --git a/packages/chronicle/src/server/utils/render-to-html.ts b/packages/chronicle/src/server/utils/render-to-html.ts deleted file mode 100644 index 09fff74..0000000 --- a/packages/chronicle/src/server/utils/render-to-html.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { PassThrough } from 'node:stream'; -import type { ReactElement } from 'react'; -import { renderToPipeableStream } from 'react-dom/server'; - -export function renderToHtml(element: ReactElement): Promise { - return new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - const { pipe } = renderToPipeableStream(element, { - onAllReady() { - const passthrough = new PassThrough(); - passthrough.on('data', (chunk: Buffer) => chunks.push(chunk)); - passthrough.on('end', () => resolve(Buffer.concat(chunks).toString())); - passthrough.on('error', reject); - pipe(passthrough); - }, - onError: reject, - }); - }); -} diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts index ac6eb74..c35bb9b 100644 --- a/packages/chronicle/src/server/vite-config.ts +++ b/packages/chronicle/src/server/vite-config.ts @@ -31,14 +31,15 @@ export async function createViteConfig( resolve: { alias: { '@': path.resolve(packageRoot, 'src'), - '@content': path.resolve(packageRoot, '.content') + '@content': path.resolve(packageRoot, '.content'), }, conditions: ['module-sync', 'import', 'node'], dedupe: [ 'react', 'react-dom', 'react/jsx-runtime', - 'react/jsx-dev-runtime' + 'react/jsx-dev-runtime', + 'react-router', ] }, server: { From 041ce2156da8baab12cea38da2732d114fc6ba44 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 13:11:11 +0530 Subject: [PATCH 10/38] feat: inject CSS and JS assets into SSR HTML head Use Nitro ?assets=client/ssr virtual imports to collect CSS/JS assets and inject as tags in HTML . Fixes unstyled flash on initial page load. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/routes/[...].ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/chronicle/src/server/routes/[...].ts b/packages/chronicle/src/server/routes/[...].ts index f0990dc..0c2b933 100644 --- a/packages/chronicle/src/server/routes/[...].ts +++ b/packages/chronicle/src/server/routes/[...].ts @@ -6,6 +6,10 @@ import { loadConfig } from '@/lib/config'; import { loadApiSpecs } from '@/lib/openapi'; import { buildPageTree, getPage, loadPageComponent } from '@/lib/source'; import { render } from '../entry-server'; +// @ts-expect-error virtual import from Nitro +import clientAssets from '../entry-client?assets=client'; +// @ts-expect-error virtual import from Nitro +import serverAssets from '../entry-server?assets=ssr'; export default defineHandler(async event => { const pathname = event.url.pathname; @@ -46,6 +50,14 @@ export default defineHandler(async event => { const appHtml = await render(event.url.href, { config, tree, page: pageData, apiSpecs }); + const assets = clientAssets.merge(serverAssets); + const cssLinks = assets.css.map((attr: { href: string }) => + `` + ).join('\n '); + const jsPreloads = assets.js.map((attr: { href: string }) => + `` + ).join('\n '); + const safeJson = JSON.stringify(embeddedData).replace(/window.__PAGE_DATA__ = ${safeJson}`; @@ -54,11 +66,13 @@ export default defineHandler(async event => { + ${cssLinks} + ${jsPreloads} ${dataScript}
${appHtml}
- + `; From a9005618d5a7b11bd7bd539697003db404c0ba0e Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 13:25:56 +0530 Subject: [PATCH 11/38] chore: add .output to gitignore Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/chronicle/.gitignore b/packages/chronicle/.gitignore index 38d9526..db04592 100644 --- a/packages/chronicle/.gitignore +++ b/packages/chronicle/.gitignore @@ -2,3 +2,4 @@ node_modules dist .source .content +.output From 251f5d1d1eb3ea33335dc8974f189b9aa89f2747 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 15:09:52 +0530 Subject: [PATCH 12/38] feat: fix production build and entry-server SSR pattern - build.ts: use createBuilder() + buildApp() for multi-environment build (client, SSR, Nitro all build correctly) - entry-server.tsx: rewrite as Nitro server entry with export default { fetch } using renderToReadableStream, asset injection via ?assets imports - source.ts: use @content/ alias in import() so Vite bundles MDX at build time (removes @vite-ignore, fixes ERR_UNKNOWN_FILE_EXTENSION in production) - source.ts: check file exists before import to prevent crashes - Remove index.html template (entry-server renders full HTML) - Remove catch-all route (entry-server handles SSR) - Remove serverDir from Nitro config (entry-server pattern) Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/cli/commands/build.ts | 5 +- packages/chronicle/src/lib/source.ts | 11 +- .../chronicle/src/server/entry-server.tsx | 134 ++++++++++++------ packages/chronicle/src/server/index.html | 12 -- packages/chronicle/src/server/routes/[...].ts | 81 ----------- packages/chronicle/src/server/vite-config.ts | 1 - 6 files changed, 102 insertions(+), 142 deletions(-) delete mode 100644 packages/chronicle/src/server/index.html delete mode 100644 packages/chronicle/src/server/routes/[...].ts diff --git a/packages/chronicle/src/cli/commands/build.ts b/packages/chronicle/src/cli/commands/build.ts index 34e6984..c920813 100644 --- a/packages/chronicle/src/cli/commands/build.ts +++ b/packages/chronicle/src/cli/commands/build.ts @@ -17,7 +17,7 @@ export const buildCommand = new Command('build') console.log(chalk.cyan('Building for production...')); - const { build } = await import('vite'); + const { createBuilder } = await import('vite'); const { createViteConfig } = await import('@/server/vite-config'); const config = await createViteConfig({ @@ -27,7 +27,8 @@ export const buildCommand = new Command('build') preset: options.preset }); - await build(config); + const builder = await createBuilder({ ...config, builder: {} }); + await builder.buildApp(); console.log(chalk.green('Build complete')); }); diff --git a/packages/chronicle/src/lib/source.ts b/packages/chronicle/src/lib/source.ts index c032c2f..6d7c811 100644 --- a/packages/chronicle/src/lib/source.ts +++ b/packages/chronicle/src/lib/source.ts @@ -117,10 +117,17 @@ export async function loadPageComponent( page: SourcePage ): Promise { if (!page.filePath) return null; + try { + await fs.access(page.filePath); + } catch { + return null; + } const contentDir = getContentDir(); const relativePath = path.relative(contentDir, page.filePath); - const symlinkPath = path.join(__CHRONICLE_PACKAGE_ROOT__, '.content', relativePath); - const mod = await import(/* @vite-ignore */ symlinkPath); + const withoutExt = relativePath.replace(/\.(mdx|md)$/, ''); + const mod = relativePath.endsWith('.md') + ? await import(`@content/${withoutExt}.md`) + : await import(`@content/${withoutExt}.mdx`); return mod.default; } diff --git a/packages/chronicle/src/server/entry-server.tsx b/packages/chronicle/src/server/entry-server.tsx index c0e555c..8c36b56 100644 --- a/packages/chronicle/src/server/entry-server.tsx +++ b/packages/chronicle/src/server/entry-server.tsx @@ -1,49 +1,95 @@ -import { PassThrough } from 'node:stream'; -import type { ReactNode } from 'react'; -import { renderToPipeableStream } from 'react-dom/server'; +import '@raystack/apsara/normalize.css'; +import '@raystack/apsara/style.css'; +import path from 'node:path'; +import React from 'react'; +import { renderToReadableStream } from 'react-dom/server.edge'; import { StaticRouter } from 'react-router'; -import type { ApiSpec } from '@/lib/openapi'; +import { mdxComponents } from '@/components/mdx'; +import { loadConfig } from '@/lib/config'; +import { loadApiSpecs } from '@/lib/openapi'; import { PageProvider } from '@/lib/page-context'; -import type { ChronicleConfig, Frontmatter, PageTree } from '@/types'; +import { buildPageTree, getPage, loadPageComponent } from '@/lib/source'; import { App } from './App'; -export interface SSRData { - config: ChronicleConfig; - tree: PageTree; - page: { - slug: string[]; - frontmatter: Frontmatter; - content: ReactNode; - } | null; - apiSpecs: ApiSpec[]; -} - -export function render(url: string, data: SSRData): Promise { - const pathname = new URL(url, 'http://localhost').pathname; - - return new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - const { pipe } = renderToPipeableStream( - - - - - , - { - onAllReady() { - const passthrough = new PassThrough(); - passthrough.on('data', (chunk: Buffer) => chunks.push(chunk)); - passthrough.on('end', () => resolve(Buffer.concat(chunks).toString())); - passthrough.on('error', reject); - pipe(passthrough); - }, - onError: reject, - } +// @ts-expect-error virtual import from Nitro +import clientAssets from './entry-client?assets=client'; +// @ts-expect-error virtual import from Nitro +import serverAssets from './entry-server?assets=ssr'; + +export default { + async fetch(req: Request) { + const url = new URL(req.url); + const pathname = url.pathname; + const slug = pathname === '/' ? [] : pathname.slice(1).split('/').filter(Boolean); + + const config = loadConfig(); + const apiSpecs = config.api?.length + ? await loadApiSpecs(config.api).catch(() => []) + : []; + + const [tree, sourcePage] = await Promise.all([ + buildPageTree(), + getPage(slug), + ]); + + const pageData = sourcePage + ? { + slug, + frontmatter: sourcePage.frontmatter, + content: await loadPageComponent(sourcePage).then(component => + component ? React.createElement(component, { components: mdxComponents }) : null + ), + } + : null; + + const relativePath = sourcePage + ? path.relative(__CHRONICLE_CONTENT_DIR__, sourcePage.filePath) + : null; + + const embeddedData = { + config, + tree, + slug, + frontmatter: pageData?.frontmatter ?? null, + relativePath, + }; + const safeJson = JSON.stringify(embeddedData).replace(/ + + + + {assets.css.map((attr: { href: string }) => ( + + ))} + {assets.js.map((attr: { href: string }) => ( + + ))} + - - diff --git a/packages/chronicle/src/server/routes/[...].ts b/packages/chronicle/src/server/routes/[...].ts deleted file mode 100644 index 0c2b933..0000000 --- a/packages/chronicle/src/server/routes/[...].ts +++ /dev/null @@ -1,81 +0,0 @@ -import path from 'node:path'; -import { defineHandler } from 'nitro'; -import React from 'react'; -import { mdxComponents } from '@/components/mdx'; -import { loadConfig } from '@/lib/config'; -import { loadApiSpecs } from '@/lib/openapi'; -import { buildPageTree, getPage, loadPageComponent } from '@/lib/source'; -import { render } from '../entry-server'; -// @ts-expect-error virtual import from Nitro -import clientAssets from '../entry-client?assets=client'; -// @ts-expect-error virtual import from Nitro -import serverAssets from '../entry-server?assets=ssr'; - -export default defineHandler(async event => { - const pathname = event.url.pathname; - const slug = - pathname === '/' ? [] : pathname.slice(1).split('/').filter(Boolean); - - const config = loadConfig(); - const apiSpecs = config.api?.length - ? await loadApiSpecs(config.api).catch(() => []) - : []; - - const [tree, sourcePage] = await Promise.all([ - buildPageTree(), - getPage(slug) - ]); - - let pageData = null; - const embeddedData: Record = { - config, - tree, - slug, - frontmatter: null, - relativePath: null - }; - - if (sourcePage) { - const component = await loadPageComponent(sourcePage); - pageData = { - slug, - frontmatter: sourcePage.frontmatter, - content: component - ? React.createElement(component, { components: mdxComponents }) - : null - }; - embeddedData.frontmatter = sourcePage.frontmatter; - embeddedData.relativePath = path.relative(__CHRONICLE_CONTENT_DIR__, sourcePage.filePath); - } - - const appHtml = await render(event.url.href, { config, tree, page: pageData, apiSpecs }); - - const assets = clientAssets.merge(serverAssets); - const cssLinks = assets.css.map((attr: { href: string }) => - `` - ).join('\n '); - const jsPreloads = assets.js.map((attr: { href: string }) => - `` - ).join('\n '); - - const safeJson = JSON.stringify(embeddedData).replace(/window.__PAGE_DATA__ = ${safeJson}`; - - const html = ` - - - - - ${cssLinks} - ${jsPreloads} - ${dataScript} - - -
${appHtml}
- - -`; - - event.res.headers.set('Content-Type', 'text/html'); - return html; -}); diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts index c35bb9b..5875384 100644 --- a/packages/chronicle/src/server/vite-config.ts +++ b/packages/chronicle/src/server/vite-config.ts @@ -23,7 +23,6 @@ export async function createViteConfig( nitro({ serverDir: path.resolve(packageRoot, 'src/server'), ...(preset && { preset }), - noExternals: true, }), mdx({}, { index: false }), react() From ac60f9fa6053ec9e5317380b43ca1543919c1886 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Thu, 12 Mar 2026 13:46:14 +0530 Subject: [PATCH 13/38] ci: add canary release workflow for PR builds Publishes canary npm release on every PR commit with version format 0.1.0-canary. using --tag canary. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/canary.yml | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/canary.yml diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml new file mode 100644 index 0000000..22caf16 --- /dev/null +++ b/.github/workflows/canary.yml @@ -0,0 +1,40 @@ +name: canary + +on: + pull_request: + types: [opened, synchronize] + +jobs: + canary-release: + name: Publish canary to npm + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + working-directory: ./packages/chronicle + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + working-directory: . + + - name: Build CLI + run: bun build-cli.ts + + - name: Set canary version + run: | + SHORT_SHA=$(echo "${{ github.event.pull_request.head.sha }}" | cut -c1-7) + VERSION=$(jq -r .version package.json)-canary.${SHORT_SHA} + jq --arg v "$VERSION" '.version = $v' package.json > package.tmp.json + mv package.tmp.json package.json + echo "Published version: $VERSION" + + - name: Publish + run: bun publish --tag canary --access public + env: + NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} From 51087f9eb9cddf243daad3713215b4cff1a728ec Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 15:42:36 +0530 Subject: [PATCH 14/38] fix: resolve @content alias in Nitro dev SSR worker - Add @content alias to Nitro config (mirrors Vite resolve.alias) - Use @content/ alias in loadPageComponent import (works in both dev via Nitro alias and build via Vite alias) - Remove @vite-ignore from dynamic imports - Add fs.access check before import to prevent crashes Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/vite-config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts index 5875384..fdba16e 100644 --- a/packages/chronicle/src/server/vite-config.ts +++ b/packages/chronicle/src/server/vite-config.ts @@ -23,6 +23,9 @@ export async function createViteConfig( nitro({ serverDir: path.resolve(packageRoot, 'src/server'), ...(preset && { preset }), + alias: { + '@content': path.resolve(packageRoot, '.content'), + }, }), mdx({}, { index: false }), react() From 6511afe84e56e962a7bc8a73088251f41c626b28 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 15:51:16 +0530 Subject: [PATCH 15/38] fix: use resolveId plugin for @content alias across all Vite environments resolve.alias doesn't propagate to Nitro's SSR worker fetchModule. A custom Vite plugin with resolveId hook runs in all environments. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/vite-config.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts index fdba16e..53f2870 100644 --- a/packages/chronicle/src/server/vite-config.ts +++ b/packages/chronicle/src/server/vite-config.ts @@ -28,7 +28,15 @@ export async function createViteConfig( }, }), mdx({}, { index: false }), - react() + react(), + { + name: 'chronicle:content-alias', + resolveId(id) { + if (id.startsWith('@content/')) { + return path.resolve(packageRoot, '.content', id.slice('@content/'.length)); + } + }, + } ], resolve: { alias: { From 49fe67cf0b165ea1561450ed9c36f5cc3761245e Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 16:00:40 +0530 Subject: [PATCH 16/38] fix: add preserveSymlinks to prevent symlink resolution outside Vite root Node resolves symlinks to real paths, which may be outside the Vite root. preserveSymlinks keeps the .content/ symlink path as-is. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/vite-config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts index 53f2870..28b0033 100644 --- a/packages/chronicle/src/server/vite-config.ts +++ b/packages/chronicle/src/server/vite-config.ts @@ -44,6 +44,7 @@ export async function createViteConfig( '@content': path.resolve(packageRoot, '.content'), }, conditions: ['module-sync', 'import', 'node'], + preserveSymlinks: true, dedupe: [ 'react', 'react-dom', From 0efaef0fd62e2025bc3752b82f65b52111937e68 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 16:09:34 +0530 Subject: [PATCH 17/38] fix: use relative ../../.content/ path for MDX imports Remove @content alias, resolveId plugin, and preserveSymlinks. Use relative path from source file to .content/ symlink instead. Vite statically analyzes the ../../.content/ prefix to glob and bundle all MDX files at build time. Works in both dev and production. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/lib/page-context.tsx | 5 ++++- packages/chronicle/src/lib/source.ts | 4 ++-- packages/chronicle/src/server/entry-client.tsx | 5 ++++- packages/chronicle/src/server/vite-config.ts | 15 +-------------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/chronicle/src/lib/page-context.tsx b/packages/chronicle/src/lib/page-context.tsx index 8f159d1..5b5e98b 100644 --- a/packages/chronicle/src/lib/page-context.tsx +++ b/packages/chronicle/src/lib/page-context.tsx @@ -48,7 +48,10 @@ interface PageProviderProps { } async function loadMdxComponent(relativePath: string): Promise { - const mod = await import(/* @vite-ignore */ `/.content/${relativePath}`); + const withoutExt = relativePath.replace(/\.(mdx|md)$/, ''); + const mod = relativePath.endsWith('.md') + ? await import(`../../.content/${withoutExt}.md`) + : await import(`../../.content/${withoutExt}.mdx`); return mod.default ? React.createElement(mod.default, { components: mdxComponents }) : null; diff --git a/packages/chronicle/src/lib/source.ts b/packages/chronicle/src/lib/source.ts index 6d7c811..c01c8d2 100644 --- a/packages/chronicle/src/lib/source.ts +++ b/packages/chronicle/src/lib/source.ts @@ -126,8 +126,8 @@ export async function loadPageComponent( const relativePath = path.relative(contentDir, page.filePath); const withoutExt = relativePath.replace(/\.(mdx|md)$/, ''); const mod = relativePath.endsWith('.md') - ? await import(`@content/${withoutExt}.md`) - : await import(`@content/${withoutExt}.mdx`); + ? await import(`../../.content/${withoutExt}.md`) + : await import(`../../.content/${withoutExt}.mdx`); return mod.default; } diff --git a/packages/chronicle/src/server/entry-client.tsx b/packages/chronicle/src/server/entry-client.tsx index 42e281b..d8c0c9a 100644 --- a/packages/chronicle/src/server/entry-client.tsx +++ b/packages/chronicle/src/server/entry-client.tsx @@ -60,7 +60,10 @@ async function hydrate() { async function loadPage( embedded: EmbeddedData ): Promise<{ slug: string[]; frontmatter: Frontmatter; content: ReactNode }> { - const mod = await import(/* @vite-ignore */ `/.content/${embedded.relativePath}`); + const withoutExt = embedded.relativePath.replace(/\.(mdx|md)$/, ''); + const mod = embedded.relativePath.endsWith('.md') + ? await import(`../../.content/${withoutExt}.md`) + : await import(`../../.content/${withoutExt}.mdx`); const content = mod.default ? React.createElement(mod.default, { components: mdxComponents }) : null; diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts index 28b0033..7f5d071 100644 --- a/packages/chronicle/src/server/vite-config.ts +++ b/packages/chronicle/src/server/vite-config.ts @@ -23,28 +23,15 @@ export async function createViteConfig( nitro({ serverDir: path.resolve(packageRoot, 'src/server'), ...(preset && { preset }), - alias: { - '@content': path.resolve(packageRoot, '.content'), - }, }), mdx({}, { index: false }), - react(), - { - name: 'chronicle:content-alias', - resolveId(id) { - if (id.startsWith('@content/')) { - return path.resolve(packageRoot, '.content', id.slice('@content/'.length)); - } - }, - } + react() ], resolve: { alias: { '@': path.resolve(packageRoot, 'src'), - '@content': path.resolve(packageRoot, '.content'), }, conditions: ['module-sync', 'import', 'node'], - preserveSymlinks: true, dedupe: [ 'react', 'react-dom', From eeb41a2521d22915574bd0cc5134fbd2edb2fbb1 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 16:19:56 +0530 Subject: [PATCH 18/38] fix: add remark plugins for callouts and mermaid diagrams Configure fumadocs-mdx/vite with remark-directive, remarkDirectiveAdmonition, remarkMdxMermaid, and remarkUnusedDirectives. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/vite-config.ts | 30 +++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts index 7f5d071..dd7b97a 100644 --- a/packages/chronicle/src/server/vite-config.ts +++ b/packages/chronicle/src/server/vite-config.ts @@ -1,8 +1,11 @@ import react from '@vitejs/plugin-react'; +import { remarkDirectiveAdmonition, remarkMdxMermaid } from 'fumadocs-core/mdx-plugins'; import mdx from 'fumadocs-mdx/vite'; import { nitro } from 'nitro/vite'; import path from 'node:path'; +import remarkDirective from 'remark-directive'; import { type InlineConfig } from 'vite'; +import remarkUnusedDirectives from '../lib/remark-unused-directives'; export interface ViteConfigOptions { packageRoot: string; @@ -24,7 +27,32 @@ export async function createViteConfig( serverDir: path.resolve(packageRoot, 'src/server'), ...(preset && { preset }), }), - mdx({}, { index: false }), + mdx({ + mdxOptions: { + remarkPlugins: [ + remarkDirective, + [remarkDirectiveAdmonition, { + tags: { + CalloutContainer: 'Callout', + CalloutTitle: 'CalloutTitle', + CalloutDescription: 'CalloutDescription', + }, + types: { + note: 'accent', + tip: 'accent', + info: 'accent', + warn: 'attention', + warning: 'attention', + danger: 'alert', + caution: 'alert', + success: 'success', + }, + }], + remarkUnusedDirectives, + remarkMdxMermaid, + ], + }, + }, { index: false }), react() ], resolve: { From 9b2413875dc2d8af3c195030e096c2c73d98b682 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 16:27:52 +0530 Subject: [PATCH 19/38] feat: add ReactRouterProvider from fumadocs-core, fix remark plugin config - Wrap app with ReactRouterProvider in both SSR and client entry - Fix fumadocs-mdx/vite config: pass mdxOptions via defineConfig as default export - Enables fumadocs-core Link, usePathname, useRouter, search integration Co-Authored-By: Claude Opus 4.6 (1M context) --- .../chronicle/src/server/entry-client.tsx | 19 ++++--- .../chronicle/src/server/entry-server.tsx | 19 ++++--- packages/chronicle/src/server/vite-config.ts | 51 ++++++++++--------- 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/packages/chronicle/src/server/entry-client.tsx b/packages/chronicle/src/server/entry-client.tsx index d8c0c9a..c54b79e 100644 --- a/packages/chronicle/src/server/entry-client.tsx +++ b/packages/chronicle/src/server/entry-client.tsx @@ -2,6 +2,7 @@ import '@vitejs/plugin-react/preamble'; import React from 'react'; import { hydrateRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router'; +import { ReactRouterProvider } from 'fumadocs-core/framework/react-router'; import { mdxComponents } from '@/components/mdx'; import { PageProvider } from '@/lib/page-context'; import type { ChronicleConfig, Frontmatter, PageTree } from '@/types'; @@ -42,14 +43,16 @@ async function hydrate() { hydrateRoot( document.getElementById('root') as HTMLElement, - - - + + + + + ); } catch (err) { diff --git a/packages/chronicle/src/server/entry-server.tsx b/packages/chronicle/src/server/entry-server.tsx index 8c36b56..668b8c2 100644 --- a/packages/chronicle/src/server/entry-server.tsx +++ b/packages/chronicle/src/server/entry-server.tsx @@ -4,6 +4,7 @@ import path from 'node:path'; import React from 'react'; import { renderToReadableStream } from 'react-dom/server.edge'; import { StaticRouter } from 'react-router'; +import { ReactRouterProvider } from 'fumadocs-core/framework/react-router'; import { mdxComponents } from '@/components/mdx'; import { loadConfig } from '@/lib/config'; import { loadApiSpecs } from '@/lib/openapi'; @@ -74,14 +75,16 @@ export default {
- - - + + + + +
diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts index dd7b97a..9c01256 100644 --- a/packages/chronicle/src/server/vite-config.ts +++ b/packages/chronicle/src/server/vite-config.ts @@ -1,5 +1,6 @@ import react from '@vitejs/plugin-react'; import { remarkDirectiveAdmonition, remarkMdxMermaid } from 'fumadocs-core/mdx-plugins'; +import { defineConfig as defineFumadocsConfig } from 'fumadocs-mdx/config'; import mdx from 'fumadocs-mdx/vite'; import { nitro } from 'nitro/vite'; import path from 'node:path'; @@ -28,30 +29,32 @@ export async function createViteConfig( ...(preset && { preset }), }), mdx({ - mdxOptions: { - remarkPlugins: [ - remarkDirective, - [remarkDirectiveAdmonition, { - tags: { - CalloutContainer: 'Callout', - CalloutTitle: 'CalloutTitle', - CalloutDescription: 'CalloutDescription', - }, - types: { - note: 'accent', - tip: 'accent', - info: 'accent', - warn: 'attention', - warning: 'attention', - danger: 'alert', - caution: 'alert', - success: 'success', - }, - }], - remarkUnusedDirectives, - remarkMdxMermaid, - ], - }, + default: defineFumadocsConfig({ + mdxOptions: { + remarkPlugins: [ + remarkDirective, + [remarkDirectiveAdmonition, { + tags: { + CalloutContainer: 'Callout', + CalloutTitle: 'CalloutTitle', + CalloutDescription: 'CalloutDescription', + }, + types: { + note: 'accent', + tip: 'accent', + info: 'accent', + warn: 'attention', + warning: 'attention', + danger: 'alert', + caution: 'alert', + success: 'success', + }, + }], + remarkUnusedDirectives, + remarkMdxMermaid, + ], + }, + }), }, { index: false }), react() ], From ab597f951c9b7c3efdab03dc40eb30ed8fe641d5 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 17:07:17 +0530 Subject: [PATCH 20/38] refactor: replace custom PageTree/TocItem types with fumadocs-core re-exports - Re-export Root, Node, Item, Folder, Separator from fumadocs-core/page-tree - Re-export TOCItemType, TableOfContents from fumadocs-core/toc - Update ThemeLayoutProps/ThemePageProps to use Root instead of PageTree - Keep custom Frontmatter and Page types (Page.toc now uses TableOfContents) Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/types/content.ts | 26 +++++-------------------- packages/chronicle/src/types/theme.ts | 7 ++++--- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/packages/chronicle/src/types/content.ts b/packages/chronicle/src/types/content.ts index a9147e7..0ca7ecf 100644 --- a/packages/chronicle/src/types/content.ts +++ b/packages/chronicle/src/types/content.ts @@ -1,4 +1,8 @@ import type { ReactNode } from 'react' +import type { TableOfContents } from 'fumadocs-core/toc' + +export type { Root, Node, Item, Folder, Separator } from 'fumadocs-core/page-tree' +export type { TOCItemType, TableOfContents } from 'fumadocs-core/toc' export interface Frontmatter { title: string @@ -12,25 +16,5 @@ export interface Page { slug: string[] frontmatter: Frontmatter content: ReactNode - toc: TocItem[] -} - -export interface TocItem { - title: string - url: string - depth: number -} - -export interface PageTreeItem { - type: 'page' | 'folder' | 'separator' - name: string - url?: string - order?: number - icon?: string - children?: PageTreeItem[] -} - -export interface PageTree { - name: string - children: PageTreeItem[] + toc: TableOfContents } diff --git a/packages/chronicle/src/types/theme.ts b/packages/chronicle/src/types/theme.ts index 206ac7d..c948bd1 100644 --- a/packages/chronicle/src/types/theme.ts +++ b/packages/chronicle/src/types/theme.ts @@ -1,18 +1,19 @@ import type { ReactNode } from 'react' +import type { Root } from 'fumadocs-core/page-tree' import type { ChronicleConfig } from './config' -import type { Page, PageTree } from './content' +import type { Page } from './content' export interface ThemeLayoutProps { children: ReactNode config: ChronicleConfig - tree: PageTree + tree: Root classNames?: { layout?: string; body?: string; sidebar?: string; content?: string } } export interface ThemePageProps { page: Page config: ChronicleConfig - tree: PageTree + tree: Root } export interface Theme { From 857feab6831346691043be6f75eb3a2d083cf358 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 17:16:06 +0530 Subject: [PATCH 21/38] refactor: use fumadocs loader API directly in source.ts and api-routes.ts - Remove SourcePage interface and buildPageTree(), use loader().pageTree with order-based sort - getPages()/getPage() now return fumadocs Page types directly - loadPageComponent() takes relativePath string instead of SourcePage - Rename _absolutePath to _relativePath in scanFiles (stored relative path was misnamed) - buildApiPageTree() returns fumadocs Root instead of custom PageTree - Update entry-server, entry-client, page-context, sitemap, api route consumers Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/lib/api-routes.ts | 14 +- packages/chronicle/src/lib/page-context.tsx | 10 +- packages/chronicle/src/lib/source.ts | 149 +++++++----------- .../src/server/api/page/[...slug].ts | 16 +- .../chronicle/src/server/entry-client.tsx | 6 +- .../chronicle/src/server/entry-server.tsx | 32 ++-- .../src/server/routes/sitemap.xml.ts | 5 +- 7 files changed, 102 insertions(+), 130 deletions(-) diff --git a/packages/chronicle/src/lib/api-routes.ts b/packages/chronicle/src/lib/api-routes.ts index 828d5e2..ce30fc0 100644 --- a/packages/chronicle/src/lib/api-routes.ts +++ b/packages/chronicle/src/lib/api-routes.ts @@ -1,6 +1,6 @@ import type { OpenAPIV3 } from 'openapi-types' import slugify from 'slugify' -import type { PageTree, PageTreeItem } from '@/types/content' +import type { Root, Node, Item, Folder } from 'fumadocs-core/page-tree' import type { ApiSpec } from './openapi' export function getSpecSlug(spec: ApiSpec): string { @@ -56,16 +56,15 @@ export function findApiOperation(specs: ApiSpec[], slug: string[]): ApiRouteMatc return null } -export function buildApiPageTree(specs: ApiSpec[]): PageTree { - const children: PageTreeItem[] = [] +export function buildApiPageTree(specs: ApiSpec[]): Root { + const children: Node[] = [] for (const spec of specs) { const specSlug = getSpecSlug(spec) const paths = spec.document.paths ?? {} const tags = spec.document.tags ?? [] - // Group operations by tag (case-insensitive to avoid duplicates) - const opsByTag = new Map() + const opsByTag = new Map() const tagDisplayName = new Map() for (const [, pathItem] of Object.entries(paths)) { @@ -90,7 +89,6 @@ export function buildApiPageTree(specs: ApiSpec[]): PageTree { } } - // Use doc.tags display names where available for (const t of tags) { const key = t.name.toLowerCase() if (opsByTag.has(key)) { @@ -98,7 +96,7 @@ export function buildApiPageTree(specs: ApiSpec[]): PageTree { } } - const tagFolders: PageTreeItem[] = Array.from(opsByTag.entries()).map(([key, ops]) => ({ + const tagFolders: Folder[] = Array.from(opsByTag.entries()).map(([key, ops]) => ({ type: 'folder' as const, name: tagDisplayName.get(key) ?? key, icon: 'rectangle-stack', @@ -110,7 +108,7 @@ export function buildApiPageTree(specs: ApiSpec[]): PageTree { type: 'folder', name: spec.name, children: tagFolders, - }) + } as Folder) } else { children.push(...tagFolders) } diff --git a/packages/chronicle/src/lib/page-context.tsx b/packages/chronicle/src/lib/page-context.tsx index 5b5e98b..5240635 100644 --- a/packages/chronicle/src/lib/page-context.tsx +++ b/packages/chronicle/src/lib/page-context.tsx @@ -8,7 +8,7 @@ import React, { import { useLocation } from 'react-router'; import { mdxComponents } from '@/components/mdx'; import type { ApiSpec } from '@/lib/openapi'; -import type { ChronicleConfig, Frontmatter, PageTree } from '@/types'; +import type { ChronicleConfig, Frontmatter, Root } from '@/types'; interface PageData { slug: string[]; @@ -18,7 +18,7 @@ interface PageData { interface PageContextValue { config: ChronicleConfig; - tree: PageTree; + tree: Root; page: PageData | null; apiSpecs: ApiSpec[]; } @@ -31,7 +31,7 @@ export function usePageContext(): PageContextValue { console.error('usePageContext: no context found!'); return { config: { title: 'Documentation' }, - tree: { name: 'root', children: [] }, + tree: { name: 'root', children: [] } as Root, page: null, apiSpecs: [] }; @@ -41,7 +41,7 @@ export function usePageContext(): PageContextValue { interface PageProviderProps { initialConfig: ChronicleConfig; - initialTree: PageTree; + initialTree: Root; initialPage: PageData | null; initialApiSpecs: ApiSpec[]; children: ReactNode; @@ -65,7 +65,7 @@ export function PageProvider({ children }: PageProviderProps) { const { pathname } = useLocation(); - const [tree] = useState(initialTree); + const [tree] = useState(initialTree); const [page, setPage] = useState(initialPage); const [apiSpecs, setApiSpecs] = useState(initialApiSpecs); const [currentPath, setCurrentPath] = useState(pathname); diff --git a/packages/chronicle/src/lib/source.ts b/packages/chronicle/src/lib/source.ts index c01c8d2..112c185 100644 --- a/packages/chronicle/src/lib/source.ts +++ b/packages/chronicle/src/lib/source.ts @@ -1,16 +1,9 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import { loader } from 'fumadocs-core/source'; +import type { Root, Node, Folder } from 'fumadocs-core/page-tree'; import matter from 'gray-matter'; import type { MDXContent } from 'mdx/types'; -import type { Frontmatter, PageTree, PageTreeItem } from '@/types'; - -export interface SourcePage { - url: string; - slugs: string[]; - filePath: string; - frontmatter: Frontmatter; -} function getContentDir(): string { return __CHRONICLE_CONTENT_DIR__ || path.join(process.cwd(), 'content'); @@ -43,7 +36,7 @@ async function scanFiles(contentDir: string) { files.push({ type: 'page', path: relativePath, - data: { ...data, _absolutePath: fullPath } + data: { ...data, _relativePath: relativePath } }); } else if (entry.name === 'meta.json' || entry.name === 'meta.yaml') { const raw = await fs.readFile(fullPath, 'utf-8'); @@ -63,7 +56,6 @@ async function scanFiles(contentDir: string) { } let cachedSource: ReturnType | null = null; -let cachedPages: SourcePage[] | null = null; async function getSource() { if (cachedSource) return cachedSource; @@ -76,111 +68,78 @@ async function getSource() { return cachedSource; } +export { getSource as source }; + export function invalidate() { cachedSource = null; - cachedPages = null; } -export async function getPages(): Promise { - if (cachedPages) return cachedPages; +function getOrder(node: Node, orderMap: Map): number | undefined { + if (node.type === 'page') return orderMap.get(node.url); + if (node.type === 'folder') { + if (node.index) return orderMap.get(node.index.url); + for (const child of node.children) { + const o = getOrder(child, orderMap); + if (o !== undefined) return o; + } + } + return undefined; +} + +function sortNodes(nodes: Node[], orderMap: Map): Node[] { + return [...nodes] + .map(n => + n.type === 'folder' + ? ({ ...n, children: sortNodes(n.children, orderMap) } as Folder) + : n + ) + .sort( + (a, b) => + (getOrder(a, orderMap) ?? Number.MAX_SAFE_INTEGER) - + (getOrder(b, orderMap) ?? Number.MAX_SAFE_INTEGER) + ); +} + +function sortTreeByOrder(tree: Root, pages: { url: string; data: unknown }[]): Root { + const orderMap = new Map(); + for (const page of pages) { + const d = page.data as Record; + const order = d.order as number | undefined; + if (order !== undefined) orderMap.set(page.url, order); + if (page.url === '/') orderMap.set('/', order ?? 0); + } + return { ...tree, children: sortNodes(tree.children, orderMap) }; +} +export async function getPageTree(): Promise { const s = await getSource(); - cachedPages = s.getPages().map(page => { - const data = page.data as Record; - return { - url: page.url, - slugs: page.slugs, - filePath: (data._absolutePath as string) ?? '', - frontmatter: { - title: - (data.title as string) ?? - page.slugs[page.slugs.length - 1] ?? - 'Untitled', - description: data.description as string | undefined, - order: data.order as number | undefined, - icon: data.icon as string | undefined, - lastModified: data.lastModified as string | undefined - } - }; - }); + return sortTreeByOrder(s.pageTree as Root, s.getPages()); +} - return cachedPages; +export async function getPages() { + const s = await getSource(); + return s.getPages(); } -export async function getPage(slug?: string[]): Promise { - const pages = await getPages(); - const targetUrl = !slug || slug.length === 0 ? '/' : `/${slug.join('/')}`; - return pages.find(p => p.url === targetUrl) ?? null; +export async function getPage(slugs?: string[]) { + const s = await getSource(); + return s.getPage(slugs); } export async function loadPageComponent( - page: SourcePage + relativePath: string ): Promise { - if (!page.filePath) return null; + if (!relativePath) return null; + const contentDir = getContentDir(); + const fullPath = path.join(contentDir, relativePath); try { - await fs.access(page.filePath); + await fs.access(fullPath); } catch { return null; } - const contentDir = getContentDir(); - const relativePath = path.relative(contentDir, page.filePath); const withoutExt = relativePath.replace(/\.(mdx|md)$/, ''); const mod = relativePath.endsWith('.md') ? await import(`../../.content/${withoutExt}.md`) : await import(`../../.content/${withoutExt}.mdx`); return mod.default; } - -export async function buildPageTree(): Promise { - const s = await getSource(); - const pages = s.getPages(); - const folders = new Map(); - const rootPages: PageTreeItem[] = []; - - for (const page of pages) { - const data = page.data as Record; - const isIndex = page.url === '/'; - const item: PageTreeItem = { - type: 'page', - name: (data.title as string) ?? page.slugs.join('/') ?? 'Untitled', - url: page.url, - order: (data.order as number | undefined) ?? (isIndex ? 0 : undefined) - }; - - if (page.slugs.length > 1) { - const folder = page.slugs[0]; - if (!folders.has(folder)) { - folders.set(folder, []); - } - folders.get(folder)?.push(item); - } else { - rootPages.push(item); - } - } - - const sortByOrder = (items: PageTreeItem[]) => - items.sort( - (a, b) => - (a.order ?? Number.MAX_SAFE_INTEGER) - - (b.order ?? Number.MAX_SAFE_INTEGER) - ); - - const children: PageTreeItem[] = sortByOrder(rootPages); - - const folderItems: PageTreeItem[] = []; - for (const [folder, items] of folders) { - const sorted = sortByOrder(items); - const indexPage = items.find(item => item.url === `/${folder}`); - const folderOrder = indexPage?.order ?? sorted[0]?.order; - folderItems.push({ - type: 'folder', - name: `${folder.charAt(0).toUpperCase()}${folder.slice(1)}`, - order: folderOrder, - children: sorted - }); - } - - children.push(...sortByOrder(folderItems)); - - return { name: 'root', children }; -} diff --git a/packages/chronicle/src/server/api/page/[...slug].ts b/packages/chronicle/src/server/api/page/[...slug].ts index e867dff..9247899 100644 --- a/packages/chronicle/src/server/api/page/[...slug].ts +++ b/packages/chronicle/src/server/api/page/[...slug].ts @@ -1,4 +1,3 @@ -import path from 'node:path'; import { defineHandler, HTTPError } from 'nitro'; import { getPage } from '@/lib/source'; @@ -11,8 +10,17 @@ export default defineHandler(async event => { throw new HTTPError({ status: 404, message: 'Page not found' }); } - const contentDir = __CHRONICLE_CONTENT_DIR__; - const relativePath = path.relative(contentDir, page.filePath); + const data = page.data as Record; + const relativePath = (data._relativePath as string) ?? ''; - return { frontmatter: page.frontmatter, relativePath }; + return { + frontmatter: { + title: (data.title as string) ?? slug[slug.length - 1] ?? 'Untitled', + description: data.description as string | undefined, + order: data.order as number | undefined, + icon: data.icon as string | undefined, + lastModified: data.lastModified as string | undefined, + }, + relativePath, + }; }); diff --git a/packages/chronicle/src/server/entry-client.tsx b/packages/chronicle/src/server/entry-client.tsx index c54b79e..59c5326 100644 --- a/packages/chronicle/src/server/entry-client.tsx +++ b/packages/chronicle/src/server/entry-client.tsx @@ -5,14 +5,14 @@ import { BrowserRouter } from 'react-router'; import { ReactRouterProvider } from 'fumadocs-core/framework/react-router'; import { mdxComponents } from '@/components/mdx'; import { PageProvider } from '@/lib/page-context'; -import type { ChronicleConfig, Frontmatter, PageTree } from '@/types'; +import type { ChronicleConfig, Frontmatter, Root } from '@/types'; import type { ApiSpec } from '@/lib/openapi'; import type { ReactNode } from 'react'; import { App } from './App'; interface EmbeddedData { config: ChronicleConfig; - tree: PageTree; + tree: Root; slug: string[]; frontmatter: Frontmatter; relativePath: string; @@ -27,7 +27,7 @@ async function hydrate() { const config: ChronicleConfig = embedded?.config ?? { title: 'Documentation' }; - const tree: PageTree = embedded?.tree ?? { name: 'root', children: [] }; + const tree: Root = embedded?.tree ?? { name: 'root', children: [] }; const isApiPage = window.location.pathname.startsWith('/apis') && !!config.api?.length; const apiSpecs: ApiSpec[] = isApiPage diff --git a/packages/chronicle/src/server/entry-server.tsx b/packages/chronicle/src/server/entry-server.tsx index 668b8c2..c69dbfd 100644 --- a/packages/chronicle/src/server/entry-server.tsx +++ b/packages/chronicle/src/server/entry-server.tsx @@ -1,6 +1,5 @@ import '@raystack/apsara/normalize.css'; import '@raystack/apsara/style.css'; -import path from 'node:path'; import React from 'react'; import { renderToReadableStream } from 'react-dom/server.edge'; import { StaticRouter } from 'react-router'; @@ -9,7 +8,7 @@ import { mdxComponents } from '@/components/mdx'; import { loadConfig } from '@/lib/config'; import { loadApiSpecs } from '@/lib/openapi'; import { PageProvider } from '@/lib/page-context'; -import { buildPageTree, getPage, loadPageComponent } from '@/lib/source'; +import { getPageTree, getPage, loadPageComponent } from '@/lib/source'; import { App } from './App'; // @ts-expect-error virtual import from Nitro @@ -28,25 +27,32 @@ export default { ? await loadApiSpecs(config.api).catch(() => []) : []; - const [tree, sourcePage] = await Promise.all([ - buildPageTree(), + const [tree, page] = await Promise.all([ + getPageTree(), getPage(slug), ]); - const pageData = sourcePage + const data = page?.data as Record | undefined; + const relativePath = (data?._relativePath as string) ?? null; + + const pageData = page ? { slug, - frontmatter: sourcePage.frontmatter, - content: await loadPageComponent(sourcePage).then(component => - component ? React.createElement(component, { components: mdxComponents }) : null - ), + frontmatter: { + title: (data?.title as string) ?? slug[slug.length - 1] ?? 'Untitled', + description: data?.description as string | undefined, + order: data?.order as number | undefined, + icon: data?.icon as string | undefined, + lastModified: data?.lastModified as string | undefined, + }, + content: relativePath + ? await loadPageComponent(relativePath).then(component => + component ? React.createElement(component, { components: mdxComponents }) : null + ) + : null, } : null; - const relativePath = sourcePage - ? path.relative(__CHRONICLE_CONTENT_DIR__, sourcePage.filePath) - : null; - const embeddedData = { config, tree, diff --git a/packages/chronicle/src/server/routes/sitemap.xml.ts b/packages/chronicle/src/server/routes/sitemap.xml.ts index a961ce3..ea93437 100644 --- a/packages/chronicle/src/server/routes/sitemap.xml.ts +++ b/packages/chronicle/src/server/routes/sitemap.xml.ts @@ -16,8 +16,9 @@ export default defineHandler(async event => { const pages = await getPages(); const docPages = pages.map(page => { - const lastmod = page.frontmatter.lastModified - ? `${new Date(page.frontmatter.lastModified).toISOString()}` + const data = page.data as Record; + const lastmod = data.lastModified + ? `${new Date(data.lastModified as string).toISOString()}` : ''; return `${baseUrl}/${page.slugs.join('/')}${lastmod}`; }); From d2f19065fc934e8cdb92d0e0b70a79b8125592ca Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 23 Mar 2026 17:19:54 +0530 Subject: [PATCH 22/38] refactor: update components to use fumadocs-core types and utilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - breadcrumbs.tsx: use getBreadcrumbItems() from fumadocs-core/breadcrumb - Toc.tsx: use AnchorProvider + useActiveAnchor from fumadocs-core/toc - ReadingProgress.tsx: TocItem → TOCItemType - Layout.tsx: PageTreeItem → Node with type narrowing for Item/Folder - ChapterNav.tsx: PageTree/PageTreeItem → Root/Node from fumadocs-core/page-tree - paper/Page.tsx: use flattenTree + getBreadcrumbItems from fumadocs-core Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/ui/breadcrumbs.tsx | 50 +++-------------- .../chronicle/src/themes/default/Layout.tsx | 23 ++++---- packages/chronicle/src/themes/default/Toc.tsx | 44 +++++---------- .../chronicle/src/themes/paper/ChapterNav.tsx | 28 +++++----- packages/chronicle/src/themes/paper/Page.tsx | 56 +++++-------------- .../src/themes/paper/ReadingProgress.tsx | 4 +- 6 files changed, 66 insertions(+), 139 deletions(-) diff --git a/packages/chronicle/src/components/ui/breadcrumbs.tsx b/packages/chronicle/src/components/ui/breadcrumbs.tsx index 4eaff20..6babcd7 100644 --- a/packages/chronicle/src/components/ui/breadcrumbs.tsx +++ b/packages/chronicle/src/components/ui/breadcrumbs.tsx @@ -1,53 +1,19 @@ 'use client' import { Breadcrumb } from '@raystack/apsara' -import type { PageTree, PageTreeItem } from '@/types' +import { getBreadcrumbItems } from 'fumadocs-core/breadcrumb' +import type { Root } from 'fumadocs-core/page-tree' interface BreadcrumbsProps { slug: string[] - tree: PageTree -} - -function findInTree(items: PageTreeItem[], targetPath: string): PageTreeItem | undefined { - for (const item of items) { - const itemUrl = item.url || `/${item.name.toLowerCase().replace(/\s+/g, '-')}` - if (itemUrl === targetPath || itemUrl === `/${targetPath}`) { - return item - } - if (item.children) { - const found = findInTree(item.children, targetPath) - if (found) return found - } - } - return undefined -} - -function getFirstPageUrl(item: PageTreeItem): string | undefined { - if (item.type === 'page' && item.url) { - return item.url - } - if (item.children) { - for (const child of item.children) { - const url = getFirstPageUrl(child) - if (url) return url - } - } - return undefined + tree: Root } export function Breadcrumbs({ slug, tree }: BreadcrumbsProps) { - const items: { label: string; href: string }[] = [] + const url = slug.length === 0 ? '/' : `/${slug.join('/')}` + const items = getBreadcrumbItems(url, tree, { includePage: true }) - for (let i = 0; i < slug.length; i++) { - const currentPath = `/${slug.slice(0, i + 1).join('/')}` - const node = findInTree(tree.children, currentPath) - const href = node?.url || (node && getFirstPageUrl(node)) || currentPath - const label = node?.name ?? slug[i] - items.push({ - label: label.charAt(0).toUpperCase() + label.slice(1), - href, - }) - } + if (items.length === 0) return null return ( @@ -55,10 +21,10 @@ export function Breadcrumbs({ slug, tree }: BreadcrumbsProps) { const breadcrumbItem = ( - {item.label} + {item.name} ) if (index === 0) return [breadcrumbItem] diff --git a/packages/chronicle/src/themes/default/Layout.tsx b/packages/chronicle/src/themes/default/Layout.tsx index b24f885..fab54d1 100644 --- a/packages/chronicle/src/themes/default/Layout.tsx +++ b/packages/chronicle/src/themes/default/Layout.tsx @@ -14,7 +14,8 @@ import { MethodBadge } from '@/components/api/method-badge'; import { ClientThemeSwitcher } from '@/components/ui/client-theme-switcher'; import { Footer } from '@/components/ui/footer'; import { Search } from '@/components/ui/search'; -import type { PageTreeItem, ThemeLayoutProps } from '@/types'; +import type { Node } from 'fumadocs-core/page-tree'; +import type { ThemeLayoutProps } from '@/types'; import styles from './Layout.module.css'; const iconMap: Record = { @@ -96,9 +97,9 @@ export function Layout({ className={cx(styles.sidebar, classNames?.sidebar)} > - {tree.children.map(item => ( + {tree.children.map((item, i) => ( @@ -118,23 +119,24 @@ function SidebarNode({ item, pathname }: { - item: PageTreeItem; + item: Node; pathname: string; }) { if (item.type === 'separator') { return null; } - if (item.type === 'folder' && item.children) { + if (item.type === 'folder') { + const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon; return ( - {item.children.map(child => ( + {item.children.map((child, i) => ( @@ -145,13 +147,14 @@ function SidebarNode({ const isActive = pathname === item.url; const href = item.url ?? '#'; + const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon; const link = useMemo(() => , [href]); return ( {item.name} diff --git a/packages/chronicle/src/themes/default/Toc.tsx b/packages/chronicle/src/themes/default/Toc.tsx index c357ad5..455befc 100644 --- a/packages/chronicle/src/themes/default/Toc.tsx +++ b/packages/chronicle/src/themes/default/Toc.tsx @@ -1,46 +1,30 @@ 'use client'; import { Text } from '@raystack/apsara'; -import { useEffect, useState } from 'react'; -import type { TocItem } from '@/types'; +import { AnchorProvider, useActiveAnchor } from 'fumadocs-core/toc'; +import type { TableOfContents, TOCItemType } from 'fumadocs-core/toc'; import styles from './Toc.module.css'; interface TocProps { - items: TocItem[]; + items: TableOfContents; } export function Toc({ items }: TocProps) { - const [activeId, setActiveId] = useState(''); - - // Filter to only show h2 and h3 headings const filteredItems = items.filter( item => item.depth >= 2 && item.depth <= 3 ); - useEffect(() => { - const headingIds = filteredItems.map(item => item.url.replace('#', '')); - - const observer = new IntersectionObserver( - entries => { - entries.forEach(entry => { - if (entry.isIntersecting) { - setActiveId(entry.target.id); - } - }); - }, - // -80px top: offset for fixed header, -80% bottom: trigger when heading is in top 20% of viewport - { rootMargin: '-80px 0px -80% 0px' } - ); - - headingIds.forEach(id => { - const element = document.getElementById(id); - if (element) observer.observe(element); - }); + if (filteredItems.length === 0) return null; - return () => observer.disconnect(); - }, [filteredItems]); + return ( + + + + ); +} - if (filteredItems.length === 0) return null; +function TocContent({ items }: { items: TOCItemType[] }) { + const activeAnchor = useActiveAnchor(); return (