@logtape/pretty 1.4.0-dev.408 → 1.4.0-dev.413

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/src/mod.ts DELETED
@@ -1,17 +0,0 @@
1
- /**
2
- * @logtape/pretty - Beautiful console formatter for LogTape
3
- *
4
- * A Signale-inspired formatter designed for local development with:
5
- * - Colorful icons for each log level
6
- * - Smart category truncation
7
- * - Dimmed styling for better readability
8
- * - Perfect column alignment
9
- *
10
- * @module
11
- */
12
-
13
- export {
14
- getPrettyFormatter,
15
- prettyFormatter,
16
- type PrettyFormatterOptions,
17
- } from "./formatter.ts";
@@ -1,67 +0,0 @@
1
- import { suite } from "@alinea/suite";
2
- import { assert } from "@std/assert/assert";
3
- import { assertEquals } from "@std/assert/equals";
4
- import {
5
- getOptimalWordWrapWidth,
6
- getTerminalWidth,
7
- isTerminal,
8
- } from "./terminal.ts";
9
-
10
- const test = suite(import.meta);
11
-
12
- test("isTerminal() returns boolean", () => {
13
- const result = isTerminal();
14
- assert(typeof result === "boolean", "isTerminal should return a boolean");
15
- });
16
-
17
- test("getTerminalWidth() returns number or null", () => {
18
- const result = getTerminalWidth();
19
- assert(
20
- result === null || (typeof result === "number" && result > 0),
21
- "getTerminalWidth should return null or a positive number",
22
- );
23
- });
24
-
25
- test("getOptimalWordWrapWidth() returns default when not in terminal", () => {
26
- // We can't easily mock the function, so we'll test with a large default
27
- // to distinguish from common terminal widths
28
- const result = getOptimalWordWrapWidth(123);
29
-
30
- assert(
31
- typeof result === "number" && result > 0,
32
- "getOptimalWordWrapWidth should return a positive number",
33
- );
34
-
35
- // If we're not in a terminal (which is likely in test environment),
36
- // we should get the default value
37
- if (!isTerminal()) {
38
- assertEquals(result, 123, "Should return default when not in terminal");
39
- }
40
- });
41
-
42
- test("getOptimalWordWrapWidth() uses reasonable default", () => {
43
- const result = getOptimalWordWrapWidth();
44
-
45
- assert(
46
- typeof result === "number" && result > 0,
47
- "Should return a positive number",
48
- );
49
-
50
- assert(
51
- result >= 20 && result <= 1000,
52
- "Should return a reasonable terminal width (20-1000 columns)",
53
- );
54
- });
55
-
56
- test("getOptimalWordWrapWidth() respects custom default", () => {
57
- const customDefault = 150;
58
- const result = getOptimalWordWrapWidth(customDefault);
59
-
60
- // If not in terminal, should return custom default
61
- if (!isTerminal()) {
62
- assertEquals(result, customDefault);
63
- } else {
64
- // If in terminal, should return detected width (which may differ)
65
- assert(typeof result === "number" && result > 0);
66
- }
67
- });
package/src/terminal.ts DELETED
@@ -1,94 +0,0 @@
1
- // deno-lint-ignore-file no-process-global
2
- /// <reference types="@types/node" />
3
- /**
4
- * @fileoverview
5
- * Terminal detection and width calculation utilities
6
- *
7
- * Provides cross-runtime compatible functions to detect if the process
8
- * is attached to a terminal and get the terminal width.
9
- */
10
-
11
- /**
12
- * Detect if the current process is attached to a terminal (TTY).
13
- *
14
- * @returns True if stdout is connected to a terminal
15
- */
16
- export function isTerminal(): boolean {
17
- try {
18
- // Deno runtime
19
- if (typeof Deno !== "undefined") {
20
- // Use modern Deno API
21
- if (Deno.stdout.isTerminal) {
22
- return Deno.stdout.isTerminal();
23
- }
24
- }
25
-
26
- // Node.js/Bun runtime
27
- if (typeof process !== "undefined" && process.stdout) {
28
- return Boolean(process.stdout.isTTY);
29
- }
30
-
31
- // Browser environment - never a terminal
32
- if (typeof window !== "undefined") {
33
- return false;
34
- }
35
-
36
- // Unknown environment - assume not a terminal
37
- return false;
38
- } catch {
39
- // If any detection method fails, assume not a terminal
40
- return false;
41
- }
42
- }
43
-
44
- /**
45
- * Get the current terminal width in columns.
46
- *
47
- * @returns Terminal width in columns, or null if not available
48
- */
49
- export function getTerminalWidth(): number | null {
50
- try {
51
- // Deno runtime
52
- if (typeof Deno !== "undefined") {
53
- // Try to get console size
54
- if (Deno.consoleSize) {
55
- const size = Deno.consoleSize();
56
- return size?.columns || null;
57
- }
58
- }
59
-
60
- // Node.js/Bun runtime
61
- if (typeof process !== "undefined" && process.stdout) {
62
- return process.stdout.columns || null;
63
- }
64
-
65
- // Fallback to environment variable
66
- const envColumns = typeof Deno !== "undefined"
67
- ? Deno.env.get("COLUMNS")
68
- : process?.env?.COLUMNS;
69
-
70
- if (envColumns) {
71
- const parsed = parseInt(envColumns, 10);
72
- return isNaN(parsed) ? null : parsed;
73
- }
74
-
75
- return null;
76
- } catch {
77
- return null;
78
- }
79
- }
80
-
81
- /**
82
- * Get the optimal word wrap width based on terminal detection.
83
- *
84
- * @param defaultWidth Default width to use when not in a terminal
85
- * @returns The optimal width
86
- */
87
- export function getOptimalWordWrapWidth(defaultWidth: number = 80): number {
88
- if (!isTerminal()) {
89
- return defaultWidth;
90
- }
91
-
92
- const terminalWidth = getTerminalWidth();
93
- return terminalWidth || defaultWidth;
94
- }
@@ -1,104 +0,0 @@
1
- import { suite } from "@alinea/suite";
2
- import { assertEquals } from "@std/assert/equals";
3
- import { truncateCategory } from "./truncate.ts";
4
-
5
- const test = suite(import.meta);
6
-
7
- test("truncateCategory() with no truncation", () => {
8
- const category = ["app", "server"];
9
-
10
- // Strategy false - no truncation
11
- assertEquals(
12
- truncateCategory(category, 10, ".", false),
13
- "app.server",
14
- );
15
-
16
- // Width is enough - no truncation needed
17
- assertEquals(
18
- truncateCategory(category, 20, ".", "middle"),
19
- "app.server",
20
- );
21
- });
22
-
23
- test("truncateCategory() with end truncation", () => {
24
- const category = ["app", "server", "http", "middleware"];
25
-
26
- assertEquals(
27
- truncateCategory(category, 15, ".", "end"),
28
- "app.server.htt…",
29
- );
30
-
31
- assertEquals(
32
- truncateCategory(category, 10, ".", "end"),
33
- "app.serve…",
34
- );
35
- });
36
-
37
- test("truncateCategory() with middle truncation", () => {
38
- const category = ["app", "server", "http", "middleware", "auth"];
39
-
40
- // Should keep first and last parts
41
- assertEquals(
42
- truncateCategory(category, 20, ".", "middle"),
43
- "app…auth",
44
- );
45
-
46
- // With more space, still just first…last for simplicity
47
- assertEquals(
48
- truncateCategory(category, 25, ".", "middle"),
49
- "app…auth",
50
- );
51
-
52
- // Very long category names
53
- const longCategory = [
54
- "application",
55
- "server",
56
- "http",
57
- "middleware",
58
- "authentication",
59
- ];
60
- assertEquals(
61
- truncateCategory(longCategory, 30, ".", "middle"),
62
- "application…authentication",
63
- );
64
- });
65
-
66
- test("truncateCategory() edge cases", () => {
67
- // Very small width
68
- assertEquals(
69
- truncateCategory(["app", "server"], 4, ".", "middle"),
70
- "…",
71
- );
72
-
73
- // Single segment - falls back to end truncation
74
- assertEquals(
75
- truncateCategory(["verylongcategoryname"], 10, ".", "middle"),
76
- "verylongc…",
77
- );
78
-
79
- // Two segments that are too long together - should fall back to end truncation
80
- assertEquals(
81
- truncateCategory(["verylongname", "anotherlongname"], 15, ".", "middle"),
82
- "verylongname.a…",
83
- );
84
-
85
- // Empty category
86
- assertEquals(
87
- truncateCategory([], 10, ".", "middle"),
88
- "",
89
- );
90
- });
91
-
92
- test("truncateCategory() with custom separator", () => {
93
- const category = ["app", "server", "http"];
94
-
95
- assertEquals(
96
- truncateCategory(category, 20, "::", "middle"),
97
- "app::server::http",
98
- );
99
-
100
- assertEquals(
101
- truncateCategory(category, 10, "::", "middle"),
102
- "app…http",
103
- );
104
- });
package/src/truncate.ts DELETED
@@ -1,90 +0,0 @@
1
- /**
2
- * Truncation strategies for category names.
3
- *
4
- * @since 1.0.0
5
- */
6
- export type TruncationStrategy = "middle" | "end" | false;
7
-
8
- /**
9
- * Truncates a category array to fit within a maximum width using the specified strategy.
10
- *
11
- * This function intelligently shortens long hierarchical category names while
12
- * preserving important context. The truncation behavior depends on the chosen
13
- * strategy:
14
- *
15
- * - `"middle"`: Keeps the first and last segments with "…" in between
16
- * - `"end"`: Truncates at the end with "…" suffix
17
- * - `false`: No truncation (returns full category string)
18
- *
19
- * When the category is too long even for middle truncation (first + "…" + last
20
- * exceeds maxWidth), it falls back to end truncation.
21
- *
22
- * @param category The category segments to truncate.
23
- * @param maxWidth Maximum width for the category string.
24
- * @param separator Category separator (default: ".").
25
- * @param strategy Truncation strategy to use (default: "middle").
26
- * @returns The truncated category string.
27
- *
28
- * @example
29
- * ```typescript
30
- * // Middle truncation
31
- * truncateCategory(["app", "server", "http", "auth"], 15, ".", "middle");
32
- * // Returns: "app…auth"
33
- *
34
- * // End truncation
35
- * truncateCategory(["app", "server", "http", "auth"], 15, ".", "end");
36
- * // Returns: "app.server.h…"
37
- *
38
- * // No truncation
39
- * truncateCategory(["app", "auth"], 20, ".", false);
40
- * // Returns: "app.auth"
41
- * ```
42
- *
43
- * @since 1.0.0
44
- */
45
- export function truncateCategory(
46
- category: readonly string[],
47
- maxWidth: number,
48
- separator: string = ".",
49
- strategy: TruncationStrategy = "middle",
50
- ): string {
51
- if (!strategy || maxWidth <= 0) {
52
- return category.join(separator);
53
- }
54
-
55
- const full = category.join(separator);
56
- if (full.length <= maxWidth) {
57
- return full;
58
- }
59
-
60
- // Minimum width needed for truncation with ellipsis
61
- const minWidth = 5; // e.g., "a…z"
62
- if (maxWidth < minWidth) {
63
- return "…";
64
- }
65
-
66
- if (strategy === "end") {
67
- return full.substring(0, maxWidth - 1) + "…";
68
- }
69
-
70
- // Middle truncation strategy
71
- if (category.length <= 2) {
72
- // For short categories, just truncate the end
73
- return full.substring(0, maxWidth - 1) + "…";
74
- }
75
-
76
- // Try to keep first and last segments
77
- const first = category[0];
78
- const last = category[category.length - 1];
79
- const ellipsis = "…";
80
-
81
- // Check if we can at least fit first…last
82
- const minimalLength = first.length + ellipsis.length + last.length;
83
- if (minimalLength > maxWidth) {
84
- // Even minimal format is too long, fallback to end truncation
85
- return full.substring(0, maxWidth - 1) + "…";
86
- }
87
-
88
- // For simple case with limited space, just do first…last
89
- return `${first}${ellipsis}${last}`;
90
- }
package/src/util.deno.ts DELETED
@@ -1,21 +0,0 @@
1
- export interface InspectOptions {
2
- colors?: boolean;
3
- depth?: number | null;
4
- compact?: boolean;
5
- getters?: boolean;
6
- showProxy?: boolean;
7
- [key: string]: unknown;
8
- }
9
-
10
- export function inspect(obj: unknown, options?: InspectOptions): string {
11
- if ("Deno" in globalThis) {
12
- return Deno.inspect(obj, {
13
- colors: options?.colors,
14
- depth: options?.depth ?? undefined,
15
- compact: options?.compact ?? true,
16
- });
17
- } else {
18
- const indent = options?.compact === true ? undefined : 2;
19
- return JSON.stringify(obj, null, indent);
20
- }
21
- }
package/src/util.node.ts DELETED
@@ -1,14 +0,0 @@
1
- import util from "node:util";
2
-
3
- export interface InspectOptions {
4
- colors?: boolean;
5
- depth?: number | null;
6
- compact?: boolean;
7
- getters?: boolean;
8
- showProxy?: boolean;
9
- [key: string]: unknown;
10
- }
11
-
12
- export function inspect(obj: unknown, options?: InspectOptions): string {
13
- return util.inspect(obj, options);
14
- }
package/src/util.ts DELETED
@@ -1,13 +0,0 @@
1
- export interface InspectOptions {
2
- colors?: boolean;
3
- depth?: number | null;
4
- compact?: boolean;
5
- getters?: boolean;
6
- showProxy?: boolean;
7
- [key: string]: unknown;
8
- }
9
-
10
- export function inspect(obj: unknown, options?: InspectOptions): string {
11
- const indent = options?.compact === true ? undefined : 2;
12
- return JSON.stringify(obj, null, indent);
13
- }
@@ -1,60 +0,0 @@
1
- import { suite } from "@alinea/suite";
2
- import { assertEquals } from "@std/assert/equals";
3
- import { getDisplayWidth, wcwidth } from "./wcwidth.ts";
4
-
5
- const test = suite(import.meta);
6
-
7
- test("wcwidth() basic functionality", () => {
8
- // Control characters should return -1
9
- assertEquals(wcwidth(0x0A), -1); // newline
10
- assertEquals(wcwidth(0x1B), -1); // escape
11
-
12
- // Regular ASCII characters should return 1
13
- assertEquals(wcwidth(0x41), 1); // 'A'
14
- assertEquals(wcwidth(0x20), 1); // space
15
-
16
- // Wide characters should return 2
17
- assertEquals(wcwidth(0x2728), 2); // ✨ sparkles
18
- assertEquals(wcwidth(0x274C), 2); // ❌ cross mark
19
- assertEquals(wcwidth(0x4E00), 2); // CJK ideograph
20
-
21
- // Zero-width characters should return 0
22
- assertEquals(wcwidth(0x0300), 0); // combining grave accent
23
- assertEquals(wcwidth(0xFE0F), 0); // variation selector
24
- });
25
-
26
- test("getDisplayWidth() with regular text", () => {
27
- assertEquals(getDisplayWidth("hello"), 5);
28
- assertEquals(getDisplayWidth(""), 0);
29
- assertEquals(getDisplayWidth("A"), 1);
30
- });
31
-
32
- test("getDisplayWidth() with emojis", () => {
33
- assertEquals(getDisplayWidth("✨"), 2);
34
- assertEquals(getDisplayWidth("❌"), 2);
35
- assertEquals(getDisplayWidth("🔍"), 2);
36
- assertEquals(getDisplayWidth("⚠️"), 2); // includes variation selector
37
- });
38
-
39
- test("getDisplayWidth() with mixed content", () => {
40
- assertEquals(getDisplayWidth("A✨B"), 4); // 1 + 2 + 1
41
- assertEquals(getDisplayWidth("test ❌ end"), 11); // 4 + 1 + 2 + 1 + 3
42
- });
43
-
44
- test("getDisplayWidth() with ANSI codes", () => {
45
- assertEquals(getDisplayWidth("\x1b[31mred\x1b[0m"), 3);
46
- assertEquals(getDisplayWidth("\x1b[1m\x1b[31mbold red\x1b[0m"), 8);
47
- assertEquals(getDisplayWidth("\x1b[38;2;255;0;0m✨\x1b[0m"), 2);
48
- });
49
-
50
- test("getDisplayWidth() with zero-width characters", () => {
51
- // Combining diacritical marks should not add to width
52
- assertEquals(getDisplayWidth("e\u0301"), 1); // e with acute accent
53
- assertEquals(getDisplayWidth("a\u0300"), 1); // a with grave accent
54
- });
55
-
56
- test("getDisplayWidth() with CJK characters", () => {
57
- assertEquals(getDisplayWidth("你好"), 4); // two CJK chars = 4 width
58
- assertEquals(getDisplayWidth("こんにちは"), 10); // five hiragana = 10 width
59
- assertEquals(getDisplayWidth("안녕"), 4); // two hangul = 4 width
60
- });