@stacksjs/env 0.74.43 → 0.74.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,6 +33,18 @@ autoLoadEnv()
33
33
  console.log(process.env.MY_SECRET)
34
34
  ```
35
35
 
36
+ When loading a named environment, `loadEnv` and `autoLoadEnv` look for its
37
+ `DOTENV_PRIVATE_KEY_<ENV>` key, then fall back to `DOTENV_PRIVATE_KEY` if the
38
+ scoped key is absent. This applies both to `.env.keys` (including a custom
39
+ `keysFile`) and to process environment variables. A standard single-key setup
40
+ does not need a duplicate `DOTENV_PRIVATE_KEY_DEVELOPMENT` alias.
41
+
42
+ The existing loader priority is unchanged: an explicit `privateKey`, then the
43
+ keys file, then process environment variables. Within each source the scoped
44
+ key wins over the generic key. A present key that fails decryption is an error,
45
+ not a reason to try a different key. Plaintext shell/CI value overrides are
46
+ still preserved unless `overload` is enabled.
47
+
36
48
  ### 2. Programmatic Usage
37
49
 
38
50
  ```typescript
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @bun
2
- import Rn from"process";import{projectPath as vn}from"@stacksjs/path";import Ce from"fs";import{existsSync as M}from"fs";import{readFileSync as Z}from"fs";import{resolve as F}from"path";import{createCipheriv as se,createDecipheriv as oe,createHash as j,createPrivateKey as ke,createPublicKey as de,diffieHellman as ce,generateKeyPairSync as ae,hkdfSync as ue,randomBytes as X}from"crypto";var Q="x25519-public:",q="x25519-private:",B="encrypted:v2:",z="encrypted:",fe=Buffer.from("stacks-env:v2","utf8"),le="Environment decryption failed: authentication or format error";function Ee(e){if(e.length===32)return e;return j("sha256").update(e).digest()}function Nn(e,n){let t=X(16),r=se("aes-256-gcm",Ee(n),t);return{ciphertext:Buffer.concat([r.update(e,"utf8"),r.final()]).toString("hex"),iv:t.toString("hex"),authTag:r.getAuthTag().toString("hex")}}function xe(e,n,t,r){try{let i=oe("aes-256-gcm",Ee(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 C(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(C(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 Ue(e){if(!e.startsWith(Q))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=de({key:b(e.slice(Q.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function $e(e){if(!e.startsWith(q))throw Error("Private key is not a version 2 X25519 key");let n=ke({key:b(e.slice(q.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function pe(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function x(){let{publicKey:e,privateKey:n}=ae("x25519"),t=e.export({format:"der",type:"spki"}),r=n.export({format:"der",type:"pkcs8"});return{publicKey:`${Q}${C(t)}`,privateKey:`${q}${C(r)}`}}function V(e,n){let t=Ue(n),r=ae("x25519"),i=C(r.publicKey.export({format:"der",type:"spki"})),s=X(16),o=X(12),d=ce({privateKey:r.privateKey,publicKey:t}),c=Buffer.from(ue("sha256",d,s,fe,32)),a={epk:i,salt:C(s),nonce:C(o)};try{let u=se("aes-256-gcm",c,o);u.setAAD(pe(a));let f=Buffer.concat([u.update(e,"utf8"),u.final()]),E={v:2,...a,ciphertext:C(f),tag:C(u.getAuthTag())};return`${B}${C(Buffer.from(JSON.stringify(E),"utf8"))}`}finally{d.fill(0),c.fill(0)}}function we(e){let n=b(e.slice(B.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 Be(e,n){try{let t=we(e),r=$e(n),i=de({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),d=b(t.ciphertext,"ciphertext"),c=b(t.tag,"tag",16),a=ce({privateKey:r,publicKey:i}),u=Buffer.from(ue("sha256",a,s,fe,32));try{let f=oe("aes-256-gcm",u,o);return f.setAAD(pe(t)),f.setAuthTag(c),Buffer.concat([f.update(d),f.final()]).toString("utf8")}finally{a.fill(0),u.fill(0)}}catch{throw Error(le)}}function Ve(e,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let t=Buffer.from(e.slice(z.length),"base64");if(t.length<32)throw Error("invalid legacy payload");let r=j("sha256").update(Buffer.from(n,"hex")).digest(),i=j("sha256").update(r).digest();return xe(t.subarray(32).toString("hex"),i,t.subarray(0,16).toString("hex"),t.subarray(16,32).toString("hex"))}catch{throw Error(le)}}function hn(e){return e.startsWith(z)&&!e.startsWith(B)}function L(e,n){if(!e.startsWith(z))return e;return e.startsWith(B)?Be(e,n):Ve(e,n)}function Pn(e,n,t){return V(L(e,n),t)}function Dn(e){let n=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function J(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as Me}from"child_process";import{readFileSync as Fe}from"fs";import{arch as Ye,hostname as We,platform as ge,release as Ge,userInfo as He}from"os";import{basename as je,dirname as Xe,resolve as Qe}from"path";function h(e,n={}){let t={},r=[],i=[],s=e.split(`
2
+ import Rn from"process";import{projectPath as vn}from"@stacksjs/path";import Ce from"fs";import{existsSync as F}from"fs";import{readFileSync as Z}from"fs";import{resolve as Y}from"path";import{createCipheriv as se,createDecipheriv as oe,createHash as X,createPrivateKey as ke,createPublicKey as de,diffieHellman as ce,generateKeyPairSync as ae,hkdfSync as ue,randomBytes as Q}from"crypto";var q="x25519-public:",z="x25519-private:",B="encrypted:v2:",J="encrypted:",fe=Buffer.from("stacks-env:v2","utf8"),le="Environment decryption failed: authentication or format error";function Ee(e){if(e.length===32)return e;return X("sha256").update(e).digest()}function Nn(e,n){let t=Q(16),r=se("aes-256-gcm",Ee(n),t);return{ciphertext:Buffer.concat([r.update(e,"utf8"),r.final()]).toString("hex"),iv:t.toString("hex"),authTag:r.getAuthTag().toString("hex")}}function xe(e,n,t,r){try{let i=oe("aes-256-gcm",Ee(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 C(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(C(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 Ue(e){if(!e.startsWith(q))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=de({key:b(e.slice(q.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function $e(e){if(!e.startsWith(z))throw Error("Private key is not a version 2 X25519 key");let n=ke({key:b(e.slice(z.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function pe(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function x(){let{publicKey:e,privateKey:n}=ae("x25519"),t=e.export({format:"der",type:"spki"}),r=n.export({format:"der",type:"pkcs8"});return{publicKey:`${q}${C(t)}`,privateKey:`${z}${C(r)}`}}function V(e,n){let t=Ue(n),r=ae("x25519"),i=C(r.publicKey.export({format:"der",type:"spki"})),s=Q(16),o=Q(12),d=ce({privateKey:r.privateKey,publicKey:t}),c=Buffer.from(ue("sha256",d,s,fe,32)),a={epk:i,salt:C(s),nonce:C(o)};try{let u=se("aes-256-gcm",c,o);u.setAAD(pe(a));let f=Buffer.concat([u.update(e,"utf8"),u.final()]),E={v:2,...a,ciphertext:C(f),tag:C(u.getAuthTag())};return`${B}${C(Buffer.from(JSON.stringify(E),"utf8"))}`}finally{d.fill(0),c.fill(0)}}function we(e){let n=b(e.slice(B.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 Be(e,n){try{let t=we(e),r=$e(n),i=de({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),d=b(t.ciphertext,"ciphertext"),c=b(t.tag,"tag",16),a=ce({privateKey:r,publicKey:i}),u=Buffer.from(ue("sha256",a,s,fe,32));try{let f=oe("aes-256-gcm",u,o);return f.setAAD(pe(t)),f.setAuthTag(c),Buffer.concat([f.update(d),f.final()]).toString("utf8")}finally{a.fill(0),u.fill(0)}}catch{throw Error(le)}}function Ve(e,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let t=Buffer.from(e.slice(J.length),"base64");if(t.length<32)throw Error("invalid legacy payload");let r=X("sha256").update(Buffer.from(n,"hex")).digest(),i=X("sha256").update(r).digest();return xe(t.subarray(32).toString("hex"),i,t.subarray(0,16).toString("hex"),t.subarray(16,32).toString("hex"))}catch{throw Error(le)}}function hn(e){return e.startsWith(J)&&!e.startsWith(B)}function L(e,n){if(!e.startsWith(J))return e;return e.startsWith(B)?Be(e,n):Ve(e,n)}function Pn(e,n,t){return V(L(e,n),t)}function Dn(e){let n=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function M(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as Me}from"child_process";import{readFileSync as Fe}from"fs";import{arch as Ye,hostname as We,platform as ge,release as Ge,userInfo as He}from"os";import{basename as je,dirname as Xe,resolve as Qe}from"path";function h(e,n={}){let t={},r=[],i=[],s=e.split(`
3
3
  `);for(let o of s){let d=o.trim();if(!d||d.startsWith("#"))continue;if(d.startsWith("DOTENV_PUBLIC_KEY=")){let f=d.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(f&&f[1]!==void 0)t.DOTENV_PUBLIC_KEY=f[1];continue}let c=d.match(/^([^=]+)=(.*)$/);if(!c||c[1]===void 0||c[2]===void 0)continue;let a=c[1].trim(),u=c[2].trim();if(u.startsWith('"')&&u.endsWith('"')||u.startsWith("'")&&u.endsWith("'")){if(u=u.slice(1,-1),u.includes("\\n"))u=u.replace(/\\n/g,`
4
- `)}if(u.startsWith("encrypted:")||u.startsWith("enc:")){if(!n.privateKey){i.push(a);continue}try{let f=u.startsWith("enc:")?`encrypted:${u.slice(4)}`:u;u=L(f,n.privateKey)}catch(f){r.push(`Failed to decrypt ${a}: ${f instanceof Error?f.message:"Unknown error"}`)}}u=qe(u,{...n.processEnv||process.env,...t}),u=en(u),t[a]=u}return{parsed:t,errors:r,skippedEncrypted:i}}function qe(e,n){return e.replace(/\$\{([^}]+)\}/g,(t,r)=>{let i=r.match(/^([^:\-+]+)(:-|-)(.+)$/);if(i){let[,o,d,c]=i,a=n[o];if(d===":-")return a||c;else return a!==void 0?a:c}let s=r.match(/^([^:\-+]+)(:?\+)(.+)$/);if(s){let[,o,d,c]=s,a=n[o];if(d===":+")return a?c:"";else return a!==void 0?c:""}return n[r]||""})}var ze=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 Ze(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 We();if(n==="whoami"&&t.length===0)return He().username;if(n==="pwd"&&t.length===0)return process.cwd();if(n==="basename"&&t.length===1)return je(t[0]??"");if(n==="dirname"&&t.length===1)return Xe(t[0]??"");if(n==="cat"&&t.length>0&&!r)return t.map((i)=>Fe(Qe(process.cwd(),i),"utf8")).join("");if(n==="uname"){let i=t[0]??"-s";if(t.length<=1&&i==="-m")return Ye();if(t.length<=1&&i==="-r")return Ge();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"}[ge()]??ge()}return}function en(e){return e.replace(/\$\(([^)]+)\)/g,(n,t)=>{try{let r=Je(t),i=r[0];if(!i||!ze.has(i))return console.warn(`[env] Blocked command substitution for disallowed command: ${i}`),"";let s=Ze(r);if(s!==void 0)return s.trim();let o=Me(i,r.slice(1),{encoding:"utf8",env:process.env});if(o.status===0)return o.stdout.trim();let d=o.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${o.status??"unknown"}): ${i}${d?`: ${d}`:""}`)}catch(r){console.warn(`[env] Command substitution error: ${r instanceof Error?r.message:String(r)}`)}return""})}async function Un(e,n={}){let t={},r=[],i=[];for(let s of e)try{let o=Bun.file(s);if(!o.size)continue;let d=await o.text(),{parsed:c,errors:a,skippedEncrypted:u}=h(d,n);r.push(...a),i.push(...u);for(let[f,E]of Object.entries(c)){if(!n.overload&&t[f]!==void 0)continue;t[f]=E}}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}}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 Y=null,W,G;function nn(e,n,t=".env.keys"){let r=F(n,t);if(!M(r))return;try{let{parsed:i}=h(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 ee(e={}){return K(e.env)||G||K("development")||K(process.env.DOTENV_ENV)||K(process.env.APP_ENV)||"development"}function tn(e={}){let n=ee(e),t=e.cwd||process.cwd();if(Y===n)return W;let r=J(n);if(!r)r=process.env.DOTENV_PRIVATE_KEY;if(!r)r=nn(n,t);return Y=n,W=r,r}function Yn(){Y=null,W=void 0,G=void 0}function _e(e,n={}){if(!U(e))return e;let t=tn(n);if(!t)return;try{let r=e.startsWith("enc:")?`encrypted:${e.slice(4)}`:e;return L(r,t)}catch{return}}function ye(e={}){let{path:n=[".env"],overload:t=!1,env:r,privateKey:i,keysFile:s=".env.keys",quiet:o=!1,cwd:d=process.cwd()}=e,c=Array.isArray(n)?n:[n],a=[],u=0,f=new Set(Object.entries(process.env).filter(([,g])=>g!==void 0&&!U(g)).map(([g])=>g)),E=i;if(!E&&s){let g=F(d,s);if(M(g))try{let p=Z(g,"utf-8"),{parsed:y}=h(p);if(r){let v=`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`;E=y[v]}else E=y.DOTENV_PRIVATE_KEY}catch(p){a.push(`Failed to load keys file: ${p instanceof Error?p.message:"Unknown error"}`)}}if(!E)E=J(r||"");for(let g of c){let p=F(d,g);if(!M(p)){if(!o)a.push(`File not found: ${p}`);continue}try{let y=Z(p,"utf-8"),{parsed:v,errors:T,skippedEncrypted:O}=h(y,{privateKey:E,processEnv:process.env});if(a.push(...T),O.length>0){for(let I of O)if(U(process.env[I]))delete process.env[I];let _=r?`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`:"DOTENV_PRIVATE_KEY",A=O.slice(0,5).join(", "),R=O.length>5?`, \u2026 +${O.length-5} more`:"";console.warn(`[env] warning: skipped ${O.length} encrypted value(s) in ${g} (${_} not set; defaults apply): ${A}${R}`)}let m=0;for(let[_,A]of Object.entries(v)){if(_==="DOTENV_PUBLIC_KEY")continue;let R=process.env[_],I=U(R)&&A!==R;if(t||!f.has(_)||I)process.env[_]=A,u++,m++}if(!o&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${m}/${Object.keys(v).length} variables from ${g}`),process.env.__ENV_LOADED__="1"}catch(y){a.push(`Failed to load ${p}: ${y instanceof Error?y.message:"Unknown error"}`)}}return{loaded:u,errors:a}}function rn(e={}){return{name:"env-plugin",setup(n){ye(e)}}}function Wn(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(G!==n)G=n,Y=null,W=void 0;let r=[],i=(d)=>{if(!r.includes(d)&&M(F(t,d)))r.push(d)};i(".env"),i(".env.local");let s=`.env.${n}`,o=`.env.${n}.local`;return i(s),i(o),ye({...e,path:r,env:n})}var Hn=rn;var Te={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 jn(e){return e}import{existsSync as P,readFileSync as N,renameSync as me,rmSync as Re,writeFileSync as D}from"fs";import{basename as ve,dirname as te,resolve as S}from"path";function Zn(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 Ae(e,n){return(!n.key||e.includes(n.key))&&(!n.excludeKey||!e.includes(n.excludeKey))}function sn(e,n){let t=[];for(let r of e.split(`
4
+ `)}if(u.startsWith("encrypted:")||u.startsWith("enc:")){if(!n.privateKey){i.push(a);continue}try{let f=u.startsWith("enc:")?`encrypted:${u.slice(4)}`:u;u=L(f,n.privateKey)}catch(f){r.push(`Failed to decrypt ${a}: ${f instanceof Error?f.message:"Unknown error"}`)}}u=qe(u,{...n.processEnv||process.env,...t}),u=en(u),t[a]=u}return{parsed:t,errors:r,skippedEncrypted:i}}function qe(e,n){return e.replace(/\$\{([^}]+)\}/g,(t,r)=>{let i=r.match(/^([^:\-+]+)(:-|-)(.+)$/);if(i){let[,o,d,c]=i,a=n[o];if(d===":-")return a||c;else return a!==void 0?a:c}let s=r.match(/^([^:\-+]+)(:?\+)(.+)$/);if(s){let[,o,d,c]=s,a=n[o];if(d===":+")return a?c:"";else return a!==void 0?c:""}return n[r]||""})}var ze=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 Ze(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 We();if(n==="whoami"&&t.length===0)return He().username;if(n==="pwd"&&t.length===0)return process.cwd();if(n==="basename"&&t.length===1)return je(t[0]??"");if(n==="dirname"&&t.length===1)return Xe(t[0]??"");if(n==="cat"&&t.length>0&&!r)return t.map((i)=>Fe(Qe(process.cwd(),i),"utf8")).join("");if(n==="uname"){let i=t[0]??"-s";if(t.length<=1&&i==="-m")return Ye();if(t.length<=1&&i==="-r")return Ge();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"}[ge()]??ge()}return}function en(e){return e.replace(/\$\(([^)]+)\)/g,(n,t)=>{try{let r=Je(t),i=r[0];if(!i||!ze.has(i))return console.warn(`[env] Blocked command substitution for disallowed command: ${i}`),"";let s=Ze(r);if(s!==void 0)return s.trim();let o=Me(i,r.slice(1),{encoding:"utf8",env:process.env});if(o.status===0)return o.stdout.trim();let d=o.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${o.status??"unknown"}): ${i}${d?`: ${d}`:""}`)}catch(r){console.warn(`[env] Command substitution error: ${r instanceof Error?r.message:String(r)}`)}return""})}async function Un(e,n={}){let t={},r=[],i=[];for(let s of e)try{let o=Bun.file(s);if(!o.size)continue;let d=await o.text(),{parsed:c,errors:a,skippedEncrypted:u}=h(d,n);r.push(...a),i.push(...u);for(let[f,E]of Object.entries(c)){if(!n.overload&&t[f]!==void 0)continue;t[f]=E}}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}}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 W=null,G,H;function nn(e,n,t=".env.keys"){let r=Y(n,t);if(!F(r))return;try{let{parsed:i}=h(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 ee(e={}){return K(e.env)||H||K("development")||K(process.env.DOTENV_ENV)||K(process.env.APP_ENV)||"development"}function tn(e={}){let n=ee(e),t=e.cwd||process.cwd();if(W===n)return G;let r=M(n);if(!r)r=process.env.DOTENV_PRIVATE_KEY;if(!r)r=nn(n,t);return W=n,G=r,r}function Yn(){W=null,G=void 0,H=void 0}function _e(e,n={}){if(!U(e))return e;let t=tn(n);if(!t)return;try{let r=e.startsWith("enc:")?`encrypted:${e.slice(4)}`:e;return L(r,t)}catch{return}}function Te(e={}){let{path:n=[".env"],overload:t=!1,env:r,privateKey:i,keysFile:s=".env.keys",quiet:o=!1,cwd:d=process.cwd()}=e,c=Array.isArray(n)?n:[n],a=[],u=0,f=new Set(Object.entries(process.env).filter(([,g])=>g!==void 0&&!U(g)).map(([g])=>g)),E=i;if(!E&&s){let g=Y(d,s);if(F(g))try{let p=Z(g,"utf-8"),{parsed:T}=h(p);if(r){let v=`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`;E=T[v]||T.DOTENV_PRIVATE_KEY}else E=T.DOTENV_PRIVATE_KEY}catch(p){a.push(`Failed to load keys file: ${p instanceof Error?p.message:"Unknown error"}`)}}if(!E)E=M(r||"")||M();for(let g of c){let p=Y(d,g);if(!F(p)){if(!o)a.push(`File not found: ${p}`);continue}try{let T=Z(p,"utf-8"),{parsed:v,errors:y,skippedEncrypted:O}=h(T,{privateKey:E,processEnv:process.env});if(a.push(...y),O.length>0){for(let I of O)if(U(process.env[I]))delete process.env[I];let _=r?`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`:"DOTENV_PRIVATE_KEY",A=O.slice(0,5).join(", "),R=O.length>5?`, \u2026 +${O.length-5} more`:"";console.warn(`[env] warning: skipped ${O.length} encrypted value(s) in ${g} (${_} not set; defaults apply): ${A}${R}`)}let m=0;for(let[_,A]of Object.entries(v)){if(_==="DOTENV_PUBLIC_KEY")continue;let R=process.env[_],I=U(R)&&A!==R;if(t||!f.has(_)||I)process.env[_]=A,u++,m++}if(!o&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${m}/${Object.keys(v).length} variables from ${g}`),process.env.__ENV_LOADED__="1"}catch(T){a.push(`Failed to load ${p}: ${T instanceof Error?T.message:"Unknown error"}`)}}return{loaded:u,errors:a}}function rn(e={}){return{name:"env-plugin",setup(n){Te(e)}}}function Wn(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(H!==n)H=n,W=null,G=void 0;let r=[],i=(d)=>{if(!r.includes(d)&&F(Y(t,d)))r.push(d)};i(".env"),i(".env.local");let s=`.env.${n}`,o=`.env.${n}.local`;return i(s),i(o),Te({...e,path:r,env:n})}var Hn=rn;var ye={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 jn(e){return e}import{existsSync as P,readFileSync as N,renameSync as me,rmSync as Re,writeFileSync as D}from"fs";import{basename as ve,dirname as te,resolve as S}from"path";function Zn(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 Ae(e,n){return(!n.key||e.includes(n.key))&&(!n.excludeKey||!e.includes(n.excludeKey))}function sn(e,n){let t=[];for(let r of e.split(`
5
5
  `)){let i=r.trim();if(!i||i.startsWith("#"))continue;if(i.startsWith("DOTENV_PUBLIC_KEY"))continue;let s=i.match(/^([^=]+)=(.*)$/);if(!s||s[1]===void 0)continue;let o=s[1].trim();if(Ae(o,n))t.push(o)}return t}function Se(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(`
6
6
  `)){let d=o.trim();if(d.startsWith("#/"))continue;if(d.startsWith("DOTENV_PUBLIC_KEY"))continue;if(!d&&!s)continue;if(!d||d.startsWith("#")){s=!0,i.push(o);continue}s=!0;let c=d.match(/^([^=]+)=(.*)$/);if(!c||c[1]===void 0||c[2]===void 0){i.push(o);continue}let a=c[1].trim(),u=c[2].trim();if(u.startsWith('"')&&u.endsWith('"')||u.startsWith("'")&&u.endsWith("'"))u=u.slice(1,-1);if(Ae(a,r)&&!u.startsWith("encrypted:"))u=V(u,n);i.push(`${a}="${u}"`)}return i.join(`
7
7
  `)}function k(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 Ie(e){let{publicKeyName:n}=k(e);return n==="DOTENV_PUBLIC_KEY"?[n]:[n,"DOTENV_PUBLIC_KEY"]}function ne(e){let{privateKeyName:n}=k(e);return n==="DOTENV_PRIVATE_KEY"?[n]:[n,"DOTENV_PRIVATE_KEY"]}function Oe(e){for(let n of e.split(`
@@ -15,21 +15,21 @@ ${u}="${i}"
15
15
  ${f}="${s}"
16
16
  `;D(r,E,"utf-8")}let d=N(t,"utf-8"),c=Se(d,i,k(e.file).publicKeyName,e);if(e.stdout)return{success:!0,output:c};return D(t,c,"utf-8"),{success:!0,output:`\u2714 encrypted (${e.file||".env"})
17
17
  \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 cn(e={}){let n=e.cwd||process.cwd(),t=S(n,e.file||".env"),r=S(n,e.keysFile||".env.keys");if(!P(t))return{success:!1,error:`File not found: ${t}`};if(!P(r))return{success:!1,error:`Keys file not found: ${r}`};try{let i=N(r,"utf-8"),{parsed:s}=h(i),c=((e.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),a=c?`DOTENV_PRIVATE_KEY_${c}`:"DOTENV_PRIVATE_KEY",u=s[a];if(!u)return{success:!1,error:`Private key not found: ${a}`};let f=[u];if(a!=="DOTENV_PRIVATE_KEY"&&s.DOTENV_PRIVATE_KEY&&s.DOTENV_PRIVATE_KEY!==u)f.push(s.DOTENV_PRIVATE_KEY);let g=N(t,"utf-8").split(`
18
- `),p=[];for(let v of g){let T=v.trim();if(!T||T.startsWith("#")){p.push(v);continue}if(T.startsWith("DOTENV_PUBLIC_KEY"))continue;let O=T.match(/^([^=]+)=(.*)$/);if(!O||O[1]===void 0||O[2]===void 0){p.push(v);continue}let m=O[1].trim(),_=O[2].trim();if(_.startsWith('"')&&_.endsWith('"')||_.startsWith("'")&&_.endsWith("'"))_=_.slice(1,-1);let A=_.startsWith("encrypted:");if(e.key&&!m.includes(e.key))A=!1;if(A){let R,I;for(let H of f)try{R=L(_,H);break}catch(Le){I=Le}if(R===void 0)throw I instanceof Error?I:Error(`Unable to decrypt ${m}`);_=R}p.push(`${m}="${_}"`)}let y=p.join(`
19
- `);if(e.stdout)return{success:!0,output:y};return D(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 nt(e,n,t={}){let r=t.cwd||process.cwd(),i=S(r,t.file||".env"),s=t.keysFile?S(r,t.keysFile):S(te(i),".env.keys");try{let o="";if(P(i))o=N(i,"utf-8");let d=o.split(`
20
- `),c=!1,a,E=((t.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),g=E?`DOTENV_PUBLIC_KEY_${E}`:"DOTENV_PUBLIC_KEY",p=E?`DOTENV_PRIVATE_KEY_${E}`:"DOTENV_PRIVATE_KEY",y=on(o,t.file),v=y?.name??g;if(a=y?.key,a){let m=!1;if(P(s)){let A=N(s,"utf-8"),{parsed:R}=h(A);m=ne(t.file).some((I)=>Boolean(R[I]))}let _=ne(t.file).some((A)=>Boolean(process.env[A]));if(!m&&!_&&!Oe(o))a=void 0}if(!t.plain&&!a&&Oe(o))return{success:!1,error:`${t.file||".env"} already contains encrypted values, but no key that can be used to add another was found.
18
+ `),p=[];for(let v of g){let y=v.trim();if(!y||y.startsWith("#")){p.push(v);continue}if(y.startsWith("DOTENV_PUBLIC_KEY"))continue;let O=y.match(/^([^=]+)=(.*)$/);if(!O||O[1]===void 0||O[2]===void 0){p.push(v);continue}let m=O[1].trim(),_=O[2].trim();if(_.startsWith('"')&&_.endsWith('"')||_.startsWith("'")&&_.endsWith("'"))_=_.slice(1,-1);let A=_.startsWith("encrypted:");if(e.key&&!m.includes(e.key))A=!1;if(A){let R,I;for(let j of f)try{R=L(_,j);break}catch(Le){I=Le}if(R===void 0)throw I instanceof Error?I:Error(`Unable to decrypt ${m}`);_=R}p.push(`${m}="${_}"`)}let T=p.join(`
19
+ `);if(e.stdout)return{success:!0,output:T};return D(t,T,"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 nt(e,n,t={}){let r=t.cwd||process.cwd(),i=S(r,t.file||".env"),s=t.keysFile?S(r,t.keysFile):S(te(i),".env.keys");try{let o="";if(P(i))o=N(i,"utf-8");let d=o.split(`
20
+ `),c=!1,a,E=((t.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),g=E?`DOTENV_PUBLIC_KEY_${E}`:"DOTENV_PUBLIC_KEY",p=E?`DOTENV_PRIVATE_KEY_${E}`:"DOTENV_PRIVATE_KEY",T=on(o,t.file),v=T?.name??g;if(a=T?.key,a){let m=!1;if(P(s)){let A=N(s,"utf-8"),{parsed:R}=h(A);m=ne(t.file).some((I)=>Boolean(R[I]))}let _=ne(t.file).some((A)=>Boolean(process.env[A]));if(!m&&!_&&!Oe(o))a=void 0}if(!t.plain&&!a&&Oe(o))return{success:!1,error:`${t.file||".env"} already contains encrypted values, but no key that can be used to add another was found.
21
21
  Generating one would leave every existing encrypted value unreadable.
22
22
  Provide the private key as ${ne(t.file)[0]} in the environment, restore ${s}, or use --plain to write this value unencrypted.`};if(!t.plain&&!a){let m=x();a=m.publicKey;let _="";if(P(s))_=N(s,"utf-8");_+=`
23
23
  ${g}="${m.publicKey}"
24
24
  ${p}="${m.privateKey}"
25
- `,D(s,_,"utf-8");let A=Ie(t.file);for(let R=d.length-1;R>=0;R--){let I=d[R];if(I!==void 0&&A.some((H)=>I.trim().startsWith(`${H}=`)))d.splice(R,1)}d.unshift(`${v}="${a}"`)}let T=n;if(!t.plain&&a)T=V(n,a);for(let m=0;m<d.length;m++){let _=d[m];if(_===void 0)continue;if(_.trim().startsWith(`${e}=`)){d[m]=`${e}="${T}"`,c=!0;break}}if(!c)d.push(`${e}="${T}"`);let O=d.join(`
26
- `);return D(i,O,"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 tt(e,n={}){let t=n.cwd||process.cwd(),r=S(t,n.file||".env");if(!P(r))return{success:!1,error:`File not found: ${r}`};try{let i,s=S(t,n.keysFile||".env.keys"),c=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),a=c?`DOTENV_PRIVATE_KEY_${c}`:"DOTENV_PRIVATE_KEY";if(P(s)){let p=N(s,"utf-8"),{parsed:y}=h(p);i=y[a]}if(!i)i=process.env[a];let u=N(r,"utf-8"),{parsed:f}=h(u,{privateKey:i});if(e&&!n.all){let p=f[e];if(p===void 0)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:p}}let E=n.all?{...process.env,...f}:f,g;switch(n.format){case"shell":g=Object.entries(E).map(([p,y])=>`${p}=${y}`).join(" ");break;case"eval":g=Object.entries(E).map(([p,y])=>`${p}="${y}"`).join(`
25
+ `,D(s,_,"utf-8");let A=Ie(t.file);for(let R=d.length-1;R>=0;R--){let I=d[R];if(I!==void 0&&A.some((j)=>I.trim().startsWith(`${j}=`)))d.splice(R,1)}d.unshift(`${v}="${a}"`)}let y=n;if(!t.plain&&a)y=V(n,a);for(let m=0;m<d.length;m++){let _=d[m];if(_===void 0)continue;if(_.trim().startsWith(`${e}=`)){d[m]=`${e}="${y}"`,c=!0;break}}if(!c)d.push(`${e}="${y}"`);let O=d.join(`
26
+ `);return D(i,O,"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 tt(e,n={}){let t=n.cwd||process.cwd(),r=S(t,n.file||".env");if(!P(r))return{success:!1,error:`File not found: ${r}`};try{let i,s=S(t,n.keysFile||".env.keys"),c=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),a=c?`DOTENV_PRIVATE_KEY_${c}`:"DOTENV_PRIVATE_KEY";if(P(s)){let p=N(s,"utf-8"),{parsed:T}=h(p);i=T[a]}if(!i)i=process.env[a];let u=N(r,"utf-8"),{parsed:f}=h(u,{privateKey:i});if(e&&!n.all){let p=f[e];if(p===void 0)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:p}}let E=n.all?{...process.env,...f}:f,g;switch(n.format){case"shell":g=Object.entries(E).map(([p,T])=>`${p}=${T}`).join(" ");break;case"eval":g=Object.entries(E).map(([p,T])=>`${p}="${T}"`).join(`
27
27
  `);break;case"json":default:g=n.prettyPrint?JSON.stringify(E,null,2):JSON.stringify(E);break}return{success:!0,output:g}}catch(i){return{success:!1,error:`Failed to get: ${i instanceof Error?i.message:"Unknown error"}`}}}function rt(e,n={}){let t=n.cwd||process.cwd(),r=S(t,n.keysFile||".env.keys");if(!P(r))return{success:!1,error:`Keys file not found: ${r}`};try{let i=N(r,"utf-8"),{parsed:s}=h(i),c=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),a=c?`DOTENV_PUBLIC_KEY_${c}`:"DOTENV_PUBLIC_KEY",u=c?`DOTENV_PRIVATE_KEY_${c}`:"DOTENV_PRIVATE_KEY";if(e){let g=s[e];if(!g)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:g}}let f={[a]:s[a],[u]:s[u]};return{success:!0,output:n.format==="shell"?`${a}=${f[a]} ${u}=${f[u]}`:JSON.stringify(f)}}catch(i){return{success:!1,error:`Failed to get keypair: ${i instanceof Error?i.message:"Unknown error"}`}}}function an(e,n,t,r){let i=e.split(`
28
28
  `),s=!1,o=!1;for(let d=0;d<i.length;d++){let c=i[d];if(c===void 0)continue;if(c.startsWith(`${t}=`))i[d]=`${t}="${n.publicKey}"`,s=!0;else if(c.startsWith(`${r}=`))i[d]=`${r}="${n.privateKey}"`,o=!0}if(!s)i.push(`${t}="${n.publicKey}"`);if(!o)i.push(`${r}="${n.privateKey}"`);return`${i.join(`
29
29
  `).replace(/\n+$/,"")}
30
- `}function it(e={}){let n=cn({file:e.file,keysFile:e.keysFile,stdout:!0,cwd:e.cwd});if(!n.success)return n;if(n.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let t=e.cwd||process.cwd(),r=e.file||".env",i=e.keysFile||".env.keys",s=S(t,r),o=S(t,i),{publicKeyName:d,privateKeyName:c}=k(e.file);if(e.dryRun){let T=sn(n.output,e);return{success:!0,output:["Dry run: nothing was written.",`${r}: ${T.length} value${T.length===1?"":"s"} would be re-encrypted under a new ${d}.`,`${i}: ${d} and ${c} would be replaced.`,`The current ${c} decrypts nothing once that happens, so keep a copy until the rotation is verified.`].join(`
30
+ `}function it(e={}){let n=cn({file:e.file,keysFile:e.keysFile,stdout:!0,cwd:e.cwd});if(!n.success)return n;if(n.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let t=e.cwd||process.cwd(),r=e.file||".env",i=e.keysFile||".env.keys",s=S(t,r),o=S(t,i),{publicKeyName:d,privateKeyName:c}=k(e.file);if(e.dryRun){let y=sn(n.output,e);return{success:!0,output:["Dry run: nothing was written.",`${r}: ${y.length} value${y.length===1?"":"s"} would be re-encrypted under a new ${d}.`,`${i}: ${d} and ${c} would be replaced.`,`The current ${c} decrypts nothing once that happens, so keep a copy until the rotation is verified.`].join(`
31
31
  `)}}let a=x(),u=Se(n.output,a.publicKey,d,e);if(e.stdout)return{success:!0,output:u,notice:[`Nothing was written. To adopt the output above, put this keypair in ${i}:`,"",`${d}="${a.publicKey}"`,`${c}="${a.privateKey}"`,"","Without it that output cannot be decrypted. Run without --stdout to have both applied together."].join(`
32
- `)};let f=N(s),E=N(o),g=an(E.toString("utf8"),a,d,c),p=`${process.pid}-${Date.now()}`,y=S(te(s),`.${ve(s)}.${p}.rotate`),v=S(te(o),`.${ve(o)}.${p}.rotate`);try{D(y,u,{encoding:"utf8",mode:384}),D(v,g,{encoding:"utf8",mode:384}),me(y,s);try{me(v,o)}catch(T){throw D(s,f),T}return{success:!0,output:`\u2714 rotated (${r}) to encrypted:v2`}}catch(T){if(!P(o))D(o,E,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${T instanceof Error?T.message:"Unknown error"}`}}finally{Re(y,{force:!0}),Re(v,{force:!0})}}function un(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}var fn=/(?:^|_)(?:PASSWORD|PASSWD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|CREDENTIALS|PRIVATE|SALT|DSN|CERT|CIPHER|SIGNATURE|WEBHOOK)(?:_|$)|(?:^|_)(?:ACCESS|SECRET)_KEY(?:_|$)|_KEY$/,ln=/^DOTENV_(?:PUBLIC|PRIVATE)_KEY/,En=/^\$\{[^}]+\}$/;function ot(e,n={}){let{placeholders:t={},strict:r=!1}=n,i=[];for(let[s,o]of Object.entries(e)){if(!o||o.trim()==="")continue;if(un(o))continue;if(ln.test(s))continue;if(En.test(o.trim()))continue;let d=fn.test(s),c=Object.prototype.hasOwnProperty.call(t,s)&&t[s]===o;if(d&&!c){i.push({key:s,reason:"plaintext-secret"});continue}if(r)i.push({key:s,reason:"plaintext-value"})}return i}function dt(e){return e.split(`
33
- `).map((n)=>n.trim()).filter(Boolean).filter((n)=>n!==".env.example").filter((n)=>/(?:^|\/)\.env(?:\.|$)/.test(n)).filter((n)=>!n.endsWith(".env.keys"))}function Ne(e){return`${e.replace(/[^a-z0-9]/gi,"").toUpperCase()}_`}function pn(e,n={}){let t=n.self?Ne(n.self):void 0,r=(n.tenants??[]).filter((o)=>typeof o==="string"&&o.trim().length>0).map((o)=>({slug:o,prefix:Ne(o)})).filter(({prefix:o})=>o!==t).sort((o,d)=>d.prefix.length-o.prefix.length),i={},s={};for(let[o,d]of Object.entries(e)){let c=r.find(({prefix:a})=>o.startsWith(a));if(c){(s[c.slug]??={})[o]=d;continue}i[o]=d}return{own:i,foreign:s}}function at(e,n={}){return pn(e,n).own}function ut(e){return Object.entries(e.foreign).map(([n,t])=>({tenant:n,keys:Object.keys(t).sort()})).sort((n,t)=>n.tenant.localeCompare(t.tenant))}import l from"process";import{platform as gn}from"os";var he=typeof Bun<"u",Pe=typeof l<"u"&&l.versions?.node!==void 0,_n=he?"bun":Pe?"node":"unknown",pt={name:_n,version:he?Bun.version:Pe?l.version:void 0},ie=gn(),gt=ie==="win32",_t=ie==="darwin",yt=ie==="linux",De=Boolean(l.stdout?.isTTY),Tt=typeof globalThis.window<"u";function w(){return l.env.APP_ENV||l.env.NODE_ENV||"local"}function mt(){return w()==="local"}function Rt(){let e=w();return e==="development"||e==="dev"||e==="local"}function vt(){return w()==="staging"}function Ot(){let e=w();return e==="production"||e==="prod"}function At(){let e=w();return e==="test"||e==="testing"}var yn=Boolean(l.env.CI||l.env.CONTINUOUS_INTEGRATION||l.env.BUILD_NUMBER||l.env.RUN_ID),St=Boolean(l.env.DEBUG||l.env.VERBOSE||l.argv.includes("--debug")||l.argv.includes("--verbose")),Tn=yn||!De,It=Boolean(!Tn&&(De||l.env.COLORTERM||l.env.FORCE_COLOR||l.env.TERM&&l.env.TERM!=="dumb")),re={github:{name:"GitHub Actions",detected:Boolean(l.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(l.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(l.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(l.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(l.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(l.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(l.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(l.env.DYNO)},aws:{name:"AWS",detected:Boolean(l.env.AWS_REGION||l.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(l.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(l.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(l.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(l.env.RENDER)}},mn=Object.keys(re).find((e)=>{let n=re[e];return n!==void 0&&n.detected})||"unknown",Nt=re[mn]||{name:"Unknown",detected:!1};var be=new Set;function On(e){if(be.has(e))return;be.add(e);let n=ee();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 An={get:(e,n)=>{let t=e,r=t[n];if(typeof r==="string"&&(r.startsWith("encrypted:")||r.startsWith("enc:"))){let s=_e(r);if(s===void 0){On(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},set:(e,n,t)=>Reflect.set(e,n,t,e),deleteProperty:(e,n)=>Reflect.deleteProperty(e,n)};function Sn(){return typeof Bun<"u"?Bun.env:Rn.env}var Ke=new Proxy(Sn(),An);function Lt(e,n,t){let r=t?.path||vn(".env"),s=Ce.readFileSync(r,"utf-8").split(`
32
+ `)};let f=N(s),E=N(o),g=an(E.toString("utf8"),a,d,c),p=`${process.pid}-${Date.now()}`,T=S(te(s),`.${ve(s)}.${p}.rotate`),v=S(te(o),`.${ve(o)}.${p}.rotate`);try{D(T,u,{encoding:"utf8",mode:384}),D(v,g,{encoding:"utf8",mode:384}),me(T,s);try{me(v,o)}catch(y){throw D(s,f),y}return{success:!0,output:`\u2714 rotated (${r}) to encrypted:v2`}}catch(y){if(!P(o))D(o,E,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${y instanceof Error?y.message:"Unknown error"}`}}finally{Re(T,{force:!0}),Re(v,{force:!0})}}function un(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}var fn=/(?:^|_)(?:PASSWORD|PASSWD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|CREDENTIALS|PRIVATE|SALT|DSN|CERT|CIPHER|SIGNATURE|WEBHOOK)(?:_|$)|(?:^|_)(?:ACCESS|SECRET)_KEY(?:_|$)|_KEY$/,ln=/^DOTENV_(?:PUBLIC|PRIVATE)_KEY/,En=/^\$\{[^}]+\}$/;function ot(e,n={}){let{placeholders:t={},strict:r=!1}=n,i=[];for(let[s,o]of Object.entries(e)){if(!o||o.trim()==="")continue;if(un(o))continue;if(ln.test(s))continue;if(En.test(o.trim()))continue;let d=fn.test(s),c=Object.prototype.hasOwnProperty.call(t,s)&&t[s]===o;if(d&&!c){i.push({key:s,reason:"plaintext-secret"});continue}if(r)i.push({key:s,reason:"plaintext-value"})}return i}function dt(e){return e.split(`
33
+ `).map((n)=>n.trim()).filter(Boolean).filter((n)=>n!==".env.example").filter((n)=>/(?:^|\/)\.env(?:\.|$)/.test(n)).filter((n)=>!n.endsWith(".env.keys"))}function Ne(e){return`${e.replace(/[^a-z0-9]/gi,"").toUpperCase()}_`}function pn(e,n={}){let t=n.self?Ne(n.self):void 0,r=(n.tenants??[]).filter((o)=>typeof o==="string"&&o.trim().length>0).map((o)=>({slug:o,prefix:Ne(o)})).filter(({prefix:o})=>o!==t).sort((o,d)=>d.prefix.length-o.prefix.length),i={},s={};for(let[o,d]of Object.entries(e)){let c=r.find(({prefix:a})=>o.startsWith(a));if(c){(s[c.slug]??={})[o]=d;continue}i[o]=d}return{own:i,foreign:s}}function at(e,n={}){return pn(e,n).own}function ut(e){return Object.entries(e.foreign).map(([n,t])=>({tenant:n,keys:Object.keys(t).sort()})).sort((n,t)=>n.tenant.localeCompare(t.tenant))}import l from"process";import{platform as gn}from"os";var he=typeof Bun<"u",Pe=typeof l<"u"&&l.versions?.node!==void 0,_n=he?"bun":Pe?"node":"unknown",pt={name:_n,version:he?Bun.version:Pe?l.version:void 0},ie=gn(),gt=ie==="win32",_t=ie==="darwin",Tt=ie==="linux",De=Boolean(l.stdout?.isTTY),yt=typeof globalThis.window<"u";function w(){return l.env.APP_ENV||l.env.NODE_ENV||"local"}function mt(){return w()==="local"}function Rt(){let e=w();return e==="development"||e==="dev"||e==="local"}function vt(){return w()==="staging"}function Ot(){let e=w();return e==="production"||e==="prod"}function At(){let e=w();return e==="test"||e==="testing"}var Tn=Boolean(l.env.CI||l.env.CONTINUOUS_INTEGRATION||l.env.BUILD_NUMBER||l.env.RUN_ID),St=Boolean(l.env.DEBUG||l.env.VERBOSE||l.argv.includes("--debug")||l.argv.includes("--verbose")),yn=Tn||!De,It=Boolean(!yn&&(De||l.env.COLORTERM||l.env.FORCE_COLOR||l.env.TERM&&l.env.TERM!=="dumb")),re={github:{name:"GitHub Actions",detected:Boolean(l.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(l.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(l.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(l.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(l.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(l.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(l.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(l.env.DYNO)},aws:{name:"AWS",detected:Boolean(l.env.AWS_REGION||l.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(l.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(l.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(l.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(l.env.RENDER)}},mn=Object.keys(re).find((e)=>{let n=re[e];return n!==void 0&&n.detected})||"unknown",Nt=re[mn]||{name:"Unknown",detected:!1};var be=new Set;function On(e){if(be.has(e))return;be.add(e);let n=ee();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 An={get:(e,n)=>{let t=e,r=t[n];if(typeof r==="string"&&(r.startsWith("encrypted:")||r.startsWith("enc:"))){let s=_e(r);if(s===void 0){On(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},set:(e,n,t)=>Reflect.set(e,n,t,e),deleteProperty:(e,n)=>Reflect.deleteProperty(e,n)};function Sn(){return typeof Bun<"u"?Bun.env:Rn.env}var Ke=new Proxy(Sn(),An);function Lt(e,n,t){let r=t?.path||vn(".env"),s=Ce.readFileSync(r,"utf-8").split(`
34
34
  `),o=s.findIndex((a)=>a.startsWith(`${e}=`)),c=/[\s"'#$\\]/.test(n)?`"${n.replace(/"/g,"\\\"")}"`:n;if(o!==-1)s[o]=`${e}=${c}`;else s.push(`${e}=${c}`);Ce.writeFileSync(r,s.join(`
35
- `))}function kt(e=Ke){let n=[],t=e;for(let[r,i]of Object.entries(Te)){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 xt(e,n=Ke){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{fn as SECRET_NAME_PATTERN,ee as activeEnvName,xe as aesDecrypt,Nn as aesEncrypt,w as appEnv,Wn as autoLoadEnv,on as declaredEnvPublicKey,cn as decryptEnv,_e as decryptEnvValue,L as decryptValue,jn as defineEnv,et as encryptEnv,V as encryptValue,Ke as env,Te as envEnum,Oe as envHasCiphertext,k as envKeyNames,rn as envPlugin,ne as envPrivateKeyNames,Ie as envPublicKeyNames,ut as foreignTenantKeys,x as generateKeypair,tt as getEnv,rt as getKeypair,J as getPrivateKey,De as hasTTY,Tt as hasWindow,he as isBun,yn as isCI,It as isColorSupported,St as isDebug,Rt as isDevelopment,un as isEncryptedValue,hn as isLegacyEncryptedValue,yt as isLinux,mt as isLocal,_t as isMacOS,Tn as isMinimal,Pe as isNode,Ot as isProduction,vt as isStaging,At as isTesting,gt as isWindows,ye as loadEnv,Un as loadEnvFiles,Pn as migrateEncryptedValue,h as parse,Dn as parseEnvFromKey,pn as partitionTenantEnv,ot as plaintextSecrets,ie as platform,Sn as process,mn as provider,Nt as providerInfo,xt as requireEnv,Yn as resetPrivateKeyCache,Zn as resolveEnvFile,tn as resolvePrivateKey,dn as reusableEnvPublicKey,it as rotateKeypair,_n as runtime,pt as runtimeInfo,nt as setEnv,at as stripForeignTenantEnv,Ne as tenantEnvPrefix,dt as trackedEnvFiles,kt as validateEnv,Lt as writeEnv};
35
+ `))}function kt(e=Ke){let n=[],t=e;for(let[r,i]of Object.entries(ye)){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 xt(e,n=Ke){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{fn as SECRET_NAME_PATTERN,ee as activeEnvName,xe as aesDecrypt,Nn as aesEncrypt,w as appEnv,Wn as autoLoadEnv,on as declaredEnvPublicKey,cn as decryptEnv,_e as decryptEnvValue,L as decryptValue,jn as defineEnv,et as encryptEnv,V as encryptValue,Ke as env,ye as envEnum,Oe as envHasCiphertext,k as envKeyNames,rn as envPlugin,ne as envPrivateKeyNames,Ie as envPublicKeyNames,ut as foreignTenantKeys,x as generateKeypair,tt as getEnv,rt as getKeypair,M as getPrivateKey,De as hasTTY,yt as hasWindow,he as isBun,Tn as isCI,It as isColorSupported,St as isDebug,Rt as isDevelopment,un as isEncryptedValue,hn as isLegacyEncryptedValue,Tt as isLinux,mt as isLocal,_t as isMacOS,yn as isMinimal,Pe as isNode,Ot as isProduction,vt as isStaging,At as isTesting,gt as isWindows,Te as loadEnv,Un as loadEnvFiles,Pn as migrateEncryptedValue,h as parse,Dn as parseEnvFromKey,pn as partitionTenantEnv,ot as plaintextSecrets,ie as platform,Sn as process,mn as provider,Nt as providerInfo,xt as requireEnv,Yn as resetPrivateKeyCache,Zn as resolveEnvFile,tn as resolvePrivateKey,dn as reusableEnvPublicKey,it as rotateKeypair,_n as runtime,pt as runtimeInfo,nt as setEnv,at as stripForeignTenantEnv,Ne as tenantEnvPrefix,dt as trackedEnvFiles,kt as validateEnv,Lt as writeEnv};
package/dist/plugin.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // @bun
2
- import{existsSync as O}from"fs";import{readFileSync as L}from"fs";import{resolve as T}from"path";import{createCipheriv as U,createDecipheriv as j,createHash as R,createPrivateKey as ne,createPublicKey as X,diffieHellman as z,generateKeyPairSync as H,hkdfSync as q,randomBytes as S}from"crypto";var I="x25519-public:",C="x25519-private:",N="encrypted:v2:",F="encrypted:",M=Buffer.from("stacks-env:v2","utf8"),G="Environment decryption failed: authentication or format error";function J(e){if(e.length===32)return e;return R("sha256").update(e).digest()}function Ne(e,t){let n=S(16),r=U("aes-256-gcm",J(t),n);return{ciphertext:Buffer.concat([r.update(e,"utf8"),r.final()]).toString("hex"),iv:n.toString("hex"),authTag:r.getAuthTag().toString("hex")}}function re(e,t,n,r){try{let s=j("aes-256-gcm",J(t),Buffer.from(n,"hex"));return s.setAuthTag(Buffer.from(r,"hex")),Buffer.concat([s.update(Buffer.from(e,"hex")),s.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function h(e){return e.toString("base64url")}function y(e,t,n){if(typeof e!=="string"||e.length>0&&!/^[A-Za-z0-9_-]+$/.test(e))throw Error(`${t} is not canonical base64url`);let r=Buffer.from(e,"base64url");if(h(r)!==e)throw Error(`${t} is not canonical base64url`);if(n!==void 0&&r.length!==n)throw Error(`${t} has an invalid length`);return r}function se(e){if(!e.startsWith(I))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let t=X({key:y(e.slice(I.length),"public key"),format:"der",type:"spki"});if(t.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return t}function ie(e){if(!e.startsWith(C))throw Error("Private key is not a version 2 X25519 key");let t=ne({key:y(e.slice(C.length),"private key"),format:"der",type:"pkcs8"});if(t.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return t}function Z(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function Oe(){let{publicKey:e,privateKey:t}=H("x25519"),n=e.export({format:"der",type:"spki"}),r=t.export({format:"der",type:"pkcs8"});return{publicKey:`${I}${h(n)}`,privateKey:`${C}${h(r)}`}}function oe(e,t){let n=se(t),r=H("x25519"),s=h(r.publicKey.export({format:"der",type:"spki"})),i=S(16),c=S(12),a=z({privateKey:r.privateKey,publicKey:n}),u=Buffer.from(q("sha256",a,i,M,32)),f={epk:s,salt:h(i),nonce:h(c)};try{let o=U("aes-256-gcm",u,c);o.setAAD(Z(f));let p=Buffer.concat([o.update(e,"utf8"),o.final()]),l={v:2,...f,ciphertext:h(p),tag:h(o.getAuthTag())};return`${N}${h(Buffer.from(JSON.stringify(l),"utf8"))}`}finally{a.fill(0),u.fill(0)}}function ce(e){let t=y(e.slice(N.length),"envelope").toString("utf8"),n=JSON.parse(t);if(!n||typeof n!=="object"||Array.isArray(n))throw Error("envelope is not an object");let r=Object.keys(n).sort(),s=["ciphertext","epk","nonce","salt","tag","v"];if(r.length!==s.length||r.some((i,c)=>i!==s[c]))throw Error("envelope fields are invalid");if(n.v!==2)throw Error("envelope version is unsupported");for(let i of["epk","salt","nonce","ciphertext","tag"])if(typeof n[i]!=="string")throw Error(`${i} is invalid`);return n}function ae(e,t){try{let n=ce(e),r=ie(t),s=X({key:y(n.epk,"ephemeral public key"),format:"der",type:"spki"});if(s.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let i=y(n.salt,"salt",16),c=y(n.nonce,"nonce",12),a=y(n.ciphertext,"ciphertext"),u=y(n.tag,"tag",16),f=z({privateKey:r,publicKey:s}),o=Buffer.from(q("sha256",f,i,M,32));try{let p=j("aes-256-gcm",o,c);return p.setAAD(Z(n)),p.setAuthTag(u),Buffer.concat([p.update(a),p.final()]).toString("utf8")}finally{f.fill(0),o.fill(0)}}catch{throw Error(G)}}function fe(e,t){try{if(!/^[0-9a-f]{64}$/.test(t))throw Error("invalid legacy private key");let n=Buffer.from(e.slice(F.length),"base64");if(n.length<32)throw Error("invalid legacy payload");let r=R("sha256").update(Buffer.from(t,"hex")).digest(),s=R("sha256").update(r).digest();return re(n.subarray(32).toString("hex"),s,n.subarray(0,16).toString("hex"),n.subarray(16,32).toString("hex"))}catch{throw Error(G)}}function Te(e){return e.startsWith(F)&&!e.startsWith(N)}function k(e,t){if(!e.startsWith(F))return e;return e.startsWith(N)?ae(e,t):fe(e,t)}function De(e,t,n){return oe(k(e,t),n)}function Be(e){let t=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return t&&t[1]?t[1].toLowerCase():""}function W(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as ue}from"child_process";import{readFileSync as pe}from"fs";import{arch as de,hostname as le,platform as Q,release as ge,userInfo as he}from"os";import{basename as ye,dirname as me,resolve as ve}from"path";function x(e,t={}){let n={},r=[],s=[],i=e.split(`
2
+ import{existsSync as T}from"fs";import{readFileSync as Y}from"fs";import{resolve as D}from"path";import{createCipheriv as U,createDecipheriv as j,createHash as S,createPrivateKey as ne,createPublicKey as X,diffieHellman as z,generateKeyPairSync as H,hkdfSync as q,randomBytes as I}from"crypto";var C="x25519-public:",F="x25519-private:",N="encrypted:v2:",W="encrypted:",M=Buffer.from("stacks-env:v2","utf8"),G="Environment decryption failed: authentication or format error";function J(e){if(e.length===32)return e;return S("sha256").update(e).digest()}function Ne(e,t){let n=I(16),r=U("aes-256-gcm",J(t),n);return{ciphertext:Buffer.concat([r.update(e,"utf8"),r.final()]).toString("hex"),iv:n.toString("hex"),authTag:r.getAuthTag().toString("hex")}}function re(e,t,n,r){try{let s=j("aes-256-gcm",J(t),Buffer.from(n,"hex"));return s.setAuthTag(Buffer.from(r,"hex")),Buffer.concat([s.update(Buffer.from(e,"hex")),s.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function h(e){return e.toString("base64url")}function m(e,t,n){if(typeof e!=="string"||e.length>0&&!/^[A-Za-z0-9_-]+$/.test(e))throw Error(`${t} is not canonical base64url`);let r=Buffer.from(e,"base64url");if(h(r)!==e)throw Error(`${t} is not canonical base64url`);if(n!==void 0&&r.length!==n)throw Error(`${t} has an invalid length`);return r}function se(e){if(!e.startsWith(C))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let t=X({key:m(e.slice(C.length),"public key"),format:"der",type:"spki"});if(t.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return t}function ie(e){if(!e.startsWith(F))throw Error("Private key is not a version 2 X25519 key");let t=ne({key:m(e.slice(F.length),"private key"),format:"der",type:"pkcs8"});if(t.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return t}function Z(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function Oe(){let{publicKey:e,privateKey:t}=H("x25519"),n=e.export({format:"der",type:"spki"}),r=t.export({format:"der",type:"pkcs8"});return{publicKey:`${C}${h(n)}`,privateKey:`${F}${h(r)}`}}function oe(e,t){let n=se(t),r=H("x25519"),s=h(r.publicKey.export({format:"der",type:"spki"})),i=I(16),c=I(12),a=z({privateKey:r.privateKey,publicKey:n}),u=Buffer.from(q("sha256",a,i,M,32)),f={epk:s,salt:h(i),nonce:h(c)};try{let o=U("aes-256-gcm",u,c);o.setAAD(Z(f));let p=Buffer.concat([o.update(e,"utf8"),o.final()]),l={v:2,...f,ciphertext:h(p),tag:h(o.getAuthTag())};return`${N}${h(Buffer.from(JSON.stringify(l),"utf8"))}`}finally{a.fill(0),u.fill(0)}}function ce(e){let t=m(e.slice(N.length),"envelope").toString("utf8"),n=JSON.parse(t);if(!n||typeof n!=="object"||Array.isArray(n))throw Error("envelope is not an object");let r=Object.keys(n).sort(),s=["ciphertext","epk","nonce","salt","tag","v"];if(r.length!==s.length||r.some((i,c)=>i!==s[c]))throw Error("envelope fields are invalid");if(n.v!==2)throw Error("envelope version is unsupported");for(let i of["epk","salt","nonce","ciphertext","tag"])if(typeof n[i]!=="string")throw Error(`${i} is invalid`);return n}function ae(e,t){try{let n=ce(e),r=ie(t),s=X({key:m(n.epk,"ephemeral public key"),format:"der",type:"spki"});if(s.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let i=m(n.salt,"salt",16),c=m(n.nonce,"nonce",12),a=m(n.ciphertext,"ciphertext"),u=m(n.tag,"tag",16),f=z({privateKey:r,publicKey:s}),o=Buffer.from(q("sha256",f,i,M,32));try{let p=j("aes-256-gcm",o,c);return p.setAAD(Z(n)),p.setAuthTag(u),Buffer.concat([p.update(a),p.final()]).toString("utf8")}finally{f.fill(0),o.fill(0)}}catch{throw Error(G)}}function fe(e,t){try{if(!/^[0-9a-f]{64}$/.test(t))throw Error("invalid legacy private key");let n=Buffer.from(e.slice(W.length),"base64");if(n.length<32)throw Error("invalid legacy payload");let r=S("sha256").update(Buffer.from(t,"hex")).digest(),s=S("sha256").update(r).digest();return re(n.subarray(32).toString("hex"),s,n.subarray(0,16).toString("hex"),n.subarray(16,32).toString("hex"))}catch{throw Error(G)}}function Te(e){return e.startsWith(W)&&!e.startsWith(N)}function k(e,t){if(!e.startsWith(W))return e;return e.startsWith(N)?ae(e,t):fe(e,t)}function De(e,t,n){return oe(k(e,t),n)}function Be(e){let t=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return t&&t[1]?t[1].toLowerCase():""}function O(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as ue}from"child_process";import{readFileSync as pe}from"fs";import{arch as de,hostname as le,platform as Q,release as ge,userInfo as he}from"os";import{basename as ye,dirname as me,resolve as Ee}from"path";function _(e,t={}){let n={},r=[],s=[],i=e.split(`
3
3
  `);for(let c of i){let a=c.trim();if(!a||a.startsWith("#"))continue;if(a.startsWith("DOTENV_PUBLIC_KEY=")){let p=a.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(p&&p[1]!==void 0)n.DOTENV_PUBLIC_KEY=p[1];continue}let u=a.match(/^([^=]+)=(.*)$/);if(!u||u[1]===void 0||u[2]===void 0)continue;let f=u[1].trim(),o=u[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(!t.privateKey){s.push(f);continue}try{let p=o.startsWith("enc:")?`encrypted:${o.slice(4)}`:o;o=k(p,t.privateKey)}catch(p){r.push(`Failed to decrypt ${f}: ${p instanceof Error?p.message:"Unknown error"}`)}}o=Ee(o,{...t.processEnv||process.env,...n}),o=_e(o),n[f]=o}return{parsed:n,errors:r,skippedEncrypted:s}}function Ee(e,t){return e.replace(/\$\{([^}]+)\}/g,(n,r)=>{let s=r.match(/^([^:\-+]+)(:-|-)(.+)$/);if(s){let[,c,a,u]=s,f=t[c];if(a===":-")return f||u;else return f!==void 0?f:u}let i=r.match(/^([^:\-+]+)(:?\+)(.+)$/);if(i){let[,c,a,u]=i,f=t[c];if(a===":+")return f?u:"";else return f!==void 0?u:""}return t[r]||""})}var we=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function ke(e){let t=[],n="",r=null,s=!1;for(let i of e.trim()){if(s){n+=i,s=!1;continue}if(i==="\\"&&r!=="'"){s=!0;continue}if(r){if(i===r)r=null;else n+=i;continue}if(i==='"'||i==="'"){r=i;continue}if(/\s/.test(i)){if(n)t.push(n),n="";continue}n+=i}if(s||r)throw Error("Command substitution contains an unterminated escape or quote");if(n)t.push(n);return t}function xe(e){let[t,...n]=e,r=n.some((s)=>s.startsWith("-"));if(t==="echo"&&!r)return n.join(" ");if(t==="printf"&&n.length===1&&!n[0]?.includes("%"))return n[0];if(t==="hostname"&&n.length===0)return le();if(t==="whoami"&&n.length===0)return he().username;if(t==="pwd"&&n.length===0)return process.cwd();if(t==="basename"&&n.length===1)return ye(n[0]??"");if(t==="dirname"&&n.length===1)return me(n[0]??"");if(t==="cat"&&n.length>0&&!r)return n.map((s)=>pe(ve(process.cwd(),s),"utf8")).join("");if(t==="uname"){let s=n[0]??"-s";if(n.length<=1&&s==="-m")return de();if(n.length<=1&&s==="-r")return ge();if(n.length<=1&&s==="-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"}[Q()]??Q()}return}function _e(e){return e.replace(/\$\(([^)]+)\)/g,(t,n)=>{try{let r=ke(n),s=r[0];if(!s||!we.has(s))return console.warn(`[env] Blocked command substitution for disallowed command: ${s}`),"";let i=xe(r);if(i!==void 0)return i.trim();let c=ue(s,r.slice(1),{encoding:"utf8",env:process.env});if(c.status===0)return c.stdout.trim();let a=c.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${c.status??"unknown"}): ${s}${a?`: ${a}`:""}`)}catch(r){console.warn(`[env] Command substitution error: ${r instanceof Error?r.message:String(r)}`)}return""})}async function We(e,t={}){let n={},r=[],s=[];for(let i of e)try{let c=Bun.file(i);if(!c.size)continue;let a=await c.text(),{parsed:u,errors:f,skippedEncrypted:o}=x(a,t);r.push(...f),s.push(...o);for(let[p,l]of Object.entries(u)){if(!t.overload&&n[p]!==void 0)continue;n[p]=l}}catch(c){if(c?.code==="ENOENT")continue;r.push(`Failed to read ${i}: ${c instanceof Error?c.message:String(c)}`)}return{parsed:n,errors:r,skippedEncrypted:s}}function _(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}function m(e){if(!e||_(e))return;let t=e.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(t))return;if(t==="prod")return"production";if(t==="stage")return"staging";if(t==="dev")return"development";return t}var D=null,B,A;function Pe(e,t,n=".env.keys"){let r=T(t,n);if(!O(r))return;try{let{parsed:s}=x(L(r,"utf-8"));if(e){let i=s[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`];if(i)return i}return s.DOTENV_PRIVATE_KEY}catch{return}}function be(e={}){return m(e.env)||A||m("development")||m(process.env.DOTENV_ENV)||m(process.env.APP_ENV)||"development"}function Ke(e={}){let t=be(e),n=e.cwd||process.cwd();if(D===t)return B;let r=W(t);if(!r)r=process.env.DOTENV_PRIVATE_KEY;if(!r)r=Pe(t,n);return D=t,B=r,r}function He(){D=null,B=void 0,A=void 0}function qe(e,t={}){if(!_(e))return e;let n=Ke(t);if(!n)return;try{let r=e.startsWith("enc:")?`encrypted:${e.slice(4)}`:e;return k(r,n)}catch{return}}function ee(e={}){let{path:t=[".env"],overload:n=!1,env:r,privateKey:s,keysFile:i=".env.keys",quiet:c=!1,cwd:a=process.cwd()}=e,u=Array.isArray(t)?t:[t],f=[],o=0,p=new Set(Object.entries(process.env).filter(([,d])=>d!==void 0&&!_(d)).map(([d])=>d)),l=s;if(!l&&i){let d=T(a,i);if(O(d))try{let g=L(d,"utf-8"),{parsed:v}=x(g);if(r){let P=`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`;l=v[P]}else l=v.DOTENV_PRIVATE_KEY}catch(g){f.push(`Failed to load keys file: ${g instanceof Error?g.message:"Unknown error"}`)}}if(!l)l=W(r||"");for(let d of u){let g=T(a,d);if(!O(g)){if(!c)f.push(`File not found: ${g}`);continue}try{let v=L(g,"utf-8"),{parsed:P,errors:te,skippedEncrypted:E}=x(v,{privateKey:l,processEnv:process.env});if(f.push(...te),E.length>0){for(let V of E)if(_(process.env[V]))delete process.env[V];let w=r?`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`:"DOTENV_PRIVATE_KEY",b=E.slice(0,5).join(", "),K=E.length>5?`, \u2026 +${E.length-5} more`:"";console.warn(`[env] warning: skipped ${E.length} encrypted value(s) in ${d} (${w} not set; defaults apply): ${b}${K}`)}let Y=0;for(let[w,b]of Object.entries(P)){if(w==="DOTENV_PUBLIC_KEY")continue;let K=process.env[w],V=_(K)&&b!==K;if(n||!p.has(w)||V)process.env[w]=b,o++,Y++}if(!c&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${Y}/${Object.keys(P).length} variables from ${d}`),process.env.__ENV_LOADED__="1"}catch(v){f.push(`Failed to load ${g}: ${v instanceof Error?v.message:"Unknown error"}`)}}return{loaded:o,errors:f}}function $e(e={}){return{name:"env-plugin",setup(t){ee(e)}}}function Me(e={}){let t=m(e.env)||m("development")||m(process.env.DOTENV_ENV)||m(process.env.APP_ENV)||"development",n=e.cwd||process.cwd();if(A!==t)A=t,D=null,B=void 0;let r=[],s=(a)=>{if(!r.includes(a)&&O(T(n,a)))r.push(a)};s(".env"),s(".env.local");let i=`.env.${t}`,c=`.env.${t}.local`;return s(i),s(c),ee({...e,path:r,env:t})}var Je=$e;export{be as activeEnvName,Me as autoLoadEnv,qe as decryptEnvValue,Je as default,$e as envPlugin,ee as loadEnv,He as resetPrivateKeyCache,Ke as resolvePrivateKey};
4
+ `)}if(o.startsWith("encrypted:")||o.startsWith("enc:")){if(!t.privateKey){s.push(f);continue}try{let p=o.startsWith("enc:")?`encrypted:${o.slice(4)}`:o;o=k(p,t.privateKey)}catch(p){r.push(`Failed to decrypt ${f}: ${p instanceof Error?p.message:"Unknown error"}`)}}o=ve(o,{...t.processEnv||process.env,...n}),o=xe(o),n[f]=o}return{parsed:n,errors:r,skippedEncrypted:s}}function ve(e,t){return e.replace(/\$\{([^}]+)\}/g,(n,r)=>{let s=r.match(/^([^:\-+]+)(:-|-)(.+)$/);if(s){let[,c,a,u]=s,f=t[c];if(a===":-")return f||u;else return f!==void 0?f:u}let i=r.match(/^([^:\-+]+)(:?\+)(.+)$/);if(i){let[,c,a,u]=i,f=t[c];if(a===":+")return f?u:"";else return f!==void 0?u:""}return t[r]||""})}var we=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function ke(e){let t=[],n="",r=null,s=!1;for(let i of e.trim()){if(s){n+=i,s=!1;continue}if(i==="\\"&&r!=="'"){s=!0;continue}if(r){if(i===r)r=null;else n+=i;continue}if(i==='"'||i==="'"){r=i;continue}if(/\s/.test(i)){if(n)t.push(n),n="";continue}n+=i}if(s||r)throw Error("Command substitution contains an unterminated escape or quote");if(n)t.push(n);return t}function _e(e){let[t,...n]=e,r=n.some((s)=>s.startsWith("-"));if(t==="echo"&&!r)return n.join(" ");if(t==="printf"&&n.length===1&&!n[0]?.includes("%"))return n[0];if(t==="hostname"&&n.length===0)return le();if(t==="whoami"&&n.length===0)return he().username;if(t==="pwd"&&n.length===0)return process.cwd();if(t==="basename"&&n.length===1)return ye(n[0]??"");if(t==="dirname"&&n.length===1)return me(n[0]??"");if(t==="cat"&&n.length>0&&!r)return n.map((s)=>pe(Ee(process.cwd(),s),"utf8")).join("");if(t==="uname"){let s=n[0]??"-s";if(n.length<=1&&s==="-m")return de();if(n.length<=1&&s==="-r")return ge();if(n.length<=1&&s==="-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"}[Q()]??Q()}return}function xe(e){return e.replace(/\$\(([^)]+)\)/g,(t,n)=>{try{let r=ke(n),s=r[0];if(!s||!we.has(s))return console.warn(`[env] Blocked command substitution for disallowed command: ${s}`),"";let i=_e(r);if(i!==void 0)return i.trim();let c=ue(s,r.slice(1),{encoding:"utf8",env:process.env});if(c.status===0)return c.stdout.trim();let a=c.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${c.status??"unknown"}): ${s}${a?`: ${a}`:""}`)}catch(r){console.warn(`[env] Command substitution error: ${r instanceof Error?r.message:String(r)}`)}return""})}async function We(e,t={}){let n={},r=[],s=[];for(let i of e)try{let c=Bun.file(i);if(!c.size)continue;let a=await c.text(),{parsed:u,errors:f,skippedEncrypted:o}=_(a,t);r.push(...f),s.push(...o);for(let[p,l]of Object.entries(u)){if(!t.overload&&n[p]!==void 0)continue;n[p]=l}}catch(c){if(c?.code==="ENOENT")continue;r.push(`Failed to read ${i}: ${c instanceof Error?c.message:String(c)}`)}return{parsed:n,errors:r,skippedEncrypted:s}}function x(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}function E(e){if(!e||x(e))return;let t=e.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(t))return;if(t==="prod")return"production";if(t==="stage")return"staging";if(t==="dev")return"development";return t}var B=null,A,R;function Pe(e,t,n=".env.keys"){let r=D(t,n);if(!T(r))return;try{let{parsed:s}=_(Y(r,"utf-8"));if(e){let i=s[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`];if(i)return i}return s.DOTENV_PRIVATE_KEY}catch{return}}function be(e={}){return E(e.env)||R||E("development")||E(process.env.DOTENV_ENV)||E(process.env.APP_ENV)||"development"}function Ke(e={}){let t=be(e),n=e.cwd||process.cwd();if(B===t)return A;let r=O(t);if(!r)r=process.env.DOTENV_PRIVATE_KEY;if(!r)r=Pe(t,n);return B=t,A=r,r}function He(){B=null,A=void 0,R=void 0}function qe(e,t={}){if(!x(e))return e;let n=Ke(t);if(!n)return;try{let r=e.startsWith("enc:")?`encrypted:${e.slice(4)}`:e;return k(r,n)}catch{return}}function ee(e={}){let{path:t=[".env"],overload:n=!1,env:r,privateKey:s,keysFile:i=".env.keys",quiet:c=!1,cwd:a=process.cwd()}=e,u=Array.isArray(t)?t:[t],f=[],o=0,p=new Set(Object.entries(process.env).filter(([,d])=>d!==void 0&&!x(d)).map(([d])=>d)),l=s;if(!l&&i){let d=D(a,i);if(T(d))try{let g=Y(d,"utf-8"),{parsed:y}=_(g);if(r){let P=`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`;l=y[P]||y.DOTENV_PRIVATE_KEY}else l=y.DOTENV_PRIVATE_KEY}catch(g){f.push(`Failed to load keys file: ${g instanceof Error?g.message:"Unknown error"}`)}}if(!l)l=O(r||"")||O();for(let d of u){let g=D(a,d);if(!T(g)){if(!c)f.push(`File not found: ${g}`);continue}try{let y=Y(g,"utf-8"),{parsed:P,errors:te,skippedEncrypted:v}=_(y,{privateKey:l,processEnv:process.env});if(f.push(...te),v.length>0){for(let V of v)if(x(process.env[V]))delete process.env[V];let w=r?`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`:"DOTENV_PRIVATE_KEY",b=v.slice(0,5).join(", "),K=v.length>5?`, \u2026 +${v.length-5} more`:"";console.warn(`[env] warning: skipped ${v.length} encrypted value(s) in ${d} (${w} not set; defaults apply): ${b}${K}`)}let L=0;for(let[w,b]of Object.entries(P)){if(w==="DOTENV_PUBLIC_KEY")continue;let K=process.env[w],V=x(K)&&b!==K;if(n||!p.has(w)||V)process.env[w]=b,o++,L++}if(!c&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${L}/${Object.keys(P).length} variables from ${d}`),process.env.__ENV_LOADED__="1"}catch(y){f.push(`Failed to load ${g}: ${y instanceof Error?y.message:"Unknown error"}`)}}return{loaded:o,errors:f}}function Ve(e={}){return{name:"env-plugin",setup(t){ee(e)}}}function Me(e={}){let t=E(e.env)||E("development")||E(process.env.DOTENV_ENV)||E(process.env.APP_ENV)||"development",n=e.cwd||process.cwd();if(R!==t)R=t,B=null,A=void 0;let r=[],s=(a)=>{if(!r.includes(a)&&T(D(n,a)))r.push(a)};s(".env"),s(".env.local");let i=`.env.${t}`,c=`.env.${t}.local`;return s(i),s(c),ee({...e,path:r,env:t})}var Je=Ve;export{be as activeEnvName,Me as autoLoadEnv,qe as decryptEnvValue,Je as default,Ve as envPlugin,ee as loadEnv,He as resetPrivateKeyCache,Ke as resolvePrivateKey};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/env",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.43",
5
+ "version": "0.74.45",
6
6
  "description": "Stacks env helper methods.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -56,10 +56,10 @@
56
56
  "prepublishOnly": "bun run build"
57
57
  },
58
58
  "dependencies": {
59
- "@stacksjs/path": "0.74.43"
59
+ "@stacksjs/path": "0.74.45"
60
60
  },
61
61
  "devDependencies": {
62
62
  "better-dx": "^0.2.24",
63
- "@stacksjs/validation": "0.74.43"
63
+ "@stacksjs/validation": "0.74.45"
64
64
  }
65
65
  }