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
129 lines
5.4 KiB
TypeScript
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");
|
|
});
|
|
});
|