jamdesk 1.1.189 → 1.1.190
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.190",
|
|
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",
|
|
@@ -15,13 +15,12 @@ import {
|
|
|
15
15
|
import { parseFrontmatterLenient } from '@/lib/frontmatter-utils';
|
|
16
16
|
import { siteHasOpenApiSpecs } from '@/lib/api-specs-bundle';
|
|
17
17
|
import { getContextualOptions } from '@/lib/contextual-defaults';
|
|
18
|
-
import { isIsrMode,
|
|
18
|
+
import { isIsrMode, getBaseUrl, getDocsSubpath } from '@/lib/page-isr-helpers';
|
|
19
19
|
import { apiSpecsMarkdownFooter } from '@/lib/api-specs-markdown-hint';
|
|
20
20
|
import { collectLocalSpecPaths } from '@/lib/openapi/operation-description';
|
|
21
21
|
import { operationMarkdownForRef } from '@/lib/openapi/enrich-page-infos';
|
|
22
22
|
import { injectAgentDirective } from '@/lib/agent-directive';
|
|
23
23
|
import { logger } from '@/shared/logger';
|
|
24
|
-
import { DEFAULT_DOCS_SUBPATH } from '@/shared/docs-subpath';
|
|
25
24
|
import type { DocsConfig } from '@/lib/docs-types';
|
|
26
25
|
import type { OpenAPI } from 'openapi-types';
|
|
27
26
|
|
|
@@ -113,7 +112,14 @@ export async function GET(
|
|
|
113
112
|
// Arg order is load-bearing: `withDirective` is the agent-visible body the
|
|
114
113
|
// footer is appended to; `raw` is the unfiltered source the helper reads
|
|
115
114
|
// frontmatter from (visibility filtering never touches the leading YAML block).
|
|
116
|
-
const body = await withApiSpecsHint(
|
|
115
|
+
const body = await withApiSpecsHint(
|
|
116
|
+
withDirective,
|
|
117
|
+
raw,
|
|
118
|
+
project,
|
|
119
|
+
config,
|
|
120
|
+
request.headers,
|
|
121
|
+
hostAtDocs,
|
|
122
|
+
);
|
|
117
123
|
|
|
118
124
|
// Two paths reach this handler via proxy rewrite:
|
|
119
125
|
// (a) `.md` URL: unique CDN cache key, safe to share.
|
|
@@ -143,17 +149,24 @@ export async function GET(
|
|
|
143
149
|
* in ISR. Non-fatal: any failure returns the markdown unchanged — it never throws
|
|
144
150
|
* and never 500s an export.
|
|
145
151
|
*
|
|
146
|
-
* The URL is built
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
152
|
+
* The URL is built with `getBaseUrl` — the same helper the canonical and OG
|
|
153
|
+
* tags use — so the link points at the host the reader actually asked for. It
|
|
154
|
+
* reads only TRUSTED_PROXY_HEADERS (canonical host, forwarded host), which
|
|
155
|
+
* buildProjectHeaders strips from the inbound request, so it stays unspoofable.
|
|
156
|
+
*
|
|
157
|
+
* It used to hard-code `https://<slug>.jamdesk.app[/docs]` off the slug. Both
|
|
158
|
+
* hosts serve the zip today, so that read as a cosmetic choice — but a tenant
|
|
159
|
+
* with `customDomainOnly: true` 404s its own bare subdomain for anything that
|
|
160
|
+
* isn't a presentation asset, and `.zip` is not on that exemption list. The
|
|
161
|
+
* footer would have handed agents a dead link the moment a customer ticked it.
|
|
150
162
|
*/
|
|
151
163
|
async function withApiSpecsHint(
|
|
152
164
|
markdown: string,
|
|
153
165
|
raw: string,
|
|
154
166
|
project: string,
|
|
155
167
|
config: DocsConfig | null,
|
|
156
|
-
|
|
168
|
+
headers: Headers,
|
|
169
|
+
hostAtDocs: boolean,
|
|
157
170
|
): Promise<string> {
|
|
158
171
|
try {
|
|
159
172
|
if (!isIsrMode()) return markdown;
|
|
@@ -196,23 +209,16 @@ async function withApiSpecsHint(
|
|
|
196
209
|
}
|
|
197
210
|
}
|
|
198
211
|
|
|
199
|
-
// Config (fetched once in GET) drives the site-wide spec check
|
|
200
|
-
// menu-enabled check
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
// the default-enabled menu.
|
|
212
|
+
// Config (fetched once in GET) drives the site-wide spec check and the
|
|
213
|
+
// menu-enabled check. A failed/missing config must not block a page-level
|
|
214
|
+
// `openapi:` page (which qualifies on its own): treat it as "no site
|
|
215
|
+
// specs" but keep the default-enabled menu.
|
|
204
216
|
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
// '/docs' through instead would silently move every default tenant's zip
|
|
211
|
-
// link. Both shapes resolve (dotted paths skip the prefix redirects), so
|
|
212
|
-
// this is byte-identity, not correctness.
|
|
213
|
-
const customPrefix =
|
|
214
|
-
docsPrefix && docsPrefix !== DEFAULT_DOCS_SUBPATH ? docsPrefix : undefined;
|
|
215
|
-
const zipUrl = `${getBaseUrlFromConfig(project, !!config?.hostAtDocs, customPrefix)}/api-specs.zip`;
|
|
217
|
+
// getBaseUrl resolves the docs prefix from the same headers it resolves
|
|
218
|
+
// the host from, so a tenant at example.com/help gets
|
|
219
|
+
// `https://example.com/help/api-specs.zip` and a bare subdomain still gets
|
|
220
|
+
// the root. The zip route is registered at both `/` and `/<prefix>`.
|
|
221
|
+
const zipUrl = `${getBaseUrl(headers, project, hostAtDocs)}/api-specs.zip`;
|
|
216
222
|
|
|
217
223
|
const footer = apiSpecsMarkdownFooter({
|
|
218
224
|
isIsr: true,
|
|
@@ -492,6 +492,18 @@ export async function DocsChrome({
|
|
|
492
492
|
const bannerContent = config.banner?.content?.trim() || '';
|
|
493
493
|
const bannerNoFlash = !embed && !!bannerContent && config.banner?.dismissible === true && !!resolvedProjectSlug;
|
|
494
494
|
|
|
495
|
+
// SSR scroll lock, off under `next dev`. React 19 logs "Encountered a script
|
|
496
|
+
// tag while rendering React component" for any inline <script> in a
|
|
497
|
+
// component tree, and Next surfaces it as a full-screen console error on
|
|
498
|
+
// every docs page in `jamdesk dev` — the same reason the subdomain-error
|
|
499
|
+
// suppressor below is prod-gated. The lock only defends against Chrome's
|
|
500
|
+
// same-tab CROSS-ORIGIN scroll-restore heuristic, which needs a real prior
|
|
501
|
+
// origin, so dev loses nothing. The attribute and the bootstrap that
|
|
502
|
+
// releases it must move together: shipping the attribute without the script
|
|
503
|
+
// leaves #content-scroll-container permanently unscrollable.
|
|
504
|
+
// Read at render time (not module scope) so tests can exercise both arms.
|
|
505
|
+
const scrollLockEnabled = process.env.NODE_ENV !== 'development';
|
|
506
|
+
|
|
495
507
|
// Font Awesome CSS uses preinit (not preload) so React 19 emits the
|
|
496
508
|
// stylesheet link in <head> at SSR time. preinit (rather than a runtime
|
|
497
509
|
// beforeInteractive <Script>/<link> injection) is what gets the @font-face
|
|
@@ -510,7 +522,7 @@ export async function DocsChrome({
|
|
|
510
522
|
preinit(FA_CSS_HREF, { as: 'style' });
|
|
511
523
|
|
|
512
524
|
return (
|
|
513
|
-
<html lang={toHreflang(lang)} dir={dir} suppressHydrationWarning data-scroll-behavior="smooth" data-scroll-locked=
|
|
525
|
+
<html lang={toHreflang(lang)} dir={dir} suppressHydrationWarning data-scroll-behavior="smooth" data-scroll-locked={scrollLockEnabled ? 'true' : undefined}>
|
|
514
526
|
<head>
|
|
515
527
|
{/* CMP scripts must be first + synchronous: auto-blocking CMPs
|
|
516
528
|
(Osano/Termly) need to intercept later scripts. Never gated.
|
|
@@ -534,18 +546,22 @@ export async function DocsChrome({
|
|
|
534
546
|
restore attempts hit a structurally non-scrollable element
|
|
535
547
|
(scrollTop pinned to 0).
|
|
536
548
|
*/}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
+
{scrollLockEnabled && (
|
|
550
|
+
<>
|
|
551
|
+
<script
|
|
552
|
+
dangerouslySetInnerHTML={{
|
|
553
|
+
__html: scrollLockBootstrap,
|
|
554
|
+
}}
|
|
555
|
+
/>
|
|
556
|
+
{/* JS-off fallback — bootstrap can't unlock, so override the rule. */}
|
|
557
|
+
<noscript
|
|
558
|
+
dangerouslySetInnerHTML={{
|
|
559
|
+
__html:
|
|
560
|
+
'<style>html[data-scroll-locked] #content-scroll-container{overflow-y:auto !important;}</style>',
|
|
561
|
+
}}
|
|
562
|
+
/>
|
|
563
|
+
</>
|
|
564
|
+
)}
|
|
549
565
|
{/* Banner no-flash: hide a previously-dismissed (dismissible) banner
|
|
550
566
|
before first paint. The <style> is inert until the script sets the
|
|
551
567
|
attribute; the script is prod-gated like the analytics inline script. */}
|