Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/configprovider-import-meta-env.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion packages/effect/CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 3 additions & 4 deletions packages/effect/src/ConfigProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -892,8 +892,7 @@ export function fromEnv(options?: {
const env: Record<string, string | undefined> = options?.env ?? {
...(globalThis as {
readonly process?: { readonly env?: Record<string, string | undefined> }
}).process?.env,
...(import.meta as any)?.env
}).process?.env
}
const preserveEmptyStrings = options?.preserveEmptyStrings === true
const trie = buildEnvTrie(env)
Expand Down
41 changes: 41 additions & 0 deletions packages/effect/test/ConfigProvider.test.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand Down Expand Up @@ -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 })
Expand Down