tailjng 0.0.62 → 0.1.1

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 (54) hide show
  1. package/README.md +187 -32
  2. package/cli/component-manager.js +71 -20
  3. package/cli/execute/init-app.js +284 -0
  4. package/cli/file-operations.js +45 -29
  5. package/cli/index.js +66 -15
  6. package/cli/settings/components-list.js +4 -151
  7. package/cli/settings/header-generator.js +9 -10
  8. package/cli/settings/lib-utils.js +89 -0
  9. package/cli/settings/overwrite-policy.js +18 -0
  10. package/cli/settings/path-utils.js +14 -29
  11. package/cli/settings/project-utils.js +290 -0
  12. package/cli/settings/prompt-utils.js +66 -5
  13. package/cli/templates/app.generator.js +382 -0
  14. package/fesm2022/tailjng.mjs +232 -66
  15. package/fesm2022/tailjng.mjs.map +1 -1
  16. package/lib/services/static/colors.service.d.ts +17 -0
  17. package/lib/services/transformer/transform.service.d.ts +3 -3
  18. package/package.json +1 -1
  19. package/public-api.d.ts +2 -0
  20. package/registry/components.json +164 -0
  21. package/src/lib/components/alert/alert-dialog/dialog-alert.component.css +17 -0
  22. package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +83 -51
  23. package/src/lib/components/alert/alert-dialog/dialog-alert.component.ts +85 -53
  24. package/src/lib/components/alert/alert-toast/toast-alert.component.css +38 -4
  25. package/src/lib/components/alert/alert-toast/toast-alert.component.html +72 -40
  26. package/src/lib/components/alert/alert-toast/toast-alert.component.ts +84 -19
  27. package/src/lib/components/badge/badge.component.ts +1 -2
  28. package/src/lib/components/button/button.component.css +14 -0
  29. package/src/lib/components/button/button.component.html +17 -17
  30. package/src/lib/components/button/button.component.ts +139 -48
  31. package/src/lib/components/card/card-crud-complete/complete-crud-card.component.ts +5 -1
  32. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +1 -1
  33. package/src/lib/components/filter/filter-complete/complete-filter.component.html +1 -1
  34. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +8 -5
  35. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +12 -0
  36. package/src/lib/components/select/select-dropdown/dropdown-select.component.css +4 -0
  37. package/src/lib/components/select/select-dropdown/dropdown-select.component.html +1 -1
  38. package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +3 -3
  39. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.css +4 -0
  40. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +1 -1
  41. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +30 -20
  42. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +504 -170
  43. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.scss +92 -0
  44. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +139 -5
  45. package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.builder.ts +116 -0
  46. package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.helper.ts +43 -0
  47. package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.types.ts +39 -0
  48. package/src/lib/components/table/table-crud-complete/index.ts +3 -0
  49. package/src/lib/components/toggle-radio/toggle-radio.component.css +4 -0
  50. package/src/lib/components/toggle-radio/toggle-radio.component.html +4 -4
  51. package/src/lib/components/toggle-radio/toggle-radio.component.ts +15 -6
  52. package/src/lib/components/tooltip/tooltip.service.ts +0 -30
  53. package/tailjng-0.1.1.tgz +0 -0
  54. package/src/lib/components/color/colors.service.ts +0 -187
package/README.md CHANGED
@@ -1,63 +1,218 @@
1
- # Tailjng
1
+ # tailjng
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.0.
3
+ Librería UI y utilidades para **Angular 19** con **Tailwind CSS v4**. Modelo híbrido: lo que es lógica compartida va en **npm**; los componentes visuales se **copian a tu app** con el CLI (estilo shadcn/ui).
4
4
 
5
- ## Code scaffolding
5
+ ---
6
6
 
