jamdesk 1.1.76 → 1.1.78
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/__tests__/unit/deps.test.js +184 -0
- package/dist/__tests__/unit/deps.test.js.map +1 -1
- package/dist/__tests__/unit/dev-spinner-ownership.test.d.ts +2 -0
- package/dist/__tests__/unit/dev-spinner-ownership.test.d.ts.map +1 -0
- package/dist/__tests__/unit/dev-spinner-ownership.test.js +37 -0
- package/dist/__tests__/unit/dev-spinner-ownership.test.js.map +1 -0
- package/dist/__tests__/unit/spinner.test.d.ts +2 -0
- package/dist/__tests__/unit/spinner.test.d.ts.map +1 -0
- package/dist/__tests__/unit/spinner.test.js +83 -0
- package/dist/__tests__/unit/spinner.test.js.map +1 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +13 -3
- package/dist/commands/dev.js.map +1 -1
- package/dist/lib/deps.d.ts +22 -0
- package/dist/lib/deps.d.ts.map +1 -1
- package/dist/lib/deps.js +117 -23
- package/dist/lib/deps.js.map +1 -1
- package/dist/lib/spinner.d.ts +24 -0
- package/dist/lib/spinner.d.ts.map +1 -1
- package/dist/lib/spinner.js +59 -0
- package/dist/lib/spinner.js.map +1 -1
- package/package.json +1 -1
- package/vendored/lib/middleware-helpers.ts +79 -8
- package/vendored/lib/page-isr-helpers.ts +14 -9
- package/vendored/lib/project-resolver.ts +21 -5
- package/vendored/lib/render-doc-page.tsx +29 -8
- package/vendored/workspace-package-lock.json +3 -3
|
@@ -58,8 +58,23 @@ export interface ProjectResolutionResult {
|
|
|
58
58
|
* (i.e., a user's own domain pointing at our CNAME), vs via the
|
|
59
59
|
* subdomain branch (slug.jamdesk.app). Used by the branded inactive
|
|
60
60
|
* page to show owner-sign-in hints only to real customers.
|
|
61
|
+
*
|
|
62
|
+
* Renamed from `customDomain` to disambiguate from the new
|
|
63
|
+
* `customDomain?: string` mirror below (which carries the public
|
|
64
|
+
* canonical hostname read from `projectCfg`). The two fields answer
|
|
65
|
+
* different questions:
|
|
66
|
+
* - `viaCustomDomain` = "did this request hit the domain: Redis key?"
|
|
67
|
+
* - `customDomain` = "what is the project's registered public face?"
|
|
61
68
|
*/
|
|
62
|
-
|
|
69
|
+
viaCustomDomain?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Public canonical hostname for this slug, mirrored from `projectCfg`.
|
|
72
|
+
* Set only when the project owner has registered + activated a custom
|
|
73
|
+
* domain. Used by the proxy to override the canonical URL when serving
|
|
74
|
+
* a hostAtDocs project directly via *.jamdesk.app (avoiding duplicate-
|
|
75
|
+
* URL indexing — the upstream subdomain is not the public face).
|
|
76
|
+
*/
|
|
77
|
+
customDomain?: string;
|
|
63
78
|
}
|
|
64
79
|
|
|
65
80
|
/**
|
|
@@ -124,7 +139,11 @@ export async function handleProjectResolution(
|
|
|
124
139
|
slug,
|
|
125
140
|
});
|
|
126
141
|
const config = await getProjectConfig(slug);
|
|
127
|
-
return {
|
|
142
|
+
return {
|
|
143
|
+
projectSlug: slug,
|
|
144
|
+
hostAtDocs: config.hostAtDocs,
|
|
145
|
+
customDomain: config.customDomain,
|
|
146
|
+
};
|
|
128
147
|
}
|
|
129
148
|
|
|
130
149
|
log('info', 'Resolving project from hostname', { hostname });
|
|
@@ -166,8 +185,18 @@ export async function handleProjectResolution(
|
|
|
166
185
|
getProjectConfig(projectSlug),
|
|
167
186
|
getProjectInactive(projectSlug),
|
|
168
187
|
]);
|
|
169
|
-
log('info', 'Project resolved from subdomain', {
|
|
170
|
-
|
|
188
|
+
log('info', 'Project resolved from subdomain', {
|
|
189
|
+
hostname,
|
|
190
|
+
projectSlug,
|
|
191
|
+
inactive,
|
|
192
|
+
customDomain: config.customDomain,
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
projectSlug,
|
|
196
|
+
hostAtDocs: config.hostAtDocs,
|
|
197
|
+
inactive,
|
|
198
|
+
customDomain: config.customDomain,
|
|
199
|
+
};
|
|
171
200
|
}
|
|
172
201
|
}
|
|
173
202
|
|
|
@@ -204,7 +233,7 @@ export async function handleProjectResolution(
|
|
|
204
233
|
hostAtDocs: resolution.hostAtDocs,
|
|
205
234
|
domainStatus: resolution.domainStatus,
|
|
206
235
|
inactive,
|
|
207
|
-
|
|
236
|
+
viaCustomDomain: true,
|
|
208
237
|
};
|
|
209
238
|
}
|
|
210
239
|
|
|
@@ -596,15 +625,48 @@ const TRUSTED_PROXY_HEADERS = [
|
|
|
596
625
|
'x-jd-custom-domain',
|
|
597
626
|
'x-jd-project-name',
|
|
598
627
|
'x-jd-project-logo',
|
|
628
|
+
// Canonical override: middleware writes this when serving *.jamdesk.app
|
|
629
|
+
// directly for a hostAtDocs project that has a registered custom
|
|
630
|
+
// domain. The page render path uses it to emit the public-face canonical
|
|
631
|
+
// instead of the upstream subdomain URL.
|
|
632
|
+
'x-jd-canonical-host',
|
|
633
|
+
// Set when *.jamdesk.app serves a hostAtDocs project that has NOT
|
|
634
|
+
// registered a custom domain yet — page emits robots: noindex so the
|
|
635
|
+
// upstream URL doesn't compete with the (yet-to-arrive) public face
|
|
636
|
+
// in search results.
|
|
637
|
+
'x-jd-noindex',
|
|
599
638
|
] as const;
|
|
600
639
|
|
|
640
|
+
/**
|
|
641
|
+
* Optional extras for `buildProjectHeaders`. All fields are middleware-
|
|
642
|
+
* authored; the corresponding header is in `TRUSTED_PROXY_HEADERS` so an
|
|
643
|
+
* inbound spoof is stripped before we set our own value.
|
|
644
|
+
*/
|
|
645
|
+
export interface BuildProjectHeadersOptions {
|
|
646
|
+
/**
|
|
647
|
+
* Public-face canonical host. Set when serving *.jamdesk.app directly
|
|
648
|
+
* for a hostAtDocs project that has a registered custom domain — the
|
|
649
|
+
* page render path emits this host in <link rel="canonical"> instead
|
|
650
|
+
* of the upstream subdomain.
|
|
651
|
+
*/
|
|
652
|
+
canonicalHost?: string;
|
|
653
|
+
/**
|
|
654
|
+
* When true, emit `x-jd-noindex: true` so the page emits a robots
|
|
655
|
+
* noindex tag. Used for *.jamdesk.app subdomains of hostAtDocs
|
|
656
|
+
* projects without a custom domain yet.
|
|
657
|
+
*/
|
|
658
|
+
noindex?: boolean;
|
|
659
|
+
}
|
|
660
|
+
|
|
601
661
|
/**
|
|
602
662
|
* Build response headers with project context.
|
|
603
663
|
*
|
|
604
664
|
* Sets:
|
|
605
|
-
* - x-project-slug
|
|
606
|
-
* - x-host-at-docs
|
|
607
|
-
* - x-jd-language
|
|
665
|
+
* - x-project-slug — resolved project for downstream handlers
|
|
666
|
+
* - x-host-at-docs — whether docs are mounted at /docs
|
|
667
|
+
* - x-jd-language — locale code if the path starts with one (e.g. /fr/...)
|
|
668
|
+
* - x-jd-canonical-host — public-face host (opts.canonicalHost)
|
|
669
|
+
* - x-jd-noindex — "true" when opts.noindex is set
|
|
608
670
|
*
|
|
609
671
|
* Strips any client-supplied copies of those headers from the inbound
|
|
610
672
|
* request to prevent header smuggling.
|
|
@@ -615,6 +677,7 @@ const TRUSTED_PROXY_HEADERS = [
|
|
|
615
677
|
* @param pathname - The request pathname (request.nextUrl.pathname or, for
|
|
616
678
|
* the unlock direct-hit branch, the `from` query param). Used to derive
|
|
617
679
|
* the active locale so the root layout can emit <html lang>.
|
|
680
|
+
* @param opts - Optional extras (canonical host override, noindex flag).
|
|
618
681
|
* @returns New headers with project context added
|
|
619
682
|
*/
|
|
620
683
|
export function buildProjectHeaders(
|
|
@@ -622,6 +685,7 @@ export function buildProjectHeaders(
|
|
|
622
685
|
existingHeaders: Headers,
|
|
623
686
|
hostAtDocs: boolean,
|
|
624
687
|
pathname: string,
|
|
688
|
+
opts: BuildProjectHeadersOptions = {},
|
|
625
689
|
): Headers {
|
|
626
690
|
const newHeaders = new Headers(existingHeaders);
|
|
627
691
|
for (const h of TRUSTED_PROXY_HEADERS) {
|
|
@@ -636,6 +700,13 @@ export function buildProjectHeaders(
|
|
|
636
700
|
newHeaders.set('x-jd-language', language);
|
|
637
701
|
}
|
|
638
702
|
|
|
703
|
+
if (opts.canonicalHost) {
|
|
704
|
+
newHeaders.set('x-jd-canonical-host', opts.canonicalHost);
|
|
705
|
+
}
|
|
706
|
+
if (opts.noindex) {
|
|
707
|
+
newHeaders.set('x-jd-noindex', 'true');
|
|
708
|
+
}
|
|
709
|
+
|
|
639
710
|
return newHeaders;
|
|
640
711
|
}
|
|
641
712
|
|
|
@@ -130,15 +130,20 @@ export function parseCacheKey(cacheKey: string): { projectSlug: string; pagePath
|
|
|
130
130
|
/**
|
|
131
131
|
* Get base URL for SEO metadata in ISR mode.
|
|
132
132
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
133
|
+
* Header priority (highest first):
|
|
134
|
+
* 1. x-jd-canonical-host — internal override set by middleware when a
|
|
135
|
+
* hostAtDocs project is served directly via *.jamdesk.app and has a
|
|
136
|
+
* registered customDomain. Forces the canonical to the public face.
|
|
137
|
+
* 2. x-jamdesk-forwarded-host — set by the Cloudflare Worker proxy
|
|
138
|
+
* (e.g. jamdesk.com → forwarded `jamdesk.com`).
|
|
139
|
+
* 3. host header — direct request hostname.
|
|
140
|
+
* 4. Subdomain fallback `<slug>.jamdesk.app` (no headers available).
|
|
137
141
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
142
|
+
* Both override headers are in TRUSTED_PROXY_HEADERS so a client can't
|
|
143
|
+
* spoof them.
|
|
140
144
|
*
|
|
141
|
-
* When hostAtDocs=true, includes /docs path prefix
|
|
145
|
+
* When hostAtDocs=true, includes /docs path prefix (forwarded-host and
|
|
146
|
+
* canonical-host paths only; subdomain fallback always serves at root).
|
|
142
147
|
*
|
|
143
148
|
* @param headers - Request headers object
|
|
144
149
|
* @param projectSlug - Project identifier (fallback for subdomain URL)
|
|
@@ -146,9 +151,9 @@ export function parseCacheKey(cacheKey: string): { projectSlug: string; pagePath
|
|
|
146
151
|
* @returns Base URL (e.g., 'https://jamdesk.com/docs' or 'https://acme.jamdesk.app')
|
|
147
152
|
*/
|
|
148
153
|
export function getBaseUrl(headers: Headers, projectSlug: string, hostAtDocs = false): string {
|
|
149
|
-
|
|
154
|
+
const canonicalHost = headers.get('x-jd-canonical-host');
|
|
150
155
|
const forwardedHost = headers.get('x-jamdesk-forwarded-host');
|
|
151
|
-
const host = forwardedHost || headers.get('host');
|
|
156
|
+
const host = canonicalHost || forwardedHost || headers.get('host');
|
|
152
157
|
|
|
153
158
|
if (host) {
|
|
154
159
|
const hostname = host.split(':')[0];
|
|
@@ -97,6 +97,17 @@ export async function resolveCustomDomain(hostname: string): Promise<DomainResol
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Resolved project configuration mirrored from Redis `projectCfg:<slug>`.
|
|
102
|
+
*
|
|
103
|
+
* `customDomain` is set on activation by the domain.ts helper when a custom
|
|
104
|
+
* domain is TXT-verified. Used by middleware to emit canonical-host headers.
|
|
105
|
+
*/
|
|
106
|
+
export interface ProjectCfg {
|
|
107
|
+
hostAtDocs: boolean;
|
|
108
|
+
customDomain?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
/**
|
|
101
112
|
* Get project configuration for a subdomain (silent-fallback variant).
|
|
102
113
|
* Used for hostAtDocs setting on *.jamdesk.app domains.
|
|
@@ -111,7 +122,7 @@ export async function resolveCustomDomain(hostname: string): Promise<DomainResol
|
|
|
111
122
|
* would memoize a wrong `false` for the cache TTL, the exact failure
|
|
112
123
|
* mode that produced the jamdesk.com/docs/* 404 incident.
|
|
113
124
|
*/
|
|
114
|
-
export async function getProjectConfig(projectSlug: string): Promise<
|
|
125
|
+
export async function getProjectConfig(projectSlug: string): Promise<ProjectCfg> {
|
|
115
126
|
if (!redis) {
|
|
116
127
|
return { hostAtDocs: false };
|
|
117
128
|
}
|
|
@@ -119,8 +130,10 @@ export async function getProjectConfig(projectSlug: string): Promise<{ hostAtDoc
|
|
|
119
130
|
try {
|
|
120
131
|
const cfgRaw = await redis.get(`projectCfg:${projectSlug}`);
|
|
121
132
|
const cfg = parseRedisConfig(cfgRaw);
|
|
122
|
-
|
|
123
|
-
|
|
133
|
+
return {
|
|
134
|
+
hostAtDocs: cfg?.hostAtDocs === true,
|
|
135
|
+
customDomain: typeof cfg?.customDomain === 'string' ? cfg.customDomain : undefined,
|
|
136
|
+
};
|
|
124
137
|
} catch {
|
|
125
138
|
return { hostAtDocs: false };
|
|
126
139
|
}
|
|
@@ -136,14 +149,17 @@ export async function getProjectConfig(projectSlug: string): Promise<{ hostAtDoc
|
|
|
136
149
|
* A missing `projectCfg:<slug>` key is NOT an error — that's the legitimate
|
|
137
150
|
* "project hasn't opted into hostAtDocs" case and is safe to cache.
|
|
138
151
|
*/
|
|
139
|
-
export async function getProjectConfigStrict(projectSlug: string): Promise<
|
|
152
|
+
export async function getProjectConfigStrict(projectSlug: string): Promise<ProjectCfg> {
|
|
140
153
|
if (!redis) {
|
|
141
154
|
return { hostAtDocs: false };
|
|
142
155
|
}
|
|
143
156
|
|
|
144
157
|
const cfgRaw = await redis.get(`projectCfg:${projectSlug}`);
|
|
145
158
|
const cfg = parseRedisConfig(cfgRaw);
|
|
146
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
hostAtDocs: cfg?.hostAtDocs === true,
|
|
161
|
+
customDomain: typeof cfg?.customDomain === 'string' ? cfg.customDomain : undefined,
|
|
162
|
+
};
|
|
147
163
|
}
|
|
148
164
|
|
|
149
165
|
/**
|
|
@@ -191,6 +191,13 @@ function resolveSlug(normalizedSlug: string[], config: DocsConfig): string[] {
|
|
|
191
191
|
export async function buildDocMetadata(input: RenderInput): Promise<Metadata> {
|
|
192
192
|
const { slug: slugInput, projectSlug, hostAtDocs, requestHeaders } = input;
|
|
193
193
|
|
|
194
|
+
// Middleware sets `x-jd-noindex: true` when serving a hostAtDocs project
|
|
195
|
+
// directly via *.jamdesk.app and the project has no registered custom
|
|
196
|
+
// domain yet. The upstream subdomain shouldn't compete with the (yet-to-
|
|
197
|
+
// arrive) public face in search results — emit robots noindex so Google
|
|
198
|
+
// skips it. See proxy.ts → projectHeaderOptsForCanonical for the source.
|
|
199
|
+
const noindexHeader = requestHeaders?.get('x-jd-noindex') === 'true';
|
|
200
|
+
|
|
194
201
|
if (isIsrMode()) {
|
|
195
202
|
if (!projectSlug) return { title: 'Not Found' };
|
|
196
203
|
const exists = await projectExists(projectSlug);
|
|
@@ -234,11 +241,18 @@ export async function buildDocMetadata(input: RenderInput): Promise<Metadata> {
|
|
|
234
241
|
|
|
235
242
|
const markdownHref = `${hostAtDocs ? '/docs/' : '/'}${pagePath}.md`;
|
|
236
243
|
|
|
244
|
+
// noindexHeader (middleware) and isRoot override frontmatter robots
|
|
245
|
+
// (already in seoMetadata) via spread order.
|
|
246
|
+
const robotsOverride =
|
|
247
|
+
isRoot || noindexHeader
|
|
248
|
+
? { robots: { index: false, follow: true } as const }
|
|
249
|
+
: {};
|
|
250
|
+
|
|
237
251
|
return {
|
|
238
252
|
title: titleValue,
|
|
239
253
|
description: data.description || '',
|
|
240
254
|
...seoMetadata,
|
|
241
|
-
...
|
|
255
|
+
...robotsOverride,
|
|
242
256
|
alternates: {
|
|
243
257
|
...seoMetadata.alternates,
|
|
244
258
|
types: {
|
|
@@ -437,13 +451,20 @@ export async function renderDocPage(input: RenderInput): Promise<ReactElement> {
|
|
|
437
451
|
}
|
|
438
452
|
}
|
|
439
453
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
454
|
+
// Gate on VERCEL_REGION so the telemetry only fires in Vercel runtime
|
|
455
|
+
// (production + preview). In `jamdesk dev` the structured JSON would
|
|
456
|
+
// pollute the user's terminal on every page render. `[r2-timing]` logs
|
|
457
|
+
// are kept out of CLI dev by stubbing `lib/r2-content.ts`; this is the
|
|
458
|
+
// equivalent guard for `[render-timing]`, which has no R2 dependency.
|
|
459
|
+
if (process.env.VERCEL_REGION) {
|
|
460
|
+
logger.info('[render-timing]', {
|
|
461
|
+
region: process.env.VERCEL_REGION,
|
|
462
|
+
pagePath,
|
|
463
|
+
snippetInlineMs,
|
|
464
|
+
openApiMs,
|
|
465
|
+
openApiCandidates,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
447
468
|
|
|
448
469
|
let mdxApiMethod: HttpMethod | null = null;
|
|
449
470
|
let mdxApiPath: string | null = null;
|
|
@@ -2987,9 +2987,9 @@
|
|
|
2987
2987
|
"license": "MIT"
|
|
2988
2988
|
},
|
|
2989
2989
|
"node_modules/electron-to-chromium": {
|
|
2990
|
-
"version": "1.5.
|
|
2991
|
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.
|
|
2992
|
-
"integrity": "sha512-
|
|
2990
|
+
"version": "1.5.353",
|
|
2991
|
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz",
|
|
2992
|
+
"integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==",
|
|
2993
2993
|
"license": "ISC"
|
|
2994
2994
|
},
|
|
2995
2995
|
"node_modules/enhanced-resolve": {
|