create-aziz-app 2.0.10 ā 2.0.12
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/bin/create-aziz-app.js +36 -0
- package/package.json +1 -1
- package/template/README.md +9 -5
- package/template/api/hello.js +5 -6
- package/template/api/users.js +6 -8
- package/template/app.html +24 -25
- package/template/docs/API-ROUTES.md +538 -0
- package/template/docs/DEV-SERVER.md +108 -22
- package/template/docs/HOW-IT-WORKS.md +362 -0
- package/template/modules/api.js +20 -124
- package/template/package-lock.json +13 -0
- package/template/package.json +1 -1
- package/template/scripts/dev-server.js +263 -170
- package/template/scripts/generate-imports.js +0 -10
- package/template/scripts/live-reload-client.js +105 -0
|
@@ -6,10 +6,153 @@ const http = require('http');
|
|
|
6
6
|
const { exec } = require('child_process');
|
|
7
7
|
|
|
8
8
|
const PORT = process.env.PORT || 3000;
|
|
9
|
-
const clients =
|
|
9
|
+
const clients = new Set();
|
|
10
|
+
const apiHandlers = {};
|
|
10
11
|
|
|
11
12
|
console.log('š Dev server starting...\n');
|
|
12
13
|
|
|
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;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const files = fs.readdirSync(apiDir).filter(f => f.endsWith('.js'));
|
|
23
|
+
console.log(`š” Loading ${files.length} API routes...\n`);
|
|
24
|
+
|
|
25
|
+
files.forEach(file => {
|
|
26
|
+
const routeName = path.basename(file, '.js');
|
|
27
|
+
const filePath = path.join(apiDir, file);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Clear require cache to allow hot reload
|
|
31
|
+
delete require.cache[require.resolve(filePath)];
|
|
32
|
+
|
|
33
|
+
// Load the API handler
|
|
34
|
+
const handler = require(filePath);
|
|
35
|
+
|
|
36
|
+
if (typeof handler === 'function') {
|
|
37
|
+
apiHandlers[routeName] = handler;
|
|
38
|
+
console.log(` ā
/api/${routeName}`);
|
|
39
|
+
} else if (handler.default && typeof handler.default === 'function') {
|
|
40
|
+
apiHandlers[routeName] = handler.default;
|
|
41
|
+
console.log(` ā
/api/${routeName}`);
|
|
42
|
+
} else {
|
|
43
|
+
console.log(` ā ļø /api/${routeName} - No default export`);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(` ā /api/${routeName} - ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Parse request body
|
|
54
|
+
function parseBody(req) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
let body = '';
|
|
57
|
+
req.on('data', chunk => {
|
|
58
|
+
body += chunk.toString();
|
|
59
|
+
});
|
|
60
|
+
req.on('end', () => {
|
|
61
|
+
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
|
+
}
|
|
70
|
+
});
|
|
71
|
+
req.on('error', reject);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Parse query string
|
|
76
|
+
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;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle API requests
|
|
89
|
+
async function handleApiRequest(req, res) {
|
|
90
|
+
const urlPath = req.url.split('?')[0];
|
|
91
|
+
const routeName = urlPath.replace('/api/', '');
|
|
92
|
+
|
|
93
|
+
const handler = apiHandlers[routeName];
|
|
94
|
+
|
|
95
|
+
if (!handler) {
|
|
96
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
97
|
+
res.end(JSON.stringify({ error: 'API route not found' }));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// Parse request
|
|
103
|
+
const body = await parseBody(req);
|
|
104
|
+
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;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
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
|
+
}
|
|
155
|
+
|
|
13
156
|
// Function to generate imports
|
|
14
157
|
function generateImports() {
|
|
15
158
|
console.log('š Regenerating imports...');
|
|
@@ -27,193 +170,134 @@ function generateImports() {
|
|
|
27
170
|
// Notify all clients to reload
|
|
28
171
|
function notifyClients() {
|
|
29
172
|
const timestamp = new Date().toLocaleTimeString();
|
|
30
|
-
console.log(`š [${timestamp}] Notifying ${clients.
|
|
173
|
+
console.log(`š [${timestamp}] Notifying ${clients.size} browser(s) to reload...`);
|
|
31
174
|
clients.forEach(client => {
|
|
32
|
-
|
|
175
|
+
try {
|
|
176
|
+
client.send('reload');
|
|
177
|
+
} catch (e) {
|
|
178
|
+
clients.delete(client);
|
|
179
|
+
}
|
|
33
180
|
});
|
|
34
181
|
}
|
|
35
182
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
let evtSource;
|
|
52
|
-
let reconnectAttempts = 0;
|
|
53
|
-
const maxReconnectAttempts = 5;
|
|
54
|
-
|
|
55
|
-
function connect() {
|
|
56
|
-
try {
|
|
57
|
-
evtSource = new EventSource('/live-reload');
|
|
58
|
-
|
|
59
|
-
evtSource.onopen = function() {
|
|
60
|
-
console.log('ā
Live reload connected');
|
|
61
|
-
reconnectAttempts = 0;
|
|
62
|
-
indicator.style.background = '#4caf50';
|
|
63
|
-
indicator.textContent = 'ā
Live Reload Active';
|
|
64
|
-
setTimeout(function() {
|
|
65
|
-
indicator.style.opacity = '0.3';
|
|
66
|
-
}, 2000);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
evtSource.onmessage = function(event) {
|
|
70
|
-
if (event.data === 'reload') {
|
|
71
|
-
console.log('š Changes detected, reloading...');
|
|
72
|
-
indicator.style.opacity = '1';
|
|
73
|
-
indicator.style.background = '#2196f3';
|
|
74
|
-
indicator.textContent = 'š Reloading...';
|
|
75
|
-
setTimeout(function() {
|
|
76
|
-
// Add timestamp to force cache bust
|
|
77
|
-
var url = window.location.href.split('?')[0];
|
|
78
|
-
window.location.href = url + '?t=' + Date.now();
|
|
79
|
-
}, 100);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
evtSource.onerror = function(err) {
|
|
84
|
-
console.log('ā ļø Live reload connection error');
|
|
85
|
-
indicator.style.background = '#ff9800';
|
|
86
|
-
indicator.style.opacity = '1';
|
|
87
|
-
indicator.textContent = 'ā ļø Connection Lost';
|
|
88
|
-
|
|
89
|
-
if (evtSource) {
|
|
90
|
-
evtSource.close();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (reconnectAttempts < maxReconnectAttempts) {
|
|
94
|
-
reconnectAttempts++;
|
|
95
|
-
console.log('Reconnecting... attempt ' + reconnectAttempts);
|
|
96
|
-
setTimeout(connect, 2000);
|
|
97
|
-
} else {
|
|
98
|
-
indicator.textContent = 'ā Reload Failed';
|
|
99
|
-
console.log('Max reconnection attempts reached. Please refresh manually.');
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
} catch (e) {
|
|
103
|
-
console.error('Failed to create EventSource:', e);
|
|
104
|
-
indicator.style.background = '#f44336';
|
|
105
|
-
indicator.textContent = 'ā Connection Error';
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
connect();
|
|
110
|
-
|
|
111
|
-
// Show indicator on hover
|
|
112
|
-
indicator.addEventListener('mouseenter', function() {
|
|
113
|
-
indicator.style.opacity = '1';
|
|
114
|
-
});
|
|
115
|
-
indicator.addEventListener('mouseleave', function() {
|
|
116
|
-
if (indicator.textContent.includes('Active')) {
|
|
117
|
-
indicator.style.opacity = '0.3';
|
|
118
|
-
}
|
|
183
|
+
// Create HTTP server
|
|
184
|
+
const server = http.createServer(async (req, res) => {
|
|
185
|
+
// Handle API routes
|
|
186
|
+
if (req.url.startsWith('/api/')) {
|
|
187
|
+
await handleApiRequest(req, res);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
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'
|
|
119
197
|
});
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
198
|
+
res.end();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
124
201
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
202
|
+
// Strip query parameters for file serving (cache busting)
|
|
203
|
+
let filePath = '.' + req.url.split('?')[0];
|
|
204
|
+
if (filePath === './') {
|
|
205
|
+
filePath = './app.html';
|
|
206
|
+
}
|
|
207
|
+
|
|
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
|
+
};
|
|
227
|
+
|
|
228
|
+
const contentType = mimeTypes[extname] || 'application/octet-stream';
|
|
229
|
+
|
|
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
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
// Add cache control headers to prevent caching during development
|
|
241
|
+
res.writeHead(200, {
|
|
242
|
+
'Content-Type': contentType,
|
|
132
243
|
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
133
|
-
'
|
|
134
|
-
'
|
|
135
|
-
'X-Accel-Buffering': 'no'
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// Send initial connection message
|
|
139
|
-
res.write(':ok\n\n');
|
|
140
|
-
|
|
141
|
-
clients.push(res);
|
|
142
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
143
|
-
console.log(`š [${timestamp}] Browser connected for live reload (${clients.length} total)`);
|
|
144
|
-
|
|
145
|
-
req.on('close', () => {
|
|
146
|
-
const index = clients.indexOf(res);
|
|
147
|
-
if (index !== -1) {
|
|
148
|
-
clients.splice(index, 1);
|
|
149
|
-
}
|
|
150
|
-
const closeTime = new Date().toLocaleTimeString();
|
|
151
|
-
console.log(`š [${closeTime}] Browser disconnected (${clients.length} remaining)`);
|
|
244
|
+
'Pragma': 'no-cache',
|
|
245
|
+
'Expires': '0'
|
|
152
246
|
});
|
|
153
|
-
|
|
154
|
-
return;
|
|
247
|
+
res.end(content, 'utf-8');
|
|
155
248
|
}
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// WebSocket implementation (simple, no external dependencies)
|
|
253
|
+
server.on('upgrade', (req, socket, head) => {
|
|
254
|
+
// Simple WebSocket handshake
|
|
255
|
+
const key = req.headers['sec-websocket-key'];
|
|
256
|
+
const acceptKey = require('crypto')
|
|
257
|
+
.createHash('sha1')
|
|
258
|
+
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
|
259
|
+
.digest('base64');
|
|
260
|
+
|
|
261
|
+
socket.write(
|
|
262
|
+
'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'
|
|
267
|
+
);
|
|
156
268
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
269
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
270
|
+
console.log(`š [${timestamp}] Browser connected (${clients.size + 1} total)`);
|
|
271
|
+
|
|
272
|
+
// Wrap socket with send method
|
|
273
|
+
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);
|
|
281
|
+
socket.write(frame);
|
|
161
282
|
}
|
|
283
|
+
};
|
|
162
284
|
|
|
163
|
-
|
|
164
|
-
const mimeTypes = {
|
|
165
|
-
'.html': 'text/html',
|
|
166
|
-
'.js': 'text/javascript',
|
|
167
|
-
'.jsx': 'text/javascript',
|
|
168
|
-
'.css': 'text/css',
|
|
169
|
-
'.json': 'application/json',
|
|
170
|
-
'.png': 'image/png',
|
|
171
|
-
'.jpg': 'image/jpg',
|
|
172
|
-
'.gif': 'image/gif',
|
|
173
|
-
'.svg': 'image/svg+xml',
|
|
174
|
-
'.wav': 'audio/wav',
|
|
175
|
-
'.mp4': 'video/mp4',
|
|
176
|
-
'.woff': 'application/font-woff',
|
|
177
|
-
'.ttf': 'application/font-ttf',
|
|
178
|
-
'.eot': 'application/vnd.ms-fontobject',
|
|
179
|
-
'.otf': 'application/font-otf',
|
|
180
|
-
'.wasm': 'application/wasm'
|
|
181
|
-
};
|
|
285
|
+
clients.add(client);
|
|
182
286
|
|
|
183
|
-
|
|
287
|
+
socket.on('close', () => {
|
|
288
|
+
clients.delete(client);
|
|
289
|
+
const closeTime = new Date().toLocaleTimeString();
|
|
290
|
+
console.log(`š [${closeTime}] Browser disconnected (${clients.size} remaining)`);
|
|
291
|
+
});
|
|
184
292
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
189
|
-
res.end('<h1>404 - File Not Found</h1>', 'utf-8');
|
|
190
|
-
} else {
|
|
191
|
-
res.writeHead(500);
|
|
192
|
-
res.end('Server Error: ' + error.code, 'utf-8');
|
|
193
|
-
}
|
|
194
|
-
} else {
|
|
195
|
-
// Inject live reload script into HTML files
|
|
196
|
-
if (extname === '.html') {
|
|
197
|
-
content = content.toString().replace('</body>', getLiveReloadScript() + '</body>');
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Add cache control headers to prevent caching during development
|
|
201
|
-
res.writeHead(200, {
|
|
202
|
-
'Content-Type': contentType,
|
|
203
|
-
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
204
|
-
'Pragma': 'no-cache',
|
|
205
|
-
'Expires': '0'
|
|
206
|
-
});
|
|
207
|
-
res.end(content, 'utf-8');
|
|
208
|
-
}
|
|
209
|
-
});
|
|
293
|
+
socket.on('error', (err) => {
|
|
294
|
+
clients.delete(client);
|
|
295
|
+
console.log('ā ļø Socket error:', err.message);
|
|
210
296
|
});
|
|
211
|
-
}
|
|
297
|
+
});
|
|
212
298
|
|
|
213
299
|
// Function to start server on available port
|
|
214
300
|
function startServer(port) {
|
|
215
|
-
const server = createServer(port);
|
|
216
|
-
|
|
217
301
|
server.listen(port, () => {
|
|
218
302
|
console.log(`š Server running at http://localhost:${port}/`);
|
|
219
303
|
console.log(`š± Open http://localhost:${port}/ in your browser\n`);
|
|
@@ -246,6 +330,12 @@ watchDirs.forEach(dir => {
|
|
|
246
330
|
clearTimeout(reloadTimeout);
|
|
247
331
|
}
|
|
248
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
|
+
|
|
249
339
|
// For CSS changes, just reload without regenerating imports
|
|
250
340
|
if (filename.endsWith('.css')) {
|
|
251
341
|
reloadTimeout = setTimeout(() => {
|
|
@@ -266,6 +356,9 @@ console.log('\nā
Dev server running with live reload!');
|
|
|
266
356
|
console.log('š” Create/modify files and browser will auto-refresh');
|
|
267
357
|
console.log('ā¹ļø Press Ctrl+C to stop\n');
|
|
268
358
|
|
|
359
|
+
// Load API routes
|
|
360
|
+
loadApiRoutes();
|
|
361
|
+
|
|
269
362
|
// Start server
|
|
270
363
|
startServer(PORT);
|
|
271
364
|
|
|
@@ -31,11 +31,9 @@ function scanDirectory(dir, extensions = []) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Scan for files
|
|
34
|
-
const apiRoutes = scanDirectory('api', ['.js']);
|
|
35
34
|
const components = scanDirectory('components', ['.jsx', '.js']);
|
|
36
35
|
const pages = scanDirectory('pages', ['.jsx', '.js']);
|
|
37
36
|
|
|
38
|
-
console.log(`š” Found ${apiRoutes.length} API routes`);
|
|
39
37
|
console.log(`š§© Found ${components.length} components`);
|
|
40
38
|
console.log(`š Found ${pages.length} pages\n`);
|
|
41
39
|
|
|
@@ -48,14 +46,6 @@ scriptTags += ' <script type="text/babel" src="modules/router.js"></script>\n
|
|
|
48
46
|
scriptTags += ' <script type="text/babel" src="modules/state.js"></script>\n';
|
|
49
47
|
scriptTags += ' <script type="text/babel" src="modules/api.js"></script>\n\n';
|
|
50
48
|
|
|
51
|
-
if (apiRoutes.length > 0) {
|
|
52
|
-
scriptTags += ' <!-- Load API routes -->\n';
|
|
53
|
-
apiRoutes.forEach(route => {
|
|
54
|
-
scriptTags += ` <script type="text/babel" src="${route}"></script>\n`;
|
|
55
|
-
});
|
|
56
|
-
scriptTags += '\n';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
49
|
if (components.length > 0) {
|
|
60
50
|
scriptTags += ' <!-- Load shared components -->\n';
|
|
61
51
|
components.forEach(component => {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Live Reload Client - Always loaded, connects to dev server
|
|
2
|
+
(function() {
|
|
3
|
+
// Only run in development (when dev server is running)
|
|
4
|
+
const isDev = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
|
|
5
|
+
if (!isDev) return;
|
|
6
|
+
|
|
7
|
+
// Create visual indicator
|
|
8
|
+
const indicator = document.createElement('div');
|
|
9
|
+
indicator.id = 'live-reload-indicator';
|
|
10
|
+
indicator.style.cssText = 'position: fixed; bottom: 20px; right: 20px; background: #4caf50; color: white; padding: 8px 16px; border-radius: 20px; font-size: 12px; font-family: monospace; z-index: 9999; box-shadow: 0 2px 8px rgba(0,0,0,0.2); transition: all 0.3s; cursor: pointer;';
|
|
11
|
+
indicator.textContent = 'š Connecting...';
|
|
12
|
+
|
|
13
|
+
// Wait for body to be ready
|
|
14
|
+
function addIndicator() {
|
|
15
|
+
if (document.body) {
|
|
16
|
+
document.body.appendChild(indicator);
|
|
17
|
+
} else {
|
|
18
|
+
setTimeout(addIndicator, 100);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
addIndicator();
|
|
22
|
+
|
|
23
|
+
let ws;
|
|
24
|
+
let reconnectAttempts = 0;
|
|
25
|
+
const maxReconnectAttempts = 10;
|
|
26
|
+
const port = window.location.port || 3000;
|
|
27
|
+
|
|
28
|
+
function connect() {
|
|
29
|
+
try {
|
|
30
|
+
console.log('š Connecting to live reload WebSocket...');
|
|
31
|
+
ws = new WebSocket(`ws://localhost:${port}`);
|
|
32
|
+
|
|
33
|
+
ws.onopen = function() {
|
|
34
|
+
console.log('ā
Live reload connected');
|
|
35
|
+
reconnectAttempts = 0;
|
|
36
|
+
indicator.style.background = '#4caf50';
|
|
37
|
+
indicator.textContent = 'ā
Live Reload';
|
|
38
|
+
setTimeout(function() {
|
|
39
|
+
indicator.style.opacity = '0.3';
|
|
40
|
+
}, 2000);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
ws.onmessage = function(event) {
|
|
44
|
+
if (event.data === 'reload') {
|
|
45
|
+
console.log('š Changes detected, reloading...');
|
|
46
|
+
indicator.style.opacity = '1';
|
|
47
|
+
indicator.style.background = '#2196f3';
|
|
48
|
+
indicator.textContent = 'š Reloading...';
|
|
49
|
+
|
|
50
|
+
// Small delay to show the indicator
|
|
51
|
+
setTimeout(function() {
|
|
52
|
+
window.location.reload();
|
|
53
|
+
}, 100);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
ws.onerror = function(err) {
|
|
58
|
+
console.log('ā ļø WebSocket error:', err);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
ws.onclose = function() {
|
|
62
|
+
console.log('ā ļø Live reload disconnected');
|
|
63
|
+
indicator.style.background = '#ff9800';
|
|
64
|
+
indicator.style.opacity = '1';
|
|
65
|
+
indicator.textContent = 'ā ļø Disconnected';
|
|
66
|
+
|
|
67
|
+
// Try to reconnect
|
|
68
|
+
if (reconnectAttempts < maxReconnectAttempts) {
|
|
69
|
+
reconnectAttempts++;
|
|
70
|
+
console.log(`Reconnecting... attempt ${reconnectAttempts}`);
|
|
71
|
+
setTimeout(connect, 2000);
|
|
72
|
+
} else {
|
|
73
|
+
indicator.textContent = 'ā Offline';
|
|
74
|
+
indicator.style.background = '#f44336';
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error('Failed to create WebSocket:', e);
|
|
79
|
+
indicator.style.background = '#f44336';
|
|
80
|
+
indicator.textContent = 'ā Error';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Start connection when page loads
|
|
85
|
+
if (document.readyState === 'loading') {
|
|
86
|
+
document.addEventListener('DOMContentLoaded', connect);
|
|
87
|
+
} else {
|
|
88
|
+
connect();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Show indicator on hover
|
|
92
|
+
indicator.addEventListener('mouseenter', function() {
|
|
93
|
+
indicator.style.opacity = '1';
|
|
94
|
+
});
|
|
95
|
+
indicator.addEventListener('mouseleave', function() {
|
|
96
|
+
if (indicator.textContent.includes('Live Reload')) {
|
|
97
|
+
indicator.style.opacity = '0.3';
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Click to manually reload
|
|
102
|
+
indicator.addEventListener('click', function() {
|
|
103
|
+
window.location.reload();
|
|
104
|
+
});
|
|
105
|
+
})();
|