@reliverse/relinka 1.5.2 → 1.5.3

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 CHANGED
@@ -1,23 +1,23 @@
1
1
  # Relinka: Logging that Actually Feels Good
2
2
 
3
- > **@reliverse/relinka** is a modern, minimal logging library that actually *feels* right. It's not just pretty output — it's a system: smart formatting, file-safe logging, runtime config support, and a `fatal` mode built for developers who care about correctness. Whether you're building CLI tools, SDKs, or full-stack apps — Relinka helps you log with intention.
4
-
5
3
  [sponsor](https://github.com/sponsors/blefnk) — [discord](https://discord.gg/Pb8uKbwpsJ) — [repo](https://github.com/reliverse/relinka) — [npm](https://npmjs.com/@reliverse/relinka)
6
4
 
7
- ## Why Relinka
5
+ > **@reliverse/relinka** is a modern logging library that actually *feels* right. It's not just pretty output — it's a system: smart formatting, file-safe logging, runtime config support, and a `fatal` mode built for developers who care about correctness. Whether you're building CLI tools, SDKs, or full-stack apps — Relinka helps you log with intention.
8
6
 
9
- - 🧙 Drop-in replacement for `node:console`, `consola`, or your internal logger
10
- - 💬 Supports: `info`, `warn`, `success`, `error`, `verbose`, `fatal`, `clear`
11
- - 🎨 Terminal output that *pops*, with automatic color handling
12
- - 📁 Save logs to file (with daily rotation, cleanup, and max-file limits)
13
- - 🧠 Structured message formatting (objects, errors, stacks — handled!)
14
- - ⚙️ Runtime config via `relinka.config.ts` (powered by `reconf`)
15
- - 🚨 `fatal` logs halt execution and trigger `debugger` in dev
16
- - 🧩 Sync-first, async, & CLI-friendly thanks to buffer flushing
7
+ ## Why Relinka
17
8
 
18
- ## Getting Started
9
+ - 🧙 **Drop-in replacement** for `node:console`, `consola`, or your internal logger
10
+ - 💬 **12 log levels**: `info`, `warn`, `success`, `error`, `verbose`, `fatal`, `log`, `step`, `box`, `message`, `internal`, `null`
11
+ - 🎨 **Beautiful terminal output** with automatic color handling and Unicode support
12
+ - 📁 **Smart file logging** with buffering, rotation, cleanup, and date-based naming
13
+ - 🧠 **Structured formatting** for objects, errors, and stack traces
14
+ - ⚙️ **Runtime configuration** via `.config/relinka.ts` or `relinka.config.ts`
15
+ - 🚨 **Fatal logging** that halts execution and triggers debugger in development
16
+ - 🧩 **Dual syntax support** - both function calls (`relinka("info", "Hello world")`) and method chaining (`relinka.info("Hello world")`)
17
+ - ⚡ **Performance optimized** with intelligent buffering and async support
18
+ - 🛎️ **Dler-ready** - if you use `dler build`, all `internal`-level logs will be removed from the built dist
19
19
 
20
- Make sure you have git, node.js, and bun/pnpm/yarn/npm installed.
20
+ ## Quick Start
21
21
 
22
22
  ### 1. Install
23
23
 
@@ -25,158 +25,173 @@ Make sure you have git, node.js, and bun/pnpm/yarn/npm installed.
25
25
  bun add @reliverse/relinka
26
26
  ```
27
27
 
28
- **Coming soon**:
28
+ ### 2. Basic Usage
29
29
 
30
- ```bash
31
- bun i -g @reliverse/dler
32
- dler relinka --console-to-relinka
30
+ ```ts
31
+ import { relinka, relinkaConfig, relinkaShutdown } from "@reliverse/relinka";
32
+
33
+ async function main() {
34
+ // Load configuration (required at start)
35
+ await relinkaConfig();
36
+
37
+ // Log with different levels
38
+ relinka("info", "Application started");
39
+ relinka("success", "Configuration loaded successfully");
40
+ relinka("warn", "This is a warning");
41
+ relinka("error", "Something went wrong");
42
+
43
+ // Use method syntax (also supported)
44
+ relinka.info("Another info message");
45
+ relinka.success("Another success message");
46
+
47
+ // Clean shutdown (required at end)
48
+ await relinkaShutdown();
49
+ }
50
+
51
+ main();
33
52
  ```
34
53
 
35
- ### 2. Use It
54
+ ## Log Levels
55
+
56
+ Relinka supports 12 different log levels, each with customizable symbols and colors:
57
+
58
+ | Level | Symbol | Description |
59
+ |-------|--------|-------------|
60
+ | `info` | ◈ | General information |
61
+ | `success` | ✓ | Success messages |
62
+ | `warn` | ⚠ | Warnings |
63
+ | `error` | ✖ | Non-fatal errors |
64
+ | `fatal` | ‼ | Fatal errors + error throwing |
65
+ | `verbose` | ✱ | Debug information |
66
+ | `log` | │ | General logging |
67
+ | `step` | → | Progress steps |
68
+ | `box` | ■ | Boxed messages |
69
+ | `message` | 🞠 | General messages |
70
+ | `internal` | ⚙ | Internal system logs |
71
+ | `null` | (none) | No symbol or spacing |
36
72
 
37
- #### Direct Method (Recommended)
73
+ ## API Reference
38
74
 
39
- - Place this **at the START** of your app's main function:
75
+ ### Core Functions
76
+
77
+ #### `relinka(level, message, ...args)`
78
+
79
+ Main logging function with dual syntax support.
40
80
 
41
81
  ```ts
42
- await relinkaConfig;
82
+ // Function syntax
83
+ relinka("info", "Hello world");
84
+ relinka("error", "Something broke", { error: "details" });
85
+
86
+ // Method syntax
87
+ relinka.info("Hello world");
88
+ relinka.error("Something broke", { error: "details" });
43
89
  ```
44
90
 
45
- - Place this **at the END** of your app's main function:
91
+ #### `relinkaAsync(level, message, ...args)`
92
+
93
+ Async version that waits for configuration to load.
46
94
 
47
95
  ```ts
48
- await relinkaShutdown();
96
+ await relinkaAsync("info", "Async message");
97
+ await relinkaAsync("success", "Operation completed");
49
98
  ```
50
99
 
51
- **Usage example**:
100
+ #### `relinkaConfig(options?)`
101
+
102
+ Load configuration with optional fresh log file support.
52
103
 
53
104
  ```ts
54
- import {
55
- relinka,
56
- relinkaAsync,
57
- relinkaConfig,
58
- relinkaShutdown,
59
- } from "@reliverse/relinka";
60
-
61
- export async function main() {
62
- await relinkaAsync(
63
- // this automatically loads the config
64
- "verbose",
65
- "This ASYNC verbose message can be seen only if verbose=true (in user config)",
66
- );
67
- await relinkaConfig; // place this at your main function or just at the top of your entry file
68
- relinka(
69
- "verbose",
70
- "This SYNC verbose message can be seen only if verbose=true (in user config) AND config was loaded ",
71
- );
72
-
73
- // --- BOX LEVEL EXAMPLES ---
74
- relinka("box", "This is a boxed message using direct syntax!");
75
- relinka.box("This is a boxed message using method syntax!");
76
-
77
- // --- MESSAGE LEVEL EXAMPLES ---
78
- relinka("message", "This is a message using direct syntax!");
79
- relinka.message("This is a message using method syntax!");
80
-
81
- // --- STEP LEVEL EXAMPLES ---
82
- relinka("step", "Step 1: Initialize application");
83
- relinka.step("Step 2: Load configuration");
84
- relinka.step("Step 3: Start services");
85
-
86
- // --- LOG LEVEL EXAMPLES ---
87
- relinka("log", "Hello! 👋");
88
- relinka("log", "Great to see you here!");
89
- relinka("info", "Everything is running smoothly");
90
- relinka("warn", "This might be a problem");
91
- relinka(
92
- "error", // non-fatal issue level can be recovered
93
- "Uh oh, something broke",
94
- );
95
-
96
- relinka(
97
- "null",
98
- "'null' level has a special handling case: no symbol or spacing",
99
- );
100
-
101
- // relinka(
102
- // "fatal",
103
- // "We should never reach this code! This should never happen! (see <anonymous> line)",
104
- // ); // fatal level throws error and halts execution
105
- relinka("success", "Thanks for using Relinka!");
106
-
107
- // Make sure to shut down the logger at the end of your program
108
- // This is important to flush all buffers and close file handles
109
- await relinkaShutdown();
105
+ // Basic config loading
106
+ await relinkaConfig();
110
107
 
111
- // Make sure to exit the program after your CLI is done
112
- // It's not required for Bun-only apps, but recommended
113
- // for other terminal runtimes like Node.js (incl. `tsx`)
114
- // It's also not required for @reliverse/rempts `runMain()`
115
- process.exit(0);
116
- }
108
+ // With fresh log file (clears existing log file)
109
+ await relinkaConfig({ supportFreshLogFile: true });
110
+ ```
111
+
112
+ #### `relinkaShutdown()`
117
113
 
118
- await main();
114
+ Flush all buffers and clean up resources. **Always call this at the end of your program.**
115
+
116
+ ```ts
117
+ await relinkaShutdown();
119
118
  ```
120
119
 
121
- #### [🔜 Soon] Singleton Method
120
+ ### Utility Functions
121
+
122
+ #### `log`, `logger`
123
+
124
+ Aliases for the main `relinka` function.
122
125
 
123
126
  ```ts
124
- const logger = initRelinkaInstance({/* per-project config */});
125
- logger("info", "Looks great!");
127
+ import { log, logger } from "@reliverse/relinka";
128
+
129
+ log("info", "Using alias");
130
+ logger.success("Another alias");
126
131
  ```
127
132
 
128
- #### [🔜 Soon] Object Method
133
+ #### `message(msg, ...args)`
134
+
135
+ Convenience function for general messages.
129
136
 
130
137
  ```ts
131
- await relinkaConfig;
132
- relinka.info("Looks great!");
138
+ import { message } from "@reliverse/relinka";
139
+ message("This is a general message");
133
140
  ```
134
141
 
135
- ## Advanced Usage
142
+ #### `step(msg, ...args)`
143
+
144
+ Convenience function for progress steps.
136
145
 
137
146
  ```ts
138
- // Clear terminal:
139
- relinka("clear", "");
140
- // Blank line:
141
- relinka("info", "");
142
- // Async variant:
143
- import { relinkaAsync } from "@reliverse/relinka";
144
- await relinkaAsync("info", "Logged from async context");
145
- // Coming soon:
146
- await relinkaAsync("info", "Something happened", { animate: true });
147
+ import { step } from "@reliverse/relinka";
148
+ step("Step 1: Initialize");
149
+ step("Step 2: Load config");
147
150
  ```
148
151
 
149
- ## Config
152
+ ## Configuration
153
+
154
+ Create a configuration file to customize Relinka's behavior:
155
+
156
+ ### File Locations (in order of priority)
150
157
 
151
- Create `relinka.config.ts`:
158
+ 1. `relinka.config.ts` (project root) - **highest priority**
159
+ 2. `.config/relinka.ts` - **lower priority**
160
+
161
+ ### Configuration Example
152
162
 
153
163
  ```ts
154
164
  import { defineConfig } from "@reliverse/relinka";
155
- /**
156
- * RELINKA CONFIGURATION FILE
157
- * - Hover over a field to see the information
158
- * - Use intellisense to see available options
159
- * @see https://github.com/reliverse/relinka
160
- */
165
+
161
166
  export default defineConfig({
162
- // Enable to see verbose logs
167
+ // Enable verbose logging
163
168
  verbose: false,
164
169
 
165
170
  // Timestamp configuration
166
171
  timestamp: {
167
- enabled: false,
168
- format: "HH:mm:ss",
172
+ enabled: true,
173
+ format: "YYYY-MM-DD HH:mm:ss.SSS",
169
174
  },
170
175
 
171
- // Control whether logs are saved to a file
176
+ // File logging
172
177
  saveLogsToFile: true,
178
+ logFile: {
179
+ outputPath: "logs/app.log",
180
+ nameWithDate: "append-before", // "disable" | "append-before" | "append-after"
181
+ freshLogFile: true, // Clear log file on each run
182
+ },
173
183
 
174
- // Disable colors in the console
175
- disableColors: false,
184
+ // Log rotation
185
+ dirs: {
186
+ maxLogFiles: 10, // Keep only the 10 most recent log files
187
+ },
176
188
 
177
- // Log file path
178
- logFilePath: "relinka.log",
189
+ // Performance tuning
190
+ bufferSize: 4096, // 4KB buffer before flushing
191
+ maxBufferAge: 5000, // 5 seconds max buffer age
192
+ cleanupInterval: 10000, // 10 seconds between cleanups
179
193
 
194
+ // Customize log levels
180
195
  levels: {
181
196
  success: {
182
197
  symbol: "✓",
@@ -185,138 +200,208 @@ export default defineConfig({
185
200
  spacing: 3,
186
201
  },
187
202
  info: {
188
- symbol: "i",
203
+ symbol: "",
189
204
  fallbackSymbol: "[i]",
190
205
  color: "cyanBright",
191
206
  spacing: 3,
192
207
  },
193
- error: {
194
- symbol: "✖",
195
- fallbackSymbol: "[ERR]",
196
- color: "redBright",
197
- spacing: 3,
198
- },
199
- warn: {
200
- symbol: "⚠",
201
- fallbackSymbol: "[WARN]",
202
- color: "yellowBright",
203
- spacing: 3,
204
- },
205
- fatal: {
206
- symbol: "‼",
207
- fallbackSymbol: "[FATAL]",
208
- color: "redBright",
209
- spacing: 3,
210
- },
211
- verbose: {
212
- symbol: "✧",
213
- fallbackSymbol: "[VERBOSE]",
214
- color: "gray",
215
- spacing: 3,
216
- },
217
- log: { symbol: "│", fallbackSymbol: "|", color: "dim", spacing: 3 },
218
- },
219
-
220
- // Directory settings
221
- dirs: {
222
- dailyLogs: true,
223
- logDir: "logs", // store logs in a custom folder
224
- maxLogFiles: 5, // keep only the 5 most recent log files
225
- specialDirs: {
226
- distDirNames: [],
227
- useParentConfigInDist: true,
228
- },
208
+ // ... customize other levels
229
209
  },
230
210
  });
231
211
  ```
232
212
 
233
- Supported files:
213
+ ### Configuration Options
214
+
215
+ | Option | Type | Default | Description |
216
+ |--------|------|---------|-------------|
217
+ | `verbose` | `boolean` | `false` | Enable verbose logging |
218
+ | `saveLogsToFile` | `boolean` | `false` | Save logs to file |
219
+ | `disableColors` | `boolean` | `false` | Disable color output |
220
+ | `timestamp.enabled` | `boolean` | `false` | Add timestamps to logs |
221
+ | `timestamp.format` | `string` | `"YYYY-MM-DD HH:mm:ss.SSS"` | Timestamp format |
222
+ | `logFile.outputPath` | `string` | `"logs.log"` | Log file path |
223
+ | `logFile.nameWithDate` | `string` | `"disable"` | Date handling in filename |
224
+ | `logFile.freshLogFile` | `boolean` | `true` | Clear log file on startup |
225
+ | `dirs.maxLogFiles` | `number` | `0` | Maximum log files to keep |
226
+ | `bufferSize` | `number` | `4096` | Buffer size in bytes |
227
+ | `maxBufferAge` | `number` | `5000` | Max buffer age in ms |
228
+ | `cleanupInterval` | `number` | `10000` | Cleanup interval in ms |
234
229
 
235
- - `relinka.config.ts`
236
- - 🔜 other formats, supported by `reconf`, are coming soon
230
+ ## File Logging
237
231
 
238
- ## Log Files
232
+ ### File Naming Patterns
239
233
 
240
- - Default: `logs/relinka.log`
241
- - Daily mode: `2025-04-11-relinka.log`
242
- - Auto-cleanup: keep latest N logs
234
+ - **Default**: `logs.log`
235
+ - **With subdirectory**: `logs/app.log`
236
+ - **With date prefix**: `2025-01-15-logs.log`
237
+ - **With date suffix**: `logs-2025-01-15.log`
238
+ - **Combined**: `logs/2025-01-15-app.log`
243
239
 
244
- ## API Summary
240
+ ### Automatic Cleanup
241
+
242
+ When `maxLogFiles` is set, Relinka automatically:
243
+
244
+ - Keeps only the N most recent log files
245
+ - Deletes older files during cleanup
246
+ - Runs cleanup periodically and on shutdown
247
+
248
+ ## Advanced Usage
249
+
250
+ ### Fatal Logging
251
+
252
+ Fatal logs throw errors and halt execution:
253
+
254
+ ```ts
255
+ // This will throw an error and trigger debugger in development
256
+ relinka("fatal", "Critical system failure");
257
+ // or
258
+ relinka.fatal("Critical system failure");
259
+ ```
260
+
261
+ ### Box Formatting
262
+
263
+ Create visually appealing boxed messages:
264
+
265
+ ```ts
266
+ relinka("box", "This message will be displayed in a box");
267
+ relinka.box("This also works with method syntax");
268
+ ```
269
+
270
+ ### Async Context
271
+
272
+ Use `relinkaAsync` when you need to ensure configuration is loaded:
273
+
274
+ ```ts
275
+ await relinkaAsync("info", "This waits for config to load");
276
+ ```
277
+
278
+ ### Clear Terminal
279
+
280
+ Clear the terminal output:
281
+
282
+ ```ts
283
+ relinka("clear", "");
284
+ // or
285
+ relinka.clear();
286
+ ```
287
+
288
+ ### Empty Lines
289
+
290
+ Add blank lines to output:
245
291
 
246
292
  ```ts
247
- relinka("info", "message", optionalDetails);
248
- relinka("fatal", "something broke"); // throws
249
- relinka("clear", ""); // clears terminal
293
+ relinka("info", "");
294
+ ```
250
295
 
251
- await relinkaAsync("warn", "something async");
296
+ ## Best Practices
297
+
298
+ ### 1. Always Initialize and Shutdown
299
+
300
+ ```ts
301
+ async function main() {
302
+ await relinkaConfig(); // At the start
303
+
304
+ // Your application logic
305
+ relinka("info", "App running");
306
+
307
+ await relinkaShutdown(); // At the end
308
+ }
252
309
  ```
253
310
 
311
+ ### 2. Use Appropriate Log Levels
312
+
254
313
  ```ts
255
- defineConfig({ ... }) // helper for relinka.config.ts
314
+ relinka("info", "User logged in"); // General info
315
+ relinka("success", "Payment processed"); // Success events
316
+ relinka("warn", "High memory usage"); // Warnings
317
+ relinka("error", "Database connection failed"); // Recoverable errors
318
+ relinka("fatal", "Critical system failure"); // Unrecoverable errors
256
319
  ```
257
320
 
258
- ## Built-in Utilities
321
+ ### 3. Leverage Verbose Logging
322
+
323
+ ```ts
324
+ relinka("verbose", "Debug information"); // Only shown when verbose=true
325
+ ```
259
326
 
260
- - Timestamping
261
- - ✅ File-safe formatting
262
- - ✅ Log rotation
263
- - Fatal logging (with debugger)
264
- - ✅ Colorized terminal output
327
+ ### 4. Structure Complex Data
328
+
329
+ ```ts
330
+ relinka("error", "API request failed", {
331
+ endpoint: "/api/users",
332
+ statusCode: 500,
333
+ error: error.message
334
+ });
335
+ ```
336
+
337
+ ## Performance Features
338
+
339
+ - **Intelligent Buffering**: Logs are buffered and flushed efficiently
340
+ - **Async File Operations**: Non-blocking file writes
341
+ - **Memory Management**: Automatic cleanup of old log files
342
+ - **Unicode Detection**: Automatic fallback for non-Unicode terminals
265
343
 
266
344
  ## FAQ
267
345
 
268
- ### Why `relinka.config.ts` doesn't works for me?
346
+ ### Why does my terminal hang after logging?
269
347
 
270
- → You forget to load user's config by using `await relinkaConfig;` **at the START** of your app's main function.
348
+ → You forgot to call `await relinkaShutdown()` at the end of your program. This is required to flush buffers.
271
349
 
272
- ### Why my terminal stucks after last relinka() usage?
350
+ ### Why isn't my configuration loading?
273
351
 
274
- You forget to flush the buffer. Place `await relinkaShutdown();` **at the END** of your app's main function.
352
+ Make sure you call `await relinkaConfig()` at the start of your application.
275
353
 
276
- ### Why does TS linter tells that something wrong with `relinka("info", args)`?
354
+ ### Does `fatal` always throw?
277
355
 
278
- Add empty string: `relinka("info", "", args)`
356
+ Yes, `fatal` logs always throw errors and halt execution. In development mode, they also trigger the debugger.
279
357
 
280
- ### Does `fatal` throw?
358
+ ### How do I disable colors?
281
359
 
282
- Yes, always. It will halt execution and trigger `debugger` in dev mode.
360
+ Set `disableColors: true` in your configuration or use `NO_COLOR=1` environment variable.
283
361
 
284
- ### What's coming next?
362
+ ### Can I use Relinka in both sync and async contexts?
285
363
 
286
- - Relinka is designed to be used in the different ways:
287
- - Use `relinka(level, message, ...args)` (recommended).
288
- - 🔜 Or, just `relinka.level(message, ...args)`
289
- - 🔜 Both designed to work with both sync (default) and async/await.
290
- - 🔜 Both designed to work with both direct and wrapper methods.
291
- - 🔜 Use the async logger if you want some advanced features (like typing text streaming animation).
364
+ Yes! Use `relinka()` for sync operations and `relinkaAsync()` when you need to use some advanced features (like typing text streaming animation).
292
365
 
293
- ## Tips
366
+ ### What's the difference between `log` and `message` levels?
294
367
 
295
- - Building CLIs? Use with [`@reliverse/prompts`](https://npmjs.com/@reliverse/prompts)
296
- - Want type-safe injections? Try [`@reliverse/reinject`](https://npmjs.com/@reliverse/reinject)
297
- - For advanced bundling? Pair with [`@reliverse/dler`](https://npmjs.com/@reliverse/dler)
368
+ `log` uses a pipe symbol (│) and is for general logging, while `message` uses a different symbol (🞠) and is for general messages.
298
369
 
299
- ## Roadmap
370
+ ## Ecosystem
300
371
 
301
- - [x] File logging
302
- - [x] Timestamps
303
- - [x] Daily logs
304
- - [x] Log rotation
305
- - [x] `fatal` type
306
- - [x] Runtime config
307
- - [ ] Implement per-project config redefinition
308
- - [ ] Plugin support (custom formatters, hooks)
309
- - [ ] CLI interface (to manage logs, config, etc)
372
+ Relinka works great with other Reliverse tools:
310
373
 
311
- ## Shoutouts
374
+ - **CLI Development**: [`@reliverse/prompts`](https://npmjs.com/@reliverse/prompts)
375
+ - **Bundling**: [`@reliverse/dler`](https://npmjs.com/@reliverse/dler)
312
376
 
313
- Relinka wouldn't exist without these gems:
377
+ ## Roadmap
378
+
379
+ - [x] File logging with rotation
380
+ - [x] Timestamp support
381
+ - [x] Date-based file naming
382
+ - [x] Automatic cleanup
383
+ - [x] Fatal logging with debugger
384
+ - [x] Runtime configuration
385
+ - [x] Dual syntax support
386
+ - [ ] Plugin system
387
+ - [ ] Custom formatters
388
+ - [ ] CLI interface for log management
389
+ - [ ] WebSocket streaming
390
+ - [ ] Structured logging (JSON)
391
+
392
+ ## Contributing
314
393
 
315
- - [unjs/consola](https://github.com/unjs/consola#readme)
316
- - [winston](https://github.com/winstonjs/winston#readme)
317
- - [pino](https://github.com/pinojs/pino#readme)
318
- - [node-bunyan](https://github.com/trentm/node-bunyan#readme)
394
+ We welcome contributions! Please see our [contributing guide](CONTRIBUTING.md) for details.
319
395
 
320
396
  ## License
321
397
 
322
398
  💖 MIT © 2025 [blefnk Nazar Kornienko](https://github.com/blefnk)
399
+
400
+ ## Acknowledgments
401
+
402
+ Relinka is inspired by these excellent logging libraries:
403
+
404
+ - [unjs/consola](https://github.com/unjs/consola) - Console logging for Node.js
405
+ - [winston](https://github.com/winstonjs/winston) - Multi-transport logging
406
+ - [pino](https://github.com/pinojs/pino) - Fast Node.js logger
407
+ - [node-bunyan](https://github.com/trentm/node-bunyan) - JSON logging
package/bin/impl.d.ts CHANGED
@@ -1,15 +1,7 @@
1
1
  import type { DefaultColorKeys } from "@reliverse/relico";
2
- /** Configuration for special directory handling. */
3
- export type RelinkaSpecialDirsConfig = {
4
- distDirNames?: string[];
5
- useParentConfigInDist?: boolean;
6
- };
7
2
  /** Configuration for directory-related settings. */
8
3
  export type RelinkaDirsConfig = {
9
- dailyLogs?: boolean;
10
- logDir?: string;
11
4
  maxLogFiles?: number;
12
- specialDirs?: RelinkaSpecialDirsConfig;
13
5
  };
14
6
  /** Configuration for a single log level. */
15
7
  export type LogLevelConfig = {
@@ -50,12 +42,7 @@ export type RelinkaConfig = {
50
42
  verbose?: boolean;
51
43
  /**
52
44
  * Configuration for directory-related settings.
53
- * - `dailyLogs`: If true, logs will be stored in a daily subdirectory.
54
- * - `logDir`: The base directory for logs.
55
45
  * - `maxLogFiles`: The maximum number of log files to keep before cleanup.
56
- * - `specialDirs`: Configuration for special directory handling.
57
- * - `distDirNames`: An array of directory names to check for special handling.
58
- * - `useParentConfigInDist`: If true, use the parent config in dist directories.
59
46
  */
60
47
  dirs?: RelinkaDirsConfig;
61
48
  /**
@@ -63,9 +50,26 @@ export type RelinkaConfig = {
63
50
  */
64
51
  disableColors?: boolean;
65
52
  /**
66
- * Path to the log file.
53
+ * Configuration for log file output.
67
54
  */
68
- logFilePath?: string;
55
+ logFile?: {
56
+ /**
57
+ * Path to the log file.
58
+ */
59
+ outputPath?: string;
60
+ /**
61
+ * How to handle date in the filename.
62
+ * - `disable`: No date prefix/suffix
63
+ * - `append-before`: Add date before the filename (e.g., "2024-01-15-log.txt")
64
+ * - `append-after`: Add date after the filename (e.g., "log-2024-01-15.txt")
65
+ */
66
+ nameWithDate?: "disable" | "append-before" | "append-after";
67
+ /**
68
+ * If true, clears the log file when relinkaConfig is executed with supportFreshLogFile: true.
69
+ * This is useful for starting with a clean log file on each run.
70
+ */
71
+ freshLogFile?: boolean;
72
+ };
69
73
  /**
70
74
  * If true, logs will be saved to a file.
71
75
  */
@@ -108,8 +112,16 @@ export type LogFileInfo = {
108
112
  path: string;
109
113
  mtime: number;
110
114
  };
115
+ /** Options for relinkaConfig */
116
+ export type RelinkaConfigOptions = {
117
+ supportFreshLogFile?: boolean;
118
+ };
111
119
  /** Promise resolved once the user's config (if any) is merged. */
112
- export declare const relinkaConfig: Promise<RelinkaConfig>;
120
+ export declare const loadRelinkaConfig: Promise<RelinkaConfig>;
121
+ /**
122
+ * Enhanced relinkaConfig function that accepts options
123
+ */
124
+ export declare function relinkaConfig(options?: RelinkaConfigOptions): Promise<RelinkaConfig>;
113
125
  /**
114
126
  * Shuts down the logger, flushing all buffers and clearing timers.
115
127
  * As Relinka user - call this at the end of your program to prevent hanging.
package/bin/impl.js CHANGED
@@ -8,16 +8,14 @@ const EXIT_GUARD = Symbol.for("relinka.exitHandlersRegistered");
8
8
  const DEFAULT_RELINKA_CONFIG = {
9
9
  verbose: false,
10
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
- }
11
+ maxLogFiles: 0
18
12
  },
19
13
  disableColors: false,
20
- logFilePath: "relinka.log",
14
+ logFile: {
15
+ outputPath: "logs.log",
16
+ nameWithDate: "disable",
17
+ freshLogFile: true
18
+ },
21
19
  saveLogsToFile: false,
22
20
  timestamp: {
23
21
  enabled: false,
@@ -115,9 +113,33 @@ function isUnicodeSupported() {
115
113
  let currentConfig = { ...DEFAULT_RELINKA_CONFIG };
116
114
  let isConfigInitialized = false;
117
115
  let resolveRelinkaConfig;
118
- export const relinkaConfig = new Promise((res) => {
116
+ let userTerminalCwd;
117
+ export const loadRelinkaConfig = new Promise((res) => {
119
118
  resolveRelinkaConfig = res;
120
119
  });
120
+ export async function relinkaConfig(options = {}) {
121
+ if (!userTerminalCwd) {
122
+ userTerminalCwd = process.cwd();
123
+ }
124
+ const config = await loadRelinkaConfig;
125
+ if (options.supportFreshLogFile && isFreshLogFileEnabled(config)) {
126
+ try {
127
+ const logFilePath = getLogFilePath(config);
128
+ await fs.ensureDir(path.dirname(logFilePath));
129
+ await fs.writeFile(logFilePath, "");
130
+ if (isVerboseEnabled(config)) {
131
+ console.log(`[Relinka Fresh Log] Cleared log file: ${logFilePath}`);
132
+ }
133
+ } catch (err) {
134
+ if (isVerboseEnabled(config)) {
135
+ console.error(
136
+ `[Relinka Fresh Log Error] Failed to clear log file: ${err instanceof Error ? err.message : String(err)}`
137
+ );
138
+ }
139
+ }
140
+ }
141
+ return config;
142
+ }
121
143
  const logBuffers = /* @__PURE__ */ new Map();
122
144
  const activeTimers = [];
123
145
  let bufferFlushTimer = null;
@@ -193,20 +215,15 @@ function isVerboseEnabled(config) {
193
215
  function isColorEnabled(config) {
194
216
  return !(config.disableColors ?? DEFAULT_RELINKA_CONFIG.disableColors);
195
217
  }
196
- function getLogDir(config) {
197
- return config.dirs?.logDir ?? DEFAULT_RELINKA_CONFIG.dirs?.logDir ?? "logs";
198
- }
199
- function isDailyLogsEnabled(config) {
200
- return config.dirs?.dailyLogs ?? DEFAULT_RELINKA_CONFIG.dirs?.dailyLogs ?? false;
201
- }
202
218
  function shouldSaveLogs(config) {
203
219
  return config.saveLogsToFile ?? DEFAULT_RELINKA_CONFIG.saveLogsToFile ?? false;
204
220
  }
205
221
  function getMaxLogFiles(config) {
206
- return config.dirs?.maxLogFiles ?? DEFAULT_RELINKA_CONFIG.dirs?.maxLogFiles ?? 0;
222
+ return config.dirs?.maxLogFiles ?? 0;
207
223
  }
208
224
  function getBaseLogName(config) {
209
- return config.logFilePath ?? DEFAULT_RELINKA_CONFIG.logFilePath ?? "relinka.log";
225
+ const logFileConfig = config.logFile || DEFAULT_RELINKA_CONFIG.logFile || {};
226
+ return logFileConfig.outputPath ?? "logs.log";
210
227
  }
211
228
  function getBufferSize(config) {
212
229
  return config.bufferSize ?? DEFAULT_RELINKA_CONFIG.bufferSize ?? 4096;
@@ -217,6 +234,9 @@ function getMaxBufferAge(config) {
217
234
  function getCleanupInterval(config) {
218
235
  return config.cleanupInterval ?? DEFAULT_RELINKA_CONFIG.cleanupInterval ?? 1e4;
219
236
  }
237
+ function isFreshLogFileEnabled(config) {
238
+ return config.logFile?.freshLogFile ?? DEFAULT_RELINKA_CONFIG.logFile?.freshLogFile ?? false;
239
+ }
220
240
  function isDevEnv() {
221
241
  return process.env.NODE_ENV === "development";
222
242
  }
@@ -232,18 +252,27 @@ function getTimestamp(config) {
232
252
  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"));
233
253
  }
234
254
  function getLogFilePath(config) {
235
- const logDir = getLogDir(config);
236
- const daily = isDailyLogsEnabled(config);
237
- let finalLogName = getBaseLogName(config);
238
- if (daily) {
239
- const datePrefix = `${getDateString()}-`;
240
- finalLogName = datePrefix + finalLogName;
255
+ const logFileConfig = config.logFile || DEFAULT_RELINKA_CONFIG.logFile || {};
256
+ const nameWithDate = logFileConfig.nameWithDate || "disable";
257
+ const outputPath = getBaseLogName(config);
258
+ const dir = path.dirname(outputPath);
259
+ let filename = path.basename(outputPath);
260
+ if (nameWithDate !== "disable") {
261
+ const dateString = getDateString();
262
+ if (nameWithDate === "append-before") {
263
+ filename = `${dateString}-${filename}`;
264
+ } else if (nameWithDate === "append-after") {
265
+ const nameWithoutExt = filename.replace(/\.log$/, "");
266
+ filename = `${nameWithoutExt}-${dateString}.log`;
267
+ }
241
268
  }
242
- if (finalLogName && !finalLogName.endsWith(".log")) {
243
- finalLogName += ".log";
269
+ if (filename && !filename.endsWith(".log")) {
270
+ filename += ".log";
244
271
  }
245
- const effectiveLogName = finalLogName || "relinka.log";
246
- return path.resolve(process.cwd(), logDir, effectiveLogName);
272
+ const effectiveFilename = filename || "logs.log";
273
+ const finalPath = dir === "." ? effectiveFilename : path.join(dir, effectiveFilename);
274
+ const baseCwd = userTerminalCwd || process.cwd();
275
+ return path.resolve(baseCwd, finalPath);
247
276
  }
248
277
  function getLevelStyle(config, level) {
249
278
  const allLevels = config.levels || DEFAULT_RELINKA_CONFIG.levels || {};
@@ -305,6 +334,7 @@ const consoleMethodMap = {
305
334
  success: console.log,
306
335
  verbose: console.log,
307
336
  log: console.log,
337
+ internal: console.log,
308
338
  step: console.log,
309
339
  box: console.log,
310
340
  message: console.log,
@@ -322,16 +352,40 @@ function logToConsole(config, level, formattedMessage) {
322
352
  method(`${colorFn(formattedMessage)}\x1B[0m`);
323
353
  }
324
354
  async function getLogFilesSortedByDate(config) {
325
- const logDirectoryPath = path.resolve(process.cwd(), getLogDir(config));
355
+ const logDirectoryPath = userTerminalCwd || process.cwd();
326
356
  try {
327
- if (!await fs.pathExists(logDirectoryPath)) {
328
- if (ENABLE_DEV_DEBUG) {
329
- console.log(`[Dev Debug] Log directory not found: ${logDirectoryPath}`);
357
+ const files = await fs.readdir(logDirectoryPath);
358
+ const logFiles = [];
359
+ for (const file of files) {
360
+ const filePath = path.join(logDirectoryPath, file);
361
+ try {
362
+ const stats = await fs.stat(filePath);
363
+ if (stats.isFile() && file.endsWith(".log")) {
364
+ logFiles.push(file);
365
+ } else if (stats.isDirectory()) {
366
+ try {
367
+ const subFiles = await fs.readdir(filePath);
368
+ for (const subFile of subFiles) {
369
+ if (subFile.endsWith(".log")) {
370
+ logFiles.push(path.join(file, subFile));
371
+ }
372
+ }
373
+ } catch (subDirErr) {
374
+ if (isVerboseEnabled(config)) {
375
+ console.error(
376
+ `[Relinka FS Debug] Error reading subdirectory ${filePath}: ${subDirErr instanceof Error ? subDirErr.message : String(subDirErr)}`
377
+ );
378
+ }
379
+ }
380
+ }
381
+ } catch (err) {
382
+ if (isVerboseEnabled(config)) {
383
+ console.error(
384
+ `[Relinka FS Debug] Error accessing ${filePath}: ${err instanceof Error ? err.message : String(err)}`
385
+ );
386
+ }
330
387
  }
331
- return [];
332
388
  }
333
- const files = await fs.readdir(logDirectoryPath);
334
- const logFiles = files.filter((f) => f.endsWith(".log"));
335
389
  if (logFiles.length === 0) return [];
336
390
  const fileInfoPromises = logFiles.map(
337
391
  async (fileName) => {
@@ -357,7 +411,7 @@ async function getLogFilesSortedByDate(config) {
357
411
  } catch (readErr) {
358
412
  if (isVerboseEnabled(config)) {
359
413
  console.error(
360
- `[Relinka FS Error] Failed reading log directory ${logDirectoryPath}: ${readErr instanceof Error ? readErr.message : String(readErr)}`
414
+ `[Relinka FS Error] Failed reading directory ${logDirectoryPath}: ${readErr instanceof Error ? readErr.message : String(readErr)}`
361
415
  );
362
416
  }
363
417
  return [];
@@ -615,7 +669,7 @@ export async function relinkaAsync(type, message, ...args) {
615
669
  console.log();
616
670
  return;
617
671
  }
618
- await relinkaConfig;
672
+ await relinkaConfig({ supportFreshLogFile: false });
619
673
  const levelName = type.toLowerCase();
620
674
  if (levelName === "fatal") {
621
675
  shouldNeverHappen(message, ...args);
@@ -658,7 +712,7 @@ export function formatBox(text) {
658
712
  const bottom = `\u2514${"\u2500".repeat(width)}\u2518`;
659
713
  const content = lines.map((line) => {
660
714
  const padding = width - line.length;
661
- return `\u2502 ${line}${" ".repeat(padding)}\u2502`;
715
+ return `\u2502 ${line}${" ".repeat(padding - 2)}\u2502`;
662
716
  }).join("\n");
663
717
  return `${top}
664
718
  ${content}
package/bin/mod.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { message, step, log, logger } from "./alias.js";
2
- export type { RelinkaSpecialDirsConfig, RelinkaDirsConfig, LogLevelConfig, LogLevelsConfig, LogLevel, RelinkaConfig, LogFileInfo, RelinkaFunction, } from "./impl.js";
3
- export { relinkaConfig, relinkaShutdown, flushAllLogBuffers, shouldNeverHappen, truncateString, casesHandled, relinka, relinkaAsync, defineConfig, formatBox, } from "./impl.js";
2
+ export type { RelinkaDirsConfig, LogLevelConfig, LogLevelsConfig, LogLevel, RelinkaConfig, LogFileInfo, RelinkaFunction, RelinkaConfigOptions, } from "./impl.js";
3
+ export { loadRelinkaConfig, relinkaConfig, relinkaShutdown, flushAllLogBuffers, shouldNeverHappen, truncateString, casesHandled, relinka, relinkaAsync, defineConfig, formatBox, } from "./impl.js";
package/bin/mod.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { message, step, log, logger } from "./alias.js";
2
2
  export {
3
+ loadRelinkaConfig,
3
4
  relinkaConfig,
4
5
  relinkaShutdown,
5
6
  flushAllLogBuffers,
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "license": "MIT",
11
11
  "name": "@reliverse/relinka",
12
12
  "type": "module",
13
- "version": "1.5.2",
13
+ "version": "1.5.3",
14
14
  "keywords": [
15
15
  "logger",
16
16
  "consola",