7
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
7
+ ## ¿Qué incluye?
8
+
9
+ ### Desde npm (`import { … } from 'tailjng'`)
10
+
11
+ | Área | Contenido |
12
+ |------|-----------|
13
+ | **Config** | `TAILJNG_CONFIG` — `urlBase`, `socketUrl` para API REST |
14
+ | **Colores** | `JColorsService` — variantes (`primary`, `success_soft`, `error_outline`, …) |
15
+ | **Iconos** | `JIconsService` — mapa Lucide usado por los componentes |
16
+ | **Alertas** | `JAlertDialogService`, `JAlertToastService` |
17
+ | **CRUD / HTTP** | `JGenericCrudService`, `JConverterCrudService`, params, files, error handler |
18
+ | **Transformación** | `JTransformService`, `JCalendarService` — fechas, moneda, tablas |
19
+ | **Reportes** | `JExcelService`, filtros Excel/upload |
20
+ | **Interfaces** | `TableColumn`, filtros, formularios, alertas, tema |
21
+ | **Shared** | `JFormShared`, `JDialogShared` |
22
+
23
+ ### Desde el CLI (`npx tailjng add <nombre>`)
24
+
25
+ **35 componentes UI** standalone copiados a `src/app/tailjng/` (ruta configurable con `.tailjng/paths.json`):
26
+
27
+ | Categoría | Ejemplos |
28
+ |-----------|----------|
29
+ | Base | `badge`, `label`, `button`, `tooltip`, `dialog`, `progress-bar`, `toggle-radio` |
30
+ | Inputs | `input`, `input-file`, `input-textarea`, `input-range`, `checkbox-input`, `checkbox-switch` |
31
+ | Selects | `select-dropdown`, `select-multi-dropdown`, `select-multi-table` |
32
+ | Formularios | `form-container`, `form-validation`, `form-sidebar` |
33
+ | Datos | `table-complete`, `table-crud-complete`, `filter-complete`, `card-complete`, `paginator-complete` |
34
+ | Otros | `alert-dialog`, `alert-toast`, `viewer-image`, `viewer-pdf`, `theme-generator`, `coach-mark` |
35
+
36
+ El CLI instala **dependencias transitivas** automáticamente (ej. `button` → `tooltip`).
37
+
38
+ > Los componentes CRUD (`table-crud-complete`, `filter-complete`, `card-*`, …) esperan un backend REST en `TAILJNG_CONFIG.urlBase`. Compatible con **[tailjnx](https://www.npmjs.com/package/tailjnx)** (librería backend del mismo ecosistema).
39
+
40
+ ---
41
+
42
+ ## Requisitos
43
+
44
+ - Angular **19.2+** (19.x dentro del mismo major; no Angular 20/21 aún)
45
+ - Tailwind CSS **4.x** (incluido si eliges Tailwind al crear el proyecto con `ng new`)
46
+ - Peers: `lucide-angular`, `date-fns`, `exceljs`, `xlsx` (el CLI los instala con `init:app` si faltan)
47
+ - Estilos globales en **CSS o SCSS** — `init:app` detecta cuál usa tu app
48
+
49
+ ---
50
+
51
+ ## Inicio rápido
8
52
 
9
53
  ```bash
10
- ng generate component component-name
54
+ npm install tailjng
11
55
  ```
12
56
 
13
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
57
+ Desde la raíz de tu app Angular:
14
58
 
15
59
  ```bash
16
- ng generate --help
60
+ npx tailjng init:app # Tailwind, providers, estilos (sin UI)
61
+ npx tailjng add button # un componente + deps
62
+ npx tailjng list # ver todos los disponibles
17
63
  ```
18
64
 
19
- ## Building
20
-
21
- To build the library, run:
65
+ Opcional — alerts y mode-toggle en el mismo paso:
22
66
 
23
67
  ```bash
24
- ng build tailjng
68
+ npx tailjng init:app --with-components
25
69
  ```
26
70
 
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
71
+ ---
72
+
73
+ ## Configuración
74
+
75
+ ### Providers (`init:app` genera esto)
76
+
77
+ ```typescript
78
+ // src/app/config/tailjng.providers.ts
79
+ import { CurrencyPipe } from '@angular/common';
80
+ import { provideHttpClient } from '@angular/common/http';
81
+ import { provideAnimations } from '@angular/platform-browser/animations';
82
+ import { TAILJNG_CONFIG } from 'tailjng';
83
+
84
+ export const tailjngProviders = [
85
+ provideHttpClient(),
86
+ provideAnimations(),
87
+ CurrencyPipe,
88
+ {
89
+ provide: TAILJNG_CONFIG,
90
+ useValue: {
91
+ urlBase: 'http://localhost:3000/api/v1',
92
+ socketUrl: 'http://localhost:3000',
93
+ },
94
+ },
95
+ ];
96
+ ```
28
97
 
29
- ### Publishing the Library
98
+ ### Estilos
30
99
 
31
- Once the project is built, you can publish your library by following these steps:
100
+ `init:app` configura `@import "tailwindcss"`, variables `@theme` y añade en `angular.json`:
32
101
 
33
- 1. Navigate to the `dist` directory:
34
- ```bash
35
- cd dist/tailjng
36
- ```
102
+ ```json
103
+ "node_modules/tailjng/src/styles.css"
104
+ ```
37
105
 
38
- 2. Run the `npm publish` command to publish your library to the npm registry:
39
- ```bash
40
- npm publish
41
- ```
106
+ ---
42
107
 
43
- ## Running unit tests
108
+ ## Uso en código
44
109
 
45
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
110
+ ### Servicios (npm)
46
111
 
47
- ```bash
48
- ng test
112
+ ```typescript
113
+ import {
114
+ JIconsService,
115
+ JColorsService,
116
+ JAlertToastService,
117
+ JGenericCrudService,
118
+ TAILJNG_CONFIG,
119
+ TableColumn,
120
+ } from 'tailjng';
121
+ ```
122
+
123
+ ### Componentes (copiados con CLI)
124
+
125
+ ```typescript
126
+ import { JButtonComponent } from './tailjng/button/button.component';
127
+ import { JDropdownSelectComponent } from './tailjng/select/select-dropdown/dropdown-select.component';
128
+ import { JAlertToastComponent } from './tailjng/alert/alert-toast/toast-alert.component';
129
+ ```
130
+
131
+ ```html
132
+ <JAlertToast />
133
+ <JButton [text]="'Guardar'" classes="primary" [icon]="icons.save" (clicked)="save()" />
134
+ ```
135
+
136
+ ### Colores — `JColorsService` (solo npm)
137
+
138
+ No uses `npx tailjng add color`. Las variantes vienen del servicio:
139
+
140
+ ```html
141
+ <JButton classes="success" … />
142
+ <JBadge classes="warning_soft" … />
143
+ ```
144
+
145
+ ### Select con datos estáticos
146
+
147
+ Opciones en el `.ts`, binding en el template:
148
+
149
+ ```typescript
150
+ readonly options = [
151
+ { label: 'Pruebas', value: 'pruebas' },
152
+ { label: 'Producción', value: 'produccion' },
153
+ ];
154
+ ```
155
+
156
+ ```html
157
+ <JDropdownSelect
158
+ formControlName="environment"
159
+ [options]="options"
160
+ optionLabel="label"
161
+ optionValue="value"
162
+ [isSearch]="false"
163
+ [showClear]="false"
164
+ />
165
+ ```
166
+
167
+ ### Select / toggle con API
168
+
169
+ Pasa `endpoint`, `type="searchable"` o `loadOnInit` según el componente; usa `JGenericCrudService` vía `TAILJNG_CONFIG.urlBase`.
170
+
171
+ ---
172
+
173
+ ## Comandos CLI
174
+
175
+ | Comando | Descripción |
176
+ |---------|-------------|
177
+ | `npx tailjng init:app` | Prepara proyecto: Tailwind, deps, providers, estilos |
178
+ | `npx tailjng init:app --yes` | Sin prompts interactivos |
179
+ | `npx tailjng init:app --with-components` | init + mode-toggle y alerts |
180
+ | `npx tailjng add <nombre>` | Instala componente + dependencias |
181
+ | `npx tailjng install-all` | Instala los 35 componentes |
182
+ | `npx tailjng list` | Lista registry e indica cuáles ya están instalados |
183
+ | `npx tailjng version` | Versión del paquete npm instalado |
184
+
185
+ Si un componente ya existe, el CLI pregunta cómo sobrescribir (uno a uno, todos o omitir).
186
+
187
+ ---
188
+
189
+ ## Estructura en tu proyecto (tras `init:app` + `add`)
190
+
191
+ ```
192
+ tu-app/
193
+ .postcssrc.json
194
+ .tailjng/paths.json
195
+ src/
196
+ environment.ts
197
+ app/
198
+ config/tailjng.providers.ts
199
+ tailjng/ ← componentes copiados por el CLI
200
+ button/
201
+ tooltip/
202
+
49
203
  ```
50
204
 
51
- ## Running end-to-end tests
205
+ ---
52
206
 
53
- For end-to-end (e2e) testing, run:
207
+ ## Publicación e instalación
54
208
 
55
209
  ```bash
56
- ng e2e
210
+ npm view tailjng version
211
+ npm install tailjng@latest
57
212
  ```
58
213
 
59
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
214
+ ---
60
215
 
61
- ## Additional Resources
216
+ ## Desarrollo del paquete (mantenedores)
62
217
 
63
- For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
218
+ Este archivo se publica en npm. El runbook del monorepo está en **[DEV.md](../../DEV.md)** (raíz del workspace).
@@ -3,30 +3,76 @@
3
3
  const { copyComponentFiles } = require("./file-operations");
4
4
  const { installDependencies } = require("./dependency-manager");
5
5
  const { COLORS } = require("./settings/colors");
6
+ const { collectDependencyTree } = require("./settings/lib-utils");
7
+ const { isComponentInstalled } = require("./settings/path-utils");
8
+ const { resetOverwritePolicy } = require("./settings/overwrite-policy");
9
+ const { askOverwriteStrategy } = require("./settings/prompt-utils");
6
10
 
7
- // Registro global para evitar re-instalaciones duplicadas
8
11
  const installedComponentsGlobal = new Set();
9
12
 
10
- /**
11
- * Instala un componente individual y sus dependencias.
12
- */
13
+ function getExistingComponents(componentNames, componentList) {
14
+ const projectRoot = process.cwd();
15
+ return componentNames.filter((name) =>
16
+ isComponentInstalled(projectRoot, name, componentList[name].path)
17
+ );
18
+ }
19
+
20
+ async function resolveOverwriteStrategy(componentNames, componentList) {
21
+ const existing = getExistingComponents(componentNames, componentList);
22
+ if (existing.length === 0) return;
23
+ await askOverwriteStrategy(existing.length, existing);
24
+ }
25
+
26
+ function logInstallPlan(componentName, componentList) {
27
+ const deps = collectDependencyTree(componentName, componentList);
28
+ const allComponents = [...deps, componentName];
29
+
30
+ console.log(
31
+ `${COLORS.blue}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.blue}Install plan for ${COLORS.bright}"${componentName}"${COLORS.reset}`
32
+ );
33
+
34
+ if (deps.length > 0) {
35
+ console.log(`${COLORS.dim} Dependencies (${deps.length}): ${deps.join(", ")}${COLORS.reset}`);
36
+ } else {
37
+ console.log(`${COLORS.dim} Dependencies: none${COLORS.reset}`);
38
+ }
39
+
40
+ const existing = getExistingComponents(allComponents, componentList);
41
+
42
+ if (existing.length > 0) {
43
+ console.log(
44
+ `${COLORS.yellow} Already in project: ${existing.join(", ")}${COLORS.reset}`
45
+ );
46
+ }
47
+
48
+ console.log("");
49
+ }
50
+
13
51
  async function addComponent(componentName, componentList) {
52
+ resetOverwritePolicy();
53
+ installedComponentsGlobal.clear();
54
+
14
55
  const componentData = componentList[componentName];
15
56
  if (!componentData) {
16
57
  console.error(
17
- `${COLORS.red}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.red}ERROR: Component ${COLORS.bright}"${componentName}"${COLORS.reset} ${COLORS.red}not found in the component list.${COLORS.reset}`
58
+ `${COLORS.red}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.red}ERROR: Component ${COLORS.bright}"${componentName}"${COLORS.reset} ${COLORS.red}not found.${COLORS.reset}`
18
59
  );
60
+ console.log(`${COLORS.cyan}Run ${COLORS.bright}npx tailjng list${COLORS.reset}${COLORS.cyan} to see available components.${COLORS.reset}`);
19
61
  process.exit(1);
20
62
  }
21
63
 
64
+ const deps = collectDependencyTree(componentName, componentList);
65
+ const allComponents = [...deps, componentName];
66
+
67
+ logInstallPlan(componentName, componentList);
68
+ await resolveOverwriteStrategy(allComponents, componentList);
69
+
22
70
  console.log(
23
71
  `${COLORS.blue}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.blue}Adding component: ${COLORS.bright}"${componentName}"${COLORS.reset}`
24
72
  );
25
73
 
26
- // Instalar dependencias primero
27
74
  await installDependencies(componentData.dependencies, componentList, installedComponentsGlobal);
28
75
 
29
- // Si el componente principal ya fue procesado (como dependencia), saltarlo
30
76
  if (installedComponentsGlobal.has(componentName)) {
31
77
  console.log(
32
78
  `${COLORS.dim}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.dim}Main component ${COLORS.bright}"${componentName}"${COLORS.reset} ${COLORS.dim}was already processed as a dependency.${COLORS.reset}`
@@ -34,10 +80,8 @@ async function addComponent(componentName, componentList) {
34
80
  return;
35
81
  }
36
82
 
37
- // Copiar los archivos del componente principal
38
83
  const wasInstalled = await copyComponentFiles(componentName, componentData.path, false);
39
-
40
- installedComponentsGlobal.add(componentName); // lo marcamos como instalado
84
+ installedComponentsGlobal.add(componentName);
41
85
 
42
86
  if (wasInstalled) {
43
87
  console.log(
@@ -45,34 +89,41 @@ async function addComponent(componentName, componentList) {
45
89
  );
46
90
  } else {
47
91
  console.log(
48
- `${COLORS.yellow}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.yellow}Component ${COLORS.bright}"${componentName}"${COLORS.reset} ${COLORS.yellow}installation was cancelled by user.${COLORS.reset}`
92
+ `${COLORS.yellow}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.yellow}Component ${COLORS.bright}"${componentName}"${COLORS.reset} ${COLORS.yellow}kept your existing version (not overwritten).${COLORS.reset}`
49
93
  );
50
94
  }
51
95
  }
52
96
 
53
- /**
54
- * Instala todos los componentes disponibles en la lista.
55
- */
56
97
  async function installAllComponents(componentList) {
98
+ resetOverwritePolicy();
99
+ installedComponentsGlobal.clear();
100
+
101
+ const allNames = Object.keys(componentList);
102
+ const existing = getExistingComponents(allNames, componentList);
103
+
57
104
  console.log(
58
105
  `${COLORS.blue}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.blue}Installing all components...${COLORS.reset}`
59
106
  );
60
107
 
61
- for (const componentName of Object.keys(componentList)) {
108
+ if (existing.length > 0) {
109
+ console.log(
110
+ `${COLORS.yellow} ${existing.length} component(s) already in project.${COLORS.reset}\n`
111
+ );
112
+ await resolveOverwriteStrategy(allNames, componentList);
113
+ }
114
+
115
+ for (const componentName of allNames) {
62
116
  const componentData = componentList[componentName];
63
117
 
64
- // Evita duplicados
65
118
  if (installedComponentsGlobal.has(componentName)) {
66
119
  console.log(
67
- `${COLORS.dim}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.dim}Skipping already installed: ${COLORS.bright}"${componentName}"${COLORS.reset}`
120
+ `${COLORS.dim}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.dim}Skipping already processed: ${COLORS.bright}"${componentName}"${COLORS.reset}`
68
121
  );
69
122
  continue;
70
123
  }
71
124
 
72
- // Instalar dependencias
73
125
  await installDependencies(componentData.dependencies, componentList, installedComponentsGlobal);
74
126
 
75
- // Instalar el componente principal
76
127
  const wasInstalled = await copyComponentFiles(componentName, componentData.path, false);
77
128
  installedComponentsGlobal.add(componentName);
78
129
 
@@ -82,7 +133,7 @@ async function installAllComponents(componentList) {
82
133
  );
83
134
  } else {
84
135
  console.log(
85
- `${COLORS.yellow}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.yellow}Component ${COLORS.bright}"${componentName}"${COLORS.reset} ${COLORS.yellow}installation was skipped by user.${COLORS.reset}`
136
+ `${COLORS.yellow}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.yellow}Component ${COLORS.bright}"${componentName}"${COLORS.reset} ${COLORS.yellow}kept your existing version.${COLORS.reset}`
86
137
  );
87
138
  }
88
139
  }
@@ -0,0 +1,284 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+ const readline = require('readline');
5
+ const { COLORS } = require('../settings/colors');
6
+ const { addComponent } = require('../component-manager');
7
+ const { getComponentList } = require('../settings/components-list');
8
+ const { buildInitFiles } = require('../templates/app.generator');
9
+ const {
10
+ findAngularWorkspace,
11
+ readJson,
12
+ getApplicationProjects,
13
+ resolveAppPaths,
14
+ getBuildOptions,
15
+ detectStyleLanguage,
16
+ getPrimaryStyleEntry,
17
+ writeFileSafe,
18
+ ensureTailjngStylesInAngularJson,
19
+ patchIndexHtml,
20
+ patchAppConfig,
21
+ patchAppComponentForAlerts,
22
+ getMissingPackages,
23
+ resolveRuntimePackages,
24
+ resolveDevPackages,
25
+ hasTailwindSetup,
26
+ fileExists,
27
+ } = require('../settings/project-utils');
28
+
29
+ const RUNTIME_PACKAGES = {
30
+ 'lucide-angular': '^0.525.0',
31
+ '@ng-icons/lucide': '>=32.0.0',
32
+ 'date-fns': '^4.1.0',
33
+ 'exceljs': '^4.4.0',
34
+ 'xlsx': '^0.18.5',
35
+ };
36
+
37
+ const DEV_PACKAGES = {
38
+ tailwindcss: '^4.0.9',
39
+ '@tailwindcss/postcss': '^4.0.9',
40
+ postcss: '^8.5.3',
41
+ autoprefixer: '^10.4.20',
42
+ };
43
+
44
+ /** Solo si pasas --with-components (opcional; no es el flujo normal). */
45
+ const OPTIONAL_BASE_COMPONENTS = ['mode-toggle', 'alert-dialog', 'alert-toast'];
46
+
47
+ function ask(question, defaultValue = '') {
48
+ if (process.argv.includes('--yes')) {
49
+ return Promise.resolve(defaultValue);
50
+ }
51
+
52
+ const suffix = defaultValue ? ` (${defaultValue})` : '';
53
+ return new Promise((resolve) => {
54
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
55
+ rl.question(`${COLORS.cyan}${question}${suffix}: ${COLORS.reset}`, (answer) => {
56
+ rl.close();
57
+ resolve(answer.trim() || defaultValue);
58
+ });
59
+ });
60
+ }
61
+
62
+ function askYesNo(question, defaultYes = true) {
63
+ if (process.argv.includes('--yes')) {
64
+ return Promise.resolve(defaultYes);
65
+ }
66
+
67
+ const hint = defaultYes ? 'Y/n' : 'y/N';
68
+ return new Promise((resolve) => {
69
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
70
+ rl.question(`${COLORS.cyan}${question} (${hint}): ${COLORS.reset}`, (answer) => {
71
+ rl.close();
72
+ const normalized = answer.trim().toLowerCase();
73
+ if (!normalized) resolve(defaultYes);
74
+ else resolve(normalized === 'y' || normalized === 'yes');
75
+ });
76
+ });
77
+ }
78
+
79
+ function installPackages(workspaceRoot, packages, isDev = false) {
80
+ const entries = Object.entries(packages);
81
+ if (entries.length === 0) return;
82
+
83
+ const spec = entries.map(([name, version]) => `${name}@${version}`).join(' ');
84
+ const flag = isDev ? '--save-dev' : '--save';
85
+ console.log(`${COLORS.blue}[tailjng CLI] Installing ${isDev ? 'dev ' : ''}dependencies...${COLORS.reset}`);
86
+
87
+ const run = (extra = '') => {
88
+ execSync(`npm install ${flag} ${extra} ${spec}`.replace(/\s+/g, ' ').trim(), {
89
+ cwd: workspaceRoot,
90
+ stdio: 'inherit',
91
+ shell: true,
92
+ });
93
+ };
94
+
95
+ try {
96
+ run();
97
+ } catch {
98
+ console.log(`${COLORS.yellow}[tailjng CLI] Retrying with --legacy-peer-deps...${COLORS.reset}`);
99
+ run('--legacy-peer-deps');
100
+ }
101
+ }
102
+
103
+ function resolveTargetPath(workspaceRoot, appProject, relativePath, appRootRelative = false) {
104
+ const appRoot = path.join(workspaceRoot, appProject.root || '');
105
+
106
+ if (appRootRelative) {
107
+ return path.join(appRoot, relativePath);
108
+ }
109
+
110
+ if (relativePath.startsWith('projects/')) {
111
+ return path.join(workspaceRoot, relativePath);
112
+ }
113
+
114
+ return path.join(workspaceRoot, appProject.sourceRoot, relativePath.replace(/^src\//, ''));
115
+ }
116
+
117
+ async function runInitApp() {
118
+ const cwd = process.cwd();
119
+ const workspace = findAngularWorkspace(cwd);
120
+
121
+ if (!workspace) {
122
+ console.error(`${COLORS.red}[tailjng CLI] ERROR: angular.json not found. Run this inside an Angular project.${COLORS.reset}`);
123
+ process.exit(1);
124
+ }
125
+
126
+ const { workspaceRoot, angularJsonPath } = workspace;
127
+ const angularJson = readJson(angularJsonPath);
128
+ const apps = getApplicationProjects(angularJson);
129
+
130
+ if (apps.length === 0) {
131
+ console.error(`${COLORS.red}[tailjng CLI] ERROR: No application project found in angular.json.${COLORS.reset}`);
132
+ process.exit(1);
133
+ }
134
+
135
+ let selectedApp = apps[0];
136
+ if (apps.length > 1) {
137
+ console.log(`${COLORS.cyan}[tailjng CLI] Multiple applications found:${COLORS.reset}`);
138
+ apps.forEach((app, index) => console.log(` ${index + 1}. ${app.name}`));
139
+ const answer = await ask('Select application number', '1');
140
+ const index = Number.parseInt(answer, 10) - 1;
141
+ selectedApp = apps[index] || apps[0];
142
+ }
143
+
144
+ const { appRoot, srcRoot } = resolveAppPaths(workspaceRoot, selectedApp);
145
+ const buildOptions = getBuildOptions(angularJson, selectedApp.name);
146
+ const styleLanguage = detectStyleLanguage(buildOptions);
147
+ const primaryStylePath = getPrimaryStyleEntry(buildOptions);
148
+ const indexPath = path.join(workspaceRoot, buildOptions.index || path.join(selectedApp.sourceRoot, 'index.html'));
149
+ const appConfigPath = path.join(srcRoot, 'app', 'app.config.ts');
150
+ const appComponentTsPath = path.join(srcRoot, 'app', 'app.component.ts');
151
+ const appComponentHtmlPath = path.join(srcRoot, 'app', 'app.component.html');
152
+ const componentsPath = 'src/app/tailjng';
153
+
154
+ console.log(`\n${COLORS.bright}${COLORS.blue}[tailjng CLI] init:app${COLORS.reset}`);
155
+ console.log(`${COLORS.dim}Workspace: ${workspaceRoot}${COLORS.reset}`);
156
+ console.log(`${COLORS.dim}Application: ${selectedApp.name}${COLORS.reset}`);
157
+ console.log(`${COLORS.dim}Styles: ${styleLanguage} (${primaryStylePath})${COLORS.reset}\n`);
158
+
159
+ const socketUrl = await ask('API socket/base URL', 'http://localhost:3000');
160
+ const urlBase = await ask('API urlBase', `${socketUrl.replace(/\/$/, '')}/api/v1`);
161
+ const installDeps = await askYesNo('Install required npm packages?', true);
162
+ const withComponentsFlag = process.argv.includes('--with-components');
163
+ const installComponents = withComponentsFlag
164
+ ? true
165
+ : await askYesNo('Install base UI components now? (default: use add / install-all later)', false);
166
+ const overwrite = process.argv.includes('--yes')
167
+ ? true
168
+ : await askYesNo('Overwrite generated config files if they already exist?', false);
169
+
170
+ const packageJsonPath = path.join(workspaceRoot, 'package.json');
171
+ const packageJson = fileExists(packageJsonPath) ? readJson(packageJsonPath) : { dependencies: {}, devDependencies: {} };
172
+ const tailwindReady = hasTailwindSetup(workspaceRoot, packageJson, primaryStylePath);
173
+
174
+ if (!packageJson.dependencies?.tailjng) {
175
+ console.log(`${COLORS.yellow}[tailjng CLI] WARNING: tailjng is not in package.json dependencies.${COLORS.reset}`);
176
+ console.log(`${COLORS.yellow}Install it first: npm install tailjng${COLORS.reset}`);
177
+ }
178
+
179
+ if (installDeps) {
180
+ if (tailwindReady) {
181
+ console.log(`${COLORS.cyan}[tailjng CLI] Tailwind already configured — skipping Tailwind/PostCSS packages and .postcssrc.json${COLORS.reset}`);
182
+ }
183
+
184
+ const runtimePackages = resolveRuntimePackages(packageJson, RUNTIME_PACKAGES);
185
+ const devPackages = resolveDevPackages(packageJson, DEV_PACKAGES, tailwindReady);
186
+ const missingRuntime = Object.fromEntries(getMissingPackages(packageJson, runtimePackages));
187
+ const missingDev = Object.fromEntries(getMissingPackages(packageJson, devPackages));
188
+
189
+ if (missingRuntime['@angular/animations']) {
190
+ console.log(`${COLORS.dim}[tailjng CLI] Adding @angular/animations aligned with @angular/core${COLORS.reset}`);
191
+ }
192
+
193
+ installPackages(workspaceRoot, missingRuntime, false);
194
+ installPackages(workspaceRoot, missingDev, true);
195
+ }
196
+
197
+ const hasAppConfig = fileExists(appConfigPath);
198
+ const files = buildInitFiles({
199
+ styleLanguage,
200
+ primaryStylePath,
201
+ urlBase,
202
+ socketUrl,
203
+ componentsPath,
204
+ overwrite,
205
+ hasAppConfig,
206
+ });
207
+
208
+ let created = 0;
209
+ let skipped = 0;
210
+
211
+ function shouldWriteStyles(filePath) {
212
+ if (!fs.existsSync(filePath)) return true;
213
+ const content = fs.readFileSync(filePath, 'utf8');
214
+ return !content.includes('tailwindcss');
215
+ }
216
+
217
+ for (const file of files) {
218
+ if (tailwindReady && file.relativePath === '.postcssrc.json') {
219
+ skipped += 1;
220
+ console.log(`${COLORS.yellow}↷ Skipped (Tailwind already configured) ${file.relativePath}${COLORS.reset}`);
221
+ continue;
222
+ }
223
+
224
+ const targetPath = resolveTargetPath(workspaceRoot, selectedApp, file.relativePath, file.appRootRelative);
225
+ const isPrimaryStyles = file.relativePath === primaryStylePath;
226
+ const forceWrite = isPrimaryStyles && shouldWriteStyles(targetPath);
227
+ const wrote = writeFileSafe(targetPath, file.content, overwrite || forceWrite);
228
+ if (wrote) {
229
+ created += 1;
230
+ console.log(`${COLORS.green}✔ Created ${path.relative(workspaceRoot, targetPath)}${COLORS.reset}`);
231
+ } else {
232
+ skipped += 1;
233
+ console.log(`${COLORS.yellow}↷ Skipped (exists) ${path.relative(workspaceRoot, targetPath)}${COLORS.reset}`);
234
+ }
235
+ }
236
+
237
+ if (hasAppConfig) {
238
+ const patched = patchAppConfig(appConfigPath);
239
+ if (patched.changed) {
240
+ console.log(`${COLORS.green}✔ Patched ${path.relative(workspaceRoot, appConfigPath)}${COLORS.reset}`);
241
+ }
242
+ }
243
+
244
+ const indexPatched = patchIndexHtml(indexPath);
245
+ if (indexPatched) {
246
+ console.log(`${COLORS.green}✔ Patched ${path.relative(workspaceRoot, indexPath)}${COLORS.reset}`);
247
+ }
248
+
249
+ const stylesPatched = ensureTailjngStylesInAngularJson(angularJsonPath, selectedApp.name, styleLanguage);
250
+ if (stylesPatched.changed) {
251
+ console.log(`${COLORS.green}✔ Added node_modules/tailjng/src/styles.css to angular.json${COLORS.reset}`);
252
+ }
253
+
254
+ if (installComponents) {
255
+ const previousCwd = process.cwd();
256
+ process.chdir(appRoot);
257
+ try {
258
+ for (const componentName of OPTIONAL_BASE_COMPONENTS) {
259
+ await addComponent(componentName, getComponentList());
260
+ }
261
+ patchAppComponentForAlerts(appComponentTsPath, appComponentHtmlPath);
262
+ console.log(`${COLORS.green}✔ Installed optional base components + alert shell${COLORS.reset}`);
263
+ } finally {
264
+ process.chdir(previousCwd);
265
+ }
266
+ }
267
+
268
+ if (tailwindReady && !shouldWriteStyles(path.join(workspaceRoot, primaryStylePath))) {
269
+ console.log(`${COLORS.yellow}[tailjng CLI] Tip: add tailjng @theme tokens to your existing styles if components look unstyled.${COLORS.reset}`);
270
+ }
271
+
272
+ console.log(`\n${COLORS.greenBright}${COLORS.bright}[tailjng CLI] init:app completed.${COLORS.reset}`);
273
+ console.log(`${COLORS.dim}Created: ${created} | Skipped: ${skipped}${COLORS.reset}`);
274
+ console.log(`\n${COLORS.cyan}Project ready. Install components when you need them:${COLORS.reset}`);
275
+ console.log(` ${COLORS.dim}One by one:${COLORS.reset} cd ${path.relative(workspaceRoot, appRoot)} && npx tailjng add button`);
276
+ console.log(` ${COLORS.dim}All at once:${COLORS.reset} cd ${path.relative(workspaceRoot, appRoot)} && npx tailjng install-all`);
277
+ console.log(` ${COLORS.dim}List:${COLORS.reset} npx tailjng list`);
278
+ console.log(`\n${COLORS.cyan}Then run:${COLORS.reset} ng serve ${selectedApp.name} -o\n`);
279
+ }
280
+
281
+ runInitApp().catch((error) => {
282
+ console.error(`${COLORS.red}[tailjng CLI] init:app failed:${COLORS.reset}`, error);
283
+ process.exit(1);
284
+ });