jamdesk 1.1.28 → 1.1.30
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/README.md +3 -1
- package/dist/lib/deps.js +3 -3
- package/package.json +3 -3
- package/vendored/app/[[...slug]]/page.tsx +36 -109
- package/vendored/app/api/assets/[...path]/route.ts +2 -2
- package/vendored/app/api/chat/[project]/route.ts +206 -138
- package/vendored/app/api/isr-health/route.ts +2 -3
- package/vendored/app/layout.tsx +2 -0
- package/vendored/components/JdReadySentinel.tsx +25 -0
- package/vendored/components/chat/ChatEmptyState.tsx +13 -1
- package/vendored/components/chat/ChatPanel.tsx +43 -9
- package/vendored/components/navigation/Breadcrumb.tsx +2 -2
- package/vendored/components/navigation/Header.tsx +23 -17
- package/vendored/components/navigation/LanguageSelector.tsx +7 -4
- package/vendored/components/navigation/Sidebar.tsx +28 -37
- package/vendored/components/navigation/TabsNav.tsx +1 -1
- package/vendored/hooks/useChat.ts +113 -60
- package/vendored/hooks/useDelayedNavigationSpinner.ts +94 -0
- package/vendored/hooks/useTextStreamPacer.ts +152 -0
- package/vendored/lib/chat-prompt.ts +69 -29
- package/vendored/lib/chat-tools.ts +111 -0
- package/vendored/lib/crisp-bridge.ts +91 -0
- package/vendored/lib/docs-types.ts +4 -0
- package/vendored/lib/embedding-chunker.ts +85 -11
- package/vendored/lib/find-first-nav-page.ts +40 -0
- package/vendored/lib/hedge-strip.ts +29 -0
- package/vendored/lib/middleware-helpers.ts +2 -1
- package/vendored/lib/openapi/lang-spec-path.ts +16 -0
- package/vendored/lib/page-isr-helpers.ts +4 -1
- package/vendored/lib/public-paths-resolver.ts +3 -42
- package/vendored/lib/query-rewriter.ts +91 -0
- package/vendored/lib/ui-strings.ts +52 -0
- package/vendored/lib/vector-store.ts +5 -3
- package/vendored/schema/docs-schema.json +15 -0
- package/vendored/workspace-package-lock.json +88 -88
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<a href="https://www.jamdesk.com">
|
|
3
|
-
<img src="https://www.jamdesk.com/logo-
|
|
3
|
+
<img src="https://www.jamdesk.com/logo-universal.png" width="280" alt="Jamdesk" />
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
@@ -435,6 +435,7 @@ See the [docs.json reference](https://www.jamdesk.com/docs/config/docs-json-refe
|
|
|
435
435
|
|
|
436
436
|
- Node.js v20.0.0+
|
|
437
437
|
- npm v8+ (recommended)
|
|
438
|
+
- macOS, Linux, or Windows (all tested in CI)
|
|
438
439
|
|
|
439
440
|
## Learn More
|
|
440
441
|
|
|
@@ -446,6 +447,7 @@ See the [docs.json reference](https://www.jamdesk.com/docs/config/docs-json-refe
|
|
|
446
447
|
- [OpenAPI](https://www.jamdesk.com/docs/api-reference/openapi-setup)
|
|
447
448
|
- [Deployment](https://www.jamdesk.com/docs/deploy)
|
|
448
449
|
- [npm Package](https://www.npmjs.com/package/jamdesk)
|
|
450
|
+
- [Release History](https://www.npmjs.com/package/jamdesk?activeTab=versions)
|
|
449
451
|
- [Homepage](https://www.jamdesk.com)
|
|
450
452
|
- [Pricing](https://www.jamdesk.com/pricing)
|
|
451
453
|
|
package/dist/lib/deps.js
CHANGED
|
@@ -73,8 +73,8 @@ export const REQUIRED_DEPS = {
|
|
|
73
73
|
'unified': '^11.0.0',
|
|
74
74
|
'unist-util-visit': '^5.0.0',
|
|
75
75
|
// CSS
|
|
76
|
-
'tailwindcss': '^4.2.
|
|
77
|
-
'@tailwindcss/postcss': '^4.2.
|
|
76
|
+
'tailwindcss': '^4.2.4',
|
|
77
|
+
'@tailwindcss/postcss': '^4.2.4',
|
|
78
78
|
'@tailwindcss/typography': '^0.5.10',
|
|
79
79
|
'postcss': '^8.5.10',
|
|
80
80
|
'autoprefixer': '^10.4.24',
|
|
@@ -83,7 +83,7 @@ export const REQUIRED_DEPS = {
|
|
|
83
83
|
'json5': '^2.2.3',
|
|
84
84
|
'glob': '^13.0.6',
|
|
85
85
|
// TypeScript (needed for Next.js to avoid auto-install breaking symlink)
|
|
86
|
-
'typescript': '^6.0.
|
|
86
|
+
'typescript': '^6.0.3',
|
|
87
87
|
'@types/node': '^25.5.2',
|
|
88
88
|
'@types/react': '^19.2.14',
|
|
89
89
|
'@types/react-dom': '^19.0.0',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.30",
|
|
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",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"json5": "^2.2.3",
|
|
115
115
|
"nspell": "^2.1.5",
|
|
116
116
|
"open": "^11.0.0",
|
|
117
|
-
"ora": "^9.
|
|
117
|
+
"ora": "^9.4.0",
|
|
118
118
|
"tar": "^7.5.9"
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"@types/fs-extra": "^11.0.0",
|
|
123
123
|
"@types/node": "^25.5.0",
|
|
124
124
|
"typescript": "^6.0.2",
|
|
125
|
-
"vitest": "^4.1.
|
|
125
|
+
"vitest": "^4.1.5"
|
|
126
126
|
},
|
|
127
127
|
"engines": {
|
|
128
128
|
"node": ">=20.0.0"
|
|
@@ -74,6 +74,9 @@ import {
|
|
|
74
74
|
type CodeExample,
|
|
75
75
|
type AuthMethod,
|
|
76
76
|
} from '@/lib/openapi';
|
|
77
|
+
import { extractLanguageFromPath, isValidLanguageCode } from '@/lib/language-utils';
|
|
78
|
+
import { findFirstNavPage } from '@/lib/find-first-nav-page';
|
|
79
|
+
import { candidateSpecPaths } from '@/lib/openapi/lang-spec-path';
|
|
77
80
|
import { ApiEndpoint } from '@/components/mdx/ApiEndpoint';
|
|
78
81
|
import { ImagePriorityProvider } from '@/components/mdx/ImagePriorityProvider';
|
|
79
82
|
import { AIActionsMenu } from '@/components/AIActionsMenu';
|
|
@@ -182,100 +185,19 @@ function getAllDocPaths(): string[] {
|
|
|
182
185
|
return paths;
|
|
183
186
|
}
|
|
184
187
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// Helper to extract page path from a page entry
|
|
192
|
-
const getPagePath = (page: unknown): string | null => {
|
|
193
|
-
if (typeof page === 'string') return page;
|
|
194
|
-
if (typeof page === 'object' && page && 'page' in page) {
|
|
195
|
-
return (page as { page: string }).page;
|
|
196
|
-
}
|
|
197
|
-
return null;
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
// Helper to extract first page from a group
|
|
201
|
-
const extractFirstFromGroup = (group: { pages?: unknown[] }): string | null => {
|
|
202
|
-
const itemPages = group.pages || [];
|
|
203
|
-
for (const page of itemPages) {
|
|
204
|
-
if (typeof page === 'object' && page && 'group' in page) {
|
|
205
|
-
const result = extractFirstFromGroup(page as { pages?: unknown[] });
|
|
206
|
-
if (result) return result;
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
const pagePath = getPagePath(page);
|
|
210
|
-
if (pagePath) return pagePath;
|
|
211
|
-
}
|
|
212
|
-
return null;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
// Check languages first (multi-language navigation structure)
|
|
216
|
-
if (navigation.languages && navigation.languages.length > 0) {
|
|
217
|
-
const firstLang = navigation.languages[0];
|
|
218
|
-
if (firstLang.tabs && firstLang.tabs.length > 0) {
|
|
219
|
-
for (const tab of firstLang.tabs) {
|
|
220
|
-
if (tab.groups) {
|
|
221
|
-
for (const group of tab.groups) {
|
|
222
|
-
const result = extractFirstFromGroup(group);
|
|
223
|
-
if (result) return result;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Check tabs first
|
|
231
|
-
if (navigation.tabs && navigation.tabs.length > 0) {
|
|
232
|
-
for (const tab of navigation.tabs) {
|
|
233
|
-
if (tab.groups) {
|
|
234
|
-
for (const group of tab.groups) {
|
|
235
|
-
const result = extractFirstFromGroup(group);
|
|
236
|
-
if (result) return result;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Check top-level groups
|
|
243
|
-
if (navigation.groups && navigation.groups.length > 0) {
|
|
244
|
-
for (const group of navigation.groups) {
|
|
245
|
-
const result = extractFirstFromGroup(group);
|
|
246
|
-
if (result) return result;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Check anchors
|
|
251
|
-
if (navigation.anchors && navigation.anchors.length > 0) {
|
|
252
|
-
for (const anchor of navigation.anchors) {
|
|
253
|
-
if (anchor.groups) {
|
|
254
|
-
for (const group of anchor.groups) {
|
|
255
|
-
const result = extractFirstFromGroup(group);
|
|
256
|
-
if (result) return result;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
188
|
+
function findFirstPage(config: DocsConfig, lang?: string): string {
|
|
189
|
+
const nav = config.navigation;
|
|
190
|
+
const langBlock = lang ? nav.languages?.find((l) => l.language === lang) : undefined;
|
|
191
|
+
const result = (langBlock && findFirstNavPage(langBlock)) || findFirstNavPage(nav);
|
|
192
|
+
return result ? result.replace(/^\//, '') : 'introduction';
|
|
193
|
+
}
|
|
261
194
|
|
|
262
|
-
|
|
263
|
-
if (
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const directPath = getPagePath(item);
|
|
267
|
-
if (directPath) return directPath;
|
|
268
|
-
|
|
269
|
-
// Could be a group object { group: "...", pages: [...] }
|
|
270
|
-
if (item && typeof item === 'object' && 'group' in item) {
|
|
271
|
-
const result = extractFirstFromGroup(item as { pages?: unknown[] });
|
|
272
|
-
if (result) return result;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
195
|
+
function resolveSlug(normalizedSlug: string[], config: DocsConfig): string[] {
|
|
196
|
+
if (normalizedSlug.length === 0) return pathToSlug(findFirstPage(config));
|
|
197
|
+
if (normalizedSlug.length === 1 && isValidLanguageCode(normalizedSlug[0])) {
|
|
198
|
+
return pathToSlug(findFirstPage(config, normalizedSlug[0]));
|
|
275
199
|
}
|
|
276
|
-
|
|
277
|
-
// Fallback
|
|
278
|
-
return 'introduction';
|
|
200
|
+
return normalizedSlug;
|
|
279
201
|
}
|
|
280
202
|
|
|
281
203
|
export async function generateStaticParams() {
|
|
@@ -342,17 +264,12 @@ export async function generateMetadata({ params }: PageProps) {
|
|
|
342
264
|
// Normalize slug: strip /docs prefix when hostAtDocs=true.
|
|
343
265
|
// Empty root → resolve to first page (see DocPage for the full rationale).
|
|
344
266
|
const normalizedSlug = normalizeSlugForContent(resolvedParams.slug || [], hostAtDocs);
|
|
267
|
+
const config = await loader.getConfig();
|
|
268
|
+
const slug = resolveSlug(normalizedSlug, config);
|
|
345
269
|
const isRoot = normalizedSlug.length === 0;
|
|
346
|
-
const slug = isRoot
|
|
347
|
-
? pathToSlug(findFirstPage(await loader.getConfig()))
|
|
348
|
-
: normalizedSlug;
|
|
349
270
|
const pagePath = slug.join('/');
|
|
350
271
|
|
|
351
|
-
|
|
352
|
-
const [fileContents, config] = await Promise.all([
|
|
353
|
-
loader.getContent(pagePath).catch(() => null),
|
|
354
|
-
loader.getConfig(),
|
|
355
|
-
]);
|
|
272
|
+
const fileContents = await loader.getContent(pagePath).catch(() => null);
|
|
356
273
|
|
|
357
274
|
if (!fileContents) {
|
|
358
275
|
return {
|
|
@@ -428,14 +345,11 @@ export default async function DocPage({ params }: PageProps) {
|
|
|
428
345
|
// redirect() emits cache-control: private, blocking CDN caching. Canonical
|
|
429
346
|
// + noindex in generateMetadata prevent duplicate indexing.
|
|
430
347
|
const normalizedSlug = normalizeSlugForContent(resolvedParams.slug || [], hostAtDocs);
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
: normalizedSlug;
|
|
348
|
+
const config = await loader.getConfig();
|
|
349
|
+
const slug = resolveSlug(normalizedSlug, config);
|
|
434
350
|
const pagePath = slug.join('/');
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
loader.getConfig(),
|
|
438
|
-
]);
|
|
351
|
+
const currentLang = extractLanguageFromPath(`/${pagePath}`);
|
|
352
|
+
const fileContents = await loader.getContent(pagePath).catch(() => null);
|
|
439
353
|
|
|
440
354
|
// Check if content exists (getContent returns null via catch if not found)
|
|
441
355
|
if (!fileContents) {
|
|
@@ -567,9 +481,13 @@ export default async function DocPage({ params }: PageProps) {
|
|
|
567
481
|
allSpecPaths.length > 0 ? allSpecPaths : undefined
|
|
568
482
|
);
|
|
569
483
|
|
|
570
|
-
const
|
|
484
|
+
const baseSpecs = parsed.isShortFormat && allSpecPaths.length > 1
|
|
571
485
|
? allSpecPaths
|
|
572
486
|
: [parsed.specPath];
|
|
487
|
+
// For each base spec, expand into [<base>.<lang>.ext, <base>.ext] when
|
|
488
|
+
// the page is under a localized URL. Resolver tries each in order; the
|
|
489
|
+
// first that loads wins. Missing lang variant falls back to English.
|
|
490
|
+
const specsToTry = baseSpecs.flatMap(p => candidateSpecPaths(p, currentLang));
|
|
573
491
|
|
|
574
492
|
// Hoist mode-dependent values before the loop
|
|
575
493
|
const useIsr = isIsrMode() && !!projectSlug;
|
|
@@ -580,7 +498,8 @@ export default async function DocPage({ params }: PageProps) {
|
|
|
580
498
|
|
|
581
499
|
let lastError: unknown = null;
|
|
582
500
|
|
|
583
|
-
for (
|
|
501
|
+
for (let i = 0; i < specsToTry.length; i++) {
|
|
502
|
+
const specPath = specsToTry[i];
|
|
584
503
|
try {
|
|
585
504
|
if (resolveSpec && projectSlug) {
|
|
586
505
|
const spec = await resolveSpec(projectSlug, specPath);
|
|
@@ -598,6 +517,14 @@ export default async function DocPage({ params }: PageProps) {
|
|
|
598
517
|
break;
|
|
599
518
|
} catch (err) {
|
|
600
519
|
lastError = err;
|
|
520
|
+
const isLast = i === specsToTry.length - 1;
|
|
521
|
+
if (!isLast) {
|
|
522
|
+
// Lang variant (or intermediate candidate) failed — log so we
|
|
523
|
+
// notice transient R2 errors that silently fall through to English.
|
|
524
|
+
console.warn(
|
|
525
|
+
`[openapi] spec candidate "${specPath}" failed; trying next: ${(err as Error).message}`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
601
528
|
}
|
|
602
529
|
}
|
|
603
530
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { NextRequest, NextResponse } from 'next/server';
|
|
9
9
|
import { fetchAsset } from '@/lib/r2-content';
|
|
10
10
|
import { log } from '@/lib/logger';
|
|
11
|
+
import { isIsrMode } from '@/lib/page-isr-helpers';
|
|
11
12
|
|
|
12
13
|
// Cache durations by content type (in seconds)
|
|
13
14
|
const CACHE_DURATIONS: Record<string, number> = {
|
|
@@ -27,8 +28,7 @@ export async function GET(
|
|
|
27
28
|
request: NextRequest,
|
|
28
29
|
{ params }: { params: Promise<{ path: string[] }> }
|
|
29
30
|
) {
|
|
30
|
-
|
|
31
|
-
if (process.env.ISR_MODE !== 'true') {
|
|
31
|
+
if (!isIsrMode()) {
|
|
32
32
|
return new NextResponse('Asset serving only available in ISR mode', {
|
|
33
33
|
status: 404,
|
|
34
34
|
});
|