@semacode/cli 1.5.0 → 1.5.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.
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
+ import { createRequire } from "node:module";
5
6
  import { fileURLToPath } from "node:url";
6
7
  import pacoteCli from "../package.json" with { type: "json" };
7
8
  import { compilarCodigo, formatarCodigo, formatarDiagnosticos, lerArquivoTexto, temErros, } from "@sema/nucleo";
@@ -321,6 +322,7 @@ Comandos uteis da CLI para esse fluxo:
321
322
  `;
322
323
  const DIRETORIO_CLI_ATUAL = path.dirname(fileURLToPath(import.meta.url));
323
324
  const VERSAO_CLI = pacoteCli.version;
325
+ const requireRuntimeCli = createRequire(import.meta.url);
324
326
  const ARQUIVOS_CANONICOS_IA_RAIZ = [
325
327
  "llms.txt",
326
328
  "SEMA_BRIEF.md",
@@ -338,6 +340,16 @@ const DOCUMENTOS_SUPORTE_IA = [
338
340
  "docs/sintaxe.md",
339
341
  "docs/cli.md",
340
342
  ];
343
+ function resolverImportadorNodeOpcional(especificador) {
344
+ try {
345
+ return requireRuntimeCli.resolve(especificador);
346
+ }
347
+ catch {
348
+ return undefined;
349
+ }
350
+ }
351
+ const TSX_IMPORTADOR_CLI = resolverImportadorNodeOpcional("tsx");
352
+ const TSX_EXECUTOR_CLI = resolverImportadorNodeOpcional("tsx/cli");
341
353
  function obterArgumentos() {
342
354
  const [, , comando, ...resto] = process.argv;
343
355
  return { comando: comando, resto };
@@ -513,25 +525,172 @@ function comandoDisponivel(comando, argumentos = ["--version"]) {
513
525
  const execucao = spawnSync(comando, argumentos, { stdio: "ignore", shell: process.platform === "win32" });
514
526
  return (execucao.status ?? 1) === 0;
515
527
  }
516
- async function comandoDoctor() {
517
- const checks = [
518
- { nome: "node", ok: comandoDisponivel("node") },
519
- { nome: "npm", ok: comandoDisponivel("npm") },
520
- { nome: "python", ok: comandoDisponivel("python") || comandoDisponivel("py") },
521
- { nome: "dotnet", ok: comandoDisponivel("dotnet") },
522
- { nome: "go", ok: comandoDisponivel("go") },
523
- { nome: "cargo", ok: comandoDisponivel("cargo") },
524
- { nome: "java", ok: comandoDisponivel("java") },
525
- { nome: "code", ok: comandoDisponivel("code", ["--version"]) },
528
+ function resolverExecucaoPython() {
529
+ if (comandoDisponivel("python")) {
530
+ return { comando: "python", argumentosBase: [], rotulo: "python" };
531
+ }
532
+ if (comandoDisponivel("py")) {
533
+ return { comando: "py", argumentosBase: [], rotulo: "py" };
534
+ }
535
+ return undefined;
536
+ }
537
+ function resolverExecucaoPytest() {
538
+ if (comandoDisponivel("pytest")) {
539
+ return { comando: "pytest", argumentosBase: [], rotulo: "pytest" };
540
+ }
541
+ const python = resolverExecucaoPython();
542
+ if (python && comandoDisponivel(python.comando, [...python.argumentosBase, "-m", "pytest", "--version"])) {
543
+ return {
544
+ comando: python.comando,
545
+ argumentosBase: [...python.argumentosBase, "-m", "pytest"],
546
+ rotulo: `${python.rotulo} -m pytest`,
547
+ };
548
+ }
549
+ return undefined;
550
+ }
551
+ function criarItemDependencia(nome, ok, detalhe, obrigatoria = true) {
552
+ return { nome, ok, detalhe, obrigatoria };
553
+ }
554
+ function coletarDependenciasDoctor() {
555
+ const python = resolverExecucaoPython();
556
+ const pytest = resolverExecucaoPytest();
557
+ return [
558
+ {
559
+ comando: "base",
560
+ itens: [
561
+ criarItemDependencia("node", comandoDisponivel("node"), "runtime principal da CLI"),
562
+ criarItemDependencia("npm", comandoDisponivel("npm"), "instalacao, pack e publish"),
563
+ criarItemDependencia("python", Boolean(python), python?.rotulo ? `resolvido via ${python.rotulo}` : "python ou py"),
564
+ criarItemDependencia("dotnet", comandoDisponivel("dotnet"), "ecosistema ASP.NET", false),
565
+ criarItemDependencia("go", comandoDisponivel("go"), "ecosistema Go", false),
566
+ criarItemDependencia("cargo", comandoDisponivel("cargo"), "ecosistema Rust", false),
567
+ criarItemDependencia("java", comandoDisponivel("java"), "ecosistema Java/Spring", false),
568
+ criarItemDependencia("code", comandoDisponivel("code", ["--version"]), "VS Code / extensao", false),
569
+ ],
570
+ },
571
+ {
572
+ comando: "verificar/typescript",
573
+ itens: [
574
+ criarItemDependencia("node", comandoDisponivel("node"), "execucao do runner TypeScript"),
575
+ criarItemDependencia("tsx", Boolean(TSX_EXECUTOR_CLI ?? TSX_IMPORTADOR_CLI), TSX_EXECUTOR_CLI
576
+ ? `runner resolvido em ${TSX_EXECUTOR_CLI}`
577
+ : TSX_IMPORTADOR_CLI
578
+ ? `importador resolvido em ${TSX_IMPORTADOR_CLI}`
579
+ : "tsx nao foi encontrado junto da CLI"),
580
+ ],
581
+ },
582
+ {
583
+ comando: "verificar/javascript",
584
+ itens: [
585
+ criarItemDependencia("node", comandoDisponivel("node"), "node --test"),
586
+ ],
587
+ },
588
+ {
589
+ comando: "verificar/python",
590
+ itens: [
591
+ criarItemDependencia("python", Boolean(python), python?.rotulo ? `resolvido via ${python.rotulo}` : "python ou py"),
592
+ criarItemDependencia("pytest", Boolean(pytest), pytest?.rotulo ? `runner resolvido via ${pytest.rotulo}` : "instale pytest ou exponha python -m pytest"),
593
+ ],
594
+ },
595
+ {
596
+ comando: "verificar/lua",
597
+ itens: [
598
+ criarItemDependencia("lua", comandoDisponivel("lua", ["-v"]), "runner de testes Lua"),
599
+ ],
600
+ },
526
601
  ];
602
+ }
603
+ function avaliarPreflightVerificacao(configuracoesAlvo) {
604
+ const dependencias = [];
605
+ const faltando = [];
606
+ const registrar = (comando, itens) => {
607
+ dependencias.push({ comando, itens });
608
+ for (const item of itens) {
609
+ if (item.obrigatoria && !item.ok) {
610
+ faltando.push({
611
+ comando,
612
+ nome: item.nome,
613
+ detalhe: item.detalhe,
614
+ });
615
+ }
616
+ }
617
+ };
618
+ registrar("base", [
619
+ criarItemDependencia("node", comandoDisponivel("node"), "runtime principal da CLI"),
620
+ criarItemDependencia("npm", comandoDisponivel("npm"), "instalacao e empacotamento"),
621
+ ]);
622
+ for (const configuracao of configuracoesAlvo) {
623
+ if (configuracao.framework !== "base") {
624
+ continue;
625
+ }
626
+ if (configuracao.alvo === "typescript") {
627
+ registrar("verificar/typescript", [
628
+ criarItemDependencia("node", comandoDisponivel("node"), "execucao do runner TypeScript"),
629
+ criarItemDependencia("tsx", Boolean(TSX_EXECUTOR_CLI ?? TSX_IMPORTADOR_CLI), TSX_EXECUTOR_CLI
630
+ ? `runner resolvido em ${TSX_EXECUTOR_CLI}`
631
+ : TSX_IMPORTADOR_CLI
632
+ ? `importador resolvido em ${TSX_IMPORTADOR_CLI}`
633
+ : "tsx nao foi encontrado junto da CLI"),
634
+ ]);
635
+ continue;
636
+ }
637
+ if (configuracao.alvo === "javascript") {
638
+ registrar("verificar/javascript", [
639
+ criarItemDependencia("node", comandoDisponivel("node"), "node --test"),
640
+ ]);
641
+ continue;
642
+ }
643
+ if (configuracao.alvo === "python") {
644
+ const python = resolverExecucaoPython();
645
+ const pytest = resolverExecucaoPytest();
646
+ registrar("verificar/python", [
647
+ criarItemDependencia("python", Boolean(python), python?.rotulo ? `resolvido via ${python.rotulo}` : "python ou py"),
648
+ criarItemDependencia("pytest", Boolean(pytest), pytest?.rotulo ? `runner resolvido via ${pytest.rotulo}` : "instale pytest ou exponha python -m pytest"),
649
+ ]);
650
+ continue;
651
+ }
652
+ if (configuracao.alvo === "lua") {
653
+ registrar("verificar/lua", [
654
+ criarItemDependencia("lua", comandoDisponivel("lua", ["-v"]), "runner de testes Lua"),
655
+ ]);
656
+ }
657
+ }
658
+ return {
659
+ ok: faltando.length === 0,
660
+ dependencias,
661
+ faltando,
662
+ };
663
+ }
664
+ function imprimirDependenciasDoctor(dependencias) {
665
+ for (const dependencia of dependencias) {
666
+ console.log(`\n[${dependencia.comando}]`);
667
+ for (const item of dependencia.itens) {
668
+ const sufixo = item.detalhe ? ` (${item.detalhe})` : "";
669
+ console.log(`- ${item.nome}: ${item.ok ? "ok" : item.obrigatoria ? "faltando" : "ausente"}${sufixo}`);
670
+ }
671
+ }
672
+ }
673
+ function imprimirPreflightVerificacao(preflight) {
674
+ console.error(renderizarCaixaAscii([
675
+ "Preflight de verificacao falhou",
676
+ "a CLI sabe o que falta antes de gerar e testar",
677
+ ]));
678
+ for (const item of preflight.faltando) {
679
+ const detalhe = item.detalhe ? ` (${item.detalhe})` : "";
680
+ console.error(`- ${item.comando}: ${item.nome} faltando${detalhe}`);
681
+ }
682
+ console.error("Rode `sema doctor` para ver as dependencias por comando.");
683
+ }
684
+ async function comandoDoctor() {
685
+ const dependencias = coletarDependenciasDoctor();
527
686
  console.log(renderizarCaixaAscii([
528
687
  "Sema doctor",
529
- "checa a toolchain minima para validar, gerar e operar a CLI",
688
+ "checa a toolchain minima e as dependencias reais por comando",
530
689
  ]));
531
- for (const check of checks) {
532
- console.log(`- ${check.nome}: ${check.ok ? "ok" : "ausente"}`);
533
- }
534
- const obrigatorios = checks.filter((check) => ["node", "npm"].includes(check.nome));
690
+ imprimirDependenciasDoctor(dependencias);
691
+ const obrigatorios = dependencias
692
+ .find((dependencia) => dependencia.comando === "base")
693
+ ?.itens.filter((item) => item.obrigatoria) ?? [];
535
694
  return obrigatorios.every((check) => check.ok) ? 0 : 1;
536
695
  }
537
696
  function validarCompatibilidadeFramework(alvo, framework) {
@@ -770,8 +929,17 @@ function executarTestesGerados(alvo, baseSaida, arquivos, silencioso = false) {
770
929
  }
771
930
  return { codigoSaida: 0, quantidadeTestes, saidaPadrao: "", saidaErro: "" };
772
931
  }
773
- const execucao = spawnSync("node", ["--import", "tsx", path.join(baseSaida, arquivoTeste)], {
932
+ if (!TSX_EXECUTOR_CLI) {
933
+ return {
934
+ codigoSaida: 1,
935
+ quantidadeTestes,
936
+ saidaPadrao: "",
937
+ saidaErro: "Nao foi possivel localizar o runner tsx junto da CLI para executar testes TypeScript.",
938
+ };
939
+ }
940
+ const execucao = spawnSync("node", [TSX_EXECUTOR_CLI, arquivoTeste], {
774
941
  stdio: silencioso ? "pipe" : "inherit",
942
+ cwd: baseSaida,
775
943
  encoding: silencioso ? "utf8" : undefined,
776
944
  });
777
945
  return {
@@ -808,6 +976,14 @@ function executarTestesGerados(alvo, baseSaida, arquivos, silencioso = false) {
808
976
  }
809
977
  return { codigoSaida: 0, quantidadeTestes, saidaPadrao: "", saidaErro: "" };
810
978
  }
979
+ if (!comandoDisponivel("lua", ["-v"])) {
980
+ return {
981
+ codigoSaida: 1,
982
+ quantidadeTestes,
983
+ saidaPadrao: "",
984
+ saidaErro: "Nao foi possivel localizar o runner lua para executar os testes gerados.",
985
+ };
986
+ }
811
987
  const execucao = spawnSync("lua", [arquivoTeste], {
812
988
  stdio: silencioso ? "pipe" : "inherit",
813
989
  cwd: baseSaida,
@@ -828,10 +1004,20 @@ function executarTestesGerados(alvo, baseSaida, arquivos, silencioso = false) {
828
1004
  }
829
1005
  return { codigoSaida: 0, quantidadeTestes, saidaPadrao: "", saidaErro: "" };
830
1006
  }
831
- const execucao = spawnSync("pytest", [arquivoTeste], {
1007
+ const pytest = resolverExecucaoPytest();
1008
+ if (!pytest) {
1009
+ return {
1010
+ codigoSaida: 1,
1011
+ quantidadeTestes,
1012
+ saidaPadrao: "",
1013
+ saidaErro: "Nao foi possivel localizar pytest. Instale pytest ou exponha python -m pytest.",
1014
+ };
1015
+ }
1016
+ const execucao = spawnSync(pytest.comando, [...pytest.argumentosBase, arquivoTeste], {
832
1017
  stdio: silencioso ? "pipe" : "inherit",
833
1018
  cwd: baseSaida,
834
1019
  encoding: silencioso ? "utf8" : undefined,
1020
+ shell: process.platform === "win32" && pytest.comando === "pytest",
835
1021
  });
836
1022
  return {
837
1023
  codigoSaida: execucao.status ?? 1,
@@ -3125,7 +3311,7 @@ async function comandoDrift(entrada, args, emJson, cwd = process.cwd()) {
3125
3311
  if (persistenciaDivergente.length > 0) {
3126
3312
  console.log("- Persistencia que pede revisao:");
3127
3313
  for (const item of persistenciaDivergente.slice(0, 8)) {
3128
- console.log(` - ${item.modulo}.${item.task} :: ${item.alvo} :: ${item.status} :: compat=${item.compatibilidade}`);
3314
+ console.log(` - ${item.modulo}.${item.task} :: ${item.alvo} :: ${item.status} :: categoria=${item.categoriaPersistencia} :: compat=${item.compatibilidade}`);
3129
3315
  }
3130
3316
  }
3131
3317
  const semImpl = resultado.tasks.filter((task) => task.semImplementacao);
@@ -3757,6 +3943,11 @@ async function comandoVerificar(entrada, baseSaida, cwd = process.cwd()) {
3757
3943
  console.error(incompatibilidade.incompatibilidade);
3758
3944
  return 1;
3759
3945
  }
3946
+ const preflight = avaliarPreflightVerificacao(configuracoesAlvo);
3947
+ if (!preflight.ok) {
3948
+ imprimirPreflightVerificacao(preflight);
3949
+ return 1;
3950
+ }
3760
3951
  const resumos = [];
3761
3952
  for (const modulo of modulos) {
3762
3953
  const ir = garantirIr(modulo.resultado, modulo.caminho);
@@ -3824,6 +4015,18 @@ async function comandoVerificarJson(entrada, baseSaida, cwd = process.cwd()) {
3824
4015
  }, null, 2));
3825
4016
  return 1;
3826
4017
  }
4018
+ const preflight = avaliarPreflightVerificacao(configuracoesAlvo);
4019
+ if (!preflight.ok) {
4020
+ console.log(JSON.stringify({
4021
+ comando: "verificar",
4022
+ sucesso: false,
4023
+ erro: "Dependencias obrigatorias ausentes para executar a verificacao.",
4024
+ preflight,
4025
+ modulos: [],
4026
+ totais: { modulos: 0, alvos: 0, arquivos: 0, testes: 0 },
4027
+ }, null, 2));
4028
+ return 1;
4029
+ }
3827
4030
  const resumos = [];
3828
4031
  let codigoSaida = 0;
3829
4032
  for (const modulo of modulos) {