@meadown/logger 1.2.0 → 1.3.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.
package/README.md CHANGED
@@ -5,9 +5,9 @@ remember _which file_ a message came from — and worse, forgetting to pull thos
5
5
  out before shipping. So I made this.
6
6
 
7
7
  It's basically `console.log` with the rough edges sanded off: every message gets a
8
- level tag, a timestamp, and a **clickable link** to the exact file and line it came
9
- from. And it stays quiet in production, so you can leave your logs where they are and
10
- not worry about them.
8
+ **color-coded** level tag, a timestamp, and the file and line it came from — as a
9
+ **clickable link** you can open straight from your terminal. And it stays quiet in
10
+ production, so you can leave your logs where they are and not worry about them.
11
11
 
12
12
  No dependencies. No config. Import it and you're done.
13
13
 
@@ -39,21 +39,27 @@ Auth user logged in
39
39
  Each line is tagged by level — `[INFO]`, `[WARN]`, or `[ERROR]` — followed by the
40
40
  timestamp and the source location, with your arguments on the next line.
41
41
 
42
- ## Click to jump to the source
42
+ ## Color-coded levels
43
43
 
44
- That `(server.ts:42)` at the end of every log isn't just text — in terminals that
45
- support it, it's a **clickable link** that opens the exact line that wrote the log.
46
- No more hunting for where a message came from.
44
+ The level tag is colored so you can spot what matters at a glance `[INFO]` in
45
+ cyan, `[WARN]` in yellow, `[ERROR]` in red. The timestamp and location stay plain so
46
+ the color draws your eye straight to the level.
47
47
 
48
- It works out of the box in editors and terminals like VS Code, iTerm2, WezTerm,
49
- Kitty, and Windows Terminal. Anywhere else, it quietly falls back to plain
50
- `(server.ts:42)` text nothing to configure, never any garbled output.
48
+ Colors appear automatically when you're in a terminal. When output is piped to a
49
+ file or another program, the tag prints as plain `[INFO]` text no stray color
50
+ codes in your log files. Nothing to configure.
51
51
 
52
- If your terminal supports links but isn't auto-detected, you can force them on:
52
+ ## Click to open the source
53
53
 
54
- ```bash
55
- FORCE_HYPERLINK=1 node app.js
56
- ```
54
+ That `(server.ts:42)` at the end of every log isn't just text — when you're in a
55
+ terminal, it's a **clickable link** that opens the file the log came from. No more
56
+ hunting for where a message came from, and the line number is right there in the
57
+ label.
58
+
59
+ There's nothing to configure. Links show up automatically when output goes to a
60
+ terminal, and when it's piped to a file or another program they quietly drop to
61
+ plain `(server.ts:42)` text — so your log files never get cluttered with escape
62
+ codes.
57
63
 
58
64
  ## What about production?
59
65
 
@@ -0,0 +1,23 @@
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 dim: 2;
7
+ };
8
+ /** The named colors/styles {@link colorize} understands. */
9
+ export type Color = keyof typeof CODES;
10
+ /**
11
+ * Wraps `text` in the ANSI escape codes for `color`, resetting afterwards.
12
+ * Pure string work — deciding *whether* to colorize is {@link supportsColor}'s
13
+ * job, kept separate so callers control it per stream.
14
+ */
15
+ export declare function colorize(text: string, color: Color): string;
16
+ /**
17
+ * Whether to emit ANSI colors for the given stream. Driven solely by the stream
18
+ * being an interactive terminal (`isTTY`) — no env vars, no config. When output
19
+ * is piped or redirected, colors are skipped so escape codes never end up in
20
+ * files or logs.
21
+ */
22
+ export declare function supportsColor(streamName: "stdout" | "stderr"): boolean;
23
+ export {};
@@ -0,0 +1,38 @@
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
+ dim: 2,
17
+ };
18
+ const RESET = "\x1b[0m";
19
+ /**
20
+ * Wraps `text` in the ANSI escape codes for `color`, resetting afterwards.
21
+ * Pure string work — deciding *whether* to colorize is {@link supportsColor}'s
22
+ * job, kept separate so callers control it per stream.
23
+ */
24
+ function colorize(text, color) {
25
+ return `\x1b[${CODES[color]}m${text}${RESET}`;
26
+ }
27
+ /**
28
+ * Whether to emit ANSI colors for the given stream. Driven solely by the stream
29
+ * being an interactive terminal (`isTTY`) — no env vars, no config. When output
30
+ * is piped or redirected, colors are skipped so escape codes never end up in
31
+ * files or logs.
32
+ */
33
+ function supportsColor(streamName) {
34
+ if (typeof process === "undefined")
35
+ return false;
36
+ const stream = streamName === "stdout" ? process.stdout : process.stderr;
37
+ return Boolean(stream?.isTTY);
38
+ }
@@ -13,7 +13,14 @@ exports.default = createLog;
13
13
  const getCaller_js_1 = __importDefault(require("./getCaller.js"));
