@mimi-ng/cli 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +42 -1
  2. package/dist/init/css.js +1 -1
  3. package/dist/init/index.js +33 -0
  4. package/dist/init/paths.js +20 -0
  5. package/dist/registry.js +4 -1
  6. package/dist/registry.json +198 -1
  7. package/dist/templates/blocks/login-01/index.ts +1 -0
  8. package/dist/templates/blocks/login-01/login-01.html +174 -0
  9. package/dist/templates/blocks/login-01/login-01.ts +114 -0
  10. package/dist/templates/blocks/login-02/index.ts +1 -0
  11. package/dist/templates/blocks/login-02/login-02.html +186 -0
  12. package/dist/templates/blocks/login-02/login-02.ts +101 -0
  13. package/dist/templates/blocks/otp-01/index.ts +1 -0
  14. package/dist/templates/blocks/otp-01/otp-01.html +166 -0
  15. package/dist/templates/blocks/otp-01/otp-01.ts +221 -0
  16. package/dist/templates/blocks/pricing-01/index.ts +1 -0
  17. package/dist/templates/blocks/pricing-01/pricing-01.html +173 -0
  18. package/dist/templates/blocks/pricing-01/pricing-01.ts +178 -0
  19. package/dist/templates/blocks/settings-01/index.ts +1 -0
  20. package/dist/templates/blocks/settings-01/settings-01.html +424 -0
  21. package/dist/templates/blocks/settings-01/settings-01.ts +392 -0
  22. package/dist/templates/blocks/signup-01/index.ts +1 -0
  23. package/dist/templates/blocks/signup-01/signup-01.html +332 -0
  24. package/dist/templates/blocks/signup-01/signup-01.ts +186 -0
  25. package/dist/templates/blocks/table-01/index.ts +1 -0
  26. package/dist/templates/blocks/table-01/table-01.html +214 -0
  27. package/dist/templates/blocks/table-01/table-01.ts +266 -0
  28. package/dist/templates/components/checkbox/checkbox.ts +14 -5
  29. package/dist/templates/components/command/command-texts.ts +65 -0
  30. package/dist/templates/components/command/command.ts +553 -0
  31. package/dist/templates/components/command/index.ts +13 -0
  32. package/dist/templates/components/data-table/data-table-texts.ts +110 -0
  33. package/dist/templates/components/data-table/data-table.ts +1153 -0
  34. package/dist/templates/components/data-table/index.ts +18 -0
  35. package/dist/templates/components/sortable-list/index.ts +9 -0
  36. package/dist/templates/components/sortable-list/sortable-list-texts.ts +71 -0
  37. package/dist/templates/components/sortable-list/sortable-list.ts +510 -0
  38. package/dist/templates/components/tabs/tabs.ts +88 -23
  39. package/dist/templates/components/tree/index.ts +9 -0
  40. package/dist/templates/components/tree/tree-texts.ts +45 -0
  41. package/dist/templates/components/tree/tree.ts +730 -0
  42. package/dist/templates/theme/provider.ts +4 -0
  43. package/dist/templates/theme/types.ts +84 -0
  44. package/dist/ui/index.js +323 -13
  45. package/dist/ui/schema.json +50 -2
  46. package/dist/utils/pages.js +138 -0
  47. package/dist/utils/project.js +59 -0
  48. package/dist/utils/tailwind.js +84 -0
  49. package/dist/utils/write-files.js +63 -8
  50. package/package.json +1 -1
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /**
3
+ * Blocks de tipo página (B.11): `ng g mimi login-01` no copia el Block tal cual (`Login01`,
4
+ * `mimi-login-01`), sino que genera una página del usuario en `aliases.pages`
5
+ * (`src/app/pages/login/login.ts`, clase `LoginPage`) con su archivo de rutas.
6
+ *
7
+ * Solo funciones de texto, sin schematics: lo usa también el sitio para mostrar el código como
8
+ * lo genera la CLI.
9
+ *
10
+ * **Seguimiento por el nombre del registro (B.11-f):** los archivos de una página se identifican
11
+ * con claves `pages/<ítem>/…` (`pages/login-01/login-01.ts`), que son también las de sus bases
12
+ * (`.mimi/base/pages/login-01/`) y las del manifiesto. La carpeta y el nombre que eligió el
13
+ * usuario solo deciden dónde se escriben y cómo se llaman.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.isBlockLikeName = exports.pageItemOf = exports.pageKeys = exports.pageKeyOf = exports.routesKey = exports.pageSourceFiles = exports.routesConstName = exports.pageClassName = exports.defaultPageName = exports.PAGE_NAME = exports.isPageFile = exports.PAGES_PREFIX = void 0;
17
+ exports.pageSelector = pageSelector;
18
+ exports.pageTargetPath = pageTargetPath;
19
+ exports.renderPageFile = renderPageFile;
20
+ exports.routesFile = routesFile;
21
+ exports.routeLine = routeLine;
22
+ /** Prefijo de las claves de los archivos de una página. */
23
+ exports.PAGES_PREFIX = 'pages/';
24
+ /** ¿Es la clave de un archivo de una página? */
25
+ const isPageFile = (file) => file.startsWith(exports.PAGES_PREFIX);
26
+ exports.isPageFile = isPageFile;
27
+ /** Nombre válido para `--name`: kebab-case, empieza con una letra (`login`, `admin-login`). */
28
+ exports.PAGE_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
29
+ /** Nombre por defecto: el del registro sin el número (`login-01` → `login`). */
30
+ const defaultPageName = (item) => item.replace(/-\d+$/, '');
31
+ exports.defaultPageName = defaultPageName;
32
+ const pascal = (kebab) => kebab
33
+ .split('-')
34
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
35
+ .join('');
36
+ const endsWithPage = (name) => name === 'page' || name.endsWith('-page');
37
+ /** `login` → `LoginPage`; `login-page` → `LoginPage` (sin repetir «Page»). */
38
+ const pageClassName = (name) => endsWithPage(name) ? pascal(name) : `${pascal(name)}Page`;
39
+ exports.pageClassName = pageClassName;
40
+ /** `app` y `login` → `app-login-page`; sin prefijo, `login-page`. */
41
+ function pageSelector(prefix, name) {
42
+ const base = endsWithPage(name) ? name : `${name}-page`;
43
+ return prefix ? `${prefix}-${base}` : base;
44
+ }
45
+ /** `login` → `LOGIN_ROUTES`; `admin-login` → `ADMIN_LOGIN_ROUTES`. */
46
+ const routesConstName = (name) => `${name.replace(/-/g, '_').toUpperCase()}_ROUTES`;
47
+ exports.routesConstName = routesConstName;
48
+ const basename = (file) => file.slice(file.lastIndexOf('/') + 1);
49
+ /** Los archivos del Block que pasan a la página: todos menos su `index.ts`. */
50
+ const pageSourceFiles = (files) => files.filter((file) => basename(file) !== 'index.ts');
51
+ exports.pageSourceFiles = pageSourceFiles;
52
+ /** Clave del archivo de rutas (`pages/login-01/login-01.routes.ts`). */
53
+ const routesKey = (item) => `${exports.PAGES_PREFIX}${item}/${item}.routes.ts`;
54
+ exports.routesKey = routesKey;
55
+ /** Clave de un archivo del Block en la página: `blocks/login-01/login-01.html` → `pages/login-01/login-01.html`. */
56
+ const pageKeyOf = (item, file) => `${exports.PAGES_PREFIX}${item}/${basename(file)}`;
57
+ exports.pageKeyOf = pageKeyOf;
58
+ /** Las claves de los archivos de una página: los del Block (sin `index.ts`) y el de rutas. */
59
+ const pageKeys = (item, files) => [
60
+ ...(0, exports.pageSourceFiles)(files).map((file) => (0, exports.pageKeyOf)(item, file)),
61
+ (0, exports.routesKey)(item),
62
+ ];
63
+ exports.pageKeys = pageKeys;
64
+ /** El ítem de una clave de página (`pages/login-01/login-01.ts` → `login-01`). */
65
+ const pageItemOf = (key) => key.slice(exports.PAGES_PREFIX.length).split('/')[0];
66
+ exports.pageItemOf = pageItemOf;
67
+ /**
68
+ * Ruta en el workspace de un archivo de la página: `pages/login-01/login-01.ts` con
69
+ * `{ dir: 'src/app/pages/login', name: 'login' }` → `src/app/pages/login/login.ts`.
70
+ */
71
+ function pageTargetPath(key, target) {
72
+ const file = basename(key);
73
+ const rest = file.startsWith(target.item) ? file.slice(target.item.length) : `.${file}`;
74
+ return `${target.dir}/${target.name}${rest}`;
75
+ }
76
+ const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
77
+ /** `settings-01` → `SETTINGS_01`; `Login-Page` → `LOGIN_PAGE`. */
78
+ const constantCase = (kebab) => kebab.replace(/-/g, '_').toUpperCase();
79
+ /**
80
+ * ¿El nombre de página contiene el del Block (`login-01`, `login-01-x`)? La conversión no
81
+ * distinguiría lo viejo de lo nuevo: `ng g mimi` lo rechaza con su propio mensaje.
82
+ */
83
+ const isBlockLikeName = (item, name) => name === item || name.startsWith(`${item}-`);
84
+ exports.isBlockLikeName = isBlockLikeName;
85
+ /**
86
+ * El código de un archivo del Block convertido en el de la página: el selector `mimi-login-01`
87
+ * (también en los `id`) pasa a `app-login-page`; la clase `Login01` y sus tipos (`Login01Value`)
88
+ * a `LoginPage` (`LoginPageValue`); las constantes (`SETTINGS_01_PLAN`) a `SETTINGS_PAGE_PLAN`;
89
+ * los archivos propios (`./login-01.html`) a `./login.html`. Lanza un error si queda algo del
90
+ * nombre del Block (un Block nuevo con otra forma necesita que se revise esta función; lo prueba
91
+ * `pages.spec.ts` con los Blocks reales). El nombre de la página no puede contener el del Block
92
+ * (`--name login-01`): lo rechaza antes `isBlockLikeName`.
93
+ */
94
+ function renderPageFile(source, target) {
95
+ const { item, name } = target;
96
+ const fromClass = pascal(item);
97
+ const fromConstant = constantCase(item);
98
+ const result = source
99
+ .split(`mimi-${item}`)
100
+ .join(pageSelector(target.prefix, name))
101
+ .replace(new RegExp(`(['"\`])\\./${escape(item)}\\.`, 'g'), `$1./${name}.`)
102
+ .replace(new RegExp(`\\b${escape(fromClass)}`, 'g'), (0, exports.pageClassName)(name))
103
+ .replace(new RegExp(`\\b${escape(fromConstant)}(?=_|\\b)`, 'g'), constantCase((0, exports.pageClassName)(name).replace(/([a-z0-9])([A-Z])/g, '$1-$2')));
104
+ const left = [`mimi-${item}`, `./${item}.`].filter((text) => result.includes(text));
105
+ if (new RegExp(`\\b${escape(fromClass)}`).test(result))
106
+ left.push(fromClass);
107
+ if (new RegExp(`\\b${escape(fromConstant)}(?=_|\\b)`).test(result))
108
+ left.push(fromConstant);
109
+ if (left.length > 0) {
110
+ throw new Error(`No se pudo convertir ${item} en una página: quedan ${left.join(', ')}. Es un error de Mimi; ` +
111
+ 'avísanos en https://github.com/nuki23/mimi-ng/issues.');
112
+ }
113
+ return result;
114
+ }
115
+ /** `login.routes.ts`: la página en carga diferida, lista para `loadChildren`. */
116
+ function routesFile(target) {
117
+ const className = (0, exports.pageClassName)(target.name);
118
+ return [
119
+ "import type { Routes } from '@angular/router';",
120
+ '',
121
+ '/**',
122
+ ` * Rutas de la página ${target.name} (${target.item} de Mimi). La página se carga al entrar en`,
123
+ ' * ella. En app.routes.ts, con loadChildren (ng g mimi te dio la línea exacta).',
124
+ ' */',
125
+ `export const ${(0, exports.routesConstName)(target.name)}: Routes = [`,
126
+ ` { path: '', loadComponent: () => import('./${target.name}').then((m) => m.${className}) },`,
127
+ '];',
128
+ '',
129
+ ].join('\n');
130
+ }
131
+ /**
132
+ * La línea para `app.routes.ts` (que está en `appDir`): `{ path: 'login', loadChildren: () =>
133
+ * import('./pages/login/login.routes').then((m) => m.LOGIN_ROUTES) },`.
134
+ */
135
+ function routeLine(target, routesImport) {
136
+ return (`{ path: '${target.name}', loadChildren: () => import('${routesImport}')` +
137
+ `.then((m) => m.${(0, exports.routesConstName)(target.name)}) },`);
138
+ }
@@ -1,12 +1,71 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MIMI_COLLECTION = void 0;
4
+ exports.defaultBlocksDir = defaultBlocksDir;
5
+ exports.defaultPagesDir = defaultPagesDir;
6
+ exports.assertAliases = assertAliases;
4
7
  exports.readJson = readJson;
