@semacode/cli 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -190,6 +190,7 @@ function mapearTipoPrimitivo(tipo) {
190
190
  const limpo = tipo.trim().replace(/\s+/g, "");
191
191
  const base = limpo
192
192
  .replace(/^Promise<(.*)>$/, "$1")
193
+ .replace(/^Future<(.*)>$/, "$1")
193
194
  .replace(/\|undefined/g, "")
194
195
  .replace(/\|null/g, "")
195
196
  .replace(/\bundefined\|/g, "")
@@ -217,10 +218,22 @@ function mapearTipoPrimitivo(tipo) {
217
218
  if (minusculo === "id" || minusculo.endsWith("id")) {
218
219
  return "Id";
219
220
  }
220
- if (minusculo.includes("[]") || minusculo.startsWith("array<") || minusculo.startsWith("record<") || minusculo.startsWith("list[") || minusculo.startsWith("dict[")) {
221
+ if (minusculo.includes("[]")
222
+ || minusculo.startsWith("array<")
223
+ || minusculo.startsWith("record<")
224
+ || minusculo.startsWith("map<")
225
+ || minusculo.startsWith("list<")
226
+ || minusculo.startsWith("list[")
227
+ || minusculo.startsWith("dict[")) {
221
228
  return "Json";
222
229
  }
223
- if (minusculo === "json" || minusculo === "object" || minusculo === "unknown" || minusculo === "any" || minusculo === "void" || minusculo === "none") {
230
+ if (minusculo === "json"
231
+ || minusculo === "object"
232
+ || minusculo === "unknown"
233
+ || minusculo === "any"
234
+ || minusculo === "dynamic"
235
+ || minusculo === "void"
236
+ || minusculo === "none") {
224
237
  return minusculo === "void" || minusculo === "none" ? "Vazio" : "Json";
225
238
  }
226
239
  return tipo.trim();
@@ -568,6 +581,624 @@ function resolverEscopoImportacaoNextJs(diretorioEntrada) {
568
581
  diretorioEscopo: resolvido,
569
582
  };
570
583
  }
584
+ function normalizarCaminhoImportado(caminhoArquivo) {
585
+ return caminhoArquivo.replace(/\\/g, "/");
586
+ }
587
+ function normalizarSegmentoRotaConsumer(segmento) {
588
+ const opcionalCatchAll = segmento.match(/^\[\[\.\.\.([A-Za-z_]\w*)\]\]$/);
589
+ if (opcionalCatchAll) {
590
+ return `{${opcionalCatchAll[1]}}`;
591
+ }
592
+ const catchAll = segmento.match(/^\[\.\.\.([A-Za-z_]\w*)\]$/);
593
+ if (catchAll) {
594
+ return `{${catchAll[1]}}`;
595
+ }
596
+ const dinamico = segmento.match(/^\[([A-Za-z_]\w*)\]$/);
597
+ if (dinamico) {
598
+ return `{${dinamico[1]}}`;
599
+ }
600
+ return segmento;
601
+ }
602
+ function montarCaminhoRotaConsumer(partes) {
603
+ const filtradas = partes
604
+ .filter((segmento) => segmento && segmento !== "index" && !/^\(.*\)$/.test(segmento) && !segmento.startsWith("@"))
605
+ .map(normalizarSegmentoRotaConsumer);
606
+ return filtradas.length > 0 ? `/${filtradas.join("/")}`.replace(/\/+/g, "/") : "/";
607
+ }
608
+ function resolverEscopoImportacaoFrontendConsumer(diretorioEntrada) {
609
+ const resolvido = path.resolve(diretorioEntrada);
610
+ const partes = path.parse(resolvido);
611
+ const segmentos = resolvido.slice(partes.root.length).split(path.sep).filter(Boolean);
612
+ const procurarSequencia = (sequencia) => segmentos.findIndex((segmento, indice) => sequencia.every((item, deslocamento) => segmentos[indice + deslocamento]?.toLowerCase() === item));
613
+ const montarBase = (indice) => indice <= 0
614
+ ? partes.root
615
+ : path.join(partes.root, ...segmentos.slice(0, indice));
616
+ for (const sequencia of [
617
+ ["src", "pages"],
618
+ ["pages"],
619
+ ["src", "app", "api"],
620
+ ["app", "api"],
621
+ ["src", "app"],
622
+ ["app"],
623
+ ["src", "lib"],
624
+ ["lib"],
625
+ ]) {
626
+ const indice = procurarSequencia(sequencia);
627
+ if (indice >= 0) {
628
+ return {
629
+ baseProjeto: montarBase(indice),
630
+ diretorioEscopo: resolvido,
631
+ };
632
+ }
633
+ }
634
+ return {
635
+ baseProjeto: resolvido,
636
+ diretorioEscopo: resolvido,
637
+ };
638
+ }
639
+ function arquivoEhBridgeNextJsConsumer(relacaoArquivo) {
640
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
641
+ return /(?:^|\/)(?:src\/)?lib\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|tsx|js|jsx)$/i.test(relacao);
642
+ }
643
+ function arquivoEhBridgeReactViteConsumer(relacaoArquivo) {
644
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
645
+ return /(?:^|\/)(?:src\/)?lib\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|tsx|js|jsx)$/i.test(relacao);
646
+ }
647
+ function arquivoEhBridgeAngularConsumer(relacaoArquivo) {
648
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
649
+ return /(?:^|\/)(?:src\/)?app\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|js)$/i.test(relacao);
650
+ }
651
+ function arquivoEhSuperficieNextJsConsumer(relacaoArquivo) {
652
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
653
+ return /(?:^|\/)(?:src\/)?app\/(?:(?!api\/).)*?(?:page|layout|loading|error)\.(?:ts|tsx|js|jsx)$/i.test(relacao);
654
+ }
655
+ function arquivoEhSuperficieReactViteConsumer(relacaoArquivo) {
656
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
657
+ return /^(?:src\/)?pages\/.+\.(?:ts|tsx|js|jsx)$/i.test(relacao)
658
+ || /^(?:src\/)?App\.(?:ts|tsx|js|jsx)$/i.test(relacao);
659
+ }
660
+ function arquivoEhRotasReactViteConsumer(relacaoArquivo, codigo) {
661
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
662
+ return /(?:^|\/)(?:src\/)?(?:app\/)?(?:router|routes)\.(?:ts|tsx|js|jsx)$/i.test(relacao)
663
+ || /from\s+["']react-router-dom["']|createBrowserRouter|RouterProvider|useRoutes\s*\(|<Routes\b|<Route\b/.test(codigo ?? "");
664
+ }
665
+ function arquivoEhRotasAngularConsumer(relacaoArquivo) {
666
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
667
+ return /(?:^|\/)(?:src\/)?app(?:\/.+)?\/[^/]+\.routes\.(?:ts|js)$/i.test(relacao);
668
+ }
669
+ function arquivoEhRotasAngularConsumerRaiz(relacaoArquivo) {
670
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
671
+ return /(?:^|\/)(?:src\/)?app\/[^/]+\.routes\.(?:ts|js)$/i.test(relacao);
672
+ }
673
+ function arquivoEhBridgeFlutterConsumer(relacaoArquivo) {
674
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
675
+ return /(?:^|\/)(?:lib\/)?(?:sema_consumer_bridge|api\/sema_contract_bridge|sema\/.+)\.dart$/i.test(relacao);
676
+ }
677
+ function arquivoEhSuperficieFlutterConsumer(relacaoArquivo) {
678
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
679
+ return /(?:^|\/)(?:lib\/)?(?:screens|pages)\/.+\.dart$/i.test(relacao)
680
+ || /(?:^|\/)(?:lib\/)?main\.dart$/i.test(relacao);
681
+ }
682
+ function arquivoEhRotasFlutterConsumer(relacaoArquivo, codigo) {
683
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
684
+ return /(?:^|\/)(?:lib\/)?(?:router|app_router|routes)\.dart$/i.test(relacao)
685
+ || /MaterialApp(?:\.router)?\s*\(|CupertinoApp(?:\.router)?\s*\(|GoRouter\s*\(/.test(codigo ?? "");
686
+ }
687
+ function inferirCaminhoNextJsConsumer(relacaoArquivo) {
688
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
689
+ const segmentos = relacao.split("/");
690
+ const indiceSrcApp = segmentos.findIndex((segmento, indice) => segmento === "src" && segmentos[indice + 1] === "app");
691
+ const indiceApp = segmentos.findIndex((segmento) => segmento === "app");
692
+ const inicioApp = indiceSrcApp >= 0 ? indiceSrcApp + 2 : indiceApp >= 0 ? indiceApp + 1 : -1;
693
+ if (inicioApp < 0) {
694
+ return undefined;
695
+ }
696
+ const arquivoFinal = segmentos.at(-1) ?? "";
697
+ const tipoArquivo = arquivoFinal.match(/^(page|layout|loading|error)\.(?:ts|tsx|js|jsx)$/)?.[1];
698
+ if (!tipoArquivo) {
699
+ return undefined;
700
+ }
701
+ const caminhoAteArquivo = segmentos.slice(inicioApp, -1);
702
+ if (caminhoAteArquivo[0] === "api") {
703
+ return undefined;
704
+ }
705
+ const partes = caminhoAteArquivo
706
+ .filter((segmento) => segmento);
707
+ const caminho = montarCaminhoRotaConsumer(partes);
708
+ return {
709
+ caminho,
710
+ arquivo: relacao,
711
+ tipoArquivo,
712
+ };
713
+ }
714
+ function inferirCaminhoReactViteConsumer(relacaoArquivo) {
715
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
716
+ if (!arquivoEhSuperficieReactViteConsumer(relacao)) {
717
+ return undefined;
718
+ }
719
+ if (/(?:^|\/)(?:src\/)?App\.(?:ts|tsx|js|jsx)$/i.test(relacao)) {
720
+ return {
721
+ caminho: "/",
722
+ arquivo: relacao,
723
+ tipoArquivo: "app",
724
+ };
725
+ }
726
+ const segmentos = relacao.split("/");
727
+ const indiceSrcPages = segmentos.findIndex((segmento, indice) => segmento === "src" && segmentos[indice + 1] === "pages");
728
+ const indicePages = segmentos.findIndex((segmento) => segmento === "pages");
729
+ const inicioPages = indiceSrcPages >= 0 ? indiceSrcPages + 2 : indicePages >= 0 ? indicePages + 1 : -1;
730
+ if (inicioPages < 0) {
731
+ return undefined;
732
+ }
733
+ const arquivoFinal = segmentos.at(-1) ?? "";
734
+ const nomeBase = arquivoFinal.replace(/\.(?:ts|tsx|js|jsx)$/i, "");
735
+ const caminho = montarCaminhoRotaConsumer([...segmentos.slice(inicioPages, -1), nomeBase]);
736
+ return {
737
+ caminho,
738
+ arquivo: relacao,
739
+ tipoArquivo: "page",
740
+ };
741
+ }
742
+ function inferirCaminhoFlutterConsumer(relacaoArquivo) {
743
+ const relacao = normalizarCaminhoImportado(relacaoArquivo);
744
+ if (!arquivoEhSuperficieFlutterConsumer(relacao)) {
745
+ return undefined;
746
+ }
747
+ if (/(?:^|\/)(?:lib\/)?main\.dart$/i.test(relacao)) {
748
+ return {
749
+ caminho: "/",
750
+ arquivo: relacao,
751
+ tipoArquivo: "app",
752
+ };
753
+ }
754
+ const segmentos = relacao.split("/");
755
+ const indiceLibScreens = segmentos.findIndex((segmento, indice) => segmento === "lib" && ["screens", "pages"].includes(segmentos[indice + 1] ?? ""));
756
+ const indiceScreens = segmentos.findIndex((segmento) => segmento === "screens" || segmento === "pages");
757
+ const inicio = indiceLibScreens >= 0 ? indiceLibScreens + 2 : indiceScreens >= 0 ? indiceScreens + 1 : -1;
758
+ if (inicio < 0) {
759
+ return undefined;
760
+ }
761
+ const arquivoFinal = segmentos.at(-1) ?? "";
762
+ const nomeBase = arquivoFinal
763
+ .replace(/\.(?:dart)$/i, "")
764
+ .replace(/_(screen|page)$/i, "");
765
+ return {
766
+ caminho: montarCaminhoRotaConsumer([...segmentos.slice(inicio, -1), nomeBase]),
767
+ arquivo: relacao,
768
+ tipoArquivo: "screen",
769
+ };
770
+ }
771
+ function normalizarRotaDeclaradaConsumer(caminhoCru, prefixo = "/") {
772
+ const partesPrefixo = prefixo.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
773
+ const partesCaminho = (caminhoCru ?? "").trim().replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
774
+ return montarCaminhoRotaConsumer([...partesPrefixo, ...partesCaminho]);
775
+ }
776
+ function resolverImportRelativoTypeScript(relacaoArquivoBase, especificador) {
777
+ if (!especificador.startsWith(".")) {
778
+ return undefined;
779
+ }
780
+ const baseDir = path.posix.dirname(normalizarCaminhoImportado(relacaoArquivoBase));
781
+ for (const sufixo of ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"]) {
782
+ const candidato = path.posix.normalize(path.posix.join(baseDir, `${especificador}${sufixo}`));
783
+ if (!/\.(?:ts|tsx|js|jsx)$/i.test(candidato)) {
784
+ continue;
785
+ }
786
+ return candidato;
787
+ }
788
+ return undefined;
789
+ }
790
+ function extrairImportsTypeScriptConsumer(relacaoArquivo, codigo) {
791
+ const imports = new Map();
792
+ for (const match of codigo.matchAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*["']([^"']+)["']/g)) {
793
+ const moduloImportado = match[2];
794
+ const relacaoImportada = resolverImportRelativoTypeScript(relacaoArquivo, moduloImportado);
795
+ if (!relacaoImportada) {
796
+ continue;
797
+ }
798
+ for (const bruto of match[1].split(",")) {
799
+ const normalizado = bruto.trim();
800
+ if (!normalizado) {
801
+ continue;
802
+ }
803
+ const local = normalizado.split(/\s+as\s+/i).at(-1)?.trim();
804
+ if (local) {
805
+ imports.set(local, relacaoImportada);
806
+ }
807
+ }
808
+ }
809
+ for (const match of codigo.matchAll(/import\s+([A-Za-z_]\w*)\s+from\s*["']([^"']+)["']/g)) {
810
+ const relacaoImportada = resolverImportRelativoTypeScript(relacaoArquivo, match[2]);
811
+ const local = match[1]?.trim();
812
+ if (relacaoImportada && local) {
813
+ imports.set(local, relacaoImportada);
814
+ }
815
+ }
816
+ return imports;
817
+ }
818
+ function extrairRotasReactViteConsumer(relacaoArquivo, codigo) {
819
+ const imports = extrairImportsTypeScriptConsumer(relacaoArquivo, codigo);
820
+ const rotas = new Map();
821
+ const registrar = (caminhoCru, componente) => {
822
+ const caminho = normalizarRotaDeclaradaConsumer(caminhoCru);
823
+ const chave = `${caminho}:${normalizarCaminhoImportado(relacaoArquivo)}:${componente ?? "router"}`;
824
+ rotas.set(chave, {
825
+ caminho,
826
+ arquivoRotas: normalizarCaminhoImportado(relacaoArquivo),
827
+ arquivoComponente: componente ? imports.get(componente) : undefined,
828
+ });
829
+ };
830
+ 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)) {
831
+ const caminhoCru = match[1] ?? "";
832
+ const componente = match[2] ?? match[3];
833
+ registrar(caminhoCru, componente);
834
+ }
835
+ 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)) {
836
+ const caminhoCru = match[2] ? "" : (match[1] ?? "");
837
+ const componente = match[3] ?? match[4];
838
+ registrar(caminhoCru, componente);
839
+ }
840
+ return [...rotas.values()];
841
+ }
842
+ function extrairRotasAngularConsumerDiretas(relacaoArquivo, codigo, prefixo = "/") {
843
+ const imports = extrairImportsTypeScriptConsumer(relacaoArquivo, codigo);
844
+ const rotas = [];
845
+ for (const match of codigo.matchAll(/path\s*:\s*["'`]([^"'`]*)["'`][\s\S]{0,320}?component\s*:\s*([A-Za-z_]\w*)/g)) {
846
+ const caminhoCru = (match[1] ?? "").trim();
847
+ const componente = match[2];
848
+ rotas.push({
849
+ caminho: normalizarRotaDeclaradaConsumer(caminhoCru, prefixo),
850
+ arquivoRotas: normalizarCaminhoImportado(relacaoArquivo),
851
+ arquivoComponente: imports.get(componente),
852
+ });
853
+ }
854
+ for (const match of codigo.matchAll(/path\s*:\s*["'`]([^"'`]*)["'`][\s\S]{0,320}?loadComponent\s*:\s*\(\s*\)\s*=>\s*import\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g)) {
855
+ const caminhoCru = (match[1] ?? "").trim();
856
+ const relacaoImportada = resolverImportRelativoTypeScript(relacaoArquivo, match[2] ?? "");
857
+ rotas.push({
858
+ caminho: normalizarRotaDeclaradaConsumer(caminhoCru, prefixo),
859
+ arquivoRotas: normalizarCaminhoImportado(relacaoArquivo),
860
+ arquivoComponente: relacaoImportada,
861
+ });
862
+ }
863
+ for (const match of codigo.matchAll(/path\s*:\s*["'`]([^"'`]*)["'`][\s\S]{0,360}?loadChildren\s*:\s*\(\s*\)\s*=>\s*import\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g)) {
864
+ const caminhoCru = (match[1] ?? "").trim();
865
+ const relacaoImportada = resolverImportRelativoTypeScript(relacaoArquivo, match[2] ?? "");
866
+ rotas.push({
867
+ caminho: normalizarRotaDeclaradaConsumer(caminhoCru, prefixo),
868
+ arquivoRotas: normalizarCaminhoImportado(relacaoArquivo),
869
+ arquivoRotasFilhas: relacaoImportada,
870
+ });
871
+ }
872
+ return rotas;
873
+ }
874
+ async function extrairRotasAngularConsumer(baseProjeto, relacaoArquivo, prefixo = "/", visitados = new Set()) {
875
+ const relacaoNormalizada = normalizarCaminhoImportado(relacaoArquivo);
876
+ if (visitados.has(relacaoNormalizada)) {
877
+ return [];
878
+ }
879
+ visitados.add(relacaoNormalizada);
880
+ const caminhoAbsoluto = path.join(baseProjeto, relacaoNormalizada);
881
+ let codigo = "";
882
+ try {
883
+ codigo = await readFile(caminhoAbsoluto, "utf8");
884
+ }
885
+ catch {
886
+ return [];
887
+ }
888
+ const diretas = extrairRotasAngularConsumerDiretas(relacaoNormalizada, codigo, prefixo);
889
+ const agregadas = [...diretas];
890
+ for (const rota of diretas) {
891
+ if (!rota.arquivoRotasFilhas) {
892
+ continue;
893
+ }
894
+ agregadas.push(...await extrairRotasAngularConsumer(baseProjeto, rota.arquivoRotasFilhas, rota.caminho, visitados));
895
+ }
896
+ return agregadas;
897
+ }
898
+ function normalizarRotaDeclaradaFlutter(caminhoCru) {
899
+ return montarCaminhoRotaConsumer((caminhoCru ?? "").trim().replace(/^\/+|\/+$/g, "").split("/").filter(Boolean));
900
+ }
901
+ function extrairRotasFlutterConsumer(relacaoArquivo, codigo) {
902
+ const rotas = new Map();
903
+ const registrar = (caminhoCru) => {
904
+ const caminho = normalizarRotaDeclaradaFlutter(caminhoCru);
905
+ rotas.set(`${caminho}:${normalizarCaminhoImportado(relacaoArquivo)}`, {
906
+ caminho,
907
+ arquivoRotas: normalizarCaminhoImportado(relacaoArquivo),
908
+ });
909
+ };
910
+ for (const match of codigo.matchAll(/GoRoute\s*\([\s\S]{0,220}?path\s*:\s*["'`]([^"'`]+)["'`]/g)) {
911
+ registrar(match[1] ?? "");
912
+ }
913
+ for (const match of codigo.matchAll(/["'`]([^"'`]+)["'`]\s*:\s*\([^)]*\)\s*=>/g)) {
914
+ registrar(match[1] ?? "");
915
+ }
916
+ if (/home\s*:\s*(?:const\s+)?[A-Za-z_]\w*\(/.test(codigo)) {
917
+ registrar("/");
918
+ }
919
+ return [...rotas.values()];
920
+ }
921
+ async function carregarContextosBridgeConsumer(baseProjeto, arquivosBridge) {
922
+ return Promise.all(arquivosBridge.map(async (arquivo) => {
923
+ const texto = await readFile(arquivo, "utf8");
924
+ const scriptKind = arquivo.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
925
+ return {
926
+ sourceFile: ts.createSourceFile(arquivo, texto, ts.ScriptTarget.Latest, true, scriptKind),
927
+ texto,
928
+ relacao: path.relative(baseProjeto, arquivo),
929
+ };
930
+ }));
931
+ }
932
+ function extrairTasksBridgeConsumer(baseProjeto, contextosBridge) {
933
+ const tiposGlobais = consolidarTiposTs(contextosBridge);
934
+ const entitiesRef = new Set();
935
+ const enumsRef = new Set();
936
+ const tasks = [];
937
+ for (const contexto of contextosBridge) {
938
+ contexto.sourceFile.forEachChild((node) => {
939
+ if (ts.isFunctionDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
940
+ const nome = node.name.text;
941
+ const input = node.parameters.flatMap((parametro) => expandirCamposTs(parametro.name.getText(contexto.sourceFile), parametro.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
942
+ const output = node.type?.getText(contexto.sourceFile) && mapearTipoPrimitivo(node.type.getText(contexto.sourceFile)) === "Vazio"
943
+ ? []
944
+ : deduplicarCampos(expandirCamposTs("resultado", node.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
945
+ tasks.push({
946
+ nome: nomeTaskBridgeConsumer(nome),
947
+ resumo: `Task consumer importada automaticamente de ${contexto.relacao}#${nome}.`,
948
+ input: deduplicarCampos(input),
949
+ output,
950
+ errors: node.body ? extrairErrosTs(node.body, contexto.sourceFile) : [],
951
+ effects: node.body ? descreverEfeitosPorHeuristica(node.body.getText(contexto.sourceFile)) : [],
952
+ impl: { ts: caminhoImplTs(baseProjeto, path.join(baseProjeto, contexto.relacao), nome) },
953
+ vinculos: deduplicarVinculos([
954
+ { tipo: "arquivo", valor: normalizarCaminhoImportado(contexto.relacao) },
955
+ { tipo: "simbolo", valor: caminhoImplTs(baseProjeto, path.join(baseProjeto, contexto.relacao), nome) },
956
+ ]),
957
+ origemArquivo: contexto.relacao,
958
+ origemSimbolo: nome,
959
+ });
960
+ }
961
+ if (ts.isClassDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
962
+ const nomeClasse = node.name.text;
963
+ for (const member of node.members) {
964
+ if (!ts.isMethodDeclaration(member) || !member.name || !member.body) {
965
+ continue;
966
+ }
967
+ if (member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword)) {
968
+ continue;
969
+ }
970
+ const nomeMetodo = member.name.getText(contexto.sourceFile);
971
+ if (nomeMetodo === "constructor") {
972
+ continue;
973
+ }
974
+ const input = member.parameters.flatMap((parametro) => expandirCamposTs(parametro.name.getText(contexto.sourceFile), parametro.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
975
+ const output = member.type?.getText(contexto.sourceFile) && mapearTipoPrimitivo(member.type.getText(contexto.sourceFile)) === "Vazio"
976
+ ? []
977
+ : deduplicarCampos(expandirCamposTs("resultado", member.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
978
+ const caminhoSimbolo = caminhoImplTs(baseProjeto, path.join(baseProjeto, contexto.relacao), `${nomeClasse}.${nomeMetodo}`);
979
+ tasks.push({
980
+ nome: nomeTaskBridgeConsumer(nomeMetodo),
981
+ resumo: `Task consumer importada automaticamente de ${contexto.relacao}#${nomeClasse}.${nomeMetodo}.`,
982
+ input: deduplicarCampos(input),
983
+ output,
984
+ errors: extrairErrosTs(member.body, contexto.sourceFile),
985
+ effects: descreverEfeitosPorHeuristica(member.body.getText(contexto.sourceFile)),
986
+ impl: { ts: caminhoSimbolo },
987
+ vinculos: deduplicarVinculos([
988
+ { tipo: "arquivo", valor: normalizarCaminhoImportado(contexto.relacao) },
989
+ { tipo: "simbolo", valor: caminhoSimbolo },
990
+ ]),
991
+ origemArquivo: contexto.relacao,
992
+ origemSimbolo: `${nomeClasse}.${nomeMetodo}`,
993
+ });
994
+ }
995
+ }
996
+ });
997
+ }
998
+ const { entities, enums } = criarEntidadesReferenciadas(tiposGlobais, entitiesRef, enumsRef);
999
+ return {
1000
+ tasks,
1001
+ entities,
1002
+ enums,
1003
+ };
1004
+ }
1005
+ function montarVinculosSuperficiesConsumer(superficies) {
1006
+ return deduplicarVinculos(superficies.flatMap((superficie) => [
1007
+ { tipo: "superficie", valor: superficie.caminho },
1008
+ { tipo: "arquivo", valor: normalizarCaminhoImportado(superficie.arquivo) },
1009
+ ]));
1010
+ }
1011
+ async function importarConsumerBase(diretorio, namespaceBase, descricaoFramework, ehBridge, coletarSuperficies) {
1012
+ const escopo = resolverEscopoImportacaoFrontendConsumer(diretorio);
1013
+ const arquivos = await listarArquivosRecursivos(escopo.baseProjeto, [".ts", ".tsx", ".js", ".jsx"]);
1014
+ const arquivosBridge = arquivos.filter((arquivo) => ehBridge(path.relative(escopo.baseProjeto, arquivo)));
1015
+ const contextosBridge = await carregarContextosBridgeConsumer(escopo.baseProjeto, arquivosBridge);
1016
+ const { tasks, entities, enums } = extrairTasksBridgeConsumer(escopo.baseProjeto, contextosBridge);
1017
+ const superficiesImportadas = await coletarSuperficies(escopo.baseProjeto, arquivos);
1018
+ const superficies = montarVinculosSuperficiesConsumer(superficiesImportadas);
1019
+ if (tasks.length === 0 && superficies.length === 0) {
1020
+ return [];
1021
+ }
1022
+ const nomeModulo = namespaceBase.endsWith(".consumer")
1023
+ ? namespaceBase
1024
+ : `${namespaceBase}.consumer`;
1025
+ return [{
1026
+ nome: nomeModulo,
1027
+ resumo: `Rascunho Sema importado automaticamente do consumer ${descricaoFramework} em ${escopo.baseProjeto}.`,
1028
+ tasks: deduplicarTarefas(tasks),
1029
+ routes: [],
1030
+ entities,
1031
+ enums,
1032
+ vinculos: superficies,
1033
+ }];
1034
+ }
1035
+ async function coletarSuperficiesNextJsConsumer(baseProjeto, arquivos) {
1036
+ return arquivos
1037
+ .map((arquivo) => inferirCaminhoNextJsConsumer(path.relative(baseProjeto, arquivo)))
1038
+ .filter((item) => Boolean(item));
1039
+ }
1040
+ async function coletarSuperficiesReactViteConsumer(baseProjeto, arquivos) {
1041
+ const superficies = [];
1042
+ for (const arquivo of arquivos) {
1043
+ const relacao = path.relative(baseProjeto, arquivo);
1044
+ const codigo = await readFile(arquivo, "utf8");
1045
+ if (arquivoEhRotasReactViteConsumer(relacao, codigo)) {
1046
+ for (const rota of extrairRotasReactViteConsumer(relacao, codigo)) {
1047
+ superficies.push({
1048
+ caminho: rota.caminho,
1049
+ arquivo: rota.arquivoRotas,
1050
+ tipoArquivo: "router",
1051
+ });
1052
+ if (rota.arquivoComponente) {
1053
+ superficies.push({
1054
+ caminho: rota.caminho,
1055
+ arquivo: rota.arquivoComponente,
1056
+ tipoArquivo: "page",
1057
+ });
1058
+ }
1059
+ }
1060
+ }
1061
+ }
1062
+ for (const arquivo of arquivos) {
1063
+ const superficie = inferirCaminhoReactViteConsumer(path.relative(baseProjeto, arquivo));
1064
+ if (superficie) {
1065
+ superficies.push(superficie);
1066
+ }
1067
+ }
1068
+ return superficies;
1069
+ }
1070
+ async function coletarSuperficiesAngularConsumer(baseProjeto, arquivos) {
1071
+ const superficies = [];
1072
+ const arquivosRotas = arquivos.filter((arquivo) => arquivoEhRotasAngularConsumer(path.relative(baseProjeto, arquivo)));
1073
+ const arquivosRaiz = arquivosRotas.filter((arquivo) => arquivoEhRotasAngularConsumerRaiz(path.relative(baseProjeto, arquivo)));
1074
+ const pontosEntrada = arquivosRaiz.length > 0 ? arquivosRaiz : arquivosRotas;
1075
+ for (const arquivoRotas of pontosEntrada) {
1076
+ const relacao = path.relative(baseProjeto, arquivoRotas);
1077
+ for (const rota of await extrairRotasAngularConsumer(baseProjeto, relacao)) {
1078
+ superficies.push({
1079
+ caminho: rota.caminho,
1080
+ arquivo: rota.arquivoRotas,
1081
+ tipoArquivo: "routes",
1082
+ });
1083
+ if (rota.arquivoComponente) {
1084
+ superficies.push({
1085
+ caminho: rota.caminho,
1086
+ arquivo: rota.arquivoComponente,
1087
+ tipoArquivo: "component",
1088
+ });
1089
+ }
1090
+ }
1091
+ }
1092
+ return superficies;
1093
+ }
1094
+ async function importarNextJsConsumerBase(diretorio, namespaceBase) {
1095
+ return importarConsumerBase(diretorio, namespaceBase, "Next.js", arquivoEhBridgeNextJsConsumer, coletarSuperficiesNextJsConsumer);
1096
+ }
1097
+ async function importarReactViteConsumerBase(diretorio, namespaceBase) {
1098
+ return importarConsumerBase(diretorio, namespaceBase, "React/Vite", arquivoEhBridgeReactViteConsumer, coletarSuperficiesReactViteConsumer);
1099
+ }
1100
+ async function importarAngularConsumerBase(diretorio, namespaceBase) {
1101
+ return importarConsumerBase(diretorio, namespaceBase, "Angular", arquivoEhBridgeAngularConsumer, coletarSuperficiesAngularConsumer);
1102
+ }
1103
+ async function extrairTasksBridgeFlutterConsumer(baseProjeto, arquivosBridge) {
1104
+ const tasks = [];
1105
+ for (const arquivo of arquivosBridge) {
1106
+ const texto = await readFile(arquivo, "utf8");
1107
+ const relacao = path.relative(baseProjeto, arquivo);
1108
+ for (const match of texto.matchAll(/(?:Future<([^\n]+)>|([\w?<>.,\s]+))\s+(\w+)\(([^)]*)\)\s*(?:async\s*)?\{/g)) {
1109
+ const retorno = (match[1] ?? match[2] ?? "").trim();
1110
+ const nome = match[3];
1111
+ if (["build", "toString", "hashCode"].includes(nome)) {
1112
+ continue;
1113
+ }
1114
+ const parametros = match[4];
1115
+ const input = parametros
1116
+ .split(",")
1117
+ .map((item) => item.trim())
1118
+ .filter(Boolean)
1119
+ .map((item) => item.replace(/^(required|final)\s+/g, ""))
1120
+ .map((item) => {
1121
+ const partes = item.split(/\s+/).filter(Boolean);
1122
+ const nomeParametro = partes.at(-1) ?? "arg";
1123
+ const tipoParametro = partes.slice(0, -1).join(" ");
1124
+ return {
1125
+ nome: paraSnakeCase(nomeParametro),
1126
+ tipo: mapearTipoPrimitivo(tipoParametro || "Json"),
1127
+ obrigatorio: !/\?/.test(tipoParametro),
1128
+ };
1129
+ });
1130
+ const caminhoSimbolo = caminhoImplDart(baseProjeto, arquivo, nome);
1131
+ tasks.push({
1132
+ nome: nomeTaskBridgeConsumer(nome),
1133
+ resumo: `Task consumer importada automaticamente de ${relacao}#${nome}.`,
1134
+ input,
1135
+ output: retorno && mapearTipoPrimitivo(retorno) === "Vazio"
1136
+ ? []
1137
+ : [{ nome: "resultado", tipo: mapearTipoPrimitivo(retorno || "Json"), obrigatorio: false }],
1138
+ errors: [],
1139
+ effects: descreverEfeitosPorHeuristica(texto),
1140
+ impl: { dart: caminhoSimbolo },
1141
+ vinculos: deduplicarVinculos([
1142
+ { tipo: "arquivo", valor: normalizarCaminhoImportado(relacao) },
1143
+ { tipo: "simbolo", valor: caminhoSimbolo },
1144
+ ]),
1145
+ origemArquivo: relacao,
1146
+ origemSimbolo: nome,
1147
+ });
1148
+ }
1149
+ }
1150
+ return tasks;
1151
+ }
1152
+ async function coletarSuperficiesFlutterConsumer(baseProjeto, arquivos) {
1153
+ const superficies = [];
1154
+ for (const arquivo of arquivos) {
1155
+ const relacao = path.relative(baseProjeto, arquivo);
1156
+ const codigo = await readFile(arquivo, "utf8");
1157
+ if (arquivoEhRotasFlutterConsumer(relacao, codigo)) {
1158
+ for (const rota of extrairRotasFlutterConsumer(relacao, codigo)) {
1159
+ superficies.push({
1160
+ caminho: rota.caminho,
1161
+ arquivo: rota.arquivoRotas,
1162
+ tipoArquivo: "router",
1163
+ });
1164
+ }
1165
+ }
1166
+ }
1167
+ for (const arquivo of arquivos) {
1168
+ const superficie = inferirCaminhoFlutterConsumer(path.relative(baseProjeto, arquivo));
1169
+ if (superficie) {
1170
+ superficies.push(superficie);
1171
+ }
1172
+ }
1173
+ return superficies;
1174
+ }
1175
+ async function importarFlutterConsumerBase(diretorio, namespaceBase) {
1176
+ const escopo = resolverEscopoImportacaoFrontendConsumer(diretorio);
1177
+ const arquivos = (await listarArquivosRecursivos(escopo.baseProjeto, [".dart"]))
1178
+ .filter((arquivo) => !arquivo.endsWith(".g.dart") && !arquivo.endsWith(".freezed.dart"));
1179
+ const arquivosBridge = arquivos.filter((arquivo) => arquivoEhBridgeFlutterConsumer(path.relative(escopo.baseProjeto, arquivo)));
1180
+ const tasks = await extrairTasksBridgeFlutterConsumer(escopo.baseProjeto, arquivosBridge);
1181
+ const superficiesImportadas = await coletarSuperficiesFlutterConsumer(escopo.baseProjeto, arquivos);
1182
+ const superficies = montarVinculosSuperficiesConsumer(superficiesImportadas);
1183
+ if (tasks.length === 0 && superficies.length === 0) {
1184
+ return [];
1185
+ }
1186
+ const nomeModulo = namespaceBase.endsWith(".consumer")
1187
+ ? namespaceBase
1188
+ : `${namespaceBase}.consumer`;
1189
+ return [{
1190
+ nome: nomeModulo,
1191
+ resumo: `Rascunho Sema importado automaticamente do consumer Flutter em ${escopo.baseProjeto}.`,
1192
+ tasks: deduplicarTarefas(tasks),
1193
+ routes: [],
1194
+ entities: [],
1195
+ enums: [],
1196
+ vinculos: superficies,
1197
+ }];
1198
+ }
1199
+ function nomeTaskBridgeConsumer(nome) {
1200
+ return paraSnakeCase(nome.replace(/^sema/, "")) || paraSnakeCase(nome) || "task_consumer";
1201
+ }
571
1202
  function extrairChamadaServiceTs(node) {
572
1203
  let encontrado;
573
1204
  const visitar = (atual) => {
@@ -614,6 +1245,16 @@ function deduplicarEfeitos(effects) {
614
1245
  }
615
1246
  return [...mapa.values()];
616
1247
  }
1248
+ function deduplicarVinculos(vinculos) {
1249
+ const mapa = new Map();
1250
+ for (const vinculo of vinculos) {
1251
+ const chave = `${vinculo.tipo}:${vinculo.valor}`;
1252
+ if (!mapa.has(chave)) {
1253
+ mapa.set(chave, vinculo);
1254
+ }
1255
+ }
1256
+ return [...mapa.values()];
1257
+ }
617
1258
  function deduplicarEntidades(entities) {
618
1259
  const mapa = new Map();
619
1260
  for (const entity of entities) {
@@ -644,6 +1285,7 @@ function deduplicarTarefas(tasks) {
644
1285
  existente.output = deduplicarCampos([...existente.output, ...task.output]);
645
1286
  existente.errors = deduplicarErros([...existente.errors, ...task.errors]);
646
1287
  existente.effects = deduplicarEfeitos([...existente.effects, ...task.effects]);
1288
+ existente.vinculos = deduplicarVinculos([...(existente.vinculos ?? []), ...(task.vinculos ?? [])]);
647
1289
  }
648
1290
  return [...mapa.values()];
649
1291
  }
@@ -723,6 +1365,26 @@ function renderizarImpl(impl, indentacao = " ") {
723
1365
  "",
724
1366
  ];
725
1367
  }
1368
+ function renderizarValorVinculo(vinculo) {
1369
+ if (vinculo.tipo === "simbolo") {
1370
+ return vinculo.valor;
1371
+ }
1372
+ if (vinculo.tipo === "arquivo" || vinculo.valor.includes("/") || vinculo.valor.includes("\\") || vinculo.valor.includes("{")) {
1373
+ return `"${escaparTexto(vinculo.valor)}"`;
1374
+ }
1375
+ return vinculo.valor;
1376
+ }
1377
+ function renderizarVinculos(vinculos, indentacao = " ") {
1378
+ if (!vinculos || vinculos.length === 0) {
1379
+ return [];
1380
+ }
1381
+ return [
1382
+ `${indentacao}vinculos {`,
1383
+ ...vinculos.map((vinculo) => `${indentacao} ${vinculo.tipo}: ${renderizarValorVinculo(vinculo)}`),
1384
+ `${indentacao}}`,
1385
+ "",
1386
+ ];
1387
+ }
726
1388
  function renderizarTask(task) {
727
1389
  const linhas = [
728
1390
  ` task ${task.nome} {`,
@@ -734,6 +1396,7 @@ function renderizarTask(task) {
734
1396
  ...renderizarCampos("output", task.output, " ", true),
735
1397
  ...renderizarEffects(task.effects, " "),
736
1398
  ...renderizarImpl(task.impl, " "),
1399
+ ...renderizarVinculos(task.vinculos, " "),
737
1400
  ...renderizarErrors(task.errors, " "),
738
1401
  ];
739
1402
  linhas.push(" guarantees {");
@@ -791,6 +1454,7 @@ function moduloParaCodigo(modulo) {
791
1454
  ` resumo: "${escaparTexto(modulo.resumo)}"`,
792
1455
  " }",
793
1456
  "",
1457
+ ...renderizarVinculos(modulo.vinculos, " "),
794
1458
  ...modulo.enums.flatMap(renderizarEnum),
795
1459
  ...modulo.entities.flatMap(renderizarEntidade),
796
1460
  ...modulo.tasks.flatMap(renderizarTask),
@@ -1672,7 +2336,7 @@ async function importarDartBase(diretorio, namespaceBase) {
1672
2336
  }
1673
2337
  return modulos;
1674
2338
  }
1675
- function criarModuloImportadoSimples(nome, resumo, tasks, routes = []) {
2339
+ function criarModuloImportadoSimples(nome, resumo, tasks, routes = [], vinculos = []) {
1676
2340
  sincronizarRotasComTasks(routes, tasks);
1677
2341
  return {
1678
2342
  nome,
@@ -1681,6 +2345,7 @@ function criarModuloImportadoSimples(nome, resumo, tasks, routes = []) {
1681
2345
  routes: deduplicarRotas(routes),
1682
2346
  entities: [],
1683
2347
  enums: [],
2348
+ vinculos: deduplicarVinculos(vinculos),
1684
2349
  };
1685
2350
  }
1686
2351
  function acumularModuloImportado(modulos, modulo) {
@@ -1693,6 +2358,7 @@ function acumularModuloImportado(modulos, modulo) {
1693
2358
  existente.routes = deduplicarRotas([...existente.routes, ...modulo.routes]);
1694
2359
  existente.entities = deduplicarEntidades([...existente.entities, ...modulo.entities]);
1695
2360
  existente.enums = deduplicarEnums([...existente.enums, ...modulo.enums]);
2361
+ existente.vinculos = deduplicarVinculos([...(existente.vinculos ?? []), ...(modulo.vinculos ?? [])]);
1696
2362
  }
1697
2363
  function selecionarSimbolosPreferidos(simbolos) {
1698
2364
  const mapa = new Map();
@@ -2029,6 +2695,18 @@ export async function importarProjetoLegado(fonte, diretorio, namespaceBase) {
2029
2695
  else if (fonte === "nextjs") {
2030
2696
  modulos = await importarNextJsBase(base, namespace);
2031
2697
  }
2698
+ else if (fonte === "nextjs-consumer") {
2699
+ modulos = await importarNextJsConsumerBase(base, namespace);
2700
+ }
2701
+ else if (fonte === "react-vite-consumer") {
2702
+ modulos = await importarReactViteConsumerBase(base, namespace);
2703
+ }
2704
+ else if (fonte === "angular-consumer") {
2705
+ modulos = await importarAngularConsumerBase(base, namespace);
2706
+ }
2707
+ else if (fonte === "flutter-consumer") {
2708
+ modulos = await importarFlutterConsumerBase(base, namespace);
2709
+ }
2032
2710
  else if (fonte === "firebase") {
2033
2711
  modulos = await importarFirebaseBase(base, namespace);
2034
2712
  }