synthesisui 0.16.411 → 0.16.412
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/commands/component.js +100 -6
- package/dist/commands/connect.js +20 -0
- package/dist/commands/generate.js +18 -1
- package/dist/commands/refit.js +21 -1
- package/dist/commands/sync.js +18 -0
- package/dist/commands/template.js +51 -3
- package/dist/commands/upgrade.js +64 -8
- package/dist/index.js +28 -1
- package/dist/install-marks.js +18 -1
- package/dist/is-a-project.js +78 -0
- package/dist/their-tongue.js +27 -0
- package/dist/wiring-read.js +22 -0
- package/dist/written.js +27 -5
- package/package.json +1 -1
|
@@ -8,10 +8,11 @@ import { detectAppDirs } from "../global-sheet.js";
|
|
|
8
8
|
import { hasInteractiveTemplate, interactiveTemplate, } from "../interactive-templates.js";
|
|
9
9
|
import { body, section, snippet } from "../output.js";
|
|
10
10
|
import { findCollision, installedThemeVars, reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
|
|
11
|
-
import { fetchComponent, RegistryError } from "../registry.js";
|
|
11
|
+
import { fetchComponent, fetchDesignSystem, RegistryError, } from "../registry.js";
|
|
12
12
|
import { installedThemeCss, whatOnlyTheSheetResolves, } from "../sheet-needed.js";
|
|
13
13
|
import { flavourResolver } from "../styles-flavour.js";
|
|
14
|
-
import { inTheirTongue, projectTongue, sumSpoken, } from "../their-tongue.js";
|
|
14
|
+
import { inTheirTongue, projectTongue, sumSpoken, tongueFromArtifacts, } from "../their-tongue.js";
|
|
15
|
+
import { resolvableVars } from "../their-vars.js";
|
|
15
16
|
import { readCensus, unreadComment, unreadForComponent, } from "../unread-for-component.js";
|
|
16
17
|
import { readWiring } from "../wiring-read.js";
|
|
17
18
|
import { editedSinceWritten, readWritten, recordWritten } from "../written.js";
|
|
@@ -114,7 +115,38 @@ export async function component(slug, name, opts) {
|
|
|
114
115
|
* apagaria a cor. Um projeto de destino chega sem mapa no `.lock`, nada é traduzido, e a folha
|
|
115
116
|
* continua sendo o caminho - o comando DIZ qual dos dois aconteceu.
|
|
116
117
|
*/
|
|
117
|
-
const
|
|
118
|
+
const installedTongue = await projectTongue(root, slug);
|
|
119
|
+
/**
|
|
120
|
+
* E SEM O `add`, O SISTEMA VEM DO REGISTRY E O VOCABULARIO E' DERIVADO EM MEMORIA.
|
|
121
|
+
*
|
|
122
|
+
* O QUE ACONTECIA (medido em 10/09): sem `.lock`, `projectTongue` responde `null`, nada era
|
|
123
|
+
* traduzido, e o arquivo saia com `var(--ds-*)` cru - inutil ate' que a pessoa rodasse `add` e
|
|
124
|
+
* materializasse 1813 linhas, 111 variaveis e 628 referencias no repositorio dela. Pedir UM
|
|
125
|
+
* botao passava por instalar o sistema inteiro.
|
|
126
|
+
*
|
|
127
|
+
* A NOSSA FOLHA E' A REFERENCIA, NAO O PRE-REQUISITO (a definicao do produto, 07/09): ela existe
|
|
128
|
+
* para o agente dele ler e construir sobre ela. A medicao que ela carregava - quais das nossas
|
|
129
|
+
* variaveis tem nome no codigo dele - nao precisa de disco: e' a mesma varredura que o `add`
|
|
130
|
+
* faz, feita aqui e jogada fora depois de traduzir.
|
|
131
|
+
*
|
|
132
|
+
* UMA IDA A MAIS A' REDE, e so' neste caminho: quem ja' instalou o sistema le' do `.lock` como
|
|
133
|
+
* sempre. O `catch` mantem o comportamento antigo - sem tradução, e o bloco de setup abaixo diz
|
|
134
|
+
* que a folha e' o caminho.
|
|
135
|
+
*/
|
|
136
|
+
const fromRegistry = installedTongue
|
|
137
|
+
? null
|
|
138
|
+
: await fetchDesignSystem(base, slug, opts.version).catch(() => null);
|
|
139
|
+
/** A leitura do sistema NAO chegou - rede fora, ou sistema privado sem sessao. Ver o relato. */
|
|
140
|
+
const installedSheetMissing = !installedTongue && fromRegistry === null;
|
|
141
|
+
/**
|
|
142
|
+
* A NOSSA FOLHA ESTA' NA PASTA DELE? - o `.lock` responde, e e' o mesmo arquivo que o `add`
|
|
143
|
+
* escreve. Desde o A1 esta pergunta deixou de ser retorica: `component` roda sem ele.
|
|
144
|
+
*/
|
|
145
|
+
const installedHere = await readFile(join(root, "_synthesisui", "ds", slug, ".lock"), "utf8").then(() => true, () => false);
|
|
146
|
+
const tongue = installedTongue ??
|
|
147
|
+
(fromRegistry
|
|
148
|
+
? await tongueFromArtifacts(root, fromRegistry.artifacts)
|
|
149
|
+
: null);
|
|
118
150
|
/**
|
|
119
151
|
* O RELATÓRIO SÓ EXISTE DEPOIS DE OS BYTES EXISTIREM - e é essa ordem que corrige o defeito.
|
|
120
152
|
*
|
|
@@ -422,6 +454,45 @@ export async function component(slug, name, opts) {
|
|
|
422
454
|
console.log(spoken.left.length === 0
|
|
423
455
|
? ` No variable in what was just written points at our stylesheet - they are all names YOUR code declares.`
|
|
424
456
|
: ` ${spoken.left.length} still point${spoken.left.length === 1 ? "s" : ""} at our stylesheet (${spoken.left.slice(0, 3).join(", ")}${spoken.left.length > 3 ? ", …" : ""}), so tokens.css carries ${spoken.left.length === 1 ? "it" : "those"}.`);
|
|
457
|
+
/**
|
|
458
|
+
* E DIZER DE ONDE VEIO O VOCABULARIO, quando o sistema NAO esta' instalado aqui.
|
|
459
|
+
*
|
|
460
|
+
* Sem esta linha o resultado seria magico: o arquivo fala a lingua dele e nada explica como.
|
|
461
|
+
* A frase importa porque a conclusao natural - *"entao o `add` ja' rodou"* - esta' errada, e a
|
|
462
|
+
* pessoa precisa saber que o que ela tem e' o componente, e que a folha continua sendo uma
|
|
463
|
+
* escolha e nao uma pendencia.
|
|
464
|
+
*/
|
|
465
|
+
if (fromRegistry)
|
|
466
|
+
console.log(` Nothing of ours was installed to do that - the system was read at v${fromRegistry.version}, and the names came from your own code.`);
|
|
467
|
+
}
|
|
468
|
+
else if (!tongue) {
|
|
469
|
+
/**
|
|
470
|
+
* A LACUNA DECLARADA (lei 8): nada foi traduzido, e ISSO SE DIZ.
|
|
471
|
+
*
|
|
472
|
+
* O QUE ACONTECIA SEM ESTA LINHA: o `.tsx` saia com `var(--ds-*)` cru e o terminal ficava
|
|
473
|
+
* mudo. A pessoa abre o arquivo, ve' variaveis que o `globals.css` dela nao declara, e a
|
|
474
|
+
* unica forma de descobrir por que a cor nao apareceu e' ir ler o nosso codigo.
|
|
475
|
+
*
|
|
476
|
+
* E OS DOIS MOTIVOS SAO DIFERENTES, entao a frase distingue: o repositorio dela nao nomeia
|
|
477
|
+
* nenhum destes valores (o caso comum, e nao ha' o que fazer), ou a leitura do sistema nao
|
|
478
|
+
* chegou - rede fora, sistema privado sem sessao. O segundo tem conserto e o primeiro nao.
|
|
479
|
+
*/
|
|
480
|
+
/**
|
|
481
|
+
* E AS TRES CAUSAS SAO DIFERENTES, entao a frase distingue - so' uma delas tem conserto na
|
|
482
|
+
* mao dele, e ela e' a mais provavel das tres.
|
|
483
|
+
*
|
|
484
|
+
* a leitura nao chegou rede fora, sessao ausente, sistema privado
|
|
485
|
+
* o build dele nao existe `resolvableVars` recusa todo par que o build nao emite, por
|
|
486
|
+
* desenho: um nome afirmado sem prova pinta a cor errada no dia
|
|
487
|
+
* em que ele o renomear. Num clone fresco isso e' o caso normal
|
|
488
|
+
* o codigo dele nao nomeia nada a unica que o silencio de fato descrevia
|
|
489
|
+
*/
|
|
490
|
+
const noBuild = !installedSheetMissing && (await resolvableVars(root)) === null;
|
|
491
|
+
console.log(installedSheetMissing
|
|
492
|
+
? ` Nothing was translated: "${slug}" could not be read from the registry just now, so the variables stayed as ours. Check your connection, or run: npx synthesisui login`
|
|
493
|
+
: noBuild
|
|
494
|
+
? ` Nothing was translated: this project has no build output to read, and a name we cannot see your build emit is a name we will not write. Run your build once and ask for it again - the file then speaks your own names.`
|
|
495
|
+
: ` Nothing was translated: your code names none of the values this system declares yet, so the variables stayed as ours and tokens.css is what resolves them.`);
|
|
425
496
|
}
|
|
426
497
|
// ── DX: concrete paths + copy-pasteable snippets, with breathing room ──
|
|
427
498
|
const tailwind = flavour === "tailwind";
|
|
@@ -454,7 +525,15 @@ export async function component(slug, name, opts) {
|
|
|
454
525
|
*/
|
|
455
526
|
const need = whatOnlyTheSheetResolves({
|
|
456
527
|
source: writtenSource,
|
|
457
|
-
|
|
528
|
+
/**
|
|
529
|
+
* O `@theme` DO SISTEMA - do disco quando ele instalou, do registry quando nao.
|
|
530
|
+
*
|
|
531
|
+
* A pergunta que este bloco faz e' *"sobrou classe que so' o nosso @theme gera?"*. Sem o
|
|
532
|
+
* `add`, `installedThemeCss` responde vazio, e vazio leria como "nao sobrou nada" - uma
|
|
533
|
+
* resposta certa por acidente que ficaria errada no dia em que sobrasse.
|
|
534
|
+
*/
|
|
535
|
+
themeCss: fromRegistry?.artifacts?.["theme.css"] ??
|
|
536
|
+
(await installedThemeCss(root, slug)),
|
|
458
537
|
/** O vocabulário DELE sai da conta - ver `theirNames`. O mapa do `.lock` é a via mais barata. */
|
|
459
538
|
theirNames: tongue?.names ? [...tongue.names.values()] : [],
|
|
460
539
|
});
|
|
@@ -517,6 +596,19 @@ export async function component(slug, name, opts) {
|
|
|
517
596
|
}
|
|
518
597
|
if (need.needed && !alreadyWired) {
|
|
519
598
|
console.log(section(`One-time setup (once per app, for "${slug}")`));
|
|
599
|
+
/**
|
|
600
|
+
* O PASSO ZERO VEM PRIMEIRO QUANDO A FOLHA NAO EXISTE - e desde o A1 este e' o caso comum.
|
|
601
|
+
*
|
|
602
|
+
* O QUE ACONTECIA: o passo 1 mandava colar `@import ".../tokens.css"` e o aviso de que o
|
|
603
|
+
* sistema nao esta' instalado saia na ULTIMA linha, entre parenteses. Quem nunca rodou `add`
|
|
604
|
+
* - agora um caminho normal, porque o `component` deixou de exigi-lo - lia a instrucao de
|
|
605
|
+
* cima para baixo e importava um caminho que nao existe. O erro do build nao fala de
|
|
606
|
+
* install: fala de um arquivo ausente.
|
|
607
|
+
*
|
|
608
|
+
* O motivo antes da instrucao, e nao depois dela.
|
|
609
|
+
*/
|
|
610
|
+
if (!installedHere)
|
|
611
|
+
console.log(body(`First: "${slug}" is not installed in this project yet, so the file the import below points at does not exist. Run \`npx synthesisui add ${slug}\` before pasting it.`));
|
|
520
612
|
console.log(body(need.variables.length > 0
|
|
521
613
|
? `(what still needs the sheet here: ${need.variables.slice(0, 3).join(", ")}${need.variables.length > 3 ? `, +${need.variables.length - 3}` : ""} - your code names no value for ${need.variables.length === 1 ? "it" : "them"})`
|
|
522
614
|
: `(what still needs the sheet here: the ${need.classes.slice(0, 3).join(", ")}${need.classes.length > 3 ? `, +${need.classes.length - 3}` : ""} ${need.classes.length === 1 ? "utility" : "utilities"}, which only this system's @theme generates)`));
|
|
@@ -529,8 +621,10 @@ export async function component(slug, name, opts) {
|
|
|
529
621
|
console.log(body(`2. Scope your app: add data-ds="${slug}" to a ROOT element, e.g. app/layout.tsx:`));
|
|
530
622
|
console.log("");
|
|
531
623
|
console.log(snippet([`<body data-ds="${slug}">{children}</body>`]));
|
|
532
|
-
|
|
533
|
-
|
|
624
|
+
if (installedHere) {
|
|
625
|
+
console.log("");
|
|
626
|
+
console.log(body(`(If you haven't installed the system yet, run: synthesisui add ${slug})`));
|
|
627
|
+
}
|
|
534
628
|
}
|
|
535
629
|
/**
|
|
536
630
|
* O ELEMENTO QUE SAIU NÃO ATIVA O QUE A RECEITA PEDE - e isso se diz, em vez de sumir (lei 8).
|
package/dist/commands/connect.js
CHANGED
|
@@ -3,6 +3,7 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { codexPinBefore, wireAgent } from "../agent-wiring.js";
|
|
4
4
|
import { blockHomes, syncClaudeMd } from "../claude-md.js";
|
|
5
5
|
import { resolveRegistry } from "../config.js";
|
|
6
|
+
import { notAProject, projectRootFrom } from "../is-a-project.js";
|
|
6
7
|
import { fmt, say } from "../lang.js";
|
|
7
8
|
import { body, bodyWrapped, paint, section, snippet } from "../output.js";
|
|
8
9
|
import { readShellAnswer, rememberShellNo } from "../shell-answer.js";
|
|
@@ -212,6 +213,25 @@ version) {
|
|
|
212
213
|
}
|
|
213
214
|
export async function connect(opts) {
|
|
214
215
|
const root = opts.dir ?? process.cwd();
|
|
216
|
+
/**
|
|
217
|
+
* ANTES DA PRIMEIRA ESCRITA, SEMPRE - ver `is-a-project.ts`.
|
|
218
|
+
*
|
|
219
|
+
* MEDIDO EM 10/09: este comando tinha 6 escritas e 0 checagens de projeto. Rodado por engano
|
|
220
|
+
* na home ou numa pasta recem-criada, ele deixava `CLAUDE.md`, `.claude/settings.json`,
|
|
221
|
+
* `.mcp.json` e a casa da governanca ali - e um `CLAUDE.md` orfao instrui todo agente que
|
|
222
|
+
* abrir naquela pasta, semanas depois, sem ninguem lembrar de onde ele veio.
|
|
223
|
+
*
|
|
224
|
+
* AQUI E NAO DENTRO DE CADA ESCRITA: recusar na porta e' a unica forma de a promessa
|
|
225
|
+
* *"nada foi escrito"* ser verdade. Meia recusa deixaria dois arquivos e nenhuma mensagem.
|
|
226
|
+
*/
|
|
227
|
+
const project = await projectRootFrom(root);
|
|
228
|
+
if (!project) {
|
|
229
|
+
console.log(section("Not a project"));
|
|
230
|
+
for (const line of notAProject("connect", root))
|
|
231
|
+
console.log(body(line));
|
|
232
|
+
process.exitCode = 1;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
215
235
|
// Both unless one is explicitly turned off - somebody who says `--no-hook`
|
|
216
236
|
// means it, and somebody who says nothing wants the thing to work.
|
|
217
237
|
const want = { hook: opts.hook !== false, mcp: opts.mcp !== false };
|
|
@@ -7,7 +7,7 @@ import { installedThemeVars, reactMajorOf, readInstalledConvention, readInstalle
|
|
|
7
7
|
import { postGenerate, RegistryError } from "../registry.js";
|
|
8
8
|
import { flavourResolver } from "../styles-flavour.js";
|
|
9
9
|
import { projectTongue } from "../their-tongue.js";
|
|
10
|
-
import { recordWritten } from "../written.js";
|
|
10
|
+
import { keptLine, recordWritten, editedHere as theirEdits, } from "../written.js";
|
|
11
11
|
/** PascalCase para o hint de import (course-card → CourseCard). */
|
|
12
12
|
function pascalName(name) {
|
|
13
13
|
return name.replace(/(^|[-_])([a-z0-9])/g, (_, __, c) => c.toUpperCase());
|
|
@@ -76,6 +76,23 @@ export async function generate(description, opts) {
|
|
|
76
76
|
await readInstalledConvention(root, slug), res.name, await readInstalledScheme(root, slug),
|
|
77
77
|
/** O VOCABULÁRIO DELE - um componente gerado cai no mesmo projeto e fala a mesma língua. */
|
|
78
78
|
await projectTongue(root, slug), await installedThemeVars(root, slug));
|
|
79
|
+
/**
|
|
80
|
+
* A EDICAO DELE VENCE A REESCRITA (T7) - a leitura que faltava.
|
|
81
|
+
*
|
|
82
|
+
* MEDIDO EM 10/09: este comando GRAVAVA o fingerprint e nunca o lia. A promessa existia pela
|
|
83
|
+
* metade - o `upgrade` respeitava o arquivo dele, e o comando que o escreveu na primeira vez
|
|
84
|
+
* passava por cima na segunda. Um pedido parecido devolve o mesmo nome, e o ajuste dele some.
|
|
85
|
+
*
|
|
86
|
+
* O QUE ELE NAO PERDE AO SER RECUSADO: a receita e o CSS ja' estao no disco - so' o `.tsx`
|
|
87
|
+
* nao foi escrito. E a saida e' a mesma que o `component` oferece.
|
|
88
|
+
*/
|
|
89
|
+
const edited = await theirEdits(join(root, "_synthesisui", "ds", slug), res.name, compDir);
|
|
90
|
+
if (edited && edited.length > 0 && !opts.force) {
|
|
91
|
+
console.log("");
|
|
92
|
+
console.log(keptLine(res.name, edited, `npx synthesisui generate "${description}" --name ${res.name} --force`));
|
|
93
|
+
console.log(` The recipe and the compiled CSS are on disk either way - only the .tsx was not written.`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
79
96
|
for (const file of files) {
|
|
80
97
|
await writeFile(join(compDir, file.filename), file.code, "utf8");
|
|
81
98
|
}
|
package/dist/commands/refit.js
CHANGED
|
@@ -8,7 +8,7 @@ import { installedThemeVars, reactMajorOf, readInstalledConvention, readInstalle
|
|
|
8
8
|
import { fetchComponent, postRefit, postSaveComponent, RegistryError, } from "../registry.js";
|
|
9
9
|
import { flavourResolver } from "../styles-flavour.js";
|
|
10
10
|
import { projectTongue } from "../their-tongue.js";
|
|
11
|
-
import { recordWritten } from "../written.js";
|
|
11
|
+
import { keptLine, recordWritten, editedHere as theirEdits, } from "../written.js";
|
|
12
12
|
/** Slugs INSTALLED under `_synthesisui/ds/` (a `.lock` marks a real install -
|
|
13
13
|
* a folder holding only refit artifacts doesn't count). */
|
|
14
14
|
/** True when the system is actually installed (tokens.css present). */
|
|
@@ -122,6 +122,26 @@ export async function refit(file, opts) {
|
|
|
122
122
|
const { files } = generateComponentFiles(slug, res.name, res.recipe, res.css, saved.version, flavourOf(res.name), await reactMajorOf(root), await readInstalledConvention(root, slug), res.name, await readInstalledScheme(root, slug),
|
|
123
123
|
/** O VOCABULÁRIO DELE - o `refit` reescreve o componente e não passava pela porta. */
|
|
124
124
|
await projectTongue(root, slug), await installedThemeVars(root, slug));
|
|
125
|
+
/**
|
|
126
|
+
* A EDICAO DELE VENCE A REESCRITA (T7) - a leitura que faltava, a mesma do `generate`.
|
|
127
|
+
*
|
|
128
|
+
* Este comando REESCREVE um componente que a pessoa ja' tem, que e' exatamente a situacao em
|
|
129
|
+
* que o arquivo dela mais provavelmente foi tocado. Ele gravava o fingerprint e nunca o lia.
|
|
130
|
+
*/
|
|
131
|
+
const edited = await theirEdits(join(root, "_synthesisui", "ds", slug), res.name, compDir);
|
|
132
|
+
if (edited && edited.length > 0 && !opts.force) {
|
|
133
|
+
console.log("");
|
|
134
|
+
console.log(
|
|
135
|
+
/**
|
|
136
|
+
* O COMANDO QUE RETOMA E' O QUE ELA ACABOU DE RODAR - `refit` recebe o CAMINHO do
|
|
137
|
+
* arquivo-fonte, nunca o nome do componente. Sugerir `refit ${res.name}` devolveria
|
|
138
|
+
* `Could not read "<nome>"`, e uma saida que nao funciona e' pior que nenhuma saida:
|
|
139
|
+
* a pessoa fez o que a tela mandou e levou uma segunda recusa.
|
|
140
|
+
*/
|
|
141
|
+
keptLine(res.name, edited, `npx synthesisui refit ${file}${opts.name ? ` --name ${opts.name}` : ""} --force`));
|
|
142
|
+
console.log(` The recipe is saved in your system's draft either way - only the .tsx was not written.`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
125
145
|
for (const f of files) {
|
|
126
146
|
await writeFile(join(compDir, f.filename), f.code, "utf8");
|
|
127
147
|
}
|
package/dist/commands/sync.js
CHANGED
|
@@ -582,6 +582,24 @@ export async function remeasure(args) {
|
|
|
582
582
|
console.log(body(`${out.written ?? 0} of ${out.total ?? 0} components written.`));
|
|
583
583
|
for (const note of out.notes ?? [])
|
|
584
584
|
console.log(body(paint.faint(note)));
|
|
585
|
+
/**
|
|
586
|
+
* A RECUSA, DITA - e este era o unico numero da tela sem explicacao.
|
|
587
|
+
*
|
|
588
|
+
* O QUE O CLIENTE VIA (medido em 10/09): `60 of 62 components written.` e mais nada. Os dois
|
|
589
|
+
* que faltaram estavam nomeados na resposta, com o motivo, e o comando descartava o campo. Quem
|
|
590
|
+
* le "60 de 62" so' pode concluir uma coisa - que a esteira perdeu dois - e vai procurar um
|
|
591
|
+
* defeito que nao existe, ou pior, nao vai.
|
|
592
|
+
*
|
|
593
|
+
* A COPY JA' EXISTIA: `because` e' a frase do servidor, e reescreve-la aqui seria a segunda
|
|
594
|
+
* versao da mesma explicacao. A primeira coisa que a segunda faz e' discordar da primeira.
|
|
595
|
+
*/
|
|
596
|
+
const refused = out.refused ?? [];
|
|
597
|
+
if (refused.length > 0) {
|
|
598
|
+
console.log("");
|
|
599
|
+
console.log(body(`${refused.length} ${refused.length === 1 ? "component was" : "components were"} refused - ${refused.length === 1 ? "it is" : "they are"} not lost, ${refused.length === 1 ? "it was" : "they were"} not written:`));
|
|
600
|
+
for (const r of refused)
|
|
601
|
+
console.log(body(paint.strong(` ✕ ${r.name} - ${r.because}`)));
|
|
602
|
+
}
|
|
585
603
|
/**
|
|
586
604
|
* A RECUSA POR ESCOPO, DITA - e não como um número que some.
|
|
587
605
|
*
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { dirname, join, relative } from "node:path";
|
|
3
3
|
import { readProjectConfig, resolveRegistry } from "../config.js";
|
|
4
4
|
import { fetchTemplate } from "../registry.js";
|
|
5
5
|
import { inTheirTongue, projectTongue } from "../their-tongue.js";
|
|
6
|
+
import { keptLine, recordWritten, editedHere as theirEdits, } from "../written.js";
|
|
6
7
|
/**
|
|
7
8
|
* Materializes a whole page from a DS template into the project (hybrid
|
|
8
9
|
* codegen-first): the server codegens deterministic files, we write them, and
|
|
@@ -28,6 +29,36 @@ export async function template(slug, name, opts) {
|
|
|
28
29
|
const defaultDir = join("templates", asName ?? name);
|
|
29
30
|
const pageRel = opts.out ?? join(defaultDir, pageFile.filename);
|
|
30
31
|
const pageDir = dirname(join(root, pageRel));
|
|
32
|
+
/**
|
|
33
|
+
* A EDICAO DELE VENCE A REESCRITA (T7) - a MESMA guarda do `component`, no comando que a nao
|
|
34
|
+
* tinha.
|
|
35
|
+
*
|
|
36
|
+
* MEDIDO EM 10/09: `component.ts` consultava o registro do que nos escrevemos em seis pontos e
|
|
37
|
+
* `template.ts` em nenhum. E este comando escreve MAIS: uma pagina inteira, mais a folha
|
|
38
|
+
* co-locada. Alguem que traz um template, ajusta o texto, e roda de novo para pegar uma correcao
|
|
39
|
+
* perdia o ajuste - sem aviso, sem `--force`, sem uma frase.
|
|
40
|
+
*
|
|
41
|
+
* A MESMA CHAVE PARA OS DOIS LADOS: `templates/<nome>` e' o que a leitura procura e o que a
|
|
42
|
+
* escrita grava, entao o fingerprint de hoje e' lido pela rodada de amanha. Sem `.lock`
|
|
43
|
+
* (template trazido antes do install) nao ha' memoria, e o comportamento de sempre continua.
|
|
44
|
+
*/
|
|
45
|
+
const slugDir = join(root, "_synthesisui", "ds", slug);
|
|
46
|
+
const entry = relative(root, pageDir).split(/[\\/]/).join("/");
|
|
47
|
+
const editedHere = await theirEdits(slugDir, entry, pageDir);
|
|
48
|
+
if (editedHere && editedHere.length > 0 && !opts.force) {
|
|
49
|
+
console.log("");
|
|
50
|
+
console.log(keptLine(entry, editedHere,
|
|
51
|
+
/**
|
|
52
|
+
* O RETAKE CARREGA `--out`, senao ele escreve em OUTRO LUGAR.
|
|
53
|
+
*
|
|
54
|
+
* Sem esta parte, quem rodou `template landing --out app/page.tsx` e seguiu a instrucao
|
|
55
|
+
* da recusa escreveria em `templates/landing/page.tsx`: o arquivo que ele editou fica
|
|
56
|
+
* intocado, nasce um segundo, e nada diz que aconteceu. Uma saida que escreve no lugar
|
|
57
|
+
* errado e' pior que uma recusa sem saida.
|
|
58
|
+
*/
|
|
59
|
+
`npx synthesisui template ${slug} ${name}${asName ? ` --as ${asName}` : ""}${opts.out ? ` --out ${opts.out}` : ""} --force`));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
31
62
|
/**
|
|
32
63
|
* NENHUMA MATERIALIZAÇÃO VAZA VOCABULÁRIO INTERNO (INV-VOLTA-02) - a mesma porta do
|
|
33
64
|
* `component`. Uma página inteira saía com `var(--ds-*)` cru enquanto um componente avulso
|
|
@@ -40,6 +71,8 @@ export async function template(slug, name, opts) {
|
|
|
40
71
|
let inlined = 0;
|
|
41
72
|
const still = new Set();
|
|
42
73
|
await mkdir(pageDir, { recursive: true });
|
|
74
|
+
/** OS BYTES QUE FORAM A DISCO, para o fingerprint lembrar EXATAMENTE o que escrevemos. */
|
|
75
|
+
const landed = [];
|
|
43
76
|
const spokenPage = speak(pageFile.code);
|
|
44
77
|
if (spokenPage) {
|
|
45
78
|
named += spokenPage.named;
|
|
@@ -47,7 +80,12 @@ export async function template(slug, name, opts) {
|
|
|
47
80
|
for (const l of spokenPage.left)
|
|
48
81
|
still.add(l);
|
|
49
82
|
}
|
|
50
|
-
|
|
83
|
+
const pageContent = spokenPage ? spokenPage.css : pageFile.code;
|
|
84
|
+
await writeFile(join(root, pageRel), pageContent, "utf8");
|
|
85
|
+
landed.push({
|
|
86
|
+
filename: pageRel.split(/[\\/]/).pop() ?? pageFile.filename,
|
|
87
|
+
content: pageContent,
|
|
88
|
+
});
|
|
51
89
|
console.log(`✓ wrote ${pageRel} (${slug} v${generated.version})`);
|
|
52
90
|
for (const f of siblings) {
|
|
53
91
|
const rel = join(dirname(pageRel), f.filename);
|
|
@@ -58,9 +96,19 @@ export async function template(slug, name, opts) {
|
|
|
58
96
|
for (const l of spoken.left)
|
|
59
97
|
still.add(l);
|
|
60
98
|
}
|
|
61
|
-
|
|
99
|
+
const content = spoken ? spoken.css : f.code;
|
|
100
|
+
await writeFile(join(root, rel), content, "utf8");
|
|
101
|
+
landed.push({ filename: f.filename, content });
|
|
62
102
|
console.log(`✓ wrote ${rel}`);
|
|
63
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* E O REGISTRO E' GRAVADO - a outra metade da guarda, e sem ela a primeira nunca dispara.
|
|
106
|
+
*
|
|
107
|
+
* A leitura la' em cima procura o fingerprint desta escrita. Ler sem gravar seria uma guarda que
|
|
108
|
+
* passa em todo teste e nunca protege ninguem: a promessa so' existe quando as duas pontas
|
|
109
|
+
* fecham.
|
|
110
|
+
*/
|
|
111
|
+
await recordWritten(slugDir, entry, landed);
|
|
64
112
|
if (named > 0 || inlined > 0) {
|
|
65
113
|
console.log(` ${named} reference${named === 1 ? "" : "s"} now speak${named === 1 ? "s" : ""} the name YOUR code gives the value${inlined > 0 ? `, and ${inlined} carr${inlined === 1 ? "ies" : "y"} the value because your code names no token for it` : ""}.`);
|
|
66
114
|
if (still.size > 0) {
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -211,6 +211,56 @@ async function theOnlyInstalled(root) {
|
|
|
211
211
|
throw new RegistryError("No design system is installed here - run `synthesisui add <slug>` first.");
|
|
212
212
|
throw new RegistryError(`More than one system is installed here (${slugs.join(", ")}) - name the one to update: \`synthesisui upgrade <slug>\`.`);
|
|
213
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* O QUE ELE EDITOU DEPOIS DE NOS ESCREVERMOS - a mesma leitura, num lugar so'.
|
|
216
|
+
*
|
|
217
|
+
* A PROMESSA T7 e' *"kept YOUR file(s)"*, e ela vivia dentro do laco que regenera. Isso amarrou a
|
|
218
|
+
* promessa a UM caminho: o do upgrade que tem versao nova. Medido em 10/09 - com a versao igual,
|
|
219
|
+
* `upgrade` retornava antes de chegar ao laco, e a pessoa que editou tres componentes nao ouvia
|
|
220
|
+
* nada sobre eles no comando cuja funcao e' justamente dizer o que aconteceu com os arquivos dela.
|
|
221
|
+
*
|
|
222
|
+
* Extraida, a resposta e' a mesma nos tres caminhos - rematerializou, ja' estava na ultima, ou
|
|
223
|
+
* subiu de versao. Uma leitura, uma frase.
|
|
224
|
+
*/
|
|
225
|
+
async function editedByHand(root, slug, slugDir, componentsDir) {
|
|
226
|
+
const componentsRoot = join(root, componentsDir);
|
|
227
|
+
const marker = `from the "${slug}" design system`;
|
|
228
|
+
const writtenMap = await readWritten(slugDir);
|
|
229
|
+
const out = [];
|
|
230
|
+
for (const entry of await readdir(componentsRoot).catch(() => [])) {
|
|
231
|
+
let head = "";
|
|
232
|
+
try {
|
|
233
|
+
head = (await readFile(join(componentsRoot, entry, `${entry}.tsx`), "utf8")).slice(0, 300);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
continue; // not a materialized component folder
|
|
237
|
+
}
|
|
238
|
+
if (!head.includes("Generated by SynthesisUI") || !head.includes(marker))
|
|
239
|
+
continue;
|
|
240
|
+
const edited = await editedSinceWritten(writtenMap[entry], join(componentsRoot, entry));
|
|
241
|
+
if (edited && edited.length > 0)
|
|
242
|
+
out.push({ entry, edited });
|
|
243
|
+
}
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* E A FRASE, DITA - nos caminhos que NAO regeneram nada.
|
|
248
|
+
*
|
|
249
|
+
* Aqui ela nao e' o relatorio de uma reescrita que respeitou o arquivo dele: e' a confirmacao de
|
|
250
|
+
* que este comando passou e nao tocou no que e' dele. E' a mesma informacao, e omiti-la nos
|
|
251
|
+
* caminhos silenciosos e' o que fazia a promessa valer so' as vezes.
|
|
252
|
+
*/
|
|
253
|
+
async function sayWhatWasKept(root, slug, slugDir) {
|
|
254
|
+
const config = await readProjectConfig(root).catch(() => null);
|
|
255
|
+
if (!config || config.target !== "next")
|
|
256
|
+
return;
|
|
257
|
+
const kept = await editedByHand(root, slug, slugDir, config.componentsDir);
|
|
258
|
+
if (kept.length === 0)
|
|
259
|
+
return;
|
|
260
|
+
console.log("");
|
|
261
|
+
for (const k of kept)
|
|
262
|
+
console.log(body(keptLine(k.entry, k.edited, `npx synthesisui component ${slug} ${k.entry}`)));
|
|
263
|
+
}
|
|
214
264
|
export async function upgrade(asked, opts) {
|
|
215
265
|
const base = resolveRegistry(opts.registry);
|
|
216
266
|
const root = opts.dir ?? process.cwd();
|
|
@@ -263,6 +313,8 @@ export async function upgrade(asked, opts) {
|
|
|
263
313
|
* medido.
|
|
264
314
|
*/
|
|
265
315
|
const measured = await checkAfterWriting(root, `v${installed}`);
|
|
316
|
+
/** ESTE CAMINHO REESCREVE OS ARTEFATOS - e o que e' dele fica onde esta'. Ver `sayWhatWasKept`. */
|
|
317
|
+
await sayWhatWasKept(root, slug, slugDir);
|
|
266
318
|
await reportWhatIsLeft(root, {
|
|
267
319
|
...(opts.cli ? { cli: opts.cli } : {}),
|
|
268
320
|
...(measured > 0 ? { justMeasured: measured } : {}),
|
|
@@ -271,6 +323,12 @@ export async function upgrade(asked, opts) {
|
|
|
271
323
|
}
|
|
272
324
|
if (!opts.force) {
|
|
273
325
|
console.log(`✓ ${slug} is already at the latest version (v${installed}).`);
|
|
326
|
+
/**
|
|
327
|
+
* E AQUI TAMBEM, que e' o caminho mais percorrido de todos: sem gap de versao, este comando
|
|
328
|
+
* dizia uma linha e sumia. Quem editou tres componentes ficava sem saber se eles seguem sendo
|
|
329
|
+
* dele - no comando que existe para responder isso.
|
|
330
|
+
*/
|
|
331
|
+
await sayWhatWasKept(root, slug, slugDir);
|
|
274
332
|
await reportWhatIsLeft(root, opts.cli ? { cli: opts.cli } : {});
|
|
275
333
|
return;
|
|
276
334
|
}
|
|
@@ -308,8 +366,9 @@ export async function upgrade(asked, opts) {
|
|
|
308
366
|
const regenerated = [];
|
|
309
367
|
const failed = [];
|
|
310
368
|
/** Componentes que ELE editou desde que os escrevemos - mantidos intactos (T7). */
|
|
311
|
-
const kept =
|
|
312
|
-
|
|
369
|
+
const kept = await editedByHand(root, slug, slugDir, config.componentsDir);
|
|
370
|
+
/** Os que ELE editou saem da regeneracao - a leitura acima ja' os separou. */
|
|
371
|
+
const keptEntries = new Set(kept.map((k) => k.entry));
|
|
313
372
|
if (config.target === "next") {
|
|
314
373
|
const componentsRoot = join(root, config.componentsDir);
|
|
315
374
|
const marker = `from the "${slug}" design system`;
|
|
@@ -345,16 +404,13 @@ export async function upgrade(asked, opts) {
|
|
|
345
404
|
if (!head.includes("Generated by SynthesisUI") || !head.includes(marker))
|
|
346
405
|
continue;
|
|
347
406
|
/**
|
|
348
|
-
* A EDIÇÃO DELE VENCE A REGENERAÇÃO (T7) - ver `
|
|
407
|
+
* A EDIÇÃO DELE VENCE A REGENERAÇÃO (T7) - ver `editedByHand`. O fingerprint do que NÓS
|
|
349
408
|
* escrevemos foi gravado no `.lock` na hora da escrita; se o disco divergiu, ele editou, e o
|
|
350
409
|
* arquivo é DELE - fica intacto e o terminal diz qual. `null` é install de antes do
|
|
351
410
|
* fingerprint: sem memória não há veredito, e o comportamento de sempre continua.
|
|
352
411
|
*/
|
|
353
|
-
|
|
354
|
-
if (edited && edited.length > 0) {
|
|
355
|
-
kept.push({ entry, edited });
|
|
412
|
+
if (keptEntries.has(entry))
|
|
356
413
|
continue;
|
|
357
|
-
}
|
|
358
414
|
try {
|
|
359
415
|
const res = await fetchComponent(base, slug, entry);
|
|
360
416
|
const { files } = generateComponentFiles(slug, res.name, res.recipe, res.css, res.version, flavourOf(res.name),
|
|
@@ -460,7 +516,7 @@ export async function upgrade(asked, opts) {
|
|
|
460
516
|
if (kept.length > 0) {
|
|
461
517
|
console.log("");
|
|
462
518
|
for (const k of kept)
|
|
463
|
-
console.log(body(keptLine(
|
|
519
|
+
console.log(body(keptLine(k.entry, k.edited, `npx synthesisui component ${slug} ${k.entry}`)));
|
|
464
520
|
}
|
|
465
521
|
const measured = await checkAfterWriting(root, `v${latest.version}`);
|
|
466
522
|
console.log(section("Migrate the app"));
|
package/dist/index.js
CHANGED
|
@@ -117,6 +117,7 @@ Options:
|
|
|
117
117
|
--instruction <s> refit: extra guidance for the adaptation
|
|
118
118
|
--dry refit: adapt and print, but save nothing
|
|
119
119
|
--force clean: apply the changes (without it, dry run)
|
|
120
|
+
component/template/generate/refit: overwrite a file YOU edited
|
|
120
121
|
upgrade: rewrite UPGRADE.md even with no version gap left
|
|
121
122
|
--from <n> upgrade --force: which older snapshot to diff from
|
|
122
123
|
--strict doctor: exit 1 when drift is found in THIS repo (for CI)
|
|
@@ -155,6 +156,21 @@ Examples:
|
|
|
155
156
|
async function main() {
|
|
156
157
|
const { positionals, flags } = parseFlags(process.argv.slice(2));
|
|
157
158
|
const [command, ...args] = positionals;
|
|
159
|
+
/**
|
|
160
|
+
* `synthesisui --version` RESPONDE A VERSAO - uma linha, exit 0.
|
|
161
|
+
*
|
|
162
|
+
* O QUE ACONTECIA (medido em 10/09): `node dist/index.js --version` imprimia as 117 linhas do
|
|
163
|
+
* help e saia 0. Quem pergunta a versao de uma ferramenta - uma pessoa num terminal, um script
|
|
164
|
+
* de CI, um relatorio de bug - recebia a lista de comandos, e a unica forma de descobrir a
|
|
165
|
+
* versao era abrir o `package.json` da instalacao.
|
|
166
|
+
*
|
|
167
|
+
* SEM COMANDO, sempre: `--version <n>` continua sendo flag do `add`, do `component` e do
|
|
168
|
+
* `template`, e la ela chega com um comando na frente. A distincao e' posicional, nao de nome.
|
|
169
|
+
*/
|
|
170
|
+
if (!command && flags.version !== undefined) {
|
|
171
|
+
console.log(CLI_VERSION);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
158
174
|
if (!command || flags.help || command === "help") {
|
|
159
175
|
/**
|
|
160
176
|
* A PRIMEIRA LINHA FALA DO PROJETO ONDE ELA FOI RODADA - ver `where-you-are.ts`.
|
|
@@ -484,6 +500,8 @@ async function main() {
|
|
|
484
500
|
target,
|
|
485
501
|
version,
|
|
486
502
|
as: as_,
|
|
503
|
+
/** A saida da guarda T7: escrever por cima do que ELE editou e' escolha dele. */
|
|
504
|
+
force: flags.force === true,
|
|
487
505
|
});
|
|
488
506
|
break;
|
|
489
507
|
}
|
|
@@ -579,6 +597,8 @@ async function main() {
|
|
|
579
597
|
instruction: typeof flags.instruction === "string" ? flags.instruction : undefined,
|
|
580
598
|
support: typeof flags.support === "string" ? flags.support : undefined,
|
|
581
599
|
dry: flags.dry === true,
|
|
600
|
+
/** A saida da guarda T7: escrever por cima do que ELE editou e' escolha dele. */
|
|
601
|
+
force: flags.force === true,
|
|
582
602
|
});
|
|
583
603
|
break;
|
|
584
604
|
}
|
|
@@ -681,7 +701,14 @@ async function main() {
|
|
|
681
701
|
}
|
|
682
702
|
const ds = typeof flags.ds === "string" ? flags.ds : undefined;
|
|
683
703
|
const name = typeof flags.name === "string" ? flags.name : undefined;
|
|
684
|
-
await generate(description, {
|
|
704
|
+
await generate(description, {
|
|
705
|
+
registry,
|
|
706
|
+
dir,
|
|
707
|
+
ds,
|
|
708
|
+
name,
|
|
709
|
+
/** A saida da guarda T7: escrever por cima do que ELE editou e' escolha dele. */
|
|
710
|
+
force: flags.force === true,
|
|
711
|
+
});
|
|
685
712
|
break;
|
|
686
713
|
}
|
|
687
714
|
default:
|
package/dist/install-marks.js
CHANGED
|
@@ -170,7 +170,24 @@
|
|
|
170
170
|
* O que o cliente ganha ao rodar `upgrade`: o agente dele no Codex passa a poder PERGUNTAR ao
|
|
171
171
|
* sistema, em vez de só receber as regras e adivinhar o resto.
|
|
172
172
|
*/
|
|
173
|
-
|
|
173
|
+
/**
|
|
174
|
+
* 0.16.403 -> 0.16.412 em 10/09, e o SIM é sobre TRÊS coisas que caem na pasta dele:
|
|
175
|
+
*
|
|
176
|
+
* `component` sem `add` sem `.lock`, o vocabulário do repositório dele passa a ser derivado
|
|
177
|
+
* em memória do sistema lido no registry. O `.tsx` que o CLI de ontem
|
|
178
|
+
* escrevia com `var(--ds-*)` cru sai com o nome que o código DELE dá
|
|
179
|
+
* àquele valor - e a nossa folha deixa de ser pré-requisito para
|
|
180
|
+
* trazer um componente.
|
|
181
|
+
* `template` com T7 a página materializada grava o fingerprint no `.lock` e passa a
|
|
182
|
+
* recusar a reescrita do que ele editou à mão. Campo novo no arquivo
|
|
183
|
+
* que o time dele commita.
|
|
184
|
+
* `generate` e `refit` gravavam o fingerprint e nunca o liam; agora recusam por edição
|
|
185
|
+
* dele, então o conjunto de bytes que uma rodada deixa é outro.
|
|
186
|
+
*
|
|
187
|
+
* O que o cliente ganha ao rodar `upgrade`: os componentes que ele já tem voltam a ser reescritos
|
|
188
|
+
* pelo materializador que respeita a edição dele em TODO comando, e não só em dois.
|
|
189
|
+
*/
|
|
190
|
+
export const MATERIALISER_SINCE = "0.16.412";
|
|
174
191
|
/**
|
|
175
192
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
176
193
|
*
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* ESTA PASTA E' UM PROJETO? - a pergunta que todo comando que ESCREVE no repositorio dele
|
|
6
|
+
* deveria fazer antes da primeira escrita.
|
|
7
|
+
*
|
|
8
|
+
* O QUE O CLIENTE VIVIA: `connect` rodado por engano no `~` ou num diretorio recem-criado
|
|
9
|
+
* escrevia `.claude/settings.json`, `CLAUDE.md`, `.mcp.json` e a pasta de governanca ali mesmo -
|
|
10
|
+
* seis escritas, medidas em 10/09, nenhuma delas precedida de uma checagem. Ninguem percebe no
|
|
11
|
+
* dia; percebe semanas depois, quando um `CLAUDE.md` orfao na home passa a instruir todo agente
|
|
12
|
+
* que abre naquela pasta.
|
|
13
|
+
*
|
|
14
|
+
* O CRITERIO E' O DO ECOSSISTEMA, nao um nosso: um `package.json` nesta pasta ou acima dela. A
|
|
15
|
+
* subida para no diretorio que carrega o `.git`, porque a raiz do repositorio e' onde o projeto
|
|
16
|
+
* acaba - continuar subindo alcancaria a home de quem roda, que e' exatamente o acidente que isto
|
|
17
|
+
* existe para recusar.
|
|
18
|
+
*
|
|
19
|
+
* GENERALIDADE: nao pergunta por Next, por React nem pela nossa pasta. Um projeto que ainda nao
|
|
20
|
+
* instalou nada e' um projeto; uma pasta vazia dentro de um repositorio Node tambem e', porque o
|
|
21
|
+
* manifesto de cima responde por ela.
|
|
22
|
+
*/
|
|
23
|
+
export async function projectRootFrom(dir) {
|
|
24
|
+
/**
|
|
25
|
+
* DENTRO DE UMA DEPENDENCIA NAO E' O PROJETO DELE - e' o projeto de outra pessoa, baixado.
|
|
26
|
+
*
|
|
27
|
+
* Um `cd` de distancia do acidente: `node_modules/<lib>` tem `package.json`, entao a subida
|
|
28
|
+
* parava ali e o comando escrevia a fiacao do agente dentro de uma dependencia - que some no
|
|
29
|
+
* proximo `npm ci`, levando junto tudo que ele configurou.
|
|
30
|
+
*/
|
|
31
|
+
if (dir.split(/[\\/]/).includes("node_modules"))
|
|
32
|
+
return null;
|
|
33
|
+
/**
|
|
34
|
+
* E A HOME E' O SEGUNDO TETO, ao lado da raiz do repositorio.
|
|
35
|
+
*
|
|
36
|
+
* O `.git` sozinho nao fechava o acidente que este modulo existe para recusar: numa maquina
|
|
37
|
+
* onde alguem rodou `npm init` na home - acontece -, uma pasta solta em `~/qualquer-coisa` sem
|
|
38
|
+
* git subia ate' `~`, encontrava aquele manifesto, e o comando escrevia `CLAUDE.md`,
|
|
39
|
+
* `.mcp.json` e a casa da governanca na HOME da pessoa.
|
|
40
|
+
*
|
|
41
|
+
* A home nunca e' "o projeto dele", tenha ela manifesto ou nao.
|
|
42
|
+
*/
|
|
43
|
+
const home = homedir();
|
|
44
|
+
let at = dir;
|
|
45
|
+
for (;;) {
|
|
46
|
+
if (at === home)
|
|
47
|
+
return null;
|
|
48
|
+
if (await exists(join(at, "package.json")))
|
|
49
|
+
return at;
|
|
50
|
+
/** A raiz do repositorio e' o teto: acima dela nao existe "o projeto dele". */
|
|
51
|
+
if (await exists(join(at, ".git")))
|
|
52
|
+
return null;
|
|
53
|
+
const up = dirname(at);
|
|
54
|
+
if (up === at)
|
|
55
|
+
return null;
|
|
56
|
+
at = up;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function exists(path) {
|
|
60
|
+
return stat(path).then(() => true, () => false);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A RECUSA, COM O MOTIVO E O QUE FAZER - nunca so' "nao posso".
|
|
64
|
+
*
|
|
65
|
+
* E ELA DIZ O QUE MEDIMOS, nao o que a pasta e'. A primeira redacao decretava
|
|
66
|
+
* *"<dir> is not a project"* e oferecia `npm init -y`: num repositorio Deno ou Rust a primeira
|
|
67
|
+
* frase e' falsa - aquilo E' um projeto - e a segunda manda sujar o repositorio dele com o
|
|
68
|
+
* manifesto de um ecossistema que ele nao usa. Representar o limite do leitor e' diferente de
|
|
69
|
+
* negar o que ele tem.
|
|
70
|
+
*/
|
|
71
|
+
export function notAProject(command, dir) {
|
|
72
|
+
return [
|
|
73
|
+
`No package.json in ${dir}, or in any folder above it up to the repository root - so this reader cannot tell which project it is looking at.`,
|
|
74
|
+
`\`${command}\` writes agent files into a project - a hook, an MCP entry, CLAUDE.md - so it stops before writing anything.`,
|
|
75
|
+
`Point it at the project: npx synthesisui ${command} --dir <path/to/project>`,
|
|
76
|
+
`(it looks for a package.json - that is what this reader knows how to place files in)`,
|
|
77
|
+
];
|
|
78
|
+
}
|
package/dist/their-tongue.js
CHANGED
|
@@ -50,6 +50,33 @@ export function inTheirTongue(css, tongue, destination = "stylesheet") {
|
|
|
50
50
|
});
|
|
51
51
|
return { css: out, named, inlined, left: [...left].sort() };
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* O VOCABULARIO DESTE REPOSITORIO **SEM O `add` TER RODADO** - a folha deixa de ser pre-requisito.
|
|
55
|
+
*
|
|
56
|
+
* O QUE ISTO MUDA PARA O CLIENTE: ele pede UM componente e recebe um arquivo que ja' fala a
|
|
57
|
+
* lingua do codigo dele - `var(--color-ink-900)`, o nome que ELE deu - sem antes materializar as
|
|
58
|
+
* 1813 linhas, 111 variaveis e 628 referencias que o `add` escreve no repositorio (medido em
|
|
59
|
+
* 10/09). A nossa folha e' a REFERENCIA que o agente dele le'; exigi-la para trazer um botao
|
|
60
|
+
* invertia isso, e contrariava a definicao de produto: nada nosso no repositorio dele.
|
|
61
|
+
*
|
|
62
|
+
* A MEDICAO E' A MESMA DO `add`, so' que em memoria: `pointTokensAtTheirNames` varre as folhas
|
|
63
|
+
* DELE e o build dele, e responde quais das nossas variaveis tem nome dele. A diferenca e' que
|
|
64
|
+
* nada disso vai para o disco - nem o mapa, nem a folha.
|
|
65
|
+
*
|
|
66
|
+
* `null` quando o repositorio dele nao nomeia nada do que este sistema declara. Ai' a folha
|
|
67
|
+
* continua sendo o caminho, e quem chama DIZ isso - em vez de escrever `var(--ds-*)` cru e
|
|
68
|
+
* deixar a pessoa descobrir por que a cor nao apareceu.
|
|
69
|
+
*/
|
|
70
|
+
export async function tongueFromArtifacts(root, artifacts) {
|
|
71
|
+
const tokens = artifacts["tokens.css"] ?? "";
|
|
72
|
+
if (!tokens)
|
|
73
|
+
return null;
|
|
74
|
+
const { pointTokensAtTheirNames } = await import("./their-vars.js");
|
|
75
|
+
const { pairs } = await pointTokensAtTheirNames(root, { artifacts });
|
|
76
|
+
if (pairs.length === 0)
|
|
77
|
+
return null;
|
|
78
|
+
return tongueOf(pairs, tokens);
|
|
79
|
+
}
|
|
53
80
|
/**
|
|
54
81
|
* O VOCABULÁRIO DESTE REPOSITÓRIO, montado do que o `add` já deixou na pasta.
|
|
55
82
|
*
|
package/dist/wiring-read.js
CHANGED
|
@@ -36,6 +36,25 @@ import { detectAppDirs, sheetChainOf } from "./global-sheet.js";
|
|
|
36
36
|
*
|
|
37
37
|
* The system itself was always resolved from the root. So is this now.
|
|
38
38
|
*/
|
|
39
|
+
/**
|
|
40
|
+
* O QUE NOS ESCREVEMOS NAO E' FIACAO DELE - e isto era um FALSO SILENCIO, o pior dos dois.
|
|
41
|
+
*
|
|
42
|
+
* MEDIDO EM 10/09: o cabecalho que `component-codegen.ts` poe em todo `.tsx` materializado diz,
|
|
43
|
+
* em comentario, *"Global setup (once per app): import _synthesisui/ds/<slug>/tokens.css"* e
|
|
44
|
+
* *"put data-ds=<slug> on a root element"*. A varredura abaixo procura exatamente essas duas
|
|
45
|
+
* strings em QUALQUER arquivo do repositorio - entao, a partir do primeiro componente que nos
|
|
46
|
+
* mesmos escrevemos, ela passava a responder `imported: true` e `scoped: true` sobre um projeto
|
|
47
|
+
* que nao importa nada e nao tem escopo nenhum.
|
|
48
|
+
*
|
|
49
|
+
* O QUE O CLIENTE VIVIA: ele traz um componente, a tela diz *"This project already imports
|
|
50
|
+
* <slug>'s tokens.css and carries data-ds - nothing to set up"*, ele cola o componente na pagina,
|
|
51
|
+
* e ve' um bloco sem cor, sem tipografia e sem sombra. O comando acabou de garantir que estava
|
|
52
|
+
* tudo certo. O `doctor` le' pela mesma porta, entao dizia o mesmo.
|
|
53
|
+
*
|
|
54
|
+
* A instrucao que ESTE arquivo carrega e' uma instrucao, nao um cumprimento dela. Um comentario
|
|
55
|
+
* nosso nunca prova nada sobre o projeto dele.
|
|
56
|
+
*/
|
|
57
|
+
const OURS = "Generated by SynthesisUI";
|
|
39
58
|
export async function readWiring(root, slug) {
|
|
40
59
|
const w = {
|
|
41
60
|
imported: false,
|
|
@@ -84,6 +103,9 @@ export async function readWiring(root, slug) {
|
|
|
84
103
|
const src = await readFile(file, "utf8").catch(() => "");
|
|
85
104
|
if (!src)
|
|
86
105
|
continue;
|
|
106
|
+
/** Ver `OURS`: o nosso proprio cabecalho respondia por ele. */
|
|
107
|
+
if (src.slice(0, 300).includes(OURS))
|
|
108
|
+
continue;
|
|
87
109
|
if (src.includes(`_synthesisui/ds/${slug}/tokens.css`))
|
|
88
110
|
w.imported = true;
|
|
89
111
|
if (src.includes(`_synthesisui/ds/${slug}/theme.css`))
|
package/dist/written.js
CHANGED
|
@@ -60,11 +60,33 @@ export async function editedSinceWritten(recorded, dir) {
|
|
|
60
60
|
return edited;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
* A
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* A PERGUNTA, NUM LUGAR SO': *ele editou o que escrevemos aqui?*
|
|
64
|
+
*
|
|
65
|
+
* As duas linhas - ler o registro do `.lock`, comparar com o disco - estavam copiadas em cada
|
|
66
|
+
* comando que se lembrou de perguntar, e ausentes nos que nao se lembraram. Medido em 10/09:
|
|
67
|
+
* `component` 6 guardas, `upgrade` 4, `generate` e `refit` gravavam o fingerprint e nunca o liam,
|
|
68
|
+
* `template` nao fazia nem uma coisa nem outra.
|
|
69
|
+
*
|
|
70
|
+
* Uma porta so' torna a omissao visivel: um comando de materializacao que nao a chama aparece no
|
|
71
|
+
* portao de `every-writer-keeps-his-edits.spec.ts`, em vez de aparecer no repositorio de alguem.
|
|
72
|
+
*
|
|
73
|
+
* `null` e' install legado (sem fingerprint gravado) - sem memoria nao ha' veredito, e quem chama
|
|
74
|
+
* mantem o comportamento de sempre.
|
|
75
|
+
*/
|
|
76
|
+
export async function editedHere(slugDir, entry, dir) {
|
|
77
|
+
return editedSinceWritten((await readWritten(slugDir))[entry], dir);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A FRASE DO TERMINAL, uma por arquivo mantido - o critério do T7 é a prova VISÍVEL: o dono
|
|
81
|
+
* fica sabendo na hora, com o arquivo nomeado e a saída dita, não num diff depois do fato.
|
|
82
|
+
*
|
|
83
|
+
* `retake` E' OBRIGATORIO, e é o comando que RETOMA a versão nova. Ele era fixo em
|
|
84
|
+
* `component <slug> <entry>`, o que estava certo enquanto só um comando falava esta frase: no
|
|
85
|
+
* `template` aquela linha mandaria a pessoa rodar o comando errado, e uma saída que não funciona
|
|
86
|
+
* é pior que nenhuma. Obrigatório em vez de opcional porque um default aqui volta a ser o
|
|
87
|
+
* comando de outro comando, calado.
|
|
66
88
|
*/
|
|
67
|
-
export function keptLine(
|
|
89
|
+
export function keptLine(entry, edited, retake) {
|
|
68
90
|
return (`! ${entry}: you edited ${edited.join(", ")} after we wrote it - kept YOUR file(s).\n` +
|
|
69
|
-
` To take the new version anyway:
|
|
91
|
+
` To take the new version anyway: ${retake}`);
|
|
70
92
|
}
|
package/package.json
CHANGED