@meadown/logger 1.8.8 → 1.8.10

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
@@ -30,7 +30,7 @@ tells you where things came from and disappears when you ship.
30
30
 
31
31
  - **Zero dependencies**
32
32
  - **Development-focused** — built for the dev experience, not production ops
33
- - **Clickable source link** — every log is a clickable link to the exact file it came from
33
+ - **Clickable source link** — every log is a clickable link that jumps to the exact file and line it came from
34
34
  - **Tap logging** — log any value or promise inline; fetch calls also get timing, status, size, and body
35
35
  - **Color-coded levels** — `[INFO]` cyan, `[WARN]` yellow, `[ERROR]` red
36
36
  - **Tree layout output** — clean, scannable structure in your terminal
@@ -163,9 +163,7 @@ as the fetch example above.
163
163
 
164
164
  ## Clickable source link
165
165
 
166
- That `(server.ts:42)` is a **clickable link**. Click it and the file opens.
167
- The line number is right there so you can jump straight to it. Works in VS Code,
168
- iTerm2, WezTerm, Kitty, and Windows Terminal. Degrades to plain text everywhere else.
166
+ That `(server.ts:42)` is a **clickable link**. Click it and your editor opens the file and jumps straight to that line. Works in VS Code, iTerm2, WezTerm, Kitty, and Windows Terminal. Degrades to plain text everywhere else.
169
167
 
170
168
  ## Color-coded levels
171
169
 
@@ -11,7 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.default = createLog;
13
13
  const getCaller_js_1 = __importDefault(require("../caller/getCaller.js"));
14
- const writeLog_js_1 = require("./writeLog.js");
14
+ const index_js_1 = require("./writeLog/index.js");
15
15
  const config_js_1 = require("../config.js");
