@raystack/chronicle 0.5.4 → 0.6.1
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 +258 -80
- package/package.json +7 -5
- package/src/cli/commands/build.ts +5 -8
- package/src/cli/commands/dev.ts +5 -6
- package/src/cli/commands/init.test.ts +77 -0
- package/src/cli/commands/init.ts +73 -40
- package/src/cli/commands/serve.ts +6 -9
- package/src/cli/commands/start.ts +5 -5
- package/src/cli/utils/config.ts +6 -12
- package/src/cli/utils/scaffold.test.ts +179 -0
- package/src/cli/utils/scaffold.ts +70 -9
- package/src/components/api/field-row.tsx +1 -1
- package/src/components/api/field-section.tsx +2 -2
- package/src/components/mdx/index.tsx +1 -1
- package/src/components/ui/breadcrumbs.tsx +4 -2
- package/src/components/ui/client-theme-switcher.tsx +21 -4
- package/src/components/ui/search.module.css +16 -41
- package/src/components/ui/search.tsx +30 -41
- package/src/lib/config.test.ts +493 -0
- package/src/lib/config.ts +123 -22
- package/src/lib/head.tsx +23 -5
- package/src/lib/llms.test.ts +94 -0
- package/src/lib/llms.ts +41 -0
- package/src/lib/navigation.test.ts +94 -0
- package/src/lib/navigation.ts +51 -0
- package/src/lib/page-context.tsx +51 -32
- package/src/lib/route-resolver.test.ts +173 -0
- package/src/lib/route-resolver.ts +73 -0
- package/src/lib/source.ts +94 -1
- package/src/lib/version-source.test.ts +163 -0
- package/src/lib/version-source.ts +101 -0
- package/src/pages/ApiPage.tsx +1 -1
- package/src/pages/DocsLayout.tsx +24 -3
- package/src/pages/DocsPage.tsx +3 -6
- package/src/pages/LandingPage.module.css +56 -0
- package/src/pages/LandingPage.tsx +39 -0
- package/src/pages/NotFound.tsx +2 -0
- package/src/server/App.tsx +21 -23
- package/src/server/api/page.ts +5 -1
- package/src/server/api/search.ts +51 -24
- package/src/server/api/specs.ts +17 -5
- package/src/server/entry-client.tsx +42 -14
- package/src/server/entry-server.tsx +33 -11
- package/src/server/routes/[...slug].md.ts +0 -6
- package/src/server/routes/[version]/llms.txt.ts +26 -0
- package/src/server/routes/llms.txt.ts +10 -13
- package/src/server/routes/og.tsx +2 -2
- package/src/server/routes/sitemap.xml.ts +14 -6
- package/src/server/vite-config.ts +5 -5
- package/src/themes/default/ContentDirButtons.tsx +66 -0
- package/src/themes/default/Layout.module.css +187 -40
- package/src/themes/default/Layout.tsx +166 -65
- package/src/themes/default/OpenInAI.tsx +112 -0
- package/src/themes/default/Page.module.css +30 -0
- package/src/themes/default/Page.tsx +1 -3
- package/src/themes/default/SidebarLogo.tsx +26 -0
- package/src/themes/default/Toc.module.css +102 -25
- package/src/themes/default/Toc.tsx +56 -10
- package/src/themes/default/VersionSwitcher.tsx +59 -0
- package/src/themes/paper/ContentDirDropdown.tsx +47 -0
- package/src/themes/paper/Layout.module.css +7 -0
- package/src/themes/paper/Layout.tsx +20 -13
- package/src/themes/paper/VersionSwitcher.tsx +60 -0
- package/src/types/config.ts +145 -23
- package/src/types/content.ts +11 -1
- package/src/types/theme.ts +1 -0
- package/src/components/ui/footer.module.css +0 -27
- package/src/components/ui/footer.tsx +0 -30
package/src/server/api/search.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import MiniSearch from 'minisearch';
|
|
2
|
-
import { defineHandler } from 'nitro';
|
|
2
|
+
import { defineHandler, HTTPError } from 'nitro';
|
|
3
3
|
import type { OpenAPIV3 } from 'openapi-types';
|
|
4
4
|
import { getSpecSlug } from '@/lib/api-routes';
|
|
5
|
-
import { loadConfig } from '@/lib/config';
|
|
5
|
+
import { getApiConfigsForVersion, loadConfig } from '@/lib/config';
|
|
6
6
|
import { loadApiSpecs } from '@/lib/openapi';
|
|
7
|
-
import {
|
|
7
|
+
import { extractFrontmatter, getPagesForVersion } from '@/lib/source';
|
|
8
|
+
import { LATEST_CONTEXT, type VersionContext } from '@/lib/version-source';
|
|
8
9
|
|
|
9
10
|
interface SearchDocument {
|
|
10
11
|
id: string;
|
|
@@ -14,8 +15,12 @@ interface SearchDocument {
|
|
|
14
15
|
type: 'page' | 'api';
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
const indexCache = new Map<string, MiniSearch<SearchDocument>>();
|
|
19
|
+
const docsCache = new Map<string, SearchDocument[]>();
|
|
20
|
+
|
|
21
|
+
function keyFor(ctx: VersionContext): string {
|
|
22
|
+
return ctx.dir ?? '__latest__';
|
|
23
|
+
}
|
|
19
24
|
|
|
20
25
|
function createIndex(docs: SearchDocument[]): MiniSearch<SearchDocument> {
|
|
21
26
|
const index = new MiniSearch<SearchDocument>({
|
|
@@ -31,8 +36,8 @@ function createIndex(docs: SearchDocument[]): MiniSearch<SearchDocument> {
|
|
|
31
36
|
return index;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
async function scanContent(): Promise<SearchDocument[]> {
|
|
35
|
-
const pages = await
|
|
39
|
+
async function scanContent(ctx: VersionContext): Promise<SearchDocument[]> {
|
|
40
|
+
const pages = await getPagesForVersion(ctx);
|
|
36
41
|
return pages.map(p => {
|
|
37
42
|
const fm = extractFrontmatter(p);
|
|
38
43
|
return {
|
|
@@ -45,12 +50,13 @@ async function scanContent(): Promise<SearchDocument[]> {
|
|
|
45
50
|
});
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
async function buildApiDocs(): Promise<SearchDocument[]> {
|
|
53
|
+
async function buildApiDocs(ctx: VersionContext): Promise<SearchDocument[]> {
|
|
49
54
|
const config = loadConfig();
|
|
50
|
-
|
|
55
|
+
const apiConfigs = getApiConfigsForVersion(config, ctx.dir);
|
|
56
|
+
if (!apiConfigs.length) return [];
|
|
51
57
|
|
|
52
58
|
const docs: SearchDocument[] = [];
|
|
53
|
-
const specs = await loadApiSpecs(
|
|
59
|
+
const specs = await loadApiSpecs(apiConfigs);
|
|
54
60
|
|
|
55
61
|
for (const spec of specs) {
|
|
56
62
|
const specSlug = getSpecSlug(spec);
|
|
@@ -60,7 +66,7 @@ async function buildApiDocs(): Promise<SearchDocument[]> {
|
|
|
60
66
|
for (const method of ['get', 'post', 'put', 'delete', 'patch'] as const) {
|
|
61
67
|
const op = pathItem[method] as OpenAPIV3.OperationObject | undefined;
|
|
62
68
|
if (!op?.operationId) continue;
|
|
63
|
-
const url =
|
|
69
|
+
const url = `${ctx.urlPrefix}/apis/${specSlug}/${encodeURIComponent(op.operationId)}`;
|
|
64
70
|
docs.push({
|
|
65
71
|
id: url,
|
|
66
72
|
url,
|
|
@@ -75,29 +81,50 @@ async function buildApiDocs(): Promise<SearchDocument[]> {
|
|
|
75
81
|
return docs;
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
async function getDocs(): Promise<SearchDocument[]> {
|
|
79
|
-
|
|
84
|
+
async function getDocs(ctx: VersionContext): Promise<SearchDocument[]> {
|
|
85
|
+
const key = keyFor(ctx);
|
|
86
|
+
const cached = docsCache.get(key);
|
|
87
|
+
if (cached) return cached;
|
|
80
88
|
const [contentDocs, apiDocs] = await Promise.all([
|
|
81
|
-
scanContent(),
|
|
82
|
-
buildApiDocs()
|
|
89
|
+
scanContent(ctx),
|
|
90
|
+
buildApiDocs(ctx)
|
|
83
91
|
]);
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
const docs = [...contentDocs, ...apiDocs];
|
|
93
|
+
docsCache.set(key, docs);
|
|
94
|
+
return docs;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function getIndex(ctx: VersionContext): Promise<MiniSearch<SearchDocument>> {
|
|
98
|
+
const key = keyFor(ctx);
|
|
99
|
+
const cached = indexCache.get(key);
|
|
100
|
+
if (cached) return cached;
|
|
101
|
+
const docs = await getDocs(ctx);
|
|
102
|
+
const index = createIndex(docs);
|
|
103
|
+
indexCache.set(key, index);
|
|
104
|
+
return index;
|
|
86
105
|
}
|
|
87
106
|
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
function resolveCtx(tag: string | null): VersionContext {
|
|
108
|
+
if (!tag) return LATEST_CONTEXT;
|
|
109
|
+
const config = loadConfig();
|
|
110
|
+
const version = config.versions?.find(v => v.dir === tag);
|
|
111
|
+
if (!version) {
|
|
112
|
+
throw new HTTPError({
|
|
113
|
+
status: 400,
|
|
114
|
+
message: `Unknown version tag: ${tag}`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return { dir: version.dir, urlPrefix: `/${version.dir}` };
|
|
93
118
|
}
|
|
94
119
|
|
|
95
120
|
export default defineHandler(async event => {
|
|
96
121
|
const query = event.url.searchParams.get('query') ?? '';
|
|
97
|
-
const
|
|
122
|
+
const tag = event.url.searchParams.get('tag');
|
|
123
|
+
const ctx = resolveCtx(tag);
|
|
124
|
+
const index = await getIndex(ctx);
|
|
98
125
|
|
|
99
126
|
if (!query) {
|
|
100
|
-
const docs = await getDocs();
|
|
127
|
+
const docs = await getDocs(ctx);
|
|
101
128
|
return docs
|
|
102
129
|
.filter(d => d.type === 'page')
|
|
103
130
|
.slice(0, 8)
|
package/src/server/api/specs.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import { defineHandler } from 'nitro';
|
|
2
|
-
import { loadConfig } from '@/lib/config';
|
|
1
|
+
import { defineHandler, HTTPError } from 'nitro';
|
|
2
|
+
import { getApiConfigsForVersion, loadConfig } from '@/lib/config';
|
|
3
3
|
import { loadApiSpecs } from '@/lib/openapi';
|
|
4
4
|
|
|
5
|
-
export default defineHandler(async
|
|
5
|
+
export default defineHandler(async event => {
|
|
6
|
+
const versionParam = event.url.searchParams.get('version');
|
|
7
|
+
const versionDir = versionParam === null || versionParam === '' ? null : versionParam;
|
|
8
|
+
|
|
6
9
|
const config = loadConfig();
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
if (versionDir && !config.versions?.some(v => v.dir === versionDir)) {
|
|
11
|
+
throw new HTTPError({
|
|
12
|
+
status: 400,
|
|
13
|
+
message: `Unknown version: ${versionDir}`,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const apiConfigs = getApiConfigsForVersion(config, versionDir);
|
|
18
|
+
if (!apiConfigs.length) return [];
|
|
19
|
+
|
|
20
|
+
return loadApiSpecs(apiConfigs);
|
|
9
21
|
});
|
|
@@ -4,8 +4,11 @@ import { hydrateRoot } from 'react-dom/client';
|
|
|
4
4
|
import { BrowserRouter } from 'react-router';
|
|
5
5
|
import { ReactRouterProvider } from 'fumadocs-core/framework/react-router';
|
|
6
6
|
import { mdxComponents } from '@/components/mdx';
|
|
7
|
+
import { getApiConfigsForVersion } from '@/lib/config';
|
|
7
8
|
import { PageProvider } from '@/lib/page-context';
|
|
8
|
-
import
|
|
9
|
+
import { resolveRoute, RouteType } from '@/lib/route-resolver';
|
|
10
|
+
import { resolveVersionFromUrl, type VersionContext } from '@/lib/version-source';
|
|
11
|
+
import type { ChronicleConfig, Frontmatter, PageNavLink, Root, TableOfContents } from '@/types';
|
|
9
12
|
import type { ApiSpec } from '@/lib/openapi';
|
|
10
13
|
import type { ReactNode } from 'react';
|
|
11
14
|
import { App } from './App';
|
|
@@ -14,11 +17,19 @@ interface EmbeddedData {
|
|
|
14
17
|
config: ChronicleConfig;
|
|
15
18
|
tree: Root;
|
|
16
19
|
slug: string[];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
version: VersionContext;
|
|
21
|
+
frontmatter: Frontmatter | null;
|
|
22
|
+
relativePath: string | null;
|
|
23
|
+
originalPath: string | null;
|
|
24
|
+
prev: PageNavLink | null;
|
|
25
|
+
next: PageNavLink | null;
|
|
20
26
|
}
|
|
21
27
|
|
|
28
|
+
const defaultConfig: ChronicleConfig = {
|
|
29
|
+
site: { title: 'Documentation' },
|
|
30
|
+
content: [{ dir: 'docs', label: 'Docs' }],
|
|
31
|
+
};
|
|
32
|
+
|
|
22
33
|
const contentModules = import.meta.glob<{ default?: React.ComponentType<any>; toc?: TableOfContents }>(
|
|
23
34
|
'../../.content/**/*.{mdx,md}'
|
|
24
35
|
);
|
|
@@ -43,23 +54,39 @@ async function hydrate() {
|
|
|
43
54
|
window as unknown as { __PAGE_DATA__?: EmbeddedData }
|
|
44
55
|
).__PAGE_DATA__;
|
|
45
56
|
|
|
46
|
-
const config: ChronicleConfig = embedded?.config ??
|
|
47
|
-
title: 'Documentation'
|
|
48
|
-
};
|
|
57
|
+
const config: ChronicleConfig = embedded?.config ?? defaultConfig;
|
|
49
58
|
const tree: Root = embedded?.tree ?? { name: 'root', children: [] };
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
|
|
60
|
+
const route = resolveRoute(window.location.pathname, config);
|
|
61
|
+
// resolveVersionFromUrl always returns a valid context — even for redirect
|
|
62
|
+
// targets (e.g. /v1 -> /v1/docs) where route.version isn't on the union.
|
|
63
|
+
const routeVersion: VersionContext = resolveVersionFromUrl(
|
|
64
|
+
window.location.pathname,
|
|
65
|
+
config,
|
|
66
|
+
);
|
|
67
|
+
const version: VersionContext = embedded?.version ?? routeVersion;
|
|
68
|
+
|
|
69
|
+
const isApiRoute =
|
|
70
|
+
route.type === RouteType.ApiIndex || route.type === RouteType.ApiPage;
|
|
71
|
+
const apiConfigs = isApiRoute
|
|
72
|
+
? getApiConfigsForVersion(config, routeVersion.dir)
|
|
73
|
+
: [];
|
|
74
|
+
const specsUrl = routeVersion.dir
|
|
75
|
+
? `/api/specs?version=${encodeURIComponent(routeVersion.dir)}`
|
|
76
|
+
: '/api/specs';
|
|
77
|
+
const apiSpecs: ApiSpec[] = apiConfigs.length
|
|
78
|
+
? await fetch(specsUrl)
|
|
54
79
|
.then(r => r.json())
|
|
55
80
|
.catch(() => [])
|
|
56
81
|
: [];
|
|
57
82
|
|
|
58
83
|
const mdxPath = embedded?.originalPath || embedded?.relativePath;
|
|
59
|
-
const page = mdxPath
|
|
84
|
+
const page = embedded && mdxPath && embedded.frontmatter
|
|
60
85
|
? {
|
|
61
|
-
slug: embedded
|
|
62
|
-
frontmatter: embedded
|
|
86
|
+
slug: embedded.slug,
|
|
87
|
+
frontmatter: embedded.frontmatter,
|
|
88
|
+
prev: embedded.prev,
|
|
89
|
+
next: embedded.next,
|
|
63
90
|
...(await loadMdxModule(mdxPath)),
|
|
64
91
|
}
|
|
65
92
|
: null;
|
|
@@ -73,6 +100,7 @@ async function hydrate() {
|
|
|
73
100
|
initialTree={tree}
|
|
74
101
|
initialPage={page}
|
|
75
102
|
initialApiSpecs={apiSpecs}
|
|
103
|
+
initialVersion={version}
|
|
76
104
|
loadMdx={loadMdxModule}
|
|
77
105
|
>
|
|
78
106
|
<App />
|
|
@@ -5,10 +5,11 @@ import { renderToReadableStream } from 'react-dom/server.edge';
|
|
|
5
5
|
import { StaticRouter } from 'react-router';
|
|
6
6
|
import { ReactRouterProvider } from 'fumadocs-core/framework/react-router';
|
|
7
7
|
import { mdxComponents } from '@/components/mdx';
|
|
8
|
-
import { loadConfig } from '@/lib/config';
|
|
8
|
+
import { getApiConfigsForVersion, loadConfig } from '@/lib/config';
|
|
9
9
|
import { loadApiSpecs } from '@/lib/openapi';
|
|
10
10
|
import { PageProvider } from '@/lib/page-context';
|
|
11
|
-
import {
|
|
11
|
+
import { resolveRoute, RouteType } from '@/lib/route-resolver';
|
|
12
|
+
import { getPageTree, getPage, getPageNav, loadPageModule, extractFrontmatter, getRelativePath, getOriginalPath } from '@/lib/source';
|
|
12
13
|
import { useNitroApp } from 'nitro/app';
|
|
13
14
|
import { App } from './App';
|
|
14
15
|
|
|
@@ -19,17 +20,32 @@ export default {
|
|
|
19
20
|
async fetch(req: Request) {
|
|
20
21
|
const url = new URL(req.url);
|
|
21
22
|
const pathname = url.pathname;
|
|
22
|
-
const slug = pathname === '/' ? [] : pathname.slice(1).split('/').filter(Boolean);
|
|
23
23
|
|
|
24
24
|
const config = loadConfig();
|
|
25
|
-
const
|
|
26
|
-
|
|
25
|
+
const route = resolveRoute(pathname, config);
|
|
26
|
+
|
|
27
|
+
if (route.type === RouteType.Redirect) {
|
|
28
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: useNitroApp is a Nitro DI accessor, not a React hook
|
|
29
|
+
useNitroApp().hooks.callHook('chronicle:ssr-rendered', pathname, route.status, 0);
|
|
30
|
+
return new Response(null, {
|
|
31
|
+
status: route.status,
|
|
32
|
+
headers: { Location: route.to },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isApiRoute = route.type === RouteType.ApiIndex || route.type === RouteType.ApiPage;
|
|
37
|
+
const pageSlug = route.type === RouteType.DocsPage ? route.slug : [];
|
|
38
|
+
|
|
39
|
+
const apiConfigs = isApiRoute
|
|
40
|
+
? getApiConfigsForVersion(config, route.version.dir)
|
|
27
41
|
: [];
|
|
42
|
+
const apiSpecs = apiConfigs.length ? await loadApiSpecs(apiConfigs) : [];
|
|
28
43
|
|
|
29
44
|
const [tree, page] = await Promise.all([
|
|
30
45
|
getPageTree(),
|
|
31
|
-
getPage(slug),
|
|
46
|
+
route.type === RouteType.DocsPage ? getPage(route.slug) : Promise.resolve(null),
|
|
32
47
|
]);
|
|
48
|
+
const nav = page ? await getPageNav(pageSlug, tree) : { prev: null, next: null };
|
|
33
49
|
|
|
34
50
|
const relativePath = page ? getRelativePath(page) : null;
|
|
35
51
|
const originalPath = page ? getOriginalPath(page) : null;
|
|
@@ -37,22 +53,27 @@ export default {
|
|
|
37
53
|
|
|
38
54
|
const pageData = page
|
|
39
55
|
? {
|
|
40
|
-
slug,
|
|
41
|
-
frontmatter: extractFrontmatter(page,
|
|
56
|
+
slug: pageSlug,
|
|
57
|
+
frontmatter: extractFrontmatter(page, pageSlug[pageSlug.length - 1]),
|
|
42
58
|
content: mdxModule?.default
|
|
43
59
|
? React.createElement(mdxModule.default, { components: mdxComponents })
|
|
44
60
|
: null,
|
|
45
61
|
toc: mdxModule?.toc ?? [],
|
|
62
|
+
prev: nav.prev,
|
|
63
|
+
next: nav.next,
|
|
46
64
|
}
|
|
47
65
|
: null;
|
|
48
66
|
|
|
49
67
|
const embeddedData = {
|
|
50
68
|
config,
|
|
51
69
|
tree,
|
|
52
|
-
slug,
|
|
70
|
+
slug: pageSlug,
|
|
71
|
+
version: route.version,
|
|
53
72
|
frontmatter: pageData?.frontmatter ?? null,
|
|
54
73
|
relativePath,
|
|
55
74
|
originalPath,
|
|
75
|
+
prev: pageData?.prev ?? null,
|
|
76
|
+
next: pageData?.next ?? null,
|
|
56
77
|
};
|
|
57
78
|
const safeJson = JSON.stringify(embeddedData).replace(/</g, '\\u003c');
|
|
58
79
|
|
|
@@ -82,6 +103,7 @@ export default {
|
|
|
82
103
|
initialTree={tree}
|
|
83
104
|
initialPage={pageData}
|
|
84
105
|
initialApiSpecs={apiSpecs}
|
|
106
|
+
initialVersion={route.version}
|
|
85
107
|
loadMdx={async () => ({ content: null, toc: [] })}
|
|
86
108
|
>
|
|
87
109
|
<App />
|
|
@@ -95,9 +117,9 @@ export default {
|
|
|
95
117
|
|
|
96
118
|
const renderDuration = performance.now() - renderStart;
|
|
97
119
|
|
|
98
|
-
const
|
|
99
|
-
const status = !page && !isApiRoute && slug.length > 0 ? 404 : 200;
|
|
120
|
+
const status = route.type === RouteType.DocsPage && !page ? 404 : 200;
|
|
100
121
|
|
|
122
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: useNitroApp is a Nitro DI accessor, not a React hook
|
|
101
123
|
useNitroApp().hooks.callHook('chronicle:ssr-rendered', pathname, status, renderDuration);
|
|
102
124
|
|
|
103
125
|
return new Response(stream, {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import matter from 'gray-matter';
|
|
3
3
|
import { defineHandler, HTTPError } from 'nitro';
|
|
4
|
-
import { loadConfig } from '@/lib/config';
|
|
5
4
|
import { getPage, getOriginalPath } from '@/lib/source';
|
|
6
5
|
import { safePath } from '@/server/utils/safe-path';
|
|
7
6
|
|
|
@@ -9,11 +8,6 @@ export default defineHandler(async event => {
|
|
|
9
8
|
const pathname = event.path || event.req.url?.split('?')[0] || '';
|
|
10
9
|
if (!pathname.endsWith('.md')) return;
|
|
11
10
|
|
|
12
|
-
const config = loadConfig();
|
|
13
|
-
if (!config.llms?.enabled) {
|
|
14
|
-
throw new HTTPError({ status: 404, message: 'Not Found' });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
const stripped = pathname.replace(/\.md$/, '');
|
|
18
12
|
const parts = stripped === '/index' || stripped === '/'
|
|
19
13
|
? []
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getRouterParam } from 'h3';
|
|
2
|
+
import { defineHandler, HTTPError } from 'nitro';
|
|
3
|
+
import { loadConfig } from '@/lib/config';
|
|
4
|
+
import { buildLlmsTxt } from '@/lib/llms';
|
|
5
|
+
import { extractFrontmatter, getPagesForVersion } from '@/lib/source';
|
|
6
|
+
|
|
7
|
+
export default defineHandler(async event => {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
|
|
10
|
+
const versionDir = getRouterParam(event, 'version');
|
|
11
|
+
const version = config.versions?.find(v => v.dir === versionDir);
|
|
12
|
+
if (!version) {
|
|
13
|
+
throw new HTTPError({ status: 404, message: 'Not Found' });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ctx = { dir: version.dir, urlPrefix: `/${version.dir}` };
|
|
17
|
+
const pages = await getPagesForVersion(ctx);
|
|
18
|
+
const body = buildLlmsTxt(
|
|
19
|
+
config,
|
|
20
|
+
pages.map(p => ({ url: p.url, title: extractFrontmatter(p).title })),
|
|
21
|
+
ctx,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
event.res.headers.set('Content-Type', 'text/plain');
|
|
25
|
+
return body;
|
|
26
|
+
});
|
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
import { defineHandler
|
|
1
|
+
import { defineHandler } from 'nitro';
|
|
2
2
|
import { loadConfig } from '@/lib/config';
|
|
3
|
-
import {
|
|
3
|
+
import { buildLlmsTxt } from '@/lib/llms';
|
|
4
|
+
import { extractFrontmatter, getPagesForVersion } from '@/lib/source';
|
|
5
|
+
import { LATEST_CONTEXT } from '@/lib/version-source';
|
|
4
6
|
|
|
5
7
|
export default defineHandler(async event => {
|
|
6
8
|
const config = loadConfig();
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const fm = extractFrontmatter(p);
|
|
15
|
-
const mdUrl = p.url === '/' ? '/index.md' : `${p.url}.md`;
|
|
16
|
-
return `- [${fm.title}](${mdUrl})`;
|
|
17
|
-
}).join('\n');
|
|
18
|
-
const body = `# ${config.title}\n\n${config.description ?? ''}\n\n${index}`;
|
|
10
|
+
const pages = await getPagesForVersion(LATEST_CONTEXT);
|
|
11
|
+
const body = buildLlmsTxt(
|
|
12
|
+
config,
|
|
13
|
+
pages.map(p => ({ url: p.url, title: extractFrontmatter(p).title })),
|
|
14
|
+
LATEST_CONTEXT,
|
|
15
|
+
);
|
|
19
16
|
|
|
20
17
|
event.res.headers.set('Content-Type', 'text/plain');
|
|
21
18
|
return body;
|
package/src/server/routes/og.tsx
CHANGED
|
@@ -22,9 +22,9 @@ async function loadFont(): Promise<ArrayBuffer> {
|
|
|
22
22
|
|
|
23
23
|
export default defineHandler(async event => {
|
|
24
24
|
const config = loadConfig();
|
|
25
|
-
const title = event.url.searchParams.get('title') ?? config.title;
|
|
25
|
+
const title = event.url.searchParams.get('title') ?? config.site.title;
|
|
26
26
|
const description = event.url.searchParams.get('description') ?? '';
|
|
27
|
-
const siteName = config.title;
|
|
27
|
+
const siteName = config.site.title;
|
|
28
28
|
|
|
29
29
|
const font = await loadFont();
|
|
30
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineHandler } from 'nitro';
|
|
2
2
|
import { buildApiRoutes } from '@/lib/api-routes';
|
|
3
|
-
import { loadConfig } from '@/lib/config';
|
|
3
|
+
import { getAllVersions, getApiConfigsForVersion, loadConfig } from '@/lib/config';
|
|
4
4
|
import { loadApiSpecs } from '@/lib/openapi';
|
|
5
5
|
import { getPages } from '@/lib/source';
|
|
6
6
|
|
|
@@ -23,11 +23,19 @@ export default defineHandler(async event => {
|
|
|
23
23
|
return `<url><loc>${baseUrl}/${page.slugs.join('/')}</loc>${lastmod}</url>`;
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
const apiPages =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
const apiPages: string[] = [];
|
|
27
|
+
for (const v of getAllVersions(config)) {
|
|
28
|
+
const versionDir = v.isLatest ? null : v.dir;
|
|
29
|
+
const apiConfigs = getApiConfigsForVersion(config, versionDir);
|
|
30
|
+
if (!apiConfigs.length) continue;
|
|
31
|
+
const prefix = versionDir ? `/${versionDir}` : '';
|
|
32
|
+
const routes = buildApiRoutes(await loadApiSpecs(apiConfigs));
|
|
33
|
+
for (const route of routes) {
|
|
34
|
+
apiPages.push(
|
|
35
|
+
`<url><loc>${baseUrl}${prefix}/apis/${route.slug.join('/')}</loc></url>`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
31
39
|
|
|
32
40
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
33
41
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
@@ -18,7 +18,6 @@ function resolveOutputDir(projectRoot: string, preset?: string): string {
|
|
|
18
18
|
export interface ViteConfigOptions {
|
|
19
19
|
packageRoot: string;
|
|
20
20
|
projectRoot: string;
|
|
21
|
-
contentDir: string;
|
|
22
21
|
configPath?: string;
|
|
23
22
|
preset?: string;
|
|
24
23
|
}
|
|
@@ -41,8 +40,9 @@ async function readChronicleConfig(projectRoot: string, configPath?: string): Pr
|
|
|
41
40
|
export async function createViteConfig(
|
|
42
41
|
options: ViteConfigOptions
|
|
43
42
|
): Promise<InlineConfig> {
|
|
44
|
-
const { packageRoot, projectRoot,
|
|
43
|
+
const { packageRoot, projectRoot, configPath, preset } = options;
|
|
45
44
|
const rawConfig = await readChronicleConfig(projectRoot, configPath);
|
|
45
|
+
const contentMirror = path.resolve(packageRoot, '.content');
|
|
46
46
|
|
|
47
47
|
return {
|
|
48
48
|
root: packageRoot,
|
|
@@ -86,8 +86,8 @@ export async function createViteConfig(
|
|
|
86
86
|
resolve: {
|
|
87
87
|
alias: {
|
|
88
88
|
'@': path.resolve(packageRoot, 'src'),
|
|
89
|
+
'tslib': 'tslib/tslib.es6.js',
|
|
89
90
|
},
|
|
90
|
-
conditions: ['module-sync', 'import', 'node'],
|
|
91
91
|
dedupe: [
|
|
92
92
|
'react',
|
|
93
93
|
'react-dom',
|
|
@@ -98,11 +98,11 @@ export async function createViteConfig(
|
|
|
98
98
|
},
|
|
99
99
|
server: {
|
|
100
100
|
fs: {
|
|
101
|
-
allow: [packageRoot, projectRoot,
|
|
101
|
+
allow: [packageRoot, projectRoot, contentMirror]
|
|
102
102
|
}
|
|
103
103
|
},
|
|
104
104
|
define: {
|
|
105
|
-
__CHRONICLE_CONTENT_DIR__: JSON.stringify(
|
|
105
|
+
__CHRONICLE_CONTENT_DIR__: JSON.stringify(contentMirror),
|
|
106
106
|
__CHRONICLE_PROJECT_ROOT__: JSON.stringify(projectRoot),
|
|
107
107
|
__CHRONICLE_CONFIG_RAW__: JSON.stringify(rawConfig),
|
|
108
108
|
},
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
|
2
|
+
import { Button, Menu, Flex } from '@raystack/apsara';
|
|
3
|
+
import { Link as RouterLink, useLocation, useNavigate } from 'react-router';
|
|
4
|
+
import { getLandingEntries } from '@/lib/config';
|
|
5
|
+
import { getActiveContentDir, splitContentButtons } from '@/lib/navigation';
|
|
6
|
+
import { usePageContext } from '@/lib/page-context';
|
|
7
|
+
|
|
8
|
+
const MAX_VISIBLE = 3;
|
|
9
|
+
|
|
10
|
+
export function ContentDirButtons() {
|
|
11
|
+
const { config, version } = usePageContext();
|
|
12
|
+
const { pathname } = useLocation();
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
|
|
15
|
+
const entries = getLandingEntries(config, version.dir);
|
|
16
|
+
if (entries.length <= 1) return null;
|
|
17
|
+
|
|
18
|
+
const active = getActiveContentDir(pathname, config);
|
|
19
|
+
const { visible, overflow } = splitContentButtons(entries, MAX_VISIBLE);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Flex gap='small' align='center'>
|
|
23
|
+
{visible.map(entry => (
|
|
24
|
+
<RouterLink
|
|
25
|
+
key={entry.href}
|
|
26
|
+
to={entry.href}
|
|
27
|
+
style={{ textDecoration: 'none' }}
|
|
28
|
+
>
|
|
29
|
+
<Button
|
|
30
|
+
size='small'
|
|
31
|
+
variant={active === entry.contentDir ? 'solid' : 'outline'}
|
|
32
|
+
color='neutral'
|
|
33
|
+
>
|
|
34
|
+
{entry.label}
|
|
35
|
+
</Button>
|
|
36
|
+
</RouterLink>
|
|
37
|
+
))}
|
|
38
|
+
{overflow.length > 0 ? (
|
|
39
|
+
<Menu>
|
|
40
|
+
<Menu.Trigger
|
|
41
|
+
render={
|
|
42
|
+
<Button
|
|
43
|
+
size='small'
|
|
44
|
+
variant='outline'
|
|
45
|
+
color='neutral'
|
|
46
|
+
trailingIcon={<ChevronDownIcon width={14} height={14} />}
|
|
47
|
+
/>
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
More
|
|
51
|
+
</Menu.Trigger>
|
|
52
|
+
<Menu.Content>
|
|
53
|
+
{overflow.map(entry => (
|
|
54
|
+
<Menu.Item
|
|
55
|
+
key={entry.href}
|
|
56
|
+
onClick={() => navigate(entry.href)}
|
|
57
|
+
>
|
|
58
|
+
{entry.label}
|
|
59
|
+
</Menu.Item>
|
|
60
|
+
))}
|
|
61
|
+
</Menu.Content>
|
|
62
|
+
</Menu>
|
|
63
|
+
) : null}
|
|
64
|
+
</Flex>
|
|
65
|
+
);
|
|
66
|
+
}
|