@mimi-ng/cli 0.4.0 → 0.5.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 (35) hide show
  1. package/README.md +29 -7
  2. package/dist/init/index.js +8 -0
  3. package/dist/registry.js +4 -1
  4. package/dist/registry.json +87 -1
  5. package/dist/templates/blocks/login-01/login-01.ts +12 -3
  6. package/dist/templates/blocks/login-02/login-02.ts +12 -3
  7. package/dist/templates/blocks/otp-01/otp-01.ts +12 -3
  8. package/dist/templates/blocks/pricing-01/pricing-01.ts +1 -1
  9. package/dist/templates/blocks/settings-01/settings-01.ts +13 -3
  10. package/dist/templates/blocks/signup-01/signup-01.ts +12 -2
  11. package/dist/templates/blocks/table-01/index.ts +1 -0
  12. package/dist/templates/blocks/table-01/table-01.html +214 -0
  13. package/dist/templates/blocks/table-01/table-01.ts +266 -0
  14. package/dist/templates/components/checkbox/checkbox.ts +14 -5
  15. package/dist/templates/components/command/command-texts.ts +65 -0
  16. package/dist/templates/components/command/command.ts +553 -0
  17. package/dist/templates/components/command/index.ts +13 -0
  18. package/dist/templates/components/data-table/data-table-texts.ts +110 -0
  19. package/dist/templates/components/data-table/data-table.ts +1153 -0
  20. package/dist/templates/components/data-table/index.ts +18 -0
  21. package/dist/templates/components/sortable-list/index.ts +9 -0
  22. package/dist/templates/components/sortable-list/sortable-list-texts.ts +71 -0
  23. package/dist/templates/components/sortable-list/sortable-list.ts +510 -0
  24. package/dist/templates/components/tree/index.ts +9 -0
  25. package/dist/templates/components/tree/tree-texts.ts +45 -0
  26. package/dist/templates/components/tree/tree.ts +730 -0
  27. package/dist/templates/theme/provider.ts +4 -0
  28. package/dist/templates/theme/types.ts +84 -0
  29. package/dist/ui/index.js +232 -40
  30. package/dist/ui/schema.json +30 -6
  31. package/dist/utils/pages.js +138 -0
  32. package/dist/utils/project.js +33 -9
  33. package/dist/utils/tailwind.js +15 -11
  34. package/dist/utils/write-files.js +32 -7
  35. package/package.json +1 -1
@@ -101,6 +101,10 @@ const COMPONENT_PREFIX: Record<keyof MimiComponentTokens, string> = {
101
101
  date: 'date',
102
102
  fileUpload: 'upload',
103
103
  colorPicker: 'color-picker',
104
+ tree: 'tree',
105
+ sortable: 'sortable',
106
+ command: 'command',
107
+ dataTable: 'table',
104
108
  };
105
109
 
106
110
  /** Nombres de la 0.1.0 que siguen funcionando hasta la 1.0 (spec 12): ruta → qué usar. */
@@ -555,6 +555,86 @@ export interface MimiColorPickerTokens {
555
555
  swatchGap?: string;
556
556
  }
557
557
 
