prev-cli 0.24.19 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2006 -1703
- package/dist/previews/components/cart-item/index.d.ts +5 -0
- package/dist/previews/components/price-tag/index.d.ts +6 -0
- package/dist/previews/screens/cart/empty.d.ts +1 -0
- package/dist/previews/screens/cart/index.d.ts +1 -0
- package/dist/previews/screens/payment/error.d.ts +1 -0
- package/dist/previews/screens/payment/index.d.ts +1 -0
- package/dist/previews/screens/payment/processing.d.ts +1 -0
- package/dist/previews/screens/receipt/index.d.ts +1 -0
- package/dist/previews/shared/data.d.ts +30 -0
- package/dist/src/content/config-parser.d.ts +30 -0
- package/dist/src/content/flow-verifier.d.ts +21 -0
- package/dist/src/content/preview-types.d.ts +288 -0
- package/dist/{vite → src/content}/previews.d.ts +3 -11
- package/dist/{preview-runtime → src/preview-runtime}/build-optimized.d.ts +2 -0
- package/dist/{preview-runtime → src/preview-runtime}/build.d.ts +1 -1
- package/dist/src/preview-runtime/region-bridge.d.ts +1 -0
- package/dist/{preview-runtime → src/preview-runtime}/types.d.ts +18 -0
- package/dist/src/preview-runtime/vendors.d.ts +11 -0
- package/dist/{renderers → src/renderers}/index.d.ts +1 -1
- package/dist/{renderers → src/renderers}/types.d.ts +3 -31
- package/dist/src/server/build.d.ts +6 -0
- package/dist/src/server/dev.d.ts +13 -0
- package/dist/src/server/plugins/aliases.d.ts +5 -0
- package/dist/src/server/plugins/mdx.d.ts +5 -0
- package/dist/src/server/plugins/virtual-modules.d.ts +8 -0
- package/dist/src/server/preview.d.ts +10 -0
- package/dist/src/server/routes/component-bundle.d.ts +1 -0
- package/dist/src/server/routes/jsx-bundle.d.ts +3 -0
- package/dist/src/server/routes/og-image.d.ts +15 -0
- package/dist/src/server/routes/preview-bundle.d.ts +1 -0
- package/dist/src/server/routes/preview-config.d.ts +1 -0
- package/dist/src/server/routes/tokens.d.ts +1 -0
- package/dist/{vite → src/server}/start.d.ts +5 -2
- package/dist/{ui → src/ui}/button.d.ts +1 -1
- package/dist/{validators → src/validators}/index.d.ts +0 -5
- package/dist/{validators → src/validators}/semantic-validator.d.ts +2 -3
- package/package.json +8 -11
- package/src/jsx/CLAUDE.md +18 -0
- package/src/jsx/jsx-runtime.ts +1 -1
- package/src/preview-runtime/CLAUDE.md +21 -0
- package/src/preview-runtime/build-optimized.ts +189 -73
- package/src/preview-runtime/build.ts +75 -79
- package/src/preview-runtime/fast-template.html +5 -1
- package/src/preview-runtime/region-bridge.test.ts +41 -0
- package/src/preview-runtime/region-bridge.ts +101 -0
- package/src/preview-runtime/types.ts +6 -0
- package/src/preview-runtime/vendors.ts +215 -22
- package/src/primitives/CLAUDE.md +17 -0
- package/src/theme/CLAUDE.md +20 -0
- package/src/theme/Preview.tsx +10 -4
- package/src/theme/Toolbar.tsx +2 -2
- package/src/theme/entry.tsx +247 -121
- package/src/theme/hooks/useAnnotations.ts +77 -0
- package/src/theme/hooks/useApprovalStatus.ts +50 -0
- package/src/theme/hooks/useSnapshots.ts +147 -0
- package/src/theme/hooks/useStorage.ts +26 -0
- package/src/theme/hooks/useTokenOverrides.ts +56 -0
- package/src/theme/hooks/useViewport.ts +23 -0
- package/src/theme/icons.tsx +39 -1
- package/src/theme/index.html +18 -0
- package/src/theme/mdx-components.tsx +1 -1
- package/src/theme/previews/AnnotationLayer.tsx +285 -0
- package/src/theme/previews/AnnotationPin.tsx +61 -0
- package/src/theme/previews/AnnotationThread.tsx +257 -0
- package/src/theme/previews/CLAUDE.md +18 -0
- package/src/theme/previews/ComponentPreview.tsx +487 -107
- package/src/theme/previews/FlowDiagram.tsx +111 -0
- package/src/theme/previews/FlowPreview.tsx +938 -174
- package/src/theme/previews/PreviewRouter.tsx +1 -4
- package/src/theme/previews/ScreenPreview.tsx +515 -175
- package/src/theme/previews/SnapshotButton.tsx +68 -0
- package/src/theme/previews/SnapshotCompare.tsx +216 -0
- package/src/theme/previews/SnapshotPanel.tsx +274 -0
- package/src/theme/previews/StatusBadge.tsx +66 -0
- package/src/theme/previews/StatusDropdown.tsx +158 -0
- package/src/theme/previews/TokenPlayground.tsx +438 -0
- package/src/theme/previews/ViewportControls.tsx +67 -0
- package/src/theme/previews/flow-diagram.test.ts +141 -0
- package/src/theme/previews/flow-diagram.ts +109 -0
- package/src/theme/previews/flow-navigation.test.ts +90 -0
- package/src/theme/previews/flow-navigation.ts +47 -0
- package/src/theme/previews/machines/derived.test.ts +225 -0
- package/src/theme/previews/machines/derived.ts +73 -0
- package/src/theme/previews/machines/flow-machine.test.ts +379 -0
- package/src/theme/previews/machines/flow-machine.ts +207 -0
- package/src/theme/previews/machines/screen-machine.test.ts +149 -0
- package/src/theme/previews/machines/screen-machine.ts +76 -0
- package/src/theme/previews/stores/flow-store.test.ts +157 -0
- package/src/theme/previews/stores/flow-store.ts +49 -0
- package/src/theme/previews/stores/screen-store.test.ts +68 -0
- package/src/theme/previews/stores/screen-store.ts +33 -0
- package/src/theme/storage.test.ts +97 -0
- package/src/theme/storage.ts +71 -0
- package/src/theme/styles.css +296 -25
- package/src/theme/types.ts +64 -0
- package/src/tokens/CLAUDE.md +16 -0
- package/src/tokens/resolver.ts +1 -1
- package/dist/preview-runtime/vendors.d.ts +0 -6
- package/dist/vite/config-parser.d.ts +0 -13
- package/dist/vite/config.d.ts +0 -12
- package/dist/vite/plugins/config-plugin.d.ts +0 -3
- package/dist/vite/plugins/debug-plugin.d.ts +0 -3
- package/dist/vite/plugins/entry-plugin.d.ts +0 -2
- package/dist/vite/plugins/fumadocs-plugin.d.ts +0 -9
- package/dist/vite/plugins/pages-plugin.d.ts +0 -5
- package/dist/vite/plugins/previews-plugin.d.ts +0 -2
- package/dist/vite/plugins/tokens-plugin.d.ts +0 -2
- package/dist/vite/preview-types.d.ts +0 -70
- package/src/theme/previews/AtlasPreview.tsx +0 -528
- package/dist/{cli.d.ts → src/cli.d.ts} +0 -0
- package/dist/{config → src/config}/index.d.ts +0 -0
- package/dist/{config → src/config}/loader.d.ts +0 -0
- package/dist/{config → src/config}/schema.d.ts +0 -0
- package/dist/{vite → src/content}/pages.d.ts +0 -0
- package/dist/{jsx → src/jsx}/adapters/html.d.ts +0 -0
- package/dist/{jsx → src/jsx}/adapters/react.d.ts +0 -0
- package/dist/{jsx → src/jsx}/define-component.d.ts +0 -0
- package/dist/{jsx → src/jsx}/index.d.ts +0 -0
- package/dist/{jsx → src/jsx}/jsx-runtime.d.ts +0 -0
- package/dist/{jsx → src/jsx}/migrate.d.ts +0 -0
- package/dist/{jsx → src/jsx}/schemas/index.d.ts +0 -0
- package/dist/{jsx → src/jsx}/schemas/primitives.d.ts +10 -10
- package/dist/{jsx → src/jsx}/schemas/tokens.d.ts +3 -3
- /package/dist/{jsx → src/jsx}/validation.d.ts +0 -0
- /package/dist/{jsx → src/jsx}/vnode.d.ts +0 -0
- /package/dist/{migrate.d.ts → src/migrate.d.ts} +0 -0
- /package/dist/{preview-runtime → src/preview-runtime}/tailwind.d.ts +0 -0
- /package/dist/{primitives → src/primitives}/index.d.ts +0 -0
- /package/dist/{primitives → src/primitives}/migrate.d.ts +0 -0
- /package/dist/{primitives → src/primitives}/parser.d.ts +0 -0
- /package/dist/{primitives → src/primitives}/template-parser.d.ts +0 -0
- /package/dist/{primitives → src/primitives}/template-renderer.d.ts +0 -0
- /package/dist/{primitives → src/primitives}/types.d.ts +0 -0
- /package/dist/{renderers → src/renderers}/html/index.d.ts +0 -0
- /package/dist/{renderers → src/renderers}/react/index.d.ts +0 -0
- /package/dist/{renderers → src/renderers}/registry.d.ts +0 -0
- /package/dist/{renderers → src/renderers}/render.d.ts +0 -0
- /package/dist/{tokens → src/tokens}/defaults.d.ts +0 -0
- /package/dist/{tokens → src/tokens}/resolver.d.ts +0 -0
- /package/dist/{tokens → src/tokens}/utils.d.ts +0 -0
- /package/dist/{tokens → src/tokens}/validation.d.ts +0 -0
- /package/dist/{typecheck → src/typecheck}/index.d.ts +0 -0
- /package/dist/{ui → src/ui}/card.d.ts +0 -0
- /package/dist/{ui → src/ui}/index.d.ts +0 -0
- /package/dist/{ui → src/ui}/utils.d.ts +0 -0
- /package/dist/{utils → src/utils}/cache.d.ts +0 -0
- /package/dist/{utils → src/utils}/debug.d.ts +0 -0
- /package/dist/{utils → src/utils}/port.d.ts +0 -0
- /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: (
|
|
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,
|
|
550
|
+
function validateNode(node, path16, errors, warnings) {
|
|
444
551
|
if (typeof node === "string") {
|
|
445
|
-
validateLeafValue(node,
|
|
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:
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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, `${
|
|
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:
|
|
608
|
+
path: path16,
|
|
502
609
|
message: `Invalid node type: expected string or object, got ${typeof node}`
|
|
503
610
|
});
|
|
504
611
|
}
|
|
505
|
-
function validateLeafValue(value,
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
1199
|
-
import { existsSync as
|
|
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/
|
|
1203
|
-
import
|
|
1204
|
-
|
|
1205
|
-
|
|
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/
|
|
1217
|
-
import
|
|
1303
|
+
// src/server/plugins/virtual-modules.ts
|
|
1304
|
+
import path4 from "path";
|
|
1218
1305
|
|
|
1219
|
-
// src/
|
|
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/
|
|
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
|
|
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/
|
|
1522
|
+
// src/content/preview-types.ts
|
|
1605
1523
|
import { z } from "zod";
|
|
1606
|
-
var
|
|
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
|
-
|
|
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/
|
|
1619
|
-
async function parsePreviewConfig(filePath) {
|
|
1620
|
-
|
|
1621
|
-
|
|
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 =
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
return result
|
|
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
|
-
|
|
1631
|
-
return null;
|
|
1640
|
+
return result;
|
|
1632
1641
|
} catch (err) {
|
|
1633
|
-
|
|
1634
|
-
return
|
|
1642
|
+
result.errors.push(`Error parsing config at ${filePath}: ${err}`);
|
|
1643
|
+
return result;
|
|
1635
1644
|
}
|
|
1636
1645
|
}
|
|
1637
|
-
async function
|
|
1638
|
-
|
|
1639
|
-
|
|
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 =
|
|
1643
|
-
|
|
1644
|
-
if (!parsed
|
|
1645
|
-
|
|
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
|
|
1676
|
+
return result;
|
|
1648
1677
|
} catch (err) {
|
|
1649
|
-
|
|
1650
|
-
return
|
|
1678
|
+
result.errors.push(`Error parsing flow config: ${err}`);
|
|
1679
|
+
return result;
|
|
1651
1680
|
}
|
|
1652
1681
|
}
|
|
1653
|
-
async function
|
|
1654
|
-
if (!
|
|
1682
|
+
async function parseFlowDefinition(filePath) {
|
|
1683
|
+
if (!existsSync(filePath)) {
|
|
1655
1684
|
return null;
|
|
1656
1685
|
}
|
|
1657
1686
|
try {
|
|
1658
|
-
const content =
|
|
1687
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1659
1688
|
const parsed = yaml.load(content);
|
|
1660
|
-
if (!parsed.name || !
|
|
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
|
|
1694
|
+
console.warn(`Error parsing flow at ${filePath}:`, err);
|
|
1666
1695
|
return null;
|
|
1667
1696
|
}
|
|
1668
1697
|
}
|
|
1669
1698
|
|
|
1670
|
-
// src/
|
|
1671
|
-
var PREVIEW_TYPE_FOLDERS = ["components", "screens", "flows"
|
|
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 =
|
|
1710
|
-
const ext =
|
|
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 =
|
|
1739
|
-
if (!
|
|
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 =
|
|
1745
|
-
if (!
|
|
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 =
|
|
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 =
|
|
1760
|
-
const
|
|
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"
|
|
1777
|
-
index = allFiles.find((f) => f === "
|
|
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/
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
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
|
-
|
|
1861
|
-
import
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
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
|
-
|
|
1922
|
-
|
|
1879
|
+
function loadConfig(rootDir) {
|
|
1880
|
+
const configPath = findConfigFile(rootDir);
|
|
1881
|
+
if (!configPath) {
|
|
1882
|
+
return defaultConfig;
|
|
1883
|
+
}
|
|
1923
1884
|
try {
|
|
1924
|
-
const
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
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
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
2041
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
2272
2042
|
const parsed = parseYaml(content);
|
|
2273
2043
|
if (!parsed || typeof parsed !== "object") {
|
|
2274
2044
|
throw new Error(`Invalid tokens file: ${filePath} - must contain a YAML object`);
|
|
@@ -2299,951 +2069,1621 @@ function resolveTokens(options = {}) {
|
|
|
2299
2069
|
return resolved;
|
|
2300
2070
|
}
|
|
2301
2071
|
|
|
2302
|
-
// src/
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
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
|
|
2082
|
+
return cachedPages;
|
|
2314
2083
|
}
|
|
2315
2084
|
return {
|
|
2316
|
-
name: "prev-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
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/
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
function createConfigPlugin(config) {
|
|
2154
|
+
// src/server/plugins/mdx.ts
|
|
2155
|
+
function mdxPlugin(options) {
|
|
2156
|
+
const { rootDir } = options;
|
|
2361
2157
|
return {
|
|
2362
|
-
name: "prev-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
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/
|
|
2378
|
-
|
|
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-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
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
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
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/
|
|
2428
|
-
import
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
if (
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
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 (
|
|
2456
|
-
|
|
2457
|
-
for (const
|
|
2458
|
-
if (
|
|
2459
|
-
|
|
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 (
|
|
2464
|
-
|
|
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
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
}
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
}
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
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
|
-
|
|
2512
|
-
|
|
2332
|
+
|
|
2333
|
+
// src/server/routes/preview-config.ts
|
|
2513
2334
|
import path8 from "path";
|
|
2335
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2514
2336
|
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
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
|
-
|
|
2552
|
-
const
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
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
|
-
|
|
2581
|
-
const
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
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/
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
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
|
-
|
|
2638
|
-
|
|
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
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
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
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
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
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
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
|
-
|
|
2670
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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 =
|
|
2673
|
-
if (
|
|
2721
|
+
const pkgPath = path12.join(dir, "package.json");
|
|
2722
|
+
if (existsSync11(pkgPath)) {
|
|
2674
2723
|
try {
|
|
2675
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
2729
|
+
const parent = path12.dirname(dir);
|
|
2682
2730
|
if (parent === dir)
|
|
2683
2731
|
break;
|
|
2684
2732
|
dir = parent;
|
|
2685
2733
|
}
|
|
2686
|
-
return
|
|
2734
|
+
return path12.dirname(path12.dirname(fileURLToPath(import.meta.url)));
|
|
2687
2735
|
}
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2736
|
+
var cliRoot = findCliRoot();
|
|
2737
|
+
var srcRoot = path12.join(cliRoot, "src");
|
|
2738
|
+
async function buildThemeApp(rootDir, include, config) {
|
|
2739
|
+
const entryPath = path12.join(srcRoot, "theme/entry.tsx");
|
|
2740
|
+
const plugins = [
|
|
2741
|
+
virtualModulesPlugin({ rootDir, include, config }),
|
|
2742
|
+
mdxPlugin({ rootDir }),
|
|
2743
|
+
aliasesPlugin({ cliRoot })
|
|
2744
|
+
];
|
|
2745
|
+
const result = await Bun.build({
|
|
2746
|
+
entrypoints: [entryPath],
|
|
2747
|
+
format: "esm",
|
|
2748
|
+
target: "browser",
|
|
2749
|
+
plugins,
|
|
2750
|
+
jsx: { runtime: "automatic", importSource: "react" },
|
|
2751
|
+
define: {
|
|
2752
|
+
"import.meta.env.DEV": "true",
|
|
2753
|
+
"import.meta.env.BASE_URL": '"/"',
|
|
2754
|
+
"process.env.NODE_ENV": '"development"'
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2757
|
+
if (!result.success) {
|
|
2758
|
+
const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message);
|
|
2759
|
+
return { js: "", css: "", success: false, errors };
|
|
2760
|
+
}
|
|
2761
|
+
const jsOutput = result.outputs.find((o) => o.path.endsWith(".js"));
|
|
2762
|
+
const cssOutput = result.outputs.find((o) => o.path.endsWith(".css"));
|
|
2763
|
+
return {
|
|
2764
|
+
js: jsOutput ? await jsOutput.text() : "",
|
|
2765
|
+
css: cssOutput ? await cssOutput.text() : "",
|
|
2766
|
+
success: true,
|
|
2767
|
+
errors: []
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
var HTML_SHELL = `<!DOCTYPE html>
|
|
2771
|
+
<html lang="en">
|
|
2772
|
+
<head>
|
|
2773
|
+
<meta charset="UTF-8" />
|
|
2774
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2775
|
+
<title>Documentation</title>
|
|
2776
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
2777
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
2778
|
+
<link rel="preload" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" as="style" />
|
|
2779
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" />
|
|
2780
|
+
<link rel="stylesheet" href="/__prev/app.css" />
|
|
2781
|
+
</head>
|
|
2782
|
+
<body>
|
|
2783
|
+
<div id="root"></div>
|
|
2784
|
+
<script type="module" src="/__prev/app.js"></script>
|
|
2785
|
+
<script>
|
|
2786
|
+
if (typeof EventSource !== 'undefined') {
|
|
2787
|
+
var es = new EventSource('/__prev/events');
|
|
2788
|
+
es.onmessage = function() { location.reload(); };
|
|
2789
|
+
}
|
|
2790
|
+
</script>
|
|
2791
|
+
</body>
|
|
2792
|
+
</html>`;
|
|
2793
|
+
async function startDevServer(options) {
|
|
2794
|
+
const { rootDir, port, include } = options;
|
|
2795
|
+
const config = options.config || loadConfig(rootDir);
|
|
2796
|
+
console.log(" Building theme...");
|
|
2797
|
+
let appBundle = await buildThemeApp(rootDir, include, config);
|
|
2798
|
+
if (!appBundle.success) {
|
|
2799
|
+
console.error(" Build errors:", appBundle.errors.join(`
|
|
2800
|
+
`));
|
|
2801
|
+
} else {
|
|
2802
|
+
console.log(" \u2713 Theme built");
|
|
2803
|
+
}
|
|
2804
|
+
const sseControllers = new Set;
|
|
2805
|
+
const encoder = new TextEncoder;
|
|
2806
|
+
function notifyReload() {
|
|
2807
|
+
const msg = encoder.encode(`data: reload
|
|
2808
|
+
|
|
2809
|
+
`);
|
|
2810
|
+
for (const ctrl of sseControllers) {
|
|
2811
|
+
try {
|
|
2812
|
+
ctrl.enqueue(msg);
|
|
2813
|
+
} catch {
|
|
2814
|
+
sseControllers.delete(ctrl);
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2692
2817
|
}
|
|
2693
|
-
|
|
2818
|
+
const previewBundleHandler = createPreviewBundleHandler(rootDir);
|
|
2819
|
+
const previewConfigHandler = createPreviewConfigHandler(rootDir);
|
|
2820
|
+
const jsxBundleHandler = createJsxBundleHandler(cliRoot);
|
|
2821
|
+
const componentBundleHandler = createComponentBundleHandler(rootDir);
|
|
2822
|
+
const tokensHandler = createTokensHandler(rootDir);
|
|
2823
|
+
const previewRuntimePath = path12.join(srcRoot, "preview-runtime/fast-template.html");
|
|
2824
|
+
const server = Bun.serve({
|
|
2825
|
+
port,
|
|
2826
|
+
async fetch(req) {
|
|
2827
|
+
const url = new URL(req.url);
|
|
2828
|
+
const pathname = url.pathname;
|
|
2829
|
+
if (pathname === "/__prev/events") {
|
|
2830
|
+
let ctrl;
|
|
2831
|
+
const stream = new ReadableStream({
|
|
2832
|
+
start(controller) {
|
|
2833
|
+
ctrl = controller;
|
|
2834
|
+
sseControllers.add(controller);
|
|
2835
|
+
},
|
|
2836
|
+
cancel() {
|
|
2837
|
+
sseControllers.delete(ctrl);
|
|
2838
|
+
}
|
|
2839
|
+
});
|
|
2840
|
+
return new Response(stream, {
|
|
2841
|
+
headers: {
|
|
2842
|
+
"Content-Type": "text/event-stream",
|
|
2843
|
+
"Cache-Control": "no-cache"
|
|
2844
|
+
}
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2847
|
+
if (pathname === "/__prev/app.js") {
|
|
2848
|
+
return new Response(appBundle.js, {
|
|
2849
|
+
headers: { "Content-Type": "application/javascript" }
|
|
2850
|
+
});
|
|
2851
|
+
}
|
|
2852
|
+
if (pathname === "/__prev/app.css") {
|
|
2853
|
+
return new Response(appBundle.css, {
|
|
2854
|
+
headers: { "Content-Type": "text/css" }
|
|
2855
|
+
});
|
|
2856
|
+
}
|
|
2857
|
+
if (pathname === "/__prev/config" && req.method === "POST") {
|
|
2858
|
+
try {
|
|
2859
|
+
const body = await req.json();
|
|
2860
|
+
updateOrder(rootDir, body.path, body.order);
|
|
2861
|
+
return Response.json({ success: true });
|
|
2862
|
+
} catch (e) {
|
|
2863
|
+
return Response.json({ error: String(e) }, { status: 400 });
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
const bundleResponse = await previewBundleHandler(req);
|
|
2867
|
+
if (bundleResponse)
|
|
2868
|
+
return bundleResponse;
|
|
2869
|
+
const configResponse = await previewConfigHandler(req);
|
|
2870
|
+
if (configResponse)
|
|
2871
|
+
return configResponse;
|
|
2872
|
+
const jsxResponse = await jsxBundleHandler(req);
|
|
2873
|
+
if (jsxResponse)
|
|
2874
|
+
return jsxResponse;
|
|
2875
|
+
const componentResponse = await componentBundleHandler(req);
|
|
2876
|
+
if (componentResponse)
|
|
2877
|
+
return componentResponse;
|
|
2878
|
+
const tokensResponse = await tokensHandler(req);
|
|
2879
|
+
if (tokensResponse)
|
|
2880
|
+
return tokensResponse;
|
|
2881
|
+
const ogResponse = handleOgImageRequest(req, []);
|
|
2882
|
+
if (ogResponse)
|
|
2883
|
+
return ogResponse;
|
|
2884
|
+
if (pathname === "/_prev/region-bridge.js") {
|
|
2885
|
+
const { REGION_BRIDGE_SCRIPT: REGION_BRIDGE_SCRIPT2 } = await Promise.resolve().then(() => exports_region_bridge);
|
|
2886
|
+
return new Response(REGION_BRIDGE_SCRIPT2, {
|
|
2887
|
+
headers: { "Content-Type": "application/javascript" }
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
if (pathname === "/_preview-runtime") {
|
|
2891
|
+
if (existsSync11(previewRuntimePath)) {
|
|
2892
|
+
const html = readFileSync6(previewRuntimePath, "utf-8");
|
|
2893
|
+
return new Response(html, {
|
|
2894
|
+
headers: { "Content-Type": "text/html" }
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
if (pathname.startsWith("/_preview/")) {
|
|
2899
|
+
const relativePath = pathname.slice("/_preview/".length);
|
|
2900
|
+
const previewsDir2 = path12.join(rootDir, "previews");
|
|
2901
|
+
const filePath = path12.resolve(previewsDir2, relativePath);
|
|
2902
|
+
if (filePath.startsWith(previewsDir2) && existsSync11(filePath) && statSync(filePath).isFile()) {
|
|
2903
|
+
return new Response(Bun.file(filePath));
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
if (!pathname.includes(".") && !pathname.startsWith("/@") && !pathname.startsWith("/__") && !pathname.startsWith("/_preview") && !pathname.startsWith("/_prev")) {
|
|
2907
|
+
if (pathname.startsWith("/previews/") && pathname !== "/previews") {
|
|
2908
|
+
const previewPath = pathname.slice("/previews/".length);
|
|
2909
|
+
const searchParams = new URL(req.url).searchParams;
|
|
2910
|
+
const ogState = searchParams.get("state");
|
|
2911
|
+
const ogStep = searchParams.get("step");
|
|
2912
|
+
const ogTitle = previewPath.split("/").pop() || "Preview";
|
|
2913
|
+
const ogParams = [
|
|
2914
|
+
ogState ? `state=${ogState}` : "",
|
|
2915
|
+
ogStep ? `step=${ogStep}` : ""
|
|
2916
|
+
].filter(Boolean).join("&");
|
|
2917
|
+
const ogImageUrl = `/_og/${previewPath}${ogParams ? `?${ogParams}` : ""}`;
|
|
2918
|
+
const ogHtml = HTML_SHELL.replace("<title>Documentation</title>", `<title>${ogTitle} - Preview</title>
|
|
2919
|
+
<meta property="og:title" content="${ogTitle}" />
|
|
2920
|
+
<meta property="og:description" content="${ogState ? `State: ${ogState}` : ogStep ? `Step: ${ogStep}` : "Preview"}" />
|
|
2921
|
+
<meta property="og:image" content="${ogImageUrl}" />
|
|
2922
|
+
<meta property="og:type" content="website" />
|
|
2923
|
+
<meta name="twitter:card" content="summary_large_image" />`);
|
|
2924
|
+
return new Response(ogHtml, {
|
|
2925
|
+
headers: { "Content-Type": "text/html" }
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
return new Response(HTML_SHELL, {
|
|
2929
|
+
headers: { "Content-Type": "text/html" }
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
return new Response("Not Found", { status: 404 });
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
const { watch } = await import("fs");
|
|
2936
|
+
const watchers = [];
|
|
2937
|
+
let rebuildTimer = null;
|
|
2938
|
+
async function rebuild() {
|
|
2939
|
+
appBundle = await buildThemeApp(rootDir, include, config);
|
|
2940
|
+
if (appBundle.success) {
|
|
2941
|
+
notifyReload();
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
function scheduleRebuild() {
|
|
2945
|
+
if (rebuildTimer)
|
|
2946
|
+
clearTimeout(rebuildTimer);
|
|
2947
|
+
rebuildTimer = setTimeout(rebuild, 150);
|
|
2948
|
+
}
|
|
2949
|
+
const previewsDir = path12.join(rootDir, "previews");
|
|
2950
|
+
if (existsSync11(previewsDir)) {
|
|
2951
|
+
watchers.push(watch(previewsDir, { recursive: true }, (_, filename) => {
|
|
2952
|
+
if (filename && /\.(tsx|ts|jsx|js|css|yaml|yml|mdx|md|html)$/.test(filename)) {
|
|
2953
|
+
scheduleRebuild();
|
|
2954
|
+
}
|
|
2955
|
+
}));
|
|
2956
|
+
}
|
|
2957
|
+
return {
|
|
2958
|
+
server,
|
|
2959
|
+
port: server.port,
|
|
2960
|
+
url: `http://localhost:${server.port}/`,
|
|
2961
|
+
stop: () => {
|
|
2962
|
+
if (rebuildTimer)
|
|
2963
|
+
clearTimeout(rebuildTimer);
|
|
2964
|
+
watchers.forEach((w) => w.close());
|
|
2965
|
+
server.stop();
|
|
2966
|
+
}
|
|
2967
|
+
};
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
// src/server/build.ts
|
|
2971
|
+
import path14 from "path";
|
|
2972
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync3, copyFileSync } from "fs";
|
|
2973
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2974
|
+
|
|
2975
|
+
// src/preview-runtime/vendors.ts
|
|
2976
|
+
import { join, dirname } from "path";
|
|
2977
|
+
import { mkdtempSync, writeFileSync as writeFileSync2, rmSync, existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
2978
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2979
|
+
import { tmpdir } from "os";
|
|
2980
|
+
function findCliRoot2() {
|
|
2981
|
+
let dir = dirname(fileURLToPath2(import.meta.url));
|
|
2694
2982
|
for (let i = 0;i < 10; i++) {
|
|
2695
|
-
const
|
|
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
|
|
2996
|
+
return dirname(dirname(fileURLToPath2(import.meta.url)));
|
|
2704
2997
|
}
|
|
2705
2998
|
var cliRoot2 = findCliRoot2();
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
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
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
3048
|
+
}
|
|
3049
|
+
async function buildJsxBundle(vendorPath) {
|
|
3050
|
+
try {
|
|
3051
|
+
const minimalJsx = `
|
|
3052
|
+
import * as React from 'react'
|
|
3053
|
+
|
|
3054
|
+
// Default token values for standalone preview rendering
|
|
3055
|
+
const defaultTokens = {
|
|
3056
|
+
background: {
|
|
3057
|
+
primary: '#3b82f6',
|
|
3058
|
+
secondary: '#f1f5f9',
|
|
3059
|
+
destructive: '#ef4444',
|
|
3060
|
+
muted: '#f1f5f9',
|
|
3061
|
+
accent: '#f1f5f9',
|
|
3062
|
+
transparent: 'transparent',
|
|
3063
|
+
},
|
|
3064
|
+
color: {
|
|
3065
|
+
'primary-foreground': '#ffffff',
|
|
3066
|
+
'secondary-foreground': '#0f172a',
|
|
3067
|
+
'destructive-foreground': '#ffffff',
|
|
3068
|
+
'muted-foreground': '#64748b',
|
|
3069
|
+
'accent-foreground': '#0f172a',
|
|
3070
|
+
foreground: '#0f172a',
|
|
3071
|
+
},
|
|
3072
|
+
spacing: {
|
|
3073
|
+
xs: '4px',
|
|
3074
|
+
sm: '8px',
|
|
3075
|
+
md: '12px',
|
|
3076
|
+
lg: '16px',
|
|
3077
|
+
xl: '24px',
|
|
3078
|
+
},
|
|
3079
|
+
radius: {
|
|
3080
|
+
none: '0',
|
|
3081
|
+
sm: '4px',
|
|
3082
|
+
md: '6px',
|
|
3083
|
+
lg: '8px',
|
|
3084
|
+
full: '9999px',
|
|
3085
|
+
},
|
|
3086
|
+
'typography.size': {
|
|
3087
|
+
xs: '12px',
|
|
3088
|
+
sm: '14px',
|
|
3089
|
+
base: '16px',
|
|
3090
|
+
lg: '18px',
|
|
3091
|
+
xl: '20px',
|
|
3092
|
+
},
|
|
3093
|
+
'typography.weight': {
|
|
3094
|
+
normal: '400',
|
|
3095
|
+
medium: '500',
|
|
3096
|
+
semibold: '600',
|
|
3097
|
+
bold: '700',
|
|
3098
|
+
},
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
// Token resolution
|
|
3102
|
+
let tokensConfig = null
|
|
3103
|
+
export function setTokensConfig(config) { tokensConfig = config }
|
|
3104
|
+
|
|
3105
|
+
function resolveToken(category, token) {
|
|
3106
|
+
// Check custom config first, then defaults
|
|
3107
|
+
const config = tokensConfig || defaultTokens
|
|
3108
|
+
const cat = config[category]
|
|
3109
|
+
return cat?.[token] ?? token
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
// VNode type
|
|
3113
|
+
export class VNode {
|
|
3114
|
+
constructor(type, props, children) {
|
|
3115
|
+
this.type = type
|
|
3116
|
+
this.props = props || {}
|
|
3117
|
+
this.children = children || []
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
// Primitives - return VNodes
|
|
3122
|
+
export function Box(props) { return new VNode('Box', props, props.children ? [props.children] : []) }
|
|
3123
|
+
export function Text(props) { return new VNode('Text', props, props.children ? [props.children] : []) }
|
|
3124
|
+
export function Col(props) { return new VNode('Col', props, props.children || []) }
|
|
3125
|
+
export function Row(props) { return new VNode('Row', props, props.children || []) }
|
|
3126
|
+
export function Spacer(props) { return new VNode('Spacer', props, []) }
|
|
3127
|
+
export function Slot(props) { return new VNode('Slot', props, []) }
|
|
3128
|
+
export function Icon(props) { return new VNode('Icon', props, []) }
|
|
3129
|
+
export function Image(props) { return new VNode('Image', props, []) }
|
|
3130
|
+
export const Fragment = React.Fragment
|
|
3131
|
+
|
|
3132
|
+
// Convert VNode to React element
|
|
3133
|
+
export function toReact(vnode) {
|
|
3134
|
+
if (!vnode || typeof vnode !== 'object') return vnode
|
|
3135
|
+
if (!(vnode instanceof VNode)) return vnode
|
|
3136
|
+
|
|
3137
|
+
const { type, props, children } = vnode
|
|
3138
|
+
const style = {}
|
|
3139
|
+
|
|
3140
|
+
// Map props to styles
|
|
3141
|
+
if (props.bg) style.backgroundColor = resolveToken('background', props.bg)
|
|
3142
|
+
if (props.padding) style.padding = resolveToken('spacing', props.padding)
|
|
3143
|
+
if (props.radius) style.borderRadius = resolveToken('radius', props.radius)
|
|
3144
|
+
if (props.color) style.color = resolveToken('color', props.color)
|
|
3145
|
+
if (props.size) style.fontSize = resolveToken('typography.size', props.size)
|
|
3146
|
+
if (props.weight) style.fontWeight = resolveToken('typography.weight', props.weight)
|
|
3147
|
+
if (props.gap) style.gap = resolveToken('spacing', props.gap)
|
|
3148
|
+
|
|
3149
|
+
// Layout types
|
|
3150
|
+
if (type === 'Col') { style.display = 'flex'; style.flexDirection = 'column' }
|
|
3151
|
+
if (type === 'Row') { style.display = 'flex'; style.flexDirection = 'row' }
|
|
3152
|
+
if (type === 'Spacer') { style.flex = 1 }
|
|
3153
|
+
|
|
3154
|
+
const childElements = children.map(c => toReact(c))
|
|
3155
|
+
|
|
3156
|
+
return React.createElement('div', { style }, ...childElements)
|
|
3157
|
+
}
|
|
3158
|
+
`;
|
|
3159
|
+
const tempDir = mkdtempSync(join(tmpdir(), "prev-jsx-"));
|
|
3160
|
+
const entryPath = join(tempDir, "entry.ts");
|
|
3161
|
+
try {
|
|
3162
|
+
writeFileSync2(entryPath, minimalJsx);
|
|
3163
|
+
const result = await Bun.build({
|
|
3164
|
+
entrypoints: [entryPath],
|
|
3165
|
+
format: "esm",
|
|
3166
|
+
target: "browser",
|
|
3167
|
+
minify: true,
|
|
3168
|
+
plugins: [
|
|
3169
|
+
{
|
|
3170
|
+
name: "jsx-externals",
|
|
3171
|
+
setup(build) {
|
|
3172
|
+
build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
|
|
3173
|
+
return { path: vendorPath, external: true };
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
2754
3176
|
}
|
|
3177
|
+
]
|
|
3178
|
+
});
|
|
3179
|
+
if (!result.success) {
|
|
3180
|
+
const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message).join("; ");
|
|
3181
|
+
return { success: false, code: "", error: errors || "Build failed" };
|
|
3182
|
+
}
|
|
3183
|
+
const jsFile = result.outputs.find((f) => f.path.endsWith(".js")) || result.outputs[0];
|
|
3184
|
+
if (!jsFile) {
|
|
3185
|
+
return { success: false, code: "", error: "No output generated" };
|
|
3186
|
+
}
|
|
3187
|
+
let code = await jsFile.text();
|
|
3188
|
+
code = code.replace(/from\s*["']react["']/g, `from"${vendorPath}"`);
|
|
3189
|
+
return { success: true, code };
|
|
3190
|
+
} finally {
|
|
3191
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
3192
|
+
}
|
|
3193
|
+
} catch (err) {
|
|
3194
|
+
return {
|
|
3195
|
+
success: false,
|
|
3196
|
+
code: "",
|
|
3197
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3198
|
+
};
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
// src/preview-runtime/tailwind.ts
|
|
3203
|
+
var {$ } = globalThis.Bun;
|
|
3204
|
+
import { mkdtempSync as mkdtempSync2, mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync8, rmSync as rmSync2, existsSync as existsSync13 } from "fs";
|
|
3205
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
3206
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3207
|
+
function findTailwindBin() {
|
|
3208
|
+
try {
|
|
3209
|
+
const tailwindPkg = __require.resolve("tailwindcss/package.json");
|
|
3210
|
+
const tailwindDir = dirname2(tailwindPkg);
|
|
3211
|
+
const binPath = join2(tailwindDir, "lib/cli.js");
|
|
3212
|
+
if (existsSync13(binPath))
|
|
3213
|
+
return binPath;
|
|
3214
|
+
} catch {}
|
|
3215
|
+
return "bunx tailwindcss@3";
|
|
3216
|
+
}
|
|
3217
|
+
var tailwindCmd = findTailwindBin();
|
|
3218
|
+
async function compileTailwind(files) {
|
|
3219
|
+
const tempDir = mkdtempSync2(join2(tmpdir2(), "prev-tailwind-"));
|
|
3220
|
+
try {
|
|
3221
|
+
for (const file of files) {
|
|
3222
|
+
const filePath = join2(tempDir, file.path);
|
|
3223
|
+
const parentDir = dirname2(filePath);
|
|
3224
|
+
mkdirSync(parentDir, { recursive: true });
|
|
3225
|
+
writeFileSync3(filePath, file.content);
|
|
3226
|
+
}
|
|
3227
|
+
const configContent = `
|
|
3228
|
+
module.exports = {
|
|
3229
|
+
content: [${JSON.stringify(tempDir + "/**/*.{tsx,jsx,ts,js,html}")}],
|
|
3230
|
+
}
|
|
3231
|
+
`;
|
|
3232
|
+
const configPath = join2(tempDir, "tailwind.config.cjs");
|
|
3233
|
+
writeFileSync3(configPath, configContent);
|
|
3234
|
+
const inputCss = `
|
|
3235
|
+
@tailwind base;
|
|
3236
|
+
@tailwind components;
|
|
3237
|
+
@tailwind utilities;
|
|
3238
|
+
`;
|
|
3239
|
+
const inputPath = join2(tempDir, "input.css");
|
|
3240
|
+
writeFileSync3(inputPath, inputCss);
|
|
3241
|
+
const outputPath = join2(tempDir, "output.css");
|
|
3242
|
+
if (tailwindCmd.startsWith("bunx")) {
|
|
3243
|
+
await $`bunx tailwindcss@3 -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet();
|
|
3244
|
+
} else {
|
|
3245
|
+
await $`bun ${tailwindCmd} -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet();
|
|
3246
|
+
}
|
|
3247
|
+
const css = readFileSync8(outputPath, "utf-8");
|
|
3248
|
+
return { success: true, css };
|
|
3249
|
+
} catch (err) {
|
|
3250
|
+
return {
|
|
3251
|
+
success: false,
|
|
3252
|
+
css: "",
|
|
3253
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3254
|
+
};
|
|
3255
|
+
} finally {
|
|
3256
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
// src/preview-runtime/build-optimized.ts
|
|
3261
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9, mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync4, rmSync as rmSync3, mkdirSync as mkdirSync2, statSync as statSync2 } from "fs";
|
|
3262
|
+
import path13 from "path";
|
|
3263
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
3264
|
+
function existsAsFile(p) {
|
|
3265
|
+
try {
|
|
3266
|
+
return statSync2(p).isFile();
|
|
3267
|
+
} catch {
|
|
3268
|
+
return false;
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
async function buildOptimizedPreview(config, options) {
|
|
3272
|
+
try {
|
|
3273
|
+
const virtualFs = {};
|
|
3274
|
+
const resolveDir = options.resolveDir || "/";
|
|
3275
|
+
for (const file of config.files) {
|
|
3276
|
+
const ext = file.path.split(".").pop()?.toLowerCase();
|
|
3277
|
+
const loader = ext === "css" ? "css" : ext === "json" ? "json" : ext || "tsx";
|
|
3278
|
+
virtualFs[file.path] = { contents: file.content, loader };
|
|
3279
|
+
}
|
|
3280
|
+
const entryFile = config.files.find((f) => f.path === config.entry);
|
|
3281
|
+
if (!entryFile) {
|
|
3282
|
+
return { success: false, html: "", css: "", error: `Entry file not found: ${config.entry}` };
|
|
3283
|
+
}
|
|
3284
|
+
const hasDefaultExport = /export\s+default/.test(entryFile.content);
|
|
3285
|
+
const userCssCollected = [];
|
|
3286
|
+
const entryCode = hasDefaultExport ? `
|
|
3287
|
+
import React, { createRoot } from '${options.vendorPath}'
|
|
3288
|
+
import App from './${config.entry}'
|
|
3289
|
+
const root = createRoot(document.getElementById('root'))
|
|
3290
|
+
root.render(React.createElement(App))
|
|
3291
|
+
` : `import './${config.entry}'`;
|
|
3292
|
+
const tempDir = mkdtempSync3(path13.join(tmpdir3(), "prev-optimized-"));
|
|
3293
|
+
const entryPath = path13.join(tempDir, "__entry.tsx");
|
|
3294
|
+
try {
|
|
3295
|
+
writeFileSync4(entryPath, entryCode);
|
|
3296
|
+
for (const [filePath, file] of Object.entries(virtualFs)) {
|
|
3297
|
+
const targetPath = path13.join(tempDir, filePath);
|
|
3298
|
+
const dir = path13.dirname(targetPath);
|
|
3299
|
+
if (!existsSync14(dir)) {
|
|
3300
|
+
mkdirSync2(dir, { recursive: true });
|
|
2755
3301
|
}
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
3302
|
+
writeFileSync4(targetPath, file.contents);
|
|
3303
|
+
}
|
|
3304
|
+
const result = await Bun.build({
|
|
3305
|
+
entrypoints: [entryPath],
|
|
3306
|
+
format: "esm",
|
|
3307
|
+
target: "browser",
|
|
3308
|
+
minify: true,
|
|
3309
|
+
jsx: { runtime: "automatic", importSource: "react" },
|
|
3310
|
+
plugins: [
|
|
3311
|
+
{
|
|
3312
|
+
name: "optimized-preview",
|
|
3313
|
+
setup(build) {
|
|
3314
|
+
build.onResolve({ filter: new RegExp(options.vendorPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) }, (args) => {
|
|
3315
|
+
return { path: args.path, external: true };
|
|
2765
3316
|
});
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
const { path: pathKey, order } = JSON.parse(body);
|
|
2769
|
-
updateOrder(rootDir, pathKey, order);
|
|
2770
|
-
res.statusCode = 200;
|
|
2771
|
-
res.end(JSON.stringify({ success: true }));
|
|
2772
|
-
} catch (e) {
|
|
2773
|
-
res.statusCode = 400;
|
|
2774
|
-
res.end(JSON.stringify({ error: String(e) }));
|
|
2775
|
-
}
|
|
3317
|
+
build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
|
|
3318
|
+
return { path: options.vendorPath, external: true };
|
|
2776
3319
|
});
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
res.setHeader("Content-Type", "text/html");
|
|
2797
|
-
res.end(html);
|
|
2798
|
-
}).catch(next);
|
|
2799
|
-
return;
|
|
2800
|
-
}
|
|
2801
|
-
next();
|
|
2802
|
-
});
|
|
2803
|
-
};
|
|
2804
|
-
}
|
|
2805
|
-
},
|
|
2806
|
-
{
|
|
2807
|
-
name: "prev-jsx-bundle",
|
|
2808
|
-
configureServer(server) {
|
|
2809
|
-
let cachedBundle = null;
|
|
2810
|
-
server.middlewares.use("/_prev/jsx.js", async (req, res, next) => {
|
|
2811
|
-
if (req.method !== "GET")
|
|
2812
|
-
return next();
|
|
2813
|
-
try {
|
|
2814
|
-
if (!cachedBundle) {
|
|
2815
|
-
const jsxEntry = path9.join(srcRoot2, "jsx/index.ts");
|
|
2816
|
-
const result = await viteBuild({
|
|
2817
|
-
logLevel: "silent",
|
|
2818
|
-
define: {
|
|
2819
|
-
"process.env.NODE_ENV": '"production"'
|
|
2820
|
-
},
|
|
2821
|
-
esbuild: {
|
|
2822
|
-
jsx: "automatic",
|
|
2823
|
-
jsxImportSource: "react",
|
|
2824
|
-
jsxDev: false
|
|
2825
|
-
},
|
|
2826
|
-
build: {
|
|
2827
|
-
write: false,
|
|
2828
|
-
lib: {
|
|
2829
|
-
entry: jsxEntry,
|
|
2830
|
-
formats: ["es"],
|
|
2831
|
-
fileName: "jsx"
|
|
2832
|
-
},
|
|
2833
|
-
rollupOptions: {
|
|
2834
|
-
external: ["react", "react-dom", "react/jsx-runtime", "zod"],
|
|
2835
|
-
output: {
|
|
2836
|
-
globals: {
|
|
2837
|
-
react: "React",
|
|
2838
|
-
"react-dom": "ReactDOM"
|
|
2839
|
-
}
|
|
2840
|
-
}
|
|
2841
|
-
},
|
|
2842
|
-
minify: false
|
|
2843
|
-
}
|
|
2844
|
-
});
|
|
2845
|
-
const output = Array.isArray(result) ? result[0] : result;
|
|
2846
|
-
if (!("output" in output)) {
|
|
2847
|
-
throw new Error("Unexpected build result type");
|
|
2848
|
-
}
|
|
2849
|
-
const jsFile = output.output.find((f) => f.type === "chunk");
|
|
2850
|
-
if (jsFile && "code" in jsFile) {
|
|
2851
|
-
cachedBundle = jsFile.code.replace(/from\s+['"]react\/jsx-runtime['"]/g, 'from "https://esm.sh/react@18/jsx-runtime"').replace(/from\s+['"]react['"]/g, 'from "https://esm.sh/react@18"').replace(/from\s+['"]react-dom['"]/g, 'from "https://esm.sh/react-dom@18"').replace(/from\s+['"]zod['"]/g, 'from "https://esm.sh/zod"');
|
|
2852
|
-
}
|
|
2853
|
-
}
|
|
2854
|
-
if (cachedBundle) {
|
|
2855
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
2856
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
2857
|
-
res.end(cachedBundle);
|
|
2858
|
-
return;
|
|
2859
|
-
}
|
|
2860
|
-
res.statusCode = 500;
|
|
2861
|
-
res.end("Failed to bundle jsx primitives");
|
|
2862
|
-
} catch (err) {
|
|
2863
|
-
console.error("Error bundling jsx:", err);
|
|
2864
|
-
res.statusCode = 500;
|
|
2865
|
-
res.end(String(err));
|
|
2866
|
-
}
|
|
2867
|
-
});
|
|
2868
|
-
server.watcher.on("change", (file) => {
|
|
2869
|
-
if (file.includes("/jsx/")) {
|
|
2870
|
-
cachedBundle = null;
|
|
2871
|
-
}
|
|
2872
|
-
});
|
|
2873
|
-
}
|
|
2874
|
-
},
|
|
2875
|
-
{
|
|
2876
|
-
name: "prev-components-bundle",
|
|
2877
|
-
configureServer(server) {
|
|
2878
|
-
const componentCache = new Map;
|
|
2879
|
-
server.middlewares.use(async (req, res, next) => {
|
|
2880
|
-
const urlPath = req.url?.split("?")[0] || "";
|
|
2881
|
-
const match = urlPath.match(/^\/_prev\/components\/([^/]+)\.js$/);
|
|
2882
|
-
if (!match || req.method !== "GET")
|
|
2883
|
-
return next();
|
|
2884
|
-
const componentName = match[1];
|
|
2885
|
-
const componentEntry = path9.join(rootDir, "previews/components", componentName, "index.tsx");
|
|
2886
|
-
if (!existsSync8(componentEntry)) {
|
|
2887
|
-
res.statusCode = 404;
|
|
2888
|
-
res.end(`Component not found: ${componentName}`);
|
|
2889
|
-
return;
|
|
2890
|
-
}
|
|
2891
|
-
try {
|
|
2892
|
-
let bundledCode = componentCache.get(componentName);
|
|
2893
|
-
if (!bundledCode) {
|
|
2894
|
-
const result = await viteBuild({
|
|
2895
|
-
logLevel: "silent",
|
|
2896
|
-
define: {
|
|
2897
|
-
"process.env.NODE_ENV": '"production"'
|
|
2898
|
-
},
|
|
2899
|
-
esbuild: {
|
|
2900
|
-
jsx: "automatic",
|
|
2901
|
-
jsxImportSource: "react",
|
|
2902
|
-
jsxDev: false
|
|
2903
|
-
},
|
|
2904
|
-
build: {
|
|
2905
|
-
write: false,
|
|
2906
|
-
lib: {
|
|
2907
|
-
entry: componentEntry,
|
|
2908
|
-
formats: ["es"],
|
|
2909
|
-
fileName: componentName
|
|
2910
|
-
},
|
|
2911
|
-
rollupOptions: {
|
|
2912
|
-
external: ["react", "react-dom", "react/jsx-runtime", "@prev/jsx"],
|
|
2913
|
-
output: {
|
|
2914
|
-
globals: {
|
|
2915
|
-
react: "React",
|
|
2916
|
-
"react-dom": "ReactDOM"
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
},
|
|
2920
|
-
minify: false
|
|
3320
|
+
build.onResolve({ filter: /^@prev\/jsx$/ }, () => {
|
|
3321
|
+
const jsxPath2 = options.jsxPath || options.vendorPath.replace("runtime.js", "jsx.js");
|
|
3322
|
+
return { path: jsxPath2, external: true };
|
|
3323
|
+
});
|
|
3324
|
+
build.onResolve({ filter: /^@prev\/components\// }, (args) => {
|
|
3325
|
+
console.warn(` Warning: @prev/components imports not supported in static builds: ${args.path}`);
|
|
3326
|
+
return { path: args.path, external: true };
|
|
3327
|
+
});
|
|
3328
|
+
build.onLoad({ filter: /\.css$/ }, (args) => {
|
|
3329
|
+
let content;
|
|
3330
|
+
const tempPath = args.path;
|
|
3331
|
+
if (existsSync14(tempPath)) {
|
|
3332
|
+
content = readFileSync9(tempPath, "utf-8");
|
|
3333
|
+
} else {
|
|
3334
|
+
const diskPath = path13.resolve(resolveDir, path13.relative(tempDir, tempPath));
|
|
3335
|
+
if (existsSync14(diskPath)) {
|
|
3336
|
+
content = readFileSync9(diskPath, "utf-8");
|
|
3337
|
+
} else {
|
|
3338
|
+
return { contents: "", loader: "js" };
|
|
2921
3339
|
}
|
|
2922
|
-
});
|
|
2923
|
-
const output = Array.isArray(result) ? result[0] : result;
|
|
2924
|
-
if (!("output" in output)) {
|
|
2925
|
-
throw new Error("Unexpected build result type");
|
|
2926
|
-
}
|
|
2927
|
-
const jsFile = output.output.find((f) => f.type === "chunk");
|
|
2928
|
-
if (jsFile && "code" in jsFile) {
|
|
2929
|
-
bundledCode = jsFile.code.replace(/from\s+['"]react\/jsx-runtime['"]/g, 'from "https://esm.sh/react@18/jsx-runtime"').replace(/from\s+['"]react['"]/g, 'from "https://esm.sh/react@18"').replace(/from\s+['"]react-dom['"]/g, 'from "https://esm.sh/react-dom@18"').replace(/from\s+['"]@prev\/jsx['"]/g, `from "${req.headers.origin || ""}/_prev/jsx.js"`);
|
|
2930
|
-
componentCache.set(componentName, bundledCode);
|
|
2931
|
-
}
|
|
2932
|
-
}
|
|
2933
|
-
if (bundledCode) {
|
|
2934
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
2935
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
2936
|
-
res.end(bundledCode);
|
|
2937
|
-
return;
|
|
2938
|
-
}
|
|
2939
|
-
res.statusCode = 500;
|
|
2940
|
-
res.end(`Failed to bundle component: ${componentName}`);
|
|
2941
|
-
} catch (err) {
|
|
2942
|
-
console.error(`Error bundling component ${componentName}:`, err);
|
|
2943
|
-
res.statusCode = 500;
|
|
2944
|
-
res.end(String(err));
|
|
2945
|
-
}
|
|
2946
|
-
});
|
|
2947
|
-
server.watcher.on("change", (file) => {
|
|
2948
|
-
if (file.includes("/previews/components/")) {
|
|
2949
|
-
const match = file.match(/\/previews\/components\/([^/]+)\//);
|
|
2950
|
-
if (match) {
|
|
2951
|
-
componentCache.delete(match[1]);
|
|
2952
|
-
}
|
|
2953
|
-
}
|
|
2954
|
-
});
|
|
2955
|
-
}
|
|
2956
|
-
},
|
|
2957
|
-
{
|
|
2958
|
-
name: "prev-preview-server",
|
|
2959
|
-
resolveId(id) {
|
|
2960
|
-
if (id.startsWith("/_preview/")) {
|
|
2961
|
-
const relativePath = id.slice("/_preview/".length);
|
|
2962
|
-
const previewsDir = path9.join(rootDir, "previews");
|
|
2963
|
-
const resolved = path9.resolve(previewsDir, relativePath);
|
|
2964
|
-
if (resolved.startsWith(previewsDir)) {
|
|
2965
|
-
return resolved;
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
|
-
},
|
|
2969
|
-
configureServer(server) {
|
|
2970
|
-
server.middlewares.use(async (req, res, next) => {
|
|
2971
|
-
const urlPath = req.url?.split("?")[0] || "";
|
|
2972
|
-
if (urlPath.startsWith("/_preview-bundle/")) {
|
|
2973
|
-
const startTime = performance.now();
|
|
2974
|
-
const previewPath = decodeURIComponent(urlPath.slice("/_preview-bundle/".length));
|
|
2975
|
-
const previewDir = path9.join(rootDir, "previews", previewPath);
|
|
2976
|
-
if (!previewDir.startsWith(path9.join(rootDir, "previews"))) {
|
|
2977
|
-
res.statusCode = 403;
|
|
2978
|
-
res.end("Forbidden");
|
|
2979
|
-
return;
|
|
2980
|
-
}
|
|
2981
|
-
const entryFiles = ["index.tsx", "index.ts", "index.jsx", "index.js", "App.tsx", "App.ts"];
|
|
2982
|
-
let entryFile = "";
|
|
2983
|
-
for (const f of entryFiles) {
|
|
2984
|
-
if (existsSync8(path9.join(previewDir, f))) {
|
|
2985
|
-
entryFile = path9.join(previewDir, f);
|
|
2986
|
-
break;
|
|
2987
3340
|
}
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
entryPoints: [entryFile],
|
|
2997
|
-
bundle: true,
|
|
2998
|
-
write: false,
|
|
2999
|
-
format: "esm",
|
|
3000
|
-
jsx: "automatic",
|
|
3001
|
-
jsxImportSource: "react",
|
|
3002
|
-
jsxDev: false,
|
|
3003
|
-
target: "es2020",
|
|
3004
|
-
minify: false,
|
|
3005
|
-
sourcemap: false,
|
|
3006
|
-
external: ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "@prev/jsx", "fs", "path", "js-yaml"],
|
|
3007
|
-
alias: {
|
|
3008
|
-
react: "https://esm.sh/react@18",
|
|
3009
|
-
"react-dom": "https://esm.sh/react-dom@18",
|
|
3010
|
-
"react-dom/client": "https://esm.sh/react-dom@18/client",
|
|
3011
|
-
"react/jsx-runtime": "https://esm.sh/react@18/jsx-runtime"
|
|
3012
|
-
},
|
|
3013
|
-
define: {
|
|
3014
|
-
"process.env.NODE_ENV": '"production"'
|
|
3015
|
-
}
|
|
3016
|
-
});
|
|
3017
|
-
const bundleTime = Math.round(performance.now() - startTime);
|
|
3018
|
-
const code = result.outputFiles[0]?.text || "";
|
|
3019
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
3020
|
-
res.setHeader("X-Bundle-Time", String(bundleTime));
|
|
3021
|
-
res.end(code);
|
|
3022
|
-
return;
|
|
3023
|
-
} catch (err) {
|
|
3024
|
-
console.error("Bundle error:", err);
|
|
3025
|
-
res.statusCode = 500;
|
|
3026
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
3027
|
-
return;
|
|
3028
|
-
}
|
|
3029
|
-
}
|
|
3030
|
-
if (urlPath === "/_preview-runtime") {
|
|
3031
|
-
const templatePath = path9.join(srcRoot2, "preview-runtime/fast-template.html");
|
|
3032
|
-
if (existsSync8(templatePath)) {
|
|
3033
|
-
const html = readFileSync7(templatePath, "utf-8");
|
|
3034
|
-
res.setHeader("Content-Type", "text/html");
|
|
3035
|
-
res.end(html);
|
|
3036
|
-
return;
|
|
3037
|
-
}
|
|
3038
|
-
}
|
|
3039
|
-
if (urlPath.startsWith("/_preview-config/")) {
|
|
3040
|
-
const pathAfterConfig = decodeURIComponent(urlPath.slice("/_preview-config/".length));
|
|
3041
|
-
const previewsDir = path9.join(rootDir, "previews");
|
|
3042
|
-
const multiTypeMatch = pathAfterConfig.match(/^(components|screens|flows|atlas)\/(.+)$/);
|
|
3043
|
-
if (multiTypeMatch) {
|
|
3044
|
-
const [, type, name] = multiTypeMatch;
|
|
3045
|
-
const previewDir = path9.join(previewsDir, type, name);
|
|
3046
|
-
if (!previewDir.startsWith(previewsDir)) {
|
|
3047
|
-
res.statusCode = 403;
|
|
3048
|
-
res.end("Forbidden");
|
|
3341
|
+
userCssCollected.push(content);
|
|
3342
|
+
return { contents: "", loader: "js" };
|
|
3343
|
+
});
|
|
3344
|
+
build.onResolve({ filter: /^\.\.?\// }, (args) => {
|
|
3345
|
+
const resolved = path13.resolve(path13.dirname(args.importer), args.path);
|
|
3346
|
+
const tryExts = [".tsx", ".ts", ".jsx", ".js", ".css"];
|
|
3347
|
+
const tryIndex = ["/index.tsx", "/index.ts", "/index.jsx", "/index.js"];
|
|
3348
|
+
if (existsAsFile(resolved))
|
|
3049
3349
|
return;
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
try {
|
|
3053
|
-
if (type === "flows") {
|
|
3054
|
-
const configPathYaml = path9.join(previewDir, "index.yaml");
|
|
3055
|
-
const configPathYml = path9.join(previewDir, "index.yml");
|
|
3056
|
-
const configPath = existsSync8(configPathYaml) ? configPathYaml : configPathYml;
|
|
3057
|
-
if (existsSync8(configPath)) {
|
|
3058
|
-
const flow = await parseFlowDefinition(configPath);
|
|
3059
|
-
if (flow) {
|
|
3060
|
-
res.setHeader("Content-Type", "application/json");
|
|
3061
|
-
res.end(JSON.stringify(flow));
|
|
3062
|
-
return;
|
|
3063
|
-
}
|
|
3064
|
-
}
|
|
3065
|
-
} else if (type === "atlas") {
|
|
3066
|
-
const configPathYaml = path9.join(previewDir, "index.yaml");
|
|
3067
|
-
const configPathYml = path9.join(previewDir, "index.yml");
|
|
3068
|
-
const configPath = existsSync8(configPathYaml) ? configPathYaml : configPathYml;
|
|
3069
|
-
if (existsSync8(configPath)) {
|
|
3070
|
-
const atlas = await parseAtlasDefinition(configPath);
|
|
3071
|
-
if (atlas) {
|
|
3072
|
-
res.setHeader("Content-Type", "application/json");
|
|
3073
|
-
res.end(JSON.stringify(atlas));
|
|
3074
|
-
return;
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
} else {
|
|
3078
|
-
const config2 = await buildPreviewConfig(previewDir);
|
|
3079
|
-
res.setHeader("Content-Type", "application/json");
|
|
3080
|
-
res.end(JSON.stringify(config2));
|
|
3081
|
-
return;
|
|
3082
|
-
}
|
|
3083
|
-
res.statusCode = 400;
|
|
3084
|
-
res.end(JSON.stringify({ error: "Invalid config format" }));
|
|
3350
|
+
for (const ext of tryExts) {
|
|
3351
|
+
if (existsAsFile(resolved + ext))
|
|
3085
3352
|
return;
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
3353
|
+
}
|
|
3354
|
+
for (const idx of tryIndex) {
|
|
3355
|
+
if (existsAsFile(resolved + idx))
|
|
3090
3356
|
return;
|
|
3091
|
-
}
|
|
3092
3357
|
}
|
|
3093
|
-
|
|
3094
|
-
const
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3358
|
+
const importerRelative = path13.relative(tempDir, args.importer);
|
|
3359
|
+
const originalDir = path13.resolve(resolveDir, path13.dirname(importerRelative));
|
|
3360
|
+
const diskPath = path13.resolve(originalDir, args.path);
|
|
3361
|
+
if (existsAsFile(diskPath))
|
|
3362
|
+
return { path: diskPath };
|
|
3363
|
+
for (const ext of tryExts) {
|
|
3364
|
+
if (existsAsFile(diskPath + ext)) {
|
|
3365
|
+
return { path: diskPath + ext };
|
|
3366
|
+
}
|
|
3099
3367
|
}
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
res.setHeader("Content-Type", "application/json");
|
|
3104
|
-
res.end(JSON.stringify(config2));
|
|
3105
|
-
return;
|
|
3106
|
-
} catch (err) {
|
|
3107
|
-
console.error("Error building preview config:", err);
|
|
3108
|
-
res.statusCode = 500;
|
|
3109
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
3110
|
-
return;
|
|
3368
|
+
for (const idx of tryIndex) {
|
|
3369
|
+
if (existsAsFile(diskPath + idx)) {
|
|
3370
|
+
return { path: diskPath + idx };
|
|
3111
3371
|
}
|
|
3112
3372
|
}
|
|
3113
|
-
|
|
3373
|
+
return;
|
|
3374
|
+
});
|
|
3114
3375
|
}
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3376
|
+
}
|
|
3377
|
+
]
|
|
3378
|
+
});
|
|
3379
|
+
if (!result.success) {
|
|
3380
|
+
const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message).join("; ");
|
|
3381
|
+
return { success: false, html: "", css: "", error: errors || "Build failed" };
|
|
3382
|
+
}
|
|
3383
|
+
const jsFile = result.outputs.find((f) => f.path.endsWith(".js")) || result.outputs[0];
|
|
3384
|
+
let jsCode = jsFile ? await jsFile.text() : "";
|
|
3385
|
+
const jsxPath = options.jsxPath || options.vendorPath.replace("runtime.js", "jsx.js");
|
|
3386
|
+
jsCode = jsCode.replace(/from\s*["']react\/jsx(-dev)?-runtime["']/g, `from"${options.vendorPath}"`);
|
|
3387
|
+
jsCode = jsCode.replace(/from\s*["']react-dom\/client["']/g, `from"${options.vendorPath}"`);
|
|
3388
|
+
jsCode = jsCode.replace(/from\s*["']react-dom["']/g, `from"${options.vendorPath}"`);
|
|
3389
|
+
jsCode = jsCode.replace(/from\s*["']react["']/g, `from"${options.vendorPath}"`);
|
|
3390
|
+
jsCode = jsCode.replace(/from\s*["']@prev\/jsx["']/g, `from"${jsxPath}"`);
|
|
3391
|
+
jsCode = jsCode.replace(/from\s*["']@prev\/components\/[^"']*["']/g, `from"${jsxPath}"`);
|
|
3392
|
+
let css = "";
|
|
3393
|
+
if (config.tailwind) {
|
|
3394
|
+
const tailwindResult = await compileTailwind(config.files.map((f) => ({ path: f.path, content: f.content })));
|
|
3395
|
+
if (tailwindResult.success)
|
|
3396
|
+
css = tailwindResult.css;
|
|
3397
|
+
}
|
|
3398
|
+
let userCss = userCssCollected.join(`
|
|
3399
|
+
`);
|
|
3400
|
+
if (config.tailwind) {
|
|
3401
|
+
userCss = userCss.replace(/@import\s*["']tailwindcss["']\s*;?/g, "");
|
|
3402
|
+
}
|
|
3403
|
+
const allCss = css + `
|
|
3404
|
+
` + userCss;
|
|
3405
|
+
const canvasStyles = `
|
|
3406
|
+
html, body {
|
|
3407
|
+
margin: 0;
|
|
3408
|
+
min-height: 100vh;
|
|
3409
|
+
}
|
|
3410
|
+
body {
|
|
3411
|
+
display: flex;
|
|
3412
|
+
align-items: center;
|
|
3413
|
+
justify-content: center;
|
|
3414
|
+
background-color: #fafafa;
|
|
3415
|
+
background-image:
|
|
3416
|
+
radial-gradient(circle at center, #e5e5e5 1px, transparent 1px);
|
|
3417
|
+
background-size: 16px 16px;
|
|
3418
|
+
padding: 24px;
|
|
3419
|
+
box-sizing: border-box;
|
|
3420
|
+
}
|
|
3421
|
+
@media (prefers-color-scheme: dark) {
|
|
3422
|
+
body {
|
|
3423
|
+
background-color: #171717;
|
|
3424
|
+
background-image:
|
|
3425
|
+
radial-gradient(circle at center, #262626 1px, transparent 1px);
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
#root {
|
|
3429
|
+
background: white;
|
|
3430
|
+
border-radius: 12px;
|
|
3431
|
+
padding: 32px;
|
|
3432
|
+
box-shadow: 0 4px 24px -4px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04);
|
|
3433
|
+
max-width: 100%;
|
|
3434
|
+
}
|
|
3435
|
+
@media (prefers-color-scheme: dark) {
|
|
3436
|
+
#root {
|
|
3437
|
+
background: #1c1c1c;
|
|
3438
|
+
box-shadow: 0 4px 24px -4px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.06);
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
`;
|
|
3442
|
+
const html = `<!DOCTYPE html>
|
|
3443
|
+
<html lang="en">
|
|
3122
3444
|
<head>
|
|
3123
3445
|
<meta charset="UTF-8">
|
|
3124
|
-
<
|
|
3125
|
-
<
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3446
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3447
|
+
<title>Preview</title>
|
|
3448
|
+
<style>${allCss}</style>
|
|
3449
|
+
<style>${canvasStyles}</style>
|
|
3450
|
+
</head>
|
|
3451
|
+
<body>
|
|
3452
|
+
<div id="root"></div>
|
|
3453
|
+
<script type="module" src="${options.vendorPath}"></script>
|
|
3454
|
+
<script type="module" src="${jsxPath}"></script>
|
|
3455
|
+
<script type="module">${jsCode}</script>
|
|
3456
|
+
</body>
|
|
3457
|
+
</html>`;
|
|
3458
|
+
return { success: true, html, css: allCss };
|
|
3459
|
+
} finally {
|
|
3460
|
+
rmSync3(tempDir, { recursive: true, force: true });
|
|
3461
|
+
}
|
|
3462
|
+
} catch (err) {
|
|
3463
|
+
return { success: false, html: "", css: "", error: err instanceof Error ? err.message : String(err) };
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3129
3466
|
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3467
|
+
// src/server/build.ts
|
|
3468
|
+
function findCliRoot3() {
|
|
3469
|
+
let dir = path14.dirname(fileURLToPath3(import.meta.url));
|
|
3470
|
+
for (let i = 0;i < 10; i++) {
|
|
3471
|
+
const pkgPath = path14.join(dir, "package.json");
|
|
3472
|
+
if (existsSync15(pkgPath)) {
|
|
3473
|
+
try {
|
|
3474
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
3475
|
+
if (pkg.name === "prev-cli")
|
|
3476
|
+
return dir;
|
|
3477
|
+
} catch {}
|
|
3478
|
+
}
|
|
3479
|
+
const parent = path14.dirname(dir);
|
|
3480
|
+
if (parent === dir)
|
|
3481
|
+
break;
|
|
3482
|
+
dir = parent;
|
|
3483
|
+
}
|
|
3484
|
+
return path14.dirname(path14.dirname(fileURLToPath3(import.meta.url)));
|
|
3485
|
+
}
|
|
3486
|
+
var cliRoot3 = findCliRoot3();
|
|
3487
|
+
var srcRoot2 = path14.join(cliRoot3, "src");
|
|
3488
|
+
async function buildProductionSite(options) {
|
|
3489
|
+
const { rootDir, include, base = "/" } = options;
|
|
3490
|
+
const config = loadConfig(rootDir);
|
|
3491
|
+
const distDir = path14.join(rootDir, "dist");
|
|
3492
|
+
const entryPath = path14.join(srcRoot2, "theme/entry.tsx");
|
|
3493
|
+
const plugins = [
|
|
3494
|
+
virtualModulesPlugin({ rootDir, include, config }),
|
|
3495
|
+
mdxPlugin({ rootDir }),
|
|
3496
|
+
aliasesPlugin({ cliRoot: cliRoot3 })
|
|
3497
|
+
];
|
|
3498
|
+
const result = await Bun.build({
|
|
3499
|
+
entrypoints: [entryPath],
|
|
3500
|
+
outdir: distDir,
|
|
3501
|
+
format: "esm",
|
|
3502
|
+
target: "browser",
|
|
3503
|
+
minify: true,
|
|
3504
|
+
splitting: true,
|
|
3505
|
+
plugins,
|
|
3506
|
+
jsx: { runtime: "automatic", importSource: "react", development: false },
|
|
3507
|
+
naming: {
|
|
3508
|
+
entry: "assets/[name]-[hash].[ext]",
|
|
3509
|
+
chunk: "assets/[name]-[hash].[ext]",
|
|
3510
|
+
asset: "assets/[name]-[hash].[ext]"
|
|
3511
|
+
},
|
|
3512
|
+
define: {
|
|
3513
|
+
"import.meta.env.DEV": "false",
|
|
3514
|
+
"import.meta.env.BASE_URL": JSON.stringify(base),
|
|
3515
|
+
"process.env.NODE_ENV": '"production"'
|
|
3516
|
+
}
|
|
3517
|
+
});
|
|
3518
|
+
if (!result.success) {
|
|
3519
|
+
const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message);
|
|
3520
|
+
throw new Error(`Build failed:
|
|
3521
|
+
${errors.join(`
|
|
3522
|
+
`)}`);
|
|
3523
|
+
}
|
|
3524
|
+
const entryOutput = result.outputs.find((o) => o.kind === "entry-point");
|
|
3525
|
+
const cssOutputs = result.outputs.filter((o) => o.path.endsWith(".css"));
|
|
3526
|
+
const entryJsPath = entryOutput ? base + path14.relative(distDir, entryOutput.path) : "";
|
|
3527
|
+
const cssLinks = cssOutputs.map((o) => {
|
|
3528
|
+
const href = base + path14.relative(distDir, o.path);
|
|
3529
|
+
return ` <link rel="stylesheet" href="${href}" />`;
|
|
3530
|
+
}).join(`
|
|
3531
|
+
`);
|
|
3532
|
+
const html = `<!DOCTYPE html>
|
|
3533
|
+
<html lang="en">
|
|
3534
|
+
<head>
|
|
3535
|
+
<meta charset="UTF-8" />
|
|
3536
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
3537
|
+
<title>Documentation</title>
|
|
3538
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
3539
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
3540
|
+
<link rel="preload" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" as="style" />
|
|
3541
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" />
|
|
3542
|
+
${cssLinks}
|
|
3134
3543
|
</head>
|
|
3135
3544
|
<body>
|
|
3136
3545
|
<div id="root"></div>
|
|
3546
|
+
<script type="module" src="${entryJsPath}"></script>
|
|
3137
3547
|
</body>
|
|
3138
3548
|
</html>`;
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3549
|
+
writeFileSync5(path14.join(distDir, "index.html"), html);
|
|
3550
|
+
copyFileSync(path14.join(distDir, "index.html"), path14.join(distDir, "404.html"));
|
|
3551
|
+
await buildPreviewHtmlFiles(rootDir, distDir);
|
|
3552
|
+
}
|
|
3553
|
+
async function buildPreviewHtmlFiles(rootDir, distDir) {
|
|
3554
|
+
const units = await scanPreviewUnits(rootDir);
|
|
3555
|
+
if (units.length === 0)
|
|
3556
|
+
return;
|
|
3557
|
+
const targetDir = path14.join(distDir, "_preview");
|
|
3558
|
+
const vendorsDir = path14.join(targetDir, "_vendors");
|
|
3559
|
+
const previewsDir = path14.join(rootDir, "previews");
|
|
3560
|
+
let hasFlowErrors = false;
|
|
3561
|
+
for (const unit of units) {
|
|
3562
|
+
if (unit.type !== "flow" || !unit.config)
|
|
3563
|
+
continue;
|
|
3564
|
+
const result = verifyFlow(unit.config, rootDir);
|
|
3565
|
+
for (const w of result.warnings) {
|
|
3566
|
+
console.warn(` \u26A0 flows/${unit.name}: ${w}`);
|
|
3567
|
+
}
|
|
3568
|
+
for (const e of result.errors) {
|
|
3569
|
+
console.error(` \u2717 flows/${unit.name}: ${e}`);
|
|
3570
|
+
hasFlowErrors = true;
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
if (hasFlowErrors) {
|
|
3574
|
+
throw new Error("Flow verification failed \u2014 fix errors above before building");
|
|
3575
|
+
}
|
|
3576
|
+
let totalBuilds = 0;
|
|
3577
|
+
for (const unit of units) {
|
|
3578
|
+
if (unit.type === "flow")
|
|
3579
|
+
continue;
|
|
3580
|
+
totalBuilds++;
|
|
3581
|
+
if (unit.files.states)
|
|
3582
|
+
totalBuilds += unit.files.states.length;
|
|
3583
|
+
}
|
|
3584
|
+
console.log(`
|
|
3585
|
+
Building ${totalBuilds} preview(s)...`);
|
|
3586
|
+
console.log(" Building shared vendor bundle...");
|
|
3587
|
+
mkdirSync3(vendorsDir, { recursive: true });
|
|
3588
|
+
const vendorResult = await buildVendorBundle();
|
|
3589
|
+
if (!vendorResult.success) {
|
|
3590
|
+
console.error(` \u2717 Vendor bundle: ${vendorResult.error}`);
|
|
3591
|
+
return;
|
|
3592
|
+
}
|
|
3593
|
+
writeFileSync5(path14.join(vendorsDir, "runtime.js"), vendorResult.code);
|
|
3594
|
+
console.log(" \u2713 _vendors/runtime.js");
|
|
3595
|
+
const jsxResult = await buildJsxBundle("../_vendors/runtime.js");
|
|
3596
|
+
if (!jsxResult.success) {
|
|
3597
|
+
console.error(` \u2717 JSX bundle: ${jsxResult.error}`);
|
|
3598
|
+
return;
|
|
3599
|
+
}
|
|
3600
|
+
writeFileSync5(path14.join(vendorsDir, "jsx.js"), jsxResult.code);
|
|
3601
|
+
console.log(" \u2713 _vendors/jsx.js");
|
|
3602
|
+
for (const unit of units) {
|
|
3603
|
+
if (unit.type === "flow")
|
|
3604
|
+
continue;
|
|
3605
|
+
const previewDir = path14.join(previewsDir, unit.type + "s", unit.name);
|
|
3606
|
+
const previewPath = `${unit.type}s/${unit.name}`;
|
|
3607
|
+
const depth = previewPath.split("/").length;
|
|
3608
|
+
const vendorPath = "../".repeat(depth) + "_vendors/runtime.js";
|
|
3609
|
+
try {
|
|
3610
|
+
const config = await buildPreviewConfig(previewDir);
|
|
3611
|
+
const result = await buildOptimizedPreview(config, { vendorPath, resolveDir: previewDir });
|
|
3612
|
+
if (!result.success) {
|
|
3613
|
+
console.error(` \u2717 ${previewPath}: ${result.error}`);
|
|
3614
|
+
} else {
|
|
3615
|
+
const outputDir = path14.join(targetDir, previewPath);
|
|
3616
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
3617
|
+
writeFileSync5(path14.join(outputDir, "index.html"), result.html);
|
|
3618
|
+
console.log(` \u2713 ${previewPath}`);
|
|
3619
|
+
}
|
|
3620
|
+
} catch (err) {
|
|
3621
|
+
console.error(` \u2717 ${previewPath}: ${err}`);
|
|
3622
|
+
}
|
|
3623
|
+
if (unit.type === "screen" && unit.files.states) {
|
|
3624
|
+
for (const stateFile of unit.files.states) {
|
|
3625
|
+
const stateName = stateFile.replace(/\.(tsx|jsx)$/, "");
|
|
3626
|
+
const stateVendorPath = "../".repeat(depth + 1) + "_vendors/runtime.js";
|
|
3627
|
+
try {
|
|
3628
|
+
const config = await buildPreviewConfig(previewDir, stateFile);
|
|
3629
|
+
const result = await buildOptimizedPreview(config, { vendorPath: stateVendorPath, resolveDir: previewDir });
|
|
3630
|
+
if (!result.success) {
|
|
3631
|
+
console.error(` \u2717 ${previewPath}/${stateName}: ${result.error}`);
|
|
3632
|
+
} else {
|
|
3633
|
+
const outputDir = path14.join(targetDir, previewPath, stateName);
|
|
3634
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
3635
|
+
writeFileSync5(path14.join(outputDir, "index.html"), result.html);
|
|
3636
|
+
console.log(` \u2713 ${previewPath}/${stateName}`);
|
|
3637
|
+
}
|
|
3638
|
+
} catch (err) {
|
|
3639
|
+
console.error(` \u2717 ${previewPath}/${stateName}: ${err}`);
|
|
3172
3640
|
}
|
|
3173
3641
|
}
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
noDiscovery: true,
|
|
3196
|
-
holdUntilCrawlEnd: false,
|
|
3197
|
-
include: [
|
|
3198
|
-
"react-dom/client",
|
|
3199
|
-
"use-sync-external-store",
|
|
3200
|
-
"use-sync-external-store/shim/with-selector.js",
|
|
3201
|
-
"mermaid",
|
|
3202
|
-
"dayjs",
|
|
3203
|
-
"@terrastruct/d2"
|
|
3204
|
-
],
|
|
3205
|
-
exclude: [
|
|
3206
|
-
"virtual:prev-config",
|
|
3207
|
-
"virtual:prev-previews",
|
|
3208
|
-
"virtual:prev-pages",
|
|
3209
|
-
"virtual:prev-page-modules",
|
|
3210
|
-
"@prev/theme"
|
|
3211
|
-
]
|
|
3212
|
-
},
|
|
3213
|
-
ssr: {
|
|
3214
|
-
noExternal: true
|
|
3215
|
-
},
|
|
3216
|
-
server: {
|
|
3217
|
-
port,
|
|
3218
|
-
strictPort: false,
|
|
3219
|
-
fs: {
|
|
3220
|
-
allow: [rootDir, cliRoot2]
|
|
3221
|
-
},
|
|
3222
|
-
warmup: {
|
|
3223
|
-
clientFiles: [
|
|
3224
|
-
path9.join(srcRoot2, "theme/entry.tsx"),
|
|
3225
|
-
path9.join(srcRoot2, "theme/styles.css")
|
|
3226
|
-
]
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
// src/server/preview.ts
|
|
3647
|
+
import path15 from "path";
|
|
3648
|
+
import { existsSync as existsSync16, statSync as statSync3 } from "fs";
|
|
3649
|
+
async function startPreviewServer(options) {
|
|
3650
|
+
const { rootDir, port } = options;
|
|
3651
|
+
const distDir = path15.join(rootDir, "dist");
|
|
3652
|
+
if (!existsSync16(distDir)) {
|
|
3653
|
+
throw new Error(`No dist/ directory found. Run 'prev build' first.`);
|
|
3654
|
+
}
|
|
3655
|
+
const indexHtml = path15.join(distDir, "index.html");
|
|
3656
|
+
const server = Bun.serve({
|
|
3657
|
+
port,
|
|
3658
|
+
async fetch(req) {
|
|
3659
|
+
const url = new URL(req.url);
|
|
3660
|
+
let pathname = url.pathname;
|
|
3661
|
+
if (pathname !== "/" && pathname.endsWith("/")) {
|
|
3662
|
+
pathname = pathname.slice(0, -1);
|
|
3227
3663
|
}
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
reportCompressedSize: false,
|
|
3236
|
-
chunkSizeWarningLimit: 1e4,
|
|
3237
|
-
rollupOptions: {
|
|
3238
|
-
input: {
|
|
3239
|
-
main: path9.join(srcRoot2, "theme/index.html")
|
|
3240
|
-
}
|
|
3664
|
+
const filePath = path15.join(distDir, pathname);
|
|
3665
|
+
if (filePath.startsWith(distDir) && existsSync16(filePath)) {
|
|
3666
|
+
try {
|
|
3667
|
+
if (statSync3(filePath).isFile()) {
|
|
3668
|
+
return new Response(Bun.file(filePath));
|
|
3669
|
+
}
|
|
3670
|
+
} catch {}
|
|
3241
3671
|
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3672
|
+
const indexPath = path15.join(distDir, pathname, "index.html");
|
|
3673
|
+
if (existsSync16(indexPath)) {
|
|
3674
|
+
return new Response(Bun.file(indexPath));
|
|
3675
|
+
}
|
|
3676
|
+
if (!pathname.includes(".") && existsSync16(indexHtml)) {
|
|
3677
|
+
return new Response(Bun.file(indexHtml));
|
|
3678
|
+
}
|
|
3679
|
+
return new Response("Not Found", { status: 404 });
|
|
3680
|
+
}
|
|
3681
|
+
});
|
|
3682
|
+
return {
|
|
3683
|
+
server,
|
|
3684
|
+
port: server.port,
|
|
3685
|
+
url: `http://localhost:${server.port}/`,
|
|
3686
|
+
stop: () => server.stop()
|
|
3247
3687
|
};
|
|
3248
3688
|
}
|
|
3249
3689
|
|
|
@@ -3278,10 +3718,8 @@ async function findAvailablePort(minPort, maxPort) {
|
|
|
3278
3718
|
throw new Error(`No available port found between ${minPort} and ${maxPort}`);
|
|
3279
3719
|
}
|
|
3280
3720
|
|
|
3281
|
-
// src/
|
|
3721
|
+
// src/server/start.ts
|
|
3282
3722
|
import { exec } from "child_process";
|
|
3283
|
-
import { existsSync as existsSync9, rmSync as rmSync3, copyFileSync } from "fs";
|
|
3284
|
-
import path10 from "path";
|
|
3285
3723
|
function printWelcome(type) {
|
|
3286
3724
|
console.log();
|
|
3287
3725
|
console.log(" \u2728 prev");
|
|
@@ -3296,7 +3734,6 @@ function printShortcuts() {
|
|
|
3296
3734
|
console.log();
|
|
3297
3735
|
console.log(" Shortcuts:");
|
|
3298
3736
|
console.log(" o \u2192 open in browser");
|
|
3299
|
-
console.log(" c \u2192 clear cache");
|
|
3300
3737
|
console.log(" h \u2192 show this help");
|
|
3301
3738
|
console.log(" q \u2192 quit");
|
|
3302
3739
|
console.log();
|
|
@@ -3313,25 +3750,7 @@ function openBrowser(url) {
|
|
|
3313
3750
|
exec(`${cmd} ${url}`);
|
|
3314
3751
|
console.log(` \u2197 Opened ${url}`);
|
|
3315
3752
|
}
|
|
3316
|
-
function
|
|
3317
|
-
const viteCacheDir = path10.join(rootDir, ".vite");
|
|
3318
|
-
const nodeModulesVite = path10.join(rootDir, "node_modules", ".vite");
|
|
3319
|
-
let cleared = 0;
|
|
3320
|
-
if (existsSync9(viteCacheDir)) {
|
|
3321
|
-
rmSync3(viteCacheDir, { recursive: true });
|
|
3322
|
-
cleared++;
|
|
3323
|
-
}
|
|
3324
|
-
if (existsSync9(nodeModulesVite)) {
|
|
3325
|
-
rmSync3(nodeModulesVite, { recursive: true });
|
|
3326
|
-
cleared++;
|
|
3327
|
-
}
|
|
3328
|
-
if (cleared === 0) {
|
|
3329
|
-
console.log(" No cache to clear");
|
|
3330
|
-
} else {
|
|
3331
|
-
console.log(` \u2713 Cleared Vite cache`);
|
|
3332
|
-
}
|
|
3333
|
-
}
|
|
3334
|
-
function setupKeyboardShortcuts(rootDir, url, quit) {
|
|
3753
|
+
function setupKeyboardShortcuts(url, quit) {
|
|
3335
3754
|
if (!process.stdin.isTTY)
|
|
3336
3755
|
return () => {};
|
|
3337
3756
|
process.stdin.setRawMode(true);
|
|
@@ -3342,9 +3761,6 @@ function setupKeyboardShortcuts(rootDir, url, quit) {
|
|
|
3342
3761
|
case "o":
|
|
3343
3762
|
openBrowser(url);
|
|
3344
3763
|
break;
|
|
3345
|
-
case "c":
|
|
3346
|
-
clearCache(rootDir);
|
|
3347
|
-
break;
|
|
3348
3764
|
case "h":
|
|
3349
3765
|
printShortcuts();
|
|
3350
3766
|
break;
|
|
@@ -3365,27 +3781,13 @@ function setupKeyboardShortcuts(rootDir, url, quit) {
|
|
|
3365
3781
|
}
|
|
3366
3782
|
async function startDev(rootDir, options = {}) {
|
|
3367
3783
|
const port = options.port ?? await getRandomPort();
|
|
3368
|
-
const
|
|
3784
|
+
const { server, url, stop } = await startDevServer({
|
|
3369
3785
|
rootDir,
|
|
3370
|
-
mode: "development",
|
|
3371
3786
|
port,
|
|
3372
|
-
include: options.include
|
|
3373
|
-
debug: options.debug
|
|
3787
|
+
include: options.include
|
|
3374
3788
|
});
|
|
3375
|
-
const server = await createServer2(config);
|
|
3376
|
-
await server.listen();
|
|
3377
|
-
const warmupPort = server.config.server.port;
|
|
3378
|
-
fetch(`http://localhost:${warmupPort}/`).catch(() => {});
|
|
3379
|
-
const debugCollector = getDebugCollector();
|
|
3380
|
-
if (debugCollector) {
|
|
3381
|
-
debugCollector.startPhase("serverReady");
|
|
3382
|
-
const reportPath = debugCollector.writeReport();
|
|
3383
|
-
console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
|
|
3384
|
-
}
|
|
3385
|
-
const actualPort = server.config.server.port || port;
|
|
3386
|
-
const url = `http://localhost:${actualPort}/`;
|
|
3387
3789
|
printWelcome("dev");
|
|
3388
|
-
|
|
3790
|
+
console.log(` \u279C Local: ${url}`);
|
|
3389
3791
|
printReady();
|
|
3390
3792
|
let isShuttingDown = false;
|
|
3391
3793
|
let cleanupStdin = () => {};
|
|
@@ -3401,17 +3803,10 @@ async function startDev(rootDir, options = {}) {
|
|
|
3401
3803
|
Shutting down...`);
|
|
3402
3804
|
}
|
|
3403
3805
|
cleanupStdin();
|
|
3404
|
-
|
|
3405
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
3406
|
-
setTimeout(() => {
|
|
3407
|
-
console.log(" Force closing (timeout)...");
|
|
3408
|
-
resolve();
|
|
3409
|
-
}, 3000);
|
|
3410
|
-
});
|
|
3411
|
-
await Promise.race([closePromise, timeoutPromise]);
|
|
3806
|
+
stop();
|
|
3412
3807
|
process.exit(0);
|
|
3413
3808
|
};
|
|
3414
|
-
cleanupStdin = setupKeyboardShortcuts(
|
|
3809
|
+
cleanupStdin = setupKeyboardShortcuts(url, () => shutdown());
|
|
3415
3810
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
3416
3811
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
3417
3812
|
process.on("uncaughtException", (err) => {
|
|
@@ -3426,26 +3821,11 @@ async function buildSite(rootDir, options = {}) {
|
|
|
3426
3821
|
console.log(" \u2728 prev build");
|
|
3427
3822
|
console.log();
|
|
3428
3823
|
console.log(" Building your documentation site...");
|
|
3429
|
-
|
|
3824
|
+
await buildProductionSite({
|
|
3430
3825
|
rootDir,
|
|
3431
|
-
mode: "production",
|
|
3432
3826
|
include: options.include,
|
|
3433
|
-
base: options.base
|
|
3434
|
-
debug: options.debug
|
|
3827
|
+
base: options.base
|
|
3435
3828
|
});
|
|
3436
|
-
await build4(config);
|
|
3437
|
-
const debugCollector = getDebugCollector();
|
|
3438
|
-
if (debugCollector) {
|
|
3439
|
-
debugCollector.startPhase("buildComplete");
|
|
3440
|
-
const reportPath = debugCollector.writeReport();
|
|
3441
|
-
console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
|
|
3442
|
-
}
|
|
3443
|
-
const distDir = path10.join(rootDir, "dist");
|
|
3444
|
-
const indexPath = path10.join(distDir, "index.html");
|
|
3445
|
-
const notFoundPath = path10.join(distDir, "404.html");
|
|
3446
|
-
if (existsSync9(indexPath)) {
|
|
3447
|
-
copyFileSync(indexPath, notFoundPath);
|
|
3448
|
-
}
|
|
3449
3829
|
console.log();
|
|
3450
3830
|
console.log(" Done! Your site is ready in ./dist");
|
|
3451
3831
|
console.log(" You can deploy this folder anywhere.");
|
|
@@ -3453,31 +3833,18 @@ async function buildSite(rootDir, options = {}) {
|
|
|
3453
3833
|
}
|
|
3454
3834
|
async function previewSite(rootDir, options = {}) {
|
|
3455
3835
|
const port = options.port ?? await getRandomPort();
|
|
3456
|
-
const
|
|
3457
|
-
rootDir,
|
|
3458
|
-
mode: "production",
|
|
3459
|
-
port,
|
|
3460
|
-
include: options.include,
|
|
3461
|
-
debug: options.debug
|
|
3462
|
-
});
|
|
3463
|
-
const server = await preview(config);
|
|
3464
|
-
const debugCollector = getDebugCollector();
|
|
3465
|
-
if (debugCollector) {
|
|
3466
|
-
debugCollector.startPhase("previewReady");
|
|
3467
|
-
const reportPath = debugCollector.writeReport();
|
|
3468
|
-
console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
|
|
3469
|
-
}
|
|
3836
|
+
const { url, stop } = await startPreviewServer({ rootDir, port });
|
|
3470
3837
|
printWelcome("preview");
|
|
3471
|
-
|
|
3838
|
+
console.log(` \u279C Local: ${url}`);
|
|
3472
3839
|
console.log();
|
|
3473
3840
|
console.log(" Press Ctrl+C to stop.");
|
|
3474
3841
|
console.log();
|
|
3475
|
-
return
|
|
3842
|
+
return { url, stop };
|
|
3476
3843
|
}
|
|
3477
3844
|
|
|
3478
3845
|
// src/validators/index.ts
|
|
3479
|
-
import { existsSync as
|
|
3480
|
-
import { join as
|
|
3846
|
+
import { existsSync as existsSync17, readdirSync, readFileSync as readFileSync12 } from "fs";
|
|
3847
|
+
import { join as join4 } from "path";
|
|
3481
3848
|
import * as yaml3 from "js-yaml";
|
|
3482
3849
|
|
|
3483
3850
|
// src/renderers/registry.ts
|
|
@@ -3512,10 +3879,10 @@ async function initializeAdapters() {
|
|
|
3512
3879
|
// src/validators/schema-validator.ts
|
|
3513
3880
|
import Ajv from "ajv";
|
|
3514
3881
|
import addFormats from "ajv-formats";
|
|
3515
|
-
import { readFileSync as
|
|
3516
|
-
import { join as
|
|
3882
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
3883
|
+
import { join as join3, dirname as dirname3 } from "path";
|
|
3517
3884
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3518
|
-
var
|
|
3885
|
+
var __dirname2 = dirname3(fileURLToPath4(import.meta.url));
|
|
3519
3886
|
var ajv = new Ajv({
|
|
3520
3887
|
allErrors: true,
|
|
3521
3888
|
strict: false,
|
|
@@ -3527,8 +3894,8 @@ function loadSchema(name) {
|
|
|
3527
3894
|
if (schemaCache.has(name)) {
|
|
3528
3895
|
return schemaCache.get(name);
|
|
3529
3896
|
}
|
|
3530
|
-
const schemaPath =
|
|
3531
|
-
const schemaContent =
|
|
3897
|
+
const schemaPath = join3(__dirname2, "..", "schemas", `${name}.schema.json`);
|
|
3898
|
+
const schemaContent = readFileSync11(schemaPath, "utf-8");
|
|
3532
3899
|
const schema = JSON.parse(schemaContent);
|
|
3533
3900
|
const validate = ajv.compile(schema);
|
|
3534
3901
|
schemaCache.set(name, validate);
|
|
@@ -3630,7 +3997,7 @@ function validateAllLayouts(layoutByRenderer, targetRenderer) {
|
|
|
3630
3997
|
init_primitives();
|
|
3631
3998
|
function parseRef(ref) {
|
|
3632
3999
|
const refStr = typeof ref === "string" ? ref : ref.ref;
|
|
3633
|
-
const match = refStr.match(/^(screens|components|flows
|
|
4000
|
+
const match = refStr.match(/^(screens|components|flows)\/([a-z0-9-]+)$/);
|
|
3634
4001
|
if (!match)
|
|
3635
4002
|
return null;
|
|
3636
4003
|
return { type: match[1], id: match[2] };
|
|
@@ -3650,22 +4017,22 @@ function detectCycle(edges, nodeIds) {
|
|
|
3650
4017
|
}
|
|
3651
4018
|
const visited = new Set;
|
|
3652
4019
|
const recursionStack = new Set;
|
|
3653
|
-
const
|
|
4020
|
+
const path16 = [];
|
|
3654
4021
|
function dfs(node) {
|
|
3655
4022
|
visited.add(node);
|
|
3656
4023
|
recursionStack.add(node);
|
|
3657
|
-
|
|
4024
|
+
path16.push(node);
|
|
3658
4025
|
for (const neighbor of adjacency.get(node) || []) {
|
|
3659
4026
|
if (!visited.has(neighbor)) {
|
|
3660
4027
|
const cycle = dfs(neighbor);
|
|
3661
4028
|
if (cycle)
|
|
3662
4029
|
return cycle;
|
|
3663
4030
|
} else if (recursionStack.has(neighbor)) {
|
|
3664
|
-
const cycleStart =
|
|
3665
|
-
return [...
|
|
4031
|
+
const cycleStart = path16.indexOf(neighbor);
|
|
4032
|
+
return [...path16.slice(cycleStart), neighbor];
|
|
3666
4033
|
}
|
|
3667
4034
|
}
|
|
3668
|
-
|
|
4035
|
+
path16.pop();
|
|
3669
4036
|
recursionStack.delete(node);
|
|
3670
4037
|
return null;
|
|
3671
4038
|
}
|
|
@@ -3678,13 +4045,13 @@ function detectCycle(edges, nodeIds) {
|
|
|
3678
4045
|
}
|
|
3679
4046
|
return null;
|
|
3680
4047
|
}
|
|
3681
|
-
function validateRef(ref, context,
|
|
4048
|
+
function validateRef(ref, context, path16) {
|
|
3682
4049
|
const errors = [];
|
|
3683
4050
|
const warnings = [];
|
|
3684
4051
|
const parsed = parseRef(ref);
|
|
3685
4052
|
if (!parsed) {
|
|
3686
4053
|
errors.push({
|
|
3687
|
-
path:
|
|
4054
|
+
path: path16,
|
|
3688
4055
|
message: `Invalid reference format: ${JSON.stringify(ref)}`,
|
|
3689
4056
|
code: "INVALID_REF"
|
|
3690
4057
|
});
|
|
@@ -3693,7 +4060,7 @@ function validateRef(ref, context, path11) {
|
|
|
3693
4060
|
const knownSet = context.knownIds[parsed.type];
|
|
3694
4061
|
if (!knownSet?.has(parsed.id)) {
|
|
3695
4062
|
errors.push({
|
|
3696
|
-
path:
|
|
4063
|
+
path: path16,
|
|
3697
4064
|
message: `Reference "${parsed.type}/${parsed.id}" not found`,
|
|
3698
4065
|
code: "INVALID_REF"
|
|
3699
4066
|
});
|
|
@@ -3705,13 +4072,13 @@ function validateRef(ref, context, path11) {
|
|
|
3705
4072
|
const screenStates = context.screenStates.get(parsed.id);
|
|
3706
4073
|
if (!screenStates) {
|
|
3707
4074
|
errors.push({
|
|
3708
|
-
path:
|
|
4075
|
+
path: path16,
|
|
3709
4076
|
message: `State "${state}" referenced but screen "${parsed.id}" has no states defined`,
|
|
3710
4077
|
code: "INVALID_STATE_REF"
|
|
3711
4078
|
});
|
|
3712
4079
|
} else if (!screenStates.has(state)) {
|
|
3713
4080
|
errors.push({
|
|
3714
|
-
path:
|
|
4081
|
+
path: path16,
|
|
3715
4082
|
message: `State "${state}" not found in screen "${parsed.id}". Available states: ${Array.from(screenStates).join(", ") || "none"}`,
|
|
3716
4083
|
code: "INVALID_STATE_REF"
|
|
3717
4084
|
});
|
|
@@ -3762,50 +4129,6 @@ function validateFlow(config, context, configPath) {
|
|
|
3762
4129
|
}
|
|
3763
4130
|
return { errors, warnings };
|
|
3764
4131
|
}
|
|
3765
|
-
function validateAtlas(config, context, configPath) {
|
|
3766
|
-
const errors = [];
|
|
3767
|
-
const warnings = [];
|
|
3768
|
-
if (!config.nodes || config.nodes.length === 0) {
|
|
3769
|
-
return { errors, warnings };
|
|
3770
|
-
}
|
|
3771
|
-
const nodeIds = new Set(config.nodes.map((n) => n.id));
|
|
3772
|
-
for (let i = 0;i < config.nodes.length; i++) {
|
|
3773
|
-
const node = config.nodes[i];
|
|
3774
|
-
if (node.ref) {
|
|
3775
|
-
const result = validateRef(node.ref, context, `${configPath}/nodes/${i}/ref`);
|
|
3776
|
-
errors.push(...result.errors);
|
|
3777
|
-
warnings.push(...result.warnings);
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
if (config.relationships) {
|
|
3781
|
-
for (let i = 0;i < config.relationships.length; i++) {
|
|
3782
|
-
const rel = config.relationships[i];
|
|
3783
|
-
if (!nodeIds.has(rel.from)) {
|
|
3784
|
-
errors.push({
|
|
3785
|
-
path: `${configPath}/relationships/${i}/from`,
|
|
3786
|
-
message: `Relationship "from" references unknown node "${rel.from}". Available nodes: ${Array.from(nodeIds).join(", ")}`,
|
|
3787
|
-
code: "INVALID_NODE_REF"
|
|
3788
|
-
});
|
|
3789
|
-
}
|
|
3790
|
-
if (!nodeIds.has(rel.to)) {
|
|
3791
|
-
errors.push({
|
|
3792
|
-
path: `${configPath}/relationships/${i}/to`,
|
|
3793
|
-
message: `Relationship "to" references unknown node "${rel.to}". Available nodes: ${Array.from(nodeIds).join(", ")}`,
|
|
3794
|
-
code: "INVALID_NODE_REF"
|
|
3795
|
-
});
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
const cycle = detectCycle(config.relationships, nodeIds);
|
|
3799
|
-
if (cycle) {
|
|
3800
|
-
errors.push({
|
|
3801
|
-
path: `${configPath}/relationships`,
|
|
3802
|
-
message: `Circular dependency detected in atlas relationships: ${cycle.join(" \u2192 ")}`,
|
|
3803
|
-
code: "CIRCULAR_DEPENDENCY"
|
|
3804
|
-
});
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
return { errors, warnings };
|
|
3808
|
-
}
|
|
3809
4132
|
function validateScreenTemplate(template, slots, states, context, configPath) {
|
|
3810
4133
|
const errors = [];
|
|
3811
4134
|
const warnings = [];
|
|
@@ -3901,25 +4224,25 @@ function validateRendererKeys(layoutByRenderer, configPath) {
|
|
|
3901
4224
|
}
|
|
3902
4225
|
return { errors, warnings };
|
|
3903
4226
|
}
|
|
3904
|
-
function extractComponentRefs(node,
|
|
4227
|
+
function extractComponentRefs(node, path16, refs) {
|
|
3905
4228
|
if (!node || typeof node !== "object")
|
|
3906
4229
|
return;
|
|
3907
4230
|
if (Array.isArray(node)) {
|
|
3908
4231
|
node.forEach((item, index) => {
|
|
3909
|
-
extractComponentRefs(item, `${
|
|
4232
|
+
extractComponentRefs(item, `${path16}/${index}`, refs);
|
|
3910
4233
|
});
|
|
3911
4234
|
return;
|
|
3912
4235
|
}
|
|
3913
4236
|
const obj = node;
|
|
3914
4237
|
if (obj.type === "ComponentRef" && typeof obj.ref === "string") {
|
|
3915
|
-
refs.push({ ref: obj.ref, path: `${
|
|
4238
|
+
refs.push({ ref: obj.ref, path: `${path16}/ref` });
|
|
3916
4239
|
}
|
|
3917
4240
|
if (obj.children) {
|
|
3918
|
-
extractComponentRefs(obj.children, `${
|
|
4241
|
+
extractComponentRefs(obj.children, `${path16}/children`, refs);
|
|
3919
4242
|
}
|
|
3920
4243
|
if (obj.props && typeof obj.props === "object") {
|
|
3921
4244
|
for (const [key, value] of Object.entries(obj.props)) {
|
|
3922
|
-
extractComponentRefs(value, `${
|
|
4245
|
+
extractComponentRefs(value, `${path16}/props/${key}`, refs);
|
|
3923
4246
|
}
|
|
3924
4247
|
}
|
|
3925
4248
|
}
|
|
@@ -3932,11 +4255,11 @@ function validateLayoutComponentRefs(layoutByRenderer, context, configPath) {
|
|
|
3932
4255
|
for (const [rendererKey, layout] of Object.entries(layoutByRenderer)) {
|
|
3933
4256
|
const refs = [];
|
|
3934
4257
|
extractComponentRefs(layout, `${configPath}/layoutByRenderer/${rendererKey}`, refs);
|
|
3935
|
-
for (const { ref, path:
|
|
4258
|
+
for (const { ref, path: path16 } of refs) {
|
|
3936
4259
|
const match = ref.match(/^components\/([a-z0-9-]+)$/);
|
|
3937
4260
|
if (!match) {
|
|
3938
4261
|
errors.push({
|
|
3939
|
-
path:
|
|
4262
|
+
path: path16,
|
|
3940
4263
|
message: `Invalid ComponentRef format: "${ref}". Expected "components/<id>"`,
|
|
3941
4264
|
code: "INVALID_REF"
|
|
3942
4265
|
});
|
|
@@ -3945,7 +4268,7 @@ function validateLayoutComponentRefs(layoutByRenderer, context, configPath) {
|
|
|
3945
4268
|
const componentId = match[1];
|
|
3946
4269
|
if (!context.knownIds.components.has(componentId)) {
|
|
3947
4270
|
errors.push({
|
|
3948
|
-
path:
|
|
4271
|
+
path: path16,
|
|
3949
4272
|
message: `ComponentRef references unknown component "${componentId}"`,
|
|
3950
4273
|
code: "INVALID_REF"
|
|
3951
4274
|
});
|
|
@@ -3993,11 +4316,6 @@ function validateSemantics(config, context, configPath = "") {
|
|
|
3993
4316
|
errors.push(...result.errors);
|
|
3994
4317
|
warnings.push(...result.warnings);
|
|
3995
4318
|
}
|
|
3996
|
-
if (config.kind === "atlas") {
|
|
3997
|
-
const result = validateAtlas(config, context, configPath);
|
|
3998
|
-
errors.push(...result.errors);
|
|
3999
|
-
warnings.push(...result.warnings);
|
|
4000
|
-
}
|
|
4001
4319
|
return {
|
|
4002
4320
|
valid: errors.length === 0,
|
|
4003
4321
|
errors,
|
|
@@ -4010,8 +4328,7 @@ function createValidationContext(rootDir) {
|
|
|
4010
4328
|
knownIds: {
|
|
4011
4329
|
components: new Set,
|
|
4012
4330
|
screens: new Set,
|
|
4013
|
-
flows: new Set
|
|
4014
|
-
atlas: new Set
|
|
4331
|
+
flows: new Set
|
|
4015
4332
|
},
|
|
4016
4333
|
screenStates: new Map
|
|
4017
4334
|
};
|
|
@@ -4040,25 +4357,25 @@ function checkDuplicateIds(ids, type) {
|
|
|
4040
4357
|
}
|
|
4041
4358
|
|
|
4042
4359
|
// src/validators/index.ts
|
|
4043
|
-
var PREVIEW_TYPES = ["components", "screens", "flows"
|
|
4360
|
+
var PREVIEW_TYPES = ["components", "screens", "flows"];
|
|
4044
4361
|
function findPreviewRoot(startDir) {
|
|
4045
4362
|
let current = startDir;
|
|
4046
4363
|
while (current !== "/") {
|
|
4047
|
-
const previewsDir =
|
|
4048
|
-
if (
|
|
4364
|
+
const previewsDir = join4(current, ".previews");
|
|
4365
|
+
if (existsSync17(previewsDir)) {
|
|
4049
4366
|
return previewsDir;
|
|
4050
4367
|
}
|
|
4051
|
-
const previewsDirAlt =
|
|
4052
|
-
if (
|
|
4368
|
+
const previewsDirAlt = join4(current, "previews");
|
|
4369
|
+
if (existsSync17(previewsDirAlt)) {
|
|
4053
4370
|
return previewsDirAlt;
|
|
4054
4371
|
}
|
|
4055
|
-
current =
|
|
4372
|
+
current = join4(current, "..");
|
|
4056
4373
|
}
|
|
4057
4374
|
return null;
|
|
4058
4375
|
}
|
|
4059
4376
|
function scanPreviewType(previewRoot, type) {
|
|
4060
|
-
const typeDir =
|
|
4061
|
-
if (!
|
|
4377
|
+
const typeDir = join4(previewRoot, type);
|
|
4378
|
+
if (!existsSync17(typeDir)) {
|
|
4062
4379
|
return [];
|
|
4063
4380
|
}
|
|
4064
4381
|
const units = [];
|
|
@@ -4066,10 +4383,10 @@ function scanPreviewType(previewRoot, type) {
|
|
|
4066
4383
|
for (const entry of entries) {
|
|
4067
4384
|
if (!entry.isDirectory())
|
|
4068
4385
|
continue;
|
|
4069
|
-
const unitPath =
|
|
4070
|
-
const configYaml =
|
|
4071
|
-
const configYml =
|
|
4072
|
-
const configPath =
|
|
4386
|
+
const unitPath = join4(typeDir, entry.name);
|
|
4387
|
+
const configYaml = join4(unitPath, "config.yaml");
|
|
4388
|
+
const configYml = join4(unitPath, "config.yml");
|
|
4389
|
+
const configPath = existsSync17(configYaml) ? configYaml : existsSync17(configYml) ? configYml : null;
|
|
4073
4390
|
if (configPath) {
|
|
4074
4391
|
units.push({
|
|
4075
4392
|
id: entry.name,
|
|
@@ -4082,7 +4399,7 @@ function scanPreviewType(previewRoot, type) {
|
|
|
4082
4399
|
}
|
|
4083
4400
|
function loadConfig2(configPath) {
|
|
4084
4401
|
try {
|
|
4085
|
-
const content =
|
|
4402
|
+
const content = readFileSync12(configPath, "utf-8");
|
|
4086
4403
|
const parsed = yaml3.load(content);
|
|
4087
4404
|
if (parsed && typeof parsed === "object") {
|
|
4088
4405
|
return parsed;
|
|
@@ -4133,8 +4450,7 @@ async function validate(rootDir = process.cwd(), options = {}) {
|
|
|
4133
4450
|
summary: {
|
|
4134
4451
|
components: { total: 0, valid: 0, invalid: 0 },
|
|
4135
4452
|
screens: { total: 0, valid: 0, invalid: 0 },
|
|
4136
|
-
flows: { total: 0, valid: 0, invalid: 0 }
|
|
4137
|
-
atlas: { total: 0, valid: 0, invalid: 0 }
|
|
4453
|
+
flows: { total: 0, valid: 0, invalid: 0 }
|
|
4138
4454
|
},
|
|
4139
4455
|
errors: [{
|
|
4140
4456
|
file: rootDir,
|
|
@@ -4151,8 +4467,7 @@ async function validate(rootDir = process.cwd(), options = {}) {
|
|
|
4151
4467
|
summary: {
|
|
4152
4468
|
components: { total: 0, valid: 0, invalid: 0 },
|
|
4153
4469
|
screens: { total: 0, valid: 0, invalid: 0 },
|
|
4154
|
-
flows: { total: 0, valid: 0, invalid: 0 }
|
|
4155
|
-
atlas: { total: 0, valid: 0, invalid: 0 }
|
|
4470
|
+
flows: { total: 0, valid: 0, invalid: 0 }
|
|
4156
4471
|
},
|
|
4157
4472
|
errors: [],
|
|
4158
4473
|
warnings: []
|
|
@@ -4284,7 +4599,7 @@ function formatValidationResult(result) {
|
|
|
4284
4599
|
}
|
|
4285
4600
|
|
|
4286
4601
|
// src/typecheck/index.ts
|
|
4287
|
-
import
|
|
4602
|
+
import path16 from "path";
|
|
4288
4603
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
4289
4604
|
import { spawn } from "child_process";
|
|
4290
4605
|
function getTsgoPath() {
|
|
@@ -4293,9 +4608,9 @@ function getTsgoPath() {
|
|
|
4293
4608
|
const platformPkg = `@typescript/native-preview-${platform}-${arch}`;
|
|
4294
4609
|
try {
|
|
4295
4610
|
const pkgJsonUrl = import.meta.resolve(`${platformPkg}/package.json`);
|
|
4296
|
-
const pkgDir =
|
|
4611
|
+
const pkgDir = path16.dirname(fileURLToPath5(pkgJsonUrl));
|
|
4297
4612
|
const exe = platform === "win32" ? "tsgo.exe" : "tsgo";
|
|
4298
|
-
return
|
|
4613
|
+
return path16.join(pkgDir, "lib", exe);
|
|
4299
4614
|
} catch {
|
|
4300
4615
|
throw new Error(`Unable to find tsgo for ${platform}-${arch}. ` + `Package ${platformPkg} may not be installed or your platform is unsupported.`);
|
|
4301
4616
|
}
|
|
@@ -4303,15 +4618,15 @@ function getTsgoPath() {
|
|
|
4303
4618
|
function getTypeRootsPath() {
|
|
4304
4619
|
try {
|
|
4305
4620
|
const reactTypesUrl = import.meta.resolve("@types/react/package.json");
|
|
4306
|
-
const reactTypesDir =
|
|
4307
|
-
return
|
|
4621
|
+
const reactTypesDir = path16.dirname(fileURLToPath5(reactTypesUrl));
|
|
4622
|
+
return path16.dirname(reactTypesDir);
|
|
4308
4623
|
} catch {
|
|
4309
4624
|
throw new Error("Unable to find @types/react. Make sure prev-cli has @types/react in dependencies.");
|
|
4310
4625
|
}
|
|
4311
4626
|
}
|
|
4312
4627
|
async function typecheck(rootDir, options = {}) {
|
|
4313
4628
|
const {
|
|
4314
|
-
previewsDir =
|
|
4629
|
+
previewsDir = path16.join(rootDir, "previews"),
|
|
4315
4630
|
include = ["**/*.{ts,tsx}"],
|
|
4316
4631
|
strict = true,
|
|
4317
4632
|
verbose = false
|
|
@@ -4340,7 +4655,7 @@ async function typecheck(rootDir, options = {}) {
|
|
|
4340
4655
|
}
|
|
4341
4656
|
if (verbose) {
|
|
4342
4657
|
console.log(` files: ${files.length}`);
|
|
4343
|
-
files.forEach((f) => console.log(` - ${
|
|
4658
|
+
files.forEach((f) => console.log(` - ${path16.relative(rootDir, f)}`));
|
|
4344
4659
|
}
|
|
4345
4660
|
const args = [
|
|
4346
4661
|
"--noEmit",
|
|
@@ -4421,8 +4736,8 @@ ${result.output}
|
|
|
4421
4736
|
}
|
|
4422
4737
|
|
|
4423
4738
|
// src/migrate.ts
|
|
4424
|
-
import { existsSync as
|
|
4425
|
-
import { join as
|
|
4739
|
+
import { existsSync as existsSync18, readdirSync as readdirSync2, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
4740
|
+
import { join as join5 } from "path";
|
|
4426
4741
|
import * as yaml4 from "js-yaml";
|
|
4427
4742
|
var PREVIEW_TYPE_FOLDERS2 = ["components", "screens", "flows", "atlas"];
|
|
4428
4743
|
var KIND_MAP = {
|
|
@@ -4434,21 +4749,21 @@ var KIND_MAP = {
|
|
|
4434
4749
|
function findPreviewRoot2(startDir) {
|
|
4435
4750
|
let current = startDir;
|
|
4436
4751
|
while (current !== "/") {
|
|
4437
|
-
const previewsDir =
|
|
4438
|
-
if (
|
|
4752
|
+
const previewsDir = join5(current, ".previews");
|
|
4753
|
+
if (existsSync18(previewsDir)) {
|
|
4439
4754
|
return previewsDir;
|
|
4440
4755
|
}
|
|
4441
|
-
const previewsDirAlt =
|
|
4442
|
-
if (
|
|
4756
|
+
const previewsDirAlt = join5(current, "previews");
|
|
4757
|
+
if (existsSync18(previewsDirAlt)) {
|
|
4443
4758
|
return previewsDirAlt;
|
|
4444
4759
|
}
|
|
4445
|
-
current =
|
|
4760
|
+
current = join5(current, "..");
|
|
4446
4761
|
}
|
|
4447
4762
|
return null;
|
|
4448
4763
|
}
|
|
4449
4764
|
function migrateConfig(configPath, folderId, kind) {
|
|
4450
4765
|
try {
|
|
4451
|
-
const content =
|
|
4766
|
+
const content = readFileSync13(configPath, "utf-8");
|
|
4452
4767
|
const config = yaml4.load(content);
|
|
4453
4768
|
if (!config || typeof config !== "object") {
|
|
4454
4769
|
return { migrated: false, error: "Invalid YAML" };
|
|
@@ -4508,18 +4823,18 @@ async function migrateConfigs(rootDir = process.cwd()) {
|
|
|
4508
4823
|
errors: []
|
|
4509
4824
|
};
|
|
4510
4825
|
for (const type of PREVIEW_TYPE_FOLDERS2) {
|
|
4511
|
-
const typeDir =
|
|
4512
|
-
if (!
|
|
4826
|
+
const typeDir = join5(previewRoot, type);
|
|
4827
|
+
if (!existsSync18(typeDir))
|
|
4513
4828
|
continue;
|
|
4514
4829
|
const kind = KIND_MAP[type];
|
|
4515
4830
|
const entries = readdirSync2(typeDir, { withFileTypes: true });
|
|
4516
4831
|
for (const entry of entries) {
|
|
4517
4832
|
if (!entry.isDirectory())
|
|
4518
4833
|
continue;
|
|
4519
|
-
const unitPath =
|
|
4520
|
-
const configYaml =
|
|
4521
|
-
const configYml =
|
|
4522
|
-
const configPath =
|
|
4834
|
+
const unitPath = join5(typeDir, entry.name);
|
|
4835
|
+
const configYaml = join5(unitPath, "config.yaml");
|
|
4836
|
+
const configYml = join5(unitPath, "config.yml");
|
|
4837
|
+
const configPath = existsSync18(configYaml) ? configYaml : existsSync18(configYml) ? configYml : null;
|
|
4523
4838
|
if (!configPath) {
|
|
4524
4839
|
result.skipped++;
|
|
4525
4840
|
continue;
|
|
@@ -4567,14 +4882,14 @@ import { createHash } from "crypto";
|
|
|
4567
4882
|
import { readdir, rm, stat, mkdir } from "fs/promises";
|
|
4568
4883
|
import { exec as exec2 } from "child_process";
|
|
4569
4884
|
import { promisify } from "util";
|
|
4570
|
-
import
|
|
4571
|
-
import
|
|
4885
|
+
import path17 from "path";
|
|
4886
|
+
import os from "os";
|
|
4572
4887
|
var execAsync = promisify(exec2);
|
|
4573
|
-
var DEFAULT_CACHE_ROOT =
|
|
4888
|
+
var DEFAULT_CACHE_ROOT = path17.join(os.homedir(), ".cache/prev");
|
|
4574
4889
|
async function getCacheDir(rootDir, branch) {
|
|
4575
4890
|
const resolvedBranch = branch ?? await getCurrentBranch(rootDir);
|
|
4576
4891
|
const hash = createHash("sha1").update(`${rootDir}:${resolvedBranch}`).digest("hex").slice(0, 12);
|
|
4577
|
-
return
|
|
4892
|
+
return path17.join(DEFAULT_CACHE_ROOT, hash);
|
|
4578
4893
|
}
|
|
4579
4894
|
async function getCurrentBranch(rootDir) {
|
|
4580
4895
|
try {
|
|
@@ -4596,7 +4911,7 @@ async function cleanCache(options) {
|
|
|
4596
4911
|
}
|
|
4597
4912
|
let removed = 0;
|
|
4598
4913
|
for (const dir of dirs) {
|
|
4599
|
-
const fullPath =
|
|
4914
|
+
const fullPath = path17.join(cacheRoot, dir);
|
|
4600
4915
|
try {
|
|
4601
4916
|
const info = await stat(fullPath);
|
|
4602
4917
|
if (info.isDirectory() && now - info.mtimeMs > maxAge) {
|
|
@@ -4612,15 +4927,15 @@ async function cleanCache(options) {
|
|
|
4612
4927
|
import yaml5 from "js-yaml";
|
|
4613
4928
|
function getVersion() {
|
|
4614
4929
|
try {
|
|
4615
|
-
let dir =
|
|
4930
|
+
let dir = path18.dirname(fileURLToPath6(import.meta.url));
|
|
4616
4931
|
for (let i = 0;i < 5; i++) {
|
|
4617
|
-
const pkgPath =
|
|
4618
|
-
if (
|
|
4619
|
-
const pkg = JSON.parse(
|
|
4932
|
+
const pkgPath = path18.join(dir, "package.json");
|
|
4933
|
+
if (existsSync19(pkgPath)) {
|
|
4934
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
|
|
4620
4935
|
if (pkg.name === "prev-cli")
|
|
4621
4936
|
return pkg.version;
|
|
4622
4937
|
}
|
|
4623
|
-
dir =
|
|
4938
|
+
dir = path18.dirname(dir);
|
|
4624
4939
|
}
|
|
4625
4940
|
} catch {}
|
|
4626
4941
|
return "unknown";
|
|
@@ -4640,7 +4955,7 @@ var { values, positionals } = parseArgs({
|
|
|
4640
4955
|
allowPositionals: true
|
|
4641
4956
|
});
|
|
4642
4957
|
var command = positionals[0] || "dev";
|
|
4643
|
-
var rootDir =
|
|
4958
|
+
var rootDir = path18.resolve(values.cwd || (command === "config" || command === "create" ? "." : positionals[1]) || ".");
|
|
4644
4959
|
function printHelp() {
|
|
4645
4960
|
console.log(`
|
|
4646
4961
|
prev - Zero-config documentation site generator
|
|
@@ -4654,7 +4969,7 @@ Usage:
|
|
|
4654
4969
|
prev migrate Migrate configs from v1 to v2 format
|
|
4655
4970
|
prev create [name] Create preview in previews/<name>/ (default: "example")
|
|
4656
4971
|
prev config [subcommand] Manage configuration
|
|
4657
|
-
prev clearcache Clear
|
|
4972
|
+
prev clearcache Clear build cache
|
|
4658
4973
|
prev clean [options] Remove old prev-cli caches
|
|
4659
4974
|
|
|
4660
4975
|
Config subcommands:
|
|
@@ -4758,30 +5073,18 @@ Examples:
|
|
|
4758
5073
|
prev clean -d 7 Remove caches older than 7 days
|
|
4759
5074
|
`);
|
|
4760
5075
|
}
|
|
4761
|
-
async function
|
|
5076
|
+
async function clearBuildCache(rootDir2) {
|
|
4762
5077
|
let cleared = 0;
|
|
4763
5078
|
try {
|
|
4764
5079
|
const prevCacheDir = await getCacheDir(rootDir2);
|
|
4765
|
-
if (
|
|
5080
|
+
if (existsSync19(prevCacheDir)) {
|
|
4766
5081
|
rmSync4(prevCacheDir, { recursive: true });
|
|
4767
5082
|
cleared++;
|
|
4768
5083
|
console.log(` \u2713 Removed ${prevCacheDir}`);
|
|
4769
5084
|
}
|
|
4770
5085
|
} catch {}
|
|
4771
|
-
const viteCacheDir = path13.join(rootDir2, ".vite");
|
|
4772
|
-
const nodeModulesVite = path13.join(rootDir2, "node_modules", ".vite");
|
|
4773
|
-
if (existsSync12(viteCacheDir)) {
|
|
4774
|
-
rmSync4(viteCacheDir, { recursive: true });
|
|
4775
|
-
cleared++;
|
|
4776
|
-
console.log(` \u2713 Removed .vite/`);
|
|
4777
|
-
}
|
|
4778
|
-
if (existsSync12(nodeModulesVite)) {
|
|
4779
|
-
rmSync4(nodeModulesVite, { recursive: true });
|
|
4780
|
-
cleared++;
|
|
4781
|
-
console.log(` \u2713 Removed node_modules/.vite/`);
|
|
4782
|
-
}
|
|
4783
5086
|
if (cleared === 0) {
|
|
4784
|
-
console.log(" No
|
|
5087
|
+
console.log(" No build cache found");
|
|
4785
5088
|
} else {
|
|
4786
5089
|
console.log(`
|
|
4787
5090
|
Cleared ${cleared} cache director${cleared === 1 ? "y" : "ies"}`);
|
|
@@ -4821,7 +5124,7 @@ function handleConfig(rootDir2, subcommand) {
|
|
|
4821
5124
|
break;
|
|
4822
5125
|
}
|
|
4823
5126
|
case "init": {
|
|
4824
|
-
const targetPath =
|
|
5127
|
+
const targetPath = path18.join(rootDir2, ".prev.yaml");
|
|
4825
5128
|
if (configPath) {
|
|
4826
5129
|
console.log(`
|
|
4827
5130
|
Config already exists: ${configPath}
|
|
@@ -4880,8 +5183,8 @@ Available subcommands: show, init, path`);
|
|
|
4880
5183
|
}
|
|
4881
5184
|
}
|
|
4882
5185
|
function createPreview(rootDir2, name) {
|
|
4883
|
-
const previewDir =
|
|
4884
|
-
if (
|
|
5186
|
+
const previewDir = path18.join(rootDir2, "previews", name);
|
|
5187
|
+
if (existsSync19(previewDir)) {
|
|
4885
5188
|
console.error(`Preview "${name}" already exists at: ${previewDir}`);
|
|
4886
5189
|
process.exit(1);
|
|
4887
5190
|
}
|
|
@@ -5006,8 +5309,8 @@ export default function App() {
|
|
|
5006
5309
|
.dark\\:text-white { color: #fff; }
|
|
5007
5310
|
}
|
|
5008
5311
|
`;
|
|
5009
|
-
writeFileSync7(
|
|
5010
|
-
writeFileSync7(
|
|
5312
|
+
writeFileSync7(path18.join(previewDir, "App.tsx"), appTsx);
|
|
5313
|
+
writeFileSync7(path18.join(previewDir, "styles.css"), stylesCss);
|
|
5011
5314
|
console.log(`
|
|
5012
5315
|
\u2728 Created preview: previews/${name}/
|
|
5013
5316
|
|
|
@@ -5056,7 +5359,7 @@ async function main() {
|
|
|
5056
5359
|
createPreview(rootDir, previewName);
|
|
5057
5360
|
break;
|
|
5058
5361
|
case "clearcache":
|
|
5059
|
-
await
|
|
5362
|
+
await clearBuildCache(rootDir);
|
|
5060
5363
|
break;
|
|
5061
5364
|
case "config":
|
|
5062
5365
|
handleConfig(rootDir, positionals[1]);
|