prev-cli 0.24.19 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/cli.js +2006 -1703
  2. package/dist/previews/components/cart-item/index.d.ts +5 -0
  3. package/dist/previews/components/price-tag/index.d.ts +6 -0
  4. package/dist/previews/screens/cart/empty.d.ts +1 -0
  5. package/dist/previews/screens/cart/index.d.ts +1 -0
  6. package/dist/previews/screens/payment/error.d.ts +1 -0
  7. package/dist/previews/screens/payment/index.d.ts +1 -0
  8. package/dist/previews/screens/payment/processing.d.ts +1 -0
  9. package/dist/previews/screens/receipt/index.d.ts +1 -0
  10. package/dist/previews/shared/data.d.ts +30 -0
  11. package/dist/src/content/config-parser.d.ts +30 -0
  12. package/dist/src/content/flow-verifier.d.ts +21 -0
  13. package/dist/src/content/preview-types.d.ts +288 -0
  14. package/dist/{vite → src/content}/previews.d.ts +3 -11
  15. package/dist/{preview-runtime → src/preview-runtime}/build-optimized.d.ts +2 -0
  16. package/dist/{preview-runtime → src/preview-runtime}/build.d.ts +1 -1
  17. package/dist/src/preview-runtime/region-bridge.d.ts +1 -0
  18. package/dist/{preview-runtime → src/preview-runtime}/types.d.ts +18 -0
  19. package/dist/src/preview-runtime/vendors.d.ts +11 -0
  20. package/dist/{renderers → src/renderers}/index.d.ts +1 -1
  21. package/dist/{renderers → src/renderers}/types.d.ts +3 -31
  22. package/dist/src/server/build.d.ts +6 -0
  23. package/dist/src/server/dev.d.ts +13 -0
  24. package/dist/src/server/plugins/aliases.d.ts +5 -0
  25. package/dist/src/server/plugins/mdx.d.ts +5 -0
  26. package/dist/src/server/plugins/virtual-modules.d.ts +8 -0
  27. package/dist/src/server/preview.d.ts +10 -0
  28. package/dist/src/server/routes/component-bundle.d.ts +1 -0
  29. package/dist/src/server/routes/jsx-bundle.d.ts +3 -0
  30. package/dist/src/server/routes/og-image.d.ts +15 -0
  31. package/dist/src/server/routes/preview-bundle.d.ts +1 -0
  32. package/dist/src/server/routes/preview-config.d.ts +1 -0
  33. package/dist/src/server/routes/tokens.d.ts +1 -0
  34. package/dist/{vite → src/server}/start.d.ts +5 -2
  35. package/dist/{ui → src/ui}/button.d.ts +1 -1
  36. package/dist/{validators → src/validators}/index.d.ts +0 -5
  37. package/dist/{validators → src/validators}/semantic-validator.d.ts +2 -3
  38. package/package.json +8 -11
  39. package/src/jsx/CLAUDE.md +18 -0
  40. package/src/jsx/jsx-runtime.ts +1 -1
  41. package/src/preview-runtime/CLAUDE.md +21 -0
  42. package/src/preview-runtime/build-optimized.ts +189 -73
  43. package/src/preview-runtime/build.ts +75 -79
  44. package/src/preview-runtime/fast-template.html +5 -1
  45. package/src/preview-runtime/region-bridge.test.ts +41 -0
  46. package/src/preview-runtime/region-bridge.ts +101 -0
  47. package/src/preview-runtime/types.ts +6 -0
  48. package/src/preview-runtime/vendors.ts +215 -22
  49. package/src/primitives/CLAUDE.md +17 -0
  50. package/src/theme/CLAUDE.md +20 -0
  51. package/src/theme/Preview.tsx +10 -4
  52. package/src/theme/Toolbar.tsx +2 -2
  53. package/src/theme/entry.tsx +247 -121
  54. package/src/theme/hooks/useAnnotations.ts +77 -0
  55. package/src/theme/hooks/useApprovalStatus.ts +50 -0
  56. package/src/theme/hooks/useSnapshots.ts +147 -0
  57. package/src/theme/hooks/useStorage.ts +26 -0
  58. package/src/theme/hooks/useTokenOverrides.ts +56 -0
  59. package/src/theme/hooks/useViewport.ts +23 -0
  60. package/src/theme/icons.tsx +39 -1
  61. package/src/theme/index.html +18 -0
  62. package/src/theme/mdx-components.tsx +1 -1
  63. package/src/theme/previews/AnnotationLayer.tsx +285 -0
  64. package/src/theme/previews/AnnotationPin.tsx +61 -0
  65. package/src/theme/previews/AnnotationThread.tsx +257 -0
  66. package/src/theme/previews/CLAUDE.md +18 -0
  67. package/src/theme/previews/ComponentPreview.tsx +487 -107
  68. package/src/theme/previews/FlowDiagram.tsx +111 -0
  69. package/src/theme/previews/FlowPreview.tsx +938 -174
  70. package/src/theme/previews/PreviewRouter.tsx +1 -4
  71. package/src/theme/previews/ScreenPreview.tsx +515 -175
  72. package/src/theme/previews/SnapshotButton.tsx +68 -0
  73. package/src/theme/previews/SnapshotCompare.tsx +216 -0
  74. package/src/theme/previews/SnapshotPanel.tsx +274 -0
  75. package/src/theme/previews/StatusBadge.tsx +66 -0
  76. package/src/theme/previews/StatusDropdown.tsx +158 -0
  77. package/src/theme/previews/TokenPlayground.tsx +438 -0
  78. package/src/theme/previews/ViewportControls.tsx +67 -0
  79. package/src/theme/previews/flow-diagram.test.ts +141 -0
  80. package/src/theme/previews/flow-diagram.ts +109 -0
  81. package/src/theme/previews/flow-navigation.test.ts +90 -0
  82. package/src/theme/previews/flow-navigation.ts +47 -0
  83. package/src/theme/previews/machines/derived.test.ts +225 -0
  84. package/src/theme/previews/machines/derived.ts +73 -0
  85. package/src/theme/previews/machines/flow-machine.test.ts +379 -0
  86. package/src/theme/previews/machines/flow-machine.ts +207 -0
  87. package/src/theme/previews/machines/screen-machine.test.ts +149 -0
  88. package/src/theme/previews/machines/screen-machine.ts +76 -0
  89. package/src/theme/previews/stores/flow-store.test.ts +157 -0
  90. package/src/theme/previews/stores/flow-store.ts +49 -0
  91. package/src/theme/previews/stores/screen-store.test.ts +68 -0
  92. package/src/theme/previews/stores/screen-store.ts +33 -0
  93. package/src/theme/storage.test.ts +97 -0
  94. package/src/theme/storage.ts +71 -0
  95. package/src/theme/styles.css +296 -25
  96. package/src/theme/types.ts +64 -0
  97. package/src/tokens/CLAUDE.md +16 -0
  98. package/src/tokens/resolver.ts +1 -1
  99. package/dist/preview-runtime/vendors.d.ts +0 -6
  100. package/dist/vite/config-parser.d.ts +0 -13
  101. package/dist/vite/config.d.ts +0 -12
  102. package/dist/vite/plugins/config-plugin.d.ts +0 -3
  103. package/dist/vite/plugins/debug-plugin.d.ts +0 -3
  104. package/dist/vite/plugins/entry-plugin.d.ts +0 -2
  105. package/dist/vite/plugins/fumadocs-plugin.d.ts +0 -9
  106. package/dist/vite/plugins/pages-plugin.d.ts +0 -5
  107. package/dist/vite/plugins/previews-plugin.d.ts +0 -2
  108. package/dist/vite/plugins/tokens-plugin.d.ts +0 -2
  109. package/dist/vite/preview-types.d.ts +0 -70
  110. package/src/theme/previews/AtlasPreview.tsx +0 -528
  111. package/dist/{cli.d.ts → src/cli.d.ts} +0 -0
  112. package/dist/{config → src/config}/index.d.ts +0 -0
  113. package/dist/{config → src/config}/loader.d.ts +0 -0
  114. package/dist/{config → src/config}/schema.d.ts +0 -0
  115. package/dist/{vite → src/content}/pages.d.ts +0 -0
  116. package/dist/{jsx → src/jsx}/adapters/html.d.ts +0 -0
  117. package/dist/{jsx → src/jsx}/adapters/react.d.ts +0 -0
  118. package/dist/{jsx → src/jsx}/define-component.d.ts +0 -0
  119. package/dist/{jsx → src/jsx}/index.d.ts +0 -0
  120. package/dist/{jsx → src/jsx}/jsx-runtime.d.ts +0 -0
  121. package/dist/{jsx → src/jsx}/migrate.d.ts +0 -0
  122. package/dist/{jsx → src/jsx}/schemas/index.d.ts +0 -0
  123. package/dist/{jsx → src/jsx}/schemas/primitives.d.ts +10 -10
  124. package/dist/{jsx → src/jsx}/schemas/tokens.d.ts +3 -3
  125. /package/dist/{jsx → src/jsx}/validation.d.ts +0 -0
  126. /package/dist/{jsx → src/jsx}/vnode.d.ts +0 -0
  127. /package/dist/{migrate.d.ts → src/migrate.d.ts} +0 -0
  128. /package/dist/{preview-runtime → src/preview-runtime}/tailwind.d.ts +0 -0
  129. /package/dist/{primitives → src/primitives}/index.d.ts +0 -0
  130. /package/dist/{primitives → src/primitives}/migrate.d.ts +0 -0
  131. /package/dist/{primitives → src/primitives}/parser.d.ts +0 -0
  132. /package/dist/{primitives → src/primitives}/template-parser.d.ts +0 -0
  133. /package/dist/{primitives → src/primitives}/template-renderer.d.ts +0 -0
  134. /package/dist/{primitives → src/primitives}/types.d.ts +0 -0
  135. /package/dist/{renderers → src/renderers}/html/index.d.ts +0 -0
  136. /package/dist/{renderers → src/renderers}/react/index.d.ts +0 -0
  137. /package/dist/{renderers → src/renderers}/registry.d.ts +0 -0
  138. /package/dist/{renderers → src/renderers}/render.d.ts +0 -0
  139. /package/dist/{tokens → src/tokens}/defaults.d.ts +0 -0
  140. /package/dist/{tokens → src/tokens}/resolver.d.ts +0 -0
  141. /package/dist/{tokens → src/tokens}/utils.d.ts +0 -0
  142. /package/dist/{tokens → src/tokens}/validation.d.ts +0 -0
  143. /package/dist/{typecheck → src/typecheck}/index.d.ts +0 -0
  144. /package/dist/{ui → src/ui}/card.d.ts +0 -0
  145. /package/dist/{ui → src/ui}/index.d.ts +0 -0
  146. /package/dist/{ui → src/ui}/utils.d.ts +0 -0
  147. /package/dist/{utils → src/utils}/cache.d.ts +0 -0
  148. /package/dist/{utils → src/utils}/debug.d.ts +0 -0
  149. /package/dist/{utils → src/utils}/port.d.ts +0 -0
  150. /package/dist/{validators → src/validators}/schema-validator.d.ts +0 -0
