bertui 1.2.9 → 2.0.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 (183) hide show
  1. package/README.md +44 -242
  2. package/TYPES_PATCH.md +17 -0
  3. package/bin/bertui.js +2 -7
  4. package/package.json +32 -98
  5. package/src/config.ts +4 -0
  6. package/src/index.ts +32 -0
  7. package/src/optional.ts +49 -0
  8. package/src/router.ts +3 -0
  9. package/tsconfig.json +29 -0
  10. package/LICENSE +0 -21
  11. package/index.js +0 -103
  12. package/src/analyzer/index.js +0 -370
  13. package/src/build/compiler/file-transpiler.js +0 -216
  14. package/src/build/compiler/index.js +0 -31
  15. package/src/build/compiler/route-discoverer.js +0 -49
  16. package/src/build/compiler/router-generator.js +0 -105
  17. package/src/build/css-builder.js +0 -81
  18. package/src/build/generators/html-generator.js +0 -263
  19. package/src/build/generators/robots-generator.js +0 -58
  20. package/src/build/generators/sitemap-generator.js +0 -63
  21. package/src/build/image-optimizer.js +0 -137
  22. package/src/build/processors/asset-processor.js +0 -19
  23. package/src/build/processors/css-builder.js +0 -142
  24. package/src/build/server-island-validator.js +0 -67
  25. package/src/build/ssr-renderer.js +0 -64
  26. package/src/build.js +0 -273
  27. package/src/cli.js +0 -131
  28. package/src/client/compiler.js +0 -522
  29. package/src/client/fast-refresh.js +0 -72
  30. package/src/client/hmr-runtime.js +0 -59
  31. package/src/compiler/index.js +0 -25
  32. package/src/compiler/router-generator-pure.js +0 -104
  33. package/src/compiler/transform.js +0 -149
  34. package/src/config/defaultConfig.js +0 -37
  35. package/src/config/index.js +0 -2
  36. package/src/config/loadConfig.js +0 -64
  37. package/src/config/og-image.png +0 -0
  38. package/src/css/index.js +0 -46
  39. package/src/css/processor.js +0 -172
  40. package/src/dev.js +0 -68
  41. package/src/hydration/index.js +0 -151
  42. package/src/image-optimizer/index.js +0 -103
  43. package/src/images/index.js +0 -102
  44. package/src/images/processor.js +0 -169
  45. package/src/layouts/index.js +0 -165
  46. package/src/loading/index.js +0 -210
  47. package/src/logger/logger.js +0 -320
  48. package/src/logger/notes.md +0 -20
  49. package/src/middleware/index.js +0 -182
  50. package/src/router/Router.js +0 -150
  51. package/src/router/SSRRouter.js +0 -156
  52. package/src/router/index.js +0 -3
  53. package/src/scaffolder/index.js +0 -310
  54. package/src/serve.js +0 -193
  55. package/src/server/dev-handler.js +0 -195
  56. package/src/server/dev-server-utils.js +0 -406
  57. package/src/server/dev-server.js +0 -15
  58. package/src/server/hmr-handler.js +0 -148
  59. package/src/server/index.js +0 -3
  60. package/src/server/notes.md +0 -1
  61. package/src/server/request-handler.js +0 -36
  62. package/src/server-islands/extractor.js +0 -198
  63. package/src/server-islands/index.js +0 -59
  64. package/src/styles/bertui.css +0 -210
  65. package/src/utils/cache.js +0 -297
  66. package/src/utils/env.js +0 -87
  67. package/src/utils/importhow.js +0 -52
  68. package/src/utils/index.js +0 -11
  69. package/src/utils/meta-extractor.js +0 -127
  70. package/types/bin/bertui.d.ts +0 -3
  71. package/types/bin/bertui.d.ts.map +0 -1
  72. package/types/error-overlay.d.ts +0 -2
  73. package/types/error-overlay.d.ts.map +0 -1
  74. package/types/index.d.ts +0 -26
  75. package/types/index.d.ts.map +0 -1
  76. package/types/scripts/fix-wasm-exports.d.ts +0 -2
  77. package/types/scripts/fix-wasm-exports.d.ts.map +0 -1
  78. package/types/src/analyzer/index.d.ts +0 -8
  79. package/types/src/analyzer/index.d.ts.map +0 -1
  80. package/types/src/build/compiler/file-transpiler.d.ts +0 -5
  81. package/types/src/build/compiler/file-transpiler.d.ts.map +0 -1
  82. package/types/src/build/compiler/index.d.ts +0 -12
  83. package/types/src/build/compiler/index.d.ts.map +0 -1
  84. package/types/src/build/compiler/route-discoverer.d.ts +0 -2
  85. package/types/src/build/compiler/route-discoverer.d.ts.map +0 -1
  86. package/types/src/build/compiler/router-generator.d.ts +0 -2
  87. package/types/src/build/compiler/router-generator.d.ts.map +0 -1
  88. package/types/src/build/css-builder.d.ts +0 -18
  89. package/types/src/build/css-builder.d.ts.map +0 -1
  90. package/types/src/build/generators/html-generator.d.ts +0 -2
  91. package/types/src/build/generators/html-generator.d.ts.map +0 -1
  92. package/types/src/build/generators/robots-generator.d.ts +0 -11
  93. package/types/src/build/generators/robots-generator.d.ts.map +0 -1
  94. package/types/src/build/generators/sitemap-generator.d.ts +0 -5
  95. package/types/src/build/generators/sitemap-generator.d.ts.map +0 -1
  96. package/types/src/build/image-optimizer.d.ts +0 -11
  97. package/types/src/build/image-optimizer.d.ts.map +0 -1
  98. package/types/src/build/processors/asset-processor.d.ts +0 -2
  99. package/types/src/build/processors/asset-processor.d.ts.map +0 -1
  100. package/types/src/build/processors/css-builder.d.ts +0 -2
  101. package/types/src/build/processors/css-builder.d.ts.map +0 -1
  102. package/types/src/build/server-island-validator.d.ts +0 -27
  103. package/types/src/build/server-island-validator.d.ts.map +0 -1
  104. package/types/src/build.d.ts +0 -5
  105. package/types/src/build.d.ts.map +0 -1
  106. package/types/src/cli.d.ts +0 -2
  107. package/types/src/cli.d.ts.map +0 -1
  108. package/types/src/client/compiler.d.ts +0 -16
  109. package/types/src/client/compiler.d.ts.map +0 -1
  110. package/types/src/client/fast-refresh.d.ts +0 -3
  111. package/types/src/client/fast-refresh.d.ts.map +0 -1
  112. package/types/src/client/hmr-runtime.d.ts +0 -4
  113. package/types/src/client/hmr-runtime.d.ts.map +0 -1
  114. package/types/src/compiler/index.d.ts +0 -8
  115. package/types/src/compiler/index.d.ts.map +0 -1
  116. package/types/src/compiler/router-generator-pure.d.ts +0 -2
  117. package/types/src/compiler/router-generator-pure.d.ts.map +0 -1
  118. package/types/src/compiler/transform.d.ts +0 -36
  119. package/types/src/compiler/transform.d.ts.map +0 -1
  120. package/types/src/config/defaultConfig.d.ts +0 -26
  121. package/types/src/config/defaultConfig.d.ts.map +0 -1
  122. package/types/src/config/index.d.ts +0 -3
  123. package/types/src/config/index.d.ts.map +0 -1
  124. package/types/src/config/loadConfig.d.ts +0 -2
  125. package/types/src/config/loadConfig.d.ts.map +0 -1
  126. package/types/src/css/index.d.ts +0 -6
  127. package/types/src/css/index.d.ts.map +0 -1
  128. package/types/src/css/processor.d.ts +0 -23
  129. package/types/src/css/processor.d.ts.map +0 -1
  130. package/types/src/dev.d.ts +0 -2
  131. package/types/src/dev.d.ts.map +0 -1
  132. package/types/src/hydration/index.d.ts +0 -33
  133. package/types/src/hydration/index.d.ts.map +0 -1
  134. package/types/src/image-optimizer/index.d.ts +0 -24
  135. package/types/src/image-optimizer/index.d.ts.map +0 -1
  136. package/types/src/images/index.d.ts +0 -12
  137. package/types/src/images/index.d.ts.map +0 -1
  138. package/types/src/images/processor.d.ts +0 -30
  139. package/types/src/images/processor.d.ts.map +0 -1
  140. package/types/src/layouts/index.d.ts +0 -28
  141. package/types/src/layouts/index.d.ts.map +0 -1
  142. package/types/src/loading/index.d.ts +0 -28
  143. package/types/src/loading/index.d.ts.map +0 -1
  144. package/types/src/logger/logger.d.ts +0 -30
  145. package/types/src/logger/logger.d.ts.map +0 -1
  146. package/types/src/middleware/index.d.ts +0 -61
  147. package/types/src/middleware/index.d.ts.map +0 -1
  148. package/types/src/router/Router.d.ts +0 -16
  149. package/types/src/router/Router.d.ts.map +0 -1
  150. package/types/src/router/SSRRouter.d.ts +0 -20
  151. package/types/src/router/SSRRouter.d.ts.map +0 -1
  152. package/types/src/router/index.d.ts +0 -3
  153. package/types/src/router/index.d.ts.map +0 -1
  154. package/types/src/scaffolder/index.d.ts +0 -14
  155. package/types/src/scaffolder/index.d.ts.map +0 -1
  156. package/types/src/serve.d.ts +0 -3
  157. package/types/src/serve.d.ts.map +0 -1
  158. package/types/src/server/dev-handler.d.ts +0 -13
  159. package/types/src/server/dev-handler.d.ts.map +0 -1
  160. package/types/src/server/dev-server-utils.d.ts +0 -6
  161. package/types/src/server/dev-server-utils.d.ts.map +0 -1
  162. package/types/src/server/dev-server.d.ts +0 -18
  163. package/types/src/server/dev-server.d.ts.map +0 -1
  164. package/types/src/server/hmr-handler.d.ts +0 -19
  165. package/types/src/server/hmr-handler.d.ts.map +0 -1
  166. package/types/src/server/index.d.ts +0 -4
  167. package/types/src/server/index.d.ts.map +0 -1
  168. package/types/src/server/request-handler.d.ts +0 -19
  169. package/types/src/server/request-handler.d.ts.map +0 -1
  170. package/types/src/server-islands/extractor.d.ts +0 -16
  171. package/types/src/server-islands/extractor.d.ts.map +0 -1
  172. package/types/src/server-islands/index.d.ts +0 -3
  173. package/types/src/server-islands/index.d.ts.map +0 -1
  174. package/types/src/utils/cache.d.ts +0 -52
  175. package/types/src/utils/cache.d.ts.map +0 -1
  176. package/types/src/utils/env.d.ts +0 -20
  177. package/types/src/utils/env.d.ts.map +0 -1
  178. package/types/src/utils/importhow.d.ts +0 -15
  179. package/types/src/utils/importhow.d.ts.map +0 -1
  180. package/types/src/utils/index.d.ts +0 -3
  181. package/types/src/utils/index.d.ts.map +0 -1
  182. package/types/src/utils/meta-extractor.d.ts +0 -13
  183. package/types/src/utils/meta-extractor.d.ts.map +0 -1
