Files
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

129 lines
5.4 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdtemp, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, it } from "node:test";
import {
LOCAL_DEVELOPMENT_TELEMETRY_ENV,
loadWorkspaceLocalEnv,
parseDotEnvLocal,
TELEMETRY_ENV_KEY,
} from "../src/local-env.js";
describe("tools-dev local env loading", () => {
it("parses common .env.local assignment forms", () => {
assert.deepEqual({ ...parseDotEnvLocal([
"# comment",
"POSTHOG_KEY=phc_local",
"POSTHOG_HOST=https://us.i.posthog.com # trailing comment",
"export LANGFUSE_PUBLIC_KEY=\"pk local\"",
"LANGFUSE_SECRET_KEY='sk#local'",
"BAD-KEY=ignored",
"",
].join("\n")) }, {
POSTHOG_KEY: "phc_local",
POSTHOG_HOST: "https://us.i.posthog.com",
LANGFUSE_PUBLIC_KEY: "pk local",
LANGFUSE_SECRET_KEY: "sk#local",
});
});
it("loads workspace .env.local over the parent environment and marks telemetry as local dev", async () => {
const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), "od-local-env-"));
await writeFile(path.join(workspaceRoot, ".env.local"), [
"POSTHOG_KEY=phc_from_file",
"LANGFUSE_PUBLIC_KEY=pk_from_file",
].join("\n"));
const env: NodeJS.ProcessEnv = { POSTHOG_KEY: "phc_from_parent" };
const result = loadWorkspaceLocalEnv({ workspaceRoot, env });
assert.equal(result.loaded, true);
assert.equal(env.POSTHOG_KEY, "phc_from_file");
assert.equal(env.LANGFUSE_PUBLIC_KEY, "pk_from_file");
assert.equal(env[TELEMETRY_ENV_KEY], LOCAL_DEVELOPMENT_TELEMETRY_ENV);
assert.deepEqual(result.loadedFiles, [".env.local"]);
assert.deepEqual(result.keys, ["LANGFUSE_PUBLIC_KEY", TELEMETRY_ENV_KEY, "POSTHOG_KEY"]);
});
it("loads workspace env files in precedence order without overriding higher-priority files", async () => {
const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), "od-local-env-"));
await writeFile(path.join(workspaceRoot, ".env"), "FROM_BASE=base\nSHARED=base\n");
await writeFile(path.join(workspaceRoot, ".env.development"), "FROM_DEV=dev\nSHARED=dev\n");
await writeFile(path.join(workspaceRoot, ".env.local"), "FROM_LOCAL=local\nSHARED=local\n");
await writeFile(path.join(workspaceRoot, ".env.development.local"), "FROM_DEV_LOCAL=dev-local\nSHARED=dev-local\n");
const logs: string[] = [];
const env: NodeJS.ProcessEnv = {};
const result = loadWorkspaceLocalEnv({ workspaceRoot, env, log: (message) => logs.push(message) });
assert.deepEqual(result.loadedFiles, [".env.development.local", ".env.local", ".env.development", ".env"]);
assert.equal(env.FROM_DEV_LOCAL, "dev-local");
assert.equal(env.FROM_LOCAL, "local");
assert.equal(env.FROM_DEV, "dev");
assert.equal(env.FROM_BASE, "base");
assert.equal(env.SHARED, "dev-local");
assert.deepEqual(logs, ["tools-dev env: loaded .env.development.local, .env.local, .env.development, .env"]);
});
it("uses explicit env files instead of default env files", async () => {
const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), "od-local-env-"));
await writeFile(path.join(workspaceRoot, ".env.local"), "DEFAULT_ONLY=yes\n");
await writeFile(path.join(workspaceRoot, "custom.env"), "CUSTOM_ONLY=yes\n");
const env: NodeJS.ProcessEnv = {};
const result = loadWorkspaceLocalEnv({ args: ["--env-file", "custom.env"], workspaceRoot, env });
assert.deepEqual(result.loadedFiles, ["custom.env"]);
assert.equal(env.CUSTOM_ONLY, "yes");
assert.equal(env.DEFAULT_ONLY, undefined);
});
it("can be disabled with --no-env-file", async () => {
const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), "od-local-env-"));
await writeFile(path.join(workspaceRoot, ".env.local"), "SHOULD_NOT_LOAD=yes\n");
const env: NodeJS.ProcessEnv = {};
const result = loadWorkspaceLocalEnv({ args: ["--no-env-file"], workspaceRoot, env });
assert.equal(result.loaded, false);
assert.equal(env.SHOULD_NOT_LOAD, undefined);
});
it("does not load env files for help output and suppresses logs for json output", async () => {
const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), "od-local-env-"));
await writeFile(path.join(workspaceRoot, ".env.local"), "VALUE=yes\n");
const helpEnv: NodeJS.ProcessEnv = {};
const helpResult = loadWorkspaceLocalEnv({ args: ["--help"], workspaceRoot, env: helpEnv });
assert.equal(helpResult.loaded, false);
assert.equal(helpEnv.VALUE, undefined);
const logs: string[] = [];
loadWorkspaceLocalEnv({ args: ["status", "--json"], workspaceRoot, env: {}, log: (message) => logs.push(message) });
assert.deepEqual(logs, []);
});
it("requires explicitly requested env files to exist", async () => {
const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), "od-local-env-"));
assert.throws(
() => loadWorkspaceLocalEnv({ args: ["--env-file", "missing.env"], workspaceRoot, env: {} }),
/env file not found: missing\.env/,
);
});
it("preserves an explicit telemetry environment from .env.local", async () => {
const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), "od-local-env-"));
await writeFile(path.join(workspaceRoot, ".env.local"), `${TELEMETRY_ENV_KEY}=dev_smoke\n`);
const env: NodeJS.ProcessEnv = {};
loadWorkspaceLocalEnv({ workspaceRoot, env });
assert.equal(env[TELEMETRY_ENV_KEY], "dev_smoke");
});
});