@devstroupe/devkit-cli 1.1.16 → 1.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +18 -0
  2. package/dist/boilerplates/angular-template/src/app/core/services/auth.service.ts +1 -1
  3. package/dist/boilerplates/angular-template/src/app/services/role.service.ts +1 -1
  4. package/dist/boilerplates/angular-template/src/app/services/user.service.ts +1 -1
  5. package/dist/boilerplates/nest-template/.dockerignore +8 -0
  6. package/dist/boilerplates/nest-template/package.json +13 -15
  7. package/dist/boilerplates/nest-template/src/app.module.ts +7 -10
  8. package/dist/boilerplates/nest-template/src/database/data-source.ts +4 -1
  9. package/dist/boilerplates/nest-template/src/database/migrations/1600000000000-create-roles.ts +2 -1
  10. package/dist/boilerplates/nest-template/src/database/migrations/1700000000000-create-users.ts +2 -2
  11. package/dist/boilerplates/nest-template/src/modules/auth/auth.controller.ts +1 -1
  12. package/dist/boilerplates/nest-template/src/modules/auth/auth.module.ts +2 -7
  13. package/dist/boilerplates/nest-template/src/modules/auth/auth.service.ts +13 -11
  14. package/dist/boilerplates/nest-template/src/modules/auth/jwt-auth.guard.ts +5 -6
  15. package/dist/boilerplates/nest-template/src/modules/core/core.module.ts +9 -0
  16. package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.orm-entity.ts +4 -1
  17. package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.repository.adapter.ts +2 -2
  18. package/dist/boilerplates/nest-template/src/modules/role/infra/http/role.controller.ts +1 -1
  19. package/dist/boilerplates/nest-template/src/modules/storage/infra/http/storage.controller.ts +1 -1
  20. package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.orm-entity.ts +4 -1
  21. package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts +2 -2
  22. package/dist/boilerplates/nest-template/src/modules/user/infra/http/user.controller.ts +1 -1
  23. package/dist/boilerplates/nest-template/src/modules/user/service/user.service.ts +6 -3
  24. package/dist/boilerplates/nest-template/src/platform/security/security.module.ts +20 -0
  25. package/dist/boilerplates/nest-template/src/platform/security/security.tokens.ts +4 -0
  26. package/dist/boilerplates/nest-template/src/platform/security/token-verification.module.ts +22 -0
  27. package/dist/boilerplates/nest-template/tsconfig.json +1 -1
  28. package/dist/generation/backend-generator.integration.test.d.ts +1 -0
  29. package/dist/generation/backend-generator.integration.test.js +232 -0
  30. package/dist/generation/backend-layout.d.ts +15 -0
  31. package/dist/generation/backend-layout.js +77 -0
  32. package/dist/generation/backend-layout.test.d.ts +1 -0
  33. package/dist/generation/backend-layout.test.js +48 -0
  34. package/dist/index.d.ts +4 -1
  35. package/dist/index.js +364 -205
  36. package/dist/migrations/entity-module-move.d.ts +16 -0
  37. package/dist/migrations/entity-module-move.js +98 -0
  38. package/dist/migrations/entity-module-move.test.d.ts +1 -0
  39. package/dist/migrations/entity-module-move.test.js +30 -0
  40. package/dist/rules/linter-rules.js +252 -22
  41. package/dist/rules/linter-rules.test.js +125 -0
  42. package/dist/templates/angular/service.template.d.ts +1 -1
  43. package/dist/templates/angular/service.template.js +2 -2
  44. package/dist/templates/generator-characterization.test.d.ts +1 -0
  45. package/dist/templates/generator-characterization.test.js +42 -0
  46. package/dist/templates/nest/crud.templates.d.ts +3 -3
  47. package/dist/templates/nest/crud.templates.js +20 -13
  48. package/dist/templates/nest/functional-module.templates.d.ts +10 -0
  49. package/dist/templates/nest/functional-module.templates.js +261 -0
  50. package/dist/templates/nest/index.d.ts +1 -1
  51. package/dist/templates/nest/index.js +1 -1
  52. package/dist/templates/nest/migration.template.js +6 -3
  53. package/dist/templates/relationship-templates.test.js +0 -4
  54. package/package.json +4 -4
  55. package/dist/boilerplates/angular-template/node_modules/.bin/acorn +0 -21
  56. package/dist/boilerplates/angular-template/node_modules/.bin/browserslist +0 -21
  57. package/dist/boilerplates/angular-template/node_modules/.bin/eslint +0 -21
  58. package/dist/boilerplates/angular-template/node_modules/.bin/jiti +0 -21
  59. package/dist/boilerplates/angular-template/node_modules/.bin/js-yaml +0 -21
  60. package/dist/boilerplates/angular-template/node_modules/.bin/lessc +0 -21
  61. package/dist/boilerplates/angular-template/node_modules/.bin/ng +0 -21
  62. package/dist/boilerplates/angular-template/node_modules/.bin/ng-xi18n +0 -21
  63. package/dist/boilerplates/angular-template/node_modules/.bin/ngc +0 -21
  64. package/dist/boilerplates/angular-template/node_modules/.bin/nx +0 -21
  65. package/dist/boilerplates/angular-template/node_modules/.bin/nx-cloud +0 -21
  66. package/dist/boilerplates/angular-template/node_modules/.bin/sass +0 -21
  67. package/dist/boilerplates/angular-template/node_modules/.bin/terser +0 -21
  68. package/dist/boilerplates/angular-template/node_modules/.bin/tsc +0 -21
  69. package/dist/boilerplates/angular-template/node_modules/.bin/tsserver +0 -21
  70. package/dist/boilerplates/angular-template/node_modules/.bin/vite +0 -21
  71. package/dist/boilerplates/angular-template/node_modules/.bin/vitest +0 -21
  72. package/dist/boilerplates/angular-template/node_modules/.bin/webpack +0 -21
  73. package/dist/boilerplates/angular-template/node_modules/.bin/yaml +0 -21
  74. package/dist/boilerplates/nest-template/node_modules/.bin/acorn +0 -21
  75. package/dist/boilerplates/nest-template/node_modules/.bin/nest +0 -21
  76. package/dist/boilerplates/nest-template/node_modules/.bin/prettier +0 -21
  77. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node +0 -21
  78. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-cwd +0 -21
  79. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-esm +0 -21
  80. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-script +0 -21
  81. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-transpile-only +0 -21
  82. package/dist/boilerplates/nest-template/node_modules/.bin/ts-script +0 -21
  83. package/dist/boilerplates/nest-template/node_modules/.bin/tsc +0 -21
  84. package/dist/boilerplates/nest-template/node_modules/.bin/tsserver +0 -21
  85. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm +0 -21
  86. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-commonjs +0 -21
  87. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-esm +0 -21
  88. package/dist/boilerplates/nest-template/node_modules/.bin/webpack +0 -21
  89. package/dist/templates/nest/microservice.templates.d.ts +0 -4
  90. package/dist/templates/nest/microservice.templates.js +0 -157
package/dist/index.js CHANGED
@@ -34,15 +34,21 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.program = void 0;
38
+ exports.patchLocalDependencies = patchLocalDependencies;
37
39
  const commander_1 = require("commander");
38
40
  const path = __importStar(require("path"));
39
41
  const fs = __importStar(require("fs-extra"));
40
42
  const cli_templates_1 = require("./templates/cli-templates");
43
+ const functional_module_templates_1 = require("./templates/nest/functional-module.templates");
41
44
  const ui_templates_1 = require("./templates/ui-templates");
42
45
  const linter_rules_1 = require("./rules/linter-rules");
43
46
  const relationships_1 = require("./templates/shared/relationships");
44
47
  const sort_entities_1 = require("./migrations/sort-entities");
45
48
  const child_process_1 = require("child_process");
49
+ const devkit_core_1 = require("@devstroupe/devkit-core");
50
+ const backend_layout_1 = require("./generation/backend-layout");
51
+ const entity_module_move_1 = require("./migrations/entity-module-move");
46
52
  // Registra ts-node para poder carregar arquivos .ts (como o config)
