prev-cli 0.24.19 → 0.25.0

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 (150) hide show
  1. package/dist/cli.js +2006 -1703
  2. package/dist/previews/components/cart-item/index.d.ts +5 -0
  3. package/dist/previews/components/price-tag/index.d.ts +6 -0
  4. package/dist/previews/screens/cart/empty.d.ts +1 -0
  5. package/dist/previews/screens/cart/index.d.ts +1 -0
  6. package/dist/previews/screens/payment/error.d.ts +1 -0
  7. package/dist/previews/screens/payment/index.d.ts +1 -0
  8. package/dist/previews/screens/payment/processing.d.ts +1 -0
  9. package/dist/previews/screens/receipt/index.d.ts +1 -0
  10. package/dist/previews/shared/data.d.ts +30 -0
  11. package/dist/src/content/config-parser.d.ts +30 -0
  12. package/dist/src/content/flow-verifier.d.ts +21 -0
  13. package/dist/src/content/preview-types.d.ts +288 -0
  14. package/dist/{vite → src/content}/previews.d.ts +3 -11
  15. package/dist/{preview-runtime → src/preview-runtime}/build-optimized.d.ts +2 -0
  16. package/dist/{preview-runtime → src/preview-runtime}/build.d.ts +1 -1
  17. package/dist/src/preview-runtime/region-bridge.d.ts +1 -0
  18. package/dist/{preview-runtime → src/preview-runtime}/types.d.ts +18 -0
  19. package/dist/src/preview-runtime/vendors.d.ts +11 -0
  20. package/dist/{renderers → src/renderers}/index.d.ts +1 -1
  21. package/dist/{renderers → src/renderers}/types.d.ts +3 -31
  22. package/dist/src/server/build.d.ts +6 -0
  23. package/dist/src/server/dev.d.ts +13 -0
  24. package/dist/src/server/plugins/aliases.d.ts +5 -0
  25. package/dist/src/server/plugins/mdx.d.ts +5 -0
  26. package/dist/src/server/plugins/virtual-modules.d.ts +8 -0
  27. package/dist/src/server/preview.d.ts +10 -0
  28. package/dist/src/server/routes/component-bundle.d.ts +1 -0
  29. package/dist/src/server/routes/jsx-bundle.d.ts +3 -0
  30. package/dist/src/server/routes/og-image.d.ts +15 -0
  31. package/dist/src/server/routes/preview-bundle.d.ts +1 -0
  32. package/dist/src/server/routes/preview-config.d.ts +1 -0
  33. package/dist/src/server/routes/tokens.d.ts +1 -0
  34. package/dist/{vite → src/server}/start.d.ts +5 -2
  35. package/dist/{ui → src/ui}/button.d.ts +1 -1
  36. package/dist/{validators → src/validators}/index.d.ts +0 -5
  37. package/dist/{validators → src/validators}/semantic-validator.d.ts +2 -3
  38. package/package.json +8 -11
  39. package/src/jsx/CLAUDE.md +18 -0
  40. package/src/jsx/jsx-runtime.ts +1 -1
  41. package/src/preview-runtime/CLAUDE.md +21 -0
  42. package/src/preview-runtime/build-optimized.ts +189 -73
  43. package/src/preview-runtime/build.ts +75 -79
  44. package/src/preview-runtime/fast-template.html +5 -1
  45. package/src/preview-runtime/region-bridge.test.ts +41 -0
  46. package/src/preview-runtime/region-bridge.ts +101 -0
  47. package/src/preview-runtime/types.ts +6 -0
  48. package/src/preview-runtime/vendors.ts +215 -22
  49. package/src/primitives/CLAUDE.md +17 -0
  50. package/src/theme/CLAUDE.md +20 -0
  51. package/src/theme/Preview.tsx +10 -4
  52. package/src/theme/Toolbar.tsx +2 -2
  53. package/src/theme/entry.tsx +247 -121
  54. package/src/theme/hooks/useAnnotations.ts +77 -0
  55. package/src/theme/hooks/useApprovalStatus.ts +50 -0
  56. package/src/theme/hooks/useSnapshots.ts +147 -0
  57. package/src/theme/hooks/useStorage.ts +26 -0
  58. package/src/theme/hooks/useTokenOverrides.ts +56 -0
  59. package/src/theme/hooks/useViewport.ts +23 -0
  60. package/src/theme/icons.tsx +39 -1
  61. package/src/theme/index.html +18 -0
  62. package/src/theme/mdx-components.tsx +1 -1
  63. package/src/theme/previews/AnnotationLayer.tsx +285 -0
  64. package/src/theme/previews/AnnotationPin.tsx +61 -0
  65. package/src/theme/previews/AnnotationThread.tsx +257 -0
  66. package/src/theme/previews/CLAUDE.md +18 -0
  67. package/src/theme/previews/ComponentPreview.tsx +487 -107
  68. package/src/theme/previews/FlowDiagram.tsx +111 -0
  69. package/src/theme/previews/FlowPreview.tsx +938 -174
  70. package/src/theme/previews/PreviewRouter.tsx +1 -4
  71. package/src/theme/previews/ScreenPreview.tsx +515 -175
  72. package/src/theme/previews/SnapshotButton.tsx +68 -0
  73. package/src/theme/previews/SnapshotCompare.tsx +216 -0
  74. package/src/theme/previews/SnapshotPanel.tsx +274 -0
  75. package/src/theme/previews/StatusBadge.tsx +66 -0
  76. package/src/theme/previews/StatusDropdown.tsx +158 -0
  77. package/src/theme/previews/TokenPlayground.tsx +438 -0
  78. package/src/theme/previews/ViewportControls.tsx +67 -0
  79. package/src/theme/previews/flow-diagram.test.ts +141 -0
  80. package/src/theme/previews/flow-diagram.ts +109 -0
  81. package/src/theme/previews/flow-navigation.test.ts +90 -0
  82. package/src/theme/previews/flow-navigation.ts +47 -0
  83. package/src/theme/previews/machines/derived.test.ts +225 -0
  84. package/src/theme/previews/machines/derived.ts +73 -0
  85. package/src/theme/previews/machines/flow-machine.test.ts +379 -0
  86. package/src/theme/previews/machines/flow-machine.ts +207 -0
  87. package/src/theme/previews/machines/screen-machine.test.ts +149 -0
  88. package/src/theme/previews/machines/screen-machine.ts +76 -0
  89. package/src/theme/previews/stores/flow-store.test.ts +157 -0
  90. package/src/theme/previews/stores/flow-store.ts +49 -0
  91. package/src/theme/previews/stores/screen-store.test.ts +68 -0
  92. package/src/theme/previews/stores/screen-store.ts +33 -0
  93. package/src/theme/storage.test.ts +97 -0
  94. package/src/theme/storage.ts +71 -0
  95. package/src/theme/styles.css +296 -25
  96. package/src/theme/types.ts +64 -0
  97. package/src/tokens/CLAUDE.md +16 -0
  98. package/src/tokens/resolver.ts +1 -1
  99. package/dist/preview-runtime/vendors.d.ts +0 -6
  100. package/dist/vite/config-parser.d.ts +0 -13
  101. package/dist/vite/config.d.ts +0 -12
  102. package/dist/vite/plugins/config-plugin.d.ts +0 -3
  103. package/dist/vite/plugins/debug-plugin.d.ts +0 -3
  104. package/dist/vite/plugins/entry-plugin.d.ts +0 -2
  105. package/dist/vite/plugins/fumadocs-plugin.d.ts +0 -9
  106. package/dist/vite/plugins/pages-plugin.d.ts +0 -5
  107. package/dist/vite/plugins/previews-plugin.d.ts +0 -2
  108. package/dist/vite/plugins/tokens-plugin.d.ts +0 -2
  109. package/dist/vite/preview-types.d.ts +0 -70
  110. package/src/theme/previews/AtlasPreview.tsx +0 -528
  111. package/dist/{cli.d.ts → src/cli.d.ts} +0 -0
  112. package/dist/{config → src/config}/index.d.ts +0 -0
  113. package/dist/{config → src/config}/loader.d.ts +0 -0
  114. package/dist/{config → src/config}/schema.d.ts +0 -0
  115. package/dist/{vite → src/content}/pages.d.ts +0 -0
  116. package/dist/{jsx → src/jsx}/adapters/html.d.ts +0 -0
  117. package/dist/{jsx → src/jsx}/adapters/react.d.ts +0 -0
  118. package/dist/{jsx → src/jsx}/define-component.d.ts +0 -0
  119. package/dist/{jsx → src/jsx}/index.d.ts +0 -0
  120. package/dist/{jsx → src/jsx}/jsx-runtime.d.ts +0 -0
  121. package/dist/{jsx → src/jsx}/migrate.d.ts +0 -0
  122. package/dist/{jsx → src/jsx}/schemas/index.d.ts +0 -0
  123. package/dist/{jsx → src/jsx}/schemas/primitives.d.ts +10 -10
  124. package/dist/{jsx → src/jsx}/schemas/tokens.d.ts +3 -3
  125. /package/dist/{jsx → src/jsx}/validation.d.ts +0 -0
  126. /package/dist/{jsx → src/jsx}/vnode.d.ts +0 -0
  127. /package/dist/{migrate.d.ts → src/migrate.d.ts} +0 -0
  128. /package/dist/{preview-runtime → src/preview-runtime}/tailwind.d.ts +0 -0
  129. /package/dist/{primitives → src/primitives}/index.d.ts +0 -0
  130. /package/dist/{primitives → src/primitives}/migrate.d.ts +0 -0
  131. /package/dist/{primitives → src/primitives}/parser.d.ts +0 -0
  132. /package/dist/{primitives → src/primitives}/template-parser.d.ts +0 -0
  133. /package/dist/{primitives → src/primitives}/template-renderer.d.ts +0 -0
  134. /package/dist/{primitives → src/primitives}/types.d.ts +0 -0
  135. /package/dist/{renderers → src/renderers}/html/index.d.ts +0 -0
  136. /package/dist/{renderers → src/renderers}/react/index.d.ts +0 -0
  137. /package/dist/{renderers → src/renderers}/registry.d.ts +0 -0
  138. /package/dist/{renderers → src/renderers}/render.d.ts +0 -0
  139. /package/dist/{tokens → src/tokens}/defaults.d.ts +0 -0
  140. /package/dist/{tokens → src/tokens}/resolver.d.ts +0 -0
  141. /package/dist/{tokens → src/tokens}/utils.d.ts +0 -0
  142. /package/dist/{tokens → src/tokens}/validation.d.ts +0 -0
  143. /package/dist/{typecheck → src/typecheck}/index.d.ts +0 -0
  144. /package/dist/{ui → src/ui}/card.d.ts +0 -0
  145. /package/dist/{ui → src/ui}/index.d.ts +0 -0
  146. /package/dist/{ui → src/ui}/utils.d.ts +0 -0
  147. /package/dist/{utils → src/utils}/cache.d.ts +0 -0
  148. /package/dist/{utils → src/utils}/debug.d.ts +0 -0
  149. /package/dist/{utils → src/utils}/port.d.ts +0 -0
  150. /package/dist/{validators → src/validators}/schema-validator.d.ts +0 -0
package/dist/cli.js CHANGED
@@ -2,18 +2,125 @@
2
2
  // @bun
3
3
  if(typeof globalThis.Bun==="undefined"){console.error("\n\x1b[31mError: prev-cli requires Bun runtime\x1b[0m\n\nYou are running with Node.js, but prev-cli uses Bun-specific APIs.\n\n\x1b[33mTo install and run with Bun:\x1b[0m\n\n # Global install (recommended)\n bun i -g prev-cli\n prev-cli dev\n\n # Or local install\n bun add -d prev-cli\n bunx --bun prev-cli dev\n\n\x1b[90mLearn more: https://bun.sh/docs/installation\x1b[0m\n");process.exit(1)}
4
4
  var __defProp = Object.defineProperty;
5
+ var __returnValue = (v) => v;
6
+ function __exportSetter(name, newValue) {
7
+ this[name] = __returnValue.bind(null, newValue);
8
+ }
5
9
  var __export = (target, all) => {
6
10
  for (var name in all)
7
11
  __defProp(target, name, {
8
12
  get: all[name],
9
13
  enumerable: true,
10
14
  configurable: true,
11
- set: (newValue) => all[name] = () => newValue
15
+ set: __exportSetter.bind(all, name)
12
16
  });
13
17
  };
14
18
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
15
19
  var __require = import.meta.require;
16
20
 
21
+ // src/preview-runtime/region-bridge.ts
22
+ var exports_region_bridge = {};
23
+ __export(exports_region_bridge, {
24
+ REGION_BRIDGE_SCRIPT: () => REGION_BRIDGE_SCRIPT
25
+ });
26
+ var REGION_BRIDGE_SCRIPT = `
27
+ (function() {
28
+ // Click handler: delegate clicks on [data-region] elements
29
+ document.addEventListener('click', function(e) {
30
+ var el = e.target;
31
+ while (el && el !== document.body) {
32
+ if (el.getAttribute && el.getAttribute('data-region')) {
33
+ e.preventDefault();
34
+ e.stopPropagation();
35
+ window.parent.postMessage({
36
+ type: 'region-click',
37
+ region: el.getAttribute('data-region')
38
+ }, '*');
39
+ return;
40
+ }
41
+ el = el.parentElement;
42
+ }
43
+ }, true);
44
+
45
+ // Highlight handler: parent sends list of region names to highlight
46
+ window.addEventListener('message', function(e) {
47
+ if (!e.data || e.data.type !== 'highlight-regions') return;
48
+
49
+ // Remove existing highlights
50
+ var existing = document.querySelectorAll('[data-region-highlight]');
51
+ for (var i = 0; i < existing.length; i++) {
52
+ existing[i].removeAttribute('data-region-highlight');
53
+ existing[i].style.cursor = '';
54
+ }
55
+
56
+ var regions = e.data.regions || [];
57
+ if (regions.length === 0) {
58
+ // Report empty rects when cleared
59
+ window.parent.postMessage({ type: 'region-rects', rects: [] }, '*');
60
+ return;
61
+ }
62
+
63
+ for (var j = 0; j < regions.length; j++) {
64
+ var els = document.querySelectorAll('[data-region="' + regions[j] + '"]');
65
+ for (var k = 0; k < els.length; k++) {
66
+ els[k].setAttribute('data-region-highlight', 'true');
67
+ els[k].style.cursor = 'pointer';
68
+ }
69
+ }
70
+
71
+ // Report bounding rects to parent
72
+ reportRegionRects();
73
+ });
74
+
75
+ // Measure and report region rects to parent
76
+ function reportRegionRects() {
77
+ var highlighted = document.querySelectorAll('[data-region-highlight]');
78
+ var rects = [];
79
+ for (var i = 0; i < highlighted.length; i++) {
80
+ var el = highlighted[i];
81
+ var rect = el.getBoundingClientRect();
82
+ rects.push({
83
+ name: el.getAttribute('data-region') || '',
84
+ x: rect.left,
85
+ y: rect.top,
86
+ width: rect.width,
87
+ height: rect.height
88
+ });
89
+ }
90
+ window.parent.postMessage({ type: 'region-rects', rects: rects }, '*');
91
+ }
92
+
93
+ // Debounced re-report on scroll/resize
94
+ var debounceTimer = null;
95
+ function debouncedReport() {
96
+ if (debounceTimer) clearTimeout(debounceTimer);
97
+ debounceTimer = setTimeout(function() {
98
+ var highlighted = document.querySelectorAll('[data-region-highlight]');
99
+ if (highlighted.length > 0) reportRegionRects();
100
+ }, 100);
101
+ }
102
+ window.addEventListener('scroll', debouncedReport, true);
103
+ window.addEventListener('resize', debouncedReport);
104
+
105
+ // Token override handler: parent sends CSS overrides to inject
106
+ window.addEventListener('message', function(e) {
107
+ if (!e.data || e.data.type !== 'token-overrides') return;
108
+ var styleId = 'prev-token-overrides';
109
+ var existing = document.getElementById(styleId);
110
+ if (e.data.css) {
111
+ if (!existing) {
112
+ existing = document.createElement('style');
113
+ existing.id = styleId;
114
+ document.head.appendChild(existing);
115
+ }
116
+ existing.textContent = e.data.css;
117
+ } else if (existing) {
118
+ existing.remove();
119
+ }
120
+ });
121
+ })();
122
+ `;
123
+
17
124
  // src/renderers/react/layout.schema.json
18
125
  var layout_schema_default;
