@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.
- package/README.md +18 -0
- package/dist/boilerplates/angular-template/src/app/core/services/auth.service.ts +1 -1
- package/dist/boilerplates/angular-template/src/app/services/role.service.ts +1 -1
- package/dist/boilerplates/angular-template/src/app/services/user.service.ts +1 -1
- package/dist/boilerplates/nest-template/.dockerignore +8 -0
- package/dist/boilerplates/nest-template/package.json +13 -15
- package/dist/boilerplates/nest-template/src/app.module.ts +7 -10
- package/dist/boilerplates/nest-template/src/database/data-source.ts +4 -1
- package/dist/boilerplates/nest-template/src/database/migrations/1600000000000-create-roles.ts +2 -1
- package/dist/boilerplates/nest-template/src/database/migrations/1700000000000-create-users.ts +2 -2
- package/dist/boilerplates/nest-template/src/modules/auth/auth.controller.ts +1 -1
- package/dist/boilerplates/nest-template/src/modules/auth/auth.module.ts +2 -7
- package/dist/boilerplates/nest-template/src/modules/auth/auth.service.ts +13 -11
- package/dist/boilerplates/nest-template/src/modules/auth/jwt-auth.guard.ts +5 -6
- package/dist/boilerplates/nest-template/src/modules/core/core.module.ts +9 -0
- package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.orm-entity.ts +4 -1
- package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.repository.adapter.ts +2 -2
- package/dist/boilerplates/nest-template/src/modules/role/infra/http/role.controller.ts +1 -1
- package/dist/boilerplates/nest-template/src/modules/storage/infra/http/storage.controller.ts +1 -1
- package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.orm-entity.ts +4 -1
- package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts +2 -2
- package/dist/boilerplates/nest-template/src/modules/user/infra/http/user.controller.ts +1 -1
- package/dist/boilerplates/nest-template/src/modules/user/service/user.service.ts +6 -3
- package/dist/boilerplates/nest-template/src/platform/security/security.module.ts +20 -0
- package/dist/boilerplates/nest-template/src/platform/security/security.tokens.ts +4 -0
- package/dist/boilerplates/nest-template/src/platform/security/token-verification.module.ts +22 -0
- package/dist/boilerplates/nest-template/tsconfig.json +1 -1
- package/dist/generation/backend-generator.integration.test.d.ts +1 -0
- package/dist/generation/backend-generator.integration.test.js +232 -0
- package/dist/generation/backend-layout.d.ts +15 -0
- package/dist/generation/backend-layout.js +77 -0
- package/dist/generation/backend-layout.test.d.ts +1 -0
- package/dist/generation/backend-layout.test.js +48 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +364 -205
- package/dist/migrations/entity-module-move.d.ts +16 -0
- package/dist/migrations/entity-module-move.js +98 -0
- package/dist/migrations/entity-module-move.test.d.ts +1 -0
- package/dist/migrations/entity-module-move.test.js +30 -0
- package/dist/rules/linter-rules.js +252 -22
- package/dist/rules/linter-rules.test.js +125 -0
- package/dist/templates/angular/service.template.d.ts +1 -1
- package/dist/templates/angular/service.template.js +2 -2
- package/dist/templates/generator-characterization.test.d.ts +1 -0
- package/dist/templates/generator-characterization.test.js +42 -0
- package/dist/templates/nest/crud.templates.d.ts +3 -3
- package/dist/templates/nest/crud.templates.js +20 -13
- package/dist/templates/nest/functional-module.templates.d.ts +10 -0
- package/dist/templates/nest/functional-module.templates.js +261 -0
- package/dist/templates/nest/index.d.ts +1 -1
- package/dist/templates/nest/index.js +1 -1
- package/dist/templates/nest/migration.template.js +6 -3
- package/dist/templates/relationship-templates.test.js +0 -4
- package/package.json +4 -4
- package/dist/boilerplates/angular-template/node_modules/.bin/acorn +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/browserslist +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/eslint +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/jiti +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/js-yaml +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/lessc +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/ng +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/ng-xi18n +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/ngc +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/nx +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/nx-cloud +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/sass +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/terser +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/tsc +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/tsserver +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/vite +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/vitest +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/webpack +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/yaml +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/acorn +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/nest +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/prettier +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-cwd +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-esm +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-script +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-transpile-only +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-script +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/tsc +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/tsserver +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-commonjs +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-esm +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/webpack +0 -21
- package/dist/templates/nest/microservice.templates.d.ts +0 -4
- 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
|
-
|
|
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(
|
|
1129
|
-
console.log(
|
|
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
|
-
|
|
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, '
|
|
1152
|
-
|
|
1153
|
-
|
|
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
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
fs.
|
|
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,
|
|
1266
|
+
ensureInitialMigrations(msRoot, members, new Map(members.map((member) => [member.name, migrationPropertiesForEntity(member, config)])));
|
|
1207
1267
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
fs.
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
-
|
|
1224
|
-
|
|
1225
|
-
const
|
|
1226
|
-
fs.
|
|
1227
|
-
|
|
1228
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
content = content.
|
|
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
|
-
//
|
|
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
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
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.
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
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
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
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 =
|
|
1719
|
-
|
|
1720
|
-
|
|
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 = `
|
|
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 =
|
|
1760
|
-
`;
|
|
1850
|
+
let gatewayDependsOn = '';
|
|
1761
1851
|
// Adiciona variáveis de microsserviços no gateway
|
|
1762
|
-
if (
|
|
1763
|
-
|
|
1764
|
-
const
|
|
1765
|
-
|
|
1766
|
-
|
|
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 += ` - ${
|
|
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 (
|
|
1800
|
-
|
|
1801
|
-
|
|
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/
|
|
1892
|
+
context: ./backend/apps/${group.serviceName}
|
|
1805
1893
|
dockerfile: Dockerfile
|
|
1806
|
-
container_name: ${projectName}-${
|
|
1894
|
+
container_name: ${projectName}-${group.serviceName}
|
|
1807
1895
|
restart: always
|
|
1808
|
-
|
|
1809
|
-
- "${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:
|
|
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 ${
|
|
1909
|
+
devServicesYaml += `\n\n ${group.serviceName}:
|
|
1821
1910
|
image: node:20-alpine
|
|
1822
|
-
container_name: ${projectName}-${
|
|
1911
|
+
container_name: ${projectName}-${group.serviceName}-dev
|
|
1823
1912
|
working_dir: /app
|
|
1824
1913
|
restart: always
|
|
1825
1914
|
volumes:
|
|
1826
|
-
- ./backend/
|
|
1915
|
+
- ./backend/apps/${group.serviceName}:/app
|
|
1827
1916
|
- /app/node_modules
|
|
1828
|
-
|
|
1829
|
-
- "${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:
|
|
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
|
|
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 = `
|
|
1938
|
-
|
|
1939
|
-
services:
|
|
2027
|
+
const dockerComposeContent = `services:
|
|
1940
2028
|
${servicesYaml}
|
|
1941
2029
|
volumes:
|
|
1942
2030
|
mysql_data:
|
|
1943
2031
|
`;
|
|
1944
|
-
const dockerComposeDevContent = `
|
|
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, '
|
|
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
|
|
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
|
+
}
|