47
53
  try {
48
54
  require('ts-node').register({
@@ -53,6 +59,7 @@ catch (e) {
53
59
  // Silencioso se ts-node não estiver presente localmente
54
60
  }
55
61
  const program = new commander_1.Command();
62
+ exports.program = program;
56
63
  const SPARTAN_STYLES = ['nova', 'vega', 'lyra', 'maia', 'mira', 'luma'];
57
64
  function isSpartanStyle(value) {
58
65
  return SPARTAN_STYLES.includes(value);
@@ -62,12 +69,7 @@ function getDesignSystemConfig(config) {
62
69
  }
63
70
  function assertValidRelationshipConfig(config) {
64
71
  (0, relationships_1.validateRelationships)(config.entities ?? []);
65
- if (config.backendType !== 'microservices')
66
- return;
67
- const manyToMany = (config.entities ?? []).flatMap((item) => (0, relationships_1.getRelationships)(item.properties ?? []).filter((relation) => relation.kind === 'many-to-many'));
68
- if (manyToMany.length > 0) {
69
- throw new Error('Many-to-many relationships require a dedicated junction CRUD when backendType is "microservices".');
70
- }
72
+ (0, backend_layout_1.validateModuleRelationships)(config.entities ?? []);
71
73
  }
72
74
  function ensureMigrationInfrastructure(backendRoot) {
73
75
  const databaseDir = path.join(backendRoot, 'src', 'database');
@@ -111,6 +113,112 @@ function ensureInitialMigrations(backendRoot, entities, propertyOverrides = new
111
113
  console.log(`[Migration] Criada ${fileName}`);
112
114
  });
113
115
  }
116
+ const CRUD_USE_CASE_KINDS = ['create', 'update', 'find', 'list', 'soft-delete'];
117
+ function generatedEntityReady(entityRoot, entityName) {
118
+ return fs.existsSync(path.join(entityRoot, entityName, 'application', 'use-cases', `${entityName}-crud.application.ts`));
119
+ }
120
+ function writeCrudEntityArtifacts(entityRoot, entity, properties, tenantScoped, moduleName) {
121
+ const root = path.join(entityRoot, entity.name);
122
+ const useCases = path.join(root, 'application', 'use-cases');
123
+ const ports = path.join(root, 'application', 'ports');
124
+ const persistence = path.join(root, 'infrastructure', 'persistence', 'typeorm');
125
+ const api = path.join(root, 'api');
126
+ [useCases, ports, persistence, api].forEach((directory) => fs.ensureDirSync(directory));
127
+ fs.writeFileSync(path.join(persistence, `${entity.name}.orm-entity.ts`), (0, cli_templates_1.nestOrmEntityTemplate)(entity.name, properties, entity.tableName || `${entity.name}s`, tenantScoped, entity.indexes, { ...(entity.persistence ?? {}), deletion: 'soft-delete' }, entity.automaticFields, '../../../../'), 'utf-8');
128
+ fs.writeFileSync(path.join(api, `${entity.name}.dto.ts`), (0, cli_templates_1.nestDtoTemplate)(entity.name, properties), 'utf-8');
129
+ fs.writeFileSync(path.join(ports, `${entity.name}.repository.port.ts`), (0, functional_module_templates_1.nestRepositoryPortV2Template)(entity.name), 'utf-8');
130
+ fs.writeFileSync(path.join(persistence, `${entity.name}.repository.ts`), (0, functional_module_templates_1.nestRepositoryAdapterV2Template)(entity.name, properties, tenantScoped), 'utf-8');
131
+ for (const kind of CRUD_USE_CASE_KINDS) {
132
+ fs.writeFileSync(path.join(useCases, `${kind}-${entity.name}.use-case.ts`), (0, functional_module_templates_1.nestCrudUseCaseTemplate)(entity.name, kind), 'utf-8');
133
+ }
134
+ fs.writeFileSync(path.join(useCases, `${entity.name}-crud.application.ts`), (0, functional_module_templates_1.nestCrudApplicationTemplate)(entity.name), 'utf-8');
135
+ fs.writeFileSync(path.join(api, `${entity.name}.controller.ts`), (0, functional_module_templates_1.nestControllerV2Template)(entity.name, moduleName), 'utf-8');
136
+ }
137
+ function generatedMembers(moduleRoot, entities, current) {
138
+ return entities.filter((candidate) => candidate.name === current.name || generatedEntityReady(moduleRoot, candidate.name));
139
+ }
140
+ function migrationPropertiesForEntity(entity, config) {
141
+ const properties = (0, backend_layout_1.persistencePropertiesForEntity)(entity, config.entities ?? []);
142
+ const tenantScoped = Boolean(config.multiTenant?.enabled && entity.tenantScoped !== false);
143
+ return tenantScoped && !properties.some((property) => property.name === 'tenantId')
144
+ ? [...properties, { name: 'tenantId', label: 'Tenant', type: 'string', required: true, index: true }]
145
+ : properties;
146
+ }
147
+ function platformPackageName(projectName) {
148
+ const scope = String(projectName || 'app')
149
+ .trim().toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '') || 'app';
150
+ return `@${scope}/platform`;
151
+ }
152
+ function linkPlatformPackage(applicationRoot, platformRoot, packageName) {
153
+ const packagePath = path.join(applicationRoot, 'package.json');
154
+ if (!fs.existsSync(packagePath))
155
+ return;
156
+ const localPackageRoot = path.join(applicationRoot, 'local_packages', 'project-platform');
157
+ fs.removeSync(localPackageRoot);
158
+ fs.copySync(platformRoot, localPackageRoot, {
159
+ filter: (source) => !source.split(path.sep).some((part) => ['node_modules', 'dist'].includes(part)),
160
+ });
161
+ const packageJson = fs.readJsonSync(packagePath);
162
+ packageJson.dependencies ??= {};
163
+ packageJson.dependencies[packageName] = 'file:local_packages/project-platform';
164
+ fs.writeJsonSync(packagePath, packageJson, { spaces: 2 });
165
+ }
166
+ function ensurePlatformLayout(backendRoot, microservices, projectName = 'app') {
167
+ const platformRoot = microservices
168
+ ? path.join(backendRoot, 'packages', 'platform')
169
+ : path.join(backendRoot, 'src', 'platform');
170
+ const capabilitiesRoot = microservices ? path.join(platformRoot, 'src') : platformRoot;
171
+ const capabilities = ['security', 'tenancy', 'http', 'messaging', 'observability', 'storage', 'integrations'];
172
+ capabilities.forEach((capability) => {
173
+ const capabilityRoot = path.join(capabilitiesRoot, capability);
174
+ fs.ensureDirSync(capabilityRoot);
175
+ const capabilityReadme = path.join(capabilityRoot, 'README.md');
176
+ if (!fs.existsSync(capabilityReadme)) {
177
+ fs.writeFileSync(capabilityReadme, `# ${capability}\n\nAdapters técnicos compartilhados de ${capability}. Não importe entities ou repositories de negócio aqui.\n`, 'utf-8');
178
+ }
179
+ });
180
+ const readmePath = path.join(platformRoot, 'README.md');
181
+ if (!fs.existsSync(readmePath)) {
182
+ fs.writeFileSync(readmePath, `# Platform\n\nInfraestrutura técnica compartilhada. Este diretório não representa um microsserviço.\n\n- Contratos de negócio permanecem nos módulos funcionais.\n- Integrações compartilhadas são nomeadas pela capacidade.\n- Platform nunca importa entities ou repositories de negócio.\n- Em microsserviços, edite este package central; as cópias em local_packages são sincronizadas pelo gerador.\n- Promova uma capacidade a serviço somente quando estado, quota, chave única, persistência, fila, auditoria ou governança central exigirem ownership próprio.\n`, 'utf-8');
183
+ }
184
+ if (microservices) {
185
+ const requestedPackageName = platformPackageName(projectName);
186
+ const platformPackagePath = path.join(platformRoot, 'package.json');
187
+ if (!fs.existsSync(platformPackagePath)) {
188
+ fs.writeJsonSync(platformPackagePath, {
189
+ name: requestedPackageName,
190
+ version: '0.0.0',
191
+ private: true,
192
+ main: './src/index.js',
193
+ types: './src/index.d.ts',
194
+ peerDependencies: { '@devstroupe/devkit-nest': `^${cliVersion}` },
195
+ }, { spaces: 2 });
196
+ }
197
+ const packageName = fs.readJsonSync(platformPackagePath).name;
198
+ const rootJs = path.join(capabilitiesRoot, 'index.js');
199
+ const rootTypes = path.join(capabilitiesRoot, 'index.d.ts');
200
+ if (!fs.existsSync(rootJs))
201
+ fs.writeFileSync(rootJs, `module.exports = require('@devstroupe/devkit-nest');\n`, 'utf-8');
202
+ if (!fs.existsSync(rootTypes))
203
+ fs.writeFileSync(rootTypes, `export * from '@devstroupe/devkit-nest';\n`, 'utf-8');
204
+ for (const capability of capabilities) {
205
+ const capabilityRoot = path.join(capabilitiesRoot, capability);
206
+ const capabilityJs = path.join(capabilityRoot, 'index.js');
207
+ const capabilityTypes = path.join(capabilityRoot, 'index.d.ts');
208
+ if (!fs.existsSync(capabilityJs))
209
+ fs.writeFileSync(capabilityJs, `module.exports = require('@devstroupe/devkit-nest');\n`, 'utf-8');
210
+ if (!fs.existsSync(capabilityTypes))
211
+ fs.writeFileSync(capabilityTypes, `export * from '@devstroupe/devkit-nest';\n`, 'utf-8');
212
+ }
213
+ const applicationRoots = [
214
+ path.join(backendRoot, 'api-gateway'),
215
+ ...(fs.existsSync(path.join(backendRoot, 'apps'))
216
+ ? fs.readdirSync(path.join(backendRoot, 'apps')).map((entry) => path.join(backendRoot, 'apps', entry))
217
+ : []),
218
+ ];
219
+ applicationRoots.forEach((applicationRoot) => linkPlatformPackage(applicationRoot, platformRoot, packageName));
220
+ }
221
+ }
114
222
  function getSpartanStyle(config) {
115
223
  const designSystem = getDesignSystemConfig(config);
116
224
  const configuredStyle = designSystem.spartanStyle || designSystem.style || designSystem.preset || 'vega';
@@ -1105,13 +1213,6 @@ program
1105
1213
  const isMultiTenant = !!config.multiTenant?.enabled;
1106
1214
  const isTenantScoped = isMultiTenant && (entity.tenantScoped !== false);
1107
1215
  const isMicroservices = config.backendType === 'microservices';
1108
- const backendProperties = isMicroservices
1109
- ? entity.properties
1110
- .filter((property) => property.type !== 'relationship' || (0, relationships_1.getRelationships)([property])[0].kind !== 'one-to-many')
1111
- .map((property) => property.type === 'relationship'
1112
- ? { ...property, type: 'number', name: (0, relationships_1.getRelationships)([property])[0].foreignKey }
1113
- : property)
1114
- : entity.properties;
1115
1216
  let boilerplatesDir = path.resolve(__dirname, 'boilerplates');
1116
1217
  if (!fs.existsSync(boilerplatesDir)) {
1117
1218
  boilerplatesDir = path.resolve(__dirname, '../boilerplates');
@@ -1120,131 +1221,64 @@ program
1120
1221
  boilerplatesDir = path.resolve(__dirname, '../../boilerplates');
1121
1222
  }
1122
1223
  const nestTemplateDir = path.join(boilerplatesDir, 'nest-template');
1224
+ const group = (0, backend_layout_1.moduleForEntity)(entity, config.entities ?? []);
1225
+ const backendProperties = (0, backend_layout_1.persistencePropertiesForEntity)(entity, config.entities ?? []);
1123
1226
  if (isMicroservices) {
1124
- const entityIndex = config.entities.findIndex((e) => e.name.toLowerCase() === entity.name.toLowerCase());
1125
- const port = 3001 + (entityIndex >= 0 ? entityIndex : 0);
1126
- // Garantir estrutura do API Gateway
1127
1227
  const gatewayRoot = path.join(backendRoot, 'api-gateway');
1128
- if (!fs.existsSync(gatewayRoot) || !fs.existsSync(path.join(gatewayRoot, 'src', 'app.module.ts'))) {
1129
- console.log(`[Gateway] Inicializando estrutura do API Gateway...`);
1228
+ if (!fs.existsSync(path.join(gatewayRoot, 'src', 'app.module.ts'))) {
1229
+ console.log('[Gateway] Inicializando estrutura do API Gateway...');
1130
1230
  fs.ensureDirSync(gatewayRoot);
1131
- fs.copySync(nestTemplateDir, gatewayRoot);
1132
- const gatewayEnv = `PORT=13000
1133
- DB_HOST=database
1134
- DB_PORT=3306
1135
- DB_USER=root
1136
- DB_PASSWORD=root
1137
- DB_NAME=devstroupe
1138
- JWT_SECRET=devstroupe-secret-key-12345
1139
- `;
1231
+ fs.copySync(nestTemplateDir, gatewayRoot, { filter: createBoilerplateCopyFilter(nestTemplateDir) });
1232
+ const gatewayEnv = `PORT=13000\nJWT_SECRET=devstroupe-secret-key-12345\n`;
1140
1233
  fs.writeFileSync(path.join(gatewayRoot, '.env'), gatewayEnv, 'utf-8');
1141
1234
  fs.writeFileSync(path.join(gatewayRoot, '.env.example'), gatewayEnv, 'utf-8');
1142
1235
  patchLocalDependencies(gatewayRoot, null, boilerplatesDir);
1143
- try {
1144
- console.log(`[Gateway] Instalando dependências em: ${gatewayRoot}...`);
1145
- (0, child_process_1.execSync)('npm install', { cwd: gatewayRoot, stdio: 'inherit' });
1146
- }
1147
- catch (err) {
1148
- console.warn(`[Aviso] Falha ao rodar "npm install" no API Gateway. Instale manualmente.`);
1149
- }
1236
+ prepareStatelessGateway(gatewayRoot);
1150
1237
  }
1151
- const msRoot = path.join(backendRoot, 'services', entity.name);
1152
- console.log(`Gerando Microsserviço para a entidade "${currentEntityName}" em: ${msRoot}...`);
1153
- if (!fs.existsSync(msRoot) || !fs.existsSync(path.join(msRoot, 'src', 'app.module.ts'))) {
1154
- console.log(`[Microsserviço] Inicializando estrutura do microsserviço...`);
1238
+ const msRoot = path.join(backendRoot, 'apps', group.serviceName);
1239
+ if (!fs.existsSync(path.join(msRoot, 'src', 'app.module.ts'))) {
1240
+ console.log(`[Microsserviço] Inicializando ${group.serviceName}...`);
1155
1241
  fs.ensureDirSync(msRoot);
1156
- fs.copySync(nestTemplateDir, msRoot);
1157
- // Limpa módulos desnecessários
1158
- fs.removeSync(path.join(msRoot, 'src', 'modules', 'user'));
1159
- fs.removeSync(path.join(msRoot, 'src', 'modules', 'auth'));
1160
- // Injeta arquivos específicos de microsserviço
1161
- fs.writeFileSync(path.join(msRoot, 'src', 'main.ts'), generateMsMainTs(port), 'utf-8');
1162
- fs.writeFileSync(path.join(msRoot, 'src', 'app.module.ts'), generateMsAppModule(entity.name), 'utf-8');
1163
- // Injeta env básico no microsserviço
1164
- const msEnv = `PORT=${port}
1165
- DB_HOST=database
1166
- DB_PORT=3306
1167
- DB_USER=root
1168
- DB_PASSWORD=root
1169
- DB_NAME=devstroupe_${entity.name.toLowerCase()}
1170
- SERVICE_PORT=${port}
1171
- `;
1242
+ fs.copySync(nestTemplateDir, msRoot, { filter: createBoilerplateCopyFilter(nestTemplateDir) });
1243
+ const isCoreService = group.name === 'core';
1244
+ if (!isCoreService) {
1245
+ const jwtGuard = fs.readFileSync(path.join(msRoot, 'src', 'modules', 'auth', 'jwt-auth.guard.ts'), 'utf-8');
1246
+ for (const moduleName of ['user', 'auth', 'role', 'storage', 'core']) {
1247
+ fs.removeSync(path.join(msRoot, 'src', 'modules', moduleName));
1248
+ }
1249
+ const authRoot = path.join(msRoot, 'src', 'modules', 'auth');
1250
+ fs.ensureDirSync(authRoot);
1251
+ fs.writeFileSync(path.join(authRoot, 'jwt-auth.guard.ts'), jwtGuard, 'utf-8');
1252
+ fs.emptyDirSync(path.join(msRoot, 'src', 'database', 'migrations'));
1253
+ }
1254
+ fs.writeFileSync(path.join(msRoot, 'src', 'main.ts'), generateMsMainTs(group.port), 'utf-8');
1255
+ fs.writeFileSync(path.join(msRoot, 'src', 'app.module.ts'), generateMsAppModule(group.name, isCoreService), 'utf-8');
1256
+ const msEnv = `PORT=${group.port}\nDB_HOST=database\nDB_PORT=3306\nDB_USER=root\nDB_PASSWORD=root\nDB_NAME=${group.databaseName}\nSERVICE_PORT=${group.port}\n`;
1172
1257
  fs.writeFileSync(path.join(msRoot, '.env'), msEnv, 'utf-8');
1173
1258
  fs.writeFileSync(path.join(msRoot, '.env.example'), msEnv, 'utf-8');
1174
1259
  patchLocalDependencies(msRoot, null, boilerplatesDir);
1175
- try {
1176
- console.log(`[Microsserviço] Instalando dependências em: ${msRoot}...`);
1177
- (0, child_process_1.execSync)('npm install', { cwd: msRoot, stdio: 'inherit' });
1178
- }
1179
- catch (err) {
1180
- console.warn(`[Aviso] Falha ao rodar "npm install" no microsserviço. Instale manualmente.`);
1181
- }
1182
1260
  }
1183
- const modulePath = path.join(msRoot, 'src', 'modules', entity.name);
1184
- fs.ensureDirSync(path.join(modulePath, 'domain'));
1185
- fs.ensureDirSync(path.join(modulePath, 'service'));
1186
- fs.ensureDirSync(path.join(modulePath, 'infra', 'database'));
1187
- fs.ensureDirSync(path.join(modulePath, 'infra', 'http'));
1188
- // Domain Entity
1189
- fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.ts`), (0, cli_templates_1.nestDomainEntityTemplate)(entity.name, backendProperties, isTenantScoped, entity.automaticFields), 'utf-8');
1190
- // Repository Port
1191
- fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.repository.port.ts`), (0, cli_templates_1.nestRepositoryPortTemplate)(entity.name), 'utf-8');
1192
- // DTO
1193
- fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.dto.ts`), (0, cli_templates_1.nestDtoTemplate)(entity.name, backendProperties), 'utf-8');
1194
- // Controller (TCP)
1195
- fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.ms.controller.ts`), (0, cli_templates_1.nestMsControllerTemplate)(entity.name), 'utf-8');
1196
- // ORM Entity
1197
- fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.orm-entity.ts`), (0, cli_templates_1.nestOrmEntityTemplate)(entity.name, backendProperties, entity.tableName || `${entity.name}s`, isTenantScoped, entity.indexes, entity.persistence, entity.automaticFields), 'utf-8');
1198
- // Repository Adapter
1199
- fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.repository.ts`), (0, cli_templates_1.nestRepositoryAdapterTemplate)(entity.name, backendProperties, isTenantScoped), 'utf-8');
1200
- // Service
1201
- fs.writeFileSync(path.join(modulePath, 'service', `${entity.name}.service.ts`), (0, cli_templates_1.nestServiceTemplate)(entity.name), 'utf-8');
1202
- // Module (TCP)
1203
- fs.writeFileSync(path.join(modulePath, `${entity.name}.module.ts`), (0, cli_templates_1.nestMsModuleTemplate)(entity.name), 'utf-8');
1204
- console.log(`[Microsserviço] Arquivos do microsserviço "${currentEntityName}" gerados com sucesso.`);
1261
+ const moduleRoot = path.join(msRoot, 'src', 'modules', group.name);
1262
+ writeCrudEntityArtifacts(moduleRoot, entity, backendProperties, isTenantScoped, group.name);
1263
+ const members = generatedMembers(moduleRoot, group.entities, entity);
1264
+ fs.writeFileSync(path.join(moduleRoot, `${group.name}.module.ts`), (0, functional_module_templates_1.nestFunctionalModuleTemplate)(group.name, members), 'utf-8');
1205
1265
  if (entity.crud?.generateMigration !== false) {
1206
- ensureInitialMigrations(msRoot, [entity], new Map([[entity.name, backendProperties]]));
1266
+ ensureInitialMigrations(msRoot, members, new Map(members.map((member) => [member.name, migrationPropertiesForEntity(member, config)])));
1207
1267
  }
