@reliverse/relinka 1.5.1 → 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,147 +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
- // --- BOX LEVEL EXAMPLES ---
73
- relinka("box", "This is a boxed message using direct syntax!");
74
- relinka.box("This is a boxed message using method syntax!");
75
- // --- LOG LEVEL EXAMPLES ---
76
- relinka("log", "Hello! 👋");
77
- relinka("log", "Great to see you here!");
78
- relinka("info", "Everything is running smoothly");
79
- relinka("warn", "This might be a problem");
80
- relinka(
81
- "error", // non-fatal issue level can be recovered
82
- "Uh oh, something broke",
83
- );
84
-
85
- relinka(
86
- "null",
87
- "'null' level has a special handling case: no symbol or spacing",
88
- );
89
-
90
- // relinka(
91
- // "fatal",
92
- // "We should never reach this code! This should never happen! (see <anonymous> line)",
93
- // ); // fatal level throws error and halts execution
94
- relinka("success", "Thanks for using Relinka!");
95
-
96
- // Make sure to shut down the logger at the end of your program
97
- // This is important to flush all buffers and close file handles
98
- await relinkaShutdown();
105
+ // Basic config loading
106
+ await relinkaConfig();
99
107
 
100
- // Make sure to exit the program after your CLI is done
101
- // It's not required for Bun-only apps, but recommended
102
- // for other terminal runtimes like Node.js (incl. `tsx`)
103
- // It's also not required for @reliverse/rempts `runMain()`
104
- process.exit(0);
105
- }
108
+ // With fresh log file (clears existing log file)
109
+ await relinkaConfig({ supportFreshLogFile: true });
110
+ ```
111
+
112
+ #### `relinkaShutdown()`
106
113
 
107
- 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();
108
118
  ```
109
119
 
110
- #### [🔜 Soon] Singleton Method
120
+ ### Utility Functions
121
+
122
+ #### `log`, `logger`
123
+
124
+ Aliases for the main `relinka` function.
111
125
 
112
126
  ```ts
113
- const logger = initRelinkaInstance({/* per-project config */});
114
- logger("info", "Looks great!");
127
+ import { log, logger } from "@reliverse/relinka";
128
+
129
+ log("info", "Using alias");
130
+ logger.success("Another alias");
115
131
  ```
116
132
 
117
- #### [🔜 Soon] Object Method
133
+ #### `message(msg, ...args)`
134
+
135
+ Convenience function for general messages.
118
136
 
119
137
  ```ts
120
- await relinkaConfig;
121
- relinka.info("Looks great!");
138
+ import { message } from "@reliverse/relinka";
139
+ message("This is a general message");
122
140
  ```
123
141
 
124
- ## Advanced Usage
142
+ #### `step(msg, ...args)`
143
+
144
+ Convenience function for progress steps.
125
145
 
126
146
  ```ts
127
- // Clear terminal:
128
- relinka("clear", "");
129
- // Blank line:
130
- relinka("info", "");
131
- // Async variant:
132
- import { relinkaAsync } from "@reliverse/relinka";
133
- await relinkaAsync("info", "Logged from async context");
134
- // Coming soon:
135
- await relinkaAsync("info", "Something happened", { animate: true });
147
+ import { step } from "@reliverse/relinka";
148
+ step("Step 1: Initialize");
149
+ step("Step 2: Load config");
136
150
  ```
137
151
 
138
- ## Config
152
+ ## Configuration
153
+
154
+ Create a configuration file to customize Relinka's behavior:
155
+
156
+ ### File Locations (in order of priority)
139
157
 
140
- Create `relinka.config.ts`:
158
+ 1. `relinka.config.ts` (project root) - **highest priority**
159
+ 2. `.config/relinka.ts` - **lower priority**
160
+
161
+ ### Configuration Example
141
162
 
