@barcidev/ngx-autogen 0.1.70 → 0.1.72
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 +339 -302
- package/README_ES.md +385 -0
- package/package.json +3 -1
- package/src/angular/component/index.js +136 -8
- package/src/angular/component/schema.json +20 -0
- package/src/angular/component/types/types.d.ts +4 -0
- package/src/ngrx/store/files/state/store/__name@dasherize__.store.ts.template +8 -9
- package/src/ngrx/store/index.js +19 -22
- package/README.es.md +0 -352
- package/src/angular/component/files/__name@dasherize__.component.css.template +0 -0
- package/src/angular/component/files/__name@dasherize__.component.html.template +0 -20
- package/src/angular/component/files/__name@dasherize__.component.spec.ts.template +0 -25
- package/src/angular/component/files/__name@dasherize__.component.ts.template +0 -17
package/README_ES.md
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./logo.png" alt="ngx-autogen logo" width="120">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="README.md">🇺🇸 English</a> | <a href="README_ES.md">🇪🇸 Español</a>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@barcidev/ngx-autogen"><img src="https://img.shields.io/npm/v/@barcidev/ngx-autogen?style=for-the-badge&color=007acc&labelColor=010101" alt="npm version"></a>
|
|
11
|
+
<a href="https://github.com/jpalacio09/ngx-autogen/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@barcidev/ngx-autogen?style=for-the-badge&color=green&labelColor=010101" alt="license"></a>
|
|
12
|
+
<img src="https://img.shields.io/badge/Angular-17%2B-DD0031?style=for-the-badge&logo=angular&labelColor=010101" alt="Angular 17+">
|
|
13
|
+
<img src="https://img.shields.io/badge/NgRx%20Signals-17%2B-BA2BD2?style=for-the-badge&logo=ngrx&labelColor=010101" alt="NgRx Signals 17+">
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<b>⚡ Schematics de Angular que generan entidades completas de NgRx Signal Store, scopes de i18n para Transloco y componentes standalone en segundos. ⚡</b>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📖 Tabla de Contenidos
|
|
23
|
+
|
|
24
|
+
- [Resumen](#resumen)
|
|
25
|
+
- [Requisitos](#requisitos)
|
|
26
|
+
- [Instalación](#instalación)
|
|
27
|
+
- [Vía ng add (recomendado)](#vía-ng-add-recomendado)
|
|
28
|
+
- [Vía npm](#vía-npm)
|
|
29
|
+
- [Exports de Runtime](#exports-de-runtime)
|
|
30
|
+
- [Referencia de Schematics](#referencia-de-schematics)
|
|
31
|
+
- [ng-add](#ng-add)
|
|
32
|
+
- [app-store](#app-store)
|
|
33
|
+
- [app-i18n](#app-i18n)
|
|
34
|
+
- [app-component](#app-component)
|
|
35
|
+
- [Licencia](#licencia)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🌟 Resumen
|
|
40
|
+
|
|
41
|
+
`@barcidev/ngx-autogen` es una colección de schematics para Angular diseñada para eliminar el scaffolding repetitivo al construir aplicaciones empresariales. Genera código listo para producción enfocado en tres áreas principales:
|
|
42
|
+
|
|
43
|
+
- **NgRx Signal Store** (`app-store`) - Un store de señales CRUD completo con gestión de entidades, estado de pagación, seguimiento de estado y una capa de servicio y modelo. Basado en `@ngrx/signals` y `@ngrx/signals/entities`.
|
|
44
|
+
- **Transloco i18n** (`app-i18n`) - Archivos de traducción con integración tipada de Transloco vía `@barcidev/typed-transloco`, registro automático de providers y actualización global del mapa de tipos.
|
|
45
|
+
- **Componente Angular** (`app-component`) - Generación de componentes standalone vía `@schematics/angular` con cableado opcional automático para el store e i18n.
|
|
46
|
+
|
|
47
|
+
La librería también incluye utilidades de runtime bajo `@barcidev/ngx-autogen/entity` que proporcionan características reutilizables para NgRx Signal Store: `withEntityStatus`, `withPagination` y ayudantes para modelos tipados.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 📋 Requisitos
|
|
52
|
+
|
|
53
|
+
| Dependencia | Versión Mínima |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `@angular/core` | `>=17.0.0` |
|
|
56
|
+
| `@angular/common` | `>=17.0.0` |
|
|
57
|
+
| `@angular/forms` | `>=17.0.0` |
|
|
58
|
+
| `@ngrx/signals` | `>=17.0.0` |
|
|
59
|
+
| Node.js | `>=16.0.0` |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🚀 Instalación
|
|
64
|
+
|
|
65
|
+
### Vía ng add (recomendado)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
ng add @barcidev/ngx-autogen
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Este comando realiza las siguientes acciones automáticamente:
|
|
72
|
+
|
|
73
|
+
1. **Mueve la librería a `devDependencies`** - Si se instaló como una dependencia de producción, se reubica en `devDependencies` en el `package.json`.
|
|
74
|
+
2. **Registra la colección de schematics** - Añade `@barcidev/ngx-autogen` a `cli.schematicCollections` en `angular.json`, habilitando alias cortos como `ng g app-store`.
|
|
75
|
+
3. **Almacena la configuración global** - Guarda el idioma predeterminado seleccionado (`lang`) bajo `schematics["@barcidev/ngx-autogen:all"]` en `angular.json` para que todos los schematics lo hereden.
|
|
76
|
+
|
|
77
|
+
**Prompt:**
|
|
78
|
+
|
|
79
|
+
| Pregunta | Por defecto |
|
|
80
|
+
|---|---|
|
|
81
|
+
| ¿Cuál es el idioma predeterminado para la aplicación (en, es)? | `en` |
|
|
82
|
+
|
|
83
|
+
### Vía npm
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm install @barcidev/ngx-autogen --save-dev
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
> [!NOTE]
|
|
90
|
+
> Al instalar vía npm, debe registrar manualmente la colección en `angular.json` y usar el nombre completo del schematic (ej: `ng generate @barcidev/ngx-autogen:app-store`).
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 🛠️ Exports de Runtime
|
|
95
|
+
|
|
96
|
+
La librería exporta características reutilizables para NgRx Signal Store bajo el punto de entrada `@barcidev/ngx-autogen/entity`:
|
|
97
|
+
|
|
98
|
+
| Export | Descripción |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `withEntityStatus()` | Característica del Signal Store que añade seguimiento de carga, errores y estado de selección para las entidades. |
|
|
101
|
+
| `withPagination()` | Característica del Signal Store que añade estado de paginación (página, tamaño, total). |
|
|
102
|
+
| `RequestConfig<TPayload, TResponse>` | Tipo genérico para configuraciones de métodos del store con callbacks `payload`, `onSuccess` y `onError`. |
|
|
103
|
+
| `FormGroupType<T>` | Tipo de utilidad que mapea una interfaz a sus tipos de control de `FormGroup` de Angular. |
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { withEntityStatus, withPagination, RequestConfig, FormGroupType } from '@barcidev/ngx-autogen/entity';
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 📖 Referencia de Schematics
|
|
112
|
+
|
|
113
|
+
### `ng-add`
|
|
114
|
+
|
|
115
|
+
**Descripción**: Schematic de ng add para ngx-autogen. Configura el espacio de trabajo para usar todos los schematics de la librería.
|
|
116
|
+
|
|
117
|
+
**Uso**
|
|
118
|
+
```bash
|
|
119
|
+
ng add @barcidev/ngx-autogen
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Opciones**
|
|
123
|
+
|
|
124
|
+
| Opción | Tipo | Por defecto | Requerido | Descripción |
|
|
125
|
+
|---|---|---|---|---|
|
|
126
|
+
| `lang` | `string` | `en` | No | El idioma predeterminado para la aplicación (`en`, `es`). |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### `app-store`
|
|
131
|
+
|
|
132
|
+
> Alias: `ng g app-store` (cuando la colección está registrada vía `ng add`)
|
|
133
|
+
|
|
134
|
+
**Descripción**: Añade NgRx Store a la aplicación Angular con soporte para señales. Genera un signal store CRUD completo, servicio y capa de modelo para una entidad dada.
|
|
135
|
+
|
|
136
|
+
**Uso**
|
|
137
|
+
```bash
|
|
138
|
+
# Forma corta (requiere ng add):
|
|
139
|
+
ng g app-store --name product
|
|
140
|
+
|
|
141
|
+
# Forma completa:
|
|
142
|
+
ng generate @barcidev/ngx-autogen:app-store --name product
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Opciones**
|
|
146
|
+
|
|
147
|
+
| Opción | Tipo | Por defecto | Requerido | Descripción |
|
|
148
|
+
|---|---|---|---|---|
|
|
149
|
+
| `name` | `string` | - | **Sí** | Nombre de la entidad. Se usa para derivar todos los nombres de clases, archivos y métodos del store. |
|
|
150
|
+
| `path` | `string` | *(cwd)* | No | Ruta de destino. Por defecto es el directorio de trabajo actual relativo a `src/`. |
|
|
151
|
+
| `grouped` | `boolean` | `false` | No | Indica si los archivos deben agruparse en subcarpetas `models/` y `services/`. |
|
|
152
|
+
| `pk` | `string` | `id` | No | El nombre de la clave primaria predeterminada (ej: `id`, `cod`, `uuid`). |
|
|
153
|
+
| `lang` | `string` | *(global)* | No | El idioma predeterminado para la aplicación (`en`, `es`). Heredado de la configuración de `ng add`. |
|
|
154
|
+
| `isProvideInRoot` | `boolean` | `false` | No | Indica si el store y el servicio deben usar `providedIn: 'root'`. |
|
|
155
|
+
|
|
156
|
+
**Qué genera**
|
|
157
|
+
|
|
158
|
+
Para un nombre de entidad dado, este schematic crea:
|
|
159
|
+
|
|
160
|
+
- `<name>.store.ts` - Signal Store de NgRx con métodos CRUD completos (`add`, `load`, `update`, `remove`, `select`), configuración de entidad, selectores computados y hook `onInit`.
|
|
161
|
+
- `<name>.service.ts` - Servicio inyectable con métodos base para `add$`, `get$` (pluralizado), `update$` y `remove$`.
|
|
162
|
+
- `<name>.model.ts` - Interfaces TypeScript: `Add<Name>`, `<Name>Dto`, `Update<Name>`, `Add<Name>Form` y `<Name>Request`.
|
|
163
|
+
- `state/index.ts` - Archivo barrel con re-exports (creado o actualizado).
|
|
164
|
+
|
|
165
|
+
También:
|
|
166
|
+
- Instala `@ngrx/signals` si no está presente (coincidiendo con la versión mayor de Angular).
|
|
167
|
+
- Configura el alias de ruta `@shared-state/*` en `tsconfig.app.json`.
|
|
168
|
+
- Guarda los valores predeterminados de `pk` y `lang` en `angular.json`.
|
|
169
|
+
|
|
170
|
+
**Ejemplos**
|
|
171
|
+
|
|
172
|
+
**Mínimo - Entidad con valores por defecto:**
|
|
173
|
+
```bash
|
|
174
|
+
ng g app-store --name product
|
|
175
|
+
```
|
|
176
|
+
```
|
|
177
|
+
src/app/state/
|
|
178
|
+
index.ts
|
|
179
|
+
product/
|
|
180
|
+
product.model.ts
|
|
181
|
+
product.service.ts
|
|
182
|
+
product.store.ts
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Diseño agrupado con PK personalizada:**
|
|
186
|
+
```bash
|
|
187
|
+
ng g app-store --name invoice --grouped --pk cod
|
|
188
|
+
```
|
|
189
|
+
```
|
|
190
|
+
src/app/state/
|
|
191
|
+
index.ts
|
|
192
|
+
invoice/
|
|
193
|
+
models/
|
|
194
|
+
invoice.model.ts
|
|
195
|
+
services/
|
|
196
|
+
invoice.service.ts
|
|
197
|
+
invoice.store.ts
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Proporcionado en root con pluralización en español:**
|
|
201
|
+
```bash
|
|
202
|
+
ng g app-store --name usuario --isProvideInRoot --lang es
|
|
203
|
+
```
|
|
204
|
+
```
|
|
205
|
+
src/app/state/
|
|
206
|
+
index.ts
|
|
207
|
+
usuario/
|
|
208
|
+
usuario.model.ts # interfaces con pk: id
|
|
209
|
+
usuario.service.ts # @Injectable({ providedIn: 'root' })
|
|
210
|
+
usuario.store.ts # signalStore({ providedIn: 'root' }, ...) - los métodos usan "Usuarios" (plural en español)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### `app-i18n`
|
|
216
|
+
|
|
217
|
+
> Alias: `ng g app-i18n` (cuando la colección está registrada vía `ng add`)
|
|
218
|
+
|
|
219
|
+
**Descripción**: Añade Transloco a la aplicación Angular. Genera un archivo de traducción con scope i18n y lo conecta al componente objetivo.
|
|
220
|
+
|
|
221
|
+
**Uso**
|
|
222
|
+
```bash
|
|
223
|
+
# Forma corta:
|
|
224
|
+
ng g app-i18n --name dashboard
|
|
225
|
+
|
|
226
|
+
# Forma completa:
|
|
227
|
+
ng generate @barcidev/ngx-autogen:app-i18n --name dashboard
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Opciones**
|
|
231
|
+
|
|
232
|
+
| Opción | Tipo | Por defecto | Requerido | Descripción |
|
|
233
|
+
|---|---|---|---|---|
|
|
234
|
+
| `name` | `string` | *(auto-detect)* | No | Nombre del componente. Se detecta automáticamente del archivo `.component.ts` en el directorio actual si no se proporciona. |
|
|
235
|
+
| `path` | `string` | *(cwd)* | No | Ruta de destino. Por defecto es el directorio de trabajo actual relativo a `src/`. |
|
|
236
|
+
| `lang` | `string` | *(global)* | No | Idioma para la pluralización (`en` para inglés, `es` para español). |
|
|
237
|
+
|
|
238
|
+
**Qué genera**
|
|
239
|
+
|
|
240
|
+
- `<name>.i18n.ts` - Archivo de scope de traducción con objetos `en-US` y `es-CO` registrados vía `TranslocoUtils.createScopeConfig`.
|
|
241
|
+
|
|
242
|
+
También:
|
|
243
|
+
- Ejecuta `@barcidev/typed-transloco:ng-add` si Transloco aún no está configurado en el proyecto.
|
|
244
|
+
- Añade `provideTranslocoScopeWrapper` al array de `providers` del componente.
|
|
245
|
+
- Añade `AppTypedTranslocoDirective` al array de `imports` del componente.
|
|
246
|
+
- Actualiza el mapa de tipos global `app.i18n.ts` con el nuevo scope.
|
|
247
|
+
|
|
248
|
+
**Ejemplos**
|
|
249
|
+
|
|
250
|
+
**Auto-detectar componente en el directorio actual:**
|
|
251
|
+
```bash
|
|
252
|
+
cd src/app/features/dashboard
|
|
253
|
+
ng g app-i18n
|
|
254
|
+
```
|
|
255
|
+
```
|
|
256
|
+
src/app/features/dashboard/
|
|
257
|
+
dashboard.i18n.ts # nuevo
|
|
258
|
+
dashboard.component.ts # actualizado con providers + imports
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Nombre y ruta explícitos:**
|
|
262
|
+
```bash
|
|
263
|
+
ng g app-i18n --name settings --path src/app/features/settings
|
|
264
|
+
```
|
|
265
|
+
```
|
|
266
|
+
src/app/features/settings/
|
|
267
|
+
settings.i18n.ts
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Con pluralización en español:**
|
|
271
|
+
```bash
|
|
272
|
+
ng g app-i18n --name usuario --lang es
|
|
273
|
+
```
|
|
274
|
+
```
|
|
275
|
+
src/app/features/usuario/
|
|
276
|
+
usuario.i18n.ts # clave de scope: "usuario", la pluralización usa reglas del español
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### `app-component`
|
|
282
|
+
|
|
283
|
+
> Alias: `ng g app-component` (cuando la colección está registrada vía `ng add`)
|
|
284
|
+
|
|
285
|
+
**Descripción**: Genera un componente de Angular con soporte opcional para i18n y NgRx. Delega en `@schematics/angular:component` para la generación base, luego encadena opcionalmente los schematics `app-store` y `app-i18n`.
|
|
286
|
+
|
|
287
|
+
**Uso**
|
|
288
|
+
```bash
|
|
289
|
+
# Forma corta:
|
|
290
|
+
ng g app-component --name product-list
|
|
291
|
+
|
|
292
|
+
# Forma completa:
|
|
293
|
+
ng generate @barcidev/ngx-autogen:app-component --name product-list
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Opciones**
|
|
297
|
+
|
|
298
|
+
| Opción | Tipo | Por defecto | Requerido | Descripción |
|
|
299
|
+
|---|---|---|---|---|
|
|
300
|
+
| `name` | `string` | - | **Sí** | Nombre del componente. |
|
|
301
|
+
| `store` | `string` | - | No | Indica si se debe crear un store para el componente. Enum: `Yes`, `No`. |
|
|
302
|
+
| `i18n` | `string` | - | No | Indica si se debe crear un archivo i18n para el componente. Enum: `Yes`, `No`. |
|
|
303
|
+
| `pk` | `string` | `id` | No | El nombre de la clave primaria predeterminada. Solo se usa cuando `store` es `Yes`. |
|
|
304
|
+
| `isProvideInRoot` | `boolean` | `false` | No | Indica si el store y el servicio deben proporcionarse en root. Solo se usa cuando `store` es `Yes`. |
|
|
305
|
+
| `path` | `string` | *(cwd)* | No | Ruta de destino. |
|
|
306
|
+
| `lang` | `string` | *(global)* | No | Idioma para la pluralización (`en`, `es`). |
|
|
307
|
+
| `style` | `string` | `css` | No | La extensión de archivo o preprocesador a usar para los archivos de estilo. |
|
|
308
|
+
| `skipTests` | `boolean` | `false` | No | No crear archivos de prueba `spec.ts`. |
|
|
309
|
+
| `inlineStyle` | `boolean` | `false` | No | Incluir estilos inline en el archivo `.ts` del componente. |
|
|
310
|
+
| `inlineTemplate` | `boolean` | `false` | No | Incluir plantilla inline en el archivo `.ts` del componente. |
|
|
311
|
+
|
|
312
|
+
**Prompts interactivos** (cuando `store` es `Yes`):
|
|
313
|
+
|
|
314
|
+
| Pregunta | Por defecto |
|
|
315
|
+
|---|---|
|
|
316
|
+
| What is the name of the entity store to create? | *(nombre del componente)* |
|
|
317
|
+
| What is the name of the default Primary Key? | `id` |
|
|
318
|
+
| Should the store and service be provided in root? | `No` |
|
|
319
|
+
|
|
320
|
+
**Qué genera**
|
|
321
|
+
|
|
322
|
+
El componente base es generado por el schematic oficial `@schematics/angular:component` con `standalone: true`. Dependiendo de las opciones:
|
|
323
|
+
|
|
324
|
+
- **Con `store: Yes`**: Encadena el schematic `app-store` dentro del directorio del componente, inyecta el store en la clase del componente, añade `JsonPipe` a los imports, conecta la propiedad computada `data$` y configura los `providers` con `provide<Name>Store()` (a menos que `isProvideInRoot` sea true). También actualiza el `.spec.ts` con providers de mock para el store.
|
|
325
|
+
- **Con `i18n: Yes`**: Encadena el schematic `app-i18n` dentro del directorio del componente.
|
|
326
|
+
|
|
327
|
+
**Ejemplos**
|
|
328
|
+
|
|
329
|
+
**Mínimo - Solo componente:**
|
|
330
|
+
```bash
|
|
331
|
+
ng g app-component --name dashboard --store No --i18n No
|
|
332
|
+
```
|
|
333
|
+
```
|
|
334
|
+
src/app/dashboard/
|
|
335
|
+
dashboard.component.ts
|
|
336
|
+
dashboard.component.html
|
|
337
|
+
dashboard.component.css
|
|
338
|
+
dashboard.component.spec.ts
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Componente con store e i18n:**
|
|
342
|
+
```bash
|
|
343
|
+
ng g app-component --name product-list --store Yes --i18n Yes --style scss
|
|
344
|
+
```
|
|
345
|
+
```
|
|
346
|
+
src/app/product-list/
|
|
347
|
+
product-list.component.ts # inyecta ProductListStore, data$, JsonPipe, providers
|
|
348
|
+
product-list.component.html # <div *typedTransloco="let t; prefix:'productList'">
|
|
349
|
+
product-list.component.scss
|
|
350
|
+
product-list.component.spec.ts # TestBed configurado con mock del store
|
|
351
|
+
product-list.i18n.ts
|
|
352
|
+
state/
|
|
353
|
+
index.ts
|
|
354
|
+
product-list/
|
|
355
|
+
product-list.model.ts
|
|
356
|
+
product-list.service.ts
|
|
357
|
+
product-list.store.ts
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Componente con nombre de store personalizado y provider en root:**
|
|
361
|
+
```bash
|
|
362
|
+
ng g app-component --name user-profile --store Yes --i18n No --pk uuid --isProvideInRoot
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Cuando se le pida el nombre del entity store, ingrese `user` en lugar del predeterminado `user-profile`:
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
src/app/user-profile/
|
|
369
|
+
user-profile.component.ts # inyecta UserStore (no UserProfileStore)
|
|
370
|
+
user-profile.component.html
|
|
371
|
+
user-profile.component.css
|
|
372
|
+
user-profile.component.spec.ts
|
|
373
|
+
state/
|
|
374
|
+
index.ts
|
|
375
|
+
user/
|
|
376
|
+
user.model.ts # campo pk: uuid
|
|
377
|
+
user.service.ts # @Injectable({ providedIn: 'root' })
|
|
378
|
+
user.store.ts # signalStore({ providedIn: 'root' }, ...)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## ⚖️ Licencia
|
|
384
|
+
|
|
385
|
+
[MIT](./LICENSE) - Jorge Palacio Barcinilla
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barcidev/ngx-autogen",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.72",
|
|
4
4
|
"description": "A collection of Angular schematics for essential functionalities.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"dist/lib/**/*.js",
|
|
19
19
|
"dist/lib/**/*.d.ts",
|
|
20
20
|
"README.md",
|
|
21
|
+
"README_ES.md",
|
|
21
22
|
"LICENSE"
|
|
22
23
|
],
|
|
23
24
|
"keywords": [
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"@angular-devkit/schematics": "^21.2.0",
|
|
55
56
|
"@barcidev/typed-transloco": "^0.0.15",
|
|
56
57
|
"@schematics/angular": "^21.2.0",
|
|
58
|
+
"ts-morph": "^28.0.0",
|
|
57
59
|
"typescript": "~5.9.2"
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
@@ -13,7 +13,7 @@ exports.component = component;
|
|
|
13
13
|
const schematics_1 = require("@angular-devkit/schematics");
|
|
14
14
|
const workspace_1 = require("@schematics/angular/utility/workspace");
|
|
15
15
|
const path_1 = require("path");
|
|
16
|
-
const
|
|
16
|
+
const ts_morph_1 = require("ts-morph");
|
|
17
17
|
const prompts_1 = require("../../common/prompts");
|
|
18
18
|
function component(options) {
|
|
19
19
|
return (tree) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -31,7 +31,17 @@ function component(options) {
|
|
|
31
31
|
}
|
|
32
32
|
const context = resolveComponentContext(workspace, options);
|
|
33
33
|
return (0, schematics_1.chain)([
|
|
34
|
-
|
|
34
|
+
(0, schematics_1.externalSchematic)("@schematics/angular", "component", {
|
|
35
|
+
name: context.options.name,
|
|
36
|
+
path: context.movePath,
|
|
37
|
+
project: context.projectName,
|
|
38
|
+
style: options.style,
|
|
39
|
+
skipTests: options.skipTests,
|
|
40
|
+
inlineStyle: options.inlineStyle,
|
|
41
|
+
inlineTemplate: options.inlineTemplate,
|
|
42
|
+
standalone: true, // Forzar standalone true como estándar moderno
|
|
43
|
+
}),
|
|
44
|
+
injectStoreAndI18n(context),
|
|
35
45
|
options.i18n === "Yes"
|
|
36
46
|
? (0, schematics_1.schematic)("app-i18n", {
|
|
37
47
|
name: context.options.name,
|
|
@@ -76,11 +86,129 @@ function resolveComponentContext(workspace, options) {
|
|
|
76
86
|
entityName: schematics_1.strings.classify(options.name),
|
|
77
87
|
};
|
|
78
88
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
function injectStoreAndI18n(ctx) {
|
|
90
|
+
return (tree) => {
|
|
91
|
+
const { options, movePath, nameDash } = ctx;
|
|
92
|
+
const storeName = options.storeName ? schematics_1.strings.classify(options.storeName) : '';
|
|
93
|
+
const storeVar = options.storeName ? schematics_1.strings.camelize(options.storeName) : '';
|
|
94
|
+
const componentName = schematics_1.strings.classify(options.name);
|
|
95
|
+
const tsPath = (0, path_1.join)(movePath, nameDash, `${nameDash}.component.ts`);
|
|
96
|
+
const project = new ts_morph_1.Project({ manipulationSettings: { quoteKind: ts_morph_1.QuoteKind.Single } });
|
|
97
|
+
if (options.store === 'Yes' && tree.exists(tsPath)) {
|
|
98
|
+
const sourceFile = project.createSourceFile('temp.ts', tree.read(tsPath).toString());
|
|
99
|
+
sourceFile.addImportDeclaration({
|
|
100
|
+
moduleSpecifier: './state',
|
|
101
|
+
namedImports: options.isProvideInRoot ? [`${storeName}Store`] : [`${storeName}Store`, `provide${storeName}Store`]
|
|
102
|
+
});
|
|
103
|
+
const hasJsonPipe = sourceFile.getImportDeclarations().some(imp => imp.getNamedImports().some(n => n.getName() === 'JsonPipe'));
|
|
104
|
+
if (!hasJsonPipe) {
|
|
105
|
+
sourceFile.addImportDeclaration({
|
|
106
|
+
moduleSpecifier: '@angular/common',
|
|
107
|
+
namedImports: ['JsonPipe']
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const hasInject = sourceFile.getImportDeclarations().some(imp => imp.getNamedImports().some(n => n.getName() === 'inject'));
|
|
111
|
+
if (!hasInject) {
|
|
112
|
+
const coreImport = sourceFile.getImportDeclaration('@angular/core');
|
|
113
|
+
if (coreImport) {
|
|
114
|
+
coreImport.addNamedImport('inject');
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
sourceFile.addImportDeclaration({
|
|
118
|
+
moduleSpecifier: '@angular/core',
|
|
119
|
+
namedImports: ['inject']
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const classDecl = sourceFile.getClass(`${componentName}Component`);
|
|
124
|
+
if (classDecl) {
|
|
125
|
+
classDecl.insertProperty(0, {
|
|
126
|
+
name: `_${storeVar}Store`,
|
|
127
|
+
initializer: `inject(${storeName}Store)`,
|
|
128
|
+
scope: ts_morph_1.Scope.Private
|
|
129
|
+
});
|
|
130
|
+
classDecl.insertProperty(1, {
|
|
131
|
+
name: `data$`,
|
|
132
|
+
initializer: `this._${storeVar}Store.entities()`
|
|
133
|
+
});
|
|
134
|
+
const componentDecorator = classDecl.getDecorator('Component');
|
|
135
|
+
if (componentDecorator) {
|
|
136
|
+
const objExpr = componentDecorator.getArguments()[0];
|
|
137
|
+
if (objExpr && objExpr.getKind() === ts_morph_1.SyntaxKind.ObjectLiteralExpression) {
|
|
138
|
+
const obj = objExpr.asKindOrThrow(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
|
|
139
|
+
const importsProp = obj.getProperty('imports');
|
|
140
|
+
if (importsProp && importsProp.getKind() === ts_morph_1.SyntaxKind.PropertyAssignment) {
|
|
141
|
+
const arr = importsProp.asKindOrThrow(ts_morph_1.SyntaxKind.PropertyAssignment).getInitializerIfKind(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
|
|
142
|
+
if (arr) {
|
|
143
|
+
arr.addElement('JsonPipe');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
obj.addPropertyAssignment({ name: 'imports', initializer: '[JsonPipe]' });
|
|
148
|
+
}
|
|
149
|
+
if (!options.isProvideInRoot) {
|
|
150
|
+
const providersProp = obj.getProperty('providers');
|
|
151
|
+
if (providersProp && providersProp.getKind() === ts_morph_1.SyntaxKind.PropertyAssignment) {
|
|
152
|
+
const arr = providersProp.asKindOrThrow(ts_morph_1.SyntaxKind.PropertyAssignment).getInitializerIfKind(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
|
|
153
|
+
if (arr) {
|
|
154
|
+
arr.addElement(`...provide${storeName}Store()`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
obj.addPropertyAssignment({ name: 'providers', initializer: `[...provide${storeName}Store()]` });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
tree.overwrite(tsPath, sourceFile.getFullText());
|
|
165
|
+
}
|
|
166
|
+
if (!options.inlineTemplate) {
|
|
167
|
+
const htmlPath = (0, path_1.join)(movePath, nameDash, `${nameDash}.component.html`);
|
|
168
|
+
if (tree.exists(htmlPath)) {
|
|
169
|
+
let htmlContent = `<div>\n <h1>${componentName} Works!</h1>\n`;
|
|
170
|
+
if (options.store === 'Yes') {
|
|
171
|
+
htmlContent += ` <pre>{{ data$() | json }}</pre>\n`;
|
|
172
|
+
}
|
|
173
|
+
htmlContent += `</div>`;
|
|
174
|
+
if (options.i18n === 'Yes') {
|
|
175
|
+
htmlContent = `<div *typedTransloco="let t; prefix:'${schematics_1.strings.camelize(options.name)}'">\n <h1>{{ t('title') }}</h1>\n`;
|
|
176
|
+
if (options.store === 'Yes') {
|
|
177
|
+
htmlContent += ` <pre>{{ data$() | json }}</pre>\n`;
|
|
178
|
+
}
|
|
179
|
+
htmlContent += `</div>`;
|
|
180
|
+
}
|
|
181
|
+
tree.overwrite(htmlPath, htmlContent);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const specPath = (0, path_1.join)(movePath, nameDash, `${nameDash}.component.spec.ts`);
|
|
185
|
+
if (options.store === 'Yes' && !options.skipTests && tree.exists(specPath)) {
|
|
186
|
+
const specSource = project.createSourceFile('spec.ts', tree.read(specPath).toString());
|
|
187
|
+
specSource.addImportDeclaration({
|
|
188
|
+
moduleSpecifier: './state',
|
|
189
|
+
namedImports: [`${storeName}Store`]
|
|
190
|
+
});
|
|
191
|
+
const callExprs = specSource.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression);
|
|
192
|
+
const configTestBed = callExprs.find(c => c.getExpression().getText() === 'TestBed.configureTestingModule');
|
|
193
|
+
if (configTestBed) {
|
|
194
|
+
const objArg = configTestBed.getArguments()[0];
|
|
195
|
+
if (objArg && objArg.getKind() === ts_morph_1.SyntaxKind.ObjectLiteralExpression) {
|
|
196
|
+
const obj = objArg.asKindOrThrow(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
|
|
197
|
+
const providersProp = obj.getProperty('providers');
|
|
198
|
+
if (providersProp && providersProp.getKind() === ts_morph_1.SyntaxKind.PropertyAssignment) {
|
|
199
|
+
const arr = providersProp.asKindOrThrow(ts_morph_1.SyntaxKind.PropertyAssignment).getInitializerIfKind(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
|
|
200
|
+
if (arr) {
|
|
201
|
+
arr.addElement(`{ provide: ${storeName}Store, useValue: { entities: () => [] } }`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
obj.addPropertyAssignment({ name: 'providers', initializer: `[{ provide: ${storeName}Store, useValue: { entities: () => [] } }]` });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
tree.overwrite(specPath, specSource.getFullText());
|
|
210
|
+
}
|
|
211
|
+
return tree;
|
|
212
|
+
};
|
|
85
213
|
}
|
|
86
214
|
//# sourceMappingURL=index.js.map
|
|
@@ -40,6 +40,26 @@
|
|
|
40
40
|
"type": "string",
|
|
41
41
|
"description": "Language for pluralization ('en' for English, 'es' for Spanish).",
|
|
42
42
|
"enum": ["en", "es"]
|
|
43
|
+
},
|
|
44
|
+
"style": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "The file extension or preprocessor to use for style files.",
|
|
47
|
+
"default": "css"
|
|
48
|
+
},
|
|
49
|
+
"skipTests": {
|
|
50
|
+
"type": "boolean",
|
|
51
|
+
"description": "Do not create \"spec.ts\" test files for the new class.",
|
|
52
|
+
"default": false
|
|
53
|
+
},
|
|
54
|
+
"inlineStyle": {
|
|
55
|
+
"type": "boolean",
|
|
56
|
+
"description": "Include styles inline in the component.ts file. Only CSS styles can be included inline.",
|
|
57
|
+
"default": false
|
|
58
|
+
},
|
|
59
|
+
"inlineTemplate": {
|
|
60
|
+
"type": "boolean",
|
|
61
|
+
"description": "Include template inline in the component.ts file.",
|
|
62
|
+
"default": false
|
|
43
63
|
}
|
|
44
64
|
},
|
|
45
65
|
"required": ["name"]
|
|
@@ -30,7 +30,7 @@ export const <%= classify(name) %>Store = signalStore(
|
|
|
30
30
|
withEntities(config),
|
|
31
31
|
withEntityStatus(),
|
|
32
32
|
withPagination(),
|
|
33
|
-
withComputed(({
|
|
33
|
+
withComputed(({ entityMap, status: { idSelected } }) => ({
|
|
34
34
|
<%= camelize(name) %>Seleccionado: computed(() => {
|
|
35
35
|
const <%= pk %> = idSelected();
|
|
36
36
|
return <%= pk %> ? entityMap()[<%= pk %>] : null;
|
|
@@ -54,8 +54,7 @@ export const <%= classify(name) %>Store = signalStore(
|
|
|
54
54
|
onSuccess(new<%= classify(name) %>);
|
|
55
55
|
}
|
|
56
56
|
}),
|
|
57
|
-
catchError(() => {
|
|
58
|
-
const error = new Error('');
|
|
57
|
+
catchError((error) => {
|
|
59
58
|
patchState(store, (state) => ({
|
|
60
59
|
status: { ...state.status, addError: error, addLoading: false }
|
|
61
60
|
}));
|
|
@@ -80,9 +79,9 @@ export const <%= classify(name) %>Store = signalStore(
|
|
|
80
79
|
status: { ...state.status, error: null, loaded: true, loading: false }
|
|
81
80
|
}));
|
|
82
81
|
}),
|
|
83
|
-
catchError(() => {
|
|
82
|
+
catchError((error) => {
|
|
84
83
|
patchState(store, (state) => ({
|
|
85
|
-
status: { ...state.status, error
|
|
84
|
+
status: { ...state.status, error, loading: false }
|
|
86
85
|
}));
|
|
87
86
|
return EMPTY;
|
|
88
87
|
})
|
|
@@ -121,13 +120,13 @@ export const <%= classify(name) %>Store = signalStore(
|
|
|
121
120
|
throw new Error();
|
|
122
121
|
}
|
|
123
122
|
}),
|
|
124
|
-
catchError(() => {
|
|
123
|
+
catchError((error) => {
|
|
125
124
|
const idsRemoving = store.status.idsRemoving() || [];
|
|
126
125
|
patchState(store, (state) => ({
|
|
127
126
|
status: {
|
|
128
127
|
...state.status,
|
|
129
128
|
_removeLoading: false,
|
|
130
|
-
error
|
|
129
|
+
error,
|
|
131
130
|
idsRemoving: idsRemoving.filter((idRemoving) => idRemoving !== payload)
|
|
132
131
|
}
|
|
133
132
|
}));
|
|
@@ -183,13 +182,13 @@ export const <%= classify(name) %>Store = signalStore(
|
|
|
183
182
|
throw new Error('');
|
|
184
183
|
}
|
|
185
184
|
}),
|
|
186
|
-
catchError(() => {
|
|
185
|
+
catchError((error) => {
|
|
187
186
|
const idsUpdating = store.status.idsUpdating() || [];
|
|
188
187
|
patchState(store, (state) => ({
|
|
189
188
|
status: {
|
|
190
189
|
...state.status,
|
|
191
190
|
_updateLoading: false,
|
|
192
|
-
error
|
|
191
|
+
error,
|
|
193
192
|
idsUpdating: idsUpdating.filter((idUpdating) => idUpdating !== payload.<%= pk %>)
|
|
194
193
|
}
|
|
195
194
|
}));
|