@adriangalilea/utils 0.0.10
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 +99 -0
- package/dist/browser.d.ts +14 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +18 -0
- package/dist/browser.js.map +1 -0
- package/dist/currency/crypto-symbols-data.d.ts +10 -0
- package/dist/currency/crypto-symbols-data.d.ts.map +1 -0
- package/dist/currency/crypto-symbols-data.js +13765 -0
- package/dist/currency/crypto-symbols-data.js.map +1 -0
- package/dist/currency/crypto-symbols.d.ts +20 -0
- package/dist/currency/crypto-symbols.d.ts.map +1 -0
- package/dist/currency/crypto-symbols.js +23 -0
- package/dist/currency/crypto-symbols.js.map +1 -0
- package/dist/currency/download-crypto-list.d.ts +10 -0
- package/dist/currency/download-crypto-list.d.ts.map +1 -0
- package/dist/currency/download-crypto-list.js +69 -0
- package/dist/currency/download-crypto-list.js.map +1 -0
- package/dist/currency/index.d.ts +84 -0
- package/dist/currency/index.d.ts.map +1 -0
- package/dist/currency/index.js +230 -0
- package/dist/currency/index.js.map +1 -0
- package/dist/dir.d.ts +40 -0
- package/dist/dir.d.ts.map +1 -0
- package/dist/dir.js +108 -0
- package/dist/dir.js.map +1 -0
- package/dist/file.d.ts +53 -0
- package/dist/file.d.ts.map +1 -0
- package/dist/file.js +211 -0
- package/dist/file.js.map +1 -0
- package/dist/format.d.ts +40 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +83 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/kev.d.ts +149 -0
- package/dist/kev.d.ts.map +1 -0
- package/dist/kev.js +761 -0
- package/dist/kev.js.map +1 -0
- package/dist/log.d.ts +91 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +300 -0
- package/dist/log.js.map +1 -0
- package/dist/logger.d.ts +91 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +269 -0
- package/dist/logger.js.map +1 -0
- package/dist/offensive.d.ts +73 -0
- package/dist/offensive.d.ts.map +1 -0
- package/dist/offensive.js +103 -0
- package/dist/offensive.js.map +1 -0
- package/dist/path.d.ts +67 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +107 -0
- package/dist/path.js.map +1 -0
- package/dist/platform/dir.d.ts +40 -0
- package/dist/platform/dir.d.ts.map +1 -0
- package/dist/platform/dir.js +108 -0
- package/dist/platform/dir.js.map +1 -0
- package/dist/platform/file.d.ts +53 -0
- package/dist/platform/file.d.ts.map +1 -0
- package/dist/platform/file.js +211 -0
- package/dist/platform/file.js.map +1 -0
- package/dist/platform/kev.d.ts +149 -0
- package/dist/platform/kev.d.ts.map +1 -0
- package/dist/platform/kev.js +762 -0
- package/dist/platform/kev.js.map +1 -0
- package/dist/platform/path.d.ts +67 -0
- package/dist/platform/path.d.ts.map +1 -0
- package/dist/platform/path.js +108 -0
- package/dist/platform/path.js.map +1 -0
- package/dist/platform/project.d.ts +35 -0
- package/dist/platform/project.d.ts.map +1 -0
- package/dist/platform/project.js +155 -0
- package/dist/platform/project.js.map +1 -0
- package/dist/project.d.ts +35 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +154 -0
- package/dist/project.js.map +1 -0
- package/dist/runtime.d.ts +65 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +193 -0
- package/dist/runtime.js.map +1 -0
- package/dist/universal/currency/crypto-symbols-data.d.ts +10 -0
- package/dist/universal/currency/crypto-symbols-data.d.ts.map +1 -0
- package/dist/universal/currency/crypto-symbols-data.js +13765 -0
- package/dist/universal/currency/crypto-symbols-data.js.map +1 -0
- package/dist/universal/currency/crypto-symbols.d.ts +20 -0
- package/dist/universal/currency/crypto-symbols.d.ts.map +1 -0
- package/dist/universal/currency/crypto-symbols.js +23 -0
- package/dist/universal/currency/crypto-symbols.js.map +1 -0
- package/dist/universal/currency/download-crypto-list.d.ts +10 -0
- package/dist/universal/currency/download-crypto-list.d.ts.map +1 -0
- package/dist/universal/currency/download-crypto-list.js +69 -0
- package/dist/universal/currency/download-crypto-list.js.map +1 -0
- package/dist/universal/currency/index.d.ts +90 -0
- package/dist/universal/currency/index.d.ts.map +1 -0
- package/dist/universal/currency/index.js +276 -0
- package/dist/universal/currency/index.js.map +1 -0
- package/dist/universal/format.d.ts +40 -0
- package/dist/universal/format.d.ts.map +1 -0
- package/dist/universal/format.js +83 -0
- package/dist/universal/format.js.map +1 -0
- package/dist/universal/log.d.ts +91 -0
- package/dist/universal/log.d.ts.map +1 -0
- package/dist/universal/log.js +309 -0
- package/dist/universal/log.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KEV - A Redis-style KV store for environment variables
|
|
3
|
+
*
|
|
4
|
+
* DEFAULT USAGE (no namespaces needed!):
|
|
5
|
+
* apiKey = KEV.mustGet("API_KEY") // Panics if not found (required config)
|
|
6
|
+
* apiKey = KEV.get("API_KEY") // Returns "" if not found
|
|
7
|
+
* apiKey = KEV.get("API_KEY", "dev") // Returns "dev" if not found
|
|
8
|
+
* port = KEV.int("PORT", 8080) // With type conversion
|
|
9
|
+
* KEV.set("DEBUG", "true") // Sets in memory (fast)
|
|
10
|
+
*
|
|
11
|
+
* KEV.get("DATABASE_URL") // memory → process.env → .env → cache result
|
|
12
|
+
* KEV.get("DATABASE_URL") // memory (cached!) ✓
|
|
13
|
+
*
|
|
14
|
+
* CUSTOMIZE THE SEARCH ORDER:
|
|
15
|
+
* KEV.source.remove("os") // Ignore OS env (perfect for tests!)
|
|
16
|
+
* KEV.source.add(".env.local") // Add more fallbacks
|
|
17
|
+
* KEV.source.set(".env.test") // Or replace entirely
|
|
18
|
+
*
|
|
19
|
+
* REDIS-STYLE NAMESPACING (when you need control):
|
|
20
|
+
* KEV.get("os:PATH") // ONLY from OS, no fallback
|
|
21
|
+
* KEV.get(".env:API_KEY") // ONLY from .env file
|
|
22
|
+
* KEV.set("os:DEBUG", "true") // Write directly to OS
|
|
23
|
+
* KEV.set(".env:API_KEY", "secret") // Update .env file
|
|
24
|
+
*
|
|
25
|
+
* // Pattern matching
|
|
26
|
+
* KEV.keys("API_*") // Find all API_ keys
|
|
27
|
+
* KEV.all("os:*") // Get all OS vars
|
|
28
|
+
* KEV.clear("TEMP_*") // Clean up temp vars
|
|
29
|
+
*
|
|
30
|
+
* SOURCE TRACKING & OBSERVABILITY:
|
|
31
|
+
* const [value, source] = KEV.getWithSource("API_KEY") // Returns value + where it came from
|
|
32
|
+
* source = KEV.sourceOf("API_KEY") // "/path/to/project/.env"
|
|
33
|
+
* KEV.debug = true // Shows lookup chain
|
|
34
|
+
* KEV.export("backup.env") // Includes # from: comments
|
|
35
|
+
*/
|
|
36
|
+
import { runtime } from '../runtime.js';
|
|
37
|
+
import { panic } from '../offensive.js';
|
|
38
|
+
import { file } from './file.js';
|
|
39
|
+
import { path } from './path.js';
|
|
40
|
+
import { findProjectRoot, findMonorepoRoot } from './project.js';
|
|
41
|
+
class SourceOps {
|
|
42
|
+
kev;
|
|
43
|
+
constructor(kev) {
|
|
44
|
+
this.kev = kev;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Replace all sources
|
|
48
|
+
*/
|
|
49
|
+
set(...sources) {
|
|
50
|
+
this.kev.sources = sources;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Add sources to the search list
|
|
54
|
+
*/
|
|
55
|
+
add(...sources) {
|
|
56
|
+
this.kev.sources.push(...sources);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Remove specific sources
|
|
60
|
+
*/
|
|
61
|
+
remove(...sources) {
|
|
62
|
+
this.kev.sources = this.kev.sources.filter(s => !sources.includes(s));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* List current sources
|
|
66
|
+
*/
|
|
67
|
+
list() {
|
|
68
|
+
return [...this.kev.sources];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clear all sources
|
|
72
|
+
*/
|
|
73
|
+
clear() {
|
|
74
|
+
this.kev.sources = [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export class KevOps {
|
|
78
|
+
memory = new Map();
|
|
79
|
+
sources = ['os', '.env'];
|
|
80
|
+
source;
|
|
81
|
+
debug = false;
|
|
82
|
+
constructor() {
|
|
83
|
+
this.source = new SourceOps(this);
|
|
84
|
+
this.initializeSmartDefaults();
|
|
85
|
+
}
|
|
86
|
+
initializeSmartDefaults() {
|
|
87
|
+
// Check for monorepo root first (turborepo)
|
|
88
|
+
const monorepoRoot = findMonorepoRoot();
|
|
89
|
+
if (monorepoRoot) {
|
|
90
|
+
const monorepoEnv = path.join(monorepoRoot, '.env');
|
|
91
|
+
this.sources.push(monorepoEnv);
|
|
92
|
+
if (this.debug) {
|
|
93
|
+
console.log('KEV: Auto-discovered monorepo root:', monorepoRoot);
|
|
94
|
+
console.log('KEV: Added monorepo .env to sources:', monorepoEnv);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Then check for project root
|
|
98
|
+
const projectRoot = findProjectRoot();
|
|
99
|
+
if (projectRoot) {
|
|
100
|
+
const projectEnv = path.join(projectRoot, '.env');
|
|
101
|
+
// Only add if it's different from monorepo env
|
|
102
|
+
if (!monorepoRoot || projectEnv !== path.join(monorepoRoot, '.env')) {
|
|
103
|
+
this.sources.push(projectEnv);
|
|
104
|
+
if (this.debug) {
|
|
105
|
+
console.log('KEV: Auto-discovered project root:', projectRoot);
|
|
106
|
+
console.log('KEV: Added project .env to sources:', projectEnv);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (this.debug) {
|
|
111
|
+
if (!monorepoRoot && !projectRoot) {
|
|
112
|
+
console.log('KEV: No project or monorepo root found, using standard sources:', this.sources);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
console.log('KEV: Default sources:', this.sources);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
parseKey(key) {
|
|
120
|
+
if (key.startsWith(':')) {
|
|
121
|
+
panic('invalid key format - starts with colon:', key);
|
|
122
|
+
}
|
|
123
|
+
if (key.includes('::')) {
|
|
124
|
+
panic('invalid key format - double colon:', key);
|
|
125
|
+
}
|
|
126
|
+
const parts = key.split(':', 2);
|
|
127
|
+
if (parts.length === 2) {
|
|
128
|
+
if (parts[0] === '' || parts[1] === '') {
|
|
129
|
+
panic('invalid key format - empty namespace or key:', key);
|
|
130
|
+
}
|
|
131
|
+
return [parts[0], parts[1]];
|
|
132
|
+
}
|
|
133
|
+
return ['', key];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get environment variable with optional default.
|
|
137
|
+
*/
|
|
138
|
+
get(key, defaultValue) {
|
|
139
|
+
const [namespace, realKey] = this.parseKey(key);
|
|
140
|
+
const debug = this.debug && key !== 'LOG_LEVEL';
|
|
141
|
+
if (debug) {
|
|
142
|
+
console.log(`KEV: Looking for ${key}`);
|
|
143
|
+
}
|
|
144
|
+
// Namespaced - direct access
|
|
145
|
+
if (namespace) {
|
|
146
|
+
const val = this.getFromNamespace(namespace, realKey);
|
|
147
|
+
if (val !== '') {
|
|
148
|
+
if (debug) {
|
|
149
|
+
console.log(` ✓ ${namespace}: found ${val}`);
|
|
150
|
+
}
|
|
151
|
+
return val;
|
|
152
|
+
}
|
|
153
|
+
if (debug) {
|
|
154
|
+
console.log(` ✗ ${namespace}: not found`);
|
|
155
|
+
}
|
|
156
|
+
if (defaultValue !== undefined) {
|
|
157
|
+
if (debug) {
|
|
158
|
+
console.log(` → using default: ${defaultValue}`);
|
|
159
|
+
}
|
|
160
|
+
return defaultValue;
|
|
161
|
+
}
|
|
162
|
+
return '';
|
|
163
|
+
}
|
|
164
|
+
// Unnamespaced - check memory first
|
|
165
|
+
const entry = this.memory.get(realKey);
|
|
166
|
+
if (entry) {
|
|
167
|
+
if (debug) {
|
|
168
|
+
console.log(` ✓ memory: ${entry.value} (from ${entry.source})`);
|
|
169
|
+
}
|
|
170
|
+
return entry.value;
|
|
171
|
+
}
|
|
172
|
+
if (debug) {
|
|
173
|
+
console.log(' ✗ memory: not found');
|
|
174
|
+
}
|
|
175
|
+
// Search through sources
|
|
176
|
+
for (const source of this.sources) {
|
|
177
|
+
const val = this.getFromNamespace(source, realKey);
|
|
178
|
+
if (val !== '') {
|
|
179
|
+
if (debug) {
|
|
180
|
+
console.log(` ✓ ${source}: found ${val} (caching)`);
|
|
181
|
+
}
|
|
182
|
+
// Cache in memory for next time with source info
|
|
183
|
+
const absoluteSource = source !== 'os' && source !== 'default' && source !== 'set'
|
|
184
|
+
? path.absolute(source)
|
|
185
|
+
: source;
|
|
186
|
+
this.memory.set(realKey, { value: val, source: absoluteSource });
|
|
187
|
+
return val;
|
|
188
|
+
}
|
|
189
|
+
if (debug) {
|
|
190
|
+
console.log(` ✗ ${source}: not found`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Use default and cache it
|
|
194
|
+
if (defaultValue !== undefined) {
|
|
195
|
+
if (debug) {
|
|
196
|
+
console.log(` → using default: ${defaultValue} (caching)`);
|
|
197
|
+
}
|
|
198
|
+
this.memory.set(realKey, { value: defaultValue, source: 'default' });
|
|
199
|
+
return defaultValue;
|
|
200
|
+
}
|
|
201
|
+
if (debug) {
|
|
202
|
+
console.log(' → not found, returning empty');
|
|
203
|
+
}
|
|
204
|
+
return '';
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get environment variable or panic if not found
|
|
208
|
+
*/
|
|
209
|
+
mustGet(key) {
|
|
210
|
+
const val = this.get(key);
|
|
211
|
+
if (val === '') {
|
|
212
|
+
panic('required key not found:', key);
|
|
213
|
+
}
|
|
214
|
+
return val;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get where a cached key came from
|
|
218
|
+
*/
|
|
219
|
+
sourceOf(key) {
|
|
220
|
+
const entry = this.memory.get(key);
|
|
221
|
+
return entry ? entry.source : '';
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Get both value and its source
|
|
225
|
+
*/
|
|
226
|
+
getWithSource(key, defaultValue) {
|
|
227
|
+
const value = this.get(key, defaultValue);
|
|
228
|
+
if (value !== '') {
|
|
229
|
+
let source = this.sourceOf(key);
|
|
230
|
+
// If not in cache but has value, it might be a namespaced get
|
|
231
|
+
if (!source) {
|
|
232
|
+
const [namespace] = this.parseKey(key);
|
|
233
|
+
if (namespace) {
|
|
234
|
+
source = namespace;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return [value, source];
|
|
238
|
+
}
|
|
239
|
+
return ['', ''];
|
|
240
|
+
}
|
|
241
|
+
getFromNamespace(namespace, key) {
|
|
242
|
+
switch (namespace) {
|
|
243
|
+
case 'os':
|
|
244
|
+
return runtime.env(key) || '';
|
|
245
|
+
default:
|
|
246
|
+
// File namespace (.env, .env.local, etc)
|
|
247
|
+
if (namespace.startsWith('.') || namespace.includes('/')) {
|
|
248
|
+
return this.getFromFile(namespace, key);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return '';
|
|
252
|
+
}
|
|
253
|
+
getFromFile(filePath, key) {
|
|
254
|
+
if (!file.exists(filePath)) {
|
|
255
|
+
return '';
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
const content = file.readText(filePath);
|
|
259
|
+
const lines = content.split('\n');
|
|
260
|
+
for (const line of lines) {
|
|
261
|
+
const trimmed = line.trim();
|
|
262
|
+
if (trimmed === '' || trimmed.startsWith('#')) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const parts = trimmed.split('=', 2);
|
|
266
|
+
if (parts.length === 2) {
|
|
267
|
+
const fileKey = parts[0].trim();
|
|
268
|
+
if (fileKey === key) {
|
|
269
|
+
let value = parts[1].trim();
|
|
270
|
+
// Remove quotes if present
|
|
271
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
272
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
273
|
+
value = value.slice(1, -1);
|
|
274
|
+
}
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// File read error
|
|
282
|
+
}
|
|
283
|
+
return '';
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Set environment variable
|
|
287
|
+
*/
|
|
288
|
+
set(key, value) {
|
|
289
|
+
const [namespace, realKey] = this.parseKey(key);
|
|
290
|
+
// Namespaced - direct write
|
|
291
|
+
if (namespace) {
|
|
292
|
+
this.setToNamespace(namespace, realKey, value);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// Unnamespaced - memory only
|
|
296
|
+
this.memory.set(realKey, { value, source: 'set' });
|
|
297
|
+
}
|
|
298
|
+
setToNamespace(namespace, key, value) {
|
|
299
|
+
switch (namespace) {
|
|
300
|
+
case 'os':
|
|
301
|
+
runtime.setEnv(key, value);
|
|
302
|
+
break;
|
|
303
|
+
default:
|
|
304
|
+
// File namespace - update or append to file
|
|
305
|
+
if (namespace.startsWith('.') || namespace.includes('/')) {
|
|
306
|
+
this.setToFile(namespace, key, value);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
setToFile(path, key, value) {
|
|
311
|
+
const lines = [];
|
|
312
|
+
let found = false;
|
|
313
|
+
if (file.exists(path)) {
|
|
314
|
+
const content = file.readText(path);
|
|
315
|
+
const existingLines = content.split('\n');
|
|
316
|
+
for (const line of existingLines) {
|
|
317
|
+
const trimmed = line.trim();
|
|
318
|
+
// Keep empty lines and comments
|
|
319
|
+
if (trimmed === '' || trimmed.startsWith('#')) {
|
|
320
|
+
lines.push(line);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
// Check if this is the key we're updating
|
|
324
|
+
const parts = trimmed.split('=', 2);
|
|
325
|
+
if (parts.length >= 1) {
|
|
326
|
+
const fileKey = parts[0].trim();
|
|
327
|
+
if (fileKey === key) {
|
|
328
|
+
// Update existing key
|
|
329
|
+
const quotedValue = value.includes(' ') || value.includes('\t') || value.includes('\n')
|
|
330
|
+
? `"${value}"`
|
|
331
|
+
: value;
|
|
332
|
+
lines.push(`${key}=${quotedValue}`);
|
|
333
|
+
found = true;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
lines.push(line);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
lines.push(line);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// If key wasn't found, append it
|
|
345
|
+
if (!found) {
|
|
346
|
+
const quotedValue = value.includes(' ') || value.includes('\t') || value.includes('\n')
|
|
347
|
+
? `"${value}"`
|
|
348
|
+
: value;
|
|
349
|
+
lines.push(`${key}=${quotedValue}`);
|
|
350
|
+
}
|
|
351
|
+
// Write back
|
|
352
|
+
file.write(path, lines.join('\n'));
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Check if key exists
|
|
356
|
+
*/
|
|
357
|
+
has(key) {
|
|
358
|
+
const [namespace, realKey] = this.parseKey(key);
|
|
359
|
+
// Namespaced - check directly
|
|
360
|
+
if (namespace) {
|
|
361
|
+
return this.hasInNamespace(namespace, realKey);
|
|
362
|
+
}
|
|
363
|
+
// Unnamespaced - check memory then sources
|
|
364
|
+
if (this.memory.has(realKey)) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
// Check sources
|
|
368
|
+
for (const source of this.sources) {
|
|
369
|
+
if (this.hasInNamespace(source, realKey)) {
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
hasInNamespace(namespace, key) {
|
|
376
|
+
switch (namespace) {
|
|
377
|
+
case 'os':
|
|
378
|
+
return runtime.hasEnv(key);
|
|
379
|
+
default:
|
|
380
|
+
// File namespace
|
|
381
|
+
if (namespace.startsWith('.') || namespace.includes('/')) {
|
|
382
|
+
return this.getFromFile(namespace, key) !== '';
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Get all keys matching pattern
|
|
389
|
+
*/
|
|
390
|
+
keys(pattern = '*') {
|
|
391
|
+
const seen = new Set();
|
|
392
|
+
const result = [];
|
|
393
|
+
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
394
|
+
for (const pat of patterns) {
|
|
395
|
+
const [namespace, keyPattern] = this.parseKey(pat);
|
|
396
|
+
if (namespace) {
|
|
397
|
+
// Namespaced pattern
|
|
398
|
+
const nsKeys = this.keysFromNamespace(namespace, keyPattern);
|
|
399
|
+
for (const key of nsKeys) {
|
|
400
|
+
const fullKey = `${namespace}:${key}`;
|
|
401
|
+
if (!seen.has(fullKey)) {
|
|
402
|
+
result.push(fullKey);
|
|
403
|
+
seen.add(fullKey);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
// Unnamespaced - get from memory and sources
|
|
409
|
+
for (const [key] of this.memory) {
|
|
410
|
+
if (this.matchPattern(key, keyPattern) && !seen.has(key)) {
|
|
411
|
+
result.push(key);
|
|
412
|
+
seen.add(key);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// Also check sources
|
|
416
|
+
for (const source of this.sources) {
|
|
417
|
+
const nsKeys = this.keysFromNamespace(source, keyPattern);
|
|
418
|
+
for (const key of nsKeys) {
|
|
419
|
+
if (!seen.has(key)) {
|
|
420
|
+
result.push(key);
|
|
421
|
+
seen.add(key);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
429
|
+
keysFromNamespace(namespace, pattern) {
|
|
430
|
+
const data = this.getNamespaceData(namespace, pattern, true);
|
|
431
|
+
return Object.keys(data);
|
|
432
|
+
}
|
|
433
|
+
getNamespaceData(namespace, pattern, keysOnly) {
|
|
434
|
+
const result = {};
|
|
435
|
+
switch (namespace) {
|
|
436
|
+
case 'os':
|
|
437
|
+
for (const [key, value] of Object.entries(runtime.allEnv())) {
|
|
438
|
+
if (this.matchPattern(key, pattern)) {
|
|
439
|
+
result[key] = keysOnly ? '' : value || '';
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
default:
|
|
444
|
+
// File namespace
|
|
445
|
+
if (namespace.startsWith('.') || namespace.includes('/')) {
|
|
446
|
+
this.parseEnvFile(namespace, pattern, result, keysOnly);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return result;
|
|
450
|
+
}
|
|
451
|
+
parseEnvFile(path, pattern, result, keysOnly) {
|
|
452
|
+
if (!file.exists(path)) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
const content = file.readText(path);
|
|
457
|
+
const lines = content.split('\n');
|
|
458
|
+
for (const line of lines) {
|
|
459
|
+
const trimmed = line.trim();
|
|
460
|
+
if (trimmed === '' || trimmed.startsWith('#')) {
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const parts = trimmed.split('=', 2);
|
|
464
|
+
if (parts.length >= 1) {
|
|
465
|
+
const key = parts[0].trim();
|
|
466
|
+
if (this.matchPattern(key, pattern)) {
|
|
467
|
+
if (keysOnly) {
|
|
468
|
+
result[key] = '';
|
|
469
|
+
}
|
|
470
|
+
else if (parts.length === 2) {
|
|
471
|
+
let value = parts[1].trim();
|
|
472
|
+
// Remove quotes
|
|
473
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
474
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
475
|
+
value = value.slice(1, -1);
|
|
476
|
+
}
|
|
477
|
+
result[key] = value;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
// File read error
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
matchPattern(key, pattern) {
|
|
488
|
+
if (pattern === '*')
|
|
489
|
+
return true;
|
|
490
|
+
if (pattern.endsWith('*')) {
|
|
491
|
+
const prefix = pattern.slice(0, -1);
|
|
492
|
+
return key.startsWith(prefix);
|
|
493
|
+
}
|
|
494
|
+
if (pattern.startsWith('*')) {
|
|
495
|
+
const suffix = pattern.slice(1);
|
|
496
|
+
return key.endsWith(suffix);
|
|
497
|
+
}
|
|
498
|
+
if (pattern.includes('*')) {
|
|
499
|
+
const parts = pattern.split('*');
|
|
500
|
+
if (parts.length === 2) {
|
|
501
|
+
return key.startsWith(parts[0]) && key.endsWith(parts[1]);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return key === pattern;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Get all variables matching patterns
|
|
508
|
+
*/
|
|
509
|
+
all(pattern) {
|
|
510
|
+
const result = {};
|
|
511
|
+
const patterns = pattern ? (Array.isArray(pattern) ? pattern : [pattern]) : [];
|
|
512
|
+
if (patterns.length === 0) {
|
|
513
|
+
// Default - return memory only with source info
|
|
514
|
+
if (this.memory.size > 0) {
|
|
515
|
+
const memCopy = {};
|
|
516
|
+
for (const [key, entry] of this.memory) {
|
|
517
|
+
memCopy[key] = `${entry.value} [from: ${entry.source}]`;
|
|
518
|
+
}
|
|
519
|
+
result.memory = memCopy;
|
|
520
|
+
}
|
|
521
|
+
return result;
|
|
522
|
+
}
|
|
523
|
+
// Check if any pattern has namespace
|
|
524
|
+
const hasNamespace = patterns.some(p => p.includes(':'));
|
|
525
|
+
if (!hasNamespace) {
|
|
526
|
+
// No namespaces - get from memory and all sources
|
|
527
|
+
const memMatches = {};
|
|
528
|
+
for (const [key, entry] of this.memory) {
|
|
529
|
+
for (const pat of patterns) {
|
|
530
|
+
if (this.matchPattern(key, pat)) {
|
|
531
|
+
memMatches[key] = `${entry.value} [from: ${entry.source}]`;
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (Object.keys(memMatches).length > 0) {
|
|
537
|
+
result.memory = memMatches;
|
|
538
|
+
}
|
|
539
|
+
// Check all sources
|
|
540
|
+
for (const source of this.sources) {
|
|
541
|
+
const sourceMatches = {};
|
|
542
|
+
const sourceVars = this.getAllFromNamespace(source, '*');
|
|
543
|
+
for (const [key, val] of Object.entries(sourceVars)) {
|
|
544
|
+
for (const pat of patterns) {
|
|
545
|
+
if (this.matchPattern(key, pat)) {
|
|
546
|
+
sourceMatches[key] = val;
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (Object.keys(sourceMatches).length > 0) {
|
|
552
|
+
result[source] = sourceMatches;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
// Special case for *:*
|
|
558
|
+
if (patterns.length === 1 && patterns[0] === '*:*') {
|
|
559
|
+
// Add memory
|
|
560
|
+
if (this.memory.size > 0) {
|
|
561
|
+
const memCopy = {};
|
|
562
|
+
for (const [key, entry] of this.memory) {
|
|
563
|
+
memCopy[key] = `${entry.value} [from: ${entry.source}]`;
|
|
564
|
+
}
|
|
565
|
+
result.memory = memCopy;
|
|
566
|
+
}
|
|
567
|
+
// Add all sources
|
|
568
|
+
for (const source of this.sources) {
|
|
569
|
+
const sourceVars = this.getAllFromNamespace(source, '*');
|
|
570
|
+
if (Object.keys(sourceVars).length > 0) {
|
|
571
|
+
result[source] = sourceVars;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
// Process specific patterns
|
|
577
|
+
for (const pat of patterns) {
|
|
578
|
+
const [namespace, keyPattern] = this.parseKey(pat);
|
|
579
|
+
if (namespace) {
|
|
580
|
+
const nsVars = this.getAllFromNamespace(namespace, keyPattern);
|
|
581
|
+
if (Object.keys(nsVars).length > 0) {
|
|
582
|
+
if (!result[namespace]) {
|
|
583
|
+
result[namespace] = {};
|
|
584
|
+
}
|
|
585
|
+
Object.assign(result[namespace], nsVars);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
getAllFromNamespace(namespace, pattern) {
|
|
593
|
+
return this.getNamespaceData(namespace, pattern, false);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Clear variables from memory
|
|
597
|
+
*/
|
|
598
|
+
clear(...patterns) {
|
|
599
|
+
// Only allow memory clearing for safety
|
|
600
|
+
for (const pattern of patterns) {
|
|
601
|
+
if (pattern.includes(':')) {
|
|
602
|
+
panic('Clear() with namespace is dangerous! Use clearUnsafe() if you really need this.');
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (patterns.length === 0) {
|
|
606
|
+
// Clear all memory
|
|
607
|
+
this.memory.clear();
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
// Clear patterns from memory only
|
|
611
|
+
for (const pattern of patterns) {
|
|
612
|
+
for (const key of this.memory.keys()) {
|
|
613
|
+
if (this.matchPattern(key, pattern)) {
|
|
614
|
+
this.memory.delete(key);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Clear from namespaces (dangerous!)
|
|
621
|
+
*/
|
|
622
|
+
clearUnsafe(...patterns) {
|
|
623
|
+
for (const pattern of patterns) {
|
|
624
|
+
const [namespace, keyPattern] = this.parseKey(pattern);
|
|
625
|
+
if (!namespace) {
|
|
626
|
+
// No namespace - just use regular clear
|
|
627
|
+
this.clear(pattern);
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
switch (namespace) {
|
|
631
|
+
case 'os':
|
|
632
|
+
if (keyPattern === '*') {
|
|
633
|
+
panic('clearUnsafe("os:*") would destroy system! This is never allowed.');
|
|
634
|
+
}
|
|
635
|
+
const keys = this.keysFromNamespace('os', keyPattern);
|
|
636
|
+
for (const key of keys) {
|
|
637
|
+
runtime.deleteEnv(key);
|
|
638
|
+
}
|
|
639
|
+
break;
|
|
640
|
+
default:
|
|
641
|
+
panic('Clearing from files not yet implemented');
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Remove specific keys
|
|
647
|
+
*/
|
|
648
|
+
unset(...keys) {
|
|
649
|
+
for (const key of keys) {
|
|
650
|
+
const [namespace, realKey] = this.parseKey(key);
|
|
651
|
+
if (namespace) {
|
|
652
|
+
// Namespaced unset
|
|
653
|
+
switch (namespace) {
|
|
654
|
+
case 'os':
|
|
655
|
+
runtime.deleteEnv(realKey);
|
|
656
|
+
break;
|
|
657
|
+
default:
|
|
658
|
+
panic('Unsetting from files not yet implemented');
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
// Unnamespaced - remove from memory
|
|
663
|
+
this.memory.delete(realKey);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Get as integer with default
|
|
669
|
+
*/
|
|
670
|
+
int(key, defaultValue) {
|
|
671
|
+
const val = this.get(key);
|
|
672
|
+
if (val === '') {
|
|
673
|
+
return defaultValue;
|
|
674
|
+
}
|
|
675
|
+
const i = parseInt(val, 10);
|
|
676
|
+
if (isNaN(i)) {
|
|
677
|
+
panic(`invalid int value for ${key}: ${val}`);
|
|
678
|
+
}
|
|
679
|
+
return i;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Get as boolean with default
|
|
683
|
+
*/
|
|
684
|
+
bool(key, defaultValue) {
|
|
685
|
+
const val = this.get(key).toLowerCase();
|
|
686
|
+
if (val === '') {
|
|
687
|
+
return defaultValue;
|
|
688
|
+
}
|
|
689
|
+
switch (val) {
|
|
690
|
+
case 'true':
|
|
691
|
+
case '1':
|
|
692
|
+
case 'yes':
|
|
693
|
+
case 'on':
|
|
694
|
+
return true;
|
|
695
|
+
case 'false':
|
|
696
|
+
case '0':
|
|
697
|
+
case 'no':
|
|
698
|
+
case 'off':
|
|
699
|
+
return false;
|
|
700
|
+
default:
|
|
701
|
+
panic(`invalid bool value for ${key}: ${val}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Get as float with default
|
|
706
|
+
*/
|
|
707
|
+
float(key, defaultValue) {
|
|
708
|
+
const val = this.get(key);
|
|
709
|
+
if (val === '') {
|
|
710
|
+
return defaultValue;
|
|
711
|
+
}
|
|
712
|
+
const f = parseFloat(val);
|
|
713
|
+
if (isNaN(f)) {
|
|
714
|
+
panic(`invalid float value for ${key}: ${val}`);
|
|
715
|
+
}
|
|
716
|
+
return f;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Export all memory variables to a file
|
|
720
|
+
*/
|
|
721
|
+
export(path) {
|
|
722
|
+
const lines = [];
|
|
723
|
+
for (const [key, entry] of this.memory) {
|
|
724
|
+
let val = entry.value;
|
|
725
|
+
// Escape values with spaces or special chars
|
|
726
|
+
if (val.includes(' ') || val.includes('\t') || val.includes('\n')) {
|
|
727
|
+
val = `"${val}"`;
|
|
728
|
+
}
|
|
729
|
+
// Add comment with source info
|
|
730
|
+
lines.push(`${key}=${val} # from: ${entry.source}`);
|
|
731
|
+
}
|
|
732
|
+
file.write(path, lines.join('\n'));
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Print all environment variables (masks sensitive keys)
|
|
736
|
+
*/
|
|
737
|
+
dump() {
|
|
738
|
+
const all = this.all();
|
|
739
|
+
for (const [namespace, vars] of Object.entries(all)) {
|
|
740
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
741
|
+
let maskedVal = val;
|
|
742
|
+
// Mask sensitive values
|
|
743
|
+
const lower = key.toLowerCase();
|
|
744
|
+
if (lower.includes('key') ||
|
|
745
|
+
lower.includes('secret') ||
|
|
746
|
+
lower.includes('password') ||
|
|
747
|
+
lower.includes('token')) {
|
|
748
|
+
if (val.length > 4) {
|
|
749
|
+
maskedVal = val.slice(0, 4) + '****';
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
maskedVal = '****';
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
console.log(`${namespace}:${key}=${maskedVal}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
// Create singleton instance
|
|
761
|
+
export const kev = new KevOps();
|
|
762
|
+
//# sourceMappingURL=kev.js.map
|