@heinrichb/console-toolkit 1.0.3 → 1.0.6
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 +162 -2
- package/dist/{progress.d.ts → components/progress.d.ts} +12 -12
- package/dist/{progress.js → components/progress.js} +5 -6
- package/dist/{progress.test.js → components/progress.test.js} +14 -10
- package/dist/components/spinner.d.ts +36 -0
- package/dist/components/spinner.js +33 -0
- package/dist/components/spinner.test.js +53 -0
- package/dist/core/layout.d.ts +25 -0
- package/dist/core/layout.js +55 -0
- package/dist/core/layout.test.d.ts +1 -0
- package/dist/core/layout.test.js +52 -0
- package/dist/core/printer.d.ts +33 -0
- package/dist/core/printer.js +129 -0
- package/dist/core/printer.test.d.ts +1 -0
- package/dist/core/printer.test.js +124 -0
- package/dist/core/style.d.ts +53 -0
- package/dist/core/style.js +171 -0
- package/dist/core/style.test.d.ts +1 -0
- package/dist/core/style.test.js +108 -0
- package/dist/core/types.d.ts +72 -0
- package/dist/core/types.js +4 -0
- package/dist/core/utils.d.ts +25 -0
- package/dist/core/utils.js +36 -0
- package/dist/core/utils.test.d.ts +1 -0
- package/dist/core/utils.test.js +25 -0
- package/dist/demo.d.ts +0 -4
- package/dist/demo.js +104 -43
- package/dist/index.d.ts +8 -99
- package/dist/index.js +14 -234
- package/dist/presets/ascii.d.ts +5 -0
- package/dist/presets/ascii.js +35 -0
- package/dist/presets/ascii.test.d.ts +1 -0
- package/dist/presets/ascii.test.js +21 -0
- package/package.json +46 -44
- package/dist/index.test.js +0 -126
- /package/dist/{progress.test.d.ts → components/progress.test.d.ts} +0 -0
- /package/dist/{index.test.d.ts → components/spinner.test.d.ts} +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { interpolateColor } from "../core/style";
|
|
2
|
+
// -----------------
|
|
3
|
+
// Presets
|
|
4
|
+
// -----------------
|
|
5
|
+
/**
|
|
6
|
+
* Returns the classic Dragon ASCII art as PrintLines with a vertical color gradient.
|
|
7
|
+
*/
|
|
8
|
+
export function getDragon(startColor = "#EF4444", endColor = "#F59E0B") {
|
|
9
|
+
const rawDragon = [
|
|
10
|
+
" ^ ^",
|
|
11
|
+
" / \\ //\\",
|
|
12
|
+
" |\\___/| / \\// .\\",
|
|
13
|
+
" /O O \\__ / // | \\ \\",
|
|
14
|
+
"/ / \\_/_/ // | \\ \\",
|
|
15
|
+
"@___@' \\_// // | \\ \\ ",
|
|
16
|
+
" | \\_// // | \\ \\ ",
|
|
17
|
+
" | \\/// | \\ \\ ",
|
|
18
|
+
" _|_ / ) // | \\ _\\",
|
|
19
|
+
" '/,_ _ _/ ( ; -. | _ _\\.-~ .-~~~^-.",
|
|
20
|
+
" ,-{ _ `-.|.-~-. .~ `.",
|
|
21
|
+
" '/\\ / ~-. _ .-~ .-~^-. \\",
|
|
22
|
+
" `. { } / \\ \\",
|
|
23
|
+
" .----~-\\. \\-' .~ \\ `. \\^-.",
|
|
24
|
+
" ///.----..> c \\ _ -~ `. ^-` ^-_",
|
|
25
|
+
" ///-._ _ _ _ _ _ _}^ - - - - ~ ~--, .-~",
|
|
26
|
+
" /.-'"
|
|
27
|
+
];
|
|
28
|
+
// Note: We could use PrintBlock's vertical gradient feature instead of manual interpolation here.
|
|
29
|
+
// But to keep logic similar and return lines directly, we manual interp.
|
|
30
|
+
return rawDragon.map((text, i) => {
|
|
31
|
+
const factor = rawDragon.length <= 1 ? 0 : i / (rawDragon.length - 1);
|
|
32
|
+
const colorStyle = interpolateColor(startColor, endColor, factor);
|
|
33
|
+
return { segments: [{ text, style: { color: colorStyle } }] };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { expect, test, describe } from "bun:test";
|
|
2
|
+
import { getDragon } from "./ascii";
|
|
3
|
+
describe("Presets", () => {
|
|
4
|
+
test("getDragon returns valid array", () => {
|
|
5
|
+
const lines = getDragon();
|
|
6
|
+
expect(lines.length).toBeGreaterThan(0);
|
|
7
|
+
const firstSegmentStyle = lines[0].segments[0].style;
|
|
8
|
+
expect(firstSegmentStyle).toBeDefined();
|
|
9
|
+
if (firstSegmentStyle) {
|
|
10
|
+
// Check that color is defined and is hex
|
|
11
|
+
expect(firstSegmentStyle.color).toBeDefined();
|
|
12
|
+
if (typeof firstSegmentStyle.color === "string") {
|
|
13
|
+
expect(firstSegmentStyle.color.startsWith("#")).toBe(true);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
// Fail if it's not a string (e.g. array)
|
|
17
|
+
expect(typeof firstSegmentStyle.color).toBe("string");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
package/package.json
CHANGED
|
@@ -1,44 +1,46 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@heinrichb/console-toolkit",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "A versatile TypeScript utility library for enhanced console logging, formatting, and layout management.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"module": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"publishConfig": {
|
|
10
|
-
"access": "public"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist"
|
|
14
|
-
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"demo": "bun run scripts/run-demo.ts",
|
|
17
|
-
"test": "bun run build && bun test src",
|
|
18
|
-
"test:coverage": "bun run build && bun test src --coverage --coverage-threshold=100",
|
|
19
|
-
"build": "bun x tsc",
|
|
20
|
-
"publish": "npm publish",
|
|
21
|
-
"format:check": "prettier --check \"src/**/*.{ts,json,md}\"",
|
|
22
|
-
"format:write": "prettier --write \"src/**/*.{ts,json,md}\"",
|
|
23
|
-
"lint": "eslint \"src/**/*.ts\"",
|
|
24
|
-
"tree": "tree -a -I '.git|node_modules|dist'"
|
|
25
|
-
},
|
|
26
|
-
"prettier": {
|
|
27
|
-
"printWidth": 128,
|
|
28
|
-
"tabWidth": 2,
|
|
29
|
-
"trailingComma": "none",
|
|
30
|
-
"bracketSameLine": true,
|
|
31
|
-
"semi": true,
|
|
32
|
-
"singleQuote": false,
|
|
33
|
-
"useTabs": true
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@eslint/js": "^
|
|
37
|
-
"@types/bun": "
|
|
38
|
-
"eslint": "^
|
|
39
|
-
"eslint-config-prettier": "^10.1.8",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@heinrichb/console-toolkit",
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"description": "A versatile TypeScript utility library for enhanced console logging, formatting, and layout management.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"demo": "bun run scripts/run-demo.ts",
|
|
17
|
+
"test": "bun run build && bun test src",
|
|
18
|
+
"test:coverage": "bun run build && bun test src --coverage --coverage-threshold=100",
|
|
19
|
+
"build": "bun x tsc",
|
|
20
|
+
"publish": "npm publish",
|
|
21
|
+
"format:check": "prettier --check \"src/**/*.{ts,json,md}\"",
|
|
22
|
+
"format:write": "prettier --write \"src/**/*.{ts,json,md}\"",
|
|
23
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
24
|
+
"tree": "tree -a -I '.git|node_modules|dist'"
|
|
25
|
+
},
|
|
26
|
+
"prettier": {
|
|
27
|
+
"printWidth": 128,
|
|
28
|
+
"tabWidth": 2,
|
|
29
|
+
"trailingComma": "none",
|
|
30
|
+
"bracketSameLine": true,
|
|
31
|
+
"semi": true,
|
|
32
|
+
"singleQuote": false,
|
|
33
|
+
"useTabs": true
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@eslint/js": "^10.0.1",
|
|
37
|
+
"@types/bun": "^1.3.9",
|
|
38
|
+
"eslint": "^10.0.2",
|
|
39
|
+
"eslint-config-prettier": "^10.1.8",
|
|
40
|
+
"globals": "^17.3.0",
|
|
41
|
+
"jiti": "^2.6.1",
|
|
42
|
+
"prettier": "^3.8.1",
|
|
43
|
+
"typescript": "^5.9.3",
|
|
44
|
+
"typescript-eslint": "^8.56.1"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/index.test.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { expect, test, describe, spyOn, afterEach } from "bun:test";
|
|
2
|
-
import { hexToRgb, rgbToAnsi, hexToAnsi, interpolateColor, getLineLength, computeMaxWidth, padLine, Printer, printDualColumn, getDragonLines, mergeColumns, resolveStyle } from "./index";
|
|
3
|
-
const lineA = { segments: [{ text: "Hello", style: [] }] };
|
|
4
|
-
const lineB = { segments: [{ text: "World!!", style: [] }] };
|
|
5
|
-
describe("Color Utilities", () => {
|
|
6
|
-
test("hexToRgb converts correctly", () => {
|
|
7
|
-
expect(hexToRgb("#FFFFFF")).toEqual({ r: 255, g: 255, b: 255 });
|
|
8
|
-
expect(hexToRgb("#000000")).toEqual({ r: 0, g: 0, b: 0 });
|
|
9
|
-
});
|
|
10
|
-
test("hexToRgb throws on invalid hex", () => {
|
|
11
|
-
expect(() => hexToRgb("invalid")).toThrow("Invalid hex color.");
|
|
12
|
-
expect(() => hexToRgb("#FFF")).toThrow("Invalid hex color.");
|
|
13
|
-
});
|
|
14
|
-
test("rgbToAnsi converts correctly", () => {
|
|
15
|
-
expect(rgbToAnsi(255, 255, 255)).toBe("\x1b[38;2;255;255;255m");
|
|
16
|
-
expect(rgbToAnsi(0, 0, 0)).toBe("\x1b[38;2;0;0;0m");
|
|
17
|
-
});
|
|
18
|
-
test("hexToAnsi converts hex string directly to ANSI", () => {
|
|
19
|
-
expect(hexToAnsi("#FFFFFF")).toBe("\x1b[38;2;255;255;255m");
|
|
20
|
-
expect(hexToAnsi("#000000")).toBe("\x1b[38;2;0;0;0m");
|
|
21
|
-
expect(hexToAnsi("#FF0000")).toBe("\x1b[38;2;255;0;0m");
|
|
22
|
-
});
|
|
23
|
-
test("interpolateColor returns hex string", () => {
|
|
24
|
-
const start = "#000000";
|
|
25
|
-
const end = "#ffffff";
|
|
26
|
-
expect(interpolateColor(start, end, 0.5)).toBe("#808080");
|
|
27
|
-
});
|
|
28
|
-
test("interpolateColor clamps factors", () => {
|
|
29
|
-
expect(interpolateColor("#000000", "#ffffff", -1)).toBe("#000000");
|
|
30
|
-
expect(interpolateColor("#000000", "#ffffff", 2)).toBe("#ffffff");
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
describe("Style Resolution", () => {
|
|
34
|
-
test("resolveStyle handles standard colors", () => {
|
|
35
|
-
expect(resolveStyle("red")).toBe("\x1b[31m");
|
|
36
|
-
expect(resolveStyle("blue")).toBe("\x1b[34m");
|
|
37
|
-
});
|
|
38
|
-
test("resolveStyle handles modifiers", () => {
|
|
39
|
-
expect(resolveStyle("bold")).toBe("\x1b[1m");
|
|
40
|
-
expect(resolveStyle("reset")).toBe("\x1b[0m");
|
|
41
|
-
});
|
|
42
|
-
test("resolveStyle handles hex colors", () => {
|
|
43
|
-
expect(resolveStyle("#FF0000")).toBe("\x1b[38;2;255;0;0m");
|
|
44
|
-
});
|
|
45
|
-
test("resolveStyle handles arrays of styles", () => {
|
|
46
|
-
expect(resolveStyle(["bold", "red"])).toBe("\x1b[1m\x1b[31m");
|
|
47
|
-
});
|
|
48
|
-
test("resolveStyle passes through raw strings", () => {
|
|
49
|
-
const raw = "\x1b[31m";
|
|
50
|
-
expect(resolveStyle(raw)).toBe(raw);
|
|
51
|
-
expect(resolveStyle("unknown")).toBe("unknown");
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe("Line Utilities", () => {
|
|
55
|
-
test("getLineLength calculates correctly", () => {
|
|
56
|
-
expect(getLineLength(lineA)).toBe(5);
|
|
57
|
-
expect(getLineLength(lineB)).toBe(7);
|
|
58
|
-
});
|
|
59
|
-
test("computeMaxWidth finds the longest line", () => {
|
|
60
|
-
expect(computeMaxWidth([lineA, lineB])).toBe(7);
|
|
61
|
-
expect(computeMaxWidth([])).toBe(0);
|
|
62
|
-
});
|
|
63
|
-
test("padLine adds padding when needed", () => {
|
|
64
|
-
const padded = padLine(lineA, 10, "red");
|
|
65
|
-
expect(getLineLength(padded)).toBe(10);
|
|
66
|
-
expect(padded.segments[1].style).toBe("red");
|
|
67
|
-
expect(padded.segments[1].text).toBe(" ");
|
|
68
|
-
});
|
|
69
|
-
test("padLine does nothing if line is already wide enough", () => {
|
|
70
|
-
const ignored = padLine(lineB, 5, "red");
|
|
71
|
-
expect(getLineLength(ignored)).toBe(7);
|
|
72
|
-
expect(ignored.segments.length).toBe(1);
|
|
73
|
-
});
|
|
74
|
-
test("mergeColumns handles asymmetric column lengths", () => {
|
|
75
|
-
const merged = mergeColumns([lineA], [lineB, lineB], 10, " | ", "");
|
|
76
|
-
expect(merged.length).toBe(2);
|
|
77
|
-
expect(getLineLength(merged[1])).toBe(10 + 3 + 7);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
describe("Printer & Layout", () => {
|
|
81
|
-
const stdoutSpy = spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
82
|
-
afterEach(() => {
|
|
83
|
-
stdoutSpy.mockClear();
|
|
84
|
-
});
|
|
85
|
-
test("Printer.print outputs to console with resolved styles", () => {
|
|
86
|
-
const printer = new Printer();
|
|
87
|
-
printer.print([{ segments: [{ text: "Test", style: "red" }] }]);
|
|
88
|
-
expect(stdoutSpy).toHaveBeenCalled();
|
|
89
|
-
const output = stdoutSpy.mock.calls[0][0];
|
|
90
|
-
expect(output).toContain("\x1b[31mTest");
|
|
91
|
-
});
|
|
92
|
-
test("Printer handles interactive clearing", () => {
|
|
93
|
-
const printer = new Printer({ interactive: true });
|
|
94
|
-
printer.print([{ segments: [{ text: "L1", style: "" }] }]);
|
|
95
|
-
printer.print([{ segments: [{ text: "L2", style: "" }] }]);
|
|
96
|
-
expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining("\x1b[1A\x1b[2K\r"));
|
|
97
|
-
});
|
|
98
|
-
test("Printer optimizes clearing of multiple lines", () => {
|
|
99
|
-
const printer = new Printer({ interactive: true });
|
|
100
|
-
printer.print([
|
|
101
|
-
{ segments: [{ text: "L1", style: "" }] },
|
|
102
|
-
{ segments: [{ text: "L2", style: "" }] },
|
|
103
|
-
{ segments: [{ text: "L3", style: "" }] }
|
|
104
|
-
]);
|
|
105
|
-
stdoutSpy.mockClear();
|
|
106
|
-
printer.print([{ segments: [{ text: "New", style: "" }] }]);
|
|
107
|
-
const clearSeq = "\x1b[1A\x1b[2K\r";
|
|
108
|
-
const expectedClear = clearSeq.repeat(3);
|
|
109
|
-
expect(stdoutSpy).toHaveBeenCalledTimes(1);
|
|
110
|
-
const callArg = stdoutSpy.mock.calls[0][0];
|
|
111
|
-
expect(callArg.startsWith(expectedClear)).toBe(true);
|
|
112
|
-
});
|
|
113
|
-
test("printDualColumn executes correctly", () => {
|
|
114
|
-
printDualColumn([lineA], [lineB]);
|
|
115
|
-
expect(stdoutSpy).toHaveBeenCalled();
|
|
116
|
-
const output = stdoutSpy.mock.calls[0][0];
|
|
117
|
-
expect(output).toContain("Hello");
|
|
118
|
-
expect(output).toContain("World!!");
|
|
119
|
-
});
|
|
120
|
-
test("getDragonLines returns valid array", () => {
|
|
121
|
-
const lines = getDragonLines();
|
|
122
|
-
expect(lines.length).toBeGreaterThan(0);
|
|
123
|
-
const firstSegmentStyle = lines[0].segments[0].style;
|
|
124
|
-
expect(firstSegmentStyle.startsWith("#")).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
File without changes
|
|
File without changes
|