jamdesk 1.1.205 → 1.1.207

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.
Files changed (47) hide show
  1. package/dist/__tests__/unit/turbopack-loader-config-drift.test.d.ts +20 -0
  2. package/dist/__tests__/unit/turbopack-loader-config-drift.test.d.ts.map +1 -0
  3. package/dist/__tests__/unit/turbopack-loader-config-drift.test.js +79 -0
  4. package/dist/__tests__/unit/turbopack-loader-config-drift.test.js.map +1 -0
  5. package/dist/__tests__/unit/vendored-sync.test.js +9 -0
  6. package/dist/__tests__/unit/vendored-sync.test.js.map +1 -1
  7. package/dist/lib/deps.js +3 -3
  8. package/dist/lib/deps.js.map +1 -1
  9. package/package.json +5 -5
  10. package/vendored/app/[[...slug]]/page.tsx +1 -102
  11. package/vendored/app/api/jd/auth/logout/route.ts +36 -4
  12. package/vendored/app/api/jd/unlock/route.ts +1 -4
  13. package/vendored/app/layout.tsx +43 -4
  14. package/vendored/components/CodeBlockCopyButton.tsx +7 -2
  15. package/vendored/components/layout/LayoutWrapper.tsx +7 -0
  16. package/vendored/components/mdx/CodeGroup.tsx +161 -13
  17. package/vendored/components/mdx/MDXComponents.tsx +93 -50
  18. package/vendored/components/navigation/Header.tsx +7 -0
  19. package/vendored/components/navigation/LanguageSelector.tsx +35 -0
  20. package/vendored/components/navigation/ThemePreviewPicker.tsx +197 -0
  21. package/vendored/components/ui/CodePanel.tsx +88 -51
  22. package/vendored/components/ui/CodePanelModal.tsx +57 -36
  23. package/vendored/hooks/useWheelScrollChaining.ts +181 -0
  24. package/vendored/lib/auth-plane-write-warning.ts +84 -0
  25. package/vendored/lib/docs-types.ts +12 -0
  26. package/vendored/lib/jwt-key-build-warning.ts +38 -0
  27. package/vendored/lib/language-cookie.ts +20 -0
  28. package/vendored/lib/language-matcher.ts +124 -0
  29. package/vendored/lib/language-utils.ts +103 -0
  30. package/vendored/lib/languages-artifact.ts +160 -0
  31. package/vendored/lib/layout-helpers.tsx +16 -3
  32. package/vendored/lib/mdx-import-scan.ts +110 -0
  33. package/vendored/lib/middleware-helpers.ts +228 -1
  34. package/vendored/lib/page-timestamps.ts +36 -0
  35. package/vendored/lib/rehype-code-meta.ts +74 -9
  36. package/vendored/lib/render-doc-page.tsx +14 -3
  37. package/vendored/lib/revalidation-helpers.ts +3 -0
  38. package/vendored/lib/root-page-slug.ts +9 -4
  39. package/vendored/lib/shiki-transformers.ts +13 -0
  40. package/vendored/lib/static-artifacts.ts +70 -2
  41. package/vendored/lib/theme-preview-context.tsx +39 -0
  42. package/vendored/lib/theme-preview.ts +150 -0
  43. package/vendored/lib/unlock-audit.ts +10 -0
  44. package/vendored/next.config.js +32 -0
  45. package/vendored/schema/docs-schema.json +21 -0
  46. package/vendored/scripts/turbopack-js-to-ts-loader.cjs +51 -0
  47. package/vendored/workspace-package-lock.json +231 -177
