@semacode/cli 3.5.0 → 3.8.1

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.
@@ -22,8 +22,8 @@ const TEMPLATES_INIT = new Set([
22
22
  "crud-simples", "auth-completo", "api-rest", "workflow", "pedido", "usuario", "upload",
23
23
  ]);
24
24
  const FONTES_IMPORTACAO = new Set([
25
- "nestjs", "express", "fastify", "fastapi", "flask", "nextjs", "nextjs-consumer", "react-vite-consumer",
26
- "angular-consumer", "flutter-consumer", "firebase", "typescript", "javascript",
25
+ "nestjs", "express", "fastify", "koa", "fastapi", "flask", "nextjs", "nextjs-consumer", "react-vite-consumer",
26
+ "angular-consumer", "flutter-consumer", "sveltekit-consumer", "nuxt-consumer", "firebase", "typescript", "javascript",
27
27
  "python", "dart", "lua", "dotnet", "java", "go", "rust", "cpp", "php",
28
28
  ]);
29
29
  const ALVOS_GERACAO = new Set([
@@ -114,7 +114,7 @@ export function criarAjudaRaiz(versao) {
114
114
  "modos: drift direto usa fresh por padrão; --cache none ainda executa sem cache persistente; resumo/inspecionar usam --drift none por padrão e não fabricam evidência",
115
115
  "impacto: sema impacto <arquivo-ou-pasta> --alvo <token> [--mudanca <descricao>] [--escopo <arquivo|modulo|projeto>] [--json]",
116
116
  "renomeação: sema renomear-semantico <arquivo-ou-pasta> --de <nome-atual> --para <nome-novo> [--escopo <arquivo|modulo|projeto>] [--json]",
117
- "importação: sema importar <nestjs|express|fastify|fastapi|flask|nextjs|nextjs-consumer|react-vite-consumer|angular-consumer|flutter-consumer|firebase|dotnet|java|go|rust|cpp|php|typescript|python|dart> <diretorio> [--saida <diretorio>] [--namespace <base>] [--json]",
117
+ "importação: sema importar <nestjs|express|fastify|koa|fastapi|flask|nextjs|nextjs-consumer|react-vite-consumer|angular-consumer|flutter-consumer|sveltekit-consumer|nuxt-consumer|firebase|dotnet|java|go|rust|cpp|php|typescript|python|dart> <diretorio> [--saida <diretorio>] [--namespace <base>] [--json]",
118
118
  "validação: sema validar <arquivo-ou-pasta> [--json]",
119
119
  "diagnóstico: sema diagnosticos <arquivo.sema> [--json]",
120
120
  "geração: sema compilar <arquivo-ou-pasta> --alvo <typescript|python|php|dart|lua|javascript|html|css|dotnet|cpp> --saida <diretorio> [--estrutura <flat|modulos|backend>] [--framework <base|nestjs|fastapi>]",
@@ -11,9 +11,9 @@ export interface SimboloResolvido {
11
11
  arquivo: string;
12
12
  simbolo: string;
13
13
  }
14
- export type ConsumerFramework = "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer";
14
+ export type ConsumerFramework = "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer" | "sveltekit-consumer" | "nuxt-consumer";
15
15
  export interface RotaResolvida {
16
- origem: "nestjs" | "express" | "fastify" | "fastapi" | "flask" | "nextjs" | ConsumerFramework | "firebase" | "dotnet" | "java" | "go" | "rust" | "php";
16
+ origem: "nestjs" | "express" | "fastify" | "koa" | "fastapi" | "flask" | "nextjs" | ConsumerFramework | "firebase" | "dotnet" | "java" | "go" | "rust" | "php";
17
17
  metodo: string;
18
18
  caminho: string;
19
19
  arquivo: string;
@@ -18,6 +18,12 @@ export declare function arquivoEhRotasAngularConsumerRaiz(relacaoArquivo: string
18
18
  export declare function arquivoEhBridgeFlutterConsumer(relacaoArquivo: string): boolean;
19
19
  export declare function arquivoEhSuperficieFlutterConsumer(relacaoArquivo: string): boolean;
20
20
  export declare function arquivoEhRotasFlutterConsumer(relacaoArquivo: string, codigo?: string): boolean;
21
+ export declare function arquivoEhBridgeSvelteKitConsumer(relacaoArquivo: string): boolean;
22
+ export declare function arquivoEhSuperficieSvelteKitConsumer(relacaoArquivo: string): boolean;
23
+ export declare function inferirRotaSvelteKitConsumer(relacaoArquivo: string): RegistroConsumerSurfaceDrift | undefined;
24
+ export declare function arquivoEhBridgeNuxtConsumer(relacaoArquivo: string): boolean;
25
+ export declare function arquivoEhSuperficieNuxtConsumer(relacaoArquivo: string): boolean;
26
+ export declare function inferirRotaNuxtConsumer(relacaoArquivo: string): RegistroConsumerSurfaceDrift | undefined;
21
27
  export declare function inferirRotaNextJsConsumer(relacaoArquivo: string): RegistroConsumerSurfaceDrift | undefined;
22
28
  export declare function inferirRotaReactViteConsumer(relacaoArquivo: string): RegistroConsumerSurfaceDrift | undefined;
23
29
  export declare function inferirRotaFlutterConsumer(relacaoArquivo: string): RegistroConsumerSurfaceDrift | undefined;
@@ -120,6 +120,74 @@ export function arquivoEhRotasFlutterConsumer(relacaoArquivo, codigo) {
120
120
  return /(?:^|\/)(?:lib\/)?(?:router|app_router|routes)\.dart$/i.test(relacao)
121
121
  || /MaterialApp(?:\.router)?\s*\(|CupertinoApp(?:\.router)?\s*\(|GoRouter\s*\(/.test(codigo ?? "");
122
122
  }
123
+ export function arquivoEhBridgeSvelteKitConsumer(relacaoArquivo) {
124
+ const relacao = normalizarRelacaoConsumer(relacaoArquivo);
125
+ return /(?:^|\/)(?:src\/)?lib\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|js)$/i.test(relacao);
126
+ }
127
+ export function arquivoEhSuperficieSvelteKitConsumer(relacaoArquivo) {
128
+ const relacao = normalizarRelacaoConsumer(relacaoArquivo);
129
+ return /(?:^|\/)(?:src\/)?routes\/(?:[^/]+\/)*\+(?:page|layout|error|server)\.(?:svelte|ts|js)$/i.test(relacao);
130
+ }
131
+ export function inferirRotaSvelteKitConsumer(relacaoArquivo) {
132
+ const relacao = normalizarRelacaoConsumer(relacaoArquivo);
133
+ const segmentos = relacao.split("/");
134
+ const indiceSrcRoutes = segmentos.findIndex((segmento, indice) => segmento === "src" && segmentos[indice + 1] === "routes");
135
+ const indiceRoutes = segmentos.findIndex((segmento) => segmento === "routes");
136
+ const inicioRoutes = indiceSrcRoutes >= 0 ? indiceSrcRoutes + 2 : indiceRoutes >= 0 ? indiceRoutes + 1 : -1;
137
+ if (inicioRoutes < 0) {
138
+ return undefined;
139
+ }
140
+ const arquivoFinal = segmentos.at(-1) ?? "";
141
+ const tipoArquivo = arquivoFinal.match(/^\+(page|layout|error|server)\.(?:svelte|ts|js)$/i)?.[1]?.toLowerCase();
142
+ if (!tipoArquivo) {
143
+ return undefined;
144
+ }
145
+ return {
146
+ rota: montarRotaConsumer(segmentos.slice(inicioRoutes, -1)),
147
+ arquivo: relacaoArquivo,
148
+ tipoArquivo,
149
+ };
150
+ }
151
+ export function arquivoEhBridgeNuxtConsumer(relacaoArquivo) {
152
+ const relacao = normalizarRelacaoConsumer(relacaoArquivo);
153
+ return /(?:^|\/)(?:app\/)?composables\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|js)$/i.test(relacao);
154
+ }
155
+ export function arquivoEhSuperficieNuxtConsumer(relacaoArquivo) {
156
+ const relacao = normalizarRelacaoConsumer(relacaoArquivo);
157
+ return /^(?:app\/)?pages\/.+\.vue$/i.test(relacao)
158
+ || /(?:^|\/)server\/api\/.+\.(?:get|post|put|patch|delete|head)\.(?:ts|js)$/i.test(relacao);
159
+ }
160
+ export function inferirRotaNuxtConsumer(relacaoArquivo) {
161
+ const relacao = normalizarRelacaoConsumer(relacaoArquivo);
162
+ const segmentos = relacao.split("/");
163
+ const indiceServer = segmentos.findIndex((segmento, indice) => segmento === "server" && segmentos[indice + 1] === "api");
164
+ if (indiceServer >= 0) {
165
+ const arquivoFinal = segmentos.at(-1) ?? "";
166
+ const metodo = arquivoFinal.match(/\.(get|post|put|patch|delete|head)\.(?:ts|js)$/i)?.[1]?.toUpperCase();
167
+ const nomeBase = arquivoFinal.replace(/\.(?:get|post|put|patch|delete|head)?\.(?:ts|js)$/i, "");
168
+ const caminhoAteArquivo = segmentos.slice(indiceServer + 1, -1);
169
+ return {
170
+ rota: montarRotaConsumer(nomeBase === "index" ? caminhoAteArquivo : [...caminhoAteArquivo, nomeBase]),
171
+ arquivo: relacaoArquivo,
172
+ tipoArquivo: metodo ?? "server",
173
+ };
174
+ }
175
+ const indicePages = segmentos.findIndex((segmento, indice) => segmento === "pages" && (indice === 0 || segmentos[indice - 1] === "app"));
176
+ if (indicePages < 0) {
177
+ return undefined;
178
+ }
179
+ const arquivoFinal = segmentos.at(-1) ?? "";
180
+ if (!arquivoFinal.endsWith(".vue")) {
181
+ return undefined;
182
+ }
183
+ const nomeBase = arquivoFinal.replace(/\.vue$/i, "");
184
+ const caminhoAteArquivo = segmentos.slice(indicePages + 1, -1);
185
+ return {
186
+ rota: montarRotaConsumer(nomeBase === "index" ? caminhoAteArquivo : [...caminhoAteArquivo, nomeBase]),
187
+ arquivo: relacaoArquivo,
188
+ tipoArquivo: "page",
189
+ };
190
+ }
123
191
  export function inferirRotaNextJsConsumer(relacaoArquivo) {
124
192
  const relacao = normalizarRelacaoConsumer(relacaoArquivo);
125
193
  const segmentos = relacao.split("/");
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import ts from "typescript";
6
6
  import { coletarSuperficiesAngularStandaloneConsumer } from "./angular-consumer-standalone.js";
7
7
  import { extrairRotasTypeScriptHttp } from "./typescript-http.js";
8
- import { arquivoEhRotasAngularConsumer, arquivoEhRotasAngularConsumerRaiz, arquivoEhRotasReactViteConsumer, arquivoEhSuperficieNextJsConsumer, arquivoEhSuperficieReactViteConsumer, extrairRotasAngularConsumer, extrairRotasReactViteConsumer, inferirRotaNextJsConsumer, inferirRotaReactViteConsumer, normalizarRelacaoConsumer, registrarAtribuicaoPrototypeTypeScript, registrarMetodosObjectAssignTypeScript } from "./drift.part05.js";
8
+ import { arquivoEhRotasAngularConsumer, arquivoEhRotasAngularConsumerRaiz, arquivoEhRotasReactViteConsumer, arquivoEhSuperficieNextJsConsumer, arquivoEhSuperficieNuxtConsumer, arquivoEhSuperficieReactViteConsumer, arquivoEhSuperficieSvelteKitConsumer, extrairRotasAngularConsumer, extrairRotasReactViteConsumer, inferirRotaNextJsConsumer, inferirRotaNuxtConsumer, inferirRotaReactViteConsumer, inferirRotaSvelteKitConsumer, normalizarRelacaoConsumer, registrarAtribuicaoPrototypeTypeScript, registrarMetodosObjectAssignTypeScript } from "./drift.part05.js";
9
9
  import { caminhosSimbolicos, chaveCaminhoCanonicoDrift, deduplicarRaizesSobrepostasDrift, desembrulharExpressaoTypeScript, extrairNomeClassePrototypeTypeScript, extrairTextoLiteral, juntarCaminhoHttp, lerDecorator, listarArquivosRecursivos, registrarSimboloTypeScript, } from "./drift.part04.js";
10
10
  import { extrairRecursosPersistenciaCodigoVivo, registrarRecursoDrift } from "./drift.part03.js";
11
11
  export function inferirConsumerFrameworkPrincipal(fontesLegado, consumerSurfaces, consumerBridges) {
@@ -31,7 +31,14 @@ export function inferirConsumerFrameworkPrincipal(fontesLegado, consumerSurfaces
31
31
  || /(?:^|\/)(?:lib\/)?(?:router|app_router|routes|main)\.dart$/i.test(arquivo))) {
32
32
  return "flutter-consumer";
33
33
  }
34
- for (const framework of ["nextjs-consumer", "react-vite-consumer", "angular-consumer", "flutter-consumer"]) {
34
+ if (arquivos.some((arquivo) => /(?:^|\/)(?:src\/)?routes\/(?:[^/]+\/)*\+(?:page|layout|error|server)\.(?:svelte|ts|js)$/i.test(arquivo))) {
35
+ return "sveltekit-consumer";
36
+ }
37
+ if (arquivos.some((arquivo) => /^(?:app\/)?pages\/.+\.vue$/i.test(arquivo)
38
+ || /(?:^|\/)server\/api\/.+\.(?:get|post|put|patch|delete|head)\.(?:ts|js)$/i.test(arquivo))) {
39
+ return "nuxt-consumer";
40
+ }
41
+ for (const framework of ["nextjs-consumer", "react-vite-consumer", "angular-consumer", "flutter-consumer", "sveltekit-consumer", "nuxt-consumer"]) {
35
42
  if (fontesLegado.includes(framework)) {
36
43
  return framework;
37
44
  }
@@ -203,8 +210,50 @@ export async function indexarTypeScript(diretorios, adaptadorLeitura) {
203
210
  const consumerSurfaces = new Map();
204
211
  const sourceFiles = new Map();
205
212
  const arquivosProcessados = new Set();
213
+ const registrarSuperficieSvelteKitDrift = (relacao, arquivo) => {
214
+ const superficie = arquivoEhSuperficieSvelteKitConsumer(relacao)
215
+ ? inferirRotaSvelteKitConsumer(relacao)
216
+ : undefined;
217
+ if (!superficie) {
218
+ return;
219
+ }
220
+ consumerSurfaces.set(`${superficie.rota}:${arquivo}:${superficie.tipoArquivo}`, {
221
+ rota: superficie.rota,
222
+ arquivo,
223
+ tipoArquivo: superficie.tipoArquivo,
224
+ });
225
+ rotas.push({
226
+ origem: "sveltekit-consumer",
227
+ metodo: superficie.tipoArquivo === "server" ? "GET" : "VIEW",
228
+ caminho: superficie.rota,
229
+ arquivo,
230
+ simbolo: superficie.tipoArquivo,
231
+ });
232
+ };
233
+ const registrarSuperficieNuxtDrift = (relacao, arquivo) => {
234
+ const superficie = arquivoEhSuperficieNuxtConsumer(relacao)
235
+ ? inferirRotaNuxtConsumer(relacao)
236
+ : undefined;
237
+ if (!superficie) {
238
+ return;
239
+ }
240
+ consumerSurfaces.set(`${superficie.rota}:${arquivo}:${superficie.tipoArquivo}`, {
241
+ rota: superficie.rota,
242
+ arquivo,
243
+ tipoArquivo: superficie.tipoArquivo,
244
+ });
245
+ rotas.push({
246
+ origem: "nuxt-consumer",
247
+ metodo: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"].includes(superficie.tipoArquivo)
248
+ ? superficie.tipoArquivo
249
+ : "VIEW",
250
+ caminho: superficie.rota,
251
+ arquivo,
252
+ simbolo: superficie.tipoArquivo,
253
+ });
254
+ };
206
255
  for (const diretorio of deduplicarRaizesSobrepostasDrift(diretorios)) {
207
- const arquivos = (await listarArquivosRecursivos(diretorio, [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"], adaptadorLeitura))
256
+ const arquivos = (await listarArquivosRecursivos(diretorio, [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte"], adaptadorLeitura))
208
257
  .filter((arquivo) => !arquivo.endsWith(".d.ts")
209
258
  && !arquivo.endsWith(".spec.ts")
210
259
  && !arquivo.endsWith(".test.ts"))
@@ -228,6 +277,12 @@ export async function indexarTypeScript(diretorios, adaptadorLeitura) {
228
277
  const codigo = adaptadorLeitura
229
278
  ? await adaptadorLeitura.lerTexto(arquivo)
230
279
  : await readFile(arquivo, "utf8");
280
+ if (/\.(?:vue|svelte)$/i.test(arquivo)) {
281
+ const relacaoSuperficie = path.relative(diretorio, arquivo);
282
+ registrarSuperficieSvelteKitDrift(relacaoSuperficie, arquivo);
283
+ registrarSuperficieNuxtDrift(relacaoSuperficie, arquivo);
284
+ continue;
285
+ }
231
286
  const origemArquivo = /\.(?:js|jsx|mjs|cjs)$/i.test(arquivo) ? "js" : "ts";
232
287
  const scriptKind = arquivo.endsWith(".tsx")
233
288
  ? ts.ScriptKind.TSX
@@ -287,6 +342,8 @@ export async function indexarTypeScript(diretorios, adaptadorLeitura) {
287
342
  simbolo: superficieReact.tipoArquivo,
288
343
  });
289
344
  }
345
+ registrarSuperficieSvelteKitDrift(relacao, arquivo);
346
+ registrarSuperficieNuxtDrift(relacao, arquivo);
290
347
  if (fonteDeclaraRotasReactVite(relacao, sourceFile)) {
291
348
  for (const rotaReact of extrairRotasReactViteConsumer(relacao, codigo)) {
292
349
  consumerSurfaces.set(`${rotaReact.rota}:${arquivo}:router`, {
@@ -5,7 +5,7 @@ import { OpcoesDriftLegado, OrigemCodigoDrift, RecursoResolvido, RegistroPersist
5
5
  import { indexarPersistenciaDetalhada } from "./drift.part08.js";
6
6
  export declare function analisarPersistenciaReal(contexto: ContextoProjetoCarregado, mapaRecursos?: Map<string, RecursoResolvido[]>, detalhesPersistencia?: Awaited<ReturnType<typeof indexarPersistenciaDetalhada>>, opcoes?: OpcoesDriftLegado, mapaImpl?: Map<string, SimboloResolvido>): Promise<RegistroPersistenciaRealDrift[]>;
7
7
  export declare function normalizarCaminhoRota(caminho?: string): string;
8
- export declare function extrairFontesHttpTypeScript(fontesLegado: FonteLegado[]): Array<"nestjs" | "nextjs" | "firebase" | "express" | "fastify">;
8
+ export declare function extrairFontesHttpTypeScript(fontesLegado: FonteLegado[]): Array<"nestjs" | "nextjs" | "firebase" | "express" | "fastify" | "koa">;
9
9
  export declare function extrairFontesHttpBackend(fontesLegado: FonteLegado[]): Array<"dotnet" | "java" | "go" | "rust" | "php">;
10
10
  export declare function ultimoSegmentoSimbolico(caminho: string): string;
11
11
  export declare function pontuarCandidatoDeclarado(candidato: SimboloResolvido, origem: OrigemCodigoDrift, caminhoDeclarado: string): SimboloCandidatoDrift | undefined;
@@ -14,7 +14,7 @@ export declare function deduplicarCandidatos(candidatos: SimboloCandidatoDrift[]
14
14
  export declare function ordenarCandidatos(candidatos: SimboloCandidatoDrift[]): SimboloCandidatoDrift[];
15
15
  export declare function sugerirCandidatosParaImpl(simbolos: SimboloResolvido[], origem: OrigemCodigoDrift, caminhoDeclarado: string): SimboloCandidatoDrift[];
16
16
  export declare function sugerirCandidatosParaTaskSemImpl(simbolos: SimboloResolvido[], nomeTask: string): SimboloCandidatoDrift[];
17
- export declare function escolherRotasEsperadas(task: IrTask, fontesLegado: FonteLegado[]): Array<"nestjs" | "express" | "fastify" | "fastapi" | "flask" | "nextjs" | "firebase" | "dotnet" | "java" | "go" | "rust" | "php">;
17
+ export declare function escolherRotasEsperadas(task: IrTask, fontesLegado: FonteLegado[]): Array<"nestjs" | "express" | "fastify" | "koa" | "fastapi" | "flask" | "nextjs" | "firebase" | "dotnet" | "java" | "go" | "rust" | "php">;
18
18
  export declare function taskEhBridgeFirebase(task: IrTask): boolean;
19
19
  export declare function tiposAceitosParaRecursoPersistencia(recurso: IrRecursoPersistencia): TipoRecursoDrift[];
20
20
  export declare function nomesRecursoPersistencia(recurso: IrRecursoPersistencia): string[];
@@ -87,7 +87,7 @@ export function normalizarCaminhoRota(caminho) {
87
87
  return normalizado.endsWith("/") && normalizado !== "/" ? normalizado.slice(0, -1) : normalizado;
88
88
  }
89
89
  export function extrairFontesHttpTypeScript(fontesLegado) {
90
- return fontesLegado.filter((fonte) => fonte === "nestjs" || fonte === "nextjs" || fonte === "firebase" || fonte === "express" || fonte === "fastify");
90
+ return fontesLegado.filter((fonte) => fonte === "nestjs" || fonte === "nextjs" || fonte === "firebase" || fonte === "express" || fonte === "fastify" || fonte === "koa");
91
91
  }
92
92
  export function extrairFontesHttpBackend(fontesLegado) {
93
93
  return fontesLegado.filter((fonte) => fonte === "dotnet" || fonte === "java" || fonte === "go" || fonte === "rust" || fonte === "php");
@@ -234,13 +234,16 @@ export function escolherRotasEsperadas(task, fontesLegado) {
234
234
  if (/\bfastify\b/i.test(implTsOuJs.caminho) && fontesTs.includes("fastify")) {
235
235
  esperadas.add("fastify");
236
236
  }
237
+ if (/\bkoa\b/i.test(implTsOuJs.caminho) && fontesTs.includes("koa")) {
238
+ esperadas.add("koa");
239
+ }
237
240
  if (esperadas.size > 0) {
238
241
  return [...esperadas];
239
242
  }
240
243
  if (fontesTs.length > 0) {
241
244
  return fontesTs;
242
245
  }
243
- return ["nestjs", "nextjs", "firebase", "express", "fastify"];
246
+ return ["nestjs", "nextjs", "firebase", "express", "fastify", "koa"];
244
247
  }
245
248
  if (task.implementacoesExternas.some((impl) => impl.origem === "py")) {
246
249
  const fontesPython = fontesLegado.filter((fonte) => fonte === "fastapi" || fonte === "flask");
@@ -277,6 +277,8 @@ export function analisarModulosSelecionadosDrift(estado) {
277
277
  && rotaResolvida.origem !== "react-vite-consumer"
278
278
  && rotaResolvida.origem !== "angular-consumer"
279
279
  && rotaResolvida.origem !== "flutter-consumer"
280
+ && rotaResolvida.origem !== "sveltekit-consumer"
281
+ && rotaResolvida.origem !== "nuxt-consumer"
280
282
  && esperadas.includes(rotaResolvida.origem));
281
283
  const combina = encontradas.some((rotaResolvida) => rotaResolvida.metodo === route.metodo
282
284
  && normalizarCaminhoRota(rotaResolvida.caminho) === normalizarCaminhoRota(route.caminho));
@@ -87,6 +87,7 @@ export function normalizarFonteImportacao(valor) {
87
87
  if (valor === "nestjs"
88
88
  || valor === "express"
89
89
  || valor === "fastify"
90
+ || valor === "koa"
90
91
  || valor === "fastapi"
91
92
  || valor === "flask"
92
93
  || valor === "nextjs"
@@ -94,6 +95,8 @@ export function normalizarFonteImportacao(valor) {
94
95
  || valor === "react-vite-consumer"
95
96
  || valor === "angular-consumer"
96
97
  || valor === "flutter-consumer"
98
+ || valor === "sveltekit-consumer"
99
+ || valor === "nuxt-consumer"
97
100
  || valor === "firebase"
98
101
  || valor === "dotnet"
99
102
  || valor === "java"
@@ -1,6 +1,6 @@
1
1
  import ts from "typescript";
2
2
  import { type Diagnostico } from "@sema/nucleo";
3
- export type FonteImportacao = "nestjs" | "express" | "fastify" | "fastapi" | "flask" | "nextjs" | "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer" | "firebase" | "typescript" | "python" | "dart" | "dotnet" | "java" | "go" | "rust" | "cpp" | "php";
3
+ export type FonteImportacao = "nestjs" | "express" | "fastify" | "koa" | "fastapi" | "flask" | "nextjs" | "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer" | "sveltekit-consumer" | "nuxt-consumer" | "firebase" | "typescript" | "python" | "dart" | "dotnet" | "java" | "go" | "rust" | "cpp" | "php";
4
4
  export type OrigemInteropImportada = "ts" | "py" | "dart" | "cs" | "java" | "go" | "rust" | "cpp" | "php";
5
5
  export interface CampoImportado {
6
6
  nome: string;
@@ -11,8 +11,12 @@ export declare function extrairTasksBridgeConsumer(baseProjeto: string, contexto
11
11
  enums: EnumImportado[];
12
12
  };
13
13
  export declare function montarVinculosSuperficiesConsumer(superficies: SuperficieConsumerImportada[]): VinculoImportado[];
14
- export declare function importarConsumerBase(diretorio: string, namespaceBase: string, descricaoFramework: string, ehBridge: (relacaoArquivo: string) => boolean, coletarSuperficies: (baseProjeto: string, arquivos: string[]) => Promise<SuperficieConsumerImportada[]>): Promise<ModuloImportado[]>;
14
+ export declare function importarConsumerBase(diretorio: string, namespaceBase: string, descricaoFramework: string, ehBridge: (relacaoArquivo: string) => boolean, coletarSuperficies: (baseProjeto: string, arquivos: string[]) => Promise<SuperficieConsumerImportada[]>, extensoes?: readonly string[]): Promise<ModuloImportado[]>;
15
15
  export declare function coletarSuperficiesNextJsConsumer(baseProjeto: string, arquivos: string[]): Promise<SuperficieConsumerImportada[]>;
16
+ export declare function coletarSuperficiesSvelteKitConsumer(baseProjeto: string, arquivos: string[]): Promise<SuperficieConsumerImportada[]>;
17
+ export declare function coletarSuperficiesNuxtConsumer(baseProjeto: string, arquivos: string[]): Promise<SuperficieConsumerImportada[]>;
18
+ export declare function importarSvelteKitConsumerBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]>;
19
+ export declare function importarNuxtConsumerBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]>;
16
20
  export declare function coletarSuperficiesReactViteConsumer(baseProjeto: string, arquivos: string[]): Promise<SuperficieConsumerImportada[]>;
17
21
  export declare function coletarSuperficiesAngularConsumer(baseProjeto: string, arquivos: string[]): Promise<SuperficieConsumerImportada[]>;
18
22
  export declare function importarNextJsConsumerBase(diretorio: string, namespaceBase: string): Promise<ModuloImportado[]>;
@@ -146,9 +146,9 @@ export function montarVinculosSuperficiesConsumer(superficies) {
146
146
  { tipo: "arquivo", valor: normalizarCaminhoImportado(superficie.arquivo) },
147
147
  ]));
148
148
  }
149
- export async function importarConsumerBase(diretorio, namespaceBase, descricaoFramework, ehBridge, coletarSuperficies) {
149
+ export async function importarConsumerBase(diretorio, namespaceBase, descricaoFramework, ehBridge, coletarSuperficies, extensoes = [".ts", ".tsx", ".js", ".jsx"]) {
150
150
  const escopo = resolverEscopoImportacaoFrontendConsumer(diretorio);
151
- const arquivos = await listarArquivosRecursivos(escopo.baseProjeto, [".ts", ".tsx", ".js", ".jsx"]);
151
+ const arquivos = await listarArquivosRecursivos(escopo.baseProjeto, [...extensoes]);
152
152
  const arquivosBridge = arquivos.filter((arquivo) => ehBridge(path.relative(escopo.baseProjeto, arquivo)));
153
153
  const contextosBridge = await carregarContextosBridgeConsumer(escopo.baseProjeto, arquivosBridge);
154
154
  const { tasks, entities, enums } = extrairTasksBridgeConsumer(escopo.baseProjeto, contextosBridge);
@@ -175,6 +175,44 @@ export async function coletarSuperficiesNextJsConsumer(baseProjeto, arquivos) {
175
175
  .map((arquivo) => inferirCaminhoNextJsConsumer(path.relative(baseProjeto, arquivo)))
176
176
  .filter((item) => Boolean(item));
177
177
  }
178
+ export async function coletarSuperficiesSvelteKitConsumer(baseProjeto, arquivos) {
179
+ const { arquivoEhSuperficieSvelteKitConsumer, inferirRotaSvelteKitConsumer } = await import("./drift.part05.js");
180
+ return arquivos
181
+ .map((arquivo) => {
182
+ const relacao = path.relative(baseProjeto, arquivo);
183
+ if (!arquivoEhSuperficieSvelteKitConsumer(relacao)) {
184
+ return undefined;
185
+ }
186
+ const superficie = inferirRotaSvelteKitConsumer(relacao);
187
+ return superficie
188
+ ? { caminho: superficie.rota, arquivo: relacao, tipoArquivo: superficie.tipoArquivo }
189
+ : undefined;
190
+ })
191
+ .filter((item) => Boolean(item));
192
+ }
193
+ export async function coletarSuperficiesNuxtConsumer(baseProjeto, arquivos) {
194
+ const { arquivoEhSuperficieNuxtConsumer, inferirRotaNuxtConsumer } = await import("./drift.part05.js");
195
+ return arquivos
196
+ .map((arquivo) => {
197
+ const relacao = path.relative(baseProjeto, arquivo);
198
+ if (!arquivoEhSuperficieNuxtConsumer(relacao)) {
199
+ return undefined;
200
+ }
201
+ const superficie = inferirRotaNuxtConsumer(relacao);
202
+ return superficie
203
+ ? { caminho: superficie.rota, arquivo: relacao, tipoArquivo: superficie.tipoArquivo }
204
+ : undefined;
205
+ })
206
+ .filter((item) => Boolean(item));
207
+ }
208
+ export async function importarSvelteKitConsumerBase(diretorio, namespaceBase) {
209
+ const { arquivoEhBridgeSvelteKitConsumer } = await import("./drift.part05.js");
210
+ return importarConsumerBase(diretorio, namespaceBase, "SvelteKit", arquivoEhBridgeSvelteKitConsumer, coletarSuperficiesSvelteKitConsumer, [".ts", ".js", ".svelte"]);
211
+ }
212
+ export async function importarNuxtConsumerBase(diretorio, namespaceBase) {
213
+ const { arquivoEhBridgeNuxtConsumer } = await import("./drift.part05.js");
214
+ return importarConsumerBase(diretorio, namespaceBase, "Nuxt", arquivoEhBridgeNuxtConsumer, coletarSuperficiesNuxtConsumer, [".ts", ".js", ".vue"]);
215
+ }
178
216
  export async function coletarSuperficiesReactViteConsumer(baseProjeto, arquivos) {
179
217
  const superficies = [];
180
218
  for (const arquivo of arquivos) {
@@ -1,8 +1,8 @@
1
1
  import { extrairRotasTypeScriptHttp } from "./typescript-http.js";
2
2
  import { CampoImportado, ModuloImportado, TipoDescoberto } from "./importador.part01.js";
3
3
  export declare function importarNestJsDeArquivo(diretorioBase: string, arquivo: string, namespaceBase: string, tiposGlobais: Map<string, TipoDescoberto>): ModuloImportado[];
4
- export declare function importarExpressFastifyDeArquivo(diretorioBase: string, arquivo: string, namespaceBase: string, origemDesejada: "express" | "fastify"): ModuloImportado[];
5
- export declare function importarExpressFastifyBase(diretorio: string, namespaceBase: string, origem: "express" | "fastify"): Promise<ModuloImportado[]>;
4
+ export declare function importarExpressFastifyDeArquivo(diretorioBase: string, arquivo: string, namespaceBase: string, origemDesejada: "express" | "fastify" | "koa"): ModuloImportado[];
5
+ export declare function importarExpressFastifyBase(diretorio: string, namespaceBase: string, origem: "express" | "fastify" | "koa"): Promise<ModuloImportado[]>;
6
6
  export declare function importarTypeScriptBase(diretorio: string, namespaceBase: string, modoNestjs?: boolean): Promise<ModuloImportado[]>;
7
7
  export declare function nomeTaskParaRotaTypeScript(caminho: string, metodo: string): string;
8
8
  export declare function camposDeParametrosRotaTypeScript(parametros: ReturnType<typeof extrairRotasTypeScriptHttp>[number]["parametros"]): CampoImportado[];
@@ -132,15 +132,29 @@ export function importarExpressFastifyDeArquivo(diretorioBase, arquivo, namespac
132
132
  for (const rota of rotasArquivo) {
133
133
  const taskNome = nomearTarefaRotaChamada(rota.metodo, rota.caminho);
134
134
  const implSimbolo = /^[A-Za-z_]\w*$/.test(rota.simbolo) ? rota.simbolo : undefined;
135
- tasks.push({
136
- nome: taskNome,
137
- resumo: `Task importada automaticamente da rota ${rota.metodo} ${rota.caminho} em ${relacao}.`,
138
- input: rota.parametros.map((parametro) => ({
135
+ const semantica = implSimbolo
136
+ ? inferirSemanticaHandlerTypeScriptHttp(sourceFile, implSimbolo, true)
137
+ : undefined;
138
+ const converterCampoInferido = (campo) => ({
139
+ nome: campo.nome,
140
+ tipo: mapearTipoPrimitivo(campo.tipoTexto ?? "string") === "Vazio" ? "Texto" : mapearTipoPrimitivo(campo.tipoTexto ?? "string"),
141
+ obrigatorio: campo.obrigatorio,
142
+ });
143
+ const camposInferidos = deduplicarCampos([
144
+ ...rota.parametros.map((parametro) => ({
139
145
  nome: parametro.nome,
140
146
  tipo: parametro.tipoSema,
141
147
  obrigatorio: true,
142
148
  })),
143
- output: [],
149
+ ...(semantica?.query ?? []).map(converterCampoInferido),
150
+ ...(semantica?.body ?? []).map(converterCampoInferido),
151
+ ]);
152
+ const camposResposta = deduplicarCampos((semantica?.response ?? []).map(converterCampoInferido));
153
+ tasks.push({
154
+ nome: taskNome,
155
+ resumo: `Task importada automaticamente da rota ${rota.metodo} ${rota.caminho} em ${relacao}.`,
156
+ input: camposInferidos,
157
+ output: camposResposta,
144
158
  errors: [],
145
159
  effects: [],
146
160
  impl: implSimbolo ? { ts: caminhoImplTs(diretorioBase, arquivo, implSimbolo) } : undefined,
@@ -153,12 +167,8 @@ export function importarExpressFastifyDeArquivo(diretorioBase, arquivo, namespac
153
167
  metodo: rota.metodo,
154
168
  caminho: rota.caminho,
155
169
  task: taskNome,
156
- input: rota.parametros.map((parametro) => ({
157
- nome: parametro.nome,
158
- tipo: parametro.tipoSema,
159
- obrigatorio: true,
160
- })),
161
- output: [],
170
+ input: camposInferidos,
171
+ output: camposResposta,
162
172
  errors: [],
163
173
  });
164
174
  }
@@ -166,7 +176,7 @@ export function importarExpressFastifyDeArquivo(diretorioBase, arquivo, namespac
166
176
  sincronizarRotasComTasks(routes, tasks);
167
177
  return [{
168
178
  nome: nomeModulo,
169
- resumo: `Rascunho Sema importado de um contexto ${origemDesejada === "express" ? "Express" : "Fastify"} legado em ${contextoSegmentos.join("/")}.`,
179
+ resumo: `Rascunho Sema importado de um contexto ${origemDesejada === "express" ? "Express" : origemDesejada === "koa" ? "Koa" : "Fastify"} legado em ${contextoSegmentos.join("/")}.`,
170
180
  entities,
171
181
  enums,
172
182
  tasks: deduplicarTarefas(tasks),
@@ -13,7 +13,7 @@ import { camposDeParametrosRotaBackend, criarCampoResultadoBackend, descreverEfe
13
13
  import { caminhoImplGenerico, importarFirebaseBase } from "./importador.part07.js";
14
14
  import { acumularModuloImportado, criarModuloImportadoSimples, importarDartBase, importarDotnetBase, importarPythonBase, resolverArquivoRustParaSimbolo, selecionarSimbolosPreferidos } from "./importador.part08.js";
15
15
  import { importarExpressFastifyBase, importarNextJsBase, importarTypeScriptBase } from "./importador.part06.js";
16
- import { importarAngularConsumerBase, importarFlutterConsumerBase, importarNextJsConsumerBase, importarReactViteConsumerBase } from "./importador.part04.js";
16
+ import { importarAngularConsumerBase, importarFlutterConsumerBase, importarNextJsConsumerBase, importarReactViteConsumerBase, importarNuxtConsumerBase, importarSvelteKitConsumerBase } from "./importador.part04.js";
17
17
  import { formatarModuloImportado, moduloParaCodigo, montarArquivoImportado } from "./importador.part05.js";
18
18
  export async function importarJavaBase(diretorio, namespaceBase) {
19
19
  const arquivos = (await listarArquivosRecursivos(diretorio, [".java"]))
@@ -306,6 +306,9 @@ export async function importarProjetoLegado(fonte, diretorio, namespaceBase) {
306
306
  else if (fonte === "fastify") {
307
307
  modulos = await importarExpressFastifyBase(base, namespace, "fastify");
308
308
  }
309
+ else if (fonte === "koa") {
310
+ modulos = await importarExpressFastifyBase(base, namespace, "koa");
311
+ }
309
312
  else if (fonte === "nextjs") {
310
313
  modulos = await importarNextJsBase(base, namespace);
311
314
  }
@@ -321,6 +324,12 @@ export async function importarProjetoLegado(fonte, diretorio, namespaceBase) {
321
324
  else if (fonte === "flutter-consumer") {
322
325
  modulos = await importarFlutterConsumerBase(base, namespace);
323
326
  }
327
+ else if (fonte === "sveltekit-consumer") {
328
+ modulos = await importarSvelteKitConsumerBase(base, namespace);
329
+ }
330
+ else if (fonte === "nuxt-consumer") {
331
+ modulos = await importarNuxtConsumerBase(base, namespace);
332
+ }
324
333
  else if (fonte === "firebase") {
325
334
  modulos = await importarFirebaseBase(base, namespace);
326
335
  }
@@ -76,6 +76,7 @@ export function normalizarFonteLegado(valor) {
76
76
  if (valor === "nestjs"
77
77
  || valor === "express"
78
78
  || valor === "fastify"
79
+ || valor === "koa"
79
80
  || valor === "fastapi"
80
81
  || valor === "flask"
81
82
  || valor === "nextjs"
@@ -83,6 +84,8 @@ export function normalizarFonteLegado(valor) {
83
84
  || valor === "react-vite-consumer"
84
85
  || valor === "angular-consumer"
85
86
  || valor === "flutter-consumer"
87
+ || valor === "sveltekit-consumer"
88
+ || valor === "nuxt-consumer"
86
89
  || valor === "firebase"
87
90
  || valor === "typescript"
88
91
  || valor === "javascript"
@@ -25,6 +25,10 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
25
25
  encontrados.add("fastify");
26
26
  encontrados.add("typescript");
27
27
  }
28
+ if (/"koa"\s*:|@koa\/router|"koa-router"/.test(packageJsonRaiz)) {
29
+ encontrados.add("koa");
30
+ encontrados.add("typescript");
31
+ }
28
32
  if (/typescript/.test(packageJsonRaiz)) {
29
33
  encontrados.add("typescript");
30
34
  }
@@ -51,6 +55,8 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
51
55
  const arquivosJava = await listarArquivosRecursivosLimitado(diretorio, [".java"], 5, 20);
52
56
  const arquivosGo = await listarArquivosRecursivosLimitado(diretorio, [".go"], 5, 20);
53
57
  const arquivosRust = await listarArquivosRecursivosLimitado(diretorio, [".rs"], 5, 20);
58
+ const arquivosVueSvelte = await listarArquivosRecursivosLimitado(diretorio, [".vue", ".svelte"], 5, 40);
59
+ const relacoesVueSvelte = arquivosVueSvelte.map((arquivo) => path.relative(diretorio, arquivo).replace(/\\/g, "/"));
54
60
  const arquivosCppBrutos = await listarArquivosRecursivosLimitado(diretorio, [".cpp", ".cc", ".cxx", ".hpp", ".h"], 5, 30);
55
61
  const arquivosCpp = arquivosCppBrutos.filter((arquivo) => !/(^|[\\/])(windows|linux|macos|runner|flutter|ephemeral|build|vendor)([\\/]|$)/i.test(arquivo));
56
62
  const arquivosPhp = (await listarArquivosRecursivosLimitado(diretorio, [".php"], 5, 30))
@@ -76,6 +82,8 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
76
82
  || amostrasTs.some((texto) => /(?:from\s+["']express["']|require\(\s*["']express["']\s*\)|\bexpress\s*\(\s*\))/.test(texto ?? ""));
77
83
  const temFastify = textosPackage.some((texto) => /"fastify"\s*:/.test(texto ?? ""))
78
84
  || amostrasTs.some((texto) => /(?:from\s+["']fastify["']|require\(\s*["']fastify["']\s*\)|\bfastify\s*\(\s*\{)/.test(texto ?? ""));
85
+ const temKoa = textosPackage.some((texto) => /"koa"\s*:|@koa\/router|"koa-router"/.test(texto ?? ""))
86
+ || amostrasTs.some((texto) => /(?:from\s+["'](?:@koa\/router|koa-router|koa)["']|require\(\s*["'](?:@koa\/router|koa-router|koa)["']\s*\)|new\s+Router\s*\()/.test(texto ?? ""));
79
87
  const temNext = textosPackage.some((texto) => /"next"\s*:/.test(texto ?? ""))
80
88
  || nextConfigs.length > 0
81
89
  || relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app\/api\/.+\/route\.(?:ts|tsx|js|jsx)$/.test(relacao));
@@ -89,6 +97,11 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
89
97
  const temSuperficieAngularConsumer = relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app\/.+\.component\.(?:ts|js)$/.test(relacao))
90
98
  || relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app(?:\/.+)?\/[^/]+\.routes\.(?:ts|js)$/.test(relacao));
91
99
  const temBridgeAngularConsumer = relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|js)$/.test(relacao));
100
+ const temSvelteKitConsumer = relacoesVueSvelte.some((relacao) => /(?:^|\/)(?:src\/)?routes\/(?:[^/]+\/)*\+(?:page|layout|error|server)\.svelte$/i.test(relacao))
101
+ || relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?routes\/(?:[^/]+\/)*\+(?:page|server)\.(?:ts|js)$/i.test(relacao))
102
+ || textosPackage.some((texto) => /@sveltejs\/kit/.test(texto ?? ""));
103
+ const temNuxtConsumer = relacoesVueSvelte.some((relacao) => /^(?:app\/)?pages\/.+\.vue$/i.test(relacao))
104
+ || textosPackage.some((texto) => /"nuxt"\s*:/.test(texto ?? ""));
92
105
  const temAngularConsumer = temSuperficieAngularConsumer
93
106
  || ((textosPackage.some((texto) => /@angular\/core|@angular\/router/.test(texto ?? "")) || angularConfigs.length > 0) && temBridgeAngularConsumer);
94
107
  const temFirebase = marcadoresFirebaseProjeto.length > 0
@@ -104,6 +117,9 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
104
117
  if (temFastify) {
105
118
  encontrados.add("fastify");
106
119
  }
120
+ if (temKoa) {
121
+ encontrados.add("koa");
122
+ }
107
123
  if (temNext) {
108
124
  encontrados.add("nextjs");
109
125
  }
@@ -116,6 +132,12 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
116
132
  if (temAngularConsumer) {
117
133
  encontrados.add("angular-consumer");
118
134
  }
135
+ if (temSvelteKitConsumer) {
136
+ encontrados.add("sveltekit-consumer");
137
+ }
138
+ if (temNuxtConsumer) {
139
+ encontrados.add("nuxt-consumer");
140
+ }
119
141
  if (temFirebase) {
120
142
  encontrados.add("firebase");
121
143
  }
@@ -52,8 +52,14 @@ async function resolverDiretoriosConfiguradosConfinados(baseProjeto, baseConfigu
52
52
  let canonico = absoluto;
53
53
  try {
54
54
  const [real, informacao] = await Promise.all([realpath(absoluto), stat(absoluto)]);
55
- if (!informacao.isDirectory()) {
56
- throw new Error(`${identificadorSeguro} nao e diretorio`);
55
+ const ehContratoIndividual = campo === "origens"
56
+ && informacao.isFile()
57
+ && absoluto.toLowerCase().endsWith(".sema");
58
+ if (!informacao.isDirectory() && !ehContratoIndividual) {
59
+ const esperado = campo === "origens"
60
+ ? "um diretorio de contratos (ex.: ./contratos) ou um arquivo .sema individual"
61
+ : "um diretorio de codigo vivo";
62
+ throw new Error(`${identificadorSeguro} nao e ${esperado}. Corrija "${campo}" no sema.config.json; configuracoes geradas por versoes antigas da CLI podem listar arquivos direto.`);
57
63
  }
58
64
  if (!caminhoEstaDentro(baseReal, real)) {
59
65
  throw new Error(`${identificadorSeguro} resolve fora da base do projeto`);
package/dist/tipos.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export type EstruturaSaida = "flat" | "modulos" | "backend";
2
- export type FonteLegado = "nestjs" | "express" | "fastify" | "fastapi" | "flask" | "nextjs" | "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer" | "firebase" | "typescript" | "javascript" | "python" | "dart" | "lua" | "dotnet" | "java" | "go" | "rust" | "cpp" | "php";
2
+ export type FonteLegado = "nestjs" | "express" | "fastify" | "koa" | "fastapi" | "flask" | "nextjs" | "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer" | "sveltekit-consumer" | "nuxt-consumer" | "firebase" | "typescript" | "javascript" | "python" | "dart" | "lua" | "dotnet" | "java" | "go" | "rust" | "cpp" | "php";
3
3
  export type ModoAdocao = "incremental";
@@ -8,6 +8,15 @@ export function resolverMetadadosOrigemBody(expr, sourceFile, requestNames, body
8
8
  if (ts.isIdentifier(normalizado) && bodyAliases.has(normalizado.text)) {
9
9
  return bodyAliases.get(normalizado.text);
10
10
  }
11
+ if (ts.isPropertyAccessExpression(normalizado)
12
+ && normalizado.name.text === "body"
13
+ && ts.isIdentifier(normalizado.expression)
14
+ && requestNames.has(normalizado.expression.text)) {
15
+ return {
16
+ tipoTexto: typeNodeDeclarado?.getText(sourceFile),
17
+ campos: [],
18
+ };
19
+ }
11
20
  if (ehCallRequest(expr, requestNames, "json")) {
12
21
  return extrairMetadadosTipoExplicito(expr, sourceFile, typeNodeDeclarado);
13
22
  }
@@ -4,7 +4,7 @@ export interface ParametroRotaTypeScript {
4
4
  tipoSema: "Texto" | "Inteiro" | "Decimal" | "Id";
5
5
  }
6
6
  export interface RotaTypeScriptExtraida {
7
- origem: "nextjs" | "firebase" | "express" | "fastify";
7
+ origem: "nextjs" | "firebase" | "express" | "fastify" | "koa";
8
8
  metodo: string;
9
9
  caminho: string;
10
10
  simbolo: string;
@@ -109,13 +109,114 @@ export function extrairRotasNodeWorker(sourceFile, relacaoArquivo) {
109
109
  }
110
110
  return deduplicarRotas(rotas);
111
111
  }
112
+ function combinarCaminhoPrefixo(base, sufixo) {
113
+ const prefixo = (base ?? "").replace(/\/+$/u, "");
114
+ const caminho = sufixo.startsWith("/") ? sufixo : `/${sufixo}`;
115
+ const combinado = `${prefixo}${caminho}`.replace(/\/{2,}/gu, "/");
116
+ return combinado.length > 1 && combinado.endsWith("/") ? combinado.slice(0, -1) : combinado;
117
+ }
118
+ function extrairPrefixoRegister(node) {
119
+ const opcoes = node.arguments[1];
120
+ if (!opcoes || !ts.isObjectLiteralExpression(opcoes)) {
121
+ return undefined;
122
+ }
123
+ for (const propriedade of opcoes.properties) {
124
+ if (ts.isPropertyAssignment(propriedade)
125
+ && ts.isIdentifier(propriedade.name)
126
+ && propriedade.name.text === "prefix"
127
+ && propriedade.initializer
128
+ && ts.isStringLiteralLike(propriedade.initializer)) {
129
+ return propriedade.initializer.text;
130
+ }
131
+ }
132
+ return undefined;
133
+ }
112
134
  export function extrairRotasExpressFastify(sourceFile) {
113
135
  const textoArquivo = sourceFile.getFullText();
114
136
  const importouExpress = /(?:from\s+["']express["']|require\(\s*["']express["']\s*\))/.test(textoArquivo);
115
137
  const importouFastify = /(?:from\s+["']fastify["']|require\(\s*["']fastify["']\s*\))/.test(textoArquivo);
116
- if (!importouExpress && !importouFastify) {
138
+ const importouKoa = /(?:from\s+["'](?:@koa\/router|koa-router|koa)["']|require\(\s*["'](?:@koa\/router|koa-router|koa)["']\s*\))/.test(textoArquivo);
139
+ if (!importouExpress && !importouFastify && !importouKoa) {
117
140
  return [];
118
141
  }
142
+ const arestasMontagem = [];
143
+ const prefixoDireto = new Map();
144
+ const prefixoPorParametro = new Map();
145
+ const prefixoResolvido = new Map();
146
+ const resolverPrefixo = (receptor, profundidade = 0) => {
147
+ if (prefixoResolvido.has(receptor)) {
148
+ return prefixoResolvido.get(receptor);
149
+ }
150
+ if (profundidade > 8) {
151
+ return "";
152
+ }
153
+ let prefixo = prefixoDireto.get(receptor) ?? "";
154
+ const aresta = arestasMontagem.find((item) => item.filho === receptor);
155
+ if (aresta) {
156
+ prefixo = combinarCaminhoPrefixo(resolverPrefixo(aresta.pai, profundidade + 1), aresta.prefixo);
157
+ }
158
+ prefixoResolvido.set(receptor, prefixo);
159
+ return prefixo;
160
+ };
161
+ const visitarMontagens = (node) => {
162
+ if (ts.isVariableStatement(node)) {
163
+ for (const declaracao of node.declarationList.declarations) {
164
+ if (!ts.isIdentifier(declaracao.name) || !declaracao.initializer) {
165
+ continue;
166
+ }
167
+ const init = declaracao.initializer;
168
+ const argumentoFastify = ts.isCallExpression(init)
169
+ && ts.isIdentifier(init.expression)
170
+ && /fastify/i.test(init.expression.text)
171
+ && init.arguments[0]
172
+ && ts.isObjectLiteralExpression(init.arguments[0])
173
+ ? init.arguments[0]
174
+ : undefined;
175
+ if (argumentoFastify) {
176
+ for (const propriedade of argumentoFastify.properties) {
177
+ if (ts.isPropertyAssignment(propriedade)
178
+ && ts.isIdentifier(propriedade.name)
179
+ && propriedade.name.text === "prefix"
180
+ && propriedade.initializer
181
+ && ts.isStringLiteralLike(propriedade.initializer)) {
182
+ prefixoDireto.set(declaracao.name.text, propriedade.initializer.text);
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression)) {
189
+ const receptor = node.expression.expression.text;
190
+ const nomeMetodo = node.expression.name.text.toLowerCase();
191
+ if (nomeMetodo === "use"
192
+ && node.arguments[0]
193
+ && ts.isStringLiteralLike(node.arguments[0])
194
+ && node.arguments[0].text.startsWith("/")
195
+ && node.arguments[1]
196
+ && ts.isIdentifier(node.arguments[1])) {
197
+ arestasMontagem.push({ pai: receptor, filho: node.arguments[1].text, prefixo: node.arguments[0].text });
198
+ }
199
+ else if (nomeMetodo === "prefix"
200
+ && node.arguments[0]
201
+ && ts.isStringLiteralLike(node.arguments[0])
202
+ && node.arguments[0].text.startsWith("/")) {
203
+ prefixoDireto.set(receptor, combinarCaminhoPrefixo(node.arguments[0].text, prefixoDireto.get(receptor) ?? ""));
204
+ }
205
+ else if (nomeMetodo === "register" && importouFastify) {
206
+ const prefixo = extrairPrefixoRegister(node);
207
+ const alvo = node.arguments[0];
208
+ if (prefixo && alvo) {
209
+ const funcao = ts.isFunctionExpression(alvo) || ts.isArrowFunction(alvo) ? alvo : undefined;
210
+ const parametro = funcao?.parameters[0];
211
+ if (parametro && ts.isIdentifier(parametro.name)) {
212
+ prefixoPorParametro.set(parametro.name.text, prefixo);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ node.forEachChild(visitarMontagens);
218
+ };
219
+ visitarMontagens(sourceFile);
119
220
  const rotas = [];
120
221
  const escolherOrigemChamada = (receptor) => {
121
222
  if (/fastify/i.test(receptor)) {
@@ -124,7 +225,10 @@ export function extrairRotasExpressFastify(sourceFile) {
124
225
  if (importouExpress) {
125
226
  return "express";
126
227
  }
127
- if (importouFastify) {
228
+ if (importouKoa) {
229
+ return "koa";
230
+ }
231
+ if (importouFastify || prefixoPorParametro.has(receptor)) {
128
232
  return "fastify";
129
233
  }
130
234
  return undefined;
@@ -133,6 +237,7 @@ export function extrairRotasExpressFastify(sourceFile) {
133
237
  if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression)) {
134
238
  const receptor = node.expression.expression.text;
135
239
  const nomeMetodo = node.expression.name.text.toLowerCase();
240
+ const prefixoReceptor = prefixoPorParametro.get(receptor) ?? resolverPrefixo(receptor);
136
241
  if (nomeMetodo === "route" && node.arguments[0] && ts.isObjectLiteralExpression(node.arguments[0]) && importouFastify) {
137
242
  let metodo;
138
243
  let caminho;
@@ -154,13 +259,13 @@ export function extrairRotasExpressFastify(sourceFile) {
154
259
  rotas.push({
155
260
  origem: "fastify",
156
261
  metodo,
157
- caminho: converterCaminhoParametros(caminho),
262
+ caminho: converterCaminhoParametros(combinarCaminhoPrefixo(prefixoReceptor, caminho)),
158
263
  simbolo: receptor,
159
264
  parametros: extrairParametrosCaminhoDoisPontos(caminho),
160
265
  });
161
266
  }
162
267
  }
163
- else if (nomeMetodo === "all" || METODOS_HTTP.has(nomeMetodo.toUpperCase())) {
268
+ else if (nomeMetodo === "all" || nomeMetodo === "del" || METODOS_HTTP.has(nomeMetodo.toUpperCase())) {
164
269
  const caminho = node.arguments[0] && ts.isStringLiteralLike(node.arguments[0])
165
270
  ? node.arguments[0].text
166
271
  : undefined;
@@ -169,14 +274,14 @@ export function extrairRotasExpressFastify(sourceFile) {
169
274
  if (caminho?.startsWith("/") && origem) {
170
275
  const metodos = nomeMetodo === "all"
171
276
  ? ["GET", "POST", "PUT", "PATCH", "DELETE"]
172
- : [nomeMetodo.toUpperCase()];
277
+ : [nomeMetodo === "del" ? "DELETE" : nomeMetodo.toUpperCase()];
173
278
  for (const metodo of metodos) {
174
279
  rotas.push({
175
280
  origem,
176
281
  metodo,
177
- caminho: converterCaminhoParametros(caminho),
282
+ caminho: converterCaminhoParametros(combinarCaminhoPrefixo(prefixoReceptor, caminho)),
178
283
  simbolo: handler ?? receptor,
179
- parametros: extrairParametrosCaminhoDoisPontos(caminho),
284
+ parametros: extrairParametrosCaminhoDoisPontos(combinarCaminhoPrefixo(prefixoReceptor, caminho)),
180
285
  });
181
286
  }
182
287
  }
@@ -1,11 +1,11 @@
1
1
  import ts from "typescript";
2
2
  import { type CampoInferidoTypeScriptHttp, type ExportacaoTypeScriptHttp, type SemanticaHandlerTypeScriptHttp, type ValorLocalTypeScriptHttp } from "./typescript-http-modelos.js";
3
- export declare function localizarExportacaoEmStatement(statement: ts.Statement, nomeExportado: string, sourceFile: ts.SourceFile): ExportacaoTypeScriptHttp | undefined;
3
+ export declare function localizarExportacaoEmStatement(statement: ts.Statement, nomeExportado: string, sourceFile: ts.SourceFile, permitirNaoExportada?: boolean): ExportacaoTypeScriptHttp | undefined;
4
4
  export declare function inferirTipoPorContexto(node: ts.Node, nomeCampo: string): string | undefined;
5
5
  export declare function extrairRetornoHttp(expr: ts.Expression, sourceFile: ts.SourceFile, valoresLocais: Map<string, ValorLocalTypeScriptHttp>): {
6
6
  campos: CampoInferidoTypeScriptHttp[];
7
7
  tipoTexto?: string;
8
8
  status?: number;
9
9
  } | undefined;
10
- export declare function localizarExportacaoTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string): ExportacaoTypeScriptHttp | undefined;
11
- export declare function inferirSemanticaHandlerTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string): SemanticaHandlerTypeScriptHttp | undefined;
10
+ export declare function localizarExportacaoTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string, permitirNaoExportada?: boolean): ExportacaoTypeScriptHttp | undefined;
11
+ export declare function inferirSemanticaHandlerTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string, permitirNaoExportada?: boolean): SemanticaHandlerTypeScriptHttp | undefined;
@@ -4,8 +4,10 @@ import ts from "typescript";
4
4
  import { deduplicarCampos, deduplicarNumeros, desembrulharExpressao, } from "./typescript-http-modelos.js";
5
5
  import { ehCallRequest, ehNewUrl, ehSearchParamsSource, extrairCamposObjetoLiteral, extrairMetadadosTipoExplicito, extrairStatusHttp, obterNomeSchema, tipoInferidoPorNomeCampo, } from "./typescript-http-expressoes.js";
6
6
  import { extrairCamposBindingPattern, extrairSchemasZodLocais, resolverMetadadosOrigemBody, } from "./typescript-http-body.js";
7
- export function localizarExportacaoEmStatement(statement, nomeExportado, sourceFile) {
8
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === nomeExportado && statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
7
+ export function localizarExportacaoEmStatement(statement, nomeExportado, sourceFile, permitirNaoExportada = false) {
8
+ const declarada = (statement) => permitirNaoExportada
9
+ || statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) === true;
10
+ if (ts.isFunctionDeclaration(statement) && statement.name?.text === nomeExportado && declarada(statement)) {
9
11
  return {
10
12
  corpo: statement.body,
11
13
  retorno: statement.type?.getText(sourceFile),
@@ -13,7 +15,7 @@ export function localizarExportacaoEmStatement(statement, nomeExportado, sourceF
13
15
  declaracao: statement,
14
16
  };
15
17
  }
16
- if (!ts.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
18
+ if (!ts.isVariableStatement(statement) || !declarada(statement)) {
17
19
  return undefined;
18
20
  }
19
21
  for (const declaracao of statement.declarationList.declarations) {
@@ -110,17 +112,17 @@ export function extrairRetornoHttp(expr, sourceFile, valoresLocais) {
110
112
  }
111
113
  return undefined;
112
114
  }
113
- export function localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado) {
115
+ export function localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado, permitirNaoExportada = false) {
114
116
  for (const node of sourceFile.statements) {
115
- const localizada = localizarExportacaoEmStatement(node, nomeExportado, sourceFile);
117
+ const localizada = localizarExportacaoEmStatement(node, nomeExportado, sourceFile, permitirNaoExportada);
116
118
  if (localizada) {
117
119
  return localizada;
118
120
  }
119
121
  }
120
122
  return undefined;
121
123
  }
122
- export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado) {
123
- const exportacao = localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado);
124
+ export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado, permitirNaoExportada = false) {
125
+ const exportacao = localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado, permitirNaoExportada);
124
126
  if (!exportacao?.corpo) {
125
127
  return undefined;
126
128
  }
@@ -139,6 +141,12 @@ export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado)
139
141
  requestNames.add(parametro.name.text);
140
142
  }
141
143
  }
144
+ const responseNames = new Set();
145
+ for (const [indice, parametro] of exportacao.parametros.entries()) {
146
+ if (indice > 0 && ts.isIdentifier(parametro.name)) {
147
+ responseNames.add(parametro.name.text);
148
+ }
149
+ }
142
150
  const visitarDeclaracoes = (node) => {
143
151
  if (ts.isVariableDeclaration(node) && node.initializer) {
144
152
  const inicializadorNormalizado = desembrulharExpressao(node.initializer);
@@ -256,6 +264,56 @@ export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado)
256
264
  bodyTipoTexto ??= tipoTexto;
257
265
  };
258
266
  const visitarSemantica = (node) => {
267
+ if (ts.isPropertyAccessExpression(node)
268
+ && ts.isPropertyAccessExpression(node.expression)
269
+ && ts.isIdentifier(node.expression.expression)
270
+ && requestNames.has(node.expression.expression.text)) {
271
+ const origemAcesso = node.expression.name.text;
272
+ const nomeCampo = node.name.text;
273
+ const ehQuery = origemAcesso === "query" || origemAcesso === "params"
274
+ || (origemAcesso === "request" && (nomeCampo === "query" || nomeCampo === "searchParams"));
275
+ const ehBody = origemAcesso === "body" || (origemAcesso === "request" && nomeCampo === "body");
276
+ if (ehQuery && origemAcesso !== "params") {
277
+ query.push({
278
+ nome: nomeCampo,
279
+ tipoTexto: inferirTipoPorContexto(node, nomeCampo) ?? "string",
280
+ obrigatorio: false,
281
+ });
282
+ }
283
+ else if (ehBody) {
284
+ body.push({
285
+ nome: nomeCampo,
286
+ tipoTexto: inferirTipoPorContexto(node, nomeCampo) ?? "string",
287
+ obrigatorio: false,
288
+ });
289
+ }
290
+ }
291
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "json") {
292
+ let alvo = node.expression.expression;
293
+ let status;
294
+ if (ts.isCallExpression(alvo)
295
+ && ts.isPropertyAccessExpression(alvo.expression)
296
+ && alvo.expression.name.text === "status"
297
+ && alvo.arguments[0]
298
+ && ts.isNumericLiteral(alvo.arguments[0])) {
299
+ status = Number(alvo.arguments[0].text);
300
+ alvo = alvo.expression.expression;
301
+ }
302
+ if (ts.isIdentifier(alvo) && responseNames.has(alvo.text)) {
303
+ const argumento = node.arguments[0];
304
+ const campos = argumento && ts.isObjectLiteralExpression(argumento)
305
+ ? extrairCamposObjetoLiteral(argumento, sourceFile)
306
+ : argumento && ts.isIdentifier(argumento) && valoresLocais.has(argumento.text)
307
+ ? valoresLocais.get(argumento.text).campos
308
+ : [];
309
+ if (typeof status === "number") {
310
+ statuses.push(status);
311
+ }
312
+ if (status === undefined || status < 400) {
313
+ response.push(...campos);
314
+ }
315
+ }
316
+ }
259
317
  if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
260
318
  const alvo = node.expression.expression;
261
319
  const metodo = node.expression.name.text;
@@ -0,0 +1,95 @@
1
+ module exemplos.koa.crud {
2
+ entity Tarefa {
3
+ fields {
4
+ id: Id
5
+ titulo: Texto
6
+ concluida: Booleano
7
+ }
8
+ }
9
+
10
+ task criar_tarefa {
11
+ input {
12
+ titulo: Texto required
13
+ }
14
+ output {
15
+ tarefa: Tarefa
16
+ }
17
+ rules {
18
+ titulo deve_ser preenchido
19
+ }
20
+ effects {
21
+ persistencia Tarefa
22
+ auditoria tarefa_criada
23
+ }
24
+ guarantees {
25
+ tarefa existe
26
+ }
27
+ tests {
28
+ caso "cria tarefa" {
29
+ given {
30
+ titulo: "Revisar contrato"
31
+ }
32
+
33
+ expect {
34
+ sucesso: verdadeiro
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ task concluir_tarefa {
41
+ input {
42
+ id: Id required
43
+ }
44
+ output {
45
+ tarefa: Tarefa
46
+ }
47
+ rules {
48
+ id deve_ser valido
49
+ }
50
+ effects {
51
+ persistencia Tarefa
52
+ auditoria tarefa_concluida
53
+ }
54
+ guarantees {
55
+ tarefa existe
56
+ }
57
+ tests {
58
+ caso "conclui tarefa por id" {
59
+ given {
60
+ id: "tar_1"
61
+ }
62
+
63
+ expect {
64
+ sucesso: verdadeiro
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ route tarefas_criar {
71
+ metodo: POST
72
+ caminho: /api/tarefas
73
+ task: criar_tarefa
74
+ finalidade: cadastro_tarefa
75
+ input {
76
+ titulo: Texto
77
+ }
78
+ output {
79
+ tarefa: Tarefa
80
+ }
81
+ }
82
+
83
+ route tarefas_concluir {
84
+ metodo: PATCH
85
+ caminho: "/api/tarefas/{id}"
86
+ task: concluir_tarefa
87
+ finalidade: alteracao_tarefa
88
+ input {
89
+ id: Id
90
+ }
91
+ output {
92
+ tarefa: Tarefa
93
+ }
94
+ }
95
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@semacode/cli",
3
- "version": "3.5.0",
3
+ "version": "3.8.1",
4
4
  "description": "Codex-native local semantic governance CLI for contracts, drift, impact, documentation gates, and code generation.",
5
5
  "type": "module",
6
6
  "icon": "logo.png",