UNPKG

@citrineos/base

Version:

The base module for OCPP v2.0.1 including all interfaces. This module is not intended to be used directly, but rather as a dependency for other modules.

126 lines 5.31 kB
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project // // SPDX-License-Identifier: Apache-2.0 import { z } from 'zod'; import { systemConfigSchema } from './types.js'; const args = typeof process !== 'undefined' && process.argv ? process.argv.slice(2) : []; let dynamicPrefix = 'citrineos_'; for (const arg of args) { if (arg.startsWith('--env-prefix=')) { dynamicPrefix = arg.split('=')[1].toLowerCase(); break; } } export const CITRINE_ENV_VAR_PREFIX = dynamicPrefix; export const BOOTSTRAP_CONFIG_ENV_VAR_PREFIX = `bootstrap_${CITRINE_ENV_VAR_PREFIX}`; /** * Finds a case-insensitive match for a key in an object. * @param obj The object to search. * @param targetKey The target key. * @returns The matching key or undefined. */ function findCaseInsensitiveMatch(obj, targetKey) { const lowerTargetKey = targetKey.toLowerCase(); return Object.keys(obj).find((key) => key.toLowerCase() === lowerTargetKey); } const getZodSchemaKeyMap = (schema) => { if (schema instanceof z.ZodNullable || schema instanceof z.ZodOptional) { return getZodSchemaKeyMap(schema.unwrap()); } if (schema instanceof z.ZodArray) { return getZodSchemaKeyMap(schema.element); } if (schema instanceof z.ZodObject) { const entries = Object.entries(schema.shape); return entries.reduce((acc, [key, value]) => { const nested = getZodSchemaKeyMap(value); if (Object.keys(nested).length > 0) { acc[key] = nested; } else { acc[key.toLowerCase()] = key; } return acc; }, {}); } return {}; }; /** * Merges configuration from environment variables into the default configuration. Allows any to keep it as generic as possible. * @param defaultConfig The default configuration. * @param envVars The environment variables. * @returns The merged configuration. */ function mergeConfigFromEnvVars(defaultConfig, envVars, configKeyMap) { const config = { ...defaultConfig }; const errors = []; for (const [fullEnvKey, value] of Object.entries(envVars)) { if (!value) { continue; } const lowercaseEnvKey = fullEnvKey.toLowerCase(); if (lowercaseEnvKey.startsWith(CITRINE_ENV_VAR_PREFIX)) { const envKeyWithoutPrefix = lowercaseEnvKey.substring(CITRINE_ENV_VAR_PREFIX.length); const path = envKeyWithoutPrefix.split('_'); let currentConfigPart = config; let currentConfigKeyMap = configKeyMap; let validMapping = true; for (let i = 0; i < path.length - 1; i++) { const part = path[i]; const matchingKey = findCaseInsensitiveMatch(currentConfigKeyMap, part); if (!matchingKey) { errors.push(`Environment variable '${fullEnvKey}' refers to unknown configuration segment '${part}'.`); validMapping = false; break; } if (currentConfigPart[matchingKey] === undefined) { currentConfigPart[matchingKey] = {}; } else if (typeof currentConfigPart[matchingKey] !== 'object' || currentConfigPart[matchingKey] === null) { errors.push(`Environment variable '${fullEnvKey}' refers to configuration segment '${part}', but its current value is not an object.`); validMapping = false; break; } currentConfigPart = currentConfigPart[matchingKey]; currentConfigKeyMap = currentConfigKeyMap[matchingKey]; } if (!validMapping) { continue; } const finalPart = path[path.length - 1]; const keyToUse = currentConfigKeyMap[finalPart.toLowerCase()] || finalPart; try { currentConfigPart[keyToUse] = JSON.parse(value); } catch { console.debug(`Mapping '${value}' as string for environment variable '${fullEnvKey}'.`); currentConfigPart[keyToUse] = value; } } } errors.forEach((err) => console.error(err)); return config; } /** * Validates the system configuration to ensure required properties are set. * @param finalConfig The final system configuration. * @throws Error if required properties are not set. */ function validateFinalConfig(finalConfig) { return systemConfigSchema.parse(finalConfig); } /** * Defines the application configuration by merging input configuration which is defined in a file with environment variables. * Takes environment variables over predefined * @param inputConfig The file defined input configuration. * @returns The final system configuration. * @throws Error if required environment variables are not set or if there are parsing errors. */ export function defineConfig(inputConfig) { const configKeyMap = getZodSchemaKeyMap(systemConfigSchema); const appConfig = mergeConfigFromEnvVars(inputConfig, process.env, configKeyMap); return validateFinalConfig(appConfig); } export const DEFAULT_TENANT_ID = 1; //# sourceMappingURL=defineConfig.js.map