vako 1.3.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.
@@ -0,0 +1,97 @@
1
+ const colors = {
2
+ reset: '\x1b[0m',
3
+ bright: '\x1b[1m',
4
+ dim: '\x1b[2m',
5
+ red: '\x1b[31m',
6
+ green: '\x1b[32m',
7
+ yellow: '\x1b[33m',
8
+ blue: '\x1b[34m',
9
+ magenta: '\x1b[35m',
10
+ cyan: '\x1b[36m',
11
+ white: '\x1b[37m',
12
+ gray: '\x1b[90m',
13
+ bgRed: '\x1b[41m',
14
+ bgGreen: '\x1b[42m',
15
+ bgYellow: '\x1b[43m',
16
+ bgBlue: '\x1b[44m',
17
+ bgMagenta: '\x1b[45m',
18
+ bgCyan: '\x1b[46m'
19
+ };
20
+
21
+ class Logger {
22
+ log(type, message, details = '') {
23
+ const timestamp = new Date().toLocaleTimeString('en-US');
24
+ const prefix = `${colors.gray}[${timestamp}]${colors.reset}`;
25
+
26
+ const logStyles = {
27
+ success: {
28
+ badge: `${colors.bgGreen}${colors.white} ✨ `,
29
+ text: `${colors.green}${colors.bright}`,
30
+ icon: '🎉'
31
+ },
32
+ error: {
33
+ badge: `${colors.bgRed}${colors.white} đŸ’Ĩ `,
34
+ text: `${colors.red}${colors.bright}`,
35
+ icon: '❌'
36
+ },
37
+ warning: {
38
+ badge: `${colors.bgYellow}${colors.white} ⚡ `,
39
+ text: `${colors.yellow}${colors.bright}`,
40
+ icon: 'âš ī¸'
41
+ },
42
+ info: {
43
+ badge: `${colors.bgBlue}${colors.white} 💎 `,
44
+ text: `${colors.blue}${colors.bright}`,
45
+ icon: 'â„šī¸'
46
+ },
47
+ server: {
48
+ badge: `${colors.bgMagenta}${colors.white} 🚀 `,
49
+ text: `${colors.magenta}${colors.bright}`,
50
+ icon: '🌟'
51
+ },
52
+ route: {
53
+ badge: `${colors.bgCyan}${colors.white} 🌐 `,
54
+ text: `${colors.cyan}${colors.bright}`,
55
+ icon: '🔗'
56
+ },
57
+ dev: {
58
+ badge: `${colors.bgBlue}${colors.white} đŸ› ī¸ `,
59
+ text: `${colors.blue}${colors.bright}`,
60
+ icon: 'âš™ī¸'
61
+ },
62
+ file: {
63
+ badge: `${colors.bgGreen}${colors.white} 📁 `,
64
+ text: `${colors.green}${colors.bright}`,
65
+ icon: '📂'
66
+ },
67
+ reload: {
68
+ badge: `${colors.bgYellow}${colors.white} 🔄 `,
69
+ text: `${colors.yellow}${colors.bright}`,
70
+ icon: '🔄'
71
+ },
72
+ create: {
73
+ badge: `${colors.bgGreen}${colors.white} ➕ `,
74
+ text: `${colors.green}${colors.bright}`,
75
+ icon: '✅'
76
+ },
77
+ delete: {
78
+ badge: `${colors.bgRed}${colors.white} đŸ—‘ī¸ `,
79
+ text: `${colors.red}${colors.bright}`,
80
+ icon: 'đŸ—‘ī¸'
81
+ },
82
+ install: {
83
+ badge: `${colors.bgMagenta}${colors.white} đŸ“Ļ `,
84
+ text: `${colors.magenta}${colors.bright}`,
85
+ icon: 'đŸ“Ļ'
86
+ }
87
+ };
88
+
89
+ const style = logStyles[type] || logStyles.info;
90
+
91
+ console.log(
92
+ `${prefix} ${style.badge}${colors.reset} ${style.text}${message}${colors.reset} ${colors.dim}${details}${colors.reset}`
93
+ );
94
+ }
95
+ }
96
+
97
+ module.exports = Logger;
@@ -0,0 +1,86 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const { execSync } = require('child_process');
4
+
5
+ class ModuleInstaller {
6
+ static requiredModules = {
7
+ 'express': '^4.18.2',
8
+ 'ejs': '^3.1.9',
9
+ 'ws': '^8.14.2',
10
+ 'chokidar': '^3.5.3',
11
+ 'chalk': '^4.1.2',
12
+ 'commander': '^11.1.0'
13
+ };
14
+
15
+ static async checkAndInstall() {
16
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
17
+ let packageJson = {};
18
+
19
+ if (fs.existsSync(packageJsonPath)) {
20
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
21
+ }
22
+
23
+ const missingModules = [];
24
+
25
+ for (const [moduleName, version] of Object.entries(this.requiredModules)) {
26
+ try {
27
+ require.resolve(moduleName);
28
+ } catch (error) {
29
+ missingModules.push({ name: moduleName, version });
30
+ }
31
+ }
32
+
33
+ if (missingModules.length > 0) {
34
+ console.log('\n🔍 Modules manquants dÊtectÊs...');
35
+ console.log('đŸ“Ļ Installation automatique en cours...\n');
36
+
37
+ for (const module of missingModules) {
38
+ await this.installModule(module.name, module.version);
39
+ }
40
+
41
+ console.log('\n✅ Tous les modules ont ÊtÊ installÊs avec succès!\n');
42
+ }
43
+ }
44
+
45
+ static async installModule(moduleName, version) {
46
+ try {
47
+ console.log(`đŸ“Ĩ Installation de ${moduleName}@${version}...`);
48
+
49
+ const command = `npm install ${moduleName}@${version}`;
50
+ execSync(command, {
51
+ stdio: 'inherit',
52
+ cwd: process.cwd()
53
+ });
54
+
55
+ console.log(`✅ ${moduleName} installÊ avec succès!`);
56
+ } catch (error) {
57
+ console.error(`❌ Erreur lors de l'installation de ${moduleName}:`, error.message);
58
+ process.exit(1);
59
+ }
60
+ }
61
+
62
+ static createPackageJsonIfNeeded() {
63
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
64
+
65
+ if (!fs.existsSync(packageJsonPath)) {
66
+ console.log('📄 CrÊation du package.json...');
67
+
68
+ const packageJson = {
69
+ name: "veko-app",
70
+ version: "1.0.0",
71
+ description: "Application Veko.js",
72
+ main: "app.js",
73
+ scripts: {
74
+ dev: "node app.js",
75
+ start: "node app.js"
76
+ },
77
+ dependencies: this.requiredModules
78
+ };
79
+
80
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
81
+ console.log('✅ package.json crÊÊ!');
82
+ }
83
+ }
84
+ }
85
+
86
+ module.exports = ModuleInstaller;
@@ -0,0 +1,292 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ let chokidar, WebSocket;
5
+
6
+ try {
7
+ chokidar = require('chokidar');
8
+ WebSocket = require('ws');
9
+ } catch (error) {
10
+ console.warn('Dev dependencies not available');
11
+ }
12
+
13
+ class DevServer {
14
+ constructor(app, options) {
15
+ this.app = app;
16
+ this.options = options;
17
+ this.wss = null;
18
+ this.watchers = [];
19
+ }
20
+
21
+ setup() {
22
+ this.setupErrorHandling();
23
+ this.setupWebSocketServer();
24
+ this.setupFileWatching();
25
+ }
26
+
27
+ setupErrorHandling() {
28
+ process.on('uncaughtException', (error) => {
29
+ this.app.logger.log('error', 'Uncaught exception', error.message);
30
+ if (this.wss) {
31
+ this.broadcast({ type: 'error', message: error.message, stack: error.stack });
32
+ }
33
+ });
34
+
35
+ process.on('unhandledRejection', (reason) => {
36
+ this.app.logger.log('error', 'Unhandled rejection', reason.toString());
37
+ if (this.wss) {
38
+ this.broadcast({ type: 'error', message: reason.toString() });
39
+ }
40
+ });
41
+ }
42
+
43
+ setupWebSocketServer() {
44
+ if (!WebSocket) {
45
+ this.app.logger.log('warning', 'WebSocket module not available', 'Hot reload disabled');
46
+ return;
47
+ }
48
+
49
+ this.wss = new WebSocket.Server({ port: this.options.wsPort });
50
+
51
+ this.wss.on('connection', (ws) => {
52
+ this.app.logger.log('dev', 'Client connected', `WebSocket on port ${this.options.wsPort}`);
53
+
54
+ ws.send(JSON.stringify({
55
+ type: 'connected',
56
+ message: 'Connected to Veko.js server ✨'
57
+ }));
58
+
59
+ if (this.options.prefetch.enabled) {
60
+ this.sendAvailableRoutes(ws);
61
+ }
62
+ });
63
+ }
64
+
65
+ setupFileWatching() {
66
+ if (!chokidar) {
67
+ this.app.logger.log('warning', 'Chokidar module not available', 'File watching disabled');
68
+ return;
69
+ }
70
+
71
+ const watchPaths = [
72
+ ...this.options.watchDirs.map(dir => path.join(process.cwd(), dir)),
73
+ path.join(process.cwd(), this.options.layouts.layoutsDir)
74
+ ];
75
+
76
+ watchPaths.forEach(watchPath => {
77
+ if (fs.existsSync(watchPath)) {
78
+ const watcher = chokidar.watch(watchPath, {
79
+ ignored: /node_modules/,
80
+ persistent: true,
81
+ ignoreInitial: true
82
+ });
83
+
84
+ watcher.on('change', (filePath) => {
85
+ this.handleFileChange(filePath);
86
+ });
87
+
88
+ watcher.on('add', (filePath) => {
89
+ this.app.logger.log('file', 'File added', `➕ ${path.relative(process.cwd(), filePath)}`);
90
+ this.handleFileChange(filePath);
91
+ });
92
+
93
+ watcher.on('unlink', (filePath) => {
94
+ this.app.logger.log('file', 'File deleted', `đŸ—‘ī¸ ${path.relative(process.cwd(), filePath)}`);
95
+ this.handleFileChange(filePath);
96
+ });
97
+
98
+ this.watchers.push(watcher);
99
+ }
100
+ });
101
+ }
102
+
103
+ handleFileChange(filePath) {
104
+ const relativePath = path.relative(process.cwd(), filePath);
105
+ this.app.logger.log('file', 'File modified', `📝 ${relativePath}`);
106
+
107
+ if (this.isRouteFile(filePath)) {
108
+ this.reloadSpecificRoute(filePath);
109
+ } else if (this.isViewFile(filePath)) {
110
+ this.broadcast({
111
+ type: 'view-reload',
112
+ file: relativePath
113
+ });
114
+ this.app.logger.log('reload', 'View reloaded', `🎨 ${relativePath}`);
115
+ } else if (this.isLayoutFile(filePath)) {
116
+ this.app.layoutManager.reloadLayouts();
117
+ this.broadcast({
118
+ type: 'layout-reload',
119
+ file: relativePath
120
+ });
121
+ this.app.logger.log('reload', 'Layout reloaded', `🎨 ${relativePath}`);
122
+ } else {
123
+ this.broadcast({ type: 'reload' });
124
+ }
125
+ }
126
+
127
+ isRouteFile(filePath) {
128
+ const routesPath = path.join(process.cwd(), this.options.routesDir);
129
+ return filePath.startsWith(routesPath) && filePath.endsWith('.js');
130
+ }
131
+
132
+ isViewFile(filePath) {
133
+ const viewsPath = path.join(process.cwd(), this.options.viewsDir);
134
+ return filePath.startsWith(viewsPath) && filePath.endsWith('.ejs');
135
+ }
136
+
137
+ isLayoutFile(filePath) {
138
+ const layoutsPath = path.join(process.cwd(), this.options.layouts.layoutsDir);
139
+ return filePath.startsWith(layoutsPath) && filePath.endsWith(this.options.layouts.extension);
140
+ }
141
+
142
+ reloadSpecificRoute(filePath) {
143
+ try {
144
+ delete require.cache[require.resolve(filePath)];
145
+
146
+ this.removeRouteFromExpress(filePath);
147
+
148
+ const routesPath = path.join(process.cwd(), this.options.routesDir);
149
+ this.app.routeManager.loadRouteFile(filePath, routesPath);
150
+
151
+ const relativePath = path.relative(process.cwd(), filePath);
152
+ this.app.logger.log('reload', 'Route reloaded', `🔄 ${relativePath}`);
153
+
154
+ this.broadcast({
155
+ type: 'route-reload',
156
+ file: relativePath,
157
+ route: this.app.routeManager.routeMap.get(filePath)
158
+ });
159
+
160
+ } catch (error) {
161
+ this.app.logger.log('error', 'Error reloading route', error.message);
162
+ this.broadcast({ type: 'reload' });
163
+ }
164
+ }
165
+
166
+ removeRouteFromExpress(filePath) {
167
+ const routePath = this.app.routeManager.routeMap.get(filePath);
168
+
169
+ if (routePath && this.app.app._router) {
170
+ this.app.app._router.stack = this.app.app._router.stack.filter(layer => {
171
+ if (layer.route && layer.route.path === routePath) {
172
+ this.app.logger.log('dev', 'Route removed from router', `đŸ—‘ī¸ ${routePath}`);
173
+ return false;
174
+ }
175
+ return true;
176
+ });
177
+ }
178
+ }
179
+
180
+ sendAvailableRoutes(ws) {
181
+ const routes = this.collectAvailableRoutes();
182
+
183
+ setTimeout(() => {
184
+ ws.send(JSON.stringify({
185
+ type: 'routes',
186
+ routes: routes,
187
+ config: this.options.prefetch
188
+ }));
189
+ this.app.logger.log('dev', 'Routes sent for prefetching', `📋 ${routes.length} routes`);
190
+ }, this.options.prefetch.prefetchDelay);
191
+ }
192
+
193
+ collectAvailableRoutes() {
194
+ const routes = ['/'];
195
+
196
+ try {
197
+ const stack = this.app.app._router?.stack || [];
198
+ stack.forEach(layer => {
199
+ if (layer.route) {
200
+ const path = layer.route.path;
201
+ if (path && !routes.includes(path)) {
202
+ routes.push(path);
203
+ }
204
+ }
205
+ });
206
+ } catch (error) {
207
+ // Ignore errors
208
+ }
209
+
210
+ return [...new Set(routes)];
211
+ }
212
+
213
+ middleware() {
214
+ return (req, res, next) => {
215
+ const start = Date.now();
216
+
217
+ res.on('finish', () => {
218
+ const duration = Date.now() - start;
219
+ const status = res.statusCode;
220
+
221
+ let logType = 'info';
222
+ if (status >= 400) logType = 'error';
223
+ else if (status >= 300) logType = 'warning';
224
+ else logType = 'success';
225
+
226
+ this.app.logger.log(logType, `${req.method} ${req.url}`, `${status} - ${duration}ms`);
227
+ });
228
+
229
+ // Inject reload script
230
+ const originalSend = res.send;
231
+
232
+ res.send = function(body) {
233
+ if (typeof body === 'string' && body.includes('</body>')) {
234
+ const reloadScript = `
235
+ <script>
236
+ (function() {
237
+ const ws = new WebSocket('ws://localhost:${req.app.locals.wsPort || 3008}');
238
+
239
+ ws.onopen = () => console.log('🔗 Veko.js connected');
240
+ ws.onmessage = (event) => {
241
+ const data = JSON.parse(event.data);
242
+
243
+ switch(data.type) {
244
+ case 'reload':
245
+ console.log('🔄 Full reload...');
246
+ setTimeout(() => window.location.reload(), 300);
247
+ break;
248
+
249
+ case 'route-reload':
250
+ console.log('🔄 Route reloaded:', data.route);
251
+ if (window.location.pathname === data.route) {
252
+ setTimeout(() => window.location.reload(), 300);
253
+ }
254
+ break;
255
+
256
+ case 'view-reload':
257
+ console.log('🎨 View reloaded:', data.file);
258
+ setTimeout(() => window.location.reload(), 300);
259
+ break;
260
+ }
261
+ };
262
+ ws.onclose = () => console.log('🔌 Veko.js disconnected');
263
+ })();
264
+ </script>
265
+ `;
266
+ body = body.replace('</body>', `${reloadScript}</body>`);
267
+ }
268
+ return originalSend.call(this, body);
269
+ };
270
+
271
+ next();
272
+ };
273
+ }
274
+
275
+ broadcast(data) {
276
+ if (this.wss) {
277
+ this.wss.clients.forEach(client => {
278
+ if (client.readyState === WebSocket.OPEN) {
279
+ client.send(JSON.stringify(data));
280
+ }
281
+ });
282
+ }
283
+ }
284
+
285
+ stop() {
286
+ this.watchers.forEach(watcher => watcher.close());
287
+ if (this.wss) this.wss.close();
288
+ this.app.logger.log('dev', 'Development server stopped', '🛑');
289
+ }
290
+ }
291
+
292
+ module.exports = DevServer;