@mimi-ng/cli 0.1.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/THIRD_PARTY_NOTICES.md +84 -0
  4. package/dist/collection.json +22 -0
  5. package/dist/init/css.js +32 -0
  6. package/dist/init/index.js +297 -0
  7. package/dist/init/paths.js +43 -0
  8. package/dist/init/schema.json +24 -0
  9. package/dist/ng-add/index.js +8 -0
  10. package/dist/registry.js +30 -0
  11. package/dist/registry.json +194 -0
  12. package/dist/templates/components/avatar/avatar.ts +129 -0
  13. package/dist/templates/components/avatar/avatar.variants.ts +25 -0
  14. package/dist/templates/components/avatar/index.ts +2 -0
  15. package/dist/templates/components/badge/badge.ts +21 -0
  16. package/dist/templates/components/badge/badge.variants.ts +27 -0
  17. package/dist/templates/components/badge/index.ts +2 -0
  18. package/dist/templates/components/button/button.ts +99 -0
  19. package/dist/templates/components/button/button.variants.ts +60 -0
  20. package/dist/templates/components/button/index.ts +2 -0
  21. package/dist/templates/components/card/card.ts +133 -0
  22. package/dist/templates/components/card/index.ts +1 -0
  23. package/dist/templates/components/checkbox/checkbox.ts +178 -0
  24. package/dist/templates/components/checkbox/index.ts +1 -0
  25. package/dist/templates/components/form-field/error-messages.ts +68 -0
  26. package/dist/templates/components/form-field/form-error.ts +82 -0
  27. package/dist/templates/components/form-field/form-field.ts +114 -0
  28. package/dist/templates/components/form-field/index.ts +3 -0
  29. package/dist/templates/components/input/index.ts +2 -0
  30. package/dist/templates/components/input/input.ts +52 -0
  31. package/dist/templates/components/input/input.variants.ts +38 -0
  32. package/dist/templates/components/separator/index.ts +2 -0
  33. package/dist/templates/components/separator/separator.ts +38 -0
  34. package/dist/templates/components/separator/separator.variants.ts +20 -0
  35. package/dist/templates/components/skeleton/index.ts +1 -0
  36. package/dist/templates/components/skeleton/skeleton.ts +27 -0
  37. package/dist/templates/components/switch/index.ts +1 -0
  38. package/dist/templates/components/switch/switch.ts +138 -0
  39. package/dist/templates/components/textarea/index.ts +2 -0
  40. package/dist/templates/components/textarea/textarea.ts +46 -0
  41. package/dist/templates/components/textarea/textarea.variants.ts +27 -0
  42. package/dist/templates/theme/index.ts +2 -0
  43. package/dist/templates/theme/provider.ts +150 -0
  44. package/dist/templates/theme/theme-base.css +241 -0
  45. package/dist/templates/theme/types.ts +124 -0
  46. package/dist/templates/utils/cn.ts +36 -0
  47. package/dist/templates/utils/control-styles.ts +27 -0
  48. package/dist/templates/utils/field-state.ts +164 -0
  49. package/dist/ui/index.js +172 -0
  50. package/dist/ui/schema.json +75 -0
  51. package/dist/utils/project.js +28 -0
  52. package/dist/utils/write-files.js +167 -0
  53. package/package.json +55 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nuki23
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # Mimi para Angular
2
+
3
+ > Angular + Tailwind CSS components you copy into your project. Docs in Spanish; English coming soon.
4
+
5
+ Componentes para **Angular** y **Tailwind CSS 4** que una CLI copia a tu proyecto: desde ese momento, el código es tuyo. Un solo prefijo (`mimi`), tema opcional y tipado, y formularios con errores automáticos.
6
+
7
+ Mimi nació como "lo mío". Su promesa es que sea tuyo.
8
+
9
+ **Vista previa 0.x:** la API puede cambiar entre versiones menores hasta la 1.0.
10
+
11
+ Documentación: **https://ng.mimiworks.dev**
12
+
13
+ ## Requisitos
14
+
15
+ - Angular 22 o superior.
16
+ - Tailwind CSS 4. Para un proyecto nuevo: `ng new mi-app --style=tailwind`. Si tu proyecto ya existe, sigue la [guía de Tailwind CSS para Angular](https://tailwindcss.com/docs/installation/framework-guides/angular).
17
+ - Node.js `^22.22.3`, `^24.15.0` o `>=26` (los que exige Angular 22).
18
+
19
+ ## Instalación
20
+
21
+ ```sh
22
+ ng add @mimi-ng/cli
23
+ ```
24
+
25
+ Verifica Angular y Tailwind (si falta algo, se detiene sin modificar nada) y configura el proyecto:
26
+
27
+ - importa el tema de Mimi en tu CSS global, justo después de `@import "tailwindcss"`;
28
+ - agrega el alias `@/components/ui/*` al `tsconfig.json`;
29
+ - registra la CLI en `angular.json`, para que funcione `ng g mimi`;
30
+ - instala `clsx`, `tailwind-merge` y `class-variance-authority` con tu gestor de paquetes;
31
+ - crea `mimi.json` y la carpeta `.mimi/`.
32
+
33
+ Con `--icons` instala también [`@lucide/angular`](https://lucide.dev) para tus íconos.
34
+
35
+ ## Agregar componentes
36
+
37
+ ```sh
38
+ ng g mimi button # uno
39
+ ng g mimi button input card # varios a la vez
40
+ ng g mimi # menú para elegir (terminal interactiva)
41
+ ng g @mimi-ng/cli:ui button # forma larga (si Mimi no está en schematicCollections)
42
+ ```
43
+
44
+ Cada componente se copia a `src/app/components/ui/<nombre>`, con lo que necesite, y se instalan las dependencias que falten. Después lo importas como cualquier otro:
45
+
46
+ ```ts
47
+ import { MimiButton } from '@/components/ui/button';
48
+
49
+ @Component({
50
+ selector: 'app-root',
51
+ imports: [MimiButton],
52
+ template: `<button mimiBtn>Hola, Mimi</button>`,
53
+ })
54
+ export class App {}
55
+ ```
56
+
57
+ ## Componentes
58
+
59
+ - **Avatar**: `ng g mimi avatar` · [documentación](https://ng.mimiworks.dev/docs/components/avatar)
60
+ - **Badge**: `ng g mimi badge` · [documentación](https://ng.mimiworks.dev/docs/components/badge)
61
+ - **Button**: `ng g mimi button` · [documentación](https://ng.mimiworks.dev/docs/components/button)
62
+ - **Card**: `ng g mimi card` · [documentación](https://ng.mimiworks.dev/docs/components/card)
63
+ - **Checkbox**: `ng g mimi checkbox` · [documentación](https://ng.mimiworks.dev/docs/components/switch)
64
+ - **FormField**: `ng g mimi form-field` · [documentación](https://ng.mimiworks.dev/docs/components/form-field)
65
+ - **Input**: `ng g mimi input` · [documentación](https://ng.mimiworks.dev/docs/components/input)
66
+ - **Separator**: `ng g mimi separator` · [documentación](https://ng.mimiworks.dev/docs/components/separator)
67
+ - **Skeleton**: `ng g mimi skeleton` · [documentación](https://ng.mimiworks.dev/docs/components/skeleton)
68
+ - **Switch**: `ng g mimi switch` · [documentación](https://ng.mimiworks.dev/docs/components/switch)
69
+ - **Textarea**: `ng g mimi textarea` · [documentación](https://ng.mimiworks.dev/docs/components/input)
70
+
71
+ ## Tus cambios
72
+
73
+ - Guarda la carpeta `.mimi/` en git: tiene la copia original de cada archivo, y con ella Mimi sabe qué cambiaste tú.
74
+ - Los componentes que modificaste nunca se sobrescriben: `ng g mimi` los omite con un aviso (`--overwrite` los reemplaza).
75
+ - Los que no tocaste se actualizan al volver a ejecutar `ng g mimi` con una versión nueva de la CLI.
76
+ - Próximamente: `mimi update`, para combinar tus cambios con la versión nueva.
77
+
78
+ ## Si usas pnpm
79
+
80
+ Desde pnpm 10, los scripts de instalación de las dependencias están bloqueados hasta que los apruebes, y la instalación puede fallar con `ERR_PNPM_IGNORED_BUILDS`. Apruébalos con `pnpm approve-builds`. Más detalles en la [página de Instalación](https://ng.mimiworks.dev/docs/installation).
81
+
82
+ ## Licencia
83
+
84
+ MIT. Incluye íconos de Lucide (ISC); ver `THIRD_PARTY_NOTICES.md`.
85
+
86
+ Mimi para Angular es un proyecto de [Mimi Works](https://mimiworks.dev).
@@ -0,0 +1,84 @@
1
+ # Avisos de terceros
2
+
3
+ Mimi incluye material de los siguientes proyectos. Cada uno conserva su licencia.
4
+
5
+ ## Lucide
6
+
7
+ Los íconos internos de los componentes (check, minus, circle-alert…) y los del layout del
8
+ showcase (menú, cerrar, sol, luna, chevron) son SVG escritos en línea con los trazos de
9
+ [Lucide](https://lucide.dev). El showcase usa además el paquete `@lucide/angular`.
10
+
11
+ Texto de `@lucide/angular` (`LICENSE`), incluido el aviso de Feather para los íconos que derivan de él:
12
+
13
+ ```
14
+ ISC License
15
+
16
+ Copyright (c) 2026 Lucide Icons and Contributors
17
+
18
+ Permission to use, copy, modify, and/or distribute this software for any
19
+ purpose with or without fee is hereby granted, provided that the above
20
+ copyright notice and this permission notice appear in all copies.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
23
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
24
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
25
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
26
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
27
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
28
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
29
+
30
+ ---
31
+
32
+ The following Lucide icons are derived from the Feather project:
33
+
34
+ airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
35
+
36
+ The MIT License (MIT) (for the icons listed above)
37
+
38
+ Copyright (c) 2013-present Cole Bemis
39
+
40
+ Permission is hereby granted, free of charge, to any person obtaining a copy
41
+ of this software and associated documentation files (the "Software"), to deal
42
+ in the Software without restriction, including without limitation the rights
43
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
44
+ copies of the Software, and to permit persons to whom the Software is
45
+ furnished to do so, subject to the following conditions:
46
+
47
+ The above copyright notice and this permission notice shall be included in all
48
+ copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
51
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
52
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
53
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
54
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
55
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
56
+ SOFTWARE.
57
+ ```
58
+
59
+ ## Octicons (GitHub)
60
+
61
+ El ícono de GitHub del header del showcase es el Octicon `mark-github`, escrito en línea.
62
+ [Octicons](https://github.com/primer/octicons) se distribuye con licencia MIT. El logo de
63
+ GitHub es una marca de GitHub, Inc.
64
+
65
+ ```
66
+ MIT License
67
+
68
+ Copyright (c) 2026 GitHub Inc.
69
+
70
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
71
+ and associated documentation files (the "Software"), to deal in the Software without
72
+ restriction, including without limitation the rights to use, copy, modify, merge, publish,
73
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
74
+ Software is furnished to do so, subject to the following conditions:
75
+
76
+ The above copyright notice and this permission notice shall be included in all copies or
77
+ substantial portions of the Software.
78
+
79
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
80
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
81
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
82
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
83
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
84
+ ```
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
3
+ "schematics": {
4
+ "ng-add": {
5
+ "factory": "./ng-add/index#ngAdd",
6
+ "schema": "./init/schema.json",
7
+ "description": "Instala Mimi en el proyecto: ejecuta init.",
8
+ "hidden": true
9
+ },
10
+ "init": {
11
+ "factory": "./init/index#init",
12
+ "schema": "./init/schema.json",
13
+ "description": "Configura Mimi: tema, utilidades, alias, dependencias y mimi.json."
14
+ },
15
+ "mimi": {
16
+ "aliases": ["ui"],
17
+ "factory": "./ui/index#ui",
18
+ "schema": "./ui/schema.json",
19
+ "description": "Agrega componentes de Mimi (varios a la vez) con sus dependencias."
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findTailwindImport = findTailwindImport;
4
+ exports.insertAfterTailwind = insertAfterTailwind;
5
+ exports.hasDirective = hasDirective;
6
+ /** `@import "tailwindcss";` o `@import 'tailwindcss' source(none);`, en su propia línea. */
7
+ const TAILWIND_IMPORT = /^[ \t]*@import\s+(["'])tailwindcss\1([^;\n]*);[^\n]*$/m;
8
+ function findTailwindImport(css) {
9
+ const match = TAILWIND_IMPORT.exec(css);
10
+ if (!match)
11
+ return null;
12
+ return {
13
+ end: match.index + match[0].length,
14
+ sourceNone: /\bsource\(\s*none\s*\)/.test(match[2]),
15
+ };
16
+ }
17
+ /** Salto de línea del archivo (respeta CRLF). */
18
+ const eolOf = (css) => (/\r\n/.test(css) ? '\r\n' : '\n');
19
+ /** Inserta líneas justo después del import de Tailwind (que debe existir). */
20
+ function insertAfterTailwind(css, lines) {
21
+ const found = findTailwindImport(css);
22
+ if (!found || lines.length === 0)
23
+ return css;
24
+ const eol = eolOf(css);
25
+ const insert = lines.map((line) => eol + line).join('');
26
+ return css.slice(0, found.end) + insert + css.slice(found.end);
27
+ }
28
+ /** ¿Ya hay un `@import` o `@source` con esa ruta (con comillas simples o dobles)? */
29
+ function hasDirective(css, directive, path) {
30
+ const escaped = path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
31
+ return new RegExp(`${directive}\\s+(["'])${escaped}\\1`).test(css);
32
+ }
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ALIAS = void 0;
4
+ exports.init = init;
5
+ const schematics_1 = require("@angular-devkit/schematics");
6
+ const utility_1 = require("@schematics/angular/utility");
7
+ const json_file_1 = require("@schematics/angular/utility/json-file");
8
+ const registry_1 = require("../registry");
9
+ const project_1 = require("../utils/project");
10
+ const write_files_1 = require("../utils/write-files");
11
+ const css_1 = require("./css");
12
+ const paths_1 = require("./paths");
13
+ /** Alias que usan todos los componentes. Lo configurable es la carpeta a la que apunta. */
14
+ exports.ALIAS = '@/components/ui/*';
15
+ /** Ítems del registro que copia init: el tema y las utilidades base. */
16
+ const INIT_ITEMS = ['theme', 'utils/cn', 'utils/control-styles'];
17
+ /** Paquetes que instala init (versiones del registro). */
18
+ const INIT_PACKAGES = ['clsx', 'tailwind-merge', 'class-variance-authority'];
19
+ const ICONS_PACKAGE = '@lucide/angular';
20
+ /** Colección por defecto de la CLI de Angular (ng g component, service…). */
21
+ const DEFAULT_COLLECTION = '@schematics/angular';
22
+ const MIN_ANGULAR = 22;
23
+ const MIN_TAILWIND = 4;
24
+ /**
25
+ * `ng g @mimi-ng/cli:init` (también lo ejecuta `ng add @mimi-ng/cli`).
26
+ *
27
+ * Primero verifica todo (Angular 22+, Tailwind 4 y su `@import`) sin tocar nada; si algo falla,
28
+ * lanza un error claro y el proyecto queda como estaba. Después copia el tema y las utilidades,
29
+ * importa el tema en el CSS global, agrega el alias al tsconfig, las dependencias y `mimi.json`.
30
+ * Es idempotente: ejecutarlo dos veces no duplica nada.
31
+ */
32
+ function init(options) {
33
+ return async (tree) => {
34
+ const plan = await inspect(tree, options);
35
+ const report = (0, write_files_1.emptyReport)();
36
+ return (0, schematics_1.chain)([
37
+ (0, write_files_1.writeTemplates)({
38
+ files: INIT_ITEMS.flatMap((item) => (0, registry_1.resolveItems)(item)).flatMap((item) => registry_1.registry.items[item].files),
39
+ componentsDir: plan.componentsDir,
40
+ overwrite: options.overwrite ?? false,
41
+ version: registry_1.registry.version,
42
+ report,
43
+ }),
44
+ updateGlobalCss(plan),
45
+ updateTsConfigs(plan),
46
+ registerCollection(plan),
47
+ writeMimiConfig(plan),
48
+ ...dependencies(plan, options.icons ?? false),
49
+ nextSteps(plan, report, options.icons ?? false),
50
+ ]);
51
+ };
52
+ }
53
+ // ── Verificaciones (sin escribir) ─────────────────────────────────────────────────────
54
+ async function inspect(tree, options) {
55
+ const packageJson = (0, project_1.readJson)(tree, 'package.json');
56
+ if (!packageJson) {
57
+ throw new schematics_1.SchematicsException('No se encontró package.json. Ejecuta init en la raíz del workspace.');
58
+ }
59
+ const deps = (0, project_1.allDependencies)(packageJson);
60
+ const angular = (0, project_1.majorOf)(deps['@angular/core']);
61
+ if (angular === null) {
62
+ throw new schematics_1.SchematicsException('No se pudo leer la versión de @angular/core en package.json. Mimi necesita Angular 22 o superior.');
63
+ }
64
+ if (angular < MIN_ANGULAR) {
65
+ throw new schematics_1.SchematicsException(`Mimi necesita Angular ${MIN_ANGULAR} o superior; este proyecto usa Angular ${angular}. ` +
66
+ 'Actualízalo con "ng update @angular/core @angular/cli".');
67
+ }
68
+ const tailwind = (0, project_1.majorOf)(deps['tailwindcss']);
69
+ if (tailwind === null || tailwind < MIN_TAILWIND) {
70
+ throw new schematics_1.SchematicsException(`Mimi necesita Tailwind CSS ${MIN_TAILWIND}, y ${tailwind === null ? 'no está instalado' : `este proyecto usa Tailwind ${tailwind}`}. Agrégalo con "ng add tailwindcss" o sigue la guía de Tailwind para Angular ` +
71
+ '(https://tailwindcss.com/docs/installation/framework-guides/angular). Después vuelve a ejecutar init.');
72
+ }
73
+ const workspace = await (0, utility_1.readWorkspace)(tree);
74
+ const { name, project } = selectProject(workspace, options.project);
75
+ const build = project.targets.get('build');
76
+ const styles = (build?.options?.['styles'] ?? [])
77
+ .map((entry) => (typeof entry === 'string' ? entry : entry?.input))
78
+ .filter((path) => typeof path === 'string')
79
+ .map(paths_1.normalizePath);
80
+ const cssFiles = styles.filter((path) => path.endsWith('.css') && tree.exists(path));
81
+ const withTailwind = cssFiles.find((path) => (0, css_1.findTailwindImport)(tree.readText(path)));
82
+ if (!withTailwind) {
83
+ throw new schematics_1.SchematicsException(cssFiles.length === 0
84
+ ? `El proyecto "${name}" no tiene un CSS global en angular.json (build → options → styles). ` +
85
+ 'Agrega uno (por ejemplo src/styles.css) con @import "tailwindcss"; y vuelve a ejecutar init.'
86
+ : `Falta @import "tailwindcss"; en el CSS global (${cssFiles.join(', ')}). ` +
87
+ 'Agrégalo al principio del archivo y vuelve a ejecutar init.');
88
+ }
89
+ const sourceRoot = (0, paths_1.normalizePath)(project.sourceRoot ?? `${project.root}/src`);
90
+ const mimiConfig = (0, project_1.readJson)(tree, 'mimi.json');
91
+ const componentsDir = (0, paths_1.normalizePath)(mimiConfig?.aliases?.components ?? `${sourceRoot}/app/components/ui`);
92
+ const tsConfig = build?.options?.['tsConfig'];
93
+ return {
94
+ projectName: name,
95
+ cssPath: withTailwind,
96
+ sourceNone: (0, css_1.findTailwindImport)(tree.readText(withTailwind)).sourceNone,
97
+ componentsDir,
98
+ buildTsConfig: typeof tsConfig === 'string' ? (0, paths_1.normalizePath)(tsConfig) : undefined,
99
+ mimiConfig,
100
+ sourceRoot,
101
+ installed: deps,
102
+ changed: false,
103
+ };
104
+ }
105
+ function selectProject(workspace, requested) {
106
+ if (requested) {
107
+ const project = workspace.projects.get(requested);
108
+ if (!project) {
109
+ throw new schematics_1.SchematicsException(`El proyecto "${requested}" no existe en angular.json.`);
110
+ }
111
+ return { name: requested, project };
112
+ }
113
+ const apps = [...workspace.projects].filter(([, project]) => project.extensions['projectType'] === 'application');
114
+ if (apps.length === 1)
115
+ return { name: apps[0][0], project: apps[0][1] };
116
+ if (apps.length === 0) {
117
+ throw new schematics_1.SchematicsException('No hay ninguna aplicación en angular.json.');
118
+ }
119
+ throw new schematics_1.SchematicsException(`Hay varias aplicaciones (${apps.map(([n]) => n).join(', ')}). ` +
120
+ 'Elige una con --project, por ejemplo: ng add @mimi-ng/cli --project=' +
121
+ apps[0][0]);
122
+ }
123
+ // ── CSS global ────────────────────────────────────────────────────────────────────────
124
+ function updateGlobalCss(plan) {
125
+ return (tree, context) => {
126
+ const cssDir = (0, paths_1.dirnameOf)(plan.cssPath);
127
+ const themePath = `${(0, paths_1.relativePath)(cssDir, plan.componentsDir)}/theme/theme-base.css`;
128
+ const sourcePath = (0, paths_1.relativePath)(cssDir, plan.componentsDir);
129
+ let css = tree.readText(plan.cssPath);
130
+ const lines = [];
131
+ if (!(0, css_1.hasDirective)(css, '@import', themePath))
132
+ lines.push(`@import "${themePath}";`);
133
+ if (plan.sourceNone && !(0, css_1.hasDirective)(css, '@source', sourcePath)) {
134
+ lines.push(`@source "${sourcePath}";`);
135
+ context.logger.warn(`El @import de Tailwind en ${plan.cssPath} usa source(none): Tailwind no escanea nada por su ` +
136
+ `cuenta. Se agregó @source "${sourcePath}"; para que genere las clases de los componentes.`);
137
+ }
138
+ if (lines.length === 0)
139
+ return;
140
+ css = (0, css_1.insertAfterTailwind)(css, lines);
141
+ tree.overwrite(plan.cssPath, css);
142
+ plan.changed = true;
143
+ };
144
+ }
145
+ // ── tsconfig ──────────────────────────────────────────────────────────────────────────
146
+ /**
147
+ * El alias va en tsconfig.json (editor, pruebas). Si el tsconfig del build define su propio
148
+ * compilerOptions.paths, ese reemplaza al de la raíz, así que también va ahí. JSONFile conserva
149
+ * los comentarios (el tsconfig de Angular es JSONC).
150
+ */
151
+ function updateTsConfigs(plan) {
152
+ return (tree, context) => {
153
+ const targets = ['tsconfig.json'];
154
+ if (plan.buildTsConfig &&
155
+ plan.buildTsConfig !== 'tsconfig.json' &&
156
+ tree.exists(plan.buildTsConfig)) {
157
+ const own = new json_file_1.JSONFile(tree, plan.buildTsConfig).get(['compilerOptions', 'paths']);
158
+ if (own !== undefined)
159
+ targets.push(plan.buildTsConfig);
160
+ }
161
+ for (const path of targets) {
162
+ if (!tree.exists(path)) {
163
+ context.logger.warn(`No se encontró ${path}: agrega el alias ${exports.ALIAS} a mano.`);
164
+ continue;
165
+ }
166
+ const file = new json_file_1.JSONFile(tree, path);
167
+ const value = `${(0, paths_1.relativePath)((0, paths_1.dirnameOf)(path), plan.componentsDir)}/*`;
168
+ const current = file.get(['compilerOptions', 'paths', exports.ALIAS]);
169
+ if (current === undefined) {
170
+ file.modify(['compilerOptions', 'paths', exports.ALIAS], [value]);
171
+ plan.changed = true;
172
+ }
173
+ else if (!sameAlias(current, value)) {
174
+ context.logger.warn(`${path} ya tiene el alias ${exports.ALIAS} con otro valor (${JSON.stringify(current)}); no se ` +
175
+ `cambió. Mimi espera ["${value}"].`);
176
+ }
177
+ }
178
+ };
179
+ }
180
+ function sameAlias(current, value) {
181
+ return (Array.isArray(current) &&
182
+ current.length === 1 &&
183
+ typeof current[0] === 'string' &&
184
+ (0, paths_1.normalizePath)(current[0]) === (0, paths_1.normalizePath)(value));
185
+ }
186
+ // ── schematicCollections (tarea 3.6) ─────────────────────────────────────────────────
187
+ /**
188
+ * Registra la colección para que funcione `ng g mimi button`. `cli.schematicCollections`
189
+ * reemplaza al valor por defecto de la CLI de Angular, así que si no existe se crea con
190
+ * `@schematics/angular` primero (si no, el usuario perdería `ng g component`). Si ya existe,
191
+ * Mimi se agrega al final sin quitar nada. El de un proyecto gana al del workspace, así que si
192
+ * el proyecto tiene el suyo, también va ahí.
193
+ */
194
+ function registerCollection(plan) {
195
+ return (tree) => {
196
+ if (!tree.exists('angular.json'))
197
+ return;
198
+ const file = new json_file_1.JSONFile(tree, 'angular.json');
199
+ const add = (path, createIfMissing) => {
200
+ const current = file.get(path);
201
+ if (current === undefined) {
202
+ if (!createIfMissing)
203
+ return;
204
+ file.modify(path, [DEFAULT_COLLECTION, project_1.MIMI_COLLECTION]);
205
+ plan.changed = true;
206
+ }
207
+ else if (Array.isArray(current) && !current.includes(project_1.MIMI_COLLECTION)) {
208
+ file.modify(path, [...current, project_1.MIMI_COLLECTION]);
209
+ plan.changed = true;
210
+ }
211
+ };
212
+ add(['cli', 'schematicCollections'], true);
213
+ add(['projects', plan.projectName, 'cli', 'schematicCollections'], false);
214
+ };
215
+ }
216
+ // ── mimi.json ─────────────────────────────────────────────────────────────────────────
217
+ function writeMimiConfig(plan) {
218
+ return (tree) => {
219
+ if (plan.mimiConfig)
220
+ return;
221
+ const config = {
222
+ style: 'vivid',
223
+ tailwind: { css: plan.cssPath },
224
+ aliases: {
225
+ components: plan.componentsDir,
226
+ utils: `${plan.componentsDir}/utils`,
227
+ theme: `${plan.sourceRoot}/app/mimi.preset.ts`,
228
+ },
229
+ components: {},
230
+ };
231
+ tree.create('mimi.json', `${JSON.stringify(config, null, 2)}\n`);
232
+ plan.changed = true;
233
+ };
234
+ }
235
+ // ── Dependencias ──────────────────────────────────────────────────────────────────────
236
+ /** Versión de un paquete según el registro (la de ui-core). */
237
+ function registryVersion(name) {
238
+ for (const item of Object.values(registry_1.registry.items)) {
239
+ const version = item.dependencies?.[name];
240
+ if (version)
241
+ return version;
242
+ }
243
+ const suggested = registry_1.registry.suggestedDependencies?.[name];
244
+ if (suggested)
245
+ return suggested;
246
+ throw new schematics_1.SchematicsException(`El registro de Mimi no tiene versión para ${name}.`);
247
+ }
248
+ /**
249
+ * Agrega las dependencias (si ya están, se dejan como estén) y agenda la instalación.
250
+ * `ng add` / `ng g` la ejecutan con el gestor de paquetes que detecta la CLI de Angular
251
+ * por el lockfile del proyecto.
252
+ */
253
+ function dependencies(plan, icons) {
254
+ const names = (icons ? [...INIT_PACKAGES, ICONS_PACKAGE] : INIT_PACKAGES).filter((name) => !plan.installed[name]);
255
+ if (names.length > 0)
256
+ plan.changed = true;
257
+ return names.map((name) => (0, utility_1.addDependency)(name, registryVersion(name), {
258
+ type: utility_1.DependencyType.Default,
259
+ existing: utility_1.ExistingBehavior.Skip,
260
+ install: utility_1.InstallBehavior.Auto,
261
+ }));
262
+ }
263
+ // ── Mensaje final ─────────────────────────────────────────────────────────────────────
264
+ function nextSteps(plan, report, icons) {
265
+ return (_tree, context) => {
266
+ for (const file of report.skipped) {
267
+ context.logger.warn(`Omitido: ${plan.componentsDir}/${file} ya existe con otro contenido ` +
268
+ '(usa --overwrite para reemplazarlo).');
269
+ }
270
+ for (const file of report.overwritten) {
271
+ context.logger.info(`Reemplazado: ${plan.componentsDir}/${file}`);
272
+ }
273
+ const itemOf = (file) => INIT_ITEMS.find((item) => registry_1.registry.items[item].files.includes(file)) ?? file;
274
+ for (const message of (0, write_files_1.updatedMessages)(report, itemOf, registry_1.registry.version)) {
275
+ context.logger.info(message);
276
+ }
277
+ if (!plan.changed && !(0, write_files_1.hasFileChanges)(report)) {
278
+ context.logger.info('Mimi ya estaba configurado; no hubo cambios.');
279
+ return;
280
+ }
281
+ context.logger.info('');
282
+ context.logger.info(`Mimi quedó configurado en "${plan.projectName}":`);
283
+ context.logger.info(` · Tema y utilidades en ${plan.componentsDir}`);
284
+ context.logger.info(` · Tema importado en ${plan.cssPath}`);
285
+ context.logger.info(` · Alias ${exports.ALIAS} en el tsconfig`);
286
+ context.logger.info(` · Configuración en mimi.json y copias originales en ${write_files_1.BASE_DIR}/`);
287
+ context.logger.info(` · ${project_1.MIMI_COLLECTION} en schematicCollections de angular.json`);
288
+ if (icons)
289
+ context.logger.info(` · ${ICONS_PACKAGE} para tus íconos`);
290
+ context.logger.info('');
291
+ context.logger.info('Agrega tu primer componente:');
292
+ context.logger.info(' ng g mimi button');
293
+ context.logger.info('');
294
+ context.logger.info(`Y úsalo: import { MimiButton } from '@/components/ui/button';`);
295
+ context.logger.info('Guarda la carpeta .mimi/ en git: Mimi la usa para actualizar tus componentes sin perder tus cambios.');
296
+ };
297
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /**
3
+ * Rutas del workspace siempre con "/", también en Windows: el árbol de schematics usa rutas
4
+ * POSIX, y lo que escribimos en el CSS y en el tsconfig debe funcionar en cualquier sistema.
5
+ * Sin `node:path`, para no depender de sus tipos.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.normalizePath = normalizePath;
9
+ exports.dirnameOf = dirnameOf;
10
+ exports.relativePath = relativePath;
11
+ /** `src\app\ui\`, `./src/app/ui` o `/src/app/ui` → `src/app/ui`. Resuelve `.` y `..`. */
12
+ function normalizePath(path) {
13
+ const out = [];
14
+ for (const part of path.replace(/\\/g, '/').split('/')) {
15
+ if (part === '' || part === '.')
16
+ continue;
17
+ if (part === '..' && out.length > 0 && out[out.length - 1] !== '..')
18
+ out.pop();
19
+ else
20
+ out.push(part);
21
+ }
22
+ return out.join('/');
23
+ }
24
+ /** Carpeta de un archivo: `src/styles.css` → `src`. */
25
+ function dirnameOf(path) {
26
+ const parts = normalizePath(path).split('/');
27
+ parts.pop();
28
+ return parts.join('/');
29
+ }
30
+ /** Ruta relativa de la carpeta `fromDir` a `to`, siempre con "./" o "../" al principio. */
31
+ function relativePath(fromDir, to) {
32
+ const from = normalizePath(fromDir).split('/').filter(Boolean);
33
+ const target = normalizePath(to).split('/').filter(Boolean);
34
+ let common = 0;
35
+ while (common < from.length && common < target.length && from[common] === target[common]) {
36
+ common++;
37
+ }
38
+ const up = from.slice(common).map(() => '..');
39
+ const rel = [...up, ...target.slice(common)].join('/');
40
+ if (rel === '')
41
+ return '.';
42
+ return rel.startsWith('..') ? rel : `./${rel}`;
43
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "$id": "MimiInit",
4
+ "title": "Mimi init",
5
+ "type": "object",
6
+ "properties": {
7
+ "project": {
8
+ "type": "string",
9
+ "description": "Proyecto de angular.json. Solo hace falta si el workspace tiene varias aplicaciones.",
10
+ "$default": { "$source": "projectName" }
11
+ },
12
+ "icons": {
13
+ "type": "boolean",
14
+ "description": "Instala @lucide/angular para tus íconos.",
15
+ "default": false,
16
+ "x-prompt": "¿Quieres instalar @lucide/angular para tus íconos? (recomendado)"
17
+ },
18
+ "overwrite": {
19
+ "type": "boolean",
20
+ "description": "Reemplaza los archivos de Mimi que ya existan con otro contenido.",
21
+ "default": false
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ngAdd = ngAdd;
4
+ const schematics_1 = require("@angular-devkit/schematics");
5
+ /** `ng add @mimi-ng/cli`: ejecuta init con las mismas opciones. */
6
+ function ngAdd(options) {
7
+ return (0, schematics_1.schematic)('init', options);
8
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registry = void 0;
7
+ exports.resolveItems = resolveItems;
8
+ const registry_json_1 = __importDefault(require("./registry.json"));
9
+ exports.registry = registry_json_1.default;
10
+ /**
11
+ * El ítem y todas sus `registryDependencies`, sin repetir, dependencias primero.
12
+ * Lanza un error si el nombre no existe.
13
+ */
14
+ function resolveItems(name, reg = exports.registry) {
15
+ const ordered = [];
16
+ const visit = (current, trail) => {
17
+ const item = reg.items[current];
18
+ if (!item) {
19
+ const from = trail.length ? ` (pedido por ${trail[trail.length - 1]})` : '';
20
+ throw new Error(`"${current}" no existe en el registro de Mimi${from}.`);
21
+ }
22
+ if (ordered.includes(current))
23
+ return;
24
+ for (const dep of item.registryDependencies ?? [])
25
+ visit(dep, [...trail, current]);
26
+ ordered.push(current);
27
+ };
28
+ visit(name, []);
29
+ return ordered;
30
+ }