16
16
  /**
17
17
  * Builds a log function bound to a console channel and tag. The returned closure
@@ -24,6 +24,6 @@ function createLog(channel, tag) {
24
24
  if (!(0, config_js_1.isLogAllowed)())
25
25
  return;
26
26
  const caller = (0, getCaller_js_1.default)();
27
- (0, writeLog_js_1.writeLog)({ channel, tag, args, caller });
27
+ (0, index_js_1.writeLog)({ channel, tag, args, caller });
28
28
  };
29
29
  }
@@ -0,0 +1,6 @@
1
+ import { type Caller } from "../../caller/getCaller.js";
2
+ /**
3
+ * Renders a caller as a `(file:line)` location — a clickable OSC-8 link on a
4
+ * supporting terminal, plain text otherwise. Pure (no stack access).
5
+ */
6
+ export declare function formatLocation(caller: Caller, interactive: boolean): string;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ /*
3
+ * formatLocation.ts
4
+ * Created by Dewan Mobashirul
5
+ * Copyright (c) 2026 dewan-meadown
6
+ * All rights reserved
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.formatLocation = formatLocation;
10
+ const link_js_1 = require("../../decorations/link.js");
11
+ /**
12
+ * Renders a caller as a `(file:line)` location — a clickable OSC-8 link on a
13
+ * supporting terminal, plain text otherwise. Pure (no stack access).
14
+ */
15
+ function formatLocation(caller, interactive) {
16
+ if (caller.file !== null && caller.line !== null && interactive)
17
+ return (0, link_js_1.hyperlink)(caller.label, (0, link_js_1.fileUrl)(caller.file));
18
+ return caller.label;
19
+ }
@@ -1,9 +1,6 @@
1
- import { type LogChannel } from "../constants.js";
2
- import { type Caller } from "../caller/getCaller.js";
3
- /** How many lines a long message shows before collapsing (0 = all). */
4
- export declare function getVisibleLines(): number;
5
- /** Set how many lines a long message shows before collapsing (0 = all). */
6
- export declare function setVisibleLines(value: number): void;
1
+ import { type LogChannel } from "../../constants.js";
2
+ import { type Caller } from "../../caller/getCaller.js";
3
+ export { getVisibleLines, setVisibleLines } from "./visibleLines.js";
7
4
  /**
8
5
  * Renders and writes one log entry. The `caller` is resolved by the *caller* of
9
6
  * this function (the log closure or `tap`) and passed in, so this helper never
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ /*
3
+ * index.ts
4
+ * Created by Dewan Mobashirul
5
+ * Copyright (c) 2026 dewan-meadown
6
+ * All rights reserved
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.setVisibleLines = exports.getVisibleLines = void 0;
13
+ exports.writeLog = writeLog;
14
+ const constants_js_1 = require("../../constants.js");
15
+ const getTimeStamp_js_1 = __importDefault(require("../../time/getTimeStamp.js"));
16
+ const color_js_1 = require("../../colors/color.js");
17
+ const isTTY_js_1 = require("../../terminal/isTTY.js");
18
+ const renderMessage_js_1 = require("./renderMessage.js");
19
+ const formatLocation_js_1 = require("./formatLocation.js");
20
+ var visibleLines_js_1 = require("./visibleLines.js");
21
+ Object.defineProperty(exports, "getVisibleLines", { enumerable: true, get: function () { return visibleLines_js_1.getVisibleLines; } });
22
+ Object.defineProperty(exports, "setVisibleLines", { enumerable: true, get: function () { return visibleLines_js_1.setVisibleLines; } });
23
+ /**
24
+ * Renders and writes one log entry. The `caller` is resolved by the *caller* of
25
+ * this function (the log closure or `tap`) and passed in, so this helper never
26
+ * touches the stack — keeping {@link getCaller}'s frame depth correct no matter
27
+ * which user-facing function delegates here.
28
+ */
29
+ function writeLog(opts) {
30
+ const { channel, tag, args, caller } = opts;
31
+ const streamName = channel === "log" ? "stdout" : "stderr";
32
+ // One terminal check drives both color and clickable links — `isTTY` is the
33
+ // single source of truth (DRY). Off when output is piped/redirected.
34
+ const useColor = (0, isTTY_js_1.isTTY)(streamName);
35
+ const paint = (s, c) => useColor ? (0, color_js_1.colorize)(s, c) : s;
36
+ const location = (0, formatLocation_js_1.formatLocation)(caller, useColor);
37
+ const tagOut = paint(tag, constants_js_1.TAG_COLOR[channel]);
38
+ const timeStamp = paint((0, getTimeStamp_js_1.default)(), "teal");
39
+ const locOut = paint(`(${location})`, "dimTeal");
40
+ const connector = paint(constants_js_1.BRANCH, "gray");
41
+ const connectorBottom = paint(constants_js_1.BRANCH_END, "gray");
42
+ const separator = paint(constants_js_1.SEPARATOR, "gray");
43
+ // Layout: the tag, the message hanging off a `├──` branch, then the timestamp
44
+ // and location on a `└──` branch below. Leading `\n` spaces entries apart.
45
+ const message = (0, renderMessage_js_1.renderMessage)(args, useColor);
46
+ const meta = `\n${connectorBottom} ${timeStamp} ${separator} ${locOut}`;
47
+ console[channel](`\n${tagOut}`, `\n${connector}`, message, meta);
48
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Renders the args into a single message string exactly as console would —
3
+ * objects/errors via util.inspect, `%s`/`%d` format specifiers, and colors when
4
+ * on a terminal — then hang-indents every continuation line so multi-line
5
+ * output stays left-aligned under the branch, and collapses very long output.
6
+ */
7
+ export declare function renderMessage(args: unknown[], useColor: boolean): string;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /*
3
+ * renderMessage.ts
4
+ * Created by Dewan Mobashirul
5
+ * Copyright (c) 2026 dewan-meadown
6
+ * All rights reserved
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.renderMessage = renderMessage;
10
+ const node_util_1 = require("node:util");
11
+ const constants_js_1 = require("../../constants.js");
12
+ const color_js_1 = require("../../colors/color.js");
13
+ const visibleLines_js_1 = require("./visibleLines.js");
14
+ /**
15
+ * Collapses a long multi-line message to {@link visibleLines} lines, replacing
16
+ * the rest with a dimmed `… N more lines` summary. When `visibleLines` is 0
17
+ * (the default) nothing is collapsed — the full message is shown.
18
+ */
19
+ function collapse(text, useColor) {
20
+ if (visibleLines_js_1.visibleLines < 1)
21
+ return text;
22
+ const lines = text.split("\n");
23
+ if (lines.length <= visibleLines_js_1.visibleLines)
24
+ return text;
25
+ const hidden = lines.length - visibleLines_js_1.visibleLines;
26
+ const summary = `${constants_js_1.MESSAGE_INDENT}... ${hidden} more line${hidden === 1 ? "" : "s"}`;
27
+ const visible = lines.slice(0, visibleLines_js_1.visibleLines);
28
+ visible.push(useColor ? (0, color_js_1.colorize)(summary, "gray") : summary);
29
+ return visible.join("\n");
30
+ }
31
+ /**
32
+ * Renders the args into a single message string exactly as console would —
33
+ * objects/errors via util.inspect, `%s`/`%d` format specifiers, and colors when
34
+ * on a terminal — then hang-indents every continuation line so multi-line
35
+ * output stays left-aligned under the branch, and collapses very long output.
36
+ */
37
+ function renderMessage(args, useColor) {
38
+ const text = (0, node_util_1.formatWithOptions)({ colors: useColor }, ...args);
39
+ return collapse(text.replace(/\n/g, `\n${(0, color_js_1.colorize)(constants_js_1.MESSAGE_INDENT, "gray")}`), useColor);
40
+ }
@@ -0,0 +1,6 @@
1
+ /** Max message lines to show before collapsing the rest; 0 (default) shows all. */
2
+ export declare let visibleLines: number;
3
+ /** How many lines a long message shows before collapsing (0 = all). */
4
+ export declare function getVisibleLines(): number;
5
+ /** Set how many lines a long message shows before collapsing (0 = all). */
6
+ export declare function setVisibleLines(value: number): void;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /*
3
+ * visibleLines.ts
4
+ * Created by Dewan Mobashirul
5
+ * Copyright (c) 2026 dewan-meadown
6
+ * All rights reserved
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.visibleLines = void 0;
10
+ exports.getVisibleLines = getVisibleLines;
11
+ exports.setVisibleLines = setVisibleLines;
12
+ const constants_js_1 = require("../../constants.js");
13
+ /** Max message lines to show before collapsing the rest; 0 (default) shows all. */
14
+ exports.visibleLines = constants_js_1.DEFAULT_MAX_LINES;
15
+ /** How many lines a long message shows before collapsing (0 = all). */
16
+ function getVisibleLines() {
17
+ return exports.visibleLines;
18
+ }
19
+ /** Set how many lines a long message shows before collapsing (0 = all). */
20
+ function setVisibleLines(value) {
21
+ exports.visibleLines = Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
22
+ }
@@ -1,10 +1,4 @@
1
- /**
2
- * Builds a valid `file://` URL for a path so terminals can open it on click.
3
- * Paths already in `file://` form are used as-is. We intentionally do NOT append
4
- * `:line` — that isn't a valid URI and breaks file openers (e.g. GNOME/`gio`,
5
- * which would look for a file literally named `foo.ts:42`). The line number
6
- * stays visible in the link's display text instead.
7
- */
1
+ /** Builds a `file://` URL for a path so terminals can open it on click. */
8
2
  export declare function fileUrl(file: string): string;
9
3
  /**
10
4
  * Wraps `text` in an OSC-8 terminal hyperlink pointing at `url`. Terminals that
@@ -9,13 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.fileUrl = fileUrl;
10
10
  exports.hyperlink = hyperlink;
11
11
  const node_url_1 = require("node:url");
12
- /**
13
- * Builds a valid `file://` URL for a path so terminals can open it on click.
14
- * Paths already in `file://` form are used as-is. We intentionally do NOT append
15
- * `:line` — that isn't a valid URI and breaks file openers (e.g. GNOME/`gio`,
16
- * which would look for a file literally named `foo.ts:42`). The line number
17
- * stays visible in the link's display text instead.
18
- */
12
+ /** Builds a `file://` URL for a path so terminals can open it on click. */
19
13
  function fileUrl(file) {
20
14
  return file.startsWith("file://") ? file : (0, node_url_1.pathToFileURL)(file).href;
21
15
  }
package/dist/cjs/index.js CHANGED
@@ -12,7 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.logger = void 0;
13
13
  const createLog_js_1 = __importDefault(require("./core/createLog.js"));
14
14
  const createTap_js_1 = __importDefault(require("./tap/createTap.js"));
15
- const writeLog_js_1 = require("./core/writeLog.js");
15
+ const index_js_1 = require("./core/writeLog/index.js");
16
16
  /**
17
17
  * Logs to the console, but only outside production. Each line is prefixed with
18
18
  * a level tag, a short local timestamp, and a clickable link to the file it was
@@ -32,8 +32,8 @@ exports.logger = logger;
32
32
  // `maxLines` is a live getter/setter backed by the shared collapse setting, so
33
33
  // setting it once affects info, error, and warn alike.
34
34
  Object.defineProperty(logger, "maxLines", {
35
- get: writeLog_js_1.getVisibleLines,
36
- set: writeLog_js_1.setVisibleLines,
35
+ get: index_js_1.getVisibleLines,
36
+ set: index_js_1.setVisibleLines,
37
37
  enumerable: true,
38
38
  configurable: true,
39
39
  });
@@ -11,7 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.default = createTap;
13
13
  const getCaller_js_1 = __importDefault(require("../caller/getCaller.js"));
14
- const writeLog_js_1 = require("../core/writeLog.js");
14
+ const index_js_1 = require("../core/writeLog/index.js");
15
15
  const config_js_1 = require("../config.js");
16
16
  const tapAsync_js_1 = require("./tapAsync.js");
17
17
  /**
@@ -38,7 +38,7 @@ function createTap() {
38
38
  (0, tapAsync_js_1.tapAsync)(value, label, caller);
39
39
  }
40
40
  else {
41
- (0, writeLog_js_1.writeLog)({
41
+ (0, index_js_1.writeLog)({
42
42
  channel: "log",
43
43
  tag: "[TAP]",
44
44
  args: label === undefined ? [value] : [label, value],
@@ -14,7 +14,7 @@ exports.isThenable = isThenable;
14
14
  exports.tapAsync = tapAsync;
15
15
  const node_perf_hooks_1 = require("node:perf_hooks");
16
16
  const node_util_1 = require("node:util");
17
- const writeLog_js_1 = require("../core/writeLog.js");
17
+ const index_js_1 = require("../core/writeLog/index.js");
18
18
  const color_js_1 = require("../colors/color.js");
19
19
  const isTTY_js_1 = require("../terminal/isTTY.js");
20
20
  /** Whether `value` is thenable (a promise we can await + time). */
@@ -151,7 +151,7 @@ function tapAsync(promise, label, caller) {
151
151
  clone = null;
152
152
  }
153
153
  if (clone === null) {
154
- (0, writeLog_js_1.writeLog)({
154
+ (0, index_js_1.writeLog)({
155
155
  channel: "log", tag: "[TAP]",
156
156
  args: buildBlock(label, ms, resolved, { data: undefined, size: "unknown" }, useColor),
157
157
  caller,
@@ -161,7 +161,7 @@ function tapAsync(promise, label, caller) {
161
161
  const cl = resolved.headers?.get?.("content-length");
162
162
  const tooLarge = cl != null && cl !== "" && Number(cl) > 512 * 1024;
163
163
  if (tooLarge) {
164
- (0, writeLog_js_1.writeLog)({
164
+ (0, index_js_1.writeLog)({
165
165
  channel: "log", tag: "[TAP]",
166
166
  args: buildBlock(label, ms, resolved, { data: "(body too large to display)", size: formatBytes(Number(cl)) }, useColor),
167
167
  caller,
@@ -169,13 +169,13 @@ function tapAsync(promise, label, caller) {
169
169
  return;
170
170
  }
171
171
  void readBody(clone).then((body) => {
172
- (0, writeLog_js_1.writeLog)({ channel: "log", tag: "[TAP]", args: buildBlock(label, ms, resolved, body, useColor), caller });
172
+ (0, index_js_1.writeLog)({ channel: "log", tag: "[TAP]", args: buildBlock(label, ms, resolved, body, useColor), caller });
173
173
  });
174
174
  return;
175
175
  }
176
176
  // Non-Response promise — plain value with elapsed time.
177
177
  const elapsed = formatDuration(ms, useColor);
178
- (0, writeLog_js_1.writeLog)({
178
+ (0, index_js_1.writeLog)({
179
179
  channel: "log", tag: "[TAP]",
180
180
  args: label === undefined ? [elapsed, resolved] : [`${label} ${elapsed}`, resolved],
181
181
  caller,
@@ -183,7 +183,7 @@ function tapAsync(promise, label, caller) {
183
183
  }, (err) => {
184
184
  const ms = Math.round(node_perf_hooks_1.performance.now() - start);
185
185
  const elapsed = formatDuration(ms, useColor);
186
- (0, writeLog_js_1.writeLog)({
186
+ (0, index_js_1.writeLog)({
187
187
  channel: "error", tag: "[TAP]",
188
188
  args: [label === undefined ? `rejected after ${elapsed}` : `${label} rejected after ${elapsed}`, err],
189
189
  caller,
@@ -5,7 +5,7 @@
5
5
  * All rights reserved
6
6
  */
7
7
  import getCaller from "../caller/getCaller.js";
8
- import { writeLog } from "./writeLog.js";
8
+ import { writeLog } from "./writeLog/index.js";
9
9
  import { isLogAllowed } from "../config.js";
10
10
  /**
11
11
  * Builds a log function bound to a console channel and tag. The returned closure
@@ -0,0 +1,6 @@
1
+ import { type Caller } from "../../caller/getCaller.js";
2
+ /**
3
+ * Renders a caller as a `(file:line)` location — a clickable OSC-8 link on a
4
+ * supporting terminal, plain text otherwise. Pure (no stack access).
5
+ */
6
+ export declare function formatLocation(caller: Caller, interactive: boolean): string;
@@ -0,0 +1,16 @@
1
+ /*
2
+ * formatLocation.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 dewan-meadown
5
+ * All rights reserved
6
+ */
7
+ import { fileUrl, hyperlink } from "../../decorations/link.js";
8
+ /**
9
+ * Renders a caller as a `(file:line)` location — a clickable OSC-8 link on a
10
+ * supporting terminal, plain text otherwise. Pure (no stack access).
11
+ */
12
+ export function formatLocation(caller, interactive) {
13
+ if (caller.file !== null && caller.line !== null && interactive)
14
+ return hyperlink(caller.label, fileUrl(caller.file));
15
+ return caller.label;
16
+ }
@@ -1,9 +1,6 @@
1
- import { type LogChannel } from "../constants.js";
2
- import { type Caller } from "../caller/getCaller.js";
3
- /** How many lines a long message shows before collapsing (0 = all). */
4
- export declare function getVisibleLines(): number;
5
- /** Set how many lines a long message shows before collapsing (0 = all). */
6
- export declare function setVisibleLines(value: number): void;
1
+ import { type LogChannel } from "../../constants.js";
2
+ import { type Caller } from "../../caller/getCaller.js";
3
+ export { getVisibleLines, setVisibleLines } from "./visibleLines.js";
7
4
  /**
8
5
  * Renders and writes one log entry. The `caller` is resolved by the *caller* of
9
6
  * this function (the log closure or `tap`) and passed in, so this helper never
@@ -0,0 +1,39 @@
1
+ /*
2
+ * index.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 dewan-meadown
5
+ * All rights reserved
6
+ */
7
+ import { TAG_COLOR, BRANCH, BRANCH_END, SEPARATOR, } from "../../constants.js";
8
+ import getTimeStamp from "../../time/getTimeStamp.js";
9
+ import { colorize } from "../../colors/color.js";
10
+ import { isTTY } from "../../terminal/isTTY.js";
11
+ import { renderMessage } from "./renderMessage.js";
12
+ import { formatLocation } from "./formatLocation.js";
13
+ export { getVisibleLines, setVisibleLines } from "./visibleLines.js";
14
+ /**
15
+ * Renders and writes one log entry. The `caller` is resolved by the *caller* of
16
+ * this function (the log closure or `tap`) and passed in, so this helper never
17
+ * touches the stack — keeping {@link getCaller}'s frame depth correct no matter
18
+ * which user-facing function delegates here.
19
+ */
20
+ export function writeLog(opts) {
21
+ const { channel, tag, args, caller } = opts;
22
+ const streamName = channel === "log" ? "stdout" : "stderr";
23
+ // One terminal check drives both color and clickable links — `isTTY` is the
24
+ // single source of truth (DRY). Off when output is piped/redirected.
25
+ const useColor = isTTY(streamName);
26
+ const paint = (s, c) => useColor ? colorize(s, c) : s;
27
+ const location = formatLocation(caller, useColor);
28
+ const tagOut = paint(tag, TAG_COLOR[channel]);
29
+ const timeStamp = paint(getTimeStamp(), "teal");
30
+ const locOut = paint(`(${location})`, "dimTeal");
31
+ const connector = paint(BRANCH, "gray");
32
+ const connectorBottom = paint(BRANCH_END, "gray");
33
+ const separator = paint(SEPARATOR, "gray");
34
+ // Layout: the tag, the message hanging off a `├──` branch, then the timestamp
35
+ // and location on a `└──` branch below. Leading `\n` spaces entries apart.
36
+ const message = renderMessage(args, useColor);
37
+ const meta = `\n${connectorBottom} ${timeStamp} ${separator} ${locOut}`;
38
+ console[channel](`\n${tagOut}`, `\n${connector}`, message, meta);
39
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Renders the args into a single message string exactly as console would —
3
+ * objects/errors via util.inspect, `%s`/`%d` format specifiers, and colors when
4
+ * on a terminal — then hang-indents every continuation line so multi-line
5
+ * output stays left-aligned under the branch, and collapses very long output.
6
+ */
7
+ export declare function renderMessage(args: unknown[], useColor: boolean): string;
@@ -0,0 +1,37 @@
1
+ /*
2
+ * renderMessage.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 dewan-meadown
5
+ * All rights reserved
6
+ */
7
+ import { formatWithOptions } from "node:util";
8
+ import { MESSAGE_INDENT } from "../../constants.js";
9
+ import { colorize } from "../../colors/color.js";
10
+ import { visibleLines } from "./visibleLines.js";
11
+ /**
12
+ * Collapses a long multi-line message to {@link visibleLines} lines, replacing
13
+ * the rest with a dimmed `… N more lines` summary. When `visibleLines` is 0
14
+ * (the default) nothing is collapsed — the full message is shown.
15
+ */
16
+ function collapse(text, useColor) {
17
+ if (visibleLines < 1)
18
+ return text;
19
+ const lines = text.split("\n");
20
+ if (lines.length <= visibleLines)
21
+ return text;
22
+ const hidden = lines.length - visibleLines;
23
+ const summary = `${MESSAGE_INDENT}... ${hidden} more line${hidden === 1 ? "" : "s"}`;
24
+ const visible = lines.slice(0, visibleLines);
25
+ visible.push(useColor ? colorize(summary, "gray") : summary);
26
+ return visible.join("\n");
27
+ }
28
+ /**
29
+ * Renders the args into a single message string exactly as console would —
30
+ * objects/errors via util.inspect, `%s`/`%d` format specifiers, and colors when
31
+ * on a terminal — then hang-indents every continuation line so multi-line
32
+ * output stays left-aligned under the branch, and collapses very long output.
33
+ */
34
+ export function renderMessage(args, useColor) {
35
+ const text = formatWithOptions({ colors: useColor }, ...args);
36
+ return collapse(text.replace(/\n/g, `\n${colorize(MESSAGE_INDENT, "gray")}`), useColor);
37
+ }
@@ -0,0 +1,6 @@
1
+ /** Max message lines to show before collapsing the rest; 0 (default) shows all. */
2
+ export declare let visibleLines: number;
3
+ /** How many lines a long message shows before collapsing (0 = all). */
4
+ export declare function getVisibleLines(): number;
5
+ /** Set how many lines a long message shows before collapsing (0 = all). */
6
+ export declare function setVisibleLines(value: number): void;
@@ -0,0 +1,17 @@
1
+ /*
2
+ * visibleLines.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 dewan-meadown
5
+ * All rights reserved
6
+ */
7
+ import { DEFAULT_MAX_LINES } from "../../constants.js";
8
+ /** Max message lines to show before collapsing the rest; 0 (default) shows all. */
9
+ export let visibleLines = DEFAULT_MAX_LINES;
10
+ /** How many lines a long message shows before collapsing (0 = all). */
11
+ export function getVisibleLines() {
12
+ return visibleLines;
13
+ }
14
+ /** Set how many lines a long message shows before collapsing (0 = all). */
15
+ export function setVisibleLines(value) {
16
+ visibleLines = Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
17
+ }
@@ -1,10 +1,4 @@
1
- /**
2
- * Builds a valid `file://` URL for a path so terminals can open it on click.
3
- * Paths already in `file://` form are used as-is. We intentionally do NOT append
4
- * `:line` — that isn't a valid URI and breaks file openers (e.g. GNOME/`gio`,
5
- * which would look for a file literally named `foo.ts:42`). The line number
6
- * stays visible in the link's display text instead.
7
- */
1
+ /** Builds a `file://` URL for a path so terminals can open it on click. */
8
2
  export declare function fileUrl(file: string): string;
9
3
  /**
10
4
  * Wraps `text` in an OSC-8 terminal hyperlink pointing at `url`. Terminals that
@@ -5,13 +5,7 @@
5
5
  * All rights reserved
6
6
  */
7
7
  import { pathToFileURL } from "node:url";
8
- /**
9
- * Builds a valid `file://` URL for a path so terminals can open it on click.
10
- * Paths already in `file://` form are used as-is. We intentionally do NOT append
11
- * `:line` — that isn't a valid URI and breaks file openers (e.g. GNOME/`gio`,
12
- * which would look for a file literally named `foo.ts:42`). The line number
13
- * stays visible in the link's display text instead.
14
- */
8
+ /** Builds a `file://` URL for a path so terminals can open it on click. */
15
9
  export function fileUrl(file) {
16
10
  return file.startsWith("file://") ? file : pathToFileURL(file).href;
17
11
  }
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import createLog from "./core/createLog.js";
8
8
  import createTap from "./tap/createTap.js";
9
- import { getVisibleLines, setVisibleLines } from "./core/writeLog.js";
9
+ import { getVisibleLines, setVisibleLines } from "./core/writeLog/index.js";
10
10
  /**
11
11
  * Logs to the console, but only outside production. Each line is prefixed with
12
12
  * a level tag, a short local timestamp, and a clickable link to the file it was
@@ -5,7 +5,7 @@
5
5
  * All rights reserved
6
6
  */
7
7
  import getCaller from "../caller/getCaller.js";
8
- import { writeLog } from "../core/writeLog.js";
8
+ import { writeLog } from "../core/writeLog/index.js";
9
9
  import { isLogAllowed } from "../config.js";
10
10
  import { isThenable, tapAsync } from "./tapAsync.js";
11
11
  /**
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { performance } from "node:perf_hooks";
12
12
  import { formatWithOptions } from "node:util";
13
- import { writeLog } from "../core/writeLog.js";
13
+ import { writeLog } from "../core/writeLog/index.js";
14
14
  import { colorize } from "../colors/color.js";
15
15
  import { isTTY } from "../terminal/isTTY.js";
16
16
  /** Whether `value` is thenable (a promise we can await + time). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meadown/logger",
3
- "version": "1.8.8",
3
+ "version": "1.8.10",
4
4
  "description": "A development-focused logger for Node.js and TypeScript — zero dependencies, clickable source links, and API response logging built in.",
5
5
  "keywords": [
6
6
  "logger",
@@ -1,92 +0,0 @@
1
- "use strict";
2
- /*
3
- * writeLog.ts
4
- * Created by Dewan Mobashirul
5
- * Copyright (c) 2026 dewan-meadown
6
- * All rights reserved
7
- */
8
- var __importDefault = (this && this.__importDefault) || function (mod) {
9
- return (mod && mod.__esModule) ? mod : { "default": mod };
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.getVisibleLines = getVisibleLines;
13
- exports.setVisibleLines = setVisibleLines;
14
- exports.writeLog = writeLog;
15
- const node_util_1 = require("node:util");
16
- const constants_js_1 = require("../constants.js");
17
- const getTimeStamp_js_1 = __importDefault(require("../time/getTimeStamp.js"));
18
- const link_js_1 = require("../decorations/link.js");
19
- const color_js_1 = require("../colors/color.js");
20
- const isTTY_js_1 = require("../terminal/isTTY.js");
21
- /** Max message lines to show before collapsing the rest; 0 (default) shows all. */
22
- let visibleLines = constants_js_1.DEFAULT_MAX_LINES;
23
- /** How many lines a long message shows before collapsing (0 = all). */
24
- function getVisibleLines() {
25
- return visibleLines;
26
- }
27
- /** Set how many lines a long message shows before collapsing (0 = all). */
28
- function setVisibleLines(value) {
29
- visibleLines = Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
30
- }
31
- /**
32
- * Collapses a long multi-line message to {@link visibleLines} lines, replacing
33
- * the rest with a dimmed `… N more lines` summary. When `visibleLines` is 0
34
- * (the default) nothing is collapsed — the full message is shown.
35
- */
36
- function collapse(text, useColor) {
37
- if (visibleLines < 1)
38
- return text;
39
- const lines = text.split("\n");
40
- if (lines.length <= visibleLines)
41
- return text;
42
- const hidden = lines.length - visibleLines;
43
- const summary = `${constants_js_1.MESSAGE_INDENT}... ${hidden} more line${hidden === 1 ? "" : "s"}`;
44
- const visible = lines.slice(0, visibleLines);
45
- visible.push(useColor ? (0, color_js_1.colorize)(summary, "gray") : summary);
46
- return visible.join("\n");
47
- }
48
- /**
49
- * Renders the args into a single message string exactly as console would —
50
- * objects/errors via util.inspect, `%s`/`%d` format specifiers, and colors when
51
- * on a terminal — then hang-indents every continuation line so multi-line
52
- * output stays left-aligned under the branch, and collapses very long output.
53
- */
54
- function renderMessage(args, useColor) {
55
- const text = (0, node_util_1.formatWithOptions)({ colors: useColor }, ...args);
56
- return collapse(text.replace(/\n/g, `\n${(0, color_js_1.colorize)(constants_js_1.MESSAGE_INDENT, "gray")}`), useColor);
57
- }
58
- /**
59
- * Renders a caller as a `(file:line)` location — a clickable OSC-8 link on a
60
- * supporting terminal, plain text otherwise. Pure (no stack access).
61
- */
62
- function formatLocation(caller, interactive) {
63
- if (caller.file !== null && caller.line !== null && interactive)
64
- return (0, link_js_1.hyperlink)(caller.label, (0, link_js_1.fileUrl)(caller.file));
65
- return caller.label;
66
- }
67
- /**
68
- * Renders and writes one log entry. The `caller` is resolved by the *caller* of
69
- * this function (the log closure or `tap`) and passed in, so this helper never
70
- * touches the stack — keeping {@link getCaller}'s frame depth correct no matter
71
- * which user-facing function delegates here.
72
- */
73
- function writeLog(opts) {
74
- const { channel, tag, args, caller } = opts;
75
- const streamName = channel === "log" ? "stdout" : "stderr";
76
- // One terminal check drives both color and clickable links — `isTTY` is the
77
- // single source of truth (DRY). Off when output is piped/redirected.
78
- const useColor = (0, isTTY_js_1.isTTY)(streamName);
79
- const paint = (s, c) => useColor ? (0, color_js_1.colorize)(s, c) : s;
80
- const location = formatLocation(caller, useColor);
81
- const tagOut = paint(tag, constants_js_1.TAG_COLOR[channel]);
82
- const timeStamp = paint((0, getTimeStamp_js_1.default)(), "teal");
83
- const locOut = paint(`(${location})`, "dimTeal");
84
- const connector = paint(constants_js_1.BRANCH, "gray");
85
- const connectorBottom = paint(constants_js_1.BRANCH_END, "gray");
86
- const separator = paint(constants_js_1.SEPARATOR, "gray");
87
- // Layout: the tag, the message hanging off a `├──` branch, then the timestamp
88
- // and location on a `└──` branch below. Leading `\n` spaces entries apart.
89
- const message = renderMessage(args, useColor);
90
- const meta = `\n${connectorBottom} ${timeStamp} ${separator} ${locOut}`;
91
- console[channel](`\n${tagOut}`, `\n${connector}`, message, meta);
92
- }
@@ -1,84 +0,0 @@
1
- /*
2
- * writeLog.ts
3
- * Created by Dewan Mobashirul
4
- * Copyright (c) 2026 dewan-meadown
5
- * All rights reserved
6
- */
7
- import { formatWithOptions } from "node:util";
8
- import { TAG_COLOR, BRANCH, BRANCH_END, SEPARATOR, MESSAGE_INDENT, DEFAULT_MAX_LINES, } from "../constants.js";
9
- import getTimeStamp from "../time/getTimeStamp.js";
10
- import { fileUrl, hyperlink } from "../decorations/link.js";
11
- import { colorize } from "../colors/color.js";
12
- import { isTTY } from "../terminal/isTTY.js";
13
- /** Max message lines to show before collapsing the rest; 0 (default) shows all. */
14
- let visibleLines = DEFAULT_MAX_LINES;
15
- /** How many lines a long message shows before collapsing (0 = all). */
16
- export function getVisibleLines() {
17
- return visibleLines;
18
- }
19
- /** Set how many lines a long message shows before collapsing (0 = all). */
20
- export function setVisibleLines(value) {
21
- visibleLines = Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
22
- }
23
- /**
24
- * Collapses a long multi-line message to {@link visibleLines} lines, replacing
25
- * the rest with a dimmed `… N more lines` summary. When `visibleLines` is 0
26
- * (the default) nothing is collapsed — the full message is shown.
27
- */
28
- function collapse(text, useColor) {
29
- if (visibleLines < 1)
30
- return text;
31
- const lines = text.split("\n");
32
- if (lines.length <= visibleLines)
33
- return text;
34
- const hidden = lines.length - visibleLines;
35
- const summary = `${MESSAGE_INDENT}... ${hidden} more line${hidden === 1 ? "" : "s"}`;
36
- const visible = lines.slice(0, visibleLines);
37
- visible.push(useColor ? colorize(summary, "gray") : summary);
38
- return visible.join("\n");
39
- }
40
- /**
41
- * Renders the args into a single message string exactly as console would —
42
- * objects/errors via util.inspect, `%s`/`%d` format specifiers, and colors when
43
- * on a terminal — then hang-indents every continuation line so multi-line
44
- * output stays left-aligned under the branch, and collapses very long output.
45
- */
46
- function renderMessage(args, useColor) {
47
- const text = formatWithOptions({ colors: useColor }, ...args);
48
- return collapse(text.replace(/\n/g, `\n${colorize(MESSAGE_INDENT, "gray")}`), useColor);
49
- }
50
- /**
51
- * Renders a caller as a `(file:line)` location — a clickable OSC-8 link on a
52
- * supporting terminal, plain text otherwise. Pure (no stack access).
53
- */
54
- function formatLocation(caller, interactive) {
55
- if (caller.file !== null && caller.line !== null && interactive)
56
- return hyperlink(caller.label, fileUrl(caller.file));
57
- return caller.label;
58
- }
59
- /**
60
- * Renders and writes one log entry. The `caller` is resolved by the *caller* of
61
- * this function (the log closure or `tap`) and passed in, so this helper never
62
- * touches the stack — keeping {@link getCaller}'s frame depth correct no matter
63
- * which user-facing function delegates here.
64
- */
65
- export function writeLog(opts) {
66
- const { channel, tag, args, caller } = opts;
67
- const streamName = channel === "log" ? "stdout" : "stderr";
68
- // One terminal check drives both color and clickable links — `isTTY` is the
69
- // single source of truth (DRY). Off when output is piped/redirected.
70
- const useColor = isTTY(streamName);
71
- const paint = (s, c) => useColor ? colorize(s, c) : s;
72
- const location = formatLocation(caller, useColor);
73
- const tagOut = paint(tag, TAG_COLOR[channel]);
74
- const timeStamp = paint(getTimeStamp(), "teal");
75
- const locOut = paint(`(${location})`, "dimTeal");
76
- const connector = paint(BRANCH, "gray");
77
- const connectorBottom = paint(BRANCH_END, "gray");
78
- const separator = paint(SEPARATOR, "gray");
79
- // Layout: the tag, the message hanging off a `├──` branch, then the timestamp
80
- // and location on a `└──` branch below. Leading `\n` spaces entries apart.
81
- const message = renderMessage(args, useColor);
82
- const meta = `\n${connectorBottom} ${timeStamp} ${separator} ${locOut}`;
83
- console[channel](`\n${tagOut}`, `\n${connector}`, message, meta);
84
- }