142
163
  ```ts
143
164
  import { defineConfig } from "@reliverse/relinka";
144
- /**
145
- * RELINKA CONFIGURATION FILE
146
- * - Hover over a field to see the information
147
- * - Use intellisense to see available options
148
- * @see https://github.com/reliverse/relinka
149
- */
165
+
150
166
  export default defineConfig({
151
- // Enable to see verbose logs
167
+ // Enable verbose logging
152
168
  verbose: false,
153
169
 
154
170
  // Timestamp configuration
155
171
  timestamp: {
156
- enabled: false,
157
- format: "HH:mm:ss",
172
+ enabled: true,
173
+ format: "YYYY-MM-DD HH:mm:ss.SSS",
158
174
  },
159
175
 
160
- // Control whether logs are saved to a file
176
+ // File logging
161
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
+ },
162
183
 
163
- // Disable colors in the console
164
- disableColors: false,
184
+ // Log rotation
185
+ dirs: {
186
+ maxLogFiles: 10, // Keep only the 10 most recent log files
187
+ },
165
188
 
166
- // Log file path
167
- 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
168
193
 
194
+ // Customize log levels
169
195
  levels: {
170
196
  success: {
171
197
  symbol: "✓",
@@ -174,138 +200,208 @@ export default defineConfig({
174
200
  spacing: 3,
175
201
  },
176
202
  info: {
177
- symbol: "i",
203
+ symbol: "",
178
204
  fallbackSymbol: "[i]",
179
205
  color: "cyanBright",
180
206
  spacing: 3,
181
207
  },
182
- error: {
183
- symbol: "✖",
184
- fallbackSymbol: "[ERR]",
185
- color: "redBright",
186
- spacing: 3,
187
- },
188
- warn: {
189
- symbol: "⚠",
190
- fallbackSymbol: "[WARN]",
191
- color: "yellowBright",
192
- spacing: 3,
193
- },
194
- fatal: {
195
- symbol: "‼",
196
- fallbackSymbol: "[FATAL]",
197
- color: "redBright",
198
- spacing: 3,
199
- },
200
- verbose: {
201
- symbol: "✧",
202
- fallbackSymbol: "[VERBOSE]",
203
- color: "gray",
204
- spacing: 3,
205
- },
206
- log: { symbol: "│", fallbackSymbol: "|", color: "dim", spacing: 3 },
207
- },
208
-
209
- // Directory settings
210
- dirs: {
211
- dailyLogs: true,
212
- logDir: "logs", // store logs in a custom folder
213
- maxLogFiles: 5, // keep only the 5 most recent log files
214
- specialDirs: {
215
- distDirNames: [],
216
- useParentConfigInDist: true,
217
- },
208
+ // ... customize other levels
218
209
  },
219
210
  });
220
211
  ```
221
212
 
222
- 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 |
223
229
 
224
- - `relinka.config.ts`
225
- - 🔜 other formats, supported by `reconf`, are coming soon
230
+ ## File Logging
226
231
 
227
- ## Log Files
232
+ ### File Naming Patterns
228
233
 
229
- - Default: `logs/relinka.log`
230
- - Daily mode: `2025-04-11-relinka.log`
231
- - 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`
232
239
 
233
- ## 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:
234
291
 
235
292
  ```ts
236
- relinka("info", "message", optionalDetails);
237
- relinka("fatal", "something broke"); // throws
238
- relinka("clear", ""); // clears terminal
293
+ relinka("info", "");
294
+ ```
239
295
 
240
- 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
+ }
241
309
  ```
242
310
 
311
+ ### 2. Use Appropriate Log Levels
312
+
243
313
  ```ts
244
- 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
245
319
  ```
246
320
 
247
- ## Built-in Utilities
321
+ ### 3. Leverage Verbose Logging
322
+
323
+ ```ts
324
+ relinka("verbose", "Debug information"); // Only shown when verbose=true
325
+ ```
248
326
 
249
- - Timestamping
250
- - ✅ File-safe formatting
251
- - ✅ Log rotation
252
- - Fatal logging (with debugger)
253
- - ✅ 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
254
343
 
255
344
  ## FAQ
256
345
 
257
- ### Why `relinka.config.ts` doesn't works for me?
346
+ ### Why does my terminal hang after logging?
258
347
 
259
- → 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.
260
349
 
261
- ### Why my terminal stucks after last relinka() usage?
350
+ ### Why isn't my configuration loading?
262
351
 
263
- 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.
264
353
 
265
- ### Why does TS linter tells that something wrong with `relinka("info", args)`?
354
+ ### Does `fatal` always throw?
266
355
 
267
- Add empty string: `relinka("info", "", args)`
356
+ Yes, `fatal` logs always throw errors and halt execution. In development mode, they also trigger the debugger.
268
357
 
269
- ### Does `fatal` throw?
358
+ ### How do I disable colors?
270
359
 
271
- 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.
272
361
 
273
- ### What's coming next?
362
+ ### Can I use Relinka in both sync and async contexts?
274
363
 
275
- - Relinka is designed to be used in the different ways:
276
- - Use `relinka(level, message, ...args)` (recommended).
277
- - 🔜 Or, just `relinka.level(message, ...args)`
278
- - 🔜 Both designed to work with both sync (default) and async/await.
279
- - 🔜 Both designed to work with both direct and wrapper methods.
280
- - 🔜 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).
281
365
 
282
- ## Tips
366
+ ### What's the difference between `log` and `message` levels?
283
367
 
