@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.
Files changed (170) hide show
  1. package/README.md +40 -0
  2. package/dist/boilerplates/angular-template/.dockerignore +4 -0
  3. package/dist/boilerplates/angular-template/.postcssrc.json +5 -0
  4. package/dist/boilerplates/angular-template/Dockerfile +14 -0
  5. package/dist/boilerplates/angular-template/angular.json +85 -0
  6. package/dist/boilerplates/angular-template/components.json +5 -0
  7. package/dist/boilerplates/angular-template/framework.code-workspace +7 -0
  8. package/dist/boilerplates/angular-template/nginx.conf +23 -0
  9. package/dist/boilerplates/angular-template/node_modules/.bin/browserslist +21 -0
  10. package/dist/boilerplates/angular-template/node_modules/.bin/jiti +21 -0
  11. package/dist/boilerplates/angular-template/node_modules/.bin/lessc +21 -0
  12. package/dist/boilerplates/angular-template/node_modules/.bin/ng +21 -0
  13. package/dist/boilerplates/angular-template/node_modules/.bin/ng-xi18n +21 -0
  14. package/dist/boilerplates/angular-template/node_modules/.bin/ngc +21 -0
  15. package/dist/boilerplates/angular-template/node_modules/.bin/sass +21 -0
  16. package/dist/boilerplates/angular-template/node_modules/.bin/terser +21 -0
  17. package/dist/boilerplates/angular-template/node_modules/.bin/tsc +21 -0
  18. package/dist/boilerplates/angular-template/node_modules/.bin/tsserver +21 -0
  19. package/dist/boilerplates/angular-template/node_modules/.bin/vite +21 -0
  20. package/dist/boilerplates/angular-template/node_modules/.bin/vitest +21 -0
  21. package/dist/boilerplates/angular-template/node_modules/.bin/yaml +21 -0
  22. package/dist/boilerplates/angular-template/package.json +47 -0
  23. package/dist/boilerplates/angular-template/postcss.config.js +5 -0
  24. package/dist/boilerplates/angular-template/proxy.conf.json +7 -0
  25. package/dist/boilerplates/angular-template/src/app/app.component.html +3 -0
  26. package/dist/boilerplates/angular-template/src/app/app.component.ts +12 -0
  27. package/dist/boilerplates/angular-template/src/app/app.config.ts +59 -0
  28. package/dist/boilerplates/angular-template/src/app/app.entity-routes.ts +4 -0
  29. package/dist/boilerplates/angular-template/src/app/app.routes.ts +25 -0
  30. package/dist/boilerplates/angular-template/src/app/core/guards/auth.guard.ts +16 -0
  31. package/dist/boilerplates/angular-template/src/app/core/services/auth.service.ts +98 -0
  32. package/dist/boilerplates/angular-template/src/app/modules/auth/login/login.component.html +51 -0
  33. package/dist/boilerplates/angular-template/src/app/modules/auth/login/login.component.ts +47 -0
  34. package/dist/boilerplates/angular-template/src/app/modules/auth/register/register.component.html +59 -0
  35. package/dist/boilerplates/angular-template/src/app/modules/auth/register/register.component.ts +47 -0
  36. package/dist/boilerplates/angular-template/src/app/modules/dashboard/dashboard.component.html +14 -0
  37. package/dist/boilerplates/angular-template/src/app/modules/dashboard/dashboard.component.ts +23 -0
  38. package/dist/boilerplates/angular-template/src/app/shared/interceptors/tenant.interceptor.ts +25 -0
  39. package/dist/boilerplates/angular-template/src/index.html +64 -0
  40. package/dist/boilerplates/angular-template/src/main.ts +6 -0
  41. package/dist/boilerplates/angular-template/src/styles.css +9 -0
  42. package/dist/boilerplates/angular-template/tsconfig.json +36 -0
  43. package/dist/boilerplates/nest-template/Dockerfile +16 -0
  44. package/dist/boilerplates/nest-template/node_modules/.bin/acorn +21 -0
  45. package/dist/boilerplates/nest-template/node_modules/.bin/nest +21 -0
  46. package/dist/boilerplates/nest-template/node_modules/.bin/prettier +21 -0
  47. package/dist/boilerplates/nest-template/node_modules/.bin/tsc +21 -0
  48. package/dist/boilerplates/nest-template/node_modules/.bin/tsserver +21 -0
  49. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm +21 -0
  50. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-commonjs +21 -0
  51. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-esm +21 -0
  52. package/dist/boilerplates/nest-template/node_modules/.bin/webpack +21 -0
  53. package/dist/boilerplates/nest-template/package.json +43 -0
  54. package/dist/boilerplates/nest-template/src/app.module.ts +24 -0
  55. package/dist/boilerplates/nest-template/src/database/data-source.ts +16 -0
  56. package/dist/boilerplates/nest-template/src/database/migrations/1700000000000-create-users.ts +26 -0
  57. package/dist/boilerplates/nest-template/src/main.ts +46 -0
  58. package/dist/boilerplates/nest-template/src/modules/auth/auth.controller.ts +34 -0
  59. package/dist/boilerplates/nest-template/src/modules/auth/auth.module.ts +22 -0
  60. package/dist/boilerplates/nest-template/src/modules/auth/auth.service.ts +57 -0
  61. package/dist/boilerplates/nest-template/src/modules/auth/current-user.decorator.ts +8 -0
  62. package/dist/boilerplates/nest-template/src/modules/auth/jwt-auth.guard.ts +31 -0
  63. package/dist/boilerplates/nest-template/src/modules/auth/roles.decorator.ts +4 -0
  64. package/dist/boilerplates/nest-template/src/modules/auth/roles.guard.ts +24 -0
  65. package/dist/boilerplates/nest-template/src/modules/user/database-seed.service.ts +47 -0
  66. package/dist/boilerplates/nest-template/src/modules/user/domain/user.repository.ts +9 -0
  67. package/dist/boilerplates/nest-template/src/modules/user/domain/user.ts +9 -0
  68. package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.orm-entity.ts +31 -0
  69. package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts +53 -0
  70. package/dist/boilerplates/nest-template/src/modules/user/service/user.service.ts +44 -0
  71. package/dist/boilerplates/nest-template/src/modules/user/user.module.ts +20 -0
  72. package/dist/boilerplates/nest-template/tsconfig.build.json +4 -0
  73. package/dist/boilerplates/nest-template/tsconfig.json +21 -0
  74. package/dist/index.d.ts +2 -0
  75. package/dist/index.js +1967 -0
  76. package/dist/migrations/sort-entities.d.ts +2 -0
  77. package/dist/migrations/sort-entities.js +30 -0
  78. package/dist/rules/linter-rules.d.ts +12 -0
  79. package/dist/rules/linter-rules.js +338 -0
  80. package/dist/rules/linter-rules.test.d.ts +1 -0
  81. package/dist/rules/linter-rules.test.js +214 -0
  82. package/dist/templates/angular/dialog-handler.template.d.ts +2 -0
  83. package/dist/templates/angular/dialog-handler.template.js +49 -0
  84. package/dist/templates/angular/form.template.d.ts +3 -0
  85. package/dist/templates/angular/form.template.js +453 -0
  86. package/dist/templates/angular/index.d.ts +4 -0
  87. package/dist/templates/angular/index.js +20 -0
  88. package/dist/templates/angular/list.template.d.ts +3 -0
  89. package/dist/templates/angular/list.template.js +213 -0
  90. package/dist/templates/angular/service.template.d.ts +1 -0
  91. package/dist/templates/angular/service.template.js +20 -0
  92. package/dist/templates/cli-templates.d.ts +2 -0
  93. package/dist/templates/cli-templates.js +18 -0
  94. package/dist/templates/nest/crud.templates.d.ts +9 -0
  95. package/dist/templates/nest/crud.templates.js +362 -0
  96. package/dist/templates/nest/index.d.ts +3 -0
  97. package/dist/templates/nest/index.js +19 -0
  98. package/dist/templates/nest/microservice.templates.d.ts +4 -0
  99. package/dist/templates/nest/microservice.templates.js +157 -0
  100. package/dist/templates/nest/migration.template.d.ts +3 -0
  101. package/dist/templates/nest/migration.template.js +127 -0
  102. package/dist/templates/relationship-templates.test.d.ts +1 -0
  103. package/dist/templates/relationship-templates.test.js +181 -0
  104. package/dist/templates/shared/names.d.ts +3 -0
  105. package/dist/templates/shared/names.js +14 -0
  106. package/dist/templates/shared/relationships.d.ts +27 -0
  107. package/dist/templates/shared/relationships.js +96 -0
  108. package/dist/templates/ui/components/avatar.template.d.ts +3 -0
  109. package/dist/templates/ui/components/avatar.template.js +66 -0
  110. package/dist/templates/ui/components/badge.template.d.ts +3 -0
  111. package/dist/templates/ui/components/badge.template.js +27 -0
  112. package/dist/templates/ui/components/button.template.d.ts +5 -0
  113. package/dist/templates/ui/components/button.template.js +64 -0
  114. package/dist/templates/ui/components/card-list.template.d.ts +5 -0
  115. package/dist/templates/ui/components/card-list.template.js +79 -0
  116. package/dist/templates/ui/components/dialog.template.d.ts +5 -0
  117. package/dist/templates/ui/components/dialog.template.js +51 -0
  118. package/dist/templates/ui/components/filter.template.d.ts +4 -0
  119. package/dist/templates/ui/components/filter.template.js +218 -0
  120. package/dist/templates/ui/components/index.d.ts +10 -0
  121. package/dist/templates/ui/components/index.js +26 -0
  122. package/dist/templates/ui/components/input.template.d.ts +4 -0
  123. package/dist/templates/ui/components/input.template.js +76 -0
  124. package/dist/templates/ui/components/select.template.d.ts +4 -0
  125. package/dist/templates/ui/components/select.template.js +176 -0
  126. package/dist/templates/ui/components/simple-list.template.d.ts +5 -0
  127. package/dist/templates/ui/components/simple-list.template.js +89 -0
  128. package/dist/templates/ui/components/table.template.d.ts +5 -0
  129. package/dist/templates/ui/components/table.template.js +112 -0
  130. package/dist/templates/ui/index.d.ts +9 -0
  131. package/dist/templates/ui/index.js +25 -0
  132. package/dist/templates/ui/layout/header.template.d.ts +5 -0
  133. package/dist/templates/ui/layout/header.template.js +236 -0
  134. package/dist/templates/ui/layout/index.d.ts +4 -0
  135. package/dist/templates/ui/layout/index.js +20 -0
  136. package/dist/templates/ui/layout/main-layout.template.d.ts +5 -0
  137. package/dist/templates/ui/layout/main-layout.template.js +126 -0
  138. package/dist/templates/ui/layout/shell.template.d.ts +5 -0
  139. package/dist/templates/ui/layout/shell.template.js +76 -0
  140. package/dist/templates/ui/layout/sidebar.template.d.ts +5 -0
  141. package/dist/templates/ui/layout/sidebar.template.js +412 -0
  142. package/dist/templates/ui/playground.template.d.ts +5 -0
  143. package/dist/templates/ui/playground.template.js +570 -0
  144. package/dist/templates/ui/readme.template.d.ts +1 -0
  145. package/dist/templates/ui/readme.template.js +23 -0
  146. package/dist/templates/ui/shared/colors.d.ts +1 -0
  147. package/dist/templates/ui/shared/colors.js +31 -0
  148. package/dist/templates/ui/shared/profile-component.template.d.ts +5 -0
  149. package/dist/templates/ui/shared/profile-component.template.js +403 -0
  150. package/dist/templates/ui/shared/profile-service.template.d.ts +1 -0
  151. package/dist/templates/ui/shared/profile-service.template.js +44 -0
  152. package/dist/templates/ui/shared/theme-service.template.d.ts +1 -0
  153. package/dist/templates/ui/shared/theme-service.template.js +47 -0
  154. package/dist/templates/ui/spartan/badge-directive.template.d.ts +3 -0
  155. package/dist/templates/ui/spartan/badge-directive.template.js +50 -0
  156. package/dist/templates/ui/spartan/button-directive.template.d.ts +3 -0
  157. package/dist/templates/ui/spartan/button-directive.template.js +82 -0
  158. package/dist/templates/ui/spartan/button-token.template.d.ts +3 -0
  159. package/dist/templates/ui/spartan/button-token.template.js +31 -0
  160. package/dist/templates/ui/spartan/hlm-core.template.d.ts +3 -0
  161. package/dist/templates/ui/spartan/hlm-core.template.js +322 -0
  162. package/dist/templates/ui/spartan/index.d.ts +5 -0
  163. package/dist/templates/ui/spartan/index.js +21 -0
  164. package/dist/templates/ui/spartan/input-directive.template.d.ts +3 -0
  165. package/dist/templates/ui/spartan/input-directive.template.js +31 -0
  166. package/dist/templates/ui/theme-css.template.d.ts +7 -0
  167. package/dist/templates/ui/theme-css.template.js +210 -0
  168. package/dist/templates/ui-templates.d.ts +1 -0
  169. package/dist/templates/ui-templates.js +17 -0
  170. package/package.json +50 -0
@@ -0,0 +1,2 @@
1
+ import { IDevkitEntityConfig } from '@devstroupe/devkit-core';
2
+ export declare function sortEntitiesForMigrations(entities: IDevkitEntityConfig[]): IDevkitEntityConfig[];
@@ -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,2 @@
1
+ export declare function angularFormDialogHandlerHtmlTemplate(name: string): string;
2
+ export declare function angularFormDialogHandlerTsTemplate(name: string): string;
@@ -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
+ }