558
+ /**
559
+ * Tree. Fondo, hover y el nodo elegido van en el CSS (`--mimi-tree-bg`, `-hover`, `-selected`,
560
+ * `-selected-fg`).
561
+ */
562
+ export interface MimiTreeTokens {
563
+ /** Relleno del contenedor (6px). */
564
+ padding?: string;
565
+ /** Alto de cada nodo (32px). */
566
+ rowHeight?: string;
567
+ /** Radio del nodo (el global `radius-sm`). */
568
+ rowRadius?: string;
569
+ /** Sangría por nivel (18px). */
570
+ indent?: string;
571
+ /** Tamaño de texto (13.5px). */
572
+ fontSize?: string;
573
+ }
574
+
575
+ /**
576
+ * Sortable List. Fondo, el hover del asa y los colores del hueco van en el CSS
577
+ * (`--mimi-sortable-bg`, `-handle-hover`, `-drop`, `-drop-bg`).
578
+ */
579
+ export interface MimiSortableTokens {
580
+ /** Alto mínimo de cada elemento (56px). */
581
+ itemHeight?: string;
582
+ /** Separación entre elementos (8px). */
583
+ gap?: string;
584
+ /** Radio del elemento y del hueco (el global `radius`). */
585
+ radius?: string;
586
+ /** Giro y escala del elemento levantado (`rotate(-1.5deg) scale(1.03)`). */
587
+ lift?: string;
588
+ /** Curva del reacomodo (`cubic-bezier(.2, .8, .2, 1)`). */
589
+ easing?: string;
590
+ }
591
+
592
+ /**
593
+ * Command. El fondo del comando activo va en el CSS (`--mimi-command-active`).
594
+ */
595
+ export interface MimiCommandTokens {
596
+ /** Ancho del panel en el diálogo (560px). */
597
+ width?: string;
598
+ /** Distancia del panel al borde superior en el diálogo (36px). */
599
+ top?: string;
600
+ /** Alto de la fila del buscador (52px). */
601
+ inputHeight?: string;
602
+ /** Tamaño del texto del buscador (15px). */
603
+ inputFontSize?: string;
604
+ /** Alto máximo de la lista (332px). */
605
+ listHeight?: string;
606
+ /** Alto de cada comando (40px). */
607
+ itemHeight?: string;
608
+ /** Radio del comando (el global `radius-sm`). */
609
+ itemRadius?: string;
610
+ /** Caja del ícono del comando (26px). */
611
+ iconSize?: string;
612
+ }
613
+
614
+ /**
615
+ * Data Table. Los fondos (tabla, encabezado, hover, elegida, detalle) y la sombra de la columna
616
+ * fija van en el CSS (`--mimi-table-bg`, `-head-bg`, `-row-hover`, `-row-selected`,
617
+ * `-expanded-bg`, `-sticky-shadow`).
618
+ */
619
+ export interface MimiTableTokens {
620
+ /** Alto de fila en la densidad compacta (42px). */
621
+ rowHeight?: string;
622
+ /** Alto de fila en la densidad cómoda (60px). */
623
+ rowHeightComfortable?: string;
624
+ /** Alto del encabezado, compacta (38px). */
625
+ headHeight?: string;
626
+ /** Alto del encabezado, cómoda (44px). */
627
+ headHeightComfortable?: string;
628
+ /** Texto de las celdas, compacta (13px). */
629
+ fontSize?: string;
630
+ /** Texto de las celdas, cómoda (14px). */
631
+ fontSizeComfortable?: string;
632
+ /** Relleno horizontal de la celda (10px). */
633
+ cellPx?: string;
634
+ /** Alto mínimo de la barra (56px). */
635
+ toolbarHeight?: string;
636
+ }
637
+
558
638
  /**
559
639
  * Tokens por componente (spec 13). Solo los que no dependen del modo: los colores de los
560
640
  * componentes (`--mimi-<prefijo>-bg`, `-fg`, `-border`…) se cambian en el CSS (spec 6.2).
@@ -594,6 +674,10 @@ export interface MimiComponentTokens {
594
674
  date?: MimiDateTokens;
595
675
  fileUpload?: MimiFileUploadTokens;
596
676
  colorPicker?: MimiColorPickerTokens;
677
+ tree?: MimiTreeTokens;
678
+ sortable?: MimiSortableTokens;
679
+ command?: MimiCommandTokens;
680
+ dataTable?: MimiTableTokens;
597
681
  }
598
682
 
599
683
  export interface MimiThemePreset {
package/dist/ui/index.js CHANGED
@@ -8,6 +8,7 @@ const css_1 = require("../init/css");
8
8
  const items_1 = require("../init/items");
9
9
  const paths_1 = require("../init/paths");
10
10
  const registry_1 = require("../registry");
11
+ const pages_1 = require("../utils/pages");
11
12
  const project_1 = require("../utils/project");
12
13
  const tailwind_1 = require("../utils/tailwind");
13
14
  const write_files_1 = require("../utils/write-files");
@@ -40,13 +41,14 @@ function ui(options) {
40
41
  implicit: plan.implicit,
41
42
  componentsDir: plan.componentsDir,
42
43
  blocksDir: plan.blocksDir,
44
+ pages: plan.pages,
43
45
  overwrite: options.overwrite ?? false,
44
46
  version: registry_1.registry.version,
45
47
  report,
46
48
  }),
47
49
  updateMimiConfig(plan, report),
48
50
  updateGlobalCss(plan),
49
- addBlocksSource(plan),
51
+ addFolderSources(plan),
50
52
  ...Object.entries(plan.newPackages).map(([name, version]) => (0, utility_1.addDependency)(name, version, {
51
53
  type: utility_1.DependencyType.Default,
52
54
  existing: utility_1.ExistingBehavior.Skip,
@@ -112,12 +114,17 @@ function inspect(tree, options) {
112
114
  const implicit = [...new Set(items_1.INIT_ITEMS.flatMap((item) => (0, registry_1.resolveItems)(item)))].filter((item) => !items.includes(item));
113
115
  const componentsDir = (0, paths_1.normalizePath)(config.aliases?.components ?? 'src/app/components/ui');
114
116
  const blocksDir = (0, paths_1.normalizePath)(config.aliases?.blocks ?? (0, project_1.defaultBlocksDir)(componentsDir));
117
+ const pagesDir = (0, paths_1.normalizePath)(config.aliases?.pages ?? (0, project_1.defaultPagesDir)(componentsDir));
118
+ const { pages, legacyPages } = planPages(tree, config, items, options.name, blocksDir, pagesDir);
115
119
  const outdated = outdatedPeers(tree, items, installed, newPackages);
116
120
  const updatable = PEERS_WITH_MINIMUM.filter((name) => installed[name] || newPackages[name]);
117
121
  const missingExclusion = mimiExclusion(tree, config, installed);
118
122
  return {
119
123
  componentsDir,
120
124
  blocksDir,
125
+ pagesDir,
126
+ pages,
127
+ legacyPages,
121
128
  config,
122
129
  items,
123
130
  implicit,
@@ -127,6 +134,134 @@ function inspect(tree, options) {
127
134
  missingExclusion,
128
135
  };
129
136
  }
137
+ // ── Páginas (B.11) ────────────────────────────────────────────────────────────────────
138
+ /**
139
+ * Dónde va cada Block de tipo `page` de esta ejecución. Todo se comprueba antes de escribir: si
140
+ * algo no cuadra (un `--name` inválido, una carpeta que ya existe y no es esa página, dos páginas
141
+ * en la misma carpeta), falla sin cambiar nada.
142
+ *
143
+ * - Ya generada (`mimi.json → blocks.<ítem>.path`): se actualiza ahí, con su nombre (B.11-f).
144
+ * - Instalada con la 0.4.0 (`<aliases.blocks>/<ítem>/<ítem>.ts`, sin `path`): se queda donde está
145
+ * y se actualiza como antes (B.11-g).
146
+ * - Nueva: `<aliases.pages>/<nombre>/`, con `--name` o el nombre por defecto (B.11-c).
147
+ */
148
+ function planPages(tree, config, items, name, blocksDir, pagesDir) {
149
+ const pageItems = items.filter((item) => (0, registry_1.isPageBlock)(item));
150
+ if (name !== undefined) {
151
+ if (!pages_1.PAGE_NAME.test(name)) {
152
+ throw new schematics_1.SchematicsException(`--name ${JSON.stringify(name)} no sirve como nombre de página: usa minúsculas, números y ` +
153
+ 'guiones, empezando con una letra (por ejemplo, --name admin-login). No se cambió nada.');
154
+ }
155
+ if (pageItems.length !== 1) {
156
+ const pageNames = exports.AVAILABLE.filter((item) => (0, registry_1.isPageBlock)(item)).join(', ');
157
+ throw new schematics_1.SchematicsException((pageItems.length === 0
158
+ ? '--name es el nombre de la página que se genera, y no pediste ningún Block de tipo página'
159
+ : `--name sirve para una sola página, y pediste ${pageItems.join(', ')}: agrégalas por separado`) +
160
+ ` (son páginas: ${pageNames}). No se cambió nada.`);
161
+ }
162
+ if ((0, pages_1.isBlockLikeName)(pageItems[0], name)) {
163
+ throw new schematics_1.SchematicsException(`--name ${name} lleva el nombre del Block (${pageItems[0]}): elige el de tu página, por ` +
164
+ `ejemplo --name ${(0, pages_1.defaultPageName)(pageItems[0])} o --name admin-` +
165
+ `${(0, pages_1.defaultPageName)(pageItems[0])}. No se cambió nada.`);
166
+ }
167
+ }
168
+ const prefix = projectPrefix(tree, pagesDir);
169
+ const pages = {};
170
+ const legacyPages = [];
171
+ const taken = new Map();
172
+ for (const item of pageItems) {
173
+ const entry = config.blocks?.[item];
174
+ let target;
175
+ if (entry?.path) {
176
+ const dir = (0, paths_1.normalizePath)(entry.path);
177
+ const trackedName = entry.name && pages_1.PAGE_NAME.test(entry.name) ? entry.name : basename(dir);
178
+ if (name !== undefined && name !== trackedName) {
179
+ throw new schematics_1.SchematicsException(`${item} ya es tu página ${trackedName} (${dir}/): Mimi la sigue por su nombre del ` +
180
+ `registro, así que está en un solo sitio. Para actualizarla, ejecuta ng g mimi ${item} ` +
181
+ 'sin --name. Si quieres otra copia con otro nombre, renombra o mueve la tuya y cambia ' +
182
+ `"path" y "name" de blocks.${item} en mimi.json. No se cambió nada.`);
183
+ }
184
+ target = { item, dir, name: trackedName, prefix };
185
+ }
186
+ else if (tree.exists(`${blocksDir}/${item}/${item}.ts`)) {
187
+ if (name !== undefined) {
188
+ throw new schematics_1.SchematicsException(`${item} está instalado como en la 0.4.0, en ${blocksDir}/${item}/, y se sigue ` +
189
+ 'actualizando ahí. Para generarlo como página, guarda tus cambios (por ejemplo, en git), ' +
190
+ `borra esa carpeta y vuelve a ejecutar el comando. No se cambió nada.`);
191
+ }
192
+ legacyPages.push(item);
193
+ continue;
194
+ }
195
+ else {
196
+ const pageName = name ?? (0, pages_1.defaultPageName)(item);
197
+ const dir = `${pagesDir}/${pageName}`;
198
+ if (hasContent(tree, dir)) {
199
+ const owner = Object.entries(config.blocks ?? {}).find(([, other]) => other?.path && (0, paths_1.normalizePath)(other.path) === dir)?.[0];
200
+ throw new schematics_1.SchematicsException(`La carpeta ${dir}/ ya existe${owner ? ` (es tu página de ${owner})` : ''}, así que ` +
201
+ `${item} no se generó ahí: Mimi no sobrescribe tus archivos. Elige otro nombre con ` +
202
+ `--name, por ejemplo: ng g mimi ${item} --name ${pageName}-2. No se cambió nada.`);
203
+ }
204
+ target = { item, dir, name: pageName, prefix };
205
+ }
206
+ const other = taken.get(target.dir);
207
+ if (other) {
208
+ throw new schematics_1.SchematicsException(`${other} y ${item} irían a la misma carpeta (${target.dir}/). Agrégalos por separado, ` +
209
+ `uno de ellos con --name (por ejemplo: ng g mimi ${item} --name ${target.name}-2). ` +
210
+ 'No se cambió nada.');
211
+ }
212
+ taken.set(target.dir, item);
213
+ pages[item] = target;
214
+ }
215
+ return { pages, legacyPages };
216
+ }
217
+ const basename = (path) => path.slice(path.lastIndexOf('/') + 1);
218
+ /** ¿La carpeta existe y tiene algo dentro? (Un árbol de schematics no tiene carpetas vacías.) */
219
+ function hasContent(tree, dir) {
220
+ const entry = tree.getDir(dir);
221
+ return entry.subfiles.length + entry.subdirs.length > 0;
222
+ }
223
+ /**
224
+ * Prefijo de los selectores (`app`, de angular.json → projects → prefix): el del proyecto que
225
+ * contiene la carpeta de páginas o, si no, el de la única aplicación. Si no se sabe, `app`.
226
+ */
227
+ function projectPrefix(tree, dir) {
228
+ let workspace = null;
229
+ try {
230
+ workspace = (0, project_1.readJson)(tree, 'angular.json');
231
+ }
232
+ catch {
233
+ return 'app';
234
+ }
235
+ const projects = Object.values(workspace?.projects ?? {});
236
+ const sourceOf = (project) => (0, paths_1.normalizePath)(project.sourceRoot ?? `${project.root ?? ''}/src`);
237
+ const inside = projects
238
+ .filter((project) => `${dir}/`.startsWith(`${sourceOf(project)}/`))
239
+ .sort((a, b) => sourceOf(b).length - sourceOf(a).length);
240
+ const apps = projects.filter((project) => project.projectType === 'application');
241
+ const project = inside[0] ?? (apps.length === 1 ? apps[0] : undefined);
242
+ return typeof project?.prefix === 'string' && /^[a-z][a-z0-9-]*$|^$/.test(project.prefix)
243
+ ? project.prefix
244
+ : 'app';
245
+ }
246
+ /**
247
+ * Cómo importa `app.routes.ts` el archivo de rutas de una página: relativo a la carpeta del
248
+ * `app.routes.ts` que está por encima de la página (`src/app/pages/login` →
249
+ * `./pages/login/login.routes`). Se busca subiendo desde la carpeta de arriba de la página (nunca
250
+ * la suya: una página `app` tiene su propio `app.routes.ts`); si no hay ninguno, la carpeta `app/`
251
+ * más cercana por encima. Si tampoco, la ruta del workspace (y `appDir` null: hay que ajustarla).
252
+ */
253
+ function routesImportOf(tree, page) {
254
+ const routes = `${page.dir}/${page.name}.routes`;
255
+ const ancestors = [];
256
+ const parts = page.dir.split('/');
257
+ for (let length = parts.length - 1; length > 0; length--) {
258
+ ancestors.push(parts.slice(0, length).join('/'));
259
+ }
260
+ const appDir = ancestors.find((dir) => tree.exists(`${dir}/app.routes.ts`)) ??
261
+ ancestors.find((dir) => basename(dir) === 'app') ??
262
+ null;
263
+ return appDir ? { path: (0, paths_1.relativePath)(appDir, routes), appDir } : { path: routes, appDir: null };
264
+ }
130
265
  /**
131
266
  * Sin `@source not` hacia `.mimi/`, Tailwind escanea las copias originales (van en git) y genera
132
267
  * sus clases aunque el usuario ya las haya cambiado en sus componentes. Proyectos de la 0.3.0 o
@@ -290,21 +425,36 @@ function updateMimiConfig(plan, report) {
290
425
  const components = { ...plan.config.components };
291
426
  const blocks = { ...plan.config.blocks };
292
427
  for (const item of plan.items) {
293
- const { type, files } = registry_1.registry.items[item];
428
+ const { type } = registry_1.registry.items[item];
294
429
  if (type !== 'component' && type !== 'block')
295
430
  continue;
296
431
  if (blocked.has(item))
297
432
  continue;
298
- if (files.some((file) => skipped.has(file)))
433
+ if ((0, write_files_1.itemFiles)(item, plan.pages).some((file) => skipped.has(file)))
299
434
  continue;
300
- (type === 'block' ? blocks : components)[item] = { version: registry_1.registry.version };
435
+ if (type === 'component') {
436
+ components[item] = { version: registry_1.registry.version };
437
+ continue;
438
+ }
439
+ // Una página guarda dónde está y con qué nombre se generó (B.11-f).
440
+ const page = plan.pages[item];
441
+ blocks[item] = page
442
+ ? { version: registry_1.registry.version, path: page.dir, name: page.name }
443
+ : { version: registry_1.registry.version };
301
444
  }
302
445
  const next = { ...plan.config, components };
303
446
  if (Object.keys(blocks).length > 0) {
304
447
  next.blocks = blocks;
305
- // Proyectos de la 0.3.0 o anteriores: la carpeta queda escrita la primera vez (B.1-a).
306
- if (!plan.config.aliases?.blocks) {
307
- next.aliases = { ...plan.config.aliases, blocks: plan.blocksDir };
448
+ // Proyectos anteriores: la carpeta queda escrita la primera vez que se copia un Block
449
+ // (B.1-a) o se genera una página (B.11-b).
450
+ const entries = Object.values(blocks);
451
+ const aliases = { ...plan.config.aliases };
452
+ if (!aliases.blocks && entries.some((entry) => !entry.path))
453
+ aliases.blocks = plan.blocksDir;
454
+ if (!aliases.pages && entries.some((entry) => entry.path))
455
+ aliases.pages = plan.pagesDir;
456
+ if (JSON.stringify(aliases) !== JSON.stringify(plan.config.aliases ?? {})) {
457
+ next.aliases = aliases;
308
458
  }
309
459
  }
310
460
  const text = `${JSON.stringify(next, null, 2)}\n`;
@@ -345,22 +495,40 @@ function updateGlobalCss(plan) {
345
495
  };
346
496
  }
347
497
  /**
348
- * G1: con `@import "tailwindcss" source(none)`, Tailwind no escanea la carpeta de los Blocks y
349
- * la pantalla queda sin sus estilos. Si se copia un Block, se agrega su `@source` (una vez).
498
+ * G1: con `@import "tailwindcss" source(none)`, Tailwind no escanea la carpeta de los Blocks ni
499
+ * la de las páginas (B.11-i), y la pantalla queda sin sus estilos. Si se copia un Block o se
500
+ * genera una página, se agrega el `@source` de su carpeta (una vez).
350
501
  */
