zod-envkit 1.4.0 → 1.5.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,3 +1,23 @@
1
+ ## [1.5.1](https://github.com/nxtxe/zod-envkit/compare/v1.5.0...v1.5.1) (2026-06-27)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **cli:** fail on empty required dotenv values in check --production ([0c46748](https://github.com/nxtxe/zod-envkit/commit/0c46748e4740c97d7fde7bb1a39b7dd4bb11109e))
7
+
8
+ # [1.5.0](https://github.com/nxtxe/zod-envkit/compare/v1.4.0...v1.5.0) (2026-06-06)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **ci:** use npm trusted publishing for semantic-release ([2f05e0b](https://github.com/nxtxe/zod-envkit/commit/2f05e0b9e45c385160bcc42bafbc70db6078b52f))
14
+ * **release:** accept semantic-release changelog heading in hygiene check ([e40a655](https://github.com/nxtxe/zod-envkit/commit/e40a655270798157a2afc4388f9a490576efd12b))
15
+
16
+
17
+ ### Features
18
+
19
+ * **cli:** fail on unknown dotenv keys in check --production mode ([ff52897](https://github.com/nxtxe/zod-envkit/commit/ff528978966982d0f59530b170d712efb86c09e6))
20
+
1
21
  # [1.4.0](https://github.com/nxtxe/zod-envkit/compare/v1.3.5...v1.4.0) (2026-05-19)
2
22
 
3
23
 
package/README.RU.md CHANGED
@@ -233,8 +233,16 @@ npx zod-envkit check
233
233
  npx zod-envkit check --strict
234
234
  ```
235
235
 
236
+ Production guard (более строгие проверки для deploy/CI; неизвестные ключи из dotenv — как в `--strict`):
237
+
238
+ ```bash
239
+ npx zod-envkit check --production
240
+ ```
241
+
236
242
  - завершает процесс с кодом `1`, если отсутствуют обязательные переменные
237
- - в `--strict` режиме также падает при наличии неизвестных переменных
243
+ - в `--strict` режиме также падает при неизвестных переменных (только ключи из dotenv)
244
+ - с `--production` также падает при неизвестных переменных из dotenv (тот же dotenv-only scope, что у `--strict`)
245
+ - с `--production` также падает, если обязательный ключ есть в dotenv, но пустой после trim (`PORT=`, `PORT=" "`)
238
246
 
239
247
  ---
240
248
 
package/README.md CHANGED
@@ -230,8 +230,16 @@ Strict mode (fail on unknown variables):
230
230
  npx zod-envkit check --strict
231
231
  ```
232
232
 
233
+ Production guard (stricter deploy/CI checks; unknown dotenv keys fail like `--strict`):
234
+
235
+ ```bash
236
+ npx zod-envkit check --production
237
+ ```
238
+
233
239
  * exits with code `1` if required variables are missing
234
- * in `--strict` mode also fails on unknown variables
240
+ * in `--strict` mode also fails on unknown variables (dotenv-loaded keys only)
241
+ * with `--production` also fails on unknown dotenv variables (same dotenv-only scope as `--strict`)
242
+ * with `--production` also fails when a required dotenv key is present but empty after trim (`PORT=`, `PORT=" "`)
235
243
 
236
244
  ---
237
245
 
@@ -172,6 +172,17 @@ function getMissingEnv(meta, env = process.env) {
172
172
  }
173
173
  return missing;
174
174
  }
175
+ function getEmptyRequiredEnv(meta, env) {
176
+ const empty = [];
177
+ for (const [key, m] of Object.entries(meta)) {
178
+ const required = m.required !== false;
179
+ if (!required) continue;
180
+ if (!Object.prototype.hasOwnProperty.call(env, key)) continue;
181
+ if (env[key].trim() === "") empty.push(key);
182
+ }
183
+ empty.sort((a, b) => a.localeCompare(b));
184
+ return empty;
185
+ }
175
186
  function getUnknownEnv(meta, env = process.env) {
176
187
  const known = new Set(Object.keys(meta));
177
188
  const unknown = [];
@@ -213,6 +224,7 @@ export {
213
224
  generateEnvExample,
214
225
  generateEnvDocs,
215
226
  getMissingEnv,
227
+ getEmptyRequiredEnv,
216
228
  getUnknownEnv,
217
229
  isSecretKey,
218
230
  checkEnv
@@ -42,6 +42,7 @@ var messages = {
42
42
  GENERATED: "Generated: {example}, {docs}",
43
43
  ENV_OK: "Environment looks good.",
44
44
  MISSING_ENV: "Missing required environment variables:",
45
+ EMPTY_REQUIRED_ENV: "Required environment variables are empty (production; dotenv-loaded keys):",
45
46
  UNKNOWN_ENV: "Unknown environment variables (strict mode; only dotenv-loaded keys):",
46
47
  INVALID_FORMAT: "Invalid docs format",
47
48
  INVALID_MASK_MODE: "Invalid mask mode",
@@ -69,6 +70,7 @@ var messages = {
69
70
  GENERATED: "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E: {example}, {docs}",
70
71
  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.",
71
72
  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:",
73
+ EMPTY_REQUIRED_ENV: "\u041E\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 \u043F\u0443\u0441\u0442\u044B\u0435 (production; \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437 dotenv-\u0444\u0430\u0439\u043B\u043E\u0432):",
72
74
  UNKNOWN_ENV: "\u041E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u044B \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F (strict; \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437 dotenv-\u0444\u0430\u0439\u043B\u043E\u0432):",
73
75
  INVALID_FORMAT: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u0438",
74
76
  INVALID_MASK_MODE: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u043C\u0430\u0441\u043A\u0438\u0440\u043E\u0432\u043A\u0438",
@@ -452,6 +454,17 @@ function getMissingEnv(meta, env = process.env) {
452
454
  }
453
455
  return missing;
454
456
  }
457
+ function getEmptyRequiredEnv(meta, env) {
458
+ const empty = [];
459
+ for (const [key, m] of Object.entries(meta)) {
460
+ const required = m.required !== false;
461
+ if (!required) continue;
462
+ if (!Object.prototype.hasOwnProperty.call(env, key)) continue;
463
+ if (env[key].trim() === "") empty.push(key);
464
+ }
465
+ empty.sort((a, b) => a.localeCompare(b));
466
+ return empty;
467
+ }
455
468
  function getUnknownEnv(meta, env = process.env) {
456
469
  const known = new Set(Object.keys(meta));
457
470
  const unknown = [];
@@ -648,17 +661,26 @@ function registerCheck(program2, getLang2) {
648
661
  ).action(async (opts) => {
649
662
  const lang = getLang2();
650
663
  const production = Boolean(opts.production);
651
- void production;
664
+ const strictEffective = Boolean(opts.strict) || production;
652
665
  const loaded = loadDotEnv(opts.dotenv);
653
666
  const { meta } = loadMeta(lang, opts.config);
654
667
  const sections = [];
655
- const missing = getMissingEnv(meta, process.env);
668
+ const emptyRequired = production ? getEmptyRequiredEnv(meta, loaded.env) : [];
669
+ const emptyRequiredSet = new Set(emptyRequired);
670
+ const missing = getMissingEnv(meta, process.env).filter(
671
+ (key) => !emptyRequiredSet.has(key)
672
+ );
656
673
  if (missing.length) {
657
674
  sections.push(t(lang, "MISSING_ENV"));
658
675
  missing.forEach((k) => sections.push(`- ${k}`));
659
676
  sections.push("");
660
677
  }
661
- if (opts.strict) {
678
+ if (production && emptyRequired.length) {
679
+ sections.push(t(lang, "EMPTY_REQUIRED_ENV"));
680
+ emptyRequired.forEach((k) => sections.push(`- ${k}`));
681
+ sections.push("");
682
+ }
683
+ if (strictEffective) {
662
684
  const unknown = getUnknownEnv(meta, loaded.env);
663
685
  if (unknown.length) {
664
686
  sections.push(t(lang, "UNKNOWN_ENV"));
package/dist/cli/index.js CHANGED
@@ -2,10 +2,11 @@
2
2
  import {
3
3
  generateEnvDocs,
4
4
  generateEnvExample,
5
+ getEmptyRequiredEnv,
5
6
  getMissingEnv,
6
7
  getUnknownEnv,
7
8
  isSecretKey
8
- } from "../chunk-WWWQDMV5.js";
9
+ } from "../chunk-AGSAWBDX.js";
9
10
 
10
11
  // src/cli/index.ts
11
12
  import { Command } from "commander";
@@ -26,6 +27,7 @@ var messages = {
26
27
  GENERATED: "Generated: {example}, {docs}",
27
28
  ENV_OK: "Environment looks good.",
28
29
  MISSING_ENV: "Missing required environment variables:",
30
+ EMPTY_REQUIRED_ENV: "Required environment variables are empty (production; dotenv-loaded keys):",
29
31
  UNKNOWN_ENV: "Unknown environment variables (strict mode; only dotenv-loaded keys):",
30
32
  INVALID_FORMAT: "Invalid docs format",
31
33
  INVALID_MASK_MODE: "Invalid mask mode",
@@ -53,6 +55,7 @@ var messages = {
53
55
  GENERATED: "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E: {example}, {docs}",
54
56
  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.",
55
57
  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:",
58
+ EMPTY_REQUIRED_ENV: "\u041E\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 \u043F\u0443\u0441\u0442\u044B\u0435 (production; \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437 dotenv-\u0444\u0430\u0439\u043B\u043E\u0432):",
56
59
  UNKNOWN_ENV: "\u041E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u044B \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F (strict; \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437 dotenv-\u0444\u0430\u0439\u043B\u043E\u0432):",
57
60
  INVALID_FORMAT: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u0438",
58
61
  INVALID_MASK_MODE: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u043C\u0430\u0441\u043A\u0438\u0440\u043E\u0432\u043A\u0438",
@@ -427,17 +430,26 @@ function registerCheck(program2, getLang2) {
427
430
  ).action(async (opts) => {
428
431
  const lang = getLang2();
429
432
  const production = Boolean(opts.production);
430
- void production;
433
+ const strictEffective = Boolean(opts.strict) || production;
431
434
  const loaded = loadDotEnv(opts.dotenv);
432
435
  const { meta } = loadMeta(lang, opts.config);
433
436
  const sections = [];
434
- const missing = getMissingEnv(meta, process.env);
437
+ const emptyRequired = production ? getEmptyRequiredEnv(meta, loaded.env) : [];
438
+ const emptyRequiredSet = new Set(emptyRequired);
439
+ const missing = getMissingEnv(meta, process.env).filter(
440
+ (key) => !emptyRequiredSet.has(key)
441
+ );
435
442
  if (missing.length) {
436
443
  sections.push(t(lang, "MISSING_ENV"));
437
444
  missing.forEach((k) => sections.push(`- ${k}`));
438
445
  sections.push("");
439
446
  }
440
- if (opts.strict) {
447
+ if (production && emptyRequired.length) {
448
+ sections.push(t(lang, "EMPTY_REQUIRED_ENV"));
449
+ emptyRequired.forEach((k) => sections.push(`- ${k}`));
450
+ sections.push("");
451
+ }
452
+ if (strictEffective) {
441
453
  const unknown = getUnknownEnv(meta, loaded.env);
442
454
  if (unknown.length) {
443
455
  sections.push(t(lang, "UNKNOWN_ENV"));
package/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  formatZodError: () => formatZodError,
25
25
  generateEnvDocs: () => generateEnvDocs,
26
26
  generateEnvExample: () => generateEnvExample,
27
+ getEmptyRequiredEnv: () => getEmptyRequiredEnv,
27
28
  getMissingEnv: () => getMissingEnv,
28
29
  getUnknownEnv: () => getUnknownEnv,
29
30
  isSecretKey: () => isSecretKey,
@@ -207,6 +208,17 @@ function getMissingEnv(meta, env = process.env) {
207
208
  }
208
209
  return missing;
209
210
  }
211
+ function getEmptyRequiredEnv(meta, env) {
212
+ const empty = [];
213
+ for (const [key, m] of Object.entries(meta)) {
214
+ const required = m.required !== false;
215
+ if (!required) continue;
216
+ if (!Object.prototype.hasOwnProperty.call(env, key)) continue;
217
+ if (env[key].trim() === "") empty.push(key);
218
+ }
219
+ empty.sort((a, b) => a.localeCompare(b));
220
+ return empty;
221
+ }
210
222
  function getUnknownEnv(meta, env = process.env) {
211
223
  const known = new Set(Object.keys(meta));
212
224
  const unknown = [];
@@ -272,6 +284,7 @@ function formatZodError(err) {
272
284
  formatZodError,
273
285
  generateEnvDocs,
274
286
  generateEnvExample,
287
+ getEmptyRequiredEnv,
275
288
  getMissingEnv,
276
289
  getUnknownEnv,
277
290
  isSecretKey,
package/dist/index.d.cts CHANGED
@@ -169,6 +169,16 @@ type EnvCheckResult = {
169
169
  * @since 1.2.0
170
170
  */
171
171
  declare function getMissingEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): string[];
172
+ /**
173
+ * Return required keys from `meta` that are present in `env` but empty after trim.
174
+ *
175
+ * Used by the CLI in `--production` mode to catch dotenv entries like `PORT=` or `PORT=" "`
176
+ * without changing default `check` behavior for whitespace-only values.
177
+ *
178
+ * @public
179
+ * @since 1.5.1
180
+ */
181
+ declare function getEmptyRequiredEnv(meta: EnvMeta, env: Record<string, string>): string[];
172
182
  /**
173
183
  * Return keys present in `env` that are not defined in `meta`.
174
184
  *
@@ -300,4 +310,4 @@ declare function mustLoadEnv<T extends z.ZodTypeAny>(schema: T): z.infer<T>;
300
310
  */
301
311
  declare function formatZodError(err: z.ZodError): string;
302
312
 
303
- export { type DocsFormat, type EnvCheckResult, type EnvMeta, type EnvMetaEntry, type GenerateDocsOptions, type LoadEnvFail, type LoadEnvOk, type LoadEnvOptions, type SortMode, checkEnv, formatZodError, generateEnvDocs, generateEnvExample, getMissingEnv, getUnknownEnv, isSecretKey, loadEnv, mustLoadEnv, sortMetaEntries };
313
+ export { type DocsFormat, type EnvCheckResult, type EnvMeta, type EnvMetaEntry, type GenerateDocsOptions, type LoadEnvFail, type LoadEnvOk, type LoadEnvOptions, type SortMode, checkEnv, formatZodError, generateEnvDocs, generateEnvExample, getEmptyRequiredEnv, getMissingEnv, getUnknownEnv, isSecretKey, loadEnv, mustLoadEnv, sortMetaEntries };
package/dist/index.d.ts CHANGED
@@ -169,6 +169,16 @@ type EnvCheckResult = {
169
169
  * @since 1.2.0
170
170
  */
171
171
  declare function getMissingEnv(meta: EnvMeta, env?: NodeJS.ProcessEnv): string[];
172
+ /**
173
+ * Return required keys from `meta` that are present in `env` but empty after trim.
174
+ *
175
+ * Used by the CLI in `--production` mode to catch dotenv entries like `PORT=` or `PORT=" "`
176
+ * without changing default `check` behavior for whitespace-only values.
177
+ *
178
+ * @public
179
+ * @since 1.5.1
180
+ */
181
+ declare function getEmptyRequiredEnv(meta: EnvMeta, env: Record<string, string>): string[];
172
182
  /**
173
183
  * Return keys present in `env` that are not defined in `meta`.
174
184
  *
@@ -300,4 +310,4 @@ declare function mustLoadEnv<T extends z.ZodTypeAny>(schema: T): z.infer<T>;
300
310
  */
301
311
  declare function formatZodError(err: z.ZodError): string;
302
312
 
303
- export { type DocsFormat, type EnvCheckResult, type EnvMeta, type EnvMetaEntry, type GenerateDocsOptions, type LoadEnvFail, type LoadEnvOk, type LoadEnvOptions, type SortMode, checkEnv, formatZodError, generateEnvDocs, generateEnvExample, getMissingEnv, getUnknownEnv, isSecretKey, loadEnv, mustLoadEnv, sortMetaEntries };
313
+ export { type DocsFormat, type EnvCheckResult, type EnvMeta, type EnvMetaEntry, type GenerateDocsOptions, type LoadEnvFail, type LoadEnvOk, type LoadEnvOptions, type SortMode, checkEnv, formatZodError, generateEnvDocs, generateEnvExample, getEmptyRequiredEnv, getMissingEnv, getUnknownEnv, isSecretKey, loadEnv, mustLoadEnv, sortMetaEntries };
package/dist/index.js CHANGED
@@ -2,11 +2,12 @@ import {
2
2
  checkEnv,
3
3
  generateEnvDocs,
4
4
  generateEnvExample,
5
+ getEmptyRequiredEnv,
5
6
  getMissingEnv,
6
7
  getUnknownEnv,
7
8
  isSecretKey,
8
9
  sortMetaEntries
9
- } from "./chunk-WWWQDMV5.js";
10
+ } from "./chunk-AGSAWBDX.js";
10
11
 
11
12
  // src/index.ts
12
13
  function loadEnv(schema, opts) {
@@ -36,6 +37,7 @@ export {
36
37
  formatZodError,
37
38
  generateEnvDocs,
38
39
  generateEnvExample,
40
+ getEmptyRequiredEnv,
39
41
  getMissingEnv,
40
42
  getUnknownEnv,
41
43
  isSecretKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod-envkit",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Validate environment variables with Zod and generate .env.example",
5
5
  "license": "MIT",
6
6
  "author": "",