zod-envkit 1.2.0 → 1.2.2

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,40 +1,70 @@
1
- # [1.2.0](https://github.com/nxtxe/zod-envkit/compare/v1.1.2...v1.2.0) (2026-02-16)
1
+ ## [1.2.2](https://github.com/nxtxe/zod-envkit/compare/v1.2.1...v1.2.2) (2026-02-16)
2
2
 
3
3
 
4
- ### Features
4
+ ### Bug Fixes
5
5
 
6
- * stabilize preprod test suite, CLI E2E coverage, and CI pipeline ([463ea6e](https://github.com/nxtxe/zod-envkit/commit/463ea6e4676662dbbbe82bc79e0d85b079b524f8))
6
+ * regenerate lockfile (remove stale link: specifier) ([19d243b](https://github.com/nxtxe/zod-envkit/commit/19d243b411f03c2044fae7f59e0c66676d1d1e90))
7
+ * remove self link dependency from optionalDependencies ([3fd0568](https://github.com/nxtxe/zod-envkit/commit/3fd0568f327d112f496e357962810efdb06deaee))
7
8
 
8
- ## [1.1.2](https://github.com/nxtxe/zod-envkit/compare/v1.1.1...v1.1.2) (2026-01-29)
9
+ ## [1.2.1](https://github.com/nxtxe/zod-envkit/compare/v1.2.0...v1.2.1) (2026-02-16)
9
10
 
10
11
 
11
12
  ### Bug Fixes
12
13
 
13
- * stabilize strict env validation and CI ([3055f18](https://github.com/nxtxe/zod-envkit/commit/3055f1859479212fbdc105cb0f183c6018df2376))
14
- * tests ([a691dab](https://github.com/nxtxe/zod-envkit/commit/a691dab0f207685bd96688e36aaabcdb5784f956))
14
+ * strengthen CLI + env contract guarantees ([d516fbb](https://github.com/nxtxe/zod-envkit/commit/d516fbb4c8248745f7d74c1f07defaf4529c33ec))
15
15
 
16
16
  # Changelog
17
17
 
18
18
  All notable changes to this project will be documented in this file.
19
19
  This project follows [Semantic Versioning](https://semver.org/).
20
20
 
21
- ## [1.1.2](https://github.com/nxtxe/zod-envkit/compare/v1.1.1...v1.1.2) (2026-01-29)
21
+ ## [1.2.0] 2026-02-16
22
22
 
23
- ### Bug Fixes
23
+ ### Added
24
24
 
25
- * **cli:** fix strict mode to validate only dotenv-loaded variables
26
- * **cli:** stabilize option validation and documentation links
27
- * **ci:** ensure CLI smoke tests and docs build in CI
28
- * internal cleanup to align behavior with documented API contract
25
+ * Preprod test suite (smoke, contract, CLI E2E, robustness)
26
+ * Strict CLI isolation for CI (no host env leakage)
27
+ * Robust helpers for CLI E2E testing
28
+ * Internal contract tests for public exports and exit codes
29
29
 
30
+ ### Changed
30
31
 
31
- ## [1.1.1](https://github.com/nxtxe/zod-envkit/compare/v1.1.0...v1.1.1) (2026-01-29)
32
+ * `init --from-meta` now generates `.env.example` from `env.meta.json`
33
+ * Test architecture restructured under `tests/test/preprod`
34
+ * CI workflow aligned with pnpm-only setup
35
+ * Improved stability of strict mode behavior
32
36
 
37
+ ### Stability
33
38
 
34
- ### Bug Fixes
39
+ * Public API (`loadEnv`, `mustLoadEnv`, CLI flags) treated as stable contract
40
+ * CLI exit codes verified by contract tests
41
+ * Documentation build enforced in CI
35
42
 
36
- * **cli:** stabilize options validation and docs links ([640b12b](https://github.com/nxtxe/zod-envkit/commit/640b12bf95304da40c998374ba7ed08b7400e88d))
37
- * stabilize public API, CLI behavior, and documentation ([4604298](https://github.com/nxtxe/zod-envkit/commit/46042981f146f021919da8a4a713b2d251c542d9))
43
+ [1.2.0]: https://www.npmjs.com/package/zod-envkit/v/1.2.0
44
+
45
+ ---
46
+
47
+ ## [1.1.2] – 2026-01-29
48
+
49
+ ### Fixed
50
+
51
+ * Strict mode now validates only dotenv-loaded variables (prevents host env leakage)
52
+ * Options validation and docs links stabilized
53
+ * CI updated to include CLI smoke tests and docs build
54
+ * Internal cleanup to align behavior with documented API contract
55
+
56
+ [1.1.2]: https://www.npmjs.com/package/zod-envkit/v/1.1.2
57
+
58
+ ---
59
+
60
+ ## [1.1.1] – 2026-01-29
61
+
62
+ ### Fixed
63
+
64
+ * Options validation and docs links stabilized
65
+ * Public API, CLI behavior, and documentation hardened
66
+
67
+ [1.1.1]: https://www.npmjs.com/package/zod-envkit/v/1.1.1
38
68
 
39
69
  ---
40
70
 
@@ -43,39 +73,31 @@ This project follows [Semantic Versioning](https://semver.org/).
43
73
  ### Added
44
74
 
45
75
  * New `zod-envkit init` command to bootstrap configuration:
46
-
47
76
  * generate `env.meta.json` from `.env.example`
48
77
  * generate `.env.example` from existing `env.meta.json`
49
78
  * Support for loading multiple dotenv files via `--dotenv`
50
-
51
79
  * example: `.env,.env.local,.env.production`
52
80
  * files are loaded in order with override semantics
53
81
  * Documentation generation in multiple formats:
54
-
55
82
  * `md` (default)
56
83
  * `json`
57
84
  * `yaml`
58
85
  * Sorting options for docs and CLI output via `--sort`:
59
-
60
86
  * `alpha`
61
87
  * `required-first`
62
88
  * `none`
63
89
  * Strict CI validation via `zod-envkit check --strict`
64
-
65
90
  * fails on missing **and** unknown environment variables
66
91
  * Configurable secret masking in `zod-envkit show`:
67
-
68
92
  * `--mask-mode partial | full | none`
69
93
  * `--no-mask` alias
70
94
  * Grouping support for environment variables via `meta.group`
71
95
  * Extended env metadata fields:
72
-
73
96
  * `default`
74
97
  * `deprecated`
75
98
  * `since`
76
99
  * `link`
77
100
  * New public core utilities:
78
-
79
101
  * `getMissingEnv`
80
102
  * `getUnknownEnv`
81
103
  * `checkEnv`
@@ -84,16 +106,13 @@ This project follows [Semantic Versioning](https://semver.org/).
84
106
  ### Changed
85
107
 
86
108
  * CLI architecture refactored into a modular structure (`src/cli/*`)
87
-
88
109
  * no breaking behavior changes
89
110
  * Documentation generator improved:
90
-
91
111
  * grouped sections
92
112
  * width-aware, centered markdown tables
93
113
  * extended metadata columns
94
114
  * Public API explicitly documented and treated as a stable contract
95
115
  * CLI default behavior preserved:
96
-
97
116
  * running `zod-envkit` without subcommand defaults to `generate`
98
117
 
99
118
  ### Fixed
@@ -119,13 +138,11 @@ This project follows [Semantic Versioning](https://semver.org/).
119
138
 
120
139
  * Public API stabilized and separated from CLI internals
121
140
  * CLI error handling improved:
122
-
123
141
  * no stack traces for user errors
124
142
  * consistent human-readable messages
125
143
  * strict exit codes
126
144
  * `ENV.md` generation now produces centered, width-aware tables
127
145
  * CLI now reliably resolves `env.meta.json` from:
128
-
129
146
  * project root
130
147
  * `./examples/`
131
148
  * explicit `-c/--config`
@@ -184,7 +201,6 @@ This project follows [Semantic Versioning](https://semver.org/).
184
201
  * Type-safe env validation with Zod
185
202
  * `loadEnv` and `formatZodError`
186
203
  * CLI to generate:
187
-
188
204
  * `.env.example`
189
205
  * `ENV.md`
190
206
  * ESM and CommonJS builds
package/README.RU.md CHANGED
@@ -16,90 +16,80 @@
16
16
  </a>
17
17
  </p>
18
18
 
19
+
19
20
  <p>
20
21
  <a href="./README.md">English</a> |
21
22
  <a href="./README.RU.md">Русский</a>
22
23
  </p>
23
24
  </div>
24
25
 
25
- Типобезопасная валидация и документация переменных окружения с помощью **Zod**.
26
26
 
27
- **zod-envkit** это небольшая, явная библиотека + CLI, которая помогает воспринимать переменные окружения как
28
- **явный runtime-контракт**, а не как неявную игру в угадайку.
27
+ Типобезопасная валидация и документация переменных окружения с помощью Zod.
29
28
 
30
- - валидация `process.env` при старте приложения
31
- - полностью типизированные переменные окружения
32
- - генерация `.env.example`
33
- - генерация документации (`ENV.md`, `ENV.json`, `ENV.yaml`)
34
- - просмотр состояния env через CLI (с маскированием секретов)
35
- - строгая проверка env в CI/CD
36
- - инициализация конфигурации через `zod-envkit init`
37
- - загрузка нескольких `.env*` файлов с приоритетом
29
+ zod-envkit это небольшая, явная библиотека + CLI, которая рассматривает переменные окружения как
30
+ явный runtime-контракт, а не как неявную игру в угадайку.
31
+ • валидирует process.env при старте приложения
32
+ • предоставляет полностью типизированные переменные окружения
33
+ • генерирует .env.example
34
+ • генерирует документацию (ENV.md, ENV.json, ENV.yaml)
35
+ • позволяет просматривать состояние env через CLI (с маскированием секретов)
36
+ • строго валидирует env в CI/CD
37
+ • инициализирует конфигурацию через zod-envkit init
38
+ • загружает несколько .env* файлов с приоритетом
38
39
 
39
40
  Никакого облака. Никакой магии. Только код.
40
41
 
41
- ---
42
+
42
43
 
43
- ## Зачем
44
+ Зачем
44
45
 
45
- Переменные окружения критичны, но почти всегда обрабатываются плохо.
46
+ Переменные окружения критичны, но обычно обрабатываются неправильно.
46
47
 
47
48
  Типичные проблемы:
49
+ • process.env — это просто string | undefined
50
+ • отсутствующие или некорректные переменные ломают приложение во время выполнения
51
+ • .env.example и документация рассинхронизируются
52
+ • CI/CD падает поздно и непредсказуемо
48
53
 
49
- - `process.env` это просто `string | undefined`
50
- - отсутствующие или некорректные переменные ломают приложение **во время выполнения**
51
- - `.env.example` и документация быстро устаревают
52
- - CI/CD падает поздно и непредсказуемо
53
-
54
- **zod-envkit** решает это, делая env:
55
-
56
- - валидируемым на раннем этапе
57
- - типизированным
58
- - документированным
59
- - проверяемым в CI
60
-
61
- ---
54
+ zod-envkit решает это, делая env:
55
+ • валидируемым на раннем этапе
56
+ • типизированным
57
+ • документированным
58
+ • проверяемым в CI
62
59
 
63
- ## Когда использовать
60
+
64
61
 
65
- Используйте **zod-envkit**, если:
62
+ Когда использовать
66
63
 
67
- - ошибки env должны обнаруживаться **при старте**, а не в продакшене
68
- - вы используете TypeScript и хотите строгие типы
69
- - `.env.example` и документация должны генерироваться из одного источника
70
- - CI должен падать при отсутствии или лишних переменных
64
+ Используйте zod-envkit, если:
65
+ вы хотите, чтобы ошибки env обнаруживались при старте, а не в продакшене
66
+ • вы используете TypeScript и вам важны корректные типы
67
+ • вы хотите получать .env.example и документацию из единого источника правды
68
+ • вы хотите, чтобы CI ловил отсутствующие или лишние переменные
71
69
 
72
- ## Когда НЕ использовать
70
+ Когда НЕ использовать
73
71
 
74
- Не стоит использовать **zod-envkit**, если:
72
+ Не стоит использовать zod-envkit, если:
73
+ • ваш проект очень маленький и неформальный
74
+ • вы вообще не контролируете переменные окружения
75
+ • вы ожидаете автоматическую интроспекцию схем или “магическое” поведение
75
76
 
76
- - проект очень маленький и неформальный
77
- - вы не контролируете переменные окружения
78
- - вы ожидаете “магическую” автогенерацию схем
77
+
79
78
 
80
- ---
79
+ Установка
81
80
 
82
- ## Установка
83
-
84
- ```bash
85
81
  npm install zod-envkit
86
- ````
87
-
88
- ```bash
89
82
  yarn add zod-envkit
90
- ```
91
-
92
- ```bash
93
83
  pnpm add zod-envkit
94
- ```
84
+ bun add zod-envkit
95
85
 
96
- ---
97
86
 
98
- ## Использование библиотеки (runtime-валидация)
87
+
88
+
89
+ Использование библиотеки (runtime-валидация)
99
90
 
100
91
  Создайте один файл, отвечающий за загрузку и валидацию env.
101
92
 
102
- ```ts
103
93
  import "dotenv/config";
104
94
  import { z } from "zod";
105
95
  import { loadEnv, mustLoadEnv, formatZodError } from "zod-envkit";
@@ -109,11 +99,9 @@ const EnvSchema = z.object({
109
99
  PORT: z.coerce.number().int().min(1).max(65535),
110
100
  DATABASE_URL: z.string().url(),
111
101
  });
112
- ```
113
102
 
114
- ### Безопасный режим (без исключений)
103
+ Безопасный режим (без исключений)
115
104
 
116
- ```ts
117
105
  const result = loadEnv(EnvSchema);
118
106
 
119
107
  if (!result.ok) {
@@ -122,37 +110,31 @@ if (!result.ok) {
122
110
  }
123
111
 
124
112
  export const env = result.env;
125
- ```
126
113
 
127
- ### Fail-fast режим (рекомендуется)
114
+ Fail-fast режим (рекомендуется)
128
115
 
129
- ```ts
130
116
  export const env = mustLoadEnv(EnvSchema);
131
- ```
132
117
 
133
118
  Теперь:
119
+ • env.PORT — это number
120
+ • env.DATABASE_URL — это string
121
+ • TypeScript знает всё на этапе компиляции
122
+ • приложение падает сразу, если env некорректен
134
123
 
135
- * `env.PORT` — это **number**
136
- * `env.DATABASE_URL` — это **string**
137
- * TypeScript всё знает на этапе компиляции
138
- * приложение падает сразу, если env некорректен
139
-
140
- ---
124
+
141
125
 
142
- ## Использование CLI
126
+ Использование CLI
143
127
 
144
- CLI работает на основе мета-файла: `env.meta.json`.
128
+ CLI работает на основе мета-файла: env.meta.json.
145
129
 
146
130
  По умолчанию он ищется в:
131
+ • ./env.meta.json
132
+ • ./examples/env.meta.json
147
133
 
148
- * `./env.meta.json`
149
- * `./examples/env.meta.json`
134
+
150
135
 
151
- ---
136
+ Пример env.meta.json
152
137
 
153
- ## Пример `env.meta.json`
154
-
155
- ```json
156
138
  {
157
139
  "NODE_ENV": {
158
140
  "description": "Режим выполнения",
@@ -170,164 +152,165 @@ CLI работает на основе мета-файла: `env.meta.json`.
170
152
  "required": true
171
153
  }
172
154
  }
173
- ```
174
155
 
175
- ---
176
156
 
177
- ## Команды CLI
157
+
158
+
159
+ Команды CLI
178
160
 
179
- ### Генерация `.env.example` и документации
161
+ Генерация .env.example и документации
180
162
 
181
163
  (Поведение по умолчанию)
182
164
 
183
- ```bash
184
165
  npx zod-envkit
185
- ```
186
166
 
187
167
  или явно:
188
168
 
189
- ```bash
190
169
  npx zod-envkit generate
191
- ```
192
170
 
193
171
  Генерация документации в разных форматах:
194
172
 
195
- ```bash
196
173
  npx zod-envkit generate --format json
197
174
  npx zod-envkit generate --format yaml
198
- ```
199
175
 
200
- Сортировка переменных:
176
+ Управление сортировкой:
201
177
 
202
- ```bash
203
178
  npx zod-envkit generate --sort alpha
204
179
  npx zod-envkit generate --sort required-first
205
- ```
206
180
 
207
- ---
208
181
 
209
- ### Просмотр текущего состояния окружения
182
+
183
+
184
+ Просмотр текущего состояния окружения
210
185
 
211
186
  Загружает dotenv-файлы, маскирует секреты и выводит читаемую таблицу.
212
187
 
213
- ```bash
214
188
  npx zod-envkit show
215
- ```
216
189
 
217
190
  Дополнительные опции:
218
191
 
219
- ```bash
220
192
  npx zod-envkit show --mask-mode full
221
193
  npx zod-envkit show --no-mask
222
194
  npx zod-envkit show --dotenv ".env,.env.local,.env.production"
223
- ```
224
195
 
225
- ---
226
196
 
227
- ### Проверка окружения (удобно для CI)
197
+
198
+
199
+ Проверка окружения (удобно для CI)
228
200
 
229
- ```bash
230
201
  npx zod-envkit check
231
- ```
232
202
 
233
- Строгий режим:
203
+ Строгий режим (падает при неизвестных переменных):
234
204
 
235
- ```bash
236
205
  npx zod-envkit check --strict
237
- ```
238
206
 
239
- * завершает процесс с кодом `1`, если отсутствуют обязательные переменные
240
- * в `--strict` режиме также падает при наличии неизвестных переменных
207
+ завершает процесс с кодом 1, если отсутствуют обязательные переменные
208
+ в --strict режиме также падает при наличии неизвестных переменных
241
209
 
242
- ---
210
+
243
211
 
244
- ### Инициализация конфигурации
212
+ Инициализация конфигурации
245
213
 
246
214
  Быстрый старт конфигурации из существующих файлов.
247
215
 
248
- Сгенерировать `env.meta.json` из `.env.example`:
216
+ Сгенерировать env.meta.json из .env.example:
249
217
 
250
- ```bash
251
218
  npx zod-envkit init
252
- ```
253
219
 
254
- Сгенерировать `.env.example` из существующего `env.meta.json`:
220
+ Сгенерировать .env.example из существующего env.meta.json:
255
221
 
256
- ```bash
257
222
  npx zod-envkit init --from-meta
258
- ```
259
223
 
260
- ---
261
224
 
262
- ## Стабильность и версионирование
225
+
226
+
227
+ Стабильность и версионирование
263
228
 
264
- `zod-envkit` следует **Semantic Versioning**.
229
+ zod-envkit следует Semantic Versioning.
265
230
 
266
- ### Стабильный public API (1.x)
231
+ Стабильность Public API (1.x)
267
232
 
268
- **Библиотека:**
233
+ Всё перечисленное ниже считается стабильным public API в рамках всей ветки 1.x.
269
234
 
270
- * `loadEnv`
271
- * `mustLoadEnv`
272
- * `formatZodError`
273
- * `checkEnv`
274
- * `getMissingEnv`
275
- * `getUnknownEnv`
276
- * `isSecretKey`
277
- * `generateEnvExample`
278
- * `generateEnvDocs`
235
+ Экспорты библиотеки (entrypoint zod-envkit):
236
+ • loadEnv
237
+ • mustLoadEnv
238
+ • formatZodError
239
+ • checkEnv
240
+ • getMissingEnv
241
+ • getUnknownEnv
242
+ • isSecretKey
243
+ • generateEnvExample
244
+ • generateEnvDocs
245
+ • sortMetaEntries
246
+ • связанные публичные типы: EnvMeta, EnvMetaEntry, EnvCheckResult, GenerateDocsOptions, DocsFormat, SortMode
279
247
 
280
- **CLI:**
248
+ CLI-контракт:
249
+ • команды: generate, show, check, init
250
+ • задокументированные флаги и значения по умолчанию
251
+ • поведение exit-кодов (успех = 0, пользовательская ошибка = 1)
281
252
 
282
- * `generate`, `show`, `check`, `init`
283
- * задокументированные флаги и коды выхода
253
+ Политика breaking-изменений
284
254
 
285
- ### Что считается breaking change
255
+ Breaking change (major) включает:
256
+ • изменение сигнатур или структуры возвращаемых значений стабильных экспортов
257
+ • удаление или переименование public-экспортов
258
+ • удаление или переименование CLI-команд или флагов
259
+ • изменение поведения CLI по умолчанию
260
+ • изменение семантики exit-кодов
261
+ • изменение контрактов формата вывода, ломающих существующие инструменты
286
262
 
287
- * изменение сигнатур или типов возвращаемых значений
288
- * удаление или переименование public-экспортов
289
- * удаление или переименование CLI-команд и флагов
290
- * изменение семантики exit-кодов
291
- * изменение поведения CLI по умолчанию
263
+ Не считается breaking (minor/patch):
264
+ • добавление новых экспортов (обратно совместимых)
265
+ • добавление новых CLI-флагов (обратно совместимых)
266
+ • добавление новых опциональных полей в env.meta.json
267
+ • улучшение сообщений об ошибках или форматирования документации без нарушения совместимости
292
268
 
293
- Не считается breaking:
269
+
294
270
 
295
- * добавление новых опциональных возможностей
296
- * добавление новых флагов
297
- * улучшение форматирования и сообщений об ошибках
271
+ Что нового в 1.2.0
298
272
 
299
- ---
273
+ Версия 1.2.0 сосредоточена на надёжности, контрактности и готовности к CI.
300
274
 
301
- ## Почему не просто dotenv?
275
+ Основные изменения:
276
+ • усилено поведение строгого режима (check --strict) для CI
277
+ • детерминированная обработка приоритета dotenv-файлов
278
+ • улучшены гарантии маскирования секретов в show
279
+ • расширен preprod-набор тестов (smoke, contract, CLI E2E, robustness)
280
+ • CI-pipeline принудительно выполняет: build → tests → docs build
302
281
 
303
- `dotenv`:
282
+ С этого момента zod-envkit — это не просто “утилита”, а
283
+ стабильный инструмент контрактов окружения для CI/CD-пайплайнов.
304
284
 
305
- * ❌ нет валидации
306
- * ❌ нет типов
307
- * ❌ нет документации
308
- * ❌ нет проверок для CI
285
+
309
286
 
310
- `zod-envkit`:
287
+ Почему не просто dotenv?
311
288
 
312
- * ✅ валидация
313
- * вывод типов для TypeScript
314
- * документация
315
- * CLI-инструменты
289
+ dotenv:
290
+ • ❌ нет валидации
291
+ • ❌ нет типов
292
+ • ❌ нет документации
293
+ • ❌ нет проверок для CI
316
294
 
317
- Они предназначены для использования **вместе**.
295
+ zod-envkit:
296
+ • ✅ валидация
297
+ • ✅ вывод типов для TypeScript
298
+ • ✅ документация
299
+ • ✅ CLI-инструменты
318
300
 
319
- ---
301
+ Они предназначены для использования вместе.
320
302
 
321
- ## Принципы дизайна
303
+
322
304
 
323
- * явная конфигурация вместо магии
324
- * отсутствие привязки к фреймворкам
325
- * маленький и предсказуемый API
326
- * библиотека и CLI независимы, но дополняют друг друга
327
- * переменные окружения это runtime-контракт
305
+ Принципы дизайна
306
+ • явная конфигурация вместо магии
307
+ • отсутствие привязки к фреймворкам
308
+ • маленький и предсказуемый API
309
+ • библиотека и CLI независимы, но дополняют друг друга
310
+ • переменные окружения — это runtime-контракт
328
311
 
329
- ---
312
+
330
313
 
331
- ## Лицензия
314
+ Лицензия
332
315
 
333
316
  MIT
package/README.md CHANGED
@@ -83,16 +83,10 @@ Skip **zod-envkit** if:
83
83
 
84
84
  ```bash
85
85
  npm install zod-envkit
86
- ````
87
-
88
- ```bash
89
86
  yarn add zod-envkit
90
- ```
91
-
92
- ```bash
93
87
  pnpm add zod-envkit
88
+ bun add zod-envkit
94
89
  ```
95
-
96
90
  ---
97
91
 
98
92
  ## Library usage (runtime validation)
@@ -263,38 +257,62 @@ npx zod-envkit init --from-meta
263
257
 
264
258
  `zod-envkit` follows **Semantic Versioning**.
265
259
 
266
- ### Stable public API (1.x)
260
+ ### Public API stability (1.x)
261
+
262
+ Everything listed below is treated as **stable public API** for the whole 1.x line.
263
+
264
+ **Library exports (entrypoint `zod-envkit`):**
265
+
266
+ - `loadEnv`
267
+ - `mustLoadEnv`
268
+ - `formatZodError`
269
+ - `checkEnv`
270
+ - `getMissingEnv`
271
+ - `getUnknownEnv`
272
+ - `isSecretKey`
273
+ - `generateEnvExample`
274
+ - `generateEnvDocs`
275
+ - `sortMetaEntries`
276
+ - related public types: `EnvMeta`, `EnvMetaEntry`, `EnvCheckResult`, `GenerateDocsOptions`, `DocsFormat`, `SortMode`
277
+
278
+ **CLI contract:**
279
+
280
+ - commands: `generate`, `show`, `check`, `init`
281
+ - documented flags and defaults
282
+ - exit code behavior (success = `0`, user error = `1`)
283
+
284
+ ### Breaking change policy
285
+
286
+ A **breaking** change (major) includes:
267
287
 
268
- **Library:**
288
+ - changing signatures or return shapes of the stable exports
289
+ - removing/renaming public exports
290
+ - removing/renaming CLI commands or flags
291
+ - changing CLI default behavior (e.g. what `zod-envkit` does with no args)
292
+ - changing exit code semantics
293
+ - changing output format contracts in a way that breaks existing tooling
269
294
 
270
- * `loadEnv`
271
- * `mustLoadEnv`
272
- * `formatZodError`
273
- * `checkEnv`
274
- * `getMissingEnv`
275
- * `getUnknownEnv`
276
- * `isSecretKey`
277
- * `generateEnvExample`
278
- * `generateEnvDocs`
295
+ A **non-breaking** change (minor/patch) includes:
279
296
 
280
- **CLI:**
297
+ - adding new exports (backwards compatible)
298
+ - adding new CLI flags (backwards compatible)
299
+ - adding new optional fields to `env.meta.json`
300
+ - improving error messages or docs output while keeping it valid
281
301
 
282
- * `generate`, `show`, `check`, `init`
283
- * documented flags and exit codes
302
+ ### What’s new in 1.2.0
284
303
 
285
- ### What is considered breaking
304
+ Version **1.2.0** focuses on reliability, contract enforcement, and CI readiness.
286
305
 
287
- * changing function signatures or return types
288
- * removing or renaming public exports
289
- * removing or renaming CLI commands or flags
290
- * changing exit code semantics
291
- * changing default CLI behavior
306
+ Highlights:
292
307
 
293
- Non-breaking:
308
+ - hardened strict mode behavior (`check --strict`) for CI usage
309
+ - deterministic dotenv priority handling
310
+ - improved secret masking guarantees in `show`
311
+ - expanded preprod test suite (smoke, contract, CLI E2E, robustness)
312
+ - CI pipeline enforces: build → tests → docs build
294
313
 
295
- * adding new optional features
296
- * adding new flags
297
- * improving error messages or formatting
314
+ This is the point where `zod-envkit` stops being “just a helper” and becomes a
315
+ **stable environment contract tool for CI/CD pipelines**.
298
316
 
299
317
  ---
300
318
 
@@ -302,17 +320,17 @@ Non-breaking:
302
320
 
303
321
  `dotenv`:
304
322
 
305
- * ❌ no validation
306
- * ❌ no types
307
- * ❌ no documentation
308
- * ❌ no CI checks
323
+ - ❌ no validation
324
+ - ❌ no types
325
+ - ❌ no documentation
326
+ - ❌ no CI checks
309
327
 
310
328
  `zod-envkit`:
311
329
 
312
- * ✅ validation
313
- * ✅ TypeScript inference
314
- * ✅ documentation
315
- * ✅ CLI tooling
330
+ - ✅ validation
331
+ - ✅ TypeScript inference
332
+ - ✅ documentation
333
+ - ✅ CLI tooling
316
334
 
317
335
  They are designed to be used **together**.
318
336
 
@@ -320,11 +338,11 @@ They are designed to be used **together**.
320
338
 
321
339
  ## Design principles
322
340
 
323
- * explicit configuration over magic
324
- * no framework coupling
325
- * small and predictable API
326
- * library and CLI are independent but complementary
327
- * environment variables are a runtime contract
341
+ - explicit configuration over magic
342
+ - no framework coupling
343
+ - small and predictable API
344
+ - library and CLI are independent but complementary
345
+ - environment variables are a runtime contract
328
346
 
329
347
  ---
330
348
 
@@ -77,9 +77,16 @@ function t(lang, key, vars) {
77
77
 
78
78
  // src/cli/lib/argv.ts
79
79
  function injectDefaultCommandIfMissing(argv, opts) {
80
+ if (argv.length <= 2) {
81
+ argv.splice(2, 0, opts.defaultCommand);
82
+ return;
83
+ }
80
84
  const args = argv.slice(2);
81
85
  const hasKnownToken = args.some((a) => opts.known.has(a));
82
- if (!hasKnownToken) argv.splice(2, 0, opts.defaultCommand);
86
+ const alreadyInjected = args[0] === opts.defaultCommand;
87
+ if (!hasKnownToken && !alreadyInjected) {
88
+ argv.splice(2, 0, opts.defaultCommand);
89
+ }
83
90
  }
84
91
 
85
92
  // src/cli/commands/generate.ts
@@ -409,36 +416,54 @@ function maskValue(key, value, mode) {
409
416
  }
410
417
 
411
418
  // src/cli/lib/table.ts
419
+ function strLen2(s) {
420
+ return (s ?? "").length;
421
+ }
412
422
  function padCenter2(text, width) {
413
423
  const v = text ?? "";
414
- const diff = width - v.length;
424
+ const diff = width - strLen2(v);
415
425
  if (diff <= 0) return v;
416
426
  const left = Math.floor(diff / 2);
417
427
  const right = diff - left;
418
428
  return " ".repeat(left) + v + " ".repeat(right);
419
429
  }
430
+ function makeDivider2(width, align) {
431
+ const w = Math.max(3, width);
432
+ if (align === "center") return ":" + "-".repeat(w - 2) + ":";
433
+ if (align === "right") return "-".repeat(w - 1) + ":";
434
+ return "-".repeat(w);
435
+ }
420
436
  function printMarkdownTable(rows, headers) {
437
+ if (headers.length === 0) return;
421
438
  const widths = {};
422
- for (const h of headers) widths[h] = h.length;
439
+ for (const h of headers) widths[h] = strLen2(h);
423
440
  for (const row of rows) {
424
- for (const h of headers) widths[h] = Math.max(widths[h], (row[h] ?? "").length);
441
+ for (const h of headers) {
442
+ widths[h] = Math.max(widths[h], strLen2(row[h] ?? ""));
443
+ }
425
444
  }
426
445
  for (const h of headers) widths[h] += 2;
427
- const headerLine = "|" + headers.map((h) => " " + padCenter2(h, widths[h]) + " ").join("|") + "|";
428
- const sepLine = "|" + headers.map((h) => ":" + "-".repeat(Math.max(3, widths[h])) + ":").join("|") + "|";
446
+ const headerLine = "|" + headers.map((h) => ` ${padCenter2(h, widths[h])} `).join("|") + "|";
447
+ const sepLine = "|" + headers.map((h) => ` ${makeDivider2(widths[h], "center")} `).join("|") + "|";
429
448
  console.log(headerLine);
430
449
  console.log(sepLine);
431
450
  for (const row of rows) {
432
- console.log("|" + headers.map((h) => " " + padCenter2(row[h] ?? "", widths[h]) + " ").join("|") + "|");
451
+ console.log(
452
+ "|" + headers.map((h) => ` ${padCenter2(row[h] ?? "", widths[h])} `).join("|") + "|"
453
+ );
433
454
  }
434
455
  }
435
456
 
436
457
  // src/cli/lib/sort.ts
437
458
  function sortKeys(meta, sort) {
438
459
  const keys = Object.keys(meta);
439
- if (sort === "none") return keys;
440
- if (sort === "alpha") return keys.sort((a, b) => a.localeCompare(b));
441
- return keys.sort((a, b) => {
460
+ if (sort === "none") {
461
+ return [...keys];
462
+ }
463
+ if (sort === "alpha") {
464
+ return [...keys].sort((a, b) => a.localeCompare(b));
465
+ }
466
+ return [...keys].sort((a, b) => {
442
467
  const ar = meta[a]?.required === false ? 1 : 0;
443
468
  const br = meta[b]?.required === false ? 1 : 0;
444
469
  if (ar !== br) return ar - br;
@@ -457,7 +482,7 @@ function registerShow(program2, getLang2) {
457
482
  program2.command("show").description("Show current env status (loads dotenv, masks secrets)").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--dotenv <list>", "Comma-separated dotenv files (default: .env)", ".env").option("--mask-mode <mode>", "Mask mode (partial | full | none)", "partial").option("--no-mask", "Alias for --mask-mode none").option("--sort <mode>", "Sort mode (alpha | required-first | none)", "none").action((opts) => {
458
483
  const lang = getLang2();
459
484
  loadDotEnv(String(opts.dotenv ?? ".env"));
460
- const { meta } = loadMeta(lang, opts.config);
485
+ const { meta } = loadMeta(lang, String(opts.config ?? "env.meta.json"));
461
486
  const fallbackMode = opts.mask === false ? "none" : "partial";
462
487
  const modeRaw = String(opts.maskMode ?? fallbackMode);
463
488
  if (!isMaskMode(modeRaw)) {
@@ -499,16 +524,12 @@ function registerCheck(program2, getLang2) {
499
524
  const { meta } = loadMeta(lang, opts.config);
500
525
  const missing = getMissingEnv(meta, process.env);
501
526
  if (missing.length) {
502
- console.error(`\u274C ${t(lang, "MISSING_ENV")}`);
503
- for (const k of missing) console.error(`- ${k}`);
504
- process.exit(1);
527
+ fail(lang, "MISSING_ENV", missing.map((k) => `- ${k}`));
505
528
  }
506
529
  if (opts.strict) {
507
530
  const unknown = getUnknownEnv(meta, loaded.env);
508
531
  if (unknown.length) {
509
- console.error(`\u274C ${t(lang, "UNKNOWN_ENV")}`);
510
- for (const k of unknown) console.error(`- ${k}`);
511
- process.exit(1);
532
+ fail(lang, "UNKNOWN_ENV", unknown.map((k) => `- ${k}`));
512
533
  }
513
534
  }
514
535
  console.log(`\u2705 ${t(lang, "ENV_OK")}`);
@@ -552,7 +573,7 @@ function registerInit(program2, getLang2) {
552
573
  }
553
574
  const env = readEnvFile(input);
554
575
  if (Object.keys(env).length === 0) {
555
- fail(lang, "META_PARSE_FAILED", [`- ${t(lang, "INIT_INPUT_EMPTY")} ${input}`]);
576
+ fail(lang, "INIT_INPUT_EMPTY", [`- ${input}`]);
556
577
  }
557
578
  const meta = metaFromEnv(env, opts.group ? String(opts.group) : void 0);
558
579
  import_node_fs4.default.writeFileSync(output, JSON.stringify(meta, null, 2) + "\n", "utf8");
package/dist/cli/index.js CHANGED
@@ -61,9 +61,16 @@ function t(lang, key, vars) {
61
61
 
62
62
  // src/cli/lib/argv.ts
63
63
  function injectDefaultCommandIfMissing(argv, opts) {
64
+ if (argv.length <= 2) {
65
+ argv.splice(2, 0, opts.defaultCommand);
66
+ return;
67
+ }
64
68
  const args = argv.slice(2);
65
69
  const hasKnownToken = args.some((a) => opts.known.has(a));
66
- if (!hasKnownToken) argv.splice(2, 0, opts.defaultCommand);
70
+ const alreadyInjected = args[0] === opts.defaultCommand;
71
+ if (!hasKnownToken && !alreadyInjected) {
72
+ argv.splice(2, 0, opts.defaultCommand);
73
+ }
67
74
  }
68
75
 
69
76
  // src/cli/commands/generate.ts
@@ -197,36 +204,54 @@ function maskValue(key, value, mode) {
197
204
  }
198
205
 
199
206
  // src/cli/lib/table.ts
207
+ function strLen(s) {
208
+ return (s ?? "").length;
209
+ }
200
210
  function padCenter(text, width) {
201
211
  const v = text ?? "";
202
- const diff = width - v.length;
212
+ const diff = width - strLen(v);
203
213
  if (diff <= 0) return v;
204
214
  const left = Math.floor(diff / 2);
205
215
  const right = diff - left;
206
216
  return " ".repeat(left) + v + " ".repeat(right);
207
217
  }
218
+ function makeDivider(width, align) {
219
+ const w = Math.max(3, width);
220
+ if (align === "center") return ":" + "-".repeat(w - 2) + ":";
221
+ if (align === "right") return "-".repeat(w - 1) + ":";
222
+ return "-".repeat(w);
223
+ }
208
224
  function printMarkdownTable(rows, headers) {
225
+ if (headers.length === 0) return;
209
226
  const widths = {};
210
- for (const h of headers) widths[h] = h.length;
227
+ for (const h of headers) widths[h] = strLen(h);
211
228
  for (const row of rows) {
212
- for (const h of headers) widths[h] = Math.max(widths[h], (row[h] ?? "").length);
229
+ for (const h of headers) {
230
+ widths[h] = Math.max(widths[h], strLen(row[h] ?? ""));
231
+ }
213
232
  }
214
233
  for (const h of headers) widths[h] += 2;
215
- const headerLine = "|" + headers.map((h) => " " + padCenter(h, widths[h]) + " ").join("|") + "|";
216
- const sepLine = "|" + headers.map((h) => ":" + "-".repeat(Math.max(3, widths[h])) + ":").join("|") + "|";
234
+ const headerLine = "|" + headers.map((h) => ` ${padCenter(h, widths[h])} `).join("|") + "|";
235
+ const sepLine = "|" + headers.map((h) => ` ${makeDivider(widths[h], "center")} `).join("|") + "|";
217
236
  console.log(headerLine);
218
237
  console.log(sepLine);
219
238
  for (const row of rows) {
220
- console.log("|" + headers.map((h) => " " + padCenter(row[h] ?? "", widths[h]) + " ").join("|") + "|");
239
+ console.log(
240
+ "|" + headers.map((h) => ` ${padCenter(row[h] ?? "", widths[h])} `).join("|") + "|"
241
+ );
221
242
  }
222
243
  }
223
244
 
224
245
  // src/cli/lib/sort.ts
225
246
  function sortKeys(meta, sort) {
226
247
  const keys = Object.keys(meta);
227
- if (sort === "none") return keys;
228
- if (sort === "alpha") return keys.sort((a, b) => a.localeCompare(b));
229
- return keys.sort((a, b) => {
248
+ if (sort === "none") {
249
+ return [...keys];
250
+ }
251
+ if (sort === "alpha") {
252
+ return [...keys].sort((a, b) => a.localeCompare(b));
253
+ }
254
+ return [...keys].sort((a, b) => {
230
255
  const ar = meta[a]?.required === false ? 1 : 0;
231
256
  const br = meta[b]?.required === false ? 1 : 0;
232
257
  if (ar !== br) return ar - br;
@@ -245,7 +270,7 @@ function registerShow(program2, getLang2) {
245
270
  program2.command("show").description("Show current env status (loads dotenv, masks secrets)").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--dotenv <list>", "Comma-separated dotenv files (default: .env)", ".env").option("--mask-mode <mode>", "Mask mode (partial | full | none)", "partial").option("--no-mask", "Alias for --mask-mode none").option("--sort <mode>", "Sort mode (alpha | required-first | none)", "none").action((opts) => {
246
271
  const lang = getLang2();
247
272
  loadDotEnv(String(opts.dotenv ?? ".env"));
248
- const { meta } = loadMeta(lang, opts.config);
273
+ const { meta } = loadMeta(lang, String(opts.config ?? "env.meta.json"));
249
274
  const fallbackMode = opts.mask === false ? "none" : "partial";
250
275
  const modeRaw = String(opts.maskMode ?? fallbackMode);
251
276
  if (!isMaskMode(modeRaw)) {
@@ -287,16 +312,12 @@ function registerCheck(program2, getLang2) {
287
312
  const { meta } = loadMeta(lang, opts.config);
288
313
  const missing = getMissingEnv(meta, process.env);
289
314
  if (missing.length) {
290
- console.error(`\u274C ${t(lang, "MISSING_ENV")}`);
291
- for (const k of missing) console.error(`- ${k}`);
292
- process.exit(1);
315
+ fail(lang, "MISSING_ENV", missing.map((k) => `- ${k}`));
293
316
  }
294
317
  if (opts.strict) {
295
318
  const unknown = getUnknownEnv(meta, loaded.env);
296
319
  if (unknown.length) {
297
- console.error(`\u274C ${t(lang, "UNKNOWN_ENV")}`);
298
- for (const k of unknown) console.error(`- ${k}`);
299
- process.exit(1);
320
+ fail(lang, "UNKNOWN_ENV", unknown.map((k) => `- ${k}`));
300
321
  }
301
322
  }
302
323
  console.log(`\u2705 ${t(lang, "ENV_OK")}`);
@@ -340,7 +361,7 @@ function registerInit(program2, getLang2) {
340
361
  }
341
362
  const env = readEnvFile(input);
342
363
  if (Object.keys(env).length === 0) {
343
- fail(lang, "META_PARSE_FAILED", [`- ${t(lang, "INIT_INPUT_EMPTY")} ${input}`]);
364
+ fail(lang, "INIT_INPUT_EMPTY", [`- ${input}`]);
344
365
  }
345
366
  const meta = metaFromEnv(env, opts.group ? String(opts.group) : void 0);
346
367
  fs4.writeFileSync(output, JSON.stringify(meta, null, 2) + "\n", "utf8");
package/dist/index.d.cts CHANGED
@@ -149,7 +149,7 @@ declare function generateEnvDocs(meta: EnvMeta, opts?: GenerateDocsOptions): str
149
149
  * - `unknown`: keys present in env but not described in meta
150
150
  *
151
151
  * @public
152
- * @since 1.1.0
152
+ * @since 1.2.0
153
153
  */
154
154
  type EnvCheckResult = {
155
155
  ok: boolean;
@@ -160,7 +160,7 @@ type EnvCheckResult = {
160
160
  * Return required keys from `meta` that are missing (or empty) in `env`.
161
161
  *
162
162
  * @public
163
- * @since 1.1.0
163
+ * @since 1.2.0
164
164
  */
165
165
  declare function getMissingEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): string[];
166
166
  /**
@@ -169,7 +169,7 @@ declare function getMissingEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): string[]
169
169
  * Note: the result is returned in stable alphabetical order.
170
170
  *
171
171
  * @public
172
- * @since 1.1.0
172
+ * @since 1.2.0
173
173
  */
174
174
  declare function getUnknownEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): string[];
175
175
  /**
@@ -178,7 +178,7 @@ declare function getUnknownEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): string[]
178
178
  * Used by the CLI to mask values (TOKEN/SECRET/PASSWORD/*_KEY/PRIVATE).
179
179
  *
180
180
  * @public
181
- * @since 1.1.0
181
+ * @since 1.2.0
182
182
  */
183
183
  declare function isSecretKey(key: string): boolean;
184
184
  /**
@@ -188,12 +188,14 @@ declare function isSecretKey(key: string): boolean;
188
188
  *
189
189
  * Note: `ok` here means:
190
190
  * - no missing required vars
191
- * - no unknown vars (because this function is strict by definition)
191
+ * - no unknown vars
192
+ *
193
+ * The CLI may choose to ignore `unknown` unless `--strict` is enabled.
192
194
  *
193
195
  * If you want "missing only" checks, use {@link getMissingEnv}.
194
196
  *
195
197
  * @public
196
- * @since 1.1.0
198
+ * @since 1.2.0
197
199
  */
198
200
  declare function checkEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): EnvCheckResult;
199
201
 
package/dist/index.d.ts CHANGED
@@ -149,7 +149,7 @@ declare function generateEnvDocs(meta: EnvMeta, opts?: GenerateDocsOptions): str
149
149
  * - `unknown`: keys present in env but not described in meta
150
150
  *
151
151
  * @public
152
- * @since 1.1.0
152
+ * @since 1.2.0
153
153
  */
154
154
  type EnvCheckResult = {
155
155
  ok: boolean;
@@ -160,7 +160,7 @@ type EnvCheckResult = {
160
160
  * Return required keys from `meta` that are missing (or empty) in `env`.
161
161
  *
162
162
  * @public
163
- * @since 1.1.0
163
+ * @since 1.2.0
164
164
  */
165
165
  declare function getMissingEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): string[];
166
166
  /**
@@ -169,7 +169,7 @@ declare function getMissingEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): string[]
169
169
  * Note: the result is returned in stable alphabetical order.
170
170
  *
171
171
  * @public
172
- * @since 1.1.0
172
+ * @since 1.2.0
173
173
  */
174
174
  declare function getUnknownEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): string[];
175
175
  /**
@@ -178,7 +178,7 @@ declare function getUnknownEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): string[]
178
178
  * Used by the CLI to mask values (TOKEN/SECRET/PASSWORD/*_KEY/PRIVATE).
179
179
  *
180
180
  * @public
181
- * @since 1.1.0
181
+ * @since 1.2.0
182
182
  */
183
183
  declare function isSecretKey(key: string): boolean;
184
184
  /**
@@ -188,12 +188,14 @@ declare function isSecretKey(key: string): boolean;
188
188
  *
189
189
  * Note: `ok` here means:
190
190
  * - no missing required vars
191
- * - no unknown vars (because this function is strict by definition)
191
+ * - no unknown vars
192
+ *
193
+ * The CLI may choose to ignore `unknown` unless `--strict` is enabled.
192
194
  *
193
195
  * If you want "missing only" checks, use {@link getMissingEnv}.
194
196
  *
195
197
  * @public
196
- * @since 1.1.0
198
+ * @since 1.2.0
197
199
  */
198
200
  declare function checkEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): EnvCheckResult;
199
201
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod-envkit",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Validate environment variables with Zod and generate .env.example",
5
5
  "license": "MIT",
6
6
  "author": "",