@stacksjs/env 0.70.88 → 0.70.90

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.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Encrypt .env file
3
+ */
4
+ export declare function encryptEnv(options?: EncryptOptions): { success: boolean, output?: string, error?: string };
5
+ /**
6
+ * Decrypt .env file
7
+ */
8
+ export declare function decryptEnv(options?: DecryptOptions): { success: boolean, output?: string, error?: string };
9
+ /**
10
+ * Set an environment variable
11
+ */
12
+ export declare function setEnv(key: string, value: string, options?: SetOptions): { success: boolean, output?: string, error?: string };
13
+ /**
14
+ * Get environment variable(s)
15
+ */
16
+ export declare function getEnv(key?: string, options?: GetOptions): { success: boolean, output?: string, error?: string };
17
+ /**
18
+ * Get keypair for .env file
19
+ */
20
+ export declare function getKeypair(keyName?: string, options?: { file?: string, keysFile?: string, format?: 'json' | 'shell', cwd?: string }): { success: boolean, output?: string, error?: string };
21
+ /**
22
+ * Rotate keypair and re-encrypt all values
23
+ */
24
+ export declare function rotateKeypair(options?: { file?: string, keysFile?: string, key?: string, excludeKey?: string, stdout?: boolean, cwd?: string }): { success: boolean, output?: string, error?: string };
25
+ export declare interface EncryptOptions {
26
+ file?: string
27
+ keysFile?: string
28
+ key?: string
29
+ excludeKey?: string
30
+ stdout?: boolean
31
+ cwd?: string
32
+ }
33
+ export declare interface DecryptOptions {
34
+ file?: string
35
+ keysFile?: string
36
+ key?: string
37
+ stdout?: boolean
38
+ cwd?: string
39
+ }
40
+ export declare interface SetOptions {
41
+ file?: string
42
+ keysFile?: string
43
+ plain?: boolean
44
+ cwd?: string
45
+ }
46
+ export declare interface GetOptions {
47
+ file?: string
48
+ keysFile?: string
49
+ all?: boolean
50
+ format?: 'json' | 'shell' | 'eval'
51
+ prettyPrint?: boolean
52
+ cwd?: string
53
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,23 @@
1
+ // @bun
2
+ import{createCipheriv as S,createDecipheriv as b,createHash as f,randomBytes as x}from"crypto";function P(j,I){let Y=x(16),X=I.length===32?I:Buffer.from(I.toString("hex").slice(0,64),"hex"),q=S("aes-256-gcm",X,Y),Q=Buffer.concat([q.update(j,"utf8"),q.final()]),U=q.getAuthTag();return{ciphertext:Q.toString("hex"),iv:Y.toString("hex"),authTag:U.toString("hex")}}function N(j,I,Y,X){let q=I.length===32?I:Buffer.from(I.toString("hex").slice(0,64),"hex");try{let Q=b("aes-256-gcm",q,Buffer.from(Y,"hex"));return Q.setAuthTag(Buffer.from(X,"hex")),Buffer.concat([Q.update(Buffer.from(j,"hex")),Q.final()]).toString("utf8")}catch(Q){throw Error(`Decryption failed (data may be corrupted or key is incorrect): ${Q instanceof Error?Q.message:String(Q)}`)}}function F(){let j=x(32);return{publicKey:f("sha256").update(j).digest().toString("hex"),privateKey:j.toString("hex")}}function V(j,I){let Y=f("sha256").update(Buffer.from(I,"hex")).digest(),{ciphertext:X,iv:q,authTag:Q}=P(j,Y);return`encrypted:${Buffer.concat([Buffer.from(q,"hex"),Buffer.from(Q,"hex"),Buffer.from(X,"hex")]).toString("base64")}`}function w(j,I){if(!j.startsWith("encrypted:"))return j;let Y=j.slice(10),X=Buffer.from(Y,"base64");if(X.length<32)throw Error("Invalid encrypted data: payload too short (need at least iv + authTag = 32 bytes)");let q=X.subarray(0,16).toString("hex"),Q=X.subarray(16,32).toString("hex"),U=X.subarray(32).toString("hex"),$=f("sha256").update(Buffer.from(I,"hex")).digest(),Z=f("sha256").update($).digest();return N(U,Z,q,Q)}function v(j){let I=j.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return I&&I[1]?I[1].toLowerCase():""}function c(j=""){if(!j)return process.env.DOTENV_PRIVATE_KEY;let I=`DOTENV_PRIVATE_KEY_${j.toUpperCase()}`;return process.env[I]}function C(j,I={}){let Y={},X=[],q=j.split(`
3
+ `);for(let Q of q){let U=Q.trim();if(!U||U.startsWith("#"))continue;if(U.startsWith("DOTENV_PUBLIC_KEY=")){let A=U.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(A&&A[1]!==void 0)Y.DOTENV_PUBLIC_KEY=A[1];continue}let $=U.match(/^([^=]+)=(.*)$/);if(!$||$[1]===void 0||$[2]===void 0)continue;let Z=$[1].trim(),J=$[2].trim();if(J.startsWith('"')&&J.endsWith('"')||J.startsWith("'")&&J.endsWith("'")){if(J=J.slice(1,-1),J.includes("\\n"))J=J.replace(/\\n/g,`
4
+ `)}if(I.privateKey&&(J.startsWith("encrypted:")||J.startsWith("enc:")))try{let A=J.startsWith("enc:")?`encrypted:${J.slice(4)}`:J;J=w(A,I.privateKey)}catch(A){X.push(`Failed to decrypt ${Z}: ${A instanceof Error?A.message:"Unknown error"}`)}J=K(J,{...I.processEnv||process.env,...Y}),J=h(J),Y[Z]=J}return{parsed:Y,errors:X}}function K(j,I){return j.replace(/\$\{([^}]+)\}/g,(Y,X)=>{let q=X.match(/^([^:\-+]+)(:-|-)(.+)$/);if(q){let[,U,$,Z]=q,J=I[U];if($===":-")return J||Z;else return J!==void 0?J:Z}let Q=X.match(/^([^:\-+]+)(:?\+)(.+)$/);if(Q){let[,U,$,Z]=Q,J=I[U];if($===":+")return J?Z:"";else return J!==void 0?Z:""}return I[X]||""})}var m=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function h(j){return j.replace(/\$\(([^)]+)\)/g,(I,Y)=>{try{let X=Y.trim().split(/\s+/),q=X[0];if(!q||!m.has(q))return console.warn(`[env] Blocked command substitution for disallowed command: ${q}`),"";let Q=Bun.spawnSync(X,{stdout:"pipe",stderr:"pipe"});if(Q.exitCode===0)return new TextDecoder().decode(Q.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${Q.exitCode}): ${q}`)}catch(X){console.warn(`[env] Command substitution error: ${X instanceof Error?X.message:String(X)}`)}return""})}async function s(j,I={}){let Y={},X=[];for(let q of j)try{let Q=Bun.file(q);if(!Q.size)continue;let U=await Q.text(),{parsed:$,errors:Z}=C(U,I);X.push(...Z);for(let[J,A]of Object.entries($)){if(!I.overload&&Y[J]!==void 0)continue;Y[J]=A}}catch(Q){if(Q?.code==="ENOENT")continue;X.push(`Failed to read ${q}: ${Q instanceof Error?Q.message:String(Q)}`)}return{parsed:Y,errors:X}}import{existsSync as g,readFileSync as D,writeFileSync as E}from"fs";import{resolve as M}from"path";function k(j={}){let I=j.cwd||process.cwd(),Y=M(I,j.file||".env"),X=M(I,j.keysFile||".env.keys");if(!g(Y))return{success:!1,error:`File not found: ${Y}`};try{let q,Q;if(g(X)){let G=D(X,"utf-8"),{parsed:O}=C(G),W=((j.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),z=W?`DOTENV_PUBLIC_KEY_${W}`:"DOTENV_PUBLIC_KEY",R=W?`DOTENV_PRIVATE_KEY_${W}`:"DOTENV_PRIVATE_KEY";if(q=O[z]||"",Q=O[R]||"",!q||!Q){let _=F();q=_.publicKey,Q=_.privateKey;let L=`
5
+ ${z}="${q}"
6
+ ${R}="${Q}"
7
+ `;E(X,G+L,"utf-8")}}else{let G=F();q=G.publicKey,Q=G.privateKey;let B=((j.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),W=B?`DOTENV_PUBLIC_KEY_${B}`:"DOTENV_PUBLIC_KEY",z=B?`DOTENV_PRIVATE_KEY_${B}`:"DOTENV_PRIVATE_KEY",R=`# .env.keys - Keep this file secure and never commit to source control
8
+ ${W}="${q}"
9
+ ${z}="${Q}"
10
+ `;E(X,R,"utf-8")}let $=D(Y,"utf-8").split(`
11
+ `),Z=[],J=j.file?j.file.replace(/^\.env\./,"").toUpperCase():"",A=J?`DOTENV_PUBLIC_KEY_${J}`:"DOTENV_PUBLIC_KEY";Z.push("#/-------------------[DOTENV_PUBLIC_KEY]--------------------/"),Z.push("#/ public-key encryption for .env files /"),Z.push("#/ [how it works](https://stacksjs.com/encryption) /"),Z.push("#/----------------------------------------------------------/"),Z.push(`${A}="${q}"`),Z.push("");for(let G of $){let O=G.trim();if(!O||O.startsWith("#")){Z.push(G);continue}if(O.startsWith("DOTENV_PUBLIC_KEY"))continue;let H=O.match(/^([^=]+)=(.*)$/);if(!H||H[1]===void 0||H[2]===void 0){Z.push(G);continue}let B=H[1].trim(),W=H[2].trim();if(W.startsWith('"')&&W.endsWith('"')||W.startsWith("'")&&W.endsWith("'"))W=W.slice(1,-1);let z=!0;if(j.key&&!B.includes(j.key))z=!1;if(j.excludeKey&&B.includes(j.excludeKey))z=!1;if(W.startsWith("encrypted:"))z=!1;if(z)W=V(W,q);Z.push(`${B}="${W}"`)}let T=Z.join(`
12
+ `);if(j.stdout)return{success:!0,output:T};return E(Y,T,"utf-8"),{success:!0,output:`\u2714 encrypted (${j.file||".env"})
13
+ \u2714 key added to ${j.keysFile||".env.keys"}`}}catch(q){return{success:!1,error:`Failed to encrypt: ${q instanceof Error?q.message:"Unknown error"}`}}}function d(j={}){let I=j.cwd||process.cwd(),Y=M(I,j.file||".env"),X=M(I,j.keysFile||".env.keys");if(!g(Y))return{success:!1,error:`File not found: ${Y}`};if(!g(X))return{success:!1,error:`Keys file not found: ${X}`};try{let q=D(X,"utf-8"),{parsed:Q}=C(q),Z=((j.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),J=Z?`DOTENV_PRIVATE_KEY_${Z}`:"DOTENV_PRIVATE_KEY",A=Q[J];if(!A)return{success:!1,error:`Private key not found: ${J}`};let G=D(Y,"utf-8").split(`
14
+ `),O=[];for(let B of G){let W=B.trim();if(!W||W.startsWith("#")){O.push(B);continue}if(W.startsWith("DOTENV_PUBLIC_KEY"))continue;let z=W.match(/^([^=]+)=(.*)$/);if(!z||z[1]===void 0||z[2]===void 0){O.push(B);continue}let R=z[1].trim(),_=z[2].trim();if(_.startsWith('"')&&_.endsWith('"')||_.startsWith("'")&&_.endsWith("'"))_=_.slice(1,-1);let L=_.startsWith("encrypted:");if(j.key&&!R.includes(j.key))L=!1;if(L)_=w(_,A);O.push(`${R}="${_}"`)}let H=O.join(`
15
+ `);if(j.stdout)return{success:!0,output:H};return E(Y,H,"utf-8"),{success:!0,output:`\u2714 decrypted (${j.file||".env"})`}}catch(q){return{success:!1,error:`Failed to decrypt: ${q instanceof Error?q.message:"Unknown error"}`}}}function i(j,I,Y={}){let X=Y.cwd||process.cwd(),q=M(X,Y.file||".env"),Q=M(X,Y.keysFile||".env.keys");try{let U="";if(g(q))U=D(q,"utf-8");let $=U.split(`
16
+ `),Z=!1,J,G=((Y.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),O=G?`DOTENV_PUBLIC_KEY_${G}`:"DOTENV_PUBLIC_KEY",H=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY";for(let z of $){let R=z.trim();if(R.startsWith(`${O}=`)){let _=R.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(_)J=_[1];break}}if(J){let z=!1;if(g(Q)){let R=D(Q,"utf-8"),{parsed:_}=C(R);z=Boolean(_[H])}if(!z)J=void 0}if(!Y.plain&&!J){let z=F();J=z.publicKey;let R="";if(g(Q))R=D(Q,"utf-8");R+=`
17
+ ${O}="${z.publicKey}"
18
+ ${H}="${z.privateKey}"
19
+ `,E(Q,R,"utf-8");for(let _=$.length-1;_>=0;_--){let L=$[_];if(L!==void 0&&L.trim().startsWith(`${O}=`))$.splice(_,1)}$.unshift(`${O}="${J}"`)}let B=I;if(!Y.plain&&J)B=V(I,J);for(let z=0;z<$.length;z++){let R=$[z];if(R===void 0)continue;if(R.trim().startsWith(`${j}=`)){$[z]=`${j}="${B}"`,Z=!0;break}}if(!Z)$.push(`${j}="${B}"`);let W=$.join(`
20
+ `);return E(q,W,"utf-8"),{success:!0,output:`set ${j}${Y.plain?"":" with encryption"} (${Y.file||".env"})`}}catch(U){return{success:!1,error:`Failed to set: ${U instanceof Error?U.message:"Unknown error"}`}}}function t(j,I={}){let Y=I.cwd||process.cwd(),X=M(Y,I.file||".env");if(!g(X))return{success:!1,error:`File not found: ${X}`};try{let q,Q=M(Y,I.keysFile||".env.keys"),Z=((I.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),J=Z?`DOTENV_PRIVATE_KEY_${Z}`:"DOTENV_PRIVATE_KEY";if(g(Q)){let H=D(Q,"utf-8"),{parsed:B}=C(H);q=B[J]}if(!q)q=process.env[J];let A=D(X,"utf-8"),{parsed:T}=C(A,{privateKey:q});if(j&&!I.all){let H=T[j];if(H===void 0)return{success:!1,error:`Key not found: ${j}`};return{success:!0,output:H}}let G=I.all?{...process.env,...T}:T,O;switch(I.format){case"shell":O=Object.entries(G).map(([H,B])=>`${H}=${B}`).join(" ");break;case"eval":O=Object.entries(G).map(([H,B])=>`${H}="${B}"`).join(`
21
+ `);break;case"json":default:O=I.prettyPrint?JSON.stringify(G,null,2):JSON.stringify(G);break}return{success:!0,output:O}}catch(q){return{success:!1,error:`Failed to get: ${q instanceof Error?q.message:"Unknown error"}`}}}function e(j,I={}){let Y=I.cwd||process.cwd(),X=M(Y,I.keysFile||".env.keys");if(!g(X))return{success:!1,error:`Keys file not found: ${X}`};try{let q=D(X,"utf-8"),{parsed:Q}=C(q),Z=((I.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),J=Z?`DOTENV_PUBLIC_KEY_${Z}`:"DOTENV_PUBLIC_KEY",A=Z?`DOTENV_PRIVATE_KEY_${Z}`:"DOTENV_PRIVATE_KEY";if(j){let O=Q[j];if(!O)return{success:!1,error:`Key not found: ${j}`};return{success:!0,output:O}}let T={[J]:Q[J],[A]:Q[A]};return{success:!0,output:I.format==="shell"?`${J}=${T[J]} ${A}=${T[A]}`:JSON.stringify(T)}}catch(q){return{success:!1,error:`Failed to get keypair: ${q instanceof Error?q.message:"Unknown error"}`}}}function jj(j={}){let I=d({file:j.file,keysFile:j.keysFile,stdout:!0,cwd:j.cwd});if(!I.success)return I;let Y=j.cwd||process.cwd(),X=M(Y,j.file||".env"),q=M(Y,j.keysFile||".env.keys");if(I.output)E(X,I.output,"utf-8");let Q=F(),$=D(q,"utf-8").split(`
22
+ `),Z=j.file?j.file.replace(/^\.env\./,"").toUpperCase():"",J=Z?`DOTENV_PUBLIC_KEY_${Z}`:"DOTENV_PUBLIC_KEY",A=Z?`DOTENV_PRIVATE_KEY_${Z}`:"DOTENV_PRIVATE_KEY";for(let T=0;T<$.length;T++){let G=$[T];if(G===void 0)continue;if(G.startsWith(`${J}=`))$[T]=`${J}="${Q.publicKey}"`;else if(G.startsWith(`${A}=`))$[T]=`${A}="${Q.privateKey}"`}return E(q,$.join(`
23
+ `),"utf-8"),k({file:j.file,keysFile:j.keysFile,key:j.key,excludeKey:j.excludeKey,stdout:j.stdout,cwd:j.cwd})}export{i as setEnv,jj as rotateKeypair,e as getKeypair,t as getEnv,k as encryptEnv,d as decryptEnv};
@@ -0,0 +1,13 @@
1
+ // AES-256-GCM encryption using Node.js crypto
2
+ export declare function aesEncrypt(plaintext: string, key: Buffer): { ciphertext: string, iv: string, authTag: string };
3
+ export declare function aesDecrypt(ciphertext: string, key: Buffer, iv: string, authTag: string): string;
4
+ // secp256k1 key generation
5
+ export declare function generateKeypair(): { publicKey: string, privateKey: string };
6
+ // Encrypt a value using public key
7
+ export declare function encryptValue(value: string, publicKey: string): string;
8
+ // Decrypt a value using private key
9
+ export declare function decryptValue(encryptedValue: string, privateKey: string): string;
10
+ // Parse environment name from private key variable
11
+ export declare function parseEnvFromKey(keyName: string): string;
12
+ // Get appropriate private key for environment
13
+ export declare function getPrivateKey(env?: string): string | undefined;
package/dist/crypto.js ADDED
@@ -0,0 +1,2 @@
1
+ // @bun
2
+ import{createCipheriv as P,createDecipheriv as Q,createHash as J,randomBytes as L}from"crypto";function R(q,j){let F=L(16),z=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex"),A=P("aes-256-gcm",z,F),w=Buffer.concat([A.update(q,"utf8"),A.final()]),I=A.getAuthTag();return{ciphertext:w.toString("hex"),iv:F.toString("hex"),authTag:I.toString("hex")}}function S(q,j,F,z){let A=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex");try{let w=Q("aes-256-gcm",A,Buffer.from(F,"hex"));return w.setAuthTag(Buffer.from(z,"hex")),Buffer.concat([w.update(Buffer.from(q,"hex")),w.final()]).toString("utf8")}catch(w){throw Error(`Decryption failed (data may be corrupted or key is incorrect): ${w instanceof Error?w.message:String(w)}`)}}function W(){let q=L(32);return{publicKey:J("sha256").update(q).digest().toString("hex"),privateKey:q.toString("hex")}}function X(q,j){let F=J("sha256").update(Buffer.from(j,"hex")).digest(),{ciphertext:z,iv:A,authTag:w}=R(q,F);return`encrypted:${Buffer.concat([Buffer.from(A,"hex"),Buffer.from(w,"hex"),Buffer.from(z,"hex")]).toString("base64")}`}function Y(q,j){if(!q.startsWith("encrypted:"))return q;let F=q.slice(10),z=Buffer.from(F,"base64");if(z.length<32)throw Error("Invalid encrypted data: payload too short (need at least iv + authTag = 32 bytes)");let A=z.subarray(0,16).toString("hex"),w=z.subarray(16,32).toString("hex"),I=z.subarray(32).toString("hex"),M=J("sha256").update(Buffer.from(j,"hex")).digest(),O=J("sha256").update(M).digest();return S(I,O,A,w)}function Z(q){let j=q.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return j&&j[1]?j[1].toLowerCase():""}function _(q=""){if(!q)return process.env.DOTENV_PRIVATE_KEY;let j=`DOTENV_PRIVATE_KEY_${q.toUpperCase()}`;return process.env[j]}export{Z as parseEnvFromKey,_ as getPrivateKey,W as generateKeypair,X as encryptValue,Y as decryptValue,R as aesEncrypt,S as aesDecrypt};
@@ -0,0 +1,27 @@
1
+ import type { EnvKey } from '../../../env';
2
+ import type { StacksEnv } from './types';
3
+ export declare function process(): StacksEnv;
4
+ // eslint-disable-next-line pickier/no-unused-vars
5
+ export declare function writeEnv(key: EnvKey, value: string, options?: { path: string }): void;
6
+ /**
7
+ * Validate environment variables against the envEnum constraints.
8
+ * Returns an array of validation error messages (empty if all valid).
9
+ */
10
+ export declare function validateEnv(envProxy?: StacksEnv): string[];
11
+ /**
12
+ * Assert that every key in `keys` is set on `process.env` (non-empty).
13
+ *
14
+ * Use this at boot to fail fast on misconfigured environments —
15
+ * `requireEnv(['APP_KEY', 'STRIPE_SECRET'])` throws a single error
16
+ * listing every missing key, which is much friendlier than "your
17
+ * Stripe API key is empty" mid-checkout. Returns the env proxy so it
18
+ * can be chained: `const envOk = requireEnv([...])`.
19
+ */
20
+ export declare function requireEnv(keys: ReadonlyArray<string>, envProxy?: StacksEnv): StacksEnv;
21
+ export declare const env: StacksEnv;
22
+ export * from './types';
23
+ export * from './crypto';
24
+ export * from './parser';
25
+ export * from './plugin';
26
+ export * from './cli';
27
+ export * from './utils';
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ // @bun
2
+ import{createCipheriv as a,createDecipheriv as i,createHash as f,randomBytes as K}from"crypto";function n(J,Q){let $=K(16),z=Q.length===32?Q:Buffer.from(Q.toString("hex").slice(0,64),"hex"),Z=a("aes-256-gcm",z,$),X=Buffer.concat([Z.update(J,"utf8"),Z.final()]),Y=Z.getAuthTag();return{ciphertext:X.toString("hex"),iv:$.toString("hex"),authTag:Y.toString("hex")}}function r(J,Q,$,z){let Z=Q.length===32?Q:Buffer.from(Q.toString("hex").slice(0,64),"hex");try{let X=i("aes-256-gcm",Z,Buffer.from($,"hex"));return X.setAuthTag(Buffer.from(z,"hex")),Buffer.concat([X.update(Buffer.from(J,"hex")),X.final()]).toString("utf8")}catch(X){throw Error(`Decryption failed (data may be corrupted or key is incorrect): ${X instanceof Error?X.message:String(X)}`)}}function b(){let J=K(32);return{publicKey:f("sha256").update(J).digest().toString("hex"),privateKey:J.toString("hex")}}function N(J,Q){let $=f("sha256").update(Buffer.from(Q,"hex")).digest(),{ciphertext:z,iv:Z,authTag:X}=n(J,$);return`encrypted:${Buffer.concat([Buffer.from(Z,"hex"),Buffer.from(X,"hex"),Buffer.from(z,"hex")]).toString("base64")}`}function S(J,Q){if(!J.startsWith("encrypted:"))return J;let $=J.slice(10),z=Buffer.from($,"base64");if(z.length<32)throw Error("Invalid encrypted data: payload too short (need at least iv + authTag = 32 bytes)");let Z=z.subarray(0,16).toString("hex"),X=z.subarray(16,32).toString("hex"),Y=z.subarray(32).toString("hex"),W=f("sha256").update(Buffer.from(Q,"hex")).digest(),G=f("sha256").update(W).digest();return r(Y,G,Z,X)}function HJ(J){let Q=J.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return Q&&Q[1]?Q[1].toLowerCase():""}function m(J=""){if(!J)return process.env.DOTENV_PRIVATE_KEY;let Q=`DOTENV_PRIVATE_KEY_${J.toUpperCase()}`;return process.env[Q]}function L(J,Q={}){let $={},z=[],Z=J.split(`
3
+ `);for(let X of Z){let Y=X.trim();if(!Y||Y.startsWith("#"))continue;if(Y.startsWith("DOTENV_PUBLIC_KEY=")){let q=Y.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(q&&q[1]!==void 0)$.DOTENV_PUBLIC_KEY=q[1];continue}let W=Y.match(/^([^=]+)=(.*)$/);if(!W||W[1]===void 0||W[2]===void 0)continue;let G=W[1].trim(),j=W[2].trim();if(j.startsWith('"')&&j.endsWith('"')||j.startsWith("'")&&j.endsWith("'")){if(j=j.slice(1,-1),j.includes("\\n"))j=j.replace(/\\n/g,`
4
+ `)}if(Q.privateKey&&(j.startsWith("encrypted:")||j.startsWith("enc:")))try{let q=j.startsWith("enc:")?`encrypted:${j.slice(4)}`:j;j=S(q,Q.privateKey)}catch(q){z.push(`Failed to decrypt ${G}: ${q instanceof Error?q.message:"Unknown error"}`)}j=o(j,{...Q.processEnv||process.env,...$}),j=t(j),$[G]=j}return{parsed:$,errors:z}}function o(J,Q){return J.replace(/\$\{([^}]+)\}/g,($,z)=>{let Z=z.match(/^([^:\-+]+)(:-|-)(.+)$/);if(Z){let[,Y,W,G]=Z,j=Q[Y];if(W===":-")return j||G;else return j!==void 0?j:G}let X=z.match(/^([^:\-+]+)(:?\+)(.+)$/);if(X){let[,Y,W,G]=X,j=Q[Y];if(W===":+")return j?G:"";else return j!==void 0?G:""}return Q[z]||""})}var s=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function t(J){return J.replace(/\$\(([^)]+)\)/g,(Q,$)=>{try{let z=$.trim().split(/\s+/),Z=z[0];if(!Z||!s.has(Z))return console.warn(`[env] Blocked command substitution for disallowed command: ${Z}`),"";let X=Bun.spawnSync(z,{stdout:"pipe",stderr:"pipe"});if(X.exitCode===0)return new TextDecoder().decode(X.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${X.exitCode}): ${Z}`)}catch(z){console.warn(`[env] Command substitution error: ${z instanceof Error?z.message:String(z)}`)}return""})}async function qJ(J,Q={}){let $={},z=[];for(let Z of J)try{let X=Bun.file(Z);if(!X.size)continue;let Y=await X.text(),{parsed:W,errors:G}=L(Y,Q);z.push(...G);for(let[j,q]of Object.entries(W)){if(!Q.overload&&$[j]!==void 0)continue;$[j]=q}}catch(X){if(X?.code==="ENOENT")continue;z.push(`Failed to read ${Z}: ${X instanceof Error?X.message:String(X)}`)}return{parsed:$,errors:z}}import{existsSync as F}from"fs";import{readFileSync as h}from"fs";import{resolve as w}from"path";function k(J={}){let{path:Q=[".env"],overload:$=!1,env:z,privateKey:Z,keysFile:X,quiet:Y=!1,cwd:W=process.cwd()}=J,G=Array.isArray(Q)?Q:[Q],j=[],q=0,R=Z;if(!R&&X){let A=w(W,X);if(F(A))try{let O=h(A,"utf-8"),{parsed:U}=L(O);if(z){let B=`DOTENV_PRIVATE_KEY_${z.toUpperCase()}`;R=U[B]}else R=U.DOTENV_PRIVATE_KEY}catch(O){j.push(`Failed to load keys file: ${O instanceof Error?O.message:"Unknown error"}`)}}if(!R)R=m(z||"");for(let A of G){let O=w(W,A);if(!F(O)){if(!Y)j.push(`File not found: ${O}`);continue}try{let U=h(O,"utf-8"),{parsed:B,errors:D}=L(U,{privateKey:R,processEnv:process.env});j.push(...D);let H=0;for(let[M,I]of Object.entries(B)){if(M==="DOTENV_PUBLIC_KEY")continue;let T=process.env[M],p=typeof T==="string"&&(T.startsWith("encrypted:")||T.startsWith("enc:"))&&I!==T;if($||T===void 0||p)process.env[M]=I,q++,H++}if(!Y&&!process.env.__ENV_LOADED__)console.log(`[env] loaded ${H}/${Object.keys(B).length} variables from ${A}`),process.env.__ENV_LOADED__="1"}catch(U){j.push(`Failed to load ${O}: ${U instanceof Error?U.message:"Unknown error"}`)}}return{loaded:q,errors:j}}function e(J={}){return{name:"env-plugin",setup(Q){k(J)}}}function CJ(J={}){let Q=J.env||"development",$=J.cwd||process.cwd(),z=[],Z=`.env.${Q}`,X=`.env.${Q}.local`;if(F(w($,X)))z.push(X);if(F(w($,Z)))z.push(Z);if(F(w($,".env.local")))z.push(".env.local");if(F(w($,".env")))z.push(".env");return k({...J,path:z,env:Q})}var gJ=e;import{existsSync as g,readFileSync as C,writeFileSync as E}from"fs";import{resolve as V}from"path";function JJ(J={}){let Q=J.cwd||process.cwd(),$=V(Q,J.file||".env"),z=V(Q,J.keysFile||".env.keys");if(!g($))return{success:!1,error:`File not found: ${$}`};try{let Z,X;if(g(z)){let A=C(z,"utf-8"),{parsed:O}=L(A),D=((J.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),H=D?`DOTENV_PUBLIC_KEY_${D}`:"DOTENV_PUBLIC_KEY",M=D?`DOTENV_PRIVATE_KEY_${D}`:"DOTENV_PRIVATE_KEY";if(Z=O[H]||"",X=O[M]||"",!Z||!X){let I=b();Z=I.publicKey,X=I.privateKey;let T=`
5
+ ${H}="${Z}"
6
+ ${M}="${X}"
7
+ `;E(z,A+T,"utf-8")}}else{let A=b();Z=A.publicKey,X=A.privateKey;let B=((J.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),D=B?`DOTENV_PUBLIC_KEY_${B}`:"DOTENV_PUBLIC_KEY",H=B?`DOTENV_PRIVATE_KEY_${B}`:"DOTENV_PRIVATE_KEY",M=`# .env.keys - Keep this file secure and never commit to source control
8
+ ${D}="${Z}"
9
+ ${H}="${X}"
10
+ `;E(z,M,"utf-8")}let W=C($,"utf-8").split(`
11
+ `),G=[],j=J.file?J.file.replace(/^\.env\./,"").toUpperCase():"",q=j?`DOTENV_PUBLIC_KEY_${j}`:"DOTENV_PUBLIC_KEY";G.push("#/-------------------[DOTENV_PUBLIC_KEY]--------------------/"),G.push("#/ public-key encryption for .env files /"),G.push("#/ [how it works](https://stacksjs.com/encryption) /"),G.push("#/----------------------------------------------------------/"),G.push(`${q}="${Z}"`),G.push("");for(let A of W){let O=A.trim();if(!O||O.startsWith("#")){G.push(A);continue}if(O.startsWith("DOTENV_PUBLIC_KEY"))continue;let U=O.match(/^([^=]+)=(.*)$/);if(!U||U[1]===void 0||U[2]===void 0){G.push(A);continue}let B=U[1].trim(),D=U[2].trim();if(D.startsWith('"')&&D.endsWith('"')||D.startsWith("'")&&D.endsWith("'"))D=D.slice(1,-1);let H=!0;if(J.key&&!B.includes(J.key))H=!1;if(J.excludeKey&&B.includes(J.excludeKey))H=!1;if(D.startsWith("encrypted:"))H=!1;if(H)D=N(D,Z);G.push(`${B}="${D}"`)}let R=G.join(`
12
+ `);if(J.stdout)return{success:!0,output:R};return E($,R,"utf-8"),{success:!0,output:`\u2714 encrypted (${J.file||".env"})
13
+ \u2714 key added to ${J.keysFile||".env.keys"}`}}catch(Z){return{success:!1,error:`Failed to encrypt: ${Z instanceof Error?Z.message:"Unknown error"}`}}}function QJ(J={}){let Q=J.cwd||process.cwd(),$=V(Q,J.file||".env"),z=V(Q,J.keysFile||".env.keys");if(!g($))return{success:!1,error:`File not found: ${$}`};if(!g(z))return{success:!1,error:`Keys file not found: ${z}`};try{let Z=C(z,"utf-8"),{parsed:X}=L(Z),G=((J.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),j=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY",q=X[j];if(!q)return{success:!1,error:`Private key not found: ${j}`};let A=C($,"utf-8").split(`
14
+ `),O=[];for(let B of A){let D=B.trim();if(!D||D.startsWith("#")){O.push(B);continue}if(D.startsWith("DOTENV_PUBLIC_KEY"))continue;let H=D.match(/^([^=]+)=(.*)$/);if(!H||H[1]===void 0||H[2]===void 0){O.push(B);continue}let M=H[1].trim(),I=H[2].trim();if(I.startsWith('"')&&I.endsWith('"')||I.startsWith("'")&&I.endsWith("'"))I=I.slice(1,-1);let T=I.startsWith("encrypted:");if(J.key&&!M.includes(J.key))T=!1;if(T)I=S(I,q);O.push(`${M}="${I}"`)}let U=O.join(`
15
+ `);if(J.stdout)return{success:!0,output:U};return E($,U,"utf-8"),{success:!0,output:`\u2714 decrypted (${J.file||".env"})`}}catch(Z){return{success:!1,error:`Failed to decrypt: ${Z instanceof Error?Z.message:"Unknown error"}`}}}function fJ(J,Q,$={}){let z=$.cwd||process.cwd(),Z=V(z,$.file||".env"),X=V(z,$.keysFile||".env.keys");try{let Y="";if(g(Z))Y=C(Z,"utf-8");let W=Y.split(`
16
+ `),G=!1,j,A=(($.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),O=A?`DOTENV_PUBLIC_KEY_${A}`:"DOTENV_PUBLIC_KEY",U=A?`DOTENV_PRIVATE_KEY_${A}`:"DOTENV_PRIVATE_KEY";for(let H of W){let M=H.trim();if(M.startsWith(`${O}=`)){let I=M.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(I)j=I[1];break}}if(j){let H=!1;if(g(X)){let M=C(X,"utf-8"),{parsed:I}=L(M);H=Boolean(I[U])}if(!H)j=void 0}if(!$.plain&&!j){let H=b();j=H.publicKey;let M="";if(g(X))M=C(X,"utf-8");M+=`
17
+ ${O}="${H.publicKey}"
18
+ ${U}="${H.privateKey}"
19
+ `,E(X,M,"utf-8");for(let I=W.length-1;I>=0;I--){let T=W[I];if(T!==void 0&&T.trim().startsWith(`${O}=`))W.splice(I,1)}W.unshift(`${O}="${j}"`)}let B=Q;if(!$.plain&&j)B=N(Q,j);for(let H=0;H<W.length;H++){let M=W[H];if(M===void 0)continue;if(M.trim().startsWith(`${J}=`)){W[H]=`${J}="${B}"`,G=!0;break}}if(!G)W.push(`${J}="${B}"`);let D=W.join(`
20
+ `);return E(Z,D,"utf-8"),{success:!0,output:`set ${J}${$.plain?"":" with encryption"} (${$.file||".env"})`}}catch(Y){return{success:!1,error:`Failed to set: ${Y instanceof Error?Y.message:"Unknown error"}`}}}function SJ(J,Q={}){let $=Q.cwd||process.cwd(),z=V($,Q.file||".env");if(!g(z))return{success:!1,error:`File not found: ${z}`};try{let Z,X=V($,Q.keysFile||".env.keys"),G=((Q.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),j=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY";if(g(X)){let U=C(X,"utf-8"),{parsed:B}=L(U);Z=B[j]}if(!Z)Z=process.env[j];let q=C(z,"utf-8"),{parsed:R}=L(q,{privateKey:Z});if(J&&!Q.all){let U=R[J];if(U===void 0)return{success:!1,error:`Key not found: ${J}`};return{success:!0,output:U}}let A=Q.all?{...process.env,...R}:R,O;switch(Q.format){case"shell":O=Object.entries(A).map(([U,B])=>`${U}=${B}`).join(" ");break;case"eval":O=Object.entries(A).map(([U,B])=>`${U}="${B}"`).join(`
21
+ `);break;case"json":default:O=Q.prettyPrint?JSON.stringify(A,null,2):JSON.stringify(A);break}return{success:!0,output:O}}catch(Z){return{success:!1,error:`Failed to get: ${Z instanceof Error?Z.message:"Unknown error"}`}}}function NJ(J,Q={}){let $=Q.cwd||process.cwd(),z=V($,Q.keysFile||".env.keys");if(!g(z))return{success:!1,error:`Keys file not found: ${z}`};try{let Z=C(z,"utf-8"),{parsed:X}=L(Z),G=((Q.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),j=G?`DOTENV_PUBLIC_KEY_${G}`:"DOTENV_PUBLIC_KEY",q=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY";if(J){let O=X[J];if(!O)return{success:!1,error:`Key not found: ${J}`};return{success:!0,output:O}}let R={[j]:X[j],[q]:X[q]};return{success:!0,output:Q.format==="shell"?`${j}=${R[j]} ${q}=${R[q]}`:JSON.stringify(R)}}catch(Z){return{success:!1,error:`Failed to get keypair: ${Z instanceof Error?Z.message:"Unknown error"}`}}}function xJ(J={}){let Q=QJ({file:J.file,keysFile:J.keysFile,stdout:!0,cwd:J.cwd});if(!Q.success)return Q;let $=J.cwd||process.cwd(),z=V($,J.file||".env"),Z=V($,J.keysFile||".env.keys");if(Q.output)E(z,Q.output,"utf-8");let X=b(),W=C(Z,"utf-8").split(`
22
+ `),G=J.file?J.file.replace(/^\.env\./,"").toUpperCase():"",j=G?`DOTENV_PUBLIC_KEY_${G}`:"DOTENV_PUBLIC_KEY",q=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY";for(let R=0;R<W.length;R++){let A=W[R];if(A===void 0)continue;if(A.startsWith(`${j}=`))W[R]=`${j}="${X.publicKey}"`;else if(A.startsWith(`${q}=`))W[R]=`${q}="${X.privateKey}"`}return E(Z,W.join(`
23
+ `),"utf-8"),JJ({file:J.file,keysFile:J.keysFile,key:J.key,excludeKey:J.excludeKey,stdout:J.stdout,cwd:J.cwd})}import _ from"process";import{platform as ZJ}from"os";var d=typeof Bun<"u",u=typeof _<"u"&&_.versions?.node!==void 0,$J=d?"bun":u?"node":"unknown",hJ={name:$J,version:d?Bun.version:u?_.version:void 0},P=ZJ(),kJ=P==="win32",dJ=P==="darwin",uJ=P==="linux",c=Boolean(_.stdout?.isTTY),cJ=typeof globalThis.window<"u",zJ=Boolean(_.env.CI||_.env.CONTINUOUS_INTEGRATION||_.env.BUILD_NUMBER||_.env.RUN_ID),vJ=Boolean(_.env.DEBUG||_.env.VERBOSE||_.argv.includes("--debug")||_.argv.includes("--verbose")),XJ=zJ||!c,lJ=Boolean(!XJ&&(c||_.env.COLORTERM||_.env.FORCE_COLOR||_.env.TERM&&_.env.TERM!=="dumb")),x={github:{name:"GitHub Actions",detected:Boolean(_.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(_.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(_.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(_.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(_.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(_.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(_.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(_.env.DYNO)},aws:{name:"AWS",detected:Boolean(_.env.AWS_REGION||_.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(_.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(_.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(_.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(_.env.RENDER)}},jJ=Object.keys(x).find((J)=>{let Q=x[J];return Q!==void 0&&Q.detected})||"unknown",yJ=x[jJ]||{name:"Unknown",detected:!1};import GJ from"process";import{projectPath as WJ}from"@stacksjs/path";import l from"fs";var v={APP_ENV:["local","dev","development","staging","prod","production"],DB_CONNECTION:["mysql","sqlite","postgres","dynamodb"],DB_MIGRATE_FRESH:["allow","confirm","disabled"],MAIL_MAILER:["smtp","mailgun","ses","log","sendgrid","mailtrap"],SEARCH_ENGINE_DRIVER:["opensearch","meilisearch","algolia","typesense"],FRONTEND_APP_ENV:["development","staging","production"]};var OJ={get:(J,Q)=>{let $=J[Q],z=["_PORT","_TIMEOUT","_TTL","_SIZE","_LIMIT","_MAX","_MIN","_INTERVAL","_RETRIES","_CONCURRENCY","_WORKERS","_CONNECTIONS"];if(typeof $==="string"&&/^\d+$/.test($)&&!$.startsWith("0")&&z.some((Z)=>Q.endsWith(Z)))return Number($);if(typeof $==="string"){let Z=$.toLowerCase();if(Z==="true")return!0;if(Z==="false")return!1}return $}};function YJ(){return typeof Bun<"u"?Bun.env:GJ.env}var y=new Proxy(YJ(),OJ);function sJ(J,Q,$){let z=$?.path||WJ(".env"),X=l.readFileSync(z,"utf-8").split(`
24
+ `),Y=X.findIndex((j)=>j.startsWith(`${J}=`)),G=/[\s"'#$\\]/.test(Q)?`"${Q.replace(/"/g,"\\\"")}"`:Q;if(Y!==-1)X[Y]=`${J}=${G}`;else X.push(`${J}=${G}`);l.writeFileSync(z,X.join(`
25
+ `))}function tJ(J=y){let Q=[];for(let[$,z]of Object.entries(v)){let Z=J[$];if(Z!==void 0&&Z!==""&&!z.includes(String(Z)))Q.push(`${$}="${Z}" is not valid. Allowed values: ${z.join(", ")}`)}return Q}function eJ(J,Q=y){let $=[];for(let z of J){let Z=Q[z];if(Z===void 0||Z===""||Z===null)$.push(z)}if($.length>0)throw Error(`[env] Missing required environment variable(s): ${$.join(", ")}. Set them in .env or your process environment before booting.`);return Q}export{sJ as writeEnv,tJ as validateEnv,fJ as setEnv,hJ as runtimeInfo,$J as runtime,xJ as rotateKeypair,eJ as requireEnv,yJ as providerInfo,jJ as provider,YJ as process,P as platform,HJ as parseEnvFromKey,L as parse,qJ as loadEnvFiles,k as loadEnv,kJ as isWindows,u as isNode,XJ as isMinimal,dJ as isMacOS,uJ as isLinux,vJ as isDebug,lJ as isColorSupported,zJ as isCI,d as isBun,cJ as hasWindow,c as hasTTY,m as getPrivateKey,NJ as getKeypair,SJ as getEnv,b as generateKeypair,e as envPlugin,v as envEnum,y as env,N as encryptValue,JJ as encryptEnv,S as decryptValue,QJ as decryptEnv,CJ as autoLoadEnv,n as aesEncrypt,r as aesDecrypt};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Parse a .env file content
3
+ */
4
+ export declare function parse(src: string, options?: ParseOptions): ParseResult;
5
+ /**
6
+ * Load and parse multiple .env files
7
+ */
8
+ export declare function loadEnvFiles(files: string[], options?: ParseOptions & { overload?: boolean }): Promise<ParseResult>;
9
+ export declare interface ParseOptions {
10
+ privateKey?: string
11
+ processEnv?: Record<string, string>
12
+ }
13
+ export declare interface ParseResult {
14
+ parsed: Record<string, string>
15
+ errors: string[]
16
+ }
package/dist/parser.js ADDED
@@ -0,0 +1,4 @@
1
+ // @bun
2
+ import{createCipheriv as G,createDecipheriv as W,createHash as $,randomBytes as w}from"crypto";function A(J,j){let R=w(16),I=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex"),Q=G("aes-256-gcm",I,R),q=Buffer.concat([Q.update(J,"utf8"),Q.final()]),U=Q.getAuthTag();return{ciphertext:q.toString("hex"),iv:R.toString("hex"),authTag:U.toString("hex")}}function O(J,j,R,I){let Q=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex");try{let q=W("aes-256-gcm",Q,Buffer.from(R,"hex"));return q.setAuthTag(Buffer.from(I,"hex")),Buffer.concat([q.update(Buffer.from(J,"hex")),q.final()]).toString("utf8")}catch(q){throw Error(`Decryption failed (data may be corrupted or key is incorrect): ${q instanceof Error?q.message:String(q)}`)}}function B(){let J=w(32);return{publicKey:$("sha256").update(J).digest().toString("hex"),privateKey:J.toString("hex")}}function g(J,j){let R=$("sha256").update(Buffer.from(j,"hex")).digest(),{ciphertext:I,iv:Q,authTag:q}=A(J,R);return`encrypted:${Buffer.concat([Buffer.from(Q,"hex"),Buffer.from(q,"hex"),Buffer.from(I,"hex")]).toString("base64")}`}function z(J,j){if(!J.startsWith("encrypted:"))return J;let R=J.slice(10),I=Buffer.from(R,"base64");if(I.length<32)throw Error("Invalid encrypted data: payload too short (need at least iv + authTag = 32 bytes)");let Q=I.subarray(0,16).toString("hex"),q=I.subarray(16,32).toString("hex"),U=I.subarray(32).toString("hex"),X=$("sha256").update(Buffer.from(j,"hex")).digest(),Y=$("sha256").update(X).digest();return O(U,Y,Q,q)}function D(J){let j=J.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return j&&j[1]?j[1].toLowerCase():""}function H(J=""){if(!J)return process.env.DOTENV_PRIVATE_KEY;let j=`DOTENV_PRIVATE_KEY_${J.toUpperCase()}`;return process.env[j]}function S(J,j={}){let R={},I=[],Q=J.split(`
3
+ `);for(let q of Q){let U=q.trim();if(!U||U.startsWith("#"))continue;if(U.startsWith("DOTENV_PUBLIC_KEY=")){let Z=U.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(Z&&Z[1]!==void 0)R.DOTENV_PUBLIC_KEY=Z[1];continue}let X=U.match(/^([^=]+)=(.*)$/);if(!X||X[1]===void 0||X[2]===void 0)continue;let Y=X[1].trim(),F=X[2].trim();if(F.startsWith('"')&&F.endsWith('"')||F.startsWith("'")&&F.endsWith("'")){if(F=F.slice(1,-1),F.includes("\\n"))F=F.replace(/\\n/g,`
4
+ `)}if(j.privateKey&&(F.startsWith("encrypted:")||F.startsWith("enc:")))try{let Z=F.startsWith("enc:")?`encrypted:${F.slice(4)}`:F;F=z(Z,j.privateKey)}catch(Z){I.push(`Failed to decrypt ${Y}: ${Z instanceof Error?Z.message:"Unknown error"}`)}F=_(F,{...j.processEnv||process.env,...R}),F=L(F),R[Y]=F}return{parsed:R,errors:I}}function _(J,j){return J.replace(/\$\{([^}]+)\}/g,(R,I)=>{let Q=I.match(/^([^:\-+]+)(:-|-)(.+)$/);if(Q){let[,U,X,Y]=Q,F=j[U];if(X===":-")return F||Y;else return F!==void 0?F:Y}let q=I.match(/^([^:\-+]+)(:?\+)(.+)$/);if(q){let[,U,X,Y]=q,F=j[U];if(X===":+")return F?Y:"";else return F!==void 0?Y:""}return j[I]||""})}var C=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function L(J){return J.replace(/\$\(([^)]+)\)/g,(j,R)=>{try{let I=R.trim().split(/\s+/),Q=I[0];if(!Q||!C.has(Q))return console.warn(`[env] Blocked command substitution for disallowed command: ${Q}`),"";let q=Bun.spawnSync(I,{stdout:"pipe",stderr:"pipe"});if(q.exitCode===0)return new TextDecoder().decode(q.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${q.exitCode}): ${Q}`)}catch(I){console.warn(`[env] Command substitution error: ${I instanceof Error?I.message:String(I)}`)}return""})}async function M(J,j={}){let R={},I=[];for(let Q of J)try{let q=Bun.file(Q);if(!q.size)continue;let U=await q.text(),{parsed:X,errors:Y}=S(U,j);I.push(...Y);for(let[F,Z]of Object.entries(X)){if(!j.overload&&R[F]!==void 0)continue;R[F]=Z}}catch(q){if(q?.code==="ENOENT")continue;I.push(`Failed to read ${Q}: ${q instanceof Error?q.message:String(q)}`)}return{parsed:R,errors:I}}export{S as parse,M as loadEnvFiles};
@@ -0,0 +1,24 @@
1
+ import type { BunPlugin } from 'bun';
2
+ /**
3
+ * Load .env files and inject into process.env
4
+ */
5
+ export declare function loadEnv(options?: EnvPluginOptions): { loaded: number, errors: string[] };
6
+ /**
7
+ * Bun plugin for automatic .env loading
8
+ */
9
+ export declare function envPlugin(options?: EnvPluginOptions): BunPlugin;
10
+ /**
11
+ * Auto-detect and load .env files based on environment
12
+ */
13
+ export declare function autoLoadEnv(options?: Omit<EnvPluginOptions, 'path'>): { loaded: number, errors: string[] };
14
+ export declare interface EnvPluginOptions {
15
+ path?: string | string[]
16
+ overload?: boolean
17
+ env?: string
18
+ privateKey?: string
19
+ keysFile?: string
20
+ quiet?: boolean
21
+ cwd?: string
22
+ }
23
+ // Default export for easy usage
24
+ export default envPlugin;
package/dist/plugin.js ADDED
@@ -0,0 +1,4 @@
1
+ // @bun
2
+ import{createCipheriv as K,createDecipheriv as P,createHash as H,randomBytes as w}from"crypto";function V(R,j){let U=w(16),I=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex"),X=K("aes-256-gcm",I,U),J=Buffer.concat([X.update(R,"utf8"),X.final()]),Y=X.getAuthTag();return{ciphertext:J.toString("hex"),iv:U.toString("hex"),authTag:Y.toString("hex")}}function f(R,j,U,I){let X=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex");try{let J=P("aes-256-gcm",X,Buffer.from(U,"hex"));return J.setAuthTag(Buffer.from(I,"hex")),Buffer.concat([J.update(Buffer.from(R,"hex")),J.final()]).toString("utf8")}catch(J){throw Error(`Decryption failed (data may be corrupted or key is incorrect): ${J instanceof Error?J.message:String(J)}`)}}function y(){let R=w(32);return{publicKey:H("sha256").update(R).digest().toString("hex"),privateKey:R.toString("hex")}}function u(R,j){let U=H("sha256").update(Buffer.from(j,"hex")).digest(),{ciphertext:I,iv:X,authTag:J}=V(R,U);return`encrypted:${Buffer.concat([Buffer.from(X,"hex"),Buffer.from(J,"hex"),Buffer.from(I,"hex")]).toString("base64")}`}function b(R,j){if(!R.startsWith("encrypted:"))return R;let U=R.slice(10),I=Buffer.from(U,"base64");if(I.length<32)throw Error("Invalid encrypted data: payload too short (need at least iv + authTag = 32 bytes)");let X=I.subarray(0,16).toString("hex"),J=I.subarray(16,32).toString("hex"),Y=I.subarray(32).toString("hex"),Z=H("sha256").update(Buffer.from(j,"hex")).digest(),$=H("sha256").update(Z).digest();return f(Y,$,X,J)}function c(R){let j=R.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return j&&j[1]?j[1].toLowerCase():""}function F(R=""){if(!R)return process.env.DOTENV_PRIVATE_KEY;let j=`DOTENV_PRIVATE_KEY_${R.toUpperCase()}`;return process.env[j]}function L(R,j={}){let U={},I=[],X=R.split(`
3
+ `);for(let J of X){let Y=J.trim();if(!Y||Y.startsWith("#"))continue;if(Y.startsWith("DOTENV_PUBLIC_KEY=")){let z=Y.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(z&&z[1]!==void 0)U.DOTENV_PUBLIC_KEY=z[1];continue}let Z=Y.match(/^([^=]+)=(.*)$/);if(!Z||Z[1]===void 0||Z[2]===void 0)continue;let $=Z[1].trim(),Q=Z[2].trim();if(Q.startsWith('"')&&Q.endsWith('"')||Q.startsWith("'")&&Q.endsWith("'")){if(Q=Q.slice(1,-1),Q.includes("\\n"))Q=Q.replace(/\\n/g,`
4
+ `)}if(j.privateKey&&(Q.startsWith("encrypted:")||Q.startsWith("enc:")))try{let z=Q.startsWith("enc:")?`encrypted:${Q.slice(4)}`:Q;Q=b(z,j.privateKey)}catch(z){I.push(`Failed to decrypt ${$}: ${z instanceof Error?z.message:"Unknown error"}`)}Q=m(Q,{...j.processEnv||process.env,...U}),Q=k(Q),U[$]=Q}return{parsed:U,errors:I}}function m(R,j){return R.replace(/\$\{([^}]+)\}/g,(U,I)=>{let X=I.match(/^([^:\-+]+)(:-|-)(.+)$/);if(X){let[,Y,Z,$]=X,Q=j[Y];if(Z===":-")return Q||$;else return Q!==void 0?Q:$}let J=I.match(/^([^:\-+]+)(:?\+)(.+)$/);if(J){let[,Y,Z,$]=J,Q=j[Y];if(Z===":+")return Q?$:"";else return Q!==void 0?$:""}return j[I]||""})}var x=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function k(R){return R.replace(/\$\(([^)]+)\)/g,(j,U)=>{try{let I=U.trim().split(/\s+/),X=I[0];if(!X||!x.has(X))return console.warn(`[env] Blocked command substitution for disallowed command: ${X}`),"";let J=Bun.spawnSync(I,{stdout:"pipe",stderr:"pipe"});if(J.exitCode===0)return new TextDecoder().decode(J.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${J.exitCode}): ${X}`)}catch(I){console.warn(`[env] Command substitution error: ${I instanceof Error?I.message:String(I)}`)}return""})}async function i(R,j={}){let U={},I=[];for(let X of R)try{let J=Bun.file(X);if(!J.size)continue;let Y=await J.text(),{parsed:Z,errors:$}=L(Y,j);I.push(...$);for(let[Q,z]of Object.entries(Z)){if(!j.overload&&U[Q]!==void 0)continue;U[Q]=z}}catch(J){if(J?.code==="ENOENT")continue;I.push(`Failed to read ${X}: ${J instanceof Error?J.message:String(J)}`)}return{parsed:U,errors:I}}import{existsSync as A}from"fs";import{readFileSync as E}from"fs";import{resolve as O}from"path";function N(R={}){let{path:j=[".env"],overload:U=!1,env:I,privateKey:X,keysFile:J,quiet:Y=!1,cwd:Z=process.cwd()}=R,$=Array.isArray(j)?j:[j],Q=[],z=0,q=X;if(!q&&J){let _=O(Z,J);if(A(_))try{let G=E(_,"utf-8"),{parsed:W}=L(G);if(I){let C=`DOTENV_PRIVATE_KEY_${I.toUpperCase()}`;q=W[C]}else q=W.DOTENV_PRIVATE_KEY}catch(G){Q.push(`Failed to load keys file: ${G instanceof Error?G.message:"Unknown error"}`)}}if(!q)q=F(I||"");for(let _ of $){let G=O(Z,_);if(!A(G)){if(!Y)Q.push(`File not found: ${G}`);continue}try{let W=E(G,"utf-8"),{parsed:C,errors:S}=L(W,{privateKey:q,processEnv:process.env});Q.push(...S);let D=0;for(let[T,M]of Object.entries(C)){if(T==="DOTENV_PUBLIC_KEY")continue;let B=process.env[T],g=typeof B==="string"&&(B.startsWith("encrypted:")||B.startsWith("enc:"))&&M!==B;if(U||B===void 0||g)process.env[T]=M,z++,D++}if(!Y&&!process.env.__ENV_LOADED__)console.log(`[env] loaded ${D}/${Object.keys(C).length} variables from ${_}`),process.env.__ENV_LOADED__="1"}catch(W){Q.push(`Failed to load ${G}: ${W instanceof Error?W.message:"Unknown error"}`)}}return{loaded:z,errors:Q}}function h(R={}){return{name:"env-plugin",setup(j){N(R)}}}function e(R={}){let j=R.env||"development",U=R.cwd||process.cwd(),I=[],X=`.env.${j}`,J=`.env.${j}.local`;if(A(O(U,J)))I.push(J);if(A(O(U,X)))I.push(X);if(A(O(U,".env.local")))I.push(".env.local");if(A(O(U,".env")))I.push(".env");return N({...R,path:I,env:j})}var Ij=h;export{N as loadEnv,h as envPlugin,Ij as default,e as autoLoadEnv};
@@ -0,0 +1,145 @@
1
+ import type { BooleanValidatorType, EnumValidatorType, NumberValidatorType, StringValidatorType } from '@stacksjs/ts-validation';
2
+ import type { EnvKey } from '../../../env';
3
+ export declare const envEnum: EnumObject;
4
+ declare interface EnumObject {
5
+ [key: string]: string[]
6
+ }
7
+ declare interface StringEnvConfig {
8
+ validation: StringValidatorType
9
+ default: string
10
+ }
11
+ declare interface NumberEnvConfig {
12
+ validation: NumberValidatorType
13
+ default: number
14
+ }
15
+ declare interface BooleanEnvConfig {
16
+ validation: BooleanValidatorType
17
+ default: boolean
18
+ }
19
+ declare interface EnumEnvConfig {
20
+ validation: EnumValidatorType
21
+ default: string
22
+ }
23
+ export declare interface StacksEnv {
24
+ APP_NAME: string | undefined
25
+ APP_ENV: 'local' | 'dev' | 'stage' | 'prod' | undefined
26
+ APP_KEY: string | undefined
27
+ APP_URL: string | undefined
28
+ APP_DOMAIN: string | undefined
29
+ APP_MAINTENANCE: boolean | undefined
30
+ APP_MAINTENANCE_SECRET: string | undefined
31
+ APP_COMING_SOON: boolean | undefined
32
+ APP_COMING_SOON_SECRET: string | undefined
33
+ APP_ROOT: string | undefined
34
+ DEBUG: boolean | undefined
35
+ PORT: number | undefined
36
+ PORT_BACKEND: number | undefined
37
+ PORT_ADMIN: number | undefined
38
+ PORT_LIBRARY: number | undefined
39
+ PORT_DESKTOP: number | undefined
40
+ PORT_EMAIL: number | undefined
41
+ PORT_DOCS: number | undefined
42
+ PORT_INSPECT: number | undefined
43
+ PORT_API: number | undefined
44
+ PORT_SYSTEM_TRAY: number | undefined
45
+ API_PREFIX: string | undefined
46
+ DOCS_PREFIX: string | undefined
47
+ DB_CONNECTION: 'mysql' | 'sqlite' | 'postgres' | undefined
48
+ DB_HOST: string | undefined
49
+ DB_PORT: number | undefined
50
+ DB_DATABASE: string | undefined
51
+ DB_DATABASE_PATH: string | undefined
52
+ DB_USERNAME: string | undefined
53
+ DB_PASSWORD: string | undefined
54
+ DB_PREFIX: string | undefined
55
+ DB_SCHEMA: string | undefined
56
+ DB_QUERY_LOGGING_ENABLED: boolean | undefined
57
+ DB_QUERY_LOGGING_SLOW_THRESHOLD: number | undefined
58
+ DB_QUERY_LOGGING_RETENTION_DAYS: number | undefined
59
+ DB_QUERY_LOGGING_PRUNE_FREQUENCY: number | undefined
60
+ DB_QUERY_LOGGING_ANALYSIS_ENABLED: boolean | undefined
61
+ DB_QUERY_LOGGING_ANALYZE_ALL: boolean | undefined
62
+ DB_QUERY_LOGGING_EXPLAIN_PLAN: boolean | undefined
63
+ DB_QUERY_LOGGING_SUGGESTIONS: boolean | undefined
64
+ DB_MIGRATE_CONFIRM: boolean | undefined
65
+ DB_MIGRATE_FRESH: 'allow' | 'confirm' | 'disabled' | undefined
66
+ DATABASE_URL: string | undefined
67
+ AWS_ACCOUNT_ID: string | undefined
68
+ AWS_ACCESS_KEY_ID: string | undefined
69
+ AWS_SECRET_ACCESS_KEY: string | undefined
70
+ AWS_DEFAULT_REGION: string | undefined
71
+ AWS_DEFAULT_PASSWORD: string | undefined
72
+ AWS_REGION: string | undefined
73
+ AWS_HOSTED_ZONE_ID: string | undefined
74
+ AWS_S3_BUCKET: string | undefined
75
+ AWS_S3_PREFIX: string | undefined
76
+ AWS_SES_REGION: string | undefined
77
+ MAIL_MAILER: string | undefined
78
+ MAIL_HOST: string | undefined
79
+ MAIL_PORT: number | undefined
80
+ MAIL_USERNAME: string | undefined
81
+ MAIL_PASSWORD: string | undefined
82
+ MAIL_ENCRYPTION: string | undefined
83
+ MAIL_FROM_NAME: string | undefined
84
+ MAIL_FROM_ADDRESS: string | undefined
85
+ MAIL_DOMAIN: string | undefined
86
+ MAIL_DRIVER: string | undefined
87
+ MAIL_SERVER_MODE: string | undefined
88
+ MAIL_SERVER_PATH: string | undefined
89
+ SEARCH_ENGINE_DRIVER: string | undefined
90
+ MEILISEARCH_HOST: string | undefined
91
+ MEILISEARCH_KEY: string | undefined
92
+ STRIPE_SECRET_KEY: string | undefined
93
+ STRIPE_PUBLISHABLE_KEY: string | undefined
94
+ FRONTEND_APP_ENV: 'development' | 'staging' | 'production' | undefined
95
+ FRONTEND_APP_URL: string | undefined
96
+ REALTIME_MODE: string | undefined
97
+ BROADCAST_DRIVER: string | undefined
98
+ BROADCAST_HOST: string | undefined
99
+ BROADCAST_PORT: number | undefined
100
+ BROADCAST_SCHEME: string | undefined
101
+ BROADCAST_APP_ID: string | undefined
102
+ BROADCAST_APP_KEY: string | undefined
103
+ BROADCAST_APP_SECRET: string | undefined
104
+ BROADCAST_CORS_ORIGIN: string | undefined
105
+ BROADCAST_DEBUG: boolean | undefined
106
+ BROADCAST_REDIS_ENABLED: boolean | undefined
107
+ BROADCAST_REDIS_PREFIX: string | undefined
108
+ BROADCAST_RATE_LIMIT_ENABLED: boolean | undefined
109
+ BROADCAST_METRICS_ENABLED: boolean | undefined
110
+ REDIS_HOST: string | undefined
111
+ REDIS_PORT: number | undefined
112
+ REDIS_PASSWORD: string | undefined
113
+ PUSHER_APP_ID: string | undefined
114
+ PUSHER_APP_KEY: string | undefined
115
+ PUSHER_APP_SECRET: string | undefined
116
+ PUSHER_APP_CLUSTER: string | undefined
117
+ PUSHER_APP_USE_TLS: boolean | undefined
118
+ SSL_DOMAINS: string | undefined
119
+ LETSENCRYPT_EMAIL: string | undefined
120
+ CONNECT_INSTANCE_ALIAS: string | undefined
121
+ PHONE_NOTIFY_EMAIL: string | undefined
122
+ PHONE_FORWARD_NUMBER: string | undefined
123
+ STORAGE_DRIVER: string | undefined
124
+ STORAGE_ROOT: string | undefined
125
+ STORAGE_PUBLIC_URL: string | undefined
126
+ QUEUE_DRIVER: string | undefined
127
+ AUTH_USERNAME_FIELD: string | undefined
128
+ AUTH_PASSWORD_FIELD: string | undefined
129
+ AUTH_TOKEN_EXPIRY: number | undefined
130
+ AUTH_REFRESH_TOKEN_EXPIRY: number | undefined
131
+ AUTH_TOKEN_ROTATION: number | undefined
132
+ AUTH_PASSWORD_RESET_EXPIRE: number | undefined
133
+ AUTH_PASSWORD_RESET_THROTTLE: number | undefined
134
+ [key: string]: string | number | boolean | undefined
135
+ }
136
+ export declare interface FrontendEnv {
137
+ FRONTEND_APP_ENV: 'local' | 'development' | 'staging' | 'production'
138
+ FRONTEND_APP_URL: string
139
+ }
140
+ declare type EnvValueConfig = StringEnvConfig | NumberEnvConfig | BooleanEnvConfig | EnumEnvConfig;
141
+ export type EnvConfig = Partial<Record<EnvKey, EnvValueConfig>>;
142
+ /** @deprecated Use `StacksEnv` instead */
143
+ export type Env = StacksEnv;
144
+ export type EnvSchema = any;
145
+ export type FrontendEnvKeys = keyof FrontendEnv;
@@ -0,0 +1,24 @@
1
+ // Runtime detection
2
+ export declare const isBun: boolean;
3
+ export declare const isNode: boolean;
4
+ export declare const runtime: 'bun' | 'node' | 'unknown';
5
+ export declare const runtimeInfo: { name: 'bun' | 'node' | 'unknown', version: string | undefined };
6
+ // Platform detection
7
+ export declare const platform: NodeJS.Platform;
8
+ export declare const isWindows: boolean;
9
+ export declare const isMacOS: boolean;
10
+ export declare const isLinux: boolean;
11
+ // TTY detection
12
+ export declare const hasTTY: boolean;
13
+ // Window detection (browser environment)
14
+ export declare const hasWindow: boolean;
15
+ // CI detection
16
+ export declare const isCI: boolean;
17
+ // Debug mode detection
18
+ export declare const isDebug: boolean;
19
+ // Minimal mode detection (CI or non-interactive)
20
+ export declare const isMinimal: boolean;
21
+ // Color support detection
22
+ export declare const isColorSupported: boolean;
23
+ export declare const provider: string;
24
+ export declare const providerInfo: { name: string, detected: boolean };
package/dist/utils.js ADDED
@@ -0,0 +1,2 @@
1
+ // @bun
2
+ import b from"process";import{platform as E}from"os";var w=typeof Bun<"u",x=typeof b<"u"&&b.versions?.node!==void 0,F=w?"bun":x?"node":"unknown",O={name:F,version:w?Bun.version:x?b.version:void 0},j=E(),Q=j==="win32",R=j==="darwin",S=j==="linux",z=Boolean(b.stdout?.isTTY),U=typeof globalThis.window<"u",G=Boolean(b.env.CI||b.env.CONTINUOUS_INTEGRATION||b.env.BUILD_NUMBER||b.env.RUN_ID),V=Boolean(b.env.DEBUG||b.env.VERBOSE||b.argv.includes("--debug")||b.argv.includes("--verbose")),H=G||!z,W=Boolean(!H&&(z||b.env.COLORTERM||b.env.FORCE_COLOR||b.env.TERM&&b.env.TERM!=="dumb")),g={github:{name:"GitHub Actions",detected:Boolean(b.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(b.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(b.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(b.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(b.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(b.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(b.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(b.env.DYNO)},aws:{name:"AWS",detected:Boolean(b.env.AWS_REGION||b.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(b.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(b.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(b.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(b.env.RENDER)}},J=Object.keys(g).find((A)=>{let q=g[A];return q!==void 0&&q.detected})||"unknown",X=g[J]||{name:"Unknown",detected:!1};export{O as runtimeInfo,F as runtime,X as providerInfo,J as provider,j as platform,Q as isWindows,x as isNode,H as isMinimal,R as isMacOS,S as isLinux,V as isDebug,W as isColorSupported,G as isCI,w as isBun,U as hasWindow,z as hasTTY};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/env",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.88",
5
+ "version": "0.70.90",
6
6
  "description": "Stacks env helper methods.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "better-dx": "^0.2.16",
54
- "@stacksjs/path": "0.70.88",
55
- "@stacksjs/validation": "0.70.88"
54
+ "@stacksjs/path": "0.70.90",
55
+ "@stacksjs/validation": "0.70.90"
56
56
  }
57
57
  }