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

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 +251 -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 +389 -210
  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
@@ -1309,6 +1347,23 @@ SERVICE_PORT=${port}
1309
1347
  console.log(`\n\x1b[32m[SUCESSO] Scaffolding de CRUD completo para "${entitiesToGenerate[0].name}" finalizado!\x1b[0m`);
1310
1348
  }
1311
1349
  });
1350
+ function hasAngularPartialCompilation(packageRoot) {
1351
+ const realtimeOutput = path.join(packageRoot, 'dist', 'services', 'realtime.service.js');
1352
+ if (!fs.existsSync(realtimeOutput))
1353
+ return false;
1354
+ const content = fs.readFileSync(realtimeOutput, 'utf-8');
1355
+ return content.includes('ɵɵngDeclareInjectable') || content.includes('ɵɵdefineInjectable');
1356
+ }
1357
+ function ensureLocalAngularPackageBuilt(packageRoot) {
1358
+ if (hasAngularPartialCompilation(packageRoot))
1359
+ return;
1360
+ console.log('[DevConfig] Build Angular local ausente ou inválido; recompilando com ngc...');
1361
+ (0, child_process_1.execSync)('npm run build', { cwd: packageRoot, stdio: 'inherit' });
1362
+ if (!hasAngularPartialCompilation(packageRoot)) {
1363
+ throw new Error('O build local de @devstroupe/devkit-angular não contém metadados AOT/partial do Angular. '
1364
+ + 'Execute o build com ngc antes de gerar o projeto.');
1365
+ }
1366
+ }
1312
1367
  // Helper para linkar dependências locais se estiver rodando no monorepo de desenvolvimento
1313
1368
  function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
1314
1369
  const packagesDir = path.resolve(boilerplatesDir, '../../..');
@@ -1365,20 +1420,21 @@ function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
1365
1420
  const coreSrc = path.join(packagesDir, 'devkit-core');
1366
1421
  const nestSrc = path.join(packagesDir, 'devkit-nest');
1367
1422
  const angularSrc = path.join(packagesDir, 'devkit-angular');
1423
+ if (frontendDest && fs.existsSync(frontendDest) && fs.existsSync(angularSrc)) {
1424
+ ensureLocalAngularPackageBuilt(angularSrc);
1425
+ }
1368
1426
  const stripDevelopmentDependencies = (packageDir) => {
1369
1427
  const packagePath = path.join(packageDir, 'package.json');
1370
1428
  if (!fs.existsSync(packagePath))
1371
1429
  return;
1372
1430
  const packageJson = fs.readJsonSync(packagePath);
1373
1431
  delete packageJson.devDependencies;
1374
- delete packageJson.peerDependencies;
1375
- delete packageJson.dependencies;
1376
1432
  packageJson.scripts = {};
1377
1433
  fs.writeJsonSync(packagePath, packageJson, { spaces: 2 });
1378
1434
  };
