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