@@ -0,0 +1 @@
1
+ export declare function createTokensHandler(rootDir: string): (req: Request) => Promise<Response | null>;
@@ -8,6 +8,9 @@ export interface BuildOptions {
8
8
  base?: string;
9
9
  debug?: boolean;
10
10
  }
11
- export declare function startDev(rootDir: string, options?: DevOptions): Promise<import("vite").ViteDevServer>;
11
+ export declare function startDev(rootDir: string, options?: DevOptions): Promise<Bun.Server<undefined>>;
12
12
  export declare function buildSite(rootDir: string, options?: BuildOptions): Promise<void>;
13
- export declare function previewSite(rootDir: string, options?: DevOptions): Promise<import("vite").PreviewServer>;
13
+ export declare function previewSite(rootDir: string, options?: DevOptions): Promise<{
14
+ url: string;
15
+ stop: () => Promise<void>;
16
+ }>;
@@ -3,7 +3,7 @@ import { type VariantProps } from 'class-variance-authority';
3
3
  declare const buttonVariants: (props?: ({
4
4
  variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
5
5
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
6
- } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
+ } & import("node_modules/class-variance-authority/dist/types").ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
8
8
  }
9
9
  declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
@@ -26,11 +26,6 @@ export interface ValidationResult {
26
26
  valid: number;
27
27
  invalid: number;
28
28
  };
