@reliverse/relinka 1.3.6 β 1.3.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/README.md +114 -76
- package/bin/main.d.ts +1 -1
- package/bin/main.js +1 -1
- package/bin/relinka-impl/impl-mod.d.ts +9 -10
- package/bin/relinka-impl/impl-mod.js +146 -283
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,116 +1,154 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Relinka: Stylish Logging Made Simple
|
|
2
2
|
|
|
3
3
|
[π GitHub Sponsors](https://github.com/sponsors/blefnk) β’ [π¬ Discord](https://discord.gg/Pb8uKbwpsJ) β’ [β¨ Repo](https://github.com/reliverse/relinka-logger) β’ [π¦ NPM](https://npmjs.com/@reliverse/relinka) β’ [π Docs](https://docs.reliverse.org)
|
|
4
4
|
|
|
5
|
-
**@reliverse/relinka** is your next
|
|
5
|
+
**@reliverse/relinka** is your next favorite logging library β built to make your terminal (and browser console β soon) output look good, stay clean, and be actually helpful. Itβs styled, structured, and smart. Oh, and it works with configs, files, and colors out of the box.
|
|
6
6
|
|
|
7
|
-
## Features
|
|
7
|
+
## π Features
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- β¨ Packed with powerful features under the hood
|
|
18
|
-
- π Highly configurable flow via a configuration file
|
|
19
|
-
- π Plugin system with one built-in plugin included
|
|
9
|
+
- π§ Drop-in replacement for `node:console` and `consola`
|
|
10
|
+
- π¬ `relinka` supports: `info`, `warn`, `success`, `error`, `verbose`
|
|
11
|
+
- π¨ Beautiful, color-coded logs in the terminal
|
|
12
|
+
- π§ Auto-formats messages, objects, and errors
|
|
13
|
+
- π Save logs to file (with daily logs, cleanup, and rotation)
|
|
14
|
+
- π¦ Use it programmatically or through CLI-compatible tools
|
|
15
|
+
- βοΈ Smart customization via config
|
|
16
|
+
- β¨ Extensible and future-proof
|
|
20
17
|
|
|
21
|
-
## Getting Started
|
|
18
|
+
## π Getting Started
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
### 1. Install
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
```bash
|
|
23
|
+
bun add @reliverse/relinka
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
And, optionally, install the CLI globally to manage your config:
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
```bash
|
|
29
|
+
bun i -g @reliverse/relinka-cli
|
|
30
|
+
```
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
### 2. Basic Usage
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { relinkaConfig, relinka } from "@reliverse/relinka";
|
|
36
|
+
export async function main() {
|
|
37
|
+
await relinkaConfig;
|
|
38
|
+
relinka(
|
|
39
|
+
"verbose",
|
|
40
|
+
"This message can be seen only if config was loaded AND debug is enabled",
|
|
41
|
+
);
|
|
42
|
+
relinka("info", "Everything is running smoothly");
|
|
43
|
+
relinka("warn", "This might be a problem");
|
|
44
|
+
relinka("error", "Uh oh, something broke");
|
|
45
|
+
relinka("success", "Thanks for using Relinka! π");
|
|
46
|
+
}
|
|
47
|
+
await main();
|
|
34
48
|
```
|
|
35
49
|
|
|
36
|
-
|
|
50
|
+
## π§ͺ Advanced Usage
|
|
51
|
+
|
|
52
|
+
Want a clean blank line?
|
|
37
53
|
|
|
38
|
-
|
|
54
|
+
```ts
|
|
55
|
+
relinka("info", ""); // Just prints a newline
|
|
56
|
+
```
|
|
39
57
|
|
|
40
|
-
|
|
41
|
-
bun i -g @reliverse/relidler
|
|
42
|
-
```
|
|
58
|
+
π Use the async logger if you want some advanced features (like typing text animation - soon):
|
|
43
59
|
|
|
44
|
-
|
|
60
|
+
```ts
|
|
61
|
+
import { relinkaAsync } from "@reliverse/relinka";
|
|
45
62
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```
|
|
63
|
+
await relinkaAsync("info", "Something happened", { animate: true });
|
|
64
|
+
```
|
|
49
65
|
|
|
50
|
-
|
|
66
|
+
## βοΈ Configuration
|
|
67
|
+
|
|
68
|
+
Create a `relinka.config.ts` file with a content like:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { defineConfig } from "@reliverse/relinka";
|
|
72
|
+
export default defineConfig({
|
|
73
|
+
// Enable debug to see verbose logs
|
|
74
|
+
debug: true,
|
|
75
|
+
// Show timestamp in each log message
|
|
76
|
+
withTimestamp: false,
|
|
77
|
+
// Control whether logs are saved to a file
|
|
78
|
+
saveLogsToFile: true,
|
|
79
|
+
// Disable colors in the console
|
|
80
|
+
disableColors: false,
|
|
81
|
+
// Log file path
|
|
82
|
+
logFilePath: "relinka.log",
|
|
83
|
+
// Directory settings
|
|
84
|
+
dirs: {
|
|
85
|
+
dailyLogs: true,
|
|
86
|
+
logDir: ".reliverse/logs", // store logs in a custom folder
|
|
87
|
+
maxLogFiles: 5, // keep only the 5 most recent log files
|
|
88
|
+
specialDirs: {
|
|
89
|
+
distDirNames: [],
|
|
90
|
+
useParentConfigInDist: true,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
51
95
|
|
|
52
|
-
|
|
96
|
+
Supported config file names:
|
|
53
97
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
echo "dist-libs" >> .gitignore
|
|
59
|
-
```
|
|
98
|
+
- `relinka.config.ts`
|
|
99
|
+
- π `.relinka.config.js`
|
|
100
|
+
- π `.relinkarc`
|
|
101
|
+
- π or any other supported by c12
|
|
60
102
|
|
|
61
|
-
|
|
103
|
+
## π Log Files
|
|
62
104
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
105
|
+
- Stored in `.reliverse/logs/` by default
|
|
106
|
+
- Filename: `relinka.log` or `YYYY-MM-DD-relinka.log` if daily logs are enabled
|
|
107
|
+
- Auto-rotates based on `maxLogFiles`
|
|
66
108
|
|
|
67
|
-
|
|
109
|
+
## π API Summary
|
|
68
110
|
|
|
69
|
-
|
|
70
|
-
relidler
|
|
71
|
-
```
|
|
111
|
+
### relinka(level, message, ...args)
|
|
72
112
|
|
|
73
|
-
|
|
74
|
-
- **It's recommended to customize this file according to your needs.**
|
|
75
|
-
- Supported names: `relidler.cfg.ts` β’ `relidler.config.ts` β’ `build.pub.ts` β’ `build.cfg.ts`.
|
|
113
|
+
Logs synchronously. Skips debug logs if `debug: false`.
|
|
76
114
|
|
|
77
|
-
|
|
115
|
+
### relinkaAsync(level, message, ...args)
|
|
78
116
|
|
|
79
|
-
|
|
80
|
-
relidler
|
|
81
|
-
```
|
|
117
|
+
Async logger that waits for config automatically, and provides some additional advanced features.
|
|
82
118
|
|
|
83
|
-
|
|
119
|
+
### defineConfig(config)
|
|
84
120
|
|
|
85
|
-
|
|
121
|
+
Helper to define typed config in `relinka.config.ts`
|
|
86
122
|
|
|
87
|
-
|
|
123
|
+
## π§° Utilities
|
|
88
124
|
|
|
89
|
-
|
|
125
|
+
β
Timestamping
|
|
126
|
+
β
Log file rotation
|
|
127
|
+
β
File-safe formatting
|
|
128
|
+
β
ANSI color support
|
|
129
|
+
β
Error object handling
|
|
90
130
|
|
|
91
|
-
|
|
131
|
+
## π‘ Tips
|
|
92
132
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
```
|
|
133
|
+
- Want `@ts-expect-error` auto-injection? Check out [`@reliverse/reinject`](https://npmjs.com/@reliverse/reinject).
|
|
134
|
+
- Using this in a CLI tool? Combine with [`@reliverse/prompts`](https://npmjs.com/@reliverse/prompts).
|
|
96
135
|
|
|
97
|
-
## TODO
|
|
136
|
+
## β
TODO
|
|
98
137
|
|
|
99
|
-
- [x]
|
|
100
|
-
- [
|
|
101
|
-
- [
|
|
102
|
-
- [
|
|
103
|
-
- [
|
|
104
|
-
- [ ]
|
|
105
|
-
- [ ]
|
|
106
|
-
- [ ] Make config file optional with sensible defaults
|
|
138
|
+
- [x] File-based logging
|
|
139
|
+
- [x] Timestamp support
|
|
140
|
+
- [x] Daily logs
|
|
141
|
+
- [x] Smart config
|
|
142
|
+
- [x] Log rotation
|
|
143
|
+
- [ ] CLI interface (optional)
|
|
144
|
+
- [ ] Plugin support (custom formatters, log levels, etc)
|
|
107
145
|
|
|
108
|
-
##
|
|
146
|
+
## π Shoutout
|
|
109
147
|
|
|
110
|
-
|
|
148
|
+
Relinka was inspired by this gem:
|
|
111
149
|
|
|
112
150
|
- [unjs/consola](https://github.com/unjs/consola#readme)
|
|
113
151
|
|
|
114
|
-
## License
|
|
152
|
+
## π License
|
|
115
153
|
|
|
116
|
-
|
|
154
|
+
π MIT Β© [blefnk (Nazar Kornienko)](https://github.com/blefnk)
|
package/bin/main.d.ts
CHANGED
|
@@ -19,4 +19,4 @@ export { writeStream } from "./relinka-impl/deprecated/utils/stream.js";
|
|
|
19
19
|
export { stripAnsi, centerAlign, rightAlign, leftAlign, align, } from "./relinka-impl/deprecated/utils/string.js";
|
|
20
20
|
export type { TreeItemObject, TreeItem, TreeOptions, } from "./relinka-impl/deprecated/utils/tree.js";
|
|
21
21
|
export { formatTree } from "./relinka-impl/deprecated/utils/tree.js";
|
|
22
|
-
export {
|
|
22
|
+
export { relinkaConfig, relinka, relinkaAsync, defineConfig, } from "./relinka-impl/impl-mod.js";
|
package/bin/main.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
+
import type { RelinkaConfig, LogLevel } from "../relinka-types.js";
|
|
1
2
|
/**
|
|
2
|
-
* Promise that resolves
|
|
3
|
-
* from defaults, environment variables, and optionally config files.
|
|
3
|
+
* Promise that resolves once c12 loads and merges the config.
|
|
4
4
|
*/
|
|
5
|
-
export declare const
|
|
5
|
+
export declare const relinkaConfig: Promise<RelinkaConfig>;
|
|
6
6
|
/**
|
|
7
7
|
* Logs a message synchronously using the current config.
|
|
8
|
-
* Skips debug-level logs unless `debug` is
|
|
8
|
+
* Skips debug-level logs unless `debug` is true in the config.
|
|
9
9
|
*/
|
|
10
|
-
export declare
|
|
10
|
+
export declare function relinka(type: LogLevel, message: string, ...args: unknown[]): void;
|
|
11
11
|
/**
|
|
12
12
|
* Logs a message asynchronously, waiting for the config to be fully loaded.
|
|
13
|
-
* Also handles file writing and log cleanup if enabled
|
|
13
|
+
* Also handles file writing and log cleanup if enabled.
|
|
14
14
|
*/
|
|
15
|
-
export declare
|
|
15
|
+
export declare function relinkaAsync(type: LogLevel, message: string, ...args: unknown[]): Promise<void>;
|
|
16
16
|
/**
|
|
17
|
-
* Type helper for
|
|
18
|
-
* Provides autocompletion and type checking for the configuration object.
|
|
17
|
+
* Type helper for user config files.
|
|
19
18
|
*/
|
|
20
|
-
export declare
|
|
19
|
+
export declare function defineConfig(config: Partial<RelinkaConfig>): Partial<RelinkaConfig>;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { re } from "@reliverse/relico";
|
|
2
2
|
import { loadConfig } from "c12";
|
|
3
3
|
import fs from "fs-extra";
|
|
4
|
-
import { createJiti } from "jiti";
|
|
5
4
|
import path from "pathe";
|
|
5
|
+
const DEV_VERBOSE = false;
|
|
6
6
|
const DEFAULT_RELINKA_CONFIG = {
|
|
7
7
|
debug: false,
|
|
8
8
|
dirs: {
|
|
9
9
|
dailyLogs: false,
|
|
10
|
-
logDir: ".reliverse",
|
|
10
|
+
logDir: ".reliverse/logs",
|
|
11
11
|
maxLogFiles: 0,
|
|
12
12
|
specialDirs: {
|
|
13
13
|
distDirNames: ["dist", "dist-jsr", "dist-npm", "dist-libs"],
|
|
@@ -19,223 +19,92 @@ const DEFAULT_RELINKA_CONFIG = {
|
|
|
19
19
|
saveLogsToFile: false,
|
|
20
20
|
withTimestamp: false
|
|
21
21
|
};
|
|
22
|
-
const SUPPORTED_EXTENSIONS = [".ts", ".js", ".mjs", ".cjs", ".json"];
|
|
23
|
-
function isDebugEnabled(config) {
|
|
24
|
-
return config?.debug ?? DEFAULT_RELINKA_CONFIG.debug;
|
|
25
|
-
}
|
|
26
|
-
function isColorEnabled(config) {
|
|
27
|
-
return !(config?.disableColors ?? DEFAULT_RELINKA_CONFIG.disableColors);
|
|
28
|
-
}
|
|
29
|
-
function getLogDir(config) {
|
|
30
|
-
return config?.dirs?.logDir ?? DEFAULT_RELINKA_CONFIG.dirs.logDir;
|
|
31
|
-
}
|
|
32
|
-
function isDailyLogsEnabled(config) {
|
|
33
|
-
return config?.dirs?.dailyLogs ?? DEFAULT_RELINKA_CONFIG.dirs.dailyLogs;
|
|
34
|
-
}
|
|
35
|
-
function shouldSaveLogs(config) {
|
|
36
|
-
return config?.saveLogsToFile ?? DEFAULT_RELINKA_CONFIG.saveLogsToFile;
|
|
37
|
-
}
|
|
38
|
-
function getMaxLogFiles(config) {
|
|
39
|
-
return config?.dirs?.maxLogFiles ?? DEFAULT_RELINKA_CONFIG.dirs.maxLogFiles;
|
|
40
|
-
}
|
|
41
|
-
function getBaseLogName(config) {
|
|
42
|
-
return config?.logFilePath ?? DEFAULT_RELINKA_CONFIG.logFilePath;
|
|
43
|
-
}
|
|
44
|
-
const getEnvBoolean = (envVarName) => {
|
|
45
|
-
const value = process.env[envVarName];
|
|
46
|
-
if (value === void 0 || value === "") return void 0;
|
|
47
|
-
const lowerValue = value.toLowerCase().trim();
|
|
48
|
-
return !["0", "false"].includes(lowerValue);
|
|
49
|
-
};
|
|
50
|
-
const getEnvString = (envVarName) => {
|
|
51
|
-
return process.env[envVarName] || void 0;
|
|
52
|
-
};
|
|
53
|
-
const getEnvNumber = (envVarName) => {
|
|
54
|
-
const value = process.env[envVarName];
|
|
55
|
-
if (value === void 0) return void 0;
|
|
56
|
-
const parsed = Number.parseInt(value, 10);
|
|
57
|
-
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
58
|
-
};
|
|
59
|
-
const getEnvOverrides = () => {
|
|
60
|
-
const overrides = {};
|
|
61
|
-
const dirsOverride = {};
|
|
62
|
-
const specialDirsOverride = {};
|
|
63
|
-
const debug = getEnvBoolean("RELINKA_DEBUG");
|
|
64
|
-
if (debug !== void 0) overrides.debug = debug;
|
|
65
|
-
const withTimestamp = getEnvBoolean("RELINKA_TIMESTAMP");
|
|
66
|
-
if (withTimestamp !== void 0) overrides.withTimestamp = withTimestamp;
|
|
67
|
-
const disableColors = getEnvBoolean("RELINKA_DISABLE_COLORS");
|
|
68
|
-
if (disableColors !== void 0) overrides.disableColors = disableColors;
|
|
69
|
-
const saveLogsToFile = getEnvBoolean("RELINKA_SAVE_LOGS");
|
|
70
|
-
if (saveLogsToFile !== void 0) overrides.saveLogsToFile = saveLogsToFile;
|
|
71
|
-
const logFilePath = getEnvString("RELINKA_LOG_FILE");
|
|
72
|
-
if (logFilePath !== void 0) overrides.logFilePath = logFilePath;
|
|
73
|
-
const logDir = getEnvString("RELINKA_LOG_DIR");
|
|
74
|
-
if (logDir !== void 0) dirsOverride.logDir = logDir;
|
|
75
|
-
const dailyLogs = getEnvBoolean("RELINKA_DAILY_LOGS");
|
|
76
|
-
if (dailyLogs !== void 0) dirsOverride.dailyLogs = dailyLogs;
|
|
77
|
-
const maxLogFiles = getEnvNumber("RELINKA_MAX_LOG_FILES");
|
|
78
|
-
if (maxLogFiles !== void 0) dirsOverride.maxLogFiles = maxLogFiles;
|
|
79
|
-
const useParentConfig = getEnvBoolean("RELINKA_USE_PARENT_CONFIG");
|
|
80
|
-
if (useParentConfig !== void 0) {
|
|
81
|
-
specialDirsOverride.useParentConfigInDist = useParentConfig;
|
|
82
|
-
}
|
|
83
|
-
if (Object.keys(specialDirsOverride).length > 0) {
|
|
84
|
-
dirsOverride.specialDirs = specialDirsOverride;
|
|
85
|
-
}
|
|
86
|
-
if (Object.keys(dirsOverride).length > 0) {
|
|
87
|
-
overrides.dirs = dirsOverride;
|
|
88
|
-
}
|
|
89
|
-
return overrides;
|
|
90
|
-
};
|
|
91
22
|
let currentConfig = { ...DEFAULT_RELINKA_CONFIG };
|
|
92
23
|
let isConfigInitialized = false;
|
|
93
|
-
let
|
|
94
|
-
export const
|
|
95
|
-
|
|
24
|
+
let resolveRelinkaConfig;
|
|
25
|
+
export const relinkaConfig = new Promise((resolve) => {
|
|
26
|
+
resolveRelinkaConfig = resolve;
|
|
96
27
|
});
|
|
97
|
-
|
|
98
|
-
for (const ext of SUPPORTED_EXTENSIONS) {
|
|
99
|
-
const filePath = basePath + ext;
|
|
100
|
-
if (fs.existsSync(filePath)) {
|
|
101
|
-
return filePath;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return void 0;
|
|
105
|
-
};
|
|
106
|
-
const initializeConfig = async () => {
|
|
28
|
+
async function initializeConfig() {
|
|
107
29
|
try {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const loadedModule = await _jitiRequire.import(filePath);
|
|
121
|
-
const configData = loadedModule.default || loadedModule;
|
|
122
|
-
return configData;
|
|
123
|
-
} catch (error) {
|
|
124
|
-
console.error(
|
|
125
|
-
`[Relinka Config Error] Failed to load or parse config from ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
126
|
-
);
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
const reliverseBasePath = path.resolve(reliverseDir, configName);
|
|
131
|
-
const reliverseConfigFile = findConfigFile(reliverseBasePath);
|
|
132
|
-
if (reliverseConfigFile) {
|
|
133
|
-
const config = await loadAndExtract(reliverseConfigFile);
|
|
134
|
-
if (config !== null) {
|
|
135
|
-
return { config, source: reliverseConfigFile };
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
const rootBasePath = path.resolve(effectiveCwd, configName);
|
|
139
|
-
const rootConfigFile = findConfigFile(rootBasePath);
|
|
140
|
-
if (rootConfigFile) {
|
|
141
|
-
const config = await loadAndExtract(rootConfigFile);
|
|
142
|
-
if (config !== null) {
|
|
143
|
-
return { config, source: rootConfigFile };
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return null;
|
|
147
|
-
};
|
|
148
|
-
const loadedConfigResult = await loadConfig({
|
|
149
|
-
name: configName,
|
|
150
|
-
cwd: projectRoot,
|
|
151
|
-
resolve: resolveConfig,
|
|
152
|
-
packageJson: "relinka",
|
|
153
|
-
dotenv: true,
|
|
154
|
-
defaults: DEFAULT_RELINKA_CONFIG,
|
|
155
|
-
overrides: envOverrides
|
|
30
|
+
const result = await loadConfig({
|
|
31
|
+
name: "relinka",
|
|
32
|
+
// base name => tries relinka.config.*, .relinkarc, etc.
|
|
33
|
+
cwd: process.cwd(),
|
|
34
|
+
// working directory
|
|
35
|
+
dotenv: false,
|
|
36
|
+
packageJson: false,
|
|
37
|
+
rcFile: false,
|
|
38
|
+
globalRc: false,
|
|
39
|
+
defaults: DEFAULT_RELINKA_CONFIG
|
|
40
|
+
// lowest priority
|
|
41
|
+
// overrides: {}, // highest priority if we would need to forcibly override
|
|
156
42
|
});
|
|
157
|
-
currentConfig =
|
|
43
|
+
currentConfig = result.config;
|
|
158
44
|
isConfigInitialized = true;
|
|
159
|
-
|
|
160
|
-
(layer) => layer.source?.includes(configName)
|
|
161
|
-
);
|
|
162
|
-
if (customResolvedLayer?.source) {
|
|
45
|
+
if (DEV_VERBOSE) {
|
|
163
46
|
console.log(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
customResolvedLayer.source
|
|
167
|
-
)}`
|
|
47
|
+
"[Relinka Config Debug] Config file used:",
|
|
48
|
+
result.configFile
|
|
168
49
|
);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
(layer) => layer.source?.endsWith("package.json")
|
|
172
|
-
);
|
|
173
|
-
if (pkgJsonLayer?.config) {
|
|
174
|
-
console.log("[Relinka Config] Loaded configuration from package.json.");
|
|
175
|
-
} else {
|
|
176
|
-
console.log(
|
|
177
|
-
"[Relinka Config] No config file or package.json entry found. Using defaults and environment variables."
|
|
178
|
-
);
|
|
179
|
-
}
|
|
50
|
+
console.log("[Relinka Config Debug] All merged layers:", result.layers);
|
|
51
|
+
console.log("[Relinka Config Debug] Final configuration:", currentConfig);
|
|
180
52
|
}
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
"[Relinka Config Debug] Final configuration object:",
|
|
184
|
-
JSON.stringify(currentConfig, null, 2)
|
|
185
|
-
);
|
|
186
|
-
console.log(
|
|
187
|
-
"[Relinka Config Debug] Resolved layers:",
|
|
188
|
-
loadedConfigResult.layers?.map((l) => ({
|
|
189
|
-
config: l.config ? "[Object]" : null,
|
|
190
|
-
source: l.source ? path.relative(projectRoot, l.source) : void 0,
|
|
191
|
-
meta: l.meta
|
|
192
|
-
}))
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
if (resolveConfigPromise) {
|
|
196
|
-
resolveConfigPromise(currentConfig);
|
|
53
|
+
if (resolveRelinkaConfig) {
|
|
54
|
+
resolveRelinkaConfig(currentConfig);
|
|
197
55
|
}
|
|
198
|
-
|
|
199
|
-
} catch (error) {
|
|
56
|
+
} catch (err) {
|
|
200
57
|
console.error(
|
|
201
|
-
`[Relinka Config Error] Failed
|
|
58
|
+
`[Relinka Config Error] Failed to load config: ${err instanceof Error ? err.message : String(err)}`
|
|
202
59
|
);
|
|
203
60
|
currentConfig = { ...DEFAULT_RELINKA_CONFIG };
|
|
204
61
|
isConfigInitialized = true;
|
|
205
|
-
if (
|
|
206
|
-
|
|
62
|
+
if (resolveRelinkaConfig) {
|
|
63
|
+
resolveRelinkaConfig(currentConfig);
|
|
207
64
|
}
|
|
208
|
-
return currentConfig;
|
|
209
65
|
}
|
|
210
|
-
}
|
|
66
|
+
}
|
|
211
67
|
initializeConfig().catch((err) => {
|
|
212
68
|
console.error(
|
|
213
|
-
`[Relinka Config Error] Unhandled error
|
|
69
|
+
`[Relinka Config Error] Unhandled error: ${err instanceof Error ? err.message : String(err)}`
|
|
214
70
|
);
|
|
215
71
|
if (!isConfigInitialized) {
|
|
216
72
|
currentConfig = { ...DEFAULT_RELINKA_CONFIG };
|
|
217
73
|
isConfigInitialized = true;
|
|
218
|
-
if (
|
|
219
|
-
|
|
74
|
+
if (resolveRelinkaConfig) {
|
|
75
|
+
resolveRelinkaConfig(currentConfig);
|
|
220
76
|
}
|
|
221
77
|
}
|
|
222
78
|
});
|
|
223
|
-
|
|
224
|
-
|
|
79
|
+
function isDebugEnabled(config) {
|
|
80
|
+
return config.debug ?? DEFAULT_RELINKA_CONFIG.debug;
|
|
81
|
+
}
|
|
82
|
+
function isColorEnabled(config) {
|
|
83
|
+
return !(config.disableColors ?? DEFAULT_RELINKA_CONFIG.disableColors);
|
|
84
|
+
}
|
|
85
|
+
function getLogDir(config) {
|
|
86
|
+
return config.dirs?.logDir ?? DEFAULT_RELINKA_CONFIG.dirs.logDir;
|
|
87
|
+
}
|
|
88
|
+
function isDailyLogsEnabled(config) {
|
|
89
|
+
return config.dirs?.dailyLogs ?? DEFAULT_RELINKA_CONFIG.dirs.dailyLogs;
|
|
90
|
+
}
|
|
91
|
+
function shouldSaveLogs(config) {
|
|
92
|
+
return config.saveLogsToFile ?? DEFAULT_RELINKA_CONFIG.saveLogsToFile;
|
|
93
|
+
}
|
|
94
|
+
function getMaxLogFiles(config) {
|
|
95
|
+
return config.dirs?.maxLogFiles ?? DEFAULT_RELINKA_CONFIG.dirs.maxLogFiles;
|
|
96
|
+
}
|
|
97
|
+
function getBaseLogName(config) {
|
|
98
|
+
return config.logFilePath ?? DEFAULT_RELINKA_CONFIG.logFilePath;
|
|
99
|
+
}
|
|
100
|
+
function getTimestamp(config) {
|
|
101
|
+
if (!config.withTimestamp) return "";
|
|
225
102
|
const now = /* @__PURE__ */ new Date();
|
|
226
|
-
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
).padStart(2, "0")}:${String(now.getMinutes()).padStart(
|
|
232
|
-
2,
|
|
233
|
-
"0"
|
|
234
|
-
)}:${String(now.getSeconds()).padStart(2, "0")}.${String(
|
|
235
|
-
now.getMilliseconds()
|
|
236
|
-
).padStart(3, "0")}`;
|
|
237
|
-
};
|
|
238
|
-
const getLogFilePath = (config) => {
|
|
103
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(
|
|
104
|
+
now.getMinutes()
|
|
105
|
+
).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}.${String(now.getMilliseconds()).padStart(3, "0")}`;
|
|
106
|
+
}
|
|
107
|
+
function getLogFilePath(config) {
|
|
239
108
|
const logDir = getLogDir(config);
|
|
240
109
|
const daily = isDailyLogsEnabled(config);
|
|
241
110
|
let finalLogName = getBaseLogName(config);
|
|
@@ -251,8 +120,8 @@ const getLogFilePath = (config) => {
|
|
|
251
120
|
}
|
|
252
121
|
const effectiveLogName = finalLogName || "relinka.log";
|
|
253
122
|
return path.resolve(process.cwd(), logDir, effectiveLogName);
|
|
254
|
-
}
|
|
255
|
-
|
|
123
|
+
}
|
|
124
|
+
function formatLogMessage(config, level, msg, details) {
|
|
256
125
|
const timestamp = getTimestamp(config);
|
|
257
126
|
let detailsStr = "";
|
|
258
127
|
if (details !== void 0) {
|
|
@@ -271,101 +140,101 @@ Stack Trace: ${details.stack || details.message}`;
|
|
|
271
140
|
}
|
|
272
141
|
const paddedLevel = level.padEnd(7, " ");
|
|
273
142
|
return timestamp ? `[${timestamp}] ${paddedLevel} ${msg}${detailsStr}` : `${paddedLevel} ${msg}${detailsStr}`;
|
|
274
|
-
}
|
|
275
|
-
|
|
143
|
+
}
|
|
144
|
+
function logToConsole(config, level, formattedMessage) {
|
|
145
|
+
const COLOR_RESET = "\x1B[0m";
|
|
276
146
|
if (!isColorEnabled(config)) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
default:
|
|
282
|
-
console.log(formattedMessage);
|
|
147
|
+
if (level === "ERROR") {
|
|
148
|
+
console.error(formattedMessage);
|
|
149
|
+
} else {
|
|
150
|
+
console.log(formattedMessage);
|
|
283
151
|
}
|
|
284
152
|
} else {
|
|
285
153
|
switch (level) {
|
|
286
154
|
case "ERROR":
|
|
287
|
-
console.error(re.redBright(formattedMessage));
|
|
155
|
+
console.error(re.redBright(formattedMessage) + COLOR_RESET);
|
|
288
156
|
break;
|
|
289
157
|
case "WARN":
|
|
290
|
-
console.warn(re.yellowBright(formattedMessage));
|
|
158
|
+
console.warn(re.yellowBright(formattedMessage) + COLOR_RESET);
|
|
291
159
|
break;
|
|
292
160
|
case "SUCCESS":
|
|
293
|
-
console.log(re.greenBright(formattedMessage));
|
|
161
|
+
console.log(re.greenBright(formattedMessage) + COLOR_RESET);
|
|
294
162
|
break;
|
|
295
163
|
case "INFO":
|
|
296
|
-
console.log(re.cyanBright(formattedMessage));
|
|
164
|
+
console.log(re.cyanBright(formattedMessage) + COLOR_RESET);
|
|
297
165
|
break;
|
|
298
166
|
default:
|
|
299
|
-
console.log(re.dim(formattedMessage));
|
|
167
|
+
console.log(re.dim(formattedMessage) + COLOR_RESET);
|
|
300
168
|
}
|
|
301
169
|
}
|
|
302
|
-
}
|
|
303
|
-
|
|
170
|
+
}
|
|
171
|
+
async function getLogFilesSortedByDate(config) {
|
|
304
172
|
const logDirectoryPath = path.resolve(process.cwd(), getLogDir(config));
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
`[Relinka FS Debug] Log directory does not exist: ${logDirectoryPath}`
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
return [];
|
|
173
|
+
if (!await fs.pathExists(logDirectoryPath)) {
|
|
174
|
+
if (isDebugEnabled(config)) {
|
|
175
|
+
console.log(
|
|
176
|
+
`[Relinka FS Debug] Log directory not found: ${logDirectoryPath}`
|
|
177
|
+
);
|
|
314
178
|
}
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
315
182
|
const files = await fs.readdir(logDirectoryPath);
|
|
316
|
-
const logFilesPromises = files.filter((
|
|
317
|
-
const filePath = path.join(logDirectoryPath,
|
|
183
|
+
const logFilesPromises = files.filter((f) => f.endsWith(".log")).map(async (f) => {
|
|
184
|
+
const filePath = path.join(logDirectoryPath, f);
|
|
318
185
|
try {
|
|
319
186
|
const stats = await fs.stat(filePath);
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
187
|
+
if (stats.isFile()) {
|
|
188
|
+
return { path: filePath, mtime: stats.mtime.getTime() };
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
} catch (err) {
|
|
192
|
+
if (isDebugEnabled(config)) {
|
|
323
193
|
console.error(
|
|
324
|
-
`[Relinka FS Debug] Error
|
|
194
|
+
`[Relinka FS Debug] Error reading stats for ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
325
195
|
);
|
|
326
196
|
}
|
|
327
197
|
return null;
|
|
328
198
|
}
|
|
329
199
|
});
|
|
330
200
|
const logFiles = (await Promise.all(logFilesPromises)).filter(
|
|
331
|
-
(
|
|
201
|
+
(f) => Boolean(f)
|
|
332
202
|
);
|
|
333
203
|
return logFiles.sort((a, b) => b.mtime - a.mtime);
|
|
334
|
-
} catch (
|
|
335
|
-
if (
|
|
204
|
+
} catch (readErr) {
|
|
205
|
+
if (isDebugEnabled(config)) {
|
|
336
206
|
console.error(
|
|
337
|
-
`[Relinka FS Error]
|
|
207
|
+
`[Relinka FS Error] Failed reading log directory ${logDirectoryPath}: ${readErr instanceof Error ? readErr.message : String(readErr)}`
|
|
338
208
|
);
|
|
339
209
|
}
|
|
340
210
|
return [];
|
|
341
211
|
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
212
|
+
}
|
|
213
|
+
async function deleteFiles(filePaths, config) {
|
|
214
|
+
await Promise.all(
|
|
215
|
+
filePaths.map(async (filePath) => {
|
|
216
|
+
try {
|
|
217
|
+
await fs.unlink(filePath);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (isDebugEnabled(config)) {
|
|
220
|
+
console.error(
|
|
221
|
+
`[Relinka FS Error] Failed to delete log file ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
353
224
|
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const maxFiles = getMaxLogFiles(config);
|
|
360
|
-
const debugEnabled = isDebugEnabled(config);
|
|
361
|
-
if (!shouldSaveLogs(config) || maxFiles <= 0) return;
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
async function cleanupOldLogFiles(config) {
|
|
229
|
+
if (!shouldSaveLogs(config) || getMaxLogFiles(config) <= 0) return;
|
|
362
230
|
try {
|
|
363
231
|
const sortedLogFiles = await getLogFilesSortedByDate(config);
|
|
232
|
+
const maxFiles = getMaxLogFiles(config);
|
|
364
233
|
if (sortedLogFiles.length > maxFiles) {
|
|
365
234
|
const filesToDelete = sortedLogFiles.slice(maxFiles).map((f) => f.path);
|
|
366
235
|
if (filesToDelete.length > 0) {
|
|
367
236
|
await deleteFiles(filesToDelete, config);
|
|
368
|
-
if (
|
|
237
|
+
if (isDebugEnabled(config)) {
|
|
369
238
|
console.log(
|
|
370
239
|
`[Relinka Cleanup] Deleted ${filesToDelete.length} old log file(s). Kept ${maxFiles}.`
|
|
371
240
|
);
|
|
@@ -373,82 +242,76 @@ const cleanupOldLogFiles = async (config) => {
|
|
|
373
242
|
}
|
|
374
243
|
}
|
|
375
244
|
} catch (err) {
|
|
376
|
-
if (
|
|
245
|
+
if (isDebugEnabled(config)) {
|
|
377
246
|
console.error(
|
|
378
247
|
`[Relinka Cleanup Error] Failed during log cleanup: ${err instanceof Error ? err.message : String(err)}`
|
|
379
248
|
);
|
|
380
249
|
}
|
|
381
250
|
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const debugEnabled = isDebugEnabled(config);
|
|
251
|
+
}
|
|
252
|
+
async function appendToLogFile(config, absoluteLogFilePath, logMessage) {
|
|
385
253
|
try {
|
|
386
254
|
await fs.ensureDir(path.dirname(absoluteLogFilePath));
|
|
387
255
|
await fs.appendFile(absoluteLogFilePath, `${logMessage}
|
|
388
256
|
`);
|
|
389
257
|
} catch (err) {
|
|
390
|
-
if (
|
|
258
|
+
if (isDebugEnabled(config)) {
|
|
391
259
|
console.error(
|
|
392
260
|
`[Relinka File Error] Failed to write to log file ${absoluteLogFilePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
393
261
|
);
|
|
394
262
|
}
|
|
395
263
|
}
|
|
396
|
-
}
|
|
397
|
-
export
|
|
398
|
-
const configToUse = currentConfig;
|
|
264
|
+
}
|
|
265
|
+
export function relinka(type, message, ...args) {
|
|
399
266
|
if (message === "") {
|
|
400
267
|
console.log();
|
|
401
268
|
return;
|
|
402
269
|
}
|
|
403
270
|
const logLevelLabel = type === "verbose" ? "DEBUG" : type.toUpperCase();
|
|
404
|
-
if (logLevelLabel === "DEBUG" && !isDebugEnabled(
|
|
271
|
+
if (logLevelLabel === "DEBUG" && !isDebugEnabled(currentConfig)) {
|
|
405
272
|
return;
|
|
406
273
|
}
|
|
407
|
-
const details = args.length >
|
|
408
|
-
const
|
|
409
|
-
|
|
274
|
+
const details = args.length > 1 ? args : args[0];
|
|
275
|
+
const formatted = formatLogMessage(
|
|
276
|
+
currentConfig,
|
|
410
277
|
logLevelLabel,
|
|
411
278
|
message,
|
|
412
279
|
details
|
|
413
280
|
);
|
|
414
|
-
logToConsole(
|
|
415
|
-
}
|
|
416
|
-
export
|
|
417
|
-
|
|
281
|
+
logToConsole(currentConfig, logLevelLabel, formatted);
|
|
282
|
+
}
|
|
283
|
+
export async function relinkaAsync(type, message, ...args) {
|
|
284
|
+
await relinkaConfig;
|
|
418
285
|
if (message === "") {
|
|
419
286
|
console.log();
|
|
420
287
|
return;
|
|
421
288
|
}
|
|
422
289
|
const logLevelLabel = type === "verbose" ? "DEBUG" : type.toUpperCase();
|
|
423
|
-
if (logLevelLabel === "DEBUG" && !isDebugEnabled(
|
|
290
|
+
if (logLevelLabel === "DEBUG" && !isDebugEnabled(currentConfig)) {
|
|
424
291
|
return;
|
|
425
292
|
}
|
|
426
|
-
const details = args.length >
|
|
427
|
-
const
|
|
428
|
-
|
|
293
|
+
const details = args.length > 1 ? args : args[0];
|
|
294
|
+
const formatted = formatLogMessage(
|
|
295
|
+
currentConfig,
|
|
429
296
|
logLevelLabel,
|
|
430
297
|
message,
|
|
431
298
|
details
|
|
432
299
|
);
|
|
433
|
-
logToConsole(
|
|
434
|
-
if (shouldSaveLogs(
|
|
435
|
-
const absoluteLogFilePath = getLogFilePath(
|
|
300
|
+
logToConsole(currentConfig, logLevelLabel, formatted);
|
|
301
|
+
if (shouldSaveLogs(currentConfig)) {
|
|
302
|
+
const absoluteLogFilePath = getLogFilePath(currentConfig);
|
|
436
303
|
try {
|
|
437
|
-
await appendToLogFile(
|
|
438
|
-
|
|
439
|
-
absoluteLogFilePath,
|
|
440
|
-
formattedMessage
|
|
441
|
-
);
|
|
442
|
-
await cleanupOldLogFiles(loadedConfig);
|
|
304
|
+
await appendToLogFile(currentConfig, absoluteLogFilePath, formatted);
|
|
305
|
+
await cleanupOldLogFiles(currentConfig);
|
|
443
306
|
} catch (err) {
|
|
444
|
-
if (isDebugEnabled(
|
|
307
|
+
if (isDebugEnabled(currentConfig)) {
|
|
445
308
|
console.error(
|
|
446
|
-
`[Relinka File Async Error] Error during file logging/cleanup
|
|
309
|
+
`[Relinka File Async Error] Error during file logging/cleanup: ${err instanceof Error ? err.message : String(err)}`
|
|
447
310
|
);
|
|
448
311
|
}
|
|
449
312
|
}
|
|
450
313
|
}
|
|
451
|
-
}
|
|
452
|
-
export
|
|
314
|
+
}
|
|
315
|
+
export function defineConfig(config) {
|
|
453
316
|
return config;
|
|
454
|
-
}
|
|
317
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "@reliverse/relinka is
|
|
2
|
+
"description": "@reliverse/relinka is your next favorite logging library β built to make your terminal (and browser console β soon) output look good, stay clean, and be actually helpful. Itβs styled, structured, and smart. Oh, and it works with configs, files, and colors out of the box.",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@reliverse/relinka",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "1.3.
|
|
6
|
+
"version": "1.3.7",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@reliverse/relico": "^1.0
|
|
8
|
+
"@reliverse/relico": "^1.1.0",
|
|
9
9
|
"@reliverse/runtime": "^1.0.3",
|
|
10
10
|
"c12": "^3.0.2",
|
|
11
11
|
"defu": "^6.1.4",
|