import { parseEnv } from "node:util"; import { loadEnvFile } from "node:process"; import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { pathToFileURL } from "node:url"; export function readEnv(key) { return process.env[key]; } export function writeEnv(key, value) { process.env[key] = value; } export function reportEnv() { const samples = [ "Z=last\nA=first\nZ=replaced\nEMPTY=\n", " # heading\nexport NAME = world\nINLINE=value#comment\nSPACES = two words \n", "SINGLE='a # b\\n c'\nDOUBLE=\"a # b\\n c\"\nTICK=`a # b\\n c`\n", "MULTI=\"first\nsecond\" trailing ignored\nNEXT=ok\n", "UNCLOSED=\"one\nNEXT=two\nLAST=three", "export X=\nexport Y= \n=\n = value\nexport =foo", "X= \nY=foo", "__proto__=ignored\nconstructor=own\ntoString=value\n2=two\n10=ten\n1=one", "UNICODE=é 😀 你好\n\uFEFFBOM=kept\nNULL=va\0lue\nLONE=\ud800\nn\0ame=value", "A=${OTHER}\nB=$OTHER\nC=one=two\nD=\"\\t\\r\\n\"", "", ]; for (const sample of samples) { const result = parseEnv(sample); console.log(JSON.stringify(result), JSON.stringify(Object.keys(result))); console.log(Object.getPrototypeOf(result) === Object.prototype); } for (const value of [undefined, null, 42, true, {}, [], Symbol("x"), 1n, () => {}, new Date(0), Object.create(null), Object.assign(Object.create(null), { X: 1 })]) { try { parseEnv(value); } catch (e) { console.log(e.name, e.code, e.message); } } const fresh = parseEnv("FRESH=one"); fresh.FRESH = "changed"; console.log(fresh.FRESH, parseEnv("FRESH=one").FRESH); const directory = mkdtempSync(tmpdir() + "/scriptc-env-island-"); const previous = process.cwd(); const prefix = "SCRIPTC_ENV_ISLAND_"; const symbol = Symbol("env"); console.log("symbols", process.env[symbol], symbol in process.env, delete process.env[symbol]); const names = ["NEW", "KEEP", "EMPTY", "SECOND", "RAW", "NUL", "DEFINED", "NUMBER"]; for (const name of names) delete process.env[prefix + name]; process.env[prefix + "KEEP"] = "existing"; process.env[prefix + "EMPTY"] = ""; process.env[prefix + "NUMBER"] = 42; Object.defineProperty(process.env, prefix + "DEFINED", { value: "defined", configurable: true, writable: true, enumerable: true }); try { console.log("chdir", process.chdir(directory), process.cwd() === directory); for (const value of [undefined, 42, {}]) { try { process.chdir(value); } catch (e) { console.log(e.name, e.code, e.message); } } writeFileSync(".env", prefix + "NEW=default\n" + prefix + "KEEP=replaced\n" + prefix + "EMPTY=replaced\n"); writeFileSync("second.env", prefix + "NEW=second\n" + prefix + "SECOND=\"two\\nlines\"\n"); console.log("default", loadEnvFile(), process.env[prefix + "NEW"], process.env[prefix + "KEEP"], JSON.stringify(process.env[prefix + "EMPTY"])); loadEnvFile("second.env"); console.log("second", process.env[prefix + "NEW"], JSON.stringify(process.env[prefix + "SECOND"])); for (const path of [undefined, null, Buffer.from(".env"), new Uint8Array(Buffer.from(".env")), pathToFileURL(directory + "/.env")]) { delete process.env[prefix + "NEW"]; console.log("path", loadEnvFile(path), process.env[prefix + "NEW"]); } writeFileSync("raw.env", Buffer.concat([Buffer.from(prefix + "RAW="), Buffer.from([0xff, 0xe2, 0x82]), Buffer.from("\n" + prefix + "NUL=va\0lue")])); loadEnvFile("raw.env"); console.log("raw", JSON.stringify(process.env[prefix + "RAW"]), process.env[prefix + "NUL"]); console.log("keys", Object.keys(process.env).filter(k => k.startsWith(prefix)).sort().join(",")); console.log("descriptor", JSON.stringify(Object.getOwnPropertyDescriptor(process.env, prefix + "DEFINED")), process.env[prefix + "NUMBER"]); for (const path of [42, false, {}, [], Symbol("path"), "a\0b", Buffer.from("a\0b"), new URL("https://example.com/"), new URL("file:///tmp/a%2fb"), "missing.env"]) { try { loadEnvFile(path); } catch (e) { console.log(e.name, e.code, e.message); } } try { Object.defineProperty(process.env, prefix + "BAD", { value: "bad" }); } catch (e) { console.log(e.name, e.code, e.message); } delete process.env[prefix + "KEEP"]; loadEnvFile(); console.log("reload", process.env[prefix + "KEEP"]); } finally { process.chdir(previous); rmSync(directory, { recursive: true, force: true }); for (const name of names) delete process.env[prefix + name]; } }