@ix-xs/node-comfort 1.2.0 → 2.1.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +340 -0
  2. package/README.md +137 -548
  3. package/index.js +80 -35
  4. package/package.json +161 -33
  5. package/src/Arr.js +1050 -418
  6. package/src/Async.js +817 -0
  7. package/src/Cache.js +433 -0
  8. package/src/Checker.js +1202 -539
  9. package/src/Cli.js +1293 -0
  10. package/src/Color.js +556 -0
  11. package/src/Crypto.js +1082 -0
  12. package/src/Emitter.js +356 -0
  13. package/src/Env.js +689 -0
  14. package/src/FS.js +1626 -894
  15. package/src/Func.js +823 -356
  16. package/src/Http.js +700 -0
  17. package/src/Id.js +568 -200
  18. package/src/Logger.js +963 -378
  19. package/src/Num.js +965 -360
  20. package/src/Obj.js +1077 -379
  21. package/src/SQLite.js +1629 -1005
  22. package/src/Schema.js +2101 -0
  23. package/src/Str.js +1209 -432
  24. package/src/Sys.js +654 -0
  25. package/src/Time.js +1533 -440
  26. package/src/Utils.js +342 -325
  27. package/src/errors.js +335 -0
  28. package/src/internal/shared.js +415 -0
  29. package/types/index.d.ts +735 -526
  30. package/types/src/Arr.d.ts +533 -145
  31. package/types/src/Arr.d.ts.map +1 -1
  32. package/types/src/Async.d.ts +372 -0
  33. package/types/src/Async.d.ts.map +1 -0
  34. package/types/src/Cache.d.ts +221 -0
  35. package/types/src/Cache.d.ts.map +1 -0
  36. package/types/src/Checker.d.ts +651 -232
  37. package/types/src/Checker.d.ts.map +1 -1
  38. package/types/src/Cli.d.ts +664 -0
  39. package/types/src/Cli.d.ts.map +1 -0
  40. package/types/src/Color.d.ts +300 -0
  41. package/types/src/Color.d.ts.map +1 -0
  42. package/types/src/Crypto.d.ts +620 -0
  43. package/types/src/Crypto.d.ts.map +1 -0
  44. package/types/src/Emitter.d.ts +189 -0
  45. package/types/src/Emitter.d.ts.map +1 -0
  46. package/types/src/Env.d.ts +412 -0
  47. package/types/src/Env.d.ts.map +1 -0
  48. package/types/src/FS.d.ts +717 -238
  49. package/types/src/FS.d.ts.map +1 -1
  50. package/types/src/Func.d.ts +508 -135
  51. package/types/src/Func.d.ts.map +1 -1
  52. package/types/src/Http.d.ts +444 -0
  53. package/types/src/Http.d.ts.map +1 -0
  54. package/types/src/Id.d.ts +199 -66
  55. package/types/src/Id.d.ts.map +1 -1
  56. package/types/src/Logger.d.ts +414 -46
  57. package/types/src/Logger.d.ts.map +1 -1
  58. package/types/src/Num.d.ts +514 -137
  59. package/types/src/Num.d.ts.map +1 -1
  60. package/types/src/Obj.d.ts +453 -104
  61. package/types/src/Obj.d.ts.map +1 -1
  62. package/types/src/SQLite.d.ts +754 -308
  63. package/types/src/SQLite.d.ts.map +1 -1
  64. package/types/src/Schema.d.ts +1189 -0
  65. package/types/src/Schema.d.ts.map +1 -0
  66. package/types/src/Str.d.ts +617 -166
  67. package/types/src/Str.d.ts.map +1 -1
  68. package/types/src/Sys.d.ts +314 -0
  69. package/types/src/Sys.d.ts.map +1 -0
  70. package/types/src/Time.d.ts +759 -204
  71. package/types/src/Time.d.ts.map +1 -1
  72. package/types/src/Utils.d.ts +158 -102
  73. package/types/src/Utils.d.ts.map +1 -1
  74. package/types/src/errors.d.ts +316 -0
  75. package/types/src/errors.d.ts.map +1 -0
  76. package/types/src/internal/shared.d.ts +123 -0
  77. package/types/src/internal/shared.d.ts.map +1 -0
  78. package/types/index.d.ts.map +0 -1