@@ -0,0 +1,39 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext, type ReactNode } from 'react';
4
+ import type { ThemeName } from '@/themes';
5
+
6
+ export interface ThemePreviewValue {
7
+ /** Whether the picker may render. False for every project but jamdesk-docs. */
8
+ enabled: boolean;
9
+ /** The theme currently rendering — the previewed one when a cookie is active. */
10
+ activeTheme: ThemeName;
11
+ /** The project's own theme from docs.json, before any preview override. */
12
+ defaultTheme: ThemeName;
13
+ }
14
+
15
+ // Default is disabled: a consumer rendered without a provider (i.e. every
16
+ // customer site) gets nothing.
17
+ const ThemePreviewContext = createContext<ThemePreviewValue>({
18
+ enabled: false,
19
+ activeTheme: 'jam',
20
+ defaultTheme: 'jam',
21
+ });
22
+
23
+ export function ThemePreviewProvider({
24
+ children,
25
+ value,
26
+ }: {
27
+ children: ReactNode;
28
+ value: ThemePreviewValue;
29
+ }): React.ReactElement {
30
+ return (
31
+ <ThemePreviewContext.Provider value={value}>
32
+ {children}
33
+ </ThemePreviewContext.Provider>
34
+ );
35
+ }
36
+
37
+ export function useThemePreview(): ThemePreviewValue {
38
+ return useContext(ThemePreviewContext);
39
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Theme preview gate — jamdesk-docs only.
3
+ *
4
+ * A visitor picks a theme from the header dropdown; the picker writes a cookie
5
+ * and reloads, and `app/layout.tsx` applies it by overriding `config.theme`
6
+ * before rendering DocsChrome. Everything in this file is pure so the entire
7
+ * gate can be tested without rendering or touching the environment.
8
+ *
9
+ * TWO OPERATIONAL INVARIANTS live here because nothing else in the tree records
10
+ * them:
11
+ *
12
+ * 1. KILL SWITCH — `THEME_PREVIEW_ENABLED`, read in `app/layout.tsx` through
13
+ * `isThemePreviewEnvEnabled`. It is **ON BY DEFAULT**: unset, empty or any
14
+ * unrecognised value leaves the picker live. Only `false`, `0`, `off` and
15
+ * `no` (any case, surrounding whitespace ignored) disable it. Setting it
16
+ * also stops the cookie being read, so visitors holding a `jd_theme` cookie
17
+ * fall back to the project's own theme on their next load.
18
+ *
19
+ * It is NOT a runtime toggle, and the rollback it buys is not a light one:
20
+ * the variable is a Vercel env var read in the SHARED ROOT LAYOUT, so
21
+ * flipping it takes a full ISR redeploy (`vercel --prod` from
22
+ * `build-service`) across all ~93 tenants — the same fleet-wide surface the
23
+ * 2026-09-03 `ERR_REQUIRE_ESM` outage came through. Verify such a deploy
24
+ * with `node scripts/smoke-tenants.mjs`, never a single URL. Prefer it to a
25
+ * code revert, but budget for a deploy: there is no faster kill path.
26
+ *
27
+ * 2. CACHEABILITY — the response varies by the `jd_theme` cookie while
28
+ * carrying NO `Vary: Cookie`, and the two hosts that serve the picker do
29
+ * NOT share a cache posture:
30
+ *
31
+ * - `jamdesk.com/docs` (apex, through the worker) is uncacheable end to
32
+ * end — `private, no-cache, no-store` upstream, `cf-cache-status:
33
+ * BYPASS` at the edge. Safe.
34
+ * - `jamdesk-docs.jamdesk.app` (the ISR subdomain) serves `public,
35
+ * max-age=0, must-revalidate` with NO `Vary`. The picker is live there
36
+ * too: the host resolves to slug `jamdesk-docs` and the cookie is
37
+ * written at `Path=/`. A shared cache on that host may serve one
38
+ * visitor's previewed theme to another.
39
+ *
40
+ * That second exposure is KNOWN and was accepted as cosmetic residual risk
41
+ * — the payload is a theme on Jamdesk's own docs, the HTML carries nothing
42
+ * visitor-specific, and the subdomain is not the surface prospects are sent
43
+ * to. Do not add `Vary: Cookie` without revisiting that call. DO revisit it
44
+ * if anything raises the stakes: a Cloudflare Cache Rule or `s-maxage` that
45
+ * makes the apex shared-cacheable, extending the preview past jamdesk-docs,
46
+ * or putting anything per-visitor in the response. A server component
47
+ * cannot set response headers, so this docblock IS the mitigation — the
48
+ * only thing that can go wrong with it is going stale.
49
+ */
50
+ import { getAllThemes, type ThemeName } from '@/themes';
51
+
52
+ /** Cookie the picker writes; read server-side in app/layout.tsx. */
53
+ export const THEME_PREVIEW_COOKIE = 'jd_theme';
54
+
55
+ /** The only project permitted to preview themes. */
56
+ export const THEME_PREVIEW_SLUG = 'jamdesk-docs';
57
+
58
+ // No lifetime constant on purpose: the picker writes a SESSION cookie (no
59
+ // Max-Age), so a preview dies with the browser. The picker is desktop-only, so
60
+ // a persisted preview would follow a visitor to mobile with no way to reset
61
+ // it. The clear path still sends `Max-Age=0` — see ThemePreviewPicker's
62
+ // applyTheme.
63
+
64
+ // Built from the registry rather than using `isValidTheme`, which is a plain
65
+ // `name in themes` check and therefore also returns true for inherited Object
66
+ // keys ('constructor', 'toString'). Those would reach getTheme(), which falls
67
+ // back to jam silently for unknown names but returns Object's own constructor
68
+ // for 'constructor' — yielding data-theme="constructor" and no theme CSS.
69
+ // A Set of the registry's own names is immune and picks up new themes for free.
70
+ const VALID_THEME_NAMES: ReadonlySet<string> = new Set(
71
+ getAllThemes().map((theme) => theme.name),
72
+ );
73
+
74
+ /**
75
+ * Values of `THEME_PREVIEW_ENABLED` that turn the picker OFF. Anything else —
76
+ * including unset and empty — leaves it ON (see invariant 1 above). The spread
77
+ * matters: an operator reaching for a kill switch mid-incident types whichever
78
+ * of these comes to hand, and matching only `'false'` would let a successful
79
+ * deploy leave the feature live with no error anywhere.
80
+ */
81
+ const DISABLING_VALUES: ReadonlySet<string> = new Set(['false', '0', 'off', 'no']);
82
+
83
+ /**
84
+ * Whether the env kill switch leaves the picker enabled.
85
+ *
86
+ * `.trim()` is load-bearing, not defensive noise: a `vercel env add` value that
87
+ * picks up a trailing newline is what silently 404'd every ISR subdomain via
88
+ * `ISR_MODE` (root CLAUDE.md), and the failure here is quieter still — an
89
+ * untrimmed `"false\n"` leaves the kill switch ON while the Vercel dashboard
90
+ * shows it set to false.
91
+ */
92
+ export function isThemePreviewEnvEnabled(raw: string | undefined): boolean {
93
+ return !DISABLING_VALUES.has(raw?.trim().toLowerCase() ?? '');
94
+ }
95
+
96
+ /** The registry theme this string names, or null. The single validation point. */
97
+ function parseThemeName(value: string | undefined | null): ThemeName | null {
98
+ const name = (value ?? '').trim().toLowerCase();
99
+ return VALID_THEME_NAMES.has(name) ? (name as ThemeName) : null;
100
+ }
101
+
102
+ /**
103
+ * A registry theme name, or `jam` for anything else. `config.theme` arrives
104
+ * from docs.json as an unvalidated string, so casting it straight to ThemeName
105
+ * would hand the picker a value that labels no row and matches no "Default"
106
+ * badge.
107
+ */
108
+ export function toThemeName(value: string | undefined | null): ThemeName {
109
+ return parseThemeName(value) ?? 'jam';
110
+ }
111
+
112
+ interface ThemePreviewGate {
113
+ /** Resolved project slug from the x-project-slug header. */
114
+ projectSlug: string | null;
115
+ /** Whether the app is running in multi-tenant ISR mode. */
116
+ isIsr: boolean;
117
+ /** Kill switch, resolved from env by the caller so this stays pure. */
118
+ enabled: boolean;
119
+ }
120
+
121
+ export interface ThemePreviewInput extends ThemePreviewGate {
122
+ /** Raw cookie value, if present. */
123
+ cookieValue: string | undefined;
124
+ }
125
+
126
+ /**
127
+ * The theme a visitor has selected, or null to use the project's own theme.
128
+ * Every condition is re-checked here even though the caller also checks some
129
+ * of them — this function is the single place a preview can be granted.
130
+ */
131
+ export function resolveThemePreview({
132
+ cookieValue,
133
+ projectSlug,
134
+ isIsr,
135
+ enabled,
136
+ }: ThemePreviewInput): ThemeName | null {
137
+ if (!isThemePreviewEnabled({ projectSlug, isIsr, enabled })) return null;
138
+ if (!cookieValue) return null;
139
+
140
+ return parseThemeName(cookieValue);
141
+ }
142
+
143
+ /** Whether the picker UI may render at all. */
144
+ export function isThemePreviewEnabled({
145
+ projectSlug,
146
+ isIsr,
147
+ enabled,
148
+ }: ThemePreviewGate): boolean {
149
+ return enabled && isIsr && projectSlug === THEME_PREVIEW_SLUG;
150
+ }
@@ -0,0 +1,10 @@
1
+ // Extracted from app/api/jd/unlock/route.ts: Next.js's route typegen requires
2
+ // that a route.ts export ONLY the known route-convention symbols (GET, POST,
3
+ // runtime, dynamic, ...) — any other export fails `tsc`'s check of
4
+ // .next/types/app/**/route.ts with TS2344 ("does not satisfy the constraint
5
+ // '{ [x: string]: never }'"). This constant is used by the route handler but
6
+ // also needs to be assertable-against in tests, so it lives here instead.
7
+
8
+ // Audit log tag emitted on every successful master-password unlock. Exported
9
+ // so tests can assert exact format without duplicating the literal.
10
+ export const MASTER_AUDIT_TAG = '[unlock] master-password used';
@@ -70,6 +70,25 @@ const nextConfig = {
70
70
  '../build/polyfills/polyfill-module': './lib/empty-polyfill.js',
71
71
  'next/dist/build/polyfills/polyfill-module': './lib/empty-polyfill.js',
72
72
  },
73
+ // Mirrors build-service/next.config.mjs. Turbopack has no equivalent of
74
+ // webpack's resolve.extensionAlias (https://github.com/vercel/next.js/issues/82945),
75
+ // so a relative `./foo.js` import whose real source is `./foo.ts` can fail
76
+ // to resolve. That is the convention every file in vendored/ uses, 120
77
+ // relative `.js` specifiers of it, including the lib/jwt-session.ts ->
78
+ // lib/public-paths-resolver.ts chain that made build-service fail
79
+ // deterministically. The workspace compiles the same tree, so it needs
80
+ // the same rule. Exempt from this file's keep-it-minimal rule: the
81
+ // 80s-compile warning above is about turbopack.root and resolution
82
+ // scope, and this is one regex pass over source text.
83
+ //
84
+ // vendored/ is copied wholesale into the workspace, so vendored/scripts
85
+ // lands at <workspace>/scripts and this relative path resolves. The
86
+ // loader is on the safeScripts allowlist in cli/scripts/vendor.js;
87
+ // turbopack-loader-config-drift.test.ts pins all of it together.
88
+ rules: {
89
+ '*.ts': { loaders: ['./scripts/turbopack-js-to-ts-loader.cjs'] },
90
+ '*.tsx': { loaders: ['./scripts/turbopack-js-to-ts-loader.cjs'] },
91
+ },
73
92
  },
