bun-query-builder 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +3493 -286
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -28,19 +28,26 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
28
28
|
var __promiseAll = (args) => Promise.all(args);
|
|
29
29
|
var __require = import.meta.require;
|
|
30
30
|
|
|
31
|
-
// ../../node_modules/.bun/bunfig@0.15.
|
|
31
|
+
// ../../node_modules/.bun/bunfig@0.15.6/node_modules/bunfig/dist/index.js
|
|
32
|
+
import { existsSync, statSync } from "fs";
|
|
33
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
34
|
+
import { homedir as homedir2 } from "os";
|
|
35
|
+
import { dirname as dirname3, resolve as resolve7 } from "path";
|
|
36
|
+
import process12 from "process";
|
|
37
|
+
import { join as join3, relative as relative2, resolve as resolve4 } from "path";
|
|
38
|
+
import process7 from "process";
|
|
32
39
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
33
40
|
import { homedir } from "os";
|
|
34
41
|
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
35
42
|
import process6 from "process";
|
|
36
43
|
import { join, relative, resolve as resolve2 } from "path";
|
|
37
44
|
import process2 from "process";
|
|
38
|
-
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
|
|
45
|
+
import { existsSync as existsSync2, mkdirSync, readdirSync, writeFileSync } from "fs";
|
|
39
46
|
import { dirname, resolve } from "path";
|
|
40
47
|
import process from "process";
|
|
41
48
|
import { Buffer } from "buffer";
|
|
42
49
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
43
|
-
import { closeSync, createReadStream, createWriteStream, existsSync as
|
|
50
|
+
import { closeSync, createReadStream, createWriteStream, existsSync as existsSync22, fsyncSync, openSync, writeFileSync as writeFileSync2 } from "fs";
|
|
44
51
|
import { access, constants, mkdir, readdir, rename, stat, unlink, writeFile } from "fs/promises";
|
|
45
52
|
import { join as join2 } from "path";
|
|
46
53
|
import process5 from "process";
|
|
@@ -48,6 +55,259 @@ import { pipeline } from "stream/promises";
|
|
|
48
55
|
import { createGzip } from "zlib";
|
|
49
56
|
import process4 from "process";
|
|
50
57
|
import process3 from "process";
|
|
58
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
59
|
+
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
|
|
60
|
+
import { closeSync as closeSync2, createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync4, fsyncSync as fsyncSync2, openSync as openSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
61
|
+
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";
|
|
62
|
+
import { isAbsolute, join as join5, resolve as resolve6 } from "path";
|
|
63
|
+
import process11 from "process";
|
|
64
|
+
import { pipeline as pipeline2 } from "stream/promises";
|
|
65
|
+
import { createGzip as createGzip2 } from "zlib";
|
|
66
|
+
import process10 from "process";
|
|
67
|
+
import process9 from "process";
|
|
68
|
+
import process8 from "process";
|
|
69
|
+
import { existsSync as existsSync5 } from "fs";
|
|
70
|
+
import { resolve as resolve5 } from "path";
|
|
71
|
+
import { existsSync as existsSync7 } from "fs";
|
|
72
|
+
|
|
73
|
+
class ConfigCache {
|
|
74
|
+
cache = new Map;
|
|
75
|
+
totalHits = 0;
|
|
76
|
+
totalMisses = 0;
|
|
77
|
+
options;
|
|
78
|
+
constructor(options = {}) {
|
|
79
|
+
this.options = {
|
|
80
|
+
enabled: true,
|
|
81
|
+
ttl: 5 * 60 * 1000,
|
|
82
|
+
maxSize: 100,
|
|
83
|
+
keyPrefix: "bunfig:",
|
|
84
|
+
...options
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
generateKey(configName, configPath) {
|
|
88
|
+
const pathKey = configPath ? `:${configPath}` : "";
|
|
89
|
+
return `${this.options.keyPrefix}${configName}${pathKey}`;
|
|
90
|
+
}
|
|
91
|
+
isExpired(entry) {
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
const age = now - entry.timestamp.getTime();
|
|
94
|
+
const expired = age > entry.ttl;
|
|
95
|
+
return expired;
|
|
96
|
+
}
|
|
97
|
+
estimateSize(value) {
|
|
98
|
+
try {
|
|
99
|
+
return JSON.stringify(value).length;
|
|
100
|
+
} catch {
|
|
101
|
+
return 1000;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
evictIfNeeded() {
|
|
105
|
+
if (this.cache.size <= this.options.maxSize) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const entries = Array.from(this.cache.entries()).sort(([, a], [, b]) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
109
|
+
const toRemove = entries.length - this.options.maxSize + 1;
|
|
110
|
+
for (let i = 0;i < toRemove; i++) {
|
|
111
|
+
this.cache.delete(entries[i][0]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
set(configName, value, configPath, customTtl) {
|
|
115
|
+
if (!this.options.enabled) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const key = this.generateKey(configName, configPath);
|
|
119
|
+
const ttl = customTtl ?? this.options.ttl;
|
|
120
|
+
const size = this.estimateSize(value);
|
|
121
|
+
this.cache.set(key, {
|
|
122
|
+
value,
|
|
123
|
+
timestamp: new Date,
|
|
124
|
+
ttl,
|
|
125
|
+
hits: 0,
|
|
126
|
+
size
|
|
127
|
+
});
|
|
128
|
+
this.evictIfNeeded();
|
|
129
|
+
}
|
|
130
|
+
get(configName, configPath) {
|
|
131
|
+
if (!this.options.enabled) {
|
|
132
|
+
this.totalMisses++;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const key = this.generateKey(configName, configPath);
|
|
136
|
+
const entry = this.cache.get(key);
|
|
137
|
+
if (!entry) {
|
|
138
|
+
this.totalMisses++;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (this.isExpired(entry)) {
|
|
142
|
+
this.cache.delete(key);
|
|
143
|
+
this.totalMisses++;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
entry.hits++;
|
|
147
|
+
this.totalHits++;
|
|
148
|
+
return entry.value;
|
|
149
|
+
}
|
|
150
|
+
isFileModified(configPath, cachedTimestamp) {
|
|
151
|
+
try {
|
|
152
|
+
if (!existsSync(configPath)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
const stats = statSync(configPath);
|
|
156
|
+
return stats.mtime > cachedTimestamp;
|
|
157
|
+
} catch {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
getWithFileCheck(configName, configPath) {
|
|
162
|
+
const cached = this.get(configName, configPath);
|
|
163
|
+
if (!cached) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (this.isFileModified(configPath, cached.fileTimestamp)) {
|
|
167
|
+
this.delete(configName, configPath);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
return cached.value;
|
|
171
|
+
}
|
|
172
|
+
setWithFileCheck(configName, value, configPath, customTtl) {
|
|
173
|
+
try {
|
|
174
|
+
const stats = existsSync(configPath) ? statSync(configPath) : null;
|
|
175
|
+
const fileTimestamp = stats ? stats.mtime : new Date;
|
|
176
|
+
this.set(configName, { value, fileTimestamp }, configPath, customTtl);
|
|
177
|
+
} catch {
|
|
178
|
+
this.set(configName, value, configPath, customTtl);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
delete(configName, configPath) {
|
|
182
|
+
const key = this.generateKey(configName, configPath);
|
|
183
|
+
return this.cache.delete(key);
|
|
184
|
+
}
|
|
185
|
+
clear() {
|
|
186
|
+
this.cache.clear();
|
|
187
|
+
this.totalHits = 0;
|
|
188
|
+
this.totalMisses = 0;
|
|
189
|
+
}
|
|
190
|
+
cleanup() {
|
|
191
|
+
let cleaned = 0;
|
|
192
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
193
|
+
if (this.isExpired(entry)) {
|
|
194
|
+
this.cache.delete(key);
|
|
195
|
+
cleaned++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return cleaned;
|
|
199
|
+
}
|
|
200
|
+
getStats() {
|
|
201
|
+
const entries = Array.from(this.cache.values());
|
|
202
|
+
const totalSize = entries.reduce((sum, entry) => sum + entry.size, 0);
|
|
203
|
+
const timestamps = entries.map((entry) => entry.timestamp).sort();
|
|
204
|
+
return {
|
|
205
|
+
size: totalSize,
|
|
206
|
+
maxSize: this.options.maxSize,
|
|
207
|
+
hitRate: this.totalHits + this.totalMisses > 0 ? this.totalHits / (this.totalHits + this.totalMisses) : 0,
|
|
208
|
+
totalHits: this.totalHits,
|
|
209
|
+
totalMisses: this.totalMisses,
|
|
210
|
+
entries: this.cache.size,
|
|
211
|
+
oldestEntry: timestamps[0],
|
|
212
|
+
newestEntry: timestamps[timestamps.length - 1]
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export() {
|
|
216
|
+
const data = {};
|
|
217
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
218
|
+
data[key] = {
|
|
219
|
+
value: entry.value,
|
|
220
|
+
timestamp: entry.timestamp.toISOString(),
|
|
221
|
+
ttl: entry.ttl,
|
|
222
|
+
hits: entry.hits,
|
|
223
|
+
size: entry.size
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return data;
|
|
227
|
+
}
|
|
228
|
+
import(data) {
|
|
229
|
+
this.cache.clear();
|
|
230
|
+
for (const [key, entryData] of Object.entries(data)) {
|
|
231
|
+
if (typeof entryData === "object" && entryData !== null) {
|
|
232
|
+
const entry = entryData;
|
|
233
|
+
this.cache.set(key, {
|
|
234
|
+
value: entry.value,
|
|
235
|
+
timestamp: new Date(entry.timestamp),
|
|
236
|
+
ttl: entry.ttl,
|
|
237
|
+
hits: entry.hits,
|
|
238
|
+
size: entry.size
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
class PerformanceMonitor {
|
|
246
|
+
metrics = [];
|
|
247
|
+
maxMetrics = 1000;
|
|
248
|
+
async track(operation, fn, context = {}) {
|
|
249
|
+
const start = performance.now();
|
|
250
|
+
const startTime = new Date;
|
|
251
|
+
try {
|
|
252
|
+
const result = await fn();
|
|
253
|
+
const duration = performance.now() - start;
|
|
254
|
+
this.recordMetric({
|
|
255
|
+
operation,
|
|
256
|
+
duration,
|
|
257
|
+
timestamp: startTime,
|
|
258
|
+
...context
|
|
259
|
+
});
|
|
260
|
+
return result;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
const duration = performance.now() - start;
|
|
263
|
+
this.recordMetric({
|
|
264
|
+
operation: `${operation}:error`,
|
|
265
|
+
duration,
|
|
266
|
+
timestamp: startTime,
|
|
267
|
+
...context
|
|
268
|
+
});
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
recordMetric(metric) {
|
|
273
|
+
this.metrics.push(metric);
|
|
274
|
+
if (this.metrics.length > this.maxMetrics) {
|
|
275
|
+
this.metrics = this.metrics.slice(-this.maxMetrics);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
getStats(operation) {
|
|
279
|
+
const filteredMetrics = operation ? this.metrics.filter((m) => m.operation === operation) : this.metrics;
|
|
280
|
+
if (filteredMetrics.length === 0) {
|
|
281
|
+
return {
|
|
282
|
+
count: 0,
|
|
283
|
+
averageDuration: 0,
|
|
284
|
+
minDuration: 0,
|
|
285
|
+
maxDuration: 0,
|
|
286
|
+
totalDuration: 0,
|
|
287
|
+
recentMetrics: []
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const durations = filteredMetrics.map((m) => m.duration);
|
|
291
|
+
const totalDuration = durations.reduce((sum, d) => sum + d, 0);
|
|
292
|
+
return {
|
|
293
|
+
count: filteredMetrics.length,
|
|
294
|
+
averageDuration: totalDuration / filteredMetrics.length,
|
|
295
|
+
minDuration: Math.min(...durations),
|
|
296
|
+
maxDuration: Math.max(...durations),
|
|
297
|
+
totalDuration,
|
|
298
|
+
recentMetrics: filteredMetrics.slice(-10)
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
getAllMetrics() {
|
|
302
|
+
return [...this.metrics];
|
|
303
|
+
}
|
|
304
|
+
clearMetrics() {
|
|
305
|
+
this.metrics = [];
|
|
306
|
+
}
|
|
307
|
+
getSlowOperations(threshold) {
|
|
308
|
+
return this.metrics.filter((m) => m.duration > threshold);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
51
311
|
function deepMerge(target, source) {
|
|
52
312
|
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) {
|
|
53
313
|
return source;
|
|
@@ -174,7 +434,7 @@ function isObject(item) {
|
|
|
174
434
|
return Boolean(item && typeof item === "object" && !Array.isArray(item));
|
|
175
435
|
}
|
|
176
436
|
async function tryLoadConfig(configPath, defaultConfig) {
|
|
177
|
-
if (!
|
|
437
|
+
if (!existsSync2(configPath))
|
|
178
438
|
return null;
|
|
179
439
|
try {
|
|
180
440
|
const importedConfig = await import(configPath);
|
|
@@ -214,7 +474,7 @@ async function loadConfig({
|
|
|
214
474
|
}
|
|
215
475
|
try {
|
|
216
476
|
const pkgPath = resolve(baseDir, "package.json");
|
|
217
|
-
if (
|
|
477
|
+
if (existsSync2(pkgPath)) {
|
|
218
478
|
const pkg = await import(pkgPath);
|
|
219
479
|
const pkgConfig = pkg[name];
|
|
220
480
|
if (pkgConfig && typeof pkgConfig === "object" && !Array.isArray(pkgConfig)) {
|
|
@@ -443,7 +703,7 @@ class Logger {
|
|
|
443
703
|
throw new Error("Operation cancelled: Logger was destroyed");
|
|
444
704
|
const dataToWrite = this.validateEncryptionConfig() ? (await this.encrypt(data)).encrypted : Buffer.from(data);
|
|
445
705
|
try {
|
|
446
|
-
if (!
|
|
706
|
+
if (!existsSync22(this.currentLogFile)) {
|
|
447
707
|
await writeFile(this.currentLogFile, "", { mode: 420 });
|
|
448
708
|
}
|
|
449
709
|
fd = openSync(this.currentLogFile, "a", 420);
|
|
@@ -779,7 +1039,7 @@ class Logger {
|
|
|
779
1039
|
}
|
|
780
1040
|
return Promise.resolve();
|
|
781
1041
|
}));
|
|
782
|
-
if (
|
|
1042
|
+
if (existsSync22(this.currentLogFile)) {
|
|
783
1043
|
try {
|
|
784
1044
|
const fd = openSync(this.currentLogFile, "r+");
|
|
785
1045
|
fsyncSync(fd);
|
|
@@ -1099,7 +1359,7 @@ class Logger {
|
|
|
1099
1359
|
createReadStream() {
|
|
1100
1360
|
if (isBrowserProcess())
|
|
1101
1361
|
throw new Error("createReadStream is not supported in browser environments");
|
|
1102
|
-
if (!
|
|
1362
|
+
if (!existsSync22(this.currentLogFile))
|
|
1103
1363
|
throw new Error(`Log file does not exist: ${this.currentLogFile}`);
|
|
1104
1364
|
return createReadStream(this.currentLogFile, { encoding: "utf8" });
|
|
1105
1365
|
}
|
|
@@ -1709,84 +1969,2772 @@ async function loadConfig3({
|
|
|
1709
1969
|
return config3;
|
|
1710
1970
|
}
|
|
1711
1971
|
}
|
|
1712
|
-
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
if (name) {
|
|
1975
|
+
const homeConfigDir = resolve3(homedir(), ".config");
|
|
1976
|
+
const homeConfigDotfilePatterns = [`.${name}.config`];
|
|
1977
|
+
if (alias)
|
|
1978
|
+
homeConfigDotfilePatterns.push(`.${alias}.config`);
|
|
1979
|
+
if (verbose)
|
|
1980
|
+
log.info(`Checking user config directory for dotfile configs: ${homeConfigDir}`);
|
|
1981
|
+
for (const configPath of homeConfigDotfilePatterns) {
|
|
1982
|
+
for (const ext of extensions) {
|
|
1983
|
+
const fullPath = resolve3(homeConfigDir, `${configPath}${ext}`);
|
|
1984
|
+
const config3 = await tryLoadConfig2(fullPath, configWithEnvVars, arrayStrategy);
|
|
1985
|
+
if (config3 !== null) {
|
|
1986
|
+
if (verbose)
|
|
1987
|
+
log.success(`Configuration loaded from user config directory dotfile: ${fullPath}`);
|
|
1988
|
+
return config3;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
if (name) {
|
|
1994
|
+
const homeDir = homedir();
|
|
1995
|
+
const homeRootPatterns = [`.${name}.config`, `.${name}`];
|
|
1996
|
+
if (alias) {
|
|
1997
|
+
homeRootPatterns.push(`.${alias}.config`);
|
|
1998
|
+
homeRootPatterns.push(`.${alias}`);
|
|
1999
|
+
}
|
|
2000
|
+
if (verbose)
|
|
2001
|
+
log.info(`Checking user home directory for dotfile configs: ${homeDir}`);
|
|
2002
|
+
for (const configPath of homeRootPatterns) {
|
|
2003
|
+
for (const ext of extensions) {
|
|
2004
|
+
const fullPath = resolve3(homeDir, `${configPath}${ext}`);
|
|
2005
|
+
const config3 = await tryLoadConfig2(fullPath, configWithEnvVars, arrayStrategy);
|
|
2006
|
+
if (config3 !== null) {
|
|
2007
|
+
if (verbose)
|
|
2008
|
+
log.success(`Configuration loaded from user home directory: ${fullPath}`);
|
|
2009
|
+
return config3;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
try {
|
|
2015
|
+
const pkgPath = resolve3(baseDir, "package.json");
|
|
2016
|
+
if (existsSync3(pkgPath)) {
|
|
2017
|
+
const pkg = await import(pkgPath);
|
|
2018
|
+
let pkgConfig = pkg[name];
|
|
2019
|
+
if (!pkgConfig && alias) {
|
|
2020
|
+
pkgConfig = pkg[alias];
|
|
2021
|
+
if (pkgConfig && verbose) {
|
|
2022
|
+
log.success(`Using alias "${alias}" configuration from package.json`);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
if (pkgConfig && typeof pkgConfig === "object" && !Array.isArray(pkgConfig)) {
|
|
2026
|
+
try {
|
|
2027
|
+
if (verbose) {
|
|
2028
|
+
log.success(`Configuration loaded from package.json: ${pkgConfig === pkg[name] ? name : alias}`);
|
|
2029
|
+
}
|
|
2030
|
+
return deepMergeWithArrayStrategy(configWithEnvVars, pkgConfig, arrayStrategy);
|
|
2031
|
+
} catch (error) {
|
|
2032
|
+
if (verbose) {
|
|
2033
|
+
log.warn(`Failed to merge package.json config:`, error);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
} catch (error) {
|
|
2039
|
+
if (verbose) {
|
|
2040
|
+
log.warn(`Failed to load package.json:`, error);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
if (verbose) {
|
|
2044
|
+
log.info(`No configuration found for "${name}"${alias ? ` or alias "${alias}"` : ""}, using default configuration with environment variables`);
|
|
2045
|
+
}
|
|
2046
|
+
return configWithEnvVars;
|
|
2047
|
+
}
|
|
2048
|
+
function getProjectRoot2(filePath, options = {}) {
|
|
2049
|
+
let path = process7.cwd();
|
|
2050
|
+
while (path.includes("storage"))
|
|
2051
|
+
path = resolve4(path, "..");
|
|
2052
|
+
const finalPath = resolve4(path, filePath || "");
|
|
2053
|
+
if (options?.relative)
|
|
2054
|
+
return relative2(process7.cwd(), finalPath);
|
|
2055
|
+
return finalPath;
|
|
2056
|
+
}
|
|
2057
|
+
async function loadConfig4() {
|
|
2058
|
+
try {
|
|
2059
|
+
const loadedConfig = await loadConfig3({
|
|
2060
|
+
name: "clarity",
|
|
2061
|
+
alias: "logging",
|
|
2062
|
+
defaultConfig: defaultConfig2,
|
|
2063
|
+
cwd: process7.cwd()
|
|
2064
|
+
});
|
|
2065
|
+
return { ...defaultConfig2, ...loadedConfig || {} };
|
|
2066
|
+
} catch {
|
|
2067
|
+
return defaultConfig2;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
function isBrowserProcess2() {
|
|
2071
|
+
if (process9.env.NODE_ENV === "test" || process9.env.BUN_ENV === "test") {
|
|
2072
|
+
return false;
|
|
2073
|
+
}
|
|
2074
|
+
return typeof window !== "undefined";
|
|
2075
|
+
}
|
|
2076
|
+
async function isServerProcess2() {
|
|
2077
|
+
if (process9.env.NODE_ENV === "test" || process9.env.BUN_ENV === "test") {
|
|
2078
|
+
return true;
|
|
2079
|
+
}
|
|
2080
|
+
if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
|
|
2081
|
+
return true;
|
|
2082
|
+
}
|
|
2083
|
+
if (typeof process9 !== "undefined") {
|
|
2084
|
+
const type = process9.type;
|
|
2085
|
+
if (type === "renderer" || type === "worker") {
|
|
2086
|
+
return false;
|
|
2087
|
+
}
|
|
2088
|
+
return !!(process9.versions && (process9.versions.node || process9.versions.bun));
|
|
2089
|
+
}
|
|
2090
|
+
return false;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
class JsonFormatter2 {
|
|
2094
|
+
async format(entry) {
|
|
2095
|
+
const isServer = await isServerProcess2();
|
|
2096
|
+
const metadata = await this.getMetadata(isServer);
|
|
2097
|
+
return JSON.stringify({
|
|
2098
|
+
timestamp: entry.timestamp.toISOString(),
|
|
2099
|
+
level: entry.level,
|
|
2100
|
+
name: entry.name,
|
|
2101
|
+
message: entry.message,
|
|
2102
|
+
metadata
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
async getMetadata(isServer) {
|
|
2106
|
+
if (isServer) {
|
|
2107
|
+
const { hostname } = await import("os");
|
|
2108
|
+
return {
|
|
2109
|
+
pid: process10.pid,
|
|
2110
|
+
hostname: hostname(),
|
|
2111
|
+
environment: process10.env.NODE_ENV || "development",
|
|
2112
|
+
platform: process10.platform,
|
|
2113
|
+
version: process10.version
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
return {
|
|
2117
|
+
userAgent: navigator.userAgent,
|
|
2118
|
+
hostname: window.location.hostname || "browser",
|
|
2119
|
+
environment: process10.env.NODE_ENV || process10.env.BUN_ENV || "development",
|
|
2120
|
+
viewport: {
|
|
2121
|
+
width: window.innerWidth,
|
|
2122
|
+
height: window.innerHeight
|
|
2123
|
+
},
|
|
2124
|
+
language: navigator.language
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
class Logger2 {
|
|
2130
|
+
name;
|
|
2131
|
+
fileLocks = new Map;
|
|
2132
|
+
currentKeyId = null;
|
|
2133
|
+
keys = new Map;
|
|
2134
|
+
fingersCrossedConfig;
|
|
2135
|
+
fingersCrossedActive = false;
|
|
2136
|
+
currentLogFile;
|
|
2137
|
+
rotationTimeout;
|
|
2138
|
+
keyRotationTimeout;
|
|
2139
|
+
encryptionKeys;
|
|
2140
|
+
logBuffer = [];
|
|
2141
|
+
isActivated = false;
|
|
2142
|
+
pendingOperations = [];
|
|
2143
|
+
enabled;
|
|
2144
|
+
fancy;
|
|
2145
|
+
tagFormat;
|
|
2146
|
+
timestampPosition;
|
|
2147
|
+
environment;
|
|
2148
|
+
config;
|
|
2149
|
+
options;
|
|
2150
|
+
formatter;
|
|
2151
|
+
timers = new Set;
|
|
2152
|
+
subLoggers = new Set;
|
|
2153
|
+
fingersCrossedBuffer = [];
|
|
2154
|
+
ANSI_PATTERN = /\u001B\[.*?m/g;
|
|
2155
|
+
activeProgressBar = null;
|
|
2156
|
+
constructor(name, options = {}) {
|
|
2157
|
+
this.name = name;
|
|
2158
|
+
this.config = { ...config2 };
|
|
2159
|
+
this.options = this.normalizeOptions(options);
|
|
2160
|
+
this.formatter = this.options.formatter || new JsonFormatter2;
|
|
2161
|
+
this.enabled = options.enabled ?? true;
|
|
2162
|
+
this.fancy = options.fancy ?? true;
|
|
2163
|
+
this.tagFormat = options.tagFormat ?? { prefix: "[", suffix: "]" };
|
|
2164
|
+
this.timestampPosition = options.timestampPosition ?? "right";
|
|
2165
|
+
this.environment = options.environment ?? process11.env.APP_ENV ?? "local";
|
|
2166
|
+
this.fingersCrossedConfig = this.initializeFingersCrossedConfig(options);
|
|
2167
|
+
const configOptions = { ...options };
|
|
2168
|
+
const hasTimestamp = options.timestamp !== undefined;
|
|
2169
|
+
if (hasTimestamp) {
|
|
2170
|
+
delete configOptions.timestamp;
|
|
2171
|
+
}
|
|
2172
|
+
this.config = {
|
|
2173
|
+
...this.config,
|
|
2174
|
+
...configOptions,
|
|
2175
|
+
timestamp: hasTimestamp || this.config.timestamp
|
|
2176
|
+
};
|
|
2177
|
+
this.currentLogFile = this.generateLogFilename();
|
|
2178
|
+
this.encryptionKeys = new Map;
|
|
2179
|
+
if (this.validateEncryptionConfig()) {
|
|
2180
|
+
this.setupRotation();
|
|
2181
|
+
const initialKeyId = this.generateKeyId();
|
|
2182
|
+
const initialKey = this.generateKey();
|
|
2183
|
+
this.currentKeyId = initialKeyId;
|
|
2184
|
+
this.keys.set(initialKeyId, initialKey);
|
|
2185
|
+
this.encryptionKeys.set(initialKeyId, {
|
|
2186
|
+
key: initialKey,
|
|
2187
|
+
createdAt: new Date
|
|
2188
|
+
});
|
|
2189
|
+
this.setupKeyRotation();
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
shouldActivateFingersCrossed(level) {
|
|
2193
|
+
if (!this.fingersCrossedConfig)
|
|
2194
|
+
return false;
|
|
2195
|
+
const levels = {
|
|
2196
|
+
debug: 0,
|
|
2197
|
+
info: 1,
|
|
2198
|
+
success: 2,
|
|
2199
|
+
warning: 3,
|
|
2200
|
+
error: 4
|
|
2201
|
+
};
|
|
2202
|
+
const activation = this.fingersCrossedConfig.activationLevel ?? "error";
|
|
2203
|
+
return levels[level] >= levels[activation];
|
|
2204
|
+
}
|
|
2205
|
+
initializeFingersCrossedConfig(options) {
|
|
2206
|
+
if (!options.fingersCrossedEnabled && options.fingersCrossed) {
|
|
2207
|
+
return {
|
|
2208
|
+
...defaultFingersCrossedConfig2,
|
|
2209
|
+
...options.fingersCrossed
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
if (!options.fingersCrossedEnabled) {
|
|
2213
|
+
return null;
|
|
2214
|
+
}
|
|
2215
|
+
if (!options.fingersCrossed) {
|
|
2216
|
+
return { ...defaultFingersCrossedConfig2 };
|
|
2217
|
+
}
|
|
2218
|
+
return {
|
|
2219
|
+
...defaultFingersCrossedConfig2,
|
|
2220
|
+
...options.fingersCrossed
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
normalizeOptions(options) {
|
|
2224
|
+
const defaultOptions = {
|
|
2225
|
+
format: "json",
|
|
2226
|
+
level: "info",
|
|
2227
|
+
logDirectory: config2.logDirectory,
|
|
2228
|
+
rotation: undefined,
|
|
2229
|
+
timestamp: undefined,
|
|
2230
|
+
fingersCrossed: {},
|
|
2231
|
+
enabled: true,
|
|
2232
|
+
showTags: false,
|
|
2233
|
+
showIcons: true,
|
|
2234
|
+
formatter: undefined
|
|
2235
|
+
};
|
|
2236
|
+
const mergedOptions = {
|
|
2237
|
+
...defaultOptions,
|
|
2238
|
+
...Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined))
|
|
2239
|
+
};
|
|
2240
|
+
if (!mergedOptions.level || !["debug", "info", "success", "warning", "error"].includes(mergedOptions.level)) {
|
|
2241
|
+
mergedOptions.level = defaultOptions.level;
|
|
2242
|
+
}
|
|
2243
|
+
return mergedOptions;
|
|
2244
|
+
}
|
|
2245
|
+
shouldWriteToFile() {
|
|
2246
|
+
return !isBrowserProcess2() && this.config.writeToFile === true;
|
|
2247
|
+
}
|
|
2248
|
+
async writeToFile(data) {
|
|
2249
|
+
const cancelled = false;
|
|
2250
|
+
const operationPromise = (async () => {
|
|
2251
|
+
let fd;
|
|
2252
|
+
let retries = 0;
|
|
2253
|
+
const maxRetries = 3;
|
|
2254
|
+
const backoffDelay = 1000;
|
|
2255
|
+
while (retries < maxRetries) {
|
|
2256
|
+
try {
|
|
2257
|
+
try {
|
|
2258
|
+
try {
|
|
2259
|
+
await access2(this.config.logDirectory, constants2.F_OK | constants2.W_OK);
|
|
2260
|
+
} catch (err) {
|
|
2261
|
+
if (err instanceof Error && "code" in err) {
|
|
2262
|
+
if (err.code === "ENOENT") {
|
|
2263
|
+
await mkdir2(this.config.logDirectory, { recursive: true, mode: 493 });
|
|
2264
|
+
} else if (err.code === "EACCES") {
|
|
2265
|
+
throw new Error(`No write permission for log directory: ${this.config.logDirectory}`);
|
|
2266
|
+
} else {
|
|
2267
|
+
throw err;
|
|
2268
|
+
}
|
|
2269
|
+
} else {
|
|
2270
|
+
throw err;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
} catch (err) {
|
|
2274
|
+
console.error("Debug: [writeToFile] Failed to create log directory:", err);
|
|
2275
|
+
throw err;
|
|
2276
|
+
}
|
|
2277
|
+
if (cancelled)
|
|
2278
|
+
throw new Error("Operation cancelled: Logger was destroyed");
|
|
2279
|
+
const dataToWrite = this.validateEncryptionConfig() ? (await this.encrypt(data)).encrypted : Buffer2.from(data);
|
|
2280
|
+
try {
|
|
2281
|
+
if (!existsSync4(this.currentLogFile)) {
|
|
2282
|
+
await writeFile2(this.currentLogFile, "", { mode: 420 });
|
|
2283
|
+
}
|
|
2284
|
+
fd = openSync2(this.currentLogFile, "a", 420);
|
|
2285
|
+
writeFileSync4(fd, dataToWrite, { flag: "a" });
|
|
2286
|
+
fsyncSync2(fd);
|
|
2287
|
+
if (fd !== undefined) {
|
|
2288
|
+
closeSync2(fd);
|
|
2289
|
+
fd = undefined;
|
|
2290
|
+
}
|
|
2291
|
+
const stats = await stat2(this.currentLogFile);
|
|
2292
|
+
if (stats.size === 0) {
|
|
2293
|
+
await writeFile2(this.currentLogFile, dataToWrite, { flag: "w", mode: 420 });
|
|
2294
|
+
const retryStats = await stat2(this.currentLogFile);
|
|
2295
|
+
if (retryStats.size === 0) {
|
|
2296
|
+
throw new Error("File exists but is empty after retry write");
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
return;
|
|
2300
|
+
} catch (err) {
|
|
2301
|
+
const error = err;
|
|
2302
|
+
if (error.code && ["ENETDOWN", "ENETUNREACH", "ENOTFOUND", "ETIMEDOUT"].includes(error.code)) {
|
|
2303
|
+
if (retries < maxRetries - 1) {
|
|
2304
|
+
const errorMessage = typeof error.message === "string" ? error.message : "Unknown error";
|
|
2305
|
+
console.error(`Network error during write attempt ${retries + 1}/${maxRetries}:`, errorMessage);
|
|
2306
|
+
const delay = backoffDelay * 2 ** retries;
|
|
2307
|
+
await new Promise((resolve52) => setTimeout(resolve52, delay));
|
|
2308
|
+
retries++;
|
|
2309
|
+
continue;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
if (error?.code && ["ENOSPC", "EDQUOT"].includes(error.code)) {
|
|
2313
|
+
throw new Error(`Disk quota exceeded or no space left on device: ${error.message}`);
|
|
2314
|
+
}
|
|
2315
|
+
console.error("Debug: [writeToFile] Error writing to file:", error);
|
|
2316
|
+
throw error;
|
|
2317
|
+
} finally {
|
|
2318
|
+
if (fd !== undefined) {
|
|
2319
|
+
try {
|
|
2320
|
+
closeSync2(fd);
|
|
2321
|
+
} catch (err) {
|
|
2322
|
+
console.error("Debug: [writeToFile] Error closing file descriptor:", err);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
} catch (err) {
|
|
2327
|
+
if (retries === maxRetries - 1) {
|
|
2328
|
+
const error = err;
|
|
2329
|
+
const errorMessage = typeof error.message === "string" ? error.message : "Unknown error";
|
|
2330
|
+
console.error("Debug: [writeToFile] Max retries reached. Final error:", errorMessage);
|
|
2331
|
+
throw err;
|
|
2332
|
+
}
|
|
2333
|
+
retries++;
|
|
2334
|
+
const delay = backoffDelay * 2 ** (retries - 1);
|
|
2335
|
+
await new Promise((resolve52) => setTimeout(resolve52, delay));
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
})();
|
|
2339
|
+
this.pendingOperations.push(operationPromise);
|
|
2340
|
+
const index = this.pendingOperations.length - 1;
|
|
2341
|
+
try {
|
|
2342
|
+
await operationPromise;
|
|
2343
|
+
} catch (err) {
|
|
2344
|
+
console.error("Debug: [writeToFile] Error in operation:", err);
|
|
2345
|
+
throw err;
|
|
2346
|
+
} finally {
|
|
2347
|
+
this.pendingOperations.splice(index, 1);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
generateLogFilename() {
|
|
2351
|
+
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")) {
|
|
2352
|
+
return join5(this.config.logDirectory, `${this.name}.log`);
|
|
2353
|
+
}
|
|
2354
|
+
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") {
|
|
2355
|
+
return join5(this.config.logDirectory, `${this.name}.log`);
|
|
2356
|
+
}
|
|
2357
|
+
const date = new Date().toISOString().split("T")[0];
|
|
2358
|
+
return join5(this.config.logDirectory, `${this.name}-${date}.log`);
|
|
2359
|
+
}
|
|
2360
|
+
setupRotation() {
|
|
2361
|
+
if (isBrowserProcess2())
|
|
2362
|
+
return;
|
|
2363
|
+
if (!this.shouldWriteToFile())
|
|
2364
|
+
return;
|
|
2365
|
+
if (typeof this.config.rotation === "boolean")
|
|
2366
|
+
return;
|
|
2367
|
+
const config3 = this.config.rotation;
|
|
2368
|
+
let interval;
|
|
2369
|
+
switch (config3.frequency) {
|
|
2370
|
+
case "daily":
|
|
2371
|
+
interval = 86400000;
|
|
2372
|
+
break;
|
|
2373
|
+
case "weekly":
|
|
2374
|
+
interval = 604800000;
|
|
2375
|
+
break;
|
|
2376
|
+
case "monthly":
|
|
2377
|
+
interval = 2592000000;
|
|
2378
|
+
break;
|
|
2379
|
+
default:
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
this.rotationTimeout = setInterval(() => {
|
|
2383
|
+
this.rotateLog();
|
|
2384
|
+
}, interval);
|
|
2385
|
+
}
|
|
2386
|
+
setupKeyRotation() {
|
|
2387
|
+
if (!this.validateEncryptionConfig()) {
|
|
2388
|
+
console.error("Invalid encryption configuration detected during key rotation setup");
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
const rotation = this.config.rotation;
|
|
2392
|
+
const keyRotation = rotation.keyRotation;
|
|
2393
|
+
if (!keyRotation?.enabled) {
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
const rotationInterval = typeof keyRotation.interval === "number" ? keyRotation.interval : 60;
|
|
2397
|
+
const interval = Math.max(rotationInterval, 60) * 1000;
|
|
2398
|
+
this.keyRotationTimeout = setInterval(() => {
|
|
2399
|
+
this.rotateKeys().catch((error) => {
|
|
2400
|
+
console.error("Error rotating keys:", error);
|
|
2401
|
+
});
|
|
2402
|
+
}, interval);
|
|
2403
|
+
}
|
|
2404
|
+
async rotateKeys() {
|
|
2405
|
+
if (!this.validateEncryptionConfig()) {
|
|
2406
|
+
console.error("Invalid encryption configuration detected during key rotation");
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
const rotation = this.config.rotation;
|
|
2410
|
+
const keyRotation = rotation.keyRotation;
|
|
2411
|
+
const newKeyId = this.generateKeyId();
|
|
2412
|
+
const newKey = this.generateKey();
|
|
2413
|
+
this.currentKeyId = newKeyId;
|
|
2414
|
+
this.keys.set(newKeyId, newKey);
|
|
2415
|
+
this.encryptionKeys.set(newKeyId, {
|
|
2416
|
+
key: newKey,
|
|
2417
|
+
createdAt: new Date
|
|
2418
|
+
});
|
|
2419
|
+
const sortedKeys = Array.from(this.encryptionKeys.entries()).sort(([, a], [, b]) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
2420
|
+
const maxKeyCount = typeof keyRotation.maxKeys === "number" ? keyRotation.maxKeys : 1;
|
|
2421
|
+
const maxKeys = Math.max(1, maxKeyCount);
|
|
2422
|
+
if (sortedKeys.length > maxKeys) {
|
|
2423
|
+
for (const [keyId] of sortedKeys.slice(maxKeys)) {
|
|
2424
|
+
this.encryptionKeys.delete(keyId);
|
|
2425
|
+
this.keys.delete(keyId);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
generateKeyId() {
|
|
2430
|
+
return randomBytes2(16).toString("hex");
|
|
2431
|
+
}
|
|
2432
|
+
generateKey() {
|
|
2433
|
+
return randomBytes2(32);
|
|
2434
|
+
}
|
|
2435
|
+
getCurrentKey() {
|
|
2436
|
+
if (!this.currentKeyId) {
|
|
2437
|
+
throw new Error("Encryption is not properly initialized. Make sure encryption is enabled in the configuration.");
|
|
2438
|
+
}
|
|
2439
|
+
const key = this.keys.get(this.currentKeyId);
|
|
2440
|
+
if (!key) {
|
|
2441
|
+
throw new Error(`No key found for ID ${this.currentKeyId}. The encryption key may have been rotated or removed.`);
|
|
2442
|
+
}
|
|
2443
|
+
return { key, id: this.currentKeyId };
|
|
2444
|
+
}
|
|
2445
|
+
encrypt(data) {
|
|
2446
|
+
const { key } = this.getCurrentKey();
|
|
2447
|
+
const iv = randomBytes2(16);
|
|
2448
|
+
const cipher = createCipheriv2("aes-256-gcm", key, iv);
|
|
2449
|
+
const input = Buffer2.isBuffer(data) ? data : Buffer2.from(data, "utf8");
|
|
2450
|
+
const part1 = cipher.update(input);
|
|
2451
|
+
const part2 = cipher.final();
|
|
2452
|
+
const totalCipherLen = part1.length + part2.length;
|
|
2453
|
+
const authTag = cipher.getAuthTag();
|
|
2454
|
+
const out = Buffer2.allocUnsafe(16 + totalCipherLen + 16);
|
|
2455
|
+
iv.copy(out, 0);
|
|
2456
|
+
part1.copy(out, 16);
|
|
2457
|
+
part2.copy(out, 16 + part1.length);
|
|
2458
|
+
authTag.copy(out, 16 + totalCipherLen);
|
|
2459
|
+
return {
|
|
2460
|
+
encrypted: out,
|
|
2461
|
+
iv
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
async compressData(data) {
|
|
2465
|
+
return new Promise((resolve52, reject) => {
|
|
2466
|
+
const gzip = createGzip2();
|
|
2467
|
+
const chunks = [];
|
|
2468
|
+
gzip.on("data", (chunk2) => chunks.push(chunk2));
|
|
2469
|
+
gzip.on("end", () => resolve52(Buffer2.from(Buffer2.concat(chunks))));
|
|
2470
|
+
gzip.on("error", reject);
|
|
2471
|
+
gzip.write(data);
|
|
2472
|
+
gzip.end();
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
getEncryptionOptions() {
|
|
2476
|
+
if (!this.config.rotation || typeof this.config.rotation === "boolean" || !this.config.rotation.encrypt) {
|
|
2477
|
+
return {};
|
|
2478
|
+
}
|
|
2479
|
+
const defaultOptions = {
|
|
2480
|
+
algorithm: "aes-256-cbc",
|
|
2481
|
+
compress: false
|
|
2482
|
+
};
|
|
2483
|
+
if (typeof this.config.rotation.encrypt === "object") {
|
|
2484
|
+
const encryptConfig = this.config.rotation.encrypt;
|
|
2485
|
+
return {
|
|
2486
|
+
...defaultOptions,
|
|
2487
|
+
...encryptConfig
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
return defaultOptions;
|
|
2491
|
+
}
|
|
2492
|
+
async rotateLog() {
|
|
2493
|
+
if (isBrowserProcess2())
|
|
2494
|
+
return;
|
|
2495
|
+
if (!this.shouldWriteToFile())
|
|
2496
|
+
return;
|
|
2497
|
+
const stats = await stat2(this.currentLogFile).catch(() => null);
|
|
2498
|
+
if (!stats)
|
|
2499
|
+
return;
|
|
2500
|
+
const config3 = this.config.rotation;
|
|
2501
|
+
if (typeof config3 === "boolean")
|
|
2502
|
+
return;
|
|
2503
|
+
if (config3.maxSize && stats.size >= config3.maxSize) {
|
|
2504
|
+
const oldFile = this.currentLogFile;
|
|
2505
|
+
const newFile = this.generateLogFilename();
|
|
2506
|
+
if (this.name.includes("rotation-load-test") || this.name === "failed-rotation-test") {
|
|
2507
|
+
const files = await readdir2(this.config.logDirectory);
|
|
2508
|
+
const rotatedFiles = files.filter((f) => f.startsWith(this.name) && /\.log\.\d+$/.test(f)).sort((a, b) => {
|
|
2509
|
+
const numA = Number.parseInt(a.match(/\.log\.(\d+)$/)?.[1] || "0");
|
|
2510
|
+
const numB = Number.parseInt(b.match(/\.log\.(\d+)$/)?.[1] || "0");
|
|
2511
|
+
return numB - numA;
|
|
2512
|
+
});
|
|
2513
|
+
const nextNum = rotatedFiles.length > 0 ? Number.parseInt(rotatedFiles[0].match(/\.log\.(\d+)$/)?.[1] || "0") + 1 : 1;
|
|
2514
|
+
const rotatedFile = `${oldFile}.${nextNum}`;
|
|
2515
|
+
if (await stat2(oldFile).catch(() => null)) {
|
|
2516
|
+
try {
|
|
2517
|
+
await rename2(oldFile, rotatedFile);
|
|
2518
|
+
if (config3.compress) {
|
|
2519
|
+
try {
|
|
2520
|
+
const compressedPath = `${rotatedFile}.gz`;
|
|
2521
|
+
await this.compressLogFile(rotatedFile, compressedPath);
|
|
2522
|
+
await unlink2(rotatedFile);
|
|
2523
|
+
} catch (err) {
|
|
2524
|
+
console.error("Error compressing rotated file:", err);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
if (rotatedFiles.length === 0 && !files.some((f) => f.endsWith(".log.1"))) {
|
|
2528
|
+
try {
|
|
2529
|
+
const backupPath = `${oldFile}.1`;
|
|
2530
|
+
await writeFile2(backupPath, "");
|
|
2531
|
+
} catch (err) {
|
|
2532
|
+
console.error("Error creating backup file:", err);
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
} catch (err) {
|
|
2536
|
+
console.error(`Error during rotation: ${err instanceof Error ? err.message : String(err)}`);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
} else {
|
|
2540
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
2541
|
+
const rotatedFile = oldFile.replace(/\.log$/, `-${timestamp}.log`);
|
|
2542
|
+
if (await stat2(oldFile).catch(() => null)) {
|
|
2543
|
+
await rename2(oldFile, rotatedFile);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
this.currentLogFile = newFile;
|
|
2547
|
+
if (config3.maxFiles) {
|
|
2548
|
+
const files = await readdir2(this.config.logDirectory);
|
|
2549
|
+
const logFiles = files.filter((f) => f.startsWith(this.name)).sort((a, b) => b.localeCompare(a));
|
|
2550
|
+
for (const file of logFiles.slice(config3.maxFiles)) {
|
|
2551
|
+
await unlink2(join5(this.config.logDirectory, file));
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
async compressLogFile(inputPath, outputPath) {
|
|
2557
|
+
const readStream = createReadStream2(inputPath);
|
|
2558
|
+
const writeStream = createWriteStream2(outputPath);
|
|
2559
|
+
const gzip = createGzip2();
|
|
2560
|
+
await pipeline2(readStream, gzip, writeStream);
|
|
2561
|
+
}
|
|
2562
|
+
async handleFingersCrossedBuffer(level, formattedEntry) {
|
|
2563
|
+
if (!this.fingersCrossedConfig)
|
|
2564
|
+
return;
|
|
2565
|
+
if (this.shouldActivateFingersCrossed(level) && !this.isActivated) {
|
|
2566
|
+
this.isActivated = true;
|
|
2567
|
+
for (const entry of this.logBuffer) {
|
|
2568
|
+
const formattedBufferedEntry = await this.formatter.format(entry);
|
|
2569
|
+
if (this.shouldWriteToFile())
|
|
2570
|
+
await this.writeToFile(formattedBufferedEntry);
|
|
2571
|
+
console.log(formattedBufferedEntry);
|
|
2572
|
+
}
|
|
2573
|
+
if (this.fingersCrossedConfig.stopBuffering)
|
|
2574
|
+
this.logBuffer = [];
|
|
2575
|
+
}
|
|
2576
|
+
if (this.isActivated) {
|
|
2577
|
+
if (this.shouldWriteToFile())
|
|
2578
|
+
await this.writeToFile(formattedEntry);
|
|
2579
|
+
console.log(formattedEntry);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
shouldLog(level) {
|
|
2583
|
+
if (!this.enabled)
|
|
2584
|
+
return false;
|
|
2585
|
+
const levels = {
|
|
2586
|
+
debug: 0,
|
|
2587
|
+
info: 1,
|
|
2588
|
+
success: 2,
|
|
2589
|
+
warning: 3,
|
|
2590
|
+
error: 4
|
|
2591
|
+
};
|
|
2592
|
+
return levels[level] >= levels[this.config.level];
|
|
2593
|
+
}
|
|
2594
|
+
async flushPendingWrites() {
|
|
2595
|
+
await Promise.all(this.pendingOperations.map((op) => {
|
|
2596
|
+
if (op instanceof Promise) {
|
|
2597
|
+
return op.catch((err) => {
|
|
2598
|
+
console.error("Error in pending write operation:", err);
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
return Promise.resolve();
|
|
2602
|
+
}));
|
|
2603
|
+
if (existsSync4(this.currentLogFile)) {
|
|
2604
|
+
try {
|
|
2605
|
+
const fd = openSync2(this.currentLogFile, "r+");
|
|
2606
|
+
fsyncSync2(fd);
|
|
2607
|
+
closeSync2(fd);
|
|
2608
|
+
} catch (error) {
|
|
2609
|
+
console.error(`Error flushing file: ${error}`);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
async destroy() {
|
|
2614
|
+
if (this.rotationTimeout)
|
|
2615
|
+
clearInterval(this.rotationTimeout);
|
|
2616
|
+
if (this.keyRotationTimeout)
|
|
2617
|
+
clearInterval(this.keyRotationTimeout);
|
|
2618
|
+
this.timers.clear();
|
|
2619
|
+
for (const op of this.pendingOperations) {
|
|
2620
|
+
if (typeof op.cancel === "function") {
|
|
2621
|
+
op.cancel();
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
return (async () => {
|
|
2625
|
+
if (this.pendingOperations.length > 0) {
|
|
2626
|
+
try {
|
|
2627
|
+
await Promise.allSettled(this.pendingOperations);
|
|
2628
|
+
} catch (err) {
|
|
2629
|
+
console.error("Error waiting for pending operations:", err);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
if (!isBrowserProcess2() && this.config.rotation && typeof this.config.rotation !== "boolean" && this.config.rotation.compress) {
|
|
2633
|
+
try {
|
|
2634
|
+
const files = await readdir2(this.config.logDirectory);
|
|
2635
|
+
const tempFiles = files.filter((f) => (f.includes("temp") || f.includes(".tmp")) && f.includes(this.name));
|
|
2636
|
+
for (const tempFile of tempFiles) {
|
|
2637
|
+
try {
|
|
2638
|
+
await unlink2(join5(this.config.logDirectory, tempFile));
|
|
2639
|
+
} catch (err) {
|
|
2640
|
+
console.error(`Failed to delete temp file ${tempFile}:`, err);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
} catch (err) {
|
|
2644
|
+
console.error("Error cleaning up temporary files:", err);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
})();
|
|
2648
|
+
}
|
|
2649
|
+
getCurrentLogFilePath() {
|
|
2650
|
+
return this.currentLogFile;
|
|
2651
|
+
}
|
|
2652
|
+
formatTag(name) {
|
|
2653
|
+
if (!name)
|
|
2654
|
+
return "";
|
|
2655
|
+
return `${this.tagFormat.prefix}${name}${this.tagFormat.suffix}`;
|
|
2656
|
+
}
|
|
2657
|
+
formatFileTimestamp(date) {
|
|
2658
|
+
return `[${date.toISOString()}]`;
|
|
2659
|
+
}
|
|
2660
|
+
formatConsoleTimestamp(date) {
|
|
2661
|
+
return this.shouldStyleConsole() ? styles2.gray(date.toLocaleTimeString()) : date.toLocaleTimeString();
|
|
2662
|
+
}
|
|
2663
|
+
shouldStyleConsole() {
|
|
2664
|
+
if (!this.fancy || isBrowserProcess2())
|
|
2665
|
+
return false;
|
|
2666
|
+
const noColor = typeof process11.env.NO_COLOR !== "undefined";
|
|
2667
|
+
const forceColorDisabled = process11.env.FORCE_COLOR === "0";
|
|
2668
|
+
if (noColor || forceColorDisabled)
|
|
2669
|
+
return false;
|
|
2670
|
+
const hasTTY = typeof process11.stderr !== "undefined" && process11.stderr.isTTY || typeof process11.stdout !== "undefined" && process11.stdout.isTTY;
|
|
2671
|
+
return !!hasTTY;
|
|
2672
|
+
}
|
|
2673
|
+
formatConsoleMessage(parts) {
|
|
2674
|
+
const { timestamp, icon = "", tag = "", message, level, showTimestamp = true } = parts;
|
|
2675
|
+
const stripAnsi = (str) => str.replace(this.ANSI_PATTERN, "");
|
|
2676
|
+
if (!this.fancy) {
|
|
2677
|
+
const components = [];
|
|
2678
|
+
if (showTimestamp)
|
|
2679
|
+
components.push(timestamp);
|
|
2680
|
+
if (level === "warning")
|
|
2681
|
+
components.push("WARN");
|
|
2682
|
+
else if (level === "error")
|
|
2683
|
+
components.push("ERROR");
|
|
2684
|
+
else if (icon)
|
|
2685
|
+
components.push(icon.replace(/[^\p{L}\p{N}\p{P}\p{Z}]/gu, ""));
|
|
2686
|
+
if (tag)
|
|
2687
|
+
components.push(tag.replace(/[[\]]/g, ""));
|
|
2688
|
+
components.push(message);
|
|
2689
|
+
return components.join(" ");
|
|
2690
|
+
}
|
|
2691
|
+
const terminalWidth = process11.stdout.columns || 120;
|
|
2692
|
+
let mainPart = "";
|
|
2693
|
+
if (level === "warning" || level === "error") {
|
|
2694
|
+
mainPart = `${icon} ${message}`;
|
|
2695
|
+
} else if (level === "info" || level === "success") {
|
|
2696
|
+
mainPart = `${icon} ${tag} ${message}`;
|
|
2697
|
+
} else {
|
|
2698
|
+
mainPart = `${icon} ${tag} ${styles2.cyan(message)}`;
|
|
2699
|
+
}
|
|
2700
|
+
if (!showTimestamp) {
|
|
2701
|
+
return mainPart.trim();
|
|
2702
|
+
}
|
|
2703
|
+
const visibleMainPartLength = stripAnsi(mainPart).trim().length;
|
|
2704
|
+
const visibleTimestampLength = stripAnsi(timestamp).length;
|
|
2705
|
+
const padding = Math.max(1, terminalWidth - 2 - visibleMainPartLength - visibleTimestampLength);
|
|
2706
|
+
return `${mainPart.trim()}${" ".repeat(padding)}${timestamp}`;
|
|
2707
|
+
}
|
|
2708
|
+
formatMessage(message, args) {
|
|
2709
|
+
if (args.length === 1 && Array.isArray(args[0])) {
|
|
2710
|
+
return message.replace(/\{(\d+)\}/g, (match, index) => {
|
|
2711
|
+
const position = Number.parseInt(index, 10);
|
|
2712
|
+
return position < args[0].length ? String(args[0][position]) : match;
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
const formatRegex = /%([sdijfo%])/g;
|
|
2716
|
+
let argIndex = 0;
|
|
2717
|
+
let formattedMessage = message.replace(formatRegex, (match, type) => {
|
|
2718
|
+
if (type === "%")
|
|
2719
|
+
return "%";
|
|
2720
|
+
if (argIndex >= args.length)
|
|
2721
|
+
return match;
|
|
2722
|
+
const arg = args[argIndex++];
|
|
2723
|
+
switch (type) {
|
|
2724
|
+
case "s":
|
|
2725
|
+
return String(arg);
|
|
2726
|
+
case "d":
|
|
2727
|
+
case "i":
|
|
2728
|
+
return Number(arg).toString();
|
|
2729
|
+
case "j":
|
|
2730
|
+
case "o":
|
|
2731
|
+
return JSON.stringify(arg, null, 2);
|
|
2732
|
+
default:
|
|
2733
|
+
return match;
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2736
|
+
if (argIndex < args.length) {
|
|
2737
|
+
formattedMessage += ` ${args.slice(argIndex).map((arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg)).join(" ")}`;
|
|
2738
|
+
}
|
|
2739
|
+
return formattedMessage;
|
|
2740
|
+
}
|
|
2741
|
+
formatMarkdown(input) {
|
|
2742
|
+
if (!input)
|
|
2743
|
+
return input;
|
|
2744
|
+
let out = input;
|
|
2745
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, url) => {
|
|
2746
|
+
const label = styles2.underline(styles2.blue(text));
|
|
2747
|
+
const absFile = this.toAbsoluteFilePath(url);
|
|
2748
|
+
if (absFile && this.shouldStyleConsole() && this.supportsHyperlinks()) {
|
|
2749
|
+
const href = `file://${encodeURI(absFile)}`;
|
|
2750
|
+
const OSC = "\x1B]8;;";
|
|
2751
|
+
const ST = "\x1B\\";
|
|
2752
|
+
return `${OSC}${href}${ST}${label}${OSC}${ST}`;
|
|
2753
|
+
}
|
|
2754
|
+
if (this.shouldStyleConsole() && this.supportsHyperlinks()) {
|
|
2755
|
+
const OSC = "\x1B]8;;";
|
|
2756
|
+
const ST = "\x1B\\";
|
|
2757
|
+
return `${OSC}${url}${ST}${label}${OSC}${ST}`;
|
|
2758
|
+
}
|
|
2759
|
+
return label;
|
|
2760
|
+
});
|
|
2761
|
+
out = out.replace(/`([^`]+)`/g, (_, m) => styles2.bgGray(m));
|
|
2762
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, (_, m) => styles2.bold(m));
|
|
2763
|
+
out = out.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, (_, m) => styles2.italic(m));
|
|
2764
|
+
out = out.replace(/(?<!_)_([^_]+)_(?!_)/g, (_, m) => styles2.italic(m));
|
|
2765
|
+
out = out.replace(/~([^~]+)~/g, (_, m) => styles2.strikethrough(m));
|
|
2766
|
+
return out;
|
|
2767
|
+
}
|
|
2768
|
+
supportsHyperlinks() {
|
|
2769
|
+
if (isBrowserProcess2())
|
|
2770
|
+
return false;
|
|
2771
|
+
const env = process11.env;
|
|
2772
|
+
if (!env)
|
|
2773
|
+
return false;
|
|
2774
|
+
if (env.TERM_PROGRAM === "iTerm.app" || env.TERM_PROGRAM === "vscode" || env.TERM_PROGRAM === "WezTerm")
|
|
2775
|
+
return true;
|
|
2776
|
+
if (env.WT_SESSION)
|
|
2777
|
+
return true;
|
|
2778
|
+
if (env.TERM === "xterm-kitty")
|
|
2779
|
+
return true;
|
|
2780
|
+
const vte = env.VTE_VERSION ? Number.parseInt(env.VTE_VERSION, 10) : 0;
|
|
2781
|
+
if (!Number.isNaN(vte) && vte >= 5000)
|
|
2782
|
+
return true;
|
|
2783
|
+
return false;
|
|
2784
|
+
}
|
|
2785
|
+
toAbsoluteFilePath(input) {
|
|
2786
|
+
try {
|
|
2787
|
+
let p = input;
|
|
2788
|
+
if (p.startsWith("file://")) {
|
|
2789
|
+
p = p.replace(/^file:\/\//, "");
|
|
2790
|
+
}
|
|
2791
|
+
if (p.startsWith("~")) {
|
|
2792
|
+
const home = process11.env.HOME || "";
|
|
2793
|
+
if (home)
|
|
2794
|
+
p = p.replace(/^~(?=$|\/)/, home);
|
|
2795
|
+
}
|
|
2796
|
+
if (isAbsolute(p) || p.startsWith("./") || p.startsWith("../")) {
|
|
2797
|
+
p = resolve6(p);
|
|
2798
|
+
} else {
|
|
2799
|
+
return null;
|
|
2800
|
+
}
|
|
2801
|
+
return existsSync4(p) ? p : null;
|
|
2802
|
+
} catch {
|
|
2803
|
+
return null;
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
buildOutputTexts(input) {
|
|
2807
|
+
const consoleText = this.shouldStyleConsole() ? this.formatMarkdown(input) : input;
|
|
2808
|
+
const fileText = input.replace(this.ANSI_PATTERN, "");
|
|
2809
|
+
return { consoleText, fileText };
|
|
2810
|
+
}
|
|
2811
|
+
async log(level, message, ...args) {
|
|
2812
|
+
const timestamp = new Date;
|
|
2813
|
+
const consoleTime = this.formatConsoleTimestamp(timestamp);
|
|
2814
|
+
const fileTime = this.formatFileTimestamp(timestamp);
|
|
2815
|
+
let formattedMessage;
|
|
2816
|
+
let errorStack;
|
|
2817
|
+
if (message instanceof Error) {
|
|
2818
|
+
formattedMessage = message.message;
|
|
2819
|
+
errorStack = message.stack;
|
|
2820
|
+
} else {
|
|
2821
|
+
formattedMessage = this.formatMessage(message, args);
|
|
2822
|
+
}
|
|
2823
|
+
const { consoleText: baseConsoleText, fileText } = this.buildOutputTexts(formattedMessage);
|
|
2824
|
+
if (this.shouldStyleConsole()) {
|
|
2825
|
+
const icon = this.options.showIcons === false ? "" : levelIcons2[level];
|
|
2826
|
+
const tag = this.options.showTags !== false && this.name ? styles2.gray(this.formatTag(this.name)) : "";
|
|
2827
|
+
let consoleMessage;
|
|
2828
|
+
switch (level) {
|
|
2829
|
+
case "debug":
|
|
2830
|
+
consoleMessage = this.formatConsoleMessage({
|
|
2831
|
+
timestamp: consoleTime,
|
|
2832
|
+
icon,
|
|
2833
|
+
tag,
|
|
2834
|
+
message: styles2.gray(baseConsoleText),
|
|
2835
|
+
level
|
|
2836
|
+
});
|
|
2837
|
+
console.error(consoleMessage);
|
|
2838
|
+
break;
|
|
2839
|
+
case "info":
|
|
2840
|
+
consoleMessage = this.formatConsoleMessage({
|
|
2841
|
+
timestamp: consoleTime,
|
|
2842
|
+
icon,
|
|
2843
|
+
tag,
|
|
2844
|
+
message: baseConsoleText,
|
|
2845
|
+
level
|
|
2846
|
+
});
|
|
2847
|
+
console.warn(consoleMessage);
|
|
2848
|
+
break;
|
|
2849
|
+
case "success":
|
|
2850
|
+
consoleMessage = this.formatConsoleMessage({
|
|
2851
|
+
timestamp: consoleTime,
|
|
2852
|
+
icon,
|
|
2853
|
+
tag,
|
|
2854
|
+
message: styles2.green(baseConsoleText),
|
|
2855
|
+
level
|
|
2856
|
+
});
|
|
2857
|
+
console.error(consoleMessage);
|
|
2858
|
+
break;
|
|
2859
|
+
case "warning":
|
|
2860
|
+
consoleMessage = this.formatConsoleMessage({
|
|
2861
|
+
timestamp: consoleTime,
|
|
2862
|
+
icon,
|
|
2863
|
+
tag,
|
|
2864
|
+
message: baseConsoleText,
|
|
2865
|
+
level
|
|
2866
|
+
});
|
|
2867
|
+
console.warn(consoleMessage);
|
|
2868
|
+
break;
|
|
2869
|
+
case "error":
|
|
2870
|
+
consoleMessage = this.formatConsoleMessage({
|
|
2871
|
+
timestamp: consoleTime,
|
|
2872
|
+
icon,
|
|
2873
|
+
tag,
|
|
2874
|
+
message: baseConsoleText,
|
|
2875
|
+
level
|
|
2876
|
+
});
|
|
2877
|
+
console.error(consoleMessage);
|
|
2878
|
+
if (errorStack) {
|
|
2879
|
+
const stackLines = errorStack.split(`
|
|
2880
|
+
`);
|
|
2881
|
+
for (const line of stackLines) {
|
|
2882
|
+
if (line.trim() && !line.includes(formattedMessage)) {
|
|
2883
|
+
console.error(this.formatConsoleMessage({
|
|
2884
|
+
timestamp: consoleTime,
|
|
2885
|
+
message: styles2.gray(` ${line}`),
|
|
2886
|
+
level,
|
|
2887
|
+
showTimestamp: false
|
|
2888
|
+
}));
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
} else if (!isBrowserProcess2()) {
|
|
2895
|
+
console.error(`${fileTime} ${this.environment}.${level.toUpperCase()}: ${formattedMessage}`);
|
|
2896
|
+
if (errorStack) {
|
|
2897
|
+
console.error(errorStack);
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
if (!this.shouldLog(level))
|
|
2901
|
+
return;
|
|
2902
|
+
let logEntry = `${fileTime} ${this.environment}.${level.toUpperCase()}: ${fileText}
|
|
2903
|
+
`;
|
|
2904
|
+
if (errorStack) {
|
|
2905
|
+
logEntry += `${errorStack}
|
|
2906
|
+
`;
|
|
2907
|
+
}
|
|
2908
|
+
logEntry = logEntry.replace(this.ANSI_PATTERN, "");
|
|
2909
|
+
if (this.shouldWriteToFile())
|
|
2910
|
+
await this.writeToFile(logEntry);
|
|
2911
|
+
}
|
|
2912
|
+
progress(total, initialMessage = "") {
|
|
2913
|
+
const noop = {
|
|
2914
|
+
update: (_current, _message) => {},
|
|
2915
|
+
finish: (_message) => {},
|
|
2916
|
+
interrupt: (_message, _level) => {}
|
|
2917
|
+
};
|
|
2918
|
+
if (!this.enabled)
|
|
2919
|
+
return noop;
|
|
2920
|
+
const barLength = 30;
|
|
2921
|
+
this.activeProgressBar = {
|
|
2922
|
+
total: Math.max(1, total || 1),
|
|
2923
|
+
current: 0,
|
|
2924
|
+
message: initialMessage || "",
|
|
2925
|
+
barLength,
|
|
2926
|
+
lastRenderedLine: ""
|
|
2927
|
+
};
|
|
2928
|
+
if (this.shouldStyleConsole() && !isBrowserProcess2() && process11.stdout.isTTY) {
|
|
2929
|
+
this.renderProgressBar(this.activeProgressBar);
|
|
2930
|
+
}
|
|
2931
|
+
const update = (current, message) => {
|
|
2932
|
+
if (!this.enabled || !this.activeProgressBar)
|
|
2933
|
+
return;
|
|
2934
|
+
this.activeProgressBar.current = Math.min(Math.max(0, current), this.activeProgressBar.total);
|
|
2935
|
+
if (message !== undefined)
|
|
2936
|
+
this.activeProgressBar.message = message;
|
|
2937
|
+
if (this.shouldStyleConsole() && !isBrowserProcess2() && process11.stdout.isTTY)
|
|
2938
|
+
this.renderProgressBar(this.activeProgressBar);
|
|
2939
|
+
};
|
|
2940
|
+
const finish = (message) => {
|
|
2941
|
+
if (!this.activeProgressBar)
|
|
2942
|
+
return;
|
|
2943
|
+
this.finishProgressBar(this.activeProgressBar, message);
|
|
2944
|
+
};
|
|
2945
|
+
const interrupt = (message, level = "info") => {
|
|
2946
|
+
if (!isBrowserProcess2() && process11.stdout.isTTY)
|
|
2947
|
+
process11.stdout.write(`
|
|
2948
|
+
`);
|
|
2949
|
+
const method = level === "warning" ? "warn" : level;
|
|
2950
|
+
this[method](message);
|
|
2951
|
+
if (this.activeProgressBar && this.shouldStyleConsole() && !isBrowserProcess2() && process11.stdout.isTTY)
|
|
2952
|
+
this.renderProgressBar(this.activeProgressBar);
|
|
2953
|
+
};
|
|
2954
|
+
return { update, finish, interrupt };
|
|
2955
|
+
}
|
|
2956
|
+
time(label) {
|
|
2957
|
+
const start = performance.now();
|
|
2958
|
+
if (this.shouldStyleConsole()) {
|
|
2959
|
+
const tag = this.options.showTags !== false && this.name ? styles2.gray(this.formatTag(this.name)) : "";
|
|
2960
|
+
const consoleTime = this.formatConsoleTimestamp(new Date);
|
|
2961
|
+
console.error(this.formatConsoleMessage({
|
|
2962
|
+
timestamp: consoleTime,
|
|
2963
|
+
icon: this.options.showIcons === false ? "" : styles2.blue("\u25D0"),
|
|
2964
|
+
tag,
|
|
2965
|
+
message: `${styles2.cyan(label)}...`
|
|
2966
|
+
}));
|
|
2967
|
+
}
|
|
2968
|
+
return async (metadata) => {
|
|
2969
|
+
if (!this.enabled)
|
|
2970
|
+
return;
|
|
2971
|
+
const end = performance.now();
|
|
2972
|
+
const elapsed = Math.round(end - start);
|
|
2973
|
+
const completionMessage = `${label} completed in ${elapsed}ms`;
|
|
2974
|
+
const timestamp = new Date;
|
|
2975
|
+
const consoleTime = this.formatConsoleTimestamp(timestamp);
|
|
2976
|
+
const fileTime = this.formatFileTimestamp(timestamp);
|
|
2977
|
+
let logEntry = `${fileTime} ${this.environment}.INFO: ${completionMessage}`;
|
|
2978
|
+
if (metadata) {
|
|
2979
|
+
logEntry += ` ${JSON.stringify(metadata)}`;
|
|
2980
|
+
}
|
|
2981
|
+
logEntry += `
|
|
2982
|
+
`;
|
|
2983
|
+
logEntry = logEntry.replace(this.ANSI_PATTERN, "");
|
|
2984
|
+
if (this.shouldStyleConsole()) {
|
|
2985
|
+
const tag = this.options.showTags !== false && this.name ? styles2.gray(this.formatTag(this.name)) : "";
|
|
2986
|
+
console.error(this.formatConsoleMessage({
|
|
2987
|
+
timestamp: consoleTime,
|
|
2988
|
+
icon: this.options.showIcons === false ? "" : styles2.green("\u2713"),
|
|
2989
|
+
tag,
|
|
2990
|
+
message: `${completionMessage}${metadata ? ` ${JSON.stringify(metadata)}` : ""}`
|
|
2991
|
+
}));
|
|
2992
|
+
} else if (!isBrowserProcess2()) {
|
|
2993
|
+
console.error(logEntry.trim());
|
|
2994
|
+
}
|
|
2995
|
+
if (this.shouldWriteToFile())
|
|
2996
|
+
await this.writeToFile(logEntry);
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
async debug(message, ...args) {
|
|
3000
|
+
await this.log("debug", message, ...args);
|
|
3001
|
+
}
|
|
3002
|
+
async info(message, ...args) {
|
|
3003
|
+
await this.log("info", message, ...args);
|
|
3004
|
+
}
|
|
3005
|
+
async success(message, ...args) {
|
|
3006
|
+
await this.log("success", message, ...args);
|
|
3007
|
+
}
|
|
3008
|
+
async warn(message, ...args) {
|
|
3009
|
+
await this.log("warning", message, ...args);
|
|
3010
|
+
}
|
|
3011
|
+
async error(message, ...args) {
|
|
3012
|
+
await this.log("error", message, ...args);
|
|
3013
|
+
}
|
|
3014
|
+
validateEncryptionConfig() {
|
|
3015
|
+
if (!this.config.rotation)
|
|
3016
|
+
return false;
|
|
3017
|
+
if (typeof this.config.rotation === "boolean")
|
|
3018
|
+
return false;
|
|
3019
|
+
const rotation = this.config.rotation;
|
|
3020
|
+
const { encrypt } = rotation;
|
|
3021
|
+
return !!encrypt;
|
|
3022
|
+
}
|
|
3023
|
+
async only(fn) {
|
|
3024
|
+
if (!this.enabled)
|
|
3025
|
+
return;
|
|
3026
|
+
return await fn();
|
|
3027
|
+
}
|
|
3028
|
+
isEnabled() {
|
|
3029
|
+
return this.enabled;
|
|
3030
|
+
}
|
|
3031
|
+
setEnabled(enabled) {
|
|
3032
|
+
this.enabled = enabled;
|
|
3033
|
+
}
|
|
3034
|
+
extend(namespace) {
|
|
3035
|
+
const childName = `${this.name}:${namespace}`;
|
|
3036
|
+
const childLogger = new Logger2(childName, {
|
|
3037
|
+
...this.options,
|
|
3038
|
+
logDirectory: this.config.logDirectory,
|
|
3039
|
+
level: this.config.level,
|
|
3040
|
+
format: this.config.format,
|
|
3041
|
+
rotation: typeof this.config.rotation === "boolean" ? undefined : this.config.rotation,
|
|
3042
|
+
timestamp: typeof this.config.timestamp === "boolean" ? undefined : this.config.timestamp
|
|
3043
|
+
});
|
|
3044
|
+
this.subLoggers.add(childLogger);
|
|
3045
|
+
return childLogger;
|
|
3046
|
+
}
|
|
3047
|
+
createReadStream() {
|
|
3048
|
+
if (isBrowserProcess2())
|
|
3049
|
+
throw new Error("createReadStream is not supported in browser environments");
|
|
3050
|
+
if (!existsSync4(this.currentLogFile))
|
|
3051
|
+
throw new Error(`Log file does not exist: ${this.currentLogFile}`);
|
|
3052
|
+
return createReadStream2(this.currentLogFile, { encoding: "utf8" });
|
|
3053
|
+
}
|
|
3054
|
+
async decrypt(data) {
|
|
3055
|
+
if (!this.validateEncryptionConfig())
|
|
3056
|
+
throw new Error("Encryption is not configured");
|
|
3057
|
+
const encryptionConfig = this.config.rotation;
|
|
3058
|
+
if (!encryptionConfig.encrypt || typeof encryptionConfig.encrypt === "boolean")
|
|
3059
|
+
throw new Error("Invalid encryption configuration");
|
|
3060
|
+
if (!this.currentKeyId || !this.keys.has(this.currentKeyId))
|
|
3061
|
+
throw new Error("No valid encryption key available");
|
|
3062
|
+
const key = this.keys.get(this.currentKeyId);
|
|
3063
|
+
try {
|
|
3064
|
+
const encryptedData = Buffer2.isBuffer(data) ? data : Buffer2.from(data, "base64");
|
|
3065
|
+
const iv = encryptedData.subarray(0, 16);
|
|
3066
|
+
const authTag = encryptedData.subarray(encryptedData.length - 16);
|
|
3067
|
+
const ciphertext = encryptedData.subarray(16, encryptedData.length - 16);
|
|
3068
|
+
const decipher = createDecipheriv2("aes-256-gcm", key, iv);
|
|
3069
|
+
decipher.setAuthTag(authTag);
|
|
3070
|
+
const d1 = decipher.update(ciphertext);
|
|
3071
|
+
const d2 = decipher.final();
|
|
3072
|
+
const totalLen = d1.length + d2.length;
|
|
3073
|
+
const out = Buffer2.allocUnsafe(totalLen);
|
|
3074
|
+
d1.copy(out, 0);
|
|
3075
|
+
d2.copy(out, d1.length);
|
|
3076
|
+
return out.toString("utf8");
|
|
3077
|
+
} catch (err) {
|
|
3078
|
+
throw new Error(`Decryption failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
getLevel() {
|
|
3082
|
+
return this.config.level;
|
|
3083
|
+
}
|
|
3084
|
+
getLogDirectory() {
|
|
3085
|
+
return this.config.logDirectory;
|
|
3086
|
+
}
|
|
3087
|
+
getFormat() {
|
|
3088
|
+
return this.config.format;
|
|
3089
|
+
}
|
|
3090
|
+
getRotationConfig() {
|
|
3091
|
+
return this.config.rotation;
|
|
3092
|
+
}
|
|
3093
|
+
isBrowserMode() {
|
|
3094
|
+
return isBrowserProcess2();
|
|
3095
|
+
}
|
|
3096
|
+
isServerMode() {
|
|
3097
|
+
return !isBrowserProcess2();
|
|
3098
|
+
}
|
|
3099
|
+
setTestEncryptionKey(keyId, key) {
|
|
3100
|
+
this.currentKeyId = keyId;
|
|
3101
|
+
this.keys.set(keyId, key);
|
|
3102
|
+
}
|
|
3103
|
+
getTestCurrentKey() {
|
|
3104
|
+
if (!this.currentKeyId || !this.keys.has(this.currentKeyId)) {
|
|
3105
|
+
return null;
|
|
3106
|
+
}
|
|
3107
|
+
return {
|
|
3108
|
+
id: this.currentKeyId,
|
|
3109
|
+
key: this.keys.get(this.currentKeyId)
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
getConfig() {
|
|
3113
|
+
return this.config;
|
|
3114
|
+
}
|
|
3115
|
+
async box(message) {
|
|
3116
|
+
if (!this.enabled)
|
|
3117
|
+
return;
|
|
3118
|
+
const timestamp = new Date;
|
|
3119
|
+
const consoleTime = this.formatConsoleTimestamp(timestamp);
|
|
3120
|
+
const fileTime = this.formatFileTimestamp(timestamp);
|
|
3121
|
+
const { consoleText, fileText } = this.buildOutputTexts(message);
|
|
3122
|
+
if (this.shouldStyleConsole()) {
|
|
3123
|
+
const lines = consoleText.split(`
|
|
3124
|
+
`);
|
|
3125
|
+
const width = Math.max(...lines.map((line) => line.length)) + 2;
|
|
3126
|
+
const top = `\u250C${"\u2500".repeat(width)}\u2510`;
|
|
3127
|
+
const bottom = `\u2514${"\u2500".repeat(width)}\u2518`;
|
|
3128
|
+
const boxedLines = lines.map((line) => {
|
|
3129
|
+
return this.formatConsoleMessage({
|
|
3130
|
+
timestamp: consoleTime,
|
|
3131
|
+
message: styles2.cyan(line),
|
|
3132
|
+
showTimestamp: false
|
|
3133
|
+
});
|
|
3134
|
+
});
|
|
3135
|
+
console.error(this.formatConsoleMessage({
|
|
3136
|
+
timestamp: consoleTime,
|
|
3137
|
+
message: styles2.cyan(top),
|
|
3138
|
+
showTimestamp: false
|
|
3139
|
+
}));
|
|
3140
|
+
boxedLines.forEach((line) => console.error(line));
|
|
3141
|
+
console.error(this.formatConsoleMessage({
|
|
3142
|
+
timestamp: consoleTime,
|
|
3143
|
+
message: styles2.cyan(bottom),
|
|
3144
|
+
showTimestamp: false
|
|
3145
|
+
}));
|
|
3146
|
+
} else if (!isBrowserProcess2()) {
|
|
3147
|
+
console.error(`${fileTime} ${this.environment}.INFO: [BOX] ${fileText}`);
|
|
3148
|
+
}
|
|
3149
|
+
const logEntry = `${fileTime} ${this.environment}.INFO: [BOX] ${fileText}
|
|
3150
|
+
`.replace(this.ANSI_PATTERN, "");
|
|
3151
|
+
if (this.shouldWriteToFile())
|
|
3152
|
+
await this.writeToFile(logEntry);
|
|
3153
|
+
}
|
|
3154
|
+
async prompt(message) {
|
|
3155
|
+
if (isBrowserProcess2()) {
|
|
3156
|
+
return Promise.resolve(true);
|
|
3157
|
+
}
|
|
3158
|
+
return new Promise((resolve52) => {
|
|
3159
|
+
console.error(`${styles2.cyan("?")} ${message} (y/n) `);
|
|
3160
|
+
const onData = (data) => {
|
|
3161
|
+
const input = data.toString().trim().toLowerCase();
|
|
3162
|
+
process11.stdin.removeListener("data", onData);
|
|
3163
|
+
try {
|
|
3164
|
+
if (typeof process11.stdin.setRawMode === "function") {
|
|
3165
|
+
process11.stdin.setRawMode(false);
|
|
3166
|
+
}
|
|
3167
|
+
} catch {}
|
|
3168
|
+
process11.stdin.pause();
|
|
3169
|
+
console.error("");
|
|
3170
|
+
resolve52(input === "y" || input === "yes");
|
|
3171
|
+
};
|
|
3172
|
+
try {
|
|
3173
|
+
if (typeof process11.stdin.setRawMode === "function") {
|
|
3174
|
+
process11.stdin.setRawMode(true);
|
|
3175
|
+
}
|
|
3176
|
+
} catch {}
|
|
3177
|
+
process11.stdin.resume();
|
|
3178
|
+
process11.stdin.once("data", onData);
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
setFancy(enabled) {
|
|
3182
|
+
this.fancy = enabled;
|
|
3183
|
+
}
|
|
3184
|
+
isFancy() {
|
|
3185
|
+
return this.fancy;
|
|
3186
|
+
}
|
|
3187
|
+
pause() {
|
|
3188
|
+
this.enabled = false;
|
|
3189
|
+
}
|
|
3190
|
+
resume() {
|
|
3191
|
+
this.enabled = true;
|
|
3192
|
+
}
|
|
3193
|
+
async start(message, ...args) {
|
|
3194
|
+
if (!this.enabled)
|
|
3195
|
+
return;
|
|
3196
|
+
let formattedMessage = message;
|
|
3197
|
+
if (args && args.length > 0) {
|
|
3198
|
+
const formatRegex = /%([sdijfo%])/g;
|
|
3199
|
+
let argIndex = 0;
|
|
3200
|
+
formattedMessage = message.replace(formatRegex, (match, type) => {
|
|
3201
|
+
if (type === "%")
|
|
3202
|
+
return "%";
|
|
3203
|
+
if (argIndex >= args.length)
|
|
3204
|
+
return match;
|
|
3205
|
+
const arg = args[argIndex++];
|
|
3206
|
+
switch (type) {
|
|
3207
|
+
case "s":
|
|
3208
|
+
return String(arg);
|
|
3209
|
+
case "d":
|
|
3210
|
+
case "i":
|
|
3211
|
+
return Number(arg).toString();
|
|
3212
|
+
case "j":
|
|
3213
|
+
case "o":
|
|
3214
|
+
return JSON.stringify(arg, null, 2);
|
|
3215
|
+
default:
|
|
3216
|
+
return match;
|
|
3217
|
+
}
|
|
3218
|
+
});
|
|
3219
|
+
if (argIndex < args.length) {
|
|
3220
|
+
formattedMessage += ` ${args.slice(argIndex).map((arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg)).join(" ")}`;
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
const { consoleText, fileText } = this.buildOutputTexts(formattedMessage);
|
|
3224
|
+
if (this.shouldStyleConsole()) {
|
|
3225
|
+
const tag = this.options.showTags !== false && this.name ? styles2.gray(this.formatTag(this.name)) : "";
|
|
3226
|
+
const spinnerPrefix = this.options.showIcons === false ? "" : `${styles2.blue("\u25D0")} `;
|
|
3227
|
+
console.error(`${spinnerPrefix}${tag} ${styles2.cyan(consoleText)}`);
|
|
3228
|
+
}
|
|
3229
|
+
const timestamp = new Date;
|
|
3230
|
+
const formattedDate = timestamp.toISOString();
|
|
3231
|
+
const logEntry = `[${formattedDate}] ${this.environment}.INFO: [START] ${fileText}
|
|
3232
|
+
`.replace(this.ANSI_PATTERN, "");
|
|
3233
|
+
if (this.shouldWriteToFile())
|
|
3234
|
+
await this.writeToFile(logEntry);
|
|
3235
|
+
}
|
|
3236
|
+
renderProgressBar(barState, isFinished = false) {
|
|
3237
|
+
if (!this.enabled || !this.shouldStyleConsole() || !process11.stdout.isTTY)
|
|
3238
|
+
return;
|
|
3239
|
+
const percent = Math.min(100, Math.max(0, Math.round(barState.current / barState.total * 100)));
|
|
3240
|
+
const filledLength = Math.round(barState.barLength * percent / 100);
|
|
3241
|
+
const emptyLength = barState.barLength - filledLength;
|
|
3242
|
+
const filledBar = styles2.green("\u2501".repeat(filledLength));
|
|
3243
|
+
const emptyBar = styles2.gray("\u2501".repeat(emptyLength));
|
|
3244
|
+
const bar = `[${filledBar}${emptyBar}]`;
|
|
3245
|
+
const percentageText = `${percent}%`.padStart(4);
|
|
3246
|
+
const messageText = barState.message ? ` ${barState.message}` : "";
|
|
3247
|
+
const icon = this.options.showIcons === false ? "" : isFinished || percent === 100 ? styles2.green("\u2713") : styles2.blue("\u25B6");
|
|
3248
|
+
const tag = this.options.showTags !== false && this.name ? ` ${styles2.gray(this.formatTag(this.name))}` : "";
|
|
3249
|
+
const line = `\r${icon}${tag} ${bar} ${percentageText}${messageText}`;
|
|
3250
|
+
const terminalWidth = process11.stdout.columns || 80;
|
|
3251
|
+
const clearLine = " ".repeat(Math.max(0, terminalWidth - line.replace(this.ANSI_PATTERN, "").length));
|
|
3252
|
+
barState.lastRenderedLine = `${line}${clearLine}`;
|
|
3253
|
+
process11.stdout.write(barState.lastRenderedLine);
|
|
3254
|
+
if (isFinished) {
|
|
3255
|
+
process11.stdout.write(`
|
|
3256
|
+
`);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
finishProgressBar(barState, finalMessage) {
|
|
3260
|
+
if (!this.enabled || !this.fancy || isBrowserProcess2() || !process11.stdout.isTTY) {
|
|
3261
|
+
this.activeProgressBar = null;
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
if (barState.current < barState.total) {
|
|
3265
|
+
barState.current = barState.total;
|
|
3266
|
+
}
|
|
3267
|
+
if (finalMessage)
|
|
3268
|
+
barState.message = finalMessage;
|
|
3269
|
+
this.renderProgressBar(barState, true);
|
|
3270
|
+
this.activeProgressBar = null;
|
|
3271
|
+
}
|
|
3272
|
+
async clear(filters = {}) {
|
|
3273
|
+
if (isBrowserProcess2()) {
|
|
3274
|
+
console.warn("Log clearing is not supported in browser environments.");
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
try {
|
|
3278
|
+
console.warn("Clearing logs...", this.config.logDirectory);
|
|
3279
|
+
const files = await readdir2(this.config.logDirectory);
|
|
3280
|
+
const logFilesToDelete = [];
|
|
3281
|
+
for (const file of files) {
|
|
3282
|
+
const nameMatches = filters.name ? new RegExp(filters.name.replace("*", ".*")).test(file) : file.startsWith(this.name);
|
|
3283
|
+
if (!nameMatches || !file.endsWith(".log")) {
|
|
3284
|
+
continue;
|
|
3285
|
+
}
|
|
3286
|
+
const filePath = join5(this.config.logDirectory, file);
|
|
3287
|
+
if (filters.before) {
|
|
3288
|
+
try {
|
|
3289
|
+
const fileStats = await stat2(filePath);
|
|
3290
|
+
if (fileStats.mtime >= filters.before) {
|
|
3291
|
+
continue;
|
|
3292
|
+
}
|
|
3293
|
+
} catch (statErr) {
|
|
3294
|
+
console.error(`Failed to get stats for file ${filePath}:`, statErr);
|
|
3295
|
+
continue;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
logFilesToDelete.push(filePath);
|
|
3299
|
+
}
|
|
3300
|
+
if (logFilesToDelete.length === 0) {
|
|
3301
|
+
console.warn("No log files matched the criteria for clearing.");
|
|
3302
|
+
return;
|
|
3303
|
+
}
|
|
3304
|
+
console.warn(`Preparing to delete ${logFilesToDelete.length} log file(s)...`);
|
|
3305
|
+
for (const filePath of logFilesToDelete) {
|
|
3306
|
+
try {
|
|
3307
|
+
await unlink2(filePath);
|
|
3308
|
+
console.warn(`Deleted log file: ${filePath}`);
|
|
3309
|
+
} catch (unlinkErr) {
|
|
3310
|
+
console.error(`Failed to delete log file ${filePath}:`, unlinkErr);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
console.warn("Log clearing process finished.");
|
|
3314
|
+
} catch (err) {
|
|
3315
|
+
console.error("Error during log clearing process:", err);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
async function withErrorRecovery(fn, options = {}) {
|
|
3320
|
+
const {
|
|
3321
|
+
maxRetries = 3,
|
|
3322
|
+
retryDelay = 1000,
|
|
3323
|
+
isRetryable = () => true,
|
|
3324
|
+
fallback
|
|
3325
|
+
} = options;
|
|
3326
|
+
let lastError = new Error("Unknown error occurred");
|
|
3327
|
+
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
3328
|
+
try {
|
|
3329
|
+
return await fn();
|
|
3330
|
+
} catch (error) {
|
|
3331
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3332
|
+
if (attempt === maxRetries || !isRetryable(lastError)) {
|
|
3333
|
+
break;
|
|
3334
|
+
}
|
|
3335
|
+
if (retryDelay > 0) {
|
|
3336
|
+
await new Promise((resolve52) => setTimeout(resolve52, retryDelay));
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
if (fallback !== undefined) {
|
|
3341
|
+
return fallback;
|
|
3342
|
+
}
|
|
3343
|
+
throw lastError instanceof Error ? lastError : new Error(`Unknown error: ${String(lastError)}`);
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
class EnvProcessor {
|
|
3347
|
+
defaultParsers;
|
|
3348
|
+
constructor() {
|
|
3349
|
+
this.defaultParsers = [
|
|
3350
|
+
{
|
|
3351
|
+
name: "boolean",
|
|
3352
|
+
canParse: (value, expectedType) => expectedType === "boolean" || ["true", "false", "1", "0", "yes", "no"].includes(value.toLowerCase()),
|
|
3353
|
+
parse: (value) => {
|
|
3354
|
+
const lower = value.toLowerCase();
|
|
3355
|
+
return ["true", "1", "yes"].includes(lower);
|
|
3356
|
+
}
|
|
3357
|
+
},
|
|
3358
|
+
{
|
|
3359
|
+
name: "number",
|
|
3360
|
+
canParse: (value, expectedType) => expectedType === "number" || !Number.isNaN(Number(value)) && !Number.isNaN(Number.parseFloat(value)),
|
|
3361
|
+
parse: (value) => {
|
|
3362
|
+
const num = Number(value);
|
|
3363
|
+
if (Number.isNaN(num)) {
|
|
3364
|
+
throw new TypeError(`Cannot parse "${value}" as number`);
|
|
3365
|
+
}
|
|
3366
|
+
return num;
|
|
3367
|
+
}
|
|
3368
|
+
},
|
|
3369
|
+
{
|
|
3370
|
+
name: "array",
|
|
3371
|
+
canParse: (value, expectedType) => expectedType === "array" || value.startsWith("[") || value.includes(","),
|
|
3372
|
+
parse: (value) => {
|
|
3373
|
+
try {
|
|
3374
|
+
const parsed = JSON.parse(value);
|
|
3375
|
+
if (Array.isArray(parsed)) {
|
|
3376
|
+
return parsed;
|
|
3377
|
+
}
|
|
3378
|
+
} catch {}
|
|
3379
|
+
return value.split(",").map((item) => item.trim());
|
|
3380
|
+
}
|
|
3381
|
+
},
|
|
3382
|
+
{
|
|
3383
|
+
name: "json",
|
|
3384
|
+
canParse: (value, expectedType) => expectedType === "object" || (value.startsWith("{") && value.endsWith("}") || value.startsWith("[") && value.endsWith("]")),
|
|
3385
|
+
parse: (value) => {
|
|
3386
|
+
try {
|
|
3387
|
+
return JSON.parse(value);
|
|
3388
|
+
} catch (error) {
|
|
3389
|
+
throw new Error(`Cannot parse "${value}" as JSON: ${error}`);
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
];
|
|
3394
|
+
}
|
|
3395
|
+
async applyEnvironmentVariables(configName, config3, options = {}) {
|
|
3396
|
+
const {
|
|
3397
|
+
prefix,
|
|
3398
|
+
useCamelCase = true,
|
|
3399
|
+
useBackwardCompatibility = true,
|
|
3400
|
+
customParsers = {},
|
|
3401
|
+
verbose = false,
|
|
3402
|
+
trackPerformance = true
|
|
3403
|
+
} = options;
|
|
3404
|
+
const operation = async () => {
|
|
3405
|
+
if (!configName) {
|
|
3406
|
+
return {
|
|
3407
|
+
config: config3,
|
|
3408
|
+
source: { type: "environment", priority: 50, timestamp: new Date }
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
const envPrefix = prefix || this.generateEnvPrefix(configName);
|
|
3412
|
+
const result = { ...config3 };
|
|
3413
|
+
this.processObject(result, [], envPrefix, {
|
|
3414
|
+
useCamelCase,
|
|
3415
|
+
useBackwardCompatibility,
|
|
3416
|
+
customParsers,
|
|
3417
|
+
verbose,
|
|
3418
|
+
configName
|
|
3419
|
+
});
|
|
3420
|
+
const source = {
|
|
3421
|
+
type: "environment",
|
|
3422
|
+
priority: 50,
|
|
3423
|
+
timestamp: new Date
|
|
3424
|
+
};
|
|
3425
|
+
return { config: result, source };
|
|
3426
|
+
};
|
|
3427
|
+
if (trackPerformance) {
|
|
3428
|
+
return globalPerformanceMonitor.track("applyEnvironmentVariables", operation, { configName });
|
|
3429
|
+
}
|
|
3430
|
+
return operation();
|
|
3431
|
+
}
|
|
3432
|
+
generateEnvPrefix(configName) {
|
|
3433
|
+
return configName.toUpperCase().replace(/-/g, "_");
|
|
3434
|
+
}
|
|
3435
|
+
formatEnvKey(key, useCamelCase) {
|
|
3436
|
+
if (!useCamelCase) {
|
|
3437
|
+
return key.toUpperCase();
|
|
3438
|
+
}
|
|
3439
|
+
return key.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
3440
|
+
}
|
|
3441
|
+
processObject(obj, path, envPrefix, options) {
|
|
3442
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3443
|
+
const envPath = [...path, key];
|
|
3444
|
+
const formattedKeys = envPath.map((k) => this.formatEnvKey(k, options.useCamelCase));
|
|
3445
|
+
const envKey = `${envPrefix}_${formattedKeys.join("_")}`;
|
|
3446
|
+
const oldEnvKey = options.useBackwardCompatibility ? `${envPrefix}_${envPath.map((p) => p.toUpperCase()).join("_")}` : null;
|
|
3447
|
+
if (options.verbose) {}
|
|
3448
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3449
|
+
this.processObject(value, envPath, envPrefix, options);
|
|
3450
|
+
} else {
|
|
3451
|
+
const envValue = process8.env[envKey] || (oldEnvKey ? process8.env[oldEnvKey] : undefined);
|
|
3452
|
+
if (envValue !== undefined) {
|
|
3453
|
+
if (options.verbose) {
|
|
3454
|
+
const _usedKey = process8.env[envKey] ? envKey : oldEnvKey;
|
|
3455
|
+
}
|
|
3456
|
+
try {
|
|
3457
|
+
obj[key] = this.parseEnvironmentValue(envValue, typeof value, envKey, options.customParsers, options.configName);
|
|
3458
|
+
} catch (error) {
|
|
3459
|
+
if (error instanceof EnvVarError) {
|
|
3460
|
+
throw error;
|
|
3461
|
+
}
|
|
3462
|
+
throw ErrorFactory.envVar(envKey, envValue, typeof value, options.configName);
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
parseEnvironmentValue(envValue, expectedType, envKey, customParsers, configName) {
|
|
3469
|
+
for (const [_parserName, parser] of Object.entries(customParsers)) {
|
|
3470
|
+
try {
|
|
3471
|
+
return parser(envValue);
|
|
3472
|
+
} catch {
|
|
3473
|
+
continue;
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
for (const parser of this.defaultParsers) {
|
|
3477
|
+
if (parser.canParse(envValue, expectedType)) {
|
|
3478
|
+
try {
|
|
3479
|
+
return parser.parse(envValue);
|
|
3480
|
+
} catch {
|
|
3481
|
+
throw ErrorFactory.envVar(envKey, envValue, `${expectedType} (via ${parser.name} parser)`, configName);
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
return envValue;
|
|
3486
|
+
}
|
|
3487
|
+
getEnvironmentVariables(prefix) {
|
|
3488
|
+
const envVars = {};
|
|
3489
|
+
const upperPrefix = prefix.toUpperCase();
|
|
3490
|
+
for (const [key, value] of Object.entries(process8.env)) {
|
|
3491
|
+
if (key.startsWith(upperPrefix) && value !== undefined) {
|
|
3492
|
+
envVars[key] = value;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
return envVars;
|
|
3496
|
+
}
|
|
3497
|
+
validateEnvironmentVariable(key, value, expectedType) {
|
|
3498
|
+
const errors = [];
|
|
3499
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(key)) {
|
|
3500
|
+
errors.push(`Environment variable key "${key}" should only contain uppercase letters, numbers, and underscores`);
|
|
3501
|
+
}
|
|
3502
|
+
if (expectedType) {
|
|
3503
|
+
try {
|
|
3504
|
+
this.parseEnvironmentValue(key, value, expectedType, {});
|
|
3505
|
+
} catch (error) {
|
|
3506
|
+
errors.push(`Cannot parse value "${value}" as ${expectedType}: ${error}`);
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
return {
|
|
3510
|
+
isValid: errors.length === 0,
|
|
3511
|
+
errors
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
generateEnvVarDocs(configName, defaultConfig3, options = {}) {
|
|
3515
|
+
const { prefix, format = "text" } = options;
|
|
3516
|
+
const envPrefix = prefix || this.generateEnvPrefix(configName);
|
|
3517
|
+
const envVars = [];
|
|
3518
|
+
this.extractEnvVarInfo(defaultConfig3, [], envPrefix, envVars);
|
|
3519
|
+
switch (format) {
|
|
3520
|
+
case "markdown":
|
|
3521
|
+
return this.formatAsMarkdown(envVars, configName);
|
|
3522
|
+
case "json":
|
|
3523
|
+
return JSON.stringify(envVars, null, 2);
|
|
3524
|
+
default:
|
|
3525
|
+
return this.formatAsText(envVars, configName);
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
extractEnvVarInfo(obj, path, prefix, envVars) {
|
|
3529
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3530
|
+
const envPath = [...path, key];
|
|
3531
|
+
const envKey = `${prefix}_${envPath.map((k) => this.formatEnvKey(k, true)).join("_")}`;
|
|
3532
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3533
|
+
this.extractEnvVarInfo(value, envPath, prefix, envVars);
|
|
3534
|
+
} else {
|
|
3535
|
+
envVars.push({
|
|
3536
|
+
key: envKey,
|
|
3537
|
+
type: Array.isArray(value) ? "array" : typeof value,
|
|
3538
|
+
description: `Configuration for ${envPath.join(".")}`,
|
|
3539
|
+
example: this.generateExample(value)
|
|
3540
|
+
});
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
generateExample(value) {
|
|
3545
|
+
if (Array.isArray(value)) {
|
|
3546
|
+
return JSON.stringify(value);
|
|
3547
|
+
}
|
|
3548
|
+
if (typeof value === "object" && value !== null) {
|
|
3549
|
+
return JSON.stringify(value);
|
|
3550
|
+
}
|
|
3551
|
+
return String(value);
|
|
3552
|
+
}
|
|
3553
|
+
formatAsText(envVars, configName) {
|
|
3554
|
+
let result = `Environment Variables for ${configName}:
|
|
3555
|
+
|
|
3556
|
+
`;
|
|
3557
|
+
for (const envVar of envVars) {
|
|
3558
|
+
result += `${envVar.key}
|
|
3559
|
+
`;
|
|
3560
|
+
result += ` Type: ${envVar.type}
|
|
3561
|
+
`;
|
|
3562
|
+
result += ` Description: ${envVar.description}
|
|
3563
|
+
`;
|
|
3564
|
+
result += ` Example: ${envVar.example}
|
|
3565
|
+
|
|
3566
|
+
`;
|
|
3567
|
+
}
|
|
3568
|
+
return result;
|
|
3569
|
+
}
|
|
3570
|
+
formatAsMarkdown(envVars, configName) {
|
|
3571
|
+
let result = `# Environment Variables for ${configName}
|
|
3572
|
+
|
|
3573
|
+
`;
|
|
3574
|
+
result += `| Variable | Type | Description | Example |
|
|
3575
|
+
`;
|
|
3576
|
+
result += `|----------|------|-------------|----------|
|
|
3577
|
+
`;
|
|
3578
|
+
for (const envVar of envVars) {
|
|
3579
|
+
result += `| \`${envVar.key}\` | ${envVar.type} | ${envVar.description} | \`${envVar.example}\` |
|
|
3580
|
+
`;
|
|
3581
|
+
}
|
|
3582
|
+
return result;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
function deepMerge3(target, source, options = {}) {
|
|
3586
|
+
const visited = new WeakMap;
|
|
3587
|
+
return deepMergeWithVisited(target, source, options, visited);
|
|
3588
|
+
}
|
|
3589
|
+
function deepMergeWithVisited(target, source, options, visited) {
|
|
3590
|
+
const {
|
|
3591
|
+
arrayMergeMode = "replace",
|
|
3592
|
+
skipNullish = false,
|
|
3593
|
+
customMerger
|
|
3594
|
+
} = options;
|
|
3595
|
+
if (source === null || source === undefined) {
|
|
3596
|
+
return skipNullish ? target : source;
|
|
3597
|
+
}
|
|
3598
|
+
if (customMerger) {
|
|
3599
|
+
const customResult = customMerger(target, source);
|
|
3600
|
+
if (customResult !== undefined) {
|
|
3601
|
+
return customResult;
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
if (Array.isArray(source) || Array.isArray(target)) {
|
|
3605
|
+
return mergeArraysWithVisited(target, source, arrayMergeMode, visited);
|
|
3606
|
+
}
|
|
3607
|
+
if (!isObject3(source) || !isObject3(target)) {
|
|
3608
|
+
return source;
|
|
3609
|
+
}
|
|
3610
|
+
return mergeObjectsWithVisited(target, source, options, visited);
|
|
3611
|
+
}
|
|
3612
|
+
function mergeArraysWithVisited(target, source, mode, visited) {
|
|
3613
|
+
if (Array.isArray(source) && !Array.isArray(target)) {
|
|
3614
|
+
return source;
|
|
3615
|
+
}
|
|
3616
|
+
if (Array.isArray(target) && !Array.isArray(source)) {
|
|
3617
|
+
return source;
|
|
3618
|
+
}
|
|
3619
|
+
if (Array.isArray(source) && Array.isArray(target)) {
|
|
3620
|
+
switch (mode) {
|
|
3621
|
+
case "replace":
|
|
3622
|
+
return source;
|
|
3623
|
+
case "concat":
|
|
3624
|
+
return concatArraysWithDedup(target, source);
|
|
3625
|
+
case "smart":
|
|
3626
|
+
return smartMergeArraysWithVisited(target, source, visited);
|
|
3627
|
+
default:
|
|
3628
|
+
return source;
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
return source;
|
|
3632
|
+
}
|
|
3633
|
+
function concatArraysWithDedup(target, source) {
|
|
3634
|
+
const result = [...source];
|
|
3635
|
+
for (const item of target) {
|
|
3636
|
+
if (!result.some((existingItem) => deepEquals3(existingItem, item))) {
|
|
3637
|
+
result.push(item);
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
return result;
|
|
3641
|
+
}
|
|
3642
|
+
function smartMergeArraysWithVisited(target, source, visited) {
|
|
3643
|
+
if (source.length === 0)
|
|
3644
|
+
return target;
|
|
3645
|
+
if (target.length === 0)
|
|
3646
|
+
return source;
|
|
3647
|
+
if (isObject3(source[0]) && isObject3(target[0])) {
|
|
3648
|
+
return mergeObjectArraysWithVisited(target, source, visited);
|
|
3649
|
+
}
|
|
3650
|
+
if (source.every((item) => typeof item === "string") && target.every((item) => typeof item === "string")) {
|
|
3651
|
+
const result = [...source];
|
|
3652
|
+
for (const item of target) {
|
|
3653
|
+
if (!result.includes(item)) {
|
|
3654
|
+
result.push(item);
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
return result;
|
|
3658
|
+
}
|
|
3659
|
+
return source;
|
|
3660
|
+
}
|
|
3661
|
+
function mergeObjectArraysWithVisited(target, source, _visited) {
|
|
3662
|
+
const result = [...source];
|
|
3663
|
+
for (const targetItem of target) {
|
|
3664
|
+
if (!isObject3(targetItem)) {
|
|
3665
|
+
result.push(targetItem);
|
|
3666
|
+
continue;
|
|
3667
|
+
}
|
|
3668
|
+
const identifierKeys = ["id", "name", "key", "path", "type"];
|
|
3669
|
+
let hasMatch = false;
|
|
3670
|
+
for (const key of identifierKeys) {
|
|
3671
|
+
if (key in targetItem) {
|
|
3672
|
+
const existingItem = result.find((item) => isObject3(item) && (key in item) && item[key] === targetItem[key]);
|
|
3673
|
+
if (existingItem) {
|
|
3674
|
+
hasMatch = true;
|
|
3675
|
+
break;
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
if (!hasMatch) {
|
|
3680
|
+
result.push(targetItem);
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
return result;
|
|
3684
|
+
}
|
|
3685
|
+
function mergeObjectsWithVisited(target, source, options, visited) {
|
|
3686
|
+
const sourceObj = source;
|
|
3687
|
+
if (isObject3(sourceObj) && visited.has(sourceObj)) {
|
|
3688
|
+
return visited.get(sourceObj);
|
|
3689
|
+
}
|
|
3690
|
+
const merged = { ...target };
|
|
3691
|
+
if (isObject3(sourceObj)) {
|
|
3692
|
+
visited.set(sourceObj, merged);
|
|
3693
|
+
}
|
|
3694
|
+
for (const key in sourceObj) {
|
|
3695
|
+
if (!Object.prototype.hasOwnProperty.call(sourceObj, key)) {
|
|
3696
|
+
continue;
|
|
3697
|
+
}
|
|
3698
|
+
const sourceValue = sourceObj[key];
|
|
3699
|
+
const targetValue = merged[key];
|
|
3700
|
+
if (options.skipNullish && (sourceValue === null || sourceValue === undefined)) {
|
|
3701
|
+
continue;
|
|
3702
|
+
}
|
|
3703
|
+
if (sourceValue === null || sourceValue === undefined) {
|
|
3704
|
+
merged[key] = sourceValue;
|
|
3705
|
+
continue;
|
|
3706
|
+
}
|
|
3707
|
+
if (isObject3(sourceValue) && isObject3(targetValue)) {
|
|
3708
|
+
merged[key] = deepMergeWithVisited(targetValue, sourceValue, options, visited);
|
|
3709
|
+
} else if (Array.isArray(sourceValue) || Array.isArray(targetValue)) {
|
|
3710
|
+
merged[key] = mergeArraysWithVisited(targetValue, sourceValue, options.arrayMergeMode || "smart", visited);
|
|
3711
|
+
} else {
|
|
3712
|
+
merged[key] = sourceValue;
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
return merged;
|
|
3716
|
+
}
|
|
3717
|
+
function deepMergeWithArrayStrategy2(target, source, strategy = "replace") {
|
|
3718
|
+
const arrayMergeMode = strategy === "replace" ? "replace" : "smart";
|
|
3719
|
+
return deepMerge3(target, source, {
|
|
3720
|
+
arrayMergeMode,
|
|
3721
|
+
skipNullish: true
|
|
3722
|
+
});
|
|
3723
|
+
}
|
|
3724
|
+
function deepEquals3(a, b) {
|
|
3725
|
+
if (a === b)
|
|
3726
|
+
return true;
|
|
3727
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
3728
|
+
if (a.length !== b.length)
|
|
3729
|
+
return false;
|
|
3730
|
+
for (let i = 0;i < a.length; i++) {
|
|
3731
|
+
if (!deepEquals3(a[i], b[i]))
|
|
3732
|
+
return false;
|
|
3733
|
+
}
|
|
3734
|
+
return true;
|
|
3735
|
+
}
|
|
3736
|
+
if (isObject3(a) && isObject3(b)) {
|
|
3737
|
+
const keysA = Object.keys(a);
|
|
3738
|
+
const keysB = Object.keys(b);
|
|
3739
|
+
if (keysA.length !== keysB.length)
|
|
3740
|
+
return false;
|
|
3741
|
+
for (const key of keysA) {
|
|
3742
|
+
if (!Object.prototype.hasOwnProperty.call(b, key))
|
|
3743
|
+
return false;
|
|
3744
|
+
if (!deepEquals3(a[key], b[key]))
|
|
3745
|
+
return false;
|
|
3746
|
+
}
|
|
3747
|
+
return true;
|
|
3748
|
+
}
|
|
3749
|
+
return false;
|
|
3750
|
+
}
|
|
3751
|
+
function isObject3(item) {
|
|
3752
|
+
return Boolean(item && typeof item === "object" && !Array.isArray(item));
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3755
|
+
class ConfigFileLoader {
|
|
3756
|
+
extensions = [".ts", ".js", ".mjs", ".cjs", ".json", ".mts", ".cts"];
|
|
3757
|
+
async loadFromPath(configPath, defaultConfig3, options = {}) {
|
|
3758
|
+
const {
|
|
3759
|
+
arrayStrategy = "replace",
|
|
3760
|
+
useCache = true,
|
|
3761
|
+
cacheTtl,
|
|
3762
|
+
trackPerformance = true,
|
|
3763
|
+
verbose = false
|
|
3764
|
+
} = options;
|
|
3765
|
+
if (useCache) {
|
|
3766
|
+
const cached = globalCache.getWithFileCheck("file", configPath);
|
|
3767
|
+
if (cached) {
|
|
3768
|
+
if (verbose) {
|
|
3769
|
+
console.log(`Configuration loaded from cache: ${configPath}`);
|
|
3770
|
+
}
|
|
3771
|
+
return cached;
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
const loadOperation = async () => {
|
|
3775
|
+
if (!existsSync5(configPath)) {
|
|
3776
|
+
return null;
|
|
3777
|
+
}
|
|
3778
|
+
try {
|
|
3779
|
+
const cacheBuster = `?t=${Date.now()}`;
|
|
3780
|
+
const importedConfig = await import(configPath + cacheBuster);
|
|
3781
|
+
const loadedConfig = importedConfig.default || importedConfig;
|
|
3782
|
+
const hasDefaultExport = "default" in importedConfig;
|
|
3783
|
+
const hasNamedExports = Object.keys(importedConfig).length > 0;
|
|
3784
|
+
if (!hasDefaultExport && !hasNamedExports) {
|
|
3785
|
+
throw new ConfigLoadError(configPath, new Error("Configuration file is empty and exports nothing"), "unknown");
|
|
3786
|
+
}
|
|
3787
|
+
if (typeof loadedConfig !== "object" || loadedConfig === null || Array.isArray(loadedConfig)) {
|
|
3788
|
+
throw new ConfigLoadError(configPath, new Error("Configuration must export a valid object"), "unknown");
|
|
3789
|
+
}
|
|
3790
|
+
const mergedConfig = deepMergeWithArrayStrategy2(defaultConfig3, loadedConfig, arrayStrategy);
|
|
3791
|
+
const source = {
|
|
3792
|
+
type: "file",
|
|
3793
|
+
path: configPath,
|
|
3794
|
+
priority: 100,
|
|
3795
|
+
timestamp: new Date
|
|
3796
|
+
};
|
|
3797
|
+
const result = { config: mergedConfig, source };
|
|
3798
|
+
if (useCache) {
|
|
3799
|
+
globalCache.setWithFileCheck("file", result, configPath, cacheTtl);
|
|
3800
|
+
}
|
|
3801
|
+
return result;
|
|
3802
|
+
} catch (error) {
|
|
3803
|
+
const bunfigError = error instanceof Error ? ErrorFactory.configLoad(configPath, error) : ErrorFactory.configLoad(configPath, new Error(String(error)));
|
|
3804
|
+
throw bunfigError;
|
|
3805
|
+
}
|
|
3806
|
+
};
|
|
3807
|
+
if (trackPerformance) {
|
|
3808
|
+
return globalPerformanceMonitor.track("loadFromPath", loadOperation, { path: configPath });
|
|
3809
|
+
}
|
|
3810
|
+
return loadOperation();
|
|
3811
|
+
}
|
|
3812
|
+
async tryLoadFromPaths(configPaths, defaultConfig3, options = {}) {
|
|
3813
|
+
for (const configPath of configPaths) {
|
|
3814
|
+
try {
|
|
3815
|
+
const result = await this.loadFromPath(configPath, defaultConfig3, options);
|
|
3816
|
+
if (result) {
|
|
3817
|
+
return result;
|
|
3818
|
+
}
|
|
3819
|
+
} catch (error) {
|
|
3820
|
+
if (error instanceof Error && error.name === "ConfigLoadError") {
|
|
3821
|
+
throw error;
|
|
3822
|
+
}
|
|
3823
|
+
if (options.verbose) {
|
|
3824
|
+
console.warn(`Failed to load config from ${configPath}:`, error);
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
return null;
|
|
3829
|
+
}
|
|
3830
|
+
generateConfigPaths(configName, directory, alias) {
|
|
3831
|
+
const patterns = this.generateNamePatterns(configName, alias);
|
|
3832
|
+
const paths = [];
|
|
3833
|
+
for (const pattern of patterns) {
|
|
3834
|
+
for (const ext of this.extensions) {
|
|
3835
|
+
paths.push(resolve5(directory, `${pattern}${ext}`));
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
return paths;
|
|
3839
|
+
}
|
|
3840
|
+
generateNamePatterns(configName, alias) {
|
|
3841
|
+
const patterns = [];
|
|
3842
|
+
patterns.push("config", ".config");
|
|
3843
|
+
if (configName) {
|
|
3844
|
+
patterns.push(configName, `.${configName}.config`, `${configName}.config`, `.${configName}`);
|
|
3845
|
+
}
|
|
3846
|
+
if (alias) {
|
|
3847
|
+
patterns.push(alias, `.${alias}.config`, `${alias}.config`, `.${alias}`);
|
|
3848
|
+
if (configName) {
|
|
3849
|
+
patterns.push(`${configName}.${alias}.config`, `.${configName}.${alias}.config`);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
return patterns.filter(Boolean);
|
|
3853
|
+
}
|
|
3854
|
+
checkFileAccess(filePath) {
|
|
3855
|
+
return withErrorRecovery(async () => {
|
|
3856
|
+
return existsSync5(filePath);
|
|
3857
|
+
}, {
|
|
3858
|
+
maxRetries: 2,
|
|
3859
|
+
retryDelay: 100,
|
|
3860
|
+
fallback: false
|
|
3861
|
+
});
|
|
3862
|
+
}
|
|
3863
|
+
async discoverConfigFiles(directory, configName, alias) {
|
|
3864
|
+
const discoveredFiles = [];
|
|
3865
|
+
if (!existsSync5(directory)) {
|
|
3866
|
+
return discoveredFiles;
|
|
3867
|
+
}
|
|
3868
|
+
if (configName || alias) {
|
|
3869
|
+
const patterns = this.generateNamePatterns(configName || "", alias);
|
|
3870
|
+
for (const pattern of patterns) {
|
|
3871
|
+
for (const ext of this.extensions) {
|
|
3872
|
+
const filePath = resolve5(directory, `${pattern}${ext}`);
|
|
3873
|
+
if (await this.checkFileAccess(filePath)) {
|
|
3874
|
+
discoveredFiles.push(filePath);
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
} else {
|
|
3879
|
+
try {
|
|
3880
|
+
const { readdirSync: readdirSync32 } = await import("fs");
|
|
3881
|
+
const files = readdirSync32(directory);
|
|
3882
|
+
for (const file of files) {
|
|
3883
|
+
if (this.looksLikeConfigFile(file)) {
|
|
3884
|
+
const filePath = resolve5(directory, file);
|
|
3885
|
+
if (await this.checkFileAccess(filePath)) {
|
|
3886
|
+
discoveredFiles.push(filePath);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
} catch {
|
|
3891
|
+
return [];
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
return discoveredFiles;
|
|
3895
|
+
}
|
|
3896
|
+
looksLikeConfigFile(filename) {
|
|
3897
|
+
const configPatterns = [
|
|
3898
|
+
/\.config\.(ts|js|mjs|cjs|json|mts|cts)$/,
|
|
3899
|
+
/^\..*\.(ts|js|mjs|cjs|json|mts|cts)$/,
|
|
3900
|
+
/config\.(ts|js|mjs|cjs|json|mts|cts)$/
|
|
3901
|
+
];
|
|
3902
|
+
return configPatterns.some((pattern) => pattern.test(filename));
|
|
3903
|
+
}
|
|
3904
|
+
async validateConfigFile(filePath) {
|
|
3905
|
+
const errors = [];
|
|
3906
|
+
try {
|
|
3907
|
+
if (!existsSync5(filePath)) {
|
|
3908
|
+
errors.push("Configuration file does not exist");
|
|
3909
|
+
return errors;
|
|
3910
|
+
}
|
|
3911
|
+
const imported = await import(filePath);
|
|
3912
|
+
const config3 = imported.default || imported;
|
|
3913
|
+
if (config3 === undefined) {
|
|
3914
|
+
errors.push("Configuration file must export a default value or named exports");
|
|
3915
|
+
} else if (typeof config3 !== "object" || config3 === null) {
|
|
3916
|
+
errors.push("Configuration must be an object");
|
|
3917
|
+
} else if (Array.isArray(config3)) {
|
|
3918
|
+
errors.push("Configuration cannot be an array at the root level");
|
|
3919
|
+
}
|
|
3920
|
+
if (filePath.endsWith(".json")) {
|
|
3921
|
+
try {
|
|
3922
|
+
const { readFileSync } = await import("fs");
|
|
3923
|
+
const content = readFileSync(filePath, "utf8");
|
|
3924
|
+
JSON.parse(content);
|
|
3925
|
+
} catch (jsonError) {
|
|
3926
|
+
errors.push(`Invalid JSON syntax: ${jsonError}`);
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
} catch (error) {
|
|
3930
|
+
errors.push(`Failed to load configuration file: ${error}`);
|
|
3931
|
+
}
|
|
3932
|
+
return errors;
|
|
3933
|
+
}
|
|
3934
|
+
async getFileModificationTime(filePath) {
|
|
3935
|
+
try {
|
|
3936
|
+
const { statSync: statSync2 } = await import("fs");
|
|
3937
|
+
const stats = statSync2(filePath);
|
|
3938
|
+
return stats.mtime;
|
|
3939
|
+
} catch {
|
|
3940
|
+
return null;
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
async preloadConfigurations(configPaths, options = {}) {
|
|
3944
|
+
const preloaded = new Map;
|
|
3945
|
+
await Promise.allSettled(configPaths.map(async (path) => {
|
|
3946
|
+
try {
|
|
3947
|
+
const result = await this.loadFromPath(path, {}, options);
|
|
3948
|
+
if (result) {
|
|
3949
|
+
preloaded.set(path, result.config);
|
|
3950
|
+
}
|
|
3951
|
+
} catch (error) {
|
|
3952
|
+
if (options.verbose) {
|
|
3953
|
+
console.warn(`Failed to preload ${path}:`, error);
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
}));
|
|
3957
|
+
return preloaded;
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
class ConfigValidator {
|
|
3962
|
+
async validateConfiguration(config3, schema, options = {}) {
|
|
3963
|
+
const {
|
|
3964
|
+
stopOnFirstError = false,
|
|
3965
|
+
validateRequired = true,
|
|
3966
|
+
validateTypes = true,
|
|
3967
|
+
customRules = [],
|
|
3968
|
+
trackPerformance = true,
|
|
3969
|
+
verbose = false
|
|
3970
|
+
} = options;
|
|
3971
|
+
const operation = async () => {
|
|
3972
|
+
const errors = [];
|
|
3973
|
+
const warnings = [];
|
|
3974
|
+
const resolvedOptions = {
|
|
3975
|
+
stopOnFirstError,
|
|
3976
|
+
validateRequired,
|
|
3977
|
+
validateTypes,
|
|
3978
|
+
customRules,
|
|
3979
|
+
trackPerformance,
|
|
3980
|
+
verbose
|
|
3981
|
+
};
|
|
3982
|
+
try {
|
|
3983
|
+
if (typeof schema === "string") {
|
|
3984
|
+
return await this.validateWithSchemaFile(config3, schema, resolvedOptions);
|
|
3985
|
+
} else if (Array.isArray(schema)) {
|
|
3986
|
+
return this.validateWithRules(config3, [...schema, ...customRules], resolvedOptions);
|
|
3987
|
+
} else {
|
|
3988
|
+
return this.validateWithJSONSchema(config3, schema, resolvedOptions);
|
|
3989
|
+
}
|
|
3990
|
+
} catch (error) {
|
|
3991
|
+
errors.push({
|
|
3992
|
+
path: "",
|
|
3993
|
+
message: `Validation failed: ${error}`,
|
|
3994
|
+
rule: "system"
|
|
3995
|
+
});
|
|
3996
|
+
return { isValid: false, errors, warnings };
|
|
3997
|
+
}
|
|
3998
|
+
};
|
|
3999
|
+
if (trackPerformance) {
|
|
4000
|
+
const result = await globalPerformanceMonitor.track("validateConfiguration", operation);
|
|
4001
|
+
return result;
|
|
4002
|
+
}
|
|
4003
|
+
return operation();
|
|
4004
|
+
}
|
|
4005
|
+
async validateWithSchemaFile(config3, schemaPath, options) {
|
|
4006
|
+
try {
|
|
4007
|
+
if (!existsSync7(schemaPath)) {
|
|
4008
|
+
throw new SchemaValidationError(schemaPath, [{ path: "", message: "Schema file does not exist" }]);
|
|
4009
|
+
}
|
|
4010
|
+
const schemaModule = await import(schemaPath);
|
|
4011
|
+
const schema = schemaModule.default || schemaModule;
|
|
4012
|
+
if (Array.isArray(schema)) {
|
|
4013
|
+
return this.validateWithRules(config3, schema, options);
|
|
4014
|
+
} else {
|
|
4015
|
+
return this.validateWithJSONSchema(config3, schema, options);
|
|
4016
|
+
}
|
|
4017
|
+
} catch (error) {
|
|
4018
|
+
throw new SchemaValidationError(schemaPath, [{ path: "", message: `Failed to load schema: ${error}` }]);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
validateWithJSONSchema(config3, schema, options) {
|
|
4022
|
+
const errors = [];
|
|
4023
|
+
const warnings = [];
|
|
4024
|
+
this.validateObjectAgainstSchema(config3, schema, "", errors, warnings, options);
|
|
4025
|
+
return {
|
|
4026
|
+
isValid: errors.length === 0,
|
|
4027
|
+
errors,
|
|
4028
|
+
warnings
|
|
4029
|
+
};
|
|
4030
|
+
}
|
|
4031
|
+
validateObjectAgainstSchema(value, schema, path, errors, warnings, options) {
|
|
4032
|
+
if (options.validateTypes && schema.type) {
|
|
4033
|
+
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
4034
|
+
const expectedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
4035
|
+
if (!expectedTypes.includes(actualType)) {
|
|
4036
|
+
errors.push({
|
|
4037
|
+
path,
|
|
4038
|
+
message: `Expected type ${expectedTypes.join(" or ")}, got ${actualType}`,
|
|
4039
|
+
expected: expectedTypes.join(" or "),
|
|
4040
|
+
actual: actualType,
|
|
4041
|
+
rule: "type"
|
|
4042
|
+
});
|
|
4043
|
+
if (options.stopOnFirstError)
|
|
4044
|
+
return;
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
4048
|
+
errors.push({
|
|
4049
|
+
path,
|
|
4050
|
+
message: `Value must be one of: ${schema.enum.join(", ")}`,
|
|
4051
|
+
expected: schema.enum.join(", "),
|
|
4052
|
+
actual: value,
|
|
4053
|
+
rule: "enum"
|
|
4054
|
+
});
|
|
4055
|
+
if (options.stopOnFirstError)
|
|
4056
|
+
return;
|
|
4057
|
+
}
|
|
4058
|
+
if (typeof value === "string") {
|
|
4059
|
+
if (schema.minLength !== undefined && value.length < schema.minLength) {
|
|
4060
|
+
errors.push({
|
|
4061
|
+
path,
|
|
4062
|
+
message: `String length must be at least ${schema.minLength}`,
|
|
4063
|
+
expected: `>= ${schema.minLength}`,
|
|
4064
|
+
actual: value.length,
|
|
4065
|
+
rule: "minLength"
|
|
4066
|
+
});
|
|
4067
|
+
}
|
|
4068
|
+
if (schema.maxLength !== undefined && value.length > schema.maxLength) {
|
|
4069
|
+
errors.push({
|
|
4070
|
+
path,
|
|
4071
|
+
message: `String length must not exceed ${schema.maxLength}`,
|
|
4072
|
+
expected: `<= ${schema.maxLength}`,
|
|
4073
|
+
actual: value.length,
|
|
4074
|
+
rule: "maxLength"
|
|
4075
|
+
});
|
|
4076
|
+
}
|
|
4077
|
+
if (schema.pattern) {
|
|
4078
|
+
const regex = new RegExp(schema.pattern);
|
|
4079
|
+
if (!regex.test(value)) {
|
|
4080
|
+
errors.push({
|
|
4081
|
+
path,
|
|
4082
|
+
message: `String does not match pattern ${schema.pattern}`,
|
|
4083
|
+
expected: schema.pattern,
|
|
4084
|
+
actual: value,
|
|
4085
|
+
rule: "pattern"
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
if (typeof value === "number") {
|
|
4091
|
+
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
4092
|
+
errors.push({
|
|
4093
|
+
path,
|
|
4094
|
+
message: `Value must be at least ${schema.minimum}`,
|
|
4095
|
+
expected: `>= ${schema.minimum}`,
|
|
4096
|
+
actual: value,
|
|
4097
|
+
rule: "minimum"
|
|
4098
|
+
});
|
|
4099
|
+
}
|
|
4100
|
+
if (schema.maximum !== undefined && value > schema.maximum) {
|
|
4101
|
+
errors.push({
|
|
4102
|
+
path,
|
|
4103
|
+
message: `Value must not exceed ${schema.maximum}`,
|
|
4104
|
+
expected: `<= ${schema.maximum}`,
|
|
4105
|
+
actual: value,
|
|
4106
|
+
rule: "maximum"
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
if (Array.isArray(value) && schema.items) {
|
|
4111
|
+
for (let i = 0;i < value.length; i++) {
|
|
4112
|
+
const itemPath = path ? `${path}[${i}]` : `[${i}]`;
|
|
4113
|
+
this.validateObjectAgainstSchema(value[i], schema.items, itemPath, errors, warnings, options);
|
|
4114
|
+
if (options.stopOnFirstError && errors.length > 0)
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
4119
|
+
const obj = value;
|
|
4120
|
+
if (options.validateRequired && schema.required) {
|
|
4121
|
+
for (const requiredProp of schema.required) {
|
|
4122
|
+
if (!(requiredProp in obj)) {
|
|
4123
|
+
errors.push({
|
|
4124
|
+
path: path ? `${path}.${requiredProp}` : requiredProp,
|
|
4125
|
+
message: `Missing required property '${requiredProp}'`,
|
|
4126
|
+
expected: "required",
|
|
4127
|
+
rule: "required"
|
|
4128
|
+
});
|
|
4129
|
+
if (options.stopOnFirstError)
|
|
4130
|
+
return;
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
if (schema.properties) {
|
|
4135
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
4136
|
+
if (propName in obj) {
|
|
4137
|
+
const propPath = path ? `${path}.${propName}` : propName;
|
|
4138
|
+
this.validateObjectAgainstSchema(obj[propName], propSchema, propPath, errors, warnings, options);
|
|
4139
|
+
if (options.stopOnFirstError && errors.length > 0)
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
if (schema.additionalProperties === false) {
|
|
4145
|
+
const allowedProps = new Set(Object.keys(schema.properties || {}));
|
|
4146
|
+
for (const propName of Object.keys(obj)) {
|
|
4147
|
+
if (!allowedProps.has(propName)) {
|
|
4148
|
+
warnings.push({
|
|
4149
|
+
path: path ? `${path}.${propName}` : propName,
|
|
4150
|
+
message: `Additional property '${propName}' is not allowed`,
|
|
4151
|
+
rule: "additionalProperties"
|
|
4152
|
+
});
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
validateWithRules(config3, rules, options) {
|
|
4159
|
+
const errors = [];
|
|
4160
|
+
const warnings = [];
|
|
4161
|
+
for (const rule of rules) {
|
|
4162
|
+
try {
|
|
4163
|
+
const value = this.getValueByPath(config3, rule.path);
|
|
4164
|
+
const ruleErrors = this.validateWithRule(value, rule, rule.path);
|
|
4165
|
+
errors.push(...ruleErrors);
|
|
4166
|
+
if (options.stopOnFirstError && errors.length > 0) {
|
|
4167
|
+
break;
|
|
4168
|
+
}
|
|
4169
|
+
} catch (error) {
|
|
4170
|
+
errors.push({
|
|
4171
|
+
path: rule.path,
|
|
4172
|
+
message: `Rule validation failed: ${error}`,
|
|
4173
|
+
rule: "system"
|
|
4174
|
+
});
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
return {
|
|
4178
|
+
isValid: errors.length === 0,
|
|
4179
|
+
errors,
|
|
4180
|
+
warnings
|
|
4181
|
+
};
|
|
4182
|
+
}
|
|
4183
|
+
validateWithRule(value, rule, path) {
|
|
4184
|
+
const errors = [];
|
|
4185
|
+
if (rule.required && (value === undefined || value === null)) {
|
|
4186
|
+
errors.push({
|
|
4187
|
+
path,
|
|
4188
|
+
message: rule.message || `Property '${path}' is required`,
|
|
4189
|
+
expected: "required",
|
|
4190
|
+
rule: "required"
|
|
4191
|
+
});
|
|
4192
|
+
return errors;
|
|
4193
|
+
}
|
|
4194
|
+
if (value === undefined || value === null) {
|
|
4195
|
+
return errors;
|
|
4196
|
+
}
|
|
4197
|
+
if (rule.type) {
|
|
4198
|
+
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
4199
|
+
if (actualType !== rule.type) {
|
|
4200
|
+
errors.push({
|
|
4201
|
+
path,
|
|
4202
|
+
message: rule.message || `Expected type ${rule.type}, got ${actualType}`,
|
|
4203
|
+
expected: rule.type,
|
|
4204
|
+
actual: actualType,
|
|
4205
|
+
rule: "type"
|
|
4206
|
+
});
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
if (rule.min !== undefined) {
|
|
4210
|
+
const length = Array.isArray(value) ? value.length : typeof value === "string" ? value.length : typeof value === "number" ? value : 0;
|
|
4211
|
+
if (length < rule.min) {
|
|
4212
|
+
errors.push({
|
|
4213
|
+
path,
|
|
4214
|
+
message: rule.message || `Value must be at least ${rule.min}`,
|
|
4215
|
+
expected: `>= ${rule.min}`,
|
|
4216
|
+
actual: length,
|
|
4217
|
+
rule: "min"
|
|
4218
|
+
});
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
if (rule.max !== undefined) {
|
|
4222
|
+
const length = Array.isArray(value) ? value.length : typeof value === "string" ? value.length : typeof value === "number" ? value : 0;
|
|
4223
|
+
if (length > rule.max) {
|
|
4224
|
+
errors.push({
|
|
4225
|
+
path,
|
|
4226
|
+
message: rule.message || `Value must not exceed ${rule.max}`,
|
|
4227
|
+
expected: `<= ${rule.max}`,
|
|
4228
|
+
actual: length,
|
|
4229
|
+
rule: "max"
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
if (rule.pattern && typeof value === "string") {
|
|
4234
|
+
if (!rule.pattern.test(value)) {
|
|
4235
|
+
errors.push({
|
|
4236
|
+
path,
|
|
4237
|
+
message: rule.message || `Value does not match pattern ${rule.pattern}`,
|
|
4238
|
+
expected: rule.pattern.toString(),
|
|
4239
|
+
actual: value,
|
|
4240
|
+
rule: "pattern"
|
|
4241
|
+
});
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
if (rule.enum && !rule.enum.includes(value)) {
|
|
4245
|
+
errors.push({
|
|
4246
|
+
path,
|
|
4247
|
+
message: rule.message || `Value must be one of: ${rule.enum.join(", ")}`,
|
|
4248
|
+
expected: rule.enum.join(", "),
|
|
4249
|
+
actual: value,
|
|
4250
|
+
rule: "enum"
|
|
4251
|
+
});
|
|
4252
|
+
}
|
|
4253
|
+
if (rule.validator) {
|
|
4254
|
+
const customError = rule.validator(value);
|
|
4255
|
+
if (customError) {
|
|
4256
|
+
errors.push({
|
|
4257
|
+
path,
|
|
4258
|
+
message: rule.message || customError,
|
|
4259
|
+
rule: "custom"
|
|
4260
|
+
});
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
return errors;
|
|
4264
|
+
}
|
|
4265
|
+
getValueByPath(obj, path) {
|
|
4266
|
+
if (!path)
|
|
4267
|
+
return obj;
|
|
4268
|
+
const keys = path.split(".");
|
|
4269
|
+
let current = obj;
|
|
4270
|
+
for (const key of keys) {
|
|
4271
|
+
if (current && typeof current === "object" && key in current) {
|
|
4272
|
+
current = current[key];
|
|
4273
|
+
} else {
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
return current;
|
|
4278
|
+
}
|
|
4279
|
+
generateRulesFromInterface(interfaceCode) {
|
|
4280
|
+
const rules = [];
|
|
4281
|
+
const propertyMatches = interfaceCode.matchAll(/(\w+)(\?)?:\s*(\w+)/g);
|
|
4282
|
+
for (const match of propertyMatches) {
|
|
4283
|
+
const [, propName, optional, typeName] = match;
|
|
4284
|
+
rules.push({
|
|
4285
|
+
path: propName,
|
|
4286
|
+
required: !optional,
|
|
4287
|
+
type: this.mapTypeScriptType(typeName)
|
|
4288
|
+
});
|
|
4289
|
+
}
|
|
4290
|
+
return rules;
|
|
4291
|
+
}
|
|
4292
|
+
mapTypeScriptType(tsType) {
|
|
4293
|
+
switch (tsType.toLowerCase()) {
|
|
4294
|
+
case "string":
|
|
4295
|
+
return "string";
|
|
4296
|
+
case "number":
|
|
4297
|
+
return "number";
|
|
4298
|
+
case "boolean":
|
|
4299
|
+
return "boolean";
|
|
4300
|
+
case "array":
|
|
4301
|
+
return "array";
|
|
4302
|
+
case "object":
|
|
4303
|
+
return "object";
|
|
4304
|
+
default:
|
|
4305
|
+
return "object";
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
static createCommonRules() {
|
|
4309
|
+
return {
|
|
4310
|
+
server: [
|
|
4311
|
+
{ path: "port", required: true, type: "number", min: 1, max: 65535 },
|
|
4312
|
+
{ path: "host", required: true, type: "string", min: 1 },
|
|
4313
|
+
{ path: "ssl", type: "boolean" }
|
|
4314
|
+
],
|
|
4315
|
+
database: [
|
|
4316
|
+
{ path: "url", required: true, type: "string", min: 1 },
|
|
4317
|
+
{ path: "pool", type: "number", min: 1, max: 100 },
|
|
4318
|
+
{ path: "timeout", type: "number", min: 0 }
|
|
4319
|
+
],
|
|
4320
|
+
api: [
|
|
4321
|
+
{ path: "baseUrl", required: true, type: "string", pattern: URL_PATTERN },
|
|
4322
|
+
{ path: "timeout", type: "number", min: 0 },
|
|
4323
|
+
{ path: "retries", type: "number", min: 0, max: 10 }
|
|
4324
|
+
]
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
class ConfigLoader {
|
|
4330
|
+
fileLoader = new ConfigFileLoader;
|
|
4331
|
+
envProcessor = new EnvProcessor;
|
|
4332
|
+
validator = new ConfigValidator;
|
|
4333
|
+
async loadConfig(options) {
|
|
4334
|
+
const startTime = Date.now();
|
|
4335
|
+
const {
|
|
4336
|
+
cache,
|
|
4337
|
+
performance: performance2,
|
|
4338
|
+
schema,
|
|
4339
|
+
validate: customValidator,
|
|
4340
|
+
...baseOptions
|
|
4341
|
+
} = options;
|
|
4342
|
+
try {
|
|
4343
|
+
if (cache?.enabled) {
|
|
4344
|
+
const cached = this.checkCache(baseOptions.name || "", baseOptions);
|
|
4345
|
+
if (cached) {
|
|
4346
|
+
return cached;
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
let result;
|
|
4350
|
+
try {
|
|
4351
|
+
result = await this.loadConfigurationStrategies(baseOptions, true, cache);
|
|
4352
|
+
} catch (error) {
|
|
4353
|
+
const isStrictMode = baseOptions.__strictErrorHandling;
|
|
4354
|
+
if (error instanceof Error && error.name === "ConfigNotFoundError") {
|
|
4355
|
+
if (isStrictMode) {
|
|
4356
|
+
throw error;
|
|
4357
|
+
}
|
|
4358
|
+
const envResult = await this.applyEnvironmentVariables(baseOptions.name || "", baseOptions.defaultConfig, baseOptions.checkEnv !== false, baseOptions.verbose || false);
|
|
4359
|
+
result = {
|
|
4360
|
+
...envResult,
|
|
4361
|
+
warnings: [`No configuration file found for "${baseOptions.name || "config"}", using defaults with environment variables`]
|
|
4362
|
+
};
|
|
4363
|
+
} else if (error instanceof Error && error.name === "ConfigLoadError") {
|
|
4364
|
+
const isPermissionError = error.message.includes("EACCES") || error.message.includes("EPERM") || error.message.includes("permission denied");
|
|
4365
|
+
const isSyntaxError = !isPermissionError && (error.message.includes("syntax") || error.message.includes("Expected") || error.message.includes("Unexpected") || error.message.includes("BuildMessage") || error.message.includes("errors building"));
|
|
4366
|
+
const isStructureError = error.message.includes("Configuration must export a valid object") || error.message.includes("Configuration file is empty and exports nothing");
|
|
4367
|
+
if (isStrictMode && (isStructureError || isPermissionError)) {
|
|
4368
|
+
throw error;
|
|
4369
|
+
}
|
|
4370
|
+
if (isSyntaxError && (!isStrictMode || !isStructureError)) {
|
|
4371
|
+
const envResult = await this.applyEnvironmentVariables(baseOptions.name || "", baseOptions.defaultConfig, baseOptions.checkEnv !== false, baseOptions.verbose || false);
|
|
4372
|
+
result = {
|
|
4373
|
+
...envResult,
|
|
4374
|
+
warnings: [`Configuration file has syntax errors, using defaults with environment variables`]
|
|
4375
|
+
};
|
|
4376
|
+
} else if (!isStrictMode) {
|
|
4377
|
+
const envResult = await this.applyEnvironmentVariables(baseOptions.name || "", baseOptions.defaultConfig, baseOptions.checkEnv !== false, baseOptions.verbose || false);
|
|
4378
|
+
result = {
|
|
4379
|
+
...envResult,
|
|
4380
|
+
warnings: [`Configuration loading error, using defaults: ${error.message}`]
|
|
4381
|
+
};
|
|
4382
|
+
} else {
|
|
4383
|
+
throw error;
|
|
4384
|
+
}
|
|
4385
|
+
} else {
|
|
4386
|
+
const envResult = await this.applyEnvironmentVariables(baseOptions.name || "", baseOptions.defaultConfig, baseOptions.checkEnv !== false, baseOptions.verbose || false);
|
|
4387
|
+
result = {
|
|
4388
|
+
...envResult,
|
|
4389
|
+
warnings: [`Configuration loading failed, using defaults: ${error instanceof Error ? error.message : String(error)}`]
|
|
4390
|
+
};
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
4393
|
+
if (schema || customValidator) {
|
|
4394
|
+
await this.validateConfiguration(result.config, schema, customValidator, baseOptions.name);
|
|
4395
|
+
}
|
|
4396
|
+
if (cache?.enabled && result) {
|
|
4397
|
+
this.cacheResult(baseOptions.name || "", result, cache, baseOptions);
|
|
4398
|
+
}
|
|
4399
|
+
if (performance2?.enabled) {
|
|
4400
|
+
const metrics = {
|
|
4401
|
+
operation: "loadConfig",
|
|
4402
|
+
duration: Date.now() - startTime,
|
|
4403
|
+
configName: baseOptions.name,
|
|
4404
|
+
timestamp: new Date
|
|
4405
|
+
};
|
|
4406
|
+
if (performance2.onMetrics) {
|
|
4407
|
+
performance2.onMetrics(metrics);
|
|
4408
|
+
}
|
|
4409
|
+
if (performance2.slowThreshold && metrics.duration > performance2.slowThreshold) {
|
|
4410
|
+
log2.warn(`Slow configuration loading detected: ${metrics.duration}ms for ${baseOptions.name}`);
|
|
4411
|
+
}
|
|
4412
|
+
result.metrics = metrics;
|
|
4413
|
+
}
|
|
4414
|
+
return result;
|
|
4415
|
+
} catch (error) {
|
|
4416
|
+
const duration = Date.now() - startTime;
|
|
4417
|
+
log2.error(`Configuration loading failed after ${duration}ms:`, [error instanceof Error ? error : new Error(String(error))]);
|
|
4418
|
+
throw error;
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
async loadConfigurationStrategies(options, throwOnNotFound = false, cacheOptions) {
|
|
4422
|
+
const {
|
|
4423
|
+
name = "",
|
|
4424
|
+
alias,
|
|
4425
|
+
cwd,
|
|
4426
|
+
configDir,
|
|
4427
|
+
defaultConfig: defaultConfig3,
|
|
4428
|
+
checkEnv = true,
|
|
4429
|
+
arrayStrategy = "replace",
|
|
4430
|
+
verbose = false
|
|
4431
|
+
} = options;
|
|
4432
|
+
const baseDir = cwd || process12.cwd();
|
|
4433
|
+
const searchPaths = [];
|
|
4434
|
+
const localResult = await this.loadLocalConfiguration(name, alias, baseDir, configDir, defaultConfig3, arrayStrategy, verbose, checkEnv, cacheOptions);
|
|
4435
|
+
if (localResult) {
|
|
4436
|
+
searchPaths.push(...this.getLocalSearchPaths(name, alias, baseDir, configDir));
|
|
4437
|
+
return this.finalizeResult(localResult, searchPaths, checkEnv, name, verbose);
|
|
4438
|
+
}
|
|
4439
|
+
const homeResult = await this.loadHomeConfiguration(name, alias, defaultConfig3, arrayStrategy, verbose, checkEnv);
|
|
4440
|
+
if (homeResult) {
|
|
4441
|
+
searchPaths.push(...this.getHomeSearchPaths(name, alias));
|
|
4442
|
+
return this.finalizeResult(homeResult, searchPaths, checkEnv, name, verbose);
|
|
4443
|
+
}
|
|
4444
|
+
const packageResult = await this.loadPackageJsonConfiguration(name, alias, baseDir, defaultConfig3, arrayStrategy, verbose, checkEnv);
|
|
4445
|
+
if (packageResult) {
|
|
4446
|
+
searchPaths.push(resolve7(baseDir, "package.json"));
|
|
4447
|
+
return this.finalizeResult(packageResult, searchPaths, checkEnv, name, verbose);
|
|
4448
|
+
}
|
|
4449
|
+
searchPaths.push(...this.getAllSearchPaths(name, alias, baseDir, configDir));
|
|
4450
|
+
if (throwOnNotFound) {
|
|
4451
|
+
throw ErrorFactory.configNotFound(name, searchPaths, alias);
|
|
4452
|
+
}
|
|
4453
|
+
const envResult = await this.applyEnvironmentVariables(name, defaultConfig3, checkEnv, verbose);
|
|
4454
|
+
return {
|
|
4455
|
+
...envResult,
|
|
4456
|
+
warnings: [`No configuration file found for "${name}"${alias ? ` or alias "${alias}"` : ""}, using defaults with environment variables`]
|
|
4457
|
+
};
|
|
1713
4458
|
}
|
|
1714
|
-
|
|
1715
|
-
const
|
|
1716
|
-
const
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
4459
|
+
async loadLocalConfiguration(name, alias, baseDir, configDir, defaultConfig3, arrayStrategy, verbose, checkEnv, cacheOptions) {
|
|
4460
|
+
const envDefaultConfig = checkEnv ? applyEnvVarsToConfig2(name, defaultConfig3, verbose) : defaultConfig3;
|
|
4461
|
+
const searchDirectories = this.getLocalDirectories(baseDir, configDir);
|
|
4462
|
+
for (const directory of searchDirectories) {
|
|
4463
|
+
if (verbose) {
|
|
4464
|
+
log2.info(`Searching for configuration in: ${directory}`);
|
|
4465
|
+
}
|
|
4466
|
+
const configPaths = this.fileLoader.generateConfigPaths(name, directory, alias);
|
|
4467
|
+
const result = await this.fileLoader.tryLoadFromPaths(configPaths, envDefaultConfig, {
|
|
4468
|
+
arrayStrategy,
|
|
4469
|
+
verbose,
|
|
4470
|
+
cacheTtl: cacheOptions?.ttl,
|
|
4471
|
+
useCache: !cacheOptions?.ttl || cacheOptions.ttl > 100
|
|
4472
|
+
});
|
|
4473
|
+
if (result) {
|
|
4474
|
+
if (verbose) {
|
|
4475
|
+
log2.success(`Configuration loaded from: ${result.source.path}`);
|
|
1729
4476
|
}
|
|
4477
|
+
return result;
|
|
1730
4478
|
}
|
|
1731
4479
|
}
|
|
4480
|
+
return null;
|
|
1732
4481
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
for (const
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
4482
|
+
async loadHomeConfiguration(name, alias, defaultConfig3, arrayStrategy, verbose, checkEnv) {
|
|
4483
|
+
if (!name)
|
|
4484
|
+
return null;
|
|
4485
|
+
const envDefaultConfig = checkEnv ? applyEnvVarsToConfig2(name, defaultConfig3, verbose) : defaultConfig3;
|
|
4486
|
+
const homeDirectories = [
|
|
4487
|
+
resolve7(homedir2(), ".config", name),
|
|
4488
|
+
resolve7(homedir2(), ".config"),
|
|
4489
|
+
homedir2()
|
|
4490
|
+
];
|
|
4491
|
+
for (const directory of homeDirectories) {
|
|
4492
|
+
if (verbose) {
|
|
4493
|
+
log2.info(`Checking home directory: ${directory}`);
|
|
4494
|
+
}
|
|
4495
|
+
const configPaths = this.fileLoader.generateConfigPaths(name, directory, alias);
|
|
4496
|
+
const result = await this.fileLoader.tryLoadFromPaths(configPaths, envDefaultConfig, { arrayStrategy, verbose });
|
|
4497
|
+
if (result) {
|
|
4498
|
+
if (verbose) {
|
|
4499
|
+
log2.success(`Configuration loaded from home directory: ${result.source.path}`);
|
|
1750
4500
|
}
|
|
4501
|
+
return result;
|
|
1751
4502
|
}
|
|
1752
4503
|
}
|
|
4504
|
+
return null;
|
|
1753
4505
|
}
|
|
1754
|
-
|
|
1755
|
-
const
|
|
1756
|
-
|
|
4506
|
+
async loadPackageJsonConfiguration(name, alias, baseDir, defaultConfig3, arrayStrategy, verbose, checkEnv) {
|
|
4507
|
+
const envDefaultConfig = checkEnv ? applyEnvVarsToConfig2(name, defaultConfig3, verbose) : defaultConfig3;
|
|
4508
|
+
try {
|
|
4509
|
+
const pkgPath = resolve7(baseDir, "package.json");
|
|
4510
|
+
if (!existsSync8(pkgPath)) {
|
|
4511
|
+
return null;
|
|
4512
|
+
}
|
|
1757
4513
|
const pkg = await import(pkgPath);
|
|
1758
4514
|
let pkgConfig = pkg[name];
|
|
4515
|
+
let usedName = name;
|
|
1759
4516
|
if (!pkgConfig && alias) {
|
|
1760
4517
|
pkgConfig = pkg[alias];
|
|
1761
|
-
|
|
1762
|
-
log.success(`Using alias "${alias}" configuration from package.json`);
|
|
1763
|
-
}
|
|
4518
|
+
usedName = alias;
|
|
1764
4519
|
}
|
|
1765
4520
|
if (pkgConfig && typeof pkgConfig === "object" && !Array.isArray(pkgConfig)) {
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
4521
|
+
if (verbose) {
|
|
4522
|
+
log2.success(`Configuration loaded from package.json: ${usedName}`);
|
|
4523
|
+
}
|
|
4524
|
+
const mergedConfig = deepMergeWithArrayStrategy2(envDefaultConfig, pkgConfig, arrayStrategy);
|
|
4525
|
+
return {
|
|
4526
|
+
config: mergedConfig,
|
|
4527
|
+
source: {
|
|
4528
|
+
type: "package.json",
|
|
4529
|
+
path: pkgPath,
|
|
4530
|
+
priority: 30,
|
|
4531
|
+
timestamp: new Date
|
|
1774
4532
|
}
|
|
4533
|
+
};
|
|
4534
|
+
}
|
|
4535
|
+
} catch (error) {
|
|
4536
|
+
if (verbose) {
|
|
4537
|
+
log2.warn(`Failed to load package.json:`, [error instanceof Error ? error : new Error(String(error))]);
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
return null;
|
|
4541
|
+
}
|
|
4542
|
+
async applyEnvironmentVariables(name, config3, checkEnv, verbose) {
|
|
4543
|
+
if (!checkEnv || !name || typeof config3 !== "object" || config3 === null || Array.isArray(config3)) {
|
|
4544
|
+
return {
|
|
4545
|
+
config: config3,
|
|
4546
|
+
source: {
|
|
4547
|
+
type: "default",
|
|
4548
|
+
priority: 10,
|
|
4549
|
+
timestamp: new Date
|
|
1775
4550
|
}
|
|
4551
|
+
};
|
|
4552
|
+
}
|
|
4553
|
+
const processedConfig = applyEnvVarsToConfig2(name, config3, verbose);
|
|
4554
|
+
return {
|
|
4555
|
+
config: processedConfig,
|
|
4556
|
+
source: {
|
|
4557
|
+
type: "environment",
|
|
4558
|
+
priority: 20,
|
|
4559
|
+
timestamp: new Date
|
|
1776
4560
|
}
|
|
4561
|
+
};
|
|
4562
|
+
}
|
|
4563
|
+
async finalizeResult(result, _searchPaths, _checkEnv, _name, _verbose) {
|
|
4564
|
+
return {
|
|
4565
|
+
config: result.config,
|
|
4566
|
+
source: result.source,
|
|
4567
|
+
path: result.source.path
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
async validateConfiguration(config3, schema, customValidator, configName) {
|
|
4571
|
+
const errors = [];
|
|
4572
|
+
if (customValidator) {
|
|
4573
|
+
const customErrors = customValidator(config3);
|
|
4574
|
+
if (customErrors) {
|
|
4575
|
+
errors.push(...customErrors);
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
if (schema) {
|
|
4579
|
+
const validationResult = await this.validator.validateConfiguration(config3, schema);
|
|
4580
|
+
if (!validationResult.isValid) {
|
|
4581
|
+
errors.push(...validationResult.errors.map((e) => e.path ? `${e.path}: ${e.message}` : e.message));
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
if (errors.length > 0) {
|
|
4585
|
+
throw ErrorFactory.configValidation(configName || "unknown", errors, configName);
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
checkCache(configName, options) {
|
|
4589
|
+
const cacheKey = this.generateCacheKey(configName, options);
|
|
4590
|
+
const result = globalCache.get(cacheKey);
|
|
4591
|
+
return result || null;
|
|
4592
|
+
}
|
|
4593
|
+
cacheResult(configName, result, cacheOptions, options) {
|
|
4594
|
+
const cacheKey = this.generateCacheKey(configName, options);
|
|
4595
|
+
globalCache.set(cacheKey, result, undefined, cacheOptions.ttl);
|
|
4596
|
+
}
|
|
4597
|
+
generateCacheKey(configName, options) {
|
|
4598
|
+
const keyParts = [configName];
|
|
4599
|
+
if (options.alias)
|
|
4600
|
+
keyParts.push(`alias:${options.alias}`);
|
|
4601
|
+
if (options.cwd)
|
|
4602
|
+
keyParts.push(`cwd:${options.cwd}`);
|
|
4603
|
+
if (options.configDir)
|
|
4604
|
+
keyParts.push(`configDir:${options.configDir}`);
|
|
4605
|
+
if ("checkEnv" in options)
|
|
4606
|
+
keyParts.push(`checkEnv:${options.checkEnv}`);
|
|
4607
|
+
return keyParts.join("|");
|
|
4608
|
+
}
|
|
4609
|
+
getLocalDirectories(baseDir, configDir) {
|
|
4610
|
+
return Array.from(new Set([
|
|
4611
|
+
baseDir,
|
|
4612
|
+
resolve7(baseDir, "config"),
|
|
4613
|
+
resolve7(baseDir, ".config"),
|
|
4614
|
+
configDir ? resolve7(baseDir, configDir) : undefined
|
|
4615
|
+
].filter(Boolean)));
|
|
4616
|
+
}
|
|
4617
|
+
getAllSearchPaths(name, alias, baseDir, configDir) {
|
|
4618
|
+
const paths = [];
|
|
4619
|
+
paths.push(...this.getLocalSearchPaths(name, alias, baseDir, configDir));
|
|
4620
|
+
paths.push(...this.getHomeSearchPaths(name, alias));
|
|
4621
|
+
paths.push(resolve7(baseDir, "package.json"));
|
|
4622
|
+
return paths;
|
|
4623
|
+
}
|
|
4624
|
+
getLocalSearchPaths(name, alias, baseDir, configDir) {
|
|
4625
|
+
const directories = this.getLocalDirectories(baseDir, configDir);
|
|
4626
|
+
const paths = [];
|
|
4627
|
+
for (const directory of directories) {
|
|
4628
|
+
paths.push(...this.fileLoader.generateConfigPaths(name, directory, alias));
|
|
4629
|
+
}
|
|
4630
|
+
return paths;
|
|
4631
|
+
}
|
|
4632
|
+
getHomeSearchPaths(name, alias) {
|
|
4633
|
+
if (!name)
|
|
4634
|
+
return [];
|
|
4635
|
+
const homeDirectories = [
|
|
4636
|
+
resolve7(homedir2(), ".config", name),
|
|
4637
|
+
resolve7(homedir2(), ".config"),
|
|
4638
|
+
homedir2()
|
|
4639
|
+
];
|
|
4640
|
+
const paths = [];
|
|
4641
|
+
for (const directory of homeDirectories) {
|
|
4642
|
+
paths.push(...this.fileLoader.generateConfigPaths(name, directory, alias));
|
|
4643
|
+
}
|
|
4644
|
+
return paths;
|
|
4645
|
+
}
|
|
4646
|
+
async loadConfigWithResult(options) {
|
|
4647
|
+
return this.loadConfig(options);
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
async function loadConfig5(options) {
|
|
4651
|
+
const defaultConfig3 = "defaultConfig" in options && options.defaultConfig !== undefined ? options.defaultConfig : {};
|
|
4652
|
+
const isEnhanced = "cache" in options || "performance" in options || "schema" in options || "validate" in options;
|
|
4653
|
+
try {
|
|
4654
|
+
let result;
|
|
4655
|
+
if (isEnhanced) {
|
|
4656
|
+
result = await globalConfigLoader.loadConfig(options);
|
|
4657
|
+
} else {
|
|
4658
|
+
result = await globalConfigLoader.loadConfig({
|
|
4659
|
+
...options,
|
|
4660
|
+
defaultConfig: defaultConfig3,
|
|
4661
|
+
cache: { enabled: true },
|
|
4662
|
+
performance: { enabled: false }
|
|
4663
|
+
});
|
|
1777
4664
|
}
|
|
4665
|
+
return result?.config ?? defaultConfig3;
|
|
1778
4666
|
} catch (error) {
|
|
1779
|
-
|
|
1780
|
-
|
|
4667
|
+
const errorName = error instanceof Error ? error.name : "UnknownError";
|
|
4668
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4669
|
+
const isConfigError = errorName === "ConfigNotFoundError" || errorName === "ConfigLoadError" || errorName === "ConfigValidationError" || errorMessage.includes("config");
|
|
4670
|
+
if (!isConfigError && options.verbose) {
|
|
4671
|
+
log2.warn(`Unexpected error loading config, using defaults:`, [error instanceof Error ? error : new Error(String(error))]);
|
|
4672
|
+
}
|
|
4673
|
+
const configOptions = isEnhanced ? { ...options, defaultConfig: defaultConfig3 } : {
|
|
4674
|
+
...options,
|
|
4675
|
+
defaultConfig: defaultConfig3,
|
|
4676
|
+
cache: { enabled: true },
|
|
4677
|
+
performance: { enabled: false }
|
|
4678
|
+
};
|
|
4679
|
+
const shouldCheckEnv = "checkEnv" in options ? options.checkEnv !== false : true;
|
|
4680
|
+
if (shouldCheckEnv) {
|
|
4681
|
+
const envResult = await globalConfigLoader.applyEnvironmentVariables(configOptions.name || "", defaultConfig3, true, configOptions.verbose || false);
|
|
4682
|
+
return envResult?.config ?? defaultConfig3;
|
|
1781
4683
|
}
|
|
4684
|
+
return defaultConfig3;
|
|
1782
4685
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
4686
|
+
}
|
|
4687
|
+
function applyEnvVarsToConfig2(name, config4, verbose = false) {
|
|
4688
|
+
const _envProcessor = new EnvProcessor;
|
|
4689
|
+
const envPrefix = name.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
4690
|
+
function processConfigLevel(obj, path = []) {
|
|
4691
|
+
const result = { ...obj };
|
|
4692
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
4693
|
+
const currentPath = [...path, key];
|
|
4694
|
+
const envKeys = [
|
|
4695
|
+
`${envPrefix}_${currentPath.join("_").toUpperCase()}`,
|
|
4696
|
+
`${envPrefix}_${currentPath.map((k) => k.toUpperCase()).join("")}`,
|
|
4697
|
+
`${envPrefix}_${currentPath.map((k) => k.replace(/([A-Z])/g, "_$1").toUpperCase()).join("")}`
|
|
4698
|
+
];
|
|
4699
|
+
let envValue;
|
|
4700
|
+
let usedKey;
|
|
4701
|
+
for (const envKey of envKeys) {
|
|
4702
|
+
envValue = process12.env[envKey];
|
|
4703
|
+
if (envValue !== undefined) {
|
|
4704
|
+
usedKey = envKey;
|
|
4705
|
+
break;
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
if (envValue !== undefined && usedKey) {
|
|
4709
|
+
if (verbose) {}
|
|
4710
|
+
if (typeof value === "boolean") {
|
|
4711
|
+
result[key] = ["true", "1", "yes"].includes(envValue.toLowerCase());
|
|
4712
|
+
} else if (typeof value === "number") {
|
|
4713
|
+
const parsed = Number(envValue);
|
|
4714
|
+
if (!Number.isNaN(parsed)) {
|
|
4715
|
+
result[key] = parsed;
|
|
4716
|
+
}
|
|
4717
|
+
} else if (Array.isArray(value)) {
|
|
4718
|
+
try {
|
|
4719
|
+
result[key] = JSON.parse(envValue);
|
|
4720
|
+
} catch {
|
|
4721
|
+
result[key] = envValue.split(",").map((s) => s.trim());
|
|
4722
|
+
}
|
|
4723
|
+
} else {
|
|
4724
|
+
result[key] = envValue;
|
|
4725
|
+
}
|
|
4726
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
4727
|
+
result[key] = processConfigLevel(value, currentPath);
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
return result;
|
|
1785
4731
|
}
|
|
1786
|
-
return
|
|
4732
|
+
return processConfigLevel(config4);
|
|
1787
4733
|
}
|
|
1788
|
-
var defaultConfigDir, defaultGeneratedDir, defaultLogDirectory, defaultConfig, config, terminalStyles, styles, red, green, yellow, blue, magenta, cyan, white, gray, bgRed, bgYellow, bold, dim, italic, underline, reset, defaultFingersCrossedConfig, levelIcons, logger, log, defaultConfigDir2, defaultGeneratedDir2;
|
|
4734
|
+
var globalCache, globalPerformanceMonitor, defaultConfigDir, defaultGeneratedDir, defaultLogDirectory, defaultConfig, config, terminalStyles, styles, red, green, yellow, blue, magenta, cyan, white, gray, bgRed, bgYellow, bold, dim, italic, underline, reset, defaultFingersCrossedConfig, levelIcons, logger, log, defaultConfigDir2, defaultGeneratedDir2, defaultLogDirectory2, defaultConfig2, config2, terminalStyles2, styles2, red2, green2, yellow2, blue2, magenta2, cyan2, white2, gray2, bgRed2, bgYellow2, bgGray, bold2, dim2, italic2, underline2, strikethrough, reset2, defaultFingersCrossedConfig2, levelIcons2, logger2, BunfigError, ConfigNotFoundError, ConfigLoadError, ConfigValidationError, ConfigMergeError, EnvVarError, FileSystemError, TypeGenerationError, SchemaValidationError, BrowserConfigError, PluginError, ErrorFactory, URL_PATTERN, log2, globalConfigLoader, defaultConfigDir3, defaultGeneratedDir3;
|
|
1789
4735
|
var init_dist = __esm(async () => {
|
|
4736
|
+
globalCache = new ConfigCache;
|
|
4737
|
+
globalPerformanceMonitor = new PerformanceMonitor;
|
|
1790
4738
|
defaultConfigDir = resolve(process.cwd(), "config");
|
|
1791
4739
|
defaultGeneratedDir = resolve(process.cwd(), "src/generated");
|
|
1792
4740
|
defaultLogDirectory = process2.env.CLARITY_LOG_DIR || join(getProjectRoot(), "logs");
|
|
@@ -1865,13 +4813,272 @@ var init_dist = __esm(async () => {
|
|
|
1865
4813
|
});
|
|
1866
4814
|
defaultConfigDir2 = resolve3(process6.cwd(), "config");
|
|
1867
4815
|
defaultGeneratedDir2 = resolve3(process6.cwd(), "src/generated");
|
|
4816
|
+
defaultLogDirectory2 = process7.env.CLARITY_LOG_DIR || join3(getProjectRoot2(), "logs");
|
|
4817
|
+
defaultConfig2 = {
|
|
4818
|
+
level: "info",
|
|
4819
|
+
defaultName: "clarity",
|
|
4820
|
+
timestamp: true,
|
|
4821
|
+
colors: true,
|
|
4822
|
+
format: "text",
|
|
4823
|
+
maxLogSize: 10485760,
|
|
4824
|
+
logDatePattern: "YYYY-MM-DD",
|
|
4825
|
+
logDirectory: defaultLogDirectory2,
|
|
4826
|
+
rotation: {
|
|
4827
|
+
frequency: "daily",
|
|
4828
|
+
maxSize: 10485760,
|
|
4829
|
+
maxFiles: 5,
|
|
4830
|
+
compress: false,
|
|
4831
|
+
rotateHour: 0,
|
|
4832
|
+
rotateMinute: 0,
|
|
4833
|
+
rotateDayOfWeek: 0,
|
|
4834
|
+
rotateDayOfMonth: 1,
|
|
4835
|
+
encrypt: false
|
|
4836
|
+
},
|
|
4837
|
+
verbose: false,
|
|
4838
|
+
writeToFile: false
|
|
4839
|
+
};
|
|
4840
|
+
config2 = await loadConfig4();
|
|
4841
|
+
terminalStyles2 = {
|
|
4842
|
+
red: (text) => `\x1B[31m${text}\x1B[0m`,
|
|
4843
|
+
green: (text) => `\x1B[32m${text}\x1B[0m`,
|
|
4844
|
+
yellow: (text) => `\x1B[33m${text}\x1B[0m`,
|
|
4845
|
+
blue: (text) => `\x1B[34m${text}\x1B[0m`,
|
|
4846
|
+
magenta: (text) => `\x1B[35m${text}\x1B[0m`,
|
|
4847
|
+
cyan: (text) => `\x1B[36m${text}\x1B[0m`,
|
|
4848
|
+
white: (text) => `\x1B[37m${text}\x1B[0m`,
|
|
4849
|
+
gray: (text) => `\x1B[90m${text}\x1B[0m`,
|
|
4850
|
+
bgRed: (text) => `\x1B[41m${text}\x1B[0m`,
|
|
4851
|
+
bgYellow: (text) => `\x1B[43m${text}\x1B[0m`,
|
|
4852
|
+
bgGray: (text) => `\x1B[100m${text}\x1B[0m`,
|
|
4853
|
+
bold: (text) => `\x1B[1m${text}\x1B[0m`,
|
|
4854
|
+
dim: (text) => `\x1B[2m${text}\x1B[0m`,
|
|
4855
|
+
italic: (text) => `\x1B[3m${text}\x1B[0m`,
|
|
4856
|
+
underline: (text) => `\x1B[4m${text}\x1B[0m`,
|
|
4857
|
+
strikethrough: (text) => `\x1B[9m${text}\x1B[0m`,
|
|
4858
|
+
reset: "\x1B[0m"
|
|
4859
|
+
};
|
|
4860
|
+
styles2 = terminalStyles2;
|
|
4861
|
+
red2 = terminalStyles2.red;
|
|
4862
|
+
green2 = terminalStyles2.green;
|
|
4863
|
+
yellow2 = terminalStyles2.yellow;
|
|
4864
|
+
blue2 = terminalStyles2.blue;
|
|
4865
|
+
magenta2 = terminalStyles2.magenta;
|
|
4866
|
+
cyan2 = terminalStyles2.cyan;
|
|
4867
|
+
white2 = terminalStyles2.white;
|
|
4868
|
+
gray2 = terminalStyles2.gray;
|
|
4869
|
+
bgRed2 = terminalStyles2.bgRed;
|
|
4870
|
+
bgYellow2 = terminalStyles2.bgYellow;
|
|
4871
|
+
bgGray = terminalStyles2.bgGray;
|
|
4872
|
+
bold2 = terminalStyles2.bold;
|
|
4873
|
+
dim2 = terminalStyles2.dim;
|
|
4874
|
+
italic2 = terminalStyles2.italic;
|
|
4875
|
+
underline2 = terminalStyles2.underline;
|
|
4876
|
+
strikethrough = terminalStyles2.strikethrough;
|
|
4877
|
+
reset2 = terminalStyles2.reset;
|
|
4878
|
+
defaultFingersCrossedConfig2 = {
|
|
4879
|
+
activationLevel: "error",
|
|
4880
|
+
bufferSize: 50,
|
|
4881
|
+
flushOnDeactivation: true,
|
|
4882
|
+
stopBuffering: false
|
|
4883
|
+
};
|
|
4884
|
+
levelIcons2 = {
|
|
4885
|
+
debug: "\uD83D\uDD0D",
|
|
4886
|
+
info: blue2("\u2139"),
|
|
4887
|
+
success: green2("\u2713"),
|
|
4888
|
+
warning: bgYellow2(white2(bold2(" WARN "))),
|
|
4889
|
+
error: bgRed2(white2(bold2(" ERROR ")))
|
|
4890
|
+
};
|
|
4891
|
+
logger2 = new Logger2("stacks");
|
|
4892
|
+
BunfigError = class BunfigError extends Error {
|
|
4893
|
+
timestamp;
|
|
4894
|
+
context;
|
|
4895
|
+
constructor(message, context = {}) {
|
|
4896
|
+
super(message);
|
|
4897
|
+
this.name = this.constructor.name;
|
|
4898
|
+
this.timestamp = new Date;
|
|
4899
|
+
this.context = context;
|
|
4900
|
+
if (Error.captureStackTrace) {
|
|
4901
|
+
Error.captureStackTrace(this, this.constructor);
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
toJSON() {
|
|
4905
|
+
return {
|
|
4906
|
+
name: this.name,
|
|
4907
|
+
code: this.code,
|
|
4908
|
+
message: this.message,
|
|
4909
|
+
timestamp: this.timestamp.toISOString(),
|
|
4910
|
+
context: this.context,
|
|
4911
|
+
stack: this.stack
|
|
4912
|
+
};
|
|
4913
|
+
}
|
|
4914
|
+
toString() {
|
|
4915
|
+
const contextStr = Object.keys(this.context).length > 0 ? ` (${Object.entries(this.context).map(([k, v]) => `${k}: ${v}`).join(", ")})` : "";
|
|
4916
|
+
return `${this.name} [${this.code}]: ${this.message}${contextStr}`;
|
|
4917
|
+
}
|
|
4918
|
+
};
|
|
4919
|
+
ConfigNotFoundError = class ConfigNotFoundError extends BunfigError {
|
|
4920
|
+
code = "CONFIG_NOT_FOUND";
|
|
4921
|
+
constructor(configName, searchPaths, alias) {
|
|
4922
|
+
const aliasStr = alias ? ` or alias "${alias}"` : "";
|
|
4923
|
+
super(`Configuration "${configName}"${aliasStr} not found`, {
|
|
4924
|
+
configName,
|
|
4925
|
+
alias,
|
|
4926
|
+
searchPaths,
|
|
4927
|
+
searchPathCount: searchPaths.length
|
|
4928
|
+
});
|
|
4929
|
+
}
|
|
4930
|
+
};
|
|
4931
|
+
ConfigLoadError = class ConfigLoadError extends BunfigError {
|
|
4932
|
+
code = "CONFIG_LOAD_ERROR";
|
|
4933
|
+
constructor(configPath, cause, configName) {
|
|
4934
|
+
super(`Failed to load configuration from "${configPath}": ${cause.message}`, {
|
|
4935
|
+
configPath,
|
|
4936
|
+
configName,
|
|
4937
|
+
originalError: cause.name,
|
|
4938
|
+
originalMessage: cause.message
|
|
4939
|
+
});
|
|
4940
|
+
this.cause = cause;
|
|
4941
|
+
}
|
|
4942
|
+
};
|
|
4943
|
+
ConfigValidationError = class ConfigValidationError extends BunfigError {
|
|
4944
|
+
code = "CONFIG_VALIDATION_ERROR";
|
|
4945
|
+
constructor(configPath, validationErrors, configName) {
|
|
4946
|
+
super(`Configuration validation failed for "${configPath}"`, {
|
|
4947
|
+
configPath,
|
|
4948
|
+
configName,
|
|
4949
|
+
validationErrors,
|
|
4950
|
+
errorCount: validationErrors.length
|
|
4951
|
+
});
|
|
4952
|
+
}
|
|
4953
|
+
};
|
|
4954
|
+
ConfigMergeError = class ConfigMergeError extends BunfigError {
|
|
4955
|
+
code = "CONFIG_MERGE_ERROR";
|
|
4956
|
+
constructor(sourcePath, targetPath, cause, configName) {
|
|
4957
|
+
super(`Failed to merge configuration from "${sourcePath}" with "${targetPath}": ${cause.message}`, {
|
|
4958
|
+
sourcePath,
|
|
4959
|
+
targetPath,
|
|
4960
|
+
configName,
|
|
4961
|
+
originalError: cause.name,
|
|
4962
|
+
originalMessage: cause.message
|
|
4963
|
+
});
|
|
4964
|
+
this.cause = cause;
|
|
4965
|
+
}
|
|
4966
|
+
};
|
|
4967
|
+
EnvVarError = class EnvVarError extends BunfigError {
|
|
4968
|
+
code = "ENV_VAR_ERROR";
|
|
4969
|
+
constructor(envKey, envValue, expectedType, configName) {
|
|
4970
|
+
super(`Failed to parse environment variable "${envKey}" with value "${envValue}" as ${expectedType}`, {
|
|
4971
|
+
envKey,
|
|
4972
|
+
envValue,
|
|
4973
|
+
expectedType,
|
|
4974
|
+
configName
|
|
4975
|
+
});
|
|
4976
|
+
}
|
|
4977
|
+
};
|
|
4978
|
+
FileSystemError = class FileSystemError extends BunfigError {
|
|
4979
|
+
code = "FILE_SYSTEM_ERROR";
|
|
4980
|
+
constructor(operation, path, cause) {
|
|
4981
|
+
super(`File system ${operation} failed for "${path}": ${cause.message}`, {
|
|
4982
|
+
operation,
|
|
4983
|
+
path,
|
|
4984
|
+
originalError: cause.name,
|
|
4985
|
+
originalMessage: cause.message
|
|
4986
|
+
});
|
|
4987
|
+
this.cause = cause;
|
|
4988
|
+
}
|
|
4989
|
+
};
|
|
4990
|
+
TypeGenerationError = class TypeGenerationError extends BunfigError {
|
|
4991
|
+
code = "TYPE_GENERATION_ERROR";
|
|
4992
|
+
constructor(configDir, outputPath, cause) {
|
|
4993
|
+
super(`Failed to generate types from "${configDir}" to "${outputPath}": ${cause.message}`, {
|
|
4994
|
+
configDir,
|
|
4995
|
+
outputPath,
|
|
4996
|
+
originalError: cause.name,
|
|
4997
|
+
originalMessage: cause.message
|
|
4998
|
+
});
|
|
4999
|
+
this.cause = cause;
|
|
5000
|
+
}
|
|
5001
|
+
};
|
|
5002
|
+
SchemaValidationError = class SchemaValidationError extends BunfigError {
|
|
5003
|
+
code = "SCHEMA_VALIDATION_ERROR";
|
|
5004
|
+
constructor(schemaPath, validationErrors, configName) {
|
|
5005
|
+
super(`Schema validation failed${configName ? ` for config "${configName}"` : ""}`, {
|
|
5006
|
+
schemaPath,
|
|
5007
|
+
configName,
|
|
5008
|
+
validationErrors,
|
|
5009
|
+
errorCount: validationErrors.length
|
|
5010
|
+
});
|
|
5011
|
+
}
|
|
5012
|
+
};
|
|
5013
|
+
BrowserConfigError = class BrowserConfigError extends BunfigError {
|
|
5014
|
+
code = "BROWSER_CONFIG_ERROR";
|
|
5015
|
+
constructor(endpoint, status, statusText, configName) {
|
|
5016
|
+
super(`Failed to fetch configuration from "${endpoint}": ${status} ${statusText}`, {
|
|
5017
|
+
endpoint,
|
|
5018
|
+
status,
|
|
5019
|
+
statusText,
|
|
5020
|
+
configName
|
|
5021
|
+
});
|
|
5022
|
+
}
|
|
5023
|
+
};
|
|
5024
|
+
PluginError = class PluginError extends BunfigError {
|
|
5025
|
+
code = "PLUGIN_ERROR";
|
|
5026
|
+
constructor(pluginName, operation, cause) {
|
|
5027
|
+
super(`Plugin "${pluginName}" failed during ${operation}: ${cause.message}`, {
|
|
5028
|
+
pluginName,
|
|
5029
|
+
operation,
|
|
5030
|
+
originalError: cause.name,
|
|
5031
|
+
originalMessage: cause.message
|
|
5032
|
+
});
|
|
5033
|
+
this.cause = cause;
|
|
5034
|
+
}
|
|
5035
|
+
};
|
|
5036
|
+
ErrorFactory = {
|
|
5037
|
+
configNotFound(configName, searchPaths, alias) {
|
|
5038
|
+
return new ConfigNotFoundError(configName, searchPaths, alias);
|
|
5039
|
+
},
|
|
5040
|
+
configLoad(configPath, cause, configName) {
|
|
5041
|
+
return new ConfigLoadError(configPath, cause, configName);
|
|
5042
|
+
},
|
|
5043
|
+
configValidation(configPath, errors, configName) {
|
|
5044
|
+
return new ConfigValidationError(configPath, errors, configName);
|
|
5045
|
+
},
|
|
5046
|
+
configMerge(sourcePath, targetPath, cause, configName) {
|
|
5047
|
+
return new ConfigMergeError(sourcePath, targetPath, cause, configName);
|
|
5048
|
+
},
|
|
5049
|
+
envVar(envKey, envValue, expectedType, configName) {
|
|
5050
|
+
return new EnvVarError(envKey, envValue, expectedType, configName);
|
|
5051
|
+
},
|
|
5052
|
+
fileSystem(operation, path, cause) {
|
|
5053
|
+
return new FileSystemError(operation, path, cause);
|
|
5054
|
+
},
|
|
5055
|
+
typeGeneration(configDir, outputPath, cause) {
|
|
5056
|
+
return new TypeGenerationError(configDir, outputPath, cause);
|
|
5057
|
+
},
|
|
5058
|
+
schemaValidation(schemaPath, errors, configName) {
|
|
5059
|
+
return new SchemaValidationError(schemaPath, errors, configName);
|
|
5060
|
+
},
|
|
5061
|
+
browserConfig(endpoint, status, statusText, configName) {
|
|
5062
|
+
return new BrowserConfigError(endpoint, status, statusText, configName);
|
|
5063
|
+
},
|
|
5064
|
+
plugin(pluginName, operation, cause) {
|
|
5065
|
+
return new PluginError(pluginName, operation, cause);
|
|
5066
|
+
}
|
|
5067
|
+
};
|
|
5068
|
+
URL_PATTERN = /^https?:\/\//;
|
|
5069
|
+
log2 = new Logger2("bunfig", {
|
|
5070
|
+
showTags: true
|
|
5071
|
+
});
|
|
5072
|
+
globalConfigLoader = new ConfigLoader;
|
|
5073
|
+
defaultConfigDir3 = resolve7(process12.cwd(), "config");
|
|
5074
|
+
defaultGeneratedDir3 = resolve7(process12.cwd(), "src/generated");
|
|
1868
5075
|
});
|
|
1869
5076
|
|
|
1870
5077
|
// src/config.ts
|
|
1871
|
-
var
|
|
5078
|
+
var defaultConfig3, config3;
|
|
1872
5079
|
var init_config = __esm(async () => {
|
|
1873
5080
|
await init_dist();
|
|
1874
|
-
|
|
5081
|
+
defaultConfig3 = {
|
|
1875
5082
|
verbose: true,
|
|
1876
5083
|
dialect: "postgres",
|
|
1877
5084
|
database: {
|
|
@@ -1928,16 +5135,16 @@ var init_config = __esm(async () => {
|
|
|
1928
5135
|
defaultFilter: true
|
|
1929
5136
|
}
|
|
1930
5137
|
};
|
|
1931
|
-
|
|
5138
|
+
config3 = await loadConfig5({
|
|
1932
5139
|
name: "query-builder",
|
|
1933
5140
|
alias: "qb",
|
|
1934
|
-
defaultConfig:
|
|
5141
|
+
defaultConfig: defaultConfig3
|
|
1935
5142
|
});
|
|
1936
5143
|
});
|
|
1937
5144
|
|
|
1938
5145
|
// src/db.ts
|
|
1939
5146
|
var {SQL } = globalThis.Bun;
|
|
1940
|
-
import
|
|
5147
|
+
import process13 from "process";
|
|
1941
5148
|
function createConnectionString(dialect, dbConfig) {
|
|
1942
5149
|
if (dbConfig.url) {
|
|
1943
5150
|
return dbConfig.url;
|
|
@@ -1958,12 +5165,12 @@ function createConnectionString(dialect, dbConfig) {
|
|
|
1958
5165
|
}
|
|
1959
5166
|
}
|
|
1960
5167
|
function getBunSql() {
|
|
1961
|
-
const connectionString = createConnectionString(
|
|
5168
|
+
const connectionString = createConnectionString(config3.dialect, config3.database);
|
|
1962
5169
|
try {
|
|
1963
5170
|
const sql = new SQL(connectionString);
|
|
1964
5171
|
if (sql && typeof sql.catch === "function") {
|
|
1965
5172
|
sql.catch((error) => {
|
|
1966
|
-
if (
|
|
5173
|
+
if (config3.verbose && !error.message.includes("database") && !error.message.includes("does not exist")) {
|
|
1967
5174
|
console.warn(`[query-builder] Database connection error: ${error.message}`);
|
|
1968
5175
|
}
|
|
1969
5176
|
});
|
|
@@ -2007,14 +5214,14 @@ var _bunSqlInstance = null, bunSql;
|
|
|
2007
5214
|
var init_db = __esm(async () => {
|
|
2008
5215
|
await init_config();
|
|
2009
5216
|
bunSql = getOrCreateBunSql();
|
|
2010
|
-
if (typeof
|
|
2011
|
-
const existingHandler =
|
|
5217
|
+
if (typeof process13 !== "undefined" && process13.on) {
|
|
5218
|
+
const existingHandler = process13.listeners("unhandledRejection").find((h) => h.name === "sqlConnectionErrorHandler");
|
|
2012
5219
|
if (!existingHandler) {
|
|
2013
5220
|
let sqlConnectionErrorHandler = function(reason) {
|
|
2014
5221
|
if (reason && (reason.message?.includes("database") || reason.message?.includes("does not exist") || reason.code === "ERR_POSTGRES_SERVER_ERROR" || reason.code === "3D000")) {}
|
|
2015
5222
|
};
|
|
2016
5223
|
Object.defineProperty(sqlConnectionErrorHandler, "name", { value: "sqlConnectionErrorHandler" });
|
|
2017
|
-
|
|
5224
|
+
process13.on("unhandledRejection", sqlConnectionErrorHandler);
|
|
2018
5225
|
}
|
|
2019
5226
|
}
|
|
2020
5227
|
});
|
|
@@ -2233,7 +5440,7 @@ function matchesSqlState(err, states) {
|
|
|
2233
5440
|
return states.includes(code);
|
|
2234
5441
|
}
|
|
2235
5442
|
function sleep(ms) {
|
|
2236
|
-
return new Promise((
|
|
5443
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
2237
5444
|
}
|
|
2238
5445
|
function computeBackoffMs(attempt, cfg) {
|
|
2239
5446
|
const base = Math.max(1, cfg?.baseMs ?? 50);
|
|
@@ -2313,16 +5520,16 @@ function createQueryBuilder(state) {
|
|
|
2313
5520
|
return _sql`${q} WHERE ${condition}`;
|
|
2314
5521
|
}
|
|
2315
5522
|
function computeSqlText(q) {
|
|
2316
|
-
const prev =
|
|
2317
|
-
if (
|
|
2318
|
-
|
|
5523
|
+
const prev = config3.debug?.captureText;
|
|
5524
|
+
if (config3.debug)
|
|
5525
|
+
config3.debug.captureText = true;
|
|
2319
5526
|
const s = String(q);
|
|
2320
|
-
if (
|
|
2321
|
-
|
|
5527
|
+
if (config3.debug)
|
|
5528
|
+
config3.debug.captureText = prev;
|
|
2322
5529
|
return s;
|
|
2323
5530
|
}
|
|
2324
5531
|
function runWithHooks(q, kind, opts) {
|
|
2325
|
-
const hooks =
|
|
5532
|
+
const hooks = config3.hooks;
|
|
2326
5533
|
const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan);
|
|
2327
5534
|
const hasTimeoutOrSignal = opts?.timeoutMs && opts.timeoutMs > 0 || opts?.signal;
|
|
2328
5535
|
if (!hasHooks && !hasTimeoutOrSignal) {
|
|
@@ -2671,7 +5878,7 @@ function createQueryBuilder(state) {
|
|
|
2671
5878
|
}
|
|
2672
5879
|
if (normalizedRelations.length === 0)
|
|
2673
5880
|
return this;
|
|
2674
|
-
const maxEagerLoad =
|
|
5881
|
+
const maxEagerLoad = config3.relations.maxEagerLoad ?? 50;
|
|
2675
5882
|
if (normalizedRelations.length > maxEagerLoad) {
|
|
2676
5883
|
throw new Error(`[query-builder] Too many relationships to eager load (${normalizedRelations.length}). Maximum allowed: ${maxEagerLoad}`);
|
|
2677
5884
|
}
|
|
@@ -2680,7 +5887,7 @@ function createQueryBuilder(state) {
|
|
|
2680
5887
|
const loadedRelationships = new Set;
|
|
2681
5888
|
const relationConditions = new Map;
|
|
2682
5889
|
const singularize = (name) => {
|
|
2683
|
-
if (
|
|
5890
|
+
if (config3.relations.singularizeStrategy === "none")
|
|
2684
5891
|
return name;
|
|
2685
5892
|
return name.endsWith("s") ? name.slice(0, -1) : name;
|
|
2686
5893
|
};
|
|
@@ -2702,7 +5909,7 @@ function createQueryBuilder(state) {
|
|
|
2702
5909
|
];
|
|
2703
5910
|
};
|
|
2704
5911
|
const addJoin = (fromTable, relationKey, depth = 0, condition) => {
|
|
2705
|
-
const maxDepth =
|
|
5912
|
+
const maxDepth = config3.relations.maxDepth ?? 10;
|
|
2706
5913
|
if (depth >= maxDepth) {
|
|
2707
5914
|
throw new Error(`[query-builder] Maximum relationship depth (${maxDepth}) exceeded at '${relationKey}'. Consider using separate queries or increasing maxDepth.`);
|
|
2708
5915
|
}
|
|
@@ -2712,8 +5919,8 @@ function createQueryBuilder(state) {
|
|
|
2712
5919
|
}
|
|
2713
5920
|
const _buildConditionalJoin = (baseJoinCondition, targetTable2) => {
|
|
2714
5921
|
let joinCondition = baseJoinCondition;
|
|
2715
|
-
if (
|
|
2716
|
-
const softDeleteColumn =
|
|
5922
|
+
if (config3.softDeletes?.enabled && config3.softDeletes?.defaultFilter) {
|
|
5923
|
+
const softDeleteColumn = config3.softDeletes.column || "deleted_at";
|
|
2717
5924
|
joinCondition = `${joinCondition} AND ${targetTable2}.${softDeleteColumn} IS NULL`;
|
|
2718
5925
|
}
|
|
2719
5926
|
if (!condition)
|
|
@@ -2733,8 +5940,8 @@ function createQueryBuilder(state) {
|
|
|
2733
5940
|
return joinCondition;
|
|
2734
5941
|
};
|
|
2735
5942
|
const addSoftDeleteCheck = (table2) => {
|
|
2736
|
-
if (
|
|
2737
|
-
const softDeleteColumn =
|
|
5943
|
+
if (config3.softDeletes?.enabled && config3.softDeletes?.defaultFilter) {
|
|
5944
|
+
const softDeleteColumn = config3.softDeletes.column || "deleted_at";
|
|
2738
5945
|
return ` AND ${table2}.${softDeleteColumn} IS NULL`;
|
|
2739
5946
|
}
|
|
2740
5947
|
return "";
|
|
@@ -2759,7 +5966,7 @@ function createQueryBuilder(state) {
|
|
|
2759
5966
|
throw new Error(`[query-builder] Relationship '${relationKey}' not found on table '${fromTable}'.${suggestion}`);
|
|
2760
5967
|
}
|
|
2761
5968
|
}
|
|
2762
|
-
if (
|
|
5969
|
+
if (config3.relations.detectCycles !== false) {
|
|
2763
5970
|
const cycleKey = `${fromTable}->${childTable}`;
|
|
2764
5971
|
if (visitedTables.has(cycleKey)) {
|
|
2765
5972
|
throw new Error(`[query-builder] Circular relationship detected: ${cycleKey}. This would cause an infinite loop.`);
|
|
@@ -3168,7 +6375,7 @@ function createQueryBuilder(state) {
|
|
|
3168
6375
|
return this;
|
|
3169
6376
|
},
|
|
3170
6377
|
whereJsonPath(path, op, value) {
|
|
3171
|
-
const dialect =
|
|
6378
|
+
const dialect = config3.dialect;
|
|
3172
6379
|
if (dialect === "postgres") {
|
|
3173
6380
|
built = sql`${built} WHERE ${sql(path)} ${op} ${value}`;
|
|
3174
6381
|
} else if (dialect === "mysql") {
|
|
@@ -3185,7 +6392,7 @@ function createQueryBuilder(state) {
|
|
|
3185
6392
|
return this;
|
|
3186
6393
|
},
|
|
3187
6394
|
whereILike(column, pattern) {
|
|
3188
|
-
if (
|
|
6395
|
+
if (config3.dialect === "postgres") {
|
|
3189
6396
|
built = sql`${built} WHERE ${sql(String(column))} ILIKE ${pattern}`;
|
|
3190
6397
|
addWhereText("WHERE", `${String(column)} ILIKE ?`);
|
|
3191
6398
|
} else {
|
|
@@ -3202,7 +6409,7 @@ function createQueryBuilder(state) {
|
|
|
3202
6409
|
return this;
|
|
3203
6410
|
},
|
|
3204
6411
|
orWhereILike(column, pattern) {
|
|
3205
|
-
if (
|
|
6412
|
+
if (config3.dialect === "postgres") {
|
|
3206
6413
|
built = sql`${built} OR ${sql(String(column))} ILIKE ${pattern}`;
|
|
3207
6414
|
addWhereText("OR", `${String(column)} ILIKE ?`);
|
|
3208
6415
|
} else {
|
|
@@ -3219,7 +6426,7 @@ function createQueryBuilder(state) {
|
|
|
3219
6426
|
return this;
|
|
3220
6427
|
},
|
|
3221
6428
|
whereNotILike(column, pattern) {
|
|
3222
|
-
if (
|
|
6429
|
+
if (config3.dialect === "postgres") {
|
|
3223
6430
|
built = sql`${built} WHERE ${sql(String(column))} NOT ILIKE ${pattern}`;
|
|
3224
6431
|
addWhereText("WHERE", `${String(column)} NOT ILIKE ?`);
|
|
3225
6432
|
} else {
|
|
@@ -3236,7 +6443,7 @@ function createQueryBuilder(state) {
|
|
|
3236
6443
|
return this;
|
|
3237
6444
|
},
|
|
3238
6445
|
orWhereNotILike(column, pattern) {
|
|
3239
|
-
if (
|
|
6446
|
+
if (config3.dialect === "postgres") {
|
|
3240
6447
|
built = sql`${built} OR ${sql(String(column))} NOT ILIKE ${pattern}`;
|
|
3241
6448
|
addWhereText("OR", `${String(column)} NOT ILIKE ?`);
|
|
3242
6449
|
} else {
|
|
@@ -3448,7 +6655,7 @@ function createQueryBuilder(state) {
|
|
|
3448
6655
|
return this;
|
|
3449
6656
|
},
|
|
3450
6657
|
inRandomOrder() {
|
|
3451
|
-
const rnd =
|
|
6658
|
+
const rnd = config3.sql.randomFunction === "RAND()" ? sql`RAND()` : sql`RANDOM()`;
|
|
3452
6659
|
built = sql`${built} ORDER BY ${rnd}`;
|
|
3453
6660
|
return this;
|
|
3454
6661
|
},
|
|
@@ -3457,12 +6664,12 @@ function createQueryBuilder(state) {
|
|
|
3457
6664
|
return this;
|
|
3458
6665
|
},
|
|
3459
6666
|
latest(column) {
|
|
3460
|
-
const col = column ??
|
|
6667
|
+
const col = column ?? config3.timestamps.defaultOrderColumn;
|
|
3461
6668
|
built = sql`${built} ORDER BY ${sql(String(col))} DESC`;
|
|
3462
6669
|
return this;
|
|
3463
6670
|
},
|
|
3464
6671
|
oldest(column) {
|
|
3465
|
-
const col = column ??
|
|
6672
|
+
const col = column ?? config3.timestamps.defaultOrderColumn;
|
|
3466
6673
|
built = sql`${built} ORDER BY ${sql(String(col))} ASC`;
|
|
3467
6674
|
return this;
|
|
3468
6675
|
},
|
|
@@ -3529,7 +6736,7 @@ function createQueryBuilder(state) {
|
|
|
3529
6736
|
for (const jt of joinedTables) {
|
|
3530
6737
|
const cols = Object.keys(schema[jt]?.columns ?? {});
|
|
3531
6738
|
for (const c of cols) {
|
|
3532
|
-
const alias =
|
|
6739
|
+
const alias = config3.aliasing.relationColumnAliasFormat === "camelCase" ? `${jt}_${c}`.replace(/_([a-z])/g, (_, ch) => ch.toUpperCase()) : config3.aliasing.relationColumnAliasFormat === "table.dot.column" ? `${jt}.${c}` : `${jt}_${c}`;
|
|
3533
6740
|
parts.push(sql`${sql(`${jt}.${c}`)} AS ${sql(alias)}`);
|
|
3534
6741
|
}
|
|
3535
6742
|
}
|
|
@@ -3717,7 +6924,7 @@ function createQueryBuilder(state) {
|
|
|
3717
6924
|
onlyTrashed() {
|
|
3718
6925
|
includeTrashed = true;
|
|
3719
6926
|
onlyTrashed = true;
|
|
3720
|
-
const softDeleteColumn =
|
|
6927
|
+
const softDeleteColumn = config3.softDeletes?.column || "deleted_at";
|
|
3721
6928
|
if (text.includes("WHERE")) {
|
|
3722
6929
|
text = text.replace(/WHERE/, `WHERE ${table}.${softDeleteColumn} IS NOT NULL AND`);
|
|
3723
6930
|
} else {
|
|
@@ -3777,7 +6984,7 @@ function createQueryBuilder(state) {
|
|
|
3777
6984
|
},
|
|
3778
6985
|
async get() {
|
|
3779
6986
|
built = whereParams.length > 0 ? _sql.unsafe(text, whereParams) : _sql.unsafe(text);
|
|
3780
|
-
if (!
|
|
6987
|
+
if (!config3.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !config3.hooks) {
|
|
3781
6988
|
const stmt = built._stmt;
|
|
3782
6989
|
const params = built._params;
|
|
3783
6990
|
if (stmt) {
|
|
@@ -3785,12 +6992,12 @@ function createQueryBuilder(state) {
|
|
|
3785
6992
|
}
|
|
3786
6993
|
return built.execute();
|
|
3787
6994
|
}
|
|
3788
|
-
if (!
|
|
6995
|
+
if (!config3.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal) {
|
|
3789
6996
|
return runWithHooks(built, "select");
|
|
3790
6997
|
}
|
|
3791
6998
|
let finalQuery = built;
|
|
3792
|
-
if (
|
|
3793
|
-
const col =
|
|
6999
|
+
if (config3.softDeletes?.enabled && config3.softDeletes.defaultFilter && !includeTrashed) {
|
|
7000
|
+
const col = config3.softDeletes.column;
|
|
3794
7001
|
const tbl = String(table);
|
|
3795
7002
|
const hasCol = schema ? Boolean(schema[tbl]?.columns?.[col]) : true;
|
|
3796
7003
|
if (hasCol && !SQL_PATTERNS.DELETED_AT.test(text)) {
|
|
@@ -3921,7 +7128,7 @@ function createQueryBuilder(state) {
|
|
|
3921
7128
|
return this;
|
|
3922
7129
|
},
|
|
3923
7130
|
sharedLock() {
|
|
3924
|
-
const syntax =
|
|
7131
|
+
const syntax = config3.sql.sharedLockSyntax === "LOCK IN SHARE MODE" ? sql`LOCK IN SHARE MODE` : sql`FOR SHARE`;
|
|
3925
7132
|
built = sql`${built} ${syntax}`;
|
|
3926
7133
|
return this;
|
|
3927
7134
|
},
|
|
@@ -3989,7 +7196,7 @@ function createQueryBuilder(state) {
|
|
|
3989
7196
|
}
|
|
3990
7197
|
return {
|
|
3991
7198
|
configure(opts) {
|
|
3992
|
-
Object.assign(
|
|
7199
|
+
Object.assign(config3, opts);
|
|
3993
7200
|
return this;
|
|
3994
7201
|
},
|
|
3995
7202
|
id(name) {
|
|
@@ -4417,13 +7624,13 @@ function createQueryBuilder(state) {
|
|
|
4417
7624
|
},
|
|
4418
7625
|
async execute() {
|
|
4419
7626
|
try {
|
|
4420
|
-
await
|
|
7627
|
+
await config3.hooks?.beforeDelete?.({ table: String(table), where: whereCondition });
|
|
4421
7628
|
} catch (err) {
|
|
4422
7629
|
throw err;
|
|
4423
7630
|
}
|
|
4424
7631
|
const result = await runWithHooks(built, "delete");
|
|
4425
7632
|
try {
|
|
4426
|
-
await
|
|
7633
|
+
await config3.hooks?.afterDelete?.({ table: String(table), where: whereCondition, result });
|
|
4427
7634
|
} catch {}
|
|
4428
7635
|
return result;
|
|
4429
7636
|
}
|
|
@@ -4457,7 +7664,7 @@ function createQueryBuilder(state) {
|
|
|
4457
7664
|
return _sql(strings, ...values).simple();
|
|
4458
7665
|
},
|
|
4459
7666
|
async advisoryLock(key) {
|
|
4460
|
-
if (
|
|
7667
|
+
if (config3.dialect !== "postgres")
|
|
4461
7668
|
return;
|
|
4462
7669
|
const s = String(key);
|
|
4463
7670
|
let hash = 7;
|
|
@@ -4468,7 +7675,7 @@ function createQueryBuilder(state) {
|
|
|
4468
7675
|
await runWithHooks(q, "raw");
|
|
4469
7676
|
},
|
|
4470
7677
|
async tryAdvisoryLock(key) {
|
|
4471
|
-
if (
|
|
7678
|
+
if (config3.dialect !== "postgres")
|
|
4472
7679
|
return false;
|
|
4473
7680
|
const s = String(key);
|
|
4474
7681
|
let hash = 7;
|
|
@@ -4520,7 +7727,7 @@ function createQueryBuilder(state) {
|
|
|
4520
7727
|
for (let i = 0;i < attempts; i++) {
|
|
4521
7728
|
if (await this.ping())
|
|
4522
7729
|
return;
|
|
4523
|
-
await new Promise((
|
|
7730
|
+
await new Promise((resolve8) => setTimeout(resolve8, delay));
|
|
4524
7731
|
}
|
|
4525
7732
|
throw new Error("Database not ready after waiting");
|
|
4526
7733
|
},
|
|
@@ -4610,7 +7817,7 @@ function createQueryBuilder(state) {
|
|
|
4610
7817
|
return built.execute();
|
|
4611
7818
|
},
|
|
4612
7819
|
async insertGetId(table, values, idColumn = "id") {
|
|
4613
|
-
if (
|
|
7820
|
+
if (config3.dialect === "mysql") {
|
|
4614
7821
|
const insertQuery = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)}`;
|
|
4615
7822
|
const result = await insertQuery.execute();
|
|
4616
7823
|
if (result && typeof result === "object" && "insertId" in result) {
|
|
@@ -4685,23 +7892,23 @@ function createQueryBuilder(state) {
|
|
|
4685
7892
|
async rawQuery(query) {
|
|
4686
7893
|
const start = Date.now();
|
|
4687
7894
|
try {
|
|
4688
|
-
|
|
7895
|
+
config3.hooks?.onQueryStart?.({ sql: query, kind: "raw" });
|
|
4689
7896
|
const res = await bunSql.unsafe(query);
|
|
4690
|
-
|
|
7897
|
+
config3.hooks?.onQueryEnd?.({ sql: query, durationMs: Date.now() - start, kind: "raw" });
|
|
4691
7898
|
return res;
|
|
4692
7899
|
} catch (err) {
|
|
4693
|
-
|
|
7900
|
+
config3.hooks?.onQueryError?.({ sql: query, error: err, durationMs: Date.now() - start, kind: "raw" });
|
|
4694
7901
|
throw err;
|
|
4695
7902
|
}
|
|
4696
7903
|
},
|
|
4697
7904
|
async create(table, values) {
|
|
4698
7905
|
const pk = meta?.primaryKeys[String(table)] ?? "id";
|
|
4699
7906
|
try {
|
|
4700
|
-
await
|
|
7907
|
+
await config3.hooks?.beforeCreate?.({ table: String(table), data: values });
|
|
4701
7908
|
} catch (err) {
|
|
4702
7909
|
throw err;
|
|
4703
7910
|
}
|
|
4704
|
-
if (
|
|
7911
|
+
if (config3.dialect === "postgres") {
|
|
4705
7912
|
const q = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)} RETURNING ${bunSql(String(pk))} as id`;
|
|
4706
7913
|
const [result] = await q.execute();
|
|
4707
7914
|
console.log("resultId", result);
|
|
@@ -4717,7 +7924,7 @@ function createQueryBuilder(state) {
|
|
|
4717
7924
|
throw new Error(`create() failed to retrieve inserted row for table ${String(table)} with id ${result.id}`);
|
|
4718
7925
|
}
|
|
4719
7926
|
try {
|
|
4720
|
-
await
|
|
7927
|
+
await config3.hooks?.afterCreate?.({ table: String(table), data: values, result: row });
|
|
4721
7928
|
} catch {}
|
|
4722
7929
|
return row;
|
|
4723
7930
|
} else {
|
|
@@ -4732,7 +7939,7 @@ function createQueryBuilder(state) {
|
|
|
4732
7939
|
throw new Error(`create() failed to retrieve inserted row for table ${String(table)} with id ${id}`);
|
|
4733
7940
|
}
|
|
4734
7941
|
try {
|
|
4735
|
-
await
|
|
7942
|
+
await config3.hooks?.afterCreate?.({ table: String(table), data: values, result: row });
|
|
4736
7943
|
} catch {}
|
|
4737
7944
|
return row;
|
|
4738
7945
|
}
|
|
@@ -4995,7 +8202,7 @@ var init_cache = __esm(async () => {
|
|
|
4995
8202
|
});
|
|
4996
8203
|
|
|
4997
8204
|
// src/actions/console.ts
|
|
4998
|
-
import
|
|
8205
|
+
import process15 from "process";
|
|
4999
8206
|
import { createInterface } from "readline";
|
|
5000
8207
|
async function startConsole() {
|
|
5001
8208
|
console.log("-- Query Builder Interactive Console");
|
|
@@ -5004,8 +8211,8 @@ async function startConsole() {
|
|
|
5004
8211
|
console.log();
|
|
5005
8212
|
const qb = createQueryBuilder();
|
|
5006
8213
|
const rl = createInterface({
|
|
5007
|
-
input:
|
|
5008
|
-
output:
|
|
8214
|
+
input: process15.stdin,
|
|
8215
|
+
output: process15.stdout,
|
|
5009
8216
|
prompt: "qb> "
|
|
5010
8217
|
});
|
|
5011
8218
|
let multilineBuffer = "";
|
|
@@ -5056,7 +8263,7 @@ Tips:
|
|
|
5056
8263
|
if (cmd === ".exit" || cmd === ".quit") {
|
|
5057
8264
|
console.log("Goodbye!");
|
|
5058
8265
|
rl.close();
|
|
5059
|
-
|
|
8266
|
+
process15.exit(0);
|
|
5060
8267
|
} else if (cmd === ".help") {
|
|
5061
8268
|
console.log(helpText);
|
|
5062
8269
|
} else if (cmd === ".clear") {
|
|
@@ -5127,7 +8334,7 @@ Tips:
|
|
|
5127
8334
|
rl.on("close", () => {
|
|
5128
8335
|
console.log(`
|
|
5129
8336
|
Goodbye!`);
|
|
5130
|
-
|
|
8337
|
+
process15.exit(0);
|
|
5131
8338
|
});
|
|
5132
8339
|
rl.prompt();
|
|
5133
8340
|
}
|
|
@@ -5139,7 +8346,7 @@ var init_console = __esm(async () => {
|
|
|
5139
8346
|
});
|
|
5140
8347
|
|
|
5141
8348
|
// src/actions/data.ts
|
|
5142
|
-
import { existsSync as
|
|
8349
|
+
import { existsSync as existsSync6, readFileSync, writeFileSync as writeFileSync6 } from "fs";
|
|
5143
8350
|
async function exportData(tableName, options = {}) {
|
|
5144
8351
|
const format = options.format || "json";
|
|
5145
8352
|
const output = options.output || `${tableName}.${format}`;
|
|
@@ -5160,11 +8367,11 @@ async function exportData(tableName, options = {}) {
|
|
|
5160
8367
|
const data = await query.execute();
|
|
5161
8368
|
console.log(`-- Retrieved ${data.length} rows`);
|
|
5162
8369
|
if (format === "json") {
|
|
5163
|
-
|
|
8370
|
+
writeFileSync6(output, JSON.stringify(data, null, 2));
|
|
5164
8371
|
console.log(`-- \u2713 Exported to JSON: ${output}`);
|
|
5165
8372
|
} else if (format === "csv") {
|
|
5166
8373
|
if (data.length === 0) {
|
|
5167
|
-
|
|
8374
|
+
writeFileSync6(output, "");
|
|
5168
8375
|
console.log(`-- \u2713 Exported to CSV: ${output} (empty)`);
|
|
5169
8376
|
return;
|
|
5170
8377
|
}
|
|
@@ -5185,7 +8392,7 @@ async function exportData(tableName, options = {}) {
|
|
|
5185
8392
|
});
|
|
5186
8393
|
csvLines.push(values.join(","));
|
|
5187
8394
|
}
|
|
5188
|
-
|
|
8395
|
+
writeFileSync6(output, csvLines.join(`
|
|
5189
8396
|
`));
|
|
5190
8397
|
console.log(`-- \u2713 Exported to CSV: ${output}`);
|
|
5191
8398
|
} else if (format === "sql") {
|
|
@@ -5204,7 +8411,7 @@ async function exportData(tableName, options = {}) {
|
|
|
5204
8411
|
});
|
|
5205
8412
|
sqlLines.push(`INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${values.join(", ")});`);
|
|
5206
8413
|
}
|
|
5207
|
-
|
|
8414
|
+
writeFileSync6(output, sqlLines.join(`
|
|
5208
8415
|
`));
|
|
5209
8416
|
console.log(`-- \u2713 Exported to SQL: ${output}`);
|
|
5210
8417
|
}
|
|
@@ -5214,7 +8421,7 @@ async function exportData(tableName, options = {}) {
|
|
|
5214
8421
|
}
|
|
5215
8422
|
}
|
|
5216
8423
|
async function importData(tableName, filePath, options = {}) {
|
|
5217
|
-
if (!
|
|
8424
|
+
if (!existsSync6(filePath)) {
|
|
5218
8425
|
console.error(`-- File not found: ${filePath}`);
|
|
5219
8426
|
throw new Error(`File not found: ${filePath}`);
|
|
5220
8427
|
}
|
|
@@ -5283,7 +8490,7 @@ async function importData(tableName, filePath, options = {}) {
|
|
|
5283
8490
|
}
|
|
5284
8491
|
}
|
|
5285
8492
|
async function dumpDatabase(options = {}) {
|
|
5286
|
-
const dialect =
|
|
8493
|
+
const dialect = config3.dialect || "postgres";
|
|
5287
8494
|
const output = options.output || `dump-${Date.now()}.sql`;
|
|
5288
8495
|
const tablesToDump = options.tables?.split(",").map((t) => t.trim());
|
|
5289
8496
|
console.log("-- Dumping database");
|
|
@@ -5358,7 +8565,7 @@ async function dumpDatabase(options = {}) {
|
|
|
5358
8565
|
}
|
|
5359
8566
|
sqlLines.push("");
|
|
5360
8567
|
}
|
|
5361
|
-
|
|
8568
|
+
writeFileSync6(output, sqlLines.join(`
|
|
5362
8569
|
`));
|
|
5363
8570
|
console.log();
|
|
5364
8571
|
console.log(`-- \u2713 Database dump saved to: ${output}`);
|
|
@@ -5376,8 +8583,8 @@ var init_data = __esm(async () => {
|
|
|
5376
8583
|
|
|
5377
8584
|
// src/actions/db-info.ts
|
|
5378
8585
|
async function dbInfo() {
|
|
5379
|
-
const dialect =
|
|
5380
|
-
const database =
|
|
8586
|
+
const dialect = config3.dialect || "postgres";
|
|
8587
|
+
const database = config3.database?.database || "unknown";
|
|
5381
8588
|
console.log("-- Database Information");
|
|
5382
8589
|
console.log(`-- Dialect: ${dialect}`);
|
|
5383
8590
|
console.log(`-- Database: ${database}`);
|
|
@@ -5508,9 +8715,9 @@ var init_db_info = __esm(async () => {
|
|
|
5508
8715
|
});
|
|
5509
8716
|
|
|
5510
8717
|
// src/actions/db-optimize.ts
|
|
5511
|
-
import
|
|
8718
|
+
import process16 from "process";
|
|
5512
8719
|
async function dbOptimize(options = {}) {
|
|
5513
|
-
const dialect = options.dialect ||
|
|
8720
|
+
const dialect = options.dialect || process16.env.DB_DIALECT || "postgres";
|
|
5514
8721
|
const aggressive = options.aggressive || false;
|
|
5515
8722
|
if (options.verbose) {
|
|
5516
8723
|
console.log(`Optimizing ${dialect} database${aggressive ? " (aggressive mode)" : ""}...`);
|
|
@@ -5550,7 +8757,7 @@ async function dbOptimize(options = {}) {
|
|
|
5550
8757
|
await bunSql`ANALYZE TABLE ${bunSql(table)}`;
|
|
5551
8758
|
}
|
|
5552
8759
|
} else {
|
|
5553
|
-
const dbName =
|
|
8760
|
+
const dbName = process16.env.DB_NAME || "test";
|
|
5554
8761
|
const tables = await bunSql`
|
|
5555
8762
|
SELECT table_name
|
|
5556
8763
|
FROM information_schema.tables
|
|
@@ -5602,9 +8809,9 @@ var init_db_optimize = __esm(async () => {
|
|
|
5602
8809
|
});
|
|
5603
8810
|
|
|
5604
8811
|
// src/actions/db-wipe.ts
|
|
5605
|
-
import
|
|
8812
|
+
import process17 from "process";
|
|
5606
8813
|
async function dbWipe(options = {}) {
|
|
5607
|
-
const dialect = options.dialect ||
|
|
8814
|
+
const dialect = options.dialect || process17.env.DB_DIALECT || "postgres";
|
|
5608
8815
|
if (options.verbose) {
|
|
5609
8816
|
console.log(`Wiping all tables from ${dialect} database...`);
|
|
5610
8817
|
}
|
|
@@ -5618,7 +8825,7 @@ async function dbWipe(options = {}) {
|
|
|
5618
8825
|
`;
|
|
5619
8826
|
tables = result.map((row) => row.tablename);
|
|
5620
8827
|
} else if (dialect === "mysql") {
|
|
5621
|
-
const dbName =
|
|
8828
|
+
const dbName = process17.env.DB_NAME || "test";
|
|
5622
8829
|
const result = await bunSql`
|
|
5623
8830
|
SELECT table_name
|
|
5624
8831
|
FROM information_schema.tables
|
|
@@ -5703,7 +8910,7 @@ var init_file = __esm(async () => {
|
|
|
5703
8910
|
|
|
5704
8911
|
// src/actions/inspect.ts
|
|
5705
8912
|
async function inspectTable(tableName, options = {}) {
|
|
5706
|
-
const dialect =
|
|
8913
|
+
const dialect = config3.dialect || "postgres";
|
|
5707
8914
|
const verbose = options.verbose ?? true;
|
|
5708
8915
|
if (verbose) {
|
|
5709
8916
|
console.log(`-- Inspecting table: ${tableName}`);
|
|
@@ -5912,31 +9119,31 @@ var init_introspect = __esm(async () => {
|
|
|
5912
9119
|
});
|
|
5913
9120
|
|
|
5914
9121
|
// src/actions/make-model.ts
|
|
5915
|
-
import { existsSync as
|
|
5916
|
-
import { dirname as
|
|
5917
|
-
import
|
|
9122
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
9123
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
9124
|
+
import process18 from "process";
|
|
5918
9125
|
function findWorkspaceRoot(startPath) {
|
|
5919
9126
|
let currentPath = startPath;
|
|
5920
|
-
while (currentPath !==
|
|
5921
|
-
if (
|
|
9127
|
+
while (currentPath !== dirname4(currentPath)) {
|
|
9128
|
+
if (existsSync9(join4(currentPath, "package.json"))) {
|
|
5922
9129
|
return currentPath;
|
|
5923
9130
|
}
|
|
5924
|
-
currentPath =
|
|
9131
|
+
currentPath = dirname4(currentPath);
|
|
5925
9132
|
}
|
|
5926
|
-
return
|
|
9133
|
+
return process18.cwd();
|
|
5927
9134
|
}
|
|
5928
9135
|
async function makeModel(name, options = {}) {
|
|
5929
|
-
const workspaceRoot = findWorkspaceRoot(
|
|
5930
|
-
const modelsDir = options.dir ||
|
|
5931
|
-
if (!
|
|
5932
|
-
|
|
9136
|
+
const workspaceRoot = findWorkspaceRoot(process18.cwd());
|
|
9137
|
+
const modelsDir = options.dir || join4(workspaceRoot, "app/Models");
|
|
9138
|
+
if (!existsSync9(modelsDir)) {
|
|
9139
|
+
mkdirSync4(modelsDir, { recursive: true });
|
|
5933
9140
|
console.log(`-- Created models directory: ${modelsDir}`);
|
|
5934
9141
|
}
|
|
5935
9142
|
const className = name.charAt(0).toUpperCase() + name.slice(1);
|
|
5936
9143
|
const tableName = options.table || `${name.toLowerCase()}s`;
|
|
5937
9144
|
const fileName = `${className}.ts`;
|
|
5938
|
-
const filePath =
|
|
5939
|
-
if (
|
|
9145
|
+
const filePath = join4(modelsDir, fileName);
|
|
9146
|
+
if (existsSync9(filePath)) {
|
|
5940
9147
|
console.error(`-- Model already exists: ${filePath}`);
|
|
5941
9148
|
throw new Error(`Model already exists: ${filePath}`);
|
|
5942
9149
|
}
|
|
@@ -5963,7 +9170,7 @@ ${timestampFields}
|
|
|
5963
9170
|
|
|
5964
9171
|
export default model
|
|
5965
9172
|
`;
|
|
5966
|
-
|
|
9173
|
+
writeFileSync8(filePath, template);
|
|
5967
9174
|
console.log(`-- \u2713 Created model: ${filePath}`);
|
|
5968
9175
|
console.log(`-- Table: ${tableName}`);
|
|
5969
9176
|
}
|
|
@@ -6441,42 +9648,42 @@ __export(exports_migrate, {
|
|
|
6441
9648
|
deleteMigrationFiles: () => deleteMigrationFiles,
|
|
6442
9649
|
copyModelsToGenerated: () => copyModelsToGenerated
|
|
6443
9650
|
});
|
|
6444
|
-
import { copyFileSync, existsSync as
|
|
9651
|
+
import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync6, mkdtempSync, readdirSync as readdirSync4, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
|
|
6445
9652
|
import { tmpdir } from "os";
|
|
6446
|
-
import { join as
|
|
6447
|
-
import
|
|
9653
|
+
import { join as join6 } from "path";
|
|
9654
|
+
import process19 from "process";
|
|
6448
9655
|
function getWorkspaceRoot() {
|
|
6449
|
-
return
|
|
9656
|
+
return process19.cwd();
|
|
6450
9657
|
}
|
|
6451
9658
|
function ensureSqlDirectory(workspaceRoot) {
|
|
6452
9659
|
const sqlDir = getSqlDirectory(workspaceRoot);
|
|
6453
|
-
if (!
|
|
6454
|
-
|
|
9660
|
+
if (!existsSync11(sqlDir)) {
|
|
9661
|
+
mkdirSync6(sqlDir, { recursive: true });
|
|
6455
9662
|
console.log(`-- Created SQL directory: ${sqlDir}`);
|
|
6456
9663
|
}
|
|
6457
9664
|
return sqlDir;
|
|
6458
9665
|
}
|
|
6459
9666
|
async function generateMigration(dir, opts = {}) {
|
|
6460
9667
|
if (!dir) {
|
|
6461
|
-
dir =
|
|
9668
|
+
dir = join6(process19.cwd(), "app/Models");
|
|
6462
9669
|
}
|
|
6463
|
-
const dialect = opts.dialect ||
|
|
9670
|
+
const dialect = opts.dialect || config3.dialect || "postgres";
|
|
6464
9671
|
const workspaceRoot = getWorkspaceRoot();
|
|
6465
9672
|
const models = await loadModels({ modelsDir: dir });
|
|
6466
9673
|
const plan = buildMigrationPlan(models, { dialect });
|
|
6467
|
-
const defaultStatePath =
|
|
9674
|
+
const defaultStatePath = join6(dir, `.qb-migrations.${dialect}.json`);
|
|
6468
9675
|
const statePath = String(opts.state || defaultStatePath);
|
|
6469
9676
|
let previous;
|
|
6470
9677
|
if (!opts.full) {
|
|
6471
|
-
const generatedDir =
|
|
6472
|
-
if (
|
|
9678
|
+
const generatedDir = join6(workspaceRoot, "generated");
|
|
9679
|
+
if (existsSync11(generatedDir)) {
|
|
6473
9680
|
try {
|
|
6474
9681
|
const oldModels = await loadModels({ modelsDir: generatedDir });
|
|
6475
9682
|
previous = buildMigrationPlan(oldModels, { dialect });
|
|
6476
9683
|
console.log("-- Comparing with models from generated/ directory");
|
|
6477
9684
|
} catch (err) {
|
|
6478
9685
|
console.log("-- No previous models found in generated/ directory, checking state file", err);
|
|
6479
|
-
if (
|
|
9686
|
+
if (existsSync11(statePath)) {
|
|
6480
9687
|
try {
|
|
6481
9688
|
const raw = readFileSync2(statePath, "utf8");
|
|
6482
9689
|
const parsed = JSON.parse(raw);
|
|
@@ -6484,7 +9691,7 @@ async function generateMigration(dir, opts = {}) {
|
|
|
6484
9691
|
} catch {}
|
|
6485
9692
|
}
|
|
6486
9693
|
}
|
|
6487
|
-
} else if (
|
|
9694
|
+
} else if (existsSync11(statePath)) {
|
|
6488
9695
|
try {
|
|
6489
9696
|
const raw = readFileSync2(statePath, "utf8");
|
|
6490
9697
|
const parsed = JSON.parse(raw);
|
|
@@ -6497,16 +9704,16 @@ async function generateMigration(dir, opts = {}) {
|
|
|
6497
9704
|
`);
|
|
6498
9705
|
const hasChanges = sqlStatements.some((stmt) => /\b(?:CREATE|ALTER)\b/i.test(stmt));
|
|
6499
9706
|
if (opts.apply) {
|
|
6500
|
-
const dirPath = mkdtempSync(
|
|
6501
|
-
const filePath =
|
|
9707
|
+
const dirPath = mkdtempSync(join6(tmpdir(), "qb-migrate-"));
|
|
9708
|
+
const filePath = join6(dirPath, "migration.sql");
|
|
6502
9709
|
try {
|
|
6503
9710
|
if (hasChanges) {
|
|
6504
|
-
|
|
9711
|
+
writeFileSync9(filePath, sql);
|
|
6505
9712
|
console.log("-- Migration applied");
|
|
6506
9713
|
} else {
|
|
6507
9714
|
console.log("-- No changes; nothing to apply");
|
|
6508
9715
|
}
|
|
6509
|
-
|
|
9716
|
+
writeFileSync9(statePath, JSON.stringify({ plan, hash: hashMigrationPlan(plan), updatedAt: new Date().toISOString() }, null, 2));
|
|
6510
9717
|
} catch (err) {
|
|
6511
9718
|
console.error("-- Migration failed:", err);
|
|
6512
9719
|
throw err;
|
|
@@ -6517,12 +9724,12 @@ async function generateMigration(dir, opts = {}) {
|
|
|
6517
9724
|
}
|
|
6518
9725
|
async function executeMigration(dir) {
|
|
6519
9726
|
if (!dir) {
|
|
6520
|
-
dir =
|
|
9727
|
+
dir = join6(process19.cwd(), "app/Models");
|
|
6521
9728
|
}
|
|
6522
9729
|
const workspaceRoot = getWorkspaceRoot();
|
|
6523
9730
|
const sqlDir = ensureSqlDirectory(workspaceRoot);
|
|
6524
|
-
const dialect =
|
|
6525
|
-
const files =
|
|
9731
|
+
const dialect = config3.dialect || "postgres";
|
|
9732
|
+
const files = readdirSync4(sqlDir);
|
|
6526
9733
|
const scriptFiles = files.filter((file2) => file2.endsWith(".sql")).sort();
|
|
6527
9734
|
if (scriptFiles.length === 0) {
|
|
6528
9735
|
console.log("-- No migration files found to execute");
|
|
@@ -6549,7 +9756,7 @@ async function executeMigration(dir) {
|
|
|
6549
9756
|
}
|
|
6550
9757
|
console.log(`-- Executing ${totalPending} migrations (${permanentMigrations.length} permanent, ${transientMigrations.length} transient)`);
|
|
6551
9758
|
for (const file2 of permanentMigrations) {
|
|
6552
|
-
const filePath =
|
|
9759
|
+
const filePath = join6(sqlDir, file2);
|
|
6553
9760
|
console.log(`-- Executing: ${file2}`);
|
|
6554
9761
|
try {
|
|
6555
9762
|
await qb.file(filePath);
|
|
@@ -6561,7 +9768,7 @@ async function executeMigration(dir) {
|
|
|
6561
9768
|
}
|
|
6562
9769
|
}
|
|
6563
9770
|
for (const file2 of transientMigrations) {
|
|
6564
|
-
const filePath =
|
|
9771
|
+
const filePath = join6(sqlDir, file2);
|
|
6565
9772
|
console.log(`-- Executing: ${file2} (transient)`);
|
|
6566
9773
|
try {
|
|
6567
9774
|
await qb.file(filePath);
|
|
@@ -6582,7 +9789,7 @@ async function executeMigration(dir) {
|
|
|
6582
9789
|
}
|
|
6583
9790
|
async function resetDatabase(dir, opts = {}) {
|
|
6584
9791
|
if (!dir) {
|
|
6585
|
-
dir =
|
|
9792
|
+
dir = join6(process19.cwd(), "app/Models");
|
|
6586
9793
|
}
|
|
6587
9794
|
const dialect = opts.dialect || "postgres";
|
|
6588
9795
|
const driver = getDialectDriver(dialect);
|
|
@@ -6669,24 +9876,24 @@ async function resetDatabase(dir, opts = {}) {
|
|
|
6669
9876
|
}
|
|
6670
9877
|
async function deleteMigrationFiles(dir, workspaceRoot, opts = {}) {
|
|
6671
9878
|
if (!dir) {
|
|
6672
|
-
dir =
|
|
9879
|
+
dir = join6(process19.cwd(), "app/Models");
|
|
6673
9880
|
}
|
|
6674
9881
|
if (!workspaceRoot) {
|
|
6675
9882
|
workspaceRoot = getWorkspaceRoot();
|
|
6676
9883
|
}
|
|
6677
9884
|
const dialect = String(opts.dialect || "postgres");
|
|
6678
|
-
const defaultStatePath =
|
|
9885
|
+
const defaultStatePath = join6(dir, `.qb-migrations.${dialect}.json`);
|
|
6679
9886
|
const statePath = String(opts.state || defaultStatePath);
|
|
6680
|
-
if (
|
|
9887
|
+
if (existsSync11(statePath)) {
|
|
6681
9888
|
unlinkSync(statePath);
|
|
6682
9889
|
console.log(`-- Removed migration state file: ${statePath}`);
|
|
6683
9890
|
}
|
|
6684
9891
|
const sqlDir = getSqlDirectory(workspaceRoot);
|
|
6685
|
-
if (
|
|
6686
|
-
const sqlFiles =
|
|
9892
|
+
if (existsSync11(sqlDir)) {
|
|
9893
|
+
const sqlFiles = readdirSync4(sqlDir);
|
|
6687
9894
|
const migrationFiles = sqlFiles.filter((file2) => file2.endsWith(".sql"));
|
|
6688
9895
|
for (const file2 of migrationFiles) {
|
|
6689
|
-
const filePath =
|
|
9896
|
+
const filePath = join6(sqlDir, file2);
|
|
6690
9897
|
unlinkSync(filePath);
|
|
6691
9898
|
console.log(`-- Removed migration file: ${file2}`);
|
|
6692
9899
|
}
|
|
@@ -6695,26 +9902,26 @@ async function deleteMigrationFiles(dir, workspaceRoot, opts = {}) {
|
|
|
6695
9902
|
}
|
|
6696
9903
|
async function copyModelsToGenerated(dir, workspaceRoot) {
|
|
6697
9904
|
if (!dir) {
|
|
6698
|
-
dir =
|
|
9905
|
+
dir = join6(process19.cwd(), "app/Models");
|
|
6699
9906
|
}
|
|
6700
9907
|
if (!workspaceRoot) {
|
|
6701
9908
|
workspaceRoot = getWorkspaceRoot();
|
|
6702
9909
|
}
|
|
6703
9910
|
try {
|
|
6704
|
-
const generatedDir =
|
|
6705
|
-
if (!
|
|
6706
|
-
|
|
9911
|
+
const generatedDir = join6(workspaceRoot, "generated");
|
|
9912
|
+
if (!existsSync11(generatedDir)) {
|
|
9913
|
+
mkdirSync6(generatedDir, { recursive: true });
|
|
6707
9914
|
console.log("-- Created generated directory");
|
|
6708
9915
|
}
|
|
6709
|
-
const files =
|
|
9916
|
+
const files = readdirSync4(dir);
|
|
6710
9917
|
const modelFiles = files.filter((file2) => file2.endsWith(".ts") || file2.endsWith(".js"));
|
|
6711
9918
|
if (modelFiles.length === 0) {
|
|
6712
9919
|
console.log("-- No model files found to copy");
|
|
6713
9920
|
return;
|
|
6714
9921
|
}
|
|
6715
9922
|
for (const file2 of modelFiles) {
|
|
6716
|
-
const sourcePath =
|
|
6717
|
-
const destPath =
|
|
9923
|
+
const sourcePath = join6(dir, file2);
|
|
9924
|
+
const destPath = join6(generatedDir, file2);
|
|
6718
9925
|
copyFileSync(sourcePath, destPath);
|
|
6719
9926
|
}
|
|
6720
9927
|
console.log("-- Model files copied successfully");
|
|
@@ -6727,7 +9934,7 @@ function getSqlDirectory(workspaceRoot) {
|
|
|
6727
9934
|
if (!workspaceRoot) {
|
|
6728
9935
|
workspaceRoot = getWorkspaceRoot();
|
|
6729
9936
|
}
|
|
6730
|
-
return
|
|
9937
|
+
return join6(workspaceRoot, "database", "sql");
|
|
6731
9938
|
}
|
|
6732
9939
|
async function createMigrationsTable(qb, dialect) {
|
|
6733
9940
|
const driver = getDialectDriver(dialect);
|
|
@@ -6779,24 +9986,24 @@ var init_migrate_generate = __esm(async () => {
|
|
|
6779
9986
|
});
|
|
6780
9987
|
|
|
6781
9988
|
// src/actions/migrate-rollback.ts
|
|
6782
|
-
import { existsSync as
|
|
6783
|
-
import { dirname as
|
|
6784
|
-
import
|
|
9989
|
+
import { existsSync as existsSync12, unlinkSync as unlinkSync2 } from "fs";
|
|
9990
|
+
import { dirname as dirname6, join as join7 } from "path";
|
|
9991
|
+
import process20 from "process";
|
|
6785
9992
|
function findWorkspaceRoot2(startPath) {
|
|
6786
9993
|
let currentPath = startPath;
|
|
6787
|
-
while (currentPath !==
|
|
6788
|
-
if (
|
|
9994
|
+
while (currentPath !== dirname6(currentPath)) {
|
|
9995
|
+
if (existsSync12(join7(currentPath, "package.json"))) {
|
|
6789
9996
|
return currentPath;
|
|
6790
9997
|
}
|
|
6791
|
-
currentPath =
|
|
9998
|
+
currentPath = dirname6(currentPath);
|
|
6792
9999
|
}
|
|
6793
|
-
return
|
|
10000
|
+
return process20.cwd();
|
|
6794
10001
|
}
|
|
6795
10002
|
function getSqlDirectory2(workspaceRoot) {
|
|
6796
10003
|
if (!workspaceRoot) {
|
|
6797
|
-
workspaceRoot = findWorkspaceRoot2(
|
|
10004
|
+
workspaceRoot = findWorkspaceRoot2(process20.cwd());
|
|
6798
10005
|
}
|
|
6799
|
-
return
|
|
10006
|
+
return join7(workspaceRoot, "sql");
|
|
6800
10007
|
}
|
|
6801
10008
|
async function migrateRollback(options = {}) {
|
|
6802
10009
|
const steps = options.steps || 1;
|
|
@@ -6835,8 +10042,8 @@ async function migrateRollback(options = {}) {
|
|
|
6835
10042
|
await qb.unsafe(deleteSql, [migration.migration]);
|
|
6836
10043
|
console.log(`-- \u2713 Removed migration record: ${migration.migration}`);
|
|
6837
10044
|
const sqlDir = getSqlDirectory2();
|
|
6838
|
-
const filePath =
|
|
6839
|
-
if (
|
|
10045
|
+
const filePath = join7(sqlDir, migration.migration);
|
|
10046
|
+
if (existsSync12(filePath)) {
|
|
6840
10047
|
unlinkSync2(filePath);
|
|
6841
10048
|
console.log(`-- \uD83D\uDDD1\uFE0F Deleted migration file: ${migration.migration}`);
|
|
6842
10049
|
}
|
|
@@ -6861,38 +10068,38 @@ var init_migrate_rollback = __esm(async () => {
|
|
|
6861
10068
|
});
|
|
6862
10069
|
|
|
6863
10070
|
// src/actions/migrate-status.ts
|
|
6864
|
-
import { existsSync as
|
|
6865
|
-
import { dirname as
|
|
6866
|
-
import
|
|
10071
|
+
import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
|
|
10072
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
10073
|
+
import process21 from "process";
|
|
6867
10074
|
function findWorkspaceRoot3(startPath) {
|
|
6868
10075
|
let currentPath = startPath;
|
|
6869
|
-
while (currentPath !==
|
|
6870
|
-
if (
|
|
10076
|
+
while (currentPath !== dirname7(currentPath)) {
|
|
10077
|
+
if (existsSync13(join8(currentPath, "package.json"))) {
|
|
6871
10078
|
return currentPath;
|
|
6872
10079
|
}
|
|
6873
|
-
currentPath =
|
|
10080
|
+
currentPath = dirname7(currentPath);
|
|
6874
10081
|
}
|
|
6875
|
-
return
|
|
10082
|
+
return process21.cwd();
|
|
6876
10083
|
}
|
|
6877
10084
|
function getSqlDirectory3(workspaceRoot) {
|
|
6878
10085
|
if (!workspaceRoot) {
|
|
6879
|
-
workspaceRoot = findWorkspaceRoot3(
|
|
10086
|
+
workspaceRoot = findWorkspaceRoot3(process21.cwd());
|
|
6880
10087
|
}
|
|
6881
|
-
return
|
|
10088
|
+
return join8(workspaceRoot, "sql");
|
|
6882
10089
|
}
|
|
6883
10090
|
async function migrateStatus() {
|
|
6884
|
-
const dialect =
|
|
10091
|
+
const dialect = config3.dialect || "postgres";
|
|
6885
10092
|
const driver = getDialectDriver(dialect);
|
|
6886
10093
|
const sqlDir = getSqlDirectory3();
|
|
6887
10094
|
console.log("-- Migration Status");
|
|
6888
10095
|
console.log(`-- Dialect: ${dialect}`);
|
|
6889
10096
|
console.log(`-- SQL directory: ${sqlDir}`);
|
|
6890
10097
|
console.log();
|
|
6891
|
-
if (!
|
|
10098
|
+
if (!existsSync13(sqlDir)) {
|
|
6892
10099
|
console.log("-- No SQL directory found. No migrations have been created yet.");
|
|
6893
10100
|
return [];
|
|
6894
10101
|
}
|
|
6895
|
-
const files =
|
|
10102
|
+
const files = readdirSync6(sqlDir);
|
|
6896
10103
|
const migrationFiles = files.filter((file2) => file2.endsWith(".sql")).sort();
|
|
6897
10104
|
if (migrationFiles.length === 0) {
|
|
6898
10105
|
console.log("-- No migration files found");
|
|
@@ -6970,13 +10177,13 @@ var init_migrate_status = __esm(async () => {
|
|
|
6970
10177
|
});
|
|
6971
10178
|
|
|
6972
10179
|
// src/actions/model-show.ts
|
|
6973
|
-
import { readdirSync as
|
|
6974
|
-
import { extname, join as
|
|
6975
|
-
import
|
|
10180
|
+
import { readdirSync as readdirSync7 } from "fs";
|
|
10181
|
+
import { extname, join as join9 } from "path";
|
|
10182
|
+
import process22 from "process";
|
|
6976
10183
|
async function modelShow(modelName, options = {}) {
|
|
6977
|
-
const dir = options.dir ||
|
|
10184
|
+
const dir = options.dir || join9(process22.cwd(), "app/Models");
|
|
6978
10185
|
try {
|
|
6979
|
-
const files =
|
|
10186
|
+
const files = readdirSync7(dir);
|
|
6980
10187
|
const modelFile = files.find((f) => {
|
|
6981
10188
|
const name = f.replace(extname(f), "");
|
|
6982
10189
|
return name.toLowerCase() === modelName.toLowerCase();
|
|
@@ -6986,7 +10193,7 @@ async function modelShow(modelName, options = {}) {
|
|
|
6986
10193
|
console.log(`Available models: ${files.filter((f) => [".ts", ".js"].includes(extname(f))).map((f) => f.replace(extname(f), "")).join(", ")}`);
|
|
6987
10194
|
return;
|
|
6988
10195
|
}
|
|
6989
|
-
const modelPath =
|
|
10196
|
+
const modelPath = join9(dir, modelFile);
|
|
6990
10197
|
const module = await import(modelPath);
|
|
6991
10198
|
const model = module.default || module[Object.keys(module)[0]];
|
|
6992
10199
|
if (!model || !model.name) {
|
|
@@ -7093,17 +10300,17 @@ var init_ping = __esm(async () => {
|
|
|
7093
10300
|
});
|
|
7094
10301
|
|
|
7095
10302
|
// src/actions/query-explain-all.ts
|
|
7096
|
-
import { readdirSync as
|
|
7097
|
-
import { extname as extname2, join as
|
|
10303
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
10304
|
+
import { extname as extname2, join as join10 } from "path";
|
|
7098
10305
|
async function queryExplainAll(path, options = {}) {
|
|
7099
10306
|
const results = [];
|
|
7100
10307
|
try {
|
|
7101
|
-
const
|
|
10308
|
+
const stat3 = statSync2(path);
|
|
7102
10309
|
let files = [];
|
|
7103
|
-
if (
|
|
7104
|
-
const allFiles =
|
|
7105
|
-
files = allFiles.filter((f) => extname2(f) === ".sql").map((f) =>
|
|
7106
|
-
} else if (
|
|
10310
|
+
if (stat3.isDirectory()) {
|
|
10311
|
+
const allFiles = readdirSync8(path);
|
|
10312
|
+
files = allFiles.filter((f) => extname2(f) === ".sql").map((f) => join10(path, f));
|
|
10313
|
+
} else if (stat3.isFile() && extname2(path) === ".sql") {
|
|
7107
10314
|
files = [path];
|
|
7108
10315
|
} else {
|
|
7109
10316
|
console.error("Path must be a .sql file or directory containing .sql files");
|
|
@@ -7186,11 +10393,11 @@ var init_query_explain_all = __esm(async () => {
|
|
|
7186
10393
|
});
|
|
7187
10394
|
|
|
7188
10395
|
// src/actions/relation-diagram.ts
|
|
7189
|
-
import { writeFileSync as
|
|
7190
|
-
import { join as
|
|
7191
|
-
import
|
|
10396
|
+
import { writeFileSync as writeFileSync10 } from "fs";
|
|
10397
|
+
import { join as join11 } from "path";
|
|
10398
|
+
import process23 from "process";
|
|
7192
10399
|
async function relationDiagram(options = {}) {
|
|
7193
|
-
const dir = options.dir ||
|
|
10400
|
+
const dir = options.dir || join11(process23.cwd(), "app/Models");
|
|
7194
10401
|
const format = options.format || "mermaid";
|
|
7195
10402
|
try {
|
|
7196
10403
|
const models = await loadModels({ modelsDir: dir });
|
|
@@ -7208,7 +10415,7 @@ async function relationDiagram(options = {}) {
|
|
|
7208
10415
|
return "";
|
|
7209
10416
|
}
|
|
7210
10417
|
if (options.output) {
|
|
7211
|
-
|
|
10418
|
+
writeFileSync10(options.output, diagram, "utf8");
|
|
7212
10419
|
console.log(`\u2713 Diagram written to: ${options.output}`);
|
|
7213
10420
|
} else {
|
|
7214
10421
|
console.log(diagram);
|
|
@@ -7310,29 +10517,29 @@ var init_relation_diagram = __esm(async () => {
|
|
|
7310
10517
|
});
|
|
7311
10518
|
|
|
7312
10519
|
// src/actions/seed.ts
|
|
7313
|
-
import { existsSync as
|
|
7314
|
-
import { dirname as
|
|
7315
|
-
import
|
|
10520
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync9, writeFileSync as writeFileSync11 } from "fs";
|
|
10521
|
+
import { dirname as dirname8, join as join12 } from "path";
|
|
10522
|
+
import process24 from "process";
|
|
7316
10523
|
function findWorkspaceRoot4(startPath) {
|
|
7317
10524
|
let currentPath = startPath;
|
|
7318
|
-
while (currentPath !==
|
|
7319
|
-
if (
|
|
10525
|
+
while (currentPath !== dirname8(currentPath)) {
|
|
10526
|
+
if (existsSync14(join12(currentPath, "package.json"))) {
|
|
7320
10527
|
return currentPath;
|
|
7321
10528
|
}
|
|
7322
|
-
currentPath =
|
|
10529
|
+
currentPath = dirname8(currentPath);
|
|
7323
10530
|
}
|
|
7324
|
-
return
|
|
10531
|
+
return process24.cwd();
|
|
7325
10532
|
}
|
|
7326
10533
|
async function loadSeeders(seedersDir) {
|
|
7327
|
-
if (!
|
|
10534
|
+
if (!existsSync14(seedersDir)) {
|
|
7328
10535
|
console.log(`-- Seeders directory not found: ${seedersDir}`);
|
|
7329
10536
|
return [];
|
|
7330
10537
|
}
|
|
7331
|
-
const files =
|
|
10538
|
+
const files = readdirSync9(seedersDir);
|
|
7332
10539
|
const seederFiles = files.filter((file2) => (file2.endsWith(".ts") || file2.endsWith(".js")) && file2 !== "index.ts" && file2 !== "index.js");
|
|
7333
10540
|
const seeders = [];
|
|
7334
10541
|
for (const file2 of seederFiles) {
|
|
7335
|
-
const filePath =
|
|
10542
|
+
const filePath = join12(seedersDir, file2);
|
|
7336
10543
|
try {
|
|
7337
10544
|
const module = await import(filePath);
|
|
7338
10545
|
const SeederClass = module.default || module[Object.keys(module)[0]];
|
|
@@ -7352,10 +10559,10 @@ async function loadSeeders(seedersDir) {
|
|
|
7352
10559
|
seeders.sort((a, b) => a.instance.order - b.instance.order);
|
|
7353
10560
|
return seeders;
|
|
7354
10561
|
}
|
|
7355
|
-
async function runSeeders(
|
|
7356
|
-
const workspaceRoot = findWorkspaceRoot4(
|
|
7357
|
-
const seedersDir =
|
|
7358
|
-
const verbose =
|
|
10562
|
+
async function runSeeders(config4 = {}) {
|
|
10563
|
+
const workspaceRoot = findWorkspaceRoot4(process24.cwd());
|
|
10564
|
+
const seedersDir = config4.seedersDir || join12(workspaceRoot, "database/seeders");
|
|
10565
|
+
const verbose = config4.verbose ?? true;
|
|
7359
10566
|
if (verbose) {
|
|
7360
10567
|
console.log("-- Running seeders...");
|
|
7361
10568
|
console.log(`-- Seeders directory: ${seedersDir}`);
|
|
@@ -7388,8 +10595,8 @@ async function runSeeders(config3 = {}) {
|
|
|
7388
10595
|
}
|
|
7389
10596
|
}
|
|
7390
10597
|
async function runSeeder(className, options = {}) {
|
|
7391
|
-
const workspaceRoot = findWorkspaceRoot4(
|
|
7392
|
-
const seedersDir =
|
|
10598
|
+
const workspaceRoot = findWorkspaceRoot4(process24.cwd());
|
|
10599
|
+
const seedersDir = join12(workspaceRoot, "database/seeders");
|
|
7393
10600
|
const verbose = options.verbose ?? true;
|
|
7394
10601
|
if (verbose) {
|
|
7395
10602
|
console.log(`-- Running seeder: ${className}`);
|
|
@@ -7412,17 +10619,17 @@ async function runSeeder(className, options = {}) {
|
|
|
7412
10619
|
}
|
|
7413
10620
|
}
|
|
7414
10621
|
async function makeSeeder(name) {
|
|
7415
|
-
const workspaceRoot = findWorkspaceRoot4(
|
|
7416
|
-
const seedersDir =
|
|
7417
|
-
if (!
|
|
7418
|
-
|
|
10622
|
+
const workspaceRoot = findWorkspaceRoot4(process24.cwd());
|
|
10623
|
+
const seedersDir = join12(workspaceRoot, "database/seeders");
|
|
10624
|
+
if (!existsSync14(seedersDir)) {
|
|
10625
|
+
mkdirSync7(seedersDir, { recursive: true });
|
|
7419
10626
|
console.log(`-- Created seeders directory: ${seedersDir}`);
|
|
7420
10627
|
}
|
|
7421
10628
|
const baseName = name.replace(/Seeder$/i, "");
|
|
7422
10629
|
const className = `${baseName}Seeder`;
|
|
7423
10630
|
const fileName = `${className}.ts`;
|
|
7424
|
-
const filePath =
|
|
7425
|
-
if (
|
|
10631
|
+
const filePath = join12(seedersDir, fileName);
|
|
10632
|
+
if (existsSync14(filePath)) {
|
|
7426
10633
|
console.error(`-- Seeder already exists: ${filePath}`);
|
|
7427
10634
|
throw new Error(`Seeder already exists: ${filePath}`);
|
|
7428
10635
|
}
|
|
@@ -7472,13 +10679,13 @@ export default class ${className} extends Seeder {
|
|
|
7472
10679
|
}
|
|
7473
10680
|
}
|
|
7474
10681
|
`;
|
|
7475
|
-
|
|
10682
|
+
writeFileSync11(filePath, template);
|
|
7476
10683
|
console.log(`-- \u2713 Created seeder: ${filePath}`);
|
|
7477
10684
|
}
|
|
7478
10685
|
async function freshDatabase(options = {}) {
|
|
7479
|
-
const workspaceRoot = findWorkspaceRoot4(
|
|
7480
|
-
const modelsDir = options.modelsDir ||
|
|
7481
|
-
const seedersDir = options.seedersDir ||
|
|
10686
|
+
const workspaceRoot = findWorkspaceRoot4(process24.cwd());
|
|
10687
|
+
const modelsDir = options.modelsDir || join12(workspaceRoot, "app/Models");
|
|
10688
|
+
const seedersDir = options.seedersDir || join12(workspaceRoot, "database/seeders");
|
|
7482
10689
|
const verbose = options.verbose ?? true;
|
|
7483
10690
|
try {
|
|
7484
10691
|
const { resetDatabase: resetDatabase2, generateMigration: generateMigration2, executeMigration: executeMigration2 } = await init_migrate().then(() => exports_migrate);
|
|
@@ -7511,8 +10718,8 @@ var init_seed = __esm(async () => {
|
|
|
7511
10718
|
function sql(dir, table, opts = {}) {
|
|
7512
10719
|
const models = loadModels({ modelsDir: dir });
|
|
7513
10720
|
const dbSchema = buildDatabaseSchema(models);
|
|
7514
|
-
if (
|
|
7515
|
-
|
|
10721
|
+
if (config3.debug)
|
|
10722
|
+
config3.debug.captureText = true;
|
|
7516
10723
|
const qb = createQueryBuilder({ schema: dbSchema });
|
|
7517
10724
|
const s = qb.selectFrom(table).limit(Number(opts.limit || 10)).toText?.() ?? "";
|
|
7518
10725
|
console.log(s || "[query]");
|
|
@@ -7538,24 +10745,24 @@ var init_unsafe = __esm(async () => {
|
|
|
7538
10745
|
});
|
|
7539
10746
|
|
|
7540
10747
|
// src/actions/validate.ts
|
|
7541
|
-
import { existsSync as
|
|
7542
|
-
import { dirname as
|
|
7543
|
-
import
|
|
10748
|
+
import { existsSync as existsSync15 } from "fs";
|
|
10749
|
+
import { dirname as dirname9, join as join13 } from "path";
|
|
10750
|
+
import process25 from "process";
|
|
7544
10751
|
function findWorkspaceRoot5(startPath) {
|
|
7545
10752
|
let currentPath = startPath;
|
|
7546
|
-
while (currentPath !==
|
|
7547
|
-
if (
|
|
10753
|
+
while (currentPath !== dirname9(currentPath)) {
|
|
10754
|
+
if (existsSync15(join13(currentPath, "package.json"))) {
|
|
7548
10755
|
return currentPath;
|
|
7549
10756
|
}
|
|
7550
|
-
currentPath =
|
|
10757
|
+
currentPath = dirname9(currentPath);
|
|
7551
10758
|
}
|
|
7552
|
-
return
|
|
10759
|
+
return process25.cwd();
|
|
7553
10760
|
}
|
|
7554
10761
|
async function validateSchema(dir) {
|
|
7555
10762
|
if (!dir) {
|
|
7556
|
-
dir =
|
|
10763
|
+
dir = join13(findWorkspaceRoot5(process25.cwd()), "app/Models");
|
|
7557
10764
|
}
|
|
7558
|
-
const dialect =
|
|
10765
|
+
const dialect = config3.dialect || "postgres";
|
|
7559
10766
|
console.log("-- Validating Schema");
|
|
7560
10767
|
console.log(`-- Models directory: ${dir}`);
|
|
7561
10768
|
console.log(`-- Dialect: ${dialect}`);
|
|
@@ -7811,17 +11018,17 @@ function buildDatabaseSchema(models) {
|
|
|
7811
11018
|
}
|
|
7812
11019
|
|
|
7813
11020
|
// src/loader.ts
|
|
7814
|
-
import { readdirSync as
|
|
11021
|
+
import { readdirSync as readdirSync10, statSync as statSync3 } from "fs";
|
|
7815
11022
|
import { basename, extname as extname3 } from "path";
|
|
7816
|
-
import
|
|
11023
|
+
import process26 from "process";
|
|
7817
11024
|
async function loadModels(options) {
|
|
7818
|
-
const cwd = options.cwd ??
|
|
11025
|
+
const cwd = options.cwd ?? process26.cwd();
|
|
7819
11026
|
const dir = options.modelsDir.startsWith("/") ? options.modelsDir : `${cwd}/${options.modelsDir}`;
|
|
7820
11027
|
const result = {};
|
|
7821
|
-
const entries =
|
|
11028
|
+
const entries = readdirSync10(dir);
|
|
7822
11029
|
for (const entry of entries) {
|
|
7823
11030
|
const full = `${dir}/${entry}`;
|
|
7824
|
-
const st =
|
|
11031
|
+
const st = statSync3(full);
|
|
7825
11032
|
if (st.isDirectory())
|
|
7826
11033
|
continue;
|
|
7827
11034
|
const ext = extname3(full);
|
|
@@ -7895,24 +11102,24 @@ function buildSchemaMeta(models) {
|
|
|
7895
11102
|
}
|
|
7896
11103
|
|
|
7897
11104
|
// src/migrations.ts
|
|
7898
|
-
import { existsSync as
|
|
7899
|
-
import { dirname as
|
|
7900
|
-
import
|
|
11105
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, writeFileSync as writeFileSync12 } from "fs";
|
|
11106
|
+
import { dirname as dirname10, join as join14 } from "path";
|
|
11107
|
+
import process27 from "process";
|
|
7901
11108
|
function findWorkspaceRoot6(startPath) {
|
|
7902
11109
|
let currentPath = startPath;
|
|
7903
|
-
while (currentPath !==
|
|
7904
|
-
if (
|
|
11110
|
+
while (currentPath !== dirname10(currentPath)) {
|
|
11111
|
+
if (existsSync16(join14(currentPath, "package.json"))) {
|
|
7905
11112
|
return currentPath;
|
|
7906
11113
|
}
|
|
7907
|
-
currentPath =
|
|
11114
|
+
currentPath = dirname10(currentPath);
|
|
7908
11115
|
}
|
|
7909
|
-
return
|
|
11116
|
+
return process27.cwd();
|
|
7910
11117
|
}
|
|
7911
11118
|
function ensureSqlDirectory2() {
|
|
7912
|
-
const workspaceRoot = findWorkspaceRoot6(
|
|
7913
|
-
const sqlDir =
|
|
7914
|
-
if (!
|
|
7915
|
-
|
|
11119
|
+
const workspaceRoot = findWorkspaceRoot6(process27.cwd());
|
|
11120
|
+
const sqlDir = join14(workspaceRoot, "database", "sql");
|
|
11121
|
+
if (!existsSync16(sqlDir)) {
|
|
11122
|
+
mkdirSync8(sqlDir, { recursive: true });
|
|
7916
11123
|
console.log(`-- Created SQL directory: ${sqlDir}`);
|
|
7917
11124
|
}
|
|
7918
11125
|
return sqlDir;
|
|
@@ -7925,8 +11132,8 @@ function createMigrationFile(statement, fileName) {
|
|
|
7925
11132
|
const timestamp = baseTimestamp + migrationCounter;
|
|
7926
11133
|
migrationCounter++;
|
|
7927
11134
|
const fullFileName = `${timestamp}-${fileName}.sql`;
|
|
7928
|
-
const filePath =
|
|
7929
|
-
|
|
11135
|
+
const filePath = join14(sqlDir, fullFileName);
|
|
11136
|
+
writeFileSync12(filePath, statement);
|
|
7930
11137
|
console.log(`-- Migration file created: ${fullFileName}`);
|
|
7931
11138
|
migrationsCreatedCount++;
|
|
7932
11139
|
return true;
|
|
@@ -8487,13 +11694,13 @@ export {
|
|
|
8487
11694
|
defineSeeder,
|
|
8488
11695
|
defineModels,
|
|
8489
11696
|
defineModel,
|
|
8490
|
-
|
|
11697
|
+
defaultConfig3 as defaultConfig,
|
|
8491
11698
|
dbWipe,
|
|
8492
11699
|
dbStats,
|
|
8493
11700
|
dbOptimize,
|
|
8494
11701
|
dbInfo,
|
|
8495
11702
|
createQueryBuilder,
|
|
8496
|
-
|
|
11703
|
+
config3 as config,
|
|
8497
11704
|
clearQueryCache,
|
|
8498
11705
|
checkSchema,
|
|
8499
11706
|
cacheStats,
|