bun-query-builder 0.1.6 → 0.1.7
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 +3492 -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.2/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: /^https?:\/\// },
|
|
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, 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,271 @@ 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
|
+
log2 = new Logger2("bunfig", {
|
|
5069
|
+
showTags: true
|
|
5070
|
+
});
|
|
5071
|
+
globalConfigLoader = new ConfigLoader;
|
|
5072
|
+
defaultConfigDir3 = resolve7(process12.cwd(), "config");
|
|
5073
|
+
defaultGeneratedDir3 = resolve7(process12.cwd(), "src/generated");
|
|
1868
5074
|
});
|
|
1869
5075
|
|
|
1870
5076
|
// src/config.ts
|
|
1871
|
-
var
|
|
5077
|
+
var defaultConfig3, config3;
|
|
1872
5078
|
var init_config = __esm(async () => {
|
|
1873
5079
|
await init_dist();
|
|
1874
|
-
|
|
5080
|
+
defaultConfig3 = {
|
|
1875
5081
|
verbose: true,
|
|
1876
5082
|
dialect: "postgres",
|
|
1877
5083
|
database: {
|
|
@@ -1928,16 +5134,16 @@ var init_config = __esm(async () => {
|
|
|
1928
5134
|
defaultFilter: true
|
|
1929
5135
|
}
|
|
1930
5136
|
};
|
|
1931
|
-
|
|
5137
|
+
config3 = await loadConfig5({
|
|
1932
5138
|
name: "query-builder",
|
|
1933
5139
|
alias: "qb",
|
|
1934
|
-
defaultConfig:
|
|
5140
|
+
defaultConfig: defaultConfig3
|
|
1935
5141
|
});
|
|
1936
5142
|
});
|
|
1937
5143
|
|
|
1938
5144
|
// src/db.ts
|
|
1939
5145
|
var {SQL } = globalThis.Bun;
|
|
1940
|
-
import
|
|
5146
|
+
import process13 from "process";
|
|
1941
5147
|
function createConnectionString(dialect, dbConfig) {
|
|
1942
5148
|
if (dbConfig.url) {
|
|
1943
5149
|
return dbConfig.url;
|
|
@@ -1958,12 +5164,12 @@ function createConnectionString(dialect, dbConfig) {
|
|
|
1958
5164
|
}
|
|
1959
5165
|
}
|
|
1960
5166
|
function getBunSql() {
|
|
1961
|
-
const connectionString = createConnectionString(
|
|
5167
|
+
const connectionString = createConnectionString(config3.dialect, config3.database);
|
|
1962
5168
|
try {
|
|
1963
5169
|
const sql = new SQL(connectionString);
|
|
1964
5170
|
if (sql && typeof sql.catch === "function") {
|
|
1965
5171
|
sql.catch((error) => {
|
|
1966
|
-
if (
|
|
5172
|
+
if (config3.verbose && !error.message.includes("database") && !error.message.includes("does not exist")) {
|
|
1967
5173
|
console.warn(`[query-builder] Database connection error: ${error.message}`);
|
|
1968
5174
|
}
|
|
1969
5175
|
});
|
|
@@ -2007,14 +5213,14 @@ var _bunSqlInstance = null, bunSql;
|
|
|
2007
5213
|
var init_db = __esm(async () => {
|
|
2008
5214
|
await init_config();
|
|
2009
5215
|
bunSql = getOrCreateBunSql();
|
|
2010
|
-
if (typeof
|
|
2011
|
-
const existingHandler =
|
|
5216
|
+
if (typeof process13 !== "undefined" && process13.on) {
|
|
5217
|
+
const existingHandler = process13.listeners("unhandledRejection").find((h) => h.name === "sqlConnectionErrorHandler");
|
|
2012
5218
|
if (!existingHandler) {
|
|
2013
5219
|
let sqlConnectionErrorHandler = function(reason) {
|
|
2014
5220
|
if (reason && (reason.message?.includes("database") || reason.message?.includes("does not exist") || reason.code === "ERR_POSTGRES_SERVER_ERROR" || reason.code === "3D000")) {}
|
|
2015
5221
|
};
|
|
2016
5222
|
Object.defineProperty(sqlConnectionErrorHandler, "name", { value: "sqlConnectionErrorHandler" });
|
|
2017
|
-
|
|
5223
|
+
process13.on("unhandledRejection", sqlConnectionErrorHandler);
|
|
2018
5224
|
}
|
|
2019
5225
|
}
|
|
2020
5226
|
});
|
|
@@ -2233,7 +5439,7 @@ function matchesSqlState(err, states) {
|
|
|
2233
5439
|
return states.includes(code);
|
|
2234
5440
|
}
|
|
2235
5441
|
function sleep(ms) {
|
|
2236
|
-
return new Promise((
|
|
5442
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
2237
5443
|
}
|
|
2238
5444
|
function computeBackoffMs(attempt, cfg) {
|
|
2239
5445
|
const base = Math.max(1, cfg?.baseMs ?? 50);
|
|
@@ -2313,16 +5519,16 @@ function createQueryBuilder(state) {
|
|
|
2313
5519
|
return _sql`${q} WHERE ${condition}`;
|
|
2314
5520
|
}
|
|
2315
5521
|
function computeSqlText(q) {
|
|
2316
|
-
const prev =
|
|
2317
|
-
if (
|
|
2318
|
-
|
|
5522
|
+
const prev = config3.debug?.captureText;
|
|
5523
|
+
if (config3.debug)
|
|
5524
|
+
config3.debug.captureText = true;
|
|
2319
5525
|
const s = String(q);
|
|
2320
|
-
if (
|
|
2321
|
-
|
|
5526
|
+
if (config3.debug)
|
|
5527
|
+
config3.debug.captureText = prev;
|
|
2322
5528
|
return s;
|
|
2323
5529
|
}
|
|
2324
5530
|
function runWithHooks(q, kind, opts) {
|
|
2325
|
-
const hooks =
|
|
5531
|
+
const hooks = config3.hooks;
|
|
2326
5532
|
const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan);
|
|
2327
5533
|
const hasTimeoutOrSignal = opts?.timeoutMs && opts.timeoutMs > 0 || opts?.signal;
|
|
2328
5534
|
if (!hasHooks && !hasTimeoutOrSignal) {
|
|
@@ -2671,7 +5877,7 @@ function createQueryBuilder(state) {
|
|
|
2671
5877
|
}
|
|
2672
5878
|
if (normalizedRelations.length === 0)
|
|
2673
5879
|
return this;
|
|
2674
|
-
const maxEagerLoad =
|
|
5880
|
+
const maxEagerLoad = config3.relations.maxEagerLoad ?? 50;
|
|
2675
5881
|
if (normalizedRelations.length > maxEagerLoad) {
|
|
2676
5882
|
throw new Error(`[query-builder] Too many relationships to eager load (${normalizedRelations.length}). Maximum allowed: ${maxEagerLoad}`);
|
|
2677
5883
|
}
|
|
@@ -2680,7 +5886,7 @@ function createQueryBuilder(state) {
|
|
|
2680
5886
|
const loadedRelationships = new Set;
|
|
2681
5887
|
const relationConditions = new Map;
|
|
2682
5888
|
const singularize = (name) => {
|
|
2683
|
-
if (
|
|
5889
|
+
if (config3.relations.singularizeStrategy === "none")
|
|
2684
5890
|
return name;
|
|
2685
5891
|
return name.endsWith("s") ? name.slice(0, -1) : name;
|
|
2686
5892
|
};
|
|
@@ -2702,7 +5908,7 @@ function createQueryBuilder(state) {
|
|
|
2702
5908
|
];
|
|
2703
5909
|
};
|
|
2704
5910
|
const addJoin = (fromTable, relationKey, depth = 0, condition) => {
|
|
2705
|
-
const maxDepth =
|
|
5911
|
+
const maxDepth = config3.relations.maxDepth ?? 10;
|
|
2706
5912
|
if (depth >= maxDepth) {
|
|
2707
5913
|
throw new Error(`[query-builder] Maximum relationship depth (${maxDepth}) exceeded at '${relationKey}'. Consider using separate queries or increasing maxDepth.`);
|
|
2708
5914
|
}
|
|
@@ -2712,8 +5918,8 @@ function createQueryBuilder(state) {
|
|
|
2712
5918
|
}
|
|
2713
5919
|
const _buildConditionalJoin = (baseJoinCondition, targetTable2) => {
|
|
2714
5920
|
let joinCondition = baseJoinCondition;
|
|
2715
|
-
if (
|
|
2716
|
-
const softDeleteColumn =
|
|
5921
|
+
if (config3.softDeletes?.enabled && config3.softDeletes?.defaultFilter) {
|
|
5922
|
+
const softDeleteColumn = config3.softDeletes.column || "deleted_at";
|
|
2717
5923
|
joinCondition = `${joinCondition} AND ${targetTable2}.${softDeleteColumn} IS NULL`;
|
|
2718
5924
|
}
|
|
2719
5925
|
if (!condition)
|
|
@@ -2733,8 +5939,8 @@ function createQueryBuilder(state) {
|
|
|
2733
5939
|
return joinCondition;
|
|
2734
5940
|
};
|
|
2735
5941
|
const addSoftDeleteCheck = (table2) => {
|
|
2736
|
-
if (
|
|
2737
|
-
const softDeleteColumn =
|
|
5942
|
+
if (config3.softDeletes?.enabled && config3.softDeletes?.defaultFilter) {
|
|
5943
|
+
const softDeleteColumn = config3.softDeletes.column || "deleted_at";
|
|
2738
5944
|
return ` AND ${table2}.${softDeleteColumn} IS NULL`;
|
|
2739
5945
|
}
|
|
2740
5946
|
return "";
|
|
@@ -2759,7 +5965,7 @@ function createQueryBuilder(state) {
|
|
|
2759
5965
|
throw new Error(`[query-builder] Relationship '${relationKey}' not found on table '${fromTable}'.${suggestion}`);
|
|
2760
5966
|
}
|
|
2761
5967
|
}
|
|
2762
|
-
if (
|
|
5968
|
+
if (config3.relations.detectCycles !== false) {
|
|
2763
5969
|
const cycleKey = `${fromTable}->${childTable}`;
|
|
2764
5970
|
if (visitedTables.has(cycleKey)) {
|
|
2765
5971
|
throw new Error(`[query-builder] Circular relationship detected: ${cycleKey}. This would cause an infinite loop.`);
|
|
@@ -3168,7 +6374,7 @@ function createQueryBuilder(state) {
|
|
|
3168
6374
|
return this;
|
|
3169
6375
|
},
|
|
3170
6376
|
whereJsonPath(path, op, value) {
|
|
3171
|
-
const dialect =
|
|
6377
|
+
const dialect = config3.dialect;
|
|
3172
6378
|
if (dialect === "postgres") {
|
|
3173
6379
|
built = sql`${built} WHERE ${sql(path)} ${op} ${value}`;
|
|
3174
6380
|
} else if (dialect === "mysql") {
|
|
@@ -3185,7 +6391,7 @@ function createQueryBuilder(state) {
|
|
|
3185
6391
|
return this;
|
|
3186
6392
|
},
|
|
3187
6393
|
whereILike(column, pattern) {
|
|
3188
|
-
if (
|
|
6394
|
+
if (config3.dialect === "postgres") {
|
|
3189
6395
|
built = sql`${built} WHERE ${sql(String(column))} ILIKE ${pattern}`;
|
|
3190
6396
|
addWhereText("WHERE", `${String(column)} ILIKE ?`);
|
|
3191
6397
|
} else {
|
|
@@ -3202,7 +6408,7 @@ function createQueryBuilder(state) {
|
|
|
3202
6408
|
return this;
|
|
3203
6409
|
},
|
|
3204
6410
|
orWhereILike(column, pattern) {
|
|
3205
|
-
if (
|
|
6411
|
+
if (config3.dialect === "postgres") {
|
|
3206
6412
|
built = sql`${built} OR ${sql(String(column))} ILIKE ${pattern}`;
|
|
3207
6413
|
addWhereText("OR", `${String(column)} ILIKE ?`);
|
|
3208
6414
|
} else {
|
|
@@ -3219,7 +6425,7 @@ function createQueryBuilder(state) {
|
|
|
3219
6425
|
return this;
|
|
3220
6426
|
},
|
|
3221
6427
|
whereNotILike(column, pattern) {
|
|
3222
|
-
if (
|
|
6428
|
+
if (config3.dialect === "postgres") {
|
|
3223
6429
|
built = sql`${built} WHERE ${sql(String(column))} NOT ILIKE ${pattern}`;
|
|
3224
6430
|
addWhereText("WHERE", `${String(column)} NOT ILIKE ?`);
|
|
3225
6431
|
} else {
|
|
@@ -3236,7 +6442,7 @@ function createQueryBuilder(state) {
|
|
|
3236
6442
|
return this;
|
|
3237
6443
|
},
|
|
3238
6444
|
orWhereNotILike(column, pattern) {
|
|
3239
|
-
if (
|
|
6445
|
+
if (config3.dialect === "postgres") {
|
|
3240
6446
|
built = sql`${built} OR ${sql(String(column))} NOT ILIKE ${pattern}`;
|
|
3241
6447
|
addWhereText("OR", `${String(column)} NOT ILIKE ?`);
|
|
3242
6448
|
} else {
|
|
@@ -3448,7 +6654,7 @@ function createQueryBuilder(state) {
|
|
|
3448
6654
|
return this;
|
|
3449
6655
|
},
|
|
3450
6656
|
inRandomOrder() {
|
|
3451
|
-
const rnd =
|
|
6657
|
+
const rnd = config3.sql.randomFunction === "RAND()" ? sql`RAND()` : sql`RANDOM()`;
|
|
3452
6658
|
built = sql`${built} ORDER BY ${rnd}`;
|
|
3453
6659
|
return this;
|
|
3454
6660
|
},
|
|
@@ -3457,12 +6663,12 @@ function createQueryBuilder(state) {
|
|
|
3457
6663
|
return this;
|
|
3458
6664
|
},
|
|
3459
6665
|
latest(column) {
|
|
3460
|
-
const col = column ??
|
|
6666
|
+
const col = column ?? config3.timestamps.defaultOrderColumn;
|
|
3461
6667
|
built = sql`${built} ORDER BY ${sql(String(col))} DESC`;
|
|
3462
6668
|
return this;
|
|
3463
6669
|
},
|
|
3464
6670
|
oldest(column) {
|
|
3465
|
-
const col = column ??
|
|
6671
|
+
const col = column ?? config3.timestamps.defaultOrderColumn;
|
|
3466
6672
|
built = sql`${built} ORDER BY ${sql(String(col))} ASC`;
|
|
3467
6673
|
return this;
|
|
3468
6674
|
},
|
|
@@ -3529,7 +6735,7 @@ function createQueryBuilder(state) {
|
|
|
3529
6735
|
for (const jt of joinedTables) {
|
|
3530
6736
|
const cols = Object.keys(schema[jt]?.columns ?? {});
|
|
3531
6737
|
for (const c of cols) {
|
|
3532
|
-
const alias =
|
|
6738
|
+
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
6739
|
parts.push(sql`${sql(`${jt}.${c}`)} AS ${sql(alias)}`);
|
|
3534
6740
|
}
|
|
3535
6741
|
}
|
|
@@ -3717,7 +6923,7 @@ function createQueryBuilder(state) {
|
|
|
3717
6923
|
onlyTrashed() {
|
|
3718
6924
|
includeTrashed = true;
|
|
3719
6925
|
onlyTrashed = true;
|
|
3720
|
-
const softDeleteColumn =
|
|
6926
|
+
const softDeleteColumn = config3.softDeletes?.column || "deleted_at";
|
|
3721
6927
|
if (text.includes("WHERE")) {
|
|
3722
6928
|
text = text.replace(/WHERE/, `WHERE ${table}.${softDeleteColumn} IS NOT NULL AND`);
|
|
3723
6929
|
} else {
|
|
@@ -3777,7 +6983,7 @@ function createQueryBuilder(state) {
|
|
|
3777
6983
|
},
|
|
3778
6984
|
async get() {
|
|
3779
6985
|
built = whereParams.length > 0 ? _sql.unsafe(text, whereParams) : _sql.unsafe(text);
|
|
3780
|
-
if (!
|
|
6986
|
+
if (!config3.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !config3.hooks) {
|
|
3781
6987
|
const stmt = built._stmt;
|
|
3782
6988
|
const params = built._params;
|
|
3783
6989
|
if (stmt) {
|
|
@@ -3785,12 +6991,12 @@ function createQueryBuilder(state) {
|
|
|
3785
6991
|
}
|
|
3786
6992
|
return built.execute();
|
|
3787
6993
|
}
|
|
3788
|
-
if (!
|
|
6994
|
+
if (!config3.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal) {
|
|
3789
6995
|
return runWithHooks(built, "select");
|
|
3790
6996
|
}
|
|
3791
6997
|
let finalQuery = built;
|
|
3792
|
-
if (
|
|
3793
|
-
const col =
|
|
6998
|
+
if (config3.softDeletes?.enabled && config3.softDeletes.defaultFilter && !includeTrashed) {
|
|
6999
|
+
const col = config3.softDeletes.column;
|
|
3794
7000
|
const tbl = String(table);
|
|
3795
7001
|
const hasCol = schema ? Boolean(schema[tbl]?.columns?.[col]) : true;
|
|
3796
7002
|
if (hasCol && !SQL_PATTERNS.DELETED_AT.test(text)) {
|
|
@@ -3921,7 +7127,7 @@ function createQueryBuilder(state) {
|
|
|
3921
7127
|
return this;
|
|
3922
7128
|
},
|
|
3923
7129
|
sharedLock() {
|
|
3924
|
-
const syntax =
|
|
7130
|
+
const syntax = config3.sql.sharedLockSyntax === "LOCK IN SHARE MODE" ? sql`LOCK IN SHARE MODE` : sql`FOR SHARE`;
|
|
3925
7131
|
built = sql`${built} ${syntax}`;
|
|
3926
7132
|
return this;
|
|
3927
7133
|
},
|
|
@@ -3989,7 +7195,7 @@ function createQueryBuilder(state) {
|
|
|
3989
7195
|
}
|
|
3990
7196
|
return {
|
|
3991
7197
|
configure(opts) {
|
|
3992
|
-
Object.assign(
|
|
7198
|
+
Object.assign(config3, opts);
|
|
3993
7199
|
return this;
|
|
3994
7200
|
},
|
|
3995
7201
|
id(name) {
|
|
@@ -4417,13 +7623,13 @@ function createQueryBuilder(state) {
|
|
|
4417
7623
|
},
|
|
4418
7624
|
async execute() {
|
|
4419
7625
|
try {
|
|
4420
|
-
await
|
|
7626
|
+
await config3.hooks?.beforeDelete?.({ table: String(table), where: whereCondition });
|
|
4421
7627
|
} catch (err) {
|
|
4422
7628
|
throw err;
|
|
4423
7629
|
}
|
|
4424
7630
|
const result = await runWithHooks(built, "delete");
|
|
4425
7631
|
try {
|
|
4426
|
-
await
|
|
7632
|
+
await config3.hooks?.afterDelete?.({ table: String(table), where: whereCondition, result });
|
|
4427
7633
|
} catch {}
|
|
4428
7634
|
return result;
|
|
4429
7635
|
}
|
|
@@ -4457,7 +7663,7 @@ function createQueryBuilder(state) {
|
|
|
4457
7663
|
return _sql(strings, ...values).simple();
|
|
4458
7664
|
},
|
|
4459
7665
|
async advisoryLock(key) {
|
|
4460
|
-
if (
|
|
7666
|
+
if (config3.dialect !== "postgres")
|
|
4461
7667
|
return;
|
|
4462
7668
|
const s = String(key);
|
|
4463
7669
|
let hash = 7;
|
|
@@ -4468,7 +7674,7 @@ function createQueryBuilder(state) {
|
|
|
4468
7674
|
await runWithHooks(q, "raw");
|
|
4469
7675
|
},
|
|
4470
7676
|
async tryAdvisoryLock(key) {
|
|
4471
|
-
if (
|
|
7677
|
+
if (config3.dialect !== "postgres")
|
|
4472
7678
|
return false;
|
|
4473
7679
|
const s = String(key);
|
|
4474
7680
|
let hash = 7;
|
|
@@ -4520,7 +7726,7 @@ function createQueryBuilder(state) {
|
|
|
4520
7726
|
for (let i = 0;i < attempts; i++) {
|
|
4521
7727
|
if (await this.ping())
|
|
4522
7728
|
return;
|
|
4523
|
-
await new Promise((
|
|
7729
|
+
await new Promise((resolve8) => setTimeout(resolve8, delay));
|
|
4524
7730
|
}
|
|
4525
7731
|
throw new Error("Database not ready after waiting");
|
|
4526
7732
|
},
|
|
@@ -4610,7 +7816,7 @@ function createQueryBuilder(state) {
|
|
|
4610
7816
|
return built.execute();
|
|
4611
7817
|
},
|
|
4612
7818
|
async insertGetId(table, values, idColumn = "id") {
|
|
4613
|
-
if (
|
|
7819
|
+
if (config3.dialect === "mysql") {
|
|
4614
7820
|
const insertQuery = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)}`;
|
|
4615
7821
|
const result = await insertQuery.execute();
|
|
4616
7822
|
if (result && typeof result === "object" && "insertId" in result) {
|
|
@@ -4685,23 +7891,23 @@ function createQueryBuilder(state) {
|
|
|
4685
7891
|
async rawQuery(query) {
|
|
4686
7892
|
const start = Date.now();
|
|
4687
7893
|
try {
|
|
4688
|
-
|
|
7894
|
+
config3.hooks?.onQueryStart?.({ sql: query, kind: "raw" });
|
|
4689
7895
|
const res = await bunSql.unsafe(query);
|
|
4690
|
-
|
|
7896
|
+
config3.hooks?.onQueryEnd?.({ sql: query, durationMs: Date.now() - start, kind: "raw" });
|
|
4691
7897
|
return res;
|
|
4692
7898
|
} catch (err) {
|
|
4693
|
-
|
|
7899
|
+
config3.hooks?.onQueryError?.({ sql: query, error: err, durationMs: Date.now() - start, kind: "raw" });
|
|
4694
7900
|
throw err;
|
|
4695
7901
|
}
|
|
4696
7902
|
},
|
|
4697
7903
|
async create(table, values) {
|
|
4698
7904
|
const pk = meta?.primaryKeys[String(table)] ?? "id";
|
|
4699
7905
|
try {
|
|
4700
|
-
await
|
|
7906
|
+
await config3.hooks?.beforeCreate?.({ table: String(table), data: values });
|
|
4701
7907
|
} catch (err) {
|
|
4702
7908
|
throw err;
|
|
4703
7909
|
}
|
|
4704
|
-
if (
|
|
7910
|
+
if (config3.dialect === "postgres") {
|
|
4705
7911
|
const q = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)} RETURNING ${bunSql(String(pk))} as id`;
|
|
4706
7912
|
const [result] = await q.execute();
|
|
4707
7913
|
console.log("resultId", result);
|
|
@@ -4717,7 +7923,7 @@ function createQueryBuilder(state) {
|
|
|
4717
7923
|
throw new Error(`create() failed to retrieve inserted row for table ${String(table)} with id ${result.id}`);
|
|
4718
7924
|
}
|
|
4719
7925
|
try {
|
|
4720
|
-
await
|
|
7926
|
+
await config3.hooks?.afterCreate?.({ table: String(table), data: values, result: row });
|
|
4721
7927
|
} catch {}
|
|
4722
7928
|
return row;
|
|
4723
7929
|
} else {
|
|
@@ -4732,7 +7938,7 @@ function createQueryBuilder(state) {
|
|
|
4732
7938
|
throw new Error(`create() failed to retrieve inserted row for table ${String(table)} with id ${id}`);
|
|
4733
7939
|
}
|
|
4734
7940
|
try {
|
|
4735
|
-
await
|
|
7941
|
+
await config3.hooks?.afterCreate?.({ table: String(table), data: values, result: row });
|
|
4736
7942
|
} catch {}
|
|
4737
7943
|
return row;
|
|
4738
7944
|
}
|
|
@@ -4995,7 +8201,7 @@ var init_cache = __esm(async () => {
|
|
|
4995
8201
|
});
|
|
4996
8202
|
|
|
4997
8203
|
// src/actions/console.ts
|
|
4998
|
-
import
|
|
8204
|
+
import process15 from "process";
|
|
4999
8205
|
import { createInterface } from "readline";
|
|
5000
8206
|
async function startConsole() {
|
|
5001
8207
|
console.log("-- Query Builder Interactive Console");
|
|
@@ -5004,8 +8210,8 @@ async function startConsole() {
|
|
|
5004
8210
|
console.log();
|
|
5005
8211
|
const qb = createQueryBuilder();
|
|
5006
8212
|
const rl = createInterface({
|
|
5007
|
-
input:
|
|
5008
|
-
output:
|
|
8213
|
+
input: process15.stdin,
|
|
8214
|
+
output: process15.stdout,
|
|
5009
8215
|
prompt: "qb> "
|
|
5010
8216
|
});
|
|
5011
8217
|
let multilineBuffer = "";
|
|
@@ -5056,7 +8262,7 @@ Tips:
|
|
|
5056
8262
|
if (cmd === ".exit" || cmd === ".quit") {
|
|
5057
8263
|
console.log("Goodbye!");
|
|
5058
8264
|
rl.close();
|
|
5059
|
-
|
|
8265
|
+
process15.exit(0);
|
|
5060
8266
|
} else if (cmd === ".help") {
|
|
5061
8267
|
console.log(helpText);
|
|
5062
8268
|
} else if (cmd === ".clear") {
|
|
@@ -5127,7 +8333,7 @@ Tips:
|
|
|
5127
8333
|
rl.on("close", () => {
|
|
5128
8334
|
console.log(`
|
|
5129
8335
|
Goodbye!`);
|
|
5130
|
-
|
|
8336
|
+
process15.exit(0);
|
|
5131
8337
|
});
|
|
5132
8338
|
rl.prompt();
|
|
5133
8339
|
}
|
|
@@ -5139,7 +8345,7 @@ var init_console = __esm(async () => {
|
|
|
5139
8345
|
});
|
|
5140
8346
|
|
|
5141
8347
|
// src/actions/data.ts
|
|
5142
|
-
import { existsSync as
|
|
8348
|
+
import { existsSync as existsSync6, readFileSync, writeFileSync as writeFileSync6 } from "fs";
|
|
5143
8349
|
async function exportData(tableName, options = {}) {
|
|
5144
8350
|
const format = options.format || "json";
|
|
5145
8351
|
const output = options.output || `${tableName}.${format}`;
|
|
@@ -5160,11 +8366,11 @@ async function exportData(tableName, options = {}) {
|
|
|
5160
8366
|
const data = await query.execute();
|
|
5161
8367
|
console.log(`-- Retrieved ${data.length} rows`);
|
|
5162
8368
|
if (format === "json") {
|
|
5163
|
-
|
|
8369
|
+
writeFileSync6(output, JSON.stringify(data, null, 2));
|
|
5164
8370
|
console.log(`-- \u2713 Exported to JSON: ${output}`);
|
|
5165
8371
|
} else if (format === "csv") {
|
|
5166
8372
|
if (data.length === 0) {
|
|
5167
|
-
|
|
8373
|
+
writeFileSync6(output, "");
|
|
5168
8374
|
console.log(`-- \u2713 Exported to CSV: ${output} (empty)`);
|
|
5169
8375
|
return;
|
|
5170
8376
|
}
|
|
@@ -5185,7 +8391,7 @@ async function exportData(tableName, options = {}) {
|
|
|
5185
8391
|
});
|
|
5186
8392
|
csvLines.push(values.join(","));
|
|
5187
8393
|
}
|
|
5188
|
-
|
|
8394
|
+
writeFileSync6(output, csvLines.join(`
|
|
5189
8395
|
`));
|
|
5190
8396
|
console.log(`-- \u2713 Exported to CSV: ${output}`);
|
|
5191
8397
|
} else if (format === "sql") {
|
|
@@ -5204,7 +8410,7 @@ async function exportData(tableName, options = {}) {
|
|
|
5204
8410
|
});
|
|
5205
8411
|
sqlLines.push(`INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${values.join(", ")});`);
|
|
5206
8412
|
}
|
|
5207
|
-
|
|
8413
|
+
writeFileSync6(output, sqlLines.join(`
|
|
5208
8414
|
`));
|
|
5209
8415
|
console.log(`-- \u2713 Exported to SQL: ${output}`);
|
|
5210
8416
|
}
|
|
@@ -5214,7 +8420,7 @@ async function exportData(tableName, options = {}) {
|
|
|
5214
8420
|
}
|
|
5215
8421
|
}
|
|
5216
8422
|
async function importData(tableName, filePath, options = {}) {
|
|
5217
|
-
if (!
|
|
8423
|
+
if (!existsSync6(filePath)) {
|
|
5218
8424
|
console.error(`-- File not found: ${filePath}`);
|
|
5219
8425
|
throw new Error(`File not found: ${filePath}`);
|
|
5220
8426
|
}
|
|
@@ -5283,7 +8489,7 @@ async function importData(tableName, filePath, options = {}) {
|
|
|
5283
8489
|
}
|
|
5284
8490
|
}
|
|
5285
8491
|
async function dumpDatabase(options = {}) {
|
|
5286
|
-
const dialect =
|
|
8492
|
+
const dialect = config3.dialect || "postgres";
|
|
5287
8493
|
const output = options.output || `dump-${Date.now()}.sql`;
|
|
5288
8494
|
const tablesToDump = options.tables?.split(",").map((t) => t.trim());
|
|
5289
8495
|
console.log("-- Dumping database");
|
|
@@ -5358,7 +8564,7 @@ async function dumpDatabase(options = {}) {
|
|
|
5358
8564
|
}
|
|
5359
8565
|
sqlLines.push("");
|
|
5360
8566
|
}
|
|
5361
|
-
|
|
8567
|
+
writeFileSync6(output, sqlLines.join(`
|
|
5362
8568
|
`));
|
|
5363
8569
|
console.log();
|
|
5364
8570
|
console.log(`-- \u2713 Database dump saved to: ${output}`);
|
|
@@ -5376,8 +8582,8 @@ var init_data = __esm(async () => {
|
|
|
5376
8582
|
|
|
5377
8583
|
// src/actions/db-info.ts
|
|
5378
8584
|
async function dbInfo() {
|
|
5379
|
-
const dialect =
|
|
5380
|
-
const database =
|
|
8585
|
+
const dialect = config3.dialect || "postgres";
|
|
8586
|
+
const database = config3.database?.database || "unknown";
|
|
5381
8587
|
console.log("-- Database Information");
|
|
5382
8588
|
console.log(`-- Dialect: ${dialect}`);
|
|
5383
8589
|
console.log(`-- Database: ${database}`);
|
|
@@ -5508,9 +8714,9 @@ var init_db_info = __esm(async () => {
|
|
|
5508
8714
|
});
|
|
5509
8715
|
|
|
5510
8716
|
// src/actions/db-optimize.ts
|
|
5511
|
-
import
|
|
8717
|
+
import process16 from "process";
|
|
5512
8718
|
async function dbOptimize(options = {}) {
|
|
5513
|
-
const dialect = options.dialect ||
|
|
8719
|
+
const dialect = options.dialect || process16.env.DB_DIALECT || "postgres";
|
|
5514
8720
|
const aggressive = options.aggressive || false;
|
|
5515
8721
|
if (options.verbose) {
|
|
5516
8722
|
console.log(`Optimizing ${dialect} database${aggressive ? " (aggressive mode)" : ""}...`);
|
|
@@ -5550,7 +8756,7 @@ async function dbOptimize(options = {}) {
|
|
|
5550
8756
|
await bunSql`ANALYZE TABLE ${bunSql(table)}`;
|
|
5551
8757
|
}
|
|
5552
8758
|
} else {
|
|
5553
|
-
const dbName =
|
|
8759
|
+
const dbName = process16.env.DB_NAME || "test";
|
|
5554
8760
|
const tables = await bunSql`
|
|
5555
8761
|
SELECT table_name
|
|
5556
8762
|
FROM information_schema.tables
|
|
@@ -5602,9 +8808,9 @@ var init_db_optimize = __esm(async () => {
|
|
|
5602
8808
|
});
|
|
5603
8809
|
|
|
5604
8810
|
// src/actions/db-wipe.ts
|
|
5605
|
-
import
|
|
8811
|
+
import process17 from "process";
|
|
5606
8812
|
async function dbWipe(options = {}) {
|
|
5607
|
-
const dialect = options.dialect ||
|
|
8813
|
+
const dialect = options.dialect || process17.env.DB_DIALECT || "postgres";
|
|
5608
8814
|
if (options.verbose) {
|
|
5609
8815
|
console.log(`Wiping all tables from ${dialect} database...`);
|
|
5610
8816
|
}
|
|
@@ -5618,7 +8824,7 @@ async function dbWipe(options = {}) {
|
|
|
5618
8824
|
`;
|
|
5619
8825
|
tables = result.map((row) => row.tablename);
|
|
5620
8826
|
} else if (dialect === "mysql") {
|
|
5621
|
-
const dbName =
|
|
8827
|
+
const dbName = process17.env.DB_NAME || "test";
|
|
5622
8828
|
const result = await bunSql`
|
|
5623
8829
|
SELECT table_name
|
|
5624
8830
|
FROM information_schema.tables
|
|
@@ -5703,7 +8909,7 @@ var init_file = __esm(async () => {
|
|
|
5703
8909
|
|
|
5704
8910
|
// src/actions/inspect.ts
|
|
5705
8911
|
async function inspectTable(tableName, options = {}) {
|
|
5706
|
-
const dialect =
|
|
8912
|
+
const dialect = config3.dialect || "postgres";
|
|
5707
8913
|
const verbose = options.verbose ?? true;
|
|
5708
8914
|
if (verbose) {
|
|
5709
8915
|
console.log(`-- Inspecting table: ${tableName}`);
|
|
@@ -5912,31 +9118,31 @@ var init_introspect = __esm(async () => {
|
|
|
5912
9118
|
});
|
|
5913
9119
|
|
|
5914
9120
|
// src/actions/make-model.ts
|
|
5915
|
-
import { existsSync as
|
|
5916
|
-
import { dirname as
|
|
5917
|
-
import
|
|
9121
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
9122
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
9123
|
+
import process18 from "process";
|
|
5918
9124
|
function findWorkspaceRoot(startPath) {
|
|
5919
9125
|
let currentPath = startPath;
|
|
5920
|
-
while (currentPath !==
|
|
5921
|
-
if (
|
|
9126
|
+
while (currentPath !== dirname4(currentPath)) {
|
|
9127
|
+
if (existsSync9(join4(currentPath, "package.json"))) {
|
|
5922
9128
|
return currentPath;
|
|
5923
9129
|
}
|
|
5924
|
-
currentPath =
|
|
9130
|
+
currentPath = dirname4(currentPath);
|
|
5925
9131
|
}
|
|
5926
|
-
return
|
|
9132
|
+
return process18.cwd();
|
|
5927
9133
|
}
|
|
5928
9134
|
async function makeModel(name, options = {}) {
|
|
5929
|
-
const workspaceRoot = findWorkspaceRoot(
|
|
5930
|
-
const modelsDir = options.dir ||
|
|
5931
|
-
if (!
|
|
5932
|
-
|
|
9135
|
+
const workspaceRoot = findWorkspaceRoot(process18.cwd());
|
|
9136
|
+
const modelsDir = options.dir || join4(workspaceRoot, "app/Models");
|
|
9137
|
+
if (!existsSync9(modelsDir)) {
|
|
9138
|
+
mkdirSync4(modelsDir, { recursive: true });
|
|
5933
9139
|
console.log(`-- Created models directory: ${modelsDir}`);
|
|
5934
9140
|
}
|
|
5935
9141
|
const className = name.charAt(0).toUpperCase() + name.slice(1);
|
|
5936
9142
|
const tableName = options.table || `${name.toLowerCase()}s`;
|
|
5937
9143
|
const fileName = `${className}.ts`;
|
|
5938
|
-
const filePath =
|
|
5939
|
-
if (
|
|
9144
|
+
const filePath = join4(modelsDir, fileName);
|
|
9145
|
+
if (existsSync9(filePath)) {
|
|
5940
9146
|
console.error(`-- Model already exists: ${filePath}`);
|
|
5941
9147
|
throw new Error(`Model already exists: ${filePath}`);
|
|
5942
9148
|
}
|
|
@@ -5963,7 +9169,7 @@ ${timestampFields}
|
|
|
5963
9169
|
|
|
5964
9170
|
export default model
|
|
5965
9171
|
`;
|
|
5966
|
-
|
|
9172
|
+
writeFileSync8(filePath, template);
|
|
5967
9173
|
console.log(`-- \u2713 Created model: ${filePath}`);
|
|
5968
9174
|
console.log(`-- Table: ${tableName}`);
|
|
5969
9175
|
}
|
|
@@ -6441,42 +9647,42 @@ __export(exports_migrate, {
|
|
|
6441
9647
|
deleteMigrationFiles: () => deleteMigrationFiles,
|
|
6442
9648
|
copyModelsToGenerated: () => copyModelsToGenerated
|
|
6443
9649
|
});
|
|
6444
|
-
import { copyFileSync, existsSync as
|
|
9650
|
+
import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync6, mkdtempSync, readdirSync as readdirSync4, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
|
|
6445
9651
|
import { tmpdir } from "os";
|
|
6446
|
-
import { join as
|
|
6447
|
-
import
|
|
9652
|
+
import { join as join6 } from "path";
|
|
9653
|
+
import process19 from "process";
|
|
6448
9654
|
function getWorkspaceRoot() {
|
|
6449
|
-
return
|
|
9655
|
+
return process19.cwd();
|
|
6450
9656
|
}
|
|
6451
9657
|
function ensureSqlDirectory(workspaceRoot) {
|
|
6452
9658
|
const sqlDir = getSqlDirectory(workspaceRoot);
|
|
6453
|
-
if (!
|
|
6454
|
-
|
|
9659
|
+
if (!existsSync11(sqlDir)) {
|
|
9660
|
+
mkdirSync6(sqlDir, { recursive: true });
|
|
6455
9661
|
console.log(`-- Created SQL directory: ${sqlDir}`);
|
|
6456
9662
|
}
|
|
6457
9663
|
return sqlDir;
|
|
6458
9664
|
}
|
|
6459
9665
|
async function generateMigration(dir, opts = {}) {
|
|
6460
9666
|
if (!dir) {
|
|
6461
|
-
dir =
|
|
9667
|
+
dir = join6(process19.cwd(), "app/Models");
|
|
6462
9668
|
}
|
|
6463
|
-
const dialect = opts.dialect ||
|
|
9669
|
+
const dialect = opts.dialect || config3.dialect || "postgres";
|
|
6464
9670
|
const workspaceRoot = getWorkspaceRoot();
|
|
6465
9671
|
const models = await loadModels({ modelsDir: dir });
|
|
6466
9672
|
const plan = buildMigrationPlan(models, { dialect });
|
|
6467
|
-
const defaultStatePath =
|
|
9673
|
+
const defaultStatePath = join6(dir, `.qb-migrations.${dialect}.json`);
|
|
6468
9674
|
const statePath = String(opts.state || defaultStatePath);
|
|
6469
9675
|
let previous;
|
|
6470
9676
|
if (!opts.full) {
|
|
6471
|
-
const generatedDir =
|
|
6472
|
-
if (
|
|
9677
|
+
const generatedDir = join6(workspaceRoot, "generated");
|
|
9678
|
+
if (existsSync11(generatedDir)) {
|
|
6473
9679
|
try {
|
|
6474
9680
|
const oldModels = await loadModels({ modelsDir: generatedDir });
|
|
6475
9681
|
previous = buildMigrationPlan(oldModels, { dialect });
|
|
6476
9682
|
console.log("-- Comparing with models from generated/ directory");
|
|
6477
9683
|
} catch (err) {
|
|
6478
9684
|
console.log("-- No previous models found in generated/ directory, checking state file", err);
|
|
6479
|
-
if (
|
|
9685
|
+
if (existsSync11(statePath)) {
|
|
6480
9686
|
try {
|
|
6481
9687
|
const raw = readFileSync2(statePath, "utf8");
|
|
6482
9688
|
const parsed = JSON.parse(raw);
|
|
@@ -6484,7 +9690,7 @@ async function generateMigration(dir, opts = {}) {
|
|
|
6484
9690
|
} catch {}
|
|
6485
9691
|
}
|
|
6486
9692
|
}
|
|
6487
|
-
} else if (
|
|
9693
|
+
} else if (existsSync11(statePath)) {
|
|
6488
9694
|
try {
|
|
6489
9695
|
const raw = readFileSync2(statePath, "utf8");
|
|
6490
9696
|
const parsed = JSON.parse(raw);
|
|
@@ -6497,16 +9703,16 @@ async function generateMigration(dir, opts = {}) {
|
|
|
6497
9703
|
`);
|
|
6498
9704
|
const hasChanges = sqlStatements.some((stmt) => /\b(?:CREATE|ALTER)\b/i.test(stmt));
|
|
6499
9705
|
if (opts.apply) {
|
|
6500
|
-
const dirPath = mkdtempSync(
|
|
6501
|
-
const filePath =
|
|
9706
|
+
const dirPath = mkdtempSync(join6(tmpdir(), "qb-migrate-"));
|
|
9707
|
+
const filePath = join6(dirPath, "migration.sql");
|
|
6502
9708
|
try {
|
|
6503
9709
|
if (hasChanges) {
|
|
6504
|
-
|
|
9710
|
+
writeFileSync9(filePath, sql);
|
|
6505
9711
|
console.log("-- Migration applied");
|
|
6506
9712
|
} else {
|
|
6507
9713
|
console.log("-- No changes; nothing to apply");
|
|
6508
9714
|
}
|
|
6509
|
-
|
|
9715
|
+
writeFileSync9(statePath, JSON.stringify({ plan, hash: hashMigrationPlan(plan), updatedAt: new Date().toISOString() }, null, 2));
|
|
6510
9716
|
} catch (err) {
|
|
6511
9717
|
console.error("-- Migration failed:", err);
|
|
6512
9718
|
throw err;
|
|
@@ -6517,12 +9723,12 @@ async function generateMigration(dir, opts = {}) {
|
|
|
6517
9723
|
}
|
|
6518
9724
|
async function executeMigration(dir) {
|
|
6519
9725
|
if (!dir) {
|
|
6520
|
-
dir =
|
|
9726
|
+
dir = join6(process19.cwd(), "app/Models");
|
|
6521
9727
|
}
|
|
6522
9728
|
const workspaceRoot = getWorkspaceRoot();
|
|
6523
9729
|
const sqlDir = ensureSqlDirectory(workspaceRoot);
|
|
6524
|
-
const dialect =
|
|
6525
|
-
const files =
|
|
9730
|
+
const dialect = config3.dialect || "postgres";
|
|
9731
|
+
const files = readdirSync4(sqlDir);
|
|
6526
9732
|
const scriptFiles = files.filter((file2) => file2.endsWith(".sql")).sort();
|
|
6527
9733
|
if (scriptFiles.length === 0) {
|
|
6528
9734
|
console.log("-- No migration files found to execute");
|
|
@@ -6549,7 +9755,7 @@ async function executeMigration(dir) {
|
|
|
6549
9755
|
}
|
|
6550
9756
|
console.log(`-- Executing ${totalPending} migrations (${permanentMigrations.length} permanent, ${transientMigrations.length} transient)`);
|
|
6551
9757
|
for (const file2 of permanentMigrations) {
|
|
6552
|
-
const filePath =
|
|
9758
|
+
const filePath = join6(sqlDir, file2);
|
|
6553
9759
|
console.log(`-- Executing: ${file2}`);
|
|
6554
9760
|
try {
|
|
6555
9761
|
await qb.file(filePath);
|
|
@@ -6561,7 +9767,7 @@ async function executeMigration(dir) {
|
|
|
6561
9767
|
}
|
|
6562
9768
|
}
|
|
6563
9769
|
for (const file2 of transientMigrations) {
|
|
6564
|
-
const filePath =
|
|
9770
|
+
const filePath = join6(sqlDir, file2);
|
|
6565
9771
|
console.log(`-- Executing: ${file2} (transient)`);
|
|
6566
9772
|
try {
|
|
6567
9773
|
await qb.file(filePath);
|
|
@@ -6582,7 +9788,7 @@ async function executeMigration(dir) {
|
|
|
6582
9788
|
}
|
|
6583
9789
|
async function resetDatabase(dir, opts = {}) {
|
|
6584
9790
|
if (!dir) {
|
|
6585
|
-
dir =
|
|
9791
|
+
dir = join6(process19.cwd(), "app/Models");
|
|
6586
9792
|
}
|
|
6587
9793
|
const dialect = opts.dialect || "postgres";
|
|
6588
9794
|
const driver = getDialectDriver(dialect);
|
|
@@ -6669,24 +9875,24 @@ async function resetDatabase(dir, opts = {}) {
|
|
|
6669
9875
|
}
|
|
6670
9876
|
async function deleteMigrationFiles(dir, workspaceRoot, opts = {}) {
|
|
6671
9877
|
if (!dir) {
|
|
6672
|
-
dir =
|
|
9878
|
+
dir = join6(process19.cwd(), "app/Models");
|
|
6673
9879
|
}
|
|
6674
9880
|
if (!workspaceRoot) {
|
|
6675
9881
|
workspaceRoot = getWorkspaceRoot();
|
|
6676
9882
|
}
|
|
6677
9883
|
const dialect = String(opts.dialect || "postgres");
|
|
6678
|
-
const defaultStatePath =
|
|
9884
|
+
const defaultStatePath = join6(dir, `.qb-migrations.${dialect}.json`);
|
|
6679
9885
|
const statePath = String(opts.state || defaultStatePath);
|
|
6680
|
-
if (
|
|
9886
|
+
if (existsSync11(statePath)) {
|
|
6681
9887
|
unlinkSync(statePath);
|
|
6682
9888
|
console.log(`-- Removed migration state file: ${statePath}`);
|
|
6683
9889
|
}
|
|
6684
9890
|
const sqlDir = getSqlDirectory(workspaceRoot);
|
|
6685
|
-
if (
|
|
6686
|
-
const sqlFiles =
|
|
9891
|
+
if (existsSync11(sqlDir)) {
|
|
9892
|
+
const sqlFiles = readdirSync4(sqlDir);
|
|
6687
9893
|
const migrationFiles = sqlFiles.filter((file2) => file2.endsWith(".sql"));
|
|
6688
9894
|
for (const file2 of migrationFiles) {
|
|
6689
|
-
const filePath =
|
|
9895
|
+
const filePath = join6(sqlDir, file2);
|
|
6690
9896
|
unlinkSync(filePath);
|
|
6691
9897
|
console.log(`-- Removed migration file: ${file2}`);
|
|
6692
9898
|
}
|
|
@@ -6695,26 +9901,26 @@ async function deleteMigrationFiles(dir, workspaceRoot, opts = {}) {
|
|
|
6695
9901
|
}
|
|
6696
9902
|
async function copyModelsToGenerated(dir, workspaceRoot) {
|
|
6697
9903
|
if (!dir) {
|
|
6698
|
-
dir =
|
|
9904
|
+
dir = join6(process19.cwd(), "app/Models");
|
|
6699
9905
|
}
|
|
6700
9906
|
if (!workspaceRoot) {
|
|
6701
9907
|
workspaceRoot = getWorkspaceRoot();
|
|
6702
9908
|
}
|
|
6703
9909
|
try {
|
|
6704
|
-
const generatedDir =
|
|
6705
|
-
if (!
|
|
6706
|
-
|
|
9910
|
+
const generatedDir = join6(workspaceRoot, "generated");
|
|
9911
|
+
if (!existsSync11(generatedDir)) {
|
|
9912
|
+
mkdirSync6(generatedDir, { recursive: true });
|
|
6707
9913
|
console.log("-- Created generated directory");
|
|
6708
9914
|
}
|
|
6709
|
-
const files =
|
|
9915
|
+
const files = readdirSync4(dir);
|
|
6710
9916
|
const modelFiles = files.filter((file2) => file2.endsWith(".ts") || file2.endsWith(".js"));
|
|
6711
9917
|
if (modelFiles.length === 0) {
|
|
6712
9918
|
console.log("-- No model files found to copy");
|
|
6713
9919
|
return;
|
|
6714
9920
|
}
|
|
6715
9921
|
for (const file2 of modelFiles) {
|
|
6716
|
-
const sourcePath =
|
|
6717
|
-
const destPath =
|
|
9922
|
+
const sourcePath = join6(dir, file2);
|
|
9923
|
+
const destPath = join6(generatedDir, file2);
|
|
6718
9924
|
copyFileSync(sourcePath, destPath);
|
|
6719
9925
|
}
|
|
6720
9926
|
console.log("-- Model files copied successfully");
|
|
@@ -6727,7 +9933,7 @@ function getSqlDirectory(workspaceRoot) {
|
|
|
6727
9933
|
if (!workspaceRoot) {
|
|
6728
9934
|
workspaceRoot = getWorkspaceRoot();
|
|
6729
9935
|
}
|
|
6730
|
-
return
|
|
9936
|
+
return join6(workspaceRoot, "database", "sql");
|
|
6731
9937
|
}
|
|
6732
9938
|
async function createMigrationsTable(qb, dialect) {
|
|
6733
9939
|
const driver = getDialectDriver(dialect);
|
|
@@ -6779,24 +9985,24 @@ var init_migrate_generate = __esm(async () => {
|
|
|
6779
9985
|
});
|
|
6780
9986
|
|
|
6781
9987
|
// src/actions/migrate-rollback.ts
|
|
6782
|
-
import { existsSync as
|
|
6783
|
-
import { dirname as
|
|
6784
|
-
import
|
|
9988
|
+
import { existsSync as existsSync12, unlinkSync as unlinkSync2 } from "fs";
|
|
9989
|
+
import { dirname as dirname6, join as join7 } from "path";
|
|
9990
|
+
import process20 from "process";
|
|
6785
9991
|
function findWorkspaceRoot2(startPath) {
|
|
6786
9992
|
let currentPath = startPath;
|
|
6787
|
-
while (currentPath !==
|
|
6788
|
-
if (
|
|
9993
|
+
while (currentPath !== dirname6(currentPath)) {
|
|
9994
|
+
if (existsSync12(join7(currentPath, "package.json"))) {
|
|
6789
9995
|
return currentPath;
|
|
6790
9996
|
}
|
|
6791
|
-
currentPath =
|
|
9997
|
+
currentPath = dirname6(currentPath);
|
|
6792
9998
|
}
|
|
6793
|
-
return
|
|
9999
|
+
return process20.cwd();
|
|
6794
10000
|
}
|
|
6795
10001
|
function getSqlDirectory2(workspaceRoot) {
|
|
6796
10002
|
if (!workspaceRoot) {
|
|
6797
|
-
workspaceRoot = findWorkspaceRoot2(
|
|
10003
|
+
workspaceRoot = findWorkspaceRoot2(process20.cwd());
|
|
6798
10004
|
}
|
|
6799
|
-
return
|
|
10005
|
+
return join7(workspaceRoot, "sql");
|
|
6800
10006
|
}
|
|
6801
10007
|
async function migrateRollback(options = {}) {
|
|
6802
10008
|
const steps = options.steps || 1;
|
|
@@ -6835,8 +10041,8 @@ async function migrateRollback(options = {}) {
|
|
|
6835
10041
|
await qb.unsafe(deleteSql, [migration.migration]);
|
|
6836
10042
|
console.log(`-- \u2713 Removed migration record: ${migration.migration}`);
|
|
6837
10043
|
const sqlDir = getSqlDirectory2();
|
|
6838
|
-
const filePath =
|
|
6839
|
-
if (
|
|
10044
|
+
const filePath = join7(sqlDir, migration.migration);
|
|
10045
|
+
if (existsSync12(filePath)) {
|
|
6840
10046
|
unlinkSync2(filePath);
|
|
6841
10047
|
console.log(`-- \uD83D\uDDD1\uFE0F Deleted migration file: ${migration.migration}`);
|
|
6842
10048
|
}
|
|
@@ -6861,38 +10067,38 @@ var init_migrate_rollback = __esm(async () => {
|
|
|
6861
10067
|
});
|
|
6862
10068
|
|
|
6863
10069
|
// src/actions/migrate-status.ts
|
|
6864
|
-
import { existsSync as
|
|
6865
|
-
import { dirname as
|
|
6866
|
-
import
|
|
10070
|
+
import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
|
|
10071
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
10072
|
+
import process21 from "process";
|
|
6867
10073
|
function findWorkspaceRoot3(startPath) {
|
|
6868
10074
|
let currentPath = startPath;
|
|
6869
|
-
while (currentPath !==
|
|
6870
|
-
if (
|
|
10075
|
+
while (currentPath !== dirname7(currentPath)) {
|
|
10076
|
+
if (existsSync13(join8(currentPath, "package.json"))) {
|
|
6871
10077
|
return currentPath;
|
|
6872
10078
|
}
|
|
6873
|
-
currentPath =
|
|
10079
|
+
currentPath = dirname7(currentPath);
|
|
6874
10080
|
}
|
|
6875
|
-
return
|
|
10081
|
+
return process21.cwd();
|
|
6876
10082
|
}
|
|
6877
10083
|
function getSqlDirectory3(workspaceRoot) {
|
|
6878
10084
|
if (!workspaceRoot) {
|
|
6879
|
-
workspaceRoot = findWorkspaceRoot3(
|
|
10085
|
+
workspaceRoot = findWorkspaceRoot3(process21.cwd());
|
|
6880
10086
|
}
|
|
6881
|
-
return
|
|
10087
|
+
return join8(workspaceRoot, "sql");
|
|
6882
10088
|
}
|
|
6883
10089
|
async function migrateStatus() {
|
|
6884
|
-
const dialect =
|
|
10090
|
+
const dialect = config3.dialect || "postgres";
|
|
6885
10091
|
const driver = getDialectDriver(dialect);
|
|
6886
10092
|
const sqlDir = getSqlDirectory3();
|
|
6887
10093
|
console.log("-- Migration Status");
|
|
6888
10094
|
console.log(`-- Dialect: ${dialect}`);
|
|
6889
10095
|
console.log(`-- SQL directory: ${sqlDir}`);
|
|
6890
10096
|
console.log();
|
|
6891
|
-
if (!
|
|
10097
|
+
if (!existsSync13(sqlDir)) {
|
|
6892
10098
|
console.log("-- No SQL directory found. No migrations have been created yet.");
|
|
6893
10099
|
return [];
|
|
6894
10100
|
}
|
|
6895
|
-
const files =
|
|
10101
|
+
const files = readdirSync6(sqlDir);
|
|
6896
10102
|
const migrationFiles = files.filter((file2) => file2.endsWith(".sql")).sort();
|
|
6897
10103
|
if (migrationFiles.length === 0) {
|
|
6898
10104
|
console.log("-- No migration files found");
|
|
@@ -6970,13 +10176,13 @@ var init_migrate_status = __esm(async () => {
|
|
|
6970
10176
|
});
|
|
6971
10177
|
|
|
6972
10178
|
// src/actions/model-show.ts
|
|
6973
|
-
import { readdirSync as
|
|
6974
|
-
import { extname, join as
|
|
6975
|
-
import
|
|
10179
|
+
import { readdirSync as readdirSync7 } from "fs";
|
|
10180
|
+
import { extname, join as join9 } from "path";
|
|
10181
|
+
import process22 from "process";
|
|
6976
10182
|
async function modelShow(modelName, options = {}) {
|
|
6977
|
-
const dir = options.dir ||
|
|
10183
|
+
const dir = options.dir || join9(process22.cwd(), "app/Models");
|
|
6978
10184
|
try {
|
|
6979
|
-
const files =
|
|
10185
|
+
const files = readdirSync7(dir);
|
|
6980
10186
|
const modelFile = files.find((f) => {
|
|
6981
10187
|
const name = f.replace(extname(f), "");
|
|
6982
10188
|
return name.toLowerCase() === modelName.toLowerCase();
|
|
@@ -6986,7 +10192,7 @@ async function modelShow(modelName, options = {}) {
|
|
|
6986
10192
|
console.log(`Available models: ${files.filter((f) => [".ts", ".js"].includes(extname(f))).map((f) => f.replace(extname(f), "")).join(", ")}`);
|
|
6987
10193
|
return;
|
|
6988
10194
|
}
|
|
6989
|
-
const modelPath =
|
|
10195
|
+
const modelPath = join9(dir, modelFile);
|
|
6990
10196
|
const module = await import(modelPath);
|
|
6991
10197
|
const model = module.default || module[Object.keys(module)[0]];
|
|
6992
10198
|
if (!model || !model.name) {
|
|
@@ -7093,17 +10299,17 @@ var init_ping = __esm(async () => {
|
|
|
7093
10299
|
});
|
|
7094
10300
|
|
|
7095
10301
|
// src/actions/query-explain-all.ts
|
|
7096
|
-
import { readdirSync as
|
|
7097
|
-
import { extname as extname2, join as
|
|
10302
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
10303
|
+
import { extname as extname2, join as join10 } from "path";
|
|
7098
10304
|
async function queryExplainAll(path, options = {}) {
|
|
7099
10305
|
const results = [];
|
|
7100
10306
|
try {
|
|
7101
|
-
const
|
|
10307
|
+
const stat3 = statSync2(path);
|
|
7102
10308
|
let files = [];
|
|
7103
|
-
if (
|
|
7104
|
-
const allFiles =
|
|
7105
|
-
files = allFiles.filter((f) => extname2(f) === ".sql").map((f) =>
|
|
7106
|
-
} else if (
|
|
10309
|
+
if (stat3.isDirectory()) {
|
|
10310
|
+
const allFiles = readdirSync8(path);
|
|
10311
|
+
files = allFiles.filter((f) => extname2(f) === ".sql").map((f) => join10(path, f));
|
|
10312
|
+
} else if (stat3.isFile() && extname2(path) === ".sql") {
|
|
7107
10313
|
files = [path];
|
|
7108
10314
|
} else {
|
|
7109
10315
|
console.error("Path must be a .sql file or directory containing .sql files");
|
|
@@ -7186,11 +10392,11 @@ var init_query_explain_all = __esm(async () => {
|
|
|
7186
10392
|
});
|
|
7187
10393
|
|
|
7188
10394
|
// src/actions/relation-diagram.ts
|
|
7189
|
-
import { writeFileSync as
|
|
7190
|
-
import { join as
|
|
7191
|
-
import
|
|
10395
|
+
import { writeFileSync as writeFileSync10 } from "fs";
|
|
10396
|
+
import { join as join11 } from "path";
|
|
10397
|
+
import process23 from "process";
|
|
7192
10398
|
async function relationDiagram(options = {}) {
|
|
7193
|
-
const dir = options.dir ||
|
|
10399
|
+
const dir = options.dir || join11(process23.cwd(), "app/Models");
|
|
7194
10400
|
const format = options.format || "mermaid";
|
|
7195
10401
|
try {
|
|
7196
10402
|
const models = await loadModels({ modelsDir: dir });
|
|
@@ -7208,7 +10414,7 @@ async function relationDiagram(options = {}) {
|
|
|
7208
10414
|
return "";
|
|
7209
10415
|
}
|
|
7210
10416
|
if (options.output) {
|
|
7211
|
-
|
|
10417
|
+
writeFileSync10(options.output, diagram, "utf8");
|
|
7212
10418
|
console.log(`\u2713 Diagram written to: ${options.output}`);
|
|
7213
10419
|
} else {
|
|
7214
10420
|
console.log(diagram);
|
|
@@ -7310,29 +10516,29 @@ var init_relation_diagram = __esm(async () => {
|
|
|
7310
10516
|
});
|
|
7311
10517
|
|
|
7312
10518
|
// src/actions/seed.ts
|
|
7313
|
-
import { existsSync as
|
|
7314
|
-
import { dirname as
|
|
7315
|
-
import
|
|
10519
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync9, writeFileSync as writeFileSync11 } from "fs";
|
|
10520
|
+
import { dirname as dirname8, join as join12 } from "path";
|
|
10521
|
+
import process24 from "process";
|
|
7316
10522
|
function findWorkspaceRoot4(startPath) {
|
|
7317
10523
|
let currentPath = startPath;
|
|
7318
|
-
while (currentPath !==
|
|
7319
|
-
if (
|
|
10524
|
+
while (currentPath !== dirname8(currentPath)) {
|
|
10525
|
+
if (existsSync14(join12(currentPath, "package.json"))) {
|
|
7320
10526
|
return currentPath;
|
|
7321
10527
|
}
|
|
7322
|
-
currentPath =
|
|
10528
|
+
currentPath = dirname8(currentPath);
|
|
7323
10529
|
}
|
|
7324
|
-
return
|
|
10530
|
+
return process24.cwd();
|
|
7325
10531
|
}
|
|
7326
10532
|
async function loadSeeders(seedersDir) {
|
|
7327
|
-
if (!
|
|
10533
|
+
if (!existsSync14(seedersDir)) {
|
|
7328
10534
|
console.log(`-- Seeders directory not found: ${seedersDir}`);
|
|
7329
10535
|
return [];
|
|
7330
10536
|
}
|
|
7331
|
-
const files =
|
|
10537
|
+
const files = readdirSync9(seedersDir);
|
|
7332
10538
|
const seederFiles = files.filter((file2) => (file2.endsWith(".ts") || file2.endsWith(".js")) && file2 !== "index.ts" && file2 !== "index.js");
|
|
7333
10539
|
const seeders = [];
|
|
7334
10540
|
for (const file2 of seederFiles) {
|
|
7335
|
-
const filePath =
|
|
10541
|
+
const filePath = join12(seedersDir, file2);
|
|
7336
10542
|
try {
|
|
7337
10543
|
const module = await import(filePath);
|
|
7338
10544
|
const SeederClass = module.default || module[Object.keys(module)[0]];
|
|
@@ -7352,10 +10558,10 @@ async function loadSeeders(seedersDir) {
|
|
|
7352
10558
|
seeders.sort((a, b) => a.instance.order - b.instance.order);
|
|
7353
10559
|
return seeders;
|
|
7354
10560
|
}
|
|
7355
|
-
async function runSeeders(
|
|
7356
|
-
const workspaceRoot = findWorkspaceRoot4(
|
|
7357
|
-
const seedersDir =
|
|
7358
|
-
const verbose =
|
|
10561
|
+
async function runSeeders(config4 = {}) {
|
|
10562
|
+
const workspaceRoot = findWorkspaceRoot4(process24.cwd());
|
|
10563
|
+
const seedersDir = config4.seedersDir || join12(workspaceRoot, "database/seeders");
|
|
10564
|
+
const verbose = config4.verbose ?? true;
|
|
7359
10565
|
if (verbose) {
|
|
7360
10566
|
console.log("-- Running seeders...");
|
|
7361
10567
|
console.log(`-- Seeders directory: ${seedersDir}`);
|
|
@@ -7388,8 +10594,8 @@ async function runSeeders(config3 = {}) {
|
|
|
7388
10594
|
}
|
|
7389
10595
|
}
|
|
7390
10596
|
async function runSeeder(className, options = {}) {
|
|
7391
|
-
const workspaceRoot = findWorkspaceRoot4(
|
|
7392
|
-
const seedersDir =
|
|
10597
|
+
const workspaceRoot = findWorkspaceRoot4(process24.cwd());
|
|
10598
|
+
const seedersDir = join12(workspaceRoot, "database/seeders");
|
|
7393
10599
|
const verbose = options.verbose ?? true;
|
|
7394
10600
|
if (verbose) {
|
|
7395
10601
|
console.log(`-- Running seeder: ${className}`);
|
|
@@ -7412,17 +10618,17 @@ async function runSeeder(className, options = {}) {
|
|
|
7412
10618
|
}
|
|
7413
10619
|
}
|
|
7414
10620
|
async function makeSeeder(name) {
|
|
7415
|
-
const workspaceRoot = findWorkspaceRoot4(
|
|
7416
|
-
const seedersDir =
|
|
7417
|
-
if (!
|
|
7418
|
-
|
|
10621
|
+
const workspaceRoot = findWorkspaceRoot4(process24.cwd());
|
|
10622
|
+
const seedersDir = join12(workspaceRoot, "database/seeders");
|
|
10623
|
+
if (!existsSync14(seedersDir)) {
|
|
10624
|
+
mkdirSync7(seedersDir, { recursive: true });
|
|
7419
10625
|
console.log(`-- Created seeders directory: ${seedersDir}`);
|
|
7420
10626
|
}
|
|
7421
10627
|
const baseName = name.replace(/Seeder$/i, "");
|
|
7422
10628
|
const className = `${baseName}Seeder`;
|
|
7423
10629
|
const fileName = `${className}.ts`;
|
|
7424
|
-
const filePath =
|
|
7425
|
-
if (
|
|
10630
|
+
const filePath = join12(seedersDir, fileName);
|
|
10631
|
+
if (existsSync14(filePath)) {
|
|
7426
10632
|
console.error(`-- Seeder already exists: ${filePath}`);
|
|
7427
10633
|
throw new Error(`Seeder already exists: ${filePath}`);
|
|
7428
10634
|
}
|
|
@@ -7472,13 +10678,13 @@ export default class ${className} extends Seeder {
|
|
|
7472
10678
|
}
|
|
7473
10679
|
}
|
|
7474
10680
|
`;
|
|
7475
|
-
|
|
10681
|
+
writeFileSync11(filePath, template);
|
|
7476
10682
|
console.log(`-- \u2713 Created seeder: ${filePath}`);
|
|
7477
10683
|
}
|
|
7478
10684
|
async function freshDatabase(options = {}) {
|
|
7479
|
-
const workspaceRoot = findWorkspaceRoot4(
|
|
7480
|
-
const modelsDir = options.modelsDir ||
|
|
7481
|
-
const seedersDir = options.seedersDir ||
|
|
10685
|
+
const workspaceRoot = findWorkspaceRoot4(process24.cwd());
|
|
10686
|
+
const modelsDir = options.modelsDir || join12(workspaceRoot, "app/Models");
|
|
10687
|
+
const seedersDir = options.seedersDir || join12(workspaceRoot, "database/seeders");
|
|
7482
10688
|
const verbose = options.verbose ?? true;
|
|
7483
10689
|
try {
|
|
7484
10690
|
const { resetDatabase: resetDatabase2, generateMigration: generateMigration2, executeMigration: executeMigration2 } = await init_migrate().then(() => exports_migrate);
|
|
@@ -7511,8 +10717,8 @@ var init_seed = __esm(async () => {
|
|
|
7511
10717
|
function sql(dir, table, opts = {}) {
|
|
7512
10718
|
const models = loadModels({ modelsDir: dir });
|
|
7513
10719
|
const dbSchema = buildDatabaseSchema(models);
|
|
7514
|
-
if (
|
|
7515
|
-
|
|
10720
|
+
if (config3.debug)
|
|
10721
|
+
config3.debug.captureText = true;
|
|
7516
10722
|
const qb = createQueryBuilder({ schema: dbSchema });
|
|
7517
10723
|
const s = qb.selectFrom(table).limit(Number(opts.limit || 10)).toText?.() ?? "";
|
|
7518
10724
|
console.log(s || "[query]");
|
|
@@ -7538,24 +10744,24 @@ var init_unsafe = __esm(async () => {
|
|
|
7538
10744
|
});
|
|
7539
10745
|
|
|
7540
10746
|
// src/actions/validate.ts
|
|
7541
|
-
import { existsSync as
|
|
7542
|
-
import { dirname as
|
|
7543
|
-
import
|
|
10747
|
+
import { existsSync as existsSync15 } from "fs";
|
|
10748
|
+
import { dirname as dirname9, join as join13 } from "path";
|
|
10749
|
+
import process25 from "process";
|
|
7544
10750
|
function findWorkspaceRoot5(startPath) {
|
|
7545
10751
|
let currentPath = startPath;
|
|
7546
|
-
while (currentPath !==
|
|
7547
|
-
if (
|
|
10752
|
+
while (currentPath !== dirname9(currentPath)) {
|
|
10753
|
+
if (existsSync15(join13(currentPath, "package.json"))) {
|
|
7548
10754
|
return currentPath;
|
|
7549
10755
|
}
|
|
7550
|
-
currentPath =
|
|
10756
|
+
currentPath = dirname9(currentPath);
|
|
7551
10757
|
}
|
|
7552
|
-
return
|
|
10758
|
+
return process25.cwd();
|
|
7553
10759
|
}
|
|
7554
10760
|
async function validateSchema(dir) {
|
|
7555
10761
|
if (!dir) {
|
|
7556
|
-
dir =
|
|
10762
|
+
dir = join13(findWorkspaceRoot5(process25.cwd()), "app/Models");
|
|
7557
10763
|
}
|
|
7558
|
-
const dialect =
|
|
10764
|
+
const dialect = config3.dialect || "postgres";
|
|
7559
10765
|
console.log("-- Validating Schema");
|
|
7560
10766
|
console.log(`-- Models directory: ${dir}`);
|
|
7561
10767
|
console.log(`-- Dialect: ${dialect}`);
|
|
@@ -7811,17 +11017,17 @@ function buildDatabaseSchema(models) {
|
|
|
7811
11017
|
}
|
|
7812
11018
|
|
|
7813
11019
|
// src/loader.ts
|
|
7814
|
-
import { readdirSync as
|
|
11020
|
+
import { readdirSync as readdirSync10, statSync as statSync3 } from "fs";
|
|
7815
11021
|
import { basename, extname as extname3 } from "path";
|
|
7816
|
-
import
|
|
11022
|
+
import process26 from "process";
|
|
7817
11023
|
async function loadModels(options) {
|
|
7818
|
-
const cwd = options.cwd ??
|
|
11024
|
+
const cwd = options.cwd ?? process26.cwd();
|
|
7819
11025
|
const dir = options.modelsDir.startsWith("/") ? options.modelsDir : `${cwd}/${options.modelsDir}`;
|
|
7820
11026
|
const result = {};
|
|
7821
|
-
const entries =
|
|
11027
|
+
const entries = readdirSync10(dir);
|
|
7822
11028
|
for (const entry of entries) {
|
|
7823
11029
|
const full = `${dir}/${entry}`;
|
|
7824
|
-
const st =
|
|
11030
|
+
const st = statSync3(full);
|
|
7825
11031
|
if (st.isDirectory())
|
|
7826
11032
|
continue;
|
|
7827
11033
|
const ext = extname3(full);
|
|
@@ -7895,24 +11101,24 @@ function buildSchemaMeta(models) {
|
|
|
7895
11101
|
}
|
|
7896
11102
|
|
|
7897
11103
|
// src/migrations.ts
|
|
7898
|
-
import { existsSync as
|
|
7899
|
-
import { dirname as
|
|
7900
|
-
import
|
|
11104
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, writeFileSync as writeFileSync12 } from "fs";
|
|
11105
|
+
import { dirname as dirname10, join as join14 } from "path";
|
|
11106
|
+
import process27 from "process";
|
|
7901
11107
|
function findWorkspaceRoot6(startPath) {
|
|
7902
11108
|
let currentPath = startPath;
|
|
7903
|
-
while (currentPath !==
|
|
7904
|
-
if (
|
|
11109
|
+
while (currentPath !== dirname10(currentPath)) {
|
|
11110
|
+
if (existsSync16(join14(currentPath, "package.json"))) {
|
|
7905
11111
|
return currentPath;
|
|
7906
11112
|
}
|
|
7907
|
-
currentPath =
|
|
11113
|
+
currentPath = dirname10(currentPath);
|
|
7908
11114
|
}
|
|
7909
|
-
return
|
|
11115
|
+
return process27.cwd();
|
|
7910
11116
|
}
|
|
7911
11117
|
function ensureSqlDirectory2() {
|
|
7912
|
-
const workspaceRoot = findWorkspaceRoot6(
|
|
7913
|
-
const sqlDir =
|
|
7914
|
-
if (!
|
|
7915
|
-
|
|
11118
|
+
const workspaceRoot = findWorkspaceRoot6(process27.cwd());
|
|
11119
|
+
const sqlDir = join14(workspaceRoot, "database", "sql");
|
|
11120
|
+
if (!existsSync16(sqlDir)) {
|
|
11121
|
+
mkdirSync8(sqlDir, { recursive: true });
|
|
7916
11122
|
console.log(`-- Created SQL directory: ${sqlDir}`);
|
|
7917
11123
|
}
|
|
7918
11124
|
return sqlDir;
|
|
@@ -7925,8 +11131,8 @@ function createMigrationFile(statement, fileName) {
|
|
|
7925
11131
|
const timestamp = baseTimestamp + migrationCounter;
|
|
7926
11132
|
migrationCounter++;
|
|
7927
11133
|
const fullFileName = `${timestamp}-${fileName}.sql`;
|
|
7928
|
-
const filePath =
|
|
7929
|
-
|
|
11134
|
+
const filePath = join14(sqlDir, fullFileName);
|
|
11135
|
+
writeFileSync12(filePath, statement);
|
|
7930
11136
|
console.log(`-- Migration file created: ${fullFileName}`);
|
|
7931
11137
|
migrationsCreatedCount++;
|
|
7932
11138
|
return true;
|
|
@@ -8487,13 +11693,13 @@ export {
|
|
|
8487
11693
|
defineSeeder,
|
|
8488
11694
|
defineModels,
|
|
8489
11695
|
defineModel,
|
|
8490
|
-
|
|
11696
|
+
defaultConfig3 as defaultConfig,
|
|
8491
11697
|
dbWipe,
|
|
8492
11698
|
dbStats,
|
|
8493
11699
|
dbOptimize,
|
|
8494
11700
|
dbInfo,
|
|
8495
11701
|
createQueryBuilder,
|
|
8496
|
-
|
|
11702
|
+
config3 as config,
|
|
8497
11703
|
clearQueryCache,
|
|
8498
11704
|
checkSchema,
|
|
8499
11705
|
cacheStats,
|