29
- atlas: {
30
- total: number;
31
- valid: number;
32
- invalid: number;
33
- };
34
29
  };
35
30
  errors: ValidationError[];
36
31
  warnings: ValidationWarning[];
@@ -14,7 +14,7 @@ export interface SemanticValidationWarning {
14
14
  message: string;
15
15
  code: SemanticWarningCode;
16
16
  }
17
- export type SemanticErrorCode = 'DUPLICATE_ID' | 'INVALID_REF' | 'INVALID_STATE_REF' | 'INVALID_STEP_REF' | 'INVALID_NODE_REF' | 'CIRCULAR_DEPENDENCY' | 'UNKNOWN_RENDERER' | 'INVALID_TEMPLATE' | 'INVALID_SLOT' | 'MISSING_SLOT_DEFINITION';
17
+ export type SemanticErrorCode = 'DUPLICATE_ID' | 'INVALID_REF' | 'INVALID_STATE_REF' | 'INVALID_STEP_REF' | 'CIRCULAR_DEPENDENCY' | 'UNKNOWN_RENDERER' | 'INVALID_TEMPLATE' | 'INVALID_SLOT' | 'MISSING_SLOT_DEFINITION';
18
18
  export type SemanticWarningCode = 'DEPRECATED_STATUS' | 'MISSING_DESCRIPTION';
