tailjng 0.0.61 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +186 -32
  2. package/cli/component-manager.js +71 -20
  3. package/cli/execute/init-app.js +251 -0
  4. package/cli/file-operations.js +45 -29
  5. package/cli/index.js +66 -15
  6. package/cli/settings/components-list.js +4 -147
  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 +220 -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/form/form-sidebar/sidebar-form.component.html +130 -97
  35. package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +3 -2
  36. package/src/lib/components/input/input/input.component.css +35 -0
  37. package/src/lib/components/input/input/input.component.html +37 -27
  38. package/src/lib/components/input/input/input.component.ts +1 -0
  39. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +56 -0
  40. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +12 -0
  41. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.ts +41 -0
  42. package/src/lib/components/select/select-dropdown/dropdown-select.component.css +4 -0
  43. package/src/lib/components/select/select-dropdown/dropdown-select.component.html +1 -1
  44. package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +3 -3
  45. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.css +4 -0
  46. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +1 -1
  47. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +30 -20
  48. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +506 -172
  49. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.scss +92 -0
  50. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +141 -7
  51. package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.builder.ts +116 -0
  52. package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.helper.ts +43 -0
  53. package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.types.ts +39 -0
  54. package/src/lib/components/table/table-crud-complete/index.ts +3 -0
  55. package/src/lib/components/toggle-radio/toggle-radio.component.css +4 -0
  56. package/src/lib/components/toggle-radio/toggle-radio.component.html +4 -4
  57. package/src/lib/components/toggle-radio/toggle-radio.component.ts +15 -6
  58. package/src/lib/components/tooltip/tooltip.service.ts +0 -30
  59. package/tailjng-0.1.0.tgz +0 -0
  60. package/src/lib/components/color/colors.service.ts +0 -187
package/README.md CHANGED
@@ -1,63 +1,217 @@
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+**
45
+ - Tailwind CSS **4.x**
46
+ - Peers: `lucide-angular`, `date-fns`, `exceljs`, `xlsx` (el CLI los instala con `init:app` si faltan)
47
+
48
+ ---
49
+
50
+ ## Inicio rápido
8
51
 
9
52
  ```bash
10
- ng generate component component-name
53
+ npm install tailjng
11
54
  ```
12
55
 
13
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
56
+ Desde la raíz de tu app Angular:
14
57
 
15
58
  ```bash
16
- ng generate --help
59
+ npx tailjng init:app # Tailwind, providers, estilos (sin UI)
60
+ npx tailjng add button # un componente + deps
61
+ npx tailjng list # ver todos los disponibles
17
62
  ```
18
63
 
19
- ## Building
20
-
21
- To build the library, run:
64
+ Opcional — alerts y mode-toggle en el mismo paso:
22
65
 
23
66
  ```bash
24
- ng build tailjng
67
+ npx tailjng init:app --with-components
25
68
  ```
26
69
 
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
70
+ ---
71
+
72
+ ## Configuración
73
+
74
+ ### Providers (`init:app` genera esto)
75
+
76
+ ```typescript
77
+ // src/app/config/tailjng.providers.ts
78
+ import { CurrencyPipe } from '@angular/common';
79
+ import { provideHttpClient } from '@angular/common/http';
80
+ import { provideAnimations } from '@angular/platform-browser/animations';
81
+ import { TAILJNG_CONFIG } from 'tailjng';
82
+
83
+ export const tailjngProviders = [
84
+ provideHttpClient(),
85
+ provideAnimations(),
86
+ CurrencyPipe,
87
+ {
88
+ provide: TAILJNG_CONFIG,
89
+ useValue: {
90
+ urlBase: 'http://localhost:3000/api/v1',
91
+ socketUrl: 'http://localhost:3000',
92
+ },
93
+ },
94
+ ];
95
+ ```
28
96
 
29
- ### Publishing the Library
97
+ ### Estilos
30
98
 
31
- Once the project is built, you can publish your library by following these steps:
99
+ `init:app` configura `@import "tailwindcss"`, variables `@theme` y añade en `angular.json`:
32
100
 