19
126
  var init_layout_schema = __esm(() => {
@@ -440,37 +547,37 @@ function validateTemplate(template) {
440
547
  warnings
441
548
  };
442
549
  }
443
- function validateNode(node, path11, errors, warnings) {
550
+ function validateNode(node, path16, errors, warnings) {
444
551
  if (typeof node === "string") {
445
- validateLeafValue(node, path11, errors, warnings);
552
+ validateLeafValue(node, path16, errors, warnings);
446
553
  return;
447
554
  }
448
555
  if (typeof node === "object" && node !== null) {
449
556
  const nodeObj = node;
450
557
  if (!("type" in nodeObj)) {
451
558
  errors.push({
452
- path: path11,
559
+ path: path16,
453
560
  message: 'Container node must have a "type" field'
454
561
  });
455
562
  return;
456
563
  }
457
564
  if (typeof nodeObj.type !== "string") {
458
565
  errors.push({
459
- path: `${path11}/type`,
566
+ path: `${path16}/type`,
460
567
  message: "Node type must be a string"
461
568
  });
462
569
  return;
463
570
  }
464
571
  if (!isPrimitive(nodeObj.type)) {
465
572
  errors.push({
466
- path: `${path11}/type`,
573
+ path: `${path16}/type`,
467
574
  message: `Node type must be a primitive (start with $): ${nodeObj.type}`
468
575
  });
469
576
  } else {
470
577
  const parseResult = parsePrimitive(nodeObj.type);
471
578
  if (!parseResult.success) {
472
579
  errors.push({
473
- path: `${path11}/type`,
580
+ path: `${path16}/type`,
474
581
  message: parseResult.error.message
475
582
  });
476
583
  }
@@ -479,49 +586,49 @@ function validateNode(node, path11, errors, warnings) {
479
586
  const primitiveType = getPrimitiveType(nodeObj.type);
480
587
  if (primitiveType && !CONTAINER_TYPES.has(primitiveType)) {
481
588
  errors.push({
482
- path: `${path11}/children`,
589
+ path: `${path16}/children`,
483
590
  message: `Primitive ${primitiveType} cannot have children. Only $col, $row, $box support children.`
484
591
  });
485
592
  }
486
593
  if (typeof nodeObj.children !== "object" || nodeObj.children === null) {
487
594
  errors.push({
488
- path: `${path11}/children`,
595
+ path: `${path16}/children`,
489
596
  message: "Children must be an object"
490
597
  });
491
598
  } else {
492
599
  const children = nodeObj.children;
493
600
  for (const [childId, childNode] of Object.entries(children)) {
494
- validateNode(childNode, `${path11}/children/${childId}`, errors, warnings);
601
+ validateNode(childNode, `${path16}/children/${childId}`, errors, warnings);
495
602
  }
496
603
  }
497
604
  }
498
605
  return;
499
606
  }
500
607
  errors.push({
501
- path: path11,
608
+ path: path16,
502
609
  message: `Invalid node type: expected string or object, got ${typeof node}`
503
610
  });
504
611
  }
505
- function validateLeafValue(value, path11, errors, warnings) {
612
+ function validateLeafValue(value, path16, errors, warnings) {
506
613
  if (isPrimitive(value)) {
507
614
  const parseResult = parsePrimitive(value);
508
615
  if (!parseResult.success) {
509
616
  errors.push({
510
- path: path11,
617
+ path: path16,
511
618
  message: parseResult.error.message
512
619
  });
513
620
  }
514
621
  } else if (isRef(value)) {
515
622
  if (!REF_PATTERN.test(value)) {
516
623
  errors.push({
517
- path: path11,
624
+ path: path16,
518
625
  message: `Invalid ref format: ${value}. Expected format: type/id (e.g., components/button)`
519
626
  });
520
627
  }
521
628
  } else {
522
629
  if (!value.match(/^[a-z][a-zA-Z0-9]*$/) && !value.startsWith('"') && !value.startsWith("'")) {
523
630
  warnings.push({
524
- path: path11,
631
+ path: path16,
525
632
  message: `Ambiguous value: ${value}. Use $primitive, type/ref, or a valid prop name.`
526
633
  });
527
634
  }
@@ -1053,12 +1160,6 @@ var init_react = __esm(() => {
1053
1160
  js: `${config.id}.js`
1054
1161
  };
1055
1162
  },
1056
- renderAtlas(config) {
1057
- return {
1058
- html: `<div data-preview-atlas="${config.id}"><!-- React atlas: ${config.title} --></div>`,
1059
- js: `${config.id}.js`
1060
- };
1061
- },
1062
1163
  supportsHMR() {
1063
1164
  return true;
1064
1165
  }
@@ -1182,11 +1283,6 @@ var init_html = __esm(() => {
1182
1283
  html: `<div data-preview-flow="${config.id}" data-step="${currentStep?.id ?? "unknown"}"><!-- HTML flow: ${config.title} --></div>`
1183
1284
  };
1184
1285
  },
1185
- renderAtlas(config) {
1186
- return {
1187
- html: `<div data-preview-atlas="${config.id}"><!-- HTML atlas: ${config.title} --></div>`
1188
- };
1189
- },
1190
1286
  supportsHMR() {
1191
1287
  return false;
1192
1288
  }
@@ -1195,28 +1291,19 @@ var init_html = __esm(() => {
1195
1291
 
1196
1292
  // src/cli.ts
1197
1293
  import { parseArgs } from "util";
1198
- import path13 from "path";
1199
- import { existsSync as existsSync12, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7, rmSync as rmSync4, readFileSync as readFileSync11 } from "fs";
1294
+ import path18 from "path";
1295
+ import { existsSync as existsSync19, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7, rmSync as rmSync4, readFileSync as readFileSync14 } from "fs";
1200
1296
  import { fileURLToPath as fileURLToPath6 } from "url";
1201
1297
 
1202
- // src/vite/start.ts
1203
- import { createServer as createServer2, build as build4, preview } from "vite";
1204
-
1205
- // src/vite/config.ts
1206
- import { createLogger, build as viteBuild } from "vite";
1207
- import react from "@vitejs/plugin-react";
1208
- import mdx from "@mdx-js/rollup";
1209
- import remarkGfm from "remark-gfm";
1210
- import rehypeHighlight from "rehype-highlight";
1211
- import path9 from "path";
1212
- import os from "os";
1213
- import { fileURLToPath as fileURLToPath3 } from "url";
1214
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
1298
+ // src/server/dev.ts
1299
+ import path12 from "path";
1300
+ import { existsSync as existsSync11, readFileSync as readFileSync6, statSync } from "fs";
1301
+ import { fileURLToPath } from "url";
1215
1302
 
1216
- // src/vite/plugins/pages-plugin.ts
1217
- import path2 from "path";
1303
+ // src/server/plugins/virtual-modules.ts
1304
+ import path4 from "path";
1218
1305
 
1219
- // src/vite/pages.ts
1306
+ // src/content/pages.ts
1220
1307
  import fg from "fast-glob";
1221
1308
  import { readFile } from "fs/promises";
1222
1309
  import path from "path";
@@ -1423,291 +1510,207 @@ function buildSidebarTree(pages) {
1423
1510
  return tree;
1424
1511
  }
1425
1512
 
1426
- // src/vite/plugins/pages-plugin.ts
1427
- var VIRTUAL_MODULE_ID = "virtual:prev-pages";
1428
- var RESOLVED_VIRTUAL_MODULE_ID = "\x00" + VIRTUAL_MODULE_ID;
1429
- var VIRTUAL_MODULES_ID = "virtual:prev-page-modules";
1430
- var RESOLVED_VIRTUAL_MODULES_ID = "\x00" + VIRTUAL_MODULES_ID;
1431
- function pagesPlugin(rootDir, options = {}) {
1432
- const { include } = options;
1433
- let cachedPages = null;
1434
- async function getPages() {
1435
- if (!cachedPages) {
1436
- cachedPages = await scanPages(rootDir, { include });
1437
- }
1438
- return cachedPages;
1439
- }
1440
- return {
1441
- name: "prev-pages",
1442
- resolveId(id) {
1443
- if (id === VIRTUAL_MODULE_ID) {
1444
- return RESOLVED_VIRTUAL_MODULE_ID;
1445
- }
1446
- if (id === VIRTUAL_MODULES_ID) {
1447
- return RESOLVED_VIRTUAL_MODULES_ID;
1448
- }
1449
- },
1450
- async load(id) {
1451
- if (id === RESOLVED_VIRTUAL_MODULE_ID) {
1452
- const pages = await getPages();
1453
- const sidebar = buildSidebarTree(pages);
1454
- return `
1455
- export const pages = ${JSON.stringify(pages)};
1456
- export const sidebar = ${JSON.stringify(sidebar)};
1457
- `;
1458
- }
1459
- if (id === RESOLVED_VIRTUAL_MODULES_ID) {
1460
- const pages = await getPages();
1461
- const imports = pages.map((page, i) => {
1462
- const absolutePath = path2.join(rootDir, page.file);
1463
- return `import * as _page${i} from ${JSON.stringify(absolutePath)};`;
1464
- }).join(`
1465
- `);
1466
- const entries = pages.map((page, i) => {
1467
- return ` ${JSON.stringify("/" + page.file)}: _page${i}`;
1468
- }).join(`,
1469
- `);
1470
- return `${imports}
1471
-
1472
- export const pageModules = {
1473
- ${entries}
1474
- };`;
1475
- }
1476
- },
1477
- handleHotUpdate({ file, server }) {
1478
- if (file.endsWith(".mdx") || file.endsWith(".md")) {
1479
- cachedPages = null;
1480
- const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
1481
- const modulesMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULES_ID);
1482
- const mods = [];
1483
- if (mod) {
1484
- server.moduleGraph.invalidateModule(mod);
1485
- mods.push(mod);
1486
- }
1487
- if (modulesMod) {
1488
- server.moduleGraph.invalidateModule(modulesMod);
1489
- mods.push(modulesMod);
1490
- }
1491
- return mods.length > 0 ? mods : undefined;
1492
- }
1493
- }
1494
- };
1495
- }
1496
-
1497
- // src/vite/plugins/entry-plugin.ts
1498
- import path3 from "path";
1499
- import { fileURLToPath } from "url";
1500
- import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
1501
- function findCliRoot() {
1502
- let dir = path3.dirname(fileURLToPath(import.meta.url));
1503
- for (let i = 0;i < 10; i++) {
1504
- const pkgPath = path3.join(dir, "package.json");
1505
- if (existsSync(pkgPath)) {
1506
- try {
1507
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1508
- if (pkg.name === "prev-cli") {
1509
- return dir;
1510
- }
1511
- } catch {}
1512
- }
1513
- const parent = path3.dirname(dir);
1514
- if (parent === dir)
1515
- break;
1516
- dir = parent;
1517
- }
1518
- return path3.dirname(path3.dirname(fileURLToPath(import.meta.url)));
1519
- }
1520
- var cliRoot = findCliRoot();
1521
- var srcRoot = path3.join(cliRoot, "src");
1522
- function getHtml(entryPath, forBuild = false) {
1523
- const scriptSrc = forBuild ? entryPath : `/@fs${entryPath}`;
1524
- return `<!DOCTYPE html>
1525
- <html lang="en">
1526
- <head>
1527
- <meta charset="UTF-8" />
1528
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1529
- <title>Documentation</title>
1530
- <!-- Preconnect to Google Fonts for faster loading -->
1531
- <link rel="preconnect" href="https://fonts.googleapis.com" />
1532
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
1533
- <!-- Preload critical fonts -->
1534
- <link rel="preload" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" as="style" />
1535
- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" />
1536
- </head>
1537
- <body>
1538
- <div id="root"></div>
1539
- <script type="module" src="${scriptSrc}"></script>
1540
- </body>
1541
- </html>`;
1542
- }
1543
- function entryPlugin(rootDir) {
1544
- const entryPath = path3.join(srcRoot, "theme/entry.tsx");
1545
- let tempHtmlPath = null;
1546
- return {
1547
- name: "prev-entry",
1548
- config(config, { command }) {
1549
- if (command === "build" && rootDir) {
1550
- tempHtmlPath = path3.join(rootDir, "index.html");
1551
- writeFileSync(tempHtmlPath, getHtml(entryPath, true));
1552
- const existingInput = config.build?.rollupOptions?.input || {};
1553
- const inputObj = typeof existingInput === "string" ? { _original: existingInput } : Array.isArray(existingInput) ? Object.fromEntries(existingInput.map((f, i) => [`entry${i}`, f])) : existingInput;
1554
- return {
1555
- build: {
1556
- rollupOptions: {
1557
- input: {
1558
- ...inputObj,
1559
- main: tempHtmlPath
1560
- }
1561
- }
1562
- }
1563
- };
1564
- }
1565
- },
1566
- buildEnd() {
1567
- if (tempHtmlPath && existsSync(tempHtmlPath)) {
1568
- unlinkSync(tempHtmlPath);
1569
- tempHtmlPath = null;
1570
- }
1571
- },
1572
- configureServer(server) {
1573
- const html = getHtml(entryPath, false);
1574
- server.middlewares.use(async (req, res, next) => {
1575
- const url = req.url || "/";
1576
- if (url === "/" || !url.includes(".") && !url.startsWith("/@") && !url.startsWith("/_preview")) {
1577
- try {
1578
- const transformed = await server.transformIndexHtml(url, html);
1579
- res.setHeader("Content-Type", "text/html");
1580
- res.statusCode = 200;
1581
- res.end(transformed);
1582
- return;
1583
- } catch (e) {
1584
- console.error("Entry plugin error:", e);
1585
- next();
1586
- return;
1587
- }
1588
- }
1589
- next();
1590
- });
1591
- }
1592
- };
1593
- }
1594
-
1595
- // src/vite/previews.ts
1513
+ // src/content/previews.ts
1596
1514
  import fg2 from "fast-glob";
1597
- import path4 from "path";
1598
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1599
-
1600
- // src/vite/config-parser.ts
1515
+ import path2 from "path";
1601
1516
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1517
+
1518
+ // src/content/config-parser.ts
1519
+ import { existsSync, readFileSync } from "fs";
1602
1520
  import * as yaml from "js-yaml";
1603
1521
 
1604
- // src/vite/preview-types.ts
1522
+ // src/content/preview-types.ts
1605
1523
  import { z } from "zod";
1606
- var configSchema = z.object({
1524
+ var refSchema = z.union([
1525
+ z.string().regex(/^([a-z0-9-]+|(screens|components|flows)\/[a-z0-9-]+)$/),
1526
+ z.object({
1527
+ ref: z.string().regex(/^([a-z0-9-]+|(screens|components|flows)\/[a-z0-9-]+)$/),
1528
+ state: z.string().optional(),
1529
+ options: z.record(z.string(), z.unknown()).optional()
1530
+ })
1531
+ ]);
1532
+ var baseConfigSchema = z.object({
1533
+ kind: z.enum(["component", "screen", "flow"]).optional(),
1534
+ id: z.string().regex(/^[a-z0-9-]+$/).optional(),
1535
+ title: z.string(),
1536
+ description: z.string().optional(),
1607
1537
  tags: z.union([
1608
1538
  z.array(z.string()),
1609
1539
  z.string().transform((s) => [s])
1610
1540
  ]).optional(),
1611
1541
  category: z.string().optional(),
1612
1542
  status: z.enum(["draft", "stable", "deprecated"]).optional(),
1543
+ schemaVersion: z.enum(["1.0", "2.0"]).optional(),
1544
+ order: z.number().optional()
1545
+ });
1546
+ var componentConfigSchema = baseConfigSchema.extend({
1547
+ kind: z.literal("component").optional(),
1548
+ props: z.record(z.string(), z.object({
1549
+ type: z.string().optional(),
1550
+ required: z.boolean().optional(),
1551
+ default: z.unknown().optional(),
1552
+ enum: z.array(z.unknown()).optional()
1553
+ })).optional(),
1554
+ slots: z.record(z.string(), z.object({
1555
+ description: z.string().optional()
1556
+ })).optional()
1557
+ });
1558
+ var screenConfigSchema = baseConfigSchema.extend({
1559
+ kind: z.literal("screen").optional(),
1560
+ states: z.record(z.string(), z.object({
1561
+ description: z.string().optional()
1562
+ })).optional(),
1563
+ layoutByRenderer: z.record(z.string(), z.array(z.unknown())).optional()
1564
+ });
1565
+ var regionGotoSchema = z.object({
1566
+ goto: z.string()
1567
+ });
1568
+ var regionOutcomesSchema = z.object({
1569
+ outcomes: z.record(z.string(), z.object({
1570
+ goto: z.string(),
1571
+ label: z.string().optional()
1572
+ }))
1573
+ });
1574
+ var regionSchema = z.union([regionGotoSchema, regionOutcomesSchema]);
1575
+ var regionNamePattern = /^[a-z0-9-]+$/;
1576
+ var flowStepSchema = z.object({
1577
+ id: z.string().optional(),
1613
1578
  title: z.string().optional(),
1614
1579
  description: z.string().optional(),
1615
- order: z.number().optional()
1580
+ screen: refSchema,
1581
+ state: z.string().optional(),
1582
+ note: z.string().optional(),
1583
+ trigger: z.string().optional(),
1584
+ highlight: z.array(z.string()).optional(),
1585
+ regions: z.record(z.string(), regionSchema).refine((r) => Object.keys(r).every((k) => regionNamePattern.test(k)), { message: "Region names must be lowercase alphanumeric with hyphens" }).optional(),
1586
+ terminal: z.boolean().optional()
1587
+ });
1588
+ var flowTransitionSchema = z.object({
1589
+ from: z.string(),
1590
+ to: z.string(),
1591
+ trigger: z.string()
1616
1592
  });
1593
+ var flowConfigSchema = baseConfigSchema.extend({
1594
+ kind: z.literal("flow").optional(),
1595
+ steps: z.array(flowStepSchema).optional(),
1596
+ transitions: z.array(flowTransitionSchema).optional()
1597
+ });
1598
+ var configSchema = z.union([
1599
+ componentConfigSchema,
1600
+ screenConfigSchema,
1601
+ flowConfigSchema,
1602
+ baseConfigSchema
1603
+ ]);
1617
1604
 
1618
- // src/vite/config-parser.ts
1619
- async function parsePreviewConfig(filePath) {
1620
- if (!existsSync2(filePath)) {
1621
- return null;
1605
+ // src/content/config-parser.ts
1606
+ async function parsePreviewConfig(filePath, options = {}) {
1607
+ const result = {
1608
+ data: null,
1609
+ errors: [],
1610
+ warnings: []
1611
+ };
1612
+ if (!existsSync(filePath)) {
1613
+ result.errors.push(`Config file not found: ${filePath}`);
1614
+ return result;
1622
1615
  }
1623
1616
  try {
1624
- const content = readFileSync2(filePath, "utf-8");
1625
- const parsed = yaml.load(content);
1626
- const result = configSchema.safeParse(parsed);
1627
- if (result.success) {
1628
- return result.data;
1617
+ const content = readFileSync(filePath, "utf-8");
1618
+ let parsed = yaml.load(content);
1619
+ if (!parsed || typeof parsed !== "object") {
1620
+ result.errors.push(`Invalid YAML in ${filePath}`);
1621
+ return result;
1622
+ }
1623
+ if (options.injectId && options.folderName && !parsed.id) {
1624
+ parsed = { ...parsed, id: options.folderName };
1625
+ }
1626
+ if (options.injectKind && options.previewType && !parsed.kind) {
1627
+ parsed = { ...parsed, kind: options.previewType };
1628
+ result.warnings.push(`Config missing 'kind' field, inferred as '${options.previewType}'`);
1629
+ }
1630
+ if (options.folderName && parsed.id && parsed.id !== options.folderName) {
1631
+ result.errors.push(`Config id "${parsed.id}" does not match folder name "${options.folderName}"`);
1632
+ return result;
1633
+ }
1634
+ const parseResult = configSchema.safeParse(parsed);
1635
+ if (parseResult.success) {
1636
+ result.data = parseResult.data;
1637
+ } else {
1638
+ result.errors.push(`Invalid config at ${filePath}: ${parseResult.error.message}`);
1629
1639
  }
1630
- console.warn(`Invalid config at ${filePath}:`, result.error.message);
1631
- return null;
1640
+ return result;
1632
1641
  } catch (err) {
1633
- console.warn(`Error parsing config at ${filePath}:`, err);
1634
- return null;
1642
+ result.errors.push(`Error parsing config at ${filePath}: ${err}`);
1643
+ return result;
1635
1644
  }
1636
1645
  }
1637
- async function parseFlowDefinition(filePath) {
1638
- if (!existsSync2(filePath)) {
1639
- return null;
1646
+ async function parseFlowConfig(configPath, options = {}) {
1647
+ const result = {
1648
+ data: null,
1649
+ errors: [],
1650
+ warnings: []
1651
+ };
1652
+ if (!existsSync(configPath)) {
1653
+ result.errors.push(`Config file not found: ${configPath}`);
1654
+ return result;
1640
1655
  }
1641
1656
  try {
1642
- const content = readFileSync2(filePath, "utf-8");
1643
- const parsed = yaml.load(content);
1644
- if (!parsed.name || !Array.isArray(parsed.steps)) {
1645
- return null;
1657
+ const content = readFileSync(configPath, "utf-8");
1658
+ let parsed = yaml.load(content);
1659
+ if (!parsed || typeof parsed !== "object") {
1660
+ result.errors.push(`Invalid YAML in ${configPath}`);
1661
+ return result;
1662
+ }
1663
+ if (options.injectId && options.folderName && !parsed.id) {
1664
+ parsed = { ...parsed, id: options.folderName };
1665
+ }
1666
+ if (options.injectKind && !parsed.kind) {
1667
+ parsed = { ...parsed, kind: "flow" };
1668
+ result.warnings.push(`Config missing 'kind' field, inferred as 'flow'`);
1669
+ }
1670
+ const parseResult = flowConfigSchema.safeParse(parsed);
1671
+ if (parseResult.success) {
1672
+ result.data = parseResult.data;
1673
+ } else {
1674
+ result.errors.push(`Invalid flow config: ${parseResult.error.message}`);
1646
1675
  }
1647
- return parsed;
1676
+ return result;
1648
1677
  } catch (err) {
1649
- console.warn(`Error parsing flow at ${filePath}:`, err);
1650
- return null;
1678
+ result.errors.push(`Error parsing flow config: ${err}`);
1679
+ return result;
1651
1680
  }
1652
1681
  }
1653
- async function parseAtlasDefinition(filePath) {
1654
- if (!existsSync2(filePath)) {
1682
+ async function parseFlowDefinition(filePath) {
1683
+ if (!existsSync(filePath)) {
1655
1684
  return null;
1656
1685
  }
1657
1686
  try {
1658
- const content = readFileSync2(filePath, "utf-8");
1687
+ const content = readFileSync(filePath, "utf-8");
1659
1688
  const parsed = yaml.load(content);
1660
- if (!parsed.name || !parsed.hierarchy?.root || !parsed.hierarchy?.areas) {
1689
+ if (!parsed.name || !Array.isArray(parsed.steps)) {
1661
1690
  return null;
1662
1691
  }
1663
1692
  return parsed;
1664
1693
  } catch (err) {
1665
- console.warn(`Error parsing atlas at ${filePath}:`, err);
1694
+ console.warn(`Error parsing flow at ${filePath}:`, err);
1666
1695
  return null;
1667
1696
  }
1668
1697
  }
1669
1698
 
1670
- // src/vite/previews.ts
1671
- var PREVIEW_TYPE_FOLDERS = ["components", "screens", "flows", "atlas"];
1699
+ // src/content/previews.ts
1700
+ var PREVIEW_TYPE_FOLDERS = ["components", "screens", "flows"];
1672
1701
  var TYPE_MAP = {
1673
1702
  components: "component",
1674
1703
  screens: "screen",
1675
- flows: "flow",
1676
- atlas: "atlas"
1704
+ flows: "flow"
1677
1705
  };
1678
- async function scanPreviews(rootDir) {
1679
- const previewsDir = path4.join(rootDir, "previews");
1680
- if (!existsSync3(previewsDir)) {
1681
- return [];
1682
- }
1683
- const entryFiles = await fg2.glob("**/{index.html,App.tsx,App.jsx,index.tsx,index.jsx}", {
1684
- cwd: previewsDir,
1685
- ignore: ["node_modules/**"]
1686
- });
1687
- const previewDirs = new Map;
1688
- for (const file of entryFiles) {
1689
- const dir = path4.dirname(file);
1690
- if (!previewDirs.has(dir)) {
1691
- previewDirs.set(dir, file);
1692
- }
1693
- }
1694
- return Array.from(previewDirs.entries()).map(([dir, file]) => {
1695
- const name = dir === "." ? path4.basename(previewsDir) : dir;
1696
- return {
1697
- name,
1698
- route: `/_preview/${name}`,
1699
- htmlPath: path4.join(previewsDir, file)
1700
- };
1701
- });
1702
- }
1703
1706
  async function scanPreviewFiles(previewDir) {
1704
1707
  const files = await fg2.glob("**/*.{tsx,ts,jsx,js,css,json}", {
1705
1708
  cwd: previewDir,
1706
1709
  ignore: ["node_modules/**", "dist/**"]
1707
1710
  });
1708
1711
  return files.map((file) => {
1709
- const content = readFileSync3(path4.join(previewDir, file), "utf-8");
1710
- const ext = path4.extname(file).slice(1);
1712
+ const content = readFileSync2(path2.join(previewDir, file), "utf-8");
1713
+ const ext = path2.extname(file).slice(1);
1711
1714
  return {
1712
1715
  path: file,
1713
1716
  content,
@@ -1725,9 +1728,9 @@ function detectEntry(files) {
1725
1728
  const jsxFile = files.find((f) => f.type === "tsx" || f.type === "jsx");
1726
1729
  return jsxFile?.path || files[0]?.path || "App.tsx";
1727
1730
  }
1728
- async function buildPreviewConfig(previewDir) {
1731
+ async function buildPreviewConfig(previewDir, entryOverride) {
1729
1732
  const files = await scanPreviewFiles(previewDir);
1730
- const entry = detectEntry(files);
1733
+ const entry = entryOverride || detectEntry(files);
1731
1734
  return {
1732
1735
  files,
1733
1736
  entry,
@@ -1735,14 +1738,14 @@ async function buildPreviewConfig(previewDir) {
1735
1738
  };
1736
1739
  }
1737
1740
  async function scanPreviewUnits(rootDir) {
1738
- const previewsDir = path4.join(rootDir, "previews");
1739
- if (!existsSync3(previewsDir)) {
1741
+ const previewsDir = path2.join(rootDir, "previews");
1742
+ if (!existsSync2(previewsDir)) {
1740
1743
  return [];
1741
1744
  }
1742
1745
  const units = [];
1743
1746
  for (const typeFolder of PREVIEW_TYPE_FOLDERS) {
1744
- const typeDir = path4.join(previewsDir, typeFolder);
1745
- if (!existsSync3(typeDir))
1747
+ const typeDir = path2.join(previewsDir, typeFolder);
1748
+ if (!existsSync2(typeDir))
1746
1749
  continue;
1747
1750
  const type = TYPE_MAP[typeFolder];
1748
1751
  const entries = await fg2.glob("*/", {
@@ -1752,18 +1755,32 @@ async function scanPreviewUnits(rootDir) {
1752
1755
  });
1753
1756
  for (const entry of entries) {
1754
1757
  const name = entry.replace(/\/$/, "");
1755
- const unitDir = path4.join(typeDir, name);
1758
+ const unitDir = path2.join(typeDir, name);
1756
1759
  const files = await detectUnitFiles(unitDir, type);
1757
1760
  if (!files.index)
1758
1761
  continue;
1759
- const configPath = existsSync3(path4.join(unitDir, "config.yaml")) ? path4.join(unitDir, "config.yaml") : path4.join(unitDir, "config.yml");
1760
- const config = await parsePreviewConfig(configPath);
1762
+ const configPath = existsSync2(path2.join(unitDir, "config.yaml")) ? path2.join(unitDir, "config.yaml") : path2.join(unitDir, "config.yml");
1763
+ const configResult = await parsePreviewConfig(configPath, {
1764
+ injectId: true,
1765
+ injectKind: true,
1766
+ folderName: name,
1767
+ previewType: type
1768
+ });
1769
+ for (const warning of configResult.warnings) {
1770
+ console.warn(`[prev] Warning in ${typeFolder}/${name}: ${warning}`);
1771
+ }
1772
+ if (configResult.errors.length > 0) {
1773
+ for (const error of configResult.errors) {
1774
+ console.error(`[prev] Error in ${typeFolder}/${name}: ${error}`);
1775
+ }
1776
+ continue;
1777
+ }
1761
1778
  units.push({
1762
1779
  type,
1763
1780
  name,
1764
1781
  path: unitDir,
1765
1782
  route: `/_preview/${typeFolder}/${name}`,
1766
- config,
1783
+ config: configResult.data,
1767
1784
  files
1768
1785
  });
1769
1786
  }
@@ -1773,8 +1790,8 @@ async function scanPreviewUnits(rootDir) {
1773
1790
  async function detectUnitFiles(unitDir, type) {
1774
1791
  const allFiles = await fg2.glob("*", { cwd: unitDir });
1775
1792
  let index;
1776
- if (type === "flow" || type === "atlas") {
1777
- index = allFiles.find((f) => f === "index.yaml" || f === "index.yml");
1793
+ if (type === "flow") {
1794
+ index = allFiles.find((f) => f === "config.yaml" || f === "config.yml");
1778
1795
  } else {
1779
1796
  const priorities = [
1780
1797
  "index.tsx",
@@ -1807,337 +1824,90 @@ async function detectUnitFiles(unitDir, type) {
1807
1824
  return result;
1808
1825
  }
1809
1826
 
1810
- // src/preview-runtime/vendors.ts
1811
- import { build } from "esbuild";
1812
- import { dirname } from "path";
1813
- import { fileURLToPath as fileURLToPath2 } from "url";
1814
- var __dirname2 = dirname(fileURLToPath2(import.meta.url));
1815
- async function buildVendorBundle() {
1816
- try {
1817
- const entryCode = `
1818
- import * as React from 'react'
1819
- import * as ReactDOM from 'react-dom'
1820
- import { createRoot } from 'react-dom/client'
1821
- export { jsx, jsxs, Fragment } from 'react/jsx-runtime'
1822
- export { React, ReactDOM, createRoot }
1823
- // Re-export React hooks as named exports (preview code imports them directly)
1824
- export {
1825
- useState, useEffect, useCallback, useMemo, useRef,
1826
- useContext, useReducer, useLayoutEffect, useInsertionEffect,
1827
- useTransition, useDeferredValue, useId, useSyncExternalStore,
1828
- useImperativeHandle, useDebugValue, memo, forwardRef,
1829
- createContext, createRef, lazy, Suspense, startTransition,
1830
- Children, cloneElement, isValidElement, createElement
1831
- } from 'react'
1832
- export default React
1833
- `;
1834
- const result = await build({
1835
- stdin: {
1836
- contents: entryCode,
1837
- loader: "ts",
1838
- resolveDir: __dirname2
1839
- },
1840
- bundle: true,
1841
- write: false,
1842
- format: "esm",
1843
- target: "es2020",
1844
- minify: true
1845
- });
1846
- const jsFile = result.outputFiles?.find((f) => f.path.endsWith(".js")) || result.outputFiles?.[0];
1847
- if (!jsFile) {
1848
- return { success: false, code: "", error: "No output generated" };
1827
+ // src/config/schema.ts
1828
+ var defaultConfig = {
1829
+ theme: "system",
1830
+ contentWidth: "constrained",
1831
+ hidden: [],
1832
+ include: [],
1833
+ order: {},
1834
+ port: undefined
1835
+ };
1836
+ function validateConfig(raw) {
1837
+ const config = { ...defaultConfig };
1838
+ if (raw && typeof raw === "object") {
1839
+ const obj = raw;
1840
+ if (obj.theme === "light" || obj.theme === "dark" || obj.theme === "system") {
1841
+ config.theme = obj.theme;
1842
+ }
1843
+ if (obj.contentWidth === "constrained" || obj.contentWidth === "full") {
1844
+ config.contentWidth = obj.contentWidth;
1845
+ }
1846
+ if (Array.isArray(obj.hidden)) {
1847
+ config.hidden = obj.hidden.filter((h) => typeof h === "string");
1848
+ }
1849
+ if (Array.isArray(obj.include)) {
1850
+ config.include = obj.include.filter((i) => typeof i === "string");
1851
+ }
1852
+ if (obj.order && typeof obj.order === "object") {
1853
+ config.order = {};
1854
+ for (const [key, value] of Object.entries(obj.order)) {
1855
+ if (Array.isArray(value)) {
1856
+ config.order[key] = value.filter((v) => typeof v === "string");
1857
+ }
1858
+ }
1859
+ }
1860
+ if (typeof obj.port === "number" && obj.port > 0 && obj.port < 65536) {
1861
+ config.port = obj.port;
1849
1862
  }
1850
- return { success: true, code: jsFile.text };
1851
- } catch (err) {
1852
- return {
1853
- success: false,
1854
- code: "",
1855
- error: err instanceof Error ? err.message : String(err)
1856
- };
1857
1863
  }
1864
+ return config;
1858
1865
  }
1859
-
1860
- // src/preview-runtime/build-optimized.ts
1861
- import { build as build2 } from "esbuild";
1862
-
1863
- // src/preview-runtime/tailwind.ts
1864
- var {$ } = globalThis.Bun;
1865
- import { mkdtempSync, mkdirSync, writeFileSync as writeFileSync2, readFileSync as readFileSync4, rmSync, existsSync as existsSync4 } from "fs";
1866
- import { join, dirname as dirname2 } from "path";
1867
- import { tmpdir } from "os";
1868
- function findTailwindBin() {
1869
- try {
1870
- const tailwindPkg = __require.resolve("tailwindcss/package.json");
1871
- const tailwindDir = dirname2(tailwindPkg);
1872
- const binPath = join(tailwindDir, "lib/cli.js");
1873
- if (existsSync4(binPath))
1874
- return binPath;
1875
- } catch {}
1876
- return "bunx tailwindcss@3";
1877
- }
1878
- var tailwindCmd = findTailwindBin();
1879
- async function compileTailwind(files) {
1880
- const tempDir = mkdtempSync(join(tmpdir(), "prev-tailwind-"));
1881
- try {
1882
- for (const file of files) {
1883
- const filePath = join(tempDir, file.path);
1884
- const parentDir = dirname2(filePath);
1885
- mkdirSync(parentDir, { recursive: true });
1886
- writeFileSync2(filePath, file.content);
1887
- }
1888
- const configContent = `
1889
- module.exports = {
1890
- content: [${JSON.stringify(tempDir + "/**/*.{tsx,jsx,ts,js,html}")}],
1891
- }
1892
- `;
1893
- const configPath = join(tempDir, "tailwind.config.cjs");
1894
- writeFileSync2(configPath, configContent);
1895
- const inputCss = `
1896
- @tailwind base;
1897
- @tailwind components;
1898
- @tailwind utilities;
1899
- `;
1900
- const inputPath = join(tempDir, "input.css");
1901
- writeFileSync2(inputPath, inputCss);
1902
- const outputPath = join(tempDir, "output.css");
1903
- if (tailwindCmd.startsWith("bunx")) {
1904
- await $`bunx tailwindcss@3 -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet();
1905
- } else {
1906
- await $`bun ${tailwindCmd} -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet();
1907
- }
1908
- const css = readFileSync4(outputPath, "utf-8");
1909
- return { success: true, css };
1910
- } catch (err) {
1911
- return {
1912
- success: false,
1913
- css: "",
1914
- error: err instanceof Error ? err.message : String(err)
1915
- };
1916
- } finally {
1917
- rmSync(tempDir, { recursive: true, force: true });
1918
- }
1866
+ // src/config/loader.ts
1867
+ import { readFileSync as readFileSync3, existsSync as existsSync3, writeFileSync } from "fs";
1868
+ import path3 from "path";
1869
+ import yaml2 from "js-yaml";
1870
+ function findConfigFile(rootDir) {
1871
+ const yamlPath = path3.join(rootDir, ".prev.yaml");
1872
+ const ymlPath = path3.join(rootDir, ".prev.yml");
1873
+ if (existsSync3(yamlPath))
1874
+ return yamlPath;
1875
+ if (existsSync3(ymlPath))
1876
+ return ymlPath;
1877
+ return null;
1919
1878
  }
1920
-
1921
- // src/preview-runtime/build-optimized.ts
1922
- async function buildOptimizedPreview(config, options) {
1879
+ function loadConfig(rootDir) {
1880
+ const configPath = findConfigFile(rootDir);
1881
+ if (!configPath) {
1882
+ return defaultConfig;
1883
+ }
1923
1884
  try {
1924
- const virtualFs = {};
1925
- for (const file of config.files) {
1926
- const ext = file.path.split(".").pop()?.toLowerCase();
1927
- const loader = ext === "css" ? "css" : ext === "json" ? "json" : ext || "tsx";
1928
- virtualFs[file.path] = { contents: file.content, loader };
1929
- }
1930
- const entryFile = config.files.find((f) => f.path === config.entry);
1931
- if (!entryFile) {
1932
- return { success: false, html: "", css: "", error: `Entry file not found: ${config.entry}` };
1933
- }
1934
- const hasDefaultExport = /export\s+default/.test(entryFile.content);
1935
- const userCssCollected = [];
1936
- const entryCode = hasDefaultExport ? `
1937
- import React, { createRoot } from '${options.vendorPath}'
1938
- import App from './${config.entry}'
1939
- const root = createRoot(document.getElementById('root'))
1940
- root.render(React.createElement(App))
1941
- ` : `import './${config.entry}'`;
1942
- const result = await build2({
1943
- stdin: { contents: entryCode, loader: "tsx", resolveDir: "/" },
1944
- bundle: true,
1945
- write: false,
1946
- format: "esm",
1947
- jsx: "automatic",
1948
- jsxImportSource: "react",
1949
- target: "es2020",
1950
- minify: true,
1951
- plugins: [
1952
- {
1953
- name: "optimized-preview",
1954
- setup(build3) {
1955
- build3.onResolve({ filter: new RegExp(options.vendorPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) }, (args) => {
1956
- return { path: args.path, external: true };
1957
- });
1958
- build3.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
1959
- return { path: options.vendorPath, external: true };
1960
- });
1961
- build3.onResolve({ filter: /^\./ }, (args) => {
1962
- let resolved = args.path.replace(/^\.\//, "");
1963
- if (!resolved.includes(".")) {
1964
- for (const ext of [".tsx", ".ts", ".jsx", ".js", ".css"]) {
1965
- if (virtualFs[resolved + ext]) {
1966
- resolved = resolved + ext;
1967
- break;
1968
- }
1969
- }
1970
- }
1971
- return { path: resolved, namespace: "virtual" };
1972
- });
1973
- build3.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => {
1974
- const file = virtualFs[args.path];
1975
- if (file) {
1976
- if (file.loader === "css") {
1977
- userCssCollected.push(file.contents);
1978
- return { contents: "", loader: "js" };
1979
- }
1980
- return { contents: file.contents, loader: file.loader };
1981
- }
1982
- return { contents: "", loader: "empty" };
1983
- });
1984
- }
1985
- }
1986
- ]
1987
- });
1988
- const jsFile = result.outputFiles?.find((f) => f.path.endsWith(".js")) || result.outputFiles?.[0];
1989
- const jsCode = jsFile?.text || "";
1990
- let css = "";
1991
- if (config.tailwind) {
1992
- const tailwindResult = await compileTailwind(config.files.map((f) => ({ path: f.path, content: f.content })));
1993
- if (tailwindResult.success)
1994
- css = tailwindResult.css;
1995
- }
1996
- let userCss = userCssCollected.join(`
1997
- `);
1998
- if (config.tailwind) {
1999
- userCss = userCss.replace(/@import\s*["']tailwindcss["']\s*;?/g, "");
2000
- }
2001
- const allCss = css + `
2002
- ` + userCss;
2003
- const html = `<!DOCTYPE html>
2004
- <html lang="en">
2005
- <head>
2006
- <meta charset="UTF-8">
2007
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
2008
- <title>Preview</title>
2009
- <style>${allCss}</style>
2010
- <style>body { margin: 0; } #root { min-height: 100vh; }</style>
2011
- </head>
2012
- <body>
2013
- <div id="root"></div>
2014
- <script type="module" src="${options.vendorPath}"></script>
2015
- <script type="module">${jsCode}</script>
2016
- </body>
2017
- </html>`;
2018
- return { success: true, html, css: allCss };
2019
- } catch (err) {
2020
- return { success: false, html: "", css: "", error: err instanceof Error ? err.message : String(err) };
1885
+ const content = readFileSync3(configPath, "utf-8");
1886
+ const raw = yaml2.load(content);
1887
+ return validateConfig(raw);
1888
+ } catch (error) {
1889
+ console.warn(`Warning: Failed to parse ${configPath}:`, error);
1890
+ return defaultConfig;
2021
1891
  }
2022
1892
  }
2023
-
2024
- // src/vite/plugins/previews-plugin.ts
2025
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
2026
- import path5 from "path";
2027
- var VIRTUAL_MODULE_ID2 = "virtual:prev-previews";
2028
- var RESOLVED_VIRTUAL_MODULE_ID2 = "\x00" + VIRTUAL_MODULE_ID2;
2029
- function previewsPlugin(rootDir) {
2030
- let isBuild = false;
2031
- return {
2032
- name: "prev-previews",
2033
- config(_, { command }) {
2034
- isBuild = command === "build";
2035
- },
2036
- resolveId(id) {
2037
- if (id === VIRTUAL_MODULE_ID2) {
2038
- return RESOLVED_VIRTUAL_MODULE_ID2;
2039
- }
2040
- },
2041
- async load(id) {
2042
- if (id === RESOLVED_VIRTUAL_MODULE_ID2) {
2043
- const units = await scanPreviewUnits(rootDir);
2044
- const legacyPreviews = await scanPreviews(rootDir);
2045
- return `
2046
- // Multi-type preview units
2047
- export const previewUnits = ${JSON.stringify(units)};
2048
-
2049
- // Legacy flat previews (backwards compatibility)
2050
- export const previews = ${JSON.stringify(legacyPreviews)};
2051
-
2052
- // Filtering helpers
2053
- export function getByType(type) {
2054
- return previewUnits.filter(u => u.type === type);
2055
- }
2056
-
2057
- export function getByTags(tags) {
2058
- return previewUnits.filter(u =>
2059
- u.config?.tags?.some(t => tags.includes(t))
2060
- );
2061
- }
2062
-
2063
- export function getByCategory(category) {
2064
- return previewUnits.filter(u => u.config?.category === category);
2065
- }
2066
-
2067
- export function getByStatus(status) {
2068
- return previewUnits.filter(u => u.config?.status === status);
1893
+ function saveConfig(rootDir, config) {
1894
+ const configPath = findConfigFile(rootDir) || path3.join(rootDir, ".prev.yaml");
1895
+ const content = yaml2.dump(config, {
1896
+ indent: 2,
1897
+ lineWidth: -1,
1898
+ quotingType: '"',
1899
+ forceQuotes: false
1900
+ });
1901
+ writeFileSync(configPath, content, "utf-8");
2069
1902
  }
2070
- `;
2071
- }
2072
- },
2073
- handleHotUpdate({ file, server }) {
2074
- const previewsPath = path5.sep + "previews" + path5.sep;
2075
- if ((file.includes(previewsPath) || file.includes("/previews/")) && /\.(html|tsx|ts|jsx|js|css|yaml|yml|mdx)$/.test(file)) {
2076
- const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID2);
2077
- if (mod) {
2078
- server.moduleGraph.invalidateModule(mod);
2079
- return [mod];
2080
- }
2081
- }
2082
- },
2083
- async closeBundle() {
2084
- if (!isBuild)
2085
- return;
2086
- const distDir = path5.join(rootDir, "dist");
2087
- const targetDir = path5.join(distDir, "_preview");
2088
- const vendorsDir = path5.join(targetDir, "_vendors");
2089
- const previewsDir = path5.join(rootDir, "previews");
2090
- const oldPreviewsDir = path5.join(distDir, "previews");
2091
- if (existsSync5(oldPreviewsDir)) {
2092
- rmSync2(oldPreviewsDir, { recursive: true });
2093
- }
2094
- if (existsSync5(targetDir)) {
2095
- rmSync2(targetDir, { recursive: true });
2096
- }
2097
- const previews = await scanPreviews(rootDir);
2098
- if (previews.length === 0)
2099
- return;
2100
- console.log(`
2101
- Building ${previews.length} preview(s)...`);
2102
- console.log(" Building shared vendor bundle...");
2103
- mkdirSync2(vendorsDir, { recursive: true });
2104
- const vendorResult = await buildVendorBundle();
2105
- if (!vendorResult.success) {
2106
- console.error(` \u2717 Vendor bundle: ${vendorResult.error}`);
2107
- return;
2108
- }
2109
- writeFileSync3(path5.join(vendorsDir, "runtime.js"), vendorResult.code);
2110
- console.log(" \u2713 _vendors/runtime.js");
2111
- for (const preview of previews) {
2112
- const previewDir = path5.join(previewsDir, preview.name);
2113
- try {
2114
- const config = await buildPreviewConfig(previewDir);
2115
- const depth = preview.name.split("/").length;
2116
- const vendorPath = "../".repeat(depth) + "_vendors/runtime.js";
2117
- const result = await buildOptimizedPreview(config, { vendorPath });
2118
- if (!result.success) {
2119
- console.error(` \u2717 ${preview.name}: ${result.error}`);
2120
- continue;
2121
- }
2122
- const outputDir = path5.join(targetDir, preview.name);
2123
- mkdirSync2(outputDir, { recursive: true });
2124
- writeFileSync3(path5.join(outputDir, "index.html"), result.html);
2125
- console.log(` \u2713 ${preview.name}`);
2126
- } catch (err) {
2127
- console.error(` \u2717 ${preview.name}: ${err}`);
2128
- }
2129
- }
2130
- }
2131
- };
1903
+ function updateOrder(rootDir, pathKey, order) {
1904
+ const config = loadConfig(rootDir);
1905
+ config.order[pathKey] = order;
1906
+ saveConfig(rootDir, config);
2132
1907
  }
2133
-
2134
- // src/vite/plugins/tokens-plugin.ts
2135
- import path6 from "path";
2136
- import { existsSync as existsSync6 } from "fs";
2137
-
2138
1908
  // src/tokens/resolver.ts
2139
1909
  import { load as parseYaml } from "js-yaml";
2140
- import { readFileSync as readFileSync5 } from "fs";
1910
+ import { readFileSync as readFileSync4 } from "fs";
2141
1911
 
2142
1912
  // src/tokens/utils.ts
2143
1913
  function isPlainObject(value) {
@@ -2263,12 +2033,12 @@ function validateTokensConfig(parsed, filePath) {
2263
2033
  return parsed;
2264
2034
  }
2265
2035
  function loadTokens(filePath) {
2266
- const content = readFileSync5(filePath, "utf-8");
2036
+ const content = readFileSync4(filePath, "utf-8");
2267
2037
  const parsed = parseYaml(content);
2268
2038
  return validateTokensConfig(parsed, filePath);
2269
2039
  }
2270
2040
  function loadPartialTokens(filePath) {
2271
- const content = readFileSync5(filePath, "utf-8");
2041
+ const content = readFileSync4(filePath, "utf-8");
2272
2042
  const parsed = parseYaml(content);
2273
2043
  if (!parsed || typeof parsed !== "object") {
2274
2044
  throw new Error(`Invalid tokens file: ${filePath} - must contain a YAML object`);
@@ -2299,951 +2069,1621 @@ function resolveTokens(options = {}) {
2299
2069
  return resolved;
2300
2070
  }
2301
2071
 
2302
- // src/vite/plugins/tokens-plugin.ts
2303
- var VIRTUAL_MODULE_ID3 = "virtual:prev-tokens";
2304
- var RESOLVED_VIRTUAL_MODULE_ID3 = "\x00" + VIRTUAL_MODULE_ID3;
2305
- function tokensPlugin(rootDir) {
2306
- let cachedTokens = null;
2307
- function getTokens() {
2308
- if (!cachedTokens) {
2309
- const userTokensPath = path6.join(rootDir, "previews/tokens.yaml");
2310
- const options = existsSync6(userTokensPath) ? { userTokensPath } : {};
2311
- cachedTokens = resolveTokens(options);
2072
+ // src/server/plugins/virtual-modules.ts
2073
+ import { existsSync as existsSync4 } from "fs";
2074
+ function virtualModulesPlugin(options) {
2075
+ const { rootDir, include } = options;
2076
+ const config = options.config || loadConfig(rootDir);
2077
+ let cachedPages = null;
2078
+ async function getPages() {
2079
+ if (!cachedPages) {
2080
+ cachedPages = await scanPages(rootDir, { include });
2312
2081
  }
2313
- return cachedTokens;
2082
+ return cachedPages;
2314
2083
  }
2315
2084
  return {
2316
- name: "prev-tokens",
2317
- configureServer(server) {
2318
- server.middlewares.use("/_prev/tokens.json", (req, res, next) => {
2319
- if (req.method !== "GET")
2320
- return next();
2321
- try {
2322
- const tokens = getTokens();
2323
- res.setHeader("Content-Type", "application/json");
2324
- res.setHeader("Cache-Control", "no-cache");
2325
- res.end(JSON.stringify(tokens));
2326
- } catch (err) {
2327
- console.error("Error serving tokens:", err);
2328
- res.statusCode = 500;
2329
- res.end(JSON.stringify({ error: String(err) }));
2085
+ name: "prev-virtual-modules",
2086
+ setup(build) {
2087
+ build.onResolve({ filter: /^virtual:prev-/ }, (args) => ({
2088
+ path: args.path,
2089
+ namespace: "prev-virtual"
2090
+ }));
2091
+ build.onLoad({ filter: /.*/, namespace: "prev-virtual" }, async (args) => {
2092
+ switch (args.path) {
2093
+ case "virtual:prev-config":
2094
+ return {
2095
+ contents: `export const config = ${JSON.stringify(config)};`,
2096
+ loader: "js"
2097
+ };
2098
+ case "virtual:prev-pages": {
2099
+ const pages = await getPages();
2100
+ const sidebar = buildSidebarTree(pages);
2101
+ return {
2102
+ contents: `export const pages = ${JSON.stringify(pages)};
2103
+ export const sidebar = ${JSON.stringify(sidebar)};`,
2104
+ loader: "js"
2105
+ };
2106
+ }
2107
+ case "virtual:prev-page-modules": {
2108
+ const pages = await getPages();
2109
+ const imports = pages.map((page, i) => {
2110
+ const absolutePath = path4.join(rootDir, page.file);
2111
+ return `import * as _page${i} from ${JSON.stringify(absolutePath)};`;
2112
+ }).join(`
2113
+ `);
2114
+ const entries = pages.map((page, i) => ` ${JSON.stringify("/" + page.file)}: _page${i}`).join(`,
2115
+ `);
2116
+ return {
2117
+ contents: `${imports}
2118
+ export const pageModules = {
2119
+ ${entries}
2120
+ };`,
2121
+ loader: "js"
2122
+ };
2123
+ }
2124
+ case "virtual:prev-previews": {
2125
+ const units = await scanPreviewUnits(rootDir);
2126
+ return {
2127
+ contents: `
2128
+ export const previewUnits = ${JSON.stringify(units)};
2129
+ export function getByType(type) { return previewUnits.filter(u => u.type === type); }
2130
+ export function getByTags(tags) { return previewUnits.filter(u => u.config?.tags?.some(t => tags.includes(t))); }
2131
+ export function getByCategory(category) { return previewUnits.filter(u => u.config?.category === category); }
2132
+ export function getByStatus(status) { return previewUnits.filter(u => u.config?.status === status); }
2133
+ `,
2134
+ loader: "js"
2135
+ };
2136
+ }
2137
+ case "virtual:prev-tokens": {
2138
+ const userTokensPath = path4.join(rootDir, "previews/tokens.yaml");
2139
+ const tokensOptions = existsSync4(userTokensPath) ? { userTokensPath } : {};
2140
+ const tokens = resolveTokens(tokensOptions);
2141
+ return {
2142
+ contents: `export const tokens = ${JSON.stringify(tokens)};`,
2143
+ loader: "js"
2144
+ };
2145
+ }
2146
+ default:
2147
+ return;
2330
2148
  }
2331
2149
  });
2332
- },
2333
- resolveId(id) {
2334
- if (id === VIRTUAL_MODULE_ID3) {
2335
- return RESOLVED_VIRTUAL_MODULE_ID3;
2336
- }
2337
- },
2338
- load(id) {
2339
- if (id === RESOLVED_VIRTUAL_MODULE_ID3) {
2340
- const tokens = getTokens();
2341
- return `export const tokens = ${JSON.stringify(tokens, null, 2)};`;
2342
- }
2343
- },
2344
- handleHotUpdate({ file, server }) {
2345
- if (file.endsWith("tokens.yaml")) {
2346
- cachedTokens = null;
2347
- const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID3);
2348
- if (mod) {
2349
- server.moduleGraph.invalidateModule(mod);
2350
- return [mod];
2351
- }
2352
- }
2353
2150
  }
2354
2151
  };
2355
2152
  }
2356
2153
 
2357
- // src/vite/plugins/config-plugin.ts
2358
- var VIRTUAL_CONFIG_ID = "virtual:prev-config";
2359
- var RESOLVED_CONFIG_ID = "\x00" + VIRTUAL_CONFIG_ID;
2360
- function createConfigPlugin(config) {
2154
+ // src/server/plugins/mdx.ts
2155
+ function mdxPlugin(options) {
2156
+ const { rootDir } = options;
2361
2157
  return {
2362
- name: "prev-config",
2363
- enforce: "pre",
2364
- resolveId(id) {
2365
- if (id === VIRTUAL_CONFIG_ID) {
2366
- return RESOLVED_CONFIG_ID;
2367
- }
2368
- },
2369
- load(id) {
2370
- if (id === RESOLVED_CONFIG_ID) {
2371
- return `export const config = ${JSON.stringify(config)};`;
2372
- }
2158
+ name: "prev-mdx",
2159
+ setup(build) {
2160
+ build.onLoad({ filter: /\.(md|mdx)$/ }, async (args) => {
2161
+ if (!args.path.startsWith(rootDir))
2162
+ return;
2163
+ const source = await Bun.file(args.path).text();
2164
+ const { compile } = await import("@mdx-js/mdx");
2165
+ const remarkGfm = (await import("remark-gfm")).default;
2166
+ const rehypeHighlight = (await import("rehype-highlight")).default;
2167
+ const compiled = await compile(source, {
2168
+ remarkPlugins: [remarkGfm],
2169
+ rehypePlugins: [rehypeHighlight],
2170
+ providerImportSource: "@mdx-js/react",
2171
+ development: false,
2172
+ jsx: false
2173
+ });
2174
+ return {
2175
+ contents: String(compiled),
2176
+ loader: "jsx"
2177
+ };
2178
+ });
2373
2179
  }
2374
2180
  };
2375
2181
  }
2376
2182
 
2377
- // src/vite/plugins/debug-plugin.ts
2378
- function debugPlugin(collector) {
2183
+ // src/server/plugins/aliases.ts
2184
+ import path5 from "path";
2185
+ import { existsSync as existsSync5 } from "fs";
2186
+ function findNodeModules(cliRoot) {
2187
+ const localNodeModules = path5.join(cliRoot, "node_modules");
2188
+ if (existsSync5(path5.join(localNodeModules, "react"))) {
2189
+ return localNodeModules;
2190
+ }
2191
+ let dir = cliRoot;
2192
+ for (let i = 0;i < 10; i++) {
2193
+ const parent = path5.dirname(dir);
2194
+ if (parent === dir)
2195
+ break;
2196
+ if (path5.basename(parent) === "node_modules" && existsSync5(path5.join(parent, "react"))) {
2197
+ return parent;
2198
+ }
2199
+ dir = parent;
2200
+ }
2201
+ return localNodeModules;
2202
+ }
2203
+ function aliasesPlugin(options) {
2204
+ const { cliRoot } = options;
2205
+ const nodeModules = findNodeModules(cliRoot);
2206
+ const srcRoot = path5.join(cliRoot, "src");
2207
+ const packages = [
2208
+ "react",
2209
+ "react-dom",
2210
+ "@tanstack/react-router",
2211
+ "@mdx-js/react",
2212
+ "mermaid",
2213
+ "dayjs",
2214
+ "@terrastruct/d2",
2215
+ "use-sync-external-store"
2216
+ ];
2379
2217
  return {
2380
- name: "prev-debug",
2381
- enforce: "pre",
2382
- configResolved() {
2383
- collector.startPhase("configResolved");
2384
- },
2385
- buildStart() {
2386
- collector.startPhase("buildStart");
2387
- },
2388
- resolveId(id, importer) {
2389
- const start = performance.now();
2390
- collector.trackFile(id, "resolve", start);
2391
- if (importer) {
2392
- collector.trackFile(`${id} <- ${importer}`, "resolve", start);
2393
- }
2394
- return null;
2395
- },
2396
- load(id) {
2397
- const start = performance.now();
2398
- collector.trackFile(id, "load", start);
2399
- return null;
2400
- },
2401
- transform(_code, id) {
2402
- const start = performance.now();
2403
- collector.trackFile(id, "transform", start);
2404
- return null;
2405
- },
2406
- configureServer(server) {
2407
- collector.startPhase("configureServer");
2408
- server.httpServer?.once("listening", () => {
2409
- collector.startPhase("serverListening");
2410
- });
2411
- server.middlewares.use((req, _res, next) => {
2412
- if (req.url && !req.url.startsWith("/@") && !req.url.includes("__")) {
2413
- collector.trackFile(req.url, "resolve", performance.now());
2218
+ name: "prev-aliases",
2219
+ setup(build) {
2220
+ build.onResolve({ filter: /^@prev\/(ui|theme)/ }, (args) => {
2221
+ const relPath = args.path.replace("@prev/", "");
2222
+ const resolved = path5.join(srcRoot, relPath);
2223
+ try {
2224
+ return { path: __require.resolve(resolved) };
2225
+ } catch {
2226
+ return { path: resolved };
2414
2227
  }
2415
- next();
2416
2228
  });
2417
- },
2418
- handleHotUpdate({ file }) {
2419
- collector.trackFile(file, "transform", performance.now());
2420
- },
2421
- buildEnd() {
2422
- collector.endPhase("build");
2229
+ for (const pkg of packages) {
2230
+ const esc = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2231
+ build.onResolve({ filter: new RegExp(`^${esc}(/|$)`) }, (args) => {
2232
+ try {
2233
+ return { path: __require.resolve(args.path, { paths: [nodeModules] }) };
2234
+ } catch {
2235
+ return;
2236
+ }
2237
+ });
2238
+ }
2423
2239
  }
2424
2240
  };
2425
2241
  }
2426
2242
 
2427
- // src/vite/config.ts
2428
- import * as esbuildLib from "esbuild";
2429
-
2430
- // src/config/schema.ts
2431
- var defaultConfig = {
2432
- theme: "system",
2433
- contentWidth: "constrained",
2434
- hidden: [],
2435
- include: [],
2436
- order: {},
2437
- port: undefined
2438
- };
2439
- function validateConfig(raw) {
2440
- const config = { ...defaultConfig };
2441
- if (raw && typeof raw === "object") {
2442
- const obj = raw;
2443
- if (obj.theme === "light" || obj.theme === "dark" || obj.theme === "system") {
2444
- config.theme = obj.theme;
2445
- }
2446
- if (obj.contentWidth === "constrained" || obj.contentWidth === "full") {
2447
- config.contentWidth = obj.contentWidth;
2448
- }
2449
- if (Array.isArray(obj.hidden)) {
2450
- config.hidden = obj.hidden.filter((h) => typeof h === "string");
2451
- }
2452
- if (Array.isArray(obj.include)) {
2453
- config.include = obj.include.filter((i) => typeof i === "string");
2243
+ // src/server/routes/preview-bundle.ts
2244
+ import path6 from "path";
2245
+ import { existsSync as existsSync6 } from "fs";
2246
+ function createPreviewBundleHandler(rootDir) {
2247
+ return async (req) => {
2248
+ const url = new URL(req.url);
2249
+ if (!url.pathname.startsWith("/_preview-bundle/"))
2250
+ return null;
2251
+ const startTime = performance.now();
2252
+ const previewPath = decodeURIComponent(url.pathname.slice("/_preview-bundle/".length));
2253
+ const previewDir = path6.join(rootDir, "previews", previewPath);
2254
+ if (!previewDir.startsWith(path6.join(rootDir, "previews"))) {
2255
+ return new Response("Forbidden", { status: 403 });
2256
+ }
2257
+ const state = url.searchParams.get("state");
2258
+ let entryFile = "";
2259
+ if (state) {
2260
+ const stateFiles = [`${state}.tsx`, `${state}.jsx`, `${state}.ts`, `${state}.js`];
2261
+ for (const f of stateFiles) {
2262
+ if (existsSync6(path6.join(previewDir, f))) {
2263
+ entryFile = path6.join(previewDir, f);
2264
+ break;
2265
+ }
2266
+ }
2454
2267
  }
2455
- if (obj.order && typeof obj.order === "object") {
2456
- config.order = {};
2457
- for (const [key, value] of Object.entries(obj.order)) {
2458
- if (Array.isArray(value)) {
2459
- config.order[key] = value.filter((v) => typeof v === "string");
2268
+ if (!entryFile) {
2269
+ const defaultFiles = ["index.tsx", "index.ts", "index.jsx", "index.js", "App.tsx", "App.ts"];
2270
+ for (const f of defaultFiles) {
2271
+ if (existsSync6(path6.join(previewDir, f))) {
2272
+ entryFile = path6.join(previewDir, f);
2273
+ break;
2460
2274
  }
2461
2275
  }
2462
2276
  }
2463
- if (typeof obj.port === "number" && obj.port > 0 && obj.port < 65536) {
2464
- config.port = obj.port;
2277
+ if (!entryFile) {
2278
+ return new Response(JSON.stringify({ error: "No entry file found" }), {
2279
+ status: 404,
2280
+ headers: { "Content-Type": "application/json" }
2281
+ });
2465
2282
  }
2466
- }
2467
- return config;
2468
- }
2469
- // src/config/loader.ts
2470
- import { readFileSync as readFileSync6, existsSync as existsSync7, writeFileSync as writeFileSync4 } from "fs";
2471
- import path7 from "path";
2472
- import yaml2 from "js-yaml";
2473
- function findConfigFile(rootDir) {
2474
- const yamlPath = path7.join(rootDir, ".prev.yaml");
2475
- const ymlPath = path7.join(rootDir, ".prev.yml");
2476
- if (existsSync7(yamlPath))
2477
- return yamlPath;
2478
- if (existsSync7(ymlPath))
2479
- return ymlPath;
2480
- return null;
2481
- }
2482
- function loadConfig(rootDir) {
2483
- const configPath = findConfigFile(rootDir);
2484
- if (!configPath) {
2485
- return defaultConfig;
2486
- }
2487
- try {
2488
- const content = readFileSync6(configPath, "utf-8");
2489
- const raw = yaml2.load(content);
2490
- return validateConfig(raw);
2491
- } catch (error) {
2492
- console.warn(`Warning: Failed to parse ${configPath}:`, error);
2493
- return defaultConfig;
2494
- }
2495
- }
2496
- function saveConfig(rootDir, config) {
2497
- const configPath = findConfigFile(rootDir) || path7.join(rootDir, ".prev.yaml");
2498
- const content = yaml2.dump(config, {
2499
- indent: 2,
2500
- lineWidth: -1,
2501
- quotingType: '"',
2502
- forceQuotes: false
2503
- });
2504
- writeFileSync4(configPath, content, "utf-8");
2505
- }
2506
- function updateOrder(rootDir, pathKey, order) {
2507
- const config = loadConfig(rootDir);
2508
- config.order[pathKey] = order;
2509
- saveConfig(rootDir, config);
2283
+ try {
2284
+ const result = await Bun.build({
2285
+ entrypoints: [entryFile],
2286
+ format: "esm",
2287
+ target: "browser",
2288
+ minify: false,
2289
+ jsx: { runtime: "automatic", importSource: "react", development: false },
2290
+ external: ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "@prev/jsx", "fs", "path", "js-yaml"],
2291
+ define: {
2292
+ "process.env.NODE_ENV": '"production"'
2293
+ },
2294
+ plugins: [{
2295
+ name: "esm-sh-aliases",
2296
+ setup(build) {
2297
+ build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, (args) => {
2298
+ const parts = args.path.split("/");
2299
+ const pkg = parts[0];
2300
+ const subpath = parts.slice(1).join("/");
2301
+ const url2 = subpath ? `https://esm.sh/${pkg}@18/${subpath}` : `https://esm.sh/${pkg}@18`;
2302
+ return { path: url2, external: true };
2303
+ });
2304
+ }
2305
+ }]
2306
+ });
2307
+ if (!result.success) {
2308
+ const errors = result.logs.map((l) => l.message).join(`
2309
+ `);
2310
+ return new Response(JSON.stringify({ error: errors }), {
2311
+ status: 500,
2312
+ headers: { "Content-Type": "application/json" }
2313
+ });
2314
+ }
2315
+ const code = result.outputs[0] ? await result.outputs[0].text() : "";
2316
+ const bundleTime = Math.round(performance.now() - startTime);
2317
+ return new Response(code, {
2318
+ headers: {
2319
+ "Content-Type": "application/javascript",
2320
+ "X-Bundle-Time": String(bundleTime)
2321
+ }
2322
+ });
2323
+ } catch (err) {
2324
+ console.error("Bundle error:", err);
2325
+ return new Response(JSON.stringify({ error: String(err) }), {
2326
+ status: 500,
2327
+ headers: { "Content-Type": "application/json" }
2328
+ });
2329
+ }
2330
+ };
2510
2331
  }
2511
- // src/utils/debug.ts
2512
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
2332
+
2333
+ // src/server/routes/preview-config.ts
2513
2334
  import path8 from "path";
2335
+ import { existsSync as existsSync8 } from "fs";
2514
2336
 
2515
- class DebugCollector {
2516
- startTime;
2517
- files = [];
2518
- phases = {};
2519
- currentPhase = "init";
2520
- rootDir;
2521
- constructor(rootDir) {
2522
- this.startTime = performance.now();
2523
- this.rootDir = rootDir;
2524
- this.startPhase("init");
2525
- }
2526
- startPhase(name) {
2527
- const now = performance.now();
2528
- if (this.phases[this.currentPhase] && !this.phases[this.currentPhase].endMs) {
2529
- this.phases[this.currentPhase].endMs = Math.round(now - this.startTime);
2530
- }
2531
- this.currentPhase = name;
2532
- this.phases[name] = { startMs: Math.round(now - this.startTime) };
2533
- }
2534
- endPhase(name) {
2535
- const phaseName = name || this.currentPhase;
2536
- const now = performance.now();
2537
- if (this.phases[phaseName]) {
2538
- this.phases[phaseName].endMs = Math.round(now - this.startTime);
2539
- }
2540
- }
2541
- trackFile(filePath, event, startTime) {
2542
- const now = performance.now();
2543
- this.files.push({
2544
- path: filePath,
2545
- event,
2546
- ms: Math.round(now - startTime),
2547
- phase: this.currentPhase,
2548
- timestamp: Math.round(startTime - this.startTime)
2549
- });
2337
+ // src/content/flow-verifier.ts
2338
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
2339
+ import path7 from "path";
2340
+ function extractRegions(source) {
2341
+ const transpiler = new Bun.Transpiler({ loader: "tsx" });
2342
+ const output = transpiler.transformSync(source);
2343
+ const regions = new Set;
2344
+ const re = /"data-region":\s*"([^"]+)"/g;
2345
+ let match;
2346
+ while ((match = re.exec(output)) !== null) {
2347
+ regions.add(match[1]);
2348
+ }
2349
+ return [...regions];
2350
+ }
2351
+ function verifyFlow(config, rootDir) {
2352
+ const errors = [];
2353
+ const warnings = [];
2354
+ const steps = config.steps ?? [];
2355
+ if (steps.length === 0)
2356
+ return { errors, warnings };
2357
+ const stepIds = new Set;
2358
+ for (const step of steps) {
2359
+ const id = step.id ?? "";
2360
+ if (id && stepIds.has(id)) {
2361
+ errors.push(`Duplicate step ID: "${id}"`);
2362
+ }
2363
+ stepIds.add(id);
2364
+ }
2365
+ const gotoTargets = new Map;
2366
+ for (const step of steps) {
2367
+ const id = step.id ?? "";
2368
+ const screenName = typeof step.screen === "string" ? step.screen : step.screen.ref;
2369
+ const screenDir = path7.join(rootDir, "previews", "screens", screenName);
2370
+ if (!existsSync7(screenDir)) {
2371
+ errors.push(`Screen directory not found: screens/${screenName} (step "${id}")`);
2372
+ continue;
2373
+ }
2374
+ if (step.state) {
2375
+ const stateFile = path7.join(screenDir, `${step.state}.tsx`);
2376
+ const stateFileJsx = path7.join(screenDir, `${step.state}.jsx`);
2377
+ if (!existsSync7(stateFile) && !existsSync7(stateFileJsx)) {
2378
+ errors.push(`State file not found: screens/${screenName}/${step.state}.tsx (step "${id}")`);
2379
+ }
2380
+ }
2381
+ if (step.regions) {
2382
+ const baseName = step.state || "index";
2383
+ const tsxFile = path7.join(screenDir, `${baseName}.tsx`);
2384
+ const jsxFile = path7.join(screenDir, `${baseName}.jsx`);
2385
+ const sourceFile = existsSync7(tsxFile) ? tsxFile : jsxFile;
2386
+ let screenRegions = [];
2387
+ if (existsSync7(sourceFile)) {
2388
+ const source = readFileSync5(sourceFile, "utf-8");
2389
+ screenRegions = extractRegions(source);
2390
+ }
2391
+ const targets = new Set;
2392
+ for (const [regionName, regionDef] of Object.entries(step.regions)) {
2393
+ if (!screenRegions.includes(regionName)) {
2394
+ errors.push(`Region "${regionName}" not found in screens/${screenName}/${baseName}.tsx (step "${id}")`);
2395
+ }
2396
+ if ("goto" in regionDef) {
2397
+ if (!stepIds.has(regionDef.goto)) {
2398
+ errors.push(`Region "${regionName}" in step "${id}" targets nonexistent step "${regionDef.goto}"`);
2399
+ }
2400
+ targets.add(regionDef.goto);
2401
+ } else if ("outcomes" in regionDef) {
2402
+ for (const [, outcome] of Object.entries(regionDef.outcomes)) {
2403
+ if (!stepIds.has(outcome.goto)) {
2404
+ errors.push(`Outcome in region "${regionName}" of step "${id}" targets nonexistent step "${outcome.goto}"`);
2405
+ }
2406
+ targets.add(outcome.goto);
2407
+ }
2408
+ }
2409
+ }
2410
+ gotoTargets.set(id, targets);
2411
+ }
2412
+ if (!step.regions && !step.terminal && step !== steps[0]) {
2413
+ warnings.push(`Step "${id}" is a dead-end (no regions and not terminal)`);
2414
+ }
2415
+ }
2416
+ const firstId = steps[0]?.id ?? "";
2417
+ const reachable = new Set([firstId]);
2418
+ const queue = [firstId];
2419
+ while (queue.length > 0) {
2420
+ const current = queue.shift();
2421
+ const targets = gotoTargets.get(current);
2422
+ if (targets) {
2423
+ for (const target of targets) {
2424
+ if (!reachable.has(target)) {
2425
+ reachable.add(target);
2426
+ queue.push(target);
2427
+ }
2428
+ }
2429
+ }
2550
2430
  }
2551
- generateSummary() {
2552
- const byDirectory = {};
2553
- const byEvent = { resolve: 0, load: 0, transform: 0 };
2554
- const fileTimeAccum = {};
2555
- for (const file of this.files) {
2556
- byEvent[file.event] = (byEvent[file.event] || 0) + 1;
2557
- fileTimeAccum[file.path] = (fileTimeAccum[file.path] || 0) + file.ms;
2558
- const relativePath = file.path.startsWith(this.rootDir) ? file.path.slice(this.rootDir.length) : file.path;
2559
- let dir;
2560
- if (relativePath.includes("node_modules")) {
2561
- const nmIndex = relativePath.indexOf("node_modules");
2562
- const prefix = relativePath.slice(0, nmIndex + "node_modules".length);
2563
- const afterNm = relativePath.slice(nmIndex + "node_modules/".length);
2564
- const pkgName = afterNm.split("/")[0].startsWith("@") ? afterNm.split("/").slice(0, 2).join("/") : afterNm.split("/")[0];
2565
- dir = `${prefix}/${pkgName}`;
2566
- } else {
2567
- const parts = relativePath.split("/").filter(Boolean);
2568
- dir = parts.length > 1 ? `/${parts[0]}` : "/";
2431
+ if (config.transitions) {
2432
+ for (const t of config.transitions) {
2433
+ if (reachable.has(t.from) && !reachable.has(t.to)) {
2434
+ reachable.add(t.to);
2435
+ const q2 = [t.to];
2436
+ while (q2.length > 0) {
2437
+ const c = q2.shift();
2438
+ const tgts = gotoTargets.get(c);
2439
+ if (tgts) {
2440
+ for (const tgt of tgts) {
2441
+ if (!reachable.has(tgt)) {
2442
+ reachable.add(tgt);
2443
+ q2.push(tgt);
2444
+ }
2445
+ }
2446
+ }
2447
+ }
2569
2448
  }
2570
- byDirectory[dir] = (byDirectory[dir] || 0) + 1;
2571
2449
  }
2572
- const slowest = Object.entries(fileTimeAccum).map(([path9, totalMs]) => ({ path: path9, totalMs })).sort((a, b) => b.totalMs - a.totalMs).slice(0, 20);
2573
- return {
2574
- totalFiles: this.files.length,
2575
- byDirectory,
2576
- byEvent,
2577
- slowest
2578
- };
2579
2450
  }
2580
- writeReport() {
2581
- const now = performance.now();
2582
- const totalStartupMs = Math.round(now - this.startTime);
2583
- this.endPhase();
2584
- const report = {
2585
- timestamp: new Date().toISOString(),
2586
- totalStartupMs,
2587
- phases: this.phases,
2588
- files: this.files,
2589
- summary: this.generateSummary()
2590
- };
2591
- const debugDir = path8.join(this.rootDir, ".prev-debug");
2592
- mkdirSync3(debugDir, { recursive: true });
2593
- const date = new Date;
2594
- const filename = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}-${String(date.getHours()).padStart(2, "0")}-${String(date.getMinutes()).padStart(2, "0")}-${String(date.getSeconds()).padStart(2, "0")}.json`;
2595
- const filepath = path8.join(debugDir, filename);
2596
- writeFileSync5(filepath, JSON.stringify(report, null, 2));
2597
- return filepath;
2451
+ for (const step of steps) {
2452
+ const id = step.id ?? "";
2453
+ if (id && !reachable.has(id) && step !== steps[0]) {
2454
+ warnings.push(`Step "${id}" is unreachable (orphan)`);
2455
+ }
2598
2456
  }
2599
- }
2600
- var currentCollector = null;
2601
- function createDebugCollector(rootDir) {
2602
- currentCollector = new DebugCollector(rootDir);
2603
- return currentCollector;
2604
- }
2605
- function getDebugCollector() {
2606
- return currentCollector;
2457
+ return { errors, warnings };
2607
2458
  }
2608
2459
 
2609
- // src/vite/config.ts
2610
- var TYPE_SINGULAR = {
2611
- components: "component",
2612
- screens: "screen",
2613
- flows: "flow",
2614
- atlas: "atlas"
2615
- };
2616
- function createFriendlyLogger() {
2617
- const logger = createLogger("info", { allowClearScreen: false });
2618
- const hiddenPatterns = [
2619
- /Re-optimizing dependencies/,
2620
- /new dependencies optimized/,
2621
- /optimized dependencies changed/,
2622
- /Dependencies bundled/,
2623
- /Pre-bundling dependencies/,
2624
- /\(client\) \u2728/
2625
- ];
2626
- const transformMessage = (msg) => {
2627
- for (const pattern of hiddenPatterns) {
2628
- if (pattern.test(msg))
2629
- return null;
2630
- }
2631
- if (msg.includes("hmr update")) {
2632
- const match = msg.match(/hmr update (.+)/);
2633
- if (match) {
2634
- return ` \u21BB Updated: ${match[1]}`;
2460
+ // src/server/routes/preview-config.ts
2461
+ function createPreviewConfigHandler(rootDir) {
2462
+ return async (req) => {
2463
+ const url = new URL(req.url);
2464
+ if (!url.pathname.startsWith("/_preview-config/"))
2465
+ return null;
2466
+ const pathAfterConfig = decodeURIComponent(url.pathname.slice("/_preview-config/".length));
2467
+ const previewsDir = path8.join(rootDir, "previews");
2468
+ const multiTypeMatch = pathAfterConfig.match(/^(components|screens|flows)\/(.+)$/);
2469
+ if (multiTypeMatch) {
2470
+ const [, type, name] = multiTypeMatch;
2471
+ const previewDir = path8.join(previewsDir, type, name);
2472
+ if (!previewDir.startsWith(previewsDir)) {
2473
+ return new Response("Forbidden", { status: 403 });
2474
+ }
2475
+ if (existsSync8(previewDir)) {
2476
+ try {
2477
+ if (type === "flows") {
2478
+ const newConfigYaml = path8.join(previewDir, "config.yaml");
2479
+ const newConfigYml = path8.join(previewDir, "config.yml");
2480
+ const newConfigPath = existsSync8(newConfigYaml) ? newConfigYaml : newConfigYml;
2481
+ if (existsSync8(newConfigPath)) {
2482
+ const result = await parseFlowConfig(newConfigPath, {
2483
+ injectId: true,
2484
+ injectKind: true,
2485
+ folderName: name
2486
+ });
2487
+ if (result.data) {
2488
+ const verification = verifyFlow(result.data, rootDir);
2489
+ return Response.json({
2490
+ name: result.data.title || name,
2491
+ description: result.data.description,
2492
+ steps: result.data.steps || [],
2493
+ _verification: verification
2494
+ });
2495
+ }
2496
+ }
2497
+ const configPathYaml = path8.join(previewDir, "index.yaml");
2498
+ const configPathYml = path8.join(previewDir, "index.yml");
2499
+ const configPath = existsSync8(configPathYaml) ? configPathYaml : configPathYml;
2500
+ if (existsSync8(configPath)) {
2501
+ const flow = await parseFlowDefinition(configPath);
2502
+ if (flow) {
2503
+ return Response.json(flow);
2504
+ }
2505
+ }
2506
+ } else {
2507
+ const config = await buildPreviewConfig(previewDir);
2508
+ return Response.json(config);
2509
+ }
2510
+ return Response.json({ error: "Invalid config format" }, { status: 400 });
2511
+ } catch (err) {
2512
+ console.error("Error building preview config:", err);
2513
+ return Response.json({ error: String(err) }, { status: 500 });
2514
+ }
2515
+ }
2516
+ } else {
2517
+ const previewDir = path8.resolve(previewsDir, pathAfterConfig);
2518
+ if (!previewDir.startsWith(previewsDir)) {
2519
+ return new Response("Forbidden", { status: 403 });
2520
+ }
2521
+ if (existsSync8(previewDir)) {
2522
+ try {
2523
+ const config = await buildPreviewConfig(previewDir);
2524
+ return Response.json(config);
2525
+ } catch (err) {
2526
+ console.error("Error building preview config:", err);
2527
+ return Response.json({ error: String(err) }, { status: 500 });
2528
+ }
2635
2529
  }
2636
2530
  }
2637
- if (msg.includes("page reload")) {
2638
- return " \u21BB Page reloaded";
2531
+ return null;
2532
+ };
2533
+ }
2534
+
2535
+ // src/server/routes/jsx-bundle.ts
2536
+ import path9 from "path";
2537
+ function createJsxBundleHandler(cliRoot) {
2538
+ let cachedBundle = null;
2539
+ const srcRoot = path9.join(cliRoot, "src");
2540
+ return async (req) => {
2541
+ const url = new URL(req.url);
2542
+ if (url.pathname !== "/_prev/jsx.js")
2543
+ return null;
2544
+ try {
2545
+ if (!cachedBundle) {
2546
+ const jsxEntry = path9.join(srcRoot, "jsx/index.ts");
2547
+ const result = await Bun.build({
2548
+ entrypoints: [jsxEntry],
2549
+ format: "esm",
2550
+ target: "browser",
2551
+ minify: false,
2552
+ jsx: { runtime: "automatic", importSource: "react", development: false },
2553
+ external: ["react", "react-dom", "react/jsx-runtime", "zod"],
2554
+ define: {
2555
+ "process.env.NODE_ENV": '"production"'
2556
+ }
2557
+ });
2558
+ if (result.success && result.outputs[0]) {
2559
+ let code = await result.outputs[0].text();
2560
+ code = code.replace(/from\s+['"]react\/jsx-runtime['"]/g, 'from "https://esm.sh/react@18/jsx-runtime"').replace(/from\s+['"]react['"]/g, 'from "https://esm.sh/react@18"').replace(/from\s+['"]react-dom['"]/g, 'from "https://esm.sh/react-dom@18"').replace(/from\s+['"]zod['"]/g, 'from "https://esm.sh/zod"');
2561
+ cachedBundle = code;
2562
+ }
2563
+ }
2564
+ if (cachedBundle) {
2565
+ return new Response(cachedBundle, {
2566
+ headers: {
2567
+ "Content-Type": "application/javascript",
2568
+ "Cache-Control": "no-cache"
2569
+ }
2570
+ });
2571
+ }
2572
+ return new Response("Failed to bundle jsx primitives", { status: 500 });
2573
+ } catch (err) {
2574
+ console.error("Error bundling jsx:", err);
2575
+ return new Response(String(err), { status: 500 });
2639
2576
  }
2640
- return msg;
2641
2577
  };
2642
- return {
2643
- ...logger,
2644
- info(msg, options) {
2645
- const transformed = transformMessage(msg);
2646
- if (transformed)
2647
- logger.info(transformed, options);
2648
- },
2649
- warn(msg, options) {
2650
- if (!hiddenPatterns.some((p) => p.test(msg))) {
2651
- logger.warn(msg, options);
2578
+ }
2579
+
2580
+ // src/server/routes/component-bundle.ts
2581
+ import path10 from "path";
2582
+ import { existsSync as existsSync9 } from "fs";
2583
+ function createComponentBundleHandler(rootDir) {
2584
+ const componentCache = new Map;
2585
+ return async (req) => {
2586
+ const url = new URL(req.url);
2587
+ const match = url.pathname.match(/^\/_prev\/components\/([^/]+)\.js$/);
2588
+ if (!match)
2589
+ return null;
2590
+ const componentName = match[1];
2591
+ const componentEntry = path10.join(rootDir, "previews/components", componentName, "index.tsx");
2592
+ if (!existsSync9(componentEntry)) {
2593
+ return new Response(`Component not found: ${componentName}`, { status: 404 });
2594
+ }
2595
+ try {
2596
+ let bundledCode = componentCache.get(componentName);
2597
+ if (!bundledCode) {
2598
+ const result = await Bun.build({
2599
+ entrypoints: [componentEntry],
2600
+ format: "esm",
2601
+ target: "browser",
2602
+ minify: false,
2603
+ jsx: { runtime: "automatic", importSource: "react", development: false },
2604
+ external: ["react", "react-dom", "react/jsx-runtime", "@prev/jsx"],
2605
+ define: {
2606
+ "process.env.NODE_ENV": '"production"'
2607
+ }
2608
+ });
2609
+ if (result.success && result.outputs[0]) {
2610
+ let code = await result.outputs[0].text();
2611
+ const origin = req.headers.get("origin") || "";
2612
+ code = code.replace(/from\s+['"]react\/jsx-runtime['"]/g, 'from "https://esm.sh/react@18/jsx-runtime"').replace(/from\s+['"]react['"]/g, 'from "https://esm.sh/react@18"').replace(/from\s+['"]react-dom['"]/g, 'from "https://esm.sh/react-dom@18"').replace(/from\s+['"]@prev\/jsx['"]/g, `from "${origin}/_prev/jsx.js"`);
2613
+ bundledCode = code;
2614
+ componentCache.set(componentName, code);
2615
+ }
2652
2616
  }
2653
- },
2654
- warnOnce(msg, options) {
2655
- if (!hiddenPatterns.some((p) => p.test(msg))) {
2656
- logger.warnOnce(msg, options);
2617
+ if (bundledCode) {
2618
+ return new Response(bundledCode, {
2619
+ headers: {
2620
+ "Content-Type": "application/javascript",
2621
+ "Cache-Control": "no-cache"
2622
+ }
2623
+ });
2657
2624
  }
2658
- },
2659
- error(msg, options) {
2660
- logger.error(msg, options);
2661
- },
2662
- clearScreen() {},
2663
- hasErrorLogged(err) {
2664
- return logger.hasErrorLogged(err);
2665
- },
2666
- hasWarned: false
2625
+ return new Response(`Failed to bundle component: ${componentName}`, { status: 500 });
2626
+ } catch (err) {
2627
+ console.error(`Error bundling component ${componentName}:`, err);
2628
+ return new Response(String(err), { status: 500 });
2629
+ }
2667
2630
  };
2668
2631
  }
2669
- function findCliRoot2() {
2670
- let dir = path9.dirname(fileURLToPath3(import.meta.url));
2632
+
2633
+ // src/server/routes/tokens.ts
2634
+ import path11 from "path";
2635
+ import { existsSync as existsSync10 } from "fs";
2636
+ function createTokensHandler(rootDir) {
2637
+ let cachedTokens = null;
2638
+ return async (req) => {
2639
+ const url = new URL(req.url);
2640
+ if (url.pathname !== "/_prev/tokens.json")
2641
+ return null;
2642
+ try {
2643
+ if (!cachedTokens) {
2644
+ const userTokensPath = path11.join(rootDir, "previews/tokens.yaml");
2645
+ const options = existsSync10(userTokensPath) ? { userTokensPath } : {};
2646
+ cachedTokens = resolveTokens(options);
2647
+ }
2648
+ return Response.json(cachedTokens, {
2649
+ headers: { "Cache-Control": "no-cache" }
2650
+ });
2651
+ } catch (err) {
2652
+ console.error("Error serving tokens:", err);
2653
+ return Response.json({ error: String(err) }, { status: 500 });
2654
+ }
2655
+ };
2656
+ }
2657
+
2658
+ // src/server/routes/og-image.ts
2659
+ var TYPE_ICONS = {
2660
+ component: "\u25C7",
2661
+ screen: "\u25A3",
2662
+ flow: "\u21E2"
2663
+ };
2664
+ var TYPE_COLORS = {
2665
+ component: "#6366f1",
2666
+ screen: "#8b5cf6",
2667
+ flow: "#06b6d4"
2668
+ };
2669
+ function generateOgImage(opts) {
2670
+ const icon = TYPE_ICONS[opts.type] || "\u25C7";
2671
+ const color = TYPE_COLORS[opts.type] || "#6366f1";
2672
+ const subtitle = opts.step ? `Step: ${opts.step}` : opts.state ? `State: ${opts.state}` : opts.type;
2673
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
2674
+ <defs>
2675
+ <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
2676
+ <stop offset="0%" style="stop-color:#1a1a2e"/>
2677
+ <stop offset="100%" style="stop-color:#16213e"/>
2678
+ </linearGradient>
2679
+ </defs>
2680
+ <rect width="1200" height="630" fill="url(#bg)"/>
2681
+ <rect x="60" y="60" width="1080" height="510" rx="24" fill="#1e1e3f" stroke="${color}" stroke-width="2" opacity="0.6"/>
2682
+ <text x="120" y="260" font-family="system-ui,sans-serif" font-size="96" fill="${color}">${icon}</text>
2683
+ <text x="120" y="380" font-family="system-ui,sans-serif" font-size="48" font-weight="700" fill="#e2e8f0">${escapeXml(opts.title)}</text>
2684
+ <text x="120" y="430" font-family="system-ui,sans-serif" font-size="24" fill="#94a3b8">${escapeXml(subtitle)}</text>
2685
+ ${opts.description ? `<text x="120" y="480" font-family="system-ui,sans-serif" font-size="20" fill="#64748b">${escapeXml(opts.description.slice(0, 80))}</text>` : ""}
2686
+ <text x="1080" y="530" font-family="system-ui,sans-serif" font-size="18" fill="#475569" text-anchor="end">prev-cli</text>
2687
+ </svg>`;
2688
+ }
2689
+ function escapeXml(s) {
2690
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2691
+ }
2692
+ function handleOgImageRequest(req, previewUnits) {
2693
+ const url = new URL(req.url);
2694
+ if (!url.pathname.startsWith("/_og/"))
2695
+ return null;
2696
+ const path12 = url.pathname.slice(5);
2697
+ const state = url.searchParams.get("state") || undefined;
2698
+ const step = url.searchParams.get("step") || undefined;
2699
+ const unit = previewUnits.find((u) => `${u.type}s/${u.name}` === path12 || u.name === path12);
2700
+ const title = unit?.config?.title || path12.split("/").pop() || "Preview";
2701
+ const type = unit?.type || path12.split("/")[0]?.replace(/s$/, "") || "preview";
2702
+ const svg = generateOgImage({
2703
+ title,
2704
+ type,
2705
+ state,
2706
+ step,
2707
+ description: unit?.config?.description
2708
+ });
2709
+ return new Response(svg, {
2710
+ headers: {
2711
+ "Content-Type": "image/svg+xml",
2712
+ "Cache-Control": "public, max-age=3600"
2713
+ }
2714
+ });
2715
+ }
2716
+
2717
+ // src/server/dev.ts
2718
+ function findCliRoot() {
2719
+ let dir = path12.dirname(fileURLToPath(import.meta.url));
2671
2720
  for (let i = 0;i < 10; i++) {
2672
- const pkgPath = path9.join(dir, "package.json");
2673
- if (existsSync8(pkgPath)) {
2721
+ const pkgPath = path12.join(dir, "package.json");
2722
+ if (existsSync11(pkgPath)) {
2674
2723
  try {
2675
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
2676
- if (pkg.name === "prev-cli") {
2724
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
2725
+ if (pkg.name === "prev-cli")
2677
2726
  return dir;
2678
- }
2679
2727
  } catch {}
2680
2728
  }
2681
- const parent = path9.dirname(dir);
2729
+ const parent = path12.dirname(dir);
2682
2730
  if (parent === dir)
2683
2731
  break;
2684
2732
  dir = parent;
2685
2733
  }
2686
- return path9.dirname(path9.dirname(fileURLToPath3(import.meta.url)));
2734
+ return path12.dirname(path12.dirname(fileURLToPath(import.meta.url)));
2687
2735
  }
2688
- function findNodeModules(cliRoot2) {
2689
- const localNodeModules = path9.join(cliRoot2, "node_modules");
2690
- if (existsSync8(path9.join(localNodeModules, "react"))) {
2691
- return localNodeModules;
2736
+ var cliRoot = findCliRoot();
2737
+ var srcRoot = path12.join(cliRoot, "src");
2738
+ async function buildThemeApp(rootDir, include, config) {
2739
+ const entryPath = path12.join(srcRoot, "theme/entry.tsx");
2740
+ const plugins = [
2741
+ virtualModulesPlugin({ rootDir, include, config }),
2742
+ mdxPlugin({ rootDir }),
2743
+ aliasesPlugin({ cliRoot })
2744
+ ];
2745
+ const result = await Bun.build({
2746
+ entrypoints: [entryPath],
2747
+ format: "esm",
2748
+ target: "browser",
2749
+ plugins,
2750
+ jsx: { runtime: "automatic", importSource: "react" },
2751
+ define: {
2752
+ "import.meta.env.DEV": "true",
2753
+ "import.meta.env.BASE_URL": '"/"',
2754
+ "process.env.NODE_ENV": '"development"'
2755
+ }
2756
+ });
2757
+ if (!result.success) {
2758
+ const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message);
2759
+ return { js: "", css: "", success: false, errors };
2760
+ }
2761
+ const jsOutput = result.outputs.find((o) => o.path.endsWith(".js"));
2762
+ const cssOutput = result.outputs.find((o) => o.path.endsWith(".css"));
2763
+ return {
2764
+ js: jsOutput ? await jsOutput.text() : "",
2765
+ css: cssOutput ? await cssOutput.text() : "",
2766
+ success: true,
2767
+ errors: []
2768
+ };
2769
+ }
2770
+ var HTML_SHELL = `<!DOCTYPE html>
2771
+ <html lang="en">
2772
+ <head>
2773
+ <meta charset="UTF-8" />
2774
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2775
+ <title>Documentation</title>
2776
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
2777
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
2778
+ <link rel="preload" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" as="style" />
2779
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" />
2780
+ <link rel="stylesheet" href="/__prev/app.css" />
2781
+ </head>
2782
+ <body>
2783
+ <div id="root"></div>
2784
+ <script type="module" src="/__prev/app.js"></script>
2785
+ <script>
2786
+ if (typeof EventSource !== 'undefined') {
2787
+ var es = new EventSource('/__prev/events');
2788
+ es.onmessage = function() { location.reload(); };
2789
+ }
2790
+ </script>
2791
+ </body>
2792
+ </html>`;
2793
+ async function startDevServer(options) {
2794
+ const { rootDir, port, include } = options;
2795
+ const config = options.config || loadConfig(rootDir);
2796
+ console.log(" Building theme...");
2797
+ let appBundle = await buildThemeApp(rootDir, include, config);
2798
+ if (!appBundle.success) {
2799
+ console.error(" Build errors:", appBundle.errors.join(`
2800
+ `));
2801
+ } else {
2802
+ console.log(" \u2713 Theme built");
2803
+ }
2804
+ const sseControllers = new Set;
2805
+ const encoder = new TextEncoder;
2806
+ function notifyReload() {
2807
+ const msg = encoder.encode(`data: reload
2808
+
2809
+ `);
2810
+ for (const ctrl of sseControllers) {
2811
+ try {
2812
+ ctrl.enqueue(msg);
2813
+ } catch {
2814
+ sseControllers.delete(ctrl);
2815
+ }
2816
+ }
2692
2817
  }
2693
- let dir = cliRoot2;
2818
+ const previewBundleHandler = createPreviewBundleHandler(rootDir);
2819
+ const previewConfigHandler = createPreviewConfigHandler(rootDir);
2820
+ const jsxBundleHandler = createJsxBundleHandler(cliRoot);
2821
+ const componentBundleHandler = createComponentBundleHandler(rootDir);
2822
+ const tokensHandler = createTokensHandler(rootDir);
2823
+ const previewRuntimePath = path12.join(srcRoot, "preview-runtime/fast-template.html");
2824
+ const server = Bun.serve({
2825
+ port,
2826
+ async fetch(req) {
2827
+ const url = new URL(req.url);
2828
+ const pathname = url.pathname;
2829
+ if (pathname === "/__prev/events") {
2830
+ let ctrl;
2831
+ const stream = new ReadableStream({
2832
+ start(controller) {
2833
+ ctrl = controller;
2834
+ sseControllers.add(controller);
2835
+ },
2836
+ cancel() {
2837
+ sseControllers.delete(ctrl);
2838
+ }
2839
+ });
2840
+ return new Response(stream, {
2841
+ headers: {
2842
+ "Content-Type": "text/event-stream",
2843
+ "Cache-Control": "no-cache"
2844
+ }
2845
+ });
2846
+ }
2847
+ if (pathname === "/__prev/app.js") {
2848
+ return new Response(appBundle.js, {
2849
+ headers: { "Content-Type": "application/javascript" }
2850
+ });
2851
+ }
2852
+ if (pathname === "/__prev/app.css") {
2853
+ return new Response(appBundle.css, {
2854
+ headers: { "Content-Type": "text/css" }
2855
+ });
2856
+ }
2857
+ if (pathname === "/__prev/config" && req.method === "POST") {
2858
+ try {
2859
+ const body = await req.json();
2860
+ updateOrder(rootDir, body.path, body.order);
2861
+ return Response.json({ success: true });
2862
+ } catch (e) {
2863
+ return Response.json({ error: String(e) }, { status: 400 });
2864
+ }
2865
+ }
2866
+ const bundleResponse = await previewBundleHandler(req);
2867
+ if (bundleResponse)
2868
+ return bundleResponse;
2869
+ const configResponse = await previewConfigHandler(req);
2870
+ if (configResponse)
2871
+ return configResponse;
2872
+ const jsxResponse = await jsxBundleHandler(req);
2873
+ if (jsxResponse)
2874
+ return jsxResponse;
2875
+ const componentResponse = await componentBundleHandler(req);
2876
+ if (componentResponse)
2877
+ return componentResponse;
2878
+ const tokensResponse = await tokensHandler(req);
2879
+ if (tokensResponse)
2880
+ return tokensResponse;
2881
+ const ogResponse = handleOgImageRequest(req, []);
2882
+ if (ogResponse)
2883
+ return ogResponse;
2884
+ if (pathname === "/_prev/region-bridge.js") {
2885
+ const { REGION_BRIDGE_SCRIPT: REGION_BRIDGE_SCRIPT2 } = await Promise.resolve().then(() => exports_region_bridge);
2886
+ return new Response(REGION_BRIDGE_SCRIPT2, {
2887
+ headers: { "Content-Type": "application/javascript" }
2888
+ });
2889
+ }
2890
+ if (pathname === "/_preview-runtime") {
2891
+ if (existsSync11(previewRuntimePath)) {
2892
+ const html = readFileSync6(previewRuntimePath, "utf-8");
2893
+ return new Response(html, {
2894
+ headers: { "Content-Type": "text/html" }
2895
+ });
2896
+ }
2897
+ }
2898
+ if (pathname.startsWith("/_preview/")) {
2899
+ const relativePath = pathname.slice("/_preview/".length);
2900
+ const previewsDir2 = path12.join(rootDir, "previews");
2901
+ const filePath = path12.resolve(previewsDir2, relativePath);
2902
+ if (filePath.startsWith(previewsDir2) && existsSync11(filePath) && statSync(filePath).isFile()) {
2903
+ return new Response(Bun.file(filePath));
2904
+ }
2905
+ }
2906
+ if (!pathname.includes(".") && !pathname.startsWith("/@") && !pathname.startsWith("/__") && !pathname.startsWith("/_preview") && !pathname.startsWith("/_prev")) {
2907
+ if (pathname.startsWith("/previews/") && pathname !== "/previews") {
2908
+ const previewPath = pathname.slice("/previews/".length);
2909
+ const searchParams = new URL(req.url).searchParams;
2910
+ const ogState = searchParams.get("state");
2911
+ const ogStep = searchParams.get("step");
2912
+ const ogTitle = previewPath.split("/").pop() || "Preview";
2913
+ const ogParams = [
2914
+ ogState ? `state=${ogState}` : "",
2915
+ ogStep ? `step=${ogStep}` : ""
2916
+ ].filter(Boolean).join("&");
2917
+ const ogImageUrl = `/_og/${previewPath}${ogParams ? `?${ogParams}` : ""}`;
2918
+ const ogHtml = HTML_SHELL.replace("<title>Documentation</title>", `<title>${ogTitle} - Preview</title>
2919
+ <meta property="og:title" content="${ogTitle}" />
2920
+ <meta property="og:description" content="${ogState ? `State: ${ogState}` : ogStep ? `Step: ${ogStep}` : "Preview"}" />
2921
+ <meta property="og:image" content="${ogImageUrl}" />
2922
+ <meta property="og:type" content="website" />
2923
+ <meta name="twitter:card" content="summary_large_image" />`);
2924
+ return new Response(ogHtml, {
2925
+ headers: { "Content-Type": "text/html" }
2926
+ });
2927
+ }
2928
+ return new Response(HTML_SHELL, {
2929
+ headers: { "Content-Type": "text/html" }
2930
+ });
2931
+ }
2932
+ return new Response("Not Found", { status: 404 });
2933
+ }
2934
+ });
2935
+ const { watch } = await import("fs");
2936
+ const watchers = [];
2937
+ let rebuildTimer = null;
2938
+ async function rebuild() {
2939
+ appBundle = await buildThemeApp(rootDir, include, config);
2940
+ if (appBundle.success) {
2941
+ notifyReload();
2942
+ }
2943
+ }
2944
+ function scheduleRebuild() {
2945
+ if (rebuildTimer)
2946
+ clearTimeout(rebuildTimer);
2947
+ rebuildTimer = setTimeout(rebuild, 150);
2948
+ }
2949
+ const previewsDir = path12.join(rootDir, "previews");
2950
+ if (existsSync11(previewsDir)) {
2951
+ watchers.push(watch(previewsDir, { recursive: true }, (_, filename) => {
2952
+ if (filename && /\.(tsx|ts|jsx|js|css|yaml|yml|mdx|md|html)$/.test(filename)) {
2953
+ scheduleRebuild();
2954
+ }
2955
+ }));
2956
+ }
2957
+ return {
2958
+ server,
2959
+ port: server.port,
2960
+ url: `http://localhost:${server.port}/`,
2961
+ stop: () => {
2962
+ if (rebuildTimer)
2963
+ clearTimeout(rebuildTimer);
2964
+ watchers.forEach((w) => w.close());
2965
+ server.stop();
2966
+ }
2967
+ };
2968
+ }
2969
+
2970
+ // src/server/build.ts
2971
+ import path14 from "path";
2972
+ import { existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync3, copyFileSync } from "fs";
2973
+ import { fileURLToPath as fileURLToPath3 } from "url";
2974
+
2975
+ // src/preview-runtime/vendors.ts
2976
+ import { join, dirname } from "path";
2977
+ import { mkdtempSync, writeFileSync as writeFileSync2, rmSync, existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
2978
+ import { fileURLToPath as fileURLToPath2 } from "url";
2979
+ import { tmpdir } from "os";
2980
+ function findCliRoot2() {
2981
+ let dir = dirname(fileURLToPath2(import.meta.url));
2694
2982
  for (let i = 0;i < 10; i++) {
2695
- const parent = path9.dirname(dir);
2983
+ const pkgPath = join(dir, "package.json");
2984
+ if (existsSync12(pkgPath)) {
2985
+ try {
2986
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
2987
+ if (pkg.name === "prev-cli")
2988
+ return dir;
2989
+ } catch {}
2990
+ }
2991
+ const parent = dirname(dir);
2696
2992
  if (parent === dir)
2697
2993
  break;
2698
- if (path9.basename(parent) === "node_modules" && existsSync8(path9.join(parent, "react"))) {
2699
- return parent;
2700
- }
2701
2994
  dir = parent;
2702
2995
  }
2703
- return localNodeModules;
2996
+ return dirname(dirname(fileURLToPath2(import.meta.url)));
2704
2997
  }
2705
2998
  var cliRoot2 = findCliRoot2();
2706
- var cliNodeModules = findNodeModules(cliRoot2);
2707
- var srcRoot2 = path9.join(cliRoot2, "src");
2708
- async function createViteConfig(options) {
2709
- const { rootDir, mode, port, include, base, debug } = options;
2710
- const config = loadConfig(rootDir);
2711
- const debugCollector = debug ? createDebugCollector(rootDir) : null;
2712
- if (debugCollector) {
2713
- debugCollector.startPhase("configLoad");
2999
+ async function buildVendorBundle() {
3000
+ try {
3001
+ const entryCode = `
3002
+ import * as React from 'react'
3003
+ import * as ReactDOM from 'react-dom'
3004
+ import { createRoot } from 'react-dom/client'
3005
+ export { jsx, jsxs, Fragment } from 'react/jsx-runtime'
3006
+ export { jsxDEV } from 'react/jsx-dev-runtime'
3007
+ export { React, ReactDOM, createRoot }
3008
+ // Re-export React hooks as named exports (preview code imports them directly)
3009
+ export {
3010
+ useState, useEffect, useCallback, useMemo, useRef,
3011
+ useContext, useReducer, useLayoutEffect, useInsertionEffect,
3012
+ useTransition, useDeferredValue, useId, useSyncExternalStore,
3013
+ useImperativeHandle, useDebugValue, memo, forwardRef,
3014
+ createContext, createRef, lazy, Suspense, startTransition,
3015
+ Children, cloneElement, isValidElement, createElement
3016
+ } from 'react'
3017
+ export default React
3018
+ `;
3019
+ const tempDir = mkdtempSync(join(cliRoot2, ".tmp-vendor-"));
3020
+ const entryPath = join(tempDir, "entry.ts");
3021
+ try {
3022
+ writeFileSync2(entryPath, entryCode);
3023
+ const result = await Bun.build({
3024
+ entrypoints: [entryPath],
3025
+ format: "esm",
3026
+ target: "browser",
3027
+ minify: true
3028
+ });
3029
+ if (!result.success) {
3030
+ const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message).join("; ");
3031
+ return { success: false, code: "", error: errors || "Build failed" };
3032
+ }
3033
+ const jsFile = result.outputs.find((f) => f.path.endsWith(".js")) || result.outputs[0];
3034
+ if (!jsFile) {
3035
+ return { success: false, code: "", error: "No output generated" };
3036
+ }
3037
+ return { success: true, code: await jsFile.text() };
3038
+ } finally {
3039
+ rmSync(tempDir, { recursive: true, force: true });
3040
+ }
3041
+ } catch (err) {
3042
+ return {
3043
+ success: false,
3044
+ code: "",
3045
+ error: err instanceof Error ? err.message : String(err)
3046
+ };
2714
3047
  }
2715
- const globalCacheDir = path9.join(os.homedir(), ".cache/prev/deps");
2716
- return {
2717
- root: rootDir,
2718
- mode,
2719
- cacheDir: globalCacheDir,
2720
- base: base || "/",
2721
- customLogger: createFriendlyLogger(),
2722
- logLevel: mode === "production" ? "silent" : "info",
2723
- envDir: cliRoot2,
2724
- envPrefix: "PREV_",
2725
- plugins: [
2726
- ...debugCollector ? [debugPlugin(debugCollector)] : [],
2727
- mdx({
2728
- remarkPlugins: [remarkGfm],
2729
- rehypePlugins: [rehypeHighlight],
2730
- providerImportSource: "@mdx-js/react",
2731
- include: [
2732
- path9.join(rootDir, "**/*.md"),
2733
- path9.join(rootDir, "**/*.mdx")
2734
- ],
2735
- exclude: [
2736
- "**/node_modules/**",
2737
- "**/.git/**"
2738
- ]
2739
- }),
2740
- react(),
2741
- createConfigPlugin(config),
2742
- pagesPlugin(rootDir, { include }),
2743
- entryPlugin(rootDir),
2744
- previewsPlugin(rootDir),
2745
- tokensPlugin(rootDir),
2746
- {
2747
- name: "prev-user-tokens",
2748
- configResolved() {
2749
- const userTokensPath = path9.join(rootDir, "previews/tokens.yaml");
2750
- if (existsSync8(userTokensPath)) {
2751
- globalThis.__PREV_USER_TOKENS_PATH = userTokensPath;
2752
- } else {
2753
- globalThis.__PREV_USER_TOKENS_PATH = null;
3048
+ }
3049
+ async function buildJsxBundle(vendorPath) {
3050
+ try {
3051
+ const minimalJsx = `
3052
+ import * as React from 'react'
3053
+
3054
+ // Default token values for standalone preview rendering
3055
+ const defaultTokens = {
3056
+ background: {
3057
+ primary: '#3b82f6',
3058
+ secondary: '#f1f5f9',
3059
+ destructive: '#ef4444',
3060
+ muted: '#f1f5f9',
3061
+ accent: '#f1f5f9',
3062
+ transparent: 'transparent',
3063
+ },
3064
+ color: {
3065
+ 'primary-foreground': '#ffffff',
3066
+ 'secondary-foreground': '#0f172a',
3067
+ 'destructive-foreground': '#ffffff',
3068
+ 'muted-foreground': '#64748b',
3069
+ 'accent-foreground': '#0f172a',
3070
+ foreground: '#0f172a',
3071
+ },
3072
+ spacing: {
3073
+ xs: '4px',
3074
+ sm: '8px',
3075
+ md: '12px',
3076
+ lg: '16px',
3077
+ xl: '24px',
3078
+ },
3079
+ radius: {
3080
+ none: '0',
3081
+ sm: '4px',
3082
+ md: '6px',
3083
+ lg: '8px',
3084
+ full: '9999px',
3085
+ },
3086
+ 'typography.size': {
3087
+ xs: '12px',
3088
+ sm: '14px',
3089
+ base: '16px',
3090
+ lg: '18px',
3091
+ xl: '20px',
3092
+ },
3093
+ 'typography.weight': {
3094
+ normal: '400',
3095
+ medium: '500',
3096
+ semibold: '600',
3097
+ bold: '700',
3098
+ },
3099
+ }
3100
+
3101
+ // Token resolution
3102
+ let tokensConfig = null
3103
+ export function setTokensConfig(config) { tokensConfig = config }
3104
+
3105
+ function resolveToken(category, token) {
3106
+ // Check custom config first, then defaults
3107
+ const config = tokensConfig || defaultTokens
3108
+ const cat = config[category]
3109
+ return cat?.[token] ?? token
3110
+ }
3111
+
3112
+ // VNode type
3113
+ export class VNode {
3114
+ constructor(type, props, children) {
3115
+ this.type = type
3116
+ this.props = props || {}
3117
+ this.children = children || []
3118
+ }
3119
+ }
3120
+
3121
+ // Primitives - return VNodes
3122
+ export function Box(props) { return new VNode('Box', props, props.children ? [props.children] : []) }
3123
+ export function Text(props) { return new VNode('Text', props, props.children ? [props.children] : []) }
3124
+ export function Col(props) { return new VNode('Col', props, props.children || []) }
3125
+ export function Row(props) { return new VNode('Row', props, props.children || []) }
3126
+ export function Spacer(props) { return new VNode('Spacer', props, []) }
3127
+ export function Slot(props) { return new VNode('Slot', props, []) }
3128
+ export function Icon(props) { return new VNode('Icon', props, []) }
3129
+ export function Image(props) { return new VNode('Image', props, []) }
3130
+ export const Fragment = React.Fragment
3131
+
3132
+ // Convert VNode to React element
3133
+ export function toReact(vnode) {
3134
+ if (!vnode || typeof vnode !== 'object') return vnode
3135
+ if (!(vnode instanceof VNode)) return vnode
3136
+
3137
+ const { type, props, children } = vnode
3138
+ const style = {}
3139
+
3140
+ // Map props to styles
3141
+ if (props.bg) style.backgroundColor = resolveToken('background', props.bg)
3142
+ if (props.padding) style.padding = resolveToken('spacing', props.padding)
3143
+ if (props.radius) style.borderRadius = resolveToken('radius', props.radius)
3144
+ if (props.color) style.color = resolveToken('color', props.color)
3145
+ if (props.size) style.fontSize = resolveToken('typography.size', props.size)
3146
+ if (props.weight) style.fontWeight = resolveToken('typography.weight', props.weight)
3147
+ if (props.gap) style.gap = resolveToken('spacing', props.gap)
3148
+
3149
+ // Layout types
3150
+ if (type === 'Col') { style.display = 'flex'; style.flexDirection = 'column' }
3151
+ if (type === 'Row') { style.display = 'flex'; style.flexDirection = 'row' }
3152
+ if (type === 'Spacer') { style.flex = 1 }
3153
+
3154
+ const childElements = children.map(c => toReact(c))
3155
+
3156
+ return React.createElement('div', { style }, ...childElements)
3157
+ }
3158
+ `;
3159
+ const tempDir = mkdtempSync(join(tmpdir(), "prev-jsx-"));
3160
+ const entryPath = join(tempDir, "entry.ts");
3161
+ try {
3162
+ writeFileSync2(entryPath, minimalJsx);
3163
+ const result = await Bun.build({
3164
+ entrypoints: [entryPath],
3165
+ format: "esm",
3166
+ target: "browser",
3167
+ minify: true,
3168
+ plugins: [
3169
+ {
3170
+ name: "jsx-externals",
3171
+ setup(build) {
3172
+ build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
3173
+ return { path: vendorPath, external: true };
3174
+ });
3175
+ }
2754
3176
  }
3177
+ ]
3178
+ });
3179
+ if (!result.success) {
3180
+ const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message).join("; ");
3181
+ return { success: false, code: "", error: errors || "Build failed" };
3182
+ }
3183
+ const jsFile = result.outputs.find((f) => f.path.endsWith(".js")) || result.outputs[0];
3184
+ if (!jsFile) {
3185
+ return { success: false, code: "", error: "No output generated" };
3186
+ }
3187
+ let code = await jsFile.text();
3188
+ code = code.replace(/from\s*["']react["']/g, `from"${vendorPath}"`);
3189
+ return { success: true, code };
3190
+ } finally {
3191
+ rmSync(tempDir, { recursive: true, force: true });
3192
+ }
3193
+ } catch (err) {
3194
+ return {
3195
+ success: false,
3196
+ code: "",
3197
+ error: err instanceof Error ? err.message : String(err)
3198
+ };
3199
+ }
3200
+ }
3201
+
3202
+ // src/preview-runtime/tailwind.ts
3203
+ var {$ } = globalThis.Bun;
3204
+ import { mkdtempSync as mkdtempSync2, mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync8, rmSync as rmSync2, existsSync as existsSync13 } from "fs";
3205
+ import { join as join2, dirname as dirname2 } from "path";
3206
+ import { tmpdir as tmpdir2 } from "os";
3207
+ function findTailwindBin() {
3208
+ try {
3209
+ const tailwindPkg = __require.resolve("tailwindcss/package.json");
3210
+ const tailwindDir = dirname2(tailwindPkg);
3211
+ const binPath = join2(tailwindDir, "lib/cli.js");
3212
+ if (existsSync13(binPath))
3213
+ return binPath;
3214
+ } catch {}
3215
+ return "bunx tailwindcss@3";
3216
+ }
3217
+ var tailwindCmd = findTailwindBin();
3218
+ async function compileTailwind(files) {
3219
+ const tempDir = mkdtempSync2(join2(tmpdir2(), "prev-tailwind-"));
3220
+ try {
3221
+ for (const file of files) {
3222
+ const filePath = join2(tempDir, file.path);
3223
+ const parentDir = dirname2(filePath);
3224
+ mkdirSync(parentDir, { recursive: true });
3225
+ writeFileSync3(filePath, file.content);
3226
+ }
3227
+ const configContent = `
3228
+ module.exports = {
3229
+ content: [${JSON.stringify(tempDir + "/**/*.{tsx,jsx,ts,js,html}")}],
3230
+ }
3231
+ `;
3232
+ const configPath = join2(tempDir, "tailwind.config.cjs");
3233
+ writeFileSync3(configPath, configContent);
3234
+ const inputCss = `
3235
+ @tailwind base;
3236
+ @tailwind components;
3237
+ @tailwind utilities;
3238
+ `;
3239
+ const inputPath = join2(tempDir, "input.css");
3240
+ writeFileSync3(inputPath, inputCss);
3241
+ const outputPath = join2(tempDir, "output.css");
3242
+ if (tailwindCmd.startsWith("bunx")) {
3243
+ await $`bunx tailwindcss@3 -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet();
3244
+ } else {
3245
+ await $`bun ${tailwindCmd} -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet();
3246
+ }
3247
+ const css = readFileSync8(outputPath, "utf-8");
3248
+ return { success: true, css };
3249
+ } catch (err) {
3250
+ return {
3251
+ success: false,
3252
+ css: "",
3253
+ error: err instanceof Error ? err.message : String(err)
3254
+ };
3255
+ } finally {
3256
+ rmSync2(tempDir, { recursive: true, force: true });
3257
+ }
3258
+ }
3259
+
3260
+ // src/preview-runtime/build-optimized.ts
3261
+ import { existsSync as existsSync14, readFileSync as readFileSync9, mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync4, rmSync as rmSync3, mkdirSync as mkdirSync2, statSync as statSync2 } from "fs";
3262
+ import path13 from "path";
3263
+ import { tmpdir as tmpdir3 } from "os";
3264
+ function existsAsFile(p) {
3265
+ try {
3266
+ return statSync2(p).isFile();
3267
+ } catch {
3268
+ return false;
3269
+ }
3270
+ }
3271
+ async function buildOptimizedPreview(config, options) {
3272
+ try {
3273
+ const virtualFs = {};
3274
+ const resolveDir = options.resolveDir || "/";
3275
+ for (const file of config.files) {
3276
+ const ext = file.path.split(".").pop()?.toLowerCase();
3277
+ const loader = ext === "css" ? "css" : ext === "json" ? "json" : ext || "tsx";
3278
+ virtualFs[file.path] = { contents: file.content, loader };
3279
+ }
3280
+ const entryFile = config.files.find((f) => f.path === config.entry);
3281
+ if (!entryFile) {
3282
+ return { success: false, html: "", css: "", error: `Entry file not found: ${config.entry}` };
3283
+ }
3284
+ const hasDefaultExport = /export\s+default/.test(entryFile.content);
3285
+ const userCssCollected = [];
3286
+ const entryCode = hasDefaultExport ? `
3287
+ import React, { createRoot } from '${options.vendorPath}'
3288
+ import App from './${config.entry}'
3289
+ const root = createRoot(document.getElementById('root'))
3290
+ root.render(React.createElement(App))
3291
+ ` : `import './${config.entry}'`;
3292
+ const tempDir = mkdtempSync3(path13.join(tmpdir3(), "prev-optimized-"));
3293
+ const entryPath = path13.join(tempDir, "__entry.tsx");
3294
+ try {
3295
+ writeFileSync4(entryPath, entryCode);
3296
+ for (const [filePath, file] of Object.entries(virtualFs)) {
3297
+ const targetPath = path13.join(tempDir, filePath);
3298
+ const dir = path13.dirname(targetPath);
3299
+ if (!existsSync14(dir)) {
3300
+ mkdirSync2(dir, { recursive: true });
2755
3301
  }
2756
- },
2757
- {
2758
- name: "prev-config-api",
2759
- configureServer(server) {
2760
- server.middlewares.use("/__prev/config", async (req, res) => {
2761
- if (req.method === "POST") {
2762
- let body = "";
2763
- req.on("data", (chunk) => {
2764
- body += chunk;
3302
+ writeFileSync4(targetPath, file.contents);
3303
+ }
3304
+ const result = await Bun.build({
3305
+ entrypoints: [entryPath],
3306
+ format: "esm",
3307
+ target: "browser",
3308
+ minify: true,
3309
+ jsx: { runtime: "automatic", importSource: "react" },
3310
+ plugins: [
3311
+ {
3312
+ name: "optimized-preview",
3313
+ setup(build) {
3314
+ build.onResolve({ filter: new RegExp(options.vendorPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) }, (args) => {
3315
+ return { path: args.path, external: true };
2765
3316
  });
2766
- req.on("end", () => {
2767
- try {
2768
- const { path: pathKey, order } = JSON.parse(body);
2769
- updateOrder(rootDir, pathKey, order);
2770
- res.statusCode = 200;
2771
- res.end(JSON.stringify({ success: true }));
2772
- } catch (e) {
2773
- res.statusCode = 400;
2774
- res.end(JSON.stringify({ error: String(e) }));
2775
- }
3317
+ build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
3318
+ return { path: options.vendorPath, external: true };
2776
3319
  });
2777
- return;
2778
- }
2779
- res.statusCode = 405;
2780
- res.end();
2781
- });
2782
- }
2783
- },
2784
- {
2785
- name: "prev-spa-fallback",
2786
- configureServer(server) {
2787
- return () => {
2788
- server.middlewares.use((req, res, next) => {
2789
- const urlPath = req.url?.split("?")[0] || "";
2790
- if (urlPath.startsWith("/__") || urlPath.startsWith("/@") || urlPath.startsWith("/node_modules") || urlPath.includes(".")) {
2791
- return next();
2792
- }
2793
- const indexPath = path9.join(srcRoot2, "theme/index.html");
2794
- if (existsSync8(indexPath)) {
2795
- server.transformIndexHtml(req.url, readFileSync7(indexPath, "utf-8")).then((html) => {
2796
- res.setHeader("Content-Type", "text/html");
2797
- res.end(html);
2798
- }).catch(next);
2799
- return;
2800
- }
2801
- next();
2802
- });
2803
- };
2804
- }
2805
- },
2806
- {
2807
- name: "prev-jsx-bundle",
2808
- configureServer(server) {
2809
- let cachedBundle = null;
2810
- server.middlewares.use("/_prev/jsx.js", async (req, res, next) => {
2811
- if (req.method !== "GET")
2812
- return next();
2813
- try {
2814
- if (!cachedBundle) {
2815
- const jsxEntry = path9.join(srcRoot2, "jsx/index.ts");
2816
- const result = await viteBuild({
2817
- logLevel: "silent",
2818
- define: {
2819
- "process.env.NODE_ENV": '"production"'
2820
- },
2821
- esbuild: {
2822
- jsx: "automatic",
2823
- jsxImportSource: "react",
2824
- jsxDev: false
2825
- },
2826
- build: {
2827
- write: false,
2828
- lib: {
2829
- entry: jsxEntry,
2830
- formats: ["es"],
2831
- fileName: "jsx"
2832
- },
2833
- rollupOptions: {
2834
- external: ["react", "react-dom", "react/jsx-runtime", "zod"],
2835
- output: {
2836
- globals: {
2837
- react: "React",
2838
- "react-dom": "ReactDOM"
2839
- }
2840
- }
2841
- },
2842
- minify: false
2843
- }
2844
- });
2845
- const output = Array.isArray(result) ? result[0] : result;
2846
- if (!("output" in output)) {
2847
- throw new Error("Unexpected build result type");
2848
- }
2849
- const jsFile = output.output.find((f) => f.type === "chunk");
2850
- if (jsFile && "code" in jsFile) {
2851
- cachedBundle = jsFile.code.replace(/from\s+['"]react\/jsx-runtime['"]/g, 'from "https://esm.sh/react@18/jsx-runtime"').replace(/from\s+['"]react['"]/g, 'from "https://esm.sh/react@18"').replace(/from\s+['"]react-dom['"]/g, 'from "https://esm.sh/react-dom@18"').replace(/from\s+['"]zod['"]/g, 'from "https://esm.sh/zod"');
2852
- }
2853
- }
2854
- if (cachedBundle) {
2855
- res.setHeader("Content-Type", "application/javascript");
2856
- res.setHeader("Cache-Control", "no-cache");
2857
- res.end(cachedBundle);
2858
- return;
2859
- }
2860
- res.statusCode = 500;
2861
- res.end("Failed to bundle jsx primitives");
2862
- } catch (err) {
2863
- console.error("Error bundling jsx:", err);
2864
- res.statusCode = 500;
2865
- res.end(String(err));
2866
- }
2867
- });
2868
- server.watcher.on("change", (file) => {
2869
- if (file.includes("/jsx/")) {
2870
- cachedBundle = null;
2871
- }
2872
- });
2873
- }
2874
- },
2875
- {
2876
- name: "prev-components-bundle",
2877
- configureServer(server) {
2878
- const componentCache = new Map;
2879
- server.middlewares.use(async (req, res, next) => {
2880
- const urlPath = req.url?.split("?")[0] || "";
2881
- const match = urlPath.match(/^\/_prev\/components\/([^/]+)\.js$/);
2882
- if (!match || req.method !== "GET")
2883
- return next();
2884
- const componentName = match[1];
2885
- const componentEntry = path9.join(rootDir, "previews/components", componentName, "index.tsx");
2886
- if (!existsSync8(componentEntry)) {
2887
- res.statusCode = 404;
2888
- res.end(`Component not found: ${componentName}`);
2889
- return;
2890
- }
2891
- try {
2892
- let bundledCode = componentCache.get(componentName);
2893
- if (!bundledCode) {
2894
- const result = await viteBuild({
2895
- logLevel: "silent",
2896
- define: {
2897
- "process.env.NODE_ENV": '"production"'
2898
- },
2899
- esbuild: {
2900
- jsx: "automatic",
2901
- jsxImportSource: "react",
2902
- jsxDev: false
2903
- },
2904
- build: {
2905
- write: false,
2906
- lib: {
2907
- entry: componentEntry,
2908
- formats: ["es"],
2909
- fileName: componentName
2910
- },
2911
- rollupOptions: {
2912
- external: ["react", "react-dom", "react/jsx-runtime", "@prev/jsx"],
2913
- output: {
2914
- globals: {
2915
- react: "React",
2916
- "react-dom": "ReactDOM"
2917
- }
2918
- }
2919
- },
2920
- minify: false
3320
+ build.onResolve({ filter: /^@prev\/jsx$/ }, () => {
3321
+ const jsxPath2 = options.jsxPath || options.vendorPath.replace("runtime.js", "jsx.js");
3322
+ return { path: jsxPath2, external: true };
3323
+ });
3324
+ build.onResolve({ filter: /^@prev\/components\// }, (args) => {
3325
+ console.warn(` Warning: @prev/components imports not supported in static builds: ${args.path}`);
3326
+ return { path: args.path, external: true };
3327
+ });
3328
+ build.onLoad({ filter: /\.css$/ }, (args) => {
3329
+ let content;
3330
+ const tempPath = args.path;
3331
+ if (existsSync14(tempPath)) {
3332
+ content = readFileSync9(tempPath, "utf-8");
3333
+ } else {
3334
+ const diskPath = path13.resolve(resolveDir, path13.relative(tempDir, tempPath));
3335
+ if (existsSync14(diskPath)) {
3336
+ content = readFileSync9(diskPath, "utf-8");
3337
+ } else {
3338
+ return { contents: "", loader: "js" };
2921
3339
  }
2922
- });
2923
- const output = Array.isArray(result) ? result[0] : result;
2924
- if (!("output" in output)) {
2925
- throw new Error("Unexpected build result type");
2926
- }
2927
- const jsFile = output.output.find((f) => f.type === "chunk");
2928
- if (jsFile && "code" in jsFile) {
2929
- bundledCode = jsFile.code.replace(/from\s+['"]react\/jsx-runtime['"]/g, 'from "https://esm.sh/react@18/jsx-runtime"').replace(/from\s+['"]react['"]/g, 'from "https://esm.sh/react@18"').replace(/from\s+['"]react-dom['"]/g, 'from "https://esm.sh/react-dom@18"').replace(/from\s+['"]@prev\/jsx['"]/g, `from "${req.headers.origin || ""}/_prev/jsx.js"`);
2930
- componentCache.set(componentName, bundledCode);
2931
- }
2932
- }
2933
- if (bundledCode) {
2934
- res.setHeader("Content-Type", "application/javascript");
2935
- res.setHeader("Cache-Control", "no-cache");
2936
- res.end(bundledCode);
2937
- return;
2938
- }
2939
- res.statusCode = 500;
2940
- res.end(`Failed to bundle component: ${componentName}`);
2941
- } catch (err) {
2942
- console.error(`Error bundling component ${componentName}:`, err);
2943
- res.statusCode = 500;
2944
- res.end(String(err));
2945
- }
2946
- });
2947
- server.watcher.on("change", (file) => {
2948
- if (file.includes("/previews/components/")) {
2949
- const match = file.match(/\/previews\/components\/([^/]+)\//);
2950
- if (match) {
2951
- componentCache.delete(match[1]);
2952
- }
2953
- }
2954
- });
2955
- }
2956
- },
2957
- {
2958
- name: "prev-preview-server",
2959
- resolveId(id) {
2960
- if (id.startsWith("/_preview/")) {
2961
- const relativePath = id.slice("/_preview/".length);
2962
- const previewsDir = path9.join(rootDir, "previews");
2963
- const resolved = path9.resolve(previewsDir, relativePath);
2964
- if (resolved.startsWith(previewsDir)) {
2965
- return resolved;
2966
- }
2967
- }
2968
- },
2969
- configureServer(server) {
2970
- server.middlewares.use(async (req, res, next) => {
2971
- const urlPath = req.url?.split("?")[0] || "";
2972
- if (urlPath.startsWith("/_preview-bundle/")) {
2973
- const startTime = performance.now();
2974
- const previewPath = decodeURIComponent(urlPath.slice("/_preview-bundle/".length));
2975
- const previewDir = path9.join(rootDir, "previews", previewPath);
2976
- if (!previewDir.startsWith(path9.join(rootDir, "previews"))) {
2977
- res.statusCode = 403;
2978
- res.end("Forbidden");
2979
- return;
2980
- }
2981
- const entryFiles = ["index.tsx", "index.ts", "index.jsx", "index.js", "App.tsx", "App.ts"];
2982
- let entryFile = "";
2983
- for (const f of entryFiles) {
2984
- if (existsSync8(path9.join(previewDir, f))) {
2985
- entryFile = path9.join(previewDir, f);
2986
- break;
2987
3340
  }
2988
- }
2989
- if (!entryFile) {
2990
- res.statusCode = 404;
2991
- res.end(JSON.stringify({ error: "No entry file found" }));
2992
- return;
2993
- }
2994
- try {
2995
- const result = await esbuildLib.build({
2996
- entryPoints: [entryFile],
2997
- bundle: true,
2998
- write: false,
2999
- format: "esm",
3000
- jsx: "automatic",
3001
- jsxImportSource: "react",
3002
- jsxDev: false,
3003
- target: "es2020",
3004
- minify: false,
3005
- sourcemap: false,
3006
- external: ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "@prev/jsx", "fs", "path", "js-yaml"],
3007
- alias: {
3008
- react: "https://esm.sh/react@18",
3009
- "react-dom": "https://esm.sh/react-dom@18",
3010
- "react-dom/client": "https://esm.sh/react-dom@18/client",
3011
- "react/jsx-runtime": "https://esm.sh/react@18/jsx-runtime"
3012
- },
3013
- define: {
3014
- "process.env.NODE_ENV": '"production"'
3015
- }
3016
- });
3017
- const bundleTime = Math.round(performance.now() - startTime);
3018
- const code = result.outputFiles[0]?.text || "";
3019
- res.setHeader("Content-Type", "application/javascript");
3020
- res.setHeader("X-Bundle-Time", String(bundleTime));
3021
- res.end(code);
3022
- return;
3023
- } catch (err) {
3024
- console.error("Bundle error:", err);
3025
- res.statusCode = 500;
3026
- res.end(JSON.stringify({ error: String(err) }));
3027
- return;
3028
- }
3029
- }
3030
- if (urlPath === "/_preview-runtime") {
3031
- const templatePath = path9.join(srcRoot2, "preview-runtime/fast-template.html");
3032
- if (existsSync8(templatePath)) {
3033
- const html = readFileSync7(templatePath, "utf-8");
3034
- res.setHeader("Content-Type", "text/html");
3035
- res.end(html);
3036
- return;
3037
- }
3038
- }
3039
- if (urlPath.startsWith("/_preview-config/")) {
3040
- const pathAfterConfig = decodeURIComponent(urlPath.slice("/_preview-config/".length));
3041
- const previewsDir = path9.join(rootDir, "previews");
3042
- const multiTypeMatch = pathAfterConfig.match(/^(components|screens|flows|atlas)\/(.+)$/);
3043
- if (multiTypeMatch) {
3044
- const [, type, name] = multiTypeMatch;
3045
- const previewDir = path9.join(previewsDir, type, name);
3046
- if (!previewDir.startsWith(previewsDir)) {
3047
- res.statusCode = 403;
3048
- res.end("Forbidden");
3341
+ userCssCollected.push(content);
3342
+ return { contents: "", loader: "js" };
3343
+ });
3344
+ build.onResolve({ filter: /^\.\.?\// }, (args) => {
3345
+ const resolved = path13.resolve(path13.dirname(args.importer), args.path);
3346
+ const tryExts = [".tsx", ".ts", ".jsx", ".js", ".css"];
3347
+ const tryIndex = ["/index.tsx", "/index.ts", "/index.jsx", "/index.js"];
3348
+ if (existsAsFile(resolved))
3049
3349
  return;
3050
- }
3051
- if (existsSync8(previewDir)) {
3052
- try {
3053
- if (type === "flows") {
3054
- const configPathYaml = path9.join(previewDir, "index.yaml");
3055
- const configPathYml = path9.join(previewDir, "index.yml");
3056
- const configPath = existsSync8(configPathYaml) ? configPathYaml : configPathYml;
3057
- if (existsSync8(configPath)) {
3058
- const flow = await parseFlowDefinition(configPath);
3059
- if (flow) {
3060
- res.setHeader("Content-Type", "application/json");
3061
- res.end(JSON.stringify(flow));
3062
- return;
3063
- }
3064
- }
3065
- } else if (type === "atlas") {
3066
- const configPathYaml = path9.join(previewDir, "index.yaml");
3067
- const configPathYml = path9.join(previewDir, "index.yml");
3068
- const configPath = existsSync8(configPathYaml) ? configPathYaml : configPathYml;
3069
- if (existsSync8(configPath)) {
3070
- const atlas = await parseAtlasDefinition(configPath);
3071
- if (atlas) {
3072
- res.setHeader("Content-Type", "application/json");
3073
- res.end(JSON.stringify(atlas));
3074
- return;
3075
- }
3076
- }
3077
- } else {
3078
- const config2 = await buildPreviewConfig(previewDir);
3079
- res.setHeader("Content-Type", "application/json");
3080
- res.end(JSON.stringify(config2));
3081
- return;
3082
- }
3083
- res.statusCode = 400;
3084
- res.end(JSON.stringify({ error: "Invalid config format" }));
3350
+ for (const ext of tryExts) {
3351
+ if (existsAsFile(resolved + ext))
3085
3352
  return;
3086
- } catch (err) {
3087
- console.error("Error building preview config:", err);
3088
- res.statusCode = 500;
3089
- res.end(JSON.stringify({ error: String(err) }));
3353
+ }
3354
+ for (const idx of tryIndex) {
3355
+ if (existsAsFile(resolved + idx))
3090
3356
  return;
3091
- }
3092
3357
  }
3093
- } else {
3094
- const previewDir = path9.resolve(previewsDir, pathAfterConfig);
3095
- if (!previewDir.startsWith(previewsDir)) {
3096
- res.statusCode = 403;
3097
- res.end("Forbidden");
3098
- return;
3358
+ const importerRelative = path13.relative(tempDir, args.importer);
3359
+ const originalDir = path13.resolve(resolveDir, path13.dirname(importerRelative));
3360
+ const diskPath = path13.resolve(originalDir, args.path);
3361
+ if (existsAsFile(diskPath))
3362
+ return { path: diskPath };
3363
+ for (const ext of tryExts) {
3364
+ if (existsAsFile(diskPath + ext)) {
3365
+ return { path: diskPath + ext };
3366
+ }
3099
3367
  }
3100
- if (existsSync8(previewDir)) {
3101
- try {
3102
- const config2 = await buildPreviewConfig(previewDir);
3103
- res.setHeader("Content-Type", "application/json");
3104
- res.end(JSON.stringify(config2));
3105
- return;
3106
- } catch (err) {
3107
- console.error("Error building preview config:", err);
3108
- res.statusCode = 500;
3109
- res.end(JSON.stringify({ error: String(err) }));
3110
- return;
3368
+ for (const idx of tryIndex) {
3369
+ if (existsAsFile(diskPath + idx)) {
3370
+ return { path: diskPath + idx };
3111
3371
  }
3112
3372
  }
3113
- }
3373
+ return;
3374
+ });
3114
3375
  }
3115
- if (urlPath.match(/^\/_preview\/(components|screens|flows|atlas)\/[^/]+\/?$/)) {
3116
- const routeMatch = urlPath.match(/^\/_preview\/(\w+)\/([^/]+)\/?$/);
3117
- if (routeMatch) {
3118
- const [, type, name] = routeMatch;
3119
- const singularType = TYPE_SINGULAR[type] || type;
3120
- const shellHtml = `<!DOCTYPE html>
3121
- <html>
3376
+ }
3377
+ ]
3378
+ });
3379
+ if (!result.success) {
3380
+ const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message).join("; ");
3381
+ return { success: false, html: "", css: "", error: errors || "Build failed" };
3382
+ }
3383
+ const jsFile = result.outputs.find((f) => f.path.endsWith(".js")) || result.outputs[0];
3384
+ let jsCode = jsFile ? await jsFile.text() : "";
3385
+ const jsxPath = options.jsxPath || options.vendorPath.replace("runtime.js", "jsx.js");
3386
+ jsCode = jsCode.replace(/from\s*["']react\/jsx(-dev)?-runtime["']/g, `from"${options.vendorPath}"`);
3387
+ jsCode = jsCode.replace(/from\s*["']react-dom\/client["']/g, `from"${options.vendorPath}"`);
3388
+ jsCode = jsCode.replace(/from\s*["']react-dom["']/g, `from"${options.vendorPath}"`);
3389
+ jsCode = jsCode.replace(/from\s*["']react["']/g, `from"${options.vendorPath}"`);
3390
+ jsCode = jsCode.replace(/from\s*["']@prev\/jsx["']/g, `from"${jsxPath}"`);
3391
+ jsCode = jsCode.replace(/from\s*["']@prev\/components\/[^"']*["']/g, `from"${jsxPath}"`);
3392
+ let css = "";
3393
+ if (config.tailwind) {
3394
+ const tailwindResult = await compileTailwind(config.files.map((f) => ({ path: f.path, content: f.content })));
3395
+ if (tailwindResult.success)
3396
+ css = tailwindResult.css;
3397
+ }
3398
+ let userCss = userCssCollected.join(`
3399
+ `);
3400
+ if (config.tailwind) {
3401
+ userCss = userCss.replace(/@import\s*["']tailwindcss["']\s*;?/g, "");
3402
+ }
3403
+ const allCss = css + `
3404
+ ` + userCss;
3405
+ const canvasStyles = `
3406
+ html, body {
3407
+ margin: 0;
3408
+ min-height: 100vh;
3409
+ }
3410
+ body {
3411
+ display: flex;
3412
+ align-items: center;
3413
+ justify-content: center;
3414
+ background-color: #fafafa;
3415
+ background-image:
3416
+ radial-gradient(circle at center, #e5e5e5 1px, transparent 1px);
3417
+ background-size: 16px 16px;
3418
+ padding: 24px;
3419
+ box-sizing: border-box;
3420
+ }
3421
+ @media (prefers-color-scheme: dark) {
3422
+ body {
3423
+ background-color: #171717;
3424
+ background-image:
3425
+ radial-gradient(circle at center, #262626 1px, transparent 1px);
3426
+ }
3427
+ }
3428
+ #root {
3429
+ background: white;
3430
+ border-radius: 12px;
3431
+ padding: 32px;
3432
+ box-shadow: 0 4px 24px -4px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04);
3433
+ max-width: 100%;
3434
+ }
3435
+ @media (prefers-color-scheme: dark) {
3436
+ #root {
3437
+ background: #1c1c1c;
3438
+ box-shadow: 0 4px 24px -4px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.06);
3439
+ }
3440
+ }
3441
+ `;
3442
+ const html = `<!DOCTYPE html>
3443
+ <html lang="en">
3122
3444
  <head>
3123
3445
  <meta charset="UTF-8">
3124
- <title>Preview: ${name}</title>
3125
- <script type="module">
3126
- import { PreviewRouter } from '@prev/theme/previews'
3127
- import { createRoot } from 'react-dom/client'
3128
- import React from 'react'
3446
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3447
+ <title>Preview</title>
3448
+ <style>${allCss}</style>
3449
+ <style>${canvasStyles}</style>
3450
+ </head>
3451
+ <body>
3452
+ <div id="root"></div>
3453
+ <script type="module" src="${options.vendorPath}"></script>
3454
+ <script type="module" src="${jsxPath}"></script>
3455
+ <script type="module">${jsCode}</script>
3456
+ </body>
3457
+ </html>`;
3458
+ return { success: true, html, css: allCss };
3459
+ } finally {
3460
+ rmSync3(tempDir, { recursive: true, force: true });
3461
+ }
3462
+ } catch (err) {
3463
+ return { success: false, html: "", css: "", error: err instanceof Error ? err.message : String(err) };
3464
+ }
3465
+ }
3129
3466
 
3130
- createRoot(document.getElementById('root')).render(
3131
- React.createElement(PreviewRouter, { type: '${singularType}', name: '${name}' })
3132
- )
3133
- </script>
3467
+ // src/server/build.ts
3468
+ function findCliRoot3() {
3469
+ let dir = path14.dirname(fileURLToPath3(import.meta.url));
3470
+ for (let i = 0;i < 10; i++) {
3471
+ const pkgPath = path14.join(dir, "package.json");
3472
+ if (existsSync15(pkgPath)) {
3473
+ try {
3474
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
3475
+ if (pkg.name === "prev-cli")
3476
+ return dir;
3477
+ } catch {}
3478
+ }
3479
+ const parent = path14.dirname(dir);
3480
+ if (parent === dir)
3481
+ break;
3482
+ dir = parent;
3483
+ }
3484
+ return path14.dirname(path14.dirname(fileURLToPath3(import.meta.url)));
3485
+ }
3486
+ var cliRoot3 = findCliRoot3();
3487
+ var srcRoot2 = path14.join(cliRoot3, "src");
3488
+ async function buildProductionSite(options) {
3489
+ const { rootDir, include, base = "/" } = options;
3490
+ const config = loadConfig(rootDir);
3491
+ const distDir = path14.join(rootDir, "dist");
3492
+ const entryPath = path14.join(srcRoot2, "theme/entry.tsx");
3493
+ const plugins = [
3494
+ virtualModulesPlugin({ rootDir, include, config }),
3495
+ mdxPlugin({ rootDir }),
3496
+ aliasesPlugin({ cliRoot: cliRoot3 })
3497
+ ];
3498
+ const result = await Bun.build({
3499
+ entrypoints: [entryPath],
3500
+ outdir: distDir,
3501
+ format: "esm",
3502
+ target: "browser",
3503
+ minify: true,
3504
+ splitting: true,
3505
+ plugins,
3506
+ jsx: { runtime: "automatic", importSource: "react", development: false },
3507
+ naming: {
3508
+ entry: "assets/[name]-[hash].[ext]",
3509
+ chunk: "assets/[name]-[hash].[ext]",
3510
+ asset: "assets/[name]-[hash].[ext]"
3511
+ },
3512
+ define: {
3513
+ "import.meta.env.DEV": "false",
3514
+ "import.meta.env.BASE_URL": JSON.stringify(base),
3515
+ "process.env.NODE_ENV": '"production"'
3516
+ }
3517
+ });
3518
+ if (!result.success) {
3519
+ const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message);
3520
+ throw new Error(`Build failed:
3521
+ ${errors.join(`
3522
+ `)}`);
3523
+ }
3524
+ const entryOutput = result.outputs.find((o) => o.kind === "entry-point");
3525
+ const cssOutputs = result.outputs.filter((o) => o.path.endsWith(".css"));
3526
+ const entryJsPath = entryOutput ? base + path14.relative(distDir, entryOutput.path) : "";
3527
+ const cssLinks = cssOutputs.map((o) => {
3528
+ const href = base + path14.relative(distDir, o.path);
3529
+ return ` <link rel="stylesheet" href="${href}" />`;
3530
+ }).join(`
3531
+ `);
3532
+ const html = `<!DOCTYPE html>
3533
+ <html lang="en">
3534
+ <head>
3535
+ <meta charset="UTF-8" />
3536
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
3537
+ <title>Documentation</title>
3538
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
3539
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
3540
+ <link rel="preload" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" as="style" />
3541
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" />
3542
+ ${cssLinks}
3134
3543
  </head>
3135
3544
  <body>
3136
3545
  <div id="root"></div>
3546
+ <script type="module" src="${entryJsPath}"></script>
3137
3547
  </body>
3138
3548
  </html>`;
3139
- const transformed = await server.transformIndexHtml(req.url, shellHtml);
3140
- res.setHeader("Content-Type", "text/html");
3141
- res.end(transformed);
3142
- return;
3143
- }
3144
- }
3145
- if (urlPath.startsWith("/_preview/")) {
3146
- const isHtmlRequest = !path9.extname(urlPath) || urlPath.endsWith("/");
3147
- if (isHtmlRequest) {
3148
- const previewName = decodeURIComponent(urlPath.slice("/_preview/".length).replace(/\/$/, ""));
3149
- const previewsDir = path9.join(rootDir, "previews");
3150
- const htmlPath = path9.resolve(previewsDir, previewName, "index.html");
3151
- if (!htmlPath.startsWith(previewsDir)) {
3152
- return next();
3153
- }
3154
- if (existsSync8(htmlPath)) {
3155
- try {
3156
- let html = readFileSync7(htmlPath, "utf-8");
3157
- const previewBase = `/_preview/${previewName}/`;
3158
- html = html.replace(/(src|href)=["']\.\/([^"']+)["']/g, `$1="${previewBase}$2"`);
3159
- const transformed = await server.transformIndexHtml(req.url, html);
3160
- res.setHeader("Content-Type", "text/html");
3161
- res.end(transformed);
3162
- return;
3163
- } catch (err) {
3164
- console.error("Error serving preview:", err);
3165
- return next();
3166
- }
3167
- }
3168
- }
3169
- }
3170
- next();
3171
- });
3549
+ writeFileSync5(path14.join(distDir, "index.html"), html);
3550
+ copyFileSync(path14.join(distDir, "index.html"), path14.join(distDir, "404.html"));
3551
+ await buildPreviewHtmlFiles(rootDir, distDir);
3552
+ }
3553
+ async function buildPreviewHtmlFiles(rootDir, distDir) {
3554
+ const units = await scanPreviewUnits(rootDir);
3555
+ if (units.length === 0)
3556
+ return;
3557
+ const targetDir = path14.join(distDir, "_preview");
3558
+ const vendorsDir = path14.join(targetDir, "_vendors");
3559
+ const previewsDir = path14.join(rootDir, "previews");
3560
+ let hasFlowErrors = false;
3561
+ for (const unit of units) {
3562
+ if (unit.type !== "flow" || !unit.config)
3563
+ continue;
3564
+ const result = verifyFlow(unit.config, rootDir);
3565
+ for (const w of result.warnings) {
3566
+ console.warn(` \u26A0 flows/${unit.name}: ${w}`);
3567
+ }
3568
+ for (const e of result.errors) {
3569
+ console.error(` \u2717 flows/${unit.name}: ${e}`);
3570
+ hasFlowErrors = true;
3571
+ }
3572
+ }
3573
+ if (hasFlowErrors) {
3574
+ throw new Error("Flow verification failed \u2014 fix errors above before building");
3575
+ }
3576
+ let totalBuilds = 0;
3577
+ for (const unit of units) {
3578
+ if (unit.type === "flow")
3579
+ continue;
3580
+ totalBuilds++;
3581
+ if (unit.files.states)
3582
+ totalBuilds += unit.files.states.length;
3583
+ }
3584
+ console.log(`
3585
+ Building ${totalBuilds} preview(s)...`);
3586
+ console.log(" Building shared vendor bundle...");
3587
+ mkdirSync3(vendorsDir, { recursive: true });
3588
+ const vendorResult = await buildVendorBundle();
3589
+ if (!vendorResult.success) {
3590
+ console.error(` \u2717 Vendor bundle: ${vendorResult.error}`);
3591
+ return;
3592
+ }
3593
+ writeFileSync5(path14.join(vendorsDir, "runtime.js"), vendorResult.code);
3594
+ console.log(" \u2713 _vendors/runtime.js");
3595
+ const jsxResult = await buildJsxBundle("../_vendors/runtime.js");
3596
+ if (!jsxResult.success) {
3597
+ console.error(` \u2717 JSX bundle: ${jsxResult.error}`);
3598
+ return;
3599
+ }
3600
+ writeFileSync5(path14.join(vendorsDir, "jsx.js"), jsxResult.code);
3601
+ console.log(" \u2713 _vendors/jsx.js");
3602
+ for (const unit of units) {
3603
+ if (unit.type === "flow")
3604
+ continue;
3605
+ const previewDir = path14.join(previewsDir, unit.type + "s", unit.name);
3606
+ const previewPath = `${unit.type}s/${unit.name}`;
3607
+ const depth = previewPath.split("/").length;
3608
+ const vendorPath = "../".repeat(depth) + "_vendors/runtime.js";
3609
+ try {
3610
+ const config = await buildPreviewConfig(previewDir);
3611
+ const result = await buildOptimizedPreview(config, { vendorPath, resolveDir: previewDir });
3612
+ if (!result.success) {
3613
+ console.error(` \u2717 ${previewPath}: ${result.error}`);
3614
+ } else {
3615
+ const outputDir = path14.join(targetDir, previewPath);
3616
+ mkdirSync3(outputDir, { recursive: true });
3617
+ writeFileSync5(path14.join(outputDir, "index.html"), result.html);
3618
+ console.log(` \u2713 ${previewPath}`);
3619
+ }
3620
+ } catch (err) {
3621
+ console.error(` \u2717 ${previewPath}: ${err}`);
3622
+ }
3623
+ if (unit.type === "screen" && unit.files.states) {
3624
+ for (const stateFile of unit.files.states) {
3625
+ const stateName = stateFile.replace(/\.(tsx|jsx)$/, "");
3626
+ const stateVendorPath = "../".repeat(depth + 1) + "_vendors/runtime.js";
3627
+ try {
3628
+ const config = await buildPreviewConfig(previewDir, stateFile);
3629
+ const result = await buildOptimizedPreview(config, { vendorPath: stateVendorPath, resolveDir: previewDir });
3630
+ if (!result.success) {
3631
+ console.error(` \u2717 ${previewPath}/${stateName}: ${result.error}`);
3632
+ } else {
3633
+ const outputDir = path14.join(targetDir, previewPath, stateName);
3634
+ mkdirSync3(outputDir, { recursive: true });
3635
+ writeFileSync5(path14.join(outputDir, "index.html"), result.html);
3636
+ console.log(` \u2713 ${previewPath}/${stateName}`);
3637
+ }
3638
+ } catch (err) {
3639
+ console.error(` \u2717 ${previewPath}/${stateName}: ${err}`);
3172
3640
  }
3173
3641
  }
3174
- ],
3175
- resolve: {
3176
- alias: {
3177
- "@prev/ui": path9.join(srcRoot2, "ui"),
3178
- "@prev/theme": path9.join(srcRoot2, "theme"),
3179
- react: path9.join(cliNodeModules, "react"),
3180
- "react-dom": path9.join(cliNodeModules, "react-dom"),
3181
- "@tanstack/react-router": path9.join(cliNodeModules, "@tanstack/react-router"),
3182
- "@mdx-js/react": path9.join(cliNodeModules, "@mdx-js/react"),
3183
- mermaid: path9.join(cliNodeModules, "mermaid"),
3184
- dayjs: path9.join(cliNodeModules, "dayjs"),
3185
- "@terrastruct/d2": path9.join(cliNodeModules, "@terrastruct/d2"),
3186
- "use-sync-external-store": path9.join(cliNodeModules, "use-sync-external-store")
3187
- },
3188
- dedupe: [
3189
- "react",
3190
- "react-dom",
3191
- "@tanstack/react-router"
3192
- ]
3193
- },
3194
- optimizeDeps: {
3195
- noDiscovery: true,
3196
- holdUntilCrawlEnd: false,
3197
- include: [
3198
- "react-dom/client",
3199
- "use-sync-external-store",
3200
- "use-sync-external-store/shim/with-selector.js",
3201
- "mermaid",
3202
- "dayjs",
3203
- "@terrastruct/d2"
3204
- ],
3205
- exclude: [
3206
- "virtual:prev-config",
3207
- "virtual:prev-previews",
3208
- "virtual:prev-pages",
3209
- "virtual:prev-page-modules",
3210
- "@prev/theme"
3211
- ]
3212
- },
3213
- ssr: {
3214
- noExternal: true
3215
- },
3216
- server: {
3217
- port,
3218
- strictPort: false,
3219
- fs: {
3220
- allow: [rootDir, cliRoot2]
3221
- },
3222
- warmup: {
3223
- clientFiles: [
3224
- path9.join(srcRoot2, "theme/entry.tsx"),
3225
- path9.join(srcRoot2, "theme/styles.css")
3226
- ]
3642
+ }
3643
+ }
3644
+ }
3645
+
3646
+ // src/server/preview.ts
3647
+ import path15 from "path";
3648
+ import { existsSync as existsSync16, statSync as statSync3 } from "fs";
3649
+ async function startPreviewServer(options) {
3650
+ const { rootDir, port } = options;
3651
+ const distDir = path15.join(rootDir, "dist");
3652
+ if (!existsSync16(distDir)) {
3653
+ throw new Error(`No dist/ directory found. Run 'prev build' first.`);
3654
+ }
3655
+ const indexHtml = path15.join(distDir, "index.html");
3656
+ const server = Bun.serve({
3657
+ port,
3658
+ async fetch(req) {
3659
+ const url = new URL(req.url);
3660
+ let pathname = url.pathname;
3661
+ if (pathname !== "/" && pathname.endsWith("/")) {
3662
+ pathname = pathname.slice(0, -1);
3227
3663
  }
3228
- },
3229
- preview: {
3230
- port,
3231
- strictPort: false
3232
- },
3233
- build: {
3234
- outDir: path9.join(rootDir, "dist"),
3235
- reportCompressedSize: false,
3236
- chunkSizeWarningLimit: 1e4,
3237
- rollupOptions: {
3238
- input: {
3239
- main: path9.join(srcRoot2, "theme/index.html")
3240
- }
3664
+ const filePath = path15.join(distDir, pathname);
3665
+ if (filePath.startsWith(distDir) && existsSync16(filePath)) {
3666
+ try {
3667
+ if (statSync3(filePath).isFile()) {
3668
+ return new Response(Bun.file(filePath));
3669
+ }
3670
+ } catch {}
3241
3671
  }
3242
- },
3243
- assetsInclude: [
3244
- "**/node_modules/**/*.md",
3245
- "**/node_modules/**/*.mdx"
3246
- ]
3672
+ const indexPath = path15.join(distDir, pathname, "index.html");
3673
+ if (existsSync16(indexPath)) {
3674
+ return new Response(Bun.file(indexPath));
3675
+ }
3676
+ if (!pathname.includes(".") && existsSync16(indexHtml)) {
3677
+ return new Response(Bun.file(indexHtml));
3678
+ }
3679
+ return new Response("Not Found", { status: 404 });
3680
+ }
3681
+ });
3682
+ return {
3683
+ server,
3684
+ port: server.port,
3685
+ url: `http://localhost:${server.port}/`,
3686
+ stop: () => server.stop()
3247
3687
  };
3248
3688
  }
3249
3689
 
@@ -3278,10 +3718,8 @@ async function findAvailablePort(minPort, maxPort) {
3278
3718
  throw new Error(`No available port found between ${minPort} and ${maxPort}`);
3279
3719
  }
3280
3720
 
3281
- // src/vite/start.ts
3721
+ // src/server/start.ts
3282
3722
  import { exec } from "child_process";
3283
- import { existsSync as existsSync9, rmSync as rmSync3, copyFileSync } from "fs";
3284
- import path10 from "path";
3285
3723
  function printWelcome(type) {
3286
3724
  console.log();
3287
3725
  console.log(" \u2728 prev");
@@ -3296,7 +3734,6 @@ function printShortcuts() {
3296
3734
  console.log();
3297
3735
  console.log(" Shortcuts:");
3298
3736
  console.log(" o \u2192 open in browser");
3299
- console.log(" c \u2192 clear cache");
3300
3737
  console.log(" h \u2192 show this help");
3301
3738
  console.log(" q \u2192 quit");
3302
3739
  console.log();
@@ -3313,25 +3750,7 @@ function openBrowser(url) {
3313
3750
  exec(`${cmd} ${url}`);
3314
3751
  console.log(` \u2197 Opened ${url}`);
3315
3752
  }
3316
- function clearCache(rootDir) {
3317
- const viteCacheDir = path10.join(rootDir, ".vite");
3318
- const nodeModulesVite = path10.join(rootDir, "node_modules", ".vite");
3319
- let cleared = 0;
3320
- if (existsSync9(viteCacheDir)) {
3321
- rmSync3(viteCacheDir, { recursive: true });
3322
- cleared++;
3323
- }
3324
- if (existsSync9(nodeModulesVite)) {
3325
- rmSync3(nodeModulesVite, { recursive: true });
3326
- cleared++;
3327
- }
3328
- if (cleared === 0) {
3329
- console.log(" No cache to clear");
3330
- } else {
3331
- console.log(` \u2713 Cleared Vite cache`);
3332
- }
3333
- }
3334
- function setupKeyboardShortcuts(rootDir, url, quit) {
3753
+ function setupKeyboardShortcuts(url, quit) {
3335
3754
  if (!process.stdin.isTTY)
3336
3755
  return () => {};
3337
3756
  process.stdin.setRawMode(true);
@@ -3342,9 +3761,6 @@ function setupKeyboardShortcuts(rootDir, url, quit) {
3342
3761
  case "o":
3343
3762
  openBrowser(url);
3344
3763
  break;
3345
- case "c":
3346
- clearCache(rootDir);
3347
- break;
3348
3764
  case "h":
3349
3765
  printShortcuts();
3350
3766
  break;
@@ -3365,27 +3781,13 @@ function setupKeyboardShortcuts(rootDir, url, quit) {
3365
3781
  }
3366
3782
  async function startDev(rootDir, options = {}) {
3367
3783
  const port = options.port ?? await getRandomPort();
3368
- const config = await createViteConfig({
3784
+ const { server, url, stop } = await startDevServer({
3369
3785
  rootDir,
3370
- mode: "development",
3371
3786
  port,
3372
- include: options.include,
3373
- debug: options.debug
3787
+ include: options.include
3374
3788
  });
3375
- const server = await createServer2(config);
3376
- await server.listen();
3377
- const warmupPort = server.config.server.port;
3378
- fetch(`http://localhost:${warmupPort}/`).catch(() => {});
3379
- const debugCollector = getDebugCollector();
3380
- if (debugCollector) {
3381
- debugCollector.startPhase("serverReady");
3382
- const reportPath = debugCollector.writeReport();
3383
- console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
3384
- }
3385
- const actualPort = server.config.server.port || port;
3386
- const url = `http://localhost:${actualPort}/`;
3387
3789
  printWelcome("dev");
3388
- server.printUrls();
3790
+ console.log(` \u279C Local: ${url}`);
3389
3791
  printReady();
3390
3792
  let isShuttingDown = false;
3391
3793
  let cleanupStdin = () => {};
@@ -3401,17 +3803,10 @@ async function startDev(rootDir, options = {}) {
3401
3803
  Shutting down...`);
3402
3804
  }
3403
3805
  cleanupStdin();
3404
- const closePromise = server.close();
3405
- const timeoutPromise = new Promise((resolve) => {
3406
- setTimeout(() => {
3407
- console.log(" Force closing (timeout)...");
3408
- resolve();
3409
- }, 3000);
3410
- });
3411
- await Promise.race([closePromise, timeoutPromise]);
3806
+ stop();
3412
3807
  process.exit(0);
3413
3808
  };
3414
- cleanupStdin = setupKeyboardShortcuts(rootDir, url, () => shutdown());
3809
+ cleanupStdin = setupKeyboardShortcuts(url, () => shutdown());
3415
3810
  process.on("SIGINT", () => shutdown("SIGINT"));
3416
3811
  process.on("SIGTERM", () => shutdown("SIGTERM"));
3417
3812
  process.on("uncaughtException", (err) => {
@@ -3426,26 +3821,11 @@ async function buildSite(rootDir, options = {}) {
3426
3821
  console.log(" \u2728 prev build");
3427
3822
  console.log();
3428
3823
  console.log(" Building your documentation site...");
3429
- const config = await createViteConfig({
3824
+ await buildProductionSite({
3430
3825
  rootDir,
3431
- mode: "production",
3432
3826
  include: options.include,
3433
- base: options.base,
3434
- debug: options.debug
3827
+ base: options.base
3435
3828
  });
3436
- await build4(config);
3437
- const debugCollector = getDebugCollector();
3438
- if (debugCollector) {
3439
- debugCollector.startPhase("buildComplete");
3440
- const reportPath = debugCollector.writeReport();
3441
- console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
3442
- }
3443
- const distDir = path10.join(rootDir, "dist");
3444
- const indexPath = path10.join(distDir, "index.html");
3445
- const notFoundPath = path10.join(distDir, "404.html");
3446
- if (existsSync9(indexPath)) {
3447
- copyFileSync(indexPath, notFoundPath);
3448
- }
3449
3829
  console.log();
3450
3830
  console.log(" Done! Your site is ready in ./dist");
3451
3831
  console.log(" You can deploy this folder anywhere.");
@@ -3453,31 +3833,18 @@ async function buildSite(rootDir, options = {}) {
3453
3833
  }
3454
3834
  async function previewSite(rootDir, options = {}) {
3455
3835
  const port = options.port ?? await getRandomPort();
3456
- const config = await createViteConfig({
3457
- rootDir,
3458
- mode: "production",
3459
- port,
3460
- include: options.include,
3461
- debug: options.debug
3462
- });
3463
- const server = await preview(config);
3464
- const debugCollector = getDebugCollector();
3465
- if (debugCollector) {
3466
- debugCollector.startPhase("previewReady");
3467
- const reportPath = debugCollector.writeReport();
3468
- console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
3469
- }
3836
+ const { url, stop } = await startPreviewServer({ rootDir, port });
3470
3837
  printWelcome("preview");
3471
- server.printUrls();
3838
+ console.log(` \u279C Local: ${url}`);
3472
3839
  console.log();
3473
3840
  console.log(" Press Ctrl+C to stop.");
3474
3841
  console.log();
3475
- return server;
3842
+ return { url, stop };
3476
3843
  }
3477
3844
 
3478
3845
  // src/validators/index.ts
3479
- import { existsSync as existsSync10, readdirSync, readFileSync as readFileSync9 } from "fs";
3480
- import { join as join3 } from "path";
3846
+ import { existsSync as existsSync17, readdirSync, readFileSync as readFileSync12 } from "fs";
3847
+ import { join as join4 } from "path";
3481
3848
  import * as yaml3 from "js-yaml";
3482
3849
 
3483
3850
  // src/renderers/registry.ts
@@ -3512,10 +3879,10 @@ async function initializeAdapters() {
3512
3879
  // src/validators/schema-validator.ts
3513
3880
  import Ajv from "ajv";
3514
3881
  import addFormats from "ajv-formats";
3515
- import { readFileSync as readFileSync8 } from "fs";
3516
- import { join as join2, dirname as dirname3 } from "path";
3882
+ import { readFileSync as readFileSync11 } from "fs";
3883
+ import { join as join3, dirname as dirname3 } from "path";
3517
3884
  import { fileURLToPath as fileURLToPath4 } from "url";
3518
- var __dirname3 = dirname3(fileURLToPath4(import.meta.url));
3885
+ var __dirname2 = dirname3(fileURLToPath4(import.meta.url));
3519
3886
  var ajv = new Ajv({
3520
3887
  allErrors: true,
3521
3888
  strict: false,
@@ -3527,8 +3894,8 @@ function loadSchema(name) {
3527
3894
  if (schemaCache.has(name)) {
3528
3895
  return schemaCache.get(name);
3529
3896
  }
3530
- const schemaPath = join2(__dirname3, "..", "schemas", `${name}.schema.json`);
3531
- const schemaContent = readFileSync8(schemaPath, "utf-8");
3897
+ const schemaPath = join3(__dirname2, "..", "schemas", `${name}.schema.json`);
3898
+ const schemaContent = readFileSync11(schemaPath, "utf-8");
3532
3899
  const schema = JSON.parse(schemaContent);
3533
3900
  const validate = ajv.compile(schema);
3534
3901
  schemaCache.set(name, validate);
@@ -3630,7 +3997,7 @@ function validateAllLayouts(layoutByRenderer, targetRenderer) {
3630
3997
  init_primitives();
3631
3998
  function parseRef(ref) {
3632
3999
  const refStr = typeof ref === "string" ? ref : ref.ref;
3633
- const match = refStr.match(/^(screens|components|flows|atlas)\/([a-z0-9-]+)$/);
4000
+ const match = refStr.match(/^(screens|components|flows)\/([a-z0-9-]+)$/);
3634
4001
  if (!match)
3635
4002
  return null;
3636
4003
  return { type: match[1], id: match[2] };
@@ -3650,22 +4017,22 @@ function detectCycle(edges, nodeIds) {
3650
4017
  }
3651
4018
  const visited = new Set;
3652
4019
  const recursionStack = new Set;
3653
- const path11 = [];
4020
+ const path16 = [];
3654
4021
  function dfs(node) {
3655
4022
  visited.add(node);
3656
4023
  recursionStack.add(node);
3657
- path11.push(node);
4024
+ path16.push(node);
3658
4025
  for (const neighbor of adjacency.get(node) || []) {
3659
4026
  if (!visited.has(neighbor)) {
3660
4027
  const cycle = dfs(neighbor);
3661
4028
  if (cycle)
3662
4029
  return cycle;
3663
4030
  } else if (recursionStack.has(neighbor)) {
3664
- const cycleStart = path11.indexOf(neighbor);
3665
- return [...path11.slice(cycleStart), neighbor];
4031
+ const cycleStart = path16.indexOf(neighbor);
4032
+ return [...path16.slice(cycleStart), neighbor];
3666
4033
  }
3667
4034
  }
3668
- path11.pop();
4035
+ path16.pop();
3669
4036
  recursionStack.delete(node);
3670
4037
  return null;
3671
4038
  }
@@ -3678,13 +4045,13 @@ function detectCycle(edges, nodeIds) {
3678
4045
  }
3679
4046
  return null;
3680
4047
  }
3681
- function validateRef(ref, context, path11) {
4048
+ function validateRef(ref, context, path16) {
3682
4049
  const errors = [];
3683
4050
  const warnings = [];
3684
4051
  const parsed = parseRef(ref);
3685
4052
  if (!parsed) {
3686
4053
  errors.push({
3687
- path: path11,
4054
+ path: path16,
3688
4055
  message: `Invalid reference format: ${JSON.stringify(ref)}`,
3689
4056
  code: "INVALID_REF"
3690
4057
  });
@@ -3693,7 +4060,7 @@ function validateRef(ref, context, path11) {
3693
4060
  const knownSet = context.knownIds[parsed.type];
3694
4061
  if (!knownSet?.has(parsed.id)) {
3695
4062
  errors.push({
3696
- path: path11,
4063
+ path: path16,
3697
4064
  message: `Reference "${parsed.type}/${parsed.id}" not found`,
3698
4065
  code: "INVALID_REF"
3699
4066
  });
@@ -3705,13 +4072,13 @@ function validateRef(ref, context, path11) {
3705
4072
  const screenStates = context.screenStates.get(parsed.id);
3706
4073
  if (!screenStates) {
3707
4074
  errors.push({
3708
- path: path11,
4075
+ path: path16,
3709
4076
  message: `State "${state}" referenced but screen "${parsed.id}" has no states defined`,
3710
4077
  code: "INVALID_STATE_REF"
3711
4078
  });
3712
4079
  } else if (!screenStates.has(state)) {
3713
4080
  errors.push({
3714
- path: path11,
4081
+ path: path16,
3715
4082
  message: `State "${state}" not found in screen "${parsed.id}". Available states: ${Array.from(screenStates).join(", ") || "none"}`,
3716
4083
  code: "INVALID_STATE_REF"
3717
4084
  });
@@ -3762,50 +4129,6 @@ function validateFlow(config, context, configPath) {
3762
4129
  }
3763
4130
  return { errors, warnings };
3764
4131
  }
3765
- function validateAtlas(config, context, configPath) {
3766
- const errors = [];
3767
- const warnings = [];
3768
- if (!config.nodes || config.nodes.length === 0) {
3769
- return { errors, warnings };
3770
- }
3771
- const nodeIds = new Set(config.nodes.map((n) => n.id));
3772
- for (let i = 0;i < config.nodes.length; i++) {
3773
- const node = config.nodes[i];
3774
- if (node.ref) {
3775
- const result = validateRef(node.ref, context, `${configPath}/nodes/${i}/ref`);
3776
- errors.push(...result.errors);
3777
- warnings.push(...result.warnings);
3778
- }
3779
- }
3780
- if (config.relationships) {
3781
- for (let i = 0;i < config.relationships.length; i++) {
3782
- const rel = config.relationships[i];
3783
- if (!nodeIds.has(rel.from)) {
3784
- errors.push({
3785
- path: `${configPath}/relationships/${i}/from`,
3786
- message: `Relationship "from" references unknown node "${rel.from}". Available nodes: ${Array.from(nodeIds).join(", ")}`,
3787
- code: "INVALID_NODE_REF"
3788
- });
3789
- }
3790
- if (!nodeIds.has(rel.to)) {
3791
- errors.push({
3792
- path: `${configPath}/relationships/${i}/to`,
3793
- message: `Relationship "to" references unknown node "${rel.to}". Available nodes: ${Array.from(nodeIds).join(", ")}`,
3794
- code: "INVALID_NODE_REF"
3795
- });
3796
- }
3797
- }
3798
- const cycle = detectCycle(config.relationships, nodeIds);
3799
- if (cycle) {
3800
- errors.push({
3801
- path: `${configPath}/relationships`,
3802
- message: `Circular dependency detected in atlas relationships: ${cycle.join(" \u2192 ")}`,
3803
- code: "CIRCULAR_DEPENDENCY"
3804
- });
3805
- }
3806
- }
3807
- return { errors, warnings };
3808
- }
3809
4132
  function validateScreenTemplate(template, slots, states, context, configPath) {
3810
4133
  const errors = [];
3811
4134
  const warnings = [];
@@ -3901,25 +4224,25 @@ function validateRendererKeys(layoutByRenderer, configPath) {
3901
4224
  }
3902
4225
  return { errors, warnings };
3903
4226
  }
3904
- function extractComponentRefs(node, path11, refs) {
4227
+ function extractComponentRefs(node, path16, refs) {
3905
4228
  if (!node || typeof node !== "object")
3906
4229
  return;
3907
4230
  if (Array.isArray(node)) {
3908
4231
  node.forEach((item, index) => {
3909
- extractComponentRefs(item, `${path11}/${index}`, refs);
4232
+ extractComponentRefs(item, `${path16}/${index}`, refs);
3910
4233
  });
3911
4234
  return;
3912
4235
  }
3913
4236
  const obj = node;
3914
4237
  if (obj.type === "ComponentRef" && typeof obj.ref === "string") {
3915
- refs.push({ ref: obj.ref, path: `${path11}/ref` });
4238
+ refs.push({ ref: obj.ref, path: `${path16}/ref` });
3916
4239
  }
3917
4240
  if (obj.children) {
3918
- extractComponentRefs(obj.children, `${path11}/children`, refs);
4241
+ extractComponentRefs(obj.children, `${path16}/children`, refs);
3919
4242
  }
3920
4243
  if (obj.props && typeof obj.props === "object") {
3921
4244
  for (const [key, value] of Object.entries(obj.props)) {
3922
- extractComponentRefs(value, `${path11}/props/${key}`, refs);
4245
+ extractComponentRefs(value, `${path16}/props/${key}`, refs);
3923
4246
  }
3924
4247
  }
3925
4248
  }
@@ -3932,11 +4255,11 @@ function validateLayoutComponentRefs(layoutByRenderer, context, configPath) {
3932
4255
  for (const [rendererKey, layout] of Object.entries(layoutByRenderer)) {
3933
4256
  const refs = [];
3934
4257
  extractComponentRefs(layout, `${configPath}/layoutByRenderer/${rendererKey}`, refs);
3935
- for (const { ref, path: path11 } of refs) {
4258
+ for (const { ref, path: path16 } of refs) {
3936
4259
  const match = ref.match(/^components\/([a-z0-9-]+)$/);
3937
4260
  if (!match) {
3938
4261
  errors.push({
3939
- path: path11,
4262
+ path: path16,
3940
4263
  message: `Invalid ComponentRef format: "${ref}". Expected "components/<id>"`,
3941
4264
  code: "INVALID_REF"
3942
4265
  });
@@ -3945,7 +4268,7 @@ function validateLayoutComponentRefs(layoutByRenderer, context, configPath) {
3945
4268
  const componentId = match[1];
3946
4269
  if (!context.knownIds.components.has(componentId)) {
3947
4270
  errors.push({
3948
- path: path11,
4271
+ path: path16,
3949
4272
  message: `ComponentRef references unknown component "${componentId}"`,
3950
4273
  code: "INVALID_REF"
3951
4274
  });
@@ -3993,11 +4316,6 @@ function validateSemantics(config, context, configPath = "") {
3993
4316
  errors.push(...result.errors);
3994
4317
  warnings.push(...result.warnings);
3995
4318
  }
3996
- if (config.kind === "atlas") {
3997
- const result = validateAtlas(config, context, configPath);
3998
- errors.push(...result.errors);
3999
- warnings.push(...result.warnings);
4000
- }
4001
4319
  return {
4002
4320
  valid: errors.length === 0,
4003
4321
  errors,
@@ -4010,8 +4328,7 @@ function createValidationContext(rootDir) {
4010
4328
  knownIds: {
4011
4329
  components: new Set,
4012
4330
  screens: new Set,
4013
- flows: new Set,
4014
- atlas: new Set
4331
+ flows: new Set
4015
4332
  },
4016
4333
  screenStates: new Map
4017
4334
  };
@@ -4040,25 +4357,25 @@ function checkDuplicateIds(ids, type) {
4040
4357
  }
4041
4358
 
4042
4359
  // src/validators/index.ts
4043
- var PREVIEW_TYPES = ["components", "screens", "flows", "atlas"];
4360
+ var PREVIEW_TYPES = ["components", "screens", "flows"];
4044
4361
  function findPreviewRoot(startDir) {
4045
4362
  let current = startDir;
4046
4363
  while (current !== "/") {
4047
- const previewsDir = join3(current, ".previews");
4048
- if (existsSync10(previewsDir)) {
4364
+ const previewsDir = join4(current, ".previews");
4365
+ if (existsSync17(previewsDir)) {
4049
4366
  return previewsDir;
4050
4367
  }
4051
- const previewsDirAlt = join3(current, "previews");
4052
- if (existsSync10(previewsDirAlt)) {
4368
+ const previewsDirAlt = join4(current, "previews");
4369
+ if (existsSync17(previewsDirAlt)) {
4053
4370
  return previewsDirAlt;
4054
4371
  }
4055
- current = join3(current, "..");
4372
+ current = join4(current, "..");
4056
4373
  }
4057
4374
  return null;
4058
4375
  }
4059
4376
  function scanPreviewType(previewRoot, type) {
4060
- const typeDir = join3(previewRoot, type);
4061
- if (!existsSync10(typeDir)) {
4377
+ const typeDir = join4(previewRoot, type);
4378
+ if (!existsSync17(typeDir)) {
4062
4379
  return [];
4063
4380
  }
4064
4381
  const units = [];
@@ -4066,10 +4383,10 @@ function scanPreviewType(previewRoot, type) {
4066
4383
  for (const entry of entries) {
4067
4384
  if (!entry.isDirectory())
4068
4385
  continue;
4069
- const unitPath = join3(typeDir, entry.name);
4070
- const configYaml = join3(unitPath, "config.yaml");
4071
- const configYml = join3(unitPath, "config.yml");
4072
- const configPath = existsSync10(configYaml) ? configYaml : existsSync10(configYml) ? configYml : null;
4386
+ const unitPath = join4(typeDir, entry.name);
4387
+ const configYaml = join4(unitPath, "config.yaml");
4388
+ const configYml = join4(unitPath, "config.yml");
4389
+ const configPath = existsSync17(configYaml) ? configYaml : existsSync17(configYml) ? configYml : null;
4073
4390
  if (configPath) {
4074
4391
  units.push({
4075
4392
  id: entry.name,
@@ -4082,7 +4399,7 @@ function scanPreviewType(previewRoot, type) {
4082
4399
  }
4083
4400
  function loadConfig2(configPath) {
4084
4401
  try {
4085
- const content = readFileSync9(configPath, "utf-8");
4402
+ const content = readFileSync12(configPath, "utf-8");
4086
4403
  const parsed = yaml3.load(content);
4087
4404
  if (parsed && typeof parsed === "object") {
4088
4405
  return parsed;
@@ -4133,8 +4450,7 @@ async function validate(rootDir = process.cwd(), options = {}) {
4133
4450
  summary: {
4134
4451
  components: { total: 0, valid: 0, invalid: 0 },
4135
4452
  screens: { total: 0, valid: 0, invalid: 0 },
4136
- flows: { total: 0, valid: 0, invalid: 0 },
4137
- atlas: { total: 0, valid: 0, invalid: 0 }
4453
+ flows: { total: 0, valid: 0, invalid: 0 }
4138
4454
  },
4139
4455
  errors: [{
4140
4456
  file: rootDir,
@@ -4151,8 +4467,7 @@ async function validate(rootDir = process.cwd(), options = {}) {
4151
4467
  summary: {
4152
4468
  components: { total: 0, valid: 0, invalid: 0 },
4153
4469
  screens: { total: 0, valid: 0, invalid: 0 },
4154
- flows: { total: 0, valid: 0, invalid: 0 },
4155
- atlas: { total: 0, valid: 0, invalid: 0 }
4470
+ flows: { total: 0, valid: 0, invalid: 0 }
4156
4471
  },
4157
4472
  errors: [],
4158
4473
  warnings: []
@@ -4284,7 +4599,7 @@ function formatValidationResult(result) {
4284
4599
  }
4285
4600
 
4286
4601
  // src/typecheck/index.ts
4287
- import path11 from "path";
4602
+ import path16 from "path";
4288
4603
  import { fileURLToPath as fileURLToPath5 } from "url";
4289
4604
  import { spawn } from "child_process";
4290
4605
  function getTsgoPath() {
@@ -4293,9 +4608,9 @@ function getTsgoPath() {
4293
4608
  const platformPkg = `@typescript/native-preview-${platform}-${arch}`;
4294
4609
  try {
4295
4610
  const pkgJsonUrl = import.meta.resolve(`${platformPkg}/package.json`);
4296
- const pkgDir = path11.dirname(fileURLToPath5(pkgJsonUrl));
4611
+ const pkgDir = path16.dirname(fileURLToPath5(pkgJsonUrl));
4297
4612
  const exe = platform === "win32" ? "tsgo.exe" : "tsgo";
4298
- return path11.join(pkgDir, "lib", exe);
4613
+ return path16.join(pkgDir, "lib", exe);
4299
4614
  } catch {
4300
4615
  throw new Error(`Unable to find tsgo for ${platform}-${arch}. ` + `Package ${platformPkg} may not be installed or your platform is unsupported.`);
4301
4616
  }
@@ -4303,15 +4618,15 @@ function getTsgoPath() {
4303
4618
  function getTypeRootsPath() {
4304
4619
  try {
4305
4620
  const reactTypesUrl = import.meta.resolve("@types/react/package.json");
4306
- const reactTypesDir = path11.dirname(fileURLToPath5(reactTypesUrl));
4307
- return path11.dirname(reactTypesDir);
4621
+ const reactTypesDir = path16.dirname(fileURLToPath5(reactTypesUrl));
4622
+ return path16.dirname(reactTypesDir);
4308
4623
  } catch {
4309
4624
  throw new Error("Unable to find @types/react. Make sure prev-cli has @types/react in dependencies.");
4310
4625
  }
4311
4626
  }
4312
4627
  async function typecheck(rootDir, options = {}) {
4313
4628
  const {
4314
- previewsDir = path11.join(rootDir, "previews"),
4629
+ previewsDir = path16.join(rootDir, "previews"),
4315
4630
  include = ["**/*.{ts,tsx}"],
4316
4631
  strict = true,
4317
4632
  verbose = false
@@ -4340,7 +4655,7 @@ async function typecheck(rootDir, options = {}) {
4340
4655
  }
4341
4656
  if (verbose) {
4342
4657
  console.log(` files: ${files.length}`);
4343
- files.forEach((f) => console.log(` - ${path11.relative(rootDir, f)}`));
4658
+ files.forEach((f) => console.log(` - ${path16.relative(rootDir, f)}`));
4344
4659
  }
4345
4660
  const args = [
4346
4661
  "--noEmit",
@@ -4421,8 +4736,8 @@ ${result.output}
4421
4736
  }
4422
4737
 
4423
4738
  // src/migrate.ts
4424
- import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
4425
- import { join as join4 } from "path";
4739
+ import { existsSync as existsSync18, readdirSync as readdirSync2, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
4740
+ import { join as join5 } from "path";
4426
4741
  import * as yaml4 from "js-yaml";
4427
4742
  var PREVIEW_TYPE_FOLDERS2 = ["components", "screens", "flows", "atlas"];
4428
4743
  var KIND_MAP = {
@@ -4434,21 +4749,21 @@ var KIND_MAP = {
4434
4749
  function findPreviewRoot2(startDir) {
4435
4750
  let current = startDir;
4436
4751
  while (current !== "/") {
4437
- const previewsDir = join4(current, ".previews");
4438
- if (existsSync11(previewsDir)) {
4752
+ const previewsDir = join5(current, ".previews");
4753
+ if (existsSync18(previewsDir)) {
4439
4754
  return previewsDir;
4440
4755
  }
4441
- const previewsDirAlt = join4(current, "previews");
4442
- if (existsSync11(previewsDirAlt)) {
4756
+ const previewsDirAlt = join5(current, "previews");
4757
+ if (existsSync18(previewsDirAlt)) {
4443
4758
  return previewsDirAlt;
4444
4759
  }
4445
- current = join4(current, "..");
4760
+ current = join5(current, "..");
4446
4761
  }
4447
4762
  return null;
4448
4763
  }
4449
4764
  function migrateConfig(configPath, folderId, kind) {
4450
4765
  try {
4451
- const content = readFileSync10(configPath, "utf-8");
4766
+ const content = readFileSync13(configPath, "utf-8");
4452
4767
  const config = yaml4.load(content);
4453
4768
  if (!config || typeof config !== "object") {
4454
4769
  return { migrated: false, error: "Invalid YAML" };
@@ -4508,18 +4823,18 @@ async function migrateConfigs(rootDir = process.cwd()) {
4508
4823
  errors: []
4509
4824
  };
4510
4825
  for (const type of PREVIEW_TYPE_FOLDERS2) {
4511
- const typeDir = join4(previewRoot, type);
4512
- if (!existsSync11(typeDir))
4826
+ const typeDir = join5(previewRoot, type);
4827
+ if (!existsSync18(typeDir))
4513
4828
  continue;
4514
4829
  const kind = KIND_MAP[type];
4515
4830
  const entries = readdirSync2(typeDir, { withFileTypes: true });
4516
4831
  for (const entry of entries) {
4517
4832
  if (!entry.isDirectory())
4518
4833
  continue;
4519
- const unitPath = join4(typeDir, entry.name);
4520
- const configYaml = join4(unitPath, "config.yaml");
4521
- const configYml = join4(unitPath, "config.yml");
4522
- const configPath = existsSync11(configYaml) ? configYaml : existsSync11(configYml) ? configYml : null;
4834
+ const unitPath = join5(typeDir, entry.name);
4835
+ const configYaml = join5(unitPath, "config.yaml");
4836
+ const configYml = join5(unitPath, "config.yml");
4837
+ const configPath = existsSync18(configYaml) ? configYaml : existsSync18(configYml) ? configYml : null;
4523
4838
  if (!configPath) {
4524
4839
  result.skipped++;
4525
4840
  continue;
@@ -4567,14 +4882,14 @@ import { createHash } from "crypto";
4567
4882
  import { readdir, rm, stat, mkdir } from "fs/promises";
4568
4883
  import { exec as exec2 } from "child_process";
4569
4884
  import { promisify } from "util";
4570
- import path12 from "path";
4571
- import os2 from "os";
4885
+ import path17 from "path";
4886
+ import os from "os";
4572
4887
  var execAsync = promisify(exec2);
4573
- var DEFAULT_CACHE_ROOT = path12.join(os2.homedir(), ".cache/prev");
4888
+ var DEFAULT_CACHE_ROOT = path17.join(os.homedir(), ".cache/prev");
4574
4889
  async function getCacheDir(rootDir, branch) {
4575
4890
  const resolvedBranch = branch ?? await getCurrentBranch(rootDir);
4576
4891
  const hash = createHash("sha1").update(`${rootDir}:${resolvedBranch}`).digest("hex").slice(0, 12);
4577
- return path12.join(DEFAULT_CACHE_ROOT, hash);
4892
+ return path17.join(DEFAULT_CACHE_ROOT, hash);
4578
4893
  }
4579
4894
  async function getCurrentBranch(rootDir) {
4580
4895
  try {
@@ -4596,7 +4911,7 @@ async function cleanCache(options) {
4596
4911
  }
4597
4912
  let removed = 0;
4598
4913
  for (const dir of dirs) {
4599
- const fullPath = path12.join(cacheRoot, dir);
4914
+ const fullPath = path17.join(cacheRoot, dir);
4600
4915
  try {
4601
4916
  const info = await stat(fullPath);
4602
4917
  if (info.isDirectory() && now - info.mtimeMs > maxAge) {
@@ -4612,15 +4927,15 @@ async function cleanCache(options) {
4612
4927
  import yaml5 from "js-yaml";
4613
4928
  function getVersion() {
4614
4929
  try {
4615
- let dir = path13.dirname(fileURLToPath6(import.meta.url));
4930
+ let dir = path18.dirname(fileURLToPath6(import.meta.url));
4616
4931
  for (let i = 0;i < 5; i++) {
4617
- const pkgPath = path13.join(dir, "package.json");
4618
- if (existsSync12(pkgPath)) {
4619
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
4932
+ const pkgPath = path18.join(dir, "package.json");
4933
+ if (existsSync19(pkgPath)) {
4934
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
4620
4935
  if (pkg.name === "prev-cli")
4621
4936
  return pkg.version;
4622
4937
  }
4623
- dir = path13.dirname(dir);
4938
+ dir = path18.dirname(dir);
4624
4939
  }
4625
4940
  } catch {}
4626
4941
  return "unknown";
@@ -4640,7 +4955,7 @@ var { values, positionals } = parseArgs({
4640
4955
  allowPositionals: true
4641
4956
  });
4642
4957
  var command = positionals[0] || "dev";
4643
- var rootDir = path13.resolve(values.cwd || (command === "config" || command === "create" ? "." : positionals[1]) || ".");
4958
+ var rootDir = path18.resolve(values.cwd || (command === "config" || command === "create" ? "." : positionals[1]) || ".");
4644
4959
  function printHelp() {
4645
4960
  console.log(`
4646
4961
  prev - Zero-config documentation site generator
@@ -4654,7 +4969,7 @@ Usage:
4654
4969
  prev migrate Migrate configs from v1 to v2 format
4655
4970
  prev create [name] Create preview in previews/<name>/ (default: "example")
4656
4971
  prev config [subcommand] Manage configuration
4657
- prev clearcache Clear Vite cache (.vite directory)
4972
+ prev clearcache Clear build cache
4658
4973
  prev clean [options] Remove old prev-cli caches
4659
4974
 
4660
4975
  Config subcommands:
@@ -4758,30 +5073,18 @@ Examples:
4758
5073
  prev clean -d 7 Remove caches older than 7 days
4759
5074
  `);
4760
5075
  }
4761
- async function clearViteCache(rootDir2) {
5076
+ async function clearBuildCache(rootDir2) {
4762
5077
  let cleared = 0;
4763
5078
  try {
4764
5079
  const prevCacheDir = await getCacheDir(rootDir2);
4765
- if (existsSync12(prevCacheDir)) {
5080
+ if (existsSync19(prevCacheDir)) {
4766
5081
  rmSync4(prevCacheDir, { recursive: true });
4767
5082
  cleared++;
4768
5083
  console.log(` \u2713 Removed ${prevCacheDir}`);
4769
5084
  }
4770
5085
  } catch {}
4771
- const viteCacheDir = path13.join(rootDir2, ".vite");
4772
- const nodeModulesVite = path13.join(rootDir2, "node_modules", ".vite");
4773
- if (existsSync12(viteCacheDir)) {
4774
- rmSync4(viteCacheDir, { recursive: true });
4775
- cleared++;
4776
- console.log(` \u2713 Removed .vite/`);
4777
- }
4778
- if (existsSync12(nodeModulesVite)) {
4779
- rmSync4(nodeModulesVite, { recursive: true });
4780
- cleared++;
4781
- console.log(` \u2713 Removed node_modules/.vite/`);
4782
- }
4783
5086
  if (cleared === 0) {
4784
- console.log(" No Vite cache found");
5087
+ console.log(" No build cache found");
4785
5088
  } else {
4786
5089
  console.log(`
4787
5090
  Cleared ${cleared} cache director${cleared === 1 ? "y" : "ies"}`);
@@ -4821,7 +5124,7 @@ function handleConfig(rootDir2, subcommand) {
4821
5124
  break;
4822
5125
  }
4823
5126
  case "init": {
4824
- const targetPath = path13.join(rootDir2, ".prev.yaml");
5127
+ const targetPath = path18.join(rootDir2, ".prev.yaml");
4825
5128
  if (configPath) {
4826
5129
  console.log(`
4827
5130
  Config already exists: ${configPath}
@@ -4880,8 +5183,8 @@ Available subcommands: show, init, path`);
4880
5183
  }
4881
5184
  }
4882
5185
  function createPreview(rootDir2, name) {
4883
- const previewDir = path13.join(rootDir2, "previews", name);
4884
- if (existsSync12(previewDir)) {
5186
+ const previewDir = path18.join(rootDir2, "previews", name);
5187
+ if (existsSync19(previewDir)) {
4885
5188
  console.error(`Preview "${name}" already exists at: ${previewDir}`);
4886
5189
  process.exit(1);
4887
5190
  }
@@ -5006,8 +5309,8 @@ export default function App() {
5006
5309
  .dark\\:text-white { color: #fff; }
5007
5310
  }
5008
5311
  `;
5009
- writeFileSync7(path13.join(previewDir, "App.tsx"), appTsx);
5010
- writeFileSync7(path13.join(previewDir, "styles.css"), stylesCss);
5312
+ writeFileSync7(path18.join(previewDir, "App.tsx"), appTsx);
5313
+ writeFileSync7(path18.join(previewDir, "styles.css"), stylesCss);
5011
5314
  console.log(`
5012
5315
  \u2728 Created preview: previews/${name}/
5013
5316
 
@@ -5056,7 +5359,7 @@ async function main() {
5056
5359
  createPreview(rootDir, previewName);
5057
5360
  break;
5058
5361
  case "clearcache":
5059
- await clearViteCache(rootDir);
5362
+ await clearBuildCache(rootDir);
5060
5363
  break;
5061
5364
  case "config":
5062
5365
  handleConfig(rootDir, positionals[1]);