14
14
  const getTimeStamp_js_1 = __importDefault(require("./getTimeStamp.js"));
15
15
  const link_js_1 = require("./link.js");
16
+ const color_js_1 = require("./color.js");
16
17
  const config_js_1 = require("../config.js");
18
+ /** The tag color for each channel: info → cyan, warn → yellow, error → red. */
19
+ const TAG_COLOR = {
20
+ log: "cyan",
21
+ warn: "yellow",
22
+ error: "red",
23
+ };
17
24
  /**
18
25
  * Renders a caller as a `(file:line)` location. When the terminal supports
19
26
  * OSC-8 hyperlinks, the location is a clickable link to the exact source line;
@@ -24,7 +31,7 @@ function formatLocation(caller, streamName) {
24
31
  if (caller.file !== null &&
25
32
  caller.line !== null &&
26
33
  (0, link_js_1.supportsHyperlinks)(streamName))
27
- return (0, link_js_1.hyperlink)(caller.label, (0, link_js_1.fileUrl)(caller.file, caller.line));
34
+ return (0, link_js_1.hyperlink)(caller.label, (0, link_js_1.fileUrl)(caller.file));
28
35
  return caller.label;
29
36
  }
30
37
  /**
@@ -41,6 +48,10 @@ function createLog(channel, tag) {
41
48
  return;
42
49
  const caller = (0, getCaller_js_1.default)();
43
50
  const location = formatLocation(caller, streamName);
44
- console[channel](tag, (0, getTimeStamp_js_1.default)(), `(${location})`, `\n`, ...args, `\n`);
51
+ // Color only the level tag, and only on a real terminal.
52
+ const tagOut = (0, color_js_1.supportsColor)(streamName)
53
+ ? (0, color_js_1.colorize)(tag, TAG_COLOR[channel])
54
+ : tag;
55
+ console[channel](tagOut, (0, getTimeStamp_js_1.default)(), `(${location})`, `\n`, ...args, `\n`);
45
56
  };
46
57
  }
@@ -2,3 +2,4 @@ export { default as createLog, type LogChannel } from "./createLog.js";
2
2
  export { default as getCaller, type Caller } from "./getCaller.js";
3
3
  export { default as getTimeStamp } from "./getTimeStamp.js";
4
4
  export { fileUrl, hyperlink, supportsHyperlinks } from "./link.js";
5
+ export { colorize, supportsColor, type Color } from "./color.js";
@@ -9,7 +9,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
9
9
  return (mod && mod.__esModule) ? mod : { "default": mod };
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.supportsHyperlinks = exports.hyperlink = exports.fileUrl = exports.getTimeStamp = exports.getCaller = exports.createLog = void 0;
12
+ exports.supportsColor = exports.colorize = exports.supportsHyperlinks = exports.hyperlink = exports.fileUrl = exports.getTimeStamp = exports.getCaller = exports.createLog = void 0;
13
13
  var createLog_js_1 = require("./createLog.js");
14
14
  Object.defineProperty(exports, "createLog", { enumerable: true, get: function () { return __importDefault(createLog_js_1).default; } });
15
15
  var getCaller_js_1 = require("./getCaller.js");
@@ -20,3 +20,6 @@ var link_js_1 = require("./link.js");
20
20
  Object.defineProperty(exports, "fileUrl", { enumerable: true, get: function () { return link_js_1.fileUrl; } });
21
21
  Object.defineProperty(exports, "hyperlink", { enumerable: true, get: function () { return link_js_1.hyperlink; } });
22
22
  Object.defineProperty(exports, "supportsHyperlinks", { enumerable: true, get: function () { return link_js_1.supportsHyperlinks; } });
23
+ var color_js_1 = require("./color.js");
24
+ Object.defineProperty(exports, "colorize", { enumerable: true, get: function () { return color_js_1.colorize; } });
25
+ Object.defineProperty(exports, "supportsColor", { enumerable: true, get: function () { return color_js_1.supportsColor; } });
@@ -1,17 +1,21 @@
1
1
  /**
2
- * Builds a `file://` URL with a trailing `:line` so terminals can jump to the
3
- * exact line. Paths already in `file://` form are used as-is.
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.
4
7
  */
