@supatent/supatent-docs 0.1.0

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.
Files changed (46) hide show
  1. package/dist/chunk-QVZFIUSH.js +13777 -0
  2. package/dist/index.d.ts +6 -0
  3. package/dist/index.js +75 -0
  4. package/dist/next-config.d.ts +20 -0
  5. package/dist/next-config.js +305 -0
  6. package/dist/types-Cf4pRODK.d.ts +60 -0
  7. package/package.json +49 -0
  8. package/runtime/middleware.ts +52 -0
  9. package/runtime/src/app/[locale]/docs-internal/[[...slug]]/page.tsx +91 -0
  10. package/runtime/src/app/[locale]/docs-internal/ai/all/route.ts +52 -0
  11. package/runtime/src/app/[locale]/docs-internal/ai/index/route.ts +52 -0
  12. package/runtime/src/app/[locale]/docs-internal/markdown/[...slug]/route.ts +50 -0
  13. package/runtime/src/app/globals.css +1082 -0
  14. package/runtime/src/app/layout.tsx +37 -0
  15. package/runtime/src/app/page.tsx +26 -0
  16. package/runtime/src/components/code-group-enhancer.tsx +128 -0
  17. package/runtime/src/components/docs-shell.tsx +140 -0
  18. package/runtime/src/components/header-dropdown.tsx +138 -0
  19. package/runtime/src/components/icons.tsx +58 -0
  20. package/runtime/src/components/locale-switcher.tsx +97 -0
  21. package/runtime/src/components/mobile-docs-menu.tsx +208 -0
  22. package/runtime/src/components/site-header.tsx +44 -0
  23. package/runtime/src/components/site-search.tsx +358 -0
  24. package/runtime/src/components/theme-toggle.tsx +74 -0
  25. package/runtime/src/docs.config.ts +91 -0
  26. package/runtime/src/framework/config.ts +76 -0
  27. package/runtime/src/framework/errors.ts +34 -0
  28. package/runtime/src/framework/locales.ts +45 -0
  29. package/runtime/src/framework/markdown-search-text.ts +11 -0
  30. package/runtime/src/framework/navigation.ts +58 -0
  31. package/runtime/src/framework/next-config.ts +160 -0
  32. package/runtime/src/framework/rendering.ts +445 -0
  33. package/runtime/src/framework/repository.ts +255 -0
  34. package/runtime/src/framework/runtime-locales.ts +85 -0
  35. package/runtime/src/framework/search-index-types.ts +34 -0
  36. package/runtime/src/framework/search-index.ts +271 -0
  37. package/runtime/src/framework/service.ts +302 -0
  38. package/runtime/src/framework/settings.ts +43 -0
  39. package/runtime/src/framework/site-title.ts +54 -0
  40. package/runtime/src/framework/theme.ts +17 -0
  41. package/runtime/src/framework/types.ts +66 -0
  42. package/runtime/src/framework/url.ts +78 -0
  43. package/runtime/src/supatent/client.ts +2 -0
  44. package/src/index.ts +11 -0
  45. package/src/next-config.ts +5 -0
  46. package/src/next.ts +10 -0
