@rusamer/envgod 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var os = require('os');
4
- var path = require('path');
5
- var fs = require('fs');
3
+ var keytar = require('keytar');
4
+
5
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
+
7
+ var keytar__default = /*#__PURE__*/_interopDefault(keytar);
6
8
 
7
9
  // src/index.ts
8
10
  var cache = /* @__PURE__ */ new Map();
@@ -68,12 +70,14 @@ async function exchangeToken(config, timeout, state) {
68
70
  state.tokenExpiresAt = new Date(data.expiresAt).getTime();
69
71
  return data.token;
70
72
  }
71
- async function readUserToken() {
72
- const tokenPath = path.join(os.homedir(), ".envgod", "token.json");
73
+ var KEYTAR_SERVICE = "envgod-cli";
74
+ var KEYTAR_ACCOUNT = "default";
75
+ async function getAccessToken() {
76
+ const data = await keytar__default.default.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
77
+ if (!data) return null;
73
78
  try {
74
- const content = await fs.promises.readFile(tokenPath, "utf-8");
75
- const data = JSON.parse(content);
76
- return data?.token || null;
79
+ const parsed = JSON.parse(data);
80
+ return parsed.accessToken || parsed.refreshToken || null;
77
81
  } catch {
78
82
  return null;
79
83
  }
@@ -123,7 +127,7 @@ async function loadEnvInternal(options) {
123
127
  checkBrowser();
124
128
  const config = getEnvGodConfig(options);
125
129
  if (!config.apiKey) {
126
- const userToken = await readUserToken();
130
+ const userToken = await getAccessToken();
127
131
  if (!userToken) {
128
132
  throw new Error("[EnvGod] Not logged in. Please run `envgod login`.");
129
133
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["join","homedir","fs"],"mappings":"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","file":"index.js","sourcesContent":["import { homedir } from 'os';\r\nimport { join } from 'path';\r\nimport { promises as fs } from 'fs';\r\nimport type { EnvGodConfig, 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\n/**\r\n * Validates and returns the configuration.\r\n * Prioritizes options > process.env.\r\n */\r\nexport function getEnvGodConfig(options?: LoadEnvOptions): EnvGodConfig {\r\n const env = process.env;\r\n const config = {\r\n apiUrl: options?.config?.apiUrl ?? env.ENVGOD_API_URL,\r\n apiKey: options?.config?.apiKey ?? env.ENVGOD_API_KEY,\r\n project: options?.config?.project ?? env.ENVGOD_PROJECT,\r\n env: options?.config?.env ?? env.ENVGOD_ENV,\r\n service: options?.config?.service ?? env.ENVGOD_SERVICE,\r\n };\r\n\r\n if (!config.apiUrl) {\r\n throw new Error('[EnvGod] Missing required configuration: apiUrl is required.');\r\n }\r\n\r\n return config as EnvGodConfig;\r\n}\r\n\r\n/**\r\n * Checks if the current environment is a browser.\r\n */\r\nfunction checkBrowser() {\r\n if (typeof window !== 'undefined') {\r\n throw new Error('[EnvGod] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.');\r\n }\r\n}\r\n\r\n/**\r\n * Fetches with timeout.\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(`[EnvGod] 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 exchangeToken(config: EnvGodConfig, timeout: number, state: CacheState): Promise<string> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/auth/exchange`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] 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 readUserToken(): Promise<string | null> {\r\n const tokenPath = join(homedir(), '.envgod', 'token.json');\r\n try {\r\n const content = await fs.readFile(tokenPath, 'utf-8');\r\n const data = JSON.parse(content);\r\n return data?.token || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nasync function fetchSecretsWithUserToken(config: EnvGodConfig, userToken: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/secrets`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${userToken}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Failed to fetch secrets: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nasync function fetchBundle(config: EnvGodConfig, token: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/bundle`, {\r\n method: 'GET',\r\n headers: {\r\n 'Authorization': `Bearer ${token}`,\r\n },\r\n timeout,\r\n });\r\n\r\n if (res.status === 401) {\r\n throw new Error('401'); // Signal to retry\r\n }\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Fetch bundle failed: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nfunction getConfigFingerprint(config: EnvGodConfig): string {\r\n const keyPrefix = config.apiKey ? config.apiKey.substring(0, 8) : 'interactive';\r\n return [config.apiUrl, keyPrefix, 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 = getEnvGodConfig(options);\r\n\r\n // Interactive mode: Use user token if no API key is provided\r\n if (!config.apiKey) {\r\n const userToken = await readUserToken();\r\n if (!userToken) {\r\n throw new Error('[EnvGod] Not logged in. Please run `envgod login`.');\r\n }\r\n const values = await fetchSecretsWithUserToken(config, userToken, options?.timeout ?? 5000);\r\n Object.assign(process.env, values);\r\n return values;\r\n }\r\n\r\n const timeout = options?.timeout ?? 5000;\r\n const now = Date.now();\r\n const TOKEN_SKEW_MS = 30 * 1000; // 30 seconds\r\n\r\n const fingerprint = getConfigFingerprint(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(config, 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(config, 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(config, timeout, state);\r\n const values = await fetchBundle(config, 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\n/**\r\n * Main entry point to load environment variables.\r\n * Uses Singleflight pattern to prevent concurrent network requests.\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)\r\n .finally(() => {\r\n pendingLoadPromise = null;\r\n });\r\n\r\n return pendingLoadPromise;\r\n}\r\n\r\nexport * from './types.js';\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["keytar"],"mappings":"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","file":"index.js","sourcesContent":["import keytar from 'keytar';\r\nimport type { EnvGodConfig, 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\n/**\r\n * Validates and returns the configuration.\r\n * Prioritizes options > process.env.\r\n */\r\nexport function getEnvGodConfig(options?: LoadEnvOptions): EnvGodConfig {\r\n const env = process.env;\r\n const config = {\r\n apiUrl: options?.config?.apiUrl ?? env.ENVGOD_API_URL,\r\n apiKey: options?.config?.apiKey ?? env.ENVGOD_API_KEY,\r\n project: options?.config?.project ?? env.ENVGOD_PROJECT,\r\n env: options?.config?.env ?? env.ENVGOD_ENV,\r\n service: options?.config?.service ?? env.ENVGOD_SERVICE,\r\n };\r\n\r\n if (!config.apiUrl) {\r\n throw new Error('[EnvGod] Missing required configuration: apiUrl is required.');\r\n }\r\n\r\n return config as EnvGodConfig;\r\n}\r\n\r\n/**\r\n * Checks if the current environment is a browser.\r\n */\r\nfunction checkBrowser() {\r\n if (typeof window !== 'undefined') {\r\n throw new Error('[EnvGod] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.');\r\n }\r\n}\r\n\r\n/**\r\n * Fetches with timeout.\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(`[EnvGod] 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 exchangeToken(config: EnvGodConfig, timeout: number, state: CacheState): Promise<string> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/auth/exchange`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] 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\nconst KEYTAR_SERVICE = 'envgod-cli';\r\nconst KEYTAR_ACCOUNT = 'default';\r\n\r\nasync function getAccessToken(): Promise<string | null> {\r\n const data = await keytar.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);\r\n if (!data) return null;\r\n try {\r\n const parsed = JSON.parse(data);\r\n // The CLI's device flow currently stores the access token in both slots.\r\n return parsed.accessToken || parsed.refreshToken || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nasync function fetchSecretsWithUserToken(config: EnvGodConfig, userToken: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/secrets`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${userToken}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Failed to fetch secrets: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nasync function fetchBundle(config: EnvGodConfig, token: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/bundle`, {\r\n method: 'GET',\r\n headers: {\r\n 'Authorization': `Bearer ${token}`,\r\n },\r\n timeout,\r\n });\r\n\r\n if (res.status === 401) {\r\n throw new Error('401'); // Signal to retry\r\n }\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Fetch bundle failed: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nfunction getConfigFingerprint(config: EnvGodConfig): string {\r\n const keyPrefix = config.apiKey ? config.apiKey.substring(0, 8) : 'interactive';\r\n return [config.apiUrl, keyPrefix, 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 = getEnvGodConfig(options);\r\n\r\n // Interactive mode: Use user token if no API key is provided\r\n if (!config.apiKey) {\r\n const userToken = await getAccessToken();\r\n if (!userToken) {\r\n throw new Error('[EnvGod] Not logged in. Please run `envgod login`.');\r\n }\r\n const values = await fetchSecretsWithUserToken(config, userToken, options?.timeout ?? 5000);\r\n Object.assign(process.env, values);\r\n return values;\r\n }\r\n\r\n const timeout = options?.timeout ?? 5000;\r\n const now = Date.now();\r\n const TOKEN_SKEW_MS = 30 * 1000; // 30 seconds\r\n\r\n const fingerprint = getConfigFingerprint(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(config, 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(config, 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(config, timeout, state);\r\n const values = await fetchBundle(config, 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\n/**\r\n * Main entry point to load environment variables.\r\n * Uses Singleflight pattern to prevent concurrent network requests.\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)\r\n .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 CHANGED
@@ -1,6 +1,4 @@
1
- import { homedir } from 'os';
2
- import { join } from 'path';
3
- import { promises } from 'fs';
1
+ import keytar from 'keytar';
4
2
 
5
3
  // src/index.ts
6
4
  var cache = /* @__PURE__ */ new Map();
@@ -66,12 +64,14 @@ async function exchangeToken(config, timeout, state) {
66
64
  state.tokenExpiresAt = new Date(data.expiresAt).getTime();
67
65
  return data.token;
68
66
  }
69
- async function readUserToken() {
70
- const tokenPath = join(homedir(), ".envgod", "token.json");
67
+ var KEYTAR_SERVICE = "envgod-cli";
68
+ var KEYTAR_ACCOUNT = "default";
69
+ async function getAccessToken() {
70
+ const data = await keytar.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
71
+ if (!data) return null;
71
72
  try {
72
- const content = await promises.readFile(tokenPath, "utf-8");
73
- const data = JSON.parse(content);
74
- return data?.token || null;
73
+ const parsed = JSON.parse(data);
74
+ return parsed.accessToken || parsed.refreshToken || null;
75
75
  } catch {
76
76
  return null;
77
77
  }
@@ -121,7 +121,7 @@ async function loadEnvInternal(options) {
121
121
  checkBrowser();
122
122
  const config = getEnvGodConfig(options);
123
123
  if (!config.apiKey) {
124
- const userToken = await readUserToken();
124
+ const userToken = await getAccessToken();
125
125
  if (!userToken) {
126
126
  throw new Error("[EnvGod] Not logged in. Please run `envgod login`.");
127
127
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["fs"],"mappings":"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","file":"index.mjs","sourcesContent":["import { homedir } from 'os';\r\nimport { join } from 'path';\r\nimport { promises as fs } from 'fs';\r\nimport type { EnvGodConfig, 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\n/**\r\n * Validates and returns the configuration.\r\n * Prioritizes options > process.env.\r\n */\r\nexport function getEnvGodConfig(options?: LoadEnvOptions): EnvGodConfig {\r\n const env = process.env;\r\n const config = {\r\n apiUrl: options?.config?.apiUrl ?? env.ENVGOD_API_URL,\r\n apiKey: options?.config?.apiKey ?? env.ENVGOD_API_KEY,\r\n project: options?.config?.project ?? env.ENVGOD_PROJECT,\r\n env: options?.config?.env ?? env.ENVGOD_ENV,\r\n service: options?.config?.service ?? env.ENVGOD_SERVICE,\r\n };\r\n\r\n if (!config.apiUrl) {\r\n throw new Error('[EnvGod] Missing required configuration: apiUrl is required.');\r\n }\r\n\r\n return config as EnvGodConfig;\r\n}\r\n\r\n/**\r\n * Checks if the current environment is a browser.\r\n */\r\nfunction checkBrowser() {\r\n if (typeof window !== 'undefined') {\r\n throw new Error('[EnvGod] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.');\r\n }\r\n}\r\n\r\n/**\r\n * Fetches with timeout.\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(`[EnvGod] 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 exchangeToken(config: EnvGodConfig, timeout: number, state: CacheState): Promise<string> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/auth/exchange`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] 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 readUserToken(): Promise<string | null> {\r\n const tokenPath = join(homedir(), '.envgod', 'token.json');\r\n try {\r\n const content = await fs.readFile(tokenPath, 'utf-8');\r\n const data = JSON.parse(content);\r\n return data?.token || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nasync function fetchSecretsWithUserToken(config: EnvGodConfig, userToken: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/secrets`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${userToken}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Failed to fetch secrets: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nasync function fetchBundle(config: EnvGodConfig, token: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/bundle`, {\r\n method: 'GET',\r\n headers: {\r\n 'Authorization': `Bearer ${token}`,\r\n },\r\n timeout,\r\n });\r\n\r\n if (res.status === 401) {\r\n throw new Error('401'); // Signal to retry\r\n }\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Fetch bundle failed: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nfunction getConfigFingerprint(config: EnvGodConfig): string {\r\n const keyPrefix = config.apiKey ? config.apiKey.substring(0, 8) : 'interactive';\r\n return [config.apiUrl, keyPrefix, 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 = getEnvGodConfig(options);\r\n\r\n // Interactive mode: Use user token if no API key is provided\r\n if (!config.apiKey) {\r\n const userToken = await readUserToken();\r\n if (!userToken) {\r\n throw new Error('[EnvGod] Not logged in. Please run `envgod login`.');\r\n }\r\n const values = await fetchSecretsWithUserToken(config, userToken, options?.timeout ?? 5000);\r\n Object.assign(process.env, values);\r\n return values;\r\n }\r\n\r\n const timeout = options?.timeout ?? 5000;\r\n const now = Date.now();\r\n const TOKEN_SKEW_MS = 30 * 1000; // 30 seconds\r\n\r\n const fingerprint = getConfigFingerprint(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(config, 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(config, 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(config, timeout, state);\r\n const values = await fetchBundle(config, 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\n/**\r\n * Main entry point to load environment variables.\r\n * Uses Singleflight pattern to prevent concurrent network requests.\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)\r\n .finally(() => {\r\n pendingLoadPromise = null;\r\n });\r\n\r\n return pendingLoadPromise;\r\n}\r\n\r\nexport * from './types.js';\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import keytar from 'keytar';\r\nimport type { EnvGodConfig, 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\n/**\r\n * Validates and returns the configuration.\r\n * Prioritizes options > process.env.\r\n */\r\nexport function getEnvGodConfig(options?: LoadEnvOptions): EnvGodConfig {\r\n const env = process.env;\r\n const config = {\r\n apiUrl: options?.config?.apiUrl ?? env.ENVGOD_API_URL,\r\n apiKey: options?.config?.apiKey ?? env.ENVGOD_API_KEY,\r\n project: options?.config?.project ?? env.ENVGOD_PROJECT,\r\n env: options?.config?.env ?? env.ENVGOD_ENV,\r\n service: options?.config?.service ?? env.ENVGOD_SERVICE,\r\n };\r\n\r\n if (!config.apiUrl) {\r\n throw new Error('[EnvGod] Missing required configuration: apiUrl is required.');\r\n }\r\n\r\n return config as EnvGodConfig;\r\n}\r\n\r\n/**\r\n * Checks if the current environment is a browser.\r\n */\r\nfunction checkBrowser() {\r\n if (typeof window !== 'undefined') {\r\n throw new Error('[EnvGod] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.');\r\n }\r\n}\r\n\r\n/**\r\n * Fetches with timeout.\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(`[EnvGod] 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 exchangeToken(config: EnvGodConfig, timeout: number, state: CacheState): Promise<string> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/auth/exchange`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] 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\nconst KEYTAR_SERVICE = 'envgod-cli';\r\nconst KEYTAR_ACCOUNT = 'default';\r\n\r\nasync function getAccessToken(): Promise<string | null> {\r\n const data = await keytar.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);\r\n if (!data) return null;\r\n try {\r\n const parsed = JSON.parse(data);\r\n // The CLI's device flow currently stores the access token in both slots.\r\n return parsed.accessToken || parsed.refreshToken || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nasync function fetchSecretsWithUserToken(config: EnvGodConfig, userToken: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/secrets`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${userToken}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Failed to fetch secrets: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nasync function fetchBundle(config: EnvGodConfig, token: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/bundle`, {\r\n method: 'GET',\r\n headers: {\r\n 'Authorization': `Bearer ${token}`,\r\n },\r\n timeout,\r\n });\r\n\r\n if (res.status === 401) {\r\n throw new Error('401'); // Signal to retry\r\n }\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Fetch bundle failed: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nfunction getConfigFingerprint(config: EnvGodConfig): string {\r\n const keyPrefix = config.apiKey ? config.apiKey.substring(0, 8) : 'interactive';\r\n return [config.apiUrl, keyPrefix, 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 = getEnvGodConfig(options);\r\n\r\n // Interactive mode: Use user token if no API key is provided\r\n if (!config.apiKey) {\r\n const userToken = await getAccessToken();\r\n if (!userToken) {\r\n throw new Error('[EnvGod] Not logged in. Please run `envgod login`.');\r\n }\r\n const values = await fetchSecretsWithUserToken(config, userToken, options?.timeout ?? 5000);\r\n Object.assign(process.env, values);\r\n return values;\r\n }\r\n\r\n const timeout = options?.timeout ?? 5000;\r\n const now = Date.now();\r\n const TOKEN_SKEW_MS = 30 * 1000; // 30 seconds\r\n\r\n const fingerprint = getConfigFingerprint(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(config, 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(config, 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(config, timeout, state);\r\n const values = await fetchBundle(config, 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\n/**\r\n * Main entry point to load environment variables.\r\n * Uses Singleflight pattern to prevent concurrent network requests.\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)\r\n .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/next.js CHANGED
@@ -1,9 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  require('server-only');
4
- var os = require('os');
5
- var path = require('path');
6
- var fs = require('fs');
4
+ var keytar = require('keytar');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var keytar__default = /*#__PURE__*/_interopDefault(keytar);
7
9
 
8
10
  // src/next.ts
9
11
  var cache = /* @__PURE__ */ new Map();
@@ -65,12 +67,14 @@ async function exchangeToken(config, timeout, state) {
65
67
  state.tokenExpiresAt = new Date(data.expiresAt).getTime();
66
68
  return data.token;
67
69
  }
68
- async function readUserToken() {
69
- const tokenPath = path.join(os.homedir(), ".envgod", "token.json");
70
+ var KEYTAR_SERVICE = "envgod-cli";
71
+ var KEYTAR_ACCOUNT = "default";
72
+ async function getAccessToken() {
73
+ const data = await keytar__default.default.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
74
+ if (!data) return null;
70
75
  try {
71
- const content = await fs.promises.readFile(tokenPath, "utf-8");
72
- const data = JSON.parse(content);
73
- return data?.token || null;
76
+ const parsed = JSON.parse(data);
77
+ return parsed.accessToken || parsed.refreshToken || null;
74
78
  } catch {
75
79
  return null;
76
80
  }
@@ -120,7 +124,7 @@ async function loadEnvInternal(options) {
120
124
  checkBrowser();
121
125
  const config = getEnvGodConfig(options);
122
126
  if (!config.apiKey) {
123
- const userToken = await readUserToken();
127
+ const userToken = await getAccessToken();
124
128
  if (!userToken) {
125
129
  throw new Error("[EnvGod] Not logged in. Please run `envgod login`.");
126
130
  }
package/dist/next.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/next.ts"],"names":["join","homedir","fs"],"mappings":"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","file":"next.js","sourcesContent":["import { homedir } from 'os';\r\nimport { join } from 'path';\r\nimport { promises as fs } from 'fs';\r\nimport type { EnvGodConfig, 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\n/**\r\n * Validates and returns the configuration.\r\n * Prioritizes options > process.env.\r\n */\r\nexport function getEnvGodConfig(options?: LoadEnvOptions): EnvGodConfig {\r\n const env = process.env;\r\n const config = {\r\n apiUrl: options?.config?.apiUrl ?? env.ENVGOD_API_URL,\r\n apiKey: options?.config?.apiKey ?? env.ENVGOD_API_KEY,\r\n project: options?.config?.project ?? env.ENVGOD_PROJECT,\r\n env: options?.config?.env ?? env.ENVGOD_ENV,\r\n service: options?.config?.service ?? env.ENVGOD_SERVICE,\r\n };\r\n\r\n if (!config.apiUrl) {\r\n throw new Error('[EnvGod] Missing required configuration: apiUrl is required.');\r\n }\r\n\r\n return config as EnvGodConfig;\r\n}\r\n\r\n/**\r\n * Checks if the current environment is a browser.\r\n */\r\nfunction checkBrowser() {\r\n if (typeof window !== 'undefined') {\r\n throw new Error('[EnvGod] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.');\r\n }\r\n}\r\n\r\n/**\r\n * Fetches with timeout.\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(`[EnvGod] 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 exchangeToken(config: EnvGodConfig, timeout: number, state: CacheState): Promise<string> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/auth/exchange`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] 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 readUserToken(): Promise<string | null> {\r\n const tokenPath = join(homedir(), '.envgod', 'token.json');\r\n try {\r\n const content = await fs.readFile(tokenPath, 'utf-8');\r\n const data = JSON.parse(content);\r\n return data?.token || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nasync function fetchSecretsWithUserToken(config: EnvGodConfig, userToken: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/secrets`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${userToken}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Failed to fetch secrets: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nasync function fetchBundle(config: EnvGodConfig, token: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/bundle`, {\r\n method: 'GET',\r\n headers: {\r\n 'Authorization': `Bearer ${token}`,\r\n },\r\n timeout,\r\n });\r\n\r\n if (res.status === 401) {\r\n throw new Error('401'); // Signal to retry\r\n }\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Fetch bundle failed: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nfunction getConfigFingerprint(config: EnvGodConfig): string {\r\n const keyPrefix = config.apiKey ? config.apiKey.substring(0, 8) : 'interactive';\r\n return [config.apiUrl, keyPrefix, 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 = getEnvGodConfig(options);\r\n\r\n // Interactive mode: Use user token if no API key is provided\r\n if (!config.apiKey) {\r\n const userToken = await readUserToken();\r\n if (!userToken) {\r\n throw new Error('[EnvGod] Not logged in. Please run `envgod login`.');\r\n }\r\n const values = await fetchSecretsWithUserToken(config, userToken, options?.timeout ?? 5000);\r\n Object.assign(process.env, values);\r\n return values;\r\n }\r\n\r\n const timeout = options?.timeout ?? 5000;\r\n const now = Date.now();\r\n const TOKEN_SKEW_MS = 30 * 1000; // 30 seconds\r\n\r\n const fingerprint = getConfigFingerprint(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(config, 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(config, 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(config, timeout, state);\r\n const values = await fetchBundle(config, 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\n/**\r\n * Main entry point to load environment variables.\r\n * Uses Singleflight pattern to prevent concurrent network requests.\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)\r\n .finally(() => {\r\n pendingLoadPromise = null;\r\n });\r\n\r\n return pendingLoadPromise;\r\n}\r\n\r\nexport * from './types.js';\r\n","import 'server-only';\r\nimport { loadEnv, type LoadEnvOptions } from './index.js';\r\n\r\nexport async function loadServerEnv(options?: LoadEnvOptions) {\r\n return loadEnv(options);\r\n}\r\n\r\nexport * from './types.js';\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/next.ts"],"names":["keytar"],"mappings":"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","file":"next.js","sourcesContent":["import keytar from 'keytar';\r\nimport type { EnvGodConfig, 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\n/**\r\n * Validates and returns the configuration.\r\n * Prioritizes options > process.env.\r\n */\r\nexport function getEnvGodConfig(options?: LoadEnvOptions): EnvGodConfig {\r\n const env = process.env;\r\n const config = {\r\n apiUrl: options?.config?.apiUrl ?? env.ENVGOD_API_URL,\r\n apiKey: options?.config?.apiKey ?? env.ENVGOD_API_KEY,\r\n project: options?.config?.project ?? env.ENVGOD_PROJECT,\r\n env: options?.config?.env ?? env.ENVGOD_ENV,\r\n service: options?.config?.service ?? env.ENVGOD_SERVICE,\r\n };\r\n\r\n if (!config.apiUrl) {\r\n throw new Error('[EnvGod] Missing required configuration: apiUrl is required.');\r\n }\r\n\r\n return config as EnvGodConfig;\r\n}\r\n\r\n/**\r\n * Checks if the current environment is a browser.\r\n */\r\nfunction checkBrowser() {\r\n if (typeof window !== 'undefined') {\r\n throw new Error('[EnvGod] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.');\r\n }\r\n}\r\n\r\n/**\r\n * Fetches with timeout.\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(`[EnvGod] 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 exchangeToken(config: EnvGodConfig, timeout: number, state: CacheState): Promise<string> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/auth/exchange`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] 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\nconst KEYTAR_SERVICE = 'envgod-cli';\r\nconst KEYTAR_ACCOUNT = 'default';\r\n\r\nasync function getAccessToken(): Promise<string | null> {\r\n const data = await keytar.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);\r\n if (!data) return null;\r\n try {\r\n const parsed = JSON.parse(data);\r\n // The CLI's device flow currently stores the access token in both slots.\r\n return parsed.accessToken || parsed.refreshToken || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nasync function fetchSecretsWithUserToken(config: EnvGodConfig, userToken: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/secrets`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${userToken}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Failed to fetch secrets: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nasync function fetchBundle(config: EnvGodConfig, token: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/bundle`, {\r\n method: 'GET',\r\n headers: {\r\n 'Authorization': `Bearer ${token}`,\r\n },\r\n timeout,\r\n });\r\n\r\n if (res.status === 401) {\r\n throw new Error('401'); // Signal to retry\r\n }\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Fetch bundle failed: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nfunction getConfigFingerprint(config: EnvGodConfig): string {\r\n const keyPrefix = config.apiKey ? config.apiKey.substring(0, 8) : 'interactive';\r\n return [config.apiUrl, keyPrefix, 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 = getEnvGodConfig(options);\r\n\r\n // Interactive mode: Use user token if no API key is provided\r\n if (!config.apiKey) {\r\n const userToken = await getAccessToken();\r\n if (!userToken) {\r\n throw new Error('[EnvGod] Not logged in. Please run `envgod login`.');\r\n }\r\n const values = await fetchSecretsWithUserToken(config, userToken, options?.timeout ?? 5000);\r\n Object.assign(process.env, values);\r\n return values;\r\n }\r\n\r\n const timeout = options?.timeout ?? 5000;\r\n const now = Date.now();\r\n const TOKEN_SKEW_MS = 30 * 1000; // 30 seconds\r\n\r\n const fingerprint = getConfigFingerprint(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(config, 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(config, 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(config, timeout, state);\r\n const values = await fetchBundle(config, 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\n/**\r\n * Main entry point to load environment variables.\r\n * Uses Singleflight pattern to prevent concurrent network requests.\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)\r\n .finally(() => {\r\n pendingLoadPromise = null;\r\n });\r\n\r\n return pendingLoadPromise;\r\n}\r\n\r\nexport * from './types.js';\r\n","import 'server-only';\r\nimport { loadEnv, type LoadEnvOptions } from './index.js';\r\n\r\nexport async function loadServerEnv(options?: LoadEnvOptions) {\r\n return loadEnv(options);\r\n}\r\n\r\nexport * from './types.js';\r\n"]}
package/dist/next.mjs CHANGED
@@ -1,7 +1,5 @@
1
1
  import 'server-only';
2
- import { homedir } from 'os';
3
- import { join } from 'path';
4
- import { promises } from 'fs';
2
+ import keytar from 'keytar';
5
3
 
6
4
  // src/next.ts
7
5
  var cache = /* @__PURE__ */ new Map();
@@ -63,12 +61,14 @@ async function exchangeToken(config, timeout, state) {
63
61
  state.tokenExpiresAt = new Date(data.expiresAt).getTime();
64
62
  return data.token;
65
63
  }
66
- async function readUserToken() {
67
- const tokenPath = join(homedir(), ".envgod", "token.json");
64
+ var KEYTAR_SERVICE = "envgod-cli";
65
+ var KEYTAR_ACCOUNT = "default";
66
+ async function getAccessToken() {
67
+ const data = await keytar.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
68
+ if (!data) return null;
68
69
  try {
69
- const content = await promises.readFile(tokenPath, "utf-8");
70
- const data = JSON.parse(content);
71
- return data?.token || null;
70
+ const parsed = JSON.parse(data);
71
+ return parsed.accessToken || parsed.refreshToken || null;
72
72
  } catch {
73
73
  return null;
74
74
  }
@@ -118,7 +118,7 @@ async function loadEnvInternal(options) {
118
118
  checkBrowser();
119
119
  const config = getEnvGodConfig(options);
120
120
  if (!config.apiKey) {
121
- const userToken = await readUserToken();
121
+ const userToken = await getAccessToken();
122
122
  if (!userToken) {
123
123
  throw new Error("[EnvGod] Not logged in. Please run `envgod login`.");
124
124
  }
package/dist/next.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/next.ts"],"names":["fs"],"mappings":"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","file":"next.mjs","sourcesContent":["import { homedir } from 'os';\r\nimport { join } from 'path';\r\nimport { promises as fs } from 'fs';\r\nimport type { EnvGodConfig, 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\n/**\r\n * Validates and returns the configuration.\r\n * Prioritizes options > process.env.\r\n */\r\nexport function getEnvGodConfig(options?: LoadEnvOptions): EnvGodConfig {\r\n const env = process.env;\r\n const config = {\r\n apiUrl: options?.config?.apiUrl ?? env.ENVGOD_API_URL,\r\n apiKey: options?.config?.apiKey ?? env.ENVGOD_API_KEY,\r\n project: options?.config?.project ?? env.ENVGOD_PROJECT,\r\n env: options?.config?.env ?? env.ENVGOD_ENV,\r\n service: options?.config?.service ?? env.ENVGOD_SERVICE,\r\n };\r\n\r\n if (!config.apiUrl) {\r\n throw new Error('[EnvGod] Missing required configuration: apiUrl is required.');\r\n }\r\n\r\n return config as EnvGodConfig;\r\n}\r\n\r\n/**\r\n * Checks if the current environment is a browser.\r\n */\r\nfunction checkBrowser() {\r\n if (typeof window !== 'undefined') {\r\n throw new Error('[EnvGod] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.');\r\n }\r\n}\r\n\r\n/**\r\n * Fetches with timeout.\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(`[EnvGod] 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 exchangeToken(config: EnvGodConfig, timeout: number, state: CacheState): Promise<string> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/auth/exchange`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] 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 readUserToken(): Promise<string | null> {\r\n const tokenPath = join(homedir(), '.envgod', 'token.json');\r\n try {\r\n const content = await fs.readFile(tokenPath, 'utf-8');\r\n const data = JSON.parse(content);\r\n return data?.token || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nasync function fetchSecretsWithUserToken(config: EnvGodConfig, userToken: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/secrets`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${userToken}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Failed to fetch secrets: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nasync function fetchBundle(config: EnvGodConfig, token: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/bundle`, {\r\n method: 'GET',\r\n headers: {\r\n 'Authorization': `Bearer ${token}`,\r\n },\r\n timeout,\r\n });\r\n\r\n if (res.status === 401) {\r\n throw new Error('401'); // Signal to retry\r\n }\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Fetch bundle failed: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nfunction getConfigFingerprint(config: EnvGodConfig): string {\r\n const keyPrefix = config.apiKey ? config.apiKey.substring(0, 8) : 'interactive';\r\n return [config.apiUrl, keyPrefix, 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 = getEnvGodConfig(options);\r\n\r\n // Interactive mode: Use user token if no API key is provided\r\n if (!config.apiKey) {\r\n const userToken = await readUserToken();\r\n if (!userToken) {\r\n throw new Error('[EnvGod] Not logged in. Please run `envgod login`.');\r\n }\r\n const values = await fetchSecretsWithUserToken(config, userToken, options?.timeout ?? 5000);\r\n Object.assign(process.env, values);\r\n return values;\r\n }\r\n\r\n const timeout = options?.timeout ?? 5000;\r\n const now = Date.now();\r\n const TOKEN_SKEW_MS = 30 * 1000; // 30 seconds\r\n\r\n const fingerprint = getConfigFingerprint(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(config, 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(config, 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(config, timeout, state);\r\n const values = await fetchBundle(config, 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\n/**\r\n * Main entry point to load environment variables.\r\n * Uses Singleflight pattern to prevent concurrent network requests.\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)\r\n .finally(() => {\r\n pendingLoadPromise = null;\r\n });\r\n\r\n return pendingLoadPromise;\r\n}\r\n\r\nexport * from './types.js';\r\n","import 'server-only';\r\nimport { loadEnv, type LoadEnvOptions } from './index.js';\r\n\r\nexport async function loadServerEnv(options?: LoadEnvOptions) {\r\n return loadEnv(options);\r\n}\r\n\r\nexport * from './types.js';\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/next.ts"],"names":[],"mappings":"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","file":"next.mjs","sourcesContent":["import keytar from 'keytar';\r\nimport type { EnvGodConfig, 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\n/**\r\n * Validates and returns the configuration.\r\n * Prioritizes options > process.env.\r\n */\r\nexport function getEnvGodConfig(options?: LoadEnvOptions): EnvGodConfig {\r\n const env = process.env;\r\n const config = {\r\n apiUrl: options?.config?.apiUrl ?? env.ENVGOD_API_URL,\r\n apiKey: options?.config?.apiKey ?? env.ENVGOD_API_KEY,\r\n project: options?.config?.project ?? env.ENVGOD_PROJECT,\r\n env: options?.config?.env ?? env.ENVGOD_ENV,\r\n service: options?.config?.service ?? env.ENVGOD_SERVICE,\r\n };\r\n\r\n if (!config.apiUrl) {\r\n throw new Error('[EnvGod] Missing required configuration: apiUrl is required.');\r\n }\r\n\r\n return config as EnvGodConfig;\r\n}\r\n\r\n/**\r\n * Checks if the current environment is a browser.\r\n */\r\nfunction checkBrowser() {\r\n if (typeof window !== 'undefined') {\r\n throw new Error('[EnvGod] Security Warning: SDK execution attempting in browser environment. This SDK is server-only.');\r\n }\r\n}\r\n\r\n/**\r\n * Fetches with timeout.\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(`[EnvGod] 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 exchangeToken(config: EnvGodConfig, timeout: number, state: CacheState): Promise<string> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/auth/exchange`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${config.apiKey}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] 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\nconst KEYTAR_SERVICE = 'envgod-cli';\r\nconst KEYTAR_ACCOUNT = 'default';\r\n\r\nasync function getAccessToken(): Promise<string | null> {\r\n const data = await keytar.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);\r\n if (!data) return null;\r\n try {\r\n const parsed = JSON.parse(data);\r\n // The CLI's device flow currently stores the access token in both slots.\r\n return parsed.accessToken || parsed.refreshToken || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nasync function fetchSecretsWithUserToken(config: EnvGodConfig, userToken: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/secrets`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${userToken}`,\r\n },\r\n body: JSON.stringify({\r\n project: config.project,\r\n env: config.env,\r\n service: config.service,\r\n }),\r\n timeout,\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Failed to fetch secrets: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nasync function fetchBundle(config: EnvGodConfig, token: string, timeout: number): Promise<Record<string, string>> {\r\n const res = await fetchWithTimeout(`${config.apiUrl}/v1/bundle`, {\r\n method: 'GET',\r\n headers: {\r\n 'Authorization': `Bearer ${token}`,\r\n },\r\n timeout,\r\n });\r\n\r\n if (res.status === 401) {\r\n throw new Error('401'); // Signal to retry\r\n }\r\n\r\n if (!res.ok) {\r\n throw new Error(`[EnvGod] Fetch bundle failed: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data = (await res.json()) as BundleResponse;\r\n return data.values;\r\n}\r\n\r\nfunction getConfigFingerprint(config: EnvGodConfig): string {\r\n const keyPrefix = config.apiKey ? config.apiKey.substring(0, 8) : 'interactive';\r\n return [config.apiUrl, keyPrefix, 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 = getEnvGodConfig(options);\r\n\r\n // Interactive mode: Use user token if no API key is provided\r\n if (!config.apiKey) {\r\n const userToken = await getAccessToken();\r\n if (!userToken) {\r\n throw new Error('[EnvGod] Not logged in. Please run `envgod login`.');\r\n }\r\n const values = await fetchSecretsWithUserToken(config, userToken, options?.timeout ?? 5000);\r\n Object.assign(process.env, values);\r\n return values;\r\n }\r\n\r\n const timeout = options?.timeout ?? 5000;\r\n const now = Date.now();\r\n const TOKEN_SKEW_MS = 30 * 1000; // 30 seconds\r\n\r\n const fingerprint = getConfigFingerprint(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(config, 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(config, 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(config, timeout, state);\r\n const values = await fetchBundle(config, 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\n/**\r\n * Main entry point to load environment variables.\r\n * Uses Singleflight pattern to prevent concurrent network requests.\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)\r\n .finally(() => {\r\n pendingLoadPromise = null;\r\n });\r\n\r\n return pendingLoadPromise;\r\n}\r\n\r\nexport * from './types.js';\r\n","import 'server-only';\r\nimport { loadEnv, type LoadEnvOptions } from './index.js';\r\n\r\nexport async function loadServerEnv(options?: LoadEnvOptions) {\r\n return loadEnv(options);\r\n}\r\n\r\nexport * from './types.js';\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rusamer/envgod",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Secure Node.js/Next.js SDK for EnvGod",
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,6 +52,7 @@
52
52
  "vitest": "^1.0.0"
53
53
  },
54
54
  "dependencies": {
55
+ "keytar": "^7.9.0",
55
56
  "server-only": "^0.0.1"
56
57
  }
57
58
  }