import { type FSWatcher, readFileSync, watch } from 'node:fs';
-import { dirname, join, resolve } from 'node:path';
+import { dirname, join } from 'node:path';
+import { env } from 'node:process';
import { fileURLToPath } from 'node:url';
import chalk from 'chalk';
import merge from 'just-merge';
+import {
+ buildPerformanceUriFilePath,
+ checkWorkerElementsPerWorker,
+ checkWorkerProcessType,
+ getDefaultPerformanceStorageUri,
+ handleFileException,
+ logPrefix,
+} from './ConfigurationUtils';
import { Constants } from './Constants';
-import { hasOwnProp, isCFEnvironment, isNotEmptyString, isUndefined, once } from './Utils';
+import { hasOwnProp, isCFEnvironment, isUndefined, once } from './Utils';
import {
ApplicationProtocol,
type ConfigurationData,
| UIServerConfiguration;
export class Configuration {
+ public static configurationChangeCallback: () => Promise<void>;
+
private static configurationFile = join(
dirname(fileURLToPath(import.meta.url)),
'assets',
'config.json',
);
+ private static configurationFileReloading = false;
private static configurationData?: ConfigurationData;
private static configurationFileWatcher?: FSWatcher;
private static configurationSectionCache = new Map<
[ConfigurationSection.uiServer, Configuration.buildUIServerSection()],
]);
- private static warnDeprecatedConfigurationKeys = false;
-
- private static configurationChangeCallback?: () => Promise<void>;
-
private constructor() {
// This is intentional
}
- public static setConfigurationChangeCallback(cb: () => Promise<void>): void {
- Configuration.configurationChangeCallback = cb;
- }
-
public static getConfigurationSection<T extends ConfigurationSectionType>(
sectionName: ConfigurationSection,
): T {
}
public static workerPoolInUse(): boolean {
- return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes(
+ return [WorkerProcessType.dynamicPool, WorkerProcessType.fixedPool].includes(
Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
.processType!,
);
}
if (isCFEnvironment() === true) {
delete uiServerConfiguration.options?.host;
- uiServerConfiguration.options!.port = parseInt(process.env.PORT!);
+ uiServerConfiguration.options!.port = parseInt(env.PORT!);
}
return uiServerConfiguration;
}
let storageConfiguration: StorageConfiguration = {
enabled: false,
type: StorageType.JSON_FILE,
- uri: Configuration.getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
+ uri: getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
};
if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.performanceStorage)) {
storageConfiguration = {
...(Configuration.getConfigurationData()?.performanceStorage?.type ===
StorageType.JSON_FILE &&
Configuration.getConfigurationData()?.performanceStorage?.uri && {
- uri: Configuration.buildPerformanceUriFilePath(
+ uri: buildPerformanceUriFilePath(
new URL(Configuration.getConfigurationData()!.performanceStorage!.uri!).pathname,
),
}),
...(hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.worker) &&
Configuration.getConfigurationData()?.worker),
};
- if (!Object.values(WorkerProcessType).includes(workerConfiguration.processType!)) {
- throw new SyntaxError(
- `Invalid worker process type '${workerConfiguration.processType}' defined in configuration`,
- );
- }
+ checkWorkerProcessType(workerConfiguration.processType!);
+ checkWorkerElementsPerWorker(workerConfiguration.elementsPerWorker);
return workerConfiguration;
}
- private static logPrefix = (): string => {
- return `${new Date().toLocaleString()} Simulator configuration |`;
- };
-
private static checkDeprecatedConfigurationKeys() {
- if (Configuration.warnDeprecatedConfigurationKeys) {
- return;
- }
// connection timeout
Configuration.warnDeprecatedConfigurationKey(
'autoReconnectTimeout',
(stationTemplateUrl: StationTemplateUrl) => {
if (!isUndefined(stationTemplateUrl?.['numberOfStation' as keyof StationTemplateUrl])) {
console.error(
- `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+ `${chalk.green(logPrefix())} ${chalk.red(
`Deprecated configuration key 'numberOfStation' usage for template file '${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use 'numberOfStations' instead`,
)}`,
);
`Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`,
);
Configuration.warnDeprecatedConfigurationKey(
- 'workerPoolSize;',
+ 'workerPoolSize',
undefined,
`Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`,
);
Configuration.warnDeprecatedConfigurationKey(
- 'workerPoolMaxSize;',
+ 'workerPoolMaxSize',
undefined,
`Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`,
);
Configuration.warnDeprecatedConfigurationKey(
- 'workerPoolStrategy;',
+ 'workerPoolStrategy',
undefined,
`Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`,
);
ConfigurationSection.worker,
'Not publicly exposed to end users',
);
+ if (
+ Configuration.getConfigurationData()?.worker?.processType ===
+ ('staticPool' as WorkerProcessType)
+ ) {
+ console.error(
+ `${chalk.green(logPrefix())} ${chalk.red(
+ `Deprecated configuration 'staticPool' value usage in worker section 'processType' field. Use '${WorkerProcessType.fixedPool}' value instead`,
+ )}`,
+ );
+ }
// log section
Configuration.warnDeprecatedConfigurationKey(
'logEnabled',
// uiServer section
if (hasOwnProp(Configuration.getConfigurationData(), 'uiWebSocketServer')) {
console.error(
- `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+ `${chalk.green(logPrefix())} ${chalk.red(
`Deprecated configuration section 'uiWebSocketServer' usage. Use '${ConfigurationSection.uiServer}' instead`,
)}`,
);
}
- Configuration.warnDeprecatedConfigurationKeys = true;
}
private static warnDeprecatedConfigurationKey(
)
) {
console.error(
- `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+ `${chalk.green(logPrefix())} ${chalk.red(
`Deprecated configuration key '${key}' usage in section '${sectionName}'${
logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
}`,
!isUndefined(Configuration.getConfigurationData()?.[key as keyof ConfigurationData])
) {
console.error(
- `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+ `${chalk.green(logPrefix())} ${chalk.red(
`Deprecated configuration key '${key}' usage${
logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
}`,
Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher();
}
} catch (error) {
- Configuration.handleFileException(
+ handleFileException(
Configuration.configurationFile,
FileType.Configuration,
error as NodeJS.ErrnoException,
- Configuration.logPrefix(),
+ logPrefix(),
);
}
}
private static getConfigurationFileWatcher(): FSWatcher | undefined {
try {
return watch(Configuration.configurationFile, (event, filename): void => {
- if (filename!.trim()!.length > 0 && event === 'change') {
+ if (
+ !Configuration.configurationFileReloading &&
+ filename!.trim()!.length > 0 &&
+ event === 'change'
+ ) {
+ Configuration.configurationFileReloading = true;
+ const consoleWarnOnce = once(console.warn, this);
+ consoleWarnOnce(
+ `${chalk.green(logPrefix())} ${chalk.yellow(
+ `${FileType.Configuration} ${this.configurationFile} file have changed, reload`,
+ )}`,
+ );
delete Configuration.configurationData;
Configuration.configurationSectionCache.clear();
if (!isUndefined(Configuration.configurationChangeCallback)) {
- Configuration.configurationChangeCallback!().catch((error) => {
- throw typeof error === 'string' ? new Error(error) : error;
- });
+ Configuration.configurationChangeCallback()
+ .catch((error) => {
+ throw typeof error === 'string' ? new Error(error) : error;
+ })
+ .finally(() => {
+ Configuration.configurationFileReloading = false;
+ });
+ } else {
+ Configuration.configurationFileReloading = false;
}
}
});
} catch (error) {
- Configuration.handleFileException(
+ handleFileException(
Configuration.configurationFile,
FileType.Configuration,
error as NodeJS.ErrnoException,
- Configuration.logPrefix(),
+ logPrefix(),
);
}
}
-
- private static handleFileException(
- file: string,
- fileType: FileType,
- error: NodeJS.ErrnoException,
- logPrefix: string,
- ): void {
- const prefix = isNotEmptyString(logPrefix) ? `${logPrefix} ` : '';
- let logMsg: string;
- switch (error.code) {
- case 'ENOENT':
- logMsg = `${fileType} file ${file} not found:`;
- break;
- case 'EEXIST':
- logMsg = `${fileType} file ${file} already exists:`;
- break;
- case 'EACCES':
- logMsg = `${fileType} file ${file} access denied:`;
- break;
- case 'EPERM':
- logMsg = `${fileType} file ${file} permission denied:`;
- break;
- default:
- logMsg = `${fileType} file ${file} error:`;
- }
- console.error(`${chalk.green(prefix)}${chalk.red(`${logMsg} `)}`, error);
- throw error;
- }
-
- private static getDefaultPerformanceStorageUri(storageType: StorageType) {
- switch (storageType) {
- case StorageType.JSON_FILE:
- return Configuration.buildPerformanceUriFilePath(
- `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME}`,
- );
- case StorageType.SQLITE:
- return Configuration.buildPerformanceUriFilePath(
- `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`,
- );
- default:
- throw new Error(`Unsupported storage type '${storageType}'`);
- }
- }
-
- private static buildPerformanceUriFilePath(file: string) {
- return `file://${join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), file)}`;
- }
}