envprotect 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 arsheriff2k3
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # envProtect
2
+
3
+ Type-safe environment variable validation with Zod.
4
+
5
+ Stop crashing at 2 AM. Load, validate, and protect your env vars with full TypeScript inference, secrets masking, and `.env.example` generation.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install envprotect zod
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { createEnv, sensitive } from 'envprotect';
17
+ import { z } from 'zod';
18
+
19
+ const env = createEnv({
20
+ schema: {
21
+ DATABASE_URL: z.string().url(),
22
+ PORT: z.coerce.number().default(3000),
23
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
24
+ API_SECRET: sensitive(z.string().min(32)),
25
+ DEBUG: z.coerce.boolean().default(false),
26
+ },
27
+ });
28
+
29
+ env.PORT // number (not string)
30
+ env.DEBUG // boolean (not "false")
31
+ env.API_SECRET // string (masked in JSON.stringify)
32
+ ```
33
+
34
+ ## Features
35
+
36
+ - **Load + Validate** - Loads `.env` files and validates with Zod in one call
37
+ - **TypeScript Inference** - Full type inference from your schema. IDE autocomplete works.
38
+ - **Secrets Masking** - `JSON.stringify(env)` and `console.log(env)` mask sensitive values as `[MASKED]`
39
+ - **Fail Fast** - Clear error messages at startup, not crashes at 2 AM
40
+ - **`.env.example` Generation** - `npx envprotect generate --schema ./src/env.ts`
41
+ - **Zero Runtime Deps** - Zod is a peer dependency, not bundled
42
+ - **Framework Agnostic** - Works with Node.js, Bun, Deno, and edge runtimes
43
+
44
+ ## Error Messages
45
+
46
+ ```
47
+ envprotect: 2 environment variables failed validation:
48
+
49
+ DATABASE_URL: Required
50
+ Expected: string
51
+ Received: undefined
52
+
53
+ PORT: Expected number, received nan
54
+ Expected: number
55
+ Received: "abc"
56
+ ```
57
+
58
+ ## Secrets Masking
59
+
60
+ ```typescript
61
+ const env = createEnv({
62
+ schema: {
63
+ PUBLIC_KEY: z.string(),
64
+ SECRET_KEY: sensitive(z.string()),
65
+ },
66
+ source: { PUBLIC_KEY: 'pk_123', SECRET_KEY: 'sk_secret_456' },
67
+ });
68
+
69
+ console.log(JSON.stringify(env));
70
+ // {"PUBLIC_KEY":"pk_123","SECRET_KEY":"[MASKED]"}
71
+
72
+ // Direct access still works
73
+ env.SECRET_KEY // "sk_secret_456"
74
+ ```
75
+
76
+ ### Masking Behavior
77
+
78
+ | Path | Masked? |
79
+ |------|---------|
80
+ | `JSON.stringify(env)` | Yes |
81
+ | `console.log(env)` | Yes |
82
+ | `util.inspect(env)` | Yes |
83
+ | `String(env)` | Yes |
84
+ | `env.SECRET` (direct access) | No (by design) |
85
+ | `const { SECRET } = env` | No (known limitation) |
86
+ | `{ ...env }` | No (known limitation) |
87
+
88
+ ## Generate `.env.example`
89
+
90
+ ```bash
91
+ npx envprotect generate --schema ./src/env.ts --output .env.example
92
+ ```
93
+
94
+ ## API
95
+
96
+ ### `createEnv(options)`
97
+
98
+ | Option | Type | Default | Description |
99
+ |--------|------|---------|-------------|
100
+ | `schema` | `Record<string, ZodType>` | required | Zod schema for each env var |
101
+ | `files` | `string[]` | `['.env', '.env.local']` | `.env` files to load |
102
+ | `source` | `Record<string, string>` | `process.env` | Override env source |
103
+ | `sensitive` | `string[]` | `[]` | Keys to mask in serialization |
104
+ | `cwd` | `string` | `process.cwd()` | Working directory for `.env` files |
105
+
106
+ ### `sensitive(schema)`
107
+
108
+ Marks a Zod schema as sensitive. Values are masked in `JSON.stringify` and `console.log`.
109
+
110
+ ### `generateEnvExample(schema, sensitiveKeys?)`
111
+
112
+ Generates `.env.example` content from a schema.
113
+
114
+ ## Benchmarks
115
+
116
+ | Metric | Value |
117
+ |--------|-------|
118
+ | Validation speed | 3.0 us/call |
119
+ | Bundle size | 12 KB (unpacked) |
120
+ | Source | 470 lines, 7 modules |
121
+ | Runtime deps | 0 (Zod is peer) |
122
+ | Tests | 33 passing |
123
+
124
+ ## License
125
+
126
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import { writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ const args = process.argv.slice(2);
5
+ const command = args[0];
6
+ if (command === "generate") {
7
+ const outputIdx = args.indexOf("--output");
8
+ const output = outputIdx !== -1 ? args[outputIdx + 1] : ".env.example";
9
+ const schemaIdx = args.indexOf("--schema");
10
+ const schemaPath = schemaIdx !== -1 ? args[schemaIdx + 1] : undefined;
11
+ if (!schemaPath) {
12
+ console.log("Usage: envprotect generate --schema ./src/env.ts --output .env.example");
13
+ console.log("");
14
+ console.log("Your schema file must export a `schema` object and optionally a `sensitive` array.");
15
+ console.log("");
16
+ console.log("Example schema file (src/env.ts):");
17
+ console.log(' import { z } from "zod";');
18
+ console.log(" export const schema = {");
19
+ console.log(" DATABASE_URL: z.string().url().describe('PostgreSQL connection string'),");
20
+ console.log(" PORT: z.coerce.number().default(3000),");
21
+ console.log(" };");
22
+ console.log(' export const sensitive = ["DATABASE_URL"];');
23
+ process.exit(1);
24
+ }
25
+ const fullSchemaPath = resolve(process.cwd(), schemaPath);
26
+ import(fullSchemaPath)
27
+ .then(async (mod) => {
28
+ const { generateEnvExample } = await import("./generate.js");
29
+ const schema = mod.schema || mod.default?.schema;
30
+ const sensitive = mod.sensitive || mod.default?.sensitive || [];
31
+ if (!schema) {
32
+ console.error(`Error: No "schema" export found in ${schemaPath}`);
33
+ process.exit(1);
34
+ }
35
+ const content = generateEnvExample(schema, sensitive);
36
+ const outputPath = resolve(process.cwd(), output);
37
+ writeFileSync(outputPath, content, "utf-8");
38
+ console.log(`Generated ${output} with ${Object.keys(schema).length} variables`);
39
+ })
40
+ .catch((err) => {
41
+ console.error(`Error loading schema from ${schemaPath}:`, err.message);
42
+ process.exit(1);
43
+ });
44
+ }
45
+ else {
46
+ console.log("envprotect CLI");
47
+ console.log("");
48
+ console.log("Commands:");
49
+ console.log(" generate Generate a .env.example file from your schema");
50
+ console.log("");
51
+ console.log("Usage:");
52
+ console.log(" envprotect generate --schema ./src/env.ts --output .env.example");
53
+ }
@@ -0,0 +1,26 @@
1
+ import { z, type ZodType } from "zod";
2
+ export interface SensitiveSchema {
3
+ _sensitive?: boolean;
4
+ }
5
+ export type EnvSchema = Record<string, ZodType<any> & Partial<SensitiveSchema>>;
6
+ export interface CreateEnvOptions<T extends EnvSchema> {
7
+ /** Zod schema for each environment variable */
8
+ schema: T;
9
+ /** .env files to load (in order, later overrides earlier). Default: ['.env', '.env.local'] */
10
+ files?: string[];
11
+ /** Working directory for resolving .env files. Default: process.cwd() */
12
+ cwd?: string;
13
+ /** Override source of environment variables (default: process.env) */
14
+ source?: Record<string, string | undefined>;
15
+ /** Keys to mask in JSON/log output */
16
+ sensitive?: (keyof T)[];
17
+ }
18
+ type InferSchema<T extends EnvSchema> = {
19
+ [K in keyof T]: z.infer<T[K]>;
20
+ };
21
+ /**
22
+ * Load, validate, and return a fully typed environment object.
23
+ * Fails fast at startup with clear error messages if any variables are invalid or missing.
24
+ */
25
+ export declare function createEnv<T extends EnvSchema>(options: CreateEnvOptions<T>): Readonly<InferSchema<T>>;
26
+ export {};
@@ -0,0 +1,53 @@
1
+ import { loadEnvFiles } from "./loader.js";
2
+ import { EnvValidationError, zodIssueToEnvError } from "./errors.js";
3
+ import { createMaskedEnv } from "./masking.js";
4
+ /**
5
+ * Load, validate, and return a fully typed environment object.
6
+ * Fails fast at startup with clear error messages if any variables are invalid or missing.
7
+ */
8
+ export function createEnv(options) {
9
+ const { schema, files = [".env", ".env.local"], cwd, source, sensitive = [], } = options;
10
+ // 1. Load .env files
11
+ const fileVars = loadEnvFiles(files, cwd);
12
+ // 2. Merge: .env files < process.env (process.env wins)
13
+ const envSource = source ?? process.env;
14
+ const merged = { ...fileVars };
15
+ for (const key of Object.keys(schema)) {
16
+ if (envSource[key] !== undefined) {
17
+ merged[key] = envSource[key];
18
+ }
19
+ }
20
+ // 3. Validate each variable
21
+ const errors = [];
22
+ const result = {};
23
+ for (const [key, zodSchema] of Object.entries(schema)) {
24
+ const raw = merged[key];
25
+ const parseResult = zodSchema.safeParse(raw);
26
+ if (parseResult.success) {
27
+ result[key] = parseResult.data;
28
+ }
29
+ else {
30
+ for (const issue of parseResult.error.issues) {
31
+ errors.push(zodIssueToEnvError(key, issue));
32
+ }
33
+ }
34
+ }
35
+ // 4. Throw aggregated errors
36
+ if (errors.length > 0) {
37
+ throw new EnvValidationError(errors);
38
+ }
39
+ // 5. Build sensitive keys set
40
+ const sensitiveKeys = new Set();
41
+ for (const key of sensitive) {
42
+ sensitiveKeys.add(key);
43
+ }
44
+ // Also check for _sensitive flag on schema
45
+ for (const [key, zodSchema] of Object.entries(schema)) {
46
+ if (zodSchema._sensitive) {
47
+ sensitiveKeys.add(key);
48
+ }
49
+ }
50
+ // 6. Apply masking proxy and freeze
51
+ const env = createMaskedEnv(result, sensitiveKeys);
52
+ return Object.freeze(env);
53
+ }
@@ -0,0 +1,12 @@
1
+ import type { ZodIssue } from "zod";
2
+ export interface EnvError {
3
+ key: string;
4
+ message: string;
5
+ expected?: string;
6
+ received?: string;
7
+ }
8
+ export declare class EnvValidationError extends Error {
9
+ readonly errors: EnvError[];
10
+ constructor(errors: EnvError[]);
11
+ }
12
+ export declare function zodIssueToEnvError(key: string, issue: ZodIssue): EnvError;
package/dist/errors.js ADDED
@@ -0,0 +1,27 @@
1
+ export class EnvValidationError extends Error {
2
+ errors;
3
+ constructor(errors) {
4
+ const header = `envprotect: ${errors.length} environment variable${errors.length > 1 ? "s" : ""} failed validation:\n`;
5
+ const details = errors
6
+ .map((e) => {
7
+ let msg = ` ${e.key}: ${e.message}`;
8
+ if (e.expected)
9
+ msg += `\n Expected: ${e.expected}`;
10
+ if (e.received)
11
+ msg += `\n Received: ${e.received}`;
12
+ return msg;
13
+ })
14
+ .join("\n\n");
15
+ super(header + details);
16
+ this.name = "EnvValidationError";
17
+ this.errors = errors;
18
+ }
19
+ }
20
+ export function zodIssueToEnvError(key, issue) {
21
+ return {
22
+ key,
23
+ message: issue.message,
24
+ expected: "expected" in issue ? String(issue.expected) : undefined,
25
+ received: "received" in issue ? String(issue.received) : undefined,
26
+ };
27
+ }
@@ -0,0 +1,5 @@
1
+ import type { EnvSchema } from "./createEnv.js";
2
+ /**
3
+ * Generate .env.example content from a schema.
4
+ */
5
+ export declare function generateEnvExample(schema: EnvSchema, sensitiveKeys?: string[]): string;
@@ -0,0 +1,94 @@
1
+ function getZodTypeInfo(schema) {
2
+ const def = schema._def;
3
+ if (def.typeName === "ZodDefault") {
4
+ const inner = getZodTypeInfo(def.innerType);
5
+ return { ...inner };
6
+ }
7
+ if (def.typeName === "ZodOptional") {
8
+ const inner = getZodTypeInfo(def.innerType);
9
+ return { ...inner };
10
+ }
11
+ if (def.typeName === "ZodEffects") {
12
+ // z.coerce.*
13
+ const inner = getZodTypeInfo(def.schema);
14
+ return { ...inner };
15
+ }
16
+ if (def.typeName === "ZodString")
17
+ return { type: "string", innerDef: def };
18
+ if (def.typeName === "ZodNumber")
19
+ return { type: "number", innerDef: def };
20
+ if (def.typeName === "ZodBoolean")
21
+ return { type: "boolean", innerDef: def };
22
+ if (def.typeName === "ZodEnum")
23
+ return { type: def.values.join(" | "), innerDef: def };
24
+ return { type: "string", innerDef: def };
25
+ }
26
+ function isOptional(schema) {
27
+ const def = schema._def;
28
+ if (def.typeName === "ZodOptional")
29
+ return true;
30
+ if (def.typeName === "ZodDefault")
31
+ return true;
32
+ if (def.typeName === "ZodEffects")
33
+ return isOptional(def.schema);
34
+ return false;
35
+ }
36
+ function getDefault(schema) {
37
+ const def = schema._def;
38
+ if (def.typeName === "ZodDefault") {
39
+ const val = def.defaultValue();
40
+ return val !== undefined ? String(val) : undefined;
41
+ }
42
+ if (def.typeName === "ZodOptional")
43
+ return getDefault(def.innerType);
44
+ if (def.typeName === "ZodEffects")
45
+ return getDefault(def.schema);
46
+ return undefined;
47
+ }
48
+ function getDescription(schema) {
49
+ if (schema.description)
50
+ return schema.description;
51
+ const def = schema._def;
52
+ if (def.typeName === "ZodDefault")
53
+ return getDescription(def.innerType);
54
+ if (def.typeName === "ZodOptional")
55
+ return getDescription(def.innerType);
56
+ if (def.typeName === "ZodEffects")
57
+ return getDescription(def.schema);
58
+ return undefined;
59
+ }
60
+ /**
61
+ * Generate .env.example content from a schema.
62
+ */
63
+ export function generateEnvExample(schema, sensitiveKeys = []) {
64
+ const sensitiveSet = new Set(sensitiveKeys);
65
+ const lines = [
66
+ "# Environment Variables",
67
+ "# Generated by envprotect",
68
+ "#",
69
+ "",
70
+ ];
71
+ for (const [key, zodSchema] of Object.entries(schema)) {
72
+ const optional = isOptional(zodSchema);
73
+ const defaultVal = getDefault(zodSchema);
74
+ const description = getDescription(zodSchema);
75
+ const { type } = getZodTypeInfo(zodSchema);
76
+ const isSensitive = sensitiveSet.has(key) || zodSchema._sensitive;
77
+ const parts = [];
78
+ parts.push(optional ? "(optional" : "(required");
79
+ if (defaultVal !== undefined)
80
+ parts[parts.length - 1] += `, default: ${defaultVal}`;
81
+ parts[parts.length - 1] += ")";
82
+ if (isSensitive)
83
+ parts.push("sensitive");
84
+ parts.push(type);
85
+ if (description)
86
+ parts.push(description);
87
+ const comment = parts.join(" ");
88
+ const value = defaultVal ?? "";
89
+ lines.push(`# ${comment}`);
90
+ lines.push(`${key}=${value}`);
91
+ lines.push("");
92
+ }
93
+ return lines.join("\n");
94
+ }
@@ -0,0 +1,7 @@
1
+ export { createEnv } from "./createEnv.js";
2
+ export type { CreateEnvOptions, EnvSchema } from "./createEnv.js";
3
+ export { sensitive } from "./sensitive.js";
4
+ export { generateEnvExample } from "./generate.js";
5
+ export { EnvValidationError } from "./errors.js";
6
+ export type { EnvError } from "./errors.js";
7
+ export { loadEnvFiles, parseEnvFile } from "./loader.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { createEnv } from "./createEnv.js";
2
+ export { sensitive } from "./sensitive.js";
3
+ export { generateEnvExample } from "./generate.js";
4
+ export { EnvValidationError } from "./errors.js";
5
+ export { loadEnvFiles, parseEnvFile } from "./loader.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Parse a .env file content into key-value pairs.
3
+ * Handles quoted values, comments, empty lines, and multiline values.
4
+ */
5
+ export declare function parseEnvFile(content: string): Record<string, string>;
6
+ /**
7
+ * Load environment variables from .env files.
8
+ * Later files override earlier ones. process.env always takes precedence.
9
+ */
10
+ export declare function loadEnvFiles(files: string[], cwd?: string): Record<string, string>;
package/dist/loader.js ADDED
@@ -0,0 +1,57 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ /**
4
+ * Parse a .env file content into key-value pairs.
5
+ * Handles quoted values, comments, empty lines, and multiline values.
6
+ */
7
+ export function parseEnvFile(content) {
8
+ const result = {};
9
+ for (const line of content.split("\n")) {
10
+ const trimmed = line.trim();
11
+ // Skip empty lines and comments
12
+ if (!trimmed || trimmed.startsWith("#"))
13
+ continue;
14
+ const eqIndex = trimmed.indexOf("=");
15
+ if (eqIndex === -1)
16
+ continue;
17
+ const key = trimmed.slice(0, eqIndex).trim();
18
+ let value = trimmed.slice(eqIndex + 1).trim();
19
+ // Remove surrounding quotes
20
+ if ((value.startsWith('"') && value.endsWith('"')) ||
21
+ (value.startsWith("'") && value.endsWith("'"))) {
22
+ value = value.slice(1, -1);
23
+ }
24
+ // Remove inline comments (only for unquoted values)
25
+ if (!trimmed.slice(eqIndex + 1).trim().startsWith('"')) {
26
+ const commentIndex = value.indexOf(" #");
27
+ if (commentIndex !== -1) {
28
+ value = value.slice(0, commentIndex).trim();
29
+ }
30
+ }
31
+ if (key) {
32
+ result[key] = value;
33
+ }
34
+ }
35
+ return result;
36
+ }
37
+ /**
38
+ * Load environment variables from .env files.
39
+ * Later files override earlier ones. process.env always takes precedence.
40
+ */
41
+ export function loadEnvFiles(files, cwd = process.cwd()) {
42
+ const loaded = {};
43
+ for (const file of files) {
44
+ const filePath = resolve(cwd, file);
45
+ if (!existsSync(filePath))
46
+ continue;
47
+ try {
48
+ const content = readFileSync(filePath, "utf-8");
49
+ const parsed = parseEnvFile(content);
50
+ Object.assign(loaded, parsed);
51
+ }
52
+ catch {
53
+ // Skip files that can't be read
54
+ }
55
+ }
56
+ return loaded;
57
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Creates a masked env wrapper.
3
+ *
4
+ * Serialization paths return masked values:
5
+ * - JSON.stringify(env) → toJSON()
6
+ * - console.log(env) → nodejs.util.inspect.custom
7
+ * - util.inspect(env) → nodejs.util.inspect.custom
8
+ * - String(env) / `${env}` → toString() / Symbol.toPrimitive
9
+ *
10
+ * Direct property access (env.SECRET) always returns the real value.
11
+ * This is intentional — masking prevents accidental bulk serialization,
12
+ * not deliberate single-field access.
13
+ *
14
+ * KNOWN LIMITATIONS (documented, by design):
15
+ * const { SECRET } = env; → real value (destructuring = direct access)
16
+ * { ...env } → real values (spread = per-key access)
17
+ * Object.assign({}, env) → real values (same as spread)
18
+ * Object.values(env) → real values (per-key access)
19
+ *
20
+ * These are deliberate access patterns. Masking targets accidental
21
+ * serialization: console.log(env), JSON.stringify(env), error reporters.
22
+ */
23
+ export declare function createMaskedEnv<T extends Record<string, unknown>>(values: T, sensitiveKeys: Set<string>): T;
@@ -0,0 +1,57 @@
1
+ const MASKED = "[MASKED]";
2
+ /**
3
+ * Build a plain object with sensitive keys masked.
4
+ */
5
+ function buildMaskedSnapshot(target, sensitiveKeys) {
6
+ const result = {};
7
+ for (const key of Object.keys(target)) {
8
+ result[key] = sensitiveKeys.has(key) ? MASKED : target[key];
9
+ }
10
+ return result;
11
+ }
12
+ /**
13
+ * Creates a masked env wrapper.
14
+ *
15
+ * Serialization paths return masked values:
16
+ * - JSON.stringify(env) → toJSON()
17
+ * - console.log(env) → nodejs.util.inspect.custom
18
+ * - util.inspect(env) → nodejs.util.inspect.custom
19
+ * - String(env) / `${env}` → toString() / Symbol.toPrimitive
20
+ *
21
+ * Direct property access (env.SECRET) always returns the real value.
22
+ * This is intentional — masking prevents accidental bulk serialization,
23
+ * not deliberate single-field access.
24
+ *
25
+ * KNOWN LIMITATIONS (documented, by design):
26
+ * const { SECRET } = env; → real value (destructuring = direct access)
27
+ * { ...env } → real values (spread = per-key access)
28
+ * Object.assign({}, env) → real values (same as spread)
29
+ * Object.values(env) → real values (per-key access)
30
+ *
31
+ * These are deliberate access patterns. Masking targets accidental
32
+ * serialization: console.log(env), JSON.stringify(env), error reporters.
33
+ */
34
+ export function createMaskedEnv(values, sensitiveKeys) {
35
+ if (sensitiveKeys.size === 0)
36
+ return values;
37
+ return new Proxy(values, {
38
+ get(target, prop, receiver) {
39
+ // JSON.stringify calls toJSON()
40
+ if (prop === "toJSON") {
41
+ return () => buildMaskedSnapshot(target, sensitiveKeys);
42
+ }
43
+ // console.log / util.inspect in Node.js
44
+ if (prop === Symbol.for("nodejs.util.inspect.custom")) {
45
+ return (_depth, _opts) => buildMaskedSnapshot(target, sensitiveKeys);
46
+ }
47
+ // String(env) or `${env}`
48
+ if (prop === Symbol.toPrimitive) {
49
+ return (_hint) => JSON.stringify(buildMaskedSnapshot(target, sensitiveKeys));
50
+ }
51
+ if (prop === "toString") {
52
+ return () => JSON.stringify(buildMaskedSnapshot(target, sensitiveKeys));
53
+ }
54
+ return Reflect.get(target, prop, receiver);
55
+ },
56
+ });
57
+ }
@@ -0,0 +1,9 @@
1
+ import type { ZodType } from "zod";
2
+ import type { SensitiveSchema } from "./createEnv.js";
3
+ /**
4
+ * Mark a Zod schema as sensitive. Values will be masked in logs and JSON output.
5
+ *
6
+ * Usage:
7
+ * sensitive(z.string().min(32))
8
+ */
9
+ export declare function sensitive<T extends ZodType<any>>(schema: T): T & SensitiveSchema;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Mark a Zod schema as sensitive. Values will be masked in logs and JSON output.
3
+ *
4
+ * Usage:
5
+ * sensitive(z.string().min(32))
6
+ */
7
+ export function sensitive(schema) {
8
+ schema._sensitive = true;
9
+ return schema;
10
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "envprotect",
3
+ "version": "1.0.0",
4
+ "description": "Type-safe environment variable validation with Zod. Load, validate, and protect your env vars with full TypeScript inference, secrets masking, and .env.example generation.",
5
+ "author": "arsheriff2k3",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/arsheriff2k3/envprotect"
10
+ },
11
+ "homepage": "https://github.com/arsheriff2k3/envprotect#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/arsheriff2k3/envprotect/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "bin": {
19
+ "envprotect": "./dist/cli.js"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "LICENSE",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "test": "bun test",
35
+ "prepublishOnly": "npm run build"
36
+ },
37
+ "keywords": [
38
+ "env",
39
+ "environment",
40
+ "validation",
41
+ "zod",
42
+ "typescript",
43
+ "dotenv",
44
+ "config",
45
+ "type-safe",
46
+ "secrets",
47
+ "masking",
48
+ "envprotect"
49
+ ],
50
+ "peerDependencies": {
51
+ "zod": "^3.22.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/bun": "latest",
55
+ "typescript": "^5.5.0",
56
+ "zod": "^3.23.0"
57
+ }
58
+ }