openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
129 lines (128 loc) • 5.43 kB
JavaScript
import { o as resolveRequiredHomeDir } from "./home-dir-BPhrG-aM.js";
import { d as resolveConfigDir } from "./utils-P__uGsPB.js";
import { i as readRegularFileSync } from "./regular-file-Equ4rrfE.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { a as normalizeEnvVarKey } from "./host-env-security-BAvlDaJf.js";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import { parse as parse$1 } from "dotenv";
//#region src/infra/dotenv-global.ts
const logger = createSubsystemLogger("infra:dotenv");
/** Maximum bytes to read from any dotenv file. */
const MAX_DOTENV_FILE_BYTES = 1048576;
function readDotEnvFile(params) {
let content;
try {
const resolved = fs.realpathSync(params.filePath);
const { buffer } = readRegularFileSync({
filePath: resolved,
maxBytes: MAX_DOTENV_FILE_BYTES
});
content = buffer;
} catch (error) {
if (!params.quiet) {
if ((error && typeof error === "object" && "code" in error ? String(error.code) : void 0) !== "ENOENT") logger.warn(`Failed to read ${params.filePath}: ${String(error)}`, { error });
if (error instanceof Error && error.message?.startsWith("File exceeds")) logger.warn(`skipping oversized .env file (max ${MAX_DOTENV_FILE_BYTES} bytes): ${params.filePath}`);
}
return null;
}
const entries = [];
for (const [rawKey, value] of Object.entries(parse$1(content))) {
const key = normalizeEnvVarKey(rawKey, { portable: true });
if (key && (params.entryFilter?.(key, value) ?? true)) entries.push({
key,
value
});
}
return {
filePath: params.filePath,
entries
};
}
function loadParsedDotEnvFiles(files, overrideKeys) {
const preExistingKeys = new Set(Object.keys(process.env));
const canonicalizeKey = (key) => normalizeEnvVarKey(key, { portable: true })?.toUpperCase() ?? null;
const normalizedOverrideKeys = new Set([...overrideKeys ?? []].flatMap((key) => {
const normalized = canonicalizeKey(key);
return normalized ? [normalized] : [];
}));
const conflicts = /* @__PURE__ */ new Map();
const firstSeen = /* @__PURE__ */ new Map();
const appliedKeysByFile = /* @__PURE__ */ new Map();
for (const file of files) for (const { key, value } of file.entries) {
const canonicalKey = canonicalizeKey(key);
const mayOverride = canonicalKey !== null && normalizedOverrideKeys.has(canonicalKey);
const precedenceKey = mayOverride && canonicalKey ? canonicalKey : key;
if (preExistingKeys.has(key) && !mayOverride) continue;
const previous = firstSeen.get(precedenceKey);
if (previous) {
if (previous.value !== value) {
const conflictKey = `${previous.filePath}\u0000${file.filePath}`;
const existing = conflicts.get(conflictKey);
if (existing) existing.keys.add(key);
else conflicts.set(conflictKey, {
keptPath: previous.filePath,
ignoredPath: file.filePath,
keys: /* @__PURE__ */ new Set([key])
});
}
continue;
}
firstSeen.set(precedenceKey, {
value,
filePath: file.filePath
});
if (process.env[key] === void 0 || mayOverride) {
if (mayOverride) {
for (const inheritedKey of preExistingKeys) if (canonicalizeKey(inheritedKey) === canonicalKey) process.env[inheritedKey] = value;
}
process.env[key] = value;
const appliedKeys = appliedKeysByFile.get(file.filePath);
if (appliedKeys) appliedKeys.push(key);
else appliedKeysByFile.set(file.filePath, [key]);
}
}
for (const conflict of conflicts.values()) {
const keys = [...conflict.keys].toSorted();
if (keys.length === 0) continue;
logger.warn(`Conflicting values in ${conflict.keptPath} and ${conflict.ignoredPath} for ${keys.join(", ")}; keeping ${conflict.keptPath}.`, {
keptPath: conflict.keptPath,
ignoredPath: conflict.ignoredPath,
keys
});
}
return appliedKeysByFile;
}
/** Load global runtime dotenv files into `process.env` with first-wins precedence. */
function loadGlobalRuntimeDotEnvFiles(opts) {
const quiet = opts?.quiet ?? true;
const stateEnvPath = opts?.stateEnvPath ?? path.join(resolveConfigDir(process.env), ".env");
const globalEnvPaths = [.../* @__PURE__ */ new Set([stateEnvPath, ...opts?.additionalEnvPaths ?? []])];
const defaultStateEnvPath = path.join(resolveRequiredHomeDir(process.env, os.homedir), ".openclaw", ".env");
const hasExplicitNonDefaultStateDir = process.env.OPENCLAW_STATE_DIR?.trim() !== void 0 && path.resolve(stateEnvPath) !== path.resolve(defaultStateEnvPath);
const globalEnvs = globalEnvPaths.map((filePath) => readDotEnvFile({
entryFilter: opts?.entryFilter,
filePath,
quiet
}));
const parsedFiles = [...globalEnvs];
let gatewayEnv = null;
if (!hasExplicitNonDefaultStateDir) {
gatewayEnv = readDotEnvFile({
entryFilter: opts?.entryFilter,
filePath: path.join(resolveRequiredHomeDir(process.env, os.homedir), ".config", "openclaw", "gateway.env"),
quiet
});
parsedFiles.push(gatewayEnv);
}
const parsed = parsedFiles.filter((file) => file !== null);
const appliedKeysByFile = loadParsedDotEnvFiles(parsed, opts?.overrideKeys);
return {
dotenvPresentKeys: [...new Set(parsed.flatMap((file) => file.entries.map(({ key }) => key)))],
stateEnvAppliedKeys: globalEnvs.flatMap((file) => file ? appliedKeysByFile.get(file.filePath) ?? [] : []),
gatewayEnvAppliedKeys: gatewayEnv ? appliedKeysByFile.get(gatewayEnv.filePath) ?? [] : []
};
}
//#endregion
export { readDotEnvFile as n, loadGlobalRuntimeDotEnvFiles as t };