ansimax 1.1.1 → 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 CHANGED
@@ -3,6 +3,56 @@
3
3
  All notable changes to **ansimax** are documented in this file.
4
4
  This project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [1.1.2] — maturity & robustness
7
+
8
+ Patch release focused on maturity: better error semantics, defensive
9
+ defaults, and cleaner type re-exports. No API breaking changes — every
10
+ 1.1.1 program runs identically.
11
+
12
+ ### Fixed
13
+
14
+ - **CI: `jest.config.js` syntax error.** The config file used `export default {}`
15
+ (ESM syntax), which crashed in Node CommonJS context — including in
16
+ GitHub Actions runners. Fixed by switching to `module.exports = {}` to
17
+ match `useESM: false` in the ts-jest configuration. Tests now run
18
+ correctly across Linux, macOS, and Windows runners.
19
+
20
+ ### Improved
21
+
22
+ - **`process.setMaxListeners` defensive bump.** Ansimax modules
23
+ (`animations`, `frames`, `loaders`, `utils/ansi`) each register
24
+ `SIGINT` / `SIGTERM` / `exit` handlers for crash-safe cursor
25
+ restoration. With Node's default cap of 10, hot-reload setups
26
+ (Vite HMR, nodemon, ts-node-dev) could occasionally emit
27
+ `MaxListenersExceededWarning`. We now bump the cap to 20 on first
28
+ install — silently and safely, only if the current limit is lower.
29
+ Production apps unaffected.
30
+ - **Uniform `TypeError` for theme validation.** `themes.register()`
31
+ now throws `TypeError` for any structural / type issue (missing
32
+ fields, non-string `name`, invalid hex), matching the rest of the
33
+ validation surface. Previously it threw a mix of `Error` and
34
+ `TypeError`, which made `try / catch` filtering inconsistent.
35
+ - **`themes.use()` throws `RangeError`** for unknown theme names
36
+ (was `Error`). `RangeError` better reflects "value out of allowed
37
+ set" semantics — same standard library convention as `Array(-1)`.
38
+ Error message now also says "Available themes:" instead of
39
+ "Available:" for clarity.
40
+ - **Cleaner type re-exports in the barrel.** Added a header comment
41
+ explaining the legacy aliases (`stripAnsiColors`, `stripAnsiCodes`)
42
+ and recommending `stripAnsi` for new code. Version string in the
43
+ barrel header updated from the stale `v1.0.0` to `v1.1.2`.
44
+
45
+ ### Notes
46
+
47
+ - All 1848 tests pass; 4 new tests cover the error-type guarantees.
48
+ - The error-type changes are technically observable via `instanceof`
49
+ checks, but `RangeError` and `TypeError` both extend `Error`, so any
50
+ `catch (e: Error)` block keeps working. We classify this as a
51
+ non-breaking quality-of-life improvement.
52
+ - No new dependencies — still zero runtime deps.
53
+
54
+ ---
55
+
6
56
  ## [1.1.1] — bug fixes + improved examples
7
57
 
8
58
  Patch release with two bug fixes from real-world testing of v1.1.0, plus
@@ -271,4 +321,4 @@ Hierarchical text renderer inspired by Rich's `Tree`.
271
321
  - TypeScript types exported.
272
322
  - Adaptive color rendering (NO_COLOR / FORCE_COLOR / TTY detection).
273
323
  - AbortSignal support across all blocking APIs.
274
- - 750+ tests, 85%+ coverage.
324
+ - 750+ tests, 85%+ coverage.
package/README.es.md CHANGED
@@ -7,7 +7,7 @@
7
7
  _Colores • Gradientes • Animaciones • ASCII Art • Pixel Art • Árboles • Componentes • Temas_
8
8
 
