@semacode/cli 1.5.11 → 1.5.16

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 (177) hide show
  1. package/README.md +6 -1
  2. package/dist/index.js +446 -32
  3. package/package.json +23 -32
  4. package/semacode-cli-1.3.1.tgz +0 -0
  5. package/src/angular-consumer-standalone.ts +312 -0
  6. package/src/cpp-symbols.ts +82 -0
  7. package/src/docs.ts +535 -0
  8. package/src/dotnet-http.ts +355 -0
  9. package/src/drift.ts +4933 -0
  10. package/src/go-http.ts +118 -0
  11. package/src/importador.ts +3891 -0
  12. package/src/index.ts +5641 -0
  13. package/src/java-http.ts +247 -0
  14. package/src/lua-symbols.ts +114 -0
  15. package/src/php-symbols.ts +462 -0
  16. package/src/projeto.ts +862 -0
  17. package/src/python-http.ts +258 -0
  18. package/src/rust-http.ts +125 -0
  19. package/src/tipos.ts +24 -0
  20. package/src/typescript-http.ts +1076 -0
  21. package/tsconfig.json +20 -0
  22. package/AGENTS.md +0 -272
  23. package/LICENSE +0 -22
  24. package/SEMA_BRIEF.curto.txt +0 -9
  25. package/SEMA_BRIEF.md +0 -63
  26. package/SEMA_BRIEF.micro.txt +0 -7
  27. package/SEMA_INDEX.json +0 -799
  28. package/dist/angular-consumer-standalone.d.ts +0 -6
  29. package/dist/angular-consumer-standalone.js.map +0 -1
  30. package/dist/cpp-symbols.d.ts +0 -10
  31. package/dist/cpp-symbols.js.map +0 -1
  32. package/dist/docs.d.ts +0 -56
  33. package/dist/docs.js.map +0 -1
  34. package/dist/dotnet-http.d.ts +0 -23
  35. package/dist/dotnet-http.js.map +0 -1
  36. package/dist/drift.d.ts +0 -225
  37. package/dist/drift.js.map +0 -1
  38. package/dist/go-http.d.ts +0 -23
  39. package/dist/go-http.js.map +0 -1
  40. package/dist/importador.d.ts +0 -31
  41. package/dist/importador.js.map +0 -1
  42. package/dist/index.d.ts +0 -2
  43. package/dist/index.js.map +0 -1
  44. package/dist/java-http.d.ts +0 -23
  45. package/dist/java-http.js.map +0 -1
  46. package/dist/lua-symbols.d.ts +0 -10
  47. package/dist/lua-symbols.js.map +0 -1
  48. package/dist/php-symbols.d.ts +0 -24
  49. package/dist/php-symbols.js.map +0 -1
  50. package/dist/projeto.d.ts +0 -53
  51. package/dist/projeto.js.map +0 -1
  52. package/dist/python-http.d.ts +0 -23
  53. package/dist/python-http.js.map +0 -1
  54. package/dist/rust-http.d.ts +0 -23
  55. package/dist/rust-http.js.map +0 -1
  56. package/dist/tipos.d.ts +0 -3
  57. package/dist/tipos.js.map +0 -1
  58. package/dist/typescript-http.d.ts +0 -35
  59. package/dist/typescript-http.js.map +0 -1
  60. package/docs/AGENT_STARTER.md +0 -102
  61. package/docs/cli.md +0 -110
  62. package/docs/como-ensinar-a-sema-para-ia.md +0 -149
  63. package/docs/deploy.md +0 -70
  64. package/docs/documentacao.md +0 -63
  65. package/docs/env.md +0 -56
  66. package/docs/extensao-vscode.md +0 -45
  67. package/docs/fluxo-pratico-ia-sema.md +0 -177
  68. package/docs/instalacao-e-primeiro-uso.md +0 -112
  69. package/docs/integracao-com-ia.md +0 -101
  70. package/docs/mcp.md +0 -53
  71. package/docs/pagamento-ponta-a-ponta.md +0 -155
  72. package/docs/persistencia-vendor-first.md +0 -145
  73. package/docs/prompt-base-ia-sema.md +0 -104
  74. package/docs/rollback.md +0 -47
  75. package/docs/sintaxe.md +0 -244
  76. package/exemplos/agendamento.sema +0 -106
  77. package/exemplos/assinatura.sema +0 -136
  78. package/exemplos/auditoria.sema +0 -88
  79. package/exemplos/autenticacao.sema +0 -125
  80. package/exemplos/automacao.sema +0 -107
  81. package/exemplos/cadastro_usuario.sema +0 -54
  82. package/exemplos/calculadora.sema +0 -78
  83. package/exemplos/crud_simples.sema +0 -89
  84. package/exemplos/estoque.sema +0 -126
  85. package/exemplos/exportacao.sema +0 -94
  86. package/exemplos/fila.sema +0 -131
  87. package/exemplos/integracao_externa.sema +0 -94
  88. package/exemplos/multi_tenant.sema +0 -140
  89. package/exemplos/notificacao.sema +0 -98
  90. package/exemplos/operacao_estrategia.sema +0 -402
  91. package/exemplos/pagamento.sema +0 -222
  92. package/exemplos/pagamento_dominio.sema +0 -35
  93. package/exemplos/pedido.sema +0 -119
  94. package/exemplos/permissao.sema +0 -121
  95. package/exemplos/persistencia_vendor_first.sema +0 -86
  96. package/exemplos/relatorio.sema +0 -93
  97. package/exemplos/testes_embutidos.sema +0 -45
  98. package/exemplos/tratamento_erro.sema +0 -157
  99. package/exemplos/upload_arquivo.sema +0 -93
  100. package/exemplos/webhook.sema +0 -96
  101. package/llms-full.txt +0 -34
  102. package/llms.txt +0 -17
  103. package/node_modules/@sema/gerador-css/dist/index.d.ts +0 -3
  104. package/node_modules/@sema/gerador-css/dist/index.js +0 -592
  105. package/node_modules/@sema/gerador-css/dist/index.js.map +0 -1
  106. package/node_modules/@sema/gerador-css/package.json +0 -7
  107. package/node_modules/@sema/gerador-dart/dist/index.d.ts +0 -3
  108. package/node_modules/@sema/gerador-dart/dist/index.js +0 -44
  109. package/node_modules/@sema/gerador-dart/dist/index.js.map +0 -1
  110. package/node_modules/@sema/gerador-dart/package.json +0 -7
  111. package/node_modules/@sema/gerador-html/dist/index.d.ts +0 -3
  112. package/node_modules/@sema/gerador-html/dist/index.js +0 -163
  113. package/node_modules/@sema/gerador-html/dist/index.js.map +0 -1
  114. package/node_modules/@sema/gerador-html/package.json +0 -7
  115. package/node_modules/@sema/gerador-javascript/dist/index.d.ts +0 -3
  116. package/node_modules/@sema/gerador-javascript/dist/index.js +0 -421
  117. package/node_modules/@sema/gerador-javascript/dist/index.js.map +0 -1
  118. package/node_modules/@sema/gerador-javascript/package.json +0 -7
  119. package/node_modules/@sema/gerador-lua/dist/index.d.ts +0 -3
  120. package/node_modules/@sema/gerador-lua/dist/index.js +0 -328
  121. package/node_modules/@sema/gerador-lua/dist/index.js.map +0 -1
  122. package/node_modules/@sema/gerador-lua/package.json +0 -7
  123. package/node_modules/@sema/gerador-python/dist/index.d.ts +0 -6
  124. package/node_modules/@sema/gerador-python/dist/index.js +0 -729
  125. package/node_modules/@sema/gerador-python/dist/index.js.map +0 -1
  126. package/node_modules/@sema/gerador-python/package.json +0 -7
  127. package/node_modules/@sema/gerador-typescript/dist/index.d.ts +0 -6
  128. package/node_modules/@sema/gerador-typescript/dist/index.js +0 -793
  129. package/node_modules/@sema/gerador-typescript/dist/index.js.map +0 -1
  130. package/node_modules/@sema/gerador-typescript/package.json +0 -7
  131. package/node_modules/@sema/nucleo/dist/ast/tipos.d.ts +0 -123
  132. package/node_modules/@sema/nucleo/dist/ast/tipos.js +0 -2
  133. package/node_modules/@sema/nucleo/dist/ast/tipos.js.map +0 -1
  134. package/node_modules/@sema/nucleo/dist/diagnosticos/index.d.ts +0 -21
  135. package/node_modules/@sema/nucleo/dist/diagnosticos/index.js +0 -12
  136. package/node_modules/@sema/nucleo/dist/diagnosticos/index.js.map +0 -1
  137. package/node_modules/@sema/nucleo/dist/formatador/index.d.ts +0 -9
  138. package/node_modules/@sema/nucleo/dist/formatador/index.js +0 -460
  139. package/node_modules/@sema/nucleo/dist/formatador/index.js.map +0 -1
  140. package/node_modules/@sema/nucleo/dist/index.d.ts +0 -35
  141. package/node_modules/@sema/nucleo/dist/index.js +0 -96
  142. package/node_modules/@sema/nucleo/dist/index.js.map +0 -1
  143. package/node_modules/@sema/nucleo/dist/ir/conversor.d.ts +0 -5
  144. package/node_modules/@sema/nucleo/dist/ir/conversor.js +0 -883
  145. package/node_modules/@sema/nucleo/dist/ir/conversor.js.map +0 -1
  146. package/node_modules/@sema/nucleo/dist/ir/modelos.d.ts +0 -345
  147. package/node_modules/@sema/nucleo/dist/ir/modelos.js +0 -2
  148. package/node_modules/@sema/nucleo/dist/ir/modelos.js.map +0 -1
  149. package/node_modules/@sema/nucleo/dist/lexer/lexer.d.ts +0 -7
  150. package/node_modules/@sema/nucleo/dist/lexer/lexer.js +0 -122
  151. package/node_modules/@sema/nucleo/dist/lexer/lexer.js.map +0 -1
  152. package/node_modules/@sema/nucleo/dist/lexer/tokens.d.ts +0 -8
  153. package/node_modules/@sema/nucleo/dist/lexer/tokens.js +0 -61
  154. package/node_modules/@sema/nucleo/dist/lexer/tokens.js.map +0 -1
  155. package/node_modules/@sema/nucleo/dist/parser/parser.d.ts +0 -9
  156. package/node_modules/@sema/nucleo/dist/parser/parser.js +0 -771
  157. package/node_modules/@sema/nucleo/dist/parser/parser.js.map +0 -1
  158. package/node_modules/@sema/nucleo/dist/persistencia/contratos.d.ts +0 -39
  159. package/node_modules/@sema/nucleo/dist/persistencia/contratos.js +0 -294
  160. package/node_modules/@sema/nucleo/dist/persistencia/contratos.js.map +0 -1
  161. package/node_modules/@sema/nucleo/dist/semantico/analisador.d.ts +0 -57
  162. package/node_modules/@sema/nucleo/dist/semantico/analisador.js +0 -1617
  163. package/node_modules/@sema/nucleo/dist/semantico/analisador.js.map +0 -1
  164. package/node_modules/@sema/nucleo/dist/semantico/estruturas.d.ts +0 -104
  165. package/node_modules/@sema/nucleo/dist/semantico/estruturas.js +0 -445
  166. package/node_modules/@sema/nucleo/dist/semantico/estruturas.js.map +0 -1
  167. package/node_modules/@sema/nucleo/dist/semantico/seguranca.d.ts +0 -91
  168. package/node_modules/@sema/nucleo/dist/semantico/seguranca.js +0 -258
  169. package/node_modules/@sema/nucleo/dist/semantico/seguranca.js.map +0 -1
  170. package/node_modules/@sema/nucleo/dist/util/arquivos.d.ts +0 -2
  171. package/node_modules/@sema/nucleo/dist/util/arquivos.js +0 -25
  172. package/node_modules/@sema/nucleo/dist/util/arquivos.js.map +0 -1
  173. package/node_modules/@sema/nucleo/package.json +0 -7
  174. package/node_modules/@sema/padroes/dist/index.d.ts +0 -25
  175. package/node_modules/@sema/padroes/dist/index.js +0 -316
  176. package/node_modules/@sema/padroes/dist/index.js.map +0 -1
  177. package/node_modules/@sema/padroes/package.json +0 -7
