generated from polymech/site-template
109 lines
4.1 KiB
TypeScript
109 lines
4.1 KiB
TypeScript
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
import yargs from 'yargs';
|
|
import { hideBin } from 'yargs/helpers';
|
|
|
|
import { substitute } from "@polymech/commons/variables";
|
|
import { appConfigSchema } from "./config.schema.js";
|
|
import type { AppConfig } from "./config.schema.js";
|
|
|
|
const I18N_SOURCE_LANGUAGE = 'en';
|
|
|
|
const LIBRARY_CONFIG_PATH = path.resolve("./app-config.json");
|
|
const USER_CONFIG_DEFAULT_PATH = path.resolve("./app-config.local.json");
|
|
|
|
function deepMerge(target: any, source: any): any {
|
|
if (typeof source !== 'object' || source === null) {
|
|
return source; // Primitives or null overwrite
|
|
}
|
|
if (Array.isArray(source)) {
|
|
return source; // Arrays overwrite
|
|
}
|
|
if (typeof target !== 'object' || target === null || Array.isArray(target)) {
|
|
return source; // Target is not mergeable object, overwrite
|
|
}
|
|
|
|
const result = { ...target };
|
|
for (const key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
const val = source[key];
|
|
if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
|
|
result[key] = deepMerge(result[key], val);
|
|
} else {
|
|
result[key] = val;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function loadConfig(
|
|
locale: string = I18N_SOURCE_LANGUAGE,
|
|
libraryPath: string = LIBRARY_CONFIG_PATH
|
|
): AppConfig {
|
|
// 1. Load Library Config (Defaults)
|
|
let rawLibraryContent: string;
|
|
try {
|
|
rawLibraryContent = fs.readFileSync(libraryPath, 'utf-8');
|
|
} catch (error) {
|
|
throw new Error(`Failed to read library config file at ${libraryPath}: ${error}`);
|
|
}
|
|
|
|
const variables = {
|
|
LANG: locale
|
|
};
|
|
|
|
const substitutedLibraryContent = substitute(false, rawLibraryContent, variables);
|
|
let libraryConfig: any;
|
|
try {
|
|
libraryConfig = JSON.parse(substitutedLibraryContent);
|
|
} catch (error) {
|
|
throw new Error(`Failed to parse library config JSON: ${error}`);
|
|
}
|
|
|
|
// 2. Parse CLI Arguments
|
|
// We assume the caller might want to pass args, or we just grab process.argv
|
|
// We cast to any because yargs returns a complex type
|
|
const argv: any = yargs(hideBin(process.argv)).parseSync ? yargs(hideBin(process.argv)).parseSync() : (yargs(hideBin(process.argv)) as any).argv;
|
|
|
|
// 3. Determine User Config Path
|
|
// Check for --config <path>
|
|
const userConfigPath = argv.config ? path.resolve(argv.config) : USER_CONFIG_DEFAULT_PATH;
|
|
|
|
// 4. Load User Config (if exists)
|
|
let userConfig: any = {};
|
|
if (fs.existsSync(userConfigPath)) {
|
|
try {
|
|
const rawUserContent = fs.readFileSync(userConfigPath, 'utf-8');
|
|
const substitutedUserContent = substitute(false, rawUserContent, variables);
|
|
userConfig = JSON.parse(substitutedUserContent);
|
|
} catch (error) {
|
|
console.warn(`Failed to load or parse user config at ${userConfigPath}: ${error}`);
|
|
}
|
|
}
|
|
|
|
// 5. Merge: Library <- User <- CLI
|
|
// Note: yargs parses --config as part of argv, but also other flags like --core.logging_namespace
|
|
// We filter out specific known CLI-only flags if needed, but config schema validation will drop unknown keys anyway?
|
|
// Actually zod 'strip' is default in safeParse? No, usually it passes through unless strict().
|
|
// We should probably rely on valid keys overwriting.
|
|
|
|
// CLI args often come with standard keys like '$0', '_' which we might want to exclude if we blindly merge.
|
|
// However, deepMerge will add them.
|
|
// Ideally we would only merge keys that exist in the schema, but dynamic is fine for now.
|
|
|
|
let mergedConfig = deepMerge(libraryConfig, userConfig);
|
|
mergedConfig = deepMerge(mergedConfig, argv);
|
|
|
|
// 6. Validate
|
|
const result = appConfigSchema.safeParse(mergedConfig);
|
|
|
|
if (!result.success) {
|
|
// Pretty print error if possible or just message
|
|
throw new Error(`Config validation failed: ${result.error.message}`);
|
|
}
|
|
|
|
return result.data;
|
|
}
|