5
- export declare function fileUrl(file: string, line: number): string;
8
+ export declare function fileUrl(file: string): string;
6
9
  /**
7
10
  * Wraps `text` in an OSC-8 terminal hyperlink pointing at `url`. Terminals that
8
- * support OSC-8 render `text` as a clickable link; others simply show `text`.
11
+ * support OSC-8 render `text` as a clickable link; others ignore the escape and
12
+ * simply show `text`.
9
13
  */
10
14
  export declare function hyperlink(text: string, url: string): string;
11
15
  /**
12
- * Conservative detection of OSC-8 hyperlink support for the given stream.
13
- * Honors `FORCE_HYPERLINK` (set to a falsy value to force off), requires a TTY,
14
- * and otherwise allow-lists terminals known to support hyperlinks. Unknown
15
- * terminals get plain text so output is never garbled.
16
+ * Whether to emit OSC-8 hyperlinks for the given stream. Driven solely by the
17
+ * stream being an interactive terminal (`isTTY`) no env vars, no config. When
18
+ * output is piped or redirected, links are skipped so escapes never end up in
19
+ * files or logs. Terminals that don't understand OSC-8 just show the plain text.
16
20
  */
17
21
  export declare function supportsHyperlinks(streamName: "stdout" | "stderr"): boolean;
@@ -11,16 +11,19 @@ exports.hyperlink = hyperlink;
11
11
  exports.supportsHyperlinks = supportsHyperlinks;
12
12
  const node_url_1 = require("node:url");
13
13
  /**
14
- * Builds a `file://` URL with a trailing `:line` so terminals can jump to the
15
- * exact line. Paths already in `file://` form are used as-is.
14
+ * Builds a valid `file://` URL for a path so terminals can open it on click.
15
+ * Paths already in `file://` form are used as-is. We intentionally do NOT append
16
+ * `:line` — that isn't a valid URI and breaks file openers (e.g. GNOME/`gio`,
17
+ * which would look for a file literally named `foo.ts:42`). The line number
18
+ * stays visible in the link's display text instead.
16
19
  */
