@ojolowoblue/lamba 1.0.2 → 1.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # lamba ⚡
2
2
 
3
- **`lamba`** is a universal, lightweight developer tool and browser widget for viewing, overriding, and swapping environment variables live in web applications (React, Vue, Next.js, Vite, Astro, Svelte, or Vanilla HTML/JS)—**without touching source code, restarting dev servers, or editing `.env` files.**
3
+ **`lamba`** is a universal, lightweight developer tool and browser widget for viewing, overriding, and swapping environment variables live in web applications—supporting **all framework prefixes** (`VITE_`, `NEXT_PUBLIC_`, `REACT_APP_`, `VUE_APP_`, `PUBLIC_`, `EXPO_PUBLIC_`, `NUXT_`, `GATSBY_`, or unprefixed keys) across React, Vue, Next.js, Vite, Create React App, Astro, Nuxt, Svelte, or Vanilla HTML/JS—**without touching source code, restarting dev servers, or editing `.env` files.**
4
4
 
5
5
  Deployed via CDN or installed via NPM, `lamba` injects a non-intrusive floating UI powered by **Shadow DOM encapsulation**. Devs, QA engineers, and project managers can test multiple API environments, toggle feature flags, switch authentication tokens, and swap backend clusters on the fly directly in the browser.
6
6
 
@@ -9,6 +9,7 @@ Deployed via CDN or installed via NPM, `lamba` injects a non-intrusive floating
9
9
  ## 🌟 Key Features
10
10
 
11
11
  - ⚡ **Zero-Setup CDN & NPM Support**: Add a single `<script>` tag or install via NPM/Yarn/PNPM.
12
+ - 🌐 **Universal Framework & Prefix Support**: Works out of the box with any env variable prefix (`VITE_`, `NEXT_PUBLIC_`, `REACT_APP_`, `VUE_APP_`, `PUBLIC_`, `EXPO_PUBLIC_`, `NUXT_`, `GATSBY_`) or unprefixed variables.
12
13
  - 🛡️ **Shadow DOM Encapsulation**: Modern glassmorphism UI rendered inside a Custom Element (`<lamba-widget>`), ensuring zero CSS style leakage into or out of your app.
13
14
  - 🌐 **Automatic Network Interception**: Automatically intercepts outbound `fetch()` and `XMLHttpRequest` calls hitting default base URLs and redirects them to your live active environment overrides on the fly.
14
15
  - 🎛️ **Preset Profile Manager**: Save named environment snapshots (e.g., *Staging API*, *Local Mock Server*, *QA Test Suite*, *Production Read-Only*) and switch between them with one click.
@@ -51,9 +52,11 @@ import lamba from '@ojolowoblue/lamba';
51
52
  lamba.init({
52
53
  position: 'bottom-right',
53
54
  env: {
54
- VITE_API_BASE_URL: 'https://api.dev.example.com',
55
- VITE_FEATURE_NEW_CHECKOUT: 'false',
55
+ // Supports any env prefix (VITE_, NEXT_PUBLIC_, REACT_APP_, VUE_APP_, PUBLIC_) or custom keys:
56
+ NEXT_PUBLIC_API_URL: 'https://api.dev.example.com',
57
+ REACT_APP_FEATURE_FLAG: 'false',
56
58
  VITE_ENABLE_ANALYTICS: 'true',
59
+ API_BASE_URL: 'https://api.dev.example.com',
57
60
  },
58
61
  });
