@raystack/chronicle 0.1.0-canary.6511afe → 0.1.0-canary.67113f8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -16,15 +16,48 @@ var __export = (target, all) => {
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
18
18
 
19
+ // src/lib/remark-unused-directives.ts
20
+ import { visit } from "unist-util-visit";
21
+ var remarkUnusedDirectives = () => {
22
+ return (tree) => {
23
+ visit(tree, ["textDirective"], (node) => {
24
+ const directive = node;
25
+ if (!directive.data) {
26
+ const hasAttributes = directive.attributes && Object.keys(directive.attributes).length > 0;
27
+ const hasChildren = directive.children && directive.children.length > 0;
28
+ if (!hasAttributes && !hasChildren) {
29
+ const name = directive.name;
30
+ if (!name)
31
+ return;
32
+ Object.keys(directive).forEach((key) => delete directive[key]);
33
+ directive.type = "text";
34
+ directive.value = `:${name}`;
35
+ }
36
+ }
37
+ });
38
+ };
39
+ }, remark_unused_directives_default;
40
+ var init_remark_unused_directives = __esm(() => {
41
+ remark_unused_directives_default = remarkUnusedDirectives;
42
+ });
43
+
19
44
  // src/server/vite-config.ts
20
45
  var exports_vite_config = {};
21
46
  __export(exports_vite_config, {
22
47
  createViteConfig: () => createViteConfig
23
48
  });
24
49
  import react from "@vitejs/plugin-react";
50
+ import { remarkDirectiveAdmonition, remarkMdxMermaid } from "fumadocs-core/mdx-plugins";
51
+ import { defineConfig as defineFumadocsConfig } from "fumadocs-mdx/config";
25
52
  import mdx from "fumadocs-mdx/vite";
26
53
  import { nitro } from "nitro/vite";
27
54
  import path4 from "node:path";
55
+ import remarkDirective from "remark-directive";
56
+ function resolveOutputDir(projectRoot, preset) {
57
+ if (preset === "vercel" || preset === "vercel-static")
58
+ return path4.resolve(projectRoot, ".vercel/output");
59
+ return path4.resolve(projectRoot, ".output");
60
+ }
28
61
  async function createViteConfig(options) {
29
62
  const { packageRoot, projectRoot, contentDir, preset } = options;
30
63
  return {
@@ -33,26 +66,41 @@ async function createViteConfig(options) {
33
66
  plugins: [
34
67
  nitro({
35
68
  serverDir: path4.resolve(packageRoot, "src/server"),
36
- ...preset && { preset },
37
- alias: {
38
- "@content": path4.resolve(packageRoot, ".content")
39
- }
69
+ ...preset && { preset }
40
70
  }),
41
- mdx({}, { index: false }),
42
- react(),
43
- {
44
- name: "chronicle:content-alias",
45
- resolveId(id) {
46
- if (id.startsWith("@content/")) {
47
- return path4.resolve(packageRoot, ".content", id.slice("@content/".length));
71
+ mdx({
72
+ default: defineFumadocsConfig({
73
+ mdxOptions: {
74
+ remarkPlugins: [
75
+ remarkDirective,
76
+ [remarkDirectiveAdmonition, {
77
+ tags: {
78
+ CalloutContainer: "Callout",
79
+ CalloutTitle: "CalloutTitle",
80
+ CalloutDescription: "CalloutDescription"
81
+ },
82
+ types: {
83
+ note: "accent",
84
+ tip: "accent",
85
+ info: "accent",
86
+ warn: "attention",
87
+ warning: "attention",
88
+ danger: "alert",
89
+ caution: "alert",
90
+ success: "success"
91
+ }
92
+ }],
93
+ remark_unused_directives_default,
94
+ remarkMdxMermaid
95
+ ]
48
96
  }
49
- }
50
- }
97
+ })
98
+ }, { index: false }),
99
+ react()
51
100
  ],
52
101
  resolve: {
53
102
  alias: {
54
- "@": path4.resolve(packageRoot, "src"),
55
- "@content": path4.resolve(packageRoot, ".content")
103
+ "@": path4.resolve(packageRoot, "src")
56
104
  },
57
105
  conditions: ["module-sync", "import", "node"],
58
106
  dedupe: [
@@ -70,8 +118,7 @@ async function createViteConfig(options) {
70
118
  },
71
119
  define: {
72
120
  __CHRONICLE_CONTENT_DIR__: JSON.stringify(contentDir),
73
- __CHRONICLE_PROJECT_ROOT__: JSON.stringify(projectRoot),
74
- __CHRONICLE_PACKAGE_ROOT__: JSON.stringify(packageRoot)
121
+ __CHRONICLE_PROJECT_ROOT__: JSON.stringify(projectRoot)
75
122
  },
76
123
  css: {
77
124
  modules: {
@@ -89,10 +136,17 @@ async function createViteConfig(options) {
89
136
  }
90
137
  }
91
138
  }
139
+ },
140
+ nitro: {
141
+ output: {
142
+ dir: resolveOutputDir(projectRoot, preset)
143
+ }
92
144
  }
93
145
  };
94
146
  }
95
- var init_vite_config = () => {};
147
+ var init_vite_config = __esm(() => {
148
+ init_remark_unused_directives();
149
+ });
96
150
 
97
151
  // src/cli/index.ts
98
152
  import { Command as Command6 } from "commander";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raystack/chronicle",
3
- "version": "0.1.0-canary.6511afe",
3
+ "version": "0.1.0-canary.67113f8",
4
4
  "description": "Config-driven documentation framework",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
- import type { ComponentProps } from 'react'
3
+ import { type ComponentProps, isValidElement, Children } from 'react'
4
+ import { Mermaid } from './mermaid'
4
5
  import styles from './code.module.css'
5
6
 
6
7
  type PreProps = ComponentProps<'pre'> & {
@@ -16,6 +17,14 @@ export function MdxCode({ children, className, ...props }: ComponentProps<'code'
16
17
  }
17
18
 
18
19
  export function MdxPre({ children, title, className, ...props }: PreProps) {
20
+ // Detect mermaid code blocks
21
+ if (isValidElement(children)) {
22
+ const childProps = children.props as { className?: string; children?: string }
23
+ if (childProps.className?.includes('language-mermaid') && typeof childProps.children === 'string') {
24
+ return <Mermaid chart={childProps.children} />
25
+ }
26
+ }
27
+
19
28
  return (
20
29
  <div className={styles.codeBlock}>
21
30
  {title && <div className={styles.codeHeader}>{title}</div>}
@@ -1,35 +1,10 @@
1
1
  .details {
2
- border: 1px solid var(--rs-color-border-base-primary);
3
- border-radius: var(--rs-radius-2);
4
2
  margin: var(--rs-space-5) 0;
5
3
  }
6
4
 
7
- .summary {
8
- padding: var(--rs-space-4) var(--rs-space-5);
9
- cursor: pointer;
5
+ .trigger {
10
6
  font-weight: 500;
11
7
  font-size: var(--rs-font-size-small);
12
- color: var(--rs-color-text-base-primary);
13
- background: var(--rs-color-background-base-secondary);
14
- list-style: none;
15
- display: flex;
16
- align-items: center;
17
- gap: var(--rs-space-3);
18
- }
19
-
20
- .summary::-webkit-details-marker {
21
- display: none;
22
- }
23
-
24
- .summary::before {
25
- content: '▶';
26
- font-size: 10px;
27
- transition: transform 0.2s ease;
28
- color: var(--rs-color-text-base-secondary);
29
- }
30
-
31
- .details[open] > .summary::before {
32
- transform: rotate(90deg);
33
8
  }
34
9
 
35
10
  .content {
@@ -1,9 +1,8 @@
1
1
  import type { ComponentProps } from 'react'
2
- import styles from './details.module.css'
3
2
 
4
3
  export function MdxDetails({ children, className, ...props }: ComponentProps<'details'>) {
5
4
  return (
6
- <details className={`${styles.details} ${className ?? ''}`} {...props}>
5
+ <details className={className} {...props}>
7
6
  {children}
8
7
  </details>
9
8
  )
@@ -11,7 +10,7 @@ export function MdxDetails({ children, className, ...props }: ComponentProps<'de
11
10
 
12
11
  export function MdxSummary({ children, className, ...props }: ComponentProps<'summary'>) {
13
12
  return (
14
- <summary className={`${styles.summary} ${className ?? ''}`} {...props}>
13
+ <summary className={className} {...props}>
15
14
  {children}
16
15
  </summary>
17
16
  )
@@ -8,6 +8,20 @@ import { Mermaid } from './mermaid'
8
8
  import { MdxParagraph } from './paragraph'
9
9
  import { CalloutContainer, CalloutTitle, CalloutDescription, MdxBlockquote } from '@/components/common/callout'
10
10
  import { Tabs } from '@raystack/apsara'
11
+ import { type ComponentProps, useEffect, useState } from 'react'
12
+
13
+ function ClientOnly({ children }: { children: React.ReactNode }) {
14
+ const [mounted, setMounted] = useState(false)
15
+ useEffect(() => setMounted(true), [])
16
+ return mounted ? <>{children}</> : null
17
+ }
18
+
19
+ function MdxTabs(props: ComponentProps<typeof Tabs>) {
20
+ return <ClientOnly><Tabs {...props} /></ClientOnly>
21
+ }
22
+ MdxTabs.List = Tabs.List
23
+ MdxTabs.Trigger = Tabs.Trigger
24
+ MdxTabs.Content = Tabs.Content
11
25
 
12
26
  export const mdxComponents: MDXComponents = {
13
27
  p: MdxParagraph,
@@ -27,7 +41,7 @@ export const mdxComponents: MDXComponents = {
27
41
  Callout: CalloutContainer,
28
42
  CalloutTitle,
29
43
  CalloutDescription,
30
- Tabs,
44
+ Tabs: MdxTabs,
31
45
  Mermaid,
32
46
  }
33
47
 
@@ -1,53 +1,19 @@
1
1
  'use client'
2
2
 
3
3
  import { Breadcrumb } from '@raystack/apsara'
4
- import type { PageTree, PageTreeItem } from '@/types'
4
+ import { getBreadcrumbItems } from 'fumadocs-core/breadcrumb'
5
+ import type { Root } from 'fumadocs-core/page-tree'
5
6
 
6
7
  interface BreadcrumbsProps {
7
8
  slug: string[]
8
- tree: PageTree
9
- }
10
-
11
- function findInTree(items: PageTreeItem[], targetPath: string): PageTreeItem | undefined {
12
- for (const item of items) {
13
- const itemUrl = item.url || `/${item.name.toLowerCase().replace(/\s+/g, '-')}`
14
- if (itemUrl === targetPath || itemUrl === `/${targetPath}`) {
15
- return item
16
- }
17
- if (item.children) {
18
- const found = findInTree(item.children, targetPath)
19
- if (found) return found
20
- }
21
- }
22
- return undefined
23
- }
24
-
25
- function getFirstPageUrl(item: PageTreeItem): string | undefined {
26
- if (item.type === 'page' && item.url) {
27
- return item.url
28
- }
29
- if (item.children) {
30
- for (const child of item.children) {
31
- const url = getFirstPageUrl(child)
32
- if (url) return url
33
- }
34
- }
35
- return undefined
9
+ tree: Root
36
10
  }
37
11
 
38
12
  export function Breadcrumbs({ slug, tree }: BreadcrumbsProps) {
39
- const items: { label: string; href: string }[] = []
13
+ const url = slug.length === 0 ? '/' : `/${slug.join('/')}`
14
+ const items = getBreadcrumbItems(url, tree, { includePage: true })
40
15
 
41
- for (let i = 0; i < slug.length; i++) {
42
- const currentPath = `/${slug.slice(0, i + 1).join('/')}`
43
- const node = findInTree(tree.children, currentPath)
44
- const href = node?.url || (node && getFirstPageUrl(node)) || currentPath
45
- const label = node?.name ?? slug[i]
46
- items.push({
47
- label: label.charAt(0).toUpperCase() + label.slice(1),
48
- href,
49
- })
50
- }
16
+ if (items.length === 0) return null
51
17
 
52
18
  return (
53
19
  <Breadcrumb size="small">
@@ -55,10 +21,10 @@ export function Breadcrumbs({ slug, tree }: BreadcrumbsProps) {
55
21
  const breadcrumbItem = (
56
22
  <Breadcrumb.Item
57
23
  key={`item-${index}`}
58
- href={item.href}
24
+ href={item.url}
59
25
  current={index === items.length - 1}
60
26
  >
61
- {item.label}
27
+ {item.name}
62
28
  </Breadcrumb.Item>
63
29
  )
64
30
  if (index === 0) return [breadcrumbItem]
@@ -1,6 +1,6 @@
1
1
  import type { OpenAPIV3 } from 'openapi-types'
2
2
  import slugify from 'slugify'
3
- import type { PageTree, PageTreeItem } from '@/types/content'
3
+ import type { Root, Node, Item, Folder } from 'fumadocs-core/page-tree'
4
4
  import type { ApiSpec } from './openapi'
5
5
 
6
6
  export function getSpecSlug(spec: ApiSpec): string {
@@ -56,16 +56,15 @@ export function findApiOperation(specs: ApiSpec[], slug: string[]): ApiRouteMatc
56
56
  return null
57
57
  }
58
58
 
59
- export function buildApiPageTree(specs: ApiSpec[]): PageTree {
60
- const children: PageTreeItem[] = []
59
+ export function buildApiPageTree(specs: ApiSpec[]): Root {
60
+ const children: Node[] = []
61
61
 
62
62
  for (const spec of specs) {
63
63
  const specSlug = getSpecSlug(spec)
64
64
  const paths = spec.document.paths ?? {}
65
65
  const tags = spec.document.tags ?? []
66
66
 
67
- // Group operations by tag (case-insensitive to avoid duplicates)
68
- const opsByTag = new Map<string, PageTreeItem[]>()
67
+ const opsByTag = new Map<string, Item[]>()
69
68
  const tagDisplayName = new Map<string, string>()
70
69
 
71
70
  for (const [, pathItem] of Object.entries(paths)) {
@@ -90,7 +89,6 @@ export function buildApiPageTree(specs: ApiSpec[]): PageTree {
90
89
  }
91
90
  }
92
91
 
93
- // Use doc.tags display names where available
94
92
  for (const t of tags) {
95
93
  const key = t.name.toLowerCase()
96
94
  if (opsByTag.has(key)) {
@@ -98,7 +96,7 @@ export function buildApiPageTree(specs: ApiSpec[]): PageTree {
98
96
  }
99
97
  }
100
98
 
101
- const tagFolders: PageTreeItem[] = Array.from(opsByTag.entries()).map(([key, ops]) => ({
99
+ const tagFolders: Folder[] = Array.from(opsByTag.entries()).map(([key, ops]) => ({
102
100
  type: 'folder' as const,
103
101
  name: tagDisplayName.get(key) ?? key,
104
102
  icon: 'rectangle-stack',
@@ -110,7 +108,7 @@ export function buildApiPageTree(specs: ApiSpec[]): PageTree {
110
108
  type: 'folder',
111
109
  name: spec.name,
112
110
  children: tagFolders,
113
- })
111
+ } as Folder)
114
112
  } else {
115
113
  children.push(...tagFolders)
116
114
  }
@@ -0,0 +1,21 @@
1
+ import React, { type ReactNode } from 'react';
2
+ import type { TableOfContents } from 'fumadocs-core/toc';
3
+ import { mdxComponents } from '@/components/mdx';
4
+
5
+ const contentModules = import.meta.glob<{ default?: React.ComponentType<any>; toc?: TableOfContents }>(
6
+ '../../.content/**/*.{mdx,md}'
7
+ );
8
+
9
+ export async function loadMdxModule(relativePath: string): Promise<{ content: ReactNode; toc: TableOfContents }> {
10
+ const withoutExt = relativePath.replace(/\.(mdx|md)$/, '');
11
+ const key = relativePath.endsWith('.md')
12
+ ? `../../.content/${withoutExt}.md`
13
+ : `../../.content/${withoutExt}.mdx`;
14
+ const loader = contentModules[key];
15
+ if (!loader) return { content: null, toc: [] };
16
+ const mod = await loader();
17
+ const content = mod.default
18
+ ? React.createElement(mod.default, { components: mdxComponents })
19
+ : null;
20
+ return { content, toc: mod.toc ?? [] };
21
+ }
@@ -1,4 +1,4 @@
1
- import React, {
1
+ import {
2
2
  createContext,
3
3
  type ReactNode,
4
4
  useContext,
@@ -6,19 +6,21 @@ import React, {
6
6
  useState
7
7
  } from 'react';
8
8
  import { useLocation } from 'react-router';
9
- import { mdxComponents } from '@/components/mdx';
10
9
  import type { ApiSpec } from '@/lib/openapi';
11
- import type { ChronicleConfig, Frontmatter, PageTree } from '@/types';
10
+ import type { ChronicleConfig, Frontmatter, Root, TableOfContents } from '@/types';
11
+
12
+ export type MdxLoader = (relativePath: string) => Promise<{ content: ReactNode; toc: TableOfContents }>;
12
13
 
13
14
  interface PageData {
14
15
  slug: string[];
15
16
  frontmatter: Frontmatter;
16
17
  content: ReactNode;
18
+ toc: TableOfContents;
17
19
  }
18
20
 
19
21
  interface PageContextValue {
20
22
  config: ChronicleConfig;
21
- tree: PageTree;
23
+ tree: Root;
22
24
  page: PageData | null;
23
25
  apiSpecs: ApiSpec[];
24
26
  }
@@ -31,7 +33,7 @@ export function usePageContext(): PageContextValue {
31
33
  console.error('usePageContext: no context found!');
32
34
  return {
33
35
  config: { title: 'Documentation' },
34
- tree: { name: 'root', children: [] },
36
+ tree: { name: 'root', children: [] } as Root,
35
37
  page: null,
36
38
  apiSpecs: []
37
39
  };
@@ -41,28 +43,23 @@ export function usePageContext(): PageContextValue {
41
43
 
42
44
  interface PageProviderProps {
43
45
  initialConfig: ChronicleConfig;
44
- initialTree: PageTree;
46
+ initialTree: Root;
45
47
  initialPage: PageData | null;
46
48
  initialApiSpecs: ApiSpec[];
49
+ loadMdx: MdxLoader;
47
50
  children: ReactNode;
48
51
  }
49
52
 
50
- async function loadMdxComponent(relativePath: string): Promise<ReactNode> {
51
- const mod = await import(/* @vite-ignore */ `/.content/${relativePath}`);
52
- return mod.default
53
- ? React.createElement(mod.default, { components: mdxComponents })
54
- : null;
55
- }
56
-
57
53
  export function PageProvider({
58
54
  initialConfig,
59
55
  initialTree,
60
56
  initialPage,
61
57
  initialApiSpecs,
58
+ loadMdx,
62
59
  children
63
60
  }: PageProviderProps) {
64
61
  const { pathname } = useLocation();
65
- const [tree] = useState<PageTree>(initialTree);
62
+ const [tree] = useState<Root>(initialTree);
66
63
  const [page, setPage] = useState<PageData | null>(initialPage);
67
64
  const [apiSpecs, setApiSpecs] = useState<ApiSpec[]>(initialApiSpecs);
68
65
  const [currentPath, setCurrentPath] = useState(pathname);
@@ -95,9 +92,9 @@ export function PageProvider({
95
92
  .then(res => res.json())
96
93
  .then(async (data: { frontmatter: Frontmatter; relativePath: string }) => {
97
94
  if (cancelled.current) return;
98
- const content = await loadMdxComponent(data.relativePath);
95
+ const { content, toc } = await loadMdx(data.relativePath);
99
96
  if (cancelled.current) return;
100
- setPage({ slug, frontmatter: data.frontmatter, content });
97
+ setPage({ slug, frontmatter: data.frontmatter, content, toc });
101
98
  })
102
99
  .catch(() => {});
103
100