colorino 0.12.7 → 0.13.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
@@ -20,12 +20,13 @@ Colorino automatically adapts its palette to your terminal or browser DevTools t
20
20
  - [Examples](#5-3-2)
21
21
  - [Customization](#5-4)
22
22
  - [Supported Environment Variables](#5-5)
23
+ - [Colorize Helper (Manual Overrides)](#5-6)
23
24
  - [Colorino vs. Chalk](#6)
24
25
  - [API Reference](#7)
25
26
  - [1. `colorino` (default instance)](#7-1)
26
27
  - [2. `createColorino(palette?, options?)` (factory)](#7-2)
27
28
  - [Extending Colorino](#8)
28
- - [Use case: automatic file/context info](#8-1)
29
+ - [Use Case: Automatic File/Context Info](#8-1)
29
30
  - [Internals & Dependencies](#9)
30
31
  - [Why This Pattern?](#9-1)
31
32
  - [License](#10)
@@ -36,7 +37,7 @@ Colorino automatically adapts its palette to your terminal or browser DevTools t
36
37
 
37
38
  Plain `console.log` is colorless and inconsistent. Libraries like `chalk` let you style strings, but you have to decorate every message and manually manage color choices.
38
39
 
39
- Colorino is different: it’s a "batteries-included" logging facade with beautiful, theme-aware colors and a familiar API—no learning curve, no configuration. Instantly upgrade your logs everywhere.
40
+ Colorino is different: it’s a "batteries-included" logging facade with beautiful, theme-aware colors and a familiar API. Instantly upgrade your logs everywhere.
40
41
 
41
42
  ## <a id="2"></a>Features
42
43
 
@@ -220,11 +221,11 @@ const darkLogger = createColorino({}, { theme: 'dark' })
220
221
 
221
222
  ### <a id="5-4"></a>Customization
222
223
 
223
- Use your brand colors by passing a partial palette to the `createColorino` factory. Any log levels you don't specify will use the detected **minimal** defaults (`minimal-dark` / `minimal-light`) unless you explicitly select a theme preset.
224
+ Use your brand colors by passing a partial palette to the `createColorino` factory. Any log levels you don't specify will use the detected default colors unless you explicitly select a theme preset.
224
225
 
225
226
  Colorino always targets the highest color fidelity supported by the environment. If your palette uses hex colors but only ANSI‑16 is available, Colorino computes the nearest ANSI color so your branding stays recognizable, even on limited terminals.
226
227
 
227
- If you pass an invalid color value (e.g. malformed hex) in a custom palette, Colorino throws an InputValidationError at creation time so broken palettes fail fast.
228
+ If you pass an invalid color value (e.g. malformed hex) in a custom palette, Colorino throws an `InputValidationError` at creation time so broken palettes fail fast.
228
229
 
229
230
  ```typescript
230
231
  import { createColorino } from 'colorino'
@@ -251,6 +252,21 @@ Colorino auto-detects your environment and color support, but you can override b
251
252
  | `WT_SESSION` | Enables color detection for Windows Terminal | |
252
253
  | `CI` | Many CI platforms default to no color | `CI=1 node app.js` |
253
254
 
255
+ ### <a id="5-6"></a>Colorize Helper (Manual Overrides)
256
+
257
+ Sometimes you want full control over a single piece of text without changing your global palette, e.g. when you use a mostly neutral theme but still want to highlight a keyword.
258
+
259
+ Colorino exposes a small `colorize(text, hex)` helper on every logger instance:
260
+
261
+ ```ts
262
+ import { colorino } from 'colorino'
263
+
264
+ const important = colorino.colorize('IMPORTANT', '#ff5733')
265
+ colorino.info(important, 'Something happened')
266
+ ```
267
+
268
+ When color is disabled (for example via `NO_COLOR=1` or lack of support), `colorize` returns the plain input string, so your logs stay readable.
269
+
254
270
  ## <a id="6"></a>Colorino vs. Chalk
255
271
 
256
272
  | Feature | 🎨 **Colorino** | 🖍️ **Chalk** |
@@ -289,7 +305,7 @@ A factory function to create your own customized logger instances.
289
305
 
290
306
  Colorino is designed for composition: create a base logger via `createColorino()`, then extend it by inheriting from the base and overriding only the methods you need.
291
307
 
292
- ### <a id="8-1"></a>Use case: automatic file/context info
308
+ ### <a id="8-1"></a>Use Case: Automatic File/Context Info
293
309
 
294
310
  This example prefixes every `.info()` and `.error()` call with best‑effort caller context (file/line) derived from a synthetic `Error` stack.
295
311
 
@@ -62,6 +62,8 @@ var ColorLevel = /* @__PURE__ */ ((ColorLevel2) => {
62
62
  function isConsoleMethod(level) {
63
63
  return ["log", "info", "warn", "error", "trace", "debug"].includes(level);
64
64
  }
65
+ const ColorinoBrowserColorized = Symbol("colorino.browserColorized");
66
+ const ColorinoBrowserObject = Symbol("colorino.browserObject");
65
67
 
66
68
  const catppuccinMochaPalette = {
67
69
  log: "#cdd6f4",
@@ -146,6 +148,33 @@ function determineBaseTheme(themeOpt, detectedBrowserTheme) {
146
148
  return baseThemeName;
147
149
  }
148
150
 
151
+ class TypeValidator {
152
+ static isNull(value) {
153
+ return value === null;
154
+ }
155
+ static isObject(value) {
156
+ return typeof value === "object" && value !== null;
157
+ }
158
+ static isString(value) {
159
+ return typeof value === "string";
160
+ }
161
+ static isError(value) {
162
+ return value instanceof Error;
163
+ }
164
+ static isBrowserColorizedArg(value) {
165
+ return typeof value === "object" && value !== null && ColorinoBrowserColorized in value;
166
+ }
167
+ static isBrowserObjectArg(value) {
168
+ return typeof value === "object" && value !== null && ColorinoBrowserObject in value;
169
+ }
170
+ static isAnsiColoredString(value) {
171
+ return TypeValidator.isString(value) && /\x1b\[[0-9;]*m/.test(value);
172
+ }
173
+ static isFormattableObject(value) {
174
+ return TypeValidator.isObject(value) && !TypeValidator.isError(value) && !TypeValidator.isBrowserColorizedArg(value);
175
+ }
176
+ }
177
+
149
178
  class MyColorino {
150
179
  constructor(initialPalette, _userPalette, _validator, _browserColorSupportDetector, _nodeColorSupportDetector, _options = {}) {
151
180
  this._userPalette = _userPalette;
@@ -164,7 +193,7 @@ class MyColorino {
164
193
  const themeOpt = this._options.theme ?? "auto";
165
194
  if (themeOpt === "auto" && this._nodeColorSupportDetector) {
166
195
  this._nodeColorSupportDetector.onTheme((resolvedTheme) => {
167
- this._appllyResolvedTheme(resolvedTheme);
196
+ this._applyResolvedTheme(resolvedTheme);
168
197
  });
169
198
  }
170
199
  }
@@ -172,12 +201,6 @@ class MyColorino {
172
201
  _colorLevel;
173
202
  isBrowser;
174
203
  _palette;
175
- _appllyResolvedTheme(resolvedTheme) {
176
- const themeOpt = this._options.theme ?? "auto";
177
- const baseThemeName = determineBaseTheme(themeOpt, resolvedTheme);
178
- const basePalette = themePalettes[baseThemeName];
179
- this._palette = { ...basePalette, ...this._userPalette };
180
- }
181
204
  log(...args) {
182
205
  this._out("log", args);
183
206
  }
@@ -196,6 +219,32 @@ class MyColorino {
196
219
  debug(...args) {
197
220
  this._out("debug", args);
198
221
  }
222
+ colorize(text, hex) {
223
+ if (this._colorLevel === ColorLevel.NO_COLOR || this._colorLevel === "UnknownEnv") {
224
+ return text;
225
+ }
226
+ if (this.isBrowser) {
227
+ return {
228
+ [ColorinoBrowserColorized]: true,
229
+ text,
230
+ hex
231
+ };
232
+ }
233
+ const ansiPrefix = this._toAnsiPrefix(hex);
234
+ if (!ansiPrefix) {
235
+ return text;
236
+ }
237
+ return `${ansiPrefix}${text}\x1B[0m`;
238
+ }
239
+ _isAnsiColoredString(value) {
240
+ return typeof value === "string" && /\x1b\[[0-9;]*m/.test(value);
241
+ }
242
+ _applyResolvedTheme(resolvedTheme) {
243
+ const themeOpt = this._options.theme ?? "auto";
244
+ const baseThemeName = determineBaseTheme(themeOpt, resolvedTheme);
245
+ const basePalette = themePalettes[baseThemeName];
246
+ this._palette = { ...basePalette, ...this._userPalette };
247
+ }
199
248
  _detectColorSupport() {
200
249
  if (this.isBrowser) {
201
250
  return this._browserColorSupportDetector?.getColorLevel() ?? "UnknownEnv";
@@ -215,7 +264,7 @@ class MyColorino {
215
264
  _formatValue(value, maxDepth = this._options.maxDepth ?? 5) {
216
265
  const seen = /* @__PURE__ */ new WeakSet();
217
266
  const sanitize = (val, currentDepth) => {
218
- if (val === null || typeof val !== "object") return val;
267
+ if (val == null || typeof val !== "object") return val;
219
268
  if (seen.has(val)) return "[Circular]";
220
269
  seen.add(val);
221
270
  if (currentDepth >= maxDepth) return "[Object]";
@@ -233,21 +282,36 @@ class MyColorino {
233
282
  };
234
283
  return JSON.stringify(sanitize(value, 0), null, 2);
235
284
  }
285
+ _normalizeString(value) {
286
+ if (value instanceof String) return value.valueOf();
287
+ return value;
288
+ }
236
289
  _processArgs(args) {
237
290
  const processedArgs = [];
238
291
  let previousWasObject = false;
239
- for (const arg of args) {
240
- const isFormattableObject = arg !== null && typeof arg === "object" && typeof arg !== "string" && !(arg instanceof Error);
241
- const isError = arg instanceof Error;
242
- if (isFormattableObject) {
243
- processedArgs.push(`
292
+ for (const rawArg of args) {
293
+ const arg = this._normalizeString(rawArg);
294
+ if (TypeValidator.isBrowserColorizedArg(arg)) {
295
+ processedArgs.push(arg);
296
+ previousWasObject = false;
297
+ continue;
298
+ }
299
+ if (TypeValidator.isFormattableObject(arg)) {
300
+ if (this.isBrowser) {
301
+ processedArgs.push({
302
+ [ColorinoBrowserObject]: true,
303
+ value: arg
304
+ });
305
+ } else {
306
+ processedArgs.push(`
244
307
  ${this._formatValue(arg)}`);
308
+ }
245
309
  previousWasObject = true;
246
- } else if (isError) {
310
+ } else if (TypeValidator.isError(arg)) {
247
311
  processedArgs.push("\n", this._cleanErrorStack(arg));
248
312
  previousWasObject = true;
249
313
  } else {
250
- if (typeof arg === "string" && previousWasObject) {
314
+ if (TypeValidator.isString(arg) && previousWasObject) {
251
315
  processedArgs.push(`
252
316
  ${arg}`);
253
317
  } else {
@@ -259,48 +323,78 @@ ${arg}`);
259
323
  return processedArgs;
260
324
  }
261
325
  _applyBrowserColors(consoleMethod, args) {
262
- const hex = this._palette[consoleMethod];
263
- if (typeof args[0] === "string") {
264
- return [`%c${args[0]}`, `color:${hex}`, ...args.slice(1)];
326
+ const formatParts = [];
327
+ const cssArgs = [];
328
+ const otherArgs = [];
329
+ const paletteHex = this._palette[consoleMethod];
330
+ for (const rawArg of args) {
331
+ const arg = this._normalizeString(rawArg);
332
+ if (TypeValidator.isBrowserColorizedArg(arg)) {
333
+ formatParts.push(`%c${arg.text}`);
334
+ cssArgs.push(`color:${arg.hex}`);
335
+ continue;
336
+ }
337
+ if (TypeValidator.isBrowserObjectArg(arg)) {
338
+ formatParts.push("%o");
339
+ otherArgs.push(arg.value);
340
+ continue;
341
+ }
342
+ if (TypeValidator.isString(arg)) {
343
+ formatParts.push(`%c${arg}`);
344
+ cssArgs.push(`color:${paletteHex}`);
345
+ continue;
346
+ }
347
+ formatParts.push("%o");
348
+ otherArgs.push(arg);
265
349
  }
266
- return args;
350
+ if (formatParts.length === 0) {
351
+ return args;
352
+ }
353
+ return [formatParts.join(""), ...cssArgs, ...otherArgs];
267
354
  }
268
- _applyNodeColors(consoleMethod, args) {
269
- const hex = this._palette[consoleMethod];
270
- let ansiCode;
355
+ _toAnsiPrefix(hex) {
356
+ if (this._colorLevel === ColorLevel.NO_COLOR || this._colorLevel === "UnknownEnv") {
357
+ return "";
358
+ }
271
359
  switch (this._colorLevel) {
272
360
  case ColorLevel.TRUECOLOR: {
273
361
  const [r, g, b] = colorConverter.hex.toRgb(hex);
274
- ansiCode = `\x1B[38;2;${r};${g};${b}m`;
275
- break;
362
+ return `\x1B[38;2;${r};${g};${b}m`;
276
363
  }
277
364
  case ColorLevel.ANSI256: {
278
365
  const code = colorConverter.hex.toAnsi256(hex);
279
- ansiCode = `\x1B[38;5;${code}m`;
280
- break;
366
+ return `\x1B[38;5;${code}m`;
281
367
  }
282
368
  case ColorLevel.ANSI:
283
369
  default: {
284
370
  const code = colorConverter.hex.toAnsi16(hex);
285
- ansiCode = `\x1B[${code}m`;
286
- break;
371
+ return `\x1B[${code}m`;
287
372
  }
288
373
  }
374
+ }
375
+ _applyNodeColors(consoleMethod, args) {
289
376
  const coloredArgs = [...args];
290
377
  const firstStringIndex = coloredArgs.findIndex(
291
378
  (arg) => typeof arg === "string"
292
379
  );
293
- if (firstStringIndex !== -1) {
294
- coloredArgs[firstStringIndex] = `${ansiCode}${coloredArgs[firstStringIndex]}\x1B[0m`;
380
+ if (firstStringIndex === -1) {
381
+ return coloredArgs;
382
+ }
383
+ const first = coloredArgs[firstStringIndex];
384
+ if (this._isAnsiColoredString(first)) {
385
+ return coloredArgs;
386
+ }
387
+ const hex = this._palette[consoleMethod];
388
+ const ansiPrefix = this._toAnsiPrefix(hex);
389
+ if (!ansiPrefix) {
390
+ return coloredArgs;
295
391
  }
392
+ coloredArgs[firstStringIndex] = `${ansiPrefix}${String(first)}\x1B[0m`;
296
393
  return coloredArgs;
297
394
  }
298
395
  _output(consoleMethod, args) {
299
- if (consoleMethod === "trace") {
300
- this._printCleanTrace(args);
301
- } else {
302
- console[consoleMethod](...args);
303
- }
396
+ if (consoleMethod === "trace") this._printCleanTrace(args);
397
+ else console[consoleMethod](...args);
304
398
  }
305
399
  _out(level, args) {
306
400
  const consoleMethod = isConsoleMethod(level) ? level : "log";
@@ -822,11 +916,11 @@ class Err {
822
916
  }
823
917
  Result.fromThrowable;
824
918
 
825
- class ColorinoError extends Error {
919
+ class InputValidationError extends Error {
826
920
  constructor(message) {
827
921
  super(message);
828
- this.name = "ColorinoError";
829
- Object.setPrototypeOf(this, ColorinoError.prototype);
922
+ this.name = "InputValidationError";
923
+ Object.setPrototypeOf(this, InputValidationError.prototype);
830
924
  }
831
925
  }
832
926
 
@@ -835,7 +929,7 @@ class InputValidator {
835
929
  const trimmedHex = hex.trim();
836
930
  const isHexValid = /^#[0-9A-F]{6}$/i.test(trimmedHex);
837
931
  if (!isHexValid) {
838
- return err(new ColorinoError(`Invalid hex color: '${hex}'`));
932
+ return err(new InputValidationError(`Invalid hex color: '${hex}'`));
839
933
  }
840
934
  return ok(true);
841
935
  }
@@ -60,6 +60,8 @@ var ColorLevel = /* @__PURE__ */ ((ColorLevel2) => {
60
60
  function isConsoleMethod(level) {
61
61
  return ["log", "info", "warn", "error", "trace", "debug"].includes(level);
62
62
  }
63
+ const ColorinoBrowserColorized = Symbol("colorino.browserColorized");
64
+ const ColorinoBrowserObject = Symbol("colorino.browserObject");
63
65
 
64
66
  const catppuccinMochaPalette = {
65
67
  log: "#cdd6f4",
@@ -144,6 +146,33 @@ function determineBaseTheme(themeOpt, detectedBrowserTheme) {
144
146
  return baseThemeName;
145
147
  }
146
148
 
149
+ class TypeValidator {
150
+ static isNull(value) {
151
+ return value === null;
152
+ }
153
+ static isObject(value) {
154
+ return typeof value === "object" && value !== null;
155
+ }
156
+ static isString(value) {
157
+ return typeof value === "string";
158
+ }
159
+ static isError(value) {
160
+ return value instanceof Error;
161
+ }
162
+ static isBrowserColorizedArg(value) {
163
+ return typeof value === "object" && value !== null && ColorinoBrowserColorized in value;
164
+ }
165
+ static isBrowserObjectArg(value) {
166
+ return typeof value === "object" && value !== null && ColorinoBrowserObject in value;
167
+ }
168
+ static isAnsiColoredString(value) {
169
+ return TypeValidator.isString(value) && /\x1b\[[0-9;]*m/.test(value);
170
+ }
171
+ static isFormattableObject(value) {
172
+ return TypeValidator.isObject(value) && !TypeValidator.isError(value) && !TypeValidator.isBrowserColorizedArg(value);
173
+ }
174
+ }
175
+
147
176
  class MyColorino {
148
177
  constructor(initialPalette, _userPalette, _validator, _browserColorSupportDetector, _nodeColorSupportDetector, _options = {}) {
149
178
  this._userPalette = _userPalette;
@@ -162,7 +191,7 @@ class MyColorino {
162
191
  const themeOpt = this._options.theme ?? "auto";
163
192
  if (themeOpt === "auto" && this._nodeColorSupportDetector) {
164
193
  this._nodeColorSupportDetector.onTheme((resolvedTheme) => {
165
- this._appllyResolvedTheme(resolvedTheme);
194
+ this._applyResolvedTheme(resolvedTheme);
166
195
  });
167
196
  }
168
197
  }
@@ -170,12 +199,6 @@ class MyColorino {
170
199
  _colorLevel;
171
200
  isBrowser;
172
201
  _palette;
173
- _appllyResolvedTheme(resolvedTheme) {
174
- const themeOpt = this._options.theme ?? "auto";
175
- const baseThemeName = determineBaseTheme(themeOpt, resolvedTheme);
176
- const basePalette = themePalettes[baseThemeName];
177
- this._palette = { ...basePalette, ...this._userPalette };
178
- }
179
202
  log(...args) {
180
203
  this._out("log", args);
181
204
  }
@@ -194,6 +217,32 @@ class MyColorino {
194
217
  debug(...args) {
195
218
  this._out("debug", args);
196
219
  }
220
+ colorize(text, hex) {
221
+ if (this._colorLevel === ColorLevel.NO_COLOR || this._colorLevel === "UnknownEnv") {
222
+ return text;
223
+ }
224
+ if (this.isBrowser) {
225
+ return {
226
+ [ColorinoBrowserColorized]: true,
227
+ text,
228
+ hex
229
+ };
230
+ }
231
+ const ansiPrefix = this._toAnsiPrefix(hex);
232
+ if (!ansiPrefix) {
233
+ return text;
234
+ }
235
+ return `${ansiPrefix}${text}\x1B[0m`;
236
+ }
237
+ _isAnsiColoredString(value) {
238
+ return typeof value === "string" && /\x1b\[[0-9;]*m/.test(value);
239
+ }
240
+ _applyResolvedTheme(resolvedTheme) {
241
+ const themeOpt = this._options.theme ?? "auto";
242
+ const baseThemeName = determineBaseTheme(themeOpt, resolvedTheme);
243
+ const basePalette = themePalettes[baseThemeName];
244
+ this._palette = { ...basePalette, ...this._userPalette };
245
+ }
197
246
  _detectColorSupport() {
198
247
  if (this.isBrowser) {
199
248
  return this._browserColorSupportDetector?.getColorLevel() ?? "UnknownEnv";
@@ -213,7 +262,7 @@ class MyColorino {
213
262
  _formatValue(value, maxDepth = this._options.maxDepth ?? 5) {
214
263
  const seen = /* @__PURE__ */ new WeakSet();
215
264
  const sanitize = (val, currentDepth) => {
216
- if (val === null || typeof val !== "object") return val;
265
+ if (val == null || typeof val !== "object") return val;
217
266
  if (seen.has(val)) return "[Circular]";
218
267
  seen.add(val);
219
268
  if (currentDepth >= maxDepth) return "[Object]";
@@ -231,21 +280,36 @@ class MyColorino {
231
280
  };
232
281
  return JSON.stringify(sanitize(value, 0), null, 2);
233
282
  }
283
+ _normalizeString(value) {
284
+ if (value instanceof String) return value.valueOf();
285
+ return value;
286
+ }
234
287
  _processArgs(args) {
235
288
  const processedArgs = [];
236
289
  let previousWasObject = false;
237
- for (const arg of args) {
238
- const isFormattableObject = arg !== null && typeof arg === "object" && typeof arg !== "string" && !(arg instanceof Error);
239
- const isError = arg instanceof Error;
240
- if (isFormattableObject) {
241
- processedArgs.push(`
290
+ for (const rawArg of args) {
291
+ const arg = this._normalizeString(rawArg);
292
+ if (TypeValidator.isBrowserColorizedArg(arg)) {
293
+ processedArgs.push(arg);
294
+ previousWasObject = false;
295
+ continue;
296
+ }
297
+ if (TypeValidator.isFormattableObject(arg)) {
298
+ if (this.isBrowser) {
299
+ processedArgs.push({
300
+ [ColorinoBrowserObject]: true,
301
+ value: arg
302
+ });
303
+ } else {
304
+ processedArgs.push(`
242
305
  ${this._formatValue(arg)}`);
306
+ }
243
307
  previousWasObject = true;
244
- } else if (isError) {
308
+ } else if (TypeValidator.isError(arg)) {
245
309
  processedArgs.push("\n", this._cleanErrorStack(arg));
246
310
  previousWasObject = true;
247
311
  } else {
248
- if (typeof arg === "string" && previousWasObject) {
312
+ if (TypeValidator.isString(arg) && previousWasObject) {
249
313
  processedArgs.push(`
250
314
  ${arg}`);
251
315
  } else {
@@ -257,48 +321,78 @@ ${arg}`);
257
321
  return processedArgs;
258
322
  }
259
323
  _applyBrowserColors(consoleMethod, args) {
260
- const hex = this._palette[consoleMethod];
261
- if (typeof args[0] === "string") {
262
- return [`%c${args[0]}`, `color:${hex}`, ...args.slice(1)];
324
+ const formatParts = [];
325
+ const cssArgs = [];
326
+ const otherArgs = [];
327
+ const paletteHex = this._palette[consoleMethod];
328
+ for (const rawArg of args) {
329
+ const arg = this._normalizeString(rawArg);
330
+ if (TypeValidator.isBrowserColorizedArg(arg)) {
331
+ formatParts.push(`%c${arg.text}`);
332
+ cssArgs.push(`color:${arg.hex}`);
333
+ continue;
334
+ }
335
+ if (TypeValidator.isBrowserObjectArg(arg)) {
336
+ formatParts.push("%o");
337
+ otherArgs.push(arg.value);
338
+ continue;
339
+ }
340
+ if (TypeValidator.isString(arg)) {
341
+ formatParts.push(`%c${arg}`);
342
+ cssArgs.push(`color:${paletteHex}`);
343
+ continue;
344
+ }
345
+ formatParts.push("%o");
346
+ otherArgs.push(arg);
263
347
  }
264
- return args;
348
+ if (formatParts.length === 0) {
349
+ return args;
350
+ }
351
+ return [formatParts.join(""), ...cssArgs, ...otherArgs];
265
352
  }
266
- _applyNodeColors(consoleMethod, args) {
267
- const hex = this._palette[consoleMethod];
268
- let ansiCode;
353
+ _toAnsiPrefix(hex) {
354
+ if (this._colorLevel === ColorLevel.NO_COLOR || this._colorLevel === "UnknownEnv") {
355
+ return "";
356
+ }
269
357
  switch (this._colorLevel) {
270
358
  case ColorLevel.TRUECOLOR: {
271
359
  const [r, g, b] = colorConverter.hex.toRgb(hex);
272
- ansiCode = `\x1B[38;2;${r};${g};${b}m`;
273
- break;
360
+ return `\x1B[38;2;${r};${g};${b}m`;
274
361
  }
275
362
  case ColorLevel.ANSI256: {
276
363
  const code = colorConverter.hex.toAnsi256(hex);
277
- ansiCode = `\x1B[38;5;${code}m`;
278
- break;
364
+ return `\x1B[38;5;${code}m`;
279
365
  }
280
366
  case ColorLevel.ANSI:
281
367
  default: {
282
368
  const code = colorConverter.hex.toAnsi16(hex);
283
- ansiCode = `\x1B[${code}m`;
284
- break;
369
+ return `\x1B[${code}m`;
285
370
  }
286
371
  }
372
+ }
373
+ _applyNodeColors(consoleMethod, args) {
287
374
  const coloredArgs = [...args];
288
375
  const firstStringIndex = coloredArgs.findIndex(
289
376
  (arg) => typeof arg === "string"
290
377
  );
291
- if (firstStringIndex !== -1) {
292
- coloredArgs[firstStringIndex] = `${ansiCode}${coloredArgs[firstStringIndex]}\x1B[0m`;
378
+ if (firstStringIndex === -1) {
379
+ return coloredArgs;
380
+ }
381
+ const first = coloredArgs[firstStringIndex];
382
+ if (this._isAnsiColoredString(first)) {
383
+ return coloredArgs;
384
+ }
385
+ const hex = this._palette[consoleMethod];
386
+ const ansiPrefix = this._toAnsiPrefix(hex);
387
+ if (!ansiPrefix) {
388
+ return coloredArgs;
293
389
  }
390
+ coloredArgs[firstStringIndex] = `${ansiPrefix}${String(first)}\x1B[0m`;
294
391
  return coloredArgs;
295
392
  }
296
393
  _output(consoleMethod, args) {
297
- if (consoleMethod === "trace") {
298
- this._printCleanTrace(args);
299
- } else {
300
- console[consoleMethod](...args);
301
- }
394
+ if (consoleMethod === "trace") this._printCleanTrace(args);
395
+ else console[consoleMethod](...args);
302
396
  }
303
397
  _out(level, args) {
304
398
  const consoleMethod = isConsoleMethod(level) ? level : "log";
@@ -820,11 +914,11 @@ class Err {
820
914
  }
821
915
  Result.fromThrowable;
822
916
 
823
- class ColorinoError extends Error {
917
+ class InputValidationError extends Error {
824
918
  constructor(message) {
825
919
  super(message);
826
- this.name = "ColorinoError";
827
- Object.setPrototypeOf(this, ColorinoError.prototype);
920
+ this.name = "InputValidationError";
921
+ Object.setPrototypeOf(this, InputValidationError.prototype);
828
922
  }
829
923
  }
830
924
 
@@ -833,7 +927,7 @@ class InputValidator {
833
927
  const trimmedHex = hex.trim();
834
928
  const isHexValid = /^#[0-9A-F]{6}$/i.test(trimmedHex);
835
929
  if (!isHexValid) {
836
- return err(new ColorinoError(`Invalid hex color: '${hex}'`));
930
+ return err(new InputValidationError(`Invalid hex color: '${hex}'`));
837
931
  }
838
932
  return ok(true);
839
933
  }
package/dist/browser.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const inputValidator = require('./shared/colorino.BgxwS1Lc.cjs');
3
+ const inputValidator = require('./shared/colorino.Dcy2ipG7.cjs');
4
4
 
5
5
  class BrowserColorSupportDetector {
6
6
  constructor(_window, _navigator, _overrideTheme) {
@@ -1,5 +1,5 @@
1
- import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.DWWtObdr.cjs';
2
- export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.DWWtObdr.cjs';
1
+ import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.FdIbpxRG.cjs';
2
+ export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.FdIbpxRG.cjs';
3
3
 
4
4
  declare function createColorino(userPalette?: Partial<Palette>, options?: ColorinoOptions): Colorino;
5
5
 
@@ -1,5 +1,5 @@
1
- import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.DWWtObdr.mjs';
2
- export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.DWWtObdr.mjs';
1
+ import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.FdIbpxRG.mjs';
2
+ export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.FdIbpxRG.mjs';
3
3
 
4
4
  declare function createColorino(userPalette?: Partial<Palette>, options?: ColorinoOptions): Colorino;
5
5
 
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.DWWtObdr.js';
2
- export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.DWWtObdr.js';
1
+ import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.FdIbpxRG.js';
2
+ export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.FdIbpxRG.js';
3
3
 
4
4
  declare function createColorino(userPalette?: Partial<Palette>, options?: ColorinoOptions): Colorino;
5
5
 
package/dist/browser.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ColorLevel, t as themePalettes, M as MyColorino, I as InputValidator, d as determineBaseTheme } from './shared/colorino.ClDpLv-k.mjs';
1
+ import { C as ColorLevel, t as themePalettes, M as MyColorino, I as InputValidator, d as determineBaseTheme } from './shared/colorino.DEvR4n1Y.mjs';
2
2
 
3
3
  class BrowserColorSupportDetector {
4
4
  constructor(_window, _navigator, _overrideTheme) {
package/dist/node.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const inputValidator = require('./shared/colorino.BgxwS1Lc.cjs');
3
+ const inputValidator = require('./shared/colorino.Dcy2ipG7.cjs');
4
4
  const node_child_process = require('node:child_process');
5
5
  const node_url = require('node:url');
6
6
  const node_path = require('node:path');
package/dist/node.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.DWWtObdr.cjs';
2
- export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.DWWtObdr.cjs';
1
+ import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.FdIbpxRG.cjs';
2
+ export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.FdIbpxRG.cjs';
3
3
 
4
4
  declare function createColorino(userPalette?: Partial<Palette>, options?: ColorinoOptions): Colorino;
5
5
 
package/dist/node.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.DWWtObdr.mjs';
2
- export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.DWWtObdr.mjs';
1
+ import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.FdIbpxRG.mjs';
2
+ export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.FdIbpxRG.mjs';
3
3
 
4
4
  declare function createColorino(userPalette?: Partial<Palette>, options?: ColorinoOptions): Colorino;
5
5
 
package/dist/node.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.DWWtObdr.js';
2
- export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.DWWtObdr.js';
1
+ import { P as Palette, C as ColorinoOptions, a as Colorino } from './shared/colorino.FdIbpxRG.js';
2
+ export { L as LogLevel, T as ThemeName, t as themePalettes } from './shared/colorino.FdIbpxRG.js';
3
3
 
4
4
  declare function createColorino(userPalette?: Partial<Palette>, options?: ColorinoOptions): Colorino;
5
5
 
package/dist/node.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ColorLevel, d as determineBaseTheme, t as themePalettes, M as MyColorino, I as InputValidator } from './shared/colorino.ClDpLv-k.mjs';
1
+ import { C as ColorLevel, d as determineBaseTheme, t as themePalettes, M as MyColorino, I as InputValidator } from './shared/colorino.DEvR4n1Y.mjs';
2
2
  import { spawnSync } from 'node:child_process';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { dirname, join } from 'node:path';
@@ -60,6 +60,8 @@ var ColorLevel = /* @__PURE__ */ ((ColorLevel2) => {
60
60
  function isConsoleMethod(level) {
61
61
  return ["log", "info", "warn", "error", "trace", "debug"].includes(level);
62
62
  }
63
+ const ColorinoBrowserColorized = Symbol("colorino.browserColorized");
64
+ const ColorinoBrowserObject = Symbol("colorino.browserObject");
63
65
 
64
66
  const catppuccinMochaPalette = {
65
67
  log: "#cdd6f4",
@@ -144,6 +146,33 @@ function determineBaseTheme(themeOpt, detectedBrowserTheme) {
144
146
  return baseThemeName;
145
147
  }
146
148
 
149
+ class TypeValidator {
150
+ static isNull(value) {
151
+ return value === null;
152
+ }
153
+ static isObject(value) {
154
+ return typeof value === "object" && value !== null;
155
+ }
156
+ static isString(value) {
157
+ return typeof value === "string";
158
+ }
159
+ static isError(value) {
160
+ return value instanceof Error;
161
+ }
162
+ static isBrowserColorizedArg(value) {
163
+ return typeof value === "object" && value !== null && ColorinoBrowserColorized in value;
164
+ }
165
+ static isBrowserObjectArg(value) {
166
+ return typeof value === "object" && value !== null && ColorinoBrowserObject in value;
167
+ }
168
+ static isAnsiColoredString(value) {
169
+ return TypeValidator.isString(value) && /\x1b\[[0-9;]*m/.test(value);
170
+ }
171
+ static isFormattableObject(value) {
172
+ return TypeValidator.isObject(value) && !TypeValidator.isError(value) && !TypeValidator.isBrowserColorizedArg(value);
173
+ }
174
+ }
175
+
147
176
  class MyColorino {
148
177
  constructor(initialPalette, _userPalette, _validator, _browserColorSupportDetector, _nodeColorSupportDetector, _options = {}) {
149
178
  this._userPalette = _userPalette;
@@ -162,7 +191,7 @@ class MyColorino {
162
191
  const themeOpt = this._options.theme ?? "auto";
163
192
  if (themeOpt === "auto" && this._nodeColorSupportDetector) {
164
193
  this._nodeColorSupportDetector.onTheme((resolvedTheme) => {
165
- this._appllyResolvedTheme(resolvedTheme);
194
+ this._applyResolvedTheme(resolvedTheme);
166
195
  });
167
196
  }
168
197
  }
@@ -170,12 +199,6 @@ class MyColorino {
170
199
  _colorLevel;
171
200
  isBrowser;
172
201
  _palette;
173
- _appllyResolvedTheme(resolvedTheme) {
174
- const themeOpt = this._options.theme ?? "auto";
175
- const baseThemeName = determineBaseTheme(themeOpt, resolvedTheme);
176
- const basePalette = themePalettes[baseThemeName];
177
- this._palette = { ...basePalette, ...this._userPalette };
178
- }
179
202
  log(...args) {
180
203
  this._out("log", args);
181
204
  }
@@ -194,6 +217,32 @@ class MyColorino {
194
217
  debug(...args) {
195
218
  this._out("debug", args);
196
219
  }
220
+ colorize(text, hex) {
221
+ if (this._colorLevel === ColorLevel.NO_COLOR || this._colorLevel === "UnknownEnv") {
222
+ return text;
223
+ }
224
+ if (this.isBrowser) {
225
+ return {
226
+ [ColorinoBrowserColorized]: true,
227
+ text,
228
+ hex
229
+ };
230
+ }
231
+ const ansiPrefix = this._toAnsiPrefix(hex);
232
+ if (!ansiPrefix) {
233
+ return text;
234
+ }
235
+ return `${ansiPrefix}${text}\x1B[0m`;
236
+ }
237
+ _isAnsiColoredString(value) {
238
+ return typeof value === "string" && /\x1b\[[0-9;]*m/.test(value);
239
+ }
240
+ _applyResolvedTheme(resolvedTheme) {
241
+ const themeOpt = this._options.theme ?? "auto";
242
+ const baseThemeName = determineBaseTheme(themeOpt, resolvedTheme);
243
+ const basePalette = themePalettes[baseThemeName];
244
+ this._palette = { ...basePalette, ...this._userPalette };
245
+ }
197
246
  _detectColorSupport() {
198
247
  if (this.isBrowser) {
199
248
  return this._browserColorSupportDetector?.getColorLevel() ?? "UnknownEnv";
@@ -213,7 +262,7 @@ class MyColorino {
213
262
  _formatValue(value, maxDepth = this._options.maxDepth ?? 5) {
214
263
  const seen = /* @__PURE__ */ new WeakSet();
215
264
  const sanitize = (val, currentDepth) => {
216
- if (val === null || typeof val !== "object") return val;
265
+ if (val == null || typeof val !== "object") return val;
217
266
  if (seen.has(val)) return "[Circular]";
218
267
  seen.add(val);
219
268
  if (currentDepth >= maxDepth) return "[Object]";
@@ -231,21 +280,36 @@ class MyColorino {
231
280
  };
232
281
  return JSON.stringify(sanitize(value, 0), null, 2);
233
282
  }
283
+ _normalizeString(value) {
284
+ if (value instanceof String) return value.valueOf();
285
+ return value;
286
+ }
234
287
  _processArgs(args) {
235
288
  const processedArgs = [];
236
289
  let previousWasObject = false;
237
- for (const arg of args) {
238
- const isFormattableObject = arg !== null && typeof arg === "object" && typeof arg !== "string" && !(arg instanceof Error);
239
- const isError = arg instanceof Error;
240
- if (isFormattableObject) {
241
- processedArgs.push(`
290
+ for (const rawArg of args) {
291
+ const arg = this._normalizeString(rawArg);
292
+ if (TypeValidator.isBrowserColorizedArg(arg)) {
293
+ processedArgs.push(arg);
294
+ previousWasObject = false;
295
+ continue;
296
+ }
297
+ if (TypeValidator.isFormattableObject(arg)) {
298
+ if (this.isBrowser) {
299
+ processedArgs.push({
300
+ [ColorinoBrowserObject]: true,
301
+ value: arg
302
+ });
303
+ } else {
304
+ processedArgs.push(`
242
305
  ${this._formatValue(arg)}`);
306
+ }
243
307
  previousWasObject = true;
244
- } else if (isError) {
308
+ } else if (TypeValidator.isError(arg)) {
245
309
  processedArgs.push("\n", this._cleanErrorStack(arg));
246
310
  previousWasObject = true;
247
311
  } else {
248
- if (typeof arg === "string" && previousWasObject) {
312
+ if (TypeValidator.isString(arg) && previousWasObject) {
249
313
  processedArgs.push(`
250
314
  ${arg}`);
251
315
  } else {
@@ -257,48 +321,78 @@ ${arg}`);
257
321
  return processedArgs;
258
322
  }
259
323
  _applyBrowserColors(consoleMethod, args) {
260
- const hex = this._palette[consoleMethod];
261
- if (typeof args[0] === "string") {
262
- return [`%c${args[0]}`, `color:${hex}`, ...args.slice(1)];
324
+ const formatParts = [];
325
+ const cssArgs = [];
326
+ const otherArgs = [];
327
+ const paletteHex = this._palette[consoleMethod];
328
+ for (const rawArg of args) {
329
+ const arg = this._normalizeString(rawArg);
330
+ if (TypeValidator.isBrowserColorizedArg(arg)) {
331
+ formatParts.push(`%c${arg.text}`);
332
+ cssArgs.push(`color:${arg.hex}`);
333
+ continue;
334
+ }
335
+ if (TypeValidator.isBrowserObjectArg(arg)) {
336
+ formatParts.push("%o");
337
+ otherArgs.push(arg.value);
338
+ continue;
339
+ }
340
+ if (TypeValidator.isString(arg)) {
341
+ formatParts.push(`%c${arg}`);
342
+ cssArgs.push(`color:${paletteHex}`);
343
+ continue;
344
+ }
345
+ formatParts.push("%o");
346
+ otherArgs.push(arg);
263
347
  }
264
- return args;
348
+ if (formatParts.length === 0) {
349
+ return args;
350
+ }
351
+ return [formatParts.join(""), ...cssArgs, ...otherArgs];
265
352
  }
266
- _applyNodeColors(consoleMethod, args) {
267
- const hex = this._palette[consoleMethod];
268
- let ansiCode;
353
+ _toAnsiPrefix(hex) {
354
+ if (this._colorLevel === ColorLevel.NO_COLOR || this._colorLevel === "UnknownEnv") {
355
+ return "";
356
+ }
269
357
  switch (this._colorLevel) {
270
358
  case ColorLevel.TRUECOLOR: {
271
359
  const [r, g, b] = colorConverter.hex.toRgb(hex);
272
- ansiCode = `\x1B[38;2;${r};${g};${b}m`;
273
- break;
360
+ return `\x1B[38;2;${r};${g};${b}m`;
274
361
  }
275
362
  case ColorLevel.ANSI256: {
276
363
  const code = colorConverter.hex.toAnsi256(hex);
277
- ansiCode = `\x1B[38;5;${code}m`;
278
- break;
364
+ return `\x1B[38;5;${code}m`;
279
365
  }
280
366
  case ColorLevel.ANSI:
281
367
  default: {
282
368
  const code = colorConverter.hex.toAnsi16(hex);
283
- ansiCode = `\x1B[${code}m`;
284
- break;
369
+ return `\x1B[${code}m`;
285
370
  }
286
371
  }
372
+ }
373
+ _applyNodeColors(consoleMethod, args) {
287
374
  const coloredArgs = [...args];
288
375
  const firstStringIndex = coloredArgs.findIndex(
289
376
  (arg) => typeof arg === "string"
290
377
  );
291
- if (firstStringIndex !== -1) {
292
- coloredArgs[firstStringIndex] = `${ansiCode}${coloredArgs[firstStringIndex]}\x1B[0m`;
378
+ if (firstStringIndex === -1) {
379
+ return coloredArgs;
380
+ }
381
+ const first = coloredArgs[firstStringIndex];
382
+ if (this._isAnsiColoredString(first)) {
383
+ return coloredArgs;
384
+ }
385
+ const hex = this._palette[consoleMethod];
386
+ const ansiPrefix = this._toAnsiPrefix(hex);
387
+ if (!ansiPrefix) {
388
+ return coloredArgs;
293
389
  }
390
+ coloredArgs[firstStringIndex] = `${ansiPrefix}${String(first)}\x1B[0m`;
294
391
  return coloredArgs;
295
392
  }
296
393
  _output(consoleMethod, args) {
297
- if (consoleMethod === "trace") {
298
- this._printCleanTrace(args);
299
- } else {
300
- console[consoleMethod](...args);
301
- }
394
+ if (consoleMethod === "trace") this._printCleanTrace(args);
395
+ else console[consoleMethod](...args);
302
396
  }
303
397
  _out(level, args) {
304
398
  const consoleMethod = isConsoleMethod(level) ? level : "log";
@@ -820,11 +914,11 @@ class Err {
820
914
  }
821
915
  Result.fromThrowable;
822
916
 
823
- class ColorinoError extends Error {
917
+ class InputValidationError extends Error {
824
918
  constructor(message) {
825
919
  super(message);
826
- this.name = "ColorinoError";
827
- Object.setPrototypeOf(this, ColorinoError.prototype);
920
+ this.name = "InputValidationError";
921
+ Object.setPrototypeOf(this, InputValidationError.prototype);
828
922
  }
829
923
  }
830
924
 
@@ -833,7 +927,7 @@ class InputValidator {
833
927
  const trimmedHex = hex.trim();
834
928
  const isHexValid = /^#[0-9A-F]{6}$/i.test(trimmedHex);
835
929
  if (!isHexValid) {
836
- return err(new ColorinoError(`Invalid hex color: '${hex}'`));
930
+ return err(new InputValidationError(`Invalid hex color: '${hex}'`));
837
931
  }
838
932
  return ok(true);
839
933
  }
@@ -62,6 +62,8 @@ var ColorLevel = /* @__PURE__ */ ((ColorLevel2) => {
62
62
  function isConsoleMethod(level) {
63
63
  return ["log", "info", "warn", "error", "trace", "debug"].includes(level);
64
64
  }
65
+ const ColorinoBrowserColorized = Symbol("colorino.browserColorized");
66
+ const ColorinoBrowserObject = Symbol("colorino.browserObject");
65
67
 
66
68
  const catppuccinMochaPalette = {
67
69
  log: "#cdd6f4",
@@ -146,6 +148,33 @@ function determineBaseTheme(themeOpt, detectedBrowserTheme) {
146
148
  return baseThemeName;
147
149
  }
148
150
 
151
+ class TypeValidator {
152
+ static isNull(value) {
153
+ return value === null;
154
+ }
155
+ static isObject(value) {
156
+ return typeof value === "object" && value !== null;
157
+ }
158
+ static isString(value) {
159
+ return typeof value === "string";
160
+ }
161
+ static isError(value) {
162
+ return value instanceof Error;
163
+ }
164
+ static isBrowserColorizedArg(value) {
165
+ return typeof value === "object" && value !== null && ColorinoBrowserColorized in value;
166
+ }
167
+ static isBrowserObjectArg(value) {
168
+ return typeof value === "object" && value !== null && ColorinoBrowserObject in value;
169
+ }
170
+ static isAnsiColoredString(value) {
171
+ return TypeValidator.isString(value) && /\x1b\[[0-9;]*m/.test(value);
172
+ }
173
+ static isFormattableObject(value) {
174
+ return TypeValidator.isObject(value) && !TypeValidator.isError(value) && !TypeValidator.isBrowserColorizedArg(value);
175
+ }
176
+ }
177
+
149
178
  class MyColorino {
150
179
  constructor(initialPalette, _userPalette, _validator, _browserColorSupportDetector, _nodeColorSupportDetector, _options = {}) {
151
180
  this._userPalette = _userPalette;
@@ -164,7 +193,7 @@ class MyColorino {
164
193
  const themeOpt = this._options.theme ?? "auto";
165
194
  if (themeOpt === "auto" && this._nodeColorSupportDetector) {
166
195
  this._nodeColorSupportDetector.onTheme((resolvedTheme) => {
167
- this._appllyResolvedTheme(resolvedTheme);
196
+ this._applyResolvedTheme(resolvedTheme);
168
197
  });
169
198
  }
170
199
  }
@@ -172,12 +201,6 @@ class MyColorino {
172
201
  _colorLevel;
173
202
  isBrowser;
174
203
  _palette;
175
- _appllyResolvedTheme(resolvedTheme) {
176
- const themeOpt = this._options.theme ?? "auto";
177
- const baseThemeName = determineBaseTheme(themeOpt, resolvedTheme);
178
- const basePalette = themePalettes[baseThemeName];
179
- this._palette = { ...basePalette, ...this._userPalette };
180
- }
181
204
  log(...args) {
182
205
  this._out("log", args);
183
206
  }
@@ -196,6 +219,32 @@ class MyColorino {
196
219
  debug(...args) {
197
220
  this._out("debug", args);
198
221
  }
222
+ colorize(text, hex) {
223
+ if (this._colorLevel === ColorLevel.NO_COLOR || this._colorLevel === "UnknownEnv") {
224
+ return text;
225
+ }
226
+ if (this.isBrowser) {
227
+ return {
228
+ [ColorinoBrowserColorized]: true,
229
+ text,
230
+ hex
231
+ };
232
+ }
233
+ const ansiPrefix = this._toAnsiPrefix(hex);
234
+ if (!ansiPrefix) {
235
+ return text;
236
+ }
237
+ return `${ansiPrefix}${text}\x1B[0m`;
238
+ }
239
+ _isAnsiColoredString(value) {
240
+ return typeof value === "string" && /\x1b\[[0-9;]*m/.test(value);
241
+ }
242
+ _applyResolvedTheme(resolvedTheme) {
243
+ const themeOpt = this._options.theme ?? "auto";
244
+ const baseThemeName = determineBaseTheme(themeOpt, resolvedTheme);
245
+ const basePalette = themePalettes[baseThemeName];
246
+ this._palette = { ...basePalette, ...this._userPalette };
247
+ }
199
248
  _detectColorSupport() {
200
249
  if (this.isBrowser) {
201
250
  return this._browserColorSupportDetector?.getColorLevel() ?? "UnknownEnv";
@@ -215,7 +264,7 @@ class MyColorino {
215
264
  _formatValue(value, maxDepth = this._options.maxDepth ?? 5) {
216
265
  const seen = /* @__PURE__ */ new WeakSet();
217
266
  const sanitize = (val, currentDepth) => {
218
- if (val === null || typeof val !== "object") return val;
267
+ if (val == null || typeof val !== "object") return val;
219
268
  if (seen.has(val)) return "[Circular]";
220
269
  seen.add(val);
221
270
  if (currentDepth >= maxDepth) return "[Object]";
@@ -233,21 +282,36 @@ class MyColorino {
233
282
  };
234
283
  return JSON.stringify(sanitize(value, 0), null, 2);
235
284
  }
285
+ _normalizeString(value) {
286
+ if (value instanceof String) return value.valueOf();
287
+ return value;
288
+ }
236
289
  _processArgs(args) {
237
290
  const processedArgs = [];
238
291
  let previousWasObject = false;
239
- for (const arg of args) {
240
- const isFormattableObject = arg !== null && typeof arg === "object" && typeof arg !== "string" && !(arg instanceof Error);
241
- const isError = arg instanceof Error;
242
- if (isFormattableObject) {
243
- processedArgs.push(`
292
+ for (const rawArg of args) {
293
+ const arg = this._normalizeString(rawArg);
294
+ if (TypeValidator.isBrowserColorizedArg(arg)) {
295
+ processedArgs.push(arg);
296
+ previousWasObject = false;
297
+ continue;
298
+ }
299
+ if (TypeValidator.isFormattableObject(arg)) {
300
+ if (this.isBrowser) {
301
+ processedArgs.push({
302
+ [ColorinoBrowserObject]: true,
303
+ value: arg
304
+ });
305
+ } else {
306
+ processedArgs.push(`
244
307
  ${this._formatValue(arg)}`);
308
+ }
245
309
  previousWasObject = true;
246
- } else if (isError) {
310
+ } else if (TypeValidator.isError(arg)) {
247
311
  processedArgs.push("\n", this._cleanErrorStack(arg));
248
312
  previousWasObject = true;
249
313
  } else {
250
- if (typeof arg === "string" && previousWasObject) {
314
+ if (TypeValidator.isString(arg) && previousWasObject) {
251
315
  processedArgs.push(`
252
316
  ${arg}`);
253
317
  } else {
@@ -259,48 +323,78 @@ ${arg}`);
259
323
  return processedArgs;
260
324
  }
261
325
  _applyBrowserColors(consoleMethod, args) {
262
- const hex = this._palette[consoleMethod];
263
- if (typeof args[0] === "string") {
264
- return [`%c${args[0]}`, `color:${hex}`, ...args.slice(1)];
326
+ const formatParts = [];
327
+ const cssArgs = [];
328
+ const otherArgs = [];
329
+ const paletteHex = this._palette[consoleMethod];
330
+ for (const rawArg of args) {
331
+ const arg = this._normalizeString(rawArg);
332
+ if (TypeValidator.isBrowserColorizedArg(arg)) {
333
+ formatParts.push(`%c${arg.text}`);
334
+ cssArgs.push(`color:${arg.hex}`);
335
+ continue;
336
+ }
337
+ if (TypeValidator.isBrowserObjectArg(arg)) {
338
+ formatParts.push("%o");
339
+ otherArgs.push(arg.value);
340
+ continue;
341
+ }
342
+ if (TypeValidator.isString(arg)) {
343
+ formatParts.push(`%c${arg}`);
344
+ cssArgs.push(`color:${paletteHex}`);
345
+ continue;
346
+ }
347
+ formatParts.push("%o");
348
+ otherArgs.push(arg);
265
349
  }
266
- return args;
350
+ if (formatParts.length === 0) {
351
+ return args;
352
+ }
353
+ return [formatParts.join(""), ...cssArgs, ...otherArgs];
267
354
  }
268
- _applyNodeColors(consoleMethod, args) {
269
- const hex = this._palette[consoleMethod];
270
- let ansiCode;
355
+ _toAnsiPrefix(hex) {
356
+ if (this._colorLevel === ColorLevel.NO_COLOR || this._colorLevel === "UnknownEnv") {
357
+ return "";
358
+ }
271
359
  switch (this._colorLevel) {
272
360
  case ColorLevel.TRUECOLOR: {
273
361
  const [r, g, b] = colorConverter.hex.toRgb(hex);
274
- ansiCode = `\x1B[38;2;${r};${g};${b}m`;
275
- break;
362
+ return `\x1B[38;2;${r};${g};${b}m`;
276
363
  }
277
364
  case ColorLevel.ANSI256: {
278
365
  const code = colorConverter.hex.toAnsi256(hex);
279
- ansiCode = `\x1B[38;5;${code}m`;
280
- break;
366
+ return `\x1B[38;5;${code}m`;
281
367
  }
282
368
  case ColorLevel.ANSI:
283
369
  default: {
284
370
  const code = colorConverter.hex.toAnsi16(hex);
285
- ansiCode = `\x1B[${code}m`;
286
- break;
371
+ return `\x1B[${code}m`;
287
372
  }
288
373
  }
374
+ }
375
+ _applyNodeColors(consoleMethod, args) {
289
376
  const coloredArgs = [...args];
290
377
  const firstStringIndex = coloredArgs.findIndex(
291
378
  (arg) => typeof arg === "string"
292
379
  );
293
- if (firstStringIndex !== -1) {
294
- coloredArgs[firstStringIndex] = `${ansiCode}${coloredArgs[firstStringIndex]}\x1B[0m`;
380
+ if (firstStringIndex === -1) {
381
+ return coloredArgs;
382
+ }
383
+ const first = coloredArgs[firstStringIndex];
384
+ if (this._isAnsiColoredString(first)) {
385
+ return coloredArgs;
386
+ }
387
+ const hex = this._palette[consoleMethod];
388
+ const ansiPrefix = this._toAnsiPrefix(hex);
389
+ if (!ansiPrefix) {
390
+ return coloredArgs;
295
391
  }
392
+ coloredArgs[firstStringIndex] = `${ansiPrefix}${String(first)}\x1B[0m`;
296
393
  return coloredArgs;
297
394
  }
298
395
  _output(consoleMethod, args) {
299
- if (consoleMethod === "trace") {
300
- this._printCleanTrace(args);
301
- } else {
302
- console[consoleMethod](...args);
303
- }
396
+ if (consoleMethod === "trace") this._printCleanTrace(args);
397
+ else console[consoleMethod](...args);
304
398
  }
305
399
  _out(level, args) {
306
400
  const consoleMethod = isConsoleMethod(level) ? level : "log";
@@ -822,11 +916,11 @@ class Err {
822
916
  }
823
917
  Result.fromThrowable;
824
918
 
825
- class ColorinoError extends Error {
919
+ class InputValidationError extends Error {
826
920
  constructor(message) {
827
921
  super(message);
828
- this.name = "ColorinoError";
829
- Object.setPrototypeOf(this, ColorinoError.prototype);
922
+ this.name = "InputValidationError";
923
+ Object.setPrototypeOf(this, InputValidationError.prototype);
830
924
  }
831
925
  }
832
926
 
@@ -835,7 +929,7 @@ class InputValidator {
835
929
  const trimmedHex = hex.trim();
836
930
  const isHexValid = /^#[0-9A-F]{6}$/i.test(trimmedHex);
837
931
  if (!isHexValid) {
838
- return err(new ColorinoError(`Invalid hex color: '${hex}'`));
932
+ return err(new InputValidationError(`Invalid hex color: '${hex}'`));
839
933
  }
840
934
  return ok(true);
841
935
  }
@@ -16,6 +16,7 @@ interface Colorino {
16
16
  error(...args: unknown[]): void;
17
17
  debug(...args: unknown[]): void;
18
18
  trace(...args: unknown[]): void;
19
+ colorize(text: string, hex: string): void;
19
20
  }
20
21
 
21
22
  declare const themePalettes: Record<ThemeName, Palette>;
@@ -16,6 +16,7 @@ interface Colorino {
16
16
  error(...args: unknown[]): void;
17
17
  debug(...args: unknown[]): void;
18
18
  trace(...args: unknown[]): void;
19
+ colorize(text: string, hex: string): void;
19
20
  }
20
21
 
21
22
  declare const themePalettes: Record<ThemeName, Palette>;
@@ -16,6 +16,7 @@ interface Colorino {
16
16
  error(...args: unknown[]): void;
17
17
  debug(...args: unknown[]): void;
18
18
  trace(...args: unknown[]): void;
19
+ colorize(text: string, hex: string): void;
19
20
  }
20
21
 
21
22
  declare const themePalettes: Record<ThemeName, Palette>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "colorino",
3
- "version": "0.12.7",
3
+ "version": "0.13.0",
4
4
  "description": "A super simple colorized logger that gets the most out of your terminal",
5
5
  "type": "module",
6
6
  "license": "MIT",