17
- function fileUrl(file, line) {
18
- const base = file.startsWith("file://") ? file : (0, node_url_1.pathToFileURL)(file).href;
19
- return `${base}:${line}`;
20
+ function fileUrl(file) {
21
+ return file.startsWith("file://") ? file : (0, node_url_1.pathToFileURL)(file).href;
20
22
  }
21
23
  /**
22
24
  * Wraps `text` in an OSC-8 terminal hyperlink pointing at `url`. Terminals that
23
- * support OSC-8 render `text` as a clickable link; others simply show `text`.
25
+ * support OSC-8 render `text` as a clickable link; others ignore the escape and
26
+ * simply show `text`.
24
27
  */
25
28
  function hyperlink(text, url) {
26
29
  const OSC = "\x1b]8;;";
@@ -28,36 +31,14 @@ function hyperlink(text, url) {
28
31
  return `${OSC}${url}${BEL}${text}${OSC}${BEL}`;
29
32
  }
30
33
  /**
31
- * Conservative detection of OSC-8 hyperlink support for the given stream.
32
- * Honors `FORCE_HYPERLINK` (set to a falsy value to force off), requires a TTY,
33
- * and otherwise allow-lists terminals known to support hyperlinks. Unknown
34
- * terminals get plain text so output is never garbled.
34
+ * Whether to emit OSC-8 hyperlinks for the given stream. Driven solely by the
35
+ * stream being an interactive terminal (`isTTY`) no env vars, no config. When
36
+ * output is piped or redirected, links are skipped so escapes never end up in
37
+ * files or logs. Terminals that don't understand OSC-8 just show the plain text.
35
38
  */
36
39
  function supportsHyperlinks(streamName) {
37
40
  if (typeof process === "undefined")
38
41
  return false;
39
- const env = process.env;
40
- const force = env.FORCE_HYPERLINK;
41
- if (force !== undefined && force !== "")
42
- return force !== "0" && force.toLowerCase() !== "false";
43
42
  const stream = streamName === "stdout" ? process.stdout : process.stderr;
44
- if (!stream || !stream.isTTY)
45
- return false;
46
- if (env.TERM === "dumb")
47
- return false;
48
- if (env.CI !== undefined && env.CI !== "")
49
- return false;
50
- const program = env.TERM_PROGRAM;
51
- if (program === "vscode" ||
52
- program === "iTerm.app" ||
53
- program === "Hyper" ||
54
- program === "WezTerm")
55
- return true;
56
- if (env.WT_SESSION)
57
- return true; // Windows Terminal
58
- if (env.KITTY_WINDOW_ID)
59
- return true;
60
- if (env.VTE_VERSION !== undefined && Number(env.VTE_VERSION) >= 5000)
61
- return true; // GNOME, etc.
62
- return false;
43
+ return Boolean(stream?.isTTY);
63
44
  }
@@ -0,0 +1,24 @@
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 dim: 2;
7
+ };
8
+ /** The named colors/styles {@link colorize} understands. */
9
+ export type Color = keyof typeof CODES;
10
+ /**
11
+ * Wraps `text` in the ANSI escape codes for `color`, resetting afterwards.
12
+ * Pure string work — deciding *whether* to colorize is {@link supportsColor}'s
13
+ * job, kept separate so callers control it per stream.
14
+ */
15
+ export declare function colorize(text: string, color: Color): string;
16
+ /**
17
+ * Whether to emit ANSI colors for the given stream. Driven solely by the stream
18
+ * being an interactive terminal (`isTTY`) — no env vars, no config. When output
19
+ * is piped or redirected, colors are skipped so escape codes never end up in
20
+ * files or logs.
21
+ */
22
+ export declare function supportsColor(streamName: "stdout" | "stderr"): boolean;
23
+ export {};
24
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../src/utils/color.ts"],"names":[],"mappings":"AAOA,4DAA4D;AAC5D,QAAA,MAAM,KAAK;;;;;CAKD,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"}
@@ -0,0 +1,35 @@
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
+ dim: 2,
13
+ };
14
+ const RESET = "\x1b[0m";
15
+ /**
16
+ * Wraps `text` in the ANSI escape codes for `color`, resetting afterwards.
17
+ * Pure string work — deciding *whether* to colorize is {@link supportsColor}'s
18
+ * job, kept separate so callers control it per stream.
19
+ */
20
+ export function colorize(text, color) {
21
+ return `\x1b[${CODES[color]}m${text}${RESET}`;
22
+ }
23
+ /**
24
+ * Whether to emit ANSI colors for the given stream. Driven solely by the stream
25
+ * being an interactive terminal (`isTTY`) — no env vars, no config. When output
26
+ * is piped or redirected, colors are skipped so escape codes never end up in
27
+ * files or logs.
28
+ */
29
+ export function supportsColor(streamName) {
30
+ if (typeof process === "undefined")
31
+ return false;
32
+ const stream = streamName === "stdout" ? process.stdout : process.stderr;
33
+ return Boolean(stream?.isTTY);
34
+ }
35
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
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,GAAG,EAAE,CAAC;CACE,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 +1 @@
1
- {"version":3,"file":"createLog.d.ts","sourceRoot":"","sources":["../../src/utils/createLog.ts"],"names":[],"mappings":"AAYA,iDAAiD;AACjD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,CAAA;AAqBjD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,GACV,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAQ9B"}
1
+ {"version":3,"file":"createLog.d.ts","sourceRoot":"","sources":["../../src/utils/createLog.ts"],"names":[],"mappings":"AAaA,iDAAiD;AACjD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,CAAA;AA4BjD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,GACV,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAc9B"}
@@ -7,7 +7,14 @@
7
7
  import getCaller from "./getCaller.js";