5
8
  exports.allDependencies = allDependencies;
6
9
  exports.majorOf = majorOf;
7
10
  const schematics_1 = require("@angular-devkit/schematics");
11
+ const paths_1 = require("../init/paths");
8
12
  /** Nombre de esta colección (el paquete). */
9
13
  exports.MIMI_COLLECTION = '@mimi-ng/cli';
14
+ /**
15
+ * Carpeta de los Blocks si mimi.json no la dice: la hermana de `components/` cuando la de
16
+ * componentes es la de siempre (`src/app/components/ui` → `src/app/blocks`), o si no, la hermana
17
+ * de la carpeta de componentes (`src/app/ui` → `src/app/blocks`).
18
+ */
19
+ function defaultBlocksDir(componentsDir) {
20
+ return siblingOfComponents(componentsDir, 'blocks');
21
+ }
22
+ /**
23
+ * Carpeta de las páginas si mimi.json no la dice (B.11-b), con la misma regla que la de los
24
+ * Blocks: `src/app/components/ui` → `src/app/pages`.
25
+ */
26
+ function defaultPagesDir(componentsDir) {
27
+ return siblingOfComponents(componentsDir, 'pages');
28
+ }
29
+ function siblingOfComponents(componentsDir, folder) {
30
+ const parts = componentsDir.split('/').filter(Boolean);
31
+ if (parts.length >= 2 && parts.at(-1) === 'ui' && parts.at(-2) === 'components') {
32
+ parts.splice(-2, 2);
33
+ }
34
+ else {
35
+ parts.pop();
36
+ }
37
+ return [...parts, folder].join('/');
38
+ }
39
+ const ALIAS_EXAMPLES = {
40
+ components: 'src/app/components/ui',
41
+ blocks: 'src/app/blocks',
42
+ pages: 'src/app/pages',
43
+ };
44
+ /**
45
+ * M2: falla con un error claro (sin escribir nada) si `aliases.components`, `aliases.blocks` o
46
+ * `aliases.pages` de mimi.json, o la carpeta de una página (`blocks.<ítem>.path`, B.11), es
47
+ * absoluto, tiene `..` o sale del proyecto.
48
+ */
49
+ function assertAliases(config) {
50
+ for (const name of ['components', 'blocks', 'pages']) {
51
+ const value = config.aliases?.[name];
52
+ if (value === undefined)
53
+ continue;
54
+ assertRelative(`aliases.${name}`, value, ALIAS_EXAMPLES[name]);
55
+ }
56
+ for (const [item, entry] of Object.entries(config.blocks ?? {})) {
57
+ if (entry?.path === undefined)
58
+ continue;
59
+ assertRelative(`blocks.${item}.path`, entry.path, `${ALIAS_EXAMPLES.pages}/login`);
60
+ }
61
+ }
62
+ function assertRelative(key, value, example) {
63
+ const reason = (0, paths_1.invalidAliasReason)(value);
64
+ if (!reason)
65
+ return;
66
+ throw new schematics_1.SchematicsException(`mimi.json → ${key} ${reason}: ${JSON.stringify(value)}. Usa una ruta relativa a ` +
67
+ `la raíz del workspace y dentro de ella (por ejemplo "${example}"). No se cambió nada.`);
68
+ }
10
69
  function readJson(tree, path) {
11
70
  if (!tree.exists(path))
12
71
  return null;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tailwindSupportsSourceNot = tailwindSupportsSourceNot;
4
+ exports.withFolderSource = withFolderSource;
5
+ exports.mimiSourceNotLine = mimiSourceNotLine;
6
+ exports.mimiSourceNotPath = mimiSourceNotPath;
7
+ exports.missingMimiExclusion = missingMimiExclusion;
8
+ const css_1 = require("../init/css");
9
+ const paths_1 = require("../init/paths");
10
+ /** La carpeta de las copias originales (`.mimi/base/`), en la raíz del workspace. */
11
+ const MIMI_DIR = '.mimi';
12
+ /**
13
+ * ¿El Tailwind del proyecto entiende `@source not` (4.1 o superior)? Manda la versión instalada
14
+ * (`node_modules`, la que corre de verdad); si no está instalado, el rango de package.json: solo
15
+ * un rango que no puede pasar de la 4.0 (`4.0.5`, `~4.0.17`) cuenta como 4.0. `^4.0.17` instala
16
+ * la última 4.x, así que sí lo entiende.
17
+ */
18
+ function tailwindSupportsSourceNot(tree, declared) {
19
+ const file = tree.read('node_modules/tailwindcss/package.json');
20
+ const installed = file
21
+ ? JSON.parse(file.toString('utf-8')).version
22
+ : undefined;
23
+ if (installed)
24
+ return atLeast41(installed);
25
+ if (!declared)
26
+ return true;
27
+ // Solo un rango exacto o con `~` fija el minor; `^4.0.17`, `>=4`, `^4`, `latest`: la última 4.x.
28
+ const match = /^\s*(?:~|=|v)?\s*(\d+)\.(\d+)/.exec(declared);
29
+ if (!match)
30
+ return true;
31
+ const [major, minor] = [Number(match[1]), Number(match[2])];
32
+ return major > 4 || (major === 4 && minor >= 1);
33
+ }
34
+ function atLeast41(version) {
35
+ const [major, minor] = version.split('.').map((n) => Number.parseInt(n, 10) || 0);
36
+ return major > 4 || (major === 4 && minor >= 1);
37
+ }
38
+ /**
39
+ * Con `source(none)`, Tailwind solo escanea lo que dice un `@source`: la carpeta de los Blocks
40
+ * (G1) y la de las páginas (B.11) necesitan el suyo, como la de componentes. Devuelve el CSS con
41
+ * la línea agregada (después del último `@source` de `afterDirs` que esté, o del tema), o `null`
42
+ * si no hace falta: sin `source(none)`, o si ya está (comillas simples o dobles).
43
+ */
44
+ function withFolderSource(css, cssPath, afterDirs, dir) {
45
+ const tailwind = (0, css_1.findTailwindImport)(css);
46
+ if (!tailwind?.sourceNone)
47
+ return null;
48
+ const cssDir = (0, paths_1.dirnameOf)(cssPath);
49
+ const path = (0, paths_1.relativePath)(cssDir, dir);
50
+ if ((0, css_1.hasDirective)(css, '@source', path))
51
+ return null;
52
+ const line = `@source "${path}";`;
53
+ let end = -1;
54
+ for (const after of afterDirs) {
55
+ const match = new RegExp(String.raw `^[ \t]*@source\s+(["'])` +
56
+ (0, paths_1.relativePath)(cssDir, after).replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
57
+ String.raw `\1[^;\n]*;[^\n]*$`, 'm').exec(css);
58
+ if (match)
59
+ end = Math.max(end, match.index + match[0].length);
60
+ }
61
+ if (end >= 0) {
62
+ const eol = /\r\n/.test(css) ? '\r\n' : '\n';
63
+ return { css: css.slice(0, end) + eol + line + css.slice(end), line };
64
+ }
65
+ const updated = (0, css_1.insertAfterTheme)(css, [line]);
66
+ return updated === null ? null : { css: updated, line };
67
+ }
68
+ /** La línea que excluye `.mimi/` del escaneo de Tailwind, relativa al CSS global. */
69
+ function mimiSourceNotLine(cssPath) {
70
+ return `@source not "${mimiSourceNotPath(cssPath)}";`;
71
+ }
72
+ function mimiSourceNotPath(cssPath) {
73
+ return (0, paths_1.relativePath)((0, paths_1.dirnameOf)(cssPath), MIMI_DIR);
74
+ }
75
+ /**
76
+ * ¿Falta la exclusión de `.mimi/` en este CSS? No hace falta con `source(none)` (Tailwind no
77
+ * escanea nada por su cuenta) ni si ya está, con comillas simples o dobles.
78
+ */
79
+ function missingMimiExclusion(css, cssPath) {
80
+ const tailwind = (0, css_1.findTailwindImport)(css);
81
+ if (!tailwind || tailwind.sourceNone)
82
+ return false;
83
+ return !(0, css_1.hasDirective)(css, '@source not', mimiSourceNotPath(cssPath));
84
+ }
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasFileChanges = exports.emptyReport = exports.MANIFEST = exports.BASE_DIR = void 0;
3
+ exports.isBlockFile = exports.hasFileChanges = exports.emptyReport = exports.MANIFEST = exports.BASE_DIR = exports.itemFiles = void 0;
4
4
  exports.projectPathOf = projectPathOf;
5
+ exports.targetPathOf = targetPathOf;
5
6
  exports.basePathOf = basePathOf;
6
7
  exports.writeTemplates = writeTemplates;
7
8
  exports.blockedMessages = blockedMessages;
@@ -9,6 +10,14 @@ exports.compareVersions = compareVersions;
9
10
  exports.updatedMessages = updatedMessages;
10
11
  const schematics_1 = require("@angular-devkit/schematics");
11
12
  const registry_1 = require("../registry");
13
+ const pages_1 = require("./pages");
14
+ const project_1 = require("./project");
15
+ /**
16
+ * Los archivos de un ítem: los del registro o, si se genera como página, sus claves
17
+ * `pages/<ítem>/…` (B.11-f).
18
+ */
19
+ const itemFiles = (item, pages = {}) => pages[item] ? (0, pages_1.pageKeys)(item, registry_1.registry.items[item].files) : registry_1.registry.items[item].files;
20
+ exports.itemFiles = itemFiles;
12
21
  /** Carpeta de las copias originales, en la raíz del workspace. Debe quedar en git. */
13
22
  exports.BASE_DIR = '.mimi/base';
14
23
  /** Versión de Mimi con la que se escribió la base de cada archivo. */
@@ -34,7 +43,33 @@ exports.hasFileChanges = hasFileChanges;
34
43
  function projectPathOf(file) {
35
44
  return file.replace(/^components\//, '');
36
45
  }
37
- /** Copia original en .mimi/base, con la misma estructura (`.mimi/base/button/button.ts`). */
46
+ /** Prefijo de los archivos de un Block en las plantillas (`blocks/login-01/login-01.ts`). */
47
+ const BLOCKS_PREFIX = 'blocks/';
48
+ /** ¿Es un archivo de un Block? */
49
+ const isBlockFile = (file) => file.startsWith(BLOCKS_PREFIX);
50
+ exports.isBlockFile = isBlockFile;
51
+ /**
52
+ * Ruta en el proyecto de un archivo de las plantillas: los Blocks van a su carpeta, sin
53
+ * `blocks/` (`blocks/login-01/login-01.ts` → `<blocksDir>/login-01/login-01.ts`); el resto, a la
54
+ * de componentes (ver projectPathOf).
55
+ */
56
+ function targetPathOf(file, componentsDir, blocksDir, pages = {}) {
57
+ if ((0, pages_1.isPageFile)(file)) {
58
+ const target = pages[(0, pages_1.pageItemOf)(file)];
59
+ if (!target)
60
+ throw new Error(`Falta la carpeta de la página de ${file}.`);
61
+ return (0, pages_1.pageTargetPath)(file, target);
62
+ }
63
+ return (0, exports.isBlockFile)(file)
64
+ ? `${blocksDir}/${file.slice(BLOCKS_PREFIX.length)}`
65
+ : `${componentsDir}/${projectPathOf(file)}`;
66
+ }
67
+ /**
68
+ * Copia original en .mimi/base, con la misma estructura (`.mimi/base/button/button.ts`). Los
69
+ * Blocks conservan `blocks/` (`.mimi/base/blocks/login-01/login-01.ts`), y esa es también su
70
+ * clave en el manifiesto. Las páginas van por su nombre del registro, no por el de su carpeta
71
+ * (`.mimi/base/pages/login-01/login-01.ts`, B.11-f).
72
+ */
38
73
  function basePathOf(file) {
39
74
  return `${exports.BASE_DIR}/${projectPathOf(file)}`;
40
75
  }
@@ -74,6 +109,8 @@ function readManifest(tree) {
74
109
  function writeTemplates(options) {
75
110
  const items = [...new Set(options.items)];
76
111
  const implicit = new Set(options.implicit ?? []);
112
+ const pages = options.pages ?? {};
113
+ const filesOf = (item) => (0, exports.itemFiles)(item, pages);
77
114
  const files = new Set(items.flatMap((item) => registry_1.registry.items[item].files));
78
115
  const contents = new Map();
79
116
  // 1. Lee las plantillas (sin escribir nada: forEach devuelve null).
@@ -86,9 +123,18 @@ function writeTemplates(options) {
86
123
  ]));
87
124
  // 2. Decide por ítem y escribe.
88
125
  const write = (tree) => {
126
+ // Las páginas: su código sale del Block, con el nombre que eligió el usuario (B.11).
127
+ for (const [item, target] of Object.entries(pages)) {
128
+ for (const file of (0, pages_1.pageSourceFiles)(registry_1.registry.items[item].files)) {
129
+ const source = contents.get(file);
130
+ if (source !== undefined)
131
+ contents.set((0, pages_1.pageKeyOf)(item, file), (0, pages_1.renderPageFile)(source, target));
132
+ }
133
+ contents.set((0, pages_1.routesKey)(item), (0, pages_1.routesFile)(target));
134
+ }
89
135
  const manifest = readManifest(tree);
90
136
  let manifestChanged = false;
91
- const targetOf = (file) => `${options.componentsDir}/${projectPathOf(file)}`;
137
+ const targetOf = (file) => targetPathOf(file, options.componentsDir, options.blocksDir ?? (0, project_1.defaultBlocksDir)(options.componentsDir), pages);
92
138
  const stateOf = (file) => {
93
139
  const target = targetOf(file);
94
140
  if (!tree.exists(target))
@@ -109,7 +155,7 @@ function writeTemplates(options) {
109
155
  };
110
156
  const states = new Map(items.map((item) => [
111
157
  item,
112
- registry_1.registry.items[item].files
158
+ filesOf(item)
113
159
  .filter((file) => contents.has(file))
114
160
  .map((file) => ({ file, ...stateOf(file) })),
115
161
  ]));
@@ -219,8 +265,8 @@ function writeTemplates(options) {
219
265
  * Los avisos de los ítems que no se escribieron (`report.blocked`): qué no se actualizó, por qué
220
266
  * y qué hacer. Nunca dicen «actualizado».
221
267
  */
222
- function blockedMessages(report, componentsDir, version) {
223
- const path = (file) => `${componentsDir}/${projectPathOf(file)}`;
268
+ function blockedMessages(report, componentsDir, version, blocksDir = (0, project_1.defaultBlocksDir)(componentsDir), pages = {}) {
269
+ const path = (file) => targetPathOf(file, componentsDir, blocksDir, pages);
224
270
  return report.blocked.map(({ item, files, dependency, installed }) => {
225
271
  if (dependency) {
226
272
  const needs = `necesita ${dependency} ${version}, que no se actualizó (ver el aviso de ${dependency}).`;
@@ -228,9 +274,18 @@ function blockedMessages(report, componentsDir, version) {
228
274
  ? `${item} no se actualizó a ${version}: ${needs}`
229
275
  : `${item} no se agregó: ${needs}`;
230
276
  }
231
- const isTheme = registry_1.registry.items[item]?.type === 'theme';
232
- const label = isTheme ? 'El tema' : item;
277
+ const type = registry_1.registry.items[item]?.type;
233
278
  const dir = projectPathOf(files[0]).split('/').slice(0, -1).join('/');
279
+ if (type === 'block') {
280
+ // Lo normal es editar un Block (es una pantalla del usuario): el aviso no alarma (B.1-b).
281
+ return (`${item} no se actualizó a ${version}: es tu pantalla y la modificaste ` +
282
+ `(${files.map(path).join(', ')}), así que no se tocó ninguno de sus archivos. Es lo ` +
283
+ `normal con un Block. La versión de Mimi que modificaste está en ${exports.BASE_DIR}/${dir}/, ` +
284
+ 'para comparar. Si quieres la nueva, guarda tus cambios (por ejemplo, en git) y vuelve ' +
285
+ 'a ejecutar el comando con --overwrite: reemplaza tu pantalla entera.');
286
+ }
287
+ const isTheme = type === 'theme';
288
+ const label = isTheme ? 'El tema' : item;
234
289
  return (`${label} no se actualizó a ${version} porque modificaste ${files.map(path).join(', ')}. ` +
235
290
  'No se cambió ninguno de sus archivos, para no mezclar versiones.' +
236
291
  (isTheme
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mimi-ng/cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.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",