1208
- // --- Gerar API Gateway Module ---
1209
- const gatewayModulePath = path.join(backendRoot, 'api-gateway', 'src', 'modules', entity.name);
1210
- console.log(`Gerando Módulo Gateway para a entidade "${currentEntityName}" em: ${gatewayModulePath}...`);
1211
- fs.ensureDirSync(path.join(gatewayModulePath, 'infra', 'http'));
1212
- // DTO (Gateway)
1213
- fs.writeFileSync(path.join(gatewayModulePath, 'infra', 'http', `${entity.name}.dto.ts`), (0, cli_templates_1.nestDtoTemplate)(entity.name, backendProperties), 'utf-8');
1214
- // Controller (Gateway)
1215
- fs.writeFileSync(path.join(gatewayModulePath, 'infra', 'http', `${entity.name}.controller.ts`), (0, cli_templates_1.nestGatewayControllerTemplate)(entity.name), 'utf-8');
1216
- // Module (Gateway)
1217
- fs.writeFileSync(path.join(gatewayModulePath, `${entity.name}.module.ts`), (0, cli_templates_1.nestGatewayModuleTemplate)(entity.name, port), 'utf-8');
1218
- // Patch app.module.ts do Gateway
1219
- patchGatewayAppModule(path.join(backendRoot, 'api-gateway', 'src', 'app.module.ts'), entity.name);
1220
- console.log(`[Gateway] Módulo Gateway "${currentEntityName}" gerado com sucesso.`);
1268
+ const gatewayModuleRoot = path.join(gatewayRoot, 'src', 'modules', group.name);
1269
+ fs.ensureDirSync(gatewayModuleRoot);
1270
+ fs.writeFileSync(path.join(gatewayModuleRoot, `${group.name}.gateway.controller.ts`), (0, functional_module_templates_1.nestGatewayModuleProxyControllerTemplate)(group.name, group.port), 'utf-8');
1271
+ fs.writeFileSync(path.join(gatewayModuleRoot, `${group.name}.module.ts`), (0, functional_module_templates_1.nestGatewayFunctionalModuleTemplate)(group.name, group.port), 'utf-8');
1272
+ patchGatewayAppModule(path.join(gatewayRoot, 'src', 'app.module.ts'), group.name);
1273
+ console.log(`[Microsserviço] ${currentEntityName} adicionado a ${group.serviceName}.`);
1221
1274
  }
