bertui 1.0.3 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bertui",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "Lightning-fast React dev server powered by Bun and Elysia",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": "^18.0.0 || ^19.0.0",
53
- "react-dom": "^18.0.0 || ^19.0.0",
53
+ "react-dom": "^19.2.3",
54
54
  "bun": ">=1.0.0"
55
55
  },
56
56
  "engines": {
@@ -0,0 +1,156 @@
1
+ // bertui/src/build/server-island-validator.js
2
+ // Fixed validation for Server Islands - no false positives!
3
+
4
+ import logger from '../logger/logger.js';
5
+
6
+ /**
7
+ * Validates that a Server Island component follows all rules
8
+ * @param {string} sourceCode - The component source code
9
+ * @param {string} filePath - Path to the file (for error messages)
10
+ * @returns {{ valid: boolean, errors: string[] }}
11
+ */
12
+ export function validateServerIsland(sourceCode, filePath) {
13
+ const errors = [];
14
+
15
+ // Rule 1: No React hooks (FIXED: only match actual function calls)
16
+ const hookPatterns = [
17
+ 'useState',
18
+ 'useEffect',
19
+ 'useContext',
20
+ 'useReducer',
21
+ 'useCallback',
22
+ 'useMemo',
23
+ 'useRef',
24
+ 'useImperativeHandle',
25
+ 'useLayoutEffect',
26
+ 'useDebugValue',
27
+ 'useId',
28
+ 'useDeferredValue',
29
+ 'useTransition',
30
+ 'useSyncExternalStore'
31
+ ];
32
+
33
+ for (const hook of hookPatterns) {
34
+ // FIXED: Only match hooks as function calls, not in text/comments
35
+ // Looks for: useState( or const [x] = useState(
36
+ const regex = new RegExp(`\\b${hook}\\s*\\(`, 'g');
37
+ if (regex.test(sourceCode)) {
38
+ errors.push(`❌ Uses React hook: ${hook}`);
39
+ }
40
+ }
41
+
42
+ // Rule 2: No bertui/router imports
43
+ if (sourceCode.includes('from \'bertui/router\'') ||
44
+ sourceCode.includes('from "bertui/router"')) {
45
+ errors.push('❌ Imports from \'bertui/router\' (use <a> tags instead of Link)');
46
+ }
47
+
48
+ // Rule 3: No browser APIs (FIXED: only match actual usage, not in strings/comments)
49
+ const browserAPIs = [
50
+ { pattern: 'window\\.', name: 'window' },
51
+ { pattern: 'document\\.', name: 'document' },
52
+ { pattern: 'localStorage\\.', name: 'localStorage' },
53
+ { pattern: 'sessionStorage\\.', name: 'sessionStorage' },
54
+ { pattern: 'navigator\\.', name: 'navigator' },
55
+ { pattern: 'location\\.', name: 'location' },
56
+ { pattern: 'history\\.', name: 'history' },
57
+ { pattern: '(?<!//.*|/\\*.*|\\*)\\bfetch\\s*\\(', name: 'fetch' },
58
+ { pattern: '\\.addEventListener\\s*\\(', name: 'addEventListener' },
59
+ { pattern: '\\.removeEventListener\\s*\\(', name: 'removeEventListener' },
60
+ { pattern: '\\bsetTimeout\\s*\\(', name: 'setTimeout' },
61
+ { pattern: '\\bsetInterval\\s*\\(', name: 'setInterval' },
62
+ { pattern: '\\brequestAnimationFrame\\s*\\(', name: 'requestAnimationFrame' }
63
+ ];
64
+
65
+ for (const api of browserAPIs) {
66
+ const regex = new RegExp(api.pattern, 'g');
67
+ if (regex.test(sourceCode)) {
68
+ if (api.name === 'console') {
69
+ logger.warn(`⚠️ ${filePath} uses console.log (will not work in static HTML)`);
70
+ } else {
71
+ errors.push(`❌ Uses browser API: ${api.name}`);
72
+ }
73
+ }
74
+ }
75
+
76
+ // Rule 4: No event handlers (these won't work without JS)
77
+ const eventHandlers = [
78
+ 'onClick=',
79
+ 'onChange=',
80
+ 'onSubmit=',
81
+ 'onInput=',
82
+ 'onFocus=',
83
+ 'onBlur=',
84
+ 'onMouseEnter=',
85
+ 'onMouseLeave=',
86
+ 'onKeyDown=',
87
+ 'onKeyUp=',
88
+ 'onScroll='
89
+ ];
90
+
91
+ for (const handler of eventHandlers) {
92
+ if (sourceCode.includes(handler)) {
93
+ errors.push(`❌ Uses event handler: ${handler.replace('=', '')} (Server Islands are static HTML)`);
94
+ }
95
+ }
96
+
97
+ // Rule 5: Check for dynamic imports
98
+ if (/import\s*\(/.test(sourceCode)) {
99
+ errors.push('❌ Uses dynamic import() (not supported in Server Islands)');
100
+ }
101
+
102
+ // Rule 6: Check for async/await (usually indicates API calls)
103
+ if (/async\s+function|async\s*\(|async\s+\w+\s*\(/.test(sourceCode)) {
104
+ errors.push('❌ Uses async/await (Server Islands must be synchronous)');
105
+ }
106
+
107
+ const valid = errors.length === 0;
108
+
109
+ return { valid, errors };
110
+ }
111
+
112
+ /**
113
+ * Display validation errors in a clear format
114
+ */
115
+ export function displayValidationErrors(filePath, errors) {
116
+ logger.error(`\n🏝️ Server Island validation failed: ${filePath}`);
117
+ logger.error('\nViolations:');
118
+ errors.forEach(error => logger.error(` ${error}`));
119
+ logger.error('\n📖 Server Island Rules:');
120
+ logger.error(' ✅ Pure static JSX only');
121
+ logger.error(' ❌ No React hooks (useState, useEffect, etc.)');
122
+ logger.error(' ❌ No Link component (use <a> tags)');
123
+ logger.error(' ❌ No browser APIs (window, document, fetch)');
124
+ logger.error(' ❌ No event handlers (onClick, onChange, etc.)');
125
+ logger.error('\n💡 Tip: Remove the "export const render = \\"server\\"" line');
126
+ logger.error(' if you need these features (page will be client-only).\n');
127
+ }
128
+
129
+ /**
130
+ * Extract and validate all Server Islands in a project
131
+ */
132
+ export async function validateAllServerIslands(routes) {
133
+ const serverIslands = [];
134
+ const validationResults = [];
135
+
136
+ for (const route of routes) {
137
+ const sourceCode = await Bun.file(route.path).text();
138
+ const isServerIsland = sourceCode.includes('export const render = "server"');
139
+
140
+ if (isServerIsland) {
141
+ const validation = validateServerIsland(sourceCode, route.path);
142
+
143
+ validationResults.push({
144
+ route: route.route,
145
+ path: route.path,
146
+ ...validation
147
+ });
148
+
149
+ if (validation.valid) {
150
+ serverIslands.push(route);
151
+ }
152
+ }
153
+ }
154
+
155
+ return { serverIslands, validationResults };
156
+ }