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.
- package/README.md +186 -32
- package/cli/component-manager.js +71 -20
- package/cli/execute/init-app.js +251 -0
- package/cli/file-operations.js +45 -29
- package/cli/index.js +66 -15
- package/cli/settings/components-list.js +4 -147
- package/cli/settings/header-generator.js +9 -10
- package/cli/settings/lib-utils.js +89 -0
- package/cli/settings/overwrite-policy.js +18 -0
- package/cli/settings/path-utils.js +14 -29
- package/cli/settings/project-utils.js +220 -0
- package/cli/settings/prompt-utils.js +66 -5
- package/cli/templates/app.generator.js +382 -0
- package/fesm2022/tailjng.mjs +232 -66
- package/fesm2022/tailjng.mjs.map +1 -1
- package/lib/services/static/colors.service.d.ts +17 -0
- package/lib/services/transformer/transform.service.d.ts +3 -3
- package/package.json +1 -1
- package/public-api.d.ts +2 -0
- package/registry/components.json +164 -0
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.css +17 -0
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +83 -51
- package/src/lib/components/alert/alert-dialog/dialog-alert.component.ts +85 -53
- package/src/lib/components/alert/alert-toast/toast-alert.component.css +38 -4
- package/src/lib/components/alert/alert-toast/toast-alert.component.html +72 -40
- package/src/lib/components/alert/alert-toast/toast-alert.component.ts +84 -19
- package/src/lib/components/badge/badge.component.ts +1 -2
- package/src/lib/components/button/button.component.css +14 -0
- package/src/lib/components/button/button.component.html +17 -17
- package/src/lib/components/button/button.component.ts +139 -48
- package/src/lib/components/card/card-crud-complete/complete-crud-card.component.ts +5 -1
- package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +1 -1
- package/src/lib/components/filter/filter-complete/complete-filter.component.html +1 -1
- package/src/lib/components/form/form-sidebar/sidebar-form.component.html +130 -97
- package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +3 -2
- package/src/lib/components/input/input/input.component.css +35 -0
- package/src/lib/components/input/input/input.component.html +37 -27
- package/src/lib/components/input/input/input.component.ts +1 -0
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +56 -0
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +12 -0
- package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.ts +41 -0
- package/src/lib/components/select/select-dropdown/dropdown-select.component.css +4 -0
- package/src/lib/components/select/select-dropdown/dropdown-select.component.html +1 -1
- package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +3 -3
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.css +4 -0
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +1 -1
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +30 -20
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +506 -172
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.scss +92 -0
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +141 -7
- package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.builder.ts +116 -0
- package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.helper.ts +43 -0
- package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.types.ts +39 -0
- package/src/lib/components/table/table-crud-complete/index.ts +3 -0
- package/src/lib/components/toggle-radio/toggle-radio.component.css +4 -0
- package/src/lib/components/toggle-radio/toggle-radio.component.html +4 -4
- package/src/lib/components/toggle-radio/toggle-radio.component.ts +15 -6
- package/src/lib/components/tooltip/tooltip.service.ts +0 -30
- package/tailjng-0.1.0.tgz +0 -0
- package/src/lib/components/color/colors.service.ts +0 -187
package/README.md
CHANGED
|
@@ -1,63 +1,217 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tailjng
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
---
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
53
|
+
npm install tailjng
|
|
11
54
|
```
|
|
12
55
|
|
|
13
|
-
|
|
56
|
+
Desde la raíz de tu app Angular:
|
|
14
57
|
|
|
15
58
|
```bash
|
|
16
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
To build the library, run:
|
|
64
|
+
Opcional — alerts y mode-toggle en el mismo paso:
|
|
22
65
|
|
|
23
66
|
```bash
|
|
24
|
-
|
|
67
|
+
npx tailjng init:app --with-components
|
|
25
68
|
```
|
|
26
69
|
|
|
27
|
-
|
|
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
|
-
###
|
|
97
|
+
### Estilos
|
|
30
98
|
|
|
31
|
-
|
|
99
|
+
`init:app` configura `@import "tailwindcss"`, variables `@theme` y añade en `angular.json`:
|
|
32
100
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```
|
|
101
|
+
```json
|
|
102
|
+
"node_modules/tailjng/src/styles.css"
|
|
103
|
+
```
|
|
37
104
|
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
npm publish
|
|
41
|
-
```
|
|
105
|
+
---
|
|
42
106
|
|
|
43
|
-
##
|
|
107
|
+
## Uso en código
|
|
44
108
|
|
|
45
|
-
|
|
109
|
+
### Servicios (npm)
|
|
46
110
|
|
|
47
|
-
```
|
|
48
|
-
|
|
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
|
-
|
|
204
|
+
---
|
|
52
205
|
|
|
53
|
-
|
|
206
|
+
## Publicación e instalación
|
|
54
207
|
|
|
55
208
|
```bash
|
|
56
|
-
|
|
209
|
+
npm view tailjng version
|
|
210
|
+
npm install tailjng@latest
|
|
57
211
|
```
|
|
58
212
|
|
|
59
|
-
|
|
213
|
+
---
|
|
60
214
|
|
|
61
|
-
##
|
|
215
|
+
## Desarrollo del paquete (mantenedores)
|
|
62
216
|
|
|
63
|
-
|
|
217
|
+
Este archivo se publica en npm. El runbook del monorepo está en **[DEV.md](../../DEV.md)** (raíz del workspace).
|
package/cli/component-manager.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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}
|
|
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
|
-
|
|
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
|
|
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}
|
|
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
|
+
});
|