1222
1275
  else {
1223
- // Monolito
1224
- console.log(`Gerando arquivos NestJS Monolito para a entidade "${currentEntityName}"...`);
1225
- const modulePath = path.join(backendRoot, 'src', 'modules', entity.name);
1226
- fs.ensureDirSync(path.join(modulePath, 'domain'));
1227
- fs.ensureDirSync(path.join(modulePath, 'service'));
1228
- fs.ensureDirSync(path.join(modulePath, 'infra', 'database'));
1229
- fs.ensureDirSync(path.join(modulePath, 'infra', 'http'));
1230
- // Domain Entity
1231
- fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.ts`), (0, cli_templates_1.nestDomainEntityTemplate)(entity.name, entity.properties, isTenantScoped, entity.automaticFields), 'utf-8');
1232
- // Repository Port
1233
- fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.repository.port.ts`), (0, cli_templates_1.nestRepositoryPortTemplate)(entity.name), 'utf-8');
1234
- // DTO
1235
- fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.dto.ts`), (0, cli_templates_1.nestDtoTemplate)(entity.name, entity.properties), 'utf-8');
1236
- // Controller
1237
- fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.controller.ts`), (0, cli_templates_1.nestControllerTemplate)(entity.name, entity.persistence), 'utf-8');
1238
- // ORM Entity
1239
- fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.orm-entity.ts`), (0, cli_templates_1.nestOrmEntityTemplate)(entity.name, entity.properties, entity.tableName || `${entity.name}s`, isTenantScoped, entity.indexes, entity.persistence, entity.automaticFields), 'utf-8');
1240
- // Repository Adapter
1241
- fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.repository.ts`), (0, cli_templates_1.nestRepositoryAdapterTemplate)(entity.name, entity.properties, isTenantScoped), 'utf-8');
1242
- // Service
1243
- fs.writeFileSync(path.join(modulePath, 'service', `${entity.name}.service.ts`), (0, cli_templates_1.nestServiceTemplate)(entity.name), 'utf-8');
1244
- // Module
1245
- fs.writeFileSync(path.join(modulePath, `${entity.name}.module.ts`), (0, cli_templates_1.nestModuleTemplate)(entity.name), 'utf-8');
1246
- console.log(`[NestJS] Módulo Monolito "${currentEntityName}" gerado com sucesso em: ${modulePath}`);
1247
- patchMonolithAppModule(path.join(backendRoot, 'src', 'app.module.ts'), entity.name);
1276
+ const moduleRoot = path.join(backendRoot, 'src', 'modules', group.name);
1277
+ writeCrudEntityArtifacts(moduleRoot, entity, backendProperties, isTenantScoped, group.name);
1278
+ const members = generatedMembers(moduleRoot, group.entities, entity);
1279
+ fs.writeFileSync(path.join(moduleRoot, `${group.name}.module.ts`), (0, functional_module_templates_1.nestFunctionalModuleTemplate)(group.name, members), 'utf-8');
1280
+ patchMonolithAppModule(path.join(backendRoot, 'src', 'app.module.ts'), group.name);
1281
+ console.log(`[NestJS] ${currentEntityName} adicionado ao módulo funcional ${group.name}.`);
1248
1282
  }
