@stacksjs/env 0.70.293 → 0.70.296

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
@@ -24,5 +24,6 @@ export * from './crypto';
24
24
  export * from './parser';
25
25
  export * from './plugin';
26
26
  export * from './cli';
27
+ export * from './plaintext';
27
28
  export * from './tenants';
28
29
  export * from './utils';
package/dist/index.js CHANGED
@@ -1,26 +1,27 @@
1
1
  // @bun
2
- import{createCipheriv as gf,createDecipheriv as Bf,createHash as r,createPrivateKey as qf,createPublicKey as $f,diffieHellman as Af,generateKeyPairSync as _f,hkdfSync as Xf,randomBytes as t}from"crypto";var y="x25519-public:",v="x25519-private:",P="encrypted:v2:",i="encrypted:",Df=Buffer.from("stacks-env:v2","utf8"),Hf="Environment decryption failed: authentication or format error";function Of(f){if(f.length===32)return f;return r("sha256").update(f).digest()}function O2(f,g){let B=t(16),A=gf("aes-256-gcm",Of(g),B);return{ciphertext:Buffer.concat([A.update(f,"utf8"),A.final()]).toString("hex"),iv:B.toString("hex"),authTag:A.getAuthTag().toString("hex")}}function zf(f,g,B,A){try{let $=Bf("aes-256-gcm",Of(g),Buffer.from(B,"hex"));return $.setAuthTag(Buffer.from(A,"hex")),Buffer.concat([$.update(Buffer.from(f,"hex")),$.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function V(f){return f.toString("base64url")}function C(f,g,B){if(typeof f!=="string"||f.length>0&&!/^[A-Za-z0-9_-]+$/.test(f))throw Error(`${g} is not canonical base64url`);let A=Buffer.from(f,"base64url");if(V(A)!==f)throw Error(`${g} is not canonical base64url`);if(B!==void 0&&A.length!==B)throw Error(`${g} has an invalid length`);return A}function Vf(f){if(!f.startsWith(y))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let g=$f({key:C(f.slice(y.length),"public key"),format:"der",type:"spki"});if(g.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return g}function xf(f){if(!f.startsWith(v))throw Error("Private key is not a version 2 X25519 key");let g=qf({key:C(f.slice(v.length),"private key"),format:"der",type:"pkcs8"});if(g.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return g}function Wf(f){return Buffer.from(`stacks-env:v2;${f.epk};${f.salt};${f.nonce}`,"utf8")}function S(){let{publicKey:f,privateKey:g}=_f("x25519"),B=f.export({format:"der",type:"spki"}),A=g.export({format:"der",type:"pkcs8"});return{publicKey:`${y}${V(B)}`,privateKey:`${v}${V(A)}`}}function c(f,g){let B=Vf(g),A=_f("x25519"),$=V(A.publicKey.export({format:"der",type:"spki"})),_=t(16),X=t(12),H=Af({privateKey:A.privateKey,publicKey:B}),W=Buffer.from(Xf("sha256",H,_,Df,32)),D={epk:$,salt:V(_),nonce:V(X)};try{let O=gf("aes-256-gcm",W,X);O.setAAD(Wf(D));let R=Buffer.concat([O.update(f,"utf8"),O.final()]),G={v:2,...D,ciphertext:V(R),tag:V(O.getAuthTag())};return`${P}${V(Buffer.from(JSON.stringify(G),"utf8"))}`}finally{H.fill(0),W.fill(0)}}function Cf(f){let g=C(f.slice(P.length),"envelope").toString("utf8"),B=JSON.parse(g);if(!B||typeof B!=="object"||Array.isArray(B))throw Error("envelope is not an object");let A=Object.keys(B).sort(),$=["ciphertext","epk","nonce","salt","tag","v"];if(A.length!==$.length||A.some((_,X)=>_!==$[X]))throw Error("envelope fields are invalid");if(B.v!==2)throw Error("envelope version is unsupported");for(let _ of["epk","salt","nonce","ciphertext","tag"])if(typeof B[_]!=="string")throw Error(`${_} is invalid`);return B}function Ff(f,g){try{let B=Cf(f),A=xf(g),$=$f({key:C(B.epk,"ephemeral public key"),format:"der",type:"spki"});if($.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let _=C(B.salt,"salt",16),X=C(B.nonce,"nonce",12),H=C(B.ciphertext,"ciphertext"),W=C(B.tag,"tag",16),D=Af({privateKey:A,publicKey:$}),O=Buffer.from(Xf("sha256",D,_,Df,32));try{let R=Bf("aes-256-gcm",O,X);return R.setAAD(Wf(B)),R.setAuthTag(W),Buffer.concat([R.update(H),R.final()]).toString("utf8")}finally{D.fill(0),O.fill(0)}}catch{throw Error(Hf)}}function bf(f,g){try{if(!/^[0-9a-f]{64}$/.test(g))throw Error("invalid legacy private key");let B=Buffer.from(f.slice(i.length),"base64");if(B.length<32)throw Error("invalid legacy payload");let A=r("sha256").update(Buffer.from(g,"hex")).digest(),$=r("sha256").update(A).digest();return zf(B.subarray(32).toString("hex"),$,B.subarray(0,16).toString("hex"),B.subarray(16,32).toString("hex"))}catch{throw Error(Hf)}}function W2(f){return f.startsWith(i)&&!f.startsWith(P)}function b(f,g){if(!f.startsWith(i))return f;return f.startsWith(P)?Ff(f,g):bf(f,g)}function R2(f,g,B){return c(b(f,g),B)}function T2(f){let g=f.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return g&&g[1]?g[1].toLowerCase():""}function n(f=""){return f?process.env[`DOTENV_PRIVATE_KEY_${f.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as Sf}from"child_process";import{readFileSync as hf}from"fs";import{arch as Kf,hostname as Nf,platform as Rf,release as Pf,userInfo as cf}from"os";import{basename as kf,dirname as mf,resolve as df}from"path";function M(f,g={}){let B={},A=[],$=[],_=f.split(`
3
- `);for(let X of _){let H=X.trim();if(!H||H.startsWith("#"))continue;if(H.startsWith("DOTENV_PUBLIC_KEY=")){let R=H.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(R&&R[1]!==void 0)B.DOTENV_PUBLIC_KEY=R[1];continue}let W=H.match(/^([^=]+)=(.*)$/);if(!W||W[1]===void 0||W[2]===void 0)continue;let D=W[1].trim(),O=W[2].trim();if(O.startsWith('"')&&O.endsWith('"')||O.startsWith("'")&&O.endsWith("'")){if(O=O.slice(1,-1),O.includes("\\n"))O=O.replace(/\\n/g,`
4
- `)}if(O.startsWith("encrypted:")||O.startsWith("enc:")){if(!g.privateKey){$.push(D);continue}try{let R=O.startsWith("enc:")?`encrypted:${O.slice(4)}`:O;O=b(R,g.privateKey)}catch(R){A.push(`Failed to decrypt ${D}: ${R instanceof Error?R.message:"Unknown error"}`)}}O=uf(O,{...g.processEnv||process.env,...B}),O=vf(O),B[D]=O}return{parsed:B,errors:A,skippedEncrypted:$}}function uf(f,g){return f.replace(/\$\{([^}]+)\}/g,(B,A)=>{let $=A.match(/^([^:\-+]+)(:-|-)(.+)$/);if($){let[,X,H,W]=$,D=g[X];if(H===":-")return D||W;else return D!==void 0?D:W}let _=A.match(/^([^:\-+]+)(:?\+)(.+)$/);if(_){let[,X,H,W]=_,D=g[X];if(H===":+")return D?W:"";else return D!==void 0?W:""}return g[A]||""})}var rf=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function tf(f){let g=[],B="",A=null,$=!1;for(let _ of f.trim()){if($){B+=_,$=!1;continue}if(_==="\\"&&A!=="'"){$=!0;continue}if(A){if(_===A)A=null;else B+=_;continue}if(_==='"'||_==="'"){A=_;continue}if(/\s/.test(_)){if(B)g.push(B),B="";continue}B+=_}if($||A)throw Error("Command substitution contains an unterminated escape or quote");if(B)g.push(B);return g}function yf(f){let[g,...B]=f,A=B.some(($)=>$.startsWith("-"));if(g==="echo"&&!A)return B.join(" ");if(g==="printf"&&B.length===1&&!B[0]?.includes("%"))return B[0];if(g==="hostname"&&B.length===0)return Nf();if(g==="whoami"&&B.length===0)return cf().username;if(g==="pwd"&&B.length===0)return process.cwd();if(g==="basename"&&B.length===1)return kf(B[0]??"");if(g==="dirname"&&B.length===1)return mf(B[0]??"");if(g==="cat"&&B.length>0&&!A)return B.map(($)=>hf(df(process.cwd(),$),"utf8")).join("");if(g==="uname"){let $=B[0]??"-s";if(B.length<=1&&$==="-m")return Kf();if(B.length<=1&&$==="-r")return Pf();if(B.length<=1&&$==="-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"}[Rf()]??Rf()}return}function vf(f){return f.replace(/\$\(([^)]+)\)/g,(g,B)=>{try{let A=tf(B),$=A[0];if(!$||!rf.has($))return console.warn(`[env] Blocked command substitution for disallowed command: ${$}`),"";let _=yf(A);if(_!==void 0)return _.trim();let X=Sf($,A.slice(1),{encoding:"utf8",env:process.env});if(X.status===0)return X.stdout.trim();let H=X.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${X.status??"unknown"}): ${$}${H?`: ${H}`:""}`)}catch(A){console.warn(`[env] Command substitution error: ${A instanceof Error?A.message:String(A)}`)}return""})}async function I2(f,g={}){let B={},A=[],$=[];for(let _ of f)try{let X=Bun.file(_);if(!X.size)continue;let H=await X.text(),{parsed:W,errors:D,skippedEncrypted:O}=M(H,g);A.push(...D),$.push(...O);for(let[R,G]of Object.entries(W)){if(!g.overload&&B[R]!==void 0)continue;B[R]=G}}catch(X){if(X?.code==="ENOENT")continue;A.push(`Failed to read ${_}: ${X instanceof Error?X.message:String(X)}`)}return{parsed:B,errors:A,skippedEncrypted:$}}import{existsSync as k}from"fs";import{readFileSync as o}from"fs";import{resolve as m}from"path";function h(f){return typeof f==="string"&&(f.startsWith("encrypted:")||f.startsWith("enc:"))}function F(f){if(!f||h(f))return;let g=f.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(g))return;if(g==="prod")return"production";if(g==="stage")return"staging";if(g==="dev")return"development";return g}var d=null,u,p;function nf(f,g,B=".env.keys"){let A=m(g,B);if(!k(A))return;try{let{parsed:$}=M(o(A,"utf-8"));if(f){let _=$[`DOTENV_PRIVATE_KEY_${f.toUpperCase()}`];if(_)return _}return $.DOTENV_PRIVATE_KEY}catch{return}}function l(f={}){return F(f.env)||p||F("development")||F(process.env.DOTENV_ENV)||F(process.env.APP_ENV)||"development"}function of(f={}){let g=l(f),B=f.cwd||process.cwd();if(d===g)return u;let A=n(g);if(!A)A=process.env.DOTENV_PRIVATE_KEY;if(!A)A=nf(g,B);return d=g,u=A,A}function V2(){d=null,u=void 0}function Tf(f,g={}){if(!h(f))return f;let B=of(g);if(!B)return;try{let A=f.startsWith("enc:")?`encrypted:${f.slice(4)}`:f;return b(A,B)}catch{return}}function Yf(f={}){let{path:g=[".env"],overload:B=!1,env:A,privateKey:$,keysFile:_=".env.keys",quiet:X=!1,cwd:H=process.cwd()}=f,W=Array.isArray(g)?g:[g],D=[],O=0,R=new Set(Object.entries(process.env).filter(([,T])=>T!==void 0&&!h(T)).map(([T])=>T)),G=$;if(!G&&_){let T=m(H,_);if(k(T))try{let Y=o(T,"utf-8"),{parsed:Z}=M(Y);if(A){let U=`DOTENV_PRIVATE_KEY_${A.toUpperCase()}`;G=Z[U]}else G=Z.DOTENV_PRIVATE_KEY}catch(Y){D.push(`Failed to load keys file: ${Y instanceof Error?Y.message:"Unknown error"}`)}}if(!G)G=n(A||"");for(let T of W){let Y=m(H,T);if(!k(Y)){if(!X)D.push(`File not found: ${Y}`);continue}try{let Z=o(Y,"utf-8"),{parsed:U,errors:Q,skippedEncrypted:I}=M(Z,{privateKey:G,processEnv:process.env});if(D.push(...Q),I.length>0){for(let N of I)if(h(process.env[N]))delete process.env[N];let w=A?`DOTENV_PRIVATE_KEY_${A.toUpperCase()}`:"DOTENV_PRIVATE_KEY",x=I.slice(0,5).join(", "),K=I.length>5?`, \u2026 +${I.length-5} more`:"";console.warn(`[env] warning: skipped ${I.length} encrypted value(s) in ${T} (${w} not set; defaults apply): ${x}${K}`)}let J=0;for(let[w,x]of Object.entries(U)){if(w==="DOTENV_PUBLIC_KEY")continue;let K=process.env[w],N=h(K)&&x!==K;if(B||!R.has(w)||N)process.env[w]=x,O++,J++}if(!X&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${J}/${Object.keys(U).length} variables from ${T}`),process.env.__ENV_LOADED__="1"}catch(Z){D.push(`Failed to load ${Y}: ${Z instanceof Error?Z.message:"Unknown error"}`)}}return{loaded:O,errors:D}}function pf(f={}){return{name:"env-plugin",setup(g){Yf(f)}}}function x2(f={}){let g=F(f.env)||F("development")||F(process.env.DOTENV_ENV)||F(process.env.APP_ENV)||"development",B=f.cwd||process.cwd();if(p!==g)p=g,d=null,u=void 0;let A=[],$=(H)=>{if(!A.includes(H)&&k(m(B,H)))A.push(H)};$(".env"),$(".env.local");let _=`.env.${g}`,X=`.env.${g}.local`;return $(_),$(X),Yf({...f,path:A,env:g})}var F2=pf;import{existsSync as q,readFileSync as E,renameSync as s,rmSync as jf,writeFileSync as z}from"fs";import{basename as a,dirname as Gf,resolve as L}from"path";function N2(f,g){if(f)return f;let B=String(g??"").trim();if(!B||B==="development"||B==="dev"||B==="local")return"";if(!/^[\w.-]+$/.test(B))return"";return`.env.${B}`}function Jf(f,g,B,A){let $=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${B}="${g}"`,""];for(let _ of f.split(`
5
- `)){let X=_.trim();if(!X||X.startsWith("#")){$.push(_);continue}if(X.startsWith("DOTENV_PUBLIC_KEY"))continue;let H=X.match(/^([^=]+)=(.*)$/);if(!H||H[1]===void 0||H[2]===void 0){$.push(_);continue}let W=H[1].trim(),D=H[2].trim();if(D.startsWith('"')&&D.endsWith('"')||D.startsWith("'")&&D.endsWith("'"))D=D.slice(1,-1);if((!A.key||W.includes(A.key))&&(!A.excludeKey||!W.includes(A.excludeKey))&&!D.startsWith("encrypted:"))D=c(D,g);$.push(`${W}="${D}"`)}return $.join(`
6
- `)}function P2(f={}){let g=f.cwd||process.cwd(),B=L(g,f.file||".env"),A=L(g,f.keysFile||".env.keys");if(!q(B))return{success:!1,error:`File not found: ${B}`};try{let $,_;if(q(A)){let O=E(A,"utf-8"),{parsed:R}=M(O),Y=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),Z=Y?`DOTENV_PUBLIC_KEY_${Y}`:"DOTENV_PUBLIC_KEY",U=Y?`DOTENV_PRIVATE_KEY_${Y}`:"DOTENV_PRIVATE_KEY";if($=R[Z]||"",_=R[U]||"",!$||!_){let Q=S();$=Q.publicKey,_=Q.privateKey;let I=`
7
- ${Z}="${$}"
8
- ${U}="${_}"
9
- `;z(A,O+I,"utf-8")}}else{let O=S();$=O.publicKey,_=O.privateKey;let T=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),Y=T?`DOTENV_PUBLIC_KEY_${T}`:"DOTENV_PUBLIC_KEY",Z=T?`DOTENV_PRIVATE_KEY_${T}`:"DOTENV_PRIVATE_KEY",U=`# .env.keys - Keep this file secure and never commit to source control
10
- ${Y}="${$}"
11
- ${Z}="${_}"
12
- `;z(A,U,"utf-8")}let X=E(B,"utf-8"),H=f.file?f.file.replace(/^\.env\./,"").toUpperCase():"",W=H?`DOTENV_PUBLIC_KEY_${H}`:"DOTENV_PUBLIC_KEY",D=Jf(X,$,W,f);if(f.stdout)return{success:!0,output:D};return z(B,D,"utf-8"),{success:!0,output:`\u2714 encrypted (${f.file||".env"})
13
- \u2714 key added to ${f.keysFile||".env.keys"}`}}catch($){return{success:!1,error:`Failed to encrypt: ${$ instanceof Error?$.message:"Unknown error"}`}}}function lf(f={}){let g=f.cwd||process.cwd(),B=L(g,f.file||".env"),A=L(g,f.keysFile||".env.keys");if(!q(B))return{success:!1,error:`File not found: ${B}`};if(!q(A))return{success:!1,error:`Keys file not found: ${A}`};try{let $=E(A,"utf-8"),{parsed:_}=M($),W=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),D=W?`DOTENV_PRIVATE_KEY_${W}`:"DOTENV_PRIVATE_KEY",O=_[D];if(!O)return{success:!1,error:`Private key not found: ${D}`};let G=E(B,"utf-8").split(`
14
- `),T=[];for(let Z of G){let U=Z.trim();if(!U||U.startsWith("#")){T.push(Z);continue}if(U.startsWith("DOTENV_PUBLIC_KEY"))continue;let Q=U.match(/^([^=]+)=(.*)$/);if(!Q||Q[1]===void 0||Q[2]===void 0){T.push(Z);continue}let I=Q[1].trim(),J=Q[2].trim();if(J.startsWith('"')&&J.endsWith('"')||J.startsWith("'")&&J.endsWith("'"))J=J.slice(1,-1);let w=J.startsWith("encrypted:");if(f.key&&!I.includes(f.key))w=!1;if(w)J=b(J,O);T.push(`${I}="${J}"`)}let Y=T.join(`
15
- `);if(f.stdout)return{success:!0,output:Y};return z(B,Y,"utf-8"),{success:!0,output:`\u2714 decrypted (${f.file||".env"})`}}catch($){return{success:!1,error:`Failed to decrypt: ${$ instanceof Error?$.message:"Unknown error"}`}}}function c2(f,g,B={}){let A=B.cwd||process.cwd(),$=L(A,B.file||".env"),_=L(A,B.keysFile||".env.keys");try{let X="";if(q($))X=E($,"utf-8");let H=X.split(`
16
- `),W=!1,D,G=((B.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),T=G?`DOTENV_PUBLIC_KEY_${G}`:"DOTENV_PUBLIC_KEY",Y=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY";for(let Q of H){let I=Q.trim();if(I.startsWith(`${T}=`)){let J=I.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(J)D=J[1];break}}if(D){let Q=!1;if(q(_)){let I=E(_,"utf-8"),{parsed:J}=M(I);Q=Boolean(J[Y])}if(!Q)D=void 0}if(!B.plain&&!D){let Q=S();D=Q.publicKey;let I="";if(q(_))I=E(_,"utf-8");I+=`
17
- ${T}="${Q.publicKey}"
18
- ${Y}="${Q.privateKey}"
19
- `,z(_,I,"utf-8");for(let J=H.length-1;J>=0;J--){let w=H[J];if(w!==void 0&&w.trim().startsWith(`${T}=`))H.splice(J,1)}H.unshift(`${T}="${D}"`)}let Z=g;if(!B.plain&&D)Z=c(g,D);for(let Q=0;Q<H.length;Q++){let I=H[Q];if(I===void 0)continue;if(I.trim().startsWith(`${f}=`)){H[Q]=`${f}="${Z}"`,W=!0;break}}if(!W)H.push(`${f}="${Z}"`);let U=H.join(`
20
- `);return z($,U,"utf-8"),{success:!0,output:`set ${f}${B.plain?"":" with encryption"} (${B.file||".env"})`}}catch(X){return{success:!1,error:`Failed to set: ${X instanceof Error?X.message:"Unknown error"}`}}}function k2(f,g={}){let B=g.cwd||process.cwd(),A=L(B,g.file||".env");if(!q(A))return{success:!1,error:`File not found: ${A}`};try{let $,_=L(B,g.keysFile||".env.keys"),W=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),D=W?`DOTENV_PRIVATE_KEY_${W}`:"DOTENV_PRIVATE_KEY";if(q(_)){let Y=E(_,"utf-8"),{parsed:Z}=M(Y);$=Z[D]}if(!$)$=process.env[D];let O=E(A,"utf-8"),{parsed:R}=M(O,{privateKey:$});if(f&&!g.all){let Y=R[f];if(Y===void 0)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:Y}}let G=g.all?{...process.env,...R}:R,T;switch(g.format){case"shell":T=Object.entries(G).map(([Y,Z])=>`${Y}=${Z}`).join(" ");break;case"eval":T=Object.entries(G).map(([Y,Z])=>`${Y}="${Z}"`).join(`
21
- `);break;case"json":default:T=g.prettyPrint?JSON.stringify(G,null,2):JSON.stringify(G);break}return{success:!0,output:T}}catch($){return{success:!1,error:`Failed to get: ${$ instanceof Error?$.message:"Unknown error"}`}}}function m2(f,g={}){let B=g.cwd||process.cwd(),A=L(B,g.keysFile||".env.keys");if(!q(A))return{success:!1,error:`Keys file not found: ${A}`};try{let $=E(A,"utf-8"),{parsed:_}=M($),W=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),D=W?`DOTENV_PUBLIC_KEY_${W}`:"DOTENV_PUBLIC_KEY",O=W?`DOTENV_PRIVATE_KEY_${W}`:"DOTENV_PRIVATE_KEY";if(f){let T=_[f];if(!T)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:T}}let R={[D]:_[D],[O]:_[O]};return{success:!0,output:g.format==="shell"?`${D}=${R[D]} ${O}=${R[O]}`:JSON.stringify(R)}}catch($){return{success:!1,error:`Failed to get keypair: ${$ instanceof Error?$.message:"Unknown error"}`}}}function d2(f={}){let g=lf({file:f.file,keysFile:f.keysFile,stdout:!0,cwd:f.cwd});if(!g.success)return g;let B=f.cwd||process.cwd(),A=L(B,f.file||".env"),$=L(B,f.keysFile||".env.keys");if(g.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let _=S(),X=E(A),H=E($),W=H.toString("utf8").split(`
22
- `),D=f.file||".env",O=a(D).replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),R=O?`DOTENV_PUBLIC_KEY_${O}`:"DOTENV_PUBLIC_KEY",G=O?`DOTENV_PRIVATE_KEY_${O}`:"DOTENV_PRIVATE_KEY",T=!1,Y=!1;for(let w=0;w<W.length;w++){let x=W[w];if(x===void 0)continue;if(x.startsWith(`${R}=`))W[w]=`${R}="${_.publicKey}"`,T=!0;else if(x.startsWith(`${G}=`))W[w]=`${G}="${_.privateKey}"`,Y=!0}if(!T)W.push(`${R}="${_.publicKey}"`);if(!Y)W.push(`${G}="${_.privateKey}"`);let Z=`${W.join(`
2
+ import{createCipheriv as gf,createDecipheriv as Bf,createHash as t,createPrivateKey as zf,createPublicKey as $f,diffieHellman as Xf,generateKeyPairSync as Df,hkdfSync as Hf,randomBytes as y}from"crypto";var v="x25519-public:",i="x25519-private:",P="encrypted:v2:",n="encrypted:",_f=Buffer.from("stacks-env:v2","utf8"),Af="Environment decryption failed: authentication or format error";function Wf(f){if(f.length===32)return f;return t("sha256").update(f).digest()}function J2(f,g){let B=y(16),X=gf("aes-256-gcm",Wf(g),B);return{ciphertext:Buffer.concat([X.update(f,"utf8"),X.final()]).toString("hex"),iv:B.toString("hex"),authTag:X.getAuthTag().toString("hex")}}function Vf(f,g,B,X){try{let $=Bf("aes-256-gcm",Wf(g),Buffer.from(B,"hex"));return $.setAuthTag(Buffer.from(X,"hex")),Buffer.concat([$.update(Buffer.from(f,"hex")),$.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function x(f){return f.toString("base64url")}function C(f,g,B){if(typeof f!=="string"||f.length>0&&!/^[A-Za-z0-9_-]+$/.test(f))throw Error(`${g} is not canonical base64url`);let X=Buffer.from(f,"base64url");if(x(X)!==f)throw Error(`${g} is not canonical base64url`);if(B!==void 0&&X.length!==B)throw Error(`${g} has an invalid length`);return X}function xf(f){if(!f.startsWith(v))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let g=$f({key:C(f.slice(v.length),"public key"),format:"der",type:"spki"});if(g.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return g}function Ef(f){if(!f.startsWith(i))throw Error("Private key is not a version 2 X25519 key");let g=zf({key:C(f.slice(i.length),"private key"),format:"der",type:"pkcs8"});if(g.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return g}function Yf(f){return Buffer.from(`stacks-env:v2;${f.epk};${f.salt};${f.nonce}`,"utf8")}function S(){let{publicKey:f,privateKey:g}=Df("x25519"),B=f.export({format:"der",type:"spki"}),X=g.export({format:"der",type:"pkcs8"});return{publicKey:`${v}${x(B)}`,privateKey:`${i}${x(X)}`}}function c(f,g){let B=xf(g),X=Df("x25519"),$=x(X.publicKey.export({format:"der",type:"spki"})),D=y(16),H=y(12),A=Xf({privateKey:X.privateKey,publicKey:B}),W=Buffer.from(Hf("sha256",A,D,_f,32)),_={epk:$,salt:x(D),nonce:x(H)};try{let Y=gf("aes-256-gcm",W,H);Y.setAAD(Yf(_));let j=Buffer.concat([Y.update(f,"utf8"),Y.final()]),Z={v:2,..._,ciphertext:x(j),tag:x(Y.getAuthTag())};return`${P}${x(Buffer.from(JSON.stringify(Z),"utf8"))}`}finally{A.fill(0),W.fill(0)}}function Cf(f){let g=C(f.slice(P.length),"envelope").toString("utf8"),B=JSON.parse(g);if(!B||typeof B!=="object"||Array.isArray(B))throw Error("envelope is not an object");let X=Object.keys(B).sort(),$=["ciphertext","epk","nonce","salt","tag","v"];if(X.length!==$.length||X.some((D,H)=>D!==$[H]))throw Error("envelope fields are invalid");if(B.v!==2)throw Error("envelope version is unsupported");for(let D of["epk","salt","nonce","ciphertext","tag"])if(typeof B[D]!=="string")throw Error(`${D} is invalid`);return B}function Ff(f,g){try{let B=Cf(f),X=Ef(g),$=$f({key:C(B.epk,"ephemeral public key"),format:"der",type:"spki"});if($.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let D=C(B.salt,"salt",16),H=C(B.nonce,"nonce",12),A=C(B.ciphertext,"ciphertext"),W=C(B.tag,"tag",16),_=Xf({privateKey:X,publicKey:$}),Y=Buffer.from(Hf("sha256",_,D,_f,32));try{let j=Bf("aes-256-gcm",Y,H);return j.setAAD(Yf(B)),j.setAuthTag(W),Buffer.concat([j.update(A),j.final()]).toString("utf8")}finally{_.fill(0),Y.fill(0)}}catch{throw Error(Af)}}function bf(f,g){try{if(!/^[0-9a-f]{64}$/.test(g))throw Error("invalid legacy private key");let B=Buffer.from(f.slice(n.length),"base64");if(B.length<32)throw Error("invalid legacy payload");let X=t("sha256").update(Buffer.from(g,"hex")).digest(),$=t("sha256").update(X).digest();return Vf(B.subarray(32).toString("hex"),$,B.subarray(0,16).toString("hex"),B.subarray(16,32).toString("hex"))}catch{throw Error(Af)}}function O2(f){return f.startsWith(n)&&!f.startsWith(P)}function b(f,g){if(!f.startsWith(n))return f;return f.startsWith(P)?Ff(f,g):bf(f,g)}function Z2(f,g,B){return c(b(f,g),B)}function Q2(f){let g=f.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return g&&g[1]?g[1].toLowerCase():""}function o(f=""){return f?process.env[`DOTENV_PRIVATE_KEY_${f.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as Sf}from"child_process";import{readFileSync as hf}from"fs";import{arch as Kf,hostname as Nf,platform as jf,release as Pf,userInfo as cf}from"os";import{basename as kf,dirname as mf,resolve as df}from"path";function M(f,g={}){let B={},X=[],$=[],D=f.split(`
3
+ `);for(let H of D){let A=H.trim();if(!A||A.startsWith("#"))continue;if(A.startsWith("DOTENV_PUBLIC_KEY=")){let j=A.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(j&&j[1]!==void 0)B.DOTENV_PUBLIC_KEY=j[1];continue}let W=A.match(/^([^=]+)=(.*)$/);if(!W||W[1]===void 0||W[2]===void 0)continue;let _=W[1].trim(),Y=W[2].trim();if(Y.startsWith('"')&&Y.endsWith('"')||Y.startsWith("'")&&Y.endsWith("'")){if(Y=Y.slice(1,-1),Y.includes("\\n"))Y=Y.replace(/\\n/g,`
4
+ `)}if(Y.startsWith("encrypted:")||Y.startsWith("enc:")){if(!g.privateKey){$.push(_);continue}try{let j=Y.startsWith("enc:")?`encrypted:${Y.slice(4)}`:Y;Y=b(j,g.privateKey)}catch(j){X.push(`Failed to decrypt ${_}: ${j instanceof Error?j.message:"Unknown error"}`)}}Y=uf(Y,{...g.processEnv||process.env,...B}),Y=vf(Y),B[_]=Y}return{parsed:B,errors:X,skippedEncrypted:$}}function uf(f,g){return f.replace(/\$\{([^}]+)\}/g,(B,X)=>{let $=X.match(/^([^:\-+]+)(:-|-)(.+)$/);if($){let[,H,A,W]=$,_=g[H];if(A===":-")return _||W;else return _!==void 0?_:W}let D=X.match(/^([^:\-+]+)(:?\+)(.+)$/);if(D){let[,H,A,W]=D,_=g[H];if(A===":+")return _?W:"";else return _!==void 0?W:""}return g[X]||""})}var rf=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function tf(f){let g=[],B="",X=null,$=!1;for(let D of f.trim()){if($){B+=D,$=!1;continue}if(D==="\\"&&X!=="'"){$=!0;continue}if(X){if(D===X)X=null;else B+=D;continue}if(D==='"'||D==="'"){X=D;continue}if(/\s/.test(D)){if(B)g.push(B),B="";continue}B+=D}if($||X)throw Error("Command substitution contains an unterminated escape or quote");if(B)g.push(B);return g}function yf(f){let[g,...B]=f,X=B.some(($)=>$.startsWith("-"));if(g==="echo"&&!X)return B.join(" ");if(g==="printf"&&B.length===1&&!B[0]?.includes("%"))return B[0];if(g==="hostname"&&B.length===0)return Nf();if(g==="whoami"&&B.length===0)return cf().username;if(g==="pwd"&&B.length===0)return process.cwd();if(g==="basename"&&B.length===1)return kf(B[0]??"");if(g==="dirname"&&B.length===1)return mf(B[0]??"");if(g==="cat"&&B.length>0&&!X)return B.map(($)=>hf(df(process.cwd(),$),"utf8")).join("");if(g==="uname"){let $=B[0]??"-s";if(B.length<=1&&$==="-m")return Kf();if(B.length<=1&&$==="-r")return Pf();if(B.length<=1&&$==="-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"}[jf()]??jf()}return}function vf(f){return f.replace(/\$\(([^)]+)\)/g,(g,B)=>{try{let X=tf(B),$=X[0];if(!$||!rf.has($))return console.warn(`[env] Blocked command substitution for disallowed command: ${$}`),"";let D=yf(X);if(D!==void 0)return D.trim();let H=Sf($,X.slice(1),{encoding:"utf8",env:process.env});if(H.status===0)return H.stdout.trim();let A=H.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${H.status??"unknown"}): ${$}${A?`: ${A}`:""}`)}catch(X){console.warn(`[env] Command substitution error: ${X instanceof Error?X.message:String(X)}`)}return""})}async function M2(f,g={}){let B={},X=[],$=[];for(let D of f)try{let H=Bun.file(D);if(!H.size)continue;let A=await H.text(),{parsed:W,errors:_,skippedEncrypted:Y}=M(A,g);X.push(..._),$.push(...Y);for(let[j,Z]of Object.entries(W)){if(!g.overload&&B[j]!==void 0)continue;B[j]=Z}}catch(H){if(H?.code==="ENOENT")continue;X.push(`Failed to read ${D}: ${H instanceof Error?H.message:String(H)}`)}return{parsed:B,errors:X,skippedEncrypted:$}}import{existsSync as k}from"fs";import{readFileSync as p}from"fs";import{resolve as m}from"path";function h(f){return typeof f==="string"&&(f.startsWith("encrypted:")||f.startsWith("enc:"))}function F(f){if(!f||h(f))return;let g=f.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(g))return;if(g==="prod")return"production";if(g==="stage")return"staging";if(g==="dev")return"development";return g}var d=null,u,r;function nf(f,g,B=".env.keys"){let X=m(g,B);if(!k(X))return;try{let{parsed:$}=M(p(X,"utf-8"));if(f){let D=$[`DOTENV_PRIVATE_KEY_${f.toUpperCase()}`];if(D)return D}return $.DOTENV_PRIVATE_KEY}catch{return}}function l(f={}){return F(f.env)||r||F("development")||F(process.env.DOTENV_ENV)||F(process.env.APP_ENV)||"development"}function of(f={}){let g=l(f),B=f.cwd||process.cwd();if(d===g)return u;let X=o(g);if(!X)X=process.env.DOTENV_PRIVATE_KEY;if(!X)X=nf(g,B);return d=g,u=X,X}function b2(){d=null,u=void 0,r=void 0}function Gf(f,g={}){if(!h(f))return f;let B=of(g);if(!B)return;try{let X=f.startsWith("enc:")?`encrypted:${f.slice(4)}`:f;return b(X,B)}catch{return}}function Jf(f={}){let{path:g=[".env"],overload:B=!1,env:X,privateKey:$,keysFile:D=".env.keys",quiet:H=!1,cwd:A=process.cwd()}=f,W=Array.isArray(g)?g:[g],_=[],Y=0,j=new Set(Object.entries(process.env).filter(([,G])=>G!==void 0&&!h(G)).map(([G])=>G)),Z=$;if(!Z&&D){let G=m(A,D);if(k(G))try{let J=p(G,"utf-8"),{parsed:R}=M(J);if(X){let T=`DOTENV_PRIVATE_KEY_${X.toUpperCase()}`;Z=R[T]}else Z=R.DOTENV_PRIVATE_KEY}catch(J){_.push(`Failed to load keys file: ${J instanceof Error?J.message:"Unknown error"}`)}}if(!Z)Z=o(X||"");for(let G of W){let J=m(A,G);if(!k(J)){if(!H)_.push(`File not found: ${J}`);continue}try{let R=p(J,"utf-8"),{parsed:T,errors:I,skippedEncrypted:w}=M(R,{privateKey:Z,processEnv:process.env});if(_.push(...I),w.length>0){for(let N of w)if(h(process.env[N]))delete process.env[N];let U=X?`DOTENV_PRIVATE_KEY_${X.toUpperCase()}`:"DOTENV_PRIVATE_KEY",E=w.slice(0,5).join(", "),K=w.length>5?`, \u2026 +${w.length-5} more`:"";console.warn(`[env] warning: skipped ${w.length} encrypted value(s) in ${G} (${U} not set; defaults apply): ${E}${K}`)}let Q=0;for(let[U,E]of Object.entries(T)){if(U==="DOTENV_PUBLIC_KEY")continue;let K=process.env[U],N=h(K)&&E!==K;if(B||!j.has(U)||N)process.env[U]=E,Y++,Q++}if(!H&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${Q}/${Object.keys(T).length} variables from ${G}`),process.env.__ENV_LOADED__="1"}catch(R){_.push(`Failed to load ${J}: ${R instanceof Error?R.message:"Unknown error"}`)}}return{loaded:Y,errors:_}}function pf(f={}){return{name:"env-plugin",setup(g){Jf(f)}}}function S2(f={}){let g=F(f.env)||F("development")||F(process.env.DOTENV_ENV)||F(process.env.APP_ENV)||"development",B=f.cwd||process.cwd();if(r!==g)r=g,d=null,u=void 0;let X=[],$=(A)=>{if(!X.includes(A)&&k(m(B,A)))X.push(A)};$(".env"),$(".env.local");let D=`.env.${g}`,H=`.env.${g}.local`;return $(D),$(H),Jf({...f,path:X,env:g})}var K2=pf;import{existsSync as z,readFileSync as q,renameSync as s,rmSync as Of,writeFileSync as V}from"fs";import{basename as a,dirname as Zf,resolve as L}from"path";function m2(f,g){if(f)return f;let B=String(g??"").trim();if(!B||B==="development"||B==="dev"||B==="local")return"";if(!/^[\w.-]+$/.test(B))return"";return`.env.${B}`}function Qf(f,g,B,X){let $=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${B}="${g}"`,""];for(let D of f.split(`
5
+ `)){let H=D.trim();if(!H||H.startsWith("#")){$.push(D);continue}if(H.startsWith("DOTENV_PUBLIC_KEY"))continue;let A=H.match(/^([^=]+)=(.*)$/);if(!A||A[1]===void 0||A[2]===void 0){$.push(D);continue}let W=A[1].trim(),_=A[2].trim();if(_.startsWith('"')&&_.endsWith('"')||_.startsWith("'")&&_.endsWith("'"))_=_.slice(1,-1);if((!X.key||W.includes(X.key))&&(!X.excludeKey||!W.includes(X.excludeKey))&&!_.startsWith("encrypted:"))_=c(_,g);$.push(`${W}="${_}"`)}return $.join(`
6
+ `)}function d2(f={}){let g=f.cwd||process.cwd(),B=L(g,f.file||".env"),X=L(g,f.keysFile||".env.keys");if(!z(B))return{success:!1,error:`File not found: ${B}`};try{let $,D;if(z(X)){let Y=q(X,"utf-8"),{parsed:j}=M(Y),J=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),R=J?`DOTENV_PUBLIC_KEY_${J}`:"DOTENV_PUBLIC_KEY",T=J?`DOTENV_PRIVATE_KEY_${J}`:"DOTENV_PRIVATE_KEY";if($=j[R]||"",D=j[T]||"",!$||!D){let I=S();$=I.publicKey,D=I.privateKey;let w=`
7
+ ${R}="${$}"
8
+ ${T}="${D}"
9
+ `;V(X,Y+w,"utf-8")}}else{let Y=S();$=Y.publicKey,D=Y.privateKey;let G=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),J=G?`DOTENV_PUBLIC_KEY_${G}`:"DOTENV_PUBLIC_KEY",R=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY",T=`# .env.keys - Keep this file secure and never commit to source control
10
+ ${J}="${$}"
11
+ ${R}="${D}"
12
+ `;V(X,T,"utf-8")}let H=q(B,"utf-8"),A=f.file?f.file.replace(/^\.env\./,"").toUpperCase():"",W=A?`DOTENV_PUBLIC_KEY_${A}`:"DOTENV_PUBLIC_KEY",_=Qf(H,$,W,f);if(f.stdout)return{success:!0,output:_};return V(B,_,"utf-8"),{success:!0,output:`\u2714 encrypted (${f.file||".env"})
13
+ \u2714 key added to ${f.keysFile||".env.keys"}`}}catch($){return{success:!1,error:`Failed to encrypt: ${$ instanceof Error?$.message:"Unknown error"}`}}}function lf(f={}){let g=f.cwd||process.cwd(),B=L(g,f.file||".env"),X=L(g,f.keysFile||".env.keys");if(!z(B))return{success:!1,error:`File not found: ${B}`};if(!z(X))return{success:!1,error:`Keys file not found: ${X}`};try{let $=q(X,"utf-8"),{parsed:D}=M($),W=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),_=W?`DOTENV_PRIVATE_KEY_${W}`:"DOTENV_PRIVATE_KEY",Y=D[_];if(!Y)return{success:!1,error:`Private key not found: ${_}`};let Z=q(B,"utf-8").split(`
14
+ `),G=[];for(let R of Z){let T=R.trim();if(!T||T.startsWith("#")){G.push(R);continue}if(T.startsWith("DOTENV_PUBLIC_KEY"))continue;let I=T.match(/^([^=]+)=(.*)$/);if(!I||I[1]===void 0||I[2]===void 0){G.push(R);continue}let w=I[1].trim(),Q=I[2].trim();if(Q.startsWith('"')&&Q.endsWith('"')||Q.startsWith("'")&&Q.endsWith("'"))Q=Q.slice(1,-1);let U=Q.startsWith("encrypted:");if(f.key&&!w.includes(f.key))U=!1;if(U)Q=b(Q,Y);G.push(`${w}="${Q}"`)}let J=G.join(`
15
+ `);if(f.stdout)return{success:!0,output:J};return V(B,J,"utf-8"),{success:!0,output:`\u2714 decrypted (${f.file||".env"})`}}catch($){return{success:!1,error:`Failed to decrypt: ${$ instanceof Error?$.message:"Unknown error"}`}}}function u2(f,g,B={}){let X=B.cwd||process.cwd(),$=L(X,B.file||".env"),D=L(X,B.keysFile||".env.keys");try{let H="";if(z($))H=q($,"utf-8");let A=H.split(`
16
+ `),W=!1,_,Z=((B.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),G=Z?`DOTENV_PUBLIC_KEY_${Z}`:"DOTENV_PUBLIC_KEY",J=Z?`DOTENV_PRIVATE_KEY_${Z}`:"DOTENV_PRIVATE_KEY";for(let I of A){let w=I.trim();if(w.startsWith(`${G}=`)){let Q=w.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(Q)_=Q[1];break}}if(_){let I=!1;if(z(D)){let w=q(D,"utf-8"),{parsed:Q}=M(w);I=Boolean(Q[J])}if(!I)_=void 0}if(!B.plain&&!_){let I=S();_=I.publicKey;let w="";if(z(D))w=q(D,"utf-8");w+=`
17
+ ${G}="${I.publicKey}"
18
+ ${J}="${I.privateKey}"
19
+ `,V(D,w,"utf-8");for(let Q=A.length-1;Q>=0;Q--){let U=A[Q];if(U!==void 0&&U.trim().startsWith(`${G}=`))A.splice(Q,1)}A.unshift(`${G}="${_}"`)}let R=g;if(!B.plain&&_)R=c(g,_);for(let I=0;I<A.length;I++){let w=A[I];if(w===void 0)continue;if(w.trim().startsWith(`${f}=`)){A[I]=`${f}="${R}"`,W=!0;break}}if(!W)A.push(`${f}="${R}"`);let T=A.join(`
20
+ `);return V($,T,"utf-8"),{success:!0,output:`set ${f}${B.plain?"":" with encryption"} (${B.file||".env"})`}}catch(H){return{success:!1,error:`Failed to set: ${H instanceof Error?H.message:"Unknown error"}`}}}function r2(f,g={}){let B=g.cwd||process.cwd(),X=L(B,g.file||".env");if(!z(X))return{success:!1,error:`File not found: ${X}`};try{let $,D=L(B,g.keysFile||".env.keys"),W=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),_=W?`DOTENV_PRIVATE_KEY_${W}`:"DOTENV_PRIVATE_KEY";if(z(D)){let J=q(D,"utf-8"),{parsed:R}=M(J);$=R[_]}if(!$)$=process.env[_];let Y=q(X,"utf-8"),{parsed:j}=M(Y,{privateKey:$});if(f&&!g.all){let J=j[f];if(J===void 0)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:J}}let Z=g.all?{...process.env,...j}:j,G;switch(g.format){case"shell":G=Object.entries(Z).map(([J,R])=>`${J}=${R}`).join(" ");break;case"eval":G=Object.entries(Z).map(([J,R])=>`${J}="${R}"`).join(`
21
+ `);break;case"json":default:G=g.prettyPrint?JSON.stringify(Z,null,2):JSON.stringify(Z);break}return{success:!0,output:G}}catch($){return{success:!1,error:`Failed to get: ${$ instanceof Error?$.message:"Unknown error"}`}}}function t2(f,g={}){let B=g.cwd||process.cwd(),X=L(B,g.keysFile||".env.keys");if(!z(X))return{success:!1,error:`Keys file not found: ${X}`};try{let $=q(X,"utf-8"),{parsed:D}=M($),W=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),_=W?`DOTENV_PUBLIC_KEY_${W}`:"DOTENV_PUBLIC_KEY",Y=W?`DOTENV_PRIVATE_KEY_${W}`:"DOTENV_PRIVATE_KEY";if(f){let G=D[f];if(!G)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:G}}let j={[_]:D[_],[Y]:D[Y]};return{success:!0,output:g.format==="shell"?`${_}=${j[_]} ${Y}=${j[Y]}`:JSON.stringify(j)}}catch($){return{success:!1,error:`Failed to get keypair: ${$ instanceof Error?$.message:"Unknown error"}`}}}function y2(f={}){let g=lf({file:f.file,keysFile:f.keysFile,stdout:!0,cwd:f.cwd});if(!g.success)return g;let B=f.cwd||process.cwd(),X=L(B,f.file||".env"),$=L(B,f.keysFile||".env.keys");if(g.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let D=S(),H=q(X),A=q($),W=A.toString("utf8").split(`
22
+ `),_=f.file||".env",Y=a(_).replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),j=Y?`DOTENV_PUBLIC_KEY_${Y}`:"DOTENV_PUBLIC_KEY",Z=Y?`DOTENV_PRIVATE_KEY_${Y}`:"DOTENV_PRIVATE_KEY",G=!1,J=!1;for(let U=0;U<W.length;U++){let E=W[U];if(E===void 0)continue;if(E.startsWith(`${j}=`))W[U]=`${j}="${D.publicKey}"`,G=!0;else if(E.startsWith(`${Z}=`))W[U]=`${Z}="${D.privateKey}"`,J=!0}if(!G)W.push(`${j}="${D.publicKey}"`);if(!J)W.push(`${Z}="${D.privateKey}"`);let R=`${W.join(`
23
23
  `).replace(/\n+$/,"")}
24
- `,U=Jf(g.output,_.publicKey,R,f),Q=`${process.pid}-${Date.now()}`,I=L(Gf(A),`.${a(A)}.${Q}.rotate`),J=L(Gf($),`.${a($)}.${Q}.rotate`);try{if(z(I,U,{encoding:"utf8",mode:384}),z(J,Z,{encoding:"utf8",mode:384}),f.stdout)return s(J,$),{success:!0,output:U};s(I,A);try{s(J,$)}catch(w){throw z(A,X),w}return{success:!0,output:`\u2714 rotated (${f.file||".env"}) to encrypted:v2`}}catch(w){if(!q($))z($,H,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${w instanceof Error?w.message:"Unknown error"}`}}finally{jf(I,{force:!0}),jf(J,{force:!0})}}import j from"process";import{platform as sf}from"os";var Zf=typeof Bun<"u",Qf=typeof j<"u"&&j.versions?.node!==void 0,af=Zf?"bun":Qf?"node":"unknown",y2={name:af,version:Zf?Bun.version:Qf?j.version:void 0},ff=sf(),v2=ff==="win32",i2=ff==="darwin",n2=ff==="linux",If=Boolean(j.stdout?.isTTY),o2=typeof globalThis.window<"u",ef=Boolean(j.env.CI||j.env.CONTINUOUS_INTEGRATION||j.env.BUILD_NUMBER||j.env.RUN_ID),p2=Boolean(j.env.DEBUG||j.env.VERBOSE||j.argv.includes("--debug")||j.argv.includes("--verbose")),f2=ef||!If,l2=Boolean(!f2&&(If||j.env.COLORTERM||j.env.FORCE_COLOR||j.env.TERM&&j.env.TERM!=="dumb")),e={github:{name:"GitHub Actions",detected:Boolean(j.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(j.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(j.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(j.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(j.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(j.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(j.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(j.env.DYNO)},aws:{name:"AWS",detected:Boolean(j.env.AWS_REGION||j.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(j.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(j.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(j.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(j.env.RENDER)}},g2=Object.keys(e).find((f)=>{let g=e[f];return g!==void 0&&g.detected})||"unknown",s2=e[g2]||{name:"Unknown",detected:!1};import $2 from"process";import{projectPath as A2}from"@stacksjs/path";import Lf from"fs";var wf={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 Uf(f){return`${f.replace(/[^a-z0-9]/gi,"").toUpperCase()}_`}function B2(f,g={}){let B=g.self?Uf(g.self):void 0,A=(g.tenants??[]).filter((X)=>typeof X==="string"&&X.trim().length>0).map((X)=>({slug:X,prefix:Uf(X)})).filter(({prefix:X})=>X!==B).sort((X,H)=>H.prefix.length-X.prefix.length),$={},_={};for(let[X,H]of Object.entries(f)){let W=A.find(({prefix:D})=>X.startsWith(D));if(W){(_[W.slug]??={})[X]=H;continue}$[X]=H}return{own:$,foreign:_}}function f5(f,g={}){return B2(f,g).own}function g5(f){return Object.entries(f.foreign).map(([g,B])=>({tenant:g,keys:Object.keys(B).sort()})).sort((g,B)=>g.tenant.localeCompare(B.tenant))}var Mf=new Set;function _2(f){if(Mf.has(f))return;Mf.add(f);let g=l();console.warn(`[env] warning: "${f}" is encrypted but no usable private key was found for the "${g}" environment; falling back to its default. Set DOTENV_PRIVATE_KEY_${g.toUpperCase()} or ship .env.keys.`)}var X2={get:(f,g)=>{let B=f[g];if(typeof B==="string"&&(B.startsWith("encrypted:")||B.startsWith("enc:"))){let $=Tf(B);if($===void 0){_2(g),delete f[g];return}f[g]=$,B=$}let A=["_PORT","_TIMEOUT","_TTL","_SIZE","_LIMIT","_MAX","_MIN","_INTERVAL","_RETRIES","_CONCURRENCY","_WORKERS","_CONNECTIONS"];if(typeof B==="string"&&/^\d+$/.test(B)&&!B.startsWith("0")&&A.some(($)=>g.endsWith($)))return Number(B);if(typeof B==="string"){let $=B.toLowerCase();if($==="true")return!0;if($==="false")return!1}return B}};function D2(){return typeof Bun<"u"?Bun.env:$2.env}var Ef=new Proxy(D2(),X2);function H5(f,g,B){let A=B?.path||A2(".env"),_=Lf.readFileSync(A,"utf-8").split(`
25
- `),X=_.findIndex((D)=>D.startsWith(`${f}=`)),W=/[\s"'#$\\]/.test(g)?`"${g.replace(/"/g,"\\\"")}"`:g;if(X!==-1)_[X]=`${f}=${W}`;else _.push(`${f}=${W}`);Lf.writeFileSync(A,_.join(`
26
- `))}function O5(f=Ef){let g=[];for(let[B,A]of Object.entries(wf)){let $=f[B];if($!==void 0&&$!==""&&!A.includes(String($)))g.push(`${B}="${$}" is not valid. Allowed values: ${A.join(", ")}`)}return g}function W5(f,g=Ef){let B=[];for(let A of f){let $=g[A];if($===void 0||$===""||$===null)B.push(A)}if(B.length>0)throw Error(`[env] Missing required environment variable(s): ${B.join(", ")}. Set them in .env or your process environment before booting.`);return g}export{H5 as writeEnv,O5 as validateEnv,Uf as tenantEnvPrefix,f5 as stripForeignTenantEnv,c2 as setEnv,y2 as runtimeInfo,af as runtime,d2 as rotateKeypair,of as resolvePrivateKey,N2 as resolveEnvFile,V2 as resetPrivateKeyCache,W5 as requireEnv,s2 as providerInfo,g2 as provider,D2 as process,ff as platform,B2 as partitionTenantEnv,T2 as parseEnvFromKey,M as parse,R2 as migrateEncryptedValue,I2 as loadEnvFiles,Yf as loadEnv,v2 as isWindows,Qf as isNode,f2 as isMinimal,i2 as isMacOS,n2 as isLinux,W2 as isLegacyEncryptedValue,p2 as isDebug,l2 as isColorSupported,ef as isCI,Zf as isBun,o2 as hasWindow,If as hasTTY,n as getPrivateKey,m2 as getKeypair,k2 as getEnv,S as generateKeypair,g5 as foreignTenantKeys,pf as envPlugin,wf as envEnum,Ef as env,c as encryptValue,P2 as encryptEnv,b as decryptValue,Tf as decryptEnvValue,lf as decryptEnv,x2 as autoLoadEnv,O2 as aesEncrypt,zf as aesDecrypt,l as activeEnvName};
24
+ `,T=Qf(g.output,D.publicKey,j,f),I=`${process.pid}-${Date.now()}`,w=L(Zf(X),`.${a(X)}.${I}.rotate`),Q=L(Zf($),`.${a($)}.${I}.rotate`);try{if(V(w,T,{encoding:"utf8",mode:384}),V(Q,R,{encoding:"utf8",mode:384}),f.stdout)return s(Q,$),{success:!0,output:T};s(w,X);try{s(Q,$)}catch(U){throw V(X,H),U}return{success:!0,output:`\u2714 rotated (${f.file||".env"}) to encrypted:v2`}}catch(U){if(!z($))V($,A,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${U instanceof Error?U.message:"Unknown error"}`}}finally{Of(w,{force:!0}),Of(Q,{force:!0})}}import O from"process";import{platform as sf}from"os";var Rf=typeof Bun<"u",If=typeof O<"u"&&O.versions?.node!==void 0,af=Rf?"bun":If?"node":"unknown",o2={name:af,version:Rf?Bun.version:If?O.version:void 0},ff=sf(),p2=ff==="win32",l2=ff==="darwin",s2=ff==="linux",wf=Boolean(O.stdout?.isTTY),a2=typeof globalThis.window<"u",ef=Boolean(O.env.CI||O.env.CONTINUOUS_INTEGRATION||O.env.BUILD_NUMBER||O.env.RUN_ID),e2=Boolean(O.env.DEBUG||O.env.VERBOSE||O.argv.includes("--debug")||O.argv.includes("--verbose")),f2=ef||!wf,f5=Boolean(!f2&&(wf||O.env.COLORTERM||O.env.FORCE_COLOR||O.env.TERM&&O.env.TERM!=="dumb")),e={github:{name:"GitHub Actions",detected:Boolean(O.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(O.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(O.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(O.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(O.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(O.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(O.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(O.env.DYNO)},aws:{name:"AWS",detected:Boolean(O.env.AWS_REGION||O.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(O.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(O.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(O.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(O.env.RENDER)}},g2=Object.keys(e).find((f)=>{let g=e[f];return g!==void 0&&g.detected})||"unknown",g5=e[g2]||{name:"Unknown",detected:!1};import _2 from"process";import{projectPath as A2}from"@stacksjs/path";import Lf from"fs";var Uf={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 B2(f){return typeof f==="string"&&(f.startsWith("encrypted:")||f.startsWith("enc:"))}var $2=/(?:^|_)(?:PASSWORD|PASSWD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|CREDENTIALS|PRIVATE|SALT|DSN|CERT|CIPHER|SIGNATURE|WEBHOOK)(?:_|$)|(?:^|_)(?:ACCESS|SECRET)_KEY(?:_|$)|_KEY$/,X2=/^DOTENV_(?:PUBLIC|PRIVATE)_KEY/,D2=/^\$\{[^}]+\}$/;function X5(f,g={}){let{placeholders:B={},strict:X=!1}=g,$=[];for(let[D,H]of Object.entries(f)){if(!H||H.trim()==="")continue;if(B2(H))continue;if(X2.test(D))continue;if(D2.test(H.trim()))continue;let A=$2.test(D),W=Object.prototype.hasOwnProperty.call(B,D)&&B[D]===H;if(A&&!W){$.push({key:D,reason:"plaintext-secret"});continue}if(X)$.push({key:D,reason:"plaintext-value"})}return $}function D5(f){return f.split(`
25
+ `).map((g)=>g.trim()).filter(Boolean).filter((g)=>g!==".env.example").filter((g)=>/(?:^|\/)\.env(?:\.|$)/.test(g)).filter((g)=>!g.endsWith(".env.keys"))}function Tf(f){return`${f.replace(/[^a-z0-9]/gi,"").toUpperCase()}_`}function H2(f,g={}){let B=g.self?Tf(g.self):void 0,X=(g.tenants??[]).filter((H)=>typeof H==="string"&&H.trim().length>0).map((H)=>({slug:H,prefix:Tf(H)})).filter(({prefix:H})=>H!==B).sort((H,A)=>A.prefix.length-H.prefix.length),$={},D={};for(let[H,A]of Object.entries(f)){let W=X.find(({prefix:_})=>H.startsWith(_));if(W){(D[W.slug]??={})[H]=A;continue}$[H]=A}return{own:$,foreign:D}}function _5(f,g={}){return H2(f,g).own}function A5(f){return Object.entries(f.foreign).map(([g,B])=>({tenant:g,keys:Object.keys(B).sort()})).sort((g,B)=>g.tenant.localeCompare(B.tenant))}var Mf=new Set;function W2(f){if(Mf.has(f))return;Mf.add(f);let g=l();console.warn(`[env] warning: "${f}" is encrypted but no usable private key was found for the "${g}" environment; falling back to its default. Set DOTENV_PRIVATE_KEY_${g.toUpperCase()} or ship .env.keys.`)}var Y2={get:(f,g)=>{let B=f[g];if(typeof B==="string"&&(B.startsWith("encrypted:")||B.startsWith("enc:"))){let $=Gf(B);if($===void 0){W2(g),delete f[g];return}f[g]=$,B=$}let X=["_PORT","_TIMEOUT","_TTL","_SIZE","_LIMIT","_MAX","_MIN","_INTERVAL","_RETRIES","_CONCURRENCY","_WORKERS","_CONNECTIONS"];if(typeof B==="string"&&/^\d+$/.test(B)&&!B.startsWith("0")&&X.some(($)=>g.endsWith($)))return Number(B);if(typeof B==="string"){let $=B.toLowerCase();if($==="true")return!0;if($==="false")return!1}return B}};function j2(){return typeof Bun<"u"?Bun.env:_2.env}var qf=new Proxy(j2(),Y2);function Z5(f,g,B){let X=B?.path||A2(".env"),D=Lf.readFileSync(X,"utf-8").split(`
26
+ `),H=D.findIndex((_)=>_.startsWith(`${f}=`)),W=/[\s"'#$\\]/.test(g)?`"${g.replace(/"/g,"\\\"")}"`:g;if(H!==-1)D[H]=`${f}=${W}`;else D.push(`${f}=${W}`);Lf.writeFileSync(X,D.join(`
27
+ `))}function Q5(f=qf){let g=[];for(let[B,X]of Object.entries(Uf)){let $=f[B];if($!==void 0&&$!==""&&!X.includes(String($)))g.push(`${B}="${$}" is not valid. Allowed values: ${X.join(", ")}`)}return g}function R5(f,g=qf){let B=[];for(let X of f){let $=g[X];if($===void 0||$===""||$===null)B.push(X)}if(B.length>0)throw Error(`[env] Missing required environment variable(s): ${B.join(", ")}. Set them in .env or your process environment before booting.`);return g}export{Z5 as writeEnv,Q5 as validateEnv,D5 as trackedEnvFiles,Tf as tenantEnvPrefix,_5 as stripForeignTenantEnv,u2 as setEnv,o2 as runtimeInfo,af as runtime,y2 as rotateKeypair,of as resolvePrivateKey,m2 as resolveEnvFile,b2 as resetPrivateKeyCache,R5 as requireEnv,g5 as providerInfo,g2 as provider,j2 as process,ff as platform,X5 as plaintextSecrets,H2 as partitionTenantEnv,Q2 as parseEnvFromKey,M as parse,Z2 as migrateEncryptedValue,M2 as loadEnvFiles,Jf as loadEnv,p2 as isWindows,If as isNode,f2 as isMinimal,l2 as isMacOS,s2 as isLinux,O2 as isLegacyEncryptedValue,B2 as isEncryptedValue,e2 as isDebug,f5 as isColorSupported,ef as isCI,Rf as isBun,a2 as hasWindow,wf as hasTTY,o as getPrivateKey,t2 as getKeypair,r2 as getEnv,S as generateKeypair,A5 as foreignTenantKeys,pf as envPlugin,Uf as envEnum,qf as env,c as encryptValue,d2 as encryptEnv,b as decryptValue,Gf as decryptEnvValue,lf as decryptEnv,S2 as autoLoadEnv,J2 as aesEncrypt,Vf as aesDecrypt,l as activeEnvName,$2 as SECRET_NAME_PATTERN};
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Unencrypted secrets in a committed env file.
3
+ *
4
+ * Stacks encrypts `.env.<environment>` in place and un-ignores those files so
5
+ * they can be committed - a good design with one failure mode: a value that was
6
+ * never encrypted looks exactly like one that was. `encrypted:`-prefixed and
7
+ * bare text sit in the same file, in the same syntax, and nothing reads the
8
+ * difference.
9
+ *
10
+ * It has already happened here. `.env.production` carried three per-mailbox
11
+ * SMTP passwords and the coming-soon bypass secret in plaintext; this
12
+ * repository is public, so they were readable at HEAD for anyone who looked.
13
+ * They were removed in a57c8ed69b (stacksjs/stacks#2211).
14
+ *
15
+ * ## Why this is not "every value must be encrypted"
16
+ *
17
+ * That rule sounds right and is unusable. `.env.production` at the time of
18
+ * writing holds 26 non-empty plaintext values, nearly all of them things like
19
+ * `APP_NAME=Stacks`, `DB_HOST=127.0.0.1` and `MAIL_HOST=mailpit`. Failing on
20
+ * those trains people to pass `--force`, which is worse than not checking.
21
+ *
22
+ * So the question asked here is narrower and answerable: **does this key look
23
+ * like a secret, and is its value real rather than a placeholder?** Two
24
+ * independent signals:
25
+ *
26
+ * - the KEY NAME matches a secret-shaped pattern ({@link SECRET_NAME_PATTERN}),
27
+ * deliberately broad because a false positive costs one exclusion and a
28
+ * missed secret is the bug this module exists for. The same trade is made,
29
+ * for the same reason, by `SECRET_PATTERNS` in the query logger.
30
+ * - the VALUE is not the documented placeholder. `.env.example` is the
31
+ * project's own statement of what a non-secret default looks like, so a
32
+ * value byte-identical to the example's value for that key is understood as
33
+ * a placeholder rather than a leak. That is what clears `MAIL_PASSWORD=null`
34
+ * and `MEILISEARCH_KEY=masterKey` without an allowlist anyone has to
35
+ * maintain.
36
+ *
37
+ * `strict` opts into the absolute rule for projects that want it - and it is
38
+ * satisfiable, since `buddy env:encrypt` encrypts every key, which is why
39
+ * `.env.development` and `.env.staging` already pass it.
40
+ *
41
+ * ## What is never returned
42
+ *
43
+ * Key names only. A report that quotes the offending value writes the secret
44
+ * into CI logs, terminal scrollback and issue trackers - which is the thing
45
+ * being prevented.
46
+ */
47
+ /**
48
+ * Whether a value has been encrypted in place.
49
+ *
50
+ * `encrypted:` is the current envelope (`encrypted:v2:` with the versioned
51
+ * header); `enc:` is the short alias. The predicate was written out four
52
+ * separate times across this package before it lived here.
53
+ */
54
+ export declare function isEncryptedValue(value: string | undefined | null): boolean;
55
+ /**
56
+ * Find values in a parsed env file that should have been encrypted.
57
+ *
58
+ * Returns key names, never values. Empty values, `${VAR}` expansions, dotenvx
59
+ * key metadata and anything already encrypted are skipped.
60
+ */
61
+ export declare function plaintextSecrets(values: Record<string, string>, options?: PlaintextScanOptions): PlaintextFinding[];
62
+ /**
63
+ * Whether an env file is committed to git and therefore in scope.
64
+ *
65
+ * Enumerating with `git ls-files` rather than a hardcoded
66
+ * production/staging/ci list, because `.gitignore` un-ignores three names but
67
+ * `.env.development` is tracked as well - no ignore rule happens to match it.
68
+ * A hardcoded list would have skipped a file that is just as public.
69
+ */
70
+ export declare function trackedEnvFiles(gitLsFilesOutput: string): string[];
71
+ /**
72
+ * Key names that should never hold readable text in a committed file.
73
+ *
74
+ * Broad on purpose. Excluding a false positive is a one-line change a person
75
+ * makes once; missing a real secret is the incident. Anchored on word
76
+ * boundaries so `KEYCLOAK_URL` does not match on `KEY`.
77
+ */
78
+ export declare const SECRET_NAME_PATTERN: unknown;
79
+ export declare interface PlaintextFinding {
80
+ key: string
81
+ reason: PlaintextReason
82
+ }
83
+ export declare interface PlaintextScanOptions {
84
+ placeholders?: Record<string, string>
85
+ strict?: boolean
86
+ }
87
+ export type PlaintextReason = 'plaintext-secret' | 'plaintext-value';
package/dist/plugin.d.ts CHANGED
@@ -14,8 +14,20 @@ export declare function activeEnvName(options?: { env?: string }): string;
14
14
  */
15
15
  export declare function resolvePrivateKey(options?: { env?: string, cwd?: string }): string | undefined;
16
16
  /**
17
- * Clear the cached private key. Needed after key rotation and between tests
18
- * that swap `DOTENV_PRIVATE_KEY*` or `APP_ENV`.
17
+ * Clear the cached private key AND the recorded environment. Needed after key
18
+ * rotation and between tests that swap `DOTENV_PRIVATE_KEY*` or `APP_ENV`.
19
+ *
20
+ * `loadedEnvName` has to go with it. It deliberately outranks `APP_ENV` in
21
+ * {@link activeEnvName} — an env file that has been read owns the keys for
22
+ * anything decrypted afterwards — so leaving it set means a later
23
+ * `APP_ENV` swap is silently ignored, which is the one thing this function
24
+ * exists to make possible. Whether it happened to be set at all depended on
25
+ * which test file ran first in the process, so the env proxy's decryption
26
+ * tests passed or failed on file ordering and CI went red on roughly half of
27
+ * all runs (stacksjs/stacks#2259).
28
+ *
29
+ * Test-only: nothing outside `core/env/tests` calls this, so clearing the
30
+ * recorded environment cannot affect a running app.
19
31
  */
20
32
  export declare function resetPrivateKeyCache(): void;
21
33
  /**
package/dist/plugin.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // @bun
2
- import{createCipheriv as q,createDecipheriv as u,createHash as L,createPrivateKey as nt,createPublicKey as P,diffieHellman as p,generateKeyPairSync as z,hkdfSync as N,randomBytes as S}from"crypto";var U="x25519-public:",Z="x25519-private:",W="encrypted:v2:",Q="encrypted:",m=Buffer.from("stacks-env:v2","utf8"),d="Environment decryption failed: authentication or format error";function v(t){if(t.length===32)return t;return L("sha256").update(t).digest()}function Wt(t,n){let f=S(16),i=q("aes-256-gcm",v(n),f);return{ciphertext:Buffer.concat([i.update(t,"utf8"),i.final()]).toString("hex"),iv:f.toString("hex"),authTag:i.getAuthTag().toString("hex")}}function ft(t,n,f,i){try{let r=u("aes-256-gcm",v(n),Buffer.from(f,"hex"));return r.setAuthTag(Buffer.from(i,"hex")),Buffer.concat([r.update(Buffer.from(t,"hex")),r.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function _(t){return t.toString("base64url")}function y(t,n,f){if(typeof t!=="string"||t.length>0&&!/^[A-Za-z0-9_-]+$/.test(t))throw Error(`${n} is not canonical base64url`);let i=Buffer.from(t,"base64url");if(_(i)!==t)throw Error(`${n} is not canonical base64url`);if(f!==void 0&&i.length!==f)throw Error(`${n} has an invalid length`);return i}function it(t){if(!t.startsWith(U))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=P({key:y(t.slice(U.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function rt(t){if(!t.startsWith(Z))throw Error("Private key is not a version 2 X25519 key");let n=nt({key:y(t.slice(Z.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function e(t){return Buffer.from(`stacks-env:v2;${t.epk};${t.salt};${t.nonce}`,"utf8")}function jt(){let{publicKey:t,privateKey:n}=z("x25519"),f=t.export({format:"der",type:"spki"}),i=n.export({format:"der",type:"pkcs8"});return{publicKey:`${U}${_(f)}`,privateKey:`${Z}${_(i)}`}}function gt(t,n){let f=it(n),i=z("x25519"),r=_(i.publicKey.export({format:"der",type:"spki"})),g=S(16),o=S(12),h=p({privateKey:i.privateKey,publicKey:f}),B=Buffer.from(N("sha256",h,g,m,32)),w={epk:r,salt:_(g),nonce:_(o)};try{let c=q("aes-256-gcm",B,o);c.setAAD(e(w));let s=Buffer.concat([c.update(t,"utf8"),c.final()]),$={v:2,...w,ciphertext:_(s),tag:_(c.getAuthTag())};return`${W}${_(Buffer.from(JSON.stringify($),"utf8"))}`}finally{h.fill(0),B.fill(0)}}function ct(t){let n=y(t.slice(W.length),"envelope").toString("utf8"),f=JSON.parse(n);if(!f||typeof f!=="object"||Array.isArray(f))throw Error("envelope is not an object");let i=Object.keys(f).sort(),r=["ciphertext","epk","nonce","salt","tag","v"];if(i.length!==r.length||i.some((g,o)=>g!==r[o]))throw Error("envelope fields are invalid");if(f.v!==2)throw Error("envelope version is unsupported");for(let g of["epk","salt","nonce","ciphertext","tag"])if(typeof f[g]!=="string")throw Error(`${g} is invalid`);return f}function ot(t,n){try{let f=ct(t),i=rt(n),r=P({key:y(f.epk,"ephemeral public key"),format:"der",type:"spki"});if(r.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let g=y(f.salt,"salt",16),o=y(f.nonce,"nonce",12),h=y(f.ciphertext,"ciphertext"),B=y(f.tag,"tag",16),w=p({privateKey:i,publicKey:r}),c=Buffer.from(N("sha256",w,g,m,32));try{let s=u("aes-256-gcm",c,o);return s.setAAD(e(f)),s.setAuthTag(B),Buffer.concat([s.update(h),s.final()]).toString("utf8")}finally{w.fill(0),c.fill(0)}}catch{throw Error(d)}}function ht(t,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let f=Buffer.from(t.slice(Q.length),"base64");if(f.length<32)throw Error("invalid legacy payload");let i=L("sha256").update(Buffer.from(n,"hex")).digest(),r=L("sha256").update(i).digest();return ft(f.subarray(32).toString("hex"),r,f.subarray(0,16).toString("hex"),f.subarray(16,32).toString("hex"))}catch{throw Error(d)}}function Ct(t){return t.startsWith(Q)&&!t.startsWith(W)}function K(t,n){if(!t.startsWith(Q))return t;return t.startsWith(W)?ot(t,n):ht(t,n)}function Gt(t,n,f){return gt(K(t,n),f)}function Jt(t){let n=t.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function V(t=""){return t?process.env[`DOTENV_PRIVATE_KEY_${t.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as wt}from"child_process";import{readFileSync as Bt}from"fs";import{arch as st,hostname as Tt,platform as l,release as $t,userInfo as At}from"os";import{basename as _t,dirname as yt,resolve as Et}from"path";function D(t,n={}){let f={},i=[],r=[],g=t.split(`
2
+ import{createCipheriv as q,createDecipheriv as u,createHash as S,createPrivateKey as nt,createPublicKey as P,diffieHellman as p,generateKeyPairSync as z,hkdfSync as N,randomBytes as U}from"crypto";var Z="x25519-public:",Q="x25519-private:",W="encrypted:v2:",V="encrypted:",m=Buffer.from("stacks-env:v2","utf8"),d="Environment decryption failed: authentication or format error";function v(t){if(t.length===32)return t;return S("sha256").update(t).digest()}function Wt(t,n){let f=U(16),i=q("aes-256-gcm",v(n),f);return{ciphertext:Buffer.concat([i.update(t,"utf8"),i.final()]).toString("hex"),iv:f.toString("hex"),authTag:i.getAuthTag().toString("hex")}}function ft(t,n,f,i){try{let r=u("aes-256-gcm",v(n),Buffer.from(f,"hex"));return r.setAuthTag(Buffer.from(i,"hex")),Buffer.concat([r.update(Buffer.from(t,"hex")),r.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function _(t){return t.toString("base64url")}function y(t,n,f){if(typeof t!=="string"||t.length>0&&!/^[A-Za-z0-9_-]+$/.test(t))throw Error(`${n} is not canonical base64url`);let i=Buffer.from(t,"base64url");if(_(i)!==t)throw Error(`${n} is not canonical base64url`);if(f!==void 0&&i.length!==f)throw Error(`${n} has an invalid length`);return i}function it(t){if(!t.startsWith(Z))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=P({key:y(t.slice(Z.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function rt(t){if(!t.startsWith(Q))throw Error("Private key is not a version 2 X25519 key");let n=nt({key:y(t.slice(Q.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function e(t){return Buffer.from(`stacks-env:v2;${t.epk};${t.salt};${t.nonce}`,"utf8")}function jt(){let{publicKey:t,privateKey:n}=z("x25519"),f=t.export({format:"der",type:"spki"}),i=n.export({format:"der",type:"pkcs8"});return{publicKey:`${Z}${_(f)}`,privateKey:`${Q}${_(i)}`}}function gt(t,n){let f=it(n),i=z("x25519"),r=_(i.publicKey.export({format:"der",type:"spki"})),g=U(16),o=U(12),h=p({privateKey:i.privateKey,publicKey:f}),B=Buffer.from(N("sha256",h,g,m,32)),w={epk:r,salt:_(g),nonce:_(o)};try{let c=q("aes-256-gcm",B,o);c.setAAD(e(w));let s=Buffer.concat([c.update(t,"utf8"),c.final()]),$={v:2,...w,ciphertext:_(s),tag:_(c.getAuthTag())};return`${W}${_(Buffer.from(JSON.stringify($),"utf8"))}`}finally{h.fill(0),B.fill(0)}}function ct(t){let n=y(t.slice(W.length),"envelope").toString("utf8"),f=JSON.parse(n);if(!f||typeof f!=="object"||Array.isArray(f))throw Error("envelope is not an object");let i=Object.keys(f).sort(),r=["ciphertext","epk","nonce","salt","tag","v"];if(i.length!==r.length||i.some((g,o)=>g!==r[o]))throw Error("envelope fields are invalid");if(f.v!==2)throw Error("envelope version is unsupported");for(let g of["epk","salt","nonce","ciphertext","tag"])if(typeof f[g]!=="string")throw Error(`${g} is invalid`);return f}function ot(t,n){try{let f=ct(t),i=rt(n),r=P({key:y(f.epk,"ephemeral public key"),format:"der",type:"spki"});if(r.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let g=y(f.salt,"salt",16),o=y(f.nonce,"nonce",12),h=y(f.ciphertext,"ciphertext"),B=y(f.tag,"tag",16),w=p({privateKey:i,publicKey:r}),c=Buffer.from(N("sha256",w,g,m,32));try{let s=u("aes-256-gcm",c,o);return s.setAAD(e(f)),s.setAuthTag(B),Buffer.concat([s.update(h),s.final()]).toString("utf8")}finally{w.fill(0),c.fill(0)}}catch{throw Error(d)}}function ht(t,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let f=Buffer.from(t.slice(V.length),"base64");if(f.length<32)throw Error("invalid legacy payload");let i=S("sha256").update(Buffer.from(n,"hex")).digest(),r=S("sha256").update(i).digest();return ft(f.subarray(32).toString("hex"),r,f.subarray(0,16).toString("hex"),f.subarray(16,32).toString("hex"))}catch{throw Error(d)}}function Ct(t){return t.startsWith(V)&&!t.startsWith(W)}function K(t,n){if(!t.startsWith(V))return t;return t.startsWith(W)?ot(t,n):ht(t,n)}function Gt(t,n,f){return gt(K(t,n),f)}function Jt(t){let n=t.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function F(t=""){return t?process.env[`DOTENV_PRIVATE_KEY_${t.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as wt}from"child_process";import{readFileSync as Bt}from"fs";import{arch as st,hostname as Tt,platform as l,release as $t,userInfo as At}from"os";import{basename as _t,dirname as yt,resolve as Rt}from"path";function D(t,n={}){let f={},i=[],r=[],g=t.split(`
3
3
  `);for(let o of g){let h=o.trim();if(!h||h.startsWith("#"))continue;if(h.startsWith("DOTENV_PUBLIC_KEY=")){let s=h.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(s&&s[1]!==void 0)f.DOTENV_PUBLIC_KEY=s[1];continue}let B=h.match(/^([^=]+)=(.*)$/);if(!B||B[1]===void 0||B[2]===void 0)continue;let w=B[1].trim(),c=B[2].trim();if(c.startsWith('"')&&c.endsWith('"')||c.startsWith("'")&&c.endsWith("'")){if(c=c.slice(1,-1),c.includes("\\n"))c=c.replace(/\\n/g,`
4
- `)}if(c.startsWith("encrypted:")||c.startsWith("enc:")){if(!n.privateKey){r.push(w);continue}try{let s=c.startsWith("enc:")?`encrypted:${c.slice(4)}`:c;c=K(s,n.privateKey)}catch(s){i.push(`Failed to decrypt ${w}: ${s instanceof Error?s.message:"Unknown error"}`)}}c=Rt(c,{...n.processEnv||process.env,...f}),c=Dt(c),f[w]=c}return{parsed:f,errors:i,skippedEncrypted:r}}function Rt(t,n){return t.replace(/\$\{([^}]+)\}/g,(f,i)=>{let r=i.match(/^([^:\-+]+)(:-|-)(.+)$/);if(r){let[,o,h,B]=r,w=n[o];if(h===":-")return w||B;else return w!==void 0?w:B}let g=i.match(/^([^:\-+]+)(:?\+)(.+)$/);if(g){let[,o,h,B]=g,w=n[o];if(h===":+")return w?B:"";else return w!==void 0?B:""}return n[i]||""})}var bt=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function xt(t){let n=[],f="",i=null,r=!1;for(let g of t.trim()){if(r){f+=g,r=!1;continue}if(g==="\\"&&i!=="'"){r=!0;continue}if(i){if(g===i)i=null;else f+=g;continue}if(g==='"'||g==="'"){i=g;continue}if(/\s/.test(g)){if(f)n.push(f),f="";continue}f+=g}if(r||i)throw Error("Command substitution contains an unterminated escape or quote");if(f)n.push(f);return n}function Kt(t){let[n,...f]=t,i=f.some((r)=>r.startsWith("-"));if(n==="echo"&&!i)return f.join(" ");if(n==="printf"&&f.length===1&&!f[0]?.includes("%"))return f[0];if(n==="hostname"&&f.length===0)return Tt();if(n==="whoami"&&f.length===0)return At().username;if(n==="pwd"&&f.length===0)return process.cwd();if(n==="basename"&&f.length===1)return _t(f[0]??"");if(n==="dirname"&&f.length===1)return yt(f[0]??"");if(n==="cat"&&f.length>0&&!i)return f.map((r)=>Bt(Et(process.cwd(),r),"utf8")).join("");if(n==="uname"){let r=f[0]??"-s";if(f.length<=1&&r==="-m")return st();if(f.length<=1&&r==="-r")return $t();if(f.length<=1&&r==="-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"}[l()]??l()}return}function Dt(t){return t.replace(/\$\(([^)]+)\)/g,(n,f)=>{try{let i=xt(f),r=i[0];if(!r||!bt.has(r))return console.warn(`[env] Blocked command substitution for disallowed command: ${r}`),"";let g=Kt(i);if(g!==void 0)return g.trim();let o=wt(r,i.slice(1),{encoding:"utf8",env:process.env});if(o.status===0)return o.stdout.trim();let h=o.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${o.status??"unknown"}): ${r}${h?`: ${h}`:""}`)}catch(i){console.warn(`[env] Command substitution error: ${i instanceof Error?i.message:String(i)}`)}return""})}async function Ft(t,n={}){let f={},i=[],r=[];for(let g of t)try{let o=Bun.file(g);if(!o.size)continue;let h=await o.text(),{parsed:B,errors:w,skippedEncrypted:c}=D(h,n);i.push(...w),r.push(...c);for(let[s,$]of Object.entries(B)){if(!n.overload&&f[s]!==void 0)continue;f[s]=$}}catch(o){if(o?.code==="ENOENT")continue;i.push(`Failed to read ${g}: ${o instanceof Error?o.message:String(o)}`)}return{parsed:f,errors:i,skippedEncrypted:r}}import{existsSync as j}from"fs";import{readFileSync as F}from"fs";import{resolve as C}from"path";function X(t){return typeof t==="string"&&(t.startsWith("encrypted:")||t.startsWith("enc:"))}function E(t){if(!t||X(t))return;let n=t.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 G=null,J,M;function Xt(t,n,f=".env.keys"){let i=C(n,f);if(!j(i))return;try{let{parsed:r}=D(F(i,"utf-8"));if(t){let g=r[`DOTENV_PRIVATE_KEY_${t.toUpperCase()}`];if(g)return g}return r.DOTENV_PRIVATE_KEY}catch{return}}function Ot(t={}){return E(t.env)||M||E("development")||E(process.env.DOTENV_ENV)||E(process.env.APP_ENV)||"development"}function Yt(t={}){let n=Ot(t),f=t.cwd||process.cwd();if(G===n)return J;let i=V(n);if(!i)i=process.env.DOTENV_PRIVATE_KEY;if(!i)i=Xt(n,f);return G=n,J=i,i}function Nt(){G=null,J=void 0}function mt(t,n={}){if(!X(t))return t;let f=Yt(n);if(!f)return;try{let i=t.startsWith("enc:")?`encrypted:${t.slice(4)}`:t;return K(i,f)}catch{return}}function a(t={}){let{path:n=[".env"],overload:f=!1,env:i,privateKey:r,keysFile:g=".env.keys",quiet:o=!1,cwd:h=process.cwd()}=t,B=Array.isArray(n)?n:[n],w=[],c=0,s=new Set(Object.entries(process.env).filter(([,T])=>T!==void 0&&!X(T)).map(([T])=>T)),$=r;if(!$&&g){let T=C(h,g);if(j(T))try{let A=F(T,"utf-8"),{parsed:R}=D(A);if(i){let O=`DOTENV_PRIVATE_KEY_${i.toUpperCase()}`;$=R[O]}else $=R.DOTENV_PRIVATE_KEY}catch(A){w.push(`Failed to load keys file: ${A instanceof Error?A.message:"Unknown error"}`)}}if(!$)$=V(i||"");for(let T of B){let A=C(h,T);if(!j(A)){if(!o)w.push(`File not found: ${A}`);continue}try{let R=F(A,"utf-8"),{parsed:O,errors:tt,skippedEncrypted:b}=D(R,{privateKey:$,processEnv:process.env});if(w.push(...tt),b.length>0){for(let I of b)if(X(process.env[I]))delete process.env[I];let x=i?`DOTENV_PRIVATE_KEY_${i.toUpperCase()}`:"DOTENV_PRIVATE_KEY",Y=b.slice(0,5).join(", "),H=b.length>5?`, \u2026 +${b.length-5} more`:"";console.warn(`[env] warning: skipped ${b.length} encrypted value(s) in ${T} (${x} not set; defaults apply): ${Y}${H}`)}let k=0;for(let[x,Y]of Object.entries(O)){if(x==="DOTENV_PUBLIC_KEY")continue;let H=process.env[x],I=X(H)&&Y!==H;if(f||!s.has(x)||I)process.env[x]=Y,c++,k++}if(!o&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${k}/${Object.keys(O).length} variables from ${T}`),process.env.__ENV_LOADED__="1"}catch(R){w.push(`Failed to load ${A}: ${R instanceof Error?R.message:"Unknown error"}`)}}return{loaded:c,errors:w}}function Ht(t={}){return{name:"env-plugin",setup(n){a(t)}}}function dt(t={}){let n=E(t.env)||E("development")||E(process.env.DOTENV_ENV)||E(process.env.APP_ENV)||"development",f=t.cwd||process.cwd();if(M!==n)M=n,G=null,J=void 0;let i=[],r=(h)=>{if(!i.includes(h)&&j(C(f,h)))i.push(h)};r(".env"),r(".env.local");let g=`.env.${n}`,o=`.env.${n}.local`;return r(g),r(o),a({...t,path:i,env:n})}var et=Ht;export{Yt as resolvePrivateKey,Nt as resetPrivateKeyCache,a as loadEnv,Ht as envPlugin,et as default,mt as decryptEnvValue,dt as autoLoadEnv,Ot as activeEnvName};
4
+ `)}if(c.startsWith("encrypted:")||c.startsWith("enc:")){if(!n.privateKey){r.push(w);continue}try{let s=c.startsWith("enc:")?`encrypted:${c.slice(4)}`:c;c=K(s,n.privateKey)}catch(s){i.push(`Failed to decrypt ${w}: ${s instanceof Error?s.message:"Unknown error"}`)}}c=Et(c,{...n.processEnv||process.env,...f}),c=Dt(c),f[w]=c}return{parsed:f,errors:i,skippedEncrypted:r}}function Et(t,n){return t.replace(/\$\{([^}]+)\}/g,(f,i)=>{let r=i.match(/^([^:\-+]+)(:-|-)(.+)$/);if(r){let[,o,h,B]=r,w=n[o];if(h===":-")return w||B;else return w!==void 0?w:B}let g=i.match(/^([^:\-+]+)(:?\+)(.+)$/);if(g){let[,o,h,B]=g,w=n[o];if(h===":+")return w?B:"";else return w!==void 0?B:""}return n[i]||""})}var bt=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function xt(t){let n=[],f="",i=null,r=!1;for(let g of t.trim()){if(r){f+=g,r=!1;continue}if(g==="\\"&&i!=="'"){r=!0;continue}if(i){if(g===i)i=null;else f+=g;continue}if(g==='"'||g==="'"){i=g;continue}if(/\s/.test(g)){if(f)n.push(f),f="";continue}f+=g}if(r||i)throw Error("Command substitution contains an unterminated escape or quote");if(f)n.push(f);return n}function Kt(t){let[n,...f]=t,i=f.some((r)=>r.startsWith("-"));if(n==="echo"&&!i)return f.join(" ");if(n==="printf"&&f.length===1&&!f[0]?.includes("%"))return f[0];if(n==="hostname"&&f.length===0)return Tt();if(n==="whoami"&&f.length===0)return At().username;if(n==="pwd"&&f.length===0)return process.cwd();if(n==="basename"&&f.length===1)return _t(f[0]??"");if(n==="dirname"&&f.length===1)return yt(f[0]??"");if(n==="cat"&&f.length>0&&!i)return f.map((r)=>Bt(Rt(process.cwd(),r),"utf8")).join("");if(n==="uname"){let r=f[0]??"-s";if(f.length<=1&&r==="-m")return st();if(f.length<=1&&r==="-r")return $t();if(f.length<=1&&r==="-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"}[l()]??l()}return}function Dt(t){return t.replace(/\$\(([^)]+)\)/g,(n,f)=>{try{let i=xt(f),r=i[0];if(!r||!bt.has(r))return console.warn(`[env] Blocked command substitution for disallowed command: ${r}`),"";let g=Kt(i);if(g!==void 0)return g.trim();let o=wt(r,i.slice(1),{encoding:"utf8",env:process.env});if(o.status===0)return o.stdout.trim();let h=o.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${o.status??"unknown"}): ${r}${h?`: ${h}`:""}`)}catch(i){console.warn(`[env] Command substitution error: ${i instanceof Error?i.message:String(i)}`)}return""})}async function Ft(t,n={}){let f={},i=[],r=[];for(let g of t)try{let o=Bun.file(g);if(!o.size)continue;let h=await o.text(),{parsed:B,errors:w,skippedEncrypted:c}=D(h,n);i.push(...w),r.push(...c);for(let[s,$]of Object.entries(B)){if(!n.overload&&f[s]!==void 0)continue;f[s]=$}}catch(o){if(o?.code==="ENOENT")continue;i.push(`Failed to read ${g}: ${o instanceof Error?o.message:String(o)}`)}return{parsed:f,errors:i,skippedEncrypted:r}}import{existsSync as j}from"fs";import{readFileSync as M}from"fs";import{resolve as C}from"path";function X(t){return typeof t==="string"&&(t.startsWith("encrypted:")||t.startsWith("enc:"))}function R(t){if(!t||X(t))return;let n=t.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 G=null,J,L;function Xt(t,n,f=".env.keys"){let i=C(n,f);if(!j(i))return;try{let{parsed:r}=D(M(i,"utf-8"));if(t){let g=r[`DOTENV_PRIVATE_KEY_${t.toUpperCase()}`];if(g)return g}return r.DOTENV_PRIVATE_KEY}catch{return}}function Ot(t={}){return R(t.env)||L||R("development")||R(process.env.DOTENV_ENV)||R(process.env.APP_ENV)||"development"}function Yt(t={}){let n=Ot(t),f=t.cwd||process.cwd();if(G===n)return J;let i=F(n);if(!i)i=process.env.DOTENV_PRIVATE_KEY;if(!i)i=Xt(n,f);return G=n,J=i,i}function Nt(){G=null,J=void 0,L=void 0}function mt(t,n={}){if(!X(t))return t;let f=Yt(n);if(!f)return;try{let i=t.startsWith("enc:")?`encrypted:${t.slice(4)}`:t;return K(i,f)}catch{return}}function a(t={}){let{path:n=[".env"],overload:f=!1,env:i,privateKey:r,keysFile:g=".env.keys",quiet:o=!1,cwd:h=process.cwd()}=t,B=Array.isArray(n)?n:[n],w=[],c=0,s=new Set(Object.entries(process.env).filter(([,T])=>T!==void 0&&!X(T)).map(([T])=>T)),$=r;if(!$&&g){let T=C(h,g);if(j(T))try{let A=M(T,"utf-8"),{parsed:E}=D(A);if(i){let O=`DOTENV_PRIVATE_KEY_${i.toUpperCase()}`;$=E[O]}else $=E.DOTENV_PRIVATE_KEY}catch(A){w.push(`Failed to load keys file: ${A instanceof Error?A.message:"Unknown error"}`)}}if(!$)$=F(i||"");for(let T of B){let A=C(h,T);if(!j(A)){if(!o)w.push(`File not found: ${A}`);continue}try{let E=M(A,"utf-8"),{parsed:O,errors:tt,skippedEncrypted:b}=D(E,{privateKey:$,processEnv:process.env});if(w.push(...tt),b.length>0){for(let I of b)if(X(process.env[I]))delete process.env[I];let x=i?`DOTENV_PRIVATE_KEY_${i.toUpperCase()}`:"DOTENV_PRIVATE_KEY",Y=b.slice(0,5).join(", "),H=b.length>5?`, \u2026 +${b.length-5} more`:"";console.warn(`[env] warning: skipped ${b.length} encrypted value(s) in ${T} (${x} not set; defaults apply): ${Y}${H}`)}let k=0;for(let[x,Y]of Object.entries(O)){if(x==="DOTENV_PUBLIC_KEY")continue;let H=process.env[x],I=X(H)&&Y!==H;if(f||!s.has(x)||I)process.env[x]=Y,c++,k++}if(!o&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${k}/${Object.keys(O).length} variables from ${T}`),process.env.__ENV_LOADED__="1"}catch(E){w.push(`Failed to load ${A}: ${E instanceof Error?E.message:"Unknown error"}`)}}return{loaded:c,errors:w}}function Ht(t={}){return{name:"env-plugin",setup(n){a(t)}}}function dt(t={}){let n=R(t.env)||R("development")||R(process.env.DOTENV_ENV)||R(process.env.APP_ENV)||"development",f=t.cwd||process.cwd();if(L!==n)L=n,G=null,J=void 0;let i=[],r=(h)=>{if(!i.includes(h)&&j(C(f,h)))i.push(h)};r(".env"),r(".env.local");let g=`.env.${n}`,o=`.env.${n}.local`;return r(g),r(o),a({...t,path:i,env:n})}var et=Ht;export{Yt as resolvePrivateKey,Nt as resetPrivateKeyCache,a as loadEnv,Ht as envPlugin,et as default,mt as decryptEnvValue,dt as autoLoadEnv,Ot as activeEnvName};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/env",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.293",
5
+ "version": "0.70.296",
6
6
  "description": "Stacks env helper methods.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "better-dx": "^0.2.17",
59
- "@stacksjs/path": "0.70.293",
60
- "@stacksjs/validation": "0.70.293"
59
+ "@stacksjs/path": "0.70.296",
60
+ "@stacksjs/validation": "0.70.296"
61
61
  }
62
62
  }