@meadown/logger 1.6.0 → 1.8.0

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.
Files changed (77) hide show
  1. package/README.md +88 -70
  2. package/SECURITY.md +30 -4
  3. package/dist/{utils → caller}/getCaller.js +0 -1
  4. package/dist/{utils → cjs/caller}/getCaller.d.ts +0 -1
  5. package/dist/cjs/colors/color.d.ts +20 -0
  6. package/dist/cjs/colors/color.js +29 -0
  7. package/dist/cjs/constants.d.ts +14 -0
  8. package/dist/cjs/constants.js +27 -0
  9. package/dist/cjs/core/createLog.d.ts +8 -0
  10. package/dist/cjs/core/createLog.js +29 -0
  11. package/dist/cjs/core/writeLog.d.ts +18 -0
  12. package/dist/cjs/core/writeLog.js +95 -0
  13. package/dist/cjs/{utils → decorations}/link.d.ts +0 -7
  14. package/dist/cjs/{utils → decorations}/link.js +0 -13
  15. package/dist/cjs/index.d.ts +10 -7
  16. package/dist/cjs/index.js +13 -13
  17. package/dist/cjs/tap/createTap.d.ts +18 -0
  18. package/dist/cjs/tap/createTap.js +51 -0
  19. package/dist/cjs/tap/tapAsync.d.ts +11 -0
  20. package/dist/cjs/tap/tapAsync.js +207 -0
  21. package/dist/cjs/terminal/isTTY.d.ts +7 -0
  22. package/dist/cjs/terminal/isTTY.js +21 -0
  23. package/dist/colors/color.d.ts +20 -0
  24. package/dist/colors/color.js +26 -0
  25. package/dist/config.d.ts +0 -1
  26. package/dist/config.js +0 -1
  27. package/dist/constants.d.ts +14 -0
  28. package/dist/constants.js +24 -0
  29. package/dist/core/createLog.d.ts +8 -0
  30. package/dist/core/createLog.js +23 -0
  31. package/dist/core/writeLog.d.ts +18 -0
  32. package/dist/core/writeLog.js +87 -0
  33. package/dist/{utils → decorations}/link.d.ts +0 -8
  34. package/dist/{utils → decorations}/link.js +0 -13
  35. package/dist/index.d.ts +10 -8
  36. package/dist/index.js +5 -8
  37. package/dist/tap/createTap.d.ts +18 -0
  38. package/dist/tap/createTap.js +45 -0
  39. package/dist/tap/tapAsync.d.ts +11 -0
  40. package/dist/tap/tapAsync.js +203 -0
  41. package/dist/terminal/isTTY.d.ts +7 -0
  42. package/dist/terminal/isTTY.js +18 -0
  43. package/dist/{utils → time}/getTimeStamp.d.ts +0 -1
  44. package/dist/{utils → time}/getTimeStamp.js +0 -1
  45. package/package.json +4 -2
  46. package/dist/cjs/utils/color.d.ts +0 -25
  47. package/dist/cjs/utils/color.js +0 -40
  48. package/dist/cjs/utils/createLog.d.ts +0 -14
  49. package/dist/cjs/utils/createLog.js +0 -116
  50. package/dist/cjs/utils/index.d.ts +0 -5
  51. package/dist/cjs/utils/index.js +0 -27
  52. package/dist/config.d.ts.map +0 -1
  53. package/dist/config.js.map +0 -1
  54. package/dist/index.d.ts.map +0 -1
  55. package/dist/index.js.map +0 -1
  56. package/dist/utils/color.d.ts +0 -26
  57. package/dist/utils/color.d.ts.map +0 -1
  58. package/dist/utils/color.js +0 -37
  59. package/dist/utils/color.js.map +0 -1
  60. package/dist/utils/createLog.d.ts +0 -15
  61. package/dist/utils/createLog.d.ts.map +0 -1
  62. package/dist/utils/createLog.js +0 -109
  63. package/dist/utils/createLog.js.map +0 -1
  64. package/dist/utils/getCaller.d.ts.map +0 -1
  65. package/dist/utils/getCaller.js.map +0 -1
  66. package/dist/utils/getTimeStamp.d.ts.map +0 -1
  67. package/dist/utils/getTimeStamp.js.map +0 -1
  68. package/dist/utils/index.d.ts +0 -6
  69. package/dist/utils/index.d.ts.map +0 -1
  70. package/dist/utils/index.js +0 -12
  71. package/dist/utils/index.js.map +0 -1
  72. package/dist/utils/link.d.ts.map +0 -1
  73. package/dist/utils/link.js.map +0 -1
  74. /package/dist/{cjs/utils → caller}/getCaller.d.ts +0 -0
  75. /package/dist/cjs/{utils → caller}/getCaller.js +0 -0
  76. /package/dist/cjs/{utils → time}/getTimeStamp.d.ts +0 -0
  77. /package/dist/cjs/{utils → time}/getTimeStamp.js +0 -0
