better-dx 0.2.0 → 0.2.2
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/config.d.ts +1 -0
- package/dist/index.js +3228 -13
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -18,18 +18,25 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = import.meta.require;
|
|
19
19
|
|
|
20
20
|
// node_modules/bunfig/dist/index.js
|
|
21
|
+
import { existsSync, statSync } from "fs";
|
|
22
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
23
|
+
import { homedir as homedir2 } from "os";
|
|
24
|
+
import { dirname as dirname3, resolve as resolve7 } from "path";
|
|
25
|
+
import process12 from "process";
|
|
26
|
+
import { join as join3, relative as relative2, resolve as resolve4 } from "path";
|
|
27
|
+
import process7 from "process";
|
|
21
28
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
22
29
|
import { homedir } from "os";
|
|
23
30
|
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
24
31
|
import process6 from "process";
|
|
25
32
|
import { join, relative, resolve as resolve2 } from "path";
|
|
26
33
|
import process2 from "process";
|
|
27
|
-
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
|
|
34
|
+
import { existsSync as existsSync2, mkdirSync, readdirSync, writeFileSync } from "fs";
|
|
28
35
|
import { dirname, resolve } from "path";
|
|
29
36
|
import process from "process";
|
|
30
37
|
import { Buffer } from "buffer";
|
|
31
38
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
32
|
-
import { closeSync, createReadStream, createWriteStream, existsSync as
|
|
39
|
+
import { closeSync, createReadStream, createWriteStream, existsSync as existsSync22, fsyncSync, openSync, writeFileSync as writeFileSync2 } from "fs";
|
|
33
40
|
import { access, constants, mkdir, readdir, rename, stat, unlink, writeFile } from "fs/promises";
|
|
34
41
|
import { join as join2 } from "path";
|
|
35
42
|
import process5 from "process";
|
|
@@ -37,6 +44,260 @@ import { pipeline } from "stream/promises";
|
|
|
37
44
|
import { createGzip } from "zlib";
|
|
38
45
|
import process4 from "process";
|
|
39
46
|
import process3 from "process";
|
|
47
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
48
|
+
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
|
|
49
|
+
import { closeSync as closeSync2, createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync4, fsyncSync as fsyncSync2, openSync as openSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
50
|
+
import { access as access2, constants as constants2, mkdir as mkdir2, readdir as readdir2, rename as rename2, stat as stat2, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
|
|
51
|
+
import { isAbsolute, join as join5, resolve as resolve6 } from "path";
|
|
52
|
+
import process11 from "process";
|
|
53
|
+
import { pipeline as pipeline2 } from "stream/promises";
|
|
54
|
+
import { createGzip as createGzip2 } from "zlib";
|
|
55
|
+
import process10 from "process";
|
|
56
|
+
import process9 from "process";
|
|
57
|
+
import process8 from "process";
|
|
58
|
+
import { existsSync as existsSync5 } from "fs";
|
|
59
|
+
import { resolve as resolve5 } from "path";
|
|
60
|
+
import { existsSync as existsSync7 } from "fs";
|
|
61
|
+
class ConfigCache {
|
|
62
|
+
cache = new Map;
|
|
63
|
+
totalHits = 0;
|
|
64
|
+
totalMisses = 0;
|
|
65
|
+
options;
|
|
66
|
+
constructor(options = {}) {
|
|
67
|
+
this.options = {
|
|
68
|
+
enabled: true,
|
|
69
|
+
ttl: 5 * 60 * 1000,
|
|
70
|
+
maxSize: 100,
|
|
71
|
+
keyPrefix: "bunfig:",
|
|
72
|
+
...options
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
generateKey(configName, configPath) {
|
|
76
|
+
const pathKey = configPath ? `:${configPath}` : "";
|
|
77
|
+
return `${this.options.keyPrefix}${configName}${pathKey}`;
|
|
78
|
+
}
|
|
79
|
+
isExpired(entry) {
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
const age = now - entry.timestamp.getTime();
|
|
82
|
+
const expired = age > entry.ttl;
|
|
83
|
+
return expired;
|
|
84
|
+
}
|
|
85
|
+
estimateSize(value) {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.stringify(value).length;
|
|
88
|
+
} catch {
|
|
89
|
+
return 1000;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
evictIfNeeded() {
|
|
93
|
+
if (this.cache.size <= this.options.maxSize) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const entries = Array.from(this.cache.entries()).sort(([, a], [, b]) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
97
|
+
const toRemove = entries.length - this.options.maxSize + 1;
|
|
98
|
+
for (let i = 0;i < toRemove; i++) {
|
|
99
|
+
this.cache.delete(entries[i][0]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
set(configName, value, configPath, customTtl) {
|
|
103
|
+
if (!this.options.enabled) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const key = this.generateKey(configName, configPath);
|
|
107
|
+
const ttl = customTtl ?? this.options.ttl;
|
|
108
|
+
const size = this.estimateSize(value);
|
|
109
|
+
this.cache.set(key, {
|
|
110
|
+
value,
|
|
111
|
+
timestamp: new Date,
|
|
112
|
+
ttl,
|
|
113
|
+
hits: 0,
|
|
114
|
+
size
|
|
115
|
+
});
|
|
116
|
+
this.evictIfNeeded();
|
|
117
|
+
}
|
|
118
|
+
get(configName, configPath) {
|
|
119
|
+
if (!this.options.enabled) {
|
|
120
|
+
this.totalMisses++;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const key = this.generateKey(configName, configPath);
|
|
124
|
+
const entry = this.cache.get(key);
|
|
125
|
+
if (!entry) {
|
|
126
|
+
this.totalMisses++;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (this.isExpired(entry)) {
|
|
130
|
+
this.cache.delete(key);
|
|
131
|
+
this.totalMisses++;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
entry.hits++;
|
|
135
|
+
this.totalHits++;
|
|
136
|
+
return entry.value;
|
|
137
|
+
}
|
|
138
|
+
isFileModified(configPath, cachedTimestamp) {
|
|
139
|
+
try {
|
|
140
|
+
if (!existsSync(configPath)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
const stats = statSync(configPath);
|
|
144
|
+
return stats.mtime > cachedTimestamp;
|
|
145
|
+
} catch {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
getWithFileCheck(configName, configPath) {
|
|
150
|
+
const cached = this.get(configName, configPath);
|
|
151
|
+
if (!cached) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (this.isFileModified(configPath, cached.fileTimestamp)) {
|
|
155
|
+
this.delete(configName, configPath);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
return cached.value;
|
|
159
|
+
}
|
|
160
|
+
setWithFileCheck(configName, value, configPath, customTtl) {
|
|
161
|
+
try {
|
|
162
|
+
const stats = existsSync(configPath) ? statSync(configPath) : null;
|
|
163
|
+
const fileTimestamp = stats ? stats.mtime : new Date;
|
|
164
|
+
this.set(configName, { value, fileTimestamp }, configPath, customTtl);
|
|
165
|
+
} catch {
|
|
166
|
+
this.set(configName, value, configPath, customTtl);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
delete(configName, configPath) {
|
|
170
|
+
const key = this.generateKey(configName, configPath);
|
|
171
|
+
return this.cache.delete(key);
|
|
172
|
+
}
|
|
173
|
+
clear() {
|
|
174
|
+
this.cache.clear();
|
|
175
|
+
this.totalHits = 0;
|
|
176
|
+
this.totalMisses = 0;
|
|
177
|
+
}
|
|
178
|
+
cleanup() {
|
|
179
|
+
let cleaned = 0;
|
|
180
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
181
|
+
if (this.isExpired(entry)) {
|
|
182
|
+
this.cache.delete(key);
|
|
183
|
+
cleaned++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return cleaned;
|
|
187
|
+
}
|
|
188
|
+
getStats() {
|
|
189
|
+
const entries = Array.from(this.cache.values());
|
|
190
|
+
const totalSize = entries.reduce((sum, entry) => sum + entry.size, 0);
|
|
191
|
+
const timestamps = entries.map((entry) => entry.timestamp).sort();
|
|
192
|
+
return {
|
|
193
|
+
size: totalSize,
|
|
194
|
+
maxSize: this.options.maxSize,
|
|
195
|
+
hitRate: this.totalHits + this.totalMisses > 0 ? this.totalHits / (this.totalHits + this.totalMisses) : 0,
|
|
196
|
+
totalHits: this.totalHits,
|
|
197
|
+
totalMisses: this.totalMisses,
|
|
198
|
+
entries: this.cache.size,
|
|
199
|
+
oldestEntry: timestamps[0],
|
|
200
|
+
newestEntry: timestamps[timestamps.length - 1]
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
export() {
|
|
204
|
+
const data = {};
|
|
205
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
206
|
+
data[key] = {
|
|
207
|
+
value: entry.value,
|
|
208
|
+
timestamp: entry.timestamp.toISOString(),
|
|
209
|
+
ttl: entry.ttl,
|
|
210
|
+
hits: entry.hits,
|
|
211
|
+
size: entry.size
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return data;
|
|
215
|
+
}
|
|
216
|
+
import(data) {
|
|
217
|
+
this.cache.clear();
|
|
218
|
+
for (const [key, entryData] of Object.entries(data)) {
|
|
219
|
+
if (typeof entryData === "object" && entryData !== null) {
|
|
220
|
+
const entry = entryData;
|
|
221
|
+
this.cache.set(key, {
|
|
222
|
+
value: entry.value,
|
|
223
|
+
timestamp: new Date(entry.timestamp),
|
|
224
|
+
ttl: entry.ttl,
|
|
225
|
+
hits: entry.hits,
|
|
226
|
+
size: entry.size
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
class PerformanceMonitor {
|
|
234
|
+
metrics = [];
|
|
235
|
+
maxMetrics = 1000;
|
|
236
|
+
async track(operation, fn, context = {}) {
|
|
237
|
+
const start = performance.now();
|
|
238
|
+
const startTime = new Date;
|
|
239
|
+
try {
|
|
240
|
+
const result = await fn();
|
|
241
|
+
const duration = performance.now() - start;
|
|
242
|
+
this.recordMetric({
|
|
243
|
+
operation,
|
|
244
|
+
duration,
|
|
245
|
+
timestamp: startTime,
|
|
246
|
+
...context
|
|
247
|
+
});
|
|
248
|
+
return result;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const duration = performance.now() - start;
|
|
251
|
+
this.recordMetric({
|
|
252
|
+
operation: `${operation}:error`,
|
|
253
|
+
duration,
|
|
254
|
+
timestamp: startTime,
|
|
255
|
+
...context
|
|
256
|
+
});
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
recordMetric(metric) {
|
|
261
|
+
this.metrics.push(metric);
|
|
262
|
+
if (this.metrics.length > this.maxMetrics) {
|
|
263
|
+
this.metrics = this.metrics.slice(-this.maxMetrics);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
getStats(operation) {
|
|
267
|
+
const filteredMetrics = operation ? this.metrics.filter((m) => m.operation === operation) : this.metrics;
|
|
268
|
+
if (filteredMetrics.length === 0) {
|
|
269
|
+
return {
|
|
270
|
+
count: 0,
|
|
271
|
+
averageDuration: 0,
|
|
272
|
+
minDuration: 0,
|
|
273
|
+
maxDuration: 0,
|
|
274
|
+
totalDuration: 0,
|
|
275
|
+
recentMetrics: []
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const durations = filteredMetrics.map((m) => m.duration);
|
|
279
|
+
const totalDuration = durations.reduce((sum, d) => sum + d, 0);
|
|
280
|
+
return {
|
|
281
|
+
count: filteredMetrics.length,
|
|
282
|
+
averageDuration: totalDuration / filteredMetrics.length,
|
|
283
|
+
minDuration: Math.min(...durations),
|
|
284
|
+
maxDuration: Math.max(...durations),
|
|
285
|
+
totalDuration,
|
|
286
|
+
recentMetrics: filteredMetrics.slice(-10)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
getAllMetrics() {
|
|
290
|
+
return [...this.metrics];
|
|
291
|
+
}
|
|
292
|
+
clearMetrics() {
|
|
293
|
+
this.metrics = [];
|
|
294
|
+
}
|
|
295
|
+
getSlowOperations(threshold) {
|
|
296
|
+
return this.metrics.filter((m) => m.duration > threshold);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
var globalCache = new ConfigCache;
|
|
300
|
+
var globalPerformanceMonitor = new PerformanceMonitor;
|
|
40
301
|
function deepMerge(target, source) {
|
|
41
302
|
if (Array.isArray(source) && Array.isArray(target) && source.length === 2 && target.length === 2 && isObject(source[0]) && "id" in source[0] && source[0].id === 3 && isObject(source[1]) && "id" in source[1] && source[1].id === 4) {
|
|
42
303
|
return source;
|
|
@@ -163,7 +424,7 @@ function isObject(item) {
|
|
|
163
424
|
return Boolean(item && typeof item === "object" && !Array.isArray(item));
|
|
164
425
|
}
|
|
165
426
|
async function tryLoadConfig(configPath, defaultConfig) {
|
|
166
|
-
if (!
|
|
427
|
+
if (!existsSync2(configPath))
|
|
167
428
|
return null;
|
|
168
429
|
try {
|
|
169
430
|
const importedConfig = await import(configPath);
|
|
@@ -203,7 +464,7 @@ async function loadConfig({
|
|
|
203
464
|
}
|
|
204
465
|
try {
|
|
205
466
|
const pkgPath = resolve(baseDir, "package.json");
|
|
206
|
-
if (
|
|
467
|
+
if (existsSync2(pkgPath)) {
|
|
207
468
|
const pkg = await import(pkgPath);
|
|
208
469
|
const pkgConfig = pkg[name];
|
|
209
470
|
if (pkgConfig && typeof pkgConfig === "object" && !Array.isArray(pkgConfig)) {
|
|
@@ -504,7 +765,7 @@ class Logger {
|
|
|
504
765
|
throw new Error("Operation cancelled: Logger was destroyed");
|
|
505
766
|
const dataToWrite = this.validateEncryptionConfig() ? (await this.encrypt(data)).encrypted : Buffer.from(data);
|
|
506
767
|
try {
|
|
507
|
-
if (!
|
|
768
|
+
if (!existsSync22(this.currentLogFile)) {
|
|
508
769
|
await writeFile(this.currentLogFile, "", { mode: 420 });
|
|
509
770
|
}
|
|
510
771
|
fd = openSync(this.currentLogFile, "a", 420);
|
|
@@ -840,7 +1101,7 @@ class Logger {
|
|
|
840
1101
|
}
|
|
841
1102
|
return Promise.resolve();
|
|
842
1103
|
}));
|
|
843
|
-
if (
|
|
1104
|
+
if (existsSync22(this.currentLogFile)) {
|
|
844
1105
|
try {
|
|
845
1106
|
const fd = openSync(this.currentLogFile, "r+");
|
|
846
1107
|
fsyncSync(fd);
|
|
@@ -1160,7 +1421,7 @@ class Logger {
|
|
|
1160
1421
|
createReadStream() {
|
|
1161
1422
|
if (isBrowserProcess())
|
|
1162
1423
|
throw new Error("createReadStream is not supported in browser environments");
|
|
1163
|
-
if (!
|
|
1424
|
+
if (!existsSync22(this.currentLogFile))
|
|
1164
1425
|
throw new Error(`Log file does not exist: ${this.currentLogFile}`);
|
|
1165
1426
|
return createReadStream(this.currentLogFile, { encoding: "utf8" });
|
|
1166
1427
|
}
|
|
@@ -1852,17 +2113,2971 @@ async function loadConfig3({
|
|
|
1852
2113
|
}
|
|
1853
2114
|
var defaultConfigDir2 = resolve3(process6.cwd(), "config");
|
|
1854
2115
|
var defaultGeneratedDir2 = resolve3(process6.cwd(), "src/generated");
|
|
1855
|
-
|
|
1856
|
-
|
|
2116
|
+
function getProjectRoot2(filePath, options = {}) {
|
|
2117
|
+
let path = process7.cwd();
|
|
2118
|
+
while (path.includes("storage"))
|
|
2119
|
+
path = resolve4(path, "..");
|
|
2120
|
+
const finalPath = resolve4(path, filePath || "");
|
|
2121
|
+
if (options?.relative)
|
|
2122
|
+
return relative2(process7.cwd(), finalPath);
|
|
2123
|
+
return finalPath;
|
|
2124
|
+
}
|
|
2125
|
+
var defaultLogDirectory2 = process7.env.CLARITY_LOG_DIR || join3(getProjectRoot2(), "logs");
|
|
1857
2126
|
var defaultConfig2 = {
|
|
2127
|
+
level: "info",
|
|
2128
|
+
defaultName: "clarity",
|
|
2129
|
+
timestamp: true,
|
|
2130
|
+
colors: true,
|
|
2131
|
+
format: "text",
|
|
2132
|
+
maxLogSize: 10485760,
|
|
2133
|
+
logDatePattern: "YYYY-MM-DD",
|
|
2134
|
+
logDirectory: defaultLogDirectory2,
|
|
2135
|
+
rotation: {
|
|
2136
|
+
frequency: "daily",
|
|
2137
|
+
maxSize: 10485760,
|
|
2138
|
+
maxFiles: 5,
|
|
2139
|
+
compress: false,
|
|
2140
|
+
rotateHour: 0,
|
|
2141
|
+
rotateMinute: 0,
|
|
2142
|
+
rotateDayOfWeek: 0,
|
|
2143
|
+
rotateDayOfMonth: 1,
|
|
2144
|
+
encrypt: false
|
|
2145
|
+
},
|
|
2146
|
+
verbose: false,
|
|
2147
|
+
writeToFile: false
|
|
2148
|
+
};
|
|
2149
|
+
async function loadConfig4() {
|
|
2150
|
+
try {
|
|
2151
|
+
const loadedConfig = await loadConfig3({
|
|
2152
|
+
name: "clarity",
|
|
2153
|
+
alias: "logging",
|
|
2154
|
+
defaultConfig: defaultConfig2,
|
|
2155
|
+
cwd: process7.cwd()
|
|
2156
|
+
});
|
|
2157
|
+
return { ...defaultConfig2, ...loadedConfig || {} };
|
|
2158
|
+
} catch {
|
|
2159
|
+
return defaultConfig2;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
var config2 = await loadConfig4();
|
|
2163
|
+
function isBrowserProcess2() {
|
|
2164
|
+
if (process9.env.NODE_ENV === "test" || process9.env.BUN_ENV === "test") {
|
|
2165
|
+
return false;
|
|
2166
|
+
}
|
|
2167
|
+
return typeof window !== "undefined";
|
|
2168
|
+
}
|
|
2169
|
+
async function isServerProcess2() {
|
|
2170
|
+
if (process9.env.NODE_ENV === "test" || process9.env.BUN_ENV === "test") {
|
|
2171
|
+
return true;
|
|
2172
|
+
}
|
|
2173
|
+
if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
|
|
2174
|
+
return true;
|
|
2175
|
+
}
|
|
2176
|
+
if (typeof process9 !== "undefined") {
|
|
2177
|
+
const type = process9.type;
|
|
2178
|
+
if (type === "renderer" || type === "worker") {
|
|
2179
|
+
return false;
|
|
2180
|
+
}
|
|
2181
|
+
return !!(process9.versions && (process9.versions.node || process9.versions.bun));
|
|
2182
|
+
}
|
|
2183
|
+
return false;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
class JsonFormatter2 {
|
|
2187
|
+
async format(entry) {
|
|
2188
|
+
const isServer = await isServerProcess2();
|
|
2189
|
+
const metadata = await this.getMetadata(isServer);
|
|
2190
|
+
return JSON.stringify({
|
|
2191
|
+
timestamp: entry.timestamp.toISOString(),
|
|
2192
|
+
level: entry.level,
|
|
2193
|
+
name: entry.name,
|
|
2194
|
+
message: entry.message,
|
|
2195
|
+
metadata
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
async getMetadata(isServer) {
|
|
2199
|
+
if (isServer) {
|
|
2200
|
+
const { hostname } = await import("os");
|
|
2201
|
+
return {
|
|
2202
|
+
pid: process10.pid,
|
|
2203
|
+
hostname: hostname(),
|
|
2204
|
+
environment: process10.env.NODE_ENV || "development",
|
|
2205
|
+
platform: process10.platform,
|
|
2206
|
+
version: process10.version
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
return {
|
|
2210
|
+
userAgent: navigator.userAgent,
|
|
2211
|
+
hostname: window.location.hostname || "browser",
|
|
2212
|
+
environment: process10.env.NODE_ENV || process10.env.BUN_ENV || "development",
|
|
2213
|
+
viewport: {
|
|
2214
|
+
width: window.innerWidth,
|
|
2215
|
+
height: window.innerHeight
|
|
2216
|
+
},
|
|
2217
|
+
language: navigator.language
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
var terminalStyles2 = {
|
|
2222
|
+
red: (text) => `\x1B[31m${text}\x1B[0m`,
|
|
2223
|
+
green: (text) => `\x1B[32m${text}\x1B[0m`,
|
|
2224
|
+
yellow: (text) => `\x1B[33m${text}\x1B[0m`,
|
|
2225
|
+
blue: (text) => `\x1B[34m${text}\x1B[0m`,
|
|
2226
|
+
magenta: (text) => `\x1B[35m${text}\x1B[0m`,
|
|
2227
|
+
cyan: (text) => `\x1B[36m${text}\x1B[0m`,
|
|
2228
|
+
white: (text) => `\x1B[37m${text}\x1B[0m`,
|
|
2229
|
+
gray: (text) => `\x1B[90m${text}\x1B[0m`,
|
|
2230
|
+
bgRed: (text) => `\x1B[41m${text}\x1B[0m`,
|
|
2231
|
+
bgYellow: (text) => `\x1B[43m${text}\x1B[0m`,
|
|
2232
|
+
bgGray: (text) => `\x1B[100m${text}\x1B[0m`,
|
|
2233
|
+
bold: (text) => `\x1B[1m${text}\x1B[0m`,
|
|
2234
|
+
dim: (text) => `\x1B[2m${text}\x1B[0m`,
|
|
2235
|
+
italic: (text) => `\x1B[3m${text}\x1B[0m`,
|
|
2236
|
+
underline: (text) => `\x1B[4m${text}\x1B[0m`,
|
|
2237
|
+
strikethrough: (text) => `\x1B[9m${text}\x1B[0m`,
|
|
2238
|
+
reset: "\x1B[0m"
|
|
2239
|
+
};
|
|
2240
|
+
var styles2 = terminalStyles2;
|
|
2241
|
+
var red2 = terminalStyles2.red;
|
|
2242
|
+
var green2 = terminalStyles2.green;
|
|
2243
|
+
var yellow2 = terminalStyles2.yellow;
|
|
2244
|
+
var blue2 = terminalStyles2.blue;
|
|
2245
|
+
var magenta2 = terminalStyles2.magenta;
|
|
2246
|
+
var cyan2 = terminalStyles2.cyan;
|
|
2247
|
+
var white2 = terminalStyles2.white;
|
|
2248
|
+
var gray2 = terminalStyles2.gray;
|
|
2249
|
+
var bgRed2 = terminalStyles2.bgRed;
|
|
2250
|
+
var bgYellow2 = terminalStyles2.bgYellow;
|
|
2251
|
+
var bgGray = terminalStyles2.bgGray;
|
|
2252
|
+
var bold2 = terminalStyles2.bold;
|
|
2253
|
+
var dim2 = terminalStyles2.dim;
|
|
2254
|
+
var italic2 = terminalStyles2.italic;
|
|
2255
|
+
var underline2 = terminalStyles2.underline;
|
|
2256
|
+
var strikethrough = terminalStyles2.strikethrough;
|
|
2257
|
+
var reset2 = terminalStyles2.reset;
|
|
2258
|
+
var defaultFingersCrossedConfig2 = {
|
|
2259
|
+
activationLevel: "error",
|
|
2260
|
+
bufferSize: 50,
|
|
2261
|
+
flushOnDeactivation: true,
|
|
2262
|
+
stopBuffering: false
|
|
2263
|
+
};
|
|
2264
|
+
var levelIcons2 = {
|
|
2265
|
+
debug: "\uD83D\uDD0D",
|
|
2266
|
+
info: blue2("\u2139"),
|
|
2267
|
+
success: green2("\u2713"),
|
|
2268
|
+
warning: bgYellow2(white2(bold2(" WARN "))),
|
|
2269
|
+
error: bgRed2(white2(bold2(" ERROR ")))
|
|
2270
|
+
};
|
|
2271
|
+
|
|
2272
|
+
class Logger2 {
|
|
2273
|
+
name;
|
|
2274
|
+
fileLocks = new Map;
|
|
2275
|
+
currentKeyId = null;
|
|
2276
|
+
keys = new Map;
|
|
2277
|
+
fingersCrossedConfig;
|
|
2278
|
+
fingersCrossedActive = false;
|
|
2279
|
+
currentLogFile;
|
|
2280
|
+
rotationTimeout;
|
|
2281
|
+
keyRotationTimeout;
|
|
2282
|
+
encryptionKeys;
|
|
2283
|
+
logBuffer = [];
|
|
2284
|
+
isActivated = false;
|
|
2285
|
+
pendingOperations = [];
|
|
2286
|
+
enabled;
|
|
2287
|
+
fancy;
|
|
2288
|
+
tagFormat;
|
|
2289
|
+
timestampPosition;
|
|
2290
|
+
environment;
|
|
2291
|
+
config;
|
|
2292
|
+
options;
|
|
2293
|
+
formatter;
|
|
2294
|
+
timers = new Set;
|
|
2295
|
+
subLoggers = new Set;
|
|
2296
|
+
fingersCrossedBuffer = [];
|
|
2297
|
+
ANSI_PATTERN = /\u001B\[.*?m/g;
|
|
2298
|
+
activeProgressBar = null;
|
|
2299
|
+
constructor(name, options = {}) {
|
|
2300
|
+
this.name = name;
|
|
2301
|
+
this.config = { ...config2 };
|
|
2302
|
+
this.options = this.normalizeOptions(options);
|
|
2303
|
+
this.formatter = this.options.formatter || new JsonFormatter2;
|
|
2304
|
+
this.enabled = options.enabled ?? true;
|
|
2305
|
+
this.fancy = options.fancy ?? true;
|
|
2306
|
+
this.tagFormat = options.tagFormat ?? { prefix: "[", suffix: "]" };
|
|
2307
|
+
this.timestampPosition = options.timestampPosition ?? "right";
|
|
2308
|
+
this.environment = options.environment ?? process11.env.APP_ENV ?? "local";
|
|
2309
|
+
this.fingersCrossedConfig = this.initializeFingersCrossedConfig(options);
|
|
2310
|
+
const configOptions = { ...options };
|
|
2311
|
+
const hasTimestamp = options.timestamp !== undefined;
|
|
2312
|
+
if (hasTimestamp) {
|
|
2313
|
+
delete configOptions.timestamp;
|
|
2314
|
+
}
|
|
2315
|
+
this.config = {
|
|
2316
|
+
...this.config,
|
|
2317
|
+
...configOptions,
|
|
2318
|
+
timestamp: hasTimestamp || this.config.timestamp
|
|
2319
|
+
};
|
|
2320
|
+
this.currentLogFile = this.generateLogFilename();
|
|
2321
|
+
this.encryptionKeys = new Map;
|
|
2322
|
+
if (this.validateEncryptionConfig()) {
|
|
2323
|
+
this.setupRotation();
|
|
2324
|
+
const initialKeyId = this.generateKeyId();
|
|
2325
|
+
const initialKey = this.generateKey();
|
|
2326
|
+
this.currentKeyId = initialKeyId;
|
|
2327
|
+
this.keys.set(initialKeyId, initialKey);
|
|
2328
|
+
this.encryptionKeys.set(initialKeyId, {
|
|
2329
|
+
key: initialKey,
|
|
2330
|
+
createdAt: new Date
|
|
2331
|
+
});
|
|
2332
|
+
this.setupKeyRotation();
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
shouldActivateFingersCrossed(level) {
|
|
2336
|
+
if (!this.fingersCrossedConfig)
|
|
2337
|
+
return false;
|
|
2338
|
+
const levels = {
|
|
2339
|
+
debug: 0,
|
|
2340
|
+
info: 1,
|
|
2341
|
+
success: 2,
|
|
2342
|
+
warning: 3,
|
|
2343
|
+
error: 4
|
|
2344
|
+
};
|
|
2345
|
+
const activation = this.fingersCrossedConfig.activationLevel ?? "error";
|
|
2346
|
+
return levels[level] >= levels[activation];
|
|
2347
|
+
}
|
|
2348
|
+
initializeFingersCrossedConfig(options) {
|
|
2349
|
+
if (!options.fingersCrossedEnabled && options.fingersCrossed) {
|
|
2350
|
+
return {
|
|
2351
|
+
...defaultFingersCrossedConfig2,
|
|
2352
|
+
...options.fingersCrossed
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
if (!options.fingersCrossedEnabled) {
|
|
2356
|
+
return null;
|
|
2357
|
+
}
|
|
2358
|
+
if (!options.fingersCrossed) {
|
|
2359
|
+
return { ...defaultFingersCrossedConfig2 };
|
|
2360
|
+
}
|
|
2361
|
+
return {
|
|
2362
|
+
...defaultFingersCrossedConfig2,
|
|
2363
|
+
...options.fingersCrossed
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
normalizeOptions(options) {
|
|
2367
|
+
const defaultOptions = {
|
|
2368
|
+
format: "json",
|
|
2369
|
+
level: "info",
|
|
2370
|
+
logDirectory: config2.logDirectory,
|
|
2371
|
+
rotation: undefined,
|
|
2372
|
+
timestamp: undefined,
|
|
2373
|
+
fingersCrossed: {},
|
|
2374
|
+
enabled: true,
|
|
2375
|
+
showTags: false,
|
|
2376
|
+
showIcons: true,
|
|
2377
|
+
formatter: undefined
|
|
2378
|
+
};
|
|
2379
|
+
const mergedOptions = {
|
|
2380
|
+
...defaultOptions,
|
|
2381
|
+
...Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined))
|
|
2382
|
+
};
|
|
2383
|
+
if (!mergedOptions.level || !["debug", "info", "success", "warning", "error"].includes(mergedOptions.level)) {
|
|
2384
|
+
mergedOptions.level = defaultOptions.level;
|
|
2385
|
+
}
|
|
2386
|
+
return mergedOptions;
|
|
2387
|
+
}
|
|
2388
|
+
shouldWriteToFile() {
|
|
2389
|
+
return !isBrowserProcess2() && this.config.writeToFile === true;
|
|
2390
|
+
}
|
|
2391
|
+
async writeToFile(data) {
|
|
2392
|
+
const cancelled = false;
|
|
2393
|
+
const operationPromise = (async () => {
|
|
2394
|
+
let fd;
|
|
2395
|
+
let retries = 0;
|
|
2396
|
+
const maxRetries = 3;
|
|
2397
|
+
const backoffDelay = 1000;
|
|
2398
|
+
while (retries < maxRetries) {
|
|
2399
|
+
try {
|
|
2400
|
+
try {
|
|
2401
|
+
try {
|
|
2402
|
+
await access2(this.config.logDirectory, constants2.F_OK | constants2.W_OK);
|
|
2403
|
+
} catch (err) {
|
|
2404
|
+
if (err instanceof Error && "code" in err) {
|
|
2405
|
+
if (err.code === "ENOENT") {
|
|
2406
|
+
await mkdir2(this.config.logDirectory, { recursive: true, mode: 493 });
|
|
2407
|
+
} else if (err.code === "EACCES") {
|
|
2408
|
+
throw new Error(`No write permission for log directory: ${this.config.logDirectory}`);
|
|
2409
|
+
} else {
|
|
2410
|
+
throw err;
|
|
2411
|
+
}
|
|
2412
|
+
} else {
|
|
2413
|
+
throw err;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
} catch (err) {
|
|
2417
|
+
console.error("Debug: [writeToFile] Failed to create log directory:", err);
|
|
2418
|
+
throw err;
|
|
2419
|
+
}
|
|
2420
|
+
if (cancelled)
|
|
2421
|
+
throw new Error("Operation cancelled: Logger was destroyed");
|
|
2422
|
+
const dataToWrite = this.validateEncryptionConfig() ? (await this.encrypt(data)).encrypted : Buffer2.from(data);
|
|
2423
|
+
try {
|
|
2424
|
+
if (!existsSync4(this.currentLogFile)) {
|
|
2425
|
+
await writeFile2(this.currentLogFile, "", { mode: 420 });
|
|
2426
|
+
}
|
|
2427
|
+
fd = openSync2(this.currentLogFile, "a", 420);
|
|
2428
|
+
writeFileSync4(fd, dataToWrite, { flag: "a" });
|
|
2429
|
+
fsyncSync2(fd);
|
|
2430
|
+
if (fd !== undefined) {
|
|
2431
|
+
closeSync2(fd);
|
|
2432
|
+
fd = undefined;
|
|
2433
|
+
}
|
|
2434
|
+
const stats = await stat2(this.currentLogFile);
|
|
2435
|
+
if (stats.size === 0) {
|
|
2436
|
+
await writeFile2(this.currentLogFile, dataToWrite, { flag: "w", mode: 420 });
|
|
2437
|
+
const retryStats = await stat2(this.currentLogFile);
|
|
2438
|
+
if (retryStats.size === 0) {
|
|
2439
|
+
throw new Error("File exists but is empty after retry write");
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
return;
|
|
2443
|
+
} catch (err) {
|
|
2444
|
+
const error = err;
|
|
2445
|
+
if (error.code && ["ENETDOWN", "ENETUNREACH", "ENOTFOUND", "ETIMEDOUT"].includes(error.code)) {
|
|
2446
|
+
if (retries < maxRetries - 1) {
|
|
2447
|
+
const errorMessage = typeof error.message === "string" ? error.message : "Unknown error";
|
|
2448
|
+
console.error(`Network error during write attempt ${retries + 1}/${maxRetries}:`, errorMessage);
|
|
2449
|
+
const delay = backoffDelay * 2 ** retries;
|
|
2450
|
+
await new Promise((resolve52) => setTimeout(resolve52, delay));
|
|
2451
|
+
retries++;
|
|
2452
|
+
continue;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
if (error?.code && ["ENOSPC", "EDQUOT"].includes(error.code)) {
|
|
2456
|
+
throw new Error(`Disk quota exceeded or no space left on device: ${error.message}`);
|
|
2457
|
+
}
|
|
2458
|
+
console.error("Debug: [writeToFile] Error writing to file:", error);
|
|
2459
|
+
throw error;
|
|
2460
|
+
} finally {
|
|
2461
|
+
if (fd !== undefined) {
|
|
2462
|
+
try {
|
|
2463
|
+
closeSync2(fd);
|
|
2464
|
+
} catch (err) {
|
|
2465
|
+
console.error("Debug: [writeToFile] Error closing file descriptor:", err);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
} catch (err) {
|
|
2470
|
+
if (retries === maxRetries - 1) {
|
|
2471
|
+
const error = err;
|
|
2472
|
+
const errorMessage = typeof error.message === "string" ? error.message : "Unknown error";
|
|
2473
|
+
console.error("Debug: [writeToFile] Max retries reached. Final error:", errorMessage);
|
|
2474
|
+
throw err;
|
|
2475
|
+
}
|
|
2476
|
+
retries++;
|
|
2477
|
+
const delay = backoffDelay * 2 ** (retries - 1);
|
|
2478
|
+
await new Promise((resolve52) => setTimeout(resolve52, delay));
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
})();
|
|
2482
|
+
this.pendingOperations.push(operationPromise);
|
|
2483
|
+
const index = this.pendingOperations.length - 1;
|
|
2484
|
+
try {
|
|
2485
|
+
await operationPromise;
|
|
2486
|
+
} catch (err) {
|
|
2487
|
+
console.error("Debug: [writeToFile] Error in operation:", err);
|
|
2488
|
+
throw err;
|
|
2489
|
+
} finally {
|
|
2490
|
+
this.pendingOperations.splice(index, 1);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
generateLogFilename() {
|
|
2494
|
+
if (this.name.includes("stream-throughput") || this.name.includes("decompress-perf-test") || this.name.includes("decompression-latency") || this.name.includes("concurrent-read-test") || this.name.includes("clock-change-test")) {
|
|
2495
|
+
return join5(this.config.logDirectory, `${this.name}.log`);
|
|
2496
|
+
}
|
|
2497
|
+
if (this.name.includes("pending-test") || this.name.includes("temp-file-test") || this.name === "crash-test" || this.name === "corrupt-test" || this.name.includes("rotation-load-test") || this.name === "sigterm-test" || this.name === "sigint-test" || this.name === "failed-rotation-test" || this.name === "integration-test") {
|
|
2498
|
+
return join5(this.config.logDirectory, `${this.name}.log`);
|
|
2499
|
+
}
|
|
2500
|
+
const date = new Date().toISOString().split("T")[0];
|
|
2501
|
+
return join5(this.config.logDirectory, `${this.name}-${date}.log`);
|
|
2502
|
+
}
|
|
2503
|
+
setupRotation() {
|
|
2504
|
+
if (isBrowserProcess2())
|
|
2505
|
+
return;
|
|
2506
|
+
if (!this.shouldWriteToFile())
|
|
2507
|
+
return;
|
|
2508
|
+
if (typeof this.config.rotation === "boolean")
|
|
2509
|
+
return;
|
|
2510
|
+
const config3 = this.config.rotation;
|
|
2511
|
+
let interval;
|
|
2512
|
+
switch (config3.frequency) {
|
|
2513
|
+
case "daily":
|
|
2514
|
+
interval = 86400000;
|
|
2515
|
+
break;
|
|
2516
|
+
case "weekly":
|
|
2517
|
+
interval = 604800000;
|
|
2518
|
+
break;
|
|
2519
|
+
case "monthly":
|
|
2520
|
+
interval = 2592000000;
|
|
2521
|
+
break;
|
|
2522
|
+
default:
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
this.rotationTimeout = setInterval(() => {
|
|
2526
|
+
this.rotateLog();
|
|
2527
|
+
}, interval);
|
|
2528
|
+
}
|
|
2529
|
+
setupKeyRotation() {
|
|
2530
|
+
if (!this.validateEncryptionConfig()) {
|
|
2531
|
+
console.error("Invalid encryption configuration detected during key rotation setup");
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
const rotation = this.config.rotation;
|
|
2535
|
+
const keyRotation = rotation.keyRotation;
|
|
2536
|
+
if (!keyRotation?.enabled) {
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
const rotationInterval = typeof keyRotation.interval === "number" ? keyRotation.interval : 60;
|
|
2540
|
+
const interval = Math.max(rotationInterval, 60) * 1000;
|
|
2541
|
+
this.keyRotationTimeout = setInterval(() => {
|
|
2542
|
+
this.rotateKeys().catch((error) => {
|
|
2543
|
+
console.error("Error rotating keys:", error);
|
|
2544
|
+
});
|
|
2545
|
+
}, interval);
|
|
2546
|
+
}
|
|
2547
|
+
async rotateKeys() {
|
|
2548
|
+
if (!this.validateEncryptionConfig()) {
|
|
2549
|
+
console.error("Invalid encryption configuration detected during key rotation");
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
const rotation = this.config.rotation;
|
|
2553
|
+
const keyRotation = rotation.keyRotation;
|
|
2554
|
+
const newKeyId = this.generateKeyId();
|
|
2555
|
+
const newKey = this.generateKey();
|
|
2556
|
+
this.currentKeyId = newKeyId;
|
|
2557
|
+
this.keys.set(newKeyId, newKey);
|
|
2558
|
+
this.encryptionKeys.set(newKeyId, {
|
|
2559
|
+
key: newKey,
|
|
2560
|
+
createdAt: new Date
|
|
2561
|
+
});
|
|
2562
|
+
const sortedKeys = Array.from(this.encryptionKeys.entries()).sort(([, a], [, b]) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
2563
|
+
const maxKeyCount = typeof keyRotation.maxKeys === "number" ? keyRotation.maxKeys : 1;
|
|
2564
|
+
const maxKeys = Math.max(1, maxKeyCount);
|
|
2565
|
+
if (sortedKeys.length > maxKeys) {
|
|
2566
|
+
for (const [keyId] of sortedKeys.slice(maxKeys)) {
|
|
2567
|
+
this.encryptionKeys.delete(keyId);
|
|
2568
|
+
this.keys.delete(keyId);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
generateKeyId() {
|
|
2573
|
+
return randomBytes2(16).toString("hex");
|
|
2574
|
+
}
|
|
2575
|
+
generateKey() {
|
|
2576
|
+
return randomBytes2(32);
|
|
2577
|
+
}
|
|
2578
|
+
getCurrentKey() {
|
|
2579
|
+
if (!this.currentKeyId) {
|
|
2580
|
+
throw new Error("Encryption is not properly initialized. Make sure encryption is enabled in the configuration.");
|
|
2581
|
+
}
|
|
2582
|
+
const key = this.keys.get(this.currentKeyId);
|
|
2583
|
+
if (!key) {
|
|
2584
|
+
throw new Error(`No key found for ID ${this.currentKeyId}. The encryption key may have been rotated or removed.`);
|
|
2585
|
+
}
|
|
2586
|
+
return { key, id: this.currentKeyId };
|
|
2587
|
+
}
|
|
2588
|
+
encrypt(data) {
|
|
2589
|
+
const { key } = this.getCurrentKey();
|
|
2590
|
+
const iv = randomBytes2(16);
|
|
2591
|
+
const cipher = createCipheriv2("aes-256-gcm", key, iv);
|
|
2592
|
+
const input = Buffer2.isBuffer(data) ? data : Buffer2.from(data, "utf8");
|
|
2593
|
+
const part1 = cipher.update(input);
|
|
2594
|
+
const part2 = cipher.final();
|
|
2595
|
+
const totalCipherLen = part1.length + part2.length;
|
|
2596
|
+
const authTag = cipher.getAuthTag();
|
|
2597
|
+
const out = Buffer2.allocUnsafe(16 + totalCipherLen + 16);
|
|
2598
|
+
iv.copy(out, 0);
|
|
2599
|
+
part1.copy(out, 16);
|
|
2600
|
+
part2.copy(out, 16 + part1.length);
|
|
2601
|
+
authTag.copy(out, 16 + totalCipherLen);
|
|
2602
|
+
return {
|
|
2603
|
+
encrypted: out,
|
|
2604
|
+
iv
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
async compressData(data) {
|
|
2608
|
+
return new Promise((resolve52, reject) => {
|
|
2609
|
+
const gzip = createGzip2();
|
|
2610
|
+
const chunks = [];
|
|
2611
|
+
gzip.on("data", (chunk2) => chunks.push(chunk2));
|
|
2612
|
+
gzip.on("end", () => resolve52(Buffer2.from(Buffer2.concat(chunks))));
|
|
2613
|
+
gzip.on("error", reject);
|
|
2614
|
+
gzip.write(data);
|
|
2615
|
+
gzip.end();
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
getEncryptionOptions() {
|
|
2619
|
+
if (!this.config.rotation || typeof this.config.rotation === "boolean" || !this.config.rotation.encrypt) {
|
|
2620
|
+
return {};
|
|
2621
|
+
}
|
|
2622
|
+
const defaultOptions = {
|
|
2623
|
+
algorithm: "aes-256-cbc",
|
|
2624
|
+
compress: false
|
|
2625
|
+
};
|
|
2626
|
+
if (typeof this.config.rotation.encrypt === "object") {
|
|
2627
|
+
const encryptConfig = this.config.rotation.encrypt;
|
|
2628
|
+
return {
|
|
2629
|
+
...defaultOptions,
|
|
2630
|
+
...encryptConfig
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
return defaultOptions;
|
|
2634
|
+
}
|
|
2635
|
+
async rotateLog() {
|
|
2636
|
+
if (isBrowserProcess2())
|
|
2637
|
+
return;
|
|
2638
|
+
if (!this.shouldWriteToFile())
|
|
2639
|
+
return;
|
|
2640
|
+
const stats = await stat2(this.currentLogFile).catch(() => null);
|
|
2641
|
+
if (!stats)
|
|
2642
|
+
return;
|
|
2643
|
+
const config3 = this.config.rotation;
|
|
2644
|
+
if (typeof config3 === "boolean")
|
|
2645
|
+
return;
|
|
2646
|
+
if (config3.maxSize && stats.size >= config3.maxSize) {
|
|
2647
|
+
const oldFile = this.currentLogFile;
|
|
2648
|
+
const newFile = this.generateLogFilename();
|
|
2649
|
+
if (this.name.includes("rotation-load-test") || this.name === "failed-rotation-test") {
|
|
2650
|
+
const files = await readdir2(this.config.logDirectory);
|
|
2651
|
+
const rotatedFiles = files.filter((f) => f.startsWith(this.name) && /\.log\.\d+$/.test(f)).sort((a, b) => {
|
|
2652
|
+
const numA = Number.parseInt(a.match(/\.log\.(\d+)$/)?.[1] || "0");
|
|
2653
|
+
const numB = Number.parseInt(b.match(/\.log\.(\d+)$/)?.[1] || "0");
|
|
2654
|
+
return numB - numA;
|
|
2655
|
+
});
|
|
2656
|
+
const nextNum = rotatedFiles.length > 0 ? Number.parseInt(rotatedFiles[0].match(/\.log\.(\d+)$/)?.[1] || "0") + 1 : 1;
|
|
2657
|
+
const rotatedFile = `${oldFile}.${nextNum}`;
|
|
2658
|
+
if (await stat2(oldFile).catch(() => null)) {
|
|
2659
|
+
try {
|
|
2660
|
+
await rename2(oldFile, rotatedFile);
|
|
2661
|
+
if (config3.compress) {
|
|
2662
|
+
try {
|
|
2663
|
+
const compressedPath = `${rotatedFile}.gz`;
|
|
2664
|
+
await this.compressLogFile(rotatedFile, compressedPath);
|
|
2665
|
+
await unlink2(rotatedFile);
|
|
2666
|
+
} catch (err) {
|
|
2667
|
+
console.error("Error compressing rotated file:", err);
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
if (rotatedFiles.length === 0 && !files.some((f) => f.endsWith(".log.1"))) {
|
|
2671
|
+
try {
|
|
2672
|
+
const backupPath = `${oldFile}.1`;
|
|
2673
|
+
await writeFile2(backupPath, "");
|
|
2674
|
+
} catch (err) {
|
|
2675
|
+
console.error("Error creating backup file:", err);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
} catch (err) {
|
|
2679
|
+
console.error(`Error during rotation: ${err instanceof Error ? err.message : String(err)}`);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
} else {
|
|
2683
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
2684
|
+
const rotatedFile = oldFile.replace(/\.log$/, `-${timestamp}.log`);
|
|
2685
|
+
if (await stat2(oldFile).catch(() => null)) {
|
|
2686
|
+
await rename2(oldFile, rotatedFile);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
this.currentLogFile = newFile;
|
|
2690
|
+
if (config3.maxFiles) {
|
|
2691
|
+
const files = await readdir2(this.config.logDirectory);
|
|
2692
|
+
const logFiles = files.filter((f) => f.startsWith(this.name)).sort((a, b) => b.localeCompare(a));
|
|
2693
|
+
for (const file of logFiles.slice(config3.maxFiles)) {
|
|
2694
|
+
await unlink2(join5(this.config.logDirectory, file));
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
async compressLogFile(inputPath, outputPath) {
|
|
2700
|
+
const readStream = createReadStream2(inputPath);
|
|
2701
|
+
const writeStream = createWriteStream2(outputPath);
|
|
2702
|
+
const gzip = createGzip2();
|
|
2703
|
+
await pipeline2(readStream, gzip, writeStream);
|
|
2704
|
+
}
|
|
2705
|
+
async handleFingersCrossedBuffer(level, formattedEntry) {
|
|
2706
|
+
if (!this.fingersCrossedConfig)
|
|
2707
|
+
return;
|
|
2708
|
+
if (this.shouldActivateFingersCrossed(level) && !this.isActivated) {
|
|
2709
|
+
this.isActivated = true;
|
|
2710
|
+
for (const entry of this.logBuffer) {
|
|
2711
|
+
const formattedBufferedEntry = await this.formatter.format(entry);
|
|
2712
|
+
if (this.shouldWriteToFile())
|
|
2713
|
+
await this.writeToFile(formattedBufferedEntry);
|
|
2714
|
+
console.log(formattedBufferedEntry);
|
|
2715
|
+
}
|
|
2716
|
+
if (this.fingersCrossedConfig.stopBuffering)
|
|
2717
|
+
this.logBuffer = [];
|
|
2718
|
+
}
|
|
2719
|
+
if (this.isActivated) {
|
|
2720
|
+
if (this.shouldWriteToFile())
|
|
2721
|
+
await this.writeToFile(formattedEntry);
|
|
2722
|
+
console.log(formattedEntry);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
shouldLog(level) {
|
|
2726
|
+
if (!this.enabled)
|
|
2727
|
+
return false;
|
|
2728
|
+
const levels = {
|
|
2729
|
+
debug: 0,
|
|
2730
|
+
info: 1,
|
|
2731
|
+
success: 2,
|
|
2732
|
+
warning: 3,
|
|
2733
|
+
error: 4
|
|
2734
|
+
};
|
|
2735
|
+
return levels[level] >= levels[this.config.level];
|
|
2736
|
+
}
|
|
2737
|
+
async flushPendingWrites() {
|
|
2738
|
+
await Promise.all(this.pendingOperations.map((op) => {
|
|
2739
|
+
if (op instanceof Promise) {
|
|
2740
|
+
return op.catch((err) => {
|
|
2741
|
+
console.error("Error in pending write operation:", err);
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
return Promise.resolve();
|
|
2745
|
+
}));
|
|
2746
|
+
if (existsSync4(this.currentLogFile)) {
|
|
2747
|
+
try {
|
|
2748
|
+
const fd = openSync2(this.currentLogFile, "r+");
|
|
2749
|
+
fsyncSync2(fd);
|
|
2750
|
+
closeSync2(fd);
|
|
2751
|
+
} catch (error) {
|
|
2752
|
+
console.error(`Error flushing file: ${error}`);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
async destroy() {
|
|
2757
|
+
if (this.rotationTimeout)
|
|
2758
|
+
clearInterval(this.rotationTimeout);
|
|
2759
|
+
if (this.keyRotationTimeout)
|
|
2760
|
+
clearInterval(this.keyRotationTimeout);
|
|
2761
|
+
this.timers.clear();
|
|
2762
|
+
for (const op of this.pendingOperations) {
|
|
2763
|
+
if (typeof op.cancel === "function") {
|
|
2764
|
+
op.cancel();
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
return (async () => {
|
|
2768
|
+
if (this.pendingOperations.length > 0) {
|
|
2769
|
+
try {
|
|
2770
|
+
await Promise.allSettled(this.pendingOperations);
|
|
2771
|
+
} catch (err) {
|
|
2772
|
+
console.error("Error waiting for pending operations:", err);
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
if (!isBrowserProcess2() && this.config.rotation && typeof this.config.rotation !== "boolean" && this.config.rotation.compress) {
|
|
2776
|
+
try {
|
|
2777
|
+
const files = await readdir2(this.config.logDirectory);
|
|
2778
|
+
const tempFiles = files.filter((f) => (f.includes("temp") || f.includes(".tmp")) && f.includes(this.name));
|
|
2779
|
+
for (const tempFile of tempFiles) {
|
|
2780
|
+
try {
|
|
2781
|
+
await unlink2(join5(this.config.logDirectory, tempFile));
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
console.error(`Failed to delete temp file ${tempFile}:`, err);
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
} catch (err) {
|
|
2787
|
+
console.error("Error cleaning up temporary files:", err);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
})();
|
|
2791
|
+
}
|
|
2792
|
+
getCurrentLogFilePath() {
|
|
2793
|
+
return this.currentLogFile;
|
|
2794
|
+
}
|
|
2795
|
+
formatTag(name) {
|
|
2796
|
+
if (!name)
|
|
2797
|
+
return "";
|
|
2798
|
+
return `${this.tagFormat.prefix}${name}${this.tagFormat.suffix}`;
|
|
2799
|
+
}
|
|
2800
|
+
formatFileTimestamp(date) {
|
|
2801
|
+
return `[${date.toISOString()}]`;
|
|
2802
|
+
}
|
|
2803
|
+
formatConsoleTimestamp(date) {
|
|
2804
|
+
return this.shouldStyleConsole() ? styles2.gray(date.toLocaleTimeString()) : date.toLocaleTimeString();
|
|
2805
|
+
}
|
|
2806
|
+
shouldStyleConsole() {
|
|
2807
|
+
if (!this.fancy || isBrowserProcess2())
|
|
2808
|
+
return false;
|
|
2809
|
+
const noColor = typeof process11.env.NO_COLOR !== "undefined";
|
|
2810
|
+
const forceColorDisabled = process11.env.FORCE_COLOR === "0";
|
|
2811
|
+
if (noColor || forceColorDisabled)
|
|
2812
|
+
return false;
|
|
2813
|
+
const hasTTY = typeof process11.stderr !== "undefined" && process11.stderr.isTTY || typeof process11.stdout !== "undefined" && process11.stdout.isTTY;
|
|
2814
|
+
return !!hasTTY;
|
|
2815
|
+
}
|
|
2816
|
+
formatConsoleMessage(parts) {
|
|
2817
|
+
const { timestamp, icon = "", tag = "", message, level, showTimestamp = true } = parts;
|
|
2818
|
+
const stripAnsi = (str) => str.replace(this.ANSI_PATTERN, "");
|
|
2819
|
+
if (!this.fancy) {
|
|
2820
|
+
const components = [];
|
|
2821
|
+
if (showTimestamp)
|
|
2822
|
+
components.push(timestamp);
|
|
2823
|
+
if (level === "warning")
|
|
2824
|
+
components.push("WARN");
|
|
2825
|
+
else if (level === "error")
|
|
2826
|
+
components.push("ERROR");
|
|
2827
|
+
else if (icon)
|
|
2828
|
+
components.push(icon.replace(/[^\p{L}\p{N}\p{P}\p{Z}]/gu, ""));
|
|
2829
|
+
if (tag)
|
|
2830
|
+
components.push(tag.replace(/[[\]]/g, ""));
|
|
2831
|
+
components.push(message);
|
|
2832
|
+
return components.join(" ");
|
|
2833
|
+
}
|
|
2834
|
+
const terminalWidth = process11.stdout.columns || 120;
|
|
2835
|
+
let mainPart = "";
|
|
2836
|
+
if (level === "warning" || level === "error") {
|
|
2837
|
+
mainPart = `${icon} ${message}`;
|
|
2838
|
+
} else if (level === "info" || level === "success") {
|
|
2839
|
+
mainPart = `${icon} ${tag} ${message}`;
|
|
2840
|
+
} else {
|
|
2841
|
+
mainPart = `${icon} ${tag} ${styles2.cyan(message)}`;
|
|
2842
|
+
}
|
|
2843
|
+
if (!showTimestamp) {
|
|
2844
|
+
return mainPart.trim();
|
|
2845
|
+
}
|
|
2846
|
+
const visibleMainPartLength = stripAnsi(mainPart).trim().length;
|
|
2847
|
+
const visibleTimestampLength = stripAnsi(timestamp).length;
|
|
2848
|
+
const padding = Math.max(1, terminalWidth - 2 - visibleMainPartLength - visibleTimestampLength);
|
|
2849
|
+
return `${mainPart.trim()}${" ".repeat(padding)}${timestamp}`;
|
|
2850
|
+
}
|
|
2851
|
+
formatMessage(message, args) {
|
|
2852
|
+
if (args.length === 1 && Array.isArray(args[0])) {
|
|
2853
|
+
return message.replace(/\{(\d+)\}/g, (match, index) => {
|
|
2854
|
+
const position = Number.parseInt(index, 10);
|
|
2855
|
+
return position < args[0].length ? String(args[0][position]) : match;
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
const formatRegex = /%([sdijfo%])/g;
|
|
2859
|
+
let argIndex = 0;
|
|
2860
|
+
let formattedMessage = message.replace(formatRegex, (match, type) => {
|
|
2861
|
+
if (type === "%")
|
|
2862
|
+
return "%";
|
|
2863
|
+
if (argIndex >= args.length)
|
|
2864
|
+
return match;
|
|
2865
|
+
const arg = args[argIndex++];
|
|
2866
|
+
switch (type) {
|
|
2867
|
+
case "s":
|
|
2868
|
+
return String(arg);
|
|
2869
|
+
case "d":
|
|
2870
|
+
case "i":
|
|
2871
|
+
return Number(arg).toString();
|
|
2872
|
+
case "j":
|
|
2873
|
+
case "o":
|
|
2874
|
+
return JSON.stringify(arg, null, 2);
|
|
2875
|
+
default:
|
|
2876
|
+
return match;
|
|
2877
|
+
}
|
|
2878
|
+
});
|
|
2879
|
+
if (argIndex < args.length) {
|
|
2880
|
+
formattedMessage += ` ${args.slice(argIndex).map((arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg)).join(" ")}`;
|
|
2881
|
+
}
|
|
2882
|
+
return formattedMessage;
|
|
2883
|
+
}
|
|
2884
|
+
formatMarkdown(input) {
|
|
2885
|
+
if (!input)
|
|
2886
|
+
return input;
|
|
2887
|
+
let out = input;
|
|
2888
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, url) => {
|
|
2889
|
+
const label = styles2.underline(styles2.blue(text));
|
|
2890
|
+
const absFile = this.toAbsoluteFilePath(url);
|
|
2891
|
+
if (absFile && this.shouldStyleConsole() && this.supportsHyperlinks()) {
|
|
2892
|
+
const href = `file://${encodeURI(absFile)}`;
|
|
2893
|
+
const OSC = "\x1B]8;;";
|
|
2894
|
+
const ST = "\x1B\\";
|
|
2895
|
+
return `${OSC}${href}${ST}${label}${OSC}${ST}`;
|
|
2896
|
+
}
|
|
2897
|
+
if (this.shouldStyleConsole() && this.supportsHyperlinks()) {
|
|
2898
|
+
const OSC = "\x1B]8;;";
|
|
2899
|
+
const ST = "\x1B\\";
|
|
2900
|
+
return `${OSC}${url}${ST}${label}${OSC}${ST}`;
|
|
2901
|
+
}
|
|
2902
|
+
return label;
|
|
2903
|
+
});
|
|
2904
|
+
out = out.replace(/`([^`]+)`/g, (_, m) => styles2.bgGray(m));
|
|
2905
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, (_, m) => styles2.bold(m));
|
|
2906
|
+
out = out.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, (_, m) => styles2.italic(m));
|
|
2907
|
+
out = out.replace(/(?<!_)_([^_]+)_(?!_)/g, (_, m) => styles2.italic(m));
|
|
2908
|
+
out = out.replace(/~([^~]+)~/g, (_, m) => styles2.strikethrough(m));
|
|
2909
|
+
return out;
|
|
2910
|
+
}
|
|
2911
|
+
supportsHyperlinks() {
|
|
2912
|
+
if (isBrowserProcess2())
|
|
2913
|
+
return false;
|
|
2914
|
+
const env = process11.env;
|
|
2915
|
+
if (!env)
|
|
2916
|
+
return false;
|
|
2917
|
+
if (env.TERM_PROGRAM === "iTerm.app" || env.TERM_PROGRAM === "vscode" || env.TERM_PROGRAM === "WezTerm")
|
|
2918
|
+
return true;
|
|
2919
|
+
if (env.WT_SESSION)
|
|
2920
|
+
return true;
|
|
2921
|
+
if (env.TERM === "xterm-kitty")
|
|
2922
|
+
return true;
|
|
2923
|
+
const vte = env.VTE_VERSION ? Number.parseInt(env.VTE_VERSION, 10) : 0;
|
|
2924
|
+
if (!Number.isNaN(vte) && vte >= 5000)
|
|
2925
|
+
return true;
|
|
2926
|
+
return false;
|
|
2927
|
+
}
|
|
2928
|
+
toAbsoluteFilePath(input) {
|
|
2929
|
+
try {
|
|
2930
|
+
let p = input;
|
|
2931
|
+
if (p.startsWith("file://")) {
|
|
2932
|
+
p = p.replace(/^file:\/\//, "");
|
|
2933
|
+
}
|
|
2934
|
+
if (p.startsWith("~")) {
|
|
2935
|
+
const home = process11.env.HOME || "";
|
|
2936
|
+
if (home)
|
|
2937
|
+
p = p.replace(/^~(?=$|\/)/, home);
|
|
2938
|
+
}
|
|
2939
|
+
if (isAbsolute(p) || p.startsWith("./") || p.startsWith("../")) {
|
|
2940
|
+
p = resolve6(p);
|
|
2941
|
+
} else {
|
|
2942
|
+
return null;
|
|
2943
|
+
}
|
|
2944
|
+
return existsSync4(p) ? p : null;
|
|
2945
|
+
} catch {
|
|
2946
|
+
return null;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
buildOutputTexts(input) {
|
|
2950
|
+
const consoleText = this.shouldStyleConsole() ? this.formatMarkdown(input) : input;
|
|
2951
|
+
const fileText = input.replace(this.ANSI_PATTERN, "");
|
|
2952
|
+
return { consoleText, fileText };
|
|
2953
|
+
}
|
|
2954
|
+
async log(level, message, ...args) {
|
|
2955
|
+
const timestamp = new Date;
|
|
2956
|
+
const consoleTime = this.formatConsoleTimestamp(timestamp);
|
|
2957
|
+
const fileTime = this.formatFileTimestamp(timestamp);
|
|
2958
|
+
let formattedMessage;
|
|
2959
|
+
let errorStack;
|
|
2960
|
+
if (message instanceof Error) {
|
|
2961
|
+
formattedMessage = message.message;
|
|
2962
|
+
errorStack = message.stack;
|
|
2963
|
+
} else {
|
|
2964
|
+
formattedMessage = this.formatMessage(message, args);
|
|
2965
|
+
}
|
|
2966
|
+
const { consoleText: baseConsoleText, fileText } = this.buildOutputTexts(formattedMessage);
|
|
2967
|
+
if (this.shouldStyleConsole()) {
|
|
2968
|
+
const icon = this.options.showIcons === false ? "" : levelIcons2[level];
|
|
2969
|
+
const tag = this.options.showTags !== false && this.name ? styles2.gray(this.formatTag(this.name)) : "";
|
|
2970
|
+
let consoleMessage;
|
|
2971
|
+
switch (level) {
|
|
2972
|
+
case "debug":
|
|
2973
|
+
consoleMessage = this.formatConsoleMessage({
|
|
2974
|
+
timestamp: consoleTime,
|
|
2975
|
+
icon,
|
|
2976
|
+
tag,
|
|
2977
|
+
message: styles2.gray(baseConsoleText),
|
|
2978
|
+
level
|
|
2979
|
+
});
|
|
2980
|
+
console.error(consoleMessage);
|
|
2981
|
+
break;
|
|
2982
|
+
case "info":
|
|
2983
|
+
consoleMessage = this.formatConsoleMessage({
|
|
2984
|
+
timestamp: consoleTime,
|
|
2985
|
+
icon,
|
|
2986
|
+
tag,
|
|
2987
|
+
message: baseConsoleText,
|
|
2988
|
+
level
|
|
2989
|
+
});
|
|
2990
|
+
console.warn(consoleMessage);
|
|
2991
|
+
break;
|
|
2992
|
+
case "success":
|
|
2993
|
+
consoleMessage = this.formatConsoleMessage({
|
|
2994
|
+
timestamp: consoleTime,
|
|
2995
|
+
icon,
|
|
2996
|
+
tag,
|
|
2997
|
+
message: styles2.green(baseConsoleText),
|
|
2998
|
+
level
|
|
2999
|
+
});
|
|
3000
|
+
console.error(consoleMessage);
|
|
3001
|
+
break;
|
|
3002
|
+
case "warning":
|
|
3003
|
+
consoleMessage = this.formatConsoleMessage({
|
|
3004
|
+
timestamp: consoleTime,
|
|
3005
|
+
icon,
|
|
3006
|
+
tag,
|
|
3007
|
+
message: baseConsoleText,
|
|
3008
|
+
level
|
|
3009
|
+
});
|
|
3010
|
+
console.warn(consoleMessage);
|
|
3011
|
+
break;
|
|
3012
|
+
case "error":
|
|
3013
|
+
consoleMessage = this.formatConsoleMessage({
|
|
3014
|
+
timestamp: consoleTime,
|
|
3015
|
+
icon,
|
|
3016
|
+
tag,
|
|
3017
|
+
message: baseConsoleText,
|
|
3018
|
+
level
|
|
3019
|
+
});
|
|
3020
|
+
console.error(consoleMessage);
|
|
3021
|
+
if (errorStack) {
|
|
3022
|
+
const stackLines = errorStack.split(`
|
|
3023
|
+
`);
|
|
3024
|
+
for (const line of stackLines) {
|
|
3025
|
+
if (line.trim() && !line.includes(formattedMessage)) {
|
|
3026
|
+
console.error(this.formatConsoleMessage({
|
|
3027
|
+
timestamp: consoleTime,
|
|
3028
|
+
message: styles2.gray(` ${line}`),
|
|
3029
|
+
level,
|
|
3030
|
+
showTimestamp: false
|
|
3031
|
+
}));
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
break;
|
|
3036
|
+
}
|
|
3037
|
+
} else if (!isBrowserProcess2()) {
|
|
3038
|
+
console.error(`${fileTime} ${this.environment}.${level.toUpperCase()}: ${formattedMessage}`);
|
|
3039
|
+
if (errorStack) {
|
|
3040
|
+
console.error(errorStack);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
if (!this.shouldLog(level))
|
|
3044
|
+
return;
|
|
3045
|
+
let logEntry = `${fileTime} ${this.environment}.${level.toUpperCase()}: ${fileText}
|
|
3046
|
+
`;
|
|
3047
|
+
if (errorStack) {
|
|
3048
|
+
logEntry += `${errorStack}
|
|
3049
|
+
`;
|
|
3050
|
+
}
|
|
3051
|
+
logEntry = logEntry.replace(this.ANSI_PATTERN, "");
|
|
3052
|
+
if (this.shouldWriteToFile())
|
|
3053
|
+
await this.writeToFile(logEntry);
|
|
3054
|
+
}
|
|
3055
|
+
progress(total, initialMessage = "") {
|
|
3056
|
+
const noop = {
|
|
3057
|
+
update: (_current, _message) => {},
|
|
3058
|
+
finish: (_message) => {},
|
|
3059
|
+
interrupt: (_message, _level) => {}
|
|
3060
|
+
};
|
|
3061
|
+
if (!this.enabled)
|
|
3062
|
+
return noop;
|
|
3063
|
+
const barLength = 30;
|
|
3064
|
+
this.activeProgressBar = {
|
|
3065
|
+
total: Math.max(1, total || 1),
|
|
3066
|
+
current: 0,
|
|
3067
|
+
message: initialMessage || "",
|
|
3068
|
+
barLength,
|
|
3069
|
+
lastRenderedLine: ""
|
|
3070
|
+
};
|
|
3071
|
+
if (this.shouldStyleConsole() && !isBrowserProcess2() && process11.stdout.isTTY) {
|
|
3072
|
+
this.renderProgressBar(this.activeProgressBar);
|
|
3073
|
+
}
|
|
3074
|
+
const update = (current, message) => {
|
|
3075
|
+
if (!this.enabled || !this.activeProgressBar)
|
|
3076
|
+
return;
|
|
3077
|
+
this.activeProgressBar.current = Math.min(Math.max(0, current), this.activeProgressBar.total);
|
|
3078
|
+
if (message !== undefined)
|
|
3079
|
+
this.activeProgressBar.message = message;
|
|
3080
|
+
if (this.shouldStyleConsole() && !isBrowserProcess2() && process11.stdout.isTTY)
|
|
3081
|
+
this.renderProgressBar(this.activeProgressBar);
|
|
3082
|
+
};
|
|
3083
|
+
const finish = (message) => {
|
|
3084
|
+
if (!this.activeProgressBar)
|
|
3085
|
+
return;
|
|
3086
|
+
this.finishProgressBar(this.activeProgressBar, message);
|
|
3087
|
+
};
|
|
3088
|
+
const interrupt = (message, level = "info") => {
|
|
3089
|
+
if (!isBrowserProcess2() && process11.stdout.isTTY)
|
|
3090
|
+
process11.stdout.write(`
|
|
3091
|
+
`);
|
|
3092
|
+
const method = level === "warning" ? "warn" : level;
|
|
3093
|
+
this[method](message);
|
|
3094
|
+
if (this.activeProgressBar && this.shouldStyleConsole() && !isBrowserProcess2() && process11.stdout.isTTY)
|
|
3095
|
+
this.renderProgressBar(this.activeProgressBar);
|
|
3096
|
+
};
|
|
3097
|
+
return { update, finish, interrupt };
|
|
3098
|
+
}
|
|
3099
|
+
time(label) {
|
|
3100
|
+
const start = performance.now();
|
|
3101
|
+
if (this.shouldStyleConsole()) {
|
|
3102
|
+
const tag = this.options.showTags !== false && this.name ? styles2.gray(this.formatTag(this.name)) : "";
|
|
3103
|
+
const consoleTime = this.formatConsoleTimestamp(new Date);
|
|
3104
|
+
console.error(this.formatConsoleMessage({
|
|
3105
|
+
timestamp: consoleTime,
|
|
3106
|
+
icon: this.options.showIcons === false ? "" : styles2.blue("\u25D0"),
|
|
3107
|
+
tag,
|
|
3108
|
+
message: `${styles2.cyan(label)}...`
|
|
3109
|
+
}));
|
|
3110
|
+
}
|
|
3111
|
+
return async (metadata) => {
|
|
3112
|
+
if (!this.enabled)
|
|
3113
|
+
return;
|
|
3114
|
+
const end = performance.now();
|
|
3115
|
+
const elapsed = Math.round(end - start);
|
|
3116
|
+
const completionMessage = `${label} completed in ${elapsed}ms`;
|
|
3117
|
+
const timestamp = new Date;
|
|
3118
|
+
const consoleTime = this.formatConsoleTimestamp(timestamp);
|
|
3119
|
+
const fileTime = this.formatFileTimestamp(timestamp);
|
|
3120
|
+
let logEntry = `${fileTime} ${this.environment}.INFO: ${completionMessage}`;
|
|
3121
|
+
if (metadata) {
|
|
3122
|
+
logEntry += ` ${JSON.stringify(metadata)}`;
|
|
3123
|
+
}
|
|
3124
|
+
logEntry += `
|
|
3125
|
+
`;
|
|
3126
|
+
logEntry = logEntry.replace(this.ANSI_PATTERN, "");
|
|
3127
|
+
if (this.shouldStyleConsole()) {
|
|
3128
|
+
const tag = this.options.showTags !== false && this.name ? styles2.gray(this.formatTag(this.name)) : "";
|
|
3129
|
+
console.error(this.formatConsoleMessage({
|
|
3130
|
+
timestamp: consoleTime,
|
|
3131
|
+
icon: this.options.showIcons === false ? "" : styles2.green("\u2713"),
|
|
3132
|
+
tag,
|
|
3133
|
+
message: `${completionMessage}${metadata ? ` ${JSON.stringify(metadata)}` : ""}`
|
|
3134
|
+
}));
|
|
3135
|
+
} else if (!isBrowserProcess2()) {
|
|
3136
|
+
console.error(logEntry.trim());
|
|
3137
|
+
}
|
|
3138
|
+
if (this.shouldWriteToFile())
|
|
3139
|
+
await this.writeToFile(logEntry);
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
async debug(message, ...args) {
|
|
3143
|
+
await this.log("debug", message, ...args);
|
|
3144
|
+
}
|
|
3145
|
+
async info(message, ...args) {
|
|
3146
|
+
await this.log("info", message, ...args);
|
|
3147
|
+
}
|
|
3148
|
+
async success(message, ...args) {
|
|
3149
|
+
await this.log("success", message, ...args);
|
|
3150
|
+
}
|
|
3151
|
+
async warn(message, ...args) {
|
|
3152
|
+
await this.log("warning", message, ...args);
|
|
3153
|
+
}
|
|
3154
|
+
async error(message, ...args) {
|
|
3155
|
+
await this.log("error", message, ...args);
|
|
3156
|
+
}
|
|
3157
|
+
validateEncryptionConfig() {
|
|
3158
|
+
if (!this.config.rotation)
|
|
3159
|
+
return false;
|
|
3160
|
+
if (typeof this.config.rotation === "boolean")
|
|
3161
|
+
return false;
|
|
3162
|
+
const rotation = this.config.rotation;
|
|
3163
|
+
const { encrypt } = rotation;
|
|
3164
|
+
return !!encrypt;
|
|
3165
|
+
}
|
|
3166
|
+
async only(fn) {
|
|
3167
|
+
if (!this.enabled)
|
|
3168
|
+
return;
|
|
3169
|
+
return await fn();
|
|
3170
|
+
}
|
|
3171
|
+
isEnabled() {
|
|
3172
|
+
return this.enabled;
|
|
3173
|
+
}
|
|
3174
|
+
setEnabled(enabled) {
|
|
3175
|
+
this.enabled = enabled;
|
|
3176
|
+
}
|
|
3177
|
+
extend(namespace) {
|
|
3178
|
+
const childName = `${this.name}:${namespace}`;
|
|
3179
|
+
const childLogger = new Logger2(childName, {
|
|
3180
|
+
...this.options,
|
|
3181
|
+
logDirectory: this.config.logDirectory,
|
|
3182
|
+
level: this.config.level,
|
|
3183
|
+
format: this.config.format,
|
|
3184
|
+
rotation: typeof this.config.rotation === "boolean" ? undefined : this.config.rotation,
|
|
3185
|
+
timestamp: typeof this.config.timestamp === "boolean" ? undefined : this.config.timestamp
|
|
3186
|
+
});
|
|
3187
|
+
this.subLoggers.add(childLogger);
|
|
3188
|
+
return childLogger;
|
|
3189
|
+
}
|
|
3190
|
+
createReadStream() {
|
|
3191
|
+
if (isBrowserProcess2())
|
|
3192
|
+
throw new Error("createReadStream is not supported in browser environments");
|
|
3193
|
+
if (!existsSync4(this.currentLogFile))
|
|
3194
|
+
throw new Error(`Log file does not exist: ${this.currentLogFile}`);
|
|
3195
|
+
return createReadStream2(this.currentLogFile, { encoding: "utf8" });
|
|
3196
|
+
}
|
|
3197
|
+
async decrypt(data) {
|
|
3198
|
+
if (!this.validateEncryptionConfig())
|
|
3199
|
+
throw new Error("Encryption is not configured");
|
|
3200
|
+
const encryptionConfig = this.config.rotation;
|
|
3201
|
+
if (!encryptionConfig.encrypt || typeof encryptionConfig.encrypt === "boolean")
|
|
3202
|
+
throw new Error("Invalid encryption configuration");
|
|
3203
|
+
if (!this.currentKeyId || !this.keys.has(this.currentKeyId))
|
|
3204
|
+
throw new Error("No valid encryption key available");
|
|
3205
|
+
const key = this.keys.get(this.currentKeyId);
|
|
3206
|
+
try {
|
|
3207
|
+
const encryptedData = Buffer2.isBuffer(data) ? data : Buffer2.from(data, "base64");
|
|
3208
|
+
const iv = encryptedData.subarray(0, 16);
|
|
3209
|
+
const authTag = encryptedData.subarray(encryptedData.length - 16);
|
|
3210
|
+
const ciphertext = encryptedData.subarray(16, encryptedData.length - 16);
|
|
3211
|
+
const decipher = createDecipheriv2("aes-256-gcm", key, iv);
|
|
3212
|
+
decipher.setAuthTag(authTag);
|
|
3213
|
+
const d1 = decipher.update(ciphertext);
|
|
3214
|
+
const d2 = decipher.final();
|
|
3215
|
+
const totalLen = d1.length + d2.length;
|
|
3216
|
+
const out = Buffer2.allocUnsafe(totalLen);
|
|
3217
|
+
d1.copy(out, 0);
|
|
3218
|
+
d2.copy(out, d1.length);
|
|
3219
|
+
return out.toString("utf8");
|
|
3220
|
+
} catch (err) {
|
|
3221
|
+
throw new Error(`Decryption failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
getLevel() {
|
|
3225
|
+
return this.config.level;
|
|
3226
|
+
}
|
|
3227
|
+
getLogDirectory() {
|
|
3228
|
+
return this.config.logDirectory;
|
|
3229
|
+
}
|
|
3230
|
+
getFormat() {
|
|
3231
|
+
return this.config.format;
|
|
3232
|
+
}
|
|
3233
|
+
getRotationConfig() {
|
|
3234
|
+
return this.config.rotation;
|
|
3235
|
+
}
|
|
3236
|
+
isBrowserMode() {
|
|
3237
|
+
return isBrowserProcess2();
|
|
3238
|
+
}
|
|
3239
|
+
isServerMode() {
|
|
3240
|
+
return !isBrowserProcess2();
|
|
3241
|
+
}
|
|
3242
|
+
setTestEncryptionKey(keyId, key) {
|
|
3243
|
+
this.currentKeyId = keyId;
|
|
3244
|
+
this.keys.set(keyId, key);
|
|
3245
|
+
}
|
|
3246
|
+
getTestCurrentKey() {
|
|
3247
|
+
if (!this.currentKeyId || !this.keys.has(this.currentKeyId)) {
|
|
3248
|
+
return null;
|
|
3249
|
+
}
|
|
3250
|
+
return {
|
|
3251
|
+
id: this.currentKeyId,
|
|
3252
|
+
key: this.keys.get(this.currentKeyId)
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
getConfig() {
|
|
3256
|
+
return this.config;
|
|
3257
|
+
}
|
|
3258
|
+
async box(message) {
|
|
3259
|
+
if (!this.enabled)
|
|
3260
|
+
return;
|
|
3261
|
+
const timestamp = new Date;
|
|
3262
|
+
const consoleTime = this.formatConsoleTimestamp(timestamp);
|
|
3263
|
+
const fileTime = this.formatFileTimestamp(timestamp);
|
|
3264
|
+
const { consoleText, fileText } = this.buildOutputTexts(message);
|
|
3265
|
+
if (this.shouldStyleConsole()) {
|
|
3266
|
+
const lines = consoleText.split(`
|
|
3267
|
+
`);
|
|
3268
|
+
const width = Math.max(...lines.map((line) => line.length)) + 2;
|
|
3269
|
+
const top = `\u250C${"\u2500".repeat(width)}\u2510`;
|
|
3270
|
+
const bottom = `\u2514${"\u2500".repeat(width)}\u2518`;
|
|
3271
|
+
const boxedLines = lines.map((line) => {
|
|
3272
|
+
return this.formatConsoleMessage({
|
|
3273
|
+
timestamp: consoleTime,
|
|
3274
|
+
message: styles2.cyan(line),
|
|
3275
|
+
showTimestamp: false
|
|
3276
|
+
});
|
|
3277
|
+
});
|
|
3278
|
+
console.error(this.formatConsoleMessage({
|
|
3279
|
+
timestamp: consoleTime,
|
|
3280
|
+
message: styles2.cyan(top),
|
|
3281
|
+
showTimestamp: false
|
|
3282
|
+
}));
|
|
3283
|
+
boxedLines.forEach((line) => console.error(line));
|
|
3284
|
+
console.error(this.formatConsoleMessage({
|
|
3285
|
+
timestamp: consoleTime,
|
|
3286
|
+
message: styles2.cyan(bottom),
|
|
3287
|
+
showTimestamp: false
|
|
3288
|
+
}));
|
|
3289
|
+
} else if (!isBrowserProcess2()) {
|
|
3290
|
+
console.error(`${fileTime} ${this.environment}.INFO: [BOX] ${fileText}`);
|
|
3291
|
+
}
|
|
3292
|
+
const logEntry = `${fileTime} ${this.environment}.INFO: [BOX] ${fileText}
|
|
3293
|
+
`.replace(this.ANSI_PATTERN, "");
|
|
3294
|
+
if (this.shouldWriteToFile())
|
|
3295
|
+
await this.writeToFile(logEntry);
|
|
3296
|
+
}
|
|
3297
|
+
async prompt(message) {
|
|
3298
|
+
if (isBrowserProcess2()) {
|
|
3299
|
+
return Promise.resolve(true);
|
|
3300
|
+
}
|
|
3301
|
+
return new Promise((resolve52) => {
|
|
3302
|
+
console.error(`${styles2.cyan("?")} ${message} (y/n) `);
|
|
3303
|
+
const onData = (data) => {
|
|
3304
|
+
const input = data.toString().trim().toLowerCase();
|
|
3305
|
+
process11.stdin.removeListener("data", onData);
|
|
3306
|
+
try {
|
|
3307
|
+
if (typeof process11.stdin.setRawMode === "function") {
|
|
3308
|
+
process11.stdin.setRawMode(false);
|
|
3309
|
+
}
|
|
3310
|
+
} catch {}
|
|
3311
|
+
process11.stdin.pause();
|
|
3312
|
+
console.error("");
|
|
3313
|
+
resolve52(input === "y" || input === "yes");
|
|
3314
|
+
};
|
|
3315
|
+
try {
|
|
3316
|
+
if (typeof process11.stdin.setRawMode === "function") {
|
|
3317
|
+
process11.stdin.setRawMode(true);
|
|
3318
|
+
}
|
|
3319
|
+
} catch {}
|
|
3320
|
+
process11.stdin.resume();
|
|
3321
|
+
process11.stdin.once("data", onData);
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
setFancy(enabled) {
|
|
3325
|
+
this.fancy = enabled;
|
|
3326
|
+
}
|
|
3327
|
+
isFancy() {
|
|
3328
|
+
return this.fancy;
|
|
3329
|
+
}
|
|
3330
|
+
pause() {
|
|
3331
|
+
this.enabled = false;
|
|
3332
|
+
}
|
|
3333
|
+
resume() {
|
|
3334
|
+
this.enabled = true;
|
|
3335
|
+
}
|
|
3336
|
+
async start(message, ...args) {
|
|
3337
|
+
if (!this.enabled)
|
|
3338
|
+
return;
|
|
3339
|
+
let formattedMessage = message;
|
|
3340
|
+
if (args && args.length > 0) {
|
|
3341
|
+
const formatRegex = /%([sdijfo%])/g;
|
|
3342
|
+
let argIndex = 0;
|
|
3343
|
+
formattedMessage = message.replace(formatRegex, (match, type) => {
|
|
3344
|
+
if (type === "%")
|
|
3345
|
+
return "%";
|
|
3346
|
+
if (argIndex >= args.length)
|
|
3347
|
+
return match;
|
|
3348
|
+
const arg = args[argIndex++];
|
|
3349
|
+
switch (type) {
|
|
3350
|
+
case "s":
|
|
3351
|
+
return String(arg);
|
|
3352
|
+
case "d":
|
|
3353
|
+
case "i":
|
|
3354
|
+
return Number(arg).toString();
|
|
3355
|
+
case "j":
|
|
3356
|
+
case "o":
|
|
3357
|
+
return JSON.stringify(arg, null, 2);
|
|
3358
|
+
default:
|
|
3359
|
+
return match;
|
|
3360
|
+
}
|
|
3361
|
+
});
|
|
3362
|
+
if (argIndex < args.length) {
|
|
3363
|
+
formattedMessage += ` ${args.slice(argIndex).map((arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg)).join(" ")}`;
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
const { consoleText, fileText } = this.buildOutputTexts(formattedMessage);
|
|
3367
|
+
if (this.shouldStyleConsole()) {
|
|
3368
|
+
const tag = this.options.showTags !== false && this.name ? styles2.gray(this.formatTag(this.name)) : "";
|
|
3369
|
+
const spinnerPrefix = this.options.showIcons === false ? "" : `${styles2.blue("\u25D0")} `;
|
|
3370
|
+
console.error(`${spinnerPrefix}${tag} ${styles2.cyan(consoleText)}`);
|
|
3371
|
+
}
|
|
3372
|
+
const timestamp = new Date;
|
|
3373
|
+
const formattedDate = timestamp.toISOString();
|
|
3374
|
+
const logEntry = `[${formattedDate}] ${this.environment}.INFO: [START] ${fileText}
|
|
3375
|
+
`.replace(this.ANSI_PATTERN, "");
|
|
3376
|
+
if (this.shouldWriteToFile())
|
|
3377
|
+
await this.writeToFile(logEntry);
|
|
3378
|
+
}
|
|
3379
|
+
renderProgressBar(barState, isFinished = false) {
|
|
3380
|
+
if (!this.enabled || !this.shouldStyleConsole() || !process11.stdout.isTTY)
|
|
3381
|
+
return;
|
|
3382
|
+
const percent = Math.min(100, Math.max(0, Math.round(barState.current / barState.total * 100)));
|
|
3383
|
+
const filledLength = Math.round(barState.barLength * percent / 100);
|
|
3384
|
+
const emptyLength = barState.barLength - filledLength;
|
|
3385
|
+
const filledBar = styles2.green("\u2501".repeat(filledLength));
|
|
3386
|
+
const emptyBar = styles2.gray("\u2501".repeat(emptyLength));
|
|
3387
|
+
const bar = `[${filledBar}${emptyBar}]`;
|
|
3388
|
+
const percentageText = `${percent}%`.padStart(4);
|
|
3389
|
+
const messageText = barState.message ? ` ${barState.message}` : "";
|
|
3390
|
+
const icon = this.options.showIcons === false ? "" : isFinished || percent === 100 ? styles2.green("\u2713") : styles2.blue("\u25B6");
|
|
3391
|
+
const tag = this.options.showTags !== false && this.name ? ` ${styles2.gray(this.formatTag(this.name))}` : "";
|
|
3392
|
+
const line = `\r${icon}${tag} ${bar} ${percentageText}${messageText}`;
|
|
3393
|
+
const terminalWidth = process11.stdout.columns || 80;
|
|
3394
|
+
const clearLine = " ".repeat(Math.max(0, terminalWidth - line.replace(this.ANSI_PATTERN, "").length));
|
|
3395
|
+
barState.lastRenderedLine = `${line}${clearLine}`;
|
|
3396
|
+
process11.stdout.write(barState.lastRenderedLine);
|
|
3397
|
+
if (isFinished) {
|
|
3398
|
+
process11.stdout.write(`
|
|
3399
|
+
`);
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
finishProgressBar(barState, finalMessage) {
|
|
3403
|
+
if (!this.enabled || !this.fancy || isBrowserProcess2() || !process11.stdout.isTTY) {
|
|
3404
|
+
this.activeProgressBar = null;
|
|
3405
|
+
return;
|
|
3406
|
+
}
|
|
3407
|
+
if (barState.current < barState.total) {
|
|
3408
|
+
barState.current = barState.total;
|
|
3409
|
+
}
|
|
3410
|
+
if (finalMessage)
|
|
3411
|
+
barState.message = finalMessage;
|
|
3412
|
+
this.renderProgressBar(barState, true);
|
|
3413
|
+
this.activeProgressBar = null;
|
|
3414
|
+
}
|
|
3415
|
+
async clear(filters = {}) {
|
|
3416
|
+
if (isBrowserProcess2()) {
|
|
3417
|
+
console.warn("Log clearing is not supported in browser environments.");
|
|
3418
|
+
return;
|
|
3419
|
+
}
|
|
3420
|
+
try {
|
|
3421
|
+
console.warn("Clearing logs...", this.config.logDirectory);
|
|
3422
|
+
const files = await readdir2(this.config.logDirectory);
|
|
3423
|
+
const logFilesToDelete = [];
|
|
3424
|
+
for (const file of files) {
|
|
3425
|
+
const nameMatches = filters.name ? new RegExp(filters.name.replace("*", ".*")).test(file) : file.startsWith(this.name);
|
|
3426
|
+
if (!nameMatches || !file.endsWith(".log")) {
|
|
3427
|
+
continue;
|
|
3428
|
+
}
|
|
3429
|
+
const filePath = join5(this.config.logDirectory, file);
|
|
3430
|
+
if (filters.before) {
|
|
3431
|
+
try {
|
|
3432
|
+
const fileStats = await stat2(filePath);
|
|
3433
|
+
if (fileStats.mtime >= filters.before) {
|
|
3434
|
+
continue;
|
|
3435
|
+
}
|
|
3436
|
+
} catch (statErr) {
|
|
3437
|
+
console.error(`Failed to get stats for file ${filePath}:`, statErr);
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
logFilesToDelete.push(filePath);
|
|
3442
|
+
}
|
|
3443
|
+
if (logFilesToDelete.length === 0) {
|
|
3444
|
+
console.warn("No log files matched the criteria for clearing.");
|
|
3445
|
+
return;
|
|
3446
|
+
}
|
|
3447
|
+
console.warn(`Preparing to delete ${logFilesToDelete.length} log file(s)...`);
|
|
3448
|
+
for (const filePath of logFilesToDelete) {
|
|
3449
|
+
try {
|
|
3450
|
+
await unlink2(filePath);
|
|
3451
|
+
console.warn(`Deleted log file: ${filePath}`);
|
|
3452
|
+
} catch (unlinkErr) {
|
|
3453
|
+
console.error(`Failed to delete log file ${filePath}:`, unlinkErr);
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
console.warn("Log clearing process finished.");
|
|
3457
|
+
} catch (err) {
|
|
3458
|
+
console.error("Error during log clearing process:", err);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
var logger2 = new Logger2("stacks");
|
|
3463
|
+
class BunfigError extends Error {
|
|
3464
|
+
timestamp;
|
|
3465
|
+
context;
|
|
3466
|
+
constructor(message, context = {}) {
|
|
3467
|
+
super(message);
|
|
3468
|
+
this.name = this.constructor.name;
|
|
3469
|
+
this.timestamp = new Date;
|
|
3470
|
+
this.context = context;
|
|
3471
|
+
if (Error.captureStackTrace) {
|
|
3472
|
+
Error.captureStackTrace(this, this.constructor);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
toJSON() {
|
|
3476
|
+
return {
|
|
3477
|
+
name: this.name,
|
|
3478
|
+
code: this.code,
|
|
3479
|
+
message: this.message,
|
|
3480
|
+
timestamp: this.timestamp.toISOString(),
|
|
3481
|
+
context: this.context,
|
|
3482
|
+
stack: this.stack
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
toString() {
|
|
3486
|
+
const contextStr = Object.keys(this.context).length > 0 ? ` (${Object.entries(this.context).map(([k, v]) => `${k}: ${v}`).join(", ")})` : "";
|
|
3487
|
+
return `${this.name} [${this.code}]: ${this.message}${contextStr}`;
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
class ConfigNotFoundError extends BunfigError {
|
|
3492
|
+
code = "CONFIG_NOT_FOUND";
|
|
3493
|
+
constructor(configName, searchPaths, alias) {
|
|
3494
|
+
const aliasStr = alias ? ` or alias "${alias}"` : "";
|
|
3495
|
+
super(`Configuration "${configName}"${aliasStr} not found`, {
|
|
3496
|
+
configName,
|
|
3497
|
+
alias,
|
|
3498
|
+
searchPaths,
|
|
3499
|
+
searchPathCount: searchPaths.length
|
|
3500
|
+
});
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
class ConfigLoadError extends BunfigError {
|
|
3505
|
+
code = "CONFIG_LOAD_ERROR";
|
|
3506
|
+
constructor(configPath, cause, configName) {
|
|
3507
|
+
super(`Failed to load configuration from "${configPath}": ${cause.message}`, {
|
|
3508
|
+
configPath,
|
|
3509
|
+
configName,
|
|
3510
|
+
originalError: cause.name,
|
|
3511
|
+
originalMessage: cause.message
|
|
3512
|
+
});
|
|
3513
|
+
this.cause = cause;
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
class ConfigValidationError extends BunfigError {
|
|
3518
|
+
code = "CONFIG_VALIDATION_ERROR";
|
|
3519
|
+
constructor(configPath, validationErrors, configName) {
|
|
3520
|
+
super(`Configuration validation failed for "${configPath}"`, {
|
|
3521
|
+
configPath,
|
|
3522
|
+
configName,
|
|
3523
|
+
validationErrors,
|
|
3524
|
+
errorCount: validationErrors.length
|
|
3525
|
+
});
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
class ConfigMergeError extends BunfigError {
|
|
3530
|
+
code = "CONFIG_MERGE_ERROR";
|
|
3531
|
+
constructor(sourcePath, targetPath, cause, configName) {
|
|
3532
|
+
super(`Failed to merge configuration from "${sourcePath}" with "${targetPath}": ${cause.message}`, {
|
|
3533
|
+
sourcePath,
|
|
3534
|
+
targetPath,
|
|
3535
|
+
configName,
|
|
3536
|
+
originalError: cause.name,
|
|
3537
|
+
originalMessage: cause.message
|
|
3538
|
+
});
|
|
3539
|
+
this.cause = cause;
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
class EnvVarError extends BunfigError {
|
|
3544
|
+
code = "ENV_VAR_ERROR";
|
|
3545
|
+
constructor(envKey, envValue, expectedType, configName) {
|
|
3546
|
+
super(`Failed to parse environment variable "${envKey}" with value "${envValue}" as ${expectedType}`, {
|
|
3547
|
+
envKey,
|
|
3548
|
+
envValue,
|
|
3549
|
+
expectedType,
|
|
3550
|
+
configName
|
|
3551
|
+
});
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
class FileSystemError extends BunfigError {
|
|
3556
|
+
code = "FILE_SYSTEM_ERROR";
|
|
3557
|
+
constructor(operation, path, cause) {
|
|
3558
|
+
super(`File system ${operation} failed for "${path}": ${cause.message}`, {
|
|
3559
|
+
operation,
|
|
3560
|
+
path,
|
|
3561
|
+
originalError: cause.name,
|
|
3562
|
+
originalMessage: cause.message
|
|
3563
|
+
});
|
|
3564
|
+
this.cause = cause;
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
class TypeGenerationError extends BunfigError {
|
|
3569
|
+
code = "TYPE_GENERATION_ERROR";
|
|
3570
|
+
constructor(configDir, outputPath, cause) {
|
|
3571
|
+
super(`Failed to generate types from "${configDir}" to "${outputPath}": ${cause.message}`, {
|
|
3572
|
+
configDir,
|
|
3573
|
+
outputPath,
|
|
3574
|
+
originalError: cause.name,
|
|
3575
|
+
originalMessage: cause.message
|
|
3576
|
+
});
|
|
3577
|
+
this.cause = cause;
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
class SchemaValidationError extends BunfigError {
|
|
3582
|
+
code = "SCHEMA_VALIDATION_ERROR";
|
|
3583
|
+
constructor(schemaPath, validationErrors, configName) {
|
|
3584
|
+
super(`Schema validation failed${configName ? ` for config "${configName}"` : ""}`, {
|
|
3585
|
+
schemaPath,
|
|
3586
|
+
configName,
|
|
3587
|
+
validationErrors,
|
|
3588
|
+
errorCount: validationErrors.length
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
class BrowserConfigError extends BunfigError {
|
|
3594
|
+
code = "BROWSER_CONFIG_ERROR";
|
|
3595
|
+
constructor(endpoint, status, statusText, configName) {
|
|
3596
|
+
super(`Failed to fetch configuration from "${endpoint}": ${status} ${statusText}`, {
|
|
3597
|
+
endpoint,
|
|
3598
|
+
status,
|
|
3599
|
+
statusText,
|
|
3600
|
+
configName
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
class PluginError extends BunfigError {
|
|
3606
|
+
code = "PLUGIN_ERROR";
|
|
3607
|
+
constructor(pluginName, operation, cause) {
|
|
3608
|
+
super(`Plugin "${pluginName}" failed during ${operation}: ${cause.message}`, {
|
|
3609
|
+
pluginName,
|
|
3610
|
+
operation,
|
|
3611
|
+
originalError: cause.name,
|
|
3612
|
+
originalMessage: cause.message
|
|
3613
|
+
});
|
|
3614
|
+
this.cause = cause;
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
var ErrorFactory = {
|
|
3618
|
+
configNotFound(configName, searchPaths, alias) {
|
|
3619
|
+
return new ConfigNotFoundError(configName, searchPaths, alias);
|
|
3620
|
+
},
|
|
3621
|
+
configLoad(configPath, cause, configName) {
|
|
3622
|
+
return new ConfigLoadError(configPath, cause, configName);
|
|
3623
|
+
},
|
|
3624
|
+
configValidation(configPath, errors, configName) {
|
|
3625
|
+
return new ConfigValidationError(configPath, errors, configName);
|
|
3626
|
+
},
|
|
3627
|
+
configMerge(sourcePath, targetPath, cause, configName) {
|
|
3628
|
+
return new ConfigMergeError(sourcePath, targetPath, cause, configName);
|
|
3629
|
+
},
|
|
3630
|
+
envVar(envKey, envValue, expectedType, configName) {
|
|
3631
|
+
return new EnvVarError(envKey, envValue, expectedType, configName);
|
|
3632
|
+
},
|
|
3633
|
+
fileSystem(operation, path, cause) {
|
|
3634
|
+
return new FileSystemError(operation, path, cause);
|
|
3635
|
+
},
|
|
3636
|
+
typeGeneration(configDir, outputPath, cause) {
|
|
3637
|
+
return new TypeGenerationError(configDir, outputPath, cause);
|
|
3638
|
+
},
|
|
3639
|
+
schemaValidation(schemaPath, errors, configName) {
|
|
3640
|
+
return new SchemaValidationError(schemaPath, errors, configName);
|
|
3641
|
+
},
|
|
3642
|
+
browserConfig(endpoint, status, statusText, configName) {
|
|
3643
|
+
return new BrowserConfigError(endpoint, status, statusText, configName);
|
|
3644
|
+
},
|
|
3645
|
+
plugin(pluginName, operation, cause) {
|
|
3646
|
+
return new PluginError(pluginName, operation, cause);
|
|
3647
|
+
}
|
|
3648
|
+
};
|
|
3649
|
+
async function withErrorRecovery(fn, options = {}) {
|
|
3650
|
+
const {
|
|
3651
|
+
maxRetries = 3,
|
|
3652
|
+
retryDelay = 1000,
|
|
3653
|
+
isRetryable = () => true,
|
|
3654
|
+
fallback
|
|
3655
|
+
} = options;
|
|
3656
|
+
let lastError = new Error("Unknown error occurred");
|
|
3657
|
+
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
3658
|
+
try {
|
|
3659
|
+
return await fn();
|
|
3660
|
+
} catch (error) {
|
|
3661
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3662
|
+
if (attempt === maxRetries || !isRetryable(lastError)) {
|
|
3663
|
+
break;
|
|
3664
|
+
}
|
|
3665
|
+
if (retryDelay > 0) {
|
|
3666
|
+
await new Promise((resolve52) => setTimeout(resolve52, retryDelay));
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
if (fallback !== undefined) {
|
|
3671
|
+
return fallback;
|
|
3672
|
+
}
|
|
3673
|
+
throw lastError instanceof Error ? lastError : new Error(`Unknown error: ${String(lastError)}`);
|
|
3674
|
+
}
|
|
3675
|
+
class EnvProcessor {
|
|
3676
|
+
defaultParsers;
|
|
3677
|
+
constructor() {
|
|
3678
|
+
this.defaultParsers = [
|
|
3679
|
+
{
|
|
3680
|
+
name: "boolean",
|
|
3681
|
+
canParse: (value, expectedType) => expectedType === "boolean" || ["true", "false", "1", "0", "yes", "no"].includes(value.toLowerCase()),
|
|
3682
|
+
parse: (value) => {
|
|
3683
|
+
const lower = value.toLowerCase();
|
|
3684
|
+
return ["true", "1", "yes"].includes(lower);
|
|
3685
|
+
}
|
|
3686
|
+
},
|
|
3687
|
+
{
|
|
3688
|
+
name: "number",
|
|
3689
|
+
canParse: (value, expectedType) => expectedType === "number" || !Number.isNaN(Number(value)) && !Number.isNaN(Number.parseFloat(value)),
|
|
3690
|
+
parse: (value) => {
|
|
3691
|
+
const num = Number(value);
|
|
3692
|
+
if (Number.isNaN(num)) {
|
|
3693
|
+
throw new TypeError(`Cannot parse "${value}" as number`);
|
|
3694
|
+
}
|
|
3695
|
+
return num;
|
|
3696
|
+
}
|
|
3697
|
+
},
|
|
3698
|
+
{
|
|
3699
|
+
name: "array",
|
|
3700
|
+
canParse: (value, expectedType) => expectedType === "array" || value.startsWith("[") || value.includes(","),
|
|
3701
|
+
parse: (value) => {
|
|
3702
|
+
try {
|
|
3703
|
+
const parsed = JSON.parse(value);
|
|
3704
|
+
if (Array.isArray(parsed)) {
|
|
3705
|
+
return parsed;
|
|
3706
|
+
}
|
|
3707
|
+
} catch {}
|
|
3708
|
+
return value.split(",").map((item) => item.trim());
|
|
3709
|
+
}
|
|
3710
|
+
},
|
|
3711
|
+
{
|
|
3712
|
+
name: "json",
|
|
3713
|
+
canParse: (value, expectedType) => expectedType === "object" || (value.startsWith("{") && value.endsWith("}") || value.startsWith("[") && value.endsWith("]")),
|
|
3714
|
+
parse: (value) => {
|
|
3715
|
+
try {
|
|
3716
|
+
return JSON.parse(value);
|
|
3717
|
+
} catch (error) {
|
|
3718
|
+
throw new Error(`Cannot parse "${value}" as JSON: ${error}`);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
];
|
|
3723
|
+
}
|
|
3724
|
+
async applyEnvironmentVariables(configName, config3, options = {}) {
|
|
3725
|
+
const {
|
|
3726
|
+
prefix,
|
|
3727
|
+
useCamelCase = true,
|
|
3728
|
+
useBackwardCompatibility = true,
|
|
3729
|
+
customParsers = {},
|
|
3730
|
+
verbose = false,
|
|
3731
|
+
trackPerformance = true
|
|
3732
|
+
} = options;
|
|
3733
|
+
const operation = async () => {
|
|
3734
|
+
if (!configName) {
|
|
3735
|
+
return {
|
|
3736
|
+
config: config3,
|
|
3737
|
+
source: { type: "environment", priority: 50, timestamp: new Date }
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
const envPrefix = prefix || this.generateEnvPrefix(configName);
|
|
3741
|
+
const result = { ...config3 };
|
|
3742
|
+
this.processObject(result, [], envPrefix, {
|
|
3743
|
+
useCamelCase,
|
|
3744
|
+
useBackwardCompatibility,
|
|
3745
|
+
customParsers,
|
|
3746
|
+
verbose,
|
|
3747
|
+
configName
|
|
3748
|
+
});
|
|
3749
|
+
const source = {
|
|
3750
|
+
type: "environment",
|
|
3751
|
+
priority: 50,
|
|
3752
|
+
timestamp: new Date
|
|
3753
|
+
};
|
|
3754
|
+
return { config: result, source };
|
|
3755
|
+
};
|
|
3756
|
+
if (trackPerformance) {
|
|
3757
|
+
return globalPerformanceMonitor.track("applyEnvironmentVariables", operation, { configName });
|
|
3758
|
+
}
|
|
3759
|
+
return operation();
|
|
3760
|
+
}
|
|
3761
|
+
generateEnvPrefix(configName) {
|
|
3762
|
+
return configName.toUpperCase().replace(/-/g, "_");
|
|
3763
|
+
}
|
|
3764
|
+
formatEnvKey(key, useCamelCase) {
|
|
3765
|
+
if (!useCamelCase) {
|
|
3766
|
+
return key.toUpperCase();
|
|
3767
|
+
}
|
|
3768
|
+
return key.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
3769
|
+
}
|
|
3770
|
+
processObject(obj, path, envPrefix, options) {
|
|
3771
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3772
|
+
const envPath = [...path, key];
|
|
3773
|
+
const formattedKeys = envPath.map((k) => this.formatEnvKey(k, options.useCamelCase));
|
|
3774
|
+
const envKey = `${envPrefix}_${formattedKeys.join("_")}`;
|
|
3775
|
+
const oldEnvKey = options.useBackwardCompatibility ? `${envPrefix}_${envPath.map((p) => p.toUpperCase()).join("_")}` : null;
|
|
3776
|
+
if (options.verbose) {}
|
|
3777
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3778
|
+
this.processObject(value, envPath, envPrefix, options);
|
|
3779
|
+
} else {
|
|
3780
|
+
const envValue = process8.env[envKey] || (oldEnvKey ? process8.env[oldEnvKey] : undefined);
|
|
3781
|
+
if (envValue !== undefined) {
|
|
3782
|
+
if (options.verbose) {
|
|
3783
|
+
const _usedKey = process8.env[envKey] ? envKey : oldEnvKey;
|
|
3784
|
+
}
|
|
3785
|
+
try {
|
|
3786
|
+
obj[key] = this.parseEnvironmentValue(envValue, typeof value, envKey, options.customParsers, options.configName);
|
|
3787
|
+
} catch (error) {
|
|
3788
|
+
if (error instanceof EnvVarError) {
|
|
3789
|
+
throw error;
|
|
3790
|
+
}
|
|
3791
|
+
throw ErrorFactory.envVar(envKey, envValue, typeof value, options.configName);
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
parseEnvironmentValue(envValue, expectedType, envKey, customParsers, configName) {
|
|
3798
|
+
for (const [_parserName, parser] of Object.entries(customParsers)) {
|
|
3799
|
+
try {
|
|
3800
|
+
return parser(envValue);
|
|
3801
|
+
} catch {
|
|
3802
|
+
continue;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
for (const parser of this.defaultParsers) {
|
|
3806
|
+
if (parser.canParse(envValue, expectedType)) {
|
|
3807
|
+
try {
|
|
3808
|
+
return parser.parse(envValue);
|
|
3809
|
+
} catch {
|
|
3810
|
+
throw ErrorFactory.envVar(envKey, envValue, `${expectedType} (via ${parser.name} parser)`, configName);
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
return envValue;
|
|
3815
|
+
}
|
|
3816
|
+
getEnvironmentVariables(prefix) {
|
|
3817
|
+
const envVars = {};
|
|
3818
|
+
const upperPrefix = prefix.toUpperCase();
|
|
3819
|
+
for (const [key, value] of Object.entries(process8.env)) {
|
|
3820
|
+
if (key.startsWith(upperPrefix) && value !== undefined) {
|
|
3821
|
+
envVars[key] = value;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
return envVars;
|
|
3825
|
+
}
|
|
3826
|
+
validateEnvironmentVariable(key, value, expectedType) {
|
|
3827
|
+
const errors = [];
|
|
3828
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(key)) {
|
|
3829
|
+
errors.push(`Environment variable key "${key}" should only contain uppercase letters, numbers, and underscores`);
|
|
3830
|
+
}
|
|
3831
|
+
if (expectedType) {
|
|
3832
|
+
try {
|
|
3833
|
+
this.parseEnvironmentValue(key, value, expectedType, {});
|
|
3834
|
+
} catch (error) {
|
|
3835
|
+
errors.push(`Cannot parse value "${value}" as ${expectedType}: ${error}`);
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
return {
|
|
3839
|
+
isValid: errors.length === 0,
|
|
3840
|
+
errors
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3843
|
+
generateEnvVarDocs(configName, defaultConfig3, options = {}) {
|
|
3844
|
+
const { prefix, format = "text" } = options;
|
|
3845
|
+
const envPrefix = prefix || this.generateEnvPrefix(configName);
|
|
3846
|
+
const envVars = [];
|
|
3847
|
+
this.extractEnvVarInfo(defaultConfig3, [], envPrefix, envVars);
|
|
3848
|
+
switch (format) {
|
|
3849
|
+
case "markdown":
|
|
3850
|
+
return this.formatAsMarkdown(envVars, configName);
|
|
3851
|
+
case "json":
|
|
3852
|
+
return JSON.stringify(envVars, null, 2);
|
|
3853
|
+
default:
|
|
3854
|
+
return this.formatAsText(envVars, configName);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
extractEnvVarInfo(obj, path, prefix, envVars) {
|
|
3858
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3859
|
+
const envPath = [...path, key];
|
|
3860
|
+
const envKey = `${prefix}_${envPath.map((k) => this.formatEnvKey(k, true)).join("_")}`;
|
|
3861
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3862
|
+
this.extractEnvVarInfo(value, envPath, prefix, envVars);
|
|
3863
|
+
} else {
|
|
3864
|
+
envVars.push({
|
|
3865
|
+
key: envKey,
|
|
3866
|
+
type: Array.isArray(value) ? "array" : typeof value,
|
|
3867
|
+
description: `Configuration for ${envPath.join(".")}`,
|
|
3868
|
+
example: this.generateExample(value)
|
|
3869
|
+
});
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
generateExample(value) {
|
|
3874
|
+
if (Array.isArray(value)) {
|
|
3875
|
+
return JSON.stringify(value);
|
|
3876
|
+
}
|
|
3877
|
+
if (typeof value === "object" && value !== null) {
|
|
3878
|
+
return JSON.stringify(value);
|
|
3879
|
+
}
|
|
3880
|
+
return String(value);
|
|
3881
|
+
}
|
|
3882
|
+
formatAsText(envVars, configName) {
|
|
3883
|
+
let result = `Environment Variables for ${configName}:
|
|
3884
|
+
|
|
3885
|
+
`;
|
|
3886
|
+
for (const envVar of envVars) {
|
|
3887
|
+
result += `${envVar.key}
|
|
3888
|
+
`;
|
|
3889
|
+
result += ` Type: ${envVar.type}
|
|
3890
|
+
`;
|
|
3891
|
+
result += ` Description: ${envVar.description}
|
|
3892
|
+
`;
|
|
3893
|
+
result += ` Example: ${envVar.example}
|
|
3894
|
+
|
|
3895
|
+
`;
|
|
3896
|
+
}
|
|
3897
|
+
return result;
|
|
3898
|
+
}
|
|
3899
|
+
formatAsMarkdown(envVars, configName) {
|
|
3900
|
+
let result = `# Environment Variables for ${configName}
|
|
3901
|
+
|
|
3902
|
+
`;
|
|
3903
|
+
result += `| Variable | Type | Description | Example |
|
|
3904
|
+
`;
|
|
3905
|
+
result += `|----------|------|-------------|----------|
|
|
3906
|
+
`;
|
|
3907
|
+
for (const envVar of envVars) {
|
|
3908
|
+
result += `| \`${envVar.key}\` | ${envVar.type} | ${envVar.description} | \`${envVar.example}\` |
|
|
3909
|
+
`;
|
|
3910
|
+
}
|
|
3911
|
+
return result;
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
function deepMerge3(target, source, options = {}) {
|
|
3915
|
+
const visited = new WeakMap;
|
|
3916
|
+
return deepMergeWithVisited(target, source, options, visited);
|
|
3917
|
+
}
|
|
3918
|
+
function deepMergeWithVisited(target, source, options, visited) {
|
|
3919
|
+
const {
|
|
3920
|
+
arrayMergeMode = "replace",
|
|
3921
|
+
skipNullish = false,
|
|
3922
|
+
customMerger
|
|
3923
|
+
} = options;
|
|
3924
|
+
if (source === null || source === undefined) {
|
|
3925
|
+
return skipNullish ? target : source;
|
|
3926
|
+
}
|
|
3927
|
+
if (customMerger) {
|
|
3928
|
+
const customResult = customMerger(target, source);
|
|
3929
|
+
if (customResult !== undefined) {
|
|
3930
|
+
return customResult;
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
if (Array.isArray(source) || Array.isArray(target)) {
|
|
3934
|
+
return mergeArraysWithVisited(target, source, arrayMergeMode, visited);
|
|
3935
|
+
}
|
|
3936
|
+
if (!isObject3(source) || !isObject3(target)) {
|
|
3937
|
+
return source;
|
|
3938
|
+
}
|
|
3939
|
+
return mergeObjectsWithVisited(target, source, options, visited);
|
|
3940
|
+
}
|
|
3941
|
+
function mergeArraysWithVisited(target, source, mode, visited) {
|
|
3942
|
+
if (Array.isArray(source) && !Array.isArray(target)) {
|
|
3943
|
+
return source;
|
|
3944
|
+
}
|
|
3945
|
+
if (Array.isArray(target) && !Array.isArray(source)) {
|
|
3946
|
+
return source;
|
|
3947
|
+
}
|
|
3948
|
+
if (Array.isArray(source) && Array.isArray(target)) {
|
|
3949
|
+
switch (mode) {
|
|
3950
|
+
case "replace":
|
|
3951
|
+
return source;
|
|
3952
|
+
case "concat":
|
|
3953
|
+
return concatArraysWithDedup(target, source);
|
|
3954
|
+
case "smart":
|
|
3955
|
+
return smartMergeArraysWithVisited(target, source, visited);
|
|
3956
|
+
default:
|
|
3957
|
+
return source;
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
return source;
|
|
3961
|
+
}
|
|
3962
|
+
function concatArraysWithDedup(target, source) {
|
|
3963
|
+
const result = [...source];
|
|
3964
|
+
for (const item of target) {
|
|
3965
|
+
if (!result.some((existingItem) => deepEquals3(existingItem, item))) {
|
|
3966
|
+
result.push(item);
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
return result;
|
|
3970
|
+
}
|
|
3971
|
+
function smartMergeArraysWithVisited(target, source, visited) {
|
|
3972
|
+
if (source.length === 0)
|
|
3973
|
+
return target;
|
|
3974
|
+
if (target.length === 0)
|
|
3975
|
+
return source;
|
|
3976
|
+
if (isObject3(source[0]) && isObject3(target[0])) {
|
|
3977
|
+
return mergeObjectArraysWithVisited(target, source, visited);
|
|
3978
|
+
}
|
|
3979
|
+
if (source.every((item) => typeof item === "string") && target.every((item) => typeof item === "string")) {
|
|
3980
|
+
const result = [...source];
|
|
3981
|
+
for (const item of target) {
|
|
3982
|
+
if (!result.includes(item)) {
|
|
3983
|
+
result.push(item);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
return result;
|
|
3987
|
+
}
|
|
3988
|
+
return source;
|
|
3989
|
+
}
|
|
3990
|
+
function mergeObjectArraysWithVisited(target, source, _visited) {
|
|
3991
|
+
const result = [...source];
|
|
3992
|
+
for (const targetItem of target) {
|
|
3993
|
+
if (!isObject3(targetItem)) {
|
|
3994
|
+
result.push(targetItem);
|
|
3995
|
+
continue;
|
|
3996
|
+
}
|
|
3997
|
+
const identifierKeys = ["id", "name", "key", "path", "type"];
|
|
3998
|
+
let hasMatch = false;
|
|
3999
|
+
for (const key of identifierKeys) {
|
|
4000
|
+
if (key in targetItem) {
|
|
4001
|
+
const existingItem = result.find((item) => isObject3(item) && (key in item) && item[key] === targetItem[key]);
|
|
4002
|
+
if (existingItem) {
|
|
4003
|
+
hasMatch = true;
|
|
4004
|
+
break;
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
if (!hasMatch) {
|
|
4009
|
+
result.push(targetItem);
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
return result;
|
|
4013
|
+
}
|
|
4014
|
+
function mergeObjectsWithVisited(target, source, options, visited) {
|
|
4015
|
+
const sourceObj = source;
|
|
4016
|
+
if (isObject3(sourceObj) && visited.has(sourceObj)) {
|
|
4017
|
+
return visited.get(sourceObj);
|
|
4018
|
+
}
|
|
4019
|
+
const merged = { ...target };
|
|
4020
|
+
if (isObject3(sourceObj)) {
|
|
4021
|
+
visited.set(sourceObj, merged);
|
|
4022
|
+
}
|
|
4023
|
+
for (const key in sourceObj) {
|
|
4024
|
+
if (!Object.prototype.hasOwnProperty.call(sourceObj, key)) {
|
|
4025
|
+
continue;
|
|
4026
|
+
}
|
|
4027
|
+
const sourceValue = sourceObj[key];
|
|
4028
|
+
const targetValue = merged[key];
|
|
4029
|
+
if (options.skipNullish && (sourceValue === null || sourceValue === undefined)) {
|
|
4030
|
+
continue;
|
|
4031
|
+
}
|
|
4032
|
+
if (sourceValue === null || sourceValue === undefined) {
|
|
4033
|
+
merged[key] = sourceValue;
|
|
4034
|
+
continue;
|
|
4035
|
+
}
|
|
4036
|
+
if (isObject3(sourceValue) && isObject3(targetValue)) {
|
|
4037
|
+
merged[key] = deepMergeWithVisited(targetValue, sourceValue, options, visited);
|
|
4038
|
+
} else if (Array.isArray(sourceValue) || Array.isArray(targetValue)) {
|
|
4039
|
+
merged[key] = mergeArraysWithVisited(targetValue, sourceValue, options.arrayMergeMode || "smart", visited);
|
|
4040
|
+
} else {
|
|
4041
|
+
merged[key] = sourceValue;
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
return merged;
|
|
4045
|
+
}
|
|
4046
|
+
function deepMergeWithArrayStrategy2(target, source, strategy = "replace") {
|
|
4047
|
+
const arrayMergeMode = strategy === "replace" ? "replace" : "smart";
|
|
4048
|
+
return deepMerge3(target, source, {
|
|
4049
|
+
arrayMergeMode,
|
|
4050
|
+
skipNullish: true
|
|
4051
|
+
});
|
|
4052
|
+
}
|
|
4053
|
+
function deepEquals3(a, b) {
|
|
4054
|
+
if (a === b)
|
|
4055
|
+
return true;
|
|
4056
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
4057
|
+
if (a.length !== b.length)
|
|
4058
|
+
return false;
|
|
4059
|
+
for (let i = 0;i < a.length; i++) {
|
|
4060
|
+
if (!deepEquals3(a[i], b[i]))
|
|
4061
|
+
return false;
|
|
4062
|
+
}
|
|
4063
|
+
return true;
|
|
4064
|
+
}
|
|
4065
|
+
if (isObject3(a) && isObject3(b)) {
|
|
4066
|
+
const keysA = Object.keys(a);
|
|
4067
|
+
const keysB = Object.keys(b);
|
|
4068
|
+
if (keysA.length !== keysB.length)
|
|
4069
|
+
return false;
|
|
4070
|
+
for (const key of keysA) {
|
|
4071
|
+
if (!Object.prototype.hasOwnProperty.call(b, key))
|
|
4072
|
+
return false;
|
|
4073
|
+
if (!deepEquals3(a[key], b[key]))
|
|
4074
|
+
return false;
|
|
4075
|
+
}
|
|
4076
|
+
return true;
|
|
4077
|
+
}
|
|
4078
|
+
return false;
|
|
4079
|
+
}
|
|
4080
|
+
function isObject3(item) {
|
|
4081
|
+
return Boolean(item && typeof item === "object" && !Array.isArray(item));
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4084
|
+
class ConfigFileLoader {
|
|
4085
|
+
extensions = [".ts", ".js", ".mjs", ".cjs", ".json", ".mts", ".cts"];
|
|
4086
|
+
async loadFromPath(configPath, defaultConfig3, options = {}) {
|
|
4087
|
+
const {
|
|
4088
|
+
arrayStrategy = "replace",
|
|
4089
|
+
useCache = true,
|
|
4090
|
+
cacheTtl,
|
|
4091
|
+
trackPerformance = true,
|
|
4092
|
+
verbose = false
|
|
4093
|
+
} = options;
|
|
4094
|
+
if (useCache) {
|
|
4095
|
+
const cached = globalCache.getWithFileCheck("file", configPath);
|
|
4096
|
+
if (cached) {
|
|
4097
|
+
if (verbose) {
|
|
4098
|
+
console.log(`Configuration loaded from cache: ${configPath}`);
|
|
4099
|
+
}
|
|
4100
|
+
return cached;
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
const loadOperation = async () => {
|
|
4104
|
+
if (!existsSync5(configPath)) {
|
|
4105
|
+
return null;
|
|
4106
|
+
}
|
|
4107
|
+
try {
|
|
4108
|
+
const cacheBuster = `?t=${Date.now()}`;
|
|
4109
|
+
const importedConfig = await import(configPath + cacheBuster);
|
|
4110
|
+
const loadedConfig = importedConfig.default || importedConfig;
|
|
4111
|
+
const hasDefaultExport = "default" in importedConfig;
|
|
4112
|
+
const hasNamedExports = Object.keys(importedConfig).length > 0;
|
|
4113
|
+
if (!hasDefaultExport && !hasNamedExports) {
|
|
4114
|
+
throw new ConfigLoadError(configPath, new Error("Configuration file is empty and exports nothing"), "unknown");
|
|
4115
|
+
}
|
|
4116
|
+
if (typeof loadedConfig !== "object" || loadedConfig === null || Array.isArray(loadedConfig)) {
|
|
4117
|
+
throw new ConfigLoadError(configPath, new Error("Configuration must export a valid object"), "unknown");
|
|
4118
|
+
}
|
|
4119
|
+
const mergedConfig = deepMergeWithArrayStrategy2(defaultConfig3, loadedConfig, arrayStrategy);
|
|
4120
|
+
const source = {
|
|
4121
|
+
type: "file",
|
|
4122
|
+
path: configPath,
|
|
4123
|
+
priority: 100,
|
|
4124
|
+
timestamp: new Date
|
|
4125
|
+
};
|
|
4126
|
+
const result = { config: mergedConfig, source };
|
|
4127
|
+
if (useCache) {
|
|
4128
|
+
globalCache.setWithFileCheck("file", result, configPath, cacheTtl);
|
|
4129
|
+
}
|
|
4130
|
+
return result;
|
|
4131
|
+
} catch (error) {
|
|
4132
|
+
const bunfigError = error instanceof Error ? ErrorFactory.configLoad(configPath, error) : ErrorFactory.configLoad(configPath, new Error(String(error)));
|
|
4133
|
+
throw bunfigError;
|
|
4134
|
+
}
|
|
4135
|
+
};
|
|
4136
|
+
if (trackPerformance) {
|
|
4137
|
+
return globalPerformanceMonitor.track("loadFromPath", loadOperation, { path: configPath });
|
|
4138
|
+
}
|
|
4139
|
+
return loadOperation();
|
|
4140
|
+
}
|
|
4141
|
+
async tryLoadFromPaths(configPaths, defaultConfig3, options = {}) {
|
|
4142
|
+
for (const configPath of configPaths) {
|
|
4143
|
+
try {
|
|
4144
|
+
const result = await this.loadFromPath(configPath, defaultConfig3, options);
|
|
4145
|
+
if (result) {
|
|
4146
|
+
return result;
|
|
4147
|
+
}
|
|
4148
|
+
} catch (error) {
|
|
4149
|
+
if (error instanceof Error && error.name === "ConfigLoadError") {
|
|
4150
|
+
throw error;
|
|
4151
|
+
}
|
|
4152
|
+
if (options.verbose) {
|
|
4153
|
+
console.warn(`Failed to load config from ${configPath}:`, error);
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
return null;
|
|
4158
|
+
}
|
|
4159
|
+
generateConfigPaths(configName, directory, alias) {
|
|
4160
|
+
const patterns = this.generateNamePatterns(configName, alias);
|
|
4161
|
+
const paths = [];
|
|
4162
|
+
for (const pattern of patterns) {
|
|
4163
|
+
for (const ext of this.extensions) {
|
|
4164
|
+
paths.push(resolve5(directory, `${pattern}${ext}`));
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
return paths;
|
|
4168
|
+
}
|
|
4169
|
+
generateNamePatterns(configName, alias) {
|
|
4170
|
+
const patterns = [];
|
|
4171
|
+
patterns.push("config", ".config");
|
|
4172
|
+
if (configName) {
|
|
4173
|
+
patterns.push(configName, `.${configName}.config`, `${configName}.config`, `.${configName}`);
|
|
4174
|
+
}
|
|
4175
|
+
if (alias) {
|
|
4176
|
+
patterns.push(alias, `.${alias}.config`, `${alias}.config`, `.${alias}`);
|
|
4177
|
+
if (configName) {
|
|
4178
|
+
patterns.push(`${configName}.${alias}.config`, `.${configName}.${alias}.config`);
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
return patterns.filter(Boolean);
|
|
4182
|
+
}
|
|
4183
|
+
checkFileAccess(filePath) {
|
|
4184
|
+
return withErrorRecovery(async () => {
|
|
4185
|
+
return existsSync5(filePath);
|
|
4186
|
+
}, {
|
|
4187
|
+
maxRetries: 2,
|
|
4188
|
+
retryDelay: 100,
|
|
4189
|
+
fallback: false
|
|
4190
|
+
});
|
|
4191
|
+
}
|
|
4192
|
+
async discoverConfigFiles(directory, configName, alias) {
|
|
4193
|
+
const discoveredFiles = [];
|
|
4194
|
+
if (!existsSync5(directory)) {
|
|
4195
|
+
return discoveredFiles;
|
|
4196
|
+
}
|
|
4197
|
+
if (configName || alias) {
|
|
4198
|
+
const patterns = this.generateNamePatterns(configName || "", alias);
|
|
4199
|
+
for (const pattern of patterns) {
|
|
4200
|
+
for (const ext of this.extensions) {
|
|
4201
|
+
const filePath = resolve5(directory, `${pattern}${ext}`);
|
|
4202
|
+
if (await this.checkFileAccess(filePath)) {
|
|
4203
|
+
discoveredFiles.push(filePath);
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
} else {
|
|
4208
|
+
try {
|
|
4209
|
+
const { readdirSync: readdirSync32 } = await import("fs");
|
|
4210
|
+
const files = readdirSync32(directory);
|
|
4211
|
+
for (const file of files) {
|
|
4212
|
+
if (this.looksLikeConfigFile(file)) {
|
|
4213
|
+
const filePath = resolve5(directory, file);
|
|
4214
|
+
if (await this.checkFileAccess(filePath)) {
|
|
4215
|
+
discoveredFiles.push(filePath);
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
} catch {
|
|
4220
|
+
return [];
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
return discoveredFiles;
|
|
4224
|
+
}
|
|
4225
|
+
looksLikeConfigFile(filename) {
|
|
4226
|
+
const configPatterns = [
|
|
4227
|
+
/\.config\.(ts|js|mjs|cjs|json|mts|cts)$/,
|
|
4228
|
+
/^\..*\.(ts|js|mjs|cjs|json|mts|cts)$/,
|
|
4229
|
+
/config\.(ts|js|mjs|cjs|json|mts|cts)$/
|
|
4230
|
+
];
|
|
4231
|
+
return configPatterns.some((pattern) => pattern.test(filename));
|
|
4232
|
+
}
|
|
4233
|
+
async validateConfigFile(filePath) {
|
|
4234
|
+
const errors = [];
|
|
4235
|
+
try {
|
|
4236
|
+
if (!existsSync5(filePath)) {
|
|
4237
|
+
errors.push("Configuration file does not exist");
|
|
4238
|
+
return errors;
|
|
4239
|
+
}
|
|
4240
|
+
const imported = await import(filePath);
|
|
4241
|
+
const config3 = imported.default || imported;
|
|
4242
|
+
if (config3 === undefined) {
|
|
4243
|
+
errors.push("Configuration file must export a default value or named exports");
|
|
4244
|
+
} else if (typeof config3 !== "object" || config3 === null) {
|
|
4245
|
+
errors.push("Configuration must be an object");
|
|
4246
|
+
} else if (Array.isArray(config3)) {
|
|
4247
|
+
errors.push("Configuration cannot be an array at the root level");
|
|
4248
|
+
}
|
|
4249
|
+
if (filePath.endsWith(".json")) {
|
|
4250
|
+
try {
|
|
4251
|
+
const { readFileSync } = await import("fs");
|
|
4252
|
+
const content = readFileSync(filePath, "utf8");
|
|
4253
|
+
JSON.parse(content);
|
|
4254
|
+
} catch (jsonError) {
|
|
4255
|
+
errors.push(`Invalid JSON syntax: ${jsonError}`);
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
} catch (error) {
|
|
4259
|
+
errors.push(`Failed to load configuration file: ${error}`);
|
|
4260
|
+
}
|
|
4261
|
+
return errors;
|
|
4262
|
+
}
|
|
4263
|
+
async getFileModificationTime(filePath) {
|
|
4264
|
+
try {
|
|
4265
|
+
const { statSync: statSync2 } = await import("fs");
|
|
4266
|
+
const stats = statSync2(filePath);
|
|
4267
|
+
return stats.mtime;
|
|
4268
|
+
} catch {
|
|
4269
|
+
return null;
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
async preloadConfigurations(configPaths, options = {}) {
|
|
4273
|
+
const preloaded = new Map;
|
|
4274
|
+
await Promise.allSettled(configPaths.map(async (path) => {
|
|
4275
|
+
try {
|
|
4276
|
+
const result = await this.loadFromPath(path, {}, options);
|
|
4277
|
+
if (result) {
|
|
4278
|
+
preloaded.set(path, result.config);
|
|
4279
|
+
}
|
|
4280
|
+
} catch (error) {
|
|
4281
|
+
if (options.verbose) {
|
|
4282
|
+
console.warn(`Failed to preload ${path}:`, error);
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
}));
|
|
4286
|
+
return preloaded;
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
var URL_PATTERN = /^https?:\/\//;
|
|
4290
|
+
|
|
4291
|
+
class ConfigValidator {
|
|
4292
|
+
async validateConfiguration(config3, schema, options = {}) {
|
|
4293
|
+
const {
|
|
4294
|
+
stopOnFirstError = false,
|
|
4295
|
+
validateRequired = true,
|
|
4296
|
+
validateTypes = true,
|
|
4297
|
+
customRules = [],
|
|
4298
|
+
trackPerformance = true,
|
|
4299
|
+
verbose = false
|
|
4300
|
+
} = options;
|
|
4301
|
+
const operation = async () => {
|
|
4302
|
+
const errors = [];
|
|
4303
|
+
const warnings = [];
|
|
4304
|
+
const resolvedOptions = {
|
|
4305
|
+
stopOnFirstError,
|
|
4306
|
+
validateRequired,
|
|
4307
|
+
validateTypes,
|
|
4308
|
+
customRules,
|
|
4309
|
+
trackPerformance,
|
|
4310
|
+
verbose
|
|
4311
|
+
};
|
|
4312
|
+
try {
|
|
4313
|
+
if (typeof schema === "string") {
|
|
4314
|
+
return await this.validateWithSchemaFile(config3, schema, resolvedOptions);
|
|
4315
|
+
} else if (Array.isArray(schema)) {
|
|
4316
|
+
return this.validateWithRules(config3, [...schema, ...customRules], resolvedOptions);
|
|
4317
|
+
} else {
|
|
4318
|
+
return this.validateWithJSONSchema(config3, schema, resolvedOptions);
|
|
4319
|
+
}
|
|
4320
|
+
} catch (error) {
|
|
4321
|
+
errors.push({
|
|
4322
|
+
path: "",
|
|
4323
|
+
message: `Validation failed: ${error}`,
|
|
4324
|
+
rule: "system"
|
|
4325
|
+
});
|
|
4326
|
+
return { isValid: false, errors, warnings };
|
|
4327
|
+
}
|
|
4328
|
+
};
|
|
4329
|
+
if (trackPerformance) {
|
|
4330
|
+
const result = await globalPerformanceMonitor.track("validateConfiguration", operation);
|
|
4331
|
+
return result;
|
|
4332
|
+
}
|
|
4333
|
+
return operation();
|
|
4334
|
+
}
|
|
4335
|
+
async validateWithSchemaFile(config3, schemaPath, options) {
|
|
4336
|
+
try {
|
|
4337
|
+
if (!existsSync7(schemaPath)) {
|
|
4338
|
+
throw new SchemaValidationError(schemaPath, [{ path: "", message: "Schema file does not exist" }]);
|
|
4339
|
+
}
|
|
4340
|
+
const schemaModule = await import(schemaPath);
|
|
4341
|
+
const schema = schemaModule.default || schemaModule;
|
|
4342
|
+
if (Array.isArray(schema)) {
|
|
4343
|
+
return this.validateWithRules(config3, schema, options);
|
|
4344
|
+
} else {
|
|
4345
|
+
return this.validateWithJSONSchema(config3, schema, options);
|
|
4346
|
+
}
|
|
4347
|
+
} catch (error) {
|
|
4348
|
+
throw new SchemaValidationError(schemaPath, [{ path: "", message: `Failed to load schema: ${error}` }]);
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
validateWithJSONSchema(config3, schema, options) {
|
|
4352
|
+
const errors = [];
|
|
4353
|
+
const warnings = [];
|
|
4354
|
+
this.validateObjectAgainstSchema(config3, schema, "", errors, warnings, options);
|
|
4355
|
+
return {
|
|
4356
|
+
isValid: errors.length === 0,
|
|
4357
|
+
errors,
|
|
4358
|
+
warnings
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
4361
|
+
validateObjectAgainstSchema(value, schema, path, errors, warnings, options) {
|
|
4362
|
+
if (options.validateTypes && schema.type) {
|
|
4363
|
+
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
4364
|
+
const expectedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
4365
|
+
if (!expectedTypes.includes(actualType)) {
|
|
4366
|
+
errors.push({
|
|
4367
|
+
path,
|
|
4368
|
+
message: `Expected type ${expectedTypes.join(" or ")}, got ${actualType}`,
|
|
4369
|
+
expected: expectedTypes.join(" or "),
|
|
4370
|
+
actual: actualType,
|
|
4371
|
+
rule: "type"
|
|
4372
|
+
});
|
|
4373
|
+
if (options.stopOnFirstError)
|
|
4374
|
+
return;
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
4378
|
+
errors.push({
|
|
4379
|
+
path,
|
|
4380
|
+
message: `Value must be one of: ${schema.enum.join(", ")}`,
|
|
4381
|
+
expected: schema.enum.join(", "),
|
|
4382
|
+
actual: value,
|
|
4383
|
+
rule: "enum"
|
|
4384
|
+
});
|
|
4385
|
+
if (options.stopOnFirstError)
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4388
|
+
if (typeof value === "string") {
|
|
4389
|
+
if (schema.minLength !== undefined && value.length < schema.minLength) {
|
|
4390
|
+
errors.push({
|
|
4391
|
+
path,
|
|
4392
|
+
message: `String length must be at least ${schema.minLength}`,
|
|
4393
|
+
expected: `>= ${schema.minLength}`,
|
|
4394
|
+
actual: value.length,
|
|
4395
|
+
rule: "minLength"
|
|
4396
|
+
});
|
|
4397
|
+
}
|
|
4398
|
+
if (schema.maxLength !== undefined && value.length > schema.maxLength) {
|
|
4399
|
+
errors.push({
|
|
4400
|
+
path,
|
|
4401
|
+
message: `String length must not exceed ${schema.maxLength}`,
|
|
4402
|
+
expected: `<= ${schema.maxLength}`,
|
|
4403
|
+
actual: value.length,
|
|
4404
|
+
rule: "maxLength"
|
|
4405
|
+
});
|
|
4406
|
+
}
|
|
4407
|
+
if (schema.pattern) {
|
|
4408
|
+
const regex = new RegExp(schema.pattern);
|
|
4409
|
+
if (!regex.test(value)) {
|
|
4410
|
+
errors.push({
|
|
4411
|
+
path,
|
|
4412
|
+
message: `String does not match pattern ${schema.pattern}`,
|
|
4413
|
+
expected: schema.pattern,
|
|
4414
|
+
actual: value,
|
|
4415
|
+
rule: "pattern"
|
|
4416
|
+
});
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
if (typeof value === "number") {
|
|
4421
|
+
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
4422
|
+
errors.push({
|
|
4423
|
+
path,
|
|
4424
|
+
message: `Value must be at least ${schema.minimum}`,
|
|
4425
|
+
expected: `>= ${schema.minimum}`,
|
|
4426
|
+
actual: value,
|
|
4427
|
+
rule: "minimum"
|
|
4428
|
+
});
|
|
4429
|
+
}
|
|
4430
|
+
if (schema.maximum !== undefined && value > schema.maximum) {
|
|
4431
|
+
errors.push({
|
|
4432
|
+
path,
|
|
4433
|
+
message: `Value must not exceed ${schema.maximum}`,
|
|
4434
|
+
expected: `<= ${schema.maximum}`,
|
|
4435
|
+
actual: value,
|
|
4436
|
+
rule: "maximum"
|
|
4437
|
+
});
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
if (Array.isArray(value) && schema.items) {
|
|
4441
|
+
for (let i = 0;i < value.length; i++) {
|
|
4442
|
+
const itemPath = path ? `${path}[${i}]` : `[${i}]`;
|
|
4443
|
+
this.validateObjectAgainstSchema(value[i], schema.items, itemPath, errors, warnings, options);
|
|
4444
|
+
if (options.stopOnFirstError && errors.length > 0)
|
|
4445
|
+
return;
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
4449
|
+
const obj = value;
|
|
4450
|
+
if (options.validateRequired && schema.required) {
|
|
4451
|
+
for (const requiredProp of schema.required) {
|
|
4452
|
+
if (!(requiredProp in obj)) {
|
|
4453
|
+
errors.push({
|
|
4454
|
+
path: path ? `${path}.${requiredProp}` : requiredProp,
|
|
4455
|
+
message: `Missing required property '${requiredProp}'`,
|
|
4456
|
+
expected: "required",
|
|
4457
|
+
rule: "required"
|
|
4458
|
+
});
|
|
4459
|
+
if (options.stopOnFirstError)
|
|
4460
|
+
return;
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
if (schema.properties) {
|
|
4465
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
4466
|
+
if (propName in obj) {
|
|
4467
|
+
const propPath = path ? `${path}.${propName}` : propName;
|
|
4468
|
+
this.validateObjectAgainstSchema(obj[propName], propSchema, propPath, errors, warnings, options);
|
|
4469
|
+
if (options.stopOnFirstError && errors.length > 0)
|
|
4470
|
+
return;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
if (schema.additionalProperties === false) {
|
|
4475
|
+
const allowedProps = new Set(Object.keys(schema.properties || {}));
|
|
4476
|
+
for (const propName of Object.keys(obj)) {
|
|
4477
|
+
if (!allowedProps.has(propName)) {
|
|
4478
|
+
warnings.push({
|
|
4479
|
+
path: path ? `${path}.${propName}` : propName,
|
|
4480
|
+
message: `Additional property '${propName}' is not allowed`,
|
|
4481
|
+
rule: "additionalProperties"
|
|
4482
|
+
});
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
validateWithRules(config3, rules, options) {
|
|
4489
|
+
const errors = [];
|
|
4490
|
+
const warnings = [];
|
|
4491
|
+
for (const rule of rules) {
|
|
4492
|
+
try {
|
|
4493
|
+
const value = this.getValueByPath(config3, rule.path);
|
|
4494
|
+
const ruleErrors = this.validateWithRule(value, rule, rule.path);
|
|
4495
|
+
errors.push(...ruleErrors);
|
|
4496
|
+
if (options.stopOnFirstError && errors.length > 0) {
|
|
4497
|
+
break;
|
|
4498
|
+
}
|
|
4499
|
+
} catch (error) {
|
|
4500
|
+
errors.push({
|
|
4501
|
+
path: rule.path,
|
|
4502
|
+
message: `Rule validation failed: ${error}`,
|
|
4503
|
+
rule: "system"
|
|
4504
|
+
});
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
return {
|
|
4508
|
+
isValid: errors.length === 0,
|
|
4509
|
+
errors,
|
|
4510
|
+
warnings
|
|
4511
|
+
};
|
|
4512
|
+
}
|
|
4513
|
+
validateWithRule(value, rule, path) {
|
|
4514
|
+
const errors = [];
|
|
4515
|
+
if (rule.required && (value === undefined || value === null)) {
|
|
4516
|
+
errors.push({
|
|
4517
|
+
path,
|
|
4518
|
+
message: rule.message || `Property '${path}' is required`,
|
|
4519
|
+
expected: "required",
|
|
4520
|
+
rule: "required"
|
|
4521
|
+
});
|
|
4522
|
+
return errors;
|
|
4523
|
+
}
|
|
4524
|
+
if (value === undefined || value === null) {
|
|
4525
|
+
return errors;
|
|
4526
|
+
}
|
|
4527
|
+
if (rule.type) {
|
|
4528
|
+
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
4529
|
+
if (actualType !== rule.type) {
|
|
4530
|
+
errors.push({
|
|
4531
|
+
path,
|
|
4532
|
+
message: rule.message || `Expected type ${rule.type}, got ${actualType}`,
|
|
4533
|
+
expected: rule.type,
|
|
4534
|
+
actual: actualType,
|
|
4535
|
+
rule: "type"
|
|
4536
|
+
});
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
if (rule.min !== undefined) {
|
|
4540
|
+
const length = Array.isArray(value) ? value.length : typeof value === "string" ? value.length : typeof value === "number" ? value : 0;
|
|
4541
|
+
if (length < rule.min) {
|
|
4542
|
+
errors.push({
|
|
4543
|
+
path,
|
|
4544
|
+
message: rule.message || `Value must be at least ${rule.min}`,
|
|
4545
|
+
expected: `>= ${rule.min}`,
|
|
4546
|
+
actual: length,
|
|
4547
|
+
rule: "min"
|
|
4548
|
+
});
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
if (rule.max !== undefined) {
|
|
4552
|
+
const length = Array.isArray(value) ? value.length : typeof value === "string" ? value.length : typeof value === "number" ? value : 0;
|
|
4553
|
+
if (length > rule.max) {
|
|
4554
|
+
errors.push({
|
|
4555
|
+
path,
|
|
4556
|
+
message: rule.message || `Value must not exceed ${rule.max}`,
|
|
4557
|
+
expected: `<= ${rule.max}`,
|
|
4558
|
+
actual: length,
|
|
4559
|
+
rule: "max"
|
|
4560
|
+
});
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
if (rule.pattern && typeof value === "string") {
|
|
4564
|
+
if (!rule.pattern.test(value)) {
|
|
4565
|
+
errors.push({
|
|
4566
|
+
path,
|
|
4567
|
+
message: rule.message || `Value does not match pattern ${rule.pattern}`,
|
|
4568
|
+
expected: rule.pattern.toString(),
|
|
4569
|
+
actual: value,
|
|
4570
|
+
rule: "pattern"
|
|
4571
|
+
});
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
if (rule.enum && !rule.enum.includes(value)) {
|
|
4575
|
+
errors.push({
|
|
4576
|
+
path,
|
|
4577
|
+
message: rule.message || `Value must be one of: ${rule.enum.join(", ")}`,
|
|
4578
|
+
expected: rule.enum.join(", "),
|
|
4579
|
+
actual: value,
|
|
4580
|
+
rule: "enum"
|
|
4581
|
+
});
|
|
4582
|
+
}
|
|
4583
|
+
if (rule.validator) {
|
|
4584
|
+
const customError = rule.validator(value);
|
|
4585
|
+
if (customError) {
|
|
4586
|
+
errors.push({
|
|
4587
|
+
path,
|
|
4588
|
+
message: rule.message || customError,
|
|
4589
|
+
rule: "custom"
|
|
4590
|
+
});
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
return errors;
|
|
4594
|
+
}
|
|
4595
|
+
getValueByPath(obj, path) {
|
|
4596
|
+
if (!path)
|
|
4597
|
+
return obj;
|
|
4598
|
+
const keys = path.split(".");
|
|
4599
|
+
let current = obj;
|
|
4600
|
+
for (const key of keys) {
|
|
4601
|
+
if (current && typeof current === "object" && key in current) {
|
|
4602
|
+
current = current[key];
|
|
4603
|
+
} else {
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
return current;
|
|
4608
|
+
}
|
|
4609
|
+
generateRulesFromInterface(interfaceCode) {
|
|
4610
|
+
const rules = [];
|
|
4611
|
+
const propertyMatches = interfaceCode.matchAll(/(\w+)(\?)?:\s*(\w+)/g);
|
|
4612
|
+
for (const match of propertyMatches) {
|
|
4613
|
+
const [, propName, optional, typeName] = match;
|
|
4614
|
+
rules.push({
|
|
4615
|
+
path: propName,
|
|
4616
|
+
required: !optional,
|
|
4617
|
+
type: this.mapTypeScriptType(typeName)
|
|
4618
|
+
});
|
|
4619
|
+
}
|
|
4620
|
+
return rules;
|
|
4621
|
+
}
|
|
4622
|
+
mapTypeScriptType(tsType) {
|
|
4623
|
+
switch (tsType.toLowerCase()) {
|
|
4624
|
+
case "string":
|
|
4625
|
+
return "string";
|
|
4626
|
+
case "number":
|
|
4627
|
+
return "number";
|
|
4628
|
+
case "boolean":
|
|
4629
|
+
return "boolean";
|
|
4630
|
+
case "array":
|
|
4631
|
+
return "array";
|
|
4632
|
+
case "object":
|
|
4633
|
+
return "object";
|
|
4634
|
+
default:
|
|
4635
|
+
return "object";
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
static createCommonRules() {
|
|
4639
|
+
return {
|
|
4640
|
+
server: [
|
|
4641
|
+
{ path: "port", required: true, type: "number", min: 1, max: 65535 },
|
|
4642
|
+
{ path: "host", required: true, type: "string", min: 1 },
|
|
4643
|
+
{ path: "ssl", type: "boolean" }
|
|
4644
|
+
],
|
|
4645
|
+
database: [
|
|
4646
|
+
{ path: "url", required: true, type: "string", min: 1 },
|
|
4647
|
+
{ path: "pool", type: "number", min: 1, max: 100 },
|
|
4648
|
+
{ path: "timeout", type: "number", min: 0 }
|
|
4649
|
+
],
|
|
4650
|
+
api: [
|
|
4651
|
+
{ path: "baseUrl", required: true, type: "string", pattern: URL_PATTERN },
|
|
4652
|
+
{ path: "timeout", type: "number", min: 0 },
|
|
4653
|
+
{ path: "retries", type: "number", min: 0, max: 10 }
|
|
4654
|
+
]
|
|
4655
|
+
};
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
var log2 = new Logger2("bunfig", {
|
|
4659
|
+
showTags: true
|
|
4660
|
+
});
|
|
4661
|
+
|
|
4662
|
+
class ConfigLoader {
|
|
4663
|
+
fileLoader = new ConfigFileLoader;
|
|
4664
|
+
envProcessor = new EnvProcessor;
|
|
4665
|
+
validator = new ConfigValidator;
|
|
4666
|
+
async loadConfig(options) {
|
|
4667
|
+
const startTime = Date.now();
|
|
4668
|
+
const {
|
|
4669
|
+
cache,
|
|
4670
|
+
performance: performance2,
|
|
4671
|
+
schema,
|
|
4672
|
+
validate: customValidator,
|
|
4673
|
+
...baseOptions
|
|
4674
|
+
} = options;
|
|
4675
|
+
try {
|
|
4676
|
+
if (cache?.enabled) {
|
|
4677
|
+
const cached = this.checkCache(baseOptions.name || "", baseOptions);
|
|
4678
|
+
if (cached) {
|
|
4679
|
+
return cached;
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
let result;
|
|
4683
|
+
try {
|
|
4684
|
+
result = await this.loadConfigurationStrategies(baseOptions, true, cache);
|
|
4685
|
+
} catch (error) {
|
|
4686
|
+
const isStrictMode = baseOptions.__strictErrorHandling;
|
|
4687
|
+
if (error instanceof Error && error.name === "ConfigNotFoundError") {
|
|
4688
|
+
if (isStrictMode) {
|
|
4689
|
+
throw error;
|
|
4690
|
+
}
|
|
4691
|
+
const envResult = await this.applyEnvironmentVariables(baseOptions.name || "", baseOptions.defaultConfig, baseOptions.checkEnv !== false, baseOptions.verbose || false);
|
|
4692
|
+
result = {
|
|
4693
|
+
...envResult,
|
|
4694
|
+
warnings: [`No configuration file found for "${baseOptions.name || "config"}", using defaults with environment variables`]
|
|
4695
|
+
};
|
|
4696
|
+
} else if (error instanceof Error && error.name === "ConfigLoadError") {
|
|
4697
|
+
const isPermissionError = error.message.includes("EACCES") || error.message.includes("EPERM") || error.message.includes("permission denied");
|
|
4698
|
+
const isSyntaxError = !isPermissionError && (error.message.includes("syntax") || error.message.includes("Expected") || error.message.includes("Unexpected") || error.message.includes("BuildMessage") || error.message.includes("errors building"));
|
|
4699
|
+
const isStructureError = error.message.includes("Configuration must export a valid object") || error.message.includes("Configuration file is empty and exports nothing");
|
|
4700
|
+
if (isStrictMode && (isStructureError || isPermissionError)) {
|
|
4701
|
+
throw error;
|
|
4702
|
+
}
|
|
4703
|
+
if (isSyntaxError && (!isStrictMode || !isStructureError)) {
|
|
4704
|
+
const envResult = await this.applyEnvironmentVariables(baseOptions.name || "", baseOptions.defaultConfig, baseOptions.checkEnv !== false, baseOptions.verbose || false);
|
|
4705
|
+
result = {
|
|
4706
|
+
...envResult,
|
|
4707
|
+
warnings: [`Configuration file has syntax errors, using defaults with environment variables`]
|
|
4708
|
+
};
|
|
4709
|
+
} else if (!isStrictMode) {
|
|
4710
|
+
const envResult = await this.applyEnvironmentVariables(baseOptions.name || "", baseOptions.defaultConfig, baseOptions.checkEnv !== false, baseOptions.verbose || false);
|
|
4711
|
+
result = {
|
|
4712
|
+
...envResult,
|
|
4713
|
+
warnings: [`Configuration loading error, using defaults: ${error.message}`]
|
|
4714
|
+
};
|
|
4715
|
+
} else {
|
|
4716
|
+
throw error;
|
|
4717
|
+
}
|
|
4718
|
+
} else {
|
|
4719
|
+
const envResult = await this.applyEnvironmentVariables(baseOptions.name || "", baseOptions.defaultConfig, baseOptions.checkEnv !== false, baseOptions.verbose || false);
|
|
4720
|
+
result = {
|
|
4721
|
+
...envResult,
|
|
4722
|
+
warnings: [`Configuration loading failed, using defaults: ${error instanceof Error ? error.message : String(error)}`]
|
|
4723
|
+
};
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
if (schema || customValidator) {
|
|
4727
|
+
await this.validateConfiguration(result.config, schema, customValidator, baseOptions.name);
|
|
4728
|
+
}
|
|
4729
|
+
if (cache?.enabled && result) {
|
|
4730
|
+
this.cacheResult(baseOptions.name || "", result, cache, baseOptions);
|
|
4731
|
+
}
|
|
4732
|
+
if (performance2?.enabled) {
|
|
4733
|
+
const metrics = {
|
|
4734
|
+
operation: "loadConfig",
|
|
4735
|
+
duration: Date.now() - startTime,
|
|
4736
|
+
configName: baseOptions.name,
|
|
4737
|
+
timestamp: new Date
|
|
4738
|
+
};
|
|
4739
|
+
if (performance2.onMetrics) {
|
|
4740
|
+
performance2.onMetrics(metrics);
|
|
4741
|
+
}
|
|
4742
|
+
if (performance2.slowThreshold && metrics.duration > performance2.slowThreshold) {
|
|
4743
|
+
log2.warn(`Slow configuration loading detected: ${metrics.duration}ms for ${baseOptions.name}`);
|
|
4744
|
+
}
|
|
4745
|
+
result.metrics = metrics;
|
|
4746
|
+
}
|
|
4747
|
+
return result;
|
|
4748
|
+
} catch (error) {
|
|
4749
|
+
const duration = Date.now() - startTime;
|
|
4750
|
+
log2.error(`Configuration loading failed after ${duration}ms:`, [error instanceof Error ? error : new Error(String(error))]);
|
|
4751
|
+
throw error;
|
|
4752
|
+
}
|
|
4753
|
+
}
|
|
4754
|
+
async loadConfigurationStrategies(options, throwOnNotFound = false, cacheOptions) {
|
|
4755
|
+
const {
|
|
4756
|
+
name = "",
|
|
4757
|
+
alias,
|
|
4758
|
+
cwd,
|
|
4759
|
+
configDir,
|
|
4760
|
+
defaultConfig: defaultConfig3,
|
|
4761
|
+
checkEnv = true,
|
|
4762
|
+
arrayStrategy = "replace",
|
|
4763
|
+
verbose = false
|
|
4764
|
+
} = options;
|
|
4765
|
+
const baseDir = cwd || process12.cwd();
|
|
4766
|
+
const searchPaths = [];
|
|
4767
|
+
const localResult = await this.loadLocalConfiguration(name, alias, baseDir, configDir, defaultConfig3, arrayStrategy, verbose, checkEnv, cacheOptions);
|
|
4768
|
+
if (localResult) {
|
|
4769
|
+
searchPaths.push(...this.getLocalSearchPaths(name, alias, baseDir, configDir));
|
|
4770
|
+
return this.finalizeResult(localResult, searchPaths, checkEnv, name, verbose);
|
|
4771
|
+
}
|
|
4772
|
+
const homeResult = await this.loadHomeConfiguration(name, alias, defaultConfig3, arrayStrategy, verbose, checkEnv);
|
|
4773
|
+
if (homeResult) {
|
|
4774
|
+
searchPaths.push(...this.getHomeSearchPaths(name, alias));
|
|
4775
|
+
return this.finalizeResult(homeResult, searchPaths, checkEnv, name, verbose);
|
|
4776
|
+
}
|
|
4777
|
+
const packageResult = await this.loadPackageJsonConfiguration(name, alias, baseDir, defaultConfig3, arrayStrategy, verbose, checkEnv);
|
|
4778
|
+
if (packageResult) {
|
|
4779
|
+
searchPaths.push(resolve7(baseDir, "package.json"));
|
|
4780
|
+
return this.finalizeResult(packageResult, searchPaths, checkEnv, name, verbose);
|
|
4781
|
+
}
|
|
4782
|
+
searchPaths.push(...this.getAllSearchPaths(name, alias, baseDir, configDir));
|
|
4783
|
+
if (throwOnNotFound) {
|
|
4784
|
+
throw ErrorFactory.configNotFound(name, searchPaths, alias);
|
|
4785
|
+
}
|
|
4786
|
+
const envResult = await this.applyEnvironmentVariables(name, defaultConfig3, checkEnv, verbose);
|
|
4787
|
+
return {
|
|
4788
|
+
...envResult,
|
|
4789
|
+
warnings: [`No configuration file found for "${name}"${alias ? ` or alias "${alias}"` : ""}, using defaults with environment variables`]
|
|
4790
|
+
};
|
|
4791
|
+
}
|
|
4792
|
+
async loadLocalConfiguration(name, alias, baseDir, configDir, defaultConfig3, arrayStrategy, verbose, checkEnv, cacheOptions) {
|
|
4793
|
+
const envDefaultConfig = checkEnv ? applyEnvVarsToConfig2(name, defaultConfig3, verbose) : defaultConfig3;
|
|
4794
|
+
const searchDirectories = this.getLocalDirectories(baseDir, configDir);
|
|
4795
|
+
for (const directory of searchDirectories) {
|
|
4796
|
+
if (verbose) {
|
|
4797
|
+
log2.info(`Searching for configuration in: ${directory}`);
|
|
4798
|
+
}
|
|
4799
|
+
const configPaths = this.fileLoader.generateConfigPaths(name, directory, alias);
|
|
4800
|
+
const result = await this.fileLoader.tryLoadFromPaths(configPaths, envDefaultConfig, {
|
|
4801
|
+
arrayStrategy,
|
|
4802
|
+
verbose,
|
|
4803
|
+
cacheTtl: cacheOptions?.ttl,
|
|
4804
|
+
useCache: !cacheOptions?.ttl || cacheOptions.ttl > 100
|
|
4805
|
+
});
|
|
4806
|
+
if (result) {
|
|
4807
|
+
if (verbose) {
|
|
4808
|
+
log2.success(`Configuration loaded from: ${result.source.path}`);
|
|
4809
|
+
}
|
|
4810
|
+
return result;
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
return null;
|
|
4814
|
+
}
|
|
4815
|
+
async loadHomeConfiguration(name, alias, defaultConfig3, arrayStrategy, verbose, checkEnv) {
|
|
4816
|
+
if (!name)
|
|
4817
|
+
return null;
|
|
4818
|
+
const envDefaultConfig = checkEnv ? applyEnvVarsToConfig2(name, defaultConfig3, verbose) : defaultConfig3;
|
|
4819
|
+
const homeDirectories = [
|
|
4820
|
+
resolve7(homedir2(), ".config", name),
|
|
4821
|
+
resolve7(homedir2(), ".config"),
|
|
4822
|
+
homedir2()
|
|
4823
|
+
];
|
|
4824
|
+
for (const directory of homeDirectories) {
|
|
4825
|
+
if (verbose) {
|
|
4826
|
+
log2.info(`Checking home directory: ${directory}`);
|
|
4827
|
+
}
|
|
4828
|
+
const configPaths = this.fileLoader.generateConfigPaths(name, directory, alias);
|
|
4829
|
+
const result = await this.fileLoader.tryLoadFromPaths(configPaths, envDefaultConfig, { arrayStrategy, verbose });
|
|
4830
|
+
if (result) {
|
|
4831
|
+
if (verbose) {
|
|
4832
|
+
log2.success(`Configuration loaded from home directory: ${result.source.path}`);
|
|
4833
|
+
}
|
|
4834
|
+
return result;
|
|
4835
|
+
}
|
|
4836
|
+
}
|
|
4837
|
+
return null;
|
|
4838
|
+
}
|
|
4839
|
+
async loadPackageJsonConfiguration(name, alias, baseDir, defaultConfig3, arrayStrategy, verbose, checkEnv) {
|
|
4840
|
+
const envDefaultConfig = checkEnv ? applyEnvVarsToConfig2(name, defaultConfig3, verbose) : defaultConfig3;
|
|
4841
|
+
try {
|
|
4842
|
+
const pkgPath = resolve7(baseDir, "package.json");
|
|
4843
|
+
if (!existsSync8(pkgPath)) {
|
|
4844
|
+
return null;
|
|
4845
|
+
}
|
|
4846
|
+
const pkg = await import(pkgPath);
|
|
4847
|
+
let pkgConfig = pkg[name];
|
|
4848
|
+
let usedName = name;
|
|
4849
|
+
if (!pkgConfig && alias) {
|
|
4850
|
+
pkgConfig = pkg[alias];
|
|
4851
|
+
usedName = alias;
|
|
4852
|
+
}
|
|
4853
|
+
if (pkgConfig && typeof pkgConfig === "object" && !Array.isArray(pkgConfig)) {
|
|
4854
|
+
if (verbose) {
|
|
4855
|
+
log2.success(`Configuration loaded from package.json: ${usedName}`);
|
|
4856
|
+
}
|
|
4857
|
+
const mergedConfig = deepMergeWithArrayStrategy2(envDefaultConfig, pkgConfig, arrayStrategy);
|
|
4858
|
+
return {
|
|
4859
|
+
config: mergedConfig,
|
|
4860
|
+
source: {
|
|
4861
|
+
type: "package.json",
|
|
4862
|
+
path: pkgPath,
|
|
4863
|
+
priority: 30,
|
|
4864
|
+
timestamp: new Date
|
|
4865
|
+
}
|
|
4866
|
+
};
|
|
4867
|
+
}
|
|
4868
|
+
} catch (error) {
|
|
4869
|
+
if (verbose) {
|
|
4870
|
+
log2.warn(`Failed to load package.json:`, [error instanceof Error ? error : new Error(String(error))]);
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
return null;
|
|
4874
|
+
}
|
|
4875
|
+
async applyEnvironmentVariables(name, config3, checkEnv, verbose) {
|
|
4876
|
+
if (!checkEnv || !name || typeof config3 !== "object" || config3 === null || Array.isArray(config3)) {
|
|
4877
|
+
return {
|
|
4878
|
+
config: config3,
|
|
4879
|
+
source: {
|
|
4880
|
+
type: "default",
|
|
4881
|
+
priority: 10,
|
|
4882
|
+
timestamp: new Date
|
|
4883
|
+
}
|
|
4884
|
+
};
|
|
4885
|
+
}
|
|
4886
|
+
const processedConfig = applyEnvVarsToConfig2(name, config3, verbose);
|
|
4887
|
+
return {
|
|
4888
|
+
config: processedConfig,
|
|
4889
|
+
source: {
|
|
4890
|
+
type: "environment",
|
|
4891
|
+
priority: 20,
|
|
4892
|
+
timestamp: new Date
|
|
4893
|
+
}
|
|
4894
|
+
};
|
|
4895
|
+
}
|
|
4896
|
+
async finalizeResult(result, _searchPaths, _checkEnv, _name, _verbose) {
|
|
4897
|
+
return {
|
|
4898
|
+
config: result.config,
|
|
4899
|
+
source: result.source,
|
|
4900
|
+
path: result.source.path
|
|
4901
|
+
};
|
|
4902
|
+
}
|
|
4903
|
+
async validateConfiguration(config3, schema, customValidator, configName) {
|
|
4904
|
+
const errors = [];
|
|
4905
|
+
if (customValidator) {
|
|
4906
|
+
const customErrors = customValidator(config3);
|
|
4907
|
+
if (customErrors) {
|
|
4908
|
+
errors.push(...customErrors);
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
if (schema) {
|
|
4912
|
+
const validationResult = await this.validator.validateConfiguration(config3, schema);
|
|
4913
|
+
if (!validationResult.isValid) {
|
|
4914
|
+
errors.push(...validationResult.errors.map((e) => e.path ? `${e.path}: ${e.message}` : e.message));
|
|
4915
|
+
}
|
|
4916
|
+
}
|
|
4917
|
+
if (errors.length > 0) {
|
|
4918
|
+
throw ErrorFactory.configValidation(configName || "unknown", errors, configName);
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
checkCache(configName, options) {
|
|
4922
|
+
const cacheKey = this.generateCacheKey(configName, options);
|
|
4923
|
+
const result = globalCache.get(cacheKey);
|
|
4924
|
+
return result || null;
|
|
4925
|
+
}
|
|
4926
|
+
cacheResult(configName, result, cacheOptions, options) {
|
|
4927
|
+
const cacheKey = this.generateCacheKey(configName, options);
|
|
4928
|
+
globalCache.set(cacheKey, result, undefined, cacheOptions.ttl);
|
|
4929
|
+
}
|
|
4930
|
+
generateCacheKey(configName, options) {
|
|
4931
|
+
const keyParts = [configName];
|
|
4932
|
+
if (options.alias)
|
|
4933
|
+
keyParts.push(`alias:${options.alias}`);
|
|
4934
|
+
if (options.cwd)
|
|
4935
|
+
keyParts.push(`cwd:${options.cwd}`);
|
|
4936
|
+
if (options.configDir)
|
|
4937
|
+
keyParts.push(`configDir:${options.configDir}`);
|
|
4938
|
+
if ("checkEnv" in options)
|
|
4939
|
+
keyParts.push(`checkEnv:${options.checkEnv}`);
|
|
4940
|
+
return keyParts.join("|");
|
|
4941
|
+
}
|
|
4942
|
+
getLocalDirectories(baseDir, configDir) {
|
|
4943
|
+
return Array.from(new Set([
|
|
4944
|
+
baseDir,
|
|
4945
|
+
resolve7(baseDir, "config"),
|
|
4946
|
+
resolve7(baseDir, ".config"),
|
|
4947
|
+
configDir ? resolve7(baseDir, configDir) : undefined
|
|
4948
|
+
].filter(Boolean)));
|
|
4949
|
+
}
|
|
4950
|
+
getAllSearchPaths(name, alias, baseDir, configDir) {
|
|
4951
|
+
const paths = [];
|
|
4952
|
+
paths.push(...this.getLocalSearchPaths(name, alias, baseDir, configDir));
|
|
4953
|
+
paths.push(...this.getHomeSearchPaths(name, alias));
|
|
4954
|
+
paths.push(resolve7(baseDir, "package.json"));
|
|
4955
|
+
return paths;
|
|
4956
|
+
}
|
|
4957
|
+
getLocalSearchPaths(name, alias, baseDir, configDir) {
|
|
4958
|
+
const directories = this.getLocalDirectories(baseDir, configDir);
|
|
4959
|
+
const paths = [];
|
|
4960
|
+
for (const directory of directories) {
|
|
4961
|
+
paths.push(...this.fileLoader.generateConfigPaths(name, directory, alias));
|
|
4962
|
+
}
|
|
4963
|
+
return paths;
|
|
4964
|
+
}
|
|
4965
|
+
getHomeSearchPaths(name, alias) {
|
|
4966
|
+
if (!name)
|
|
4967
|
+
return [];
|
|
4968
|
+
const homeDirectories = [
|
|
4969
|
+
resolve7(homedir2(), ".config", name),
|
|
4970
|
+
resolve7(homedir2(), ".config"),
|
|
4971
|
+
homedir2()
|
|
4972
|
+
];
|
|
4973
|
+
const paths = [];
|
|
4974
|
+
for (const directory of homeDirectories) {
|
|
4975
|
+
paths.push(...this.fileLoader.generateConfigPaths(name, directory, alias));
|
|
4976
|
+
}
|
|
4977
|
+
return paths;
|
|
4978
|
+
}
|
|
4979
|
+
async loadConfigWithResult(options) {
|
|
4980
|
+
return this.loadConfig(options);
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4983
|
+
var globalConfigLoader = new ConfigLoader;
|
|
4984
|
+
async function loadConfig5(options) {
|
|
4985
|
+
const defaultConfig3 = "defaultConfig" in options && options.defaultConfig !== undefined ? options.defaultConfig : {};
|
|
4986
|
+
const isEnhanced = "cache" in options || "performance" in options || "schema" in options || "validate" in options;
|
|
4987
|
+
try {
|
|
4988
|
+
let result;
|
|
4989
|
+
if (isEnhanced) {
|
|
4990
|
+
result = await globalConfigLoader.loadConfig(options);
|
|
4991
|
+
} else {
|
|
4992
|
+
result = await globalConfigLoader.loadConfig({
|
|
4993
|
+
...options,
|
|
4994
|
+
defaultConfig: defaultConfig3,
|
|
4995
|
+
cache: { enabled: true },
|
|
4996
|
+
performance: { enabled: false }
|
|
4997
|
+
});
|
|
4998
|
+
}
|
|
4999
|
+
return result?.config ?? defaultConfig3;
|
|
5000
|
+
} catch (error) {
|
|
5001
|
+
const errorName = error instanceof Error ? error.name : "UnknownError";
|
|
5002
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5003
|
+
const isConfigError = errorName === "ConfigNotFoundError" || errorName === "ConfigLoadError" || errorName === "ConfigValidationError" || errorMessage.includes("config");
|
|
5004
|
+
if (!isConfigError && options.verbose) {
|
|
5005
|
+
log2.warn(`Unexpected error loading config, using defaults:`, [error instanceof Error ? error : new Error(String(error))]);
|
|
5006
|
+
}
|
|
5007
|
+
const configOptions = isEnhanced ? { ...options, defaultConfig: defaultConfig3 } : {
|
|
5008
|
+
...options,
|
|
5009
|
+
defaultConfig: defaultConfig3,
|
|
5010
|
+
cache: { enabled: true },
|
|
5011
|
+
performance: { enabled: false }
|
|
5012
|
+
};
|
|
5013
|
+
const shouldCheckEnv = "checkEnv" in options ? options.checkEnv !== false : true;
|
|
5014
|
+
if (shouldCheckEnv) {
|
|
5015
|
+
const envResult = await globalConfigLoader.applyEnvironmentVariables(configOptions.name || "", defaultConfig3, true, configOptions.verbose || false);
|
|
5016
|
+
return envResult?.config ?? defaultConfig3;
|
|
5017
|
+
}
|
|
5018
|
+
return defaultConfig3;
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
function applyEnvVarsToConfig2(name, config4, verbose = false) {
|
|
5022
|
+
const _envProcessor = new EnvProcessor;
|
|
5023
|
+
const envPrefix = name.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
5024
|
+
function processConfigLevel(obj, path = []) {
|
|
5025
|
+
const result = { ...obj };
|
|
5026
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
5027
|
+
const currentPath = [...path, key];
|
|
5028
|
+
const envKeys = [
|
|
5029
|
+
`${envPrefix}_${currentPath.join("_").toUpperCase()}`,
|
|
5030
|
+
`${envPrefix}_${currentPath.map((k) => k.toUpperCase()).join("")}`,
|
|
5031
|
+
`${envPrefix}_${currentPath.map((k) => k.replace(/([A-Z])/g, "_$1").toUpperCase()).join("")}`
|
|
5032
|
+
];
|
|
5033
|
+
let envValue;
|
|
5034
|
+
let usedKey;
|
|
5035
|
+
for (const envKey of envKeys) {
|
|
5036
|
+
envValue = process12.env[envKey];
|
|
5037
|
+
if (envValue !== undefined) {
|
|
5038
|
+
usedKey = envKey;
|
|
5039
|
+
break;
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
if (envValue !== undefined && usedKey) {
|
|
5043
|
+
if (verbose) {}
|
|
5044
|
+
if (typeof value === "boolean") {
|
|
5045
|
+
result[key] = ["true", "1", "yes"].includes(envValue.toLowerCase());
|
|
5046
|
+
} else if (typeof value === "number") {
|
|
5047
|
+
const parsed = Number(envValue);
|
|
5048
|
+
if (!Number.isNaN(parsed)) {
|
|
5049
|
+
result[key] = parsed;
|
|
5050
|
+
}
|
|
5051
|
+
} else if (Array.isArray(value)) {
|
|
5052
|
+
try {
|
|
5053
|
+
result[key] = JSON.parse(envValue);
|
|
5054
|
+
} catch {
|
|
5055
|
+
result[key] = envValue.split(",").map((s) => s.trim());
|
|
5056
|
+
}
|
|
5057
|
+
} else {
|
|
5058
|
+
result[key] = envValue;
|
|
5059
|
+
}
|
|
5060
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
5061
|
+
result[key] = processConfigLevel(value, currentPath);
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
return result;
|
|
5065
|
+
}
|
|
5066
|
+
return processConfigLevel(config4);
|
|
5067
|
+
}
|
|
5068
|
+
var defaultConfigDir3 = resolve7(process12.cwd(), "config");
|
|
5069
|
+
var defaultGeneratedDir3 = resolve7(process12.cwd(), "src/generated");
|
|
5070
|
+
|
|
5071
|
+
// src/config.ts
|
|
5072
|
+
var defaultConfig3 = {
|
|
1858
5073
|
verbose: true
|
|
1859
5074
|
};
|
|
1860
|
-
var
|
|
5075
|
+
var config3 = await loadConfig5({
|
|
1861
5076
|
name: "development",
|
|
1862
5077
|
alias: "dx",
|
|
1863
|
-
defaultConfig:
|
|
5078
|
+
defaultConfig: defaultConfig3
|
|
1864
5079
|
});
|
|
1865
5080
|
export {
|
|
1866
|
-
|
|
1867
|
-
|
|
5081
|
+
defaultConfig3 as defaultConfig,
|
|
5082
|
+
config3 as config
|
|
1868
5083
|
};
|