1249
1283
  }
1250
1284
  // 2. Gerar Frontend (Angular)
@@ -1268,7 +1302,7 @@ SERVICE_PORT=${port}
1268
1302
  fs.ensureDirSync(path.join(modulePath, 'form-dialog-handler'));
1269
1303
  }
1270
1304
  // Angular Service
1271
- fs.writeFileSync(path.join(servicePath, `${entity.name}.service.ts`), (0, cli_templates_1.angularServiceTemplate)(entity.name), 'utf-8');
1305
+ fs.writeFileSync(path.join(servicePath, `${entity.name}.service.ts`), (0, cli_templates_1.angularServiceTemplate)(entity.name, (0, devkit_core_1.resolveEntityModule)(entity)), 'utf-8');
1272
1306
  // List HTML
1273
1307
  fs.writeFileSync(path.join(modulePath, 'list', `${entity.name}-list.component.html`), (0, cli_templates_1.angularListHtmlTemplate)(entity.name, entity.properties, listType, formType, filterType), 'utf-8');
1274
1308
  // List TS
@@ -1287,11 +1321,15 @@ SERVICE_PORT=${port}
1287
1321
  // 3. Pós-processamento geral (depois de gerar todas as entidades selecionadas)
1288
1322
  // Sincronizar migrações no Monolito, se houver
1289
1323
  const isMicroservices = config.backendType === 'microservices';
1324
+ ensurePlatformLayout(backendRoot, isMicroservices, config.projectName);
1290
1325
  if (!isMicroservices) {
1291
1326
  const migrationEntities = (config.entities ?? []).filter((configuredEntity) => configuredEntity.crud?.generateBackend !== false
1292
1327
  && configuredEntity.crud?.generateMigration !== false);
1293
1328
  if (migrationEntities.length > 0) {
1294
- ensureInitialMigrations(backendRoot, migrationEntities);
1329
+ ensureInitialMigrations(backendRoot, migrationEntities, new Map(migrationEntities.map((member) => [
1330
+ member.name,
1331
+ migrationPropertiesForEntity(member, config),
1332
+ ])));
1295
1333
  }
1296
1334
  }
1297
1335
  // Regerar docker-compose.yml e init.sql
@@ -1371,8 +1409,6 @@ function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
1371
1409
  return;
1372
1410
  const packageJson = fs.readJsonSync(packagePath);
1373
1411
  delete packageJson.devDependencies;
1374
- delete packageJson.peerDependencies;
1375
- delete packageJson.dependencies;
1376
1412
  packageJson.scripts = {};
1377
1413
  fs.writeJsonSync(packagePath, packageJson, { spaces: 2 });
1378
1414
  };
@@ -1442,17 +1478,19 @@ function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
1442
1478
  console.error('[Erro] Falha ao injetar links locais no backend:', err);
1443
1479
  }
1444
1480
  }
1445
- // Patch Backend Dockerfile para injetar "COPY local_packages/ ./local_packages/" antes do npm install e limpar node_modules local após instalação
1481
+ // File dependencies keep their runtime dependencies under local_packages.
1482
+ // Removing that node_modules directory makes packaged drivers unavailable.
1483
+ // Peer dependencies remain resolved by the application root.
1446
1484
  const backendDockerfile = path.join(backendDest, 'Dockerfile');
1447
1485
  if (fs.existsSync(backendDockerfile)) {
1448
1486
  try {
1449
1487
  let content = fs.readFileSync(backendDockerfile, 'utf-8');
1450
1488
  const targetStr = 'COPY package*.json ./';
1451
1489
  const replacementStr = 'COPY package*.json ./\nCOPY local_packages/ ./local_packages/';
1452
- content = content.split(targetStr).join(replacementStr);
1453
- // Injeta limpeza de node_modules locais após npm install
1454
- content = content.replace('RUN npm install --only=production', 'RUN npm install --only=production && rm -rf local_packages/*/node_modules');
1455
- content = content.replace('RUN npm install', 'RUN npm install && rm -rf local_packages/*/node_modules');
1490
+ if (!content.includes('COPY local_packages/ ./local_packages/')) {
1491
+ content = content.split(targetStr).join(replacementStr);
1492
+ }
1493
+ content = content.replaceAll(' && rm -rf local_packages/*/node_modules', '');
1456
1494
  fs.writeFileSync(backendDockerfile, content, 'utf-8');
1457
1495
  console.log('[DevConfig] Dockerfile do backend ajustado para incluir dependências locais.');
1458
1496
  }
@@ -1530,16 +1568,17 @@ function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
1530
1568
  console.error('[Erro] Falha ao injetar links locais no frontend:', err);
1531
1569
  }
1532
1570
  }
1533
- // Patch Frontend Dockerfile para injetar "COPY local_packages/ ./local_packages/" antes do npm install e limpar node_modules local após instalação
1571
+ // Preserve runtime dependencies installed for local file packages.
1534
1572
  const frontendDockerfile = path.join(frontendDest, 'Dockerfile');