@@ -0,0 +1,18 @@
1
+ /** Logs a value and returns it unchanged. Promises route to the timed path. */
2
+ export interface Tap {
3
+ <T>(value: T, label?: string): T;
4
+ }
5
+ /**
6
+ * Builds `tap` — logs a value and returns it **unchanged**, so it drops into any
7
+ * expression (`const u = logger.tap(getUser(), "user")`). The consumer always
8
+ * gets back exactly what they passed; the only effect is a clean log.
9
+ *
10
+ * - A plain value is logged synchronously and returned.
11
+ * - A **promise** is returned as-is (same object, never awaited or wrapped), and
12
+ * its elapsed time — plus the HTTP status if it resolves to a `Response` — is
13
+ * logged in the background (fire-and-forget, non-blocking).
14
+ *
15
+ * The returned closure is what the caller invokes directly, so {@link getCaller}
16
+ * resolves the caller's own frame. Silent in production; the value still flows.
17
+ */
18
+ export default function createTap(): Tap;
@@ -0,0 +1,45 @@
1
+ /*
2
+ * createTap.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 dewan-meadown
5
+ * All rights reserved
6
+ */
7
+ import getCaller from "../caller/getCaller.js";
8
+ import { writeLog } from "../core/writeLog.js";
9
+ import { isLogAllowed } from "../config.js";
10
+ import { isThenable, tapAsync } from "./tapAsync.js";
11
+ /**
12
+ * Builds `tap` — logs a value and returns it **unchanged**, so it drops into any
13
+ * expression (`const u = logger.tap(getUser(), "user")`). The consumer always
14
+ * gets back exactly what they passed; the only effect is a clean log.
15
+ *
16
+ * - A plain value is logged synchronously and returned.
17
+ * - A **promise** is returned as-is (same object, never awaited or wrapped), and
18
+ * its elapsed time — plus the HTTP status if it resolves to a `Response` — is
19
+ * logged in the background (fire-and-forget, non-blocking).
20
+ *
21
+ * The returned closure is what the caller invokes directly, so {@link getCaller}
22
+ * resolves the caller's own frame. Silent in production; the value still flows.
23
+ */
24
+ export default function createTap() {
25
+ const tap = (value, label) => {
26
+ if (!isLogAllowed())
27
+ return value;
28
+ const caller = getCaller();
29
+ if (isThenable(value)) {
30
+ // value is a promise → hand off to the async tap (caller passed in so the
31
+ // location stays on the user's file, not on this helper).
32
+ tapAsync(value, label, caller);
33
+ }
34
+ else {
35
+ writeLog({
36
+ channel: "log",
37
+ tag: "[TAP]",
38
+ args: label === undefined ? [value] : [label, value],
39
+ caller,
40
+ });
41
+ }
42
+ return value;
43
+ };
44
+ return tap;
45
+ }
@@ -0,0 +1,11 @@
1
+ import { type Caller } from "../caller/getCaller.js";
2
+ /** Whether `value` is thenable (a promise we can await + time). */
3
+ export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
4
+ /**
5
+ * The async tap. Fire-and-forget: returns `promise` immediately (unchanged),
6
+ * and logs a rich block once it resolves. For a `Response`, reads the body from
7
+ * a clone so the caller's original stays consumable. A rejection logs an error
8
+ * to stderr. `caller` MUST be resolved by `tap` (the user-facing function) so
9
+ * the logged location points at the user's file.
10
+ */
11
+ export declare function tapAsync(promise: PromiseLike<unknown>, label: string | undefined, caller: Caller): void;
@@ -0,0 +1,203 @@
1
+ /*
2
+ * tapAsync.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 dewan-meadown
5
+ * All rights reserved
6
+ *
7
+ * The async side of `tap`: times a promise and logs a rich response block when
8
+ * it resolves to a `Response` — status color, slow-request highlighting, size,
9
+ * and the actual body — all fire-and-forget so the caller never waits.
10
+ */
11
+ import { performance } from "node:perf_hooks";
12
+ import { formatWithOptions } from "node:util";
13
+ import { writeLog } from "../core/writeLog.js";
14
+ import { colorize } from "../colors/color.js";
15
+ import { isTTY } from "../terminal/isTTY.js";
16
+ /** Whether `value` is thenable (a promise we can await + time). */
17
+ export function isThenable(value) {
18
+ return (typeof value === "object" &&
19
+ value !== null &&
20
+ typeof value.then === "function");
21
+ }
22
+ /** Whether the resolved value is a fetch `Response` we can read. */
23
+ function isResponse(value) {
24
+ const v = value;
25
+ return (typeof v?.status === "number" &&
26
+ typeof v.clone === "function" &&
27
+ typeof v.text === "function");
28
+ }
29
+ /** `65ms` (green) · `1.2s` (yellow) · `5.8s` (red). */
30
+ function formatDuration(ms, useColor) {
31
+ const text = ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${ms}ms`;
32
+ if (!useColor)
33
+ return text;
34
+ if (ms >= 3000)
35
+ return colorize(text, "red");
36
+ if (ms >= 1000)
37
+ return colorize(text, "yellow");
38
+ return colorize(text, "green");
39
+ }
40
+ /** `848 B` / `1.84 KB` / `2.10 MB`. */
41
+ function formatBytes(bytes) {
42
+ if (bytes < 1024)
43
+ return `${bytes} B`;
44
+ if (bytes < 1024 * 1024)
45
+ return `${(bytes / 1024).toFixed(2)} KB`;
46
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
47
+ }
48
+ /**
49
+ * Status badge color:
50
+ * 2xx → green · 3xx → cyan · 4xx → yellow · 5xx → red
51
+ */
52
+ function statusColor(status) {
53
+ if (status >= 500)
54
+ return "red";
55
+ if (status >= 400)
56
+ return "yellow";
57
+ if (status >= 300)
58
+ return "cyan";
59
+ return "green";
60
+ }
61
+ function formatStatus(res, useColor) {
62
+ const text = typeof res.statusText === "string" && res.statusText
63
+ ? `${res.status} ${res.statusText}`
64
+ : `${res.status}`;
65
+ return useColor ? colorize(text, statusColor(res.status)) : text;
66
+ }
67
+ /**
68
+ * Reads a (cloned) response body and returns both the parsed value and the
69
+ * actual byte size. Size is calculated from the body text — not `Content-Length`,
70
+ * which is absent on compressed responses — so size is always shown.
71
+ */
72
+ async function readBody(res) {
73
+ try {
74
+ const text = await res.text();
75
+ const bytes = new TextEncoder().encode(text).length;
76
+ const size = formatBytes(bytes);
77
+ if (text === "")
78
+ return { data: undefined, size };
79
+ try {
80
+ return { data: JSON.parse(text), size };
81
+ }
82
+ catch {
83
+ return { data: text, size };
84
+ }
85
+ }
86
+ catch {
87
+ return { data: undefined, size: "unknown" };
88
+ }
89
+ }
90
+ /**
91
+ * Renders the nested tree block the user asked for:
92
+ *
93
+ * GET /users/1
94
+ * │
95
+ * │ response:
96
+ * │ ├── time: 65ms
97
+ * │ ├── status: 200 OK
98
+ * │ └── size: 848 B
99
+ * │
100
+ * │ body:
101
+ * │ ├── id: 1
102
+ * │ └── name: Leanne Graham
103
+ */
104
+ function buildBlock(label, ms, res, body, useColor) {
105
+ const pipe = useColor ? colorize("│", "gray") : "│";
106
+ const branch = useColor ? colorize("├──", "gray") : "├──";
107
+ const last = useColor ? colorize("└──", "gray") : "└──";
108
+ const indent = `${pipe} `;
109
+ const statusLine = `${indent}${branch} status: ${formatStatus(res, useColor)}`;
110
+ const timeLine = `${indent}${branch} time: ${formatDuration(ms, useColor)}`;
111
+ const sizeLine = `${indent}${last} size: ${body.size}`;
112
+ const responseBlock = [
113
+ `${pipe}`,
114
+ `${indent}response:`,
115
+ timeLine,
116
+ statusLine,
117
+ sizeLine,
118
+ ].join("\n");
119
+ if (body.data === undefined) {
120
+ const head = label === undefined ? "" : `${label}\n`;
121
+ return [`${head}${responseBlock}`];
122
+ }
123
+ // Render the body using util.formatWithOptions so objects/arrays look like
124
+ // console.log output — colors, proper nesting, no JSON.stringify quirkiness.
125
+ const bodyText = formatWithOptions({ colors: useColor }, body.data);
126
+ const bodyLines = bodyText.split("\n");
127
+ const lastIdx = bodyLines.length - 1;
128
+ const bodyBlock = bodyLines
129
+ .map((line, i) => `${indent}${i === lastIdx ? last : branch} ${line}`)
130
+ .join("\n");
131
+ const full = [
132
+ `${pipe}`,
133
+ `${indent}response:`,
134
+ timeLine,
135
+ statusLine,
136
+ sizeLine,
137
+ `${pipe}`,
138
+ `${indent}body:`,
139
+ bodyBlock,
140
+ ].join("\n");
141
+ const head = label === undefined ? "" : `${label}\n`;
142
+ return [`${head}${full}`];
143
+ }
144
+ /**
145
+ * The async tap. Fire-and-forget: returns `promise` immediately (unchanged),
146
+ * and logs a rich block once it resolves. For a `Response`, reads the body from
147
+ * a clone so the caller's original stays consumable. A rejection logs an error
148
+ * to stderr. `caller` MUST be resolved by `tap` (the user-facing function) so
149
+ * the logged location points at the user's file.
150
+ */
151
+ export function tapAsync(promise, label, caller) {
152
+ const useColor = isTTY("stdout");
153
+ const start = performance.now();
154
+ void promise.then((resolved) => {
155
+ const ms = Math.round(performance.now() - start);
156
+ if (isResponse(resolved)) {
157
+ let clone = null;
158
+ try {
159
+ clone = resolved.clone();
160
+ }
161
+ catch {
162
+ clone = null;
163
+ }
164
+ if (clone === null) {
165
+ writeLog({
166
+ channel: "log", tag: "[TAP]",
167
+ args: buildBlock(label, ms, resolved, { data: undefined, size: "unknown" }, useColor),
168
+ caller,
169
+ });
170
+ return;
171
+ }
172
+ const cl = resolved.headers?.get?.("content-length");
173
+ const tooLarge = cl != null && cl !== "" && Number(cl) > 512 * 1024;
174
+ if (tooLarge) {
175
+ writeLog({
176
+ channel: "log", tag: "[TAP]",
177
+ args: buildBlock(label, ms, resolved, { data: "(body too large to display)", size: formatBytes(Number(cl)) }, useColor),
178
+ caller,
179
+ });
180
+ return;
181
+ }
182
+ void readBody(clone).then((body) => {
183
+ writeLog({ channel: "log", tag: "[TAP]", args: buildBlock(label, ms, resolved, body, useColor), caller });
184
+ });
185
+ return;
186
+ }
187
+ // Non-Response promise — plain value with elapsed time.
188
+ const elapsed = formatDuration(ms, useColor);
189
+ writeLog({
190
+ channel: "log", tag: "[TAP]",
191
+ args: label === undefined ? [elapsed, resolved] : [`${label} ${elapsed}`, resolved],
192
+ caller,
193
+ });
194
+ }, (err) => {
195
+ const ms = Math.round(performance.now() - start);
196
+ const elapsed = formatDuration(ms, useColor);
197
+ writeLog({
198
+ channel: "error", tag: "[TAP]",
199
+ args: [label === undefined ? `rejected after ${elapsed}` : `${label} rejected after ${elapsed}`, err],
200
+ caller,
201
+ });
202
+ });
203
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Whether the given stream is an interactive terminal — the single source of
3
+ * truth for "should we emit terminal escapes (colors, clickable links)?". No env
4
+ * vars, no config. When output is piped or redirected this is `false`, so escape
5
+ * codes never end up in files or logs.
6
+ */
7
+ export declare function isTTY(streamName: "stdout" | "stderr"): boolean;
@@ -0,0 +1,18 @@
1
+ /*
2
+ * isTTY.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 dewan-meadown
5
+ * All rights reserved
6
+ */
7
+ /**
8
+ * Whether the given stream is an interactive terminal — the single source of
9
+ * truth for "should we emit terminal escapes (colors, clickable links)?". No env
10
+ * vars, no config. When output is piped or redirected this is `false`, so escape
11
+ * codes never end up in files or logs.
12
+ */
13
+ export function isTTY(streamName) {
14
+ if (typeof process === "undefined")
15
+ return false;
16
+ const stream = streamName === "stdout" ? process.stdout : process.stderr;
17
+ return Boolean(stream?.isTTY);
18
+ }
@@ -4,4 +4,3 @@
4
4
  * compact; date and time are both local so they stay consistent.
5
5
  */
6
6
  export default function getTimeStamp(date?: Date): string;
7
- //# sourceMappingURL=getTimeStamp.d.ts.map
@@ -23,4 +23,3 @@ export default function getTimeStamp(date = new Date()) {
23
23
  const datePart = `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
24
24
  return `${datePart} ${TIME_FORMATTER.format(date)}`;
25
25
  }