284
- - Building CLIs? Use with [`@reliverse/prompts`](https://npmjs.com/@reliverse/prompts)
285
- - Want type-safe injections? Try [`@reliverse/reinject`](https://npmjs.com/@reliverse/reinject)
286
- - 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.
287
369
 
288
- ## Roadmap
370
+ ## Ecosystem
289
371
 
290
- - [x] File logging
291
- - [x] Timestamps
292
- - [x] Daily logs
293
- - [x] Log rotation
294
- - [x] `fatal` type
295
- - [x] Runtime config
296
- - [ ] Implement per-project config redefinition
297
- - [ ] Plugin support (custom formatters, hooks)
298
- - [ ] CLI interface (to manage logs, config, etc)
372
+ Relinka works great with other Reliverse tools:
299
373
 
300
- ## Shoutouts
374
+ - **CLI Development**: [`@reliverse/prompts`](https://npmjs.com/@reliverse/prompts)
375
+ - **Bundling**: [`@reliverse/dler`](https://npmjs.com/@reliverse/dler)
301
376
 
302
- 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
303
393
 
304
- - [unjs/consola](https://github.com/unjs/consola#readme)
305
- - [winston](https://github.com/winstonjs/winston#readme)
306
- - [pino](https://github.com/pinojs/pino#readme)
307
- - [node-bunyan](https://github.com/trentm/node-bunyan#readme)
394
+ We welcome contributions! Please see our [contributing guide](CONTRIBUTING.md) for details.
308
395
 
309
396
  ## License
310
397
 
311
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 = {
@@ -34,7 +26,7 @@ export type LogLevelConfig = {
34
26
  /** Configuration for all log levels. */
35
27
  export type LogLevelsConfig = Partial<Record<LogLevel, LogLevelConfig>>;
36
28
  /** Log level types used by the logger. */
37
- export type LogLevel = "error" | "fatal" | "info" | "success" | "verbose" | "warn" | "log" | "internal" | "null" | "step" | "box";
29
+ export type LogLevel = "error" | "fatal" | "info" | "success" | "verbose" | "warn" | "log" | "internal" | "null" | "step" | "box" | "message";
38
30
  /**
39
31
  * Configuration options for the Relinka logger.
40
32
  * All properties are optional to allow for partial configuration.
@@ -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,
@@ -86,6 +84,12 @@ const DEFAULT_RELINKA_CONFIG = {
86
84
  color: "whiteBright",
87
85
  spacing: 1
88
86
  },
87
+ message: {
88
+ symbol: "\u{1F7A0}",
89
+ fallbackSymbol: "[MSG]",
90
+ color: "cyan",
91
+ spacing: 3
92
+ },
89
93
  null: { symbol: "", fallbackSymbol: "", color: "dim", spacing: 0 }
90
94
  }
91
95
  };
@@ -109,9 +113,33 @@ function isUnicodeSupported() {
109
113
  let currentConfig = { ...DEFAULT_RELINKA_CONFIG };
110
114
  let isConfigInitialized = false;
111
115
  let resolveRelinkaConfig;
112
- export const relinkaConfig = new Promise((res) => {
116
+ let userTerminalCwd;
117
+ export const loadRelinkaConfig = new Promise((res) => {
113
118
  resolveRelinkaConfig = res;
114
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
+ }
115
143
  const logBuffers = /* @__PURE__ */ new Map();
116
144
  const activeTimers = [];
117
145
  let bufferFlushTimer = null;
@@ -187,20 +215,15 @@ function isVerboseEnabled(config) {
187
215
  function isColorEnabled(config) {
188
216
  return !(config.disableColors ?? DEFAULT_RELINKA_CONFIG.disableColors);
189
217
  }
190
- function getLogDir(config) {
191
- return config.dirs?.logDir ?? DEFAULT_RELINKA_CONFIG.dirs?.logDir ?? "logs";
192
- }
193
- function isDailyLogsEnabled(config) {
194
- return config.dirs?.dailyLogs ?? DEFAULT_RELINKA_CONFIG.dirs?.dailyLogs ?? false;
195
- }
196
218
  function shouldSaveLogs(config) {
197
219
  return config.saveLogsToFile ?? DEFAULT_RELINKA_CONFIG.saveLogsToFile ?? false;
198
220
  }
199
221
  function getMaxLogFiles(config) {
200
- return config.dirs?.maxLogFiles ?? DEFAULT_RELINKA_CONFIG.dirs?.maxLogFiles ?? 0;
222
+ return config.dirs?.maxLogFiles ?? 0;
201
223
  }
202
224
  function getBaseLogName(config) {
203
- return config.logFilePath ?? DEFAULT_RELINKA_CONFIG.logFilePath ?? "relinka.log";
225
+ const logFileConfig = config.logFile || DEFAULT_RELINKA_CONFIG.logFile || {};
226
+ return logFileConfig.outputPath ?? "logs.log";
204
227
  }
205
228
  function getBufferSize(config) {
206
229
  return config.bufferSize ?? DEFAULT_RELINKA_CONFIG.bufferSize ?? 4096;
@@ -211,6 +234,9 @@ function getMaxBufferAge(config) {
211
234
  function getCleanupInterval(config) {
212
235
  return config.cleanupInterval ?? DEFAULT_RELINKA_CONFIG.cleanupInterval ?? 1e4;
213
236
  }
237
+ function isFreshLogFileEnabled(config) {
238
+ return config.logFile?.freshLogFile ?? DEFAULT_RELINKA_CONFIG.logFile?.freshLogFile ?? false;
239
+ }
214
240
  function isDevEnv() {
215
241
  return process.env.NODE_ENV === "development";
216
242
  }
@@ -226,18 +252,27 @@ function getTimestamp(config) {
226
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"));
227
253
  }
228
254
  function getLogFilePath(config) {
229
- const logDir = getLogDir(config);
230
- const daily = isDailyLogsEnabled(config);
231
- let finalLogName = getBaseLogName(config);
232
- if (daily) {
233
- const datePrefix = `${getDateString()}-`;
234
- 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
+ }
235
268
  }
236
- if (finalLogName && !finalLogName.endsWith(".log")) {
237
- finalLogName += ".log";
269
+ if (filename && !filename.endsWith(".log")) {
270
+ filename += ".log";
238
271
  }
239
- const effectiveLogName = finalLogName || "relinka.log";
240
- 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);
241
276
  }
242
277
  function getLevelStyle(config, level) {
243
278
  const allLevels = config.levels || DEFAULT_RELINKA_CONFIG.levels || {};
@@ -299,8 +334,10 @@ const consoleMethodMap = {
299
334
  success: console.log,
300
335
  verbose: console.log,
301
336
  log: console.log,
337
+ internal: console.log,
302
338
  step: console.log,
303
339
  box: console.log,
340
+ message: console.log,
304
341
  null: console.log
305
342
  };
306
343
  function logToConsole(config, level, formattedMessage) {
@@ -315,16 +352,40 @@ function logToConsole(config, level, formattedMessage) {
315
352
  method(`${colorFn(formattedMessage)}\x1B[0m`);
316
353
  }
317
354
  async function getLogFilesSortedByDate(config) {
318
- const logDirectoryPath = path.resolve(process.cwd(), getLogDir(config));
355
+ const logDirectoryPath = userTerminalCwd || process.cwd();
319
356
  try {
320
- if (!await fs.pathExists(logDirectoryPath)) {
321
- if (ENABLE_DEV_DEBUG) {
322
- 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
+ }
323
387
  }
324
- return [];
325
388
  }
326
- const files = await fs.readdir(logDirectoryPath);
327
- const logFiles = files.filter((f) => f.endsWith(".log"));
328
389
  if (logFiles.length === 0) return [];
329
390
  const fileInfoPromises = logFiles.map(
330
391
  async (fileName) => {
@@ -350,7 +411,7 @@ async function getLogFilesSortedByDate(config) {
350
411
  } catch (readErr) {
351
412
  if (isVerboseEnabled(config)) {
352
413
  console.error(
353
- `[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)}`
354
415
  );
355
416
  }
356
417
  return [];
@@ -602,13 +663,13 @@ relinka.null = (message, ...args) => relinka("null", message, ...args);
602
663
  relinka.step = (message, ...args) => relinka("step", message, ...args);
603
664
  relinka.box = (message, ...args) => relinka("box", message, ...args);
604
665
  relinka.clear = () => relinka("clear", "");
605
- relinka.message = (message, ...args) => relinka("log", message, ...args);
666
+ relinka.message = (message, ...args) => relinka("message", message, ...args);
606
667
  export async function relinkaAsync(type, message, ...args) {
607
668
  if (message === "") {
608
669
  console.log();
609
670
  return;
610
671
  }
611
- await relinkaConfig;
672
+ await relinkaConfig({ supportFreshLogFile: false });
612
673
  const levelName = type.toLowerCase();
613
674
  if (levelName === "fatal") {
614
675
  shouldNeverHappen(message, ...args);
@@ -651,7 +712,7 @@ export function formatBox(text) {
651
712
  const bottom = `\u2514${"\u2500".repeat(width)}\u2518`;
652
713
  const content = lines.map((line) => {
653
714
  const padding = width - line.length;
654
- return `\u2502 ${line}${" ".repeat(padding)}\u2502`;
715
+ return `\u2502 ${line}${" ".repeat(padding - 2)}\u2502`;
655
716
  }).join("\n");
656
717
  return `${top}
657
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.1",
13
+ "version": "1.5.3",
14
14
  "keywords": [
15
15
  "logger",
16
16
  "consola",