create-aziz-app 2.0.13 → 2.0.15

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.
@@ -1,369 +1,458 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
3
+ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const http = require('http');
6
- const { exec } = require('child_process');
6
+ const crypto = require('crypto');
7
7
 
8
8
  const PORT = process.env.PORT || 3000;
9
+ const ROOT = process.cwd();
9
10
  const clients = new Set();
10
11
  const apiHandlers = {};
11
12
 
12
- console.log('šŸš€ Dev server starting...\n');
13
+ console.log('šŸš€ AzizApp dev server starting...\n');
14
+
15
+ // ─── Route Manifest ──────────────────────────────────────────
16
+ // Scans app/ folder and builds a list of pages and layouts
17
+
18
+ function buildRouteManifest() {
19
+ const manifest = [];
20
+ const appDir = path.join(ROOT, 'app');
21
+ if (!fs.existsSync(appDir)) return manifest;
22
+
23
+ function scan(dir, routeBase) {
24
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
25
+
26
+ for (const entry of entries) {
27
+ const fullPath = path.join(dir, entry.name);
28
+
29
+ if (entry.isDirectory()) {
30
+ // Dynamic route: [slug] → :slug
31
+ const segment = entry.name.startsWith('[') && entry.name.endsWith(']')
32
+ ? ':' + entry.name.slice(1, -1)
33
+ : entry.name;
34
+ scan(fullPath, routeBase === '/' ? '/' + segment : routeBase + '/' + segment);
35
+ } else if (entry.name === 'page.js' || entry.name === 'page.jsx') {
36
+ manifest.push({
37
+ type: 'page',
38
+ pathname: routeBase || '/',
39
+ filePath: '/app' + (routeBase === '/' ? '' : routeBase) + '/' + entry.name
40
+ });
41
+ } else if (entry.name === 'layout.js' || entry.name === 'layout.jsx') {
42
+ manifest.push({
43
+ type: 'layout',
44
+ pathname: routeBase || '/',
45
+ filePath: '/app' + (routeBase === '/' ? '' : routeBase) + '/' + entry.name
46
+ });
47
+ }
48
+ }
49
+ }
13
50
 
14
- // Load API routes from api/ folder
15
- function loadApiRoutes() {
16
- const apiDir = path.join(process.cwd(), 'api');
17
- if (!fs.existsSync(apiDir)) {
18
- console.log('āš ļø No api/ folder found\n');
19
- return;
51
+ scan(appDir, '/');
52
+ return manifest;
53
+ }
54
+
55
+ // ─── Bare Import Rewriter ─────────────────────────────────────
56
+ // Transforms: import X from 'mongoose' → import X from '/_modules/mongoose'
57
+ // Transforms: import X from '@/lib/db' → import X from '/lib/db.js'
58
+
59
+ function rewriteImports(code, filePath) {
60
+ // Rewrite @/ alias to absolute path
61
+ code = code.replace(
62
+ /from\s+['"]@\/([^'"]+)['"]/g,
63
+ (_, p) => `from '/${p}'`
64
+ );
65
+
66
+ // Rewrite bare specifiers (not starting with /, ./, ../)
67
+ code = code.replace(
68
+ /from\s+['"]([^'"./][^'"]*)['"]/g,
69
+ (match, specifier) => `from '/_modules/${specifier}'`
70
+ );
71
+
72
+ // Same for dynamic imports
73
+ code = code.replace(
74
+ /import\s*\(\s*['"]([^'"./][^'"]*)['"]\s*\)/g,
75
+ (_, specifier) => `import('/_modules/${specifier}')`
76
+ );
77
+
78
+ return code;
79
+ }
80
+
81
+ // ─── Module Resolver ─────────────────────────────────────────
82
+ // Serves node_modules files for bare imports
83
+
84
+ function resolveNodeModule(specifier) {
85
+ // Try to find the package in node_modules
86
+ const parts = specifier.split('/');
87
+ const pkgName = parts[0].startsWith('@') ? parts[0] + '/' + parts[1] : parts[0];
88
+ const subPath = parts[0].startsWith('@') ? parts.slice(2).join('/') : parts.slice(1).join('/');
89
+
90
+ const nodeModulesDir = path.join(ROOT, 'node_modules');
91
+
92
+ // Try package.json to find main/module entry
93
+ const pkgJsonPath = path.join(nodeModulesDir, pkgName, 'package.json');
94
+ if (!fs.existsSync(pkgJsonPath)) {
95
+ return null;
96
+ }
97
+
98
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
99
+
100
+ if (subPath) {
101
+ // Specific file requested
102
+ const candidates = [
103
+ path.join(nodeModulesDir, pkgName, subPath),
104
+ path.join(nodeModulesDir, pkgName, subPath + '.js'),
105
+ path.join(nodeModulesDir, pkgName, subPath + '/index.js'),
106
+ ];
107
+ for (const c of candidates) {
108
+ if (fs.existsSync(c)) return c;
109
+ }
110
+ return null;
20
111
  }
21
112
 
113
+ // Use module > browser > main priority
114
+ const entry = pkg.module || pkg.browser || pkg.main || 'index.js';
115
+ const entryPath = path.join(nodeModulesDir, pkgName, entry);
116
+ return fs.existsSync(entryPath) ? entryPath : null;
117
+ }
118
+
119
+ // ─── JSX Transform ───────────────────────────────────────────
120
+ // Adds /** @jsx h */ pragma if file uses JSX and doesn't have it
121
+
122
+ function ensureJsxPragma(code) {
123
+ if (code.includes('@jsx') || !code.includes('<')) return code;
124
+ // Simple heuristic: if it has JSX-like syntax, add pragma
125
+ if (/<[A-Z]|<[a-z]+[\s/>]/.test(code)) {
126
+ return '/** @jsx h */\n' + code;
127
+ }
128
+ return code;
129
+ }
130
+
131
+ // ─── API Route Loader ─────────────────────────────────────────
132
+
133
+ function loadApiRoutes() {
134
+ const apiDir = path.join(ROOT, 'api');
135
+ if (!fs.existsSync(apiDir)) return;
136
+
22
137
  const files = fs.readdirSync(apiDir).filter(f => f.endsWith('.js'));
23
- console.log(`šŸ“” Loading ${files.length} API routes...\n`);
138
+ console.log(`šŸ“” Loading ${files.length} API route(s):`);
24
139
 
25
140
  files.forEach(file => {
26
141
  const routeName = path.basename(file, '.js');
27
- const filePath = path.join(apiDir, file);
28
-
142
+ const filePath = path.join(apiDir, file);
143
+
29
144
  try {
30
- // Clear require cache to allow hot reload
31
145
  delete require.cache[require.resolve(filePath)];
32
-
33
- // Load the API handler
34
- const module = require(filePath);
35
-
36
- // Support both default export and module.exports
37
- const handler = module.default || module;
38
-
39
- if (typeof handler === 'function') {
40
- apiHandlers[routeName] = handler;
41
- console.log(` āœ… /api/${routeName}`);
146
+ const mod = require(filePath);
147
+
148
+ if (mod.GET || mod.POST || mod.PUT || mod.DELETE || mod.PATCH) {
149
+ const methods = {};
150
+ ['GET','POST','PUT','DELETE','PATCH'].forEach(m => { if (mod[m]) methods[m] = mod[m]; });
151
+ apiHandlers[routeName] = methods;
152
+ console.log(` āœ… /api/${routeName} [${Object.keys(methods).join(', ')}]`);
42
153
  } else {
43
- console.log(` āš ļø /api/${routeName} - No handler function exported`);
154
+ const handler = mod.default || mod;
155
+ if (typeof handler === 'function') {
156
+ apiHandlers[routeName] = handler;
157
+ console.log(` āœ… /api/${routeName}`);
158
+ }
44
159
  }
45
- } catch (error) {
46
- console.error(` āŒ /api/${routeName} - ${error.message}`);
160
+ } catch (err) {
161
+ console.error(` āŒ /api/${routeName} — ${err.message}`);
47
162
  }
48
163
  });
49
-
164
+
50
165
  console.log('');
51
166
  }
52
167
 
53
- // Parse request body
168
+ // ─── Request Helpers ─────────────────────────────────────────
169
+
54
170
  function parseBody(req) {
55
171
  return new Promise((resolve, reject) => {
56
172
  let body = '';
57
- req.on('data', chunk => {
58
- body += chunk.toString();
59
- });
173
+ req.on('data', c => body += c);
60
174
  req.on('end', () => {
61
175
  try {
62
- if (body && req.headers['content-type']?.includes('application/json')) {
63
- resolve(JSON.parse(body));
64
- } else {
65
- resolve(body);
66
- }
67
- } catch (e) {
68
- resolve(body);
69
- }
176
+ resolve(body && req.headers['content-type']?.includes('application/json')
177
+ ? JSON.parse(body) : body);
178
+ } catch { resolve(body); }
70
179
  });
71
180
  req.on('error', reject);
72
181
  });
73
182
  }
74
183
 
75
- // Parse query string
76
184
  function parseQuery(url) {
77
- const query = {};
78
- const queryString = url.split('?')[1];
79
- if (queryString) {
80
- queryString.split('&').forEach(param => {
81
- const [key, value] = param.split('=');
82
- query[decodeURIComponent(key)] = decodeURIComponent(value || '');
83
- });
84
- }
85
- return query;
185
+ const q = {};
186
+ const qs = url.split('?')[1];
187
+ if (qs) qs.split('&').forEach(p => {
188
+ const [k, v] = p.split('=');
189
+ q[decodeURIComponent(k)] = decodeURIComponent(v || '');
190
+ });
191
+ return q;
86
192
  }
87
193
 
88
- // Handle API requests
194
+ // ─── API Request Handler ──────────────────────────────────────
195
+
89
196
  async function handleApiRequest(req, res) {
90
- const urlPath = req.url.split('?')[0];
197
+ const urlPath = req.url.split('?')[0];
91
198
  const routeName = urlPath.replace('/api/', '');
92
-
93
- const handler = apiHandlers[routeName];
94
-
199
+ const handler = apiHandlers[routeName];
200
+
95
201
  if (!handler) {
96
202
  res.writeHead(404, { 'Content-Type': 'application/json' });
97
- res.end(JSON.stringify({ error: 'API route not found' }));
203
+ res.end(JSON.stringify({ error: `API route /api/${routeName} not found` }));
98
204
  return;
99
205
  }
100
206
 
101
207
  try {
102
- // Parse request
103
- const body = await parseBody(req);
208
+ const body = await parseBody(req);
104
209
  const query = parseQuery(req.url);
105
-
106
- // Create Next.js-like req/res objects
107
- const apiReq = {
108
- method: req.method,
109
- body: body,
110
- query: query,
111
- headers: req.headers,
112
- url: req.url
113
- };
114
-
115
- const apiRes = {
116
- statusCode: 200,
117
- headers: {},
118
- status(code) {
119
- this.statusCode = code;
120
- return this;
121
- },
122
- json(data) {
123
- res.writeHead(this.statusCode, {
124
- 'Content-Type': 'application/json',
125
- 'Access-Control-Allow-Origin': '*',
126
- ...this.headers
127
- });
128
- res.end(JSON.stringify(data));
129
- return this;
130
- },
131
- send(data) {
132
- res.writeHead(this.statusCode, {
133
- 'Content-Type': 'text/plain',
134
- 'Access-Control-Allow-Origin': '*',
135
- ...this.headers
136
- });
137
- res.end(data);
138
- return this;
139
- },
140
- setHeader(key, value) {
141
- this.headers[key] = value;
142
- return this;
210
+
211
+ if (typeof handler === 'function') {
212
+ // Old style
213
+ const apiReq = { method: req.method, body, query, headers: req.headers, url: req.url };
214
+ const apiRes = {
215
+ statusCode: 200, headers: {},
216
+ status(c) { this.statusCode = c; return this; },
217
+ json(d) {
218
+ res.writeHead(this.statusCode, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...this.headers });
219
+ res.end(JSON.stringify(d));
220
+ },
221
+ send(d) {
222
+ res.writeHead(this.statusCode, { 'Content-Type': 'text/plain', 'Access-Control-Allow-Origin': '*', ...this.headers });
223
+ res.end(d);
224
+ },
225
+ setHeader(k, v) { this.headers[k] = v; return this; }
226
+ };
227
+ await handler(apiReq, apiRes);
228
+ } else {
229
+ // New style: named exports
230
+ const methodFn = handler[req.method];
231
+ if (!methodFn) {
232
+ res.writeHead(405, { 'Content-Type': 'application/json', 'Allow': Object.keys(handler).join(', ') });
233
+ res.end(JSON.stringify({ error: 'Method not allowed', allowed: Object.keys(handler) }));
234
+ return;
143
235
  }
144
- };
145
236
 
146
- // Call the handler
147
- await handler(apiReq, apiRes);
148
-
149
- } catch (error) {
150
- console.error(`āŒ API Error on /api/${routeName}:`, error);
151
- res.writeHead(500, { 'Content-Type': 'application/json' });
152
- res.end(JSON.stringify({ error: error.message }));
153
- }
154
- }
237
+ const request = {
238
+ method: req.method,
239
+ headers: req.headers,
240
+ url: `http://localhost:${PORT}${req.url}`,
241
+ json: async () => body,
242
+ text: async () => typeof body === 'string' ? body : JSON.stringify(body)
243
+ };
155
244
 
156
- // Function to generate imports
157
- function generateImports() {
158
- console.log('šŸ”„ Regenerating imports...');
159
- exec('node scripts/generate-imports.js', (error, stdout, stderr) => {
160
- if (error) {
161
- console.error('āŒ Error:', error);
162
- return;
163
- }
164
- console.log(stdout);
165
- // Notify all connected clients to reload
166
- notifyClients();
167
- });
168
- }
245
+ const result = await methodFn(request);
169
246
 
170
- // Notify all clients to reload
171
- function notifyClients() {
172
- const timestamp = new Date().toLocaleTimeString();
173
- console.log(`šŸ”„ [${timestamp}] Notifying ${clients.size} browser(s) to reload...`);
174
- clients.forEach(client => {
175
- try {
176
- client.send('reload');
177
- } catch (e) {
178
- clients.delete(client);
247
+ if (result && result.body !== undefined) {
248
+ const headers = { 'Access-Control-Allow-Origin': '*', ...Object.fromEntries(result.headers || []) };
249
+ res.writeHead(result.status || 200, headers);
250
+ res.end(result.body);
251
+ }
179
252
  }
180
- });
253
+ } catch (err) {
254
+ console.error(`āŒ API Error /api/${routeName}:`, err);
255
+ res.writeHead(500, { 'Content-Type': 'application/json' });
256
+ res.end(JSON.stringify({ error: err.message }));
257
+ }
181
258
  }
182
259
 
183
- // Create HTTP server
260
+ // ─── HTTP Server ──────────────────────────────────────────────
261
+
262
+ const MIME = {
263
+ '.html': 'text/html',
264
+ '.js': 'text/javascript',
265
+ '.jsx': 'text/javascript',
266
+ '.mjs': 'text/javascript',
267
+ '.css': 'text/css',
268
+ '.json': 'application/json',
269
+ '.png': 'image/png',
270
+ '.jpg': 'image/jpeg',
271
+ '.svg': 'image/svg+xml',
272
+ '.ico': 'image/x-icon',
273
+ '.woff': 'font/woff',
274
+ '.woff2':'font/woff2',
275
+ '.ttf': 'font/ttf',
276
+ };
277
+
278
+ const NO_CACHE = {
279
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
280
+ 'Pragma': 'no-cache',
281
+ 'Expires': '0'
282
+ };
283
+
184
284
  const server = http.createServer(async (req, res) => {
185
- // Handle API routes
186
- if (req.url.startsWith('/api/')) {
285
+ const urlWithoutQuery = req.url.split('?')[0];
286
+
287
+ // ── CORS preflight
288
+ if (req.method === 'OPTIONS') {
289
+ res.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' });
290
+ res.end();
291
+ return;
292
+ }
293
+
294
+ // ── API routes
295
+ if (urlWithoutQuery.startsWith('/api/')) {
187
296
  await handleApiRequest(req, res);
188
297
  return;
189
298
  }
190
299
 
191
- // Handle CORS preflight
192
- if (req.method === 'OPTIONS') {
193
- res.writeHead(200, {
194
- 'Access-Control-Allow-Origin': '*',
195
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
196
- 'Access-Control-Allow-Headers': 'Content-Type'
197
- });
198
- res.end();
300
+ // ── Route manifest (used by router.js to discover pages)
301
+ if (urlWithoutQuery === '/_framework/routes.json') {
302
+ const manifest = buildRouteManifest();
303
+ res.writeHead(200, { 'Content-Type': 'application/json', ...NO_CACHE });
304
+ res.end(JSON.stringify(manifest));
199
305
  return;
200
306
  }
201
307
 
202
- // Strip query parameters for file serving (cache busting)
203
- let filePath = '.' + req.url.split('?')[0];
204
- if (filePath === './') {
205
- filePath = './app.html';
308
+ // ── node_modules bare imports: /_modules/mongoose → node_modules/mongoose
309
+ if (urlWithoutQuery.startsWith('/_modules/')) {
310
+ const specifier = urlWithoutQuery.slice('/_modules/'.length);
311
+ const filePath = resolveNodeModule(specifier);
312
+
313
+ if (!filePath) {
314
+ res.writeHead(404, { 'Content-Type': 'application/json' });
315
+ res.end(JSON.stringify({ error: `Module not found: ${specifier}. Run: npm install ${specifier.split('/')[0]}` }));
316
+ return;
317
+ }
318
+
319
+ try {
320
+ let code = fs.readFileSync(filePath, 'utf8');
321
+ code = rewriteImports(code, filePath);
322
+ res.writeHead(200, { 'Content-Type': 'text/javascript', ...NO_CACHE });
323
+ res.end(code);
324
+ } catch (err) {
325
+ res.writeHead(500);
326
+ res.end('Error reading module: ' + err.message);
327
+ }
328
+ return;
206
329
  }
207
330
 
208
- const extname = String(path.extname(filePath)).toLowerCase();
209
- const mimeTypes = {
210
- '.html': 'text/html',
211
- '.js': 'text/javascript',
212
- '.jsx': 'text/javascript',
213
- '.css': 'text/css',
214
- '.json': 'application/json',
215
- '.png': 'image/png',
216
- '.jpg': 'image/jpg',
217
- '.gif': 'image/gif',
218
- '.svg': 'image/svg+xml',
219
- '.wav': 'audio/wav',
220
- '.mp4': 'video/mp4',
221
- '.woff': 'application/font-woff',
222
- '.ttf': 'application/font-ttf',
223
- '.eot': 'application/vnd.ms-fontobject',
224
- '.otf': 'application/font-otf',
225
- '.wasm': 'application/wasm'
226
- };
331
+ // ── Static files
332
+ let filePath = path.join(ROOT, urlWithoutQuery);
227
333
 
228
- const contentType = mimeTypes[extname] || 'application/octet-stream';
334
+ // Default to index.html
335
+ if (urlWithoutQuery === '/' || urlWithoutQuery === '') {
336
+ filePath = path.join(ROOT, 'index.html');
337
+ }
229
338
 
230
- fs.readFile(filePath, (error, content) => {
231
- if (error) {
232
- if (error.code === 'ENOENT') {
233
- res.writeHead(404, { 'Content-Type': 'text/html' });
234
- res.end('<h1>404 - File Not Found</h1>', 'utf-8');
235
- } else {
236
- res.writeHead(500);
237
- res.end('Server Error: ' + error.code, 'utf-8');
238
- }
339
+ // SPA fallback: any path without extension → index.html
340
+ const ext = path.extname(urlWithoutQuery);
341
+ if (!ext && urlWithoutQuery !== '/') {
342
+ filePath = path.join(ROOT, 'index.html');
343
+ }
344
+
345
+ if (!fs.existsSync(filePath)) {
346
+ // Try with .js extension
347
+ if (fs.existsSync(filePath + '.js')) {
348
+ filePath = filePath + '.js';
239
349
  } else {
240
- // Add cache control headers to prevent caching during development
241
- res.writeHead(200, {
242
- 'Content-Type': contentType,
243
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
244
- 'Pragma': 'no-cache',
245
- 'Expires': '0'
246
- });
247
- res.end(content, 'utf-8');
350
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
351
+ res.end('Not found: ' + urlWithoutQuery);
352
+ return;
248
353
  }
249
- });
354
+ }
355
+
356
+ try {
357
+ let content = fs.readFileSync(filePath);
358
+ const fileExt = path.extname(filePath).toLowerCase();
359
+ const contentType = MIME[fileExt] || 'application/octet-stream';
360
+
361
+ // Transform JS/JSX files: rewrite imports
362
+ if (fileExt === '.js' || fileExt === '.jsx' || fileExt === '.mjs') {
363
+ let code = content.toString();
364
+ code = rewriteImports(code, filePath);
365
+ content = Buffer.from(code);
366
+ }
367
+
368
+ res.writeHead(200, { 'Content-Type': contentType, ...NO_CACHE });
369
+ res.end(content);
370
+ } catch (err) {
371
+ res.writeHead(500);
372
+ res.end('Server error: ' + err.message);
373
+ }
250
374
  });
251
375
 
252
- // WebSocket implementation (simple, no external dependencies)
253
- server.on('upgrade', (req, socket, head) => {
254
- // Simple WebSocket handshake
376
+ // ─── WebSocket (live reload) ──────────────────────────────────
377
+
378
+ server.on('upgrade', (req, socket) => {
255
379
  const key = req.headers['sec-websocket-key'];
256
- const acceptKey = require('crypto')
257
- .createHash('sha1')
380
+ const accept = crypto.createHash('sha1')
258
381
  .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
259
382
  .digest('base64');
260
383
 
261
384
  socket.write(
262
385
  'HTTP/1.1 101 Switching Protocols\r\n' +
263
- 'Upgrade: websocket\r\n' +
264
- 'Connection: Upgrade\r\n' +
265
- `Sec-WebSocket-Accept: ${acceptKey}\r\n` +
266
- '\r\n'
386
+ 'Upgrade: websocket\r\nConnection: Upgrade\r\n' +
387
+ `Sec-WebSocket-Accept: ${accept}\r\n\r\n`
267
388
  );
268
389
 
269
- const timestamp = new Date().toLocaleTimeString();
270
- console.log(`šŸ”Œ [${timestamp}] Browser connected (${clients.size + 1} total)`);
271
-
272
- // Wrap socket with send method
273
390
  const client = {
274
- socket: socket,
275
- send: function(message) {
276
- const buffer = Buffer.from(message);
277
- const frame = Buffer.alloc(buffer.length + 2);
278
- frame[0] = 0x81; // Text frame
279
- frame[1] = buffer.length;
280
- buffer.copy(frame, 2);
391
+ send(msg) {
392
+ const buf = Buffer.from(msg);
393
+ const frame = Buffer.alloc(buf.length + 2);
394
+ frame[0] = 0x81; frame[1] = buf.length;
395
+ buf.copy(frame, 2);
281
396
  socket.write(frame);
282
397
  }
283
398
  };
284
399
 
285
400
  clients.add(client);
401
+ console.log(`šŸ”Œ Browser connected (${clients.size} total)`);
286
402
 
287
- socket.on('close', () => {
288
- clients.delete(client);
289
- const closeTime = new Date().toLocaleTimeString();
290
- console.log(`šŸ”Œ [${closeTime}] Browser disconnected (${clients.size} remaining)`);
291
- });
403
+ socket.on('close', () => { clients.delete(client); });
404
+ socket.on('error', () => { clients.delete(client); });
405
+ });
406
+
407
+ function notifyClients() {
408
+ console.log(`šŸ”„ Reloading ${clients.size} browser(s)...`);
409
+ clients.forEach(c => { try { c.send('reload'); } catch { clients.delete(c); } });
410
+ }
411
+
412
+ // ─── File Watcher ─────────────────────────────────────────────
292
413
 
293
- socket.on('error', (err) => {
294
- clients.delete(client);
295
- console.log('āš ļø Socket error:', err.message);
414
+ const watchDirs = ['app', 'api', 'components', 'lib', 'models', 'styles', '_framework'];
415
+ let reloadTimer = null;
416
+
417
+ console.log('šŸ‘€ Watching:');
418
+ watchDirs.forEach(dir => {
419
+ const full = path.join(ROOT, dir);
420
+ if (!fs.existsSync(full)) return;
421
+ console.log(` - ${dir}/`);
422
+
423
+ fs.watch(full, { recursive: true }, (_, filename) => {
424
+ if (!filename) return;
425
+ const ext = path.extname(filename);
426
+ if (!['.js', '.jsx', '.css', '.json'].includes(ext)) return;
427
+
428
+ console.log(`\nšŸ“ Changed: ${dir}/${filename}`);
429
+ clearTimeout(reloadTimer);
430
+
431
+ if (dir === 'api') {
432
+ loadApiRoutes();
433
+ }
434
+
435
+ reloadTimer = setTimeout(notifyClients, 300);
296
436
  });
297
437
  });
298
438
 
299
- // Function to start server on available port
439
+ // ─── Start ────────────────────────────────────────────────────
440
+
300
441
  function startServer(port) {
301
442
  server.listen(port, () => {
302
- console.log(`🌐 Server running at http://localhost:${port}/`);
303
- console.log(`šŸ“± Open http://localhost:${port}/ in your browser\n`);
304
- }).on('error', (err) => {
443
+ console.log(`\nāœ… Dev server running!`);
444
+ console.log(`🌐 http://localhost:${port}/\n`);
445
+ console.log('ā¹ļø Ctrl+C to stop\n');
446
+ }).on('error', err => {
305
447
  if (err.code === 'EADDRINUSE') {
306
- console.log(`āš ļø Port ${port} is busy, trying ${port + 1}...`);
448
+ console.log(`āš ļø Port ${port} busy, trying ${port + 1}...`);
307
449
  startServer(port + 1);
308
450
  } else {
309
- console.error('āŒ Server error:', err);
451
+ console.error('āŒ', err);
310
452
  }
311
453
  });
312
454
  }
313
455
 
314
- // Watch directories
315
- const watchDirs = ['api', 'components', 'pages', 'modules', 'styles'];
316
-
317
- // Debounce to prevent multiple rapid reloads
318
- let reloadTimeout = null;
319
-
320
- console.log('šŸ‘€ Watching for changes in:');
321
- watchDirs.forEach(dir => {
322
- if (fs.existsSync(dir)) {
323
- console.log(` - ${dir}/`);
324
- fs.watch(dir, { recursive: false }, (eventType, filename) => {
325
- if (filename && (filename.endsWith('.js') || filename.endsWith('.jsx') || filename.endsWith('.css'))) {
326
- console.log(`\nšŸ“ Detected change: ${dir}/${filename}`);
327
-
328
- // Clear existing timeout
329
- if (reloadTimeout) {
330
- clearTimeout(reloadTimeout);
331
- }
332
-
333
- // Reload API routes if api/ folder changed
334
- if (dir === 'api' && filename.endsWith('.js')) {
335
- console.log('šŸ”„ Reloading API routes...');
336
- loadApiRoutes();
337
- }
338
-
339
- // For CSS changes, just reload without regenerating imports
340
- if (filename.endsWith('.css')) {
341
- reloadTimeout = setTimeout(() => {
342
- notifyClients();
343
- }, 300);
344
- } else {
345
- // For JS/JSX changes, regenerate imports
346
- reloadTimeout = setTimeout(() => {
347
- generateImports();
348
- }, 500);
349
- }
350
- }
351
- });
352
- }
353
- });
354
-
355
- console.log('\nāœ… Dev server running with live reload!');
356
- console.log('šŸ’” Create/modify files and browser will auto-refresh');
357
- console.log('ā¹ļø Press Ctrl+C to stop\n');
358
-
359
- // Load API routes
360
456
  loadApiRoutes();
361
-
362
- // Start server
363
457
  startServer(PORT);
364
-
365
- // Generate imports on startup
366
- generateImports();
367
-
368
- // Keep the process running
369
458
  process.stdin.resume();