envfortress
Version:
Turn your .env into a fortress—type-checked, secure, and effortless for React, Vite, and Node.
26 lines (24 loc) • 5.04 kB
JavaScript
import {z}from'zod';export{z}from'zod';import {existsSync,readFileSync}from'fs';var $={boolean:()=>z.string().transform(e=>{let n=e.toLowerCase().trim();if(["true","1","yes","on"].includes(n))return true;if(["false","0","no","off"].includes(n))return false;throw new Error(`Invalid boolean value: ${e}. Expected 'true'/'false', '1'/'0', 'yes'/'no', or 'on'/'off'`)}),number:()=>z.string().transform(e=>{let n=Number(e);if(isNaN(n))throw new Error(`Invalid number: ${e}`);return n}),integer:()=>z.string().transform(e=>{let n=parseInt(e,10);if(isNaN(n))throw new Error(`Invalid integer: ${e}`);return n}),url:()=>z.string().url(),email:()=>z.string().email(),port:()=>z.string().transform(e=>{let n=parseInt(e,10);if(isNaN(n)||n<1||n>65535)throw new Error(`Invalid port number: ${e}. Must be between 1 and 65535`);return n}),json:e=>z.string().transform(n=>{try{let t=JSON.parse(n);return e?e.parse(t):t}catch{throw new Error(`Invalid JSON: ${n}`)}}),list:(e=",")=>z.string().transform(n=>n.split(e).map(t=>t.trim()).filter(Boolean)),secret:(e=32)=>z.string().min(e,{message:`Secret must be at least ${e} characters long`})};function C(e){if(!existsSync(e))return new Set;try{let n=readFileSync(e,"utf-8"),t=new Set;return n.split(`
`).forEach(r=>{let o=r.trim();if(o&&!o.startsWith("#")&&!o.startsWith("//")){let[s]=o.split("=");s&&!s.includes(" ")&&t.add(s.trim());}}),t}catch(n){return console.warn(`\u26A0\uFE0F Could not read ${e}:`,n),new Set}}function N(e,n,t){let r=[];for(let o of n)!e[o]&&!t&&r.push(o);if(r.length>0)throw new Error(`\u274C Missing required environment variables:
${r.map(s=>` - ${s}`).join(`
`)}
\u{1F4A1} To fix this:
1. Copy .env.example to .env
2. Fill in the missing values
3. Or set allowMissingInProduction: true if these are optional`)}function O(e,n){let t=e.issues.map(r=>{let o=Array.isArray(r.path)?r.path.join("."):"unknown",s=r.message;r.message.includes("Invalid URL")?s+=`
\u{1F4A1} Expected a valid URL (e.g., https://api.example.com)`:r.message.includes("too small")?s+=`
\u{1F4A1} Check the minimum length requirement`:r.message.includes("Invalid option")?s+=`
\u{1F4A1} Check the allowed values for this environment variable`:r.message.includes("Invalid JSON")?s+=`
\u{1F4A1} Expected valid JSON format`:r.message.includes("unrecognized keys")?s+=`
\u{1F4A1} Remove unknown environment variables or add them to your schema`:r.message.includes("Invalid email")?s+=`
\u{1F4A1} Expected a valid email address (e.g., user@example.com)`:r.message.includes("Invalid boolean")?s+=`
\u{1F4A1} Expected 'true'/'false', '1'/'0', 'yes'/'no', or 'on'/'off'`:r.message.includes("Invalid number")?s+=`
\u{1F4A1} Expected a valid number`:r.message.includes("Invalid integer")?s+=`
\u{1F4A1} Expected a valid integer`:r.message.includes("Invalid port")&&(s+=`
\u{1F4A1} Expected a port number between 1 and 65535`);let a=n[o];if(a&&a instanceof z.ZodEnum){let f=a.options?.join(", ");s+=`
\u{1F4A1} Allowed values: ${f}`;}return r.message.includes("Expected string")&&(s+=`
\u{1F4A1} Environment variables are always strings. Use transforms to convert to other types.`),{...r,message:s}});return new z.ZodError(t)}function h(e,n,t){let r={};for(let a of Object.keys(e))n[a]!==void 0&&(r[a]=n[a]);let s=z.object(e).safeParse(r);return s.success?{success:true,data:s.data}:{success:false,error:s.error}}var l=null,d=null;function y(e,n={}){let t=JSON.stringify(process.env);if(l&&d===t)return l;let{client:r,server:o}=e,{exampleFile:s=".env.example",allowMissingInProduction:a=false,strict:f=false,debug:S=false,onValidationError:x=c=>{console.error("\u274C [envfortress] Invalid environment variables:",c.issues),process.exit(1);}}=n,m=C(s);if(!a&&process.env.NODE_ENV==="production"&&m.size>0&&console.warn("\u26A0\uFE0F You are enforcing .env.example validation in production. Consider setting `allowMissingInProduction: true` if this causes deploy issues."),m.size>0&&N(process.env,m,a),process.env.NODE_ENV!=="production")for(let c in process.env)typeof process.env[c]>"u"&&console.warn(`\u26A0\uFE0F process.env["${c}"] is undefined`);let v=h(r,process.env),u=h(o,process.env),T=Object.keys(r),I=Object.keys(o),E=T.filter(c=>I.includes(c));if(E.length>0)throw new Error(`\u274C Environment variable overlap detected:
${E.map(g=>` - ${g} is defined in both client and server schemas`).join(`
`)}
\u{1F4A1} Each variable should only be defined in one schema (client or server)`);if(v.success===false||u.success===false){let c=[...v.success?[]:v.error.issues,...u.success?[]:u.error.issues],g=new z.ZodError(c);return x(O(g,{...r,...o})),{}}let p={...v.data,...u.data};return S&&process.env.NODE_ENV!=="production"&&console.log("[envfortress] Parsed env:",p),l=p,d=t,p}function Z(e,n={}){let t=JSON.stringify(process.env);if(l&&d===t)return l;let r=y({client:e,server:{}},n);return l=r,d=t,r}function K(){l=null,d=null;}var V=y;export{K as _clearEnvCacheForTests,y as createEnv,Z as createPublicEnv,V as default,$ as envUtils};//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map