@mimi-ng/cli 0.3.0 → 0.4.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 +20 -1
- package/dist/init/css.js +1 -1
- package/dist/init/index.js +25 -0
- package/dist/init/paths.js +20 -0
- package/dist/registry.json +112 -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 +105 -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 +92 -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 +212 -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 +382 -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 +176 -0
- package/dist/templates/components/tabs/tabs.ts +88 -23
- package/dist/ui/index.js +128 -10
- package/dist/ui/schema.json +26 -2
- package/dist/utils/project.js +35 -0
- package/dist/utils/tailwind.js +80 -0
- package/dist/utils/write-files.js +37 -7
- package/package.json +1 -1
|
@@ -97,37 +97,75 @@ export class MimiTabsOutlet {
|
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Orientación de la fila de pestañas (B.10): horizontal (por defecto) o vertical, para una
|
|
102
|
+
* navegación lateral. En vertical, las flechas son ↑/↓ (las pone aria) y el panel va al lado.
|
|
103
|
+
*/
|
|
104
|
+
export type MimiTabsOrientation = 'horizontal' | 'vertical';
|
|
105
|
+
|
|
100
106
|
/** El contenedor visible de las pestañas (recibe el `class` del usuario). */
|
|
101
107
|
const containerVariants = cva('relative font-sans', {
|
|
102
108
|
variants: {
|
|
103
109
|
variant: {
|
|
104
|
-
underline: 'border-
|
|
110
|
+
underline: 'border-border',
|
|
105
111
|
// 4px fijos (no rem): el indicador resta los mismos 8px.
|
|
106
|
-
pill: '
|
|
112
|
+
pill: 'rounded-[calc(var(--mimi-radius)_+_4px)] bg-[color:var(--mimi-tabs-pill-bg,var(--mimi-muted))] p-[4px]',
|
|
107
113
|
},
|
|
114
|
+
orientation: { horizontal: '', vertical: 'self-start' },
|
|
108
115
|
},
|
|
109
|
-
|
|
116
|
+
compoundVariants: [
|
|
117
|
+
{ variant: 'underline', orientation: 'horizontal', class: 'border-b' },
|
|
118
|
+
{ variant: 'underline', orientation: 'vertical', class: 'border-s' },
|
|
119
|
+
{ variant: 'pill', orientation: 'horizontal', class: 'max-w-[520px]' },
|
|
120
|
+
],
|
|
121
|
+
defaultVariants: { variant: 'underline', orientation: 'horizontal' },
|
|
110
122
|
});
|
|
111
123
|
|
|
112
124
|
/**
|
|
113
|
-
* El indicador: columnas iguales, así que su
|
|
114
|
-
* ancho por cada
|
|
125
|
+
* El indicador: columnas (o filas) iguales, así que su tamaño es el de una pestaña y se desplaza
|
|
126
|
+
* su propio ancho (o alto) por cada una (sin medir nada). En derecha a izquierda, al revés.
|
|
115
127
|
*/
|
|
116
128
|
const indicatorVariants = cva(
|
|
117
129
|
[
|
|
118
130
|
'pointer-events-none absolute',
|
|
119
|
-
'translate-x-[calc(100%_*_var(--mimi-tabs-index))] rtl:translate-x-[calc(-100%_*_var(--mimi-tabs-index))]',
|
|
120
131
|
'transition-[translate] duration-[450ms] ease-[var(--mimi-tabs-spring,cubic-bezier(0.3,1.35,0.5,1))] motion-reduce:transition-none',
|
|
121
132
|
],
|
|
122
133
|
{
|
|
123
134
|
variants: {
|
|
124
135
|
variant: {
|
|
125
|
-
underline:
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
underline: 'rounded-[2px] bg-[color:var(--mimi-tabs-indicator,var(--mimi-foreground))]',
|
|
137
|
+
pill: 'rounded-[var(--mimi-radius)] bg-[color:var(--mimi-tabs-pill-active,var(--mimi-card))] shadow-neutral',
|
|
138
|
+
},
|
|
139
|
+
orientation: {
|
|
140
|
+
horizontal:
|
|
141
|
+
'translate-x-[calc(100%_*_var(--mimi-tabs-index))] rtl:translate-x-[calc(-100%_*_var(--mimi-tabs-index))]',
|
|
142
|
+
vertical: 'translate-y-[calc(100%_*_var(--mimi-tabs-index))]',
|
|
128
143
|
},
|
|
129
144
|
},
|
|
130
|
-
|
|
145
|
+
compoundVariants: [
|
|
146
|
+
{
|
|
147
|
+
variant: 'underline',
|
|
148
|
+
orientation: 'horizontal',
|
|
149
|
+
class: '-bottom-px start-0 h-0.5 w-[calc(100%_/_var(--mimi-tabs-count))]',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
// La barra va a la izquierda (al inicio), sobre el borde del contenedor.
|
|
153
|
+
variant: 'underline',
|
|
154
|
+
orientation: 'vertical',
|
|
155
|
+
class: '-start-px top-0 w-0.5 h-[calc(100%_/_var(--mimi-tabs-count))]',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
variant: 'pill',
|
|
159
|
+
orientation: 'horizontal',
|
|
160
|
+
class: 'start-[4px] top-[4px] bottom-[4px] w-[calc((100%_-_8px)_/_var(--mimi-tabs-count))]',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
variant: 'pill',
|
|
164
|
+
orientation: 'vertical',
|
|
165
|
+
class: 'start-[4px] end-[4px] top-[4px] h-[calc((100%_-_8px)_/_var(--mimi-tabs-count))]',
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
defaultVariants: { variant: 'underline', orientation: 'horizontal' },
|
|
131
169
|
},
|
|
132
170
|
);
|
|
133
171
|
|
|
@@ -148,8 +186,10 @@ const tabVariants = cva(
|
|
|
148
186
|
// 34px sin token (el token de alto es el de la subrayada): plan D6.
|
|
149
187
|
pill: 'h-[34px] gap-1.5 rounded-[var(--mimi-radius)] text-[13.5px] [&_svg]:size-[15px] [&_svg]:shrink-0',
|
|
150
188
|
},
|
|
189
|
+
// En vertical, el texto al inicio, como una navegación lateral.
|
|
190
|
+
orientation: { horizontal: '', vertical: 'justify-start px-3 text-start' },
|
|
151
191
|
},
|
|
152
|
-
defaultVariants: { variant: 'underline' },
|
|
192
|
+
defaultVariants: { variant: 'underline', orientation: 'horizontal' },
|
|
153
193
|
},
|
|
154
194
|
);
|
|
155
195
|
|
|
@@ -196,11 +236,12 @@ const COUNT_CLASSES =
|
|
|
196
236
|
<div
|
|
197
237
|
ngTabList
|
|
198
238
|
data-slot="tablist"
|
|
239
|
+
[orientation]="orientation()"
|
|
199
240
|
[selectedTab]="painted()"
|
|
200
241
|
(selectedTabChange)="fromAria($event)"
|
|
201
242
|
[attr.aria-label]="ariaLabel() || null"
|
|
202
243
|
[attr.aria-labelledby]="ariaLabelledby() || null"
|
|
203
|
-
[class]="tablistClasses"
|
|
244
|
+
[class]="tablistClasses()"
|
|
204
245
|
(click)="choose($event)"
|
|
205
246
|
(keydown.enter)="choose($event)"
|
|
206
247
|
(keydown.space)="choose($event)"
|
|
@@ -248,12 +289,19 @@ const COUNT_CLASSES =
|
|
|
248
289
|
</div>
|
|
249
290
|
`,
|
|
250
291
|
host: {
|
|
251
|
-
|
|
292
|
+
// flex-direction va en su binding (B.10), no aquí.
|
|
293
|
+
style: 'display: flex; gap: 14px',
|
|
252
294
|
// El class es del contenedor de las pestañas; aria-label y aria-labelledby, del tablist.
|
|
253
295
|
'[attr.class]': 'null',
|
|
254
296
|
'[attr.aria-label]': 'null',
|
|
255
297
|
'[attr.aria-labelledby]': 'null',
|
|
256
298
|
'[attr.data-variant]': 'variant()',
|
|
299
|
+
'[attr.data-orientation]': 'orientation()',
|
|
300
|
+
// En vertical, las pestañas a un lado y el panel al otro. Bindings sueltos (no [style] con
|
|
301
|
+
// un objeto, que sumaba ɵɵstyleMap al JS inicial: spec 10).
|
|
302
|
+
// Siempre con valor: con null, Angular quita también el flex-direction del style estático.
|
|
303
|
+
'[style.flex-direction]': 'orientation() === "vertical" ? "row" : "column"',
|
|
304
|
+
'[style.align-items]': 'orientation() === "vertical" ? "flex-start" : null',
|
|
257
305
|
},
|
|
258
306
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
259
307
|
})
|
|
@@ -264,6 +312,8 @@ export class MimiTabs {
|
|
|
264
312
|
readonly value = model<string | undefined>(undefined);
|
|
265
313
|
/** Por defecto, `underline`. */
|
|
266
314
|
readonly variant = input<MimiTabsVariant>('underline');
|
|
315
|
+
/** Por defecto, `horizontal`. Puede cambiar en vivo (por ejemplo, con el ancho). */
|
|
316
|
+
readonly orientation = input<MimiTabsOrientation>('horizontal');
|
|
267
317
|
/** Conserva el contenido de los paneles al cambiar de pestaña (por defecto se destruye). */
|
|
268
318
|
readonly preserveContent = input(false, { transform: booleanAttribute });
|
|
269
319
|
/** Nombre accesible de la fila de pestañas. */
|
|
@@ -298,22 +348,37 @@ export class MimiTabs {
|
|
|
298
348
|
this.tabs().findIndex((tab) => tab.value() === this.painted()),
|
|
299
349
|
);
|
|
300
350
|
|
|
351
|
+
private readonly variants = computed(() => ({
|
|
352
|
+
variant: this.variant(),
|
|
353
|
+
orientation: this.orientation(),
|
|
354
|
+
}));
|
|
301
355
|
protected readonly containerClasses = computed(() =>
|
|
302
|
-
cn(containerVariants(
|
|
356
|
+
cn(containerVariants(this.variants()), this.userClass()),
|
|
303
357
|
);
|
|
304
|
-
protected readonly indicatorClasses = computed(() =>
|
|
305
|
-
|
|
306
|
-
)
|
|
307
|
-
protected readonly tabClasses = computed(() => tabVariants(
|
|
358
|
+
protected readonly indicatorClasses = computed(() => indicatorVariants(this.variants()));
|
|
359
|
+
// Con cn(): la vertical cambia `justify-center` por `justify-start` (sin él, quedaban las dos y
|
|
360
|
+
// ganaba la que va después en el CSS).
|
|
361
|
+
protected readonly tabClasses = computed(() => cn(tabVariants(this.variants())));
|
|
308
362
|
// Sin outline-none: en Tailwind 4 pone --tw-outline-style en none y anula el anillo de foco.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
363
|
+
// Las clases de cada orientación, completas (no variantes encadenadas: selectores largos, G2.1).
|
|
364
|
+
protected readonly tablistClasses = computed(() =>
|
|
365
|
+
cn(
|
|
366
|
+
this.orientation() === 'vertical'
|
|
367
|
+
? 'grid grid-rows-[repeat(var(--mimi-tabs-count),minmax(0,1fr))]'
|
|
368
|
+
: 'grid grid-cols-[repeat(var(--mimi-tabs-count),minmax(0,1fr))]',
|
|
369
|
+
'rounded-[inherit]',
|
|
370
|
+
// Con todas deshabilitadas, el foco queda en la fila (aria).
|
|
371
|
+
buttonFocusStyles,
|
|
372
|
+
),
|
|
313
373
|
);
|
|
314
374
|
protected readonly countClasses = COUNT_CLASSES;
|
|
315
375
|
protected readonly panelClasses = computed(() =>
|
|
316
|
-
cn(
|
|
376
|
+
cn(
|
|
377
|
+
'px-0.5 py-1',
|
|
378
|
+
this.orientation() === 'vertical' && 'min-w-0 flex-1',
|
|
379
|
+
buttonFocusStyles,
|
|
380
|
+
this.panelClass(),
|
|
381
|
+
),
|
|
317
382
|
);
|
|
318
383
|
|
|
319
384
|
/** El último valor que puso Mimi (no viene de fuera). */
|
package/dist/ui/index.js
CHANGED
|
@@ -9,12 +9,17 @@ const items_1 = require("../init/items");
|
|
|
9
9
|
const paths_1 = require("../init/paths");
|
|
10
10
|
const registry_1 = require("../registry");
|
|
11
11
|
const project_1 = require("../utils/project");
|
|
12
|
+
const tailwind_1 = require("../utils/tailwind");
|
|
12
13
|
const write_files_1 = require("../utils/write-files");
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.filter(([, item]) => item.type === 'component')
|
|
14
|
+
const namesOf = (type) => Object.entries(registry_1.registry.items)
|
|
15
|
+
.filter(([, item]) => item.type === type)
|
|
16
16
|
.map(([name]) => name)
|
|
17
17
|
.sort();
|
|
18
|
+
/**
|
|
19
|
+
* Lo que se puede agregar: los ítems `component` del registro y, después, los `block` (B.1-c;
|
|
20
|
+
* el mismo orden que el x-prompt).
|
|
21
|
+
*/
|
|
22
|
+
exports.AVAILABLE = [...namesOf('component'), ...namesOf('block')];
|
|
18
23
|
/**
|
|
19
24
|
* `ng g mimi button input form-field`: copia los componentes y sus dependencias del registro a la
|
|
20
25
|
* carpeta de mimi.json. Sin nombres, pregunta (x-prompt de selección múltiple).
|
|
@@ -34,12 +39,14 @@ function ui(options) {
|
|
|
34
39
|
items: [...plan.items, ...plan.implicit],
|
|
35
40
|
implicit: plan.implicit,
|
|
36
41
|
componentsDir: plan.componentsDir,
|
|
42
|
+
blocksDir: plan.blocksDir,
|
|
37
43
|
overwrite: options.overwrite ?? false,
|
|
38
44
|
version: registry_1.registry.version,
|
|
39
45
|
report,
|
|
40
46
|
}),
|
|
41
47
|
updateMimiConfig(plan, report),
|
|
42
48
|
updateGlobalCss(plan),
|
|
49
|
+
addBlocksSource(plan),
|
|
43
50
|
...Object.entries(plan.newPackages).map(([name, version]) => (0, utility_1.addDependency)(name, version, {
|
|
44
51
|
type: utility_1.DependencyType.Default,
|
|
45
52
|
existing: utility_1.ExistingBehavior.Skip,
|
|
@@ -56,6 +63,7 @@ function inspect(tree, options) {
|
|
|
56
63
|
throw new schematics_1.SchematicsException('Mimi no está configurado en este proyecto (falta mimi.json). Ejecuta primero:\n' +
|
|
57
64
|
' ng add @mimi-ng/cli');
|
|
58
65
|
}
|
|
66
|
+
(0, project_1.assertAliases)(config);
|
|
59
67
|
const requested = [...new Set((options.components ?? []).map((name) => name.trim()))].filter(Boolean);
|
|
60
68
|
if (requested.length === 0) {
|
|
61
69
|
throw new schematics_1.SchematicsException(`Indica qué componentes agregar, por ejemplo: ng g mimi button\nDisponibles: ${exports.AVAILABLE.join(', ')}.`);
|
|
@@ -64,7 +72,7 @@ function inspect(tree, options) {
|
|
|
64
72
|
if (unknown.length > 0) {
|
|
65
73
|
const lines = unknown.map((name) => {
|
|
66
74
|
const suggestion = closest(name);
|
|
67
|
-
return `"${name}" no es un componente de Mimi.${suggestion ? ` ¿Quisiste decir ${suggestion}?` : ''}`;
|
|
75
|
+
return `"${name}" no es un componente ni un Block de Mimi.${suggestion ? ` ¿Quisiste decir ${suggestion}?` : ''}`;
|
|
68
76
|
});
|
|
69
77
|
throw new schematics_1.SchematicsException(`${lines.join('\n')}\nDisponibles: ${exports.AVAILABLE.join(', ')}.`);
|
|
70
78
|
}
|
|
@@ -103,9 +111,37 @@ function inspect(tree, options) {
|
|
|
103
111
|
}
|
|
104
112
|
const implicit = [...new Set(items_1.INIT_ITEMS.flatMap((item) => (0, registry_1.resolveItems)(item)))].filter((item) => !items.includes(item));
|
|
105
113
|
const componentsDir = (0, paths_1.normalizePath)(config.aliases?.components ?? 'src/app/components/ui');
|
|
114
|
+
const blocksDir = (0, paths_1.normalizePath)(config.aliases?.blocks ?? (0, project_1.defaultBlocksDir)(componentsDir));
|
|
106
115
|
const outdated = outdatedPeers(tree, items, installed, newPackages);
|
|
107
116
|
const updatable = PEERS_WITH_MINIMUM.filter((name) => installed[name] || newPackages[name]);
|
|
108
|
-
|
|
117
|
+
const missingExclusion = mimiExclusion(tree, config, installed);
|
|
118
|
+
return {
|
|
119
|
+
componentsDir,
|
|
120
|
+
blocksDir,
|
|
121
|
+
config,
|
|
122
|
+
items,
|
|
123
|
+
implicit,
|
|
124
|
+
newPackages,
|
|
125
|
+
outdated,
|
|
126
|
+
updatable,
|
|
127
|
+
missingExclusion,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Sin `@source not` hacia `.mimi/`, Tailwind escanea las copias originales (van en git) y genera
|
|
132
|
+
* sus clases aunque el usuario ya las haya cambiado en sus componentes. Proyectos de la 0.3.0 o
|
|
133
|
+
* anteriores no la tienen: se avisa para que no tengan que volver a ejecutar ng add. Con
|
|
134
|
+
* Tailwind 4.0 no se avisa aquí (no la entiende; init ya lo explicó).
|
|
135
|
+
*/
|
|
136
|
+
function mimiExclusion(tree, config, installed) {
|
|
137
|
+
const cssPath = config.tailwind?.css;
|
|
138
|
+
if (!cssPath || !tree.exists(cssPath))
|
|
139
|
+
return null;
|
|
140
|
+
if (!(0, tailwind_1.tailwindSupportsSourceNot)(tree, installed['tailwindcss']))
|
|
141
|
+
return null;
|
|
142
|
+
if (!(0, tailwind_1.missingMimiExclusion)(tree.readText(cssPath), cssPath))
|
|
143
|
+
return null;
|
|
144
|
+
return { cssPath, line: (0, tailwind_1.mimiSourceNotLine)(cssPath) };
|
|
109
145
|
}
|
|
110
146
|
// ── Versiones mínimas de cdk y aria (M.4) ─────────────────────────────────────────────
|
|
111
147
|
/**
|
|
@@ -252,16 +288,25 @@ function updateMimiConfig(plan, report) {
|
|
|
252
288
|
const skipped = new Set(report.skipped);
|
|
253
289
|
const blocked = new Set(report.blocked.map((b) => b.item));
|
|
254
290
|
const components = { ...plan.config.components };
|
|
291
|
+
const blocks = { ...plan.config.blocks };
|
|
255
292
|
for (const item of plan.items) {
|
|
256
|
-
|
|
293
|
+
const { type, files } = registry_1.registry.items[item];
|
|
294
|
+
if (type !== 'component' && type !== 'block')
|
|
257
295
|
continue;
|
|
258
296
|
if (blocked.has(item))
|
|
259
297
|
continue;
|
|
260
|
-
if (
|
|
298
|
+
if (files.some((file) => skipped.has(file)))
|
|
261
299
|
continue;
|
|
262
|
-
components[item] = { version: registry_1.registry.version };
|
|
300
|
+
(type === 'block' ? blocks : components)[item] = { version: registry_1.registry.version };
|
|
263
301
|
}
|
|
264
302
|
const next = { ...plan.config, components };
|
|
303
|
+
if (Object.keys(blocks).length > 0) {
|
|
304
|
+
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 };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
265
310
|
const text = `${JSON.stringify(next, null, 2)}\n`;
|
|
266
311
|
if (tree.readText('mimi.json') !== text)
|
|
267
312
|
tree.overwrite('mimi.json', text);
|
|
@@ -299,11 +344,58 @@ function updateGlobalCss(plan) {
|
|
|
299
344
|
context.logger.info(` ${line}`);
|
|
300
345
|
};
|
|
301
346
|
}
|
|
347
|
+
/**
|
|
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).
|
|
350
|
+
*/
|
|
351
|
+
function addBlocksSource(plan) {
|
|
352
|
+
return (tree, context) => {
|
|
353
|
+
if (!plan.items.some((item) => registry_1.registry.items[item].type === 'block'))
|
|
354
|
+
return;
|
|
355
|
+
const cssPath = plan.config.tailwind?.css;
|
|
356
|
+
if (!cssPath || !tree.exists(cssPath))
|
|
357
|
+
return;
|
|
358
|
+
const result = (0, tailwind_1.withBlocksSource)(tree.readText(cssPath), cssPath, plan.componentsDir, plan.blocksDir);
|
|
359
|
+
if (!result)
|
|
360
|
+
return;
|
|
361
|
+
tree.overwrite(cssPath, result.css);
|
|
362
|
+
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}`);
|
|
364
|
+
};
|
|
365
|
+
}
|
|
302
366
|
// ── Mensaje final ─────────────────────────────────────────────────────────────────────
|
|
367
|
+
/**
|
|
368
|
+
* M1: un componente que se actualizó y que usa un Block instalado (en `mimi.json → blocks`) que
|
|
369
|
+
* no se actualizó en esta ejecución (porque el usuario lo modificó, o porque no se pidió). Solo
|
|
370
|
+
* avisa: la pantalla es del usuario y puede necesitar cambios con la API nueva.
|
|
371
|
+
*/
|
|
372
|
+
function blocksUsingUpdated(plan, report, itemOf) {
|
|
373
|
+
const updated = new Set(report.updated.map(({ file }) => itemOf(file)));
|
|
374
|
+
const messages = [];
|
|
375
|
+
for (const block of Object.keys(plan.config.blocks ?? {}).sort()) {
|
|
376
|
+
if (updated.has(block) || registry_1.registry.items[block]?.type !== 'block')
|
|
377
|
+
continue;
|
|
378
|
+
const used = (0, registry_1.resolveItems)(block).filter((item) => updated.has(item) && registry_1.registry.items[item].type === 'component');
|
|
379
|
+
if (used.length === 0)
|
|
380
|
+
continue;
|
|
381
|
+
const list = used.join(', ');
|
|
382
|
+
messages.push(`Tu Block ${block} usa ${list}, que se actualizó a ${registry_1.registry.version}; revisa el ` +
|
|
383
|
+
'CHANGELOG por si tu pantalla necesita algún cambio.');
|
|
384
|
+
}
|
|
385
|
+
return messages;
|
|
386
|
+
}
|
|
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
|
+
}
|
|
303
395
|
function summary(plan, report) {
|
|
304
396
|
return (_tree, context) => {
|
|
305
397
|
const log = context.logger;
|
|
306
|
-
const path = (file) =>
|
|
398
|
+
const path = (file) => (0, write_files_1.targetPathOf)(file, plan.componentsDir, plan.blocksDir);
|
|
307
399
|
if (report.created.length > 0) {
|
|
308
400
|
log.info('Agregado:');
|
|
309
401
|
for (const file of report.created)
|
|
@@ -313,17 +405,38 @@ function summary(plan, report) {
|
|
|
313
405
|
log.info('Reemplazado (--overwrite):');
|
|
314
406
|
for (const file of report.overwritten)
|
|
315
407
|
log.info(` ~ ${path(file)}`);
|
|
408
|
+
// 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 ` +
|
|
414
|
+
`Mimi ${registry_1.registry.version}. Si tenías cambios, recupéralos de git.`);
|
|
415
|
+
}
|
|
316
416
|
}
|
|
317
417
|
const itemOf = (file) => [...plan.items, ...plan.implicit].find((item) => registry_1.registry.items[item].files.includes(file)) ??
|
|
318
418
|
file;
|
|
319
419
|
for (const message of (0, write_files_1.updatedMessages)(report, itemOf, registry_1.registry.version))
|
|
320
420
|
log.info(message);
|
|
421
|
+
for (const message of blocksUsingUpdated(plan, report, itemOf))
|
|
422
|
+
log.warn(message);
|
|
321
423
|
for (const file of report.skipped) {
|
|
322
424
|
log.warn(`Omitido: ${path(file)} ya existe con otro contenido (usa --overwrite para reemplazarlo).`);
|
|
323
425
|
}
|
|
324
|
-
for (const message of (0, write_files_1.blockedMessages)(report, plan.componentsDir, registry_1.registry.version)) {
|
|
426
|
+
for (const message of (0, write_files_1.blockedMessages)(report, plan.componentsDir, registry_1.registry.version, plan.blocksDir)) {
|
|
325
427
|
log.warn(message);
|
|
326
428
|
}
|
|
429
|
+
// Un Block nuevo es una página: se enruta (B.1, «API pública»).
|
|
430
|
+
const created = new Set(report.created);
|
|
431
|
+
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)))
|
|
434
|
+
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)}').`);
|
|
439
|
+
}
|
|
327
440
|
if (!(0, write_files_1.hasFileChanges)(report) && report.skipped.length === 0 && report.blocked.length === 0) {
|
|
328
441
|
log.info('Nada que hacer: los archivos ya están al día.');
|
|
329
442
|
}
|
|
@@ -340,6 +453,11 @@ function summary(plan, report) {
|
|
|
340
453
|
`tienen errores conocidos con sus componentes) y tu proyecto tiene:\n${lines.join('\n')}\n` +
|
|
341
454
|
`Actualízalos con:\n ng update ${plan.updatable.join(' ')}`);
|
|
342
455
|
}
|
|
456
|
+
if (plan.missingExclusion) {
|
|
457
|
+
const { cssPath, line } = plan.missingExclusion;
|
|
458
|
+
log.warn(`Tailwind escanea ${write_files_1.BASE_DIR}/ (las copias originales de Mimi) y genera CSS de clases que ` +
|
|
459
|
+
`quizá ya no usas. Agrega a ${cssPath}, después de @import "tailwindcss":\n ${line}`);
|
|
460
|
+
}
|
|
343
461
|
if ((0, write_files_1.hasFileChanges)(report)) {
|
|
344
462
|
log.info(`Guarda la carpeta ${write_files_1.BASE_DIR.split('/')[0]}/ en git: Mimi la usa para actualizar tus ` +
|
|
345
463
|
'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": [
|
|
@@ -162,6 +162,30 @@
|
|
|
162
162
|
{
|
|
163
163
|
"value": "tooltip",
|
|
164
164
|
"label": "tooltip"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"value": "login-01",
|
|
168
|
+
"label": "login-01 (Block)"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"value": "login-02",
|
|
172
|
+
"label": "login-02 (Block)"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"value": "otp-01",
|
|
176
|
+
"label": "otp-01 (Block)"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"value": "pricing-01",
|
|
180
|
+
"label": "pricing-01 (Block)"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"value": "settings-01",
|
|
184
|
+
"label": "settings-01 (Block)"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"value": "signup-01",
|
|
188
|
+
"label": "signup-01 (Block)"
|
|
165
189
|
}
|
|
166
190
|
]
|
|
167
191
|
}
|
package/dist/utils/project.js
CHANGED
|
@@ -1,12 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MIMI_COLLECTION = void 0;
|
|
4
|
+
exports.defaultBlocksDir = defaultBlocksDir;
|
|
5
|
+
exports.assertAliases = assertAliases;
|
|
4
6
|
exports.readJson = readJson;
|
|
5
7
|
exports.allDependencies = allDependencies;
|
|
6
8
|
exports.majorOf = majorOf;
|
|
7
9
|
const schematics_1 = require("@angular-devkit/schematics");
|
|
10
|
+
const paths_1 = require("../init/paths");
|
|
8
11
|
/** Nombre de esta colección (el paquete). */
|
|
9
12
|
exports.MIMI_COLLECTION = '@mimi-ng/cli';
|
|
13
|
+
/**
|
|
14
|
+
* Carpeta de los Blocks si mimi.json no la dice: la hermana de `components/` cuando la de
|
|
15
|
+
* componentes es la de siempre (`src/app/components/ui` → `src/app/blocks`), o si no, la hermana
|
|
16
|
+
* de la carpeta de componentes (`src/app/ui` → `src/app/blocks`).
|
|
17
|
+
*/
|
|
18
|
+
function defaultBlocksDir(componentsDir) {
|
|
19
|
+
const parts = componentsDir.split('/').filter(Boolean);
|
|
20
|
+
if (parts.length >= 2 && parts.at(-1) === 'ui' && parts.at(-2) === 'components') {
|
|
21
|
+
parts.splice(-2, 2);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
parts.pop();
|
|
25
|
+
}
|
|
26
|
+
return [...parts, 'blocks'].join('/');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* M2: falla con un error claro (sin escribir nada) si `aliases.components` o `aliases.blocks` de
|
|
30
|
+
* mimi.json es absoluto, tiene `..` o sale del proyecto.
|
|
31
|
+
*/
|
|
32
|
+
function assertAliases(config) {
|
|
33
|
+
for (const name of ['components', 'blocks']) {
|
|
34
|
+
const value = config.aliases?.[name];
|
|
35
|
+
if (value === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
const reason = (0, paths_1.invalidAliasReason)(value);
|
|
38
|
+
if (!reason)
|
|
39
|
+
continue;
|
|
40
|
+
const example = name === 'blocks' ? 'src/app/blocks' : 'src/app/components/ui';
|
|
41
|
+
throw new schematics_1.SchematicsException(`mimi.json → aliases.${name} ${reason}: ${JSON.stringify(value)}. Usa una ruta relativa a ` +
|
|
42
|
+
`la raíz del workspace y dentro de ella (por ejemplo "${example}"). No se cambió nada.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
10
45
|
function readJson(tree, path) {
|
|
11
46
|
if (!tree.exists(path))
|
|
12
47
|
return null;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tailwindSupportsSourceNot = tailwindSupportsSourceNot;
|
|
4
|
+
exports.withBlocksSource = withBlocksSource;
|
|
5
|
+
exports.mimiSourceNotLine = mimiSourceNotLine;
|
|
6
|
+
exports.mimiSourceNotPath = mimiSourceNotPath;
|
|
7
|
+
exports.missingMimiExclusion = missingMimiExclusion;
|
|
8
|
+
const css_1 = require("../init/css");
|
|
9
|
+
const paths_1 = require("../init/paths");
|
|
10
|
+
/** La carpeta de las copias originales (`.mimi/base/`), en la raíz del workspace. */
|
|
11
|
+
const MIMI_DIR = '.mimi';
|
|
12
|
+
/**
|
|
13
|
+
* ¿El Tailwind del proyecto entiende `@source not` (4.1 o superior)? Manda la versión instalada
|
|
14
|
+
* (`node_modules`, la que corre de verdad); si no está instalado, el rango de package.json: solo
|
|
15
|
+
* un rango que no puede pasar de la 4.0 (`4.0.5`, `~4.0.17`) cuenta como 4.0. `^4.0.17` instala
|
|
16
|
+
* la última 4.x, así que sí lo entiende.
|
|
17
|
+
*/
|
|
18
|
+
function tailwindSupportsSourceNot(tree, declared) {
|
|
19
|
+
const file = tree.read('node_modules/tailwindcss/package.json');
|
|
20
|
+
const installed = file
|
|
21
|
+
? JSON.parse(file.toString('utf-8')).version
|
|
22
|
+
: undefined;
|
|
23
|
+
if (installed)
|
|
24
|
+
return atLeast41(installed);
|
|
25
|
+
if (!declared)
|
|
26
|
+
return true;
|
|
27
|
+
// Solo un rango exacto o con `~` fija el minor; `^4.0.17`, `>=4`, `^4`, `latest`: la última 4.x.
|
|
28
|
+
const match = /^\s*(?:~|=|v)?\s*(\d+)\.(\d+)/.exec(declared);
|
|
29
|
+
if (!match)
|
|
30
|
+
return true;
|
|
31
|
+
const [major, minor] = [Number(match[1]), Number(match[2])];
|
|
32
|
+
return major > 4 || (major === 4 && minor >= 1);
|
|
33
|
+
}
|
|
34
|
+
function atLeast41(version) {
|
|
35
|
+
const [major, minor] = version.split('.').map((n) => Number.parseInt(n, 10) || 0);
|
|
36
|
+
return major > 4 || (major === 4 && minor >= 1);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Con `source(none)`, Tailwind solo escanea lo que dice un `@source`: la carpeta de los Blocks
|
|
40
|
+
* necesita el suyo (G1), como la de componentes. Devuelve el CSS con la línea agregada (después
|
|
41
|
+
* del `@source` de componentes, o del tema), o `null` si no hace falta: sin `source(none)`, o si
|
|
42
|
+
* ya está (comillas simples o dobles).
|
|
43
|
+
*/
|
|
44
|
+
function withBlocksSource(css, cssPath, componentsDir, blocksDir) {
|
|
45
|
+
const tailwind = (0, css_1.findTailwindImport)(css);
|
|
46
|
+
if (!tailwind?.sourceNone)
|
|
47
|
+
return null;
|
|
48
|
+
const cssDir = (0, paths_1.dirnameOf)(cssPath);
|
|
49
|
+
const path = (0, paths_1.relativePath)(cssDir, blocksDir);
|
|
50
|
+
if ((0, css_1.hasDirective)(css, '@source', path))
|
|
51
|
+
return null;
|
|
52
|
+
const line = `@source "${path}";`;
|
|
53
|
+
const after = new RegExp(String.raw `^[ \t]*@source\s+(["'])` +
|
|
54
|
+
(0, paths_1.relativePath)(cssDir, componentsDir).replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
|
|
55
|
+
String.raw `\1[^;\n]*;[^\n]*$`, 'm').exec(css);
|
|
56
|
+
if (after) {
|
|
57
|
+
const end = after.index + after[0].length;
|
|
58
|
+
const eol = /\r\n/.test(css) ? '\r\n' : '\n';
|
|
59
|
+
return { css: css.slice(0, end) + eol + line + css.slice(end), line };
|
|
60
|
+
}
|
|
61
|
+
const updated = (0, css_1.insertAfterTheme)(css, [line]);
|
|
62
|
+
return updated === null ? null : { css: updated, line };
|
|
63
|
+
}
|
|
64
|
+
/** La línea que excluye `.mimi/` del escaneo de Tailwind, relativa al CSS global. */
|
|
65
|
+
function mimiSourceNotLine(cssPath) {
|
|
66
|
+
return `@source not "${mimiSourceNotPath(cssPath)}";`;
|
|
67
|
+
}
|
|
68
|
+
function mimiSourceNotPath(cssPath) {
|
|
69
|
+
return (0, paths_1.relativePath)((0, paths_1.dirnameOf)(cssPath), MIMI_DIR);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* ¿Falta la exclusión de `.mimi/` en este CSS? No hace falta con `source(none)` (Tailwind no
|
|
73
|
+
* escanea nada por su cuenta) ni si ya está, con comillas simples o dobles.
|
|
74
|
+
*/
|
|
75
|
+
function missingMimiExclusion(css, cssPath) {
|
|
76
|
+
const tailwind = (0, css_1.findTailwindImport)(css);
|
|
77
|
+
if (!tailwind || tailwind.sourceNone)
|
|
78
|
+
return false;
|
|
79
|
+
return !(0, css_1.hasDirective)(css, '@source not', mimiSourceNotPath(cssPath));
|
|
80
|
+
}
|