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