From 555a5caaaff4f50d2badb01c9dee94b40b7c9f3b Mon Sep 17 00:00:00 2001 From: MarkXian Date: Fri, 24 Jul 2026 17:31:04 +0800 Subject: [PATCH 1/5] fix: guard import.meta env lookup --- packages/effect/src/ConfigProvider.ts | 10 +++++++++- packages/effect/test/ConfigProvider.test.ts | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/effect/src/ConfigProvider.ts b/packages/effect/src/ConfigProvider.ts index f9585b20842..7c47bec90a5 100644 --- a/packages/effect/src/ConfigProvider.ts +++ b/packages/effect/src/ConfigProvider.ts @@ -851,7 +851,7 @@ export function fromEnv(options?: { }): ConfigProvider { const env: Record = options?.env ?? { ...globalThis?.process?.env, - ...(import.meta as any)?.env + ...getImportMetaEnv() } const preserveEmptyStrings = options?.preserveEmptyStrings === true const trie = buildEnvTrie(env) @@ -859,6 +859,14 @@ export function fromEnv(options?: { return make((path) => Effect.succeed(nodeAtEnv(trie, env, path, preserveEmptyStrings))) } +function getImportMetaEnv(): Record | undefined { + try { + return (import.meta as any)?.env + } catch { + return undefined + } +} + type EnvTrieNode = { children?: Record } diff --git a/packages/effect/test/ConfigProvider.test.ts b/packages/effect/test/ConfigProvider.test.ts index ca4309183b5..16775f3efce 100644 --- a/packages/effect/test/ConfigProvider.test.ts +++ b/packages/effect/test/ConfigProvider.test.ts @@ -191,6 +191,22 @@ 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("env without an underscore", async () => { const env = { A: "value1" } const provider = ConfigProvider.fromEnv({ env }) From d85872d5c4beb5e792965201c3e82ceb7716acbe Mon Sep 17 00:00:00 2001 From: MarkXian Date: Fri, 24 Jul 2026 20:57:12 +0800 Subject: [PATCH 2/5] fix: remove import.meta from default env provider --- packages/effect/src/ConfigProvider.ts | 15 +++---------- packages/effect/test/ConfigProvider.test.ts | 25 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/effect/src/ConfigProvider.ts b/packages/effect/src/ConfigProvider.ts index 7c47bec90a5..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, - ...getImportMetaEnv() + ...globalThis?.process?.env } const preserveEmptyStrings = options?.preserveEmptyStrings === true const trie = buildEnvTrie(env) @@ -859,14 +858,6 @@ export function fromEnv(options?: { return make((path) => Effect.succeed(nodeAtEnv(trie, env, path, preserveEmptyStrings))) } -function getImportMetaEnv(): Record | undefined { - try { - return (import.meta as any)?.env - } catch { - return undefined - } -} - type EnvTrieNode = { children?: Record } diff --git a/packages/effect/test/ConfigProvider.test.ts b/packages/effect/test/ConfigProvider.test.ts index 16775f3efce..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, @@ -207,6 +208,30 @@ describe("ConfigProvider", () => { } }) + 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 }) From 94ce1567f5750cae73079ceb97bbf3920310e03a Mon Sep 17 00:00:00 2001 From: MarkXian Date: Fri, 24 Jul 2026 21:05:16 +0800 Subject: [PATCH 3/5] chore: add config provider changeset --- .changeset/configprovider-import-meta-env.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/configprovider-import-meta-env.md 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. From fad9c53add8ca0b2d8e5ee0e9ba1e95d7ade81e4 Mon Sep 17 00:00:00 2001 From: MarkXian Date: Mon, 27 Jul 2026 11:05:24 +0800 Subject: [PATCH 4/5] docs: update ConfigProvider fromEnv default env docs --- packages/effect/CONFIG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/effect/CONFIG.md b/packages/effect/CONFIG.md index 158768692f7..edc6d8e2798 100644 --- a/packages/effect/CONFIG.md +++ b/packages/effect/CONFIG.md @@ -265,7 +265,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 From b097ad8e54d234408d42fac33b7b8d2eb98d6af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=BC=E5=81=A5=E8=81=AA?= Date: Wed, 5 Aug 2026 21:09:43 +0800 Subject: [PATCH 5/5] test(config): keep import.meta regression portable --- packages/effect/src/ConfigProvider.ts | 2 +- packages/effect/test/ConfigProvider.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/effect/src/ConfigProvider.ts b/packages/effect/src/ConfigProvider.ts index 975711016db..edc9658a40a 100644 --- a/packages/effect/src/ConfigProvider.ts +++ b/packages/effect/src/ConfigProvider.ts @@ -892,7 +892,7 @@ export function fromEnv(options?: { const env: Record = options?.env ?? { ...(globalThis as { readonly process?: { readonly env?: Record } - }).process?.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 e83957cea32..fcde9514639 100644 --- a/packages/effect/test/ConfigProvider.test.ts +++ b/packages/effect/test/ConfigProvider.test.ts @@ -322,12 +322,12 @@ describe("ConfigProvider", () => { } }) - it("does not reference import.meta in the common ConfigProvider module", () => { + 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"), false) + deepStrictEqual(source.includes("import.meta.env"), false) }) it("env without an underscore", async () => {