jamdesk 1.1.142 → 1.1.144

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 (46) hide show
  1. package/dist/commands/dev.d.ts.map +1 -1
  2. package/dist/commands/dev.js +20 -0
  3. package/dist/commands/dev.js.map +1 -1
  4. package/package.json +1 -1
  5. package/vendored/app/[[...slug]]/page.tsx +8 -1
  6. package/vendored/app/api/subscribe/[project]/route.ts +154 -0
  7. package/vendored/components/mdx/EmailSubscribe.tsx +167 -0
  8. package/vendored/components/mdx/MDXComponents.tsx +3 -0
  9. package/vendored/components/mdx/NativeSubscribeForm.tsx +172 -0
  10. package/vendored/components/navigation/Header.tsx +4 -3
  11. package/vendored/components/search/SearchModal.tsx +10 -4
  12. package/vendored/lib/docs-types.ts +45 -2
  13. package/vendored/lib/email-subscribe-autoplacement.ts +46 -0
  14. package/vendored/lib/email-subscribe-providers.ts +120 -0
  15. package/vendored/lib/middleware-helpers.ts +21 -0
  16. package/vendored/lib/navigation-resolver.ts +5 -5
  17. package/vendored/lib/newsletter/adapters/beehiiv.ts +23 -0
  18. package/vendored/lib/newsletter/adapters/brevo.ts +31 -0
  19. package/vendored/lib/newsletter/adapters/kit.ts +35 -0
  20. package/vendored/lib/newsletter/adapters/loops.ts +22 -0
  21. package/vendored/lib/newsletter/adapters/mailchimp.ts +52 -0
  22. package/vendored/lib/newsletter/adapters/resend.ts +23 -0
  23. package/vendored/lib/newsletter/adapters/sendgrid.ts +23 -0
  24. package/vendored/lib/newsletter/descriptors.ts +84 -0
  25. package/vendored/lib/newsletter/http.ts +19 -0
  26. package/vendored/lib/newsletter/registry.ts +27 -0
  27. package/vendored/lib/newsletter/types.ts +52 -0
  28. package/vendored/lib/newsletter-display.ts +43 -0
  29. package/vendored/lib/render-doc-page.tsx +46 -2
  30. package/vendored/lib/seo.ts +13 -2
  31. package/vendored/lib/validate-content-images.ts +150 -0
  32. package/vendored/schema/docs-schema.json +29 -1
  33. package/vendored/shared/status-reporter.ts +1 -1
  34. package/vendored/workspace-package-lock.json +3 -87
  35. package/dist/__tests__/unit/dev-workspace-symlinks.test.d.ts +0 -2
  36. package/dist/__tests__/unit/dev-workspace-symlinks.test.d.ts.map +0 -1
  37. package/dist/__tests__/unit/dev-workspace-symlinks.test.js +0 -112
  38. package/dist/__tests__/unit/dev-workspace-symlinks.test.js.map +0 -1
  39. package/dist/__tests__/unit/language-filter.test.d.ts +0 -2
  40. package/dist/__tests__/unit/language-filter.test.d.ts.map +0 -1
  41. package/dist/__tests__/unit/language-filter.test.js +0 -166
  42. package/dist/__tests__/unit/language-filter.test.js.map +0 -1
  43. package/dist/lib/language-filter.d.ts +0 -31
  44. package/dist/lib/language-filter.d.ts.map +0 -1
  45. package/dist/lib/language-filter.js +0 -14
  46. package/dist/lib/language-filter.js.map +0 -1
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Content Image Validation (build-service)
3
+ *
4
+ * Warns when a local image referenced from page/snippet MDX — markdown
5
+ * `![alt](/images/foo.webp)`, `<img src="…">`, `<Image src="…">` — points at a
6
+ * file that isn't in the project's collected assets. Emits non-blocking
7
+ * `missing_image` BuildWarning entries.
8
+ *
9
+ * Nothing else in the build covers this: validate-links.cjs skips asset
10
+ * extensions and the WebP converter silently ignores missing references, so a
11
+ * typo'd or never-uploaded image otherwise ships and only 404s in a reader's
12
+ * browser.
13
+ *
14
+ * PARITY TWIN of the CLI's `builder/cli/src/lib/validate-image-refs.ts` — the
15
+ * two run on different surfaces (in-memory maps here, filesystem walk there) so
16
+ * they can't share a module, but their DETECTION must agree so `jamdesk
17
+ * validate` and the cloud build flag the same references. The extraction
18
+ * (stripCodeRegions + the two regexes + the external/data skip) is copied from
19
+ * that file verbatim; keep them in sync. This build-service copy adds two
20
+ * build-only concerns the CLI lacks: resolution against the already-collected
21
+ * asset list (instead of fs), and convertToWebp awareness (a `.webp` reference
22
+ * backed by a `.png`/`.jpg` source is valid once the converter runs).
23
+ */
24
+
25
+ import path from 'path';
26
+ import { normalizeAssetPath } from './isr-build-executor.js';
27
+ import type { BuildWarning } from '../shared/status-reporter.js';
28
+
29
+ export interface ValidateContentImagesOptions {
30
+ /** When docs.json images.convertToWebp is on, a `.webp` reference is served
31
+ * from a `.png`/`.jpg` source — accept those so conversion isn't flagged. */
32
+ convertToWebp?: boolean;
33
+ /** Max warnings to return (Firestore document-size guard). Default 50. */
34
+ cap?: number;
35
+ }
36
+
37
+ const CONVERTIBLE_EXT_RE = /\.(png|jpe?g)$/i;
38
+
39
+ // --- Extraction (kept identical to the CLI twin) -----------------------------
40
+
41
+ function isExternalOrDataUrl(src: string): boolean {
42
+ return (
43
+ src.startsWith('http://') ||
44
+ src.startsWith('https://') ||
45
+ src.startsWith('//') ||
46
+ src.startsWith('data:')
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Blank out MDX regions that hold non-rendered content — HTML comments, JSX
52
+ * comments, fenced code blocks, inline code spans — replacing them with spaces
53
+ * of equal length so reported line numbers stay accurate. Image-like syntax
54
+ * inside these is illustrative (a tutorial showing `![alt](path)`), not a real
55
+ * reference, and flagging it would be a false positive.
56
+ */
57
+ function stripCodeRegions(content: string): string {
58
+ const blank = (m: string) => m.replace(/[^\n]/g, ' ');
59
+ let out = content.replace(/<!--[\s\S]*?-->/g, blank);
60
+ out = out.replace(/\{\s*\/\*(?:(?!\*\/)[\s\S])*?\*\/\s*\}/g, blank);
61
+ out = out.replace(/(^|\n)[ \t]*(```|~~~)[^\n]*\n([\s\S]*?)\n[ \t]*\2(?=\n|$)/g, blank);
62
+ out = out.replace(/`[^`\n]+`/g, blank);
63
+ return out;
64
+ }
65
+
66
+ function extractImageRefs(content: string): { src: string; line: number }[] {
67
+ const refs: { src: string; line: number }[] = [];
68
+ const scan = stripCodeRegions(content);
69
+
70
+ // <img src="…"> / <Image src="…"> — string literal only (skips {expr}).
71
+ const jsxRegex = /<(?:img|Image)\b[^>]*?\bsrc\s*=\s*(["'])([^"'{}]+?)\1/gi;
72
+ // Markdown ![alt](path) — capture path before an optional title.
73
+ const mdRegex = /!\[[^\]]*\]\(\s*<?([^)\s<>]+)>?(?:\s+["'][^"']*["'])?\s*\)/g;
74
+
75
+ const indexToLine = (idx: number): number => {
76
+ let line = 1;
77
+ for (let i = 0; i < idx; i++) if (scan.charCodeAt(i) === 10) line++;
78
+ return line;
79
+ };
80
+
81
+ for (const m of scan.matchAll(jsxRegex)) refs.push({ src: m[2], line: indexToLine(m.index ?? 0) });
82
+ for (const m of scan.matchAll(mdRegex)) refs.push({ src: m[1], line: indexToLine(m.index ?? 0) });
83
+ return refs;
84
+ }
85
+
86
+ // --- Resolution (build-service: against the collected asset list) ------------
87
+
88
+ /** Resolve a reference to the same key shape as collected asset paths
89
+ * (public/ stripped). Absolute `/x` is project-rooted; a relative ref is
90
+ * resolved against the referencing file's directory. */
91
+ function resolveKey(fileKey: string, src: string): string | null {
92
+ const clean = src.split(/[?#]/)[0];
93
+ if (!clean) return null;
94
+ const rel = clean.startsWith('/')
95
+ ? clean.slice(1)
96
+ : path.posix.normalize(path.posix.join(path.posix.dirname(fileKey), clean));
97
+ return normalizeAssetPath(rel.replace(/^\/+/, ''));
98
+ }
99
+
100
+ /**
101
+ * @param fileContents - path -> MDX content (pages keyed by relative path,
102
+ * snippets by `snippets/<name>`), as assembled in build.ts.
103
+ * @param assetFiles - asset paths relative to project dir (from collectAssetFiles).
104
+ */
105
+ export function validateContentImages(
106
+ fileContents: Map<string, string>,
107
+ assetFiles: string[],
108
+ options: ValidateContentImagesOptions = {},
109
+ ): BuildWarning[] {
110
+ const cap = options.cap ?? 50;
111
+
112
+ const assetSet = new Set(assetFiles.map(normalizeAssetPath));
113
+ if (options.convertToWebp) {
114
+ for (const asset of assetFiles) {
115
+ const normalized = normalizeAssetPath(asset);
116
+ if (CONVERTIBLE_EXT_RE.test(normalized)) {
117
+ assetSet.add(normalized.replace(CONVERTIBLE_EXT_RE, '.webp'));
118
+ }
119
+ }
120
+ }
121
+
122
+ const warnings: BuildWarning[] = [];
123
+ const seen = new Set<string>();
124
+
125
+ for (const [file, content] of fileContents) {
126
+ if (warnings.length >= cap) break;
127
+ for (const { src, line } of extractImageRefs(content)) {
128
+ if (warnings.length >= cap) break;
129
+ const trimmed = src.trim();
130
+ if (!trimmed || isExternalOrDataUrl(trimmed)) continue;
131
+
132
+ const key = resolveKey(file, trimmed);
133
+ if (key === null || assetSet.has(key)) continue;
134
+
135
+ const dedupeKey = `${file}|${key}`;
136
+ if (seen.has(dedupeKey)) continue;
137
+ seen.add(dedupeKey);
138
+
139
+ warnings.push({
140
+ type: 'missing_image',
141
+ file,
142
+ line,
143
+ link: trimmed,
144
+ message: `Image "${trimmed}" referenced in ${file} was not found in the project`,
145
+ });
146
+ }
147
+ }
148
+
149
+ return warnings;
150
+ }
@@ -555,7 +555,7 @@
555
555
  "slug": {
556
556
  "type": "string",
557
557
  "minLength": 1,
558
- "description": "Page path (without .mdx extension)"
558
+ "description": "Page path, without a leading slash or file extension. A leading slash makes a broken // URL. Nested pages use the folder path: e.g. 'quickstart', or 'guides/authentication' for the file guides/authentication.mdx."
559
559
  },
560
560
  "icon": {
561
561
  "$ref": "#/definitions/iconSchema",
@@ -1158,6 +1158,34 @@
1158
1158
  ],
1159
1159
  "additionalProperties": false
1160
1160
  },
1161
+ "newsletter": {
1162
+ "type": "object",
1163
+ "description": "Inline newsletter / changelog-notification signup rendered by <EmailSubscribe>. Customer owns the list and sending.",
1164
+ "properties": {
1165
+ "provider": {
1166
+ "type": "string",
1167
+ "enum": ["resend", "mailchimp", "kit", "loops", "beehiiv", "brevo", "sendgrid", "buttondown", "substack"],
1168
+ "description": "Newsletter provider. Native providers (resend, mailchimp, kit, loops, beehiiv, brevo, sendgrid) are configured in the dashboard (Settings → Email signups) and render a Jamdesk-hosted capture form. buttondown and substack are embed-only — supply a \"username\" to render their iframe/form."
1169
+ },
1170
+ "action": { "type": "string", "description": "Mailchimp: full form POST action URL." },
1171
+ "username": { "type": "string", "description": "Buttondown / Substack account username." },
1172
+ "src": { "type": "string", "description": "Beehiiv: full iframe embed src URL." },
1173
+ "snippet": { "type": "string", "description": "Raw embed snippet (any vendor) — escape hatch." },
1174
+ "height": { "type": "number", "description": "iframe height in px for substack/beehiiv embeds." },
1175
+ "title": { "type": "string" },
1176
+ "description": { "type": "string" },
1177
+ "collapsed": {
1178
+ "type": "boolean",
1179
+ "description": "Native providers only: render a compact Subscribe button that expands to the form on click, instead of the full email box."
1180
+ },
1181
+ "placement": {
1182
+ "type": "string",
1183
+ "enum": ["none", "changelog"],
1184
+ "description": "'changelog' auto-mounts on rss:true pages; 'none' (default) = manual <EmailSubscribe>."
1185
+ }
1186
+ },
1187
+ "additionalProperties": false
1188
+ },
1161
1189
  "intercom": {
1162
1190
  "type": "object",
1163
1191
  "properties": {
@@ -22,7 +22,7 @@ export interface ProgressUpdate {
22
22
  }
23
23
 
24
24
  /** Warning types that can occur during builds (non-blocking) */
25
- export type BuildWarningType = 'broken_link' | 'auto_migrate' | 'missing_asset' | 'missing_page' | 'missing_openapi_ref' | 'inline_code_on_api_page' | 'invalid_openapi_spec' | 'missing_snippet' | 'risky_expression' | 'missing_favicon' | 'missing_description' | 'missing_logo';
25
+ export type BuildWarningType = 'broken_link' | 'auto_migrate' | 'missing_asset' | 'missing_image' | 'missing_page' | 'missing_openapi_ref' | 'inline_code_on_api_page' | 'invalid_openapi_spec' | 'missing_snippet' | 'risky_expression' | 'missing_favicon' | 'missing_description' | 'missing_logo';
26
26
 
27
27
  /** Build warning structure */
28
28
  export interface BuildWarning {
@@ -184,9 +184,9 @@
184
184
  "license": "Apache-2.0"
185
185
  },
186
186
  "node_modules/@emnapi/runtime": {
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==",
187
+ "version": "1.11.1",
188
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
189
+ "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
190
190
  "license": "MIT",
191
191
  "optional": true,
192
192
  "dependencies": {
@@ -373,9 +373,6 @@
373
373
  "cpu": [
374
374
  "arm"
375
375
  ],
376
- "libc": [
377
- "glibc"
378
- ],
379
376
  "license": "LGPL-3.0-or-later",
380
377
  "optional": true,
381
378
  "os": [
@@ -392,9 +389,6 @@
392
389
  "cpu": [
393
390
  "arm64"
394
391
  ],
395
- "libc": [
396
- "glibc"
397
- ],
398
392
  "license": "LGPL-3.0-or-later",
399
393
  "optional": true,
400
394
  "os": [
@@ -411,9 +405,6 @@
411
405
  "cpu": [
412
406
  "ppc64"
413
407
  ],
414
- "libc": [
415
- "glibc"
416
- ],
417
408
  "license": "LGPL-3.0-or-later",
418
409
  "optional": true,
419
410
  "os": [
@@ -430,9 +421,6 @@
430
421
  "cpu": [
431
422
  "riscv64"
432
423
  ],
433
- "libc": [
434
- "glibc"
435
- ],
436
424
  "license": "LGPL-3.0-or-later",
437
425
  "optional": true,
438
426
  "os": [
@@ -449,9 +437,6 @@
449
437
  "cpu": [
450
438
  "s390x"
451
439
  ],
452
- "libc": [
453
- "glibc"
454
- ],
455
440
  "license": "LGPL-3.0-or-later",
456
441
  "optional": true,
457
442
  "os": [
@@ -468,9 +453,6 @@
468
453
  "cpu": [
469
454
  "x64"
470
455
  ],
471
- "libc": [
472
- "glibc"
473
- ],
474
456
  "license": "LGPL-3.0-or-later",
475
457
  "optional": true,
476
458
  "os": [
@@ -487,9 +469,6 @@
487
469
  "cpu": [
488
470
  "arm64"
489
471
  ],
490
- "libc": [
491
- "musl"
492
- ],
493
472
  "license": "LGPL-3.0-or-later",
494
473
  "optional": true,
495
474
  "os": [
@@ -506,9 +485,6 @@
506
485
  "cpu": [
507
486
  "x64"
508
487
  ],
509
- "libc": [
510
- "musl"
511
- ],
512
488
  "license": "LGPL-3.0-or-later",
513
489
  "optional": true,
514
490
  "os": [
@@ -525,9 +501,6 @@
525
501
  "cpu": [
526
502
  "arm"
527
503
  ],
528
- "libc": [
529
- "glibc"
530
- ],
531
504
  "license": "Apache-2.0",
532
505
  "optional": true,
533
506
  "os": [
@@ -550,9 +523,6 @@
550
523
  "cpu": [
551
524
  "arm64"
552
525
  ],
553
- "libc": [
554
- "glibc"
555
- ],
556
526
  "license": "Apache-2.0",
557
527
  "optional": true,
558
528
  "os": [
@@ -575,9 +545,6 @@
575
545
  "cpu": [
576
546
  "ppc64"
577
547
  ],
578
- "libc": [
579
- "glibc"
580
- ],
581
548
  "license": "Apache-2.0",
582
549
  "optional": true,
583
550
  "os": [
@@ -600,9 +567,6 @@
600
567
  "cpu": [
601
568
  "riscv64"
602
569
  ],
603
- "libc": [
604
- "glibc"
605
- ],
606
570
  "license": "Apache-2.0",
607
571
  "optional": true,
608
572
  "os": [
@@ -625,9 +589,6 @@
625
589
  "cpu": [
626
590
  "s390x"
627
591
  ],
628
- "libc": [
629
- "glibc"
630
- ],
631
592
  "license": "Apache-2.0",
632
593
  "optional": true,
633
594
  "os": [
@@ -650,9 +611,6 @@
650
611
  "cpu": [
651
612
  "x64"
652
613
  ],
653
- "libc": [
654
- "glibc"
655
- ],
656
614
  "license": "Apache-2.0",
657
615
  "optional": true,
658
616
  "os": [
@@ -675,9 +633,6 @@
675
633
  "cpu": [
676
634
  "arm64"
677
635
  ],
678
- "libc": [
679
- "musl"
680
- ],
681
636
  "license": "Apache-2.0",
682
637
  "optional": true,
683
638
  "os": [
@@ -700,9 +655,6 @@
700
655
  "cpu": [
701
656
  "x64"
702
657
  ],
703
- "libc": [
704
- "musl"
705
- ],
706
658
  "license": "Apache-2.0",
707
659
  "optional": true,
708
660
  "os": [
@@ -977,9 +929,6 @@
977
929
  "cpu": [
978
930
  "arm64"
979
931
  ],
980
- "libc": [
981
- "glibc"
982
- ],
983
932
  "license": "MIT",
984
933
  "optional": true,
985
934
  "os": [
@@ -996,9 +945,6 @@
996
945
  "cpu": [
997
946
  "arm64"
998
947
  ],
999
- "libc": [
1000
- "musl"
1001
- ],
1002
948
  "license": "MIT",
1003
949
  "optional": true,
1004
950
  "os": [
@@ -1015,9 +961,6 @@
1015
961
  "cpu": [
1016
962
  "x64"
1017
963
  ],
1018
- "libc": [
1019
- "glibc"
1020
- ],
1021
964
  "license": "MIT",
1022
965
  "optional": true,
1023
966
  "os": [
@@ -1034,9 +977,6 @@
1034
977
  "cpu": [
1035
978
  "x64"
1036
979
  ],
1037
- "libc": [
1038
- "musl"
1039
- ],
1040
980
  "license": "MIT",
1041
981
  "optional": true,
1042
982
  "os": [
@@ -1376,9 +1316,6 @@
1376
1316
  "cpu": [
1377
1317
  "arm64"
1378
1318
  ],
1379
- "libc": [
1380
- "glibc"
1381
- ],
1382
1319
  "license": "MIT",
1383
1320
  "optional": true,
1384
1321
  "os": [
@@ -1395,9 +1332,6 @@
1395
1332
  "cpu": [
1396
1333
  "arm64"
1397
1334
  ],
1398
- "libc": [
1399
- "musl"
1400
- ],
1401
1335
  "license": "MIT",
1402
1336
  "optional": true,
1403
1337
  "os": [
@@ -1414,9 +1348,6 @@
1414
1348
  "cpu": [
1415
1349
  "x64"
1416
1350
  ],
1417
- "libc": [
1418
- "glibc"
1419
- ],
1420
1351
  "license": "MIT",
1421
1352
  "optional": true,
1422
1353
  "os": [
@@ -1433,9 +1364,6 @@
1433
1364
  "cpu": [
1434
1365
  "x64"
1435
1366
  ],
1436
- "libc": [
1437
- "musl"
1438
- ],
1439
1367
  "license": "MIT",
1440
1368
  "optional": true,
1441
1369
  "os": [
@@ -3937,9 +3865,6 @@
3937
3865
  "cpu": [
3938
3866
  "arm64"
3939
3867
  ],
3940
- "libc": [
3941
- "glibc"
3942
- ],
3943
3868
  "license": "MPL-2.0",
3944
3869
  "optional": true,
3945
3870
  "os": [
@@ -3960,9 +3885,6 @@
3960
3885
  "cpu": [
3961
3886
  "arm64"
3962
3887
  ],
3963
- "libc": [
3964
- "musl"
3965
- ],
3966
3888
  "license": "MPL-2.0",
3967
3889
  "optional": true,
3968
3890
  "os": [
@@ -3983,9 +3905,6 @@
3983
3905
  "cpu": [
3984
3906
  "x64"
3985
3907
  ],
3986
- "libc": [
3987
- "glibc"
3988
- ],
3989
3908
  "license": "MPL-2.0",
3990
3909
  "optional": true,
3991
3910
  "os": [
@@ -4006,9 +3925,6 @@
4006
3925
  "cpu": [
4007
3926
  "x64"
4008
3927
  ],
4009
- "libc": [
4010
- "musl"
4011
- ],
4012
3928
  "license": "MPL-2.0",
4013
3929
  "optional": true,
4014
3930
  "os": [
@@ -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":""}