@aravindc26/velu 0.11.0 → 0.11.3
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/package.json +15 -6
- package/schema/velu.schema.json +1251 -115
- package/src/build.ts +1121 -304
- package/src/cli.ts +90 -26
- package/src/engine/_server.mjs +1684 -277
- package/src/engine/app/(docs)/[...slug]/layout.tsx +371 -0
- package/src/engine/app/(docs)/[...slug]/page.tsx +926 -0
- package/src/engine/app/api/proxy/route.ts +23 -0
- package/src/engine/app/copy-page.css +59 -1
- package/src/engine/app/global.css +3157 -3
- package/src/engine/app/layout.tsx +56 -1
- package/src/engine/app/llms-file/route.ts +87 -0
- package/src/engine/app/llms-full-file/route.ts +62 -0
- package/src/engine/app/md-file/[...slug]/route.ts +409 -0
- package/src/engine/app/page.tsx +45 -0
- package/src/engine/app/robots.txt/route.ts +63 -0
- package/src/engine/app/rss-file/[...slug]/route.ts +169 -0
- package/src/engine/app/sitemap.xml/route.ts +82 -0
- package/src/engine/components/assistant.tsx +16 -5
- package/src/engine/components/changelog-filters.tsx +114 -0
- package/src/engine/components/code-group.tsx +383 -0
- package/src/engine/components/color.tsx +118 -0
- package/src/engine/components/expandable.tsx +77 -0
- package/src/engine/components/icon.tsx +136 -0
- package/src/engine/components/image-zoom-fallback.tsx +147 -0
- package/src/engine/components/image.tsx +111 -0
- package/src/engine/components/manual-api-playground.tsx +154 -0
- package/src/engine/components/mermaid.tsx +142 -0
- package/src/engine/components/openapi-toc-sync.tsx +59 -0
- package/src/engine/components/openapi.tsx +1682 -0
- package/src/engine/components/page-feedback.tsx +153 -0
- package/src/engine/components/product-switcher.tsx +27 -3
- package/src/engine/components/prompt.tsx +90 -0
- package/src/engine/components/providers.tsx +1 -6
- package/src/engine/components/search.tsx +4 -0
- package/src/engine/components/sidebar-links.tsx +13 -15
- package/src/engine/components/synced-tabs.tsx +57 -0
- package/src/engine/components/toc-examples.tsx +110 -0
- package/src/engine/components/view.tsx +344 -0
- package/src/engine/generated/redirects.ts +3 -0
- package/src/engine/lib/changelog.ts +246 -0
- package/src/engine/lib/layout.shared.ts +30 -2
- package/src/engine/lib/llms.ts +444 -0
- package/src/engine/lib/navigation-normalize.mjs +481 -412
- package/src/engine/lib/navigation-normalize.ts +261 -54
- package/src/engine/lib/redirects.ts +194 -0
- package/src/engine/lib/source.ts +107 -4
- package/src/engine/lib/velu.ts +368 -2
- package/src/engine/mdx-components.tsx +648 -0
- package/src/engine/middleware.ts +66 -0
- package/src/engine/public/icons/cursor-dark.svg +12 -0
- package/src/engine/public/icons/cursor-light.svg +12 -0
- package/src/engine/source.config.ts +98 -1
- package/src/engine/src/components/PageTitle.astro +16 -5
- package/src/engine/src/lib/velu.ts +11 -3
- package/src/navigation-normalize.ts +252 -54
- package/src/themes.ts +6 -6
- package/src/validate.ts +119 -6
- package/src/engine/app/(docs)/[[...slug]]/layout.tsx +0 -87
- package/src/engine/app/(docs)/[[...slug]]/page.tsx +0 -146
package/src/engine/lib/velu.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { normalizeConfigNavigation } from './navigation-normalize';
|
|
4
|
+
const PRIMARY_CONFIG_NAME = 'docs.json';
|
|
5
|
+
const LEGACY_CONFIG_NAME = 'velu.json';
|
|
6
|
+
|
|
7
|
+
function resolveConfigPath(cwd: string): string {
|
|
8
|
+
const primary = resolve(cwd, PRIMARY_CONFIG_NAME);
|
|
9
|
+
if (existsSync(primary)) return primary;
|
|
10
|
+
return resolve(cwd, LEGACY_CONFIG_NAME);
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
interface VeluTab {
|
|
6
14
|
tab: string;
|
|
@@ -10,6 +18,11 @@ interface VeluTab {
|
|
|
10
18
|
groups?: VeluGroup[];
|
|
11
19
|
}
|
|
12
20
|
|
|
21
|
+
interface VeluTabMenuItem {
|
|
22
|
+
item: string;
|
|
23
|
+
pages?: Array<string | VeluSeparator | VeluLink>;
|
|
24
|
+
}
|
|
25
|
+
|
|
13
26
|
interface VeluSeparator {
|
|
14
27
|
separator: string;
|
|
15
28
|
}
|
|
@@ -29,6 +42,7 @@ interface VeluAnchor {
|
|
|
29
42
|
anchor: string;
|
|
30
43
|
href?: string;
|
|
31
44
|
icon?: string;
|
|
45
|
+
iconType?: string;
|
|
32
46
|
color?: {
|
|
33
47
|
light: string;
|
|
34
48
|
dark: string;
|
|
@@ -52,6 +66,7 @@ interface VeluProductNav {
|
|
|
52
66
|
product: string;
|
|
53
67
|
description?: string;
|
|
54
68
|
icon?: string;
|
|
69
|
+
iconType?: string;
|
|
55
70
|
hidden?: boolean;
|
|
56
71
|
href?: string;
|
|
57
72
|
}
|
|
@@ -63,11 +78,47 @@ interface VeluVersionNav {
|
|
|
63
78
|
href?: string;
|
|
64
79
|
}
|
|
65
80
|
|
|
81
|
+
type VeluApiAuthMethod = 'bearer' | 'basic' | 'key' | 'none';
|
|
82
|
+
|
|
83
|
+
interface VeluApiConfig {
|
|
84
|
+
baseUrl?: string;
|
|
85
|
+
playground?: {
|
|
86
|
+
mode?: string;
|
|
87
|
+
display?: string;
|
|
88
|
+
proxy?: boolean;
|
|
89
|
+
};
|
|
90
|
+
examples?: {
|
|
91
|
+
languages?: string[];
|
|
92
|
+
defaults?: 'required' | 'all';
|
|
93
|
+
prefill?: boolean;
|
|
94
|
+
autogenerate?: boolean;
|
|
95
|
+
};
|
|
96
|
+
mdx?: {
|
|
97
|
+
server?: string | string[];
|
|
98
|
+
auth?: {
|
|
99
|
+
method?: VeluApiAuthMethod | string;
|
|
100
|
+
name?: string;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface VeluSeoConfig {
|
|
106
|
+
metatags?: Record<string, unknown>;
|
|
107
|
+
indexing?: 'navigable' | 'all' | string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface VeluThemeAsset {
|
|
111
|
+
light?: string;
|
|
112
|
+
dark?: string;
|
|
113
|
+
href?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
66
116
|
export interface VeluProductOption {
|
|
67
117
|
product: string;
|
|
68
118
|
slug: string;
|
|
69
119
|
description?: string;
|
|
70
120
|
icon?: string;
|
|
121
|
+
iconType?: string;
|
|
71
122
|
tabSlugs: string[];
|
|
72
123
|
defaultPath: string;
|
|
73
124
|
}
|
|
@@ -81,8 +132,24 @@ export interface VeluVersionOption {
|
|
|
81
132
|
}
|
|
82
133
|
|
|
83
134
|
interface VeluConfig {
|
|
135
|
+
name?: string;
|
|
136
|
+
title?: string;
|
|
137
|
+
favicon?: string | VeluThemeAsset;
|
|
138
|
+
logo?: string | VeluThemeAsset;
|
|
139
|
+
colors?: {
|
|
140
|
+
primary?: string;
|
|
141
|
+
light?: string;
|
|
142
|
+
dark?: string;
|
|
143
|
+
};
|
|
144
|
+
icons?: {
|
|
145
|
+
library?: string;
|
|
146
|
+
};
|
|
84
147
|
appearance?: 'system' | 'light' | 'dark';
|
|
85
148
|
languages?: string[];
|
|
149
|
+
openapi?: string | string[] | Record<string, unknown>;
|
|
150
|
+
asyncapi?: string | string[] | Record<string, unknown>;
|
|
151
|
+
api?: VeluApiConfig;
|
|
152
|
+
seo?: VeluSeoConfig;
|
|
86
153
|
navigation: {
|
|
87
154
|
tabs?: VeluTab[];
|
|
88
155
|
languages?: VeluLanguageNav[];
|
|
@@ -97,19 +164,39 @@ interface VeluConfig {
|
|
|
97
164
|
}
|
|
98
165
|
|
|
99
166
|
let cachedConfig: VeluConfig | null = null;
|
|
167
|
+
let cachedRawConfig: Record<string, unknown> | null = null;
|
|
100
168
|
|
|
101
169
|
function loadVeluConfig(): VeluConfig {
|
|
102
170
|
if (cachedConfig) return cachedConfig;
|
|
103
|
-
const configPath =
|
|
171
|
+
const configPath = resolveConfigPath(process.cwd());
|
|
104
172
|
const raw = readFileSync(configPath, 'utf-8');
|
|
105
173
|
cachedConfig = normalizeConfigNavigation(JSON.parse(raw)) as VeluConfig;
|
|
106
174
|
return cachedConfig;
|
|
107
175
|
}
|
|
108
176
|
|
|
177
|
+
function loadRawConfig(): Record<string, unknown> {
|
|
178
|
+
if (cachedRawConfig) return cachedRawConfig;
|
|
179
|
+
const configPath = resolveConfigPath(process.cwd());
|
|
180
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
181
|
+
const parsed = JSON.parse(raw);
|
|
182
|
+
cachedRawConfig = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
183
|
+
? parsed as Record<string, unknown>
|
|
184
|
+
: {};
|
|
185
|
+
return cachedRawConfig;
|
|
186
|
+
}
|
|
187
|
+
|
|
109
188
|
function isGroup(item: unknown): item is VeluGroup {
|
|
110
189
|
return typeof item === 'object' && item !== null && 'group' in item;
|
|
111
190
|
}
|
|
112
191
|
|
|
192
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
193
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isTabMenuItem(value: unknown): value is VeluTabMenuItem {
|
|
197
|
+
return isRecord(value) && typeof value.item === 'string';
|
|
198
|
+
}
|
|
199
|
+
|
|
113
200
|
function slugify(input: string, fallback: string): string {
|
|
114
201
|
const slug = input
|
|
115
202
|
.toLowerCase()
|
|
@@ -201,6 +288,60 @@ export function getGlobalAnchors(): VeluAnchor[] {
|
|
|
201
288
|
);
|
|
202
289
|
}
|
|
203
290
|
|
|
291
|
+
export interface VeluTabMenuDefinition {
|
|
292
|
+
tab: string;
|
|
293
|
+
items: Array<{
|
|
294
|
+
item: string;
|
|
295
|
+
pages: string[];
|
|
296
|
+
}>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function extractMenuItems(menuValue: unknown): VeluTabMenuDefinition['items'] {
|
|
300
|
+
if (!Array.isArray(menuValue)) return [];
|
|
301
|
+
const out: VeluTabMenuDefinition['items'] = [];
|
|
302
|
+
for (const entry of menuValue) {
|
|
303
|
+
if (!isTabMenuItem(entry)) continue;
|
|
304
|
+
const pages = Array.isArray(entry.pages)
|
|
305
|
+
? entry.pages.filter((page): page is string => typeof page === 'string' && page.trim().length > 0)
|
|
306
|
+
: [];
|
|
307
|
+
out.push({ item: entry.item, pages });
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function collectTabMenus(section: unknown, out: VeluTabMenuDefinition[]): void {
|
|
313
|
+
if (!isRecord(section)) return;
|
|
314
|
+
|
|
315
|
+
const tabs = Array.isArray(section.tabs) ? section.tabs : [];
|
|
316
|
+
for (const tabCandidate of tabs) {
|
|
317
|
+
if (!isRecord(tabCandidate)) continue;
|
|
318
|
+
if (typeof tabCandidate.tab === 'string') {
|
|
319
|
+
const items = extractMenuItems(tabCandidate.menu);
|
|
320
|
+
if (items.length > 0) out.push({ tab: tabCandidate.tab, items });
|
|
321
|
+
}
|
|
322
|
+
collectTabMenus(tabCandidate, out);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const nestedKeys: Array<'dropdowns' | 'products' | 'versions' | 'anchors'> = [
|
|
326
|
+
'dropdowns',
|
|
327
|
+
'products',
|
|
328
|
+
'versions',
|
|
329
|
+
'anchors',
|
|
330
|
+
];
|
|
331
|
+
for (const key of nestedKeys) {
|
|
332
|
+
const list = Array.isArray(section[key]) ? section[key] : [];
|
|
333
|
+
for (const entry of list) collectTabMenus(entry, out);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function getTabMenuDefinitions(): VeluTabMenuDefinition[] {
|
|
338
|
+
const raw = loadRawConfig();
|
|
339
|
+
const navigation = isRecord(raw.navigation) ? raw.navigation : {};
|
|
340
|
+
const out: VeluTabMenuDefinition[] = [];
|
|
341
|
+
collectTabMenus(navigation, out);
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
|
|
204
345
|
export function getLanguages(): string[] {
|
|
205
346
|
const config = loadVeluConfig();
|
|
206
347
|
// Prefer navigation.languages codes, fall back to top-level languages
|
|
@@ -239,6 +380,7 @@ export function getProductOptions(): VeluProductOption[] {
|
|
|
239
380
|
slug: prefix,
|
|
240
381
|
description: product.description,
|
|
241
382
|
icon: product.icon,
|
|
383
|
+
iconType: product.iconType,
|
|
242
384
|
tabSlugs,
|
|
243
385
|
defaultPath,
|
|
244
386
|
};
|
|
@@ -304,3 +446,227 @@ export function getAppearance(): 'system' | 'light' | 'dark' {
|
|
|
304
446
|
if (appearance === 'light' || appearance === 'dark') return appearance;
|
|
305
447
|
return 'system';
|
|
306
448
|
}
|
|
449
|
+
|
|
450
|
+
export type VeluIconLibrary = 'fontawesome' | 'lucide' | 'tabler';
|
|
451
|
+
|
|
452
|
+
export function getIconLibrary(): VeluIconLibrary {
|
|
453
|
+
const raw = loadVeluConfig().icons?.library;
|
|
454
|
+
if (raw === 'lucide' || raw === 'tabler' || raw === 'fontawesome') return raw;
|
|
455
|
+
return 'fontawesome';
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
type PlaygroundDisplayMode = 'interactive' | 'simple' | 'none' | 'auth';
|
|
459
|
+
|
|
460
|
+
export interface VeluResolvedApiConfig {
|
|
461
|
+
baseUrl?: string;
|
|
462
|
+
mdxServer?: string;
|
|
463
|
+
mdxServers?: string[];
|
|
464
|
+
authMethod: VeluApiAuthMethod;
|
|
465
|
+
authName?: string;
|
|
466
|
+
playgroundDisplay: PlaygroundDisplayMode;
|
|
467
|
+
playgroundProxyEnabled: boolean;
|
|
468
|
+
exampleLanguages?: string[];
|
|
469
|
+
exampleDefaults: 'required' | 'all';
|
|
470
|
+
examplePrefill: boolean;
|
|
471
|
+
exampleAutogenerate: boolean;
|
|
472
|
+
defaultOpenApiSpec?: string;
|
|
473
|
+
defaultAsyncApiSpec?: string;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export interface VeluResolvedSeoConfig {
|
|
477
|
+
metatags: Record<string, string>;
|
|
478
|
+
indexing: 'navigable' | 'all';
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function normalizePlaygroundDisplay(api: VeluApiConfig | undefined): PlaygroundDisplayMode {
|
|
482
|
+
const display = api?.playground?.display;
|
|
483
|
+
if (display === 'interactive' || display === 'simple' || display === 'none') return display;
|
|
484
|
+
if (display === 'auth') return 'none';
|
|
485
|
+
|
|
486
|
+
const mode = api?.playground?.mode;
|
|
487
|
+
if (mode === 'hide' || mode === 'none') return 'none';
|
|
488
|
+
return 'interactive';
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function normalizeAuthMethod(method: unknown): VeluApiAuthMethod {
|
|
492
|
+
if (method === 'bearer' || method === 'basic' || method === 'key' || method === 'none') return method;
|
|
493
|
+
return 'none';
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function extractOpenApiSource(openapi: VeluConfig['openapi']): string | string[] | undefined {
|
|
497
|
+
if (typeof openapi === 'string' || Array.isArray(openapi)) return openapi;
|
|
498
|
+
if (openapi && typeof openapi === 'object') {
|
|
499
|
+
const source = (openapi as Record<string, unknown>).source;
|
|
500
|
+
if (typeof source === 'string' || Array.isArray(source)) return source as string | string[];
|
|
501
|
+
}
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function resolveDefaultOpenApiSpec(openapi: VeluConfig['openapi']): string | undefined {
|
|
506
|
+
const source = extractOpenApiSource(openapi);
|
|
507
|
+
if (typeof source === 'string' && source.trim()) return source.trim();
|
|
508
|
+
if (Array.isArray(source)) {
|
|
509
|
+
const first = source.find((entry) => typeof entry === 'string' && entry.trim().length > 0);
|
|
510
|
+
return typeof first === 'string' ? first.trim() : undefined;
|
|
511
|
+
}
|
|
512
|
+
return undefined;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function normalizeExampleLanguages(value: unknown): string[] | undefined {
|
|
516
|
+
if (!Array.isArray(value)) return undefined;
|
|
517
|
+
const normalized = value
|
|
518
|
+
.filter((entry): entry is string => typeof entry === 'string')
|
|
519
|
+
.map((entry) => entry.trim())
|
|
520
|
+
.filter((entry) => entry.length > 0);
|
|
521
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function normalizeMdxServers(value: unknown): string[] | undefined {
|
|
525
|
+
const rawValues = Array.isArray(value) ? value : (typeof value === 'string' ? [value] : []);
|
|
526
|
+
const normalized = rawValues
|
|
527
|
+
.filter((entry): entry is string => typeof entry === 'string')
|
|
528
|
+
.map((entry) => entry.trim())
|
|
529
|
+
.filter((entry) => entry.length > 0);
|
|
530
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function normalizeSeoMetatags(value: unknown): Record<string, string> {
|
|
534
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
535
|
+
const output: Record<string, string> = {};
|
|
536
|
+
for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {
|
|
537
|
+
const tag = key.trim();
|
|
538
|
+
if (!tag) continue;
|
|
539
|
+
if (typeof raw === 'string') {
|
|
540
|
+
const normalized = raw.trim();
|
|
541
|
+
if (normalized) output[tag] = normalized;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (typeof raw === 'number' || typeof raw === 'boolean') {
|
|
545
|
+
output[tag] = String(raw);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return output;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function normalizeAssetPath(value: unknown): string | undefined {
|
|
552
|
+
if (typeof value !== 'string') return undefined;
|
|
553
|
+
const trimmed = value.trim();
|
|
554
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function normalizeThemeAsset(value: unknown): VeluThemeAsset {
|
|
558
|
+
if (typeof value === 'string') {
|
|
559
|
+
const asset = normalizeAssetPath(value);
|
|
560
|
+
return asset ? { light: asset, dark: asset } : {};
|
|
561
|
+
}
|
|
562
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
563
|
+
const record = value as Record<string, unknown>;
|
|
564
|
+
const light = normalizeAssetPath(record.light);
|
|
565
|
+
const dark = normalizeAssetPath(record.dark);
|
|
566
|
+
const any = normalizeAssetPath(record.default);
|
|
567
|
+
const href = normalizeAssetPath(record.href);
|
|
568
|
+
return {
|
|
569
|
+
...(light ? { light } : {}),
|
|
570
|
+
...(dark ? { dark } : {}),
|
|
571
|
+
...(!light && any ? { light: any } : {}),
|
|
572
|
+
...(!dark && any ? { dark: any } : {}),
|
|
573
|
+
...(href ? { href } : {}),
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function extractOrigin(value: string | undefined): string | undefined {
|
|
578
|
+
if (!value) return undefined;
|
|
579
|
+
const trimmed = value.trim();
|
|
580
|
+
if (!trimmed) return undefined;
|
|
581
|
+
try {
|
|
582
|
+
return new URL(trimmed).origin;
|
|
583
|
+
} catch {
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export function getApiConfig(): VeluResolvedApiConfig {
|
|
589
|
+
const config = loadVeluConfig();
|
|
590
|
+
const api = config.api;
|
|
591
|
+
const auth = api?.mdx?.auth;
|
|
592
|
+
const examples = api?.examples;
|
|
593
|
+
const playgroundDisplay = normalizePlaygroundDisplay(api);
|
|
594
|
+
const staticExportBuild = process.env.VELU_STATIC_EXPORT === '1';
|
|
595
|
+
const mdxServers = normalizeMdxServers(api?.mdx?.server);
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
baseUrl: typeof api?.baseUrl === 'string' && api.baseUrl.trim() ? api.baseUrl.trim() : undefined,
|
|
599
|
+
mdxServer: mdxServers?.[0],
|
|
600
|
+
mdxServers,
|
|
601
|
+
authMethod: normalizeAuthMethod(auth?.method),
|
|
602
|
+
authName: typeof auth?.name === 'string' && auth.name.trim() ? auth.name.trim() : undefined,
|
|
603
|
+
playgroundDisplay,
|
|
604
|
+
// Next static export cannot include runtime route handlers such as /api/proxy.
|
|
605
|
+
// Disable proxy automatically for static export builds.
|
|
606
|
+
playgroundProxyEnabled: !staticExportBuild && api?.playground?.proxy !== false,
|
|
607
|
+
exampleLanguages: normalizeExampleLanguages(examples?.languages),
|
|
608
|
+
exampleDefaults: examples?.defaults === 'required' ? 'required' : 'all',
|
|
609
|
+
examplePrefill: examples?.prefill === true,
|
|
610
|
+
exampleAutogenerate: examples?.autogenerate !== false,
|
|
611
|
+
defaultOpenApiSpec: resolveDefaultOpenApiSpec(config.openapi),
|
|
612
|
+
defaultAsyncApiSpec: resolveDefaultOpenApiSpec(config.asyncapi),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function getSeoConfig(): VeluResolvedSeoConfig {
|
|
617
|
+
const config = loadVeluConfig();
|
|
618
|
+
const seo = config.seo;
|
|
619
|
+
const indexing: 'navigable' | 'all' = seo?.indexing === 'all' ? 'all' : 'navigable';
|
|
620
|
+
return {
|
|
621
|
+
metatags: normalizeSeoMetatags(seo?.metatags),
|
|
622
|
+
indexing,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export function getSiteName(): string {
|
|
627
|
+
const config = loadVeluConfig();
|
|
628
|
+
const fromName = normalizeAssetPath(config.name);
|
|
629
|
+
if (fromName) return fromName;
|
|
630
|
+
const fromTitle = normalizeAssetPath(config.title);
|
|
631
|
+
if (fromTitle) return fromTitle;
|
|
632
|
+
return 'Velu Docs';
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export function getSiteFavicon(): string | undefined {
|
|
636
|
+
const config = loadVeluConfig();
|
|
637
|
+
const asset = normalizeThemeAsset(config.favicon);
|
|
638
|
+
return asset.light ?? asset.dark;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export function getSiteLogoAsset(): VeluThemeAsset {
|
|
642
|
+
const config = loadVeluConfig();
|
|
643
|
+
return normalizeThemeAsset(config.logo);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function getSitePrimaryColor(): string | undefined {
|
|
647
|
+
const config = loadVeluConfig();
|
|
648
|
+
const colors = config.colors;
|
|
649
|
+
if (!colors) return undefined;
|
|
650
|
+
return normalizeAssetPath(colors.primary) ?? normalizeAssetPath(colors.light) ?? normalizeAssetPath(colors.dark);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export function getSiteOrigin(): string {
|
|
654
|
+
const seo = getSeoConfig();
|
|
655
|
+
const envCandidates = [
|
|
656
|
+
process.env.VELU_SITE_URL,
|
|
657
|
+
process.env.NEXT_PUBLIC_SITE_URL,
|
|
658
|
+
process.env.SITE_URL,
|
|
659
|
+
];
|
|
660
|
+
for (const candidate of envCandidates) {
|
|
661
|
+
const origin = extractOrigin(candidate);
|
|
662
|
+
if (origin) return origin;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const canonicalOrigin = extractOrigin(seo.metatags.canonical);
|
|
666
|
+
if (canonicalOrigin) return canonicalOrigin;
|
|
667
|
+
|
|
668
|
+
const ogOrigin = extractOrigin(seo.metatags['og:url']);
|
|
669
|
+
if (ogOrigin) return ogOrigin;
|
|
670
|
+
|
|
671
|
+
return 'http://localhost:4321';
|
|
672
|
+
}
|