@mimi-ng/cli 0.1.1 → 0.2.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 (61) hide show
  1. package/README.md +14 -1
  2. package/dist/init/css.js +17 -0
  3. package/dist/init/index.js +7 -5
  4. package/dist/init/items.js +9 -0
  5. package/dist/registry.json +222 -1
  6. package/dist/templates/components/avatar/avatar.ts +1 -1
  7. package/dist/templates/components/avatar/avatar.variants.ts +4 -4
  8. package/dist/templates/components/badge/badge.ts +41 -6
  9. package/dist/templates/components/badge/badge.variants.ts +105 -11
  10. package/dist/templates/components/button/button.ts +74 -7
  11. package/dist/templates/components/button/button.variants.ts +145 -21
  12. package/dist/templates/components/card/card.ts +8 -5
  13. package/dist/templates/components/checkbox/checkbox.ts +3 -3
  14. package/dist/templates/components/confirm/confirm-texts.ts +46 -0
  15. package/dist/templates/components/confirm/confirm.ts +190 -0
  16. package/dist/templates/components/confirm/index.ts +2 -0
  17. package/dist/templates/components/context-menu/context-menu.ts +378 -0
  18. package/dist/templates/components/context-menu/index.ts +14 -0
  19. package/dist/templates/components/dialog/dialog-texts.ts +41 -0
  20. package/dist/templates/components/dialog/dialog.ts +701 -0
  21. package/dist/templates/components/dialog/index.ts +18 -0
  22. package/dist/templates/components/dropdown-menu/dropdown-menu.ts +252 -0
  23. package/dist/templates/components/dropdown-menu/index.ts +14 -0
  24. package/dist/templates/components/form-field/form-error.ts +1 -1
  25. package/dist/templates/components/form-field/form-field.ts +3 -2
  26. package/dist/templates/components/input/input.variants.ts +6 -4
  27. package/dist/templates/components/menu/index.ts +15 -0
  28. package/dist/templates/components/menu/menu.ts +656 -0
  29. package/dist/templates/components/pagination/index.ts +2 -0
  30. package/dist/templates/components/pagination/pagination-page-size.ts +74 -0
  31. package/dist/templates/components/pagination/pagination-texts.ts +69 -0
  32. package/dist/templates/components/pagination/pagination.ts +443 -0
  33. package/dist/templates/components/popover/index.ts +12 -0
  34. package/dist/templates/components/popover/popover.ts +399 -0
  35. package/dist/templates/components/select/index.ts +12 -0
  36. package/dist/templates/components/select/select-texts.ts +62 -0
  37. package/dist/templates/components/select/select.ts +1085 -0
  38. package/dist/templates/components/separator/separator.variants.ts +10 -7
  39. package/dist/templates/components/skeleton/skeleton.ts +4 -1
  40. package/dist/templates/components/switch/switch.ts +6 -6
  41. package/dist/templates/components/tabs/index.ts +2 -0
  42. package/dist/templates/components/tabs/tabs.ts +426 -0
  43. package/dist/templates/components/textarea/textarea.variants.ts +14 -11
  44. package/dist/templates/components/toast/index.ts +3 -0
  45. package/dist/templates/components/toast/toast-config.ts +45 -0
  46. package/dist/templates/components/toast/toast-texts.ts +46 -0
  47. package/dist/templates/components/toast/toast.ts +1000 -0
  48. package/dist/templates/components/toolbar/index.ts +1 -0
  49. package/dist/templates/components/toolbar/toolbar.ts +377 -0
  50. package/dist/templates/components/tooltip/index.ts +7 -0
  51. package/dist/templates/components/tooltip/tooltip.ts +407 -0
  52. package/dist/templates/theme/provider.ts +62 -6
  53. package/dist/templates/theme/theme-base.css +307 -9
  54. package/dist/templates/theme/types.ts +278 -4
  55. package/dist/templates/utils/cn.ts +15 -1
  56. package/dist/templates/utils/control-styles.ts +2 -2
  57. package/dist/templates/utils/overlay.ts +140 -0
  58. package/dist/ui/index.js +80 -8
  59. package/dist/ui/schema.json +44 -0
  60. package/dist/utils/write-files.js +156 -59
  61. package/package.json +1 -1