33
- 1. Navigate to the `dist` directory:
34
- ```bash
35
- cd dist/tailjng
36
- ```
101
+ ```json
102
+ "node_modules/tailjng/src/styles.css"
103
+ ```
37
104
 
38
- 2. Run the `npm publish` command to publish your library to the npm registry:
39
- ```bash
40
- npm publish
41
- ```
105
+ ---
42
106
 
43
- ## Running unit tests
107
+ ## Uso en código
44
108
 
45
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
109
+ ### Servicios (npm)
46
110
 
47
- ```bash
48
- ng test
111
+ ```typescript
112
+ import {
113
+ JIconsService,
114
+ JColorsService,
115
+ JAlertToastService,
116
+ JGenericCrudService,
117
+ TAILJNG_CONFIG,
118
+ TableColumn,
119
+ } from 'tailjng';
120
+ ```
121
+
122
+ ### Componentes (copiados con CLI)
123
+
124
+ ```typescript
125
+ import { JButtonComponent } from './tailjng/button/button.component';
126
+ import { JDropdownSelectComponent } from './tailjng/select/select-dropdown/dropdown-select.component';
127
+ import { JAlertToastComponent } from './tailjng/alert/alert-toast/toast-alert.component';
128
+ ```
129
+
130
+ ```html
131
+ <JAlertToast />
132
+ <JButton [text]="'Guardar'" classes="primary" [icon]="icons.save" (clicked)="save()" />
133
+ ```
134
+
135
+ ### Colores — `JColorsService` (solo npm)
136
+
137
+ No uses `npx tailjng add color`. Las variantes vienen del servicio:
138
+
139
+ ```html
140
+ <JButton classes="success" … />
141
+ <JBadge classes="warning_soft" … />
142
+ ```
143
+
144
+ ### Select con datos estáticos
145
+
146
+ Opciones en el `.ts`, binding en el template:
147
+
148
+ ```typescript
149
+ readonly options = [
150
+ { label: 'Pruebas', value: 'pruebas' },
151
+ { label: 'Producción', value: 'produccion' },
152
+ ];
153
+ ```
154
+
155
+ ```html
156
+ <JDropdownSelect
157
+ formControlName="environment"
158
+ [options]="options"
159
+ optionLabel="label"
160
+ optionValue="value"
161
+ [isSearch]="false"
162
+ [showClear]="false"
163
+ />
164
+ ```
165
+
166
+ ### Select / toggle con API
167
+
168
+ Pasa `endpoint`, `type="searchable"` o `loadOnInit` según el componente; usa `JGenericCrudService` vía `TAILJNG_CONFIG.urlBase`.
169
+
170
+ ---
171
+
172
+ ## Comandos CLI
173
+
174
+ | Comando | Descripción |
175
+ |---------|-------------|
176
+ | `npx tailjng init:app` | Prepara proyecto: Tailwind, deps, providers, estilos |
177
+ | `npx tailjng init:app --yes` | Sin prompts interactivos |
178
+ | `npx tailjng init:app --with-components` | init + mode-toggle y alerts |
179
+ | `npx tailjng add <nombre>` | Instala componente + dependencias |
180
+ | `npx tailjng install-all` | Instala los 35 componentes |
181
+ | `npx tailjng list` | Lista registry e indica cuáles ya están instalados |
182
+ | `npx tailjng version` | Versión del paquete npm instalado |
183
+
184
+ Si un componente ya existe, el CLI pregunta cómo sobrescribir (uno a uno, todos o omitir).
185
+
186
+ ---
187
+
188
+ ## Estructura en tu proyecto (tras `init:app` + `add`)
189
+
190
+ ```
191
+ tu-app/
192
+ .postcssrc.json
193
+ .tailjng/paths.json
194
+ src/
195
+ environment.ts
196
+ app/
197
+ config/tailjng.providers.ts
198
+ tailjng/ ← componentes copiados por el CLI
199
+ button/
200
+ tooltip/
201
+
49
202
  ```
50
203
 
51
- ## Running end-to-end tests
204
+ ---
52
205
 
53
- For end-to-end (e2e) testing, run:
206
+ ## Publicación e instalación
54
207
 
55
208
  ```bash