8
8
  import getTimeStamp from "./getTimeStamp.js";
9
9
  import { fileUrl, hyperlink, supportsHyperlinks } from "./link.js";
10
+ import { colorize, supportsColor } from "./color.js";
10
11
  import { isLogAllowed } from "../config.js";
12
+ /** The tag color for each channel: info → cyan, warn → yellow, error → red. */
13
+ const TAG_COLOR = {
14
+ log: "cyan",
15
+ warn: "yellow",
16
+ error: "red",
17
+ };
11
18
  /**
12
19
  * Renders a caller as a `(file:line)` location. When the terminal supports
13
20
  * OSC-8 hyperlinks, the location is a clickable link to the exact source line;
@@ -18,7 +25,7 @@ function formatLocation(caller, streamName) {
18
25
  if (caller.file !== null &&
19
26
  caller.line !== null &&
20
27
  supportsHyperlinks(streamName))
21
- return hyperlink(caller.label, fileUrl(caller.file, caller.line));
28
+ return hyperlink(caller.label, fileUrl(caller.file));
22
29
  return caller.label;
23
30
  }
24
31
  /**
@@ -35,7 +42,11 @@ export default function createLog(channel, tag) {
35
42
  return;
36
43
  const caller = getCaller();
37
44
  const location = formatLocation(caller, streamName);
38
- console[channel](tag, getTimeStamp(), `(${location})`, `\n`, ...args, `\n`);
45
+ // Color only the level tag, and only on a real terminal.
46
+ const tagOut = supportsColor(streamName)
47
+ ? colorize(tag, TAG_COLOR[channel])
48
+ : tag;
49
+ console[channel](tagOut, getTimeStamp(), `(${location})`, `\n`, ...args, `\n`);
39
50
  };
40
51
  }
41
52
  //# sourceMappingURL=createLog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createLog.js","sourceRoot":"","sources":["../../src/utils/createLog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,SAA0B,MAAM,gBAAgB,CAAA;AACvD,OAAO,YAAY,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAK3C;;;;;GAKG;AACH,SAAS,cAAc,CACrB,MAAc,EACd,UAA+B;IAE/B,IACE,MAAM,CAAC,IAAI,KAAK,IAAI;QACpB,MAAM,CAAC,IAAI,KAAK,IAAI;QACpB,kBAAkB,CAAC,UAAU,CAAC;QAE9B,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;IACnE,OAAO,MAAM,CAAC,KAAK,CAAA;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,OAAmB,EACnB,GAAW;IAEX,MAAM,UAAU,GAAG,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;IAC1D,OAAO,CAAC,GAAG,IAAe,EAAQ,EAAE;QAClC,IAAI,CAAC,YAAY,EAAE;YAAE,OAAM;QAC3B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;QAC1B,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;QACnD,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,IAAI,QAAQ,GAAG,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,CAAC,CAAA;IAC7E,CAAC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"createLog.js","sourceRoot":"","sources":["../../src/utils/createLog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,SAA0B,MAAM,gBAAgB,CAAA;AACvD,OAAO,YAAY,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAClE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAc,MAAM,YAAY,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAK3C,+EAA+E;AAC/E,MAAM,SAAS,GAA8B;IAC3C,GAAG,EAAE,MAAM;IACX,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,KAAK;CACb,CAAA;AAED;;;;;GAKG;AACH,SAAS,cAAc,CACrB,MAAc,EACd,UAA+B;IAE/B,IACE,MAAM,CAAC,IAAI,KAAK,IAAI;QACpB,MAAM,CAAC,IAAI,KAAK,IAAI;QACpB,kBAAkB,CAAC,UAAU,CAAC;QAE9B,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;IACtD,OAAO,MAAM,CAAC,KAAK,CAAA;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,OAAmB,EACnB,GAAW;IAEX,MAAM,UAAU,GAAG,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;IAC1D,OAAO,CAAC,GAAG,IAAe,EAAQ,EAAE;QAClC,IAAI,CAAC,YAAY,EAAE;YAAE,OAAM;QAC3B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;QAC1B,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;QAEnD,yDAAyD;QACzD,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC;YACtC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;YACnC,CAAC,CAAC,GAAG,CAAA;QAEP,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,IAAI,QAAQ,GAAG,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,CAAC,CAAA;IAChF,CAAC,CAAA;AACH,CAAC"}
@@ -2,4 +2,5 @@ export { default as createLog, type LogChannel } from "./createLog.js";
2
2
  export { default as getCaller, type Caller } from "./getCaller.js";
3
3
  export { default as getTimeStamp } from "./getTimeStamp.js";
4
4
  export { fileUrl, hyperlink, supportsHyperlinks } from "./link.js";
5
+ export { colorize, supportsColor, type Color } from "./color.js";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACtE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACtE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAClE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAA"}
@@ -8,4 +8,5 @@ export { default as createLog } from "./createLog.js";
8
8
  export { default as getCaller } from "./getCaller.js";
9
9
  export { default as getTimeStamp } from "./getTimeStamp.js";
10
10
  export { fileUrl, hyperlink, supportsHyperlinks } from "./link.js";
11
+ export { colorize, supportsColor } from "./color.js";
11
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAmB,MAAM,gBAAgB,CAAA;AACtE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAe,MAAM,gBAAgB,CAAA;AAClE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAmB,MAAM,gBAAgB,CAAA;AACtE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAe,MAAM,gBAAgB,CAAA;AAClE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAClE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAc,MAAM,YAAY,CAAA"}
@@ -1,18 +1,22 @@
1
1
  /**
2
- * Builds a `file://` URL with a trailing `:line` so terminals can jump to the
3
- * exact line. Paths already in `file://` form are used as-is.
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.
4
7
  */
