jamdesk 1.1.5 → 1.1.6
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/lib/deps.js +3 -3
- package/dist/lib/deps.js.map +1 -1
- package/package.json +1 -1
- package/vendored/app/[[...slug]]/page.tsx +2 -2
- package/vendored/app/layout.tsx +3 -6
- package/vendored/components/navigation/SocialFooter.tsx +10 -5
- package/vendored/hooks/useShikiHighlight.ts +1 -11
- package/vendored/lib/docs-types.ts +12 -3
- package/vendored/lib/docs.ts +80 -2
- package/vendored/lib/isr-build-executor.ts +10 -0
- package/vendored/lib/middleware-helpers.ts +10 -3
- package/vendored/lib/shiki-client.ts +64 -15
- package/vendored/lib/static-artifacts.ts +82 -3
- package/vendored/lib/static-file-route.ts +4 -4
- package/vendored/scripts/dev-project.cjs +3 -10
- package/vendored/scripts/enhance-navigation.cjs +16 -28
- package/vendored/themes/base.css +0 -2
package/dist/lib/deps.js
CHANGED
|
@@ -20,13 +20,13 @@ const DEPS_DIR = path.join(JAMDESK_DIR, 'node_modules');
|
|
|
20
20
|
// Match build-service versions exactly
|
|
21
21
|
const REQUIRED_DEPS = {
|
|
22
22
|
// Next.js and React
|
|
23
|
-
'next': '
|
|
23
|
+
'next': '16.1.7',
|
|
24
24
|
// OpenAPI validation (for API reference docs)
|
|
25
25
|
'@apidevtools/swagger-parser': '^12.1.0',
|
|
26
26
|
'openapi-types': '^12.1.3',
|
|
27
27
|
'react': '^19.2.4',
|
|
28
28
|
'react-dom': '^19.2.4',
|
|
29
|
-
'@next/mdx': '
|
|
29
|
+
'@next/mdx': '16.1.7',
|
|
30
30
|
'next-mdx-remote': '^6.0.0',
|
|
31
31
|
'next-themes': '^0.4.6',
|
|
32
32
|
// Icons
|
|
@@ -85,7 +85,7 @@ const REQUIRED_DEPS = {
|
|
|
85
85
|
'@types/node': '^25.5.0',
|
|
86
86
|
'@types/react': '^19.2.14',
|
|
87
87
|
'@types/react-dom': '^19.0.0',
|
|
88
|
-
'@next/third-parties': '
|
|
88
|
+
'@next/third-parties': '16.1.7',
|
|
89
89
|
};
|
|
90
90
|
/**
|
|
91
91
|
* Generate a hash of REQUIRED_DEPS to detect when dependencies change.
|
package/dist/lib/deps.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/lib/deps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,UAAU,GAAG,OAAO,CAAC,oBAAoB,CAAwB,CAAC;AAExE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;AAExD,uCAAuC;AACvC,MAAM,aAAa,GAA2B;IAC5C,oBAAoB;IACpB,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/lib/deps.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jamdesk",
|
|
@@ -714,7 +714,7 @@ export default async function DocPage({ params }: PageProps) {
|
|
|
714
714
|
|
|
715
715
|
{/* Previous/Next Navigation */}
|
|
716
716
|
<PageNavigation currentSlug={slug.join('/')} config={config} />
|
|
717
|
-
<SocialFooter config={config} hidden={data.hideFooter} />
|
|
717
|
+
<SocialFooter config={config} hidden={data.hideFooter} projectSlug={projectSlug ?? undefined} />
|
|
718
718
|
</article>
|
|
719
719
|
</ApiPageWrapper></>
|
|
720
720
|
);
|
|
@@ -771,7 +771,7 @@ export default async function DocPage({ params }: PageProps) {
|
|
|
771
771
|
{/* Previous/Next Navigation - inside prose to match content width */}
|
|
772
772
|
<PageNavigation currentSlug={slug.join('/')} config={config} isWideMode={isWideMode || hasPanel} />
|
|
773
773
|
</div>
|
|
774
|
-
<SocialFooter config={config} hidden={data.hideFooter} />
|
|
774
|
+
<SocialFooter config={config} hidden={data.hideFooter} projectSlug={projectSlug ?? undefined} />
|
|
775
775
|
</article>
|
|
776
776
|
);
|
|
777
777
|
|
package/vendored/app/layout.tsx
CHANGED
|
@@ -42,11 +42,8 @@ const DEFAULT_FAVICON = `${ASSET_PREFIX}/branding/favicon.svg`;
|
|
|
42
42
|
*/
|
|
43
43
|
function getFaviconPath(favicon: Favicon | undefined, assetVersion?: string): string {
|
|
44
44
|
if (!favicon) return DEFAULT_FAVICON;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
// Object format: return light variant (dark mode handled client-side)
|
|
49
|
-
return transformConfigImagePath(favicon.light, assetVersion) || DEFAULT_FAVICON;
|
|
45
|
+
const raw = typeof favicon === 'string' ? favicon : favicon.light;
|
|
46
|
+
return transformConfigImagePath(raw, assetVersion) || DEFAULT_FAVICON;
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
const FALLBACK_METADATA: Metadata = {
|
|
@@ -350,7 +347,7 @@ export default async function RootLayout({
|
|
|
350
347
|
: null;
|
|
351
348
|
|
|
352
349
|
return (
|
|
353
|
-
<html lang="en" suppressHydrationWarning>
|
|
350
|
+
<html lang="en" suppressHydrationWarning data-scroll-behavior="smooth">
|
|
354
351
|
<head>
|
|
355
352
|
{/* Add viewport meta for mobile */}
|
|
356
353
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
@@ -7,18 +7,20 @@ import { getIconClass } from '@/lib/icon-utils';
|
|
|
7
7
|
// Branding configuration (read at build time from environment variables)
|
|
8
8
|
// Uses NEXT_PUBLIC_ prefix so these are inlined during build for client components
|
|
9
9
|
const showBranding = process.env.NEXT_PUBLIC_SHOW_BRANDING !== 'false'; // Default true
|
|
10
|
-
const projectSlug = process.env.NEXT_PUBLIC_PROJECT_SLUG || 'docs';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
|
-
* Generate branding URL with UTM parameters for attribution tracking
|
|
12
|
+
* Generate branding URL with UTM parameters for attribution tracking.
|
|
13
|
+
* projectSlug is passed as a prop (works in ISR multi-tenant mode).
|
|
14
|
+
* Falls back to NEXT_PUBLIC_PROJECT_SLUG env var (works in CLI dev mode).
|
|
14
15
|
*/
|
|
15
|
-
function getBrandingUrl(): string {
|
|
16
|
+
function getBrandingUrl(projectSlug: string): string {
|
|
16
17
|
return `https://www.jamdesk.com?utm_campaign=poweredBy&utm_medium=referral&utm_source=${projectSlug}`;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
interface SocialFooterProps {
|
|
20
21
|
config: DocsConfig;
|
|
21
22
|
hidden?: boolean;
|
|
23
|
+
projectSlug?: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -134,7 +136,7 @@ function SocialIcons({ socials }: { socials: Partial<Record<SocialPlatform, stri
|
|
|
134
136
|
);
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
export function SocialFooter({ config, hidden }: SocialFooterProps) {
|
|
139
|
+
export function SocialFooter({ config, hidden, projectSlug }: SocialFooterProps) {
|
|
138
140
|
if (hidden) return null;
|
|
139
141
|
|
|
140
142
|
const { socials, links } = config.footer || {};
|
|
@@ -148,6 +150,9 @@ export function SocialFooter({ config, hidden }: SocialFooterProps) {
|
|
|
148
150
|
return null;
|
|
149
151
|
}
|
|
150
152
|
|
|
153
|
+
// Resolve slug: prop (ISR) → env var (CLI dev) → fallback
|
|
154
|
+
const slug = projectSlug || process.env.NEXT_PUBLIC_PROJECT_SLUG || 'docs';
|
|
155
|
+
|
|
151
156
|
return (
|
|
152
157
|
<footer className="mt-12 sm:mt-16 pt-6 sm:pt-8 border-t border-[var(--color-border)]">
|
|
153
158
|
{hasLinks && <LinkColumns columns={links} />}
|
|
@@ -156,7 +161,7 @@ export function SocialFooter({ config, hidden }: SocialFooterProps) {
|
|
|
156
161
|
{hasSocials && <SocialIcons socials={socials} />}
|
|
157
162
|
{showBranding && (
|
|
158
163
|
<a
|
|
159
|
-
href={getBrandingUrl()}
|
|
164
|
+
href={getBrandingUrl(slug)}
|
|
160
165
|
target="_blank"
|
|
161
166
|
rel="noopener noreferrer"
|
|
162
167
|
className="group flex items-center gap-1 text-sm text-[#AEAEAE] hover:text-[#6A6D70] transition-colors whitespace-nowrap"
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
-
import { highlightCode } from '@/lib/shiki-client';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Escape HTML entities for fallback rendering
|
|
8
|
-
*/
|
|
9
|
-
function escapeHtml(text: string): string {
|
|
10
|
-
return text
|
|
11
|
-
.replace(/&/g, '&')
|
|
12
|
-
.replace(/</g, '<')
|
|
13
|
-
.replace(/>/g, '>');
|
|
14
|
-
}
|
|
4
|
+
import { escapeHtml, highlightCode } from '@/lib/shiki-client';
|
|
15
5
|
|
|
16
6
|
/**
|
|
17
7
|
* Hook for async Shiki syntax highlighting
|
|
@@ -866,16 +866,25 @@ export function appendAssetVersion(url: string, assetVersion?: string): string {
|
|
|
866
866
|
* Transform an image path from /images/... to /_jd/images/...
|
|
867
867
|
*
|
|
868
868
|
* Used for config-defined images (favicon, logo) that need the asset prefix.
|
|
869
|
+
* Also normalizes relative paths and strips /public/ prefix (Next.js convention).
|
|
869
870
|
*/
|
|
870
871
|
export function transformConfigImagePath(
|
|
871
872
|
path: string | undefined,
|
|
872
873
|
assetVersion?: string,
|
|
873
874
|
): string | undefined {
|
|
874
875
|
if (!path) return path;
|
|
875
|
-
|
|
876
|
-
|
|
876
|
+
// Skip external URLs
|
|
877
|
+
if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('//')) {
|
|
878
|
+
return path;
|
|
877
879
|
}
|
|
878
|
-
|
|
880
|
+
// Ensure absolute path — relative paths break on nested routes
|
|
881
|
+
let normalized = path.startsWith('/') ? path : `/${path}`;
|
|
882
|
+
// Strip /public/ prefix (Next.js convention: public/logo.png served at /logo.png)
|
|
883
|
+
normalized = normalized.replace(/^\/public\//, '/');
|
|
884
|
+
if (normalized.startsWith('/images/')) {
|
|
885
|
+
return appendAssetVersion(ASSET_PREFIX + normalized, assetVersion);
|
|
886
|
+
}
|
|
887
|
+
return normalized;
|
|
879
888
|
}
|
|
880
889
|
|
|
881
890
|
/**
|
package/vendored/lib/docs.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import * as fsPromises from 'fs/promises';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import matter from 'gray-matter';
|
|
4
5
|
|
|
5
6
|
// Re-export types from client-safe module
|
|
6
7
|
export type {
|
|
@@ -17,6 +18,11 @@ export { normalizeNavPage } from './docs-types';
|
|
|
17
18
|
|
|
18
19
|
import type { DocsConfig } from './docs-types';
|
|
19
20
|
import { normalizeConfig } from './normalize-config';
|
|
21
|
+
import {
|
|
22
|
+
enhanceConfigNavigation,
|
|
23
|
+
RECURSE_KEYS,
|
|
24
|
+
type PageInfo,
|
|
25
|
+
} from './enhance-navigation';
|
|
20
26
|
|
|
21
27
|
/**
|
|
22
28
|
* Get the current project name from environment variable.
|
|
@@ -85,6 +91,70 @@ export function getImagesDir(): string {
|
|
|
85
91
|
return path.join(process.cwd(), 'content', 'images');
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Collect all unique page paths from a navigation structure.
|
|
96
|
+
* Walks pages arrays for leaf entries and recurses into groups/tabs/anchors/etc.
|
|
97
|
+
*/
|
|
98
|
+
function collectNavPagePaths(nav: unknown, paths = new Set<string>()): Set<string> {
|
|
99
|
+
if (!nav || typeof nav !== 'object') return paths;
|
|
100
|
+
const obj = nav as Record<string, unknown>;
|
|
101
|
+
|
|
102
|
+
if (Array.isArray(obj.pages)) {
|
|
103
|
+
for (const item of obj.pages) {
|
|
104
|
+
if (typeof item === 'string') {
|
|
105
|
+
paths.add(item);
|
|
106
|
+
} else if (item && typeof item === 'object') {
|
|
107
|
+
if ('page' in item) paths.add((item as { page: string }).page);
|
|
108
|
+
if ('group' in item) collectNavPagePaths(item, paths);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const key of RECURSE_KEYS) {
|
|
114
|
+
if (Array.isArray(obj[key])) {
|
|
115
|
+
for (const child of obj[key] as unknown[]) {
|
|
116
|
+
collectNavPagePaths(child, paths);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return paths;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Extract just the YAML frontmatter from an MDX file without parsing the full body.
|
|
126
|
+
* Reads only the frontmatter block (between --- delimiters) to avoid loading
|
|
127
|
+
* potentially large MDX content into memory.
|
|
128
|
+
*/
|
|
129
|
+
function readFrontmatterOnly(filePath: string): Record<string, unknown> {
|
|
130
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
131
|
+
if (!raw.startsWith('---')) return {};
|
|
132
|
+
const endMatch = raw.slice(3).match(/\n---(\r?\n|$)/);
|
|
133
|
+
if (!endMatch) return {};
|
|
134
|
+
// Pass only the frontmatter slice to gray-matter
|
|
135
|
+
const fmEnd = 3 + endMatch.index! + endMatch[0].length;
|
|
136
|
+
const { data } = matter(raw.slice(0, fmEnd));
|
|
137
|
+
return data;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Enhance navigation config with frontmatter metadata (titles, API methods, icons).
|
|
142
|
+
* Reads MDX frontmatter for all nav pages and merges into the config.
|
|
143
|
+
*/
|
|
144
|
+
function enhanceNavInDev(config: DocsConfig): DocsConfig {
|
|
145
|
+
const contentDir = getContentDir();
|
|
146
|
+
const pagePaths = collectNavPagePaths(config.navigation);
|
|
147
|
+
const pageInfos: PageInfo[] = [...pagePaths].map((pagePath) => {
|
|
148
|
+
const mdxPath = path.join(contentDir, pagePath + '.mdx');
|
|
149
|
+
try {
|
|
150
|
+
return { path: pagePath, frontmatter: readFrontmatterOnly(mdxPath), content: '' };
|
|
151
|
+
} catch {
|
|
152
|
+
return { path: pagePath, frontmatter: {}, content: '' };
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
return enhanceConfigNavigation(config, pageInfos);
|
|
156
|
+
}
|
|
157
|
+
|
|
88
158
|
// Cache the config to avoid re-parsing 368KB+ JSON for every page during SSR.
|
|
89
159
|
// In development, reads from the project source (not public/docs.json) and uses
|
|
90
160
|
// mtime-based invalidation so docs.json edits are picked up on browser refresh
|
|
@@ -108,11 +178,19 @@ export function getDocsConfig(): DocsConfig {
|
|
|
108
178
|
}
|
|
109
179
|
}
|
|
110
180
|
|
|
181
|
+
// Capture mtime before reading so we don't need a second statSync
|
|
182
|
+
const currentMtimeMs = fs.statSync(docsPath).mtimeMs;
|
|
111
183
|
const fileContents = fs.readFileSync(docsPath, 'utf8');
|
|
112
184
|
const rawConfig = JSON.parse(fileContents);
|
|
113
185
|
|
|
114
186
|
// Normalize Mintlify fields to Jamdesk format
|
|
115
|
-
|
|
187
|
+
let { config, warnings } = normalizeConfig(rawConfig);
|
|
188
|
+
|
|
189
|
+
// In dev, enhance navigation with frontmatter (API methods, sidebarTitles, icons).
|
|
190
|
+
// In production/ISR, this is done during the build pipeline before R2 upload.
|
|
191
|
+
if (isDev) {
|
|
192
|
+
config = enhanceNavInDev(config);
|
|
193
|
+
}
|
|
116
194
|
|
|
117
195
|
// Log deprecation warnings (only once due to caching)
|
|
118
196
|
if (warnings.length > 0 && !deprecationWarningsLogged) {
|
|
@@ -123,7 +201,7 @@ export function getDocsConfig(): DocsConfig {
|
|
|
123
201
|
}
|
|
124
202
|
|
|
125
203
|
cachedConfig = config;
|
|
126
|
-
cachedMtimeMs =
|
|
204
|
+
cachedMtimeMs = currentMtimeMs;
|
|
127
205
|
return cachedConfig;
|
|
128
206
|
}
|
|
129
207
|
|
|
@@ -70,6 +70,16 @@ export async function collectAssetFiles(projectDir: string): Promise<string[]> {
|
|
|
70
70
|
return files;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Normalize an asset path for R2 storage.
|
|
75
|
+
*
|
|
76
|
+
* Strips leading public/ prefix to match Next.js convention where files in
|
|
77
|
+
* public/ are served at the root URL path (e.g., public/logo.png → /logo.png).
|
|
78
|
+
*/
|
|
79
|
+
export function normalizeAssetPath(assetPath: string): string {
|
|
80
|
+
return assetPath.replace(/^public\//, '');
|
|
81
|
+
}
|
|
82
|
+
|
|
73
83
|
/**
|
|
74
84
|
* Collect snippet files from project directory.
|
|
75
85
|
*
|
|
@@ -338,6 +338,9 @@ const STATIC_ASSET_PREFIXES = [
|
|
|
338
338
|
`${ASSET_PREFIX}/fonts/`, // Font Awesome web fonts
|
|
339
339
|
] as const;
|
|
340
340
|
|
|
341
|
+
/** Pattern to strip the /_jd/ prefix from asset paths */
|
|
342
|
+
const ASSET_PREFIX_PATTERN = new RegExp(`^${ASSET_PREFIX}/`);
|
|
343
|
+
|
|
341
344
|
/**
|
|
342
345
|
* Check if middleware should run for this path.
|
|
343
346
|
*
|
|
@@ -535,8 +538,12 @@ export function isAssetRequest(pathname: string): boolean {
|
|
|
535
538
|
* @returns Assets API path (e.g., '/api/assets/images/logo.png')
|
|
536
539
|
*/
|
|
537
540
|
export function getAssetsApiPath(pathname: string): string {
|
|
538
|
-
// Strip
|
|
539
|
-
|
|
540
|
-
|
|
541
|
+
// Strip /_jd/ prefix and leading slash, then normalize away public/ prefix.
|
|
542
|
+
// Users may reference assets as /public/logo.png (mirroring file-system layout)
|
|
543
|
+
// or /logo.png (Next.js convention) — both resolve to the same R2 key.
|
|
544
|
+
const assetPath = pathname
|
|
545
|
+
.replace(ASSET_PREFIX_PATTERN, '')
|
|
546
|
+
.replace(/^\//, '')
|
|
547
|
+
.replace(/^public\//, '');
|
|
541
548
|
return `/api/assets/${assetPath}`;
|
|
542
549
|
}
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-side Shiki syntax highlighting utility
|
|
3
3
|
* Used for dynamic code highlighting (e.g., OpenAPI code examples)
|
|
4
|
+
*
|
|
5
|
+
* Uses shiki/core + JavaScript regex engine to avoid WASM loading.
|
|
6
|
+
* Turbopack can't bundle WASM files for client delivery (Next.js #84972),
|
|
7
|
+
* so the default shiki import (which uses Oniguruma WASM) causes ChunkLoadError.
|
|
8
|
+
* The JS engine handles all client languages perfectly without WASM.
|
|
4
9
|
*/
|
|
5
10
|
'use client';
|
|
6
11
|
|
|
7
|
-
import type {
|
|
12
|
+
import type { HighlighterCore } from 'shiki/core';
|
|
13
|
+
import type { BundledLanguage, BundledTheme } from 'shiki';
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
// Cache the Promise (not the resolved value) to prevent duplicate instances
|
|
16
|
+
// when concurrent callers both see null before the first resolves.
|
|
17
|
+
// Matches the pattern in shiki-highlighter.ts (server-side).
|
|
18
|
+
let highlighterPromise: Promise<HighlighterCore> | null = null;
|
|
19
|
+
// Separate resolved reference for synchronous access (highlightCodeSync)
|
|
20
|
+
let highlighterInstance: HighlighterCore | null = null;
|
|
10
21
|
|
|
11
22
|
// Languages needed for API code examples
|
|
12
23
|
const CLIENT_LANGUAGES: BundledLanguage[] = [
|
|
@@ -25,24 +36,54 @@ const THEMES = {
|
|
|
25
36
|
};
|
|
26
37
|
|
|
27
38
|
/**
|
|
28
|
-
* Get or initialize the Shiki highlighter for client-side use
|
|
39
|
+
* Get or initialize the Shiki highlighter for client-side use.
|
|
40
|
+
* Uses shiki/core with JavaScript engine (no WASM) for Turbopack compatibility.
|
|
29
41
|
*/
|
|
30
|
-
async function getHighlighter(): Promise<
|
|
31
|
-
if (!
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
async function getHighlighter(): Promise<HighlighterCore> {
|
|
43
|
+
if (!highlighterPromise) {
|
|
44
|
+
highlighterPromise = (async () => {
|
|
45
|
+
const [
|
|
46
|
+
{ createHighlighterCore },
|
|
47
|
+
{ createJavaScriptRegexEngine },
|
|
48
|
+
] = await Promise.all([
|
|
49
|
+
import('shiki/core'),
|
|
50
|
+
import('shiki/engine/javascript'),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
// Keep in sync with CLIENT_LANGUAGES above
|
|
54
|
+
const hl = await createHighlighterCore({
|
|
55
|
+
themes: [
|
|
56
|
+
import('shiki/themes/github-dark-default.mjs'),
|
|
57
|
+
import('shiki/themes/github-light-default.mjs'),
|
|
58
|
+
],
|
|
59
|
+
langs: [
|
|
60
|
+
import('shiki/langs/javascript.mjs'),
|
|
61
|
+
import('shiki/langs/typescript.mjs'),
|
|
62
|
+
import('shiki/langs/python.mjs'),
|
|
63
|
+
import('shiki/langs/bash.mjs'),
|
|
64
|
+
import('shiki/langs/json.mjs'),
|
|
65
|
+
import('shiki/langs/shell.mjs'),
|
|
66
|
+
],
|
|
67
|
+
engine: createJavaScriptRegexEngine(),
|
|
68
|
+
});
|
|
69
|
+
highlighterInstance = hl;
|
|
70
|
+
return hl;
|
|
71
|
+
})().catch((err) => {
|
|
72
|
+
// Clear cached state so next call retries instead of returning
|
|
73
|
+
// the same rejected promise forever (no page reload needed)
|
|
74
|
+
highlighterPromise = null;
|
|
75
|
+
highlighterInstance = null;
|
|
76
|
+
throw err;
|
|
37
77
|
});
|
|
38
78
|
}
|
|
39
|
-
return
|
|
79
|
+
return highlighterPromise;
|
|
40
80
|
}
|
|
41
81
|
|
|
42
82
|
/**
|
|
43
|
-
* Escape HTML entities for fallback rendering
|
|
83
|
+
* Escape HTML entities for fallback rendering.
|
|
84
|
+
* Exported for reuse by useShikiHighlight hook.
|
|
44
85
|
*/
|
|
45
|
-
function escapeHtml(text: string): string {
|
|
86
|
+
export function escapeHtml(text: string): string {
|
|
46
87
|
return text
|
|
47
88
|
.replace(/&/g, '&')
|
|
48
89
|
.replace(/</g, '<')
|
|
@@ -56,14 +97,14 @@ function escapeHtml(text: string): string {
|
|
|
56
97
|
* This is useful for initial render - the component can re-render when highlighting is done
|
|
57
98
|
*/
|
|
58
99
|
export function highlightCodeSync(code: string, language: string): string {
|
|
59
|
-
if (!
|
|
100
|
+
if (!highlighterInstance) {
|
|
60
101
|
// Return escaped code if highlighter not ready yet
|
|
61
102
|
return escapeHtml(code);
|
|
62
103
|
}
|
|
63
104
|
|
|
64
105
|
try {
|
|
65
106
|
const lang = normalizeLanguage(language);
|
|
66
|
-
return
|
|
107
|
+
return highlighterInstance.codeToHtml(code, {
|
|
67
108
|
lang,
|
|
68
109
|
themes: THEMES,
|
|
69
110
|
defaultColor: 'dark',
|
|
@@ -104,6 +145,14 @@ export async function preloadHighlighter(): Promise<void> {
|
|
|
104
145
|
await getHighlighter();
|
|
105
146
|
}
|
|
106
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Reset the highlighter cache (useful for testing).
|
|
150
|
+
*/
|
|
151
|
+
export function resetClientHighlighterCache(): void {
|
|
152
|
+
highlighterPromise = null;
|
|
153
|
+
highlighterInstance = null;
|
|
154
|
+
}
|
|
155
|
+
|
|
107
156
|
/**
|
|
108
157
|
* Normalize language aliases
|
|
109
158
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Static Artifact Generator for ISR Mode
|
|
3
3
|
*
|
|
4
|
-
* Generates sitemap.xml, llms.txt, robots.txt, feed.xml, and search-data.json
|
|
4
|
+
* Generates sitemap.xml, llms.txt, llms-full.txt, robots.txt, feed.xml, and search-data.json
|
|
5
5
|
* for ISR projects. These are uploaded to R2 alongside the MDX content.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -249,6 +249,79 @@ export function extractNavigationPaths(
|
|
|
249
249
|
return paths;
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Page info with content for llms-full.txt generation.
|
|
254
|
+
*/
|
|
255
|
+
export interface LlmsFullPageInfo {
|
|
256
|
+
/** File path with extension */
|
|
257
|
+
path: string;
|
|
258
|
+
/** Raw MDX content (frontmatter stripped) */
|
|
259
|
+
content: string;
|
|
260
|
+
/** Frontmatter from MDX file */
|
|
261
|
+
frontmatter: {
|
|
262
|
+
title?: string;
|
|
263
|
+
description?: string;
|
|
264
|
+
noindex?: boolean;
|
|
265
|
+
hidden?: boolean;
|
|
266
|
+
seo?: {
|
|
267
|
+
noindex?: boolean;
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Options for generating llms-full.txt.
|
|
274
|
+
*/
|
|
275
|
+
export interface LlmsFullTxtOptions {
|
|
276
|
+
/** Documentation name */
|
|
277
|
+
name: string;
|
|
278
|
+
/** Pages with full content */
|
|
279
|
+
pages: LlmsFullPageInfo[];
|
|
280
|
+
/** Block all crawlers - generates empty file */
|
|
281
|
+
noindex?: boolean;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Generate llms-full.txt with complete documentation content for LLM context windows.
|
|
286
|
+
* Follows https://llmstxt.org/ spec.
|
|
287
|
+
*/
|
|
288
|
+
export function generateLlmsFullTxt(options: LlmsFullTxtOptions): string {
|
|
289
|
+
const { name, pages, noindex = false } = options;
|
|
290
|
+
|
|
291
|
+
if (noindex) {
|
|
292
|
+
return '';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const visiblePages = pages.filter(p =>
|
|
296
|
+
!p.frontmatter.noindex && !p.frontmatter.hidden && !p.frontmatter.seo?.noindex
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const parts: string[] = [
|
|
300
|
+
`# ${name} - Complete Documentation\n\n`,
|
|
301
|
+
`> This file contains the complete documentation for ${name}.\n`,
|
|
302
|
+
`> Total pages: ${visiblePages.length}\n\n`,
|
|
303
|
+
`---\n\n`,
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
for (const page of visiblePages) {
|
|
307
|
+
const title = page.frontmatter.title || page.path.replace(/\.mdx?$/, '');
|
|
308
|
+
parts.push(`## ${title}\n\n`);
|
|
309
|
+
|
|
310
|
+
if (page.frontmatter.description) {
|
|
311
|
+
parts.push(`*${page.frontmatter.description}*\n\n`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const content = page.content.trim();
|
|
315
|
+
if (content) {
|
|
316
|
+
parts.push(`${content}\n\n`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
parts.push(`---\n\n`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return parts.join('').trim();
|
|
323
|
+
}
|
|
324
|
+
|
|
252
325
|
/**
|
|
253
326
|
* Options for generating all artifacts.
|
|
254
327
|
*/
|
|
@@ -269,6 +342,8 @@ export interface GenerateAllOptions {
|
|
|
269
342
|
noindex?: boolean;
|
|
270
343
|
/** Pages with rss: true frontmatter (for RSS feed generation) */
|
|
271
344
|
rssPages?: RssPageInfo[];
|
|
345
|
+
/** Pages with full content (for llms-full.txt generation) */
|
|
346
|
+
llmsFullPages?: LlmsFullPageInfo[];
|
|
272
347
|
}
|
|
273
348
|
|
|
274
349
|
/**
|
|
@@ -277,6 +352,7 @@ export interface GenerateAllOptions {
|
|
|
277
352
|
export interface GeneratedArtifacts {
|
|
278
353
|
sitemap: string;
|
|
279
354
|
llmsTxt: string;
|
|
355
|
+
llmsFullTxt: string;
|
|
280
356
|
robotsTxt: string;
|
|
281
357
|
rssFeed: string | null;
|
|
282
358
|
}
|
|
@@ -289,13 +365,16 @@ export interface GeneratedArtifacts {
|
|
|
289
365
|
*/
|
|
290
366
|
export function generateAllArtifacts(options: GenerateAllOptions): GeneratedArtifacts {
|
|
291
367
|
const {
|
|
292
|
-
baseUrl, name, description, pages, hostAtDocs, noindex, rssPages,
|
|
368
|
+
baseUrl, name, description, pages, hostAtDocs, noindex, rssPages, llmsFullPages,
|
|
293
369
|
} = options;
|
|
294
370
|
|
|
295
371
|
const sitemap = generateSitemap({ baseUrl, pages, hostAtDocs, noindex });
|
|
296
372
|
const llmsTxt = generateLlmsTxt({
|
|
297
373
|
name, description, baseUrl, pages, hostAtDocs, noindex,
|
|
298
374
|
});
|
|
375
|
+
const llmsFullTxt = llmsFullPages
|
|
376
|
+
? generateLlmsFullTxt({ name, pages: llmsFullPages, noindex })
|
|
377
|
+
: '';
|
|
299
378
|
const robotsTxt = generateRobotsTxt({ baseUrl, hostAtDocs, noindex });
|
|
300
379
|
|
|
301
380
|
// Generate RSS feed if any pages have rss: true
|
|
@@ -309,7 +388,7 @@ export function generateAllArtifacts(options: GenerateAllOptions): GeneratedArti
|
|
|
309
388
|
}
|
|
310
389
|
}
|
|
311
390
|
|
|
312
|
-
return { sitemap, llmsTxt, robotsTxt, rssFeed };
|
|
391
|
+
return { sitemap, llmsTxt, llmsFullTxt, robotsTxt, rssFeed };
|
|
313
392
|
}
|
|
314
393
|
|
|
315
394
|
// =============================================================================
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared handler for static file routes (sitemap.xml, robots.txt, llms.txt, search-data.json, feed.xml).
|
|
2
|
+
* Shared handler for static file routes (sitemap.xml, robots.txt, llms.txt, llms-full.txt, search-data.json, feed.xml).
|
|
3
3
|
*
|
|
4
4
|
* All static file routes follow the same pattern: check ISR mode, extract project slug,
|
|
5
5
|
* fetch from R2, and return with appropriate headers. This helper eliminates the
|
|
6
|
-
* duplication across
|
|
6
|
+
* duplication across 12 route files (6 at root + 6 at /docs).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { NextRequest, NextResponse } from 'next/server';
|
|
@@ -12,9 +12,9 @@ import { log } from '@/lib/logger';
|
|
|
12
12
|
import { isIsrMode } from '@/lib/page-isr-helpers';
|
|
13
13
|
import { fetchStaticFile } from '@/lib/r2-content';
|
|
14
14
|
|
|
15
|
-
/** Filenames served via createStaticFileHandler (
|
|
15
|
+
/** Filenames served via createStaticFileHandler (6 at root + 6 at /docs). */
|
|
16
16
|
export const STATIC_FILE_NAMES = [
|
|
17
|
-
'sitemap.xml', 'robots.txt', 'llms.txt', 'feed.xml', 'search-data.json',
|
|
17
|
+
'sitemap.xml', 'robots.txt', 'llms.txt', 'llms-full.txt', 'feed.xml', 'search-data.json',
|
|
18
18
|
] as const;
|
|
19
19
|
|
|
20
20
|
/** All CDN paths for static file routes — used by revalidation to purge CDN cache. */
|
|
@@ -423,15 +423,10 @@ async function runDev() {
|
|
|
423
423
|
env,
|
|
424
424
|
});
|
|
425
425
|
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
execSync(`node scripts/enhance-navigation.cjs --project ${effectiveProjectName}`, {
|
|
429
|
-
stdio: 'inherit',
|
|
430
|
-
cwd: path.join(__dirname, '..'),
|
|
431
|
-
env,
|
|
432
|
-
});
|
|
426
|
+
// Navigation enhancement (titles, API method badges) is now handled inline
|
|
427
|
+
// by getDocsConfig() in dev mode — no separate startup step needed.
|
|
433
428
|
|
|
434
|
-
// Step 2.
|
|
429
|
+
// Step 2.5: Compile snippets (if project has snippets folder)
|
|
435
430
|
const snippetsDir = path.join(projectDir, 'snippets');
|
|
436
431
|
if (fs.existsSync(snippetsDir)) {
|
|
437
432
|
console.log('🧩 Compiling snippets...');
|
|
@@ -461,11 +456,9 @@ async function runDev() {
|
|
|
461
456
|
console.log(`\n🚀 Starting dev server on port ${port}...`);
|
|
462
457
|
// Get first page and OpenAPI config from docs.json (reuse projectDocsJson from step 1)
|
|
463
458
|
let firstPage = 'introduction';
|
|
464
|
-
let hasOpenApi = false;
|
|
465
459
|
try {
|
|
466
460
|
const docsConfig = JSON.parse(fs.readFileSync(projectDocsJson, 'utf-8'));
|
|
467
461
|
firstPage = getFirstPage(docsConfig);
|
|
468
|
-
hasOpenApi = Boolean(docsConfig.api?.openapi);
|
|
469
462
|
} catch {
|
|
470
463
|
// Fall back to defaults if docs.json can't be read
|
|
471
464
|
}
|
|
@@ -98,42 +98,30 @@ function parseOpenApiMethod(openapiField) {
|
|
|
98
98
|
/**
|
|
99
99
|
* Collect all page paths from navigation structure
|
|
100
100
|
*/
|
|
101
|
+
// Keys whose children are sub-nodes (must stay in sync with enhance-navigation.ts RECURSE_KEYS)
|
|
102
|
+
const RECURSE_KEYS = ['groups', 'tabs', 'anchors', 'dropdowns', 'products', 'versions', 'languages', 'menu'];
|
|
103
|
+
|
|
101
104
|
function collectPagePaths(navigation, paths = new Set()) {
|
|
102
105
|
if (!navigation) return paths;
|
|
103
106
|
|
|
104
|
-
// Handle pages array
|
|
107
|
+
// Handle pages array — contains leaf page entries mixed with nested groups
|
|
105
108
|
if (navigation.pages && Array.isArray(navigation.pages)) {
|
|
106
109
|
for (const page of navigation.pages) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (navigation.groups && Array.isArray(navigation.groups)) {
|
|
114
|
-
for (const group of navigation.groups) {
|
|
115
|
-
collectPagePaths(group, paths);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Handle tabs array
|
|
120
|
-
if (navigation.tabs && Array.isArray(navigation.tabs)) {
|
|
121
|
-
for (const tab of navigation.tabs) {
|
|
122
|
-
collectPagePaths(tab, paths);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Handle anchors array
|
|
127
|
-
if (navigation.anchors && Array.isArray(navigation.anchors)) {
|
|
128
|
-
for (const anchor of navigation.anchors) {
|
|
129
|
-
collectPagePaths(anchor, paths);
|
|
110
|
+
if (page && typeof page === 'object' && 'group' in page) {
|
|
111
|
+
collectPagePaths(page, paths);
|
|
112
|
+
} else {
|
|
113
|
+
const pagePath = typeof page === 'string' ? page : page?.page;
|
|
114
|
+
if (pagePath) paths.add(pagePath);
|
|
115
|
+
}
|
|
130
116
|
}
|
|
131
117
|
}
|
|
132
118
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
119
|
+
// Recurse into all structural keys
|
|
120
|
+
for (const key of RECURSE_KEYS) {
|
|
121
|
+
if (Array.isArray(navigation[key])) {
|
|
122
|
+
for (const child of navigation[key]) {
|
|
123
|
+
collectPagePaths(child, paths);
|
|
124
|
+
}
|
|
137
125
|
}
|
|
138
126
|
}
|
|
139
127
|
|