1535
1573
  if (fs.existsSync(frontendDockerfile)) {
1536
1574
  try {
1537
1575
  let content = fs.readFileSync(frontendDockerfile, 'utf-8');
1538
1576
  const targetStr = 'COPY package*.json ./';
1539
1577
  const replacementStr = 'COPY package*.json ./\nCOPY local_packages/ ./local_packages/';
1540
- content = content.split(targetStr).join(replacementStr);
1541
- // Injeta limpeza de node_modules locais após npm install
1542
- content = content.replace('RUN npm install --legacy-peer-deps', 'RUN npm install --legacy-peer-deps && rm -rf local_packages/*/node_modules');
1578
+ if (!content.includes('COPY local_packages/ ./local_packages/')) {
1579
+ content = content.split(targetStr).join(replacementStr);
1580
+ }
1581
+ content = content.replaceAll(' && rm -rf local_packages/*/node_modules', '');
1543
1582
  fs.writeFileSync(frontendDockerfile, content, 'utf-8');
1544
1583
  console.log('[DevConfig] Dockerfile do frontend ajustado para incluir dependências locais.');
1545
1584
  }
@@ -1607,32 +1646,51 @@ if (fs.existsSync(envPath)) {
1607
1646
  }
1608
1647
 
1609
1648
  import { NestFactory } from '@nestjs/core';
1610
- import { Transport, MicroserviceOptions } from '@nestjs/microservices';
1611
1649
  import { AppModule } from './app.module';
1650
+ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
1612
1651
 
1613
1652
  async function bootstrap() {
1614
1653
  const port = Number(process.env.SERVICE_PORT) || ${port};
1615
- const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
1616
- transport: Transport.TCP,
1617
- options: {
1618
- host: '0.0.0.0',
1619
- port: port,
1620
- },
1621
- });
1622
- await app.listen();
1623
- console.log(\`Microsserviço iniciado via TCP na porta \${port}\`);
1654
+ const app = await NestFactory.create(AppModule);
1655
+ app.setGlobalPrefix('api');
1656
+ const swagger = new DocumentBuilder().setTitle('Module API').setVersion('1.0').addBearerAuth().build();
1657
+ SwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, swagger));
1658
+ await app.listen(port, '0.0.0.0');
1659
+ console.log(\`Serviço HTTP iniciado na porta \${port}\`);
1624
1660
  }
1625
1661
  bootstrap();
1626
1662
  `;
1627
1663
  }
