zod-envkit 1.5.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,10 @@
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
+
1
8
  # [1.5.0](https://github.com/nxtxe/zod-envkit/compare/v1.4.0...v1.5.0) (2026-06-06)
2
9
 
3
10
 
package/README.RU.md CHANGED
@@ -242,6 +242,7 @@ npx zod-envkit check --production
242
242
  - завершает процесс с кодом `1`, если отсутствуют обязательные переменные
243
243
  - в `--strict` режиме также падает при неизвестных переменных (только ключи из dotenv)
244
244
  - с `--production` также падает при неизвестных переменных из dotenv (тот же dotenv-only scope, что у `--strict`)
245
+ - с `--production` также падает, если обязательный ключ есть в dotenv, но пустой после trim (`PORT=`, `PORT=" "`)
245
246
 
246
247
  ---
247
248
 
package/README.md CHANGED
@@ -239,6 +239,7 @@ npx zod-envkit check --production
239
239
  * exits with code `1` if required variables are missing
240
240
  * in `--strict` mode also fails on unknown variables (dotenv-loaded keys only)
241
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=" "`)
242
243
 
243
244
  ---
244
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 = [];
@@ -652,12 +665,21 @@ function registerCheck(program2, getLang2) {
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
  }
678
+ if (production && emptyRequired.length) {
679
+ sections.push(t(lang, "EMPTY_REQUIRED_ENV"));
680
+ emptyRequired.forEach((k) => sections.push(`- ${k}`));
681
+ sections.push("");
682
+ }
661
683
  if (strictEffective) {
662
684
  const unknown = getUnknownEnv(meta, loaded.env);
663
685
  if (unknown.length) {
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",
@@ -431,12 +434,21 @@ function registerCheck(program2, getLang2) {
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
  }
447
+ if (production && emptyRequired.length) {
448
+ sections.push(t(lang, "EMPTY_REQUIRED_ENV"));
449
+ emptyRequired.forEach((k) => sections.push(`- ${k}`));
450
+ sections.push("");
451
+ }
440
452
  if (strictEffective) {
441
453
  const unknown = getUnknownEnv(meta, loaded.env);
442
454
  if (unknown.length) {
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.5.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": "",