dotenv-gad 1.4.0 → 1.4.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/README.md CHANGED
@@ -1,18 +1,23 @@
1
1
  # dotenv-gad
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/dotenv-gad.svg)](https://badge.fury.io/js/dotenv-gad)
4
+ [![CI](https://github.com/kasimlyee/dotenv-gad/actions/workflows/ci.yml/badge.svg)](https://github.com/kasimlyee/dotenv-gad/actions/workflows/ci.yml)
4
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Docs](https://img.shields.io/badge/docs-latest-blue?style=flat-square)](https://kasimlyee.github.io/dotenv-gad/latest/)
6
-
7
- **dotenv-gad** is an environment variable validation tool that brings type safety and schema validation to your Node.js and JavaScript applications. It extends `dotenv` with features like:
8
-
9
- - Type-safe environment variables
10
- - Schema validation
11
- - Schema composition
12
- - Automatic documentation generation
13
- - TypeScript support
14
- - CLI tooling
15
- - Secret management
6
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-first--class-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
8
+ [![Docs](https://img.shields.io/badge/docs-latest-blue?style=flat-square)](https://kasimlyee.github.io/dotenv-gad/)
9
+ [![npm downloads](https://img.shields.io/npm/dm/dotenv-gad.svg)](https://www.npmjs.com/package/dotenv-gad)
10
+
11
+ **dotenv-gad** is an environment variable validation library that brings type safety, schema validation, and runtime checks to your Node.js and JavaScript applications. It works with any environment variable source — `.env` files, platform dashboards (Vercel, Railway, Docker), CI/CD pipelines, or `process.env` directly.
12
+
13
+ - Type-safe environment variables with full IntelliSense
14
+ - Schema validation (string, number, boolean, url, email, ip, port, json, array, object)
15
+ - Schema composition for modular configs
16
+ - Automatic documentation and `.env.example` generation
17
+ - First-class TypeScript support
18
+ - CLI tooling (check, sync, types, init, fix, docs)
19
+ - Sensitive value management and redaction
20
+ - Vite plugin with client-safe filtering and HMR
16
21
 
17
22
  ## Installation
18
23
 
@@ -55,22 +60,13 @@ const env = loadEnv(schema);
55
60
  console.log(`Server running on port ${env.PORT}`);
56
61
  ```
57
62
 
58
- Documentation
59
-
60
- [![Docs](https://img.shields.io/badge/docs-latest-blue?style=flat-square)](https://kasimlyee.github.io/dotenv-gad/latest/)
61
-
62
- Full documentation is available via GitHub Pages (published from `docs/`).
63
-
64
- To preview locally:
63
+ `loadEnv` reads from both `process.env` and your `.env` file (if present). This means it works out of the box on platforms like Vercel, Railway, Docker, and AWS Lambda where variables are injected into `process.env` directly — no `.env` file required.
65
64
 
66
- ```bash
67
- npm ci
68
- npm run docs:serve
69
- ```
65
+ ## Documentation
70
66
 
71
- Docs preview on PRs
67
+ [![Docs](https://img.shields.io/badge/docs-latest-blue?style=flat-square)](https://kasimlyee.github.io/dotenv-gad/)
72
68
 
73
- When you open or update a pull request that changes docs, an automated preview will be published to GitHub Pages under `previews/pr-<number>/` and a comment with the preview link will be posted on the PR. This makes it easy to review documentation changes without merging.
69
+ Full documentation is available at [kasimlyee.github.io/dotenv-gad](https://kasimlyee.github.io/dotenv-gad/).
74
70
 
75
71
  ## CLI Commands
76
72
 
@@ -90,11 +86,12 @@ npx dotenv-gad types
90
86
 
91
87
  ## Vite Plugin
92
88
 
93
- The Vite plugin provides build-time environment variable validation with automatic client-safe filtering for browser-based applications.
89
+ The Vite plugin validates environment variables at build time and exposes a typed, client-safe subset to your browser code via a virtual module.
94
90
 
95
- ### Add to your `vite.config.ts`:
91
+ ### Setup
96
92
 
97
93
  ```typescript
94
+ // vite.config.ts
98
95
  import { defineConfig } from "vite";
99
96
  import dotenvGad from "dotenv-gad/vite";
100
97
 
@@ -102,38 +99,39 @@ export default defineConfig({
102
99
  plugins: [
103
100
  dotenvGad({
104
101
  schemaPath: "./env.schema.ts",
105
- clientPrefix: "VITE_", // Default prefix for client-safe variables
106
- publicKeys: [], // Additional non-prefixed keys to expose
107
- generatedTypes: true, // Generate dotenv-gad.d.ts for IntelliSense
102
+ // clientPrefix: "VITE_", // default — keys matching this prefix are exposed
103
+ // publicKeys: [], // additional non-prefixed keys to expose
104
+ // generatedTypes: true, // generate .d.ts for IntelliSense
108
105
  }),
109
106
  ],
110
107
  });
111
108
  ```
112
109
 
113
- ### Use validated environment variables in your app:
110
+ ### Usage
114
111
 
115
112
  ```typescript
116
113
  import { env } from "dotenv-gad/client";
117
114
 
118
- console.log(env.VITE_API_URL); // Full type safety & validation
115
+ console.log(env.VITE_API_URL); // Full type safety & autocomplete
119
116
  ```
120
117
 
121
118
  ### Key Features
122
119
 
123
- - **Build-time validation**: Environment checked every dev/build cycle
124
- - **Client-safe filtering**: Only `VITE_` prefixed variables (or custom `publicKeys`) exposed to browser
125
- - **Automatic TypeScript types**: Generated `dotenv-gad.d.ts` for full IntelliSense
126
- - **Sensitive protection**: Variables marked `sensitive: true` are excluded by default
127
- - **HMR support**: Hot reload on `.env` changes during development
120
+ - **Build-time validation** environment checked every dev/build cycle
121
+ - **Client-safe filtering** only `VITE_*` prefixed variables (or custom `publicKeys`) exposed to browser
122
+ - **Sensitive protection** variables marked `sensitive: true` are always excluded
123
+ - **Auto-generated types** `dotenv-gad.d.ts` gives full IntelliSense on `env.`
124
+ - **HMR support** hot reload on `.env` or schema changes during development
125
+ - **SSR safety** — server-side code gets the full env, not the filtered subset
128
126
 
129
127
  ## Features
130
128
 
131
129
  ### Core Validation
132
130
 
133
- - Type checking (string, number, boolean, array, object)
134
- - Required/optional fields
135
- - Default values
131
+ - Type checking (string, number, boolean, array, object, url, email, ip, port, json, date)
132
+ - Required/optional fields with defaults
136
133
  - Custom validation functions
134
+ - Value transforms
137
135
  - Environment-specific rules
138
136
 
139
137
  ### Advanced Types
@@ -150,13 +148,23 @@ console.log(env.VITE_API_URL); // Full type safety & validation
150
148
  }
151
149
  ```
152
150
 
153
- ### CLI Features
151
+ ### Schema Composition
154
152
 
155
- - Color-coded output
156
- - Interactive fixes
157
- - Strict mode
158
- - Custom schema paths
159
- - CI/CD friendly
153
+ Merge multiple schemas for modular configuration:
154
+
155
+ ```typescript
156
+ import { defineSchema, composeSchema } from "dotenv-gad";
157
+
158
+ const baseSchema = defineSchema({
159
+ NODE_ENV: { type: "string", default: "development" },
160
+ });
161
+
162
+ const dbSchema = defineSchema({
163
+ DATABASE_URL: { type: "string", required: true, sensitive: true },
164
+ });
165
+
166
+ const schema = composeSchema(baseSchema, dbSchema);
167
+ ```
160
168
 
161
169
  ### Secret Management
162
170
 
@@ -164,12 +172,37 @@ console.log(env.VITE_API_URL); // Full type safety & validation
164
172
  {
165
173
  API_KEY: {
166
174
  type: 'string',
167
- sensitive: true, // Excluded from .env.example
175
+ sensitive: true, // masked in errors, excluded from .env.example
168
176
  validate: (val) => val.startsWith('sk_')
169
177
  }
170
178
  }
171
179
  ```
172
180
 
181
+ ### Grouping / Namespaced Envs
182
+
183
+ Group related variables into a single validated object:
184
+
185
+ ```typescript
186
+ const schema = defineSchema({
187
+ DATABASE: {
188
+ type: "object",
189
+ envPrefix: "DATABASE_", // optional; defaults to 'DATABASE_'
190
+ properties: {
191
+ DB_NAME: { type: "string", required: true },
192
+ PORT: { type: "port", default: 5432 },
193
+ PWD: { type: "string", sensitive: true },
194
+ },
195
+ },
196
+ });
197
+ ```
198
+
199
+ Given `DATABASE_DB_NAME=mydb`, `DATABASE_PORT=5432`, `DATABASE_PWD=supersecret`:
200
+
201
+ ```typescript
202
+ const env = loadEnv(schema);
203
+ // { DATABASE: { DB_NAME: 'mydb', PORT: 5432, PWD: 'supersecret' } }
204
+ ```
205
+
173
206
  ## Framework Integrations
174
207
 
175
208
  ### Express.js
@@ -189,8 +222,6 @@ app.listen(env.PORT, () => {
189
222
 
190
223
  ### Next.js
191
224
 
192
- Create `next.config.js`:
193
-
194
225
  ```javascript
195
226
  const { loadEnv } = require("dotenv-gad");
196
227
  const schema = require("./env.schema");
@@ -204,9 +235,7 @@ module.exports = {
204
235
  };
205
236
  ```
206
237
 
207
- ## Validation Reports
208
-
209
- Example error output:
238
+ ## Error Reporting
210
239
 
211
240
  ```
212
241
  Environment validation failed:
@@ -215,38 +244,20 @@ Environment validation failed:
215
244
  - API_KEY: Must start with 'sk_' (received: "invalid")
216
245
  ```
217
246
 
218
- By default values in the report are redacted (sensitive values are always masked). You can opt-in to include raw values in error reports when instantiating the validator (useful for local debugging) by using the `includeRaw` option. If you also want to reveal values marked as `sensitive: true` set `includeSensitive` to `true` (use with caution).
247
+ Sensitive values are always masked in error output. Use `includeRaw` for local debugging:
219
248
 
220
- ```ts
221
- // include raw values in errors (non-sensitive values only)
222
- import { loadEnv } from "dotenv-gad";
249
+ ```typescript
223
250
  const env = loadEnv(schema, { includeRaw: true });
224
251
 
225
252
  // or with finer control
226
253
  import { EnvValidator } from "dotenv-gad";
227
- const validator = new EnvValidator(schema, { includeRaw: true, includeSensitive: true });
228
- try {
229
- validator.validate(process.env);
230
- } catch (err) {
231
- console.error(String(err));
232
- }
254
+ const validator = new EnvValidator(schema, {
255
+ includeRaw: true,
256
+ includeSensitive: true,
257
+ });
233
258
  ```
234
259
 
235
- ## more usages
236
-
237
- ### Environment-Specific Rules
238
-
239
- ```typescript
240
- {
241
- DEBUG: {
242
- type: 'boolean',
243
- env: {
244
- development: { default: true },
245
- production: { default: false }
246
- }
247
- }
248
- }
249
- ```
260
+ ## More Examples
250
261
 
251
262
  ### Custom Validators
252
263
 
@@ -260,7 +271,7 @@ try {
260
271
  }
261
272
  ```
262
273
 
263
- ### Transformations
274
+ ### Transforms
264
275
 
265
276
  ```typescript
266
277
  {
@@ -271,49 +282,22 @@ try {
271
282
  }
272
283
  ```
273
284
 
274
- ### Grouping / Namespaced envs
275
-
276
- You can group related environment variables into a single object using `object` with `properties` and an optional `envPrefix` (defaults to `KEY_`):
285
+ ### Environment-Specific Rules
277
286
 
278
- ```ts
279
- const schema = defineSchema({
280
- DATABASE: {
281
- type: 'object',
282
- envPrefix: 'DATABASE_', // optional; defaults to 'DATABASE_'
283
- properties: {
284
- DB_NAME: { type: 'string', required: true },
285
- PORT: { type: 'port', default: 5432 },
286
- PWD: { type: 'string', sensitive: true }
287
+ ```typescript
288
+ {
289
+ DEBUG: {
290
+ type: 'boolean',
291
+ env: {
292
+ development: { default: true },
293
+ production: { default: false }
287
294
  }
288
295
  }
289
- });
290
- ```
291
-
292
- Given the following environment:
293
-
294
- ```
295
- DATABASE_DB_NAME=mydb
296
- DATABASE_PORT=5432
297
- DATABASE_PWD=supersecret
298
- ```
299
-
300
- `loadEnv(schema)` will return:
301
-
302
- ```ts
303
- { DATABASE: { DB_NAME: 'mydb', PORT: 5432, PWD: 'supersecret' } }
296
+ }
304
297
  ```
305
298
 
306
- Notes and behavior:
307
-
308
- - The default `envPrefix` is `${KEY}_` (for `DATABASE` it's `DATABASE_`) if you don't specify `envPrefix`.
309
- - Prefixed variables take precedence over a JSON top-level env var (e.g., `DATABASE` = '{...}'). If both are present, prefixed variables win and a warning is printed.
310
- - In strict mode (`{ strict: true }`), unexpected subkeys inside a group (e.g., `DATABASE_EXTRA`) will cause validation to fail.
311
- - `sensitive` and `includeRaw` behavior still applies for grouped properties: sensitive properties are still masked in errors unless `includeSensitive` is explicitly set.
312
-
313
- The CLI `sync` command will now generate grouped entries in `.env.example` for object properties so it's easier to scaffold grouped configuration.
314
-
315
299
  ## License
316
300
 
317
301
  MIT © [Kasim Lyee]
318
302
 
319
- [Contributions](https://github.com/kasimlyee/dotenv-gad/blob/main/CONTRIBUTING.md) are welcome!!
303
+ [Contributions](https://github.com/kasimlyee/dotenv-gad/blob/main/CONTRIBUTING.md) are welcome!
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/client.ts
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
+ default: () => client_default,
24
+ env: () => env
25
+ });
26
+ module.exports = __toCommonJS(client_exports);
27
+ var env = {};
28
+ var client_default = env;
29
+ // Annotate the CommonJS export names for ESM import in node:
30
+ 0 && (module.exports = {
31
+ env
32
+ });
package/dist/index.cjs ADDED
@@ -0,0 +1,436 @@
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 __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ AggregateError: () => AggregateError,
34
+ EnvAggregateError: () => EnvAggregateError,
35
+ EnvValidationError: () => EnvValidationError,
36
+ EnvValidator: () => EnvValidator,
37
+ composeSchema: () => composeSchema,
38
+ createEnvProxy: () => createEnvProxy,
39
+ defineSchema: () => defineSchema,
40
+ loadEnv: () => loadEnv,
41
+ validateEnv: () => validateEnv
42
+ });
43
+ module.exports = __toCommonJS(src_exports);
44
+
45
+ // src/errors.ts
46
+ var EnvValidationError = class extends Error {
47
+ constructor(key, message, receiveValue) {
48
+ super(message);
49
+ this.key = key;
50
+ this.message = message;
51
+ this.receiveValue = receiveValue;
52
+ this.name = "EnvValidationError";
53
+ }
54
+ };
55
+ var EnvAggregateError = class _EnvAggregateError extends Error {
56
+ constructor(errors, message) {
57
+ super(message);
58
+ this.errors = errors;
59
+ this.name = "EnvAggregateError";
60
+ Object.setPrototypeOf(this, _EnvAggregateError.prototype);
61
+ }
62
+ toString() {
63
+ const errorList = this.errors.map((e) => {
64
+ let msg = ` - ${e.key}: ${e.message}`;
65
+ if (e.value !== void 0)
66
+ msg += ` (received: ${JSON.stringify(e.value)})`;
67
+ if (e.rule?.docs) msg += `
68
+ ${e.rule.docs}`;
69
+ return msg;
70
+ }).join("\n");
71
+ return `${this.message}:
72
+ ${errorList}`;
73
+ }
74
+ };
75
+ var AggregateError = EnvAggregateError;
76
+
77
+ // src/validator.ts
78
+ var EnvValidator = class {
79
+ /**
80
+ * Constructs a new EnvValidator instance.
81
+ * @param {SchemaDefinition} schema The schema definition for the environment variables.
82
+ * @param {Object} [options] Optional options for the validation process.
83
+ * @param {boolean} [options.strict] When true, environment variables not present in the schema will be rejected.
84
+ */
85
+ constructor(schema, options) {
86
+ this.schema = schema;
87
+ this.options = options;
88
+ }
89
+ errors = [];
90
+ validate(env) {
91
+ this.errors = [];
92
+ const result = {};
93
+ const groupedEnv = {};
94
+ const prefixes = [];
95
+ for (const [k, r] of Object.entries(this.schema)) {
96
+ const eff = this.getEffectiveRule(k, r);
97
+ if (eff.type === "object" && eff.properties) {
98
+ const prefix = eff.envPrefix ?? `${k}_`;
99
+ prefixes.push({ key: k, prefix });
100
+ groupedEnv[k] = {};
101
+ }
102
+ }
103
+ const envKeys = Object.keys(env);
104
+ for (let i = 0; i < envKeys.length; i++) {
105
+ const eKey = envKeys[i];
106
+ for (let j = 0; j < prefixes.length; j++) {
107
+ const { key, prefix } = prefixes[j];
108
+ if (eKey.startsWith(prefix)) {
109
+ const subKey = eKey.slice(prefix.length);
110
+ groupedEnv[key][subKey] = env[eKey];
111
+ }
112
+ }
113
+ }
114
+ const schemaKeys = Object.keys(this.schema);
115
+ for (let i = 0; i < schemaKeys.length; i++) {
116
+ const key = schemaKeys[i];
117
+ const rule = this.schema[key];
118
+ try {
119
+ const valToValidate = groupedEnv[key] && Object.keys(groupedEnv[key]).length > 0 ? groupedEnv[key] : env[key];
120
+ if (groupedEnv[key] && Object.keys(groupedEnv[key]).length > 0 && env[key] !== void 0) {
121
+ console.warn(`Both prefixed variables and top-level ${key} exist; prefixed vars are used`);
122
+ }
123
+ if (this.options?.strict && groupedEnv[key] && Object.keys(groupedEnv[key]).length > 0) {
124
+ const propNames = rule.properties ? Object.keys(rule.properties) : [];
125
+ const extras = Object.keys(groupedEnv[key]).filter((s) => !propNames.includes(s));
126
+ if (extras.length > 0) {
127
+ this.errors.push({
128
+ key,
129
+ message: `Unexpected grouped environment variables: ${extras.join(", ")}`,
130
+ value: Object.keys(groupedEnv[key]),
131
+ rule
132
+ });
133
+ continue;
134
+ }
135
+ }
136
+ result[key] = this.validateKey(key, rule, valToValidate);
137
+ } catch (error) {
138
+ if (error instanceof Error) {
139
+ let displayedValue;
140
+ if (env[key] === void 0) {
141
+ displayedValue = void 0;
142
+ } else if (this.options?.includeRaw) {
143
+ if (rule.sensitive && !this.options?.includeSensitive) {
144
+ displayedValue = "****";
145
+ } else {
146
+ displayedValue = env[key];
147
+ }
148
+ } else {
149
+ displayedValue = this.redactValue(env[key], rule.sensitive);
150
+ }
151
+ this.errors.push({
152
+ key,
153
+ message: error.message,
154
+ value: displayedValue,
155
+ rule
156
+ });
157
+ }
158
+ }
159
+ }
160
+ if (this.options?.strict) {
161
+ const prefixedKeys = /* @__PURE__ */ new Set();
162
+ for (const p of prefixes) {
163
+ for (const eKey of envKeys) {
164
+ if (eKey.startsWith(p.prefix)) prefixedKeys.add(eKey);
165
+ }
166
+ }
167
+ for (const k of envKeys) {
168
+ if (!(k in this.schema) && !prefixedKeys.has(k)) {
169
+ this.errors.push({
170
+ key: k,
171
+ message: `Unexpected environment variable`
172
+ });
173
+ }
174
+ }
175
+ }
176
+ if (this.errors.length > 0) {
177
+ const keys = this.errors.map((e) => e.key).join(", ");
178
+ throw new EnvAggregateError(
179
+ this.errors,
180
+ `Environment validation failed: ${keys}`
181
+ );
182
+ }
183
+ return result;
184
+ }
185
+ // Redact or trim sensitive values for error reporting
186
+ redactValue(value, sensitive) {
187
+ if (value === void 0) return void 0;
188
+ if (sensitive) return "****";
189
+ if (typeof value !== "string") return value;
190
+ if (value.length > 64) {
191
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
192
+ }
193
+ return value;
194
+ }
195
+ // Try to quickly determine if a string *might* be JSON before parsing to avoid
196
+ // costly exceptions in the hot path for clearly non-JSON values.
197
+ tryParseJSON(value) {
198
+ if (typeof value !== "string") return { ok: false };
199
+ const s = value.trim();
200
+ if (!s) return { ok: false };
201
+ const c = s[0];
202
+ if (c !== "{" && c !== "[" && c !== '"' && c !== "t" && c !== "f" && c !== "n" && (c < "0" || c > "9") && c !== "-") {
203
+ return { ok: false };
204
+ }
205
+ try {
206
+ return { ok: true, value: JSON.parse(s) };
207
+ } catch {
208
+ return { ok: false };
209
+ }
210
+ }
211
+ validateKey(key, rule, value) {
212
+ const effectiveRule = this.getEffectiveRule(key, rule);
213
+ if (value === void 0 || value === "") {
214
+ if (effectiveRule.required)
215
+ throw new Error(`Missing required environment variable`);
216
+ return effectiveRule.default;
217
+ }
218
+ if (effectiveRule.transform) {
219
+ value = effectiveRule.transform(value);
220
+ }
221
+ switch (effectiveRule.type) {
222
+ case "string":
223
+ value = String(value).trim();
224
+ if (effectiveRule.minLength !== void 0 && value.length < effectiveRule.minLength) {
225
+ throw new Error(
226
+ `Environment variable ${key} must be at least ${effectiveRule.minLength} characters`
227
+ );
228
+ }
229
+ if (effectiveRule.maxLength !== void 0 && value.length > effectiveRule.maxLength) {
230
+ throw new Error(
231
+ `Environment variable ${key} must be at most ${effectiveRule.maxLength} characters`
232
+ );
233
+ }
234
+ break;
235
+ case "number":
236
+ value = Number(value);
237
+ if (isNaN(value)) {
238
+ throw new Error(`Environment variable ${key} must be a number`);
239
+ }
240
+ if (effectiveRule.min !== void 0 && value < effectiveRule.min) {
241
+ throw new Error(
242
+ `Environment variable ${key} must be at least ${effectiveRule.min}`
243
+ );
244
+ }
245
+ if (effectiveRule.max !== void 0 && value > effectiveRule.max) {
246
+ throw new Error(
247
+ `Environment variable ${key} must be at most ${effectiveRule.max}`
248
+ );
249
+ }
250
+ break;
251
+ case "boolean":
252
+ if (typeof value === "string") {
253
+ value = value.toLowerCase();
254
+ if (value === "true") {
255
+ value = true;
256
+ } else if (value === "false") {
257
+ value = false;
258
+ }
259
+ }
260
+ if (typeof value !== "boolean") {
261
+ throw new Error(
262
+ `Environment variable ${key} must be a boolean (true/false)`
263
+ );
264
+ }
265
+ value = Boolean(value);
266
+ break;
267
+ case "date":
268
+ const date = new Date(value);
269
+ if (isNaN(date.getTime())) {
270
+ throw new Error(`Environment variable ${key} must be a valid date`);
271
+ }
272
+ value = date;
273
+ break;
274
+ case "url":
275
+ try {
276
+ new URL(String(value));
277
+ } catch {
278
+ throw new Error("Must be a valid URL");
279
+ }
280
+ break;
281
+ case "email":
282
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value))) {
283
+ throw new Error("Must be a valid email");
284
+ }
285
+ break;
286
+ case "ip":
287
+ if (!/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(String(value))) {
288
+ throw new Error("Must be a valid IP address");
289
+ }
290
+ break;
291
+ case "port":
292
+ const port = Number(value);
293
+ if (isNaN(port) || !Number.isInteger(port)) throw new Error("Must be an integer");
294
+ if (port < 1 || port > 65535) {
295
+ throw new Error("Must be between 1 and 65535");
296
+ }
297
+ value = port;
298
+ break;
299
+ case "json":
300
+ const maybeJson = this.tryParseJSON(value);
301
+ if (!maybeJson.ok) {
302
+ throw new Error("Must be valid JSON");
303
+ }
304
+ value = maybeJson.value;
305
+ break;
306
+ case "array":
307
+ if (!Array.isArray(value)) {
308
+ const parsed = this.tryParseJSON(value);
309
+ if (!parsed.ok || !Array.isArray(parsed.value)) {
310
+ throw new Error("Must be a valid array or JSON array string");
311
+ }
312
+ value = parsed.value;
313
+ }
314
+ if (effectiveRule.items) {
315
+ value = value.map((item, i) => {
316
+ try {
317
+ return this.validateKey(
318
+ `${key}[${i}]`,
319
+ effectiveRule.items,
320
+ item
321
+ );
322
+ } catch (error) {
323
+ throw new Error(`Array item '${i}':${error.message}`);
324
+ }
325
+ });
326
+ }
327
+ break;
328
+ case "object":
329
+ if (typeof value === "string") {
330
+ const parsed = this.tryParseJSON(value);
331
+ if (!parsed.ok || typeof parsed.value !== "object" || Array.isArray(parsed.value)) {
332
+ throw new Error("Must be a valid object or JSON string");
333
+ }
334
+ value = parsed.value;
335
+ }
336
+ if (effectiveRule.properties) {
337
+ const obj = {};
338
+ for (const prop in effectiveRule.properties) {
339
+ if (!Object.prototype.hasOwnProperty.call(effectiveRule.properties, prop)) continue;
340
+ const propRule = effectiveRule.properties[prop];
341
+ try {
342
+ obj[prop] = this.validateKey(
343
+ `${key}.${prop}`,
344
+ propRule,
345
+ value[prop]
346
+ );
347
+ } catch (error) {
348
+ throw new Error(`Property '${prop}':${error.message}`);
349
+ }
350
+ }
351
+ value = obj;
352
+ }
353
+ break;
354
+ }
355
+ if (effectiveRule.enum && !effectiveRule.enum.includes(value)) {
356
+ throw new Error(
357
+ `Environment variable ${key} must be one of ${effectiveRule.enum.join(
358
+ ", "
359
+ )}`
360
+ );
361
+ }
362
+ if (effectiveRule.regex && !effectiveRule.regex.test(String(value))) {
363
+ throw new Error(
364
+ effectiveRule.regexError || `Environment variable ${key} must match ${effectiveRule.regex}`
365
+ );
366
+ }
367
+ if (effectiveRule.validate && !effectiveRule.validate(value)) {
368
+ throw new Error(effectiveRule.error || "Custom validation failed");
369
+ }
370
+ return value;
371
+ }
372
+ getEffectiveRule(key, rule) {
373
+ const envName = process.env.NODE_ENV || "development";
374
+ const envRule = rule.env?.[envName] || {};
375
+ return { ...rule, ...envRule };
376
+ }
377
+ };
378
+
379
+ // src/schema.ts
380
+ function defineSchema(schema) {
381
+ return schema;
382
+ }
383
+
384
+ // src/utils.ts
385
+ var import_dotenv = __toESM(require("dotenv"), 1);
386
+ function loadEnv(schema, options) {
387
+ const fileEnv = import_dotenv.default.config({ debug: false, path: options?.path }).parsed || {};
388
+ const env = { ...process.env, ...fileEnv };
389
+ const validator = new EnvValidator(schema, options);
390
+ return validator.validate(env);
391
+ }
392
+ function createEnvProxy(validatedEnv) {
393
+ return new Proxy(validatedEnv, {
394
+ get(target, prop) {
395
+ if (typeof prop === "string" && !(prop in target)) {
396
+ throw new Error(
397
+ `Environment variable ${prop} is not validated`
398
+ );
399
+ }
400
+ return target[prop];
401
+ }
402
+ });
403
+ }
404
+
405
+ // src/compose.ts
406
+ function composeSchema(...schemas) {
407
+ const result = /* @__PURE__ */ Object.create(null);
408
+ for (const schema of schemas) {
409
+ for (const key of Object.keys(schema)) {
410
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
411
+ result[key] = schema[key];
412
+ }
413
+ }
414
+ return result;
415
+ }
416
+
417
+ // src/index.ts
418
+ var import_dotenv2 = __toESM(require("dotenv"), 1);
419
+ function validateEnv(schema, options) {
420
+ const fileEnv = import_dotenv2.default.config({ debug: false, path: options?.path }).parsed || {};
421
+ const env = { ...process.env, ...fileEnv };
422
+ const validator = new EnvValidator(schema, options);
423
+ return validator.validate(env);
424
+ }
425
+ // Annotate the CommonJS export names for ESM import in node:
426
+ 0 && (module.exports = {
427
+ AggregateError,
428
+ EnvAggregateError,
429
+ EnvValidationError,
430
+ EnvValidator,
431
+ composeSchema,
432
+ createEnvProxy,
433
+ defineSchema,
434
+ loadEnv,
435
+ validateEnv
436
+ });
package/dist/index.js CHANGED
@@ -8,7 +8,8 @@ export { defineSchema, EnvAggregateError,
8
8
  /** @deprecated Use `EnvAggregateError` instead. */
9
9
  AggregateError, EnvValidationError, EnvValidator, loadEnv, createEnvProxy, composeSchema, };
10
10
  export function validateEnv(schema, options) {
11
- const env = dotenv.config({ debug: false, path: options?.path }).parsed || {};
11
+ const fileEnv = dotenv.config({ debug: false, path: options?.path }).parsed || {};
12
+ const env = { ...process.env, ...fileEnv };
12
13
  const validator = new EnvValidator(schema, options);
13
14
  return validator.validate(env);
14
15
  }
package/dist/utils.js CHANGED
@@ -14,7 +14,8 @@ import { EnvValidator } from "./validator.js";
14
14
  * @returns {InferEnv<S>} The validated environment variables.
15
15
  */
16
16
  export function loadEnv(schema, options) {
17
- const env = dotenv.config({ debug: false, path: options?.path }).parsed || {};
17
+ const fileEnv = dotenv.config({ debug: false, path: options?.path }).parsed || {};
18
+ const env = { ...process.env, ...fileEnv };
18
19
  const validator = new EnvValidator(schema, options);
19
20
  return validator.validate(env);
20
21
  }
@@ -71,7 +71,7 @@ function generateDtsContent(filteredKeys, schemaAbsPath, dtsAbsPath) {
71
71
  "// ──────────────────────────────────────────────────────────────",
72
72
  "",
73
73
  'declare module "dotenv-gad/client" {',
74
- ` interface DotenvGadEnv extends ${envType} {}`,
74
+ ` export interface DotenvGadEnv extends ${envType} {}`,
75
75
  " export const env: DotenvGadEnv;",
76
76
  " export default env;",
77
77
  "}",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotenv-gad",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/types/index.d.ts",
6
6
  "type": "module",
@@ -8,7 +8,7 @@
8
8
  "dotenv-gad": "./dist/cli/index.js"
9
9
  },
10
10
  "scripts": {
11
- "build": "tsc -p tsconfig.build.json",
11
+ "build": "tsc -p tsconfig.build.json && tsup",
12
12
  "test": "vitest run",
13
13
  "test:watch": "vitest",
14
14
  "test:coverage": "vitest run --coverage",
@@ -40,7 +40,8 @@
40
40
  "exports": {
41
41
  ".": {
42
42
  "types": "./dist/types/index.d.ts",
43
- "import": "./dist/index.js"
43
+ "import": "./dist/index.js",
44
+ "require": "./dist/index.cjs"
44
45
  },
45
46
  "./vite": {
46
47
  "types": "./dist/types/vite-plugin.d.ts",
@@ -48,12 +49,14 @@
48
49
  },
49
50
  "./client": {
50
51
  "types": "./dist/types/client.d.ts",
51
- "import": "./dist/client.js"
52
+ "import": "./dist/client.js",
53
+ "require": "./dist/client.cjs"
52
54
  },
53
55
  "./package.json": "./package.json"
54
56
  },
55
57
  "files": [
56
58
  "dist/**/*.js",
59
+ "dist/**/*.cjs",
57
60
  "dist/**/*.d.ts",
58
61
  "README.md",
59
62
  "LICENSE"
@@ -78,6 +81,7 @@
78
81
  "globals": "^15.14.0",
79
82
  "husky": "^9.1.7",
80
83
  "prettier": "^3.4.2",
84
+ "tsup": "^8.5.1",
81
85
  "typescript": "^5.7.0",
82
86
  "typescript-eslint": "^8.19.1",
83
87
  "vite": "^6.0.0",