@@ -0,0 +1,85 @@
1
+ import { cache } from "react";
2
+ import { createClient } from "@supatent/client";
3
+ import { docsConfig } from "../docs.config";
4
+ import type { DocsDataMode } from "./types";
5
+
6
+ export type RuntimeLocales = {
7
+ defaultLocale: string;
8
+ locales: string[];
9
+ };
10
+
11
+ type RuntimeLocalesOptions = {
12
+ dataMode?: DocsDataMode;
13
+ };
14
+
15
+ function normalizeLocales(locales: string[]): string[] {
16
+ const unique = new Set<string>();
17
+
18
+ for (const locale of locales) {
19
+ const normalized = locale.trim();
20
+ if (normalized) {
21
+ unique.add(normalized);
22
+ }
23
+ }
24
+
25
+ return [...unique];
26
+ }
27
+
28
+ function resolveConfiguredRuntimeLocales(): RuntimeLocales {
29
+ const locales = normalizeLocales(docsConfig.routing.locales);
30
+ const fallbackDefault = docsConfig.routing.defaultLocale;
31
+ const hasDefault = locales.includes(fallbackDefault);
32
+ const defaultLocale = hasDefault ? fallbackDefault : (locales[0] ?? fallbackDefault);
33
+
34
+ return {
35
+ defaultLocale,
36
+ locales: locales.length > 0 ? locales : [fallbackDefault],
37
+ };
38
+ }
39
+
40
+ async function fetchRuntimeLocales(): Promise<RuntimeLocales> {
41
+ const client = createClient({
42
+ baseUrl: docsConfig.supatent.baseUrl,
43
+ projectSlug: docsConfig.supatent.projectSlug,
44
+ apiKey: docsConfig.supatent.apiKey,
45
+ // Locales are project metadata and should not depend on draft/published data mode.
46
+ preview: false,
47
+ cache: docsConfig.supatent.cache,
48
+ });
49
+
50
+ try {
51
+ const response = await client.getLocales();
52
+ const locales = normalizeLocales(response.locales.map((entry) => entry.code));
53
+
54
+ if (locales.length === 0) {
55
+ throw new Error("Supatent returned no locales.");
56
+ }
57
+
58
+ const defaultLocale = locales.includes(response.default)
59
+ ? response.default
60
+ : locales[0];
61
+
62
+ return {
63
+ defaultLocale,
64
+ locales,
65
+ };
66
+ } catch (error) {
67
+ const fallback = resolveConfiguredRuntimeLocales();
68
+ const reason = error instanceof Error ? error.message : "Unknown locale fetch failure";
69
+ console.warn(
70
+ `[supatent-docs] Locale metadata fetch failed (${reason}). Falling back to configured locales: ${fallback.locales.join(", ")}.`,
71
+ );
72
+ return fallback;
73
+ }
74
+ }
75
+
76
+ const getPublishedRuntimeLocales = cache(async (): Promise<RuntimeLocales> =>
77
+ fetchRuntimeLocales(),
78
+ );
79
+
80
+ export async function getRuntimeLocales(
81
+ options: RuntimeLocalesOptions = {},
82
+ ): Promise<RuntimeLocales> {
83
+ void options;
84
+ return getPublishedRuntimeLocales();
85
+ }
@@ -0,0 +1,34 @@
1
+ export const DEFAULT_SEARCH_INDEX_OUTPUT_DIR = "public/.supatent-docs/search";
2
+ export const SEARCH_INDEX_PUBLIC_BASE_PATH = "/.supatent-docs/search";
3
+
4
+ export type SearchIndexDocument = {
5
+ id: string;
6
+ locale: string;
7
+ slug: string;
8
+ title: string;
9
+ path: string;
10
+ order: number;
11
+ plainText: string;
12
+ };
13
+
14
+ export type LocaleSearchIndexPayload = {
15
+ locale: string;
16
+ generatedAt: string;
17
+ documents: SearchIndexDocument[];
18
+ };
19
+
20
+ export type SearchIndexManifestEntry = {
21
+ locale: string;
22
+ file: string;
23
+ hash: string;
24
+ documentCount: number;
25
+ };
26
+
27
+ export type SearchIndexManifest = {
28
+ version: 1;
29
+ generatedAt: string;
30
+ defaultLocale: string;
31
+ basePath: string;
32
+ outputDir: string;
33
+ entries: SearchIndexManifestEntry[];
34
+ };
@@ -0,0 +1,271 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readdir, unlink, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import {
5
+ createClient,
6
+ type ContentItem,
7
+ type SupatentClient,
8
+ } from "@supatent/client";
9
+ import { z } from "zod";
10
+ import type { DocsFrameworkConfig, DocsPage } from "./types";
11
+ import {
12
+ DEFAULT_SEARCH_INDEX_OUTPUT_DIR,
13
+ type LocaleSearchIndexPayload,
14
+ type SearchIndexDocument,
15
+ type SearchIndexManifest,
16
+ type SearchIndexManifestEntry,
17
+ } from "./search-index-types";
18
+ import { markdownToSearchText } from "./markdown-search-text";
19
+ const MANIFEST_FILENAME = "manifest.json";
20
+
21
+ const pageFieldsSchema = z.object({
22
+ title: z.string().min(1),
23
+ body: z.string(),
24
+ category: z.string().min(1),
25
+ order: z.preprocess(
26
+ (value) => {
27
+ if (typeof value === "string") {
28
+ return Number(value);
29
+ }
30
+ return value;
31
+ },
32
+ z.number().finite(),
33
+ ),
34
+ });
35
+
36
+ export type GenerateStaticSearchIndexesOptions = {
37
+ config: DocsFrameworkConfig;
38
+ cwd?: string;
39
+ outputDir?: string;
40
+ logger?: (message: string) => void;
41
+ };
42
+
43
+ export type GenerateStaticSearchIndexesResult = {
44
+ outputDir: string;
45
+ manifestPath: string;
46
+ manifest: SearchIndexManifest;
47
+ };
48
+
49
+ type RuntimeLocales = {
50
+ defaultLocale: string;
51
+ locales: string[];
52
+ };
53
+
54
+ function normalizeLocales(locales: string[]): string[] {
55
+ const unique = new Set<string>();
56
+
57
+ for (const locale of locales) {
58
+ const normalized = locale.trim();
59
+ if (normalized) {
60
+ unique.add(normalized);
61
+ }
62
+ }
63
+
64
+ return [...unique];
65
+ }
66
+
67
+ function resolveConfiguredRuntimeLocales(config: DocsFrameworkConfig): RuntimeLocales {
68
+ const locales = normalizeLocales(config.routing.locales);
69
+ const fallbackDefault = config.routing.defaultLocale;
70
+ const hasDefault = locales.includes(fallbackDefault);
71
+ const defaultLocale = hasDefault ? fallbackDefault : (locales[0] ?? fallbackDefault);
72
+
73
+ return {
74
+ defaultLocale,
75
+ locales: locales.length > 0 ? locales : [fallbackDefault],
76
+ };
77
+ }
78
+
79
+ function sanitizeLocaleForFileName(locale: string): string {
80
+ const normalized = locale.trim().toLowerCase();
81
+ return normalized.replace(/[^a-z0-9-]/g, "-");
82
+ }
83
+
84
+ function normalizeSlugPart(value: string): string {
85
+ return value.replace(/^\/+|\/+$/g, "");
86
+ }
87
+
88
+ function joinPath(...segments: string[]): string {
89
+ const clean = segments.map(normalizeSlugPart).filter(Boolean);
90
+ return `/${clean.join("/")}`;
91
+ }
92
+
93
+ function toDocsPagePath(config: DocsFrameworkConfig, locale: string, slug: string): string {
94
+ return joinPath(locale, config.routing.basePath, slug);
95
+ }
96
+
97
+ function normalizePage(item: ContentItem<Record<string, unknown>>): DocsPage {
98
+ const parsed = pageFieldsSchema.safeParse(item.data);
99
+ if (!parsed.success) {
100
+ const issues = parsed.error.issues.map((issue) => issue.message).join("; ");
101
+ throw new Error(`Invalid page shape for slug "${item.slug}": ${issues}`);
102
+ }
103
+
104
+ return {
105
+ slug: item.slug,
106
+ title: parsed.data.title,
107
+ body: parsed.data.body,
108
+ category: parsed.data.category,
109
+ order: parsed.data.order,
110
+ locale: item.locale,
111
+ };
112
+ }
113
+
114
+ function sortPages(pages: DocsPage[]): DocsPage[] {
115
+ return [...pages].sort((a, b) => {
116
+ if (a.order === b.order) {
117
+ return a.slug.localeCompare(b.slug);
118
+ }
119
+ return a.order - b.order;
120
+ });
121
+ }
122
+
123
+ function buildDocument(page: DocsPage, config: DocsFrameworkConfig, locale: string): SearchIndexDocument {
124
+ return {
125
+ id: `${locale}:${page.slug}`,
126
+ locale,
127
+ slug: page.slug,
128
+ title: page.title,
129
+ path: toDocsPagePath(config, locale, page.slug),
130
+ order: page.order,
131
+ plainText: markdownToSearchText(page.body),
132
+ };
133
+ }
134
+
135
+ function toStableJson(value: unknown): string {
136
+ return JSON.stringify(value);
137
+ }
138
+
139
+ function buildIndexFileName(locale: string, payloadJson: string): {
140
+ fileName: string;
141
+ hash: string;
142
+ } {
143
+ const hash = createHash("sha256").update(payloadJson).digest("hex").slice(0, 12);
144
+ const fileName = `index.${sanitizeLocaleForFileName(locale)}.${hash}.json`;
145
+ return { fileName, hash };
146
+ }
147
+
148
+ function createLocaleClient(config: DocsFrameworkConfig, locale?: string): SupatentClient {
149
+ return createClient({
150
+ baseUrl: config.supatent.baseUrl,
151
+ projectSlug: config.supatent.projectSlug,
152
+ apiKey: config.supatent.apiKey,
153
+ preview: false,
154
+ locale,
155
+ cache: config.supatent.cache,
156
+ });
157
+ }
158
+
159
+ async function fetchRuntimeLocales(config: DocsFrameworkConfig): Promise<RuntimeLocales> {
160
+ try {
161
+ const response = await createLocaleClient(config).getLocales();
162
+ const locales = normalizeLocales(response.locales.map((entry) => entry.code));
163
+
164
+ if (locales.length === 0) {
165
+ throw new Error("Supatent returned no locales.");
166
+ }
167
+
168
+ const defaultLocale = locales.includes(response.default)
169
+ ? response.default
170
+ : locales[0];
171
+
172
+ return {
173
+ defaultLocale,
174
+ locales,
175
+ };
176
+ } catch {
177
+ return resolveConfiguredRuntimeLocales(config);
178
+ }
179
+ }
180
+
181
+ async function fetchPublishedPagesForLocale(
182
+ config: DocsFrameworkConfig,
183
+ locale: string,
184
+ ): Promise<DocsPage[]> {
185
+ const items = await createLocaleClient(config, locale)
186
+ .schema<Record<string, unknown>>(config.schemas.page)
187
+ .all();
188
+
189
+ const published = items.filter((item) => item.publishedAt !== null);
190
+ return sortPages(published.map(normalizePage));
191
+ }
192
+
193
+ async function clearGeneratedIndexFiles(outputDir: string): Promise<void> {
194
+ const entries = await readdir(outputDir, { withFileTypes: true });
195
+ await Promise.all(
196
+ entries
197
+ .filter(
198
+ (entry) =>
199
+ entry.isFile() &&
200
+ (entry.name === MANIFEST_FILENAME ||
201
+ (entry.name.startsWith("index.") && entry.name.endsWith(".json"))),
202
+ )
203
+ .map((entry) => unlink(path.join(outputDir, entry.name))),
204
+ );
205
+ }
206
+
207
+ export function createLocaleSearchIndexPayload(
208
+ locale: string,
209
+ pages: DocsPage[],
210
+ config: DocsFrameworkConfig,
211
+ generatedAt: string,
212
+ ): LocaleSearchIndexPayload {
213
+ return {
214
+ locale,
215
+ generatedAt,
216
+ documents: pages.map((page) => buildDocument(page, config, locale)),
217
+ };
218
+ }
219
+
220
+ export async function generateStaticSearchIndexes(
221
+ options: GenerateStaticSearchIndexesOptions,
222
+ ): Promise<GenerateStaticSearchIndexesResult> {
223
+ const { config, logger } = options;
224
+ const cwd = options.cwd ?? process.cwd();
225
+ const outputDir = path.resolve(cwd, options.outputDir ?? DEFAULT_SEARCH_INDEX_OUTPUT_DIR);
226
+
227
+ await mkdir(outputDir, { recursive: true });
228
+ await clearGeneratedIndexFiles(outputDir);
229
+
230
+ const runtimeLocales = await fetchRuntimeLocales(config);
231
+ const generatedAt = new Date().toISOString();
232
+ const manifestEntries: SearchIndexManifestEntry[] = [];
233
+
234
+ for (const locale of runtimeLocales.locales) {
235
+ const pages = await fetchPublishedPagesForLocale(config, locale);
236
+ const payload = createLocaleSearchIndexPayload(locale, pages, config, generatedAt);
237
+ const payloadJson = toStableJson(payload);
238
+ const { fileName, hash } = buildIndexFileName(locale, payloadJson);
239
+ await writeFile(path.join(outputDir, fileName), payloadJson, "utf8");
240
+
241
+ manifestEntries.push({
242
+ locale,
243
+ file: fileName,
244
+ hash,
245
+ documentCount: payload.documents.length,
246
+ });
247
+
248
+ logger?.(
249
+ `Built search index locale=${locale} docs=${payload.documents.length} file=${fileName}`,
250
+ );
251
+ }
252
+
253
+ const manifest: SearchIndexManifest = {
254
+ version: 1,
255
+ generatedAt,
256
+ defaultLocale: runtimeLocales.defaultLocale,
257
+ basePath: config.routing.basePath,
258
+ outputDir: options.outputDir ?? DEFAULT_SEARCH_INDEX_OUTPUT_DIR,
259
+ entries: manifestEntries,
260
+ };
261
+
262
+ const manifestPath = path.join(outputDir, MANIFEST_FILENAME);
263
+ await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
264
+ logger?.(`Wrote search index manifest: ${manifestPath}`);
265
+
266
+ return {
267
+ outputDir,
268
+ manifestPath,
269
+ manifest,
270
+ };
271
+ }
@@ -0,0 +1,302 @@
1
+ import type {
2
+ DocsCategory,
3
+ DocsDataMode,
4
+ DocsFrameworkConfig,
5
+ DocsLocaleRuntimeConfig,
6
+ DocsNavNode,
7
+ DocsPage,
8
+ DocsPageResolution,
9
+ } from "./types";
10
+ import { DocsFrameworkError } from "./errors";
11
+ import type { DocsRepository } from "./repository";
12
+ import { createSupatentDocsRepository } from "./repository";
13
+ import { resolveLocaleCandidates } from "./locales";
14
+ import { buildNavigationTree } from "./navigation";
15
+ import {
16
+ toDocsAiAllPath,
17
+ toDocsAiIndexPath,
18
+ toDocsMarkdownPath,
19
+ toDocsPagePath,
20
+ } from "./url";
21
+ import { markdownToPlainText } from "./rendering";
22
+
23
+ type DocsNavigationResult = {
24
+ requestedLocale: string;
25
+ resolvedLocale: string;
26
+ usedFallback: boolean;
27
+ navigation: DocsNavNode[];
28
+ pages: DocsPage[];
29
+ categories: DocsCategory[];
30
+ };
31
+
32
+ type DocsAiIndexResult = {
33
+ requestedLocale: string;
34
+ resolvedLocale: string;
35
+ usedFallback: boolean;
36
+ route: string;
37
+ pages: Array<{
38
+ slug: string;
39
+ title: string;
40
+ locale: string;
41
+ htmlPath: string;
42
+ markdownPath: string;
43
+ }>;
44
+ };
45
+
46
+ type DocsAiAllResult = {
47
+ requestedLocale: string;
48
+ resolvedLocale: string;
49
+ usedFallback: boolean;
50
+ route: string;
51
+ pages: Array<{
52
+ slug: string;
53
+ title: string;
54
+ locale: string;
55
+ markdown: string;
56
+ plainText: string;
57
+ htmlPath: string;
58
+ markdownPath: string;
59
+ }>;
60
+ };
61
+
62
+ type DocsServiceOptions = {
63
+ dataMode?: DocsDataMode;
64
+ localeConfig?: DocsLocaleRuntimeConfig;
65
+ repository?: DocsRepository;
66
+ };
67
+
68
+ export function createDocsService(
69
+ config: DocsFrameworkConfig,
70
+ options: DocsServiceOptions = {},
71
+ ) {
72
+ const dataMode = options.dataMode ?? config.supatent.dataMode;
73
+ const localeConfig = options.localeConfig ?? config.routing;
74
+ const repository =
75
+ options.repository ?? createSupatentDocsRepository(config, { dataMode });
76
+ const publishedOnly = dataMode === "published";
77
+
78
+ async function resolvePublishedPages(requestedLocale: string): Promise<{
79
+ requestedLocale: string;
80
+ resolvedLocale: string;
81
+ usedFallback: boolean;
82
+ pages: DocsPage[];
83
+ categories: DocsCategory[];
84
+ } | null>;
85
+ async function resolvePublishedPages(
86
+ requestedLocale: string,
87
+ options: { resolveMarkdownAssets?: boolean },
88
+ ): Promise<{
89
+ requestedLocale: string;
90
+ resolvedLocale: string;
91
+ usedFallback: boolean;
92
+ pages: DocsPage[];
93
+ categories: DocsCategory[];
94
+ } | null>;
95
+ async function resolvePublishedPages(
96
+ requestedLocale: string,
97
+ options: { resolveMarkdownAssets?: boolean } = {},
98
+ ): Promise<{
99
+ requestedLocale: string;
100
+ resolvedLocale: string;
101
+ usedFallback: boolean;
102
+ pages: DocsPage[];
103
+ categories: DocsCategory[];
104
+ } | null> {
105
+ function sortPagesWithinCategories(
106
+ pages: DocsPage[],
107
+ categories: DocsCategory[],
108
+ ): DocsPage[] {
109
+ const pagesByCategory = new Map<string, DocsPage[]>();
110
+ for (const page of pages) {
111
+ const list = pagesByCategory.get(page.category);
112
+ if (list) {
113
+ list.push(page);
114
+ } else {
115
+ pagesByCategory.set(page.category, [page]);
116
+ }
117
+ }
118
+
119
+ const orderedCategories = [...categories].sort((a, b) => {
120
+ if (a.order === b.order) {
121
+ return a.slug.localeCompare(b.slug);
122
+ }
123
+ return a.order - b.order;
124
+ });
125
+
126
+ const orderedPages: DocsPage[] = [];
127
+ for (const category of orderedCategories) {
128
+ const categoryPages = [...(pagesByCategory.get(category.slug) ?? [])].sort((a, b) => {
129
+ if (a.order === b.order) {
130
+ return a.slug.localeCompare(b.slug);
131
+ }
132
+ return a.order - b.order;
133
+ });
134
+ orderedPages.push(...categoryPages);
135
+ }
136
+
137
+ return orderedPages;
138
+ }
139
+
140
+ const candidates = resolveLocaleCandidates(requestedLocale, localeConfig);
141
+ for (const locale of candidates) {
142
+ const [pages, categories] = await Promise.all([
143
+ repository.getPages(locale, {
144
+ publishedOnly,
145
+ resolveMarkdownAssets: options.resolveMarkdownAssets,
146
+ }),
147
+ repository.getCategories(locale, { publishedOnly }),
148
+ ]);
149
+ if (pages.length > 0) {
150
+ if (categories.length === 0) {
151
+ throw new DocsFrameworkError(
152
+ "DATA_ERROR",
153
+ `No categories found for locale "${locale}" while pages exist.`,
154
+ );
155
+ }
156
+
157
+ const categorySlugSet = new Set(categories.map((entry) => entry.slug));
158
+ const missingCategories = pages
159
+ .filter((page) => !categorySlugSet.has(page.category))
160
+ .map((page) => `${page.slug} -> ${page.category}`);
161
+
162
+ if (missingCategories.length > 0) {
163
+ throw new DocsFrameworkError(
164
+ "DATA_ERROR",
165
+ `Pages reference missing categories for locale "${locale}": ${missingCategories.join(", ")}`,
166
+ );
167
+ }
168
+
169
+ return {
170
+ requestedLocale,
171
+ resolvedLocale: locale,
172
+ usedFallback: locale !== requestedLocale,
173
+ pages: sortPagesWithinCategories(pages, categories),
174
+ categories,
175
+ };
176
+ }
177
+ }
178
+
179
+ return null;
180
+ }
181
+
182
+ async function getPageBySlug(
183
+ requestedLocale: string,
184
+ slug: string,
185
+ options: { resolveMarkdownAssets?: boolean } = {},
186
+ ): Promise<DocsPageResolution | null> {
187
+ const candidates = resolveLocaleCandidates(requestedLocale, localeConfig);
188
+ for (const locale of candidates) {
189
+ const page = await repository.getPageBySlugLocale(slug, locale, {
190
+ publishedOnly,
191
+ resolveMarkdownAssets: options.resolveMarkdownAssets,
192
+ });
193
+ if (page) {
194
+ return {
195
+ page,
196
+ requestedLocale,
197
+ resolvedLocale: locale,
198
+ usedFallback: locale !== requestedLocale,
199
+ };
200
+ }
201
+ }
202
+
203
+ return null;
204
+ }
205
+
206
+ return {
207
+ async getPage(requestedLocale: string, slug = ""): Promise<DocsPageResolution | null> {
208
+ if (slug) {
209
+ return getPageBySlug(requestedLocale, slug, {
210
+ resolveMarkdownAssets: true,
211
+ });
212
+ }
213
+
214
+ const resolvedPages = await resolvePublishedPages(requestedLocale);
215
+ if (!resolvedPages || resolvedPages.pages.length === 0) {
216
+ return null;
217
+ }
218
+
219
+ const firstPage = await repository.getPageBySlugLocale(
220
+ resolvedPages.pages[0].slug,
221
+ resolvedPages.resolvedLocale,
222
+ {
223
+ publishedOnly,
224
+ resolveMarkdownAssets: true,
225
+ },
226
+ );
227
+ if (!firstPage) {
228
+ return null;
229
+ }
230
+
231
+ return {
232
+ page: firstPage,
233
+ requestedLocale,
234
+ resolvedLocale: resolvedPages.resolvedLocale,
235
+ usedFallback: resolvedPages.usedFallback,
236
+ };
237
+ },
238
+
239
+ async getNavigation(requestedLocale: string): Promise<DocsNavigationResult | null> {
240
+ const resolvedPages = await resolvePublishedPages(requestedLocale);
241
+ if (!resolvedPages) {
242
+ return null;
243
+ }
244
+
245
+ return {
246
+ ...resolvedPages,
247
+ navigation: buildNavigationTree(
248
+ resolvedPages.pages,
249
+ resolvedPages.categories,
250
+ requestedLocale,
251
+ config,
252
+ ),
253
+ };
254
+ },
255
+
256
+ async getAiIndex(requestedLocale: string): Promise<DocsAiIndexResult | null> {
257
+ const resolvedPages = await resolvePublishedPages(requestedLocale);
258
+ if (!resolvedPages) {
259
+ return null;
260
+ }
261
+
262
+ return {
263
+ requestedLocale,
264
+ resolvedLocale: resolvedPages.resolvedLocale,
265
+ usedFallback: resolvedPages.usedFallback,
266
+ route: toDocsAiIndexPath(config, requestedLocale),
267
+ pages: resolvedPages.pages.map((page) => ({
268
+ slug: page.slug,
269
+ title: page.title,
270
+ locale: page.locale,
271
+ htmlPath: toDocsPagePath(config, requestedLocale, page.slug),
272
+ markdownPath: toDocsMarkdownPath(config, requestedLocale, page.slug),
273
+ })),
274
+ };
275
+ },
276
+
277
+ async getAiAll(requestedLocale: string): Promise<DocsAiAllResult | null> {
278
+ const resolvedPages = await resolvePublishedPages(requestedLocale, {
279
+ resolveMarkdownAssets: true,
280
+ });
281
+ if (!resolvedPages) {
282
+ return null;
283
+ }
284
+
285
+ return {
286
+ requestedLocale,
287
+ resolvedLocale: resolvedPages.resolvedLocale,
288
+ usedFallback: resolvedPages.usedFallback,
289
+ route: toDocsAiAllPath(config, requestedLocale),
290
+ pages: resolvedPages.pages.map((page) => ({
291
+ slug: page.slug,
292
+ title: page.title,
293
+ locale: page.locale,
294
+ markdown: page.body,
295
+ plainText: markdownToPlainText(page.body),
296
+ htmlPath: toDocsPagePath(config, requestedLocale, page.slug),
297
+ markdownPath: toDocsMarkdownPath(config, requestedLocale, page.slug),
298
+ })),
299
+ };
300
+ },
301
+ };
302
+ }