Skip to content

Commit 1b70bb5

Browse files
authored
✨ Unlock the potential of dotenv (#44)
* ✨ Unlock the potential of dotenv * ✅ Test without NODE_ENV * 📝 Change wording for path
1 parent d5989e3 commit 1b70bb5

File tree

8 files changed

+67
-14
lines changed

8 files changed

+67
-14
lines changed

.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ EXAMPLE_NO_UUID=
3030
EXAMPLE_DATE=2025-05-28T00:50:58.816Z
3131
EXAMPLE_INVALID_DATE=EXAMPLE_INVALID_DATE
3232
EXAMPLE_NO_DATE=
33+
34+
ENV_FILE=cwd

.env.development

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ENV_FILE=development

.github/dependabot.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ updates:
2222
eslint:
2323
patterns:
2424
- "*eslint*"
25+
- jiti
2526
prettier:
2627
patterns:
2728
- "*prettier*"

src/env.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
} from "./env.ts"
2020
import { loadEnv } from "./load_env.ts"
2121

22-
beforeAll(loadEnv)
22+
beforeAll(() => loadEnv())
2323

2424
describe("envBool", () => {
2525
test("valid", ({ expect }) => {

src/load_env.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { afterEach, describe, test } from "vitest"
2+
import { envString } from "./env.ts"
3+
import { loadEnv } from "./load_env.ts"
4+
5+
describe("loadEnv", () => {
6+
test("cwd", ({ expect }) => {
7+
loadEnv()
8+
9+
const ENV_FILE = envString("ENV_FILE")
10+
expect(ENV_FILE).toBe("cwd")
11+
})
12+
13+
test("path", ({ expect }) => {
14+
loadEnv({ path: "test" })
15+
16+
const ENV_FILE = envString("ENV_FILE")
17+
expect(ENV_FILE).toBe("test")
18+
})
19+
20+
test("development", ({ expect }) => {
21+
delete process.env["NODE_ENV"]
22+
loadEnv()
23+
24+
const ENV_FILE = envString("ENV_FILE")
25+
expect(ENV_FILE).toBe("development")
26+
})
27+
})
28+
29+
afterEach(() => {
30+
for (const key of Object.keys(process.env)) {
31+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
32+
if (key.startsWith("EXAMPLE_")) delete process.env[key]
33+
}
34+
35+
delete process.env["ENV_FILE"]
36+
process.env["NODE_ENV"] = "test"
37+
})

src/load_env.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import type { DotenvConfigOptions } from "dotenv"
12
import { config } from "dotenv"
23
import { join } from "path"
34
import type { LoadedEnv } from "./loaded_env.ts"
45

5-
/** Loads environment variables from the `.env` files. `NODE_ENV` has to be
6-
* set in the environment and will not be picked up from the filesystem.
6+
/** Loads environment variables from the `.env` files. `NODE_ENV` has to be set
7+
* in the environment and will not be picked up from the filesystem.
78
*
89
* If `NODE_ENV` is not set, it defaults to `development`.
910
*
@@ -13,19 +14,21 @@ import type { LoadedEnv } from "./loaded_env.ts"
1314
* 2. `.env.${NODE_ENV}`
1415
* 3. `.env.local`
1516
* 4. `.env`
17+
*
18+
* @param options Additional options to be passed to `dotenv.config` where
19+
* `path` is where to find `.env` files.
1620
*/
17-
export function loadEnv(): LoadedEnv {
18-
const cwd = process.cwd()
21+
export function loadEnv(options?: LoadEnvOptions): LoadedEnv {
1922
const NODE_ENV = process.env["NODE_ENV"]?.trim() || "development"
2023

21-
const { parsed, error } = config({
22-
path: [
23-
join(cwd, `.env.${NODE_ENV}.local`),
24-
join(cwd, `.env.${NODE_ENV}`),
25-
join(cwd, ".env.local"),
26-
join(cwd, ".env"),
27-
],
28-
})
24+
const paths = [
25+
`.env.${NODE_ENV}.local`,
26+
`.env.${NODE_ENV}`,
27+
".env.local",
28+
".env",
29+
].map(file => prepend(file, options?.path))
30+
31+
const { parsed, error } = config({ ...options, path: paths })
2932

3033
if (!parsed)
3134
throw new Error("Environment variables could not be loaded.", {
@@ -36,3 +39,12 @@ export function loadEnv(): LoadedEnv {
3639
process.env = merged
3740
return merged
3841
}
42+
43+
function prepend(file: string, path: string | undefined): string {
44+
return path ? join(path, file) : file
45+
}
46+
47+
export interface LoadEnvOptions extends Omit<DotenvConfigOptions, "path"> {
48+
/** Where to find `.env` files. */
49+
readonly path?: string | undefined
50+
}

test/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ENV_FILE=test

vitest.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { defineConfig } from "vitest/config"
33

44
const config: ViteUserConfig = defineConfig({
55
test: {
6-
reporters: ["default", "github-actions"],
76
include: ["src/**/*.test.ts"],
87
coverage: {
98
include: ["src/**/*.ts"],

0 commit comments

Comments
 (0)