108 lines
2.7 KiB
JavaScript
108 lines
2.7 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ENV_FILE_CANDIDATES = [
|
|
path.join(process.cwd(), '.env'),
|
|
path.join(process.cwd(), 'backend', '.env'),
|
|
];
|
|
|
|
function applyEnvFile(filePath, env = process.env) {
|
|
const rawFile = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/u, '');
|
|
const lines = rawFile.split(/\r?\n/u);
|
|
|
|
for (const line of lines) {
|
|
const trimmedLine = line.trim();
|
|
if (!trimmedLine || trimmedLine.startsWith('#')) {
|
|
continue;
|
|
}
|
|
|
|
const withoutExport = trimmedLine.startsWith('export ')
|
|
? trimmedLine.slice('export '.length).trim()
|
|
: trimmedLine;
|
|
const separatorIndex = withoutExport.indexOf('=');
|
|
if (separatorIndex <= 0) {
|
|
continue;
|
|
}
|
|
|
|
const key = withoutExport.slice(0, separatorIndex).trim();
|
|
let value = withoutExport.slice(separatorIndex + 1).trim();
|
|
|
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key) || key in env) {
|
|
continue;
|
|
}
|
|
|
|
if (value.startsWith('"') && value.endsWith('"')) {
|
|
value = value
|
|
.slice(1, -1)
|
|
.replace(/\\n/gu, '\n')
|
|
.replace(/\\r/gu, '\r')
|
|
.replace(/\\t/gu, '\t')
|
|
.replace(/\\"/gu, '"')
|
|
.replace(/\\\\/gu, '\\');
|
|
} else if (value.startsWith("'") && value.endsWith("'")) {
|
|
value = value.slice(1, -1);
|
|
} else {
|
|
value = value.replace(/\s+#.*$/u, '').trim();
|
|
}
|
|
|
|
env[key] = value;
|
|
}
|
|
}
|
|
|
|
function loadEnvFromFileCandidates(envFileCandidates = ENV_FILE_CANDIDATES, env = process.env) {
|
|
for (const envFilePath of envFileCandidates) {
|
|
if (!fs.existsSync(envFilePath)) {
|
|
continue;
|
|
}
|
|
|
|
applyEnvFile(envFilePath, env);
|
|
return envFilePath;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function parsePositiveIntEnv(names, fallback, env = process.env) {
|
|
const candidates = Array.isArray(names) ? names : [names];
|
|
|
|
for (const name of candidates) {
|
|
const rawValue = env[name];
|
|
if (rawValue === undefined || rawValue === null || rawValue.trim().length === 0) {
|
|
continue;
|
|
}
|
|
|
|
const parsed = Number.parseInt(rawValue, 10);
|
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
throw new Error(`${name} must be a positive integer. Received "${rawValue}".`);
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
return fallback;
|
|
}
|
|
|
|
function parseTimeZoneEnv(name, fallback, env = process.env) {
|
|
const rawValue = env[name];
|
|
const value =
|
|
rawValue === undefined || rawValue === null || rawValue.trim().length === 0
|
|
? fallback
|
|
: rawValue.trim();
|
|
|
|
try {
|
|
new Intl.DateTimeFormat('en-GB', {
|
|
timeZone: value,
|
|
});
|
|
} catch {
|
|
throw new Error(`${name} must be a valid IANA timezone. Received "${rawValue}".`);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
module.exports = {
|
|
loadEnvFromFileCandidates,
|
|
parsePositiveIntEnv,
|
|
parseTimeZoneEnv,
|
|
};
|