jamdesk 1.1.199 → 1.1.200
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/vendored/lib/render-doc-page.tsx +85 -54
- package/vendored/lib/seo.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.200",
|
|
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",
|
|
@@ -348,6 +348,79 @@ export async function resolveOpenApiMetaDescription(
|
|
|
348
348
|
return null;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
+
/**
|
|
352
|
+
* The description used for `<meta>`, the OG/Twitter tags and the OG card
|
|
353
|
+
* image: authored frontmatter first, then the OpenAPI operation, then the
|
|
354
|
+
* page's first prose paragraph.
|
|
355
|
+
*
|
|
356
|
+
* Shared by `buildDocMetadata` and `renderDocPage` so the meta tags and the
|
|
357
|
+
* JSON-LD `image` resolve the SAME text. They used to disagree: only the
|
|
358
|
+
* metadata path resolved a description, so on pages with none authored the
|
|
359
|
+
* JSON-LD card URL omitted `description=` while og:image carried it, and the
|
|
360
|
+
* two URLs rendered two genuinely different cards — two OG renders and two CDN
|
|
361
|
+
* entries per page.
|
|
362
|
+
*
|
|
363
|
+
* The gate stays on the RAW value, not frontmatterText(): YAML hands back a
|
|
364
|
+
* Date for `description: 2026-01-01` and a number for `description: 404`.
|
|
365
|
+
* Both are truthy and must keep suppressing the fallbacks exactly as before.
|
|
366
|
+
*
|
|
367
|
+
* NEVER write the result back onto `data`. `parseFrontmatter` is gray-matter,
|
|
368
|
+
* whose cache returns a shallow copy that SHARES one `data` object per content
|
|
369
|
+
* string (gray-matter/index.js:39, `Object.assign({}, cached)`), so an
|
|
370
|
+
* assignment reached `renderDocPage`'s own parse of the same page and rendered
|
|
371
|
+
* as the page SUBTITLE: the operation description twice on every spec-backed
|
|
372
|
+
* page, and the first paragraph twice on every description-less prose page.
|
|
373
|
+
* The cache is module-global, so it persisted across requests rather than
|
|
374
|
+
* being a within-request ordering race. (#7 follow-up.)
|
|
375
|
+
*/
|
|
376
|
+
export async function resolveDerivedDescription(
|
|
377
|
+
config: DocsConfig,
|
|
378
|
+
data: FrontmatterData,
|
|
379
|
+
content: string,
|
|
380
|
+
projectSlug: string | null | undefined,
|
|
381
|
+
): Promise<string | undefined> {
|
|
382
|
+
let metaDescription: string | undefined = data.description;
|
|
383
|
+
if (metaDescription) return metaDescription;
|
|
384
|
+
|
|
385
|
+
if (typeof data.openapi === 'string' && data.openapi && config.api?.openapi) {
|
|
386
|
+
// Source the description from the OpenAPI operation before falling back to
|
|
387
|
+
// first-paragraph extraction (which is empty for prose-less API pages).
|
|
388
|
+
// The spec loader REUSES the render path's cached loaders — the ISR module
|
|
389
|
+
// cache (`r2:${slug}:${specPath}`, 10-min TTL) that `renderDocPage`
|
|
390
|
+
// populates when it renders the page's <ApiEndpoint>, or the static
|
|
391
|
+
// `getCachedSpec`. So the spec is the same warm parse the render already
|
|
392
|
+
// did: the marginal cost of this lookup is a cache hit, never an uncached
|
|
393
|
+
// fetch+parse. That matters because docs routes are force-dynamic, so this
|
|
394
|
+
// metadata runs on EVERY page view (every human + crawler). Mirrors the
|
|
395
|
+
// `useIsr`/`projectDir` derivation in `renderDocPage`'s OpenAPI branch.
|
|
396
|
+
const specPaths = collectLocalSpecPaths(config.api.openapi);
|
|
397
|
+
const useIsr = isIsrMode() && !!projectSlug;
|
|
398
|
+
const projectDir = useIsr ? null : getContentDir();
|
|
399
|
+
const loadSpecForMeta = async (sp: string): Promise<unknown | null> => {
|
|
400
|
+
try {
|
|
401
|
+
if (useIsr && projectSlug) {
|
|
402
|
+
const { resolveOpenApiSpec } = await import('@/lib/openapi-isr');
|
|
403
|
+
return await resolveOpenApiSpec(projectSlug, sp);
|
|
404
|
+
}
|
|
405
|
+
if (projectDir) {
|
|
406
|
+
const { api } = await getCachedSpec(sp, projectDir);
|
|
407
|
+
return api;
|
|
408
|
+
}
|
|
409
|
+
return null;
|
|
410
|
+
} catch {
|
|
411
|
+
// Any load/parse error degrades to generateAutoDescription below.
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
const sourced = await resolveOpenApiMetaDescription(
|
|
416
|
+
data.openapi, specPaths, loadSpecForMeta,
|
|
417
|
+
);
|
|
418
|
+
if (sourced) metaDescription = sourced;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return metaDescription || generateAutoDescription(content);
|
|
422
|
+
}
|
|
423
|
+
|
|
351
424
|
export async function buildDocMetadata(input: RenderInput): Promise<Metadata> {
|
|
352
425
|
const { slug: slugInput, projectSlug, hostAtDocs, docsPrefix, requestHeaders } = input;
|
|
353
426
|
const linkPrefix = docsPrefix ?? (hostAtDocs ? '/docs' : '');
|
|
@@ -413,59 +486,9 @@ export async function buildDocMetadata(input: RenderInput): Promise<Metadata> {
|
|
|
413
486
|
const parsed = parseFrontmatter(fileContents);
|
|
414
487
|
const data = parsed.data as FrontmatterData;
|
|
415
488
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// here reached `renderDocPage`'s own parse of the same page and rendered as
|
|
420
|
-
// the page SUBTITLE: the operation description twice on every spec-backed
|
|
421
|
-
// page, and the first paragraph twice on every description-less prose page.
|
|
422
|
-
// The cache is module-global, so it persisted across requests rather than
|
|
423
|
-
// being a within-request ordering race. (#7 follow-up.)
|
|
424
|
-
//
|
|
425
|
-
// The gate stays on the RAW value, not frontmatterText(): YAML hands back a
|
|
426
|
-
// Date for `description: 2026-01-01` and a number for `description: 404`.
|
|
427
|
-
// Both are truthy and must keep suppressing the fallbacks exactly as before.
|
|
428
|
-
let metaDescription: string | undefined = data.description;
|
|
429
|
-
if (!metaDescription) {
|
|
430
|
-
if (typeof data.openapi === 'string' && data.openapi && config.api?.openapi) {
|
|
431
|
-
// Source the description from the OpenAPI operation before falling back to
|
|
432
|
-
// first-paragraph extraction (which is empty for prose-less API pages).
|
|
433
|
-
// The spec loader REUSES the render path's cached loaders — the ISR module
|
|
434
|
-
// cache (`r2:${slug}:${specPath}`, 10-min TTL) that `renderDocPage`
|
|
435
|
-
// populates when it renders the page's <ApiEndpoint>, or the static
|
|
436
|
-
// `getCachedSpec`. So the spec is the same warm parse the render already
|
|
437
|
-
// did: the marginal cost of this lookup is a cache hit, never an uncached
|
|
438
|
-
// fetch+parse. That matters because docs routes are force-dynamic, so this
|
|
439
|
-
// metadata runs on EVERY page view (every human + crawler). Mirrors the
|
|
440
|
-
// `useIsr`/`projectDir` derivation in `renderDocPage`'s OpenAPI branch.
|
|
441
|
-
const specPaths = collectLocalSpecPaths(config.api.openapi);
|
|
442
|
-
const useIsr = isIsrMode() && !!projectSlug;
|
|
443
|
-
const projectDir = useIsr ? null : getContentDir();
|
|
444
|
-
const loadSpecForMeta = async (sp: string): Promise<unknown | null> => {
|
|
445
|
-
try {
|
|
446
|
-
if (useIsr && projectSlug) {
|
|
447
|
-
const { resolveOpenApiSpec } = await import('@/lib/openapi-isr');
|
|
448
|
-
return await resolveOpenApiSpec(projectSlug, sp);
|
|
449
|
-
}
|
|
450
|
-
if (projectDir) {
|
|
451
|
-
const { api } = await getCachedSpec(sp, projectDir);
|
|
452
|
-
return api;
|
|
453
|
-
}
|
|
454
|
-
return null;
|
|
455
|
-
} catch {
|
|
456
|
-
// Any load/parse error degrades to generateAutoDescription below.
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
const sourced = await resolveOpenApiMetaDescription(
|
|
461
|
-
data.openapi, specPaths, loadSpecForMeta,
|
|
462
|
-
);
|
|
463
|
-
if (sourced) metaDescription = sourced;
|
|
464
|
-
}
|
|
465
|
-
if (!metaDescription) {
|
|
466
|
-
metaDescription = generateAutoDescription(parsed.content);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
489
|
+
const metaDescription = await resolveDerivedDescription(
|
|
490
|
+
config, data, parsed.content, projectSlug,
|
|
491
|
+
);
|
|
469
492
|
|
|
470
493
|
// buildSeoMetadata reads `frontmatter.description` for og:, twitter: and the
|
|
471
494
|
// OG card, so it must see the RESOLVED text — via a copy, never by mutating
|
|
@@ -611,7 +634,15 @@ export async function renderDocPage(input: RenderInput): Promise<ReactElement> {
|
|
|
611
634
|
|
|
612
635
|
const baseUrl = resolveBaseUrl(requestHeaders, projectSlug, hostAtDocs);
|
|
613
636
|
const faqPairs = extractFaqPairs(rawContent);
|
|
614
|
-
|
|
637
|
+
// The same resolution buildDocMetadata performs, so og:image and the JSON-LD
|
|
638
|
+
// `image` point at ONE card. Passed as a copy — never a write onto the shared
|
|
639
|
+
// `data`, which is what caused #7 in the first place.
|
|
640
|
+
const ogDescription = await resolveDerivedDescription(
|
|
641
|
+
config, data, rawContent, projectSlug,
|
|
642
|
+
);
|
|
643
|
+
const ogImageUrl = buildPageOgImageUrl(
|
|
644
|
+
config, { ...data, description: ogDescription }, baseUrl, linkPrefix,
|
|
645
|
+
);
|
|
615
646
|
// See frontmatterText: a YAML Date title throws when React renders it as a
|
|
616
647
|
// child below, so every consumer in this function reads the coerced value.
|
|
617
648
|
const titleText = frontmatterText(data.title);
|
package/vendored/lib/seo.ts
CHANGED
|
@@ -879,7 +879,7 @@ const MAX_DESCRIPTION_LENGTH = 155;
|
|
|
879
879
|
/** Patterns that indicate a paragraph is not prose (headings, components, images, comments, etc). */
|
|
880
880
|
const NON_PROSE_PATTERNS = [
|
|
881
881
|
/^#{1,6}\s/, // headings
|
|
882
|
-
|
|
882
|
+
/^<\/?[A-Z]/, // MDX components, opening AND closing (`</ResponseExample>`)
|
|
883
883
|
/^!\[/, // images
|
|
884
884
|
/^<!--/, // HTML comments
|
|
885
885
|
/^>/, // blockquotes
|