74
93
  // Critical for fast builds
75
94
  experimental: {
@@ -87,6 +106,19 @@ const nextConfig = {
87
106
  '@radix-ui/react-tooltip',
88
107
  ],
89
108
  },
109
+ // `jamdesk dev --webpack` is an advertised fallback (src/index.ts), and
110
+ // webpack needs the same `.js`-means-`.ts` mapping the Turbopack rule above
111
+ // provides. Without it the escape hatch fails on the imports the escape
112
+ // hatch exists to work around.
113
+ webpack: (config) => {
114
+ config.resolve.extensionAlias = {
115
+ ...config.resolve.extensionAlias,
116
+ '.js': ['.ts', '.tsx', '.js'],
117
+ '.mjs': ['.mts', '.mjs'],
118
+ '.cjs': ['.cts', '.cjs'],
119
+ }
120
+ return config
121
+ },
90
122
  }
91
123
 
92
124
  export default nextConfig
@@ -1567,6 +1567,18 @@
1567
1567
  "additionalProperties": false,
1568
1568
  "description": "Configure user interaction behavior with navigation and links"
1569
1569
  },
1570
+ "localization": {
1571
+ "type": "object",
1572
+ "properties": {
1573
+ "autoRedirect": {
1574
+ "type": "boolean",
1575
+ "default": false,
1576
+ "description": "Redirect a first-time visitor from a locale root to the language matching their Accept-Language header. Requires navigation.languages. Bots are never redirected. Defaults to `false`"
1577
+ }
1578
+ },
1579
+ "additionalProperties": false,
1580
+ "description": "Control how visitors reach the translated version of your docs"
1581
+ },
1570
1582
  "metadata": {
1571
1583
  "type": "object",
1572
1584
  "properties": {
@@ -1833,6 +1845,9 @@
1833
1845
  "metadata": {
1834
1846
  "$ref": "#/anyOf/0/properties/metadata"
1835
1847
  },
1848
+ "localization": {
1849
+ "$ref": "#/anyOf/0/properties/localization"
1850
+ },
1836
1851
  "chat": {
1837
1852
  "$ref": "#/anyOf/0/properties/chat"
1838
1853
  },
@@ -1954,6 +1969,9 @@
1954
1969
  "metadata": {
1955
1970
  "$ref": "#/anyOf/0/properties/metadata"
1956
1971
  },
1972
+ "localization": {
1973
+ "$ref": "#/anyOf/0/properties/localization"
1974
+ },
1957
1975
  "chat": {
1958
1976
  "$ref": "#/anyOf/0/properties/chat"
1959
1977
  },
@@ -2075,6 +2093,9 @@
2075
2093
  "metadata": {
2076
2094
  "$ref": "#/anyOf/0/properties/metadata"
2077
2095
  },
2096
+ "localization": {
2097
+ "$ref": "#/anyOf/0/properties/localization"
2098
+ },
2078
2099
  "chat": {
2079
2100
  "$ref": "#/anyOf/0/properties/chat"
2080
2101
  },
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Turbopack build-time source transform: strips the `.js` suffix off
3
+ * RELATIVE import/export/dynamic-import specifiers before Turbopack's own
4
+ * resolver sees them.
5
+ *
6
+ * Why this exists: this project's tsconfig.json sets
7
+ * `moduleResolution: "bundler"`, the TypeScript convention where source
8
+ * files are `.ts` but every relative import of them is written with a
9
+ * `.js` suffix (e.g. `import { x } from './foo.js'` for a file that is
10
+ * actually `./foo.ts` on disk). webpack supports this natively via
11
+ * `resolve.extensionAlias` (wired up in next.config.mjs's `webpack()`
12
+ * function). Turbopack has NO equivalent, documented, built-in support for
13
+ * this as of Next.js 16.3.x — see https://github.com/vercel/next.js/issues/82945
14
+ * ("Turbopack: support importing .ts/.tsx via .js extension"), which is
15
+ * open/unfixed upstream. In practice Turbopack resolves the VAST MAJORITY
16
+ * of this project's `.js`-suffixed relative imports fine through some
17
+ * internal inference, but was observed to throw a hard "Module not found"
18
+ * for specific files/subtrees (e.g. the whole lib/jwt-session.ts ->
19
+ * lib/public-paths-resolver.ts -> lib/{docs-types,glob-match,
20
+ * find-first-nav-page,language-utils}.ts chain added by the jwt docs-auth
21
+ * feature) — confirmed via a standalone unrs-resolver probe, and via
22
+ * removing the `.js` suffix by hand (which fixes resolution one hop at a
23
+ * time, confirming the failure is the extension-alias gap, not a real
24
+ * missing file). Rather than rewrite this project's whole import style
25
+ * (pervasive, deliberate convention — see tsconfig.json's
26
+ * `moduleResolution: "bundler"` and CLAUDE.md), this loader rewrites the
27
+ * `.js` -> (nothing) at Turbopack-compile time only. TypeScript itself,
28
+ * `tsc --noEmit`, vitest, and webpack builds are all UNAFFECTED (this file
29
+ * is wired into next.config.mjs's `turbopack.rules` only).
30
+ *
31
+ * Scoped to RELATIVE specifiers only (`./x.js`, `../x.js`) — never bare
32
+ * package specifiers — so a legitimate deep import of an npm package's own
33
+ * `.js` file (e.g. a subpath export) is never touched.
34
+ *
35
+ * Community-sourced workaround for the same open issue, adapted to be
36
+ * relative-imports-only (the referenced version was unscoped):
37
+ * https://github.com/vercel/next.js/issues/82945#issuecomment-3269958583
38
+ */
39
+
40
+ // Matches: (import|export) ... from '<spec>'; and dynamic import('<spec>')
41
+ // where <spec> starts with ./ or ../ and ends in .js/.jsx/.mjs, capturing
42
+ // the quote character so it round-trips exactly.
43
+ const RELATIVE_JS_SPECIFIER_RE =
44
+ /((?:from\s+|import\()\s*)(["'])(\.\.?\/[^"']+?)\.(m?js|jsx)\2/g;
45
+
46
+ module.exports = function stripRelativeJsExtensions(source) {
47
+ return source.replace(
48
+ RELATIVE_JS_SPECIFIER_RE,
49
+ (_match, prefix, quote, specPath) => `${prefix}${quote}${specPath}${quote}`,
50
+ );
51
+ };