19
19
  export interface ValidationContext {
20
20
  /** Root directory containing previews folder */
@@ -24,7 +24,6 @@ export interface ValidationContext {
24
24
  components: Set<string>;
25
25
  screens: Set<string>;
26
26
  flows: Set<string>;
27
- atlas: Set<string>;
28
27
  };
29
28
  /** Map of screen states by screen ID */
30
29
  screenStates: Map<string, Set<string>>;
@@ -40,7 +39,7 @@ export declare function createValidationContext(rootDir: string): ValidationCont
40
39
  /**
41
40
  * Register a preview unit in the validation context
42
41
  */
43
- export declare function registerPreviewUnit(context: ValidationContext, type: 'component' | 'screen' | 'flow' | 'atlas', id: string, states?: string[]): void;
42
+ export declare function registerPreviewUnit(context: ValidationContext, type: 'component' | 'screen' | 'flow', id: string, states?: string[]): void;
44
43
  /**
45
44
  * Check for duplicate IDs within a type
46
45
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prev-cli",
3
- "version": "0.24.19",
3
+ "version": "0.25.0",
4
4
  "description": "Transform MDX directories into beautiful documentation websites",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,7 +22,7 @@
22
22
  "markdown",
23
23
  "cli",
24
24
  "static-site",
25
- "vite",
25
+ "bun",
26
26
  "react"
27
27
  ],
28
28
  "repository": {
@@ -46,20 +46,17 @@
46
46
  "test:all": "bun run test && bun run test:integration"
47
47
  },
48
48
  "dependencies": {
49
- "@types/react": "^19.0.0",
50
- "@types/react-dom": "^19.0.0",
51
- "@typescript/native-preview": "^7.0.0-dev",
49
+ "@mdx-js/mdx": "^3.1.0",
52
50
  "@mdx-js/react": "^3.1.1",
53
- "@mdx-js/rollup": "^3.0.0",
54
- "@tailwindcss/vite": "^4.0.0",
55
51
  "@tanstack/react-router": "^1.145.7",
56
52
  "@terrastruct/d2": "^0.1.33",
57
- "@vitejs/plugin-react": "^5.1.2",
53
+ "@types/react": "^19.0.0",
54
+ "@types/react-dom": "^19.0.0",
55
+ "@typescript/native-preview": "^7.0.0-dev",
58
56
  "ajv": "^8.17.1",
59
57
  "ajv-formats": "^3.0.1",
60
58
  "class-variance-authority": "^0.7.0",
61
59
  "clsx": "^2.1.0",
62
- "esbuild": "^0.27.2",
63
60
  "fast-glob": "^3.3.0",
64
61
  "fumadocs-core": "^16.4.3",
65
62
  "fumadocs-ui": "^16.4.3",
@@ -73,8 +70,8 @@
73
70
  "remark-gfm": "^4.0.0",
74
71
  "tailwind-merge": "^2.5.0",
75
72
  "tailwindcss": "3",
76
- "vite": "npm:rolldown-vite@^7.3.1",
77
- "zod": "^4.3.5"
73
+ "zod": "^4.3.5",
74
+ "zustand": "^5.0.11"
78
75
  },
79
76
  "devDependencies": {
80
77
  "@types/js-yaml": "^4.0.9",
@@ -0,0 +1,18 @@
1
+ <!-- c3-generated: c3-501, c3-502, c3-503, c3-504 -->
2
+ # JSX Primitives (WIP)
3
+
4
+ Before modifying this code, read:
5
+ - `.c3/c3-5-jsx/c3-501-vnode.md` - Virtual node structure
6
+ - `.c3/c3-5-jsx/c3-502-jsx-runtime.md` - JSX transformation
7
+ - `.c3/c3-5-jsx/c3-503-define-component.md` - Component definition API
8
+ - `.c3/c3-5-jsx/c3-504-html-adapter.md` - HTML rendering
9
+
10
+ Primitive types: `col`, `row`, `box`, `spacer`, `slot`, `text`, `icon`, `image`, `fragment`
11
+
12
+ Key files:
13
+ - `vnode.ts` - VNode creation and normalization
14
+ - `jsx-runtime.ts` - JSX transformation runtime
15
+ - `define-component.ts` - Component definition
16
+ - `adapters/html.ts` - HTML output adapter
17
+ - `adapters/react.tsx` - React output adapter
18
+ <!-- end-c3-generated -->
@@ -1,5 +1,5 @@
1
1
  // src/jsx/jsx-runtime.ts
2
- // JSX runtime for Vite's automatic JSX transform
2
+ // JSX runtime for automatic JSX transform
3
3
  import {
4
4
  createVNode,
5
5
  normalizeChildren,
@@ -0,0 +1,21 @@
1
+ <!-- c3-generated: c3-207 -->
2
+ # c3-207: Preview Runtime
3
+
4
+ Before modifying this code, read:
5
+ - Component: `.c3/c3-2-build/c3-207-preview-runtime.md`
6
+ - Patterns: `ref-preview-types`
7
+
8
+ Key responsibilities:
9
+ - Build shared vendor bundle (React, ReactDOM)
10
+ - Compile individual preview bundles with Bun.build()
11
+ - Process Tailwind CSS v4 for production
12
+ - Generate standalone HTML files
13
+
14
+ Files:
15
+ - `build-optimized.ts` - Main preview building logic
16
+ - `vendors.ts` - Shared vendor bundle
17
+ - `tailwind.ts` - Tailwind CSS compilation
18
+ - `types.ts` - Type definitions
19
+
20
+ Full refs: `.c3/refs/ref-preview-types.md`
21
+ <!-- end-c3-generated -->
@@ -1,9 +1,17 @@
1
- import { build } from 'esbuild'
2
1
  import type { PreviewConfig } from './types'
3
2
  import { compileTailwind } from './tailwind'
3
+ import { existsSync, readFileSync, mkdtempSync, writeFileSync, rmSync, mkdirSync, statSync } from 'fs'
4
+ import path from 'path'
5
+ import { tmpdir } from 'os'
6
+
7
+ function existsAsFile(p: string): boolean {
8
+ try { return statSync(p).isFile() } catch { return false }
9
+ }
4
10
 
5
11
  export interface OptimizedBuildOptions {
6
12
  vendorPath: string
13
+ jsxPath?: string
14
+ resolveDir?: string
7
15
  }
8
16
 
9
17
  export interface OptimizedBuildResult {
@@ -19,6 +27,9 @@ export async function buildOptimizedPreview(
19
27
  ): Promise<OptimizedBuildResult> {
20
28
  try {
21
29
  const virtualFs: Record<string, { contents: string; loader: string }> = {}
30
+ const resolveDir = options.resolveDir || '/'
31
+
32
+ // Index files by their path for quick lookup
22
33
  for (const file of config.files) {
23
34
  const ext = file.path.split('.').pop()?.toLowerCase()
24
35
  const loader = ext === 'css' ? 'css' : ext === 'json' ? 'json' : ext || 'tsx'
@@ -42,99 +53,204 @@ export async function buildOptimizedPreview(
42
53
  `
43
54
  : `import './${config.entry}'`
44
55
 
45
- const result = await build({
46
- stdin: { contents: entryCode, loader: 'tsx', resolveDir: '/' },
47
- bundle: true,
48
- write: false,
49
- format: 'esm',
50
- jsx: 'automatic',
51
- jsxImportSource: 'react',
52
- target: 'es2020',
53
- minify: true,
54
- plugins: [
55
- {
56
- name: 'optimized-preview',
57
- setup(build) {
58
- // External: vendor runtime
59
- build.onResolve(
60
- { filter: new RegExp(options.vendorPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) },
61
- args => {
56
+ // Write all virtual files to a temp directory for Bun.build
57
+ const tempDir = mkdtempSync(path.join(tmpdir(), 'prev-optimized-'))
58
+ const entryPath = path.join(tempDir, '__entry.tsx')
59
+
60
+ try {
61
+ writeFileSync(entryPath, entryCode)
62
+
63
+ // Write virtual files to temp dir
64
+ for (const [filePath, file] of Object.entries(virtualFs)) {
65
+ const targetPath = path.join(tempDir, filePath)
66
+ const dir = path.dirname(targetPath)
67
+ if (!existsSync(dir)) {
68
+ mkdirSync(dir, { recursive: true })
69
+ }
70
+ writeFileSync(targetPath, file.contents)
71
+ }
72
+
73
+ const result = await Bun.build({
74
+ entrypoints: [entryPath],
75
+ format: 'esm',
76
+ target: 'browser',
77
+ minify: true,
78
+ jsx: { runtime: 'automatic', importSource: 'react' },
79
+ plugins: [
80
+ {
81
+ name: 'optimized-preview',
82
+ setup(build) {
83
+ // External: vendor runtime
84
+ build.onResolve(
85
+ { filter: new RegExp(options.vendorPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) },
86
+ args => {
87
+ return { path: args.path, external: true }
88
+ }
89
+ )
90
+
91
+ // External: React (map to vendor bundle)
92
+ build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
93
+ return { path: options.vendorPath, external: true }
94
+ })
95
+
96
+ // External: @prev/jsx (map to jsx bundle)
97
+ build.onResolve({ filter: /^@prev\/jsx$/ }, () => {
98
+ const jsxPath = options.jsxPath || options.vendorPath.replace('runtime.js', 'jsx.js')
99
+ return { path: jsxPath, external: true }
100
+ })
101
+
102
+ // External: @prev/components/* (not supported in static builds yet)
103
+ build.onResolve({ filter: /^@prev\/components\// }, args => {
104
+ console.warn(` Warning: @prev/components imports not supported in static builds: ${args.path}`)
62
105
  return { path: args.path, external: true }
63
- }
64
- )
65
-
66
- // External: React (map to vendor bundle)
67
- build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
68
- return { path: options.vendorPath, external: true }
69
- })
70
-
71
- // Resolve relative imports
72
- build.onResolve({ filter: /^\./ }, args => {
73
- let resolved = args.path.replace(/^\.\//, '')
74
- if (!resolved.includes('.')) {
75
- for (const ext of ['.tsx', '.ts', '.jsx', '.js', '.css']) {
76
- if (virtualFs[resolved + ext]) {
77
- resolved = resolved + ext
78
- break
106
+ })
107
+
108
+ // Intercept CSS imports to collect them
109
+ build.onLoad({ filter: /\.css$/ }, args => {
110
+ // Read from temp dir or resolveDir
111
+ let content: string
112
+ const tempPath = args.path
113
+ if (existsSync(tempPath)) {
114
+ content = readFileSync(tempPath, 'utf-8')
115
+ } else {
116
+ const diskPath = path.resolve(resolveDir, path.relative(tempDir, tempPath))
117
+ if (existsSync(diskPath)) {
118
+ content = readFileSync(diskPath, 'utf-8')
119
+ } else {
120
+ return { contents: '', loader: 'js' }
79
121
  }
80
122
  }
81
- }
82
- return { path: resolved, namespace: 'virtual' }
83
- })
84
-
85
- // Load from virtual FS
86
- build.onLoad({ filter: /.*/, namespace: 'virtual' }, args => {
87
- const file = virtualFs[args.path]
88
- if (file) {
89
- if (file.loader === 'css') {
90
- userCssCollected.push(file.contents)
91
- return { contents: '', loader: 'js' }
123
+ userCssCollected.push(content)
124
+ return { contents: '', loader: 'js' }
125
+ })
126
+
127
+ // Resolve relative imports that might need to fall back to resolveDir
128
+ build.onResolve({ filter: /^\.\.?\// }, args => {
129
+ const resolved = path.resolve(path.dirname(args.importer), args.path)
130
+ // If it exists in temp dir as a file, let Bun handle it
131
+ const tryExts = ['.tsx', '.ts', '.jsx', '.js', '.css']
132
+ const tryIndex = ['/index.tsx', '/index.ts', '/index.jsx', '/index.js']
133
+ if (existsAsFile(resolved)) return undefined
134
+ for (const ext of tryExts) {
135
+ if (existsAsFile(resolved + ext)) return undefined
136
+ }
137
+ for (const idx of tryIndex) {
138
+ if (existsAsFile(resolved + idx)) return undefined
92
139
  }
93
- return { contents: file.contents, loader: file.loader as any }
94
- }
95
- return { contents: '', loader: 'empty' }
96
- })
140
+ // Fall back: resolve using original directory structure
141
+ const importerRelative = path.relative(tempDir, args.importer)
142
+ const originalDir = path.resolve(resolveDir, path.dirname(importerRelative))
143
+ const diskPath = path.resolve(originalDir, args.path)
144
+ if (existsAsFile(diskPath)) return { path: diskPath }
145
+ for (const ext of tryExts) {
146
+ if (existsAsFile(diskPath + ext)) {
147
+ return { path: diskPath + ext }
148
+ }
149
+ }
150
+ for (const idx of tryIndex) {
151
+ if (existsAsFile(diskPath + idx)) {
152
+ return { path: diskPath + idx }
153
+ }
154
+ }
155
+ return undefined
156
+ })
157
+ },
97
158
  },
