114 lines
3.3 KiB
JavaScript
114 lines
3.3 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const { CONFIG } = require('../config');
|
|
const { getValidator } = require('./configSchema');
|
|
|
|
const ENV_PATH = process.env.ENV_FILE
|
|
? path.resolve(process.env.ENV_FILE)
|
|
: path.resolve(process.cwd(), '.env');
|
|
|
|
/**
|
|
* Read the current .env file and parse into a key->value Map.
|
|
*/
|
|
function readEnvFile() {
|
|
if (!fs.existsSync(ENV_PATH)) return new Map();
|
|
const lines = fs.readFileSync(ENV_PATH, 'utf8').split('\n');
|
|
const map = new Map();
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
const idx = line.indexOf('=');
|
|
if (idx === -1) continue;
|
|
const key = line.slice(0, idx).trim();
|
|
const value = line.slice(idx + 1).trim();
|
|
map.set(key, value);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
* Write a Map of key->value back to the .env file,
|
|
* preserving comments and blank lines.
|
|
*/
|
|
function writeEnvFile(updates) {
|
|
if (!fs.existsSync(ENV_PATH)) {
|
|
const lines = [];
|
|
for (const [k, v] of updates) lines.push(`${k}=${v}`);
|
|
fs.writeFileSync(ENV_PATH, lines.join('\n') + '\n', 'utf8');
|
|
return;
|
|
}
|
|
|
|
const raw = fs.readFileSync(ENV_PATH, 'utf8');
|
|
const lines = raw.split('\n');
|
|
const written = new Set();
|
|
|
|
const result = lines.map(line => {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) return line;
|
|
const idx = line.indexOf('=');
|
|
if (idx === -1) return line;
|
|
const key = line.slice(0, idx).trim();
|
|
if (updates.has(key)) {
|
|
written.add(key);
|
|
return `${key}=${updates.get(key)}`;
|
|
}
|
|
return line;
|
|
});
|
|
|
|
// Append any new keys not already in the file
|
|
for (const [k, v] of updates) {
|
|
if (!written.has(k)) result.push(`${k}=${v}`);
|
|
}
|
|
|
|
fs.writeFileSync(ENV_PATH, result.join('\n'), 'utf8');
|
|
}
|
|
|
|
/**
|
|
* Apply a flat object of { KEY: value } to both CONFIG and .env.
|
|
* Returns { applied: string[], errors: Array<{ key, error }> }.
|
|
*
|
|
* Every value is routed through services/configSchema so invalid inputs are
|
|
* rejected before being persisted. Valid values are typed via the validator's
|
|
* coerced return (boolean/number/string) into CONFIG; .env always stores
|
|
* String(coerced). Invalid values are reported and NEITHER written to .env NOR
|
|
* applied to CONFIG — callers see them in the `errors` array.
|
|
*/
|
|
function applyConfigUpdates(updates) {
|
|
const applied = [];
|
|
const errors = [];
|
|
const coercedForEnv = new Map();
|
|
|
|
for (const [key, rawValue] of Object.entries(updates)) {
|
|
const validator = getValidator(key);
|
|
const result = validator.validate(rawValue);
|
|
if (!result.ok) {
|
|
errors.push({ key, error: result.error });
|
|
continue;
|
|
}
|
|
CONFIG[key] = result.coerced;
|
|
coercedForEnv.set(key, String(result.coerced));
|
|
applied.push(key);
|
|
}
|
|
|
|
// Write only the valid entries to .env. Skip the disk write entirely when
|
|
// nothing validated, so a fully-rejected save doesn't rewrite the file.
|
|
if (applied.length > 0) {
|
|
const envMap = readEnvFile();
|
|
for (const [key, value] of coercedForEnv) {
|
|
envMap.set(key, value);
|
|
}
|
|
writeEnvFile(envMap);
|
|
}
|
|
|
|
return { applied, errors };
|
|
}
|
|
|
|
/**
|
|
* Read all current env values for the settings UI.
|
|
*/
|
|
function readAllConfig() {
|
|
return readEnvFile();
|
|
}
|
|
|
|
module.exports = { applyConfigUpdates, readAllConfig, readEnvFile, writeEnvFile };
|