@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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { NextRequest } from 'next/server';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import redirectRules from '@/generated/redirects';
|
|
4
|
+
import {
|
|
5
|
+
compileRedirectRules,
|
|
6
|
+
isExternalDestination,
|
|
7
|
+
normalizeRedirectRules,
|
|
8
|
+
resolveRedirect,
|
|
9
|
+
} from '@/lib/redirects';
|
|
10
|
+
|
|
11
|
+
const compiledRedirects = compileRedirectRules(normalizeRedirectRules(redirectRules));
|
|
12
|
+
|
|
13
|
+
export function middleware(request: NextRequest) {
|
|
14
|
+
const { pathname } = request.nextUrl;
|
|
15
|
+
|
|
16
|
+
if (pathname.startsWith('/rss-file')) {
|
|
17
|
+
return NextResponse.next();
|
|
18
|
+
}
|
|
19
|
+
if (pathname.startsWith('/llms-file') || pathname.startsWith('/llms-full-file')) {
|
|
20
|
+
return NextResponse.next();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (pathname.endsWith('/rss.xml')) {
|
|
24
|
+
const rewritten = request.nextUrl.clone();
|
|
25
|
+
rewritten.pathname = `/rss-file${pathname.slice(0, -('/rss.xml'.length))}`;
|
|
26
|
+
return NextResponse.rewrite(rewritten);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (pathname === '/llms.txt') {
|
|
30
|
+
const rewritten = request.nextUrl.clone();
|
|
31
|
+
rewritten.pathname = '/llms-file';
|
|
32
|
+
return NextResponse.rewrite(rewritten);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (pathname === '/llms-full.txt') {
|
|
36
|
+
const rewritten = request.nextUrl.clone();
|
|
37
|
+
rewritten.pathname = '/llms-full-file';
|
|
38
|
+
return NextResponse.rewrite(rewritten);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (pathname.endsWith('.md')) {
|
|
42
|
+
const rewritten = request.nextUrl.clone();
|
|
43
|
+
rewritten.pathname = `/md-file${pathname}`;
|
|
44
|
+
return NextResponse.rewrite(rewritten);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const redirect = resolveRedirect(pathname, compiledRedirects);
|
|
48
|
+
if (redirect) {
|
|
49
|
+
if (isExternalDestination(redirect.destination)) {
|
|
50
|
+
return NextResponse.redirect(redirect.destination, redirect.statusCode);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const target = request.nextUrl.clone();
|
|
54
|
+
target.pathname = redirect.destination;
|
|
55
|
+
if (!target.search && request.nextUrl.search) {
|
|
56
|
+
target.search = request.nextUrl.search;
|
|
57
|
+
}
|
|
58
|
+
return NextResponse.redirect(target, redirect.statusCode);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return NextResponse.next();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const config = {
|
|
65
|
+
matcher: ['/((?!_next|favicon.ico|sitemap.xml|robots.txt|assets|images).*)'],
|
|
66
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 466.73 532.09">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 29.6.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 9) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0 {
|
|
7
|
+
fill: #edecec;
|
|
8
|
+
}
|
|
9
|
+
</style>
|
|
10
|
+
</defs>
|
|
11
|
+
<path class="st0" d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/>
|
|
12
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 466.73 532.09">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 29.6.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 9) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0 {
|
|
7
|
+
fill: #26251e;
|
|
8
|
+
}
|
|
9
|
+
</style>
|
|
10
|
+
</defs>
|
|
11
|
+
<path class="st0" d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/>
|
|
12
|
+
</svg>
|
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
|
|
2
2
|
import { metaSchema, pageSchema } from 'fumadocs-core/source/schema';
|
|
3
|
+
import { transformerMetaHighlight } from '@shikijs/transformers';
|
|
4
|
+
|
|
5
|
+
function remarkCodeFilenameToTitle() {
|
|
6
|
+
const booleanMetaFlags = new Set([
|
|
7
|
+
'wrap',
|
|
8
|
+
'copy',
|
|
9
|
+
'nocopy',
|
|
10
|
+
'lineNumbers',
|
|
11
|
+
'linenumbers',
|
|
12
|
+
'showLineNumbers',
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function quoteTitle(value: string): string {
|
|
16
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ensureTitleMeta(meta: string): string {
|
|
20
|
+
const trimmed = meta.trim();
|
|
21
|
+
if (!trimmed) return trimmed;
|
|
22
|
+
if (/\btitle\s*=/.test(trimmed)) return trimmed;
|
|
23
|
+
|
|
24
|
+
const fileWithRest = trimmed.match(/^([^\s]+?\.[a-z0-9_-]+)(\s+.*)?$/i);
|
|
25
|
+
if (fileWithRest) {
|
|
26
|
+
const file = fileWithRest[1];
|
|
27
|
+
const rest = (fileWithRest[2] ?? '').trim();
|
|
28
|
+
return rest ? `title="${quoteTitle(file)}" ${rest}` : `title="${quoteTitle(file)}"`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!trimmed.includes('=') && !trimmed.includes('{') && !trimmed.includes('}')) {
|
|
32
|
+
if (booleanMetaFlags.has(trimmed)) return trimmed;
|
|
33
|
+
return `title="${quoteTitle(trimmed)}"`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return trimmed;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function visit(node: any) {
|
|
40
|
+
if (!node || typeof node !== 'object') return;
|
|
41
|
+
|
|
42
|
+
if (node.type === 'code' && typeof node.meta === 'string') {
|
|
43
|
+
let meta = node.meta.trim();
|
|
44
|
+
// Mint-style fence syntax: ```lang filename.ext
|
|
45
|
+
// Convert it into title metadata so code tabs can use file names.
|
|
46
|
+
meta = ensureTitleMeta(meta);
|
|
47
|
+
|
|
48
|
+
// Mint-style line highlight syntax: highlight=1 or highlight="1,3-5"
|
|
49
|
+
// Convert to Shiki meta-highlight format: {1,3-5}
|
|
50
|
+
const hlMatch = meta.match(/(?:^|\s)highlight=(?:"([^"]+)"|'([^']+)'|([^\s]+))/i);
|
|
51
|
+
if (hlMatch) {
|
|
52
|
+
const raw = (hlMatch[1] ?? hlMatch[2] ?? hlMatch[3] ?? '').trim();
|
|
53
|
+
const lineSpec = raw.replace(/[{}]/g, '');
|
|
54
|
+
meta = meta.replace(hlMatch[0], '').replace(/\s+/g, ' ').trim();
|
|
55
|
+
if (lineSpec && !/\{\s*\d[\d,\-\s]*\s*\}/.test(meta)) {
|
|
56
|
+
meta = `${meta} {${lineSpec}}`.trim();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// theme={null} is a Mint docs hint; remove it from fence meta.
|
|
61
|
+
meta = meta.replace(/\btheme=\{null\}\b/g, '').replace(/\s+/g, ' ').trim();
|
|
62
|
+
node.meta = meta;
|
|
63
|
+
}
|
|
64
|
+
if (node.type === 'code' && typeof node.meta !== 'string') {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (node.type === 'code' && node.meta === '') {
|
|
69
|
+
delete node.meta;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (node.type === 'code' && typeof node.meta === 'string') {
|
|
73
|
+
node.meta = node.meta.trim();
|
|
74
|
+
if (!node.meta) {
|
|
75
|
+
delete node.meta;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const children = node.children;
|
|
80
|
+
if (Array.isArray(children)) {
|
|
81
|
+
for (const child of children) visit(child);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (tree: any) => visit(tree);
|
|
86
|
+
}
|
|
3
87
|
|
|
4
88
|
export const docs = defineDocs({
|
|
5
89
|
dir: 'content/docs',
|
|
@@ -14,4 +98,17 @@ export const docs = defineDocs({
|
|
|
14
98
|
},
|
|
15
99
|
});
|
|
16
100
|
|
|
17
|
-
export default defineConfig(
|
|
101
|
+
export default defineConfig({
|
|
102
|
+
mdxOptions: {
|
|
103
|
+
remarkPlugins: [remarkCodeFilenameToTitle],
|
|
104
|
+
rehypeCodeOptions: ({
|
|
105
|
+
lazy: false,
|
|
106
|
+
fallbackLanguage: 'bash',
|
|
107
|
+
transformers: [transformerMetaHighlight()],
|
|
108
|
+
langAlias: {
|
|
109
|
+
gradle: 'groovy',
|
|
110
|
+
proguard: 'properties',
|
|
111
|
+
},
|
|
112
|
+
} as any),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
@@ -405,19 +405,30 @@ const title = Astro.locals.starlightRoute.entry.data.title;
|
|
|
405
405
|
}
|
|
406
406
|
}, true);
|
|
407
407
|
|
|
408
|
-
// Hide ask bar only when
|
|
409
|
-
|
|
408
|
+
// Hide ask bar only when truly near the page bottom.
|
|
409
|
+
function syncAskBarVisibility() {
|
|
410
410
|
if (isPanelOpen()) return;
|
|
411
411
|
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
412
412
|
var docHeight = document.documentElement.scrollHeight;
|
|
413
413
|
var winHeight = window.innerHeight;
|
|
414
|
-
|
|
415
|
-
|
|
414
|
+
var bottomGap = docHeight - scrollTop - winHeight;
|
|
415
|
+
var nearBottomThreshold = 8;
|
|
416
|
+
|
|
417
|
+
if (docHeight <= winHeight + 2) {
|
|
418
|
+
askBar.classList.remove('velu-ask-bar-hidden');
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (bottomGap <= nearBottomThreshold) {
|
|
416
423
|
askBar.classList.add('velu-ask-bar-hidden');
|
|
417
424
|
} else {
|
|
418
425
|
askBar.classList.remove('velu-ask-bar-hidden');
|
|
419
426
|
}
|
|
420
|
-
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
window.addEventListener('scroll', syncAskBarVisibility, { passive: true });
|
|
430
|
+
window.addEventListener('resize', syncAskBarVisibility, { passive: true });
|
|
431
|
+
syncAskBarVisibility();
|
|
421
432
|
|
|
422
433
|
document.onkeydown = function(e) {
|
|
423
434
|
if (e.key === 'Escape' && isPanelOpen()) { closePanel(); }
|
|
@@ -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 '../../lib/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
|
// ── Types ───────────────────────────────────────────────────────────────────
|
|
6
14
|
|
|
@@ -82,7 +90,7 @@ let _cachedConfig: VeluConfig | null = null;
|
|
|
82
90
|
|
|
83
91
|
export function loadVeluConfig(): VeluConfig {
|
|
84
92
|
if (_cachedConfig) return _cachedConfig;
|
|
85
|
-
const configPath =
|
|
93
|
+
const configPath = resolveConfigPath(process.cwd());
|
|
86
94
|
const raw = readFileSync(configPath, 'utf-8');
|
|
87
95
|
_cachedConfig = normalizeConfigNavigation(JSON.parse(raw));
|
|
88
96
|
return _cachedConfig!;
|
|
@@ -145,7 +153,7 @@ function firstGroupPage(group: VeluGroup, tabSlug: string): string | undefined {
|
|
|
145
153
|
|
|
146
154
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
147
155
|
|
|
148
|
-
/** Build the full Starlight sidebar array from velu.json */
|
|
156
|
+
/** Build the full Starlight sidebar array from docs.json/velu.json */
|
|
149
157
|
export function getSidebar(): any[] {
|
|
150
158
|
const config = loadVeluConfig();
|
|
151
159
|
const sidebar: any[] = [];
|