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