1628
- function generateMsAppModule(entityName) {
1664
+ function generateMsAppModule(entityName, coreService) {
1629
1665
  const pascalName = kebabToPascal(entityName);
1666
+ const securityImport = coreService
1667
+ ? `import { SecurityModule } from './platform/security/security.module';`
1668
+ : `import { TokenVerificationModule } from './platform/security/token-verification.module';`;
1669
+ const securityModule = coreService ? 'SecurityModule' : 'TokenVerificationModule';
1670
+ const storageImports = coreService
1671
+ ? `import { StorageModule } from '@devstroupe/devkit-nest';`
1672
+ : '';
1673
+ const storageRegistration = coreService
1674
+ ? `
1675
+ (StorageModule as any).registerAsync({
1676
+ useFactory: () => ({
1677
+ driver: (process.env.STORAGE_DRIVER || 'local') as 'local' | 's3' | 'r2',
1678
+ local: { storagePath: process.env.STORAGE_LOCAL_PATH || './storage' },
1679
+ }),
1680
+ }),`
1681
+ : '';
1630
1682
  return `import { Module } from '@nestjs/common';
1683
+ import { APP_INTERCEPTOR } from '@nestjs/core';
1631
1684
  import { TypeOrmModule } from '@nestjs/typeorm';
1685
+ import { ExecutionContextInterceptor } from '@devstroupe/devkit-nest';
1686
+ import * as path from 'path';
1632
1687
  import { ${pascalName}Module } from './modules/${entityName}/${entityName}.module';
1688
+ ${securityImport}
1689
+ ${storageImports}
1633
1690
 
1634
1691
  @Module({
1635
1692
  imports: [
1693
+ ${securityModule},
1636
1694
  TypeOrmModule.forRoot({
1637
1695
  type: 'mysql',
1638
1696
  host: process.env.DB_HOST || 'localhost',
@@ -1642,15 +1700,49 @@ import { ${pascalName}Module } from './modules/${entityName}/${entityName}.modul
1642
1700
  database: process.env.DB_NAME || 'devstroupe_${entityName}',
1643
1701
  autoLoadEntities: true,
1644
1702
  synchronize: false,
1703
+ migrations: [path.join(__dirname, './database/migrations/*{.ts,.js}')],
1704
+ migrationsRun: true,
1645
1705
  }),
1646
- ${pascalName}Module,
1706
+ ${pascalName}Module,${storageRegistration}
1647
1707
  ],
1648
1708
  controllers: [],
1649
- providers: [],
1709
+ providers: [{ provide: APP_INTERCEPTOR, useClass: ExecutionContextInterceptor }],
1710
+ })
1711
+ export class AppModule {}
1712
+ `;
1713
+ }
1714
+ function generateGatewayMainTs() {
1715
+ return `import { NestFactory } from '@nestjs/core';
1716
+ import { AppModule } from './app.module';
1717
+
1718
+ async function bootstrap() {
1719
+ const app = await NestFactory.create(AppModule, { bodyParser: false });
1720
+ app.setGlobalPrefix('api');
1721
+ app.enableCors();
1722
+ const port = Number(process.env.PORT) || 13000;
1723
+ await app.listen(port, '0.0.0.0');
1724
+ console.log(\`API Gateway stateless iniciado na porta \${port}\`);
1725
+ }
1726
+ bootstrap();
1727
+ `;
1728
+ }
1729
+ function generateGatewayAppModule() {
1730
+ return `import { Module } from '@nestjs/common';
1731
+ import { TokenVerificationModule } from './platform/security/token-verification.module';
1732
+
1733
+ @Module({
1734
+ imports: [TokenVerificationModule],
1650
1735
  })
1651
1736
  export class AppModule {}
1652
1737
  `;
1653
1738
  }
1739
+ function prepareStatelessGateway(gatewayRoot) {
1740
+ fs.removeSync(path.join(gatewayRoot, 'src', 'database'));
1741
+ fs.emptyDirSync(path.join(gatewayRoot, 'src', 'modules'));
1742
+ fs.removeSync(path.join(gatewayRoot, 'src', 'platform', 'security', 'security.module.ts'));
1743
+ fs.writeFileSync(path.join(gatewayRoot, 'src', 'main.ts'), generateGatewayMainTs(), 'utf-8');
1744
+ fs.writeFileSync(path.join(gatewayRoot, 'src', 'app.module.ts'), generateGatewayAppModule(), 'utf-8');
1745
+ }
1654
1746
  function patchGatewayAppModule(appModulePath, entityName) {
1655
1747
  const pascalName = kebabToPascal(entityName);
1656
1748
  const moduleName = `${pascalName}GatewayModule`;
@@ -1667,16 +1759,11 @@ function patchGatewayAppModule(appModulePath, entityName) {
1667
1759
  // Adiciona a declaração de import no topo
1668
1760
  const importStatement = `import { ${moduleName} } from '${importPath}';\n`;
1669
1761
  content = importStatement + content;
1670
- // Encontra a lista de imports do decorator @Module
1671
- // Encontra a lista de imports do decorator @Module
1672
- const moduleDecoratorRegex = /@Module\s*\(\s*\{\s*imports\s*:\s*\[([\s\S]*?)\]/;
1673
- const match = content.match(moduleDecoratorRegex);
1674
- if (match) {
1675
- const currentImports = match[1];
1676
- // Adiciona o novo módulo no início dos imports
1677
- const updatedImports = `\n ${moduleName},${currentImports}`;
1678
- content = content.replace(currentImports, updatedImports);
1679
- }
1762
+ const moduleImportsRegex = /(@Module\s*\(\s*\{\s*imports\s*:\s*\[)([\s\S]*?)(\]\s*,?)/;
1763
+ content = content.replace(moduleImportsRegex, (_match, prefix, body, suffix) => {
1764
+ const imports = body.split(',').map((entry) => entry.trim()).filter(Boolean);
1765
+ return `${prefix}\n${[moduleName, ...imports].map((entry) => ` ${entry},`).join('\n')}\n ${suffix}`;
1766
+ });
1680
1767
  fs.writeFileSync(appModulePath, content, 'utf-8');
1681
1768
  console.log(`[Gateway] app.module.ts patcheado com sucesso para importar ${moduleName}.`);
1682
1769
  }
@@ -1705,9 +1792,20 @@ function patchMonolithAppModule(appModulePath, entityName) {
1705
1792
  fs.writeFileSync(appModulePath, content, 'utf-8');
1706
1793
  console.log(`[NestJS] app.module.ts atualizado para importar ${moduleName}.`);
1707
1794
  }
1795
+ function removeMonolithModuleImport(appModulePath, moduleName) {
1796
+ if (!fs.existsSync(appModulePath))
1797
+ return;
1798
+ const pascalName = kebabToPascal(moduleName);
1799
+ const className = `${pascalName}Module`;
1800
+ let content = fs.readFileSync(appModulePath, 'utf-8');
1801
+ content = content.replace(new RegExp(`^import \\{ ${className} \\} from ['"]\\./modules/${moduleName}/${moduleName}\\.module['"];?\\n`, 'm'), '');
1802
+ content = content.replace(new RegExp(`\\n\\s*${className},?`, 'g'), '');
1803
+ fs.writeFileSync(appModulePath, content, 'utf-8');
1804
+ }
1708
1805
  function generateDockerComposeAndDbInit(projectRoot, config) {
1709
1806
  const isMicroservices = config.backendType === 'microservices';
1710
1807
  const projectName = config.projectName || 'devstroupe-project';
1808
+ const moduleGroups = (0, backend_layout_1.buildBackendModuleGroups)(config.entities ?? []);
1711
1809
  // 1. db-init/init.sql
1712
1810
  const dbInitDir = path.join(projectRoot, 'db-init');
1713
1811
  fs.ensureDirSync(dbInitDir);
@@ -1715,12 +1813,9 @@ function generateDockerComposeAndDbInit(projectRoot, config) {
1715
1813
  fs.chmodSync(dbInitDir, 0o755);
1716
1814
  }
1717
1815
  catch { }
1718
- const dbs = ['devstroupe']; // Banco principal (usado pelo gateway ou monolith)
1719
- if (isMicroservices && config.entities) {
1720
- config.entities.forEach((entity) => {
1721
- dbs.push(`devstroupe_${entity.name.toLowerCase()}`);
1722
- });
1723
- }
1816
+ const dbs = isMicroservices
1817
+ ? moduleGroups.map((group) => group.databaseName)
1818
+ : ['devstroupe'];
1724
1819
  const initSqlContent = dbs.map(db => `CREATE DATABASE IF NOT EXISTS \`${db}\`;`).join('\n') + '\n';
1725
1820
  const sqlPath = path.join(dbInitDir, 'init.sql');
1726
1821
  fs.writeFileSync(sqlPath, initSqlContent, 'utf-8');
@@ -1749,24 +1844,18 @@ function generateDockerComposeAndDbInit(projectRoot, config) {
1749
1844
  devServicesYaml += dbServiceYaml;
1750
1845
  if (isMicroservices) {
1751
1846
  // Gateway service
1752
- let gatewayEnv = ` DB_HOST: database
1753
- DB_PORT: 3306
1754
- DB_USER: root
1755
- DB_PASSWORD: root
1756
- DB_NAME: devstroupe
1847
+ let gatewayEnv = ` PORT: 13000
1757
1848
  JWT_SECRET: devstroupe-secret-key-12345
1758
1849
  `;
1759
- let gatewayDependsOn = ` - database
1760
- `;
1850
+ let gatewayDependsOn = '';
1761
1851
  // Adiciona variáveis de microsserviços no gateway
1762
- if (config.entities) {
1763
- config.entities.forEach((entity, index) => {
1764
- const port = 3001 + index;
1765
- const nameUpper = entity.name.toUpperCase();
1766
- gatewayEnv += ` ${nameUpper}_SERVICE_HOST: ${entity.name}-service
1767
- ${nameUpper}_SERVICE_PORT: ${port}
1852
+ if (moduleGroups.length) {
1853
+ moduleGroups.forEach((group) => {
1854
+ const nameUpper = group.name.replace(/-/g, '_').toUpperCase();
1855
+ gatewayEnv += ` ${nameUpper}_SERVICE_HOST: ${group.serviceName}
1856
+ ${nameUpper}_SERVICE_PORT: ${group.port}
1768
1857
  `;
1769
- gatewayDependsOn += ` - ${entity.name}-service
1858
+ gatewayDependsOn += ` - ${group.serviceName}
1770
1859
  `;
1771
1860
  });
1772
1861
  }
@@ -1796,44 +1885,45 @@ ${gatewayEnv} command: sh -c "npm install && npm run start:dev"
1796
1885
  depends_on:
1797
1886
  ${gatewayDependsOn}`;
1798
1887
  // Containers de microsserviços
1799
- if (config.entities) {
1800
- config.entities.forEach((entity, index) => {
1801
- const port = 3001 + index;
1802
- servicesYaml += `\n\n ${entity.name}-service:
1888
+ if (moduleGroups.length) {
1889
+ moduleGroups.forEach((group) => {
1890
+ servicesYaml += `\n\n ${group.serviceName}:
1803
1891
  build:
1804
- context: ./backend/services/${entity.name}
1892
+ context: ./backend/apps/${group.serviceName}
1805
1893
  dockerfile: Dockerfile
1806
- container_name: ${projectName}-${entity.name}-service
1894
+ container_name: ${projectName}-${group.serviceName}
1807
1895
  restart: always
1808
- ports:
1809
- - "${port}0:${port}"
1896
+ expose:
1897
+ - "${group.port}"
1810
1898
  environment:
1811
1899
  DB_HOST: database
1812
1900
  DB_PORT: 3306
1813
1901
  DB_USER: root
1814
1902
  DB_PASSWORD: root
1815
- DB_NAME: devstroupe_${entity.name.toLowerCase()}
1816
- SERVICE_PORT: ${port}
1903
+ DB_NAME: ${group.databaseName}
1904
+ SERVICE_PORT: ${group.port}
1905
+ JWT_SECRET: devstroupe-secret-key-12345
1817
1906
  depends_on:
1818
1907
  - database
1819
1908
  `;
1820
- devServicesYaml += `\n\n ${entity.name}-service:
1909
+ devServicesYaml += `\n\n ${group.serviceName}:
1821
1910
  image: node:20-alpine
1822
- container_name: ${projectName}-${entity.name}-service-dev
1911
+ container_name: ${projectName}-${group.serviceName}-dev
1823
1912
  working_dir: /app
1824
1913
  restart: always
1825
1914
  volumes:
1826
- - ./backend/services/${entity.name}:/app
1915
+ - ./backend/apps/${group.serviceName}:/app
1827
1916
  - /app/node_modules
1828
- ports:
1829
- - "${port}0:${port}"
1917
+ expose:
1918
+ - "${group.port}"
1830
1919
  environment:
1831
1920
  DB_HOST: database
1832
1921
  DB_PORT: 3306
1833
1922
  DB_USER: root
1834
1923
  DB_PASSWORD: root
1835
- DB_NAME: devstroupe_${entity.name.toLowerCase()}
1836
- SERVICE_PORT: ${port}
1924
+ DB_NAME: ${group.databaseName}
1925
+ SERVICE_PORT: ${group.port}
1926
+ JWT_SECRET: devstroupe-secret-key-12345
1837
1927
  command: sh -c "npm install && npm run start:dev"
1838
1928
  depends_on:
1839
1929
  - database
@@ -1864,7 +1954,7 @@ ${gatewayDependsOn}`;
1864
1954
  - "14200:4200"
1865
1955
  command: sh -c "npm install && npm run start -- --host 0.0.0.0 --poll 2000"
1866
1956
  depends_on:
1867
- - api-gateway-dev
1957
+ - api-gateway
1868
1958
  `;
1869
1959
  }
1870
1960
  else {
@@ -1934,16 +2024,12 @@ ${gatewayDependsOn}`;
1934
2024
  - backend
1935
2025
  `;
1936
2026
  }
1937
- const dockerComposeContent = `version: '3.8'
1938
-
1939
- services:
2027
+ const dockerComposeContent = `services:
1940
2028
  ${servicesYaml}
1941
2029
  volumes:
1942
2030
  mysql_data:
1943
2031
  `;
1944
- const dockerComposeDevContent = `version: '3.8'
1945
-
1946
- services:
2032
+ const dockerComposeDevContent = `services:
1947
2033
  ${devServicesYaml}
1948
2034
  volumes:
1949
2035
  mysql_data:
@@ -1963,7 +2049,8 @@ function generateProject(projectRoot, config) {
1963
2049
  let backendDest;
1964
2050
  if (isMicroservices) {
1965
2051
  backendDest = path.resolve(projectRoot, backendPathRel, 'api-gateway');
1966
- fs.ensureDirSync(path.resolve(projectRoot, backendPathRel, 'services'));
2052
+ fs.ensureDirSync(path.resolve(projectRoot, backendPathRel, 'apps'));
2053
+ fs.ensureDirSync(path.resolve(projectRoot, backendPathRel, 'packages', 'platform'));
1967
2054
  }
1968
2055
  else {
1969
2056
  backendDest = path.resolve(projectRoot, backendPathRel);
@@ -1991,7 +2078,9 @@ function generateProject(projectRoot, config) {
1991
2078
  fs.copySync(nestTemplateDir, backendDest, { filter: createBoilerplateCopyFilter(nestTemplateDir) });
1992
2079
  // Gerar arquivos .env e .env.example
1993
2080
  console.log('Gerando arquivos .env e .env.example no backend...');
1994
- const envContent = `PORT=13000
2081
+ const envContent = isMicroservices ? `PORT=13000
2082
+ JWT_SECRET=devstroupe-secret-key-12345
2083
+ ` : `PORT=13000
1995
2084
  DB_HOST=localhost
1996
2085
  DB_PORT=3306
1997
2086
  DB_USER=root
@@ -2016,6 +2105,8 @@ R2_SECRET_KEY=
2016
2105
  `;
2017
2106
  fs.writeFileSync(path.join(backendDest, '.env'), envContent, 'utf-8');
2018
2107
  fs.writeFileSync(path.join(backendDest, '.env.example'), envContent, 'utf-8');
2108
+ if (isMicroservices)
2109
+ prepareStatelessGateway(backendDest);
2019
2110
  }
2020
2111
  if (!fs.existsSync(frontendDest) || fs.readdirSync(frontendDest).length === 0) {
2021
2112
  console.log(`Copiando boilerplate do frontend para: ${frontendDest}...`);
@@ -2345,4 +2436,72 @@ export class Devkit${pascalName}Component {}
2345
2436
  console.log(`[UI] Componente customizado do DevKit "devkit-${componentName}" criado com sucesso em: ${customDir}`);
2346
2437
  }
2347
2438
  });
2348
- program.parse(process.argv);
2439
+ program
2440
+ .command('entity:set-module <entityName> <moduleName>')
2441
+ .description('Planeja ou aplica a movimentação segura de uma entidade entre módulos funcionais')
2442
+ .option('--dry-run', 'Exibe o plano sem alterar arquivos (padrão)')
2443
+ .option('--apply', 'Aplica o plano monolítico quando não há conflitos')
2444
+ .action((entityName, moduleName, options) => {
2445
+ const projectRoot = process.cwd();
2446
+ const configPath = path.join(projectRoot, 'devstroupe.config.ts');
2447
+ if (!fs.existsSync(configPath))
2448
+ throw new Error('devstroupe.config.ts não encontrado.');
2449
+ const imported = require(configPath);
2450
+ const config = imported.default ?? imported;
2451
+ const plan = (0, entity_module_move_1.planEntityModuleMove)(projectRoot, config, entityName, moduleName);
2452
+ console.log(JSON.stringify(plan, null, 2));
2453
+ if (!options.apply) {
2454
+ console.log('[Dry-run] Nenhum arquivo foi alterado. Revise o plano e use --apply para aprovar.');
2455
+ return;
2456
+ }
2457
+ if (plan.conflicts.length) {
2458
+ throw new Error(`Movimentação não aplicada:\n- ${plan.conflicts.join('\n- ')}`);
2459
+ }
2460
+ if (plan.backendType !== 'monolith') {
2461
+ throw new Error('Mudanças de ownership entre bancos exigem plano de dados explícito e não são aplicadas automaticamente.');
2462
+ }
2463
+ const configSource = fs.readFileSync(configPath, 'utf-8');
2464
+ const backupPath = `${configPath}.devstroupe-backup`;
2465
+ if (!fs.existsSync(backupPath))
2466
+ fs.copyFileSync(configPath, backupPath);
2467
+ fs.writeFileSync(configPath, (0, entity_module_move_1.updateEntityModuleInConfig)(configSource, entityName, plan.targetModule), 'utf-8');
2468
+ const entity = config.entities.find((candidate) => candidate.name === entityName);
2469
+ entity.module = plan.targetModule;
2470
+ const move = plan.operations.find((operation) => operation.kind === 'move');
2471
+ fs.ensureDirSync(path.dirname(move.to));
2472
+ fs.moveSync(move.from, move.to, { overwrite: false });
2473
+ const movedController = path.join(move.to, 'api', `${entityName}.controller.ts`);
2474
+ if (fs.existsSync(movedController)) {
2475
+ const source = fs.readFileSync(movedController, 'utf-8');
2476
+ fs.writeFileSync(movedController, source.replace(/@Controller\(['"][^'"]+['"]\)/, `@Controller('${plan.targetModule}/${entityName}')`), 'utf-8');
2477
+ }
2478
+ const backendRoot = path.resolve(projectRoot, config.backendPath ?? './backend');
2479
+ for (const functionalModule of [plan.sourceModule, plan.targetModule]) {
2480
+ const moduleRoot = path.join(backendRoot, 'src', 'modules', functionalModule);
2481
+ const members = config.entities.filter((candidate) => (0, devkit_core_1.resolveEntityModule)(candidate) === functionalModule && generatedEntityReady(moduleRoot, candidate.name));
2482
+ const moduleFile = path.join(moduleRoot, `${functionalModule}.module.ts`);
2483
+ if (members.length || functionalModule === 'core') {
2484
+ fs.writeFileSync(moduleFile, (0, functional_module_templates_1.nestFunctionalModuleTemplate)(functionalModule, members), 'utf-8');
2485
+ patchMonolithAppModule(path.join(backendRoot, 'src', 'app.module.ts'), functionalModule);
2486
+ }
2487
+ else {
2488
+ if (fs.existsSync(moduleFile))
2489
+ fs.removeSync(moduleFile);
2490
+ removeMonolithModuleImport(path.join(backendRoot, 'src', 'app.module.ts'), functionalModule);
2491
+ }
2492
+ }
2493
+ const migrationsRoot = path.join(backendRoot, 'src', 'database', 'migrations');
2494
+ if (fs.existsSync(migrationsRoot)) {
2495
+ for (const file of fs.readdirSync(migrationsRoot).filter((candidate) => candidate.endsWith(`-create-${entityName}.ts`))) {
2496
+ const migrationPath = path.join(migrationsRoot, file);
2497
+ const source = fs.readFileSync(migrationPath, 'utf-8');
2498
+ fs.writeFileSync(migrationPath, source.match(/^\/\/ owner-module:/)
2499
+ ? source.replace(/^\/\/ owner-module:.*$/m, `// owner-module: ${plan.targetModule}`)
2500
+ : `// owner-module: ${plan.targetModule}\n${source}`, 'utf-8');
2501
+ }
2502
+ }
2503
+ console.log(`[Aplicado] ${entityName}: ${plan.sourceModule} -> ${plan.targetModule}. Backup: ${backupPath}`);
2504
+ });
2505
+ if (require.main === module) {
2506
+ program.parse(process.argv);
2507
+ }