@reliverse/relico 1.4.1 → 2.2.7
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 +16 -20
- package/dist/mod.d.ts +60 -0
- package/dist/mod.js +455 -0
- package/package.json +17 -20
- package/.vscode/extensions.json +0 -8
- package/.vscode/settings.json +0 -45
- package/LICENSE +0 -21
- package/biome.json +0 -86
- package/examples/benchmarks/bundle-size.ts +0 -143
- package/examples/benchmarks/config.example.ts +0 -55
- package/examples/benchmarks/config.ts +0 -93
- package/examples/benchmarks/performance.ts +0 -172
- package/examples/core.ts +0 -62
- package/reliverse.ts +0 -399
- package/reltypes.ts +0 -1490
- package/src/mod.ts +0 -537
- package/tsconfig.json +0 -33
package/README.md
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
# Reliverse Relico
|
|
2
2
|
|
|
3
|
-
> @reliverse/
|
|
3
|
+
> @reliverse/relico is a themeable, chainable, typed, truecolor-powered terminal styling toolkit — built for humans, not just terminals. It makes your CLI output beautiful, accessible, and expressive — with developer-first ergonomics, smart config, and blazing-fast performance.
|
|
4
4
|
|
|
5
|
-
[sponsor](https://github.com/sponsors/blefnk) — [discord](https://discord.gg/Pb8uKbwpsJ) — [repo](https://github.com/reliverse/relico) — [npm](https://npmjs.com/@reliverse/
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
‼️Attention! The latest implementation of `@reliverse/relico` now lives in the framework repository for building libraries — `@reliverse/dler`. Please use [@reliverse/dler-colors](https://github.com/reliverse/dler/tree/main/packages/colors) instead of `@reliverse/relico`.
|
|
5
|
+
[sponsor](https://github.com/sponsors/blefnk) — [discord](https://discord.gg/Pb8uKbwpsJ) — [repo](https://github.com/reliverse/relico) — [npm](https://npmjs.com/@reliverse/relico)
|
|
10
6
|
|
|
11
7
|
## Why Relico?
|
|
12
8
|
|
|
@@ -28,7 +24,7 @@ Because terminal styling shouldn't feel like duct tape. **Relico** brings design
|
|
|
28
24
|
## Installation
|
|
29
25
|
|
|
30
26
|
```bash
|
|
31
|
-
bun add @reliverse/
|
|
27
|
+
bun add @reliverse/relico
|
|
32
28
|
# bun • pnpm • yarn • npm
|
|
33
29
|
```
|
|
34
30
|
|
|
@@ -95,7 +91,7 @@ The readme will be updated soon.
|
|
|
95
91
|
**If you're end-user OR developer, create `relico.config.ts` in your root**:
|
|
96
92
|
|
|
97
93
|
```ts
|
|
98
|
-
import { defineConfig } from "@reliverse/
|
|
94
|
+
import { defineConfig } from "@reliverse/relico";
|
|
99
95
|
|
|
100
96
|
export default defineConfig({
|
|
101
97
|
colorLevel: 3, // 0 = off, 1 = basic, 2 = 256, 3 = truecolor
|
|
@@ -112,7 +108,7 @@ export default defineConfig({
|
|
|
112
108
|
**If you're developer, initialize in your app (optional)**:
|
|
113
109
|
|
|
114
110
|
```ts
|
|
115
|
-
import { initUserConfig, re } from "@reliverse/
|
|
111
|
+
import { initUserConfig, re } from "@reliverse/relico";
|
|
116
112
|
|
|
117
113
|
// Use this to override Relico's
|
|
118
114
|
// default settings for your app
|
|
@@ -124,7 +120,7 @@ console.log(re.info("Custom config loaded!"));
|
|
|
124
120
|
## API Sneak Peek
|
|
125
121
|
|
|
126
122
|
```ts
|
|
127
|
-
import { re, rgb } from "@reliverse/
|
|
123
|
+
import { re, rgb } from "@reliverse/relico";
|
|
128
124
|
|
|
129
125
|
console.log(re.red("Red!"));
|
|
130
126
|
console.log(re.bold(re.green("Bold green")));
|
|
@@ -175,7 +171,7 @@ console.log(boldRed("This text is bold and red"));
|
|
|
175
171
|
### Want to Get Only Certain Colors?
|
|
176
172
|
|
|
177
173
|
```ts
|
|
178
|
-
import type { DefaultColorKeys } from "@reliverse/
|
|
174
|
+
import type { DefaultColorKeys } from "@reliverse/relico";
|
|
179
175
|
const brandColors: DefaultColorKeys[] = ["magentaBright", "maroon"];
|
|
180
176
|
```
|
|
181
177
|
|
|
@@ -184,7 +180,7 @@ const brandColors: DefaultColorKeys[] = ["magentaBright", "maroon"];
|
|
|
184
180
|
Relico detects your terminal's capability:
|
|
185
181
|
|
|
186
182
|
```ts
|
|
187
|
-
import { colorSupport } from "@reliverse/
|
|
183
|
+
import { colorSupport } from "@reliverse/relico";
|
|
188
184
|
|
|
189
185
|
console.log(colorSupport.terminalName); // iTerm2, Windows Terminal, etc.
|
|
190
186
|
console.log(colorSupport.level); // 0, 1, 2, or 3
|
|
@@ -200,7 +196,7 @@ console.log(colorSupport.level); // 0, 1, 2, or 3
|
|
|
200
196
|
### Custom RGB + Hex
|
|
201
197
|
|
|
202
198
|
```ts
|
|
203
|
-
import { rgb, bgHex, hex } from "@reliverse/
|
|
199
|
+
import { rgb, bgHex, hex } from "@reliverse/relico";
|
|
204
200
|
|
|
205
201
|
console.log(rgb(255, 105, 180)("Hot pink"));
|
|
206
202
|
console.log(bgHex("#1e90ff")("Dodger blue background"));
|
|
@@ -209,7 +205,7 @@ console.log(bgHex("#1e90ff")("Dodger blue background"));
|
|
|
209
205
|
### Gradients & Rainbow
|
|
210
206
|
|
|
211
207
|
```ts
|
|
212
|
-
import { gradient, multiGradient, rainbow } from "@reliverse/
|
|
208
|
+
import { gradient, multiGradient, rainbow } from "@reliverse/relico";
|
|
213
209
|
|
|
214
210
|
console.log(rainbow("🎉 Woohoo!"));
|
|
215
211
|
console.log(gradient("From red to blue", "#ff0000", "#0000ff"));
|
|
@@ -222,7 +218,7 @@ This function allows you to combine multiple color formatters into a single form
|
|
|
222
218
|
## Basic Usage
|
|
223
219
|
|
|
224
220
|
```typescript
|
|
225
|
-
import { re, chain } from "@reliverse/
|
|
221
|
+
import { re, chain } from "@reliverse/relico";
|
|
226
222
|
|
|
227
223
|
// Create a custom style that combines bold and red text
|
|
228
224
|
const boldRed = chain(re.bold, re.red);
|
|
@@ -240,7 +236,7 @@ console.log(importantError("CRITICAL ERROR: System failure"));
|
|
|
240
236
|
## Creating Theme Combinations
|
|
241
237
|
|
|
242
238
|
```typescript
|
|
243
|
-
import { re, chain } from "@reliverse/
|
|
239
|
+
import { re, chain } from "@reliverse/relico";
|
|
244
240
|
|
|
245
241
|
// Create themed message styles
|
|
246
242
|
const successStyle = chain(re.bold, re.green);
|
|
@@ -258,7 +254,7 @@ console.log(warnStyle("⚠ API rate limit approaching"));
|
|
|
258
254
|
## Custom RGB Combinations
|
|
259
255
|
|
|
260
256
|
```typescript
|
|
261
|
-
import { re, rgb, bgRgb, chain } from "@reliverse/
|
|
257
|
+
import { re, rgb, bgRgb, chain } from "@reliverse/relico";
|
|
262
258
|
|
|
263
259
|
// Create a custom color scheme with RGB values
|
|
264
260
|
const customHeader = chain(
|
|
@@ -282,7 +278,7 @@ console.log(danger("Danger: High voltage detected!"));
|
|
|
282
278
|
The `chain()` function automatically handles multiline text to prevent style leakage:
|
|
283
279
|
|
|
284
280
|
```typescript
|
|
285
|
-
import { re, chain } from "@reliverse/
|
|
281
|
+
import { re, chain } from "@reliverse/relico";
|
|
286
282
|
|
|
287
283
|
const highlight = chain(re.bgYellow, re.black, re.bold);
|
|
288
284
|
|
|
@@ -297,7 +293,7 @@ console.log(highlight(multilineText));
|
|
|
297
293
|
## Creating a Simple Logger
|
|
298
294
|
|
|
299
295
|
```typescript
|
|
300
|
-
import { re, chain } from "@reliverse/
|
|
296
|
+
import { re, chain } from "@reliverse/relico";
|
|
301
297
|
|
|
302
298
|
// Create logger styles
|
|
303
299
|
const styles = {
|
|
@@ -320,7 +316,7 @@ const logger = {
|
|
|
320
316
|
// Usage
|
|
321
317
|
logger.info("Application started");
|
|
322
318
|
logger.success("Data loaded successfully");
|
|
323
|
-
logger.
|
|
319
|
+
logger.warn("Cache expired, refreshing data");
|
|
324
320
|
logger.error("Failed to connect to database");
|
|
325
321
|
logger.debug("Request payload: " + JSON.stringify({id: 123}));
|
|
326
322
|
```
|
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
type ColorLevel = 0 | 1 | 2 | 3;
|
|
2
|
+
interface Rgb {
|
|
3
|
+
r: number;
|
|
4
|
+
g: number;
|
|
5
|
+
b: number;
|
|
6
|
+
}
|
|
7
|
+
type SgrOp = {
|
|
8
|
+
kind: "style";
|
|
9
|
+
open: number[];
|
|
10
|
+
} | {
|
|
11
|
+
kind: "fg-basic";
|
|
12
|
+
idx: number;
|
|
13
|
+
bright: boolean;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "bg-basic";
|
|
16
|
+
idx: number;
|
|
17
|
+
bright: boolean;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "fg-256";
|
|
20
|
+
code: number;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "bg-256";
|
|
23
|
+
code: number;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "fg-true";
|
|
26
|
+
rgb: Rgb;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "bg-true";
|
|
29
|
+
rgb: Rgb;
|
|
30
|
+
};
|
|
31
|
+
type ApplyInput = string | number;
|
|
32
|
+
type FormatCallable = ((input: ApplyInput) => string) & {
|
|
33
|
+
readonly [OP_SYMBOL]: SgrOp[];
|
|
34
|
+
};
|
|
35
|
+
export type BaseColorName = "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "gray" | "orange" | "pink" | "purple" | "teal" | "lime" | "brown" | "navy" | "maroon" | "olive" | "silver";
|
|
36
|
+
export type ColorName = BaseColorName | BrightColorName | BgColorName;
|
|
37
|
+
export type BrightColorName = "blackBright" | "redBright" | "greenBright" | "yellowBright" | "blueBright" | "magentaBright" | "cyanBright" | "whiteBright" | "orangeBright" | "pinkBright" | "purpleBright" | "tealBright" | "limeBright" | "brownBright" | "navyBright" | "maroonBright" | "oliveBright" | "silverBright";
|
|
38
|
+
export type BgColorName = `bg${Capitalize<BaseColorName>}` | `bg${Capitalize<BrightColorName>}`;
|
|
39
|
+
export type ReStyleKey = "reset" | "bold" | "dim" | "italic" | "underline" | "inverse" | "hidden" | "strikethrough";
|
|
40
|
+
export type Re = FormatCallable & {
|
|
41
|
+
readonly [K in ReStyleKey]: Re;
|
|
42
|
+
} & {
|
|
43
|
+
readonly [K in ColorName]: Re;
|
|
44
|
+
} & {
|
|
45
|
+
readonly [K in BgColorName]: Re;
|
|
46
|
+
};
|
|
47
|
+
declare const OP_SYMBOL: unique symbol;
|
|
48
|
+
export declare const setColorLevel: (level: ColorLevel) => void;
|
|
49
|
+
export declare const re: Re;
|
|
50
|
+
export declare const chain: (...parts: FormatCallable[]) => Re;
|
|
51
|
+
type BunColorInput = {
|
|
52
|
+
r: number;
|
|
53
|
+
g: number;
|
|
54
|
+
b: number;
|
|
55
|
+
a?: number;
|
|
56
|
+
} | [number, number, number] | [number, number, number, number] | string | number | {
|
|
57
|
+
toString(): string;
|
|
58
|
+
};
|
|
59
|
+
export declare const color: (input: BunColorInput, isBg?: boolean) => Re;
|
|
60
|
+
export {};
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
const RESET = "\x1B[0m";
|
|
2
|
+
const OP_SYMBOL = /* @__PURE__ */ Symbol("re.ops");
|
|
3
|
+
const COLOR_LEVEL_OFF = 0;
|
|
4
|
+
const COLOR_LEVEL_BASIC = 1;
|
|
5
|
+
const COLOR_LEVEL_256 = 2;
|
|
6
|
+
const COLOR_LEVEL_TRUECOLOR = 3;
|
|
7
|
+
const MIN_BYTE = 0;
|
|
8
|
+
const MAX_BYTE = 255;
|
|
9
|
+
const WHITE_RGB = 255;
|
|
10
|
+
const ANSI_256_GRAYSCALE_MIN = 8;
|
|
11
|
+
const ANSI_256_GRAYSCALE_MAX = 248;
|
|
12
|
+
const ANSI_256_BASE_OFFSET = 16;
|
|
13
|
+
const ANSI_256_GRAYSCALE_BASE = 232;
|
|
14
|
+
const ANSI_256_GRAYSCALE_RANGE = 247;
|
|
15
|
+
const ANSI_256_GRAYSCALE_STEPS = 24;
|
|
16
|
+
const ANSI_256_BRIGHT_THRESHOLD = 231;
|
|
17
|
+
const ANSI_256_RGB_LEVELS = 5;
|
|
18
|
+
const ANSI_256_RGB_RED_MULTIPLIER = 36;
|
|
19
|
+
const ANSI_256_RGB_GREEN_MULTIPLIER = 6;
|
|
20
|
+
const SGR_FG_BASE = 30;
|
|
21
|
+
const SGR_BG_BASE = 40;
|
|
22
|
+
const SGR_FG_BRIGHT_BASE = 90;
|
|
23
|
+
const SGR_BG_BRIGHT_BASE = 100;
|
|
24
|
+
const SGR_RESET = 0;
|
|
25
|
+
const SGR_BOLD = 1;
|
|
26
|
+
const SGR_DIM = 2;
|
|
27
|
+
const SGR_ITALIC = 3;
|
|
28
|
+
const SGR_UNDERLINE = 4;
|
|
29
|
+
const SGR_INVERSE = 7;
|
|
30
|
+
const SGR_HIDDEN = 8;
|
|
31
|
+
const SGR_STRIKETHROUGH = 9;
|
|
32
|
+
const HEX_RADIX = 16;
|
|
33
|
+
const BRIGHT_SUFFIX_LENGTH = 6;
|
|
34
|
+
const BG_PREFIX_LENGTH = 2;
|
|
35
|
+
const BRIGHT_MIX_FACTOR = 0.25;
|
|
36
|
+
const BRIGHT_SUFFIX = "Bright";
|
|
37
|
+
const IS_BUN = typeof process !== "undefined" && process.versions?.bun !== void 0 && typeof Bun !== "undefined" && typeof Bun.color === "function";
|
|
38
|
+
let CURRENT_LEVEL = COLOR_LEVEL_TRUECOLOR;
|
|
39
|
+
export const setColorLevel = (level) => {
|
|
40
|
+
if (level !== COLOR_LEVEL_OFF && level !== COLOR_LEVEL_BASIC && level !== COLOR_LEVEL_256 && level !== COLOR_LEVEL_TRUECOLOR) {
|
|
41
|
+
throw new Error("Invalid color level");
|
|
42
|
+
}
|
|
43
|
+
CURRENT_LEVEL = level;
|
|
44
|
+
};
|
|
45
|
+
const clampByte = (n) => {
|
|
46
|
+
if (n <= MIN_BYTE || !Number.isFinite(n)) return MIN_BYTE;
|
|
47
|
+
if (n >= MAX_BYTE) return MAX_BYTE;
|
|
48
|
+
return Math.round(n);
|
|
49
|
+
};
|
|
50
|
+
const BASIC8 = Object.freeze([
|
|
51
|
+
Object.freeze({ r: 0, g: 0, b: 0 }),
|
|
52
|
+
Object.freeze({ r: 205, g: 0, b: 0 }),
|
|
53
|
+
Object.freeze({ r: 0, g: 205, b: 0 }),
|
|
54
|
+
Object.freeze({ r: 205, g: 205, b: 0 }),
|
|
55
|
+
Object.freeze({ r: 0, g: 0, b: 238 }),
|
|
56
|
+
Object.freeze({ r: 205, g: 0, b: 205 }),
|
|
57
|
+
Object.freeze({ r: 0, g: 205, b: 205 }),
|
|
58
|
+
Object.freeze({ r: 229, g: 229, b: 229 })
|
|
59
|
+
]);
|
|
60
|
+
const sgrCache = /* @__PURE__ */ new Map();
|
|
61
|
+
const sgr = (codes) => {
|
|
62
|
+
if (codes.length === 1) {
|
|
63
|
+
const code = codes[0];
|
|
64
|
+
const cached2 = sgrCache.get(String(code));
|
|
65
|
+
if (cached2) return cached2;
|
|
66
|
+
const seq2 = `\x1B[${code}m`;
|
|
67
|
+
sgrCache.set(String(code), seq2);
|
|
68
|
+
return seq2;
|
|
69
|
+
}
|
|
70
|
+
const key = codes.join(";");
|
|
71
|
+
const cached = sgrCache.get(key);
|
|
72
|
+
if (cached) return cached;
|
|
73
|
+
const seq = `\x1B[${key}m`;
|
|
74
|
+
sgrCache.set(key, seq);
|
|
75
|
+
return seq;
|
|
76
|
+
};
|
|
77
|
+
const nearestBasicIndex = (rgb) => {
|
|
78
|
+
if (IS_BUN) {
|
|
79
|
+
const ansiStr = Bun.color(rgb, "ansi-16");
|
|
80
|
+
if (ansiStr) {
|
|
81
|
+
const match = ansiStr.match(/38;5;(\d+)/);
|
|
82
|
+
if (match?.[1]) {
|
|
83
|
+
return Number.parseInt(match[1], 10) & 7;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
let best = 0;
|
|
88
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
89
|
+
for (let i = 0; i < 8; i++) {
|
|
90
|
+
const c = BASIC8[i];
|
|
91
|
+
const dr = c.r - rgb.r;
|
|
92
|
+
const dg = c.g - rgb.g;
|
|
93
|
+
const db = c.b - rgb.b;
|
|
94
|
+
if (dr === 0 && dg === 0 && db === 0) return i;
|
|
95
|
+
const d = dr * dr + dg * dg + db * db;
|
|
96
|
+
if (d < bestDist) {
|
|
97
|
+
bestDist = d;
|
|
98
|
+
best = i;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return best;
|
|
102
|
+
};
|
|
103
|
+
const rgbToAnsi256 = (rgb) => {
|
|
104
|
+
if (IS_BUN) {
|
|
105
|
+
const ansiStr = Bun.color(rgb, "ansi-256");
|
|
106
|
+
if (ansiStr) {
|
|
107
|
+
const match = ansiStr.match(/38;5;(\d+)/);
|
|
108
|
+
if (match?.[1]) {
|
|
109
|
+
return Number.parseInt(match[1], 10);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (rgb.r === rgb.g && rgb.g === rgb.b) {
|
|
114
|
+
if (rgb.r < ANSI_256_GRAYSCALE_MIN) return ANSI_256_BASE_OFFSET;
|
|
115
|
+
if (rgb.r > ANSI_256_GRAYSCALE_MAX) return ANSI_256_BRIGHT_THRESHOLD;
|
|
116
|
+
const step = Math.round(
|
|
117
|
+
(rgb.r - ANSI_256_GRAYSCALE_MIN) / ANSI_256_GRAYSCALE_RANGE * ANSI_256_GRAYSCALE_STEPS
|
|
118
|
+
);
|
|
119
|
+
return ANSI_256_GRAYSCALE_BASE + step;
|
|
120
|
+
}
|
|
121
|
+
const r = (rgb.r * ANSI_256_RGB_LEVELS + 127) / MAX_BYTE | 0;
|
|
122
|
+
const g = (rgb.g * ANSI_256_RGB_LEVELS + 127) / MAX_BYTE | 0;
|
|
123
|
+
const b = (rgb.b * ANSI_256_RGB_LEVELS + 127) / MAX_BYTE | 0;
|
|
124
|
+
return ANSI_256_BASE_OFFSET + r * ANSI_256_RGB_RED_MULTIPLIER + g * ANSI_256_RGB_GREEN_MULTIPLIER + b;
|
|
125
|
+
};
|
|
126
|
+
const NAMED_COLORS = {
|
|
127
|
+
black: "#000000",
|
|
128
|
+
red: "#ff0000",
|
|
129
|
+
green: "#00ff00",
|
|
130
|
+
yellow: "#ffff00",
|
|
131
|
+
blue: "#0000ff",
|
|
132
|
+
magenta: "#ff00ff",
|
|
133
|
+
cyan: "#00ffff",
|
|
134
|
+
white: "#ffffff",
|
|
135
|
+
gray: "#808080",
|
|
136
|
+
orange: "#ffa500",
|
|
137
|
+
pink: "#ffc0cb",
|
|
138
|
+
purple: "#800080",
|
|
139
|
+
teal: "#008080",
|
|
140
|
+
lime: "#00ff00",
|
|
141
|
+
brown: "#a52a2a",
|
|
142
|
+
navy: "#000080",
|
|
143
|
+
maroon: "#800000",
|
|
144
|
+
olive: "#808000",
|
|
145
|
+
silver: "#c0c0c0"
|
|
146
|
+
};
|
|
147
|
+
const rgbCache = /* @__PURE__ */ new Map();
|
|
148
|
+
for (const name of ["black", "white", "red", "green", "blue"]) {
|
|
149
|
+
const hex = NAMED_COLORS[name];
|
|
150
|
+
if (hex) {
|
|
151
|
+
const clean = hex.slice(1);
|
|
152
|
+
const r = Number.parseInt(clean.slice(0, 2), HEX_RADIX);
|
|
153
|
+
const g = Number.parseInt(clean.slice(2, 4), HEX_RADIX);
|
|
154
|
+
const b = Number.parseInt(clean.slice(4, 6), HEX_RADIX);
|
|
155
|
+
rgbCache.set(name, Object.freeze({ r, g, b }));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const mixWithWhite = (rgb, factor) => {
|
|
159
|
+
const invFactor = 1 - factor;
|
|
160
|
+
return {
|
|
161
|
+
r: clampByte(rgb.r * invFactor + WHITE_RGB * factor),
|
|
162
|
+
g: clampByte(rgb.g * invFactor + WHITE_RGB * factor),
|
|
163
|
+
b: clampByte(rgb.b * invFactor + WHITE_RGB * factor)
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
const fromNamed = (name) => {
|
|
167
|
+
const cached = rgbCache.get(name);
|
|
168
|
+
if (cached) return cached;
|
|
169
|
+
const hex = NAMED_COLORS[name];
|
|
170
|
+
if (!hex) return { r: 0, g: 0, b: 0 };
|
|
171
|
+
if (IS_BUN) {
|
|
172
|
+
const rgb = Bun.color(hex, "{rgb}");
|
|
173
|
+
if (rgb) {
|
|
174
|
+
rgbCache.set(name, rgb);
|
|
175
|
+
return rgb;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const clean = hex[0] === "#" ? hex.slice(1) : hex;
|
|
179
|
+
const len = clean.length;
|
|
180
|
+
let r, g, b;
|
|
181
|
+
if (len === 3) {
|
|
182
|
+
const rv = Number.parseInt(clean[0], HEX_RADIX);
|
|
183
|
+
const gv = Number.parseInt(clean[1], HEX_RADIX);
|
|
184
|
+
const bv = Number.parseInt(clean[2], HEX_RADIX);
|
|
185
|
+
r = rv << 4 | rv;
|
|
186
|
+
g = gv << 4 | gv;
|
|
187
|
+
b = bv << 4 | bv;
|
|
188
|
+
} else if (len === 6) {
|
|
189
|
+
r = Number.parseInt(clean.slice(0, 2), HEX_RADIX);
|
|
190
|
+
g = Number.parseInt(clean.slice(2, 4), HEX_RADIX);
|
|
191
|
+
b = Number.parseInt(clean.slice(4, 6), HEX_RADIX);
|
|
192
|
+
} else {
|
|
193
|
+
return { r: 0, g: 0, b: 0 };
|
|
194
|
+
}
|
|
195
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
|
196
|
+
return { r: 0, g: 0, b: 0 };
|
|
197
|
+
}
|
|
198
|
+
const result = Object.freeze({ r, g, b });
|
|
199
|
+
rgbCache.set(name, result);
|
|
200
|
+
return result;
|
|
201
|
+
};
|
|
202
|
+
const toBaseName = (compound) => {
|
|
203
|
+
if (!compound) return "black";
|
|
204
|
+
const base = compound.slice(0, -BRIGHT_SUFFIX_LENGTH);
|
|
205
|
+
if (!base) return "black";
|
|
206
|
+
return base[0]?.toLowerCase() + base.slice(1);
|
|
207
|
+
};
|
|
208
|
+
const parseColorName = (name) => {
|
|
209
|
+
if (!name) return { rgb: { r: 0, g: 0, b: 0 }, wantBright: false };
|
|
210
|
+
if (name.endsWith(BRIGHT_SUFFIX)) {
|
|
211
|
+
const base = toBaseName(name);
|
|
212
|
+
const rgb = fromNamed(base);
|
|
213
|
+
const rgbAdj = mixWithWhite(rgb, BRIGHT_MIX_FACTOR);
|
|
214
|
+
return { rgb: rgbAdj, wantBright: true };
|
|
215
|
+
}
|
|
216
|
+
return { rgb: fromNamed(name), wantBright: false };
|
|
217
|
+
};
|
|
218
|
+
const openForOp = (op) => {
|
|
219
|
+
if (CURRENT_LEVEL === COLOR_LEVEL_OFF) return "";
|
|
220
|
+
switch (op.kind) {
|
|
221
|
+
case "style":
|
|
222
|
+
return sgr(op.open);
|
|
223
|
+
case "fg-basic": {
|
|
224
|
+
const code = (op.bright ? SGR_FG_BRIGHT_BASE : SGR_FG_BASE) + op.idx;
|
|
225
|
+
return sgr([code]);
|
|
226
|
+
}
|
|
227
|
+
case "bg-basic": {
|
|
228
|
+
const code = (op.bright ? SGR_BG_BRIGHT_BASE : SGR_BG_BASE) + op.idx;
|
|
229
|
+
return sgr([code]);
|
|
230
|
+
}
|
|
231
|
+
case "fg-256": {
|
|
232
|
+
if (IS_BUN) {
|
|
233
|
+
const ansi = Bun.color(op.code, "ansi-256");
|
|
234
|
+
if (ansi) return ansi;
|
|
235
|
+
}
|
|
236
|
+
return `\x1B[38;5;${op.code}m`;
|
|
237
|
+
}
|
|
238
|
+
case "bg-256": {
|
|
239
|
+
if (IS_BUN) {
|
|
240
|
+
const fgAnsi = Bun.color(op.code, "ansi-256");
|
|
241
|
+
if (fgAnsi) return fgAnsi.replace("38;5;", "48;5;");
|
|
242
|
+
}
|
|
243
|
+
return `\x1B[48;5;${op.code}m`;
|
|
244
|
+
}
|
|
245
|
+
case "fg-true": {
|
|
246
|
+
if (IS_BUN) {
|
|
247
|
+
const ansi = Bun.color(op.rgb, "ansi-16m");
|
|
248
|
+
if (ansi) return ansi;
|
|
249
|
+
}
|
|
250
|
+
const { r, g, b } = op.rgb;
|
|
251
|
+
return `\x1B[38;2;${r};${g};${b}m`;
|
|
252
|
+
}
|
|
253
|
+
case "bg-true": {
|
|
254
|
+
if (IS_BUN) {
|
|
255
|
+
const fgAnsi = Bun.color(op.rgb, "ansi-16m");
|
|
256
|
+
if (fgAnsi) return fgAnsi.replace("38;2;", "48;2;");
|
|
257
|
+
}
|
|
258
|
+
const { r, g, b } = op.rgb;
|
|
259
|
+
return `\x1B[48;2;${r};${g};${b}m`;
|
|
260
|
+
}
|
|
261
|
+
default:
|
|
262
|
+
return "";
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
const opsToOpen = (ops) => {
|
|
266
|
+
if (CURRENT_LEVEL === COLOR_LEVEL_OFF) return "";
|
|
267
|
+
const len = ops.length;
|
|
268
|
+
if (len === 0) return "";
|
|
269
|
+
if (len === 1) return openForOp(ops[0]);
|
|
270
|
+
let result = "";
|
|
271
|
+
for (let i = 0; i < len; i++) {
|
|
272
|
+
result += openForOp(ops[i]);
|
|
273
|
+
}
|
|
274
|
+
return result;
|
|
275
|
+
};
|
|
276
|
+
const applyOpsToText = (ops, input) => {
|
|
277
|
+
const text = String(input);
|
|
278
|
+
const textLen = text.length;
|
|
279
|
+
if (CURRENT_LEVEL === COLOR_LEVEL_OFF || ops.length === 0 || textLen === 0) {
|
|
280
|
+
return text;
|
|
281
|
+
}
|
|
282
|
+
const open = opsToOpen(ops);
|
|
283
|
+
if (!open) return text;
|
|
284
|
+
const nlIdx = text.indexOf("\n");
|
|
285
|
+
if (nlIdx === -1) {
|
|
286
|
+
return `${open}${text}${RESET}`;
|
|
287
|
+
}
|
|
288
|
+
let result = "";
|
|
289
|
+
let start = 0;
|
|
290
|
+
while (start < textLen) {
|
|
291
|
+
const end = text.indexOf("\n", start);
|
|
292
|
+
if (end === -1) {
|
|
293
|
+
const line2 = text.slice(start);
|
|
294
|
+
if (line2) {
|
|
295
|
+
if (line2.endsWith("\r")) {
|
|
296
|
+
result += `${open}${line2.slice(0, -1)}\r${RESET}`;
|
|
297
|
+
} else {
|
|
298
|
+
result += `${open}${line2}${RESET}`;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
if (start > 0) result += "\n";
|
|
304
|
+
const line = text.slice(start, end);
|
|
305
|
+
if (line) {
|
|
306
|
+
if (line.endsWith("\r")) {
|
|
307
|
+
result += `${open}${line.slice(0, -1)}\r${RESET}`;
|
|
308
|
+
} else {
|
|
309
|
+
result += `${open}${line}${RESET}`;
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
result += `${open}${RESET}`;
|
|
313
|
+
}
|
|
314
|
+
start = end + 1;
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
};
|
|
318
|
+
const mkFgOpsFromRgb = (rgb, wantBright = false) => {
|
|
319
|
+
if (CURRENT_LEVEL === COLOR_LEVEL_BASIC) {
|
|
320
|
+
return [
|
|
321
|
+
{ kind: "fg-basic", idx: nearestBasicIndex(rgb), bright: wantBright }
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
if (CURRENT_LEVEL === COLOR_LEVEL_256) {
|
|
325
|
+
return [{ kind: "fg-256", code: rgbToAnsi256(rgb) }];
|
|
326
|
+
}
|
|
327
|
+
return [{ kind: "fg-true", rgb }];
|
|
328
|
+
};
|
|
329
|
+
const mkBgOpsFromRgb = (rgb, wantBright = false) => {
|
|
330
|
+
if (CURRENT_LEVEL === COLOR_LEVEL_BASIC) {
|
|
331
|
+
return [
|
|
332
|
+
{ kind: "bg-basic", idx: nearestBasicIndex(rgb), bright: wantBright }
|
|
333
|
+
];
|
|
334
|
+
}
|
|
335
|
+
if (CURRENT_LEVEL === COLOR_LEVEL_256) {
|
|
336
|
+
return [{ kind: "bg-256", code: rgbToAnsi256(rgb) }];
|
|
337
|
+
}
|
|
338
|
+
return [{ kind: "bg-true", rgb }];
|
|
339
|
+
};
|
|
340
|
+
const STYLE_TABLE = {
|
|
341
|
+
reset: Object.freeze({ kind: "style", open: [SGR_RESET] }),
|
|
342
|
+
bold: Object.freeze({ kind: "style", open: [SGR_BOLD] }),
|
|
343
|
+
dim: Object.freeze({ kind: "style", open: [SGR_DIM] }),
|
|
344
|
+
italic: Object.freeze({ kind: "style", open: [SGR_ITALIC] }),
|
|
345
|
+
underline: Object.freeze({ kind: "style", open: [SGR_UNDERLINE] }),
|
|
346
|
+
inverse: Object.freeze({ kind: "style", open: [SGR_INVERSE] }),
|
|
347
|
+
hidden: Object.freeze({ kind: "style", open: [SGR_HIDDEN] }),
|
|
348
|
+
strikethrough: Object.freeze({ kind: "style", open: [SGR_STRIKETHROUGH] })
|
|
349
|
+
};
|
|
350
|
+
const STYLE_KEYS = Object.freeze(
|
|
351
|
+
/* @__PURE__ */ new Set([
|
|
352
|
+
"reset",
|
|
353
|
+
"bold",
|
|
354
|
+
"dim",
|
|
355
|
+
"italic",
|
|
356
|
+
"underline",
|
|
357
|
+
"inverse",
|
|
358
|
+
"hidden",
|
|
359
|
+
"strikethrough"
|
|
360
|
+
])
|
|
361
|
+
);
|
|
362
|
+
const COLOR_KEYS = Object.freeze(new Set(Object.keys(NAMED_COLORS)));
|
|
363
|
+
const BRIGHT_COLOR_KEYS = Object.freeze(
|
|
364
|
+
new Set(Object.keys(NAMED_COLORS).map((name) => `${name}Bright`))
|
|
365
|
+
);
|
|
366
|
+
const BG_COLOR_KEYS = Object.freeze(
|
|
367
|
+
new Set(
|
|
368
|
+
Object.keys(NAMED_COLORS).map(
|
|
369
|
+
(name) => `bg${name[0]?.toUpperCase()}${name.slice(1)}`
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
);
|
|
373
|
+
const BG_BRIGHT_COLOR_KEYS = Object.freeze(
|
|
374
|
+
new Set(
|
|
375
|
+
Object.keys(NAMED_COLORS).map(
|
|
376
|
+
(name) => `bg${name[0]?.toUpperCase()}${name.slice(1)}Bright`
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
);
|
|
380
|
+
const isColorKey = (key) => COLOR_KEYS.has(key) || BRIGHT_COLOR_KEYS.has(key);
|
|
381
|
+
const isBgKey = (key) => {
|
|
382
|
+
const len = key.length;
|
|
383
|
+
if (len <= BG_PREFIX_LENGTH || key[0] !== "b" || key[1] !== "g") {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
return BG_COLOR_KEYS.has(key) || BG_BRIGHT_COLOR_KEYS.has(key);
|
|
387
|
+
};
|
|
388
|
+
const proxyCache = /* @__PURE__ */ new WeakMap();
|
|
389
|
+
const callableProxy = (ops) => {
|
|
390
|
+
const cached = proxyCache.get(ops);
|
|
391
|
+
if (cached) return cached;
|
|
392
|
+
const base = ((input) => applyOpsToText(ops, input));
|
|
393
|
+
Object.defineProperty(base, OP_SYMBOL, {
|
|
394
|
+
value: ops,
|
|
395
|
+
enumerable: false,
|
|
396
|
+
configurable: false,
|
|
397
|
+
writable: false
|
|
398
|
+
});
|
|
399
|
+
const proxy = new Proxy(base, {
|
|
400
|
+
apply(_target, _thisArg, argArray) {
|
|
401
|
+
return applyOpsToText(ops, argArray[0]);
|
|
402
|
+
},
|
|
403
|
+
get(_target, prop) {
|
|
404
|
+
if (prop === OP_SYMBOL) return ops;
|
|
405
|
+
const key = String(prop);
|
|
406
|
+
if (STYLE_KEYS.has(key)) {
|
|
407
|
+
const op = STYLE_TABLE[key];
|
|
408
|
+
const newOps = [...ops, op];
|
|
409
|
+
return callableProxy(newOps);
|
|
410
|
+
}
|
|
411
|
+
if (isBgKey(key)) {
|
|
412
|
+
const colorName = key[2]?.toLowerCase() + key.slice(3);
|
|
413
|
+
const { rgb, wantBright } = parseColorName(colorName);
|
|
414
|
+
const bgOps = mkBgOpsFromRgb(rgb, wantBright);
|
|
415
|
+
const newOps = [...ops, ...bgOps];
|
|
416
|
+
return callableProxy(newOps);
|
|
417
|
+
}
|
|
418
|
+
if (isColorKey(key)) {
|
|
419
|
+
const { rgb, wantBright } = parseColorName(key);
|
|
420
|
+
const fgOps = mkFgOpsFromRgb(rgb, wantBright);
|
|
421
|
+
const newOps = [...ops, ...fgOps];
|
|
422
|
+
return callableProxy(newOps);
|
|
423
|
+
}
|
|
424
|
+
return proxy;
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
proxyCache.set(ops, proxy);
|
|
428
|
+
return proxy;
|
|
429
|
+
};
|
|
430
|
+
export const re = callableProxy([]);
|
|
431
|
+
export const chain = (...parts) => {
|
|
432
|
+
if (parts.length === 0) return re;
|
|
433
|
+
if (parts.length === 1) return parts[0];
|
|
434
|
+
const collected = [];
|
|
435
|
+
for (let i = 0; i < parts.length; i++) {
|
|
436
|
+
const ops = parts[i][OP_SYMBOL];
|
|
437
|
+
if (ops?.length) {
|
|
438
|
+
collected.push(...ops);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return callableProxy(collected);
|
|
442
|
+
};
|
|
443
|
+
export const color = (input, isBg = false) => {
|
|
444
|
+
if (!IS_BUN) {
|
|
445
|
+
if (typeof input === "object" && "r" in input && "g" in input && "b" in input) {
|
|
446
|
+
const ops2 = isBg ? mkBgOpsFromRgb(input, false) : mkFgOpsFromRgb(input, false);
|
|
447
|
+
return callableProxy(ops2);
|
|
448
|
+
}
|
|
449
|
+
return re;
|
|
450
|
+
}
|
|
451
|
+
const rgb = Bun.color(input, "{rgb}");
|
|
452
|
+
if (!rgb) return re;
|
|
453
|
+
const ops = isBg ? mkBgOpsFromRgb(rgb, false) : mkFgOpsFromRgb(rgb, false);
|
|
454
|
+
return callableProxy(ops);
|
|
455
|
+
};
|
package/package.json
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reliverse/relico",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"version": "2.2.7",
|
|
4
|
+
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "@reliverse/relico is a themeable, chainable, typed, truecolor-powered, modern terminal styling toolkit. Designed to make your CLI output colorful, accessible, and human-friendly. It gives you a flexible way to apply colors and styles — with reliability and a smart developer experience baked in. Built for humans, not just terminals.",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"size": "bun examples/benchmarks/bundle-size.ts",
|
|
13
|
-
"bench": "bun examples/benchmarks/performance.ts",
|
|
14
|
-
"check": "bun tsc --noEmit && bun biome check --fix --unsafe"
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/mod.d.ts",
|
|
10
|
+
"default": "./dist/mod.js"
|
|
11
|
+
}
|
|
15
12
|
},
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"package.json"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT"
|
|
22
|
+
}
|