@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.
- package/dist/chunk-QVZFIUSH.js +13777 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +75 -0
- package/dist/next-config.d.ts +20 -0
- package/dist/next-config.js +305 -0
- package/dist/types-Cf4pRODK.d.ts +60 -0
- package/package.json +49 -0
- package/runtime/middleware.ts +52 -0
- package/runtime/src/app/[locale]/docs-internal/[[...slug]]/page.tsx +91 -0
- package/runtime/src/app/[locale]/docs-internal/ai/all/route.ts +52 -0
- package/runtime/src/app/[locale]/docs-internal/ai/index/route.ts +52 -0
- package/runtime/src/app/[locale]/docs-internal/markdown/[...slug]/route.ts +50 -0
- package/runtime/src/app/globals.css +1082 -0
- package/runtime/src/app/layout.tsx +37 -0
- package/runtime/src/app/page.tsx +26 -0
- package/runtime/src/components/code-group-enhancer.tsx +128 -0
- package/runtime/src/components/docs-shell.tsx +140 -0
- package/runtime/src/components/header-dropdown.tsx +138 -0
- package/runtime/src/components/icons.tsx +58 -0
- package/runtime/src/components/locale-switcher.tsx +97 -0
- package/runtime/src/components/mobile-docs-menu.tsx +208 -0
- package/runtime/src/components/site-header.tsx +44 -0
- package/runtime/src/components/site-search.tsx +358 -0
- package/runtime/src/components/theme-toggle.tsx +74 -0
- package/runtime/src/docs.config.ts +91 -0
- package/runtime/src/framework/config.ts +76 -0
- package/runtime/src/framework/errors.ts +34 -0
- package/runtime/src/framework/locales.ts +45 -0
- package/runtime/src/framework/markdown-search-text.ts +11 -0
- package/runtime/src/framework/navigation.ts +58 -0
- package/runtime/src/framework/next-config.ts +160 -0
- package/runtime/src/framework/rendering.ts +445 -0
- package/runtime/src/framework/repository.ts +255 -0
- package/runtime/src/framework/runtime-locales.ts +85 -0
- package/runtime/src/framework/search-index-types.ts +34 -0
- package/runtime/src/framework/search-index.ts +271 -0
- package/runtime/src/framework/service.ts +302 -0
- package/runtime/src/framework/settings.ts +43 -0
- package/runtime/src/framework/site-title.ts +54 -0
- package/runtime/src/framework/theme.ts +17 -0
- package/runtime/src/framework/types.ts +66 -0
- package/runtime/src/framework/url.ts +78 -0
- package/runtime/src/supatent/client.ts +2 -0
- package/src/index.ts +11 -0
- package/src/next-config.ts +5 -0
- package/src/next.ts +10 -0
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|