zod-envkit 1.1.1 → 1.2.0

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,16 +1,41 @@
1
- ## [1.1.1](https://github.com/nxtxe/zod-envkit/compare/v1.1.0...v1.1.1) (2026-01-29)
1
+ # [1.2.0](https://github.com/nxtxe/zod-envkit/compare/v1.1.2...v1.2.0) (2026-02-16)
2
+
3
+
4
+ ### Features
5
+
6
+ * stabilize preprod test suite, CLI E2E coverage, and CI pipeline ([463ea6e](https://github.com/nxtxe/zod-envkit/commit/463ea6e4676662dbbbe82bc79e0d85b079b524f8))
7
+
8
+ ## [1.1.2](https://github.com/nxtxe/zod-envkit/compare/v1.1.1...v1.1.2) (2026-01-29)
2
9
 
3
10
 
4
11
  ### Bug Fixes
5
12
 
6
- * **cli:** stabilize options validation and docs links ([640b12b](https://github.com/nxtxe/zod-envkit/commit/640b12bf95304da40c998374ba7ed08b7400e88d))
7
- * stabilize public API, CLI behavior, and documentation ([4604298](https://github.com/nxtxe/zod-envkit/commit/46042981f146f021919da8a4a713b2d251c542d9))
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))
8
15
 
9
16
  # Changelog
10
17
 
11
18
  All notable changes to this project will be documented in this file.
12
19
  This project follows [Semantic Versioning](https://semver.org/).
13
20
 
21
+ ## [1.1.2](https://github.com/nxtxe/zod-envkit/compare/v1.1.1...v1.1.2) (2026-01-29)
22
+
23
+ ### Bug Fixes
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
29
+
30
+
31
+ ## [1.1.1](https://github.com/nxtxe/zod-envkit/compare/v1.1.0...v1.1.1) (2026-01-29)
32
+
33
+
34
+ ### Bug Fixes
35
+
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))
38
+
14
39
  ---
15
40
 
16
41
  ## [1.1.0] – 2026-01-27
@@ -346,6 +346,7 @@ function loadDotEnv(files) {
346
346
  const list = files?.split(",").map((s) => s.trim()).filter(Boolean) ?? [".env"];
347
347
  const loaded = [];
348
348
  const skipped = [];
349
+ const merged = {};
349
350
  for (const f of list) {
350
351
  const abs = resolvePath(f);
351
352
  if (!import_node_fs3.default.existsSync(abs)) {
@@ -354,10 +355,11 @@ function loadDotEnv(files) {
354
355
  }
355
356
  const raw = import_node_fs3.default.readFileSync(abs, "utf8");
356
357
  const parsed = import_dotenv.default.parse(raw);
358
+ for (const [k, v] of Object.entries(parsed)) merged[k] = v;
357
359
  for (const [k, v] of Object.entries(parsed)) process.env[k] = v;
358
360
  loaded.push(f);
359
361
  }
360
- return { loaded, skipped };
362
+ return { loaded, skipped, env: merged };
361
363
  }
362
364
 
363
365
  // src/env.ts
@@ -491,9 +493,9 @@ function registerShow(program2, getLang2) {
491
493
 
492
494
  // src/cli/commands/check.ts
493
495
  function registerCheck(program2, getLang2) {
494
- program2.command("check").description("Exit with code 1 if env is invalid (loads dotenv)").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--dotenv <list>", "Comma-separated dotenv files (default: .env)", ".env").option("--strict", "Fail if unknown env vars are present").action((opts) => {
496
+ program2.command("check").description("Exit with code 1 if env is invalid (loads dotenv)").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--dotenv <list>", "Comma-separated dotenv files (default: .env)", ".env").option("--strict", "Fail if unknown env vars are present (dotenv-only)").action((opts) => {
495
497
  const lang = getLang2();
496
- loadDotEnv(String(opts.dotenv ?? ".env"));
498
+ const loaded = loadDotEnv(opts.dotenv);
497
499
  const { meta } = loadMeta(lang, opts.config);
498
500
  const missing = getMissingEnv(meta, process.env);
499
501
  if (missing.length) {
@@ -501,8 +503,8 @@ function registerCheck(program2, getLang2) {
501
503
  for (const k of missing) console.error(`- ${k}`);
502
504
  process.exit(1);
503
505
  }
504
- if (Boolean(opts.strict)) {
505
- const unknown = getUnknownEnv(meta, process.env);
506
+ if (opts.strict) {
507
+ const unknown = getUnknownEnv(meta, loaded.env);
506
508
  if (unknown.length) {
507
509
  console.error(`\u274C ${t(lang, "UNKNOWN_ENV")}`);
508
510
  for (const k of unknown) console.error(`- ${k}`);
@@ -538,18 +540,19 @@ function metaFromEnv(env, defaultGroup) {
538
540
  return meta;
539
541
  }
540
542
  function registerInit(program2, getLang2) {
541
- program2.command("init").description("Initialize env.meta.json from .env.example (or generate .env.example from meta)").option("--input <file>", "Input file (default: .env.example)", ".env.example").option("--output <file>", "Output file (default: env.meta.json)", "env.meta.json").option("--from-meta", "Generate .env.example from env.meta.json instead").option("--group <name>", "Default group for all vars (when generating meta)").action((opts) => {
543
+ program2.command("init").description("Initialize env.meta.json from .env.example (or generate .env.example from meta)").option("--input <file>", "Input file (.env.example or env.meta.json)").option("--output <file>", "Output file (env.meta.json or .env.example)").option("--from-meta", "Generate .env.example from env.meta.json instead").option("--group <name>", "Default group for all vars (when generating meta)").action((opts) => {
542
544
  const lang = getLang2();
543
- const input = String(opts.input ?? ".env.example");
544
- const output = String(opts.output ?? "env.meta.json");
545
- if (Boolean(opts.fromMeta)) {
545
+ const fromMeta = Boolean(opts.fromMeta);
546
+ const input = String(opts.input ?? (fromMeta ? "env.meta.json" : ".env.example"));
547
+ const output = String(opts.output ?? (fromMeta ? ".env.example" : "env.meta.json"));
548
+ if (fromMeta) {
546
549
  const { meta: meta2 } = loadMeta(lang, input);
547
550
  import_node_fs4.default.writeFileSync(output, generateEnvExample(meta2), "utf8");
548
551
  process.exit(0);
549
552
  }
550
553
  const env = readEnvFile(input);
551
554
  if (Object.keys(env).length === 0) {
552
- fail(lang, "INIT_INPUT_EMPTY", [`- ${input}`]);
555
+ fail(lang, "META_PARSE_FAILED", [`- ${t(lang, "INIT_INPUT_EMPTY")} ${input}`]);
553
556
  }
554
557
  const meta = metaFromEnv(env, opts.group ? String(opts.group) : void 0);
555
558
  import_node_fs4.default.writeFileSync(output, JSON.stringify(meta, null, 2) + "\n", "utf8");
package/dist/cli/index.js CHANGED
@@ -167,6 +167,7 @@ function loadDotEnv(files) {
167
167
  const list = files?.split(",").map((s) => s.trim()).filter(Boolean) ?? [".env"];
168
168
  const loaded = [];
169
169
  const skipped = [];
170
+ const merged = {};
170
171
  for (const f of list) {
171
172
  const abs = resolvePath(f);
172
173
  if (!fs3.existsSync(abs)) {
@@ -175,10 +176,11 @@ function loadDotEnv(files) {
175
176
  }
176
177
  const raw = fs3.readFileSync(abs, "utf8");
177
178
  const parsed = dotenv.parse(raw);
179
+ for (const [k, v] of Object.entries(parsed)) merged[k] = v;
178
180
  for (const [k, v] of Object.entries(parsed)) process.env[k] = v;
179
181
  loaded.push(f);
180
182
  }
181
- return { loaded, skipped };
183
+ return { loaded, skipped, env: merged };
182
184
  }
183
185
 
184
186
  // src/cli/lib/mask.ts
@@ -279,9 +281,9 @@ function registerShow(program2, getLang2) {
279
281
 
280
282
  // src/cli/commands/check.ts
281
283
  function registerCheck(program2, getLang2) {
282
- program2.command("check").description("Exit with code 1 if env is invalid (loads dotenv)").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--dotenv <list>", "Comma-separated dotenv files (default: .env)", ".env").option("--strict", "Fail if unknown env vars are present").action((opts) => {
284
+ program2.command("check").description("Exit with code 1 if env is invalid (loads dotenv)").option("-c, --config <file>", "Path to env meta json", "env.meta.json").option("--dotenv <list>", "Comma-separated dotenv files (default: .env)", ".env").option("--strict", "Fail if unknown env vars are present (dotenv-only)").action((opts) => {
283
285
  const lang = getLang2();
284
- loadDotEnv(String(opts.dotenv ?? ".env"));
286
+ const loaded = loadDotEnv(opts.dotenv);
285
287
  const { meta } = loadMeta(lang, opts.config);
286
288
  const missing = getMissingEnv(meta, process.env);
287
289
  if (missing.length) {
@@ -289,8 +291,8 @@ function registerCheck(program2, getLang2) {
289
291
  for (const k of missing) console.error(`- ${k}`);
290
292
  process.exit(1);
291
293
  }
292
- if (Boolean(opts.strict)) {
293
- const unknown = getUnknownEnv(meta, process.env);
294
+ if (opts.strict) {
295
+ const unknown = getUnknownEnv(meta, loaded.env);
294
296
  if (unknown.length) {
295
297
  console.error(`\u274C ${t(lang, "UNKNOWN_ENV")}`);
296
298
  for (const k of unknown) console.error(`- ${k}`);
@@ -326,18 +328,19 @@ function metaFromEnv(env, defaultGroup) {
326
328
  return meta;
327
329
  }
328
330
  function registerInit(program2, getLang2) {
329
- program2.command("init").description("Initialize env.meta.json from .env.example (or generate .env.example from meta)").option("--input <file>", "Input file (default: .env.example)", ".env.example").option("--output <file>", "Output file (default: env.meta.json)", "env.meta.json").option("--from-meta", "Generate .env.example from env.meta.json instead").option("--group <name>", "Default group for all vars (when generating meta)").action((opts) => {
331
+ program2.command("init").description("Initialize env.meta.json from .env.example (or generate .env.example from meta)").option("--input <file>", "Input file (.env.example or env.meta.json)").option("--output <file>", "Output file (env.meta.json or .env.example)").option("--from-meta", "Generate .env.example from env.meta.json instead").option("--group <name>", "Default group for all vars (when generating meta)").action((opts) => {
330
332
  const lang = getLang2();
331
- const input = String(opts.input ?? ".env.example");
332
- const output = String(opts.output ?? "env.meta.json");
333
- if (Boolean(opts.fromMeta)) {
333
+ const fromMeta = Boolean(opts.fromMeta);
334
+ const input = String(opts.input ?? (fromMeta ? "env.meta.json" : ".env.example"));
335
+ const output = String(opts.output ?? (fromMeta ? ".env.example" : "env.meta.json"));
336
+ if (fromMeta) {
334
337
  const { meta: meta2 } = loadMeta(lang, input);
335
338
  fs4.writeFileSync(output, generateEnvExample(meta2), "utf8");
336
339
  process.exit(0);
337
340
  }
338
341
  const env = readEnvFile(input);
339
342
  if (Object.keys(env).length === 0) {
340
- fail(lang, "INIT_INPUT_EMPTY", [`- ${input}`]);
343
+ fail(lang, "META_PARSE_FAILED", [`- ${t(lang, "INIT_INPUT_EMPTY")} ${input}`]);
341
344
  }
342
345
  const meta = metaFromEnv(env, opts.group ? String(opts.group) : void 0);
343
346
  fs4.writeFileSync(output, JSON.stringify(meta, null, 2) + "\n", "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod-envkit",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Validate environment variables with Zod and generate .env.example",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -38,16 +38,24 @@
38
38
  "README.md",
39
39
  "README.RU.md",
40
40
  "CHANGELOG.md",
41
- "LICENSE"
41
+ "LICENSE",
42
+ "package.json"
42
43
  ],
43
44
  "scripts": {
44
45
  "build": "rm -rf dist && tsup src/index.ts src/cli/index.ts --format esm,cjs --dts --outDir dist",
45
46
  "dev": "tsup src/index.ts src/cli/index.ts --watch --dts --outDir dist",
46
- "test": "vitest run",
47
- "release": "semantic-release",
48
- "docs:api": "typedoc",
47
+ "test": "pnpm test:preprod",
48
+ "test:preprod": "pnpm build && vitest run tests/test/preprod",
49
+ "test:preprod:robust": "pnpm build && PREPROD_ITER=30 vitest run tests/test/preprod/30-robustness",
50
+ "test:before-release": "pnpm build && pnpm test:preprod && pnpm test:preprod:robust",
51
+ "ci:smoke": "pnpm build && node dist/cli/index.js --help",
52
+ "ci:docs": "pnpm docs:build",
53
+ "ci:all": "pnpm ci:smoke && pnpm test && pnpm ci:docs",
54
+ "prepublishOnly": "pnpm build && pnpm test",
55
+ "docs:api": "typedoc --tsconfig tsconfig.docs.json --entryPoints src/index.ts",
49
56
  "docs:build": "pnpm docs:api && vitepress build docs",
50
- "docs:dev": "vitepress dev docs"
57
+ "docs:dev": "vitepress dev docs",
58
+ "release": "semantic-release"
51
59
  },
52
60
  "dependencies": {
53
61
  "commander": "^13.1.0",
@@ -63,7 +71,9 @@
63
71
  "@semantic-release/release-notes-generator": "^12.1.0",
64
72
  "@types/node": "^25.0.10",
65
73
  "eslint": "^9.39.2",
74
+ "execa": "^9.6.1",
66
75
  "semantic-release": "^22.0.12",
76
+ "tmp-promise": "^3.0.3",
67
77
  "tsup": "^8.5.1",
68
78
  "typedoc": "^0.28.16",
69
79
  "typedoc-plugin-markdown": "^4.9.0",