opticore-webapp 1.0.71 → 1.0.73

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,6 +1,6 @@
1
1
  // src/core/webServer.core.ts
2
2
  import * as path3 from "path";
3
- import process6 from "process";
3
+ import process5 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";
@@ -10,7 +10,7 @@ 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 process2 from "process";
13
+ import process 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
- process2.on(event.beforeExit, (code) => {
24
+ process.on(event.beforeExit, (code) => {
25
25
  setTimeout(() => {
26
26
  serverListenEvent.processBeforeExit(code);
27
27
  }, 100);
28
28
  });
29
- process2.on(event.disconnect, () => {
29
+ process.on(event.disconnect, () => {
30
30
  serverListenEvent.processDisconnected();
31
31
  });
32
- process2.on(event.exit, (code) => {
32
+ process.on(event.exit, (code) => {
33
33
  serverListenEvent.exited(code);
34
34
  });
35
- process2.on(event.rejectionHandled, (promise) => {
35
+ process.on(event.rejectionHandled, (promise) => {
36
36
  serverListenEvent.promiseRejectionHandled(promise);
37
37
  });
38
- process2.on(event.uncaughtException, (error) => {
38
+ process.on(event.uncaughtException, (error) => {
39
39
  serverListenEvent.uncaughtException(error);
40
40
  });
41
- process2.on(event.uncaughtExceptionMonitor, (error) => {
41
+ process.on(event.uncaughtExceptionMonitor, (error) => {
42
42
  serverListenEvent.uncaughtExceptionMonitor(error);
43
43
  });
44
- process2.on(event.unhandledRejection, (reason, promise) => {
44
+ process.on(event.unhandledRejection, (reason, promise) => {
45
45
  serverListenEvent.unhandledRejection(reason, promise);
46
46
  });
47
- process2.on(event.warning, (warning) => {
47
+ process.on(event.warning, (warning) => {
48
48
  serverListenEvent.warning(warning);
49
49
  });
50
- process2.on(event.message, (message) => {
50
+ process.on(event.message, (message) => {
51
51
  serverListenEvent.message(message);
52
52
  });
53
- process2.on(event.multipleResolves, (type, promise, reason) => {
53
+ process.on(event.multipleResolves, (type, promise, reason) => {
54
54
  serverListenEvent.multipleResolves(type, promise, reason);
55
55
  });
56
- process2.on(event.sigint, () => {
56
+ process.on(event.sigint, () => {
57
57
  serverListenEvent.processInterrupted();
58
58
  });
59
- process2.on(event.sigterm, (signal) => {
59
+ process.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 process4 from "process";
68
+ import process3 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 process3 from "process";
79
- var envPath = process3.cwd() + "/config/env/.env";
78
+ import process2 from "process";
79
+ var envPath = process2.cwd() + "/config/env/.env";
80
80
 
81
81
  // src/application/service/loaderTranslationFile.service.ts
82
82
  import { translationLoaderConfig } from "opticore-loader-translation";
@@ -167,7 +167,7 @@ var CoreService = class {
167
167
  getEnvFileLoading(filePath) {
168
168
  this.loadTranslationFiles();
169
169
  try {
170
- const fullPath = path.resolve(process4.cwd(), filePath);
170
+ const fullPath = path.resolve(process3.cwd(), filePath);
171
171
  if (fs.existsSync(fullPath)) {
172
172
  const env = fs.readFileSync(fullPath, "utf-8");
173
173
  const lines = env.split("\n");
@@ -175,7 +175,7 @@ var CoreService = class {
175
175
  const match = line.match(/^([^#=]+)=([^#]+)$/);
176
176
  if (match) {
177
177
  const key = match[1].trim();
178
- process4.env[key] = match[2].trim();
178
+ process3.env[key] = match[2].trim();
179
179
  }
180
180
  });
181
181
  }
@@ -193,7 +193,7 @@ var CoreService = class {
193
193
  * Returns an Object containing a node version, openssl, and v0
194
194
  */
195
195
  getVersions() {
196
- const { node, openssl, v8 } = process4.versions;
196
+ const { node, openssl, v8 } = process3.versions;
197
197
  const data = {
198
198
  "node version": node,
199
199
  "openssl": openssl,
@@ -214,14 +214,14 @@ var CoreService = class {
214
214
  */
215
215
  getUsageMemory() {
216
216
  this.loadTranslationFiles();
217
- const memoryData = process4.memoryUsage();
217
+ const memoryData = process3.memoryUsage();
218
218
  const data = {
219
219
  [TranslationLoader.t("totalMemoryAllocated", this.localLanguage)]: this.formatMemoryUsage(memoryData.rss),
220
220
  [TranslationLoader.t("sizeAllocatedHeap", this.localLanguage)]: this.formatMemoryUsage(memoryData.heapTotal),
221
221
  [TranslationLoader.t("memoryUsedExecution", this.localLanguage)]: this.formatMemoryUsage(memoryData.heapUsed),
222
222
  [TranslationLoader.t("externalMemory", this.localLanguage)]: this.formatMemoryUsage(memoryData.external),
223
- [TranslationLoader.t("memoryUsageUser", this.localLanguage)]: this.formatMemoryUsage(process4.cpuUsage().user),
224
- [TranslationLoader.t("memoryUsageSystem", this.localLanguage)]: this.formatMemoryUsage(process4.cpuUsage().system)
223
+ [TranslationLoader.t("memoryUsageUser", this.localLanguage)]: this.formatMemoryUsage(process3.cpuUsage().user),
224
+ [TranslationLoader.t("memoryUsageSystem", this.localLanguage)]: this.formatMemoryUsage(process3.cpuUsage().system)
225
225
  };
226
226
  return {
227
227
  "rss": data[TranslationLoader.t("totalMemoryAllocated", this.localLanguage)],
@@ -230,18 +230,18 @@ var CoreService = class {
230
230
  "external": data[TranslationLoader.t("externalMemory", this.localLanguage)],
231
231
  "user": data[TranslationLoader.t("memoryUsageUser", this.localLanguage)],
232
232
  "system": data[TranslationLoader.t("memoryUsageSystem", this.localLanguage)],
233
- "pid": process4.pid
233
+ "pid": process3.pid
234
234
  };
235
235
  }
236
236
  /**
237
237
  * Return an Object containing a project path and running server time
238
238
  */
239
239
  getProjectInfo() {
240
- const startTime = process4.hrtime();
241
- const endTime = process4.hrtime(startTime);
240
+ const startTime = process3.hrtime();
241
+ const endTime = process3.hrtime(startTime);
242
242
  const executionTime = (endTime[0] * 1e9 + endTime[1]) / 1e6;
243
243
  return {
244
- "projectPath": path.join(process4.cwd()),
244
+ "projectPath": path.join(process3.cwd()),
245
245
  "startingTime": `${executionTime.toFixed(5)} ms`
246
246
  };
247
247
  }
@@ -290,7 +290,7 @@ var CoreService = class {
290
290
  const maxLength = Math.max(...messages.map((m) => {
291
291
  return m.replace(/\u001b\[[0-9]{1,2}m/g, "").length;
292
292
  })) + 4;
293
- console.log(chalk.blackBright(`${TranslationLoader.t("tailingServerLog", this.localLanguage)} (${path.join(path.basename(process4.cwd()), "logs", "app.log")})`));
293
+ console.log(chalk.blackBright(`${TranslationLoader.t("tailingServerLog", this.localLanguage)} (${path.join(path.basename(process3.cwd()), "logs", "app.log")})`));
294
294
  const border = chalk.bgGreen.white(" ".repeat(maxLength));
295
295
  console.log(border);
296
296
  messages.forEach((msg) => {
@@ -395,7 +395,7 @@ import { SContainer as SContainer3 } from "opticore-dependency-inject";
395
395
 
396
396
  // src/application/service/getEnvFileLoading.service.ts
397
397
  import path2 from "path";
398
- import process5 from "process";
398
+ import process4 from "process";
399
399
  import fs2 from "fs";
400
400
  import { TranslationLoader as TranslationLoader2 } from "opticore-translator";
401
401
  import { HttpStatusCode as status2 } from "opticore-http-response";
@@ -403,7 +403,7 @@ var getEnvFileLoadingService = (filePath, localLanguage) => {
403
403
  loaderTranslationFile(localLanguage);
404
404
  const logger = dependenciesContainerProvider(localLanguage).resolve("LoggerCore");
405
405
  try {
406
- const fullPath = path2.resolve(process5.cwd(), filePath);
406
+ const fullPath = path2.resolve(process4.cwd(), filePath);
407
407
  if (fs2.existsSync(fullPath)) {
408
408
  const env = fs2.readFileSync(fullPath, "utf-8");
409
409
  const lines = env.split("\n");
@@ -411,7 +411,7 @@ var getEnvFileLoadingService = (filePath, localLanguage) => {
411
411
  const match = line.match(/^([^#=]+)=([^#]+)$/);
412
412
  if (match) {
413
413
  const key = match[1].trim();
414
- process5.env[key] = match[2].trim();
414
+ process4.env[key] = match[2].trim();
415
415
  }
416
416
  });
417
417
  }
@@ -427,6 +427,7 @@ var getEnvFileLoadingService = (filePath, localLanguage) => {
427
427
  };
428
428
 
429
429
  // src/core/webServer.core.ts
430
+ import { HotReloadWatcher } from "opticore-watcher";
430
431
  var WebServerCore = class {
431
432
  serverUtility;
432
433
  expressApp = express2();
@@ -438,6 +439,7 @@ var WebServerCore = class {
438
439
  environmentPath;
439
440
  serverListenEvent;
440
441
  dependenciesRegistered = false;
442
+ hotReloadCfg;
441
443
  constructor(paramsConstructor) {
442
444
  this.loadTranslationFiles();
443
445
  this.stackTraceErrorHandling();
@@ -454,6 +456,8 @@ var WebServerCore = class {
454
456
  this.expressApp.use(corsOrigin(paramsConstructor.corsOriginOptions));
455
457
  this.serverListenEvent = new ServerListenEventError2(paramsConstructor.localLanguage);
456
458
  this.serverUtility = new CoreService(paramsConstructor.localLanguage, paramsConstructor.environmentPath);
459
+ const hr = paramsConstructor.hotReload;
460
+ this.hotReloadCfg = !hr ? null : hr === true ? {} : hr;
457
461
  }
458
462
  /**
459
463
  *
@@ -502,7 +506,7 @@ var WebServerCore = class {
502
506
  httpCodeValue: HttpStatusCode3.NOT_FOUND
503
507
  });
504
508
  } else {
505
- return this.expressApp.listen(
509
+ const server = this.expressApp.listen(
506
510
  Number(this.getEnvironmentValue.appPort),
507
511
  () => {
508
512
  this.loadTranslationFiles();
@@ -512,13 +516,18 @@ var WebServerCore = class {
512
516
  this.registerDependencies(dependenciesProvider);
513
517
  }
514
518
  this.container.loadServices();
515
- this.expressApp.use(express2.static(path3.join(process6.cwd(), "public/template")));
519
+ this.expressApp.use(express2.static(path3.join(process5.cwd(), "public/template")));
516
520
  this.registerRoutes(routers);
521
+ const resolvedHmr = this.resolveHotReloadConfig();
522
+ if (resolvedHmr !== null) {
523
+ new HotReloadWatcher(resolvedHmr).attach(server);
524
+ }
517
525
  } catch (err) {
518
526
  SServerStartError(err, this.environmentPath);
519
527
  }
520
528
  }
521
529
  );
530
+ return server;
522
531
  }
523
532
  }
524
533
  /**
@@ -562,6 +571,62 @@ var WebServerCore = class {
562
571
  loadTranslationFiles() {
563
572
  loaderTranslationFile(this.localLanguage);
564
573
  }
574
+ /**
575
+ * Resolve the final HotReloadConfig by merging constructor config with
576
+ * HMR environment variables.
577
+ *
578
+ * Priority (highest → lowest):
579
+ * 1. Constructor hotReload properties (explicit code-level config)
580
+ * 2. HMR_* env variables (runtime / per-environment config)
581
+ * 3. HotReloadWatcher internal defaults (built-in fallbacks)
582
+ *
583
+ * The watcher starts when:
584
+ * - constructor passed hotReload: true | HotReloadConfig
585
+ * - OR HMR_ENABLED=true in the .env file
586
+ */
587
+ resolveHotReloadConfig() {
588
+ const env = this.getEnvironmentValue;
589
+ if (this.hotReloadCfg === null && !env.hmrEnabled) {
590
+ return null;
591
+ }
592
+ const base = this.hotReloadCfg ?? {};
593
+ const envExtensions = this.hmrExtractExtensions(env.hmrWatchPatterns);
594
+ const envIgnore = this.hmrExtractIgnore(env.hmrIgnorePatterns);
595
+ return {
596
+ rootDir: base.rootDir,
597
+ watchDirs: base.watchDirs,
598
+ watchExtensions: base.watchExtensions ?? (envExtensions.length ? envExtensions : void 0),
599
+ ignore: base.ignore ?? (envIgnore.length ? envIgnore : void 0),
600
+ hotReloadExtensions: base.hotReloadExtensions,
601
+ debounceMs: base.debounceMs ?? (env.hmrDebounceMs > 0 ? env.hmrDebounceMs : void 0),
602
+ envFile: base.envFile
603
+ };
604
+ }
605
+ /**
606
+ * Extract file extensions from glob patterns such as "src/** /*.ts".
607
+ * "src/** /*.ts"
608
+ * ".env" skipped, handled natively by the watcher
609
+ */
610
+ hmrExtractExtensions(patterns) {
611
+ const exts = /* @__PURE__ */ new Set();
612
+ for (const p of patterns) {
613
+ const m = p.match(/\*(\.[a-zA-Z0-9]+)$/);
614
+ if (m) {
615
+ exts.add(m[1]);
616
+ }
617
+ }
618
+ return [...exts];
619
+ }
620
+ /**
621
+ * Extract ignore names from glob patterns such as "node_modules/**".
622
+ * "node_modules/**" → "node_modules"
623
+ * "dist/**" → "dist"
624
+ */
625
+ hmrExtractIgnore(patterns) {
626
+ return [...new Set(
627
+ patterns.map((p) => p.split("/")[0]).filter(Boolean)
628
+ )];
629
+ }
565
630
  /**
566
631
  *
567
632
  * @param allFeatureRoutes
@@ -593,399 +658,7 @@ var WebServerCore = class {
593
658
  );
594
659
  }
595
660
  };
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
- };
987
661
  export {
988
- HotReloadWatcher,
989
662
  WebServerCore as WebServer,
990
663
  envPath
991
664
  };