Dokploy builds failed with "failed to stat /root/.npmrc": the compose build
secret pointed at ${HOME}/.npmrc, which only exists on a developer machine.
The token now comes from the NEXUS_TOKEN environment variable (Dokploy's
Environment tab or .env), is written to a temporary npmrc and removed in the
same RUN, so it never lands in an image layer.
Also removes a temporary UI dev-server spec that was committed by mistake,
and documents the variable (.env.example, docs 06/07, Word/PDF export).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
6.9 KiB
Markdown
95 lines
6.9 KiB
Markdown
# 6. Desarrollo
|
|
|
|
## 6.1 Requisitos
|
|
|
|
- Node 22 y npm 10.
|
|
- Docker (PostgreSQL, MongoDB y Redis).
|
|
- Acceso al Nexus de Pireon para `@pi/ui`. El scope está en `.npmrc` (sin token); el token va en tu `~/.npmrc`:
|
|
|
|
```sh
|
|
npm config set //nexus.pireon.dev/repository/npm-releases/:_authToken "<token>"
|
|
```
|
|
|
|
## 6.2 Puesta en marcha
|
|
|
|
```sh
|
|
npm install
|
|
docker compose up -d postgres mongo redis
|
|
cp .env.example .env # ajusta PIREON_*
|
|
npx nx serve api # http://localhost:3000 (GraphiQL en /graphql)
|
|
npx nx serve web # http://localhost:4200
|
|
```
|
|
|
|
`nx serve web` usa `apps/web/proxy.conf.json` para enviar `/api` y `/graphql` (también el WebSocket) a la API, así que
|
|
la web y la API comparten origen igual que en Docker.
|
|
|
|
## 6.3 Comandos
|
|
|
|
| Comando | Qué hace |
|
|
| -------------------------------- | --------------------------------------------------------------------------------- |
|
|
| `npx nx run-many -t lint` | Lint de todo, incluidas las reglas de capas y contextos |
|
|
| `npx nx run-many -t test` | Tests unitarios (dominio y aplicación, API y web) |
|
|
| `npx nx e2e api` | Flujo completo de la API contra Docker, con Pireon simulado |
|
|
| `npx nx run-many -t build` | Builds de la API y la web |
|
|
| `npx nx run web:codegen` | Regenera los tipos GraphQL del cliente desde `apps/api/schema.gql` |
|
|
| `npx nx run api:prune` | Build de la API con `package.json` y lockfile podados (lo usa Docker) |
|
|
| `npx nx graph` | Grafo de dependencias entre proyectos |
|
|
| `npx nx test api-tracker-domain` | Tests de una librería concreta (`api-<contexto>-<capa>`, `web-<contexto>-<capa>`) |
|
|
|
|
### Tests
|
|
|
|
| Dónde | Qué se prueba | Herramienta |
|
|
| ----------------------------------- | ---------------------------------------------------------------------- | ------------------------ |
|
|
| `api/*/domain` | Reglas de los agregados y reconstrucción desde el historial de eventos | Jest |
|
|
| `api/*/application` | Commands contra un event store en memoria (incluida la concurrencia) | Jest + `@nestjs/testing` |
|
|
| `apps/api/e2e` | Todo el flujo: GraphQL → Postgres → Redis → Mongo → subscriptions | Jest contra Docker |
|
|
| `web/*/domain`, `web/*/application` | Sesión y reducers del tablero | Jest / Vitest |
|
|
| `apps/web` | Rutas y guards | Vitest |
|
|
|
|
El e2e sustituye a Pireon por usuarios fijos (`token-ana`, `token-bob`…) con `overrideProvider(IdentityProvider)`; el
|
|
resto de la pila es real.
|
|
|
|
## 6.4 Cómo añadir una funcionalidad
|
|
|
|
Ejemplo: **marcar un ticket como bloqueado con un motivo**. El recorrido es siempre el mismo, de dentro hacia fuera.
|
|
|
|
### API
|
|
|
|
1. **Dominio** (`libs/api/tracker/domain`)
|
|
- Evento `issue-blocked.event.ts` con `@AutowiredEvent`, que extiende `TrackerEvent`.
|
|
- En `Issue`, el método `block(actorId, reason)` valida y hace `this.apply(new IssueBlockedEvent(…))`; el handler
|
|
`onIssueBlockedEvent(event)` cambia el estado.
|
|
- Test en `issue.spec.ts`, incluida la reconstrucción desde el historial (`replay`).
|
|
2. **Aplicación** (`libs/api/tracker/application`)
|
|
- `commands/block-issue.command.ts` y `block-issue.command-handler.ts`: rehidratar (`WriteAccess`), llamar al
|
|
dominio y `commitAndWait()`. Registrar el handler en `command-handlers.ts`.
|
|
- Método en `IssuesService`.
|
|
- Proyección `event-handlers/issue-blocked.event-handler.ts` (`@EventsHandler`) que actualiza el read model y avisa
|
|
por `ReadModelNotifier`. Añadir el evento al `ActivityEventHandler` y una frase en `activity-summary.ts`.
|
|
- Si el read model cambia, añadir el campo a `IssueReadModel` (dominio) y al esquema Mongo (infraestructura).
|
|
3. **Presenters** (`libs/api/tracker/presenters`): input y mutation `blockIssue` en `IssueResolver`, que llama al service.
|
|
4. Arrancar la API para regenerar `apps/api/schema.gql`.
|
|
|
|
### Web
|
|
|
|
5. `npx nx run web:codegen` tras añadir la mutation a `issues.graphql`.
|
|
6. **Dominio**: método en el puerto `IssuesGateway`. **Infraestructura**: implementarlo en `GraphqlIssuesGateway`.
|
|
7. **Aplicación**: evento `blockRequested` en `issuePageEvents`, su efecto en `IssueStore` (`runCommand`) y el método
|
|
`block(reason)` en `IssueFacade`.
|
|
8. **Presenters**: botón y formulario en la página del ticket que llaman a `facade.block(reason)`.
|
|
|
|
El lint impide atajos: si un resolver intenta usar un repositorio o una página intenta usar Apollo, falla. Y como `application` solo
|
|
exporta facades, una página tampoco puede tocar un store ni despachar eventos por su cuenta.
|
|
|
|
## 6.5 Problemas conocidos
|
|
|
|
| Síntoma | Causa y solución |
|
|
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
| `npm install` falla con `Cannot read properties of null (reading 'edgesOut')` | Bug de npm con un peer opcional de Vite que arrastra vitest 5. El `overrides` de `package.json` fija vitest; no lo quites |
|
|
| `401` al instalar `@pi/ui` | Falta el token del Nexus en `~/.npmrc` (o `NEXUS_TOKEN` al construir con Docker) |
|
|
| `failed to stat /root/.npmrc` al desplegar | Versión antigua del `docker-compose.yml`: ahora el token llega por `NEXUS_TOKEN` (ver [Despliegue](07-despliegue.md)) |
|
|
| `nx serve web` arranca en el puerto 3000 | Nx carga el `.env` raíz (`PORT=3000`) en todas las tareas; `apps/web/.env` lo fija en 4200 |
|
|
| La API no ve un evento nuevo al deserializar | La clase del evento no tiene `@AutowiredEvent` o no se importa |
|
|
| Un componente de `@pi/ui` lanza `Unable to find icon` | Falta registrar ese icono en `libs/web/shared/ui/src/lib/icons.ts` |
|
|
| Tablas antiguas (`projects`, `issues`, `outbox`) en tu Postgres local | Restos de una versión anterior sin event sourcing; `docker compose down -v` empieza de cero |
|