351
- function addBlocksSource(plan) {
502
+ function addFolderSources(plan) {
352
503
  return (tree, context) => {
353
- if (!plan.items.some((item) => registry_1.registry.items[item].type === 'block'))
504
+ const dirs = [];
505
+ if (plan.items.some((item) => registry_1.registry.items[item].type === 'block' && !plan.pages[item])) {
506
+ dirs.push(plan.blocksDir);
507
+ }
508
+ for (const page of Object.values(plan.pages)) {
509
+ const dir = `${page.dir}/`.startsWith(`${plan.pagesDir}/`) ? plan.pagesDir : page.dir;
510
+ if (!dirs.includes(dir))
511
+ dirs.push(dir);
512
+ }
513
+ if (dirs.length === 0)
354
514
  return;
355
515
  const cssPath = plan.config.tailwind?.css;
356
516
  if (!cssPath || !tree.exists(cssPath))
357
517
  return;
358
- const result = (0, tailwind_1.withBlocksSource)(tree.readText(cssPath), cssPath, plan.componentsDir, plan.blocksDir);
359
- if (!result)
518
+ let css = tree.readText(cssPath);
519
+ const lines = [];
520
+ for (const dir of dirs) {
521
+ const result = (0, tailwind_1.withFolderSource)(css, cssPath, [plan.componentsDir, plan.blocksDir, plan.pagesDir], dir);
522
+ if (!result)
523
+ continue;
524
+ css = result.css;
525
+ lines.push(result.line);
526
+ }
527
+ if (lines.length === 0)
360
528
  return;
361
- tree.overwrite(cssPath, result.css);
529
+ tree.overwrite(cssPath, css);
362
530
  context.logger.info(`Agregado a ${cssPath} (tu @import de Tailwind usa source(none), así que Tailwind solo ` +
363
- `escanea las carpetas que indicas):\n ${result.line}`);
531
+ `escanea las carpetas que indicas):\n${lines.map((line) => ` ${line}`).join('\n')}`);
364
532
  };
365
533
  }
