@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,6 @@
1
+ import { D as DocsFrameworkConfig } from './types-Cf4pRODK.js';
2
+ export { a as DocsCategory, b as DocsDataMode, c as DocsLocaleRuntimeConfig, d as DocsNavNode, e as DocsPage, f as DocsPageResolution, L as LocaleFallbackMode } from './types-Cf4pRODK.js';
3
+
4
+ declare function defineDocsConfig(input: unknown): DocsFrameworkConfig;
5
+
6
+ export { DocsFrameworkConfig, defineDocsConfig };
package/dist/index.js ADDED
@@ -0,0 +1,75 @@
1
+ import {
2
+ external_exports
3
+ } from "./chunk-QVZFIUSH.js";
4
+
5
+ // runtime/src/framework/errors.ts
6
+ var DocsFrameworkError = class extends Error {
7
+ code;
8
+ causeError;
9
+ constructor(code, message, causeError) {
10
+ super(message);
11
+ this.name = "DocsFrameworkError";
12
+ this.code = code;
13
+ this.causeError = causeError;
14
+ }
15
+ };
16
+
17
+ // runtime/src/framework/config.ts
18
+ var slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
19
+ function normalizeBasePath(input) {
20
+ const trimmed = input.trim();
21
+ if (!trimmed) {
22
+ return "/docs";
23
+ }
24
+ const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
25
+ return withLeadingSlash.replace(/\/+$/, "") || "/docs";
26
+ }
27
+ var configSchema = external_exports.object({
28
+ supatent: external_exports.object({
29
+ baseUrl: external_exports.string().url(),
30
+ apiKey: external_exports.string().min(1, "supatent.apiKey must not be empty").optional(),
31
+ dataMode: external_exports.enum(["published", "draft"]).default("published"),
32
+ projectSlug: external_exports.string().regex(slugPattern, "supatent.projectSlug must be a valid slug"),
33
+ cache: external_exports.object({
34
+ maxAge: external_exports.number().int().nonnegative(),
35
+ staleWhileRevalidate: external_exports.number().int().nonnegative()
36
+ })
37
+ }),
38
+ schemas: external_exports.object({
39
+ settings: external_exports.string().regex(slugPattern, "schemas.settings must be a valid slug"),
40
+ page: external_exports.string().regex(slugPattern, "schemas.page must be a valid slug"),
41
+ category: external_exports.string().regex(slugPattern, "schemas.category must be a valid slug")
42
+ }),
43
+ routing: external_exports.object({
44
+ basePath: external_exports.string().transform(normalizeBasePath),
45
+ defaultLocale: external_exports.string().min(2),
46
+ locales: external_exports.array(external_exports.string().min(2)).min(1),
47
+ fallbackMode: external_exports.enum(["none", "defaultLocale"]).default("defaultLocale")
48
+ })
49
+ }).superRefine((value, ctx) => {
50
+ if (value.supatent.dataMode === "draft" && !value.supatent.apiKey) {
51
+ ctx.addIssue({
52
+ code: external_exports.ZodIssueCode.custom,
53
+ message: 'supatent.apiKey is required when supatent.dataMode is "draft"',
54
+ path: ["supatent", "apiKey"]
55
+ });
56
+ }
57
+ if (!value.routing.locales.includes(value.routing.defaultLocale)) {
58
+ ctx.addIssue({
59
+ code: external_exports.ZodIssueCode.custom,
60
+ message: "routing.defaultLocale must be included in routing.locales",
61
+ path: ["routing", "defaultLocale"]
62
+ });
63
+ }
64
+ });
65
+ function defineDocsConfig(input) {
66
+ const parsed = configSchema.safeParse(input);
67
+ if (!parsed.success) {
68
+ const issueList = parsed.error.issues.map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`).join("; ");
69
+ throw new DocsFrameworkError("CONFIG_ERROR", `Invalid docs config: ${issueList}`);
70
+ }
71
+ return parsed.data;
72
+ }
73
+ export {
74
+ defineDocsConfig
75
+ };
@@ -0,0 +1,20 @@
1
+ import { NextConfig } from 'next';
2
+ import { D as DocsFrameworkConfig } from './types-Cf4pRODK.js';
3
+
4
+ type NextConfigContext = {
5
+ defaultConfig: NextConfig;
6
+ };
7
+ type NextConfigFactory = (phase: string, context: NextConfigContext) => NextConfig | Promise<NextConfig>;
8
+ type NextConfigInput = NextConfig | NextConfigFactory;
9
+ type SupatentDocsSearchIndexOptions = {
10
+ enabled?: boolean;
11
+ outputDir?: string;
12
+ cwd?: string;
13
+ };
14
+ type SupatentDocsNextConfigOptions = {
15
+ docsConfig?: DocsFrameworkConfig;
16
+ searchIndex?: SupatentDocsSearchIndexOptions;
17
+ };
18
+ declare function withSupatentDocs(input?: NextConfigInput, options?: SupatentDocsNextConfigOptions): NextConfigFactory;
19
+
20
+ export { type SupatentDocsNextConfigOptions, type SupatentDocsSearchIndexOptions, withSupatentDocs };
@@ -0,0 +1,305 @@
1
+ import {
2
+ external_exports
3
+ } from "./chunk-QVZFIUSH.js";
4
+
5
+ // runtime/src/framework/next-config.ts
6
+ import { PHASE_PRODUCTION_BUILD } from "next/constants.js";
7
+
8
+ // runtime/src/framework/search-index.ts
9
+ import { createHash } from "crypto";
10
+ import { mkdir, readdir, unlink, writeFile } from "fs/promises";
11
+ import path from "path";
12
+ import {
13
+ createClient
14
+ } from "@supatent/client";
15
+
16
+ // runtime/src/framework/search-index-types.ts
17
+ var DEFAULT_SEARCH_INDEX_OUTPUT_DIR = "public/.supatent-docs/search";
18
+
19
+ // runtime/src/framework/markdown-search-text.ts
20
+ function markdownToSearchText(markdown) {
21
+ return markdown.replace(/^:::[^\n]*$/gm, " ").replace(/^(```|~~~)[^\n]*$/gm, " ").replace(/`([^`]+)`/g, "$1").replace(/!\[[^\]]*]\([^)]*\)/g, " ").replace(/\[([^\]]+)]\(([^)]+)\)/g, "$1").replace(/[#>*_-]+/g, " ").replace(/\s+/g, " ").trim();
22
+ }
23
+
24
+ // runtime/src/framework/search-index.ts
25
+ var MANIFEST_FILENAME = "manifest.json";
26
+ var pageFieldsSchema = external_exports.object({
27
+ title: external_exports.string().min(1),
28
+ body: external_exports.string(),
29
+ category: external_exports.string().min(1),
30
+ order: external_exports.preprocess(
31
+ (value) => {
32
+ if (typeof value === "string") {
33
+ return Number(value);
34
+ }
35
+ return value;
36
+ },
37
+ external_exports.number().finite()
38
+ )
39
+ });
40
+ function normalizeLocales(locales) {
41
+ const unique = /* @__PURE__ */ new Set();
42
+ for (const locale of locales) {
43
+ const normalized = locale.trim();
44
+ if (normalized) {
45
+ unique.add(normalized);
46
+ }
47
+ }
48
+ return [...unique];
49
+ }
50
+ function resolveConfiguredRuntimeLocales(config) {
51
+ const locales = normalizeLocales(config.routing.locales);
52
+ const fallbackDefault = config.routing.defaultLocale;
53
+ const hasDefault = locales.includes(fallbackDefault);
54
+ const defaultLocale = hasDefault ? fallbackDefault : locales[0] ?? fallbackDefault;
55
+ return {
56
+ defaultLocale,
57
+ locales: locales.length > 0 ? locales : [fallbackDefault]
58
+ };
59
+ }
60
+ function sanitizeLocaleForFileName(locale) {
61
+ const normalized = locale.trim().toLowerCase();
62
+ return normalized.replace(/[^a-z0-9-]/g, "-");
63
+ }
64
+ function normalizeSlugPart(value) {
65
+ return value.replace(/^\/+|\/+$/g, "");
66
+ }
67
+ function joinPath(...segments) {
68
+ const clean = segments.map(normalizeSlugPart).filter(Boolean);
69
+ return `/${clean.join("/")}`;
70
+ }
71
+ function toDocsPagePath(config, locale, slug) {
72
+ return joinPath(locale, config.routing.basePath, slug);
73
+ }
74
+ function normalizePage(item) {
75
+ const parsed = pageFieldsSchema.safeParse(item.data);
76
+ if (!parsed.success) {
77
+ const issues = parsed.error.issues.map((issue) => issue.message).join("; ");
78
+ throw new Error(`Invalid page shape for slug "${item.slug}": ${issues}`);
79
+ }
80
+ return {
81
+ slug: item.slug,
82
+ title: parsed.data.title,
83
+ body: parsed.data.body,
84
+ category: parsed.data.category,
85
+ order: parsed.data.order,
86
+ locale: item.locale
87
+ };
88
+ }
89
+ function sortPages(pages) {
90
+ return [...pages].sort((a, b) => {
91
+ if (a.order === b.order) {
92
+ return a.slug.localeCompare(b.slug);
93
+ }
94
+ return a.order - b.order;
95
+ });
96
+ }
97
+ function buildDocument(page, config, locale) {
98
+ return {
99
+ id: `${locale}:${page.slug}`,
100
+ locale,
101
+ slug: page.slug,
102
+ title: page.title,
103
+ path: toDocsPagePath(config, locale, page.slug),
104
+ order: page.order,
105
+ plainText: markdownToSearchText(page.body)
106
+ };
107
+ }
108
+ function toStableJson(value) {
109
+ return JSON.stringify(value);
110
+ }
111
+ function buildIndexFileName(locale, payloadJson) {
112
+ const hash = createHash("sha256").update(payloadJson).digest("hex").slice(0, 12);
113
+ const fileName = `index.${sanitizeLocaleForFileName(locale)}.${hash}.json`;
114
+ return { fileName, hash };
115
+ }
116
+ function createLocaleClient(config, locale) {
117
+ return createClient({
118
+ baseUrl: config.supatent.baseUrl,
119
+ projectSlug: config.supatent.projectSlug,
120
+ apiKey: config.supatent.apiKey,
121
+ preview: false,
122
+ locale,
123
+ cache: config.supatent.cache
124
+ });
125
+ }
126
+ async function fetchRuntimeLocales(config) {
127
+ try {
128
+ const response = await createLocaleClient(config).getLocales();
129
+ const locales = normalizeLocales(response.locales.map((entry) => entry.code));
130
+ if (locales.length === 0) {
131
+ throw new Error("Supatent returned no locales.");
132
+ }
133
+ const defaultLocale = locales.includes(response.default) ? response.default : locales[0];
134
+ return {
135
+ defaultLocale,
136
+ locales
137
+ };
138
+ } catch {
139
+ return resolveConfiguredRuntimeLocales(config);
140
+ }
141
+ }
142
+ async function fetchPublishedPagesForLocale(config, locale) {
143
+ const items = await createLocaleClient(config, locale).schema(config.schemas.page).all();
144
+ const published = items.filter((item) => item.publishedAt !== null);
145
+ return sortPages(published.map(normalizePage));
146
+ }
147
+ async function clearGeneratedIndexFiles(outputDir) {
148
+ const entries = await readdir(outputDir, { withFileTypes: true });
149
+ await Promise.all(
150
+ entries.filter(
151
+ (entry) => entry.isFile() && (entry.name === MANIFEST_FILENAME || entry.name.startsWith("index.") && entry.name.endsWith(".json"))
152
+ ).map((entry) => unlink(path.join(outputDir, entry.name)))
153
+ );
154
+ }
155
+ function createLocaleSearchIndexPayload(locale, pages, config, generatedAt) {
156
+ return {
157
+ locale,
158
+ generatedAt,
159
+ documents: pages.map((page) => buildDocument(page, config, locale))
160
+ };
161
+ }
162
+ async function generateStaticSearchIndexes(options) {
163
+ const { config, logger } = options;
164
+ const cwd = options.cwd ?? process.cwd();
165
+ const outputDir = path.resolve(cwd, options.outputDir ?? DEFAULT_SEARCH_INDEX_OUTPUT_DIR);
166
+ await mkdir(outputDir, { recursive: true });
167
+ await clearGeneratedIndexFiles(outputDir);
168
+ const runtimeLocales = await fetchRuntimeLocales(config);
169
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
170
+ const manifestEntries = [];
171
+ for (const locale of runtimeLocales.locales) {
172
+ const pages = await fetchPublishedPagesForLocale(config, locale);
173
+ const payload = createLocaleSearchIndexPayload(locale, pages, config, generatedAt);
174
+ const payloadJson = toStableJson(payload);
175
+ const { fileName, hash } = buildIndexFileName(locale, payloadJson);
176
+ await writeFile(path.join(outputDir, fileName), payloadJson, "utf8");
177
+ manifestEntries.push({
178
+ locale,
179
+ file: fileName,
180
+ hash,
181
+ documentCount: payload.documents.length
182
+ });
183
+ logger?.(
184
+ `Built search index locale=${locale} docs=${payload.documents.length} file=${fileName}`
185
+ );
186
+ }
187
+ const manifest = {
188
+ version: 1,
189
+ generatedAt,
190
+ defaultLocale: runtimeLocales.defaultLocale,
191
+ basePath: config.routing.basePath,
192
+ outputDir: options.outputDir ?? DEFAULT_SEARCH_INDEX_OUTPUT_DIR,
193
+ entries: manifestEntries
194
+ };
195
+ const manifestPath = path.join(outputDir, MANIFEST_FILENAME);
196
+ await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
197
+ `, "utf8");
198
+ logger?.(`Wrote search index manifest: ${manifestPath}`);
199
+ return {
200
+ outputDir,
201
+ manifestPath,
202
+ manifest
203
+ };
204
+ }
205
+
206
+ // runtime/src/framework/next-config.ts
207
+ var buildPromise = null;
208
+ function shouldBuildIndex(phase, options) {
209
+ if (phase !== PHASE_PRODUCTION_BUILD) {
210
+ return false;
211
+ }
212
+ if (options?.enabled === false) {
213
+ return false;
214
+ }
215
+ if (process.env.SUPATENT_DOCS_SKIP_INDEX_BUILD === "1") {
216
+ return false;
217
+ }
218
+ return true;
219
+ }
220
+ async function resolveConfig(input, phase, context) {
221
+ if (typeof input === "function") {
222
+ return input(phase, context);
223
+ }
224
+ return input;
225
+ }
226
+ async function ensureSearchIndexBuilt(options) {
227
+ if (!buildPromise) {
228
+ const searchIndexOptions = options.searchIndex ?? {};
229
+ const docsConfig = options.docsConfig ?? resolveDocsConfigFromEnv();
230
+ buildPromise = generateStaticSearchIndexes({
231
+ config: docsConfig,
232
+ cwd: searchIndexOptions.cwd,
233
+ outputDir: searchIndexOptions.outputDir ?? DEFAULT_SEARCH_INDEX_OUTPUT_DIR,
234
+ logger: (message) => {
235
+ console.log(`[supatent-docs] ${message}`);
236
+ }
237
+ }).then(() => void 0);
238
+ }
239
+ return buildPromise;
240
+ }
241
+ function withSupatentDocs(input = {}, options = {}) {
242
+ return async (phase, context) => {
243
+ if (shouldBuildIndex(phase, options.searchIndex)) {
244
+ await ensureSearchIndexBuilt(options);
245
+ }
246
+ return resolveConfig(input, phase, context);
247
+ };
248
+ }
249
+ function requireEnv(name) {
250
+ const value = process.env[name];
251
+ if (!value || value.trim().length === 0) {
252
+ throw new Error(`Missing required environment variable: ${name}`);
253
+ }
254
+ return value.trim();
255
+ }
256
+ function parseNonNegativeInteger(value, fallback) {
257
+ if (!value) {
258
+ return fallback;
259
+ }
260
+ const parsed = Number(value);
261
+ if (!Number.isFinite(parsed) || parsed < 0) {
262
+ return fallback;
263
+ }
264
+ return Math.trunc(parsed);
265
+ }
266
+ function parseLocales(value, fallback) {
267
+ if (!value) {
268
+ return [fallback];
269
+ }
270
+ const locales = value.split(",").map((entry) => entry.trim()).filter(Boolean);
271
+ return locales.length > 0 ? locales : [fallback];
272
+ }
273
+ function resolveDocsConfigFromEnv() {
274
+ const defaultLocale = process.env.DOCS_DEFAULT_LOCALE?.trim() || "en";
275
+ const apiKey = process.env.SUPATENT_API_KEY;
276
+ return {
277
+ supatent: {
278
+ baseUrl: process.env.SUPATENT_BASE_URL || "https://app.supatent.ai",
279
+ projectSlug: requireEnv("SUPATENT_PROJECT_SLUG"),
280
+ apiKey,
281
+ dataMode: "published",
282
+ cache: {
283
+ maxAge: parseNonNegativeInteger(process.env.DOCS_CACHE_MAX_AGE, 60),
284
+ staleWhileRevalidate: parseNonNegativeInteger(
285
+ process.env.DOCS_CACHE_STALE_WHILE_REVALIDATE,
286
+ 300
287
+ )
288
+ }
289
+ },
290
+ schemas: {
291
+ settings: requireEnv("DOCS_SCHEMA_SETTINGS"),
292
+ page: requireEnv("DOCS_SCHEMA_PAGE"),
293
+ category: requireEnv("DOCS_SCHEMA_CATEGORY")
294
+ },
295
+ routing: {
296
+ basePath: process.env.DOCS_BASE_PATH || "/docs",
297
+ defaultLocale,
298
+ locales: parseLocales(process.env.DOCS_LOCALES, defaultLocale),
299
+ fallbackMode: process.env.DOCS_FALLBACK_MODE === "none" ? "none" : "defaultLocale"
300
+ }
301
+ };
302
+ }
303
+ export {
304
+ withSupatentDocs
305
+ };
@@ -0,0 +1,60 @@
1
+ type LocaleFallbackMode = "none" | "defaultLocale";
2
+ type DocsDataMode = "published" | "draft";
3
+ type DocsFrameworkConfig = {
4
+ supatent: {
5
+ baseUrl: string;
6
+ apiKey?: string;
7
+ dataMode: DocsDataMode;
8
+ projectSlug: string;
9
+ cache: {
10
+ maxAge: number;
11
+ staleWhileRevalidate: number;
12
+ };
13
+ };
14
+ schemas: {
15
+ settings: string;
16
+ page: string;
17
+ category: string;
18
+ };
19
+ routing: {
20
+ basePath: string;
21
+ defaultLocale: string;
22
+ locales: string[];
23
+ fallbackMode: LocaleFallbackMode;
24
+ };
25
+ };
26
+ type DocsPage = {
27
+ slug: string;
28
+ title: string;
29
+ body: string;
30
+ category: string;
31
+ order: number;
32
+ locale: string;
33
+ };
34
+ type DocsCategory = {
35
+ slug: string;
36
+ title: string;
37
+ order: number;
38
+ locale: string;
39
+ };
40
+ type DocsNavNode = {
41
+ id: string;
42
+ title: string;
43
+ slug: string | null;
44
+ path: string | null;
45
+ order: number;
46
+ children: DocsNavNode[];
47
+ };
48
+ type DocsPageResolution = {
49
+ page: DocsPage;
50
+ requestedLocale: string;
51
+ resolvedLocale: string;
52
+ usedFallback: boolean;
53
+ };
54
+ type DocsLocaleRuntimeConfig = {
55
+ defaultLocale: string;
56
+ locales: string[];
57
+ fallbackMode: LocaleFallbackMode;
58
+ };
59
+
60
+ export type { DocsFrameworkConfig as D, LocaleFallbackMode as L, DocsCategory as a, DocsDataMode as b, DocsLocaleRuntimeConfig as c, DocsNavNode as d, DocsPage as e, DocsPageResolution as f };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@supatent/supatent-docs",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "src",
11
+ "runtime"
12
+ ],
13
+ "scripts": {
14
+ "prepare:runtime": "node scripts/prepare-runtime.mjs",
15
+ "build": "pnpm run prepare:runtime && tsup --config tsup.config.ts",
16
+ "clean": "rm -rf dist",
17
+ "prepack": "pnpm run build"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./next": {
26
+ "types": "./src/next.ts",
27
+ "import": "./src/next.ts",
28
+ "default": "./src/next.ts"
29
+ },
30
+ "./next-config": {
31
+ "types": "./dist/next-config.d.ts",
32
+ "import": "./dist/next-config.js",
33
+ "default": "./dist/next-config.js"
34
+ },
35
+ "./styles.css": "./runtime/src/app/globals.css",
36
+ "./package.json": "./package.json"
37
+ },
38
+ "dependencies": {
39
+ "@supatent/client": "^0.3.2"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "peerDependencies": {
45
+ "next": "^16.1.6",
46
+ "react": "^19.2.4",
47
+ "react-dom": "^19.2.4"
48
+ }
49
+ }
@@ -0,0 +1,52 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { NextResponse } from "next/server";
3
+ import { docsConfig } from "./src/docs.config";
4
+ import { INTERNAL_DOCS_SEGMENT, toInternalDocsPath } from "./src/framework/url";
5
+
6
+ const INTERNAL_PREFIX = `/${INTERNAL_DOCS_SEGMENT}`;
7
+
8
+ function getLocaleFromPathname(pathname: string): string | null {
9
+ const firstSegment = pathname.split("/").filter(Boolean)[0];
10
+ return firstSegment || null;
11
+ }
12
+
13
+ export function middleware(request: NextRequest) {
14
+ const { pathname } = request.nextUrl;
15
+ const requestHeaders = new Headers(request.headers);
16
+ const locale = getLocaleFromPathname(pathname);
17
+
18
+ if (locale) {
19
+ requestHeaders.set("x-docs-request-locale", locale);
20
+ } else {
21
+ requestHeaders.delete("x-docs-request-locale");
22
+ }
23
+
24
+ if (pathname.includes(INTERNAL_PREFIX)) {
25
+ return NextResponse.next({
26
+ request: {
27
+ headers: requestHeaders,
28
+ },
29
+ });
30
+ }
31
+
32
+ const internalPath = toInternalDocsPath(pathname, docsConfig);
33
+ if (!internalPath) {
34
+ return NextResponse.next({
35
+ request: {
36
+ headers: requestHeaders,
37
+ },
38
+ });
39
+ }
40
+
41
+ const rewrittenUrl = request.nextUrl.clone();
42
+ rewrittenUrl.pathname = internalPath;
43
+ return NextResponse.rewrite(rewrittenUrl, {
44
+ request: {
45
+ headers: requestHeaders,
46
+ },
47
+ });
48
+ }
49
+
50
+ export const config = {
51
+ matcher: ["/((?!_next|api|favicon.ico|robots.txt|sitemap.xml).*)"],
52
+ };
@@ -0,0 +1,91 @@
1
+ import { notFound } from "next/navigation";
2
+ import { DocsShell } from "../../../../components/docs-shell";
3
+ import { docsConfig } from "../../../../docs.config";
4
+ import { isSupportedLocale } from "../../../../framework/locales";
5
+ import { renderMarkdownToHtml } from "../../../../framework/rendering";
6
+ import { createDocsService } from "../../../../framework/service";
7
+ import { getRuntimeLocales } from "../../../../framework/runtime-locales";
8
+
9
+ type RouteParams = {
10
+ locale: string;
11
+ slug?: string[];
12
+ };
13
+
14
+ export const dynamic = "force-dynamic";
15
+
16
+ function slugify(value: string): string {
17
+ return value
18
+ .toLowerCase()
19
+ .replace(/[`*_~()[\]]/g, "")
20
+ .replace(/[^\w\s-]/g, "")
21
+ .trim()
22
+ .replace(/\s+/g, "-");
23
+ }
24
+
25
+ function extractToc(markdown: string): Array<{ id: string; label: string; depth: number }> {
26
+ const items = [{ id: "overview", label: "Overview", depth: 1 }];
27
+
28
+ for (const line of markdown.split("\n")) {
29
+ const match = line.match(/^(##|###)\s+(.+)$/);
30
+ if (!match) continue;
31
+
32
+ items.push({
33
+ id: slugify(match[2]),
34
+ label: match[2].trim(),
35
+ depth: match[1].length,
36
+ });
37
+ }
38
+
39
+ return items;
40
+ }
41
+
42
+ export default async function DocsPage({
43
+ params,
44
+ }: {
45
+ params: Promise<RouteParams> | RouteParams;
46
+ }) {
47
+ const resolvedParams = await Promise.resolve(params);
48
+ const locale = resolvedParams.locale;
49
+ const runtimeLocales = await getRuntimeLocales({
50
+ dataMode: docsConfig.supatent.dataMode,
51
+ });
52
+ const localeConfig = {
53
+ defaultLocale: runtimeLocales.defaultLocale,
54
+ locales: runtimeLocales.locales,
55
+ fallbackMode: docsConfig.routing.fallbackMode,
56
+ };
57
+ const docsService = createDocsService(docsConfig, {
58
+ dataMode: docsConfig.supatent.dataMode,
59
+ localeConfig,
60
+ });
61
+
62
+ if (!isSupportedLocale(locale, localeConfig)) {
63
+ notFound();
64
+ }
65
+
66
+ const slug = resolvedParams.slug?.join("/") ?? "";
67
+
68
+ const [pageResult, navResult] = await Promise.all([
69
+ docsService.getPage(locale, slug),
70
+ docsService.getNavigation(locale),
71
+ ]);
72
+
73
+ if (!pageResult || !navResult) {
74
+ notFound();
75
+ }
76
+
77
+ const pageHtml = await renderMarkdownToHtml(pageResult.page.body);
78
+ const tocItems = extractToc(pageResult.page.body);
79
+
80
+ return (
81
+ <DocsShell
82
+ pageHtml={pageHtml}
83
+ navigation={navResult.navigation}
84
+ activeSlug={pageResult.page.slug}
85
+ locales={runtimeLocales.locales}
86
+ defaultLocale={runtimeLocales.defaultLocale}
87
+ basePath={docsConfig.routing.basePath}
88
+ tocItems={tocItems}
89
+ />
90
+ );
91
+ }