jamdesk 1.1.132 → 1.1.133

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.
@@ -16,39 +16,26 @@ import path from 'path';
16
16
  import { fileURLToPath } from 'url';
17
17
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
18
  /**
19
- * Files that MUST stay in sync between build-service and cli/vendored.
20
- * Add new files here when they're vendored and need sync verification.
19
+ * NON-lib vendored files that must stay byte-identical between build-service and
20
+ * cli/vendored. Everything under `cli/vendored/lib/` is covered comprehensively by
21
+ * the "vendored/lib comprehensive sync" suite below, so do NOT list lib/ files
22
+ * here — add them to that suite's exception set only if vendor.js transforms them.
21
23
  *
22
- * NOTE: Only include files that should be IDENTICAL. Some vendored files
23
- * (like MDXComponents.tsx) are intentionally different (stripped-down versions)
24
- * and should NOT be in this list.
24
+ * Add a non-lib file here when it's vendored and must match its source. Only list
25
+ * files that should be IDENTICAL: some vendored files (e.g. the stripped-down
26
+ * MDXComponents.tsx, the PlaygroundModal-stripped OpenApiEndpoint.tsx) are
27
+ * intentionally transformed by vendor.js and must NOT be listed.
25
28
  *
26
29
  * Format: [build-service path, cli/vendored path]
27
30
  */
28
31
  const SYNCED_FILES = [
29
- // Shiki configuration (affects syntax highlighting and language detection)
30
- // This file MUST be identical - it controls which languages are highlighted
31
- // and which are passed through to special components like Mermaid
32
- ['build-service/lib/shiki-config.ts', 'cli/vendored/lib/shiki-config.ts'],
33
- ['build-service/lib/shiki-client.ts', 'cli/vendored/lib/shiki-client.ts'],
34
- ['build-service/lib/cache-tags.ts', 'cli/vendored/lib/cache-tags.ts'],
35
- ['build-service/lib/revalidation-helpers.ts', 'cli/vendored/lib/revalidation-helpers.ts'],
36
- ['build-service/lib/revalidation-trigger.ts', 'cli/vendored/lib/revalidation-trigger.ts'],
37
- ['build-service/lib/fs-readfile.ts', 'cli/vendored/lib/fs-readfile.ts'],
38
- ['build-service/lib/r2-manifest.ts', 'cli/vendored/lib/r2-manifest.ts'],
39
- ['build-service/lib/rehype-unwrap-nested-anchors.ts', 'cli/vendored/lib/rehype-unwrap-nested-anchors.ts'],
40
- // Mintlify migration detection — error-parser reads MIGRATION_DOCS_URL from
41
- // validate-config, so the two files must move together. Drift here means
42
- // CLI surfaces would miss the migrate hint while production catches it.
43
- ['build-service/lib/validate-config.ts', 'cli/vendored/lib/validate-config.ts'],
44
- ['build-service/lib/build/error-parser.ts', 'cli/vendored/lib/build/error-parser.ts'],
45
32
  // Theme rendering — `themes/index.ts` lowercases in `getTheme()`, `Header.tsx`
46
- // reads `themeConfig.name` (canonical case), `layout-helpers.tsx` lowercases
47
- // `data-theme` so `body[data-theme="nebula"]` CSS selectors match for any-case
48
- // theme strings. All three must move together with the canonical.
33
+ // reads `themeConfig.name` (canonical case), and `lib/layout-helpers.tsx`
34
+ // (covered by the comprehensive lib suite) lowercases `data-theme` so
35
+ // `body[data-theme="nebula"]` selectors match for any-case theme strings all
36
+ // three must move together with the canonical.
49
37
  ['build-service/themes/index.ts', 'cli/vendored/themes/index.ts'],
50
38
  ['build-service/components/navigation/Header.tsx', 'cli/vendored/components/navigation/Header.tsx'],
51
- ['build-service/lib/layout-helpers.tsx', 'cli/vendored/lib/layout-helpers.tsx'],
52
39
  ];
