@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 +7 -3
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +78 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +78 -22
- package/dist/index.mjs.map +1 -1
- package/dist/integrations/react.js +78 -22
- package/dist/integrations/react.js.map +1 -1
- package/dist/integrations/react.mjs +78 -22
- package/dist/integrations/react.mjs.map +1 -1
- package/dist/integrations/vue.d.mts +1 -1
- package/dist/integrations/vue.d.ts +1 -1
- package/dist/integrations/vue.js +78 -22
- package/dist/integrations/vue.js.map +1 -1
- package/dist/integrations/vue.mjs +78 -22
- package/dist/integrations/vue.mjs.map +1 -1
- package/dist/lamba.min.js +15 -15
- package/package.json +1 -1
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
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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,
|
|
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
|
|
464
|
-
const
|
|
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
|
-
|
|
1378
|
+
if (str === null || str === void 0) return "";
|
|
1379
|
+
return String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
|
1489
|
-
if (
|
|
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
|
}
|