package/src/Color.js ADDED
@@ -0,0 +1,556 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Terminal colors you can chain, like `chalk`. Truecolor is supported and
5
+ * downgraded on older terminals. Colors switch off by themselves when the
6
+ * output isn't a terminal or `NO_COLOR` is set; `FORCE_COLOR=1|2|3` turns
7
+ * them back on.
8
+ *
9
+ * @example
10
+ * console.log(color.green.bold("✔ Done"), color.gray("in 1.2s"));
11
+ * console.log(color.bgHex("#1e1e2e").hex("#cba6f7")(" nc "));
12
+ * console.log(color.gradient("node-comfort", ["#ff5f6d", "#ffc371"]));
13
+ */
14
+
15
+ const { segmenter, graphemes } = require("./internal/shared.js");
16
+
17
+ /**
18
+ * Every named style.
19
+ * @typedef {"reset" | "bold" | "dim" | "italic" | "underline" | "overline" | "inverse" | "hidden" | "strikethrough"
20
+ * | "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "gray" | "grey"
21
+ * | "blackBright" | "redBright" | "greenBright" | "yellowBright" | "blueBright" | "magentaBright" | "cyanBright" | "whiteBright"
22
+ * | "bgBlack" | "bgRed" | "bgGreen" | "bgYellow" | "bgBlue" | "bgMagenta" | "bgCyan" | "bgWhite" | "bgGray" | "bgGrey"
23
+ * | "bgBlackBright" | "bgRedBright" | "bgGreenBright" | "bgYellowBright" | "bgBlueBright" | "bgMagentaBright" | "bgCyanBright" | "bgWhiteBright"} StyleName
24
+ */
25
+
26
+ /**
27
+ * How many colors the terminal can show: `0` none, `1` 16, `2` 256, `3` millions.
28
+ * @typedef {0 | 1 | 2 | 3} ColorLevel
29
+ */
30
+
31
+ /**
32
+ * The named styles. Each one can be called on text or chained with others:
33
+ * `color.red("x")`, `color.red.bold.underline("x")`.
34
+ * @typedef {object} ColorStyleProps
35
+ * @property {ColorChain} reset Removes all styles.
36
+ * @property {ColorChain} bold Bold text.
37
+ * @property {ColorChain} dim Faint text.
38
+ * @property {ColorChain} italic Italic text, where the terminal supports it.
39
+ * @property {ColorChain} underline Underlined text.
40
+ * @property {ColorChain} overline Line above the text, where supported.
41
+ * @property {ColorChain} inverse Swaps text and background colors.
42
+ * @property {ColorChain} hidden Invisible text, still copyable.
43
+ * @property {ColorChain} strikethrough Crossed-out text.
44
+ * @property {ColorChain} black Black text.
45
+ * @property {ColorChain} red Red text.
46
+ * @property {ColorChain} green Green text.
47
+ * @property {ColorChain} yellow Yellow text.
48
+ * @property {ColorChain} blue Blue text.
49
+ * @property {ColorChain} magenta Magenta text.
50
+ * @property {ColorChain} cyan Cyan text.
51
+ * @property {ColorChain} white White text.
52
+ * @property {ColorChain} gray Gray text.
53
+ * @property {ColorChain} grey Same as `gray`.
54
+ * @property {ColorChain} blackBright Bright black text.
55
+ * @property {ColorChain} redBright Bright red text.
56
+ * @property {ColorChain} greenBright Bright green text.
57
+ * @property {ColorChain} yellowBright Bright yellow text.
58
+ * @property {ColorChain} blueBright Bright blue text.
59
+ * @property {ColorChain} magentaBright Bright magenta text.
60
+ * @property {ColorChain} cyanBright Bright cyan text.
61
+ * @property {ColorChain} whiteBright Bright white text.
62
+ * @property {ColorChain} bgBlack Black background.
63
+ * @property {ColorChain} bgRed Red background.
64
+ * @property {ColorChain} bgGreen Green background.
65
+ * @property {ColorChain} bgYellow Yellow background.
66
+ * @property {ColorChain} bgBlue Blue background.
67
+ * @property {ColorChain} bgMagenta Magenta background.
68
+ * @property {ColorChain} bgCyan Cyan background.
69
+ * @property {ColorChain} bgWhite White background.
70
+ * @property {ColorChain} bgGray Gray background.
71
+ * @property {ColorChain} bgGrey Same as `bgGray`.
72
+ * @property {ColorChain} bgBlackBright Bright black background.
73
+ * @property {ColorChain} bgRedBright Bright red background.
74
+ * @property {ColorChain} bgGreenBright Bright green background.
75
+ * @property {ColorChain} bgYellowBright Bright yellow background.
76
+ * @property {ColorChain} bgBlueBright Bright blue background.
77
+ * @property {ColorChain} bgMagentaBright Bright magenta background.
78
+ * @property {ColorChain} bgCyanBright Bright cyan background.
79
+ * @property {ColorChain} bgWhiteBright Bright white background.
80
+ */
81
+
82
+ /**
83
+ * The named styles, read-only.
84
+ * @typedef {Readonly<ColorStyleProps>} ColorStyles
85
+ */
86
+
87
+ /**
88
+ * A style you can call on text, or chain with more styles.
89
+ *
90
+ * @typedef {((...text: unknown[]) => string) & ColorStyles & ColorMethods} ColorChain
91
+ */
92
+
93
+ /**
94
+ * Custom colors, available on every chain.
95
+ * @typedef {object} ColorMethods
96
+ * @property {(r: number, g: number, b: number) => ColorChain} rgb Text color from red, green and blue (0-255).
97
+ * @property {(r: number, g: number, b: number) => ColorChain} bgRgb Background from red, green and blue (0-255).
98
+ * @property {(hex: string) => ColorChain} hex Text color from a hex code like `"#f80"`.
99
+ * @property {(hex: string) => ColorChain} bgHex Background from a hex code.
100
+ * @property {(code: number) => ColorChain} ansi256 Text color from the 256-color palette.
101
+ * @property {(code: number) => ColorChain} bgAnsi256 Background from the 256-color palette.
102
+ */
103
+
104
+ /**
105
+ * `nc.color`: every style and color, plus a few helpers.
106
+ *
107
+ * @typedef {ColorStyles & ColorMethods & ColorHelpers} ColorModule
108
+ */
109
+
110
+ /**
111
+ * Color helpers.
112
+ * @typedef {object} ColorHelpers
113
+ * @property {ColorLevel} level The current color level. Set it to force one: `0` turns colors off, `undefined` goes back to what the terminal supports.
114
+ * @property {boolean} enabled Whether colors are being output.
115
+ * @property {(level?: ColorLevel) => ColorModule} create A separate instance with its own level, for another stream or for tests.
116
+ * @property {(stream?: NodeJS.WriteStream | { isTTY?: boolean }) => ColorLevel} detect The color level a stream supports, taking `NO_COLOR` and `FORCE_COLOR` into account.
117
+ * @property {(text: string) => string} strip Removes colors and other terminal escape codes.
118
+ * @property {(text: string) => number} width How many columns the text takes, ignoring colors and counting emoji and CJK as 2.
119
+ * @property {(text: string, columns: number, options?: { ellipsis?: string }) => string} truncate Cuts text to a number of columns and adds `…`. Colors are kept and still closed properly.
120
+ * @property {(text: string, url: string) => string} link A clickable link in terminals that support it, `"text (url)"` elsewhere.
121
+ * @property {(text: string, colors: string[]) => string} gradient Colors the text along a gradient of hex colors.
122
+ */
123
+
124
+ /** @type {Record<string, [number, number]>} */
125
+ const _CODES = {
126
+ reset: [0, 0], bold: [1, 22], dim: [2, 22], italic: [3, 23], underline: [4, 24], overline: [53, 55],
127
+ inverse: [7, 27], hidden: [8, 28], strikethrough: [9, 29],
128
+ black: [30, 39], red: [31, 39], green: [32, 39], yellow: [33, 39], blue: [34, 39], magenta: [35, 39], cyan: [36, 39], white: [37, 39],
129
+ gray: [90, 39], grey: [90, 39], blackBright: [90, 39], redBright: [91, 39], greenBright: [92, 39], yellowBright: [93, 39],
130
+ blueBright: [94, 39], magentaBright: [95, 39], cyanBright: [96, 39], whiteBright: [97, 39],
131
+ bgBlack: [40, 49], bgRed: [41, 49], bgGreen: [42, 49], bgYellow: [43, 49], bgBlue: [44, 49], bgMagenta: [45, 49], bgCyan: [46, 49], bgWhite: [47, 49],
132
+ bgGray: [100, 49], bgGrey: [100, 49], bgBlackBright: [100, 49], bgRedBright: [101, 49], bgGreenBright: [102, 49], bgYellowBright: [103, 49],
133
+ bgBlueBright: [104, 49], bgMagentaBright: [105, 49], bgCyanBright: [106, 49], bgWhiteBright: [107, 49],
134
+ };
135
+
136
+ /** @type {Record<string, { open: string, close: string }>} */
137
+ const _STYLES = {};
138
+ for (const [name, [open, close]] of Object.entries(_CODES)) _STYLES[name] = { open: `\u001b[${open}m`, close: `\u001b[${close}m` };
139
+
140
+ /**
141
+ * A style in a chain: fixed codes, or a color resolved at output time.
142
+ * @typedef {{ open: string, close: string } | { rgb: [number, number, number], bg: boolean } | { ansi256: number, bg: boolean }} StyleEntry
143
+ */
144
+
145
+ const _ANSI_REGEX = /[\u001b\u009b](?:\][^\u0007\u001b]*(?:\u0007|\u001b\\)|[[\]()#;?]*(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~])/g;
146
+
147
+ /**
148
+ * @param {string} hex
149
+ * @returns {[number, number, number]}
150
+ */
151
+ const _hexToRgb = (hex) => {
152
+ const clean = String(hex).replace(/^#/, "");
153
+ const full = clean.length === 3 || clean.length === 4 ? [...clean.slice(0, 3)].map((c) => c + c).join("") : clean.slice(0, 6);
154
+ if (!/^[0-9a-f]{6}$/i.test(full)) throw new TypeError(`Invalid hex color "${hex}"`);
155
+ return [parseInt(full.slice(0, 2), 16), parseInt(full.slice(2, 4), 16), parseInt(full.slice(4, 6), 16)];
156
+ };
157
+
158
+ /**
159
+ * @param {number} r
160
+ * @param {number} g
161
+ * @param {number} b
162
+ * @returns {number}
163
+ */
164
+ const _rgbToAnsi256 = (r, g, b) => {
165
+ if (r === g && g === b) {
166
+ if (r < 8) return 16;
167
+ if (r > 248) return 231;
168
+ return Math.round(((r - 8) / 247) * 24) + 232;
169
+ }
170
+ return 16 + 36 * Math.round((r / 255) * 5) + 6 * Math.round((g / 255) * 5) + Math.round((b / 255) * 5);
171
+ };
172
+
173
+ /** @type {Array<[number, number, number]>} */
174
+ const _ANSI16_RGB = [
175
+ [0, 0, 0], [205, 0, 0], [0, 205, 0], [205, 205, 0], [0, 0, 238], [205, 0, 205], [0, 205, 205], [229, 229, 229],
176
+ [127, 127, 127], [255, 0, 0], [0, 255, 0], [255, 255, 0], [92, 92, 255], [255, 0, 255], [0, 255, 255], [255, 255, 255],
177
+ ];
178
+
179
+ /**
180
+ * @param {number} code
181
+ * @returns {[number, number, number]}
182
+ */
183
+ const _ansi256ToRgb = (code) => {
184
+ if (code < 16) return _ANSI16_RGB[code];
185
+ if (code >= 232) {
186
+ const v = (code - 232) * 10 + 8;
187
+ return [v, v, v];
188
+ }
189
+ const n = code - 16;
190
+ const step = (/** @type {number} */ x) => (x === 0 ? 0 : x * 40 + 55);
191
+ return [step(Math.floor(n / 36)), step(Math.floor((n % 36) / 6)), step(n % 6)];
192
+ };
193
+
194
+ /**
195
+ * @param {number} r
196
+ * @param {number} g
197
+ * @param {number} b
198
+ * @returns {number} A 16-color foreground code (30-37, 90-97).
199
+ */
200
+ const _rgbToAnsi16 = (r, g, b) => {
201
+ const value = Math.round((Math.max(r, g, b) / 255) * 2);
202
+ if (value === 0) return 30;
203
+ const code = 30 + ((Math.round(b / 255) << 2) | (Math.round(g / 255) << 1) | Math.round(r / 255));
204
+ return value === 2 ? code + 60 : code;
205
+ };
206
+
207
+ /**
208
+ * Resolves a style entry into escape codes for a given level.
209
+ * @param {StyleEntry} entry
210
+ * @param {ColorLevel} level
211
+ * @returns {{ open: string, close: string }}
212
+ */
213
+ const _resolve = (entry, level) => {
214
+ if ("open" in entry) return entry;
215
+ const close = entry.bg ? "\u001b[49m" : "\u001b[39m";
216
+ const rgb = "rgb" in entry ? entry.rgb : _ansi256ToRgb(entry.ansi256);
217
+ if (level >= 3 && "rgb" in entry) return { open: `\u001b[${entry.bg ? 48 : 38};2;${rgb[0]};${rgb[1]};${rgb[2]}m`, close };
218
+ if (level >= 2) {
219
+ const code = "ansi256" in entry ? entry.ansi256 : _rgbToAnsi256(...rgb);
220
+ return { open: `\u001b[${entry.bg ? 48 : 38};5;${code}m`, close };
221
+ }
222
+ const code = _rgbToAnsi16(...rgb) + (entry.bg ? 10 : 0);
223
+ return { open: `\u001b[${code}m`, close };
224
+ };
225
+
226
+ // Command line flags don't change while the program runs: read them once.
227
+ /** @type {{ off: boolean, level: ColorLevel | undefined, forced: boolean } | undefined} */
228
+ let _flags;
229
+ const _argvFlags = () => {
230
+ if (_flags) return _flags;
231
+ const argv = process.argv;
232
+ const has = (/** @type {string[]} */ ...names) => names.some((name) => argv.includes(name));
233
+ return (_flags = {
234
+ off: has("--no-color", "--no-colors", "--color=false", "--color=never"),
235
+ level: has("--color=16m", "--color=full", "--color=truecolor") ? 3 : has("--color=256") ? 2 : undefined,
236
+ forced: has("--color", "--colors", "--color=true", "--color=always"),
237
+ });
238
+ };
239
+
240
+ const _CI_WITH_COLORS = ["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"];
241
+
242
+ /**
243
+ * @param {NodeJS.WriteStream | { isTTY?: boolean } | undefined} stream
244
+ * @returns {ColorLevel}
245
+ */
246
+ const _detect = (stream = process.stdout) => {
247
+ const env = process.env;
248
+ const flags = _argvFlags();
249
+ if (flags.off) return 0;
250
+ if (env.NO_COLOR !== undefined && env.NO_COLOR !== "") return 0;
251
+ if (env.NODE_DISABLE_COLORS !== undefined && env.NODE_DISABLE_COLORS !== "") return 0;
252
+ if (env.FORCE_COLOR !== undefined) {
253
+ const value = env.FORCE_COLOR.toLowerCase();
254
+ if (value === "false" || value === "0") return 0;
255
+ if (value === "2") return 2;
256
+ if (value === "3") return 3;
257
+ return 1;
258
+ }
259
+ if (flags.level !== undefined) return flags.level;
260
+ if (env.TERM === "dumb" && !flags.forced) return 0;
261
+ const tty = /** @type {NodeJS.WriteStream | undefined} */ (stream && "isTTY" in stream && stream.isTTY ? stream : undefined);
262
+ if (tty && typeof tty.getColorDepth === "function") {
263
+ const depth = tty.getColorDepth();
264
+ if (depth >= 24) return 3;
265
+ if (depth >= 8) return 2;
266
+ if (depth >= 4) return 1;
267
+ return flags.forced ? 1 : 0;
268
+ }
269
+ if (env.CI !== undefined) {
270
+ if (env.GITHUB_ACTIONS || env.GITEA_ACTIONS) return 3;
271
+ if (_CI_WITH_COLORS.some((name) => name in env)) return 1;
272
+ }
273
+ return flags.forced ? 1 : 0;
274
+ };
275
+
276
+ const _PLAIN = /^[\x20-\x7e]*$/;
277
+ // What can turn several code points into one character on screen: accents, joiners, flags, skin tones.
278
+ const _JOINED = /[\p{Mark}\u200d\ufe0e\ufe0f\u{1f1e6}-\u{1f1ff}\u{1f3fb}-\u{1f3ff}\u{e0020}-\u{e007f}]/u;
279
+ const _MARK = /^\p{Mark}+$/u;
280
+ const _EMOJI = /\p{Emoji_Presentation}/u;
281
+ const _WIDE = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩\u{20000}-\u{3FFFD}]/u;
282
+
283
+ /**
284
+ * Terminal width of one grapheme.
285
+ * @param {string} grapheme
286
+ * @returns {number}
287
+ */
288
+ const _charWidth = (grapheme) => {
289
+ const cp = /** @type {number} */ (grapheme.codePointAt(0));
290
+ if (cp < 32 || (cp >= 0x7f && cp < 0xa0)) return 0;
291
+ if (cp < 0x300 && grapheme.length === 1) return 1;
292
+ // zero width space and joiners, direction marks, byte order mark
293
+ if ((cp >= 0x200b && cp <= 0x200f) || cp === 0x2060 || cp === 0xfeff) return 0;
294
+ if (_MARK.test(grapheme)) return 0;
295
+ if (_EMOJI.test(grapheme) || grapheme.includes("\ufe0f")) return 2;
296
+ return _WIDE.test(grapheme) ? 2 : 1;
297
+ };
298
+
299
+ /**
300
+ * @param {string} text
301
+ * @returns {string}
302
+ */
303
+ const strip = (text) => {
304
+ const value = typeof text === "string" ? text : String(text);
305
+ return value.indexOf("\u001b") === -1 && value.indexOf("\u009b") === -1 ? value : value.replace(_ANSI_REGEX, "");
306
+ };
307
+
308
+ /**
309
+ * @param {string} text
310
+ * @returns {number}
311
+ */
312
+ const width = (text) => {
313
+ const plain = strip(text);
314
+ let i = 0;
315
+ // ASCII and accented Latin letters: one column each.
316
+ for (; i < plain.length; i++) {
317
+ const code = plain.charCodeAt(i);
318
+ if (code < 0x20 || (code > 0x7e && code < 0xa0) || code > 0x2ff) break;
319
+ }
320
+ if (i === plain.length) return i;
321
+ let total = 0;
322
+ if (_JOINED.test(plain)) {
323
+ for (const { segment } of segmenter().segment(plain)) total += _charWidth(segment);
324
+ } else {
325
+ for (const char of plain) total += _charWidth(char);
326
+ }
327
+ return total;
328
+ };
329
+
330
+ /**
331
+ * @param {string} text
332
+ * @param {number} columns
333
+ * @param {{ ellipsis?: string }} [options]
334
+ * @returns {string}
335
+ */
336
+ const truncate = (text, columns, options = {}) => {
337
+ const value = typeof text === "string" ? text : String(text);
338
+ const max = Number.isFinite(columns) ? Math.max(0, Math.floor(columns)) : Infinity;
339
+ if (width(value) <= max) return value;
340
+ const mark = options.ellipsis ?? "…";
341
+ const ellipsis = width(mark) <= max ? mark : "";
342
+ const room = max - width(ellipsis);
343
+ if (_PLAIN.test(value)) return value.slice(0, room) + ellipsis;
344
+ let out = "";
345
+ let used = 0;
346
+ let cut = false;
347
+ const take = (/** @type {string} */ plain) => {
348
+ if (cut || !plain) return;
349
+ if (_PLAIN.test(plain) && used + plain.length <= room) {
350
+ out += plain;
351
+ used += plain.length;
352
+ return;
353
+ }
354
+ for (const { segment } of segmenter().segment(plain)) {
355
+ const columnsTaken = _charWidth(segment);
356
+ if (used + columnsTaken > room) {
357
+ cut = true;
358
+ out += ellipsis;
359
+ return;
360
+ }
361
+ out += segment;
362
+ used += columnsTaken;
363
+ }
364
+ };
365
+ let last = 0;
366
+ // Escape codes past the cut are kept: they are what closes the styles opened before it.
367
+ for (const match of value.matchAll(_ANSI_REGEX)) {
368
+ take(value.slice(last, match.index));
369
+ out += match[0];
370
+ last = match.index + match[0].length;
371
+ }
372
+ take(value.slice(last));
373
+ return out;
374
+ };
375
+
376
+ const _supportsHyperlinks = () => {
377
+ const env = process.env;
378
+ if (env.FORCE_HYPERLINK === "1") return true;
379
+ if (env.FORCE_HYPERLINK === "0") return false;
380
+ if (!process.stdout.isTTY) return false;
381
+ if (env.WT_SESSION || env.KONSOLE_VERSION || env.DOMTERM) return true;
382
+ if (["iTerm.app", "WezTerm", "vscode", "Hyper", "ghostty", "Tabby"].includes(env.TERM_PROGRAM ?? "")) return true;
383
+ if (env.VTE_VERSION && Number(env.VTE_VERSION) >= 5000) return true;
384
+ return false;
385
+ };
386
+
387
+ /** @param {number} value */
388
+ const _byte = (value) => (value > 0 ? (value < 255 ? Math.round(value) : 255) : 0);
389
+
390
+ const _NEWLINE = /\r?\n/g;
391
+ const _STACK = Symbol("stack");
392
+ const _CUSTOM = Symbol("custom");
393
+
394
+ /**
395
+ * Builds an independent color instance.
396
+ * @param {ColorLevel | undefined} forcedLevel
397
+ * @returns {ColorModule}
398
+ */
399
+ const _createModule = (forcedLevel) => {
400
+ /** @type {ColorLevel | undefined} */
401
+ let level = forcedLevel;
402
+ // Goes up when the level changes, so every chain knows its codes are out of date.
403
+ let generation = 0;
404
+ const currentLevel = () => (level === undefined ? (level = _detect()) : level);
405
+
406
+ /** @type {any} */
407
+ const proto = Object.create(Function.prototype);
408
+
409
+ /**
410
+ * @param {StyleEntry[]} stack
411
+ * @returns {ColorChain}
412
+ */
413
+ const chain = (stack) => {
414
+ let seen = -1;
415
+ let open = "";
416
+ let close = "";
417
+ /** @type {Array<{ open: string, close: string }>} */
418
+ let pairs = [];
419
+ const refresh = () => {
420
+ seen = generation;
421
+ open = "";
422
+ close = "";
423
+ pairs = [];
424
+ const current = currentLevel();
425
+ if (current === 0) return;
426
+ for (const entry of stack) {
427
+ const pair = _resolve(entry, current);
428
+ pairs.push(pair);
429
+ open += pair.open;
430
+ close = pair.close + close;
431
+ }
432
+ };
433
+ /** @type {any} */
434
+ const fn = (/** @type {unknown[]} */ ...parts) => {
435
+ const text = parts.length === 1 ? (typeof parts[0] === "string" ? parts[0] : String(parts[0])) : parts.map((part) => String(part)).join(" ");
436
+ if (seen !== generation) refresh();
437
+ if (open === "" || text === "") return text;
438
+ if (text.indexOf("\u001b") === -1 && text.indexOf("\n") === -1) return open + text + close;
439
+ let result = text;
440
+ // A style closed inside the text would also end ours: open it again right after.
441
+ for (const pair of pairs) {
442
+ if (result.includes(pair.close)) result = result.replaceAll(pair.close, pair.close + pair.open);
443
+ }
444
+ // Styles stop at the end of each line, or a background would run to the edge of the terminal.
445
+ if (result.includes("\n")) result = result.replace(_NEWLINE, (newline) => close + newline + open);
446
+ return open + result + close;
447
+ };
448
+ Object.setPrototypeOf(fn, proto);
449
+ fn[_STACK] = stack;
450
+ return fn;
451
+ };
452
+
453
+ // Each chain is built the first time it's read, then kept on its parent:
454
+ // `color.red.bold` costs two property reads from then on.
455
+ for (const name of Object.keys(_STYLES)) {
456
+ Object.defineProperty(proto, name, {
457
+ get() {
458
+ const next = chain([...this[_STACK], _STYLES[name]]);
459
+ Object.defineProperty(this, name, { value: next });
460
+ return next;
461
+ },
462
+ });
463
+ }
464
+
465
+ /**
466
+ * @param {any} parent
467
+ * @param {string} key
468
+ * @param {() => StyleEntry} entry
469
+ * @returns {ColorChain}
470
+ */
471
+ const custom = (parent, key, entry) => {
472
+ /** @type {Map<string, ColorChain>} */
473
+ const known = (parent[_CUSTOM] ??= new Map());
474
+ let next = known.get(key);
475
+ if (!next) {
476
+ if (known.size >= 256) known.clear();
477
+ known.set(key, (next = chain([...parent[_STACK], entry()])));
478
+ }
479
+ return next;
480
+ };
481
+ /** @type {Record<string, (this: any, ...args: any[]) => ColorChain>} */
482
+ const methods = {
483
+ rgb(r, g, b) { return custom(this, `f${r},${g},${b}`, () => ({ rgb: [_byte(r), _byte(g), _byte(b)], bg: false })); },
484
+ bgRgb(r, g, b) { return custom(this, `b${r},${g},${b}`, () => ({ rgb: [_byte(r), _byte(g), _byte(b)], bg: true })); },
485
+ hex(hex) { return custom(this, `F${hex}`, () => ({ rgb: _hexToRgb(hex), bg: false })); },
486
+ bgHex(hex) { return custom(this, `B${hex}`, () => ({ rgb: _hexToRgb(hex), bg: true })); },
487
+ ansi256(code) { return custom(this, `a${code}`, () => ({ ansi256: _byte(code), bg: false })); },
488
+ bgAnsi256(code) { return custom(this, `A${code}`, () => ({ ansi256: _byte(code), bg: true })); },
489
+ };
490
+ for (const [name, method] of Object.entries(methods)) Object.defineProperty(proto, name, { value: method });
491
+
492
+ /**
493
+ * @param {string} text
494
+ * @param {string[]} colors
495
+ * @returns {string}
496
+ */
497
+ const gradient = (text, colors) => {
498
+ const value = String(text);
499
+ const stops = colors.map(_hexToRgb);
500
+ const current = currentLevel();
501
+ if (!stops.length || current === 0) return value;
502
+ const letters = graphemes(value);
503
+ let visible = 0;
504
+ for (const letter of letters) if (letter.trim()) visible++;
505
+ const last = stops.length - 1;
506
+ let index = 0;
507
+ let out = "";
508
+ for (const letter of letters) {
509
+ if (!letter.trim()) {
510
+ out += letter;
511
+ continue;
512
+ }
513
+ const position = visible <= 1 ? 0 : (index++ / (visible - 1)) * last;
514
+ const from = Math.min(Math.floor(position), Math.max(0, last - 1));
515
+ const a = stops[from];
516
+ const b = stops[Math.min(from + 1, last)];
517
+ const share = position - from;
518
+ const pair = _resolve({ rgb: [Math.round(a[0] + (b[0] - a[0]) * share), Math.round(a[1] + (b[1] - a[1]) * share), Math.round(a[2] + (b[2] - a[2]) * share)], bg: false }, current);
519
+ out += pair.open + letter + pair.close;
520
+ }
521
+ return out;
522
+ };
523
+
524
+ /** @type {any} */
525
+ const root = Object.create(proto);
526
+ root[_STACK] = [];
527
+ Object.defineProperties(root, {
528
+ level: {
529
+ enumerable: true,
530
+ get: currentLevel,
531
+ set(/** @type {ColorLevel | undefined} */ value) {
532
+ // `undefined` goes back to what the terminal supports.
533
+ level = value === undefined ? undefined : /** @type {ColorLevel} */ (Number.isFinite(value) ? Math.max(0, Math.min(3, Math.floor(value))) : 0);
534
+ generation++;
535
+ },
536
+ },
537
+ enabled: { enumerable: true, get: () => currentLevel() > 0 },
538
+ create: { enumerable: true, value: (/** @type {ColorLevel | undefined} */ lvl) => _createModule(lvl) },
539
+ detect: { enumerable: true, value: _detect },
540
+ strip: { enumerable: true, value: strip },
541
+ width: { enumerable: true, value: width },
542
+ truncate: { enumerable: true, value: truncate },
543
+ link: {
544
+ enumerable: true,
545
+ value: (/** @type {string} */ text, /** @type {string} */ url) =>
546
+ currentLevel() > 0 && _supportsHyperlinks() ? `\u001b]8;;${url}\u0007${text}\u001b]8;;\u0007` : `${text} (${url})`,
547
+ },
548
+ gradient: { enumerable: true, value: gradient },
549
+ });
550
+ return root;
551
+ };
552
+
553
+ /** @type {ColorModule} */
554
+ const color = _createModule(undefined);
555
+
556
+ module.exports = color;