53
40
  describe('CLI vendored files sync', () => {
54
41
  it.each(SYNCED_FILES)('should have matching content: %s', async (buildServicePath, vendoredPath) => {
@@ -80,6 +67,71 @@ describe('CLI vendored files sync', () => {
80
67
  }
81
68
  });
82
69
  });
70
+ /**
71
+ * `cli/vendored/lib/` is a near-verbatim byte-copy of `build-service/lib/`
72
+ * produced by scripts/vendor.js. A hand-maintained allowlist (the old approach)
73
+ * silently MISSED any file it didn't enumerate — e.g. `isr-build-executor.ts`
74
+ * could drift between source and vendored with zero CI failure, shipping a stale
75
+ * copy to `jamdesk dev`. This suite inverts the guarantee: it walks EVERY vendored
76
+ * lib file and requires a byte-match, so new vendored files are covered
77
+ * automatically and the only sanctioned way to differ is to appear on the small,
78
+ * explicit exception set below.
79
+ *
80
+ * EXCEPTIONS = the files vendor.js intentionally rewrites instead of byte-copying.
81
+ * An empirical `cmp` of the whole tree must yield EXACTLY these (169 files / 2
82
+ * exceptions at time of writing). The stale-exception guard below fails if one of
83
+ * these stops differing; the count guard fails if the walk resolves nothing.
84
+ */
85
+ const VENDORED_LIB_INTENTIONAL_DIFFERENCES = new Set([
86
+ 'r2-content.ts', // vendor.js writes an AWS-SDK-free stub (the real one needs the S3 client)
87
+ 'openapi/index.ts', // vendor.js strips the playground-codegen re-export (codegen isn't vendored)
88
+ ]);
89
+ function walkTsFiles(dir, base = dir) {
90
+ const out = [];
91
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
92
+ const full = path.join(dir, entry.name);
93
+ if (entry.isDirectory())
94
+ out.push(...walkTsFiles(full, base));
95
+ else if (/\.tsx?$/.test(entry.name))
96
+ out.push(path.relative(base, full).replace(/\\/g, '/'));
97
+ }
98
+ return out;
99
+ }
100
+ const BUILDER_ROOT = path.join(__dirname, '../../../..');
101
+ const VENDORED_LIB_FILES = walkTsFiles(path.join(BUILDER_ROOT, 'cli/vendored/lib')).filter((rel) => !VENDORED_LIB_INTENTIONAL_DIFFERENCES.has(rel));
102
+ describe('CLI vendored/lib comprehensive sync (no silent drift)', () => {
103
+ it('discovers the full vendored lib tree (guards against an empty/0-file walk)', () => {
104
+ // ~169 files at time of writing; >100 proves the walk resolved the real tree
105
+ // and the it.each below is not silently testing nothing (e.g. wrong root path).
106
+ expect(VENDORED_LIB_FILES.length).toBeGreaterThan(100);
107
+ });
108
+ it.each(VENDORED_LIB_FILES)('cli/vendored/lib/%s is byte-identical to its build-service/lib source', (rel) => {
109
+ const vendored = path.join(BUILDER_ROOT, 'cli/vendored/lib', rel);
110
+ const source = path.join(BUILDER_ROOT, 'build-service/lib', rel);
111
+ if (!fs.existsSync(source)) {
112
+ throw new Error(`cli/vendored/lib/${rel} has no build-service/lib counterpart.\n` +
113
+ `If vendor.js synthesizes it, add '${rel}' to VENDORED_LIB_INTENTIONAL_DIFFERENCES.`);
114
+ }
115
+ const vendoredContent = fs.readFileSync(vendored, 'utf-8');
116
+ const sourceContent = fs.readFileSync(source, 'utf-8');
117
+ if (vendoredContent !== sourceContent) {
118
+ throw new Error(`Vendored drift: cli/vendored/lib/${rel} differs from build-service/lib/${rel}.\n\n` +
119
+ `Run "npm run vendor" to resync.\n` +
120
+ `If vendor.js intentionally transforms this file, add '${rel}' to ` +
121
+ `VENDORED_LIB_INTENTIONAL_DIFFERENCES (and document the transform).`);
122
+ }
123
+ });
124
+ // Keep the exception list honest: if a listed file stops differing (the
125
+ // transform was removed), it must be dropped from the set so real drift on that
126
+ // file is caught again.
127
+ it.each([...VENDORED_LIB_INTENTIONAL_DIFFERENCES])('intentional-difference %s exists in both trees and actually differs', (rel) => {
128
+ const vendored = path.join(BUILDER_ROOT, 'cli/vendored/lib', rel);
129
+ const source = path.join(BUILDER_ROOT, 'build-service/lib', rel);
130
+ expect(fs.existsSync(vendored), `${rel} is an intentional-difference but missing from vendored`).toBe(true);
131
+ expect(fs.existsSync(source), `${rel} is an intentional-difference but missing from build-service`).toBe(true);
132
+ expect(fs.readFileSync(vendored, 'utf-8') === fs.readFileSync(source, 'utf-8'), `${rel} is byte-identical — remove it from VENDORED_LIB_INTENTIONAL_DIFFERENCES`).toBe(false);
133
+ });
134
+ });
83
135
  describe('CLI package.json files entries', () => {
84
136
  it('should have valid paths for all vendored/public entries', async () => {
85
137
  const cliRoot = path.join(__dirname, '../../..');
@@ -1 +1 @@
1
- {"version":3,"file":"vendored-sync.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/vendored-sync.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vendored-sync.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/vendored-sync.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jamdesk",
3
- "version": "1.1.132",
3
+ "version": "1.1.133",
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",
@@ -42,6 +42,7 @@ import { Columns } from './Columns';
42
42
  import { View, ViewProvider, ViewSelector, ViewWrapper } from './View';
43
43
  import { YouTube } from './YouTube';
44
44
  import { Video } from './Video';
45
+ import { Widget } from './Widget';
45
46
  import { Visibility } from './Visibility';
46
47
  import { MdxErrorBlock } from './MdxErrorBlock';
47
48
 
@@ -248,6 +249,8 @@ export const MDXComponents = {
248
249
  YouTube,
249
250
  // Video player for local video files
250
251
  Video,
252
+ // Live "What's New" widget demo (renders a trigger that opens the hosted modal)
253
+ Widget,
251
254
  // Audience-conditional content. Filtered at MDX-compile time by
252
255
  // lib/remark-visibility.ts for the HTML render path and by
253
256
  // lib/visibility-filter.ts for text surfaces (.md export, llms-full.txt,
@@ -0,0 +1,133 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useId, type ReactElement } from 'react';
4
+ import { useProjectSlug } from '../../lib/project-slug-context';
5
+ import { useLinkPrefix } from '../../lib/link-prefix-context';
6
+
7
+ export interface WidgetProps {
8
+ /** Docs path the modal opens. Default "/changelog". */
9
+ page?: string;
10
+ /** Force the modal color scheme. */
11
+ theme?: 'auto' | 'light' | 'dark';
12
+ /** Trigger button text (also the modal aria-label). Default "What's new". */
13
+ label?: string;
14
+ /** Bind to your own element (CSS selector) instead of rendering a button. */
15
+ trigger?: string;
16
+ /** Modal width / height / corner radius (any CSS length). */
17
+ width?: string;
18
+ height?: string;
19
+ radius?: string;
20
+ /** Set false to hide the unread dot. */
21
+ unread?: boolean;
22
+ /** Unread dot color (hex or CSS color name). */
23
+ unreadColor?: string;
24
+ /** Override the localStorage "seen" key (rarely needed). */
25
+ project?: string;
26
+ /** Extra class names for the rendered button. */
27
+ className?: string;
28
+ }
29
+
30
+ // `page` flows into a cross-origin iframe `src` (<base><page>?embed=1). In a
31
+ // PUBLIC component an attacker-authored page value must not be coercible into an
32
+ // absolute/protocol-relative URL that frames another origin. Accept only a clean
33
+ // same-site relative path; anything else falls back to the safe default.
34
+ // Trim first so an authoring typo (a trailing space → "/changelog ?embed=1",
35
+ // a malformed URL that silently 404s the modal) can't slip through, and a
36
+ // leading-space path still resolves instead of falling back.
37
+ // Exported for direct unit testing.
38
+ export function safePage(page: string): string {
39
+ const trimmed = page.trim();
40
+ if (!trimmed.startsWith('/') || trimmed.startsWith('//') || trimmed.includes('://') || trimmed.includes('\\')) {
41
+ return '/changelog';
42
+ }
43
+ return trimmed;
44
+ }
45
+
46
+ export function Widget({
47
+ page = '/changelog',
48
+ theme,
49
+ label = "What's new",
50
+ trigger,
51
+ width,
52
+ height,
53
+ radius,
54
+ unread,
55
+ unreadColor,
56
+ project,
57
+ className,
58
+ }: WidgetProps): ReactElement | null {
59
+ const slug = useProjectSlug();
60
+ const prefix = useLinkPrefix(); // '/docs' when hostAtDocs, else ''
61
+
62
+ // useId() returns ":r0:"-style ids — invalid in a CSS selector and in
63
+ // querySelector('#...'), which is exactly what the widget runs on
64
+ // data-trigger. Strip the colons.
65
+ const buttonId = `jd-w-${useId().replace(/:/g, '')}`;
66
+
67
+ // Canonical ISR origin that renders ?embed=1. Custom domains can't, so always
68
+ // target <slug>.jamdesk.app (+ /docs when hostAtDocs) — never window.origin,
69
+ // and NO caller override: a `base` prop would let MDX frame an arbitrary
70
+ // origin in the chromeless overlay (clickjacking). Empty slug → no-op.
71
+ const resolvedBase = slug ? `https://${slug}.jamdesk.app${prefix}` : '';
72
+
73
+ useEffect(() => {
74
+ if (!resolvedBase) return;
75
+ const triggerSel = trigger ?? `#${buttonId}`;
76
+
77
+ // Idempotent guard. React Strict Mode double-invokes effects (mount →
78
+ // cleanup → mount) in dev, and soft-nav can re-render — never stack two
79
+ // managed scripts for the same trigger (two scripts → two click listeners →
80
+ // the modal opens twice). Key on the trigger selector.
81
+ const sel = `script[data-jd-widget-managed][data-widget-key="${triggerSel.replace(/"/g, '\\"')}"]`;
82
+ if (document.querySelector(sel)) return;
83
+
84
+ const s = document.createElement('script');
85
+ // resolvedBase is always https://<slug>.jamdesk.app[/docs] — well-formed, no
86
+ // try/catch needed. widget.js lives at the subdomain ROOT, never under /docs.
87
+ s.src = `${new URL(resolvedBase).origin}/_jd/widget.js`;
88
+ s.async = true;
89
+ s.dataset.base = resolvedBase;
90
+ s.dataset.page = safePage(page); // reject absolute/protocol-relative paths
91
+ s.dataset.trigger = triggerSel;
92
+ if (theme) s.dataset.theme = theme;
93
+ if (width) s.dataset.width = width;
94
+ if (height) s.dataset.height = height;
95
+ if (radius) s.dataset.radius = radius;
96
+ if (unread === false) s.dataset.unread = 'off';
97
+ if (unreadColor) s.dataset.unreadColor = unreadColor;
98
+ if (project) s.dataset.project = project;
99
+ s.setAttribute('data-jd-widget-managed', '');
100
+ s.setAttribute('data-widget-key', triggerSel); // idempotency key (above)
101
+ document.body.appendChild(s);
102
+
103
+ return () => {
104
+ // Soft-nav cleanup: remove the script. The widget's click listener lives
105
+ // on the button (unmounted with this component) — we always bind a
106
+ // trigger, so no floating launcher is left orphaned. (If the modal is open
107
+ // at unmount the widget's own ESC/close path still tears the dialog down.)
108
+ s.remove();
109
+ };
110
+ }, [
111
+ resolvedBase, page, trigger, theme, width, height, radius,
112
+ unread, unreadColor, project, buttonId,
113
+ ]);
114
+
115
+ if (trigger) return null;
116
+
117
+ return (
118
+ <button
119
+ type="button"
120
+ id={buttonId}
121
+ className={
122
+ 'jd-widget-trigger my-4 inline-flex cursor-pointer items-center gap-2 rounded-lg ' +
123
+ 'border border-gray-200 bg-white px-4 py-2 text-sm font-medium ' +
124
+ 'text-gray-900 shadow-sm transition hover:shadow-md ' +
125
+ 'dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100' +
126
+ (className ? ` ${className}` : '')
127
+ }
128
+ >
129
+ <span aria-hidden="true">✦</span>
130
+ {label}
131
+ </button>
132
+ );
133
+ }
@@ -22,6 +22,13 @@ import {
22
22
  } from './r2-content';
23
23
  import { isIsrMode } from './page-isr-helpers';
24
24
 
25
+ /**
26
+ * Thrown by getDocsConfig when a project is absent in R2 and has no dev-fallback.
27
+ * Shared so projectExists can distinguish "genuinely missing" from a transient
28
+ * R2 error without coupling to a duplicated literal.
29
+ */
30
+ const PROJECT_NOT_FOUND_PREFIX = 'Project not found in R2';
31
+
25
32
  /**
26
33
  * Ensure ISR mode is enabled, throw otherwise.
27
34
  */
@@ -99,7 +106,7 @@ async function getDocsConfigUncached(projectSlug: string): Promise<DocsConfig> {
99
106
  // fall through to throw below
100
107
  }
101
108
  }
102
- throw new Error(`Project not found in R2: ${projectSlug}`);
109
+ throw new Error(`${PROJECT_NOT_FOUND_PREFIX}: ${projectSlug}`);
103
110
  }
104
111
 
105
112
  /**
@@ -192,9 +199,26 @@ export async function getSnippet(
192
199
  */
193
200
  export async function projectExists(projectSlug: string): Promise<boolean> {
194
201
  requireIsrMode();
195
- const config = await fetchDocsConfig(projectSlug);
196
- if (config !== null) return true;
197
- return devFallbackDir(projectSlug) !== null;
202
+ // Route through getDocsConfig (the React cache()-wrapped reader) instead of a
203
+ // raw fetchDocsConfig so this existence check dedupes with the config read the
204
+ // page render performs in the same request — render-doc-page.tsx calls this
205
+ // immediately before loader.getConfig() in BOTH the metadata and render passes,
206
+ // and the raw call was paying its own R2 round-trip each time.
207
+ //
208
+ // getDocsConfig folds in the same dev-fallback this used to do explicitly and
209
+ // throws PROJECT_NOT_FOUND_PREFIX only when the project is absent in R2 *and*
210
+ // dev-fallback. Map that one case back to the original devFallbackDir check
211
+ // (preserving the dir-exists-but-unreadable-docs.json dev edge) and re-throw
212
+ // genuine (transient) R2 errors, so true/false/throw outcomes are unchanged.
213
+ try {
214
+ await getDocsConfig(projectSlug);
215
+ return true;
216
+ } catch (err) {
217
+ if (err instanceof Error && err.message.startsWith(PROJECT_NOT_FOUND_PREFIX)) {
218
+ return devFallbackDir(projectSlug) !== null;
219
+ }
220
+ throw err;
221
+ }
198
222
  }
199
223
 
200
224
  /**
@@ -184,9 +184,9 @@
184
184
  "license": "Apache-2.0"
185
185
  },
186
186
  "node_modules/@emnapi/runtime": {
187
- "version": "1.10.0",
188
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
189
- "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
187
+ "version": "1.11.0",
188
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz",
189
+ "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==",
190
190
  "license": "MIT",
191
191
  "optional": true,
192
192
  "dependencies": {
@@ -1580,15 +1580,15 @@
1580
1580
  }
1581
1581
  },
1582
1582
  "node_modules/@tailwindcss/typography": {
1583
- "version": "0.5.19",
1584
- "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
1585
- "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
1583
+ "version": "0.5.20",
1584
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.20.tgz",
1585
+ "integrity": "sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==",
1586
1586
  "license": "MIT",
1587
1587
  "dependencies": {
1588
1588
  "postcss-selector-parser": "6.0.10"
1589
1589
  },
1590
1590
  "peerDependencies": {
1591
- "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
1591
+ "tailwindcss": ">=3.0.0 || >=4.0.0 || insiders"
1592
1592
  }
1593
1593
  },
1594
1594
  "node_modules/@terrastruct/d2": {
@@ -1911,9 +1911,9 @@
1911
1911
  }
1912
1912
  },
1913
1913
  "node_modules/@types/mdx": {
1914
- "version": "2.0.13",
1915
- "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz",
1916
- "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==",
1914
+ "version": "2.0.14",
1915
+ "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz",
1916
+ "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==",
1917
1917
  "license": "MIT"
1918
1918
  },
1919
1919
  "node_modules/@types/ms": {
@@ -1932,18 +1932,18 @@
1932
1932
  }
1933
1933
  },
1934
1934
  "node_modules/@types/node": {
1935
- "version": "25.9.1",
1936
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
1937
- "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
1935
+ "version": "25.9.2",
1936
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz",
1937
+ "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==",
1938
1938
  "license": "MIT",
1939
1939
  "dependencies": {
1940
1940
  "undici-types": ">=7.24.0 <7.24.7"
1941
1941
  }
1942
1942
  },
1943
1943
  "node_modules/@types/react": {
1944
- "version": "19.2.16",
1945
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz",
1946
- "integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==",
1944
+ "version": "19.2.17",
1945
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
1946
+ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
1947
1947
  "license": "MIT",
1948
1948
  "dependencies": {
1949
1949
  "csstype": "^3.2.2"
@@ -2145,9 +2145,9 @@
2145
2145
  }
2146
2146
  },
2147
2147
  "node_modules/baseline-browser-mapping": {
2148
- "version": "2.10.33",
2149
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz",
2150
- "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==",
2148
+ "version": "2.10.34",
2149
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz",
2150
+ "integrity": "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==",
2151
2151
  "license": "Apache-2.0",
2152
2152
  "bin": {
2153
2153
  "baseline-browser-mapping": "dist/cli.cjs"
@@ -2208,9 +2208,9 @@
2208
2208
  "license": "MIT"
2209
2209
  },
2210
2210
  "node_modules/caniuse-lite": {
2211
- "version": "1.0.30001793",
2212
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
2213
- "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
2211
+ "version": "1.0.30001797",
2212
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz",
2213
+ "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==",
2214
2214
  "funding": [
2215
2215
  {
2216
2216
  "type": "opencollective",
@@ -2939,15 +2939,15 @@
2939
2939
  "license": "MIT"
2940
2940
  },
2941
2941
  "node_modules/electron-to-chromium": {
2942
- "version": "1.5.367",
2943
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.367.tgz",
2944
- "integrity": "sha512-4Mk/mrynCNQ+atY40D3UpmhLWB6AHMbYMlIrPhHcMF6x0L7O0b052FCAsxw1LlaR++UFuNg3D/A6XCuGDa0guQ==",
2942
+ "version": "1.5.368",
2943
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz",
2944
+ "integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==",
2945
2945
  "license": "ISC"
2946
2946
  },
2947
2947
  "node_modules/enhanced-resolve": {
2948
- "version": "5.22.2",
2949
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.2.tgz",
2950
- "integrity": "sha512-0rxICaFZ7NQho/sHely2bvOPRP0Eu2B0NZ9zM54YvRvWMn7jfz3DmnOZDR9LlXDdDcqntAVc6Hfy4gr/tdH/Ag==",
2948
+ "version": "5.23.0",
2949
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.23.0.tgz",
2950
+ "integrity": "sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==",
2951
2951
  "license": "MIT",
2952
2952
  "dependencies": {
2953
2953
  "graceful-fs": "^4.2.4",
@@ -3238,9 +3238,9 @@
3238
3238
  }
3239
3239
  },
3240
3240
  "node_modules/fuse.js": {
3241
- "version": "7.4.1",
3242
- "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.4.1.tgz",
3243
- "integrity": "sha512-AY7lKAXK71hi3WgUvDy6oZL67UEHOOtvCAwVdOXHyJd6ZzftBy7QqxuXt4HxmmAhYjmp/YCuOELZtIvAdlZ+fw==",
3241
+ "version": "7.4.2",
3242
+ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.4.2.tgz",
3243
+ "integrity": "sha512-LVbzjD4WA6UP5B1UnP8wuaXJiLnqMdM/E4fiJXTJ5haJ5b/MBNsK29h2fm6swEoQaVQjvYFWKLE2RanyZIoRVQ==",
3244
3244
  "license": "Apache-2.0",
3245
3245
  "engines": {
3246
3246
  "node": ">=10"
@@ -6079,9 +6079,9 @@
6079
6079
  }
6080
6080
  },
6081
6081
  "node_modules/semver": {
6082
- "version": "7.8.2",
6083
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz",
6084
- "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==",
6082
+ "version": "7.8.3",
6083
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.3.tgz",
6084
+ "integrity": "sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==",
6085
6085
  "license": "ISC",
6086
6086
  "optional": true,
6087
6087
  "bin": {
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=dev-workspace-symlinks.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dev-workspace-symlinks.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/dev-workspace-symlinks.test.ts"],"names":[],"mappings":""}
@@ -1,112 +0,0 @@
1
- /**
2
- * @vitest-environment node
3
- *
4
- * Tests prepareProjectWorkspaceLinks — replaces the single
5
- * <workspace>/projects/<name> -> <projectDir> symlink with per-entry
6
- * symlinks that skip non-active language directories. This is what
7
- * actually reduces Turbopack's filesystem scan from 403 MDX files to
8
- * 135 on jamdesk-docs.
9
- */
10
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
11
- import fs from 'fs-extra';
12
- import path from 'path';
13
- import { tmpdir } from 'os';
14
- import { prepareProjectWorkspaceLinks } from '../../commands/dev.js';
15
- import { output } from '../../lib/output.js';
16
- describe('prepareProjectWorkspaceLinks', () => {
17
- let tmpRoot;
18
- let projectDir;
19
- let workspaceProjectDir;
20
- beforeEach(() => {
21
- tmpRoot = fs.mkdtempSync(path.join(tmpdir(), 'jam-ws-'));
22
- projectDir = path.join(tmpRoot, 'project');
23
- workspaceProjectDir = path.join(tmpRoot, 'ws', 'projects', 'project');
24
- // Set up a project layout that mirrors a multi-language docs project:
25
- // project/
26
- // ai/intro.mdx (en at root)
27
- // development/foo.mdx (en at root)
28
- // es/ai/intro.mdx (spanish)
29
- // fr/ai/intro.mdx (french)
30
- // docs.json
31
- // images/logo.png
32
- fs.mkdirpSync(path.join(projectDir, 'ai'));
33
- fs.writeFileSync(path.join(projectDir, 'ai', 'intro.mdx'), '# en');
34
- fs.mkdirpSync(path.join(projectDir, 'development'));
35
- fs.writeFileSync(path.join(projectDir, 'development', 'foo.mdx'), '# en');
36
- fs.mkdirpSync(path.join(projectDir, 'es', 'ai'));
37
- fs.writeFileSync(path.join(projectDir, 'es', 'ai', 'intro.mdx'), '# es');
38
- fs.mkdirpSync(path.join(projectDir, 'fr', 'ai'));
39
- fs.writeFileSync(path.join(projectDir, 'fr', 'ai', 'intro.mdx'), '# fr');
40
- fs.writeFileSync(path.join(projectDir, 'docs.json'), '{}');
41
- fs.mkdirpSync(path.join(projectDir, 'images'));
42
- fs.writeFileSync(path.join(projectDir, 'images', 'logo.png'), '');
43
- });
44
- afterEach(() => {
45
- fs.removeSync(tmpRoot);
46
- });
47
- it('symlinks every top-level entry when skip set is empty', async () => {
48
- await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set());
49
- expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
50
- expect(fs.existsSync(path.join(workspaceProjectDir, 'es', 'ai', 'intro.mdx'))).toBe(true);
51
- expect(fs.existsSync(path.join(workspaceProjectDir, 'fr', 'ai', 'intro.mdx'))).toBe(true);
52
- });
53
- it('does not symlink docs.json (caller writes a filtered copy)', async () => {
54
- // docs.json must not be symlinked — the caller writes a per-language
55
- // filtered copy, and fs.writeFile through a symlink would clobber the
56
- // user's source docs.json.
57
- await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set());
58
- expect(fs.existsSync(path.join(workspaceProjectDir, 'docs.json'))).toBe(false);
59
- });
60
- it('skips entries whose names are in the skip set', async () => {
61
- await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set(['es', 'fr']));
62
- expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
63
- expect(fs.existsSync(path.join(workspaceProjectDir, 'development', 'foo.mdx'))).toBe(true);
64
- expect(fs.existsSync(path.join(workspaceProjectDir, 'images', 'logo.png'))).toBe(true);
65
- // Skipped:
66
- expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(false);
67
- expect(fs.existsSync(path.join(workspaceProjectDir, 'fr'))).toBe(false);
68
- // docs.json not symlinked — caller writes a filtered copy.
69
- expect(fs.existsSync(path.join(workspaceProjectDir, 'docs.json'))).toBe(false);
70
- });
71
- it('rebuilds the workspace links from scratch on subsequent calls', async () => {
72
- // First call: no skip
73
- await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set());
74
- expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(true);
75
- // Second call: skip es
76
- await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set(['es']));
77
- expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(false);
78
- expect(fs.existsSync(path.join(workspaceProjectDir, 'fr'))).toBe(true);
79
- expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
80
- });
81
- it('handles a pre-existing single symlink at workspaceProjectDir (legacy layout)', async () => {
82
- // Pre-create the legacy single-symlink layout
83
- fs.mkdirpSync(path.dirname(workspaceProjectDir));
84
- fs.symlinkSync(projectDir, workspaceProjectDir, 'junction');
85
- await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set(['es', 'fr']));
86
- const lstat = fs.lstatSync(workspaceProjectDir);
87
- expect(lstat.isDirectory()).toBe(true);
88
- expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
89
- expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(false);
90
- });
91
- it('surfaces friendly error (not raw stack trace) when fs.rm throws ENOTEMPTY', async () => {
92
- // Regression: before safeRemoveCache, fs.remove raised an unfriendly stack
93
- // trace when Turbopack still held open files. Now safeRemoveCache detects
94
- // the race and calls process.exit(1) with a human-readable message.
95
- const rmSpy = vi.spyOn(fs, 'rm');
96
- const exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code) => {
97
- throw new Error(`process.exit:${code}`);
98
- }));
99
- const errorSpy = vi.spyOn(output, 'error').mockImplementation(() => undefined);
100
- const enotempty = Object.assign(new Error('ENOTEMPTY'), { code: 'ENOTEMPTY' });
101
- // fs.rm will be called by safeRemoveCache; make it fail with ENOTEMPTY
102
- // even after the internal maxRetries — simulate persistent race condition.
103
- rmSpy.mockRejectedValue(enotempty);
104
- await expect(prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set())).rejects.toThrow('process.exit:1');
105
- const msg = errorSpy.mock.calls[0]?.[0] ?? '';
106
- expect(msg).toContain('Another `jamdesk dev` instance');
107
- expect(msg).toContain('pkill -f');
108
- expect(exitSpy).toHaveBeenCalledWith(1);
109
- vi.restoreAllMocks();
110
- });
111
- });
112
- //# sourceMappingURL=dev-workspace-symlinks.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dev-workspace-symlinks.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/dev-workspace-symlinks.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=language-filter.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"language-filter.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/language-filter.test.ts"],"names":[],"mappings":""}
@@ -1,166 +0,0 @@
1
- /**
2
- * @vitest-environment node
3
- *
4
- * Tests for getActiveLanguageFilter — pure helper that decides which
5
- * top-level language directories to skip when symlinking project content
6
- * into the dev workspace. Multi-language sites' non-default languages
7
- * inflate Turbopack's filesystem scan and balloon cold compile time
8
- * (jamdesk-docs: 67s with 3 langs vs. 12.5s with 1 lang).
9
- */
10
- import { describe, it, expect } from 'vitest';
11
- import { getActiveLanguageFilter, isPageInSkippedLanguage, filterConfigByActiveLanguage, } from '../../lib/language-filter.js';
12
- const config = (langs) => ({
13
- navigation: { languages: langs.map(l => ({ language: l.language, default: l.default })) },
14
- });
15
- describe('getActiveLanguageFilter', () => {
16
- it('returns null filter when project has no languages array', () => {
17
- const result = getActiveLanguageFilter({ navigation: {} }, undefined, false);
18
- expect(result).toEqual({ active: null, skip: new Set() });
19
- });
20
- it('returns null filter when project has only one language', () => {
21
- const result = getActiveLanguageFilter(config([{ language: 'en', default: true }]), undefined, false);
22
- expect(result).toEqual({ active: 'en', skip: new Set() });
23
- });
24
- it('skips non-default languages when active is the default', () => {
25
- const result = getActiveLanguageFilter(config([
26
- { language: 'en', default: true },
27
- { language: 'es' },
28
- { language: 'fr' },
29
- ]), undefined, false);
30
- expect(result.active).toBe('en');
31
- expect(result.skip).toEqual(new Set(['es', 'fr']));
32
- });
33
- it('falls back to first language when none is marked default', () => {
34
- const result = getActiveLanguageFilter(config([{ language: 'en' }, { language: 'es' }, { language: 'fr' }]), undefined, false);
35
- expect(result.active).toBe('en');
36
- expect(result.skip).toEqual(new Set(['es', 'fr']));
37
- });
38
- it('accepts --lang matching the default language (no-op equivalence)', () => {
39
- const result = getActiveLanguageFilter(config([
40
- { language: 'en', default: true },
41
- { language: 'es' },
42
- ]), 'en', false);
43
- expect(result.active).toBe('en');
44
- expect(result.skip).toEqual(new Set(['es']));
45
- });
46
- it('throws on --lang for a non-default language (initial release scope)', () => {
47
- // Non-default-language layouts mix root-level default-lang content with
48
- // <lang>/ subdirs for translations; correctly stripping the default
49
- // content while keeping <lang>/ takes more work and is deferred to a
50
- // follow-up. For now, surface the workaround clearly.
51
- expect(() => getActiveLanguageFilter(config([
52
- { language: 'en', default: true },
53
- { language: 'es' },
54
- { language: 'fr' },
55
- ]), 'es', false)).toThrow(/--lang es: previewing non-default languages.*--all-langs/i);
56
- });
57
- it('returns empty skip set when --all-langs is set', () => {
58
- const result = getActiveLanguageFilter(config([
59
- { language: 'en', default: true },
60
- { language: 'es' },
61
- ]), undefined, true);
62
- expect(result.active).toBe('en');
63
- expect(result.skip).toEqual(new Set());
64
- });
65
- it('throws on --lang code that does not exist in docs.json', () => {
66
- expect(() => getActiveLanguageFilter(config([{ language: 'en', default: true }, { language: 'es' }]), 'de', false)).toThrow(/--lang de.*not.*docs\.json.*Available: en, es/);
67
- });
68
- it('handles malformed languages array (entries missing language field) by ignoring them', () => {
69
- const malformed = {
70
- navigation: {
71
- languages: [
72
- { language: 'en', default: true },
73
- { default: false }, // missing language: ignored
74
- { language: 'es' },
75
- { language: 42 }, // wrong type: ignored
76
- ],
77
- },
78
- };
79
- const result = getActiveLanguageFilter(malformed, undefined, false);
80
- expect(result.active).toBe('en');
81
- expect(result.skip).toEqual(new Set(['es']));
82
- });
83
- it('returns null active when no valid language entries exist', () => {
84
- const result = getActiveLanguageFilter({ navigation: { languages: [{ default: true }] } }, undefined, false);
85
- expect(result).toEqual({ active: null, skip: new Set() });
86
- });
87
- it('rejects empty-string --lang as an invalid code (commander passes "" through)', () => {
88
- // commander.js treats `--lang ""` as a value, not as missing — so
89
- // langOption is "" (defined, but empty). Empty string is never a valid
90
- // language code; surface it as a clear error rather than silently
91
- // falling back to the default.
92
- expect(() => getActiveLanguageFilter(config([{ language: 'en', default: true }, { language: 'es' }]), '', false)).toThrow(/--lang.*not.*docs\.json/);
93
- });
94
- });
95
- describe('isPageInSkippedLanguage', () => {
96
- it('returns false when skip set is empty', () => {
97
- expect(isPageInSkippedLanguage('fr/introduction', new Set())).toBe(false);
98
- });
99
- it('returns true when first path segment is a skipped language', () => {
100
- expect(isPageInSkippedLanguage('fr/introduction', new Set(['fr']))).toBe(true);
101
- });
102
- it('returns true for nested paths inside a skipped language', () => {
103
- expect(isPageInSkippedLanguage('fr/setup/connecting-github', new Set(['fr', 'de']))).toBe(true);
104
- });
105
- it('returns false when first segment is the active language', () => {
106
- expect(isPageInSkippedLanguage('en/introduction', new Set(['fr']))).toBe(false);
107
- });
108
- it('returns false for unprefixed root pages', () => {
109
- expect(isPageInSkippedLanguage('introduction', new Set(['fr']))).toBe(false);
110
- });
111
- it('does not match when the skip code is a prefix-substring of a different segment', () => {
112
- // Guard against startsWith('fr/') matching e.g. 'fr-something/foo'
113
- expect(isPageInSkippedLanguage('fr-something/foo', new Set(['fr']))).toBe(false);
114
- });
115
- it('returns true for a path with a fragment anchor in a skipped language', () => {
116
- // Broken-anchor warnings from validate-links.cjs surface as link values
117
- // like `fr/introduction#missing-section` — the fragment lives past the
118
- // first segment so the language check still works.
119
- expect(isPageInSkippedLanguage('fr/introduction#setup', new Set(['fr']))).toBe(true);
120
- });
121
- });
122
- describe('filterConfigByActiveLanguage', () => {
123
- it('returns the input unchanged (same reference) when skip is empty', () => {
124
- const config = { name: 'foo', navigation: { languages: [{ language: 'en' }] } };
125
- const result = filterConfigByActiveLanguage(config, { active: 'en', skip: new Set() });
126
- expect(result).toBe(config);
127
- });
128
- it('drops skipped languages from navigation.languages', () => {
129
- const config = {
130
- name: 'jamdesk-docs',
131
- navigation: {
132
- languages: [
133
- { language: 'en', default: true, tabs: [{ tab: 'Guide' }] },
134
- { language: 'fr', tabs: [{ tab: 'Guide' }] },
135
- { language: 'de', tabs: [{ tab: 'Guide' }] },
136
- ],
137
- },
138
- };
139
- const result = filterConfigByActiveLanguage(config, {
140
- active: 'en',
141
- skip: new Set(['fr', 'de']),
142
- });
143
- expect(result.navigation.languages).toEqual([
144
- { language: 'en', default: true, tabs: [{ tab: 'Guide' }] },
145
- ]);
146
- // Top-level fields preserved.
147
- expect(result.name).toBe('jamdesk-docs');
148
- // Original config untouched.
149
- expect(config.navigation.languages).toHaveLength(3);
150
- });
151
- it('preserves other navigation fields (tabs, global, anchors, etc.)', () => {
152
- const config = {
153
- navigation: {
154
- languages: [{ language: 'en' }, { language: 'fr' }],
155
- global: { anchors: [{ anchor: 'Support' }] },
156
- },
157
- };
158
- const result = filterConfigByActiveLanguage(config, {
159
- active: 'en',
160
- skip: new Set(['fr']),
161
- });
162
- expect(result.navigation.global).toEqual({ anchors: [{ anchor: 'Support' }] });
163
- expect(result.navigation.languages).toHaveLength(1);
164
- });
165
- });
166
- //# sourceMappingURL=language-filter.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"language-filter.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/language-filter.test.ts"],"names":[],"mappings":"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"}
@@ -1,31 +0,0 @@
1
- /**
2
- * Picks which `<lang>/` directories under projectDir to keep as workspace
3
- * symlinks and which to skip. Skipping non-active language directories
4
- * shrinks Turbopack's filesystem scan and dropped cold compile from ~67s
5
- * to ~12s on jamdesk-docs (commit 90d781b4).
6
- *
7
- * The dev server still reads content from the user's source tree via
8
- * JAMDESK_PROJECTS_DIR, so the language picker keeps showing every
9
- * language and clicks always 200 OK — the workspace symlinks are a
10
- * Turbopack-only performance layer.
11
- *
12
- * Default rules:
13
- * 1. Language with `default: true`
14
- * 2. First language in `navigation.languages[]`
15
- */
16
- export interface LanguageFilter {
17
- active: string | null;
18
- skip: Set<string>;
19
- }
20
- interface NavigationLanguageEntry {
21
- language?: string;
22
- default?: boolean;
23
- }
24
- interface MinimalConfig {
25
- navigation?: {
26
- languages?: NavigationLanguageEntry[];
27
- };
28
- }
29
- export declare function getActiveLanguageFilter(config: MinimalConfig): LanguageFilter;
30
- export {};
31
- //# sourceMappingURL=language-filter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"language-filter.d.ts","sourceRoot":"","sources":["../../src/lib/language-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB;AAED,UAAU,uBAAuB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,aAAa;IACrB,UAAU,CAAC,EAAE;QACX,SAAS,CAAC,EAAE,uBAAuB,EAAE,CAAC;KACvC,CAAC;CACH;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,GAAG,cAAc,CAc7E"}
@@ -1,14 +0,0 @@
1
- export function getActiveLanguageFilter(config) {
2
- const validEntries = (config.navigation?.languages ?? []).filter((l) => typeof l.language === 'string');
3
- if (validEntries.length === 0) {
4
- return { active: null, skip: new Set() };
5
- }
6
- const codes = validEntries.map((l) => l.language);
7
- const active = validEntries.find((l) => l.default)?.language ?? validEntries[0].language;
8
- if (codes.length === 1) {
9
- return { active, skip: new Set() };
10
- }
11
- const skip = new Set(codes.filter((c) => c !== active));
12
- return { active, skip };
13
- }
14
- //# sourceMappingURL=language-filter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"language-filter.js","sourceRoot":"","sources":["../../src/lib/language-filter.ts"],"names":[],"mappings":"AA+BA,MAAM,UAAU,uBAAuB,CAAC,MAAqB;IAC3D,MAAM,YAAY,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,CAC9D,CAAC,CAAC,EAAgD,EAAE,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CACpF,CAAC;IACF,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,EAAU,EAAE,CAAC;IACnD,CAAC;IACD,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACzF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,EAAU,EAAE,CAAC;IAC7C,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC;IACxD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1B,CAAC"}