@mimi-ng/cli 0.3.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.
- package/README.md +42 -1
- package/dist/init/css.js +1 -1
- package/dist/init/index.js +33 -0
- package/dist/init/paths.js +20 -0
- package/dist/registry.js +4 -1
- package/dist/registry.json +198 -1
- package/dist/templates/blocks/login-01/index.ts +1 -0
- package/dist/templates/blocks/login-01/login-01.html +174 -0
- package/dist/templates/blocks/login-01/login-01.ts +114 -0
- package/dist/templates/blocks/login-02/index.ts +1 -0
- package/dist/templates/blocks/login-02/login-02.html +186 -0
- package/dist/templates/blocks/login-02/login-02.ts +101 -0
- package/dist/templates/blocks/otp-01/index.ts +1 -0
- package/dist/templates/blocks/otp-01/otp-01.html +166 -0
- package/dist/templates/blocks/otp-01/otp-01.ts +221 -0
- package/dist/templates/blocks/pricing-01/index.ts +1 -0
- package/dist/templates/blocks/pricing-01/pricing-01.html +173 -0
- package/dist/templates/blocks/pricing-01/pricing-01.ts +178 -0
- package/dist/templates/blocks/settings-01/index.ts +1 -0
- package/dist/templates/blocks/settings-01/settings-01.html +424 -0
- package/dist/templates/blocks/settings-01/settings-01.ts +392 -0
- package/dist/templates/blocks/signup-01/index.ts +1 -0
- package/dist/templates/blocks/signup-01/signup-01.html +332 -0
- package/dist/templates/blocks/signup-01/signup-01.ts +186 -0
- package/dist/templates/blocks/table-01/index.ts +1 -0
- package/dist/templates/blocks/table-01/table-01.html +214 -0
- package/dist/templates/blocks/table-01/table-01.ts +266 -0
- package/dist/templates/components/checkbox/checkbox.ts +14 -5
- package/dist/templates/components/command/command-texts.ts +65 -0
- package/dist/templates/components/command/command.ts +553 -0
- package/dist/templates/components/command/index.ts +13 -0
- package/dist/templates/components/data-table/data-table-texts.ts +110 -0
- package/dist/templates/components/data-table/data-table.ts +1153 -0
- package/dist/templates/components/data-table/index.ts +18 -0
- package/dist/templates/components/sortable-list/index.ts +9 -0
- package/dist/templates/components/sortable-list/sortable-list-texts.ts +71 -0
- package/dist/templates/components/sortable-list/sortable-list.ts +510 -0
- package/dist/templates/components/tabs/tabs.ts +88 -23
- package/dist/templates/components/tree/index.ts +9 -0
- package/dist/templates/components/tree/tree-texts.ts +45 -0
- package/dist/templates/components/tree/tree.ts +730 -0
- package/dist/templates/theme/provider.ts +4 -0
- package/dist/templates/theme/types.ts +84 -0
- package/dist/ui/index.js +323 -13
- package/dist/ui/schema.json +50 -2
- package/dist/utils/pages.js +138 -0
- package/dist/utils/project.js +59 -0
- package/dist/utils/tailwind.js +84 -0
- package/dist/utils/write-files.js +63 -8
- 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,13 +8,19 @@ 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");
|
|
13
|
+
const tailwind_1 = require("../utils/tailwind");
|
|
12
14
|
const write_files_1 = require("../utils/write-files");
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.filter(([, item]) => item.type === 'component')
|
|
15
|
+
const namesOf = (type) => Object.entries(registry_1.registry.items)
|
|
16
|
+
.filter(([, item]) => item.type === type)
|
|
16
17
|
.map(([name]) => name)
|
|
17
18
|
.sort();
|
|
19
|
+
/**
|
|
20
|
+
* Lo que se puede agregar: los ítems `component` del registro y, después, los `block` (B.1-c;
|
|
21
|
+
* el mismo orden que el x-prompt).
|
|
22
|
+
*/
|
|
23
|
+
exports.AVAILABLE = [...namesOf('component'), ...namesOf('block')];
|
|
18
24
|
/**
|
|
19
25
|
* `ng g mimi button input form-field`: copia los componentes y sus dependencias del registro a la
|
|
20
26
|
* carpeta de mimi.json. Sin nombres, pregunta (x-prompt de selección múltiple).
|
|
@@ -34,12 +40,15 @@ function ui(options) {
|
|
|
34
40
|
items: [...plan.items, ...plan.implicit],
|
|
35
41
|
implicit: plan.implicit,
|
|
36
42
|
componentsDir: plan.componentsDir,
|
|
43
|
+
blocksDir: plan.blocksDir,
|
|
44
|
+
pages: plan.pages,
|
|
37
45
|
overwrite: options.overwrite ?? false,
|
|
38
46
|
version: registry_1.registry.version,
|
|
39
47
|
report,
|
|
40
48
|
}),
|
|
41
49
|
updateMimiConfig(plan, report),
|
|
42
50
|
updateGlobalCss(plan),
|
|
51
|
+
addFolderSources(plan),
|
|
43
52
|
...Object.entries(plan.newPackages).map(([name, version]) => (0, utility_1.addDependency)(name, version, {
|
|
44
53
|
type: utility_1.DependencyType.Default,
|
|
45
54
|
existing: utility_1.ExistingBehavior.Skip,
|
|
@@ -56,6 +65,7 @@ function inspect(tree, options) {
|
|
|
56
65
|
throw new schematics_1.SchematicsException('Mimi no está configurado en este proyecto (falta mimi.json). Ejecuta primero:\n' +
|
|
57
66
|
' ng add @mimi-ng/cli');
|
|
58
67
|
}
|
|
68
|
+
(0, project_1.assertAliases)(config);
|
|
59
69
|
const requested = [...new Set((options.components ?? []).map((name) => name.trim()))].filter(Boolean);
|
|
60
70
|
if (requested.length === 0) {
|
|
61
71
|
throw new schematics_1.SchematicsException(`Indica qué componentes agregar, por ejemplo: ng g mimi button\nDisponibles: ${exports.AVAILABLE.join(', ')}.`);
|
|
@@ -64,7 +74,7 @@ function inspect(tree, options) {
|
|
|
64
74
|
if (unknown.length > 0) {
|
|
65
75
|
const lines = unknown.map((name) => {
|
|
66
76
|
const suggestion = closest(name);
|
|
67
|
-
return `"${name}" no es un componente de Mimi.${suggestion ? ` ¿Quisiste decir ${suggestion}?` : ''}`;
|
|
77
|
+
return `"${name}" no es un componente ni un Block de Mimi.${suggestion ? ` ¿Quisiste decir ${suggestion}?` : ''}`;
|
|
68
78
|
});
|
|
69
79
|
throw new schematics_1.SchematicsException(`${lines.join('\n')}\nDisponibles: ${exports.AVAILABLE.join(', ')}.`);
|
|
70
80
|
}
|
|
@@ -103,9 +113,170 @@ function inspect(tree, options) {
|
|
|
103
113
|
}
|
|
104
114
|
const implicit = [...new Set(items_1.INIT_ITEMS.flatMap((item) => (0, registry_1.resolveItems)(item)))].filter((item) => !items.includes(item));
|
|
105
115
|
const componentsDir = (0, paths_1.normalizePath)(config.aliases?.components ?? 'src/app/components/ui');
|
|
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);
|
|
106
119
|
const outdated = outdatedPeers(tree, items, installed, newPackages);
|
|
107
120
|
const updatable = PEERS_WITH_MINIMUM.filter((name) => installed[name] || newPackages[name]);
|
|
108
|
-
|
|
121
|
+
const missingExclusion = mimiExclusion(tree, config, installed);
|
|
122
|
+
return {
|
|
123
|
+
componentsDir,
|
|
124
|
+
blocksDir,
|
|
125
|
+
pagesDir,
|
|
126
|
+
pages,
|
|
127
|
+
legacyPages,
|
|
128
|
+
config,
|
|
129
|
+
items,
|
|
130
|
+
implicit,
|
|
131
|
+
newPackages,
|
|
132
|
+
outdated,
|
|
133
|
+
updatable,
|
|
134
|
+
missingExclusion,
|
|
135
|
+
};
|
|
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
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Sin `@source not` hacia `.mimi/`, Tailwind escanea las copias originales (van en git) y genera
|
|
267
|
+
* sus clases aunque el usuario ya las haya cambiado en sus componentes. Proyectos de la 0.3.0 o
|
|
268
|
+
* anteriores no la tienen: se avisa para que no tengan que volver a ejecutar ng add. Con
|
|
269
|
+
* Tailwind 4.0 no se avisa aquí (no la entiende; init ya lo explicó).
|
|
270
|
+
*/
|
|
271
|
+
function mimiExclusion(tree, config, installed) {
|
|
272
|
+
const cssPath = config.tailwind?.css;
|
|
273
|
+
if (!cssPath || !tree.exists(cssPath))
|
|
274
|
+
return null;
|
|
275
|
+
if (!(0, tailwind_1.tailwindSupportsSourceNot)(tree, installed['tailwindcss']))
|
|
276
|
+
return null;
|
|
277
|
+
if (!(0, tailwind_1.missingMimiExclusion)(tree.readText(cssPath), cssPath))
|
|
278
|
+
return null;
|
|
279
|
+
return { cssPath, line: (0, tailwind_1.mimiSourceNotLine)(cssPath) };
|
|
109
280
|
}
|
|
110
281
|
// ── Versiones mínimas de cdk y aria (M.4) ─────────────────────────────────────────────
|
|
111
282
|
/**
|
|
@@ -252,16 +423,40 @@ function updateMimiConfig(plan, report) {
|
|
|
252
423
|
const skipped = new Set(report.skipped);
|
|
253
424
|
const blocked = new Set(report.blocked.map((b) => b.item));
|
|
254
425
|
const components = { ...plan.config.components };
|
|
426
|
+
const blocks = { ...plan.config.blocks };
|
|
255
427
|
for (const item of plan.items) {
|
|
256
|
-
|
|
428
|
+
const { type } = registry_1.registry.items[item];
|
|
429
|
+
if (type !== 'component' && type !== 'block')
|
|
257
430
|
continue;
|
|
258
431
|
if (blocked.has(item))
|
|
259
432
|
continue;
|
|
260
|
-
if (
|
|
433
|
+
if ((0, write_files_1.itemFiles)(item, plan.pages).some((file) => skipped.has(file)))
|
|
261
434
|
continue;
|
|
262
|
-
|
|
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 };
|
|
263
444
|
}
|
|
264
445
|
const next = { ...plan.config, components };
|
|
446
|
+
if (Object.keys(blocks).length > 0) {
|
|
447
|
+
next.blocks = blocks;
|
|
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;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
265
460
|
const text = `${JSON.stringify(next, null, 2)}\n`;
|
|
266
461
|
if (tree.readText('mimi.json') !== text)
|
|
267
462
|
tree.overwrite('mimi.json', text);
|
|
@@ -299,11 +494,70 @@ function updateGlobalCss(plan) {
|
|
|
299
494
|
context.logger.info(` ${line}`);
|
|
300
495
|
};
|
|
301
496
|
}
|
|
497
|
+
/**
|
|
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).
|
|
501
|
+
*/
|
|
502
|
+
function addFolderSources(plan) {
|
|
503
|
+
return (tree, context) => {
|
|
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)
|
|
514
|
+
return;
|
|
515
|
+
const cssPath = plan.config.tailwind?.css;
|
|
516
|
+
if (!cssPath || !tree.exists(cssPath))
|
|
517
|
+
return;
|
|
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)
|
|
528
|
+
return;
|
|
529
|
+
tree.overwrite(cssPath, css);
|
|
530
|
+
context.logger.info(`Agregado a ${cssPath} (tu @import de Tailwind usa source(none), así que Tailwind solo ` +
|
|
531
|
+
`escanea las carpetas que indicas):\n${lines.map((line) => ` ${line}`).join('\n')}`);
|
|
532
|
+
};
|
|
533
|
+
}
|
|
302
534
|
// ── Mensaje final ─────────────────────────────────────────────────────────────────────
|
|
535
|
+
/**
|
|
536
|
+
* M1: un componente que se actualizó y que usa un Block instalado (en `mimi.json → blocks`) que
|
|
537
|
+
* no se actualizó en esta ejecución (porque el usuario lo modificó, o porque no se pidió). Solo
|
|
538
|
+
* avisa: la pantalla es del usuario y puede necesitar cambios con la API nueva.
|
|
539
|
+
*/
|
|
540
|
+
function blocksUsingUpdated(plan, report, itemOf) {
|
|
541
|
+
const updated = new Set(report.updated.map(({ file }) => itemOf(file)));
|
|
542
|
+
const messages = [];
|
|
543
|
+
for (const block of Object.keys(plan.config.blocks ?? {}).sort()) {
|
|
544
|
+
if (updated.has(block) || registry_1.registry.items[block]?.type !== 'block')
|
|
545
|
+
continue;
|
|
546
|
+
const used = (0, registry_1.resolveItems)(block).filter((item) => updated.has(item) && registry_1.registry.items[item].type === 'component');
|
|
547
|
+
if (used.length === 0)
|
|
548
|
+
continue;
|
|
549
|
+
const list = used.join(', ');
|
|
550
|
+
messages.push(`Tu Block ${block} usa ${list}, que se actualizó a ${registry_1.registry.version}; revisa el ` +
|
|
551
|
+
'CHANGELOG por si tu pantalla necesita algún cambio.');
|
|
552
|
+
}
|
|
553
|
+
return messages;
|
|
554
|
+
}
|
|
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());
|
|
303
557
|
function summary(plan, report) {
|
|
304
|
-
return (
|
|
558
|
+
return (tree, context) => {
|
|
305
559
|
const log = context.logger;
|
|
306
|
-
const path = (file) =>
|
|
560
|
+
const path = (file) => (0, write_files_1.targetPathOf)(file, plan.componentsDir, plan.blocksDir, plan.pages);
|
|
307
561
|
if (report.created.length > 0) {
|
|
308
562
|
log.info('Agregado:');
|
|
309
563
|
for (const file of report.created)
|
|
@@ -313,17 +567,68 @@ function summary(plan, report) {
|
|
|
313
567
|
log.info('Reemplazado (--overwrite):');
|
|
314
568
|
for (const file of report.overwritten)
|
|
315
569
|
log.info(` ~ ${path(file)}`);
|
|
570
|
+
// Un Block es una pantalla del usuario: --overwrite la reemplaza entera (B.1, trampas).
|
|
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 ` +
|
|
584
|
+
`Mimi ${registry_1.registry.version}. Si tenías cambios, recupéralos de git.`);
|
|
585
|
+
}
|
|
316
586
|
}
|
|
317
|
-
const itemOf = (file) => [...plan.items, ...plan.implicit].find((item) =>
|
|
318
|
-
file;
|
|
587
|
+
const itemOf = (file) => [...plan.items, ...plan.implicit].find((item) => (0, write_files_1.itemFiles)(item, plan.pages).includes(file)) ?? file;
|
|
319
588
|
for (const message of (0, write_files_1.updatedMessages)(report, itemOf, registry_1.registry.version))
|
|
320
589
|
log.info(message);
|
|
590
|
+
for (const message of blocksUsingUpdated(plan, report, itemOf))
|
|
591
|
+
log.warn(message);
|
|
321
592
|
for (const file of report.skipped) {
|
|
322
593
|
log.warn(`Omitido: ${path(file)} ya existe con otro contenido (usa --overwrite para reemplazarlo).`);
|
|
323
594
|
}
|
|
324
|
-
for (const message of (0, write_files_1.blockedMessages)(report, plan.componentsDir, registry_1.registry.version)) {
|
|
595
|
+
for (const message of (0, write_files_1.blockedMessages)(report, plan.componentsDir, registry_1.registry.version, plan.blocksDir, plan.pages)) {
|
|
325
596
|
log.warn(message);
|
|
326
597
|
}
|
|
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.
|
|
600
|
+
const created = new Set(report.created);
|
|
601
|
+
for (const item of plan.items) {
|
|
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)}`);
|
|
616
|
+
continue;
|
|
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}.`);
|
|
631
|
+
}
|
|
327
632
|
if (!(0, write_files_1.hasFileChanges)(report) && report.skipped.length === 0 && report.blocked.length === 0) {
|
|
328
633
|
log.info('Nada que hacer: los archivos ya están al día.');
|
|
329
634
|
}
|
|
@@ -340,6 +645,11 @@ function summary(plan, report) {
|
|
|
340
645
|
`tienen errores conocidos con sus componentes) y tu proyecto tiene:\n${lines.join('\n')}\n` +
|
|
341
646
|
`Actualízalos con:\n ng update ${plan.updatable.join(' ')}`);
|
|
342
647
|
}
|
|
648
|
+
if (plan.missingExclusion) {
|
|
649
|
+
const { cssPath, line } = plan.missingExclusion;
|
|
650
|
+
log.warn(`Tailwind escanea ${write_files_1.BASE_DIR}/ (las copias originales de Mimi) y genera CSS de clases que ` +
|
|
651
|
+
`quizá ya no usas. Agrega a ${cssPath}, después de @import "tailwindcss":\n ${line}`);
|
|
652
|
+
}
|
|
343
653
|
if ((0, write_files_1.hasFileChanges)(report)) {
|
|
344
654
|
log.info(`Guarda la carpeta ${write_files_1.BASE_DIR.split('/')[0]}/ en git: Mimi la usa para actualizar tus ` +
|
|
345
655
|
'componentes sin perder tus cambios.');
|
package/dist/ui/schema.json
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
"items": {
|
|
10
10
|
"type": "string"
|
|
11
11
|
},
|
|
12
|
-
"description": "Componentes que se agregan (varios a la vez: button input form-field).",
|
|
12
|
+
"description": "Componentes o Blocks que se agregan (varios a la vez: button input form-field, o un Block: login-01).",
|
|
13
13
|
"$default": {
|
|
14
14
|
"$source": "argv",
|
|
15
15
|
"index": 0
|
|
16
16
|
},
|
|
17
17
|
"x-prompt": {
|
|
18
|
-
"message": "¿Qué componentes quieres agregar?",
|
|
18
|
+
"message": "¿Qué componentes o Blocks quieres agregar?",
|
|
19
19
|
"type": "list",
|
|
20
20
|
"multiselect": true,
|
|
21
21
|
"items": [
|
|
@@ -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"
|
|
@@ -162,6 +174,38 @@
|
|
|
162
174
|
{
|
|
163
175
|
"value": "tooltip",
|
|
164
176
|
"label": "tooltip"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"value": "tree",
|
|
180
|
+
"label": "tree"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"value": "login-01",
|
|
184
|
+
"label": "login-01 (Block, página)"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"value": "login-02",
|
|
188
|
+
"label": "login-02 (Block, página)"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"value": "otp-01",
|
|
192
|
+
"label": "otp-01 (Block, página)"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"value": "pricing-01",
|
|
196
|
+
"label": "pricing-01 (Block, sección)"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"value": "settings-01",
|
|
200
|
+
"label": "settings-01 (Block, página)"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"value": "signup-01",
|
|
204
|
+
"label": "signup-01 (Block, página)"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"value": "table-01",
|
|
208
|
+
"label": "table-01 (Block, página)"
|
|
165
209
|
}
|
|
166
210
|
]
|
|
167
211
|
}
|
|
@@ -170,6 +214,10 @@
|
|
|
170
214
|
"type": "boolean",
|
|
171
215
|
"default": false,
|
|
172
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."
|
|
173
221
|
}
|
|
174
222
|
}
|
|
175
223
|
}
|