59
62
  ```
@@ -174,6 +177,7 @@ Initializes the lamba manager, hydrates saved overrides from `localStorage`, ena
174
177
  | `secretKeysPattern` | `RegExp` | `/(KEY\|SECRET\|TOKEN\|PASSWORD\|AUTH\|PRIVATE)/i` | Regular expression to automatically obscure sensitive keys in the UI. |
175
178
  | `autoFetchEnvFile` | `boolean` | `false` | Whether to attempt fetching root `/.env` file during local development. |
176
179
  | `interceptNetworkRequests` | `boolean` | `true` | Whether to implicitly intercept `fetch` & `XHR` calls matching original base URLs. |
180
+ | `allowedPrefixes` | `string \| string[] \| RegExp \| null` | `null` | Optional prefix filter (e.g. `['VITE_', 'NEXT_PUBLIC_']`). Omitting allows ALL keys regardless of prefix. |
177
181
 
178
182
  ---
179
183
 
package/dist/index.d.mts CHANGED
@@ -41,6 +41,11 @@ interface LambaOptions {
41
41
  * Defaults to true.
42
42
  */
43
43
  interceptNetworkRequests?: boolean;
44
+ /**
45
+ * Optional prefix filter or array of prefixes to include (e.g. ['VITE_', 'NEXT_PUBLIC_', 'REACT_APP_']).
46
+ * If omitted, null, or empty, ALL environment variable keys are allowed and supported regardless of prefix.
47
+ */
48
+ allowedPrefixes?: string | string[] | RegExp | null;
44
49
  }
45
50
  type EnvChangeListener = (key: string, value: string, isOverridden: boolean) => void;
46
51
  type StoreChangeListener = (variables: Record<string, EnvVariable>, presets: PresetProfile[], activePresetId: string | null) => void;
package/dist/index.d.ts CHANGED
@@ -41,6 +41,11 @@ interface LambaOptions {
41
41
  * Defaults to true.
42
42
  */
43
43
  interceptNetworkRequests?: boolean;
44
+ /**
45
+ * Optional prefix filter or array of prefixes to include (e.g. ['VITE_', 'NEXT_PUBLIC_', 'REACT_APP_']).
46
+ * If omitted, null, or empty, ALL environment variable keys are allowed and supported regardless of prefix.
47
+ */
48
+ allowedPrefixes?: string | string[] | RegExp | null;
44
49
  }
45
50
  type EnvChangeListener = (key: string, value: string, isOverridden: boolean) => void;
46
51
  type StoreChangeListener = (variables: Record<string, EnvVariable>, presets: PresetProfile[], activePresetId: string | null) => void;
package/dist/index.js CHANGED
@@ -136,25 +136,41 @@ function stringifyEnv(env) {
136
136
  function autoDiscoverBrowserEnv() {
137
137
  const discovered = {};
138
138
  if (typeof window === "undefined") return discovered;
139
+ const extractPrimitiveProps = (obj) => {
140
+ if (!obj || typeof obj !== "object") return;
141
+ for (const [k, v] of Object.entries(obj)) {
142
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
143
+ discovered[k] = String(v);
144
+ }
145
+ }
146
+ };
139
147
  try {
140
148
  const winProcess = window.process;
141
149
  if (winProcess && winProcess.env && typeof winProcess.env === "object") {
142
- for (const [k, v] of Object.entries(winProcess.env)) {
143
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
144
- discovered[k] = String(v);
145
- }
146
- }
150
+ extractPrimitiveProps(winProcess.env);
147
151
  }
148
152
  } catch (e) {
149
153
  }
150
154
  try {
151
- const customEnv = window.__ENV__ || window.ENV;
152
- if (customEnv && typeof customEnv === "object") {
153
- for (const [k, v] of Object.entries(customEnv)) {
154
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
155
- discovered[k] = String(v);
156
- }
157
- }
155
+ const nextData = window.__NEXT_DATA__;
156
+ if (nextData) {
157
+ if (nextData.env) extractPrimitiveProps(nextData.env);
158
+ if (nextData.runtimeConfig?.public) extractPrimitiveProps(nextData.runtimeConfig.public);
159
+ }
160
+ } catch (e) {
161
+ }
162
+ try {
163
+ const nuxtData = window.__NUXT__;
164
+ if (nuxtData?.config?.public) {
165
+ extractPrimitiveProps(nuxtData.config.public);
166
+ }
167
+ } catch (e) {
168
+ }
169
+ try {
170
+ const win = window;
171
+ const customEnv = win.__ENV__ || win.ENV || win.PUBLIC_ENV || win.__LAMBA_ENV__;
172
+ if (customEnv) {
173
+ extractPrimitiveProps(customEnv);
158
174
  }
159
175
  } catch (e) {
160
176
  }
@@ -205,6 +221,7 @@ var EnvStore = class {
205
221
  this.envChangeListeners = /* @__PURE__ */ new Set();
206
222
  this.storeChangeListeners = /* @__PURE__ */ new Set();
207
223
  this.secretPattern = options.secretKeysPattern || DEFAULT_SECRET_PATTERN;
224
+ this.allowedPrefixes = options.allowedPrefixes;
208
225
  this.presetManager = new PresetManager();
209
226
  this.loadOverridesFromStorage();
210
227
  if (options.env) {
@@ -241,7 +258,6 @@ var EnvStore = class {
241
258
  }
242
259
  }
243
260
  /**
244
- * Monkey-patches window.process.env to reactively return overridden values.
245
261
  */
246
262
  patchProcessEnv() {
247
263
  if (typeof window === "undefined") return;
@@ -270,11 +286,30 @@ var EnvStore = class {
270
286
  } catch (e) {
271
287
  }
272
288
  }
289
+ /**
290
+ * Checks if an environment variable key satisfies the allowedPrefixes option.
291
+ */
292
+ isAllowedKey(key) {
293
+ if (!this.allowedPrefixes) return true;
294
+ if (typeof this.allowedPrefixes === "string") {
295
+ return key.startsWith(this.allowedPrefixes);
296
+ }
297
+ if (Array.isArray(this.allowedPrefixes)) {
298
+ if (this.allowedPrefixes.length === 0) return true;
299
+ return this.allowedPrefixes.some((p) => key.startsWith(p));
300
+ }
301
+ if (this.allowedPrefixes instanceof RegExp) {
302
+ return this.allowedPrefixes.test(key);
303
+ }
304
+ return true;
305
+ }
273
306
  mergeDefaults(env) {
274
307
  let changed = false;
275
- for (const [key, defaultValue] of Object.entries(env)) {
308
+ for (const [key, rawDefault] of Object.entries(env)) {
309
+ if (!this.isAllowedKey(key)) continue;
310
+ const defaultValue = rawDefault !== null && rawDefault !== void 0 ? String(rawDefault) : "";
276
311
  const isOverridden = key in this.overrides;
277
- const currentValue = isOverridden ? this.overrides[key] : defaultValue;
312
+ const currentValue = isOverridden ? String(this.overrides[key] ?? "") : defaultValue;
278
313
  const isSecret = this.secretPattern.test(key);
279
314
  const existing = this.variables.get(key);
280
315
  if (!existing) {
@@ -459,9 +494,12 @@ var NetworkInterceptor = class {
459
494
  const allVars = this.store.getAll();
460
495
  let resultUrl = url;
461
496
  for (const varObj of Object.values(allVars)) {
462
- if (varObj.isOverridden && varObj.defaultValue && varObj.value) {
463
- const defaultBase = varObj.defaultValue.replace(/\/+$/, "");
464
- const overrideBase = varObj.value.replace(/\/+$/, "");
497
+ if (varObj.isOverridden && varObj.defaultValue !== void 0 && varObj.value !== void 0) {
498
+ const rawDefault = String(varObj.defaultValue ?? "");
499
+ const rawOverride = String(varObj.value ?? "");
500
+ if (!rawDefault || !rawOverride) continue;
501
+ const defaultBase = rawDefault.replace(/\/+$/, "");
502
+ const overrideBase = rawOverride.replace(/\/+$/, "");
465
503
  if (defaultBase && resultUrl.includes(defaultBase)) {
466
504
  resultUrl = resultUrl.replace(defaultBase, overrideBase);
467
505
  }
@@ -1154,7 +1192,7 @@ var ModalUI = class {
1154
1192
  </div>
1155
1193
  `;
