Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions apps/server/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
import { existsSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { parseEnv } from "node:util";

if (existsSync(".env")) process.loadEnvFile(".env");
/** .env keys whose file value loses to a different value already set in the environment. */
export function shadowedEnvKeys(
file: Record<string, string | undefined>,
env: Record<string, string | undefined> = process.env,
): string[] {
return Object.keys(file).filter((key) => env[key] !== undefined && env[key] !== file[key]);
}

if (existsSync(".env")) {
// loadEnvFile never overrides existing variables. A stale shell or system-wide value
// (for example OPENAI_API_KEY) would otherwise silently replace the .env setting.
const shadowed = shadowedEnvKeys(parseEnv(readFileSync(".env", "utf8")));
process.loadEnvFile(".env");
if (shadowed.length)
console.warn(
`[OpenMuse] Using ${shadowed.join(", ")} from the environment instead of .env. ` +
(shadowed.length === 1
? "Unset it to use the .env value."
: "Unset them to use the .env values."),
);
}
process.env.DO_NOT_TRACK ??= "1";
process.env.COPILOTKIT_TELEMETRY_DISABLED ??= "true";

Expand Down
13 changes: 12 additions & 1 deletion tests/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { assertApiDeploymentConfig, type Config } from "../apps/server/src/config.ts";
import {
assertApiDeploymentConfig,
type Config,
shadowedEnvKeys,
} from "../apps/server/src/config.ts";

const sampleConfig: Config = {
mode: "sample",
Expand Down Expand Up @@ -46,3 +50,10 @@ test("every API mode accepts a non-empty Intelligence key", () => {
);
}
});

test("environment variables that override a different .env value are reported by name", () => {
const file = { OPENAI_API_KEY: "sk-or-file", MODEL: "openai/gpt-5", PORT: "8787", EMPTY: "" };
const env = { OPENAI_API_KEY: "sk-proj-system", MODEL: "openai/gpt-5", EMPTY: "set" };
assert.deepEqual(shadowedEnvKeys(file, env), ["OPENAI_API_KEY", "EMPTY"]);
assert.deepEqual(shadowedEnvKeys(file, {}), []);
});
Loading