zod-envkit 1.0.3 → 1.0.5

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,3 +1,37 @@
1
+ ## [1.0.5](https://github.com/nxtxe/zod-envkit/compare/v1.0.4...v1.0.5) (2026-01-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * release 1.0.5 ([4d48479](https://github.com/nxtxe/zod-envkit/commit/4d48479b62819d4c17cc05eb14c64f387806cbec))
7
+
8
+ ## [1.0.4](https://github.com/nxtxe/zod-envkit/compare/v1.0.3...v1.0.4) (2026-01-26)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **ci:** retry install to avoid rollup optional deps issue ([356e2c2](https://github.com/nxtxe/zod-envkit/commit/356e2c2b8980484767be29062d5f8eb4cdb4fde4))
14
+ * **ci:** retry install to avoid rollup optional deps issue ([7063e29](https://github.com/nxtxe/zod-envkit/commit/7063e29bb5cc3c7a8151a2afade035300ac5cff0))
15
+ * **cli:** stabilize release and execution behavior ([13f9b4f](https://github.com/nxtxe/zod-envkit/commit/13f9b4fe9f8893ec0d0b5b0f2373ef24cad76108))
16
+ * **release:** publish fix ([6811915](https://github.com/nxtxe/zod-envkit/commit/681191593e681d48b2e6490fcdd49f2b06a6bda5))
17
+ * **release:** publish repair ([a7f6557](https://github.com/nxtxe/zod-envkit/commit/a7f655758b3c214bb977f1e34cbb10246b39234a))
18
+ * **release:** publish retry ([1c55df0](https://github.com/nxtxe/zod-envkit/commit/1c55df00cf48623178d0a7cc81dfc68fc7b8dac0))
19
+ * **release:** retry with granular npm token ([c851824](https://github.com/nxtxe/zod-envkit/commit/c851824ba1777d31e14f7ea5306dc92adcab4de7))
20
+ * trigger patch release ([2c978e5](https://github.com/nxtxe/zod-envkit/commit/2c978e5b93b159117ca52eeed84e07063e9ecfea))
21
+
22
+ ## [1.0.4](https://github.com/nxtxe/zod-envkit/compare/v1.0.3...v1.0.4) (2026-01-26)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **ci:** retry install to avoid rollup optional deps issue ([356e2c2](https://github.com/nxtxe/zod-envkit/commit/356e2c2b8980484767be29062d5f8eb4cdb4fde4))
28
+ * **ci:** retry install to avoid rollup optional deps issue ([7063e29](https://github.com/nxtxe/zod-envkit/commit/7063e29bb5cc3c7a8151a2afade035300ac5cff0))
29
+ * **cli:** stabilize release and execution behavior ([13f9b4f](https://github.com/nxtxe/zod-envkit/commit/13f9b4fe9f8893ec0d0b5b0f2373ef24cad76108))
30
+ * **release:** publish fix ([6811915](https://github.com/nxtxe/zod-envkit/commit/681191593e681d48b2e6490fcdd49f2b06a6bda5))
31
+ * **release:** publish repair ([a7f6557](https://github.com/nxtxe/zod-envkit/commit/a7f655758b3c214bb977f1e34cbb10246b39234a))
32
+ * **release:** publish retry ([1c55df0](https://github.com/nxtxe/zod-envkit/commit/1c55df00cf48623178d0a7cc81dfc68fc7b8dac0))
33
+ * **release:** retry with granular npm token ([c851824](https://github.com/nxtxe/zod-envkit/commit/c851824ba1777d31e14f7ea5306dc92adcab4de7))
34
+
1
35
  # Changelog
2
36
 
3
37
  All notable changes to this project will be documented in this file.
@@ -5,17 +39,54 @@ This project follows [Semantic Versioning](https://semver.org/).
5
39
 
6
40
  ---
7
41
 
8
- ## [1.0.2] – 2026-01-26
42
+ ## [1.0.5] – 2026-01-26
9
43
 
10
44
  ### Added
11
- - `zod-envkit show` command to display environment status in a readable table (with secret masking)
12
- - `zod-envkit check` command to validate required variables with CI-friendly exit codes
45
+ - `mustLoadEnv` helper for fail-fast env loading (throws on invalid env)
46
+ - Default CLI behavior: running `zod-envkit` without subcommand now behaves like `zod-envkit generate`
47
+ - Better secret masking in `zod-envkit show` (TOKEN / SECRET / PASSWORD / *_KEY / PRIVATE)
48
+
49
+ ### Changed
50
+ - Public API (`loadEnv`, `mustLoadEnv`, `formatZodError`) stabilized and separated from CLI/generators
51
+ - CLI error handling improved:
52
+ - no stack traces for user errors
53
+ - consistent human-readable messages
54
+ - strict exit codes (`0` success, `1` error)
55
+ - `ENV.md` generation now produces fully centered, width-aware Markdown tables
56
+ - CLI now reliably resolves `env.meta.json` from:
57
+ - project root
58
+ - `./examples/`
59
+ - explicit `-c/--config` path
60
+
61
+ ### Fixed
62
+ - TypeScript type narrowing issues in CLI (`fs.readFileSync` with undefined paths)
63
+ - Potential double execution when running CLI without subcommands
64
+ - Inconsistent env loading behavior across CLI commands
65
+
66
+ [1.0.5]: https://www.npmjs.com/package/zod-envkit/v/1.0.5
67
+
68
+ ---
69
+
70
+ ## [1.0.4] – 2026-01-26
71
+
72
+ ### Added
73
+ - `zod-envkit show` command to display env status in a readable table (with secret masking)
74
+ - `zod-envkit check` command to validate required variables (CI-friendly exit codes)
13
75
 
14
76
  ### Changed
15
77
  - CLI now also searches for `env.meta.json` in `./examples/` by default
16
- - CLI output and documentation updated accordingly
17
78
 
18
- [1.0.2]: https://www.npmjs.com/package/zod-envkit/v/1.0.2
79
+ [1.0.4]: https://www.npmjs.com/package/zod-envkit/v/1.0.4
80
+
81
+ ---
82
+
83
+ ## [1.0.3] – 2026-01-26
84
+ - Git tag only (not published to npm)
85
+
86
+ ---
87
+
88
+ ## [1.0.2] – 2026-01-26
89
+ - Git tag only (not published to npm)
19
90
 
20
91
  ---
21
92
 
package/README.RU.md ADDED
@@ -0,0 +1,278 @@
1
+ <div align="center">
2
+ <br />
3
+ <p>
4
+ <img src="./zod-envkit.svg" width="546" alt="zod-envkit" />
5
+ </p>
6
+ <br />
7
+ <p>
8
+ <a href="https://github.com/nxtxe/zod-envkit">
9
+ <img src="https://github.com/nxtxe/zod-envkit/actions/workflows/release.yml/badge.svg" />
10
+ </a>
11
+ <a href="https://www.npmjs.com/package/zod-envkit">
12
+ <img src="https://img.shields.io/npm/v/zod-envkit.svg?maxAge=100" alt="npm version" />
13
+ </a>
14
+ <a href="https://www.npmjs.com/package/zod-envkit">
15
+ <img src="https://img.shields.io/npm/dt/zod-envkit.svg?maxAge=100" alt="npm downloads" />
16
+ </a>
17
+ </p>
18
+
19
+ <p>
20
+ <a href="./README.md">English</a> |
21
+ <a href="./README.RU.md">Русский</a>
22
+ </p>
23
+ </div>
24
+
25
+ Ниже — русский перевод текста, сохраняя структуру и смысл оригинала.
26
+
27
+ ---
28
+
29
+ Типобезопасная валидация и документация переменных окружения с помощью **Zod**.
30
+
31
+ **zod-envkit** — это небольшая библиотека + CLI, которая помогает воспринимать переменные окружения как
32
+ **явный контракт времени выполнения**, а не как игру в угадайку.
33
+
34
+ * валидация `process.env` при старте приложения
35
+ * полностью типизированные переменные окружения
36
+ * генерация `.env.example`
37
+ * генерация читаемой документации (`ENV.md`)
38
+ * просмотр и проверка состояния env через CLI
39
+ * быстрый фейл в CI/CD до деплоя
40
+
41
+ Никакого облака. Никакой магии. Только код.
42
+
43
+ ---
44
+
45
+ ## Зачем
46
+
47
+ Переменные окружения критичны, но почти всегда обрабатываются плохо.
48
+
49
+ Типичные проблемы:
50
+
51
+ * `process.env` — это просто `string | undefined`
52
+ * отсутствующие или некорректные переменные падают **во время выполнения**
53
+ * `.env.example` и документация быстро устаревают
54
+ * CI/CD ломается поздно и непредсказуемо
55
+
56
+ **zod-envkit** решает это, делая env:
57
+
58
+ * валидируемым на раннем этапе
59
+ * типизированным
60
+ * документированным
61
+ * проверяемым в CI
62
+
63
+ ---
64
+
65
+ ## Установка
66
+
67
+ ```bash
68
+ npm install zod-envkit
69
+ ```
70
+
71
+ ```bash
72
+ yarn add zod-envkit
73
+ ```
74
+
75
+ ```bash
76
+ pnpm add zod-envkit
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Использование библиотеки (runtime-валидация)
82
+
83
+ Создайте один файл, отвечающий за загрузку и валидацию env.
84
+
85
+ ```ts
86
+ import "dotenv/config";
87
+ import { z } from "zod";
88
+ import { loadEnv, mustLoadEnv, formatZodError } from "zod-envkit";
89
+
90
+ const EnvSchema = z.object({
91
+ NODE_ENV: z.enum(["development", "test", "production"]),
92
+ PORT: z.coerce.number().int().min(1).max(65535),
93
+ DATABASE_URL: z.string().url(),
94
+ });
95
+ ```
96
+
97
+ ### Вариант 1 — безопасный режим (без исключений)
98
+
99
+ ```ts
100
+ const result = loadEnv(EnvSchema);
101
+
102
+ if (!result.ok) {
103
+ console.error("Некорректное окружение:\n" + formatZodError(result.error));
104
+ process.exit(1);
105
+ }
106
+
107
+ export const env = result.env;
108
+ ```
109
+
110
+ ### Вариант 2 — fail-fast (рекомендуется)
111
+
112
+ ```ts
113
+ export const env = mustLoadEnv(EnvSchema);
114
+ ```
115
+
116
+ Теперь:
117
+
118
+ * `env.PORT` — это **number**
119
+ * `env.DATABASE_URL` — это **string**
120
+ * TypeScript всё знает на этапе компиляции
121
+ * приложение падает сразу, если env некорректен
122
+
123
+ ---
124
+
125
+ ## Использование CLI
126
+
127
+ CLI работает на основе простого мета-файла: `env.meta.json`.
128
+
129
+ По умолчанию он ищется в:
130
+
131
+ * `./env.meta.json`
132
+ * `./examples/env.meta.json`
133
+
134
+ ---
135
+
136
+ ### Пример `env.meta.json`
137
+
138
+ ```json
139
+ {
140
+ "NODE_ENV": {
141
+ "description": "Режим выполнения",
142
+ "example": "development",
143
+ "required": true
144
+ },
145
+ "PORT": {
146
+ "description": "HTTP-порт",
147
+ "example": "3000",
148
+ "required": true
149
+ },
150
+ "DATABASE_URL": {
151
+ "description": "Строка подключения к Postgres",
152
+ "example": "postgresql://user:pass@localhost:5432/db",
153
+ "required": true
154
+ }
155
+ }
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Команды CLI
161
+
162
+ ### Генерация `.env.example` и `ENV.md`
163
+
164
+ (Поведение по умолчанию)
165
+
166
+ ```bash
167
+ npx zod-envkit
168
+ ```
169
+
170
+ или явно:
171
+
172
+ ```bash
173
+ npx zod-envkit generate
174
+ ```
175
+
176
+ ---
177
+
178
+ ### Просмотр текущего состояния окружения
179
+
180
+ Загружает `.env`, маскирует секреты и показывает читаемую таблицу.
181
+
182
+ ```bash
183
+ npx zod-envkit show
184
+ ```
185
+
186
+ Показывает:
187
+
188
+ * какие переменные обязательны
189
+ * какие присутствуют
190
+ * замаскированные значения секретов (`TOKEN`, `SECRET`, `*_KEY` и т.д.)
191
+
192
+ ---
193
+
194
+ ### Проверка обязательных переменных (удобно для CI)
195
+
196
+ ```bash
197
+ npx zod-envkit check
198
+ ```
199
+
200
+ * завершает процесс с кодом `1`, если отсутствует хотя бы одна обязательная переменная
201
+ * идеально для CI/CD пайплайнов и pre-deploy проверок
202
+
203
+ Пример шага в CI:
204
+
205
+ ```bash
206
+ npx zod-envkit check
207
+ ```
208
+
209
+ ---
210
+
211
+ ### Опции CLI
212
+
213
+ ```bash
214
+ zod-envkit --help
215
+ ```
216
+
217
+ Часто используемые флаги:
218
+
219
+ ```bash
220
+ zod-envkit show \
221
+ --config examples/env.meta.json \
222
+ --env-file .env
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Почему не просто dotenv?
228
+
229
+ `dotenv`:
230
+
231
+ * ❌ нет валидации
232
+ * ❌ нет типов
233
+ * ❌ нет документации
234
+ * ❌ нет проверок для CI
235
+
236
+ `zod-envkit`:
237
+
238
+ * ✅ валидация
239
+ * ✅ вывод типов для TypeScript
240
+ * ✅ документация
241
+ * ✅ CLI-инструменты
242
+
243
+ Они созданы для использования **вместе**.
244
+
245
+ ---
246
+
247
+ ## Принципы дизайна
248
+
249
+ * явная конфигурация вместо магии
250
+ * отсутствие привязки к фреймворкам
251
+ * маленький и предсказуемый API
252
+ * библиотека и CLI независимы, но дополняют друг друга
253
+ * переменные окружения — это runtime-контракт
254
+
255
+ ---
256
+
257
+ ## Roadmap
258
+
259
+ * [ ] проверка согласованности schema ↔ meta
260
+ * [ ] группировка секций в сгенерированной документации
261
+ * [ ] более аккуратный и человеко-понятный вывод ошибок
262
+ * [ ] экспорт в JSON Schema
263
+ * [ ] более строгие режимы валидации для production
264
+
265
+ ---
266
+
267
+ ## Для кого это?
268
+
269
+ * backend- и fullstack-проекты
270
+ * сервисы на Node.js и Bun
271
+ * CI/CD пайплайны
272
+ * команды, которые хотят, чтобы ошибки env находились рано, а не поздно
273
+
274
+ ---
275
+
276
+ ## Лицензия
277
+
278
+ MIT
package/README.md CHANGED
@@ -5,6 +5,9 @@
5
5
  </p>
6
6
  <br />
7
7
  <p>
8
+ <a href="https://github.com/nxtxe/zod-envkit">
9
+ <img src="https://github.com/nxtxe/zod-envkit/actions/workflows/release.yml/badge.svg" />
10
+ </a>
8
11
  <a href="https://www.npmjs.com/package/zod-envkit">
9
12
  <img src="https://img.shields.io/npm/v/zod-envkit.svg?maxAge=100" alt="npm version" />
10
13
  </a>
@@ -12,17 +15,25 @@
12
15
  <img src="https://img.shields.io/npm/dt/zod-envkit.svg?maxAge=100" alt="npm downloads" />
13
16
  </a>
14
17
  </p>
18
+
19
+ <p>
20
+ <a href="./README.md">English</a> |
21
+ <a href="./README.RU.md">Русский</a>
22
+ </p>
23
+
15
24
  </div>
16
25
 
17
26
  Type-safe environment variable validation and documentation using **Zod**.
18
27
 
19
- **zod-envkit** is a small library + CLI that helps you treat environment variables as an **explicit runtime contract**, not an implicit guessing game.
28
+ **zod-envkit** is a small library + CLI that helps you treat environment variables as an
29
+ **explicit runtime contract**, not an implicit guessing game.
20
30
 
21
31
  - validate `process.env` at startup
22
32
  - get fully typed environment variables
23
33
  - generate `.env.example`
24
34
  - generate readable documentation (`ENV.md`)
25
35
  - inspect and verify env state via CLI
36
+ - fail fast in CI/CD before deploy
26
37
 
27
38
  No cloud. No magic. Just code.
28
39
 
@@ -32,7 +43,7 @@ No cloud. No magic. Just code.
32
43
 
33
44
  Environment variables are critical, but usually poorly handled.
34
45
 
35
- The usual problems:
46
+ Typical problems:
36
47
  - `process.env` is just `string | undefined`
37
48
  - missing or invalid variables fail **at runtime**
38
49
  - `.env.example` and docs get outdated
@@ -52,7 +63,9 @@ The usual problems:
52
63
  npm install zod-envkit
53
64
  ````
54
65
 
55
- or
66
+ ```bash
67
+ yarn add zod-envkit
68
+ ```
56
69
 
57
70
  ```bash
58
71
  pnpm add zod-envkit
@@ -67,14 +80,18 @@ Create a single file responsible for loading and validating env.
67
80
  ```ts
68
81
  import "dotenv/config";
69
82
  import { z } from "zod";
70
- import { loadEnv, formatZodError } from "zod-envkit";
83
+ import { loadEnv, mustLoadEnv, formatZodError } from "zod-envkit";
71
84
 
72
85
  const EnvSchema = z.object({
73
86
  NODE_ENV: z.enum(["development", "test", "production"]),
74
87
  PORT: z.coerce.number().int().min(1).max(65535),
75
88
  DATABASE_URL: z.string().url(),
76
89
  });
90
+ ```
91
+
92
+ ### Option 1 — safe mode (no throw)
77
93
 
94
+ ```ts
78
95
  const result = loadEnv(EnvSchema);
79
96
 
80
97
  if (!result.ok) {
@@ -85,6 +102,12 @@ if (!result.ok) {
85
102
  export const env = result.env;
86
103
  ```
87
104
 
105
+ ### Option 2 — fail-fast (recommended)
106
+
107
+ ```ts
108
+ export const env = mustLoadEnv(EnvSchema);
109
+ ```
110
+
88
111
  Now:
89
112
 
90
113
  * `env.PORT` is a **number**
@@ -155,11 +178,11 @@ Loads `.env`, masks secrets, and displays a readable table.
155
178
  npx zod-envkit show
156
179
  ```
157
180
 
158
- Example output:
181
+ Shows:
159
182
 
160
183
  * which variables are required
161
184
  * which are present
162
- * masked values for secrets
185
+ * masked values for secrets (`TOKEN`, `SECRET`, `*_KEY`, etc.)
163
186
 
164
187
  ---
165
188
 
@@ -170,7 +193,13 @@ npx zod-envkit check
170
193
  ```
171
194
 
172
195
  * exits with code `1` if any required variable is missing
173
- * perfect for CI/CD pipelines and pre-deploy checks
196
+ * ideal for CI/CD pipelines and pre-deploy checks
197
+
198
+ Example CI step:
199
+
200
+ ```bash
201
+ npx zod-envkit check
202
+ ```
174
203
 
175
204
  ---
176
205
 
@@ -242,4 +271,3 @@ They are designed to be used **together**.
242
271
  ## License
243
272
 
244
273
  MIT
245
-
package/dist/cli.cjs CHANGED
@@ -29,17 +29,56 @@ var import_node_path = __toESM(require("path"), 1);
29
29
  var import_commander = require("commander");
30
30
  var import_dotenv = __toESM(require("dotenv"), 1);
31
31
 
32
+ // src/messages.ts
33
+ var messages = {
34
+ en: {
35
+ META_NOT_FOUND: "env meta file not found.",
36
+ META_TRIED: "Tried:",
37
+ META_TIP: "Tip:",
38
+ GENERATED: "Generated: {example}, {docs}",
39
+ ENV_OK: "Environment looks good.",
40
+ MISSING_ENV: "Missing required environment variables:",
41
+ META_PARSE_FAILED: "Failed to read/parse env meta file:"
42
+ },
43
+ ru: {
44
+ META_NOT_FOUND: "\u0424\u0430\u0439\u043B env.meta.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.",
45
+ META_TRIED: "\u041F\u0440\u043E\u0431\u043E\u0432\u0430\u043B\u0438:",
46
+ META_TIP: "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430:",
47
+ GENERATED: "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E: {example}, {docs}",
48
+ ENV_OK: "\u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u0432 \u043F\u043E\u0440\u044F\u0434\u043A\u0435.",
49
+ MISSING_ENV: "\u041E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0442 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F:",
50
+ META_PARSE_FAILED: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C/\u0440\u0430\u0441\u043F\u0430\u0440\u0441\u0438\u0442\u044C env meta \u0444\u0430\u0439\u043B:"
51
+ }
52
+ };
53
+
54
+ // src/i18n.ts
55
+ function resolveLang(cliLang) {
56
+ if (cliLang === "ru" || cliLang === "en") return cliLang;
57
+ const envLang = process.env.LANG?.toLowerCase();
58
+ if (envLang?.startsWith("ru")) return "ru";
59
+ return "en";
60
+ }
61
+ function t(lang, key, vars) {
62
+ let text = messages[lang][key] ?? messages.en[key];
63
+ if (vars) {
64
+ for (const [k, v] of Object.entries(vars)) {
65
+ text = text.replace(`{${k}}`, v);
66
+ }
67
+ }
68
+ return text;
69
+ }
70
+
32
71
  // src/generate.ts
33
72
  function strLen(s) {
34
- return s.length;
73
+ return (s ?? "").length;
35
74
  }
36
75
  function padCenter(text, width) {
37
- const t = text ?? "";
38
- const diff = width - strLen(t);
39
- if (diff <= 0) return t;
76
+ const t2 = text ?? "";
77
+ const diff = width - strLen(t2);
78
+ if (diff <= 0) return t2;
40
79
  const left = Math.floor(diff / 2);
41
80
  const right = diff - left;
42
- return " ".repeat(left) + t + " ".repeat(right);
81
+ return " ".repeat(left) + t2 + " ".repeat(right);
43
82
  }
44
83
  function makeDivider(width, align) {
45
84
  if (width < 3) width = 3;
@@ -54,7 +93,7 @@ function generateEnvExample(meta) {
54
93
  lines.push(`${key}=${m.example ?? ""}`);
55
94
  lines.push("");
56
95
  }
57
- return lines.join("\n").trim() + "\n";
96
+ return lines.join("\n").trimEnd() + "\n";
58
97
  }
59
98
  function generateEnvDocs(meta) {
60
99
  const headers = ["Key", "Required", "Example", "Description"];
@@ -99,73 +138,83 @@ function generateEnvDocs(meta) {
99
138
  }
100
139
 
101
140
  // src/cli.ts
141
+ function fail(lang, key, details) {
142
+ console.error(`\u274C ${t(lang, key)}`);
143
+ if (details?.length) for (const d of details) console.error(d);
144
+ process.exit(1);
145
+ }
102
146
  function maskValue(key, value) {
103
147
  const k = key.toUpperCase();
104
- const isSecret = k.includes("TOKEN") || k.includes("SECRET") || k.includes("PASSWORD") || k.includes("API_KEY") || k.includes("KEY");
148
+ const isSecret = k.includes("TOKEN") || k.includes("SECRET") || k.includes("PASSWORD") || k.includes("API_KEY") || k.endsWith("_KEY") || k.includes("PRIVATE");
105
149
  if (!isSecret) return value;
106
150
  if (value.length <= 4) return "*".repeat(value.length);
107
151
  return value.slice(0, 2) + "*".repeat(Math.max(1, value.length - 4)) + value.slice(-2);
108
152
  }
109
153
  function padCenter2(text, width) {
110
- const t = text ?? "";
111
- const diff = width - t.length;
112
- if (diff <= 0) return t;
154
+ const v = text ?? "";
155
+ const diff = width - v.length;
156
+ if (diff <= 0) return v;
113
157
  const left = Math.floor(diff / 2);
114
158
  const right = diff - left;
115
- return " ".repeat(left) + t + " ".repeat(right);
159
+ return " ".repeat(left) + v + " ".repeat(right);
116
160
  }
117
161
  function printTable(rows, headers) {
118
162
  const widths = {};
119
163
  for (const h of headers) widths[h] = h.length;
120
164
  for (const row of rows) {
121
- for (const h of headers) {
122
- widths[h] = Math.max(widths[h], (row[h] ?? "").length);
123
- }
165
+ for (const h of headers) widths[h] = Math.max(widths[h], (row[h] ?? "").length);
124
166
  }
125
167
  for (const h of headers) widths[h] += 2;
126
- const line = "|" + headers.map((h) => " " + padCenter2(h, widths[h]) + " ").join("|") + "|";
127
- const sep = "|" + headers.map((h) => ":" + "-".repeat(Math.max(3, widths[h])) + ":").join("|") + "|";
128
- console.log(line);
129
- console.log(sep);
168
+ const headerLine = "|" + headers.map((h) => " " + padCenter2(h, widths[h]) + " ").join("|") + "|";
169
+ const sepLine = "|" + headers.map((h) => ":" + "-".repeat(Math.max(3, widths[h])) + ":").join("|") + "|";
170
+ console.log(headerLine);
171
+ console.log(sepLine);
130
172
  for (const row of rows) {
131
- const rowLine = "|" + headers.map((h) => " " + padCenter2(row[h] ?? "", widths[h]) + " ").join("|") + "|";
132
- console.log(rowLine);
173
+ console.log("|" + headers.map((h) => " " + padCenter2(row[h] ?? "", widths[h]) + " ").join("|") + "|");
133
174
  }
134
175
  }
135
- function loadMeta(configFile) {
176
+ function loadDotEnv(envFile) {
177
+ import_dotenv.default.config({ path: import_node_path.default.resolve(process.cwd(), envFile), quiet: true });
178
+ }
179
+ function loadMeta(lang, configFile) {
136
180
  const cwd = process.cwd();
137
181
  const candidates = [
138
182
  import_node_path.default.resolve(cwd, configFile),
139
- // ./env.meta.json (по умолчанию)
140
183
  import_node_path.default.resolve(cwd, "examples", configFile),
141
- // ./examples/env.meta.json
142
184
  import_node_path.default.resolve(cwd, "examples", "env.meta.json")
143
- // явный fallback
144
185
  ];
145
- const configPath = candidates.find((p) => import_node_fs.default.existsSync(p));
146
- if (!configPath) {
147
- console.error("\u274C env meta file not found.");
148
- console.error("Tried:");
149
- for (const p of candidates) console.error(`- ${p}`);
150
- console.error("\nTip:");
151
- console.error(" zod-envkit show -c examples/env.meta.json");
152
- process.exit(1);
186
+ const found = candidates.find((p) => import_node_fs.default.existsSync(p));
187
+ if (!found) {
188
+ fail(lang, "META_NOT_FOUND", [
189
+ t(lang, "META_TRIED"),
190
+ ...candidates.map((p) => `- ${p}`),
191
+ "",
192
+ t(lang, "META_TIP"),
193
+ " npx zod-envkit show -c examples/env.meta.json"
194
+ ]);
195
+ }
196
+ const configPath = found;
197
+ try {
198
+ const raw = import_node_fs.default.readFileSync(configPath, "utf8");
199
+ return { meta: JSON.parse(raw), configPath };
200
+ } catch {
201
+ fail(lang, "META_PARSE_FAILED", [`- ${configPath}`]);
153
202
  }
154
- const raw = import_node_fs.default.readFileSync(configPath, "utf8");
155
- return JSON.parse(raw);
156
203
  }
157
204
  var program = new import_commander.Command();
158
- program.name("zod-envkit").description("Env docs + runtime checks for Node.js projects");
205
+ program.name("zod-envkit").description("Env docs + runtime checks for Node.js projects").showHelpAfterError().showSuggestionAfterError().option("--lang <lang>", "CLI language (en | ru)");
159
206
  program.command("generate").description("Generate .env.example and ENV.md from env.meta.json").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--out-example <file>", "Output file for .env.example", ".env.example").option("--out-docs <file>", "Output file for docs", "ENV.md").action((opts) => {
160
- const meta = loadMeta(opts.config);
207
+ const lang = resolveLang(program.opts().lang);
208
+ const { meta } = loadMeta(lang, opts.config);
161
209
  import_node_fs.default.writeFileSync(opts.outExample, generateEnvExample(meta), "utf8");
162
210
  import_node_fs.default.writeFileSync(opts.outDocs, generateEnvDocs(meta), "utf8");
163
- console.log(`Generated: ${opts.outExample}, ${opts.outDocs}`);
211
+ console.log(t(lang, "GENERATED", { example: opts.outExample, docs: opts.outDocs }));
212
+ process.exit(0);
164
213
  });
165
- program.option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--out-example <file>", "Output file for .env.example", ".env.example").option("--out-docs <file>", "Output file for docs", "ENV.md");
166
214
  program.command("show").description("Show current env status (loads .env, masks secrets)").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--env-file <file>", "Path to .env file", ".env").action((opts) => {
167
- import_dotenv.default.config({ path: import_node_path.default.resolve(process.cwd(), opts.envFile), quiet: true });
168
- const meta = loadMeta(opts.config);
215
+ const lang = resolveLang(program.opts().lang);
216
+ loadDotEnv(opts.envFile);
217
+ const { meta } = loadMeta(lang, opts.config);
169
218
  const rows = Object.entries(meta).map(([key, m]) => {
170
219
  const required = m.required === false ? "no" : "yes";
171
220
  const raw = process.env[key];
@@ -180,10 +229,12 @@ program.command("show").description("Show current env status (loads .env, masks
180
229
  };
181
230
  });
182
231
  printTable(rows, ["Key", "Required", "Present", "Value", "Description"]);
232
+ process.exit(0);
183
233
  });
184
234
  program.command("check").description("Exit with code 1 if any required env var is missing (loads .env)").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--env-file <file>", "Path to .env file", ".env").action((opts) => {
185
- import_dotenv.default.config({ path: import_node_path.default.resolve(process.cwd(), opts.envFile) });
186
- const meta = loadMeta(opts.config);
235
+ const lang = resolveLang(program.opts().lang);
236
+ loadDotEnv(opts.envFile);
237
+ const { meta } = loadMeta(lang, opts.config);
187
238
  const missing = [];
188
239
  for (const [key, m] of Object.entries(meta)) {
189
240
  const required = m.required !== false;
@@ -192,18 +243,14 @@ program.command("check").description("Exit with code 1 if any required env var i
192
243
  if (!raw || raw.length === 0) missing.push(key);
193
244
  }
194
245
  if (missing.length) {
195
- console.error("\u274C Missing required environment variables:");
246
+ console.error(`\u274C ${t(lang, "MISSING_ENV")}`);
196
247
  for (const k of missing) console.error(`- ${k}`);
197
248
  process.exit(1);
198
249
  }
199
- console.log("\u2705 Environment looks good.");
250
+ console.log(`\u2705 ${t(lang, "ENV_OK")}`);
251
+ process.exit(0);
200
252
  });
253
+ var known = /* @__PURE__ */ new Set(["generate", "show", "check", "-h", "--help", "-V", "--version", "--lang"]);
254
+ var hasCommand = process.argv.slice(2).some((a) => known.has(a));
255
+ if (!hasCommand) process.argv.splice(2, 0, "generate");
201
256
  program.parse(process.argv);
202
- var hasSubcommand = process.argv.slice(2).some((a) => ["generate", "show", "check"].includes(a));
203
- if (!hasSubcommand) {
204
- const opts = program.opts();
205
- const meta = loadMeta(opts.config ?? "env.meta.json");
206
- import_node_fs.default.writeFileSync(opts.outExample ?? ".env.example", generateEnvExample(meta), "utf8");
207
- import_node_fs.default.writeFileSync(opts.outDocs ?? "ENV.md", generateEnvDocs(meta), "utf8");
208
- console.log(`Generated: ${opts.outExample ?? ".env.example"}, ${opts.outDocs ?? "ENV.md"}`);
209
- }
package/dist/cli.js CHANGED
@@ -6,17 +6,56 @@ import path from "path";
6
6
  import { Command } from "commander";
7
7
  import dotenv from "dotenv";
8
8
 
9
+ // src/messages.ts
10
+ var messages = {
11
+ en: {
12
+ META_NOT_FOUND: "env meta file not found.",
13
+ META_TRIED: "Tried:",
14
+ META_TIP: "Tip:",
15
+ GENERATED: "Generated: {example}, {docs}",
16
+ ENV_OK: "Environment looks good.",
17
+ MISSING_ENV: "Missing required environment variables:",
18
+ META_PARSE_FAILED: "Failed to read/parse env meta file:"
19
+ },
20
+ ru: {
21
+ META_NOT_FOUND: "\u0424\u0430\u0439\u043B env.meta.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.",
22
+ META_TRIED: "\u041F\u0440\u043E\u0431\u043E\u0432\u0430\u043B\u0438:",
23
+ META_TIP: "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430:",
24
+ GENERATED: "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E: {example}, {docs}",
25
+ ENV_OK: "\u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u0432 \u043F\u043E\u0440\u044F\u0434\u043A\u0435.",
26
+ MISSING_ENV: "\u041E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0442 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F:",
27
+ META_PARSE_FAILED: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C/\u0440\u0430\u0441\u043F\u0430\u0440\u0441\u0438\u0442\u044C env meta \u0444\u0430\u0439\u043B:"
28
+ }
29
+ };
30
+
31
+ // src/i18n.ts
32
+ function resolveLang(cliLang) {
33
+ if (cliLang === "ru" || cliLang === "en") return cliLang;
34
+ const envLang = process.env.LANG?.toLowerCase();
35
+ if (envLang?.startsWith("ru")) return "ru";
36
+ return "en";
37
+ }
38
+ function t(lang, key, vars) {
39
+ let text = messages[lang][key] ?? messages.en[key];
40
+ if (vars) {
41
+ for (const [k, v] of Object.entries(vars)) {
42
+ text = text.replace(`{${k}}`, v);
43
+ }
44
+ }
45
+ return text;
46
+ }
47
+
9
48
  // src/generate.ts
10
49
  function strLen(s) {
11
- return s.length;
50
+ return (s ?? "").length;
12
51
  }
13
52
  function padCenter(text, width) {
14
- const t = text ?? "";
15
- const diff = width - strLen(t);
16
- if (diff <= 0) return t;
53
+ const t2 = text ?? "";
54
+ const diff = width - strLen(t2);
55
+ if (diff <= 0) return t2;
17
56
  const left = Math.floor(diff / 2);
18
57
  const right = diff - left;
19
- return " ".repeat(left) + t + " ".repeat(right);
58
+ return " ".repeat(left) + t2 + " ".repeat(right);
20
59
  }
21
60
  function makeDivider(width, align) {
22
61
  if (width < 3) width = 3;
@@ -31,7 +70,7 @@ function generateEnvExample(meta) {
31
70
  lines.push(`${key}=${m.example ?? ""}`);
32
71
  lines.push("");
33
72
  }
34
- return lines.join("\n").trim() + "\n";
73
+ return lines.join("\n").trimEnd() + "\n";
35
74
  }
36
75
  function generateEnvDocs(meta) {
37
76
  const headers = ["Key", "Required", "Example", "Description"];
@@ -76,73 +115,83 @@ function generateEnvDocs(meta) {
76
115
  }
77
116
 
78
117
  // src/cli.ts
118
+ function fail(lang, key, details) {
119
+ console.error(`\u274C ${t(lang, key)}`);
120
+ if (details?.length) for (const d of details) console.error(d);
121
+ process.exit(1);
122
+ }
79
123
  function maskValue(key, value) {
80
124
  const k = key.toUpperCase();
81
- const isSecret = k.includes("TOKEN") || k.includes("SECRET") || k.includes("PASSWORD") || k.includes("API_KEY") || k.includes("KEY");
125
+ const isSecret = k.includes("TOKEN") || k.includes("SECRET") || k.includes("PASSWORD") || k.includes("API_KEY") || k.endsWith("_KEY") || k.includes("PRIVATE");
82
126
  if (!isSecret) return value;
83
127
  if (value.length <= 4) return "*".repeat(value.length);
84
128
  return value.slice(0, 2) + "*".repeat(Math.max(1, value.length - 4)) + value.slice(-2);
85
129
  }
86
130
  function padCenter2(text, width) {
87
- const t = text ?? "";
88
- const diff = width - t.length;
89
- if (diff <= 0) return t;
131
+ const v = text ?? "";
132
+ const diff = width - v.length;
133
+ if (diff <= 0) return v;
90
134
  const left = Math.floor(diff / 2);
91
135
  const right = diff - left;
92
- return " ".repeat(left) + t + " ".repeat(right);
136
+ return " ".repeat(left) + v + " ".repeat(right);
93
137
  }
94
138
  function printTable(rows, headers) {
95
139
  const widths = {};
96
140
  for (const h of headers) widths[h] = h.length;
97
141
  for (const row of rows) {
98
- for (const h of headers) {
99
- widths[h] = Math.max(widths[h], (row[h] ?? "").length);
100
- }
142
+ for (const h of headers) widths[h] = Math.max(widths[h], (row[h] ?? "").length);
101
143
  }
102
144
  for (const h of headers) widths[h] += 2;
103
- const line = "|" + headers.map((h) => " " + padCenter2(h, widths[h]) + " ").join("|") + "|";
104
- const sep = "|" + headers.map((h) => ":" + "-".repeat(Math.max(3, widths[h])) + ":").join("|") + "|";
105
- console.log(line);
106
- console.log(sep);
145
+ const headerLine = "|" + headers.map((h) => " " + padCenter2(h, widths[h]) + " ").join("|") + "|";
146
+ const sepLine = "|" + headers.map((h) => ":" + "-".repeat(Math.max(3, widths[h])) + ":").join("|") + "|";
147
+ console.log(headerLine);
148
+ console.log(sepLine);
107
149
  for (const row of rows) {
108
- const rowLine = "|" + headers.map((h) => " " + padCenter2(row[h] ?? "", widths[h]) + " ").join("|") + "|";
109
- console.log(rowLine);
150
+ console.log("|" + headers.map((h) => " " + padCenter2(row[h] ?? "", widths[h]) + " ").join("|") + "|");
110
151
  }
111
152
  }
112
- function loadMeta(configFile) {
153
+ function loadDotEnv(envFile) {
154
+ dotenv.config({ path: path.resolve(process.cwd(), envFile), quiet: true });
155
+ }
156
+ function loadMeta(lang, configFile) {
113
157
  const cwd = process.cwd();
114
158
  const candidates = [
115
159
  path.resolve(cwd, configFile),
116
- // ./env.meta.json (по умолчанию)
117
160
  path.resolve(cwd, "examples", configFile),
118
- // ./examples/env.meta.json
119
161
  path.resolve(cwd, "examples", "env.meta.json")
120
- // явный fallback
121
162
  ];
122
- const configPath = candidates.find((p) => fs.existsSync(p));
123
- if (!configPath) {
124
- console.error("\u274C env meta file not found.");
125
- console.error("Tried:");
126
- for (const p of candidates) console.error(`- ${p}`);
127
- console.error("\nTip:");
128
- console.error(" zod-envkit show -c examples/env.meta.json");
129
- process.exit(1);
163
+ const found = candidates.find((p) => fs.existsSync(p));
164
+ if (!found) {
165
+ fail(lang, "META_NOT_FOUND", [
166
+ t(lang, "META_TRIED"),
167
+ ...candidates.map((p) => `- ${p}`),
168
+ "",
169
+ t(lang, "META_TIP"),
170
+ " npx zod-envkit show -c examples/env.meta.json"
171
+ ]);
172
+ }
173
+ const configPath = found;
174
+ try {
175
+ const raw = fs.readFileSync(configPath, "utf8");
176
+ return { meta: JSON.parse(raw), configPath };
177
+ } catch {
178
+ fail(lang, "META_PARSE_FAILED", [`- ${configPath}`]);
130
179
  }
131
- const raw = fs.readFileSync(configPath, "utf8");
132
- return JSON.parse(raw);
133
180
  }
134
181
  var program = new Command();
135
- program.name("zod-envkit").description("Env docs + runtime checks for Node.js projects");
182
+ program.name("zod-envkit").description("Env docs + runtime checks for Node.js projects").showHelpAfterError().showSuggestionAfterError().option("--lang <lang>", "CLI language (en | ru)");
136
183
  program.command("generate").description("Generate .env.example and ENV.md from env.meta.json").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--out-example <file>", "Output file for .env.example", ".env.example").option("--out-docs <file>", "Output file for docs", "ENV.md").action((opts) => {
137
- const meta = loadMeta(opts.config);
184
+ const lang = resolveLang(program.opts().lang);
185
+ const { meta } = loadMeta(lang, opts.config);
138
186
  fs.writeFileSync(opts.outExample, generateEnvExample(meta), "utf8");
139
187
  fs.writeFileSync(opts.outDocs, generateEnvDocs(meta), "utf8");
140
- console.log(`Generated: ${opts.outExample}, ${opts.outDocs}`);
188
+ console.log(t(lang, "GENERATED", { example: opts.outExample, docs: opts.outDocs }));
189
+ process.exit(0);
141
190
  });
142
- program.option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--out-example <file>", "Output file for .env.example", ".env.example").option("--out-docs <file>", "Output file for docs", "ENV.md");
143
191
  program.command("show").description("Show current env status (loads .env, masks secrets)").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--env-file <file>", "Path to .env file", ".env").action((opts) => {
144
- dotenv.config({ path: path.resolve(process.cwd(), opts.envFile), quiet: true });
145
- const meta = loadMeta(opts.config);
192
+ const lang = resolveLang(program.opts().lang);
193
+ loadDotEnv(opts.envFile);
194
+ const { meta } = loadMeta(lang, opts.config);
146
195
  const rows = Object.entries(meta).map(([key, m]) => {
147
196
  const required = m.required === false ? "no" : "yes";
148
197
  const raw = process.env[key];
@@ -157,10 +206,12 @@ program.command("show").description("Show current env status (loads .env, masks
157
206
  };
158
207
  });
159
208
  printTable(rows, ["Key", "Required", "Present", "Value", "Description"]);
209
+ process.exit(0);
160
210
  });
161
211
  program.command("check").description("Exit with code 1 if any required env var is missing (loads .env)").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--env-file <file>", "Path to .env file", ".env").action((opts) => {
162
- dotenv.config({ path: path.resolve(process.cwd(), opts.envFile) });
163
- const meta = loadMeta(opts.config);
212
+ const lang = resolveLang(program.opts().lang);
213
+ loadDotEnv(opts.envFile);
214
+ const { meta } = loadMeta(lang, opts.config);
164
215
  const missing = [];
165
216
  for (const [key, m] of Object.entries(meta)) {
166
217
  const required = m.required !== false;
@@ -169,18 +220,14 @@ program.command("check").description("Exit with code 1 if any required env var i
169
220
  if (!raw || raw.length === 0) missing.push(key);
170
221
  }
171
222
  if (missing.length) {
172
- console.error("\u274C Missing required environment variables:");
223
+ console.error(`\u274C ${t(lang, "MISSING_ENV")}`);
173
224
  for (const k of missing) console.error(`- ${k}`);
174
225
  process.exit(1);
175
226
  }
176
- console.log("\u2705 Environment looks good.");
227
+ console.log(`\u2705 ${t(lang, "ENV_OK")}`);
228
+ process.exit(0);
177
229
  });
230
+ var known = /* @__PURE__ */ new Set(["generate", "show", "check", "-h", "--help", "-V", "--version", "--lang"]);
231
+ var hasCommand = process.argv.slice(2).some((a) => known.has(a));
232
+ if (!hasCommand) process.argv.splice(2, 0, "generate");
178
233
  program.parse(process.argv);
179
- var hasSubcommand = process.argv.slice(2).some((a) => ["generate", "show", "check"].includes(a));
180
- if (!hasSubcommand) {
181
- const opts = program.opts();
182
- const meta = loadMeta(opts.config ?? "env.meta.json");
183
- fs.writeFileSync(opts.outExample ?? ".env.example", generateEnvExample(meta), "utf8");
184
- fs.writeFileSync(opts.outDocs ?? "ENV.md", generateEnvDocs(meta), "utf8");
185
- console.log(`Generated: ${opts.outExample ?? ".env.example"}, ${opts.outDocs ?? "ENV.md"}`);
186
- }
package/dist/index.cjs CHANGED
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  formatZodError: () => formatZodError,
24
- loadEnv: () => loadEnv
24
+ loadEnv: () => loadEnv,
25
+ mustLoadEnv: () => mustLoadEnv
25
26
  });
26
27
  module.exports = __toCommonJS(index_exports);
27
28
  function loadEnv(schema, opts) {
@@ -30,11 +31,17 @@ function loadEnv(schema, opts) {
30
31
  if (opts?.throwOnError) throw parsed.error;
31
32
  return { ok: false, error: parsed.error };
32
33
  }
34
+ function mustLoadEnv(schema) {
35
+ const res = loadEnv(schema);
36
+ if (res.ok) return res.env;
37
+ throw res.error;
38
+ }
33
39
  function formatZodError(err) {
34
40
  return err.issues.map((i) => `- ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
35
41
  }
36
42
  // Annotate the CommonJS export names for ESM import in node:
37
43
  0 && (module.exports = {
38
44
  formatZodError,
39
- loadEnv
45
+ loadEnv,
46
+ mustLoadEnv
40
47
  });
package/dist/index.d.cts CHANGED
@@ -1,15 +1,28 @@
1
1
  import { z } from 'zod';
2
2
 
3
- type EnvResult<T extends z.ZodTypeAny> = z.infer<T>;
4
- declare function loadEnv<T extends z.ZodTypeAny>(schema: T, opts?: {
3
+ type LoadEnvOptions = {
4
+ /**
5
+ * If true, throws ZodError instead of returning { ok: false }
6
+ */
5
7
  throwOnError?: boolean;
6
- }): {
8
+ };
9
+ type LoadEnvOk<T extends z.ZodTypeAny> = {
7
10
  ok: true;
8
11
  env: z.infer<T>;
9
- } | {
12
+ };
13
+ type LoadEnvFail = {
10
14
  ok: false;
11
15
  error: z.ZodError;
12
16
  };
17
+ declare function loadEnv<T extends z.ZodTypeAny>(schema: T, opts?: LoadEnvOptions): LoadEnvOk<T> | LoadEnvFail;
18
+ /**
19
+ * Convenience wrapper around loadEnv(schema, { throwOnError: true })
20
+ * Returns typed env or throws ZodError
21
+ */
22
+ declare function mustLoadEnv<T extends z.ZodTypeAny>(schema: T): z.infer<T>;
23
+ /**
24
+ * Human-friendly ZodError formatting (one issue per line)
25
+ */
13
26
  declare function formatZodError(err: z.ZodError): string;
14
27
 
15
- export { type EnvResult, formatZodError, loadEnv };
28
+ export { type LoadEnvFail, type LoadEnvOk, type LoadEnvOptions, formatZodError, loadEnv, mustLoadEnv };
package/dist/index.d.ts CHANGED
@@ -1,15 +1,28 @@
1
1
  import { z } from 'zod';
2
2
 
3
- type EnvResult<T extends z.ZodTypeAny> = z.infer<T>;
4
- declare function loadEnv<T extends z.ZodTypeAny>(schema: T, opts?: {
3
+ type LoadEnvOptions = {
4
+ /**
5
+ * If true, throws ZodError instead of returning { ok: false }
6
+ */
5
7
  throwOnError?: boolean;
6
- }): {
8
+ };
9
+ type LoadEnvOk<T extends z.ZodTypeAny> = {
7
10
  ok: true;
8
11
  env: z.infer<T>;
9
- } | {
12
+ };
13
+ type LoadEnvFail = {
10
14
  ok: false;
11
15
  error: z.ZodError;
12
16
  };
17
+ declare function loadEnv<T extends z.ZodTypeAny>(schema: T, opts?: LoadEnvOptions): LoadEnvOk<T> | LoadEnvFail;
18
+ /**
19
+ * Convenience wrapper around loadEnv(schema, { throwOnError: true })
20
+ * Returns typed env or throws ZodError
21
+ */
22
+ declare function mustLoadEnv<T extends z.ZodTypeAny>(schema: T): z.infer<T>;
23
+ /**
24
+ * Human-friendly ZodError formatting (one issue per line)
25
+ */
13
26
  declare function formatZodError(err: z.ZodError): string;
14
27
 
15
- export { type EnvResult, formatZodError, loadEnv };
28
+ export { type LoadEnvFail, type LoadEnvOk, type LoadEnvOptions, formatZodError, loadEnv, mustLoadEnv };
package/dist/index.js CHANGED
@@ -5,10 +5,16 @@ function loadEnv(schema, opts) {
5
5
  if (opts?.throwOnError) throw parsed.error;
6
6
  return { ok: false, error: parsed.error };
7
7
  }
8
+ function mustLoadEnv(schema) {
9
+ const res = loadEnv(schema);
10
+ if (res.ok) return res.env;
11
+ throw res.error;
12
+ }
8
13
  function formatZodError(err) {
9
14
  return err.issues.map((i) => `- ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
10
15
  }
11
16
  export {
12
17
  formatZodError,
13
- loadEnv
18
+ loadEnv,
19
+ mustLoadEnv
14
20
  };
package/package.json CHANGED
@@ -1,7 +1,24 @@
1
1
  {
2
2
  "name": "zod-envkit",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Validate environment variables with Zod and generate .env.example",
5
+ "license": "MIT",
6
+ "author": "",
7
+ "homepage": "https://github.com/nxtxe/zod-envkit#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/nxtxe/zod-envkit.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/nxtxe/zod-envkit/issues"
14
+ },
15
+ "keywords": [
16
+ "env",
17
+ "dotenv",
18
+ "zod",
19
+ "validation",
20
+ "cli"
21
+ ],
5
22
  "type": "module",
6
23
  "main": "dist/index.cjs",
7
24
  "module": "dist/index.js",
@@ -22,39 +39,36 @@
22
39
  "CHANGELOG.md",
23
40
  "LICENSE"
24
41
  ],
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/nxtxe/zod-envkit.git"
28
- },
29
- "bugs": {
30
- "url": "https://github.com/nxtxe/zod-envkit/issues"
31
- },
32
- "homepage": "https://github.com/nxtxe/zod-envkit#readme",
33
42
  "scripts": {
34
43
  "build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts",
35
44
  "dev": "tsup src/index.ts src/cli.ts --watch --dts",
36
45
  "test": "vitest run",
37
- "prepublishOnly": "npm run test && npm run build"
46
+ "release": "semantic-release"
38
47
  },
39
- "keywords": [
40
- "env",
41
- "dotenv",
42
- "zod",
43
- "validation",
44
- "cli"
45
- ],
46
- "author": "",
47
- "license": "MIT",
48
48
  "dependencies": {
49
49
  "commander": "^13.1.0",
50
50
  "dotenv": "^17.2.3",
51
51
  "zod": "^4.3.6"
52
52
  },
53
53
  "devDependencies": {
54
+ "@semantic-release/changelog": "^6.0.3",
55
+ "@semantic-release/commit-analyzer": "^11.1.0",
56
+ "@semantic-release/git": "^10.0.1",
57
+ "@semantic-release/github": "^9.2.6",
58
+ "@semantic-release/npm": "^11.0.3",
59
+ "@semantic-release/release-notes-generator": "^12.1.0",
54
60
  "@types/node": "^25.0.10",
55
61
  "eslint": "^9.39.2",
62
+ "semantic-release": "^22.0.12",
56
63
  "tsup": "^8.5.1",
57
64
  "typescript": "^5.9.3",
58
65
  "vitest": "^3.2.4"
59
- }
66
+ },
67
+ "publishConfig": {
68
+ "access": "public"
69
+ },
70
+ "engines": {
71
+ "node": ">=18"
72
+ },
73
+ "packageManager": "pnpm@9.15.9"
60
74
  }