synthesisui 0.16.189 → 0.16.191

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
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
3
4
  import { absorb } from "./commands/absorb.js";
4
5
  import { add } from "./commands/add.js";
5
6
  import { adopt } from "./commands/adopt.js";
@@ -26,6 +27,7 @@ import { sync } from "./commands/sync.js";
26
27
  import { template } from "./commands/template.js";
27
28
  import { upgrade } from "./commands/upgrade.js";
28
29
  import { use } from "./commands/use.js";
30
+ import { appendEvent } from "./doctor/ledger.js";
29
31
  import { RegistryError } from "./registry.js";
30
32
  /** Our own version, for pinning the hook and MCP commands we write into a
31
33
  * project. Read from the package we are running out of, so a pinned command
@@ -602,7 +604,135 @@ async function main() {
602
604
  process.exitCode = 1;
603
605
  }
604
606
  }
605
- main().catch((err) => {
607
+ /**
608
+ * TODA EXECUÇÃO DEIXA UMA LINHA - e ela é escrita AQUI, nunca dentro de um comando.
609
+ *
610
+ * Metade dos defeitos de 09/08 era comportamento da ferramenta: um `upgrade` que rematerializava sem
611
+ * medir, uma barra que entupia o clipboard, um aviso de versão invertido. Nenhum virava número em
612
+ * lugar nenhum, e os três foram achados pelo dono lendo a saída no terminal. A tela de causas era
613
+ * sólida sobre o que a gente PRODUZ e cega sobre o que a ferramenta FAZ.
614
+ *
615
+ * NO PONTO DE ENTRADA, por uma razão que já custou caro duas vezes hoje: um comando pode esquecer de
616
+ * se registrar, e um caminho novo dentro dele nasce esquecido. A porta não esquece.
617
+ *
618
+ * E NO `finally`, porque a execução que mais importa registrar é a que NÃO terminou. Uma queda que
619
+ * não deixa rastro é exatamente "perder uma entrada".
620
+ *
621
+ * O registro nunca derruba o comando nem muda o código de saída: ele é a nossa ferramenta de
622
+ * diagnóstico, e perder o trabalho de alguém para gravar uma linha dela seria a pior troca da esteira.
623
+ */
624
+ /**
625
+ * A MENSAGEM SEM O DISCO DELE - o que este registro pode carregar, e o que ele nunca pode.
626
+ *
627
+ * O spec que executa o CLI pegou o vazamento na primeira rodada: a mensagem de erro trazia o
628
+ * argumento digitado, e mensagens de Node trazem caminho absoluto o tempo todo (`ENOENT: … open
629
+ * '/home/alguem/projeto/src/App.tsx'`). Um registro que carrega o caminho do projeto de alguém é um
630
+ * registro que ninguém deixa ligado - e aí a esteira perde o sinal inteiro, não só a linha.
631
+ *
632
+ * O que sobra é a FORMA do erro, que é o que dá para consertar a partir daqui. Caminho vira `<path>`;
633
+ * o resto passa.
634
+ */
635
+ function scrub(message) {
636
+ return message
637
+ .replace(/[A-Za-z]:\\[^\s'"`)]+/g, "<path>")
638
+ .replace(/(?:\.{0,2}\/)[^\s'"`)]{2,}/g, "<path>")
639
+ .trim();
640
+ }
641
+ async function recordRun(command, started, ok, error, bytes) {
642
+ try {
643
+ const root = resolve(typeof parseFlags(process.argv.slice(2)).flags.dir === "string"
644
+ ? parseFlags(process.argv.slice(2)).flags.dir
645
+ : process.cwd());
646
+ /**
647
+ * ONDE, DENTRO DO NOSSO CÓDIGO, e é o que separa uma queda consertável de uma inútil.
648
+ *
649
+ * `Cannot read properties of undefined` sem lugar nenhum não se conserta daqui - a única saída
650
+ * seria pedir para a pessoa reproduzir, que é exatamente o que não pode acontecer (dono, 09/08:
651
+ * *"não quero pedir para o usuário rodar alguma coisa porque a gente não pegou"*).
652
+ *
653
+ * O primeiro quadro da pilha que aponta para o NOSSO pacote resolve isso, e ele não é dado dele:
654
+ * `index.js:1234` é o nosso bundle. O diretório - que é o cache de npx na máquina dele - fica de
655
+ * fora, e um quadro que não seja nosso é descartado em vez de viajar.
656
+ */
657
+ const frame = (() => {
658
+ const stack = error instanceof Error ? (error.stack ?? "") : "";
659
+ for (const line of stack.split("\n").slice(1)) {
660
+ const at = /\(?([^()\s]+\.js):(\d+):(\d+)\)?\s*$/.exec(line.trim());
661
+ if (!at)
662
+ continue;
663
+ const file = at[1].split("/").pop() ?? "";
664
+ /** Só o nosso bundle e os nossos módulos: um quadro do Node ou de outro pacote não é nosso. */
665
+ if (!file || /node:/.test(at[1]))
666
+ continue;
667
+ if (!at[1].includes("synthesisui") && !at[1].endsWith("/dist/index.js"))
668
+ continue;
669
+ return `${file}:${at[2]}`;
670
+ }
671
+ return "";
672
+ })();
673
+ const message = scrub(error instanceof Error ? error.message : error ? String(error) : "");
674
+ await appendEvent(root, {
675
+ kind: "run",
676
+ at: new Date().toISOString(),
677
+ command,
678
+ ok,
679
+ ms: Date.now() - started,
680
+ /** Só a primeira linha, cortada: uma stack carrega caminhos do projeto de alguém. */
681
+ ...(message ? { error: message.split("\n")[0].slice(0, 200) } : {}),
682
+ ...(typeof bytes === "number" && bytes > 0 ? { bytes } : {}),
683
+ ...(frame ? { where: frame } : {}),
684
+ });
685
+ }
686
+ catch {
687
+ /** Sem ledger, sem `_synthesisui`, disco cheio: nada disso é um comando que falhou. */
688
+ }
689
+ }
690
+ /**
691
+ * QUANTO O COMANDO ESCREVEU NA TELA - o segundo defeito de 09/08, e ele É medível.
692
+ *
693
+ * A barra de progresso se redesenha com `\r`: na tela é uma linha, no clipboard são todos os quadros.
694
+ * O dono tentou copiar a saída de um `sync` para mostrar um problema e levou junto 2 172 quadros -
695
+ * 195 879 bytes -, e o relatório que ele queria ficou enterrado. Aquilo foi achado por ele tentando
696
+ * copiar; não havia número em lugar nenhum.
697
+ *
698
+ * Contar bytes é a coisa mais simples que pega essa família inteira: uma saída de 200 KB não é uma
699
+ * saída, é um despejo, e a diferença entre 12 KB e 200 KB não depende de ninguém perceber.
700
+ *
701
+ * O contador NÃO altera a saída - ele soma e delega. Um wrapper que muda o que é impresso seria uma
702
+ * ferramenta mentindo sobre si mesma no exato lugar onde a gente foi conferir a verdade.
703
+ */
704
+ function countingStdout() {
705
+ const original = process.stdout.write.bind(process.stdout);
706
+ let bytes = 0;
707
+ process.stdout.write = ((chunk, ...rest) => {
708
+ bytes +=
709
+ typeof chunk === "string"
710
+ ? Buffer.byteLength(chunk)
711
+ : Buffer.isBuffer(chunk)
712
+ ? chunk.length
713
+ : 0;
714
+ return original(chunk, ...rest);
715
+ });
716
+ return () => bytes;
717
+ }
718
+ async function guarded() {
719
+ const started = Date.now();
720
+ const command = parseFlags(process.argv.slice(2)).positionals[0] ?? "";
721
+ /** `help` e a invocação vazia não são trabalho sobre um projeto, e registrá-las seria só ruído. */
722
+ const worth = command && command !== "help";
723
+ const written = worth ? countingStdout() : () => 0;
724
+ try {
725
+ await main();
726
+ if (worth)
727
+ await recordRun(command, started, process.exitCode !== 1, undefined, written());
728
+ }
729
+ catch (err) {
730
+ if (worth)
731
+ await recordRun(command, started, false, err, written());
732
+ throw err;
733
+ }
734
+ }
735
+ guarded().catch((err) => {
606
736
  if (err instanceof RegistryError) {
607
737
  console.error(`error: ${err.message}`);
608
738
  }
@@ -39,7 +39,25 @@ export async function repoStateOf(projectRoot, slug, cli) {
39
39
  * própria aqui seria uma segunda implementação do formato do registro - e a primeira coisa que
40
40
  * ela faria é discordar da outra sobre o que é uma linha válida.
41
41
  */
42
- const doctor = (await readEvents(projectRoot))
42
+ const events = await readEvents(projectRoot);
43
+ /**
44
+ * AS ÚLTIMAS 20, e a janela é deliberada: o que interessa é a sessão de agora, não o histórico.
45
+ * Uma pessoa que roda vinte comandos sem sincronizar está no meio de um problema, e é dessa vintena
46
+ * que sai a resposta.
47
+ */
48
+ const runs = events
49
+ .filter((e) => e.kind === "run" && e.command && e.at)
50
+ .slice(-20)
51
+ .map((e) => ({
52
+ command: String(e.command),
53
+ at: String(e.at),
54
+ ...(e.ok === false ? { ok: false } : {}),
55
+ ...(typeof e.ms === "number" ? { ms: Math.round(e.ms) } : {}),
56
+ ...(e.error ? { error: String(e.error).slice(0, 200) } : {}),
57
+ ...(typeof e.bytes === "number" ? { bytes: Math.round(e.bytes) } : {}),
58
+ ...(e.where ? { where: String(e.where).slice(0, 60) } : {}),
59
+ }));
60
+ const doctor = events
43
61
  .filter((e) => e.kind === "doctor" && e.at)
44
62
  .sort((a, b) => String(b.at).localeCompare(String(a.at)))[0];
45
63
  const payload = {
@@ -56,6 +74,7 @@ export async function repoStateOf(projectRoot, slug, cli) {
56
74
  ...(doctor?.matched && doctor.matched.length > 0
57
75
  ? { matched: doctor.matched }
58
76
  : {}),
77
+ ...(runs.length > 0 ? { runs } : {}),
59
78
  ...(typeof lock?.fetchedAt === "string"
60
79
  ? { installedAt: lock.fetchedAt }
61
80
  : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.189",
3
+ "version": "0.16.191",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {