@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 +295 -199
- package/bin/impl.d.ts +29 -17
- package/bin/impl.js +99 -38
- package/bin/mod.d.ts +2 -2
- package/bin/mod.js +1 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
+
### 2. Basic Usage
|
|
29
29
|
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
+
## API Reference
|
|
38
74
|
|
|
39
|
-
|
|
75
|
+
### Core Functions
|
|
76
|
+
|
|
77
|
+
#### `relinka(level, message, ...args)`
|
|
78
|
+
|
|
79
|
+
Main logging function with dual syntax support.
|
|
40
80
|
|
|
41
81
|
```ts
|
|
42
|
-
|
|
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
|
-
|
|
91
|
+
#### `relinkaAsync(level, message, ...args)`
|
|
92
|
+
|
|
93
|
+
Async version that waits for configuration to load.
|
|
46
94
|
|
|
47
95
|
```ts
|
|
48
|
-
await
|
|
96
|
+
await relinkaAsync("info", "Async message");
|
|
97
|
+
await relinkaAsync("success", "Operation completed");
|
|
49
98
|
```
|
|
50
99
|
|
|
51
|
-
|
|
100
|
+
#### `relinkaConfig(options?)`
|
|
101
|
+
|
|
102
|
+
Load configuration with optional fresh log file support.
|
|
52
103
|
|
|
53
104
|
```ts
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
108
|
+
// With fresh log file (clears existing log file)
|
|
109
|
+
await relinkaConfig({ supportFreshLogFile: true });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### `relinkaShutdown()`
|
|
106
113
|
|
|
107
|
-
|
|
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
|
-
|
|
120
|
+
### Utility Functions
|
|
121
|
+
|
|
122
|
+
#### `log`, `logger`
|
|
123
|
+
|
|
124
|
+
Aliases for the main `relinka` function.
|
|
111
125
|
|
|
112
126
|
```ts
|
|
113
|
-
|
|
114
|
-
|
|
127
|
+
import { log, logger } from "@reliverse/relinka";
|
|
128
|
+
|
|
129
|
+
log("info", "Using alias");
|
|
130
|
+
logger.success("Another alias");
|
|
115
131
|
```
|
|
116
132
|
|
|
117
|
-
####
|
|
133
|
+
#### `message(msg, ...args)`
|
|
134
|
+
|
|
135
|
+
Convenience function for general messages.
|
|
118
136
|
|
|
119
137
|
```ts
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
import { message } from "@reliverse/relinka";
|
|
139
|
+
message("This is a general message");
|
|
122
140
|
```
|
|
123
141
|
|
|
124
|
-
|
|
142
|
+
#### `step(msg, ...args)`
|
|
143
|
+
|
|
144
|
+
Convenience function for progress steps.
|
|
125
145
|
|
|
126
146
|
```ts
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
##
|
|
152
|
+
## Configuration
|
|
153
|
+
|
|
154
|
+
Create a configuration file to customize Relinka's behavior:
|
|
155
|
+
|
|
156
|
+
### File Locations (in order of priority)
|
|
139
157
|
|
|
140
|
-
|
|
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
|
|
167
|
+
// Enable verbose logging
|
|
152
168
|
verbose: false,
|
|
153
169
|
|
|
154
170
|
// Timestamp configuration
|
|
155
171
|
timestamp: {
|
|
156
|
-
enabled:
|
|
157
|
-
format: "HH:mm:ss",
|
|
172
|
+
enabled: true,
|
|
173
|
+
format: "YYYY-MM-DD HH:mm:ss.SSS",
|
|
158
174
|
},
|
|
159
175
|
|
|
160
|
-
//
|
|
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
|
-
//
|
|
164
|
-
|
|
184
|
+
// Log rotation
|
|
185
|
+
dirs: {
|
|
186
|
+
maxLogFiles: 10, // Keep only the 10 most recent log files
|
|
187
|
+
},
|
|
165
188
|
|
|
166
|
-
//
|
|
167
|
-
|
|
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: "
|
|
203
|
+
symbol: "◈",
|
|
178
204
|
fallbackSymbol: "[i]",
|
|
179
205
|
color: "cyanBright",
|
|
180
206
|
spacing: 3,
|
|
181
207
|
},
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
- 🔜 other formats, supported by `reconf`, are coming soon
|
|
230
|
+
## File Logging
|
|
226
231
|
|
|
227
|
-
|
|
232
|
+
### File Naming Patterns
|
|
228
233
|
|
|
229
|
-
- Default
|
|
230
|
-
-
|
|
231
|
-
-
|
|
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
|
-
|
|
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", "
|
|
237
|
-
|
|
238
|
-
relinka("clear", ""); // clears terminal
|
|
293
|
+
relinka("info", "");
|
|
294
|
+
```
|
|
239
295
|
|
|
240
|
-
|
|
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
|
-
|
|
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
|
-
|
|
321
|
+
### 3. Leverage Verbose Logging
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
relinka("verbose", "Debug information"); // Only shown when verbose=true
|
|
325
|
+
```
|
|
248
326
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
|
346
|
+
### Why does my terminal hang after logging?
|
|
258
347
|
|
|
259
|
-
→ You
|
|
348
|
+
→ You forgot to call `await relinkaShutdown()` at the end of your program. This is required to flush buffers.
|
|
260
349
|
|
|
261
|
-
### Why my
|
|
350
|
+
### Why isn't my configuration loading?
|
|
262
351
|
|
|
263
|
-
→
|
|
352
|
+
→ Make sure you call `await relinkaConfig()` at the start of your application.
|
|
264
353
|
|
|
265
|
-
###
|
|
354
|
+
### Does `fatal` always throw?
|
|
266
355
|
|
|
267
|
-
→
|
|
356
|
+
→ Yes, `fatal` logs always throw errors and halt execution. In development mode, they also trigger the debugger.
|
|
268
357
|
|
|
269
|
-
###
|
|
358
|
+
### How do I disable colors?
|
|
270
359
|
|
|
271
|
-
→
|
|
360
|
+
→ Set `disableColors: true` in your configuration or use `NO_COLOR=1` environment variable.
|
|
272
361
|
|
|
273
|
-
###
|
|
362
|
+
### Can I use Relinka in both sync and async contexts?
|
|
274
363
|
|
|
275
|
-
|
|
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
|
-
|
|
366
|
+
### What's the difference between `log` and `message` levels?
|
|
283
367
|
|
|
284
|
-
|
|
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
|
-
##
|
|
370
|
+
## Ecosystem
|
|
289
371
|
|
|
290
|
-
|
|
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
|
-
|
|
374
|
+
- **CLI Development**: [`@reliverse/prompts`](https://npmjs.com/@reliverse/prompts)
|
|
375
|
+
- **Bundling**: [`@reliverse/dler`](https://npmjs.com/@reliverse/dler)
|
|
301
376
|
|
|
302
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
53
|
+
* Configuration for log file output.
|
|
67
54
|
*/
|
|
68
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ??
|
|
222
|
+
return config.dirs?.maxLogFiles ?? 0;
|
|
201
223
|
}
|
|
202
224
|
function getBaseLogName(config) {
|
|
203
|
-
|
|
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
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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 (
|
|
237
|
-
|
|
269
|
+
if (filename && !filename.endsWith(".log")) {
|
|
270
|
+
filename += ".log";
|
|
238
271
|
}
|
|
239
|
-
const
|
|
240
|
-
|
|
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 =
|
|
355
|
+
const logDirectoryPath = userTerminalCwd || process.cwd();
|
|
319
356
|
try {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
|
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("
|
|
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 {
|
|
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