@@ -0,0 +1,3891 @@
1
+ import { access, readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import ts from "typescript";
4
+ import { compilarProjeto, formatarCodigo, temErros, type Diagnostico } from "@sema/nucleo";
5
+ import { normalizarSegmentoModulo } from "@sema/padroes";
6
+ import { extrairSimbolosCpp } from "./cpp-symbols.js";
7
+ import { extrairRotasDotnet, extrairSimbolosDotnet } from "./dotnet-http.js";
8
+ import { extrairRotasGo, extrairSimbolosGo } from "./go-http.js";
9
+ import { extrairRotasJava, extrairSimbolosJava } from "./java-http.js";
10
+ import { extrairSimbolosLua } from "./lua-symbols.js";
11
+ import { extrairRotasPhp, extrairSimbolosPhp } from "./php-symbols.js";
12
+ import { extrairParametrosCaminhoFlask, extrairRotasFlaskDecoradas } from "./python-http.js";
13
+ import { extrairRotasRust, extrairSimbolosRust } from "./rust-http.js";
14
+ import {
15
+ extrairRotasTypeScriptHttp,
16
+ inferirSemanticaHandlerTypeScriptHttp,
17
+ localizarExportacaoTypeScriptHttp,
18
+ type CampoInferidoTypeScriptHttp,
19
+ } from "./typescript-http.js";
20
+ import { coletarSuperficiesAngularStandaloneConsumer } from "./angular-consumer-standalone.js";
21
+
22
+ export type FonteImportacao =
23
+ | "nestjs"
24
+ | "fastapi"
25
+ | "flask"
26
+ | "nextjs"
27
+ | "nextjs-consumer"
28
+ | "react-vite-consumer"
29
+ | "angular-consumer"
30
+ | "flutter-consumer"
31
+ | "firebase"
32
+ | "typescript"
33
+ | "python"
34
+ | "dart"
35
+ | "lua"
36
+ | "php"
37
+ | "dotnet"
38
+ | "java"
39
+ | "go"
40
+ | "rust"
41
+ | "cpp";
42
+
43
+ type OrigemInteropImportada = "ts" | "py" | "dart" | "lua" | "php" | "cs" | "java" | "go" | "rust" | "cpp";
44
+
45
+ interface CampoImportado {
46
+ nome: string;
47
+ tipo: string;
48
+ obrigatorio: boolean;
49
+ }
50
+
51
+ interface ErroImportado {
52
+ nome: string;
53
+ mensagem: string;
54
+ }
55
+
56
+ interface EfeitoImportado {
57
+ categoria: "persistencia" | "consulta" | "evento" | "notificacao" | "auditoria";
58
+ alvo: string;
59
+ criticidade?: "baixa" | "media" | "alta";
60
+ }
61
+
62
+ interface VinculoImportado {
63
+ tipo: string;
64
+ valor: string;
65
+ }
66
+
67
+ interface EnumImportado {
68
+ nome: string;
69
+ valores: string[];
70
+ }
71
+
72
+ interface EntidadeImportada {
73
+ nome: string;
74
+ campos: CampoImportado[];
75
+ }
76
+
77
+ interface TarefaImportada {
78
+ nome: string;
79
+ resumo: string;
80
+ input: CampoImportado[];
81
+ output: CampoImportado[];
82
+ errors: ErroImportado[];
83
+ effects: EfeitoImportado[];
84
+ impl?: Partial<Record<OrigemInteropImportada, string>>;
85
+ vinculos?: VinculoImportado[];
86
+ origemArquivo: string;
87
+ origemSimbolo: string;
88
+ }
89
+
90
+ interface RotaImportada {
91
+ nome: string;
92
+ resumo: string;
93
+ metodo: string;
94
+ caminho: string;
95
+ task: string;
96
+ input: CampoImportado[];
97
+ output: CampoImportado[];
98
+ errors: ErroImportado[];
99
+ }
100
+
101
+ interface RecursoDatabaseImportado {
102
+ tipo: "table" | "query" | "collection" | "document" | "keyspace" | "stream";
103
+ nome: string;
104
+ mode?: "sql" | "documento" | "chave_valor" | "pipeline" | "stream";
105
+ table?: string;
106
+ collection?: string;
107
+ ttl?: string;
108
+ surface?: string;
109
+ }
110
+
111
+ interface DatabaseImportado {
112
+ nome: string;
113
+ resumo: string;
114
+ engine: "postgres" | "mysql" | "sqlite" | "mongodb" | "redis";
115
+ queryModel?: "sql" | "documento" | "chave_valor" | "pipeline" | "stream";
116
+ transactionModel?: "mvcc" | "bloqueio" | "documento" | "single_thread";
117
+ resources: RecursoDatabaseImportado[];
118
+ diagnostics?: string[];
119
+ }
120
+
121
+ interface ModuloImportado {
122
+ nome: string;
123
+ resumo: string;
124
+ enums: EnumImportado[];
125
+ entities: EntidadeImportada[];
126
+ tasks: TarefaImportada[];
127
+ routes: RotaImportada[];
128
+ databases?: DatabaseImportado[];
129
+ vinculos?: VinculoImportado[];
130
+ }
131
+
132
+ export interface ArquivoImportado {
133
+ caminhoRelativo: string;
134
+ conteudo: string;
135
+ modulo: string;
136
+ tarefas: number;
137
+ rotas: number;
138
+ entidades: number;
139
+ enums: number;
140
+ databases: number;
141
+ }
142
+
143
+ export interface ResultadoImportacao {
144
+ fonte: FonteImportacao;
145
+ diretorio: string;
146
+ namespaceBase: string;
147
+ arquivos: ArquivoImportado[];
148
+ diagnosticos: Diagnostico[];
149
+ }
150
+
151
+ interface TipoObjetoDescoberto {
152
+ tipo: "objeto";
153
+ nome: string;
154
+ campos: Array<{ nome: string; tipoTexto?: string; obrigatorio: boolean }>;
155
+ }
156
+
157
+ interface TipoEnumDescoberto {
158
+ tipo: "enum";
159
+ nome: string;
160
+ valores: string[];
161
+ }
162
+
163
+ type TipoDescoberto = TipoObjetoDescoberto | TipoEnumDescoberto;
164
+
165
+ interface ContextoTsArquivo {
166
+ sourceFile: ts.SourceFile;
167
+ texto: string;
168
+ relacao: string;
169
+ }
170
+
171
+ interface TipoPythonDescoberto {
172
+ tipo: "objeto" | "enum";
173
+ nome: string;
174
+ campos?: Array<{ nome: string; tipoTexto?: string; obrigatorio: boolean }>;
175
+ valores?: string[];
176
+ }
177
+
178
+ const DIRETORIOS_IGNORADOS = new Set([
179
+ ".git",
180
+ ".hg",
181
+ ".svn",
182
+ ".gradle",
183
+ ".cargo",
184
+ "node_modules",
185
+ "dist",
186
+ "build",
187
+ "bin",
188
+ "obj",
189
+ ".next",
190
+ ".nuxt",
191
+ ".dart_tool",
192
+ "__pycache__",
193
+ ".venv",
194
+ "venv",
195
+ "coverage",
196
+ ".tmp",
197
+ "generated",
198
+ "vendor",
199
+ "ephemeral",
200
+ ]);
201
+
202
+ const SUFIXOS_WRAPPER = ["Entrada", "Saida", "Dto", "Request", "Response", "Payload", "Body", "Input", "Output"];
203
+ const NOMES_RESERVADOS_CAMPO = new Set([
204
+ "module",
205
+ "use",
206
+ "type",
207
+ "entity",
208
+ "enum",
209
+ "task",
210
+ "input",
211
+ "output",
212
+ "rules",
213
+ "effects",
214
+ "impl",
215
+ "guarantees",
216
+ "state",
217
+ "flow",
218
+ "route",
219
+ "tests",
220
+ "error",
221
+ "docs",
222
+ "comments",
223
+ "fields",
224
+ "invariants",
225
+ "transitions",
226
+ "given",
227
+ "when",
228
+ "expect",
229
+ "caso",
230
+ "required",
231
+ ]);
232
+
233
+ function escaparTexto(texto: string): string {
234
+ return texto.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
235
+ }
236
+
237
+ function paraSnakeCase(valor: string): string {
238
+ return valor
239
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
240
+ .replace(/[^A-Za-z0-9_]+/g, "_")
241
+ .replace(/_{2,}/g, "_")
242
+ .replace(/^_+|_+$/g, "")
243
+ .toLowerCase();
244
+ }
245
+
246
+ function paraIdentificadorModulo(valor: string): string {
247
+ return normalizarSegmentoModulo(valor).replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
248
+ }
249
+
250
+ function normalizarNomeCampoImportado(valor: string): string {
251
+ const normalizado = paraSnakeCase(valor);
252
+ return NOMES_RESERVADOS_CAMPO.has(normalizado)
253
+ ? `${normalizado}_campo`
254
+ : normalizado;
255
+ }
256
+
257
+ function nomeProjetoPadrao(diretorio: string): string {
258
+ const base = path.basename(diretorio);
259
+ if (["src", "app", "api", "backend", "server"].includes(base.toLowerCase())) {
260
+ return `${path.basename(path.dirname(diretorio))}.${base}`;
261
+ }
262
+ return base;
263
+ }
264
+
265
+ export function inferirNamespaceBase(diretorio: string, namespaceExplicito?: string): string {
266
+ if (namespaceExplicito) {
267
+ return namespaceExplicito
268
+ .split(".")
269
+ .map((segmento) => paraIdentificadorModulo(segmento))
270
+ .filter(Boolean)
271
+ .join(".");
272
+ }
273
+
274
+ return ["legado", ...nomeProjetoPadrao(diretorio).split(/[\\/._-]+/g)]
275
+ .map((segmento) => paraIdentificadorModulo(segmento))
276
+ .filter(Boolean)
277
+ .join(".");
278
+ }
279
+
280
+ async function listarArquivosRecursivos(
281
+ diretorio: string,
282
+ extensoes: string[],
283
+ ): Promise<string[]> {
284
+ const entradas = await readdir(diretorio, { withFileTypes: true });
285
+ const encontrados: string[] = [];
286
+
287
+ for (const entrada of entradas) {
288
+ if (DIRETORIOS_IGNORADOS.has(entrada.name)) {
289
+ continue;
290
+ }
291
+
292
+ const caminhoAtual = path.join(diretorio, entrada.name);
293
+ if (entrada.isDirectory()) {
294
+ encontrados.push(...await listarArquivosRecursivos(caminhoAtual, extensoes));
295
+ continue;
296
+ }
297
+
298
+ if (extensoes.some((extensao) => entrada.name.endsWith(extensao))) {
299
+ encontrados.push(caminhoAtual);
300
+ }
301
+ }
302
+
303
+ return encontrados.sort((a, b) => a.localeCompare(b, "pt-BR"));
304
+ }
305
+
306
+ function inferirContextoPorArquivo(
307
+ relacao: string,
308
+ opcoes?: { preservarUltimo?: boolean; snakeCaseUltimo?: boolean },
309
+ ): string[] {
310
+ const semExtensao = relacao.replace(/\.[^.]+$/, "");
311
+ const segmentosOriginais = semExtensao.split(path.sep).filter(Boolean);
312
+ const segmentos = segmentosOriginais.map((segmento) => paraIdentificadorModulo(segmento)).filter(Boolean);
313
+ if (segmentos[0] === "src" || segmentos[0] === "app") {
314
+ segmentos.shift();
315
+ segmentosOriginais.shift();
316
+ }
317
+
318
+ const ultimo = segmentos.at(-1) ?? "";
319
+ const semSufixo = ultimo
320
+ .replace(/(\.controller|\.service|\.module|_router|_service|_schemas|_contract)$/g, "")
321
+ .replace(/(controller|service|module|router|schemas|contract)$/g, "")
322
+ .replace(/_+$/g, "");
323
+
324
+ if (segmentos.length === 0) {
325
+ return ["importado"];
326
+ }
327
+
328
+ if (!opcoes?.preservarUltimo && semSufixo && semSufixo !== ultimo) {
329
+ segmentos[segmentos.length - 1] = semSufixo;
330
+ }
331
+
332
+ if (opcoes?.snakeCaseUltimo && segmentos.length > 0) {
333
+ segmentos[segmentos.length - 1] = paraSnakeCase(segmentosOriginais[segmentosOriginais.length - 1] ?? ultimo);
334
+ }
335
+
336
+ if (segmentos.length > 1 && segmentos[segmentos.length - 1] === segmentos[segmentos.length - 2]) {
337
+ segmentos.pop();
338
+ }
339
+
340
+ return segmentos;
341
+ }
342
+
343
+ function juntarCaminhoHttp(base: string | undefined, sufixo: string | undefined): string {
344
+ const partes = [base ?? "", sufixo ?? ""]
345
+ .map((item) => item.trim())
346
+ .filter(Boolean)
347
+ .map((item) => item.replace(/^\/+|\/+$/g, ""));
348
+
349
+ const caminho = `/${partes.join("/")}`.replace(/\/+/g, "/");
350
+ return caminho === "//" ? "/" : caminho;
351
+ }
352
+
353
+ function extrairTextoLiteral(expr?: ts.Expression): string | undefined {
354
+ if (!expr) {
355
+ return undefined;
356
+ }
357
+ if (ts.isStringLiteralLike(expr) || ts.isNoSubstitutionTemplateLiteral(expr)) {
358
+ return expr.text;
359
+ }
360
+ if (ts.isNumericLiteral(expr)) {
361
+ return expr.text;
362
+ }
363
+ return undefined;
364
+ }
365
+
366
+ function listarDecoradores(node: ts.Node): readonly ts.Decorator[] {
367
+ return ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : [];
368
+ }
369
+
370
+ function lerDecorator(node: ts.Node, nomes: string[]): { nome: string; argumentos: ts.NodeArray<ts.Expression> } | undefined {
371
+ for (const decorator of listarDecoradores(node)) {
372
+ const expressao = decorator.expression;
373
+ if (ts.isCallExpression(expressao)) {
374
+ const alvo = expressao.expression;
375
+ if (ts.isIdentifier(alvo) && nomes.includes(alvo.text)) {
376
+ return { nome: alvo.text, argumentos: expressao.arguments };
377
+ }
378
+ } else if (ts.isIdentifier(expressao) && nomes.includes(expressao.text)) {
379
+ return { nome: expressao.text, argumentos: ts.factory.createNodeArray() };
380
+ }
381
+ }
382
+ return undefined;
383
+ }
384
+
385
+ function mapearTipoPrimitivo(tipo: string): string {
386
+ const limpo = tipo.trim().replace(/\s+/g, "");
387
+ const base = limpo
388
+ .replace(/^Promise<(.*)>$/, "$1")
389
+ .replace(/^Future<(.*)>$/, "$1")
390
+ .replace(/\|undefined/g, "")
391
+ .replace(/\|null/g, "")
392
+ .replace(/\bundefined\|/g, "")
393
+ .replace(/\bnull\|/g, "")
394
+ .trim();
395
+
396
+ const minusculo = base.toLowerCase();
397
+ if (minusculo === "string" || minusculo === "texto") {
398
+ return "Texto";
399
+ }
400
+ if (minusculo === "number" || minusculo === "float" || minusculo === "double") {
401
+ return "Decimal";
402
+ }
403
+ if (minusculo === "int" || minusculo === "integer" || minusculo === "inteiro") {
404
+ return "Inteiro";
405
+ }
406
+ if (minusculo === "boolean" || minusculo === "bool") {
407
+ return "Booleano";
408
+ }
409
+ if (minusculo === "date") {
410
+ return "Data";
411
+ }
412
+ if (minusculo === "datetime" || minusculo === "timestamp") {
413
+ return "DataHora";
414
+ }
415
+ if (minusculo === "id" || minusculo.endsWith("id")) {
416
+ return "Id";
417
+ }
418
+ if (
419
+ minusculo.includes("[]")
420
+ || minusculo.startsWith("array<")
421
+ || minusculo.startsWith("record<")
422
+ || minusculo.startsWith("map<")
423
+ || minusculo.startsWith("list<")
424
+ || minusculo.startsWith("list[")
425
+ || minusculo.startsWith("dict[")
426
+ ) {
427
+ return "Json";
428
+ }
429
+ if (
430
+ minusculo === "json"
431
+ || minusculo === "object"
432
+ || minusculo === "unknown"
433
+ || minusculo === "any"
434
+ || minusculo === "dynamic"
435
+ || minusculo === "void"
436
+ || minusculo === "none"
437
+ ) {
438
+ return minusculo === "void" || minusculo === "none" ? "Vazio" : "Json";
439
+ }
440
+ return tipo.trim();
441
+ }
442
+
443
+ function limparTipoBackend(tipo: string | undefined): string | undefined {
444
+ if (!tipo) {
445
+ return undefined;
446
+ }
447
+ return tipo
448
+ .trim()
449
+ .replace(/^\?/, "")
450
+ .replace(/^Task<(.+)>$/i, "$1")
451
+ .replace(/^ActionResult<(.+)>$/i, "$1")
452
+ .replace(/^IActionResult$/i, "Json")
453
+ .replace(/^Results<(.+)>$/i, "$1")
454
+ .replace(/^ResponseEntity<(.+)>$/i, "$1")
455
+ .replace(/^Optional<(.+)>$/i, "$1")
456
+ .replace(/^Result<(.+)>$/i, "$1")
457
+ .replace(/^impl\s+IntoResponse$/i, "Json")
458
+ .replace(/^Json<(.+)>$/i, "$1")
459
+ .replace(/^Option<(.+)>$/i, "$1")
460
+ .replace(/^Vec<(.+)>$/i, "Json")
461
+ .replace(/^List<(.+)>$/i, "Json")
462
+ .replace(/^Map<(.+)>$/i, "Json")
463
+ .replace(/^Dictionary<(.+)>$/i, "Json")
464
+ .replace(/\|null$/i, "")
465
+ .replace(/^null\|/i, "")
466
+ .replace(/^(array|mixed|object)$/i, "Json");
467
+ }
468
+
469
+ function mapearTipoBackendParaSema(tipo: string | undefined): string {
470
+ const limpo = limparTipoBackend(tipo);
471
+ if (!limpo) {
472
+ return "Json";
473
+ }
474
+ const basico = mapearTipoPrimitivo(limpo);
475
+ if (basico !== limpo) {
476
+ return basico;
477
+ }
478
+ if (/\[\]$/.test(limpo) || /^(IEnumerable|IReadOnlyList|List|Vec|HashMap|Map|Dictionary)</.test(limpo)) {
479
+ return "Json";
480
+ }
481
+ if (/^(void|unit|\(\)|nil)$/i.test(limpo)) {
482
+ return "Vazio";
483
+ }
484
+ if (/\b(uuid|guid)\b/i.test(limpo)) {
485
+ return "Id";
486
+ }
487
+ return "Json";
488
+ }
489
+
490
+ function criarCampoResultadoBackend(tipo: string | undefined): CampoImportado[] {
491
+ const tipoSema = mapearTipoBackendParaSema(tipo);
492
+ return tipoSema === "Vazio"
493
+ ? []
494
+ : [{ nome: "resultado", tipo: tipoSema, obrigatorio: false }];
495
+ }
496
+
497
+ function camposDeParametrosRotaBackend(
498
+ parametros: Array<{ nome: string; tipoSema: "Texto" | "Inteiro" | "Decimal" | "Id" }>,
499
+ ): CampoImportado[] {
500
+ return parametros.map((parametro) => ({
501
+ nome: normalizarNomeCampoImportado(parametro.nome),
502
+ tipo: parametro.tipoSema,
503
+ obrigatorio: true,
504
+ }));
505
+ }
506
+
507
+ function pareceWrapperTipo(nome: string): boolean {
508
+ return SUFIXOS_WRAPPER.some((sufixo) => nome.endsWith(sufixo)) || /(entrada|saida|dto|request|response|payload|body|input|output)/i.test(nome);
509
+ }
510
+
511
+ function descreverEfeitosPorHeuristica(codigo: string): EfeitoImportado[] {
512
+ const texto = codigo.toLowerCase();
513
+ const efeitos: EfeitoImportado[] = [];
514
+
515
+ const adicionar = (categoria: EfeitoImportado["categoria"], alvo: string, criticidade?: EfeitoImportado["criticidade"]) => {
516
+ if (!efeitos.some((efeito) => efeito.categoria === categoria && efeito.alvo === alvo)) {
517
+ efeitos.push({ categoria, alvo, criticidade });
518
+ }
519
+ };
520
+
521
+ if (/(prisma\.|repository\.|\.create\(|\.update\(|\.delete\(|\.save\()/i.test(codigo)) {
522
+ adicionar("persistencia", "banco", "alta");
523
+ }
524
+ if (/(findmany|findunique|findfirst|\.find\(|\.select\(|\.query\(|\.get\()/i.test(texto)) {
525
+ adicionar("consulta", "dados", "media");
526
+ }
527
+ if (/(emit\(|publish\(|dispatch\(|eventbus|event_emitter)/i.test(texto)) {
528
+ adicionar("evento", "dominio", "media");
529
+ }
530
+ if (/(notify|notification|sendmessage|send_email|telegram|smtp|mail)/i.test(texto)) {
531
+ adicionar("notificacao", "usuarios", "media");
532
+ }
533
+ if (/(audit|logger|logging|log\.|trace|observability)/i.test(texto)) {
534
+ adicionar("auditoria", "operacao", "baixa");
535
+ }
536
+
537
+ return efeitos;
538
+ }
539
+
540
+ function deduplicarDatabases(databases: DatabaseImportado[]): DatabaseImportado[] {
541
+ const mapa = new Map<string, DatabaseImportado>();
542
+ for (const database of databases) {
543
+ const chave = `${database.engine}:${database.nome}`;
544
+ const existente = mapa.get(chave);
545
+ if (!existente) {
546
+ mapa.set(chave, {
547
+ ...database,
548
+ resources: [...database.resources],
549
+ diagnostics: [...(database.diagnostics ?? [])],
550
+ });
551
+ continue;
552
+ }
553
+
554
+ const recursos = new Map<string, RecursoDatabaseImportado>();
555
+ for (const recurso of [...existente.resources, ...database.resources]) {
556
+ recursos.set(`${recurso.tipo}:${recurso.nome}`, recurso);
557
+ }
558
+ existente.resources = [...recursos.values()];
559
+ existente.diagnostics = [...new Set([...(existente.diagnostics ?? []), ...(database.diagnostics ?? [])])];
560
+ }
561
+
562
+ return [...mapa.values()];
563
+ }
564
+
565
+ function inferirDatabasesPorHeuristica(codigo: string, relacao: string): DatabaseImportado[] {
566
+ const databases: DatabaseImportado[] = [];
567
+ const adicionar = (database: DatabaseImportado) => {
568
+ databases.push(database);
569
+ };
570
+
571
+ const texto = codigo.toLowerCase();
572
+
573
+ if (/(postgresql|postgres\b|node-postgres|pg\.pool|pgclient|typeorm.+postgres|sequelize.+postgres|provider\s*=\s*["']postgresql["'])/i.test(codigo)) {
574
+ adicionar({
575
+ nome: "principal_postgres",
576
+ resumo: `Persistencia PostgreSQL inferida automaticamente de ${relacao}.`,
577
+ engine: "postgres",
578
+ queryModel: "sql",
579
+ transactionModel: "mvcc",
580
+ resources: [
581
+ { tipo: "table", nome: "tabelas_relacionais", table: "legado_principal" },
582
+ { tipo: "query", nome: "consultas_sql", mode: "sql" },
583
+ ],
584
+ diagnostics: ["inferido_por_heuristica"],
585
+ });
586
+ }
587
+
588
+ if (/(mysql\b|mariadb|mysql2|typeorm.+mysql|sequelize.+mysql|provider\s*=\s*["']mysql["'])/i.test(codigo)) {
589
+ adicionar({
590
+ nome: "principal_mysql",
591
+ resumo: `Persistencia MySQL inferida automaticamente de ${relacao}.`,
592
+ engine: "mysql",
593
+ queryModel: "sql",
594
+ transactionModel: "bloqueio",
595
+ resources: [
596
+ { tipo: "table", nome: "tabelas_relacionais", table: "legado_principal" },
597
+ { tipo: "query", nome: "consultas_sql", mode: "sql" },
598
+ ],
599
+ diagnostics: ["inferido_por_heuristica"],
600
+ });
601
+ }
602
+
603
+ if (/(sqlite\b|better-sqlite3|provider\s*=\s*["']sqlite["'])/i.test(codigo)) {
604
+ adicionar({
605
+ nome: "principal_sqlite",
606
+ resumo: `Persistencia SQLite inferida automaticamente de ${relacao}.`,
607
+ engine: "sqlite",
608
+ queryModel: "sql",
609
+ transactionModel: "single_thread",
610
+ resources: [
611
+ { tipo: "table", nome: "tabelas_locais", table: "legado_local" },
612
+ { tipo: "query", nome: "consultas_locais", mode: "sql" },
613
+ ],
614
+ diagnostics: ["inferido_por_heuristica"],
615
+ });
616
+ }
617
+
618
+ if (/(mongodb|mongoose|mongo\.collection|mongoclient|prisma.+mongodb|provider\s*=\s*["']mongodb["'])/i.test(codigo)) {
619
+ adicionar({
620
+ nome: "principal_mongodb",
621
+ resumo: `Persistencia MongoDB inferida automaticamente de ${relacao}.`,
622
+ engine: "mongodb",
623
+ queryModel: "documento",
624
+ transactionModel: "documento",
625
+ resources: [
626
+ { tipo: "collection", nome: "colecoes_documentais", collection: "documentos" },
627
+ { tipo: "document", nome: "documentos_agregados", mode: /aggregate|\$match|\$group/i.test(codigo) ? "pipeline" : "documento" },
628
+ ],
629
+ diagnostics: ["inferido_por_heuristica"],
630
+ });
631
+ }
632
+
633
+ if (/(redis\b|ioredis|upstash|bullmq|xadd|xreadgroup)/i.test(codigo)) {
634
+ adicionar({
635
+ nome: "principal_redis",
636
+ resumo: `Persistencia Redis inferida automaticamente de ${relacao}.`,
637
+ engine: "redis",
638
+ queryModel: "chave_valor",
639
+ transactionModel: "single_thread",
640
+ resources: [
641
+ { tipo: "keyspace", nome: "estado_chaves", ttl: /\bttl\b|expire\(/i.test(codigo) ? "300s" : undefined },
642
+ { tipo: "stream", nome: "eventos_stream", surface: /bullmq|queue|worker/i.test(codigo) ? "fila" : "evento" },
643
+ ],
644
+ diagnostics: ["inferido_por_heuristica"],
645
+ });
646
+ }
647
+
648
+ return deduplicarDatabases(databases);
649
+ }
650
+
651
+ function normalizarNomeErroBruto(nome: string): string {
652
+ return paraSnakeCase(nome.replace(/(Error|Erro|Exception)$/i, "")) || "erro_importado";
653
+ }
654
+
655
+ function extrairErrosTs(node: ts.Node, sourceFile: ts.SourceFile): ErroImportado[] {
656
+ const encontrados = new Map<string, string>();
657
+
658
+ const visitar = (atual: ts.Node): void => {
659
+ if (ts.isThrowStatement(atual) && atual.expression) {
660
+ const expressao = atual.expression;
661
+ if (ts.isNewExpression(expressao)) {
662
+ const nomeErro = expressao.expression.getText(sourceFile);
663
+ const mensagem = extrairTextoLiteral(expressao.arguments?.[0]) ?? `Erro importado automaticamente de ${nomeErro}.`;
664
+ encontrados.set(normalizarNomeErroBruto(nomeErro), mensagem);
665
+ }
666
+ }
667
+ atual.forEachChild(visitar);
668
+ };
669
+
670
+ node.forEachChild(visitar);
671
+ return [...encontrados.entries()].map(([nome, mensagem]) => ({ nome, mensagem }));
672
+ }
673
+
674
+ function extrairTiposTs(sourceFile: ts.SourceFile): Map<string, TipoDescoberto> {
675
+ const tipos = new Map<string, TipoDescoberto>();
676
+
677
+ const adicionarObjeto = (nome: string, campos: Array<{ nome: string; tipoTexto?: string; obrigatorio: boolean }>) => {
678
+ if (!campos.length || tipos.has(nome)) {
679
+ return;
680
+ }
681
+ tipos.set(nome, { tipo: "objeto", nome, campos });
682
+ };
683
+
684
+ const adicionarEnum = (nome: string, valores: string[]) => {
685
+ if (!valores.length || tipos.has(nome)) {
686
+ return;
687
+ }
688
+ tipos.set(nome, { tipo: "enum", nome, valores });
689
+ };
690
+
691
+ sourceFile.forEachChild((node) => {
692
+ if (ts.isInterfaceDeclaration(node) && node.name) {
693
+ const campos = node.members
694
+ .filter(ts.isPropertySignature)
695
+ .map((member) => ({
696
+ nome: member.name.getText(sourceFile),
697
+ tipoTexto: member.type?.getText(sourceFile),
698
+ obrigatorio: !member.questionToken,
699
+ }));
700
+ adicionarObjeto(node.name.text, campos);
701
+ return;
702
+ }
703
+
704
+ if (ts.isClassDeclaration(node) && node.name) {
705
+ const campos = node.members
706
+ .filter((member): member is ts.PropertyDeclaration => ts.isPropertyDeclaration(member) && Boolean(member.name))
707
+ .map((member) => ({
708
+ nome: member.name.getText(sourceFile),
709
+ tipoTexto: member.type?.getText(sourceFile),
710
+ obrigatorio: !member.questionToken,
711
+ }));
712
+ adicionarObjeto(node.name.text, campos);
713
+ return;
714
+ }
715
+
716
+ if (ts.isTypeAliasDeclaration(node) && node.name) {
717
+ if (ts.isUnionTypeNode(node.type) && node.type.types.every((tipo) => ts.isLiteralTypeNode(tipo) && ts.isStringLiteralLike(tipo.literal))) {
718
+ adicionarEnum(
719
+ node.name.text,
720
+ node.type.types
721
+ .map((tipo) => ts.isLiteralTypeNode(tipo) && ts.isStringLiteralLike(tipo.literal) ? tipo.literal.text : undefined)
722
+ .filter((valor): valor is string => Boolean(valor)),
723
+ );
724
+ return;
725
+ }
726
+ if (ts.isTypeLiteralNode(node.type)) {
727
+ const campos = node.type.members
728
+ .filter(ts.isPropertySignature)
729
+ .map((member) => ({
730
+ nome: member.name.getText(sourceFile),
731
+ tipoTexto: member.type?.getText(sourceFile),
732
+ obrigatorio: !member.questionToken,
733
+ }));
734
+ adicionarObjeto(node.name.text, campos);
735
+ }
736
+ }
737
+ });
738
+
739
+ return tipos;
740
+ }
741
+
742
+ function mapearTipoTsParaSema(
743
+ tipoTexto: string | undefined,
744
+ tipos: Map<string, TipoDescoberto>,
745
+ entidadesReferenciadas: Set<string>,
746
+ enumsReferenciados: Set<string>,
747
+ ): string {
748
+ if (!tipoTexto) {
749
+ return "Json";
750
+ }
751
+
752
+ const basico = mapearTipoPrimitivo(tipoTexto);
753
+ if (basico !== tipoTexto.trim()) {
754
+ return basico;
755
+ }
756
+
757
+ const limpo = tipoTexto
758
+ .trim()
759
+ .replace(/^Promise<(.*)>$/, "$1")
760
+ .replace(/\| undefined/g, "")
761
+ .replace(/\| null/g, "")
762
+ .replace(/Readonly<(.+)>/, "$1")
763
+ .replace(/Partial<(.+)>/, "$1")
764
+ .trim();
765
+
766
+ if (tipos.has(limpo)) {
767
+ const encontrado = tipos.get(limpo)!;
768
+ if (encontrado.tipo === "enum") {
769
+ enumsReferenciados.add(encontrado.nome);
770
+ return encontrado.nome;
771
+ }
772
+
773
+ if (!pareceWrapperTipo(encontrado.nome)) {
774
+ entidadesReferenciadas.add(encontrado.nome);
775
+ return encontrado.nome;
776
+ }
777
+ }
778
+
779
+ return "Json";
780
+ }
781
+
782
+ function expandirCamposTs(
783
+ nomeParametro: string,
784
+ tipoTexto: string | undefined,
785
+ tipos: Map<string, TipoDescoberto>,
786
+ entidadesReferenciadas: Set<string>,
787
+ enumsReferenciados: Set<string>,
788
+ obrigatorio: boolean,
789
+ ): CampoImportado[] {
790
+ if (!tipoTexto) {
791
+ return [{ nome: normalizarNomeCampoImportado(nomeParametro), tipo: "Json", obrigatorio }];
792
+ }
793
+
794
+ const limpo = tipoTexto
795
+ .trim()
796
+ .replace(/^Promise<(.*)>$/, "$1")
797
+ .replace(/\| undefined/g, "")
798
+ .replace(/\| null/g, "")
799
+ .replace(/Readonly<(.+)>/, "$1")
800
+ .replace(/Partial<(.+)>/, "$1")
801
+ .trim();
802
+
803
+ const descoberto = tipos.get(limpo);
804
+ if (descoberto?.tipo === "objeto" && pareceWrapperTipo(descoberto.nome)) {
805
+ return descoberto.campos.map((campo) => ({
806
+ nome: normalizarNomeCampoImportado(campo.nome),
807
+ tipo: mapearTipoTsParaSema(campo.tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
808
+ obrigatorio: campo.obrigatorio,
809
+ }));
810
+ }
811
+
812
+ return [{
813
+ nome: normalizarNomeCampoImportado(nomeParametro),
814
+ tipo: mapearTipoTsParaSema(tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
815
+ obrigatorio,
816
+ }];
817
+ }
818
+
819
+ function criarEntidadesReferenciadas(
820
+ tipos: Map<string, TipoDescoberto>,
821
+ entidadesReferenciadas: Set<string>,
822
+ enumsReferenciados: Set<string>,
823
+ ): { entities: EntidadeImportada[]; enums: EnumImportado[] } {
824
+ const fila = [...entidadesReferenciadas];
825
+ const processadas = new Set<string>();
826
+ const entities: EntidadeImportada[] = [];
827
+
828
+ while (fila.length > 0) {
829
+ const nomeAtual = fila.shift()!;
830
+ if (processadas.has(nomeAtual)) {
831
+ continue;
832
+ }
833
+ processadas.add(nomeAtual);
834
+ const tipo = tipos.get(nomeAtual);
835
+ if (!tipo || tipo.tipo !== "objeto") {
836
+ continue;
837
+ }
838
+
839
+ const entidade: EntidadeImportada = {
840
+ nome: tipo.nome,
841
+ campos: tipo.campos.map((campo) => ({
842
+ nome: normalizarNomeCampoImportado(campo.nome),
843
+ tipo: mapearTipoTsParaSema(campo.tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
844
+ obrigatorio: campo.obrigatorio,
845
+ })),
846
+ };
847
+ entities.push(entidade);
848
+
849
+ for (const referenciado of entidadesReferenciadas) {
850
+ if (!processadas.has(referenciado) && !fila.includes(referenciado)) {
851
+ fila.push(referenciado);
852
+ }
853
+ }
854
+ }
855
+
856
+ const enums = [...enumsReferenciados]
857
+ .map((nome) => tipos.get(nome))
858
+ .filter((item): item is TipoEnumDescoberto => Boolean(item && item.tipo === "enum"))
859
+ .map((tipo) => ({
860
+ nome: tipo.nome,
861
+ valores: tipo.valores,
862
+ }))
863
+ .filter((enumItem, indice, lista) => lista.findIndex((item) => item.nome === enumItem.nome) === indice);
864
+
865
+ return { entities: deduplicarEntidades(entities), enums };
866
+ }
867
+
868
+ function caminhoImplTs(diretorioBase: string, arquivo: string, simbolo: string): string {
869
+ const relativo = path.relative(diretorioBase, arquivo).replace(/\.[^.]+$/, "");
870
+ const segmentos = relativo.split(path.sep).map((segmento) => paraIdentificadorModulo(segmento)).filter(Boolean);
871
+ return [...segmentos, simbolo].join(".");
872
+ }
873
+
874
+ function mapearCampoInferidoTypeScriptHttp(
875
+ campo: CampoInferidoTypeScriptHttp,
876
+ tipos: Map<string, TipoDescoberto>,
877
+ entidadesReferenciadas: Set<string>,
878
+ enumsReferenciados: Set<string>,
879
+ ): CampoImportado {
880
+ const tipoBasico = campo.tipoTexto ? mapearTipoPrimitivo(campo.tipoTexto) : "Json";
881
+ return {
882
+ nome: paraSnakeCase(campo.nome),
883
+ tipo: ["Texto", "Decimal", "Inteiro", "Booleano", "Data", "DataHora", "Id", "Json", "Vazio"].includes(tipoBasico)
884
+ ? tipoBasico
885
+ : campo.tipoTexto
886
+ ? mapearTipoTsParaSema(campo.tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados)
887
+ : "Json",
888
+ obrigatorio: campo.obrigatorio,
889
+ };
890
+ }
891
+
892
+ function camposDeSemanticaTypeScriptHttp(
893
+ campos: CampoInferidoTypeScriptHttp[],
894
+ tipos: Map<string, TipoDescoberto>,
895
+ entidadesReferenciadas: Set<string>,
896
+ enumsReferenciados: Set<string>,
897
+ ): CampoImportado[] {
898
+ return campos.map((campo) => mapearCampoInferidoTypeScriptHttp(campo, tipos, entidadesReferenciadas, enumsReferenciados));
899
+ }
900
+
901
+ function camposEstruturadosTypeScriptHttp(
902
+ nomeParametro: string,
903
+ tipoTexto: string | undefined,
904
+ tipos: Map<string, TipoDescoberto>,
905
+ entidadesReferenciadas: Set<string>,
906
+ enumsReferenciados: Set<string>,
907
+ ): CampoImportado[] {
908
+ const campos = expandirCamposTs(nomeParametro, tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados, false);
909
+ const nomeWrapper = normalizarNomeCampoImportado(nomeParametro);
910
+ if (campos.length === 1 && campos[0]?.nome === nomeWrapper && campos[0]?.tipo === "Json") {
911
+ return [];
912
+ }
913
+ return campos;
914
+ }
915
+
916
+ function errosPorStatusHttp(statuses: number[]): ErroImportado[] {
917
+ return [...new Set(statuses)].map((status) => {
918
+ switch (status) {
919
+ case 401:
920
+ return { nome: "nao_autorizado", mensagem: "Falha HTTP importada automaticamente com status 401." };
921
+ case 403:
922
+ return { nome: "acesso_negado", mensagem: "Falha HTTP importada automaticamente com status 403." };
923
+ case 404:
924
+ return { nome: "nao_encontrado", mensagem: "Falha HTTP importada automaticamente com status 404." };
925
+ case 409:
926
+ return { nome: "conflito", mensagem: "Falha HTTP importada automaticamente com status 409." };
927
+ case 422:
928
+ return { nome: "entrada_invalida", mensagem: "Falha HTTP importada automaticamente com status 422." };
929
+ case 500:
930
+ return { nome: "erro_interno", mensagem: "Falha HTTP importada automaticamente com status 500." };
931
+ default:
932
+ return { nome: `erro_http_${status}`, mensagem: `Falha HTTP importada automaticamente com status ${status}.` };
933
+ }
934
+ });
935
+ }
936
+
937
+ function resolverEscopoImportacaoNextJs(diretorioEntrada: string): { baseProjeto: string; diretorioEscopo: string } {
938
+ const resolvido = path.resolve(diretorioEntrada);
939
+ const partes = path.parse(resolvido);
940
+ const relativoSemRaiz = resolvido.slice(partes.root.length);
941
+ const segmentos = relativoSemRaiz.split(path.sep).filter(Boolean);
942
+ const procurarSequencia = (sequencia: string[]): number =>
943
+ segmentos.findIndex((segmento, indice) => sequencia.every((item, deslocamento) => segmentos[indice + deslocamento]?.toLowerCase() === item));
944
+ const montarBase = (indice: number) =>
945
+ indice <= 0
946
+ ? partes.root
947
+ : path.join(partes.root, ...segmentos.slice(0, indice));
948
+
949
+ const indiceSrcAppApi = procurarSequencia(["src", "app", "api"]);
950
+ if (indiceSrcAppApi >= 0) {
951
+ return {
952
+ baseProjeto: montarBase(indiceSrcAppApi),
953
+ diretorioEscopo: resolvido,
954
+ };
955
+ }
956
+
957
+ const indiceAppApi = procurarSequencia(["app", "api"]);
958
+ if (indiceAppApi >= 0) {
959
+ return {
960
+ baseProjeto: montarBase(indiceAppApi),
961
+ diretorioEscopo: resolvido,
962
+ };
963
+ }
964
+
965
+ const indiceSrcApp = procurarSequencia(["src", "app"]);
966
+ if (indiceSrcApp >= 0) {
967
+ return {
968
+ baseProjeto: montarBase(indiceSrcApp),
969
+ diretorioEscopo: resolvido,
970
+ };
971
+ }
972
+
973
+ const indiceApp = procurarSequencia(["app"]);
974
+ if (indiceApp >= 0) {
975
+ return {
976
+ baseProjeto: montarBase(indiceApp),
977
+ diretorioEscopo: resolvido,
978
+ };
979
+ }
980
+
981
+ return {
982
+ baseProjeto: resolvido,
983
+ diretorioEscopo: resolvido,
984
+ };
985
+ }
986
+
987
+ interface SuperficieConsumerImportada {
988
+ caminho: string;
989
+ arquivo: string;
990
+ tipoArquivo: string;
991
+ }
992
+
993
+ function normalizarCaminhoImportado(caminhoArquivo: string): string {
994
+ return caminhoArquivo.replace(/\\/g, "/");
995
+ }
996
+
997
+ function normalizarSegmentoRotaConsumer(segmento: string): string {
998
+ const opcionalCatchAll = segmento.match(/^\[\[\.\.\.([A-Za-z_]\w*)\]\]$/);
999
+ if (opcionalCatchAll) {
1000
+ return `{${opcionalCatchAll[1]}}`;
1001
+ }
1002
+ const catchAll = segmento.match(/^\[\.\.\.([A-Za-z_]\w*)\]$/);
1003
+ if (catchAll) {
1004
+ return `{${catchAll[1]}}`;
1005
+ }
1006
+ const dinamico = segmento.match(/^\[([A-Za-z_]\w*)\]$/);
1007
+ if (dinamico) {
1008
+ return `{${dinamico[1]}}`;
1009
+ }
1010
+ return segmento;
1011
+ }
1012
+
1013
+ function montarCaminhoRotaConsumer(partes: string[]): string {
1014
+ const filtradas = partes
1015
+ .filter((segmento) => segmento && segmento !== "index" && !/^\(.*\)$/.test(segmento) && !segmento.startsWith("@"))
1016
+ .map(normalizarSegmentoRotaConsumer);
1017
+ return filtradas.length > 0 ? `/${filtradas.join("/")}`.replace(/\/+/g, "/") : "/";
1018
+ }
1019
+
1020
+ function resolverEscopoImportacaoFrontendConsumer(diretorioEntrada: string): { baseProjeto: string; diretorioEscopo: string } {
1021
+ const resolvido = path.resolve(diretorioEntrada);
1022
+ const partes = path.parse(resolvido);
1023
+ const segmentos = resolvido.slice(partes.root.length).split(path.sep).filter(Boolean);
1024
+ const procurarSequencia = (sequencia: string[]) =>
1025
+ segmentos.findIndex((segmento, indice) => sequencia.every((item, deslocamento) => segmentos[indice + deslocamento]?.toLowerCase() === item));
1026
+ const montarBase = (indice: number) =>
1027
+ indice <= 0
1028
+ ? partes.root
1029
+ : path.join(partes.root, ...segmentos.slice(0, indice));
1030
+
1031
+ for (const sequencia of [
1032
+ ["src", "pages"],
1033
+ ["pages"],
1034
+ ["src", "app", "api"],
1035
+ ["app", "api"],
1036
+ ["src", "app"],
1037
+ ["app"],
1038
+ ["src", "lib"],
1039
+ ["lib"],
1040
+ ]) {
1041
+ const indice = procurarSequencia(sequencia);
1042
+ if (indice >= 0) {
1043
+ return {
1044
+ baseProjeto: montarBase(indice),
1045
+ diretorioEscopo: resolvido,
1046
+ };
1047
+ }
1048
+ }
1049
+
1050
+ return {
1051
+ baseProjeto: resolvido,
1052
+ diretorioEscopo: resolvido,
1053
+ };
1054
+ }
1055
+
1056
+ function arquivoEhBridgeNextJsConsumer(relacaoArquivo: string): boolean {
1057
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1058
+ return /(?:^|\/)(?:src\/)?lib\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|tsx|js|jsx)$/i.test(relacao);
1059
+ }
1060
+
1061
+ function arquivoEhBridgeReactViteConsumer(relacaoArquivo: string): boolean {
1062
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1063
+ return /(?:^|\/)(?:src\/)?lib\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|tsx|js|jsx)$/i.test(relacao);
1064
+ }
1065
+
1066
+ function arquivoEhBridgeAngularConsumer(relacaoArquivo: string): boolean {
1067
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1068
+ return /(?:^|\/)(?:src\/)?app\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|js)$/i.test(relacao);
1069
+ }
1070
+
1071
+ function arquivoEhSuperficieNextJsConsumer(relacaoArquivo: string): boolean {
1072
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1073
+ return /(?:^|\/)(?:src\/)?app\/(?:(?!api\/).)*?(?:page|layout|loading|error)\.(?:ts|tsx|js|jsx)$/i.test(relacao);
1074
+ }
1075
+
1076
+ function arquivoEhSuperficieReactViteConsumer(relacaoArquivo: string): boolean {
1077
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1078
+ return /^(?:src\/)?pages\/.+\.(?:ts|tsx|js|jsx)$/i.test(relacao)
1079
+ || /^(?:src\/)?App\.(?:ts|tsx|js|jsx)$/i.test(relacao);
1080
+ }
1081
+
1082
+ function arquivoEhRotasReactViteConsumer(relacaoArquivo: string, codigo?: string): boolean {
1083
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1084
+ return /(?:^|\/)(?:src\/)?(?:app\/)?(?:router|routes)\.(?:ts|tsx|js|jsx)$/i.test(relacao)
1085
+ || /from\s+["']react-router-dom["']|createBrowserRouter|RouterProvider|useRoutes\s*\(|<Routes\b|<Route\b/.test(codigo ?? "");
1086
+ }
1087
+
1088
+ function arquivoEhRotasAngularConsumer(relacaoArquivo: string): boolean {
1089
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1090
+ return /(?:^|\/)(?:src\/)?app(?:\/.+)?\/[^/]+\.routes\.(?:ts|js)$/i.test(relacao);
1091
+ }
1092
+
1093
+ function arquivoEhRotasAngularConsumerRaiz(relacaoArquivo: string): boolean {
1094
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1095
+ return /(?:^|\/)(?:src\/)?app\/[^/]+\.routes\.(?:ts|js)$/i.test(relacao);
1096
+ }
1097
+
1098
+ function arquivoEhBridgeFlutterConsumer(relacaoArquivo: string): boolean {
1099
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1100
+ return /(?:^|\/)(?:lib\/)?(?:sema_consumer_bridge|api\/sema_contract_bridge|sema\/.+)\.dart$/i.test(relacao);
1101
+ }
1102
+
1103
+ function arquivoEhSuperficieFlutterConsumer(relacaoArquivo: string): boolean {
1104
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1105
+ return /(?:^|\/)(?:lib\/)?(?:screens|pages)\/.+\.dart$/i.test(relacao)
1106
+ || /(?:^|\/)(?:lib\/)?main\.dart$/i.test(relacao);
1107
+ }
1108
+
1109
+ function arquivoEhRotasFlutterConsumer(relacaoArquivo: string, codigo?: string): boolean {
1110
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1111
+ return /(?:^|\/)(?:lib\/)?(?:router|app_router|routes)\.dart$/i.test(relacao)
1112
+ || /MaterialApp(?:\.router)?\s*\(|CupertinoApp(?:\.router)?\s*\(|GoRouter\s*\(/.test(codigo ?? "");
1113
+ }
1114
+
1115
+ function inferirCaminhoNextJsConsumer(relacaoArquivo: string): SuperficieConsumerImportada | undefined {
1116
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1117
+ const segmentos = relacao.split("/");
1118
+ const indiceSrcApp = segmentos.findIndex((segmento, indice) =>
1119
+ segmento === "src" && segmentos[indice + 1] === "app");
1120
+ const indiceApp = segmentos.findIndex((segmento) => segmento === "app");
1121
+ const inicioApp = indiceSrcApp >= 0 ? indiceSrcApp + 2 : indiceApp >= 0 ? indiceApp + 1 : -1;
1122
+ if (inicioApp < 0) {
1123
+ return undefined;
1124
+ }
1125
+
1126
+ const arquivoFinal = segmentos.at(-1) ?? "";
1127
+ const tipoArquivo = arquivoFinal.match(/^(page|layout|loading|error)\.(?:ts|tsx|js|jsx)$/)?.[1];
1128
+ if (!tipoArquivo) {
1129
+ return undefined;
1130
+ }
1131
+
1132
+ const caminhoAteArquivo = segmentos.slice(inicioApp, -1);
1133
+ if (caminhoAteArquivo[0] === "api") {
1134
+ return undefined;
1135
+ }
1136
+
1137
+ const partes = caminhoAteArquivo
1138
+ .filter((segmento) => segmento);
1139
+
1140
+ const caminho = montarCaminhoRotaConsumer(partes);
1141
+ return {
1142
+ caminho,
1143
+ arquivo: relacao,
1144
+ tipoArquivo,
1145
+ };
1146
+ }
1147
+
1148
+ function inferirCaminhoReactViteConsumer(relacaoArquivo: string): SuperficieConsumerImportada | undefined {
1149
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1150
+ if (!arquivoEhSuperficieReactViteConsumer(relacao)) {
1151
+ return undefined;
1152
+ }
1153
+
1154
+ if (/(?:^|\/)(?:src\/)?App\.(?:ts|tsx|js|jsx)$/i.test(relacao)) {
1155
+ return {
1156
+ caminho: "/",
1157
+ arquivo: relacao,
1158
+ tipoArquivo: "app",
1159
+ };
1160
+ }
1161
+
1162
+ const segmentos = relacao.split("/");
1163
+ const indiceSrcPages = segmentos.findIndex((segmento, indice) => segmento === "src" && segmentos[indice + 1] === "pages");
1164
+ const indicePages = segmentos.findIndex((segmento) => segmento === "pages");
1165
+ const inicioPages = indiceSrcPages >= 0 ? indiceSrcPages + 2 : indicePages >= 0 ? indicePages + 1 : -1;
1166
+ if (inicioPages < 0) {
1167
+ return undefined;
1168
+ }
1169
+
1170
+ const arquivoFinal = segmentos.at(-1) ?? "";
1171
+ const nomeBase = arquivoFinal.replace(/\.(?:ts|tsx|js|jsx)$/i, "");
1172
+ const caminho = montarCaminhoRotaConsumer([...segmentos.slice(inicioPages, -1), nomeBase]);
1173
+ return {
1174
+ caminho,
1175
+ arquivo: relacao,
1176
+ tipoArquivo: "page",
1177
+ };
1178
+ }
1179
+
1180
+ function inferirCaminhoFlutterConsumer(relacaoArquivo: string): SuperficieConsumerImportada | undefined {
1181
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
1182
+ if (!arquivoEhSuperficieFlutterConsumer(relacao)) {
1183
+ return undefined;
1184
+ }
1185
+
1186
+ if (/(?:^|\/)(?:lib\/)?main\.dart$/i.test(relacao)) {
1187
+ return {
1188
+ caminho: "/",
1189
+ arquivo: relacao,
1190
+ tipoArquivo: "app",
1191
+ };
1192
+ }
1193
+
1194
+ const segmentos = relacao.split("/");
1195
+ const indiceLibScreens = segmentos.findIndex((segmento, indice) => segmento === "lib" && ["screens", "pages"].includes(segmentos[indice + 1] ?? ""));
1196
+ const indiceScreens = segmentos.findIndex((segmento) => segmento === "screens" || segmento === "pages");
1197
+ const inicio = indiceLibScreens >= 0 ? indiceLibScreens + 2 : indiceScreens >= 0 ? indiceScreens + 1 : -1;
1198
+ if (inicio < 0) {
1199
+ return undefined;
1200
+ }
1201
+
1202
+ const arquivoFinal = segmentos.at(-1) ?? "";
1203
+ const nomeBase = arquivoFinal
1204
+ .replace(/\.(?:dart)$/i, "")
1205
+ .replace(/_(screen|page)$/i, "");
1206
+ return {
1207
+ caminho: montarCaminhoRotaConsumer([...segmentos.slice(inicio, -1), nomeBase]),
1208
+ arquivo: relacao,
1209
+ tipoArquivo: "screen",
1210
+ };
1211
+ }
1212
+
1213
+ interface RotaReactViteConsumerImportada {
1214
+ caminho: string;
1215
+ arquivoRotas: string;
1216
+ arquivoComponente?: string;
1217
+ }
1218
+
1219
+ interface RotaFlutterConsumerImportada {
1220
+ caminho: string;
1221
+ arquivoRotas: string;
1222
+ }
1223
+
1224
+ interface RotaAngularConsumerImportada {
1225
+ caminho: string;
1226
+ arquivoRotas: string;
1227
+ arquivoComponente?: string;
1228
+ arquivoRotasFilhas?: string;
1229
+ }
1230
+
1231
+ function normalizarRotaDeclaradaConsumer(caminhoCru: string, prefixo = "/"): string {
1232
+ const partesPrefixo = prefixo.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
1233
+ const partesCaminho = (caminhoCru ?? "").trim().replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
1234
+ return montarCaminhoRotaConsumer([...partesPrefixo, ...partesCaminho]);
1235
+ }
1236
+
1237
+ function resolverImportRelativoTypeScript(relacaoArquivoBase: string, especificador: string): string | undefined {
1238
+ if (!especificador.startsWith(".")) {
1239
+ return undefined;
1240
+ }
1241
+ const baseDir = path.posix.dirname(normalizarCaminhoImportado(relacaoArquivoBase));
1242
+ for (const sufixo of ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"]) {
1243
+ const candidato = path.posix.normalize(path.posix.join(baseDir, `${especificador}${sufixo}`));
1244
+ if (!/\.(?:ts|tsx|js|jsx)$/i.test(candidato)) {
1245
+ continue;
1246
+ }
1247
+ return candidato;
1248
+ }
1249
+ return undefined;
1250
+ }
1251
+
1252
+ function extrairImportsTypeScriptConsumer(relacaoArquivo: string, codigo: string): Map<string, string> {
1253
+ const imports = new Map<string, string>();
1254
+ for (const match of codigo.matchAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*["']([^"']+)["']/g)) {
1255
+ const moduloImportado = match[2];
1256
+ const relacaoImportada = resolverImportRelativoTypeScript(relacaoArquivo, moduloImportado);
1257
+ if (!relacaoImportada) {
1258
+ continue;
1259
+ }
1260
+ for (const bruto of match[1].split(",")) {
1261
+ const normalizado = bruto.trim();
1262
+ if (!normalizado) {
1263
+ continue;
1264
+ }
1265
+ const local = normalizado.split(/\s+as\s+/i).at(-1)?.trim();
1266
+ if (local) {
1267
+ imports.set(local, relacaoImportada);
1268
+ }
1269
+ }
1270
+ }
1271
+ for (const match of codigo.matchAll(/import\s+([A-Za-z_]\w*)\s+from\s*["']([^"']+)["']/g)) {
1272
+ const relacaoImportada = resolverImportRelativoTypeScript(relacaoArquivo, match[2]);
1273
+ const local = match[1]?.trim();
1274
+ if (relacaoImportada && local) {
1275
+ imports.set(local, relacaoImportada);
1276
+ }
1277
+ }
1278
+ return imports;
1279
+ }
1280
+
1281
+ function extrairRotasReactViteConsumer(relacaoArquivo: string, codigo: string): RotaReactViteConsumerImportada[] {
1282
+ const imports = extrairImportsTypeScriptConsumer(relacaoArquivo, codigo);
1283
+ const rotas = new Map<string, RotaReactViteConsumerImportada>();
1284
+ const registrar = (caminhoCru: string, componente?: string) => {
1285
+ const caminho = normalizarRotaDeclaradaConsumer(caminhoCru);
1286
+ const chave = `${caminho}:${normalizarCaminhoImportado(relacaoArquivo)}:${componente ?? "router"}`;
1287
+ rotas.set(chave, {
1288
+ caminho,
1289
+ arquivoRotas: normalizarCaminhoImportado(relacaoArquivo),
1290
+ arquivoComponente: componente ? imports.get(componente) : undefined,
1291
+ });
1292
+ };
1293
+
1294
+ for (const match of codigo.matchAll(/(?:path\s*:\s*["'`]([^"'`]*)["'`]|index\s*:\s*true)[\s\S]{0,260}?(?:element\s*:\s*<\s*([A-Za-z_]\w*)|Component\s*:\s*([A-Za-z_]\w*))/g)) {
1295
+ const caminhoCru = match[1] ?? "";
1296
+ const componente = match[2] ?? match[3];
1297
+ registrar(caminhoCru, componente);
1298
+ }
1299
+
1300
+ for (const match of codigo.matchAll(/<Route\b[^>]*?(?:path=["'`]([^"'`]*)["'`][^>]*?)?(index\b)?[^>]*?(?:element=\{\s*<\s*([A-Za-z_]\w*)|Component=\{\s*([A-Za-z_]\w*))/g)) {
1301
+ const caminhoCru = match[2] ? "" : (match[1] ?? "");
1302
+ const componente = match[3] ?? match[4];
1303
+ registrar(caminhoCru, componente);
1304
+ }
1305
+
1306
+ return [...rotas.values()];
1307
+ }
1308
+
1309
+ function extrairRotasAngularConsumerDiretas(
1310
+ relacaoArquivo: string,
1311
+ codigo: string,
1312
+ prefixo = "/",
1313
+ ): RotaAngularConsumerImportada[] {
1314
+ const imports = extrairImportsTypeScriptConsumer(relacaoArquivo, codigo);
1315
+ const rotas: RotaAngularConsumerImportada[] = [];
1316
+
1317
+ for (const match of codigo.matchAll(/path\s*:\s*["'`]([^"'`]*)["'`][\s\S]{0,320}?component\s*:\s*([A-Za-z_]\w*)/g)) {
1318
+ const caminhoCru = (match[1] ?? "").trim();
1319
+ const componente = match[2];
1320
+ rotas.push({
1321
+ caminho: normalizarRotaDeclaradaConsumer(caminhoCru, prefixo),
1322
+ arquivoRotas: normalizarCaminhoImportado(relacaoArquivo),
1323
+ arquivoComponente: imports.get(componente),
1324
+ });
1325
+ }
1326
+
1327
+ for (const match of codigo.matchAll(/path\s*:\s*["'`]([^"'`]*)["'`][\s\S]{0,320}?loadComponent\s*:\s*\(\s*\)\s*=>\s*import\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g)) {
1328
+ const caminhoCru = (match[1] ?? "").trim();
1329
+ const relacaoImportada = resolverImportRelativoTypeScript(relacaoArquivo, match[2] ?? "");
1330
+ rotas.push({
1331
+ caminho: normalizarRotaDeclaradaConsumer(caminhoCru, prefixo),
1332
+ arquivoRotas: normalizarCaminhoImportado(relacaoArquivo),
1333
+ arquivoComponente: relacaoImportada,
1334
+ });
1335
+ }
1336
+
1337
+ for (const match of codigo.matchAll(/path\s*:\s*["'`]([^"'`]*)["'`][\s\S]{0,360}?loadChildren\s*:\s*\(\s*\)\s*=>\s*import\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g)) {
1338
+ const caminhoCru = (match[1] ?? "").trim();
1339
+ const relacaoImportada = resolverImportRelativoTypeScript(relacaoArquivo, match[2] ?? "");
1340
+ rotas.push({
1341
+ caminho: normalizarRotaDeclaradaConsumer(caminhoCru, prefixo),
1342
+ arquivoRotas: normalizarCaminhoImportado(relacaoArquivo),
1343
+ arquivoRotasFilhas: relacaoImportada,
1344
+ });
1345
+ }
1346
+
1347
+ return rotas;
1348
+ }
1349
+
1350
+ async function extrairRotasAngularConsumer(
1351
+ baseProjeto: string,
1352
+ relacaoArquivo: string,
1353
+ prefixo = "/",
1354
+ visitados = new Set<string>(),
1355
+ ): Promise<RotaAngularConsumerImportada[]> {
1356
+ const relacaoNormalizada = normalizarCaminhoImportado(relacaoArquivo);
1357
+ if (visitados.has(relacaoNormalizada)) {
1358
+ return [];
1359
+ }
1360
+ visitados.add(relacaoNormalizada);
1361
+
1362
+ const caminhoAbsoluto = path.join(baseProjeto, relacaoNormalizada);
1363
+ let codigo = "";
1364
+ try {
1365
+ codigo = await readFile(caminhoAbsoluto, "utf8");
1366
+ } catch {
1367
+ return [];
1368
+ }
1369
+
1370
+ const diretas = extrairRotasAngularConsumerDiretas(relacaoNormalizada, codigo, prefixo);
1371
+ const agregadas = [...diretas];
1372
+ for (const rota of diretas) {
1373
+ if (!rota.arquivoRotasFilhas) {
1374
+ continue;
1375
+ }
1376
+ agregadas.push(...await extrairRotasAngularConsumer(baseProjeto, rota.arquivoRotasFilhas, rota.caminho, visitados));
1377
+ }
1378
+ return agregadas;
1379
+ }
1380
+
1381
+ function normalizarRotaDeclaradaFlutter(caminhoCru: string): string {
1382
+ return montarCaminhoRotaConsumer((caminhoCru ?? "").trim().replace(/^\/+|\/+$/g, "").split("/").filter(Boolean));
1383
+ }
1384
+
1385
+ function extrairRotasFlutterConsumer(relacaoArquivo: string, codigo: string): RotaFlutterConsumerImportada[] {
1386
+ const rotas = new Map<string, RotaFlutterConsumerImportada>();
1387
+ const registrar = (caminhoCru: string) => {
1388
+ const caminho = normalizarRotaDeclaradaFlutter(caminhoCru);
1389
+ rotas.set(`${caminho}:${normalizarCaminhoImportado(relacaoArquivo)}`, {
1390
+ caminho,
1391
+ arquivoRotas: normalizarCaminhoImportado(relacaoArquivo),
1392
+ });
1393
+ };
1394
+
1395
+ for (const match of codigo.matchAll(/GoRoute\s*\([\s\S]{0,220}?path\s*:\s*["'`]([^"'`]+)["'`]/g)) {
1396
+ registrar(match[1] ?? "");
1397
+ }
1398
+
1399
+ for (const match of codigo.matchAll(/["'`]([^"'`]+)["'`]\s*:\s*\([^)]*\)\s*=>/g)) {
1400
+ registrar(match[1] ?? "");
1401
+ }
1402
+
1403
+ if (/home\s*:\s*(?:const\s+)?[A-Za-z_]\w*\(/.test(codigo)) {
1404
+ registrar("/");
1405
+ }
1406
+
1407
+ return [...rotas.values()];
1408
+ }
1409
+
1410
+ async function carregarContextosBridgeConsumer(baseProjeto: string, arquivosBridge: string[]): Promise<ContextoTsArquivo[]> {
1411
+ return Promise.all(arquivosBridge.map(async (arquivo) => {
1412
+ const texto = await readFile(arquivo, "utf8");
1413
+ const scriptKind = arquivo.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
1414
+ return {
1415
+ sourceFile: ts.createSourceFile(arquivo, texto, ts.ScriptTarget.Latest, true, scriptKind),
1416
+ texto,
1417
+ relacao: path.relative(baseProjeto, arquivo),
1418
+ };
1419
+ }));
1420
+ }
1421
+
1422
+ function extrairTasksBridgeConsumer(
1423
+ baseProjeto: string,
1424
+ contextosBridge: ContextoTsArquivo[],
1425
+ ): { tasks: TarefaImportada[]; entities: EntidadeImportada[]; enums: EnumImportado[] } {
1426
+ const tiposGlobais = consolidarTiposTs(contextosBridge);
1427
+ const entitiesRef = new Set<string>();
1428
+ const enumsRef = new Set<string>();
1429
+ const tasks: TarefaImportada[] = [];
1430
+
1431
+ for (const contexto of contextosBridge) {
1432
+ contexto.sourceFile.forEachChild((node) => {
1433
+ if (ts.isFunctionDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
1434
+ const nome = node.name.text;
1435
+ const input = node.parameters.flatMap((parametro) =>
1436
+ expandirCamposTs(parametro.name.getText(contexto.sourceFile), parametro.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
1437
+ const output = node.type?.getText(contexto.sourceFile) && mapearTipoPrimitivo(node.type.getText(contexto.sourceFile)) === "Vazio"
1438
+ ? []
1439
+ : deduplicarCampos(expandirCamposTs("resultado", node.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
1440
+ tasks.push({
1441
+ nome: nomeTaskBridgeConsumer(nome),
1442
+ resumo: `Task consumer importada automaticamente de ${contexto.relacao}#${nome}.`,
1443
+ input: deduplicarCampos(input),
1444
+ output,
1445
+ errors: node.body ? extrairErrosTs(node.body, contexto.sourceFile) : [],
1446
+ effects: node.body ? descreverEfeitosPorHeuristica(node.body.getText(contexto.sourceFile)) : [],
1447
+ impl: { ts: caminhoImplTs(baseProjeto, path.join(baseProjeto, contexto.relacao), nome) },
1448
+ vinculos: deduplicarVinculos([
1449
+ { tipo: "arquivo", valor: normalizarCaminhoImportado(contexto.relacao) },
1450
+ { tipo: "simbolo", valor: caminhoImplTs(baseProjeto, path.join(baseProjeto, contexto.relacao), nome) },
1451
+ ]),
1452
+ origemArquivo: contexto.relacao,
1453
+ origemSimbolo: nome,
1454
+ });
1455
+ }
1456
+
1457
+ if (ts.isClassDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
1458
+ const nomeClasse = node.name.text;
1459
+ for (const member of node.members) {
1460
+ if (!ts.isMethodDeclaration(member) || !member.name || !member.body) {
1461
+ continue;
1462
+ }
1463
+ if (member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword)) {
1464
+ continue;
1465
+ }
1466
+ const nomeMetodo = member.name.getText(contexto.sourceFile);
1467
+ if (nomeMetodo === "constructor") {
1468
+ continue;
1469
+ }
1470
+ const input = member.parameters.flatMap((parametro) =>
1471
+ expandirCamposTs(parametro.name.getText(contexto.sourceFile), parametro.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
1472
+ const output = member.type?.getText(contexto.sourceFile) && mapearTipoPrimitivo(member.type.getText(contexto.sourceFile)) === "Vazio"
1473
+ ? []
1474
+ : deduplicarCampos(expandirCamposTs("resultado", member.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
1475
+ const caminhoSimbolo = caminhoImplTs(baseProjeto, path.join(baseProjeto, contexto.relacao), `${nomeClasse}.${nomeMetodo}`);
1476
+ tasks.push({
1477
+ nome: nomeTaskBridgeConsumer(nomeMetodo),
1478
+ resumo: `Task consumer importada automaticamente de ${contexto.relacao}#${nomeClasse}.${nomeMetodo}.`,
1479
+ input: deduplicarCampos(input),
1480
+ output,
1481
+ errors: extrairErrosTs(member.body, contexto.sourceFile),
1482
+ effects: descreverEfeitosPorHeuristica(member.body.getText(contexto.sourceFile)),
1483
+ impl: { ts: caminhoSimbolo },
1484
+ vinculos: deduplicarVinculos([
1485
+ { tipo: "arquivo", valor: normalizarCaminhoImportado(contexto.relacao) },
1486
+ { tipo: "simbolo", valor: caminhoSimbolo },
1487
+ ]),
1488
+ origemArquivo: contexto.relacao,
1489
+ origemSimbolo: `${nomeClasse}.${nomeMetodo}`,
1490
+ });
1491
+ }
1492
+ }
1493
+ });
1494
+ }
1495
+
1496
+ const { entities, enums } = criarEntidadesReferenciadas(tiposGlobais, entitiesRef, enumsRef);
1497
+ return {
1498
+ tasks,
1499
+ entities,
1500
+ enums,
1501
+ };
1502
+ }
1503
+
1504
+ function montarVinculosSuperficiesConsumer(superficies: SuperficieConsumerImportada[]): VinculoImportado[] {
1505
+ return deduplicarVinculos(superficies.flatMap((superficie) => [
1506
+ { tipo: "superficie", valor: superficie.caminho },
1507
+ { tipo: "arquivo", valor: normalizarCaminhoImportado(superficie.arquivo) },
1508
+ ]));
1509
+ }
1510
+
1511
+ async function importarConsumerBase(
1512
+ diretorio: string,
1513
+ namespaceBase: string,
1514
+ descricaoFramework: string,
1515
+ ehBridge: (relacaoArquivo: string) => boolean,
1516
+ coletarSuperficies: (baseProjeto: string, arquivos: string[]) => Promise<SuperficieConsumerImportada[]>,
1517
+ ): Promise<ModuloImportado[]> {
1518
+ const escopo = resolverEscopoImportacaoFrontendConsumer(diretorio);
1519
+ const arquivos = await listarArquivosRecursivos(escopo.baseProjeto, [".ts", ".tsx", ".js", ".jsx"]);
1520
+ const arquivosBridge = arquivos.filter((arquivo) => ehBridge(path.relative(escopo.baseProjeto, arquivo)));
1521
+ const contextosBridge = await carregarContextosBridgeConsumer(escopo.baseProjeto, arquivosBridge);
1522
+ const { tasks, entities, enums } = extrairTasksBridgeConsumer(escopo.baseProjeto, contextosBridge);
1523
+ const superficiesImportadas = await coletarSuperficies(escopo.baseProjeto, arquivos);
1524
+ const superficies = montarVinculosSuperficiesConsumer(superficiesImportadas);
1525
+
1526
+ if (tasks.length === 0 && superficies.length === 0) {
1527
+ return [];
1528
+ }
1529
+
1530
+ const nomeModulo = namespaceBase.endsWith(".consumer")
1531
+ ? namespaceBase
1532
+ : `${namespaceBase}.consumer`;
1533
+
1534
+ return [{
1535
+ nome: nomeModulo,
1536
+ resumo: `Rascunho Sema importado automaticamente do consumer ${descricaoFramework} em ${escopo.baseProjeto}.`,
1537
+ tasks: deduplicarTarefas(tasks),
1538
+ routes: [],
1539
+ entities,
1540
+ enums,
1541
+ vinculos: superficies,
1542
+ }];
1543
+ }
1544
+
1545
+ async function coletarSuperficiesNextJsConsumer(baseProjeto: string, arquivos: string[]): Promise<SuperficieConsumerImportada[]> {
1546
+ return arquivos
1547
+ .map((arquivo) => inferirCaminhoNextJsConsumer(path.relative(baseProjeto, arquivo)))
1548
+ .filter((item): item is SuperficieConsumerImportada => Boolean(item));
1549
+ }
1550
+
1551
+ async function coletarSuperficiesReactViteConsumer(baseProjeto: string, arquivos: string[]): Promise<SuperficieConsumerImportada[]> {
1552
+ const superficies: SuperficieConsumerImportada[] = [];
1553
+
1554
+ for (const arquivo of arquivos) {
1555
+ const relacao = path.relative(baseProjeto, arquivo);
1556
+ const codigo = await readFile(arquivo, "utf8");
1557
+ if (arquivoEhRotasReactViteConsumer(relacao, codigo)) {
1558
+ for (const rota of extrairRotasReactViteConsumer(relacao, codigo)) {
1559
+ superficies.push({
1560
+ caminho: rota.caminho,
1561
+ arquivo: rota.arquivoRotas,
1562
+ tipoArquivo: "router",
1563
+ });
1564
+ if (rota.arquivoComponente) {
1565
+ superficies.push({
1566
+ caminho: rota.caminho,
1567
+ arquivo: rota.arquivoComponente,
1568
+ tipoArquivo: "page",
1569
+ });
1570
+ }
1571
+ }
1572
+ }
1573
+ }
1574
+
1575
+ for (const arquivo of arquivos) {
1576
+ const superficie = inferirCaminhoReactViteConsumer(path.relative(baseProjeto, arquivo));
1577
+ if (superficie) {
1578
+ superficies.push(superficie);
1579
+ }
1580
+ }
1581
+
1582
+ return superficies;
1583
+ }
1584
+
1585
+ async function coletarSuperficiesAngularConsumer(baseProjeto: string, arquivos: string[]): Promise<SuperficieConsumerImportada[]> {
1586
+ const superficies: SuperficieConsumerImportada[] = [];
1587
+ const arquivosRotas = arquivos.filter((arquivo) => arquivoEhRotasAngularConsumer(path.relative(baseProjeto, arquivo)));
1588
+ const arquivosRaiz = arquivosRotas.filter((arquivo) => arquivoEhRotasAngularConsumerRaiz(path.relative(baseProjeto, arquivo)));
1589
+ const pontosEntrada = arquivosRaiz.length > 0 ? arquivosRaiz : arquivosRotas;
1590
+ for (const arquivoRotas of pontosEntrada) {
1591
+ const relacao = path.relative(baseProjeto, arquivoRotas);
1592
+ for (const rota of await extrairRotasAngularConsumer(baseProjeto, relacao)) {
1593
+ superficies.push({
1594
+ caminho: rota.caminho,
1595
+ arquivo: rota.arquivoRotas,
1596
+ tipoArquivo: "routes",
1597
+ });
1598
+ if (rota.arquivoComponente) {
1599
+ superficies.push({
1600
+ caminho: rota.caminho,
1601
+ arquivo: rota.arquivoComponente,
1602
+ tipoArquivo: "component",
1603
+ });
1604
+ }
1605
+ }
1606
+ }
1607
+ if (superficies.length > 0) {
1608
+ return superficies;
1609
+ }
1610
+ return (await coletarSuperficiesAngularStandaloneConsumer(baseProjeto, arquivos)).map((superficie) => ({
1611
+ caminho: superficie.rota,
1612
+ arquivo: superficie.arquivo,
1613
+ tipoArquivo: superficie.tipoArquivo,
1614
+ }));
1615
+ }
1616
+
1617
+ async function importarNextJsConsumerBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
1618
+ return importarConsumerBase(
1619
+ diretorio,
1620
+ namespaceBase,
1621
+ "Next.js",
1622
+ arquivoEhBridgeNextJsConsumer,
1623
+ coletarSuperficiesNextJsConsumer,
1624
+ );
1625
+ }
1626
+
1627
+ async function importarReactViteConsumerBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
1628
+ return importarConsumerBase(
1629
+ diretorio,
1630
+ namespaceBase,
1631
+ "React/Vite",
1632
+ arquivoEhBridgeReactViteConsumer,
1633
+ coletarSuperficiesReactViteConsumer,
1634
+ );
1635
+ }
1636
+
1637
+ async function importarAngularConsumerBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
1638
+ return importarConsumerBase(
1639
+ diretorio,
1640
+ namespaceBase,
1641
+ "Angular",
1642
+ arquivoEhBridgeAngularConsumer,
1643
+ coletarSuperficiesAngularConsumer,
1644
+ );
1645
+ }
1646
+
1647
+ async function extrairTasksBridgeFlutterConsumer(baseProjeto: string, arquivosBridge: string[]): Promise<TarefaImportada[]> {
1648
+ const tasks: TarefaImportada[] = [];
1649
+
1650
+ for (const arquivo of arquivosBridge) {
1651
+ const texto = await readFile(arquivo, "utf8");
1652
+ const relacao = path.relative(baseProjeto, arquivo);
1653
+
1654
+ for (const match of texto.matchAll(/(?:Future<([^\n]+)>|([\w?<>.,\s]+))\s+(\w+)\(([^)]*)\)\s*(?:async\s*)?\{/g)) {
1655
+ const retorno = (match[1] ?? match[2] ?? "").trim();
1656
+ const nome = match[3]!;
1657
+ if (["build", "toString", "hashCode"].includes(nome)) {
1658
+ continue;
1659
+ }
1660
+ const parametros = match[4]!;
1661
+ const input = parametros
1662
+ .split(",")
1663
+ .map((item) => item.trim())
1664
+ .filter(Boolean)
1665
+ .map((item) => item.replace(/^(required|final)\s+/g, ""))
1666
+ .map((item) => {
1667
+ const partes = item.split(/\s+/).filter(Boolean);
1668
+ const nomeParametro = partes.at(-1) ?? "arg";
1669
+ const tipoParametro = partes.slice(0, -1).join(" ");
1670
+ return {
1671
+ nome: paraSnakeCase(nomeParametro),
1672
+ tipo: mapearTipoPrimitivo(tipoParametro || "Json"),
1673
+ obrigatorio: !/\?/.test(tipoParametro),
1674
+ };
1675
+ });
1676
+ const caminhoSimbolo = caminhoImplDart(baseProjeto, arquivo, nome);
1677
+ tasks.push({
1678
+ nome: nomeTaskBridgeConsumer(nome),
1679
+ resumo: `Task consumer importada automaticamente de ${relacao}#${nome}.`,
1680
+ input,
1681
+ output: retorno && mapearTipoPrimitivo(retorno) === "Vazio"
1682
+ ? []
1683
+ : [{ nome: "resultado", tipo: mapearTipoPrimitivo(retorno || "Json"), obrigatorio: false }],
1684
+ errors: [],
1685
+ effects: descreverEfeitosPorHeuristica(texto),
1686
+ impl: { dart: caminhoSimbolo },
1687
+ vinculos: deduplicarVinculos([
1688
+ { tipo: "arquivo", valor: normalizarCaminhoImportado(relacao) },
1689
+ { tipo: "simbolo", valor: caminhoSimbolo },
1690
+ ]),
1691
+ origemArquivo: relacao,
1692
+ origemSimbolo: nome,
1693
+ });
1694
+ }
1695
+ }
1696
+
1697
+ return tasks;
1698
+ }
1699
+
1700
+ async function coletarSuperficiesFlutterConsumer(baseProjeto: string, arquivos: string[]): Promise<SuperficieConsumerImportada[]> {
1701
+ const superficies: SuperficieConsumerImportada[] = [];
1702
+
1703
+ for (const arquivo of arquivos) {
1704
+ const relacao = path.relative(baseProjeto, arquivo);
1705
+ const codigo = await readFile(arquivo, "utf8");
1706
+ if (arquivoEhRotasFlutterConsumer(relacao, codigo)) {
1707
+ for (const rota of extrairRotasFlutterConsumer(relacao, codigo)) {
1708
+ superficies.push({
1709
+ caminho: rota.caminho,
1710
+ arquivo: rota.arquivoRotas,
1711
+ tipoArquivo: "router",
1712
+ });
1713
+ }
1714
+ }
1715
+ }
1716
+
1717
+ for (const arquivo of arquivos) {
1718
+ const superficie = inferirCaminhoFlutterConsumer(path.relative(baseProjeto, arquivo));
1719
+ if (superficie) {
1720
+ superficies.push(superficie);
1721
+ }
1722
+ }
1723
+
1724
+ return superficies;
1725
+ }
1726
+
1727
+ async function importarFlutterConsumerBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
1728
+ const escopo = resolverEscopoImportacaoFrontendConsumer(diretorio);
1729
+ const arquivos = (await listarArquivosRecursivos(escopo.baseProjeto, [".dart"]))
1730
+ .filter((arquivo) => !arquivo.endsWith(".g.dart") && !arquivo.endsWith(".freezed.dart"));
1731
+ const arquivosBridge = arquivos.filter((arquivo) => arquivoEhBridgeFlutterConsumer(path.relative(escopo.baseProjeto, arquivo)));
1732
+ const tasks = await extrairTasksBridgeFlutterConsumer(escopo.baseProjeto, arquivosBridge);
1733
+ const superficiesImportadas = await coletarSuperficiesFlutterConsumer(escopo.baseProjeto, arquivos);
1734
+ const superficies = montarVinculosSuperficiesConsumer(superficiesImportadas);
1735
+
1736
+ if (tasks.length === 0 && superficies.length === 0) {
1737
+ return [];
1738
+ }
1739
+
1740
+ const nomeModulo = namespaceBase.endsWith(".consumer")
1741
+ ? namespaceBase
1742
+ : `${namespaceBase}.consumer`;
1743
+
1744
+ return [{
1745
+ nome: nomeModulo,
1746
+ resumo: `Rascunho Sema importado automaticamente do consumer Flutter em ${escopo.baseProjeto}.`,
1747
+ tasks: deduplicarTarefas(tasks),
1748
+ routes: [],
1749
+ entities: [],
1750
+ enums: [],
1751
+ vinculos: superficies,
1752
+ }];
1753
+ }
1754
+
1755
+ function nomeTaskBridgeConsumer(nome: string): string {
1756
+ return paraSnakeCase(nome.replace(/^sema/, "")) || paraSnakeCase(nome) || "task_consumer";
1757
+ }
1758
+
1759
+ function extrairChamadaServiceTs(node: ts.Node): string | undefined {
1760
+ let encontrado: string | undefined;
1761
+ const visitar = (atual: ts.Node): void => {
1762
+ if (encontrado) {
1763
+ return;
1764
+ }
1765
+ if (ts.isCallExpression(atual) && ts.isPropertyAccessExpression(atual.expression)) {
1766
+ const alvo = atual.expression.expression;
1767
+ if (ts.isPropertyAccessExpression(alvo) && alvo.expression.kind === ts.SyntaxKind.ThisKeyword && alvo.name.text.endsWith("Service")) {
1768
+ encontrado = atual.expression.name.text;
1769
+ return;
1770
+ }
1771
+ }
1772
+ atual.forEachChild(visitar);
1773
+ };
1774
+ node.forEachChild(visitar);
1775
+ return encontrado;
1776
+ }
1777
+
1778
+ function deduplicarCampos(campos: CampoImportado[]): CampoImportado[] {
1779
+ const mapa = new Map<string, CampoImportado>();
1780
+ for (const campo of campos) {
1781
+ if (!mapa.has(campo.nome)) {
1782
+ mapa.set(campo.nome, campo);
1783
+ }
1784
+ }
1785
+ return [...mapa.values()];
1786
+ }
1787
+
1788
+ function deduplicarErros(errors: ErroImportado[]): ErroImportado[] {
1789
+ const mapa = new Map<string, ErroImportado>();
1790
+ for (const erro of errors) {
1791
+ if (!mapa.has(erro.nome)) {
1792
+ mapa.set(erro.nome, erro);
1793
+ }
1794
+ }
1795
+ return [...mapa.values()];
1796
+ }
1797
+
1798
+ function deduplicarEfeitos(effects: EfeitoImportado[]): EfeitoImportado[] {
1799
+ const mapa = new Map<string, EfeitoImportado>();
1800
+ for (const effect of effects) {
1801
+ const chave = `${effect.categoria}:${effect.alvo}`;
1802
+ if (!mapa.has(chave)) {
1803
+ mapa.set(chave, effect);
1804
+ }
1805
+ }
1806
+ return [...mapa.values()];
1807
+ }
1808
+
1809
+ function deduplicarVinculos(vinculos: VinculoImportado[]): VinculoImportado[] {
1810
+ const mapa = new Map<string, VinculoImportado>();
1811
+ for (const vinculo of vinculos) {
1812
+ const chave = `${vinculo.tipo}:${vinculo.valor}`;
1813
+ if (!mapa.has(chave)) {
1814
+ mapa.set(chave, vinculo);
1815
+ }
1816
+ }
1817
+ return [...mapa.values()];
1818
+ }
1819
+
1820
+ function deduplicarEntidades(entities: EntidadeImportada[]): EntidadeImportada[] {
1821
+ const mapa = new Map<string, EntidadeImportada>();
1822
+ for (const entity of entities) {
1823
+ if (!mapa.has(entity.nome)) {
1824
+ mapa.set(entity.nome, entity);
1825
+ }
1826
+ }
1827
+ return [...mapa.values()];
1828
+ }
1829
+
1830
+ function deduplicarEnums(enums: EnumImportado[]): EnumImportado[] {
1831
+ const mapa = new Map<string, EnumImportado>();
1832
+ for (const enumItem of enums) {
1833
+ if (!mapa.has(enumItem.nome)) {
1834
+ mapa.set(enumItem.nome, enumItem);
1835
+ }
1836
+ }
1837
+ return [...mapa.values()];
1838
+ }
1839
+
1840
+ function deduplicarTarefas(tasks: TarefaImportada[]): TarefaImportada[] {
1841
+ const mapa = new Map<string, TarefaImportada>();
1842
+ for (const task of tasks) {
1843
+ if (!mapa.has(task.nome)) {
1844
+ mapa.set(task.nome, task);
1845
+ continue;
1846
+ }
1847
+ const existente = mapa.get(task.nome)!;
1848
+ existente.input = deduplicarCampos([...existente.input, ...task.input]);
1849
+ existente.output = deduplicarCampos([...existente.output, ...task.output]);
1850
+ existente.errors = deduplicarErros([...existente.errors, ...task.errors]);
1851
+ existente.effects = deduplicarEfeitos([...existente.effects, ...task.effects]);
1852
+ existente.vinculos = deduplicarVinculos([...(existente.vinculos ?? []), ...(task.vinculos ?? [])]);
1853
+ }
1854
+ return [...mapa.values()];
1855
+ }
1856
+
1857
+ function deduplicarRotas(routes: RotaImportada[]): RotaImportada[] {
1858
+ const mapa = new Map<string, RotaImportada>();
1859
+ for (const route of routes) {
1860
+ const chave = `${route.metodo}:${route.caminho}`;
1861
+ if (!mapa.has(chave)) {
1862
+ mapa.set(chave, route);
1863
+ }
1864
+ }
1865
+ return [...mapa.values()];
1866
+ }
1867
+
1868
+ function sincronizarRotasComTasks(routes: RotaImportada[], tasks: TarefaImportada[]): void {
1869
+ const mapaTasks = new Map(tasks.map((task) => [task.nome, task]));
1870
+ for (const route of routes) {
1871
+ const task = mapaTasks.get(route.task);
1872
+ if (!task) {
1873
+ continue;
1874
+ }
1875
+ if (route.output.length === 0) {
1876
+ route.output = task.output.map((campo) => ({ ...campo, obrigatorio: true }));
1877
+ }
1878
+ if (route.errors.length === 0) {
1879
+ route.errors = task.errors;
1880
+ }
1881
+ }
1882
+ }
1883
+
1884
+ function renderizarCampos(bloco: string, campos: CampoImportado[], indentacao = " ", sempre = false): string[] {
1885
+ if (campos.length === 0 && !sempre) {
1886
+ return [];
1887
+ }
1888
+ return [
1889
+ `${indentacao}${bloco} {`,
1890
+ ...campos.map((campo) => `${indentacao} ${normalizarNomeCampoImportado(campo.nome)}: ${campo.tipo}${campo.obrigatorio ? " required" : ""}`),
1891
+ `${indentacao}}`,
1892
+ "",
1893
+ ];
1894
+ }
1895
+
1896
+ function renderizarErrors(erros: ErroImportado[], indentacao = " "): string[] {
1897
+ if (erros.length === 0) {
1898
+ return [];
1899
+ }
1900
+ return [
1901
+ `${indentacao}error {`,
1902
+ ...erros.map((erro) => `${indentacao} ${erro.nome}: "${escaparTexto(erro.mensagem)}"`),
1903
+ `${indentacao}}`,
1904
+ "",
1905
+ ];
1906
+ }
1907
+
1908
+ function renderizarEffects(effects: EfeitoImportado[], indentacao = " "): string[] {
1909
+ if (effects.length === 0) {
1910
+ return [];
1911
+ }
1912
+ return [
1913
+ `${indentacao}effects {`,
1914
+ ...effects.map((effect) => `${indentacao} ${effect.categoria} ${effect.alvo}${effect.criticidade ? ` criticidade = ${effect.criticidade}` : ""}`),
1915
+ `${indentacao}}`,
1916
+ "",
1917
+ ];
1918
+ }
1919
+
1920
+ function renderizarImpl(impl: Partial<Record<OrigemInteropImportada, string>> | undefined, indentacao = " "): string[] {
1921
+ if (!impl || Object.keys(impl).length === 0) {
1922
+ return [];
1923
+ }
1924
+ return [
1925
+ `${indentacao}impl {`,
1926
+ ...(impl.ts ? [`${indentacao} ts: ${impl.ts}`] : []),
1927
+ ...(impl.py ? [`${indentacao} py: ${impl.py}`] : []),
1928
+ ...(impl.dart ? [`${indentacao} dart: ${impl.dart}`] : []),
1929
+ ...(impl.lua ? [`${indentacao} lua: ${impl.lua}`] : []),
1930
+ ...(impl.php ? [`${indentacao} php: ${impl.php}`] : []),
1931
+ ...(impl.cs ? [`${indentacao} cs: ${impl.cs}`] : []),
1932
+ ...(impl.java ? [`${indentacao} java: ${impl.java}`] : []),
1933
+ ...(impl.go ? [`${indentacao} go: ${impl.go}`] : []),
1934
+ ...(impl.rust ? [`${indentacao} rust: ${impl.rust}`] : []),
1935
+ ...(impl.cpp ? [`${indentacao} cpp: ${impl.cpp}`] : []),
1936
+ `${indentacao}}`,
1937
+ "",
1938
+ ];
1939
+ }
1940
+
1941
+ function renderizarValorVinculo(vinculo: VinculoImportado): string {
1942
+ if (vinculo.tipo === "simbolo") {
1943
+ return vinculo.valor;
1944
+ }
1945
+ if (vinculo.tipo === "arquivo" || vinculo.valor.includes("/") || vinculo.valor.includes("\\") || vinculo.valor.includes("{")) {
1946
+ return `"${escaparTexto(vinculo.valor)}"`;
1947
+ }
1948
+ return vinculo.valor;
1949
+ }
1950
+
1951
+ function renderizarVinculos(vinculos: VinculoImportado[] | undefined, indentacao = " "): string[] {
1952
+ if (!vinculos || vinculos.length === 0) {
1953
+ return [];
1954
+ }
1955
+ return [
1956
+ `${indentacao}vinculos {`,
1957
+ ...vinculos.map((vinculo) => `${indentacao} ${vinculo.tipo}: ${renderizarValorVinculo(vinculo)}`),
1958
+ `${indentacao}}`,
1959
+ "",
1960
+ ];
1961
+ }
1962
+
1963
+ function renderizarTask(task: TarefaImportada): string[] {
1964
+ const linhas = [
1965
+ ` task ${task.nome} {`,
1966
+ " docs {",
1967
+ ` resumo: "${escaparTexto(task.resumo)}"`,
1968
+ " }",
1969
+ "",
1970
+ ...renderizarCampos("input", task.input, " ", true),
1971
+ ...renderizarCampos("output", task.output, " ", true),
1972
+ ...renderizarEffects(task.effects, " "),
1973
+ ...renderizarImpl(task.impl, " "),
1974
+ ...renderizarVinculos(task.vinculos, " "),
1975
+ ...renderizarErrors(task.errors, " "),
1976
+ ];
1977
+
1978
+ linhas.push(" guarantees {");
1979
+ for (const campo of task.output) {
1980
+ linhas.push(` ${normalizarNomeCampoImportado(campo.nome)} existe`);
1981
+ }
1982
+ linhas.push(" }");
1983
+ linhas.push("");
1984
+
1985
+ linhas.push(" }");
1986
+ linhas.push("");
1987
+ return linhas;
1988
+ }
1989
+
1990
+ function renderizarRoute(route: RotaImportada): string[] {
1991
+ const caminhoRenderizado = /[{}]/.test(route.caminho)
1992
+ ? `"${escaparTexto(route.caminho)}"`
1993
+ : route.caminho;
1994
+ return [
1995
+ ` route ${route.nome} {`,
1996
+ " docs {",
1997
+ ` resumo: "${escaparTexto(route.resumo)}"`,
1998
+ " }",
1999
+ "",
2000
+ ` metodo: ${route.metodo}`,
2001
+ ` caminho: ${caminhoRenderizado}`,
2002
+ ` task: ${route.task}`,
2003
+ ...renderizarCampos("input", route.input, " "),
2004
+ ...renderizarCampos("output", route.output, " "),
2005
+ ...renderizarErrors(route.errors, " "),
2006
+ " }",
2007
+ "",
2008
+ ];
2009
+ }
2010
+
2011
+ function renderizarEnum(enumItem: EnumImportado): string[] {
2012
+ return [
2013
+ ` enum ${enumItem.nome} {`,
2014
+ ` ${enumItem.valores.join(",\n ")}`,
2015
+ " }",
2016
+ "",
2017
+ ];
2018
+ }
2019
+
2020
+ function renderizarEntidade(entity: EntidadeImportada): string[] {
2021
+ return [
2022
+ ` entity ${entity.nome} {`,
2023
+ " fields {",
2024
+ ...entity.campos.map((campo) => ` ${normalizarNomeCampoImportado(campo.nome)}: ${campo.tipo}`),
2025
+ " }",
2026
+ " }",
2027
+ "",
2028
+ ];
2029
+ }
2030
+
2031
+ function renderizarValorDatabase(valor?: string): string | undefined {
2032
+ if (!valor) {
2033
+ return undefined;
2034
+ }
2035
+ return /[\s/{}"]/u.test(valor)
2036
+ ? `"${escaparTexto(valor)}"`
2037
+ : valor;
2038
+ }
2039
+
2040
+ function renderizarRecursoDatabase(recurso: RecursoDatabaseImportado): string[] {
2041
+ const linhas = [` ${recurso.tipo} ${recurso.nome} {`];
2042
+ const mode = renderizarValorDatabase(recurso.mode);
2043
+ const table = renderizarValorDatabase(recurso.table);
2044
+ const collection = renderizarValorDatabase(recurso.collection);
2045
+ const ttl = renderizarValorDatabase(recurso.ttl);
2046
+ const surface = renderizarValorDatabase(recurso.surface);
2047
+
2048
+ if (mode) {
2049
+ linhas.push(` mode: ${mode}`);
2050
+ }
2051
+ if (table) {
2052
+ linhas.push(` table: ${table}`);
2053
+ }
2054
+ if (collection) {
2055
+ linhas.push(` collection: ${collection}`);
2056
+ }
2057
+ if (ttl) {
2058
+ linhas.push(` ttl: ${ttl}`);
2059
+ }
2060
+ if (surface) {
2061
+ linhas.push(` surface: ${surface}`);
2062
+ }
2063
+ linhas.push(" }");
2064
+ linhas.push("");
2065
+ return linhas;
2066
+ }
2067
+
2068
+ function renderizarDatabase(database: DatabaseImportado): string[] {
2069
+ const queryModel = renderizarValorDatabase(database.queryModel);
2070
+ const transactionModel = renderizarValorDatabase(database.transactionModel);
2071
+ return [
2072
+ ` database ${database.nome} {`,
2073
+ ` engine: ${database.engine}`,
2074
+ ...(queryModel ? [` query_model: ${queryModel}`] : []),
2075
+ ...(transactionModel ? [` transaction_model: ${transactionModel}`] : []),
2076
+ ...(database.diagnostics?.length
2077
+ ? [
2078
+ " diagnostics {",
2079
+ ...database.diagnostics.map((diagnostico) => ` ${diagnostico}`),
2080
+ " }",
2081
+ ]
2082
+ : []),
2083
+ ...database.resources.flatMap(renderizarRecursoDatabase),
2084
+ " }",
2085
+ "",
2086
+ ];
2087
+ }
2088
+
2089
+ function moduloParaCodigo(modulo: ModuloImportado): string {
2090
+ const linhas = [
2091
+ `module ${modulo.nome} {`,
2092
+ " docs {",
2093
+ ` resumo: "${escaparTexto(modulo.resumo)}"`,
2094
+ " }",
2095
+ "",
2096
+ ...renderizarVinculos(modulo.vinculos, " "),
2097
+ ...(modulo.databases ?? []).flatMap(renderizarDatabase),
2098
+ ...modulo.enums.flatMap(renderizarEnum),
2099
+ ...modulo.entities.flatMap(renderizarEntidade),
2100
+ ...modulo.tasks.flatMap(renderizarTask),
2101
+ ...modulo.routes.flatMap(renderizarRoute),
2102
+ "}",
2103
+ "",
2104
+ ];
2105
+
2106
+ return linhas.join("\n");
2107
+ }
2108
+
2109
+ async function formatarModuloImportado(codigo: string, caminhoVirtual: string): Promise<string> {
2110
+ const formatado = formatarCodigo(codigo, caminhoVirtual);
2111
+ return formatado.codigoFormatado ?? codigo;
2112
+ }
2113
+
2114
+ function nomeArquivoModulo(modulo: string): string {
2115
+ const segmentos = modulo.split(".");
2116
+ return `${segmentos.at(-1) ?? "modulo"}.sema`;
2117
+ }
2118
+
2119
+ function contextoArquivoModulo(modulo: string, namespaceBase: string): string {
2120
+ const prefixo = namespaceBase.split(".");
2121
+ const segmentos = modulo.split(".");
2122
+ const relativos = segmentos.slice(prefixo.length, -1);
2123
+ return relativos.length ? path.join(...relativos) : "";
2124
+ }
2125
+
2126
+ function montarArquivoImportado(modulo: ModuloImportado, namespaceBase: string, conteudo: string): ArquivoImportado {
2127
+ const pasta = contextoArquivoModulo(modulo.nome, namespaceBase);
2128
+ const caminhoRelativo = pasta ? path.join(pasta, nomeArquivoModulo(modulo.nome)) : nomeArquivoModulo(modulo.nome);
2129
+ return {
2130
+ caminhoRelativo,
2131
+ conteudo,
2132
+ modulo: modulo.nome,
2133
+ tarefas: modulo.tasks.length,
2134
+ rotas: modulo.routes.length,
2135
+ entidades: modulo.entities.length,
2136
+ enums: modulo.enums.length,
2137
+ databases: modulo.databases?.length ?? 0,
2138
+ };
2139
+ }
2140
+
2141
+ function consolidarTiposTs(contextos: ContextoTsArquivo[]): Map<string, TipoDescoberto> {
2142
+ const tipos = new Map<string, TipoDescoberto>();
2143
+ for (const contexto of contextos) {
2144
+ for (const [nome, tipo] of extrairTiposTs(contexto.sourceFile)) {
2145
+ if (!tipos.has(nome)) {
2146
+ tipos.set(nome, tipo);
2147
+ }
2148
+ }
2149
+ }
2150
+ return tipos;
2151
+ }
2152
+
2153
+ function importarNestJsDeArquivo(
2154
+ diretorioBase: string,
2155
+ arquivo: string,
2156
+ namespaceBase: string,
2157
+ tiposGlobais: Map<string, TipoDescoberto>,
2158
+ ): ModuloImportado[] {
2159
+ const relacao = path.relative(diretorioBase, arquivo);
2160
+ const codigo = ts.sys.readFile(arquivo, "utf8") ?? "";
2161
+ const sourceFile = ts.createSourceFile(arquivo, codigo, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
2162
+ const contextoSegmentos = inferirContextoPorArquivo(relacao);
2163
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
2164
+ const entitiesRef = new Set<string>();
2165
+ const enumsRef = new Set<string>();
2166
+ const tasks: TarefaImportada[] = [];
2167
+ const routes: RotaImportada[] = [];
2168
+
2169
+ for (const node of sourceFile.statements) {
2170
+ if (!ts.isClassDeclaration(node)) {
2171
+ continue;
2172
+ }
2173
+
2174
+ const controllerDecorator = lerDecorator(node, ["Controller"]);
2175
+ if (controllerDecorator) {
2176
+ const basePath = extrairTextoLiteral(controllerDecorator.argumentos[0]);
2177
+ for (const member of node.members) {
2178
+ if (!ts.isMethodDeclaration(member) || !member.body) {
2179
+ continue;
2180
+ }
2181
+ const httpDecorator = lerDecorator(member, ["Get", "Post", "Put", "Patch", "Delete"]);
2182
+ if (!httpDecorator) {
2183
+ continue;
2184
+ }
2185
+ const taskOriginal = extrairChamadaServiceTs(member.body) ?? member.name.getText(sourceFile);
2186
+ const taskNome = paraSnakeCase(taskOriginal);
2187
+ const routeInput = member.parameters.flatMap((parametro) =>
2188
+ expandirCamposTs(
2189
+ parametro.name.getText(sourceFile),
2190
+ parametro.type?.getText(sourceFile),
2191
+ tiposGlobais,
2192
+ entitiesRef,
2193
+ enumsRef,
2194
+ !parametro.questionToken,
2195
+ ));
2196
+ const routeOutputTipo = member.type?.getText(sourceFile);
2197
+ const routeOutput = !routeOutputTipo || mapearTipoPrimitivo(routeOutputTipo) === "Vazio"
2198
+ ? []
2199
+ : deduplicarCampos(expandirCamposTs("resultado", routeOutputTipo, tiposGlobais, entitiesRef, enumsRef, false));
2200
+
2201
+ routes.push({
2202
+ nome: `${taskNome}_publico`,
2203
+ resumo: `Rota importada automaticamente de ${relacao}#${member.name.getText(sourceFile)}.`,
2204
+ metodo: httpDecorator.nome.toUpperCase(),
2205
+ caminho: juntarCaminhoHttp(basePath, extrairTextoLiteral(httpDecorator.argumentos[0])),
2206
+ task: taskNome,
2207
+ input: deduplicarCampos(routeInput),
2208
+ output: routeOutput,
2209
+ errors: [],
2210
+ });
2211
+ }
2212
+ }
2213
+
2214
+ if (!node.name?.text.endsWith("Service")) {
2215
+ continue;
2216
+ }
2217
+ for (const member of node.members) {
2218
+ if (!ts.isMethodDeclaration(member) || !member.body || !member.name) {
2219
+ continue;
2220
+ }
2221
+ if (member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword)) {
2222
+ continue;
2223
+ }
2224
+ const nomeMetodo = member.name.getText(sourceFile);
2225
+ if (nomeMetodo === "constructor") {
2226
+ continue;
2227
+ }
2228
+ const input = member.parameters.flatMap((parametro) =>
2229
+ expandirCamposTs(
2230
+ parametro.name.getText(sourceFile),
2231
+ parametro.type?.getText(sourceFile),
2232
+ tiposGlobais,
2233
+ entitiesRef,
2234
+ enumsRef,
2235
+ !parametro.questionToken,
2236
+ ));
2237
+ const output = member.type?.getText(sourceFile) && mapearTipoPrimitivo(member.type.getText(sourceFile)) === "Vazio"
2238
+ ? []
2239
+ : deduplicarCampos(expandirCamposTs("resultado", member.type?.getText(sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
2240
+ tasks.push({
2241
+ nome: paraSnakeCase(nomeMetodo),
2242
+ resumo: `Task importada automaticamente de ${relacao}#${nomeMetodo}.`,
2243
+ input: deduplicarCampos(input),
2244
+ output,
2245
+ errors: extrairErrosTs(member.body, sourceFile),
2246
+ effects: descreverEfeitosPorHeuristica(member.body.getText(sourceFile)),
2247
+ impl: { ts: caminhoImplTs(diretorioBase, arquivo, nomeMetodo) },
2248
+ origemArquivo: relacao,
2249
+ origemSimbolo: nomeMetodo,
2250
+ });
2251
+ }
2252
+ }
2253
+
2254
+ if (!tasks.length && !routes.length) {
2255
+ return [];
2256
+ }
2257
+
2258
+ const { entities, enums } = criarEntidadesReferenciadas(tiposGlobais, entitiesRef, enumsRef);
2259
+ sincronizarRotasComTasks(routes, tasks);
2260
+
2261
+ return [{
2262
+ nome: nomeModulo,
2263
+ resumo: `Rascunho Sema importado de um contexto NestJS legado em ${contextoSegmentos.join("/")}.`,
2264
+ entities,
2265
+ enums,
2266
+ tasks: deduplicarTarefas(tasks),
2267
+ routes: deduplicarRotas(routes),
2268
+ }];
2269
+ }
2270
+
2271
+ async function importarTypeScriptBase(
2272
+ diretorio: string,
2273
+ namespaceBase: string,
2274
+ modoNestjs = false,
2275
+ ): Promise<ModuloImportado[]> {
2276
+ const arquivos = await listarArquivosRecursivos(diretorio, [".ts"]);
2277
+ const uteis = arquivos.filter((arquivo) =>
2278
+ !arquivo.endsWith(".spec.ts")
2279
+ && !arquivo.endsWith(".test.ts")
2280
+ && !arquivo.endsWith(".d.ts")
2281
+ && !(modoNestjs && !arquivo.endsWith(".controller.ts") && !arquivo.endsWith(".service.ts")),
2282
+ );
2283
+ const contextosTodos = await Promise.all(arquivos
2284
+ .filter((arquivo) => !arquivo.endsWith(".spec.ts") && !arquivo.endsWith(".test.ts") && !arquivo.endsWith(".d.ts"))
2285
+ .map(async (arquivo) => {
2286
+ const texto = await readFile(arquivo, "utf8");
2287
+ return {
2288
+ sourceFile: ts.createSourceFile(arquivo, texto, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS),
2289
+ texto,
2290
+ relacao: path.relative(diretorio, arquivo),
2291
+ };
2292
+ }));
2293
+ const contextos = await Promise.all(uteis.map(async (arquivo) => {
2294
+ const texto = await readFile(arquivo, "utf8");
2295
+ return {
2296
+ sourceFile: ts.createSourceFile(arquivo, texto, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS),
2297
+ texto,
2298
+ relacao: path.relative(diretorio, arquivo),
2299
+ };
2300
+ }));
2301
+ const tiposGlobais = consolidarTiposTs(contextosTodos);
2302
+ const modulos = new Map<string, ModuloImportado>();
2303
+
2304
+ if (modoNestjs) {
2305
+ for (const contexto of contextos) {
2306
+ for (const modulo of importarNestJsDeArquivo(diretorio, path.join(diretorio, contexto.relacao), namespaceBase, tiposGlobais)) {
2307
+ const existente = modulos.get(modulo.nome);
2308
+ if (!existente) {
2309
+ modulos.set(modulo.nome, modulo);
2310
+ continue;
2311
+ }
2312
+ existente.tasks = deduplicarTarefas([...existente.tasks, ...modulo.tasks]);
2313
+ existente.routes = deduplicarRotas([...existente.routes, ...modulo.routes]);
2314
+ existente.entities = deduplicarEntidades([...existente.entities, ...modulo.entities]);
2315
+ existente.enums = deduplicarEnums([...existente.enums, ...modulo.enums]);
2316
+ }
2317
+ }
2318
+ return [...modulos.values()];
2319
+ }
2320
+
2321
+ for (const contexto of contextos) {
2322
+ const entitiesRef = new Set<string>();
2323
+ const enumsRef = new Set<string>();
2324
+ const contextoSegmentos = inferirContextoPorArquivo(contexto.relacao);
2325
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
2326
+ const tasks: TarefaImportada[] = [];
2327
+
2328
+ contexto.sourceFile.forEachChild((node) => {
2329
+ if (ts.isFunctionDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
2330
+ const nome = node.name.text;
2331
+ const input = node.parameters.flatMap((parametro) =>
2332
+ expandirCamposTs(parametro.name.getText(contexto.sourceFile), parametro.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
2333
+ const output = node.type?.getText(contexto.sourceFile) && mapearTipoPrimitivo(node.type.getText(contexto.sourceFile)) === "Vazio"
2334
+ ? []
2335
+ : deduplicarCampos(expandirCamposTs("resultado", node.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
2336
+ const errors = node.body ? extrairErrosTs(node.body, contexto.sourceFile) : [];
2337
+ const effects = node.body ? descreverEfeitosPorHeuristica(node.body.getText(contexto.sourceFile)) : [];
2338
+ tasks.push({
2339
+ nome: paraSnakeCase(nome),
2340
+ resumo: `Task importada automaticamente de ${contexto.relacao}#${nome}.`,
2341
+ input: deduplicarCampos(input),
2342
+ output,
2343
+ errors,
2344
+ effects,
2345
+ impl: { ts: caminhoImplTs(diretorio, path.join(diretorio, contexto.relacao), nome) },
2346
+ origemArquivo: contexto.relacao,
2347
+ origemSimbolo: nome,
2348
+ });
2349
+ }
2350
+
2351
+ if (ts.isClassDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
2352
+ const nomeClasse = node.name.text;
2353
+ for (const member of node.members) {
2354
+ if (!ts.isMethodDeclaration(member) || !member.name || !member.body) {
2355
+ continue;
2356
+ }
2357
+ if (member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword)) {
2358
+ continue;
2359
+ }
2360
+ const nomeMetodo = member.name.getText(contexto.sourceFile);
2361
+ if (nomeMetodo === "constructor") {
2362
+ continue;
2363
+ }
2364
+ const input = member.parameters.flatMap((parametro) =>
2365
+ expandirCamposTs(parametro.name.getText(contexto.sourceFile), parametro.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
2366
+ const output = member.type?.getText(contexto.sourceFile) && mapearTipoPrimitivo(member.type.getText(contexto.sourceFile)) === "Vazio"
2367
+ ? []
2368
+ : deduplicarCampos(expandirCamposTs("resultado", member.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
2369
+ tasks.push({
2370
+ nome: paraSnakeCase(nomeMetodo),
2371
+ resumo: `Task importada automaticamente de ${contexto.relacao}#${nomeClasse}.${nomeMetodo}.`,
2372
+ input: deduplicarCampos(input),
2373
+ output,
2374
+ errors: extrairErrosTs(member.body, contexto.sourceFile),
2375
+ effects: descreverEfeitosPorHeuristica(member.body.getText(contexto.sourceFile)),
2376
+ impl: { ts: caminhoImplTs(diretorio, path.join(diretorio, contexto.relacao), `${nomeClasse}.${nomeMetodo}`) },
2377
+ origemArquivo: contexto.relacao,
2378
+ origemSimbolo: `${nomeClasse}.${nomeMetodo}`,
2379
+ });
2380
+ }
2381
+ }
2382
+ });
2383
+
2384
+ if (tasks.length === 0) {
2385
+ continue;
2386
+ }
2387
+
2388
+ const { entities, enums } = criarEntidadesReferenciadas(tiposGlobais, entitiesRef, enumsRef);
2389
+ modulos.set(nomeModulo, {
2390
+ nome: nomeModulo,
2391
+ resumo: `Rascunho Sema importado automaticamente de ${contexto.relacao}.`,
2392
+ tasks: deduplicarTarefas(tasks),
2393
+ routes: [],
2394
+ entities,
2395
+ enums,
2396
+ });
2397
+ }
2398
+
2399
+ return [...modulos.values()];
2400
+ }
2401
+
2402
+ function nomeTaskParaRotaTypeScript(caminho: string, metodo: string): string {
2403
+ const segmentos = caminho
2404
+ .replace(/^\/+|\/+$/g, "")
2405
+ .split("/")
2406
+ .filter(Boolean)
2407
+ .map((segmento) => segmento.replace(/[{}]/g, ""));
2408
+ return paraSnakeCase([...segmentos, metodo.toLowerCase()].join("_")) || `rota_${metodo.toLowerCase()}`;
2409
+ }
2410
+
2411
+ function camposDeParametrosRotaTypeScript(
2412
+ parametros: ReturnType<typeof extrairRotasTypeScriptHttp>[number]["parametros"],
2413
+ ): CampoImportado[] {
2414
+ return parametros.map((parametro) => ({
2415
+ nome: paraSnakeCase(parametro.nome),
2416
+ tipo: parametro.tipoSema,
2417
+ obrigatorio: true,
2418
+ }));
2419
+ }
2420
+
2421
+ function extrairColecoesFirebaseImportacao(texto: string): string[] {
2422
+ const encontrados = new Set<string>();
2423
+
2424
+ for (const match of texto.matchAll(/\b(?:export\s+)?const\s+\w*COLLECTIONS?\w*\s*=\s*\{([\s\S]*?)\n\}/g)) {
2425
+ const corpo = match[1] ?? "";
2426
+ for (const valor of corpo.matchAll(/:\s*["'`]([^"'`]+)["'`]/g)) {
2427
+ encontrados.add(valor[1]!);
2428
+ }
2429
+ }
2430
+
2431
+ return [...encontrados];
2432
+ }
2433
+
2434
+ async function importarNextJsBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
2435
+ const escopo = resolverEscopoImportacaoNextJs(diretorio);
2436
+ const arquivos = await listarArquivosRecursivos(escopo.diretorioEscopo, [".ts", ".tsx", ".js", ".jsx"]);
2437
+ const uteis = arquivos.filter((arquivo) =>
2438
+ !arquivo.endsWith(".spec.ts")
2439
+ && !arquivo.endsWith(".test.ts")
2440
+ && !arquivo.endsWith(".d.ts")
2441
+ && /(\\|\/)(?:src\\|src\/)?app(\\|\/)api(\\|\/).+(\\|\/)route\.(ts|tsx|js|jsx)$/i.test(arquivo));
2442
+
2443
+ const contextos = await Promise.all(uteis.map(async (arquivo) => {
2444
+ const texto = await readFile(arquivo, "utf8");
2445
+ const scriptKind = arquivo.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
2446
+ return {
2447
+ sourceFile: ts.createSourceFile(arquivo, texto, ts.ScriptTarget.Latest, true, scriptKind),
2448
+ texto,
2449
+ relacao: path.relative(escopo.baseProjeto, arquivo),
2450
+ };
2451
+ }));
2452
+ const tiposGlobais = consolidarTiposTs(contextos);
2453
+ const modulos = new Map<string, ModuloImportado>();
2454
+
2455
+ for (const contexto of contextos) {
2456
+ const entitiesRef = new Set<string>();
2457
+ const enumsRef = new Set<string>();
2458
+ const contextoSegmentos = inferirContextoPorArquivo(
2459
+ contexto.relacao.replace(/[\\/]route\.(?:ts|tsx|js|jsx)$/i, ""),
2460
+ ).filter((segmento, indice) => !(indice === 0 && segmento === "app"));
2461
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
2462
+ const tasks: TarefaImportada[] = [];
2463
+ const routes: RotaImportada[] = [];
2464
+
2465
+ for (const rota of extrairRotasTypeScriptHttp(contexto.sourceFile, contexto.relacao).filter((item) => item.origem === "nextjs")) {
2466
+ const taskNome = nomeTaskParaRotaTypeScript(rota.caminho, rota.metodo);
2467
+ const exportacao = localizarExportacaoTypeScriptHttp(contexto.sourceFile, rota.simbolo);
2468
+ const semantica = inferirSemanticaHandlerTypeScriptHttp(contexto.sourceFile, rota.simbolo);
2469
+ const input = deduplicarCampos([
2470
+ ...camposDeParametrosRotaTypeScript(rota.parametros),
2471
+ ...camposDeSemanticaTypeScriptHttp(semantica?.query ?? [], tiposGlobais, entitiesRef, enumsRef),
2472
+ ...camposEstruturadosTypeScriptHttp("body", semantica?.bodyTipoTexto, tiposGlobais, entitiesRef, enumsRef),
2473
+ ...camposDeSemanticaTypeScriptHttp(semantica?.body ?? [], tiposGlobais, entitiesRef, enumsRef),
2474
+ ]);
2475
+ const output = semantica && semantica.response.length > 0
2476
+ ? deduplicarCampos(camposDeSemanticaTypeScriptHttp(semantica.response, tiposGlobais, entitiesRef, enumsRef))
2477
+ : semantica?.responseTipoTexto
2478
+ ? deduplicarCampos(expandirCamposTs("resultado", semantica.responseTipoTexto, tiposGlobais, entitiesRef, enumsRef, false))
2479
+ : exportacao?.retorno && mapearTipoPrimitivo(exportacao.retorno) === "Vazio"
2480
+ ? []
2481
+ : deduplicarCampos(expandirCamposTs("resultado", exportacao?.retorno, tiposGlobais, entitiesRef, enumsRef, false));
2482
+ const taskOutput = output.length > 0 ? output : [{ nome: "resultado", tipo: "Json", obrigatorio: false }];
2483
+ const resumoBase = `Rota Next.js App Router importada automaticamente de ${contexto.relacao}#${rota.simbolo}.`;
2484
+ const errors = deduplicarErros([
2485
+ ...(exportacao?.corpo ? extrairErrosTs(exportacao.corpo, contexto.sourceFile) : []),
2486
+ ...errosPorStatusHttp(semantica?.errorStatuses ?? []),
2487
+ ]);
2488
+
2489
+ tasks.push({
2490
+ nome: taskNome,
2491
+ resumo: `Task derivada automaticamente de ${contexto.relacao}#${rota.simbolo}.`,
2492
+ input,
2493
+ output: taskOutput,
2494
+ errors,
2495
+ effects: exportacao?.corpo ? descreverEfeitosPorHeuristica(exportacao.corpo.getText(contexto.sourceFile)) : descreverEfeitosPorHeuristica(contexto.texto),
2496
+ impl: { ts: caminhoImplTs(escopo.baseProjeto, path.join(escopo.baseProjeto, contexto.relacao), rota.simbolo) },
2497
+ origemArquivo: contexto.relacao,
2498
+ origemSimbolo: rota.simbolo,
2499
+ });
2500
+
2501
+ routes.push({
2502
+ nome: `${taskNome}_publico`,
2503
+ resumo: resumoBase,
2504
+ metodo: rota.metodo,
2505
+ caminho: rota.caminho,
2506
+ task: taskNome,
2507
+ input,
2508
+ output: taskOutput,
2509
+ errors,
2510
+ });
2511
+ }
2512
+
2513
+ if (!tasks.length && !routes.length) {
2514
+ continue;
2515
+ }
2516
+
2517
+ sincronizarRotasComTasks(routes, tasks);
2518
+ const { entities, enums } = criarEntidadesReferenciadas(tiposGlobais, entitiesRef, enumsRef);
2519
+ modulos.set(nomeModulo, {
2520
+ nome: nomeModulo,
2521
+ resumo: `Rascunho Sema importado automaticamente de ${contexto.relacao}.`,
2522
+ tasks: deduplicarTarefas(tasks),
2523
+ routes: deduplicarRotas(routes),
2524
+ entities,
2525
+ enums,
2526
+ });
2527
+ }
2528
+
2529
+ return [...modulos.values()];
2530
+ }
2531
+
2532
+ async function importarFirebaseBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
2533
+ const arquivos = await listarArquivosRecursivos(diretorio, [".ts", ".tsx", ".js", ".jsx"]);
2534
+ const uteis = arquivos.filter((arquivo) =>
2535
+ !arquivo.endsWith(".spec.ts")
2536
+ && !arquivo.endsWith(".test.ts")
2537
+ && !arquivo.endsWith(".d.ts")
2538
+ && /(sema_contract_bridge|health-check|collections?|firestore)/i.test(arquivo));
2539
+
2540
+ const contextos = await Promise.all(uteis.map(async (arquivo) => {
2541
+ const texto = await readFile(arquivo, "utf8");
2542
+ const scriptKind = arquivo.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
2543
+ return {
2544
+ sourceFile: ts.createSourceFile(arquivo, texto, ts.ScriptTarget.Latest, true, scriptKind),
2545
+ texto,
2546
+ relacao: path.relative(diretorio, arquivo),
2547
+ };
2548
+ }));
2549
+ const tiposGlobais = consolidarTiposTs(contextos);
2550
+ const modulos = new Map<string, ModuloImportado>();
2551
+
2552
+ for (const contexto of contextos) {
2553
+ const entitiesRef = new Set<string>();
2554
+ const enumsRef = new Set<string>();
2555
+ const contextoSegmentos = inferirContextoPorArquivo(contexto.relacao);
2556
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
2557
+ const tasks: TarefaImportada[] = [];
2558
+ const routes: RotaImportada[] = [];
2559
+
2560
+ for (const node of contexto.sourceFile.statements) {
2561
+ if (ts.isFunctionDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
2562
+ const nome = node.name.text;
2563
+ const input = node.parameters.flatMap((parametro) =>
2564
+ expandirCamposTs(parametro.name.getText(contexto.sourceFile), parametro.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
2565
+ const output = node.type?.getText(contexto.sourceFile) && mapearTipoPrimitivo(node.type.getText(contexto.sourceFile)) === "Vazio"
2566
+ ? []
2567
+ : deduplicarCampos(expandirCamposTs("resultado", node.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
2568
+ tasks.push({
2569
+ nome: paraSnakeCase(nome.replace(/^sema/, "")) || paraSnakeCase(nome),
2570
+ resumo: `Task Firebase/worker importada automaticamente de ${contexto.relacao}#${nome}.`,
2571
+ input: deduplicarCampos(input),
2572
+ output: output.length > 0 ? output : [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
2573
+ errors: node.body ? extrairErrosTs(node.body, contexto.sourceFile) : [],
2574
+ effects: node.body ? descreverEfeitosPorHeuristica(node.body.getText(contexto.sourceFile)) : descreverEfeitosPorHeuristica(contexto.texto),
2575
+ impl: { ts: caminhoImplTs(diretorio, path.join(diretorio, contexto.relacao), nome) },
2576
+ origemArquivo: contexto.relacao,
2577
+ origemSimbolo: nome,
2578
+ });
2579
+ }
2580
+ }
2581
+
2582
+ for (const rota of extrairRotasTypeScriptHttp(contexto.sourceFile, contexto.relacao).filter((item) => item.origem === "firebase")) {
2583
+ const taskNome = nomeTaskParaRotaTypeScript(rota.caminho, rota.metodo);
2584
+ const exportacao = localizarExportacaoTypeScriptHttp(contexto.sourceFile, rota.simbolo);
2585
+ const input = deduplicarCampos(camposDeParametrosRotaTypeScript(rota.parametros));
2586
+ const output = exportacao?.retorno && mapearTipoPrimitivo(exportacao.retorno) === "Vazio"
2587
+ ? []
2588
+ : deduplicarCampos(expandirCamposTs("resultado", exportacao?.retorno, tiposGlobais, entitiesRef, enumsRef, false));
2589
+ const taskOutput = output.length > 0 ? output : [{ nome: "resultado", tipo: "Json", obrigatorio: false }];
2590
+
2591
+ tasks.push({
2592
+ nome: taskNome,
2593
+ resumo: `Task HTTP do worker importada automaticamente de ${contexto.relacao}#${rota.simbolo}.`,
2594
+ input,
2595
+ output: taskOutput,
2596
+ errors: exportacao?.corpo ? extrairErrosTs(exportacao.corpo, contexto.sourceFile) : [],
2597
+ effects: exportacao?.corpo ? descreverEfeitosPorHeuristica(exportacao.corpo.getText(contexto.sourceFile)) : descreverEfeitosPorHeuristica(contexto.texto),
2598
+ impl: { ts: caminhoImplTs(diretorio, path.join(diretorio, contexto.relacao), rota.simbolo) },
2599
+ origemArquivo: contexto.relacao,
2600
+ origemSimbolo: rota.simbolo,
2601
+ });
2602
+
2603
+ routes.push({
2604
+ nome: `${taskNome}_publico`,
2605
+ resumo: `Rota do worker importada automaticamente de ${contexto.relacao}#${rota.simbolo}.`,
2606
+ metodo: rota.metodo,
2607
+ caminho: rota.caminho,
2608
+ task: taskNome,
2609
+ input,
2610
+ output: taskOutput,
2611
+ errors: exportacao?.corpo ? extrairErrosTs(exportacao.corpo, contexto.sourceFile) : [],
2612
+ });
2613
+ }
2614
+
2615
+ for (const colecao of extrairColecoesFirebaseImportacao(contexto.texto)) {
2616
+ tasks.push({
2617
+ nome: paraSnakeCase(`inventariar_${colecao}`),
2618
+ resumo: `Task sintetica para registrar o recurso persistido ${colecao} descoberto em ${contexto.relacao}.`,
2619
+ input: [],
2620
+ output: [{ nome: "colecao", tipo: "Texto", obrigatorio: false }],
2621
+ errors: [],
2622
+ effects: [{ categoria: "persistencia", alvo: colecao, criticidade: "media" }],
2623
+ origemArquivo: contexto.relacao,
2624
+ origemSimbolo: colecao,
2625
+ });
2626
+ }
2627
+
2628
+ if (!tasks.length && !routes.length) {
2629
+ continue;
2630
+ }
2631
+
2632
+ sincronizarRotasComTasks(routes, tasks);
2633
+ const { entities, enums } = criarEntidadesReferenciadas(tiposGlobais, entitiesRef, enumsRef);
2634
+ modulos.set(nomeModulo, {
2635
+ nome: nomeModulo,
2636
+ resumo: `Rascunho Sema importado automaticamente de ${contexto.relacao}.`,
2637
+ tasks: deduplicarTarefas(tasks),
2638
+ routes: deduplicarRotas(routes),
2639
+ entities,
2640
+ enums,
2641
+ });
2642
+ }
2643
+
2644
+ return [...modulos.values()];
2645
+ }
2646
+
2647
+ function extrairTiposPython(texto: string): Map<string, TipoPythonDescoberto> {
2648
+ const encontrados = new Map<string, TipoPythonDescoberto>();
2649
+
2650
+ const regexBaseModel = /^class\s+(\w+)(?:\(([^)]*)\))?:\n((?:^[ \t].*(?:\n|$))*)/gm;
2651
+ for (const match of texto.matchAll(regexBaseModel)) {
2652
+ const [, nome, bases = "", corpo] = match;
2653
+ if (!bases.includes("BaseModel")) {
2654
+ continue;
2655
+ }
2656
+ const campos = [...corpo.matchAll(/^\s{4}(\w+)\s*:\s*([^\n=]+)(?:\s*=.+)?$/gm)].map((campo) => ({
2657
+ nome: campo[1]!,
2658
+ tipoTexto: campo[2]!.trim(),
2659
+ obrigatorio: !/=/.test(campo[0]!),
2660
+ }));
2661
+ encontrados.set(nome!, { tipo: "objeto", nome: nome!, campos });
2662
+ }
2663
+
2664
+ const regexEnum = /^class\s+(\w+)(?:\(([^)]*)\))?:\n((?:^[ \t].*(?:\n|$))*)/gm;
2665
+ for (const match of texto.matchAll(regexEnum)) {
2666
+ const [, nome, bases = "", corpo] = match;
2667
+ if (!/(Enum|StrEnum)/.test(bases)) {
2668
+ continue;
2669
+ }
2670
+ const valores = [...corpo.matchAll(/^\s{4}(\w+)\s*=\s*["']([^"']+)["']$/gm)].map((valor) => valor[1]!).filter(Boolean);
2671
+ if (valores.length) {
2672
+ encontrados.set(nome!, { tipo: "enum", nome: nome!, valores });
2673
+ }
2674
+ }
2675
+
2676
+ return encontrados;
2677
+ }
2678
+
2679
+ function mapearTipoPythonParaSema(
2680
+ tipoTexto: string | undefined,
2681
+ tipos: Map<string, TipoPythonDescoberto>,
2682
+ entidadesReferenciadas: Set<string>,
2683
+ enumsReferenciados: Set<string>,
2684
+ ): string {
2685
+ if (!tipoTexto) {
2686
+ return "Json";
2687
+ }
2688
+
2689
+ const limpo = tipoTexto.replace(/\s+/g, "");
2690
+ const basico = mapearTipoPrimitivo(limpo);
2691
+ if (basico !== limpo) {
2692
+ return basico;
2693
+ }
2694
+
2695
+ const simples = limpo.replace(/Optional\[(.+)\]/, "$1").replace(/list\[(.+)\]/i, "Json").replace(/dict\[(.+)\]/i, "Json");
2696
+ if (tipos.has(simples)) {
2697
+ const encontrado = tipos.get(simples)!;
2698
+ if (encontrado.tipo === "enum") {
2699
+ enumsReferenciados.add(encontrado.nome);
2700
+ return encontrado.nome;
2701
+ }
2702
+ if (!pareceWrapperTipo(encontrado.nome)) {
2703
+ entidadesReferenciadas.add(encontrado.nome);
2704
+ return encontrado.nome;
2705
+ }
2706
+ }
2707
+
2708
+ return "Json";
2709
+ }
2710
+
2711
+ function expandirCamposPython(
2712
+ nomeParametro: string,
2713
+ tipoTexto: string | undefined,
2714
+ tipos: Map<string, TipoPythonDescoberto>,
2715
+ entidadesReferenciadas: Set<string>,
2716
+ enumsReferenciados: Set<string>,
2717
+ obrigatorio: boolean,
2718
+ ): CampoImportado[] {
2719
+ if (!tipoTexto) {
2720
+ return [{ nome: paraSnakeCase(nomeParametro), tipo: "Json", obrigatorio }];
2721
+ }
2722
+ const limpo = tipoTexto.replace(/\s+/g, "").replace(/Optional\[(.+)\]/, "$1");
2723
+ const descoberto = tipos.get(limpo);
2724
+ if (descoberto?.tipo === "objeto" && pareceWrapperTipo(descoberto.nome) && descoberto.campos) {
2725
+ return descoberto.campos.map((campo) => ({
2726
+ nome: paraSnakeCase(campo.nome),
2727
+ tipo: mapearTipoPythonParaSema(campo.tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
2728
+ obrigatorio: campo.obrigatorio,
2729
+ }));
2730
+ }
2731
+ return [{
2732
+ nome: paraSnakeCase(nomeParametro),
2733
+ tipo: mapearTipoPythonParaSema(tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
2734
+ obrigatorio,
2735
+ }];
2736
+ }
2737
+
2738
+ function criarEntidadesPython(
2739
+ tipos: Map<string, TipoPythonDescoberto>,
2740
+ entidadesReferenciadas: Set<string>,
2741
+ enumsReferenciados: Set<string>,
2742
+ ): { entities: EntidadeImportada[]; enums: EnumImportado[] } {
2743
+ const entities = [...entidadesReferenciadas]
2744
+ .map((nome) => tipos.get(nome))
2745
+ .filter((item): item is TipoPythonDescoberto => Boolean(item?.tipo === "objeto" && item.campos))
2746
+ .map((item) => ({
2747
+ nome: item.nome,
2748
+ campos: item.campos!.map((campo) => ({
2749
+ nome: paraSnakeCase(campo.nome),
2750
+ tipo: mapearTipoPythonParaSema(campo.tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
2751
+ obrigatorio: campo.obrigatorio,
2752
+ })),
2753
+ }));
2754
+
2755
+ const enums = [...enumsReferenciados]
2756
+ .map((nome) => tipos.get(nome))
2757
+ .filter((item): item is TipoPythonDescoberto => Boolean(item?.tipo === "enum" && item.valores))
2758
+ .map((item) => ({
2759
+ nome: item.nome,
2760
+ valores: item.valores!,
2761
+ }));
2762
+
2763
+ return { entities: deduplicarEntidades(entities), enums: deduplicarEnums(enums) };
2764
+ }
2765
+
2766
+ function extrairErrosPython(texto: string): ErroImportado[] {
2767
+ const erros = new Map<string, string>();
2768
+ for (const match of texto.matchAll(/raise\s+(\w+)(?:\(([^)]*)\))?/g)) {
2769
+ const nomeBruto = match[1]!;
2770
+ const mensagem = (match[2] ?? "").match(/["']([^"']+)["']/)?.[1] ?? `Erro importado automaticamente de ${nomeBruto}.`;
2771
+ erros.set(normalizarNomeErroBruto(nomeBruto), mensagem);
2772
+ }
2773
+ return [...erros.entries()].map(([nome, mensagem]) => ({ nome, mensagem }));
2774
+ }
2775
+
2776
+ function extrairErrosLua(texto: string): ErroImportado[] {
2777
+ const erros = new Map<string, string>();
2778
+ for (const match of texto.matchAll(/\berror\s*\(\s*(?:(["'])(.*?)\1|([A-Za-z_]\w*))/g)) {
2779
+ const mensagem = match[2] ?? `Erro importado automaticamente de ${match[3] ?? "error"}.`;
2780
+ erros.set(normalizarNomeErroBruto(mensagem), mensagem);
2781
+ }
2782
+ return [...erros.entries()].map(([nome, mensagem]) => ({ nome, mensagem }));
2783
+ }
2784
+
2785
+ function extrairErrosPhp(texto: string): ErroImportado[] {
2786
+ const erros = new Map<string, string>();
2787
+ for (const match of texto.matchAll(/\bthrow\s+new\s+([A-Za-z_\\][A-Za-z0-9_\\]*)\s*\(([^)]*)\)/g)) {
2788
+ const nomeBruto = match[1]!.split("\\").at(-1) ?? match[1]!;
2789
+ const mensagem = (match[2] ?? "").match(/["']([^"']+)["']/)?.[1] ?? `Erro importado automaticamente de ${nomeBruto}.`;
2790
+ erros.set(normalizarNomeErroBruto(nomeBruto), mensagem);
2791
+ }
2792
+ for (const match of texto.matchAll(/\babort\s*\(\s*(\d{3})(?:\s*,\s*["']([^"']+)["'])?/g)) {
2793
+ const mensagem = match[2] ?? `Abort HTTP ${match[1]}.`;
2794
+ erros.set(`http_${match[1]}`, mensagem);
2795
+ }
2796
+ return [...erros.entries()].map(([nome, mensagem]) => ({ nome, mensagem }));
2797
+ }
2798
+
2799
+ function caminhoImplGenerico(
2800
+ diretorioBase: string,
2801
+ arquivo: string,
2802
+ simbolo: string,
2803
+ opcoes?: { snakeCaseUltimoArquivo?: boolean },
2804
+ ): string {
2805
+ const relativo = path.relative(diretorioBase, arquivo).replace(/\.[^.]+$/, "");
2806
+ const segmentos = relativo.split(path.sep).map((segmento, indice, lista) =>
2807
+ opcoes?.snakeCaseUltimoArquivo && indice === lista.length - 1
2808
+ ? paraSnakeCase(segmento)
2809
+ : paraIdentificadorModulo(segmento))
2810
+ .filter(Boolean);
2811
+ return [...segmentos, simbolo].join(".");
2812
+ }
2813
+
2814
+ function caminhoImplPython(diretorioBase: string, arquivo: string, simbolo: string): string {
2815
+ return caminhoImplGenerico(diretorioBase, arquivo, simbolo);
2816
+ }
2817
+
2818
+ function caminhoImplDart(diretorioBase: string, arquivo: string, simbolo: string): string {
2819
+ return caminhoImplGenerico(diretorioBase, arquivo, simbolo);
2820
+ }
2821
+
2822
+ function caminhoImplPhp(diretorioBase: string, arquivo: string, simbolo: string): string {
2823
+ return simbolo.includes(".")
2824
+ ? simbolo
2825
+ : caminhoImplGenerico(diretorioBase, arquivo, simbolo);
2826
+ }
2827
+
2828
+ const NOMES_RESERVADOS_TASK_IMPORTADA = new Set([
2829
+ "database",
2830
+ "table",
2831
+ "view",
2832
+ "query",
2833
+ "transaction",
2834
+ "index",
2835
+ "constraint",
2836
+ "relationship",
2837
+ "collection",
2838
+ "document",
2839
+ "keyspace",
2840
+ "stream",
2841
+ "lock",
2842
+ "retention",
2843
+ "replication",
2844
+ "input",
2845
+ "output",
2846
+ "rules",
2847
+ "effects",
2848
+ "impl",
2849
+ "vinculos",
2850
+ "execucao",
2851
+ "auth",
2852
+ "authz",
2853
+ "dados",
2854
+ "audit",
2855
+ "segredos",
2856
+ "forbidden",
2857
+ "guarantees",
2858
+ "state",
2859
+ "tests",
2860
+ "error",
2861
+ "flow",
2862
+ "route",
2863
+ "worker",
2864
+ "evento",
2865
+ "fila",
2866
+ "cron",
2867
+ "webhook",
2868
+ "cache",
2869
+ "storage",
2870
+ "policy",
2871
+ "when",
2872
+ "given",
2873
+ "expect",
2874
+ ]);
2875
+
2876
+ function normalizarNomeTaskImportada(valor: string): string {
2877
+ const nome = paraSnakeCase(valor) || "task_importada";
2878
+ return NOMES_RESERVADOS_TASK_IMPORTADA.has(nome) ? `${nome}_task` : nome;
2879
+ }
2880
+
2881
+ type ModoHttpPython = "nenhum" | "fastapi" | "flask";
2882
+
2883
+ function dividirParametrosPython(parametros: string): string[] {
2884
+ const partes: string[] = [];
2885
+ let atual = "";
2886
+ let profundidade = 0;
2887
+
2888
+ for (const caractere of parametros) {
2889
+ if (caractere === "," && profundidade === 0) {
2890
+ if (atual.trim()) {
2891
+ partes.push(atual.trim());
2892
+ }
2893
+ atual = "";
2894
+ continue;
2895
+ }
2896
+
2897
+ if (["[", "(", "{", "<"].includes(caractere)) {
2898
+ profundidade += 1;
2899
+ } else if (["]", ")", "}", ">"].includes(caractere) && profundidade > 0) {
2900
+ profundidade -= 1;
2901
+ }
2902
+
2903
+ atual += caractere;
2904
+ }
2905
+
2906
+ if (atual.trim()) {
2907
+ partes.push(atual.trim());
2908
+ }
2909
+
2910
+ return partes;
2911
+ }
2912
+
2913
+ function extrairAssinaturaParametrosPython(parametros: string): Map<string, { tipoTexto?: string; obrigatorio: boolean }> {
2914
+ const assinatura = new Map<string, { tipoTexto?: string; obrigatorio: boolean }>();
2915
+
2916
+ for (const item of dividirParametrosPython(parametros)) {
2917
+ if (!item || item.startsWith("self") || item.startsWith("cls") || item.startsWith("*")) {
2918
+ continue;
2919
+ }
2920
+
2921
+ const obrigatorio = !item.includes("=");
2922
+ const semValorPadrao = item.split("=")[0]?.trim() ?? item.trim();
2923
+ const [nomeBruto, tipo] = semValorPadrao.split(":").map((parte) => parte.trim());
2924
+ const nome = nomeBruto?.replace(/^\*{1,2}/, "").trim();
2925
+ if (!nome) {
2926
+ continue;
2927
+ }
2928
+
2929
+ assinatura.set(nome, {
2930
+ tipoTexto: tipo || undefined,
2931
+ obrigatorio,
2932
+ });
2933
+ }
2934
+
2935
+ return assinatura;
2936
+ }
2937
+
2938
+ function mapearConversorFlaskParaSema(conversor?: string): string {
2939
+ switch ((conversor ?? "").toLowerCase()) {
2940
+ case "int":
2941
+ return "Inteiro";
2942
+ case "float":
2943
+ return "Decimal";
2944
+ case "uuid":
2945
+ return "Id";
2946
+ case "path":
2947
+ default:
2948
+ return "Texto";
2949
+ }
2950
+ }
2951
+
2952
+ function criarInputRotaFlask(
2953
+ caminho: string,
2954
+ parametros: string,
2955
+ tiposGlobais: Map<string, TipoPythonDescoberto>,
2956
+ entitiesRef: Set<string>,
2957
+ enumsRef: Set<string>,
2958
+ ): CampoImportado[] {
2959
+ const assinatura = extrairAssinaturaParametrosPython(parametros);
2960
+ return extrairParametrosCaminhoFlask(caminho).map((parametro) => {
2961
+ const correspondente = assinatura.get(parametro.nome);
2962
+ return {
2963
+ nome: paraSnakeCase(parametro.nome),
2964
+ tipo: correspondente?.tipoTexto
2965
+ ? mapearTipoPythonParaSema(correspondente.tipoTexto, tiposGlobais, entitiesRef, enumsRef)
2966
+ : mapearConversorFlaskParaSema(parametro.conversor),
2967
+ obrigatorio: correspondente?.obrigatorio ?? true,
2968
+ };
2969
+ });
2970
+ }
2971
+
2972
+ async function importarPythonBase(
2973
+ diretorio: string,
2974
+ namespaceBase: string,
2975
+ modoHttp: ModoHttpPython = "nenhum",
2976
+ ): Promise<ModuloImportado[]> {
2977
+ const arquivos = (await listarArquivosRecursivos(diretorio, [".py"]))
2978
+ .filter((arquivo) => !arquivo.endsWith("__init__.py") && !/tests?[\\/]/i.test(arquivo));
2979
+
2980
+ const textos = new Map<string, string>();
2981
+ const tiposGlobais = new Map<string, TipoPythonDescoberto>();
2982
+ for (const arquivo of arquivos) {
2983
+ const texto = await readFile(arquivo, "utf8");
2984
+ textos.set(arquivo, texto);
2985
+ for (const [nome, tipo] of extrairTiposPython(texto)) {
2986
+ if (!tiposGlobais.has(nome)) {
2987
+ tiposGlobais.set(nome, tipo);
2988
+ }
2989
+ }
2990
+ }
2991
+
2992
+ const modulos = new Map<string, ModuloImportado>();
2993
+
2994
+ for (const arquivo of arquivos) {
2995
+ const texto = textos.get(arquivo)!;
2996
+ const relacao = path.relative(diretorio, arquivo);
2997
+ const contextoSegmentos = inferirContextoPorArquivo(relacao);
2998
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
2999
+ const entitiesRef = new Set<string>();
3000
+ const enumsRef = new Set<string>();
3001
+ const tasks: TarefaImportada[] = [];
3002
+ const routes: RotaImportada[] = [];
3003
+
3004
+ if (modoHttp === "fastapi") {
3005
+ const prefixo = texto.match(/APIRouter\s*\(\s*prefix\s*=\s*["']([^"']+)["']/)?.[1];
3006
+ const routeRegex = /@(?:router|app)\.(get|post|put|patch|delete)\(([^)]*)\)\s*\n(?:async\s+)?def\s+(\w+)\(([^)]*)\)(?:\s*->\s*([^:]+))?:/g;
3007
+ for (const match of texto.matchAll(routeRegex)) {
3008
+ const metodo = match[1]!.toUpperCase();
3009
+ const argumentoDecorator = match[2] ?? "";
3010
+ const sufixo = argumentoDecorator.match(/["']([^"']+)["']/)?.[1];
3011
+ const nomeFuncao = match[3]!;
3012
+ const parametros = match[4]!;
3013
+ const retorno = match[5]?.trim();
3014
+ const routeInput = parametros
3015
+ .split(",")
3016
+ .map((item) => item.trim())
3017
+ .filter(Boolean)
3018
+ .filter((item) => !item.startsWith("self") && !item.startsWith("cls"))
3019
+ .flatMap((item) => {
3020
+ const [nome, tipo] = item.split(":").map((parte) => parte.trim());
3021
+ const obrigatorio = !item.includes("=");
3022
+ return expandirCamposPython(nome, tipo, tiposGlobais, entitiesRef, enumsRef, obrigatorio);
3023
+ });
3024
+ const routeOutput = retorno && mapearTipoPrimitivo(retorno) === "Vazio"
3025
+ ? []
3026
+ : deduplicarCampos(expandirCamposPython("resultado", retorno, tiposGlobais, entitiesRef, enumsRef, false));
3027
+ const taskNome = paraSnakeCase(nomeFuncao);
3028
+ routes.push({
3029
+ nome: `${taskNome}_publico`,
3030
+ resumo: `Rota FastAPI importada automaticamente de ${relacao}#${nomeFuncao}.`,
3031
+ metodo,
3032
+ caminho: juntarCaminhoHttp(prefixo, sufixo),
3033
+ task: taskNome,
3034
+ input: deduplicarCampos(routeInput),
3035
+ output: routeOutput,
3036
+ errors: [],
3037
+ });
3038
+ }
3039
+ } else if (modoHttp === "flask") {
3040
+ for (const rota of extrairRotasFlaskDecoradas(texto)) {
3041
+ const taskNome = paraSnakeCase(rota.nomeFuncao);
3042
+ const nomeBase = `${taskNome}_publico`;
3043
+ const nome = routes.some((route) => route.nome === nomeBase)
3044
+ ? `${taskNome}_${rota.metodo.toLowerCase()}_publico`
3045
+ : nomeBase;
3046
+ routes.push({
3047
+ nome,
3048
+ resumo: `Rota Flask importada automaticamente de ${relacao}#${rota.nomeFuncao}.`,
3049
+ metodo: rota.metodo,
3050
+ caminho: rota.caminho,
3051
+ task: taskNome,
3052
+ input: deduplicarCampos(criarInputRotaFlask(rota.caminho, rota.parametros, tiposGlobais, entitiesRef, enumsRef)),
3053
+ output: [],
3054
+ errors: [],
3055
+ });
3056
+ }
3057
+ }
3058
+
3059
+ const funcRegex = /^(async\s+def|def)\s+(\w+)\(([^)]*)\)(?:\s*->\s*([^:]+))?:/gm;
3060
+ for (const match of texto.matchAll(funcRegex)) {
3061
+ const nomeFuncao = match[2]!;
3062
+ if (nomeFuncao.startsWith("_")) {
3063
+ continue;
3064
+ }
3065
+ const parametros = match[3]!;
3066
+ const retorno = match[4]?.trim();
3067
+ const inicioCorpo = match.index ?? 0;
3068
+ const trecho = texto.slice(inicioCorpo, Math.min(texto.length, inicioCorpo + 1500));
3069
+ const input = parametros
3070
+ .split(",")
3071
+ .map((item) => item.trim())
3072
+ .filter(Boolean)
3073
+ .filter((item) => !item.startsWith("self") && !item.startsWith("cls"))
3074
+ .flatMap((item) => {
3075
+ const [nome, tipo] = item.split(":").map((parte) => parte.trim());
3076
+ const obrigatorio = !item.includes("=");
3077
+ return expandirCamposPython(nome, tipo, tiposGlobais, entitiesRef, enumsRef, obrigatorio);
3078
+ });
3079
+ const output = retorno && mapearTipoPrimitivo(retorno) === "Vazio"
3080
+ ? []
3081
+ : deduplicarCampos(expandirCamposPython("resultado", retorno, tiposGlobais, entitiesRef, enumsRef, false));
3082
+ tasks.push({
3083
+ nome: paraSnakeCase(nomeFuncao),
3084
+ resumo: `Task importada automaticamente de ${relacao}#${nomeFuncao}.`,
3085
+ input: deduplicarCampos(input),
3086
+ output,
3087
+ errors: extrairErrosPython(trecho),
3088
+ effects: descreverEfeitosPorHeuristica(trecho),
3089
+ impl: { py: caminhoImplPython(diretorio, arquivo, nomeFuncao) },
3090
+ origemArquivo: relacao,
3091
+ origemSimbolo: nomeFuncao,
3092
+ });
3093
+ }
3094
+
3095
+ const classRegex = /^class\s+(\w+)(?:\(([^)]*)\))?:\n((?:^[ \t].*(?:\n|$))*)/gm;
3096
+ for (const match of texto.matchAll(classRegex)) {
3097
+ const nomeClasse = match[1]!;
3098
+ const bases = match[2] ?? "";
3099
+ const corpo = match[3]!;
3100
+ if (/(BaseModel|Enum|StrEnum)/.test(bases)) {
3101
+ continue;
3102
+ }
3103
+ for (const metodo of corpo.matchAll(/^\s{4}(?:async\s+def|def)\s+(\w+)\(([^)]*)\)(?:\s*->\s*([^:]+))?:/gm)) {
3104
+ const nomeMetodo = metodo[1]!;
3105
+ if (nomeMetodo.startsWith("_")) {
3106
+ continue;
3107
+ }
3108
+ const parametros = metodo[2]!;
3109
+ const retorno = metodo[3]?.trim();
3110
+ const input = parametros
3111
+ .split(",")
3112
+ .map((item) => item.trim())
3113
+ .filter(Boolean)
3114
+ .filter((item) => !item.startsWith("self") && !item.startsWith("cls"))
3115
+ .flatMap((item) => {
3116
+ const [nome, tipo] = item.split(":").map((parte) => parte.trim());
3117
+ const obrigatorio = !item.includes("=");
3118
+ return expandirCamposPython(nome, tipo, tiposGlobais, entitiesRef, enumsRef, obrigatorio);
3119
+ });
3120
+ const output = retorno && mapearTipoPrimitivo(retorno) === "Vazio"
3121
+ ? []
3122
+ : deduplicarCampos(expandirCamposPython("resultado", retorno, tiposGlobais, entitiesRef, enumsRef, false));
3123
+ tasks.push({
3124
+ nome: paraSnakeCase(nomeMetodo),
3125
+ resumo: `Task importada automaticamente de ${relacao}#${nomeClasse}.${nomeMetodo}.`,
3126
+ input: deduplicarCampos(input),
3127
+ output,
3128
+ errors: extrairErrosPython(corpo),
3129
+ effects: descreverEfeitosPorHeuristica(corpo),
3130
+ impl: { py: caminhoImplPython(diretorio, arquivo, `${nomeClasse}.${nomeMetodo}`) },
3131
+ origemArquivo: relacao,
3132
+ origemSimbolo: `${nomeClasse}.${nomeMetodo}`,
3133
+ });
3134
+ }
3135
+ }
3136
+
3137
+ if (!tasks.length && !routes.length) {
3138
+ continue;
3139
+ }
3140
+
3141
+ const { entities, enums } = criarEntidadesPython(tiposGlobais, entitiesRef, enumsRef);
3142
+ sincronizarRotasComTasks(routes, tasks);
3143
+ modulos.set(nomeModulo, {
3144
+ nome: nomeModulo,
3145
+ resumo: `Rascunho Sema importado automaticamente de ${relacao}.`,
3146
+ tasks: deduplicarTarefas(tasks),
3147
+ routes: deduplicarRotas(routes),
3148
+ entities,
3149
+ enums,
3150
+ });
3151
+ }
3152
+
3153
+ return [...modulos.values()];
3154
+ }
3155
+
3156
+ async function importarDartBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
3157
+ const arquivos = (await listarArquivosRecursivos(diretorio, [".dart"]))
3158
+ .filter((arquivo) => !arquivo.endsWith(".g.dart") && !arquivo.endsWith(".freezed.dart"));
3159
+ const modulos: ModuloImportado[] = [];
3160
+
3161
+ for (const arquivo of arquivos) {
3162
+ const texto = await readFile(arquivo, "utf8");
3163
+ const relacao = path.relative(diretorio, arquivo);
3164
+ const contextoSegmentos = inferirContextoPorArquivo(relacao);
3165
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
3166
+ const tasks: TarefaImportada[] = [];
3167
+
3168
+ for (const match of texto.matchAll(/(?:Future<([^\n]+)>|([\w?<>.,\s]+))\s+(\w+)\(([^)]*)\)\s*(?:async\s*)?\{/g)) {
3169
+ const retorno = (match[1] ?? match[2] ?? "").trim();
3170
+ const nome = match[3]!;
3171
+ if (["build", "toString", "hashCode"].includes(nome)) {
3172
+ continue;
3173
+ }
3174
+ const parametros = match[4]!;
3175
+ const input = parametros
3176
+ .split(",")
3177
+ .map((item) => item.trim())
3178
+ .filter(Boolean)
3179
+ .map((item) => item.replace(/^(required|final)\s+/g, ""))
3180
+ .map((item) => {
3181
+ const partes = item.split(/\s+/);
3182
+ const nomeParametro = partes.at(-1) ?? "param";
3183
+ const tipoParametro = partes.length > 1 ? partes.slice(0, -1).join(" ") : undefined;
3184
+ return {
3185
+ nome: paraSnakeCase(nomeParametro),
3186
+ tipo: mapearTipoPrimitivo(tipoParametro ?? "Json"),
3187
+ obrigatorio: item.includes("required"),
3188
+ };
3189
+ });
3190
+ const output = retorno && mapearTipoPrimitivo(retorno) === "Vazio"
3191
+ ? []
3192
+ : [{ nome: "resultado", tipo: mapearTipoPrimitivo(retorno || "Json"), obrigatorio: false }];
3193
+ tasks.push({
3194
+ nome: paraSnakeCase(nome),
3195
+ resumo: `Task importada automaticamente de ${relacao}#${nome}.`,
3196
+ input,
3197
+ output,
3198
+ errors: [],
3199
+ effects: descreverEfeitosPorHeuristica(texto),
3200
+ impl: { dart: caminhoImplDart(diretorio, arquivo, nome) },
3201
+ origemArquivo: relacao,
3202
+ origemSimbolo: nome,
3203
+ });
3204
+ }
3205
+
3206
+ if (tasks.length === 0) {
3207
+ continue;
3208
+ }
3209
+
3210
+ modulos.push({
3211
+ nome: nomeModulo,
3212
+ resumo: `Rascunho Sema importado automaticamente de ${relacao}.`,
3213
+ tasks: deduplicarTarefas(tasks),
3214
+ routes: [],
3215
+ entities: [],
3216
+ enums: [],
3217
+ });
3218
+ }
3219
+
3220
+ return modulos;
3221
+ }
3222
+
3223
+ async function importarLuaBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
3224
+ const arquivos = (await listarArquivosRecursivos(diretorio, [".lua"]))
3225
+ .filter((arquivo) => !/(^|[\\/])tests?([\\/]|$)|(?:^|[\\/])spec([\\/]|$)|(?:_spec|_test)\.lua$/i.test(arquivo));
3226
+ const modulos: ModuloImportado[] = [];
3227
+
3228
+ for (const arquivo of arquivos) {
3229
+ const texto = await readFile(arquivo, "utf8");
3230
+ const relacao = path.relative(diretorio, arquivo);
3231
+ const contextoSegmentos = inferirContextoPorArquivo(relacao);
3232
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
3233
+ const tasks: TarefaImportada[] = [];
3234
+
3235
+ for (const simbolo of selecionarSimbolosPreferidos(extrairSimbolosLua(texto))) {
3236
+ const nomeBase = simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo;
3237
+ tasks.push({
3238
+ nome: paraSnakeCase(nomeBase),
3239
+ resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
3240
+ input: simbolo.parametros.map((parametro) => ({
3241
+ nome: normalizarNomeCampoImportado(parametro.nome),
3242
+ tipo: mapearTipoPrimitivo(parametro.tipoTexto ?? "Json"),
3243
+ obrigatorio: parametro.obrigatorio,
3244
+ })),
3245
+ output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
3246
+ errors: extrairErrosLua(texto),
3247
+ effects: descreverEfeitosPorHeuristica(texto),
3248
+ impl: { lua: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo) },
3249
+ origemArquivo: relacao,
3250
+ origemSimbolo: simbolo.simbolo,
3251
+ });
3252
+ }
3253
+
3254
+ if (tasks.length === 0) {
3255
+ continue;
3256
+ }
3257
+
3258
+ modulos.push({
3259
+ nome: nomeModulo,
3260
+ resumo: `Rascunho Sema importado automaticamente de ${relacao}.`,
3261
+ tasks: deduplicarTarefas(tasks),
3262
+ routes: [],
3263
+ entities: [],
3264
+ enums: [],
3265
+ databases: inferirDatabasesPorHeuristica(texto, relacao),
3266
+ });
3267
+ }
3268
+
3269
+ return modulos;
3270
+ }
3271
+
3272
+ async function importarPhpBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
3273
+ const arquivos = (await listarArquivosRecursivos(diretorio, [".php"]))
3274
+ .filter((arquivo) => !/(^|[\\/])tests?([\\/]|$)|(?:^|[\\/])spec([\\/]|$)|(?:Test|Spec)\.php$/i.test(arquivo));
3275
+ const modulos = new Map<string, ModuloImportado>();
3276
+
3277
+ for (const arquivo of arquivos) {
3278
+ const texto = await readFile(arquivo, "utf8");
3279
+ const relacao = path.relative(diretorio, arquivo);
3280
+ const contextoSegmentos = inferirContextoPorArquivo(relacao);
3281
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
3282
+ const tasks: TarefaImportada[] = [];
3283
+ const routes: RotaImportada[] = [];
3284
+
3285
+ for (const simbolo of selecionarSimbolosPreferidos(extrairSimbolosPhp(texto))) {
3286
+ const nomeBase = simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo;
3287
+ tasks.push({
3288
+ nome: normalizarNomeTaskImportada(nomeBase),
3289
+ resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
3290
+ input: simbolo.parametros.map((parametro) => ({
3291
+ nome: normalizarNomeCampoImportado(parametro.nome),
3292
+ tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
3293
+ obrigatorio: parametro.obrigatorio,
3294
+ })),
3295
+ output: criarCampoResultadoBackend(simbolo.retorno),
3296
+ errors: extrairErrosPhp(texto),
3297
+ effects: descreverEfeitosPorHeuristica(texto),
3298
+ impl: { php: caminhoImplPhp(diretorio, arquivo, simbolo.simbolo) },
3299
+ origemArquivo: relacao,
3300
+ origemSimbolo: simbolo.simbolo,
3301
+ });
3302
+ }
3303
+
3304
+ for (const rota of extrairRotasPhp(texto)) {
3305
+ const taskNome = normalizarNomeTaskImportada(rota.simbolo.split(".").at(-1) ?? rota.simbolo);
3306
+ const output = rota.retorno ? criarCampoResultadoBackend(rota.retorno) : [{ nome: "resultado", tipo: "Json", obrigatorio: false }];
3307
+ const nomeBase = `${taskNome}_publico`;
3308
+ const nome = routes.some((route) => route.nome === nomeBase)
3309
+ ? `${taskNome}_${rota.metodo.toLowerCase()}_publico`
3310
+ : nomeBase;
3311
+ tasks.push({
3312
+ nome: taskNome,
3313
+ resumo: `Task HTTP PHP importada automaticamente de ${relacao}#${rota.simbolo}.`,
3314
+ input: camposDeParametrosRotaBackend(rota.parametros),
3315
+ output,
3316
+ errors: extrairErrosPhp(texto),
3317
+ effects: [{ categoria: "consulta", alvo: "http", criticidade: "media" }],
3318
+ impl: { php: caminhoImplPhp(diretorio, arquivo, rota.simbolo) },
3319
+ origemArquivo: relacao,
3320
+ origemSimbolo: rota.simbolo,
3321
+ });
3322
+ routes.push({
3323
+ nome,
3324
+ resumo: `Rota PHP importada automaticamente de ${relacao}#${rota.simbolo}.`,
3325
+ metodo: rota.metodo,
3326
+ caminho: rota.caminho,
3327
+ task: taskNome,
3328
+ input: camposDeParametrosRotaBackend(rota.parametros),
3329
+ output,
3330
+ errors: [],
3331
+ });
3332
+ }
3333
+
3334
+ if (tasks.length === 0 && routes.length === 0) {
3335
+ continue;
3336
+ }
3337
+
3338
+ acumularModuloImportado(modulos, criarModuloImportadoSimples(
3339
+ nomeModulo,
3340
+ `Rascunho Sema importado automaticamente de ${relacao}.`,
3341
+ tasks,
3342
+ routes,
3343
+ [],
3344
+ inferirDatabasesPorHeuristica(texto, relacao),
3345
+ ));
3346
+ }
3347
+
3348
+ return [...modulos.values()];
3349
+ }
3350
+
3351
+ function criarModuloImportadoSimples(
3352
+ nome: string,
3353
+ resumo: string,
3354
+ tasks: TarefaImportada[],
3355
+ routes: RotaImportada[] = [],
3356
+ vinculos: VinculoImportado[] = [],
3357
+ databases: DatabaseImportado[] = [],
3358
+ ): ModuloImportado {
3359
+ sincronizarRotasComTasks(routes, tasks);
3360
+ return {
3361
+ nome,
3362
+ resumo,
3363
+ tasks: deduplicarTarefas(tasks),
3364
+ routes: deduplicarRotas(routes),
3365
+ entities: [],
3366
+ enums: [],
3367
+ databases: deduplicarDatabases(databases),
3368
+ vinculos: deduplicarVinculos(vinculos),
3369
+ };
3370
+ }
3371
+
3372
+ function acumularModuloImportado(
3373
+ modulos: Map<string, ModuloImportado>,
3374
+ modulo: ModuloImportado,
3375
+ ): void {
3376
+ const existente = modulos.get(modulo.nome);
3377
+ if (!existente) {
3378
+ modulos.set(modulo.nome, modulo);
3379
+ return;
3380
+ }
3381
+
3382
+ existente.tasks = deduplicarTarefas([...existente.tasks, ...modulo.tasks]);
3383
+ existente.routes = deduplicarRotas([...existente.routes, ...modulo.routes]);
3384
+ existente.entities = deduplicarEntidades([...existente.entities, ...modulo.entities]);
3385
+ existente.enums = deduplicarEnums([...existente.enums, ...modulo.enums]);
3386
+ existente.databases = deduplicarDatabases([...(existente.databases ?? []), ...(modulo.databases ?? [])]);
3387
+ existente.vinculos = deduplicarVinculos([...(existente.vinculos ?? []), ...(modulo.vinculos ?? [])]);
3388
+ }
3389
+
3390
+ function selecionarSimbolosPreferidos<T extends { simbolo: string }>(simbolos: T[]): T[] {
3391
+ const mapa = new Map<string, T>();
3392
+ for (const simbolo of simbolos) {
3393
+ const chave = simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo;
3394
+ const existente = mapa.get(chave);
3395
+ if (!existente) {
3396
+ mapa.set(chave, simbolo);
3397
+ continue;
3398
+ }
3399
+ const pontuacaoAtual = simbolo.simbolo.split(".").length;
3400
+ const pontuacaoExistente = existente.simbolo.split(".").length;
3401
+ if (pontuacaoAtual > pontuacaoExistente) {
3402
+ mapa.set(chave, simbolo);
3403
+ }
3404
+ }
3405
+ return [...mapa.values()];
3406
+ }
3407
+
3408
+ async function existeArquivo(caminho: string): Promise<boolean> {
3409
+ try {
3410
+ await access(caminho);
3411
+ return true;
3412
+ } catch {
3413
+ return false;
3414
+ }
3415
+ }
3416
+
3417
+ async function resolverArquivoRustParaSimbolo(
3418
+ diretorio: string,
3419
+ relacaoFonte: string,
3420
+ simbolo: string,
3421
+ ): Promise<string> {
3422
+ const partes = simbolo.split(".").filter(Boolean);
3423
+ if (partes.length <= 1) {
3424
+ return path.join(diretorio, relacaoFonte);
3425
+ }
3426
+
3427
+ const moduloPartes = partes.slice(0, -1);
3428
+ const baseAtual = path.dirname(relacaoFonte);
3429
+ const candidatos = [
3430
+ path.join(baseAtual, ...moduloPartes) + ".rs",
3431
+ path.join(baseAtual, ...moduloPartes, "mod.rs"),
3432
+ path.join("src", ...moduloPartes) + ".rs",
3433
+ path.join("src", ...moduloPartes, "mod.rs"),
3434
+ ];
3435
+
3436
+ for (const candidato of candidatos) {
3437
+ const absoluto = path.join(diretorio, candidato);
3438
+ if (await existeArquivo(absoluto)) {
3439
+ return absoluto;
3440
+ }
3441
+ }
3442
+
3443
+ return path.join(diretorio, relacaoFonte);
3444
+ }
3445
+
3446
+ async function importarDotnetBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
3447
+ const arquivos = (await listarArquivosRecursivos(diretorio, [".cs"]))
3448
+ .filter((arquivo) => !/(^|[\\/])(bin|obj|Test[s]?)([\\/]|$)/i.test(arquivo));
3449
+ const modulos = new Map<string, ModuloImportado>();
3450
+
3451
+ for (const arquivo of arquivos) {
3452
+ const texto = await readFile(arquivo, "utf8");
3453
+ const relacao = path.relative(diretorio, arquivo);
3454
+ const contextoSegmentos = inferirContextoPorArquivo(relacao, { preservarUltimo: true, snakeCaseUltimo: true });
3455
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
3456
+ const tasks: TarefaImportada[] = [];
3457
+ const routes: RotaImportada[] = [];
3458
+
3459
+ for (const simbolo of extrairSimbolosDotnet(texto)) {
3460
+ const taskNome = paraSnakeCase(simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo);
3461
+ tasks.push({
3462
+ nome: taskNome,
3463
+ resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
3464
+ input: simbolo.parametros.map((parametro) => ({
3465
+ nome: paraSnakeCase(parametro.nome),
3466
+ tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
3467
+ obrigatorio: parametro.obrigatorio,
3468
+ })),
3469
+ output: criarCampoResultadoBackend(simbolo.retorno),
3470
+ errors: [],
3471
+ effects: descreverEfeitosPorHeuristica(texto),
3472
+ impl: { cs: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo, { snakeCaseUltimoArquivo: true }) },
3473
+ origemArquivo: relacao,
3474
+ origemSimbolo: simbolo.simbolo,
3475
+ });
3476
+ }
3477
+
3478
+ for (const rota of extrairRotasDotnet(texto)) {
3479
+ const taskNome = paraSnakeCase(rota.simbolo.split(".").at(-1) ?? rota.simbolo);
3480
+ const output = criarCampoResultadoBackend(rota.retorno);
3481
+ tasks.push({
3482
+ nome: taskNome,
3483
+ resumo: `Task HTTP ASP.NET Core importada automaticamente de ${relacao}#${rota.simbolo}.`,
3484
+ input: camposDeParametrosRotaBackend(rota.parametros),
3485
+ output,
3486
+ errors: [],
3487
+ effects: [{ categoria: "consulta", alvo: "http", criticidade: "media" }],
3488
+ impl: { cs: caminhoImplGenerico(diretorio, arquivo, rota.simbolo, { snakeCaseUltimoArquivo: true }) },
3489
+ origemArquivo: relacao,
3490
+ origemSimbolo: rota.simbolo,
3491
+ });
3492
+ routes.push({
3493
+ nome: `${taskNome}_publico`,
3494
+ resumo: `Rota ASP.NET Core importada automaticamente de ${relacao}#${rota.simbolo}.`,
3495
+ metodo: rota.metodo,
3496
+ caminho: rota.caminho,
3497
+ task: taskNome,
3498
+ input: camposDeParametrosRotaBackend(rota.parametros),
3499
+ output,
3500
+ errors: [],
3501
+ });
3502
+ }
3503
+
3504
+ if (tasks.length === 0 && routes.length === 0) {
3505
+ continue;
3506
+ }
3507
+
3508
+ acumularModuloImportado(modulos, criarModuloImportadoSimples(
3509
+ nomeModulo,
3510
+ `Rascunho Sema importado automaticamente de ${relacao}.`,
3511
+ tasks,
3512
+ routes,
3513
+ [],
3514
+ inferirDatabasesPorHeuristica(texto, relacao),
3515
+ ));
3516
+ }
3517
+
3518
+ return [...modulos.values()];
3519
+ }
3520
+
3521
+ async function importarJavaBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
3522
+ const arquivos = (await listarArquivosRecursivos(diretorio, [".java"]))
3523
+ .filter((arquivo) => !/(^|[\\/])(target|build|out|Test[s]?)([\\/]|$)/i.test(arquivo));
3524
+ const modulos = new Map<string, ModuloImportado>();
3525
+
3526
+ for (const arquivo of arquivos) {
3527
+ const texto = await readFile(arquivo, "utf8");
3528
+ const relacao = path.relative(diretorio, arquivo);
3529
+ const contextoSegmentos = inferirContextoPorArquivo(relacao, { preservarUltimo: true, snakeCaseUltimo: true });
3530
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
3531
+ const tasks: TarefaImportada[] = [];
3532
+ const routes: RotaImportada[] = [];
3533
+
3534
+ for (const simbolo of extrairSimbolosJava(texto)) {
3535
+ const taskNome = paraSnakeCase(simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo);
3536
+ tasks.push({
3537
+ nome: taskNome,
3538
+ resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
3539
+ input: simbolo.parametros.map((parametro) => ({
3540
+ nome: paraSnakeCase(parametro.nome),
3541
+ tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
3542
+ obrigatorio: parametro.obrigatorio,
3543
+ })),
3544
+ output: criarCampoResultadoBackend(simbolo.retorno),
3545
+ errors: [],
3546
+ effects: descreverEfeitosPorHeuristica(texto),
3547
+ impl: { java: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo, { snakeCaseUltimoArquivo: true }) },
3548
+ origemArquivo: relacao,
3549
+ origemSimbolo: simbolo.simbolo,
3550
+ });
3551
+ }
3552
+
3553
+ for (const rota of extrairRotasJava(texto)) {
3554
+ const taskNome = paraSnakeCase(rota.simbolo.split(".").at(-1) ?? rota.simbolo);
3555
+ const output = criarCampoResultadoBackend(rota.retorno);
3556
+ tasks.push({
3557
+ nome: taskNome,
3558
+ resumo: `Task HTTP Spring Boot importada automaticamente de ${relacao}#${rota.simbolo}.`,
3559
+ input: camposDeParametrosRotaBackend(rota.parametros),
3560
+ output,
3561
+ errors: [],
3562
+ effects: [{ categoria: "consulta", alvo: "http", criticidade: "media" }],
3563
+ impl: { java: caminhoImplGenerico(diretorio, arquivo, rota.simbolo, { snakeCaseUltimoArquivo: true }) },
3564
+ origemArquivo: relacao,
3565
+ origemSimbolo: rota.simbolo,
3566
+ });
3567
+ routes.push({
3568
+ nome: `${taskNome}_publico`,
3569
+ resumo: `Rota Spring Boot importada automaticamente de ${relacao}#${rota.simbolo}.`,
3570
+ metodo: rota.metodo,
3571
+ caminho: rota.caminho,
3572
+ task: taskNome,
3573
+ input: camposDeParametrosRotaBackend(rota.parametros),
3574
+ output,
3575
+ errors: [],
3576
+ });
3577
+ }
3578
+
3579
+ if (tasks.length === 0 && routes.length === 0) {
3580
+ continue;
3581
+ }
3582
+
3583
+ acumularModuloImportado(modulos, criarModuloImportadoSimples(
3584
+ nomeModulo,
3585
+ `Rascunho Sema importado automaticamente de ${relacao}.`,
3586
+ tasks,
3587
+ routes,
3588
+ [],
3589
+ inferirDatabasesPorHeuristica(texto, relacao),
3590
+ ));
3591
+ }
3592
+
3593
+ return [...modulos.values()];
3594
+ }
3595
+
3596
+ async function importarGoBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
3597
+ const arquivos = await listarArquivosRecursivos(diretorio, [".go"]);
3598
+ const modulos = new Map<string, ModuloImportado>();
3599
+
3600
+ for (const arquivo of arquivos) {
3601
+ const texto = await readFile(arquivo, "utf8");
3602
+ const relacao = path.relative(diretorio, arquivo);
3603
+ const contextoSegmentos = inferirContextoPorArquivo(relacao);
3604
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
3605
+ const tasks: TarefaImportada[] = [];
3606
+ const routes: RotaImportada[] = [];
3607
+
3608
+ for (const simbolo of extrairSimbolosGo(texto)) {
3609
+ const taskNome = paraSnakeCase(simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo);
3610
+ tasks.push({
3611
+ nome: taskNome,
3612
+ resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
3613
+ input: simbolo.parametros.map((parametro) => ({
3614
+ nome: paraSnakeCase(parametro.nome),
3615
+ tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
3616
+ obrigatorio: parametro.obrigatorio,
3617
+ })),
3618
+ output: criarCampoResultadoBackend(simbolo.retorno),
3619
+ errors: [],
3620
+ effects: descreverEfeitosPorHeuristica(texto),
3621
+ impl: { go: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo) },
3622
+ origemArquivo: relacao,
3623
+ origemSimbolo: simbolo.simbolo,
3624
+ });
3625
+ }
3626
+
3627
+ for (const rota of extrairRotasGo(texto)) {
3628
+ const taskNome = paraSnakeCase(rota.simbolo.split(".").at(-1) ?? rota.simbolo);
3629
+ tasks.push({
3630
+ nome: taskNome,
3631
+ resumo: `Task HTTP Go importada automaticamente de ${relacao}#${rota.simbolo}.`,
3632
+ input: camposDeParametrosRotaBackend(rota.parametros),
3633
+ output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
3634
+ errors: [],
3635
+ effects: [{ categoria: "consulta", alvo: "http", criticidade: "media" }],
3636
+ impl: { go: caminhoImplGenerico(diretorio, arquivo, rota.simbolo) },
3637
+ origemArquivo: relacao,
3638
+ origemSimbolo: rota.simbolo,
3639
+ });
3640
+ routes.push({
3641
+ nome: `${taskNome}_publico`,
3642
+ resumo: `Rota Go importada automaticamente de ${relacao}#${rota.simbolo}.`,
3643
+ metodo: rota.metodo,
3644
+ caminho: rota.caminho,
3645
+ task: taskNome,
3646
+ input: camposDeParametrosRotaBackend(rota.parametros),
3647
+ output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
3648
+ errors: [],
3649
+ });
3650
+ }
3651
+
3652
+ if (tasks.length === 0 && routes.length === 0) {
3653
+ continue;
3654
+ }
3655
+
3656
+ modulos.set(nomeModulo, criarModuloImportadoSimples(
3657
+ nomeModulo,
3658
+ `Rascunho Sema importado automaticamente de ${relacao}.`,
3659
+ tasks,
3660
+ routes,
3661
+ [],
3662
+ inferirDatabasesPorHeuristica(texto, relacao),
3663
+ ));
3664
+ }
3665
+
3666
+ return [...modulos.values()];
3667
+ }
3668
+
3669
+ async function importarRustBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
3670
+ const arquivos = await listarArquivosRecursivos(diretorio, [".rs"]);
3671
+ const modulos = new Map<string, ModuloImportado>();
3672
+
3673
+ for (const arquivo of arquivos) {
3674
+ const texto = await readFile(arquivo, "utf8");
3675
+ const relacao = path.relative(diretorio, arquivo);
3676
+ const contextoSegmentos = inferirContextoPorArquivo(relacao);
3677
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
3678
+ const tasks: TarefaImportada[] = [];
3679
+ const routes: RotaImportada[] = [];
3680
+
3681
+ for (const simbolo of extrairSimbolosRust(texto)) {
3682
+ const taskNome = paraSnakeCase(simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo);
3683
+ tasks.push({
3684
+ nome: taskNome,
3685
+ resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
3686
+ input: simbolo.parametros.map((parametro) => ({
3687
+ nome: paraSnakeCase(parametro.nome),
3688
+ tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
3689
+ obrigatorio: parametro.obrigatorio,
3690
+ })),
3691
+ output: criarCampoResultadoBackend(simbolo.retorno),
3692
+ errors: [],
3693
+ effects: descreverEfeitosPorHeuristica(texto),
3694
+ impl: { rust: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo) },
3695
+ origemArquivo: relacao,
3696
+ origemSimbolo: simbolo.simbolo,
3697
+ });
3698
+ }
3699
+
3700
+ acumularModuloImportado(modulos, criarModuloImportadoSimples(
3701
+ nomeModulo,
3702
+ `Rascunho Sema importado automaticamente de ${relacao}.`,
3703
+ tasks,
3704
+ routes,
3705
+ [],
3706
+ inferirDatabasesPorHeuristica(texto, relacao),
3707
+ ));
3708
+
3709
+ for (const rota of extrairRotasRust(texto)) {
3710
+ const simboloLimpo = rota.simbolo.replace(/::/g, ".");
3711
+ const nomeSimbolo = simboloLimpo.split(".").at(-1) ?? simboloLimpo;
3712
+ const arquivoAlvo = await resolverArquivoRustParaSimbolo(diretorio, relacao, simboloLimpo);
3713
+ const relacaoAlvo = path.relative(diretorio, arquivoAlvo);
3714
+ const moduloAlvo = [namespaceBase, ...inferirContextoPorArquivo(relacaoAlvo)].join(".");
3715
+ const taskNome = paraSnakeCase(rota.simbolo.split(".").at(-1) ?? rota.simbolo);
3716
+ const task: TarefaImportada = {
3717
+ nome: taskNome,
3718
+ resumo: `Task HTTP Axum importada automaticamente de ${relacao}#${rota.simbolo}.`,
3719
+ input: camposDeParametrosRotaBackend(rota.parametros),
3720
+ output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
3721
+ errors: [],
3722
+ effects: [{ categoria: "consulta", alvo: "http", criticidade: "media" }],
3723
+ impl: { rust: caminhoImplGenerico(diretorio, arquivoAlvo, nomeSimbolo) },
3724
+ origemArquivo: relacaoAlvo,
3725
+ origemSimbolo: nomeSimbolo,
3726
+ };
3727
+ const route: RotaImportada = {
3728
+ nome: `${taskNome}_publico`,
3729
+ resumo: `Rota Axum importada automaticamente de ${relacao}#${rota.simbolo}.`,
3730
+ metodo: rota.metodo,
3731
+ caminho: rota.caminho,
3732
+ task: taskNome,
3733
+ input: camposDeParametrosRotaBackend(rota.parametros),
3734
+ output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
3735
+ errors: [],
3736
+ };
3737
+ acumularModuloImportado(modulos, criarModuloImportadoSimples(
3738
+ moduloAlvo,
3739
+ `Rascunho Sema importado automaticamente de ${relacaoAlvo}.`,
3740
+ [task],
3741
+ [route],
3742
+ [],
3743
+ inferirDatabasesPorHeuristica(texto, relacao),
3744
+ ));
3745
+ }
3746
+ }
3747
+
3748
+ return [...modulos.values()];
3749
+ }
3750
+
3751
+ async function importarCppBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]> {
3752
+ const arquivos = (await listarArquivosRecursivos(diretorio, [".cpp", ".cc", ".cxx", ".hpp", ".h"]))
3753
+ .filter((arquivo) => !/(^|[\\/])(windows|linux|macos|runner|flutter|ephemeral|build|vendor)([\\/]|$)/i.test(arquivo));
3754
+ const modulos = new Map<string, ModuloImportado>();
3755
+
3756
+ for (const arquivo of arquivos) {
3757
+ const texto = await readFile(arquivo, "utf8");
3758
+ const relacao = path.relative(diretorio, arquivo);
3759
+ const contextoSegmentos = inferirContextoPorArquivo(relacao);
3760
+ const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
3761
+ const tasks: TarefaImportada[] = [];
3762
+
3763
+ for (const simbolo of selecionarSimbolosPreferidos(extrairSimbolosCpp(texto))) {
3764
+ const taskNome = paraSnakeCase(simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo);
3765
+ tasks.push({
3766
+ nome: taskNome,
3767
+ resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
3768
+ input: simbolo.parametros.map((parametro) => ({
3769
+ nome: paraSnakeCase(parametro.nome),
3770
+ tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
3771
+ obrigatorio: parametro.obrigatorio,
3772
+ })),
3773
+ output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
3774
+ errors: [],
3775
+ effects: descreverEfeitosPorHeuristica(texto),
3776
+ impl: { cpp: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo) },
3777
+ origemArquivo: relacao,
3778
+ origemSimbolo: simbolo.simbolo,
3779
+ });
3780
+ }
3781
+
3782
+ if (tasks.length === 0) {
3783
+ continue;
3784
+ }
3785
+
3786
+ acumularModuloImportado(modulos, criarModuloImportadoSimples(
3787
+ nomeModulo,
3788
+ `Rascunho Sema importado automaticamente de ${relacao}.`,
3789
+ tasks,
3790
+ [],
3791
+ [],
3792
+ inferirDatabasesPorHeuristica(texto, relacao),
3793
+ ));
3794
+ }
3795
+
3796
+ return [...modulos.values()];
3797
+ }
3798
+
3799
+ export async function importarProjetoLegado(
3800
+ fonte: FonteImportacao,
3801
+ diretorio: string,
3802
+ namespaceBase?: string,
3803
+ ): Promise<ResultadoImportacao> {
3804
+ const base = path.resolve(diretorio);
3805
+ const namespace = inferirNamespaceBase(base, namespaceBase);
3806
+
3807
+ let modulos: ModuloImportado[] = [];
3808
+ if (fonte === "nestjs") {
3809
+ modulos = await importarTypeScriptBase(base, namespace, true);
3810
+ } else if (fonte === "nextjs") {
3811
+ modulos = await importarNextJsBase(base, namespace);
3812
+ } else if (fonte === "nextjs-consumer") {
3813
+ modulos = await importarNextJsConsumerBase(base, namespace);
3814
+ } else if (fonte === "react-vite-consumer") {
3815
+ modulos = await importarReactViteConsumerBase(base, namespace);
3816
+ } else if (fonte === "angular-consumer") {
3817
+ modulos = await importarAngularConsumerBase(base, namespace);
3818
+ } else if (fonte === "flutter-consumer") {
3819
+ modulos = await importarFlutterConsumerBase(base, namespace);
3820
+ } else if (fonte === "firebase") {
3821
+ modulos = await importarFirebaseBase(base, namespace);
3822
+ } else if (fonte === "typescript") {
3823
+ modulos = await importarTypeScriptBase(base, namespace, false);
3824
+ } else if (fonte === "fastapi") {
3825
+ modulos = await importarPythonBase(base, namespace, "fastapi");
3826
+ } else if (fonte === "flask") {
3827
+ modulos = await importarPythonBase(base, namespace, "flask");
3828
+ } else if (fonte === "python") {
3829
+ modulos = await importarPythonBase(base, namespace, "nenhum");
3830
+ } else if (fonte === "dart") {
3831
+ modulos = await importarDartBase(base, namespace);
3832
+ } else if (fonte === "lua") {
3833
+ modulos = await importarLuaBase(base, namespace);
3834
+ } else if (fonte === "php") {
3835
+ modulos = await importarPhpBase(base, namespace);
3836
+ } else if (fonte === "dotnet") {
3837
+ modulos = await importarDotnetBase(base, namespace);
3838
+ } else if (fonte === "java") {
3839
+ modulos = await importarJavaBase(base, namespace);
3840
+ } else if (fonte === "go") {
3841
+ modulos = await importarGoBase(base, namespace);
3842
+ } else if (fonte === "rust") {
3843
+ modulos = await importarRustBase(base, namespace);
3844
+ } else if (fonte === "cpp") {
3845
+ modulos = await importarCppBase(base, namespace);
3846
+ }
3847
+
3848
+ const arquivos: ArquivoImportado[] = [];
3849
+ for (const modulo of modulos) {
3850
+ const bruto = moduloParaCodigo(modulo);
3851
+ const formatado = await formatarModuloImportado(bruto, `${modulo.nome}.sema`);
3852
+ arquivos.push(montarArquivoImportado(modulo, namespace, formatado));
3853
+ }
3854
+
3855
+ const diagnosticos = compilarProjeto(
3856
+ arquivos.map((arquivo) => ({
3857
+ caminho: path.join(base, ".tmp", "importado", arquivo.caminhoRelativo),
3858
+ codigo: arquivo.conteudo,
3859
+ })),
3860
+ ).diagnosticos;
3861
+
3862
+ return {
3863
+ fonte,
3864
+ diretorio: base,
3865
+ namespaceBase: namespace,
3866
+ arquivos,
3867
+ diagnosticos,
3868
+ };
3869
+ }
3870
+
3871
+ export function resumoImportacao(resultado: ResultadoImportacao): {
3872
+ modulos: number;
3873
+ tarefas: number;
3874
+ rotas: number;
3875
+ entidades: number;
3876
+ enums: number;
3877
+ databases: number;
3878
+ diagnosticos: number;
3879
+ sucesso: boolean;
3880
+ } {
3881
+ return {
3882
+ modulos: resultado.arquivos.length,
3883
+ tarefas: resultado.arquivos.reduce((total, arquivo) => total + arquivo.tarefas, 0),
3884
+ rotas: resultado.arquivos.reduce((total, arquivo) => total + arquivo.rotas, 0),
3885
+ entidades: resultado.arquivos.reduce((total, arquivo) => total + arquivo.entidades, 0),
3886
+ enums: resultado.arquivos.reduce((total, arquivo) => total + arquivo.enums, 0),
3887
+ databases: resultado.arquivos.reduce((total, arquivo) => total + arquivo.databases, 0),
3888
+ diagnosticos: resultado.diagnosticos.length,
3889
+ sucesso: !temErros(resultado.diagnosticos),
3890
+ };
3891
+ }