5
- export declare function fileUrl(file: string, line: number): string;
8
+ export declare function fileUrl(file: string): string;
6
9
  /**
7
10
  * Wraps `text` in an OSC-8 terminal hyperlink pointing at `url`. Terminals that
8
- * support OSC-8 render `text` as a clickable link; others simply show `text`.
11
+ * support OSC-8 render `text` as a clickable link; others ignore the escape and
12
+ * simply show `text`.
9
13
  */
10
14
  export declare function hyperlink(text: string, url: string): string;
11
15
  /**
12
- * Conservative detection of OSC-8 hyperlink support for the given stream.
13
- * Honors `FORCE_HYPERLINK` (set to a falsy value to force off), requires a TTY,
14
- * and otherwise allow-lists terminals known to support hyperlinks. Unknown
15
- * terminals get plain text so output is never garbled.
16
+ * Whether to emit OSC-8 hyperlinks for the given stream. Driven solely by the
17
+ * stream being an interactive terminal (`isTTY`) no env vars, no config. When
18
+ * output is piped or redirected, links are skipped so escapes never end up in
19
+ * files or logs. Terminals that don't understand OSC-8 just show the plain text.
16
20
  */
17
21
  export declare function supportsHyperlinks(streamName: "stdout" | "stderr"): boolean;
18
22
  //# sourceMappingURL=link.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/utils/link.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAI3D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CA2B3E"}
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/utils/link.ts"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAI3D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAI3E"}
@@ -6,16 +6,19 @@
6
6
  */
7
7
  import { pathToFileURL } from "node:url";
8
8
  /**
9
- * Builds a `file://` URL with a trailing `:line` so terminals can jump to the
10
- * exact line. Paths already in `file://` form are used as-is.
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.
11
14
  */
