clovie 0.1.28 → 0.1.32
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/README.md +102 -2
- package/dist/ExpressAdapter-BsYIpF7A.js +109 -0
- package/dist/ExpressAdapter-BsYIpF7A.js.map +1 -0
- package/dist/{LiveReload-PORXKqL8.js → LiveReload-ClrG9hAe.js} +8 -2
- package/dist/{LiveReload-PORXKqL8.js.map → LiveReload-ClrG9hAe.js.map} +1 -1
- package/dist/{Server-BkZZGWiq.js → Server-GcEIC8Hj.js} +185 -160
- package/dist/Server-GcEIC8Hj.js.map +1 -0
- package/dist/cjs/ExpressAdapter--oUcRq-k.cjs +81 -0
- package/dist/cjs/ExpressAdapter--oUcRq-k.cjs.map +1 -0
- package/dist/cjs/{LiveReload-YaB3G8c8.cjs → LiveReload-BvFz-bG3.cjs} +4 -3
- package/dist/cjs/{LiveReload-YaB3G8c8.cjs.map → LiveReload-BvFz-bG3.cjs.map} +1 -1
- package/dist/cjs/{Server-BqbjrDXw.cjs → Server-DkYaMJak.cjs} +63 -14
- package/dist/cjs/Server-DkYaMJak.cjs.map +1 -0
- package/dist/cjs/{createClovie-bhagLY-k.cjs → createClovie-CJ1h3Pfo.cjs} +74 -12
- package/dist/cjs/createClovie-CJ1h3Pfo.cjs.map +1 -0
- package/dist/cjs/{index-D5kY4esA.cjs → index-DU3_mYKB.cjs} +4 -4
- package/dist/cjs/{index-D5kY4esA.cjs.map → index-DU3_mYKB.cjs.map} +1 -1
- package/dist/cjs/index.cjs +3 -2
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/{createClovie-ChsQoilz.js → createClovie-Dh7RbB5L.js} +74 -10
- package/dist/createClovie-Dh7RbB5L.js.map +1 -0
- package/dist/{index-CIb3KPoa.js → index-D8ThZ_1b.js} +6 -2
- package/dist/{index-CIb3KPoa.js.map → index-D8ThZ_1b.js.map} +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/server/AI_DEVELOPMENT_GUIDE.md +71 -8
- package/dist/Server-BkZZGWiq.js.map +0 -1
- package/dist/cjs/Server-BqbjrDXw.cjs.map +0 -1
- package/dist/cjs/createClovie-bhagLY-k.cjs.map +0 -1
- package/dist/createClovie-ChsQoilz.js.map +0 -1
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ npm run dev
|
|
|
26
26
|
- **🔄 Live Reload**: WebSocket-based live reload during development
|
|
27
27
|
- **🗄️ Database Ready**: SQLite integration for server mode applications
|
|
28
28
|
- **🛣️ Dynamic Routing**: Data-driven page generation and API endpoints
|
|
29
|
+
- **🧩 App Orchestration**: Build and serve external Vite/Webpack/Rollup/esbuild apps via kernel handlers
|
|
29
30
|
- **🔧 Service Architecture**: Modular, extensible service-oriented design
|
|
30
31
|
|
|
31
32
|
## 🏗️ Architecture Overview
|
|
@@ -279,11 +280,23 @@ export default {
|
|
|
279
280
|
// Database configuration (optional)
|
|
280
281
|
dbPath: './data/app.db',
|
|
281
282
|
|
|
282
|
-
// Express middleware
|
|
283
|
+
// Express middleware (auto-selects Express adapter)
|
|
283
284
|
middleware: [
|
|
284
285
|
express.json(),
|
|
285
286
|
express.urlencoded({ extended: true }),
|
|
286
|
-
|
|
287
|
+
|
|
288
|
+
// Authentication middleware for protected routes
|
|
289
|
+
(req, res, next) => {
|
|
290
|
+
if (req.url.startsWith('/api/protected/')) {
|
|
291
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
292
|
+
if (!token) {
|
|
293
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
294
|
+
}
|
|
295
|
+
// Verify token and attach user to request
|
|
296
|
+
req.user = verifyJWT(token);
|
|
297
|
+
}
|
|
298
|
+
next();
|
|
299
|
+
}
|
|
287
300
|
],
|
|
288
301
|
|
|
289
302
|
// API endpoints
|
|
@@ -333,6 +346,93 @@ export default {
|
|
|
333
346
|
|
|
334
347
|
## 🚀 Advanced Features
|
|
335
348
|
|
|
349
|
+
### 🔐 Middleware & Authentication
|
|
350
|
+
|
|
351
|
+
Clovie supports Express middleware for server applications. When you configure middleware, Clovie automatically uses the Express adapter for full compatibility.
|
|
352
|
+
|
|
353
|
+
**Common Authentication Pattern:**
|
|
354
|
+
```javascript
|
|
355
|
+
export default {
|
|
356
|
+
type: 'server',
|
|
357
|
+
|
|
358
|
+
middleware: [
|
|
359
|
+
// Request logging
|
|
360
|
+
(req, res, next) => {
|
|
361
|
+
console.log(`${req.method} ${req.url}`);
|
|
362
|
+
next();
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
// Selective authentication
|
|
366
|
+
(req, res, next) => {
|
|
367
|
+
// Public routes
|
|
368
|
+
const publicPaths = ['/api/login', '/api/health'];
|
|
369
|
+
if (publicPaths.some(path => req.url.startsWith(path))) {
|
|
370
|
+
return next();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Protect /api/protected/* routes
|
|
374
|
+
if (req.url.startsWith('/api/protected/')) {
|
|
375
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
376
|
+
if (!token) {
|
|
377
|
+
return res.status(401).json({ error: 'Token required' });
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
req.user = verifyJWT(token);
|
|
381
|
+
next();
|
|
382
|
+
} catch (error) {
|
|
383
|
+
res.status(401).json({ error: 'Invalid token' });
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
next();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
]
|
|
390
|
+
};
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Test Authentication:**
|
|
394
|
+
```bash
|
|
395
|
+
# Public endpoint
|
|
396
|
+
curl http://localhost:3000/api/health
|
|
397
|
+
|
|
398
|
+
# Protected endpoint (fails)
|
|
399
|
+
curl http://localhost:3000/api/protected/data
|
|
400
|
+
|
|
401
|
+
# Protected endpoint (works)
|
|
402
|
+
curl -H "Authorization: Bearer your-token" http://localhost:3000/api/protected/data
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### 🧩 App Orchestration
|
|
406
|
+
|
|
407
|
+
Clovie can manage multiple frontend apps alongside your core project using kernel-level handlers, so no Express-specific middleware is required. Define apps in `clovie.config.js`, and Clovie will:
|
|
408
|
+
|
|
409
|
+
- Detect the build tool (Vite, Webpack, Rollup, esbuild) or use the specified `buildTool`
|
|
410
|
+
- Run builds during `clovie build` and `clovie serve`
|
|
411
|
+
- Launch tool-specific dev middleware during `clovie dev`
|
|
412
|
+
- Mount bundles or dev servers at configurable mount paths (e.g., `/admin`, `/studio`)
|
|
413
|
+
|
|
414
|
+
```javascript
|
|
415
|
+
export default {
|
|
416
|
+
type: 'server',
|
|
417
|
+
apps: [
|
|
418
|
+
{
|
|
419
|
+
name: 'studio',
|
|
420
|
+
source: './apps/studio',
|
|
421
|
+
buildTool: 'vite',
|
|
422
|
+
buildOptions: {
|
|
423
|
+
watch: true,
|
|
424
|
+
build: { outDir: './apps/studio/dist' }
|
|
425
|
+
},
|
|
426
|
+
dev: {
|
|
427
|
+
mountPath: '/studio'
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
]
|
|
431
|
+
};
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
See [Apps Integration](docs/CONFIGURATION.md#apps-integration) for full examples covering Webpack, Rollup, and esbuild setups.
|
|
435
|
+
|
|
336
436
|
### 📊 Database Integration (Server Mode)
|
|
337
437
|
|
|
338
438
|
Clovie includes built-in SQLite database support for server applications:
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { A as AdapterInterface } from "./Server-GcEIC8Hj.js";
|
|
2
|
+
|
|
3
|
+
import "./createClovie-Dh7RbB5L.js";
|
|
4
|
+
|
|
5
|
+
import "stream";
|
|
6
|
+
|
|
7
|
+
import "module";
|
|
8
|
+
|
|
9
|
+
import "path";
|
|
10
|
+
|
|
11
|
+
import "crypto";
|
|
12
|
+
|
|
13
|
+
import "fs";
|
|
14
|
+
|
|
15
|
+
import "istextorbinary";
|
|
16
|
+
|
|
17
|
+
import "chokidar";
|
|
18
|
+
|
|
19
|
+
import "readline";
|
|
20
|
+
|
|
21
|
+
import "events";
|
|
22
|
+
|
|
23
|
+
import "node:process";
|
|
24
|
+
|
|
25
|
+
import "node:os";
|
|
26
|
+
|
|
27
|
+
import "node:tty";
|
|
28
|
+
|
|
29
|
+
import "fs/promises";
|
|
30
|
+
|
|
31
|
+
import "child_process";
|
|
32
|
+
|
|
33
|
+
import "url";
|
|
34
|
+
|
|
35
|
+
import "node:http";
|
|
36
|
+
|
|
37
|
+
import "util";
|
|
38
|
+
|
|
39
|
+
class ExpressAdapter extends AdapterInterface {
|
|
40
|
+
#connections=new Set;
|
|
41
|
+
constructor(express = null) {
|
|
42
|
+
super("express"), this.express = express, this.app = null;
|
|
43
|
+
}
|
|
44
|
+
async initialize(kernel, opts = {}, log = null) {
|
|
45
|
+
if (await super.initialize(kernel, opts, log), !this.express) try {
|
|
46
|
+
this.express = await import("express"), this.express = this.express.default;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error("Express not installed. Run: npm install express");
|
|
49
|
+
}
|
|
50
|
+
if (this.app = this.express(), opts.middleware && this.#hasBodyParser(opts.middleware) || (this.app.use(this.express.json()),
|
|
51
|
+
this.app.use(this.express.urlencoded({
|
|
52
|
+
extended: !0
|
|
53
|
+
}))), opts.middleware && Array.isArray(opts.middleware)) {
|
|
54
|
+
this.log?.info(`Applying ${opts.middleware.length} middleware functions`);
|
|
55
|
+
for (const [index, middleware] of opts.middleware.entries()) try {
|
|
56
|
+
this.app.use(middleware), this.log?.debug(`Applied middleware ${index + 1}/${opts.middleware.length}`);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new Error(`Failed to apply middleware[${index}]: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
registerKernelHandler() {
|
|
63
|
+
this.app && this.app.all("*", async (req, res) => {
|
|
64
|
+
await this.handleRequest(req, res);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
getExpressApp() {
|
|
68
|
+
return this.app;
|
|
69
|
+
}
|
|
70
|
+
async start({port: port = 3e3, host: host = "0.0.0.0"} = {}) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
this.server = this.app.listen(port, host, err => {
|
|
73
|
+
err ? reject(err) : (this.server.on("connection", conn => {
|
|
74
|
+
this.#connections.add(conn), conn.on("close", () => {
|
|
75
|
+
this.#connections.delete(conn);
|
|
76
|
+
});
|
|
77
|
+
}), this.log?.info(`Express Server with middleware listening on http://${host}:${port}`),
|
|
78
|
+
resolve(this.server));
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async stop() {
|
|
83
|
+
if (this.server?.close) {
|
|
84
|
+
for (const conn of this.#connections) conn.destroy();
|
|
85
|
+
return this.#connections.clear(), new Promise(resolve => {
|
|
86
|
+
this.server.close(() => {
|
|
87
|
+
this.log?.info("Express Server stopped"), resolve();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async handleRequest(req, res) {
|
|
93
|
+
try {
|
|
94
|
+
const ctx = this.createContext(req, res), response = await this.kernel.handle(ctx);
|
|
95
|
+
await this.sendResponse(res, response, "HEAD" === req.method);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
await this.handleError(res, error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
#hasBodyParser(middlewareArray) {
|
|
101
|
+
return middlewareArray.some(mw => {
|
|
102
|
+
const name = mw.name?.toLowerCase() || mw.toString().toLowerCase();
|
|
103
|
+
return name.includes("json") || name.includes("urlencoded") || name.includes("bodyparser");
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { ExpressAdapter };
|
|
109
|
+
//# sourceMappingURL=ExpressAdapter-BsYIpF7A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpressAdapter-BsYIpF7A.js","sources":["../lib/Server/adapters/ExpressAdapter.js"],"sourcesContent":["/**\n * Express adapter for Clovie with full middleware support\n */\nimport { AdapterInterface } from './AdapterInterface.js';\n\nexport class ExpressAdapter extends AdapterInterface {\n #connections = new Set();\n\n constructor(express = null) {\n super('express');\n this.express = express;\n this.app = null;\n }\n\n async initialize(kernel, opts = {}, log = null) {\n await super.initialize(kernel, opts, log);\n \n // Import Express\n if (!this.express) {\n try {\n this.express = await import('express');\n this.express = this.express.default;\n } catch (error) {\n throw new Error('Express not installed. Run: npm install express');\n }\n }\n\n // Create Express app\n this.app = this.express();\n\n // Apply default middleware (can be overridden by user middleware)\n if (!opts.middleware || !this.#hasBodyParser(opts.middleware)) {\n this.app.use(this.express.json());\n this.app.use(this.express.urlencoded({ extended: true }));\n }\n\n // Apply user-defined middleware\n if (opts.middleware && Array.isArray(opts.middleware)) {\n this.log?.info(`Applying ${opts.middleware.length} middleware functions`);\n \n for (const [index, middleware] of opts.middleware.entries()) {\n try {\n this.app.use(middleware);\n this.log?.debug(`Applied middleware ${index + 1}/${opts.middleware.length}`);\n } catch (error) {\n throw new Error(`Failed to apply middleware[${index}]: ${error.message}`);\n }\n }\n }\n\n // Additional middleware will be mounted by the server before kernel handler registration\n }\n\n registerKernelHandler() {\n if (!this.app) {\n return;\n }\n\n this.app.all('*', async (req, res) => {\n await this.handleRequest(req, res);\n });\n }\n\n getExpressApp() {\n return this.app;\n }\n\n async start({ port = 3000, host = '0.0.0.0' } = {}) {\n return new Promise((resolve, reject) => {\n this.server = this.app.listen(port, host, (err) => {\n if (err) {\n reject(err);\n } else {\n // Track connections for clean shutdown\n this.server.on('connection', (conn) => {\n this.#connections.add(conn);\n conn.on('close', () => {\n this.#connections.delete(conn);\n });\n });\n\n this.log?.info(`Express Server with middleware listening on http://${host}:${port}`);\n resolve(this.server);\n }\n });\n });\n }\n\n async stop() {\n if (this.server?.close) {\n // Destroy all active connections\n for (const conn of this.#connections) {\n conn.destroy();\n }\n this.#connections.clear();\n\n return new Promise((resolve) => {\n this.server.close(() => {\n this.log?.info('Express Server stopped');\n resolve();\n });\n });\n }\n }\n\n async handleRequest(req, res) {\n try {\n // Create context from Express request\n const ctx = this.createContext(req, res);\n \n // Delegate to kernel for route matching and handling\n const response = await this.kernel.handle(ctx);\n \n // Send response using shared method\n await this.sendResponse(res, response, req.method === 'HEAD');\n\n } catch (error) {\n await this.handleError(res, error);\n }\n }\n\n /**\n * Check if middleware array contains body parser\n * @private\n */\n #hasBodyParser(middlewareArray) {\n return middlewareArray.some(mw => {\n // Check for common body parser middleware names\n const name = mw.name?.toLowerCase() || mw.toString().toLowerCase();\n return name.includes('json') || name.includes('urlencoded') || name.includes('bodyparser');\n });\n }\n}\n"],"names":["ExpressAdapter","AdapterInterface","connections","Set","constructor","express","super","this","app","initialize","kernel","opts","log","import","default","error","Error","middleware","hasBodyParser","use","json","urlencoded","extended","Array","isArray","info","length","index","entries","debug","message","registerKernelHandler","all","async","req","res","handleRequest","getExpressApp","start","port","host","Promise","resolve","reject","server","listen","err","on","conn","add","delete","stop","close","destroy","clear","ctx","createContext","response","handle","sendResponse","method","handleError","middlewareArray","some","mw","name","toLowerCase","toString","includes"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKO,MAAMA,uBAAuBC;IAClCC,aAAe,IAAIC;IAEnB,WAAAC,CAAYC,UAAU;QACpBC,MAAM,YACNC,KAAKF,UAAUA,SACfE,KAAKC,MAAM;AACb;IAEA,gBAAMC,CAAWC,QAAQC,OAAO,CAAA,GAAIC,MAAM;QAIxC,UAHMN,MAAMG,WAAWC,QAAQC,MAAMC,OAGhCL,KAAKF,SACR;YACEE,KAAKF,gBAAgBQ,OAAO,YAC5BN,KAAKF,UAAUE,KAAKF,QAAQS;AAC9B,UAAE,OAAOC;YACP,MAAM,IAAIC,MAAM;AAClB;QAaF,IATAT,KAAKC,MAAMD,KAAKF,WAGXM,KAAKM,cAAeV,MAAKW,cAAeP,KAAKM,gBAChDV,KAAKC,IAAIW,IAAIZ,KAAKF,QAAQe;QAC1Bb,KAAKC,IAAIW,IAAIZ,KAAKF,QAAQgB,WAAW;YAAEC,WAAU;cAI/CX,KAAKM,cAAcM,MAAMC,QAAQb,KAAKM,aAAa;YACrDV,KAAKK,KAAKa,KAAK,YAAYd,KAAKM,WAAWS;YAE3C,KAAK,OAAOC,OAAOV,eAAeN,KAAKM,WAAWW,WAChD;gBACErB,KAAKC,IAAIW,IAAIF,aACbV,KAAKK,KAAKiB,MAAM,sBAAsBF,QAAQ,KAAKhB,KAAKM,WAAWS;AACrE,cAAE,OAAOX;gBACP,MAAM,IAAIC,MAAM,8BAA8BW,WAAWZ,MAAMe;AACjE;AAEJ;AAGF;IAEA,qBAAAC;QACOxB,KAAKC,OAIVD,KAAKC,IAAIwB,IAAI,KAAKC,OAAOC,KAAKC;kBACtB5B,KAAK6B,cAAcF,KAAKC;;AAElC;IAEA,aAAAE;QACE,OAAO9B,KAAKC;AACd;IAEA,WAAM8B,EAAMC,MAAEA,OAAO,KAAIC,MAAEA,OAAO,aAAc;QAC9C,OAAO,IAAIC,QAAQ,CAACC,SAASC;YAC3BpC,KAAKqC,SAASrC,KAAKC,IAAIqC,OAAON,MAAMC,MAAOM;gBACrCA,MACFH,OAAOG,QAGPvC,KAAKqC,OAAOG,GAAG,cAAeC;oBAC5BzC,MAAKL,YAAa+C,IAAID,OACtBA,KAAKD,GAAG,SAAS;wBACfxC,MAAKL,YAAagD,OAAOF;;oBAI7BzC,KAAKK,KAAKa,KAAK,sDAAsDe,QAAQD;gBAC7EG,QAAQnC,KAAKqC;;;AAIrB;IAEA,UAAMO;QACJ,IAAI5C,KAAKqC,QAAQQ,OAAO;YAEtB,KAAK,MAAMJ,QAAQzC,MAAKL,aACtB8C,KAAKK;YAIP,OAFA9C,MAAKL,YAAaoD,SAEX,IAAIb,QAASC;gBAClBnC,KAAKqC,OAAOQ,MAAM;oBAChB7C,KAAKK,KAAKa,KAAK,2BACfiB;;;AAGN;AACF;IAEA,mBAAMN,CAAcF,KAAKC;QACvB;YAEE,MAAMoB,MAAMhD,KAAKiD,cAActB,KAAKC,MAG9BsB,iBAAiBlD,KAAKG,OAAOgD,OAAOH;kBAGpChD,KAAKoD,aAAaxB,KAAKsB,UAAyB,WAAfvB,IAAI0B;AAE7C,UAAE,OAAO7C;kBACDR,KAAKsD,YAAY1B,KAAKpB;AAC9B;AACF;IAMA,cAAAG,CAAe4C;QACb,OAAOA,gBAAgBC,KAAKC;YAE1B,MAAMC,OAAOD,GAAGC,MAAMC,iBAAiBF,GAAGG,WAAWD;YACrD,OAAOD,KAAKG,SAAS,WAAWH,KAAKG,SAAS,iBAAiBH,KAAKG,SAAS;;AAEjF;;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Server } from "socket.io";
|
|
2
2
|
|
|
3
|
-
import { S as ServiceProvider, q as queueMacrotask } from "./createClovie-
|
|
3
|
+
import { S as ServiceProvider, q as queueMacrotask } from "./createClovie-Dh7RbB5L.js";
|
|
4
4
|
|
|
5
5
|
import "stream";
|
|
6
6
|
|
|
@@ -26,6 +26,12 @@ import "node:os";
|
|
|
26
26
|
|
|
27
27
|
import "node:tty";
|
|
28
28
|
|
|
29
|
+
import "fs/promises";
|
|
30
|
+
|
|
31
|
+
import "child_process";
|
|
32
|
+
|
|
33
|
+
import "url";
|
|
34
|
+
|
|
29
35
|
class LiveReload extends ServiceProvider {
|
|
30
36
|
static manifest={
|
|
31
37
|
name: "Clovie LiveReload",
|
|
@@ -92,4 +98,4 @@ class LiveReload extends ServiceProvider {
|
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
export { LiveReload };
|
|
95
|
-
//# sourceMappingURL=LiveReload-
|
|
101
|
+
//# sourceMappingURL=LiveReload-ClrG9hAe.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LiveReload-
|
|
1
|
+
{"version":3,"file":"LiveReload-ClrG9hAe.js","sources":["../lib/LiveReload.js"],"sourcesContent":["import { Server as SocketIOServer } from 'socket.io';\nimport { ServiceProvider } from '@brickworks/engine';\nimport { queueMacrotask } from './utils/tasks.js';\n\nexport class LiveReload extends ServiceProvider {\n static manifest = {\n name: 'Clovie LiveReload',\n namespace: 'liveReload',\n version: '1.0.0',\n };\n\n #io;\n #reloading = false;\n\n getter () {\n return {\n io: () => this.#io,\n }\n }\n\n actions() {\n return {\n initializeServer: async (server, opts) => {\n const log = this.useContext('log');\n if (opts.mode === 'development') {\n await this.#setupSocketIO(server, log);\n }\n },\n notifyReload: () => {\n if (this.#reloading) return;\n this.#reloading = true;\n \n queueMacrotask(() => {\n this.#reloading = false;\n if (this.#io) {\n this.#io.emit('reload')\n }\n });\n },\n injectLiveReloadScript: async (renderedContent, opts) => {\n const lastBodyIndex = renderedContent.lastIndexOf('</body>');\n if (lastBodyIndex !== -1) {\n const scriptConfig = { \n mode: opts.mode || 'development', \n port: opts.port || 3000 \n };\n \n try {\n // Import live reload script dynamically \n renderedContent = renderedContent.substring(0, lastBodyIndex) + \n this.#liveReloadScript(scriptConfig) + '\\n' + \n renderedContent.substring(lastBodyIndex);\n } catch (err) {\n console.warn('⚠️ Could not load live reload script:', err.message);\n }\n }\n return renderedContent;\n }\n }\n }\n\n async #setupSocketIO(server, log) {\n try {\n // Wait a bit for server to be fully initialized\n await new Promise(resolve => setTimeout(resolve, 100));\n\n log.debug('Setting up Socket.IO...');\n \n // Configure Socket.IO with proper CORS and options\n this.#io = new SocketIOServer(server, {\n cors: {\n origin: \"*\",\n methods: [\"GET\", \"POST\"]\n },\n transports: ['polling', 'websocket'],\n allowEIO3: true\n });\n\n this.#io.on('connection', (socket) => {\n log.debug(`Client connected: ${socket.id}`);\n \n socket.on('disconnect', (reason) => {\n log.debug(`Client disconnected: ${socket.id} - ${reason}`);\n });\n \n socket.on('error', (error) => {\n log.error('Socket error:', error);\n });\n });\n\n log.info('Socket.IO server ready');\n } catch (error) {\n log.error('Error setting up Socket.IO:', error);\n }\n }\n\n #liveReloadScript = (opts) => `<!-- Live Reload Script (Development Mode Only) -->\n<script src=\"https://cdn.socket.io/4.7.4/socket.io.min.js\"></script>\n<script>\n (function() {\n const opts = ${JSON.stringify(opts)};\n \n console.log('Initializing live reload with opts:', opts);\n \n // Configure Socket.IO client with proper options\n const socket = io({\n transports: ['polling', 'websocket'],\n timeout: 20000,\n forceNew: true\n });\n \n socket.on('reload', () => {\n console.log('Live reload triggered');\n window.location.reload();\n });\n \n socket.on('connect', () => {\n console.log('Connected to live reload server');\n });\n \n socket.on('disconnect', (reason) => {\n console.log('Disconnected from live reload server:', reason);\n });\n \n socket.on('connect_error', (error) => {\n console.error('Connection error:', error);\n console.log('Retrying connection in 3 seconds...');\n setTimeout(() => {\n socket.connect();\n }, 3000);\n });\n \n socket.on('error', (error) => {\n console.error('Socket error:', error);\n });\n \n // Add connection timeout\n setTimeout(() => {\n if (!socket.connected) {\n console.warn('Live reload connection timeout - server may not be running');\n }\n }, 5000);\n })();\n</script>`;\n}"],"names":["LiveReload","ServiceProvider","static","name","namespace","version","io","reloading","getter","this","actions","initializeServer","async","server","opts","log","useContext","mode","setupSocketIO","notifyReload","queueMacrotask","emit","injectLiveReloadScript","renderedContent","lastBodyIndex","lastIndexOf","scriptConfig","port","substring","liveReloadScript","err","console","warn","message","Promise","resolve","setTimeout","debug","SocketIOServer","cors","origin","methods","transports","allowEIO3","on","socket","id","reason","error","info","JSON","stringify"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIO,MAAMA,mBAAmBC;IAC9BC,gBAAkB;QAChBC,MAAM;QACNC,WAAW;QACXC,SAAS;;IAGXC;IACAC,YAAa;IAEb,MAAAC;QACE,OAAO;YACLF,IAAI,MAAMG,MAAKH;;AAEnB;IAEA,OAAAI;QACE,OAAO;YACLC,kBAAkBC,OAAOC,QAAQC;gBAC/B,MAAMC,MAAMN,KAAKO,WAAW;gBACV,kBAAdF,KAAKG,cACDR,MAAKS,cAAeL,QAAQE;;YAGtCI,cAAc;gBACRV,MAAKF,cACTE,MAAKF,aAAa,GAElBa,eAAe;oBACbX,MAAKF,aAAa,GACdE,MAAKH,MACPG,MAAKH,GAAIe,KAAK;;;YAIpBC,wBAAwBV,OAAOW,iBAAiBT;gBAC9C,MAAMU,gBAAgBD,gBAAgBE,YAAY;gBAClD,KAAsB,MAAlBD,eAAsB;oBACxB,MAAME,eAAe;wBACnBT,MAAMH,KAAKG,QAAQ;wBACnBU,MAAMb,KAAKa,QAAQ;;oBAGrB;wBAEEJ,kBAAkBA,gBAAgBK,UAAU,GAAGJ,iBAC/Bf,MAAKoB,iBAAkBH,gBAAgB,OACvCH,gBAAgBK,UAAUJ;AAC5C,sBAAE,OAAOM;wBACPC,QAAQC,KAAK,0CAA0CF,IAAIG;AAC7D;AACF;gBACA,OAAOV;;;AAGb;IAEA,oBAAML,CAAeL,QAAQE;QAC3B;kBAEQ,IAAImB,QAAQC,WAAWC,WAAWD,SAAS,OAEjDpB,IAAIsB,MAAM;YAGV5B,MAAKH,KAAM,IAAIgC,OAAezB,QAAQ;gBACpC0B,MAAM;oBACJC,QAAQ;oBACRC,SAAS,EAAC,OAAO;;gBAEnBC,YAAY,EAAC,WAAW;gBACxBC,YAAW;gBAGblC,MAAKH,GAAIsC,GAAG,cAAeC;gBACzB9B,IAAIsB,MAAM,qBAAqBQ,OAAOC,OAEtCD,OAAOD,GAAG,cAAeG;oBACvBhC,IAAIsB,MAAM,wBAAwBQ,OAAOC,QAAQC;oBAGnDF,OAAOD,GAAG,SAAUI;oBAClBjC,IAAIiC,MAAM,iBAAiBA;;gBAI/BjC,IAAIkC,KAAK;AACX,UAAE,OAAOD;YACPjC,IAAIiC,MAAM,+BAA+BA;AAC3C;AACF;IAEAnB,kBAAqBf,QAAS,iLAIboC,KAAKC,UAAUrC;;;"}
|