@rusamer/env-guards 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 EnvGod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # EnvGod SDK
2
+
3
+ A secure, server-side Node.js and Next.js SDK for fetching environment bundles from the EnvGod Data Plane.
4
+
5
+ ## Features
6
+
7
+ - **Secure by Default**: Throws if executed in a browser environment.
8
+ - **In-Memory Only**: Never writes secrets to disk or logs them.
9
+ - **Auto-Auth**: Exchanges `ENVGOD_API_KEY` for short-lived JWTs.
10
+ - **Smart Caching**: Caches tokens and bundles in memory until expiry.
11
+ - **Reliable**: Automatic retry on 401 (token expiry) and network resiliency.
12
+ - **Next.js Ready**: Dedicated `envgod/next` entry point with `server-only` guards.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @rusamer/envgod
18
+ # or
19
+ pnpm add @rusamer/envgod
20
+ # or
21
+ yarn add @rusamer/envgod
22
+ ```
23
+
24
+ ## Configuration
25
+
26
+ The SDK automatically reads the following environment variables:
27
+
28
+ | Variable | Description |
29
+ |C...|...|
30
+ | `ENVGOD_API_URL` | URL of the EnvGod Data Plane |
31
+ | `ENVGOD_API_KEY` | Your project Service Key |
32
+ | `ENVGOD_PROJECT` | Project ID |
33
+ | `ENVGOD_ENV` | Environment Name (e.g., prod) |
34
+ | `ENVGOD_SERVICE` | Service Name |
35
+
36
+ ```env
37
+ ENVGOD_API_URL=https://api.example.com
38
+ ENVGOD_API_KEY=sk_xxx
39
+ ENVGOD_PROJECT=myapp
40
+ ENVGOD_ENV=prod
41
+ ENVGOD_SERVICE=web
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ### Node.js
47
+
48
+ ```typescript
49
+ import { loadEnv } from '@rusamer/envgod';
50
+
51
+ async function main() {
52
+ const env = await loadEnv();
53
+
54
+ console.log(process.env.MY_SECRET); // Accessed from process.env
55
+ console.log(env.MY_SECRET); // Or from the returned object
56
+ }
57
+
58
+ main();
59
+ ```
60
+
61
+ ### Next.js (App Router / Server Actions)
62
+
63
+ Use the Next.js specific helper to ensure server-side only execution.
64
+
65
+ ```typescript
66
+ // src/lib/env.ts
67
+ import { loadServerEnv } from '@rusamer/envgod/next';
68
+
69
+ export async function getSecrets() {
70
+ return loadServerEnv();
71
+ }
72
+ ```
73
+
74
+ ```typescript
75
+ // src/app/page.tsx
76
+ import { getSecrets } from '@/lib/env';
77
+
78
+ export default async function Page() {
79
+ const env = await getSecrets();
80
+ return <div>Secret length: {env.API_KEY.length}</div>;
81
+ }
82
+ ```
83
+
84
+ ## Security Notes
85
+
86
+ 1. **Server-Only**: This SDK is designed strictly for server environments. It explicitly checks for `window` and imports `server-only` in the Next.js entrypoint.
87
+ 2. **No Persistence**: Secrets are held in memory. Restarting the server will trigger a fresh fetch.
88
+ 3. **Logs**: The SDK does not log secret values.
89
+
90
+ ## For Maintainers
91
+
92
+ ### Build
93
+
94
+ ```bash
95
+ pnpm build
96
+ ```
97
+
98
+ ### Test
99
+
100
+ ```bash
101
+ pnpm test
102
+ ```
103
+
104
+ ### Publish
105
+
106
+ 1. `npm version patch`
107
+ 2. `npm publish`
@@ -0,0 +1,8 @@
1
+ import { L as LoadEnvOptions } from './types-BxDfpIC9.mjs';
2
+ export { A as AuthExchangeResponse, B as BundleResponse, E as EnvGuardsConfig } from './types-BxDfpIC9.mjs';
3
+
4
+ /** @internal For testing only */
5
+ declare function _resetState(): void;
6
+ declare function loadEnv(options?: LoadEnvOptions): Promise<Record<string, string>>;
7
+
8
+ export { LoadEnvOptions, _resetState, loadEnv };
@@ -0,0 +1,8 @@
1
+ import { L as LoadEnvOptions } from './types-BxDfpIC9.js';
2
+ export { A as AuthExchangeResponse, B as BundleResponse, E as EnvGuardsConfig } from './types-BxDfpIC9.js';
3
+
4
+ /** @internal For testing only */
5
+ declare function _resetState(): void;
6
+ declare function loadEnv(options?: LoadEnvOptions): Promise<Record<string, string>>;
7
+
8
+ export { LoadEnvOptions, _resetState, loadEnv };
package/dist/index.js ADDED
@@ -0,0 +1,239 @@
1
+ 'use strict';
2
+
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
10
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
11
+ }) : x)(function(x) {
12
+ if (typeof require !== "undefined") return require.apply(this, arguments);
13
+ throw Error('Dynamic require of "' + x + '" is not supported');
14
+ });
15
+ var __esm = (fn, res) => function __init() {
16
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
17
+ };
18
+ var __commonJS = (cb, mod) => function __require2() {
19
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
20
+ };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
30
+ // If the importer is in node compatibility mode or this is not an ESM
31
+ // file that has been converted to a CommonJS file using a Babel-
32
+ // compatible transform (i.e. "__esModule" has not been set), then set
33
+ // "default" to the CommonJS "module.exports" for node compatibility.
34
+ !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
35
+ mod
36
+ ));
37
+
38
+ // node_modules/keytar/build/Release/keytar.node
39
+ var keytar_default;
40
+ var init_keytar = __esm({
41
+ "node_modules/keytar/build/Release/keytar.node"() {
42
+ keytar_default = "./keytar-F4YAPN53.node";
43
+ }
44
+ });
45
+
46
+ // node-file:D:\projects\envguards-sdk\node_modules\keytar\build\Release\keytar.node
47
+ var require_keytar = __commonJS({
48
+ "node-file:D:\\projects\\envguards-sdk\\node_modules\\keytar\\build\\Release\\keytar.node"(exports$1, module) {
49
+ init_keytar();
50
+ try {
51
+ module.exports = __require(keytar_default);
52
+ } catch {
53
+ }
54
+ }
55
+ });
56
+
57
+ // node_modules/keytar/lib/keytar.js
58
+ var require_keytar2 = __commonJS({
59
+ "node_modules/keytar/lib/keytar.js"(exports$1, module) {
60
+ var keytar = require_keytar();
61
+ function checkRequired(val, name) {
62
+ if (!val || val.length <= 0) {
63
+ throw new Error(name + " is required.");
64
+ }
65
+ }
66
+ module.exports = {
67
+ getPassword: function(service, account) {
68
+ checkRequired(service, "Service");
69
+ checkRequired(account, "Account");
70
+ return keytar.getPassword(service, account);
71
+ },
72
+ setPassword: function(service, account, password) {
73
+ checkRequired(service, "Service");
74
+ checkRequired(account, "Account");
75
+ checkRequired(password, "Password");
76
+ return keytar.setPassword(service, account, password);
77
+ },
78
+ deletePassword: function(service, account) {
79
+ checkRequired(service, "Service");
80
+ checkRequired(account, "Account");
81
+ return keytar.deletePassword(service, account);
82
+ },
83
+ findPassword: function(service) {
84
+ checkRequired(service, "Service");
85
+ return keytar.findPassword(service);
86
+ },
87
+ findCredentials: function(service) {
88
+ checkRequired(service, "Service");
89
+ return keytar.findCredentials(service);
90
+ }
91
+ };
92
+ }
93
+ });
94
+
95
+ // src/index.ts
96
+ var cache = /* @__PURE__ */ new Map();
97
+ var pendingLoadPromise = null;
98
+ function _resetState() {
99
+ cache.clear();
100
+ pendingLoadPromise = null;
101
+ }
102
+ function checkBrowser() {
103
+ if (typeof window !== "undefined") {
104
+ throw new Error("[Env.Guards] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.");
105
+ }
106
+ }
107
+ async function fetchWithTimeout(url, init) {
108
+ const { timeout = 5e3, ...rest } = init;
109
+ const controller = new AbortController();
110
+ const id = setTimeout(() => controller.abort(), timeout);
111
+ try {
112
+ const res = await fetch(url, { ...rest, signal: controller.signal });
113
+ return res;
114
+ } catch (err) {
115
+ if (err.name === "AbortError") {
116
+ throw new Error(`[Env.Guards] Request timed out after ${timeout}ms`);
117
+ }
118
+ throw err;
119
+ } finally {
120
+ clearTimeout(id);
121
+ }
122
+ }
123
+ async function resolveRuntimeKey(config) {
124
+ if (config.apiKey) return config.apiKey;
125
+ if (process.env.ENV_GUARDS_API_KEY) return process.env.ENV_GUARDS_API_KEY;
126
+ try {
127
+ const keytar = (await Promise.resolve().then(() => __toESM(require_keytar2()))).default;
128
+ const SERVICE = "env-guards";
129
+ const { apiUrl, org, project, env, service } = config;
130
+ if (apiUrl && org && project && env && service) {
131
+ const account = `runtime:${apiUrl}:${org}:${project}:${env}:${service}`;
132
+ const key = await keytar.getPassword(SERVICE, account);
133
+ if (key) return key;
134
+ }
135
+ } catch (err) {
136
+ }
137
+ throw new Error("[Env.Guards] Runtime key not found. Please set ENV_GUARDS_API_KEY, use `env-guards run`, or run `env-guards add-runtime-key`.");
138
+ }
139
+ function getFullConfig(options) {
140
+ const env = process.env;
141
+ return {
142
+ apiUrl: options?.config?.apiUrl ?? env.ENV_GUARDS_API_URL,
143
+ apiKey: options?.config?.apiKey ?? env.ENV_GUARDS_API_KEY,
144
+ org: options?.config?.org ?? env.ENV_GUARDS_ORG,
145
+ project: options?.config?.project ?? env.ENV_GUARDS_PROJECT,
146
+ env: options?.config?.env ?? env.ENV_GUARDS_ENV,
147
+ service: options?.config?.service ?? env.ENV_GUARDS_SERVICE
148
+ };
149
+ }
150
+ async function exchangeToken(apiUrl, runtimeKey, scope, timeout, state) {
151
+ const res = await fetchWithTimeout(`${apiUrl}/v1/auth/exchange`, {
152
+ method: "POST",
153
+ headers: {
154
+ "Content-Type": "application/json",
155
+ "Authorization": `Bearer ${runtimeKey}`
156
+ },
157
+ body: JSON.stringify(scope),
158
+ timeout
159
+ });
160
+ if (!res.ok) {
161
+ throw new Error(`[Env.Guards] Auth exchange failed: ${res.status} ${res.statusText}`);
162
+ }
163
+ const data = await res.json();
164
+ state.token = data.token;
165
+ state.tokenExpiresAt = new Date(data.expiresAt).getTime();
166
+ return data.token;
167
+ }
168
+ async function fetchBundle(apiUrl, token, timeout) {
169
+ const res = await fetchWithTimeout(`${apiUrl}/v1/bundle`, {
170
+ method: "GET",
171
+ headers: { "Authorization": `Bearer ${token}` },
172
+ timeout
173
+ });
174
+ if (res.status === 401) throw new Error("401");
175
+ if (!res.ok) throw new Error(`[Env.Guards] Fetch bundle failed: ${res.status} ${res.statusText}`);
176
+ const data = await res.json();
177
+ return data.values;
178
+ }
179
+ function getConfigFingerprint(apiUrl, runtimeKey, config) {
180
+ const keyPrefix = runtimeKey.substring(0, 12);
181
+ return [apiUrl, keyPrefix, config.org, config.project, config.env, config.service].join("|");
182
+ }
183
+ async function loadEnvInternal(options) {
184
+ checkBrowser();
185
+ const config = getFullConfig(options);
186
+ const { apiUrl, ...scope } = config;
187
+ if (!apiUrl) {
188
+ throw new Error("[Env.Guards] Missing required configuration: apiUrl is required.");
189
+ }
190
+ const runtimeKey = await resolveRuntimeKey(config);
191
+ const timeout = options?.timeout ?? 5e3;
192
+ const now = Date.now();
193
+ const TOKEN_SKEW_MS = 30 * 1e3;
194
+ const fingerprint = getConfigFingerprint(apiUrl, runtimeKey, config);
195
+ if (!cache.has(fingerprint)) {
196
+ cache.set(fingerprint, { token: null, tokenExpiresAt: null, bundle: null });
197
+ }
198
+ const state = cache.get(fingerprint);
199
+ let token = state.token;
200
+ let tokenValid = token && state.tokenExpiresAt && state.tokenExpiresAt > now + TOKEN_SKEW_MS;
201
+ if (!tokenValid) {
202
+ token = await exchangeToken(apiUrl, runtimeKey, scope, timeout, state);
203
+ }
204
+ if (state.bundle && tokenValid) {
205
+ Object.assign(process.env, state.bundle);
206
+ return state.bundle;
207
+ }
208
+ try {
209
+ const values = await fetchBundle(apiUrl, token, timeout);
210
+ state.bundle = values;
211
+ Object.assign(process.env, values);
212
+ return values;
213
+ } catch (err) {
214
+ if (err.message === "401") {
215
+ state.token = null;
216
+ state.bundle = null;
217
+ const newToken = await exchangeToken(apiUrl, runtimeKey, scope, timeout, state);
218
+ const values = await fetchBundle(apiUrl, newToken, timeout);
219
+ state.bundle = values;
220
+ Object.assign(process.env, values);
221
+ return values;
222
+ }
223
+ throw err;
224
+ }
225
+ }
226
+ function loadEnv(options) {
227
+ if (pendingLoadPromise) {
228
+ return pendingLoadPromise;
229
+ }
230
+ pendingLoadPromise = loadEnvInternal(options).finally(() => {
231
+ pendingLoadPromise = null;
232
+ });
233
+ return pendingLoadPromise;
234
+ }
235
+
236
+ exports._resetState = _resetState;
237
+ exports.loadEnv = loadEnv;
238
+ //# sourceMappingURL=index.js.map
239
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["node-file:D:/projects/envguards-sdk/node_modules/keytar/build/Release/keytar.node","../node_modules/keytar/lib/keytar.js","../src/index.ts"],"names":["exports","require_keytar"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAA,cAAA,GAAA,UAAA,CAAA;AAAA,EAAA,0FAAA,CAAAA,SAAA,EAAA,MAAA,EAAA;AACY,IAAA,WAAA,EAAA;AACA,IAAA,IAAI;AAAE,MAAA,MAAA,CAAO,OAAA,GAAU,UAAQ,cAAI,CAAA;AAAA,IAAE,CAAA,CAAA,MAC/B;AAAA,IAAC;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACHnB,IAAAC,eAAAA,GAAA,UAAA,CAAA;AAAA,EAAA,mCAAA,CAAAD,SAAA,EAAA,MAAA,EAAA;AAAA,IAAA,IAAI,MAAA,GAAS,cAAA,EAAA;AAEb,IAAA,SAAS,aAAA,CAAc,KAAK,IAAA,EAAM;AAChC,MAAA,IAAI,CAAC,GAAA,IAAO,GAAA,CAAI,MAAA,IAAU,CAAA,EAAG;AAC3B,QAAA,MAAM,IAAI,KAAA,CAAM,IAAA,GAAO,eAAe,CAAA;AAAA,MACxC;AAAA,IACF;AAEA,IAAA,MAAA,CAAO,OAAA,GAAU;AAAA,MACf,WAAA,EAAa,SAAU,OAAA,EAAS,OAAA,EAAS;AACvC,QAAA,aAAA,CAAc,SAAS,SAAS,CAAA;AAChC,QAAA,aAAA,CAAc,SAAS,SAAS,CAAA;AAEhC,QAAA,OAAO,MAAA,CAAO,WAAA,CAAY,OAAA,EAAS,OAAO,CAAA;AAAA,MAC5C,CAAA;AAAA,MAEA,WAAA,EAAa,SAAU,OAAA,EAAS,OAAA,EAAS,QAAA,EAAU;AACjD,QAAA,aAAA,CAAc,SAAS,SAAS,CAAA;AAChC,QAAA,aAAA,CAAc,SAAS,SAAS,CAAA;AAChC,QAAA,aAAA,CAAc,UAAU,UAAU,CAAA;AAElC,QAAA,OAAO,MAAA,CAAO,WAAA,CAAY,OAAA,EAAS,OAAA,EAAS,QAAQ,CAAA;AAAA,MACtD,CAAA;AAAA,MAEA,cAAA,EAAgB,SAAU,OAAA,EAAS,OAAA,EAAS;AAC1C,QAAA,aAAA,CAAc,SAAS,SAAS,CAAA;AAChC,QAAA,aAAA,CAAc,SAAS,SAAS,CAAA;AAEhC,QAAA,OAAO,MAAA,CAAO,cAAA,CAAe,OAAA,EAAS,OAAO,CAAA;AAAA,MAC/C,CAAA;AAAA,MAEA,YAAA,EAAc,SAAU,OAAA,EAAS;AAC/B,QAAA,aAAA,CAAc,SAAS,SAAS,CAAA;AAEhC,QAAA,OAAO,MAAA,CAAO,aAAa,OAAO,CAAA;AAAA,MACpC,CAAA;AAAA,MAEA,eAAA,EAAiB,SAAU,OAAA,EAAS;AAClC,QAAA,aAAA,CAAc,SAAS,SAAS,CAAA;AAEhC,QAAA,OAAO,MAAA,CAAO,gBAAgB,OAAO,CAAA;AAAA,MACvC;AAAA,KACF;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACjCA,IAAM,KAAA,uBAAY,GAAA,EAAwB;AAC1C,IAAI,kBAAA,GAA6D,IAAA;AAG1D,SAAS,WAAA,GAAc;AAC1B,EAAA,KAAA,CAAM,KAAA,EAAM;AACZ,EAAA,kBAAA,GAAqB,IAAA;AACzB;AAIA,SAAS,YAAA,GAAe;AACpB,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAC/B,IAAA,MAAM,IAAI,MAAM,0GAA0G,CAAA;AAAA,EAC9H;AACJ;AAEA,eAAe,gBAAA,CAAiB,KAAa,IAAA,EAA0C;AACnF,EAAA,MAAM,EAAE,OAAA,GAAU,GAAA,EAAM,GAAG,MAAK,GAAI,IAAA;AACpC,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,KAAK,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,OAAO,CAAA;AAEvD,EAAA,IAAI;AACA,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAQ,CAAA;AACnE,IAAA,OAAO,GAAA;AAAA,EACX,SAAS,GAAA,EAAU;AACf,IAAA,IAAI,GAAA,CAAI,SAAS,YAAA,EAAc;AAC3B,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,qCAAA,EAAwC,OAAO,CAAA,EAAA,CAAI,CAAA;AAAA,IACvE;AACA,IAAA,MAAM,GAAA;AAAA,EACV,CAAA,SAAE;AACE,IAAA,YAAA,CAAa,EAAE,CAAA;AAAA,EACnB;AACJ;AAIA,eAAe,kBAAkB,MAAA,EAA0C;AAEvE,EAAA,IAAI,MAAA,CAAO,MAAA,EAAQ,OAAO,MAAA,CAAO,MAAA;AAGjC,EAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,kBAAA,EAAoB,OAAO,QAAQ,GAAA,CAAI,kBAAA;AAGvD,EAAA,IAAI;AACA,IAAA,MAAM,MAAA,GAAA,CAAU,MAAM,OAAA,CAAA,OAAA,EAAA,CAAA,IAAA,CAAA,MAAA,OAAA,CAAA,eAAA,EAAA,CAAA,CAAA,EAAkB,OAAA;AACxC,IAAA,MAAM,OAAA,GAAU,YAAA;AAChB,IAAA,MAAM,EAAE,MAAA,EAAQ,GAAA,EAAK,OAAA,EAAS,GAAA,EAAK,SAAQ,GAAI,MAAA;AAC/C,IAAA,IAAI,MAAA,IAAU,GAAA,IAAO,OAAA,IAAW,GAAA,IAAO,OAAA,EAAS;AAC5C,MAAA,MAAM,OAAA,GAAU,CAAA,QAAA,EAAW,MAAM,CAAA,CAAA,EAAI,GAAG,IAAI,OAAO,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AACrE,MAAA,MAAM,GAAA,GAAM,MAAM,MAAA,CAAO,WAAA,CAAY,SAAS,OAAO,CAAA;AACrD,MAAA,IAAI,KAAK,OAAO,GAAA;AAAA,IACpB;AAAA,EACJ,SAAS,GAAA,EAAK;AAAA,EAEd;AAEA,EAAA,MAAM,IAAI,MAAM,+HAA+H,CAAA;AACnJ;AAEA,SAAS,cAAc,OAAA,EAA2C;AAC9D,EAAA,MAAM,MAAM,OAAA,CAAQ,GAAA;AACpB,EAAA,OAAO;AAAA,IACH,MAAA,EAAQ,OAAA,EAAS,MAAA,EAAQ,MAAA,IAAU,GAAA,CAAI,kBAAA;AAAA,IACvC,MAAA,EAAQ,OAAA,EAAS,MAAA,EAAQ,MAAA,IAAU,GAAA,CAAI,kBAAA;AAAA,IACvC,GAAA,EAAK,OAAA,EAAS,MAAA,EAAQ,GAAA,IAAO,GAAA,CAAI,cAAA;AAAA,IACjC,OAAA,EAAS,OAAA,EAAS,MAAA,EAAQ,OAAA,IAAW,GAAA,CAAI,kBAAA;AAAA,IACzC,GAAA,EAAK,OAAA,EAAS,MAAA,EAAQ,GAAA,IAAO,GAAA,CAAI,cAAA;AAAA,IACjC,OAAA,EAAS,OAAA,EAAS,MAAA,EAAQ,OAAA,IAAW,GAAA,CAAI;AAAA,GAC7C;AACJ;AAEA,eAAe,aAAA,CAAc,MAAA,EAAgB,UAAA,EAAoB,KAAA,EAAiC,SAAiB,KAAA,EAAoC;AACnJ,EAAA,MAAM,GAAA,GAAM,MAAM,gBAAA,CAAiB,CAAA,EAAG,MAAM,CAAA,iBAAA,CAAA,EAAqB;AAAA,IAC7D,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACL,cAAA,EAAgB,kBAAA;AAAA,MAChB,eAAA,EAAiB,UAAU,UAAU,CAAA;AAAA,KACzC;AAAA,IACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AAAA,IAC1B;AAAA,GACH,CAAA;AAED,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACT,IAAA,MAAM,IAAI,MAAM,CAAA,mCAAA,EAAsC,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,GAAA,CAAI,UAAU,CAAA,CAAE,CAAA;AAAA,EACxF;AAEA,EAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,EAAA,KAAA,CAAM,QAAQ,IAAA,CAAK,KAAA;AACnB,EAAA,KAAA,CAAM,iBAAiB,IAAI,IAAA,CAAK,IAAA,CAAK,SAAS,EAAE,OAAA,EAAQ;AACxD,EAAA,OAAO,IAAA,CAAK,KAAA;AAChB;AAEA,eAAe,WAAA,CAAY,MAAA,EAAgB,KAAA,EAAe,OAAA,EAAkD;AACxG,EAAA,MAAM,GAAA,GAAM,MAAM,gBAAA,CAAiB,CAAA,EAAG,MAAM,CAAA,UAAA,CAAA,EAAc;AAAA,IACtD,MAAA,EAAQ,KAAA;AAAA,IACR,OAAA,EAAS,EAAE,eAAA,EAAiB,CAAA,OAAA,EAAU,KAAK,CAAA,CAAA,EAAG;AAAA,IAC9C;AAAA,GACH,CAAA;AAED,EAAA,IAAI,IAAI,MAAA,KAAW,GAAA,EAAK,MAAM,IAAI,MAAM,KAAK,CAAA;AAC7C,EAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,kCAAA,EAAqC,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,GAAA,CAAI,UAAU,CAAA,CAAE,CAAA;AAEhG,EAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,EAAA,OAAO,IAAA,CAAK,MAAA;AAChB;AAEA,SAAS,oBAAA,CAAqB,MAAA,EAAgB,UAAA,EAAoB,MAAA,EAAiC;AAC/F,EAAA,MAAM,SAAA,GAAY,UAAA,CAAW,SAAA,CAAU,CAAA,EAAG,EAAE,CAAA;AAC5C,EAAA,OAAO,CAAC,MAAA,EAAQ,SAAA,EAAW,MAAA,CAAO,GAAA,EAAK,MAAA,CAAO,OAAA,EAAS,MAAA,CAAO,GAAA,EAAK,MAAA,CAAO,OAAO,CAAA,CAAE,KAAK,GAAG,CAAA;AAC/F;AAEA,eAAe,gBAAgB,OAAA,EAA2D;AACtF,EAAA,YAAA,EAAa;AACb,EAAA,MAAM,MAAA,GAAS,cAAc,OAAO,CAAA;AACpC,EAAA,MAAM,EAAE,MAAA,EAAQ,GAAG,KAAA,EAAM,GAAI,MAAA;AAE7B,EAAA,IAAI,CAAC,MAAA,EAAQ;AACT,IAAA,MAAM,IAAI,MAAM,kEAAkE,CAAA;AAAA,EACtF;AAEA,EAAA,MAAM,UAAA,GAAa,MAAM,iBAAA,CAAkB,MAAM,CAAA;AACjD,EAAA,MAAM,OAAA,GAAU,SAAS,OAAA,IAAW,GAAA;AACpC,EAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,EAAA,MAAM,gBAAgB,EAAA,GAAK,GAAA;AAE3B,EAAA,MAAM,WAAA,GAAc,oBAAA,CAAqB,MAAA,EAAQ,UAAA,EAAY,MAAM,CAAA;AACnE,EAAA,IAAI,CAAC,KAAA,CAAM,GAAA,CAAI,WAAW,CAAA,EAAG;AACzB,IAAA,KAAA,CAAM,GAAA,CAAI,aAAa,EAAE,KAAA,EAAO,MAAM,cAAA,EAAgB,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,CAAA;AAAA,EAC9E;AACA,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,WAAW,CAAA;AAEnC,EAAA,IAAI,QAAQ,KAAA,CAAM,KAAA;AAClB,EAAA,IAAI,aAAa,KAAA,IAAS,KAAA,CAAM,cAAA,IAAkB,KAAA,CAAM,iBAAkB,GAAA,GAAM,aAAA;AAEhF,EAAA,IAAI,CAAC,UAAA,EAAY;AACb,IAAA,KAAA,GAAQ,MAAM,aAAA,CAAc,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,SAAS,KAAK,CAAA;AAAA,EACzE;AAEA,EAAA,IAAI,KAAA,CAAM,UAAU,UAAA,EAAY;AAC5B,IAAA,MAAA,CAAO,MAAA,CAAO,OAAA,CAAQ,GAAA,EAAK,KAAA,CAAM,MAAM,CAAA;AACvC,IAAA,OAAO,KAAA,CAAM,MAAA;AAAA,EACjB;AAEA,EAAA,IAAI;AACA,IAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,MAAA,EAAQ,OAAQ,OAAO,CAAA;AACxD,IAAA,KAAA,CAAM,MAAA,GAAS,MAAA;AACf,IAAA,MAAA,CAAO,MAAA,CAAO,OAAA,CAAQ,GAAA,EAAK,MAAM,CAAA;AACjC,IAAA,OAAO,MAAA;AAAA,EACX,SAAS,GAAA,EAAU;AACf,IAAA,IAAI,GAAA,CAAI,YAAY,KAAA,EAAO;AACvB,MAAA,KAAA,CAAM,KAAA,GAAQ,IAAA;AACd,MAAA,KAAA,CAAM,MAAA,GAAS,IAAA;AACf,MAAA,MAAM,WAAW,MAAM,aAAA,CAAc,QAAQ,UAAA,EAAY,KAAA,EAAO,SAAS,KAAK,CAAA;AAC9E,MAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,MAAA,EAAQ,UAAU,OAAO,CAAA;AAC1D,MAAA,KAAA,CAAM,MAAA,GAAS,MAAA;AACf,MAAA,MAAA,CAAO,MAAA,CAAO,OAAA,CAAQ,GAAA,EAAK,MAAM,CAAA;AACjC,MAAA,OAAO,MAAA;AAAA,IACX;AACA,IAAA,MAAM,GAAA;AAAA,EACV;AACJ;AAEO,SAAS,QAAQ,OAAA,EAA2D;AAC/E,EAAA,IAAI,kBAAA,EAAoB;AACpB,IAAA,OAAO,kBAAA;AAAA,EACX;AAEA,EAAA,kBAAA,GAAqB,eAAA,CAAgB,OAAO,CAAA,CAAE,OAAA,CAAQ,MAAM;AACxD,IAAA,kBAAA,GAAqB,IAAA;AAAA,EACzB,CAAC,CAAA;AAED,EAAA,OAAO,kBAAA;AACX","file":"index.js","sourcesContent":["\n import path from \"D:\\\\projects\\\\envguards-sdk\\\\node_modules\\\\keytar\\\\build\\\\Release\\\\keytar.node\"\n try { module.exports = require(path) }\n catch {}\n ","var keytar = require('../build/Release/keytar.node')\n\nfunction checkRequired(val, name) {\n if (!val || val.length <= 0) {\n throw new Error(name + ' is required.');\n }\n}\n\nmodule.exports = {\n getPassword: function (service, account) {\n checkRequired(service, 'Service')\n checkRequired(account, 'Account')\n\n return keytar.getPassword(service, account)\n },\n\n setPassword: function (service, account, password) {\n checkRequired(service, 'Service')\n checkRequired(account, 'Account')\n checkRequired(password, 'Password')\n\n return keytar.setPassword(service, account, password)\n },\n\n deletePassword: function (service, account) {\n checkRequired(service, 'Service')\n checkRequired(account, 'Account')\n\n return keytar.deletePassword(service, account)\n },\n\n findPassword: function (service) {\n checkRequired(service, 'Service')\n\n return keytar.findPassword(service)\n },\n\n findCredentials: function (service) {\n checkRequired(service, 'Service')\n\n return keytar.findCredentials(service)\n }\n}\n","import type { EnvGuardsConfig, LoadEnvOptions, AuthExchangeResponse, BundleResponse } from './types.js';\r\n\r\n// --- State ---\r\ninterface CacheState {\r\n token: string | null;\r\n tokenExpiresAt: number | null; // Timestamp in ms\r\n bundle: Record<string, string> | null;\r\n}\r\n\r\nconst cache = new Map<string, CacheState>();\r\nlet pendingLoadPromise: Promise<Record<string, string>> | null = null;\r\n\r\n/** @internal For testing only */\r\nexport function _resetState() {\r\n cache.clear();\r\n pendingLoadPromise = null;\r\n}\r\n\r\n// --- Helpers ---\r\n\r\nfunction checkBrowser() {\r\n if (typeof window !== 'undefined') {\r\n throw new Error('[Env.Guards] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.');\r\n }\r\n}\r\n\r\nasync function fetchWithTimeout(url: string, init: RequestInit & { timeout?: number }) {\r\n const { timeout = 5000, ...rest } = init;\r\n const controller = new AbortController();\r\n const id = setTimeout(() => controller.abort(), timeout);\r\n\r\n try {\r\n const res = await fetch(url, { ...rest, signal: controller.signal });\r\n return res;\r\n } catch (err: any) {\r\n if (err.name === 'AbortError') {\r\n throw new Error(`[Env.Guards] Request timed out after ${timeout}ms`);\r\n }\r\n throw err;\r\n } finally {\r\n clearTimeout(id);\r\n }\r\n}\r\n\r\n// --- Core Logic ---\r\n\r\nasync function resolveRuntimeKey(config: EnvGuardsConfig): Promise<string> {\r\n // 1. Explicitly passed apiKey\r\n if (config.apiKey) return config.apiKey;\r\n\r\n // 2. Environment variable\r\n if (process.env.ENV_GUARDS_API_KEY) return process.env.ENV_GUARDS_API_KEY;\r\n\r\n // 3. Keytar (for local development, optional)\r\n try {\r\n const keytar = (await import('keytar')).default;\r\n const SERVICE = 'env-guards';\r\n const { apiUrl, org, project, env, service } = config;\r\n if (apiUrl && org && project && env && service) {\r\n const account = `runtime:${apiUrl}:${org}:${project}:${env}:${service}`;\r\n const key = await keytar.getPassword(SERVICE, account);\r\n if (key) return key;\r\n }\r\n } catch (err) {\r\n // Keytar is optional, so we ignore errors (e.g., native module not built).\r\n }\r\n\r\n throw new Error('[Env.Guards] Runtime key not found. Please set ENV_GUARDS_API_KEY, use `env-guards run`, or run `env-guards add-runtime-key`.');\r\n}\r\n\r\nfunction getFullConfig(options?: LoadEnvOptions): EnvGuardsConfig {\r\n const env = process.env;\r\n return {\r\n apiUrl: options?.config?.apiUrl ?? env.ENV_GUARDS_API_URL,\r\n apiKey: options?.config?.apiKey ?? env.ENV_GUARDS_API_KEY,\r\n org: options?.config?.org ?? env.ENV_GUARDS_ORG,\r\n project: options?.config?.project ?? env.ENV_GUARDS_PROJECT,\r\n env: options?.config?.env ?? env.ENV_GUARDS_ENV,\r\n service: options?.config?.service ?? env.ENV_GUARDS_SERVICE,\r\n };\r\n}\r\n\r\nasync function exchangeToken(apiUrl: string, runtimeKey: string, scope: Partial<EnvGuardsConfig>, timeout: number, state: CacheState): Promise<string> {\r\n const res = await fetchWithTimeout(`${apiUrl}/v1/auth/exchange`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${runtimeKey}`,\r\n },\r\n body: JSON.stringify(scope),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[Env.Guards] Auth exchange failed: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as AuthExchangeResponse;\r\n state.token = data.token;\r\n state.tokenExpiresAt = new Date(data.expiresAt).getTime();\r\n return data.token;\r\n}\r\n\r\nasync function fetchBundle(apiUrl: string, token: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${apiUrl}/v1/bundle`, {\r\n method: 'GET',\r\n headers: { 'Authorization': `Bearer ${token}` },\r\n timeout,\r\n });\r\n\r\n if (res.status === 401) throw new Error('401'); // Signal to retry\r\n if (!res.ok) throw new Error(`[Env.Guards] Fetch bundle failed: ${res.status} ${res.statusText}`);\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nfunction getConfigFingerprint(apiUrl: string, runtimeKey: string, config: EnvGuardsConfig): string {\r\n const keyPrefix = runtimeKey.substring(0, 12); // env-guards_sk_...\r\n return [apiUrl, keyPrefix, config.org, config.project, config.env, config.service].join('|');\r\n}\r\n\r\nasync function loadEnvInternal(options?: LoadEnvOptions): Promise<Record<string, string>> {\r\n checkBrowser();\r\n const config = getFullConfig(options);\r\n const { apiUrl, ...scope } = config;\r\n\r\n if (!apiUrl) {\r\n throw new Error('[Env.Guards] Missing required configuration: apiUrl is required.');\r\n }\r\n\r\n const runtimeKey = await resolveRuntimeKey(config);\r\n const timeout = options?.timeout ?? 5000;\r\n const now = Date.now();\r\n const TOKEN_SKEW_MS = 30 * 1000;\r\n\r\n const fingerprint = getConfigFingerprint(apiUrl, runtimeKey, config);\r\n if (!cache.has(fingerprint)) {\r\n cache.set(fingerprint, { token: null, tokenExpiresAt: null, bundle: null });\r\n }\r\n const state = cache.get(fingerprint)!;\r\n\r\n let token = state.token;\r\n let tokenValid = token && state.tokenExpiresAt && state.tokenExpiresAt > (now + TOKEN_SKEW_MS);\r\n\r\n if (!tokenValid) {\r\n token = await exchangeToken(apiUrl, runtimeKey, scope, timeout, state);\r\n }\r\n\r\n if (state.bundle && tokenValid) {\r\n Object.assign(process.env, state.bundle);\r\n return state.bundle;\r\n }\r\n\r\n try {\r\n const values = await fetchBundle(apiUrl, token!, timeout);\r\n state.bundle = values;\r\n Object.assign(process.env, values);\r\n return values;\r\n } catch (err: any) {\r\n if (err.message === '401') {\r\n state.token = null;\r\n state.bundle = null;\r\n const newToken = await exchangeToken(apiUrl, runtimeKey, scope, timeout, state);\r\n const values = await fetchBundle(apiUrl, newToken, timeout);\r\n state.bundle = values;\r\n Object.assign(process.env, values);\r\n return values;\r\n }\r\n throw err;\r\n }\r\n}\r\n\r\nexport function loadEnv(options?: LoadEnvOptions): Promise<Record<string, string>> {\r\n if (pendingLoadPromise) {\r\n return pendingLoadPromise;\r\n }\r\n\r\n pendingLoadPromise = loadEnvInternal(options).finally(() => {\r\n pendingLoadPromise = null;\r\n });\r\n\r\n return pendingLoadPromise;\r\n}\r\n\r\nexport * from './types.js';\r\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,236 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
+ }) : x)(function(x) {
10
+ if (typeof require !== "undefined") return require.apply(this, arguments);
11
+ throw Error('Dynamic require of "' + x + '" is not supported');
12
+ });
13
+ var __esm = (fn, res) => function __init() {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ };
16
+ var __commonJS = (cb, mod) => function __require2() {
17
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
28
+ // If the importer is in node compatibility mode or this is not an ESM
29
+ // file that has been converted to a CommonJS file using a Babel-
30
+ // compatible transform (i.e. "__esModule" has not been set), then set
31
+ // "default" to the CommonJS "module.exports" for node compatibility.
32
+ !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
33
+ mod
34
+ ));
35
+
36
+ // node_modules/keytar/build/Release/keytar.node
37
+ var keytar_default;
38
+ var init_keytar = __esm({
39
+ "node_modules/keytar/build/Release/keytar.node"() {
40
+ keytar_default = "./keytar-F4YAPN53.node";
41
+ }
42
+ });
43
+
44
+ // node-file:D:\projects\envguards-sdk\node_modules\keytar\build\Release\keytar.node
45
+ var require_keytar = __commonJS({
46
+ "node-file:D:\\projects\\envguards-sdk\\node_modules\\keytar\\build\\Release\\keytar.node"(exports$1, module) {
47
+ init_keytar();
48
+ try {
49
+ module.exports = __require(keytar_default);
50
+ } catch {
51
+ }
52
+ }
53
+ });
54
+
55
+ // node_modules/keytar/lib/keytar.js
56
+ var require_keytar2 = __commonJS({
57
+ "node_modules/keytar/lib/keytar.js"(exports$1, module) {
58
+ var keytar = require_keytar();
59
+ function checkRequired(val, name) {
60
+ if (!val || val.length <= 0) {
61
+ throw new Error(name + " is required.");
62
+ }
63
+ }
64
+ module.exports = {
65
+ getPassword: function(service, account) {
66
+ checkRequired(service, "Service");
67
+ checkRequired(account, "Account");
68
+ return keytar.getPassword(service, account);
69
+ },
70
+ setPassword: function(service, account, password) {
71
+ checkRequired(service, "Service");
72
+ checkRequired(account, "Account");
73
+ checkRequired(password, "Password");
74
+ return keytar.setPassword(service, account, password);
75
+ },
76
+ deletePassword: function(service, account) {
77
+ checkRequired(service, "Service");
78
+ checkRequired(account, "Account");
79
+ return keytar.deletePassword(service, account);
80
+ },
81
+ findPassword: function(service) {
82
+ checkRequired(service, "Service");
83
+ return keytar.findPassword(service);
84
+ },
85
+ findCredentials: function(service) {
86
+ checkRequired(service, "Service");
87
+ return keytar.findCredentials(service);
88
+ }
89
+ };
90
+ }
91
+ });
92
+
93
+ // src/index.ts
94
+ var cache = /* @__PURE__ */ new Map();
95
+ var pendingLoadPromise = null;
96
+ function _resetState() {
97
+ cache.clear();
98
+ pendingLoadPromise = null;
99
+ }
100
+ function checkBrowser() {
101
+ if (typeof window !== "undefined") {
102
+ throw new Error("[Env.Guards] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.");
103
+ }
104
+ }
105
+ async function fetchWithTimeout(url, init) {
106
+ const { timeout = 5e3, ...rest } = init;
107
+ const controller = new AbortController();
108
+ const id = setTimeout(() => controller.abort(), timeout);
109
+ try {
110
+ const res = await fetch(url, { ...rest, signal: controller.signal });
111
+ return res;
112
+ } catch (err) {
113
+ if (err.name === "AbortError") {
114
+ throw new Error(`[Env.Guards] Request timed out after ${timeout}ms`);
115
+ }
116
+ throw err;
117
+ } finally {
118
+ clearTimeout(id);
119
+ }
120
+ }
121
+ async function resolveRuntimeKey(config) {
122
+ if (config.apiKey) return config.apiKey;
123
+ if (process.env.ENV_GUARDS_API_KEY) return process.env.ENV_GUARDS_API_KEY;
124
+ try {
125
+ const keytar = (await Promise.resolve().then(() => __toESM(require_keytar2()))).default;
126
+ const SERVICE = "env-guards";
127
+ const { apiUrl, org, project, env, service } = config;
128
+ if (apiUrl && org && project && env && service) {
129
+ const account = `runtime:${apiUrl}:${org}:${project}:${env}:${service}`;
130
+ const key = await keytar.getPassword(SERVICE, account);
131
+ if (key) return key;
132
+ }
133
+ } catch (err) {
134
+ }
135
+ throw new Error("[Env.Guards] Runtime key not found. Please set ENV_GUARDS_API_KEY, use `env-guards run`, or run `env-guards add-runtime-key`.");
136
+ }
137
+ function getFullConfig(options) {
138
+ const env = process.env;
139
+ return {
140
+ apiUrl: options?.config?.apiUrl ?? env.ENV_GUARDS_API_URL,
141
+ apiKey: options?.config?.apiKey ?? env.ENV_GUARDS_API_KEY,
142
+ org: options?.config?.org ?? env.ENV_GUARDS_ORG,
143
+ project: options?.config?.project ?? env.ENV_GUARDS_PROJECT,
144
+ env: options?.config?.env ?? env.ENV_GUARDS_ENV,
145
+ service: options?.config?.service ?? env.ENV_GUARDS_SERVICE
146
+ };
147
+ }
148
+ async function exchangeToken(apiUrl, runtimeKey, scope, timeout, state) {
149
+ const res = await fetchWithTimeout(`${apiUrl}/v1/auth/exchange`, {
150
+ method: "POST",
151
+ headers: {
152
+ "Content-Type": "application/json",
153
+ "Authorization": `Bearer ${runtimeKey}`
154
+ },
155
+ body: JSON.stringify(scope),
156
+ timeout
157
+ });
158
+ if (!res.ok) {
159
+ throw new Error(`[Env.Guards] Auth exchange failed: ${res.status} ${res.statusText}`);
160
+ }
161
+ const data = await res.json();
162
+ state.token = data.token;
163
+ state.tokenExpiresAt = new Date(data.expiresAt).getTime();
164
+ return data.token;
165
+ }
166
+ async function fetchBundle(apiUrl, token, timeout) {
167
+ const res = await fetchWithTimeout(`${apiUrl}/v1/bundle`, {
168
+ method: "GET",
169
+ headers: { "Authorization": `Bearer ${token}` },
170
+ timeout
171
+ });
172
+ if (res.status === 401) throw new Error("401");
173
+ if (!res.ok) throw new Error(`[Env.Guards] Fetch bundle failed: ${res.status} ${res.statusText}`);
174
+ const data = await res.json();
175
+ return data.values;
176
+ }
177
+ function getConfigFingerprint(apiUrl, runtimeKey, config) {
178
+ const keyPrefix = runtimeKey.substring(0, 12);
179
+ return [apiUrl, keyPrefix, config.org, config.project, config.env, config.service].join("|");
180
+ }
181
+ async function loadEnvInternal(options) {
182
+ checkBrowser();
183
+ const config = getFullConfig(options);
184
+ const { apiUrl, ...scope } = config;
185
+ if (!apiUrl) {
186
+ throw new Error("[Env.Guards] Missing required configuration: apiUrl is required.");
187
+ }
188
+ const runtimeKey = await resolveRuntimeKey(config);
189
+ const timeout = options?.timeout ?? 5e3;
190
+ const now = Date.now();
191
+ const TOKEN_SKEW_MS = 30 * 1e3;
192
+ const fingerprint = getConfigFingerprint(apiUrl, runtimeKey, config);
193
+ if (!cache.has(fingerprint)) {
194
+ cache.set(fingerprint, { token: null, tokenExpiresAt: null, bundle: null });
195
+ }
196
+ const state = cache.get(fingerprint);
197
+ let token = state.token;
198
+ let tokenValid = token && state.tokenExpiresAt && state.tokenExpiresAt > now + TOKEN_SKEW_MS;
199
+ if (!tokenValid) {
200
+ token = await exchangeToken(apiUrl, runtimeKey, scope, timeout, state);
201
+ }
202
+ if (state.bundle && tokenValid) {
203
+ Object.assign(process.env, state.bundle);
204
+ return state.bundle;
205
+ }
206
+ try {
207
+ const values = await fetchBundle(apiUrl, token, timeout);
208
+ state.bundle = values;
209
+ Object.assign(process.env, values);
210
+ return values;
211
+ } catch (err) {
212
+ if (err.message === "401") {
213
+ state.token = null;
214
+ state.bundle = null;
215
+ const newToken = await exchangeToken(apiUrl, runtimeKey, scope, timeout, state);
216
+ const values = await fetchBundle(apiUrl, newToken, timeout);
217
+ state.bundle = values;
218
+ Object.assign(process.env, values);
219
+ return values;
220
+ }
221
+ throw err;
222
+ }
223
+ }
224
+ function loadEnv(options) {
225
+ if (pendingLoadPromise) {
226
+ return pendingLoadPromise;
227
+ }
228
+ pendingLoadPromise = loadEnvInternal(options).finally(() => {
229
+ pendingLoadPromise = null;
230
+ });
231
+ return pendingLoadPromise;
232
+ }
233
+
234
+ export { _resetState, loadEnv };
235
+ //# sourceMappingURL=index.mjs.map
236
+ //# sourceMappingURL=index.mjs.map