zod-envkit 1.3.0 → 1.3.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,4 +1,32 @@
1
- # [1.3.0](https://github.com/nxtxe/zod-envkit/compare/v1.2.3...v1.3.0) (2026-03-04)
1
+ ## [1.3.2](https://github.com/nxtxe/zod-envkit/compare/v1.3.1...v1.3.2) (2026-03-23)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * handle meta fallback edge case ([f54596e](https://github.com/nxtxe/zod-envkit/commit/f54596e9835f47076c079029f2c926228329d990))
7
+
8
+ ## [1.3.2](https://github.com/nxtxe/zod-envkit/compare/v1.3.1...v1.3.2) (2026-03-xx)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * edge hardening for meta fallback:
14
+ * fail with actionable guidance when `.env.example` exists but has no parseable variables
15
+ * keep fallback behavior for valid `.env.example` (minimal meta generation)
16
+ * improve robustness coverage for edge paths:
17
+ * invalid JSON meta
18
+ * missing meta + fallback behavior
19
+ * strict dotenv-only checks
20
+ * repeated runs/idempotency scenarios
21
+
22
+ ## [1.3.1](https://github.com/nxtxe/zod-envkit/compare/v1.3.0...v1.3.1) (2026-03-19)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **cli:** improve env error grouping and hints ([d098a70](https://github.com/nxtxe/zod-envkit/commit/d098a70683f8028b7be275bd16a580f4fccb4938))
28
+
29
+ ## [1.3.0](https://github.com/nxtxe/zod-envkit/compare/v1.2.3...v1.3.0) (2026-03-04)
2
30
 
3
31
 
4
32
  ### Features
@@ -29,16 +29,18 @@ var import_commander = require("commander");
29
29
  // src/messages.ts