9
9
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
10
- [![npm](https://img.shields.io/badge/npm-v1.1.1-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.1.2-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg?style=flat-square)](tsconfig.json)
12
12
  [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen.svg?style=flat-square)](#testing)
13
13
  [![Tests](https://img.shields.io/badge/tests-1700%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
@@ -50,14 +50,14 @@ npm install ansimax
50
50
  ```
51
51
 
52
52
  ```ts
53
- import { color, gradient, ascii, loader } from 'ansimax';
53
+ import { color, gradient, ascii, loader, sleep } from 'ansimax';
54
54
 
55
55
  console.log(ascii.banner('hola', {
56
56
  colorFn: (t) => gradient(t, ['#ff79c6', '#bd93f9', '#8be9fd']),
57
57
  }));
58
58
 
59
59
  const stop = loader.spin('Construyendo proyecto', { color: '#bd93f9' });
60
- await algunaTareaAsync();
60
+ await sleep(1500);
61
61
  stop('Build completado', true);
62
62
  ```
63
63
 
@@ -123,14 +123,14 @@ yarn add ansimax
123
123
  ## ⚡ Ejemplo en 30 segundos
124
124
 
125
125
  ```ts
126
- import { color, gradient, loader, ascii } from 'ansimax';
126
+ import { color, gradient, loader, ascii, sleep } from 'ansimax';
127
127
 
128
128
  console.log(ascii.banner('deploy', {
129
129
  colorFn: (t) => gradient(t, ['#ff6b6b', '#feca57', '#48dbfb']),
130
130
  }));
131
131
 
132
132
  const stop = loader.spin('Construyendo proyecto', { color: '#bd93f9' });
133
- await algunaTareaAsync();
133
+ await sleep(1500); // simula trabajo asíncrono
134
134
  stop('Build completado', true); // ✓ + color de éxito
135
135
 
136
136
  console.log(color.green('✓') + ' Listo en ' + color.bold('1.4s'));
@@ -187,12 +187,19 @@ console.log(themes.primary('primary de cyberpunk'));
187
187
  <img src="media/colors.png" alt="Colores y gradientes" />
188
188
 
189
189
  ```ts
190
- import { color, gradient } from 'ansimax';
190
+ import { color, gradient, rainbow } from 'ansimax';
191
191
 
192
- color.red('rojo'); color.green('verde'); color.blue('azul');
193
- color.bold(texto); color.italic(texto); color.underline(texto);
194
- gradient('fuego a océano', ['#ff6b6b', '#feca57', '#48dbfb']);
195
- color.rainbow('preset rainbow integrado');
192
+ // Colores básicos
193
+ console.log(color.red('rojo'), color.green('verde'), color.blue('azul'));
194
+
195
+ // Modificadores de estilo
196
+ console.log(color.bold('negrita'), color.italic('itálica'), color.underline('subrayado'));
197
+
198
+ // Gradiente multi-stop
199
+ console.log(gradient('fuego a océano', ['#ff6b6b', '#feca57', '#48dbfb']));
200
+
201
+ // Preset rainbow integrado
202
+ console.log(rainbow('preset rainbow integrado'));
196
203
  ```
197
204
 
198
205
  ### ASCII Art
@@ -202,13 +209,13 @@ color.rainbow('preset rainbow integrado');
202
209
  ```ts
203
210
  import { ascii, gradient } from 'ansimax';
204
211
 
205
- ascii.banner('HOLA', {
212
+ console.log(ascii.banner('HOLA', {
206
213
  font: 'big',
207
214
  align: 'center',
208
215
  colorFn: (t) => gradient(t, ['#ff79c6', '#bd93f9']),
209
- });
216
+ }));
210
217
 
211
- ascii.box('¡Caja arcoiris!', { padding: 1, borderStyle: 'rounded' });
218
+ console.log(ascii.box('¡Caja arcoiris!', { padding: 1, borderStyle: 'rounded' }));
212
219
  ```
213
220
 
214
221
  ### Árboles
@@ -235,7 +242,7 @@ console.log(proyecto.render({
235
242
  <img src="media/pixel_art.png" alt="Pixel art" />
236
243
 
237
244
  ```ts
238
- import { images, createCanvas, gradientRect } from 'ansimax';
245
+ import { images, createCanvas, gradientRect, SPRITES } from 'ansimax';
239
246
 
240
247
  // Sprite integrado
241
248
  console.log(images.sprite('heart'));
@@ -251,7 +258,8 @@ console.log(gradientRect({
251
258
  const c = createCanvas(40, 10);
252
259
  c.fill({ r: 18, g: 18, b: 38 });
253
260
  c.drawCircle(20, 5, 4, { r: 255, g: 200, b: 0 }, true);
254
- c.drawSprite(2, 2, images.sprites.star!.pixels);
261
+ const starSprite = SPRITES.star;
262
+ if (starSprite) c.drawSprite(2, 2, starSprite.pixels);
255
263
  c.print();
256
264
  ```
257
265
 
@@ -262,15 +270,15 @@ c.print();
262
270
  ```ts
263
271
  import { components, color } from 'ansimax';
264
272
 
265
- components.table([
273
+ console.log(components.table([
266
274
  ['Módulo', 'Estado', 'Cobertura'],
267
275
  ['colors', color.green('● listo'), '100%'],
268
276
  ['animations', color.green('● listo'), '100%'],
269
277
  ['loaders', color.green('● listo'), '100%'],
270
- ], { borderStyle: 'rounded' });
278
+ ], { borderStyle: 'rounded' }));
271
279
 
272
- components.badge('VERSION', 'v1.1.1');
273
- components.badge('BUILD', 'passing');
280
+ console.log(components.badge('VERSION', 'v1.1.2'));
281
+ console.log(components.badge('BUILD', 'passing'));
274
282
  ```
275
283
 
276
284
  ### Timeline
@@ -278,22 +286,24 @@ components.badge('BUILD', 'passing');
278
286
  <img src="media/timeline.png" alt="Timeline" />
279
287
 
280
288
  ```ts
281
- components.timeline([
289
+ import { components } from 'ansimax';
290
+
291
+ console.log(components.timeline([
282
292
  { label: 'Init del proyecto', done: true, time: '10:00' },
283
293
  { label: 'Pipeline de build', done: true, time: '10:15' },
284
294
  { label: 'Correr tests', done: false, time: '10:32' },
285
295
  { label: 'Deploy a npm', done: false },
286
- ]);
296
+ ]));
287
297
  ```
288
298
 
289
299
  ### Loaders y Progreso
290
300
 
291
301
  ```ts
292
- import { loader } from 'ansimax';
302
+ import { loader, sleep } from 'ansimax';
293
303
 
294
304
  // Spinner con éxito/fallo
295
305
  const stop = loader.spin('Cargando...', { color: '#bd93f9' });
296
- await tarea();
306
+ await sleep(1500);
297
307
  stop('¡Listo!', true); // ✓ ícono verde
298
308
 
299
309
  // Barra de progreso animada
@@ -303,18 +313,22 @@ await loader.progressAnimate(100, 'Descargando', {
303
313
 
304
314
  // Tareas jerárquicas con ejecución paralela
305
315
  await loader.tasks([
306
- { text: 'Build', fn: async () => build(), subtasks: [
307
- { text: 'TypeScript', fn: async () => tsc() },
308
- { text: 'Bundle', fn: async () => bundle() },
309
- ]},
310
- { text: 'Test', fn: async () => test() },
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) },
311
325
  ], { parallel: true });
312
326
  ```
313
327
 
314
328
  ### Animaciones
315
329
 
316
330
  ```ts
317
- import { animate, gradient } from 'ansimax';
331
+ import { animate, gradient, sleep } from 'ansimax';
318
332
 
319
333
  await animate.typewriter('Bienvenido al wizard de deployment...', {
320
334
  speed: 30,
@@ -325,9 +339,9 @@ await animate.fadeIn('Carga completa', { duration: 600 });
325
339
 
326
340
  // Carrera de pasos contra timeout — nunca se cuelga
327
341
  await animate.parallel([
328
- async () => await checkNetwork(),
329
- async () => await checkDatabase(),
330
- async () => await checkAuth(),
342
+ async () => await sleep(500), // simulación de chequeo de red
343
+ async () => await sleep(700), // simulación de chequeo de base de datos
344
+ async () => await sleep(400), // simulación de chequeo de auth
331
345
  ], { timeout: 5000 });
332
346
  ```
333
347
 
@@ -340,7 +354,7 @@ import { themes, createTheme } from 'ansimax';
340
354
 
341
355
  // Temas built-in
342
356
  themes.use('dracula');
343
- themes.primary('hola');
357
+ console.log(themes.primary('hola'));
344
358
 
345
359
  // Escuchar cambios
346
360
  const off = themes.onChange((nuevo, anterior) => {
@@ -350,7 +364,27 @@ const off = themes.onChange((nuevo, anterior) => {
350
364
  // Multi-tenant: cada instancia totalmente aislada
351
365
  const tenantA = createTheme('nord');
352
366
  const tenantB = createTheme('matrix');
353
- tenantA.register('custom', miDef); // no se filtra a tenantB
367
+
368
+ // Definir un tema personalizado y registrarlo SÓLO en 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 incluye custom?', tenantA.list().includes('custom'));
386
+ console.log('tenantB incluye custom?', tenantB.list().includes('custom'));
387
+ // ↑ false — aislamiento total
354
388
  ```
355
389
 
356
390
  ---
@@ -421,11 +455,11 @@ const off = onConfigKeyChange('theme', (nuevo, anterior) => {
421
455
 
422
456
  // Override temporal + restauración automática al completar o lanzar
423
457
  await withConfig({ animationSpeed: 'fast' }, async () => {
424
- await correrDemo();
458
+ // ...tu código en modo fast aquí...
425
459
  });
426
460
 
427
461
  // Modo strict captura typos en config
428
- configure({ unknwnKey: 'x' }, { strict: true }); // lanza RangeError
462
+ // configure({ unknwnKey: 'x' }, { strict: true }); // lanza RangeError
429
463
  ```
430
464
 
431
465
  ---
@@ -645,6 +679,17 @@ ansimax/
645
679
 
646
680
  ## 📝 Changelog
647
681
 
682
+ ### v1.1.2 — Madurez y robustez
683
+
684
+ Release patch enfocado en refinamientos de calidad — sin cambios en la API.
685
+
686
+ - 🛡️ **Bump defensivo de `process.setMaxListeners`** — previene `MaxListenersExceededWarning` en setups con HMR / nodemon / ts-node-dev donde los módulos de ansimax re-registran handlers de restauración de cursor
687
+ - 🧪 **`TypeError` uniforme para validación de themes** — `themes.register()` ahora arroja consistentemente `TypeError` para errores estructurales / de tipo (antes era mezcla de `Error` y `TypeError`)
688
+ - 🎯 **`themes.use()` arroja `RangeError`** para nombres de tema desconocidos (antes `Error`) — mejor match semántico con "valor fuera del set permitido"
689
+ - 📝 **Re-exports más limpios en el barrel** — comentario de header ahora documenta aliases legacy y recomienda nombres canónicos
690
+
691
+ Drop-in replacement para `1.1.1`.
692
+
648
693
  ### v1.1.1 — Fixes de bugs + ejemplos limpios
649
694
 
650
695
  Release patch que arregla dos bugs encontrados en testing real de v1.1.0, más una carpeta de ejemplos refrescada.
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](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
10
- [![npm](https://img.shields.io/badge/npm-v1.1.1-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.1.2-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg?style=flat-square)](tsconfig.json)
12
12
  [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen.svg?style=flat-square)](#testing)
13
13
  [![Tests](https://img.shields.io/badge/tests-1700%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
@@ -50,14 +50,14 @@ npm install ansimax
50
50
  ```
51
51
 
52
52
  ```ts
53
- import { color, gradient, ascii, loader } from 'ansimax';
53
+ import { color, gradient, ascii, loader, sleep } from 'ansimax';
54
54
 
55
55
  console.log(ascii.banner('hello', {
56
56
  colorFn: (t) => gradient(t, ['#ff79c6', '#bd93f9', '#8be9fd']),
57
57
  }));
58
58
 
59
59
  const stop = loader.spin('Building project', { color: '#bd93f9' });
60
- await someAsyncWork();
60
+ await sleep(1500);
61
61
  stop('Build complete', true);
62
62
  ```
63
63
 
@@ -123,14 +123,14 @@ yarn add ansimax
123
123
  ## ⚡ 30-second example
124
124
 
125
125
  ```ts
126
- import { color, gradient, loader, ascii } from 'ansimax';
126
+ import { color, gradient, loader, ascii, sleep } from 'ansimax';
127
127
 
128
128
  console.log(ascii.banner('deploy', {
129
129
  colorFn: (t) => gradient(t, ['#ff6b6b', '#feca57', '#48dbfb']),
130
130
  }));
131
131
 
132
132
  const stop = loader.spin('Building project', { color: '#bd93f9' });
133
- await someAsyncWork();
133
+ await sleep(1500); // simulate async work
134
134
  stop('Build complete', true); // ✓ + success color
135
135
 
136
136
  console.log(color.green('✓') + ' Ready in ' + color.bold('1.4s'));
@@ -187,12 +187,19 @@ console.log(themes.primary('cyberpunk primary'));
187
187
  <img src="media/colors.png" alt="Colors and gradients" />
188
188
 
189
189
  ```ts
190
- import { color, gradient } from 'ansimax';
190
+ import { color, gradient, rainbow } from 'ansimax';
191
191
 
192
- color.red('red'); color.green('green'); color.blue('blue');
193
- color.bold(text); color.italic(text); color.underline(text);
194
- gradient('fire to ocean', ['#ff6b6b', '#feca57', '#48dbfb']);
195
- color.rainbow('built-in rainbow preset');
192
+ // Basic colors
193
+ console.log(color.red('red'), color.green('green'), color.blue('blue'));
194
+
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'));
196
203
  ```
197
204
 
198
205
  ### ASCII Art
@@ -202,13 +209,13 @@ color.rainbow('built-in rainbow preset');
202
209
  ```ts
203
210
  import { ascii, gradient } from 'ansimax';
204
211
 
205
- ascii.banner('HELLO', {
212
+ console.log(ascii.banner('HELLO', {
206
213
  font: 'big',
207
214
  align: 'center',
208
215
  colorFn: (t) => gradient(t, ['#ff79c6', '#bd93f9']),
209
- });
216
+ }));
210
217
 
211
- ascii.box('Rainbow box!', { padding: 1, borderStyle: 'rounded' });
218
+ console.log(ascii.box('Rainbow box!', { padding: 1, borderStyle: 'rounded' }));
212
219
  ```
213
220
 
214
221
  ### Trees
@@ -235,7 +242,7 @@ console.log(project.render({
235
242
  <img src="media/pixel_art.png" alt="Pixel art" />
236
243
 
237
244
  ```ts
238
- import { images, createCanvas, gradientRect } from 'ansimax';
245
+ import { images, createCanvas, gradientRect, SPRITES } from 'ansimax';
239
246
 
240
247
  // Built-in sprite
241
248
  console.log(images.sprite('heart'));
@@ -251,7 +258,8 @@ console.log(gradientRect({
251
258
  const c = createCanvas(40, 10);
252
259
  c.fill({ r: 18, g: 18, b: 38 });
253
260
  c.drawCircle(20, 5, 4, { r: 255, g: 200, b: 0 }, true);
254
- c.drawSprite(2, 2, images.sprites.star!.pixels);
261
+ const starSprite = SPRITES.star;
262
+ if (starSprite) c.drawSprite(2, 2, starSprite.pixels);
255
263
  c.print();
256
264
  ```
257
265
 
@@ -262,15 +270,15 @@ c.print();
262
270
  ```ts
263
271
  import { components, color } from 'ansimax';
264
272
 
265
- components.table([
273
+ console.log(components.table([
266
274
  ['Module', 'Status', 'Coverage'],
267
275
  ['colors', color.green('● ready'), '100%'],
268
276
  ['animations', color.green('● ready'), '100%'],
269
277
  ['loaders', color.green('● ready'), '100%'],
270
- ], { borderStyle: 'rounded' });
278
+ ], { borderStyle: 'rounded' }));
271
279
 
272
- components.badge('VERSION', 'v1.1.1');
273
- components.badge('BUILD', 'passing');
280
+ console.log(components.badge('VERSION', 'v1.1.2'));
281
+ console.log(components.badge('BUILD', 'passing'));
274
282
  ```
275
283
 
276
284
  ### Timeline
@@ -278,22 +286,24 @@ components.badge('BUILD', 'passing');
278
286
  <img src="media/timeline.png" alt="Timeline" />
279
287
 
280
288
  ```ts
281
- components.timeline([
289
+ import { components } from 'ansimax';
290
+
291
+ console.log(components.timeline([
282
292
  { label: 'Project init', done: true, time: '10:00' },
283
293
  { label: 'Build pipeline', done: true, time: '10:15' },
284
294
  { label: 'Run tests', done: false, time: '10:32' },
285
295
  { label: 'Deploy to npm', done: false },
286
- ]);
296
+ ]));
287
297
  ```
288
298
 
289
299
  ### Loaders & Progress
290
300
 
291
301
  ```ts
292
- import { loader } from 'ansimax';
302
+ import { loader, sleep } from 'ansimax';
293
303
 
294
304
  // Spinner with success/failure
295
305
  const stop = loader.spin('Loading...', { color: '#bd93f9' });
296
- await work();
306
+ await sleep(1500);
297
307
  stop('Done!', true); // ✓ green icon
298
308
 
299
309
  // Animated progress bar
@@ -303,18 +313,22 @@ await loader.progressAnimate(100, 'Downloading', {
303
313
 
304
314
  // Hierarchical tasks with parallel execution
305
315
  await loader.tasks([
306
- { text: 'Build', fn: async () => build(), subtasks: [
307
- { text: 'TypeScript', fn: async () => tsc() },
308
- { text: 'Bundle', fn: async () => bundle() },
309
- ]},
310
- { text: 'Test', fn: async () => test() },
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) },
311
325
  ], { parallel: true });
312
326
  ```
313
327
 
314
328
  ### Animations
315
329
 
316
330
  ```ts
317
- import { animate, gradient } from 'ansimax';
331
+ import { animate, gradient, sleep } from 'ansimax';
318
332
 
319
333
  await animate.typewriter('Welcome to the deployment wizard...', {
320
334
  speed: 30,
@@ -325,9 +339,9 @@ await animate.fadeIn('Loading complete', { duration: 600 });
325
339
 
326
340
  // Race steps against a timeout — never hang
327
341
  await animate.parallel([
328
- async () => await checkNetwork(),
329
- async () => await checkDatabase(),
330
- async () => await checkAuth(),
342
+ async () => await sleep(500), // simulated network check
343
+ async () => await sleep(700), // simulated database check
344
+ async () => await sleep(400), // simulated auth check
331
345
  ], { timeout: 5000 });
332
346
  ```
333
347
 
@@ -340,7 +354,7 @@ import { themes, createTheme } from 'ansimax';
340
354
 
341
355
  // Built-in themes
342
356
  themes.use('dracula');
343
- themes.primary('hello');
357
+ console.log(themes.primary('hello'));
344
358
 
345
359
  // Listen for changes
346
360
  const off = themes.onChange((newTheme, oldTheme) => {
@@ -350,7 +364,27 @@ const off = themes.onChange((newTheme, oldTheme) => {
350
364
  // Multi-tenant: each instance fully isolated
351
365
  const tenantA = createTheme('nord');
352
366
  const tenantB = createTheme('matrix');
353
- tenantA.register('custom', myDef); // doesn't leak to tenantB
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
354
388
  ```
355
389
 
356
390
  ---
@@ -421,11 +455,11 @@ const off = onConfigKeyChange('theme', (newTheme, oldTheme) => {
421
455
 
422
456
  // Temporary override + auto-restore on completion or throw
423
457
  await withConfig({ animationSpeed: 'fast' }, async () => {
424
- await runDemo();
458
+ // ...your fast-mode code here...
425
459
  });
426
460
 
427
461
  // Strict mode catches config typos
428
- configure({ unknwnKey: 'x' }, { strict: true }); // throws RangeError
462
+ // configure({ unknwnKey: 'x' }, { strict: true }); // throws RangeError
429
463
  ```
430
464
 
431
465
  ---
@@ -645,6 +679,17 @@ ansimax/
645
679
 
646
680
  ## 📝 Changelog
647
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
+
648
693
  ### v1.1.1 — Bug fixes + cleaner examples
649
694
 
650
695
  Patch release fixing two bugs from real-world v1.1.0 testing, plus a refreshed examples folder.
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());
@@ -4113,12 +4118,12 @@ var validateTheme = (t) => {
4113
4118
  }
4114
4119
  const obj = t;
4115
4120
  if (typeof obj.name !== "string" || obj.name.length === 0) {
4116
- throw new Error('Theme must have a non-empty "name" string.');
4121
+ throw new TypeError('Theme must have a non-empty "name" string.');
4117
4122
  }
4118
4123
  for (const key of REQUIRED_COLOR_KEYS) {
4119
4124
  const value = obj[key];
4120
4125
  if (typeof value !== "string" || !HEX_RE4.test(value)) {
4121
- throw new Error(
4126
+ throw new TypeError(
4122
4127
  `Invalid hex in theme "${obj.name}" \u2192 ${key}: ${JSON.stringify(value)}. Expected #RGB or #RRGGBB.`
4123
4128
  );
4124
4129
  }
@@ -4127,20 +4132,20 @@ var validateTheme = (t) => {
4127
4132
  const value = obj[key];
4128
4133
  if (value === void 0) continue;
4129
4134
  if (typeof value !== "string" || !HEX_RE4.test(value)) {
4130
- throw new Error(
4135
+ throw new TypeError(
4131
4136
  `Invalid hex in theme "${obj.name}" \u2192 ${key}: ${JSON.stringify(value)}.`
4132
4137
  );
4133
4138
  }
4134
4139
  }
4135
4140
  if (!Array.isArray(obj.gradient) || obj.gradient.length < 2) {
4136
- throw new Error(
4141
+ throw new TypeError(
4137
4142
  `Theme "${obj.name}" must define a "gradient" array with at least 2 colors.`
4138
4143
  );
4139
4144
  }
4140
4145
  for (let i = 0; i < obj.gradient.length; i++) {
4141
4146
  const stop = obj.gradient[i];
4142
4147
  if (typeof stop !== "string" || !HEX_RE4.test(stop)) {
4143
- throw new Error(
4148
+ throw new TypeError(
4144
4149
  `Invalid hex in theme "${obj.name}" \u2192 gradient[${i}]: ${JSON.stringify(stop)}.`
4145
4150
  );
4146
4151
  }
@@ -4452,8 +4457,8 @@ var themes = {
4452
4457
  use(name) {
4453
4458
  const t = _globalRegistry.get(name);
4454
4459
  if (!t) {
4455
- throw new Error(
4456
- `Theme "${name}" not found. Available: ${[..._globalRegistry.keys()].join(", ")}`
4460
+ throw new RangeError(
4461
+ `Theme "${name}" not found. Available themes: ${[..._globalRegistry.keys()].join(", ")}`
4457
4462
  );
4458
4463
  }
4459
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());
@@ -3941,12 +3946,12 @@ var validateTheme = (t) => {
3941
3946
  }
3942
3947
  const obj = t;
3943
3948
  if (typeof obj.name !== "string" || obj.name.length === 0) {
3944
- throw new Error('Theme must have a non-empty "name" string.');
3949
+ throw new TypeError('Theme must have a non-empty "name" string.');
3945
3950
  }
3946
3951
  for (const key of REQUIRED_COLOR_KEYS) {
3947
3952
  const value = obj[key];
3948
3953
  if (typeof value !== "string" || !HEX_RE4.test(value)) {
3949
- throw new Error(
3954
+ throw new TypeError(
3950
3955
  `Invalid hex in theme "${obj.name}" \u2192 ${key}: ${JSON.stringify(value)}. Expected #RGB or #RRGGBB.`
3951
3956
  );
3952
3957
  }
@@ -3955,20 +3960,20 @@ var validateTheme = (t) => {
3955
3960
  const value = obj[key];
3956
3961
  if (value === void 0) continue;
3957
3962
  if (typeof value !== "string" || !HEX_RE4.test(value)) {
3958
- throw new Error(
3963
+ throw new TypeError(
3959
3964
  `Invalid hex in theme "${obj.name}" \u2192 ${key}: ${JSON.stringify(value)}.`
3960
3965
  );
3961
3966
  }
3962
3967
  }
3963
3968
  if (!Array.isArray(obj.gradient) || obj.gradient.length < 2) {
3964
- throw new Error(
3969
+ throw new TypeError(
3965
3970
  `Theme "${obj.name}" must define a "gradient" array with at least 2 colors.`
3966
3971
  );
3967
3972
  }
3968
3973
  for (let i = 0; i < obj.gradient.length; i++) {
3969
3974
  const stop = obj.gradient[i];
3970
3975
  if (typeof stop !== "string" || !HEX_RE4.test(stop)) {
3971
- throw new Error(
3976
+ throw new TypeError(
3972
3977
  `Invalid hex in theme "${obj.name}" \u2192 gradient[${i}]: ${JSON.stringify(stop)}.`
3973
3978
  );
3974
3979
  }
@@ -4280,8 +4285,8 @@ var themes = {
4280
4285
  use(name) {
4281
4286
  const t = _globalRegistry.get(name);
4282
4287
  if (!t) {
4283
- throw new Error(
4284
- `Theme "${name}" not found. Available: ${[..._globalRegistry.keys()].join(", ")}`
4288
+ throw new RangeError(
4289
+ `Theme "${name}" not found. Available themes: ${[..._globalRegistry.keys()].join(", ")}`
4285
4290
  );
4286
4291
  }
4287
4292
  const old = _globalActive;
@@ -118,7 +118,7 @@ async function main() {
118
118
  console.log(components.section('🏷️ Badges & Status', { width: 60 }));
119
119
  console.log();
120
120
  console.log(' ',
121
- components.badge('VERSION', 'v1.1.1'),
121
+ components.badge('VERSION', 'v1.1.2'),
122
122
  components.badge('BUILD', 'passing'),
123
123
  components.badge('LICENSE', 'Apache 2.0'));
124
124
  console.log();
@@ -117,7 +117,7 @@ console.log();
117
117
  console.log(components.section('🏷️ Badges & Status', { width: 60 }));
118
118
  console.log();
119
119
  console.log(' ',
120
- components.badge('VERSION', 'v1.1.1'),
120
+ components.badge('VERSION', 'v1.1.2'),
121
121
  components.badge('BUILD', 'passing'),
122
122
  components.badge('LICENSE', 'Apache 2.0'));
123
123
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ansimax",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Zero-dependency CLI rendering library: colors, gradients, animations, ASCII art, pixel art, components, and themes \u2014 all in TypeScript.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",