@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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sortEntitiesForMigrations = sortEntitiesForMigrations;
|
|
4
|
+
const relationships_1 = require("../templates/shared/relationships");
|
|
5
|
+
function migrationDependencies(entity) {
|
|
6
|
+
return (0, relationships_1.getRelationships)(entity.properties ?? [])
|
|
7
|
+
.filter((relationship) => relationship.kind !== 'one-to-many')
|
|
8
|
+
.map((relationship) => relationship.target);
|
|
9
|
+
}
|
|
10
|
+
function sortEntitiesForMigrations(entities) {
|
|
11
|
+
const byName = new Map(entities.map((entity) => [entity.name, entity]));
|
|
12
|
+
const visiting = new Set();
|
|
13
|
+
const visited = new Set();
|
|
14
|
+
const ordered = [];
|
|
15
|
+
const visit = (entity) => {
|
|
16
|
+
if (visited.has(entity.name) || visiting.has(entity.name))
|
|
17
|
+
return;
|
|
18
|
+
visiting.add(entity.name);
|
|
19
|
+
migrationDependencies(entity).forEach((dependency) => {
|
|
20
|
+
const target = byName.get(dependency);
|
|
21
|
+
if (target)
|
|
22
|
+
visit(target);
|
|
23
|
+
});
|
|
24
|
+
visiting.delete(entity.name);
|
|
25
|
+
visited.add(entity.name);
|
|
26
|
+
ordered.push(entity);
|
|
27
|
+
};
|
|
28
|
+
entities.forEach(visit);
|
|
29
|
+
return ordered;
|
|
30
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface LintViolation {
|
|
2
|
+
file: string;
|
|
3
|
+
line: number;
|
|
4
|
+
rule: string;
|
|
5
|
+
severity: 'error' | 'warn';
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export interface LintResult {
|
|
9
|
+
violations: LintViolation[];
|
|
10
|
+
filesScanned: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function runLinter(projectRoot: string, config: any): LintResult;
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runLinter = runLinter;
|
|
37
|
+
const fs = __importStar(require("fs-extra"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
// Helper para ler arquivos recursivamente com filtros de extensão
|
|
40
|
+
function getFilesRecursively(dir, extensions) {
|
|
41
|
+
let results = [];
|
|
42
|
+
if (!fs.existsSync(dir))
|
|
43
|
+
return results;
|
|
44
|
+
const list = fs.readdirSync(dir);
|
|
45
|
+
for (const file of list) {
|
|
46
|
+
const filePath = path.join(dir, file);
|
|
47
|
+
const stat = fs.statSync(filePath);
|
|
48
|
+
if (stat && stat.isDirectory()) {
|
|
49
|
+
const baseName = path.basename(filePath);
|
|
50
|
+
// Ignorar diretórios comuns de build/dependência
|
|
51
|
+
if (baseName !== 'node_modules' &&
|
|
52
|
+
baseName !== 'dist' &&
|
|
53
|
+
baseName !== '.git' &&
|
|
54
|
+
baseName !== '.angular' &&
|
|
55
|
+
baseName !== 'coverage') {
|
|
56
|
+
results = results.concat(getFilesRecursively(filePath, extensions));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const ext = path.extname(file);
|
|
61
|
+
if (extensions.includes(ext)) {
|
|
62
|
+
results.push(filePath);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
function runLinter(projectRoot, config) {
|
|
69
|
+
const violations = [];
|
|
70
|
+
let filesScanned = 0;
|
|
71
|
+
// Carrega configurações de regras (padrão: error para quase tudo, warn para imports de arquitetura)
|
|
72
|
+
const rulesConfig = config.rules || {};
|
|
73
|
+
const getRuleSeverity = (ruleName, defaultSeverity) => {
|
|
74
|
+
return rulesConfig[ruleName] !== undefined ? rulesConfig[ruleName] : defaultSeverity;
|
|
75
|
+
};
|
|
76
|
+
const severityNoPolling = getRuleSeverity('no-polling-in-frontend', 'error');
|
|
77
|
+
const severityForcePagination = getRuleSeverity('force-pagination-in-backend', 'error');
|
|
78
|
+
const severityStandardQueries = getRuleSeverity('use-standard-devkit-queries', 'error');
|
|
79
|
+
const severityCleanArch = getRuleSeverity('clean-architecture-imports', 'warn');
|
|
80
|
+
const severitySynchronize = getRuleSeverity('disable-typeorm-synchronize', 'error');
|
|
81
|
+
const severitySpartanRadius = getRuleSeverity('no-custom-spartan-radius', 'error');
|
|
82
|
+
const backendRoot = path.resolve(projectRoot, config.backendPath || './backend');
|
|
83
|
+
const frontendRoot = path.resolve(projectRoot, config.frontendPath || './frontend');
|
|
84
|
+
// --- 1. Auditando o Backend (NestJS) ---
|
|
85
|
+
if (fs.existsSync(backendRoot)) {
|
|
86
|
+
const backendFiles = getFilesRecursively(backendRoot, ['.ts']);
|
|
87
|
+
filesScanned += backendFiles.length;
|
|
88
|
+
for (const file of backendFiles) {
|
|
89
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
90
|
+
const lines = content.split('\n');
|
|
91
|
+
const relativePath = path.relative(projectRoot, file);
|
|
92
|
+
if (severitySynchronize !== 'off') {
|
|
93
|
+
lines.forEach((line, index) => {
|
|
94
|
+
if (/\bsynchronize\s*:\s*true\b/.test(line)) {
|
|
95
|
+
violations.push({
|
|
96
|
+
file: relativePath,
|
|
97
|
+
line: index + 1,
|
|
98
|
+
rule: 'disable-typeorm-synchronize',
|
|
99
|
+
severity: severitySynchronize,
|
|
100
|
+
message: 'Use migrations TypeORM e mantenha "synchronize: false" para evitar alterações automáticas no schema.'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// Regra: clean-architecture-imports
|
|
106
|
+
// Impede que arquivos sob subdiretórios de domínio importem infra/controllers
|
|
107
|
+
if (severityCleanArch !== 'off' && (file.includes('/domain/') || file.includes('/entities/'))) {
|
|
108
|
+
lines.forEach((line, index) => {
|
|
109
|
+
const trimmed = line.trim();
|
|
110
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*'))
|
|
111
|
+
return;
|
|
112
|
+
// Import contendo infra, controller, module ou similar
|
|
113
|
+
if (/import\s+.*\s+from\s+['"].*(?:\/infra\/|\/controllers\/|\/modules\/|\.controller|\.module).*['"]/i.test(line)) {
|
|
114
|
+
violations.push({
|
|
115
|
+
file: relativePath,
|
|
116
|
+
line: index + 1,
|
|
117
|
+
rule: 'clean-architecture-imports',
|
|
118
|
+
severity: severityCleanArch,
|
|
119
|
+
message: 'Camada de Domínio/Entidade não deve importar classes da camada de Infraestrutura, Controladores ou Módulos.'
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Regra: force-pagination-in-backend
|
|
125
|
+
// Garante que repositórios/controllers evitem .find() brutas ou .getMany() brutas
|
|
126
|
+
if (severityForcePagination !== 'off') {
|
|
127
|
+
const hasRepositoryOrController = file.endsWith('.repository.ts') || file.endsWith('.controller.ts') || file.endsWith('.service.ts');
|
|
128
|
+
if (hasRepositoryOrController) {
|
|
129
|
+
lines.forEach((line, index) => {
|
|
130
|
+
const trimmed = line.trim();
|
|
131
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*'))
|
|
132
|
+
return;
|
|
133
|
+
// .find() sem argumentos ou sem filtros limitados, ou getMany() direto no queryBuilder sem skip/take/limit/take
|
|
134
|
+
if (/\.find\(\s*(?:\{\s*\})?\s*\)/.test(line)) {
|
|
135
|
+
violations.push({
|
|
136
|
+
file: relativePath,
|
|
137
|
+
line: index + 1,
|
|
138
|
+
rule: 'force-pagination-in-backend',
|
|
139
|
+
severity: severityForcePagination,
|
|
140
|
+
message: 'Evite usar consultas brutas de tabela cheia ".find()". Prefira a paginação padrão do repositório ("findMany").'
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (/\.getMany\(\)/.test(line) && !content.includes('.take(') && !content.includes('.limit(')) {
|
|
144
|
+
violations.push({
|
|
145
|
+
file: relativePath,
|
|
146
|
+
line: index + 1,
|
|
147
|
+
rule: 'force-pagination-in-backend',
|
|
148
|
+
severity: severityForcePagination,
|
|
149
|
+
message: 'Evite chamar ".getMany()" no QueryBuilder sem especificar limites de paginação (".take" ou ".limit").'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Regra: use-standard-devkit-queries (Backend)
|
|
156
|
+
if (severityStandardQueries !== 'off') {
|
|
157
|
+
// Controllers devem herdar de BaseCrudController
|
|
158
|
+
if (file.endsWith('.controller.ts') && !file.endsWith('auth.controller.ts') && content.includes('@Controller') && !content.includes('extends BaseCrudController')) {
|
|
159
|
+
violations.push({
|
|
160
|
+
file: relativePath,
|
|
161
|
+
line: 1,
|
|
162
|
+
rule: 'use-standard-devkit-queries',
|
|
163
|
+
severity: severityStandardQueries,
|
|
164
|
+
message: 'Controllers de CRUD devem herdar de "BaseCrudController" para garantir consistência e herança de comportamento.'
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// Repositories devem herdar de BaseRepository
|
|
168
|
+
if (file.endsWith('.repository.ts') && content.includes('@Injectable') && !content.includes('extends BaseRepository')) {
|
|
169
|
+
violations.push({
|
|
170
|
+
file: relativePath,
|
|
171
|
+
line: 1,
|
|
172
|
+
rule: 'use-standard-devkit-queries',
|
|
173
|
+
severity: severityStandardQueries,
|
|
174
|
+
message: 'Repositories devem herdar de "BaseRepository" para garantir paginação e parsing de filtros consistentes.'
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// --- 2. Auditando o Frontend (Angular) ---
|
|
181
|
+
if (fs.existsSync(frontendRoot)) {
|
|
182
|
+
const frontendFiles = getFilesRecursively(frontendRoot, ['.ts', '.html', '.css', '.scss']);
|
|
183
|
+
filesScanned += frontendFiles.length;
|
|
184
|
+
for (const file of frontendFiles) {
|
|
185
|
+
const relativePath = path.relative(projectRoot, file);
|
|
186
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
187
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
188
|
+
const lines = content.split('\n');
|
|
189
|
+
if (severitySpartanRadius !== 'off' && normalizedPath.includes('shared/components/devkit/')) {
|
|
190
|
+
lines.forEach((line, index) => {
|
|
191
|
+
const definesRadiusToken = /--radius(?:-[\w-]+)?\s*:/.test(line);
|
|
192
|
+
const radiusDeclaration = line.match(/border-radius\s*:\s*([^;]+)/);
|
|
193
|
+
const radiusValue = radiusDeclaration?.[1]?.trim();
|
|
194
|
+
const usesOfficialTokenOrSemanticCircle = !radiusValue ||
|
|
195
|
+
/^var\(--radius(?:-[\w-]+)?\)$/.test(radiusValue) ||
|
|
196
|
+
radiusValue === '9999px' ||
|
|
197
|
+
radiusValue === '50%';
|
|
198
|
+
if (definesRadiusToken || !usesOfficialTokenOrSemanticCircle) {
|
|
199
|
+
violations.push({
|
|
200
|
+
file: relativePath,
|
|
201
|
+
line: index + 1,
|
|
202
|
+
rule: 'no-custom-spartan-radius',
|
|
203
|
+
severity: severitySpartanRadius,
|
|
204
|
+
message: 'Wrappers DevKit não devem definir radius próprio. Use o primitive ou token oficial gerado pelo Spartan.'
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// Ignorar wrappers locais nas demais regras e primitives Helm gerados pelo Spartan.
|
|
210
|
+
if (normalizedPath.includes('shared/components/devkit/') ||
|
|
211
|
+
normalizedPath.includes('frontend/libs/ui/')) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// Auditoria de arquivos TypeScript do Frontend
|
|
215
|
+
if (file.endsWith('.ts')) {
|
|
216
|
+
const importsRxjs = content.includes('rxjs');
|
|
217
|
+
// Regra: no-polling-in-frontend
|
|
218
|
+
if (severityNoPolling !== 'off') {
|
|
219
|
+
lines.forEach((line, index) => {
|
|
220
|
+
const trimmed = line.trim();
|
|
221
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*'))
|
|
222
|
+
return;
|
|
223
|
+
// Busca setInterval, interval( ou timer( com período)
|
|
224
|
+
if (/\bsetInterval\s*\(/.test(line)) {
|
|
225
|
+
violations.push({
|
|
226
|
+
file: relativePath,
|
|
227
|
+
line: index + 1,
|
|
228
|
+
rule: 'no-polling-in-frontend',
|
|
229
|
+
severity: severityNoPolling,
|
|
230
|
+
message: 'Não faça polling contínuo no frontend usando "setInterval". Utilize WebSockets ou disparos reativos.'
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (/\binterval\s*\(/.test(line) && (importsRxjs || line.includes('rxjs'))) {
|
|
234
|
+
violations.push({
|
|
235
|
+
file: relativePath,
|
|
236
|
+
line: index + 1,
|
|
237
|
+
rule: 'no-polling-in-frontend',
|
|
238
|
+
severity: severityNoPolling,
|
|
239
|
+
message: 'Não utilize o operador "interval" do RxJS para criar loops infinitos de polling.'
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (/\btimer\s*\(\s*\d+\s*,\s*\d+\s*\)/.test(line)) {
|
|
243
|
+
violations.push({
|
|
244
|
+
file: relativePath,
|
|
245
|
+
line: index + 1,
|
|
246
|
+
rule: 'no-polling-in-frontend',
|
|
247
|
+
severity: severityNoPolling,
|
|
248
|
+
message: 'Não utilize o operador "timer" com período de re-execução para loops de polling.'
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
// Regra: use-standard-devkit-queries (Frontend Component)
|
|
254
|
+
if (severityStandardQueries !== 'off' && file.endsWith('-list.component.ts')) {
|
|
255
|
+
if (!content.includes('extends BaseListPage')) {
|
|
256
|
+
violations.push({
|
|
257
|
+
file: relativePath,
|
|
258
|
+
line: 1,
|
|
259
|
+
rule: 'use-standard-devkit-queries',
|
|
260
|
+
severity: severityStandardQueries,
|
|
261
|
+
message: 'Componentes de listagem devem herdar de "BaseListPage" para garantir sincronização de filtros com a URL.'
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Regra: use-standard-devkit-queries (Frontend CRUD Service)
|
|
266
|
+
if (severityStandardQueries !== 'off' && file.endsWith('.service.ts') && !file.endsWith('base.service.ts')) {
|
|
267
|
+
const looksLikeCrudService = /\blist\s*\(/.test(content) &&
|
|
268
|
+
/\bcreate\s*\(/.test(content) &&
|
|
269
|
+
/\bupdate\s*\(/.test(content) &&
|
|
270
|
+
/\bdelete\s*\(/.test(content);
|
|
271
|
+
if (looksLikeCrudService && !content.includes('extends BaseService')) {
|
|
272
|
+
violations.push({
|
|
273
|
+
file: relativePath,
|
|
274
|
+
line: 1,
|
|
275
|
+
rule: 'use-standard-devkit-queries',
|
|
276
|
+
severity: severityStandardQueries,
|
|
277
|
+
message: 'Serviços CRUD do frontend devem herdar de "BaseService" para padronizar endpoints, paginação e operações de CRUD.'
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Regra: force-separate-template-and-styles
|
|
282
|
+
const severitySeparate = getRuleSeverity('force-separate-template-and-styles', 'error');
|
|
283
|
+
if (severitySeparate !== 'off') {
|
|
284
|
+
lines.forEach((line, index) => {
|
|
285
|
+
const trimmed = line.trim();
|
|
286
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*'))
|
|
287
|
+
return;
|
|
288
|
+
if (/\btemplate\s*:\s*[`'"]/.test(line)) {
|
|
289
|
+
violations.push({
|
|
290
|
+
file: relativePath,
|
|
291
|
+
line: index + 1,
|
|
292
|
+
rule: 'force-separate-template-and-styles',
|
|
293
|
+
severity: severitySeparate,
|
|
294
|
+
message: 'Evite declarar template embutido (inline) no arquivo .ts. Utilize um arquivo HTML externo ("templateUrl").'
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
if (/\bstyles\s*:\s*\[/.test(line)) {
|
|
298
|
+
violations.push({
|
|
299
|
+
file: relativePath,
|
|
300
|
+
line: index + 1,
|
|
301
|
+
rule: 'force-separate-template-and-styles',
|
|
302
|
+
severity: severitySeparate,
|
|
303
|
+
message: 'Evite declarar estilos embutidos (inline) no arquivo .ts. Utilize um arquivo CSS/SCSS externo ("styleUrls" ou "styleUrl").'
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Auditoria de templates HTML de Listas no Frontend
|
|
310
|
+
if (file.endsWith('-list.component.html')) {
|
|
311
|
+
if (severityStandardQueries !== 'off') {
|
|
312
|
+
const hasTable = content.includes('<devkit-table') || content.includes('devkit-table');
|
|
313
|
+
const hasCardList = content.includes('<devkit-card-list') || content.includes('devkit-card-list');
|
|
314
|
+
const hasSimpleList = content.includes('<devkit-simple-list') || content.includes('devkit-simple-list');
|
|
315
|
+
if (!hasTable && !hasCardList && !hasSimpleList) {
|
|
316
|
+
violations.push({
|
|
317
|
+
file: relativePath,
|
|
318
|
+
line: 1,
|
|
319
|
+
rule: 'use-standard-devkit-queries',
|
|
320
|
+
severity: severityStandardQueries,
|
|
321
|
+
message: 'Telas de listagem devem utilizar um componente de exibição padrão do design system ("<devkit-table>", "<devkit-card-list>" ou "<devkit-simple-list>").'
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
if (!content.includes('<devkit-filter') && !content.includes('devkit-filter')) {
|
|
325
|
+
violations.push({
|
|
326
|
+
file: relativePath,
|
|
327
|
+
line: 1,
|
|
328
|
+
rule: 'use-standard-devkit-queries',
|
|
329
|
+
severity: severityStandardQueries,
|
|
330
|
+
message: 'Telas de listagem devem utilizar o componente de filtros padrão "<devkit-filter>".'
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return { violations, filesScanned };
|
|
338
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const node_test_1 = require("node:test");
|
|
37
|
+
const assert = __importStar(require("node:assert"));
|
|
38
|
+
const fs = __importStar(require("fs-extra"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const linter_rules_1 = require("./linter-rules");
|
|
41
|
+
const sandboxDir = path.resolve(__dirname, 'test-sandbox');
|
|
42
|
+
function setupSandbox() {
|
|
43
|
+
fs.ensureDirSync(sandboxDir);
|
|
44
|
+
fs.ensureDirSync(path.join(sandboxDir, 'backend/src/modules/cabin/controllers'));
|
|
45
|
+
fs.ensureDirSync(path.join(sandboxDir, 'backend/src/modules/cabin/entities'));
|
|
46
|
+
fs.ensureDirSync(path.join(sandboxDir, 'backend/src/modules/cabin/repositories'));
|
|
47
|
+
fs.ensureDirSync(path.join(sandboxDir, 'frontend/src/app/modules/cabin/list'));
|
|
48
|
+
fs.ensureDirSync(path.join(sandboxDir, 'frontend/src/app/services'));
|
|
49
|
+
}
|
|
50
|
+
function teardownSandbox() {
|
|
51
|
+
fs.removeSync(sandboxDir);
|
|
52
|
+
}
|
|
53
|
+
(0, node_test_1.test)('runLinter should detect all rules violations', () => {
|
|
54
|
+
setupSandbox();
|
|
55
|
+
// 1. Violando clean-architecture-imports e use-standard-devkit-queries no Backend
|
|
56
|
+
fs.writeFileSync(path.join(sandboxDir, 'backend/src/modules/cabin/entities/cabin.entity.ts'), `import { Controller } from '@nestjs/common';
|
|
57
|
+
import { CabinController } from '../controllers/cabin.controller'; // VIOLAÇÃO Clean Arch
|
|
58
|
+
export class CabinEntity {}`, 'utf-8');
|
|
59
|
+
fs.writeFileSync(path.join(sandboxDir, 'backend/src/modules/cabin/controllers/cabin.controller.ts'), `import { Controller } from '@nestjs/common';
|
|
60
|
+
@Controller('cabin')
|
|
61
|
+
export class CabinController {} // VIOLAÇÃO use-standard-devkit-queries (não estende BaseCrudController)`, 'utf-8');
|
|
62
|
+
// 2. Violando no-polling-in-frontend e force-separate-template-and-styles no Frontend
|
|
63
|
+
fs.writeFileSync(path.join(sandboxDir, 'frontend/src/app/modules/cabin/list/cabin-list.component.ts'), `import { Component } from '@angular/core';
|
|
64
|
+
import { interval } from 'rxjs'; // Para regra de polling
|
|
65
|
+
@Component({
|
|
66
|
+
selector: 'app-cabin-list',
|
|
67
|
+
template: '<div>Inline</div>', // VIOLAÇÃO force-separate-template-and-styles
|
|
68
|
+
styles: ['h1 { color: red; }'] // VIOLAÇÃO force-separate-template-and-styles
|
|
69
|
+
})
|
|
70
|
+
export class CabinListComponent {
|
|
71
|
+
constructor() {
|
|
72
|
+
setInterval(() => {}, 1000); // VIOLAÇÃO no-polling-in-frontend
|
|
73
|
+
interval(5000).subscribe(); // VIOLAÇÃO no-polling-in-frontend
|
|
74
|
+
}
|
|
75
|
+
}`, 'utf-8');
|
|
76
|
+
// 3. Violando use-standard-devkit-queries (HTML sem devkit-table ou devkit-filter)
|
|
77
|
+
fs.writeFileSync(path.join(sandboxDir, 'frontend/src/app/modules/cabin/list/cabin-list.component.html'), `<div>Apenas uma div comum sem a tabela padrão</div>`, 'utf-8');
|
|
78
|
+
fs.writeFileSync(path.join(sandboxDir, 'frontend/src/app/services/cabin.service.ts'), `import { Injectable } from '@angular/core';
|
|
79
|
+
@Injectable({ providedIn: 'root' })
|
|
80
|
+
export class CabinService {
|
|
81
|
+
list() {}
|
|
82
|
+
create() {}
|
|
83
|
+
update() {}
|
|
84
|
+
delete() {}
|
|
85
|
+
}`, 'utf-8');
|
|
86
|
+
const config = {
|
|
87
|
+
backendPath: './backend',
|
|
88
|
+
frontendPath: './frontend',
|
|
89
|
+
rules: {
|
|
90
|
+
'no-polling-in-frontend': 'error',
|
|
91
|
+
'force-pagination-in-backend': 'error',
|
|
92
|
+
'use-standard-devkit-queries': 'error',
|
|
93
|
+
'clean-architecture-imports': 'warn',
|
|
94
|
+
'force-separate-template-and-styles': 'error'
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const result = (0, linter_rules_1.runLinter)(sandboxDir, config);
|
|
98
|
+
teardownSandbox();
|
|
99
|
+
// Verifica que os arquivos foram escaneados
|
|
100
|
+
assert.ok(result.filesScanned >= 4);
|
|
101
|
+
// Mapeia regras violadas por facilidade de asserção
|
|
102
|
+
const rulesViolated = result.violations.map(v => v.rule);
|
|
103
|
+
// Deve encontrar clean-architecture-imports
|
|
104
|
+
assert.ok(rulesViolated.includes('clean-architecture-imports'), 'Deveria detectar clean-architecture-imports');
|
|
105
|
+
// Deve encontrar use-standard-devkit-queries no Controller
|
|
106
|
+
assert.ok(rulesViolated.includes('use-standard-devkit-queries'), 'Deveria detectar use-standard-devkit-queries');
|
|
107
|
+
// Deve encontrar force-separate-template-and-styles (template e styles inline)
|
|
108
|
+
const separateViolations = result.violations.filter(v => v.rule === 'force-separate-template-and-styles');
|
|
109
|
+
assert.strictEqual(separateViolations.length, 2, 'Deveria detectar template e styles inline (2 violações)');
|
|
110
|
+
// Deve encontrar no-polling-in-frontend (setInterval e rxjs interval)
|
|
111
|
+
const pollingViolations = result.violations.filter(v => v.rule === 'no-polling-in-frontend');
|
|
112
|
+
assert.strictEqual(pollingViolations.length, 2, 'Deveria detectar setInterval e interval do rxjs (2 violações)');
|
|
113
|
+
});
|
|
114
|
+
(0, node_test_1.test)('runLinter should pass use-standard-devkit-queries when using devkit-card-list or devkit-simple-list', () => {
|
|
115
|
+
setupSandbox();
|
|
116
|
+
// Caso 1: Usando devkit-card-list no HTML e import correto no TS
|
|
117
|
+
fs.writeFileSync(path.join(sandboxDir, 'frontend/src/app/modules/cabin/list/cabin-list.component.html'), `<div class="p-6">
|
|
118
|
+
<devkit-filter [config]="[]"></devkit-filter>
|
|
119
|
+
<devkit-card-list [columns]="[]" [items]="[]"></devkit-card-list>
|
|
120
|
+
</div>`, 'utf-8');
|
|
121
|
+
fs.writeFileSync(path.join(sandboxDir, 'frontend/src/app/modules/cabin/list/cabin-list.component.ts'), `import { Component } from '@angular/core';
|
|
122
|
+
import { BaseListPage } from '@devstroupe/devkit-angular';
|
|
123
|
+
@Component({
|
|
124
|
+
selector: 'app-cabin-list',
|
|
125
|
+
templateUrl: './cabin-list.component.html',
|
|
126
|
+
styleUrls: ['./cabin-list.component.css']
|
|
127
|
+
})
|
|
128
|
+
export class CabinListComponent extends BaseListPage<any> {}`, 'utf-8');
|
|
129
|
+
const config = {
|
|
130
|
+
backendPath: './backend',
|
|
131
|
+
frontendPath: './frontend',
|
|
132
|
+
rules: {
|
|
133
|
+
'use-standard-devkit-queries': 'error'
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const result1 = (0, linter_rules_1.runLinter)(sandboxDir, config);
|
|
137
|
+
// Caso 2: Usando devkit-simple-list no HTML
|
|
138
|
+
fs.writeFileSync(path.join(sandboxDir, 'frontend/src/app/modules/cabin/list/cabin-list.component.html'), `<div class="p-6">
|
|
139
|
+
<devkit-filter [config]="[]"></devkit-filter>
|
|
140
|
+
<devkit-simple-list [columns]="[]" [items]="[]"></devkit-simple-list>
|
|
141
|
+
</div>`, 'utf-8');
|
|
142
|
+
const result2 = (0, linter_rules_1.runLinter)(sandboxDir, config);
|
|
143
|
+
teardownSandbox();
|
|
144
|
+
// As duas execuções não devem gerar violações no frontend
|
|
145
|
+
const violations1 = result1.violations.filter(v => v.file.includes('cabin-list.component.html'));
|
|
146
|
+
const violations2 = result2.violations.filter(v => v.file.includes('cabin-list.component.html'));
|
|
147
|
+
assert.strictEqual(violations1.length, 0, 'Deveria passar com devkit-card-list');
|
|
148
|
+
assert.strictEqual(violations2.length, 0, 'Deveria passar com devkit-simple-list');
|
|
149
|
+
});
|
|
150
|
+
(0, node_test_1.test)('runLinter should pass use-standard-devkit-queries when CRUD service extends BaseService', () => {
|
|
151
|
+
setupSandbox();
|
|
152
|
+
fs.writeFileSync(path.join(sandboxDir, 'frontend/src/app/services/cabin.service.ts'), `import { Injectable } from '@angular/core';
|
|
153
|
+
import { BaseService } from '@devstroupe/devkit-angular';
|
|
154
|
+
@Injectable({ providedIn: 'root' })
|
|
155
|
+
export class CabinService extends BaseService<any> {}`, 'utf-8');
|
|
156
|
+
const result = (0, linter_rules_1.runLinter)(sandboxDir, {
|
|
157
|
+
backendPath: './backend',
|
|
158
|
+
frontendPath: './frontend',
|
|
159
|
+
rules: {
|
|
160
|
+
'use-standard-devkit-queries': 'error'
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
teardownSandbox();
|
|
164
|
+
const serviceViolations = result.violations.filter(v => v.file.includes('cabin.service.ts'));
|
|
165
|
+
assert.strictEqual(serviceViolations.length, 0, 'Deveria passar quando serviço CRUD herda de BaseService');
|
|
166
|
+
});
|
|
167
|
+
(0, node_test_1.test)('runLinter should ignore Spartan generated primitives in frontend libs/ui', () => {
|
|
168
|
+
setupSandbox();
|
|
169
|
+
const spartanDir = path.join(sandboxDir, 'frontend/libs/ui/button/src/lib');
|
|
170
|
+
fs.ensureDirSync(spartanDir);
|
|
171
|
+
fs.writeFileSync(path.join(spartanDir, 'hlm-button.ts'), `import { Component } from '@angular/core';
|
|
172
|
+
@Component({
|
|
173
|
+
selector: 'button[hlmBtn]',
|
|
174
|
+
template: '<ng-content />',
|
|
175
|
+
styles: [':host { display: inline-flex; }']
|
|
176
|
+
})
|
|
177
|
+
export class HlmButton {}`, 'utf-8');
|
|
178
|
+
const result = (0, linter_rules_1.runLinter)(sandboxDir, {
|
|
179
|
+
backendPath: './backend',
|
|
180
|
+
frontendPath: './frontend',
|
|
181
|
+
rules: {
|
|
182
|
+
'force-separate-template-and-styles': 'error'
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
teardownSandbox();
|
|
186
|
+
const spartanViolations = result.violations.filter(v => v.file.includes('frontend/libs/ui/'));
|
|
187
|
+
assert.strictEqual(spartanViolations.length, 0, 'Não deveria auditar primitives Helm gerados em frontend/libs/ui');
|
|
188
|
+
});
|
|
189
|
+
(0, node_test_1.test)('runLinter should reject custom radius in DevKit wrappers', () => {
|
|
190
|
+
setupSandbox();
|
|
191
|
+
const wrapperDir = path.join(sandboxDir, 'frontend/src/app/shared/components/devkit/card');
|
|
192
|
+
fs.ensureDirSync(wrapperDir);
|
|
193
|
+
fs.writeFileSync(path.join(wrapperDir, 'card.component.css'), `:host { --radius: 12px; border-radius: 12px; }`, 'utf-8');
|
|
194
|
+
const result = (0, linter_rules_1.runLinter)(sandboxDir, {
|
|
195
|
+
backendPath: './backend',
|
|
196
|
+
frontendPath: './frontend',
|
|
197
|
+
rules: { 'no-custom-spartan-radius': 'error' }
|
|
198
|
+
});
|
|
199
|
+
teardownSandbox();
|
|
200
|
+
const violations = result.violations.filter(v => v.rule === 'no-custom-spartan-radius');
|
|
201
|
+
assert.strictEqual(violations.length, 1, 'Deveria detectar redefinição e radius próprio na mesma linha');
|
|
202
|
+
});
|
|
203
|
+
(0, node_test_1.test)('runLinter should reject TypeORM synchronize enabled', () => {
|
|
204
|
+
setupSandbox();
|
|
205
|
+
fs.writeFileSync(path.join(sandboxDir, 'backend/src/app.module.ts'), `export const databaseOptions = { synchronize: true };`, 'utf-8');
|
|
206
|
+
const result = (0, linter_rules_1.runLinter)(sandboxDir, {
|
|
207
|
+
backendPath: './backend',
|
|
208
|
+
frontendPath: './frontend',
|
|
209
|
+
rules: { 'disable-typeorm-synchronize': 'error' }
|
|
210
|
+
});
|
|
211
|
+
teardownSandbox();
|
|
212
|
+
const violations = result.violations.filter(v => v.rule === 'disable-typeorm-synchronize');
|
|
213
|
+
assert.strictEqual(violations.length, 1);
|
|
214
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.angularFormDialogHandlerHtmlTemplate = angularFormDialogHandlerHtmlTemplate;
|
|
4
|
+
exports.angularFormDialogHandlerTsTemplate = angularFormDialogHandlerTsTemplate;
|
|
5
|
+
const names_1 = require("../shared/names");
|
|
6
|
+
function angularFormDialogHandlerHtmlTemplate(name) {
|
|
7
|
+
return `<devkit-dialog [isOpen]="true" (close)="onClose()">
|
|
8
|
+
<app-${name}-form
|
|
9
|
+
[isDialog]="true"
|
|
10
|
+
[itemId]="itemId"
|
|
11
|
+
(saved)="onClose()"
|
|
12
|
+
(cancelled)="onClose()"
|
|
13
|
+
></app-${name}-form>
|
|
14
|
+
</devkit-dialog>
|
|
15
|
+
`;
|
|
16
|
+
}
|
|
17
|
+
function angularFormDialogHandlerTsTemplate(name) {
|
|
18
|
+
const pascalName = (0, names_1.kebabToPascal)(name);
|
|
19
|
+
return `import { Component, OnInit, inject } from '@angular/core';
|
|
20
|
+
import { CommonModule } from '@angular/common';
|
|
21
|
+
import { ActivatedRoute, Router } from '@angular/router';
|
|
22
|
+
import { DevkitDialogComponent } from '@devstroupe/ui/dialog/dialog.component';
|
|
23
|
+
import { ${pascalName}FormComponent } from '../form/${name}-form.component';
|
|
24
|
+
|
|
25
|
+
@Component({
|
|
26
|
+
selector: 'app-${name}-form-dialog-handler',
|
|
27
|
+
standalone: true,
|
|
28
|
+
imports: [CommonModule, DevkitDialogComponent, ${pascalName}FormComponent],
|
|
29
|
+
templateUrl: './${name}-form-dialog-handler.component.html'
|
|
30
|
+
})
|
|
31
|
+
export class ${pascalName}FormDialogHandlerComponent implements OnInit {
|
|
32
|
+
private router = inject(Router);
|
|
33
|
+
private route = inject(ActivatedRoute);
|
|
34
|
+
|
|
35
|
+
itemId?: string | number;
|
|
36
|
+
|
|
37
|
+
ngOnInit(): void {
|
|
38
|
+
const id = this.route.snapshot.paramMap.get('id');
|
|
39
|
+
if (id) {
|
|
40
|
+
this.itemId = id;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onClose(): void {
|
|
45
|
+
this.router.navigate(['../'], { relativeTo: this.route });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
}
|