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/CONFIG.md b/packages/effect/CONFIG.md index 8b932b79019..67540fec520 100644 --- a/packages/effect/CONFIG.md +++ b/packages/effect/CONFIG.md @@ -347,7 +347,7 @@ Effect.runSync(host) // "localhost" **How `_` splitting works**: env var names are split on `_` to build a tree. This means `DATABASE_HOST=localhost` is accessible at both `["DATABASE_HOST"]` (flat) and `["DATABASE", "HOST"]` (nested). Querying `["DATABASE"]` returns a Record node with child key `"HOST"`. -Pass `{ env: { ... } }` for testing. Omit to use `process.env` (merged with `import.meta.env` when available). +Pass `{ env: { ... } }` for testing. Omit to use `process.env` when available. ### `ConfigProvider.fromUnknown` — Plain JS Objects diff --git a/packages/effect/src/ConfigProvider.ts b/packages/effect/src/ConfigProvider.ts index c76631e7091..edc9658a40a 100644 --- a/packages/effect/src/ConfigProvider.ts +++ b/packages/effect/src/ConfigProvider.ts @@ -850,8 +850,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 @@ -892,8 +892,7 @@ export function fromEnv(options?: { const env: Record = options?.env ?? { ...(globalThis as { readonly process?: { readonly env?: Record } - }).process?.env, - ...(import.meta as any)?.env + }).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 92c13a0eeab..fcde9514639 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" const notFound = (method: string): PlatformError.PlatformError => PlatformError.systemError({ @@ -289,6 +290,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.env 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.env"), false) + }) + it("env without an underscore", async () => { const env = { A: "value1" } const provider = ConfigProvider.fromEnv({ env })