UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

204 lines (203 loc) • 7.93 kB
import { r as normalizeFsSafeNativeEnv, t as clearFsSafeEnvFallback } from "./fs-safe-env-CCe2lU-3.mjs"; import { i as resolveRequiredHomeDir } from "./home-dir-Dc6nN6HB.mjs"; import { _ as resolveConfigDir } from "./utils-DgZn2dLu.mjs"; import { t as createSubsystemLogger } from "./subsystem-I-uqdx4f.mjs"; import { a as normalizeEnvVarKey } from "./host-env-security-BgpkByrc.mjs"; import fs from "node:fs"; import path from "node:path"; import os from "node:os"; import { readRegularFile, readRegularFileSync } from "@openclaw/fs-safe/advanced"; import { parse as parse$1 } from "dotenv"; //#region src/infra/dotenv-global-core.ts /** Maximum bytes to read from any dotenv file. */ const MAX_DOTENV_FILE_BYTES = 1048576; function reportDotEnvReadError(params, error) { if (params.quiet) return; if ((error && typeof error === "object" && "code" in error ? String(error.code) : void 0) !== "ENOENT") params.onWarning?.(`Failed to read ${params.filePath}: ${String(error)}`, { error }); if (error instanceof Error && error.message?.startsWith("File exceeds")) params.onWarning?.(`skipping oversized .env file (max ${MAX_DOTENV_FILE_BYTES} bytes): ${params.filePath}`); } function parseDotEnvFile(params, content) { 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 readDotEnvFileCore(params) { let content; try { const resolved = fs.realpathSync(params.filePath); const { buffer } = readRegularFileSync({ filePath: resolved, maxBytes: MAX_DOTENV_FILE_BYTES }); content = buffer; } catch (error) { reportDotEnvReadError(params, error); return null; } return parseDotEnvFile(params, content); } async function readDotEnvFileAsyncCore(params) { let content; try { const resolved = await fs.promises.realpath(params.filePath); const { buffer } = await readRegularFile({ filePath: resolved, maxBytes: MAX_DOTENV_FILE_BYTES }); content = buffer; } catch (error) { reportDotEnvReadError(params, error); return null; } return parseDotEnvFile(params, content); } function loadParsedDotEnvFiles(files, env, overrideKeys, onWarning) { clearFsSafeEnvFallback(env); const preExistingKeys = new Set(Object.keys(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 (env[key] === void 0 || mayOverride) { if (mayOverride) { for (const inheritedKey of preExistingKeys) if (canonicalizeKey(inheritedKey) === canonicalKey) env[inheritedKey] = value; } env[key] = value; const appliedKeys = appliedKeysByFile.get(file.filePath); if (appliedKeys) appliedKeys.push(key); else appliedKeysByFile.set(file.filePath, [key]); } } normalizeFsSafeNativeEnv(env); for (const conflict of conflicts.values()) { const keys = [...conflict.keys].toSorted(); onWarning?.(`Conflicting values in ${conflict.keptPath} and ${conflict.ignoredPath} for ${keys.join(", ")}; keeping ${conflict.keptPath}.`, { keptPath: conflict.keptPath, ignoredPath: conflict.ignoredPath, keys }); } return appliedKeysByFile; } function resolveGlobalDotEnvPaths(opts, env) { const stateEnvPath = opts.stateEnvPath ?? path.join(resolveConfigDir(env), ".env"); const globalEnvPaths = [.../* @__PURE__ */ new Set([stateEnvPath, ...opts.additionalEnvPaths ?? []])]; const home = resolveRequiredHomeDir(env, os.homedir); const defaultStateEnvPath = path.join(home, ".openclaw", ".env"); return { globalEnvPaths, gatewayEnvPath: env.OPENCLAW_STATE_DIR?.trim() !== void 0 && path.resolve(stateEnvPath) !== path.resolve(defaultStateEnvPath) ? void 0 : path.join(home, ".config", "openclaw", "gateway.env") }; } function applyGlobalDotEnvFiles(globalEnvs, gatewayEnv, env, overrideKeys, onWarning) { const parsed = [...globalEnvs, gatewayEnv].filter((file) => file !== null); const appliedKeysByFile = loadParsedDotEnvFiles(parsed, env, overrideKeys, onWarning); 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) ?? [] : [] }; } /** Load global runtime dotenv files with first-wins precedence, defaulting to `process.env`. */ function loadGlobalRuntimeDotEnvFilesCore(opts = {}) { const env = opts.env ?? process.env; const { globalEnvPaths, gatewayEnvPath } = resolveGlobalDotEnvPaths(opts, env); const readOptions = { entryFilter: opts.entryFilter, quiet: opts.quiet ?? true, onWarning: opts.onWarning }; return applyGlobalDotEnvFiles(globalEnvPaths.map((filePath) => readDotEnvFileCore({ ...readOptions, filePath })), gatewayEnvPath ? readDotEnvFileCore({ ...readOptions, filePath: gatewayEnvPath }) : null, env, opts.overrideKeys, opts.onWarning); } /** Read global runtime dotenv files asynchronously into a caller-owned environment. */ async function loadGlobalRuntimeDotEnvFilesAsyncCore(opts) { const { env } = opts; const { globalEnvPaths, gatewayEnvPath } = resolveGlobalDotEnvPaths(opts, env); const readOptions = { entryFilter: opts.entryFilter, quiet: opts.quiet ?? true, onWarning: opts.onWarning }; const globalEnvs = []; for (const filePath of globalEnvPaths) globalEnvs.push(await readDotEnvFileAsyncCore({ ...readOptions, filePath })); return applyGlobalDotEnvFiles(globalEnvs, gatewayEnvPath ? await readDotEnvFileAsyncCore({ ...readOptions, filePath: gatewayEnvPath }) : null, env, opts.overrideKeys, opts.onWarning); } //#endregion //#region src/infra/dotenv-global.ts const logger = createSubsystemLogger("infra:dotenv"); function readDotEnvFile(params) { return readDotEnvFileCore({ ...params, onWarning: logger.warn }); } async function readDotEnvFileAsync(params) { return await readDotEnvFileAsyncCore({ ...params, onWarning: logger.warn }); } /** Load global runtime dotenv files with first-wins precedence, defaulting to `process.env`. */ function loadGlobalRuntimeDotEnvFiles(opts = {}) { return loadGlobalRuntimeDotEnvFilesCore({ ...opts, onWarning: logger.warn }); } /** Read global runtime dotenv files asynchronously into a caller-owned environment. */ async function loadGlobalRuntimeDotEnvFilesAsync(opts) { return await loadGlobalRuntimeDotEnvFilesAsyncCore({ ...opts, onWarning: logger.warn }); } //#endregion export { readDotEnvFileAsync as i, loadGlobalRuntimeDotEnvFilesAsync as n, readDotEnvFile as r, loadGlobalRuntimeDotEnvFiles as t };