prev-cli 0.24.20 → 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 -1714
  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,962 +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
+ }
2817
+ }
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
+ }));
2692
2956
  }
2693
- let dir = cliRoot2;
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
- development: false,
2732
- include: [
2733
- path9.join(rootDir, "**/*.md"),
2734
- path9.join(rootDir, "**/*.mdx")
2735
- ],
2736
- exclude: [
2737
- "**/node_modules/**",
2738
- "**/.git/**"
2739
- ]
2740
- }),
2741
- react(),
2742
- createConfigPlugin(config),
2743
- pagesPlugin(rootDir, { include }),
2744
- entryPlugin(rootDir),
2745
- previewsPlugin(rootDir),
2746
- tokensPlugin(rootDir),
2747
- {
2748
- name: "prev-user-tokens",
2749
- configResolved() {
2750
- const userTokensPath = path9.join(rootDir, "previews/tokens.yaml");
2751
- if (existsSync8(userTokensPath)) {
2752
- globalThis.__PREV_USER_TOKENS_PATH = userTokensPath;
2753
- } else {
2754
- 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
+ }
2755
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 });
2756
3301
  }
2757
- },
2758
- {
2759
- name: "prev-config-api",
2760
- configureServer(server) {
2761
- server.middlewares.use("/__prev/config", async (req, res) => {
2762
- if (req.method === "POST") {
2763
- let body = "";
2764
- req.on("data", (chunk) => {
2765
- 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 };
2766
3316
  });
2767
- req.on("end", () => {
2768
- try {
2769
- const { path: pathKey, order } = JSON.parse(body);
2770
- updateOrder(rootDir, pathKey, order);
2771
- res.statusCode = 200;
2772
- res.end(JSON.stringify({ success: true }));
2773
- } catch (e) {
2774
- res.statusCode = 400;
2775
- res.end(JSON.stringify({ error: String(e) }));
2776
- }
3317
+ build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
3318
+ return { path: options.vendorPath, external: true };
2777
3319
  });
2778
- return;
2779
- }
2780
- res.statusCode = 405;
2781
- res.end();
2782
- });
2783
- }
2784
- },
2785
- {
2786
- name: "prev-spa-fallback",
2787
- configureServer(server) {
2788
- return () => {
2789
- server.middlewares.use((req, res, next) => {
2790
- const urlPath = req.url?.split("?")[0] || "";
2791
- if (urlPath.startsWith("/__") || urlPath.startsWith("/@") || urlPath.startsWith("/node_modules") || urlPath.includes(".")) {
2792
- return next();
2793
- }
2794
- const indexPath = path9.join(srcRoot2, "theme/index.html");
2795
- if (existsSync8(indexPath)) {
2796
- server.transformIndexHtml(req.url, readFileSync7(indexPath, "utf-8")).then((html) => {
2797
- res.setHeader("Content-Type", "text/html");
2798
- res.end(html);
2799
- }).catch(next);
2800
- return;
2801
- }
2802
- next();
2803
- });
2804
- };
2805
- }
2806
- },
2807
- {
2808
- name: "prev-jsx-bundle",
2809
- configureServer(server) {
2810
- let cachedBundle = null;
2811
- server.middlewares.use("/_prev/jsx.js", async (req, res, next) => {
2812
- if (req.method !== "GET")
2813
- return next();
2814
- try {
2815
- if (!cachedBundle) {
2816
- const jsxEntry = path9.join(srcRoot2, "jsx/index.ts");
2817
- const result = await viteBuild({
2818
- logLevel: "silent",
2819
- define: {
2820
- "process.env.NODE_ENV": '"production"'
2821
- },
2822
- esbuild: {
2823
- jsx: "automatic",
2824
- jsxImportSource: "react",
2825
- jsxDev: false
2826
- },
2827
- build: {
2828
- write: false,
2829
- lib: {
2830
- entry: jsxEntry,
2831
- formats: ["es"],
2832
- fileName: "jsx"
2833
- },
2834
- rollupOptions: {
2835
- external: ["react", "react-dom", "react/jsx-runtime", "zod"],
2836
- output: {
2837
- globals: {
2838
- react: "React",
2839
- "react-dom": "ReactDOM"
2840
- }
2841
- }
2842
- },
2843
- minify: false
2844
- }
2845
- });
2846
- const output = Array.isArray(result) ? result[0] : result;
2847
- if (!("output" in output)) {
2848
- throw new Error("Unexpected build result type");
2849
- }
2850
- const jsFile = output.output.find((f) => f.type === "chunk");
2851
- if (jsFile && "code" in jsFile) {
2852
- 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"');
2853
- }
2854
- }
2855
- if (cachedBundle) {
2856
- res.setHeader("Content-Type", "application/javascript");
2857
- res.setHeader("Cache-Control", "no-cache");
2858
- res.end(cachedBundle);
2859
- return;
2860
- }
2861
- res.statusCode = 500;
2862
- res.end("Failed to bundle jsx primitives");
2863
- } catch (err) {
2864
- console.error("Error bundling jsx:", err);
2865
- res.statusCode = 500;
2866
- res.end(String(err));
2867
- }
2868
- });
2869
- server.watcher.on("change", (file) => {
2870
- if (file.includes("/jsx/")) {
2871
- cachedBundle = null;
2872
- }
2873
- });
2874
- }
2875
- },
2876
- {
2877
- name: "prev-components-bundle",
2878
- configureServer(server) {
2879
- const componentCache = new Map;
2880
- server.middlewares.use(async (req, res, next) => {
2881
- const urlPath = req.url?.split("?")[0] || "";
2882
- const match = urlPath.match(/^\/_prev\/components\/([^/]+)\.js$/);
2883
- if (!match || req.method !== "GET")
2884
- return next();
2885
- const componentName = match[1];
2886
- const componentEntry = path9.join(rootDir, "previews/components", componentName, "index.tsx");
2887
- if (!existsSync8(componentEntry)) {
2888
- res.statusCode = 404;
2889
- res.end(`Component not found: ${componentName}`);
2890
- return;
2891
- }
2892
- try {
2893
- let bundledCode = componentCache.get(componentName);
2894
- if (!bundledCode) {
2895
- const result = await viteBuild({
2896
- logLevel: "silent",
2897
- define: {
2898
- "process.env.NODE_ENV": '"production"'
2899
- },
2900
- esbuild: {
2901
- jsx: "automatic",
2902
- jsxImportSource: "react",
2903
- jsxDev: false
2904
- },
2905
- build: {
2906
- write: false,
2907
- lib: {
2908
- entry: componentEntry,
2909
- formats: ["es"],
2910
- fileName: componentName
2911
- },
2912
- rollupOptions: {
2913
- external: ["react", "react-dom", "react/jsx-runtime", "@prev/jsx"],
2914
- output: {
2915
- globals: {
2916
- react: "React",
2917
- "react-dom": "ReactDOM"
2918
- }
2919
- }
2920
- },
2921
- 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" };
2922
3339
  }
2923
- });
2924
- const output = Array.isArray(result) ? result[0] : result;
2925
- if (!("output" in output)) {
2926
- throw new Error("Unexpected build result type");
2927
- }
2928
- const jsFile = output.output.find((f) => f.type === "chunk");
2929
- if (jsFile && "code" in jsFile) {
2930
- 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"`);
2931
- componentCache.set(componentName, bundledCode);
2932
- }
2933
- }
2934
- if (bundledCode) {
2935
- res.setHeader("Content-Type", "application/javascript");
2936
- res.setHeader("Cache-Control", "no-cache");
2937
- res.end(bundledCode);
2938
- return;
2939
- }
2940
- res.statusCode = 500;
2941
- res.end(`Failed to bundle component: ${componentName}`);
2942
- } catch (err) {
2943
- console.error(`Error bundling component ${componentName}:`, err);
2944
- res.statusCode = 500;
2945
- res.end(String(err));
2946
- }
2947
- });
2948
- server.watcher.on("change", (file) => {
2949
- if (file.includes("/previews/components/")) {
2950
- const match = file.match(/\/previews\/components\/([^/]+)\//);
2951
- if (match) {
2952
- componentCache.delete(match[1]);
2953
- }
2954
- }
2955
- });
2956
- }
2957
- },
2958
- {
2959
- name: "prev-preview-server",
2960
- resolveId(id) {
2961
- if (id.startsWith("/_preview/")) {
2962
- const relativePath = id.slice("/_preview/".length);
2963
- const previewsDir = path9.join(rootDir, "previews");
2964
- const resolved = path9.resolve(previewsDir, relativePath);
2965
- if (resolved.startsWith(previewsDir)) {
2966
- return resolved;
2967
- }
2968
- }
2969
- },
2970
- configureServer(server) {
2971
- server.middlewares.use(async (req, res, next) => {
2972
- const urlPath = req.url?.split("?")[0] || "";
2973
- if (urlPath.startsWith("/_preview-bundle/")) {
2974
- const startTime = performance.now();
2975
- const previewPath = decodeURIComponent(urlPath.slice("/_preview-bundle/".length));
2976
- const previewDir = path9.join(rootDir, "previews", previewPath);
2977
- if (!previewDir.startsWith(path9.join(rootDir, "previews"))) {
2978
- res.statusCode = 403;
2979
- res.end("Forbidden");
2980
- return;
2981
- }
2982
- const entryFiles = ["index.tsx", "index.ts", "index.jsx", "index.js", "App.tsx", "App.ts"];
2983
- let entryFile = "";
2984
- for (const f of entryFiles) {
2985
- if (existsSync8(path9.join(previewDir, f))) {
2986
- entryFile = path9.join(previewDir, f);
2987
- break;
2988
3340
  }
2989
- }
2990
- if (!entryFile) {
2991
- res.statusCode = 404;
2992
- res.end(JSON.stringify({ error: "No entry file found" }));
2993
- return;
2994
- }
2995
- try {
2996
- const result = await esbuildLib.build({
2997
- entryPoints: [entryFile],
2998
- bundle: true,
2999
- write: false,
3000
- format: "esm",
3001
- jsx: "automatic",
3002
- jsxImportSource: "react",
3003
- jsxDev: false,
3004
- target: "es2020",
3005
- minify: false,
3006
- sourcemap: false,
3007
- external: ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "@prev/jsx", "fs", "path", "js-yaml"],
3008
- alias: {
3009
- react: "https://esm.sh/react@18",
3010
- "react-dom": "https://esm.sh/react-dom@18",
3011
- "react-dom/client": "https://esm.sh/react-dom@18/client",
3012
- "react/jsx-runtime": "https://esm.sh/react@18/jsx-runtime"
3013
- },
3014
- define: {
3015
- "process.env.NODE_ENV": '"production"'
3016
- }
3017
- });
3018
- const bundleTime = Math.round(performance.now() - startTime);
3019
- const code = result.outputFiles[0]?.text || "";
3020
- res.setHeader("Content-Type", "application/javascript");
3021
- res.setHeader("X-Bundle-Time", String(bundleTime));
3022
- res.end(code);
3023
- return;
3024
- } catch (err) {
3025
- console.error("Bundle error:", err);
3026
- res.statusCode = 500;
3027
- res.end(JSON.stringify({ error: String(err) }));
3028
- return;
3029
- }
3030
- }
3031
- if (urlPath === "/_preview-runtime") {
3032
- const templatePath = path9.join(srcRoot2, "preview-runtime/fast-template.html");
3033
- if (existsSync8(templatePath)) {
3034
- const html = readFileSync7(templatePath, "utf-8");
3035
- res.setHeader("Content-Type", "text/html");
3036
- res.end(html);
3037
- return;
3038
- }
3039
- }
3040
- if (urlPath.startsWith("/_preview-config/")) {
3041
- const pathAfterConfig = decodeURIComponent(urlPath.slice("/_preview-config/".length));
3042
- const previewsDir = path9.join(rootDir, "previews");
3043
- const multiTypeMatch = pathAfterConfig.match(/^(components|screens|flows|atlas)\/(.+)$/);
3044
- if (multiTypeMatch) {
3045
- const [, type, name] = multiTypeMatch;
3046
- const previewDir = path9.join(previewsDir, type, name);
3047
- if (!previewDir.startsWith(previewsDir)) {
3048
- res.statusCode = 403;
3049
- 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))
3050
3349
  return;
3051
- }
3052
- if (existsSync8(previewDir)) {
3053
- try {
3054
- if (type === "flows") {
3055
- const configPathYaml = path9.join(previewDir, "index.yaml");
3056
- const configPathYml = path9.join(previewDir, "index.yml");
3057
- const configPath = existsSync8(configPathYaml) ? configPathYaml : configPathYml;
3058
- if (existsSync8(configPath)) {
3059
- const flow = await parseFlowDefinition(configPath);
3060
- if (flow) {
3061
- res.setHeader("Content-Type", "application/json");
3062
- res.end(JSON.stringify(flow));
3063
- return;
3064
- }
3065
- }
3066
- } else if (type === "atlas") {
3067
- const configPathYaml = path9.join(previewDir, "index.yaml");
3068
- const configPathYml = path9.join(previewDir, "index.yml");
3069
- const configPath = existsSync8(configPathYaml) ? configPathYaml : configPathYml;
3070
- if (existsSync8(configPath)) {
3071
- const atlas = await parseAtlasDefinition(configPath);
3072
- if (atlas) {
3073
- res.setHeader("Content-Type", "application/json");
3074
- res.end(JSON.stringify(atlas));
3075
- return;
3076
- }
3077
- }
3078
- } else {
3079
- const config2 = await buildPreviewConfig(previewDir);
3080
- res.setHeader("Content-Type", "application/json");
3081
- res.end(JSON.stringify(config2));
3082
- return;
3083
- }
3084
- res.statusCode = 400;
3085
- res.end(JSON.stringify({ error: "Invalid config format" }));
3350
+ for (const ext of tryExts) {
3351
+ if (existsAsFile(resolved + ext))
3086
3352
  return;
3087
- } catch (err) {
3088
- console.error("Error building preview config:", err);
3089
- res.statusCode = 500;
3090
- res.end(JSON.stringify({ error: String(err) }));
3353
+ }
3354
+ for (const idx of tryIndex) {
3355
+ if (existsAsFile(resolved + idx))
3091
3356
  return;
3092
- }
3093
3357
  }
3094
- } else {
3095
- const previewDir = path9.resolve(previewsDir, pathAfterConfig);
3096
- if (!previewDir.startsWith(previewsDir)) {
3097
- res.statusCode = 403;
3098
- res.end("Forbidden");
3099
- 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
+ }
3100
3367
  }
3101
- if (existsSync8(previewDir)) {
3102
- try {
3103
- const config2 = await buildPreviewConfig(previewDir);
3104
- res.setHeader("Content-Type", "application/json");
3105
- res.end(JSON.stringify(config2));
3106
- return;
3107
- } catch (err) {
3108
- console.error("Error building preview config:", err);
3109
- res.statusCode = 500;
3110
- res.end(JSON.stringify({ error: String(err) }));
3111
- return;
3368
+ for (const idx of tryIndex) {
3369
+ if (existsAsFile(diskPath + idx)) {
3370
+ return { path: diskPath + idx };
3112
3371
  }
3113
3372
  }
3114
- }
3373
+ return;
3374
+ });
3115
3375
  }
3116
- if (urlPath.match(/^\/_preview\/(components|screens|flows|atlas)\/[^/]+\/?$/)) {
3117
- const routeMatch = urlPath.match(/^\/_preview\/(\w+)\/([^/]+)\/?$/);
3118
- if (routeMatch) {
3119
- const [, type, name] = routeMatch;
3120
- const singularType = TYPE_SINGULAR[type] || type;
3121
- const shellHtml = `<!DOCTYPE html>
3122
- <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">
3123
3444
  <head>
3124
3445
  <meta charset="UTF-8">
3125
- <title>Preview: ${name}</title>
3126
- <script type="module">
3127
- import { PreviewRouter } from '@prev/theme/previews'
3128
- import { createRoot } from 'react-dom/client'
3129
- 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
+ }
3130
3466
 
3131
- createRoot(document.getElementById('root')).render(
3132
- React.createElement(PreviewRouter, { type: '${singularType}', name: '${name}' })
3133
- )
3134
- </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}
3135
3543
  </head>
3136
3544
  <body>
3137
3545
  <div id="root"></div>
3546
+ <script type="module" src="${entryJsPath}"></script>
3138
3547
  </body>
3139
3548
  </html>`;
3140
- const transformed = await server.transformIndexHtml(req.url, shellHtml);
3141
- res.setHeader("Content-Type", "text/html");
3142
- res.end(transformed);
3143
- return;
3144
- }
3145
- }
3146
- if (urlPath.startsWith("/_preview/")) {
3147
- const isHtmlRequest = !path9.extname(urlPath) || urlPath.endsWith("/");
3148
- if (isHtmlRequest) {
3149
- const previewName = decodeURIComponent(urlPath.slice("/_preview/".length).replace(/\/$/, ""));
3150
- const previewsDir = path9.join(rootDir, "previews");
3151
- const htmlPath = path9.resolve(previewsDir, previewName, "index.html");
3152
- if (!htmlPath.startsWith(previewsDir)) {
3153
- return next();
3154
- }
3155
- if (existsSync8(htmlPath)) {
3156
- try {
3157
- let html = readFileSync7(htmlPath, "utf-8");
3158
- const previewBase = `/_preview/${previewName}/`;
3159
- html = html.replace(/(src|href)=["']\.\/([^"']+)["']/g, `$1="${previewBase}$2"`);
3160
- const transformed = await server.transformIndexHtml(req.url, html);
3161
- res.setHeader("Content-Type", "text/html");
3162
- res.end(transformed);
3163
- return;
3164
- } catch (err) {
3165
- console.error("Error serving preview:", err);
3166
- return next();
3167
- }
3168
- }
3169
- }
3170
- }
3171
- next();
3172
- });
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}`);
3173
3640
  }
3174
3641
  }
3175
- ],
3176
- resolve: {
3177
- alias: {
3178
- "@prev/ui": path9.join(srcRoot2, "ui"),
3179
- "@prev/theme": path9.join(srcRoot2, "theme"),
3180
- react: path9.join(cliNodeModules, "react"),
3181
- "react-dom": path9.join(cliNodeModules, "react-dom"),
3182
- "@tanstack/react-router": path9.join(cliNodeModules, "@tanstack/react-router"),
3183
- "@mdx-js/react": path9.join(cliNodeModules, "@mdx-js/react"),
3184
- mermaid: path9.join(cliNodeModules, "mermaid"),
3185
- dayjs: path9.join(cliNodeModules, "dayjs"),
3186
- "@terrastruct/d2": path9.join(cliNodeModules, "@terrastruct/d2"),
3187
- "use-sync-external-store": path9.join(cliNodeModules, "use-sync-external-store")
3188
- },
3189
- dedupe: [
3190
- "react",
3191
- "react-dom",
3192
- "@tanstack/react-router"
3193
- ]
3194
- },
3195
- esbuild: {
3196
- jsx: "automatic",
3197
- jsxImportSource: "react",
3198
- jsxDev: false
3199
- },
3200
- optimizeDeps: {
3201
- noDiscovery: true,
3202
- holdUntilCrawlEnd: false,
3203
- esbuildOptions: {
3204
- jsx: "automatic",
3205
- jsxImportSource: "react",
3206
- jsxDev: false
3207
- },
3208
- include: [
3209
- "react-dom/client",
3210
- "use-sync-external-store",
3211
- "use-sync-external-store/shim/with-selector.js",
3212
- "mermaid",
3213
- "dayjs",
3214
- "@terrastruct/d2"
3215
- ],
3216
- exclude: [
3217
- "virtual:prev-config",
3218
- "virtual:prev-previews",
3219
- "virtual:prev-pages",
3220
- "virtual:prev-page-modules",
3221
- "@prev/theme"
3222
- ]
3223
- },
3224
- ssr: {
3225
- noExternal: true
3226
- },
3227
- server: {
3228
- port,
3229
- strictPort: false,
3230
- fs: {
3231
- allow: [rootDir, cliRoot2]
3232
- },
3233
- warmup: {
3234
- clientFiles: [
3235
- path9.join(srcRoot2, "theme/entry.tsx"),
3236
- path9.join(srcRoot2, "theme/styles.css")
3237
- ]
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);
3238
3663
  }
3239
- },
3240
- preview: {
3241
- port,
3242
- strictPort: false
3243
- },
3244
- build: {
3245
- outDir: path9.join(rootDir, "dist"),
3246
- reportCompressedSize: false,
3247
- chunkSizeWarningLimit: 1e4,
3248
- rollupOptions: {
3249
- input: {
3250
- main: path9.join(srcRoot2, "theme/index.html")
3251
- }
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 {}
3252
3671
  }
3253
- },
3254
- assetsInclude: [
3255
- "**/node_modules/**/*.md",
3256
- "**/node_modules/**/*.mdx"
3257
- ]
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()
3258
3687
  };
3259
3688
  }
3260
3689
 
@@ -3289,10 +3718,8 @@ async function findAvailablePort(minPort, maxPort) {
3289
3718
  throw new Error(`No available port found between ${minPort} and ${maxPort}`);
3290
3719
  }
3291
3720
 
3292
- // src/vite/start.ts
3721
+ // src/server/start.ts
3293
3722
  import { exec } from "child_process";
3294
- import { existsSync as existsSync9, rmSync as rmSync3, copyFileSync } from "fs";
3295
- import path10 from "path";
3296
3723
  function printWelcome(type) {
3297
3724
  console.log();
3298
3725
  console.log(" \u2728 prev");
@@ -3307,7 +3734,6 @@ function printShortcuts() {
3307
3734
  console.log();
3308
3735
  console.log(" Shortcuts:");
3309
3736
  console.log(" o \u2192 open in browser");
3310
- console.log(" c \u2192 clear cache");
3311
3737
  console.log(" h \u2192 show this help");
3312
3738
  console.log(" q \u2192 quit");
3313
3739
  console.log();
@@ -3324,25 +3750,7 @@ function openBrowser(url) {
3324
3750
  exec(`${cmd} ${url}`);
3325
3751
  console.log(` \u2197 Opened ${url}`);
3326
3752
  }
3327
- function clearCache(rootDir) {
3328
- const viteCacheDir = path10.join(rootDir, ".vite");
3329
- const nodeModulesVite = path10.join(rootDir, "node_modules", ".vite");
3330
- let cleared = 0;
3331
- if (existsSync9(viteCacheDir)) {
3332
- rmSync3(viteCacheDir, { recursive: true });
3333
- cleared++;
3334
- }
3335
- if (existsSync9(nodeModulesVite)) {
3336
- rmSync3(nodeModulesVite, { recursive: true });
3337
- cleared++;
3338
- }
3339
- if (cleared === 0) {
3340
- console.log(" No cache to clear");
3341
- } else {
3342
- console.log(` \u2713 Cleared Vite cache`);
3343
- }
3344
- }
3345
- function setupKeyboardShortcuts(rootDir, url, quit) {
3753
+ function setupKeyboardShortcuts(url, quit) {
3346
3754
  if (!process.stdin.isTTY)
3347
3755
  return () => {};
3348
3756
  process.stdin.setRawMode(true);
@@ -3353,9 +3761,6 @@ function setupKeyboardShortcuts(rootDir, url, quit) {
3353
3761
  case "o":
3354
3762
  openBrowser(url);
3355
3763
  break;
3356
- case "c":
3357
- clearCache(rootDir);
3358
- break;
3359
3764
  case "h":
3360
3765
  printShortcuts();
3361
3766
  break;
@@ -3376,27 +3781,13 @@ function setupKeyboardShortcuts(rootDir, url, quit) {
3376
3781
  }
3377
3782
  async function startDev(rootDir, options = {}) {
3378
3783
  const port = options.port ?? await getRandomPort();
3379
- const config = await createViteConfig({
3784
+ const { server, url, stop } = await startDevServer({
3380
3785
  rootDir,
3381
- mode: "development",
3382
3786
  port,
3383
- include: options.include,
3384
- debug: options.debug
3787
+ include: options.include
3385
3788
  });
3386
- const server = await createServer2(config);
3387
- await server.listen();
3388
- const warmupPort = server.config.server.port;
3389
- fetch(`http://localhost:${warmupPort}/`).catch(() => {});
3390
- const debugCollector = getDebugCollector();
3391
- if (debugCollector) {
3392
- debugCollector.startPhase("serverReady");
3393
- const reportPath = debugCollector.writeReport();
3394
- console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
3395
- }
3396
- const actualPort = server.config.server.port || port;
3397
- const url = `http://localhost:${actualPort}/`;
3398
3789
  printWelcome("dev");