30
30
  var messages = {
31
31
  en: {
32
+ ENV_INVALID: "Environment is invalid.",
32
33
  META_NOT_FOUND: "env meta file not found.",
33
34
  META_TRIED: "Tried:",
34
35
  META_TIP: "Tip:",
35
36
  META_PARSE_FAILED: "Failed to read/parse env meta file:",
37
+ META_EXAMPLE_EMPTY: ".env.example exists but has no parseable variables.",
36
38
  META_FALLBACK_EXAMPLE: "env.meta.json not found, falling back to .env.example (minimal meta).",
37
39
  META_FALLBACK_TIP: "Tip: create env.meta.json for richer docs, grouping, and stable CLI behavior.",
38
40
  GENERATED: "Generated: {example}, {docs}",
39
41
  ENV_OK: "Environment looks good.",
40
42
  MISSING_ENV: "Missing required environment variables:",
41
- UNKNOWN_ENV: "Unknown environment variables:",
43
+ UNKNOWN_ENV: "Unknown environment variables (strict mode; only dotenv-loaded keys):",
42
44
  INVALID_FORMAT: "Invalid docs format",
43
45
  INVALID_MASK_MODE: "Invalid mask mode",
44
46
  INVALID_SORT: "Invalid sort mode",
@@ -51,16 +53,18 @@ var messages = {
51
53
  META_HINT_SYNC_SCHEMA: "Hint: add these to your Zod schema or remove from env.meta.json."
52
54
  },
53
55
  ru: {
56
+ ENV_INVALID: "\u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u043D\u044B \u043D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E.",
54
57
  META_NOT_FOUND: "\u0424\u0430\u0439\u043B env.meta.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.",
55
58
  META_TRIED: "\u041F\u0440\u043E\u0431\u043E\u0432\u0430\u043B\u0438:",
56
59
  META_TIP: "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430:",
57
60
  META_PARSE_FAILED: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0438\u043B\u0438 \u0440\u0430\u0441\u043F\u0430\u0440\u0441\u0438\u0442\u044C env meta \u0444\u0430\u0439\u043B:",
61
+ META_EXAMPLE_EMPTY: ".env.example \u043D\u0430\u0439\u0434\u0435\u043D, \u043D\u043E \u0432 \u043D\u0435\u043C \u043D\u0435\u0442 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u0435\u043C\u044B\u0445 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445.",
58
62
  META_FALLBACK_EXAMPLE: "env.meta.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u043C .env.example \u043A\u0430\u043A \u043C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u0443\u044E \u043C\u0435\u0442\u0443.",
59
63
  META_FALLBACK_TIP: "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430: \u043B\u0443\u0447\u0448\u0435 \u0441\u043E\u0437\u0434\u0430\u0442\u044C env.meta.json \u2014 \u0431\u0443\u0434\u0435\u0442 \u0431\u043E\u0433\u0430\u0447\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F \u0438 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u0435\u0435 CLI-\u043A\u043E\u043D\u0442\u0440\u0430\u043A\u0442.",
60
64
  GENERATED: "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E: {example}, {docs}",
61
65
  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.",
62
66
  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:",
63
- 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:",
67
+ 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):",
64
68
  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",
65
69
  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",
66
70
  INVALID_SORT: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0438",
@@ -160,22 +164,38 @@ function loadMeta(lang, configFile) {
160
164
  const raw = import_node_fs.default.readFileSync(found, "utf8");
161
165
  return { meta: JSON.parse(raw), configPath: found };
162
166
  } catch {
163
- fail(lang, "META_PARSE_FAILED", [`- ${found}`]);
167
+ fail(lang, "META_PARSE_FAILED", [
168
+ `- ${found}`,
169
+ "",
170
+ t(lang, "META_TIP"),
171
+ " Run: npx zod-envkit generate -c env.meta.json"
172
+ ]);
164
173
  }
165
174
  }
166
175
  const examplePath = import_node_path.default.resolve(process.cwd(), ".env.example");
167
176
  if (import_node_fs.default.existsSync(examplePath)) {
177
+ const meta = buildMetaFromEnvExample(examplePath);
178
+ const keys = Object.keys(meta);
179
+ if (keys.length === 0) {
180
+ fail(lang, "META_EXAMPLE_EMPTY", [
181
+ `- ${examplePath}`,
182
+ "",
183
+ t(lang, "META_TIP"),
184
+ " Add at least one KEY=value line to .env.example,",
185
+ " or create env.meta.json and run: npx zod-envkit generate -c env.meta.json"
186
+ ]);
187
+ }
168
188
  console.warn(`\u26A0\uFE0F ${t(lang, "META_FALLBACK_EXAMPLE")}`);
169
189
  console.warn(` ${t(lang, "META_FALLBACK_TIP")}`);
170
190
  console.warn("");
171
- return { meta: buildMetaFromEnvExample(examplePath), configPath: examplePath };
191
+ return { meta, configPath: examplePath };
172
192
  }
173
193
  fail(lang, "META_NOT_FOUND", [
174
194
  t(lang, "META_TRIED"),
175
195
  ...candidates.map((p) => `- ${p}`),
176
196
  "",
177
197
  t(lang, "META_TIP"),
178
- " npx zod-envkit show -c examples/env.meta.json"
198
+ " Run: npx zod-envkit generate -c env.meta.json"
179
199
  ]);
180
200
  }
181
201
 
