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.
- package/CHANGELOG.md +63 -0
- package/README.md +1944 -0
- package/bin/commands/quick-setup.js +111 -0
- package/bin/commands/setup-executor.js +203 -0
- package/bin/commands/setup.js +737 -0
- package/bin/create-veko-app.js +75 -0
- package/bin/veko-update.js +205 -0
- package/bin/veko.js +188 -0
- package/error/error.ejs +382 -0
- package/index.js +36 -0
- package/lib/adapters/nextjs-adapter.js +241 -0
- package/lib/app.js +749 -0
- package/lib/core/auth-manager.js +1353 -0
- package/lib/core/auto-updater.js +1118 -0
- package/lib/core/logger.js +97 -0
- package/lib/core/module-installer.js +86 -0
- package/lib/dev/dev-server.js +292 -0
- package/lib/layout/layout-manager.js +834 -0
- package/lib/plugin-manager.js +1795 -0
- package/lib/routing/route-manager.js +1000 -0
- package/package.json +231 -0
- package/templates/public/css/style.css +2 -0
- package/templates/public/js/main.js +1 -0
- package/tsconfig.json +50 -0
- package/types/index.d.ts +238 -0
|
@@ -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;
|