@reliverse/relinka 1.3.8 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -71
- package/bin/{core-impl/deprecated → deprecated}/components/levels/levels.d.ts +1 -1
- package/bin/{core-impl/deprecated → deprecated}/components/modes/basic.d.ts +1 -1
- package/bin/{core-impl/deprecated → deprecated}/components/modes/browser.d.ts +1 -1
- package/bin/deprecated/components/modes/shared.d.ts +2 -0
- package/bin/deprecated/components/modes/shared.js +1 -0
- package/bin/{core-impl/deprecated → deprecated}/components/relinka-deprecated/mod.d.ts +1 -1
- package/bin/{core-impl/deprecated → deprecated}/components/relinka-deprecated/relinka.d.ts +2 -2
- package/bin/{core-impl/deprecated → deprecated}/components/reporters/basic.d.ts +1 -1
- package/bin/{core-impl/deprecated → deprecated}/components/reporters/browser.d.ts +1 -1
- package/bin/{core-impl/deprecated → deprecated}/components/reporters/fancy.d.ts +2 -2
- package/bin/{core-impl/deprecated → deprecated}/components/reporters/fancy.js +1 -3
- package/bin/deprecated/impl-old.js +0 -0
- package/bin/{core-types.d.ts → deprecated/types.d.ts} +0 -32
- package/bin/deprecated/types.js +0 -0
- package/bin/impl.d.ts +149 -0
- package/bin/impl.js +606 -0
- package/bin/main.d.ts +24 -22
- package/bin/main.js +24 -21
- package/bin/types.d.ts +0 -0
- package/bin/types.js +0 -0
- package/package.json +41 -6
- package/bin/core-impl/deprecated/components/modes/shared.d.ts +0 -2
- package/bin/core-impl/deprecated/components/modes/shared.js +0 -4
- package/bin/core-impl/deprecated/components/relinka-deprecated/relinka.test.d.ts +0 -1
- package/bin/core-impl/deprecated/components/relinka-deprecated/relinka.test.js +0 -57
- package/bin/core-impl/impl-mod.d.ts +0 -19
- package/bin/core-impl/impl-mod.js +0 -321
- /package/bin/{core-impl/deprecated → deprecated}/components/levels/levels.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/components/modes/basic.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/components/modes/browser.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/components/relinka-deprecated/logger.d.ts +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/components/relinka-deprecated/logger.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/components/relinka-deprecated/mod.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/components/relinka-deprecated/relinka.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/components/reporters/basic.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/components/reporters/browser.js +0 -0
- /package/bin/{core-types.js → deprecated/impl-old.d.ts} +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/box.d.ts +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/box.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/deprecatedColors.d.ts +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/deprecatedColors.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/error.d.ts +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/error.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/format.d.ts +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/format.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/log.d.ts +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/log.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/stream.d.ts +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/stream.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/string.d.ts +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/string.js +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/tree.d.ts +0 -0
- /package/bin/{core-impl/deprecated → deprecated}/utils/tree.js +0 -0
package/bin/impl.js
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
import { re } from "@reliverse/relico";
|
|
2
|
+
import { loadConfig } from "c12";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "pathe";
|
|
6
|
+
const ENABLE_DEV_DEBUG = false;
|
|
7
|
+
const activeTimers = [];
|
|
8
|
+
const DEFAULT_RELINKA_CONFIG = {
|
|
9
|
+
verbose: false,
|
|
10
|
+
dirs: {
|
|
11
|
+
dailyLogs: false,
|
|
12
|
+
logDir: "logs",
|
|
13
|
+
maxLogFiles: 0,
|
|
14
|
+
specialDirs: {
|
|
15
|
+
distDirNames: ["dist", "dist-jsr", "dist-npm", "dist-libs"],
|
|
16
|
+
useParentConfigInDist: true
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
disableColors: false,
|
|
20
|
+
logFilePath: "relinka.log",
|
|
21
|
+
saveLogsToFile: false,
|
|
22
|
+
timestamp: {
|
|
23
|
+
enabled: false,
|
|
24
|
+
format: "YYYY-MM-DD HH:mm:ss.SSS"
|
|
25
|
+
},
|
|
26
|
+
cleanupInterval: 1e4,
|
|
27
|
+
// 10 seconds
|
|
28
|
+
bufferSize: 4096,
|
|
29
|
+
// 4KB
|
|
30
|
+
maxBufferAge: 5e3,
|
|
31
|
+
// 5 seconds
|
|
32
|
+
// A default set of levels, with fallback symbols for non-Unicode terminals:
|
|
33
|
+
levels: {
|
|
34
|
+
success: {
|
|
35
|
+
symbol: "\u2713",
|
|
36
|
+
fallbackSymbol: "[OK]",
|
|
37
|
+
color: "greenBright",
|
|
38
|
+
spacing: 3
|
|
39
|
+
},
|
|
40
|
+
info: {
|
|
41
|
+
symbol: "\u2BCE",
|
|
42
|
+
fallbackSymbol: "[i]",
|
|
43
|
+
color: "cyanBright",
|
|
44
|
+
spacing: 3
|
|
45
|
+
},
|
|
46
|
+
error: {
|
|
47
|
+
symbol: "\u2716",
|
|
48
|
+
fallbackSymbol: "[ERR]",
|
|
49
|
+
color: "redBright",
|
|
50
|
+
spacing: 3
|
|
51
|
+
},
|
|
52
|
+
warn: {
|
|
53
|
+
symbol: "\u26A0",
|
|
54
|
+
fallbackSymbol: "[WARN]",
|
|
55
|
+
color: "yellowBright",
|
|
56
|
+
spacing: 3
|
|
57
|
+
},
|
|
58
|
+
fatal: {
|
|
59
|
+
symbol: "\u203C",
|
|
60
|
+
fallbackSymbol: "[FATAL]",
|
|
61
|
+
color: "redBright",
|
|
62
|
+
spacing: 3
|
|
63
|
+
},
|
|
64
|
+
verbose: {
|
|
65
|
+
symbol: "\u2727",
|
|
66
|
+
fallbackSymbol: "[VERBOSE]",
|
|
67
|
+
color: "gray",
|
|
68
|
+
spacing: 3
|
|
69
|
+
},
|
|
70
|
+
log: { symbol: "\u2502", fallbackSymbol: "|", color: "dim", spacing: 3 }
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function isUnicodeSupported() {
|
|
74
|
+
if (process.env.TERM_PROGRAM === "vscode" || process.env.WT_SESSION || process.env.TERM_PROGRAM === "iTerm.app" || process.env.TERM_PROGRAM === "hyper" || process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm" || process.env.ConEmuTask === "{cmd::Cmder}" || process.env.TERM === "xterm-256color") {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (process.platform === "win32") {
|
|
78
|
+
const osRelease = os.release();
|
|
79
|
+
const match = /(\d+)\.(\d+)/.exec(osRelease);
|
|
80
|
+
if (match && Number.parseInt(match[1], 10) >= 10) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (process.env.TERM_PROGRAM === "mintty") {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
let currentConfig = { ...DEFAULT_RELINKA_CONFIG };
|
|
91
|
+
let isConfigInitialized = false;
|
|
92
|
+
let resolveRelinkaConfig;
|
|
93
|
+
export const relinkaConfig = new Promise((resolve) => {
|
|
94
|
+
resolveRelinkaConfig = resolve;
|
|
95
|
+
});
|
|
96
|
+
const logBuffers = /* @__PURE__ */ new Map();
|
|
97
|
+
let lastCleanupTime = 0;
|
|
98
|
+
let cleanupScheduled = false;
|
|
99
|
+
let bufferFlushTimer = null;
|
|
100
|
+
async function initializeConfig() {
|
|
101
|
+
try {
|
|
102
|
+
const result = await loadConfig({
|
|
103
|
+
name: "relinka",
|
|
104
|
+
cwd: process.cwd(),
|
|
105
|
+
dotenv: false,
|
|
106
|
+
packageJson: false,
|
|
107
|
+
rcFile: false,
|
|
108
|
+
globalRc: false,
|
|
109
|
+
defaults: DEFAULT_RELINKA_CONFIG
|
|
110
|
+
});
|
|
111
|
+
currentConfig = result.config;
|
|
112
|
+
isConfigInitialized = true;
|
|
113
|
+
if (ENABLE_DEV_DEBUG) {
|
|
114
|
+
console.log("[Dev Debug] Config file used:", result.configFile);
|
|
115
|
+
console.log("[Dev Debug] All merged layers:", result.layers);
|
|
116
|
+
console.log("[Dev Debug] Final configuration:", currentConfig);
|
|
117
|
+
}
|
|
118
|
+
if (resolveRelinkaConfig) {
|
|
119
|
+
resolveRelinkaConfig(currentConfig);
|
|
120
|
+
resolveRelinkaConfig = void 0;
|
|
121
|
+
}
|
|
122
|
+
setupBufferFlushTimer();
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error(
|
|
125
|
+
`[Relinka Config Error] Failed to load config: ${err instanceof Error ? err.message : String(err)}`
|
|
126
|
+
);
|
|
127
|
+
currentConfig = { ...DEFAULT_RELINKA_CONFIG };
|
|
128
|
+
isConfigInitialized = true;
|
|
129
|
+
if (resolveRelinkaConfig) {
|
|
130
|
+
resolveRelinkaConfig(currentConfig);
|
|
131
|
+
resolveRelinkaConfig = void 0;
|
|
132
|
+
}
|
|
133
|
+
setupBufferFlushTimer();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function setupBufferFlushTimer() {
|
|
137
|
+
if (bufferFlushTimer) {
|
|
138
|
+
clearInterval(bufferFlushTimer);
|
|
139
|
+
const index = activeTimers.indexOf(bufferFlushTimer);
|
|
140
|
+
if (index !== -1) {
|
|
141
|
+
activeTimers.splice(index, 1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const maxBufferAge = getMaxBufferAge(currentConfig);
|
|
145
|
+
bufferFlushTimer = setInterval(
|
|
146
|
+
() => {
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
for (const [filePath, buffer] of logBuffers.entries()) {
|
|
149
|
+
if (buffer.entries.length > 0 && now - buffer.lastFlush >= maxBufferAge) {
|
|
150
|
+
flushLogBuffer(currentConfig, filePath).catch((err) => {
|
|
151
|
+
console.error(
|
|
152
|
+
`[Relinka Buffer Flush Error] Failed to flush aged buffer: ${err instanceof Error ? err.message : String(err)}`
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
Math.min(maxBufferAge / 2, 2500)
|
|
159
|
+
// Check at least every 2.5 seconds or half the max age
|
|
160
|
+
);
|
|
161
|
+
activeTimers.push(bufferFlushTimer);
|
|
162
|
+
}
|
|
163
|
+
initializeConfig().catch((err) => {
|
|
164
|
+
console.error(
|
|
165
|
+
`[Relinka Config Error] Unhandled error: ${err instanceof Error ? err.message : String(err)}`
|
|
166
|
+
);
|
|
167
|
+
if (!isConfigInitialized) {
|
|
168
|
+
currentConfig = { ...DEFAULT_RELINKA_CONFIG };
|
|
169
|
+
isConfigInitialized = true;
|
|
170
|
+
if (resolveRelinkaConfig) {
|
|
171
|
+
resolveRelinkaConfig(currentConfig);
|
|
172
|
+
resolveRelinkaConfig = void 0;
|
|
173
|
+
}
|
|
174
|
+
setupBufferFlushTimer();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
function isVerboseEnabled(config) {
|
|
178
|
+
return config.verbose ?? DEFAULT_RELINKA_CONFIG.verbose;
|
|
179
|
+
}
|
|
180
|
+
function isColorEnabled(config) {
|
|
181
|
+
return !(config.disableColors ?? DEFAULT_RELINKA_CONFIG.disableColors);
|
|
182
|
+
}
|
|
183
|
+
function getLogDir(config) {
|
|
184
|
+
return config.dirs?.logDir ?? DEFAULT_RELINKA_CONFIG.dirs.logDir;
|
|
185
|
+
}
|
|
186
|
+
function isDailyLogsEnabled(config) {
|
|
187
|
+
return config.dirs?.dailyLogs ?? DEFAULT_RELINKA_CONFIG.dirs.dailyLogs;
|
|
188
|
+
}
|
|
189
|
+
function shouldSaveLogs(config) {
|
|
190
|
+
return config.saveLogsToFile ?? DEFAULT_RELINKA_CONFIG.saveLogsToFile;
|
|
191
|
+
}
|
|
192
|
+
function getMaxLogFiles(config) {
|
|
193
|
+
return config.dirs?.maxLogFiles ?? DEFAULT_RELINKA_CONFIG.dirs.maxLogFiles;
|
|
194
|
+
}
|
|
195
|
+
function getBaseLogName(config) {
|
|
196
|
+
return config.logFilePath ?? DEFAULT_RELINKA_CONFIG.logFilePath;
|
|
197
|
+
}
|
|
198
|
+
function getBufferSize(config) {
|
|
199
|
+
return config.bufferSize ?? DEFAULT_RELINKA_CONFIG.bufferSize;
|
|
200
|
+
}
|
|
201
|
+
function getMaxBufferAge(config) {
|
|
202
|
+
return config.maxBufferAge ?? DEFAULT_RELINKA_CONFIG.maxBufferAge;
|
|
203
|
+
}
|
|
204
|
+
function getCleanupInterval(config) {
|
|
205
|
+
return config.cleanupInterval ?? DEFAULT_RELINKA_CONFIG.cleanupInterval;
|
|
206
|
+
}
|
|
207
|
+
function isDevEnv() {
|
|
208
|
+
return process.env.NODE_ENV === "development";
|
|
209
|
+
}
|
|
210
|
+
function getDateString(date = /* @__PURE__ */ new Date()) {
|
|
211
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(
|
|
212
|
+
date.getDate()
|
|
213
|
+
).padStart(2, "0")}`;
|
|
214
|
+
}
|
|
215
|
+
function getTimestamp(config) {
|
|
216
|
+
if (!config.timestamp?.enabled) return "";
|
|
217
|
+
const now = /* @__PURE__ */ new Date();
|
|
218
|
+
const format = config.timestamp?.format || "YYYY-MM-DD HH:mm:ss.SSS";
|
|
219
|
+
return format.replace("YYYY", String(now.getFullYear())).replace("MM", String(now.getMonth() + 1).padStart(2, "0")).replace("DD", String(now.getDate()).padStart(2, "0")).replace("HH", String(now.getHours()).padStart(2, "0")).replace("mm", String(now.getMinutes()).padStart(2, "0")).replace("ss", String(now.getSeconds()).padStart(2, "0")).replace("SSS", String(now.getMilliseconds()).padStart(3, "0"));
|
|
220
|
+
}
|
|
221
|
+
function getLogFilePath(config) {
|
|
222
|
+
const logDir = getLogDir(config);
|
|
223
|
+
const daily = isDailyLogsEnabled(config);
|
|
224
|
+
let finalLogName = getBaseLogName(config);
|
|
225
|
+
if (daily) {
|
|
226
|
+
const datePrefix = `${getDateString()}-`;
|
|
227
|
+
finalLogName = datePrefix + finalLogName;
|
|
228
|
+
}
|
|
229
|
+
if (finalLogName && !finalLogName.endsWith(".log")) {
|
|
230
|
+
finalLogName += ".log";
|
|
231
|
+
}
|
|
232
|
+
const effectiveLogName = finalLogName || "relinka.log";
|
|
233
|
+
return path.resolve(process.cwd(), logDir, effectiveLogName);
|
|
234
|
+
}
|
|
235
|
+
function getLevelStyle(config, level) {
|
|
236
|
+
const allLevels = config.levels || DEFAULT_RELINKA_CONFIG.levels || {};
|
|
237
|
+
const levelConfig = allLevels[level];
|
|
238
|
+
if (!levelConfig) {
|
|
239
|
+
return {
|
|
240
|
+
symbol: `[${level.toUpperCase()}]`,
|
|
241
|
+
color: "dim",
|
|
242
|
+
spacing: 3
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const { symbol, fallbackSymbol, color, spacing } = levelConfig;
|
|
246
|
+
const effectiveSymbol = isUnicodeSupported() ? symbol : fallbackSymbol || `[${level.toUpperCase()}]`;
|
|
247
|
+
return {
|
|
248
|
+
symbol: effectiveSymbol,
|
|
249
|
+
color,
|
|
250
|
+
spacing: spacing ?? 3
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function formatDetails(details) {
|
|
254
|
+
if (details === void 0) return "";
|
|
255
|
+
if (details instanceof Error) {
|
|
256
|
+
return `
|
|
257
|
+
Stack Trace: ${details.stack || details.message}`;
|
|
258
|
+
}
|
|
259
|
+
if (typeof details === "object" && details !== null) {
|
|
260
|
+
try {
|
|
261
|
+
return ` ${JSON.stringify(details, null, 2)}`;
|
|
262
|
+
} catch {
|
|
263
|
+
return " [object Object]";
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return ` ${String(details)}`;
|
|
267
|
+
}
|
|
268
|
+
function formatLogMessage(config, level, msg, details) {
|
|
269
|
+
const timestamp = getTimestamp(config);
|
|
270
|
+
const detailsStr = formatDetails(details);
|
|
271
|
+
const { symbol, spacing } = getLevelStyle(config, level);
|
|
272
|
+
const symbolWithSpaces = symbol ? `${symbol}${" ".repeat(spacing)}` : "";
|
|
273
|
+
const prefix = timestamp ? `[${timestamp}] ` : "";
|
|
274
|
+
return `${prefix}${symbolWithSpaces}${msg}${detailsStr}`;
|
|
275
|
+
}
|
|
276
|
+
const consoleMethodMap = {
|
|
277
|
+
error: console.error,
|
|
278
|
+
fatal: console.error,
|
|
279
|
+
warn: console.warn,
|
|
280
|
+
info: console.info,
|
|
281
|
+
success: console.log,
|
|
282
|
+
verbose: console.log,
|
|
283
|
+
log: console.log
|
|
284
|
+
};
|
|
285
|
+
function logToConsole(config, level, formattedMessage) {
|
|
286
|
+
if (!isColorEnabled(config)) {
|
|
287
|
+
const method2 = consoleMethodMap[level] || console.log;
|
|
288
|
+
method2(formattedMessage);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const { color } = getLevelStyle(config, level);
|
|
292
|
+
const colorFn = re[color] || re.dim;
|
|
293
|
+
const method = consoleMethodMap[level] || console.log;
|
|
294
|
+
method(`${colorFn(formattedMessage)}\x1B[0m`);
|
|
295
|
+
}
|
|
296
|
+
async function getLogFilesSortedByDate(config) {
|
|
297
|
+
const logDirectoryPath = path.resolve(process.cwd(), getLogDir(config));
|
|
298
|
+
try {
|
|
299
|
+
if (!await fs.pathExists(logDirectoryPath)) {
|
|
300
|
+
if (ENABLE_DEV_DEBUG) {
|
|
301
|
+
console.log(`[Dev Debug] Log directory not found: ${logDirectoryPath}`);
|
|
302
|
+
}
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
const files = await fs.readdir(logDirectoryPath);
|
|
306
|
+
const logFiles = files.filter((f) => f.endsWith(".log"));
|
|
307
|
+
if (logFiles.length === 0) return [];
|
|
308
|
+
const fileInfoPromises = logFiles.map(
|
|
309
|
+
async (fileName) => {
|
|
310
|
+
const filePath = path.join(logDirectoryPath, fileName);
|
|
311
|
+
try {
|
|
312
|
+
const stats = await fs.stat(filePath);
|
|
313
|
+
if (stats.isFile()) {
|
|
314
|
+
return { path: filePath, mtime: stats.mtime.getTime() };
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
} catch (err) {
|
|
318
|
+
if (isVerboseEnabled(config)) {
|
|
319
|
+
console.error(
|
|
320
|
+
`[Relinka FS Debug] Error reading stats for ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
const logFileInfos = (await Promise.all(fileInfoPromises)).filter(Boolean);
|
|
328
|
+
return logFileInfos.sort((a, b) => b.mtime - a.mtime);
|
|
329
|
+
} catch (readErr) {
|
|
330
|
+
if (isVerboseEnabled(config)) {
|
|
331
|
+
console.error(
|
|
332
|
+
`[Relinka FS Error] Failed reading log directory ${logDirectoryPath}: ${readErr instanceof Error ? readErr.message : String(readErr)}`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function deleteFiles(filePaths, config) {
|
|
339
|
+
if (filePaths.length === 0) return;
|
|
340
|
+
const results = await Promise.allSettled(
|
|
341
|
+
filePaths.map((filePath) => fs.unlink(filePath))
|
|
342
|
+
);
|
|
343
|
+
const errors = results.map(
|
|
344
|
+
(result, index) => result.status === "rejected" ? { path: filePaths[index], error: result.reason } : null
|
|
345
|
+
).filter(Boolean);
|
|
346
|
+
if (errors.length > 0 && isVerboseEnabled(config)) {
|
|
347
|
+
console.error(
|
|
348
|
+
`[Relinka FS Error] Failed to delete ${errors.length} log files:`,
|
|
349
|
+
errors.map(
|
|
350
|
+
(e) => `${e?.path}: ${e?.error instanceof Error ? e.error.message : String(e?.error)}`
|
|
351
|
+
).join(", ")
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
export async function relinkaShutdown() {
|
|
356
|
+
activeTimers.forEach((timer) => clearTimeout(timer));
|
|
357
|
+
activeTimers.length = 0;
|
|
358
|
+
cleanupScheduled = false;
|
|
359
|
+
await flushAllLogBuffers();
|
|
360
|
+
}
|
|
361
|
+
async function cleanupOldLogFiles(config) {
|
|
362
|
+
const maxFiles = getMaxLogFiles(config);
|
|
363
|
+
const cleanupInterval = getCleanupInterval(config);
|
|
364
|
+
if (!shouldSaveLogs(config) || maxFiles <= 0) return;
|
|
365
|
+
const now = Date.now();
|
|
366
|
+
if (now - lastCleanupTime < cleanupInterval) {
|
|
367
|
+
if (!cleanupScheduled) {
|
|
368
|
+
cleanupScheduled = true;
|
|
369
|
+
const delay = cleanupInterval - (now - lastCleanupTime);
|
|
370
|
+
const timer = setTimeout(() => {
|
|
371
|
+
cleanupScheduled = false;
|
|
372
|
+
lastCleanupTime = Date.now();
|
|
373
|
+
const index = activeTimers.indexOf(timer);
|
|
374
|
+
if (index !== -1) activeTimers.splice(index, 1);
|
|
375
|
+
cleanupOldLogFiles(config).catch((err) => {
|
|
376
|
+
if (isVerboseEnabled(config)) {
|
|
377
|
+
console.error(
|
|
378
|
+
`[Relinka Delayed Cleanup Error] ${err instanceof Error ? err.message : String(err)}`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}, delay);
|
|
383
|
+
activeTimers.push(timer);
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
lastCleanupTime = now;
|
|
388
|
+
try {
|
|
389
|
+
const sortedLogFiles = await getLogFilesSortedByDate(config);
|
|
390
|
+
if (sortedLogFiles.length > maxFiles) {
|
|
391
|
+
const filesToDelete = sortedLogFiles.slice(maxFiles).map((f) => f.path);
|
|
392
|
+
if (filesToDelete.length > 0) {
|
|
393
|
+
await deleteFiles(filesToDelete, config);
|
|
394
|
+
if (isVerboseEnabled(config)) {
|
|
395
|
+
console.log(
|
|
396
|
+
`[Relinka Cleanup] Deleted ${filesToDelete.length} old log file(s). Kept ${maxFiles}.`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
} catch (err) {
|
|
402
|
+
if (isVerboseEnabled(config)) {
|
|
403
|
+
console.error(
|
|
404
|
+
`[Relinka Cleanup Error] Failed during log cleanup: ${err instanceof Error ? err.message : String(err)}`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async function appendToLogFileImmediate(config, absoluteLogFilePath, logMessage) {
|
|
410
|
+
try {
|
|
411
|
+
await fs.ensureDir(path.dirname(absoluteLogFilePath));
|
|
412
|
+
await fs.appendFile(absoluteLogFilePath, `${logMessage}
|
|
413
|
+
`);
|
|
414
|
+
} catch (err) {
|
|
415
|
+
if (isVerboseEnabled(config)) {
|
|
416
|
+
console.error(
|
|
417
|
+
`[Relinka File Error] Failed to write to log file ${absoluteLogFilePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
let logWriteChain = Promise.resolve();
|
|
423
|
+
function addToLogBuffer(config, filePath, message) {
|
|
424
|
+
const bufferSize = getBufferSize(config);
|
|
425
|
+
let buffer = logBuffers.get(filePath);
|
|
426
|
+
if (!buffer) {
|
|
427
|
+
buffer = {
|
|
428
|
+
filePath,
|
|
429
|
+
entries: [],
|
|
430
|
+
size: 0,
|
|
431
|
+
lastFlush: Date.now()
|
|
432
|
+
};
|
|
433
|
+
logBuffers.set(filePath, buffer);
|
|
434
|
+
}
|
|
435
|
+
buffer.entries.push(message);
|
|
436
|
+
buffer.size += message.length + 1;
|
|
437
|
+
if (buffer.size >= bufferSize) {
|
|
438
|
+
return flushLogBuffer(config, filePath);
|
|
439
|
+
}
|
|
440
|
+
return Promise.resolve();
|
|
441
|
+
}
|
|
442
|
+
function flushLogBuffer(config, filePath) {
|
|
443
|
+
const buffer = logBuffers.get(filePath);
|
|
444
|
+
if (!buffer || buffer.entries.length === 0) {
|
|
445
|
+
return Promise.resolve();
|
|
446
|
+
}
|
|
447
|
+
const content = `${buffer.entries.join("\n")}
|
|
448
|
+
`;
|
|
449
|
+
buffer.entries = [];
|
|
450
|
+
buffer.size = 0;
|
|
451
|
+
buffer.lastFlush = Date.now();
|
|
452
|
+
logWriteChain = logWriteChain.then(() => {
|
|
453
|
+
return appendToLogFileImmediate(config, filePath, content);
|
|
454
|
+
}).catch((err) => {
|
|
455
|
+
if (isVerboseEnabled(config)) {
|
|
456
|
+
console.error(
|
|
457
|
+
`[Relinka Buffer Flush Error] Failed to flush buffer for ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
return logWriteChain;
|
|
462
|
+
}
|
|
463
|
+
function queueLogWrite(config, absoluteLogFilePath, logMessage) {
|
|
464
|
+
return addToLogBuffer(config, absoluteLogFilePath, logMessage);
|
|
465
|
+
}
|
|
466
|
+
export async function flushAllLogBuffers() {
|
|
467
|
+
const filePaths = Array.from(logBuffers.keys());
|
|
468
|
+
await Promise.all(
|
|
469
|
+
filePaths.map((path2) => flushLogBuffer(currentConfig, path2))
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
function internalFatalLogAndThrow(message, ...args) {
|
|
473
|
+
const formatted = formatLogMessage(currentConfig, "fatal", message, args);
|
|
474
|
+
logToConsole(currentConfig, "fatal", formatted);
|
|
475
|
+
if (shouldSaveLogs(currentConfig)) {
|
|
476
|
+
try {
|
|
477
|
+
const absoluteLogFilePath = getLogFilePath(currentConfig);
|
|
478
|
+
fs.ensureDirSync(path.dirname(absoluteLogFilePath));
|
|
479
|
+
fs.appendFileSync(absoluteLogFilePath, `${formatted}
|
|
480
|
+
`);
|
|
481
|
+
} catch (err) {
|
|
482
|
+
console.error(
|
|
483
|
+
`[Relinka Fatal File Error] Failed to write fatal error: ${err instanceof Error ? err.message : String(err)}`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (isDevEnv()) {
|
|
488
|
+
debugger;
|
|
489
|
+
}
|
|
490
|
+
throw new Error(`Fatal error: ${message}`);
|
|
491
|
+
}
|
|
492
|
+
export function shouldNeverHappen(message, ...args) {
|
|
493
|
+
return internalFatalLogAndThrow(message, ...args);
|
|
494
|
+
}
|
|
495
|
+
export function truncateString(msg, maxLength = 100) {
|
|
496
|
+
if (!msg || msg.length <= maxLength) return msg;
|
|
497
|
+
return `${msg.slice(0, maxLength - 1)}\u2026`;
|
|
498
|
+
}
|
|
499
|
+
export function casesHandled(unexpectedCase) {
|
|
500
|
+
debugger;
|
|
501
|
+
throw new Error(
|
|
502
|
+
`A case was not handled for value: ${truncateString(String(unexpectedCase ?? "unknown"))}`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
export function relinka(type, message, ...args) {
|
|
506
|
+
if (type === "clear") {
|
|
507
|
+
console.clear();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (message === "") {
|
|
511
|
+
console.log();
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const levelName = type.toLowerCase();
|
|
515
|
+
if (levelName === "fatal") {
|
|
516
|
+
return internalFatalLogAndThrow(message, ...args);
|
|
517
|
+
}
|
|
518
|
+
if (levelName === "verbose" && !isVerboseEnabled(currentConfig)) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const details = args.length > 1 ? args : args[0];
|
|
522
|
+
const formatted = formatLogMessage(
|
|
523
|
+
currentConfig,
|
|
524
|
+
levelName,
|
|
525
|
+
message,
|
|
526
|
+
details
|
|
527
|
+
);
|
|
528
|
+
logToConsole(currentConfig, levelName, formatted);
|
|
529
|
+
if (shouldSaveLogs(currentConfig) && levelName !== "fatal") {
|
|
530
|
+
const absoluteLogFilePath = getLogFilePath(currentConfig);
|
|
531
|
+
queueLogWrite(currentConfig, absoluteLogFilePath, formatted).catch(
|
|
532
|
+
(err) => {
|
|
533
|
+
if (isVerboseEnabled(currentConfig)) {
|
|
534
|
+
console.error(
|
|
535
|
+
`[Relinka File Error] Failed to write log line: ${err instanceof Error ? err.message : String(err)}`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
);
|
|
540
|
+
if (getMaxLogFiles(currentConfig) > 0) {
|
|
541
|
+
cleanupOldLogFiles(currentConfig).catch((err) => {
|
|
542
|
+
if (isVerboseEnabled(currentConfig)) {
|
|
543
|
+
console.error(
|
|
544
|
+
`[Relinka Cleanup Error] ${err instanceof Error ? err.message : String(err)}`
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
export async function relinkaAsync(type, message, ...args) {
|
|
552
|
+
if (message === "") {
|
|
553
|
+
console.log();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
await relinkaConfig;
|
|
557
|
+
const levelName = type.toLowerCase();
|
|
558
|
+
if (levelName === "fatal") {
|
|
559
|
+
shouldNeverHappen(message, ...args);
|
|
560
|
+
}
|
|
561
|
+
if (levelName === "verbose" && !isVerboseEnabled(currentConfig)) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const details = args.length > 1 ? args : args[0];
|
|
565
|
+
const formatted = formatLogMessage(
|
|
566
|
+
currentConfig,
|
|
567
|
+
levelName,
|
|
568
|
+
message,
|
|
569
|
+
details
|
|
570
|
+
);
|
|
571
|
+
logToConsole(currentConfig, levelName, formatted);
|
|
572
|
+
if (shouldSaveLogs(currentConfig)) {
|
|
573
|
+
const absoluteLogFilePath = getLogFilePath(currentConfig);
|
|
574
|
+
try {
|
|
575
|
+
await queueLogWrite(currentConfig, absoluteLogFilePath, formatted);
|
|
576
|
+
if (getMaxLogFiles(currentConfig) > 0) {
|
|
577
|
+
await cleanupOldLogFiles(currentConfig);
|
|
578
|
+
}
|
|
579
|
+
} catch (err) {
|
|
580
|
+
if (isVerboseEnabled(currentConfig)) {
|
|
581
|
+
console.error(
|
|
582
|
+
`[Relinka File Async Error] Error during file logging/cleanup: ${err instanceof Error ? err.message : String(err)}`
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
export function defineConfig(config) {
|
|
589
|
+
return config;
|
|
590
|
+
}
|
|
591
|
+
process.on("beforeExit", () => {
|
|
592
|
+
flushAllLogBuffers().catch(() => {
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
process.on("SIGINT", () => {
|
|
596
|
+
flushAllLogBuffers().catch(() => {
|
|
597
|
+
}).finally(() => {
|
|
598
|
+
process.exit(0);
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
process.on("SIGTERM", () => {
|
|
602
|
+
flushAllLogBuffers().catch(() => {
|
|
603
|
+
}).finally(() => {
|
|
604
|
+
process.exit(0);
|
|
605
|
+
});
|
|
606
|
+
});
|
package/bin/main.d.ts
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
export
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export
|
|
12
|
-
export {
|
|
13
|
-
export
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export
|
|
21
|
-
export {
|
|
22
|
-
export {
|
|
1
|
+
export * from "./deprecated/types.js";
|
|
2
|
+
export { LogLevelsDeprecated, LogTypesDeprecated, } from "./deprecated/components/levels/levels.js";
|
|
3
|
+
export { createRelinkaBaseDeprecated, relinkaBasicDeprecated, } from "./deprecated/components/modes/basic.js";
|
|
4
|
+
export { createRelinkaBrowserDeprecatedDeprecated, relinkaBrowserDeprecated, } from "./deprecated/components/modes/browser.js";
|
|
5
|
+
export { relinkaDeprecated } from "./deprecated/components/relinka-deprecated/logger.js";
|
|
6
|
+
export { createRelinkaSharedDeprecated, relinkaInstanceDeprecated, } from "./deprecated/components/relinka-deprecated/mod.js";
|
|
7
|
+
export type { LogFn, RelinkaInstanceDeprecated, } from "./deprecated/components/relinka-deprecated/relinka.js";
|
|
8
|
+
export { RelinkaInterface, createRelinkaDeprecated, } from "./deprecated/components/relinka-deprecated/relinka.js";
|
|
9
|
+
export { BasicReporter } from "./deprecated/components/reporters/basic.js";
|
|
10
|
+
export { BrowserReporter } from "./deprecated/components/reporters/browser.js";
|
|
11
|
+
export { TYPE_COLOR_MAP, LEVEL_COLOR_MAP, FancyReporter, } from "./deprecated/components/reporters/fancy.js";
|
|
12
|
+
export type { BoxBorderStyle, BoxStyle, BoxOpts, } from "./deprecated/utils/box.js";
|
|
13
|
+
export { box } from "./deprecated/utils/box.js";
|
|
14
|
+
export type { ColorName, ColorFunction, } from "./deprecated/utils/deprecatedColors.js";
|
|
15
|
+
export { colors, getColor, colorize, } from "./deprecated/utils/deprecatedColors.js";
|
|
16
|
+
export { parseStack } from "./deprecated/utils/error.js";
|
|
17
|
+
export { compileFormat, formatString, } from "./deprecated/utils/format.js";
|
|
18
|
+
export { isPlainObject, isLogObj } from "./deprecated/utils/log.js";
|
|
19
|
+
export { writeStream } from "./deprecated/utils/stream.js";
|
|
20
|
+
export { stripAnsi, centerAlign, rightAlign, leftAlign, align, } from "./deprecated/utils/string.js";
|
|
21
|
+
export type { TreeItemObject, TreeItem, TreeOptions, } from "./deprecated/utils/tree.js";
|
|
22
|
+
export { formatTree } from "./deprecated/utils/tree.js";
|
|
23
|
+
export type { RelinkaSpecialDirsConfig, RelinkaDirsConfig, LogLevelConfig, LogLevelsConfig, LogLevel, RelinkaConfig, LogFileInfo, } from "./impl.js";
|
|
24
|
+
export { relinkaConfig, relinkaShutdown, flushAllLogBuffers, shouldNeverHappen, truncateString, casesHandled, relinka, relinkaAsync, defineConfig, } from "./impl.js";
|