fix: centralize dotenv env cleanup

This commit is contained in:
Shakker
2026-06-20 02:41:58 +01:00
parent c80f4c110e
commit e27c9a9a41
+24 -34
View File
@@ -12,6 +12,7 @@ import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plug
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
import { listKnownProviderAuthEnvVarNames } from "../secrets/provider-env-vars.js";
import { captureFullEnv, deleteTestEnvValue, setTestEnvValue } from "../test-utils/env.js";
import { loadDotEnv, loadWorkspaceDotEnvFile } from "./dotenv.js";
const loggerMocks = vi.hoisted(() => ({
@@ -80,7 +81,7 @@ async function writeEnvFile(filePath: string, contents: string) {
function clearEnv(keys: readonly string[]) {
for (const key of keys) {
delete process.env[key];
deleteTestEnvValue(key);
}
}
@@ -91,23 +92,12 @@ function expectEnvUndefined(keys: readonly string[]) {
}
async function withIsolatedEnvAndCwd(run: () => Promise<void>) {
const prevEnv = { ...process.env };
const envSnapshot = captureFullEnv();
try {
await run();
} finally {
vi.restoreAllMocks();
for (const key of Object.keys(process.env)) {
if (!(key in prevEnv)) {
delete process.env[key];
}
}
for (const [key, value] of Object.entries(prevEnv)) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
envSnapshot.restore();
}
}
@@ -169,7 +159,7 @@ async function withDotEnvFixture(run: (fixture: DotEnvFixture) => Promise<void>)
const base = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-dotenv-test-"));
const cwdDir = path.join(base, "cwd");
const stateDir = path.join(base, "state");
process.env.OPENCLAW_STATE_DIR = stateDir;
setTestEnvValue("OPENCLAW_STATE_DIR", stateDir);
await fs.mkdir(cwdDir, { recursive: true });
await fs.mkdir(stateDir, { recursive: true });
await run({ base, cwdDir, stateDir });
@@ -228,9 +218,9 @@ describe("loadDotEnv", () => {
it("loads the Ubuntu gateway.env compatibility fallback after ~/.openclaw/.env", async () => {
await withIsolatedEnvAndCwd(async () => {
await withDotEnvFixture(async ({ base, cwdDir }) => {
process.env.HOME = base;
setTestEnvValue("HOME", base);
const defaultStateDir = path.join(base, ".openclaw");
process.env.OPENCLAW_STATE_DIR = defaultStateDir;
setTestEnvValue("OPENCLAW_STATE_DIR", defaultStateDir);
await writeEnvFile(path.join(defaultStateDir, ".env"), "FOO=from-global\n");
await writeEnvFile(
path.join(base, ".config", "openclaw", "gateway.env"),
@@ -259,7 +249,7 @@ describe("loadDotEnv", () => {
it("does not warn about dotenv conflicts when the key is already set", async () => {
await withIsolatedEnvAndCwd(async () => {
await withDotEnvFixture(async ({ base, cwdDir, stateDir }) => {
process.env.HOME = base;
setTestEnvValue("HOME", base);
process.env.FOO = "from-shell";
await writeEnvFile(path.join(stateDir, ".env"), "FOO=from-global\n");
await writeEnvFile(
@@ -314,7 +304,7 @@ describe("loadDotEnv", () => {
delete process.env.NODE_REPL_EXTERNAL_MODULE;
delete process.env.NODE_REPL_HISTORY;
delete process.env.NODE_V8_COVERAGE;
delete process.env.OPENCLAW_CONFIG_PATH;
deleteTestEnvValue("OPENCLAW_CONFIG_PATH");
delete process.env.ANTHROPIC_BASE_URL;
delete process.env.CLOUDSDK_PYTHON;
delete process.env.EXAMPLE_API_HOST;
@@ -396,9 +386,9 @@ describe("loadDotEnv", () => {
].join("\n"),
);
delete process.env.OPENCLAW_STATE_DIR;
deleteTestEnvValue("OPENCLAW_STATE_DIR");
delete process.env.STATE_DIRECTORY;
delete process.env.OPENCLAW_CONFIG_PATH;
deleteTestEnvValue("OPENCLAW_CONFIG_PATH");
loadWorkspaceDotEnvFile(path.join(cwdDir, ".env"), { quiet: true });
@@ -453,7 +443,7 @@ describe("loadDotEnv", () => {
].join("\n"),
);
delete process.env.OPENCLAW_AGENT_DIR;
deleteTestEnvValue("OPENCLAW_AGENT_DIR");
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
delete process.env.OPENCLAW_OAUTH_DIR;
delete process.env.PI_CODING_AGENT_DIR;
@@ -548,7 +538,7 @@ describe("loadDotEnv", () => {
await withDotEnvFixture(async ({ cwdDir }) => {
await writeEnvFile(path.join(cwdDir, ".env"), `${key}=./evil/npm-cli.js\n`);
delete process.env[key];
deleteTestEnvValue(key);
loadWorkspaceDotEnvFile(path.join(cwdDir, ".env"), { quiet: true });
@@ -656,7 +646,7 @@ describe("loadCliDotEnv", () => {
// Delete the fixture-provided value so the blocking must come from
// the workspace blocklist, not the "already set" skip.
delete process.env.OPENCLAW_STATE_DIR;
deleteTestEnvValue("OPENCLAW_STATE_DIR");
vi.spyOn(process, "cwd").mockReturnValue(cwdDir);
loadCliDotEnv({ quiet: true });
@@ -669,9 +659,9 @@ describe("loadCliDotEnv", () => {
it("loads the gateway.env compatibility fallback during CLI startup", async () => {
await withIsolatedEnvAndCwd(async () => {
await withDotEnvFixture(async ({ base, cwdDir }) => {
process.env.HOME = base;
setTestEnvValue("HOME", base);
const defaultStateDir = path.join(base, ".openclaw");
process.env.OPENCLAW_STATE_DIR = defaultStateDir;
setTestEnvValue("OPENCLAW_STATE_DIR", defaultStateDir);
await writeEnvFile(path.join(defaultStateDir, ".env"), "FOO=from-global\n");
await writeEnvFile(
path.join(base, ".config", "openclaw", "gateway.env"),
@@ -693,9 +683,9 @@ describe("loadCliDotEnv", () => {
it("can defer global dotenv while loading only workspace env", async () => {
await withIsolatedEnvAndCwd(async () => {
await withDotEnvFixture(async ({ base, cwdDir }) => {
process.env.HOME = base;
setTestEnvValue("HOME", base);
const defaultStateDir = path.join(base, ".openclaw");
process.env.OPENCLAW_STATE_DIR = defaultStateDir;
setTestEnvValue("OPENCLAW_STATE_DIR", defaultStateDir);
await writeEnvFile(path.join(cwdDir, ".env"), "BAZ=from-workspace\n");
await writeEnvFile(path.join(defaultStateDir, ".env"), "FOO=from-global\n");
await writeEnvFile(
@@ -721,8 +711,8 @@ describe("loadCliDotEnv", () => {
await withIsolatedEnvAndCwd(async () => {
await withDotEnvFixture(async ({ base, cwdDir }) => {
const customStateDir = path.join(base, "custom-state");
process.env.HOME = base;
process.env.OPENCLAW_STATE_DIR = customStateDir;
setTestEnvValue("HOME", base);
setTestEnvValue("OPENCLAW_STATE_DIR", customStateDir);
await writeEnvFile(
path.join(base, ".config", "openclaw", "gateway.env"),
"FOO=from-gateway\n",
@@ -745,8 +735,8 @@ describe("loadCliDotEnv", () => {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-dotenv-legacy-"));
const cwdDir = path.join(base, "cwd");
const legacyStateDir = path.join(base, ".clawdbot");
process.env.HOME = base;
delete process.env.OPENCLAW_STATE_DIR;
setTestEnvValue("HOME", base);
deleteTestEnvValue("OPENCLAW_STATE_DIR");
delete process.env.OPENCLAW_TEST_FAST;
await fs.mkdir(cwdDir, { recursive: true });
await writeEnvFile(path.join(legacyStateDir, ".env"), "LEGACY_ONLY=from-legacy\n");
@@ -800,7 +790,7 @@ describe("loadCliDotEnv", () => {
vi.spyOn(process, "cwd").mockReturnValue(cwdDir);
delete process.env.SAFE_KEY;
delete process.env.OPENCLAW_CONFIG_PATH;
deleteTestEnvValue("OPENCLAW_CONFIG_PATH");
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
delete process.env.NODE_OPTIONS;
delete process.env.NODE_REDIRECT_WARNINGS;
@@ -970,7 +960,7 @@ describe("workspace .env blocklist completeness", () => {
);
for (const key of runtimeControlKeys) {
delete process.env[key];
deleteTestEnvValue(key);
}
loadWorkspaceDotEnvFile(path.join(cwdDir, ".env"), { quiet: true });