opticore-webapp 1.0.70 → 1.0.72

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.js CHANGED
@@ -1,16 +1,16 @@
1
1
  // src/core/webServer.core.ts
2
- import * as path2 from "path";
3
- import process4 from "process";
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 TranslationLoader2 } from "opticore-translator";
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 process from "process";
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
- process.on(event.beforeExit, (code) => {
24
+ process2.on(event.beforeExit, (code) => {
25
25
  setTimeout(() => {
26
26
  serverListenEvent.processBeforeExit(code);
27
27
  }, 100);
28
28
  });
29
- process.on(event.disconnect, () => {
29
+ process2.on(event.disconnect, () => {
30
30
  serverListenEvent.processDisconnected();
31
31
  });
32
- process.on(event.exit, (code) => {
32
+ process2.on(event.exit, (code) => {
33
33
  serverListenEvent.exited(code);
34
34
  });
35
- process.on(event.rejectionHandled, (promise) => {
35
+ process2.on(event.rejectionHandled, (promise) => {
36
36
  serverListenEvent.promiseRejectionHandled(promise);
37
37
  });
38
- process.on(event.uncaughtException, (error) => {
38
+ process2.on(event.uncaughtException, (error) => {
39
39
  serverListenEvent.uncaughtException(error);
40
40
  });
41
- process.on(event.uncaughtExceptionMonitor, (error) => {
41
+ process2.on(event.uncaughtExceptionMonitor, (error) => {
42
42
  serverListenEvent.uncaughtExceptionMonitor(error);
43
43
  });
44
- process.on(event.unhandledRejection, (reason, promise) => {
44
+ process2.on(event.unhandledRejection, (reason, promise) => {
45
45
  serverListenEvent.unhandledRejection(reason, promise);
46
46
  });
47
- process.on(event.warning, (warning) => {
47
+ process2.on(event.warning, (warning) => {
48
48
  serverListenEvent.warning(warning);
49
49
  });
50
- process.on(event.message, (message) => {
50
+ process2.on(event.message, (message) => {
51
51
  serverListenEvent.message(message);
52
52
  });
53
- process.on(event.multipleResolves, (type, promise, reason) => {
53
+ process2.on(event.multipleResolves, (type, promise, reason) => {
54
54
  serverListenEvent.multipleResolves(type, promise, reason);
55
55
  });
56
- process.on(event.sigint, () => {
56
+ process2.on(event.sigint, () => {
57
57
  serverListenEvent.processInterrupted();
58
58
  });
59
- process.on(event.sigterm, (signal) => {
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 process3 from "process";
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 process2 from "process";
79
- var envPath = process2.cwd() + "/config/env/.env";
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(process3.cwd(), filePath);
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
- process3.env[key] = match[2].trim();
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 } = process3.versions;
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 = process3.memoryUsage();
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(process3.cpuUsage().user),
205
- [TranslationLoader.t("memoryUsageSystem", this.localLanguage)]: this.formatMemoryUsage(process3.cpuUsage().system)
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": process3.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 = process3.hrtime();
222
- const endTime = process3.hrtime(startTime);
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(process3.cwd()),
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.protocolTransfert === "" ? colors.underline(`http://${host}:${port}`) : colors.underline(`${getEnvironment.protocolTransfert}://${host}:${port}`);
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(process3.cwd()), "logs", "app.log")})`));
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: (/* @__PURE__ */ new Date()).toString(),
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,284 @@ var SServerStartError = (err, environmentPath) => {
358
391
  };
359
392
 
360
393
  // src/core/webServer.core.ts
361
- import { SContainer as SContainer2 } from "opticore-dependency-inject";
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/hotReload/hotReload.watcher.ts
430
+ import { watch as fsWatch } from "fs";
431
+ import { readdir, access } from "fs/promises";
432
+ import { join as join2, resolve as resolve2, extname, relative, basename as basename2 } from "path";
433
+ import { config as dotenvConfig } from "dotenv";
434
+ import chalk2 from "chalk";
435
+ import colors2 from "ansi-colors";
436
+ import gradient from "gradient-string";
437
+ var DEFAULT_WATCH_EXTENSIONS = [".ts", ".js", ".mjs", ".cjs", ".json"];
438
+ var DEFAULT_HOT_RELOAD_EXTENSIONS = [".json"];
439
+ var DEFAULT_IGNORE = [
440
+ "node_modules",
441
+ "dist",
442
+ ".git",
443
+ "package.json",
444
+ "package-lock.json",
445
+ ".idea",
446
+ ".vscode",
447
+ "coverage",
448
+ "logs"
449
+ ];
450
+ var DEFAULT_DEBOUNCE_MS = 300;
451
+ var HotReloadWatcher = class {
452
+ cfg;
453
+ server = null;
454
+ watchers = [];
455
+ debounceTimer = null;
456
+ restarting = false;
457
+ constructor(config) {
458
+ const c = config ?? {};
459
+ this.cfg = {
460
+ rootDir: resolve2(c.rootDir ?? process.cwd()),
461
+ watchDirs: c.watchDirs ?? [],
462
+ watchExtensions: c.watchExtensions ?? DEFAULT_WATCH_EXTENSIONS,
463
+ ignore: [...DEFAULT_IGNORE, ...c.ignore ?? []],
464
+ envFile: c.envFile ?? ".env",
465
+ hotReloadExtensions: c.hotReloadExtensions ?? DEFAULT_HOT_RELOAD_EXTENSIONS,
466
+ debounceMs: c.debounceMs ?? DEFAULT_DEBOUNCE_MS
467
+ };
468
+ }
469
+ // ─── Called by WebServerCore once the HTTP server is listening ────────────
470
+ async attach(server) {
471
+ this.server = server;
472
+ this.printBanner();
473
+ await this.setupWatchers();
474
+ this.setupProcessSignals();
475
+ }
476
+ // ─── File watching ────────────────────────────────────────────────────────
477
+ async setupWatchers() {
478
+ const dirs = [
479
+ this.cfg.rootDir,
480
+ ...this.cfg.watchDirs.map((d) => resolve2(d))
481
+ ];
482
+ for (const dir of dirs) {
483
+ await this.watchRecursive(dir);
484
+ }
485
+ }
486
+ async watchRecursive(dir) {
487
+ if (this.shouldIgnoreDir(dir)) return;
488
+ try {
489
+ await access(dir);
490
+ } catch {
491
+ return;
492
+ }
493
+ try {
494
+ const w = fsWatch(dir, (_, filename) => {
495
+ if (filename) this.onFileChange(join2(dir, filename));
496
+ });
497
+ w.on("error", () => {
498
+ });
499
+ this.watchers.push(w);
500
+ const entries = await readdir(dir, { withFileTypes: true });
501
+ for (const entry of entries) {
502
+ if (entry.isDirectory()) await this.watchRecursive(join2(dir, entry.name));
503
+ }
504
+ } catch {
505
+ }
506
+ }
507
+ // ─── Change handling ──────────────────────────────────────────────────────
508
+ onFileChange(filePath) {
509
+ if (this.shouldIgnoreFile(filePath)) return;
510
+ if (!this.isWatched(filePath)) return;
511
+ if (this.restarting) return;
512
+ const rel = relative(this.cfg.rootDir, filePath);
513
+ if (this.isHotReloadable(filePath)) {
514
+ this.doHotReload(filePath, rel);
515
+ } else {
516
+ this.scheduleRestart(rel);
517
+ }
518
+ }
519
+ doHotReload(filePath, rel) {
520
+ if (this.isEnvFile(filePath)) {
521
+ dotenvConfig({ path: filePath, override: true });
522
+ this.printHot(rel, "env variables reloaded");
523
+ } else {
524
+ this.printHot(rel, "json config reloaded");
525
+ }
526
+ }
527
+ scheduleRestart(rel) {
528
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
529
+ this.debounceTimer = setTimeout(() => {
530
+ this.restarting = true;
531
+ this.printReloading(rel);
532
+ this.closeAndExit();
533
+ }, this.cfg.debounceMs);
534
+ }
535
+ /**
536
+ * Gracefully close the HTTP server so in-flight requests can finish,
537
+ * then exit with code 0 — the external runner restarts the process.
538
+ */
539
+ closeAndExit() {
540
+ if (!this.server) {
541
+ process.exit(0);
542
+ return;
543
+ }
544
+ this.server.close(() => {
545
+ process.exit(0);
546
+ });
547
+ setTimeout(() => {
548
+ process.exit(0);
549
+ }, 3e3).unref();
550
+ }
551
+ // ─── Filters ──────────────────────────────────────────────────────────────
552
+ shouldIgnoreDir(dir) {
553
+ return this.cfg.ignore.some(
554
+ (p) => dir.includes(`/${p}`) || dir.endsWith(`/${p}`) || dir.endsWith(p)
555
+ );
556
+ }
557
+ shouldIgnoreFile(filePath) {
558
+ const name = basename2(filePath);
559
+ const rel = relative(this.cfg.rootDir, filePath);
560
+ return this.cfg.ignore.some(
561
+ (p) => name === p || rel.includes(p) || filePath.includes(`/${p}/`)
562
+ );
563
+ }
564
+ isWatched(filePath) {
565
+ if (this.isEnvFile(filePath)) return true;
566
+ return this.cfg.watchExtensions.includes(extname(filePath));
567
+ }
568
+ isHotReloadable(filePath) {
569
+ if (this.isEnvFile(filePath)) return true;
570
+ return this.cfg.hotReloadExtensions.includes(extname(filePath));
571
+ }
572
+ isEnvFile(filePath) {
573
+ const name = basename2(filePath);
574
+ return name === this.cfg.envFile || name.startsWith(".env");
575
+ }
576
+ // ─── ANSI strip (box width calculation) ───────────────────────────────────
577
+ strip(str) {
578
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
579
+ }
580
+ // ─── Timestamp ────────────────────────────────────────────────────────────
581
+ ts() {
582
+ const n = /* @__PURE__ */ new Date();
583
+ return [n.getHours(), n.getMinutes(), n.getSeconds()].map((v) => String(v).padStart(2, "0")).join(":");
584
+ }
585
+ // ─── Console output ───────────────────────────────────────────────────────
586
+ /**
587
+ * Startup banner — same bgGreen box style as CoreService.infoServer().
588
+ *
589
+ * OPTICORE HOT RELOAD ← gradient
590
+ * ████████████████████████████████ ← bgGreen border
591
+ * root ./src
592
+ * watching .ts .js .json .env
593
+ * debounce 300ms
594
+ * ████████████████████████████████ ← bgGreen border
595
+ * watching for changes...
596
+ */
597
+ printBanner() {
598
+ const rootRel = relative(process.cwd(), this.cfg.rootDir) || ".";
599
+ const extStr = [...this.cfg.watchExtensions, ".env"].join(" ");
600
+ const ignoreParts = this.cfg.ignore.slice(0, 4);
601
+ const ignoreMore = this.cfg.ignore.length > 4 ? ` ${chalk2.blackBright(`+${this.cfg.ignore.length - 4} more`)}` : "";
602
+ const ignoreStr = chalk2.blackBright(ignoreParts.join(" ")) + ignoreMore;
603
+ const TITLE = "OPTICORE HOT RELOAD";
604
+ const rows = [
605
+ ` root ${chalk2.white(rootRel)}`,
606
+ ` watching ${chalk2.cyan(extStr)}`,
607
+ ` ignoring ${ignoreStr}`,
608
+ ` debounce ${chalk2.white(`${this.cfg.debounceMs}ms`)}`
609
+ ];
610
+ const maxLen = Math.max(
611
+ TITLE.length + 4,
612
+ ...rows.map((r) => this.strip(r).length)
613
+ ) + 4;
614
+ const border = chalk2.bgGreen.white(" ".repeat(maxLen));
615
+ const titlePad = " ".repeat(Math.max(0, Math.floor((maxLen - TITLE.length) / 2)));
616
+ console.log("\n" + titlePad + gradient(["#43e97b", "#38f9d7", "#00c6fb"])(TITLE));
617
+ console.log(border);
618
+ for (const row of rows) {
619
+ const pad = Math.max(0, maxLen - this.strip(row).length - 2);
620
+ console.log(chalk2.bgGreen.white(` ${row}${" ".repeat(pad)} `));
621
+ }
622
+ console.log(border);
623
+ console.log(chalk2.blackBright(` watching for changes...
624
+ `));
625
+ }
626
+ /**
627
+ * ✔ [ HOT ] 14:23:45 | .env → env variables reloaded
628
+ */
629
+ printHot(file, action) {
630
+ const badge = colors2.bgCyan(colors2.white.bold(" HOT "));
631
+ const filePart = chalk2.cyan.bold(file.padEnd(32));
632
+ console.log(
633
+ ` ${chalk2.green("\u2714")} ${badge} ${chalk2.blackBright(this.ts())} ${chalk2.blackBright("|")} ${filePart} ${chalk2.green("\u2192")} ${chalk2.green(action)}`
634
+ );
635
+ }
636
+ /**
637
+ * ⚡ [ RELOAD ] 14:24:03 | src/routes/user.ts → restarting server...
638
+ */
639
+ printReloading(file) {
640
+ const badge = colors2.bgYellow(colors2.white.bold(" RELOAD "));
641
+ const filePart = chalk2.yellow.bold(file.padEnd(32));
642
+ console.log(
643
+ `
644
+ ${chalk2.yellow("\u26A1")} ${badge} ${chalk2.blackBright(this.ts())} ${chalk2.blackBright("|")} ${filePart} ${chalk2.yellow("\u2192")} ${chalk2.yellow("restarting server...")}
645
+ `
646
+ );
647
+ }
648
+ // ─── Graceful shutdown on CTRL+C ──────────────────────────────────────────
649
+ setupProcessSignals() {
650
+ const shutdown = () => {
651
+ const badge = colors2.bgBlackBright(colors2.white.bold(" STOPPED"));
652
+ console.log(
653
+ `
654
+ ${chalk2.gray("\u25A0")} ${badge} ${chalk2.blackBright(this.ts())} ${chalk2.blackBright("|")} ${chalk2.gray("watcher stopped")}
655
+ `
656
+ );
657
+ this.watchers.forEach((w) => {
658
+ w.close();
659
+ });
660
+ process.exit(0);
661
+ };
662
+ process.once("SIGINT", shutdown);
663
+ process.once("SIGTERM", shutdown);
664
+ }
665
+ };
666
+
667
+ // src/core/webServer.core.ts
362
668
  var WebServerCore = class {
363
669
  serverUtility;
364
670
  expressApp = express2();
671
+ container;
365
672
  localLanguage;
366
673
  loggerConfig;
367
674
  routerExpressApp;
@@ -369,6 +676,7 @@ var WebServerCore = class {
369
676
  environmentPath;
370
677
  serverListenEvent;
371
678
  dependenciesRegistered = false;
679
+ hotReloadCfg;
372
680
  constructor(paramsConstructor) {
373
681
  this.loadTranslationFiles();
374
682
  this.stackTraceErrorHandling();
@@ -377,6 +685,7 @@ var WebServerCore = class {
377
685
  this.loggerConfig = paramsConstructor.loggerConfig;
378
686
  this.localLanguage = paramsConstructor.localLanguage;
379
687
  this.environmentPath = paramsConstructor.environmentPath;
688
+ this.container = new SContainer3(paramsConstructor.localLanguage);
380
689
  this.expressApp.use(express2.json());
381
690
  this.expressApp.use(express2.raw());
382
691
  this.expressApp.use(express2.text());
@@ -384,17 +693,20 @@ var WebServerCore = class {
384
693
  this.expressApp.use(corsOrigin(paramsConstructor.corsOriginOptions));
385
694
  this.serverListenEvent = new ServerListenEventError2(paramsConstructor.localLanguage);
386
695
  this.serverUtility = new CoreService(paramsConstructor.localLanguage, paramsConstructor.environmentPath);
696
+ const hr = paramsConstructor.hotReload;
697
+ this.hotReloadCfg = !hr ? null : hr === true ? {} : hr;
387
698
  }
699
+ /**
700
+ *
701
+ * @param dependencies
702
+ */
388
703
  registerDependencies(dependencies2) {
704
+ this.loadTranslationFiles();
389
705
  try {
706
+ getEnvFileLoadingService(".env", this.localLanguage);
390
707
  if (dependencies2 && dependencies2.length > 0) {
391
708
  dependencies2.forEach((dependency) => {
392
- return new SContainer2(this.localLanguage).register(dependency.key, dependency.factory, dependency.scope);
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)
709
+ this.container.register(dependency.key, dependency.factory, dependency.scope);
398
710
  });
399
711
  }
400
712
  } catch (err) {
@@ -407,8 +719,15 @@ var WebServerCore = class {
407
719
  });
408
720
  }
409
721
  }
722
+ /**
723
+ *
724
+ * @param routers
725
+ * @param databaseCallback
726
+ * @param dependenciesProvider
727
+ */
410
728
  onStartServer(routers, databaseCallback, dependenciesProvider) {
411
729
  this.loadTranslationFiles();
730
+ getEnvFileLoadingService(".env", this.localLanguage);
412
731
  if (this.getEnvironmentValue.appPort === "" && Number(this.getEnvironmentValue.appPort) === 0) {
413
732
  this.serverListenEvent.hostPortUndefined(Number(this.getEnvironmentValue.appPort));
414
733
  } else if (this.getEnvironmentValue.appHost === "") {
@@ -417,29 +736,41 @@ var WebServerCore = class {
417
736
  this.serverListenEvent.portUndefined();
418
737
  } else if (this.localLanguage === "") {
419
738
  SLogger(this.environmentPath).logger.error({
420
- message: TranslationLoader2.t("noDefaultLocalLang", this.localLanguage),
421
- title: TranslationLoader2.t("noLocalLang", this.localLanguage),
422
- errorType: TranslationLoader2.t("localLangMissing", this.localLanguage),
739
+ message: TranslationLoader3.t("noDefaultLocalLang", this.localLanguage),
740
+ title: TranslationLoader3.t("noLocalLang", this.localLanguage),
741
+ errorType: TranslationLoader3.t("localLangMissing", this.localLanguage),
423
742
  stackTrace: void 0,
424
743
  httpCodeValue: HttpStatusCode3.NOT_FOUND
425
744
  });
426
745
  } else {
427
- return this.expressApp.listen(
746
+ const server = this.expressApp.listen(
428
747
  Number(this.getEnvironmentValue.appPort),
429
748
  () => {
430
749
  this.loadTranslationFiles();
431
750
  try {
432
751
  databaseCallback(this.getEnvironmentValue);
433
- new SContainer2(this.localLanguage, dependenciesProvider);
434
- this.expressApp.use(express2.static(path2.join(process4.cwd(), "public/template")));
752
+ if (dependenciesProvider) {
753
+ this.registerDependencies(dependenciesProvider);
754
+ }
755
+ this.container.loadServices();
756
+ this.expressApp.use(express2.static(path3.join(process6.cwd(), "public/template")));
435
757
  this.registerRoutes(routers);
758
+ const resolvedHmr = this.resolveHotReloadConfig();
759
+ if (resolvedHmr !== null) {
760
+ new HotReloadWatcher(resolvedHmr).attach(server);
761
+ }
436
762
  } catch (err) {
437
763
  SServerStartError(err, this.environmentPath);
438
764
  }
439
765
  }
440
766
  );
767
+ return server;
441
768
  }
442
769
  }
770
+ /**
771
+ *
772
+ * @param serverWeb
773
+ */
443
774
  onListeningOnServerEvent(serverWeb) {
444
775
  this.loadTranslationFiles();
445
776
  serverWeb.on(eventName2.error, (err) => {
@@ -452,6 +783,10 @@ var WebServerCore = class {
452
783
  this.infoWebApp();
453
784
  });
454
785
  }
786
+ /**
787
+ *
788
+ * @param serverWeb
789
+ */
455
790
  onRequestOnServerEvent(serverWeb) {
456
791
  this.loadTranslationFiles();
457
792
  serverWeb.on(eventName2.request, (req, res) => {
@@ -473,6 +808,62 @@ var WebServerCore = class {
473
808
  loadTranslationFiles() {
474
809
  loaderTranslationFile(this.localLanguage);
475
810
  }
811
+ /**
812
+ * Resolve the final HotReloadConfig by merging constructor config with
813
+ * HMR environment variables.
814
+ *
815
+ * Priority (highest → lowest):
816
+ * 1. Constructor hotReload properties (explicit code-level config)
817
+ * 2. HMR_* env variables (runtime / per-environment config)
818
+ * 3. HotReloadWatcher internal defaults (built-in fallbacks)
819
+ *
820
+ * The watcher starts when:
821
+ * - constructor passed hotReload: true | HotReloadConfig
822
+ * - OR HMR_ENABLED=true in the .env file
823
+ */
824
+ resolveHotReloadConfig() {
825
+ const env = this.getEnvironmentValue;
826
+ if (this.hotReloadCfg === null && !env.hmrEnabled) {
827
+ return null;
828
+ }
829
+ const base = this.hotReloadCfg ?? {};
830
+ const envExtensions = this.hmrExtractExtensions(env.hmrWatchPatterns);
831
+ const envIgnore = this.hmrExtractIgnore(env.hmrIgnorePatterns);
832
+ return {
833
+ rootDir: base.rootDir,
834
+ watchDirs: base.watchDirs,
835
+ watchExtensions: base.watchExtensions ?? (envExtensions.length ? envExtensions : void 0),
836
+ ignore: base.ignore ?? (envIgnore.length ? envIgnore : void 0),
837
+ hotReloadExtensions: base.hotReloadExtensions,
838
+ debounceMs: base.debounceMs ?? (env.hmrDebounceMs > 0 ? env.hmrDebounceMs : void 0),
839
+ envFile: base.envFile
840
+ };
841
+ }
842
+ /**
843
+ * Extract file extensions from glob patterns such as "src/** /*.ts".
844
+ * "src/** /*.ts"
845
+ * ".env" skipped, handled natively by the watcher
846
+ */
847
+ hmrExtractExtensions(patterns) {
848
+ const exts = /* @__PURE__ */ new Set();
849
+ for (const p of patterns) {
850
+ const m = p.match(/\*(\.[a-zA-Z0-9]+)$/);
851
+ if (m) {
852
+ exts.add(m[1]);
853
+ }
854
+ }
855
+ return [...exts];
856
+ }
857
+ /**
858
+ * Extract ignore names from glob patterns such as "node_modules/**".
859
+ * "node_modules/**" → "node_modules"
860
+ * "dist/**" → "dist"
861
+ */
862
+ hmrExtractIgnore(patterns) {
863
+ return [...new Set(
864
+ patterns.map((p) => p.split("/")[0]).filter(Boolean)
865
+ )];
866
+ }
476
867
  /**
477
868
  *
478
869
  * @param allFeatureRoutes
@@ -505,6 +896,7 @@ var WebServerCore = class {
505
896
  }
506
897
  };
507
898
  export {
899
+ HotReloadWatcher,
508
900
  WebServerCore as WebServer,
509
901
  envPath
510
902
  };