ansimax 1.1.0 → 1.1.2
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/CHANGELOG.md +324 -0
- package/README.es.md +148 -47
- package/README.md +147 -46
- package/dist/index.js +39 -8
- package/dist/index.mjs +39 -8
- package/examples/01-quick-smoke.ts +121 -0
- package/examples/02-colors-gradients.ts +108 -0
- package/examples/03-ascii-banners.ts +81 -0
- package/examples/04-trees.ts +117 -0
- package/examples/05-components.ts +96 -0
- package/examples/06-pixel-art.ts +116 -0
- package/examples/07-animations.ts +68 -0
- package/examples/08-loaders.ts +98 -0
- package/examples/09-themes.ts +90 -0
- package/examples/10-everything.ts +133 -0
- package/examples/all-in-one.cjs +210 -0
- package/examples/all-in-one.mjs +203 -0
- package/examples/tsconfig.json +18 -18
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
_Colors • Gradients • Animations • ASCII Art • Pixel Art • Trees • Components • Themes_
|
|
8
8
|
|
|
9
9
|
[](LICENSE)
|
|
10
|
-
[](https://www.npmjs.com/package/ansimax)
|
|
11
11
|
[](tsconfig.json)
|
|
12
12
|
[](#testing)
|
|
13
13
|
[](#testing)
|
|
@@ -20,6 +20,27 @@ _Colors • Gradients • Animations • ASCII Art • Pixel Art • Trees • C
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
<div align="center">
|
|
24
|
+
|
|
25
|
+
### 🎬 Preview
|
|
26
|
+
|
|
27
|
+
<table>
|
|
28
|
+
<tr>
|
|
29
|
+
<td align="center">
|
|
30
|
+
<strong>Animations</strong><br/>
|
|
31
|
+
<img src="media/animations.gif" alt="Ansimax animations demo" width="420"/>
|
|
32
|
+
</td>
|
|
33
|
+
<td align="center">
|
|
34
|
+
<strong>Loaders</strong><br/>
|
|
35
|
+
<img src="media/loaders.gif" alt="Ansimax loaders demo" width="420"/>
|
|
36
|
+
</td>
|
|
37
|
+
</tr>
|
|
38
|
+
</table>
|
|
39
|
+
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
23
44
|
## 🌟 What is Ansimax?
|
|
24
45
|
|
|
25
46
|
Ansimax is a **batteries-included rendering library** for building beautiful terminal UIs in Node.js. One package replaces a stack of 8+ dependencies — colors, gradients, ASCII art, spinners, progress bars, tables, menus, trees, themes, pixel art — combined into a single coherent TypeScript API with **zero runtime dependencies**.
|
|
@@ -29,14 +50,14 @@ npm install ansimax
|
|
|
29
50
|
```
|
|
30
51
|
|
|
31
52
|
```ts
|
|
32
|
-
import { color, gradient, ascii, loader } from 'ansimax';
|
|
53
|
+
import { color, gradient, ascii, loader, sleep } from 'ansimax';
|
|
33
54
|
|
|
34
55
|
console.log(ascii.banner('hello', {
|
|
35
56
|
colorFn: (t) => gradient(t, ['#ff79c6', '#bd93f9', '#8be9fd']),
|
|
36
57
|
}));
|
|
37
58
|
|
|
38
59
|
const stop = loader.spin('Building project', { color: '#bd93f9' });
|
|
39
|
-
await
|
|
60
|
+
await sleep(1500);
|
|
40
61
|
stop('Build complete', true);
|
|
41
62
|
```
|
|
42
63
|
|
|
@@ -102,14 +123,14 @@ yarn add ansimax
|
|
|
102
123
|
## ⚡ 30-second example
|
|
103
124
|
|
|
104
125
|
```ts
|
|
105
|
-
import { color, gradient, loader, ascii } from 'ansimax';
|
|
126
|
+
import { color, gradient, loader, ascii, sleep } from 'ansimax';
|
|
106
127
|
|
|
107
128
|
console.log(ascii.banner('deploy', {
|
|
108
129
|
colorFn: (t) => gradient(t, ['#ff6b6b', '#feca57', '#48dbfb']),
|
|
109
130
|
}));
|
|
110
131
|
|
|
111
132
|
const stop = loader.spin('Building project', { color: '#bd93f9' });
|
|
112
|
-
await
|
|
133
|
+
await sleep(1500); // simulate async work
|
|
113
134
|
stop('Build complete', true); // ✓ + success color
|
|
114
135
|
|
|
115
136
|
console.log(color.green('✓') + ' Ready in ' + color.bold('1.4s'));
|
|
@@ -166,12 +187,19 @@ console.log(themes.primary('cyberpunk primary'));
|
|
|
166
187
|
<img src="media/colors.png" alt="Colors and gradients" />
|
|
167
188
|
|
|
168
189
|
```ts
|
|
169
|
-
import { color, gradient } from 'ansimax';
|
|
190
|
+
import { color, gradient, rainbow } from 'ansimax';
|
|
191
|
+
|
|
192
|
+
// Basic colors
|
|
193
|
+
console.log(color.red('red'), color.green('green'), color.blue('blue'));
|
|
170
194
|
|
|
171
|
-
|
|
172
|
-
color.bold(
|
|
173
|
-
|
|
174
|
-
|
|
195
|
+
// Style modifiers
|
|
196
|
+
console.log(color.bold('bold'), color.italic('italic'), color.underline('underlined'));
|
|
197
|
+
|
|
198
|
+
// Multi-stop gradient
|
|
199
|
+
console.log(gradient('fire to ocean', ['#ff6b6b', '#feca57', '#48dbfb']));
|
|
200
|
+
|
|
201
|
+
// Built-in rainbow preset
|
|
202
|
+
console.log(rainbow('built-in rainbow preset'));
|
|
175
203
|
```
|
|
176
204
|
|
|
177
205
|
### ASCII Art
|
|
@@ -181,13 +209,13 @@ color.rainbow('built-in rainbow preset');
|
|
|
181
209
|
```ts
|
|
182
210
|
import { ascii, gradient } from 'ansimax';
|
|
183
211
|
|
|
184
|
-
ascii.banner('HELLO', {
|
|
212
|
+
console.log(ascii.banner('HELLO', {
|
|
185
213
|
font: 'big',
|
|
186
214
|
align: 'center',
|
|
187
215
|
colorFn: (t) => gradient(t, ['#ff79c6', '#bd93f9']),
|
|
188
|
-
});
|
|
216
|
+
}));
|
|
189
217
|
|
|
190
|
-
ascii.box('Rainbow box!', { padding: 1, borderStyle: 'rounded' });
|
|
218
|
+
console.log(ascii.box('Rainbow box!', { padding: 1, borderStyle: 'rounded' }));
|
|
191
219
|
```
|
|
192
220
|
|
|
193
221
|
### Trees
|
|
@@ -214,7 +242,7 @@ console.log(project.render({
|
|
|
214
242
|
<img src="media/pixel_art.png" alt="Pixel art" />
|
|
215
243
|
|
|
216
244
|
```ts
|
|
217
|
-
import { images, createCanvas, gradientRect } from 'ansimax';
|
|
245
|
+
import { images, createCanvas, gradientRect, SPRITES } from 'ansimax';
|
|
218
246
|
|
|
219
247
|
// Built-in sprite
|
|
220
248
|
console.log(images.sprite('heart'));
|
|
@@ -230,7 +258,8 @@ console.log(gradientRect({
|
|
|
230
258
|
const c = createCanvas(40, 10);
|
|
231
259
|
c.fill({ r: 18, g: 18, b: 38 });
|
|
232
260
|
c.drawCircle(20, 5, 4, { r: 255, g: 200, b: 0 }, true);
|
|
233
|
-
|
|
261
|
+
const starSprite = SPRITES.star;
|
|
262
|
+
if (starSprite) c.drawSprite(2, 2, starSprite.pixels);
|
|
234
263
|
c.print();
|
|
235
264
|
```
|
|
236
265
|
|
|
@@ -241,15 +270,15 @@ c.print();
|
|
|
241
270
|
```ts
|
|
242
271
|
import { components, color } from 'ansimax';
|
|
243
272
|
|
|
244
|
-
components.table([
|
|
273
|
+
console.log(components.table([
|
|
245
274
|
['Module', 'Status', 'Coverage'],
|
|
246
275
|
['colors', color.green('● ready'), '100%'],
|
|
247
276
|
['animations', color.green('● ready'), '100%'],
|
|
248
277
|
['loaders', color.green('● ready'), '100%'],
|
|
249
|
-
], { borderStyle: 'rounded' });
|
|
278
|
+
], { borderStyle: 'rounded' }));
|
|
250
279
|
|
|
251
|
-
components.badge('VERSION', 'v1.1.
|
|
252
|
-
components.badge('BUILD', 'passing');
|
|
280
|
+
console.log(components.badge('VERSION', 'v1.1.2'));
|
|
281
|
+
console.log(components.badge('BUILD', 'passing'));
|
|
253
282
|
```
|
|
254
283
|
|
|
255
284
|
### Timeline
|
|
@@ -257,22 +286,24 @@ components.badge('BUILD', 'passing');
|
|
|
257
286
|
<img src="media/timeline.png" alt="Timeline" />
|
|
258
287
|
|
|
259
288
|
```ts
|
|
260
|
-
components
|
|
289
|
+
import { components } from 'ansimax';
|
|
290
|
+
|
|
291
|
+
console.log(components.timeline([
|
|
261
292
|
{ label: 'Project init', done: true, time: '10:00' },
|
|
262
293
|
{ label: 'Build pipeline', done: true, time: '10:15' },
|
|
263
294
|
{ label: 'Run tests', done: false, time: '10:32' },
|
|
264
295
|
{ label: 'Deploy to npm', done: false },
|
|
265
|
-
]);
|
|
296
|
+
]));
|
|
266
297
|
```
|
|
267
298
|
|
|
268
299
|
### Loaders & Progress
|
|
269
300
|
|
|
270
301
|
```ts
|
|
271
|
-
import { loader } from 'ansimax';
|
|
302
|
+
import { loader, sleep } from 'ansimax';
|
|
272
303
|
|
|
273
304
|
// Spinner with success/failure
|
|
274
305
|
const stop = loader.spin('Loading...', { color: '#bd93f9' });
|
|
275
|
-
await
|
|
306
|
+
await sleep(1500);
|
|
276
307
|
stop('Done!', true); // ✓ green icon
|
|
277
308
|
|
|
278
309
|
// Animated progress bar
|
|
@@ -282,18 +313,22 @@ await loader.progressAnimate(100, 'Downloading', {
|
|
|
282
313
|
|
|
283
314
|
// Hierarchical tasks with parallel execution
|
|
284
315
|
await loader.tasks([
|
|
285
|
-
{
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
316
|
+
{
|
|
317
|
+
text: 'Build',
|
|
318
|
+
fn: async () => await sleep(500),
|
|
319
|
+
subtasks: [
|
|
320
|
+
{ text: 'TypeScript', fn: async () => await sleep(800) },
|
|
321
|
+
{ text: 'Bundle', fn: async () => await sleep(600) },
|
|
322
|
+
],
|
|
323
|
+
},
|
|
324
|
+
{ text: 'Test', fn: async () => await sleep(700) },
|
|
290
325
|
], { parallel: true });
|
|
291
326
|
```
|
|
292
327
|
|
|
293
328
|
### Animations
|
|
294
329
|
|
|
295
330
|
```ts
|
|
296
|
-
import { animate, gradient } from 'ansimax';
|
|
331
|
+
import { animate, gradient, sleep } from 'ansimax';
|
|
297
332
|
|
|
298
333
|
await animate.typewriter('Welcome to the deployment wizard...', {
|
|
299
334
|
speed: 30,
|
|
@@ -304,9 +339,9 @@ await animate.fadeIn('Loading complete', { duration: 600 });
|
|
|
304
339
|
|
|
305
340
|
// Race steps against a timeout — never hang
|
|
306
341
|
await animate.parallel([
|
|
307
|
-
async () => await
|
|
308
|
-
async () => await
|
|
309
|
-
async () => await
|
|
342
|
+
async () => await sleep(500), // simulated network check
|
|
343
|
+
async () => await sleep(700), // simulated database check
|
|
344
|
+
async () => await sleep(400), // simulated auth check
|
|
310
345
|
], { timeout: 5000 });
|
|
311
346
|
```
|
|
312
347
|
|
|
@@ -319,7 +354,7 @@ import { themes, createTheme } from 'ansimax';
|
|
|
319
354
|
|
|
320
355
|
// Built-in themes
|
|
321
356
|
themes.use('dracula');
|
|
322
|
-
themes.primary('hello');
|
|
357
|
+
console.log(themes.primary('hello'));
|
|
323
358
|
|
|
324
359
|
// Listen for changes
|
|
325
360
|
const off = themes.onChange((newTheme, oldTheme) => {
|
|
@@ -329,28 +364,60 @@ const off = themes.onChange((newTheme, oldTheme) => {
|
|
|
329
364
|
// Multi-tenant: each instance fully isolated
|
|
330
365
|
const tenantA = createTheme('nord');
|
|
331
366
|
const tenantB = createTheme('matrix');
|
|
332
|
-
|
|
367
|
+
|
|
368
|
+
// Define a custom theme and register it ONLY in tenantA
|
|
369
|
+
tenantA.register('custom', {
|
|
370
|
+
name: 'Custom',
|
|
371
|
+
primary: '#ff5e5e',
|
|
372
|
+
secondary: '#5e5eff',
|
|
373
|
+
accent: '#5eff5e',
|
|
374
|
+
success: '#10b981',
|
|
375
|
+
warning: '#fbbf24',
|
|
376
|
+
error: '#ef4444',
|
|
377
|
+
info: '#06b6d4',
|
|
378
|
+
muted: '#6b7280',
|
|
379
|
+
bg: '#1e293b',
|
|
380
|
+
surface: '#334155',
|
|
381
|
+
text: '#f1f5f9',
|
|
382
|
+
gradient: ['#ff5e5e', '#5eff5e', '#5e5eff'],
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
console.log('tenantA themes include custom?', tenantA.list().includes('custom'));
|
|
386
|
+
console.log('tenantB themes include custom?', tenantB.list().includes('custom'));
|
|
387
|
+
// ↑ false — full isolation
|
|
333
388
|
```
|
|
334
389
|
|
|
335
390
|
---
|
|
336
391
|
|
|
337
392
|
## 📚 Examples
|
|
338
393
|
|
|
339
|
-
|
|
394
|
+
Eleven production-grade examples ship in the npm package and are runnable directly. Find them in [`/examples`](./examples) once you install:
|
|
340
395
|
|
|
341
396
|
| File | What it demonstrates |
|
|
342
397
|
|---|---|
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
398
|
+
| `01-quick-smoke.ts` | Quick smoke test — verifies every major import works |
|
|
399
|
+
| `02-colors-gradients.ts` | Every color fn, gradient types, presets, compose, chain API |
|
|
400
|
+
| `03-ascii-banners.ts` | Banners (`big`/`small`), 6 box styles, dividers, logo composer |
|
|
401
|
+
| `04-trees.ts` | Tree builder + plain-data API, 4 styles, palettes, algorithms (walk/find/map/filter) |
|
|
402
|
+
| `05-components.ts` | Tables, badges, status, sections, columns, timelines, progress bars |
|
|
403
|
+
| `06-pixel-art.ts` | Sprites, custom canvas, gradient rects with dither, transforms (flip/rotate) |
|
|
404
|
+
| `07-animations.ts` | typewriter, fadeIn/Out, slide, pulse, wave, glitch, reveal |
|
|
405
|
+
| `08-loaders.ts` | spinner styles, animated progress, hierarchical tasks, countdown |
|
|
406
|
+
| `09-themes.ts` | All 8 built-in themes, listeners, custom theme registration, per-instance isolation |
|
|
407
|
+
| `10-everything.ts` | Comprehensive showcase — every module exercised in one cohesive demo |
|
|
408
|
+
| `all-in-one.mjs` | Full demo in **ESM** (plain JS with `import`) — no TypeScript needed |
|
|
409
|
+
| `all-in-one.cjs` | Full demo in **CommonJS** (plain JS with `require`) — no TypeScript needed |
|
|
350
410
|
|
|
351
411
|
Run any example with:
|
|
352
412
|
```bash
|
|
353
|
-
|
|
413
|
+
# TypeScript examples
|
|
414
|
+
npx tsx examples/10-everything.ts
|
|
415
|
+
|
|
416
|
+
# Plain JS — ESM
|
|
417
|
+
node examples/all-in-one.mjs
|
|
418
|
+
|
|
419
|
+
# Plain JS — CommonJS
|
|
420
|
+
node examples/all-in-one.cjs
|
|
354
421
|
```
|
|
355
422
|
|
|
356
423
|
---
|
|
@@ -388,11 +455,11 @@ const off = onConfigKeyChange('theme', (newTheme, oldTheme) => {
|
|
|
388
455
|
|
|
389
456
|
// Temporary override + auto-restore on completion or throw
|
|
390
457
|
await withConfig({ animationSpeed: 'fast' }, async () => {
|
|
391
|
-
|
|
458
|
+
// ...your fast-mode code here...
|
|
392
459
|
});
|
|
393
460
|
|
|
394
461
|
// Strict mode catches config typos
|
|
395
|
-
configure({ unknwnKey: 'x' }, { strict: true }); // throws RangeError
|
|
462
|
+
// configure({ unknwnKey: 'x' }, { strict: true }); // throws RangeError
|
|
396
463
|
```
|
|
397
464
|
|
|
398
465
|
---
|
|
@@ -604,7 +671,7 @@ ansimax/
|
|
|
604
671
|
│ ├── trees/ Tree builder + algorithms
|
|
605
672
|
│ ├── utils/ ANSI primitives + helpers
|
|
606
673
|
│ └── configure.ts Global config + subscribers
|
|
607
|
-
├── examples/
|
|
674
|
+
├── examples/ 10 examples (TS) + 2 (JS — ESM & CJS) — all features covered
|
|
608
675
|
└── __tests__/ 16 test suites, 1700+ tests
|
|
609
676
|
```
|
|
610
677
|
|
|
@@ -612,6 +679,28 @@ ansimax/
|
|
|
612
679
|
|
|
613
680
|
## 📝 Changelog
|
|
614
681
|
|
|
682
|
+
### v1.1.2 — Maturity & robustness
|
|
683
|
+
|
|
684
|
+
Patch release focused on quality refinements — no API changes.
|
|
685
|
+
|
|
686
|
+
- 🛡️ **`process.setMaxListeners` defensive bump** — prevents `MaxListenersExceededWarning` in HMR / nodemon / ts-node-dev setups where ansimax modules re-register cursor-restore handlers
|
|
687
|
+
- 🧪 **Uniform `TypeError` for theme validation** — `themes.register()` now consistently throws `TypeError` for structural / type errors (was a mix of `Error` and `TypeError`)
|
|
688
|
+
- 🎯 **`themes.use()` throws `RangeError`** for unknown theme names (was `Error`) — better semantic match for "value out of allowed set"
|
|
689
|
+
- 📝 **Cleaner barrel re-exports** — header comment now documents legacy aliases and recommends canonical names
|
|
690
|
+
|
|
691
|
+
Drop-in replacement for `1.1.1`.
|
|
692
|
+
|
|
693
|
+
### v1.1.1 — Bug fixes + cleaner examples
|
|
694
|
+
|
|
695
|
+
Patch release fixing two bugs from real-world v1.1.0 testing, plus a refreshed examples folder.
|
|
696
|
+
|
|
697
|
+
- 🐛 **Fixed `box()` crash** with `padding: { x, y }` — now gracefully falls back to default for non-numeric padding (also handles NaN, Infinity, strings)
|
|
698
|
+
- 🐛 **Fixed `components.menu()` cursor leak** on abrupt exit (Ctrl+C, SIGTERM) — emergency cleanup handlers now restore the cursor even when the process is killed mid-menu
|
|
699
|
+
- 📚 **New examples** — 10 TypeScript examples + 2 plain JS variants (`all-in-one.mjs` for ESM, `all-in-one.cjs` for CommonJS)
|
|
700
|
+
- 📖 **READMEs updated** — preview GIFs in the header, comprehensive showcase GIF in the footer
|
|
701
|
+
|
|
702
|
+
No API changes — drop-in replacement for `1.1.0`.
|
|
703
|
+
|
|
615
704
|
### v1.1.0 — Comprehensive hardening + new features
|
|
616
705
|
|
|
617
706
|
A massive robustness pass across every module, plus a new `trees` module. **100% backward compatible** — every existing API works identically.
|
|
@@ -676,6 +765,18 @@ If Ansimax saves you time, please star the repo on [GitHub](https://github.com/B
|
|
|
676
765
|
|
|
677
766
|
---
|
|
678
767
|
|
|
768
|
+
## 🎬 Full showcase
|
|
769
|
+
|
|
770
|
+
<div align="center">
|
|
771
|
+
|
|
772
|
+
<img src="media/all-ansimax.gif" alt="Ansimax full showcase — everything in action" width="720"/>
|
|
773
|
+
|
|
774
|
+
_All features in action — typewriter, gradients, ASCII banners, trees, tables, spinners, themes, and pixel art_
|
|
775
|
+
|
|
776
|
+
</div>
|
|
777
|
+
|
|
778
|
+
---
|
|
779
|
+
|
|
679
780
|
## 📜 License
|
|
680
781
|
|
|
681
782
|
[Apache License 2.0](LICENSE) © 2026 Brashkie
|
package/dist/index.js
CHANGED
|
@@ -225,6 +225,11 @@ var cursor = {
|
|
|
225
225
|
var _exitHandlerRegistered = false;
|
|
226
226
|
var _isTestEnv = () => process.env["JEST_WORKER_ID"] !== void 0 || process.env["VITEST"] !== void 0 || process.env["NODE_ENV"] === "test";
|
|
227
227
|
var _installCursorRestoreImpl = () => {
|
|
228
|
+
try {
|
|
229
|
+
const current = process.getMaxListeners?.() ?? 10;
|
|
230
|
+
if (current < 20) process.setMaxListeners?.(20);
|
|
231
|
+
} catch {
|
|
232
|
+
}
|
|
228
233
|
const restore = () => {
|
|
229
234
|
try {
|
|
230
235
|
safeStreamWrite(process.stdout, cursor.show());
|
|
@@ -2264,7 +2269,8 @@ var banner = (text, opts = {}) => {
|
|
|
2264
2269
|
var box = (text, opts = {}) => {
|
|
2265
2270
|
const safe = ensureString2(text, "box(text)");
|
|
2266
2271
|
const { padding = 1, borderStyle = "rounded", width = null } = opts;
|
|
2267
|
-
const
|
|
2272
|
+
const padNum = typeof padding === "number" && Number.isFinite(padding) ? padding : 1;
|
|
2273
|
+
const safePadding = Math.max(0, Math.floor(padNum));
|
|
2268
2274
|
const b = BOX_STYLES[borderStyle] ?? BOX_STYLES.rounded;
|
|
2269
2275
|
const lines = safe.split("\n");
|
|
2270
2276
|
const inner = width != null ? lines.map((l) => padEnd(truncateAnsi(l, width, ""), width)) : lines;
|
|
@@ -3677,7 +3683,32 @@ var menu = (items, opts = {}) => {
|
|
|
3677
3683
|
}
|
|
3678
3684
|
cursorHidden = false;
|
|
3679
3685
|
}
|
|
3686
|
+
try {
|
|
3687
|
+
process.off("SIGINT", emergencyCleanup);
|
|
3688
|
+
process.off("SIGTERM", emergencyCleanup);
|
|
3689
|
+
process.off("exit", emergencyCleanup);
|
|
3690
|
+
} catch {
|
|
3691
|
+
}
|
|
3692
|
+
};
|
|
3693
|
+
const emergencyCleanup = () => {
|
|
3694
|
+
if (cursorHidden) {
|
|
3695
|
+
try {
|
|
3696
|
+
out.write(cursor.show());
|
|
3697
|
+
} catch {
|
|
3698
|
+
}
|
|
3699
|
+
cursorHidden = false;
|
|
3700
|
+
}
|
|
3701
|
+
try {
|
|
3702
|
+
if (inp.setRawMode) inp.setRawMode(false);
|
|
3703
|
+
} catch {
|
|
3704
|
+
}
|
|
3680
3705
|
};
|
|
3706
|
+
try {
|
|
3707
|
+
process.once("SIGINT", emergencyCleanup);
|
|
3708
|
+
process.once("SIGTERM", emergencyCleanup);
|
|
3709
|
+
process.once("exit", emergencyCleanup);
|
|
3710
|
+
} catch {
|
|
3711
|
+
}
|
|
3681
3712
|
try {
|
|
3682
3713
|
emit(cursor.hide());
|
|
3683
3714
|
cursorHidden = true;
|
|
@@ -4087,12 +4118,12 @@ var validateTheme = (t) => {
|
|
|
4087
4118
|
}
|
|
4088
4119
|
const obj = t;
|
|
4089
4120
|
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
4090
|
-
throw new
|
|
4121
|
+
throw new TypeError('Theme must have a non-empty "name" string.');
|
|
4091
4122
|
}
|
|
4092
4123
|
for (const key of REQUIRED_COLOR_KEYS) {
|
|
4093
4124
|
const value = obj[key];
|
|
4094
4125
|
if (typeof value !== "string" || !HEX_RE4.test(value)) {
|
|
4095
|
-
throw new
|
|
4126
|
+
throw new TypeError(
|
|
4096
4127
|
`Invalid hex in theme "${obj.name}" \u2192 ${key}: ${JSON.stringify(value)}. Expected #RGB or #RRGGBB.`
|
|
4097
4128
|
);
|
|
4098
4129
|
}
|
|
@@ -4101,20 +4132,20 @@ var validateTheme = (t) => {
|
|
|
4101
4132
|
const value = obj[key];
|
|
4102
4133
|
if (value === void 0) continue;
|
|
4103
4134
|
if (typeof value !== "string" || !HEX_RE4.test(value)) {
|
|
4104
|
-
throw new
|
|
4135
|
+
throw new TypeError(
|
|
4105
4136
|
`Invalid hex in theme "${obj.name}" \u2192 ${key}: ${JSON.stringify(value)}.`
|
|
4106
4137
|
);
|
|
4107
4138
|
}
|
|
4108
4139
|
}
|
|
4109
4140
|
if (!Array.isArray(obj.gradient) || obj.gradient.length < 2) {
|
|
4110
|
-
throw new
|
|
4141
|
+
throw new TypeError(
|
|
4111
4142
|
`Theme "${obj.name}" must define a "gradient" array with at least 2 colors.`
|
|
4112
4143
|
);
|
|
4113
4144
|
}
|
|
4114
4145
|
for (let i = 0; i < obj.gradient.length; i++) {
|
|
4115
4146
|
const stop = obj.gradient[i];
|
|
4116
4147
|
if (typeof stop !== "string" || !HEX_RE4.test(stop)) {
|
|
4117
|
-
throw new
|
|
4148
|
+
throw new TypeError(
|
|
4118
4149
|
`Invalid hex in theme "${obj.name}" \u2192 gradient[${i}]: ${JSON.stringify(stop)}.`
|
|
4119
4150
|
);
|
|
4120
4151
|
}
|
|
@@ -4426,8 +4457,8 @@ var themes = {
|
|
|
4426
4457
|
use(name) {
|
|
4427
4458
|
const t = _globalRegistry.get(name);
|
|
4428
4459
|
if (!t) {
|
|
4429
|
-
throw new
|
|
4430
|
-
`Theme "${name}" not found. Available: ${[..._globalRegistry.keys()].join(", ")}`
|
|
4460
|
+
throw new RangeError(
|
|
4461
|
+
`Theme "${name}" not found. Available themes: ${[..._globalRegistry.keys()].join(", ")}`
|
|
4431
4462
|
);
|
|
4432
4463
|
}
|
|
4433
4464
|
const old = _globalActive;
|
package/dist/index.mjs
CHANGED
|
@@ -53,6 +53,11 @@ var cursor = {
|
|
|
53
53
|
var _exitHandlerRegistered = false;
|
|
54
54
|
var _isTestEnv = () => process.env["JEST_WORKER_ID"] !== void 0 || process.env["VITEST"] !== void 0 || process.env["NODE_ENV"] === "test";
|
|
55
55
|
var _installCursorRestoreImpl = () => {
|
|
56
|
+
try {
|
|
57
|
+
const current = process.getMaxListeners?.() ?? 10;
|
|
58
|
+
if (current < 20) process.setMaxListeners?.(20);
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
56
61
|
const restore = () => {
|
|
57
62
|
try {
|
|
58
63
|
safeStreamWrite(process.stdout, cursor.show());
|
|
@@ -2092,7 +2097,8 @@ var banner = (text, opts = {}) => {
|
|
|
2092
2097
|
var box = (text, opts = {}) => {
|
|
2093
2098
|
const safe = ensureString2(text, "box(text)");
|
|
2094
2099
|
const { padding = 1, borderStyle = "rounded", width = null } = opts;
|
|
2095
|
-
const
|
|
2100
|
+
const padNum = typeof padding === "number" && Number.isFinite(padding) ? padding : 1;
|
|
2101
|
+
const safePadding = Math.max(0, Math.floor(padNum));
|
|
2096
2102
|
const b = BOX_STYLES[borderStyle] ?? BOX_STYLES.rounded;
|
|
2097
2103
|
const lines = safe.split("\n");
|
|
2098
2104
|
const inner = width != null ? lines.map((l) => padEnd(truncateAnsi(l, width, ""), width)) : lines;
|
|
@@ -3505,7 +3511,32 @@ var menu = (items, opts = {}) => {
|
|
|
3505
3511
|
}
|
|
3506
3512
|
cursorHidden = false;
|
|
3507
3513
|
}
|
|
3514
|
+
try {
|
|
3515
|
+
process.off("SIGINT", emergencyCleanup);
|
|
3516
|
+
process.off("SIGTERM", emergencyCleanup);
|
|
3517
|
+
process.off("exit", emergencyCleanup);
|
|
3518
|
+
} catch {
|
|
3519
|
+
}
|
|
3520
|
+
};
|
|
3521
|
+
const emergencyCleanup = () => {
|
|
3522
|
+
if (cursorHidden) {
|
|
3523
|
+
try {
|
|
3524
|
+
out.write(cursor.show());
|
|
3525
|
+
} catch {
|
|
3526
|
+
}
|
|
3527
|
+
cursorHidden = false;
|
|
3528
|
+
}
|
|
3529
|
+
try {
|
|
3530
|
+
if (inp.setRawMode) inp.setRawMode(false);
|
|
3531
|
+
} catch {
|
|
3532
|
+
}
|
|
3508
3533
|
};
|
|
3534
|
+
try {
|
|
3535
|
+
process.once("SIGINT", emergencyCleanup);
|
|
3536
|
+
process.once("SIGTERM", emergencyCleanup);
|
|
3537
|
+
process.once("exit", emergencyCleanup);
|
|
3538
|
+
} catch {
|
|
3539
|
+
}
|
|
3509
3540
|
try {
|
|
3510
3541
|
emit(cursor.hide());
|
|
3511
3542
|
cursorHidden = true;
|
|
@@ -3915,12 +3946,12 @@ var validateTheme = (t) => {
|
|
|
3915
3946
|
}
|
|
3916
3947
|
const obj = t;
|
|
3917
3948
|
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
3918
|
-
throw new
|
|
3949
|
+
throw new TypeError('Theme must have a non-empty "name" string.');
|
|
3919
3950
|
}
|
|
3920
3951
|
for (const key of REQUIRED_COLOR_KEYS) {
|
|
3921
3952
|
const value = obj[key];
|
|
3922
3953
|
if (typeof value !== "string" || !HEX_RE4.test(value)) {
|
|
3923
|
-
throw new
|
|
3954
|
+
throw new TypeError(
|
|
3924
3955
|
`Invalid hex in theme "${obj.name}" \u2192 ${key}: ${JSON.stringify(value)}. Expected #RGB or #RRGGBB.`
|
|
3925
3956
|
);
|
|
3926
3957
|
}
|
|
@@ -3929,20 +3960,20 @@ var validateTheme = (t) => {
|
|
|
3929
3960
|
const value = obj[key];
|
|
3930
3961
|
if (value === void 0) continue;
|
|
3931
3962
|
if (typeof value !== "string" || !HEX_RE4.test(value)) {
|
|
3932
|
-
throw new
|
|
3963
|
+
throw new TypeError(
|
|
3933
3964
|
`Invalid hex in theme "${obj.name}" \u2192 ${key}: ${JSON.stringify(value)}.`
|
|
3934
3965
|
);
|
|
3935
3966
|
}
|
|
3936
3967
|
}
|
|
3937
3968
|
if (!Array.isArray(obj.gradient) || obj.gradient.length < 2) {
|
|
3938
|
-
throw new
|
|
3969
|
+
throw new TypeError(
|
|
3939
3970
|
`Theme "${obj.name}" must define a "gradient" array with at least 2 colors.`
|
|
3940
3971
|
);
|
|
3941
3972
|
}
|
|
3942
3973
|
for (let i = 0; i < obj.gradient.length; i++) {
|
|
3943
3974
|
const stop = obj.gradient[i];
|
|
3944
3975
|
if (typeof stop !== "string" || !HEX_RE4.test(stop)) {
|
|
3945
|
-
throw new
|
|
3976
|
+
throw new TypeError(
|
|
3946
3977
|
`Invalid hex in theme "${obj.name}" \u2192 gradient[${i}]: ${JSON.stringify(stop)}.`
|
|
3947
3978
|
);
|
|
3948
3979
|
}
|
|
@@ -4254,8 +4285,8 @@ var themes = {
|
|
|
4254
4285
|
use(name) {
|
|
4255
4286
|
const t = _globalRegistry.get(name);
|
|
4256
4287
|
if (!t) {
|
|
4257
|
-
throw new
|
|
4258
|
-
`Theme "${name}" not found. Available: ${[..._globalRegistry.keys()].join(", ")}`
|
|
4288
|
+
throw new RangeError(
|
|
4289
|
+
`Theme "${name}" not found. Available themes: ${[..._globalRegistry.keys()].join(", ")}`
|
|
4259
4290
|
);
|
|
4260
4291
|
}
|
|
4261
4292
|
const old = _globalActive;
|