56
- ng e2e
209
+ npm view tailjng version
210
+ npm install tailjng@latest
57
211
  ```
58
212
 
59
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
213
+ ---
60
214
 
61
- ## Additional Resources
215
+ ## Desarrollo del paquete (mantenedores)
62
216
 
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.
217
+ 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,251 @@
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
+ fileExists,
24
+ } = require('../settings/project-utils');
25
+
26
+ const RUNTIME_PACKAGES = {
27
+ '@angular/animations': '^19.2.0',
28
+ 'lucide-angular': '^0.525.0',
29
+ '@ng-icons/lucide': '>=32.0.0',
30
+ 'date-fns': '^4.1.0',
31
+ 'exceljs': '^4.4.0',
32
+ 'xlsx': '^0.18.5',
33
+ };
34
+
35
+ const DEV_PACKAGES = {
36
+ tailwindcss: '^4.0.9',
37
+ '@tailwindcss/postcss': '^4.0.9',
38
+ postcss: '^8.5.3',
39
+ autoprefixer: '^10.4.20',
40
+ };
41
+
42
+ /** Solo si pasas --with-components (opcional; no es el flujo normal). */
43
+ const OPTIONAL_BASE_COMPONENTS = ['mode-toggle', 'alert-dialog', 'alert-toast'];
44
+
45
+ function ask(question, defaultValue = '') {
46
+ if (process.argv.includes('--yes')) {
47
+ return Promise.resolve(defaultValue);
48
+ }
49
+
50
+ const suffix = defaultValue ? ` (${defaultValue})` : '';
51
+ return new Promise((resolve) => {
52
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
53
+ rl.question(`${COLORS.cyan}${question}${suffix}: ${COLORS.reset}`, (answer) => {
54
+ rl.close();
55
+ resolve(answer.trim() || defaultValue);
56
+ });
57
+ });
58
+ }
59
+
60
+ function askYesNo(question, defaultYes = true) {
61
+ if (process.argv.includes('--yes')) {
62
+ return Promise.resolve(defaultYes);
63
+ }
64
+
65
+ const hint = defaultYes ? 'Y/n' : 'y/N';
66
+ return new Promise((resolve) => {
67
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
68
+ rl.question(`${COLORS.cyan}${question} (${hint}): ${COLORS.reset}`, (answer) => {
69
+ rl.close();
70
+ const normalized = answer.trim().toLowerCase();
71
+ if (!normalized) resolve(defaultYes);
72
+ else resolve(normalized === 'y' || normalized === 'yes');
73
+ });
74
+ });
75
+ }
76
+
77
+ function installPackages(workspaceRoot, packages, isDev = false) {
78
+ const entries = Object.entries(packages);
79
+ if (entries.length === 0) return;
80
+
81
+ const spec = entries.map(([name, version]) => `${name}@${version}`).join(' ');
82
+ const flag = isDev ? '--save-dev' : '--save';
83
+ console.log(`${COLORS.blue}[tailjng CLI] Installing ${isDev ? 'dev ' : ''}dependencies...${COLORS.reset}`);
84
+
85
+ try {
86
+ execSync(`npm install ${flag} ${spec}`, { cwd: workspaceRoot, stdio: 'inherit' });
87
+ } catch {
88
+ execSync(`npm install ${flag} ${spec}`, { cwd: workspaceRoot, stdio: 'inherit', shell: true });
89
+ }
90
+ }
91
+
92
+ function resolveTargetPath(workspaceRoot, appProject, relativePath, appRootRelative = false) {
93
+ const appRoot = path.join(workspaceRoot, appProject.root || '');
94
+
95
+ if (appRootRelative) {
96
+ return path.join(appRoot, relativePath);
97
+ }
98
+
99
+ if (relativePath.startsWith('projects/')) {
100
+ return path.join(workspaceRoot, relativePath);
101
+ }
102
+
103
+ return path.join(workspaceRoot, appProject.sourceRoot, relativePath.replace(/^src\//, ''));
104
+ }
105
+
106
+ async function runInitApp() {
107
+ const cwd = process.cwd();
108
+ const workspace = findAngularWorkspace(cwd);
109
+
110
+ if (!workspace) {
111
+ console.error(`${COLORS.red}[tailjng CLI] ERROR: angular.json not found. Run this inside an Angular project.${COLORS.reset}`);
112
+ process.exit(1);
113
+ }
114
+
115
+ const { workspaceRoot, angularJsonPath } = workspace;
116
+ const angularJson = readJson(angularJsonPath);
117
+ const apps = getApplicationProjects(angularJson);
118
+
119
+ if (apps.length === 0) {
120
+ console.error(`${COLORS.red}[tailjng CLI] ERROR: No application project found in angular.json.${COLORS.reset}`);
121
+ process.exit(1);
122
+ }
123
+
124
+ let selectedApp = apps[0];
125
+ if (apps.length > 1) {
126
+ console.log(`${COLORS.cyan}[tailjng CLI] Multiple applications found:${COLORS.reset}`);
127
+ apps.forEach((app, index) => console.log(` ${index + 1}. ${app.name}`));
128
+ const answer = await ask('Select application number', '1');
129
+ const index = Number.parseInt(answer, 10) - 1;
130
+ selectedApp = apps[index] || apps[0];
131
+ }
132
+
133
+ const { appRoot, srcRoot } = resolveAppPaths(workspaceRoot, selectedApp);
134
+ const buildOptions = getBuildOptions(angularJson, selectedApp.name);
135
+ const styleLanguage = detectStyleLanguage(buildOptions);
136
+ const primaryStylePath = getPrimaryStyleEntry(buildOptions);
137
+ const indexPath = path.join(workspaceRoot, buildOptions.index || path.join(selectedApp.sourceRoot, 'index.html'));
138
+ const appConfigPath = path.join(srcRoot, 'app', 'app.config.ts');
139
+ const appComponentTsPath = path.join(srcRoot, 'app', 'app.component.ts');
140
+ const appComponentHtmlPath = path.join(srcRoot, 'app', 'app.component.html');
141
+ const componentsPath = 'src/app/tailjng';
142
+
143
+ console.log(`\n${COLORS.bright}${COLORS.blue}[tailjng CLI] init:app${COLORS.reset}`);
144
+ console.log(`${COLORS.dim}Workspace: ${workspaceRoot}${COLORS.reset}`);
145
+ console.log(`${COLORS.dim}Application: ${selectedApp.name}${COLORS.reset}`);
146
+ console.log(`${COLORS.dim}Styles: ${styleLanguage} (${primaryStylePath})${COLORS.reset}\n`);
147
+
148
+ const socketUrl = await ask('API socket/base URL', 'http://localhost:3000');
149
+ const urlBase = await ask('API urlBase', `${socketUrl.replace(/\/$/, '')}/api/v1`);
150
+ const installDeps = await askYesNo('Install required npm packages?', true);
151
+ const withComponentsFlag = process.argv.includes('--with-components');
152
+ const installComponents = withComponentsFlag
153
+ ? true
154
+ : await askYesNo('Install base UI components now? (default: use add / install-all later)', false);
155
+ const overwrite = process.argv.includes('--yes')
156
+ ? true
157
+ : await askYesNo('Overwrite generated config files if they already exist?', false);
158
+
159
+ const packageJsonPath = path.join(workspaceRoot, 'package.json');
160
+ const packageJson = fileExists(packageJsonPath) ? readJson(packageJsonPath) : { dependencies: {}, devDependencies: {} };
161
+
162
+ if (!packageJson.dependencies?.tailjng) {
163
+ console.log(`${COLORS.yellow}[tailjng CLI] WARNING: tailjng is not in package.json dependencies.${COLORS.reset}`);
164
+ console.log(`${COLORS.yellow}Install it first: npm install tailjng${COLORS.reset}`);
165
+ }
166
+
167
+ if (installDeps) {
168
+ const missingRuntime = Object.fromEntries(getMissingPackages(packageJson, RUNTIME_PACKAGES));
169
+ const missingDev = Object.fromEntries(getMissingPackages(packageJson, DEV_PACKAGES));
170
+ installPackages(workspaceRoot, missingRuntime, false);
171
+ installPackages(workspaceRoot, missingDev, true);
172
+ }
173
+
174
+ const hasAppConfig = fileExists(appConfigPath);
175
+ const files = buildInitFiles({
176
+ styleLanguage,
177
+ primaryStylePath,
178
+ urlBase,
179
+ socketUrl,
180
+ componentsPath,
181
+ overwrite,
182
+ hasAppConfig,
183
+ });
184
+
185
+ let created = 0;
186
+ let skipped = 0;
187
+
188
+ function shouldWriteStyles(filePath) {
189
+ if (!fs.existsSync(filePath)) return true;
190
+ const content = fs.readFileSync(filePath, 'utf8');
191
+ return !content.includes('tailwindcss');
192
+ }
193
+
194
+ for (const file of files) {
195
+ const targetPath = resolveTargetPath(workspaceRoot, selectedApp, file.relativePath, file.appRootRelative);
196
+ const isPrimaryStyles = file.relativePath === primaryStylePath;
197
+ const forceWrite = isPrimaryStyles && shouldWriteStyles(targetPath);
198
+ const wrote = writeFileSafe(targetPath, file.content, overwrite || forceWrite);
199
+ if (wrote) {
200
+ created += 1;
201
+ console.log(`${COLORS.green}✔ Created ${path.relative(workspaceRoot, targetPath)}${COLORS.reset}`);
202
+ } else {
203
+ skipped += 1;
204
+ console.log(`${COLORS.yellow}↷ Skipped (exists) ${path.relative(workspaceRoot, targetPath)}${COLORS.reset}`);
205
+ }
206
+ }
207
+
208
+ if (hasAppConfig) {
209
+ const patched = patchAppConfig(appConfigPath);
210
+ if (patched.changed) {
211
+ console.log(`${COLORS.green}✔ Patched ${path.relative(workspaceRoot, appConfigPath)}${COLORS.reset}`);
212
+ }
213
+ }
214
+
215
+ const indexPatched = patchIndexHtml(indexPath);
216
+ if (indexPatched) {
217
+ console.log(`${COLORS.green}✔ Patched ${path.relative(workspaceRoot, indexPath)}${COLORS.reset}`);
218
+ }
219
+
220
+ const stylesPatched = ensureTailjngStylesInAngularJson(angularJsonPath, selectedApp.name, styleLanguage);
221
+ if (stylesPatched.changed) {
222
+ console.log(`${COLORS.green}✔ Added node_modules/tailjng/src/styles.css to angular.json${COLORS.reset}`);
223
+ }
224
+
225
+ if (installComponents) {
226
+ const previousCwd = process.cwd();
227
+ process.chdir(appRoot);
228
+ try {
229
+ for (const componentName of OPTIONAL_BASE_COMPONENTS) {
230
+ await addComponent(componentName, getComponentList());
231
+ }
232
+ patchAppComponentForAlerts(appComponentTsPath, appComponentHtmlPath);
233
+ console.log(`${COLORS.green}✔ Installed optional base components + alert shell${COLORS.reset}`);
234
+ } finally {
235
+ process.chdir(previousCwd);
236
+ }
237
+ }
238
+
239
+ console.log(`\n${COLORS.greenBright}${COLORS.bright}[tailjng CLI] init:app completed.${COLORS.reset}`);
240
+ console.log(`${COLORS.dim}Created: ${created} | Skipped: ${skipped}${COLORS.reset}`);
241
+ console.log(`\n${COLORS.cyan}Project ready. Install components when you need them:${COLORS.reset}`);
242
+ console.log(` ${COLORS.dim}One by one:${COLORS.reset} cd ${path.relative(workspaceRoot, appRoot)} && npx tailjng add button`);
243
+ console.log(` ${COLORS.dim}All at once:${COLORS.reset} cd ${path.relative(workspaceRoot, appRoot)} && npx tailjng install-all`);
244
+ console.log(` ${COLORS.dim}List:${COLORS.reset} npx tailjng list`);
245
+ console.log(`\n${COLORS.cyan}Then run:${COLORS.reset} ng serve ${selectedApp.name} -o\n`);
246
+ }
247
+
248
+ runInitApp().catch((error) => {
249
+ console.error(`${COLORS.red}[tailjng CLI] init:app failed:${COLORS.reset}`, error);
250
+ process.exit(1);
251
+ });