@safe-hand/safe-env-check 1.0.2 → 1.1.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.
@@ -0,0 +1,36 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ release:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v3
13
+
14
+ - name: Set up Node.js
15
+ uses: actions/setup-node@v3
16
+ with:
17
+ node-version: "20"
18
+ registry-url: "https://registry.npmjs.org"
19
+
20
+ - name: Install dependencies
21
+ run: npm install
22
+
23
+ - name: Build
24
+ run: npm run build
25
+
26
+ - name: Publish to npm
27
+ env:
28
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
29
+ run: npm publish --access public
30
+
31
+ - name: Create GitHub Release
32
+ uses: ncipollo/release-action@v1
33
+ env:
34
+ GITHUB_TOKEN: ${{secrets.GH_PAT}}
35
+ with:
36
+ tag: ${{ github.ref_name }}
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
+ and [Semantic Versioning](https://semver.org/).
7
+
8
+ ---
9
+
10
+ ## [Unreleased]
11
+
12
+ ### Added
13
+
14
+ - Placeholder for upcoming changes
15
+
16
+ ---
17
+
18
+ ## [1.0.3] - 2026-02-18
19
+
20
+ ### Added
21
+
22
+ - Initial CHANGELOG.md for tracking
23
+ - Release workflow setup
24
+
25
+ ## [1.0.2] - 2026-02-18
26
+
27
+ ### Added
28
+
29
+ - Initial npm release (version already existed)
30
+ - Schema-based environment validation
31
+ - CLI support
32
+ - Strict mode
33
+ - TypeScript support
34
+ - Support for types: `string`, `number`, `boolean`, `enum`
35
+ - Required and default value support
36
+
37
+ ### Fixed
38
+
39
+ - None yet
package/README.md CHANGED
@@ -1,19 +1,53 @@
1
1
  # safe-env-check
2
2
 
3
- A tiny TypeScript library to validate environment variables with schema, strict mode, dotenv and CLI support.
3
+ ![npm](https://img.shields.io/npm/v/@safe-hand/safe-env-check)
4
+ ![license](https://img.shields.io/npm/l/@safe-hand/safe-env-check)
5
+ ![downloads](https://img.shields.io/npm/dm/@safe-hand/safe-env-check)
4
6
 
5
- ## Install
7
+ A tiny TypeScript library to validate environment variables using a schema with support for:
8
+
9
+ - ✅ Type validation
10
+ - ✅ Required & default values
11
+ - ✅ Enum values
12
+ - ✅ Strict mode (no extra env vars)
13
+ - ✅ dotenv integration
14
+ - ✅ Custom error formatting
15
+ - ✅ CLI support
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @safe-hand/safe-env-check
23
+ ```
24
+
25
+ or
6
26
 
7
27
  ```bash
8
- npm install safe-env-check
28
+ yarn add @safe-hand/safe-env-check
9
29
  ```
10
30
 
11
- ## Usage
31
+ ## Features
32
+
33
+ - Validate process.env using a schema
34
+
35
+ - Strongly typed output (TypeScript)
36
+
37
+ - Prevents app startup with invalid configuration
12
38
 
13
- ```js
14
- import { validateEnv } from "safe-env-check";
39
+ - Supports CLI for CI/CD and deployment checks
15
40
 
16
- const env = validateEnv({
41
+ - Customizable error messages
42
+
43
+ - Optional strict mode to disallow unknown variables
44
+
45
+ ## Basic Usage
46
+
47
+ ### Define a schema
48
+
49
+ ```ts
50
+ const schema = {
17
51
  PORT: { type: "number", required: true },
18
52
  JWT_SECRET: { type: "string", required: true },
19
53
  NODE_ENV: {
@@ -21,36 +55,90 @@ const env = validateEnv({
21
55
  values: ["development", "production"],
22
56
  default: "development",
23
57
  },
24
- });
58
+ };
59
+ ```
60
+
61
+ ### Validate environment variables
62
+
63
+ ```ts
64
+ import { validateEnv } from "@safe-hand/safe-env-check";
65
+
66
+ const env = validateEnv(schema);
67
+
68
+ console.log(env.PORT); // number
69
+ console.log(env.NODE_ENV); // "development" | "production"
70
+ ```
71
+
72
+ ## Schema Options
73
+
74
+ Each environment variables supports the following options:
75
+
76
+ | Field | Type | Description |
77
+ | ---------- | ---------------------------------------------- | -------------------------------- |
78
+ | `type` | `"string" or "number" or "boolean" or "enum"` | Expected value type |
79
+ | `required` | `boolean` | Whether the variable is required |
80
+ | `default` | `any` | Default value if not provided |
81
+ | `values` | `string[]` | Required for `enum` type |
82
+
83
+ ## Example
84
+
85
+ ```ts
86
+ DATABASE_URL: { type: "string", required: true },
87
+ DEBUG: { type: "boolean", default: false },
88
+ MODE: { type: "enum", values: ["dev", "prod"] }
25
89
  ```
26
90
 
27
- **With Strict Mode**
91
+ ## Strict Mode
28
92
 
29
- ```js
93
+ Disallow environment variables that are not defined in the schema.
94
+
95
+ ```ts
30
96
  validateEnv(schema, { strict: true });
31
97
  ```
32
98
 
33
- **With Custom Error Formatter**
99
+ If extra variables are found, validation will fail.
100
+
101
+ ## Custom Error Formatter
34
102
 
35
- ```js
103
+ You can control how errors are displayed:
104
+
105
+ ```ts
36
106
  validateEnv(schema, {
37
107
  formatError: (errors) => `Config error:\n${errors.join("\n")}`,
38
108
  });
39
109
  ```
40
110
 
41
- ## CLI
111
+ ## Dotenv Support
112
+
113
+ By default, the library loads .env automatically using dotenv.
114
+
115
+ Example .env file:
42
116
 
43
- Create a file called env.schema.js
117
+ ```bash
118
+ PORT=3000
119
+ JWT_SECRET=supersecret
120
+ NODE_ENV=development
121
+ ```
44
122
 
45
- ```js
123
+ ## CLI Usage
124
+
125
+ Create a schema file called env.schema.js:
126
+
127
+ ```ts
46
128
  module.exports = {
47
129
  PORT: { type: "number", required: true },
48
- NODE_ENV: { type: "enum", values: ["dev", "prod"] },
130
+ NODE_ENV: { type: "enum", values: ["dev", "prod"], default: "dev" },
49
131
  };
50
132
  ```
51
133
 
134
+ Run validation:
135
+
52
136
  ```bash
53
137
  npx safe-env-check env.schema.js
138
+ npx safe-env-check env.schema.js
139
+ npx safe-env-check --schema env.schema.js --strict
140
+ npx safe-env-check env.schema.js --env-file .env.production
141
+ npx safe-env-check env.schema.js --format json
54
142
  ```
55
143
 
56
144
  ## License
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/cli.ts
26
+ var import_dotenv = __toESM(require("dotenv"));
27
+ var import_path = __toESM(require("path"));
28
+ var import_fs = __toESM(require("fs"));
29
+
30
+ // package.json
31
+ var version = "1.1.0";
32
+
33
+ // src/validateEnv.ts
34
+ var validateEnv = (schema, options = {}) => {
35
+ const errors = [];
36
+ const result = {};
37
+ if (options.strict) {
38
+ const schemaKeys = Object.keys(schema);
39
+ const envKeys = Object.keys(process.env);
40
+ const unknownKeys = envKeys.filter((key) => !schemaKeys.includes(key));
41
+ if (unknownKeys.length)
42
+ errors.push(`Unknown evn variables: ${unknownKeys.join(", ")}`);
43
+ }
44
+ for (const key in schema) {
45
+ const rule = schema[key];
46
+ const rawValue = process.env[key];
47
+ if (!rawValue) {
48
+ if (rule.required && rule.default === void 0) {
49
+ errors.push(`${key} is required`);
50
+ continue;
51
+ }
52
+ result[key] = rule.default;
53
+ continue;
54
+ }
55
+ switch (rule.type) {
56
+ case "string":
57
+ result[key] = rawValue;
58
+ break;
59
+ case "number":
60
+ const num = Number(rawValue);
61
+ if (isNaN(num)) errors.push(`${key} must be a number`);
62
+ else result[key] = num;
63
+ break;
64
+ case "boolean":
65
+ result[key] = rawValue === "true";
66
+ break;
67
+ case "enum":
68
+ if (!rule.values.includes(rawValue)) {
69
+ errors.push(`${key} must be one of: ${rule.values.join(", ")}`);
70
+ } else {
71
+ result[key] = rawValue;
72
+ }
73
+ break;
74
+ }
75
+ }
76
+ if (errors.length) {
77
+ const message = options.formatError ? options.formatError(errors) : defaultErrorFormatter(errors);
78
+ throw new Error(message);
79
+ }
80
+ return result;
81
+ };
82
+ var defaultErrorFormatter = (errors) => {
83
+ return "\u274C Environment validation failed:\n" + errors.map((e) => `- ${e}`).join("\n");
84
+ };
85
+
86
+ // src/cli.ts
87
+ var args = process.argv.slice(2);
88
+ var getArg = (flag) => {
89
+ const index = args.indexOf(flag);
90
+ return index !== -1 ? args[index + 1] : void 0;
91
+ };
92
+ var hasFlag = (flag) => args.includes(flag);
93
+ var getPositionalArgs = (args2) => {
94
+ return args2.filter((arg, index) => {
95
+ if (arg.startsWith("--")) return false;
96
+ const prev = args2[index - 1];
97
+ if (prev === "--schema" || prev === "--env-file" || prev === "--format") {
98
+ return false;
99
+ }
100
+ return true;
101
+ });
102
+ };
103
+ var printHelp = () => {
104
+ console.log(`
105
+ safe-env-check
106
+
107
+ Usage:
108
+ safe-env-check <schema-file> [options]
109
+
110
+ Options:
111
+ --schema <file> Path to schema file
112
+ --strict Enable strict mode
113
+ --env-file <file> Load a custom .env file
114
+ --format json Output errors as JSON
115
+ --quiet Suppress success message
116
+ --version Show version
117
+ --help Show help
118
+
119
+ Examples:
120
+ safe-env-check env.schema.js
121
+ safe-env-check --schema env.schema.js --strict
122
+ safe-env-check env.schema.js --env-file .env.production
123
+ safe-env-check env.schema.js --format json
124
+ safe-env-check --strict env.schema.js
125
+ `);
126
+ };
127
+ if (hasFlag("--help")) {
128
+ printHelp();
129
+ process.exit(0);
130
+ }
131
+ if (hasFlag("--version")) {
132
+ console.log(version);
133
+ process.exit(0);
134
+ }
135
+ var positionalArgs = getPositionalArgs(args);
136
+ var schemaFile = getArg("--schema") || positionalArgs[0];
137
+ if (!schemaFile) {
138
+ console.error("\u274C Schema file is required.\n");
139
+ printHelp();
140
+ process.exit(1);
141
+ }
142
+ var envFile = getArg("--env-file");
143
+ if (envFile) {
144
+ import_dotenv.default.config({ path: envFile });
145
+ }
146
+ var isStrict = hasFlag("--strict");
147
+ var isQuiet = hasFlag("--quiet");
148
+ var format = getArg("--format");
149
+ try {
150
+ const fullPath = import_path.default.resolve(process.cwd(), schemaFile);
151
+ if (!import_fs.default.existsSync(fullPath)) {
152
+ throw new Error(`Schema file not found: ${schemaFile}`);
153
+ }
154
+ const schema = require(fullPath);
155
+ validateEnv(schema, {
156
+ strict: isStrict,
157
+ formatError: (errors) => {
158
+ if (format === "json") {
159
+ return JSON.stringify({ errors }, null, 2);
160
+ }
161
+ return "\u274C Environment validation failed:\n" + errors.map((e) => `- ${e}`).join("\n");
162
+ }
163
+ });
164
+ if (!isQuiet) {
165
+ console.log("\u2705 Environment variables are valid");
166
+ }
167
+ } catch (error) {
168
+ console.error(error);
169
+ process.exit(1);
170
+ }
package/package.json CHANGED
@@ -1,13 +1,18 @@
1
1
  {
2
2
  "name": "@safe-hand/safe-env-check",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/shshamim63/safe-env-check.git"
7
+ },
8
+ "homepage": "https://github.com/your-username/your-repo#readme",
4
9
  "main": "dist/index.js",
5
10
  "types": "dist/index.d.ts",
6
11
  "bin": {
7
12
  "safe-env-check": "dist/cli.js"
8
13
  },
9
14
  "scripts": {
10
- "build": "tsup src/index.ts --dts",
15
+ "build": "tsup src/index.ts src/cli.ts --dts",
11
16
  "dev": "ts-node src/index.ts",
12
17
  "test": "jest",
13
18
  "lint": "tsc --noEmit"
package/src/cli.ts CHANGED
@@ -1,20 +1,143 @@
1
+ import dotenv from "dotenv";
1
2
  import path from "path";
3
+ import fs from "fs";
4
+ import { version } from "../package.json";
2
5
  import { validateEnv } from "./validateEnv";
3
6
 
4
- const schemaPath = process.argv[2];
7
+ const args = process.argv.slice(2);
5
8
 
6
- if (!schemaPath) {
7
- console.error("Usage: safe-env-check <schema-file>");
9
+ /**
10
+ * Get value of a flag: --schema file, --env-file file, --format json
11
+ */
12
+ const getArg = (flag: string): string | undefined => {
13
+ const index = args.indexOf(flag);
14
+ return index !== -1 ? args[index + 1] : undefined;
15
+ };
16
+
17
+ /**
18
+ * Check if flag exists
19
+ */
20
+ const hasFlag = (flag: string) => args.includes(flag);
21
+
22
+ /**
23
+ * Extract positional (non-flag) arguments safely
24
+ */
25
+ const getPositionalArgs = (args: string[]) => {
26
+ return args.filter((arg, index) => {
27
+ // remove flags
28
+ if (arg.startsWith("--")) return false;
29
+
30
+ // remove values of flags
31
+ const prev = args[index - 1];
32
+ if (prev === "--schema" || prev === "--env-file" || prev === "--format") {
33
+ return false;
34
+ }
35
+
36
+ return true;
37
+ });
38
+ };
39
+
40
+ const printHelp = () => {
41
+ console.log(`
42
+ safe-env-check
43
+
44
+ Usage:
45
+ safe-env-check <schema-file> [options]
46
+
47
+ Options:
48
+ --schema <file> Path to schema file
49
+ --strict Enable strict mode
50
+ --env-file <file> Load a custom .env file
51
+ --format json Output errors as JSON
52
+ --quiet Suppress success message
53
+ --version Show version
54
+ --help Show help
55
+
56
+ Examples:
57
+ safe-env-check env.schema.js
58
+ safe-env-check --schema env.schema.js --strict
59
+ safe-env-check env.schema.js --env-file .env.production
60
+ safe-env-check env.schema.js --format json
61
+ safe-env-check --strict env.schema.js
62
+ `);
63
+ };
64
+
65
+ /* =======================
66
+ Early exit flags
67
+ ======================= */
68
+
69
+ if (hasFlag("--help")) {
70
+ printHelp();
71
+ process.exit(0);
72
+ }
73
+
74
+ if (hasFlag("--version")) {
75
+ console.log(version);
76
+ process.exit(0);
77
+ }
78
+
79
+ /* =======================
80
+ Resolve schema file
81
+ ======================= */
82
+
83
+ const positionalArgs = getPositionalArgs(args);
84
+ const schemaFile = getArg("--schema") || positionalArgs[0];
85
+
86
+ if (!schemaFile) {
87
+ console.error("❌ Schema file is required.\n");
88
+ printHelp();
8
89
  process.exit(1);
9
90
  }
10
91
 
92
+ /* =======================
93
+ Load env file if provided
94
+ ======================= */
95
+
96
+ const envFile = getArg("--env-file");
97
+
98
+ if (envFile) {
99
+ dotenv.config({ path: envFile });
100
+ }
101
+
102
+ /* =======================
103
+ Flags
104
+ ======================= */
105
+
106
+ const isStrict = hasFlag("--strict");
107
+ const isQuiet = hasFlag("--quiet");
108
+ const format = getArg("--format");
109
+
110
+ /* =======================
111
+ Main logic
112
+ ======================= */
113
+
11
114
  try {
12
- const fullPath = path.resolve(process.cwd(), schemaPath);
115
+ const fullPath = path.resolve(process.cwd(), schemaFile);
116
+
117
+ if (!fs.existsSync(fullPath)) {
118
+ throw new Error(`Schema file not found: ${schemaFile}`);
119
+ }
120
+
13
121
  const schema = require(fullPath);
14
122
 
15
- validateEnv(schema);
16
- console.log("✅ Environment variables are valid");
17
- } catch (error: any) {
18
- console.error(error.message);
123
+ validateEnv(schema, {
124
+ strict: isStrict,
125
+ formatError: (errors) => {
126
+ if (format === "json") {
127
+ return JSON.stringify({ errors }, null, 2);
128
+ }
129
+
130
+ return (
131
+ "❌ Environment validation failed:\n" +
132
+ errors.map((e) => `- ${e}`).join("\n")
133
+ );
134
+ },
135
+ });
136
+
137
+ if (!isQuiet) {
138
+ console.log("✅ Environment variables are valid");
139
+ }
140
+ } catch (error) {
141
+ console.error(error);
19
142
  process.exit(1);
20
143
  }
@@ -0,0 +1,200 @@
1
+ import path from "path";
2
+
3
+ /**
4
+ * Mock validateEnv
5
+ */
6
+ const mockValidateEnv = jest.fn();
7
+
8
+ jest.mock("../src/validateEnv", () => ({
9
+ validateEnv: mockValidateEnv,
10
+ }));
11
+
12
+ /**
13
+ * Mock fs
14
+ */
15
+ jest.mock("fs", () => ({
16
+ existsSync: jest.fn(),
17
+ }));
18
+
19
+ /**
20
+ * Mock dotenv
21
+ */
22
+ jest.mock("dotenv", () => ({
23
+ config: jest.fn(),
24
+ }));
25
+
26
+ /**
27
+ * Mock package.json version
28
+ */
29
+ jest.mock("../package.json", () => ({
30
+ version: "1.0.0",
31
+ }));
32
+
33
+ describe("CLI", () => {
34
+ const originalArgv = process.argv;
35
+ const originalEnv = process.env;
36
+
37
+ let exitSpy: jest.SpyInstance;
38
+ let logSpy: jest.SpyInstance;
39
+ let errorSpy: jest.SpyInstance;
40
+
41
+ beforeEach(() => {
42
+ jest.resetModules();
43
+
44
+ process.argv = ["node", "cli.ts"];
45
+ process.env = { ...originalEnv };
46
+
47
+ exitSpy = jest.spyOn(process, "exit").mockImplementation((() => {
48
+ throw new Error("process.exit");
49
+ }) as never);
50
+
51
+ logSpy = jest.spyOn(console, "log").mockImplementation(() => {});
52
+ errorSpy = jest.spyOn(console, "error").mockImplementation(() => {});
53
+ });
54
+
55
+ afterEach(() => {
56
+ process.argv = originalArgv;
57
+ process.env = originalEnv;
58
+ jest.restoreAllMocks();
59
+ });
60
+
61
+ test("prints help when --help is passed", () => {
62
+ process.argv = ["node", "cli.ts", "--help"];
63
+
64
+ expect(() => require("../src/cli")).toThrow("process.exit");
65
+
66
+ expect(exitSpy).toHaveBeenCalledWith(0);
67
+ expect(logSpy).toHaveBeenCalled();
68
+ });
69
+
70
+ test("prints version when --version is passed", () => {
71
+ process.argv = ["node", "cli.ts", "--version"];
72
+
73
+ expect(() => require("../src/cli")).toThrow("process.exit");
74
+
75
+ expect(logSpy).toHaveBeenCalledWith("1.0.0");
76
+ expect(exitSpy).toHaveBeenCalledWith(0);
77
+ });
78
+
79
+ test("fails when schema file is missing", () => {
80
+ process.argv = ["node", "cli.ts"];
81
+
82
+ expect(() => require("../src/cli")).toThrow("process.exit");
83
+
84
+ expect(errorSpy).toHaveBeenCalledWith("❌ Schema file is required.\n");
85
+ expect(exitSpy).toHaveBeenCalledWith(1);
86
+ });
87
+
88
+ test("fails if schema file does not exist", () => {
89
+ const fs = require("fs");
90
+ fs.existsSync.mockReturnValue(false);
91
+
92
+ process.argv = ["node", "cli.ts", "env.schema.js"];
93
+
94
+ expect(() => require("../src/cli")).toThrow("process.exit");
95
+ expect(exitSpy).toHaveBeenCalledWith(1);
96
+ });
97
+
98
+ test("loads custom env file when --env-file is provided", () => {
99
+ const fs = require("fs");
100
+ const dotenv = require("dotenv");
101
+
102
+ fs.existsSync.mockReturnValue(true);
103
+
104
+ const schemaPath = path.resolve(process.cwd(), "env.schema.js");
105
+
106
+ jest.doMock(schemaPath, () => ({}), { virtual: true });
107
+
108
+ process.argv = [
109
+ "node",
110
+ "cli.ts",
111
+ "env.schema.js",
112
+ "--env-file",
113
+ ".env.production",
114
+ ];
115
+
116
+ mockValidateEnv.mockImplementation(() => ({}));
117
+
118
+ require("../src/cli");
119
+
120
+ expect(dotenv.config).toHaveBeenCalledWith({
121
+ path: ".env.production",
122
+ });
123
+ });
124
+
125
+ test("calls validateEnv with correct options", () => {
126
+ const fs = require("fs");
127
+ fs.existsSync.mockReturnValue(true);
128
+
129
+ const schemaPath = path.resolve(process.cwd(), "env.schema.js");
130
+
131
+ jest.doMock(
132
+ schemaPath,
133
+ () => ({
134
+ PORT: { type: "number", required: true },
135
+ }),
136
+ { virtual: true },
137
+ );
138
+
139
+ process.argv = [
140
+ "node",
141
+ "cli.ts",
142
+ "env.schema.js",
143
+ "--strict",
144
+ "--format",
145
+ "json",
146
+ ];
147
+
148
+ mockValidateEnv.mockImplementation(() => ({}));
149
+
150
+ require("../src/cli");
151
+
152
+ expect(mockValidateEnv).toHaveBeenCalledWith(
153
+ expect.any(Object),
154
+ expect.objectContaining({
155
+ strict: true,
156
+ formatError: expect.any(Function),
157
+ }),
158
+ );
159
+
160
+ expect(logSpy).toHaveBeenCalledWith("✅ Environment variables are valid");
161
+ });
162
+
163
+ test("suppresses success message with --quiet", () => {
164
+ const fs = require("fs");
165
+ fs.existsSync.mockReturnValue(true);
166
+
167
+ const schemaPath = path.resolve(process.cwd(), "env.schema.js");
168
+
169
+ jest.doMock(schemaPath, () => ({}), { virtual: true });
170
+
171
+ process.argv = ["node", "cli.ts", "env.schema.js", "--quiet"];
172
+
173
+ mockValidateEnv.mockImplementation(() => ({}));
174
+
175
+ require("../src/cli");
176
+
177
+ expect(logSpy).not.toHaveBeenCalledWith(
178
+ "✅ Environment variables are valid",
179
+ );
180
+ });
181
+
182
+ test("prints formatted error when validateEnv throws", () => {
183
+ const fs = require("fs");
184
+ fs.existsSync.mockReturnValue(true);
185
+
186
+ const schemaPath = path.resolve(process.cwd(), "env.schema.js");
187
+
188
+ jest.doMock(schemaPath, () => ({}), { virtual: true });
189
+
190
+ process.argv = ["node", "cli.ts", "env.schema.js", "--format", "json"];
191
+
192
+ mockValidateEnv.mockImplementation(() => {
193
+ throw new Error(JSON.stringify({ errors: ["PORT is required"] }));
194
+ });
195
+
196
+ expect(() => require("../src/cli")).toThrow("process.exit");
197
+
198
+ expect(exitSpy).toHaveBeenCalledWith(1);
199
+ });
200
+ });
package/tsconfig.json CHANGED
@@ -7,6 +7,7 @@
7
7
  "rootDir": "src",
8
8
  "strict": true,
9
9
  "moduleResolution": "node",
10
+ "resolveJsonModule": true,
10
11
  "esModuleInterop": true,
11
12
  "skipLibCheck": true
12
13
  },