build(deps-dev): apply updates
[e-mobility-charging-stations-simulator.git] / src / utils / Configuration.ts
1 import { existsSync, type FSWatcher, readFileSync, watch } from 'node:fs'
2 import { dirname, join } from 'node:path'
3 import { env } from 'node:process'
4 import { fileURLToPath } from 'node:url'
5
6 import chalk from 'chalk'
7 import { mergeDeepRight, once } from 'rambda'
8
9 import {
10 ApplicationProtocol,
11 ApplicationProtocolVersion,
12 type ConfigurationData,
13 ConfigurationSection,
14 FileType,
15 type LogConfiguration,
16 type StationTemplateUrl,
17 type StorageConfiguration,
18 StorageType,
19 SupervisionUrlDistribution,
20 type UIServerConfiguration,
21 type WorkerConfiguration
22 } from '../types/index.js'
23 import {
24 DEFAULT_ELEMENT_ADD_DELAY,
25 DEFAULT_POOL_MAX_SIZE,
26 DEFAULT_POOL_MIN_SIZE,
27 DEFAULT_WORKER_START_DELAY,
28 WorkerProcessType
29 } from '../worker/index.js'
30 import {
31 buildPerformanceUriFilePath,
32 checkWorkerElementsPerWorker,
33 checkWorkerProcessType,
34 getDefaultPerformanceStorageUri,
35 handleFileException,
36 logPrefix
37 } from './ConfigurationUtils.js'
38 import { Constants } from './Constants.js'
39 import { hasOwnProp, isCFEnvironment } from './Utils.js'
40
41 type ConfigurationSectionType =
42 | LogConfiguration
43 | StorageConfiguration
44 | WorkerConfiguration
45 | UIServerConfiguration
46
47 const defaultUIServerConfiguration: UIServerConfiguration = {
48 enabled: false,
49 type: ApplicationProtocol.WS,
50 version: ApplicationProtocolVersion.VERSION_11,
51 options: {
52 host: Constants.DEFAULT_UI_SERVER_HOST,
53 port: Constants.DEFAULT_UI_SERVER_PORT
54 }
55 }
56
57 const defaultStorageConfiguration: StorageConfiguration = {
58 enabled: true,
59 type: StorageType.NONE
60 }
61
62 const defaultLogConfiguration: LogConfiguration = {
63 enabled: true,
64 file: 'logs/combined.log',
65 errorFile: 'logs/error.log',
66 statisticsInterval: Constants.DEFAULT_LOG_STATISTICS_INTERVAL,
67 level: 'info',
68 format: 'simple',
69 rotate: true
70 }
71
72 const defaultWorkerConfiguration: WorkerConfiguration = {
73 processType: WorkerProcessType.workerSet,
74 startDelay: DEFAULT_WORKER_START_DELAY,
75 elementsPerWorker: 'auto',
76 elementAddDelay: DEFAULT_ELEMENT_ADD_DELAY,
77 poolMinSize: DEFAULT_POOL_MIN_SIZE,
78 poolMaxSize: DEFAULT_POOL_MAX_SIZE
79 }
80
81 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
82 export class Configuration {
83 public static configurationChangeCallback?: () => Promise<void>
84
85 private static configurationFile: string | undefined
86 private static configurationFileReloading = false
87 private static configurationData?: ConfigurationData
88 private static configurationFileWatcher?: FSWatcher
89 private static configurationSectionCache: Map<ConfigurationSection, ConfigurationSectionType>
90
91 static {
92 const configurationFile = join(dirname(fileURLToPath(import.meta.url)), 'assets', 'config.json')
93 if (existsSync(configurationFile)) {
94 Configuration.configurationFile = configurationFile
95 } else {
96 console.error(
97 `${chalk.green(logPrefix())} ${chalk.red(
98 "Configuration file './src/assets/config.json' not found, using default configuration"
99 )}`
100 )
101 Configuration.configurationData = {
102 stationTemplateUrls: [
103 {
104 file: 'siemens.station-template.json',
105 numberOfStations: 1
106 }
107 ],
108 supervisionUrls: 'ws://localhost:8180/steve/websocket/CentralSystemService',
109 supervisionUrlDistribution: SupervisionUrlDistribution.ROUND_ROBIN,
110 uiServer: defaultUIServerConfiguration,
111 performanceStorage: defaultStorageConfiguration,
112 log: defaultLogConfiguration,
113 worker: defaultWorkerConfiguration
114 }
115 }
116 Configuration.configurationSectionCache = new Map<
117 ConfigurationSection,
118 ConfigurationSectionType
119 >([
120 [ConfigurationSection.log, Configuration.buildLogSection()],
121 [ConfigurationSection.performanceStorage, Configuration.buildPerformanceStorageSection()],
122 [ConfigurationSection.worker, Configuration.buildWorkerSection()],
123 [ConfigurationSection.uiServer, Configuration.buildUIServerSection()]
124 ])
125 }
126
127 private constructor () {
128 // This is intentional
129 }
130
131 public static getConfigurationSection<T extends ConfigurationSectionType>(
132 sectionName: ConfigurationSection
133 ): T {
134 if (!Configuration.isConfigurationSectionCached(sectionName)) {
135 Configuration.cacheConfigurationSection(sectionName)
136 }
137 return Configuration.configurationSectionCache.get(sectionName) as T
138 }
139
140 public static getStationTemplateUrls (): StationTemplateUrl[] | undefined {
141 const checkDeprecatedConfigurationKeysOnce = once(
142 Configuration.checkDeprecatedConfigurationKeys.bind(Configuration)
143 )
144 checkDeprecatedConfigurationKeysOnce()
145 return Configuration.getConfigurationData()?.stationTemplateUrls
146 }
147
148 public static getSupervisionUrls (): string | string[] | undefined {
149 if (
150 Configuration.getConfigurationData()?.['supervisionURLs' as keyof ConfigurationData] != null
151 ) {
152 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
153 Configuration.getConfigurationData()!.supervisionUrls = Configuration.getConfigurationData()![
154 'supervisionURLs' as keyof ConfigurationData
155 ] as string | string[]
156 }
157 return Configuration.getConfigurationData()?.supervisionUrls
158 }
159
160 public static getSupervisionUrlDistribution (): SupervisionUrlDistribution | undefined {
161 return hasOwnProp(Configuration.getConfigurationData(), 'supervisionUrlDistribution')
162 ? Configuration.getConfigurationData()?.supervisionUrlDistribution
163 : SupervisionUrlDistribution.ROUND_ROBIN
164 }
165
166 public static workerPoolInUse (): boolean {
167 return [WorkerProcessType.dynamicPool, WorkerProcessType.fixedPool].includes(
168 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
169 Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
170 .processType!
171 )
172 }
173
174 public static workerDynamicPoolInUse (): boolean {
175 return (
176 Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
177 .processType === WorkerProcessType.dynamicPool
178 )
179 }
180
181 private static isConfigurationSectionCached (sectionName: ConfigurationSection): boolean {
182 return Configuration.configurationSectionCache.has(sectionName)
183 }
184
185 private static cacheConfigurationSection (sectionName: ConfigurationSection): void {
186 switch (sectionName) {
187 case ConfigurationSection.log:
188 Configuration.configurationSectionCache.set(sectionName, Configuration.buildLogSection())
189 break
190 case ConfigurationSection.performanceStorage:
191 Configuration.configurationSectionCache.set(
192 sectionName,
193 Configuration.buildPerformanceStorageSection()
194 )
195 break
196 case ConfigurationSection.worker:
197 Configuration.configurationSectionCache.set(sectionName, Configuration.buildWorkerSection())
198 break
199 case ConfigurationSection.uiServer:
200 Configuration.configurationSectionCache.set(
201 sectionName,
202 Configuration.buildUIServerSection()
203 )
204 break
205 default:
206 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
207 throw new Error(`Unknown configuration section '${sectionName}'`)
208 }
209 }
210
211 private static buildUIServerSection (): UIServerConfiguration {
212 let uiServerConfiguration: UIServerConfiguration = defaultUIServerConfiguration
213 if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.uiServer)) {
214 uiServerConfiguration = mergeDeepRight(
215 uiServerConfiguration,
216 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
217 Configuration.getConfigurationData()!.uiServer!
218 )
219 }
220 if (isCFEnvironment()) {
221 delete uiServerConfiguration.options?.host
222 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
223 uiServerConfiguration.options!.port = Number.parseInt(env.PORT!)
224 }
225 return uiServerConfiguration
226 }
227
228 private static buildPerformanceStorageSection (): StorageConfiguration {
229 let storageConfiguration: StorageConfiguration
230 switch (Configuration.getConfigurationData()?.performanceStorage?.type) {
231 case StorageType.SQLITE:
232 storageConfiguration = {
233 enabled: false,
234 type: StorageType.SQLITE,
235 uri: getDefaultPerformanceStorageUri(StorageType.SQLITE)
236 }
237 break
238 case StorageType.JSON_FILE:
239 storageConfiguration = {
240 enabled: false,
241 type: StorageType.JSON_FILE,
242 uri: getDefaultPerformanceStorageUri(StorageType.JSON_FILE)
243 }
244 break
245 case StorageType.NONE:
246 default:
247 storageConfiguration = defaultStorageConfiguration
248 break
249 }
250 if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.performanceStorage)) {
251 storageConfiguration = {
252 ...storageConfiguration,
253 ...Configuration.getConfigurationData()?.performanceStorage,
254 ...((Configuration.getConfigurationData()?.performanceStorage?.type ===
255 StorageType.JSON_FILE ||
256 Configuration.getConfigurationData()?.performanceStorage?.type === StorageType.SQLITE) &&
257 Configuration.getConfigurationData()?.performanceStorage?.uri != null && {
258 uri: buildPerformanceUriFilePath(
259 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
260 new URL(Configuration.getConfigurationData()!.performanceStorage!.uri!).pathname
261 )
262 })
263 }
264 }
265 return storageConfiguration
266 }
267
268 private static buildLogSection (): LogConfiguration {
269 const deprecatedLogConfiguration: LogConfiguration = {
270 ...(hasOwnProp(Configuration.getConfigurationData(), 'logEnabled') && {
271 enabled: Configuration.getConfigurationData()?.logEnabled
272 }),
273 ...(hasOwnProp(Configuration.getConfigurationData(), 'logFile') && {
274 file: Configuration.getConfigurationData()?.logFile
275 }),
276 ...(hasOwnProp(Configuration.getConfigurationData(), 'logErrorFile') && {
277 errorFile: Configuration.getConfigurationData()?.logErrorFile
278 }),
279 ...(hasOwnProp(Configuration.getConfigurationData(), 'logStatisticsInterval') && {
280 statisticsInterval: Configuration.getConfigurationData()?.logStatisticsInterval
281 }),
282 ...(hasOwnProp(Configuration.getConfigurationData(), 'logLevel') && {
283 level: Configuration.getConfigurationData()?.logLevel
284 }),
285 ...(hasOwnProp(Configuration.getConfigurationData(), 'logConsole') && {
286 console: Configuration.getConfigurationData()?.logConsole
287 }),
288 ...(hasOwnProp(Configuration.getConfigurationData(), 'logFormat') && {
289 format: Configuration.getConfigurationData()?.logFormat
290 }),
291 ...(hasOwnProp(Configuration.getConfigurationData(), 'logRotate') && {
292 rotate: Configuration.getConfigurationData()?.logRotate
293 }),
294 ...(hasOwnProp(Configuration.getConfigurationData(), 'logMaxFiles') && {
295 maxFiles: Configuration.getConfigurationData()?.logMaxFiles
296 }),
297 ...(hasOwnProp(Configuration.getConfigurationData(), 'logMaxSize') && {
298 maxSize: Configuration.getConfigurationData()?.logMaxSize
299 })
300 }
301 const logConfiguration: LogConfiguration = {
302 ...defaultLogConfiguration,
303 ...deprecatedLogConfiguration,
304 ...(hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.log) &&
305 Configuration.getConfigurationData()?.log)
306 }
307 return logConfiguration
308 }
309
310 private static buildWorkerSection (): WorkerConfiguration {
311 const deprecatedWorkerConfiguration: WorkerConfiguration = {
312 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerProcess') && {
313 processType: Configuration.getConfigurationData()?.workerProcess
314 }),
315 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerStartDelay') && {
316 startDelay: Configuration.getConfigurationData()?.workerStartDelay
317 }),
318 ...(hasOwnProp(Configuration.getConfigurationData(), 'chargingStationsPerWorker') && {
319 elementsPerWorker: Configuration.getConfigurationData()?.chargingStationsPerWorker
320 }),
321 ...(hasOwnProp(Configuration.getConfigurationData(), 'elementAddDelay') && {
322 elementAddDelay: Configuration.getConfigurationData()?.elementAddDelay
323 }),
324 ...(hasOwnProp(Configuration.getConfigurationData()?.worker, 'elementStartDelay') && {
325 elementAddDelay: Configuration.getConfigurationData()?.worker?.elementStartDelay
326 }),
327 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerPoolMinSize') && {
328 poolMinSize: Configuration.getConfigurationData()?.workerPoolMinSize
329 }),
330 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerPoolMaxSize') && {
331 poolMaxSize: Configuration.getConfigurationData()?.workerPoolMaxSize
332 })
333 }
334 hasOwnProp(Configuration.getConfigurationData(), 'workerPoolStrategy') &&
335 delete Configuration.getConfigurationData()?.workerPoolStrategy
336 const workerConfiguration: WorkerConfiguration = {
337 ...defaultWorkerConfiguration,
338 ...deprecatedWorkerConfiguration,
339 ...(hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.worker) &&
340 Configuration.getConfigurationData()?.worker)
341 }
342 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
343 checkWorkerProcessType(workerConfiguration.processType!)
344 checkWorkerElementsPerWorker(workerConfiguration.elementsPerWorker)
345 return workerConfiguration
346 }
347
348 private static checkDeprecatedConfigurationKeys (): void {
349 // connection timeout
350 Configuration.warnDeprecatedConfigurationKey(
351 'autoReconnectTimeout',
352 undefined,
353 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
354 )
355 Configuration.warnDeprecatedConfigurationKey(
356 'connectionTimeout',
357 undefined,
358 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
359 )
360 // connection retries
361 Configuration.warnDeprecatedConfigurationKey(
362 'autoReconnectMaxRetries',
363 undefined,
364 'Use it in charging station template instead'
365 )
366 // station template url(s)
367 Configuration.warnDeprecatedConfigurationKey(
368 'stationTemplateURLs',
369 undefined,
370 "Use 'stationTemplateUrls' instead"
371 )
372 Configuration.getConfigurationData()?.['stationTemplateURLs' as keyof ConfigurationData] !=
373 null &&
374 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
375 (Configuration.getConfigurationData()!.stationTemplateUrls =
376 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
377 Configuration.getConfigurationData()![
378 'stationTemplateURLs' as keyof ConfigurationData
379 ] as StationTemplateUrl[])
380 Configuration.getConfigurationData()?.stationTemplateUrls.forEach(
381 (stationTemplateUrl: StationTemplateUrl) => {
382 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
383 if (stationTemplateUrl['numberOfStation' as keyof StationTemplateUrl] != null) {
384 console.error(
385 `${chalk.green(logPrefix())} ${chalk.red(
386 `Deprecated configuration key 'numberOfStation' usage for template file '${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use 'numberOfStations' instead`
387 )}`
388 )
389 }
390 }
391 )
392 // supervision url(s)
393 Configuration.warnDeprecatedConfigurationKey(
394 'supervisionURLs',
395 undefined,
396 "Use 'supervisionUrls' instead"
397 )
398 // supervision urls distribution
399 Configuration.warnDeprecatedConfigurationKey(
400 'distributeStationToTenantEqually',
401 undefined,
402 "Use 'supervisionUrlDistribution' instead"
403 )
404 Configuration.warnDeprecatedConfigurationKey(
405 'distributeStationsToTenantsEqually',
406 undefined,
407 "Use 'supervisionUrlDistribution' instead"
408 )
409 // worker section
410 Configuration.warnDeprecatedConfigurationKey(
411 'useWorkerPool',
412 undefined,
413 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
414 )
415 Configuration.warnDeprecatedConfigurationKey(
416 'workerProcess',
417 undefined,
418 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
419 )
420 Configuration.warnDeprecatedConfigurationKey(
421 'workerStartDelay',
422 undefined,
423 `Use '${ConfigurationSection.worker}' section to define the worker start delay instead`
424 )
425 Configuration.warnDeprecatedConfigurationKey(
426 'chargingStationsPerWorker',
427 undefined,
428 `Use '${ConfigurationSection.worker}' section to define the number of element(s) per worker instead`
429 )
430 Configuration.warnDeprecatedConfigurationKey(
431 'elementAddDelay',
432 undefined,
433 `Use '${ConfigurationSection.worker}' section to define the worker's element add delay instead`
434 )
435 Configuration.warnDeprecatedConfigurationKey(
436 'workerPoolMinSize',
437 undefined,
438 `Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`
439 )
440 Configuration.warnDeprecatedConfigurationKey(
441 'workerPoolSize',
442 undefined,
443 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
444 )
445 Configuration.warnDeprecatedConfigurationKey(
446 'workerPoolMaxSize',
447 undefined,
448 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
449 )
450 Configuration.warnDeprecatedConfigurationKey(
451 'workerPoolStrategy',
452 undefined,
453 `Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`
454 )
455 Configuration.warnDeprecatedConfigurationKey(
456 'poolStrategy',
457 ConfigurationSection.worker,
458 'Not publicly exposed to end users'
459 )
460 Configuration.warnDeprecatedConfigurationKey(
461 'elementStartDelay',
462 ConfigurationSection.worker,
463 "Use 'elementAddDelay' instead"
464 )
465 if (
466 Configuration.getConfigurationData()?.worker?.processType ===
467 ('staticPool' as WorkerProcessType)
468 ) {
469 console.error(
470 `${chalk.green(logPrefix())} ${chalk.red(
471 `Deprecated configuration 'staticPool' value usage in worker section 'processType' field. Use '${WorkerProcessType.fixedPool}' value instead`
472 )}`
473 )
474 }
475 // log section
476 Configuration.warnDeprecatedConfigurationKey(
477 'logEnabled',
478 undefined,
479 `Use '${ConfigurationSection.log}' section to define the logging enablement instead`
480 )
481 Configuration.warnDeprecatedConfigurationKey(
482 'logFile',
483 undefined,
484 `Use '${ConfigurationSection.log}' section to define the log file instead`
485 )
486 Configuration.warnDeprecatedConfigurationKey(
487 'logErrorFile',
488 undefined,
489 `Use '${ConfigurationSection.log}' section to define the log error file instead`
490 )
491 Configuration.warnDeprecatedConfigurationKey(
492 'logConsole',
493 undefined,
494 `Use '${ConfigurationSection.log}' section to define the console logging enablement instead`
495 )
496 Configuration.warnDeprecatedConfigurationKey(
497 'logStatisticsInterval',
498 undefined,
499 `Use '${ConfigurationSection.log}' section to define the log statistics interval instead`
500 )
501 Configuration.warnDeprecatedConfigurationKey(
502 'logLevel',
503 undefined,
504 `Use '${ConfigurationSection.log}' section to define the log level instead`
505 )
506 Configuration.warnDeprecatedConfigurationKey(
507 'logFormat',
508 undefined,
509 `Use '${ConfigurationSection.log}' section to define the log format instead`
510 )
511 Configuration.warnDeprecatedConfigurationKey(
512 'logRotate',
513 undefined,
514 `Use '${ConfigurationSection.log}' section to define the log rotation enablement instead`
515 )
516 Configuration.warnDeprecatedConfigurationKey(
517 'logMaxFiles',
518 undefined,
519 `Use '${ConfigurationSection.log}' section to define the log maximum files instead`
520 )
521 Configuration.warnDeprecatedConfigurationKey(
522 'logMaxSize',
523 undefined,
524 `Use '${ConfigurationSection.log}' section to define the log maximum size instead`
525 )
526 // performanceStorage section
527 Configuration.warnDeprecatedConfigurationKey(
528 'URI',
529 ConfigurationSection.performanceStorage,
530 "Use 'uri' instead"
531 )
532 // uiServer section
533 if (hasOwnProp(Configuration.getConfigurationData(), 'uiWebSocketServer')) {
534 console.error(
535 `${chalk.green(logPrefix())} ${chalk.red(
536 `Deprecated configuration section 'uiWebSocketServer' usage. Use '${ConfigurationSection.uiServer}' instead`
537 )}`
538 )
539 }
540 }
541
542 private static warnDeprecatedConfigurationKey (
543 key: string,
544 configurationSection?: ConfigurationSection,
545 logMsgToAppend = ''
546 ): void {
547 if (
548 configurationSection != null &&
549 Configuration.getConfigurationData()?.[configurationSection as keyof ConfigurationData] !=
550 null &&
551 (
552 Configuration.getConfigurationData()?.[
553 configurationSection as keyof ConfigurationData
554 ] as Record<string, unknown>
555 )[key] != null
556 ) {
557 console.error(
558 `${chalk.green(logPrefix())} ${chalk.red(
559 `Deprecated configuration key '${key}' usage in section '${configurationSection}'${
560 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
561 }`
562 )}`
563 )
564 } else if (Configuration.getConfigurationData()?.[key as keyof ConfigurationData] != null) {
565 console.error(
566 `${chalk.green(logPrefix())} ${chalk.red(
567 `Deprecated configuration key '${key}' usage${
568 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
569 }`
570 )}`
571 )
572 }
573 }
574
575 public static getConfigurationData (): ConfigurationData | undefined {
576 if (
577 Configuration.configurationData == null &&
578 Configuration.configurationFile != null &&
579 Configuration.configurationFile.length > 0
580 ) {
581 try {
582 Configuration.configurationData = JSON.parse(
583 readFileSync(Configuration.configurationFile, 'utf8')
584 ) as ConfigurationData
585 if (Configuration.configurationFileWatcher == null) {
586 Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher()
587 }
588 } catch (error) {
589 handleFileException(
590 Configuration.configurationFile,
591 FileType.Configuration,
592 error as NodeJS.ErrnoException,
593 logPrefix()
594 )
595 }
596 }
597 return Configuration.configurationData
598 }
599
600 private static getConfigurationFileWatcher (): FSWatcher | undefined {
601 if (Configuration.configurationFile == null || Configuration.configurationFile.length === 0) {
602 return
603 }
604 try {
605 return watch(Configuration.configurationFile, (event, filename): void => {
606 if (
607 !Configuration.configurationFileReloading &&
608 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
609 filename!.trim().length > 0 &&
610 event === 'change'
611 ) {
612 Configuration.configurationFileReloading = true
613 const consoleWarnOnce = once(console.warn)
614 consoleWarnOnce(
615 `${chalk.green(logPrefix())} ${chalk.yellow(
616 `${FileType.Configuration} ${this.configurationFile} file have changed, reload`
617 )}`
618 )
619 delete Configuration.configurationData
620 Configuration.configurationSectionCache.clear()
621 if (Configuration.configurationChangeCallback != null) {
622 Configuration.configurationChangeCallback()
623 .catch((error: unknown) => {
624 throw typeof error === 'string' ? new Error(error) : error
625 })
626 .finally(() => {
627 Configuration.configurationFileReloading = false
628 })
629 } else {
630 Configuration.configurationFileReloading = false
631 }
632 }
633 })
634 } catch (error) {
635 handleFileException(
636 Configuration.configurationFile,
637 FileType.Configuration,
638 error as NodeJS.ErrnoException,
639 logPrefix()
640 )
641 }
642 }
643 }