98
- },
99
- ],
100
- })
101
-
102
- const jsFile = result.outputFiles?.find(f => f.path.endsWith('.js')) || result.outputFiles?.[0]
103
- const jsCode = jsFile?.text || ''
104
-
105
- let css = ''
106
- if (config.tailwind) {
107
- const tailwindResult = await compileTailwind(
108
- config.files.map(f => ({ path: f.path, content: f.content }))
109
- )
110
- if (tailwindResult.success) css = tailwindResult.css
111
- }
159
+ ],
160
+ })
112
161
 
113
- // Strip @import "tailwindcss" from user CSS when Tailwind is compiled
114
- // (the import is Tailwind v4 dev syntax, not valid for static HTML)
115
- let userCss = userCssCollected.join('\n')
116
- if (config.tailwind) {
117
- userCss = userCss.replace(/@import\s*["']tailwindcss["']\s*;?/g, '')
118
- }
119
- const allCss = css + '\n' + userCss
162
+ if (!result.success) {
163
+ const errors = result.logs.filter(l => l.level === 'error').map(l => l.message).join('; ')
164
+ return { success: false, html: '', css: '', error: errors || 'Build failed' }
165
+ }
120
166
 
121
- const html = `<!DOCTYPE html>
167
+ const jsFile = result.outputs.find(f => f.path.endsWith('.js')) || result.outputs[0]
168
+ let jsCode = jsFile ? await jsFile.text() : ''
169
+
170
+ // Fix bare specifiers in output — Bun.build externalizes but keeps original specifiers
171
+ const jsxPath = options.jsxPath || options.vendorPath.replace('runtime.js', 'jsx.js')
172
+ jsCode = jsCode.replace(/from\s*["']react\/jsx(-dev)?-runtime["']/g, `from"${options.vendorPath}"`)
173
+ jsCode = jsCode.replace(/from\s*["']react-dom\/client["']/g, `from"${options.vendorPath}"`)
174
+ jsCode = jsCode.replace(/from\s*["']react-dom["']/g, `from"${options.vendorPath}"`)
175
+ jsCode = jsCode.replace(/from\s*["']react["']/g, `from"${options.vendorPath}"`)
176
+ jsCode = jsCode.replace(/from\s*["']@prev\/jsx["']/g, `from"${jsxPath}"`)
177
+ jsCode = jsCode.replace(/from\s*["']@prev\/components\/[^"']*["']/g, `from"${jsxPath}"`)
178
+
179
+ let css = ''
180
+ if (config.tailwind) {
181
+ const tailwindResult = await compileTailwind(
182
+ config.files.map(f => ({ path: f.path, content: f.content }))
183
+ )
184
+ if (tailwindResult.success) css = tailwindResult.css
185
+ }
186
+
187
+ // Strip @import "tailwindcss" from user CSS when Tailwind is compiled
188
+ let userCss = userCssCollected.join('\n')
189
+ if (config.tailwind) {
190
+ userCss = userCss.replace(/@import\s*["']tailwindcss["']\s*;?/g, '')
191
+ }
192
+ const allCss = css + '\n' + userCss
193
+
194
+ // Canvas styling for showcase presentation
195
+ const canvasStyles = `
196
+ html, body {
197
+ margin: 0;
198
+ min-height: 100vh;
199
+ }
200
+ body {
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: center;
204
+ background-color: #fafafa;
205
+ background-image:
206
+ radial-gradient(circle at center, #e5e5e5 1px, transparent 1px);
207
+ background-size: 16px 16px;
208
+ padding: 24px;
209
+ box-sizing: border-box;
210
+ }
211
+ @media (prefers-color-scheme: dark) {
212
+ body {
213
+ background-color: #171717;
214
+ background-image:
215
+ radial-gradient(circle at center, #262626 1px, transparent 1px);
216
+ }
217
+ }
218
+ #root {
219
+ background: white;
220
+ border-radius: 12px;
221
+ padding: 32px;
222
+ box-shadow: 0 4px 24px -4px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04);
223
+ max-width: 100%;
224
+ }
225
+ @media (prefers-color-scheme: dark) {
226
+ #root {
227
+ background: #1c1c1c;
228
+ box-shadow: 0 4px 24px -4px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.06);
229
+ }
230
+ }
231
+ `
232
+
233
+ const html = `<!DOCTYPE html>
122
234
  <html lang="en">
123
235
  <head>
124
236
  <meta charset="UTF-8">
125
237
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
126
238
  <title>Preview</title>
127
239
  <style>${allCss}</style>
128
- <style>body { margin: 0; } #root { min-height: 100vh; }</style>
240
+ <style>${canvasStyles}</style>
129
241
  </head>
130
242
  <body>
131
243
  <div id="root"></div>
132
244
  <script type="module" src="${options.vendorPath}"></script>
245
+ <script type="module" src="${jsxPath}"></script>
133
246
  <script type="module">${jsCode}</script>
134
247
  </body>
135
248
  </html>`
136
249
 
137
- return { success: true, html, css: allCss }
250
+ return { success: true, html, css: allCss }
251
+ } finally {
252
+ rmSync(tempDir, { recursive: true, force: true })
253
+ }
138
254
  } catch (err) {
139
255
  return { success: false, html: '', css: '', error: err instanceof Error ? err.message : String(err) }
140
256
  }