envzod 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.1
4
+
5
+ - Fix branding — error output now correctly shows `envzod` instead of `zod-env`
6
+
3
7
  ## 1.1.0
4
8
 
5
9
  ### CLI
package/README.md CHANGED
@@ -1,34 +1,29 @@
1
1
  # envzod
2
2
 
3
- Universal, type-safe environment variable validation powered by [Zod](https://zod.dev).
3
+ [![npm](https://img.shields.io/npm/v/envzod)](https://www.npmjs.com/package/envzod)
4
+ [![license](https://img.shields.io/npm/l/envzod)](./LICENSE)
4
5
 
5
- Works with **Next.js, Express, Fastify, Remix, Bun**any Node.js project.
6
+ **Your app should crash at startup if the env is wrong not three requests into production.**
6
7
 
7
- ---
8
-
9
- ## Why
8
+ `envzod` validates environment variables against a Zod schema at boot time. You get a typed object back. If anything is missing or invalid, it throws with a clear error that tells you exactly what's wrong.
10
9
 
11
- - `process.env` values are all `string | undefined` — no types, no validation
12
- - Errors surface at runtime deep in your app instead of at startup
13
- - `envzod` validates your env at boot and gives you a **fully typed object** — no casting needed
10
+ - **Typed** — `env.PORT` is `number`, not `string | undefined`
11
+ - **Fail-fast** crashes at startup with a readable error, not deep in a request handler
12
+ - **CLI check** `npx envzod check` validates before deploy, works in CI/CD
13
+ - **Next.js ready** — server/client split via `envzod/next`, no schema duplication
14
14
 
15
15
  ---
16
16
 
17
17
  ## Install
18
18
 
19
19
  ```bash
20
- npm install envzod
21
- # or
20
+ npm install envzod # zod is a peer dep — install it separately if you haven't
22
21
  pnpm add envzod
23
- # or
24
- yarn add envzod
25
22
  ```
26
23
 
27
- > `zod` is a peer dependency and is installed automatically (npm 7+, pnpm, yarn).
28
-
29
24
  ---
30
25
 
31
- ## Basic Usage
26
+ ## Basic usage
32
27
 
33
28
  ```ts
34
29
  // env.ts
@@ -37,26 +32,26 @@ import { z } from "zod";
37
32
 
38
33
  export const env = createEnv({
39
34
  DATABASE_URL: z.string().url(),
40
- PORT: z.coerce.number().default(3000),
41
- NODE_ENV: z.enum(["development", "test", "production"]),
42
- JWT_SECRET: z.string().min(32),
35
+ PORT: z.coerce.number().default(3000),
36
+ NODE_ENV: z.enum(["development", "test", "production"]),
37
+ JWT_SECRET: z.string().min(32),
43
38
  });
44
-
45
- // Fully typed — no casting needed
46
- env.PORT; // number
47
- env.DATABASE_URL; // string
48
- env.NODE_ENV; // "development" | "test" | "production"
49
39
  ```
50
40
 
51
- ---
41
+ ```ts
42
+ // anywhere in your app
43
+ import { env } from "./env";
52
44
 
53
- ## Error Output
45
+ env.PORT // number — not string, not undefined
46
+ env.DATABASE_URL // string
47
+ env.NODE_ENV // "development" | "test" | "production"
48
+ ```
54
49
 
55
- When validation fails, `envzod` prints a clear, readable error and throws:
50
+ If validation fails at startup you get this — not a runtime crash 10 minutes later:
56
51
 
57
52
  ```
58
53
  ╔════════════════════════════════════════════╗
59
- zod-env: Invalid Environment
54
+ envzod: Invalid Environment
60
55
  ╚════════════════════════════════════════════╝
61
56
 
62
57
  ✗ DATABASE_URL
@@ -67,77 +62,71 @@ When validation fails, `envzod` prints a clear, readable error and throws:
67
62
  String must contain at least 32 character(s)
68
63
  Got: "tooshort"
69
64
 
70
- ✗ NODE_ENV
71
- Invalid enum value. Expected 'development' | 'test' | 'production'
72
- Got: "prod"
73
-
74
65
  Fix the above and restart your server.
75
66
  ```
76
67
 
77
- The thrown error includes the full formatted details — visible in Next.js error overlays, server logs, and CI output.
78
-
79
68
  ---
80
69
 
81
- ## Options
82
-
83
- ```ts
84
- const env = createEnv(schema, {
85
- // Custom env source. Defaults to process.env
86
- source: myCustomObject,
87
-
88
- // Log "✅ zod-env: N variables validated" on success (good for dev)
89
- verbose: true,
70
+ ## CLI — validate before you deploy
90
71
 
91
- // Called with structured errors before throwing — use for Sentry, logging, etc.
92
- onError: (errors) => {
93
- Sentry.captureException(new Error("Invalid env"), { extra: { errors } });
94
- },
95
- });
72
+ ```bash
73
+ npx envzod check
74
+ npx envzod check --env .env.production
75
+ npx envzod check --config envzod.config # tries .ts then .js
96
76
  ```
97
77
 
98
- ### `onError` signature
78
+ Reads your `envzod.config.ts` (or `.js`) and validates against your env file. Exits with code 1 on failure.
79
+
80
+ **envzod.config.ts:**
99
81
 
100
82
  ```ts
101
- type EnvValidationError = {
102
- field: string;
103
- message: string;
104
- received: string | undefined;
83
+ import { z } from "zod";
84
+
85
+ export default {
86
+ DATABASE_URL: z.string().url(),
87
+ PORT: z.coerce.number().default(3000),
88
+ JWT_SECRET: z.string().min(32),
105
89
  };
106
90
  ```
107
91
 
108
- ---
92
+ **GitHub Actions:**
109
93
 
110
- ## Framework Examples
94
+ ```yaml
95
+ - name: Validate environment
96
+ run: npx envzod check --env .env.production
97
+ ```
98
+
99
+ The deploy fails here — not after the pod starts and serves broken responses.
100
+
101
+ ---
111
102
 
112
- ### Next.js (App Router) — `envzod/next`
103
+ ## Next.js
113
104
 
114
- Use `createNextEnv` from `envzod/next` for the best Next.js experience:
105
+ Next.js has a hard constraint: `NEXT_PUBLIC_*` variables must be referenced as literal strings (`process.env.NEXT_PUBLIC_FOO`) for webpack/Turbopack to inline them into the client bundle. Dynamic access doesn't work.
115
106
 
116
- - **Server vars** are auto-sourced from `process.env` — no repetition needed
117
- - **Client vars** (`NEXT_PUBLIC_*`) still require explicit `process.env.KEY` references — this is a hard webpack/Turbopack requirement, not a library limitation
107
+ `createNextEnv` handles this with a server/client split:
108
+ - **Server vars** auto-sourced from `process.env`, no repetition
109
+ - **Client vars** — you provide explicit `process.env.KEY` references (unavoidable webpack requirement)
118
110
 
119
- **Step 1 — Define schema once in `envzod.config.ts`:**
111
+ **Step 1 — define schema once in `envzod.config.ts`:**
120
112
 
121
113
  ```ts
122
- // envzod.config.ts
123
114
  import { z } from "zod";
124
115
 
125
116
  export const server = {
126
117
  DATABASE_URL: z.string().url(),
127
- JWT_SECRET: z.string().min(32),
128
- NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
118
+ JWT_SECRET: z.string().min(32),
119
+ NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
129
120
  };
130
121
 
131
122
  export const client = {
132
123
  NEXT_PUBLIC_API_URL: z.string().url(),
133
- NEXT_PUBLIC_APP_NAME: z.string().default("My App"),
134
124
  };
135
125
  ```
136
126
 
137
- **Step 2 — Wire it up in `env.ts`:**
127
+ **Step 2 — wire it in `env.ts`:**
138
128
 
139
129
  ```ts
140
- // env.ts
141
130
  import { createNextEnv } from "envzod/next";
142
131
  import { server, client } from "./envzod.config";
143
132
 
@@ -145,165 +134,121 @@ export const env = createNextEnv({
145
134
  server,
146
135
  client,
147
136
  runtimeEnv: {
148
- // Only NEXT_PUBLIC_* vars needed here — server vars auto-sourced
149
137
  NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
150
- NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME,
138
+ // server keys are auto-sourced — only client keys go here
151
139
  },
152
140
  verbose: process.env.NODE_ENV === "development",
153
- // bail: true clean process.exit(1) on error instead of throwing
141
+ bail: true, // process.exit(1) instead of throwing — avoids Next.js stack trace noise
154
142
  });
155
143
  ```
156
144
 
157
- **Step 3 — CLI also reads `envzod.config.ts` automatically:**
145
+ The CLI reads from `envzod.config.ts` automatically — same schema, no duplication:
158
146
 
159
147
  ```bash
160
148
  npx envzod check
161
149
  ```
162
150
 
163
- #### `createNextEnv` options
164
-
165
- | Option | Type | Default | Description |
166
- | ------------ | --------------------------------- | --------- | -------------------------------------------------------- |
167
- | `server` | `EnvSchema` | `{}` | Server-only vars, auto-sourced from `process.env` |
168
- | `client` | `EnvSchema` | `{}` | Client vars, must all start with `NEXT_PUBLIC_` |
169
- | `runtimeEnv` | `{ [K in keyof client]: string }` | required | Explicit `process.env.KEY` refs for each client key |
170
- | `verbose` | `boolean` | `false` | Log success summary |
171
- | `onError` | `(errors) => void` | — | Called with structured errors before throwing |
172
- | `bail` | `boolean` | `false` | Call `process.exit(1)` instead of throwing — no Next.js stack trace noise |
173
-
174
151
  ---
175
152
 
176
- ### Express
153
+ ## Express / Node
177
154
 
178
155
  ```ts
179
- // src/env.ts
180
156
  import { createEnv } from "envzod";
181
157
  import { z } from "zod";
182
158
 
183
- export const env = createEnv(
184
- {
185
- PORT: z.coerce.number().default(3000),
186
- DATABASE_URL: z.string().url(),
187
- JWT_SECRET: z.string().min(32),
188
- },
189
- {
190
- verbose: process.env.NODE_ENV === "development",
191
- },
192
- );
159
+ export const env = createEnv({
160
+ PORT: z.coerce.number().default(3000),
161
+ DATABASE_URL: z.string().url(),
162
+ JWT_SECRET: z.string().min(32),
163
+ });
193
164
 
194
- // app.ts
195
- import { env } from "./env";
196
165
  app.listen(env.PORT);
197
166
  ```
198
167
 
199
- ### Bun
168
+ ---
169
+
170
+ ## Bun
200
171
 
201
172
  ```ts
202
- // env.ts
203
173
  import { createEnv } from "envzod";
204
174
  import { z } from "zod";
205
175
 
206
176
  export const env = createEnv(
207
177
  {
208
- PORT: z.coerce.number().default(3000),
178
+ PORT: z.coerce.number().default(3000),
209
179
  DATABASE_URL: z.string().url(),
210
- JWT_SECRET: z.string().min(32),
211
- },
212
- {
213
- source: Bun.env,
214
- verbose: Bun.env.NODE_ENV === "development",
215
180
  },
181
+ { source: Bun.env },
216
182
  );
217
183
  ```
218
184
 
219
185
  ---
220
186
 
221
- ## TypeScript
222
-
223
- `envzod` uses the `InferEnv<T>` utility type to derive the return type from your schema. No manual type annotations needed.
224
-
225
- ```ts
226
- import type { InferEnv } from "envzod";
227
- import { z } from "zod";
228
-
229
- const schema = {
230
- PORT: z.coerce.number(),
231
- NODE_ENV: z.enum(["development", "production"]),
232
- };
233
-
234
- type Env = InferEnv<typeof schema>;
235
- // { PORT: number; NODE_ENV: "development" | "production" }
236
- ```
187
+ ## Options
237
188
 
238
- ---
189
+ ### `createEnv(schema, options?)`
239
190
 
240
- ## CLI `npx envzod check`
191
+ | Option | Type | Default | Description |
192
+ |--------|------|---------|-------------|
193
+ | `source` | `Record<string, string \| undefined>` | `process.env` | Custom env source |
194
+ | `verbose` | `boolean` | `false` | Log `✅ envzod: N variables validated` on success |
195
+ | `onError` | `(errors: EnvValidationError[]) => void` | — | Called before throwing — use for Sentry, logging |
241
196
 
242
- Validate your env before deploying great for CI/CD pipelines.
197
+ ### `createNextEnv(options)``envzod/next`
243
198
 
244
- **Supports both `.ts` and `.js` config files** TypeScript config is auto-detected.
199
+ | Option | Type | Default | Description |
200
+ |--------|------|---------|-------------|
201
+ | `server` | `EnvSchema` | `{}` | Server-only vars, auto-sourced from `process.env` |
202
+ | `client` | `EnvSchema` | `{}` | Client vars — must all start with `NEXT_PUBLIC_` |
203
+ | `runtimeEnv` | `{ [K in keyof client]: string \| undefined }` | required | Explicit refs for each client key |
204
+ | `verbose` | `boolean` | `false` | Log success summary |
205
+ | `onError` | `(errors: EnvValidationError[]) => void` | — | Called before throwing |
206
+ | `bail` | `boolean` | `false` | Call `process.exit(1)` instead of throwing |
245
207
 
246
- **1. Create `envzod.config.ts` (or `.js`) in your project root:**
208
+ ### Error shape
247
209
 
248
210
  ```ts
249
- // envzod.config.ts
250
- import { z } from "zod";
251
-
252
- export default {
253
- DATABASE_URL: z.string().url(),
254
- PORT: z.coerce.number().default(3000),
255
- NODE_ENV: z.enum(["development", "test", "production"]),
256
- };
257
- ```
258
-
259
- Or CommonJS:
260
-
261
- ```js
262
- // envzod.config.js
263
- const { z } = require("zod");
264
-
265
- module.exports = {
266
- DATABASE_URL: z.string().url(),
267
- PORT: z.coerce.number().default(3000),
268
- NODE_ENV: z.enum(["development", "test", "production"]),
211
+ type EnvValidationError = {
212
+ field: string;
213
+ message: string;
214
+ received: string | undefined;
269
215
  };
270
216
  ```
271
217
 
272
- **2. Run the check:**
273
-
274
- ```bash
275
- npx envzod check
276
- ```
218
+ ---
277
219
 
278
- **Options:**
220
+ ## TypeScript
279
221
 
280
- ```bash
281
- npx envzod check --env .env.production # custom env file (default: .env)
282
- npx envzod check --config my.config # custom config file (tries .ts then .js)
283
- ```
222
+ ```ts
223
+ import type { InferEnv } from "envzod";
224
+ import { z } from "zod";
284
225
 
285
- **Add to CI (GitHub Actions example):**
226
+ const schema = {
227
+ PORT: z.coerce.number(),
228
+ NODE_ENV: z.enum(["development", "production"]),
229
+ };
286
230
 
287
- ```yaml
288
- - name: Validate environment
289
- run: npx envzod check --env .env.production
231
+ type Env = InferEnv<typeof schema>;
232
+ // { PORT: number; NODE_ENV: "development" | "production" }
290
233
  ```
291
234
 
292
235
  ---
293
236
 
294
237
  ## vs t3-env
295
238
 
296
- | Feature | envzod | t3-env |
297
- | ---------------- | ------------------------------- | ------------------------------ |
298
- | Framework | Universal | Next.js focused |
299
- | Basic setup | `createEnv(schema)` | Separate client/server schemas |
300
- | Next.js helper | `createNextEnv` via `envzod/next` | Built-in |
301
- | Server/client split | Yes — `server` + `client` | Yes — `server` + `client` |
302
- | Source repetition | Server: none, Client: required | Always required (`runtimeEnv`) |
303
- | CLI check | `npx envzod check` (TS + JS) | None |
304
- | Dependencies | zod only | Next.js types + more |
305
- | Bundle | CJS + ESM | ESM only |
306
- | Error output | Pretty box with field details | Zod default |
239
+ Both libraries solve the same problem. Key differences:
240
+
241
+ | | envzod | t3-env |
242
+ |--|--------|--------|
243
+ | Works outside Next.js | Yes | Limited |
244
+ | Next.js server/client split | `envzod/next` | Built-in |
245
+ | Server var repetition | None (auto-sourced) | Required |
246
+ | Client var repetition | Required (webpack constraint — same for both) | Required |
247
+ | CLI pre-deploy check | Yes TS + JS config | No |
248
+ | Error output | Formatted box, field-level | Zod default |
249
+ | Output formats | CJS + ESM | ESM only |
250
+
251
+ The client var repetition (`runtimeEnv`) is a webpack/Turbopack hard requirement — no library can eliminate it.
307
252
 
308
253
  ---
309
254
 
package/dist/cli.js CHANGED
@@ -35538,7 +35538,7 @@ function pad(text, width) {
35538
35538
  }
35539
35539
  function formatErrors(errors) {
35540
35540
  const lines = [];
35541
- const title = " zod-env: Invalid Environment ";
35541
+ const title = " envzod: Invalid Environment ";
35542
35542
  lines.push(`\u2554${"\u2550".repeat(BOX_WIDTH)}\u2557`);
35543
35543
  lines.push(`\u2551${pad(title, BOX_WIDTH)}\u2551`);
35544
35544
  lines.push(`\u255A${"\u2550".repeat(BOX_WIDTH)}\u255D`);
@@ -35555,7 +35555,7 @@ function formatErrors(errors) {
35555
35555
  return lines.join("\n");
35556
35556
  }
35557
35557
  function formatSuccess(count) {
35558
- return `\u2705 zod-env: ${count} variable${count === 1 ? "" : "s"} validated`;
35558
+ return `\u2705 envzod: ${count} variable${count === 1 ? "" : "s"} validated`;
35559
35559
  }
35560
35560
 
35561
35561
  // src/cli.ts
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ function pad(text, width) {
9
9
  }
10
10
  function formatErrors(errors) {
11
11
  const lines = [];
12
- const title = " zod-env: Invalid Environment ";
12
+ const title = " envzod: Invalid Environment ";
13
13
  lines.push(`\u2554${"\u2550".repeat(BOX_WIDTH)}\u2557`);
14
14
  lines.push(`\u2551${pad(title, BOX_WIDTH)}\u2551`);
15
15
  lines.push(`\u255A${"\u2550".repeat(BOX_WIDTH)}\u255D`);
@@ -26,7 +26,7 @@ function formatErrors(errors) {
26
26
  return lines.join("\n");
27
27
  }
28
28
  function formatSuccess(count) {
29
- return `\u2705 zod-env: ${count} variable${count === 1 ? "" : "s"} validated`;
29
+ return `\u2705 envzod: ${count} variable${count === 1 ? "" : "s"} validated`;
30
30
  }
31
31
  function validate(schema, source) {
32
32
  const shape = {};
package/dist/index.mjs CHANGED
@@ -7,7 +7,7 @@ function pad(text, width) {
7
7
  }
8
8
  function formatErrors(errors) {
9
9
  const lines = [];
10
- const title = " zod-env: Invalid Environment ";
10
+ const title = " envzod: Invalid Environment ";
11
11
  lines.push(`\u2554${"\u2550".repeat(BOX_WIDTH)}\u2557`);
12
12
  lines.push(`\u2551${pad(title, BOX_WIDTH)}\u2551`);
13
13
  lines.push(`\u255A${"\u2550".repeat(BOX_WIDTH)}\u255D`);
@@ -24,7 +24,7 @@ function formatErrors(errors) {
24
24
  return lines.join("\n");
25
25
  }
26
26
  function formatSuccess(count) {
27
- return `\u2705 zod-env: ${count} variable${count === 1 ? "" : "s"} validated`;
27
+ return `\u2705 envzod: ${count} variable${count === 1 ? "" : "s"} validated`;
28
28
  }
29
29
  function validate(schema, source) {
30
30
  const shape = {};
package/dist/next.js CHANGED
@@ -9,7 +9,7 @@ function pad(text, width) {
9
9
  }
10
10
  function formatErrors(errors) {
11
11
  const lines = [];
12
- const title = " zod-env: Invalid Environment ";
12
+ const title = " envzod: Invalid Environment ";
13
13
  lines.push(`\u2554${"\u2550".repeat(BOX_WIDTH)}\u2557`);
14
14
  lines.push(`\u2551${pad(title, BOX_WIDTH)}\u2551`);
15
15
  lines.push(`\u255A${"\u2550".repeat(BOX_WIDTH)}\u255D`);
@@ -26,7 +26,7 @@ function formatErrors(errors) {
26
26
  return lines.join("\n");
27
27
  }
28
28
  function formatSuccess(count) {
29
- return `\u2705 zod-env: ${count} variable${count === 1 ? "" : "s"} validated`;
29
+ return `\u2705 envzod: ${count} variable${count === 1 ? "" : "s"} validated`;
30
30
  }
31
31
  function validate(schema, source) {
32
32
  const shape = {};
package/dist/next.mjs CHANGED
@@ -7,7 +7,7 @@ function pad(text, width) {
7
7
  }
8
8
  function formatErrors(errors) {
9
9
  const lines = [];
10
- const title = " zod-env: Invalid Environment ";
10
+ const title = " envzod: Invalid Environment ";
11
11
  lines.push(`\u2554${"\u2550".repeat(BOX_WIDTH)}\u2557`);
12
12
  lines.push(`\u2551${pad(title, BOX_WIDTH)}\u2551`);
13
13
  lines.push(`\u255A${"\u2550".repeat(BOX_WIDTH)}\u255D`);
@@ -24,7 +24,7 @@ function formatErrors(errors) {
24
24
  return lines.join("\n");
25
25
  }
26
26
  function formatSuccess(count) {
27
- return `\u2705 zod-env: ${count} variable${count === 1 ? "" : "s"} validated`;
27
+ return `\u2705 envzod: ${count} variable${count === 1 ? "" : "s"} validated`;
28
28
  }
29
29
  function validate(schema, source) {
30
30
  const shape = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envzod",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Universal, type-safe environment variable validation powered by Zod. Zero config, works everywhere.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",