package/dist/ui/index.js CHANGED
@@ -4,6 +4,8 @@ exports.AVAILABLE = void 0;
4
4
  exports.ui = ui;
5
5
  const schematics_1 = require("@angular-devkit/schematics");
6
6
  const utility_1 = require("@schematics/angular/utility");
7
+ const css_1 = require("../init/css");
8
+ const items_1 = require("../init/items");
7
9
  const paths_1 = require("../init/paths");
8
10
  const registry_1 = require("../registry");
9
11
  const project_1 = require("../utils/project");
@@ -17,8 +19,10 @@ exports.AVAILABLE = Object.entries(registry_1.registry.items)
17
19
  * `ng g mimi button input form-field`: copia los componentes y sus dependencias del registro a la
18
20
  * carpeta de mimi.json. Sin nombres, pregunta (x-prompt de selección múltiple).
19
21
  *
20
- * Requiere init (mimi.json). Archivos existentes: igual → nada; distinto → se omite con aviso;
21
- * --overwrite → se reemplaza. Guarda la copia original en .mimi/base/ y la versión de cada
22
+ * Requiere init (mimi.json). Revisa también el tema y lo que copia init, y lo actualiza si el
23
+ * usuario no lo modificó. Todo o nada por componente (ver writeTemplates): si el usuario modificó
24
+ * un archivo de una versión anterior, ese componente y los que dependen de él no se tocan, con
25
+ * un aviso; --overwrite reemplaza. Guarda la copia original en .mimi/base/ y la versión de cada
22
26
  * componente en mimi.json. Instala las dependencias que falten con el gestor del proyecto.
23
27
  */
