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