UNPKG

open-collaboration-server

Version:

Open Collaboration Server implementation, part of the Open Collaboration Tools project

150 lines (126 loc) 5.28 kB
// ****************************************************************************** // Copyright 2024 TypeFox GmbH // This program and the accompanying materials are made available under the // terms of the MIT License, which is available in the project root. // ****************************************************************************** import * as fs from 'node:fs'; import * as path from 'path'; import { inject, injectable, optional, postConstruct } from 'inversify'; import { parse as parseYaml } from 'yaml'; export interface Configuration { /** * Get a string value for the given configuration key. * The key must be provided in kebab-case. */ getValue(key: string, type?: 'string'): string | undefined; /** * Get a number value for the given configuration key. * The key must be provided in kebab-case. */ getValue(key: string, type: 'number'): number | undefined; /** * Get a boolean value for the given configuration key. * The key must be provided in kebab-case. */ getValue(key: string, type: 'boolean'): boolean | undefined; } export const Configuration = Symbol('Configuration'); export const ConfigurationFile = Symbol('ConfigurationFile'); export type ConfigurationValue = string | number | boolean; @injectable() export class DefaultConfiguration implements Configuration { @inject(ConfigurationFile)@optional() protected configurationFile: string | undefined; protected configuration: Record<string, unknown> = {}; @postConstruct() protected initialize() { if (this.configurationFile) { const ext = path.extname(this.configurationFile).toLowerCase(); const configContent = fs.readFileSync(this.configurationFile, 'utf8'); let config: unknown; if (ext === '.yml' || ext === '.yaml') { config = parseYaml(configContent); } else { config = JSON.parse(configContent); } if (typeof config === 'object' && config !== null) { this.configuration = config as Record<string, unknown>; } } } getValue(key: string, type?: 'string'): string | undefined; getValue(key: string, type: 'number'): number | undefined; getValue(key: string, type: 'boolean'): boolean | undefined; getValue(key: string, type?: 'string' | 'number' | 'boolean'): ConfigurationValue | undefined { let value: ConfigurationValue | undefined = this.getFromEnv(key); if (!value) { value = this.getFromConfig(key); } return this.convertToType(value, type); } protected convertToType(value: ConfigurationValue | undefined, type?: 'string' | 'number' | 'boolean'): ConfigurationValue | undefined { if (value === undefined) { return undefined; } if (type === 'string' || type === undefined) { return String(value); } else if (type === 'number') { if (typeof value === 'string') { const parsed = parseInt(value, 10); if (!isNaN(parsed)) { return parsed; } } else if (typeof value === 'number') { return value; } } else if (type === 'boolean') { if (typeof value === 'string') { const lower = value.toLowerCase(); switch (lower) { case 'true': return true; case 'false': return false; } } else if (typeof value === 'boolean') { return value; } } return undefined; } protected getFromEnv(key: string): string | undefined { return process.env[toEnvKey(key)]; } protected getFromConfig(key: string): ConfigurationValue | undefined { return this.getFromConfigObject(this.configuration, toObjKey(key)); } private getFromConfigObject(object: Record<string, unknown>, key: string): ConfigurationValue | undefined { let value = object[key]; if (isConfigValue(value)) { return value; } const segments = key.split('.'); for (let i = segments.length - 1; i >= 1; i--) { const key = segments.slice(0, i).join('.'); value = object[key]; if (typeof value === 'object' && value !== null) { return this.getFromConfigObject(value as Record<string, unknown>, segments.slice(i).join('.')); } } return undefined; } } /** * Converts a kebab-case key to a SNAKE_CASE key. */ function toEnvKey(key: string): string { return key.replace(/-/g, '_').toUpperCase(); } /** * Converts a kebab-case key to a dot.separated.key. */ function toObjKey(key: string): string { return key.replace(/-/g, '.'); } function isConfigValue(value: unknown): value is ConfigurationValue { return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'; }