@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.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/THIRD_PARTY_NOTICES.md +84 -0
- package/dist/collection.json +22 -0
- package/dist/init/css.js +32 -0
- package/dist/init/index.js +297 -0
- package/dist/init/paths.js +43 -0
- package/dist/init/schema.json +24 -0
- package/dist/ng-add/index.js +8 -0
- package/dist/registry.js +30 -0
- package/dist/registry.json +194 -0
- package/dist/templates/components/avatar/avatar.ts +129 -0
- package/dist/templates/components/avatar/avatar.variants.ts +25 -0
- package/dist/templates/components/avatar/index.ts +2 -0
- package/dist/templates/components/badge/badge.ts +21 -0
- package/dist/templates/components/badge/badge.variants.ts +27 -0
- package/dist/templates/components/badge/index.ts +2 -0
- package/dist/templates/components/button/button.ts +99 -0
- package/dist/templates/components/button/button.variants.ts +60 -0
- package/dist/templates/components/button/index.ts +2 -0
- package/dist/templates/components/card/card.ts +133 -0
- package/dist/templates/components/card/index.ts +1 -0
- package/dist/templates/components/checkbox/checkbox.ts +178 -0
- package/dist/templates/components/checkbox/index.ts +1 -0
- package/dist/templates/components/form-field/error-messages.ts +68 -0
- package/dist/templates/components/form-field/form-error.ts +82 -0
- package/dist/templates/components/form-field/form-field.ts +114 -0
- package/dist/templates/components/form-field/index.ts +3 -0
- package/dist/templates/components/input/index.ts +2 -0
- package/dist/templates/components/input/input.ts +52 -0
- package/dist/templates/components/input/input.variants.ts +38 -0
- package/dist/templates/components/separator/index.ts +2 -0
- package/dist/templates/components/separator/separator.ts +38 -0
- package/dist/templates/components/separator/separator.variants.ts +20 -0
- package/dist/templates/components/skeleton/index.ts +1 -0
- package/dist/templates/components/skeleton/skeleton.ts +27 -0
- package/dist/templates/components/switch/index.ts +1 -0
- package/dist/templates/components/switch/switch.ts +138 -0
- package/dist/templates/components/textarea/index.ts +2 -0
- package/dist/templates/components/textarea/textarea.ts +46 -0
- package/dist/templates/components/textarea/textarea.variants.ts +27 -0
- package/dist/templates/theme/index.ts +2 -0
- package/dist/templates/theme/provider.ts +150 -0
- package/dist/templates/theme/theme-base.css +241 -0
- package/dist/templates/theme/types.ts +124 -0
- package/dist/templates/utils/cn.ts +36 -0
- package/dist/templates/utils/control-styles.ts +27 -0
- package/dist/templates/utils/field-state.ts +164 -0
- package/dist/ui/index.js +172 -0
- package/dist/ui/schema.json +75 -0
- package/dist/utils/project.js +28 -0
- package/dist/utils/write-files.js +167 -0
- 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
|
+
}
|
package/dist/init/css.js
ADDED
|
@@ -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
|
+
}
|
package/dist/registry.js
ADDED
|
@@ -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
|
+
}
|