import { execFileSync } from "node:child_process"; import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; const script = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "ensure-local-env.sh"); const EXAMPLE = `NODE_ENV=development BETTER_AUTH_SECRET=replace-with-32-plus-character-secret ENCRYPTION_KEY=replace-with-64-random-hex-characters SANDBOX_SUPERVISOR_TOKEN= SCREEN_PROXY_SECRET=replace-with-32-plus-character-screen-proxy-secret COMPUTER_USER_PASSWORD= COMPUTER_ALLOW_SUDO= # VITE_DEFAULT_UI_LOCALE=en `; function readKey(file: string, key: string) { let value = ""; for (const line of readFileSync(file, "utf8").split(/\r?\n/)) { if (line.startsWith(`${key}=`)) value = line.slice(key.length + 1); } return value; } function run(dir: string, extraEnv: NodeJS.ProcessEnv = {}) { const env = { ...process.env }; for (const key of [ "BETTER_AUTH_SECRET", "ENCRYPTION_KEY", "SANDBOX_SUPERVISOR_TOKEN", "SCREEN_PROXY_SECRET", "COMPUTER_USER_PASSWORD", "COMPUTER_ALLOW_SUDO", "VITE_DEFAULT_UI_LOCALE", "LOCAL_COMPUTER_PASSWORD", "LOCAL_COMPUTER_SUDO", "LOCAL_UI_LOCALE", ]) { delete env[key]; } return execFileSync("bash", [script], { encoding: "utf8", env: { ...env, ENV_FILE: path.join(dir, ".env"), EXAMPLE_FILE: path.join(dir, ".env.example"), ...extraEnv, }, }); } describe("ensure-local-env", () => { it("creates .env and injects the local computer baseline plus secrets", () => { const dir = mkdtempSync(path.join(tmpdir(), "bangso-env-")); writeFileSync(path.join(dir, ".env.example"), EXAMPLE); const output = run(dir); const envFile = path.join(dir, ".env"); expect(output).toContain("created"); expect(readKey(envFile, "COMPUTER_ALLOW_SUDO")).toBe("1"); expect(readKey(envFile, "COMPUTER_USER_PASSWORD")).toBe("bangso"); expect(readKey(envFile, "VITE_DEFAULT_UI_LOCALE")).toBe("zh-TW"); expect(readKey(envFile, "BETTER_AUTH_SECRET")).toMatch(/^[0-9a-f]{64}$/); expect(readKey(envFile, "ENCRYPTION_KEY")).toMatch(/^[0-9a-f]{64}$/); expect(readKey(envFile, "SANDBOX_SUPERVISOR_TOKEN")).toMatch(/^[0-9a-f]{64}$/); expect(readKey(envFile, "SCREEN_PROXY_SECRET")).toMatch(/^[0-9a-f]{64}$/); expect(readKey(envFile, "NODE_ENV")).toBe("development"); }); it("does not clobber values already set in .env", () => { const dir = mkdtempSync(path.join(tmpdir(), "bangso-env-")); writeFileSync(path.join(dir, ".env.example"), EXAMPLE); writeFileSync( path.join(dir, ".env"), `BETTER_AUTH_SECRET=keep-auth-secret-value-32chars-min ENCRYPTION_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa SANDBOX_SUPERVISOR_TOKEN=keep-supervisor-token-value-32chars SCREEN_PROXY_SECRET=keep-screen-proxy-secret-value-32c COMPUTER_USER_PASSWORD=already-set COMPUTER_ALLOW_SUDO=0 VITE_DEFAULT_UI_LOCALE=en `, ); const output = run(dir); const envFile = path.join(dir, ".env"); expect(output).not.toContain("injected"); expect(readKey(envFile, "COMPUTER_ALLOW_SUDO")).toBe("0"); expect(readKey(envFile, "COMPUTER_USER_PASSWORD")).toBe("already-set"); expect(readKey(envFile, "VITE_DEFAULT_UI_LOCALE")).toBe("en"); expect(readKey(envFile, "BETTER_AUTH_SECRET")).toBe("keep-auth-secret-value-32chars-min"); }); it("fills only blank computer keys and accepts process-env overrides", () => { const dir = mkdtempSync(path.join(tmpdir(), "bangso-env-")); writeFileSync(path.join(dir, ".env.example"), EXAMPLE); writeFileSync( path.join(dir, ".env"), `BETTER_AUTH_SECRET=keep-auth-secret-value-32chars-min ENCRYPTION_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa SANDBOX_SUPERVISOR_TOKEN=keep-supervisor-token-value-32chars SCREEN_PROXY_SECRET=keep-screen-proxy-secret-value-32c COMPUTER_USER_PASSWORD= COMPUTER_ALLOW_SUDO= `, ); run(dir, { COMPUTER_USER_PASSWORD: "from-make", COMPUTER_ALLOW_SUDO: "1" }); const envFile = path.join(dir, ".env"); expect(readKey(envFile, "COMPUTER_USER_PASSWORD")).toBe("from-make"); expect(readKey(envFile, "COMPUTER_ALLOW_SUDO")).toBe("1"); expect(readKey(envFile, "VITE_DEFAULT_UI_LOCALE")).toBe("zh-TW"); expect(readKey(envFile, "BETTER_AUTH_SECRET")).toBe("keep-auth-secret-value-32chars-min"); }); });