zod-envkit 1.2.2 → 1.2.3

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.2.3](https://github.com/nxtxe/zod-envkit/compare/v1.2.2...v1.2.3) (2026-02-19)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * reliability polish (meta fallback, strict isolation, deterministic errors) ([a439230](https://github.com/nxtxe/zod-envkit/commit/a439230ccd06f7c7dbd70963cdfe33ed9fae9c57))
7
+
1
8
  ## [1.2.2](https://github.com/nxtxe/zod-envkit/compare/v1.2.1...v1.2.2) (2026-02-16)
2
9
 
3
10
 
@@ -33,6 +33,8 @@ var messages = {
33
33
  META_TRIED: "Tried:",
34
34
  META_TIP: "Tip:",
35
35
  META_PARSE_FAILED: "Failed to read/parse env meta file:",
36
+ META_FALLBACK_EXAMPLE: "env.meta.json not found, falling back to .env.example (minimal meta).",
37
+ META_FALLBACK_TIP: "Tip: create env.meta.json for richer docs, grouping, and stable CLI behavior.",
36
38
  GENERATED: "Generated: {example}, {docs}",
37
39
  ENV_OK: "Environment looks good.",
38
40
  MISSING_ENV: "Missing required environment variables:",
@@ -47,6 +49,8 @@ var messages = {
47
49
  META_TRIED: "\u041F\u0440\u043E\u0431\u043E\u0432\u0430\u043B\u0438:",
48
50
  META_TIP: "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430:",
49
51
  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:",
52
+ 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.",
53
+ 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.",
50
54
  GENERATED: "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E: {example}, {docs}",
51
55
  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.",
52
56
  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:",
@@ -59,6 +63,9 @@ var messages = {
59
63
  };
60
64
 
61
65
  // src/i18n.ts
66
+ function escapeRegExp(source) {
67
+ return source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
68
+ }
62
69
  function resolveLang(cliLang) {
63
70
  if (cliLang === "ru" || cliLang === "en") return cliLang;
64
71
  const envLang = process.env.LANG?.toLowerCase();
@@ -69,7 +76,8 @@ function t(lang, key, vars) {
69
76
  let text = messages[lang][key] ?? messages.en[key];
70
77
  if (vars) {
71
78
  for (const [k, v] of Object.entries(vars)) {
72
- text = text.replace(`{${k}}`, v);
79
+ const pattern = new RegExp(`\\{${escapeRegExp(k)}\\}`, "g");
80
+ text = text.replace(pattern, v);
73
81
  }
74
82
  }
75
83
  return text;
@@ -95,6 +103,7 @@ var import_node_fs2 = __toESM(require("fs"), 1);
95
103
  // src/cli/lib/meta.ts
96
104
  var import_node_fs = __toESM(require("fs"), 1);
97
105
  var import_node_path = __toESM(require("path"), 1);
106
+ var import_dotenv = __toESM(require("dotenv"), 1);
98
107
 
99
108
  // src/cli/lib/fail.ts
100
109
  function fail(lang, key, details) {
@@ -118,24 +127,44 @@ function resolveMetaPath(configFile) {
118
127
  const found = candidates.find((p) => import_node_fs.default.existsSync(p));
119
128
  return { candidates, found };
120
129
  }
130
+ function buildMetaFromEnvExample(examplePath) {
131
+ const raw = import_node_fs.default.readFileSync(examplePath, "utf8");
132
+ const parsed = import_dotenv.default.parse(raw);
133
+ const meta = {};
134
+ const keys = Object.keys(parsed).sort((a, b) => a.localeCompare(b));
135
+ for (const key of keys) {
136
+ meta[key] = {
137
+ example: parsed[key] ?? "",
138
+ required: true,
139
+ description: ""
140
+ };
141
+ }
142
+ return meta;
143
+ }
121
144
  function loadMeta(lang, configFile) {
122
145
  const { candidates, found } = resolveMetaPath(configFile);
123
- if (!found) {
124
- fail(lang, "META_NOT_FOUND", [
125
- t(lang, "META_TRIED"),
126
- ...candidates.map((p) => `- ${p}`),
127
- "",
128
- t(lang, "META_TIP"),
129
- " npx zod-envkit show -c examples/env.meta.json"
130
- ]);
146
+ if (found) {
147
+ try {
148
+ const raw = import_node_fs.default.readFileSync(found, "utf8");
149
+ return { meta: JSON.parse(raw), configPath: found };
150
+ } catch {
151
+ fail(lang, "META_PARSE_FAILED", [`- ${found}`]);
152
+ }
131
153
  }
132
- const configPath = found;
133
- try {
134
- const raw = import_node_fs.default.readFileSync(configPath, "utf8");
135
- return { meta: JSON.parse(raw), configPath };
136
- } catch {
137
- fail(lang, "META_PARSE_FAILED", [`- ${configPath}`]);
154
+ const examplePath = import_node_path.default.resolve(process.cwd(), ".env.example");
155
+ if (import_node_fs.default.existsSync(examplePath)) {
156
+ console.warn(`\u26A0\uFE0F ${t(lang, "META_FALLBACK_EXAMPLE")}`);
157
+ console.warn(` ${t(lang, "META_FALLBACK_TIP")}`);
158
+ console.warn("");
159
+ return { meta: buildMetaFromEnvExample(examplePath), configPath: examplePath };
138
160
  }
161
+ fail(lang, "META_NOT_FOUND", [
162
+ t(lang, "META_TRIED"),
163
+ ...candidates.map((p) => `- ${p}`),
164
+ "",
165
+ t(lang, "META_TIP"),
166
+ " npx zod-envkit show -c examples/env.meta.json"
167
+ ]);
139
168
  }
140
169
 
141
170
  // src/generate.ts
@@ -345,12 +374,15 @@ function registerGenerate(program2, getLang2) {
345
374
  // src/cli/lib/dotenv.ts
346
375
  var import_node_fs3 = __toESM(require("fs"), 1);
347
376
  var import_node_path2 = __toESM(require("path"), 1);
348
- var import_dotenv = __toESM(require("dotenv"), 1);
377
+ var import_dotenv2 = __toESM(require("dotenv"), 1);
349
378
  function resolvePath(p) {
350
379
  return import_node_path2.default.resolve(process.cwd(), p);
351
380
  }
352
381
  function loadDotEnv(files) {
353
- const list = files?.split(",").map((s) => s.trim()).filter(Boolean) ?? [".env"];
382
+ let list = files?.split(",").map((s) => s.trim()).filter(Boolean) ?? [".env"];
383
+ if (list.length === 0) {
384
+ list = [".env"];
385
+ }
354
386
  const loaded = [];
355
387
  const skipped = [];
356
388
  const merged = {};
@@ -361,7 +393,7 @@ function loadDotEnv(files) {
361
393
  continue;
362
394
  }
363
395
  const raw = import_node_fs3.default.readFileSync(abs, "utf8");
364
- const parsed = import_dotenv.default.parse(raw);
396
+ const parsed = import_dotenv2.default.parse(raw);
365
397
  for (const [k, v] of Object.entries(parsed)) merged[k] = v;
366
398
  for (const [k, v] of Object.entries(parsed)) process.env[k] = v;
367
399
  loaded.push(f);
@@ -479,12 +511,18 @@ function isSortMode2(v) {
479
511
  return v === "alpha" || v === "required-first" || v === "none";
480
512
  }
481
513
  function registerShow(program2, getLang2) {
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) => {
514
+ 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)").option("--no-mask", "Alias for --mask-mode none").option("--sort <mode>", "Sort mode (alpha | required-first | none)", "none").action((opts) => {
483
515
  const lang = getLang2();
484
516
  loadDotEnv(String(opts.dotenv ?? ".env"));
485
517
  const { meta } = loadMeta(lang, String(opts.config ?? "env.meta.json"));
486
- const fallbackMode = opts.mask === false ? "none" : "partial";
487
- const modeRaw = String(opts.maskMode ?? fallbackMode);
518
+ let modeRaw;
519
+ if (opts.mask === false) {
520
+ modeRaw = "none";
521
+ } else if (opts.maskMode) {
522
+ modeRaw = String(opts.maskMode);
523
+ } else {
524
+ modeRaw = "partial";
525
+ }
488
526
  if (!isMaskMode(modeRaw)) {
489
527
  console.error(`\u274C ${t(lang, "INVALID_MASK_MODE")}: ${modeRaw}`);
490
528
  console.error(`- partial | full | none`);
@@ -540,12 +578,12 @@ function registerCheck(program2, getLang2) {
540
578
  // src/cli/commands/init.ts
541
579
  var import_node_fs4 = __toESM(require("fs"), 1);
542
580
  var import_node_path3 = __toESM(require("path"), 1);
543
- var import_dotenv4 = __toESM(require("dotenv"), 1);
581
+ var import_dotenv5 = __toESM(require("dotenv"), 1);
544
582
  function readEnvFile(file) {
545
583
  const abs = import_node_path3.default.resolve(process.cwd(), file);
546
584
  if (!import_node_fs4.default.existsSync(abs)) return {};
547
585
  const raw = import_node_fs4.default.readFileSync(abs, "utf8");
548
- return import_dotenv4.default.parse(raw);
586
+ return import_dotenv5.default.parse(raw);
549
587
  }
550
588
  function metaFromEnv(env, defaultGroup) {
551
589
  const meta = {};
package/dist/cli/index.js CHANGED
@@ -17,6 +17,8 @@ var messages = {
17
17
  META_TRIED: "Tried:",
18
18
  META_TIP: "Tip:",
19
19
  META_PARSE_FAILED: "Failed to read/parse env meta file:",
20
+ META_FALLBACK_EXAMPLE: "env.meta.json not found, falling back to .env.example (minimal meta).",
21
+ META_FALLBACK_TIP: "Tip: create env.meta.json for richer docs, grouping, and stable CLI behavior.",
20
22
  GENERATED: "Generated: {example}, {docs}",
21
23
  ENV_OK: "Environment looks good.",
22
24
  MISSING_ENV: "Missing required environment variables:",
@@ -31,6 +33,8 @@ var messages = {
31
33
  META_TRIED: "\u041F\u0440\u043E\u0431\u043E\u0432\u0430\u043B\u0438:",
32
34
  META_TIP: "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430:",
33
35
  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:",
36
+ 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.",
37
+ 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.",
34
38
  GENERATED: "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E: {example}, {docs}",
35
39
  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.",
36
40
  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:",
@@ -43,6 +47,9 @@ var messages = {
43
47
  };
44
48
 
45
49
  // src/i18n.ts
50
+ function escapeRegExp(source) {
51
+ return source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
52
+ }
46
53
  function resolveLang(cliLang) {
47
54
  if (cliLang === "ru" || cliLang === "en") return cliLang;
48
55
  const envLang = process.env.LANG?.toLowerCase();
@@ -53,7 +60,8 @@ function t(lang, key, vars) {
53
60
  let text = messages[lang][key] ?? messages.en[key];
54
61
  if (vars) {
55
62
  for (const [k, v] of Object.entries(vars)) {
56
- text = text.replace(`{${k}}`, v);
63
+ const pattern = new RegExp(`\\{${escapeRegExp(k)}\\}`, "g");
64
+ text = text.replace(pattern, v);
57
65
  }
58
66
  }
59
67
  return text;
@@ -79,6 +87,7 @@ import fs2 from "fs";
79
87
  // src/cli/lib/meta.ts
80
88
  import fs from "fs";
81
89
  import path from "path";
90
+ import dotenv from "dotenv";
82
91
 
83
92
  // src/cli/lib/fail.ts
84
93
  function fail(lang, key, details) {
@@ -102,24 +111,44 @@ function resolveMetaPath(configFile) {
102
111
  const found = candidates.find((p) => fs.existsSync(p));
103
112
  return { candidates, found };
104
113
  }
114
+ function buildMetaFromEnvExample(examplePath) {
115
+ const raw = fs.readFileSync(examplePath, "utf8");
116
+ const parsed = dotenv.parse(raw);
117
+ const meta = {};
118
+ const keys = Object.keys(parsed).sort((a, b) => a.localeCompare(b));
119
+ for (const key of keys) {
120
+ meta[key] = {
121
+ example: parsed[key] ?? "",
122
+ required: true,
123
+ description: ""
124
+ };
125
+ }
126
+ return meta;
127
+ }
105
128
  function loadMeta(lang, configFile) {
106
129
  const { candidates, found } = resolveMetaPath(configFile);
107
- if (!found) {
108
- fail(lang, "META_NOT_FOUND", [
109
- t(lang, "META_TRIED"),
110
- ...candidates.map((p) => `- ${p}`),
111
- "",
112
- t(lang, "META_TIP"),
113
- " npx zod-envkit show -c examples/env.meta.json"
114
- ]);
130
+ if (found) {
131
+ try {
132
+ const raw = fs.readFileSync(found, "utf8");
133
+ return { meta: JSON.parse(raw), configPath: found };
134
+ } catch {
135
+ fail(lang, "META_PARSE_FAILED", [`- ${found}`]);
136
+ }
115
137
  }
116
- const configPath = found;
117
- try {
118
- const raw = fs.readFileSync(configPath, "utf8");
119
- return { meta: JSON.parse(raw), configPath };
120
- } catch {
121
- fail(lang, "META_PARSE_FAILED", [`- ${configPath}`]);
138
+ const examplePath = path.resolve(process.cwd(), ".env.example");
139
+ if (fs.existsSync(examplePath)) {
140
+ console.warn(`\u26A0\uFE0F ${t(lang, "META_FALLBACK_EXAMPLE")}`);
141
+ console.warn(` ${t(lang, "META_FALLBACK_TIP")}`);
142
+ console.warn("");
143
+ return { meta: buildMetaFromEnvExample(examplePath), configPath: examplePath };
122
144
  }
145
+ fail(lang, "META_NOT_FOUND", [
146
+ t(lang, "META_TRIED"),
147
+ ...candidates.map((p) => `- ${p}`),
148
+ "",
149
+ t(lang, "META_TIP"),
150
+ " npx zod-envkit show -c examples/env.meta.json"
151
+ ]);
123
152
  }
124
153
 
125
154
  // src/cli/commands/generate.ts
@@ -166,12 +195,15 @@ function registerGenerate(program2, getLang2) {
166
195
  // src/cli/lib/dotenv.ts
167
196
  import fs3 from "fs";
168
197
  import path2 from "path";
169
- import dotenv from "dotenv";
198
+ import dotenv2 from "dotenv";
170
199
  function resolvePath(p) {
171
200
  return path2.resolve(process.cwd(), p);
172
201
  }
173
202
  function loadDotEnv(files) {
174
- const list = files?.split(",").map((s) => s.trim()).filter(Boolean) ?? [".env"];
203
+ let list = files?.split(",").map((s) => s.trim()).filter(Boolean) ?? [".env"];
204
+ if (list.length === 0) {
205
+ list = [".env"];
206
+ }
175
207
  const loaded = [];
176
208
  const skipped = [];
177
209
  const merged = {};
@@ -182,7 +214,7 @@ function loadDotEnv(files) {
182
214
  continue;
183
215
  }
184
216
  const raw = fs3.readFileSync(abs, "utf8");
185
- const parsed = dotenv.parse(raw);
217
+ const parsed = dotenv2.parse(raw);
186
218
  for (const [k, v] of Object.entries(parsed)) merged[k] = v;
187
219
  for (const [k, v] of Object.entries(parsed)) process.env[k] = v;
188
220
  loaded.push(f);
@@ -267,12 +299,18 @@ function isSortMode2(v) {
267
299
  return v === "alpha" || v === "required-first" || v === "none";
268
300
  }
269
301
  function registerShow(program2, getLang2) {
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) => {
302
+ 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)").option("--no-mask", "Alias for --mask-mode none").option("--sort <mode>", "Sort mode (alpha | required-first | none)", "none").action((opts) => {
271
303
  const lang = getLang2();
272
304
  loadDotEnv(String(opts.dotenv ?? ".env"));
273
305
  const { meta } = loadMeta(lang, String(opts.config ?? "env.meta.json"));
274
- const fallbackMode = opts.mask === false ? "none" : "partial";
275
- const modeRaw = String(opts.maskMode ?? fallbackMode);
306
+ let modeRaw;
307
+ if (opts.mask === false) {
308
+ modeRaw = "none";
309
+ } else if (opts.maskMode) {
310
+ modeRaw = String(opts.maskMode);
311
+ } else {
312
+ modeRaw = "partial";
313
+ }
276
314
  if (!isMaskMode(modeRaw)) {
277
315
  console.error(`\u274C ${t(lang, "INVALID_MASK_MODE")}: ${modeRaw}`);
278
316
  console.error(`- partial | full | none`);
@@ -328,12 +366,12 @@ function registerCheck(program2, getLang2) {
328
366
  // src/cli/commands/init.ts
329
367
  import fs4 from "fs";
330
368
  import path3 from "path";
331
- import dotenv2 from "dotenv";
369
+ import dotenv3 from "dotenv";
332
370
  function readEnvFile(file) {
333
371
  const abs = path3.resolve(process.cwd(), file);
334
372
  if (!fs4.existsSync(abs)) return {};
335
373
  const raw = fs4.readFileSync(abs, "utf8");
336
- return dotenv2.parse(raw);
374
+ return dotenv3.parse(raw);
337
375
  }
338
376
  function metaFromEnv(env, defaultGroup) {
339
377
  const meta = {};
package/dist/index.cjs CHANGED
@@ -247,7 +247,14 @@ function mustLoadEnv(schema) {
247
247
  throw res.error;
248
248
  }
249
249
  function formatZodError(err) {
250
- return err.issues.map((i) => `- ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
250
+ return err.issues.slice().sort((a, b) => {
251
+ const pa = a.path.length ? a.path.join(".") : "";
252
+ const pb = b.path.length ? b.path.join(".") : "";
253
+ return pa.localeCompare(pb);
254
+ }).map((issue) => {
255
+ const path = issue.path.length ? issue.path.join(".") : "(root)";
256
+ return `- ${path}: ${issue.message.trim()}`;
257
+ }).join("\n");
251
258
  }
252
259
  // Annotate the CommonJS export names for ESM import in node:
253
260
  0 && (module.exports = {
package/dist/index.d.cts CHANGED
@@ -274,10 +274,8 @@ declare function mustLoadEnv<T extends z.ZodTypeAny>(schema: T): z.infer<T>;
274
274
  /**
275
275
  * Format `ZodError` into a human-friendly multi-line message (one issue per line).
276
276
  *
277
- * @example
278
- * ```ts
279
- * console.error("Invalid environment:\n" + formatZodError(err));
280
- * ```
277
+ * Output format (stable in 1.x):
278
+ * - path: message
281
279
  *
282
280
  * @public
283
281
  * @since 1.0.0
package/dist/index.d.ts CHANGED
@@ -274,10 +274,8 @@ declare function mustLoadEnv<T extends z.ZodTypeAny>(schema: T): z.infer<T>;
274
274
  /**
275
275
  * Format `ZodError` into a human-friendly multi-line message (one issue per line).
276
276
  *
277
- * @example
278
- * ```ts
279
- * console.error("Invalid environment:\n" + formatZodError(err));
280
- * ```
277
+ * Output format (stable in 1.x):
278
+ * - path: message
281
279
  *
282
280
  * @public
283
281
  * @since 1.0.0
package/dist/index.js CHANGED
@@ -21,7 +21,14 @@ function mustLoadEnv(schema) {
21
21
  throw res.error;
22
22
  }
23
23
  function formatZodError(err) {
24
- return err.issues.map((i) => `- ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
24
+ return err.issues.slice().sort((a, b) => {
25
+ const pa = a.path.length ? a.path.join(".") : "";
26
+ const pb = b.path.length ? b.path.join(".") : "";
27
+ return pa.localeCompare(pb);
28
+ }).map((issue) => {
29
+ const path = issue.path.length ? issue.path.join(".") : "(root)";
30
+ return `- ${path}: ${issue.message.trim()}`;
31
+ }).join("\n");
25
32
  }
26
33
  export {
27
34
  checkEnv,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod-envkit",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Validate environment variables with Zod and generate .env.example",
5
5
  "license": "MIT",
6
6
  "author": "",