diff --git a/.changeset/configprovider-import-meta-env.md b/.changeset/configprovider-import-meta-env.md new file mode 100644 index 00000000000..bd02b360dbb --- /dev/null +++ b/.changeset/configprovider-import-meta-env.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove the default `import.meta.env` lookup from `ConfigProvider.fromEnv`, fixing module analysis failures in runtimes that do not support `import.meta`, closes #6358. diff --git a/packages/effect/src/ConfigProvider.ts b/packages/effect/src/ConfigProvider.ts index f9585b20842..0b9e9bbe735 100644 --- a/packages/effect/src/ConfigProvider.ts +++ b/packages/effect/src/ConfigProvider.ts @@ -810,8 +810,8 @@ function emptyStringAsMissing(value: string | undefined, preserveEmptyStrings: b * purely numeric names, the node is reported as an `Array`; otherwise as a * `Record`. * - * The default environment merges `process.env` and `import.meta.env` (when - * available). Override by passing `{ env: { ... } }`. + * The default environment reads `process.env` when available. For runtimes that + * expose environment variables elsewhere, pass `{ env: { ... } }`. * * Literal empty strings are treated as missing values when loaded as values by * default. Pass `{ preserveEmptyStrings: true }` to keep empty strings as @@ -850,8 +850,7 @@ export function fromEnv(options?: { readonly preserveEmptyStrings?: boolean | undefined }): ConfigProvider { const env: Record = options?.env ?? { - ...globalThis?.process?.env, - ...(import.meta as any)?.env + ...globalThis?.process?.env } const preserveEmptyStrings = options?.preserveEmptyStrings === true const trie = buildEnvTrie(env) diff --git a/packages/effect/test/ConfigProvider.test.ts b/packages/effect/test/ConfigProvider.test.ts index ca4309183b5..df19e011b8b 100644 --- a/packages/effect/test/ConfigProvider.test.ts +++ b/packages/effect/test/ConfigProvider.test.ts @@ -1,6 +1,7 @@ import { describe, it } from "@effect/vitest" import { deepStrictEqual } from "@effect/vitest/utils" import { ConfigProvider, Effect, FileSystem, Layer, Path, PlatformError, Result } from "effect" +import * as Fs from "node:fs" async function assertSuccess( provider: ConfigProvider.ConfigProvider, @@ -191,6 +192,46 @@ describe("ConfigProvider", () => { }) describe("fromEnv", () => { + it("uses the default environment when no env is provided", async () => { + const key = "EFFECT_CONFIG_PROVIDER_TEST_DEFAULT_ENV" + const previous = process.env[key] + process.env[key] = "value1" + try { + const provider = ConfigProvider.fromEnv() + await assertSuccess(provider, [key], ConfigProvider.makeValue("value1")) + } finally { + if (previous === undefined) { + delete process.env[key] + } else { + process.env[key] = previous + } + } + }) + + it("uses an explicit env over the default environment", async () => { + const key = "EFFECT_CONFIG_PROVIDER_TEST_DEFAULT_ENV" + const previous = process.env[key] + process.env[key] = "default" + try { + const provider = ConfigProvider.fromEnv({ env: { [key]: "explicit" } }) + await assertSuccess(provider, [key], ConfigProvider.makeValue("explicit")) + } finally { + if (previous === undefined) { + delete process.env[key] + } else { + process.env[key] = previous + } + } + }) + + it("does not reference import.meta in the common ConfigProvider module", () => { + const sourcePath = Fs.existsSync("src/ConfigProvider.ts") + ? "src/ConfigProvider.ts" + : "packages/effect/src/ConfigProvider.ts" + const source = Fs.readFileSync(sourcePath, "utf8") + deepStrictEqual(source.includes("import.meta"), false) + }) + it("env without an underscore", async () => { const env = { A: "value1" } const provider = ConfigProvider.fromEnv({ env })