3399
- server.printUrls();
3790
+ console.log(` \u279C Local: ${url}`);
3400
3791
  printReady();
3401
3792
  let isShuttingDown = false;
3402
3793
  let cleanupStdin = () => {};
@@ -3412,17 +3803,10 @@ async function startDev(rootDir, options = {}) {
3412
3803
  Shutting down...`);
3413
3804
  }
3414
3805
  cleanupStdin();
3415
- const closePromise = server.close();
3416
- const timeoutPromise = new Promise((resolve) => {
3417
- setTimeout(() => {
3418
- console.log(" Force closing (timeout)...");
3419
- resolve();
3420
- }, 3000);
3421
- });
3422
- await Promise.race([closePromise, timeoutPromise]);
3806
+ stop();
3423
3807
  process.exit(0);
3424
3808
  };
3425
- cleanupStdin = setupKeyboardShortcuts(rootDir, url, () => shutdown());
3809
+ cleanupStdin = setupKeyboardShortcuts(url, () => shutdown());
3426
3810
  process.on("SIGINT", () => shutdown("SIGINT"));
3427
3811
  process.on("SIGTERM", () => shutdown("SIGTERM"));
3428
3812
  process.on("uncaughtException", (err) => {
@@ -3437,26 +3821,11 @@ async function buildSite(rootDir, options = {}) {
3437
3821
  console.log(" \u2728 prev build");
3438
3822
  console.log();
3439
3823
  console.log(" Building your documentation site...");
3440
- const config = await createViteConfig({
3824
+ await buildProductionSite({
3441
3825
  rootDir,
3442
- mode: "production",
3443
3826
  include: options.include,
3444
- base: options.base,
3445
- debug: options.debug
3827
+ base: options.base
3446
3828
  });
3447
- await build4(config);
3448
- const debugCollector = getDebugCollector();
3449
- if (debugCollector) {
3450
- debugCollector.startPhase("buildComplete");
3451
- const reportPath = debugCollector.writeReport();
3452
- console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
3453
- }
3454
- const distDir = path10.join(rootDir, "dist");
3455
- const indexPath = path10.join(distDir, "index.html");
3456
- const notFoundPath = path10.join(distDir, "404.html");
3457
- if (existsSync9(indexPath)) {
3458
- copyFileSync(indexPath, notFoundPath);
3459
- }
3460
3829
  console.log();
3461
3830
  console.log(" Done! Your site is ready in ./dist");
3462
3831
  console.log(" You can deploy this folder anywhere.");
@@ -3464,31 +3833,18 @@ async function buildSite(rootDir, options = {}) {
3464
3833
  }
3465
3834
  async function previewSite(rootDir, options = {}) {
3466
3835
  const port = options.port ?? await getRandomPort();
3467
- const config = await createViteConfig({
3468
- rootDir,
3469
- mode: "production",
3470
- port,
3471
- include: options.include,
3472
- debug: options.debug
3473
- });
3474
- const server = await preview(config);
3475
- const debugCollector = getDebugCollector();
3476
- if (debugCollector) {
3477
- debugCollector.startPhase("previewReady");
3478
- const reportPath = debugCollector.writeReport();
3479
- console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
3480
- }
3836
+ const { url, stop } = await startPreviewServer({ rootDir, port });
3481
3837
  printWelcome("preview");
3482
- server.printUrls();
3838
+ console.log(` \u279C Local: ${url}`);
3483
3839
  console.log();
3484
3840
  console.log(" Press Ctrl+C to stop.");
3485
3841
  console.log();
3486
- return server;
3842
+ return { url, stop };
3487
3843
  }
3488
3844
 
3489
3845
  // src/validators/index.ts
3490
- import { existsSync as existsSync10, readdirSync, readFileSync as readFileSync9 } from "fs";
3491
- import { join as join3 } from "path";
3846
+ import { existsSync as existsSync17, readdirSync, readFileSync as readFileSync12 } from "fs";
3847
+ import { join as join4 } from "path";
3492
3848
  import * as yaml3 from "js-yaml";
3493
3849
 
3494
3850
  // src/renderers/registry.ts
@@ -3523,10 +3879,10 @@ async function initializeAdapters() {
3523
3879
  // src/validators/schema-validator.ts
3524
3880
  import Ajv from "ajv";
3525
3881
  import addFormats from "ajv-formats";
3526
- import { readFileSync as readFileSync8 } from "fs";
3527
- 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";
3528
3884
  import { fileURLToPath as fileURLToPath4 } from "url";
3529
- var __dirname3 = dirname3(fileURLToPath4(import.meta.url));
3885
+ var __dirname2 = dirname3(fileURLToPath4(import.meta.url));
3530
3886
  var ajv = new Ajv({
3531
3887
  allErrors: true,
3532
3888
  strict: false,
@@ -3538,8 +3894,8 @@ function loadSchema(name) {
3538
3894
  if (schemaCache.has(name)) {
3539
3895
  return schemaCache.get(name);
3540
3896
  }
3541
- const schemaPath = join2(__dirname3, "..", "schemas", `${name}.schema.json`);
3542
- const schemaContent = readFileSync8(schemaPath, "utf-8");
3897
+ const schemaPath = join3(__dirname2, "..", "schemas", `${name}.schema.json`);
3898
+ const schemaContent = readFileSync11(schemaPath, "utf-8");
3543
3899
  const schema = JSON.parse(schemaContent);
3544
3900
  const validate = ajv.compile(schema);
3545
3901
  schemaCache.set(name, validate);
@@ -3641,7 +3997,7 @@ function validateAllLayouts(layoutByRenderer, targetRenderer) {
3641
3997
  init_primitives();
3642
3998
  function parseRef(ref) {
3643
3999
  const refStr = typeof ref === "string" ? ref : ref.ref;
3644
- const match = refStr.match(/^(screens|components|flows|atlas)\/([a-z0-9-]+)$/);
4000
+ const match = refStr.match(/^(screens|components|flows)\/([a-z0-9-]+)$/);
3645
4001
  if (!match)
3646
4002
  return null;
3647
4003
  return { type: match[1], id: match[2] };
@@ -3661,22 +4017,22 @@ function detectCycle(edges, nodeIds) {
3661
4017
  }
3662
4018
  const visited = new Set;
3663
4019
  const recursionStack = new Set;
3664
- const path11 = [];
4020
+ const path16 = [];
3665
4021
  function dfs(node) {
3666
4022
  visited.add(node);
3667
4023
  recursionStack.add(node);
3668
- path11.push(node);
4024
+ path16.push(node);
3669
4025
  for (const neighbor of adjacency.get(node) || []) {
3670
4026
  if (!visited.has(neighbor)) {
3671
4027
  const cycle = dfs(neighbor);
3672
4028
  if (cycle)
3673
4029
  return cycle;
3674
4030
  } else if (recursionStack.has(neighbor)) {
3675
- const cycleStart = path11.indexOf(neighbor);
3676
- return [...path11.slice(cycleStart), neighbor];
4031
+ const cycleStart = path16.indexOf(neighbor);
4032
+ return [...path16.slice(cycleStart), neighbor];
3677
4033
  }
3678
4034
  }
3679
- path11.pop();
4035
+ path16.pop();
3680
4036
  recursionStack.delete(node);
3681
4037
  return null;
3682
4038
  }
@@ -3689,13 +4045,13 @@ function detectCycle(edges, nodeIds) {
3689
4045
  }
3690
4046
  return null;
3691
4047
  }
3692
- function validateRef(ref, context, path11) {
4048
+ function validateRef(ref, context, path16) {
3693
4049
  const errors = [];
3694
4050
  const warnings = [];
3695
4051
  const parsed = parseRef(ref);
3696
4052
  if (!parsed) {
3697
4053
  errors.push({
3698
- path: path11,
4054
+ path: path16,
3699
4055
  message: `Invalid reference format: ${JSON.stringify(ref)}`,
3700
4056
  code: "INVALID_REF"
3701
4057
  });
@@ -3704,7 +4060,7 @@ function validateRef(ref, context, path11) {
3704
4060
  const knownSet = context.knownIds[parsed.type];
3705
4061
  if (!knownSet?.has(parsed.id)) {
3706
4062
  errors.push({
3707
- path: path11,
4063
+ path: path16,
3708
4064
  message: `Reference "${parsed.type}/${parsed.id}" not found`,
3709
4065
  code: "INVALID_REF"
3710
4066
  });
@@ -3716,13 +4072,13 @@ function validateRef(ref, context, path11) {
3716
4072
  const screenStates = context.screenStates.get(parsed.id);
3717
4073
  if (!screenStates) {
3718
4074
  errors.push({
3719
- path: path11,
4075
+ path: path16,
3720
4076
  message: `State "${state}" referenced but screen "${parsed.id}" has no states defined`,
3721
4077
  code: "INVALID_STATE_REF"
3722
4078
  });
3723
4079
  } else if (!screenStates.has(state)) {
3724
4080
  errors.push({
3725
- path: path11,
4081
+ path: path16,
3726
4082
  message: `State "${state}" not found in screen "${parsed.id}". Available states: ${Array.from(screenStates).join(", ") || "none"}`,
3727
4083
  code: "INVALID_STATE_REF"
3728
4084
  });
@@ -3773,50 +4129,6 @@ function validateFlow(config, context, configPath) {
3773
4129
  }
3774
4130
  return { errors, warnings };
3775
4131
  }
3776
- function validateAtlas(config, context, configPath) {
3777
- const errors = [];
3778
- const warnings = [];
3779
- if (!config.nodes || config.nodes.length === 0) {
3780
- return { errors, warnings };
3781
- }
3782
- const nodeIds = new Set(config.nodes.map((n) => n.id));
3783
- for (let i = 0;i < config.nodes.length; i++) {
3784
- const node = config.nodes[i];
3785
- if (node.ref) {
3786
- const result = validateRef(node.ref, context, `${configPath}/nodes/${i}/ref`);
3787
- errors.push(...result.errors);
3788
- warnings.push(...result.warnings);
3789
- }
3790
- }
3791
- if (config.relationships) {
3792
- for (let i = 0;i < config.relationships.length; i++) {
3793
- const rel = config.relationships[i];
3794
- if (!nodeIds.has(rel.from)) {
3795
- errors.push({
3796
- path: `${configPath}/relationships/${i}/from`,
3797
- message: `Relationship "from" references unknown node "${rel.from}". Available nodes: ${Array.from(nodeIds).join(", ")}`,
3798
- code: "INVALID_NODE_REF"
3799
- });
3800
- }
3801
- if (!nodeIds.has(rel.to)) {
3802
- errors.push({
3803
- path: `${configPath}/relationships/${i}/to`,
3804
- message: `Relationship "to" references unknown node "${rel.to}". Available nodes: ${Array.from(nodeIds).join(", ")}`,
3805
- code: "INVALID_NODE_REF"
3806
- });
3807
- }
3808
- }
3809
- const cycle = detectCycle(config.relationships, nodeIds);
3810
- if (cycle) {
3811
- errors.push({
3812
- path: `${configPath}/relationships`,
3813
- message: `Circular dependency detected in atlas relationships: ${cycle.join(" \u2192 ")}`,
3814
- code: "CIRCULAR_DEPENDENCY"
3815
- });
3816
- }
3817
- }
3818
- return { errors, warnings };
3819
- }
3820
4132
  function validateScreenTemplate(template, slots, states, context, configPath) {
3821
4133
  const errors = [];
3822
4134
  const warnings = [];
@@ -3912,25 +4224,25 @@ function validateRendererKeys(layoutByRenderer, configPath) {
3912
4224
  }
3913
4225
  return { errors, warnings };
3914
4226
  }
3915
- function extractComponentRefs(node, path11, refs) {
4227
+ function extractComponentRefs(node, path16, refs) {
3916
4228
  if (!node || typeof node !== "object")
3917
4229
  return;
3918
4230
  if (Array.isArray(node)) {
3919
4231
  node.forEach((item, index) => {
3920
- extractComponentRefs(item, `${path11}/${index}`, refs);
4232
+ extractComponentRefs(item, `${path16}/${index}`, refs);
3921
4233
  });
3922
4234
  return;
3923
4235
  }
3924
4236
  const obj = node;
3925
4237
  if (obj.type === "ComponentRef" && typeof obj.ref === "string") {
3926
- refs.push({ ref: obj.ref, path: `${path11}/ref` });
4238
+ refs.push({ ref: obj.ref, path: `${path16}/ref` });
3927
4239
  }
3928
4240
  if (obj.children) {
3929
- extractComponentRefs(obj.children, `${path11}/children`, refs);
4241
+ extractComponentRefs(obj.children, `${path16}/children`, refs);
3930
4242
  }
3931
4243
  if (obj.props && typeof obj.props === "object") {
3932
4244
  for (const [key, value] of Object.entries(obj.props)) {
3933
- extractComponentRefs(value, `${path11}/props/${key}`, refs);
4245
+ extractComponentRefs(value, `${path16}/props/${key}`, refs);
3934
4246
  }
3935
4247
  }
3936
4248
  }
@@ -3943,11 +4255,11 @@ function validateLayoutComponentRefs(layoutByRenderer, context, configPath) {
3943
4255
  for (const [rendererKey, layout] of Object.entries(layoutByRenderer)) {
3944
4256
  const refs = [];
3945
4257
  extractComponentRefs(layout, `${configPath}/layoutByRenderer/${rendererKey}`, refs);
3946
- for (const { ref, path: path11 } of refs) {
4258
+ for (const { ref, path: path16 } of refs) {
3947
4259
  const match = ref.match(/^components\/([a-z0-9-]+)$/);
3948
4260
  if (!match) {
3949
4261
  errors.push({
3950
- path: path11,
4262
+ path: path16,
3951
4263
  message: `Invalid ComponentRef format: "${ref}". Expected "components/<id>"`,
3952
4264
  code: "INVALID_REF"
3953
4265
  });
@@ -3956,7 +4268,7 @@ function validateLayoutComponentRefs(layoutByRenderer, context, configPath) {
3956
4268
  const componentId = match[1];
3957
4269
  if (!context.knownIds.components.has(componentId)) {
3958
4270
  errors.push({
3959
- path: path11,
4271
+ path: path16,
3960
4272
  message: `ComponentRef references unknown component "${componentId}"`,
3961
4273
  code: "INVALID_REF"
3962
4274
  });
@@ -4004,11 +4316,6 @@ function validateSemantics(config, context, configPath = "") {
4004
4316
  errors.push(...result.errors);
4005
4317
  warnings.push(...result.warnings);
4006
4318
  }
4007
- if (config.kind === "atlas") {
4008
- const result = validateAtlas(config, context, configPath);
4009
- errors.push(...result.errors);
4010
- warnings.push(...result.warnings);
4011
- }
4012
4319
  return {
4013
4320
  valid: errors.length === 0,
4014
4321
  errors,
@@ -4021,8 +4328,7 @@ function createValidationContext(rootDir) {
4021
4328
  knownIds: {
4022
4329
  components: new Set,
4023
4330
  screens: new Set,
4024
- flows: new Set,
4025
- atlas: new Set
4331
+ flows: new Set
4026
4332
  },
4027
4333
  screenStates: new Map
4028
4334
  };
@@ -4051,25 +4357,25 @@ function checkDuplicateIds(ids, type) {
4051
4357
  }
4052
4358
 
4053
4359
  // src/validators/index.ts
4054
- var PREVIEW_TYPES = ["components", "screens", "flows", "atlas"];
4360
+ var PREVIEW_TYPES = ["components", "screens", "flows"];
4055
4361
  function findPreviewRoot(startDir) {
4056
4362
  let current = startDir;
4057
4363
  while (current !== "/") {
4058
- const previewsDir = join3(current, ".previews");
4059
- if (existsSync10(previewsDir)) {
4364
+ const previewsDir = join4(current, ".previews");
4365
+ if (existsSync17(previewsDir)) {
4060
4366
  return previewsDir;
4061
4367
  }
4062
- const previewsDirAlt = join3(current, "previews");
4063
- if (existsSync10(previewsDirAlt)) {
4368
+ const previewsDirAlt = join4(current, "previews");
4369
+ if (existsSync17(previewsDirAlt)) {
4064
4370
  return previewsDirAlt;
4065
4371
  }
4066
- current = join3(current, "..");
4372
+ current = join4(current, "..");
4067
4373
  }
4068
4374
  return null;
4069
4375
  }
4070
4376
  function scanPreviewType(previewRoot, type) {
4071
- const typeDir = join3(previewRoot, type);
4072
- if (!existsSync10(typeDir)) {
4377
+ const typeDir = join4(previewRoot, type);
4378
+ if (!existsSync17(typeDir)) {
4073
4379
  return [];
4074
4380
  }
4075
4381
  const units = [];
@@ -4077,10 +4383,10 @@ function scanPreviewType(previewRoot, type) {
4077
4383
  for (const entry of entries) {
4078
4384
  if (!entry.isDirectory())
4079
4385
  continue;
4080
- const unitPath = join3(typeDir, entry.name);
4081
- const configYaml = join3(unitPath, "config.yaml");
4082
- const configYml = join3(unitPath, "config.yml");
4083
- 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;
4084
4390
  if (configPath) {
4085
4391
  units.push({
4086
4392
  id: entry.name,
@@ -4093,7 +4399,7 @@ function scanPreviewType(previewRoot, type) {
4093
4399
  }
4094
4400
  function loadConfig2(configPath) {
4095
4401
  try {
4096
- const content = readFileSync9(configPath, "utf-8");
4402
+ const content = readFileSync12(configPath, "utf-8");
4097
4403
  const parsed = yaml3.load(content);
4098
4404
  if (parsed && typeof parsed === "object") {
4099
4405
  return parsed;
@@ -4144,8 +4450,7 @@ async function validate(rootDir = process.cwd(), options = {}) {
4144
4450
  summary: {
4145
4451
  components: { total: 0, valid: 0, invalid: 0 },
4146
4452
  screens: { total: 0, valid: 0, invalid: 0 },
4147
- flows: { total: 0, valid: 0, invalid: 0 },
4148
- atlas: { total: 0, valid: 0, invalid: 0 }
4453
+ flows: { total: 0, valid: 0, invalid: 0 }
4149
4454
  },
4150
4455
  errors: [{
4151
4456
  file: rootDir,
@@ -4162,8 +4467,7 @@ async function validate(rootDir = process.cwd(), options = {}) {
4162
4467
  summary: {
4163
4468
  components: { total: 0, valid: 0, invalid: 0 },
4164
4469
  screens: { total: 0, valid: 0, invalid: 0 },
4165
- flows: { total: 0, valid: 0, invalid: 0 },
4166
- atlas: { total: 0, valid: 0, invalid: 0 }
4470
+ flows: { total: 0, valid: 0, invalid: 0 }
4167
4471
  },
4168
4472
  errors: [],
4169
4473
  warnings: []
@@ -4295,7 +4599,7 @@ function formatValidationResult(result) {
4295
4599
  }
4296
4600
 
4297
4601
  // src/typecheck/index.ts
4298
- import path11 from "path";
4602
+ import path16 from "path";
4299
4603
  import { fileURLToPath as fileURLToPath5 } from "url";
4300
4604
  import { spawn } from "child_process";
4301
4605
  function getTsgoPath() {
@@ -4304,9 +4608,9 @@ function getTsgoPath() {
4304
4608
  const platformPkg = `@typescript/native-preview-${platform}-${arch}`;
4305
4609
  try {
4306
4610
  const pkgJsonUrl = import.meta.resolve(`${platformPkg}/package.json`);
4307
- const pkgDir = path11.dirname(fileURLToPath5(pkgJsonUrl));
4611
+ const pkgDir = path16.dirname(fileURLToPath5(pkgJsonUrl));
4308
4612
  const exe = platform === "win32" ? "tsgo.exe" : "tsgo";
4309
- return path11.join(pkgDir, "lib", exe);
4613
+ return path16.join(pkgDir, "lib", exe);
4310
4614
  } catch {
4311
4615
  throw new Error(`Unable to find tsgo for ${platform}-${arch}. ` + `Package ${platformPkg} may not be installed or your platform is unsupported.`);
4312
4616
  }
@@ -4314,15 +4618,15 @@ function getTsgoPath() {
4314
4618
  function getTypeRootsPath() {
4315
4619
  try {
4316
4620
  const reactTypesUrl = import.meta.resolve("@types/react/package.json");
4317
- const reactTypesDir = path11.dirname(fileURLToPath5(reactTypesUrl));
4318
- return path11.dirname(reactTypesDir);
4621
+ const reactTypesDir = path16.dirname(fileURLToPath5(reactTypesUrl));
4622
+ return path16.dirname(reactTypesDir);
4319
4623
  } catch {
4320
4624
  throw new Error("Unable to find @types/react. Make sure prev-cli has @types/react in dependencies.");
4321
4625
  }
4322
4626
  }
4323
4627
  async function typecheck(rootDir, options = {}) {
4324
4628
  const {
4325
- previewsDir = path11.join(rootDir, "previews"),
4629
+ previewsDir = path16.join(rootDir, "previews"),
4326
4630
  include = ["**/*.{ts,tsx}"],
4327
4631
  strict = true,
4328
4632
  verbose = false
@@ -4351,7 +4655,7 @@ async function typecheck(rootDir, options = {}) {
4351
4655
  }
4352
4656
  if (verbose) {
4353
4657
  console.log(` files: ${files.length}`);
4354
- files.forEach((f) => console.log(` - ${path11.relative(rootDir, f)}`));
4658
+ files.forEach((f) => console.log(` - ${path16.relative(rootDir, f)}`));
4355
4659
  }
4356
4660
  const args = [
4357
4661
  "--noEmit",
@@ -4432,8 +4736,8 @@ ${result.output}
4432
4736
  }
4433
4737
 
4434
4738
  // src/migrate.ts
4435
- import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
4436
- 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";
4437
4741
  import * as yaml4 from "js-yaml";
4438
4742
  var PREVIEW_TYPE_FOLDERS2 = ["components", "screens", "flows", "atlas"];
4439
4743
  var KIND_MAP = {
@@ -4445,21 +4749,21 @@ var KIND_MAP = {
4445
4749
  function findPreviewRoot2(startDir) {
4446
4750
  let current = startDir;
4447
4751
  while (current !== "/") {
4448
- const previewsDir = join4(current, ".previews");
4449
- if (existsSync11(previewsDir)) {
4752
+ const previewsDir = join5(current, ".previews");
4753
+ if (existsSync18(previewsDir)) {
4450
4754
  return previewsDir;
4451
4755
  }
4452
- const previewsDirAlt = join4(current, "previews");
4453
- if (existsSync11(previewsDirAlt)) {
4756
+ const previewsDirAlt = join5(current, "previews");
4757
+ if (existsSync18(previewsDirAlt)) {
4454
4758
  return previewsDirAlt;
4455
4759
  }
4456
- current = join4(current, "..");
4760
+ current = join5(current, "..");
4457
4761
  }
4458
4762
  return null;
4459
4763
  }
4460
4764
  function migrateConfig(configPath, folderId, kind) {
4461
4765
  try {
4462
- const content = readFileSync10(configPath, "utf-8");
4766
+ const content = readFileSync13(configPath, "utf-8");
4463
4767
  const config = yaml4.load(content);
4464
4768
  if (!config || typeof config !== "object") {
4465
4769
  return { migrated: false, error: "Invalid YAML" };
@@ -4519,18 +4823,18 @@ async function migrateConfigs(rootDir = process.cwd()) {
4519
4823
  errors: []
4520
4824
  };
4521
4825
  for (const type of PREVIEW_TYPE_FOLDERS2) {
4522
- const typeDir = join4(previewRoot, type);
4523
- if (!existsSync11(typeDir))
4826
+ const typeDir = join5(previewRoot, type);
4827
+ if (!existsSync18(typeDir))
4524
4828
  continue;
4525
4829
  const kind = KIND_MAP[type];
4526
4830
  const entries = readdirSync2(typeDir, { withFileTypes: true });
4527
4831
  for (const entry of entries) {
4528
4832
  if (!entry.isDirectory())
4529
4833
  continue;
4530
- const unitPath = join4(typeDir, entry.name);
4531
- const configYaml = join4(unitPath, "config.yaml");
4532
- const configYml = join4(unitPath, "config.yml");
4533
- 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;
4534
4838
  if (!configPath) {
4535
4839
  result.skipped++;
4536
4840
  continue;
@@ -4578,14 +4882,14 @@ import { createHash } from "crypto";
4578
4882
  import { readdir, rm, stat, mkdir } from "fs/promises";
4579
4883
  import { exec as exec2 } from "child_process";
4580
4884
  import { promisify } from "util";
4581
- import path12 from "path";
4582
- import os2 from "os";
4885
+ import path17 from "path";
4886
+ import os from "os";
4583
4887
  var execAsync = promisify(exec2);
4584
- var DEFAULT_CACHE_ROOT = path12.join(os2.homedir(), ".cache/prev");
4888
+ var DEFAULT_CACHE_ROOT = path17.join(os.homedir(), ".cache/prev");
4585
4889
  async function getCacheDir(rootDir, branch) {
4586
4890
  const resolvedBranch = branch ?? await getCurrentBranch(rootDir);
4587
4891
  const hash = createHash("sha1").update(`${rootDir}:${resolvedBranch}`).digest("hex").slice(0, 12);
4588
- return path12.join(DEFAULT_CACHE_ROOT, hash);
4892
+ return path17.join(DEFAULT_CACHE_ROOT, hash);
4589
4893
  }
4590
4894
  async function getCurrentBranch(rootDir) {
4591
4895
  try {
@@ -4607,7 +4911,7 @@ async function cleanCache(options) {
4607
4911
  }
4608
4912
  let removed = 0;
4609
4913
  for (const dir of dirs) {
4610
- const fullPath = path12.join(cacheRoot, dir);
4914
+ const fullPath = path17.join(cacheRoot, dir);
4611
4915
  try {
4612
4916
  const info = await stat(fullPath);
4613
4917
  if (info.isDirectory() && now - info.mtimeMs > maxAge) {
@@ -4623,15 +4927,15 @@ async function cleanCache(options) {
4623
4927
  import yaml5 from "js-yaml";
4624
4928
  function getVersion() {
4625
4929
  try {
4626
- let dir = path13.dirname(fileURLToPath6(import.meta.url));
4930
+ let dir = path18.dirname(fileURLToPath6(import.meta.url));
4627
4931
  for (let i = 0;i < 5; i++) {
4628
- const pkgPath = path13.join(dir, "package.json");
4629
- if (existsSync12(pkgPath)) {
4630
- 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"));
4631
4935
  if (pkg.name === "prev-cli")
4632
4936
  return pkg.version;
4633
4937
  }
4634
- dir = path13.dirname(dir);
4938
+ dir = path18.dirname(dir);
4635
4939
  }
4636
4940
  } catch {}
4637
4941
  return "unknown";
@@ -4651,7 +4955,7 @@ var { values, positionals } = parseArgs({
4651
4955
  allowPositionals: true
4652
4956
  });
4653
4957
  var command = positionals[0] || "dev";
4654
- 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]) || ".");
4655
4959
  function printHelp() {
4656
4960
  console.log(`
4657
4961
  prev - Zero-config documentation site generator
@@ -4665,7 +4969,7 @@ Usage:
4665
4969
  prev migrate Migrate configs from v1 to v2 format
4666
4970
  prev create [name] Create preview in previews/<name>/ (default: "example")
4667
4971
  prev config [subcommand] Manage configuration
4668
- prev clearcache Clear Vite cache (.vite directory)
4972
+ prev clearcache Clear build cache
4669
4973
  prev clean [options] Remove old prev-cli caches
4670
4974
 
4671
4975
  Config subcommands:
@@ -4769,30 +5073,18 @@ Examples:
4769
5073
  prev clean -d 7 Remove caches older than 7 days
4770
5074
  `);
4771
5075
  }
4772
- async function clearViteCache(rootDir2) {
5076
+ async function clearBuildCache(rootDir2) {
4773
5077
  let cleared = 0;
4774
5078
  try {
4775
5079
  const prevCacheDir = await getCacheDir(rootDir2);
4776
- if (existsSync12(prevCacheDir)) {
5080
+ if (existsSync19(prevCacheDir)) {
4777
5081
  rmSync4(prevCacheDir, { recursive: true });
4778
5082
  cleared++;
4779
5083
  console.log(` \u2713 Removed ${prevCacheDir}`);
4780
5084
  }
4781
5085
  } catch {}
4782
- const viteCacheDir = path13.join(rootDir2, ".vite");
4783
- const nodeModulesVite = path13.join(rootDir2, "node_modules", ".vite");
4784
- if (existsSync12(viteCacheDir)) {
4785
- rmSync4(viteCacheDir, { recursive: true });
4786
- cleared++;
4787
- console.log(` \u2713 Removed .vite/`);
4788
- }
4789
- if (existsSync12(nodeModulesVite)) {
4790
- rmSync4(nodeModulesVite, { recursive: true });
4791
- cleared++;
4792
- console.log(` \u2713 Removed node_modules/.vite/`);
4793
- }
4794
5086
  if (cleared === 0) {
4795
- console.log(" No Vite cache found");
5087
+ console.log(" No build cache found");
4796
5088
  } else {
4797
5089
  console.log(`
4798
5090
  Cleared ${cleared} cache director${cleared === 1 ? "y" : "ies"}`);
@@ -4832,7 +5124,7 @@ function handleConfig(rootDir2, subcommand) {
4832
5124
  break;
4833
5125
  }
4834
5126
  case "init": {
4835
- const targetPath = path13.join(rootDir2, ".prev.yaml");
5127
+ const targetPath = path18.join(rootDir2, ".prev.yaml");
4836
5128
  if (configPath) {
4837
5129
  console.log(`
4838
5130
  Config already exists: ${configPath}
@@ -4891,8 +5183,8 @@ Available subcommands: show, init, path`);
4891
5183
  }
4892
5184
  }
4893
5185
  function createPreview(rootDir2, name) {
4894
- const previewDir = path13.join(rootDir2, "previews", name);
4895
- if (existsSync12(previewDir)) {
5186
+ const previewDir = path18.join(rootDir2, "previews", name);
5187
+ if (existsSync19(previewDir)) {
4896
5188
  console.error(`Preview "${name}" already exists at: ${previewDir}`);
4897
5189
  process.exit(1);
4898
5190
  }
@@ -5017,8 +5309,8 @@ export default function App() {
5017
5309
  .dark\\:text-white { color: #fff; }
5018
5310
  }
5019
5311
  `;
5020
- writeFileSync7(path13.join(previewDir, "App.tsx"), appTsx);
5021
- writeFileSync7(path13.join(previewDir, "styles.css"), stylesCss);
5312
+ writeFileSync7(path18.join(previewDir, "App.tsx"), appTsx);
5313
+ writeFileSync7(path18.join(previewDir, "styles.css"), stylesCss);
5022
5314
  console.log(`
5023
5315
  \u2728 Created preview: previews/${name}/
5024
5316
 
@@ -5067,7 +5359,7 @@ async function main() {
5067
5359
  createPreview(rootDir, previewName);
5068
5360
  break;
5069
5361
  case "clearcache":
5070
- await clearViteCache(rootDir);
5362
+ await clearBuildCache(rootDir);
5071
5363
  break;
5072
5364
  case "config":
5073
5365
  handleConfig(rootDir, positionals[1]);