@devstroupe/devkit-cli 1.0.0
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 +40 -0
- package/dist/boilerplates/angular-template/.dockerignore +4 -0
- package/dist/boilerplates/angular-template/.postcssrc.json +5 -0
- package/dist/boilerplates/angular-template/Dockerfile +14 -0
- package/dist/boilerplates/angular-template/angular.json +85 -0
- package/dist/boilerplates/angular-template/components.json +5 -0
- package/dist/boilerplates/angular-template/framework.code-workspace +7 -0
- package/dist/boilerplates/angular-template/nginx.conf +23 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/browserslist +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/jiti +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/lessc +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/ng +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/ng-xi18n +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/ngc +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/sass +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/terser +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/tsc +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/tsserver +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/vite +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/vitest +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/yaml +21 -0
- package/dist/boilerplates/angular-template/package.json +47 -0
- package/dist/boilerplates/angular-template/postcss.config.js +5 -0
- package/dist/boilerplates/angular-template/proxy.conf.json +7 -0
- package/dist/boilerplates/angular-template/src/app/app.component.html +3 -0
- package/dist/boilerplates/angular-template/src/app/app.component.ts +12 -0
- package/dist/boilerplates/angular-template/src/app/app.config.ts +59 -0
- package/dist/boilerplates/angular-template/src/app/app.entity-routes.ts +4 -0
- package/dist/boilerplates/angular-template/src/app/app.routes.ts +25 -0
- package/dist/boilerplates/angular-template/src/app/core/guards/auth.guard.ts +16 -0
- package/dist/boilerplates/angular-template/src/app/core/services/auth.service.ts +98 -0
- package/dist/boilerplates/angular-template/src/app/modules/auth/login/login.component.html +51 -0
- package/dist/boilerplates/angular-template/src/app/modules/auth/login/login.component.ts +47 -0
- package/dist/boilerplates/angular-template/src/app/modules/auth/register/register.component.html +59 -0
- package/dist/boilerplates/angular-template/src/app/modules/auth/register/register.component.ts +47 -0
- package/dist/boilerplates/angular-template/src/app/modules/dashboard/dashboard.component.html +14 -0
- package/dist/boilerplates/angular-template/src/app/modules/dashboard/dashboard.component.ts +23 -0
- package/dist/boilerplates/angular-template/src/app/shared/interceptors/tenant.interceptor.ts +25 -0
- package/dist/boilerplates/angular-template/src/index.html +64 -0
- package/dist/boilerplates/angular-template/src/main.ts +6 -0
- package/dist/boilerplates/angular-template/src/styles.css +9 -0
- package/dist/boilerplates/angular-template/tsconfig.json +36 -0
- package/dist/boilerplates/nest-template/Dockerfile +16 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/acorn +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/nest +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/prettier +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/tsc +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/tsserver +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-commonjs +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-esm +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/webpack +21 -0
- package/dist/boilerplates/nest-template/package.json +43 -0
- package/dist/boilerplates/nest-template/src/app.module.ts +24 -0
- package/dist/boilerplates/nest-template/src/database/data-source.ts +16 -0
- package/dist/boilerplates/nest-template/src/database/migrations/1700000000000-create-users.ts +26 -0
- package/dist/boilerplates/nest-template/src/main.ts +46 -0
- package/dist/boilerplates/nest-template/src/modules/auth/auth.controller.ts +34 -0
- package/dist/boilerplates/nest-template/src/modules/auth/auth.module.ts +22 -0
- package/dist/boilerplates/nest-template/src/modules/auth/auth.service.ts +57 -0
- package/dist/boilerplates/nest-template/src/modules/auth/current-user.decorator.ts +8 -0
- package/dist/boilerplates/nest-template/src/modules/auth/jwt-auth.guard.ts +31 -0
- package/dist/boilerplates/nest-template/src/modules/auth/roles.decorator.ts +4 -0
- package/dist/boilerplates/nest-template/src/modules/auth/roles.guard.ts +24 -0
- package/dist/boilerplates/nest-template/src/modules/user/database-seed.service.ts +47 -0
- package/dist/boilerplates/nest-template/src/modules/user/domain/user.repository.ts +9 -0
- package/dist/boilerplates/nest-template/src/modules/user/domain/user.ts +9 -0
- package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.orm-entity.ts +31 -0
- package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts +53 -0
- package/dist/boilerplates/nest-template/src/modules/user/service/user.service.ts +44 -0
- package/dist/boilerplates/nest-template/src/modules/user/user.module.ts +20 -0
- package/dist/boilerplates/nest-template/tsconfig.build.json +4 -0
- package/dist/boilerplates/nest-template/tsconfig.json +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1967 -0
- package/dist/migrations/sort-entities.d.ts +2 -0
- package/dist/migrations/sort-entities.js +30 -0
- package/dist/rules/linter-rules.d.ts +12 -0
- package/dist/rules/linter-rules.js +338 -0
- package/dist/rules/linter-rules.test.d.ts +1 -0
- package/dist/rules/linter-rules.test.js +214 -0
- package/dist/templates/angular/dialog-handler.template.d.ts +2 -0
- package/dist/templates/angular/dialog-handler.template.js +49 -0
- package/dist/templates/angular/form.template.d.ts +3 -0
- package/dist/templates/angular/form.template.js +453 -0
- package/dist/templates/angular/index.d.ts +4 -0
- package/dist/templates/angular/index.js +20 -0
- package/dist/templates/angular/list.template.d.ts +3 -0
- package/dist/templates/angular/list.template.js +213 -0
- package/dist/templates/angular/service.template.d.ts +1 -0
- package/dist/templates/angular/service.template.js +20 -0
- package/dist/templates/cli-templates.d.ts +2 -0
- package/dist/templates/cli-templates.js +18 -0
- package/dist/templates/nest/crud.templates.d.ts +9 -0
- package/dist/templates/nest/crud.templates.js +362 -0
- package/dist/templates/nest/index.d.ts +3 -0
- package/dist/templates/nest/index.js +19 -0
- package/dist/templates/nest/microservice.templates.d.ts +4 -0
- package/dist/templates/nest/microservice.templates.js +157 -0
- package/dist/templates/nest/migration.template.d.ts +3 -0
- package/dist/templates/nest/migration.template.js +127 -0
- package/dist/templates/relationship-templates.test.d.ts +1 -0
- package/dist/templates/relationship-templates.test.js +181 -0
- package/dist/templates/shared/names.d.ts +3 -0
- package/dist/templates/shared/names.js +14 -0
- package/dist/templates/shared/relationships.d.ts +27 -0
- package/dist/templates/shared/relationships.js +96 -0
- package/dist/templates/ui/components/avatar.template.d.ts +3 -0
- package/dist/templates/ui/components/avatar.template.js +66 -0
- package/dist/templates/ui/components/badge.template.d.ts +3 -0
- package/dist/templates/ui/components/badge.template.js +27 -0
- package/dist/templates/ui/components/button.template.d.ts +5 -0
- package/dist/templates/ui/components/button.template.js +64 -0
- package/dist/templates/ui/components/card-list.template.d.ts +5 -0
- package/dist/templates/ui/components/card-list.template.js +79 -0
- package/dist/templates/ui/components/dialog.template.d.ts +5 -0
- package/dist/templates/ui/components/dialog.template.js +51 -0
- package/dist/templates/ui/components/filter.template.d.ts +4 -0
- package/dist/templates/ui/components/filter.template.js +218 -0
- package/dist/templates/ui/components/index.d.ts +10 -0
- package/dist/templates/ui/components/index.js +26 -0
- package/dist/templates/ui/components/input.template.d.ts +4 -0
- package/dist/templates/ui/components/input.template.js +76 -0
- package/dist/templates/ui/components/select.template.d.ts +4 -0
- package/dist/templates/ui/components/select.template.js +176 -0
- package/dist/templates/ui/components/simple-list.template.d.ts +5 -0
- package/dist/templates/ui/components/simple-list.template.js +89 -0
- package/dist/templates/ui/components/table.template.d.ts +5 -0
- package/dist/templates/ui/components/table.template.js +112 -0
- package/dist/templates/ui/index.d.ts +9 -0
- package/dist/templates/ui/index.js +25 -0
- package/dist/templates/ui/layout/header.template.d.ts +5 -0
- package/dist/templates/ui/layout/header.template.js +236 -0
- package/dist/templates/ui/layout/index.d.ts +4 -0
- package/dist/templates/ui/layout/index.js +20 -0
- package/dist/templates/ui/layout/main-layout.template.d.ts +5 -0
- package/dist/templates/ui/layout/main-layout.template.js +126 -0
- package/dist/templates/ui/layout/shell.template.d.ts +5 -0
- package/dist/templates/ui/layout/shell.template.js +76 -0
- package/dist/templates/ui/layout/sidebar.template.d.ts +5 -0
- package/dist/templates/ui/layout/sidebar.template.js +412 -0
- package/dist/templates/ui/playground.template.d.ts +5 -0
- package/dist/templates/ui/playground.template.js +570 -0
- package/dist/templates/ui/readme.template.d.ts +1 -0
- package/dist/templates/ui/readme.template.js +23 -0
- package/dist/templates/ui/shared/colors.d.ts +1 -0
- package/dist/templates/ui/shared/colors.js +31 -0
- package/dist/templates/ui/shared/profile-component.template.d.ts +5 -0
- package/dist/templates/ui/shared/profile-component.template.js +403 -0
- package/dist/templates/ui/shared/profile-service.template.d.ts +1 -0
- package/dist/templates/ui/shared/profile-service.template.js +44 -0
- package/dist/templates/ui/shared/theme-service.template.d.ts +1 -0
- package/dist/templates/ui/shared/theme-service.template.js +47 -0
- package/dist/templates/ui/spartan/badge-directive.template.d.ts +3 -0
- package/dist/templates/ui/spartan/badge-directive.template.js +50 -0
- package/dist/templates/ui/spartan/button-directive.template.d.ts +3 -0
- package/dist/templates/ui/spartan/button-directive.template.js +82 -0
- package/dist/templates/ui/spartan/button-token.template.d.ts +3 -0
- package/dist/templates/ui/spartan/button-token.template.js +31 -0
- package/dist/templates/ui/spartan/hlm-core.template.d.ts +3 -0
- package/dist/templates/ui/spartan/hlm-core.template.js +322 -0
- package/dist/templates/ui/spartan/index.d.ts +5 -0
- package/dist/templates/ui/spartan/index.js +21 -0
- package/dist/templates/ui/spartan/input-directive.template.d.ts +3 -0
- package/dist/templates/ui/spartan/input-directive.template.js +31 -0
- package/dist/templates/ui/theme-css.template.d.ts +7 -0
- package/dist/templates/ui/theme-css.template.js +210 -0
- package/dist/templates/ui-templates.d.ts +1 -0
- package/dist/templates/ui-templates.js +17 -0
- package/package.json +50 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1967 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const fs = __importStar(require("fs-extra"));
|
|
40
|
+
const cli_templates_1 = require("./templates/cli-templates");
|
|
41
|
+
const ui_templates_1 = require("./templates/ui-templates");
|
|
42
|
+
const linter_rules_1 = require("./rules/linter-rules");
|
|
43
|
+
const relationships_1 = require("./templates/shared/relationships");
|
|
44
|
+
const sort_entities_1 = require("./migrations/sort-entities");
|
|
45
|
+
const child_process_1 = require("child_process");
|
|
46
|
+
// Registra ts-node para poder carregar arquivos .ts (como o config)
|
|
47
|
+
try {
|
|
48
|
+
require('ts-node').register({
|
|
49
|
+
compilerOptions: { module: 'commonjs' }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
// Silencioso se ts-node não estiver presente localmente
|
|
54
|
+
}
|
|
55
|
+
const program = new commander_1.Command();
|
|
56
|
+
const SPARTAN_STYLES = ['nova', 'vega', 'lyra', 'maia', 'mira', 'luma'];
|
|
57
|
+
function isSpartanStyle(value) {
|
|
58
|
+
return SPARTAN_STYLES.includes(value);
|
|
59
|
+
}
|
|
60
|
+
function getDesignSystemConfig(config) {
|
|
61
|
+
return config?.designSystem || {};
|
|
62
|
+
}
|
|
63
|
+
function assertValidRelationshipConfig(config) {
|
|
64
|
+
(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
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function ensureMigrationInfrastructure(backendRoot) {
|
|
73
|
+
const databaseDir = path.join(backendRoot, 'src', 'database');
|
|
74
|
+
fs.ensureDirSync(path.join(databaseDir, 'migrations'));
|
|
75
|
+
const dataSourcePath = path.join(databaseDir, 'data-source.ts');
|
|
76
|
+
if (!fs.existsSync(dataSourcePath)) {
|
|
77
|
+
fs.writeFileSync(dataSourcePath, (0, cli_templates_1.nestDataSourceTemplate)(), 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
const packagePath = path.join(backendRoot, 'package.json');
|
|
80
|
+
if (fs.existsSync(packagePath)) {
|
|
81
|
+
const packageJson = fs.readJsonSync(packagePath);
|
|
82
|
+
packageJson.scripts = {
|
|
83
|
+
...(packageJson.scripts ?? {}),
|
|
84
|
+
'migration:run': 'typeorm-ts-node-commonjs -d src/database/data-source.ts migration:run',
|
|
85
|
+
'migration:revert': 'typeorm-ts-node-commonjs -d src/database/data-source.ts migration:revert',
|
|
86
|
+
'migration:generate': 'typeorm-ts-node-commonjs -d src/database/data-source.ts migration:generate src/database/migrations/schema',
|
|
87
|
+
};
|
|
88
|
+
packageJson.devDependencies = { ...(packageJson.devDependencies ?? {}), 'ts-node': '^10.9.2' };
|
|
89
|
+
packageJson.dependencies = { ...(packageJson.dependencies ?? {}), dotenv: '^16.4.5' };
|
|
90
|
+
fs.writeJsonSync(packagePath, packageJson, { spaces: 2 });
|
|
91
|
+
}
|
|
92
|
+
const appModulePath = path.join(backendRoot, 'src', 'app.module.ts');
|
|
93
|
+
if (fs.existsSync(appModulePath)) {
|
|
94
|
+
const appModule = fs.readFileSync(appModulePath, 'utf-8');
|
|
95
|
+
const updated = appModule.replace(/synchronize:\s*true/g, 'synchronize: false');
|
|
96
|
+
if (updated !== appModule)
|
|
97
|
+
fs.writeFileSync(appModulePath, updated, 'utf-8');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function ensureInitialMigrations(backendRoot, entities, propertyOverrides = new Map()) {
|
|
101
|
+
ensureMigrationInfrastructure(backendRoot);
|
|
102
|
+
const migrationsDir = path.join(backendRoot, 'src', 'database', 'migrations');
|
|
103
|
+
const existing = fs.readdirSync(migrationsDir);
|
|
104
|
+
const missing = (0, sort_entities_1.sortEntitiesForMigrations)(entities).filter((entity) => !existing.some((file) => file.endsWith(`-create-${entity.name}.ts`)));
|
|
105
|
+
const baseTimestamp = Date.now();
|
|
106
|
+
missing.forEach((entity, index) => {
|
|
107
|
+
const timestamp = baseTimestamp + index;
|
|
108
|
+
const fileName = `${timestamp}-create-${entity.name}.ts`;
|
|
109
|
+
const properties = propertyOverrides.get(entity.name) ?? entity.properties;
|
|
110
|
+
fs.writeFileSync(path.join(migrationsDir, fileName), (0, cli_templates_1.nestMigrationTemplate)(entity, entities, timestamp, properties), 'utf-8');
|
|
111
|
+
console.log(`[Migration] Criada ${fileName}`);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function getSpartanStyle(config) {
|
|
115
|
+
const designSystem = getDesignSystemConfig(config);
|
|
116
|
+
const configuredStyle = designSystem.spartanStyle || designSystem.style || designSystem.preset || 'vega';
|
|
117
|
+
return isSpartanStyle(configuredStyle) ? configuredStyle : 'vega';
|
|
118
|
+
}
|
|
119
|
+
function getCurrentSpartanStyle(frontendRoot, config) {
|
|
120
|
+
const componentsPath = path.join(frontendRoot, 'components.json');
|
|
121
|
+
if (fs.existsSync(componentsPath)) {
|
|
122
|
+
try {
|
|
123
|
+
const componentsConfig = fs.readJsonSync(componentsPath);
|
|
124
|
+
if (isSpartanStyle(componentsConfig.style)) {
|
|
125
|
+
return componentsConfig.style;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// fallback para o devstroupe.config.ts abaixo
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return getSpartanStyle(config);
|
|
133
|
+
}
|
|
134
|
+
function getPackageManagerCommand(projectRoot) {
|
|
135
|
+
const localNg = path.join(projectRoot, 'node_modules', '.bin', process.platform === 'win32' ? 'ng.cmd' : 'ng');
|
|
136
|
+
if (fs.existsSync(localNg)) {
|
|
137
|
+
return `"${localNg}"`;
|
|
138
|
+
}
|
|
139
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
140
|
+
try {
|
|
141
|
+
const pkg = fs.existsSync(pkgPath) ? fs.readJsonSync(pkgPath) : {};
|
|
142
|
+
if (typeof pkg.packageManager === 'string' && pkg.packageManager.startsWith('pnpm')) {
|
|
143
|
+
return 'pnpm exec ng';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// fallback abaixo
|
|
148
|
+
}
|
|
149
|
+
if (fs.existsSync(path.join(projectRoot, 'pnpm-lock.yaml'))) {
|
|
150
|
+
return 'pnpm exec ng';
|
|
151
|
+
}
|
|
152
|
+
return 'npx ng';
|
|
153
|
+
}
|
|
154
|
+
function ensureSpartanComponentsJson(frontendRoot, style) {
|
|
155
|
+
const componentsPath = path.join(frontendRoot, 'components.json');
|
|
156
|
+
const nextConfig = {
|
|
157
|
+
componentsPath: 'libs/ui',
|
|
158
|
+
importAlias: '@spartan-ng/helm',
|
|
159
|
+
style
|
|
160
|
+
};
|
|
161
|
+
fs.writeJsonSync(componentsPath, nextConfig, { spaces: 2 });
|
|
162
|
+
console.log(`[Spartan] components.json configurado com style="${style}".`);
|
|
163
|
+
}
|
|
164
|
+
function ensureSpartanTsconfigPaths(frontendRoot) {
|
|
165
|
+
const tsconfigPath = path.join(frontendRoot, 'tsconfig.json');
|
|
166
|
+
if (!fs.existsSync(tsconfigPath))
|
|
167
|
+
return;
|
|
168
|
+
const utilsIndexPath = path.join(frontendRoot, 'libs', 'ui', 'utils', 'src', 'index.ts');
|
|
169
|
+
if (!fs.existsSync(utilsIndexPath)) {
|
|
170
|
+
throw new Error('[Spartan] libs/ui/utils não foi gerado. Execute o generator do Spartan antes de registrar os aliases.');
|
|
171
|
+
}
|
|
172
|
+
const tsconfig = fs.readJsonSync(tsconfigPath);
|
|
173
|
+
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
|
|
174
|
+
tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
|
|
175
|
+
tsconfig.compilerOptions.paths['@spartan-ng/helm/utils'] = ['libs/ui/utils/src/index.ts'];
|
|
176
|
+
tsconfig.compilerOptions.paths['@spartan-ng/helm/*'] = ['libs/ui/*/src/index.ts'];
|
|
177
|
+
fs.writeJsonSync(tsconfigPath, tsconfig, { spaces: 2 });
|
|
178
|
+
console.log('[Spartan] Path aliases @spartan-ng/helm configurados no tsconfig.json.');
|
|
179
|
+
}
|
|
180
|
+
function removeStaleSpartanTsconfigPaths(frontendRoot) {
|
|
181
|
+
const tsconfigPath = path.join(frontendRoot, 'tsconfig.json');
|
|
182
|
+
if (!fs.existsSync(tsconfigPath))
|
|
183
|
+
return;
|
|
184
|
+
const tsconfig = fs.readJsonSync(tsconfigPath);
|
|
185
|
+
const paths = tsconfig.compilerOptions?.paths;
|
|
186
|
+
if (!paths)
|
|
187
|
+
return;
|
|
188
|
+
const utilsIndexPath = path.join(frontendRoot, 'libs', 'ui', 'utils', 'src', 'index.ts');
|
|
189
|
+
let updated = false;
|
|
190
|
+
if (!fs.existsSync(utilsIndexPath) && paths['@spartan-ng/helm/utils']) {
|
|
191
|
+
delete paths['@spartan-ng/helm/utils'];
|
|
192
|
+
updated = true;
|
|
193
|
+
}
|
|
194
|
+
if (paths['@spartan-ng/helm/*']) {
|
|
195
|
+
delete paths['@spartan-ng/helm/*'];
|
|
196
|
+
updated = true;
|
|
197
|
+
}
|
|
198
|
+
if (updated) {
|
|
199
|
+
fs.writeJsonSync(tsconfigPath, tsconfig, { spaces: 2 });
|
|
200
|
+
console.log('[Spartan] Aliases genéricos antigos removidos antes da geração.');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function removeSpartanGeneratedUi(frontendRoot) {
|
|
204
|
+
const uiRoot = path.join(frontendRoot, 'libs', 'ui');
|
|
205
|
+
if (fs.existsSync(uiRoot)) {
|
|
206
|
+
fs.removeSync(uiRoot);
|
|
207
|
+
console.log('[Spartan] libs/ui removido para regenerar o style real.');
|
|
208
|
+
}
|
|
209
|
+
const tsconfigPath = path.join(frontendRoot, 'tsconfig.json');
|
|
210
|
+
if (!fs.existsSync(tsconfigPath))
|
|
211
|
+
return;
|
|
212
|
+
const tsconfig = fs.readJsonSync(tsconfigPath);
|
|
213
|
+
const paths = tsconfig.compilerOptions?.paths;
|
|
214
|
+
if (!paths)
|
|
215
|
+
return;
|
|
216
|
+
let updated = false;
|
|
217
|
+
for (const alias of Object.keys(paths)) {
|
|
218
|
+
if (alias === '@spartan-ng/helm/utils' || alias.startsWith('@spartan-ng/helm/')) {
|
|
219
|
+
delete paths[alias];
|
|
220
|
+
updated = true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (updated) {
|
|
224
|
+
fs.writeJsonSync(tsconfigPath, tsconfig, { spaces: 2 });
|
|
225
|
+
console.log('[Spartan] Aliases Helm removidos para forçar nova geração.');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function ensureSpartanTailwindSource(frontendRoot) {
|
|
229
|
+
const styleCandidates = [
|
|
230
|
+
path.join(frontendRoot, 'src', 'styles.scss'),
|
|
231
|
+
path.join(frontendRoot, 'src', 'styles.css')
|
|
232
|
+
];
|
|
233
|
+
const stylesPath = styleCandidates.find((candidate) => fs.existsSync(candidate));
|
|
234
|
+
if (!stylesPath)
|
|
235
|
+
return;
|
|
236
|
+
let content = fs.readFileSync(stylesPath, 'utf-8');
|
|
237
|
+
if (!content.includes("@source './app'") && !content.includes('@source "./app"')) {
|
|
238
|
+
content = content.replace(/(@import ['"]@angular\/cdk\/overlay-prebuilt\.css['"];?)/, `$1\n\n@source './app';`);
|
|
239
|
+
}
|
|
240
|
+
if (!content.includes("@source '../libs/ui'") && !content.includes('@source "../libs/ui"')) {
|
|
241
|
+
content = content.replace(/(@import ['"]@spartan-ng\/brain\/hlm-tailwind-preset\.css['"];?)/, `$1\n\n@source '../libs/ui';`);
|
|
242
|
+
}
|
|
243
|
+
fs.writeFileSync(stylesPath, content, 'utf-8');
|
|
244
|
+
console.log('[Spartan] @source ./app e ../libs/ui garantidos nos estilos globais.');
|
|
245
|
+
}
|
|
246
|
+
function ensureSpartanProvider(frontendRoot) {
|
|
247
|
+
const appConfigPath = path.join(frontendRoot, 'src', 'app', 'app.config.ts');
|
|
248
|
+
if (!fs.existsSync(appConfigPath))
|
|
249
|
+
return;
|
|
250
|
+
let content = fs.readFileSync(appConfigPath, 'utf-8');
|
|
251
|
+
if (!content.includes("provideSpartanHlm")) {
|
|
252
|
+
content = content.replace("import { ApplicationConfig } from '@angular/core';", "import { ApplicationConfig } from '@angular/core';\nimport { provideSpartanHlm } from '@spartan-ng/helm/utils';");
|
|
253
|
+
content = content.replace(/providers:\s*\[/, 'providers: [\n provideSpartanHlm(),');
|
|
254
|
+
fs.writeFileSync(appConfigPath, content, 'utf-8');
|
|
255
|
+
console.log('[Spartan] provideSpartanHlm() registrado no app.config.ts.');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function getSupportedSpartanPrimitiveNames(frontendRoot) {
|
|
259
|
+
const supportedLibrariesPath = path.join(frontendRoot, 'node_modules', '@spartan-ng', 'cli', 'src', 'generators', 'ui', 'supported-ui-libraries.json');
|
|
260
|
+
if (!fs.existsSync(supportedLibrariesPath)) {
|
|
261
|
+
throw new Error('[Spartan] @spartan-ng/cli não encontrado em node_modules. Instale as dependências do frontend antes de executar init-ui.');
|
|
262
|
+
}
|
|
263
|
+
const supportedLibraries = JSON.parse(fs.readFileSync(supportedLibrariesPath, 'utf-8'));
|
|
264
|
+
return Object.keys(supportedLibraries).sort();
|
|
265
|
+
}
|
|
266
|
+
function runSpartanGenerator(frontendRoot, style, forceRegenerate = false) {
|
|
267
|
+
ensureSpartanComponentsJson(frontendRoot, style);
|
|
268
|
+
if (forceRegenerate) {
|
|
269
|
+
removeSpartanGeneratedUi(frontendRoot);
|
|
270
|
+
}
|
|
271
|
+
removeStaleSpartanTsconfigPaths(frontendRoot);
|
|
272
|
+
ensureSpartanTailwindSource(frontendRoot);
|
|
273
|
+
ensureSpartanProvider(frontendRoot);
|
|
274
|
+
const ng = getPackageManagerCommand(frontendRoot);
|
|
275
|
+
const primitiveNames = getSupportedSpartanPrimitiveNames(frontendRoot);
|
|
276
|
+
console.log(`[Spartan] Gerando ${primitiveNames.length} primitives Helm reais (${style})...`);
|
|
277
|
+
for (const primitiveName of primitiveNames) {
|
|
278
|
+
(0, child_process_1.execSync)(`${ng} g @spartan-ng/cli:ui ${primitiveName} --defaults --force`, {
|
|
279
|
+
cwd: frontendRoot,
|
|
280
|
+
stdio: 'inherit'
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
ensureSpartanTsconfigPaths(frontendRoot);
|
|
284
|
+
}
|
|
285
|
+
function ensureOfficialSpartanTheme(frontendRoot, theme = 'neutral') {
|
|
286
|
+
const stylesPath = [
|
|
287
|
+
path.join(frontendRoot, 'src', 'styles.css'),
|
|
288
|
+
path.join(frontendRoot, 'src', 'styles.scss')
|
|
289
|
+
].find((candidate) => fs.existsSync(candidate));
|
|
290
|
+
if (!stylesPath)
|
|
291
|
+
return;
|
|
292
|
+
const currentStyles = fs.readFileSync(stylesPath, 'utf-8');
|
|
293
|
+
if (/--radius\s*:/.test(currentStyles) && /--sidebar-background|--sidebar\s*:/.test(currentStyles)) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const ng = getPackageManagerCommand(frontendRoot);
|
|
297
|
+
console.log(`[Spartan] Aplicando tema oficial "${theme}"...`);
|
|
298
|
+
(0, child_process_1.execSync)(`${ng} g @spartan-ng/cli:ui-theme --theme=${theme} --defaults`, {
|
|
299
|
+
cwd: frontendRoot,
|
|
300
|
+
stdio: 'inherit'
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
function runSpartanPrimitiveGenerator(frontendRoot, style, primitiveName) {
|
|
304
|
+
ensureSpartanComponentsJson(frontendRoot, style);
|
|
305
|
+
removeStaleSpartanTsconfigPaths(frontendRoot);
|
|
306
|
+
ensureSpartanTailwindSource(frontendRoot);
|
|
307
|
+
ensureSpartanProvider(frontendRoot);
|
|
308
|
+
const supportedPrimitives = getSupportedSpartanPrimitiveNames(frontendRoot);
|
|
309
|
+
if (!supportedPrimitives.includes(primitiveName)) {
|
|
310
|
+
throw new Error(`[Spartan] Primitive "${primitiveName}" não existe na lista oficial do Spartan CLI.`);
|
|
311
|
+
}
|
|
312
|
+
const ng = getPackageManagerCommand(frontendRoot);
|
|
313
|
+
console.log(`[Spartan] Gerando primitive Helm real "${primitiveName}" (${style})...`);
|
|
314
|
+
(0, child_process_1.execSync)(`${ng} g @spartan-ng/cli:ui ${primitiveName} --defaults --force`, {
|
|
315
|
+
cwd: frontendRoot,
|
|
316
|
+
stdio: 'inherit'
|
|
317
|
+
});
|
|
318
|
+
ensureSpartanTsconfigPaths(frontendRoot);
|
|
319
|
+
}
|
|
320
|
+
function createBoilerplateCopyFilter(templateRoot) {
|
|
321
|
+
const ignored = new Set([
|
|
322
|
+
'node_modules',
|
|
323
|
+
'.angular',
|
|
324
|
+
'dist',
|
|
325
|
+
'coverage',
|
|
326
|
+
'.turbo',
|
|
327
|
+
'.nx'
|
|
328
|
+
]);
|
|
329
|
+
return (src) => {
|
|
330
|
+
const relativePath = path.relative(templateRoot, src);
|
|
331
|
+
return !relativePath.split(path.sep).some((part) => ignored.has(part));
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
// Helper para garantir e configurar os caminhos de path alias no tsconfig.json do Frontend
|
|
335
|
+
function ensureTsconfigPaths(frontendRoot) {
|
|
336
|
+
const tsconfigPath = path.join(frontendRoot, 'tsconfig.json');
|
|
337
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
338
|
+
console.warn(`[Aviso] tsconfig.json não encontrado em ${frontendRoot}. Certifique-se de configurar os path aliases manualmente.`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const tsconfig = fs.readJsonSync(tsconfigPath);
|
|
343
|
+
if (!tsconfig.compilerOptions) {
|
|
344
|
+
tsconfig.compilerOptions = {};
|
|
345
|
+
}
|
|
346
|
+
if (!tsconfig.compilerOptions.paths) {
|
|
347
|
+
tsconfig.compilerOptions.paths = {};
|
|
348
|
+
}
|
|
349
|
+
const aliasKey = '@devstroupe/ui/*';
|
|
350
|
+
const aliasVal = ['src/app/shared/components/devkit/*'];
|
|
351
|
+
const spartanUtilsKey = '@spartan-ng/helm/utils';
|
|
352
|
+
const spartanUtilsVal = ['libs/ui/utils/src/index.ts'];
|
|
353
|
+
const spartanAliasKey = '@spartan-ng/helm/*';
|
|
354
|
+
const spartanAliasVal = ['libs/ui/*/src/index.ts'];
|
|
355
|
+
let updated = false;
|
|
356
|
+
if (!tsconfig.compilerOptions.paths[aliasKey] ||
|
|
357
|
+
JSON.stringify(tsconfig.compilerOptions.paths[aliasKey]) !== JSON.stringify(aliasVal)) {
|
|
358
|
+
tsconfig.compilerOptions.paths[aliasKey] = aliasVal;
|
|
359
|
+
updated = true;
|
|
360
|
+
}
|
|
361
|
+
if (!tsconfig.compilerOptions.paths[spartanUtilsKey] ||
|
|
362
|
+
JSON.stringify(tsconfig.compilerOptions.paths[spartanUtilsKey]) !== JSON.stringify(spartanUtilsVal)) {
|
|
363
|
+
tsconfig.compilerOptions.paths[spartanUtilsKey] = spartanUtilsVal;
|
|
364
|
+
updated = true;
|
|
365
|
+
}
|
|
366
|
+
if (!tsconfig.compilerOptions.paths[spartanAliasKey] ||
|
|
367
|
+
JSON.stringify(tsconfig.compilerOptions.paths[spartanAliasKey]) !== JSON.stringify(spartanAliasVal)) {
|
|
368
|
+
tsconfig.compilerOptions.paths[spartanAliasKey] = spartanAliasVal;
|
|
369
|
+
updated = true;
|
|
370
|
+
}
|
|
371
|
+
if (updated) {
|
|
372
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2), 'utf-8');
|
|
373
|
+
console.log(`[TSConfig] Path aliases configurados com sucesso em: ${tsconfigPath}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
console.error(`[Erro] Falha ao atualizar o tsconfig.json em ${tsconfigPath}:`, err);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function ensureIconDependencies(frontendRoot) {
|
|
381
|
+
const pkgJsonPath = path.join(frontendRoot, 'package.json');
|
|
382
|
+
if (!fs.existsSync(pkgJsonPath))
|
|
383
|
+
return;
|
|
384
|
+
try {
|
|
385
|
+
const pkgJson = fs.readJsonSync(pkgJsonPath);
|
|
386
|
+
const deps = ['@ng-icons/core', '@ng-icons/lucide'];
|
|
387
|
+
const missingDeps = deps.filter(dep => {
|
|
388
|
+
const hasDep = (pkgJson.dependencies && pkgJson.dependencies[dep]) ||
|
|
389
|
+
(pkgJson.devDependencies && pkgJson.devDependencies[dep]);
|
|
390
|
+
return !hasDep;
|
|
391
|
+
});
|
|
392
|
+
if (missingDeps.length > 0) {
|
|
393
|
+
console.log(`[Icones] Instalando dependências de ícones necessárias: ${missingDeps.join(', ')}...`);
|
|
394
|
+
const workspaceRoot = process.cwd();
|
|
395
|
+
const isPnpm = fs.existsSync(path.join(workspaceRoot, 'pnpm-lock.yaml'));
|
|
396
|
+
const { execSync } = require('child_process');
|
|
397
|
+
for (const dep of missingDeps) {
|
|
398
|
+
if (isPnpm) {
|
|
399
|
+
execSync(`pnpm --dir "${frontendRoot}" add ${dep}`, { stdio: 'inherit' });
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
execSync(`npm --prefix "${frontendRoot}" install ${dep} --legacy-peer-deps`, { stdio: 'inherit' });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
console.log('[Icones] Dependências de ícones instaladas com sucesso!');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
console.warn('[Aviso] Falha ao instalar automaticamente dependências de ícones:', err);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Helper para inicializar o Design System local copiando os arquivos de template de UI
|
|
413
|
+
function initUi(frontendRoot, config) {
|
|
414
|
+
ensureIconDependencies(frontendRoot);
|
|
415
|
+
const designSystemConfig = config.designSystem || {};
|
|
416
|
+
const projectName = config.projectName || 'DevsTroupe Project';
|
|
417
|
+
// Obter navegação configurada ou padrão
|
|
418
|
+
let navigation = [];
|
|
419
|
+
if (config.layout && config.layout.navigation) {
|
|
420
|
+
navigation = config.layout.navigation;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
navigation = [
|
|
424
|
+
{ label: 'Dashboard', icon: 'lucideHome', route: '/dashboard' },
|
|
425
|
+
{ label: 'Playground', icon: 'lucidePalette', route: '/playground' }
|
|
426
|
+
];
|
|
427
|
+
if (config.entities && Array.isArray(config.entities)) {
|
|
428
|
+
config.entities.forEach((entity) => {
|
|
429
|
+
navigation.push({
|
|
430
|
+
label: entity.label || entity.name,
|
|
431
|
+
icon: 'lucideTable',
|
|
432
|
+
route: `/${entity.name}`
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const targetDir = path.join(frontendRoot, 'src', 'app', 'shared', 'components', 'devkit');
|
|
438
|
+
const layoutsDir = path.join(frontendRoot, 'src', 'app', 'shared', 'layouts', 'main-layout');
|
|
439
|
+
const spartanStyle = getSpartanStyle(config);
|
|
440
|
+
console.log(`Injetando componentes locais do Design System em: ${targetDir}...`);
|
|
441
|
+
runSpartanGenerator(frontendRoot, spartanStyle);
|
|
442
|
+
ensureOfficialSpartanTheme(frontendRoot, designSystemConfig.spartanTheme || 'neutral');
|
|
443
|
+
// Garantir diretórios dos componentes
|
|
444
|
+
fs.ensureDirSync(targetDir);
|
|
445
|
+
fs.ensureDirSync(path.join(targetDir, 'button'));
|
|
446
|
+
fs.ensureDirSync(path.join(targetDir, 'input'));
|
|
447
|
+
fs.ensureDirSync(path.join(targetDir, 'select'));
|
|
448
|
+
fs.ensureDirSync(path.join(targetDir, 'table'));
|
|
449
|
+
fs.ensureDirSync(path.join(targetDir, 'filter'));
|
|
450
|
+
fs.ensureDirSync(path.join(targetDir, 'card-list'));
|
|
451
|
+
fs.ensureDirSync(path.join(targetDir, 'simple-list'));
|
|
452
|
+
fs.ensureDirSync(path.join(targetDir, 'dialog'));
|
|
453
|
+
fs.ensureDirSync(path.join(targetDir, 'shell'));
|
|
454
|
+
fs.ensureDirSync(path.join(targetDir, 'sidebar'));
|
|
455
|
+
fs.ensureDirSync(path.join(targetDir, 'header'));
|
|
456
|
+
fs.ensureDirSync(path.join(targetDir, 'ui-badge-helm'));
|
|
457
|
+
fs.ensureDirSync(path.join(targetDir, 'ui-avatar-helm'));
|
|
458
|
+
// Garantir diretório do Layout
|
|
459
|
+
fs.ensureDirSync(layoutsDir);
|
|
460
|
+
// O tema e os tokens visuais pertencem ao gerador oficial do Spartan.
|
|
461
|
+
fs.writeFileSync(path.join(targetDir, 'README.md'), (0, ui_templates_1.devkitLocalReadmeTemplate)(), 'utf-8');
|
|
462
|
+
// 2. Escrever Button Component
|
|
463
|
+
const btn = (0, ui_templates_1.devkitButtonTemplate)();
|
|
464
|
+
fs.writeFileSync(path.join(targetDir, 'button', 'button.component.ts'), btn.ts, 'utf-8');
|
|
465
|
+
fs.writeFileSync(path.join(targetDir, 'button', 'button.component.html'), btn.html, 'utf-8');
|
|
466
|
+
fs.writeFileSync(path.join(targetDir, 'button', 'button.component.css'), btn.css, 'utf-8');
|
|
467
|
+
// 3. Escrever Input Component
|
|
468
|
+
const inp = (0, ui_templates_1.devkitInputTemplate)();
|
|
469
|
+
fs.writeFileSync(path.join(targetDir, 'input', 'input.component.ts'), inp.ts, 'utf-8');
|
|
470
|
+
fs.writeFileSync(path.join(targetDir, 'input', 'input.component.html'), inp.html, 'utf-8');
|
|
471
|
+
// 4. Escrever Select Component
|
|
472
|
+
const sel = (0, ui_templates_1.devkitSelectTemplate)();
|
|
473
|
+
fs.writeFileSync(path.join(targetDir, 'select', 'select.component.ts'), sel.ts, 'utf-8');
|
|
474
|
+
fs.writeFileSync(path.join(targetDir, 'select', 'select.component.html'), sel.html, 'utf-8');
|
|
475
|
+
// 5. Escrever Table Component
|
|
476
|
+
const tbl = (0, ui_templates_1.devkitTableTemplate)();
|
|
477
|
+
fs.writeFileSync(path.join(targetDir, 'table', 'table.component.ts'), tbl.ts, 'utf-8');
|
|
478
|
+
fs.writeFileSync(path.join(targetDir, 'table', 'table.component.html'), tbl.html, 'utf-8');
|
|
479
|
+
fs.writeFileSync(path.join(targetDir, 'table', 'table.component.css'), tbl.css, 'utf-8');
|
|
480
|
+
// 6. Escrever Filter Component
|
|
481
|
+
const flt = (0, ui_templates_1.devkitFilterTemplate)();
|
|
482
|
+
fs.writeFileSync(path.join(targetDir, 'filter', 'filter.component.ts'), flt.ts, 'utf-8');
|
|
483
|
+
fs.writeFileSync(path.join(targetDir, 'filter', 'filter.component.html'), flt.html, 'utf-8');
|
|
484
|
+
// 7. Escrever Card List Component
|
|
485
|
+
const crd = (0, ui_templates_1.devkitCardListTemplate)();
|
|
486
|
+
fs.writeFileSync(path.join(targetDir, 'card-list', 'card-list.component.ts'), crd.ts, 'utf-8');
|
|
487
|
+
fs.writeFileSync(path.join(targetDir, 'card-list', 'card-list.component.html'), crd.html, 'utf-8');
|
|
488
|
+
fs.writeFileSync(path.join(targetDir, 'card-list', 'card-list.component.css'), crd.css, 'utf-8');
|
|
489
|
+
// 8. Escrever Simple List Component
|
|
490
|
+
const smp = (0, ui_templates_1.devkitSimpleListTemplate)();
|
|
491
|
+
fs.writeFileSync(path.join(targetDir, 'simple-list', 'simple-list.component.ts'), smp.ts, 'utf-8');
|
|
492
|
+
fs.writeFileSync(path.join(targetDir, 'simple-list', 'simple-list.component.html'), smp.html, 'utf-8');
|
|
493
|
+
fs.writeFileSync(path.join(targetDir, 'simple-list', 'simple-list.component.css'), smp.css, 'utf-8');
|
|
494
|
+
// 9. Escrever Dialog Component
|
|
495
|
+
const dlg = (0, ui_templates_1.devkitDialogTemplate)();
|
|
496
|
+
fs.writeFileSync(path.join(targetDir, 'dialog', 'dialog.component.ts'), dlg.ts, 'utf-8');
|
|
497
|
+
fs.writeFileSync(path.join(targetDir, 'dialog', 'dialog.component.html'), dlg.html, 'utf-8');
|
|
498
|
+
fs.writeFileSync(path.join(targetDir, 'dialog', 'dialog.component.css'), dlg.css, 'utf-8');
|
|
499
|
+
// 10. Escrever Shell Component
|
|
500
|
+
const shl = (0, ui_templates_1.devkitShellTemplate)();
|
|
501
|
+
fs.writeFileSync(path.join(targetDir, 'shell', 'shell.component.ts'), shl.ts, 'utf-8');
|
|
502
|
+
fs.writeFileSync(path.join(targetDir, 'shell', 'shell.component.html'), shl.html, 'utf-8');
|
|
503
|
+
fs.writeFileSync(path.join(targetDir, 'shell', 'shell.component.css'), shl.css, 'utf-8');
|
|
504
|
+
// 11. Escrever Sidebar Component
|
|
505
|
+
const sdb = (0, ui_templates_1.devkitSidebarTemplate)();
|
|
506
|
+
fs.writeFileSync(path.join(targetDir, 'sidebar', 'sidebar.component.ts'), sdb.ts, 'utf-8');
|
|
507
|
+
fs.writeFileSync(path.join(targetDir, 'sidebar', 'sidebar.component.html'), sdb.html, 'utf-8');
|
|
508
|
+
fs.writeFileSync(path.join(targetDir, 'sidebar', 'sidebar.component.css'), sdb.css, 'utf-8');
|
|
509
|
+
// 12. Escrever Header Component
|
|
510
|
+
const hdr = (0, ui_templates_1.devkitHeaderTemplate)();
|
|
511
|
+
fs.writeFileSync(path.join(targetDir, 'header', 'header.component.ts'), hdr.ts, 'utf-8');
|
|
512
|
+
fs.writeFileSync(path.join(targetDir, 'header', 'header.component.html'), hdr.html, 'utf-8');
|
|
513
|
+
fs.writeFileSync(path.join(targetDir, 'header', 'header.component.css'), hdr.css, 'utf-8');
|
|
514
|
+
// 13. Escrever MainLayout Component
|
|
515
|
+
writeAppNavigation(frontendRoot, navigation);
|
|
516
|
+
const brandLogo = config.layout?.brandLogo || '';
|
|
517
|
+
const brandLogoCollapsed = config.layout?.brandLogoCollapsed || '';
|
|
518
|
+
const lyt = (0, ui_templates_1.devkitMainLayoutTemplate)(projectName, brandLogo, brandLogoCollapsed);
|
|
519
|
+
fs.writeFileSync(path.join(layoutsDir, 'main-layout.component.ts'), lyt.ts, 'utf-8');
|
|
520
|
+
fs.writeFileSync(path.join(layoutsDir, 'main-layout.component.html'), lyt.html, 'utf-8');
|
|
521
|
+
fs.writeFileSync(path.join(layoutsDir, 'main-layout.component.css'), lyt.css, 'utf-8');
|
|
522
|
+
// 14. Escrever DevkitBadge Component baseado no HlmBadge real gerado pelo Spartan
|
|
523
|
+
const bdg = (0, ui_templates_1.devkitBadgeTemplate)();
|
|
524
|
+
fs.writeFileSync(path.join(targetDir, 'ui-badge-helm', 'badge.component.ts'), bdg.ts, 'utf-8');
|
|
525
|
+
// 15. Escrever DevkitAvatar Component
|
|
526
|
+
const avt = (0, ui_templates_1.devkitAvatarTemplate)();
|
|
527
|
+
fs.writeFileSync(path.join(targetDir, 'ui-avatar-helm', 'avatar.component.ts'), avt.ts, 'utf-8');
|
|
528
|
+
// 16. Escrever Playground Component
|
|
529
|
+
const playgroundDir = path.join(frontendRoot, 'src', 'app', 'modules', 'playground');
|
|
530
|
+
fs.ensureDirSync(playgroundDir);
|
|
531
|
+
const ply = (0, ui_templates_1.devkitPlaygroundComponentTemplate)(getSupportedSpartanPrimitiveNames(frontendRoot));
|
|
532
|
+
fs.writeFileSync(path.join(playgroundDir, 'playground.component.ts'), ply.ts, 'utf-8');
|
|
533
|
+
fs.writeFileSync(path.join(playgroundDir, 'playground.component.html'), ply.html, 'utf-8');
|
|
534
|
+
fs.writeFileSync(path.join(playgroundDir, 'playground.component.css'), ply.css, 'utf-8');
|
|
535
|
+
// 17. Escrever ThemeService
|
|
536
|
+
const servicesDir = path.join(frontendRoot, 'src', 'app', 'core', 'services');
|
|
537
|
+
fs.ensureDirSync(servicesDir);
|
|
538
|
+
fs.writeFileSync(path.join(servicesDir, 'theme.service.ts'), (0, ui_templates_1.devkitThemeServiceTemplate)(), 'utf-8');
|
|
539
|
+
// 18. Escrever ProfileService
|
|
540
|
+
fs.writeFileSync(path.join(servicesDir, 'profile.service.ts'), (0, ui_templates_1.devkitProfileServiceTemplate)(), 'utf-8');
|
|
541
|
+
// 19. Escrever ProfileComponent
|
|
542
|
+
const profileDir = path.join(frontendRoot, 'src', 'app', 'modules', 'profile');
|
|
543
|
+
fs.ensureDirSync(profileDir);
|
|
544
|
+
const prf = (0, ui_templates_1.devkitProfileComponentTemplate)();
|
|
545
|
+
fs.writeFileSync(path.join(profileDir, 'profile.component.ts'), prf.ts, 'utf-8');
|
|
546
|
+
fs.writeFileSync(path.join(profileDir, 'profile.component.html'), prf.html, 'utf-8');
|
|
547
|
+
fs.writeFileSync(path.join(profileDir, 'profile.component.css'), prf.css, 'utf-8');
|
|
548
|
+
console.log(`[UI] Componentes DevKit, layout, Playground, ThemeService, ProfileService e ProfileComponent gerados com Spartan style="${spartanStyle}".`);
|
|
549
|
+
// Configurar tsconfig.json
|
|
550
|
+
ensureTsconfigPaths(frontendRoot);
|
|
551
|
+
// Atualizar roteamento para suportar MainLayoutComponent
|
|
552
|
+
updateAppRoutes(frontendRoot, config);
|
|
553
|
+
}
|
|
554
|
+
function updateAppRoutes(frontendRoot, config = {}) {
|
|
555
|
+
const routesPath = path.join(frontendRoot, 'src', 'app', 'app.routes.ts');
|
|
556
|
+
if (!fs.existsSync(routesPath)) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
// Filtrar entidades que possuem pasta em src/app/modules
|
|
560
|
+
const entities = Array.isArray(config) ? config : config.entities;
|
|
561
|
+
const activeEntities = (entities || []).filter((entity) => {
|
|
562
|
+
if (!entity || !entity.name)
|
|
563
|
+
return false;
|
|
564
|
+
const modulePath = path.join(frontendRoot, 'src', 'app', 'modules', entity.name);
|
|
565
|
+
return fs.existsSync(modulePath);
|
|
566
|
+
});
|
|
567
|
+
let routeImports = '';
|
|
568
|
+
let entityRoutes = '';
|
|
569
|
+
activeEntities.forEach((entity) => {
|
|
570
|
+
const pascalName = kebabToPascal(entity.name);
|
|
571
|
+
const formDialogPath = path.join(frontendRoot, 'src', 'app', 'modules', entity.name, 'form-dialog-handler');
|
|
572
|
+
const hasDialog = fs.existsSync(formDialogPath);
|
|
573
|
+
const routeConstant = `${entity.name.replace(/-/g, '_').toUpperCase()}_ROUTES`;
|
|
574
|
+
const featureRoutesPath = path.join(frontendRoot, 'src', 'app', 'modules', entity.name, `${entity.name}.routes.ts`);
|
|
575
|
+
let featureRoutes = `import { Routes } from '@angular/router';\n\nexport const ${routeConstant}: Routes = [\n`;
|
|
576
|
+
if (hasDialog) {
|
|
577
|
+
featureRoutes += ` {\n`;
|
|
578
|
+
featureRoutes += ` path: '',\n`;
|
|
579
|
+
featureRoutes += ` loadComponent: () => import('./list/${entity.name}-list.component').then(m => m.${pascalName}ListComponent),\n`;
|
|
580
|
+
featureRoutes += ` children: [\n`;
|
|
581
|
+
featureRoutes += ` { path: 'new', loadComponent: () => import('./form-dialog-handler/${entity.name}-form-dialog-handler.component').then(m => m.${pascalName}FormDialogHandlerComponent) },\n`;
|
|
582
|
+
featureRoutes += ` { path: ':id/edit', loadComponent: () => import('./form-dialog-handler/${entity.name}-form-dialog-handler.component').then(m => m.${pascalName}FormDialogHandlerComponent) }\n`;
|
|
583
|
+
featureRoutes += ` ]\n`;
|
|
584
|
+
featureRoutes += ` }\n`;
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
featureRoutes += ` { path: 'new', loadComponent: () => import('./form/${entity.name}-form.component').then(m => m.${pascalName}FormComponent) },\n`;
|
|
588
|
+
featureRoutes += ` { path: ':id/edit', loadComponent: () => import('./form/${entity.name}-form.component').then(m => m.${pascalName}FormComponent) },\n`;
|
|
589
|
+
featureRoutes += ` { path: '', loadComponent: () => import('./list/${entity.name}-list.component').then(m => m.${pascalName}ListComponent) }\n`;
|
|
590
|
+
}
|
|
591
|
+
featureRoutes += `];\n`;
|
|
592
|
+
fs.writeFileSync(featureRoutesPath, featureRoutes, 'utf-8');
|
|
593
|
+
routeImports += `import { ${routeConstant} } from './modules/${entity.name}/${entity.name}.routes';\n`;
|
|
594
|
+
entityRoutes += ` { path: '${entity.name}', children: ${routeConstant} },\n`;
|
|
595
|
+
});
|
|
596
|
+
const entityRoutesPath = path.join(frontendRoot, 'src', 'app', 'app.entity-routes.ts');
|
|
597
|
+
fs.writeFileSync(entityRoutesPath, `import { Routes } from '@angular/router';
|
|
598
|
+
|
|
599
|
+
${routeImports}
|
|
600
|
+
// Gerado a partir do devstroupe.config.ts. As rotas de cada feature ficam no próprio módulo.
|
|
601
|
+
export const ENTITY_ROUTES: Routes = [
|
|
602
|
+
${entityRoutes}];
|
|
603
|
+
`, 'utf-8');
|
|
604
|
+
const currentRoutes = fs.readFileSync(routesPath, 'utf-8');
|
|
605
|
+
if (!currentRoutes.includes('ENTITY_ROUTES')) {
|
|
606
|
+
const updatedRoutesContent = `import { Routes } from '@angular/router';
|
|
607
|
+
import { authGuard } from './core/guards/auth.guard';
|
|
608
|
+
import { LoginComponent } from './modules/auth/login/login.component';
|
|
609
|
+
import { RegisterComponent } from './modules/auth/register/register.component';
|
|
610
|
+
import { DashboardComponent } from './modules/dashboard/dashboard.component';
|
|
611
|
+
import { MainLayoutComponent } from './shared/layouts/main-layout/main-layout.component';
|
|
612
|
+
import { ENTITY_ROUTES } from './app.entity-routes';
|
|
613
|
+
|
|
614
|
+
export const routes: Routes = [
|
|
615
|
+
{ path: 'login', component: LoginComponent },
|
|
616
|
+
{ path: 'register', component: RegisterComponent },
|
|
617
|
+
{
|
|
618
|
+
path: '',
|
|
619
|
+
component: MainLayoutComponent,
|
|
620
|
+
canActivate: [authGuard],
|
|
621
|
+
children: [
|
|
622
|
+
{ path: '', component: DashboardComponent },
|
|
623
|
+
{ path: 'dashboard', component: DashboardComponent },
|
|
624
|
+
{ path: 'profile', loadComponent: () => import('./modules/profile/profile.component').then(m => m.ProfileComponent) },
|
|
625
|
+
{ path: 'playground', loadComponent: () => import('./modules/playground/playground.component').then(m => m.PlaygroundComponent) },
|
|
626
|
+
...ENTITY_ROUTES
|
|
627
|
+
]
|
|
628
|
+
},
|
|
629
|
+
{ path: '**', redirectTo: '' }
|
|
630
|
+
];
|
|
631
|
+
`;
|
|
632
|
+
fs.writeFileSync(routesPath, updatedRoutesContent, 'utf-8');
|
|
633
|
+
}
|
|
634
|
+
console.log('[Roteamento] app.entity-routes.ts sincronizado com as entidades ativas.');
|
|
635
|
+
updateMainLayoutNavigation(frontendRoot, activeEntities, config.layout?.navigation);
|
|
636
|
+
}
|
|
637
|
+
function updateMainLayoutNavigation(frontendRoot, entities = [], configuredNavigation) {
|
|
638
|
+
const layoutPath = path.join(frontendRoot, 'src', 'app', 'shared', 'layouts', 'main-layout', 'main-layout.component.ts');
|
|
639
|
+
if (!fs.existsSync(layoutPath))
|
|
640
|
+
return;
|
|
641
|
+
const navigation = configuredNavigation ?? [
|
|
642
|
+
{ label: 'Dashboard', icon: 'lucideHome', route: '/dashboard' },
|
|
643
|
+
{ label: 'Playground', icon: 'lucidePalette', route: '/playground' },
|
|
644
|
+
...entities
|
|
645
|
+
.filter((entity) => !entity.navigation?.hidden)
|
|
646
|
+
.sort((left, right) => (left.navigation?.order ?? Number.MAX_SAFE_INTEGER) -
|
|
647
|
+
(right.navigation?.order ?? Number.MAX_SAFE_INTEGER))
|
|
648
|
+
.map((entity) => ({
|
|
649
|
+
label: entity.navigation?.label || entity.label || entity.name,
|
|
650
|
+
icon: entity.navigation?.icon || 'lucideTable',
|
|
651
|
+
route: `/${entity.name}`
|
|
652
|
+
}))
|
|
653
|
+
];
|
|
654
|
+
writeAppNavigation(frontendRoot, navigation);
|
|
655
|
+
const content = fs.readFileSync(layoutPath, 'utf-8');
|
|
656
|
+
const declaration = 'menuItems: INavigationItem[] =';
|
|
657
|
+
const declarationIndex = content.indexOf(declaration);
|
|
658
|
+
if (declarationIndex < 0)
|
|
659
|
+
return;
|
|
660
|
+
const initializerEnd = content.indexOf(';', declarationIndex + declaration.length);
|
|
661
|
+
const initializer = content.slice(declarationIndex + declaration.length, initializerEnd).trim();
|
|
662
|
+
if (initializer === 'APP_NAVIGATION') {
|
|
663
|
+
console.log('[Navegação] Menu principal sincronizado com as entidades ativas.');
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const arrayStart = content.indexOf('[', declarationIndex + declaration.length);
|
|
667
|
+
if (arrayStart < 0)
|
|
668
|
+
return;
|
|
669
|
+
let depth = 0;
|
|
670
|
+
let arrayEnd = -1;
|
|
671
|
+
for (let index = arrayStart; index < content.length; index++) {
|
|
672
|
+
if (content[index] === '[')
|
|
673
|
+
depth++;
|
|
674
|
+
if (content[index] === ']')
|
|
675
|
+
depth--;
|
|
676
|
+
if (depth === 0) {
|
|
677
|
+
arrayEnd = index;
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (arrayEnd < 0)
|
|
682
|
+
return;
|
|
683
|
+
let updated = `${content.slice(0, arrayStart)}APP_NAVIGATION${content.slice(arrayEnd + 1)}`;
|
|
684
|
+
if (!updated.includes("from '../../../app.navigation'")) {
|
|
685
|
+
const importLine = "import { APP_NAVIGATION } from '../../../app.navigation';\n";
|
|
686
|
+
const lastImportEnd = updated.lastIndexOf('\n', updated.lastIndexOf('import '));
|
|
687
|
+
updated = `${updated.slice(0, lastImportEnd + 1)}${importLine}${updated.slice(lastImportEnd + 1)}`;
|
|
688
|
+
}
|
|
689
|
+
fs.writeFileSync(layoutPath, updated, 'utf-8');
|
|
690
|
+
console.log('[Navegação] Menu principal sincronizado com as entidades ativas.');
|
|
691
|
+
}
|
|
692
|
+
function writeAppNavigation(frontendRoot, navigation) {
|
|
693
|
+
const navigationPath = path.join(frontendRoot, 'src', 'app', 'app.navigation.ts');
|
|
694
|
+
const items = JSON.stringify(navigation, null, 2)
|
|
695
|
+
.replace(/"(label|icon|route|badge|roles|children)":/g, '$1:');
|
|
696
|
+
const content = `import { INavigationItem } from './shared/components/devkit/sidebar/sidebar.component';
|
|
697
|
+
|
|
698
|
+
// Gerado a partir do devstroupe.config.ts. Customize pela configuração da entidade.
|
|
699
|
+
export const APP_NAVIGATION: INavigationItem[] = ${items};
|
|
700
|
+
`;
|
|
701
|
+
fs.writeFileSync(navigationPath, content, 'utf-8');
|
|
702
|
+
ensureNavigationIcons(frontendRoot, navigation.map((item) => item.icon).filter(Boolean));
|
|
703
|
+
}
|
|
704
|
+
function ensureNavigationIcons(frontendRoot, icons) {
|
|
705
|
+
const configPath = path.join(frontendRoot, 'src', 'app', 'app.config.ts');
|
|
706
|
+
if (!fs.existsSync(configPath))
|
|
707
|
+
return;
|
|
708
|
+
const validIcons = [...new Set(icons)].filter((icon) => /^lucide[A-Z][A-Za-z0-9]*$/.test(icon));
|
|
709
|
+
let content = fs.readFileSync(configPath, 'utf-8');
|
|
710
|
+
const missingIcons = validIcons.filter((icon) => !new RegExp(`\\b${icon}\\b`).test(content));
|
|
711
|
+
if (missingIcons.length === 0)
|
|
712
|
+
return;
|
|
713
|
+
content = content.replace(/([\s\S]*?)(\n} from '@ng-icons\/lucide';)/, (match, imports, suffix) => `${imports.replace(/\s*$/, '')},\n ${missingIcons.join(',\n ')}${suffix}`);
|
|
714
|
+
content = content.replace(/(provideIcons\(\{[\s\S]*?)(\n\s*}\))/, (match, providers, suffix) => `${providers.replace(/\s*$/, '')},\n ${missingIcons.join(',\n ')}${suffix}`);
|
|
715
|
+
fs.writeFileSync(configPath, content, 'utf-8');
|
|
716
|
+
}
|
|
717
|
+
program
|
|
718
|
+
.name('devstroupe')
|
|
719
|
+
.description('DevsTroupe Development Kit (DT-DevKit) CLI')
|
|
720
|
+
.version('1.0.0');
|
|
721
|
+
// Comando init
|
|
722
|
+
program
|
|
723
|
+
.command('init')
|
|
724
|
+
.description('Inicializa o arquivo de configuração devstroupe.config.ts na raiz do projeto')
|
|
725
|
+
.action(() => {
|
|
726
|
+
const configPath = path.join(process.cwd(), 'devstroupe.config.ts');
|
|
727
|
+
if (fs.existsSync(configPath)) {
|
|
728
|
+
console.log('O arquivo devstroupe.config.ts já existe na raiz do projeto.');
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const configContent = `export default {
|
|
732
|
+
projectName: 'TrevoIA',
|
|
733
|
+
backendPath: './backend',
|
|
734
|
+
frontendPath: './frontend',
|
|
735
|
+
designSystem: {
|
|
736
|
+
provider: 'spartan',
|
|
737
|
+
spartanStyle: 'vega', // 'nova' | 'vega' | 'lyra' | 'maia' | 'mira' | 'luma'
|
|
738
|
+
spartanTheme: 'neutral', // 'neutral' | 'stone' | 'zinc' | 'gray' | 'slate'
|
|
739
|
+
installAllPrimitives: true,
|
|
740
|
+
includePlayground: true,
|
|
741
|
+
},
|
|
742
|
+
entities: [
|
|
743
|
+
{
|
|
744
|
+
name: 'company',
|
|
745
|
+
label: 'Empresa',
|
|
746
|
+
tableName: 'companies',
|
|
747
|
+
properties: [
|
|
748
|
+
{ name: 'name', type: 'string', required: true, label: 'Nome' },
|
|
749
|
+
{
|
|
750
|
+
name: 'cabins', type: 'relationship', required: false, label: 'Cabines',
|
|
751
|
+
relation: {
|
|
752
|
+
target: 'cabin', kind: 'one-to-many', foreignKey: 'company_id',
|
|
753
|
+
inverseSide: 'company', control: 'table', embedded: true,
|
|
754
|
+
columns: ['name', 'code', 'is_active'], lookup: { pageSize: 10 }
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
],
|
|
758
|
+
crud: { generateFrontend: true, generateBackend: true }
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
name: 'cabin',
|
|
762
|
+
label: 'Cabine',
|
|
763
|
+
tableName: 'cabins',
|
|
764
|
+
properties: [
|
|
765
|
+
{ name: 'name', type: 'string', required: true, label: 'Nome' },
|
|
766
|
+
{ name: 'code', type: 'string', required: false, label: 'Código' },
|
|
767
|
+
{
|
|
768
|
+
name: 'company_id', type: 'relationship', required: true, label: 'Empresa',
|
|
769
|
+
relation: {
|
|
770
|
+
target: 'company', kind: 'many-to-one', relationName: 'company',
|
|
771
|
+
inverseSide: 'cabins', control: 'select', displayField: 'name', onDelete: 'CASCADE',
|
|
772
|
+
lookup: { mode: 'remote', pageSize: 20, minSearchLength: 0 }
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
{ name: 'is_active', type: 'boolean', required: true, label: 'Ativo', default: true }
|
|
776
|
+
],
|
|
777
|
+
crud: {
|
|
778
|
+
generateFrontend: true,
|
|
779
|
+
generateBackend: true
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
]
|
|
783
|
+
};
|
|
784
|
+
`;
|
|
785
|
+
fs.writeFileSync(configPath, configContent, 'utf-8');
|
|
786
|
+
console.log('Arquivo devstroupe.config.ts criado com sucesso!');
|
|
787
|
+
});
|
|
788
|
+
// Novo Comando init-ui para forçar geração manual do Design System
|
|
789
|
+
program
|
|
790
|
+
.command('init-ui')
|
|
791
|
+
.description('Inicializa o Design System local copiando estilos e componentes primitivos para o projeto Angular')
|
|
792
|
+
.option('-s, --style <style>', 'Style Spartan: nova, vega, lyra, maia, mira ou luma')
|
|
793
|
+
.action((options) => {
|
|
794
|
+
const configPath = path.join(process.cwd(), 'devstroupe.config.ts');
|
|
795
|
+
if (!fs.existsSync(configPath)) {
|
|
796
|
+
console.error('Arquivo devstroupe.config.ts não encontrado na raiz. Rode "devstroupe init" primeiro.');
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
let config;
|
|
800
|
+
try {
|
|
801
|
+
const importedConfig = require(configPath);
|
|
802
|
+
config = importedConfig.default || importedConfig;
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
console.error('Erro ao carregar devstroupe.config.ts:', error);
|
|
806
|
+
process.exit(1);
|
|
807
|
+
}
|
|
808
|
+
try {
|
|
809
|
+
assertValidRelationshipConfig(config);
|
|
810
|
+
}
|
|
811
|
+
catch (error) {
|
|
812
|
+
console.error('Configuração de relacionamentos inválida:', error instanceof Error ? error.message : error);
|
|
813
|
+
process.exit(1);
|
|
814
|
+
}
|
|
815
|
+
const frontendRoot = path.resolve(process.cwd(), config.frontendPath || './frontend');
|
|
816
|
+
if (!fs.existsSync(frontendRoot)) {
|
|
817
|
+
console.error(`Diretório frontend não encontrado em: ${frontendRoot}. Certifique-se de configurar o "frontendPath" no devstroupe.config.ts corretamente.`);
|
|
818
|
+
process.exit(1);
|
|
819
|
+
}
|
|
820
|
+
const requestedStyle = options.style;
|
|
821
|
+
if (requestedStyle) {
|
|
822
|
+
if (!isSpartanStyle(requestedStyle)) {
|
|
823
|
+
console.error(`Style Spartan inválido "${requestedStyle}". Use: ${SPARTAN_STYLES.join(', ')}.`);
|
|
824
|
+
process.exit(1);
|
|
825
|
+
}
|
|
826
|
+
config.designSystem = { ...(config.designSystem || {}), spartanStyle: requestedStyle };
|
|
827
|
+
}
|
|
828
|
+
initUi(frontendRoot, config);
|
|
829
|
+
console.log('Design System local inicializado e mapeado com sucesso!');
|
|
830
|
+
});
|
|
831
|
+
program
|
|
832
|
+
.command('ui:set-style <style>')
|
|
833
|
+
.description('Altera o style do Spartan e regenera todos os primitives Helm reais em libs/ui')
|
|
834
|
+
.action((style) => {
|
|
835
|
+
if (!isSpartanStyle(style)) {
|
|
836
|
+
console.error(`Style Spartan inválido "${style}". Use: ${SPARTAN_STYLES.join(', ')}.`);
|
|
837
|
+
process.exit(1);
|
|
838
|
+
}
|
|
839
|
+
const configPath = path.join(process.cwd(), 'devstroupe.config.ts');
|
|
840
|
+
if (!fs.existsSync(configPath)) {
|
|
841
|
+
console.error('Arquivo devstroupe.config.ts não encontrado na raiz. Rode "devstroupe init" primeiro.');
|
|
842
|
+
process.exit(1);
|
|
843
|
+
}
|
|
844
|
+
let config;
|
|
845
|
+
try {
|
|
846
|
+
delete require.cache[require.resolve(configPath)];
|
|
847
|
+
const importedConfig = require(configPath);
|
|
848
|
+
config = importedConfig.default || importedConfig;
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
console.error('Erro ao carregar devstroupe.config.ts:', error);
|
|
852
|
+
process.exit(1);
|
|
853
|
+
}
|
|
854
|
+
const frontendRoot = path.resolve(process.cwd(), config.frontendPath || './frontend');
|
|
855
|
+
if (!fs.existsSync(frontendRoot)) {
|
|
856
|
+
console.error(`Diretório frontend não encontrado em: ${frontendRoot}.`);
|
|
857
|
+
process.exit(1);
|
|
858
|
+
}
|
|
859
|
+
runSpartanGenerator(frontendRoot, style, true);
|
|
860
|
+
console.log(`[Spartan] Style alterado para "${style}" e primitives regenerados em libs/ui.`);
|
|
861
|
+
});
|
|
862
|
+
// Novo comando lint:rules
|
|
863
|
+
program
|
|
864
|
+
.command('lint:rules')
|
|
865
|
+
.description('Audita o projeto para garantir a conformidade com as regras e boas práticas de governança da DevsTroupe')
|
|
866
|
+
.action(() => {
|
|
867
|
+
const configPath = path.join(process.cwd(), 'devstroupe.config.ts');
|
|
868
|
+
if (!fs.existsSync(configPath)) {
|
|
869
|
+
console.error('Arquivo devstroupe.config.ts não encontrado na raiz. Rode "devstroupe init" primeiro.');
|
|
870
|
+
process.exit(1);
|
|
871
|
+
}
|
|
872
|
+
let config;
|
|
873
|
+
try {
|
|
874
|
+
const importedConfig = require(configPath);
|
|
875
|
+
config = importedConfig.default || importedConfig;
|
|
876
|
+
}
|
|
877
|
+
catch (error) {
|
|
878
|
+
console.error('Erro ao carregar devstroupe.config.ts:', error);
|
|
879
|
+
process.exit(1);
|
|
880
|
+
}
|
|
881
|
+
console.log('Iniciando varredura estática de governança da DevsTroupe...');
|
|
882
|
+
const result = (0, linter_rules_1.runLinter)(process.cwd(), config);
|
|
883
|
+
console.log(`\nVarredura finalizada. Escaneados ${result.filesScanned} arquivos.`);
|
|
884
|
+
if (result.violations.length === 0) {
|
|
885
|
+
console.log('\x1b[32m[SUCESSO] Nenhuma violação de governança encontrada! O projeto segue todos os padrões de código.\x1b[0m');
|
|
886
|
+
process.exit(0);
|
|
887
|
+
}
|
|
888
|
+
console.log(`Encontradas ${result.violations.length} violações de governança:\n`);
|
|
889
|
+
let errorCount = 0;
|
|
890
|
+
let warnCount = 0;
|
|
891
|
+
result.violations.forEach((v) => {
|
|
892
|
+
const isError = v.severity === 'error';
|
|
893
|
+
if (isError)
|
|
894
|
+
errorCount++;
|
|
895
|
+
else
|
|
896
|
+
warnCount++;
|
|
897
|
+
const severityTag = isError ? '\x1b[31m[ERRO]\x1b[0m' : '\x1b[33m[AVISO]\x1b[0m';
|
|
898
|
+
console.log(`${severityTag} ${v.file}:${v.line} (${v.rule})`);
|
|
899
|
+
console.log(` => ${v.message}\n`);
|
|
900
|
+
});
|
|
901
|
+
console.log(`Resumo: ${errorCount} erros críticos, ${warnCount} avisos de atenção.`);
|
|
902
|
+
if (errorCount > 0) {
|
|
903
|
+
console.error('\x1b[31m[FALHA] A validação de governança falhou devido a erros críticos.\x1b[0m');
|
|
904
|
+
process.exit(1);
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
console.log('\x1b[33m[AVISO] Varredura passou com avisos secundários.\x1b[0m');
|
|
908
|
+
process.exit(0);
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
// Comando generate crud
|
|
912
|
+
program
|
|
913
|
+
.command('generate <type> <entityName>')
|
|
914
|
+
.description('Gera artefatos do DevKit. Tipos suportados: crud')
|
|
915
|
+
.action((type, entityName) => {
|
|
916
|
+
if (type !== 'crud') {
|
|
917
|
+
console.error(`Tipo de geração "${type}" não é suportado. Use "crud".`);
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
const configPath = path.join(process.cwd(), 'devstroupe.config.ts');
|
|
921
|
+
if (!fs.existsSync(configPath)) {
|
|
922
|
+
console.error('Arquivo devstroupe.config.ts não encontrado na raiz. Rode "devstroupe init" primeiro.');
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
925
|
+
let config;
|
|
926
|
+
try {
|
|
927
|
+
const importedConfig = require(configPath);
|
|
928
|
+
config = importedConfig.default || importedConfig;
|
|
929
|
+
}
|
|
930
|
+
catch (error) {
|
|
931
|
+
console.error('Erro ao carregar devstroupe.config.ts:', error);
|
|
932
|
+
process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
try {
|
|
935
|
+
assertValidRelationshipConfig(config);
|
|
936
|
+
}
|
|
937
|
+
catch (error) {
|
|
938
|
+
console.error('Configuração de relacionamentos inválida:', error instanceof Error ? error.message : error);
|
|
939
|
+
process.exit(1);
|
|
940
|
+
}
|
|
941
|
+
const entity = config.entities?.find((e) => e.name.toLowerCase() === entityName.toLowerCase());
|
|
942
|
+
if (!entity) {
|
|
943
|
+
console.error(`Entidade "${entityName}" não configurada no devstroupe.config.ts.`);
|
|
944
|
+
process.exit(1);
|
|
945
|
+
}
|
|
946
|
+
const backendRoot = path.resolve(process.cwd(), config.backendPath || './backend');
|
|
947
|
+
const frontendRoot = path.resolve(process.cwd(), config.frontendPath || './frontend');
|
|
948
|
+
// 1. Gerar Backend (NestJS)
|
|
949
|
+
if (entity.crud?.generateBackend !== false) {
|
|
950
|
+
const isMultiTenant = !!config.multiTenant?.enabled;
|
|
951
|
+
const isTenantScoped = isMultiTenant && (entity.tenantScoped !== false);
|
|
952
|
+
const isMicroservices = config.backendType === 'microservices';
|
|
953
|
+
const backendProperties = isMicroservices
|
|
954
|
+
? entity.properties
|
|
955
|
+
.filter((property) => property.type !== 'relationship' || (0, relationships_1.getRelationships)([property])[0].kind !== 'one-to-many')
|
|
956
|
+
.map((property) => property.type === 'relationship'
|
|
957
|
+
? { ...property, type: 'number', name: (0, relationships_1.getRelationships)([property])[0].foreignKey }
|
|
958
|
+
: property)
|
|
959
|
+
: entity.properties;
|
|
960
|
+
let boilerplatesDir = path.resolve(__dirname, 'boilerplates');
|
|
961
|
+
if (!fs.existsSync(boilerplatesDir)) {
|
|
962
|
+
boilerplatesDir = path.resolve(__dirname, '../boilerplates');
|
|
963
|
+
}
|
|
964
|
+
if (!fs.existsSync(boilerplatesDir)) {
|
|
965
|
+
boilerplatesDir = path.resolve(__dirname, '../../boilerplates');
|
|
966
|
+
}
|
|
967
|
+
const nestTemplateDir = path.join(boilerplatesDir, 'nest-template');
|
|
968
|
+
if (isMicroservices) {
|
|
969
|
+
const entityIndex = config.entities.findIndex((e) => e.name.toLowerCase() === entity.name.toLowerCase());
|
|
970
|
+
const port = 3001 + (entityIndex >= 0 ? entityIndex : 0);
|
|
971
|
+
// Garantir estrutura do API Gateway
|
|
972
|
+
const gatewayRoot = path.join(backendRoot, 'api-gateway');
|
|
973
|
+
if (!fs.existsSync(gatewayRoot) || !fs.existsSync(path.join(gatewayRoot, 'src', 'app.module.ts'))) {
|
|
974
|
+
console.log(`[Gateway] Inicializando estrutura do API Gateway...`);
|
|
975
|
+
fs.ensureDirSync(gatewayRoot);
|
|
976
|
+
fs.copySync(nestTemplateDir, gatewayRoot);
|
|
977
|
+
const gatewayEnv = `PORT=13000
|
|
978
|
+
DB_HOST=database
|
|
979
|
+
DB_PORT=3306
|
|
980
|
+
DB_USER=root
|
|
981
|
+
DB_PASSWORD=root
|
|
982
|
+
DB_NAME=devstroupe
|
|
983
|
+
JWT_SECRET=devstroupe-secret-key-12345
|
|
984
|
+
`;
|
|
985
|
+
fs.writeFileSync(path.join(gatewayRoot, '.env'), gatewayEnv, 'utf-8');
|
|
986
|
+
fs.writeFileSync(path.join(gatewayRoot, '.env.example'), gatewayEnv, 'utf-8');
|
|
987
|
+
patchLocalDependencies(gatewayRoot, null, boilerplatesDir);
|
|
988
|
+
try {
|
|
989
|
+
console.log(`[Gateway] Instalando dependências em: ${gatewayRoot}...`);
|
|
990
|
+
(0, child_process_1.execSync)('npm install', { cwd: gatewayRoot, stdio: 'inherit' });
|
|
991
|
+
}
|
|
992
|
+
catch (err) {
|
|
993
|
+
console.warn(`[Aviso] Falha ao rodar "npm install" no API Gateway. Instale manualmente.`);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const msRoot = path.join(backendRoot, 'services', entity.name);
|
|
997
|
+
console.log(`Gerando Microsserviço para a entidade "${entityName}" em: ${msRoot}...`);
|
|
998
|
+
if (!fs.existsSync(msRoot) || !fs.existsSync(path.join(msRoot, 'src', 'app.module.ts'))) {
|
|
999
|
+
console.log(`[Microsserviço] Inicializando estrutura do microsserviço...`);
|
|
1000
|
+
fs.ensureDirSync(msRoot);
|
|
1001
|
+
fs.copySync(nestTemplateDir, msRoot);
|
|
1002
|
+
// Limpa módulos desnecessários
|
|
1003
|
+
fs.removeSync(path.join(msRoot, 'src', 'modules', 'user'));
|
|
1004
|
+
fs.removeSync(path.join(msRoot, 'src', 'modules', 'auth'));
|
|
1005
|
+
// Injeta arquivos específicos de microsserviço
|
|
1006
|
+
fs.writeFileSync(path.join(msRoot, 'src', 'main.ts'), generateMsMainTs(port), 'utf-8');
|
|
1007
|
+
fs.writeFileSync(path.join(msRoot, 'src', 'app.module.ts'), generateMsAppModule(entity.name), 'utf-8');
|
|
1008
|
+
// Injeta env básico no microsserviço
|
|
1009
|
+
const msEnv = `PORT=${port}
|
|
1010
|
+
DB_HOST=database
|
|
1011
|
+
DB_PORT=3306
|
|
1012
|
+
DB_USER=root
|
|
1013
|
+
DB_PASSWORD=root
|
|
1014
|
+
DB_NAME=devstroupe_${entity.name.toLowerCase()}
|
|
1015
|
+
SERVICE_PORT=${port}
|
|
1016
|
+
`;
|
|
1017
|
+
fs.writeFileSync(path.join(msRoot, '.env'), msEnv, 'utf-8');
|
|
1018
|
+
fs.writeFileSync(path.join(msRoot, '.env.example'), msEnv, 'utf-8');
|
|
1019
|
+
patchLocalDependencies(msRoot, null, boilerplatesDir);
|
|
1020
|
+
try {
|
|
1021
|
+
console.log(`[Microsserviço] Instalando dependências em: ${msRoot}...`);
|
|
1022
|
+
(0, child_process_1.execSync)('npm install', { cwd: msRoot, stdio: 'inherit' });
|
|
1023
|
+
}
|
|
1024
|
+
catch (err) {
|
|
1025
|
+
console.warn(`[Aviso] Falha ao rodar "npm install" no microsserviço. Instale manualmente.`);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
const modulePath = path.join(msRoot, 'src', 'modules', entity.name);
|
|
1029
|
+
fs.ensureDirSync(path.join(modulePath, 'domain'));
|
|
1030
|
+
fs.ensureDirSync(path.join(modulePath, 'service'));
|
|
1031
|
+
fs.ensureDirSync(path.join(modulePath, 'infra', 'database'));
|
|
1032
|
+
fs.ensureDirSync(path.join(modulePath, 'infra', 'http'));
|
|
1033
|
+
// Domain Entity
|
|
1034
|
+
fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.ts`), (0, cli_templates_1.nestDomainEntityTemplate)(entity.name, backendProperties, isTenantScoped), 'utf-8');
|
|
1035
|
+
// Repository Port
|
|
1036
|
+
fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.repository.port.ts`), (0, cli_templates_1.nestRepositoryPortTemplate)(entity.name), 'utf-8');
|
|
1037
|
+
// DTO
|
|
1038
|
+
fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.dto.ts`), (0, cli_templates_1.nestDtoTemplate)(entity.name, backendProperties), 'utf-8');
|
|
1039
|
+
// Controller (TCP)
|
|
1040
|
+
fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.ms.controller.ts`), (0, cli_templates_1.nestMsControllerTemplate)(entity.name), 'utf-8');
|
|
1041
|
+
// ORM Entity
|
|
1042
|
+
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), 'utf-8');
|
|
1043
|
+
// Repository Adapter
|
|
1044
|
+
fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.repository.ts`), (0, cli_templates_1.nestRepositoryAdapterTemplate)(entity.name, backendProperties, isTenantScoped), 'utf-8');
|
|
1045
|
+
// Service
|
|
1046
|
+
fs.writeFileSync(path.join(modulePath, 'service', `${entity.name}.service.ts`), (0, cli_templates_1.nestServiceTemplate)(entity.name), 'utf-8');
|
|
1047
|
+
// Module (TCP)
|
|
1048
|
+
fs.writeFileSync(path.join(modulePath, `${entity.name}.module.ts`), (0, cli_templates_1.nestMsModuleTemplate)(entity.name), 'utf-8');
|
|
1049
|
+
console.log(`[Microsserviço] Arquivos do microsserviço "${entityName}" gerados com sucesso.`);
|
|
1050
|
+
if (entity.crud?.generateMigration !== false) {
|
|
1051
|
+
ensureInitialMigrations(msRoot, [entity], new Map([[entity.name, backendProperties]]));
|
|
1052
|
+
}
|
|
1053
|
+
// --- Gerar API Gateway Module ---
|
|
1054
|
+
const gatewayModulePath = path.join(backendRoot, 'api-gateway', 'src', 'modules', entity.name);
|
|
1055
|
+
console.log(`Gerando Módulo Gateway para a entidade "${entityName}" em: ${gatewayModulePath}...`);
|
|
1056
|
+
fs.ensureDirSync(path.join(gatewayModulePath, 'infra', 'http'));
|
|
1057
|
+
// DTO (Gateway)
|
|
1058
|
+
fs.writeFileSync(path.join(gatewayModulePath, 'infra', 'http', `${entity.name}.dto.ts`), (0, cli_templates_1.nestDtoTemplate)(entity.name, backendProperties), 'utf-8');
|
|
1059
|
+
// Controller (Gateway)
|
|
1060
|
+
fs.writeFileSync(path.join(gatewayModulePath, 'infra', 'http', `${entity.name}.controller.ts`), (0, cli_templates_1.nestGatewayControllerTemplate)(entity.name), 'utf-8');
|
|
1061
|
+
// Module (Gateway)
|
|
1062
|
+
fs.writeFileSync(path.join(gatewayModulePath, `${entity.name}.module.ts`), (0, cli_templates_1.nestGatewayModuleTemplate)(entity.name, port), 'utf-8');
|
|
1063
|
+
// Patch app.module.ts do Gateway
|
|
1064
|
+
patchGatewayAppModule(path.join(backendRoot, 'api-gateway', 'src', 'app.module.ts'), entity.name);
|
|
1065
|
+
console.log(`[Gateway] Módulo Gateway "${entityName}" gerado com sucesso.`);
|
|
1066
|
+
}
|
|
1067
|
+
else {
|
|
1068
|
+
// Monolito
|
|
1069
|
+
console.log(`Gerando arquivos NestJS Monolito para a entidade "${entityName}"...`);
|
|
1070
|
+
const modulePath = path.join(backendRoot, 'src', 'modules', entity.name);
|
|
1071
|
+
fs.ensureDirSync(path.join(modulePath, 'domain'));
|
|
1072
|
+
fs.ensureDirSync(path.join(modulePath, 'service'));
|
|
1073
|
+
fs.ensureDirSync(path.join(modulePath, 'infra', 'database'));
|
|
1074
|
+
fs.ensureDirSync(path.join(modulePath, 'infra', 'http'));
|
|
1075
|
+
// Domain Entity
|
|
1076
|
+
fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.ts`), (0, cli_templates_1.nestDomainEntityTemplate)(entity.name, entity.properties, isTenantScoped), 'utf-8');
|
|
1077
|
+
// Repository Port
|
|
1078
|
+
fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.repository.port.ts`), (0, cli_templates_1.nestRepositoryPortTemplate)(entity.name), 'utf-8');
|
|
1079
|
+
// DTO
|
|
1080
|
+
fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.dto.ts`), (0, cli_templates_1.nestDtoTemplate)(entity.name, entity.properties), 'utf-8');
|
|
1081
|
+
// Controller
|
|
1082
|
+
fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.controller.ts`), (0, cli_templates_1.nestControllerTemplate)(entity.name), 'utf-8');
|
|
1083
|
+
// ORM Entity
|
|
1084
|
+
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), 'utf-8');
|
|
1085
|
+
// Repository Adapter
|
|
1086
|
+
fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.repository.ts`), (0, cli_templates_1.nestRepositoryAdapterTemplate)(entity.name, entity.properties, isTenantScoped), 'utf-8');
|
|
1087
|
+
// Service
|
|
1088
|
+
fs.writeFileSync(path.join(modulePath, 'service', `${entity.name}.service.ts`), (0, cli_templates_1.nestServiceTemplate)(entity.name), 'utf-8');
|
|
1089
|
+
// Module
|
|
1090
|
+
fs.writeFileSync(path.join(modulePath, `${entity.name}.module.ts`), (0, cli_templates_1.nestModuleTemplate)(entity.name), 'utf-8');
|
|
1091
|
+
console.log(`[NestJS] Módulo Monolito "${entityName}" gerado com sucesso em: ${modulePath}`);
|
|
1092
|
+
patchMonolithAppModule(path.join(backendRoot, 'src', 'app.module.ts'), entity.name);
|
|
1093
|
+
const migrationEntities = (config.entities ?? []).filter((configuredEntity) => configuredEntity.crud?.generateBackend !== false
|
|
1094
|
+
&& configuredEntity.crud?.generateMigration !== false);
|
|
1095
|
+
if (migrationEntities.length > 0) {
|
|
1096
|
+
ensureInitialMigrations(backendRoot, migrationEntities);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
// Regerar docker-compose.yml e init.sql para sincronizar portas/bancos
|
|
1100
|
+
generateDockerComposeAndDbInit(process.cwd(), config);
|
|
1101
|
+
}
|
|
1102
|
+
// 2. Gerar Frontend (Angular)
|
|
1103
|
+
if (entity.crud?.generateFrontend !== false) {
|
|
1104
|
+
console.log(`Gerando arquivos Angular para a entidade "${entityName}"...`);
|
|
1105
|
+
const servicePath = path.join(frontendRoot, 'src', 'app', 'services');
|
|
1106
|
+
const modulePath = path.join(frontendRoot, 'src', 'app', 'modules', entity.name);
|
|
1107
|
+
const uiComponentsDir = path.join(frontendRoot, 'src', 'app', 'shared', 'components', 'devkit');
|
|
1108
|
+
// Hook automático: se a pasta de design system local não existir no frontend, inicializa-a primeiro!
|
|
1109
|
+
if (!fs.existsSync(uiComponentsDir)) {
|
|
1110
|
+
console.log('[Autoconfig] Design System local não encontrado. Inicializando automaticamente...');
|
|
1111
|
+
initUi(frontendRoot, config);
|
|
1112
|
+
}
|
|
1113
|
+
const listType = entity.crud?.listType || config.designSystem?.defaultListType || 'table';
|
|
1114
|
+
const formType = entity.crud?.formType || 'page';
|
|
1115
|
+
const filterType = entity.crud?.filterType || 'popover';
|
|
1116
|
+
fs.ensureDirSync(servicePath);
|
|
1117
|
+
fs.ensureDirSync(path.join(modulePath, 'list'));
|
|
1118
|
+
fs.ensureDirSync(path.join(modulePath, 'form'));
|
|
1119
|
+
if (formType === 'dialog') {
|
|
1120
|
+
fs.ensureDirSync(path.join(modulePath, 'form-dialog-handler'));
|
|
1121
|
+
}
|
|
1122
|
+
// Angular Service
|
|
1123
|
+
fs.writeFileSync(path.join(servicePath, `${entity.name}.service.ts`), (0, cli_templates_1.angularServiceTemplate)(entity.name), 'utf-8');
|
|
1124
|
+
// List HTML
|
|
1125
|
+
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');
|
|
1126
|
+
// List TS
|
|
1127
|
+
fs.writeFileSync(path.join(modulePath, 'list', `${entity.name}-list.component.ts`), (0, cli_templates_1.angularListTsTemplate)(entity.name, entity.properties, listType, formType), 'utf-8');
|
|
1128
|
+
// Form HTML
|
|
1129
|
+
fs.writeFileSync(path.join(modulePath, 'form', `${entity.name}-form.component.html`), (0, cli_templates_1.angularFormHtmlTemplate)(entity.name, entity.properties, config.entities), 'utf-8');
|
|
1130
|
+
// Form TS
|
|
1131
|
+
fs.writeFileSync(path.join(modulePath, 'form', `${entity.name}-form.component.ts`), (0, cli_templates_1.angularFormTsTemplate)(entity.name, entity.properties, config.entities), 'utf-8');
|
|
1132
|
+
if (formType === 'dialog') {
|
|
1133
|
+
fs.writeFileSync(path.join(modulePath, 'form-dialog-handler', `${entity.name}-form-dialog-handler.component.html`), (0, cli_templates_1.angularFormDialogHandlerHtmlTemplate)(entity.name), 'utf-8');
|
|
1134
|
+
fs.writeFileSync(path.join(modulePath, 'form-dialog-handler', `${entity.name}-form-dialog-handler.component.ts`), (0, cli_templates_1.angularFormDialogHandlerTsTemplate)(entity.name), 'utf-8');
|
|
1135
|
+
}
|
|
1136
|
+
console.log(`[Angular] Módulo "${entityName}" gerado com sucesso em: ${modulePath}`);
|
|
1137
|
+
console.log(`\n\x1b[32m[SUCESSO] Scaffolding de CRUD completo para "${entityName}" finalizado!\x1b[0m`);
|
|
1138
|
+
console.log('[Angular] Rotas e navegação serão sincronizadas automaticamente.');
|
|
1139
|
+
updateAppRoutes(frontendRoot, config);
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
// Helper para linkar dependências locais se estiver rodando no monorepo de desenvolvimento
|
|
1143
|
+
function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
|
|
1144
|
+
const packagesDir = path.resolve(boilerplatesDir, '../../..');
|
|
1145
|
+
if (!fs.existsSync(packagesDir) || !fs.existsSync(path.join(packagesDir, 'devkit-core'))) {
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const coreSrc = path.join(packagesDir, 'devkit-core');
|
|
1149
|
+
const nestSrc = path.join(packagesDir, 'devkit-nest');
|
|
1150
|
+
const angularSrc = path.join(packagesDir, 'devkit-angular');
|
|
1151
|
+
const stripDevelopmentDependencies = (packageDir) => {
|
|
1152
|
+
const packagePath = path.join(packageDir, 'package.json');
|
|
1153
|
+
if (!fs.existsSync(packagePath))
|
|
1154
|
+
return;
|
|
1155
|
+
const packageJson = fs.readJsonSync(packagePath);
|
|
1156
|
+
delete packageJson.devDependencies;
|
|
1157
|
+
packageJson.scripts = {};
|
|
1158
|
+
fs.writeJsonSync(packagePath, packageJson, { spaces: 2 });
|
|
1159
|
+
};
|
|
1160
|
+
const copyFilter = (src) => {
|
|
1161
|
+
const relativePath = path.relative(packagesDir, src);
|
|
1162
|
+
const ignoreList = ['node_modules', '.git', '.angular', 'tsconfig.json', 'tsconfig.build.json', 'src', 'tests', 'test'];
|
|
1163
|
+
const parts = relativePath.split(path.sep);
|
|
1164
|
+
if (parts.length > 1) {
|
|
1165
|
+
const subParts = parts.slice(1);
|
|
1166
|
+
if (subParts.some(p => ignoreList.includes(p))) {
|
|
1167
|
+
return false;
|
|
1168
|
+
}
|
|
1169
|
+
const lastPart = subParts[subParts.length - 1];
|
|
1170
|
+
if (lastPart.endsWith('.ts') && !subParts.includes('dist')) {
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return true;
|
|
1175
|
+
};
|
|
1176
|
+
// 1. Preparar e copiar para o backend
|
|
1177
|
+
if (backendDest && fs.existsSync(backendDest)) {
|
|
1178
|
+
const backendLocalPackages = path.join(backendDest, 'local_packages');
|
|
1179
|
+
if (fs.existsSync(coreSrc)) {
|
|
1180
|
+
const coreDest = path.join(backendLocalPackages, 'devkit-core');
|
|
1181
|
+
fs.ensureDirSync(coreDest);
|
|
1182
|
+
fs.copySync(coreSrc, coreDest, { filter: copyFilter });
|
|
1183
|
+
stripDevelopmentDependencies(coreDest);
|
|
1184
|
+
}
|
|
1185
|
+
if (fs.existsSync(nestSrc)) {
|
|
1186
|
+
const nestDest = path.join(backendLocalPackages, 'devkit-nest');
|
|
1187
|
+
fs.ensureDirSync(nestDest);
|
|
1188
|
+
fs.copySync(nestSrc, nestDest, { filter: copyFilter });
|
|
1189
|
+
stripDevelopmentDependencies(nestDest);
|
|
1190
|
+
// Correção de workspace:* para file:../devkit-core
|
|
1191
|
+
const nestPkgPath = path.join(nestDest, 'package.json');
|
|
1192
|
+
if (fs.existsSync(nestPkgPath)) {
|
|
1193
|
+
try {
|
|
1194
|
+
const nestPkg = fs.readJsonSync(nestPkgPath);
|
|
1195
|
+
if (nestPkg.dependencies && nestPkg.dependencies['@devstroupe/devkit-core'] === 'workspace:*') {
|
|
1196
|
+
nestPkg.dependencies['@devstroupe/devkit-core'] = 'file:../devkit-core';
|
|
1197
|
+
fs.writeJsonSync(nestPkgPath, nestPkg, { spaces: 2 });
|
|
1198
|
+
console.log('[DevConfig] Ajustado workspace:* para file:../devkit-core no devkit-nest local.');
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
catch (e) {
|
|
1202
|
+
console.error('[Erro] Falha ao ajustar dependências no devkit-nest local:', e);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
// Patch Backend package.json
|
|
1207
|
+
const backendPkgPath = path.join(backendDest, 'package.json');
|
|
1208
|
+
if (fs.existsSync(backendPkgPath)) {
|
|
1209
|
+
try {
|
|
1210
|
+
const pkg = fs.readJsonSync(backendPkgPath);
|
|
1211
|
+
const relCore = 'file:local_packages/devkit-core';
|
|
1212
|
+
const relNest = 'file:local_packages/devkit-nest';
|
|
1213
|
+
if (pkg.dependencies['@devstroupe/devkit-core']) {
|
|
1214
|
+
pkg.dependencies['@devstroupe/devkit-core'] = relCore;
|
|
1215
|
+
}
|
|
1216
|
+
if (pkg.dependencies['@devstroupe/devkit-nest']) {
|
|
1217
|
+
pkg.dependencies['@devstroupe/devkit-nest'] = relNest;
|
|
1218
|
+
}
|
|
1219
|
+
fs.writeJsonSync(backendPkgPath, pkg, { spaces: 2 });
|
|
1220
|
+
console.log('[DevConfig] Dependências locais de backend vinculadas com sucesso.');
|
|
1221
|
+
}
|
|
1222
|
+
catch (err) {
|
|
1223
|
+
console.error('[Erro] Falha ao injetar links locais no backend:', err);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
// Patch Backend Dockerfile para injetar "COPY local_packages/ ./local_packages/" antes do npm install e limpar node_modules local após instalação
|
|
1227
|
+
const backendDockerfile = path.join(backendDest, 'Dockerfile');
|
|
1228
|
+
if (fs.existsSync(backendDockerfile)) {
|
|
1229
|
+
try {
|
|
1230
|
+
let content = fs.readFileSync(backendDockerfile, 'utf-8');
|
|
1231
|
+
const targetStr = 'COPY package*.json ./';
|
|
1232
|
+
const replacementStr = 'COPY package*.json ./\nCOPY local_packages/ ./local_packages/';
|
|
1233
|
+
content = content.split(targetStr).join(replacementStr);
|
|
1234
|
+
// Injeta limpeza de node_modules locais após npm install
|
|
1235
|
+
content = content.replace('RUN npm install --only=production', 'RUN npm install --only=production && rm -rf local_packages/*/node_modules');
|
|
1236
|
+
content = content.replace('RUN npm install', 'RUN npm install && rm -rf local_packages/*/node_modules');
|
|
1237
|
+
fs.writeFileSync(backendDockerfile, content, 'utf-8');
|
|
1238
|
+
console.log('[DevConfig] Dockerfile do backend ajustado para incluir dependências locais.');
|
|
1239
|
+
}
|
|
1240
|
+
catch (err) {
|
|
1241
|
+
console.error('[Erro] Falha ao ajustar Dockerfile do backend:', err);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
// Patch Backend tsconfig.json to prevent duplicate typeorm types in local development
|
|
1245
|
+
const backendTsconfigPath = path.join(backendDest, 'tsconfig.json');
|
|
1246
|
+
if (fs.existsSync(backendTsconfigPath)) {
|
|
1247
|
+
try {
|
|
1248
|
+
const tsconfig = fs.readJsonSync(backendTsconfigPath);
|
|
1249
|
+
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
|
|
1250
|
+
tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
|
|
1251
|
+
// Mapeia pacotes que causam problemas de duplicidade de tipo em links locais
|
|
1252
|
+
tsconfig.compilerOptions.paths['typeorm'] = ['node_modules/typeorm'];
|
|
1253
|
+
tsconfig.compilerOptions.paths['@nestjs/common'] = ['node_modules/@nestjs/common'];
|
|
1254
|
+
tsconfig.compilerOptions.paths['@nestjs/core'] = ['node_modules/@nestjs/core'];
|
|
1255
|
+
tsconfig.compilerOptions.paths['@nestjs/swagger'] = ['node_modules/@nestjs/swagger'];
|
|
1256
|
+
fs.writeJsonSync(backendTsconfigPath, tsconfig, { spaces: 2 });
|
|
1257
|
+
console.log('[DevConfig] Mapeamentos de TSConfig do backend configurados para prevenir duplicidade de pacotes.');
|
|
1258
|
+
}
|
|
1259
|
+
catch (err) {
|
|
1260
|
+
console.error('[Erro] Falha ao atualizar tsconfig.json do backend:', err);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
// 2. Preparar e copiar para o frontend
|
|
1265
|
+
if (frontendDest && fs.existsSync(frontendDest)) {
|
|
1266
|
+
const frontendLocalPackages = path.join(frontendDest, 'local_packages');
|
|
1267
|
+
if (fs.existsSync(coreSrc)) {
|
|
1268
|
+
const coreDest = path.join(frontendLocalPackages, 'devkit-core');
|
|
1269
|
+
fs.ensureDirSync(coreDest);
|
|
1270
|
+
fs.copySync(coreSrc, coreDest, { filter: copyFilter });
|
|
1271
|
+
stripDevelopmentDependencies(coreDest);
|
|
1272
|
+
}
|
|
1273
|
+
if (fs.existsSync(angularSrc)) {
|
|
1274
|
+
const angularDest = path.join(frontendLocalPackages, 'devkit-angular');
|
|
1275
|
+
fs.ensureDirSync(angularDest);
|
|
1276
|
+
fs.copySync(angularSrc, angularDest, { filter: copyFilter });
|
|
1277
|
+
stripDevelopmentDependencies(angularDest);
|
|
1278
|
+
// Correção de workspace:* para file:../devkit-core
|
|
1279
|
+
const angularPkgPath = path.join(angularDest, 'package.json');
|
|
1280
|
+
if (fs.existsSync(angularPkgPath)) {
|
|
1281
|
+
try {
|
|
1282
|
+
const angularPkg = fs.readJsonSync(angularPkgPath);
|
|
1283
|
+
if (angularPkg.dependencies && angularPkg.dependencies['@devstroupe/devkit-core'] === 'workspace:*') {
|
|
1284
|
+
angularPkg.dependencies['@devstroupe/devkit-core'] = 'file:../devkit-core';
|
|
1285
|
+
fs.writeJsonSync(angularPkgPath, angularPkg, { spaces: 2 });
|
|
1286
|
+
console.log('[DevConfig] Ajustado workspace:* para file:../devkit-core no devkit-angular local.');
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
catch (e) {
|
|
1290
|
+
console.error('[Erro] Falha ao ajustar dependências no devkit-angular local:', e);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
// Patch Frontend package.json
|
|
1295
|
+
const frontendPkgPath = path.join(frontendDest, 'package.json');
|
|
1296
|
+
if (fs.existsSync(frontendPkgPath)) {
|
|
1297
|
+
try {
|
|
1298
|
+
const pkg = fs.readJsonSync(frontendPkgPath);
|
|
1299
|
+
const relCore = 'file:local_packages/devkit-core';
|
|
1300
|
+
const relAngular = 'file:local_packages/devkit-angular';
|
|
1301
|
+
if (pkg.dependencies['@devstroupe/devkit-core']) {
|
|
1302
|
+
pkg.dependencies['@devstroupe/devkit-core'] = relCore;
|
|
1303
|
+
}
|
|
1304
|
+
if (pkg.dependencies['@devstroupe/devkit-angular']) {
|
|
1305
|
+
pkg.dependencies['@devstroupe/devkit-angular'] = relAngular;
|
|
1306
|
+
}
|
|
1307
|
+
fs.writeJsonSync(frontendPkgPath, pkg, { spaces: 2 });
|
|
1308
|
+
console.log('[DevConfig] Dependências locais de frontend vinculadas com sucesso.');
|
|
1309
|
+
}
|
|
1310
|
+
catch (err) {
|
|
1311
|
+
console.error('[Erro] Falha ao injetar links locais no frontend:', err);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
// Patch Frontend Dockerfile para injetar "COPY local_packages/ ./local_packages/" antes do npm install e limpar node_modules local após instalação
|
|
1315
|
+
const frontendDockerfile = path.join(frontendDest, 'Dockerfile');
|
|
1316
|
+
if (fs.existsSync(frontendDockerfile)) {
|
|
1317
|
+
try {
|
|
1318
|
+
let content = fs.readFileSync(frontendDockerfile, 'utf-8');
|
|
1319
|
+
const targetStr = 'COPY package*.json ./';
|
|
1320
|
+
const replacementStr = 'COPY package*.json ./\nCOPY local_packages/ ./local_packages/';
|
|
1321
|
+
content = content.split(targetStr).join(replacementStr);
|
|
1322
|
+
// Injeta limpeza de node_modules locais após npm install
|
|
1323
|
+
content = content.replace('RUN npm install --legacy-peer-deps', 'RUN npm install --legacy-peer-deps && rm -rf local_packages/*/node_modules');
|
|
1324
|
+
fs.writeFileSync(frontendDockerfile, content, 'utf-8');
|
|
1325
|
+
console.log('[DevConfig] Dockerfile do frontend ajustado para incluir dependências locais.');
|
|
1326
|
+
}
|
|
1327
|
+
catch (err) {
|
|
1328
|
+
console.error('[Erro] Falha ao ajustar Dockerfile do frontend:', err);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
// Patch Frontend tsconfig.json to prevent duplicate angular types in local development
|
|
1332
|
+
const frontendTsconfigPath = path.join(frontendDest, 'tsconfig.json');
|
|
1333
|
+
if (fs.existsSync(frontendTsconfigPath)) {
|
|
1334
|
+
try {
|
|
1335
|
+
const tsconfig = fs.readJsonSync(frontendTsconfigPath);
|
|
1336
|
+
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
|
|
1337
|
+
tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
|
|
1338
|
+
// Mapeia pacotes que causam problemas de duplicidade de tipo em links locais no frontend
|
|
1339
|
+
tsconfig.compilerOptions.paths['@angular/core'] = ['node_modules/@angular/core'];
|
|
1340
|
+
tsconfig.compilerOptions.paths['@angular/core/*'] = ['node_modules/@angular/core/*'];
|
|
1341
|
+
tsconfig.compilerOptions.paths['@angular/common'] = ['node_modules/@angular/common'];
|
|
1342
|
+
tsconfig.compilerOptions.paths['@angular/common/*'] = ['node_modules/@angular/common/*'];
|
|
1343
|
+
tsconfig.compilerOptions.paths['@angular/common/http'] = ['node_modules/@angular/common/http'];
|
|
1344
|
+
tsconfig.compilerOptions.paths['@angular/common/http/*'] = ['node_modules/@angular/common/http/*'];
|
|
1345
|
+
tsconfig.compilerOptions.paths['@angular/router'] = ['node_modules/@angular/router'];
|
|
1346
|
+
tsconfig.compilerOptions.paths['@angular/router/*'] = ['node_modules/@angular/router/*'];
|
|
1347
|
+
tsconfig.compilerOptions.paths['rxjs'] = ['node_modules/rxjs'];
|
|
1348
|
+
tsconfig.compilerOptions.paths['rxjs/*'] = ['node_modules/rxjs/*'];
|
|
1349
|
+
tsconfig.compilerOptions.paths['@devstroupe/devkit-core'] = ['node_modules/@devstroupe/devkit-core'];
|
|
1350
|
+
tsconfig.compilerOptions.paths['@devstroupe/devkit-core/*'] = ['node_modules/@devstroupe/devkit-core/*'];
|
|
1351
|
+
fs.writeJsonSync(frontendTsconfigPath, tsconfig, { spaces: 2 });
|
|
1352
|
+
console.log('[DevConfig] Mapeamentos de TSConfig do frontend configurados para prevenir duplicidade de pacotes.');
|
|
1353
|
+
}
|
|
1354
|
+
catch (err) {
|
|
1355
|
+
console.error('[Erro] Falha ao atualizar tsconfig.json do frontend:', err);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
// ---------------- Helpers para Microsserviços NestJS ----------------
|
|
1361
|
+
function capitalize(s) {
|
|
1362
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1363
|
+
}
|
|
1364
|
+
function kebabToCamel(s) {
|
|
1365
|
+
return s.replace(/-./g, (x) => x[1].toUpperCase());
|
|
1366
|
+
}
|
|
1367
|
+
function kebabToPascal(s) {
|
|
1368
|
+
return capitalize(kebabToCamel(s));
|
|
1369
|
+
}
|
|
1370
|
+
function generateMsMainTs(port) {
|
|
1371
|
+
return `import * as fs from 'fs';
|
|
1372
|
+
import * as path from 'path';
|
|
1373
|
+
|
|
1374
|
+
// Carrega variáveis do arquivo .env manualmente se ele existir
|
|
1375
|
+
const envPath = path.resolve(process.cwd(), '.env');
|
|
1376
|
+
if (fs.existsSync(envPath)) {
|
|
1377
|
+
const envConfig = fs.readFileSync(envPath, 'utf-8');
|
|
1378
|
+
envConfig.split('\\n').forEach((line) => {
|
|
1379
|
+
const trimmed = line.trim();
|
|
1380
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
1381
|
+
const [key, ...values] = trimmed.split('=');
|
|
1382
|
+
const val = values.join('=').trim();
|
|
1383
|
+
if (key && val) {
|
|
1384
|
+
process.env[key.trim()] = val.replace(/^['"]|['"]$/g, '');
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
import { NestFactory } from '@nestjs/core';
|
|
1391
|
+
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
|
|
1392
|
+
import { AppModule } from './app.module';
|
|
1393
|
+
|
|
1394
|
+
async function bootstrap() {
|
|
1395
|
+
const port = Number(process.env.SERVICE_PORT) || ${port};
|
|
1396
|
+
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
|
|
1397
|
+
transport: Transport.TCP,
|
|
1398
|
+
options: {
|
|
1399
|
+
host: '0.0.0.0',
|
|
1400
|
+
port: port,
|
|
1401
|
+
},
|
|
1402
|
+
});
|
|
1403
|
+
await app.listen();
|
|
1404
|
+
console.log(\`Microsserviço iniciado via TCP na porta \${port}\`);
|
|
1405
|
+
}
|
|
1406
|
+
bootstrap();
|
|
1407
|
+
`;
|
|
1408
|
+
}
|
|
1409
|
+
function generateMsAppModule(entityName) {
|
|
1410
|
+
const pascalName = kebabToPascal(entityName);
|
|
1411
|
+
return `import { Module } from '@nestjs/common';
|
|
1412
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
1413
|
+
import { ${pascalName}Module } from './modules/${entityName}/${entityName}.module';
|
|
1414
|
+
|
|
1415
|
+
@Module({
|
|
1416
|
+
imports: [
|
|
1417
|
+
TypeOrmModule.forRoot({
|
|
1418
|
+
type: 'mysql',
|
|
1419
|
+
host: process.env.DB_HOST || 'localhost',
|
|
1420
|
+
port: Number(process.env.DB_PORT) || 3306,
|
|
1421
|
+
username: process.env.DB_USER || 'root',
|
|
1422
|
+
password: process.env.DB_PASSWORD || 'root',
|
|
1423
|
+
database: process.env.DB_NAME || 'devstroupe_${entityName}',
|
|
1424
|
+
autoLoadEntities: true,
|
|
1425
|
+
synchronize: false,
|
|
1426
|
+
}),
|
|
1427
|
+
${pascalName}Module,
|
|
1428
|
+
],
|
|
1429
|
+
controllers: [],
|
|
1430
|
+
providers: [],
|
|
1431
|
+
})
|
|
1432
|
+
export class AppModule {}
|
|
1433
|
+
`;
|
|
1434
|
+
}
|
|
1435
|
+
function patchGatewayAppModule(appModulePath, entityName) {
|
|
1436
|
+
const pascalName = kebabToPascal(entityName);
|
|
1437
|
+
const moduleName = `${pascalName}GatewayModule`;
|
|
1438
|
+
const importPath = `./modules/${entityName}/${entityName}.module`;
|
|
1439
|
+
if (!fs.existsSync(appModulePath)) {
|
|
1440
|
+
console.warn(`[Aviso] Arquivo ${appModulePath} não encontrado para aplicar patch.`);
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
let content = fs.readFileSync(appModulePath, 'utf-8');
|
|
1444
|
+
// Verifica se o import já existe
|
|
1445
|
+
if (content.includes(moduleName)) {
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
// Adiciona a declaração de import no topo
|
|
1449
|
+
const importStatement = `import { ${moduleName} } from '${importPath}';\n`;
|
|
1450
|
+
content = importStatement + content;
|
|
1451
|
+
// Encontra a lista de imports do decorator @Module
|
|
1452
|
+
// Encontra a lista de imports do decorator @Module
|
|
1453
|
+
const moduleDecoratorRegex = /@Module\s*\(\s*\{\s*imports\s*:\s*\[([\s\S]*?)\]/;
|
|
1454
|
+
const match = content.match(moduleDecoratorRegex);
|
|
1455
|
+
if (match) {
|
|
1456
|
+
const currentImports = match[1];
|
|
1457
|
+
// Adiciona o novo módulo no início dos imports
|
|
1458
|
+
const updatedImports = `\n ${moduleName},${currentImports}`;
|
|
1459
|
+
content = content.replace(currentImports, updatedImports);
|
|
1460
|
+
}
|
|
1461
|
+
fs.writeFileSync(appModulePath, content, 'utf-8');
|
|
1462
|
+
console.log(`[Gateway] app.module.ts patcheado com sucesso para importar ${moduleName}.`);
|
|
1463
|
+
}
|
|
1464
|
+
function patchMonolithAppModule(appModulePath, entityName) {
|
|
1465
|
+
const pascalName = kebabToPascal(entityName);
|
|
1466
|
+
const moduleName = `${pascalName}Module`;
|
|
1467
|
+
const importPath = `./modules/${entityName}/${entityName}.module`;
|
|
1468
|
+
if (!fs.existsSync(appModulePath)) {
|
|
1469
|
+
console.warn(`[Aviso] Arquivo ${appModulePath} não encontrado para aplicar patch.`);
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
let content = fs.readFileSync(appModulePath, 'utf-8');
|
|
1473
|
+
if (content.includes(`import { ${moduleName} }`))
|
|
1474
|
+
return;
|
|
1475
|
+
const lastImportIndex = content.lastIndexOf('import ');
|
|
1476
|
+
const lastImportEnd = content.indexOf('\n', lastImportIndex);
|
|
1477
|
+
const importStatement = `import { ${moduleName} } from '${importPath}';`;
|
|
1478
|
+
content = `${content.slice(0, lastImportEnd + 1)}${importStatement}\n${content.slice(lastImportEnd + 1)}`;
|
|
1479
|
+
const importsArrayStart = content.indexOf('imports: [');
|
|
1480
|
+
if (importsArrayStart === -1) {
|
|
1481
|
+
console.warn(`[Aviso] Lista imports do @Module não encontrada em ${appModulePath}.`);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
const insertionPoint = importsArrayStart + 'imports: ['.length;
|
|
1485
|
+
content = `${content.slice(0, insertionPoint)}\n ${moduleName},${content.slice(insertionPoint)}`;
|
|
1486
|
+
fs.writeFileSync(appModulePath, content, 'utf-8');
|
|
1487
|
+
console.log(`[NestJS] app.module.ts atualizado para importar ${moduleName}.`);
|
|
1488
|
+
}
|
|
1489
|
+
function generateDockerComposeAndDbInit(projectRoot, config) {
|
|
1490
|
+
const isMicroservices = config.backendType === 'microservices';
|
|
1491
|
+
const projectName = config.projectName || 'devstroupe-project';
|
|
1492
|
+
// 1. db-init/init.sql
|
|
1493
|
+
const dbInitDir = path.join(projectRoot, 'db-init');
|
|
1494
|
+
fs.ensureDirSync(dbInitDir);
|
|
1495
|
+
const dbs = ['devstroupe']; // Banco principal (usado pelo gateway ou monolith)
|
|
1496
|
+
if (isMicroservices && config.entities) {
|
|
1497
|
+
config.entities.forEach((entity) => {
|
|
1498
|
+
dbs.push(`devstroupe_${entity.name.toLowerCase()}`);
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
const initSqlContent = dbs.map(db => `CREATE DATABASE IF NOT EXISTS \`${db}\`;`).join('\n') + '\n';
|
|
1502
|
+
fs.writeFileSync(path.join(dbInitDir, 'init.sql'), initSqlContent, 'utf-8');
|
|
1503
|
+
console.log(`[Docker] db-init/init.sql gerado com sucesso.`);
|
|
1504
|
+
// 2. docker-compose.yml
|
|
1505
|
+
let servicesYaml = '';
|
|
1506
|
+
// Database service
|
|
1507
|
+
servicesYaml += ` database:
|
|
1508
|
+
image: mysql:8.0
|
|
1509
|
+
container_name: ${projectName}-database
|
|
1510
|
+
restart: always
|
|
1511
|
+
environment:
|
|
1512
|
+
MYSQL_ROOT_PASSWORD: root
|
|
1513
|
+
ports:
|
|
1514
|
+
- "33060:3306"
|
|
1515
|
+
volumes:
|
|
1516
|
+
- mysql_data:/var/lib/mysql
|
|
1517
|
+
- ./db-init:/docker-entrypoint-initdb.d
|
|
1518
|
+
`;
|
|
1519
|
+
if (isMicroservices) {
|
|
1520
|
+
// Gateway service
|
|
1521
|
+
let gatewayEnv = ` DB_HOST: database
|
|
1522
|
+
DB_PORT: 3306
|
|
1523
|
+
DB_USER: root
|
|
1524
|
+
DB_PASSWORD: root
|
|
1525
|
+
DB_NAME: devstroupe
|
|
1526
|
+
JWT_SECRET: devstroupe-secret-key-12345
|
|
1527
|
+
`;
|
|
1528
|
+
let gatewayDependsOn = ` - database
|
|
1529
|
+
`;
|
|
1530
|
+
// Adiciona variáveis de microsserviços no gateway
|
|
1531
|
+
if (config.entities) {
|
|
1532
|
+
config.entities.forEach((entity, index) => {
|
|
1533
|
+
const port = 3001 + index;
|
|
1534
|
+
const nameUpper = entity.name.toUpperCase();
|
|
1535
|
+
gatewayEnv += ` ${nameUpper}_SERVICE_HOST: ${entity.name}-service
|
|
1536
|
+
${nameUpper}_SERVICE_PORT: ${port}
|
|
1537
|
+
`;
|
|
1538
|
+
gatewayDependsOn += ` - ${entity.name}-service
|
|
1539
|
+
`;
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
servicesYaml += `\n api-gateway:
|
|
1543
|
+
build:
|
|
1544
|
+
context: ./backend/api-gateway
|
|
1545
|
+
dockerfile: Dockerfile
|
|
1546
|
+
container_name: ${projectName}-api-gateway
|
|
1547
|
+
restart: always
|
|
1548
|
+
ports:
|
|
1549
|
+
- "13000:13000"
|
|
1550
|
+
environment:
|
|
1551
|
+
${gatewayEnv} depends_on:
|
|
1552
|
+
${gatewayDependsOn}`;
|
|
1553
|
+
// Containers de microsserviços
|
|
1554
|
+
if (config.entities) {
|
|
1555
|
+
config.entities.forEach((entity, index) => {
|
|
1556
|
+
const port = 3001 + index;
|
|
1557
|
+
servicesYaml += `\n\n ${entity.name}-service:
|
|
1558
|
+
build:
|
|
1559
|
+
context: ./backend/services/${entity.name}
|
|
1560
|
+
dockerfile: Dockerfile
|
|
1561
|
+
container_name: ${projectName}-${entity.name}-service
|
|
1562
|
+
restart: always
|
|
1563
|
+
ports:
|
|
1564
|
+
- "${port}0:${port}"
|
|
1565
|
+
environment:
|
|
1566
|
+
DB_HOST: database
|
|
1567
|
+
DB_PORT: 3306
|
|
1568
|
+
DB_USER: root
|
|
1569
|
+
DB_PASSWORD: root
|
|
1570
|
+
DB_NAME: devstroupe_${entity.name.toLowerCase()}
|
|
1571
|
+
SERVICE_PORT: ${port}
|
|
1572
|
+
depends_on:
|
|
1573
|
+
- database
|
|
1574
|
+
`;
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
// Frontend service (depends on api-gateway)
|
|
1578
|
+
servicesYaml += `\n\n frontend:
|
|
1579
|
+
build:
|
|
1580
|
+
context: ./frontend
|
|
1581
|
+
dockerfile: Dockerfile
|
|
1582
|
+
container_name: ${projectName}-frontend
|
|
1583
|
+
restart: always
|
|
1584
|
+
ports:
|
|
1585
|
+
- "14200:80"
|
|
1586
|
+
depends_on:
|
|
1587
|
+
- api-gateway
|
|
1588
|
+
`;
|
|
1589
|
+
}
|
|
1590
|
+
else {
|
|
1591
|
+
// Monolith services
|
|
1592
|
+
servicesYaml += `\n backend:
|
|
1593
|
+
build:
|
|
1594
|
+
context: ./backend
|
|
1595
|
+
dockerfile: Dockerfile
|
|
1596
|
+
container_name: ${projectName}-backend
|
|
1597
|
+
restart: always
|
|
1598
|
+
ports:
|
|
1599
|
+
- "13000:13000"
|
|
1600
|
+
environment:
|
|
1601
|
+
DB_HOST: database
|
|
1602
|
+
DB_PORT: 3306
|
|
1603
|
+
DB_USER: root
|
|
1604
|
+
DB_PASSWORD: root
|
|
1605
|
+
DB_NAME: devstroupe
|
|
1606
|
+
JWT_SECRET: devstroupe-secret-key-12345
|
|
1607
|
+
depends_on:
|
|
1608
|
+
- database
|
|
1609
|
+
|
|
1610
|
+
frontend:
|
|
1611
|
+
build:
|
|
1612
|
+
context: ./frontend
|
|
1613
|
+
dockerfile: Dockerfile
|
|
1614
|
+
container_name: ${projectName}-frontend
|
|
1615
|
+
restart: always
|
|
1616
|
+
ports:
|
|
1617
|
+
- "14200:80"
|
|
1618
|
+
depends_on:
|
|
1619
|
+
- backend
|
|
1620
|
+
`;
|
|
1621
|
+
}
|
|
1622
|
+
const dockerComposeContent = `version: '3.8'
|
|
1623
|
+
|
|
1624
|
+
services:
|
|
1625
|
+
${servicesYaml}
|
|
1626
|
+
volumes:
|
|
1627
|
+
mysql_data:
|
|
1628
|
+
`;
|
|
1629
|
+
fs.writeFileSync(path.join(projectRoot, 'docker-compose.yml'), dockerComposeContent, 'utf-8');
|
|
1630
|
+
console.log(`[Docker] docker-compose.yml regerado com sucesso.`);
|
|
1631
|
+
}
|
|
1632
|
+
// Comando new-project
|
|
1633
|
+
program
|
|
1634
|
+
.command('new-project <projectName>')
|
|
1635
|
+
.description('Cria um novo projeto com frontend Angular e backend NestJS pré-configurados')
|
|
1636
|
+
.option('--microservices', 'Cria o projeto utilizando arquitetura de microsserviços')
|
|
1637
|
+
.action((projectName, options) => {
|
|
1638
|
+
const projectRoot = path.resolve(process.cwd(), projectName);
|
|
1639
|
+
if (fs.existsSync(projectRoot) && fs.readdirSync(projectRoot).length > 0) {
|
|
1640
|
+
console.error(`[Erro] O diretório "${projectName}" já existe e não está vazio.`);
|
|
1641
|
+
process.exit(1);
|
|
1642
|
+
}
|
|
1643
|
+
const isMicroservices = !!options.microservices;
|
|
1644
|
+
console.log(`Criando novo projeto "${projectName}" (Modo: ${isMicroservices ? 'Microsserviços' : 'Monólito'}) em: ${projectRoot}...`);
|
|
1645
|
+
// Resolver caminhos de boilerplates
|
|
1646
|
+
let boilerplatesDir = path.resolve(__dirname, 'boilerplates');
|
|
1647
|
+
if (!fs.existsSync(boilerplatesDir)) {
|
|
1648
|
+
boilerplatesDir = path.resolve(__dirname, '../boilerplates');
|
|
1649
|
+
}
|
|
1650
|
+
if (!fs.existsSync(boilerplatesDir)) {
|
|
1651
|
+
boilerplatesDir = path.resolve(__dirname, '../../boilerplates');
|
|
1652
|
+
}
|
|
1653
|
+
const nestTemplateDir = path.join(boilerplatesDir, 'nest-template');
|
|
1654
|
+
const angularTemplateDir = path.join(boilerplatesDir, 'angular-template');
|
|
1655
|
+
if (!fs.existsSync(nestTemplateDir) || !fs.existsSync(angularTemplateDir)) {
|
|
1656
|
+
console.error(`[Erro] Não foi possível localizar os templates de boilerplate em: ${boilerplatesDir}`);
|
|
1657
|
+
process.exit(1);
|
|
1658
|
+
}
|
|
1659
|
+
try {
|
|
1660
|
+
// 1. Criar diretórios de destino
|
|
1661
|
+
fs.ensureDirSync(projectRoot);
|
|
1662
|
+
const frontendDest = path.join(projectRoot, 'frontend');
|
|
1663
|
+
let backendDest;
|
|
1664
|
+
if (isMicroservices) {
|
|
1665
|
+
backendDest = path.join(projectRoot, 'backend', 'api-gateway');
|
|
1666
|
+
fs.ensureDirSync(path.join(projectRoot, 'backend', 'services'));
|
|
1667
|
+
}
|
|
1668
|
+
else {
|
|
1669
|
+
backendDest = path.join(projectRoot, 'backend');
|
|
1670
|
+
}
|
|
1671
|
+
// 2. Copiar boilerplates
|
|
1672
|
+
console.log(`Copiando boilerplate do backend (${isMicroservices ? 'API Gateway' : 'NestJS Monólito'})...`);
|
|
1673
|
+
fs.copySync(nestTemplateDir, backendDest, { filter: createBoilerplateCopyFilter(nestTemplateDir) });
|
|
1674
|
+
console.log('Copiando boilerplate do frontend (Angular)...');
|
|
1675
|
+
fs.copySync(angularTemplateDir, frontendDest, { filter: createBoilerplateCopyFilter(angularTemplateDir) });
|
|
1676
|
+
// 2.1 Criar arquivos .env e .env.example no backend
|
|
1677
|
+
console.log('Gerando arquivos .env e .env.example no backend...');
|
|
1678
|
+
const envContent = `PORT=13000
|
|
1679
|
+
DB_HOST=localhost
|
|
1680
|
+
DB_PORT=3306
|
|
1681
|
+
DB_USER=root
|
|
1682
|
+
DB_PASSWORD=root
|
|
1683
|
+
DB_NAME=devstroupe
|
|
1684
|
+
`;
|
|
1685
|
+
fs.writeFileSync(path.join(backendDest, '.env'), envContent, 'utf-8');
|
|
1686
|
+
fs.writeFileSync(path.join(backendDest, '.env.example'), envContent, 'utf-8');
|
|
1687
|
+
// Patch local dependencies if in monorepo development
|
|
1688
|
+
patchLocalDependencies(backendDest, frontendDest, boilerplatesDir);
|
|
1689
|
+
// 3. Criar devstroupe.config.ts
|
|
1690
|
+
console.log('Gerando arquivo devstroupe.config.ts...');
|
|
1691
|
+
const configPath = path.join(projectRoot, 'devstroupe.config.ts');
|
|
1692
|
+
const designSystemConfig = {
|
|
1693
|
+
provider: 'spartan',
|
|
1694
|
+
spartanStyle: 'vega',
|
|
1695
|
+
spartanTheme: 'neutral',
|
|
1696
|
+
installAllPrimitives: true,
|
|
1697
|
+
includePlayground: true,
|
|
1698
|
+
};
|
|
1699
|
+
const configContent = `export default {
|
|
1700
|
+
projectName: '${projectName}',
|
|
1701
|
+
backendType: '${isMicroservices ? 'microservices' : 'monolith'}',
|
|
1702
|
+
backendPath: './backend',
|
|
1703
|
+
frontendPath: './frontend',
|
|
1704
|
+
designSystem: {
|
|
1705
|
+
provider: '${designSystemConfig.provider}',
|
|
1706
|
+
spartanStyle: '${designSystemConfig.spartanStyle}',
|
|
1707
|
+
spartanTheme: '${designSystemConfig.spartanTheme}',
|
|
1708
|
+
installAllPrimitives: ${designSystemConfig.installAllPrimitives},
|
|
1709
|
+
includePlayground: ${designSystemConfig.includePlayground}
|
|
1710
|
+
},
|
|
1711
|
+
entities: [
|
|
1712
|
+
{
|
|
1713
|
+
name: 'company',
|
|
1714
|
+
label: 'Empresa',
|
|
1715
|
+
tableName: 'companies',
|
|
1716
|
+
properties: [
|
|
1717
|
+
{ name: 'name', type: 'string', required: true, label: 'Nome' },
|
|
1718
|
+
{
|
|
1719
|
+
name: 'cabins', type: 'relationship', required: false, label: 'Cabines',
|
|
1720
|
+
relation: {
|
|
1721
|
+
target: 'cabin', kind: 'one-to-many', foreignKey: 'company_id',
|
|
1722
|
+
inverseSide: 'company', control: 'table', embedded: true,
|
|
1723
|
+
columns: ['name', 'code', 'is_active'], lookup: { pageSize: 10 }
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
],
|
|
1727
|
+
crud: { generateFrontend: true, generateBackend: true }
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
name: 'cabin',
|
|
1731
|
+
label: 'Cabine',
|
|
1732
|
+
tableName: 'cabins',
|
|
1733
|
+
properties: [
|
|
1734
|
+
{ name: 'name', type: 'string', required: true, label: 'Nome' },
|
|
1735
|
+
{ name: 'code', type: 'string', required: false, label: 'Código' },
|
|
1736
|
+
{
|
|
1737
|
+
name: 'company_id', type: 'relationship', required: true, label: 'Empresa',
|
|
1738
|
+
relation: {
|
|
1739
|
+
target: 'company', kind: 'many-to-one', relationName: 'company',
|
|
1740
|
+
inverseSide: 'cabins', control: 'select', displayField: 'name', onDelete: 'CASCADE',
|
|
1741
|
+
lookup: { mode: 'remote', pageSize: 20, minSearchLength: 0 }
|
|
1742
|
+
}
|
|
1743
|
+
},
|
|
1744
|
+
{ name: 'is_active', type: 'boolean', required: true, label: 'Ativo', default: true }
|
|
1745
|
+
],
|
|
1746
|
+
crud: {
|
|
1747
|
+
generateFrontend: true,
|
|
1748
|
+
generateBackend: true
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
]
|
|
1752
|
+
};
|
|
1753
|
+
`;
|
|
1754
|
+
fs.writeFileSync(configPath, configContent, 'utf-8');
|
|
1755
|
+
// 3.1 Criar docker-compose.yml e init.sql
|
|
1756
|
+
console.log('Gerando docker-compose.yml e base de dados...');
|
|
1757
|
+
const tempConfig = {
|
|
1758
|
+
projectName,
|
|
1759
|
+
backendType: isMicroservices ? 'microservices' : 'monolith',
|
|
1760
|
+
entities: [
|
|
1761
|
+
{
|
|
1762
|
+
name: 'cabin',
|
|
1763
|
+
tableName: 'cabins',
|
|
1764
|
+
}
|
|
1765
|
+
]
|
|
1766
|
+
};
|
|
1767
|
+
generateDockerComposeAndDbInit(projectRoot, tempConfig);
|
|
1768
|
+
// 3.2 Criar Makefile na raiz
|
|
1769
|
+
console.log('Gerando arquivo Makefile na raiz...');
|
|
1770
|
+
const backendServiceName = isMicroservices ? 'api-gateway' : 'backend';
|
|
1771
|
+
const makefileContent = `.PHONY: up down restart build logs ps clean backend-shell frontend-shell db-shell help
|
|
1772
|
+
|
|
1773
|
+
DC = docker compose
|
|
1774
|
+
|
|
1775
|
+
help:
|
|
1776
|
+
@echo "Comandos disponíveis:"
|
|
1777
|
+
@echo " make up Inicia os containers em background"
|
|
1778
|
+
@echo " make down Para os containers e mantém os dados"
|
|
1779
|
+
@echo " make restart Reinicia os containers"
|
|
1780
|
+
@echo " make build Reconstrói as imagens Docker"
|
|
1781
|
+
@echo " make logs Exibe os logs em tempo real"
|
|
1782
|
+
@echo " make ps Exibe o status dos containers"
|
|
1783
|
+
@echo " make clean Para os containers e apaga todos os volumes (reseta o banco)"
|
|
1784
|
+
@echo " make backend-shell Abre o terminal sh do container do backend"
|
|
1785
|
+
@echo " make frontend-shell Abre o terminal sh do container do frontend"
|
|
1786
|
+
@echo " make db-shell Abre o cliente mysql no container do banco"
|
|
1787
|
+
|
|
1788
|
+
up:
|
|
1789
|
+
\$(DC) up -d
|
|
1790
|
+
|
|
1791
|
+
down:
|
|
1792
|
+
\$(DC) down
|
|
1793
|
+
|
|
1794
|
+
restart:
|
|
1795
|
+
\$(DC) restart
|
|
1796
|
+
|
|
1797
|
+
build:
|
|
1798
|
+
\$(DC) build
|
|
1799
|
+
|
|
1800
|
+
logs:
|
|
1801
|
+
\$(DC) logs -f
|
|
1802
|
+
|
|
1803
|
+
ps:
|
|
1804
|
+
\$(DC) ps
|
|
1805
|
+
|
|
1806
|
+
clean:
|
|
1807
|
+
\$(DC) down -v
|
|
1808
|
+
|
|
1809
|
+
backend-shell:
|
|
1810
|
+
\$(DC) exec ${backendServiceName} sh
|
|
1811
|
+
|
|
1812
|
+
frontend-shell:
|
|
1813
|
+
\$(DC) exec frontend sh
|
|
1814
|
+
|
|
1815
|
+
db-shell:
|
|
1816
|
+
\$(DC) exec database mysql -u root -p
|
|
1817
|
+
|
|
1818
|
+
# Evita que argumentos (ex: main, staging) sejam interpretados como target
|
|
1819
|
+
%:
|
|
1820
|
+
@:
|
|
1821
|
+
`;
|
|
1822
|
+
fs.writeFileSync(path.join(projectRoot, 'Makefile'), makefileContent, 'utf-8');
|
|
1823
|
+
// 4. Instalar dependências automaticamente
|
|
1824
|
+
try {
|
|
1825
|
+
console.log(`Instalando dependências no backend (${isMicroservices ? 'API Gateway' : 'Monólito'})...`);
|
|
1826
|
+
(0, child_process_1.execSync)('npm install', { cwd: backendDest, stdio: 'inherit' });
|
|
1827
|
+
}
|
|
1828
|
+
catch (err) {
|
|
1829
|
+
console.warn('[Aviso] Falha ao rodar "npm install" no backend. Instale as dependências manualmente.');
|
|
1830
|
+
}
|
|
1831
|
+
try {
|
|
1832
|
+
console.log('Instalando dependências no frontend...');
|
|
1833
|
+
(0, child_process_1.execSync)('npm install --legacy-peer-deps', { cwd: frontendDest, stdio: 'inherit' });
|
|
1834
|
+
}
|
|
1835
|
+
catch (err) {
|
|
1836
|
+
console.warn('[Aviso] Falha ao rodar "npm install" no frontend. Instale as dependências manualmente.');
|
|
1837
|
+
}
|
|
1838
|
+
// 5. Inicializar UI no frontend depois do install para disponibilizar @spartan-ng/cli
|
|
1839
|
+
console.log('Inicializando componentes de UI locais...');
|
|
1840
|
+
const generatedConfig = require(configPath).default ?? require(configPath);
|
|
1841
|
+
initUi(frontendDest, generatedConfig);
|
|
1842
|
+
console.log(`\n\x1b[32m[SUCESSO] Projeto "${projectName}" criado com sucesso!\x1b[0m`);
|
|
1843
|
+
console.log(`Para começar:`);
|
|
1844
|
+
console.log(` 1. Entre na pasta do projeto: cd ${projectName}`);
|
|
1845
|
+
console.log(` 2. Desenvolva sua aplicação com o DT-DevKit!`);
|
|
1846
|
+
}
|
|
1847
|
+
catch (err) {
|
|
1848
|
+
console.error('[Erro] Falha ao criar o novo projeto:', err);
|
|
1849
|
+
process.exit(1);
|
|
1850
|
+
}
|
|
1851
|
+
});
|
|
1852
|
+
// Comando add-ui
|
|
1853
|
+
program
|
|
1854
|
+
.command('add-ui <componentName>')
|
|
1855
|
+
.description('Gera um componente ou diretiva de UI local (Spartan ou Customizado DevKit) no frontend')
|
|
1856
|
+
.action((componentName) => {
|
|
1857
|
+
const configPath = path.join(process.cwd(), 'devstroupe.config.ts');
|
|
1858
|
+
if (!fs.existsSync(configPath)) {
|
|
1859
|
+
console.error('Arquivo devstroupe.config.ts não encontrado na raiz. Rode "devstroupe init" primeiro.');
|
|
1860
|
+
process.exit(1);
|
|
1861
|
+
}
|
|
1862
|
+
let config;
|
|
1863
|
+
try {
|
|
1864
|
+
const importedConfig = require(configPath);
|
|
1865
|
+
config = importedConfig.default || importedConfig;
|
|
1866
|
+
}
|
|
1867
|
+
catch (error) {
|
|
1868
|
+
console.error('Erro ao carregar devstroupe.config.ts:', error);
|
|
1869
|
+
process.exit(1);
|
|
1870
|
+
}
|
|
1871
|
+
const frontendRoot = path.resolve(process.cwd(), config.frontendPath || './frontend');
|
|
1872
|
+
if (!fs.existsSync(frontendRoot)) {
|
|
1873
|
+
console.error(`Diretório frontend não encontrado em: ${frontendRoot}.`);
|
|
1874
|
+
process.exit(1);
|
|
1875
|
+
}
|
|
1876
|
+
const targetDir = path.join(frontendRoot, 'src', 'app', 'shared', 'components', 'devkit');
|
|
1877
|
+
fs.ensureDirSync(targetDir);
|
|
1878
|
+
const compNameLower = componentName.toLowerCase();
|
|
1879
|
+
const pascalName = kebabToPascal(componentName);
|
|
1880
|
+
// Mapear e autoinstalar dependências do Spartan Brain correspondentes
|
|
1881
|
+
const spartanDeps = {
|
|
1882
|
+
dialog: '@spartan-ng/brain',
|
|
1883
|
+
sheet: '@spartan-ng/brain',
|
|
1884
|
+
tabs: '@spartan-ng/brain',
|
|
1885
|
+
accordion: '@spartan-ng/brain',
|
|
1886
|
+
'alert-dialog': '@spartan-ng/brain',
|
|
1887
|
+
menu: '@spartan-ng/brain',
|
|
1888
|
+
'dropdown-menu': '@spartan-ng/brain'
|
|
1889
|
+
};
|
|
1890
|
+
const depToInstall = spartanDeps[compNameLower];
|
|
1891
|
+
if (depToInstall) {
|
|
1892
|
+
const pkgJsonPath = path.join(frontendRoot, 'package.json');
|
|
1893
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
1894
|
+
const pkgJson = fs.readJsonSync(pkgJsonPath);
|
|
1895
|
+
const hasDep = (pkgJson.dependencies && pkgJson.dependencies[depToInstall]) ||
|
|
1896
|
+
(pkgJson.devDependencies && pkgJson.devDependencies[depToInstall]);
|
|
1897
|
+
if (!hasDep) {
|
|
1898
|
+
console.log(`[Dep] Instalando dependência necessária do Spartan: ${depToInstall}...`);
|
|
1899
|
+
const workspaceRoot = process.cwd();
|
|
1900
|
+
const isPnpm = fs.existsSync(path.join(workspaceRoot, 'pnpm-lock.yaml'));
|
|
1901
|
+
try {
|
|
1902
|
+
const { execSync } = require('child_process');
|
|
1903
|
+
if (isPnpm) {
|
|
1904
|
+
execSync(`pnpm --dir "${frontendRoot}" add ${depToInstall}`, { stdio: 'inherit' });
|
|
1905
|
+
}
|
|
1906
|
+
else {
|
|
1907
|
+
execSync(`npm --prefix "${frontendRoot}" install ${depToInstall} --legacy-peer-deps`, { stdio: 'inherit' });
|
|
1908
|
+
}
|
|
1909
|
+
console.log(`[Dep] ${depToInstall} instalado com sucesso!`);
|
|
1910
|
+
}
|
|
1911
|
+
catch (err) {
|
|
1912
|
+
console.warn(`[Aviso] Falha ao instalar automaticamente ${depToInstall}. Por favor, instale manualmente.`);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
if (compNameLower === 'badge') {
|
|
1918
|
+
const badgeDir = path.join(targetDir, 'ui-badge-helm');
|
|
1919
|
+
fs.ensureDirSync(badgeDir);
|
|
1920
|
+
const bdg = (0, ui_templates_1.devkitBadgeTemplate)();
|
|
1921
|
+
fs.writeFileSync(path.join(badgeDir, 'badge.component.ts'), bdg.ts, 'utf-8');
|
|
1922
|
+
console.log(`[UI] Componente devkit-badge injetado em: ${badgeDir}. O hlmBadge real vem de @spartan-ng/helm/badge.`);
|
|
1923
|
+
}
|
|
1924
|
+
else if (compNameLower === 'avatar') {
|
|
1925
|
+
const avatarDir = path.join(targetDir, 'ui-avatar-helm');
|
|
1926
|
+
fs.ensureDirSync(avatarDir);
|
|
1927
|
+
const avt = (0, ui_templates_1.devkitAvatarTemplate)();
|
|
1928
|
+
fs.writeFileSync(path.join(avatarDir, 'avatar.component.ts'), avt.ts, 'utf-8');
|
|
1929
|
+
console.log(`[UI] Componente devkit-avatar injetado em: ${avatarDir}`);
|
|
1930
|
+
}
|
|
1931
|
+
else if (['dialog', 'sheet', 'tabs', 'accordion', 'alert-dialog', 'menu', 'dropdown-menu'].includes(compNameLower)) {
|
|
1932
|
+
const primitiveName = compNameLower === 'menu' ? 'dropdown-menu' : compNameLower;
|
|
1933
|
+
const style = getCurrentSpartanStyle(frontendRoot, config);
|
|
1934
|
+
runSpartanPrimitiveGenerator(frontendRoot, style, primitiveName);
|
|
1935
|
+
console.log(`[UI] Primitive Spartan real disponível via import "@spartan-ng/helm/${primitiveName}". Nenhum wrapper fake foi gerado.`);
|
|
1936
|
+
}
|
|
1937
|
+
else {
|
|
1938
|
+
// Componente customizado livre!
|
|
1939
|
+
const customDir = path.join(targetDir, componentName);
|
|
1940
|
+
fs.ensureDirSync(customDir);
|
|
1941
|
+
const tsContent = `import { Component } from '@angular/core';
|
|
1942
|
+
import { CommonModule } from '@angular/common';
|
|
1943
|
+
|
|
1944
|
+
@Component({
|
|
1945
|
+
selector: 'devkit-${componentName}',
|
|
1946
|
+
standalone: true,
|
|
1947
|
+
imports: [CommonModule],
|
|
1948
|
+
templateUrl: './${componentName}.component.html',
|
|
1949
|
+
styleUrls: ['./${componentName}.component.css']
|
|
1950
|
+
})
|
|
1951
|
+
export class Devkit${pascalName}Component {}
|
|
1952
|
+
`;
|
|
1953
|
+
const htmlContent = `<div class="p-4 border border-dashed border-border rounded-lg bg-card text-card-foreground">
|
|
1954
|
+
<p class="text-sm font-medium">Componente Customizado: devkit-${componentName}</p>
|
|
1955
|
+
</div>
|
|
1956
|
+
`;
|
|
1957
|
+
const cssContent = `:host {
|
|
1958
|
+
display: block;
|
|
1959
|
+
}
|
|
1960
|
+
`;
|
|
1961
|
+
fs.writeFileSync(path.join(customDir, `${componentName}.component.ts`), tsContent, 'utf-8');
|
|
1962
|
+
fs.writeFileSync(path.join(customDir, `${componentName}.component.html`), htmlContent, 'utf-8');
|
|
1963
|
+
fs.writeFileSync(path.join(customDir, `${componentName}.component.css`), cssContent, 'utf-8');
|
|
1964
|
+
console.log(`[UI] Componente customizado do DevKit "devkit-${componentName}" criado com sucesso em: ${customDir}`);
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
program.parse(process.argv);
|