26
- //# sourceMappingURL=getTimeStamp.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meadown/logger",
3
- "version": "1.6.0",
4
- "description": "A tiny, zero-dependency logger for Node.js and TypeScript that tags each line, timestamps it, shows the source file and line, and goes quiet in production.",
3
+ "version": "1.8.0",
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",
7
7
  "logging",
@@ -54,6 +54,8 @@
54
54
  "lint": "eslint src",
55
55
  "lint:fix": "eslint src --fix",
56
56
  "demo": "pnpm build && node examples/demo.mjs",
57
+ "demo:calls": "pnpm build && node examples/call-sites.mjs",
58
+ "demo:api": "pnpm build && node examples/api.mjs",
57
59
  "prepublishOnly": "pnpm run lint && pnpm test && pnpm run build",
58
60
  "prepare": "husky",
59
61
  "release": "semantic-release"
@@ -1,25 +0,0 @@
1
- /** ANSI SGR codes for the colors/styles the logger uses. */
2
- declare const CODES: {
3
- readonly red: 31;
4
- readonly yellow: 33;
5
- readonly cyan: 36;
6
- readonly gray: 90;
7
- readonly teal: "38;5;30";
8
- readonly dimTeal: "38;5;23";
9
- };
10
- /** The named colors/styles {@link colorize} understands. */
11
- export type Color = keyof typeof CODES;
12
- /**
13
- * Wraps `text` in the ANSI escape codes for `color`, resetting afterwards.
14
- * Pure string work — deciding *whether* to colorize is {@link supportsColor}'s
15
- * job, kept separate so callers control it per stream.
16
- */
17
- export declare function colorize(text: string, color: Color): string;
18
- /**
19
- * Whether to emit ANSI colors for the given stream. Driven solely by the stream
20
- * being an interactive terminal (`isTTY`) — no env vars, no config. When output
21
- * is piped or redirected, colors are skipped so escape codes never end up in
22
- * files or logs.
23
- */
24
- export declare function supportsColor(streamName: "stdout" | "stderr"): boolean;
25
- export {};
@@ -1,40 +0,0 @@
1
- "use strict";
2
- /*
3
- * color.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.colorize = colorize;
10
- exports.supportsColor = supportsColor;
11
- /** ANSI SGR codes for the colors/styles the logger uses. */
12
- const CODES = {
13
- red: 31,
14
- yellow: 33,
15
- cyan: 36,
16
- gray: 90, // bright black — renders as light gray
17
- teal: "38;5;30", // 256-color teal (#008787)
18
- dimTeal: "38;5;23", // 256-color darker teal (#005f5f)
19
- };
20
- const RESET = "\x1b[0m";
21
- /**
22
- * Wraps `text` in the ANSI escape codes for `color`, resetting afterwards.
23
- * Pure string work — deciding *whether* to colorize is {@link supportsColor}'s
24
- * job, kept separate so callers control it per stream.
25
- */
26
- function colorize(text, color) {
27
- return `\x1b[${CODES[color]}m${text}${RESET}`;
28
- }
29
- /**
30
- * Whether to emit ANSI colors for the given stream. Driven solely by the stream
31
- * being an interactive terminal (`isTTY`) — no env vars, no config. When output
32
- * is piped or redirected, colors are skipped so escape codes never end up in
33
- * files or logs.
34
- */
35
- function supportsColor(streamName) {
36
- if (typeof process === "undefined")
37
- return false;
38
- const stream = streamName === "stdout" ? process.stdout : process.stderr;
39
- return Boolean(stream?.isTTY);
40
- }
@@ -1,14 +0,0 @@
1
- /** The console channels the logger writes to. */
2
- export type LogChannel = "log" | "error" | "warn";
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;
7
- /**
8
- * Builds a log function bound to a console channel and tag. The returned closure
9
- * is what the caller invokes directly, so {@link getCaller} still resolves the
10
- * caller's own frame (no extra stack frame is inserted). `console[channel]` is
11
- * looked up at call time, so reassigning `console.log` (e.g. in tests) is
12
- * respected. Logs only outside production — see {@link isLogAllowed}.
13
- */
14
- export default function createLog(channel: LogChannel, tag: string): (...args: unknown[]) => void;
@@ -1,116 +0,0 @@
1
- "use strict";
2
- /*
3
- * createLog.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.default = createLog;
15
- const node_util_1 = require("node:util");
16
- const getCaller_js_1 = __importDefault(require("./getCaller.js"));
17
- const getTimeStamp_js_1 = __importDefault(require("./getTimeStamp.js"));
18
- const link_js_1 = require("./link.js");
19
- const color_js_1 = require("./color.js");
20
- const config_js_1 = require("../config.js");
21
- /** The tag color for each channel: info → cyan, warn → yellow, error → red. */
22
- const TAG_COLOR = {
23
- log: "cyan",
24
- warn: "yellow",
25
- error: "red",
26
- };
27
- /** Visible width of the `└── ` branch; message lines left-align under it. */
28
- const MESSAGE_INDENT = " ";
29
- /** Max message lines to show before collapsing the rest; 0 (default) shows all. */
30
- let visibleLines = 0;
31
- /** How many lines a long message shows before collapsing (0 = all). */
32
- function getVisibleLines() {
33
- return visibleLines;
34
- }
35
- /** Set how many lines a long message shows before collapsing (0 = all). */
36
- function setVisibleLines(value) {
37
- visibleLines = Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
38
- }
39
- /**
40
- * Collapses a long multi-line message to {@link visibleLines} lines, replacing
41
- * the rest with a dimmed `… N more lines` summary. When `visibleLines` is 0
42
- * (the default) nothing is collapsed — the full message is shown.
43
- */
44
- function collapse(text, useColor) {
45
- if (visibleLines < 1)
46
- return text;
47
- const lines = text.split("\n");
48
- if (lines.length <= visibleLines)
49
- return text;
50
- const hidden = lines.length - visibleLines;
51
- const summary = `${MESSAGE_INDENT}... ${hidden} more line${hidden === 1 ? "" : "s"}`;
52
- const visible = lines.slice(0, visibleLines);
53
- visible.push(useColor ? (0, color_js_1.colorize)(summary, "gray") : summary);
54
- return visible.join("\n");
55
- }
56
- /**
57
- * Renders the args into a single message string exactly as console would —
58
- * objects/errors via util.inspect, `%s`/`%d` format specifiers, and colors when
59
- * on a terminal — then hang-indents every continuation line so multi-line
60
- * output (multi-line strings, pretty-printed objects, error stacks) all stays
61
- * left-aligned under the `└── ` branch, and collapses very long output.
62
- */
63
- function renderMessage(args, useColor) {
64
- const text = (0, node_util_1.formatWithOptions)({ colors: useColor }, ...args);
65
- return collapse(text.replace(/\n/g, `\n${MESSAGE_INDENT}`), useColor);
66
- }
67
- /**
68
- * Renders a caller as a `(file:line)` location. When the terminal supports
69
- * OSC-8 hyperlinks, the location is a clickable link to the source file while
70
- * the line stays visible in the label; otherwise it's plain text. Pure (no
71
- * stack access), so it can be called from a helper without disturbing
72
- * {@link getCaller}'s frame depth.
73
- */
74
- function formatLocation(caller, streamName) {
75
- if (caller.file !== null &&
76
- caller.line !== null &&
77
- (0, link_js_1.supportsHyperlinks)(streamName))
78
- return (0, link_js_1.hyperlink)(caller.label, (0, link_js_1.fileUrl)(caller.file));
79
- return caller.label;
80
- }
81
- /**
82
- * Builds a log function bound to a console channel and tag. The returned closure
83
- * is what the caller invokes directly, so {@link getCaller} still resolves the
84
- * caller's own frame (no extra stack frame is inserted). `console[channel]` is
85
- * looked up at call time, so reassigning `console.log` (e.g. in tests) is
86
- * respected. Logs only outside production — see {@link isLogAllowed}.
87
- */
88
- function createLog(channel, tag) {
89
- const streamName = channel === "log" ? "stdout" : "stderr";
90
- return (...args) => {
91
- if (!(0, config_js_1.isLogAllowed)())
92
- return;
93
- const caller = (0, getCaller_js_1.default)();
94
- const location = formatLocation(caller, streamName);
95
- // Colors (terminal only): tag by level, timestamp teal, location dim teal,
96
- // branch and separator gray.
97
- const useColor = (0, color_js_1.supportsColor)(streamName);
98
- const tagOut = useColor ? (0, color_js_1.colorize)(tag, TAG_COLOR[channel]) : tag;
99
- const timeStamp = useColor
100
- ? (0, color_js_1.colorize)((0, getTimeStamp_js_1.default)(), "teal")
101
- : (0, getTimeStamp_js_1.default)();
102
- const locOut = useColor
103
- ? (0, color_js_1.colorize)(`(${location})`, "dimTeal")
104
- : `(${location})`;
105
- const connector = useColor ? (0, color_js_1.colorize)("├──", "gray") : "└──";
106
- const connectorBottom = useColor ? (0, color_js_1.colorize)("└──", "gray") : "└──";
107
- const separator = useColor ? (0, color_js_1.colorize)("-", "gray") : "-";
108
- // Layout: the tag, the message hanging off a `├──` branch, then the
109
- // timestamp and location on a `└──` branch below.
110
- const message = renderMessage(args, useColor);
111
- const meta = `\n${connectorBottom} ${timeStamp} ${separator} ${locOut}`;
112
- // Leading `\n` puts a blank line above each entry — in the same call and on
113
- // the right stream (a separate `console.log` would always hit stdout).
114
- console[channel](`\n${tagOut}`, `\n${connector}`, message, meta);
115
- };
116
- }
@@ -1,5 +0,0 @@
1
- export { default as createLog, getVisibleLines, setVisibleLines, type LogChannel, } from "./createLog.js";
2
- export { default as getCaller, type Caller } from "./getCaller.js";
3
- export { default as getTimeStamp } from "./getTimeStamp.js";
4
- export { fileUrl, hyperlink, supportsHyperlinks } from "./link.js";
5
- export { colorize, supportsColor, type Color } from "./color.js";
@@ -1,27 +0,0 @@
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.supportsColor = exports.colorize = exports.supportsHyperlinks = exports.hyperlink = exports.fileUrl = exports.getTimeStamp = exports.getCaller = exports.setVisibleLines = exports.getVisibleLines = exports.createLog = void 0;
13
- var createLog_js_1 = require("./createLog.js");
14
- Object.defineProperty(exports, "createLog", { enumerable: true, get: function () { return __importDefault(createLog_js_1).default; } });
15
- Object.defineProperty(exports, "getVisibleLines", { enumerable: true, get: function () { return createLog_js_1.getVisibleLines; } });
16
- Object.defineProperty(exports, "setVisibleLines", { enumerable: true, get: function () { return createLog_js_1.setVisibleLines; } });
17
- var getCaller_js_1 = require("./getCaller.js");
18
- Object.defineProperty(exports, "getCaller", { enumerable: true, get: function () { return __importDefault(getCaller_js_1).default; } });
19
- var getTimeStamp_js_1 = require("./getTimeStamp.js");
20
- Object.defineProperty(exports, "getTimeStamp", { enumerable: true, get: function () { return __importDefault(getTimeStamp_js_1).default; } });
21
- var link_js_1 = require("./link.js");
22
- Object.defineProperty(exports, "fileUrl", { enumerable: true, get: function () { return link_js_1.fileUrl; } });
23
- Object.defineProperty(exports, "hyperlink", { enumerable: true, get: function () { return link_js_1.hyperlink; } });
24
- Object.defineProperty(exports, "supportsHyperlinks", { enumerable: true, get: function () { return link_js_1.supportsHyperlinks; } });
25
- var color_js_1 = require("./color.js");
26
- Object.defineProperty(exports, "colorize", { enumerable: true, get: function () { return color_js_1.colorize; } });
27
- Object.defineProperty(exports, "supportsColor", { enumerable: true, get: function () { return color_js_1.supportsColor; } });
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAItC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,UAAU,YAAY;IAC1B,6EAA6E;IAC7E,wEAAwE;IACxE,OAAO,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAA;AAChF,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,gFAAgF;AAChF,MAAM,WAAW,KAAK;IACpB,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC1B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC9B;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;GASG;AACH,QAAA,MAAM,MAAM,EAGN,KAAK,CAAA;AAWX;;;GAGG;AACH,QAAA,MAAM,SAAS,OAAS,CAAA;AAExB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAA;AAC5B,eAAe,MAAM,CAAA"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAc9E;;;;;;;;;GASG;AACH,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE;IACvD,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC;IACpC,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC;CAClC,CAAU,CAAA;AAEX,+EAA+E;AAC/E,uDAAuD;AACvD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE;IACxC,GAAG,EAAE,eAAe;IACpB,GAAG,EAAE,eAAe;IACpB,UAAU,EAAE,IAAI;IAChB,YAAY,EAAE,IAAI;CACnB,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,SAAS,GAAG,MAAM,CAAA;AAExB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAA;AAC5B,eAAe,MAAM,CAAA"}
@@ -1,26 +0,0 @@
1
- /** ANSI SGR codes for the colors/styles the logger uses. */
2
- declare const CODES: {
3
- readonly red: 31;
4
- readonly yellow: 33;
5
- readonly cyan: 36;
6
- readonly gray: 90;
7
- readonly teal: "38;5;30";
8
- readonly dimTeal: "38;5;23";
9
- };
10
- /** The named colors/styles {@link colorize} understands. */
11
- export type Color = keyof typeof CODES;
12
- /**
13
- * Wraps `text` in the ANSI escape codes for `color`, resetting afterwards.
14
- * Pure string work — deciding *whether* to colorize is {@link supportsColor}'s
15
- * job, kept separate so callers control it per stream.
16
- */
17
- export declare function colorize(text: string, color: Color): string;
18
- /**
19
- * Whether to emit ANSI colors for the given stream. Driven solely by the stream
20
- * being an interactive terminal (`isTTY`) — no env vars, no config. When output
21
- * is piped or redirected, colors are skipped so escape codes never end up in
22
- * files or logs.
23
- */
24
- export declare function supportsColor(streamName: "stdout" | "stderr"): boolean;
25
- export {};
26
- //# sourceMappingURL=color.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../src/utils/color.ts"],"names":[],"mappings":"AAOA,4DAA4D;AAC5D,QAAA,MAAM,KAAK;;;;;;;CAOD,CAAA;AAEV,4DAA4D;AAC5D,MAAM,MAAM,KAAK,GAAG,MAAM,OAAO,KAAK,CAAA;AAItC;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAItE"}
@@ -1,37 +0,0 @@
1
- /*
2
- * color.ts
3
- * Created by Dewan Mobashirul
4
- * Copyright (c) 2026 dewan-meadown
5
- * All rights reserved
6
- */
7
- /** ANSI SGR codes for the colors/styles the logger uses. */
8
- const CODES = {
9
- red: 31,
10
- yellow: 33,
11
- cyan: 36,
12
- gray: 90, // bright black — renders as light gray
13
- teal: "38;5;30", // 256-color teal (#008787)
14
- dimTeal: "38;5;23", // 256-color darker teal (#005f5f)
15
- };
16
- const RESET = "\x1b[0m";
17
- /**
18
- * Wraps `text` in the ANSI escape codes for `color`, resetting afterwards.
19
- * Pure string work — deciding *whether* to colorize is {@link supportsColor}'s
20
- * job, kept separate so callers control it per stream.
21
- */
22
- export function colorize(text, color) {
23
- return `\x1b[${CODES[color]}m${text}${RESET}`;
24
- }
25
- /**
26
- * Whether to emit ANSI colors for the given stream. Driven solely by the stream
27
- * being an interactive terminal (`isTTY`) — no env vars, no config. When output
28
- * is piped or redirected, colors are skipped so escape codes never end up in
29
- * files or logs.
30
- */
31
- export function supportsColor(streamName) {
32
- if (typeof process === "undefined")
33
- return false;
34
- const stream = streamName === "stdout" ? process.stdout : process.stderr;
35
- return Boolean(stream?.isTTY);
36
- }
37
- //# sourceMappingURL=color.js.map