pino-nice 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tim Zadorozhny
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ <p align="center">
2
+ <img src="pino-nice.png" alt="pino-nice" width="100%" />
3
+ </p>
4
+
5
+ # pino-nice
6
+
7
+ โœจ Pretty-print [pino](https://github.com/pinojs/pino) (and pino-like) JSON logs from stdin into a clean, human-friendly streaming view.
8
+
9
+ ```bash
10
+ node app.js | npx pino-nice
11
+ ```
12
+
13
+ ## ๐Ÿค” Why pino-nice?
14
+
15
+ Structured JSON logs are great for machines and terrible for human eyes. `pino-pretty` helps, but pino-nice takes a different, opinionated approach focused on readability while tailing logs:
16
+
17
+ - ๐Ÿงพ **Sectioned details + headline.** Each record renders its structured fields as an indented, YAML-like block, connected with box-drawing lines (`โ”‚` / `โ””โ”€`) down to a single headline `LEVEL timestamp message`.
18
+ - ๐ŸŽจ **Color-coded levels.** `trace`/`debug`/`info`/`warn`/`error`/`fatal` each get a distinct color, applied to the connectors too, so severity pops at a glance.
19
+ - ๐Ÿงจ **First-class errors.** Stack traces are cleaned up, internal `node_modules`/`node:` frames are dimmed, and nested `cause` chains and `AggregateError`s are rendered with their structure intact.
20
+ - ๐Ÿ”Œ **Works with more than pino.** Recognizes pino defaults plus common aliases like `severity` / `message` / `timestamp`, so logs from other services format nicely too.
21
+ - ๐Ÿ•’ **Readable timestamps.** Local time by default (or `--utc`), with a missing timestamp filled in from the current time.
22
+ - ๐Ÿ”— **URL-safe wrapping.** Lines flow to your terminal's width with no manual mid-token breaks, so long URLs stay clickable.
23
+ - ๐Ÿš€ **Bun & Node, zero runtime deps.** Ships as a tiny dependency-free CLI.
24
+
25
+ ## ๐Ÿ“ฆ Install
26
+
27
+ Global install:
28
+
29
+ ```bash
30
+ npm install -g pino-nice
31
+ # or
32
+ bun add -g pino-nice
33
+ ```
34
+
35
+ Or run it without installing:
36
+
37
+ ```bash
38
+ npx pino-nice
39
+ # or
40
+ bunx pino-nice
41
+ ```
42
+
43
+ ## ๐Ÿ› ๏ธ Usage
44
+
45
+ Pipe any stream of pino/NDJSON logs into `pino-nice`:
46
+
47
+ ```bash
48
+ # installed globally
49
+ node app.js | pino-nice
50
+
51
+ # without installing
52
+ node app.js | npx pino-nice --warn
53
+ bun run app.ts | bunx pino-nice --utc
54
+ ```
55
+
56
+ Filter by minimum level and choose a timezone:
57
+
58
+ ```bash
59
+ # only warnings and above, timestamps in UTC
60
+ node app.js | pino-nice --warn --utc
61
+ ```
62
+
63
+ ## โš™๏ธ Options
64
+
65
+ ```text
66
+ pino-nice - pretty-print pino (and pino-like) JSON logs from stdin
67
+
68
+ Usage:
69
+ <command emitting json logs> | pino-nice [options]
70
+
71
+ Options:
72
+ --utc Print timestamps in UTC. Default: local time.
73
+ --trace Show trace and above (all levels).
74
+ --debug Show debug and above.
75
+ --info Show info and above.
76
+ --warn Show warnings and above.
77
+ --error Show errors and above.
78
+ --fatal Show fatal only.
79
+ -h, --help Show this help and exit.
80
+
81
+ Notes:
82
+ Level flags filter by minimum severity; if several are given, the last wins.
83
+ ```
84
+
85
+ If you pass several level flags, the **last one wins** (e.g. `--error --info` shows info and above).
86
+
87
+ ## ๐Ÿงฉ Supported log shapes
88
+
89
+ pino-nice resolves fields from common conventions:
90
+
91
+ - **Level:** `level` (numeric `10`โ€“`60`) or `severity` (`"INFO"`, `"DEBUG"`, ...)
92
+ - **Message:** `msg` or `message`
93
+ - **Timestamp:** `time` or `timestamp` (epoch ms or ISO string); if absent, the current time is used
94
+
95
+ All six pino levels are supported: `trace` (10), `debug` (20), `info` (30), `warn` (40), `error` (50), `fatal` (60).
96
+
97
+ ## ๐Ÿ“„ License
98
+
99
+ [MIT](LICENSE) ยฉ Tim Zadorozhny
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ interface PinoNiceOptions {
3
+ /** Print timestamps in UTC. Defaults to local time. */
4
+ utc?: boolean;
5
+ /** Minimum numeric level to display; lower levels are hidden. Defaults to 0. */
6
+ minLevel?: number;
7
+ }
8
+ declare class PinoNice {
9
+ private readonly utc;
10
+ private readonly minLevel;
11
+ constructor(options?: PinoNiceOptions);
12
+ private handleLine;
13
+ pipe(): Promise<void>;
14
+ }
15
+
16
+ export { type PinoNiceOptions, PinoNice as default };
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env node
2
+
3
+ // pino-nice.ts
4
+ import process from "process";
5
+ import { pathToFileURL } from "url";
6
+ var colorsEnabled = (() => {
7
+ if (process.env.NO_COLOR) return false;
8
+ const force = process.env.FORCE_COLOR;
9
+ if (force !== void 0 && force !== "" && force !== "0") return true;
10
+ if (typeof Bun !== "undefined") return Bun.enableANSIColors;
11
+ return Boolean(process.stdout.isTTY);
12
+ })();
13
+ function sgr(open, close) {
14
+ return (s) => colorsEnabled ? `\x1B[${open}m${s}\x1B[${close}m` : s;
15
+ }
16
+ var c = {
17
+ bold: sgr(1, 22),
18
+ dim: sgr(2, 22),
19
+ red: sgr(31, 39),
20
+ green: sgr(32, 39),
21
+ yellow: sgr(33, 39),
22
+ blue: sgr(34, 39),
23
+ magenta: sgr(35, 39),
24
+ cyan: sgr(36, 39),
25
+ gray: sgr(90, 39)
26
+ };
27
+ var BOX = {
28
+ vertical: "\u2502",
29
+ // โ”‚
30
+ corner: "\u2514",
31
+ // โ””
32
+ horizontal: "\u2500"
33
+ // โ”€
34
+ };
35
+ var LEVELS = {
36
+ 10: { label: "TRACE", color: c.gray },
37
+ 20: { label: "DEBUG", color: c.cyan },
38
+ 30: { label: "INFO", color: c.green },
39
+ 40: { label: "WARN", color: c.yellow },
40
+ 50: { label: "ERROR", color: c.red },
41
+ 60: { label: "FATAL", color: (s) => c.bold(c.red(s)) }
42
+ };
43
+ var LEVEL_WIDTH = 5;
44
+ var LABEL_TO_LEVEL = Object.fromEntries(
45
+ Object.entries(LEVELS).map(([num, info]) => [info.label, Number(num)])
46
+ );
47
+ function levelToNumber(level) {
48
+ if (typeof level === "number") return level;
49
+ if (typeof level === "string") {
50
+ const upper = level.toUpperCase();
51
+ const known = LABEL_TO_LEVEL[upper];
52
+ if (known !== void 0) return known;
53
+ const n = Number(level);
54
+ if (!Number.isNaN(n)) return n;
55
+ }
56
+ return Number.POSITIVE_INFINITY;
57
+ }
58
+ var SPECIAL_FIELDS = /* @__PURE__ */ new Set([
59
+ "time",
60
+ "timestamp",
61
+ "level",
62
+ "severity",
63
+ "msg",
64
+ "message",
65
+ "pid",
66
+ "hostname",
67
+ "v"
68
+ ]);
69
+ var TIME_KEYS = ["time", "timestamp"];
70
+ var LEVEL_KEYS = ["level", "severity"];
71
+ var MESSAGE_KEYS = ["msg", "message"];
72
+ function firstDefined(entry, keys) {
73
+ for (const key of keys) {
74
+ if (entry[key] !== void 0 && entry[key] !== null) return entry[key];
75
+ }
76
+ return void 0;
77
+ }
78
+ var IMPORTANT_KEYS = /* @__PURE__ */ new Set([
79
+ "reqId",
80
+ "requestId",
81
+ "traceId",
82
+ "spanId",
83
+ "userId"
84
+ ]);
85
+ function isPlainObject(value) {
86
+ return typeof value === "object" && value !== null && !Array.isArray(value);
87
+ }
88
+ function isErrorLike(value) {
89
+ return isPlainObject(value) && typeof value.message === "string" && typeof value.stack === "string";
90
+ }
91
+ function highlightKey(key) {
92
+ return IMPORTANT_KEYS.has(key) ? c.bold(c.cyan(key)) : c.cyan(key);
93
+ }
94
+ function formatScalar(value) {
95
+ if (value === null) return c.dim("null");
96
+ if (value === void 0) return c.dim("undefined");
97
+ if (typeof value === "boolean" || typeof value === "number") {
98
+ return c.yellow(String(value));
99
+ }
100
+ return String(value);
101
+ }
102
+ function errorHeader(err) {
103
+ const type = err.type ?? "Error";
104
+ const message = err.message ?? "";
105
+ return c.bold(c.red(`${type}: ${message}`));
106
+ }
107
+ function renderStackFrames(stack, indent) {
108
+ const pad = " ".repeat(indent);
109
+ const out = [];
110
+ const lines = stack.split("\n");
111
+ for (let i = 0; i < lines.length; i++) {
112
+ const line = (lines[i] ?? "").trim();
113
+ if (line === "") continue;
114
+ if (i === 0 && !line.startsWith("at ")) continue;
115
+ if (line.startsWith("at ")) {
116
+ const internal = line.includes("node_modules") || line.includes("node:");
117
+ if (internal) {
118
+ out.push(pad + c.dim(line));
119
+ continue;
120
+ }
121
+ const m = line.match(/^at (?:(.+?) \()?(.+?):(\d+):(\d+)\)?$/);
122
+ if (m && m[2] && m[3] && m[4]) {
123
+ const fn = m[1];
124
+ const loc = `${c.cyan(m[2])}:${c.yellow(m[3])}:${c.yellow(m[4])}`;
125
+ out.push(
126
+ pad + (fn ? `${c.dim("at")} ${fn} (${loc})` : `${c.dim("at")} ${loc}`)
127
+ );
128
+ } else {
129
+ out.push(pad + c.dim(line));
130
+ }
131
+ continue;
132
+ }
133
+ if (line.startsWith("caused by:")) {
134
+ const rest = line.slice("caused by:".length).trim();
135
+ out.push(pad + c.dim("caused by:") + " " + c.bold(c.red(rest)));
136
+ } else {
137
+ out.push(pad + c.dim(line));
138
+ }
139
+ }
140
+ return out;
141
+ }
142
+ function renderErrorBody(err, indent, depth) {
143
+ if (depth > 6) return [];
144
+ const pad = " ".repeat(indent);
145
+ const out = [];
146
+ if (typeof err.stack === "string") {
147
+ out.push(...renderStackFrames(err.stack, indent));
148
+ }
149
+ const aggregate = err.aggregateErrors ?? err.errors;
150
+ if (Array.isArray(aggregate)) {
151
+ for (const sub of aggregate) {
152
+ if (isErrorLike(sub)) {
153
+ out.push(`${pad}${c.dim("-")} ${errorHeader(sub)}`);
154
+ out.push(...renderErrorBody(sub, indent + 2, depth + 1));
155
+ }
156
+ }
157
+ }
158
+ if (isErrorLike(err.cause)) {
159
+ out.push(`${pad}${c.dim("caused by:")} ${errorHeader(err.cause)}`);
160
+ out.push(...renderErrorBody(err.cause, indent + 2, depth + 1));
161
+ }
162
+ return out;
163
+ }
164
+ function renderData(value, indent, depth = 0) {
165
+ const pad = " ".repeat(indent);
166
+ if (Array.isArray(value)) {
167
+ if (value.length === 0) return [`${pad}${c.dim("[]")}`];
168
+ const out = [];
169
+ for (const item of value) {
170
+ if (isErrorLike(item)) {
171
+ out.push(`${pad}${c.dim("-")} ${errorHeader(item)}`);
172
+ out.push(...renderErrorBody(item, indent + 2, depth + 1));
173
+ } else if (isPlainObject(item) || Array.isArray(item)) {
174
+ const nested = renderData(item, indent + 2, depth + 1);
175
+ const first = nested[0];
176
+ if (first !== void 0) {
177
+ out.push(`${pad}${c.dim("-")} ${first.slice(indent + 2)}`);
178
+ out.push(...nested.slice(1));
179
+ } else {
180
+ out.push(`${pad}${c.dim("-")}`);
181
+ }
182
+ } else {
183
+ out.push(`${pad}${c.dim("-")} ${formatScalar(item)}`);
184
+ }
185
+ }
186
+ return out;
187
+ }
188
+ if (isPlainObject(value)) {
189
+ const entries = Object.entries(value);
190
+ if (entries.length === 0) return [`${pad}${c.dim("{}")}`];
191
+ const out = [];
192
+ for (const [key, val] of entries) {
193
+ const keyStr = highlightKey(key);
194
+ if (isErrorLike(val)) {
195
+ out.push(`${pad}${keyStr}: ${errorHeader(val)}`);
196
+ out.push(...renderErrorBody(val, indent + 2, depth + 1));
197
+ continue;
198
+ }
199
+ if (Array.isArray(val) || isPlainObject(val)) {
200
+ const empty = Array.isArray(val) ? val.length === 0 ? "[]" : null : Object.keys(val).length === 0 ? "{}" : null;
201
+ if (empty !== null) {
202
+ out.push(`${pad}${keyStr}: ${c.dim(empty)}`);
203
+ } else {
204
+ out.push(`${pad}${keyStr}:`);
205
+ out.push(...renderData(val, indent + 2, depth + 1));
206
+ }
207
+ continue;
208
+ }
209
+ if (typeof val === "string" && val.includes("\n")) {
210
+ out.push(`${pad}${keyStr}: ${c.dim("|")}`);
211
+ for (const line of val.split("\n")) {
212
+ out.push(`${" ".repeat(indent + 2)}${line}`);
213
+ }
214
+ continue;
215
+ }
216
+ out.push(`${pad}${keyStr}: ${formatScalar(val)}`);
217
+ }
218
+ return out;
219
+ }
220
+ return [`${pad}${formatScalar(value)}`];
221
+ }
222
+ function toDate(time) {
223
+ if (typeof time === "number") {
224
+ const d = new Date(time);
225
+ return Number.isNaN(d.getTime()) ? null : d;
226
+ }
227
+ if (typeof time === "string") {
228
+ const d = new Date(/^\d+$/.test(time) ? Number(time) : time);
229
+ return Number.isNaN(d.getTime()) ? null : d;
230
+ }
231
+ return null;
232
+ }
233
+ function formatTimestamp(time, utc) {
234
+ const date = toDate(time);
235
+ if (!date) return typeof time === "string" ? time : String(time);
236
+ const pad = (n, w = 2) => String(n).padStart(w, "0");
237
+ const datePart = utc ? `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}` : `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
238
+ const clock = utc ? `${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}` : `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
239
+ const millis = pad(
240
+ utc ? date.getUTCMilliseconds() : date.getMilliseconds(),
241
+ 3
242
+ );
243
+ return `${c.dim(datePart)} ${c.bold(clock)}${c.dim(`.${millis}`)}${utc ? c.dim("Z") : ""}`;
244
+ }
245
+ function levelInfo(level) {
246
+ if (typeof level === "number" && LEVELS[level]) return LEVELS[level];
247
+ if (typeof level === "string") {
248
+ const upper = level.toUpperCase();
249
+ for (const info of Object.values(LEVELS)) {
250
+ if (info.label === upper) return info;
251
+ }
252
+ return { label: upper, color: (s) => s };
253
+ }
254
+ return { label: String(level), color: (s) => s };
255
+ }
256
+ function summaryLine(time, level, msg, utc) {
257
+ const info = levelInfo(level);
258
+ const paddedLabel = info.label.padEnd(LEVEL_WIDTH);
259
+ const hasTime = time !== void 0 && time !== null;
260
+ const tsPart = hasTime ? `${formatTimestamp(time, utc)} ` : "";
261
+ const connector = `${BOX.corner}${BOX.horizontal} `;
262
+ const message = typeof msg === "string" ? msg : String(msg);
263
+ return `${info.color(connector)}${info.color(paddedLabel)} ${tsPart}${message}`.trimEnd();
264
+ }
265
+ var PinoNice = class {
266
+ utc;
267
+ minLevel;
268
+ constructor(options = {}) {
269
+ this.utc = options.utc ?? false;
270
+ this.minLevel = options.minLevel ?? 0;
271
+ }
272
+ handleLine(rawLine) {
273
+ const line = rawLine.replace(/\r$/, "");
274
+ if (line.trim() === "") return;
275
+ let entry;
276
+ try {
277
+ entry = JSON.parse(line);
278
+ } catch {
279
+ return;
280
+ }
281
+ if (!isPlainObject(entry)) return;
282
+ const level = firstDefined(entry, LEVEL_KEYS);
283
+ if (level === void 0) return;
284
+ const message = firstDefined(entry, MESSAGE_KEYS) ?? "";
285
+ const time = firstDefined(entry, TIME_KEYS) ?? Date.now();
286
+ if (levelToNumber(level) < this.minLevel) return;
287
+ const rest = {};
288
+ for (const [key, value] of Object.entries(entry)) {
289
+ if (!SPECIAL_FIELDS.has(key)) rest[key] = value;
290
+ }
291
+ const info = levelInfo(level);
292
+ const out = [];
293
+ const dataLines = Object.keys(rest).length > 0 ? renderData(rest, 1) : [];
294
+ for (const dataLine of dataLines) {
295
+ out.push(info.color(BOX.vertical) + " " + dataLine);
296
+ }
297
+ out.push(summaryLine(time, level, message, this.utc));
298
+ try {
299
+ process.stdout.write(out.join("\n") + "\n");
300
+ } catch (error) {
301
+ if (error?.code === "EPIPE") process.exit(0);
302
+ throw error;
303
+ }
304
+ }
305
+ async pipe() {
306
+ const decoder = new TextDecoder();
307
+ let buffer = "";
308
+ for await (const chunk of process.stdin) {
309
+ buffer += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
310
+ let nl;
311
+ while ((nl = buffer.indexOf("\n")) !== -1) {
312
+ const line = buffer.slice(0, nl);
313
+ buffer = buffer.slice(nl + 1);
314
+ this.handleLine(line);
315
+ }
316
+ }
317
+ buffer += decoder.decode();
318
+ if (buffer.length > 0) this.handleLine(buffer);
319
+ }
320
+ };
321
+ var LEVEL_FLAGS = {
322
+ "--trace": 10,
323
+ "--debug": 20,
324
+ "--info": 30,
325
+ "--warn": 40,
326
+ "--error": 50,
327
+ "--fatal": 60
328
+ };
329
+ var HELP = `pino-nice - pretty-print pino (and pino-like) JSON logs from stdin
330
+
331
+ Usage:
332
+ <command emitting json logs> | pino-nice [options]
333
+
334
+ Options:
335
+ --utc Print timestamps in UTC. Default: local time.
336
+ --trace Show trace and above (all levels).
337
+ --debug Show debug and above.
338
+ --info Show info and above.
339
+ --warn Show warnings and above.
340
+ --error Show errors and above.
341
+ --fatal Show fatal only.
342
+ -h, --help Show this help and exit.
343
+
344
+ Notes:
345
+ Level flags filter by minimum severity; if several are given, the last wins.
346
+ `;
347
+ function parseArgs(argv) {
348
+ const options = {};
349
+ for (const arg of argv) {
350
+ if (arg === "--help" || arg === "-h") {
351
+ process.stdout.write(HELP);
352
+ process.exit(0);
353
+ } else if (arg === "--utc") {
354
+ options.utc = true;
355
+ } else if (LEVEL_FLAGS[arg] !== void 0) {
356
+ options.minLevel = LEVEL_FLAGS[arg];
357
+ } else {
358
+ process.stderr.write(`pino-nice: unknown option '${arg}' (try --help)
359
+ `);
360
+ process.exit(1);
361
+ }
362
+ }
363
+ return options;
364
+ }
365
+ var isMainModule = import.meta.main ?? (process.argv[1] !== void 0 && import.meta.url === pathToFileURL(process.argv[1]).href);
366
+ if (isMainModule) {
367
+ process.stdout.on("error", (error) => {
368
+ if (error.code === "EPIPE") process.exit(0);
369
+ throw error;
370
+ });
371
+ const options = parseArgs(process.argv.slice(2));
372
+ new PinoNice(options).pipe().catch((error) => {
373
+ if (error?.code === "EPIPE") process.exit(0);
374
+ console.error(error);
375
+ process.exit(1);
376
+ });
377
+ }
378
+ export {
379
+ PinoNice as default
380
+ };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "pino-nice",
3
+ "version": "0.0.1",
4
+ "description": "Pretty-print pino (and pino-like) JSON logs from stdin with a clean, human-friendly streaming view.",
5
+ "type": "module",
6
+ "author": "Tim Zadorozhny <tzador@gmail.com>",
7
+ "license": "MIT",
8
+ "bin": {
9
+ "pino-nice": "./dist/pino-nice.js"
10
+ },
11
+ "main": "./dist/pino-nice.js",
12
+ "module": "./dist/pino-nice.js",
13
+ "types": "./dist/pino-nice.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/pino-nice.d.ts",
17
+ "import": "./dist/pino-nice.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "pino-nice.png"
24
+ ],
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "keywords": [
29
+ "pino",
30
+ "pretty",
31
+ "logger",
32
+ "cli",
33
+ "logs",
34
+ "formatter",
35
+ "ndjson"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/tzador/pino-nice.git"
40
+ },
41
+ "homepage": "https://github.com/tzador/pino-nice#readme",
42
+ "bugs": "https://github.com/tzador/pino-nice/issues",
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "prepublishOnly": "bun run build",
49
+ "release": "bun run build && npm publish",
50
+ "demo": "bun run pino-nice-demo.ts | bun run pino-nice.ts",
51
+ "format": "prettier --write ."
52
+ },
53
+ "devDependencies": {
54
+ "@types/bun": "latest",
55
+ "pino": "^10.3.1",
56
+ "prettier": "^3.8.3",
57
+ "tsup": "^8.5.1",
58
+ "typescript": "^6.0.3"
59
+ }
60
+ }
package/pino-nice.png ADDED
Binary file