12
- export function fileUrl(file, line) {
13
- const base = file.startsWith("file://") ? file : pathToFileURL(file).href;
14
- return `${base}:${line}`;
15
+ export function fileUrl(file) {
16
+ return file.startsWith("file://") ? file : pathToFileURL(file).href;
15
17
  }
16
18
  /**
17
19
  * Wraps `text` in an OSC-8 terminal hyperlink pointing at `url`. Terminals that
18
- * support OSC-8 render `text` as a clickable link; others simply show `text`.
20
+ * support OSC-8 render `text` as a clickable link; others ignore the escape and
21
+ * simply show `text`.
19
22
  */
20
23
  export function hyperlink(text, url) {
21
24
  const OSC = "\x1b]8;;";
@@ -23,37 +26,15 @@ export function hyperlink(text, url) {
23
26
  return `${OSC}${url}${BEL}${text}${OSC}${BEL}`;
24
27
  }
25
28
  /**
26
- * Conservative detection of OSC-8 hyperlink support for the given stream.
27
- * Honors `FORCE_HYPERLINK` (set to a falsy value to force off), requires a TTY,
28
- * and otherwise allow-lists terminals known to support hyperlinks. Unknown
29
- * terminals get plain text so output is never garbled.
29
+ * Whether to emit OSC-8 hyperlinks for the given stream. Driven solely by the
30
+ * stream being an interactive terminal (`isTTY`) no env vars, no config. When
31
+ * output is piped or redirected, links are skipped so escapes never end up in
32
+ * files or logs. Terminals that don't understand OSC-8 just show the plain text.
30
33
  */
31
34
  export function supportsHyperlinks(streamName) {
32
35
  if (typeof process === "undefined")
33
36
  return false;
34
- const env = process.env;
35
- const force = env.FORCE_HYPERLINK;
36
- if (force !== undefined && force !== "")
37
- return force !== "0" && force.toLowerCase() !== "false";
38
37
  const stream = streamName === "stdout" ? process.stdout : process.stderr;
39
- if (!stream || !stream.isTTY)
40
- return false;
41
- if (env.TERM === "dumb")
42
- return false;
43
- if (env.CI !== undefined && env.CI !== "")
44
- return false;
45
- const program = env.TERM_PROGRAM;
46
- if (program === "vscode" ||
47
- program === "iTerm.app" ||
48
- program === "Hyper" ||
49
- program === "WezTerm")
50
- return true;
51
- if (env.WT_SESSION)
52
- return true; // Windows Terminal
53
- if (env.KITTY_WINDOW_ID)
54
- return true;
55
- if (env.VTE_VERSION !== undefined && Number(env.VTE_VERSION) >= 5000)
56
- return true; // GNOME, etc.
57
- return false;
38
+ return Boolean(stream?.isTTY);
58
39
  }
59
40
  //# sourceMappingURL=link.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/utils/link.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,IAAY;IAChD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAA;IACzE,OAAO,GAAG,IAAI,IAAI,IAAI,EAAE,CAAA;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,GAAW;IACjD,MAAM,GAAG,GAAG,UAAU,CAAA;IACtB,MAAM,GAAG,GAAG,MAAM,CAAA;IAClB,OAAO,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG,EAAE,CAAA;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAA+B;IAChE,IAAI,OAAO,OAAO,KAAK,WAAW;QAAE,OAAO,KAAK,CAAA;IAChD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAA;IAEvB,MAAM,KAAK,GAAG,GAAG,CAAC,eAAe,CAAA;IACjC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;QACrC,OAAO,KAAK,KAAK,GAAG,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,OAAO,CAAA;IAEzD,MAAM,MAAM,GAAG,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAA;IACxE,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IAC1C,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,KAAK,CAAA;IACrC,IAAI,GAAG,CAAC,EAAE,KAAK,SAAS,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE;QAAE,OAAO,KAAK,CAAA;IAEvD,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAA;IAChC,IACE,OAAO,KAAK,QAAQ;QACpB,OAAO,KAAK,WAAW;QACvB,OAAO,KAAK,OAAO;QACnB,OAAO,KAAK,SAAS;QAErB,OAAO,IAAI,CAAA;IACb,IAAI,GAAG,CAAC,UAAU;QAAE,OAAO,IAAI,CAAA,CAAC,mBAAmB;IACnD,IAAI,GAAG,CAAC,eAAe;QAAE,OAAO,IAAI,CAAA;IACpC,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,IAAI;QAClE,OAAO,IAAI,CAAA,CAAC,cAAc;IAE5B,OAAO,KAAK,CAAA;AACd,CAAC"}
1
+ {"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/utils/link.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAA;AACrE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,GAAW;IACjD,MAAM,GAAG,GAAG,UAAU,CAAA;IACtB,MAAM,GAAG,GAAG,MAAM,CAAA;IAClB,OAAO,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG,EAAE,CAAA;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAA+B;IAChE,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meadown/logger",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
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.",
5
5
  "keywords": [
6
6
  "logger",