@meadown/logger 1.7.0 → 1.8.1
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 +95 -63
- package/SECURITY.md +30 -4
- package/dist/{utils → caller}/getCaller.js +0 -1
- package/dist/{utils → cjs/caller}/getCaller.d.ts +0 -1
- package/dist/cjs/colors/color.d.ts +20 -0
- package/dist/cjs/colors/color.js +29 -0
- package/dist/cjs/constants.d.ts +14 -0
- package/dist/cjs/constants.js +27 -0
- package/dist/cjs/core/createLog.d.ts +8 -0
- package/dist/cjs/core/createLog.js +29 -0
- package/dist/cjs/core/writeLog.d.ts +18 -0
- package/dist/cjs/core/writeLog.js +95 -0
- package/dist/cjs/{utils → decorations}/link.d.ts +0 -7
- package/dist/cjs/{utils → decorations}/link.js +0 -13
- package/dist/cjs/index.d.ts +9 -1
- package/dist/cjs/index.js +12 -6
- package/dist/cjs/tap/createTap.d.ts +18 -0
- package/dist/cjs/tap/createTap.js +51 -0
- package/dist/cjs/tap/tapAsync.d.ts +11 -0
- package/dist/cjs/tap/tapAsync.js +207 -0
- package/dist/cjs/terminal/isTTY.d.ts +7 -0
- package/dist/cjs/terminal/isTTY.js +21 -0
- package/dist/colors/color.d.ts +20 -0
- package/dist/colors/color.js +26 -0
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/constants.d.ts +14 -0
- package/dist/constants.js +24 -0
- package/dist/core/createLog.d.ts +8 -0
- package/dist/core/createLog.js +23 -0
- package/dist/core/writeLog.d.ts +18 -0
- package/dist/core/writeLog.js +87 -0
- package/dist/{utils → decorations}/link.d.ts +0 -8
- package/dist/{utils → decorations}/link.js +0 -13
- package/dist/index.d.ts +9 -2
- package/dist/index.js +4 -2
- package/dist/tap/createTap.d.ts +18 -0
- package/dist/tap/createTap.js +45 -0
- package/dist/tap/tapAsync.d.ts +11 -0
- package/dist/tap/tapAsync.js +203 -0
- package/dist/terminal/isTTY.d.ts +7 -0
- package/dist/terminal/isTTY.js +18 -0
- package/dist/{utils → time}/getTimeStamp.d.ts +0 -1
- package/dist/{utils → time}/getTimeStamp.js +0 -1
- package/package.json +4 -2
- package/dist/cjs/utils/color.d.ts +0 -25
- package/dist/cjs/utils/color.js +0 -40
- package/dist/cjs/utils/createLog.d.ts +0 -14
- package/dist/cjs/utils/createLog.js +0 -116
- package/dist/cjs/utils/index.d.ts +0 -5
- package/dist/cjs/utils/index.js +0 -27
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/utils/color.d.ts +0 -26
- package/dist/utils/color.d.ts.map +0 -1
- package/dist/utils/color.js +0 -37
- package/dist/utils/color.js.map +0 -1
- package/dist/utils/createLog.d.ts +0 -15
- package/dist/utils/createLog.d.ts.map +0 -1
- package/dist/utils/createLog.js +0 -109
- package/dist/utils/createLog.js.map +0 -1
- package/dist/utils/getCaller.d.ts.map +0 -1
- package/dist/utils/getCaller.js.map +0 -1
- package/dist/utils/getTimeStamp.d.ts.map +0 -1
- package/dist/utils/getTimeStamp.js.map +0 -1
- package/dist/utils/index.d.ts +0 -6
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -12
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/link.d.ts.map +0 -1
- package/dist/utils/link.js.map +0 -1
- /package/dist/{cjs/utils → caller}/getCaller.d.ts +0 -0
- /package/dist/cjs/{utils → caller}/getCaller.js +0 -0
- /package/dist/cjs/{utils → time}/getTimeStamp.d.ts +0 -0
- /package/dist/cjs/{utils → time}/getTimeStamp.js +0 -0
|
@@ -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 >= 2000)
|
|
35
|
+
return colorize(text, "red");
|
|
36
|
+
if (ms >= 500)
|
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meadown/logger",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "1.8.1",
|
|
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 {};
|
package/dist/cjs/utils/color.js
DELETED
|
@@ -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";
|
package/dist/cjs/utils/index.js
DELETED
|
@@ -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; } });
|
package/dist/config.d.ts.map
DELETED
|
@@ -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"}
|
package/dist/config.js.map
DELETED
|
@@ -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"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -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,OAAO,EAAE,MAAM,EAAE,CAAA;AACjB,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,OAAO,EAAE,MAAM,EAAE,CAAA;AACjB,eAAe,MAAM,CAAA"}
|
package/dist/utils/color.d.ts
DELETED
|
@@ -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"}
|
package/dist/utils/color.js
DELETED
|
@@ -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
|
package/dist/utils/color.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"color.js","sourceRoot":"","sources":["../../src/utils/color.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,4DAA4D;AAC5D,MAAM,KAAK,GAAG;IACZ,GAAG,EAAE,EAAE;IACP,MAAM,EAAE,EAAE;IACV,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE,EAAE,uCAAuC;IACjD,IAAI,EAAE,SAAS,EAAE,2BAA2B;IAC5C,OAAO,EAAE,SAAS,EAAE,kCAAkC;CAC9C,CAAA;AAKV,MAAM,KAAK,GAAG,SAAS,CAAA;AAEvB;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,KAAY;IACjD,OAAO,QAAQ,KAAK,CAAC,KAAK,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAA;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,UAA+B;IAC3D,IAAI,OAAO,OAAO,KAAK,WAAW;QAAE,OAAO,KAAK,CAAA;IAChD,MAAM,MAAM,GAAG,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAA;IACxE,OAAO,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AAC/B,CAAC"}
|
|
@@ -1,15 +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;
|
|
15
|
-
//# sourceMappingURL=createLog.d.ts.map
|