@@ -1,310 +0,0 @@
1
- // bertui/src/scaffolder/index.js
2
- // CLI component/page/layout scaffolder
3
-
4
- import { join } from 'path';
5
- import { existsSync, mkdirSync } from 'fs';
6
- import logger from '../logger/logger.js';
7
- // ─── TEMPLATES ─────────────────────────────────────────────────────────────
8
-
9
- const TEMPLATES = {
10
- component: (name) => `import React, { useState } from 'react';
11
-
12
- interface ${name}Props {
13
- className?: string;
14
- children?: React.ReactNode;
15
- }
16
-
17
- export default function ${name}({ className = '', children }: ${name}Props) {
18
- return (
19
- <div className={className}>
20
- {children ?? <p>${name} component</p>}
21
- </div>
22
- );
23
- }
24
- `,
25
-
26
- page: (name, route) => `// Route: ${route}
27
- import React from 'react';
28
- import { Link } from 'bertui/router';
29
-
30
- export const title = '${name}';
31
- export const description = '${name} page';
32
-
33
- export default function ${name}Page() {
34
- return (
35
- <main style={{ padding: '2rem', fontFamily: 'system-ui' }}>
36
- <h1>${name}</h1>
37
- <p>Welcome to ${name}</p>
38
- <Link to="/">← Back home</Link>
39
- </main>
40
- );
41
- }
42
- `,
43
-
44
- layout: (name) => `import React from 'react';
45
-
46
- interface ${name}LayoutProps {
47
- children: React.ReactNode;
48
- }
49
-
50
- // This layout wraps all pages${name === 'default' ? '' : ` under /${name.toLowerCase()}/`}
51
- export default function ${name}Layout({ children }: ${name}LayoutProps) {
52
- return (
53
- <div style={{ minHeight: '100vh', fontFamily: 'system-ui' }}>
54
- <header style={{
55
- padding: '1rem 2rem',
56
- borderBottom: '1px solid #e5e7eb',
57
- display: 'flex',
58
- alignItems: 'center',
59
- justifyContent: 'space-between',
60
- }}>
61
- <a href="/" style={{ fontWeight: 700, textDecoration: 'none', color: 'inherit' }}>
62
- My App
63
- </a>
64
- <nav style={{ display: 'flex', gap: '1.5rem' }}>
65
- <a href="/" style={{ color: '#4b5563', textDecoration: 'none' }}>Home</a>
66
- <a href="/about" style={{ color: '#4b5563', textDecoration: 'none' }}>About</a>
67
- </nav>
68
- </header>
69
- <main style={{ padding: '2rem' }}>
70
- {children}
71
- </main>
72
- <footer style={{
73
- padding: '1rem 2rem',
74
- borderTop: '1px solid #e5e7eb',
75
- color: '#9ca3af',
76
- fontSize: '14px',
77
- textAlign: 'center',
78
- }}>
79
- Built with BertUI ⚡
80
- </footer>
81
- </div>
82
- );
83
- }
84
- `,
85
-
86
- loading: (name) => `import React from 'react';
87
-
88
- // Loading state for ${name} route
89
- // This shows while the page JavaScript loads
90
- export default function ${name}Loading() {
91
- return (
92
- <div style={{
93
- display: 'flex',
94
- flexDirection: 'column',
95
- alignItems: 'center',
96
- justifyContent: 'center',
97
- minHeight: '60vh',
98
- gap: '1rem',
99
- fontFamily: 'system-ui',
100
- }}>
101
- <div style={{
102
- width: '40px',
103
- height: '40px',
104
- border: '3px solid #e5e7eb',
105
- borderTopColor: '#10b981',
106
- borderRadius: '50%',
107
- animation: 'spin 0.7s linear infinite',
108
- }} />
109
- <style>{'@keyframes spin { to { transform: rotate(360deg); } }'}</style>
110
- <p style={{ color: '#6b7280', fontSize: '14px' }}>Loading ${name}...</p>
111
- </div>
112
- );
113
- }
114
- `,
115
-
116
- middleware: () => `import type { MiddlewareContext } from 'bertui/middleware';
117
-
118
- // Runs before EVERY page request
119
- // Use ctx.redirect(), ctx.respond(), or ctx.locals to pass data
120
-
121
- export async function onRequest(ctx: MiddlewareContext) {
122
- // Example: protect /dashboard
123
- // if (ctx.pathname.startsWith('/dashboard')) {
124
- // const token = ctx.headers['authorization'];
125
- // if (!token) return ctx.redirect('/login');
126
- // }
127
-
128
- // Example: add custom headers
129
- // ctx.setHeader('X-Powered-By', 'BertUI');
130
-
131
- // Example: pass data to pages via locals
132
- // ctx.locals.user = await getUser(ctx.headers.cookie);
133
-
134
- console.log('[Middleware]', ctx.method, ctx.pathname);
135
- }
136
-
137
- // Optional: handle middleware errors
138
- export async function onError(ctx: MiddlewareContext, error: Error) {
139
- console.error('[Middleware Error]', error.message);
140
- }
141
- `,
142
- };
143
-
144
- // ─── SCAFFOLDER ─────────────────────────────────────────────────────────────
145
-
146
- export async function scaffold(type, name, options = {}) {
147
- const root = options.root || process.cwd();
148
- const ts = options.ts !== false; // Default: TypeScript
149
-
150
- const ext = ts ? '.tsx' : '.jsx';
151
-
152
- switch (type) {
153
- case 'component':
154
- return createComponent(name, root, ext);
155
- case 'page':
156
- return createPage(name, root, ext);
157
- case 'layout':
158
- return createLayout(name, root, ext);
159
- case 'loading':
160
- return createLoading(name, root, ext);
161
- case 'middleware':
162
- return createMiddleware(root, ts);
163
- default:
164
- logger.error(`Unknown scaffold type: ${type}`);
165
- logger.info('Available types: component, page, layout, loading, middleware');
166
- return false;
167
- }
168
- }
169
-
170
- async function createComponent(name, root, ext) {
171
- const pascal = toPascalCase(name);
172
- const dir = join(root, 'src', 'components');
173
-
174
- fsMkdir(dir, { recursive: true });
175
-
176
- const filePath = join(dir, `${pascal}${ext}`);
177
-
178
- if (fsExists(filePath)) {
179
- logger.warn(`Component already exists: src/components/${pascal}${ext}`);
180
- return false;
181
- }
182
-
183
- await Bun.write(filePath, TEMPLATES.component(pascal));
184
- logger.success(`✅ Created component: src/components/${pascal}${ext}`);
185
- return filePath;
186
- }
187
-
188
- async function createPage(name, root, ext) {
189
- const pascal = toPascalCase(name);
190
- const route = `/${name.toLowerCase().replace(/\s+/g, '-')}`;
191
-
192
- // Handle nested routes like "blog/[slug]"
193
- const parts = name.split('/');
194
- const pageName = toPascalCase(parts[parts.length - 1]);
195
- const dir = join(root, 'src', 'pages', ...parts.slice(0, -1));
196
-
197
- fsMkdir(dir, { recursive: true });
198
-
199
- // Handle index pages
200
- const fileName = pageName.toLowerCase() === 'index' ? 'index' : pageName.toLowerCase();
201
- const filePath = join(dir, `${fileName}${ext}`);
202
-
203
- if (fsExists(filePath)) {
204
- logger.warn(`Page already exists: src/pages/${name}${ext}`);
205
- return false;
206
- }
207
-
208
- await Bun.write(filePath, TEMPLATES.page(pascal, route));
209
- logger.success(`✅ Created page: src/pages/${name}${ext}`);
210
- logger.info(` Route: ${route}`);
211
- return filePath;
212
- }
213
-
214
- async function createLayout(name, root, ext) {
215
- const pascal = toPascalCase(name);
216
- const dir = join(root, 'src', 'layouts');
217
-
218
- fsMkdir(dir, { recursive: true });
219
-
220
- const filePath = join(dir, `${name.toLowerCase()}${ext}`);
221
-
222
- if (fsExists(filePath)) {
223
- logger.warn(`Layout already exists: src/layouts/${name.toLowerCase()}${ext}`);
224
- return false;
225
- }
226
-
227
- await Bun.write(filePath, TEMPLATES.layout(pascal));
228
- logger.success(`✅ Created layout: src/layouts/${name.toLowerCase()}${ext}`);
229
-
230
- if (name.toLowerCase() === 'default') {
231
- logger.info(' → This layout will wrap ALL pages automatically');
232
- } else {
233
- logger.info(` → This layout will wrap pages under /${name.toLowerCase()}/`);
234
- }
235
-
236
- return filePath;
237
- }
238
-
239
- async function createLoading(name, root, ext) {
240
- const pascal = toPascalCase(name);
241
-
242
- // Place loading.tsx next to the relevant page/route
243
- const isRoot = name.toLowerCase() === 'root' || name === '/';
244
- const dir = isRoot
245
- ? join(root, 'src', 'pages')
246
- : join(root, 'src', 'pages', name.toLowerCase());
247
-
248
- fsMkdir(dir, { recursive: true });
249
-
250
- const filePath = join(dir, `loading${ext}`);
251
-
252
- if (fsExists(filePath)) {
253
- logger.warn(`Loading component already exists at ${dir}/loading${ext}`);
254
- return false;
255
- }
256
-
257
- await Bun.write(filePath, TEMPLATES.loading(pascal));
258
- logger.success(`✅ Created loading state: ${filePath.replace(root, '')}`);
259
- logger.info(` → Shows while ${isRoot ? 'root' : `/${name.toLowerCase()}`} route loads`);
260
- return filePath;
261
- }
262
-
263
- async function createMiddleware(root, ts) {
264
- const ext = ts ? '.ts' : '.js';
265
- const filePath = join(root, 'src', `middleware${ext}`);
266
-
267
- if (fsExists(filePath)) {
268
- logger.warn(`Middleware already exists: src/middleware${ext}`);
269
- return false;
270
- }
271
-
272
- await Bun.write(filePath, TEMPLATES.middleware());
273
- logger.success(`✅ Created middleware: src/middleware${ext}`);
274
- logger.info(' → Runs before every page request');
275
- return filePath;
276
- }
277
-
278
- // ─── HELPERS ────────────────────────────────────────────────────────────────
279
-
280
- function toPascalCase(str) {
281
- return str
282
- .replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase())
283
- .replace(/^(.)/, c => c.toUpperCase())
284
- .replace(/[[\]]/g, ''); // Remove bracket from dynamic routes
285
- }
286
-
287
- /**
288
- * Parse CLI args for the create command
289
- * Usage: bertui create component Button
290
- * bertui create page About
291
- * bertui create layout default
292
- * bertui create loading blog
293
- * bertui create middleware
294
- */
295
- export function parseCreateArgs(args) {
296
- const [type, name] = args;
297
-
298
- if (!type) {
299
- logger.error('Usage: bertui create <type> [name]');
300
- logger.info('Types: component, page, layout, loading, middleware');
301
- return null;
302
- }
303
-
304
- if (type !== 'middleware' && !name) {
305
- logger.error(`Usage: bertui create ${type} <name>`);
306
- return null;
307
- }
308
-
309
- return { type, name: name || type };
310
- }
package/src/serve.js DELETED
@@ -1,193 +0,0 @@
1
- // bertui/src/serve.js - ULTRA-FAST PRODUCTION PREVIEW SERVER
2
- import { join, extname } from 'path';
3
- import { existsSync } from 'fs';
4
- import logger from './logger/logger.js';
5
- import { globalCache } from './utils/cache.js';
6
-
7
- // MIME types for fast serving
8
- const MIME_TYPES = {
9
- '.html': 'text/html',
10
- '.css': 'text/css',
11
- '.js': 'application/javascript',
12
- '.mjs': 'application/javascript',
13
- '.json': 'application/json',
14
- '.png': 'image/png',
15
- '.jpg': 'image/jpeg',
16
- '.jpeg': 'image/jpeg',
17
- '.gif': 'image/gif',
18
- '.svg': 'image/svg+xml',
19
- '.webp': 'image/webp',
20
- '.avif': 'image/avif',
21
- '.ico': 'image/x-icon',
22
- '.woff': 'font/woff',
23
- '.woff2': 'font/woff2',
24
- '.ttf': 'font/ttf',
25
- '.otf': 'font/otf',
26
- '.txt': 'text/plain',
27
- '.xml': 'application/xml',
28
- '.pdf': 'application/pdf',
29
- '.map': 'application/json'
30
- };
31
-
32
- export async function startPreviewServer(options = {}) {
33
- const root = options.root || process.cwd();
34
- const port = options.port || 5000;
35
- const distDir = options.dir || 'dist';
36
- const publicPath = join(root, distDir);
37
-
38
- // Check if dist folder exists
39
- if (!existsSync(publicPath)) {
40
- logger.error(`❌ ${distDir}/ folder not found!`);
41
- logger.info(` Run 'bertui build' first to generate production files.`);
42
- process.exit(1);
43
- }
44
-
45
- console.log(`\n 🚀 Preview running at http://localhost:${port}`);
46
- console.log(` Press Ctrl+C to stop\n`);
47
-
48
- // Track connections for graceful shutdown
49
- const connections = new Set();
50
-
51
- // Create ultra-fast static server
52
- const server = Bun.serve({
53
- port,
54
- async fetch(req) {
55
- const url = new URL(req.url);
56
- let filePath = join(publicPath, url.pathname);
57
-
58
- // Handle root path - serve index.html
59
- if (url.pathname === '/') {
60
- filePath = join(publicPath, 'index.html');
61
- }
62
-
63
- // Handle directory requests - serve index.html
64
- if (!extname(filePath)) {
65
- const indexPath = join(filePath, 'index.html');
66
- if (existsSync(indexPath)) {
67
- filePath = indexPath;
68
- }
69
- }
70
-
71
- // Check if file exists
72
- if (!existsSync(filePath)) {
73
- // Try fallback to index.html for SPA routing
74
- if (!url.pathname.includes('.')) {
75
- const spaPath = join(publicPath, 'index.html');
76
- if (existsSync(spaPath)) {
77
- const file = Bun.file(spaPath);
78
- return new Response(file, {
79
- headers: {
80
- 'Content-Type': 'text/html',
81
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
82
- 'X-BertUI-Preview': 'spa-fallback'
83
- }
84
- });
85
- }
86
- }
87
-
88
- return new Response('Not Found', { status: 404 });
89
- }
90
-
91
- // Get file stats for caching
92
- const stats = await Bun.file(filePath).stat();
93
- const ext = extname(filePath).toLowerCase();
94
- const contentType = MIME_TYPES[ext] || 'application/octet-stream';
95
-
96
- // Set cache headers based on file type
97
- const isStaticAsset = ['.js', '.css', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif', '.ico'].includes(ext);
98
- const cacheControl = isStaticAsset
99
- ? 'public, max-age=31536000, immutable' // 1 year for assets with hash in name
100
- : 'no-cache, no-store, must-revalidate'; // No cache for HTML
101
-
102
- // Serve file with proper headers
103
- const file = Bun.file(filePath);
104
-
105
- return new Response(file, {
106
- headers: {
107
- 'Content-Type': contentType,
108
- 'Content-Length': stats.size,
109
- 'Cache-Control': cacheControl,
110
- 'X-BertUI-Preview': 'static'
111
- }
112
- });
113
- },
114
-
115
- // Track connections for graceful shutdown
116
- websocket: {
117
- open(ws) {
118
- connections.add(ws);
119
- },
120
- close(ws) {
121
- connections.delete(ws);
122
- }
123
- }
124
- });
125
-
126
- // Handle graceful shutdown
127
- process.on('SIGINT', () => {
128
- logger.info('\n👋 Shutting down preview server...');
129
-
130
- // Close all WebSocket connections
131
- for (const ws of connections) {
132
- try {
133
- ws.close();
134
- } catch (e) {}
135
- }
136
-
137
- server.stop();
138
- process.exit(0);
139
- });
140
-
141
- return server;
142
- }
143
-
144
- // FAST FILE LISTING FOR DEBUGGING
145
- export async function listDistContents(distPath) {
146
- try {
147
- const { readdirSync, statSync } = await import('fs');
148
- const { join, relative } = await import('path');
149
-
150
- function scan(dir, level = 0) {
151
- const files = readdirSync(dir);
152
- const result = [];
153
-
154
- for (const file of files) {
155
- const fullPath = join(dir, file);
156
- const stat = statSync(fullPath);
157
- const relPath = relative(distPath, fullPath);
158
-
159
- if (stat.isDirectory()) {
160
- result.push({
161
- name: file,
162
- path: relPath,
163
- type: 'directory',
164
- children: scan(fullPath, level + 1)
165
- });
166
- } else {
167
- result.push({
168
- name: file,
169
- path: relPath,
170
- type: 'file',
171
- size: stat.size,
172
- sizeFormatted: formatBytes(stat.size)
173
- });
174
- }
175
- }
176
-
177
- return result;
178
- }
179
-
180
- return scan(distPath);
181
- } catch (error) {
182
- logger.error(`Failed to list dist contents: ${error.message}`);
183
- return [];
184
- }
185
- }
186
-
187
- function formatBytes(bytes) {
188
- if (bytes === 0) return '0 B';
189
- const k = 1024;
190
- const sizes = ['B', 'KB', 'MB', 'GB'];
191
- const i = Math.floor(Math.log(bytes) / Math.log(k));
192
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
193
- }
@@ -1,195 +0,0 @@
1
- // bertui/src/server/dev-handler.js - WITH MIDDLEWARE + LAYOUTS + LOADING
2
- import { join, extname, dirname } from 'path';
3
- import { existsSync } from 'fs';
4
- import logger from '../logger/logger.js';
5
- import { compileProject } from '../client/compiler.js';
6
- import { loadConfig } from '../config/loadConfig.js';
7
- import { getContentType, getImageContentType, serveHTML, setupFileWatcher } from './dev-server-utils.js';
8
-
9
- export async function createDevHandler(options = {}) {
10
- const root = options.root || process.cwd();
11
- const port = parseInt(options.port) || 3000;
12
- const middlewareManager = options.middleware || null;
13
- const layouts = options.layouts || {};
14
- const loadingComponents = options.loadingComponents || {};
15
-
16
- const compiledDir = join(root, '.bertui', 'compiled');
17
- const stylesDir = join(root, '.bertui', 'styles');
18
- const srcDir = join(root, 'src');
19
- const publicDir = join(root, 'public');
20
-
21
- const config = await loadConfig(root);
22
-
23
- let hasRouter = existsSync(join(compiledDir, 'router.js'));
24
- const clients = new Set();
25
-
26
- const websocketHandler = {
27
- open(ws) {
28
- clients.add(ws);
29
- logger.debug(`HMR client connected (${clients.size} total)`);
30
- },
31
- close(ws) {
32
- clients.delete(ws);
33
- },
34
- };
35
-
36
- function notifyClients(message) {
37
- for (const client of clients) {
38
- try { client.send(JSON.stringify(message)); }
39
- catch (e) { clients.delete(client); }
40
- }
41
- }
42
-
43
- let watcherCleanup = null;
44
- if (root) {
45
- watcherCleanup = setupFileWatcher(root, compiledDir, clients, async () => {
46
- hasRouter = existsSync(join(compiledDir, 'router.js'));
47
- });
48
- }
49
-
50
- async function handleRequest(request) {
51
- const url = new URL(request.url);
52
-
53
- // WebSocket upgrade for HMR
54
- if (url.pathname === '/__hmr' && request.headers.get('upgrade') === 'websocket') {
55
- return { type: 'websocket', handler: websocketHandler };
56
- }
57
-
58
- // ✅ Run middleware BEFORE every page request
59
- if (middlewareManager && isPageRequest(url.pathname)) {
60
- const middlewareResponse = await middlewareManager.run(request, {
61
- route: url.pathname,
62
- });
63
- if (middlewareResponse) {
64
- logger.debug(`🛡️ Middleware handled: ${url.pathname}`);
65
- return middlewareResponse;
66
- }
67
- }
68
-
69
- // Serve page HTML
70
- if (url.pathname === '/' || (!url.pathname.includes('.') && !url.pathname.startsWith('/compiled'))) {
71
- const html = await serveHTML(root, hasRouter, config, port);
72
- return new Response(html, {
73
- headers: { 'Content-Type': 'text/html' },
74
- });
75
- }
76
-
77
- // Compiled JS (includes layouts and loading components)
78
- if (url.pathname.startsWith('/compiled/')) {
79
- const filepath = join(compiledDir, url.pathname.replace('/compiled/', ''));
80
- const file = Bun.file(filepath);
81
- if (await file.exists()) {
82
- return new Response(file, {
83
- headers: {
84
- 'Content-Type': 'application/javascript; charset=utf-8',
85
- 'Cache-Control': 'no-store',
86
- },
87
- });
88
- }
89
- }
90
-
91
- // CSS
92
- if (url.pathname.startsWith('/styles/')) {
93
- const filepath = join(stylesDir, url.pathname.replace('/styles/', ''));
94
- const file = Bun.file(filepath);
95
- if (await file.exists()) {
96
- return new Response(file, {
97
- headers: { 'Content-Type': 'text/css', 'Cache-Control': 'no-store' },
98
- });
99
- }
100
- }
101
-
102
- // Error overlay script
103
- if (url.pathname === '/error-overlay.js') {
104
- const overlayPath = join(root, 'node_modules/bertui/error-overlay.js');
105
- const file = Bun.file(overlayPath);
106
- if (await file.exists()) {
107
- return new Response(file, {
108
- headers: { 'Content-Type': 'application/javascript; charset=utf-8', 'Cache-Control': 'no-store' },
109
- });
110
- }
111
- }
112
-
113
- // bertui-animate CSS
114
- if (url.pathname === '/bertui-animate.css') {
115
- const animPath = join(root, 'node_modules/bertui-animate/dist/bertui-animate.min.css');
116
- const file = Bun.file(animPath);
117
- if (await file.exists()) {
118
- return new Response(file, { headers: { 'Content-Type': 'text/css' } });
119
- }
120
- }
121
-
122
- // Images
123
- if (url.pathname.startsWith('/images/')) {
124
- const filepath = join(srcDir, 'images', url.pathname.replace('/images/', ''));
125
- const file = Bun.file(filepath);
126
- if (await file.exists()) {
127
- const ext = extname(filepath).toLowerCase();
128
- return new Response(file, {
129
- headers: { 'Content-Type': getImageContentType(ext), 'Cache-Control': 'no-cache' },
130
- });
131
- }
132
- }
133
-
134
- // Public directory
135
- if (url.pathname.startsWith('/public/') || existsSync(join(publicDir, url.pathname.slice(1)))) {
136
- const filepath = join(publicDir, url.pathname.replace('/public/', ''));
137
- const file = Bun.file(filepath);
138
- if (await file.exists()) {
139
- return new Response(file, { headers: { 'Cache-Control': 'no-cache' } });
140
- }
141
- }
142
-
143
- // node_modules
144
- if (url.pathname.startsWith('/node_modules/')) {
145
- const filepath = join(root, 'node_modules', url.pathname.replace('/node_modules/', ''));
146
- const file = Bun.file(filepath);
147
- if (await file.exists()) {
148
- const contentType = ext === '.css' ? 'text/css' :
149
- ['.js', '.mjs', '.jsx', '.ts', '.tsx'].includes(ext) ? 'application/javascript; charset=utf-8' :
150
- ext === '.json' ? 'application/json' :
151
- getContentType(ext);
152
- return new Response(file, {
153
- headers: { 'Content-Type': contentType, 'Cache-Control': 'no-cache' },
154
- });
155
- }
156
- }
157
-
158
- return null;
159
- }
160
-
161
- async function start() {
162
- const server = Bun.serve({
163
- port,
164
- async fetch(req, server) {
165
- const url = new URL(req.url);
166
- if (url.pathname === '/__hmr') {
167
- const success = server.upgrade(req);
168
- if (success) return undefined;
169
- return new Response('WebSocket upgrade failed', { status: 500 });
170
- }
171
- const response = await handleRequest(req);
172
- if (response) return response;
173
- return new Response('Not found', { status: 404 });
174
- },
175
- websocket: websocketHandler,
176
- });
177
-
178
- logger.success(`🚀 BertUI running at http://localhost:${port}`);
179
- return server;
180
- }
181
-
182
- function dispose() {
183
- if (watcherCleanup) watcherCleanup();
184
- clients.clear();
185
- if (middlewareManager) middlewareManager.dispose();
186
- }
187
-
188
- return { handleRequest, start, notifyClients, dispose, config, hasRouter, websocketHandler };
189
- }
190
-
191
- function isPageRequest(pathname) {
192
- // Skip asset requests
193
- return !pathname.includes('.') ||
194
- pathname.endsWith('.html');
195
- }