24
28
  function ui(options) {
@@ -27,13 +31,15 @@ function ui(options) {
27
31
  const report = (0, write_files_1.emptyReport)();
28
32
  return (0, schematics_1.chain)([
29
33
  (0, write_files_1.writeTemplates)({
30
- files: plan.items.flatMap((item) => registry_1.registry.items[item].files),
34
+ items: [...plan.items, ...plan.implicit],
35
+ implicit: plan.implicit,
31
36
  componentsDir: plan.componentsDir,
32
37
  overwrite: options.overwrite ?? false,
33
38
  version: registry_1.registry.version,
34
39
  report,
35
40
  }),
36
41
  updateMimiConfig(plan, report),
42
+ updateGlobalCss(plan),
37
43
  ...Object.entries(plan.newPackages).map(([name, version]) => (0, utility_1.addDependency)(name, version, {
38
44
  type: utility_1.DependencyType.Default,
39
45
  existing: utility_1.ExistingBehavior.Skip,
@@ -82,11 +88,37 @@ function inspect(tree, options) {
82
88
  for (const [name, version] of Object.entries(registry_1.registry.items[item].peerDependencies ?? {})) {
83
89
  if (installed[name])
84
90
  continue;
85
- newPackages[name] = name.startsWith('@angular/') && angularRange ? angularRange : version;
91
+ newPackages[name] =
92
+ name === '@angular/aria'
93
+ ? ariaVersion(tree, installed, version)
94
+ : name.startsWith('@angular/') && angularRange
95
+ ? angularRange
96
+ : version;
86
97
  }
87
98
  }
99
+ // aria necesita el cdk (bidi, a11y, platform). Un ítem que solo importa aria (Tabs) no declara
100
+ // el cdk como peer; sin otro ítem que lo traiga, va con la misma versión que aria.
101
+ if (newPackages['@angular/aria'] && !installed['@angular/cdk'] && !newPackages['@angular/cdk']) {
102
+ newPackages['@angular/cdk'] = newPackages['@angular/aria'];
103
+ }
104
+ const implicit = [...new Set(items_1.INIT_ITEMS.flatMap((item) => (0, registry_1.resolveItems)(item)))].filter((item) => !items.includes(item));
88
105
  const componentsDir = (0, paths_1.normalizePath)(config.aliases?.components ?? 'src/app/components/ui');
89
- return { componentsDir, config, items, newPackages };
106
+ return { componentsDir, config, items, implicit, newPackages };
107
+ }
108
+ /**
109
+ * @angular/aria pide como peer la versión exacta de @angular/cdk (22.1.7 → cdk 22.1.7). Si el cdk
110
+ * está instalado, aria va en esa versión (de `node_modules`, que el árbol del schematic lee del
111
+ * disco); si está declarado pero sin instalar, con su mismo rango; si no hay cdk, con el rango de
112
+ * `@angular/core`, igual que el cdk que se agrega junto con él (lo declare otro ítem o no).
113
+ */
114
+ function ariaVersion(tree, installed, fallback) {
115
+ const cdk = tree.read('node_modules/@angular/cdk/package.json');
116
+ if (cdk) {
117
+ const { version } = JSON.parse(cdk.toString('utf-8'));
118
+ if (version)
119
+ return version;
120
+ }
121
+ return installed['@angular/cdk'] ?? installed['@angular/core'] ?? fallback;
90
122
  }
91
123
  /** El componente más parecido (distancia de edición ≤ 2, o que empiece igual). */
92
124
  function closest(name) {
@@ -115,15 +147,19 @@ function editDistance(a, b) {
115
147
  // ── mimi.json ─────────────────────────────────────────────────────────────────────────
116
148
  /**
117
149
  * Registra la versión de cada componente cuyos archivos quedaron iguales a la plantilla. Si
118
- * alguno se omitió por estar modificado, se conserva lo que había.
150
+ * alguno se omitió por estar modificado, o el componente no se escribió (bloqueado), se conserva
151
+ * lo que había (y uno que no se agregó no aparece).
119
152
  */
120
153
  function updateMimiConfig(plan, report) {
121
154
  return (tree) => {
122
155
  const skipped = new Set(report.skipped);
156
+ const blocked = new Set(report.blocked.map((b) => b.item));
123
157
  const components = { ...plan.config.components };
124
158
  for (const item of plan.items) {
125
159
  if (registry_1.registry.items[item].type !== 'component')
126
160
  continue;
161
+ if (blocked.has(item))
162
+ continue;
127
163
  if (registry_1.registry.items[item].files.some((file) => skipped.has(file)))
128
164
  continue;
129
165
  components[item] = { version: registry_1.registry.version };
@@ -134,6 +170,38 @@ function updateMimiConfig(plan, report) {
134
170
  tree.overwrite('mimi.json', text);
135
171
  };
136
172
  }
173
+ // ── CSS global ────────────────────────────────────────────────────────────────────────
174
+ /**
175
+ * Agrega los `cssImports` de los ítems (p. ej. `@angular/cdk/overlay-prebuilt.css`, que pide la
176
+ * guía del CDK) al CSS global de mimi.json, una sola vez. Van después del `@import` del tema (o
177
+ * del de Tailwind): los `@import` tienen que ir antes que cualquier otra regla, y así quedan
178
+ * junto a los de Mimi. Si no se encuentra el archivo, se explica cómo agregarlos a mano.
179
+ */
180
+ function updateGlobalCss(plan) {
181
+ return (tree, context) => {
182
+ const imports = [
183
+ ...new Set(plan.items.flatMap((item) => registry_1.registry.items[item].cssImports ?? [])),
184
+ ];
185
+ if (imports.length === 0)
186
+ return;
187
+ const cssPath = plan.config.tailwind?.css;
188
+ const css = cssPath && tree.exists(cssPath) ? tree.readText(cssPath) : null;
189
+ const missing = imports.filter((path) => !css || !(0, css_1.hasDirective)(css, '@import', path));
190
+ if (missing.length === 0)
191
+ return;
192
+ const lines = missing.map((path) => `@import "${path}";`);
193
+ const updated = css === null ? null : (0, css_1.insertAfterTheme)(css, lines);
194
+ if (!cssPath || updated === null) {
195
+ context.logger.warn(`No se encontró el CSS global${cssPath ? ` (${cssPath})` : ''} con el @import de ` +
196
+ `Tailwind. Agrega a mano, después del tema de Mimi:\n${lines.map((l) => ` ${l}`).join('\n')}`);
197
+ return;
198
+ }
199
+ tree.overwrite(cssPath, updated);
200
+ context.logger.info(`Agregado a ${cssPath}:`);
201
+ for (const line of lines)
202
+ context.logger.info(` ${line}`);
203
+ };
204
+ }
137
205
  // ── Mensaje final ─────────────────────────────────────────────────────────────────────
138
206
  function summary(plan, report) {
139
207
  return (_tree, context) => {
@@ -149,13 +217,17 @@ function summary(plan, report) {
149
217
  for (const file of report.overwritten)
150
218
  log.info(` ~ ${path(file)}`);
151
219
  }
152
- const itemOf = (file) => plan.items.find((item) => registry_1.registry.items[item].files.includes(file)) ?? file;
220
+ const itemOf = (file) => [...plan.items, ...plan.implicit].find((item) => registry_1.registry.items[item].files.includes(file)) ??
221
+ file;
153
222
  for (const message of (0, write_files_1.updatedMessages)(report, itemOf, registry_1.registry.version))
154
223
  log.info(message);
155
224
  for (const file of report.skipped) {
156
225
  log.warn(`Omitido: ${path(file)} ya existe con otro contenido (usa --overwrite para reemplazarlo).`);
157
226
  }
158
- if (!(0, write_files_1.hasFileChanges)(report) && report.skipped.length === 0) {
227
+ for (const message of (0, write_files_1.blockedMessages)(report, plan.componentsDir, registry_1.registry.version)) {
228
+ log.warn(message);
229
+ }
230
+ if (!(0, write_files_1.hasFileChanges)(report) && report.skipped.length === 0 && report.blocked.length === 0) {
159
231
  log.info('Nada que hacer: los archivos ya están al día.');
160
232
  }
161
233
  const packages = Object.entries(plan.newPackages);
@@ -39,6 +39,22 @@
39
39
  "value": "checkbox",
40
40
  "label": "checkbox"
41
41
  },
42
+ {
43
+ "value": "confirm",
44
+ "label": "confirm"
45
+ },
46
+ {
47
+ "value": "context-menu",
48
+ "label": "context-menu"
49
+ },
50
+ {
51
+ "value": "dialog",
52
+ "label": "dialog"
53
+ },
54
+ {
55
+ "value": "dropdown-menu",
56
+ "label": "dropdown-menu"
57
+ },
42
58
  {
43
59
  "value": "form-field",
44
60
  "label": "form-field"
@@ -47,6 +63,18 @@
47
63
  "value": "input",
48
64
  "label": "input"
49
65
  },
66
+ {
67
+ "value": "pagination",
68
+ "label": "pagination"
69
+ },
70
+ {
71
+ "value": "popover",
72
+ "label": "popover"
73
+ },
74
+ {
75
+ "value": "select",
76
+ "label": "select"
77
+ },
50
78
  {
51
79
  "value": "separator",
52
80
  "label": "separator"
@@ -59,9 +87,25 @@
59
87
  "value": "switch",
60
88
  "label": "switch"
61
89
  },
90
+ {
91
+ "value": "tabs",
92
+ "label": "tabs"
93
+ },
62
94
  {
63
95
  "value": "textarea",
64
96
  "label": "textarea"
97
+ },
98
+ {
99
+ "value": "toast",
100
+ "label": "toast"
101
+ },
102
+ {
103
+ "value": "toolbar",
104
+ "label": "toolbar"
105
+ },
106
+ {
107
+ "value": "tooltip",
108
+ "label": "tooltip"
65
109
  }
66
110
  ]
67
111
  }
@@ -4,9 +4,11 @@ exports.hasFileChanges = exports.emptyReport = exports.MANIFEST = exports.BASE_D
4
4
  exports.projectPathOf = projectPathOf;
5
5
  exports.basePathOf = basePathOf;
6
6
  exports.writeTemplates = writeTemplates;
7
+ exports.blockedMessages = blockedMessages;
7
8
  exports.compareVersions = compareVersions;
8
9
  exports.updatedMessages = updatedMessages;
9
10
  const schematics_1 = require("@angular-devkit/schematics");
11
+ const registry_1 = require("../registry");
10
12
  /** Carpeta de las copias originales, en la raíz del workspace. Debe quedar en git. */
11
13
  exports.BASE_DIR = '.mimi/base';
12
14
  /** Versión de Mimi con la que se escribió la base de cada archivo. */
@@ -17,6 +19,7 @@ const emptyReport = () => ({
17
19
  updated: [],
18
20
  unchanged: [],
19
21
  skipped: [],
22
+ blocked: [],
20
23
  });
21
24
  exports.emptyReport = emptyReport;
22
25
  /** ¿El reporte tiene cambios en archivos del proyecto? */
@@ -47,26 +50,97 @@ function readManifest(tree) {
47
50
  }
48
51
  }
49
52
  /**
50
- * Copia archivos de las plantillas del paquete (`dist/templates`, rutas relativas a esa
51
- * carpeta) a `componentsDir`, revisando el árbol antes de escribir. Si el archivo ya existe:
53
+ * Copia los archivos de los ítems del registro desde las plantillas del paquete
54
+ * (`dist/templates`) a `componentsDir`. **Todo o nada por ítem:** primero revisa cada archivo
55
+ * (ver `FileState`) y después decide por ítem:
52
56
  *
53
- * - igual a la plantilla → al día;
54
- * - igual a su base en `.mimi/base/` → el usuario no lo tocó. Si la base es de esta versión,
55
- * la diferencia con la plantilla es solo de formato (la CLI de Angular pasa el Prettier del
56
- * proyecto por lo que escriben los schematics): al día. Si es de una versión anterior, se
57
- * actualiza;
58
- * - distinto de su base, o sin base → modificado por el usuario: se omite (o se reemplaza con
59
- * `overwrite`).
57
+ * - si el usuario modificó alguno de sus archivos de una versión anterior, no se escribe
58
+ * ninguno (ni los nuevos ni los que no tocó): mezclar versiones rompe la compilación;
59
+ * - lo mismo si depende (por `registryDependencies`, en cualquier nivel) de un ítem que quedó
60
+ * así, esté instalado o no;
61
+ * - si no, se crean los que faltan y se actualizan los que el usuario no tocó. Los modificados
62
+ * sobre esta versión se conservan (`skipped`).
60
63
  *
61
- * Nunca deja que mergeWith encuentre un archivo existente ("merge conflicted"). Guarda la base
62
- * y su versión en `.mimi/manifest.json` siempre que el archivo quede igual a la plantilla; si se
63
- * omitió por estar modificado, su base no cambia.
64
+ * Con `overwrite`, todo se reemplaza y nada se bloquea. Los ítems `implicit` (el tema y lo que
65
+ * copia init, que `ng g mimi` revisa aunque no se pidan) solo se escriben si hay que
66
+ * actualizarlos: un cambio del usuario sobre la versión actual se respeta en silencio, también
67
+ * con `overwrite`, y no bloquean a otros ítems.
68
+ *
69
+ * Nunca deja que mergeWith encuentre un archivo existente ("merge conflicted"). Guarda la base y
70
+ * su versión en `.mimi/manifest.json` siempre que el archivo quede igual a la plantilla; los de un
71
+ * ítem bloqueado o conservado no cambian su base.
64
72
  */
65
73
  function writeTemplates(options) {
66
- const files = new Set(options.files);
67
- return (tree) => {
74
+ const items = [...new Set(options.items)];
75
+ const implicit = new Set(options.implicit ?? []);
76
+ const files = new Set(items.flatMap((item) => registry_1.registry.items[item].files));
77
+ const contents = new Map();
78
+ // 1. Lee las plantillas (sin escribir nada: forEach devuelve null).
79
+ const read = (0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)('../templates'), [
80
+ (0, schematics_1.filter)((path) => files.has(path.replace(/^\//, ''))),
81
+ (0, schematics_1.forEach)((entry) => {
82
+ contents.set(entry.path.replace(/^\//, ''), entry.content.toString('utf8'));
83
+ return null;
84
+ }),
85
+ ]));
86
+ // 2. Decide por ítem y escribe.
87
+ const write = (tree) => {
68
88
  const manifest = readManifest(tree);
69
89
  let manifestChanged = false;
90
+ const targetOf = (file) => `${options.componentsDir}/${projectPathOf(file)}`;
91
+ const stateOf = (file) => {
92
+ const target = targetOf(file);
93
+ if (!tree.exists(target))
94
+ return { state: 'new', from: null };
95
+ const current = tree.readText(target);
96
+ if (current === contents.get(file))
97
+ return { state: 'same', from: null };
98
+ const from = manifest.files[projectPathOf(file)] ?? null;
99
+ const base = basePathOf(file);
100
+ if (tree.exists(base)) {
101
+ if (tree.readText(base) === current) {
102
+ return { state: from === options.version ? 'formatted' : 'old', from };
103
+ }
104
+ if (from !== null && from !== options.version)
105
+ return { state: 'modifiedOld', from };
106
+ }
107
+ return { state: 'modified', from };
108
+ };
109
+ const states = new Map(items.map((item) => [
110
+ item,
111
+ registry_1.registry.items[item].files
112
+ .filter((file) => contents.has(file))
113
+ .map((file) => ({ file, ...stateOf(file) })),
114
+ ]));
115
+ // Bloqueados por un cambio propio y, después, por una dependencia bloqueada.
116
+ const blocked = new Map();
117
+ if (!options.overwrite) {
118
+ for (const item of items) {
119
+ const modified = states.get(item).filter((s) => s.state === 'modifiedOld');
120
+ if (modified.length > 0) {
121
+ blocked.set(item, {
122
+ item,
123
+ files: modified.map((s) => s.file),
124
+ installed: true,
125
+ });
126
+ }
127
+ }
128
+ for (const item of items) {
129
+ if (blocked.has(item))
130
+ continue;
131
+ const dependency = (0, registry_1.resolveItems)(item)
132
+ .filter((dep) => dep !== item)
133
+ .find((dep) => blocked.has(dep) && blocked.get(dep).files.length > 0);
134
+ if (dependency) {
135
+ blocked.set(item, {
136
+ item,
137
+ files: [],
138
+ dependency,
139
+ installed: states.get(item).some((s) => s.state !== 'new'),
140
+ });
141
+ }
142
+ }
143
+ }
70
144
  const writeBase = (file, content) => {
71
145
  const base = basePathOf(file);
72
146
  if (!tree.exists(base))
@@ -79,60 +153,83 @@ function writeTemplates(options) {
79
153
  manifestChanged = true;
80
154
  }
81
155
  };
82
- const copy = (0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)('../templates'), [
83
- (0, schematics_1.filter)((path) => files.has(path.replace(/^\//, ''))),
84
- (0, schematics_1.forEach)((entry) => {
85
- const file = entry.path.replace(/^\//, '');
86
- const target = `${options.componentsDir}/${projectPathOf(file)}`;
87
- const base = basePathOf(file);
88
- const content = entry.content.toString('utf8');
89
- if (!tree.exists(target)) {
90
- tree.create(target, content);
91
- options.report.created.push(file);
92
- writeBase(file, content);
93
- return null;
94
- }
95
- const current = tree.readText(target);
96
- if (current === content) {
97
- options.report.unchanged.push(file);
98
- writeBase(file, content);
99
- }
100
- else if (tree.exists(base) && tree.readText(base) === current) {
101
- const from = manifest.files[projectPathOf(file)] ?? null;
102
- if (from === options.version) {
103
- // Mismo contenido que escribió Mimi, solo con otro formato.
156
+ for (const item of items) {
157
+ if (blocked.has(item)) {
158
+ options.report.blocked.push(blocked.get(item));
159
+ continue;
160
+ }
161
+ for (const { file, state, from } of states.get(item)) {
162
+ const content = contents.get(file);
163
+ const target = targetOf(file);
164
+ switch (state) {
165
+ case 'new':
166
+ tree.create(target, content);
167
+ options.report.created.push(file);
168
+ writeBase(file, content);
169
+ break;
170
+ case 'same':
171
+ options.report.unchanged.push(file);
172
+ writeBase(file, content);
173
+ break;
174
+ case 'formatted':
104
175
  options.report.unchanged.push(file);
105
- }
106
- else {
176
+ break;
177
+ case 'old':
107
178
  tree.overwrite(target, content);
108
179
  options.report.updated.push({ file, from });
109
180
  writeBase(file, content);
110
- }
111
- }
112
- else if (options.overwrite) {
113
- tree.overwrite(target, content);
114
- options.report.overwritten.push(file);
115
- writeBase(file, content);
116
- }
117
- else {
118
- options.report.skipped.push(file);
181
+ break;
182
+ case 'modifiedOld':
183
+ case 'modified':
184
+ // Un cambio sobre la versión actual de un ítem implícito se respeta siempre.
185
+ if (options.overwrite && !(implicit.has(item) && state === 'modified')) {
186
+ tree.overwrite(target, content);
187
+ options.report.overwritten.push(file);
188
+ writeBase(file, content);
189
+ }
190
+ else if (!implicit.has(item)) {
191
+ options.report.skipped.push(file);
192
+ }
193
+ break;
119
194
  }
120
- // Todo se escribe arriba, en el árbol del proyecto: nada llega a mergeWith.
121
- return null;
122
- }),
123
- ]));
124
- const saveManifest = (t) => {
125
- if (!manifestChanged)
126
- return;
195
+ }
196
+ }
197
+ if (manifestChanged) {
127
198
  const sorted = Object.fromEntries(Object.entries(manifest.files).sort());
128
199
  const text = `${JSON.stringify({ files: sorted }, null, 2)}\n`;
129
- if (t.exists(exports.MANIFEST))
130
- t.overwrite(exports.MANIFEST, text);
200
+ if (tree.exists(exports.MANIFEST))
201
+ tree.overwrite(exports.MANIFEST, text);
131
202
  else
132
- t.create(exports.MANIFEST, text);
133
- };
134
- return (0, schematics_1.chain)([copy, saveManifest]);
203
+ tree.create(exports.MANIFEST, text);
204
+ }
135
205
  };
206
+ return (0, schematics_1.chain)([read, write]);
207
+ }
208
+ /**
209
+ * Los avisos de los ítems que no se escribieron (`report.blocked`): qué no se actualizó, por qué
210
+ * y qué hacer. Nunca dicen «actualizado».
211
+ */
212
+ function blockedMessages(report, componentsDir, version) {
213
+ const path = (file) => `${componentsDir}/${projectPathOf(file)}`;
214
+ return report.blocked.map(({ item, files, dependency, installed }) => {
215
+ if (dependency) {
216
+ const needs = `necesita ${dependency} ${version}, que no se actualizó (ver el aviso de ${dependency}).`;
217
+ return installed
218
+ ? `${item} no se actualizó a ${version}: ${needs}`
219
+ : `${item} no se agregó: ${needs}`;
220
+ }
221
+ const isTheme = registry_1.registry.items[item]?.type === 'theme';
222
+ const label = isTheme ? 'El tema' : item;
223
+ const dir = projectPathOf(files[0]).split('/').slice(0, -1).join('/');
224
+ return (`${label} no se actualizó a ${version} porque modificaste ${files.map(path).join(', ')}. ` +
225
+ 'No se cambió ninguno de sus archivos, para no mezclar versiones.' +
226
+ (isTheme
227
+ ? ` Los componentes de la ${version} usan tokens nuevos del tema: sin ellos, algunos estilos no se generan.`
228
+ : '') +
229
+ ' Para actualizarlo: guarda tus cambios (por ejemplo, en git), vuelve a ejecutar el ' +
230
+ 'comando con --overwrite y aplícalos de nuevo. La versión de Mimi que modificaste está en ' +
231
+ `${exports.BASE_DIR}/${dir}/, para comparar.`);
232
+ });
136
233
  }
137
234
  /** Compara versiones `x.y.z` numéricamente (`0.10.0` > `0.9.0`). `null` es la más vieja. */
138
235
  function compareVersions(a, b) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mimi-ng/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Angular + Tailwind CSS 4 UI components that a CLI copies into your project, so you own the code. Schematics for ng add and ng generate.",
5
5
  "keywords": [
6
6
  "angular",