@aravindc26/velu 0.11.1 ā 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 +1 -1
- package/src/cli.ts +32 -10
- package/src/engine/app/(docs)/[...slug]/layout.tsx +2 -8
- package/src/engine/app/(docs)/[...slug]/page.tsx +24 -15
- package/src/engine/app/llms-file/route.ts +3 -3
- package/src/engine/app/llms-full-file/route.ts +3 -3
- package/src/engine/app/robots.txt/route.ts +2 -0
- package/src/engine/app/rss-file/[...slug]/route.ts +3 -10
- package/src/engine/app/sitemap.xml/route.ts +2 -0
- package/src/engine/components/openapi.tsx +26 -23
- package/src/engine/lib/source.ts +2 -2
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve, join, dirname, delimiter } from "node:path";
|
|
2
|
-
import { existsSync, mkdirSync, writeFileSync, readdirSync, copyFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync, readdirSync, copyFileSync, cpSync, rmSync } from "node:fs";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
@@ -195,13 +195,25 @@ async function paths(docsDir: string) {
|
|
|
195
195
|
|
|
196
196
|
async function generateProject(docsDir: string): Promise<string> {
|
|
197
197
|
const { build } = await import("./build.js");
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
const outDir = join(PACKAGE_ROOT, ".velu-out");
|
|
198
|
+
// Generate into the active docs project directory.
|
|
199
|
+
const outDir = join(docsDir, ".velu-out");
|
|
201
200
|
build(docsDir, outDir);
|
|
202
201
|
return outDir;
|
|
203
202
|
}
|
|
204
203
|
|
|
204
|
+
function samePath(a: string, b: string): boolean {
|
|
205
|
+
return resolve(a).replace(/\\/g, "/").toLowerCase() === resolve(b).replace(/\\/g, "/").toLowerCase();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function prepareRuntimeOutDir(docsOutDir: string): string {
|
|
209
|
+
const runtimeOutDir = join(PACKAGE_ROOT, ".velu-out");
|
|
210
|
+
if (samePath(docsOutDir, runtimeOutDir)) return runtimeOutDir;
|
|
211
|
+
|
|
212
|
+
rmSync(runtimeOutDir, { recursive: true, force: true });
|
|
213
|
+
cpSync(docsOutDir, runtimeOutDir, { recursive: true, force: true });
|
|
214
|
+
return runtimeOutDir;
|
|
215
|
+
}
|
|
216
|
+
|
|
205
217
|
async function buildStatic(outDir: string, docsDir: string) {
|
|
206
218
|
await new Promise<void>((res, rej) => {
|
|
207
219
|
const child = spawn("node", ["_server.mjs", "build"], {
|
|
@@ -245,10 +257,19 @@ function exportMarkdownRoutes(outDir: string) {
|
|
|
245
257
|
}
|
|
246
258
|
|
|
247
259
|
async function buildSite(docsDir: string) {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
260
|
+
const docsOutDir = await generateProject(docsDir);
|
|
261
|
+
const runtimeOutDir = prepareRuntimeOutDir(docsOutDir);
|
|
262
|
+
await buildStatic(runtimeOutDir, docsDir);
|
|
263
|
+
exportMarkdownRoutes(runtimeOutDir);
|
|
264
|
+
|
|
265
|
+
if (!samePath(docsOutDir, runtimeOutDir)) {
|
|
266
|
+
const docsDistDir = join(docsOutDir, "dist");
|
|
267
|
+
const runtimeDistDir = join(runtimeOutDir, "dist");
|
|
268
|
+
rmSync(docsDistDir, { recursive: true, force: true });
|
|
269
|
+
cpSync(runtimeDistDir, docsDistDir, { recursive: true, force: true });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const staticOutDir = join(docsOutDir, "dist");
|
|
252
273
|
console.log(`\nš Static site output: ${staticOutDir}`);
|
|
253
274
|
}
|
|
254
275
|
|
|
@@ -269,8 +290,9 @@ function spawnServer(outDir: string, command: string, port: number, docsDir: str
|
|
|
269
290
|
}
|
|
270
291
|
|
|
271
292
|
async function run(docsDir: string, port: number) {
|
|
272
|
-
const
|
|
273
|
-
|
|
293
|
+
const docsOutDir = await generateProject(docsDir);
|
|
294
|
+
const runtimeOutDir = prepareRuntimeOutDir(docsOutDir);
|
|
295
|
+
spawnServer(runtimeOutDir, "dev", port, docsDir);
|
|
274
296
|
}
|
|
275
297
|
|
|
276
298
|
// āā Parse args āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
@@ -173,13 +173,7 @@ function buildNavbarTabs(tree: unknown): Array<{
|
|
|
173
173
|
urls,
|
|
174
174
|
};
|
|
175
175
|
})
|
|
176
|
-
.filter((tab): tab is
|
|
177
|
-
url: string;
|
|
178
|
-
title: ReactNode;
|
|
179
|
-
icon?: ReactNode;
|
|
180
|
-
description?: ReactNode;
|
|
181
|
-
urls: Set<string>;
|
|
182
|
-
} => tab !== null);
|
|
176
|
+
.filter((tab): tab is NonNullable<typeof tab> => tab !== null);
|
|
183
177
|
|
|
184
178
|
return tabs.length > 0 ? tabs : undefined;
|
|
185
179
|
}
|
|
@@ -265,7 +259,7 @@ function scopeTreeToTab<T extends { children?: unknown[] }>(
|
|
|
265
259
|
if (!rootFolder || !Array.isArray(rootFolder.children)) return tree;
|
|
266
260
|
|
|
267
261
|
const normalizedContainer = (containerSlug ?? '').trim().toLowerCase();
|
|
268
|
-
const matchingChildren = rootFolder.children.filter((child) => {
|
|
262
|
+
const matchingChildren = rootFolder.children.filter((child): child is PageTreeFolderNode => {
|
|
269
263
|
const folder = child as PageTreeFolderNode;
|
|
270
264
|
if (folder?.type !== 'folder') return false;
|
|
271
265
|
|
|
@@ -556,9 +556,10 @@ export default async function Page({ params }: PageProps) {
|
|
|
556
556
|
|
|
557
557
|
if (!page) notFound();
|
|
558
558
|
|
|
559
|
-
const MDX = page.data.body;
|
|
560
|
-
const sourceMarkdown = await loadMarkdownForSlug(pageSlug, locale, hasI18n);
|
|
561
559
|
const pageDataRecord = (page.data as unknown) as Record<string, unknown>;
|
|
560
|
+
const MDX = pageDataRecord.body as any;
|
|
561
|
+
if (typeof MDX !== 'function') notFound();
|
|
562
|
+
const sourceMarkdown = await loadMarkdownForSlug(pageSlug, locale, hasI18n);
|
|
562
563
|
const dataMarkdown = typeof pageDataRecord.processedMarkdown === 'string'
|
|
563
564
|
? String(pageDataRecord.processedMarkdown)
|
|
564
565
|
: undefined;
|
|
@@ -589,8 +590,14 @@ export default async function Page({ params }: PageProps) {
|
|
|
589
590
|
const playgroundDisplay = normalizePlaygroundDisplay(frontmatter.playground, apiConfig.playgroundDisplay);
|
|
590
591
|
const proxyUrl = apiConfig.playgroundProxyEnabled ? '/api/proxy' : '';
|
|
591
592
|
const authMethod = normalizeAuthMethod(frontmatter.authMethod, apiConfig.authMethod);
|
|
593
|
+
const inlineApiTitle = typeof pageDataRecord.title === 'string' && pageDataRecord.title.trim().length > 0
|
|
594
|
+
? pageDataRecord.title
|
|
595
|
+
: (frontmatter.title ?? pageSlug);
|
|
596
|
+
const inlineApiDescription = typeof pageDataRecord.description === 'string'
|
|
597
|
+
? pageDataRecord.description
|
|
598
|
+
: frontmatter.description;
|
|
592
599
|
const inlineApiDoc = parsedApiFrontmatter
|
|
593
|
-
? buildInlineApiDoc(parsedApiFrontmatter,
|
|
600
|
+
? buildInlineApiDoc(parsedApiFrontmatter, inlineApiTitle, inlineApiDescription, authMethod, apiConfig.authName)
|
|
594
601
|
: null;
|
|
595
602
|
const hasPanelExamples = typeof effectiveMarkdown === 'string'
|
|
596
603
|
&& /<(?:Panel|RequestExample|ResponseExample)(?:\s|>)/.test(effectiveMarkdown);
|
|
@@ -616,7 +623,9 @@ export default async function Page({ params }: PageProps) {
|
|
|
616
623
|
<div id="velu-api-toc-rail-host" />
|
|
617
624
|
</div>
|
|
618
625
|
) : undefined;
|
|
619
|
-
const
|
|
626
|
+
const pageToc = pageDataRecord.toc as any;
|
|
627
|
+
const pageFull = typeof pageDataRecord.full === 'boolean' ? pageDataRecord.full : undefined;
|
|
628
|
+
const toc = hasChangelog ? parsedChangelog.toc : pageToc;
|
|
620
629
|
const tableOfContentHeader = apiTocHeader ?? (hasPanelExamples ? <div className="velu-toc-panel-rail" /> : undefined);
|
|
621
630
|
const orderedPages = hasI18n ? source.getPages(locale) : source.getPages();
|
|
622
631
|
const currentPageUrl = (typeof sourcePageUrl === 'string' && sourcePageUrl.trim())
|
|
@@ -643,12 +652,12 @@ export default async function Page({ params }: PageProps) {
|
|
|
643
652
|
}
|
|
644
653
|
|
|
645
654
|
return (
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
655
|
+
<DocsPage
|
|
656
|
+
toc={toc}
|
|
657
|
+
full={hasChangelog ? false : (hasApiTocRail ? false : pageFull)}
|
|
658
|
+
tableOfContent={tableOfContentHeader ? { header: tableOfContentHeader } : undefined}
|
|
659
|
+
footer={{ enabled: false }}
|
|
660
|
+
>
|
|
652
661
|
<div
|
|
653
662
|
data-pagefind-body
|
|
654
663
|
data-pagefind-meta={metaAttrs.join(',')}
|
|
@@ -836,17 +845,17 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
|
|
836
845
|
height: Number(ogImageHeight),
|
|
837
846
|
}));
|
|
838
847
|
const twitterImages = twitterImagesRaw.map((entry) => toAbsoluteMetaUrl(siteOrigin, entry));
|
|
839
|
-
const openGraph:
|
|
840
|
-
type: (mergedMetatags['og:type'] as
|
|
848
|
+
const openGraph: Metadata['openGraph'] = {
|
|
849
|
+
type: (mergedMetatags['og:type'] as any) || 'website',
|
|
841
850
|
siteName: mergedMetatags['og:site_name'] || siteName,
|
|
842
851
|
title: mergedMetatags['og:title'] || resolvedTitle,
|
|
843
852
|
...(resolvedDescription ? { description: mergedMetatags['og:description'] || resolvedDescription } : {}),
|
|
844
853
|
url: mergedMetatags['og:url'] ? toAbsoluteMetaUrl(siteOrigin, mergedMetatags['og:url']) : canonical,
|
|
845
854
|
...(mergedMetatags['og:locale'] ? { locale: mergedMetatags['og:locale'] } : {}),
|
|
846
|
-
...(openGraphImages.length > 0 ? { images: openGraphImages as
|
|
855
|
+
...(openGraphImages.length > 0 ? { images: openGraphImages as any } : {}),
|
|
847
856
|
};
|
|
848
|
-
const twitter:
|
|
849
|
-
card: (mergedMetatags['twitter:card'] as
|
|
857
|
+
const twitter: Metadata['twitter'] = {
|
|
858
|
+
card: (mergedMetatags['twitter:card'] as any) || 'summary_large_image',
|
|
850
859
|
title: mergedMetatags['twitter:title'] || resolvedTitle,
|
|
851
860
|
...(resolvedDescription ? { description: mergedMetatags['twitter:description'] || resolvedDescription } : {}),
|
|
852
861
|
...(mergedMetatags['twitter:site'] ? { site: mergedMetatags['twitter:site'] } : {}),
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
getSiteTitle,
|
|
4
4
|
normalizePath,
|
|
5
5
|
readCustomLlmsFile,
|
|
6
|
-
resolveRequestOrigin,
|
|
7
6
|
} from '@/lib/llms';
|
|
7
|
+
import { getSiteOrigin } from '@/lib/velu';
|
|
8
8
|
|
|
9
9
|
export const dynamic = 'force-static';
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ function toSpecUrl(origin: string, spec: string): string {
|
|
|
24
24
|
return `${origin}${normalizePath(trimmed)}`;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export async function GET(
|
|
27
|
+
export async function GET() {
|
|
28
28
|
const custom = await readCustomLlmsFile('llms.txt');
|
|
29
29
|
if (custom !== null) {
|
|
30
30
|
return new Response(custom, {
|
|
@@ -38,7 +38,7 @@ export async function GET(request: Request) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
const siteTitle = getSiteTitle();
|
|
41
|
-
const origin =
|
|
41
|
+
const origin = getSiteOrigin();
|
|
42
42
|
const pages = await collectLlmsPages();
|
|
43
43
|
const docsPages = pages.filter((page) => !page.noindex && !(page.sourceKind === 'generated' && page.isOpenApiOperation));
|
|
44
44
|
const openApiSpecs = Array.from(
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
getSiteTitle,
|
|
4
4
|
normalizePath,
|
|
5
5
|
readCustomLlmsFile,
|
|
6
|
-
resolveRequestOrigin,
|
|
7
6
|
} from '@/lib/llms';
|
|
7
|
+
import { getSiteOrigin } from '@/lib/velu';
|
|
8
8
|
|
|
9
9
|
export const dynamic = 'force-static';
|
|
10
10
|
|
|
11
|
-
export async function GET(
|
|
11
|
+
export async function GET() {
|
|
12
12
|
const custom = await readCustomLlmsFile('llms-full.txt');
|
|
13
13
|
if (custom !== null) {
|
|
14
14
|
return new Response(custom, {
|
|
@@ -22,7 +22,7 @@ export async function GET(request: Request) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const siteTitle = getSiteTitle();
|
|
25
|
-
const origin =
|
|
25
|
+
const origin = getSiteOrigin();
|
|
26
26
|
const pages = await collectLlmsPages({ includeMarkdown: true });
|
|
27
27
|
const includedPages = pages.filter((page) => {
|
|
28
28
|
if (page.noindex) return false;
|
|
@@ -3,6 +3,8 @@ import { readFile } from 'node:fs/promises';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { getSeoConfig, getSiteOrigin } from '@/lib/velu';
|
|
5
5
|
|
|
6
|
+
export const dynamic = 'force-static';
|
|
7
|
+
|
|
6
8
|
async function readCustomRobotsFile(): Promise<string | null> {
|
|
7
9
|
const docsDir = process.env.VELU_DOCS_DIR?.trim();
|
|
8
10
|
if (docsDir) {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
parseChangelogFromMarkdown,
|
|
7
7
|
parseFrontmatterValue,
|
|
8
8
|
} from '@/lib/changelog';
|
|
9
|
-
import { getLanguages } from '@/lib/velu';
|
|
9
|
+
import { getLanguages, getSiteOrigin } from '@/lib/velu';
|
|
10
10
|
|
|
11
11
|
interface RouteParams {
|
|
12
12
|
slug?: string[];
|
|
@@ -85,7 +85,7 @@ export async function generateStaticParams() {
|
|
|
85
85
|
return out;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
export async function GET(
|
|
88
|
+
export async function GET(_request: Request, { params }: { params: Promise<RouteParams> }) {
|
|
89
89
|
const resolvedParams = await params;
|
|
90
90
|
const fullSlug = resolvedParams.slug ?? [];
|
|
91
91
|
|
|
@@ -120,14 +120,7 @@ export async function GET(request: Request, { params }: { params: Promise<RouteP
|
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
const
|
|
124
|
-
const forwardedHost = request.headers.get('x-forwarded-host') ?? request.headers.get('host');
|
|
125
|
-
const forwardedProto = request.headers.get('x-forwarded-proto') ?? requestUrl.protocol.replace(':', '');
|
|
126
|
-
const devPort = process.env.PORT?.trim();
|
|
127
|
-
const fallbackOrigin = (requestUrl.hostname === 'localhost' && requestUrl.port === '3000' && devPort)
|
|
128
|
-
? `${requestUrl.protocol}//${requestUrl.hostname}:${devPort}`
|
|
129
|
-
: requestUrl.origin;
|
|
130
|
-
const origin = forwardedHost ? `${forwardedProto}://${forwardedHost}` : fallbackOrigin;
|
|
123
|
+
const origin = getSiteOrigin();
|
|
131
124
|
const pagePath = normalizePath(fullSlug.join('/'));
|
|
132
125
|
const pageUrl = `${origin}${pagePath}`;
|
|
133
126
|
const rssUrl = `${pageUrl.replace(/\/$/, '')}/rss.xml`;
|
|
@@ -4,6 +4,8 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { collectLlmsPages, normalizePath } from '@/lib/llms';
|
|
5
5
|
import { getSeoConfig, getSiteOrigin } from '@/lib/velu';
|
|
6
6
|
|
|
7
|
+
export const dynamic = 'force-static';
|
|
8
|
+
|
|
7
9
|
function escapeXml(value: string): string {
|
|
8
10
|
return value
|
|
9
11
|
.replace(/&/g, '&')
|
|
@@ -766,26 +766,26 @@ function collectResponseHeaderFields(response: OpenApiRecord): NormalizedField[]
|
|
|
766
766
|
const headers = toRecord(response.headers);
|
|
767
767
|
if (!headers) return [];
|
|
768
768
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
769
|
+
const fields: NormalizedField[] = [];
|
|
770
|
+
for (const [headerName, rawHeader] of Object.entries(headers)) {
|
|
771
|
+
const headerObject = toRecord(rawHeader);
|
|
772
|
+
if (!headerObject) continue;
|
|
773
|
+
|
|
774
|
+
const schema = headerObjectSchema(headerObject);
|
|
775
|
+
const description = typeof headerObject.description === 'string'
|
|
776
|
+
? headerObject.description
|
|
777
|
+
: (typeof schema?.description === 'string' ? schema.description : undefined);
|
|
778
|
+
|
|
779
|
+
fields.push({
|
|
780
|
+
name: headerName,
|
|
781
|
+
type: resolveSchemaType(schema),
|
|
782
|
+
required: Boolean(headerObject.required),
|
|
783
|
+
deprecated: Boolean(headerObject.deprecated ?? schema?.deprecated),
|
|
784
|
+
defaultValue: stringifyDefaultValue(schema?.default),
|
|
785
|
+
description,
|
|
786
|
+
} satisfies NormalizedField);
|
|
787
|
+
}
|
|
788
|
+
return fields;
|
|
789
789
|
}
|
|
790
790
|
|
|
791
791
|
function renderAuthorizationSection(method: MethodInformation, ctx: RenderContext): ReactNode {
|
|
@@ -1591,6 +1591,11 @@ export async function VeluOpenAPI({
|
|
|
1591
1591
|
&& normalizeWebhookName(resolvedTarget.item.name) !== normalizeWebhookName(endpointPath))
|
|
1592
1592
|
),
|
|
1593
1593
|
);
|
|
1594
|
+
const fallbackTargetLabel = resolvedTarget
|
|
1595
|
+
? (resolvedTarget.type === 'operation'
|
|
1596
|
+
? `${resolvedTarget.item.method.toUpperCase()} ${resolvedTarget.item.path}`
|
|
1597
|
+
: `WEBHOOK ${resolvedTarget.item.name}`)
|
|
1598
|
+
: '';
|
|
1594
1599
|
|
|
1595
1600
|
return (
|
|
1596
1601
|
<section className={className}>
|
|
@@ -1598,9 +1603,7 @@ export async function VeluOpenAPI({
|
|
|
1598
1603
|
<div className="velu-openapi-warning">
|
|
1599
1604
|
<p>
|
|
1600
1605
|
Could not find <code>{endpointMethod.toUpperCase()} {endpointPath}</code> in <code>{inlineDocument ? inlineDocumentId : schemaSource}</code>.
|
|
1601
|
-
Showing <code>{
|
|
1602
|
-
? `${resolvedTarget.item.method.toUpperCase()} ${resolvedTarget.item.path}`
|
|
1603
|
-
: `WEBHOOK ${resolvedTarget.item.name}`}</code> instead.
|
|
1606
|
+
Showing <code>{fallbackTargetLabel}</code> instead.
|
|
1604
1607
|
</p>
|
|
1605
1608
|
</div>
|
|
1606
1609
|
) : null}
|
package/src/engine/lib/source.ts
CHANGED
|
@@ -99,9 +99,9 @@ function openApiSidebarMethodBadgePlugin() {
|
|
|
99
99
|
|
|
100
100
|
export const source = loader({
|
|
101
101
|
baseUrl: '/',
|
|
102
|
-
source: docsCollection.toFumadocsSource(),
|
|
102
|
+
source: docsCollection.toFumadocsSource() as any,
|
|
103
103
|
plugins: [
|
|
104
|
-
openApiSidebarMethodBadgePlugin(),
|
|
104
|
+
openApiSidebarMethodBadgePlugin() as any,
|
|
105
105
|
statusBadgesPlugin({
|
|
106
106
|
renderBadge: (status: string) => {
|
|
107
107
|
const normalized = status.trim().toLowerCase();
|