@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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { defineDocsConfig } from "./framework/config";
|
|
2
|
+
import type { DocsDataMode } from "./framework/types";
|
|
3
|
+
|
|
4
|
+
function requiredEnv(name: string): string {
|
|
5
|
+
const value = process.env[name];
|
|
6
|
+
if (!value) {
|
|
7
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function resolveDataMode(
|
|
14
|
+
value: string | undefined,
|
|
15
|
+
apiKey: string | undefined,
|
|
16
|
+
): DocsDataMode {
|
|
17
|
+
if (value === "draft" || value === "published") {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (process.env.NODE_ENV !== "production" && apiKey) {
|
|
22
|
+
return "draft";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return "published";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseLocales(value: string | undefined, defaultLocale: string): string[] {
|
|
29
|
+
if (!value) {
|
|
30
|
+
return [defaultLocale];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const locales = value
|
|
34
|
+
.split(",")
|
|
35
|
+
.map((entry) => entry.trim())
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
|
|
38
|
+
if (locales.length === 0) {
|
|
39
|
+
return [defaultLocale];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return locales;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseNonNegativeInteger(
|
|
46
|
+
value: string | undefined,
|
|
47
|
+
fallback: number,
|
|
48
|
+
): number {
|
|
49
|
+
if (!value) {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const parsed = Number(value);
|
|
54
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
55
|
+
return fallback;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return Math.trunc(parsed);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const defaultLocale = process.env.DOCS_DEFAULT_LOCALE?.trim() || "en";
|
|
62
|
+
const fallbackMode = process.env.DOCS_FALLBACK_MODE === "none" ? "none" : "defaultLocale";
|
|
63
|
+
const apiKey = process.env.SUPATENT_API_KEY;
|
|
64
|
+
const dataMode = resolveDataMode(process.env.DOCS_DATA_MODE, apiKey);
|
|
65
|
+
|
|
66
|
+
export const docsConfig = defineDocsConfig({
|
|
67
|
+
supatent: {
|
|
68
|
+
baseUrl: process.env.SUPATENT_BASE_URL || "https://app.supatent.ai",
|
|
69
|
+
apiKey,
|
|
70
|
+
dataMode,
|
|
71
|
+
projectSlug: requiredEnv("SUPATENT_PROJECT_SLUG"),
|
|
72
|
+
cache: {
|
|
73
|
+
maxAge: parseNonNegativeInteger(process.env.DOCS_CACHE_MAX_AGE, 60),
|
|
74
|
+
staleWhileRevalidate: parseNonNegativeInteger(
|
|
75
|
+
process.env.DOCS_CACHE_STALE_WHILE_REVALIDATE,
|
|
76
|
+
300,
|
|
77
|
+
),
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
schemas: {
|
|
81
|
+
settings: requiredEnv("DOCS_SCHEMA_SETTINGS"),
|
|
82
|
+
page: requiredEnv("DOCS_SCHEMA_PAGE"),
|
|
83
|
+
category: requiredEnv("DOCS_SCHEMA_CATEGORY"),
|
|
84
|
+
},
|
|
85
|
+
routing: {
|
|
86
|
+
basePath: process.env.DOCS_BASE_PATH || "/docs",
|
|
87
|
+
defaultLocale,
|
|
88
|
+
locales: parseLocales(process.env.DOCS_LOCALES, defaultLocale),
|
|
89
|
+
fallbackMode,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { DocsFrameworkConfig } from "./types";
|
|
3
|
+
import { DocsFrameworkError } from "./errors";
|
|
4
|
+
|
|
5
|
+
const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
6
|
+
|
|
7
|
+
function normalizeBasePath(input: string): string {
|
|
8
|
+
const trimmed = input.trim();
|
|
9
|
+
if (!trimmed) {
|
|
10
|
+
return "/docs";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
14
|
+
return withLeadingSlash.replace(/\/+$/, "") || "/docs";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const configSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
supatent: z.object({
|
|
20
|
+
baseUrl: z.string().url(),
|
|
21
|
+
apiKey: z.string().min(1, "supatent.apiKey must not be empty").optional(),
|
|
22
|
+
dataMode: z.enum(["published", "draft"]).default("published"),
|
|
23
|
+
projectSlug: z
|
|
24
|
+
.string()
|
|
25
|
+
.regex(slugPattern, "supatent.projectSlug must be a valid slug"),
|
|
26
|
+
cache: z.object({
|
|
27
|
+
maxAge: z.number().int().nonnegative(),
|
|
28
|
+
staleWhileRevalidate: z.number().int().nonnegative(),
|
|
29
|
+
}),
|
|
30
|
+
}),
|
|
31
|
+
schemas: z.object({
|
|
32
|
+
settings: z
|
|
33
|
+
.string()
|
|
34
|
+
.regex(slugPattern, "schemas.settings must be a valid slug"),
|
|
35
|
+
page: z.string().regex(slugPattern, "schemas.page must be a valid slug"),
|
|
36
|
+
category: z
|
|
37
|
+
.string()
|
|
38
|
+
.regex(slugPattern, "schemas.category must be a valid slug"),
|
|
39
|
+
}),
|
|
40
|
+
routing: z.object({
|
|
41
|
+
basePath: z.string().transform(normalizeBasePath),
|
|
42
|
+
defaultLocale: z.string().min(2),
|
|
43
|
+
locales: z.array(z.string().min(2)).min(1),
|
|
44
|
+
fallbackMode: z.enum(["none", "defaultLocale"]).default("defaultLocale"),
|
|
45
|
+
}),
|
|
46
|
+
})
|
|
47
|
+
.superRefine((value, ctx) => {
|
|
48
|
+
if (value.supatent.dataMode === "draft" && !value.supatent.apiKey) {
|
|
49
|
+
ctx.addIssue({
|
|
50
|
+
code: z.ZodIssueCode.custom,
|
|
51
|
+
message: "supatent.apiKey is required when supatent.dataMode is \"draft\"",
|
|
52
|
+
path: ["supatent", "apiKey"],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!value.routing.locales.includes(value.routing.defaultLocale)) {
|
|
57
|
+
ctx.addIssue({
|
|
58
|
+
code: z.ZodIssueCode.custom,
|
|
59
|
+
message: "routing.defaultLocale must be included in routing.locales",
|
|
60
|
+
path: ["routing", "defaultLocale"],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export function defineDocsConfig(input: unknown): DocsFrameworkConfig {
|
|
66
|
+
const parsed = configSchema.safeParse(input);
|
|
67
|
+
|
|
68
|
+
if (!parsed.success) {
|
|
69
|
+
const issueList = parsed.error.issues
|
|
70
|
+
.map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`)
|
|
71
|
+
.join("; ");
|
|
72
|
+
throw new DocsFrameworkError("CONFIG_ERROR", `Invalid docs config: ${issueList}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return parsed.data;
|
|
76
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type DocsErrorCode =
|
|
2
|
+
| "CONFIG_ERROR"
|
|
3
|
+
| "DATA_ERROR"
|
|
4
|
+
| "NOT_FOUND"
|
|
5
|
+
| "UPSTREAM_ERROR";
|
|
6
|
+
|
|
7
|
+
export class DocsFrameworkError extends Error {
|
|
8
|
+
public readonly code: DocsErrorCode;
|
|
9
|
+
|
|
10
|
+
public readonly causeError?: unknown;
|
|
11
|
+
|
|
12
|
+
constructor(code: DocsErrorCode, message: string, causeError?: unknown) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "DocsFrameworkError";
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.causeError = causeError;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function toDocsFrameworkError(
|
|
21
|
+
error: unknown,
|
|
22
|
+
defaultCode: DocsErrorCode = "UPSTREAM_ERROR",
|
|
23
|
+
): DocsFrameworkError {
|
|
24
|
+
if (error instanceof DocsFrameworkError) {
|
|
25
|
+
return error;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (error instanceof Error) {
|
|
29
|
+
return new DocsFrameworkError(defaultCode, error.message, error);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return new DocsFrameworkError(defaultCode, "Unknown docs framework error", error);
|
|
33
|
+
}
|
|
34
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DocsFrameworkConfig, DocsLocaleRuntimeConfig } from "./types";
|
|
2
|
+
|
|
3
|
+
type LocaleConfigLike =
|
|
4
|
+
| DocsFrameworkConfig
|
|
5
|
+
| {
|
|
6
|
+
routing: DocsLocaleRuntimeConfig;
|
|
7
|
+
}
|
|
8
|
+
| DocsLocaleRuntimeConfig;
|
|
9
|
+
|
|
10
|
+
function toLocaleConfig(config: LocaleConfigLike): DocsLocaleRuntimeConfig {
|
|
11
|
+
if ("routing" in config) {
|
|
12
|
+
return config.routing;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return config;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isSupportedLocale(
|
|
19
|
+
locale: string,
|
|
20
|
+
config: LocaleConfigLike,
|
|
21
|
+
): boolean {
|
|
22
|
+
return toLocaleConfig(config).locales.includes(locale);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveLocaleCandidates(
|
|
26
|
+
requestedLocale: string,
|
|
27
|
+
config: LocaleConfigLike,
|
|
28
|
+
): string[] {
|
|
29
|
+
const localeConfig = toLocaleConfig(config);
|
|
30
|
+
const candidates: string[] = [];
|
|
31
|
+
|
|
32
|
+
if (isSupportedLocale(requestedLocale, localeConfig)) {
|
|
33
|
+
candidates.push(requestedLocale);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
localeConfig.fallbackMode === "defaultLocale" &&
|
|
38
|
+
localeConfig.defaultLocale !== requestedLocale &&
|
|
39
|
+
isSupportedLocale(localeConfig.defaultLocale, localeConfig)
|
|
40
|
+
) {
|
|
41
|
+
candidates.push(localeConfig.defaultLocale);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return candidates;
|
|
45
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function markdownToSearchText(markdown: string): string {
|
|
2
|
+
return markdown
|
|
3
|
+
.replace(/^:::[^\n]*$/gm, " ")
|
|
4
|
+
.replace(/^(```|~~~)[^\n]*$/gm, " ")
|
|
5
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
6
|
+
.replace(/!\[[^\]]*]\([^)]*\)/g, " ")
|
|
7
|
+
.replace(/\[([^\]]+)]\(([^)]+)\)/g, "$1")
|
|
8
|
+
.replace(/[#>*_-]+/g, " ")
|
|
9
|
+
.replace(/\s+/g, " ")
|
|
10
|
+
.trim();
|
|
11
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DocsCategory,
|
|
3
|
+
DocsFrameworkConfig,
|
|
4
|
+
DocsNavNode,
|
|
5
|
+
DocsPage,
|
|
6
|
+
} from "./types";
|
|
7
|
+
import { toDocsPagePath } from "./url";
|
|
8
|
+
|
|
9
|
+
function sortPages(pages: DocsPage[]): DocsPage[] {
|
|
10
|
+
return [...pages].sort((a, b) => {
|
|
11
|
+
if (a.order === b.order) {
|
|
12
|
+
return a.slug.localeCompare(b.slug);
|
|
13
|
+
}
|
|
14
|
+
return a.order - b.order;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function sortCategories(categories: DocsCategory[]): DocsCategory[] {
|
|
19
|
+
return [...categories].sort((a, b) => {
|
|
20
|
+
if (a.order === b.order) {
|
|
21
|
+
return a.slug.localeCompare(b.slug);
|
|
22
|
+
}
|
|
23
|
+
return a.order - b.order;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function buildNavigationTree(
|
|
28
|
+
pages: DocsPage[],
|
|
29
|
+
categories: DocsCategory[],
|
|
30
|
+
locale: string,
|
|
31
|
+
config: DocsFrameworkConfig,
|
|
32
|
+
): DocsNavNode[] {
|
|
33
|
+
const pagesByCategory = new Map<string, DocsPage[]>();
|
|
34
|
+
for (const page of sortPages(pages)) {
|
|
35
|
+
const list = pagesByCategory.get(page.category);
|
|
36
|
+
if (list) {
|
|
37
|
+
list.push(page);
|
|
38
|
+
} else {
|
|
39
|
+
pagesByCategory.set(page.category, [page]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return sortCategories(categories).map((category) => ({
|
|
44
|
+
id: `category:${category.slug}`,
|
|
45
|
+
title: category.title,
|
|
46
|
+
slug: null,
|
|
47
|
+
path: null,
|
|
48
|
+
order: category.order,
|
|
49
|
+
children: (pagesByCategory.get(category.slug) ?? []).map((page) => ({
|
|
50
|
+
id: `page:${page.slug}`,
|
|
51
|
+
title: page.title,
|
|
52
|
+
slug: page.slug,
|
|
53
|
+
path: toDocsPagePath(config, locale, page.slug),
|
|
54
|
+
order: page.order,
|
|
55
|
+
children: [],
|
|
56
|
+
})),
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
import { PHASE_PRODUCTION_BUILD } from "next/constants.js";
|
|
3
|
+
import type { DocsFrameworkConfig } from "./types";
|
|
4
|
+
import { generateStaticSearchIndexes } from "./search-index";
|
|
5
|
+
import { DEFAULT_SEARCH_INDEX_OUTPUT_DIR } from "./search-index-types";
|
|
6
|
+
|
|
7
|
+
type NextConfigContext = {
|
|
8
|
+
defaultConfig: NextConfig;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type NextConfigFactory = (
|
|
12
|
+
phase: string,
|
|
13
|
+
context: NextConfigContext,
|
|
14
|
+
) => NextConfig | Promise<NextConfig>;
|
|
15
|
+
|
|
16
|
+
type NextConfigInput = NextConfig | NextConfigFactory;
|
|
17
|
+
|
|
18
|
+
export type SupatentDocsSearchIndexOptions = {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
outputDir?: string;
|
|
21
|
+
cwd?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type SupatentDocsNextConfigOptions = {
|
|
25
|
+
docsConfig?: DocsFrameworkConfig;
|
|
26
|
+
searchIndex?: SupatentDocsSearchIndexOptions;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let buildPromise: Promise<void> | null = null;
|
|
30
|
+
|
|
31
|
+
function shouldBuildIndex(
|
|
32
|
+
phase: string,
|
|
33
|
+
options: SupatentDocsSearchIndexOptions | undefined,
|
|
34
|
+
): boolean {
|
|
35
|
+
if (phase !== PHASE_PRODUCTION_BUILD) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (options?.enabled === false) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (process.env.SUPATENT_DOCS_SKIP_INDEX_BUILD === "1") {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function resolveConfig(
|
|
51
|
+
input: NextConfigInput,
|
|
52
|
+
phase: string,
|
|
53
|
+
context: NextConfigContext,
|
|
54
|
+
): Promise<NextConfig> {
|
|
55
|
+
if (typeof input === "function") {
|
|
56
|
+
return input(phase, context);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return input;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function ensureSearchIndexBuilt(
|
|
63
|
+
options: SupatentDocsNextConfigOptions,
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
if (!buildPromise) {
|
|
66
|
+
const searchIndexOptions = options.searchIndex ?? {};
|
|
67
|
+
const docsConfig = options.docsConfig ?? resolveDocsConfigFromEnv();
|
|
68
|
+
buildPromise = generateStaticSearchIndexes({
|
|
69
|
+
config: docsConfig,
|
|
70
|
+
cwd: searchIndexOptions.cwd,
|
|
71
|
+
outputDir: searchIndexOptions.outputDir ?? DEFAULT_SEARCH_INDEX_OUTPUT_DIR,
|
|
72
|
+
logger: (message) => {
|
|
73
|
+
// eslint-disable-next-line no-console
|
|
74
|
+
console.log(`[supatent-docs] ${message}`);
|
|
75
|
+
},
|
|
76
|
+
}).then(() => undefined);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return buildPromise;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function withSupatentDocs(
|
|
83
|
+
input: NextConfigInput = {},
|
|
84
|
+
options: SupatentDocsNextConfigOptions = {},
|
|
85
|
+
): NextConfigFactory {
|
|
86
|
+
return async (phase, context) => {
|
|
87
|
+
if (shouldBuildIndex(phase, options.searchIndex)) {
|
|
88
|
+
await ensureSearchIndexBuilt(options);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return resolveConfig(input, phase, context);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function requireEnv(name: string): string {
|
|
96
|
+
const value = process.env[name];
|
|
97
|
+
if (!value || value.trim().length === 0) {
|
|
98
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return value.trim();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseNonNegativeInteger(value: string | undefined, fallback: number): number {
|
|
105
|
+
if (!value) {
|
|
106
|
+
return fallback;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const parsed = Number(value);
|
|
110
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
111
|
+
return fallback;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return Math.trunc(parsed);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseLocales(value: string | undefined, fallback: string): string[] {
|
|
118
|
+
if (!value) {
|
|
119
|
+
return [fallback];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const locales = value
|
|
123
|
+
.split(",")
|
|
124
|
+
.map((entry) => entry.trim())
|
|
125
|
+
.filter(Boolean);
|
|
126
|
+
|
|
127
|
+
return locales.length > 0 ? locales : [fallback];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function resolveDocsConfigFromEnv(): DocsFrameworkConfig {
|
|
131
|
+
const defaultLocale = process.env.DOCS_DEFAULT_LOCALE?.trim() || "en";
|
|
132
|
+
const apiKey = process.env.SUPATENT_API_KEY;
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
supatent: {
|
|
136
|
+
baseUrl: process.env.SUPATENT_BASE_URL || "https://app.supatent.ai",
|
|
137
|
+
projectSlug: requireEnv("SUPATENT_PROJECT_SLUG"),
|
|
138
|
+
apiKey,
|
|
139
|
+
dataMode: "published",
|
|
140
|
+
cache: {
|
|
141
|
+
maxAge: parseNonNegativeInteger(process.env.DOCS_CACHE_MAX_AGE, 60),
|
|
142
|
+
staleWhileRevalidate: parseNonNegativeInteger(
|
|
143
|
+
process.env.DOCS_CACHE_STALE_WHILE_REVALIDATE,
|
|
144
|
+
300,
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
schemas: {
|
|
149
|
+
settings: requireEnv("DOCS_SCHEMA_SETTINGS"),
|
|
150
|
+
page: requireEnv("DOCS_SCHEMA_PAGE"),
|
|
151
|
+
category: requireEnv("DOCS_SCHEMA_CATEGORY"),
|
|
152
|
+
},
|
|
153
|
+
routing: {
|
|
154
|
+
basePath: process.env.DOCS_BASE_PATH || "/docs",
|
|
155
|
+
defaultLocale,
|
|
156
|
+
locales: parseLocales(process.env.DOCS_LOCALES, defaultLocale),
|
|
157
|
+
fallbackMode: process.env.DOCS_FALLBACK_MODE === "none" ? "none" : "defaultLocale",
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|