366
534
  // ── Mensaje final ─────────────────────────────────────────────────────────────────────
@@ -384,18 +552,12 @@ function blocksUsingUpdated(plan, report, itemOf) {
384
552
  }
385
553
  return messages;
386
554
  }
387
- /**
388
- * Cómo se importa un Block desde `app.routes.ts` (en `src/app/`): `src/app/blocks` →
389
- * `./blocks/login-01`. Si la carpeta no está dentro de `app/`, la ruta del workspace.
390
- */
391
- function routesImport(blocksDir, item) {
392
- const at = `/${blocksDir}`.lastIndexOf('/app/');
393
- return at >= 0 ? `./${`/${blocksDir}`.slice(at + 5)}/${item}` : `${blocksDir}/${item}`;
394
- }
555
+ /** `pricing-01` → `Pricing01`: la clase de un Block copiado tal cual. */
556
+ const blockClassName = (item) => item.replace(/(^|-)([a-z0-9])/g, (_, _dash, c) => c.toUpperCase());
395
557
  function summary(plan, report) {
396
- return (_tree, context) => {
558
+ return (tree, context) => {
397
559
  const log = context.logger;
398
- const path = (file) => (0, write_files_1.targetPathOf)(file, plan.componentsDir, plan.blocksDir);
560
+ const path = (file) => (0, write_files_1.targetPathOf)(file, plan.componentsDir, plan.blocksDir, plan.pages);
399
561
  if (report.created.length > 0) {
400
562
  log.info('Agregado:');
401
563
  for (const file of report.created)
@@ -406,16 +568,23 @@ function summary(plan, report) {
406
568
  for (const file of report.overwritten)
407
569
  log.info(` ~ ${path(file)}`);
408
570
  // Un Block es una pantalla del usuario: --overwrite la reemplaza entera (B.1, trampas).
409
- const screens = [
410
- ...new Set(report.overwritten.filter(write_files_1.isBlockFile).map((file) => file.split('/')[1])),
411
- ];
412
- for (const block of screens) {
413
- log.warn(`--overwrite reemplazó tu pantalla ${block} (${plan.blocksDir}/${block}/) por la de ` +
571
+ const screens = new Map();
572
+ for (const file of report.overwritten) {
573
+ if ((0, pages_1.isPageFile)(file)) {
574
+ const item = (0, pages_1.pageItemOf)(file);
575
+ screens.set(item, `${plan.pages[item].dir}/`);
576
+ }
577
+ else if ((0, write_files_1.isBlockFile)(file)) {
578
+ const item = file.split('/')[1];
579
+ screens.set(item, `${plan.blocksDir}/${item}/`);
580
+ }
581
+ }
582
+ for (const [block, dir] of screens) {
583
+ log.warn(`--overwrite reemplazó tu pantalla ${block} (${dir}) por la de ` +
414
584
  `Mimi ${registry_1.registry.version}. Si tenías cambios, recupéralos de git.`);
415
585
  }
416
586
  }
417
- const itemOf = (file) => [...plan.items, ...plan.implicit].find((item) => registry_1.registry.items[item].files.includes(file)) ??
418
- file;
587
+ const itemOf = (file) => [...plan.items, ...plan.implicit].find((item) => (0, write_files_1.itemFiles)(item, plan.pages).includes(file)) ?? file;
419
588
  for (const message of (0, write_files_1.updatedMessages)(report, itemOf, registry_1.registry.version))
420
589
  log.info(message);
421
590
  for (const message of blocksUsingUpdated(plan, report, itemOf))
@@ -423,19 +592,42 @@ function summary(plan, report) {
423
592
  for (const file of report.skipped) {
424
593
  log.warn(`Omitido: ${path(file)} ya existe con otro contenido (usa --overwrite para reemplazarlo).`);
425
594
  }
426
- for (const message of (0, write_files_1.blockedMessages)(report, plan.componentsDir, registry_1.registry.version, plan.blocksDir)) {
595
+ for (const message of (0, write_files_1.blockedMessages)(report, plan.componentsDir, registry_1.registry.version, plan.blocksDir, plan.pages)) {
427
596
  log.warn(message);
428
597
  }
429
- // Un Block nuevo es una página: se enruta (B.1, «API pública»).
598
+ // Una página nueva se enruta con la línea exacta (B.11-e); la CLI no edita app.routes.ts.
599
+ // Una sección nueva va dentro de una página.
430
600
  const created = new Set(report.created);
431
601
  for (const item of plan.items) {
432
- const { type, files } = registry_1.registry.items[item];
433
- if (type !== 'block' || !files.some((file) => created.has(file)))
602
+ if (registry_1.registry.items[item].type !== 'block' || plan.legacyPages.includes(item))
603
+ continue;
604
+ const page = plan.pages[item];
605
+ if (page) {
606
+ if (!created.has((0, pages_1.routesKey)(item)))
607
+ continue;
608
+ const { path: routes, appDir } = routesImportOf(tree, page);
609
+ const routesFile = appDir ? `${appDir}/app.routes.ts` : null;
610
+ log.info(`${item} es ahora tu página ${page.name} (${page.dir}/, clase ` +
611
+ `${(0, pages_1.pageClassName)(page.name)}). Para abrirla en /${page.name}, agrega esta línea a ` +
612
+ (routesFile && tree.exists(routesFile)
613
+ ? `las rutas de ${routesFile}:`
614
+ : 'tus rutas (la ruta del import es relativa a tu app.routes.ts):'));
615
+ log.info(` ${(0, pages_1.routeLine)(page, routes)}`);
434
616
  continue;
435
- const className = item.replace(/(^|-)([a-z0-9])/g, (_, _dash, c) => c.toUpperCase());
436
- log.info(`${item} es una pantalla: agrégala a tus rutas, por ejemplo ` +
437
- `{ path: '${item.split('-')[0]}', component: ${className} } ` +
438
- `(import { ${className} } from '${routesImport(plan.blocksDir, item)}').`);
617
+ }
618
+ if (!registry_1.registry.items[item].files.some((file) => created.has(file)))
619
+ continue;
620
+ log.info(`${item} es una sección (una parte de una página): ponla en una de tus páginas con ` +
621
+ `<mimi-${item} />, importando ${blockClassName(item)} de ${plan.blocksDir}/${item}.`);
622
+ }
623
+ // Pantallas de la 0.4.0: siguen donde estaban (B.11-g). Una bloqueada ya tiene su aviso.
624
+ const blockedItems = new Set(report.blocked.map((b) => b.item));
625
+ for (const item of plan.legacyPages.filter((legacy) => !blockedItems.has(legacy))) {
626
+ log.info(`${item} sigue en ${plan.blocksDir}/${item}/, donde lo instalaste con la 0.4.0, y se ` +
627
+ `actualiza ahí. Los Blocks de pantalla se generan ahora como páginas ` +
628
+ `(${plan.pagesDir}/${(0, pages_1.defaultPageName)(item)}/, ${(0, pages_1.pageClassName)((0, pages_1.defaultPageName)(item))}): ` +
629
+ 'para pasarlo, guarda tus cambios, borra esa carpeta y vuelve a ejecutar ' +
630
+ `ng g mimi ${item}.`);
439
631
  }
440
632
  if (!(0, write_files_1.hasFileChanges)(report) && report.skipped.length === 0 && report.blocked.length === 0) {
441
633
  log.info('Nada que hacer: los archivos ya están al día.');
@@ -51,6 +51,10 @@
51
51
  "value": "combobox",
52
52
  "label": "combobox"
53
53
  },
54
+ {
55
+ "value": "command",
56
+ "label": "command"
57
+ },
54
58
  {
55
59
  "value": "confirm",
56
60
  "label": "confirm"
@@ -59,6 +63,10 @@
59
63
  "value": "context-menu",
60
64
  "label": "context-menu"
61
65
  },
66
+ {
67
+ "value": "data-table",
68
+ "label": "data-table"
69
+ },
62
70
  {
63
71
  "value": "date-picker",
64
72
  "label": "date-picker"
@@ -131,6 +139,10 @@
131
139
  "value": "slider",
132
140
  "label": "slider"
133
141
  },
142
+ {
143
+ "value": "sortable-list",
144
+ "label": "sortable-list"
145
+ },
134
146
  {
135
147
  "value": "switch",
136
148
  "label": "switch"
@@ -163,29 +175,37 @@
163
175
  "value": "tooltip",
164
176
  "label": "tooltip"
165
177
  },
178
+ {
179
+ "value": "tree",
180
+ "label": "tree"
181
+ },
166
182
  {
167
183
  "value": "login-01",
168
- "label": "login-01 (Block)"
184
+ "label": "login-01 (Block, página)"
169
185
  },
170
186
  {
171
187
  "value": "login-02",
172
- "label": "login-02 (Block)"
188
+ "label": "login-02 (Block, página)"
173
189
  },
174
190
  {
175
191
  "value": "otp-01",
176
- "label": "otp-01 (Block)"
192
+ "label": "otp-01 (Block, página)"
177
193
  },
178
194
  {
179
195
  "value": "pricing-01",
180
- "label": "pricing-01 (Block)"
196
+ "label": "pricing-01 (Block, sección)"
181
197
  },
182
198
  {
183
199
  "value": "settings-01",
184
- "label": "settings-01 (Block)"
200
+ "label": "settings-01 (Block, página)"
185
201
  },
186
202
  {
187
203
  "value": "signup-01",
188
- "label": "signup-01 (Block)"
204
+ "label": "signup-01 (Block, página)"
205
+ },
206
+ {
207
+ "value": "table-01",
208
+ "label": "table-01 (Block, página)"
189
209
  }
190
210
  ]
191
211
  }
@@ -194,6 +214,10 @@
194
214
  "type": "boolean",
195
215
  "default": false,
196
216
  "description": "Reemplaza los archivos que ya existan con otro contenido."
217
+ },
218
+ "name": {
219
+ "type": "string",
220
+ "description": "Nombre de la página que genera un Block de tipo página (por defecto, el del Block sin el número: login-01 → login). En kebab-case: --name admin-login."
197
221
  }
198
222
  }
199
223
  }