1156
1194
  footer.querySelector(".lamba-add-var-btn")?.addEventListener("click", () => {
1157
- const key = prompt("Enter new environment variable key name (e.g. VITE_API_URL):");
1195
+ const key = prompt("Enter new environment variable key name (e.g. API_URL, NEXT_PUBLIC_API_URL, REACT_APP_API_URL, VITE_API_URL):");
1158
1196
  if (key) {
1159
1197
  const value = prompt(`Enter value for ${key}:`) || "";
1160
1198
  this.store.setOverride(key, value);
@@ -1337,7 +1375,8 @@ var ModalUI = class {
1337
1375
  return card;
1338
1376
  }
1339
1377
  escapeHtml(str) {
1340
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1378
+ if (str === null || str === void 0) return "";
1379
+ return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1341
1380
  }
1342
1381
  };
1343
1382
 
@@ -1372,6 +1411,23 @@ var LambaManager = class {
1372
1411
  * in a dynamic proxy that implicitly resolves live overrides from lamba.
1373
1412
  */
1374
1413
  wrap(targetEnv) {
1414
+ if (!this.store) {
1415
+ this.init();
1416
+ }
1417
+ if (targetEnv && typeof targetEnv === "object" && this.store) {
1418
+ try {
1419
+ const defaultObj = {};
1420
+ for (const [k, v] of Object.entries(targetEnv)) {
1421
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
1422
+ defaultObj[k] = String(v);
1423
+ }
1424
+ }
1425
+ if (Object.keys(defaultObj).length > 0) {
1426
+ this.store.mergeDefaults(defaultObj);
1427
+ }
1428
+ } catch (e) {
1429
+ }
1430
+ }
1375
1431
  const self = this;
1376
1432
  return new Proxy(targetEnv, {
1377
1433
  get(target, prop) {
@@ -1485,8 +1541,8 @@ var LambaManager = class {
1485
1541
  var lamba = new LambaManager();
1486
1542
  if (typeof window !== "undefined") {
1487
1543
  window.lamba = lamba;
1488
- const isCdnScript = document.currentScript !== null || !!document.querySelector('script[src*="lamba"]');
1489
- if (isCdnScript) {
1544
+ const currentScript = document.currentScript;
1545
+ if (currentScript && (currentScript.hasAttribute("data-lamba-auto") || currentScript.hasAttribute("data-auto-init"))) {
1490
1546
  lamba.init();
1491
1547
  }
1492
1548
  }