jamdesk 1.1.216 → 1.1.218
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/app/api/og/route.tsx +63 -56
- package/vendored/lib/docs-types.ts +17 -0
- package/vendored/lib/og-card.ts +144 -0
- package/vendored/lib/seo.ts +212 -60
- package/vendored/schema/docs-schema.json +40 -0
- package/vendored/themes/index.ts +5 -0
- package/vendored/themes/types.ts +7 -0
- package/vendored/workspace-package-lock.json +61 -61
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.218",
|
|
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",
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { ImageResponse } from '@vercel/og';
|
|
2
2
|
import { NextRequest } from 'next/server';
|
|
3
3
|
import { sanitizeLogoUrl } from '@/lib/sanitize-url';
|
|
4
|
+
import { parseCardOptions, isRenderableImageType } from '@/lib/og-card';
|
|
4
5
|
|
|
5
6
|
export const runtime = 'edge';
|
|
6
7
|
|
|
7
|
-
//
|
|
8
|
-
const ACCENT_COLOR = '#ff3621';
|
|
9
|
-
const ACCENT_GRADIENT = `linear-gradient(90deg, ${ACCENT_COLOR} 0%, #e02e1a 50%, #c4200f 100%)`;
|
|
10
|
-
const BG_GRADIENT = 'linear-gradient(135deg, #faf8f5 0%, #f3f0eb 100%)';
|
|
11
|
-
const TEXT_COLOR = '#1b3139';
|
|
12
|
-
const MUTED_COLOR = '#5a6b73';
|
|
13
|
-
const BADGE_BG = `rgba(255, 54, 33, 0.08)`; // ACCENT_COLOR at 8% opacity
|
|
8
|
+
// Colors live in lib/og-card.ts; with no theme/accent params it reproduces the original warm cream/red-orange card.
|
|
14
9
|
const MAX_DESCRIPTION_LENGTH = 120;
|
|
15
10
|
const LONG_TITLE_THRESHOLD = 40;
|
|
16
11
|
const FONT_STACK =
|
|
@@ -23,7 +18,7 @@ function truncateText(text: string, maxLength: number): string {
|
|
|
23
18
|
|
|
24
19
|
/**
|
|
25
20
|
* Fetch an image and convert it to a base64 data URI for Satori compatibility
|
|
26
|
-
* (Satori
|
|
21
|
+
* (Satori can fail on remote URLs). 3s timeout; '' on failure or on a type Satori can't decode.
|
|
27
22
|
*/
|
|
28
23
|
async function fetchDataUri(url: string): Promise<string> {
|
|
29
24
|
const controller = new AbortController();
|
|
@@ -33,6 +28,8 @@ async function fetchDataUri(url: string): Promise<string> {
|
|
|
33
28
|
const res = await fetch(url, { signal: controller.signal, redirect: 'manual' });
|
|
34
29
|
if (!res.ok) return '';
|
|
35
30
|
const contentType = res.headers.get('content-type') || 'image/png';
|
|
31
|
+
// Satori throws on these and the route would answer 200 with an empty body; "no image" is the safe outcome.
|
|
32
|
+
if (!isRenderableImageType(contentType)) return '';
|
|
36
33
|
const buf = await res.arrayBuffer();
|
|
37
34
|
return `data:${contentType};base64,${Buffer.from(buf).toString('base64')}`;
|
|
38
35
|
} catch {
|
|
@@ -54,7 +51,10 @@ async function fetchDataUri(url: string): Promise<string> {
|
|
|
54
51
|
* - siteName: Site name (optional, defaults to "Documentation")
|
|
55
52
|
* - logo: URL to the project's logo (optional)
|
|
56
53
|
* - bg: URL to a custom background image rendered behind the text (optional)
|
|
57
|
-
* - theme:
|
|
54
|
+
* - theme: 'light' (default, warm cream) or 'dark'
|
|
55
|
+
* - accent: hex color (3 or 6 digits, '#' optional) for the badge and bottom bar; low-contrast values fall back to a readable color
|
|
56
|
+
* - hideBar: '1' hides the bottom accent bar
|
|
57
|
+
* - hideName: '1' hides the site name (the logo, if any, still renders)
|
|
58
58
|
*
|
|
59
59
|
* Example:
|
|
60
60
|
* /api/og?title=Introduction§ion=Get%20Started&siteName=Acme%20Docs&logo=https://...
|
|
@@ -66,6 +66,7 @@ export async function GET(request: NextRequest) {
|
|
|
66
66
|
const description = searchParams.get('description') || '';
|
|
67
67
|
const section = searchParams.get('section') || '';
|
|
68
68
|
const siteName = searchParams.get('siteName') || 'Documentation';
|
|
69
|
+
const card = parseCardOptions(searchParams);
|
|
69
70
|
// SSRF guard: HTTPS-only, blocks private/loopback/metadata IPs (incl. encoded forms).
|
|
70
71
|
const logoUrl = sanitizeLogoUrl(searchParams.get('logo') || '');
|
|
71
72
|
const bgUrl = sanitizeLogoUrl(searchParams.get('bg') || '');
|
|
@@ -85,7 +86,7 @@ export async function GET(request: NextRequest) {
|
|
|
85
86
|
height: '100%',
|
|
86
87
|
width: '100%',
|
|
87
88
|
display: 'flex',
|
|
88
|
-
background:
|
|
89
|
+
background: card.palette.background,
|
|
89
90
|
fontFamily: FONT_STACK,
|
|
90
91
|
}}
|
|
91
92
|
>
|
|
@@ -116,42 +117,46 @@ export async function GET(request: NextRequest) {
|
|
|
116
117
|
padding: '60px',
|
|
117
118
|
}}
|
|
118
119
|
>
|
|
119
|
-
{/* Logo and site name */}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
display: 'flex',
|
|
123
|
-
flexDirection: 'column',
|
|
124
|
-
alignItems: 'flex-start',
|
|
125
|
-
marginBottom: '40px',
|
|
126
|
-
}}
|
|
127
|
-
>
|
|
128
|
-
{logo && (
|
|
129
|
-
<img
|
|
130
|
-
src={logo}
|
|
131
|
-
alt=""
|
|
132
|
-
width={360}
|
|
133
|
-
height={48}
|
|
134
|
-
style={{
|
|
135
|
-
height: '48px',
|
|
136
|
-
maxWidth: '360px',
|
|
137
|
-
objectFit: 'contain',
|
|
138
|
-
marginBottom: '16px',
|
|
139
|
-
borderRadius: '8px',
|
|
140
|
-
}}
|
|
141
|
-
/>
|
|
142
|
-
)}
|
|
143
|
-
<span
|
|
120
|
+
{/* Logo and site name. Skipped when both are hidden so its 40px margin doesn't push the title down. */}
|
|
121
|
+
{(logo || card.showSiteName) && (
|
|
122
|
+
<div
|
|
144
123
|
style={{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
letterSpacing: '0.05em',
|
|
124
|
+
display: 'flex',
|
|
125
|
+
flexDirection: 'column',
|
|
126
|
+
alignItems: 'flex-start',
|
|
127
|
+
marginBottom: '40px',
|
|
150
128
|
}}
|
|
151
129
|
>
|
|
152
|
-
{
|
|
153
|
-
|
|
154
|
-
|
|
130
|
+
{logo && (
|
|
131
|
+
<img
|
|
132
|
+
src={logo}
|
|
133
|
+
alt=""
|
|
134
|
+
width={360}
|
|
135
|
+
height={48}
|
|
136
|
+
style={{
|
|
137
|
+
height: '48px',
|
|
138
|
+
maxWidth: '360px',
|
|
139
|
+
objectFit: 'contain',
|
|
140
|
+
marginBottom: card.showSiteName ? '16px' : 0,
|
|
141
|
+
borderRadius: '8px',
|
|
142
|
+
}}
|
|
143
|
+
/>
|
|
144
|
+
)}
|
|
145
|
+
{card.showSiteName && (
|
|
146
|
+
<span
|
|
147
|
+
style={{
|
|
148
|
+
fontSize: '24px',
|
|
149
|
+
fontWeight: 600,
|
|
150
|
+
color: card.palette.muted,
|
|
151
|
+
textTransform: 'uppercase',
|
|
152
|
+
letterSpacing: '0.05em',
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
{siteName}
|
|
156
|
+
</span>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
155
160
|
|
|
156
161
|
{/* Section badge */}
|
|
157
162
|
{section && (
|
|
@@ -165,9 +170,9 @@ export async function GET(request: NextRequest) {
|
|
|
165
170
|
style={{
|
|
166
171
|
fontSize: '18px',
|
|
167
172
|
fontWeight: 500,
|
|
168
|
-
color:
|
|
173
|
+
color: card.palette.badgeText,
|
|
169
174
|
padding: '8px 16px',
|
|
170
|
-
background:
|
|
175
|
+
background: card.palette.badgeBg,
|
|
171
176
|
borderRadius: '20px',
|
|
172
177
|
}}
|
|
173
178
|
>
|
|
@@ -189,7 +194,7 @@ export async function GET(request: NextRequest) {
|
|
|
189
194
|
style={{
|
|
190
195
|
fontSize: titleFontSize,
|
|
191
196
|
fontWeight: 700,
|
|
192
|
-
color:
|
|
197
|
+
color: card.palette.title,
|
|
193
198
|
lineHeight: 1.2,
|
|
194
199
|
margin: 0,
|
|
195
200
|
maxWidth: '900px',
|
|
@@ -202,7 +207,7 @@ export async function GET(request: NextRequest) {
|
|
|
202
207
|
<p
|
|
203
208
|
style={{
|
|
204
209
|
fontSize: '28px',
|
|
205
|
-
color:
|
|
210
|
+
color: card.palette.muted,
|
|
206
211
|
marginTop: '24px',
|
|
207
212
|
lineHeight: 1.4,
|
|
208
213
|
maxWidth: '800px',
|
|
@@ -214,15 +219,17 @@ export async function GET(request: NextRequest) {
|
|
|
214
219
|
</div>
|
|
215
220
|
|
|
216
221
|
{/* Bottom accent bar */}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
{card.showAccentBar && (
|
|
223
|
+
<div
|
|
224
|
+
style={{
|
|
225
|
+
display: 'flex',
|
|
226
|
+
height: '6px',
|
|
227
|
+
background: card.palette.bar,
|
|
228
|
+
borderRadius: '3px',
|
|
229
|
+
marginTop: '40px',
|
|
230
|
+
}}
|
|
231
|
+
/>
|
|
232
|
+
)}
|
|
226
233
|
</div>
|
|
227
234
|
</div>
|
|
228
235
|
),
|
|
@@ -723,6 +723,22 @@ export interface ChatConfig {
|
|
|
723
723
|
starterQuestions?: string[];
|
|
724
724
|
}
|
|
725
725
|
|
|
726
|
+
/**
|
|
727
|
+
* Options for the generated social sharing card (og:image).
|
|
728
|
+
*/
|
|
729
|
+
export interface SocialCardConfig {
|
|
730
|
+
/** Card text/background style (default: 'light') */
|
|
731
|
+
theme?: 'light' | 'dark';
|
|
732
|
+
/** Use colors.primary for the accent bar and section badge (default: false) */
|
|
733
|
+
brandColor?: boolean;
|
|
734
|
+
/** Show the colored bar along the bottom (default: true) */
|
|
735
|
+
accentBar?: boolean;
|
|
736
|
+
/** Show the site name under the logo (default: true) */
|
|
737
|
+
siteName?: boolean;
|
|
738
|
+
/** 'background' draws the title over a custom og:image; 'replace' uses it as-is (default: 'background') */
|
|
739
|
+
customImage?: 'background' | 'replace';
|
|
740
|
+
}
|
|
741
|
+
|
|
726
742
|
/**
|
|
727
743
|
* SEO configuration
|
|
728
744
|
*/
|
|
@@ -732,6 +748,7 @@ export interface SeoConfig {
|
|
|
732
748
|
/** Include hidden pages in sitemap (default: false) */
|
|
733
749
|
indexHiddenPages?: boolean;
|
|
734
750
|
ai?: { llmsTxt?: boolean }; // default true; false suppresses llms.txt + llms-full.txt
|
|
751
|
+
socialCard?: SocialCardConfig;
|
|
735
752
|
}
|
|
736
753
|
|
|
737
754
|
/**
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the social card (app/api/og). No imports: the route runs on
|
|
3
|
+
* the edge runtime and this must stay dependency-free.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type CardTheme = 'light' | 'dark';
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_ACCENT = '#ff3621';
|
|
9
|
+
|
|
10
|
+
/** Today's bar, kept verbatim so a card with no new params renders exactly as before. */
|
|
11
|
+
export const LEGACY_ACCENT_GRADIENT = 'linear-gradient(90deg, #ff3621 0%, #e02e1a 50%, #c4200f 100%)';
|
|
12
|
+
|
|
13
|
+
export interface CardPalette {
|
|
14
|
+
background: string; // CSS background (gradient) used when there is no bg image
|
|
15
|
+
surface: string; // solid hex the gradient starts from; used for contrast checks
|
|
16
|
+
title: string;
|
|
17
|
+
muted: string;
|
|
18
|
+
accent: string; // resolved accent hex
|
|
19
|
+
bar: string; // CSS background for the bar
|
|
20
|
+
badgeText: string;
|
|
21
|
+
badgeBg: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CardOptions {
|
|
25
|
+
theme: CardTheme;
|
|
26
|
+
palette: CardPalette;
|
|
27
|
+
showAccentBar: boolean;
|
|
28
|
+
showSiteName: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const THEMES = {
|
|
32
|
+
light: {
|
|
33
|
+
surface: '#faf8f5',
|
|
34
|
+
background: 'linear-gradient(135deg, #faf8f5 0%, #f3f0eb 100%)',
|
|
35
|
+
title: '#1b3139',
|
|
36
|
+
muted: '#5a6b73',
|
|
37
|
+
badgeAlpha: 0.08,
|
|
38
|
+
},
|
|
39
|
+
dark: {
|
|
40
|
+
surface: '#12161a',
|
|
41
|
+
background: 'linear-gradient(135deg, #12161a 0%, #1c2228 100%)',
|
|
42
|
+
title: '#f5f3ef',
|
|
43
|
+
muted: '#a3b1b8',
|
|
44
|
+
badgeAlpha: 0.16,
|
|
45
|
+
},
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
// Accent text under 3:1 is hard to read at badge size; a bar under 1.5:1 all but vanishes.
|
|
49
|
+
const MIN_TEXT_CONTRAST = 3;
|
|
50
|
+
const MIN_BAR_CONTRAST = 1.5;
|
|
51
|
+
|
|
52
|
+
/** Accepts 3- or 6-digit hex with or without '#'. Anything else is null, so the value is safe to inline in CSS. */
|
|
53
|
+
export function normalizeHex(input: string | null | undefined): string | null {
|
|
54
|
+
if (!input) return null;
|
|
55
|
+
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(input);
|
|
56
|
+
if (!m) return null;
|
|
57
|
+
const h = m[1].toLowerCase();
|
|
58
|
+
const full = h.length === 3 ? h.split('').map((c) => c + c).join('') : h;
|
|
59
|
+
return `#${full}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function channels(hex: string): [number, number, number] {
|
|
63
|
+
const n = parseInt(hex.slice(1), 16);
|
|
64
|
+
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function hexToRgba(hex: string, alpha: number): string {
|
|
68
|
+
const [r, g, b] = channels(hex);
|
|
69
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function luminance(hex: string): number {
|
|
73
|
+
const [r, g, b] = channels(hex).map((v) => {
|
|
74
|
+
const c = v / 255;
|
|
75
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
76
|
+
});
|
|
77
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function contrastRatio(a: string, b: string): number {
|
|
81
|
+
const la = luminance(a);
|
|
82
|
+
const lb = luminance(b);
|
|
83
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function resolveCardPalette(theme: CardTheme, accent: string | null): CardPalette {
|
|
87
|
+
const t = THEMES[theme];
|
|
88
|
+
const base = { background: t.background, surface: t.surface, title: t.title, muted: t.muted };
|
|
89
|
+
|
|
90
|
+
// No accent means every pre-existing card URL: reproduce today's colors with no contrast logic.
|
|
91
|
+
if (accent === null) {
|
|
92
|
+
return {
|
|
93
|
+
...base,
|
|
94
|
+
accent: DEFAULT_ACCENT,
|
|
95
|
+
bar: LEGACY_ACCENT_GRADIENT,
|
|
96
|
+
badgeText: DEFAULT_ACCENT,
|
|
97
|
+
badgeBg: hexToRgba(DEFAULT_ACCENT, t.badgeAlpha),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const contrast = contrastRatio(accent, t.surface);
|
|
102
|
+
return {
|
|
103
|
+
...base,
|
|
104
|
+
accent,
|
|
105
|
+
bar: contrast >= MIN_BAR_CONTRAST ? accent : t.muted,
|
|
106
|
+
badgeText: contrast >= MIN_TEXT_CONTRAST ? accent : t.title,
|
|
107
|
+
badgeBg: hexToRgba(accent, t.badgeAlpha),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function parseCardOptions(params: URLSearchParams): CardOptions {
|
|
112
|
+
const theme: CardTheme = params.get('theme') === 'dark' ? 'dark' : 'light';
|
|
113
|
+
return {
|
|
114
|
+
theme,
|
|
115
|
+
palette: resolveCardPalette(theme, normalizeHex(params.get('accent'))),
|
|
116
|
+
showAccentBar: params.get('hideBar') !== '1',
|
|
117
|
+
showSiteName: params.get('hideName') !== '1',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Satori throws on these, and the route then answers 200 with an empty body that
|
|
123
|
+
* the CDN caches as a blank card. A deny-list rather than an allow-list on purpose:
|
|
124
|
+
* any type not listed renders (or fails) exactly as it does today, so no root tenant
|
|
125
|
+
* loses a logo that currently shows. ICO is deliberately NOT listed: Satori drops the
|
|
126
|
+
* icon but still renders a full card (with the logo slot reserved), and an existing
|
|
127
|
+
* card may only change when it is broken. Measured 2026-10-02; GIF, SVG, JPEG, PNG and
|
|
128
|
+
* octet-stream all draw.
|
|
129
|
+
*/
|
|
130
|
+
const UNRENDERABLE_IMAGE_TYPES = new Set([
|
|
131
|
+
'image/webp', 'image/avif',
|
|
132
|
+
'image/tiff', 'image/heic', 'image/heif', 'image/jxl', 'image/bmp',
|
|
133
|
+
// Non-standard spellings of JPEG that real servers send; Satori only knows image/jpeg.
|
|
134
|
+
'image/jpg', 'image/pjpeg',
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
export function isRenderableImageType(contentType: string): boolean {
|
|
138
|
+
const type = contentType.split(';')[0].trim().toLowerCase();
|
|
139
|
+
// A 200 HTML page for a missing image (soft 404) is the realistic non-image case.
|
|
140
|
+
// Only plain-letter subtypes throw (html, plain, xml, css, svg+xml); text/x-generic,
|
|
141
|
+
// text/vnd.foo and text/html5 are byte-sniffed and draw today, so they must stay allowed.
|
|
142
|
+
if (/^text\/[a-z+]+$/.test(type)) return false;
|
|
143
|
+
return !UNRENDERABLE_IMAGE_TYPES.has(type);
|
|
144
|
+
}
|
package/vendored/lib/seo.ts
CHANGED
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Metadata } from 'next';
|
|
12
12
|
import { stripMarkdownForMeta } from './openapi/operation-description';
|
|
13
|
-
import type { DocsConfig, Logo, LogoConfig, Favicon, LanguageConfig, LanguageCode } from './docs-types';
|
|
14
|
-
import { normalizeLogo } from './docs-types';
|
|
13
|
+
import type { DocsConfig, Logo, LogoConfig, Favicon, LanguageConfig, LanguageCode, SocialCardConfig, ColorsConfig } from './docs-types';
|
|
14
|
+
import { normalizeLogo, transformConfigImagePath } from './docs-types';
|
|
15
|
+
import { normalizeHex } from './og-card';
|
|
16
|
+
import { getTheme } from '../themes';
|
|
15
17
|
import { findHreflangAliasCollisions, transformLanguagePath, toHreflang, resolveLocaleFromPath } from './language-utils';
|
|
16
18
|
import { fallbackSeo } from './locale-fallback';
|
|
17
19
|
import { logger } from '../shared/logger';
|
|
@@ -59,11 +61,48 @@ export const FALLBACK_METADATA: Metadata = {
|
|
|
59
61
|
description: 'Documentation',
|
|
60
62
|
};
|
|
61
63
|
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a Logo or Favicon config to a path string. Light prefers the light
|
|
66
|
+
* variant, dark prefers the dark one; each falls back to the other so a
|
|
67
|
+
* single-variant config still yields an image.
|
|
68
|
+
*/
|
|
69
|
+
function resolveImagePath(image?: Logo | Favicon, variant: 'light' | 'dark' = 'light'): string {
|
|
64
70
|
if (!image) return '';
|
|
65
71
|
if (typeof image === 'string') return image;
|
|
66
|
-
|
|
72
|
+
const { light, dark } = image as LogoConfig;
|
|
73
|
+
return (variant === 'dark' ? dark || light : light || dark) || '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Make a config-supplied image reference fetchable by the OG route, which loads
|
|
78
|
+
* it server-side. Local paths go through the /_jd asset prefix (where the
|
|
79
|
+
* pipeline serves them); an existing /_jd path is not doubled. Returns '' for
|
|
80
|
+
* data: URIs: the route drops them and base64 would bloat the og:image URL.
|
|
81
|
+
*/
|
|
82
|
+
export function toAbsoluteAssetUrl(pathOrUrl: string, rootUrl: string): string {
|
|
83
|
+
if (pathOrUrl.startsWith('data:')) return '';
|
|
84
|
+
if (pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')) return pathOrUrl;
|
|
85
|
+
// transformConfigImagePath passes '//host' through untouched, which would
|
|
86
|
+
// then be glued onto rootUrl as a path.
|
|
87
|
+
if (pathOrUrl.startsWith('//')) return `https:${pathOrUrl}`;
|
|
88
|
+
return `${rootUrl}${transformConfigImagePath(pathOrUrl) ?? ''}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* True for a WebP or AVIF image path/URL. Satori in the OG route cannot decode
|
|
93
|
+
* either and answers with an empty PNG instead of an error.
|
|
94
|
+
*/
|
|
95
|
+
function isUndecodableImage(pathOrUrl: string): boolean {
|
|
96
|
+
return /\.(?:webp|avif)$/i.test(pathOrUrl.split(/[?#]/)[0]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* True for formats the OG card reliably draws as a logo. Deliberately an
|
|
101
|
+
* allow-list: ICO renders as an empty slot and WebP/AVIF are dropped, so none
|
|
102
|
+
* of them gain anything from the subpath /_jd/ rewrite.
|
|
103
|
+
*/
|
|
104
|
+
function isSubpathRewritableImage(pathOrUrl: string): boolean {
|
|
105
|
+
return /\.(?:png|jpe?g|gif|svg)$/i.test(pathOrUrl.split(/[?#]/)[0]);
|
|
67
106
|
}
|
|
68
107
|
|
|
69
108
|
/**
|
|
@@ -102,6 +141,9 @@ function buildOgImageUrl(
|
|
|
102
141
|
logo?: Logo;
|
|
103
142
|
favicon?: Favicon;
|
|
104
143
|
bg?: string;
|
|
144
|
+
card?: SocialCardConfig;
|
|
145
|
+
colors?: ColorsConfig;
|
|
146
|
+
theme?: string;
|
|
105
147
|
}
|
|
106
148
|
): string {
|
|
107
149
|
const params = new URLSearchParams();
|
|
@@ -122,32 +164,79 @@ function buildOgImageUrl(
|
|
|
122
164
|
const { prefix: resolvedPrefix, rootUrl } = resolveRootUrl(baseUrl, docsPrefix);
|
|
123
165
|
const isHostAtDocs = resolvedPrefix !== '';
|
|
124
166
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
const card = options?.card;
|
|
168
|
+
if (!card) {
|
|
169
|
+
// Tenants without seo.socialCard keep today's URLs byte for byte, including
|
|
170
|
+
// the non-prefixed asset paths: cached cards and shared links must not change.
|
|
171
|
+
// Handle logo - prefer light variant for OG images (cream background)
|
|
172
|
+
// Satori (@vercel/og) can't render webp, so fall back to favicon if logo is webp
|
|
173
|
+
if (options?.logo || options?.favicon) {
|
|
174
|
+
let logoPath = resolveImagePath(options?.logo);
|
|
175
|
+
// Fall back to favicon when logo is webp (Satori can't decode webp)
|
|
176
|
+
if ((!logoPath || logoPath.endsWith('.webp')) && options?.favicon) {
|
|
177
|
+
logoPath = resolveImagePath(options.favicon);
|
|
178
|
+
}
|
|
179
|
+
// Make logo URL absolute (use root URL since assets are at domain root)
|
|
180
|
+
if (logoPath && !logoPath.startsWith('http')) {
|
|
181
|
+
// A subpath tenant's worker only proxies /docs and /_jd/, so a bare
|
|
182
|
+
// root-relative logo 404s on their origin; /_jd/ is where it is served.
|
|
183
|
+
// Gated to formats the card renders, so ICO/WebP/AVIF keep today's URL
|
|
184
|
+
// rather than turning "no logo" into an empty icon slot.
|
|
185
|
+
logoPath = isHostAtDocs && isSubpathRewritableImage(logoPath)
|
|
186
|
+
? toAbsoluteAssetUrl(logoPath, rootUrl)
|
|
187
|
+
: `${rootUrl}${logoPath.startsWith('/') ? '' : '/'}${logoPath}`;
|
|
188
|
+
}
|
|
189
|
+
if (logoPath) {
|
|
190
|
+
params.set('logo', logoPath);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Custom background image: composite the card text over it instead of using it
|
|
195
|
+
// as a bare static image. The OG route fetches it server-side, so it must be
|
|
196
|
+
// absolute — assets live at the domain root, so resolve against rootUrl.
|
|
197
|
+
if (options?.bg) {
|
|
198
|
+
let bgPath = options.bg;
|
|
199
|
+
if (!bgPath.startsWith('http')) {
|
|
200
|
+
bgPath = `${rootUrl}${bgPath.startsWith('/') ? '' : '/'}${bgPath}`;
|
|
201
|
+
}
|
|
202
|
+
params.set('bg', bgPath);
|
|
132
203
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
204
|
+
} else {
|
|
205
|
+
const variant = card.theme === 'dark' ? 'dark' : 'light';
|
|
206
|
+
|
|
207
|
+
// Resolved before any param is set so the param order stays as it was.
|
|
208
|
+
let logoUrl = '';
|
|
209
|
+
let logoIsFavicon = false;
|
|
210
|
+
if (options?.logo || options?.favicon) {
|
|
211
|
+
let logoPath = resolveImagePath(options?.logo, variant);
|
|
212
|
+
if ((!logoPath || isUndecodableImage(logoPath)) && options?.favicon) {
|
|
213
|
+
logoPath = resolveImagePath(options.favicon, variant);
|
|
214
|
+
logoIsFavicon = true;
|
|
215
|
+
}
|
|
216
|
+
// A webp/avif logo makes the route return a 0-byte PNG that the CDN then
|
|
217
|
+
// caches, so a card with no logo is the better outcome.
|
|
218
|
+
if (logoPath && !isUndecodableImage(logoPath)) logoUrl = toAbsoluteAssetUrl(logoPath, rootUrl);
|
|
136
219
|
}
|
|
137
|
-
|
|
138
|
-
|
|
220
|
+
|
|
221
|
+
if (variant === 'dark') params.set('theme', 'dark');
|
|
222
|
+
if (card.accentBar === false) params.set('hideBar', '1');
|
|
223
|
+
// Hiding the name is only safe when the tenant's own logo is drawn: with the
|
|
224
|
+
// favicon (or nothing) in its place the card would carry no brand at all.
|
|
225
|
+
if (card.siteName === false && logoUrl && !logoIsFavicon) params.set('hideName', '1');
|
|
226
|
+
|
|
227
|
+
if (card.brandColor === true) {
|
|
228
|
+
const colors = options?.colors;
|
|
229
|
+
const configured = variant === 'dark' ? colors?.light ?? colors?.primary : colors?.primary;
|
|
230
|
+
const accent = normalizeHex(configured ?? getTheme(options?.theme).defaultAccent);
|
|
231
|
+
if (accent) params.set('accent', accent.slice(1));
|
|
139
232
|
}
|
|
140
|
-
}
|
|
141
233
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (!bgPath.startsWith('http')) {
|
|
148
|
-
bgPath = `${rootUrl}${bgPath.startsWith('/') ? '' : '/'}${bgPath}`;
|
|
234
|
+
if (logoUrl) params.set('logo', logoUrl);
|
|
235
|
+
|
|
236
|
+
if (options?.bg && !isUndecodableImage(options.bg)) {
|
|
237
|
+
const bgUrl = toAbsoluteAssetUrl(options.bg, rootUrl);
|
|
238
|
+
if (bgUrl) params.set('bg', bgUrl);
|
|
149
239
|
}
|
|
150
|
-
params.set('bg', bgPath);
|
|
151
240
|
}
|
|
152
241
|
|
|
153
242
|
// hostAtDocs: use /_jd/og (proxied to ISR app's /api/og) for same-domain OG images
|
|
@@ -157,37 +246,79 @@ function buildOgImageUrl(
|
|
|
157
246
|
}
|
|
158
247
|
|
|
159
248
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
249
|
+
* Resolve the social image a page advertises, plus whether it is the generated
|
|
250
|
+
* card. Mirrors the global+page metatag merge that buildSeoMetadata performs, so
|
|
251
|
+
* callers outside the metadata path (the JSON-LD layer) get the exact image the
|
|
252
|
+
* og:image points at.
|
|
253
|
+
*
|
|
254
|
+
* Default: always the generated /api/og card, with a custom og:image composited
|
|
255
|
+
* in as its background. With `seo.socialCard.customImage: 'replace'` and an
|
|
256
|
+
* og:image set, that image is served untouched instead (`generated: false`).
|
|
257
|
+
*
|
|
258
|
+
* Declared dimensions come from the SAME layer that supplied the winning
|
|
259
|
+
* og:image: a page image must not inherit the site default's width/height, which
|
|
260
|
+
* describe a different file.
|
|
163
261
|
*
|
|
164
262
|
* @param docsPrefix - This tenant's external docs URL prefix ('' | '/docs' |
|
|
165
263
|
* a custom subpath like '/help'). Falls back to the legacy baseUrl-suffix
|
|
166
264
|
* sniff when omitted — see buildOgImageUrl.
|
|
167
265
|
*/
|
|
168
|
-
export function
|
|
266
|
+
export function resolvePageSocialImage(
|
|
169
267
|
config: DocsConfig,
|
|
170
268
|
frontmatter: PageFrontmatter,
|
|
171
269
|
baseUrl: string,
|
|
172
270
|
docsPrefix?: string,
|
|
173
|
-
): string {
|
|
271
|
+
): { url: string; generated: boolean; width?: number; height?: number } {
|
|
174
272
|
const globalMeta = config.seo?.metatags || {};
|
|
175
273
|
const pageMeta = normalizePageMetatags(frontmatter as Record<string, unknown>);
|
|
176
274
|
const metatags: Record<string, string> = { ...globalMeta, ...pageMeta };
|
|
177
275
|
const customImage = metatags['og:image']?.trim();
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
276
|
+
|
|
277
|
+
if (config.seo?.socialCard?.customImage === 'replace' && customImage) {
|
|
278
|
+
const { rootUrl } = resolveRootUrl(baseUrl, docsPrefix);
|
|
279
|
+
const url = toAbsoluteAssetUrl(customImage, rootUrl);
|
|
280
|
+
// A data: URI resolves to '' (it would bloat the tag and crawlers drop it),
|
|
281
|
+
// so fall through to the generated card rather than emit an empty og:image.
|
|
282
|
+
if (url) {
|
|
283
|
+
const dims = pageMeta['og:image']?.trim() ? pageMeta : globalMeta;
|
|
284
|
+
return {
|
|
285
|
+
url,
|
|
286
|
+
generated: false,
|
|
287
|
+
width: positiveIntMeta(dims, 'og:image:width'),
|
|
288
|
+
height: positiveIntMeta(dims, 'og:image:height'),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
generated: true,
|
|
295
|
+
url: buildOgImageUrl(
|
|
296
|
+
baseUrl,
|
|
297
|
+
metatags['og:title'] || frontmatter.title || 'Documentation',
|
|
298
|
+
config.name,
|
|
299
|
+
docsPrefix,
|
|
300
|
+
{
|
|
301
|
+
description: metaDescriptionText(metatags['og:description'] || frontmatter.description),
|
|
302
|
+
section: frontmatter.section,
|
|
303
|
+
logo: config.logo,
|
|
304
|
+
favicon: config.favicon,
|
|
305
|
+
bg: customImage || undefined,
|
|
306
|
+
card: config.seo?.socialCard,
|
|
307
|
+
colors: config.colors,
|
|
308
|
+
theme: config.theme,
|
|
309
|
+
},
|
|
310
|
+
),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** The social image URL only; see resolvePageSocialImage. */
|
|
315
|
+
export function buildPageOgImageUrl(
|
|
316
|
+
config: DocsConfig,
|
|
317
|
+
frontmatter: PageFrontmatter,
|
|
318
|
+
baseUrl: string,
|
|
319
|
+
docsPrefix?: string,
|
|
320
|
+
): string {
|
|
321
|
+
return resolvePageSocialImage(config, frontmatter, baseUrl, docsPrefix).url;
|
|
191
322
|
}
|
|
192
323
|
|
|
193
324
|
/**
|
|
@@ -297,9 +428,21 @@ function numericMeta(metatags: Record<string, string>, key: string): number | un
|
|
|
297
428
|
return Number.isNaN(n) ? undefined : n;
|
|
298
429
|
}
|
|
299
430
|
|
|
431
|
+
// numericMeta lets 0, negatives, fractions and whitespace-only strings
|
|
432
|
+
// (Number(' ') === 0) through; a crawler can't use any of those as a pixel size.
|
|
433
|
+
function positiveIntMeta(metatags: Record<string, string>, key: string): number | undefined {
|
|
434
|
+
const n = numericMeta(metatags, key);
|
|
435
|
+
return n !== undefined && Number.isInteger(n) && n > 0 ? n : undefined;
|
|
436
|
+
}
|
|
437
|
+
|
|
300
438
|
type FallbackOg = {
|
|
301
439
|
title?: string; description?: string; url: string; siteName: string; ogImageUrl: string;
|
|
302
440
|
locale?: string;
|
|
441
|
+
/** False when ogImageUrl is the author's own image (customImage: 'replace'). */
|
|
442
|
+
ogImageGenerated?: boolean;
|
|
443
|
+
/** Declared size of an author-supplied image; ignored for the generated card. */
|
|
444
|
+
ogImageWidth?: number;
|
|
445
|
+
ogImageHeight?: number;
|
|
303
446
|
};
|
|
304
447
|
|
|
305
448
|
/**
|
|
@@ -344,16 +487,21 @@ export function buildOpenGraphMetadata(
|
|
|
344
487
|
const ttl = numericMeta(metatags, 'og:ttl');
|
|
345
488
|
if (ttl !== undefined) og.ttl = ttl;
|
|
346
489
|
|
|
347
|
-
// Image:
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
// user
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
490
|
+
// Image: the generated card (1200x630 PNG) unless the caller resolved the
|
|
491
|
+
// author's own image (customImage: 'replace'). In the default mode a custom
|
|
492
|
+
// og:image is composited as the card BACKGROUND upstream (via /api/og's `bg`
|
|
493
|
+
// param), so the emitted image is never the raw user file. For a raw image we
|
|
494
|
+
// can't know the format or size, so emit only the dimensions the author
|
|
495
|
+
// declared and no `type`. og:image:alt describes either.
|
|
496
|
+
const image: Record<string, unknown> = { url: fb.ogImageUrl };
|
|
497
|
+
if (fb.ogImageGenerated === false) {
|
|
498
|
+
if (fb.ogImageWidth !== undefined) image.width = fb.ogImageWidth;
|
|
499
|
+
if (fb.ogImageHeight !== undefined) image.height = fb.ogImageHeight;
|
|
500
|
+
} else {
|
|
501
|
+
image.width = 1200;
|
|
502
|
+
image.height = 630;
|
|
503
|
+
image.type = 'image/png';
|
|
504
|
+
}
|
|
357
505
|
if (metatags['og:image:alt']) image.alt = metatags['og:image:alt'];
|
|
358
506
|
og.images = [image];
|
|
359
507
|
|
|
@@ -827,17 +975,21 @@ export function buildSeoMetadata(
|
|
|
827
975
|
(k) => k.startsWith('og:') || k.startsWith('article:') || k.startsWith('book:') || k.startsWith('profile:'),
|
|
828
976
|
);
|
|
829
977
|
if (hasOgFamily || frontmatter.title) {
|
|
830
|
-
//
|
|
831
|
-
//
|
|
832
|
-
//
|
|
833
|
-
//
|
|
834
|
-
|
|
978
|
+
// By default a custom og:image becomes the card BACKGROUND (text renders on
|
|
979
|
+
// top) and the emitted image is the generated card. With
|
|
980
|
+
// seo.socialCard.customImage: 'replace' the author's image is emitted as-is.
|
|
981
|
+
// The generated card's text follows og:title/og:description overrides so the
|
|
982
|
+
// image matches the link-card text built in buildOpenGraphMetadata.
|
|
983
|
+
const socialImage = resolvePageSocialImage(config, frontmatter, baseUrl, docsPrefix);
|
|
835
984
|
metadata.openGraph = buildOpenGraphMetadata(metatags, {
|
|
836
985
|
title: frontmatter.title,
|
|
837
986
|
description: metaDescriptionText(frontmatter.description),
|
|
838
987
|
url: pageUrl,
|
|
839
988
|
siteName: config.name,
|
|
840
|
-
ogImageUrl,
|
|
989
|
+
ogImageUrl: socialImage.url,
|
|
990
|
+
ogImageGenerated: socialImage.generated,
|
|
991
|
+
ogImageWidth: socialImage.width,
|
|
992
|
+
ogImageHeight: socialImage.height,
|
|
841
993
|
// og:locale describes the language of the CONTENT, so on the fallback it
|
|
842
994
|
// must follow the English body, not the locale URL. (og:url deliberately
|
|
843
995
|
// still points at the locale URL — that is the page's real address, and
|
|
@@ -609,6 +609,46 @@
|
|
|
609
609
|
"description": "Generate llms.txt and llms-full.txt for AI crawlers. Default true. Set false to stop publishing AI-context files (independent of search noindex)."
|
|
610
610
|
}
|
|
611
611
|
}
|
|
612
|
+
},
|
|
613
|
+
"socialCard": {
|
|
614
|
+
"type": "object",
|
|
615
|
+
"additionalProperties": false,
|
|
616
|
+
"properties": {
|
|
617
|
+
"theme": {
|
|
618
|
+
"type": "string",
|
|
619
|
+
"enum": [
|
|
620
|
+
"light",
|
|
621
|
+
"dark"
|
|
622
|
+
],
|
|
623
|
+
"default": "light",
|
|
624
|
+
"description": "Text and background style of the card. `dark` uses light text on a dark background and your `logo.dark` image. Defaults to `light`."
|
|
625
|
+
},
|
|
626
|
+
"brandColor": {
|
|
627
|
+
"type": "boolean",
|
|
628
|
+
"default": false,
|
|
629
|
+
"description": "Use your `colors.primary` for the card's accent bar and section badge (`colors.light` on a dark card). Defaults to `false`, which keeps the default red."
|
|
630
|
+
},
|
|
631
|
+
"accentBar": {
|
|
632
|
+
"type": "boolean",
|
|
633
|
+
"default": true,
|
|
634
|
+
"description": "Show the colored bar along the bottom of the card. Defaults to `true`."
|
|
635
|
+
},
|
|
636
|
+
"siteName": {
|
|
637
|
+
"type": "boolean",
|
|
638
|
+
"default": true,
|
|
639
|
+
"description": "Show the site name under the logo. Set `false` when your logo already contains the name. Defaults to `true`."
|
|
640
|
+
},
|
|
641
|
+
"customImage": {
|
|
642
|
+
"type": "string",
|
|
643
|
+
"enum": [
|
|
644
|
+
"background",
|
|
645
|
+
"replace"
|
|
646
|
+
],
|
|
647
|
+
"default": "background",
|
|
648
|
+
"description": "How a custom `og:image` is used. `background` draws the page title on top of it. `replace` uses the image as-is, with no text. Defaults to `background`."
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
"description": "Customize the generated social sharing card (og:image) for every page."
|
|
612
652
|
}
|
|
613
653
|
},
|
|
614
654
|
"additionalProperties": false,
|
package/vendored/themes/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ const jamTheme: ThemeConfig = {
|
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
33
|
cssPath: 'jam/variables.css',
|
|
34
|
+
defaultAccent: '#3b82f6',
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
const nebulaTheme: ThemeConfig = {
|
|
@@ -52,6 +53,7 @@ const nebulaTheme: ThemeConfig = {
|
|
|
52
53
|
},
|
|
53
54
|
},
|
|
54
55
|
cssPath: 'nebula/variables.css',
|
|
56
|
+
defaultAccent: '#f97316',
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
const pulsarTheme: ThemeConfig = {
|
|
@@ -73,6 +75,7 @@ const pulsarTheme: ThemeConfig = {
|
|
|
73
75
|
},
|
|
74
76
|
},
|
|
75
77
|
cssPath: 'pulsar/variables.css',
|
|
78
|
+
defaultAccent: '#3b82f6',
|
|
76
79
|
};
|
|
77
80
|
|
|
78
81
|
const haloTheme: ThemeConfig = {
|
|
@@ -98,6 +101,7 @@ const haloTheme: ThemeConfig = {
|
|
|
98
101
|
},
|
|
99
102
|
},
|
|
100
103
|
cssPath: 'halo/variables.css',
|
|
104
|
+
defaultAccent: '#C0873F',
|
|
101
105
|
};
|
|
102
106
|
|
|
103
107
|
const duskTheme: ThemeConfig = {
|
|
@@ -125,6 +129,7 @@ const duskTheme: ThemeConfig = {
|
|
|
125
129
|
},
|
|
126
130
|
},
|
|
127
131
|
cssPath: 'dusk/variables.css',
|
|
132
|
+
defaultAccent: '#2158A1',
|
|
128
133
|
};
|
|
129
134
|
|
|
130
135
|
// Theme registry
|
package/vendored/themes/types.ts
CHANGED
|
@@ -78,6 +78,13 @@ export interface ThemeConfig {
|
|
|
78
78
|
|
|
79
79
|
/** CSS file path relative to themes directory */
|
|
80
80
|
cssPath: string;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The theme's light-mode `--color-accent`. The OG social card uses it as the
|
|
84
|
+
* accent when a tenant opts into brandColor but sets no colors.primary.
|
|
85
|
+
* Pinned to variables.css by __tests__/theme-default-accent.test.ts.
|
|
86
|
+
*/
|
|
87
|
+
defaultAccent: string;
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
/**
|
|
@@ -380,42 +380,40 @@
|
|
|
380
380
|
"license": "MIT"
|
|
381
381
|
},
|
|
382
382
|
"node_modules/@chevrotain/cst-dts-gen": {
|
|
383
|
-
"version": "
|
|
384
|
-
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-
|
|
385
|
-
"integrity": "sha512-
|
|
383
|
+
"version": "13.2.0",
|
|
384
|
+
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-13.2.0.tgz",
|
|
385
|
+
"integrity": "sha512-bA9dvWlhAUcHkNwU+8w9yiUxJPlj/Lm3VqS7MD+NbIR/7LmpNLuK/AWNFdCo/T3SK5ND0/UHf3IJvl/Jn/i6yg==",
|
|
386
386
|
"license": "Apache-2.0",
|
|
387
387
|
"dependencies": {
|
|
388
|
-
"@chevrotain/gast": "
|
|
389
|
-
"@chevrotain/types": "
|
|
390
|
-
"lodash-es": "4.17.23"
|
|
388
|
+
"@chevrotain/gast": "13.2.0",
|
|
389
|
+
"@chevrotain/types": "13.2.0"
|
|
391
390
|
}
|
|
392
391
|
},
|
|
393
392
|
"node_modules/@chevrotain/gast": {
|
|
394
|
-
"version": "
|
|
395
|
-
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-
|
|
396
|
-
"integrity": "sha512-
|
|
393
|
+
"version": "13.2.0",
|
|
394
|
+
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-13.2.0.tgz",
|
|
395
|
+
"integrity": "sha512-bCARdAsG+eEbOveSXcOAC7pVxA04I1ionzeMOwIYf5NX7SaS7zgn4Dgug2BSqoL+iyKeMNSorQjsFSHJHsCNwg==",
|
|
397
396
|
"license": "Apache-2.0",
|
|
398
397
|
"dependencies": {
|
|
399
|
-
"@chevrotain/types": "
|
|
400
|
-
"lodash-es": "4.17.23"
|
|
398
|
+
"@chevrotain/types": "13.2.0"
|
|
401
399
|
}
|
|
402
400
|
},
|
|
403
401
|
"node_modules/@chevrotain/regexp-to-ast": {
|
|
404
|
-
"version": "
|
|
405
|
-
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-
|
|
406
|
-
"integrity": "sha512-
|
|
402
|
+
"version": "13.2.0",
|
|
403
|
+
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-13.2.0.tgz",
|
|
404
|
+
"integrity": "sha512-kxpUkLtiaK09ph+dib+Yvm+3sWSnuwlo25gqfKRgTPNmF6H7uW4gVkMjaRdtnN783S9sM6vUNWh9CsxYX4QwKw==",
|
|
407
405
|
"license": "Apache-2.0"
|
|
408
406
|
},
|
|
409
407
|
"node_modules/@chevrotain/types": {
|
|
410
|
-
"version": "
|
|
411
|
-
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-
|
|
412
|
-
"integrity": "sha512-
|
|
408
|
+
"version": "13.2.0",
|
|
409
|
+
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-13.2.0.tgz",
|
|
410
|
+
"integrity": "sha512-7qrYHRlKfWf2AvBfJi5eVOrKAkbxvMFsy/iIHTGIiApNcEecCaVIdR+mIQhyCBsTDJxBz4JWXHMo6krbhR9hVg==",
|
|
413
411
|
"license": "Apache-2.0"
|
|
414
412
|
},
|
|
415
413
|
"node_modules/@chevrotain/utils": {
|
|
416
|
-
"version": "
|
|
417
|
-
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-
|
|
418
|
-
"integrity": "sha512-
|
|
414
|
+
"version": "13.2.0",
|
|
415
|
+
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-13.2.0.tgz",
|
|
416
|
+
"integrity": "sha512-SzKYY68rseOrb9KILVzPsl6SjkQtdWw01SRGB5aCVI5kFcHltQsOYV3JB0Ffst72DgkvHCyDQ0NLwxtmCCf97Q==",
|
|
419
417
|
"license": "Apache-2.0"
|
|
420
418
|
},
|
|
421
419
|
"node_modules/@emnapi/runtime": {
|
|
@@ -1126,12 +1124,12 @@
|
|
|
1126
1124
|
}
|
|
1127
1125
|
},
|
|
1128
1126
|
"node_modules/@mermaid-js/parser": {
|
|
1129
|
-
"version": "2.0.
|
|
1130
|
-
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-2.0.
|
|
1131
|
-
"integrity": "sha512-
|
|
1127
|
+
"version": "2.0.1",
|
|
1128
|
+
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-2.0.1.tgz",
|
|
1129
|
+
"integrity": "sha512-j37dj/h6p6pots1L7pLPPNuABEEu9WP4OYg9pW1g+nYG331j0+3canE9OagtYdytYvdSAMG+2fKU5cxep6LgjA==",
|
|
1132
1130
|
"license": "MIT",
|
|
1133
1131
|
"dependencies": {
|
|
1134
|
-
"@chevrotain/types": "~
|
|
1132
|
+
"@chevrotain/types": "~13.2.0"
|
|
1135
1133
|
},
|
|
1136
1134
|
"engines": {
|
|
1137
1135
|
"node": ">=22.12.0"
|
|
@@ -2155,9 +2153,9 @@
|
|
|
2155
2153
|
}
|
|
2156
2154
|
},
|
|
2157
2155
|
"node_modules/@types/node": {
|
|
2158
|
-
"version": "26.6.
|
|
2159
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.
|
|
2160
|
-
"integrity": "sha512-
|
|
2156
|
+
"version": "26.6.4",
|
|
2157
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.4.tgz",
|
|
2158
|
+
"integrity": "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==",
|
|
2161
2159
|
"license": "MIT",
|
|
2162
2160
|
"dependencies": {
|
|
2163
2161
|
"undici-types": "~8.9.0"
|
|
@@ -2234,9 +2232,9 @@
|
|
|
2234
2232
|
}
|
|
2235
2233
|
},
|
|
2236
2234
|
"node_modules/@vercel/cli-config": {
|
|
2237
|
-
"version": "0.3.
|
|
2238
|
-
"resolved": "https://registry.npmjs.org/@vercel/cli-config/-/cli-config-0.3.
|
|
2239
|
-
"integrity": "sha512-
|
|
2235
|
+
"version": "0.3.1",
|
|
2236
|
+
"resolved": "https://registry.npmjs.org/@vercel/cli-config/-/cli-config-0.3.1.tgz",
|
|
2237
|
+
"integrity": "sha512-sISbygqlFXYb8qfPu/+2Cf/A8IjrlAuZePdag3EDQr2PRA455s7UZtl9Ro674CWsB/saBRmz3jEkw0YsQs8OoQ==",
|
|
2240
2238
|
"license": "Apache-2.0",
|
|
2241
2239
|
"dependencies": {
|
|
2242
2240
|
"xdg-app-paths": "5",
|
|
@@ -2256,12 +2254,12 @@
|
|
|
2256
2254
|
}
|
|
2257
2255
|
},
|
|
2258
2256
|
"node_modules/@vercel/functions": {
|
|
2259
|
-
"version": "3.9.
|
|
2260
|
-
"resolved": "https://registry.npmjs.org/@vercel/functions/-/functions-3.9.
|
|
2261
|
-
"integrity": "sha512-
|
|
2257
|
+
"version": "3.9.10",
|
|
2258
|
+
"resolved": "https://registry.npmjs.org/@vercel/functions/-/functions-3.9.10.tgz",
|
|
2259
|
+
"integrity": "sha512-s/Hbm2e/9YDGqAgjsAuhdnSc07psq0AVuELkImVfzoT52CbHLT3DTVoN6N4wtVeel9Tv1G4xB7S2cEkPH4j0og==",
|
|
2262
2260
|
"license": "Apache-2.0",
|
|
2263
2261
|
"dependencies": {
|
|
2264
|
-
"@vercel/oidc": "3.8.
|
|
2262
|
+
"@vercel/oidc": "3.8.10"
|
|
2265
2263
|
},
|
|
2266
2264
|
"engines": {
|
|
2267
2265
|
"node": ">= 20"
|
|
@@ -2280,12 +2278,12 @@
|
|
|
2280
2278
|
}
|
|
2281
2279
|
},
|
|
2282
2280
|
"node_modules/@vercel/oidc": {
|
|
2283
|
-
"version": "3.8.
|
|
2284
|
-
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.8.
|
|
2285
|
-
"integrity": "sha512-
|
|
2281
|
+
"version": "3.8.10",
|
|
2282
|
+
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.8.10.tgz",
|
|
2283
|
+
"integrity": "sha512-sPw1kIfQEA6/2dB+zlAJHnhhNBRJ8t/t4HfFvKO+g0EPBA7NbCKyGovuhgAV6Mnl865ka7HqCEguIy596czdAw==",
|
|
2286
2284
|
"license": "Apache-2.0",
|
|
2287
2285
|
"dependencies": {
|
|
2288
|
-
"@vercel/cli-config": "0.3.
|
|
2286
|
+
"@vercel/cli-config": "0.3.1",
|
|
2289
2287
|
"@vercel/cli-exec": "1.0.1",
|
|
2290
2288
|
"jose": "^5.9.6"
|
|
2291
2289
|
},
|
|
@@ -2451,9 +2449,9 @@
|
|
|
2451
2449
|
}
|
|
2452
2450
|
},
|
|
2453
2451
|
"node_modules/baseline-browser-mapping": {
|
|
2454
|
-
"version": "2.11.
|
|
2455
|
-
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.
|
|
2456
|
-
"integrity": "sha512-
|
|
2452
|
+
"version": "2.11.27",
|
|
2453
|
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz",
|
|
2454
|
+
"integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==",
|
|
2457
2455
|
"license": "Apache-2.0",
|
|
2458
2456
|
"bin": {
|
|
2459
2457
|
"baseline-browser-mapping": "dist/cli.cjs"
|
|
@@ -2584,17 +2582,19 @@
|
|
|
2584
2582
|
}
|
|
2585
2583
|
},
|
|
2586
2584
|
"node_modules/chevrotain": {
|
|
2587
|
-
"version": "
|
|
2588
|
-
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-
|
|
2589
|
-
"integrity": "sha512-
|
|
2585
|
+
"version": "13.2.0",
|
|
2586
|
+
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-13.2.0.tgz",
|
|
2587
|
+
"integrity": "sha512-GTouk5YzgBxlTPCaDn5q0q2EJKkrYmnPF+HmF8v0Qo4r3iyPG7HJivkN1wthCXZcm3sIcZFpGTRovLPNdJNRCA==",
|
|
2590
2588
|
"license": "Apache-2.0",
|
|
2591
2589
|
"dependencies": {
|
|
2592
|
-
"@chevrotain/cst-dts-gen": "
|
|
2593
|
-
"@chevrotain/gast": "
|
|
2594
|
-
"@chevrotain/regexp-to-ast": "
|
|
2595
|
-
"@chevrotain/types": "
|
|
2596
|
-
"@chevrotain/utils": "
|
|
2597
|
-
|
|
2590
|
+
"@chevrotain/cst-dts-gen": "13.2.0",
|
|
2591
|
+
"@chevrotain/gast": "13.2.0",
|
|
2592
|
+
"@chevrotain/regexp-to-ast": "13.2.0",
|
|
2593
|
+
"@chevrotain/types": "13.2.0",
|
|
2594
|
+
"@chevrotain/utils": "13.2.0"
|
|
2595
|
+
},
|
|
2596
|
+
"engines": {
|
|
2597
|
+
"node": ">=22.0.0"
|
|
2598
2598
|
}
|
|
2599
2599
|
},
|
|
2600
2600
|
"node_modules/client-only": {
|
|
@@ -3279,9 +3279,9 @@
|
|
|
3279
3279
|
"license": "MIT"
|
|
3280
3280
|
},
|
|
3281
3281
|
"node_modules/electron-to-chromium": {
|
|
3282
|
-
"version": "1.5.
|
|
3283
|
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.
|
|
3284
|
-
"integrity": "sha512-
|
|
3282
|
+
"version": "1.5.444",
|
|
3283
|
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.444.tgz",
|
|
3284
|
+
"integrity": "sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==",
|
|
3285
3285
|
"license": "ISC"
|
|
3286
3286
|
},
|
|
3287
3287
|
"node_modules/elkjs": {
|
|
@@ -4509,9 +4509,9 @@
|
|
|
4509
4509
|
}
|
|
4510
4510
|
},
|
|
4511
4511
|
"node_modules/lodash-es": {
|
|
4512
|
-
"version": "4.
|
|
4513
|
-
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.
|
|
4514
|
-
"integrity": "sha512-
|
|
4512
|
+
"version": "4.18.1",
|
|
4513
|
+
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
|
|
4514
|
+
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
|
4515
4515
|
"license": "MIT"
|
|
4516
4516
|
},
|
|
4517
4517
|
"node_modules/longest-streak": {
|
|
@@ -4915,17 +4915,17 @@
|
|
|
4915
4915
|
"license": "MIT"
|
|
4916
4916
|
},
|
|
4917
4917
|
"node_modules/mermaid": {
|
|
4918
|
-
"version": "12.
|
|
4919
|
-
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-12.
|
|
4920
|
-
"integrity": "sha512
|
|
4918
|
+
"version": "12.1.0",
|
|
4919
|
+
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-12.1.0.tgz",
|
|
4920
|
+
"integrity": "sha512-wlVCp+8eTupfCeeFvoZNNiTuHrvag0P2jz/ILgb/f/6jkVokUefOcujefi8qUe/j2asHiSePncVsz/xzzA80LQ==",
|
|
4921
4921
|
"license": "MIT",
|
|
4922
4922
|
"dependencies": {
|
|
4923
4923
|
"@braintree/sanitize-url": "^7.1.2",
|
|
4924
4924
|
"@iconify/utils": "^3.0.2",
|
|
4925
|
-
"@mermaid-js/parser": "^2.0.
|
|
4925
|
+
"@mermaid-js/parser": "^2.0.1",
|
|
4926
4926
|
"@types/d3": "^7.4.3",
|
|
4927
4927
|
"@upsetjs/venn.js": "^2.0.0",
|
|
4928
|
-
"chevrotain": "~
|
|
4928
|
+
"chevrotain": "~13.2.0",
|
|
4929
4929
|
"cytoscape": "^3.34.0",
|
|
4930
4930
|
"cytoscape-cose-bilkent": "^4.1.0",
|
|
4931
4931
|
"cytoscape-fcose": "^2.2.0",
|