@@ -618,16 +638,24 @@ function registerCheck(program2, getLang2) {
618
638
  const lang = getLang2();
619
639
  const loaded = loadDotEnv(opts.dotenv);
620
640
  const { meta } = loadMeta(lang, opts.config);
641
+ const sections = [];
621
642
  const missing = getMissingEnv(meta, process.env);
622
643
  if (missing.length) {
623
- fail(lang, "MISSING_ENV", missing.map((k) => `- ${k}`));
644
+ sections.push(t(lang, "MISSING_ENV"));
645
+ missing.forEach((k) => sections.push(`- ${k}`));
646
+ sections.push("");
624
647
  }
625
648
  if (opts.strict) {
626
649
  const unknown = getUnknownEnv(meta, loaded.env);
627
650
  if (unknown.length) {
628
- fail(lang, "UNKNOWN_ENV", unknown.map((k) => `- ${k}`));
651
+ sections.push(t(lang, "UNKNOWN_ENV"));
652
+ unknown.forEach((k) => sections.push(`- ${k}`));
653
+ sections.push("");
629
654
  }
630
655
  }
656
+ if (sections.length) {
657
+ fail(lang, "ENV_INVALID", sections);
658
+ }
631
659
  if (opts.schema) {
632
660
  const schemaMode = String(opts.schemaMode ?? "strict").toLowerCase();
633
661
  if (schemaMode !== "warn" && schemaMode !== "strict") {
package/dist/cli/index.js CHANGED
@@ -13,16 +13,18 @@ import { Command } from "commander";
13
13
  // src/messages.ts
14
14
  var messages = {
15
15
  en: {
16
+ ENV_INVALID: "Environment is invalid.",
16
17
  META_NOT_FOUND: "env meta file not found.",
17
18
  META_TRIED: "Tried:",
18
19
  META_TIP: "Tip:",
19
20
  META_PARSE_FAILED: "Failed to read/parse env meta file:",
21
+ META_EXAMPLE_EMPTY: ".env.example exists but has no parseable variables.",
20
22
  META_FALLBACK_EXAMPLE: "env.meta.json not found, falling back to .env.example (minimal meta).",
21
23
  META_FALLBACK_TIP: "Tip: create env.meta.json for richer docs, grouping, and stable CLI behavior.",
22
24
  GENERATED: "Generated: {example}, {docs}",
23
25
  ENV_OK: "Environment looks good.",
24
26
  MISSING_ENV: "Missing required environment variables:",
25
- UNKNOWN_ENV: "Unknown environment variables:",
27
+ UNKNOWN_ENV: "Unknown environment variables (strict mode; only dotenv-loaded keys):",
26
28
  INVALID_FORMAT: "Invalid docs format",
27
29
  INVALID_MASK_MODE: "Invalid mask mode",
28
30
  INVALID_SORT: "Invalid sort mode",
@@ -35,16 +37,18 @@ var messages = {
35
37
  META_HINT_SYNC_SCHEMA: "Hint: add these to your Zod schema or remove from env.meta.json."
36
38
  },
37
39
  ru: {
40
+ ENV_INVALID: "\u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u043D\u044B \u043D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E.",
38
41
  META_NOT_FOUND: "\u0424\u0430\u0439\u043B env.meta.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.",
39
42
  META_TRIED: "\u041F\u0440\u043E\u0431\u043E\u0432\u0430\u043B\u0438:",
40
43
  META_TIP: "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430:",
41
44
  META_PARSE_FAILED: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0438\u043B\u0438 \u0440\u0430\u0441\u043F\u0430\u0440\u0441\u0438\u0442\u044C env meta \u0444\u0430\u0439\u043B:",
45
+ META_EXAMPLE_EMPTY: ".env.example \u043D\u0430\u0439\u0434\u0435\u043D, \u043D\u043E \u0432 \u043D\u0435\u043C \u043D\u0435\u0442 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u0435\u043C\u044B\u0445 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445.",
42
46
  META_FALLBACK_EXAMPLE: "env.meta.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u043C .env.example \u043A\u0430\u043A \u043C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u0443\u044E \u043C\u0435\u0442\u0443.",
43
47
  META_FALLBACK_TIP: "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430: \u043B\u0443\u0447\u0448\u0435 \u0441\u043E\u0437\u0434\u0430\u0442\u044C env.meta.json \u2014 \u0431\u0443\u0434\u0435\u0442 \u0431\u043E\u0433\u0430\u0447\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F \u0438 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u0435\u0435 CLI-\u043A\u043E\u043D\u0442\u0440\u0430\u043A\u0442.",
44
48
  GENERATED: "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E: {example}, {docs}",
45
49
  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.",
46
50
  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:",
47
- 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:",
51
+ 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):",
48
52
  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",
49
53
  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",
50
54
  INVALID_SORT: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0438",
@@ -144,22 +148,38 @@ function loadMeta(lang, configFile) {
144
148
  const raw = fs.readFileSync(found, "utf8");
145
149
  return { meta: JSON.parse(raw), configPath: found };
146
150
  } catch {
147
- fail(lang, "META_PARSE_FAILED", [`- ${found}`]);
151
+ fail(lang, "META_PARSE_FAILED", [
152
+ `- ${found}`,
153
+ "",
154
+ t(lang, "META_TIP"),
155
+ " Run: npx zod-envkit generate -c env.meta.json"
156
+ ]);
148
157
  }
149
158
  }
150
159
  const examplePath = path.resolve(process.cwd(), ".env.example");
151
160
  if (fs.existsSync(examplePath)) {
161
+ const meta = buildMetaFromEnvExample(examplePath);
162
+ const keys = Object.keys(meta);
163
+ if (keys.length === 0) {
164
+ fail(lang, "META_EXAMPLE_EMPTY", [
165
+ `- ${examplePath}`,
166
+ "",
167
+ t(lang, "META_TIP"),
168
+ " Add at least one KEY=value line to .env.example,",
169
+ " or create env.meta.json and run: npx zod-envkit generate -c env.meta.json"
170
+ ]);
171
+ }
152
172
  console.warn(`\u26A0\uFE0F ${t(lang, "META_FALLBACK_EXAMPLE")}`);
153
173
  console.warn(` ${t(lang, "META_FALLBACK_TIP")}`);
154
174
  console.warn("");
155
- return { meta: buildMetaFromEnvExample(examplePath), configPath: examplePath };
175
+ return { meta, configPath: examplePath };
156
176
  }
157
177
  fail(lang, "META_NOT_FOUND", [
158
178
  t(lang, "META_TRIED"),
159
179
  ...candidates.map((p) => `- ${p}`),
160
180
  "",
161
181
  t(lang, "META_TIP"),
162
- " npx zod-envkit show -c examples/env.meta.json"
182
+ " Run: npx zod-envkit generate -c env.meta.json"
163
183
  ]);
164
184
  }
165
185
 
@@ -397,16 +417,24 @@ function registerCheck(program2, getLang2) {
397
417
  const lang = getLang2();
398
418
  const loaded = loadDotEnv(opts.dotenv);
399
419
  const { meta } = loadMeta(lang, opts.config);
420
+ const sections = [];
400
421
  const missing = getMissingEnv(meta, process.env);
401
422
  if (missing.length) {
402
- fail(lang, "MISSING_ENV", missing.map((k) => `- ${k}`));
423
+ sections.push(t(lang, "MISSING_ENV"));
424
+ missing.forEach((k) => sections.push(`- ${k}`));
425
+ sections.push("");
403
426
  }
404
427
  if (opts.strict) {
405
428
  const unknown = getUnknownEnv(meta, loaded.env);
406
429
  if (unknown.length) {
407
- fail(lang, "UNKNOWN_ENV", unknown.map((k) => `- ${k}`));
430
+ sections.push(t(lang, "UNKNOWN_ENV"));
431
+ unknown.forEach((k) => sections.push(`- ${k}`));
432
+ sections.push("");
408
433
  }
409
434
  }
435
+ if (sections.length) {
436
+ fail(lang, "ENV_INVALID", sections);
437
+ }
410
438
  if (opts.schema) {
411
439
  const schemaMode = String(opts.schemaMode ?? "strict").toLowerCase();
412
440
  if (schemaMode !== "warn" && schemaMode !== "strict") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod-envkit",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Validate environment variables with Zod and generate .env.example",
5
5
  "license": "MIT",
6
6
  "author": "",