From c5ed10b8fd7d242e6d935ed1f8ee52998c2c8483 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 27 Sep 2025 21:28:50 +0200 Subject: [PATCH] =?UTF-8?q?Plantilla=20para=20m=C3=B3dulos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/TEMPLATE.md | 43 ++ package.json | 7 +- plopfile.js | 26 + pnpm-lock.yaml | 480 +++++++++++++++++- scripts/create-package.ts | 65 --- .../templates/client/__PACKAGE_NAME__Page.tsx | 3 - scripts/templates/client/manifest.ts | 12 - scripts/templates/client/tsconfig.json | 4 - scripts/templates/package.json | 23 - scripts/templates/server/controller.ts | 3 - scripts/templates/server/index.ts | 13 - scripts/templates/server/tsconfig.json | 10 - templates/new-module/package.json | 9 + .../{{kebabCase name}}.full.presenter.ts | 12 + .../list-{{kebabCase name}}s.presenter.ts | 22 + .../{{kebabCase name}}-not-exists.spec.ts | 17 + .../{{kebabCase name}}-unique-name.spec.ts | 18 + .../create-{{kebabCase name}}.use-case.ts | 28 + .../domain/aggregates/{{kebabCase name}}.ts | 17 + ...{{kebabCase name}}-repository.interface.ts | 12 + ...reate-{{kebabCase name}}.controller.ts.hbs | 32 ++ ...elete-{{kebabCase name}}.controller.ts.hbs | 25 + .../get-{{kebabCase name}}.controller.ts.hbs | 25 + .../express/controllers/index.ts.hbs | 6 + .../list-{{kebabCase name}}.controller.ts.hbs | 28 + ...pdate-{{kebabCase name}}.controller.ts.hbs | 32 ++ .../api/infrastructure/express/index.ts.hbs | 3 + .../{{kebabCase plural}}.routes.ts.hbs | 33 ++ .../src/api/infrastructure/index.ts.hbs | 5 + .../mappers/domain/index.ts.hbs | 1 + .../domain/{{kebabCase name}}.mapper.ts.hbs | 27 + .../api/infrastructure/mappers/index.ts.hbs | 2 + .../mappers/queries/index.ts.hbs | 1 + .../{{kebabCase name}}.list.mapper.ts.hbs | 13 + .../repositories/dependencies.ts.hbs | 43 ++ .../infrastructure/repositories/index.ts.hbs | 2 + .../{{kebabCase name}}.repository.ts.hbs | 36 ++ .../api/infrastructure/sequelize/index.ts.hbs | 5 + .../sequelize/models/index.ts.hbs | 2 + .../models/{{kebabCase name}}.model.ts.hbs | 64 +++ templates/new-module/tsconfig.json | 9 + 41 files changed, 1079 insertions(+), 139 deletions(-) create mode 100644 docs/TEMPLATE.md create mode 100644 plopfile.js delete mode 100644 scripts/create-package.ts delete mode 100644 scripts/templates/client/__PACKAGE_NAME__Page.tsx delete mode 100644 scripts/templates/client/manifest.ts delete mode 100644 scripts/templates/client/tsconfig.json delete mode 100644 scripts/templates/package.json delete mode 100644 scripts/templates/server/controller.ts delete mode 100644 scripts/templates/server/index.ts delete mode 100644 scripts/templates/server/tsconfig.json create mode 100644 templates/new-module/package.json create mode 100644 templates/new-module/src/api/application/presenters/domain/{{kebabCase name}}.full.presenter.ts create mode 100644 templates/new-module/src/api/application/presenters/queries/list-{{kebabCase name}}s.presenter.ts create mode 100644 templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-not-exists.spec.ts create mode 100644 templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-unique-name.spec.ts create mode 100644 templates/new-module/src/api/application/use-cases/create-{{kebabCase name}}.use-case.ts create mode 100644 templates/new-module/src/api/domain/aggregates/{{kebabCase name}}.ts create mode 100644 templates/new-module/src/api/domain/repositories/{{kebabCase name}}-repository.interface.ts create mode 100644 templates/new-module/src/api/infrastructure/express/controllers/create-{{kebabCase name}}.controller.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/express/controllers/delete-{{kebabCase name}}.controller.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/express/controllers/get-{{kebabCase name}}.controller.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/express/controllers/index.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/express/controllers/list-{{kebabCase name}}.controller.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/express/controllers/update-{{kebabCase name}}.controller.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/express/index.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/express/{{kebabCase plural}}.routes.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/index.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/mappers/domain/index.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/mappers/domain/{{kebabCase name}}.mapper.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/mappers/index.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/mappers/queries/index.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/mappers/queries/{{kebabCase name}}.list.mapper.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/repositories/dependencies.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/repositories/index.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/repositories/{{kebabCase name}}.repository.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/sequelize/index.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/sequelize/models/index.ts.hbs create mode 100644 templates/new-module/src/api/infrastructure/sequelize/models/{{kebabCase name}}.model.ts.hbs create mode 100644 templates/new-module/tsconfig.json diff --git a/docs/TEMPLATE.md b/docs/TEMPLATE.md new file mode 100644 index 00000000..a8f89b14 --- /dev/null +++ b/docs/TEMPLATE.md @@ -0,0 +1,43 @@ +### Prompt para tu README / CLI + +**Generar un nuevo módulo ERP desde la plantilla** + +Nuestro ERP permite crear módulos de forma **estandarizada** usando una **plantilla base** (Hexagonal/DDD, TS, Express, Sequelize) y un **generador Plop**. + +**Opción A — con Plop (recomendada)** + +1. Instala dependencias de desarrollo si no las tienes: + + * `pnpm add -D plop` +2. Asegúrate de tener en el repo: + + * `plopfile.js` con el generador `module` + * Carpeta `templates/new-module/` con la plantilla (placeholders Handlebars: `{{kebabCase name}}`, `{{pascalCase name}}`, etc.) +3. Ejecuta el generador: + + * `pnpm plop module` +4. Introduce el nombre del módulo en **kebab-case** (p. ej., `suppliers`). +5. Se creará `packages/suppliers/` con: + + * `src/api/{application,domain,infrastructure,helpers}` + `common` + * Casos de uso CRUD, presenters (FULL/LIST), specs comunes y contratos de repositorio. +6. Integra en el monorepo: + + * Añade targets en `turbo.json` si aplica. + * Ejecuta `pnpm -w build` para validar compilación. + +**Opción B — manual (desde ZIP de plantilla)** + +1. Descomprime `new-module-template.zip`. +2. Copia `new-module/` a `packages//`. +3. Renombra los archivos con `entity`/`{{name}}` por el nombre real o usa búsqueda global. +4. Ajusta `package.json` (`name`, `scripts`), y registra el módulo en `turbo.json`. +5. `pnpm -w install && pnpm -w build`. + +**Convenciones clave** + +* Nombres de archivos/directorios en **kebab-case**; código en **Inglés**; comentarios **TSDoc en castellano**. +* Mantén SOLID, SoC, alta cohesión/bajo acoplamiento, y hexagonal (puertos/adaptadores). +* Observabilidad (logs estructurados), idempotencia en endpoints, y principio de menor privilegio. + + diff --git a/package.json b/package.json index 1a6da17b..0bd38be3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,11 @@ { "name": "uecko-erp-2025", "private": true, - "workspaces": ["apps/*", "modules/*", "packages/*"], + "workspaces": [ + "apps/*", + "modules/*", + "packages/*" + ], "scripts": { "build": "turbo build", "dev": "turbo dev", @@ -18,6 +22,7 @@ "@biomejs/biome": "1.9.4", "@repo/typescript-config": "workspace:*", "inquirer": "^12.5.2", + "plop": "^4.0.4", "ts-node": "^10.9.2", "turbo": "^2.5.1", "typescript": "5.8.3" diff --git a/plopfile.js b/plopfile.js new file mode 100644 index 00000000..4b7dc287 --- /dev/null +++ b/plopfile.js @@ -0,0 +1,26 @@ +export default function (plop) { + plop.setGenerator("module", { + description: "Crea un nuevo módulo ERP con plantilla base", + prompts: [ + { + type: "input", + name: "name", + message: "Nombre del módulo (kebab-case):", + }, + { + type: "input", + name: "plural", + message: "Nombre plural para las rutas (kebab-case):", + default: (answers) => `${plop.getHelper("kebabCase")(answers.name)}s`, + }, + ], + actions: [ + { + type: "addMany", + destination: "modules/{{kebabCase name}}", + base: "templates/new-module", + templateFiles: "templates/new-module/**", + }, + ], + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 509a2335..356d8871 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: inquirer: specifier: ^12.5.2 version: 12.6.3(@types/node@24.0.3) + plop: + specifier: ^4.0.4 + version: 4.0.4(@types/node@24.0.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@24.0.3)(typescript@5.8.3) @@ -185,7 +188,7 @@ importers: version: 29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)) ts-jest: specifier: ^29.2.5 - version: 29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(esbuild@0.25.5)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3) + version: 29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 @@ -663,6 +666,40 @@ importers: specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4) + modules/doc-numbering: + dependencies: + '@erp/auth': + specifier: workspace:* + version: link:../auth + '@erp/core': + specifier: workspace:* + version: link:../core + '@repo/rdx-criteria': + specifier: workspace:* + version: link:../../packages/rdx-criteria + '@repo/rdx-ddd': + specifier: workspace:* + version: link:../../packages/rdx-ddd + '@repo/rdx-logger': + specifier: workspace:* + version: link:../../packages/rdx-logger + '@repo/rdx-utils': + specifier: workspace:* + version: link:../../packages/rdx-utils + express: + specifier: ^4.18.2 + version: 4.21.2 + sequelize: + specifier: ^6.37.5 + version: 6.37.7(mysql2@3.14.1) + zod: + specifier: ^4.1.11 + version: 4.1.11 + devDependencies: + '@types/express': + specifier: ^4.17.21 + version: 4.17.23 + modules/document-numbering: dependencies: '@erp/auth': @@ -1716,6 +1753,15 @@ packages: '@types/node': optional: true + '@inquirer/external-editor@1.0.2': + resolution: {integrity: sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/figures@1.0.12': resolution: {integrity: sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==} engines: {node: '>=18'} @@ -3064,6 +3110,9 @@ packages: '@types/express@4.17.23': resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/fined@1.1.5': + resolution: {integrity: sha512-2N93vadEGDFhASTIRbizbl4bNqpMOId5zZfj6hHqYZfEzEfO9onnU4Im8xvzo8uudySDveDHBOOSlTWf38ErfQ==} + '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -3079,6 +3128,9 @@ packages: '@types/inquirer@6.5.0': resolution: {integrity: sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==} + '@types/inquirer@9.0.9': + resolution: {integrity: sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -3094,6 +3146,9 @@ packages: '@types/jsonwebtoken@9.0.10': resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/liftoff@4.0.3': + resolution: {integrity: sha512-UgbL2kR5pLrWICvr8+fuSg0u43LY250q7ZMkC+XKC3E+rs/YBDEnQIzsnhU5dYsLlwMi3R75UvCL87pObP1sxw==} + '@types/lodash@4.17.17': resolution: {integrity: sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==} @@ -3130,6 +3185,9 @@ packages: '@types/passport@1.0.17': resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==} + '@types/picomatch@4.0.2': + resolution: {integrity: sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==} + '@types/postcss-modules-local-by-default@4.0.2': resolution: {integrity: sha512-CtYCcD+L+trB3reJPny+bKWKMzPfxEyQpKIwit7kErnOexf5/faaGpkFy4I5AwbV4hp1sk7/aTg0tt0B67VkLQ==} @@ -3324,9 +3382,17 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + array-each@1.0.1: + resolution: {integrity: sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==} + engines: {node: '>=0.10.0'} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + array-slice@1.1.0: + resolution: {integrity: sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==} + engines: {node: '>=0.10.0'} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -3552,6 +3618,9 @@ packages: change-case@3.1.0: resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -3559,6 +3628,9 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + chardet@2.1.0: + resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -3917,6 +3989,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + detect-libc@1.0.3: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} engines: {node: '>=0.10'} @@ -3951,6 +4027,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -4151,6 +4230,10 @@ packages: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + expect-type@1.2.2: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} @@ -4166,6 +4249,9 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -4206,6 +4292,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} @@ -4234,6 +4329,18 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} + findup-sync@5.0.0: + resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==} + engines: {node: '>= 10.13.0'} + + fined@2.0.0: + resolution: {integrity: sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==} + engines: {node: '>= 10.13.0'} + + flagged-respawn@2.0.0: + resolution: {integrity: sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==} + engines: {node: '>= 10.13.0'} + fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} @@ -4246,6 +4353,14 @@ packages: debug: optional: true + for-in@1.0.2: + resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} + engines: {node: '>=0.10.0'} + + for-own@1.0.0: + resolution: {integrity: sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==} + engines: {node: '>=0.10.0'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -4360,6 +4475,14 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported + global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + + global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -4424,6 +4547,10 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -4481,6 +4608,10 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + icss-utils@5.1.0: resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} @@ -4559,10 +4690,18 @@ packages: resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} engines: {node: '>=12.0.0'} + inquirer@9.3.8: + resolution: {integrity: sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==} + engines: {node: '>=18'} + internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + interpret@3.1.1: + resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} + engines: {node: '>=10.13.0'} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -4571,6 +4710,10 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-absolute@1.0.0: + resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} + engines: {node: '>=0.10.0'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -4616,13 +4759,25 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + is-relative@1.0.0: + resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==} + engines: {node: '>=0.10.0'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-unc-path@1.0.0: + resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==} + engines: {node: '>=0.10.0'} + is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} @@ -4633,13 +4788,25 @@ packages: is-what@3.14.1: resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} + isbinaryfile@5.0.6: + resolution: {integrity: sha512-I+NmIfBHUl+r2wcDd6JwE9yWje/PIVY/R5/CmV8dXLZd5K+L9X2klAOwfAHNnondLXkbHyTAleQAWonpTJBTtw==} + engines: {node: '>= 18.0.0'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -4874,6 +5041,10 @@ packages: libphonenumber-js@1.12.9: resolution: {integrity: sha512-VWwAdNeJgN7jFOD+wN4qx83DTPMVPPAUyx9/TUkBXKLiNkuWWk6anV0439tgdtwaJDrEdqkvdN22iA6J4bUCZg==} + liftoff@5.0.1: + resolution: {integrity: sha512-wwLXMbuxSF8gMvubFcFRp56lkFV69twvbU5vDPbaw+Q+/rF8j0HKjGbIdlSi+LuJm9jf7k9PB+nTxnsLMPcv2Q==} + engines: {node: '>=10.13.0'} + lightningcss-darwin-arm64@1.30.1: resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} engines: {node: '>= 12.0.0'} @@ -5078,6 +5249,10 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + map-cache@0.2.2: + resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} + engines: {node: '>=0.10.0'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -5203,6 +5378,10 @@ packages: mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + mute-stream@2.0.0: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -5223,6 +5402,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanospinner@1.2.2: + resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -5279,6 +5461,10 @@ packages: resolution: {integrity: sha512-Cov028YhBZ5aB7MdMWJEmwyBig43aGL5WT4vdoB28Oitau1zZAcHUn8Sgfk9HM33TqhtLJ9PlM/O0Mv+QpV/4Q==} engines: {node: '>=8.9.4'} + node-plop@0.32.3: + resolution: {integrity: sha512-tn+OxutdqhvoByKJ7p84FZBSUDfUB76bcvj0ugLBvgE9V52LFcnz8cauCDKi6otnctvFCqa9XkrU35pBY5Baig==} + engines: {node: '>=18'} + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -5318,6 +5504,14 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + object.defaults@1.1.0: + resolution: {integrity: sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==} + engines: {node: '>=0.10.0'} + + object.pick@1.3.0: + resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} + engines: {node: '>=0.10.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -5392,6 +5586,10 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-filepath@1.0.2: + resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==} + engines: {node: '>=0.8'} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -5400,6 +5598,10 @@ packages: resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} engines: {node: '>= 0.10'} + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -5443,6 +5645,14 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-root-regex@0.1.2: + resolution: {integrity: sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==} + engines: {node: '>=0.10.0'} + + path-root@0.1.1: + resolution: {integrity: sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==} + engines: {node: '>=0.10.0'} + path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} @@ -5493,6 +5703,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -5505,6 +5719,11 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + plop@4.0.4: + resolution: {integrity: sha512-YdxtHWcPV8hDsszVPr4VQBVGNdn5ZQmEW+cZakZkuVeQHtENmrtY4AhuyoZW6s7ZjpmrS+llLQrfDgRKNQNsmg==} + engines: {node: '>=18'} + hasBin: true + pnpm@10.12.2: resolution: {integrity: sha512-oyVAGFuWTuMLtOl55AWtxq9ZImtDjuTMGfnodzZnpm0wL1v+5go508rGnjXkuW5winHdACt+k1nEESoXIqwyPw==} engines: {node: '>=18.12'} @@ -5803,6 +6022,10 @@ packages: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + rechoir@0.8.0: + resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} + engines: {node: '>= 10.13.0'} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -5828,6 +6051,10 @@ packages: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} + resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -6276,6 +6503,10 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinygradient@1.1.5: resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==} @@ -6294,6 +6525,9 @@ packages: title-case@2.1.1: resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} + title-case@4.3.2: + resolution: {integrity: sha512-I/nkcBo73mO42Idfv08jhInV61IMb61OdIFxk+B4Gu1oBjWBPOLmhZdsli+oJCVaD+86pYQA93cJfFt224ZFAA==} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -6488,6 +6722,10 @@ packages: engines: {node: '>=0.8.0'} hasBin: true + unc-path-regex@0.1.2: + resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==} + engines: {node: '>=0.10.0'} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -6583,6 +6821,10 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} + v8flags@4.0.1: + resolution: {integrity: sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==} + engines: {node: '>= 10.13.0'} + validate-npm-package-name@5.0.1: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -6715,6 +6957,10 @@ packages: whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -7398,6 +7644,13 @@ snapshots: optionalDependencies: '@types/node': 24.0.3 + '@inquirer/external-editor@1.0.2(@types/node@24.0.3)': + dependencies: + chardet: 2.1.0 + iconv-lite: 0.7.0 + optionalDependencies: + '@types/node': 24.0.3 + '@inquirer/figures@1.0.12': {} '@inquirer/input@4.1.12(@types/node@24.0.3)': @@ -8796,7 +9049,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 24.0.3 + '@types/node': 22.15.32 '@types/cors@2.8.19': dependencies: @@ -8850,6 +9103,8 @@ snapshots: '@types/qs': 6.14.0 '@types/serve-static': 1.15.7 + '@types/fined@1.1.5': {} + '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 @@ -8871,6 +9126,11 @@ snapshots: '@types/through': 0.0.33 rxjs: 6.6.7 + '@types/inquirer@9.0.9': + dependencies: + '@types/through': 0.0.33 + rxjs: 7.8.2 + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -8891,6 +9151,11 @@ snapshots: '@types/ms': 2.1.0 '@types/node': 22.15.32 + '@types/liftoff@4.0.3': + dependencies: + '@types/fined': 1.1.5 + '@types/node': 22.15.32 + '@types/lodash@4.17.17': {} '@types/luxon@3.6.2': {} @@ -8931,6 +9196,8 @@ snapshots: dependencies: '@types/express': 4.17.23 + '@types/picomatch@4.0.2': {} + '@types/postcss-modules-local-by-default@4.0.2': dependencies: postcss: 8.5.6 @@ -8969,7 +9236,7 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 24.0.3 + '@types/node': 22.15.32 '@types/serve-static@1.15.7': dependencies: @@ -9143,8 +9410,12 @@ snapshots: dependencies: tslib: 2.8.1 + array-each@1.0.1: {} + array-flatten@1.1.1: {} + array-slice@1.1.0: {} + array-union@2.1.0: {} assertion-error@2.0.1: {} @@ -9438,10 +9709,14 @@ snapshots: upper-case: 1.1.3 upper-case-first: 1.1.2 + change-case@5.4.4: {} + char-regex@1.0.2: {} chardet@0.7.0: {} + chardet@2.1.0: {} + check-error@2.1.1: {} chokidar@4.0.3: @@ -9754,6 +10029,8 @@ snapshots: destroy@1.2.0: {} + detect-file@1.0.0: {} + detect-libc@1.0.3: {} detect-libc@2.0.4: {} @@ -9774,6 +10051,8 @@ snapshots: dependencies: path-type: 4.0.0 + dlv@1.1.3: {} + dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.27.6 @@ -9982,6 +10261,10 @@ snapshots: exit@0.1.2: {} + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + expect-type@1.2.2: {} expect@29.7.0: @@ -10030,6 +10313,8 @@ snapshots: transitivePeerDependencies: - supports-color + extend@3.0.2: {} + external-editor@3.1.0: dependencies: chardet: 0.7.0 @@ -10076,6 +10361,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + fecha@4.2.3: {} figures@3.2.0: @@ -10113,10 +10402,33 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 + findup-sync@5.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.8 + resolve-dir: 1.0.1 + + fined@2.0.0: + dependencies: + expand-tilde: 2.0.2 + is-plain-object: 5.0.0 + object.defaults: 1.1.0 + object.pick: 1.3.0 + parse-filepath: 1.0.2 + + flagged-respawn@2.0.0: {} + fn.name@1.1.0: {} follow-redirects@1.15.9: {} + for-in@1.0.2: {} + + for-own@1.0.0: + dependencies: + for-in: 1.0.2 + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -10248,6 +10560,20 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + globals@11.12.0: {} globby@10.0.2: @@ -10310,6 +10636,10 @@ snapshots: dependencies: react-is: 16.13.1 + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + html-escaper@2.0.2: {} html-minifier-terser@6.1.0: @@ -10379,6 +10709,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + icss-utils@5.1.0(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -10470,8 +10804,27 @@ snapshots: through: 2.3.8 wrap-ansi: 6.2.0 + inquirer@9.3.8(@types/node@24.0.3): + dependencies: + '@inquirer/external-editor': 1.0.2(@types/node@24.0.3) + '@inquirer/figures': 1.0.12 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + transitivePeerDependencies: + - '@types/node' + internmap@2.0.3: {} + interpret@3.1.1: {} + ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -10479,6 +10832,11 @@ snapshots: ipaddr.js@1.9.1: {} + is-absolute@1.0.0: + dependencies: + is-relative: 1.0.0 + is-windows: 1.0.2 + is-arrayish@0.2.1: {} is-arrayish@0.3.2: {} @@ -10509,10 +10867,20 @@ snapshots: is-path-inside@3.0.3: {} + is-plain-object@5.0.0: {} + is-property@1.0.2: {} + is-relative@1.0.0: + dependencies: + is-unc-path: 1.0.0 + is-stream@2.0.1: {} + is-unc-path@1.0.0: + dependencies: + unc-path-regex: 0.1.2 + is-unicode-supported@0.1.0: {} is-upper-case@1.1.2: @@ -10521,10 +10889,16 @@ snapshots: is-what@3.14.1: {} + is-windows@1.0.2: {} + isbinaryfile@4.0.10: {} + isbinaryfile@5.0.6: {} + isexe@2.0.0: {} + isobject@3.0.1: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: @@ -10973,6 +11347,16 @@ snapshots: libphonenumber-js@1.12.9: {} + liftoff@5.0.1: + dependencies: + extend: 3.0.2 + findup-sync: 5.0.0 + fined: 2.0.0 + flagged-respawn: 2.0.0 + is-plain-object: 5.0.0 + rechoir: 0.8.0 + resolve: 1.22.10 + lightningcss-darwin-arm64@1.30.1: optional: true @@ -11136,6 +11520,8 @@ snapshots: dependencies: tmpl: 1.0.5 + map-cache@0.2.2: {} + math-intrinsics@1.1.0: {} media-typer@0.3.0: {} @@ -11228,6 +11614,8 @@ snapshots: mute-stream@0.0.8: {} + mute-stream@1.0.0: {} + mute-stream@2.0.0: {} mysql2@3.14.1: @@ -11254,6 +11642,10 @@ snapshots: nanoid@3.3.11: {} + nanospinner@1.2.2: + dependencies: + picocolors: 1.1.1 + natural-compare@1.4.0: {} needle@3.3.1: @@ -11311,6 +11703,21 @@ snapshots: mkdirp: 0.5.6 resolve: 1.22.10 + node-plop@0.32.3(@types/node@24.0.3): + dependencies: + '@types/inquirer': 9.0.9 + '@types/picomatch': 4.0.2 + change-case: 5.4.4 + dlv: 1.1.3 + handlebars: 4.7.8 + inquirer: 9.3.8(@types/node@24.0.3) + isbinaryfile: 5.0.6 + resolve: 1.22.10 + tinyglobby: 0.2.15 + title-case: 4.3.2 + transitivePeerDependencies: + - '@types/node' + node-releases@2.0.19: {} nopt@5.0.0: @@ -11342,6 +11749,17 @@ snapshots: object-inspect@1.13.4: {} + object.defaults@1.1.0: + dependencies: + array-each: 1.0.1 + array-slice: 1.1.0 + for-own: 1.0.0 + isobject: 3.0.1 + + object.pick@1.3.0: + dependencies: + isobject: 3.0.1 + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -11438,6 +11856,12 @@ snapshots: dependencies: callsites: 3.1.0 + parse-filepath@1.0.2: + dependencies: + is-absolute: 1.0.0 + map-cache: 0.2.2 + path-root: 0.1.1 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.27.1 @@ -11447,6 +11871,8 @@ snapshots: parse-node-version@1.0.1: {} + parse-passwd@1.0.0: {} + parseurl@1.3.3: {} pascal-case@2.0.1: @@ -11488,6 +11914,12 @@ snapshots: path-parse@1.0.7: {} + path-root-regex@0.1.2: {} + + path-root@0.1.1: + dependencies: + path-root-regex: 0.1.2 + path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 @@ -11529,6 +11961,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.3: {} + pify@4.0.1: optional: true @@ -11538,6 +11972,19 @@ snapshots: dependencies: find-up: 4.1.0 + plop@4.0.4(@types/node@24.0.3): + dependencies: + '@types/liftoff': 4.0.3 + interpret: 3.1.1 + liftoff: 5.0.1 + minimist: 1.2.8 + nanospinner: 1.2.2 + node-plop: 0.32.3(@types/node@24.0.3) + picocolors: 1.1.1 + v8flags: 4.0.1 + transitivePeerDependencies: + - '@types/node' + pnpm@10.12.2: {} postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)): @@ -11857,6 +12304,10 @@ snapshots: tiny-invariant: 1.3.3 victory-vendor: 36.9.2 + rechoir@0.8.0: + dependencies: + resolve: 1.22.10 + reflect-metadata@0.2.2: {} registry-auth-token@3.3.2: @@ -11878,6 +12329,11 @@ snapshots: dependencies: resolve-from: 5.0.0 + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -12361,6 +12817,11 @@ snapshots: fdir: 6.4.5(picomatch@4.0.2) picomatch: 4.0.2 + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tinygradient@1.1.5: dependencies: '@types/tinycolor2': 1.4.6 @@ -12377,6 +12838,8 @@ snapshots: no-case: 2.3.2 upper-case: 1.1.3 + title-case@4.3.2: {} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -12403,7 +12866,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(esbuild@0.25.5)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3): + ts-jest@29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -12421,7 +12884,6 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.27.4) - esbuild: 0.25.5 jest-util: 29.7.0 ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3): @@ -12580,6 +13042,8 @@ snapshots: uglify-js@3.19.3: optional: true + unc-path-regex@0.1.2: {} + undici-types@6.21.0: {} undici-types@7.8.0: {} @@ -12656,6 +13120,8 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + v8flags@4.0.1: {} + validate-npm-package-name@5.0.1: {} validator@13.15.15: {} @@ -12843,6 +13309,10 @@ snapshots: tr46: 1.0.1 webidl-conversions: 4.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/scripts/create-package.ts b/scripts/create-package.ts deleted file mode 100644 index cea94d7d..00000000 --- a/scripts/create-package.ts +++ /dev/null @@ -1,65 +0,0 @@ -import fs from "fs"; -import inquirer from "inquirer"; -import path from "path"; - -function capitalize(str: string) { - return str.charAt(0).toUpperCase() + str.slice(1); -} - -async function main() { - let rawName = process.argv[2]; - - if (!rawName) { - const answers = await inquirer.prompt([ - { - type: "input", - name: "packageName", - message: "Nombre del nuevo package:", - validate: (input) => (input ? true : "El nombre no puede estar vacío"), - }, - ]); - rawName = answers.packageName; - } - - const name = rawName.toLowerCase(); - const capitalized = capitalize(name); - const packagePath = path.resolve(__dirname, "../packages", name); - const clientPath = path.join(packagePath, "client"); - const serverPath = path.join(packagePath, "server"); - const templatePath = path.resolve(__dirname, "./templates"); - - // Crear carpetas - fs.mkdirSync(clientPath, { recursive: true }); - fs.mkdirSync(serverPath, { recursive: true }); - - // Función de reemplazo de contenido - const renderTemplate = (content: string) => - content - .replace(/__PACKAGE_NAME__/g, name) - .replace(/__PACKAGE_NAME_CAPITALIZED__/g, capitalized); - - // Copiar plantillas desde carpeta 'templates/client' - const copyFromTemplate = (srcDir: string, destDir: string) => { - const files = fs.readdirSync(srcDir); - for (const file of files) { - const filePath = path.join(srcDir, file); - const content = fs.readFileSync(filePath, "utf-8"); - - const outputName = file.replace(/__PACKAGE_NAME__/g, capitalized); - const outputPath = path.join(destDir, outputName); - - fs.writeFileSync(outputPath, renderTemplate(content)); - } - }; - - copyFromTemplate(path.join(templatePath, "client"), clientPath); - copyFromTemplate(path.join(templatePath, "server"), serverPath); - - // package.json - const pkgJsonTemplate = fs.readFileSync(path.join(templatePath, "package.json"), "utf-8"); - fs.writeFileSync(path.join(packagePath, "package.json"), renderTemplate(pkgJsonTemplate)); - - console.log(`✅ Package '${name}' creado correctamente en packages/${name}`); -} - -main(); diff --git a/scripts/templates/client/__PACKAGE_NAME__Page.tsx b/scripts/templates/client/__PACKAGE_NAME__Page.tsx deleted file mode 100644 index 443aef51..00000000 --- a/scripts/templates/client/__PACKAGE_NAME__Page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function __PACKAGE_NAME_CAPITALIZED__Page() { - return
__PACKAGE_NAME_CAPITALIZED__ Package Page
; -} diff --git a/scripts/templates/client/manifest.ts b/scripts/templates/client/manifest.ts deleted file mode 100644 index 165d9809..00000000 --- a/scripts/templates/client/manifest.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { IPackageClient } from "@packages/package"; -import __PACKAGE_NAME_CAPITALIZED__Page from "./__PACKAGE_NAME__Page"; - -export const __PACKAGE_NAME_CAPITALIZED__Package: IPackageClient = { - metadata: { - name: "__PACKAGE_NAME__", - route: "/__PACKAGE_NAME__", - version: "1.0.0", - description: "__PACKAGE_NAME_CAPITALIZED__ package", - }, - component: __PACKAGE_NAME_CAPITALIZED__Page, -}; diff --git a/scripts/templates/client/tsconfig.json b/scripts/templates/client/tsconfig.json deleted file mode 100644 index bbb38ed9..00000000 --- a/scripts/templates/client/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "@repo/typescript-config/react", - "include": ["."] -} diff --git a/scripts/templates/package.json b/scripts/templates/package.json deleted file mode 100644 index 8295f994..00000000 --- a/scripts/templates/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@packages/__PACKAGE_NAME__", - "version": "1.0.0", - "private": true, - "main": "server/index.ts", - "scripts": { - "build": "tsc -b", - "dev": "turbo run dev --filter=@packages/__PACKAGE_NAME__" - }, - "dependencies": {}, - "devDependencies": {}, - "peerDependencies": { - "@types/express": "*", - "@types/node": "*", - "@types/react": "*", - "express": "*", - "react": "*", - "react-router-dom": "*", - "sequelize": "*", - "typescript": "*", - "zod": "*" - } -} diff --git a/scripts/templates/server/controller.ts b/scripts/templates/server/controller.ts deleted file mode 100644 index 7e0c7526..00000000 --- a/scripts/templates/server/controller.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function __PACKAGE_NAME__Controller(req, res) { - res.send("__PACKAGE_NAME_CAPITALIZED__ package response"); -} diff --git a/scripts/templates/server/index.ts b/scripts/templates/server/index.ts deleted file mode 100644 index 26750cf2..00000000 --- a/scripts/templates/server/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { IPackageServer } from "@packages/package"; -import { __PACKAGE_NAME__Controller } from "./controller"; - -export const __PACKAGE_NAME_CAPITALIZED__Package: IPackageServer = { - metadata: { - name: "__PACKAGE_NAME__", - version: "1.0.0", - dependencies: [], - }, - init(app) { - app.get("/__PACKAGE_NAME__", __PACKAGE_NAME__Controller); - }, -}; diff --git a/scripts/templates/server/tsconfig.json b/scripts/templates/server/tsconfig.json deleted file mode 100644 index 0593b224..00000000 --- a/scripts/templates/server/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/node", - "include": ["."], - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./*"] - } - } -} diff --git a/templates/new-module/package.json b/templates/new-module/package.json new file mode 100644 index 00000000..d44ef494 --- /dev/null +++ b/templates/new-module/package.json @@ -0,0 +1,9 @@ +{ + "name": "@erp/{{kebabCase name}}", + "version": "0.1.0", + "main": "src/index.ts", + "scripts": { + "build": "tsc -p tsconfig.json", + "lint": "eslint . --ext .ts" + } +} diff --git a/templates/new-module/src/api/application/presenters/domain/{{kebabCase name}}.full.presenter.ts b/templates/new-module/src/api/application/presenters/domain/{{kebabCase name}}.full.presenter.ts new file mode 100644 index 00000000..2336517d --- /dev/null +++ b/templates/new-module/src/api/application/presenters/domain/{{kebabCase name}}.full.presenter.ts @@ -0,0 +1,12 @@ +import { Presenter } from "@erp/core/api"; +import { {{pascalCase name}} } from "../../../domain"; + +export class {{pascalCase name}}FullPresenter extends Presenter<{{pascalCase name}}, any> { + toOutput(entity: {{pascalCase name}}): any { + return { + id: entity.id.toPrimitive(), + name: entity.name, + metadata: { entity: "{{kebabCase name}}" } + }; + } +} diff --git a/templates/new-module/src/api/application/presenters/queries/list-{{kebabCase name}}s.presenter.ts b/templates/new-module/src/api/application/presenters/queries/list-{{kebabCase name}}s.presenter.ts new file mode 100644 index 00000000..5b46f6fa --- /dev/null +++ b/templates/new-module/src/api/application/presenters/queries/list-{{kebabCase name}}s.presenter.ts @@ -0,0 +1,22 @@ +import { Presenter } from "@erp/core/api"; +import { Criteria } from "@repo/rdx-criteria/server"; +import { Collection } from "@repo/rdx-utils"; +import { {{pascalCase name}} } from "../../../domain"; + +export class List{{pascalCase name}}sPresenter extends Presenter { + private _mapEntity(entity: {{pascalCase name}}) { + return { id: entity.id.toPrimitive(), name: entity.name }; + } + + toOutput(params: { entities: Collection<{{pascalCase name}}>; criteria: Criteria }): any { + const { entities, criteria } = params; + return { + page: criteria.pageNumber, + per_page: criteria.pageSize, + total_pages: Math.ceil(entities.total() / criteria.pageSize), + total_items: entities.total(), + items: entities.map((e) => this._mapEntity(e)), + metadata: { entity: "{{kebabCase name}}s", criteria: criteria.toJSON() } + }; + } +} diff --git a/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-not-exists.spec.ts b/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-not-exists.spec.ts new file mode 100644 index 00000000..197122c6 --- /dev/null +++ b/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-not-exists.spec.ts @@ -0,0 +1,17 @@ +import { CompositeSpecification, UniqueID } from "@repo/rdx-ddd"; +import { I{{pascalCase name}}Repository } from "../../domain/repositories"; + +export class {{pascalCase name}}NotExistsSpecification extends CompositeSpecification { + constructor( + private readonly repo: I{{pascalCase name}}Repository, + private readonly transaction?: any + ) { + super(); + } + + async isSatisfiedBy(entityId: UniqueID): Promise { + const existsOrError = await this.repo.existsById(entityId, this.transaction); + if (existsOrError.isFailure) throw existsOrError.error; + return existsOrError.data === false; + } +} diff --git a/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-unique-name.spec.ts b/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-unique-name.spec.ts new file mode 100644 index 00000000..918558b0 --- /dev/null +++ b/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-unique-name.spec.ts @@ -0,0 +1,18 @@ +import { CompositeSpecification } from "@repo/rdx-ddd"; +import { I{{pascalCase name}}Repository } from "../../domain/repositories"; + +export class {{pascalCase name}}UniqueNameSpecification extends CompositeSpecification { + constructor( + private readonly repo: I{{pascalCase name}}Repository, + private readonly transaction?: any + ) { + super(); + } + + async isSatisfiedBy(name: string): Promise { + const criteria = { filters: { name } }; + const resultOrError = await this.repo.findByCriteria(criteria as any, this.transaction); + if (resultOrError.isFailure) throw resultOrError.error; + return resultOrError.data.total() === 0; + } +} diff --git a/templates/new-module/src/api/application/use-cases/create-{{kebabCase name}}.use-case.ts b/templates/new-module/src/api/application/use-cases/create-{{kebabCase name}}.use-case.ts new file mode 100644 index 00000000..a8bbac19 --- /dev/null +++ b/templates/new-module/src/api/application/use-cases/create-{{kebabCase name}}.use-case.ts @@ -0,0 +1,28 @@ +import { IPresenterRegistry, ITransactionManager } from "@erp/core/api"; +import { Result } from "@repo/rdx-utils"; +import { I{{pascalCase name}}Repository } from "../../domain/repositories"; +import { {{pascalCase name}} } from "../../domain/aggregates"; +import { {{pascalCase name}}FullPresenter } from "../presenters"; + +type Create{{pascalCase name}}Input = { name: string }; + +export class Create{{pascalCase name}}UseCase { + constructor( + private readonly repo: I{{pascalCase name}}Repository, + private readonly txManager: ITransactionManager, + private readonly presenters: IPresenterRegistry + ) {} + + public execute(params: Create{{pascalCase name}}Input) { + return this.txManager.complete(async (tx) => { + const entityOrError = {{pascalCase name}}.create({ name: params.name }); + if (entityOrError.isFailure) return Result.fail(entityOrError.error); + + const savedOrError = await this.repo.save(entityOrError.data, tx); + if (savedOrError.isFailure) return Result.fail(savedOrError.error); + + const presenter = this.presenters.getPresenter({ resource: "{{kebabCase name}}", projection: "FULL" }) as {{pascalCase name}}FullPresenter; + return Result.ok(presenter.toOutput(savedOrError.data)); + }); + } +} diff --git a/templates/new-module/src/api/domain/aggregates/{{kebabCase name}}.ts b/templates/new-module/src/api/domain/aggregates/{{kebabCase name}}.ts new file mode 100644 index 00000000..aaf59de0 --- /dev/null +++ b/templates/new-module/src/api/domain/aggregates/{{kebabCase name}}.ts @@ -0,0 +1,17 @@ +import { AggregateRoot, UniqueID } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +export interface I{{pascalCase name}}Props { + name: string; +} + +export class {{pascalCase name}} extends AggregateRoot { + static create(props: I{{pascalCase name}}Props, id?: UniqueID): Result<{{pascalCase name}}, Error> { + const entity = new {{pascalCase name}}(props, id); + return Result.ok(entity); + } + + get name(): string { + return this.props.name; + } +} diff --git a/templates/new-module/src/api/domain/repositories/{{kebabCase name}}-repository.interface.ts b/templates/new-module/src/api/domain/repositories/{{kebabCase name}}-repository.interface.ts new file mode 100644 index 00000000..2ce5000e --- /dev/null +++ b/templates/new-module/src/api/domain/repositories/{{kebabCase name}}-repository.interface.ts @@ -0,0 +1,12 @@ +import { UniqueID } from "@repo/rdx-ddd"; +import { Result, Collection } from "@repo/rdx-utils"; +import { Criteria } from "@repo/rdx-criteria/server"; +import { {{pascalCase name}} } from "../aggregates"; + +export interface I{{pascalCase name}}Repository { + save(entity: {{pascalCase name}}, transaction: any): Promise>; + existsById(id: UniqueID, transaction?: any): Promise>; + getById(id: UniqueID, transaction?: any): Promise>; + findByCriteria(criteria: Criteria, transaction?: any): Promise, Error>>; + deleteById(id: UniqueID, transaction: any): Promise>; +} diff --git a/templates/new-module/src/api/infrastructure/express/controllers/create-{{kebabCase name}}.controller.ts.hbs b/templates/new-module/src/api/infrastructure/express/controllers/create-{{kebabCase name}}.controller.ts.hbs new file mode 100644 index 00000000..d008ea5d --- /dev/null +++ b/templates/new-module/src/api/infrastructure/express/controllers/create-{{kebabCase name}}.controller.ts.hbs @@ -0,0 +1,32 @@ +import { ExpressController, authGuard, tenantGuard, forbidQueryFieldGuard } from "@erp/core/api"; +import { Create{{pascalCase name}}UseCase } from "../../../application"; + +/** + * Controlador de creación + * @remarks + * - Aplica guards de autenticación, tenant y prohíbe `companyId` en query. + */ +export class Create{{pascalCase name}}Controller extends ExpressController { + public constructor(private readonly useCase: Create{{pascalCase name}}UseCase) { + super(); + this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); + } + + protected async executeImpl() { + const companyId = this.getTenantId(); + if (!companyId) return this.forbiddenError("Tenant ID not found"); + + // TODO: descomentar cuando exista DTO + // const validation = validateRequest(Create{{pascalCase name}}Dto, this.req.body); + // if (validation.isErr()) return this.badRequestError(validation.error.message); + // const dto = validation.value; + + const dto = this.req.body; + const result = await this.useCase.execute({ companyId, ...dto }); + + return result.match( + (data) => this.ok(data), + (err) => this.handleError(err) + ); + } +} diff --git a/templates/new-module/src/api/infrastructure/express/controllers/delete-{{kebabCase name}}.controller.ts.hbs b/templates/new-module/src/api/infrastructure/express/controllers/delete-{{kebabCase name}}.controller.ts.hbs new file mode 100644 index 00000000..e0371bab --- /dev/null +++ b/templates/new-module/src/api/infrastructure/express/controllers/delete-{{kebabCase name}}.controller.ts.hbs @@ -0,0 +1,25 @@ +import { ExpressController, authGuard, tenantGuard, forbidQueryFieldGuard } from "@erp/core/api"; +import { Delete{{pascalCase name}}UseCase } from "../../../application"; + +/** + * Controlador de borrado lógico/físico según políticas + */ +export class Delete{{pascalCase name}}Controller extends ExpressController { + public constructor(private readonly useCase: Delete{{pascalCase name}}UseCase) { + super(); + this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); + } + + protected async executeImpl() { + const companyId = this.getTenantId(); + if (!companyId) return this.forbiddenError("Tenant ID not found"); + + const { {{snakeCase name}}_id } = this.req.params as { {{snakeCase name}}_id: string }; + const result = await this.useCase.execute({ {{snakeCase name}}_id, companyId }); + + return result.match( + (data) => this.ok(data), + (err) => this.handleError(err) + ); + } +} diff --git a/templates/new-module/src/api/infrastructure/express/controllers/get-{{kebabCase name}}.controller.ts.hbs b/templates/new-module/src/api/infrastructure/express/controllers/get-{{kebabCase name}}.controller.ts.hbs new file mode 100644 index 00000000..86a39925 --- /dev/null +++ b/templates/new-module/src/api/infrastructure/express/controllers/get-{{kebabCase name}}.controller.ts.hbs @@ -0,0 +1,25 @@ +import { ExpressController, authGuard, tenantGuard, forbidQueryFieldGuard } from "@erp/core/api"; +import { Get{{pascalCase name}}UseCase } from "../../../application"; + +/** + * Controlador de lectura por id en el contexto del tenant + */ +export class Get{{pascalCase name}}Controller extends ExpressController { + public constructor(private readonly useCase: Get{{pascalCase name}}UseCase) { + super(); + this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); + } + + protected async executeImpl() { + const companyId = this.getTenantId(); + if (!companyId) return this.forbiddenError("Tenant ID not found"); + + const { {{snakeCase name}}_id } = this.req.params as { {{snakeCase name}}_id: string }; + const result = await this.useCase.execute({ {{snakeCase name}}_id, companyId }); + + return result.match( + (data) => this.ok(data), + (err) => this.handleError(err) + ); + } +} diff --git a/templates/new-module/src/api/infrastructure/express/controllers/index.ts.hbs b/templates/new-module/src/api/infrastructure/express/controllers/index.ts.hbs new file mode 100644 index 00000000..4e17a415 --- /dev/null +++ b/templates/new-module/src/api/infrastructure/express/controllers/index.ts.hbs @@ -0,0 +1,6 @@ +// Barrel de controllers +export * from "./create-{{kebabCase name}}.controller"; +export * from "./delete-{{kebabCase name}}.controller"; +export * from "./get-{{kebabCase name}}.controller"; +export * from "./list-{{kebabCase name}}.controller"; +export * from "./update-{{kebabCase name}}.controller"; diff --git a/templates/new-module/src/api/infrastructure/express/controllers/list-{{kebabCase name}}.controller.ts.hbs b/templates/new-module/src/api/infrastructure/express/controllers/list-{{kebabCase name}}.controller.ts.hbs new file mode 100644 index 00000000..e8fd343d --- /dev/null +++ b/templates/new-module/src/api/infrastructure/express/controllers/list-{{kebabCase name}}.controller.ts.hbs @@ -0,0 +1,28 @@ +import { ExpressController, authGuard, tenantGuard, forbidQueryFieldGuard } from "@erp/core/api"; +import { List{{pascalCase name}}UseCase } from "../../../application"; + +/** + * Controlador de listado paginado por tenant + */ +export class List{{pascalCase name}}Controller extends ExpressController { + public constructor(private readonly useCase: List{{pascalCase name}}UseCase) { + super(); + this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); + } + + protected async executeImpl() { + const companyId = this.getTenantId(); + if (!companyId) return this.forbiddenError("Tenant ID not found"); + + // TODO: mapear query → criteria/pagination + const criteria = this.req.query ?? {}; + const pagination = undefined; + + const result = await this.useCase.execute({ companyId, criteria, pagination }); + + return result.match( + (data) => this.ok(data), + (err) => this.handleError(err) + ); + } +} diff --git a/templates/new-module/src/api/infrastructure/express/controllers/update-{{kebabCase name}}.controller.ts.hbs b/templates/new-module/src/api/infrastructure/express/controllers/update-{{kebabCase name}}.controller.ts.hbs new file mode 100644 index 00000000..787cf85a --- /dev/null +++ b/templates/new-module/src/api/infrastructure/express/controllers/update-{{kebabCase name}}.controller.ts.hbs @@ -0,0 +1,32 @@ +import { ExpressController, authGuard, tenantGuard, forbidQueryFieldGuard } from "@erp/core/api"; +import { Update{{pascalCase name}}UseCase } from "../../../application"; + +/** + * Controlador de actualización parcial/total + */ +export class Update{{pascalCase name}}Controller extends ExpressController { + public constructor(private readonly useCase: Update{{pascalCase name}}UseCase) { + super(); + this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); + } + + protected async executeImpl() { + const companyId = this.getTenantId(); + if (!companyId) return this.forbiddenError("Tenant ID not found"); + + const { {{snakeCase name}}_id } = this.req.params as { {{snakeCase name}}_id: string }; + + // TODO: descomentar cuando exista DTO + // const validation = validateRequest(Update{{pascalCase name}}Dto, this.req.body); + // if (validation.isErr()) return this.badRequestError(validation.error.message); + // const dto = validation.value; + + const dto = this.req.body; + const result = await this.useCase.execute({ {{snakeCase name}}_id, companyId, ...dto }); + + return result.match( + (data) => this.ok(data), + (err) => this.handleError(err) + ); + } +} diff --git a/templates/new-module/src/api/infrastructure/express/index.ts.hbs b/templates/new-module/src/api/infrastructure/express/index.ts.hbs new file mode 100644 index 00000000..d57c1f0a --- /dev/null +++ b/templates/new-module/src/api/infrastructure/express/index.ts.hbs @@ -0,0 +1,3 @@ +// Barrel de express +export * from "./controllers"; +export * from "./{{kebabCase plural}}.routes"; diff --git a/templates/new-module/src/api/infrastructure/express/{{kebabCase plural}}.routes.ts.hbs b/templates/new-module/src/api/infrastructure/express/{{kebabCase plural}}.routes.ts.hbs new file mode 100644 index 00000000..ea465701 --- /dev/null +++ b/templates/new-module/src/api/infrastructure/express/{{kebabCase plural}}.routes.ts.hbs @@ -0,0 +1,33 @@ +import { Router } from "express"; +import type { ModuleParams } from "@erp/core/api"; +import { build{{pascalCase name}}Dependencies } from "../repositories"; +import { + Create{{pascalCase name}}Controller, + Delete{{pascalCase name}}Controller, + Get{{pascalCase name}}Controller, + List{{pascalCase name}}Controller, + Update{{pascalCase name}}Controller, +} from "./controllers"; + +/** + * Monta las rutas del módulo en `params.app` bajo `params.baseRoutePath/{{kebabCase plural}}` + * @remarks + * - Las validaciones DTO están comentadas hasta que existan en `common/dto`. + */ +export const mount{{pascalCase plural}}Routes = (params: ModuleParams) => { + const router = Router(); + const deps = build{{pascalCase name}}Dependencies(params); + + // TODO: descomentar cuando exista validateRequest y DTOs + // const validateCreate = validateRequest(Create{{pascalCase name}}Dto); + // const validateUpdate = validateRequest(Update{{pascalCase name}}Dto); + + router.get("/", (req, res, next) => deps.build.list().bind(req, res, next)); + router.get("/:{{snakeCase name}}_id", (req, res, next) => deps.build.get().bind(req, res, next)); + router.post("/", /* validateCreate, */ (req, res, next) => deps.build.create().bind(req, res, next)); + router.patch("/:{{snakeCase name}}_id", /* validateUpdate, */ (req, res, next) => deps.build.update().bind(req, res, next)); + router.put("/:{{snakeCase name}}_id", /* validateUpdate, */ (req, res, next) => deps.build.update().bind(req, res, next)); + router.delete("/:{{snakeCase name}}_id", (req, res, next) => deps.build.delete().bind(req, res, next)); + + params.app.use(`${params.baseRoutePath}/{{kebabCase plural}}`, router); +}; diff --git a/templates/new-module/src/api/infrastructure/index.ts.hbs b/templates/new-module/src/api/infrastructure/index.ts.hbs new file mode 100644 index 00000000..d90bd53e --- /dev/null +++ b/templates/new-module/src/api/infrastructure/index.ts.hbs @@ -0,0 +1,5 @@ +// Barrel de infraestructura +export * from "./express"; +export * from "./mappers"; +export * from "./repositories"; +export * from "./sequelize"; diff --git a/templates/new-module/src/api/infrastructure/mappers/domain/index.ts.hbs b/templates/new-module/src/api/infrastructure/mappers/domain/index.ts.hbs new file mode 100644 index 00000000..e9a64b92 --- /dev/null +++ b/templates/new-module/src/api/infrastructure/mappers/domain/index.ts.hbs @@ -0,0 +1 @@ +export * from "./{{kebabCase name}}.mapper"; diff --git a/templates/new-module/src/api/infrastructure/mappers/domain/{{kebabCase name}}.mapper.ts.hbs b/templates/new-module/src/api/infrastructure/mappers/domain/{{kebabCase name}}.mapper.ts.hbs new file mode 100644 index 00000000..d78c5e5e --- /dev/null +++ b/templates/new-module/src/api/infrastructure/mappers/domain/{{kebabCase name}}.mapper.ts.hbs @@ -0,0 +1,27 @@ +import type { {{pascalCase name}} } from "../../../domain"; +import type { {{pascalCase name}}Model } from "../../sequelize/models/{{kebabCase name}}.model"; + +/** + * Mapper de dominio ↔ ORM/DTO + * @remarks + * - SSOT: punto único de traducción entre capas. + */ +export const {{camelCase name}}DomainMapper = { + toPersistence(entity: {{pascalCase name}}) { + // TODO: mapear campos reales + return { + id: entity.id, + company_id: entity.companyId, + status: (entity as any).status ?? "active", + }; + }, + + toDomain(model: {{pascalCase name}}Model): {{pascalCase name}} { + // TODO: mapear campos reales + return { + id: model.id, + companyId: model.company_id, + status: (model as any).status, + } as unknown as {{pascalCase name}}; + }, +}; diff --git a/templates/new-module/src/api/infrastructure/mappers/index.ts.hbs b/templates/new-module/src/api/infrastructure/mappers/index.ts.hbs new file mode 100644 index 00000000..9e03d7a9 --- /dev/null +++ b/templates/new-module/src/api/infrastructure/mappers/index.ts.hbs @@ -0,0 +1,2 @@ +export * from "./domain"; +export * from "./queries"; diff --git a/templates/new-module/src/api/infrastructure/mappers/queries/index.ts.hbs b/templates/new-module/src/api/infrastructure/mappers/queries/index.ts.hbs new file mode 100644 index 00000000..b2f78cb5 --- /dev/null +++ b/templates/new-module/src/api/infrastructure/mappers/queries/index.ts.hbs @@ -0,0 +1 @@ +export * from "./{{kebabCase name}}.list.mapper"; diff --git a/templates/new-module/src/api/infrastructure/mappers/queries/{{kebabCase name}}.list.mapper.ts.hbs b/templates/new-module/src/api/infrastructure/mappers/queries/{{kebabCase name}}.list.mapper.ts.hbs new file mode 100644 index 00000000..90e82a07 --- /dev/null +++ b/templates/new-module/src/api/infrastructure/mappers/queries/{{kebabCase name}}.list.mapper.ts.hbs @@ -0,0 +1,13 @@ +/** + * Mapper para listados (read-models) + */ +export const {{camelCase name}}ListMapper = { + toListItem(p: any) { + // TODO: adaptar a la proyección real + return { + id: p.id, + name: p.name ?? "", + status: p.status ?? "active", + }; + }, +}; diff --git a/templates/new-module/src/api/infrastructure/repositories/dependencies.ts.hbs b/templates/new-module/src/api/infrastructure/repositories/dependencies.ts.hbs new file mode 100644 index 00000000..20acca2e --- /dev/null +++ b/templates/new-module/src/api/infrastructure/repositories/dependencies.ts.hbs @@ -0,0 +1,43 @@ +import { InMemoryMapperRegistry, InMemoryPresenterRegistry, SequelizeTransactionManager } from "@erp/core/api"; +import type { ModuleParams } from "@erp/core/api"; +import { {{pascalCase name}}Repository } from "./{{kebabCase name}}.repository"; +import { Create{{pascalCase name}}Controller, Delete{{pascalCase name}}Controller, Get{{pascalCase name}}Controller, List{{pascalCase name}}Controller, Update{{pascalCase name}}Controller } from "../express/controllers"; +// Los use-cases/servicios se resuelven desde application/domain (puede no compilar hasta que existan) +import { + Create{{pascalCase name}}UseCase, + Delete{{pascalCase name}}UseCase, + Get{{pascalCase name}}UseCase, + List{{pascalCase name}}UseCase, + Update{{pascalCase name}}UseCase, +} from "../../application"; +import { {{pascalCase name}}Service } from "../../domain"; + +/** + * Wiring/DI de infraestructura del módulo + */ +export const build{{pascalCase name}}Dependencies = (params: ModuleParams) => { + const mapperRegistry = new InMemoryMapperRegistry(); + const presenterRegistry = new InMemoryPresenterRegistry(); + const transactionManager = new SequelizeTransactionManager(params.database); + + const repository = new {{pascalCase name}}Repository(params.database); + const service = new {{pascalCase name}}Service(repository); + + // TODO: enlazar presenters reales cuando existan + // presenterRegistry.register("{{camelCase name}}.created", ...); + + return { + mapperRegistry, + presenterRegistry, + transactionManager, + repository, + service, + build: { + list: () => new List{{pascalCase name}}Controller(new List{{pascalCase name}}UseCase(service)), + get: () => new Get{{pascalCase name}}Controller(new Get{{pascalCase name}}UseCase(service)), + create: () => new Create{{pascalCase name}}Controller(new Create{{pascalCase name}}UseCase(service)), + update: () => new Update{{pascalCase name}}Controller(new Update{{pascalCase name}}UseCase(service)), + delete: () => new Delete{{pascalCase name}}Controller(new Delete{{pascalCase name}}UseCase(service)), + }, + }; +}; diff --git a/templates/new-module/src/api/infrastructure/repositories/index.ts.hbs b/templates/new-module/src/api/infrastructure/repositories/index.ts.hbs new file mode 100644 index 00000000..99de7edd --- /dev/null +++ b/templates/new-module/src/api/infrastructure/repositories/index.ts.hbs @@ -0,0 +1,2 @@ +export * from "./{{kebabCase name}}.repository"; +export * from "./dependencies"; diff --git a/templates/new-module/src/api/infrastructure/repositories/{{kebabCase name}}.repository.ts.hbs b/templates/new-module/src/api/infrastructure/repositories/{{kebabCase name}}.repository.ts.hbs new file mode 100644 index 00000000..07fd2aca --- /dev/null +++ b/templates/new-module/src/api/infrastructure/repositories/{{kebabCase name}}.repository.ts.hbs @@ -0,0 +1,36 @@ +import type { Sequelize } from "sequelize"; +import { Result } from "@erp/core/api"; +import type { Criteria, Pagination } from "@erp/core/api"; +import type { I{{pascalCase name}}Repository, {{pascalCase name}} } from "../../domain"; +import type { {{pascalCase name}}Model } from "../sequelize/models/{{kebabCase name}}.model"; + +/** + * Repositorio concreto (Sequelize) + * @remarks + * - Implementa métodos por tenant (companyId) desde el inicio. + */ +export class {{pascalCase name}}Repository implements I{{pascalCase name}}Repository { + public constructor(private readonly database: Sequelize) {} + + async create(entity: {{pascalCase name}}) { + // TODO: usar mapper y modelo + return Result.fail<{{pascalCase name}}>(new Error("Not implemented")); + } + + async update(entity: {{pascalCase name}}) { + return Result.fail<{{pascalCase name}}>(new Error("Not implemented")); + } + + async deleteByIdInCompany(id: string, companyId: string) { + return Result.fail(new Error("Not implemented")); + } + + async findByIdInCompany(id: string, companyId: string) { + return Result.fail<{{pascalCase name}}>(new Error("Not implemented")); + } + + async findByCriteriaInCompany(criteria: Criteria, companyId: string, pagination?: Pagination) { + // TODO: aplicar where/order/limit/offset usando CriteriaToSequelizeConverter si existe en core + return Result.fail<{ items: {{pascalCase name}}[]; total: number }>(new Error("Not implemented")); + } +} diff --git a/templates/new-module/src/api/infrastructure/sequelize/index.ts.hbs b/templates/new-module/src/api/infrastructure/sequelize/index.ts.hbs new file mode 100644 index 00000000..6fc375ae --- /dev/null +++ b/templates/new-module/src/api/infrastructure/sequelize/index.ts.hbs @@ -0,0 +1,5 @@ +// Barrel de sequelize y registro de modelos +export * from "./models"; +import { init{{pascalCase name}}Model } from "./models"; + +export const models = [init{{pascalCase name}}Model]; diff --git a/templates/new-module/src/api/infrastructure/sequelize/models/index.ts.hbs b/templates/new-module/src/api/infrastructure/sequelize/models/index.ts.hbs new file mode 100644 index 00000000..b2789626 --- /dev/null +++ b/templates/new-module/src/api/infrastructure/sequelize/models/index.ts.hbs @@ -0,0 +1,2 @@ +export { {{pascalCase name}}Model as {{pascalCase name}}Model } from "./{{kebabCase name}}.model"; +export { default as init{{pascalCase name}}Model } from "./{{kebabCase name}}.model"; diff --git a/templates/new-module/src/api/infrastructure/sequelize/models/{{kebabCase name}}.model.ts.hbs b/templates/new-module/src/api/infrastructure/sequelize/models/{{kebabCase name}}.model.ts.hbs new file mode 100644 index 00000000..02596f5a --- /dev/null +++ b/templates/new-module/src/api/infrastructure/sequelize/models/{{kebabCase name}}.model.ts.hbs @@ -0,0 +1,64 @@ +import { + CreationOptional, + DataTypes, + InferAttributes, + InferCreationAttributes, + Model, + Sequelize, +} from "sequelize"; + +export type {{pascalCase name}}CreationAttributes = InferCreationAttributes<{{pascalCase name}}Model, {}> & {}; + +/** + * Modelo Sequelize (infraestructura, no dominio) + */ +export class {{pascalCase name}}Model extends Model< + InferAttributes<{{pascalCase name}}Model>, + InferCreationAttributes<{{pascalCase name}}Model> +> { + declare id: string; + declare company_id: string; + declare status: CreationOptional; + declare created_at: CreationOptional; + declare updated_at: CreationOptional; + declare deleted_at: CreationOptional; + + static associate(database: Sequelize) { + // TODO: definir relaciones si aplica + } + + static hooks(database: Sequelize) { + // TODO: definir hooks si aplica + } +} + +export default (database: Sequelize) => { + {{pascalCase name}}Model.init( + { + id: { type: DataTypes.UUID, primaryKey: true }, + company_id: { type: DataTypes.UUID, allowNull: false }, + status: { type: DataTypes.STRING, allowNull: false, defaultValue: "active" }, + created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW }, + updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW }, + deleted_at: { type: DataTypes.DATE, allowNull: true, defaultValue: null }, + }, + { + sequelize: database, + tableName: "{{snakeCase plural}}", + underscored: true, + paranoid: true, + timestamps: true, + createdAt: "created_at", + updatedAt: "updated_at", + deletedAt: "deleted_at", + indexes: [ + { name: "company_idx", fields: ["company_id"], unique: false }, + { name: "idx_company_idx", fields: ["id", "company_id"], unique: true }, + ], + whereMergeStrategy: "and", + defaultScope: {}, + scopes: {}, + } + ); + return {{pascalCase name}}Model; +}; diff --git a/templates/new-module/tsconfig.json b/templates/new-module/tsconfig.json new file mode 100644 index 00000000..67cac569 --- /dev/null +++ b/templates/new-module/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "strict": true + }, + "include": ["src"] +}