@stacksjs/env 0.71.10 → 0.72.1
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/cli.js +17 -17
- package/dist/index.js +16 -16
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{createCipheriv as
|
|
2
|
+
import{createCipheriv as m,createDecipheriv as n,createHash as P,createPrivateKey as wf,createPublicKey as v,diffieHellman as o,generateKeyPairSync as i,hkdfSync as s,randomBytes as r}from"crypto";var N="x25519-public:",K="x25519-private:",F="encrypted:v2:",t="encrypted:",y=Buffer.from("stacks-env:v2","utf8"),p="Environment decryption failed: authentication or format error";function l(f){if(f.length===32)return f;return P("sha256").update(f).digest()}function qf(f,E){let g=r(16),$=m("aes-256-gcm",l(E),g);return{ciphertext:Buffer.concat([$.update(f,"utf8"),$.final()]).toString("hex"),iv:g.toString("hex"),authTag:$.getAuthTag().toString("hex")}}function Af(f,E,g,$){try{let B=n("aes-256-gcm",l(E),Buffer.from(g,"hex"));return B.setAuthTag(Buffer.from($,"hex")),Buffer.concat([B.update(Buffer.from(f,"hex")),B.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function Z(f){return f.toString("base64url")}function V(f,E,g){if(typeof f!=="string"||f.length>0&&!/^[A-Za-z0-9_-]+$/.test(f))throw Error(`${E} is not canonical base64url`);let $=Buffer.from(f,"base64url");if(Z($)!==f)throw Error(`${E} is not canonical base64url`);if(g!==void 0&&$.length!==g)throw Error(`${E} has an invalid length`);return $}function _f(f){if(!f.startsWith(N))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let E=v({key:V(f.slice(N.length),"public key"),format:"der",type:"spki"});if(E.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return E}function Tf(f){if(!f.startsWith(K))throw Error("Private key is not a version 2 X25519 key");let E=wf({key:V(f.slice(K.length),"private key"),format:"der",type:"pkcs8"});if(E.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return E}function a(f){return Buffer.from(`stacks-env:v2;${f.epk};${f.salt};${f.nonce}`,"utf8")}function M(){let{publicKey:f,privateKey:E}=i("x25519"),g=f.export({format:"der",type:"spki"}),$=E.export({format:"der",type:"pkcs8"});return{publicKey:`${N}${Z(g)}`,privateKey:`${K}${Z($)}`}}function c(f,E){let g=_f(E),$=i("x25519"),B=Z($.publicKey.export({format:"der",type:"spki"})),w=r(16),D=r(12),I=o({privateKey:$.privateKey,publicKey:g}),_=Buffer.from(s("sha256",I,w,y,32)),T={epk:B,salt:Z(w),nonce:Z(D)};try{let A=m("aes-256-gcm",_,D);A.setAAD(a(T));let X=Buffer.concat([A.update(f,"utf8"),A.final()]),R={v:2,...T,ciphertext:Z(X),tag:Z(A.getAuthTag())};return`${F}${Z(Buffer.from(JSON.stringify(R),"utf8"))}`}finally{I.fill(0),_.fill(0)}}function If(f){let E=V(f.slice(F.length),"envelope").toString("utf8"),g=JSON.parse(E);if(!g||typeof g!=="object"||Array.isArray(g))throw Error("envelope is not an object");let $=Object.keys(g).sort(),B=["ciphertext","epk","nonce","salt","tag","v"];if($.length!==B.length||$.some((w,D)=>w!==B[D]))throw Error("envelope fields are invalid");if(g.v!==2)throw Error("envelope version is unsupported");for(let w of["epk","salt","nonce","ciphertext","tag"])if(typeof g[w]!=="string")throw Error(`${w} is invalid`);return g}function Df(f,E){try{let g=If(f),$=Tf(E),B=v({key:V(g.epk,"ephemeral public key"),format:"der",type:"spki"});if(B.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let w=V(g.salt,"salt",16),D=V(g.nonce,"nonce",12),I=V(g.ciphertext,"ciphertext"),_=V(g.tag,"tag",16),T=o({privateKey:$,publicKey:B}),A=Buffer.from(s("sha256",T,w,y,32));try{let X=n("aes-256-gcm",A,D);return X.setAAD(a(g)),X.setAuthTag(_),Buffer.concat([X.update(I),X.final()]).toString("utf8")}finally{T.fill(0),A.fill(0)}}catch{throw Error(p)}}function Xf(f,E){try{if(!/^[0-9a-f]{64}$/.test(E))throw Error("invalid legacy private key");let g=Buffer.from(f.slice(t.length),"base64");if(g.length<32)throw Error("invalid legacy payload");let $=P("sha256").update(Buffer.from(E,"hex")).digest(),B=P("sha256").update($).digest();return Af(g.subarray(32).toString("hex"),B,g.subarray(0,16).toString("hex"),g.subarray(16,32).toString("hex"))}catch{throw Error(p)}}function Mf(f){return f.startsWith(t)&&!f.startsWith(F)}function C(f,E){if(!f.startsWith(t))return f;return f.startsWith(F)?Df(f,E):Xf(f,E)}function Cf(f,E,g){return c(C(f,E),g)}function Ff(f){let E=f.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return E&&E[1]?E[1].toLowerCase():""}function cf(f=""){return f?process.env[`DOTENV_PRIVATE_KEY_${f.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as Of}from"child_process";import{readFileSync as Rf}from"fs";import{arch as Yf,hostname as Hf,platform as e,release as Wf,userInfo as hf}from"os";import{basename as jf,dirname as xf,resolve as Gf}from"path";function L(f,E={}){let g={},$=[],B=[],w=f.split(`
|
|
3
3
|
`);for(let D of w){let I=D.trim();if(!I||I.startsWith("#"))continue;if(I.startsWith("DOTENV_PUBLIC_KEY=")){let X=I.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(X&&X[1]!==void 0)g.DOTENV_PUBLIC_KEY=X[1];continue}let _=I.match(/^([^=]+)=(.*)$/);if(!_||_[1]===void 0||_[2]===void 0)continue;let T=_[1].trim(),A=_[2].trim();if(A.startsWith('"')&&A.endsWith('"')||A.startsWith("'")&&A.endsWith("'")){if(A=A.slice(1,-1),A.includes("\\n"))A=A.replace(/\\n/g,`
|
|
4
|
-
`)}if(A.startsWith("encrypted:")||A.startsWith("enc:")){if(!E.privateKey){B.push(T);continue}try{let X=A.startsWith("enc:")?`encrypted:${A.slice(4)}`:A;A=
|
|
5
|
-
`)){let I=D.trim();if(I.startsWith("#/"))continue;if(I.startsWith("DOTENV_PUBLIC_KEY"))continue;if(!I&&!w)continue;if(!I||I.startsWith("#")){w=!0,B.push(D);continue}w=!0;let _=I.match(/^([^=]+)=(.*)$/);if(!_||_[1]===void 0||_[2]===void 0){B.push(D);continue}let T=_[1].trim(),A=_[2].trim();if(A.startsWith('"')&&A.endsWith('"')||A.startsWith("'")&&A.endsWith("'"))A=A.slice(1,-1);if((!$.key||T.includes($.key))&&(!$.excludeKey||!T.includes($.excludeKey))&&!A.startsWith("encrypted:"))A=
|
|
6
|
-
`)}function
|
|
4
|
+
`)}if(A.startsWith("encrypted:")||A.startsWith("enc:")){if(!E.privateKey){B.push(T);continue}try{let X=A.startsWith("enc:")?`encrypted:${A.slice(4)}`:A;A=C(X,E.privateKey)}catch(X){$.push(`Failed to decrypt ${T}: ${X instanceof Error?X.message:"Unknown error"}`)}}A=Jf(A,{...E.processEnv||process.env,...g}),A=Qf(A),g[T]=A}return{parsed:g,errors:$,skippedEncrypted:B}}function Jf(f,E){return f.replace(/\$\{([^}]+)\}/g,(g,$)=>{let B=$.match(/^([^:\-+]+)(:-|-)(.+)$/);if(B){let[,D,I,_]=B,T=E[D];if(I===":-")return T||_;else return T!==void 0?T:_}let w=$.match(/^([^:\-+]+)(:?\+)(.+)$/);if(w){let[,D,I,_]=w,T=E[D];if(I===":+")return T?_:"";else return T!==void 0?_:""}return E[$]||""})}var Uf=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function zf(f){let E=[],g="",$=null,B=!1;for(let w of f.trim()){if(B){g+=w,B=!1;continue}if(w==="\\"&&$!=="'"){B=!0;continue}if($){if(w===$)$=null;else g+=w;continue}if(w==='"'||w==="'"){$=w;continue}if(/\s/.test(w)){if(g)E.push(g),g="";continue}g+=w}if(B||$)throw Error("Command substitution contains an unterminated escape or quote");if(g)E.push(g);return E}function Zf(f){let[E,...g]=f,$=g.some((B)=>B.startsWith("-"));if(E==="echo"&&!$)return g.join(" ");if(E==="printf"&&g.length===1&&!g[0]?.includes("%"))return g[0];if(E==="hostname"&&g.length===0)return Hf();if(E==="whoami"&&g.length===0)return hf().username;if(E==="pwd"&&g.length===0)return process.cwd();if(E==="basename"&&g.length===1)return jf(g[0]??"");if(E==="dirname"&&g.length===1)return xf(g[0]??"");if(E==="cat"&&g.length>0&&!$)return g.map((B)=>Rf(Gf(process.cwd(),B),"utf8")).join("");if(E==="uname"){let B=g[0]??"-s";if(g.length<=1&&B==="-m")return Yf();if(g.length<=1&&B==="-r")return Wf();if(g.length<=1&&B==="-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"}[e()]??e()}return}function Qf(f){return f.replace(/\$\(([^)]+)\)/g,(E,g)=>{try{let $=zf(g),B=$[0];if(!B||!Uf.has(B))return console.warn(`[env] Blocked command substitution for disallowed command: ${B}`),"";let w=Zf($);if(w!==void 0)return w.trim();let D=Of(B,$.slice(1),{encoding:"utf8",env:process.env});if(D.status===0)return D.stdout.trim();let I=D.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${D.status??"unknown"}): ${B}${I?`: ${I}`:""}`)}catch($){console.warn(`[env] Command substitution error: ${$ instanceof Error?$.message:String($)}`)}return""})}async function tf(f,E={}){let g={},$=[],B=[];for(let w of f)try{let D=Bun.file(w);if(!D.size)continue;let I=await D.text(),{parsed:_,errors:T,skippedEncrypted:A}=L(I,E);$.push(...T),B.push(...A);for(let[X,R]of Object.entries(_)){if(!E.overload&&g[X]!==void 0)continue;g[X]=R}}catch(D){if(D?.code==="ENOENT")continue;$.push(`Failed to read ${w}: ${D instanceof Error?D.message:String(D)}`)}return{parsed:g,errors:$,skippedEncrypted:B}}import{existsSync as U,readFileSync as J,renameSync as k,rmSync as ff,writeFileSync as z}from"fs";import{basename as d,dirname as gf,resolve as G}from"path";function vf(f,E){if(f)return f;let g=String(E??"").trim();if(!g||g==="development"||g==="dev"||g==="local")return"";if(!/^[\w.-]+$/.test(g))return"";return`.env.${g}`}function Ef(f,E,g,$){let B=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${g}="${E}"`,""],w=!1;for(let D of f.split(`
|
|
5
|
+
`)){let I=D.trim();if(I.startsWith("#/"))continue;if(I.startsWith("DOTENV_PUBLIC_KEY"))continue;if(!I&&!w)continue;if(!I||I.startsWith("#")){w=!0,B.push(D);continue}w=!0;let _=I.match(/^([^=]+)=(.*)$/);if(!_||_[1]===void 0||_[2]===void 0){B.push(D);continue}let T=_[1].trim(),A=_[2].trim();if(A.startsWith('"')&&A.endsWith('"')||A.startsWith("'")&&A.endsWith("'"))A=A.slice(1,-1);if((!$.key||T.includes($.key))&&(!$.excludeKey||!T.includes($.excludeKey))&&!A.startsWith("encrypted:"))A=c(A,E);B.push(`${T}="${A}"`)}return B.join(`
|
|
6
|
+
`)}function u(f){let g=((f||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase();return{publicKeyName:g?`DOTENV_PUBLIC_KEY_${g}`:"DOTENV_PUBLIC_KEY",privateKeyName:g?`DOTENV_PRIVATE_KEY_${g}`:"DOTENV_PRIVATE_KEY"}}function of(f={}){let E=f.cwd||process.cwd(),g=G(E,f.file||".env"),$=G(E,f.keysFile||".env.keys");if(!U(g))return{success:!1,error:`File not found: ${g}`};try{let B,w;if(U($)){let _=J($,"utf-8"),{parsed:T}=L(_),{publicKeyName:A,privateKeyName:X}=u(f.file);if(B=T[A]||"",w=T[X]||"",!B||!w){let R=M();B=R.publicKey,w=R.privateKey;let W=`
|
|
7
7
|
${A}="${B}"
|
|
8
8
|
${X}="${w}"
|
|
9
|
-
`;z($,_+
|
|
9
|
+
`;z($,_+W,"utf-8")}}else{let _=M();B=_.publicKey,w=_.privateKey;let{publicKeyName:T,privateKeyName:A}=u(f.file),X=`# .env.keys - Keep this file secure and never commit to source control
|
|
10
10
|
${T}="${B}"
|
|
11
11
|
${A}="${w}"
|
|
12
|
-
`;z($,X,"utf-8")}let D=J(g,"utf-8"),I=
|
|
13
|
-
\u2714 key added to ${f.keysFile||".env.keys"}`}}catch(B){return{success:!1,error:`Failed to encrypt: ${B instanceof Error?B.message:"Unknown error"}`}}}function
|
|
14
|
-
`),
|
|
15
|
-
`);if(f.stdout)return{success:!0,output:
|
|
16
|
-
`),_=!1,T,R=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),
|
|
17
|
-
${
|
|
18
|
-
${h}="${
|
|
19
|
-
`,z(w,
|
|
20
|
-
`);return z(B,
|
|
21
|
-
`);break;case"json":default:
|
|
22
|
-
`),T=f.file||".env",A=
|
|
12
|
+
`;z($,X,"utf-8")}let D=J(g,"utf-8"),I=Ef(D,B,u(f.file).publicKeyName,f);if(f.stdout)return{success:!0,output:I};return z(g,I,"utf-8"),{success:!0,output:`\u2714 encrypted (${f.file||".env"})
|
|
13
|
+
\u2714 key added to ${f.keysFile||".env.keys"}`}}catch(B){return{success:!1,error:`Failed to encrypt: ${B instanceof Error?B.message:"Unknown error"}`}}}function Vf(f={}){let E=f.cwd||process.cwd(),g=G(E,f.file||".env"),$=G(E,f.keysFile||".env.keys");if(!U(g))return{success:!1,error:`File not found: ${g}`};if(!U($))return{success:!1,error:`Keys file not found: ${$}`};try{let B=J($,"utf-8"),{parsed:w}=L(B),_=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),T=_?`DOTENV_PRIVATE_KEY_${_}`:"DOTENV_PRIVATE_KEY",A=w[T];if(!A)return{success:!1,error:`Private key not found: ${T}`};let X=[A];if(T!=="DOTENV_PRIVATE_KEY"&&w.DOTENV_PRIVATE_KEY&&w.DOTENV_PRIVATE_KEY!==A)X.push(w.DOTENV_PRIVATE_KEY);let W=J(g,"utf-8").split(`
|
|
14
|
+
`),h=[];for(let Q of W){let Y=Q.trim();if(!Y||Y.startsWith("#")){h.push(Q);continue}if(Y.startsWith("DOTENV_PUBLIC_KEY"))continue;let H=Y.match(/^([^=]+)=(.*)$/);if(!H||H[1]===void 0||H[2]===void 0){h.push(Q);continue}let j=H[1].trim(),O=H[2].trim();if(O.startsWith('"')&&O.endsWith('"')||O.startsWith("'")&&O.endsWith("'"))O=O.slice(1,-1);let q=O.startsWith("encrypted:");if(f.key&&!j.includes(f.key))q=!1;if(q){let S,b;for(let Bf of X)try{S=C(O,Bf);break}catch($f){b=$f}if(S===void 0)throw b instanceof Error?b:Error(`Unable to decrypt ${j}`);O=S}h.push(`${j}="${O}"`)}let x=h.join(`
|
|
15
|
+
`);if(f.stdout)return{success:!0,output:x};return z(g,x,"utf-8"),{success:!0,output:`\u2714 decrypted (${f.file||".env"})`}}catch(B){return{success:!1,error:`Failed to decrypt: ${B instanceof Error?B.message:"Unknown error"}`}}}function sf(f,E,g={}){let $=g.cwd||process.cwd(),B=G($,g.file||".env"),w=G($,g.keysFile||".env.keys");try{let D="";if(U(B))D=J(B,"utf-8");let I=D.split(`
|
|
16
|
+
`),_=!1,T,R=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),W=R?`DOTENV_PUBLIC_KEY_${R}`:"DOTENV_PUBLIC_KEY",h=R?`DOTENV_PRIVATE_KEY_${R}`:"DOTENV_PRIVATE_KEY";for(let Y of I){let H=Y.trim();if(H.startsWith(`${W}=`)){let j=H.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(j)T=j[1];break}}if(T){let Y=!1;if(U(w)){let H=J(w,"utf-8"),{parsed:j}=L(H);Y=Boolean(j[h])}if(!Y)T=void 0}if(!g.plain&&!T){let Y=M();T=Y.publicKey;let H="";if(U(w))H=J(w,"utf-8");H+=`
|
|
17
|
+
${W}="${Y.publicKey}"
|
|
18
|
+
${h}="${Y.privateKey}"
|
|
19
|
+
`,z(w,H,"utf-8");for(let j=I.length-1;j>=0;j--){let O=I[j];if(O!==void 0&&O.trim().startsWith(`${W}=`))I.splice(j,1)}I.unshift(`${W}="${T}"`)}let x=E;if(!g.plain&&T)x=c(E,T);for(let Y=0;Y<I.length;Y++){let H=I[Y];if(H===void 0)continue;if(H.trim().startsWith(`${f}=`)){I[Y]=`${f}="${x}"`,_=!0;break}}if(!_)I.push(`${f}="${x}"`);let Q=I.join(`
|
|
20
|
+
`);return z(B,Q,"utf-8"),{success:!0,output:`set ${f}${g.plain?"":" with encryption"} (${g.file||".env"})`}}catch(D){return{success:!1,error:`Failed to set: ${D instanceof Error?D.message:"Unknown error"}`}}}function yf(f,E={}){let g=E.cwd||process.cwd(),$=G(g,E.file||".env");if(!U($))return{success:!1,error:`File not found: ${$}`};try{let B,w=G(g,E.keysFile||".env.keys"),_=((E.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),T=_?`DOTENV_PRIVATE_KEY_${_}`:"DOTENV_PRIVATE_KEY";if(U(w)){let h=J(w,"utf-8"),{parsed:x}=L(h);B=x[T]}if(!B)B=process.env[T];let A=J($,"utf-8"),{parsed:X}=L(A,{privateKey:B});if(f&&!E.all){let h=X[f];if(h===void 0)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:h}}let R=E.all?{...process.env,...X}:X,W;switch(E.format){case"shell":W=Object.entries(R).map(([h,x])=>`${h}=${x}`).join(" ");break;case"eval":W=Object.entries(R).map(([h,x])=>`${h}="${x}"`).join(`
|
|
21
|
+
`);break;case"json":default:W=E.prettyPrint?JSON.stringify(R,null,2):JSON.stringify(R);break}return{success:!0,output:W}}catch(B){return{success:!1,error:`Failed to get: ${B instanceof Error?B.message:"Unknown error"}`}}}function pf(f,E={}){let g=E.cwd||process.cwd(),$=G(g,E.keysFile||".env.keys");if(!U($))return{success:!1,error:`Keys file not found: ${$}`};try{let B=J($,"utf-8"),{parsed:w}=L(B),_=((E.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),T=_?`DOTENV_PUBLIC_KEY_${_}`:"DOTENV_PUBLIC_KEY",A=_?`DOTENV_PRIVATE_KEY_${_}`:"DOTENV_PRIVATE_KEY";if(f){let W=w[f];if(!W)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:W}}let X={[T]:w[T],[A]:w[A]};return{success:!0,output:E.format==="shell"?`${T}=${X[T]} ${A}=${X[A]}`:JSON.stringify(X)}}catch(B){return{success:!1,error:`Failed to get keypair: ${B instanceof Error?B.message:"Unknown error"}`}}}function lf(f={}){let E=Vf({file:f.file,keysFile:f.keysFile,stdout:!0,cwd:f.cwd});if(!E.success)return E;let g=f.cwd||process.cwd(),$=G(g,f.file||".env"),B=G(g,f.keysFile||".env.keys");if(E.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let w=M(),D=J($),I=J(B),_=I.toString("utf8").split(`
|
|
22
|
+
`),T=f.file||".env",A=d(T).replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),X=A?`DOTENV_PUBLIC_KEY_${A}`:"DOTENV_PUBLIC_KEY",R=A?`DOTENV_PRIVATE_KEY_${A}`:"DOTENV_PRIVATE_KEY",W=!1,h=!1;for(let O=0;O<_.length;O++){let q=_[O];if(q===void 0)continue;if(q.startsWith(`${X}=`))_[O]=`${X}="${w.publicKey}"`,W=!0;else if(q.startsWith(`${R}=`))_[O]=`${R}="${w.privateKey}"`,h=!0}if(!W)_.push(`${X}="${w.publicKey}"`);if(!h)_.push(`${R}="${w.privateKey}"`);let x=`${_.join(`
|
|
23
23
|
`).replace(/\n+$/,"")}
|
|
24
|
-
`,
|
|
24
|
+
`,Q=Ef(E.output,w.publicKey,X,f),Y=`${process.pid}-${Date.now()}`,H=G(gf($),`.${d($)}.${Y}.rotate`),j=G(gf(B),`.${d(B)}.${Y}.rotate`);try{if(z(H,Q,{encoding:"utf8",mode:384}),z(j,x,{encoding:"utf8",mode:384}),f.stdout)return k(j,B),{success:!0,output:Q};k(H,$);try{k(j,B)}catch(O){throw z($,D),O}return{success:!0,output:`\u2714 rotated (${f.file||".env"}) to encrypted:v2`}}catch(O){if(!U(B))z(B,I,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${O instanceof Error?O.message:"Unknown error"}`}}finally{ff(H,{force:!0}),ff(j,{force:!0})}}export{sf as setEnv,lf as rotateKeypair,vf as resolveEnvFile,pf as getKeypair,yf as getEnv,u as envKeyNames,of as encryptEnv,Vf as decryptEnv};
|
package/dist/index.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{createCipheriv as B2,createDecipheriv as $2,createHash as t,createPrivateKey as
|
|
2
|
+
import{createCipheriv as B2,createDecipheriv as $2,createHash as t,createPrivateKey as E2,createPublicKey as X2,diffieHellman as D2,generateKeyPairSync as H2,hkdfSync as _2,randomBytes as y}from"crypto";var v="x25519-public:",n="x25519-private:",P="encrypted:v2:",i="encrypted:",A2=Buffer.from("stacks-env:v2","utf8"),W2="Environment decryption failed: authentication or format error";function Y2(f){if(f.length===32)return f;return t("sha256").update(f).digest()}function Zf(f,g){let B=y(16),X=B2("aes-256-gcm",Y2(g),B);return{ciphertext:Buffer.concat([X.update(f,"utf8"),X.final()]).toString("hex"),iv:B.toString("hex"),authTag:X.getAuthTag().toString("hex")}}function x2(f,g,B,X){try{let $=$2("aes-256-gcm",Y2(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 E(f){return f.toString("base64url")}function b(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(E(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 F2(f){if(!f.startsWith(v))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let g=X2({key:b(f.slice(v.length),"public key"),format:"der",type:"spki"});if(g.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return g}function b2(f){if(!f.startsWith(n))throw Error("Private key is not a version 2 X25519 key");let g=E2({key:b(f.slice(n.length),"private key"),format:"der",type:"pkcs8"});if(g.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return g}function j2(f){return Buffer.from(`stacks-env:v2;${f.epk};${f.salt};${f.nonce}`,"utf8")}function K(){let{publicKey:f,privateKey:g}=H2("x25519"),B=f.export({format:"der",type:"spki"}),X=g.export({format:"der",type:"pkcs8"});return{publicKey:`${v}${E(B)}`,privateKey:`${n}${E(X)}`}}function c(f,g){let B=F2(g),X=H2("x25519"),$=E(X.publicKey.export({format:"der",type:"spki"})),D=y(16),H=y(12),W=D2({privateKey:X.privateKey,publicKey:B}),A=Buffer.from(_2("sha256",W,D,A2,32)),Y={epk:$,salt:E(D),nonce:E(H)};try{let _=B2("aes-256-gcm",A,H);_.setAAD(j2(Y));let j=Buffer.concat([_.update(f,"utf8"),_.final()]),Z={v:2,...Y,ciphertext:E(j),tag:E(_.getAuthTag())};return`${P}${E(Buffer.from(JSON.stringify(Z),"utf8"))}`}finally{W.fill(0),A.fill(0)}}function S2(f){let g=b(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 h2(f,g){try{let B=S2(f),X=b2(g),$=X2({key:b(B.epk,"ephemeral public key"),format:"der",type:"spki"});if($.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let D=b(B.salt,"salt",16),H=b(B.nonce,"nonce",12),W=b(B.ciphertext,"ciphertext"),A=b(B.tag,"tag",16),Y=D2({privateKey:X,publicKey:$}),_=Buffer.from(_2("sha256",Y,D,A2,32));try{let j=$2("aes-256-gcm",_,H);return j.setAAD(j2(B)),j.setAuthTag(A),Buffer.concat([j.update(W),j.final()]).toString("utf8")}finally{Y.fill(0),_.fill(0)}}catch{throw Error(W2)}}function K2(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 X=t("sha256").update(Buffer.from(g,"hex")).digest(),$=t("sha256").update(X).digest();return x2(B.subarray(32).toString("hex"),$,B.subarray(0,16).toString("hex"),B.subarray(16,32).toString("hex"))}catch{throw Error(W2)}}function Qf(f){return f.startsWith(i)&&!f.startsWith(P)}function h(f,g){if(!f.startsWith(i))return f;return f.startsWith(P)?h2(f,g):K2(f,g)}function Rf(f,g,B){return c(h(f,g),B)}function If(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 N2}from"child_process";import{readFileSync as P2}from"fs";import{arch as c2,hostname as k2,platform as G2,release as m2,userInfo as d2}from"os";import{basename as u2,dirname as r2,resolve as t2}from"path";function M(f,g={}){let B={},X=[],$=[],D=f.split(`
|
|
3
3
|
`);for(let H of D){let W=H.trim();if(!W||W.startsWith("#"))continue;if(W.startsWith("DOTENV_PUBLIC_KEY=")){let j=W.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(j&&j[1]!==void 0)B.DOTENV_PUBLIC_KEY=j[1];continue}let A=W.match(/^([^=]+)=(.*)$/);if(!A||A[1]===void 0||A[2]===void 0)continue;let Y=A[1].trim(),_=A[2].trim();if(_.startsWith('"')&&_.endsWith('"')||_.startsWith("'")&&_.endsWith("'")){if(_=_.slice(1,-1),_.includes("\\n"))_=_.replace(/\\n/g,`
|
|
4
|
-
`)}if(_.startsWith("encrypted:")||_.startsWith("enc:")){if(!g.privateKey){$.push(Y);continue}try{let j=_.startsWith("enc:")?`encrypted:${_.slice(4)}`:_;_=
|
|
4
|
+
`)}if(_.startsWith("encrypted:")||_.startsWith("enc:")){if(!g.privateKey){$.push(Y);continue}try{let j=_.startsWith("enc:")?`encrypted:${_.slice(4)}`:_;_=h(j,g.privateKey)}catch(j){X.push(`Failed to decrypt ${Y}: ${j instanceof Error?j.message:"Unknown error"}`)}}_=y2(_,{...g.processEnv||process.env,...B}),_=o2(_),B[Y]=_}return{parsed:B,errors:X,skippedEncrypted:$}}function y2(f,g){return f.replace(/\$\{([^}]+)\}/g,(B,X)=>{let $=X.match(/^([^:\-+]+)(:-|-)(.+)$/);if($){let[,H,W,A]=$,Y=g[H];if(W===":-")return Y||A;else return Y!==void 0?Y:A}let D=X.match(/^([^:\-+]+)(:?\+)(.+)$/);if(D){let[,H,W,A]=D,Y=g[H];if(W===":+")return Y?A:"";else return Y!==void 0?A:""}return g[X]||""})}var v2=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function n2(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 i2(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 k2();if(g==="whoami"&&B.length===0)return d2().username;if(g==="pwd"&&B.length===0)return process.cwd();if(g==="basename"&&B.length===1)return u2(B[0]??"");if(g==="dirname"&&B.length===1)return r2(B[0]??"");if(g==="cat"&&B.length>0&&!X)return B.map(($)=>P2(t2(process.cwd(),$),"utf8")).join("");if(g==="uname"){let $=B[0]??"-s";if(B.length<=1&&$==="-m")return c2();if(B.length<=1&&$==="-r")return m2();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"}[G2()]??G2()}return}function o2(f){return f.replace(/\$\(([^)]+)\)/g,(g,B)=>{try{let X=n2(B),$=X[0];if(!$||!v2.has($))return console.warn(`[env] Blocked command substitution for disallowed command: ${$}`),"";let D=i2(X);if(D!==void 0)return D.trim();let H=N2($,X.slice(1),{encoding:"utf8",env:process.env});if(H.status===0)return H.stdout.trim();let W=H.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${H.status??"unknown"}): ${$}${W?`: ${W}`:""}`)}catch(X){console.warn(`[env] Command substitution error: ${X instanceof Error?X.message:String(X)}`)}return""})}async function zf(f,g={}){let B={},X=[],$=[];for(let D of f)try{let H=Bun.file(D);if(!H.size)continue;let W=await H.text(),{parsed:A,errors:Y,skippedEncrypted:_}=M(W,g);X.push(...Y),$.push(..._);for(let[j,Z]of Object.entries(A)){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 N(f){return typeof f==="string"&&(f.startsWith("encrypted:")||f.startsWith("enc:"))}function S(f){if(!f||N(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 p2(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 S(f.env)||r||S("development")||S(process.env.DOTENV_ENV)||S(process.env.APP_ENV)||"development"}function l2(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=p2(g,B);return d=g,u=X,X}function hf(){d=null,u=void 0,r=void 0}function J2(f,g={}){if(!N(f))return f;let B=l2(g);if(!B)return;try{let X=f.startsWith("enc:")?`encrypted:${f.slice(4)}`:f;return h(X,B)}catch{return}}function O2(f={}){let{path:g=[".env"],overload:B=!1,env:X,privateKey:$,keysFile:D=".env.keys",quiet:H=!1,cwd:W=process.cwd()}=f,A=Array.isArray(g)?g:[g],Y=[],_=0,j=new Set(Object.entries(process.env).filter(([,G])=>G!==void 0&&!N(G)).map(([G])=>G)),Z=$;if(!Z&&D){let G=m(W,D);if(k(G))try{let Q=p(G,"utf-8"),{parsed:w}=M(Q);if(X){let T=`DOTENV_PRIVATE_KEY_${X.toUpperCase()}`;Z=w[T]}else Z=w.DOTENV_PRIVATE_KEY}catch(Q){Y.push(`Failed to load keys file: ${Q instanceof Error?Q.message:"Unknown error"}`)}}if(!Z)Z=o(X||"");for(let G of A){let Q=m(W,G);if(!k(Q)){if(!H)Y.push(`File not found: ${Q}`);continue}try{let w=p(Q,"utf-8"),{parsed:T,errors:I,skippedEncrypted:R}=M(w,{privateKey:Z,processEnv:process.env});if(Y.push(...I),R.length>0){for(let F of R)if(N(process.env[F]))delete process.env[F];let J=X?`DOTENV_PRIVATE_KEY_${X.toUpperCase()}`:"DOTENV_PRIVATE_KEY",z=R.slice(0,5).join(", "),x=R.length>5?`, \u2026 +${R.length-5} more`:"";console.warn(`[env] warning: skipped ${R.length} encrypted value(s) in ${G} (${J} not set; defaults apply): ${z}${x}`)}let U=0;for(let[J,z]of Object.entries(T)){if(J==="DOTENV_PUBLIC_KEY")continue;let x=process.env[J],F=N(x)&&z!==x;if(B||!j.has(J)||F)process.env[J]=z,_++,U++}if(!H&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${U}/${Object.keys(T).length} variables from ${G}`),process.env.__ENV_LOADED__="1"}catch(w){Y.push(`Failed to load ${Q}: ${w instanceof Error?w.message:"Unknown error"}`)}}return{loaded:_,errors:Y}}function s2(f={}){return{name:"env-plugin",setup(g){O2(f)}}}function Kf(f={}){let g=S(f.env)||S("development")||S(process.env.DOTENV_ENV)||S(process.env.APP_ENV)||"development",B=f.cwd||process.cwd();if(r!==g)r=g,d=null,u=void 0;let X=[],$=(W)=>{if(!X.includes(W)&&k(m(B,W)))X.push(W)};$(".env"),$(".env.local");let D=`.env.${g}`,H=`.env.${g}.local`;return $(D),$(H),O2({...f,path:X,env:g})}var Pf=s2;import{existsSync as V,readFileSync as q,renameSync as s,rmSync as Z2,writeFileSync as C}from"fs";import{basename as a,dirname as Q2,resolve as L}from"path";function uf(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 R2(f,g,B,X){let $=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${B}="${g}"`,""],D=!1;for(let H of f.split(`
|
|
5
5
|
`)){let W=H.trim();if(W.startsWith("#/"))continue;if(W.startsWith("DOTENV_PUBLIC_KEY"))continue;if(!W&&!D)continue;if(!W||W.startsWith("#")){D=!0,$.push(H);continue}D=!0;let A=W.match(/^([^=]+)=(.*)$/);if(!A||A[1]===void 0||A[2]===void 0){$.push(H);continue}let Y=A[1].trim(),_=A[2].trim();if(_.startsWith('"')&&_.endsWith('"')||_.startsWith("'")&&_.endsWith("'"))_=_.slice(1,-1);if((!X.key||Y.includes(X.key))&&(!X.excludeKey||!Y.includes(X.excludeKey))&&!_.startsWith("encrypted:"))_=c(_,g);$.push(`${Y}="${_}"`)}return $.join(`
|
|
6
|
-
`)}function e(f){let B=((f||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase();return{publicKeyName:B?`DOTENV_PUBLIC_KEY_${B}`:"DOTENV_PUBLIC_KEY",privateKeyName:B?`DOTENV_PRIVATE_KEY_${B}`:"DOTENV_PRIVATE_KEY"}}function
|
|
6
|
+
`)}function e(f){let B=((f||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase();return{publicKeyName:B?`DOTENV_PUBLIC_KEY_${B}`:"DOTENV_PUBLIC_KEY",privateKeyName:B?`DOTENV_PRIVATE_KEY_${B}`:"DOTENV_PRIVATE_KEY"}}function rf(f={}){let g=f.cwd||process.cwd(),B=L(g,f.file||".env"),X=L(g,f.keysFile||".env.keys");if(!V(B))return{success:!1,error:`File not found: ${B}`};try{let $,D;if(V(X)){let A=q(X,"utf-8"),{parsed:Y}=M(A),{publicKeyName:_,privateKeyName:j}=e(f.file);if($=Y[_]||"",D=Y[j]||"",!$||!D){let Z=K();$=Z.publicKey,D=Z.privateKey;let G=`
|
|
7
7
|
${_}="${$}"
|
|
8
8
|
${j}="${D}"
|
|
9
|
-
`;
|
|
9
|
+
`;C(X,A+G,"utf-8")}}else{let A=K();$=A.publicKey,D=A.privateKey;let{publicKeyName:Y,privateKeyName:_}=e(f.file),j=`# .env.keys - Keep this file secure and never commit to source control
|
|
10
10
|
${Y}="${$}"
|
|
11
11
|
${_}="${D}"
|
|
12
|
-
`;
|
|
13
|
-
\u2714 key added to ${f.keysFile||".env.keys"}`}}catch($){return{success:!1,error:`Failed to encrypt: ${$ instanceof Error?$.message:"Unknown error"}`}}}function
|
|
14
|
-
`),
|
|
15
|
-
`);if(f.stdout)return{success:!0,output:
|
|
16
|
-
`),A=!1,Y,
|
|
12
|
+
`;C(X,j,"utf-8")}let H=q(B,"utf-8"),W=R2(H,$,e(f.file).publicKeyName,f);if(f.stdout)return{success:!0,output:W};return C(B,W,"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 a2(f={}){let g=f.cwd||process.cwd(),B=L(g,f.file||".env"),X=L(g,f.keysFile||".env.keys");if(!V(B))return{success:!1,error:`File not found: ${B}`};if(!V(X))return{success:!1,error:`Keys file not found: ${X}`};try{let $=q(X,"utf-8"),{parsed:D}=M($),A=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),Y=A?`DOTENV_PRIVATE_KEY_${A}`:"DOTENV_PRIVATE_KEY",_=D[Y];if(!_)return{success:!1,error:`Private key not found: ${Y}`};let j=[_];if(Y!=="DOTENV_PRIVATE_KEY"&&D.DOTENV_PRIVATE_KEY&&D.DOTENV_PRIVATE_KEY!==_)j.push(D.DOTENV_PRIVATE_KEY);let G=q(B,"utf-8").split(`
|
|
14
|
+
`),Q=[];for(let T of G){let I=T.trim();if(!I||I.startsWith("#")){Q.push(T);continue}if(I.startsWith("DOTENV_PUBLIC_KEY"))continue;let R=I.match(/^([^=]+)=(.*)$/);if(!R||R[1]===void 0||R[2]===void 0){Q.push(T);continue}let U=R[1].trim(),J=R[2].trim();if(J.startsWith('"')&&J.endsWith('"')||J.startsWith("'")&&J.endsWith("'"))J=J.slice(1,-1);let z=J.startsWith("encrypted:");if(f.key&&!U.includes(f.key))z=!1;if(z){let x,F;for(let V2 of j)try{x=h(J,V2);break}catch(C2){F=C2}if(x===void 0)throw F instanceof Error?F:Error(`Unable to decrypt ${U}`);J=x}Q.push(`${U}="${J}"`)}let w=Q.join(`
|
|
15
|
+
`);if(f.stdout)return{success:!0,output:w};return C(B,w,"utf-8"),{success:!0,output:`\u2714 decrypted (${f.file||".env"})`}}catch($){return{success:!1,error:`Failed to decrypt: ${$ instanceof Error?$.message:"Unknown error"}`}}}function tf(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(V($))H=q($,"utf-8");let W=H.split(`
|
|
16
|
+
`),A=!1,Y,Z=((B.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),G=Z?`DOTENV_PUBLIC_KEY_${Z}`:"DOTENV_PUBLIC_KEY",Q=Z?`DOTENV_PRIVATE_KEY_${Z}`:"DOTENV_PRIVATE_KEY";for(let I of W){let R=I.trim();if(R.startsWith(`${G}=`)){let U=R.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(U)Y=U[1];break}}if(Y){let I=!1;if(V(D)){let R=q(D,"utf-8"),{parsed:U}=M(R);I=Boolean(U[Q])}if(!I)Y=void 0}if(!B.plain&&!Y){let I=K();Y=I.publicKey;let R="";if(V(D))R=q(D,"utf-8");R+=`
|
|
17
17
|
${G}="${I.publicKey}"
|
|
18
18
|
${Q}="${I.privateKey}"
|
|
19
|
-
`,
|
|
20
|
-
`);return
|
|
21
|
-
`);break;case"json":default:G=g.prettyPrint?JSON.stringify(
|
|
22
|
-
`),Y=f.file||".env",_=a(Y).replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),j=_?`DOTENV_PUBLIC_KEY_${_}`:"DOTENV_PUBLIC_KEY",
|
|
19
|
+
`,C(D,R,"utf-8");for(let U=W.length-1;U>=0;U--){let J=W[U];if(J!==void 0&&J.trim().startsWith(`${G}=`))W.splice(U,1)}W.unshift(`${G}="${Y}"`)}let w=g;if(!B.plain&&Y)w=c(g,Y);for(let I=0;I<W.length;I++){let R=W[I];if(R===void 0)continue;if(R.trim().startsWith(`${f}=`)){W[I]=`${f}="${w}"`,A=!0;break}}if(!A)W.push(`${f}="${w}"`);let T=W.join(`
|
|
20
|
+
`);return C($,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 yf(f,g={}){let B=g.cwd||process.cwd(),X=L(B,g.file||".env");if(!V(X))return{success:!1,error:`File not found: ${X}`};try{let $,D=L(B,g.keysFile||".env.keys"),A=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),Y=A?`DOTENV_PRIVATE_KEY_${A}`:"DOTENV_PRIVATE_KEY";if(V(D)){let Q=q(D,"utf-8"),{parsed:w}=M(Q);$=w[Y]}if(!$)$=process.env[Y];let _=q(X,"utf-8"),{parsed:j}=M(_,{privateKey:$});if(f&&!g.all){let Q=j[f];if(Q===void 0)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:Q}}let Z=g.all?{...process.env,...j}:j,G;switch(g.format){case"shell":G=Object.entries(Z).map(([Q,w])=>`${Q}=${w}`).join(" ");break;case"eval":G=Object.entries(Z).map(([Q,w])=>`${Q}="${w}"`).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 vf(f,g={}){let B=g.cwd||process.cwd(),X=L(B,g.keysFile||".env.keys");if(!V(X))return{success:!1,error:`Keys file not found: ${X}`};try{let $=q(X,"utf-8"),{parsed:D}=M($),A=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),Y=A?`DOTENV_PUBLIC_KEY_${A}`:"DOTENV_PUBLIC_KEY",_=A?`DOTENV_PRIVATE_KEY_${A}`:"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={[Y]:D[Y],[_]:D[_]};return{success:!0,output:g.format==="shell"?`${Y}=${j[Y]} ${_}=${j[_]}`:JSON.stringify(j)}}catch($){return{success:!1,error:`Failed to get keypair: ${$ instanceof Error?$.message:"Unknown error"}`}}}function nf(f={}){let g=a2({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=K(),H=q(X),W=q($),A=W.toString("utf8").split(`
|
|
22
|
+
`),Y=f.file||".env",_=a(Y).replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),j=_?`DOTENV_PUBLIC_KEY_${_}`:"DOTENV_PUBLIC_KEY",Z=_?`DOTENV_PRIVATE_KEY_${_}`:"DOTENV_PRIVATE_KEY",G=!1,Q=!1;for(let J=0;J<A.length;J++){let z=A[J];if(z===void 0)continue;if(z.startsWith(`${j}=`))A[J]=`${j}="${D.publicKey}"`,G=!0;else if(z.startsWith(`${Z}=`))A[J]=`${Z}="${D.privateKey}"`,Q=!0}if(!G)A.push(`${j}="${D.publicKey}"`);if(!Q)A.push(`${Z}="${D.privateKey}"`);let w=`${A.join(`
|
|
23
23
|
`).replace(/\n+$/,"")}
|
|
24
|
-
`,T=R2(g.output,D.publicKey,j,f),I=`${process.pid}-${Date.now()}`,R=L(Q2(X),`.${a(X)}.${I}.rotate`),
|
|
25
|
-
`).map((g)=>g.trim()).filter(Boolean).filter((g)=>g!==".env.example").filter((g)=>/(?:^|\/)\.env(?:\.|$)/.test(g)).filter((g)=>!g.endsWith(".env.keys"))}function L2(f){return`${f.replace(/[^a-z0-9]/gi,"").toUpperCase()}_`}function
|
|
24
|
+
`,T=R2(g.output,D.publicKey,j,f),I=`${process.pid}-${Date.now()}`,R=L(Q2(X),`.${a(X)}.${I}.rotate`),U=L(Q2($),`.${a($)}.${I}.rotate`);try{if(C(R,T,{encoding:"utf8",mode:384}),C(U,w,{encoding:"utf8",mode:384}),f.stdout)return s(U,$),{success:!0,output:T};s(R,X);try{s(U,$)}catch(J){throw C(X,H),J}return{success:!0,output:`\u2714 rotated (${f.file||".env"}) to encrypted:v2`}}catch(J){if(!V($))C($,W,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${J instanceof Error?J.message:"Unknown error"}`}}finally{Z2(R,{force:!0}),Z2(U,{force:!0})}}import O from"process";import{platform as e2}from"os";var I2=typeof Bun<"u",w2=typeof O<"u"&&O.versions?.node!==void 0,ff=I2?"bun":w2?"node":"unknown",sf={name:ff,version:I2?Bun.version:w2?O.version:void 0},g2=e2(),af=g2==="win32",ef=g2==="darwin",f5=g2==="linux",U2=Boolean(O.stdout?.isTTY),g5=typeof globalThis.window<"u",gf=Boolean(O.env.CI||O.env.CONTINUOUS_INTEGRATION||O.env.BUILD_NUMBER||O.env.RUN_ID),B5=Boolean(O.env.DEBUG||O.env.VERBOSE||O.argv.includes("--debug")||O.argv.includes("--verbose")),Bf=gf||!U2,$5=Boolean(!Bf&&(U2||O.env.COLORTERM||O.env.FORCE_COLOR||O.env.TERM&&O.env.TERM!=="dumb")),f2={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)}},$f=Object.keys(f2).find((f)=>{let g=f2[f];return g!==void 0&&g.detected})||"unknown",X5=f2[$f]||{name:"Unknown",detected:!1};import Wf from"process";import{projectPath as Yf}from"@stacksjs/path";import M2 from"fs";var T2={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 Xf(f){return typeof f==="string"&&(f.startsWith("encrypted:")||f.startsWith("enc:"))}var Df=/(?:^|_)(?:PASSWORD|PASSWD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|CREDENTIALS|PRIVATE|SALT|DSN|CERT|CIPHER|SIGNATURE|WEBHOOK)(?:_|$)|(?:^|_)(?:ACCESS|SECRET)_KEY(?:_|$)|_KEY$/,Hf=/^DOTENV_(?:PUBLIC|PRIVATE)_KEY/,_f=/^\$\{[^}]+\}$/;function _5(f,g={}){let{placeholders:B={},strict:X=!1}=g,$=[];for(let[D,H]of Object.entries(f)){if(!H||H.trim()==="")continue;if(Xf(H))continue;if(Hf.test(D))continue;if(_f.test(H.trim()))continue;let W=Df.test(D),A=Object.prototype.hasOwnProperty.call(B,D)&&B[D]===H;if(W&&!A){$.push({key:D,reason:"plaintext-secret"});continue}if(X)$.push({key:D,reason:"plaintext-value"})}return $}function A5(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 L2(f){return`${f.replace(/[^a-z0-9]/gi,"").toUpperCase()}_`}function Af(f,g={}){let B=g.self?L2(g.self):void 0,X=(g.tenants??[]).filter((H)=>typeof H==="string"&&H.trim().length>0).map((H)=>({slug:H,prefix:L2(H)})).filter(({prefix:H})=>H!==B).sort((H,W)=>W.prefix.length-H.prefix.length),$={},D={};for(let[H,W]of Object.entries(f)){let A=X.find(({prefix:Y})=>H.startsWith(Y));if(A){(D[A.slug]??={})[H]=W;continue}$[H]=W}return{own:$,foreign:D}}function Y5(f,g={}){return Af(f,g).own}function j5(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 q2=new Set;function jf(f){if(q2.has(f))return;q2.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 Gf={get:(f,g)=>{let B=f[g];if(typeof B==="string"&&(B.startsWith("encrypted:")||B.startsWith("enc:"))){let $=J2(B);if($===void 0){jf(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 Jf(){return typeof Bun<"u"?Bun.env:Wf.env}var z2=new Proxy(Jf(),Gf);function I5(f,g,B){let X=B?.path||Yf(".env"),D=M2.readFileSync(X,"utf-8").split(`
|
|
26
26
|
`),H=D.findIndex((Y)=>Y.startsWith(`${f}=`)),A=/[\s"'#$\\]/.test(g)?`"${g.replace(/"/g,"\\\"")}"`:g;if(H!==-1)D[H]=`${f}=${A}`;else D.push(`${f}=${A}`);M2.writeFileSync(X,D.join(`
|
|
27
|
-
`))}function
|
|
27
|
+
`))}function w5(f=z2){let g=[];for(let[B,X]of Object.entries(T2)){let $=f[B];if($!==void 0&&$!==""&&!X.includes(String($)))g.push(`${B}="${$}" is not valid. Allowed values: ${X.join(", ")}`)}return g}function U5(f,g=z2){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{I5 as writeEnv,w5 as validateEnv,A5 as trackedEnvFiles,L2 as tenantEnvPrefix,Y5 as stripForeignTenantEnv,tf as setEnv,sf as runtimeInfo,ff as runtime,nf as rotateKeypair,l2 as resolvePrivateKey,uf as resolveEnvFile,hf as resetPrivateKeyCache,U5 as requireEnv,X5 as providerInfo,$f as provider,Jf as process,g2 as platform,_5 as plaintextSecrets,Af as partitionTenantEnv,If as parseEnvFromKey,M as parse,Rf as migrateEncryptedValue,zf as loadEnvFiles,O2 as loadEnv,af as isWindows,w2 as isNode,Bf as isMinimal,ef as isMacOS,f5 as isLinux,Qf as isLegacyEncryptedValue,Xf as isEncryptedValue,B5 as isDebug,$5 as isColorSupported,gf as isCI,I2 as isBun,g5 as hasWindow,U2 as hasTTY,o as getPrivateKey,vf as getKeypair,yf as getEnv,K as generateKeypair,j5 as foreignTenantKeys,s2 as envPlugin,e as envKeyNames,T2 as envEnum,z2 as env,c as encryptValue,rf as encryptEnv,h as decryptValue,J2 as decryptEnvValue,a2 as decryptEnv,Kf as autoLoadEnv,Zf as aesEncrypt,x2 as aesDecrypt,l as activeEnvName,Df as SECRET_NAME_PATTERN};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/env",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.72.1",
|
|
6
6
|
"description": "Stacks env helper methods.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"prepublishOnly": "bun run build"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@stacksjs/path": "0.
|
|
58
|
+
"@stacksjs/path": "0.72.1"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"better-dx": "^0.2.23",
|
|
62
|
-
"@stacksjs/validation": "0.
|
|
62
|
+
"@stacksjs/validation": "0.72.1"
|
|
63
63
|
}
|
|
64
64
|
}
|