opticore-webapp 1.0.70 → 1.0.71
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/dist/index.cjs +506 -24
- package/dist/index.d.cts +178 -1
- package/dist/index.d.ts +178 -1
- package/dist/index.js +529 -48
- package/dist/utils/translations/message.translation.en.json +4 -13
- package/dist/utils/translations/message.translation.fr.json +6 -2
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// src/core/webServer.core.ts
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
2
|
+
import * as path3 from "path";
|
|
3
|
+
import process6 from "process";
|
|
4
4
|
import corsOrigin from "cors";
|
|
5
5
|
import { CEventNameError as eventName2, ServerListenEventError as ServerListenEventError2 } from "opticore-catch-exception-error";
|
|
6
6
|
import { express as express2 } from "opticore-express";
|
|
7
7
|
import { getEnvironmentValue as getEnvironmentValue2 } from "opticore-env-access";
|
|
8
8
|
import { HttpStatusCode as HttpStatusCode3 } from "opticore-http-response";
|
|
9
|
-
import { TranslationLoader as
|
|
9
|
+
import { TranslationLoader as TranslationLoader3 } from "opticore-translator";
|
|
10
10
|
import { requestCallsEvent } from "opticore-request-call-event";
|
|
11
11
|
|
|
12
12
|
// src/core/handlers/eventProcess.handler.ts
|
|
13
|
-
import
|
|
13
|
+
import process2 from "process";
|
|
14
14
|
import EventEmitter from "events";
|
|
15
15
|
import { express } from "opticore-express";
|
|
16
16
|
import { ServerListenEventError, CEventNameError as eventName, CEvent as event } from "opticore-catch-exception-error";
|
|
@@ -21,42 +21,42 @@ function eventProcessHandler(localeLanguage) {
|
|
|
21
21
|
errorEmitter.on(eventName.error, (error) => {
|
|
22
22
|
serverListenEvent.listenerError(error);
|
|
23
23
|
});
|
|
24
|
-
|
|
24
|
+
process2.on(event.beforeExit, (code) => {
|
|
25
25
|
setTimeout(() => {
|
|
26
26
|
serverListenEvent.processBeforeExit(code);
|
|
27
27
|
}, 100);
|
|
28
28
|
});
|
|
29
|
-
|
|
29
|
+
process2.on(event.disconnect, () => {
|
|
30
30
|
serverListenEvent.processDisconnected();
|
|
31
31
|
});
|
|
32
|
-
|
|
32
|
+
process2.on(event.exit, (code) => {
|
|
33
33
|
serverListenEvent.exited(code);
|
|
34
34
|
});
|
|
35
|
-
|
|
35
|
+
process2.on(event.rejectionHandled, (promise) => {
|
|
36
36
|
serverListenEvent.promiseRejectionHandled(promise);
|
|
37
37
|
});
|
|
38
|
-
|
|
38
|
+
process2.on(event.uncaughtException, (error) => {
|
|
39
39
|
serverListenEvent.uncaughtException(error);
|
|
40
40
|
});
|
|
41
|
-
|
|
41
|
+
process2.on(event.uncaughtExceptionMonitor, (error) => {
|
|
42
42
|
serverListenEvent.uncaughtExceptionMonitor(error);
|
|
43
43
|
});
|
|
44
|
-
|
|
44
|
+
process2.on(event.unhandledRejection, (reason, promise) => {
|
|
45
45
|
serverListenEvent.unhandledRejection(reason, promise);
|
|
46
46
|
});
|
|
47
|
-
|
|
47
|
+
process2.on(event.warning, (warning) => {
|
|
48
48
|
serverListenEvent.warning(warning);
|
|
49
49
|
});
|
|
50
|
-
|
|
50
|
+
process2.on(event.message, (message) => {
|
|
51
51
|
serverListenEvent.message(message);
|
|
52
52
|
});
|
|
53
|
-
|
|
53
|
+
process2.on(event.multipleResolves, (type, promise, reason) => {
|
|
54
54
|
serverListenEvent.multipleResolves(type, promise, reason);
|
|
55
55
|
});
|
|
56
|
-
|
|
56
|
+
process2.on(event.sigint, () => {
|
|
57
57
|
serverListenEvent.processInterrupted();
|
|
58
58
|
});
|
|
59
|
-
|
|
59
|
+
process2.on(event.sigterm, (signal) => {
|
|
60
60
|
serverListenEvent.sigtermSignalReceived(signal);
|
|
61
61
|
});
|
|
62
62
|
app.use((err, req, res, next) => {
|
|
@@ -65,7 +65,7 @@ function eventProcessHandler(localeLanguage) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// src/application/service/core.service.ts
|
|
68
|
-
import
|
|
68
|
+
import process4 from "process";
|
|
69
69
|
import chalk from "chalk";
|
|
70
70
|
import * as path from "path";
|
|
71
71
|
import * as fs from "fs";
|
|
@@ -75,8 +75,8 @@ import { TranslationLoader } from "opticore-translator";
|
|
|
75
75
|
import { getEnvironmentValue } from "opticore-env-access";
|
|
76
76
|
|
|
77
77
|
// src/utils/envPath.utils.ts
|
|
78
|
-
import
|
|
79
|
-
var envPath =
|
|
78
|
+
import process3 from "process";
|
|
79
|
+
var envPath = process3.cwd() + "/config/env/.env";
|
|
80
80
|
|
|
81
81
|
// src/application/service/loaderTranslationFile.service.ts
|
|
82
82
|
import { translationLoaderConfig } from "opticore-loader-translation";
|
|
@@ -112,16 +112,35 @@ var dependenciesContainerProvider = (localLang) => {
|
|
|
112
112
|
return new SContainer(localLang, dependencies);
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
+
// src/application/service/core.service.ts
|
|
116
|
+
import { SContainer as SContainer2 } from "opticore-dependency-inject";
|
|
117
|
+
|
|
118
|
+
// src/utils/parsingDateTime.utils.ts
|
|
119
|
+
var parsingDateTimeUtils = (localFormatTime) => {
|
|
120
|
+
const now = /* @__PURE__ */ new Date();
|
|
121
|
+
const formatter = new Intl.DateTimeFormat(localFormatTime, {
|
|
122
|
+
day: "2-digit",
|
|
123
|
+
month: "2-digit",
|
|
124
|
+
year: "numeric",
|
|
125
|
+
hour: "2-digit",
|
|
126
|
+
minute: "2-digit",
|
|
127
|
+
hour12: false
|
|
128
|
+
});
|
|
129
|
+
return formatter.format(now);
|
|
130
|
+
};
|
|
131
|
+
|
|
115
132
|
// src/application/service/core.service.ts
|
|
116
133
|
var CoreService = class {
|
|
117
134
|
localLanguage;
|
|
118
135
|
environmentPath;
|
|
119
136
|
serverLog;
|
|
120
137
|
logger;
|
|
138
|
+
container;
|
|
121
139
|
constructor(localLang, environmentPath) {
|
|
122
140
|
this.loadTranslationFiles();
|
|
123
141
|
this.environmentPath = environmentPath;
|
|
124
142
|
this.localLanguage = localLang;
|
|
143
|
+
this.container = new SContainer2(localLang);
|
|
125
144
|
this.serverLog = dependenciesContainerProvider(localLang).resolve("OpticoreLogger");
|
|
126
145
|
this.logger = dependenciesContainerProvider(localLang).resolve("LoggerCore");
|
|
127
146
|
}
|
|
@@ -148,7 +167,7 @@ var CoreService = class {
|
|
|
148
167
|
getEnvFileLoading(filePath) {
|
|
149
168
|
this.loadTranslationFiles();
|
|
150
169
|
try {
|
|
151
|
-
const fullPath = path.resolve(
|
|
170
|
+
const fullPath = path.resolve(process4.cwd(), filePath);
|
|
152
171
|
if (fs.existsSync(fullPath)) {
|
|
153
172
|
const env = fs.readFileSync(fullPath, "utf-8");
|
|
154
173
|
const lines = env.split("\n");
|
|
@@ -156,7 +175,7 @@ var CoreService = class {
|
|
|
156
175
|
const match = line.match(/^([^#=]+)=([^#]+)$/);
|
|
157
176
|
if (match) {
|
|
158
177
|
const key = match[1].trim();
|
|
159
|
-
|
|
178
|
+
process4.env[key] = match[2].trim();
|
|
160
179
|
}
|
|
161
180
|
});
|
|
162
181
|
}
|
|
@@ -174,7 +193,7 @@ var CoreService = class {
|
|
|
174
193
|
* Returns an Object containing a node version, openssl, and v0
|
|
175
194
|
*/
|
|
176
195
|
getVersions() {
|
|
177
|
-
const { node, openssl, v8 } =
|
|
196
|
+
const { node, openssl, v8 } = process4.versions;
|
|
178
197
|
const data = {
|
|
179
198
|
"node version": node,
|
|
180
199
|
"openssl": openssl,
|
|
@@ -195,14 +214,14 @@ var CoreService = class {
|
|
|
195
214
|
*/
|
|
196
215
|
getUsageMemory() {
|
|
197
216
|
this.loadTranslationFiles();
|
|
198
|
-
const memoryData =
|
|
217
|
+
const memoryData = process4.memoryUsage();
|
|
199
218
|
const data = {
|
|
200
219
|
[TranslationLoader.t("totalMemoryAllocated", this.localLanguage)]: this.formatMemoryUsage(memoryData.rss),
|
|
201
220
|
[TranslationLoader.t("sizeAllocatedHeap", this.localLanguage)]: this.formatMemoryUsage(memoryData.heapTotal),
|
|
202
221
|
[TranslationLoader.t("memoryUsedExecution", this.localLanguage)]: this.formatMemoryUsage(memoryData.heapUsed),
|
|
203
222
|
[TranslationLoader.t("externalMemory", this.localLanguage)]: this.formatMemoryUsage(memoryData.external),
|
|
204
|
-
[TranslationLoader.t("memoryUsageUser", this.localLanguage)]: this.formatMemoryUsage(
|
|
205
|
-
[TranslationLoader.t("memoryUsageSystem", this.localLanguage)]: this.formatMemoryUsage(
|
|
223
|
+
[TranslationLoader.t("memoryUsageUser", this.localLanguage)]: this.formatMemoryUsage(process4.cpuUsage().user),
|
|
224
|
+
[TranslationLoader.t("memoryUsageSystem", this.localLanguage)]: this.formatMemoryUsage(process4.cpuUsage().system)
|
|
206
225
|
};
|
|
207
226
|
return {
|
|
208
227
|
"rss": data[TranslationLoader.t("totalMemoryAllocated", this.localLanguage)],
|
|
@@ -211,18 +230,18 @@ var CoreService = class {
|
|
|
211
230
|
"external": data[TranslationLoader.t("externalMemory", this.localLanguage)],
|
|
212
231
|
"user": data[TranslationLoader.t("memoryUsageUser", this.localLanguage)],
|
|
213
232
|
"system": data[TranslationLoader.t("memoryUsageSystem", this.localLanguage)],
|
|
214
|
-
"pid":
|
|
233
|
+
"pid": process4.pid
|
|
215
234
|
};
|
|
216
235
|
}
|
|
217
236
|
/**
|
|
218
237
|
* Return an Object containing a project path and running server time
|
|
219
238
|
*/
|
|
220
239
|
getProjectInfo() {
|
|
221
|
-
const startTime =
|
|
222
|
-
const endTime =
|
|
240
|
+
const startTime = process4.hrtime();
|
|
241
|
+
const endTime = process4.hrtime(startTime);
|
|
223
242
|
const executionTime = (endTime[0] * 1e9 + endTime[1]) / 1e6;
|
|
224
243
|
return {
|
|
225
|
-
"projectPath": path.join(
|
|
244
|
+
"projectPath": path.join(process4.cwd()),
|
|
226
245
|
"startingTime": `${executionTime.toFixed(5)} ms`
|
|
227
246
|
};
|
|
228
247
|
}
|
|
@@ -261,7 +280,7 @@ var CoreService = class {
|
|
|
261
280
|
this.loadTranslationFiles();
|
|
262
281
|
try {
|
|
263
282
|
const getEnvironment = getEnvironmentValue(this.environmentPath);
|
|
264
|
-
const msg5 = getEnvironment.
|
|
283
|
+
const msg5 = getEnvironment.protocolTransfer === "" ? colors.underline(`http://${host}:${port}`) : colors.underline(`${getEnvironment.protocolTransfer}://${host}:${port}`);
|
|
265
284
|
const messages = [
|
|
266
285
|
TranslationLoader.t("webServerListening", this.localLanguage),
|
|
267
286
|
TranslationLoader.t("webServerUsingNodeVersion", this.localLanguage, { nodeVersion: this.getVersions().nodeVersion }),
|
|
@@ -271,7 +290,7 @@ var CoreService = class {
|
|
|
271
290
|
const maxLength = Math.max(...messages.map((m) => {
|
|
272
291
|
return m.replace(/\u001b\[[0-9]{1,2}m/g, "").length;
|
|
273
292
|
})) + 4;
|
|
274
|
-
console.log(chalk.blackBright(`${TranslationLoader.t("tailingServerLog", this.localLanguage)} (${path.join(path.basename(
|
|
293
|
+
console.log(chalk.blackBright(`${TranslationLoader.t("tailingServerLog", this.localLanguage)} (${path.join(path.basename(process4.cwd()), "logs", "app.log")})`));
|
|
275
294
|
const border = chalk.bgGreen.white(" ".repeat(maxLength));
|
|
276
295
|
console.log(border);
|
|
277
296
|
messages.forEach((msg) => {
|
|
@@ -281,17 +300,31 @@ var CoreService = class {
|
|
|
281
300
|
});
|
|
282
301
|
console.log(border);
|
|
283
302
|
console.log("\n");
|
|
284
|
-
this.logger.success({
|
|
285
|
-
title: TranslationLoader.t("serverRunningTitle", this.localLanguage),
|
|
286
|
-
message: TranslationLoader.t("serverRunningAt", this.localLanguage, { server: msg5 })
|
|
287
|
-
});
|
|
288
303
|
this.serverLog.serverLog({
|
|
289
|
-
timestamp: (
|
|
304
|
+
timestamp: parsingDateTimeUtils(getEnvironment.localFormatTime),
|
|
290
305
|
level: "SERVER",
|
|
291
306
|
title: TranslationLoader.t("serverRunningTitle", this.localLanguage),
|
|
292
307
|
typeName: TranslationLoader.t("opticoreServerTypeName", this.localLanguage),
|
|
293
308
|
message: TranslationLoader.t("serverRunningAt", this.localLanguage, { server: msg5 })
|
|
294
309
|
});
|
|
310
|
+
const dependenciesList = this.container.listDependencies();
|
|
311
|
+
if (dependenciesList.length > 0) {
|
|
312
|
+
this.serverLog.opticoreLog({
|
|
313
|
+
level: "OPTICORE",
|
|
314
|
+
message: TranslationLoader.t("DEPENDENCIES_STORED", this.localLanguage, { dependenciesList: dependenciesList.join(", ") }),
|
|
315
|
+
timestamp: parsingDateTimeUtils(getEnvironment.localFormatTime),
|
|
316
|
+
title: TranslationLoader.t("DEPENDENCIES_CONTAINER", this.localLanguage),
|
|
317
|
+
typeName: "DEPENDENCIES_CONTAINER"
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
this.serverLog.opticoreLog({
|
|
321
|
+
level: "OPTICORE",
|
|
322
|
+
message: TranslationLoader.t("NO_DEPENDENCIES_STORED", this.localLanguage),
|
|
323
|
+
timestamp: parsingDateTimeUtils(getEnvironment.localFormatTime),
|
|
324
|
+
title: TranslationLoader.t("DEPENDENCIES_CONTAINER", this.localLanguage),
|
|
325
|
+
typeName: "DEPENDENCIES_CONTAINER"
|
|
326
|
+
});
|
|
327
|
+
}
|
|
295
328
|
} catch (err) {
|
|
296
329
|
this.logger.error({
|
|
297
330
|
message: err.message,
|
|
@@ -358,10 +391,46 @@ var SServerStartError = (err, environmentPath) => {
|
|
|
358
391
|
};
|
|
359
392
|
|
|
360
393
|
// src/core/webServer.core.ts
|
|
361
|
-
import { SContainer as
|
|
394
|
+
import { SContainer as SContainer3 } from "opticore-dependency-inject";
|
|
395
|
+
|
|
396
|
+
// src/application/service/getEnvFileLoading.service.ts
|
|
397
|
+
import path2 from "path";
|
|
398
|
+
import process5 from "process";
|
|
399
|
+
import fs2 from "fs";
|
|
400
|
+
import { TranslationLoader as TranslationLoader2 } from "opticore-translator";
|
|
401
|
+
import { HttpStatusCode as status2 } from "opticore-http-response";
|
|
402
|
+
var getEnvFileLoadingService = (filePath, localLanguage) => {
|
|
403
|
+
loaderTranslationFile(localLanguage);
|
|
404
|
+
const logger = dependenciesContainerProvider(localLanguage).resolve("LoggerCore");
|
|
405
|
+
try {
|
|
406
|
+
const fullPath = path2.resolve(process5.cwd(), filePath);
|
|
407
|
+
if (fs2.existsSync(fullPath)) {
|
|
408
|
+
const env = fs2.readFileSync(fullPath, "utf-8");
|
|
409
|
+
const lines = env.split("\n");
|
|
410
|
+
lines.forEach((line) => {
|
|
411
|
+
const match = line.match(/^([^#=]+)=([^#]+)$/);
|
|
412
|
+
if (match) {
|
|
413
|
+
const key = match[1].trim();
|
|
414
|
+
process5.env[key] = match[2].trim();
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
} catch (err) {
|
|
419
|
+
logger.error({
|
|
420
|
+
message: err.message,
|
|
421
|
+
title: TranslationLoader2.t("EnvFileLoading", localLanguage),
|
|
422
|
+
errorType: err.code,
|
|
423
|
+
stackTrace: err.stack,
|
|
424
|
+
httpCodeValue: status2.INTERNAL_SERVER_ERROR
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// src/core/webServer.core.ts
|
|
362
430
|
var WebServerCore = class {
|
|
363
431
|
serverUtility;
|
|
364
432
|
expressApp = express2();
|
|
433
|
+
container;
|
|
365
434
|
localLanguage;
|
|
366
435
|
loggerConfig;
|
|
367
436
|
routerExpressApp;
|
|
@@ -377,6 +446,7 @@ var WebServerCore = class {
|
|
|
377
446
|
this.loggerConfig = paramsConstructor.loggerConfig;
|
|
378
447
|
this.localLanguage = paramsConstructor.localLanguage;
|
|
379
448
|
this.environmentPath = paramsConstructor.environmentPath;
|
|
449
|
+
this.container = new SContainer3(paramsConstructor.localLanguage);
|
|
380
450
|
this.expressApp.use(express2.json());
|
|
381
451
|
this.expressApp.use(express2.raw());
|
|
382
452
|
this.expressApp.use(express2.text());
|
|
@@ -385,16 +455,17 @@ var WebServerCore = class {
|
|
|
385
455
|
this.serverListenEvent = new ServerListenEventError2(paramsConstructor.localLanguage);
|
|
386
456
|
this.serverUtility = new CoreService(paramsConstructor.localLanguage, paramsConstructor.environmentPath);
|
|
387
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
*
|
|
460
|
+
* @param dependencies
|
|
461
|
+
*/
|
|
388
462
|
registerDependencies(dependencies2) {
|
|
463
|
+
this.loadTranslationFiles();
|
|
389
464
|
try {
|
|
465
|
+
getEnvFileLoadingService(".env", this.localLanguage);
|
|
390
466
|
if (dependencies2 && dependencies2.length > 0) {
|
|
391
467
|
dependencies2.forEach((dependency) => {
|
|
392
|
-
|
|
393
|
-
});
|
|
394
|
-
} else {
|
|
395
|
-
SLogger(this.environmentPath).logger.info({
|
|
396
|
-
message: TranslationLoader2.t("NO_REGISTER_DEPENDENCIES", this.localLanguage),
|
|
397
|
-
title: TranslationLoader2.t("REGISTER_DEPENDENCIES", this.localLanguage)
|
|
468
|
+
this.container.register(dependency.key, dependency.factory, dependency.scope);
|
|
398
469
|
});
|
|
399
470
|
}
|
|
400
471
|
} catch (err) {
|
|
@@ -407,8 +478,15 @@ var WebServerCore = class {
|
|
|
407
478
|
});
|
|
408
479
|
}
|
|
409
480
|
}
|
|
481
|
+
/**
|
|
482
|
+
*
|
|
483
|
+
* @param routers
|
|
484
|
+
* @param databaseCallback
|
|
485
|
+
* @param dependenciesProvider
|
|
486
|
+
*/
|
|
410
487
|
onStartServer(routers, databaseCallback, dependenciesProvider) {
|
|
411
488
|
this.loadTranslationFiles();
|
|
489
|
+
getEnvFileLoadingService(".env", this.localLanguage);
|
|
412
490
|
if (this.getEnvironmentValue.appPort === "" && Number(this.getEnvironmentValue.appPort) === 0) {
|
|
413
491
|
this.serverListenEvent.hostPortUndefined(Number(this.getEnvironmentValue.appPort));
|
|
414
492
|
} else if (this.getEnvironmentValue.appHost === "") {
|
|
@@ -417,9 +495,9 @@ var WebServerCore = class {
|
|
|
417
495
|
this.serverListenEvent.portUndefined();
|
|
418
496
|
} else if (this.localLanguage === "") {
|
|
419
497
|
SLogger(this.environmentPath).logger.error({
|
|
420
|
-
message:
|
|
421
|
-
title:
|
|
422
|
-
errorType:
|
|
498
|
+
message: TranslationLoader3.t("noDefaultLocalLang", this.localLanguage),
|
|
499
|
+
title: TranslationLoader3.t("noLocalLang", this.localLanguage),
|
|
500
|
+
errorType: TranslationLoader3.t("localLangMissing", this.localLanguage),
|
|
423
501
|
stackTrace: void 0,
|
|
424
502
|
httpCodeValue: HttpStatusCode3.NOT_FOUND
|
|
425
503
|
});
|
|
@@ -430,8 +508,11 @@ var WebServerCore = class {
|
|
|
430
508
|
this.loadTranslationFiles();
|
|
431
509
|
try {
|
|
432
510
|
databaseCallback(this.getEnvironmentValue);
|
|
433
|
-
|
|
434
|
-
|
|
511
|
+
if (dependenciesProvider) {
|
|
512
|
+
this.registerDependencies(dependenciesProvider);
|
|
513
|
+
}
|
|
514
|
+
this.container.loadServices();
|
|
515
|
+
this.expressApp.use(express2.static(path3.join(process6.cwd(), "public/template")));
|
|
435
516
|
this.registerRoutes(routers);
|
|
436
517
|
} catch (err) {
|
|
437
518
|
SServerStartError(err, this.environmentPath);
|
|
@@ -440,6 +521,10 @@ var WebServerCore = class {
|
|
|
440
521
|
);
|
|
441
522
|
}
|
|
442
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
*
|
|
526
|
+
* @param serverWeb
|
|
527
|
+
*/
|
|
443
528
|
onListeningOnServerEvent(serverWeb) {
|
|
444
529
|
this.loadTranslationFiles();
|
|
445
530
|
serverWeb.on(eventName2.error, (err) => {
|
|
@@ -452,6 +537,10 @@ var WebServerCore = class {
|
|
|
452
537
|
this.infoWebApp();
|
|
453
538
|
});
|
|
454
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
*
|
|
542
|
+
* @param serverWeb
|
|
543
|
+
*/
|
|
455
544
|
onRequestOnServerEvent(serverWeb) {
|
|
456
545
|
this.loadTranslationFiles();
|
|
457
546
|
serverWeb.on(eventName2.request, (req, res) => {
|
|
@@ -504,7 +593,399 @@ var WebServerCore = class {
|
|
|
504
593
|
);
|
|
505
594
|
}
|
|
506
595
|
};
|
|
596
|
+
|
|
597
|
+
// src/hotReload/hotReload.watcher.ts
|
|
598
|
+
import { fork, spawn } from "child_process";
|
|
599
|
+
import { watch as fsWatch } from "fs";
|
|
600
|
+
import { readdir, access } from "fs/promises";
|
|
601
|
+
import { join as join3, resolve as resolve2, extname, relative, basename as basename2 } from "path";
|
|
602
|
+
import { config as dotenvConfig } from "dotenv";
|
|
603
|
+
import chalk2 from "chalk";
|
|
604
|
+
import colors2 from "ansi-colors";
|
|
605
|
+
import gradient from "gradient-string";
|
|
606
|
+
var DEFAULT_WATCH_EXTENSIONS = [".ts", ".js", ".mjs", ".cjs", ".json"];
|
|
607
|
+
var DEFAULT_HOT_RELOAD_EXTENSIONS = [".json"];
|
|
608
|
+
var DEFAULT_IGNORE = [
|
|
609
|
+
"node_modules",
|
|
610
|
+
"dist",
|
|
611
|
+
".git",
|
|
612
|
+
"package.json",
|
|
613
|
+
"package-lock.json",
|
|
614
|
+
".idea",
|
|
615
|
+
".vscode",
|
|
616
|
+
"coverage",
|
|
617
|
+
"logs"
|
|
618
|
+
];
|
|
619
|
+
var DEFAULT_DEBOUNCE_MS = 300;
|
|
620
|
+
var DEFAULT_MAX_CRASH_RESTARTS = 5;
|
|
621
|
+
var HotReloadWatcher = class {
|
|
622
|
+
cfg;
|
|
623
|
+
child = null;
|
|
624
|
+
watchers = [];
|
|
625
|
+
debounceTimer = null;
|
|
626
|
+
isRestarting = false;
|
|
627
|
+
crashRestartCount = 0;
|
|
628
|
+
started = false;
|
|
629
|
+
restartStart = 0;
|
|
630
|
+
constructor(config) {
|
|
631
|
+
this.cfg = {
|
|
632
|
+
entry: resolve2(config.entry),
|
|
633
|
+
runtime: config.runtime ?? "node",
|
|
634
|
+
runtimeArgs: config.runtimeArgs ?? [],
|
|
635
|
+
rootDir: resolve2(config.rootDir ?? process.cwd()),
|
|
636
|
+
watchDirs: config.watchDirs ?? [],
|
|
637
|
+
watchExtensions: config.watchExtensions ?? DEFAULT_WATCH_EXTENSIONS,
|
|
638
|
+
ignore: [...DEFAULT_IGNORE, ...config.ignore ?? []],
|
|
639
|
+
envFile: config.envFile ?? ".env",
|
|
640
|
+
hotReloadExtensions: config.hotReloadExtensions ?? DEFAULT_HOT_RELOAD_EXTENSIONS,
|
|
641
|
+
debounceMs: config.debounceMs ?? DEFAULT_DEBOUNCE_MS,
|
|
642
|
+
restartOnCrash: config.restartOnCrash ?? true,
|
|
643
|
+
maxCrashRestarts: config.maxCrashRestarts ?? DEFAULT_MAX_CRASH_RESTARTS
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
// ─── Public API ──────────────────────────────────────────────────────────
|
|
647
|
+
async start() {
|
|
648
|
+
if (this.started) return;
|
|
649
|
+
this.started = true;
|
|
650
|
+
this.printBanner();
|
|
651
|
+
this.spawnChild();
|
|
652
|
+
await this.setupWatchers();
|
|
653
|
+
this.setupProcessSignals();
|
|
654
|
+
}
|
|
655
|
+
async stop() {
|
|
656
|
+
this.watchers.forEach((w) => {
|
|
657
|
+
w.close();
|
|
658
|
+
});
|
|
659
|
+
this.watchers = [];
|
|
660
|
+
await this.killChild(true);
|
|
661
|
+
this.printStopped();
|
|
662
|
+
}
|
|
663
|
+
// ─── Child process management ────────────────────────────────────────────
|
|
664
|
+
spawnChild() {
|
|
665
|
+
const { entry, runtime, runtimeArgs } = this.cfg;
|
|
666
|
+
try {
|
|
667
|
+
this.child = runtime === "node" ? fork(entry, [], {
|
|
668
|
+
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
669
|
+
env: process.env
|
|
670
|
+
}) : spawn(runtime, [...runtimeArgs, entry], {
|
|
671
|
+
stdio: "inherit",
|
|
672
|
+
env: process.env,
|
|
673
|
+
shell: false
|
|
674
|
+
});
|
|
675
|
+
this.child.on("exit", (code) => {
|
|
676
|
+
if (this.isRestarting) return;
|
|
677
|
+
if (code !== 0 && code !== null) {
|
|
678
|
+
this.printCrash(code);
|
|
679
|
+
if (this.cfg.restartOnCrash && this.crashRestartCount < this.cfg.maxCrashRestarts) {
|
|
680
|
+
this.crashRestartCount++;
|
|
681
|
+
this.printCrashRetry(this.crashRestartCount, this.cfg.maxCrashRestarts);
|
|
682
|
+
setTimeout(() => {
|
|
683
|
+
this.spawnChild();
|
|
684
|
+
}, 1e3);
|
|
685
|
+
} else if (this.crashRestartCount >= this.cfg.maxCrashRestarts) {
|
|
686
|
+
this.printCrashLimit(this.cfg.maxCrashRestarts);
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
this.child.on("error", (err) => {
|
|
692
|
+
this.printError(`Failed to start child process: ${err.message}`);
|
|
693
|
+
});
|
|
694
|
+
} catch (err) {
|
|
695
|
+
this.printError(`Cannot spawn '${runtime} ${entry}': ${err.message}`);
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
async killChild(silent = false) {
|
|
700
|
+
if (!this.child) return;
|
|
701
|
+
const child = this.child;
|
|
702
|
+
this.child = null;
|
|
703
|
+
await new Promise((done) => {
|
|
704
|
+
const forceKill = setTimeout(() => {
|
|
705
|
+
child.kill("SIGKILL");
|
|
706
|
+
done();
|
|
707
|
+
}, 3e3);
|
|
708
|
+
child.once("exit", () => {
|
|
709
|
+
clearTimeout(forceKill);
|
|
710
|
+
done();
|
|
711
|
+
});
|
|
712
|
+
child.kill("SIGTERM");
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
// ─── File watching ────────────────────────────────────────────────────────
|
|
716
|
+
async setupWatchers() {
|
|
717
|
+
const dirs = [
|
|
718
|
+
this.cfg.rootDir,
|
|
719
|
+
...this.cfg.watchDirs.map((d) => resolve2(d))
|
|
720
|
+
];
|
|
721
|
+
for (const dir of dirs) {
|
|
722
|
+
await this.watchDirectoryRecursive(dir);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
async watchDirectoryRecursive(dir) {
|
|
726
|
+
if (this.shouldIgnoreDir(dir)) return;
|
|
727
|
+
try {
|
|
728
|
+
await access(dir);
|
|
729
|
+
} catch {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
try {
|
|
733
|
+
const watcher = fsWatch(dir, (event2, filename) => {
|
|
734
|
+
if (!filename) return;
|
|
735
|
+
this.onFileChange(join3(dir, filename));
|
|
736
|
+
});
|
|
737
|
+
watcher.on("error", () => {
|
|
738
|
+
});
|
|
739
|
+
this.watchers.push(watcher);
|
|
740
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
741
|
+
for (const entry of entries) {
|
|
742
|
+
if (entry.isDirectory()) {
|
|
743
|
+
await this.watchDirectoryRecursive(join3(dir, entry.name));
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
} catch {
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
// ─── Change handling ─────────────────────────────────────────────────────
|
|
750
|
+
onFileChange(filePath) {
|
|
751
|
+
if (this.shouldIgnoreFile(filePath)) return;
|
|
752
|
+
if (!this.isWatchedFile(filePath)) return;
|
|
753
|
+
const rel = relative(this.cfg.rootDir, filePath);
|
|
754
|
+
if (this.isHotReloadable(filePath)) {
|
|
755
|
+
this.doHotReload(filePath, rel);
|
|
756
|
+
} else {
|
|
757
|
+
this.scheduleRestart(rel);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
doHotReload(filePath, relativePath) {
|
|
761
|
+
if (this.isEnvFile(filePath)) {
|
|
762
|
+
dotenvConfig({ path: filePath, override: true });
|
|
763
|
+
this.printHot(relativePath, "env variables reloaded");
|
|
764
|
+
this.sendIpc({ type: "hot-reload", file: relativePath, kind: "env" });
|
|
765
|
+
} else {
|
|
766
|
+
this.printHot(relativePath, "json config reloaded");
|
|
767
|
+
this.sendIpc({ type: "hot-reload", file: relativePath, kind: "json" });
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
scheduleRestart(relativePath) {
|
|
771
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
772
|
+
this.debounceTimer = setTimeout(async () => {
|
|
773
|
+
this.printReloading(relativePath);
|
|
774
|
+
this.restartStart = Date.now();
|
|
775
|
+
this.isRestarting = true;
|
|
776
|
+
this.crashRestartCount = 0;
|
|
777
|
+
await this.killChild(true);
|
|
778
|
+
this.isRestarting = false;
|
|
779
|
+
this.spawnChild();
|
|
780
|
+
this.printReady(Date.now() - this.restartStart);
|
|
781
|
+
}, this.cfg.debounceMs);
|
|
782
|
+
}
|
|
783
|
+
// ─── IPC ─────────────────────────────────────────────────────────────────
|
|
784
|
+
sendIpc(message) {
|
|
785
|
+
if (this.child && typeof this.child.send === "function") {
|
|
786
|
+
try {
|
|
787
|
+
this.child.send(message);
|
|
788
|
+
} catch {
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
// ─── Ignore / watch filters ───────────────────────────────────────────────
|
|
793
|
+
shouldIgnoreDir(dirPath) {
|
|
794
|
+
return this.cfg.ignore.some((p) => dirPath.includes(`/${p}`) || dirPath.endsWith(p));
|
|
795
|
+
}
|
|
796
|
+
shouldIgnoreFile(filePath) {
|
|
797
|
+
const name = basename2(filePath);
|
|
798
|
+
const rel = relative(this.cfg.rootDir, filePath);
|
|
799
|
+
return this.cfg.ignore.some((p) => name === p || rel.includes(p) || filePath.includes(`/${p}/`));
|
|
800
|
+
}
|
|
801
|
+
isWatchedFile(filePath) {
|
|
802
|
+
if (this.isEnvFile(filePath)) return true;
|
|
803
|
+
return this.cfg.watchExtensions.includes(extname(filePath));
|
|
804
|
+
}
|
|
805
|
+
isHotReloadable(filePath) {
|
|
806
|
+
if (this.isEnvFile(filePath)) return true;
|
|
807
|
+
return this.cfg.hotReloadExtensions.includes(extname(filePath));
|
|
808
|
+
}
|
|
809
|
+
isEnvFile(filePath) {
|
|
810
|
+
const name = basename2(filePath);
|
|
811
|
+
return name === this.cfg.envFile || name.startsWith(".env");
|
|
812
|
+
}
|
|
813
|
+
// ─── ANSI strip helper (for width calculation) ────────────────────────────
|
|
814
|
+
strip(str) {
|
|
815
|
+
return str.replace(/\[[0-9;]*m/g, "").replace(/\][^]*/g, "");
|
|
816
|
+
}
|
|
817
|
+
// ─── Timestamp ────────────────────────────────────────────────────────────
|
|
818
|
+
ts() {
|
|
819
|
+
const n = /* @__PURE__ */ new Date();
|
|
820
|
+
const h = String(n.getHours()).padStart(2, "0");
|
|
821
|
+
const m = String(n.getMinutes()).padStart(2, "0");
|
|
822
|
+
const s = String(n.getSeconds()).padStart(2, "0");
|
|
823
|
+
return `${h}:${m}:${s}`;
|
|
824
|
+
}
|
|
825
|
+
// ─── Console output ───────────────────────────────────────────────────────
|
|
826
|
+
/**
|
|
827
|
+
* Startup banner — mirrors the infoServer() box style from CoreService.
|
|
828
|
+
*
|
|
829
|
+
* ╔══════════════════════════════════════════╗
|
|
830
|
+
* gradient title
|
|
831
|
+
* ╔══ bgGreen box ════════════════════════╗
|
|
832
|
+
* entry dist/index.js
|
|
833
|
+
* runtime node (IPC enabled)
|
|
834
|
+
* root ./src
|
|
835
|
+
* watching .ts .js .json .env
|
|
836
|
+
* debounce 300ms
|
|
837
|
+
* ╚══════════════════════════════════════════╝
|
|
838
|
+
*/
|
|
839
|
+
printBanner() {
|
|
840
|
+
const entryRel = relative(process.cwd(), this.cfg.entry) || this.cfg.entry;
|
|
841
|
+
const rootRel = relative(process.cwd(), this.cfg.rootDir) || ".";
|
|
842
|
+
const runtimeStr = this.cfg.runtime === "node" ? `node ${chalk2.blackBright("(IPC enabled)")}` : this.cfg.runtime;
|
|
843
|
+
const extStr = [...this.cfg.watchExtensions, ".env"].join(" ");
|
|
844
|
+
const ignoreStr = this.cfg.ignore.slice(0, 4).join(" ") + (this.cfg.ignore.length > 4 ? ` ${chalk2.blackBright(`+${this.cfg.ignore.length - 4} more`)}` : "");
|
|
845
|
+
const TITLE = "OPTICORE HOT RELOAD";
|
|
846
|
+
const rows = [
|
|
847
|
+
` entry ${chalk2.white.bold(entryRel)}`,
|
|
848
|
+
` runtime ${runtimeStr}`,
|
|
849
|
+
` root ${chalk2.white(rootRel)}`,
|
|
850
|
+
` watching ${chalk2.cyan(extStr)}`,
|
|
851
|
+
` ignoring ${chalk2.blackBright(ignoreStr)}`,
|
|
852
|
+
` debounce ${chalk2.white(`${this.cfg.debounceMs}ms`)}`
|
|
853
|
+
];
|
|
854
|
+
const maxLen = Math.max(
|
|
855
|
+
TITLE.length + 4,
|
|
856
|
+
...rows.map((r) => this.strip(r).length)
|
|
857
|
+
) + 4;
|
|
858
|
+
const border = chalk2.bgGreen.white(" ".repeat(maxLen));
|
|
859
|
+
const titlePad = " ".repeat(Math.max(0, Math.floor((maxLen - TITLE.length) / 2)));
|
|
860
|
+
console.log("\n" + titlePad + gradient(["#43e97b", "#38f9d7", "#00c6fb"])(TITLE));
|
|
861
|
+
console.log(border);
|
|
862
|
+
rows.forEach((row) => {
|
|
863
|
+
const cleanLen = this.strip(row).length;
|
|
864
|
+
const padding = Math.max(0, maxLen - cleanLen - 2);
|
|
865
|
+
console.log(chalk2.bgGreen.white(` ${row}${" ".repeat(padding)} `));
|
|
866
|
+
});
|
|
867
|
+
console.log(border);
|
|
868
|
+
console.log(chalk2.blackBright(` watching for changes...
|
|
869
|
+
`));
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* HOT event — in-process reload, no server restart.
|
|
873
|
+
*
|
|
874
|
+
* ✔ [ HOT ] 14:23:45 | .env → env variables reloaded
|
|
875
|
+
*/
|
|
876
|
+
printHot(file, action) {
|
|
877
|
+
const badge = colors2.bgCyan(colors2.white.bold(" HOT "));
|
|
878
|
+
const ts = chalk2.blackBright(this.ts());
|
|
879
|
+
const sep = chalk2.blackBright("|");
|
|
880
|
+
const filePart = chalk2.cyan.bold(file.padEnd(32));
|
|
881
|
+
const arrow = chalk2.green("\u2192");
|
|
882
|
+
const msg = chalk2.green(action);
|
|
883
|
+
console.log(` ${chalk2.green("\u2714")} ${badge} ${ts} ${sep} ${filePart} ${arrow} ${msg}`);
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* RELOAD event — server restart triggered.
|
|
887
|
+
*
|
|
888
|
+
* ⚡ [ RELOAD ] 14:24:03 | src/routes/user.ts → restarting server...
|
|
889
|
+
*/
|
|
890
|
+
printReloading(file) {
|
|
891
|
+
const badge = colors2.bgYellow(colors2.white.bold(" RELOAD "));
|
|
892
|
+
const ts = chalk2.blackBright(this.ts());
|
|
893
|
+
const sep = chalk2.blackBright("|");
|
|
894
|
+
const filePart = chalk2.yellow.bold(file.padEnd(32));
|
|
895
|
+
const arrow = chalk2.yellow("\u2192");
|
|
896
|
+
const msg = chalk2.yellow("restarting server...");
|
|
897
|
+
console.log(`
|
|
898
|
+
${chalk2.yellow("\u26A1")} ${badge} ${ts} ${sep} ${filePart} ${arrow} ${msg}`);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* READY event — server successfully restarted.
|
|
902
|
+
* Uses the same full-width bgGreen border as infoServer().
|
|
903
|
+
*
|
|
904
|
+
* ════════════════════════════════════════════
|
|
905
|
+
* READY server restarted in 245ms
|
|
906
|
+
* ════════════════════════════════════════════
|
|
907
|
+
*/
|
|
908
|
+
printReady(elapsedMs) {
|
|
909
|
+
const msg = ` server restarted in ${chalk2.white.bold(`${elapsedMs}ms`)}`;
|
|
910
|
+
const cleanLen = this.strip(msg).length;
|
|
911
|
+
const width = Math.max(52, cleanLen + 6);
|
|
912
|
+
const border = chalk2.bgGreen.white(" ".repeat(width));
|
|
913
|
+
const padding = Math.max(0, width - cleanLen - 2);
|
|
914
|
+
const line = chalk2.bgGreen.white(` ${chalk2.bgGreen.white.bold(" READY ")} ${msg}${" ".repeat(padding)} `);
|
|
915
|
+
console.log(` ${border}`);
|
|
916
|
+
console.log(` ${line}`);
|
|
917
|
+
console.log(` ${border}
|
|
918
|
+
`);
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* CRASH event — unexpected child process exit.
|
|
922
|
+
*
|
|
923
|
+
* ✘ [ CRASH ] 14:24:10 | server exited with code 1
|
|
924
|
+
*/
|
|
925
|
+
printCrash(code) {
|
|
926
|
+
const badge = colors2.bgRed(colors2.white.bold(" CRASH "));
|
|
927
|
+
const ts = chalk2.blackBright(this.ts());
|
|
928
|
+
const sep = chalk2.blackBright("|");
|
|
929
|
+
const msg = chalk2.red(`server exited with code ${chalk2.bold(String(code))}`);
|
|
930
|
+
console.log(`
|
|
931
|
+
${chalk2.red("\u2718")} ${badge} ${ts} ${sep} ${msg}`);
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* RETRY event — auto-restart after crash.
|
|
935
|
+
*
|
|
936
|
+
* ↺ [ RETRY ] 14:24:11 | attempt 1 / 5
|
|
937
|
+
*/
|
|
938
|
+
printCrashRetry(attempt, max) {
|
|
939
|
+
const badge = colors2.bgMagenta(colors2.white.bold(" RETRY "));
|
|
940
|
+
const ts = chalk2.blackBright(this.ts());
|
|
941
|
+
const sep = chalk2.blackBright("|");
|
|
942
|
+
const msg = chalk2.magenta(`auto-restart attempt ${chalk2.bold(`${attempt} / ${max}`)}`);
|
|
943
|
+
console.log(` ${chalk2.magenta("\u21BA")} ${badge} ${ts} ${sep} ${msg}`);
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* LIMIT reached — give up restarting.
|
|
947
|
+
*/
|
|
948
|
+
printCrashLimit(max) {
|
|
949
|
+
const badge = colors2.bgRed(colors2.white.bold(" ERROR "));
|
|
950
|
+
const ts = chalk2.blackBright(this.ts());
|
|
951
|
+
const sep = chalk2.blackBright("|");
|
|
952
|
+
const msg = chalk2.red(`max crash restarts reached (${chalk2.bold(String(max))}), giving up`);
|
|
953
|
+
console.log(`
|
|
954
|
+
${chalk2.red("\u2718")} ${badge} ${ts} ${sep} ${msg}
|
|
955
|
+
`);
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Error — miscellaneous internal error.
|
|
959
|
+
*/
|
|
960
|
+
printError(message) {
|
|
961
|
+
const badge = colors2.bgRed(colors2.white.bold(" ERROR "));
|
|
962
|
+
const ts = chalk2.blackBright(this.ts());
|
|
963
|
+
const sep = chalk2.blackBright("|");
|
|
964
|
+
console.error(` ${chalk2.red("\u2718")} ${badge} ${ts} ${sep} ${chalk2.red(message)}`);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Stopped — watcher shut down.
|
|
968
|
+
*/
|
|
969
|
+
printStopped() {
|
|
970
|
+
const badge = colors2.bgBlackBright(colors2.white.bold(" STOPPED"));
|
|
971
|
+
const ts = chalk2.blackBright(this.ts());
|
|
972
|
+
const sep = chalk2.blackBright("|");
|
|
973
|
+
console.log(` ${chalk2.gray("\u25A0")} ${badge} ${ts} ${sep} ${chalk2.gray("watcher stopped")}
|
|
974
|
+
`);
|
|
975
|
+
}
|
|
976
|
+
// ─── Graceful shutdown ────────────────────────────────────────────────────
|
|
977
|
+
setupProcessSignals() {
|
|
978
|
+
const shutdown = async () => {
|
|
979
|
+
console.log("");
|
|
980
|
+
await this.stop();
|
|
981
|
+
process.exit(0);
|
|
982
|
+
};
|
|
983
|
+
process.once("SIGINT", shutdown);
|
|
984
|
+
process.once("SIGTERM", shutdown);
|
|
985
|
+
}
|
|
986
|
+
};
|
|
507
987
|
export {
|
|
988
|
+
HotReloadWatcher,
|
|
508
989
|
WebServerCore as WebServer,
|
|
509
990
|
envPath
|
|
510
991
|
};
|