@reliverse/relinka 1.4.0 → 1.4.2

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright (c) 2025 blefnk Nazar Kornienko
3
+ Copyright (c) Nazar Kornienko (blefnk), Reliverse
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -2,11 +2,9 @@
2
2
 
3
3
  [💖 GitHub Sponsors](https://github.com/sponsors/blefnk) • [💬 Discord](https://discord.gg/Pb8uKbwpsJ) • [✨ Repo](https://github.com/reliverse/relinka-logger) • [📦 NPM](https://npmjs.com/@reliverse/relinka)
4
4
 
5
- **@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.
5
+ **@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.
6
6
 
7
- Whether you're building CLI tools, SDKs, or full-stack apps — Relinka helps you log with intention.
8
-
9
- ## ✨ Why Relinka
7
+ ## Why Relinka
10
8
 
11
9
  - 🧙 Drop-in replacement for `node:console`, `consola`, or your internal logger
12
10
  - 💬 Supports: `info`, `warn`, `success`, `error`, `verbose`, `fatal`, `clear`
@@ -19,13 +17,15 @@ Whether you're building CLI tools, SDKs, or full-stack apps — Relinka helps yo
19
17
 
20
18
  ## Getting Started
21
19
 
22
- ### 2️⃣ Install
20
+ Make sure you have git, node.js, and bun/pnpm/yarn/npm installed.
21
+
22
+ ### 1. Install
23
23
 
24
24
  ```bash
25
25
  bun add @reliverse/relinka
26
26
  ```
27
27
 
28
- ### 2️⃣ Use It
28
+ ### 2. Use It
29
29
 
30
30
  #### Direct Method (Recommended)
31
31
 
@@ -50,6 +50,7 @@ import {
50
50
  relinkaConfig,
51
51
  relinkaShutdown,
52
52
  } from "@reliverse/relinka";
53
+
53
54
  export async function main() {
54
55
  await relinkaAsync(
55
56
  // this automatically loads the config
@@ -74,17 +75,25 @@ export async function main() {
74
75
  // "We should never reach this code! This should never happen! (see <anonymous> line)",
75
76
  // ); // fatal level throws error and halts execution
76
77
  relinka("success", "Thanks for using Relinka!");
78
+
77
79
  // Make sure to shut down the logger at the end of your program
78
80
  // This is important to flush all buffers and close file handles
79
81
  await relinkaShutdown();
82
+
83
+ // Make sure to exit the program after your CLI is done
84
+ // It's not required for Bun-only apps, but recommended
85
+ // for other terminal runtimes like Node.js (incl. `tsx`)
86
+ // It's also not required for @reliverse/rempts `runMain()`
87
+ process.exit(0);
80
88
  }
89
+
81
90
  await main();
82
91
  ```
83
92
 
84
93
  #### [🔜 Soon] Singleton Method
85
94
 
86
95
  ```ts
87
- const logger = initRelinkaInstance();
96
+ const logger = initRelinkaInstance({/* per-project config */});
88
97
  logger("info", "Looks great!");
89
98
  ```
90
99
 
@@ -257,7 +266,7 @@ defineConfig({ ... }) // helper for relinka.config.ts
257
266
 
258
267
  - Building CLIs? Use with [`@reliverse/prompts`](https://npmjs.com/@reliverse/prompts)
259
268
  - Want type-safe injections? Try [`@reliverse/reinject`](https://npmjs.com/@reliverse/reinject)
260
- - For advanced bundling? Pair with [`@reliverse/relidler`](https://npmjs.com/@reliverse/relidler)
269
+ - For advanced bundling? Pair with [`@reliverse/dler`](https://npmjs.com/@reliverse/dler)
261
270
 
262
271
  ## Roadmap
263
272
 
@@ -267,14 +276,18 @@ defineConfig({ ... }) // helper for relinka.config.ts
267
276
  - [x] Log rotation
268
277
  - [x] `fatal` type
269
278
  - [x] Runtime config
270
- - [ ] CLI interface (to manage logs, config, etc.)
279
+ - [ ] Implement per-project config redefinition
271
280
  - [ ] Plugin support (custom formatters, hooks)
281
+ - [ ] CLI interface (to manage logs, config, etc)
272
282
 
273
- ## Thanks
283
+ ## Shoutouts
274
284
 
275
- Relinka's DX design was inspired by this gem:
285
+ Relinka wouldn't exist without these gems:
276
286
 
277
- - [unjs/consola](https://github.com/unjs/consola)
287
+ - [unjs/consola](https://github.com/unjs/consola#readme)
288
+ - [winston](https://github.com/winstonjs/winston#readme)
289
+ - [pino](https://github.com/pinojs/pino#readme)
290
+ - [node-bunyan](https://github.com/trentm/node-bunyan#readme)
278
291
 
279
292
  ## License
280
293
 
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Compiles a format string with named or positional arguments to a standard format string.
3
+ * Uses @reliverse/repris's built-in compileFormat.
4
+ * @param {string} format - The format string containing named placeholders.
5
+ * @returns {string} The compiled format string with positional indices.
6
+ */
7
+ export declare function compile(format: string): string;
8
+ /**
9
+ * Formats a string using either an array or an object of arguments.
10
+ * Uses @reliverse/repris's built-in formatString.
11
+ * @param {string} format - The format string (named or positional).
12
+ * @param {any[] | Record<string, unknown>} args - Arguments to format into the string.
13
+ * @returns {string} The formatted string.
14
+ */
15
+ export declare function formatStr(format: string, args: any[] | Record<string, unknown>): string;
@@ -0,0 +1,13 @@
1
+ import { compileFormat, formatString } from "@reliverse/repris";
2
+ const _compileCache = {};
3
+ export function compile(format) {
4
+ if (_compileCache[format]) {
5
+ return _compileCache[format];
6
+ }
7
+ const compiled = compileFormat(format);
8
+ _compileCache[format] = compiled;
9
+ return compiled;
10
+ }
11
+ export function formatStr(format, args) {
12
+ return formatString(format, args);
13
+ }
@@ -4,11 +4,11 @@
4
4
  * @param {string} format - The format string containing the placeholders to replace.
5
5
  * @returns {string} The compiled format string with placeholders replaced by positional indices.
6
6
  */
7
- export declare function compileFormat(format: string): any;
7
+ export declare function compileFormatCompat(format: string): string;
8
8
  /**
9
9
  * Formats a string according to a custom format, using vsprintf for string formatting.
10
10
  * @param {string} format - The custom format string.
11
11
  * @param {any[]} argv - The arguments to format into the string.
12
12
  * @returns {string} The formatted string.
13
13
  */
14
- export declare function formatString(format: string, argv: any): string;
14
+ export declare function formatStringCompat(format: string, argv: any): string;
@@ -1,4 +1,4 @@
1
- import { vsprintf } from "printj";
1
+ import { vsprintf } from "@reliverse/repris";
2
2
  const FORMAT_ARGS = [
3
3
  ["additional", 5],
4
4
  ["message", 4],
@@ -7,7 +7,7 @@ const FORMAT_ARGS = [
7
7
  ["tag", 3]
8
8
  ];
9
9
  const _compileCache = {};
10
- export function compileFormat(format) {
10
+ export function compileFormatCompat(format) {
11
11
  if (_compileCache[format]) {
12
12
  return _compileCache[format];
13
13
  }
@@ -21,6 +21,6 @@ export function compileFormat(format) {
21
21
  _compileCache[format] = _format;
22
22
  return _format;
23
23
  }
24
- export function formatString(format, argv) {
25
- return vsprintf(compileFormat(format), argv);
24
+ export function formatStringCompat(format, argv) {
25
+ return vsprintf(compileFormatCompat(format), argv);
26
26
  }
package/bin/impl.d.ts CHANGED
@@ -108,9 +108,7 @@ export type LogFileInfo = {
108
108
  path: string;
109
109
  mtime: number;
110
110
  };
111
- /**
112
- * Promise that resolves once `reconf` loads and merges the config.
113
- */
111
+ /** Promise resolved once the user's config (if any) is merged. */
114
112
  export declare const relinkaConfig: Promise<RelinkaConfig>;
115
113
  /**
116
114
  * Shuts down the logger, flushing all buffers and clearing timers.
package/bin/impl.js CHANGED
@@ -4,7 +4,7 @@ import fs from "fs-extra";
4
4
  import os from "node:os";
5
5
  import path from "pathe";
6
6
  const ENABLE_DEV_DEBUG = false;
7
- const activeTimers = [];
7
+ const EXIT_GUARD = Symbol.for("relinka.exitHandlersRegistered");
8
8
  const DEFAULT_RELINKA_CONFIG = {
9
9
  verbose: false,
10
10
  dirs: {
@@ -38,7 +38,7 @@ const DEFAULT_RELINKA_CONFIG = {
38
38
  spacing: 3
39
39
  },
40
40
  info: {
41
- symbol: "\u2BCE",
41
+ symbol: "\u25C8",
42
42
  fallbackSymbol: "[i]",
43
43
  color: "cyanBright",
44
44
  spacing: 3
@@ -62,7 +62,7 @@ const DEFAULT_RELINKA_CONFIG = {
62
62
  spacing: 3
63
63
  },
64
64
  verbose: {
65
- symbol: "\u2727",
65
+ symbol: "\u2731",
66
66
  fallbackSymbol: "[VERBOSE]",
67
67
  color: "gray",
68
68
  spacing: 3
@@ -90,13 +90,14 @@ function isUnicodeSupported() {
90
90
  let currentConfig = { ...DEFAULT_RELINKA_CONFIG };
91
91
  let isConfigInitialized = false;
92
92
  let resolveRelinkaConfig;
93
- export const relinkaConfig = new Promise((resolve) => {
94
- resolveRelinkaConfig = resolve;
93
+ export const relinkaConfig = new Promise((res) => {
94
+ resolveRelinkaConfig = res;
95
95
  });
96
96
  const logBuffers = /* @__PURE__ */ new Map();
97
+ const activeTimers = [];
98
+ let bufferFlushTimer = null;
97
99
  let lastCleanupTime = 0;
98
100
  let cleanupScheduled = false;
99
- let bufferFlushTimer = null;
100
101
  async function initializeConfig() {
101
102
  try {
102
103
  const result = await loadConfig({
@@ -110,55 +111,42 @@ async function initializeConfig() {
110
111
  });
111
112
  currentConfig = result.config;
112
113
  isConfigInitialized = true;
114
+ resolveRelinkaConfig?.(currentConfig);
115
+ resolveRelinkaConfig = void 0;
113
116
  if (ENABLE_DEV_DEBUG) {
114
117
  console.log("[Dev Debug] Config file used:", result.configFile);
115
118
  console.log("[Dev Debug] All merged layers:", result.layers);
116
119
  console.log("[Dev Debug] Final configuration:", currentConfig);
117
120
  }
118
- if (resolveRelinkaConfig) {
119
- resolveRelinkaConfig(currentConfig);
120
- resolveRelinkaConfig = void 0;
121
- }
122
- setupBufferFlushTimer();
123
121
  } catch (err) {
124
122
  console.error(
125
123
  `[Relinka Config Error] Failed to load config: ${err instanceof Error ? err.message : String(err)}`
126
124
  );
127
125
  currentConfig = { ...DEFAULT_RELINKA_CONFIG };
128
126
  isConfigInitialized = true;
129
- if (resolveRelinkaConfig) {
130
- resolveRelinkaConfig(currentConfig);
131
- resolveRelinkaConfig = void 0;
132
- }
127
+ resolveRelinkaConfig?.(currentConfig);
128
+ resolveRelinkaConfig = void 0;
129
+ } finally {
133
130
  setupBufferFlushTimer();
134
131
  }
135
132
  }
136
133
  function setupBufferFlushTimer() {
137
134
  if (bufferFlushTimer) {
138
135
  clearInterval(bufferFlushTimer);
139
- const index = activeTimers.indexOf(bufferFlushTimer);
140
- if (index !== -1) {
141
- activeTimers.splice(index, 1);
142
- }
136
+ activeTimers.splice(activeTimers.indexOf(bufferFlushTimer), 1);
143
137
  }
144
- const maxBufferAge = getMaxBufferAge(currentConfig);
145
- bufferFlushTimer = setInterval(
146
- () => {
147
- const now = Date.now();
148
- for (const [filePath, buffer] of logBuffers.entries()) {
149
- if (buffer.entries.length > 0 && now - buffer.lastFlush >= maxBufferAge) {
150
- flushLogBuffer(currentConfig, filePath).catch((err) => {
151
- console.error(
152
- `[Relinka Buffer Flush Error] Failed to flush aged buffer: ${err instanceof Error ? err.message : String(err)}`
153
- );
154
- });
155
- }
156
- }
157
- },
158
- Math.min(maxBufferAge / 2, 2500)
159
- // Check at least every 2.5 seconds or half the max age
160
- );
138
+ const maxAge = getMaxBufferAge(currentConfig);
139
+ bufferFlushTimer = setInterval(flushDueBuffers, Math.min(maxAge / 2, 2500));
140
+ bufferFlushTimer.unref();
161
141
  activeTimers.push(bufferFlushTimer);
142
+ function flushDueBuffers() {
143
+ const now = Date.now();
144
+ for (const [fp, buf] of logBuffers) {
145
+ if (buf.entries.length && now - buf.lastFlush >= maxAge) {
146
+ flushLogBuffer(currentConfig, fp).catch(console.error);
147
+ }
148
+ }
149
+ }
162
150
  }
163
151
  initializeConfig().catch((err) => {
164
152
  console.error(
@@ -352,10 +340,14 @@ async function deleteFiles(filePaths, config) {
352
340
  );
353
341
  }
354
342
  }
343
+ let sigintHandler;
344
+ let sigtermHandler;
355
345
  export async function relinkaShutdown() {
356
346
  activeTimers.forEach((timer) => clearTimeout(timer));
357
347
  activeTimers.length = 0;
358
348
  cleanupScheduled = false;
349
+ if (sigintHandler) process.off("SIGINT", sigintHandler);
350
+ if (sigtermHandler) process.off("SIGTERM", sigtermHandler);
359
351
  await flushAllLogBuffers();
360
352
  }
361
353
  async function cleanupOldLogFiles(config) {
@@ -380,6 +372,7 @@ async function cleanupOldLogFiles(config) {
380
372
  }
381
373
  });
382
374
  }, delay);
375
+ timer.unref();
383
376
  activeTimers.push(timer);
384
377
  }
385
378
  return;
@@ -502,6 +495,22 @@ export function casesHandled(unexpectedCase) {
502
495
  `A case was not handled for value: ${truncateString(String(unexpectedCase ?? "unknown"))}`
503
496
  );
504
497
  }
498
+ function registerExitHandlers() {
499
+ if (globalThis[EXIT_GUARD]) return;
500
+ globalThis[EXIT_GUARD] = true;
501
+ process.once("beforeExit", () => {
502
+ void flushAllLogBuffers();
503
+ });
504
+ sigintHandler = () => {
505
+ void flushAllLogBuffers().finally(() => process.exit(0));
506
+ };
507
+ sigtermHandler = () => {
508
+ void flushAllLogBuffers().finally(() => process.exit(0));
509
+ };
510
+ process.once("SIGINT", sigintHandler);
511
+ process.once("SIGTERM", sigtermHandler);
512
+ }
513
+ registerExitHandlers();
505
514
  export function relinka(type, message, ...args) {
506
515
  if (type === "clear") {
507
516
  console.clear();
@@ -588,19 +597,3 @@ export async function relinkaAsync(type, message, ...args) {
588
597
  export function defineConfig(config) {
589
598
  return config;
590
599
  }
591
- process.on("beforeExit", () => {
592
- flushAllLogBuffers().catch(() => {
593
- });
594
- });
595
- process.on("SIGINT", () => {
596
- flushAllLogBuffers().catch(() => {
597
- }).finally(() => {
598
- process.exit(0);
599
- });
600
- });
601
- process.on("SIGTERM", () => {
602
- flushAllLogBuffers().catch(() => {
603
- }).finally(() => {
604
- process.exit(0);
605
- });
606
- });
@@ -14,7 +14,7 @@ export { box } from "./deprecated/utils/box.js";
14
14
  export type { ColorName, ColorFunction, } from "./deprecated/utils/deprecatedColors.js";
15
15
  export { colors, getColor, colorize, } from "./deprecated/utils/deprecatedColors.js";
16
16
  export { parseStack } from "./deprecated/utils/error.js";
17
- export { compileFormat, formatString, } from "./deprecated/utils/format.js";
17
+ export { compileFormatCompat, formatStringCompat, } from "./deprecated/utils/format.js";
18
18
  export { isPlainObject, isLogObj } from "./deprecated/utils/log.js";
19
19
  export { writeStream } from "./deprecated/utils/stream.js";
20
20
  export { stripAnsi, centerAlign, rightAlign, leftAlign, align, } from "./deprecated/utils/string.js";
@@ -35,8 +35,8 @@ export {
35
35
  } from "./deprecated/utils/deprecatedColors.js";
36
36
  export { parseStack } from "./deprecated/utils/error.js";
37
37
  export {
38
- compileFormat,
39
- formatString
38
+ compileFormatCompat,
39
+ formatStringCompat
40
40
  } from "./deprecated/utils/format.js";
41
41
  export { isPlainObject, isLogObj } from "./deprecated/utils/log.js";
42
42
  export { writeStream } from "./deprecated/utils/stream.js";
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "dependencies": {
3
- "@reliverse/relico": "^1.1.0",
3
+ "@reliverse/relico": "^1.1.1",
4
+ "@reliverse/repris": "^1.0.0",
4
5
  "@reliverse/runtime": "^1.0.3",
5
6
  "c12": "^3.0.3",
6
7
  "defu": "^6.1.4",
7
8
  "fs-extra": "^11.3.0",
8
9
  "pathe": "^2.0.3",
9
- "printj": "^1.3.1",
10
10
  "std-env": "^3.9.0",
11
11
  "string-width": "^7.2.0"
12
12
  },
@@ -15,7 +15,7 @@
15
15
  "license": "MIT",
16
16
  "name": "@reliverse/relinka",
17
17
  "type": "module",
18
- "version": "1.4.0",
18
+ "version": "1.4.2",
19
19
  "keywords": [
20
20
  "logger",
21
21
  "consola",
@@ -34,25 +34,26 @@
34
34
  ],
35
35
  "devDependencies": {
36
36
  "@biomejs/biome": "1.9.4",
37
- "@eslint/js": "^9.24.0",
37
+ "@eslint/js": "^9.26.0",
38
38
  "@reliverse/relidler-cfg": "^1.1.3",
39
39
  "@stylistic/eslint-plugin": "^4.2.0",
40
40
  "@total-typescript/ts-reset": "^0.6.1",
41
- "@types/bun": "^1.2.9",
41
+ "@types/bun": "^1.2.13",
42
42
  "@types/fs-extra": "^11.0.4",
43
- "@types/node": "^22.14.1",
43
+ "@types/node": "^22.15.17",
44
44
  "@types/sentencer": "^0.2.3",
45
- "eslint": "^9.24.0",
45
+ "eslint": "^9.26.0",
46
46
  "eslint-plugin-no-relative-import-paths": "^1.6.1",
47
- "eslint-plugin-perfectionist": "^4.11.0",
47
+ "eslint-plugin-perfectionist": "^4.13.0",
48
48
  "jiti": "^2.4.2",
49
- "knip": "^5.50.2",
49
+ "knip": "^5.55.1",
50
50
  "sentencer": "^0.2.1",
51
+ "tsx": "^4.19.4",
51
52
  "typescript": "^5.8.3",
52
- "typescript-eslint": "^8.29.1"
53
+ "typescript-eslint": "^8.32.1"
53
54
  },
54
55
  "exports": {
55
- ".": "./bin/main.js"
56
+ ".": "./bin/mod.js"
56
57
  },
57
58
  "files": [
58
59
  "bin",
@@ -60,8 +61,8 @@
60
61
  "README.md",
61
62
  "LICENSE"
62
63
  ],
63
- "main": "./bin/main.js",
64
- "module": "./bin/main.js",
64
+ "main": "./bin/mod.js",
65
+ "module": "./bin/mod.js",
65
66
  "publishConfig": {
66
67
  "access": "public"
67
68
  }