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