@semacode/cli 1.3.2 → 1.3.5

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