1379
1435
  const copyFilter = (src) => {
1380
1436
  const relativePath = path.relative(packagesDir, src);
1381
- const ignoreList = ['node_modules', '.git', '.angular', 'tsconfig.json', 'tsconfig.build.json', 'src', 'tests', 'test'];
1437
+ const ignoreList = ['node_modules', '.git', '.angular', 'dist-test', 'tsconfig.json', 'tsconfig.build.json', 'src', 'tests', 'test'];
1382
1438
  const parts = relativePath.split(path.sep);
1383
1439
  if (parts.length > 1) {
1384
1440
  const subParts = parts.slice(1);
@@ -1397,13 +1453,13 @@ function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
1397
1453
  const backendLocalPackages = path.join(backendDest, 'local_packages');
1398
1454
  if (fs.existsSync(coreSrc)) {
1399
1455
  const coreDest = path.join(backendLocalPackages, 'devkit-core');
1400
- fs.ensureDirSync(coreDest);
1456
+ fs.emptyDirSync(coreDest);
1401
1457
  fs.copySync(coreSrc, coreDest, { filter: copyFilter });
1402
1458
  stripDevelopmentDependencies(coreDest);
1403
1459
  }
1404
1460
  if (fs.existsSync(nestSrc)) {
1405
1461
  const nestDest = path.join(backendLocalPackages, 'devkit-nest');
1406
- fs.ensureDirSync(nestDest);
1462
+ fs.emptyDirSync(nestDest);
1407
1463
  fs.copySync(nestSrc, nestDest, { filter: copyFilter });
1408
1464
  stripDevelopmentDependencies(nestDest);
1409
1465
  // Correção de workspace:* para file:../devkit-core
@@ -1442,17 +1498,19 @@ function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
1442
1498
  console.error('[Erro] Falha ao injetar links locais no backend:', err);
1443
1499
  }
1444
1500
  }
1445
- // Patch Backend Dockerfile para injetar "COPY local_packages/ ./local_packages/" antes do npm install e limpar node_modules local após instalação
1501
+ // File dependencies keep their runtime dependencies under local_packages.
1502
+ // Removing that node_modules directory makes packaged drivers unavailable.
1503
+ // Peer dependencies remain resolved by the application root.
1446
1504
  const backendDockerfile = path.join(backendDest, 'Dockerfile');
1447
1505
  if (fs.existsSync(backendDockerfile)) {
1448
1506
  try {
1449
1507
  let content = fs.readFileSync(backendDockerfile, 'utf-8');
1450
1508
  const targetStr = 'COPY package*.json ./';
1451
1509
  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');
1510
+ if (!content.includes('COPY local_packages/ ./local_packages/')) {
1511
+ content = content.split(targetStr).join(replacementStr);
1512
+ }
1513
+ content = content.replaceAll(' && rm -rf local_packages/*/node_modules', '');
1456
1514
  fs.writeFileSync(backendDockerfile, content, 'utf-8');
1457
1515
  console.log('[DevConfig] Dockerfile do backend ajustado para incluir dependências locais.');
1458
1516
  }
@@ -1485,13 +1543,13 @@ function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
1485
1543
  const frontendLocalPackages = path.join(frontendDest, 'local_packages');
1486
1544
  if (fs.existsSync(coreSrc)) {
1487
1545
  const coreDest = path.join(frontendLocalPackages, 'devkit-core');
1488
- fs.ensureDirSync(coreDest);
1546
+ fs.emptyDirSync(coreDest);
1489
1547
  fs.copySync(coreSrc, coreDest, { filter: copyFilter });
1490
1548
  stripDevelopmentDependencies(coreDest);
1491
1549
  }
1492
1550
  if (fs.existsSync(angularSrc)) {
1493
1551
  const angularDest = path.join(frontendLocalPackages, 'devkit-angular');
1494
- fs.ensureDirSync(angularDest);
1552
+ fs.emptyDirSync(angularDest);
1495
1553
  fs.copySync(angularSrc, angularDest, { filter: copyFilter });
1496
1554
  stripDevelopmentDependencies(angularDest);
1497
1555
  // Correção de workspace:* para file:../devkit-core
@@ -1530,16 +1588,17 @@ function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
1530
1588
  console.error('[Erro] Falha ao injetar links locais no frontend:', err);
1531
1589
  }
1532
1590
  }
1533
- // Patch Frontend Dockerfile para injetar "COPY local_packages/ ./local_packages/" antes do npm install e limpar node_modules local após instalação
1591
+ // Preserve runtime dependencies installed for local file packages.
1534
1592
  const frontendDockerfile = path.join(frontendDest, 'Dockerfile');
1535
1593
  if (fs.existsSync(frontendDockerfile)) {
1536
1594
  try {
1537
1595
  let content = fs.readFileSync(frontendDockerfile, 'utf-8');
1538
1596
  const targetStr = 'COPY package*.json ./';
1539
1597
  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');
1598
+ if (!content.includes('COPY local_packages/ ./local_packages/')) {
1599
+ content = content.split(targetStr).join(replacementStr);
1600
+ }
1601
+ content = content.replaceAll(' && rm -rf local_packages/*/node_modules', '');
1543
1602
  fs.writeFileSync(frontendDockerfile, content, 'utf-8');
1544
1603
  console.log('[DevConfig] Dockerfile do frontend ajustado para incluir dependências locais.');
1545
1604
  }
@@ -1607,32 +1666,51 @@ if (fs.existsSync(envPath)) {
1607
1666
  }
1608
1667
 
1609
1668
  import { NestFactory } from '@nestjs/core';
1610
- import { Transport, MicroserviceOptions } from '@nestjs/microservices';
1611
1669
  import { AppModule } from './app.module';
1670
+ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
1612
1671
 
1613
1672
  async function bootstrap() {
1614
1673
  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}\`);
1674
+ const app = await NestFactory.create(AppModule);
1675
+ app.setGlobalPrefix('api');
1676
+ const swagger = new DocumentBuilder().setTitle('Module API').setVersion('1.0').addBearerAuth().build();
1677
+ SwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, swagger));
1678
+ await app.listen(port, '0.0.0.0');
1679
+ console.log(\`Serviço HTTP iniciado na porta \${port}\`);
1624
1680
  }
1625
1681
  bootstrap();
1626
1682
  `;
1627
1683
  }
1628
- function generateMsAppModule(entityName) {
1684
+ function generateMsAppModule(entityName, coreService) {
1629
1685
  const pascalName = kebabToPascal(entityName);
1686
+ const securityImport = coreService
1687
+ ? `import { SecurityModule } from './platform/security/security.module';`
1688
+ : `import { TokenVerificationModule } from './platform/security/token-verification.module';`;
1689
+ const securityModule = coreService ? 'SecurityModule' : 'TokenVerificationModule';
1690
+ const storageImports = coreService
1691
+ ? `import { StorageModule } from '@devstroupe/devkit-nest';`
1692
+ : '';
1693
+ const storageRegistration = coreService
1694
+ ? `
1695
+ (StorageModule as any).registerAsync({
1696
+ useFactory: () => ({
1697
+ driver: (process.env.STORAGE_DRIVER || 'local') as 'local' | 's3' | 'r2',
1698
+ local: { storagePath: process.env.STORAGE_LOCAL_PATH || './storage' },
1699
+ }),
1700
+ }),`
1701
+ : '';
1630
1702
  return `import { Module } from '@nestjs/common';
1703
+ import { APP_INTERCEPTOR } from '@nestjs/core';
1631
1704
  import { TypeOrmModule } from '@nestjs/typeorm';
1705
+ import { ExecutionContextInterceptor } from '@devstroupe/devkit-nest';
1706
+ import * as path from 'path';
1632
1707
  import { ${pascalName}Module } from './modules/${entityName}/${entityName}.module';
1708
+ ${securityImport}
1709
+ ${storageImports}
1633
1710
 
1634
1711
  @Module({
1635
1712
  imports: [
1713
+ ${securityModule},
1636
1714
  TypeOrmModule.forRoot({
1637
1715
  type: 'mysql',
1638
1716
  host: process.env.DB_HOST || 'localhost',
@@ -1642,15 +1720,49 @@ import { ${pascalName}Module } from './modules/${entityName}/${entityName}.modul
1642
1720
  database: process.env.DB_NAME || 'devstroupe_${entityName}',
1643
1721
  autoLoadEntities: true,
1644
1722
  synchronize: false,
1723
+ migrations: [path.join(__dirname, './database/migrations/*{.ts,.js}')],
1724
+ migrationsRun: true,
1645
1725
  }),
1646
- ${pascalName}Module,
1726
+ ${pascalName}Module,${storageRegistration}
1647
1727
  ],
1648
1728
  controllers: [],
1649
- providers: [],
1729
+ providers: [{ provide: APP_INTERCEPTOR, useClass: ExecutionContextInterceptor }],
1650
1730
  })
1651
1731
  export class AppModule {}
1652
1732
  `;
1653
1733
  }
1734
+ function generateGatewayMainTs() {
1735
+ return `import { NestFactory } from '@nestjs/core';
1736
+ import { AppModule } from './app.module';
1737
+
1738
+ async function bootstrap() {
1739
+ const app = await NestFactory.create(AppModule, { bodyParser: false });
1740
+ app.setGlobalPrefix('api');
1741
+ app.enableCors();
1742
+ const port = Number(process.env.PORT) || 13000;
1743
+ await app.listen(port, '0.0.0.0');
1744
+ console.log(\`API Gateway stateless iniciado na porta \${port}\`);
1745
+ }
1746
+ bootstrap();
1747
+ `;
1748
+ }
1749
+ function generateGatewayAppModule() {
1750
+ return `import { Module } from '@nestjs/common';
1751
+ import { TokenVerificationModule } from './platform/security/token-verification.module';
1752
+
1753
+ @Module({
1754
+ imports: [TokenVerificationModule],
1755
+ })
1756
+ export class AppModule {}
1757
+ `;
1758
+ }
1759
+ function prepareStatelessGateway(gatewayRoot) {
1760
+ fs.removeSync(path.join(gatewayRoot, 'src', 'database'));
1761
+ fs.emptyDirSync(path.join(gatewayRoot, 'src', 'modules'));
1762
+ fs.removeSync(path.join(gatewayRoot, 'src', 'platform', 'security', 'security.module.ts'));
1763
+ fs.writeFileSync(path.join(gatewayRoot, 'src', 'main.ts'), generateGatewayMainTs(), 'utf-8');
1764
+ fs.writeFileSync(path.join(gatewayRoot, 'src', 'app.module.ts'), generateGatewayAppModule(), 'utf-8');
1765
+ }
1654
1766
  function patchGatewayAppModule(appModulePath, entityName) {
1655
1767
  const pascalName = kebabToPascal(entityName);
1656
1768
  const moduleName = `${pascalName}GatewayModule`;
@@ -1667,16 +1779,11 @@ function patchGatewayAppModule(appModulePath, entityName) {
1667
1779
  // Adiciona a declaração de import no topo
1668
1780
  const importStatement = `import { ${moduleName} } from '${importPath}';\n`;
1669
1781
  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
- }
1782
+ const moduleImportsRegex = /(@Module\s*\(\s*\{\s*imports\s*:\s*\[)([\s\S]*?)(\]\s*,?)/;
1783
+ content = content.replace(moduleImportsRegex, (_match, prefix, body, suffix) => {
1784
+ const imports = body.split(',').map((entry) => entry.trim()).filter(Boolean);
1785
+ return `${prefix}\n${[moduleName, ...imports].map((entry) => ` ${entry},`).join('\n')}\n ${suffix}`;
1786
+ });
1680
1787
  fs.writeFileSync(appModulePath, content, 'utf-8');
1681
1788
  console.log(`[Gateway] app.module.ts patcheado com sucesso para importar ${moduleName}.`);
1682
1789
  }
@@ -1705,9 +1812,20 @@ function patchMonolithAppModule(appModulePath, entityName) {
1705
1812
  fs.writeFileSync(appModulePath, content, 'utf-8');
1706
1813
  console.log(`[NestJS] app.module.ts atualizado para importar ${moduleName}.`);
1707
1814
  }
1815
+ function removeMonolithModuleImport(appModulePath, moduleName) {
1816
+ if (!fs.existsSync(appModulePath))
1817
+ return;
1818
+ const pascalName = kebabToPascal(moduleName);
1819
+ const className = `${pascalName}Module`;
1820
+ let content = fs.readFileSync(appModulePath, 'utf-8');
1821
+ content = content.replace(new RegExp(`^import \\{ ${className} \\} from ['"]\\./modules/${moduleName}/${moduleName}\\.module['"];?\\n`, 'm'), '');
1822
+ content = content.replace(new RegExp(`\\n\\s*${className},?`, 'g'), '');
1823
+ fs.writeFileSync(appModulePath, content, 'utf-8');
1824
+ }
1708
1825
  function generateDockerComposeAndDbInit(projectRoot, config) {
1709
1826
  const isMicroservices = config.backendType === 'microservices';
1710
1827
  const projectName = config.projectName || 'devstroupe-project';
1828
+ const moduleGroups = (0, backend_layout_1.buildBackendModuleGroups)(config.entities ?? []);
1711
1829
  // 1. db-init/init.sql
1712
1830
  const dbInitDir = path.join(projectRoot, 'db-init');
1713
1831
  fs.ensureDirSync(dbInitDir);
@@ -1715,12 +1833,9 @@ function generateDockerComposeAndDbInit(projectRoot, config) {
1715
1833
  fs.chmodSync(dbInitDir, 0o755);
1716
1834
  }
1717
1835
  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
- }
1836
+ const dbs = isMicroservices
1837
+ ? moduleGroups.map((group) => group.databaseName)
1838
+ : ['devstroupe'];
1724
1839
  const initSqlContent = dbs.map(db => `CREATE DATABASE IF NOT EXISTS \`${db}\`;`).join('\n') + '\n';
1725
1840
  const sqlPath = path.join(dbInitDir, 'init.sql');
1726
1841
  fs.writeFileSync(sqlPath, initSqlContent, 'utf-8');
@@ -1749,24 +1864,18 @@ function generateDockerComposeAndDbInit(projectRoot, config) {
1749
1864
  devServicesYaml += dbServiceYaml;
1750
1865
  if (isMicroservices) {
1751
1866
  // Gateway service
1752
- let gatewayEnv = ` DB_HOST: database
1753
- DB_PORT: 3306
1754
- DB_USER: root
1755
- DB_PASSWORD: root
1756
- DB_NAME: devstroupe
1867
+ let gatewayEnv = ` PORT: 13000
1757
1868
  JWT_SECRET: devstroupe-secret-key-12345
1758
1869
  `;
1759
- let gatewayDependsOn = ` - database
1760
- `;
1870
+ let gatewayDependsOn = '';
1761
1871
  // 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}
1872
+ if (moduleGroups.length) {
1873
+ moduleGroups.forEach((group) => {
1874
+ const nameUpper = group.name.replace(/-/g, '_').toUpperCase();
1875
+ gatewayEnv += ` ${nameUpper}_SERVICE_HOST: ${group.serviceName}
1876
+ ${nameUpper}_SERVICE_PORT: ${group.port}
1768
1877
  `;
1769
- gatewayDependsOn += ` - ${entity.name}-service
1878
+ gatewayDependsOn += ` - ${group.serviceName}
1770
1879
  `;
1771
1880
  });
1772
1881
  }
@@ -1796,44 +1905,45 @@ ${gatewayEnv} command: sh -c "npm install && npm run start:dev"
1796
1905
  depends_on:
1797
1906
  ${gatewayDependsOn}`;
1798
1907
  // 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:
1908
+ if (moduleGroups.length) {
1909
+ moduleGroups.forEach((group) => {
1910
+ servicesYaml += `\n\n ${group.serviceName}:
1803
1911
  build:
1804
- context: ./backend/services/${entity.name}
1912
+ context: ./backend/apps/${group.serviceName}
1805
1913
  dockerfile: Dockerfile
1806
- container_name: ${projectName}-${entity.name}-service
1914
+ container_name: ${projectName}-${group.serviceName}
1807
1915
  restart: always
1808
- ports:
1809
- - "${port}0:${port}"
1916
+ expose:
1917
+ - "${group.port}"
1810
1918
  environment:
1811
1919
  DB_HOST: database
1812
1920
  DB_PORT: 3306
1813
1921
  DB_USER: root
1814
1922
  DB_PASSWORD: root
1815
- DB_NAME: devstroupe_${entity.name.toLowerCase()}
1816
- SERVICE_PORT: ${port}
1923
+ DB_NAME: ${group.databaseName}
1924
+ SERVICE_PORT: ${group.port}
1925
+ JWT_SECRET: devstroupe-secret-key-12345
1817
1926
  depends_on:
1818
1927
  - database
1819
1928
  `;
1820
- devServicesYaml += `\n\n ${entity.name}-service:
1929
+ devServicesYaml += `\n\n ${group.serviceName}:
1821
1930
  image: node:20-alpine
1822
- container_name: ${projectName}-${entity.name}-service-dev
1931
+ container_name: ${projectName}-${group.serviceName}-dev
1823
1932
  working_dir: /app
1824
1933
  restart: always
1825
1934
  volumes:
1826
- - ./backend/services/${entity.name}:/app
1935
+ - ./backend/apps/${group.serviceName}:/app
1827
1936
  - /app/node_modules
1828
- ports:
1829
- - "${port}0:${port}"
1937
+ expose:
1938
+ - "${group.port}"
1830
1939
  environment:
1831
1940
  DB_HOST: database
1832
1941
  DB_PORT: 3306
1833
1942
  DB_USER: root
1834
1943
  DB_PASSWORD: root
1835
- DB_NAME: devstroupe_${entity.name.toLowerCase()}
1836
- SERVICE_PORT: ${port}
1944
+ DB_NAME: ${group.databaseName}
1945
+ SERVICE_PORT: ${group.port}
1946
+ JWT_SECRET: devstroupe-secret-key-12345
1837
1947
  command: sh -c "npm install && npm run start:dev"
1838
1948
  depends_on:
1839
1949
  - database
@@ -1864,7 +1974,7 @@ ${gatewayDependsOn}`;
1864
1974
  - "14200:4200"
1865
1975
  command: sh -c "npm install && npm run start -- --host 0.0.0.0 --poll 2000"
1866
1976
  depends_on:
1867
- - api-gateway-dev
1977
+ - api-gateway
1868
1978
  `;
1869
1979
  }
1870
1980
  else {
@@ -1934,16 +2044,12 @@ ${gatewayDependsOn}`;
1934
2044
  - backend
1935
2045
  `;
1936
2046
  }
1937
- const dockerComposeContent = `version: '3.8'
1938
-
1939
- services:
2047
+ const dockerComposeContent = `services:
1940
2048
  ${servicesYaml}
1941
2049
  volumes:
1942
2050
  mysql_data:
1943
2051
  `;
1944
- const dockerComposeDevContent = `version: '3.8'
1945
-
1946
- services:
2052
+ const dockerComposeDevContent = `services:
1947
2053
  ${devServicesYaml}
1948
2054
  volumes:
1949
2055
  mysql_data:
@@ -1963,7 +2069,8 @@ function generateProject(projectRoot, config) {
1963
2069
  let backendDest;
1964
2070
  if (isMicroservices) {
1965
2071
  backendDest = path.resolve(projectRoot, backendPathRel, 'api-gateway');
1966
- fs.ensureDirSync(path.resolve(projectRoot, backendPathRel, 'services'));
2072
+ fs.ensureDirSync(path.resolve(projectRoot, backendPathRel, 'apps'));
2073
+ fs.ensureDirSync(path.resolve(projectRoot, backendPathRel, 'packages', 'platform'));
1967
2074
  }
1968
2075
  else {
1969
2076
  backendDest = path.resolve(projectRoot, backendPathRel);
@@ -1991,7 +2098,9 @@ function generateProject(projectRoot, config) {
1991
2098
  fs.copySync(nestTemplateDir, backendDest, { filter: createBoilerplateCopyFilter(nestTemplateDir) });
1992
2099
  // Gerar arquivos .env e .env.example
1993
2100
  console.log('Gerando arquivos .env e .env.example no backend...');
1994
- const envContent = `PORT=13000
2101
+ const envContent = isMicroservices ? `PORT=13000
2102
+ JWT_SECRET=devstroupe-secret-key-12345
2103
+ ` : `PORT=13000
1995
2104
  DB_HOST=localhost
1996
2105
  DB_PORT=3306
1997
2106
  DB_USER=root
@@ -2016,6 +2125,8 @@ R2_SECRET_KEY=
2016
2125
  `;
2017
2126
  fs.writeFileSync(path.join(backendDest, '.env'), envContent, 'utf-8');
2018
2127
  fs.writeFileSync(path.join(backendDest, '.env.example'), envContent, 'utf-8');
2128
+ if (isMicroservices)
2129
+ prepareStatelessGateway(backendDest);
2019
2130
  }
2020
2131
  if (!fs.existsSync(frontendDest) || fs.readdirSync(frontendDest).length === 0) {
2021
2132
  console.log(`Copiando boilerplate do frontend para: ${frontendDest}...`);
@@ -2345,4 +2456,72 @@ export class Devkit${pascalName}Component {}
2345
2456
  console.log(`[UI] Componente customizado do DevKit "devkit-${componentName}" criado com sucesso em: ${customDir}`);
2346
2457
  }
2347
2458
  });
2348
- program.parse(process.argv);
2459
+ program
2460
+ .command('entity:set-module <entityName> <moduleName>')
2461
+ .description('Planeja ou aplica a movimentação segura de uma entidade entre módulos funcionais')
2462
+ .option('--dry-run', 'Exibe o plano sem alterar arquivos (padrão)')
2463
+ .option('--apply', 'Aplica o plano monolítico quando não há conflitos')
2464
+ .action((entityName, moduleName, options) => {
2465
+ const projectRoot = process.cwd();
2466
+ const configPath = path.join(projectRoot, 'devstroupe.config.ts');
2467
+ if (!fs.existsSync(configPath))
2468
+ throw new Error('devstroupe.config.ts não encontrado.');
2469
+ const imported = require(configPath);
2470
+ const config = imported.default ?? imported;
2471
+ const plan = (0, entity_module_move_1.planEntityModuleMove)(projectRoot, config, entityName, moduleName);
2472
+ console.log(JSON.stringify(plan, null, 2));
2473
+ if (!options.apply) {
2474
+ console.log('[Dry-run] Nenhum arquivo foi alterado. Revise o plano e use --apply para aprovar.');
2475
+ return;
2476
+ }
2477
+ if (plan.conflicts.length) {
2478
+ throw new Error(`Movimentação não aplicada:\n- ${plan.conflicts.join('\n- ')}`);
2479
+ }
2480
+ if (plan.backendType !== 'monolith') {
2481
+ throw new Error('Mudanças de ownership entre bancos exigem plano de dados explícito e não são aplicadas automaticamente.');
2482
+ }
2483
+ const configSource = fs.readFileSync(configPath, 'utf-8');
2484
+ const backupPath = `${configPath}.devstroupe-backup`;
2485
+ if (!fs.existsSync(backupPath))
2486
+ fs.copyFileSync(configPath, backupPath);
2487
+ fs.writeFileSync(configPath, (0, entity_module_move_1.updateEntityModuleInConfig)(configSource, entityName, plan.targetModule), 'utf-8');
2488
+ const entity = config.entities.find((candidate) => candidate.name === entityName);
2489
+ entity.module = plan.targetModule;
2490
+ const move = plan.operations.find((operation) => operation.kind === 'move');
2491
+ fs.ensureDirSync(path.dirname(move.to));
2492
+ fs.moveSync(move.from, move.to, { overwrite: false });
2493
+ const movedController = path.join(move.to, 'api', `${entityName}.controller.ts`);
2494
+ if (fs.existsSync(movedController)) {
2495
+ const source = fs.readFileSync(movedController, 'utf-8');
2496
+ fs.writeFileSync(movedController, source.replace(/@Controller\(['"][^'"]+['"]\)/, `@Controller('${plan.targetModule}/${entityName}')`), 'utf-8');
2497
+ }
2498
+ const backendRoot = path.resolve(projectRoot, config.backendPath ?? './backend');
2499
+ for (const functionalModule of [plan.sourceModule, plan.targetModule]) {
2500
+ const moduleRoot = path.join(backendRoot, 'src', 'modules', functionalModule);
2501
+ const members = config.entities.filter((candidate) => (0, devkit_core_1.resolveEntityModule)(candidate) === functionalModule && generatedEntityReady(moduleRoot, candidate.name));
2502
+ const moduleFile = path.join(moduleRoot, `${functionalModule}.module.ts`);
2503
+ if (members.length || functionalModule === 'core') {
2504
+ fs.writeFileSync(moduleFile, (0, functional_module_templates_1.nestFunctionalModuleTemplate)(functionalModule, members), 'utf-8');
2505
+ patchMonolithAppModule(path.join(backendRoot, 'src', 'app.module.ts'), functionalModule);
2506
+ }
2507
+ else {
2508
+ if (fs.existsSync(moduleFile))
2509
+ fs.removeSync(moduleFile);
2510
+ removeMonolithModuleImport(path.join(backendRoot, 'src', 'app.module.ts'), functionalModule);
2511
+ }
2512
+ }
2513
+ const migrationsRoot = path.join(backendRoot, 'src', 'database', 'migrations');
2514
+ if (fs.existsSync(migrationsRoot)) {
2515
+ for (const file of fs.readdirSync(migrationsRoot).filter((candidate) => candidate.endsWith(`-create-${entityName}.ts`))) {
2516
+ const migrationPath = path.join(migrationsRoot, file);
2517
+ const source = fs.readFileSync(migrationPath, 'utf-8');
2518
+ fs.writeFileSync(migrationPath, source.match(/^\/\/ owner-module:/)
2519
+ ? source.replace(/^\/\/ owner-module:.*$/m, `// owner-module: ${plan.targetModule}`)
2520
+ : `// owner-module: ${plan.targetModule}\n${source}`, 'utf-8');
2521
+ }
2522
+ }
2523
+ console.log(`[Aplicado] ${entityName}: ${plan.sourceModule} -> ${plan.targetModule}. Backup: ${backupPath}`);
2524
+ });
2525
+ if (require.main === module) {
2526
+ program.parse(process.argv);
2527
+ }