prev-cli 0.24.20 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2006 -1714
- 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,962 +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
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
const previewBundleHandler = createPreviewBundleHandler(rootDir);
|
|
2819
|
+
const previewConfigHandler = createPreviewConfigHandler(rootDir);
|
|
2820
|
+
const jsxBundleHandler = createJsxBundleHandler(cliRoot);
|
|
2821
|
+
const componentBundleHandler = createComponentBundleHandler(rootDir);
|
|
2822
|
+
const tokensHandler = createTokensHandler(rootDir);
|
|
2823
|
+
const previewRuntimePath = path12.join(srcRoot, "preview-runtime/fast-template.html");
|
|
2824
|
+
const server = Bun.serve({
|
|
2825
|
+
port,
|
|
2826
|
+
async fetch(req) {
|
|
2827
|
+
const url = new URL(req.url);
|
|
2828
|
+
const pathname = url.pathname;
|
|
2829
|
+
if (pathname === "/__prev/events") {
|
|
2830
|
+
let ctrl;
|
|
2831
|
+
const stream = new ReadableStream({
|
|
2832
|
+
start(controller) {
|
|
2833
|
+
ctrl = controller;
|
|
2834
|
+
sseControllers.add(controller);
|
|
2835
|
+
},
|
|
2836
|
+
cancel() {
|
|
2837
|
+
sseControllers.delete(ctrl);
|
|
2838
|
+
}
|
|
2839
|
+
});
|
|
2840
|
+
return new Response(stream, {
|
|
2841
|
+
headers: {
|
|
2842
|
+
"Content-Type": "text/event-stream",
|
|
2843
|
+
"Cache-Control": "no-cache"
|
|
2844
|
+
}
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2847
|
+
if (pathname === "/__prev/app.js") {
|
|
2848
|
+
return new Response(appBundle.js, {
|
|
2849
|
+
headers: { "Content-Type": "application/javascript" }
|
|
2850
|
+
});
|
|
2851
|
+
}
|
|
2852
|
+
if (pathname === "/__prev/app.css") {
|
|
2853
|
+
return new Response(appBundle.css, {
|
|
2854
|
+
headers: { "Content-Type": "text/css" }
|
|
2855
|
+
});
|
|
2856
|
+
}
|
|
2857
|
+
if (pathname === "/__prev/config" && req.method === "POST") {
|
|
2858
|
+
try {
|
|
2859
|
+
const body = await req.json();
|
|
2860
|
+
updateOrder(rootDir, body.path, body.order);
|
|
2861
|
+
return Response.json({ success: true });
|
|
2862
|
+
} catch (e) {
|
|
2863
|
+
return Response.json({ error: String(e) }, { status: 400 });
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
const bundleResponse = await previewBundleHandler(req);
|
|
2867
|
+
if (bundleResponse)
|
|
2868
|
+
return bundleResponse;
|
|
2869
|
+
const configResponse = await previewConfigHandler(req);
|
|
2870
|
+
if (configResponse)
|
|
2871
|
+
return configResponse;
|
|
2872
|
+
const jsxResponse = await jsxBundleHandler(req);
|
|
2873
|
+
if (jsxResponse)
|
|
2874
|
+
return jsxResponse;
|
|
2875
|
+
const componentResponse = await componentBundleHandler(req);
|
|
2876
|
+
if (componentResponse)
|
|
2877
|
+
return componentResponse;
|
|
2878
|
+
const tokensResponse = await tokensHandler(req);
|
|
2879
|
+
if (tokensResponse)
|
|
2880
|
+
return tokensResponse;
|
|
2881
|
+
const ogResponse = handleOgImageRequest(req, []);
|
|
2882
|
+
if (ogResponse)
|
|
2883
|
+
return ogResponse;
|
|
2884
|
+
if (pathname === "/_prev/region-bridge.js") {
|
|
2885
|
+
const { REGION_BRIDGE_SCRIPT: REGION_BRIDGE_SCRIPT2 } = await Promise.resolve().then(() => exports_region_bridge);
|
|
2886
|
+
return new Response(REGION_BRIDGE_SCRIPT2, {
|
|
2887
|
+
headers: { "Content-Type": "application/javascript" }
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
if (pathname === "/_preview-runtime") {
|
|
2891
|
+
if (existsSync11(previewRuntimePath)) {
|
|
2892
|
+
const html = readFileSync6(previewRuntimePath, "utf-8");
|
|
2893
|
+
return new Response(html, {
|
|
2894
|
+
headers: { "Content-Type": "text/html" }
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
if (pathname.startsWith("/_preview/")) {
|
|
2899
|
+
const relativePath = pathname.slice("/_preview/".length);
|
|
2900
|
+
const previewsDir2 = path12.join(rootDir, "previews");
|
|
2901
|
+
const filePath = path12.resolve(previewsDir2, relativePath);
|
|
2902
|
+
if (filePath.startsWith(previewsDir2) && existsSync11(filePath) && statSync(filePath).isFile()) {
|
|
2903
|
+
return new Response(Bun.file(filePath));
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
if (!pathname.includes(".") && !pathname.startsWith("/@") && !pathname.startsWith("/__") && !pathname.startsWith("/_preview") && !pathname.startsWith("/_prev")) {
|
|
2907
|
+
if (pathname.startsWith("/previews/") && pathname !== "/previews") {
|
|
2908
|
+
const previewPath = pathname.slice("/previews/".length);
|
|
2909
|
+
const searchParams = new URL(req.url).searchParams;
|
|
2910
|
+
const ogState = searchParams.get("state");
|
|
2911
|
+
const ogStep = searchParams.get("step");
|
|
2912
|
+
const ogTitle = previewPath.split("/").pop() || "Preview";
|
|
2913
|
+
const ogParams = [
|
|
2914
|
+
ogState ? `state=${ogState}` : "",
|
|
2915
|
+
ogStep ? `step=${ogStep}` : ""
|
|
2916
|
+
].filter(Boolean).join("&");
|
|
2917
|
+
const ogImageUrl = `/_og/${previewPath}${ogParams ? `?${ogParams}` : ""}`;
|
|
2918
|
+
const ogHtml = HTML_SHELL.replace("<title>Documentation</title>", `<title>${ogTitle} - Preview</title>
|
|
2919
|
+
<meta property="og:title" content="${ogTitle}" />
|
|
2920
|
+
<meta property="og:description" content="${ogState ? `State: ${ogState}` : ogStep ? `Step: ${ogStep}` : "Preview"}" />
|
|
2921
|
+
<meta property="og:image" content="${ogImageUrl}" />
|
|
2922
|
+
<meta property="og:type" content="website" />
|
|
2923
|
+
<meta name="twitter:card" content="summary_large_image" />`);
|
|
2924
|
+
return new Response(ogHtml, {
|
|
2925
|
+
headers: { "Content-Type": "text/html" }
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
return new Response(HTML_SHELL, {
|
|
2929
|
+
headers: { "Content-Type": "text/html" }
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
return new Response("Not Found", { status: 404 });
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
const { watch } = await import("fs");
|
|
2936
|
+
const watchers = [];
|
|
2937
|
+
let rebuildTimer = null;
|
|
2938
|
+
async function rebuild() {
|
|
2939
|
+
appBundle = await buildThemeApp(rootDir, include, config);
|
|
2940
|
+
if (appBundle.success) {
|
|
2941
|
+
notifyReload();
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
function scheduleRebuild() {
|
|
2945
|
+
if (rebuildTimer)
|
|
2946
|
+
clearTimeout(rebuildTimer);
|
|
2947
|
+
rebuildTimer = setTimeout(rebuild, 150);
|
|
2948
|
+
}
|
|
2949
|
+
const previewsDir = path12.join(rootDir, "previews");
|
|
2950
|
+
if (existsSync11(previewsDir)) {
|
|
2951
|
+
watchers.push(watch(previewsDir, { recursive: true }, (_, filename) => {
|
|
2952
|
+
if (filename && /\.(tsx|ts|jsx|js|css|yaml|yml|mdx|md|html)$/.test(filename)) {
|
|
2953
|
+
scheduleRebuild();
|
|
2954
|
+
}
|
|
2955
|
+
}));
|
|
2692
2956
|
}
|
|
2693
|
-
|
|
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
|
-
|
|
2754
|
-
|
|
3048
|
+
}
|
|
3049
|
+
async function buildJsxBundle(vendorPath) {
|
|
3050
|
+
try {
|
|
3051
|
+
const minimalJsx = `
|
|
3052
|
+
import * as React from 'react'
|
|
3053
|
+
|
|
3054
|
+
// Default token values for standalone preview rendering
|
|
3055
|
+
const defaultTokens = {
|
|
3056
|
+
background: {
|
|
3057
|
+
primary: '#3b82f6',
|
|
3058
|
+
secondary: '#f1f5f9',
|
|
3059
|
+
destructive: '#ef4444',
|
|
3060
|
+
muted: '#f1f5f9',
|
|
3061
|
+
accent: '#f1f5f9',
|
|
3062
|
+
transparent: 'transparent',
|
|
3063
|
+
},
|
|
3064
|
+
color: {
|
|
3065
|
+
'primary-foreground': '#ffffff',
|
|
3066
|
+
'secondary-foreground': '#0f172a',
|
|
3067
|
+
'destructive-foreground': '#ffffff',
|
|
3068
|
+
'muted-foreground': '#64748b',
|
|
3069
|
+
'accent-foreground': '#0f172a',
|
|
3070
|
+
foreground: '#0f172a',
|
|
3071
|
+
},
|
|
3072
|
+
spacing: {
|
|
3073
|
+
xs: '4px',
|
|
3074
|
+
sm: '8px',
|
|
3075
|
+
md: '12px',
|
|
3076
|
+
lg: '16px',
|
|
3077
|
+
xl: '24px',
|
|
3078
|
+
},
|
|
3079
|
+
radius: {
|
|
3080
|
+
none: '0',
|
|
3081
|
+
sm: '4px',
|
|
3082
|
+
md: '6px',
|
|
3083
|
+
lg: '8px',
|
|
3084
|
+
full: '9999px',
|
|
3085
|
+
},
|
|
3086
|
+
'typography.size': {
|
|
3087
|
+
xs: '12px',
|
|
3088
|
+
sm: '14px',
|
|
3089
|
+
base: '16px',
|
|
3090
|
+
lg: '18px',
|
|
3091
|
+
xl: '20px',
|
|
3092
|
+
},
|
|
3093
|
+
'typography.weight': {
|
|
3094
|
+
normal: '400',
|
|
3095
|
+
medium: '500',
|
|
3096
|
+
semibold: '600',
|
|
3097
|
+
bold: '700',
|
|
3098
|
+
},
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
// Token resolution
|
|
3102
|
+
let tokensConfig = null
|
|
3103
|
+
export function setTokensConfig(config) { tokensConfig = config }
|
|
3104
|
+
|
|
3105
|
+
function resolveToken(category, token) {
|
|
3106
|
+
// Check custom config first, then defaults
|
|
3107
|
+
const config = tokensConfig || defaultTokens
|
|
3108
|
+
const cat = config[category]
|
|
3109
|
+
return cat?.[token] ?? token
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
// VNode type
|
|
3113
|
+
export class VNode {
|
|
3114
|
+
constructor(type, props, children) {
|
|
3115
|
+
this.type = type
|
|
3116
|
+
this.props = props || {}
|
|
3117
|
+
this.children = children || []
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
// Primitives - return VNodes
|
|
3122
|
+
export function Box(props) { return new VNode('Box', props, props.children ? [props.children] : []) }
|
|
3123
|
+
export function Text(props) { return new VNode('Text', props, props.children ? [props.children] : []) }
|
|
3124
|
+
export function Col(props) { return new VNode('Col', props, props.children || []) }
|
|
3125
|
+
export function Row(props) { return new VNode('Row', props, props.children || []) }
|
|
3126
|
+
export function Spacer(props) { return new VNode('Spacer', props, []) }
|
|
3127
|
+
export function Slot(props) { return new VNode('Slot', props, []) }
|
|
3128
|
+
export function Icon(props) { return new VNode('Icon', props, []) }
|
|
3129
|
+
export function Image(props) { return new VNode('Image', props, []) }
|
|
3130
|
+
export const Fragment = React.Fragment
|
|
3131
|
+
|
|
3132
|
+
// Convert VNode to React element
|
|
3133
|
+
export function toReact(vnode) {
|
|
3134
|
+
if (!vnode || typeof vnode !== 'object') return vnode
|
|
3135
|
+
if (!(vnode instanceof VNode)) return vnode
|
|
3136
|
+
|
|
3137
|
+
const { type, props, children } = vnode
|
|
3138
|
+
const style = {}
|
|
3139
|
+
|
|
3140
|
+
// Map props to styles
|
|
3141
|
+
if (props.bg) style.backgroundColor = resolveToken('background', props.bg)
|
|
3142
|
+
if (props.padding) style.padding = resolveToken('spacing', props.padding)
|
|
3143
|
+
if (props.radius) style.borderRadius = resolveToken('radius', props.radius)
|
|
3144
|
+
if (props.color) style.color = resolveToken('color', props.color)
|
|
3145
|
+
if (props.size) style.fontSize = resolveToken('typography.size', props.size)
|
|
3146
|
+
if (props.weight) style.fontWeight = resolveToken('typography.weight', props.weight)
|
|
3147
|
+
if (props.gap) style.gap = resolveToken('spacing', props.gap)
|
|
3148
|
+
|
|
3149
|
+
// Layout types
|
|
3150
|
+
if (type === 'Col') { style.display = 'flex'; style.flexDirection = 'column' }
|
|
3151
|
+
if (type === 'Row') { style.display = 'flex'; style.flexDirection = 'row' }
|
|
3152
|
+
if (type === 'Spacer') { style.flex = 1 }
|
|
3153
|
+
|
|
3154
|
+
const childElements = children.map(c => toReact(c))
|
|
3155
|
+
|
|
3156
|
+
return React.createElement('div', { style }, ...childElements)
|
|
3157
|
+
}
|
|
3158
|
+
`;
|
|
3159
|
+
const tempDir = mkdtempSync(join(tmpdir(), "prev-jsx-"));
|
|
3160
|
+
const entryPath = join(tempDir, "entry.ts");
|
|
3161
|
+
try {
|
|
3162
|
+
writeFileSync2(entryPath, minimalJsx);
|
|
3163
|
+
const result = await Bun.build({
|
|
3164
|
+
entrypoints: [entryPath],
|
|
3165
|
+
format: "esm",
|
|
3166
|
+
target: "browser",
|
|
3167
|
+
minify: true,
|
|
3168
|
+
plugins: [
|
|
3169
|
+
{
|
|
3170
|
+
name: "jsx-externals",
|
|
3171
|
+
setup(build) {
|
|
3172
|
+
build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
|
|
3173
|
+
return { path: vendorPath, external: true };
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
2755
3176
|
}
|
|
3177
|
+
]
|
|
3178
|
+
});
|
|
3179
|
+
if (!result.success) {
|
|
3180
|
+
const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message).join("; ");
|
|
3181
|
+
return { success: false, code: "", error: errors || "Build failed" };
|
|
3182
|
+
}
|
|
3183
|
+
const jsFile = result.outputs.find((f) => f.path.endsWith(".js")) || result.outputs[0];
|
|
3184
|
+
if (!jsFile) {
|
|
3185
|
+
return { success: false, code: "", error: "No output generated" };
|
|
3186
|
+
}
|
|
3187
|
+
let code = await jsFile.text();
|
|
3188
|
+
code = code.replace(/from\s*["']react["']/g, `from"${vendorPath}"`);
|
|
3189
|
+
return { success: true, code };
|
|
3190
|
+
} finally {
|
|
3191
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
3192
|
+
}
|
|
3193
|
+
} catch (err) {
|
|
3194
|
+
return {
|
|
3195
|
+
success: false,
|
|
3196
|
+
code: "",
|
|
3197
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3198
|
+
};
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
// src/preview-runtime/tailwind.ts
|
|
3203
|
+
var {$ } = globalThis.Bun;
|
|
3204
|
+
import { mkdtempSync as mkdtempSync2, mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync8, rmSync as rmSync2, existsSync as existsSync13 } from "fs";
|
|
3205
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
3206
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3207
|
+
function findTailwindBin() {
|
|
3208
|
+
try {
|
|
3209
|
+
const tailwindPkg = __require.resolve("tailwindcss/package.json");
|
|
3210
|
+
const tailwindDir = dirname2(tailwindPkg);
|
|
3211
|
+
const binPath = join2(tailwindDir, "lib/cli.js");
|
|
3212
|
+
if (existsSync13(binPath))
|
|
3213
|
+
return binPath;
|
|
3214
|
+
} catch {}
|
|
3215
|
+
return "bunx tailwindcss@3";
|
|
3216
|
+
}
|
|
3217
|
+
var tailwindCmd = findTailwindBin();
|
|
3218
|
+
async function compileTailwind(files) {
|
|
3219
|
+
const tempDir = mkdtempSync2(join2(tmpdir2(), "prev-tailwind-"));
|
|
3220
|
+
try {
|
|
3221
|
+
for (const file of files) {
|
|
3222
|
+
const filePath = join2(tempDir, file.path);
|
|
3223
|
+
const parentDir = dirname2(filePath);
|
|
3224
|
+
mkdirSync(parentDir, { recursive: true });
|
|
3225
|
+
writeFileSync3(filePath, file.content);
|
|
3226
|
+
}
|
|
3227
|
+
const configContent = `
|
|
3228
|
+
module.exports = {
|
|
3229
|
+
content: [${JSON.stringify(tempDir + "/**/*.{tsx,jsx,ts,js,html}")}],
|
|
3230
|
+
}
|
|
3231
|
+
`;
|
|
3232
|
+
const configPath = join2(tempDir, "tailwind.config.cjs");
|
|
3233
|
+
writeFileSync3(configPath, configContent);
|
|
3234
|
+
const inputCss = `
|
|
3235
|
+
@tailwind base;
|
|
3236
|
+
@tailwind components;
|
|
3237
|
+
@tailwind utilities;
|
|
3238
|
+
`;
|
|
3239
|
+
const inputPath = join2(tempDir, "input.css");
|
|
3240
|
+
writeFileSync3(inputPath, inputCss);
|
|
3241
|
+
const outputPath = join2(tempDir, "output.css");
|
|
3242
|
+
if (tailwindCmd.startsWith("bunx")) {
|
|
3243
|
+
await $`bunx tailwindcss@3 -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet();
|
|
3244
|
+
} else {
|
|
3245
|
+
await $`bun ${tailwindCmd} -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet();
|
|
3246
|
+
}
|
|
3247
|
+
const css = readFileSync8(outputPath, "utf-8");
|
|
3248
|
+
return { success: true, css };
|
|
3249
|
+
} catch (err) {
|
|
3250
|
+
return {
|
|
3251
|
+
success: false,
|
|
3252
|
+
css: "",
|
|
3253
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3254
|
+
};
|
|
3255
|
+
} finally {
|
|
3256
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
// src/preview-runtime/build-optimized.ts
|
|
3261
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9, mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync4, rmSync as rmSync3, mkdirSync as mkdirSync2, statSync as statSync2 } from "fs";
|
|
3262
|
+
import path13 from "path";
|
|
3263
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
3264
|
+
function existsAsFile(p) {
|
|
3265
|
+
try {
|
|
3266
|
+
return statSync2(p).isFile();
|
|
3267
|
+
} catch {
|
|
3268
|
+
return false;
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
async function buildOptimizedPreview(config, options) {
|
|
3272
|
+
try {
|
|
3273
|
+
const virtualFs = {};
|
|
3274
|
+
const resolveDir = options.resolveDir || "/";
|
|
3275
|
+
for (const file of config.files) {
|
|
3276
|
+
const ext = file.path.split(".").pop()?.toLowerCase();
|
|
3277
|
+
const loader = ext === "css" ? "css" : ext === "json" ? "json" : ext || "tsx";
|
|
3278
|
+
virtualFs[file.path] = { contents: file.content, loader };
|
|
3279
|
+
}
|
|
3280
|
+
const entryFile = config.files.find((f) => f.path === config.entry);
|
|
3281
|
+
if (!entryFile) {
|
|
3282
|
+
return { success: false, html: "", css: "", error: `Entry file not found: ${config.entry}` };
|
|
3283
|
+
}
|
|
3284
|
+
const hasDefaultExport = /export\s+default/.test(entryFile.content);
|
|
3285
|
+
const userCssCollected = [];
|
|
3286
|
+
const entryCode = hasDefaultExport ? `
|
|
3287
|
+
import React, { createRoot } from '${options.vendorPath}'
|
|
3288
|
+
import App from './${config.entry}'
|
|
3289
|
+
const root = createRoot(document.getElementById('root'))
|
|
3290
|
+
root.render(React.createElement(App))
|
|
3291
|
+
` : `import './${config.entry}'`;
|
|
3292
|
+
const tempDir = mkdtempSync3(path13.join(tmpdir3(), "prev-optimized-"));
|
|
3293
|
+
const entryPath = path13.join(tempDir, "__entry.tsx");
|
|
3294
|
+
try {
|
|
3295
|
+
writeFileSync4(entryPath, entryCode);
|
|
3296
|
+
for (const [filePath, file] of Object.entries(virtualFs)) {
|
|
3297
|
+
const targetPath = path13.join(tempDir, filePath);
|
|
3298
|
+
const dir = path13.dirname(targetPath);
|
|
3299
|
+
if (!existsSync14(dir)) {
|
|
3300
|
+
mkdirSync2(dir, { recursive: true });
|
|
2756
3301
|
}
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
3302
|
+
writeFileSync4(targetPath, file.contents);
|
|
3303
|
+
}
|
|
3304
|
+
const result = await Bun.build({
|
|
3305
|
+
entrypoints: [entryPath],
|
|
3306
|
+
format: "esm",
|
|
3307
|
+
target: "browser",
|
|
3308
|
+
minify: true,
|
|
3309
|
+
jsx: { runtime: "automatic", importSource: "react" },
|
|
3310
|
+
plugins: [
|
|
3311
|
+
{
|
|
3312
|
+
name: "optimized-preview",
|
|
3313
|
+
setup(build) {
|
|
3314
|
+
build.onResolve({ filter: new RegExp(options.vendorPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) }, (args) => {
|
|
3315
|
+
return { path: args.path, external: true };
|
|
2766
3316
|
});
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
const { path: pathKey, order } = JSON.parse(body);
|
|
2770
|
-
updateOrder(rootDir, pathKey, order);
|
|
2771
|
-
res.statusCode = 200;
|
|
2772
|
-
res.end(JSON.stringify({ success: true }));
|
|
2773
|
-
} catch (e) {
|
|
2774
|
-
res.statusCode = 400;
|
|
2775
|
-
res.end(JSON.stringify({ error: String(e) }));
|
|
2776
|
-
}
|
|
3317
|
+
build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
|
|
3318
|
+
return { path: options.vendorPath, external: true };
|
|
2777
3319
|
});
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
res.setHeader("Content-Type", "text/html");
|
|
2798
|
-
res.end(html);
|
|
2799
|
-
}).catch(next);
|
|
2800
|
-
return;
|
|
2801
|
-
}
|
|
2802
|
-
next();
|
|
2803
|
-
});
|
|
2804
|
-
};
|
|
2805
|
-
}
|
|
2806
|
-
},
|
|
2807
|
-
{
|
|
2808
|
-
name: "prev-jsx-bundle",
|
|
2809
|
-
configureServer(server) {
|
|
2810
|
-
let cachedBundle = null;
|
|
2811
|
-
server.middlewares.use("/_prev/jsx.js", async (req, res, next) => {
|
|
2812
|
-
if (req.method !== "GET")
|
|
2813
|
-
return next();
|
|
2814
|
-
try {
|
|
2815
|
-
if (!cachedBundle) {
|
|
2816
|
-
const jsxEntry = path9.join(srcRoot2, "jsx/index.ts");
|
|
2817
|
-
const result = await viteBuild({
|
|
2818
|
-
logLevel: "silent",
|
|
2819
|
-
define: {
|
|
2820
|
-
"process.env.NODE_ENV": '"production"'
|
|
2821
|
-
},
|
|
2822
|
-
esbuild: {
|
|
2823
|
-
jsx: "automatic",
|
|
2824
|
-
jsxImportSource: "react",
|
|
2825
|
-
jsxDev: false
|
|
2826
|
-
},
|
|
2827
|
-
build: {
|
|
2828
|
-
write: false,
|
|
2829
|
-
lib: {
|
|
2830
|
-
entry: jsxEntry,
|
|
2831
|
-
formats: ["es"],
|
|
2832
|
-
fileName: "jsx"
|
|
2833
|
-
},
|
|
2834
|
-
rollupOptions: {
|
|
2835
|
-
external: ["react", "react-dom", "react/jsx-runtime", "zod"],
|
|
2836
|
-
output: {
|
|
2837
|
-
globals: {
|
|
2838
|
-
react: "React",
|
|
2839
|
-
"react-dom": "ReactDOM"
|
|
2840
|
-
}
|
|
2841
|
-
}
|
|
2842
|
-
},
|
|
2843
|
-
minify: false
|
|
2844
|
-
}
|
|
2845
|
-
});
|
|
2846
|
-
const output = Array.isArray(result) ? result[0] : result;
|
|
2847
|
-
if (!("output" in output)) {
|
|
2848
|
-
throw new Error("Unexpected build result type");
|
|
2849
|
-
}
|
|
2850
|
-
const jsFile = output.output.find((f) => f.type === "chunk");
|
|
2851
|
-
if (jsFile && "code" in jsFile) {
|
|
2852
|
-
cachedBundle = jsFile.code.replace(/from\s+['"]react\/jsx-runtime['"]/g, 'from "https://esm.sh/react@18/jsx-runtime"').replace(/from\s+['"]react['"]/g, 'from "https://esm.sh/react@18"').replace(/from\s+['"]react-dom['"]/g, 'from "https://esm.sh/react-dom@18"').replace(/from\s+['"]zod['"]/g, 'from "https://esm.sh/zod"');
|
|
2853
|
-
}
|
|
2854
|
-
}
|
|
2855
|
-
if (cachedBundle) {
|
|
2856
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
2857
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
2858
|
-
res.end(cachedBundle);
|
|
2859
|
-
return;
|
|
2860
|
-
}
|
|
2861
|
-
res.statusCode = 500;
|
|
2862
|
-
res.end("Failed to bundle jsx primitives");
|
|
2863
|
-
} catch (err) {
|
|
2864
|
-
console.error("Error bundling jsx:", err);
|
|
2865
|
-
res.statusCode = 500;
|
|
2866
|
-
res.end(String(err));
|
|
2867
|
-
}
|
|
2868
|
-
});
|
|
2869
|
-
server.watcher.on("change", (file) => {
|
|
2870
|
-
if (file.includes("/jsx/")) {
|
|
2871
|
-
cachedBundle = null;
|
|
2872
|
-
}
|
|
2873
|
-
});
|
|
2874
|
-
}
|
|
2875
|
-
},
|
|
2876
|
-
{
|
|
2877
|
-
name: "prev-components-bundle",
|
|
2878
|
-
configureServer(server) {
|
|
2879
|
-
const componentCache = new Map;
|
|
2880
|
-
server.middlewares.use(async (req, res, next) => {
|
|
2881
|
-
const urlPath = req.url?.split("?")[0] || "";
|
|
2882
|
-
const match = urlPath.match(/^\/_prev\/components\/([^/]+)\.js$/);
|
|
2883
|
-
if (!match || req.method !== "GET")
|
|
2884
|
-
return next();
|
|
2885
|
-
const componentName = match[1];
|
|
2886
|
-
const componentEntry = path9.join(rootDir, "previews/components", componentName, "index.tsx");
|
|
2887
|
-
if (!existsSync8(componentEntry)) {
|
|
2888
|
-
res.statusCode = 404;
|
|
2889
|
-
res.end(`Component not found: ${componentName}`);
|
|
2890
|
-
return;
|
|
2891
|
-
}
|
|
2892
|
-
try {
|
|
2893
|
-
let bundledCode = componentCache.get(componentName);
|
|
2894
|
-
if (!bundledCode) {
|
|
2895
|
-
const result = await viteBuild({
|
|
2896
|
-
logLevel: "silent",
|
|
2897
|
-
define: {
|
|
2898
|
-
"process.env.NODE_ENV": '"production"'
|
|
2899
|
-
},
|
|
2900
|
-
esbuild: {
|
|
2901
|
-
jsx: "automatic",
|
|
2902
|
-
jsxImportSource: "react",
|
|
2903
|
-
jsxDev: false
|
|
2904
|
-
},
|
|
2905
|
-
build: {
|
|
2906
|
-
write: false,
|
|
2907
|
-
lib: {
|
|
2908
|
-
entry: componentEntry,
|
|
2909
|
-
formats: ["es"],
|
|
2910
|
-
fileName: componentName
|
|
2911
|
-
},
|
|
2912
|
-
rollupOptions: {
|
|
2913
|
-
external: ["react", "react-dom", "react/jsx-runtime", "@prev/jsx"],
|
|
2914
|
-
output: {
|
|
2915
|
-
globals: {
|
|
2916
|
-
react: "React",
|
|
2917
|
-
"react-dom": "ReactDOM"
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
},
|
|
2921
|
-
minify: false
|
|
3320
|
+
build.onResolve({ filter: /^@prev\/jsx$/ }, () => {
|
|
3321
|
+
const jsxPath2 = options.jsxPath || options.vendorPath.replace("runtime.js", "jsx.js");
|
|
3322
|
+
return { path: jsxPath2, external: true };
|
|
3323
|
+
});
|
|
3324
|
+
build.onResolve({ filter: /^@prev\/components\// }, (args) => {
|
|
3325
|
+
console.warn(` Warning: @prev/components imports not supported in static builds: ${args.path}`);
|
|
3326
|
+
return { path: args.path, external: true };
|
|
3327
|
+
});
|
|
3328
|
+
build.onLoad({ filter: /\.css$/ }, (args) => {
|
|
3329
|
+
let content;
|
|
3330
|
+
const tempPath = args.path;
|
|
3331
|
+
if (existsSync14(tempPath)) {
|
|
3332
|
+
content = readFileSync9(tempPath, "utf-8");
|
|
3333
|
+
} else {
|
|
3334
|
+
const diskPath = path13.resolve(resolveDir, path13.relative(tempDir, tempPath));
|
|
3335
|
+
if (existsSync14(diskPath)) {
|
|
3336
|
+
content = readFileSync9(diskPath, "utf-8");
|
|
3337
|
+
} else {
|
|
3338
|
+
return { contents: "", loader: "js" };
|
|
2922
3339
|
}
|
|
2923
|
-
});
|
|
2924
|
-
const output = Array.isArray(result) ? result[0] : result;
|
|
2925
|
-
if (!("output" in output)) {
|
|
2926
|
-
throw new Error("Unexpected build result type");
|
|
2927
|
-
}
|
|
2928
|
-
const jsFile = output.output.find((f) => f.type === "chunk");
|
|
2929
|
-
if (jsFile && "code" in jsFile) {
|
|
2930
|
-
bundledCode = jsFile.code.replace(/from\s+['"]react\/jsx-runtime['"]/g, 'from "https://esm.sh/react@18/jsx-runtime"').replace(/from\s+['"]react['"]/g, 'from "https://esm.sh/react@18"').replace(/from\s+['"]react-dom['"]/g, 'from "https://esm.sh/react-dom@18"').replace(/from\s+['"]@prev\/jsx['"]/g, `from "${req.headers.origin || ""}/_prev/jsx.js"`);
|
|
2931
|
-
componentCache.set(componentName, bundledCode);
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
if (bundledCode) {
|
|
2935
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
2936
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
2937
|
-
res.end(bundledCode);
|
|
2938
|
-
return;
|
|
2939
|
-
}
|
|
2940
|
-
res.statusCode = 500;
|
|
2941
|
-
res.end(`Failed to bundle component: ${componentName}`);
|
|
2942
|
-
} catch (err) {
|
|
2943
|
-
console.error(`Error bundling component ${componentName}:`, err);
|
|
2944
|
-
res.statusCode = 500;
|
|
2945
|
-
res.end(String(err));
|
|
2946
|
-
}
|
|
2947
|
-
});
|
|
2948
|
-
server.watcher.on("change", (file) => {
|
|
2949
|
-
if (file.includes("/previews/components/")) {
|
|
2950
|
-
const match = file.match(/\/previews\/components\/([^/]+)\//);
|
|
2951
|
-
if (match) {
|
|
2952
|
-
componentCache.delete(match[1]);
|
|
2953
|
-
}
|
|
2954
|
-
}
|
|
2955
|
-
});
|
|
2956
|
-
}
|
|
2957
|
-
},
|
|
2958
|
-
{
|
|
2959
|
-
name: "prev-preview-server",
|
|
2960
|
-
resolveId(id) {
|
|
2961
|
-
if (id.startsWith("/_preview/")) {
|
|
2962
|
-
const relativePath = id.slice("/_preview/".length);
|
|
2963
|
-
const previewsDir = path9.join(rootDir, "previews");
|
|
2964
|
-
const resolved = path9.resolve(previewsDir, relativePath);
|
|
2965
|
-
if (resolved.startsWith(previewsDir)) {
|
|
2966
|
-
return resolved;
|
|
2967
|
-
}
|
|
2968
|
-
}
|
|
2969
|
-
},
|
|
2970
|
-
configureServer(server) {
|
|
2971
|
-
server.middlewares.use(async (req, res, next) => {
|
|
2972
|
-
const urlPath = req.url?.split("?")[0] || "";
|
|
2973
|
-
if (urlPath.startsWith("/_preview-bundle/")) {
|
|
2974
|
-
const startTime = performance.now();
|
|
2975
|
-
const previewPath = decodeURIComponent(urlPath.slice("/_preview-bundle/".length));
|
|
2976
|
-
const previewDir = path9.join(rootDir, "previews", previewPath);
|
|
2977
|
-
if (!previewDir.startsWith(path9.join(rootDir, "previews"))) {
|
|
2978
|
-
res.statusCode = 403;
|
|
2979
|
-
res.end("Forbidden");
|
|
2980
|
-
return;
|
|
2981
|
-
}
|
|
2982
|
-
const entryFiles = ["index.tsx", "index.ts", "index.jsx", "index.js", "App.tsx", "App.ts"];
|
|
2983
|
-
let entryFile = "";
|
|
2984
|
-
for (const f of entryFiles) {
|
|
2985
|
-
if (existsSync8(path9.join(previewDir, f))) {
|
|
2986
|
-
entryFile = path9.join(previewDir, f);
|
|
2987
|
-
break;
|
|
2988
3340
|
}
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
entryPoints: [entryFile],
|
|
2998
|
-
bundle: true,
|
|
2999
|
-
write: false,
|
|
3000
|
-
format: "esm",
|
|
3001
|
-
jsx: "automatic",
|
|
3002
|
-
jsxImportSource: "react",
|
|
3003
|
-
jsxDev: false,
|
|
3004
|
-
target: "es2020",
|
|
3005
|
-
minify: false,
|
|
3006
|
-
sourcemap: false,
|
|
3007
|
-
external: ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "@prev/jsx", "fs", "path", "js-yaml"],
|
|
3008
|
-
alias: {
|
|
3009
|
-
react: "https://esm.sh/react@18",
|
|
3010
|
-
"react-dom": "https://esm.sh/react-dom@18",
|
|
3011
|
-
"react-dom/client": "https://esm.sh/react-dom@18/client",
|
|
3012
|
-
"react/jsx-runtime": "https://esm.sh/react@18/jsx-runtime"
|
|
3013
|
-
},
|
|
3014
|
-
define: {
|
|
3015
|
-
"process.env.NODE_ENV": '"production"'
|
|
3016
|
-
}
|
|
3017
|
-
});
|
|
3018
|
-
const bundleTime = Math.round(performance.now() - startTime);
|
|
3019
|
-
const code = result.outputFiles[0]?.text || "";
|
|
3020
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
3021
|
-
res.setHeader("X-Bundle-Time", String(bundleTime));
|
|
3022
|
-
res.end(code);
|
|
3023
|
-
return;
|
|
3024
|
-
} catch (err) {
|
|
3025
|
-
console.error("Bundle error:", err);
|
|
3026
|
-
res.statusCode = 500;
|
|
3027
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
3028
|
-
return;
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
if (urlPath === "/_preview-runtime") {
|
|
3032
|
-
const templatePath = path9.join(srcRoot2, "preview-runtime/fast-template.html");
|
|
3033
|
-
if (existsSync8(templatePath)) {
|
|
3034
|
-
const html = readFileSync7(templatePath, "utf-8");
|
|
3035
|
-
res.setHeader("Content-Type", "text/html");
|
|
3036
|
-
res.end(html);
|
|
3037
|
-
return;
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
if (urlPath.startsWith("/_preview-config/")) {
|
|
3041
|
-
const pathAfterConfig = decodeURIComponent(urlPath.slice("/_preview-config/".length));
|
|
3042
|
-
const previewsDir = path9.join(rootDir, "previews");
|
|
3043
|
-
const multiTypeMatch = pathAfterConfig.match(/^(components|screens|flows|atlas)\/(.+)$/);
|
|
3044
|
-
if (multiTypeMatch) {
|
|
3045
|
-
const [, type, name] = multiTypeMatch;
|
|
3046
|
-
const previewDir = path9.join(previewsDir, type, name);
|
|
3047
|
-
if (!previewDir.startsWith(previewsDir)) {
|
|
3048
|
-
res.statusCode = 403;
|
|
3049
|
-
res.end("Forbidden");
|
|
3341
|
+
userCssCollected.push(content);
|
|
3342
|
+
return { contents: "", loader: "js" };
|
|
3343
|
+
});
|
|
3344
|
+
build.onResolve({ filter: /^\.\.?\// }, (args) => {
|
|
3345
|
+
const resolved = path13.resolve(path13.dirname(args.importer), args.path);
|
|
3346
|
+
const tryExts = [".tsx", ".ts", ".jsx", ".js", ".css"];
|
|
3347
|
+
const tryIndex = ["/index.tsx", "/index.ts", "/index.jsx", "/index.js"];
|
|
3348
|
+
if (existsAsFile(resolved))
|
|
3050
3349
|
return;
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
try {
|
|
3054
|
-
if (type === "flows") {
|
|
3055
|
-
const configPathYaml = path9.join(previewDir, "index.yaml");
|
|
3056
|
-
const configPathYml = path9.join(previewDir, "index.yml");
|
|
3057
|
-
const configPath = existsSync8(configPathYaml) ? configPathYaml : configPathYml;
|
|
3058
|
-
if (existsSync8(configPath)) {
|
|
3059
|
-
const flow = await parseFlowDefinition(configPath);
|
|
3060
|
-
if (flow) {
|
|
3061
|
-
res.setHeader("Content-Type", "application/json");
|
|
3062
|
-
res.end(JSON.stringify(flow));
|
|
3063
|
-
return;
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
} else if (type === "atlas") {
|
|
3067
|
-
const configPathYaml = path9.join(previewDir, "index.yaml");
|
|
3068
|
-
const configPathYml = path9.join(previewDir, "index.yml");
|
|
3069
|
-
const configPath = existsSync8(configPathYaml) ? configPathYaml : configPathYml;
|
|
3070
|
-
if (existsSync8(configPath)) {
|
|
3071
|
-
const atlas = await parseAtlasDefinition(configPath);
|
|
3072
|
-
if (atlas) {
|
|
3073
|
-
res.setHeader("Content-Type", "application/json");
|
|
3074
|
-
res.end(JSON.stringify(atlas));
|
|
3075
|
-
return;
|
|
3076
|
-
}
|
|
3077
|
-
}
|
|
3078
|
-
} else {
|
|
3079
|
-
const config2 = await buildPreviewConfig(previewDir);
|
|
3080
|
-
res.setHeader("Content-Type", "application/json");
|
|
3081
|
-
res.end(JSON.stringify(config2));
|
|
3082
|
-
return;
|
|
3083
|
-
}
|
|
3084
|
-
res.statusCode = 400;
|
|
3085
|
-
res.end(JSON.stringify({ error: "Invalid config format" }));
|
|
3350
|
+
for (const ext of tryExts) {
|
|
3351
|
+
if (existsAsFile(resolved + ext))
|
|
3086
3352
|
return;
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
3353
|
+
}
|
|
3354
|
+
for (const idx of tryIndex) {
|
|
3355
|
+
if (existsAsFile(resolved + idx))
|
|
3091
3356
|
return;
|
|
3092
|
-
}
|
|
3093
3357
|
}
|
|
3094
|
-
|
|
3095
|
-
const
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3358
|
+
const importerRelative = path13.relative(tempDir, args.importer);
|
|
3359
|
+
const originalDir = path13.resolve(resolveDir, path13.dirname(importerRelative));
|
|
3360
|
+
const diskPath = path13.resolve(originalDir, args.path);
|
|
3361
|
+
if (existsAsFile(diskPath))
|
|
3362
|
+
return { path: diskPath };
|
|
3363
|
+
for (const ext of tryExts) {
|
|
3364
|
+
if (existsAsFile(diskPath + ext)) {
|
|
3365
|
+
return { path: diskPath + ext };
|
|
3366
|
+
}
|
|
3100
3367
|
}
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
res.setHeader("Content-Type", "application/json");
|
|
3105
|
-
res.end(JSON.stringify(config2));
|
|
3106
|
-
return;
|
|
3107
|
-
} catch (err) {
|
|
3108
|
-
console.error("Error building preview config:", err);
|
|
3109
|
-
res.statusCode = 500;
|
|
3110
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
3111
|
-
return;
|
|
3368
|
+
for (const idx of tryIndex) {
|
|
3369
|
+
if (existsAsFile(diskPath + idx)) {
|
|
3370
|
+
return { path: diskPath + idx };
|
|
3112
3371
|
}
|
|
3113
3372
|
}
|
|
3114
|
-
|
|
3373
|
+
return;
|
|
3374
|
+
});
|
|
3115
3375
|
}
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3376
|
+
}
|
|
3377
|
+
]
|
|
3378
|
+
});
|
|
3379
|
+
if (!result.success) {
|
|
3380
|
+
const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message).join("; ");
|
|
3381
|
+
return { success: false, html: "", css: "", error: errors || "Build failed" };
|
|
3382
|
+
}
|
|
3383
|
+
const jsFile = result.outputs.find((f) => f.path.endsWith(".js")) || result.outputs[0];
|
|
3384
|
+
let jsCode = jsFile ? await jsFile.text() : "";
|
|
3385
|
+
const jsxPath = options.jsxPath || options.vendorPath.replace("runtime.js", "jsx.js");
|
|
3386
|
+
jsCode = jsCode.replace(/from\s*["']react\/jsx(-dev)?-runtime["']/g, `from"${options.vendorPath}"`);
|
|
3387
|
+
jsCode = jsCode.replace(/from\s*["']react-dom\/client["']/g, `from"${options.vendorPath}"`);
|
|
3388
|
+
jsCode = jsCode.replace(/from\s*["']react-dom["']/g, `from"${options.vendorPath}"`);
|
|
3389
|
+
jsCode = jsCode.replace(/from\s*["']react["']/g, `from"${options.vendorPath}"`);
|
|
3390
|
+
jsCode = jsCode.replace(/from\s*["']@prev\/jsx["']/g, `from"${jsxPath}"`);
|
|
3391
|
+
jsCode = jsCode.replace(/from\s*["']@prev\/components\/[^"']*["']/g, `from"${jsxPath}"`);
|
|
3392
|
+
let css = "";
|
|
3393
|
+
if (config.tailwind) {
|
|
3394
|
+
const tailwindResult = await compileTailwind(config.files.map((f) => ({ path: f.path, content: f.content })));
|
|
3395
|
+
if (tailwindResult.success)
|
|
3396
|
+
css = tailwindResult.css;
|
|
3397
|
+
}
|
|
3398
|
+
let userCss = userCssCollected.join(`
|
|
3399
|
+
`);
|
|
3400
|
+
if (config.tailwind) {
|
|
3401
|
+
userCss = userCss.replace(/@import\s*["']tailwindcss["']\s*;?/g, "");
|
|
3402
|
+
}
|
|
3403
|
+
const allCss = css + `
|
|
3404
|
+
` + userCss;
|
|
3405
|
+
const canvasStyles = `
|
|
3406
|
+
html, body {
|
|
3407
|
+
margin: 0;
|
|
3408
|
+
min-height: 100vh;
|
|
3409
|
+
}
|
|
3410
|
+
body {
|
|
3411
|
+
display: flex;
|
|
3412
|
+
align-items: center;
|
|
3413
|
+
justify-content: center;
|
|
3414
|
+
background-color: #fafafa;
|
|
3415
|
+
background-image:
|
|
3416
|
+
radial-gradient(circle at center, #e5e5e5 1px, transparent 1px);
|
|
3417
|
+
background-size: 16px 16px;
|
|
3418
|
+
padding: 24px;
|
|
3419
|
+
box-sizing: border-box;
|
|
3420
|
+
}
|
|
3421
|
+
@media (prefers-color-scheme: dark) {
|
|
3422
|
+
body {
|
|
3423
|
+
background-color: #171717;
|
|
3424
|
+
background-image:
|
|
3425
|
+
radial-gradient(circle at center, #262626 1px, transparent 1px);
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
#root {
|
|
3429
|
+
background: white;
|
|
3430
|
+
border-radius: 12px;
|
|
3431
|
+
padding: 32px;
|
|
3432
|
+
box-shadow: 0 4px 24px -4px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04);
|
|
3433
|
+
max-width: 100%;
|
|
3434
|
+
}
|
|
3435
|
+
@media (prefers-color-scheme: dark) {
|
|
3436
|
+
#root {
|
|
3437
|
+
background: #1c1c1c;
|
|
3438
|
+
box-shadow: 0 4px 24px -4px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.06);
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
`;
|
|
3442
|
+
const html = `<!DOCTYPE html>
|
|
3443
|
+
<html lang="en">
|
|
3123
3444
|
<head>
|
|
3124
3445
|
<meta charset="UTF-8">
|
|
3125
|
-
<
|
|
3126
|
-
<
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3446
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3447
|
+
<title>Preview</title>
|
|
3448
|
+
<style>${allCss}</style>
|
|
3449
|
+
<style>${canvasStyles}</style>
|
|
3450
|
+
</head>
|
|
3451
|
+
<body>
|
|
3452
|
+
<div id="root"></div>
|
|
3453
|
+
<script type="module" src="${options.vendorPath}"></script>
|
|
3454
|
+
<script type="module" src="${jsxPath}"></script>
|
|
3455
|
+
<script type="module">${jsCode}</script>
|
|
3456
|
+
</body>
|
|
3457
|
+
</html>`;
|
|
3458
|
+
return { success: true, html, css: allCss };
|
|
3459
|
+
} finally {
|
|
3460
|
+
rmSync3(tempDir, { recursive: true, force: true });
|
|
3461
|
+
}
|
|
3462
|
+
} catch (err) {
|
|
3463
|
+
return { success: false, html: "", css: "", error: err instanceof Error ? err.message : String(err) };
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3130
3466
|
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3467
|
+
// src/server/build.ts
|
|
3468
|
+
function findCliRoot3() {
|
|
3469
|
+
let dir = path14.dirname(fileURLToPath3(import.meta.url));
|
|
3470
|
+
for (let i = 0;i < 10; i++) {
|
|
3471
|
+
const pkgPath = path14.join(dir, "package.json");
|
|
3472
|
+
if (existsSync15(pkgPath)) {
|
|
3473
|
+
try {
|
|
3474
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
3475
|
+
if (pkg.name === "prev-cli")
|
|
3476
|
+
return dir;
|
|
3477
|
+
} catch {}
|
|
3478
|
+
}
|
|
3479
|
+
const parent = path14.dirname(dir);
|
|
3480
|
+
if (parent === dir)
|
|
3481
|
+
break;
|
|
3482
|
+
dir = parent;
|
|
3483
|
+
}
|
|
3484
|
+
return path14.dirname(path14.dirname(fileURLToPath3(import.meta.url)));
|
|
3485
|
+
}
|
|
3486
|
+
var cliRoot3 = findCliRoot3();
|
|
3487
|
+
var srcRoot2 = path14.join(cliRoot3, "src");
|
|
3488
|
+
async function buildProductionSite(options) {
|
|
3489
|
+
const { rootDir, include, base = "/" } = options;
|
|
3490
|
+
const config = loadConfig(rootDir);
|
|
3491
|
+
const distDir = path14.join(rootDir, "dist");
|
|
3492
|
+
const entryPath = path14.join(srcRoot2, "theme/entry.tsx");
|
|
3493
|
+
const plugins = [
|
|
3494
|
+
virtualModulesPlugin({ rootDir, include, config }),
|
|
3495
|
+
mdxPlugin({ rootDir }),
|
|
3496
|
+
aliasesPlugin({ cliRoot: cliRoot3 })
|
|
3497
|
+
];
|
|
3498
|
+
const result = await Bun.build({
|
|
3499
|
+
entrypoints: [entryPath],
|
|
3500
|
+
outdir: distDir,
|
|
3501
|
+
format: "esm",
|
|
3502
|
+
target: "browser",
|
|
3503
|
+
minify: true,
|
|
3504
|
+
splitting: true,
|
|
3505
|
+
plugins,
|
|
3506
|
+
jsx: { runtime: "automatic", importSource: "react", development: false },
|
|
3507
|
+
naming: {
|
|
3508
|
+
entry: "assets/[name]-[hash].[ext]",
|
|
3509
|
+
chunk: "assets/[name]-[hash].[ext]",
|
|
3510
|
+
asset: "assets/[name]-[hash].[ext]"
|
|
3511
|
+
},
|
|
3512
|
+
define: {
|
|
3513
|
+
"import.meta.env.DEV": "false",
|
|
3514
|
+
"import.meta.env.BASE_URL": JSON.stringify(base),
|
|
3515
|
+
"process.env.NODE_ENV": '"production"'
|
|
3516
|
+
}
|
|
3517
|
+
});
|
|
3518
|
+
if (!result.success) {
|
|
3519
|
+
const errors = result.logs.filter((l) => l.level === "error").map((l) => l.message);
|
|
3520
|
+
throw new Error(`Build failed:
|
|
3521
|
+
${errors.join(`
|
|
3522
|
+
`)}`);
|
|
3523
|
+
}
|
|
3524
|
+
const entryOutput = result.outputs.find((o) => o.kind === "entry-point");
|
|
3525
|
+
const cssOutputs = result.outputs.filter((o) => o.path.endsWith(".css"));
|
|
3526
|
+
const entryJsPath = entryOutput ? base + path14.relative(distDir, entryOutput.path) : "";
|
|
3527
|
+
const cssLinks = cssOutputs.map((o) => {
|
|
3528
|
+
const href = base + path14.relative(distDir, o.path);
|
|
3529
|
+
return ` <link rel="stylesheet" href="${href}" />`;
|
|
3530
|
+
}).join(`
|
|
3531
|
+
`);
|
|
3532
|
+
const html = `<!DOCTYPE html>
|
|
3533
|
+
<html lang="en">
|
|
3534
|
+
<head>
|
|
3535
|
+
<meta charset="UTF-8" />
|
|
3536
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
3537
|
+
<title>Documentation</title>
|
|
3538
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
3539
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
3540
|
+
<link rel="preload" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" as="style" />
|
|
3541
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=IBM+Plex+Mono:wght@400;500&display=swap" />
|
|
3542
|
+
${cssLinks}
|
|
3135
3543
|
</head>
|
|
3136
3544
|
<body>
|
|
3137
3545
|
<div id="root"></div>
|
|
3546
|
+
<script type="module" src="${entryJsPath}"></script>
|
|
3138
3547
|
</body>
|
|
3139
3548
|
</html>`;
|
|
3140
|
-
|
|
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
|
-
|
|
3172
|
-
|
|
3549
|
+
writeFileSync5(path14.join(distDir, "index.html"), html);
|
|
3550
|
+
copyFileSync(path14.join(distDir, "index.html"), path14.join(distDir, "404.html"));
|
|
3551
|
+
await buildPreviewHtmlFiles(rootDir, distDir);
|
|
3552
|
+
}
|
|
3553
|
+
async function buildPreviewHtmlFiles(rootDir, distDir) {
|
|
3554
|
+
const units = await scanPreviewUnits(rootDir);
|
|
3555
|
+
if (units.length === 0)
|
|
3556
|
+
return;
|
|
3557
|
+
const targetDir = path14.join(distDir, "_preview");
|
|
3558
|
+
const vendorsDir = path14.join(targetDir, "_vendors");
|
|
3559
|
+
const previewsDir = path14.join(rootDir, "previews");
|
|
3560
|
+
let hasFlowErrors = false;
|
|
3561
|
+
for (const unit of units) {
|
|
3562
|
+
if (unit.type !== "flow" || !unit.config)
|
|
3563
|
+
continue;
|
|
3564
|
+
const result = verifyFlow(unit.config, rootDir);
|
|
3565
|
+
for (const w of result.warnings) {
|
|
3566
|
+
console.warn(` \u26A0 flows/${unit.name}: ${w}`);
|
|
3567
|
+
}
|
|
3568
|
+
for (const e of result.errors) {
|
|
3569
|
+
console.error(` \u2717 flows/${unit.name}: ${e}`);
|
|
3570
|
+
hasFlowErrors = true;
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
if (hasFlowErrors) {
|
|
3574
|
+
throw new Error("Flow verification failed \u2014 fix errors above before building");
|
|
3575
|
+
}
|
|
3576
|
+
let totalBuilds = 0;
|
|
3577
|
+
for (const unit of units) {
|
|
3578
|
+
if (unit.type === "flow")
|
|
3579
|
+
continue;
|
|
3580
|
+
totalBuilds++;
|
|
3581
|
+
if (unit.files.states)
|
|
3582
|
+
totalBuilds += unit.files.states.length;
|
|
3583
|
+
}
|
|
3584
|
+
console.log(`
|
|
3585
|
+
Building ${totalBuilds} preview(s)...`);
|
|
3586
|
+
console.log(" Building shared vendor bundle...");
|
|
3587
|
+
mkdirSync3(vendorsDir, { recursive: true });
|
|
3588
|
+
const vendorResult = await buildVendorBundle();
|
|
3589
|
+
if (!vendorResult.success) {
|
|
3590
|
+
console.error(` \u2717 Vendor bundle: ${vendorResult.error}`);
|
|
3591
|
+
return;
|
|
3592
|
+
}
|
|
3593
|
+
writeFileSync5(path14.join(vendorsDir, "runtime.js"), vendorResult.code);
|
|
3594
|
+
console.log(" \u2713 _vendors/runtime.js");
|
|
3595
|
+
const jsxResult = await buildJsxBundle("../_vendors/runtime.js");
|
|
3596
|
+
if (!jsxResult.success) {
|
|
3597
|
+
console.error(` \u2717 JSX bundle: ${jsxResult.error}`);
|
|
3598
|
+
return;
|
|
3599
|
+
}
|
|
3600
|
+
writeFileSync5(path14.join(vendorsDir, "jsx.js"), jsxResult.code);
|
|
3601
|
+
console.log(" \u2713 _vendors/jsx.js");
|
|
3602
|
+
for (const unit of units) {
|
|
3603
|
+
if (unit.type === "flow")
|
|
3604
|
+
continue;
|
|
3605
|
+
const previewDir = path14.join(previewsDir, unit.type + "s", unit.name);
|
|
3606
|
+
const previewPath = `${unit.type}s/${unit.name}`;
|
|
3607
|
+
const depth = previewPath.split("/").length;
|
|
3608
|
+
const vendorPath = "../".repeat(depth) + "_vendors/runtime.js";
|
|
3609
|
+
try {
|
|
3610
|
+
const config = await buildPreviewConfig(previewDir);
|
|
3611
|
+
const result = await buildOptimizedPreview(config, { vendorPath, resolveDir: previewDir });
|
|
3612
|
+
if (!result.success) {
|
|
3613
|
+
console.error(` \u2717 ${previewPath}: ${result.error}`);
|
|
3614
|
+
} else {
|
|
3615
|
+
const outputDir = path14.join(targetDir, previewPath);
|
|
3616
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
3617
|
+
writeFileSync5(path14.join(outputDir, "index.html"), result.html);
|
|
3618
|
+
console.log(` \u2713 ${previewPath}`);
|
|
3619
|
+
}
|
|
3620
|
+
} catch (err) {
|
|
3621
|
+
console.error(` \u2717 ${previewPath}: ${err}`);
|
|
3622
|
+
}
|
|
3623
|
+
if (unit.type === "screen" && unit.files.states) {
|
|
3624
|
+
for (const stateFile of unit.files.states) {
|
|
3625
|
+
const stateName = stateFile.replace(/\.(tsx|jsx)$/, "");
|
|
3626
|
+
const stateVendorPath = "../".repeat(depth + 1) + "_vendors/runtime.js";
|
|
3627
|
+
try {
|
|
3628
|
+
const config = await buildPreviewConfig(previewDir, stateFile);
|
|
3629
|
+
const result = await buildOptimizedPreview(config, { vendorPath: stateVendorPath, resolveDir: previewDir });
|
|
3630
|
+
if (!result.success) {
|
|
3631
|
+
console.error(` \u2717 ${previewPath}/${stateName}: ${result.error}`);
|
|
3632
|
+
} else {
|
|
3633
|
+
const outputDir = path14.join(targetDir, previewPath, stateName);
|
|
3634
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
3635
|
+
writeFileSync5(path14.join(outputDir, "index.html"), result.html);
|
|
3636
|
+
console.log(` \u2713 ${previewPath}/${stateName}`);
|
|
3637
|
+
}
|
|
3638
|
+
} catch (err) {
|
|
3639
|
+
console.error(` \u2717 ${previewPath}/${stateName}: ${err}`);
|
|
3173
3640
|
}
|
|
3174
3641
|
}
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
jsx: "automatic",
|
|
3197
|
-
jsxImportSource: "react",
|
|
3198
|
-
jsxDev: false
|
|
3199
|
-
},
|
|
3200
|
-
optimizeDeps: {
|
|
3201
|
-
noDiscovery: true,
|
|
3202
|
-
holdUntilCrawlEnd: false,
|
|
3203
|
-
esbuildOptions: {
|
|
3204
|
-
jsx: "automatic",
|
|
3205
|
-
jsxImportSource: "react",
|
|
3206
|
-
jsxDev: false
|
|
3207
|
-
},
|
|
3208
|
-
include: [
|
|
3209
|
-
"react-dom/client",
|
|
3210
|
-
"use-sync-external-store",
|
|
3211
|
-
"use-sync-external-store/shim/with-selector.js",
|
|
3212
|
-
"mermaid",
|
|
3213
|
-
"dayjs",
|
|
3214
|
-
"@terrastruct/d2"
|
|
3215
|
-
],
|
|
3216
|
-
exclude: [
|
|
3217
|
-
"virtual:prev-config",
|
|
3218
|
-
"virtual:prev-previews",
|
|
3219
|
-
"virtual:prev-pages",
|
|
3220
|
-
"virtual:prev-page-modules",
|
|
3221
|
-
"@prev/theme"
|
|
3222
|
-
]
|
|
3223
|
-
},
|
|
3224
|
-
ssr: {
|
|
3225
|
-
noExternal: true
|
|
3226
|
-
},
|
|
3227
|
-
server: {
|
|
3228
|
-
port,
|
|
3229
|
-
strictPort: false,
|
|
3230
|
-
fs: {
|
|
3231
|
-
allow: [rootDir, cliRoot2]
|
|
3232
|
-
},
|
|
3233
|
-
warmup: {
|
|
3234
|
-
clientFiles: [
|
|
3235
|
-
path9.join(srcRoot2, "theme/entry.tsx"),
|
|
3236
|
-
path9.join(srcRoot2, "theme/styles.css")
|
|
3237
|
-
]
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
// src/server/preview.ts
|
|
3647
|
+
import path15 from "path";
|
|
3648
|
+
import { existsSync as existsSync16, statSync as statSync3 } from "fs";
|
|
3649
|
+
async function startPreviewServer(options) {
|
|
3650
|
+
const { rootDir, port } = options;
|
|
3651
|
+
const distDir = path15.join(rootDir, "dist");
|
|
3652
|
+
if (!existsSync16(distDir)) {
|
|
3653
|
+
throw new Error(`No dist/ directory found. Run 'prev build' first.`);
|
|
3654
|
+
}
|
|
3655
|
+
const indexHtml = path15.join(distDir, "index.html");
|
|
3656
|
+
const server = Bun.serve({
|
|
3657
|
+
port,
|
|
3658
|
+
async fetch(req) {
|
|
3659
|
+
const url = new URL(req.url);
|
|
3660
|
+
let pathname = url.pathname;
|
|
3661
|
+
if (pathname !== "/" && pathname.endsWith("/")) {
|
|
3662
|
+
pathname = pathname.slice(0, -1);
|
|
3238
3663
|
}
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
reportCompressedSize: false,
|
|
3247
|
-
chunkSizeWarningLimit: 1e4,
|
|
3248
|
-
rollupOptions: {
|
|
3249
|
-
input: {
|
|
3250
|
-
main: path9.join(srcRoot2, "theme/index.html")
|
|
3251
|
-
}
|
|
3664
|
+
const filePath = path15.join(distDir, pathname);
|
|
3665
|
+
if (filePath.startsWith(distDir) && existsSync16(filePath)) {
|
|
3666
|
+
try {
|
|
3667
|
+
if (statSync3(filePath).isFile()) {
|
|
3668
|
+
return new Response(Bun.file(filePath));
|
|
3669
|
+
}
|
|
3670
|
+
} catch {}
|
|
3252
3671
|
}
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3672
|
+
const indexPath = path15.join(distDir, pathname, "index.html");
|
|
3673
|
+
if (existsSync16(indexPath)) {
|
|
3674
|
+
return new Response(Bun.file(indexPath));
|
|
3675
|
+
}
|
|
3676
|
+
if (!pathname.includes(".") && existsSync16(indexHtml)) {
|
|
3677
|
+
return new Response(Bun.file(indexHtml));
|
|
3678
|
+
}
|
|
3679
|
+
return new Response("Not Found", { status: 404 });
|
|
3680
|
+
}
|
|
3681
|
+
});
|
|
3682
|
+
return {
|
|
3683
|
+
server,
|
|
3684
|
+
port: server.port,
|
|
3685
|
+
url: `http://localhost:${server.port}/`,
|
|
3686
|
+
stop: () => server.stop()
|
|
3258
3687
|
};
|
|
3259
3688
|
}
|
|
3260
3689
|
|
|
@@ -3289,10 +3718,8 @@ async function findAvailablePort(minPort, maxPort) {
|
|
|
3289
3718
|
throw new Error(`No available port found between ${minPort} and ${maxPort}`);
|
|
3290
3719
|
}
|
|
3291
3720
|
|
|
3292
|
-
// src/
|
|
3721
|
+
// src/server/start.ts
|
|
3293
3722
|
import { exec } from "child_process";
|
|
3294
|
-
import { existsSync as existsSync9, rmSync as rmSync3, copyFileSync } from "fs";
|
|
3295
|
-
import path10 from "path";
|
|
3296
3723
|
function printWelcome(type) {
|
|
3297
3724
|
console.log();
|
|
3298
3725
|
console.log(" \u2728 prev");
|
|
@@ -3307,7 +3734,6 @@ function printShortcuts() {
|
|
|
3307
3734
|
console.log();
|
|
3308
3735
|
console.log(" Shortcuts:");
|
|
3309
3736
|
console.log(" o \u2192 open in browser");
|
|
3310
|
-
console.log(" c \u2192 clear cache");
|
|
3311
3737
|
console.log(" h \u2192 show this help");
|
|
3312
3738
|
console.log(" q \u2192 quit");
|
|
3313
3739
|
console.log();
|
|
@@ -3324,25 +3750,7 @@ function openBrowser(url) {
|
|
|
3324
3750
|
exec(`${cmd} ${url}`);
|
|
3325
3751
|
console.log(` \u2197 Opened ${url}`);
|
|
3326
3752
|
}
|
|
3327
|
-
function
|
|
3328
|
-
const viteCacheDir = path10.join(rootDir, ".vite");
|
|
3329
|
-
const nodeModulesVite = path10.join(rootDir, "node_modules", ".vite");
|
|
3330
|
-
let cleared = 0;
|
|
3331
|
-
if (existsSync9(viteCacheDir)) {
|
|
3332
|
-
rmSync3(viteCacheDir, { recursive: true });
|
|
3333
|
-
cleared++;
|
|
3334
|
-
}
|
|
3335
|
-
if (existsSync9(nodeModulesVite)) {
|
|
3336
|
-
rmSync3(nodeModulesVite, { recursive: true });
|
|
3337
|
-
cleared++;
|
|
3338
|
-
}
|
|
3339
|
-
if (cleared === 0) {
|
|
3340
|
-
console.log(" No cache to clear");
|
|
3341
|
-
} else {
|
|
3342
|
-
console.log(` \u2713 Cleared Vite cache`);
|
|
3343
|
-
}
|
|
3344
|
-
}
|
|
3345
|
-
function setupKeyboardShortcuts(rootDir, url, quit) {
|
|
3753
|
+
function setupKeyboardShortcuts(url, quit) {
|
|
3346
3754
|
if (!process.stdin.isTTY)
|
|
3347
3755
|
return () => {};
|
|
3348
3756
|
process.stdin.setRawMode(true);
|
|
@@ -3353,9 +3761,6 @@ function setupKeyboardShortcuts(rootDir, url, quit) {
|
|
|
3353
3761
|
case "o":
|
|
3354
3762
|
openBrowser(url);
|
|
3355
3763
|
break;
|
|
3356
|
-
case "c":
|
|
3357
|
-
clearCache(rootDir);
|
|
3358
|
-
break;
|
|
3359
3764
|
case "h":
|
|
3360
3765
|
printShortcuts();
|
|
3361
3766
|
break;
|
|
@@ -3376,27 +3781,13 @@ function setupKeyboardShortcuts(rootDir, url, quit) {
|
|
|
3376
3781
|
}
|
|
3377
3782
|
async function startDev(rootDir, options = {}) {
|
|
3378
3783
|
const port = options.port ?? await getRandomPort();
|
|
3379
|
-
const
|
|
3784
|
+
const { server, url, stop } = await startDevServer({
|
|
3380
3785
|
rootDir,
|
|
3381
|
-
mode: "development",
|
|
3382
3786
|
port,
|
|
3383
|
-
include: options.include
|
|
3384
|
-
debug: options.debug
|
|
3787
|
+
include: options.include
|
|
3385
3788
|
});
|
|
3386
|
-
const server = await createServer2(config);
|
|
3387
|
-
await server.listen();
|
|
3388
|
-
const warmupPort = server.config.server.port;
|
|
3389
|
-
fetch(`http://localhost:${warmupPort}/`).catch(() => {});
|
|
3390
|
-
const debugCollector = getDebugCollector();
|
|
3391
|
-
if (debugCollector) {
|
|
3392
|
-
debugCollector.startPhase("serverReady");
|
|
3393
|
-
const reportPath = debugCollector.writeReport();
|
|
3394
|
-
console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
|
|
3395
|
-
}
|
|
3396
|
-
const actualPort = server.config.server.port || port;
|
|
3397
|
-
const url = `http://localhost:${actualPort}/`;
|
|
3398
3789
|
printWelcome("dev");
|
|
3399
|
-
|
|
3790
|
+
console.log(` \u279C Local: ${url}`);
|
|
3400
3791
|
printReady();
|
|
3401
3792
|
let isShuttingDown = false;
|
|
3402
3793
|
let cleanupStdin = () => {};
|
|
@@ -3412,17 +3803,10 @@ async function startDev(rootDir, options = {}) {
|
|
|
3412
3803
|
Shutting down...`);
|
|
3413
3804
|
}
|
|
3414
3805
|
cleanupStdin();
|
|
3415
|
-
|
|
3416
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
3417
|
-
setTimeout(() => {
|
|
3418
|
-
console.log(" Force closing (timeout)...");
|
|
3419
|
-
resolve();
|
|
3420
|
-
}, 3000);
|
|
3421
|
-
});
|
|
3422
|
-
await Promise.race([closePromise, timeoutPromise]);
|
|
3806
|
+
stop();
|
|
3423
3807
|
process.exit(0);
|
|
3424
3808
|
};
|
|
3425
|
-
cleanupStdin = setupKeyboardShortcuts(
|
|
3809
|
+
cleanupStdin = setupKeyboardShortcuts(url, () => shutdown());
|
|
3426
3810
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
3427
3811
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
3428
3812
|
process.on("uncaughtException", (err) => {
|
|
@@ -3437,26 +3821,11 @@ async function buildSite(rootDir, options = {}) {
|
|
|
3437
3821
|
console.log(" \u2728 prev build");
|
|
3438
3822
|
console.log();
|
|
3439
3823
|
console.log(" Building your documentation site...");
|
|
3440
|
-
|
|
3824
|
+
await buildProductionSite({
|
|
3441
3825
|
rootDir,
|
|
3442
|
-
mode: "production",
|
|
3443
3826
|
include: options.include,
|
|
3444
|
-
base: options.base
|
|
3445
|
-
debug: options.debug
|
|
3827
|
+
base: options.base
|
|
3446
3828
|
});
|
|
3447
|
-
await build4(config);
|
|
3448
|
-
const debugCollector = getDebugCollector();
|
|
3449
|
-
if (debugCollector) {
|
|
3450
|
-
debugCollector.startPhase("buildComplete");
|
|
3451
|
-
const reportPath = debugCollector.writeReport();
|
|
3452
|
-
console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
|
|
3453
|
-
}
|
|
3454
|
-
const distDir = path10.join(rootDir, "dist");
|
|
3455
|
-
const indexPath = path10.join(distDir, "index.html");
|
|
3456
|
-
const notFoundPath = path10.join(distDir, "404.html");
|
|
3457
|
-
if (existsSync9(indexPath)) {
|
|
3458
|
-
copyFileSync(indexPath, notFoundPath);
|
|
3459
|
-
}
|
|
3460
3829
|
console.log();
|
|
3461
3830
|
console.log(" Done! Your site is ready in ./dist");
|
|
3462
3831
|
console.log(" You can deploy this folder anywhere.");
|
|
@@ -3464,31 +3833,18 @@ async function buildSite(rootDir, options = {}) {
|
|
|
3464
3833
|
}
|
|
3465
3834
|
async function previewSite(rootDir, options = {}) {
|
|
3466
3835
|
const port = options.port ?? await getRandomPort();
|
|
3467
|
-
const
|
|
3468
|
-
rootDir,
|
|
3469
|
-
mode: "production",
|
|
3470
|
-
port,
|
|
3471
|
-
include: options.include,
|
|
3472
|
-
debug: options.debug
|
|
3473
|
-
});
|
|
3474
|
-
const server = await preview(config);
|
|
3475
|
-
const debugCollector = getDebugCollector();
|
|
3476
|
-
if (debugCollector) {
|
|
3477
|
-
debugCollector.startPhase("previewReady");
|
|
3478
|
-
const reportPath = debugCollector.writeReport();
|
|
3479
|
-
console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
|
|
3480
|
-
}
|
|
3836
|
+
const { url, stop } = await startPreviewServer({ rootDir, port });
|
|
3481
3837
|
printWelcome("preview");
|
|
3482
|
-
|
|
3838
|
+
console.log(` \u279C Local: ${url}`);
|
|
3483
3839
|
console.log();
|
|
3484
3840
|
console.log(" Press Ctrl+C to stop.");
|
|
3485
3841
|
console.log();
|
|
3486
|
-
return
|
|
3842
|
+
return { url, stop };
|
|
3487
3843
|
}
|
|
3488
3844
|
|
|
3489
3845
|
// src/validators/index.ts
|
|
3490
|
-
import { existsSync as
|
|
3491
|
-
import { join as
|
|
3846
|
+
import { existsSync as existsSync17, readdirSync, readFileSync as readFileSync12 } from "fs";
|
|
3847
|
+
import { join as join4 } from "path";
|
|
3492
3848
|
import * as yaml3 from "js-yaml";
|
|
3493
3849
|
|
|
3494
3850
|
// src/renderers/registry.ts
|
|
@@ -3523,10 +3879,10 @@ async function initializeAdapters() {
|
|
|
3523
3879
|
// src/validators/schema-validator.ts
|
|
3524
3880
|
import Ajv from "ajv";
|
|
3525
3881
|
import addFormats from "ajv-formats";
|
|
3526
|
-
import { readFileSync as
|
|
3527
|
-
import { join as
|
|
3882
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
3883
|
+
import { join as join3, dirname as dirname3 } from "path";
|
|
3528
3884
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3529
|
-
var
|
|
3885
|
+
var __dirname2 = dirname3(fileURLToPath4(import.meta.url));
|
|
3530
3886
|
var ajv = new Ajv({
|
|
3531
3887
|
allErrors: true,
|
|
3532
3888
|
strict: false,
|
|
@@ -3538,8 +3894,8 @@ function loadSchema(name) {
|
|
|
3538
3894
|
if (schemaCache.has(name)) {
|
|
3539
3895
|
return schemaCache.get(name);
|
|
3540
3896
|
}
|
|
3541
|
-
const schemaPath =
|
|
3542
|
-
const schemaContent =
|
|
3897
|
+
const schemaPath = join3(__dirname2, "..", "schemas", `${name}.schema.json`);
|
|
3898
|
+
const schemaContent = readFileSync11(schemaPath, "utf-8");
|
|
3543
3899
|
const schema = JSON.parse(schemaContent);
|
|
3544
3900
|
const validate = ajv.compile(schema);
|
|
3545
3901
|
schemaCache.set(name, validate);
|
|
@@ -3641,7 +3997,7 @@ function validateAllLayouts(layoutByRenderer, targetRenderer) {
|
|
|
3641
3997
|
init_primitives();
|
|
3642
3998
|
function parseRef(ref) {
|
|
3643
3999
|
const refStr = typeof ref === "string" ? ref : ref.ref;
|
|
3644
|
-
const match = refStr.match(/^(screens|components|flows
|
|
4000
|
+
const match = refStr.match(/^(screens|components|flows)\/([a-z0-9-]+)$/);
|
|
3645
4001
|
if (!match)
|
|
3646
4002
|
return null;
|
|
3647
4003
|
return { type: match[1], id: match[2] };
|
|
@@ -3661,22 +4017,22 @@ function detectCycle(edges, nodeIds) {
|
|
|
3661
4017
|
}
|
|
3662
4018
|
const visited = new Set;
|
|
3663
4019
|
const recursionStack = new Set;
|
|
3664
|
-
const
|
|
4020
|
+
const path16 = [];
|
|
3665
4021
|
function dfs(node) {
|
|
3666
4022
|
visited.add(node);
|
|
3667
4023
|
recursionStack.add(node);
|
|
3668
|
-
|
|
4024
|
+
path16.push(node);
|
|
3669
4025
|
for (const neighbor of adjacency.get(node) || []) {
|
|
3670
4026
|
if (!visited.has(neighbor)) {
|
|
3671
4027
|
const cycle = dfs(neighbor);
|
|
3672
4028
|
if (cycle)
|
|
3673
4029
|
return cycle;
|
|
3674
4030
|
} else if (recursionStack.has(neighbor)) {
|
|
3675
|
-
const cycleStart =
|
|
3676
|
-
return [...
|
|
4031
|
+
const cycleStart = path16.indexOf(neighbor);
|
|
4032
|
+
return [...path16.slice(cycleStart), neighbor];
|
|
3677
4033
|
}
|
|
3678
4034
|
}
|
|
3679
|
-
|
|
4035
|
+
path16.pop();
|
|
3680
4036
|
recursionStack.delete(node);
|
|
3681
4037
|
return null;
|
|
3682
4038
|
}
|
|
@@ -3689,13 +4045,13 @@ function detectCycle(edges, nodeIds) {
|
|
|
3689
4045
|
}
|
|
3690
4046
|
return null;
|
|
3691
4047
|
}
|
|
3692
|
-
function validateRef(ref, context,
|
|
4048
|
+
function validateRef(ref, context, path16) {
|
|
3693
4049
|
const errors = [];
|
|
3694
4050
|
const warnings = [];
|
|
3695
4051
|
const parsed = parseRef(ref);
|
|
3696
4052
|
if (!parsed) {
|
|
3697
4053
|
errors.push({
|
|
3698
|
-
path:
|
|
4054
|
+
path: path16,
|
|
3699
4055
|
message: `Invalid reference format: ${JSON.stringify(ref)}`,
|
|
3700
4056
|
code: "INVALID_REF"
|
|
3701
4057
|
});
|
|
@@ -3704,7 +4060,7 @@ function validateRef(ref, context, path11) {
|
|
|
3704
4060
|
const knownSet = context.knownIds[parsed.type];
|
|
3705
4061
|
if (!knownSet?.has(parsed.id)) {
|
|
3706
4062
|
errors.push({
|
|
3707
|
-
path:
|
|
4063
|
+
path: path16,
|
|
3708
4064
|
message: `Reference "${parsed.type}/${parsed.id}" not found`,
|
|
3709
4065
|
code: "INVALID_REF"
|
|
3710
4066
|
});
|
|
@@ -3716,13 +4072,13 @@ function validateRef(ref, context, path11) {
|
|
|
3716
4072
|
const screenStates = context.screenStates.get(parsed.id);
|
|
3717
4073
|
if (!screenStates) {
|
|
3718
4074
|
errors.push({
|
|
3719
|
-
path:
|
|
4075
|
+
path: path16,
|
|
3720
4076
|
message: `State "${state}" referenced but screen "${parsed.id}" has no states defined`,
|
|
3721
4077
|
code: "INVALID_STATE_REF"
|
|
3722
4078
|
});
|
|
3723
4079
|
} else if (!screenStates.has(state)) {
|
|
3724
4080
|
errors.push({
|
|
3725
|
-
path:
|
|
4081
|
+
path: path16,
|
|
3726
4082
|
message: `State "${state}" not found in screen "${parsed.id}". Available states: ${Array.from(screenStates).join(", ") || "none"}`,
|
|
3727
4083
|
code: "INVALID_STATE_REF"
|
|
3728
4084
|
});
|
|
@@ -3773,50 +4129,6 @@ function validateFlow(config, context, configPath) {
|
|
|
3773
4129
|
}
|
|
3774
4130
|
return { errors, warnings };
|
|
3775
4131
|
}
|
|
3776
|
-
function validateAtlas(config, context, configPath) {
|
|
3777
|
-
const errors = [];
|
|
3778
|
-
const warnings = [];
|
|
3779
|
-
if (!config.nodes || config.nodes.length === 0) {
|
|
3780
|
-
return { errors, warnings };
|
|
3781
|
-
}
|
|
3782
|
-
const nodeIds = new Set(config.nodes.map((n) => n.id));
|
|
3783
|
-
for (let i = 0;i < config.nodes.length; i++) {
|
|
3784
|
-
const node = config.nodes[i];
|
|
3785
|
-
if (node.ref) {
|
|
3786
|
-
const result = validateRef(node.ref, context, `${configPath}/nodes/${i}/ref`);
|
|
3787
|
-
errors.push(...result.errors);
|
|
3788
|
-
warnings.push(...result.warnings);
|
|
3789
|
-
}
|
|
3790
|
-
}
|
|
3791
|
-
if (config.relationships) {
|
|
3792
|
-
for (let i = 0;i < config.relationships.length; i++) {
|
|
3793
|
-
const rel = config.relationships[i];
|
|
3794
|
-
if (!nodeIds.has(rel.from)) {
|
|
3795
|
-
errors.push({
|
|
3796
|
-
path: `${configPath}/relationships/${i}/from`,
|
|
3797
|
-
message: `Relationship "from" references unknown node "${rel.from}". Available nodes: ${Array.from(nodeIds).join(", ")}`,
|
|
3798
|
-
code: "INVALID_NODE_REF"
|
|
3799
|
-
});
|
|
3800
|
-
}
|
|
3801
|
-
if (!nodeIds.has(rel.to)) {
|
|
3802
|
-
errors.push({
|
|
3803
|
-
path: `${configPath}/relationships/${i}/to`,
|
|
3804
|
-
message: `Relationship "to" references unknown node "${rel.to}". Available nodes: ${Array.from(nodeIds).join(", ")}`,
|
|
3805
|
-
code: "INVALID_NODE_REF"
|
|
3806
|
-
});
|
|
3807
|
-
}
|
|
3808
|
-
}
|
|
3809
|
-
const cycle = detectCycle(config.relationships, nodeIds);
|
|
3810
|
-
if (cycle) {
|
|
3811
|
-
errors.push({
|
|
3812
|
-
path: `${configPath}/relationships`,
|
|
3813
|
-
message: `Circular dependency detected in atlas relationships: ${cycle.join(" \u2192 ")}`,
|
|
3814
|
-
code: "CIRCULAR_DEPENDENCY"
|
|
3815
|
-
});
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
return { errors, warnings };
|
|
3819
|
-
}
|
|
3820
4132
|
function validateScreenTemplate(template, slots, states, context, configPath) {
|
|
3821
4133
|
const errors = [];
|
|
3822
4134
|
const warnings = [];
|
|
@@ -3912,25 +4224,25 @@ function validateRendererKeys(layoutByRenderer, configPath) {
|
|
|
3912
4224
|
}
|
|
3913
4225
|
return { errors, warnings };
|
|
3914
4226
|
}
|
|
3915
|
-
function extractComponentRefs(node,
|
|
4227
|
+
function extractComponentRefs(node, path16, refs) {
|
|
3916
4228
|
if (!node || typeof node !== "object")
|
|
3917
4229
|
return;
|
|
3918
4230
|
if (Array.isArray(node)) {
|
|
3919
4231
|
node.forEach((item, index) => {
|
|
3920
|
-
extractComponentRefs(item, `${
|
|
4232
|
+
extractComponentRefs(item, `${path16}/${index}`, refs);
|
|
3921
4233
|
});
|
|
3922
4234
|
return;
|
|
3923
4235
|
}
|
|
3924
4236
|
const obj = node;
|
|
3925
4237
|
if (obj.type === "ComponentRef" && typeof obj.ref === "string") {
|
|
3926
|
-
refs.push({ ref: obj.ref, path: `${
|
|
4238
|
+
refs.push({ ref: obj.ref, path: `${path16}/ref` });
|
|
3927
4239
|
}
|
|
3928
4240
|
if (obj.children) {
|
|
3929
|
-
extractComponentRefs(obj.children, `${
|
|
4241
|
+
extractComponentRefs(obj.children, `${path16}/children`, refs);
|
|
3930
4242
|
}
|
|
3931
4243
|
if (obj.props && typeof obj.props === "object") {
|
|
3932
4244
|
for (const [key, value] of Object.entries(obj.props)) {
|
|
3933
|
-
extractComponentRefs(value, `${
|
|
4245
|
+
extractComponentRefs(value, `${path16}/props/${key}`, refs);
|
|
3934
4246
|
}
|
|
3935
4247
|
}
|
|
3936
4248
|
}
|
|
@@ -3943,11 +4255,11 @@ function validateLayoutComponentRefs(layoutByRenderer, context, configPath) {
|
|
|
3943
4255
|
for (const [rendererKey, layout] of Object.entries(layoutByRenderer)) {
|
|
3944
4256
|
const refs = [];
|
|
3945
4257
|
extractComponentRefs(layout, `${configPath}/layoutByRenderer/${rendererKey}`, refs);
|
|
3946
|
-
for (const { ref, path:
|
|
4258
|
+
for (const { ref, path: path16 } of refs) {
|
|
3947
4259
|
const match = ref.match(/^components\/([a-z0-9-]+)$/);
|
|
3948
4260
|
if (!match) {
|
|
3949
4261
|
errors.push({
|
|
3950
|
-
path:
|
|
4262
|
+
path: path16,
|
|
3951
4263
|
message: `Invalid ComponentRef format: "${ref}". Expected "components/<id>"`,
|
|
3952
4264
|
code: "INVALID_REF"
|
|
3953
4265
|
});
|
|
@@ -3956,7 +4268,7 @@ function validateLayoutComponentRefs(layoutByRenderer, context, configPath) {
|
|
|
3956
4268
|
const componentId = match[1];
|
|
3957
4269
|
if (!context.knownIds.components.has(componentId)) {
|
|
3958
4270
|
errors.push({
|
|
3959
|
-
path:
|
|
4271
|
+
path: path16,
|
|
3960
4272
|
message: `ComponentRef references unknown component "${componentId}"`,
|
|
3961
4273
|
code: "INVALID_REF"
|
|
3962
4274
|
});
|
|
@@ -4004,11 +4316,6 @@ function validateSemantics(config, context, configPath = "") {
|
|
|
4004
4316
|
errors.push(...result.errors);
|
|
4005
4317
|
warnings.push(...result.warnings);
|
|
4006
4318
|
}
|
|
4007
|
-
if (config.kind === "atlas") {
|
|
4008
|
-
const result = validateAtlas(config, context, configPath);
|
|
4009
|
-
errors.push(...result.errors);
|
|
4010
|
-
warnings.push(...result.warnings);
|
|
4011
|
-
}
|
|
4012
4319
|
return {
|
|
4013
4320
|
valid: errors.length === 0,
|
|
4014
4321
|
errors,
|
|
@@ -4021,8 +4328,7 @@ function createValidationContext(rootDir) {
|
|
|
4021
4328
|
knownIds: {
|
|
4022
4329
|
components: new Set,
|
|
4023
4330
|
screens: new Set,
|
|
4024
|
-
flows: new Set
|
|
4025
|
-
atlas: new Set
|
|
4331
|
+
flows: new Set
|
|
4026
4332
|
},
|
|
4027
4333
|
screenStates: new Map
|
|
4028
4334
|
};
|
|
@@ -4051,25 +4357,25 @@ function checkDuplicateIds(ids, type) {
|
|
|
4051
4357
|
}
|
|
4052
4358
|
|
|
4053
4359
|
// src/validators/index.ts
|
|
4054
|
-
var PREVIEW_TYPES = ["components", "screens", "flows"
|
|
4360
|
+
var PREVIEW_TYPES = ["components", "screens", "flows"];
|
|
4055
4361
|
function findPreviewRoot(startDir) {
|
|
4056
4362
|
let current = startDir;
|
|
4057
4363
|
while (current !== "/") {
|
|
4058
|
-
const previewsDir =
|
|
4059
|
-
if (
|
|
4364
|
+
const previewsDir = join4(current, ".previews");
|
|
4365
|
+
if (existsSync17(previewsDir)) {
|
|
4060
4366
|
return previewsDir;
|
|
4061
4367
|
}
|
|
4062
|
-
const previewsDirAlt =
|
|
4063
|
-
if (
|
|
4368
|
+
const previewsDirAlt = join4(current, "previews");
|
|
4369
|
+
if (existsSync17(previewsDirAlt)) {
|
|
4064
4370
|
return previewsDirAlt;
|
|
4065
4371
|
}
|
|
4066
|
-
current =
|
|
4372
|
+
current = join4(current, "..");
|
|
4067
4373
|
}
|
|
4068
4374
|
return null;
|
|
4069
4375
|
}
|
|
4070
4376
|
function scanPreviewType(previewRoot, type) {
|
|
4071
|
-
const typeDir =
|
|
4072
|
-
if (!
|
|
4377
|
+
const typeDir = join4(previewRoot, type);
|
|
4378
|
+
if (!existsSync17(typeDir)) {
|
|
4073
4379
|
return [];
|
|
4074
4380
|
}
|
|
4075
4381
|
const units = [];
|
|
@@ -4077,10 +4383,10 @@ function scanPreviewType(previewRoot, type) {
|
|
|
4077
4383
|
for (const entry of entries) {
|
|
4078
4384
|
if (!entry.isDirectory())
|
|
4079
4385
|
continue;
|
|
4080
|
-
const unitPath =
|
|
4081
|
-
const configYaml =
|
|
4082
|
-
const configYml =
|
|
4083
|
-
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;
|
|
4084
4390
|
if (configPath) {
|
|
4085
4391
|
units.push({
|
|
4086
4392
|
id: entry.name,
|
|
@@ -4093,7 +4399,7 @@ function scanPreviewType(previewRoot, type) {
|
|
|
4093
4399
|
}
|
|
4094
4400
|
function loadConfig2(configPath) {
|
|
4095
4401
|
try {
|
|
4096
|
-
const content =
|
|
4402
|
+
const content = readFileSync12(configPath, "utf-8");
|
|
4097
4403
|
const parsed = yaml3.load(content);
|
|
4098
4404
|
if (parsed && typeof parsed === "object") {
|
|
4099
4405
|
return parsed;
|
|
@@ -4144,8 +4450,7 @@ async function validate(rootDir = process.cwd(), options = {}) {
|
|
|
4144
4450
|
summary: {
|
|
4145
4451
|
components: { total: 0, valid: 0, invalid: 0 },
|
|
4146
4452
|
screens: { total: 0, valid: 0, invalid: 0 },
|
|
4147
|
-
flows: { total: 0, valid: 0, invalid: 0 }
|
|
4148
|
-
atlas: { total: 0, valid: 0, invalid: 0 }
|
|
4453
|
+
flows: { total: 0, valid: 0, invalid: 0 }
|
|
4149
4454
|
},
|
|
4150
4455
|
errors: [{
|
|
4151
4456
|
file: rootDir,
|
|
@@ -4162,8 +4467,7 @@ async function validate(rootDir = process.cwd(), options = {}) {
|
|
|
4162
4467
|
summary: {
|
|
4163
4468
|
components: { total: 0, valid: 0, invalid: 0 },
|
|
4164
4469
|
screens: { total: 0, valid: 0, invalid: 0 },
|
|
4165
|
-
flows: { total: 0, valid: 0, invalid: 0 }
|
|
4166
|
-
atlas: { total: 0, valid: 0, invalid: 0 }
|
|
4470
|
+
flows: { total: 0, valid: 0, invalid: 0 }
|
|
4167
4471
|
},
|
|
4168
4472
|
errors: [],
|
|
4169
4473
|
warnings: []
|
|
@@ -4295,7 +4599,7 @@ function formatValidationResult(result) {
|
|
|
4295
4599
|
}
|
|
4296
4600
|
|
|
4297
4601
|
// src/typecheck/index.ts
|
|
4298
|
-
import
|
|
4602
|
+
import path16 from "path";
|
|
4299
4603
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
4300
4604
|
import { spawn } from "child_process";
|
|
4301
4605
|
function getTsgoPath() {
|
|
@@ -4304,9 +4608,9 @@ function getTsgoPath() {
|
|
|
4304
4608
|
const platformPkg = `@typescript/native-preview-${platform}-${arch}`;
|
|
4305
4609
|
try {
|
|
4306
4610
|
const pkgJsonUrl = import.meta.resolve(`${platformPkg}/package.json`);
|
|
4307
|
-
const pkgDir =
|
|
4611
|
+
const pkgDir = path16.dirname(fileURLToPath5(pkgJsonUrl));
|
|
4308
4612
|
const exe = platform === "win32" ? "tsgo.exe" : "tsgo";
|
|
4309
|
-
return
|
|
4613
|
+
return path16.join(pkgDir, "lib", exe);
|
|
4310
4614
|
} catch {
|
|
4311
4615
|
throw new Error(`Unable to find tsgo for ${platform}-${arch}. ` + `Package ${platformPkg} may not be installed or your platform is unsupported.`);
|
|
4312
4616
|
}
|
|
@@ -4314,15 +4618,15 @@ function getTsgoPath() {
|
|
|
4314
4618
|
function getTypeRootsPath() {
|
|
4315
4619
|
try {
|
|
4316
4620
|
const reactTypesUrl = import.meta.resolve("@types/react/package.json");
|
|
4317
|
-
const reactTypesDir =
|
|
4318
|
-
return
|
|
4621
|
+
const reactTypesDir = path16.dirname(fileURLToPath5(reactTypesUrl));
|
|
4622
|
+
return path16.dirname(reactTypesDir);
|
|
4319
4623
|
} catch {
|
|
4320
4624
|
throw new Error("Unable to find @types/react. Make sure prev-cli has @types/react in dependencies.");
|
|
4321
4625
|
}
|
|
4322
4626
|
}
|
|
4323
4627
|
async function typecheck(rootDir, options = {}) {
|
|
4324
4628
|
const {
|
|
4325
|
-
previewsDir =
|
|
4629
|
+
previewsDir = path16.join(rootDir, "previews"),
|
|
4326
4630
|
include = ["**/*.{ts,tsx}"],
|
|
4327
4631
|
strict = true,
|
|
4328
4632
|
verbose = false
|
|
@@ -4351,7 +4655,7 @@ async function typecheck(rootDir, options = {}) {
|
|
|
4351
4655
|
}
|
|
4352
4656
|
if (verbose) {
|
|
4353
4657
|
console.log(` files: ${files.length}`);
|
|
4354
|
-
files.forEach((f) => console.log(` - ${
|
|
4658
|
+
files.forEach((f) => console.log(` - ${path16.relative(rootDir, f)}`));
|
|
4355
4659
|
}
|
|
4356
4660
|
const args = [
|
|
4357
4661
|
"--noEmit",
|
|
@@ -4432,8 +4736,8 @@ ${result.output}
|
|
|
4432
4736
|
}
|
|
4433
4737
|
|
|
4434
4738
|
// src/migrate.ts
|
|
4435
|
-
import { existsSync as
|
|
4436
|
-
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";
|
|
4437
4741
|
import * as yaml4 from "js-yaml";
|
|
4438
4742
|
var PREVIEW_TYPE_FOLDERS2 = ["components", "screens", "flows", "atlas"];
|
|
4439
4743
|
var KIND_MAP = {
|
|
@@ -4445,21 +4749,21 @@ var KIND_MAP = {
|
|
|
4445
4749
|
function findPreviewRoot2(startDir) {
|
|
4446
4750
|
let current = startDir;
|
|
4447
4751
|
while (current !== "/") {
|
|
4448
|
-
const previewsDir =
|
|
4449
|
-
if (
|
|
4752
|
+
const previewsDir = join5(current, ".previews");
|
|
4753
|
+
if (existsSync18(previewsDir)) {
|
|
4450
4754
|
return previewsDir;
|
|
4451
4755
|
}
|
|
4452
|
-
const previewsDirAlt =
|
|
4453
|
-
if (
|
|
4756
|
+
const previewsDirAlt = join5(current, "previews");
|
|
4757
|
+
if (existsSync18(previewsDirAlt)) {
|
|
4454
4758
|
return previewsDirAlt;
|
|
4455
4759
|
}
|
|
4456
|
-
current =
|
|
4760
|
+
current = join5(current, "..");
|
|
4457
4761
|
}
|
|
4458
4762
|
return null;
|
|
4459
4763
|
}
|
|
4460
4764
|
function migrateConfig(configPath, folderId, kind) {
|
|
4461
4765
|
try {
|
|
4462
|
-
const content =
|
|
4766
|
+
const content = readFileSync13(configPath, "utf-8");
|
|
4463
4767
|
const config = yaml4.load(content);
|
|
4464
4768
|
if (!config || typeof config !== "object") {
|
|
4465
4769
|
return { migrated: false, error: "Invalid YAML" };
|
|
@@ -4519,18 +4823,18 @@ async function migrateConfigs(rootDir = process.cwd()) {
|
|
|
4519
4823
|
errors: []
|
|
4520
4824
|
};
|
|
4521
4825
|
for (const type of PREVIEW_TYPE_FOLDERS2) {
|
|
4522
|
-
const typeDir =
|
|
4523
|
-
if (!
|
|
4826
|
+
const typeDir = join5(previewRoot, type);
|
|
4827
|
+
if (!existsSync18(typeDir))
|
|
4524
4828
|
continue;
|
|
4525
4829
|
const kind = KIND_MAP[type];
|
|
4526
4830
|
const entries = readdirSync2(typeDir, { withFileTypes: true });
|
|
4527
4831
|
for (const entry of entries) {
|
|
4528
4832
|
if (!entry.isDirectory())
|
|
4529
4833
|
continue;
|
|
4530
|
-
const unitPath =
|
|
4531
|
-
const configYaml =
|
|
4532
|
-
const configYml =
|
|
4533
|
-
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;
|
|
4534
4838
|
if (!configPath) {
|
|
4535
4839
|
result.skipped++;
|
|
4536
4840
|
continue;
|
|
@@ -4578,14 +4882,14 @@ import { createHash } from "crypto";
|
|
|
4578
4882
|
import { readdir, rm, stat, mkdir } from "fs/promises";
|
|
4579
4883
|
import { exec as exec2 } from "child_process";
|
|
4580
4884
|
import { promisify } from "util";
|
|
4581
|
-
import
|
|
4582
|
-
import
|
|
4885
|
+
import path17 from "path";
|
|
4886
|
+
import os from "os";
|
|
4583
4887
|
var execAsync = promisify(exec2);
|
|
4584
|
-
var DEFAULT_CACHE_ROOT =
|
|
4888
|
+
var DEFAULT_CACHE_ROOT = path17.join(os.homedir(), ".cache/prev");
|
|
4585
4889
|
async function getCacheDir(rootDir, branch) {
|
|
4586
4890
|
const resolvedBranch = branch ?? await getCurrentBranch(rootDir);
|
|
4587
4891
|
const hash = createHash("sha1").update(`${rootDir}:${resolvedBranch}`).digest("hex").slice(0, 12);
|
|
4588
|
-
return
|
|
4892
|
+
return path17.join(DEFAULT_CACHE_ROOT, hash);
|
|
4589
4893
|
}
|
|
4590
4894
|
async function getCurrentBranch(rootDir) {
|
|
4591
4895
|
try {
|
|
@@ -4607,7 +4911,7 @@ async function cleanCache(options) {
|
|
|
4607
4911
|
}
|
|
4608
4912
|
let removed = 0;
|
|
4609
4913
|
for (const dir of dirs) {
|
|
4610
|
-
const fullPath =
|
|
4914
|
+
const fullPath = path17.join(cacheRoot, dir);
|
|
4611
4915
|
try {
|
|
4612
4916
|
const info = await stat(fullPath);
|
|
4613
4917
|
if (info.isDirectory() && now - info.mtimeMs > maxAge) {
|
|
@@ -4623,15 +4927,15 @@ async function cleanCache(options) {
|
|
|
4623
4927
|
import yaml5 from "js-yaml";
|
|
4624
4928
|
function getVersion() {
|
|
4625
4929
|
try {
|
|
4626
|
-
let dir =
|
|
4930
|
+
let dir = path18.dirname(fileURLToPath6(import.meta.url));
|
|
4627
4931
|
for (let i = 0;i < 5; i++) {
|
|
4628
|
-
const pkgPath =
|
|
4629
|
-
if (
|
|
4630
|
-
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"));
|
|
4631
4935
|
if (pkg.name === "prev-cli")
|
|
4632
4936
|
return pkg.version;
|
|
4633
4937
|
}
|
|
4634
|
-
dir =
|
|
4938
|
+
dir = path18.dirname(dir);
|
|
4635
4939
|
}
|
|
4636
4940
|
} catch {}
|
|
4637
4941
|
return "unknown";
|
|
@@ -4651,7 +4955,7 @@ var { values, positionals } = parseArgs({
|
|
|
4651
4955
|
allowPositionals: true
|
|
4652
4956
|
});
|
|
4653
4957
|
var command = positionals[0] || "dev";
|
|
4654
|
-
var rootDir =
|
|
4958
|
+
var rootDir = path18.resolve(values.cwd || (command === "config" || command === "create" ? "." : positionals[1]) || ".");
|
|
4655
4959
|
function printHelp() {
|
|
4656
4960
|
console.log(`
|
|
4657
4961
|
prev - Zero-config documentation site generator
|
|
@@ -4665,7 +4969,7 @@ Usage:
|
|
|
4665
4969
|
prev migrate Migrate configs from v1 to v2 format
|
|
4666
4970
|
prev create [name] Create preview in previews/<name>/ (default: "example")
|
|
4667
4971
|
prev config [subcommand] Manage configuration
|
|
4668
|
-
prev clearcache Clear
|
|
4972
|
+
prev clearcache Clear build cache
|
|
4669
4973
|
prev clean [options] Remove old prev-cli caches
|
|
4670
4974
|
|
|
4671
4975
|
Config subcommands:
|
|
@@ -4769,30 +5073,18 @@ Examples:
|
|
|
4769
5073
|
prev clean -d 7 Remove caches older than 7 days
|
|
4770
5074
|
`);
|
|
4771
5075
|
}
|
|
4772
|
-
async function
|
|
5076
|
+
async function clearBuildCache(rootDir2) {
|
|
4773
5077
|
let cleared = 0;
|
|
4774
5078
|
try {
|
|
4775
5079
|
const prevCacheDir = await getCacheDir(rootDir2);
|
|
4776
|
-
if (
|
|
5080
|
+
if (existsSync19(prevCacheDir)) {
|
|
4777
5081
|
rmSync4(prevCacheDir, { recursive: true });
|
|
4778
5082
|
cleared++;
|
|
4779
5083
|
console.log(` \u2713 Removed ${prevCacheDir}`);
|
|
4780
5084
|
}
|
|
4781
5085
|
} catch {}
|
|
4782
|
-
const viteCacheDir = path13.join(rootDir2, ".vite");
|
|
4783
|
-
const nodeModulesVite = path13.join(rootDir2, "node_modules", ".vite");
|
|
4784
|
-
if (existsSync12(viteCacheDir)) {
|
|
4785
|
-
rmSync4(viteCacheDir, { recursive: true });
|
|
4786
|
-
cleared++;
|
|
4787
|
-
console.log(` \u2713 Removed .vite/`);
|
|
4788
|
-
}
|
|
4789
|
-
if (existsSync12(nodeModulesVite)) {
|
|
4790
|
-
rmSync4(nodeModulesVite, { recursive: true });
|
|
4791
|
-
cleared++;
|
|
4792
|
-
console.log(` \u2713 Removed node_modules/.vite/`);
|
|
4793
|
-
}
|
|
4794
5086
|
if (cleared === 0) {
|
|
4795
|
-
console.log(" No
|
|
5087
|
+
console.log(" No build cache found");
|
|
4796
5088
|
} else {
|
|
4797
5089
|
console.log(`
|
|
4798
5090
|
Cleared ${cleared} cache director${cleared === 1 ? "y" : "ies"}`);
|
|
@@ -4832,7 +5124,7 @@ function handleConfig(rootDir2, subcommand) {
|
|
|
4832
5124
|
break;
|
|
4833
5125
|
}
|
|
4834
5126
|
case "init": {
|
|
4835
|
-
const targetPath =
|
|
5127
|
+
const targetPath = path18.join(rootDir2, ".prev.yaml");
|
|
4836
5128
|
if (configPath) {
|
|
4837
5129
|
console.log(`
|
|
4838
5130
|
Config already exists: ${configPath}
|
|
@@ -4891,8 +5183,8 @@ Available subcommands: show, init, path`);
|
|
|
4891
5183
|
}
|
|
4892
5184
|
}
|
|
4893
5185
|
function createPreview(rootDir2, name) {
|
|
4894
|
-
const previewDir =
|
|
4895
|
-
if (
|
|
5186
|
+
const previewDir = path18.join(rootDir2, "previews", name);
|
|
5187
|
+
if (existsSync19(previewDir)) {
|
|
4896
5188
|
console.error(`Preview "${name}" already exists at: ${previewDir}`);
|
|
4897
5189
|
process.exit(1);
|
|
4898
5190
|
}
|
|
@@ -5017,8 +5309,8 @@ export default function App() {
|
|
|
5017
5309
|
.dark\\:text-white { color: #fff; }
|
|
5018
5310
|
}
|
|
5019
5311
|
`;
|
|
5020
|
-
writeFileSync7(
|
|
5021
|
-
writeFileSync7(
|
|
5312
|
+
writeFileSync7(path18.join(previewDir, "App.tsx"), appTsx);
|
|
5313
|
+
writeFileSync7(path18.join(previewDir, "styles.css"), stylesCss);
|
|
5022
5314
|
console.log(`
|
|
5023
5315
|
\u2728 Created preview: previews/${name}/
|
|
5024
5316
|
|
|
@@ -5067,7 +5359,7 @@ async function main() {
|
|
|
5067
5359
|
createPreview(rootDir, previewName);
|
|
5068
5360
|
break;
|
|
5069
5361
|
case "clearcache":
|
|
5070
|
-
await
|
|
5362
|
+
await clearBuildCache(rootDir);
|
|
5071
5363
|
break;
|
|
5072
5364
|
case "config":
|
|
5073
5365
|
handleConfig(rootDir, positionals[1]);
|