refactor progress
This commit is contained in:
41
packages/config/src/config_loader/ConfigObjectMerge.tsx
Normal file
41
packages/config/src/config_loader/ConfigObjectMerge.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export type ConfigObject = Record<string, unknown>;
|
||||
|
||||
export function isPlainObject(value: unknown): value is ConfigObject {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export function deepMerge(target: ConfigObject, source: ConfigObject): ConfigObject {
|
||||
const result = {...target};
|
||||
|
||||
for (const key in source) {
|
||||
const sourceValue = source[key];
|
||||
const targetValue = result[key];
|
||||
|
||||
if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
|
||||
result[key] = deepMerge(targetValue, sourceValue);
|
||||
} else {
|
||||
result[key] = sourceValue;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
82
packages/config/src/config_loader/EnvironmentOverrides.tsx
Normal file
82
packages/config/src/config_loader/EnvironmentOverrides.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {type ConfigObject, isPlainObject} from '@fluxer/config/src/config_loader/ConfigObjectMerge';
|
||||
|
||||
function toChildObject(value: unknown): ConfigObject {
|
||||
if (isPlainObject(value)) {
|
||||
return value;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export function parseEnvValue(raw: string): unknown {
|
||||
const trimmed = raw.trim();
|
||||
if (trimmed === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (trimmed === 'false') {
|
||||
return false;
|
||||
}
|
||||
if (/^-?\d+$/.test(trimmed)) {
|
||||
return Number.parseInt(trimmed, 10);
|
||||
}
|
||||
if (/^-?\d+\.\d+$/.test(trimmed)) {
|
||||
return Number.parseFloat(trimmed);
|
||||
}
|
||||
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
||||
try {
|
||||
return JSON.parse(trimmed);
|
||||
} catch {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
export function setNestedValue(target: ConfigObject, keys: Array<string>, value: unknown): void {
|
||||
if (keys.length === 0) {
|
||||
return;
|
||||
}
|
||||
const [first, ...rest] = keys;
|
||||
if (rest.length === 0) {
|
||||
target[first] = value;
|
||||
return;
|
||||
}
|
||||
if (!isPlainObject(target[first])) {
|
||||
target[first] = {};
|
||||
}
|
||||
setNestedValue(toChildObject(target[first]), rest, value);
|
||||
}
|
||||
|
||||
export function buildEnvOverrides(env: NodeJS.ProcessEnv, prefix: string): ConfigObject {
|
||||
const overrides: ConfigObject = {};
|
||||
for (const [envKey, envValue] of Object.entries(env)) {
|
||||
if (!envKey.startsWith(prefix) || envValue === undefined) {
|
||||
continue;
|
||||
}
|
||||
const remainder = envKey.slice(prefix.length);
|
||||
if (remainder === '') {
|
||||
continue;
|
||||
}
|
||||
const path = remainder.split('__').map((segment) => segment.toLowerCase());
|
||||
setNestedValue(overrides, path, parseEnvValue(envValue));
|
||||
}
|
||||
return overrides;
|
||||
}
|
||||
35
packages/config/src/config_loader/JsonConfigReader.tsx
Normal file
35
packages/config/src/config_loader/JsonConfigReader.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {existsSync, readFileSync} from 'node:fs';
|
||||
import {type ConfigObject, isPlainObject} from '@fluxer/config/src/config_loader/ConfigObjectMerge';
|
||||
import {assertValidJsonConfig} from '@fluxer/config/src/JsonValidation';
|
||||
|
||||
export function loadJsonFile(path: string): ConfigObject {
|
||||
if (!existsSync(path)) {
|
||||
throw new Error(`Config file not found: ${path}`);
|
||||
}
|
||||
const content = readFileSync(path, 'utf-8');
|
||||
const parsed = JSON.parse(content);
|
||||
if (!isPlainObject(parsed)) {
|
||||
throw new Error(`Invalid JSON: expected object at root`);
|
||||
}
|
||||
assertValidJsonConfig(parsed);
|
||||
return parsed;
|
||||
}
|
||||
Reference in New Issue
Block a user