@stacksjs/env 0.72.78 → 0.72.80

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/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { EnvKey } from '../../../env';
2
- import type { StacksEnv } from './types';
1
+ import type { EnvKey, StacksEnv } from './types';
3
2
  export declare function process(): StacksEnv;
4
3
  // eslint-disable-next-line pickier/no-unused-vars
5
4
  export declare function writeEnv(key: EnvKey, value: string, options?: { path: string }): void;
package/dist/index.js CHANGED
@@ -1,28 +1,28 @@
1
1
  // @bun
2
- import{createCipheriv as re,createDecipheriv as ie,createHash as G,createPrivateKey as Ce,createPublicKey as se,diffieHellman as oe,generateKeyPairSync as de,hkdfSync as ce,randomBytes as H}from"crypto";var X="x25519-public:",j="x25519-private:",w="encrypted:v2:",Q="encrypted:",ae=Buffer.from("stacks-env:v2","utf8"),ue="Environment decryption failed: authentication or format error";function fe(e){if(e.length===32)return e;return G("sha256").update(e).digest()}function Rn(e,n){let t=H(16),r=re("aes-256-gcm",fe(n),t);return{ciphertext:Buffer.concat([r.update(e,"utf8"),r.final()]).toString("hex"),iv:t.toString("hex"),authTag:r.getAuthTag().toString("hex")}}function be(e,n,t,r){try{let i=ie("aes-256-gcm",fe(n),Buffer.from(t,"hex"));return i.setAuthTag(Buffer.from(r,"hex")),Buffer.concat([i.update(Buffer.from(e,"hex")),i.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function P(e){return e.toString("base64url")}function b(e,n,t){if(typeof e!=="string"||e.length>0&&!/^[A-Za-z0-9_-]+$/.test(e))throw Error(`${n} is not canonical base64url`);let r=Buffer.from(e,"base64url");if(P(r)!==e)throw Error(`${n} is not canonical base64url`);if(t!==void 0&&r.length!==t)throw Error(`${n} has an invalid length`);return r}function Le(e){if(!e.startsWith(X))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=se({key:b(e.slice(X.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function Ke(e){if(!e.startsWith(j))throw Error("Private key is not a version 2 X25519 key");let n=Ce({key:b(e.slice(j.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function Ee(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function k(){let{publicKey:e,privateKey:n}=de("x25519"),t=e.export({format:"der",type:"spki"}),r=n.export({format:"der",type:"pkcs8"});return{publicKey:`${X}${P(t)}`,privateKey:`${j}${P(r)}`}}function B(e,n){let t=Le(n),r=de("x25519"),i=P(r.publicKey.export({format:"der",type:"spki"})),s=H(16),o=H(12),a=oe({privateKey:r.privateKey,publicKey:t}),c=Buffer.from(ce("sha256",a,s,ae,32)),u={epk:i,salt:P(s),nonce:P(o)};try{let d=re("aes-256-gcm",c,o);d.setAAD(Ee(u));let f=Buffer.concat([d.update(e,"utf8"),d.final()]),p={v:2,...u,ciphertext:P(f),tag:P(d.getAuthTag())};return`${w}${P(Buffer.from(JSON.stringify(p),"utf8"))}`}finally{a.fill(0),c.fill(0)}}function ke(e){let n=b(e.slice(w.length),"envelope").toString("utf8"),t=JSON.parse(n);if(!t||typeof t!=="object"||Array.isArray(t))throw Error("envelope is not an object");let r=Object.keys(t).sort(),i=["ciphertext","epk","nonce","salt","tag","v"];if(r.length!==i.length||r.some((s,o)=>s!==i[o]))throw Error("envelope fields are invalid");if(t.v!==2)throw Error("envelope version is unsupported");for(let s of["epk","salt","nonce","ciphertext","tag"])if(typeof t[s]!=="string")throw Error(`${s} is invalid`);return t}function $e(e,n){try{let t=ke(e),r=Ke(n),i=se({key:b(t.epk,"ephemeral public key"),format:"der",type:"spki"});if(i.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let s=b(t.salt,"salt",16),o=b(t.nonce,"nonce",12),a=b(t.ciphertext,"ciphertext"),c=b(t.tag,"tag",16),u=oe({privateKey:r,publicKey:i}),d=Buffer.from(ce("sha256",u,s,ae,32));try{let f=ie("aes-256-gcm",d,o);return f.setAAD(Ee(t)),f.setAuthTag(c),Buffer.concat([f.update(a),f.final()]).toString("utf8")}finally{u.fill(0),d.fill(0)}}catch{throw Error(ue)}}function Ue(e,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let t=Buffer.from(e.slice(Q.length),"base64");if(t.length<32)throw Error("invalid legacy payload");let r=G("sha256").update(Buffer.from(n,"hex")).digest(),i=G("sha256").update(r).digest();return be(t.subarray(32).toString("hex"),i,t.subarray(0,16).toString("hex"),t.subarray(16,32).toString("hex"))}catch{throw Error(ue)}}function yn(e){return e.startsWith(Q)&&!e.startsWith(w)}function K(e,n){if(!e.startsWith(Q))return e;return e.startsWith(w)?$e(e,n):Ue(e,n)}function Tn(e,n,t){return B(K(e,n),t)}function vn(e){let n=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function q(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as we}from"child_process";import{readFileSync as Be}from"fs";import{arch as xe,hostname as Ve,platform as le,release as Me,userInfo as Fe}from"os";import{basename as Ye,dirname as We,resolve as Ge}from"path";function O(e,n={}){let t={},r=[],i=[],s=e.split(`
3
- `);for(let o of s){let a=o.trim();if(!a||a.startsWith("#"))continue;if(a.startsWith("DOTENV_PUBLIC_KEY=")){let f=a.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(f&&f[1]!==void 0)t.DOTENV_PUBLIC_KEY=f[1];continue}let c=a.match(/^([^=]+)=(.*)$/);if(!c||c[1]===void 0||c[2]===void 0)continue;let u=c[1].trim(),d=c[2].trim();if(d.startsWith('"')&&d.endsWith('"')||d.startsWith("'")&&d.endsWith("'")){if(d=d.slice(1,-1),d.includes("\\n"))d=d.replace(/\\n/g,`
4
- `)}if(d.startsWith("encrypted:")||d.startsWith("enc:")){if(!n.privateKey){i.push(u);continue}try{let f=d.startsWith("enc:")?`encrypted:${d.slice(4)}`:d;d=K(f,n.privateKey)}catch(f){r.push(`Failed to decrypt ${u}: ${f instanceof Error?f.message:"Unknown error"}`)}}d=He(d,{...n.processEnv||process.env,...t}),d=qe(d),t[u]=d}return{parsed:t,errors:r,skippedEncrypted:i}}function He(e,n){return e.replace(/\$\{([^}]+)\}/g,(t,r)=>{let i=r.match(/^([^:\-+]+)(:-|-)(.+)$/);if(i){let[,o,a,c]=i,u=n[o];if(a===":-")return u||c;else return u!==void 0?u:c}let s=r.match(/^([^:\-+]+)(:?\+)(.+)$/);if(s){let[,o,a,c]=s,u=n[o];if(a===":+")return u?c:"";else return u!==void 0?c:""}return n[r]||""})}var Xe=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function je(e){let n=[],t="",r=null,i=!1;for(let s of e.trim()){if(i){t+=s,i=!1;continue}if(s==="\\"&&r!=="'"){i=!0;continue}if(r){if(s===r)r=null;else t+=s;continue}if(s==='"'||s==="'"){r=s;continue}if(/\s/.test(s)){if(t)n.push(t),t="";continue}t+=s}if(i||r)throw Error("Command substitution contains an unterminated escape or quote");if(t)n.push(t);return n}function Qe(e){let[n,...t]=e,r=t.some((i)=>i.startsWith("-"));if(n==="echo"&&!r)return t.join(" ");if(n==="printf"&&t.length===1&&!t[0]?.includes("%"))return t[0];if(n==="hostname"&&t.length===0)return Ve();if(n==="whoami"&&t.length===0)return Fe().username;if(n==="pwd"&&t.length===0)return process.cwd();if(n==="basename"&&t.length===1)return Ye(t[0]??"");if(n==="dirname"&&t.length===1)return We(t[0]??"");if(n==="cat"&&t.length>0&&!r)return t.map((i)=>Be(Ge(process.cwd(),i),"utf8")).join("");if(n==="uname"){let i=t[0]??"-s";if(t.length<=1&&i==="-m")return xe();if(t.length<=1&&i==="-r")return Me();if(t.length<=1&&i==="-s")return{aix:"AIX",android:"Android",cygwin:"CYGWIN",darwin:"Darwin",freebsd:"FreeBSD",haiku:"Haiku",linux:"Linux",netbsd:"NetBSD",openbsd:"OpenBSD",sunos:"SunOS",win32:"Windows_NT"}[le()]??le()}return}function qe(e){return e.replace(/\$\(([^)]+)\)/g,(n,t)=>{try{let r=je(t),i=r[0];if(!i||!Xe.has(i))return console.warn(`[env] Blocked command substitution for disallowed command: ${i}`),"";let s=Qe(r);if(s!==void 0)return s.trim();let o=we(i,r.slice(1),{encoding:"utf8",env:process.env});if(o.status===0)return o.stdout.trim();let a=o.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${o.status??"unknown"}): ${i}${a?`: ${a}`:""}`)}catch(r){console.warn(`[env] Command substitution error: ${r instanceof Error?r.message:String(r)}`)}return""})}async function Pn(e,n={}){let t={},r=[],i=[];for(let s of e)try{let o=Bun.file(s);if(!o.size)continue;let a=await o.text(),{parsed:c,errors:u,skippedEncrypted:d}=O(a,n);r.push(...u),i.push(...d);for(let[f,p]of Object.entries(c)){if(!n.overload&&t[f]!==void 0)continue;t[f]=p}}catch(o){if(o?.code==="ENOENT")continue;r.push(`Failed to read ${s}: ${o instanceof Error?o.message:String(o)}`)}return{parsed:t,errors:r,skippedEncrypted:i}}import{existsSync as x}from"fs";import{readFileSync as z}from"fs";import{resolve as V}from"path";function U(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}function L(e){if(!e||U(e))return;let n=e.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(n))return;if(n==="prod")return"production";if(n==="stage")return"staging";if(n==="dev")return"development";return n}var M=null,F,Y;function ze(e,n,t=".env.keys"){let r=V(n,t);if(!x(r))return;try{let{parsed:i}=O(z(r,"utf-8"));if(e){let s=i[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`];if(s)return s}return i.DOTENV_PRIVATE_KEY}catch{return}}function J(e={}){return L(e.env)||Y||L("development")||L(process.env.DOTENV_ENV)||L(process.env.APP_ENV)||"development"}function Je(e={}){let n=J(e),t=e.cwd||process.cwd();if(M===n)return F;let r=q(n);if(!r)r=process.env.DOTENV_PRIVATE_KEY;if(!r)r=ze(n,t);return M=n,F=r,r}function $n(){M=null,F=void 0,Y=void 0}function pe(e,n={}){if(!U(e))return e;let t=Je(n);if(!t)return;try{let r=e.startsWith("enc:")?`encrypted:${e.slice(4)}`:e;return K(r,t)}catch{return}}function ge(e={}){let{path:n=[".env"],overload:t=!1,env:r,privateKey:i,keysFile:s=".env.keys",quiet:o=!1,cwd:a=process.cwd()}=e,c=Array.isArray(n)?n:[n],u=[],d=0,f=new Set(Object.entries(process.env).filter(([,E])=>E!==void 0&&!U(E)).map(([E])=>E)),p=i;if(!p&&s){let E=V(a,s);if(x(E))try{let l=z(E,"utf-8"),{parsed:y}=O(l);if(r){let v=`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`;p=y[v]}else p=y.DOTENV_PRIVATE_KEY}catch(l){u.push(`Failed to load keys file: ${l instanceof Error?l.message:"Unknown error"}`)}}if(!p)p=q(r||"");for(let E of c){let l=V(a,E);if(!x(l)){if(!o)u.push(`File not found: ${l}`);continue}try{let y=z(l,"utf-8"),{parsed:v,errors:R,skippedEncrypted:m}=O(y,{privateKey:p,processEnv:process.env});if(u.push(...R),m.length>0){for(let C of m)if(U(process.env[C]))delete process.env[C];let g=r?`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`:"DOTENV_PRIVATE_KEY",N=m.slice(0,5).join(", "),D=m.length>5?`, \u2026 +${m.length-5} more`:"";console.warn(`[env] warning: skipped ${m.length} encrypted value(s) in ${E} (${g} not set; defaults apply): ${N}${D}`)}let T=0;for(let[g,N]of Object.entries(v)){if(g==="DOTENV_PUBLIC_KEY")continue;let D=process.env[g],C=U(D)&&N!==D;if(t||!f.has(g)||C)process.env[g]=N,d++,T++}if(!o&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${T}/${Object.keys(v).length} variables from ${E}`),process.env.__ENV_LOADED__="1"}catch(y){u.push(`Failed to load ${l}: ${y instanceof Error?y.message:"Unknown error"}`)}}return{loaded:d,errors:u}}function Ze(e={}){return{name:"env-plugin",setup(n){ge(e)}}}function Un(e={}){let n=L(e.env)||L("development")||L(process.env.DOTENV_ENV)||L(process.env.APP_ENV)||"development",t=e.cwd||process.cwd();if(Y!==n)Y=n,M=null,F=void 0;let r=[],i=(a)=>{if(!r.includes(a)&&x(V(t,a)))r.push(a)};i(".env"),i(".env.local");let s=`.env.${n}`,o=`.env.${n}.local`;return i(s),i(o),ge({...e,path:r,env:n})}var Bn=Ze;import{existsSync as S,readFileSync as I,renameSync as Z,rmSync as _e,writeFileSync as h}from"fs";import{basename as ee,dirname as me,resolve as A}from"path";function Yn(e,n){if(e)return e;let t=String(n??"").trim();if(!t||t==="development"||t==="dev"||t==="local")return"";if(!/^[\w.-]+$/.test(t))return"";return`.env.${t}`}function Re(e,n,t,r){let i=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${t}="${n}"`,""],s=!1;for(let o of e.split(`
5
- `)){let a=o.trim();if(a.startsWith("#/"))continue;if(a.startsWith("DOTENV_PUBLIC_KEY"))continue;if(!a&&!s)continue;if(!a||a.startsWith("#")){s=!0,i.push(o);continue}s=!0;let c=a.match(/^([^=]+)=(.*)$/);if(!c||c[1]===void 0||c[2]===void 0){i.push(o);continue}let u=c[1].trim(),d=c[2].trim();if(d.startsWith('"')&&d.endsWith('"')||d.startsWith("'")&&d.endsWith("'"))d=d.slice(1,-1);if((!r.key||u.includes(r.key))&&(!r.excludeKey||!u.includes(r.excludeKey))&&!d.startsWith("encrypted:"))d=B(d,n);i.push(`${u}="${d}"`)}return i.join(`
6
- `)}function W(e){let t=((e||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase();return{publicKeyName:t?`DOTENV_PUBLIC_KEY_${t}`:"DOTENV_PUBLIC_KEY",privateKeyName:t?`DOTENV_PRIVATE_KEY_${t}`:"DOTENV_PRIVATE_KEY"}}function ye(e,n){let t,r=!1;for(let i of e.split(`
7
- `)){let s=i.trim();if(!s||s.startsWith("#"))continue;let o=s.match(/^([^=]+)=(.*)$/);if(!o?.[1]||o[2]===void 0)continue;let a=o[1].trim(),c=o[2].trim().replace(/^["']|["']$/g,"");if(a===n)t=c;else if(c.startsWith("encrypted:")||c.startsWith("enc:"))r=!0}return t&&r?t:void 0}function Wn(e={}){let n=e.cwd||process.cwd(),t=A(n,e.file||".env"),r=A(n,e.keysFile||".env.keys");if(!S(t))return{success:!1,error:`File not found: ${t}`};try{let i,s,o=ye(I(t,"utf-8"),W(e.file).publicKeyName);if(o)i=o;else if(S(r)){let u=I(r,"utf-8"),{parsed:d}=O(u),{publicKeyName:f,privateKeyName:p}=W(e.file);if(i=d[f]||"",s=d[p]||"",!i||!s){let E=k();i=E.publicKey,s=E.privateKey;let l=`
2
+ import{createCipheriv as re,createDecipheriv as ie,createHash as G,createPrivateKey as Ce,createPublicKey as se,diffieHellman as oe,generateKeyPairSync as de,hkdfSync as ae,randomBytes as H}from"crypto";var X="x25519-public:",j="x25519-private:",x="encrypted:v2:",Q="encrypted:",ce=Buffer.from("stacks-env:v2","utf8"),ue="Environment decryption failed: authentication or format error";function fe(e){if(e.length===32)return e;return G("sha256").update(e).digest()}function yn(e,n){let t=H(16),r=re("aes-256-gcm",fe(n),t);return{ciphertext:Buffer.concat([r.update(e,"utf8"),r.final()]).toString("hex"),iv:t.toString("hex"),authTag:r.getAuthTag().toString("hex")}}function be(e,n,t,r){try{let i=ie("aes-256-gcm",fe(n),Buffer.from(t,"hex"));return i.setAuthTag(Buffer.from(r,"hex")),Buffer.concat([i.update(Buffer.from(e,"hex")),i.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function P(e){return e.toString("base64url")}function b(e,n,t){if(typeof e!=="string"||e.length>0&&!/^[A-Za-z0-9_-]+$/.test(e))throw Error(`${n} is not canonical base64url`);let r=Buffer.from(e,"base64url");if(P(r)!==e)throw Error(`${n} is not canonical base64url`);if(t!==void 0&&r.length!==t)throw Error(`${n} has an invalid length`);return r}function Ke(e){if(!e.startsWith(X))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=se({key:b(e.slice(X.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function Le(e){if(!e.startsWith(j))throw Error("Private key is not a version 2 X25519 key");let n=Ce({key:b(e.slice(j.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function Ee(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function k(){let{publicKey:e,privateKey:n}=de("x25519"),t=e.export({format:"der",type:"spki"}),r=n.export({format:"der",type:"pkcs8"});return{publicKey:`${X}${P(t)}`,privateKey:`${j}${P(r)}`}}function w(e,n){let t=Ke(n),r=de("x25519"),i=P(r.publicKey.export({format:"der",type:"spki"})),s=H(16),o=H(12),c=oe({privateKey:r.privateKey,publicKey:t}),a=Buffer.from(ae("sha256",c,s,ce,32)),u={epk:i,salt:P(s),nonce:P(o)};try{let d=re("aes-256-gcm",a,o);d.setAAD(Ee(u));let f=Buffer.concat([d.update(e,"utf8"),d.final()]),p={v:2,...u,ciphertext:P(f),tag:P(d.getAuthTag())};return`${x}${P(Buffer.from(JSON.stringify(p),"utf8"))}`}finally{c.fill(0),a.fill(0)}}function ke(e){let n=b(e.slice(x.length),"envelope").toString("utf8"),t=JSON.parse(n);if(!t||typeof t!=="object"||Array.isArray(t))throw Error("envelope is not an object");let r=Object.keys(t).sort(),i=["ciphertext","epk","nonce","salt","tag","v"];if(r.length!==i.length||r.some((s,o)=>s!==i[o]))throw Error("envelope fields are invalid");if(t.v!==2)throw Error("envelope version is unsupported");for(let s of["epk","salt","nonce","ciphertext","tag"])if(typeof t[s]!=="string")throw Error(`${s} is invalid`);return t}function Ue(e,n){try{let t=ke(e),r=Le(n),i=se({key:b(t.epk,"ephemeral public key"),format:"der",type:"spki"});if(i.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let s=b(t.salt,"salt",16),o=b(t.nonce,"nonce",12),c=b(t.ciphertext,"ciphertext"),a=b(t.tag,"tag",16),u=oe({privateKey:r,publicKey:i}),d=Buffer.from(ae("sha256",u,s,ce,32));try{let f=ie("aes-256-gcm",d,o);return f.setAAD(Ee(t)),f.setAuthTag(a),Buffer.concat([f.update(c),f.final()]).toString("utf8")}finally{u.fill(0),d.fill(0)}}catch{throw Error(ue)}}function xe(e,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let t=Buffer.from(e.slice(Q.length),"base64");if(t.length<32)throw Error("invalid legacy payload");let r=G("sha256").update(Buffer.from(n,"hex")).digest(),i=G("sha256").update(r).digest();return be(t.subarray(32).toString("hex"),i,t.subarray(0,16).toString("hex"),t.subarray(16,32).toString("hex"))}catch{throw Error(ue)}}function mn(e){return e.startsWith(Q)&&!e.startsWith(x)}function L(e,n){if(!e.startsWith(Q))return e;return e.startsWith(x)?Ue(e,n):xe(e,n)}function Rn(e,n,t){return w(L(e,n),t)}function vn(e){let n=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function q(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as $e}from"child_process";import{readFileSync as we}from"fs";import{arch as Be,hostname as Ve,platform as le,release as Me,userInfo as Fe}from"os";import{basename as Ye,dirname as We,resolve as Ge}from"path";function I(e,n={}){let t={},r=[],i=[],s=e.split(`
3
+ `);for(let o of s){let c=o.trim();if(!c||c.startsWith("#"))continue;if(c.startsWith("DOTENV_PUBLIC_KEY=")){let f=c.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(f&&f[1]!==void 0)t.DOTENV_PUBLIC_KEY=f[1];continue}let a=c.match(/^([^=]+)=(.*)$/);if(!a||a[1]===void 0||a[2]===void 0)continue;let u=a[1].trim(),d=a[2].trim();if(d.startsWith('"')&&d.endsWith('"')||d.startsWith("'")&&d.endsWith("'")){if(d=d.slice(1,-1),d.includes("\\n"))d=d.replace(/\\n/g,`
4
+ `)}if(d.startsWith("encrypted:")||d.startsWith("enc:")){if(!n.privateKey){i.push(u);continue}try{let f=d.startsWith("enc:")?`encrypted:${d.slice(4)}`:d;d=L(f,n.privateKey)}catch(f){r.push(`Failed to decrypt ${u}: ${f instanceof Error?f.message:"Unknown error"}`)}}d=He(d,{...n.processEnv||process.env,...t}),d=qe(d),t[u]=d}return{parsed:t,errors:r,skippedEncrypted:i}}function He(e,n){return e.replace(/\$\{([^}]+)\}/g,(t,r)=>{let i=r.match(/^([^:\-+]+)(:-|-)(.+)$/);if(i){let[,o,c,a]=i,u=n[o];if(c===":-")return u||a;else return u!==void 0?u:a}let s=r.match(/^([^:\-+]+)(:?\+)(.+)$/);if(s){let[,o,c,a]=s,u=n[o];if(c===":+")return u?a:"";else return u!==void 0?a:""}return n[r]||""})}var Xe=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function je(e){let n=[],t="",r=null,i=!1;for(let s of e.trim()){if(i){t+=s,i=!1;continue}if(s==="\\"&&r!=="'"){i=!0;continue}if(r){if(s===r)r=null;else t+=s;continue}if(s==='"'||s==="'"){r=s;continue}if(/\s/.test(s)){if(t)n.push(t),t="";continue}t+=s}if(i||r)throw Error("Command substitution contains an unterminated escape or quote");if(t)n.push(t);return n}function Qe(e){let[n,...t]=e,r=t.some((i)=>i.startsWith("-"));if(n==="echo"&&!r)return t.join(" ");if(n==="printf"&&t.length===1&&!t[0]?.includes("%"))return t[0];if(n==="hostname"&&t.length===0)return Ve();if(n==="whoami"&&t.length===0)return Fe().username;if(n==="pwd"&&t.length===0)return process.cwd();if(n==="basename"&&t.length===1)return Ye(t[0]??"");if(n==="dirname"&&t.length===1)return We(t[0]??"");if(n==="cat"&&t.length>0&&!r)return t.map((i)=>we(Ge(process.cwd(),i),"utf8")).join("");if(n==="uname"){let i=t[0]??"-s";if(t.length<=1&&i==="-m")return Be();if(t.length<=1&&i==="-r")return Me();if(t.length<=1&&i==="-s")return{aix:"AIX",android:"Android",cygwin:"CYGWIN",darwin:"Darwin",freebsd:"FreeBSD",haiku:"Haiku",linux:"Linux",netbsd:"NetBSD",openbsd:"OpenBSD",sunos:"SunOS",win32:"Windows_NT"}[le()]??le()}return}function qe(e){return e.replace(/\$\(([^)]+)\)/g,(n,t)=>{try{let r=je(t),i=r[0];if(!i||!Xe.has(i))return console.warn(`[env] Blocked command substitution for disallowed command: ${i}`),"";let s=Qe(r);if(s!==void 0)return s.trim();let o=$e(i,r.slice(1),{encoding:"utf8",env:process.env});if(o.status===0)return o.stdout.trim();let c=o.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${o.status??"unknown"}): ${i}${c?`: ${c}`:""}`)}catch(r){console.warn(`[env] Command substitution error: ${r instanceof Error?r.message:String(r)}`)}return""})}async function Pn(e,n={}){let t={},r=[],i=[];for(let s of e)try{let o=Bun.file(s);if(!o.size)continue;let c=await o.text(),{parsed:a,errors:u,skippedEncrypted:d}=I(c,n);r.push(...u),i.push(...d);for(let[f,p]of Object.entries(a)){if(!n.overload&&t[f]!==void 0)continue;t[f]=p}}catch(o){if(o?.code==="ENOENT")continue;r.push(`Failed to read ${s}: ${o instanceof Error?o.message:String(o)}`)}return{parsed:t,errors:r,skippedEncrypted:i}}import{existsSync as B}from"fs";import{readFileSync as z}from"fs";import{resolve as V}from"path";function U(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}function K(e){if(!e||U(e))return;let n=e.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(n))return;if(n==="prod")return"production";if(n==="stage")return"staging";if(n==="dev")return"development";return n}var M=null,F,Y;function ze(e,n,t=".env.keys"){let r=V(n,t);if(!B(r))return;try{let{parsed:i}=I(z(r,"utf-8"));if(e){let s=i[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`];if(s)return s}return i.DOTENV_PRIVATE_KEY}catch{return}}function J(e={}){return K(e.env)||Y||K("development")||K(process.env.DOTENV_ENV)||K(process.env.APP_ENV)||"development"}function Je(e={}){let n=J(e),t=e.cwd||process.cwd();if(M===n)return F;let r=q(n);if(!r)r=process.env.DOTENV_PRIVATE_KEY;if(!r)r=ze(n,t);return M=n,F=r,r}function Un(){M=null,F=void 0,Y=void 0}function pe(e,n={}){if(!U(e))return e;let t=Je(n);if(!t)return;try{let r=e.startsWith("enc:")?`encrypted:${e.slice(4)}`:e;return L(r,t)}catch{return}}function ge(e={}){let{path:n=[".env"],overload:t=!1,env:r,privateKey:i,keysFile:s=".env.keys",quiet:o=!1,cwd:c=process.cwd()}=e,a=Array.isArray(n)?n:[n],u=[],d=0,f=new Set(Object.entries(process.env).filter(([,E])=>E!==void 0&&!U(E)).map(([E])=>E)),p=i;if(!p&&s){let E=V(c,s);if(B(E))try{let l=z(E,"utf-8"),{parsed:m}=I(l);if(r){let v=`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`;p=m[v]}else p=m.DOTENV_PRIVATE_KEY}catch(l){u.push(`Failed to load keys file: ${l instanceof Error?l.message:"Unknown error"}`)}}if(!p)p=q(r||"");for(let E of a){let l=V(c,E);if(!B(l)){if(!o)u.push(`File not found: ${l}`);continue}try{let m=z(l,"utf-8"),{parsed:v,errors:y,skippedEncrypted:T}=I(m,{privateKey:p,processEnv:process.env});if(u.push(...y),T.length>0){for(let C of T)if(U(process.env[C]))delete process.env[C];let g=r?`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`:"DOTENV_PRIVATE_KEY",N=T.slice(0,5).join(", "),D=T.length>5?`, \u2026 +${T.length-5} more`:"";console.warn(`[env] warning: skipped ${T.length} encrypted value(s) in ${E} (${g} not set; defaults apply): ${N}${D}`)}let R=0;for(let[g,N]of Object.entries(v)){if(g==="DOTENV_PUBLIC_KEY")continue;let D=process.env[g],C=U(D)&&N!==D;if(t||!f.has(g)||C)process.env[g]=N,d++,R++}if(!o&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${R}/${Object.keys(v).length} variables from ${E}`),process.env.__ENV_LOADED__="1"}catch(m){u.push(`Failed to load ${l}: ${m instanceof Error?m.message:"Unknown error"}`)}}return{loaded:d,errors:u}}function Ze(e={}){return{name:"env-plugin",setup(n){ge(e)}}}function xn(e={}){let n=K(e.env)||K("development")||K(process.env.DOTENV_ENV)||K(process.env.APP_ENV)||"development",t=e.cwd||process.cwd();if(Y!==n)Y=n,M=null,F=void 0;let r=[],i=(c)=>{if(!r.includes(c)&&B(V(t,c)))r.push(c)};i(".env"),i(".env.local");let s=`.env.${n}`,o=`.env.${n}.local`;return i(s),i(o),ge({...e,path:r,env:n})}var wn=Ze;import{existsSync as S,readFileSync as O,renameSync as Z,rmSync as _e,writeFileSync as h}from"fs";import{basename as ee,dirname as Te,resolve as A}from"path";function Yn(e,n){if(e)return e;let t=String(n??"").trim();if(!t||t==="development"||t==="dev"||t==="local")return"";if(!/^[\w.-]+$/.test(t))return"";return`.env.${t}`}function ye(e,n,t,r){let i=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${t}="${n}"`,""],s=!1;for(let o of e.split(`
5
+ `)){let c=o.trim();if(c.startsWith("#/"))continue;if(c.startsWith("DOTENV_PUBLIC_KEY"))continue;if(!c&&!s)continue;if(!c||c.startsWith("#")){s=!0,i.push(o);continue}s=!0;let a=c.match(/^([^=]+)=(.*)$/);if(!a||a[1]===void 0||a[2]===void 0){i.push(o);continue}let u=a[1].trim(),d=a[2].trim();if(d.startsWith('"')&&d.endsWith('"')||d.startsWith("'")&&d.endsWith("'"))d=d.slice(1,-1);if((!r.key||u.includes(r.key))&&(!r.excludeKey||!u.includes(r.excludeKey))&&!d.startsWith("encrypted:"))d=w(d,n);i.push(`${u}="${d}"`)}return i.join(`
6
+ `)}function W(e){let t=((e||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase();return{publicKeyName:t?`DOTENV_PUBLIC_KEY_${t}`:"DOTENV_PUBLIC_KEY",privateKeyName:t?`DOTENV_PRIVATE_KEY_${t}`:"DOTENV_PRIVATE_KEY"}}function me(e,n){let t,r=!1;for(let i of e.split(`
7
+ `)){let s=i.trim();if(!s||s.startsWith("#"))continue;let o=s.match(/^([^=]+)=(.*)$/);if(!o?.[1]||o[2]===void 0)continue;let c=o[1].trim(),a=o[2].trim().replace(/^["']|["']$/g,"");if(c===n)t=a;else if(a.startsWith("encrypted:")||a.startsWith("enc:"))r=!0}return t&&r?t:void 0}function Wn(e={}){let n=e.cwd||process.cwd(),t=A(n,e.file||".env"),r=A(n,e.keysFile||".env.keys");if(!S(t))return{success:!1,error:`File not found: ${t}`};try{let i,s,o=me(O(t,"utf-8"),W(e.file).publicKeyName);if(o)i=o;else if(S(r)){let u=O(r,"utf-8"),{parsed:d}=I(u),{publicKeyName:f,privateKeyName:p}=W(e.file);if(i=d[f]||"",s=d[p]||"",!i||!s){let E=k();i=E.publicKey,s=E.privateKey;let l=`
8
8
  ${f}="${i}"
9
9
  ${p}="${s}"
10
10
  `;h(r,u+l,"utf-8")}}else{let u=k();i=u.publicKey,s=u.privateKey;let{publicKeyName:d,privateKeyName:f}=W(e.file),p=`# .env.keys - Keep this file secure and never commit to source control
11
11
  ${d}="${i}"
12
12
  ${f}="${s}"
13
- `;h(r,p,"utf-8")}let a=I(t,"utf-8"),c=Re(a,i,W(e.file).publicKeyName,e);if(e.stdout)return{success:!0,output:c};return h(t,c,"utf-8"),{success:!0,output:`\u2714 encrypted (${e.file||".env"})
14
- \u2714 key added to ${e.keysFile||".env.keys"}`}}catch(i){return{success:!1,error:`Failed to encrypt: ${i instanceof Error?i.message:"Unknown error"}`}}}function en(e={}){let n=e.cwd||process.cwd(),t=A(n,e.file||".env"),r=A(n,e.keysFile||".env.keys");if(!S(t))return{success:!1,error:`File not found: ${t}`};if(!S(r))return{success:!1,error:`Keys file not found: ${r}`};try{let i=I(r,"utf-8"),{parsed:s}=O(i),c=((e.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),u=c?`DOTENV_PRIVATE_KEY_${c}`:"DOTENV_PRIVATE_KEY",d=s[u];if(!d)return{success:!1,error:`Private key not found: ${u}`};let f=[d];if(u!=="DOTENV_PRIVATE_KEY"&&s.DOTENV_PRIVATE_KEY&&s.DOTENV_PRIVATE_KEY!==d)f.push(s.DOTENV_PRIVATE_KEY);let E=I(t,"utf-8").split(`
15
- `),l=[];for(let v of E){let R=v.trim();if(!R||R.startsWith("#")){l.push(v);continue}if(R.startsWith("DOTENV_PUBLIC_KEY"))continue;let m=R.match(/^([^=]+)=(.*)$/);if(!m||m[1]===void 0||m[2]===void 0){l.push(v);continue}let T=m[1].trim(),g=m[2].trim();if(g.startsWith('"')&&g.endsWith('"')||g.startsWith("'")&&g.endsWith("'"))g=g.slice(1,-1);let N=g.startsWith("encrypted:");if(e.key&&!T.includes(e.key))N=!1;if(N){let D,C;for(let Pe of f)try{D=K(g,Pe);break}catch(De){C=De}if(D===void 0)throw C instanceof Error?C:Error(`Unable to decrypt ${T}`);g=D}l.push(`${T}="${g}"`)}let y=l.join(`
16
- `);if(e.stdout)return{success:!0,output:y};return h(t,y,"utf-8"),{success:!0,output:`\u2714 decrypted (${e.file||".env"})`}}catch(i){return{success:!1,error:`Failed to decrypt: ${i instanceof Error?i.message:"Unknown error"}`}}}function Gn(e,n,t={}){let r=t.cwd||process.cwd(),i=A(r,t.file||".env"),s=A(r,t.keysFile||".env.keys");try{let o="";if(S(i))o=I(i,"utf-8");let a=o.split(`
17
- `),c=!1,u,p=((t.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),E=p?`DOTENV_PUBLIC_KEY_${p}`:"DOTENV_PUBLIC_KEY",l=p?`DOTENV_PRIVATE_KEY_${p}`:"DOTENV_PRIVATE_KEY";for(let R of a){let m=R.trim();if(m.startsWith(`${E}=`)){let T=m.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(T)u=T[1];break}}if(u){let R=!1;if(S(s)){let m=I(s,"utf-8"),{parsed:T}=O(m);R=Boolean(T[l])}if(!R&&!process.env[l]&&!ye(o,E))u=void 0}if(!t.plain&&!u){let R=k();u=R.publicKey;let m="";if(S(s))m=I(s,"utf-8");m+=`
18
- ${E}="${R.publicKey}"
19
- ${l}="${R.privateKey}"
20
- `,h(s,m,"utf-8");for(let T=a.length-1;T>=0;T--){let g=a[T];if(g!==void 0&&g.trim().startsWith(`${E}=`))a.splice(T,1)}a.unshift(`${E}="${u}"`)}let y=n;if(!t.plain&&u)y=B(n,u);for(let R=0;R<a.length;R++){let m=a[R];if(m===void 0)continue;if(m.trim().startsWith(`${e}=`)){a[R]=`${e}="${y}"`,c=!0;break}}if(!c)a.push(`${e}="${y}"`);let v=a.join(`
21
- `);return h(i,v,"utf-8"),{success:!0,output:`set ${e}${t.plain?"":" with encryption"} (${t.file||".env"})`}}catch(o){return{success:!1,error:`Failed to set: ${o instanceof Error?o.message:"Unknown error"}`}}}function Hn(e,n={}){let t=n.cwd||process.cwd(),r=A(t,n.file||".env");if(!S(r))return{success:!1,error:`File not found: ${r}`};try{let i,s=A(t,n.keysFile||".env.keys"),c=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),u=c?`DOTENV_PRIVATE_KEY_${c}`:"DOTENV_PRIVATE_KEY";if(S(s)){let l=I(s,"utf-8"),{parsed:y}=O(l);i=y[u]}if(!i)i=process.env[u];let d=I(r,"utf-8"),{parsed:f}=O(d,{privateKey:i});if(e&&!n.all){let l=f[e];if(l===void 0)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:l}}let p=n.all?{...process.env,...f}:f,E;switch(n.format){case"shell":E=Object.entries(p).map(([l,y])=>`${l}=${y}`).join(" ");break;case"eval":E=Object.entries(p).map(([l,y])=>`${l}="${y}"`).join(`
22
- `);break;case"json":default:E=n.prettyPrint?JSON.stringify(p,null,2):JSON.stringify(p);break}return{success:!0,output:E}}catch(i){return{success:!1,error:`Failed to get: ${i instanceof Error?i.message:"Unknown error"}`}}}function Xn(e,n={}){let t=n.cwd||process.cwd(),r=A(t,n.keysFile||".env.keys");if(!S(r))return{success:!1,error:`Keys file not found: ${r}`};try{let i=I(r,"utf-8"),{parsed:s}=O(i),c=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),u=c?`DOTENV_PUBLIC_KEY_${c}`:"DOTENV_PUBLIC_KEY",d=c?`DOTENV_PRIVATE_KEY_${c}`:"DOTENV_PRIVATE_KEY";if(e){let E=s[e];if(!E)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:E}}let f={[u]:s[u],[d]:s[d]};return{success:!0,output:n.format==="shell"?`${u}=${f[u]} ${d}=${f[d]}`:JSON.stringify(f)}}catch(i){return{success:!1,error:`Failed to get keypair: ${i instanceof Error?i.message:"Unknown error"}`}}}function jn(e={}){let n=en({file:e.file,keysFile:e.keysFile,stdout:!0,cwd:e.cwd});if(!n.success)return n;let t=e.cwd||process.cwd(),r=A(t,e.file||".env"),i=A(t,e.keysFile||".env.keys");if(n.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let s=k(),o=I(r),a=I(i),c=a.toString("utf8").split(`
23
- `),u=e.file||".env",d=ee(u).replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),f=d?`DOTENV_PUBLIC_KEY_${d}`:"DOTENV_PUBLIC_KEY",p=d?`DOTENV_PRIVATE_KEY_${d}`:"DOTENV_PRIVATE_KEY",E=!1,l=!1;for(let g=0;g<c.length;g++){let N=c[g];if(N===void 0)continue;if(N.startsWith(`${f}=`))c[g]=`${f}="${s.publicKey}"`,E=!0;else if(N.startsWith(`${p}=`))c[g]=`${p}="${s.privateKey}"`,l=!0}if(!E)c.push(`${f}="${s.publicKey}"`);if(!l)c.push(`${p}="${s.privateKey}"`);let y=`${c.join(`
13
+ `;h(r,p,"utf-8")}let c=O(t,"utf-8"),a=ye(c,i,W(e.file).publicKeyName,e);if(e.stdout)return{success:!0,output:a};return h(t,a,"utf-8"),{success:!0,output:`\u2714 encrypted (${e.file||".env"})
14
+ \u2714 key added to ${e.keysFile||".env.keys"}`}}catch(i){return{success:!1,error:`Failed to encrypt: ${i instanceof Error?i.message:"Unknown error"}`}}}function en(e={}){let n=e.cwd||process.cwd(),t=A(n,e.file||".env"),r=A(n,e.keysFile||".env.keys");if(!S(t))return{success:!1,error:`File not found: ${t}`};if(!S(r))return{success:!1,error:`Keys file not found: ${r}`};try{let i=O(r,"utf-8"),{parsed:s}=I(i),a=((e.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),u=a?`DOTENV_PRIVATE_KEY_${a}`:"DOTENV_PRIVATE_KEY",d=s[u];if(!d)return{success:!1,error:`Private key not found: ${u}`};let f=[d];if(u!=="DOTENV_PRIVATE_KEY"&&s.DOTENV_PRIVATE_KEY&&s.DOTENV_PRIVATE_KEY!==d)f.push(s.DOTENV_PRIVATE_KEY);let E=O(t,"utf-8").split(`
15
+ `),l=[];for(let v of E){let y=v.trim();if(!y||y.startsWith("#")){l.push(v);continue}if(y.startsWith("DOTENV_PUBLIC_KEY"))continue;let T=y.match(/^([^=]+)=(.*)$/);if(!T||T[1]===void 0||T[2]===void 0){l.push(v);continue}let R=T[1].trim(),g=T[2].trim();if(g.startsWith('"')&&g.endsWith('"')||g.startsWith("'")&&g.endsWith("'"))g=g.slice(1,-1);let N=g.startsWith("encrypted:");if(e.key&&!R.includes(e.key))N=!1;if(N){let D,C;for(let Pe of f)try{D=L(g,Pe);break}catch(De){C=De}if(D===void 0)throw C instanceof Error?C:Error(`Unable to decrypt ${R}`);g=D}l.push(`${R}="${g}"`)}let m=l.join(`
16
+ `);if(e.stdout)return{success:!0,output:m};return h(t,m,"utf-8"),{success:!0,output:`\u2714 decrypted (${e.file||".env"})`}}catch(i){return{success:!1,error:`Failed to decrypt: ${i instanceof Error?i.message:"Unknown error"}`}}}function Gn(e,n,t={}){let r=t.cwd||process.cwd(),i=A(r,t.file||".env"),s=A(r,t.keysFile||".env.keys");try{let o="";if(S(i))o=O(i,"utf-8");let c=o.split(`
17
+ `),a=!1,u,p=((t.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),E=p?`DOTENV_PUBLIC_KEY_${p}`:"DOTENV_PUBLIC_KEY",l=p?`DOTENV_PRIVATE_KEY_${p}`:"DOTENV_PRIVATE_KEY";for(let y of c){let T=y.trim();if(T.startsWith(`${E}=`)){let R=T.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(R)u=R[1];break}}if(u){let y=!1;if(S(s)){let T=O(s,"utf-8"),{parsed:R}=I(T);y=Boolean(R[l])}if(!y&&!process.env[l]&&!me(o,E))u=void 0}if(!t.plain&&!u){let y=k();u=y.publicKey;let T="";if(S(s))T=O(s,"utf-8");T+=`
18
+ ${E}="${y.publicKey}"
19
+ ${l}="${y.privateKey}"
20
+ `,h(s,T,"utf-8");for(let R=c.length-1;R>=0;R--){let g=c[R];if(g!==void 0&&g.trim().startsWith(`${E}=`))c.splice(R,1)}c.unshift(`${E}="${u}"`)}let m=n;if(!t.plain&&u)m=w(n,u);for(let y=0;y<c.length;y++){let T=c[y];if(T===void 0)continue;if(T.trim().startsWith(`${e}=`)){c[y]=`${e}="${m}"`,a=!0;break}}if(!a)c.push(`${e}="${m}"`);let v=c.join(`
21
+ `);return h(i,v,"utf-8"),{success:!0,output:`set ${e}${t.plain?"":" with encryption"} (${t.file||".env"})`}}catch(o){return{success:!1,error:`Failed to set: ${o instanceof Error?o.message:"Unknown error"}`}}}function Hn(e,n={}){let t=n.cwd||process.cwd(),r=A(t,n.file||".env");if(!S(r))return{success:!1,error:`File not found: ${r}`};try{let i,s=A(t,n.keysFile||".env.keys"),a=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),u=a?`DOTENV_PRIVATE_KEY_${a}`:"DOTENV_PRIVATE_KEY";if(S(s)){let l=O(s,"utf-8"),{parsed:m}=I(l);i=m[u]}if(!i)i=process.env[u];let d=O(r,"utf-8"),{parsed:f}=I(d,{privateKey:i});if(e&&!n.all){let l=f[e];if(l===void 0)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:l}}let p=n.all?{...process.env,...f}:f,E;switch(n.format){case"shell":E=Object.entries(p).map(([l,m])=>`${l}=${m}`).join(" ");break;case"eval":E=Object.entries(p).map(([l,m])=>`${l}="${m}"`).join(`
22
+ `);break;case"json":default:E=n.prettyPrint?JSON.stringify(p,null,2):JSON.stringify(p);break}return{success:!0,output:E}}catch(i){return{success:!1,error:`Failed to get: ${i instanceof Error?i.message:"Unknown error"}`}}}function Xn(e,n={}){let t=n.cwd||process.cwd(),r=A(t,n.keysFile||".env.keys");if(!S(r))return{success:!1,error:`Keys file not found: ${r}`};try{let i=O(r,"utf-8"),{parsed:s}=I(i),a=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),u=a?`DOTENV_PUBLIC_KEY_${a}`:"DOTENV_PUBLIC_KEY",d=a?`DOTENV_PRIVATE_KEY_${a}`:"DOTENV_PRIVATE_KEY";if(e){let E=s[e];if(!E)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:E}}let f={[u]:s[u],[d]:s[d]};return{success:!0,output:n.format==="shell"?`${u}=${f[u]} ${d}=${f[d]}`:JSON.stringify(f)}}catch(i){return{success:!1,error:`Failed to get keypair: ${i instanceof Error?i.message:"Unknown error"}`}}}function jn(e={}){let n=en({file:e.file,keysFile:e.keysFile,stdout:!0,cwd:e.cwd});if(!n.success)return n;let t=e.cwd||process.cwd(),r=A(t,e.file||".env"),i=A(t,e.keysFile||".env.keys");if(n.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let s=k(),o=O(r),c=O(i),a=c.toString("utf8").split(`
23
+ `),u=e.file||".env",d=ee(u).replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),f=d?`DOTENV_PUBLIC_KEY_${d}`:"DOTENV_PUBLIC_KEY",p=d?`DOTENV_PRIVATE_KEY_${d}`:"DOTENV_PRIVATE_KEY",E=!1,l=!1;for(let g=0;g<a.length;g++){let N=a[g];if(N===void 0)continue;if(N.startsWith(`${f}=`))a[g]=`${f}="${s.publicKey}"`,E=!0;else if(N.startsWith(`${p}=`))a[g]=`${p}="${s.privateKey}"`,l=!0}if(!E)a.push(`${f}="${s.publicKey}"`);if(!l)a.push(`${p}="${s.privateKey}"`);let m=`${a.join(`
24
24
  `).replace(/\n+$/,"")}
25
- `,v=Re(n.output,s.publicKey,f,e),R=`${process.pid}-${Date.now()}`,m=A(me(r),`.${ee(r)}.${R}.rotate`),T=A(me(i),`.${ee(i)}.${R}.rotate`);try{if(h(m,v,{encoding:"utf8",mode:384}),h(T,y,{encoding:"utf8",mode:384}),e.stdout)return Z(T,i),{success:!0,output:v};Z(m,r);try{Z(T,i)}catch(g){throw h(r,o),g}return{success:!0,output:`\u2714 rotated (${e.file||".env"}) to encrypted:v2`}}catch(g){if(!S(i))h(i,a,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${g instanceof Error?g.message:"Unknown error"}`}}finally{_e(m,{force:!0}),_e(T,{force:!0})}}import _ from"process";import{platform as nn}from"os";var Te=typeof Bun<"u",ve=typeof _<"u"&&_.versions?.node!==void 0,tn=Te?"bun":ve?"node":"unknown",Jn={name:tn,version:Te?Bun.version:ve?_.version:void 0},te=nn(),Zn=te==="win32",et=te==="darwin",nt=te==="linux",Ae=Boolean(_.stdout?.isTTY),tt=typeof globalThis.window<"u",rn=Boolean(_.env.CI||_.env.CONTINUOUS_INTEGRATION||_.env.BUILD_NUMBER||_.env.RUN_ID),rt=Boolean(_.env.DEBUG||_.env.VERBOSE||_.argv.includes("--debug")||_.argv.includes("--verbose")),sn=rn||!Ae,it=Boolean(!sn&&(Ae||_.env.COLORTERM||_.env.FORCE_COLOR||_.env.TERM&&_.env.TERM!=="dumb")),ne={github:{name:"GitHub Actions",detected:Boolean(_.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(_.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(_.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(_.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(_.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(_.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(_.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(_.env.DYNO)},aws:{name:"AWS",detected:Boolean(_.env.AWS_REGION||_.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(_.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(_.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(_.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(_.env.RENDER)}},on=Object.keys(ne).find((e)=>{let n=ne[e];return n!==void 0&&n.detected})||"unknown",st=ne[on]||{name:"Unknown",detected:!1};import En from"process";import{projectPath as ln}from"@stacksjs/path";import Ne from"fs";var Ie={APP_ENV:["local","dev","development","staging","prod","production"],DB_CONNECTION:["mysql","sqlite","postgres","singlestore","vitess","dynamodb"],DB_MIGRATE_FRESH:["allow","confirm","disabled"],MAIL_MAILER:["smtp","mailgun","ses","log","sendgrid","mailtrap","capture"],SEARCH_ENGINE_DRIVER:["opensearch","meilisearch","algolia","typesense"],FRONTEND_APP_ENV:["development","staging","production"]};function dn(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}var cn=/(?:^|_)(?:PASSWORD|PASSWD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|CREDENTIALS|PRIVATE|SALT|DSN|CERT|CIPHER|SIGNATURE|WEBHOOK)(?:_|$)|(?:^|_)(?:ACCESS|SECRET)_KEY(?:_|$)|_KEY$/,an=/^DOTENV_(?:PUBLIC|PRIVATE)_KEY/,un=/^\$\{[^}]+\}$/;function ct(e,n={}){let{placeholders:t={},strict:r=!1}=n,i=[];for(let[s,o]of Object.entries(e)){if(!o||o.trim()==="")continue;if(dn(o))continue;if(an.test(s))continue;if(un.test(o.trim()))continue;let a=cn.test(s),c=Object.prototype.hasOwnProperty.call(t,s)&&t[s]===o;if(a&&!c){i.push({key:s,reason:"plaintext-secret"});continue}if(r)i.push({key:s,reason:"plaintext-value"})}return i}function at(e){return e.split(`
26
- `).map((n)=>n.trim()).filter(Boolean).filter((n)=>n!==".env.example").filter((n)=>/(?:^|\/)\.env(?:\.|$)/.test(n)).filter((n)=>!n.endsWith(".env.keys"))}function Oe(e){return`${e.replace(/[^a-z0-9]/gi,"").toUpperCase()}_`}function fn(e,n={}){let t=n.self?Oe(n.self):void 0,r=(n.tenants??[]).filter((o)=>typeof o==="string"&&o.trim().length>0).map((o)=>({slug:o,prefix:Oe(o)})).filter(({prefix:o})=>o!==t).sort((o,a)=>a.prefix.length-o.prefix.length),i={},s={};for(let[o,a]of Object.entries(e)){let c=r.find(({prefix:u})=>o.startsWith(u));if(c){(s[c.slug]??={})[o]=a;continue}i[o]=a}return{own:i,foreign:s}}function ft(e,n={}){return fn(e,n).own}function Et(e){return Object.entries(e.foreign).map(([n,t])=>({tenant:n,keys:Object.keys(t).sort()})).sort((n,t)=>n.tenant.localeCompare(t.tenant))}var Se=new Set;function pn(e){if(Se.has(e))return;Se.add(e);let n=J();console.warn(`[env] warning: "${e}" is encrypted but no usable private key was found for the "${n}" environment; falling back to its default. Set DOTENV_PRIVATE_KEY_${n.toUpperCase()} or ship .env.keys.`)}var gn={get:(e,n)=>{let t=e,r=t[n];if(typeof r==="string"&&(r.startsWith("encrypted:")||r.startsWith("enc:"))){let s=pe(r);if(s===void 0){pn(n),delete t[n];return}t[n]=s,r=s}let i=["_PORT","_TIMEOUT","_TTL","_SIZE","_LIMIT","_MAX","_MIN","_INTERVAL","_RETRIES","_CONCURRENCY","_WORKERS","_CONNECTIONS"];if(typeof r==="string"&&/^\d+$/.test(r)&&!r.startsWith("0")&&i.some((s)=>n.endsWith(s)))return Number(r);if(typeof r==="string"){let s=r.toLowerCase();if(s==="true")return!0;if(s==="false")return!1}return r}};function _n(){return typeof Bun<"u"?Bun.env:En.env}var he=new Proxy(_n(),gn);function yt(e,n,t){let r=t?.path||ln(".env"),s=Ne.readFileSync(r,"utf-8").split(`
27
- `),o=s.findIndex((u)=>u.startsWith(`${e}=`)),c=/[\s"'#$\\]/.test(n)?`"${n.replace(/"/g,"\\\"")}"`:n;if(o!==-1)s[o]=`${e}=${c}`;else s.push(`${e}=${c}`);Ne.writeFileSync(r,s.join(`
28
- `))}function Tt(e=he){let n=[],t=e;for(let[r,i]of Object.entries(Ie)){let s=t[r];if(s!==void 0&&s!==""&&!i.includes(String(s)))n.push(`${r}="${s}" is not valid. Allowed values: ${i.join(", ")}`)}return n}function vt(e,n=he){let t=[],r=n;for(let i of e){let s=r[i];if(s===void 0||s===""||s===null)t.push(i)}if(t.length>0)throw Error(`[env] Missing required environment variable(s): ${t.join(", ")}. Set them in .env or your process environment before booting.`);return n}export{cn as SECRET_NAME_PATTERN,J as activeEnvName,be as aesDecrypt,Rn as aesEncrypt,Un as autoLoadEnv,en as decryptEnv,pe as decryptEnvValue,K as decryptValue,Wn as encryptEnv,B as encryptValue,he as env,Ie as envEnum,W as envKeyNames,Ze as envPlugin,Et as foreignTenantKeys,k as generateKeypair,Hn as getEnv,Xn as getKeypair,q as getPrivateKey,Ae as hasTTY,tt as hasWindow,Te as isBun,rn as isCI,it as isColorSupported,rt as isDebug,dn as isEncryptedValue,yn as isLegacyEncryptedValue,nt as isLinux,et as isMacOS,sn as isMinimal,ve as isNode,Zn as isWindows,ge as loadEnv,Pn as loadEnvFiles,Tn as migrateEncryptedValue,O as parse,vn as parseEnvFromKey,fn as partitionTenantEnv,ct as plaintextSecrets,te as platform,_n as process,on as provider,st as providerInfo,vt as requireEnv,$n as resetPrivateKeyCache,Yn as resolveEnvFile,Je as resolvePrivateKey,ye as reusableEnvPublicKey,jn as rotateKeypair,tn as runtime,Jn as runtimeInfo,Gn as setEnv,ft as stripForeignTenantEnv,Oe as tenantEnvPrefix,at as trackedEnvFiles,Tt as validateEnv,yt as writeEnv};
25
+ `,v=ye(n.output,s.publicKey,f,e),y=`${process.pid}-${Date.now()}`,T=A(Te(r),`.${ee(r)}.${y}.rotate`),R=A(Te(i),`.${ee(i)}.${y}.rotate`);try{if(h(T,v,{encoding:"utf8",mode:384}),h(R,m,{encoding:"utf8",mode:384}),e.stdout)return Z(R,i),{success:!0,output:v};Z(T,r);try{Z(R,i)}catch(g){throw h(r,o),g}return{success:!0,output:`\u2714 rotated (${e.file||".env"}) to encrypted:v2`}}catch(g){if(!S(i))h(i,c,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${g instanceof Error?g.message:"Unknown error"}`}}finally{_e(T,{force:!0}),_e(R,{force:!0})}}import _ from"process";import{platform as nn}from"os";var Re=typeof Bun<"u",ve=typeof _<"u"&&_.versions?.node!==void 0,tn=Re?"bun":ve?"node":"unknown",Jn={name:tn,version:Re?Bun.version:ve?_.version:void 0},te=nn(),Zn=te==="win32",et=te==="darwin",nt=te==="linux",Ae=Boolean(_.stdout?.isTTY),tt=typeof globalThis.window<"u",rn=Boolean(_.env.CI||_.env.CONTINUOUS_INTEGRATION||_.env.BUILD_NUMBER||_.env.RUN_ID),rt=Boolean(_.env.DEBUG||_.env.VERBOSE||_.argv.includes("--debug")||_.argv.includes("--verbose")),sn=rn||!Ae,it=Boolean(!sn&&(Ae||_.env.COLORTERM||_.env.FORCE_COLOR||_.env.TERM&&_.env.TERM!=="dumb")),ne={github:{name:"GitHub Actions",detected:Boolean(_.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(_.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(_.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(_.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(_.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(_.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(_.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(_.env.DYNO)},aws:{name:"AWS",detected:Boolean(_.env.AWS_REGION||_.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(_.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(_.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(_.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(_.env.RENDER)}},on=Object.keys(ne).find((e)=>{let n=ne[e];return n!==void 0&&n.detected})||"unknown",st=ne[on]||{name:"Unknown",detected:!1};import En from"process";import{projectPath as ln}from"@stacksjs/path";import Ne from"fs";var Oe={APP_ENV:["local","dev","development","staging","prod","production"],DB_CONNECTION:["mysql","sqlite","postgres","singlestore","vitess","dynamodb"],DB_MIGRATE_FRESH:["allow","confirm","disabled"],MAIL_MAILER:["smtp","mailgun","ses","log","sendgrid","mailtrap","capture"],SEARCH_ENGINE_DRIVER:["opensearch","meilisearch","algolia","typesense"],FRONTEND_APP_ENV:["development","staging","production"]};function dt(e){return e}function dn(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}var an=/(?:^|_)(?:PASSWORD|PASSWD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|CREDENTIALS|PRIVATE|SALT|DSN|CERT|CIPHER|SIGNATURE|WEBHOOK)(?:_|$)|(?:^|_)(?:ACCESS|SECRET)_KEY(?:_|$)|_KEY$/,cn=/^DOTENV_(?:PUBLIC|PRIVATE)_KEY/,un=/^\$\{[^}]+\}$/;function ct(e,n={}){let{placeholders:t={},strict:r=!1}=n,i=[];for(let[s,o]of Object.entries(e)){if(!o||o.trim()==="")continue;if(dn(o))continue;if(cn.test(s))continue;if(un.test(o.trim()))continue;let c=an.test(s),a=Object.prototype.hasOwnProperty.call(t,s)&&t[s]===o;if(c&&!a){i.push({key:s,reason:"plaintext-secret"});continue}if(r)i.push({key:s,reason:"plaintext-value"})}return i}function ut(e){return e.split(`
26
+ `).map((n)=>n.trim()).filter(Boolean).filter((n)=>n!==".env.example").filter((n)=>/(?:^|\/)\.env(?:\.|$)/.test(n)).filter((n)=>!n.endsWith(".env.keys"))}function Ie(e){return`${e.replace(/[^a-z0-9]/gi,"").toUpperCase()}_`}function fn(e,n={}){let t=n.self?Ie(n.self):void 0,r=(n.tenants??[]).filter((o)=>typeof o==="string"&&o.trim().length>0).map((o)=>({slug:o,prefix:Ie(o)})).filter(({prefix:o})=>o!==t).sort((o,c)=>c.prefix.length-o.prefix.length),i={},s={};for(let[o,c]of Object.entries(e)){let a=r.find(({prefix:u})=>o.startsWith(u));if(a){(s[a.slug]??={})[o]=c;continue}i[o]=c}return{own:i,foreign:s}}function Et(e,n={}){return fn(e,n).own}function lt(e){return Object.entries(e.foreign).map(([n,t])=>({tenant:n,keys:Object.keys(t).sort()})).sort((n,t)=>n.tenant.localeCompare(t.tenant))}var Se=new Set;function pn(e){if(Se.has(e))return;Se.add(e);let n=J();console.warn(`[env] warning: "${e}" is encrypted but no usable private key was found for the "${n}" environment; falling back to its default. Set DOTENV_PRIVATE_KEY_${n.toUpperCase()} or ship .env.keys.`)}var gn={get:(e,n)=>{let t=e,r=t[n];if(typeof r==="string"&&(r.startsWith("encrypted:")||r.startsWith("enc:"))){let s=pe(r);if(s===void 0){pn(n),delete t[n];return}t[n]=s,r=s}let i=["_PORT","_TIMEOUT","_TTL","_SIZE","_LIMIT","_MAX","_MIN","_INTERVAL","_RETRIES","_CONCURRENCY","_WORKERS","_CONNECTIONS"];if(typeof r==="string"&&/^\d+$/.test(r)&&!r.startsWith("0")&&i.some((s)=>n.endsWith(s)))return Number(r);if(typeof r==="string"){let s=r.toLowerCase();if(s==="true")return!0;if(s==="false")return!1}return r}};function _n(){return typeof Bun<"u"?Bun.env:En.env}var he=new Proxy(_n(),gn);function Rt(e,n,t){let r=t?.path||ln(".env"),s=Ne.readFileSync(r,"utf-8").split(`
27
+ `),o=s.findIndex((u)=>u.startsWith(`${e}=`)),a=/[\s"'#$\\]/.test(n)?`"${n.replace(/"/g,"\\\"")}"`:n;if(o!==-1)s[o]=`${e}=${a}`;else s.push(`${e}=${a}`);Ne.writeFileSync(r,s.join(`
28
+ `))}function vt(e=he){let n=[],t=e;for(let[r,i]of Object.entries(Oe)){let s=t[r];if(s!==void 0&&s!==""&&!i.includes(String(s)))n.push(`${r}="${s}" is not valid. Allowed values: ${i.join(", ")}`)}return n}function At(e,n=he){let t=[],r=n;for(let i of e){let s=r[i];if(s===void 0||s===""||s===null)t.push(i)}if(t.length>0)throw Error(`[env] Missing required environment variable(s): ${t.join(", ")}. Set them in .env or your process environment before booting.`);return n}export{an as SECRET_NAME_PATTERN,J as activeEnvName,be as aesDecrypt,yn as aesEncrypt,xn as autoLoadEnv,en as decryptEnv,pe as decryptEnvValue,L as decryptValue,dt as defineEnv,Wn as encryptEnv,w as encryptValue,he as env,Oe as envEnum,W as envKeyNames,Ze as envPlugin,lt as foreignTenantKeys,k as generateKeypair,Hn as getEnv,Xn as getKeypair,q as getPrivateKey,Ae as hasTTY,tt as hasWindow,Re as isBun,rn as isCI,it as isColorSupported,rt as isDebug,dn as isEncryptedValue,mn as isLegacyEncryptedValue,nt as isLinux,et as isMacOS,sn as isMinimal,ve as isNode,Zn as isWindows,ge as loadEnv,Pn as loadEnvFiles,Rn as migrateEncryptedValue,I as parse,vn as parseEnvFromKey,fn as partitionTenantEnv,ct as plaintextSecrets,te as platform,_n as process,on as provider,st as providerInfo,At as requireEnv,Un as resetPrivateKeyCache,Yn as resolveEnvFile,Je as resolvePrivateKey,me as reusableEnvPublicKey,jn as rotateKeypair,tn as runtime,Jn as runtimeInfo,Gn as setEnv,Et as stripForeignTenantEnv,Ie as tenantEnvPrefix,ut as trackedEnvFiles,vt as validateEnv,Rt as writeEnv};
package/dist/types.d.ts CHANGED
@@ -1,5 +1,11 @@
1
- import type { BooleanValidatorType, NumberValidatorType, StringValidatorType } from '@stacksjs/ts-validation';
2
- import type { EnvKey } from '../../../env';
1
+ import type { BooleanValidatorType, EnumValidatorType, NumberValidatorType, StringValidatorType, Validator } from '@stacksjs/ts-validation';
2
+ /**
3
+ * Declare the application's environment variables.
4
+ *
5
+ * Returns the schema with its literal type intact, which is what lets
6
+ * {@link InferEnv} read each entry's validator.
7
+ */
8
+ export declare function defineEnv<const TSchema extends EnvConfig>(schema: TSchema): TSchema;
3
9
  export declare const envEnum: EnumObject;
4
10
  declare interface EnumObject {
5
11
  [key: string]: string[]
@@ -16,14 +22,19 @@ declare interface BooleanEnvConfig {
16
22
  validation: BooleanValidatorType
17
23
  default: boolean
18
24
  }
19
- declare interface EnumEnvConfig {
20
- validation: {
21
- readonly name: 'enum'
22
- getAllowedValues: () => readonly string[]
23
- }
24
- default: string
25
+ declare interface EnumEnvConfig<TValues extends string = string> {
26
+ validation: EnumValidatorType<TValues>
27
+ default: TValues | string
25
28
  }
26
- export declare interface StacksEnv {
29
+ /**
30
+ * The variables the framework itself defines.
31
+ *
32
+ * Closed on purpose: `StacksEnv` is what module augmentation extends, and
33
+ * {@link InferEnv} subtracts the keys named here. Were those keys read off
34
+ * `StacksEnv` instead, an augmentation built from `InferEnv` would reference
35
+ * the interface it is augmenting and TypeScript would reject the cycle.
36
+ */
37
+ export declare interface FrameworkEnv {
27
38
  APP_NAME: string | undefined
28
39
  APP_ENV: 'local' | 'dev' | 'development' | 'stage' | 'staging' | 'prod' | 'production' | undefined
29
40
  APP_KEY: string | undefined
@@ -35,6 +46,12 @@ export declare interface StacksEnv {
35
46
  APP_COMING_SOON_SECRET: string | undefined
36
47
  APP_ROOT: string | undefined
37
48
  DEBUG: boolean | undefined
49
+ FILESYSTEM_DISK: string | undefined
50
+ HCLOUD_TOKEN: string | undefined
51
+ HETZNER_API_TOKEN: string | undefined
52
+ DOTENV_PRIVATE_KEY: string | undefined
53
+ DOTENV_PUBLIC_KEY: string | undefined
54
+ SUDO_PASSWORD: string | undefined
38
55
  PORT: number | undefined
39
56
  PORT_BACKEND: number | undefined
40
57
  PORT_ADMIN: number | undefined
@@ -81,6 +98,9 @@ export declare interface StacksEnv {
81
98
  AWS_REGION: string | undefined
82
99
  AWS_HOSTED_ZONE_ID: string | undefined
83
100
  AWS_S3_BUCKET: string | undefined
101
+ AWS_BUCKET: string | undefined
102
+ AWS_URL: string | undefined
103
+ AWS_PROFILE: string | undefined
84
104
  AWS_S3_PREFIX: string | undefined
85
105
  AWS_SES_REGION: string | undefined
86
106
  MAIL_MAILER: string | undefined
@@ -235,11 +255,74 @@ export declare interface StacksEnv {
235
255
  VONAGE_API_SECRET: string | undefined
236
256
  VONAGE_FROM_NUMBER: string | undefined
237
257
  }
258
+ /**
259
+ * Every environment variable the application can read through `env`.
260
+ *
261
+ * Extends the framework's own set; an application adds its variables by
262
+ * augmenting this interface from `config/env.ts`, which is the file that
263
+ * already declares them:
264
+ *
265
+ * ```ts
266
+ * const envSchema = defineEnv({
267
+ * STRIPE_WEBHOOK_SECRET: { validation: schema.string(), default: '' },
268
+ * })
269
+ *
270
+ * declare module '@stacksjs/env' {
271
+ * interface StacksEnv extends InferEnv<typeof envSchema> {}
272
+ * }
273
+ *
274
+ * export default envSchema
275
+ * ```
276
+ *
277
+ * Nothing is generated: the types follow the schema, so they are the same on a
278
+ * fresh clone, in CI, and in production. They used to come from a generated
279
+ * `storage/framework/types/env.d.ts` whose key set was scraped from whichever
280
+ * `.env` happened to be on the machine running the generator, and whose types
281
+ * were read off each variable's live value there - so a production-only
282
+ * variable could never be typed at all, and one that was set locally could be
283
+ * `number` on one machine and `string` on another.
284
+ */
285
+ export declare interface StacksEnv extends FrameworkEnv {}
238
286
  export declare interface FrontendEnv {
239
287
  FRONTEND_APP_ENV: 'local' | 'development' | 'staging' | 'production'
240
288
  FRONTEND_APP_URL: string
241
289
  }
242
- declare type EnvValueConfig = StringEnvConfig | NumberEnvConfig | BooleanEnvConfig | EnumEnvConfig;
243
- export type EnvConfig = Partial<Record<EnvKey, EnvValueConfig>>;
290
+ export type EnvValueConfig = StringEnvConfig | NumberEnvConfig | BooleanEnvConfig | EnumEnvConfig<any>;
291
+ /**
292
+ * The shape of `config/env.ts`.
293
+ *
294
+ * Keyed by plain string rather than by a generated `EnvKey` union. That union
295
+ * came from `storage/framework/env.ts` - a file this package cannot see from an
296
+ * installed app, so the import resolved to `any` and the constraint silently
297
+ * did nothing anyway. More importantly it had the dependency backwards: an
298
+ * application could not declare its own variable until a generator had scraped
299
+ * that variable out of `.env` first.
300
+ */
301
+ export type EnvConfig = Record<string, EnvValueConfig>;
302
+ /**
303
+ * A key that can be written back to an `.env` file.
304
+ *
305
+ * Every declared variable, with autocompletion, without rejecting one the
306
+ * types do not know about yet.
307
+ */
308
+ export type EnvKey = keyof StacksEnv | (string & {});
309
+ /** The value type a single `config/env.ts` entry describes. */
310
+ declare type EnvValueOf<TEntry> = TEntry extends { validation: Validator<infer TValue> }
311
+ ? TValue
312
+ : TEntry extends { default: infer TDefault }
313
+ ? TDefault
314
+ : string;
315
+ /**
316
+ * The `StacksEnv` members an application's `config/env.ts` contributes.
317
+ *
318
+ * Each entry is typed by its validator, so `schema.number()` is a `number` and
319
+ * `schema.enum(['a', 'b'])` is `'a' | 'b'`, and every variable is optional
320
+ * because the process may simply not have it set. Keys the framework already
321
+ * declares are dropped: re-stating one with a different type is what makes a
322
+ * merged declaration fail to compile.
323
+ */
324
+ export type InferEnv<TSchema> = {
325
+ [K in Exclude<keyof TSchema, keyof FrameworkEnv>]: EnvValueOf<TSchema[K]> | undefined
326
+ }
244
327
  export type EnvSchema = EnvConfig;
245
328
  export type FrontendEnvKeys = keyof FrontendEnv;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/env",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.72.78",
5
+ "version": "0.72.80",
6
6
  "description": "Stacks env helper methods.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -52,13 +52,14 @@
52
52
  "scripts": {
53
53
  "build": "bun build.ts",
54
54
  "typecheck": "bun tsc --noEmit",
55
+ "typecheck:types": "bun tsc --noEmit -p tsconfig.type-tests.json --pretty false",
55
56
  "prepublishOnly": "bun run build"
56
57
  },
57
58
  "dependencies": {
58
- "@stacksjs/path": "0.72.78"
59
+ "@stacksjs/path": "0.72.80"
59
60
  },
60
61
  "devDependencies": {
61
62
  "better-dx": "^0.2.24",
62
- "@stacksjs/validation": "0.72.78"
63
+ "@stacksjs/validation": "0.72.80"
63
64
  }
64
65
  }