synthesisui 0.16.373 → 0.16.375
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/doctor.js +86 -34
- package/dist/doctor/apply-fix.js +73 -6
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -589,6 +589,55 @@ async function readWiring(root, slug) {
|
|
|
589
589
|
}
|
|
590
590
|
/** Onde o retrato mora por default: dentro do que a esteira já escreve, e commitável. */
|
|
591
591
|
const DEFAULT_BASELINE = "_synthesisui/drift-baseline.json";
|
|
592
|
+
/**
|
|
593
|
+
* A PORTA ÚNICA DO `--fix` - e ela existe porque o conserto passou a ter DOIS chamadores.
|
|
594
|
+
*
|
|
595
|
+
* O caminho normal chama com o diagnóstico inteiro. O caminho de um projeto sem fiação chama com o
|
|
596
|
+
* subconjunto que o código DELE já resolve - ver `ownName` e o bloco que decide isso. Duas cópias
|
|
597
|
+
* desta sequência divergiriam no dia em que uma delas ganhasse um passo: o `writeFix` sem o
|
|
598
|
+
* `appendEvent`, ou o recibo sem a linha que manda ler o diff.
|
|
599
|
+
*/
|
|
600
|
+
async function runFix(root, d, writing) {
|
|
601
|
+
const read = await readerFor(root, d.findings.filter((f) => nameToWrite(f)).map((f) => f.file));
|
|
602
|
+
const { result, next } = planFix(d, read);
|
|
603
|
+
if (writing)
|
|
604
|
+
await writeFix(root, next);
|
|
605
|
+
console.log(section(writing ? "Fixed what had an answer" : "What --fix would do"));
|
|
606
|
+
for (const line of describeFix(result, !writing))
|
|
607
|
+
console.log(body(line));
|
|
608
|
+
if (!writing && result.applied.length > 0) {
|
|
609
|
+
console.log("");
|
|
610
|
+
console.log(body("Nothing was written. When the list above is what you want:"));
|
|
611
|
+
console.log(paint.blue(snippet(["npx synthesisui@latest doctor --fix --write"])));
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* O CONSERTO DEIXA RASTRO - ver `CheckEvent.kind: "fix"`.
|
|
615
|
+
*
|
|
616
|
+
* Sem esta linha, "ele trocou 129 valores por token" existia só no terminal daquele minuto: o
|
|
617
|
+
* ledger deduz conserto por arquivo que limpou, e um `--fix` que toca 79 arquivos de uma vez não
|
|
618
|
+
* aparece como um ato.
|
|
619
|
+
*/
|
|
620
|
+
if (writing && result.applied.length > 0) {
|
|
621
|
+
await appendEvent(root, {
|
|
622
|
+
kind: "fix",
|
|
623
|
+
at: new Date().toISOString(),
|
|
624
|
+
named: result.applied.length,
|
|
625
|
+
});
|
|
626
|
+
console.log("");
|
|
627
|
+
console.log(body(paint.dim("Read the diff before committing - it is your code, and this replaced literals with the tokens your own system declares.")));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* O NOME QUE ESTA TROCA ESCREVERIA É DELE? - a pergunta que a recusa do `--fix` nunca fez.
|
|
632
|
+
*
|
|
633
|
+
* `nameToWrite` devolve `theirToken ?? token`: quando o repositório dele nomeia aquele valor, é o
|
|
634
|
+
* nome DELE que vai para o código. Um nome dele resolve no navegador dele HOJE, com ou sem a nossa
|
|
635
|
+
* folha instalada - a folha só é necessária para os `--ds-*`, que são nossos.
|
|
636
|
+
*/
|
|
637
|
+
const ownName = (f) => {
|
|
638
|
+
const name = nameToWrite(f);
|
|
639
|
+
return Boolean(name && !name.startsWith("--ds-"));
|
|
640
|
+
};
|
|
592
641
|
export async function doctor(opts) {
|
|
593
642
|
const root = resolve(opts.dir ?? process.cwd());
|
|
594
643
|
/**
|
|
@@ -982,9 +1031,42 @@ export async function doctor(opts) {
|
|
|
982
1031
|
}).catch(() => { });
|
|
983
1032
|
console.log("");
|
|
984
1033
|
if (opts.fix) {
|
|
985
|
-
/**
|
|
986
|
-
|
|
987
|
-
|
|
1034
|
+
/**
|
|
1035
|
+
* A RECUSA PASSA A SER POR VALOR, E NÃO PELO COMANDO INTEIRO.
|
|
1036
|
+
*
|
|
1037
|
+
* NASCEU DE UMA PERGUNTA DO DONO (04/09): *"se o design system dele já está funcionando e são
|
|
1038
|
+
* as mesmas referências, faz sentido esse ligamento? combinamos que não seria invasivo"*.
|
|
1039
|
+
*
|
|
1040
|
+
* A MEDIÇÃO DEU RAZÃO A ELE, pelo mesmo caminho que a tela usa (`loadSystem` + `scanSource` +
|
|
1041
|
+
* `diagnose` sobre packages/ui + apps/landing + apps/web do codelevel): dos 216 valores com
|
|
1042
|
+
* nome esperando, **156 (72%) escreveriam o nome DELE** - `--color-brand-violet`,
|
|
1043
|
+
* `--color-tier-gold`, `--duration-base` - e esses resolvem no navegador dele HOJE, sem folha
|
|
1044
|
+
* nenhuma instalada. Só 60 apontam para `--ds-*`, que são nossos e precisam da fiação.
|
|
1045
|
+
*
|
|
1046
|
+
* A recusa olhava `blocked` e nada mais. Ela nunca perguntava de quem era o nome que ia
|
|
1047
|
+
* escrever, embora `nameToWrite` já responda isso (`theirToken ?? token`). O produto recusava
|
|
1048
|
+
* 156 consertos que funcionariam para proteger 60 que quebrariam - e cobrava, em troca, uma
|
|
1049
|
+
* folha inteira dentro do app de alguém que só queria governar o próprio vocabulário.
|
|
1050
|
+
*
|
|
1051
|
+
* ISSO ATINGE O CASO DE USO CENTRAL: quem importa um design system que já existe tem, por
|
|
1052
|
+
* construção, a maioria dos nomes sendo dele.
|
|
1053
|
+
*
|
|
1054
|
+
* O QUE FICA DE FORA CONTINUA SENDO DITO, com o número e o motivo - Lei 8. O que muda é que a
|
|
1055
|
+
* lacuna deixou de fechar a porta inteira.
|
|
1056
|
+
*/
|
|
1057
|
+
const theirs = d.findings.filter(ownName).length;
|
|
1058
|
+
const ours = d.findings.filter((f) => nameToWrite(f) && !ownName(f)).length;
|
|
1059
|
+
if (theirs > 0) {
|
|
1060
|
+
console.log(body(`${theirs} of your hand-written values ${theirs === 1 ? "carries" : "carry"} a name your own code declares, and ${theirs === 1 ? "it resolves" : "those resolve"} today - --fix will swap ${theirs === 1 ? "it" : "them"}.`));
|
|
1061
|
+
if (ours > 0)
|
|
1062
|
+
console.log(body(`${ours} more point at ${table.name ?? table.slug} tokens, which only resolve once the wiring above is done. Left alone.`));
|
|
1063
|
+
console.log("");
|
|
1064
|
+
await runFix(root, { ...d, findings: d.findings.filter(ownName) }, opts.write === true);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
/** Nenhum nome dele em jogo: aqui a recusa continua sendo a resposta certa, e inteira. */
|
|
1068
|
+
const one = ours === 1;
|
|
1069
|
+
console.log(body(`--fix is refused while ${bothMissing ? "those two are" : "that one is"} false. ${ours} of your hand-written values`));
|
|
988
1070
|
console.log(body(`${one ? "does" : "do"} have a name in the system, and swapping ${one ? "it" : "them"} now would point ${one ? "it" : "them"} at`));
|
|
989
1071
|
console.log(body(`variables the browser cannot resolve - the ${one ? "declaration" : "declarations"} would be dropped`));
|
|
990
1072
|
console.log(body(`and the page would change. Wire ${bothMissing ? "the two lines" : "that line"} first.`));
|
|
@@ -1878,37 +1960,7 @@ export async function doctor(opts) {
|
|
|
1878
1960
|
* 1500 achados e depois dizer "troquei 900" é fazer a pessoa procurar a linha que importa.
|
|
1879
1961
|
*/
|
|
1880
1962
|
if (opts.fix) {
|
|
1881
|
-
|
|
1882
|
-
const { result, next } = planFix(d, read);
|
|
1883
|
-
const writing = opts.write === true;
|
|
1884
|
-
if (writing)
|
|
1885
|
-
await writeFix(root, next);
|
|
1886
|
-
console.log(section(writing ? "Fixed what had an answer" : "What --fix would do"));
|
|
1887
|
-
for (const line of describeFix(result, !writing))
|
|
1888
|
-
console.log(body(line));
|
|
1889
|
-
if (!writing && result.applied.length > 0) {
|
|
1890
|
-
console.log("");
|
|
1891
|
-
console.log(body("Nothing was written. When the list above is what you want:"));
|
|
1892
|
-
console.log(paint.blue(snippet(["npx synthesisui@latest doctor --fix --write"])));
|
|
1893
|
-
}
|
|
1894
|
-
/**
|
|
1895
|
-
* O CONSERTO DEIXA RASTRO - ver `CheckEvent.kind: "fix"`.
|
|
1896
|
-
*
|
|
1897
|
-
* Sem esta linha, "ele trocou 129 valores por token" existia só no terminal daquele minuto: o
|
|
1898
|
-
* ledger deduz conserto por arquivo que limpou, e um `--fix` que toca 79 arquivos de uma vez não
|
|
1899
|
-
* aparece como um ato.
|
|
1900
|
-
*/
|
|
1901
|
-
if (writing && result.applied.length > 0) {
|
|
1902
|
-
await appendEvent(root, {
|
|
1903
|
-
kind: "fix",
|
|
1904
|
-
at: new Date().toISOString(),
|
|
1905
|
-
named: result.applied.length,
|
|
1906
|
-
});
|
|
1907
|
-
}
|
|
1908
|
-
if (writing && result.applied.length > 0) {
|
|
1909
|
-
console.log("");
|
|
1910
|
-
console.log(body(paint.dim("Read the diff before committing - it is your code, and this replaced literals with the tokens your own system declares.")));
|
|
1911
|
-
}
|
|
1963
|
+
await runFix(root, d, opts.write === true);
|
|
1912
1964
|
return;
|
|
1913
1965
|
}
|
|
1914
1966
|
if (opts.writeBaseline) {
|
package/dist/doctor/apply-fix.js
CHANGED
|
@@ -51,6 +51,8 @@ export function planFix(d, read) {
|
|
|
51
51
|
const next = new Map();
|
|
52
52
|
/** Linhas por arquivo, mutadas em memória: dois achados no mesmo arquivo compõem. */
|
|
53
53
|
const lines = new Map();
|
|
54
|
+
/** A mesma chave, congelada antes da primeira troca - ver o skip de `not-written`. */
|
|
55
|
+
const originals = new Map();
|
|
54
56
|
/**
|
|
55
57
|
* DE BAIXO PARA CIMA no arquivo não é necessário - a troca não muda a contagem de linhas - mas a
|
|
56
58
|
* ORDEM por arquivo é, para dois achados na mesma linha não se atropelarem: o segundo procura o seu
|
|
@@ -119,6 +121,11 @@ export function planFix(d, read) {
|
|
|
119
121
|
continue;
|
|
120
122
|
}
|
|
121
123
|
lines.set(f.file, raw.split("\n"));
|
|
124
|
+
/**
|
|
125
|
+
* A LINHA COMO ELA ESTAVA ANTES DE QUALQUER TROCA DESTA RODADA - e é ela que separa dois
|
|
126
|
+
* skips que hoje se dizem com a mesma frase. Ver o bloco de `swapped === null`.
|
|
127
|
+
*/
|
|
128
|
+
originals.set(f.file, raw.split("\n"));
|
|
122
129
|
}
|
|
123
130
|
const body = lines.get(f.file);
|
|
124
131
|
if (!body)
|
|
@@ -127,12 +134,38 @@ export function planFix(d, read) {
|
|
|
127
134
|
const current = body[idx];
|
|
128
135
|
const swapped = current === undefined ? null : swap(current, f.literal, name);
|
|
129
136
|
if (swapped === null) {
|
|
130
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* DOIS MOTIVOS DIFERENTES, E A TELA DIZIA UM SÓ - e o que ela dizia era falso quase sempre.
|
|
139
|
+
*
|
|
140
|
+
* O QUE O DONO VIU em 05/09: *"13 lines changed since the scan and were left alone - run it
|
|
141
|
+
* again"*, num repositório onde ele não tinha editado nada. Medido: **13 de 13** eram classe
|
|
142
|
+
* utilitária. A linha era `"transition-all duration-300 ease-[var(--ease-out-soft)]"`, o scan
|
|
143
|
+
* derivou `300ms` de `duration-300` - corretamente -, e o `swap` procura o texto `300ms`, que
|
|
144
|
+
* nunca esteve escrito ali. `grep 300ms` no arquivo devolve zero.
|
|
145
|
+
*
|
|
146
|
+
* O comentário que estava aqui já sabia disso: *"o arquivo mudou desde a medição, OU o literal
|
|
147
|
+
* já foi trocado por outro achado"*. A frase impressa contava só o primeiro caso, e mandava
|
|
148
|
+
* rodar de novo - o que não muda nada, porque nada mudou.
|
|
149
|
+
*
|
|
150
|
+
* A LINHA ORIGINAL DESEMPATA. Sem o literal nela, o valor veio de forma abreviada e trocá-lo é
|
|
151
|
+
* outra reescrita (`duration-[var(--duration-base)]`), não uma troca de literal. Com o literal
|
|
152
|
+
* nela, alguma coisa mexeu na linha depois - e aí rodar de novo é o conselho certo.
|
|
153
|
+
*
|
|
154
|
+
* É GERAL: `duration-*` é Tailwind puro - 15 arquivos no repositório dele, 37 no frontend-hub.
|
|
155
|
+
*/
|
|
156
|
+
/**
|
|
157
|
+
* A LINHA SUMIU É OUTRA COISA - e é `moved` de verdade: um arquivo que encolheu desde a
|
|
158
|
+
* medição mudou, e rodar de novo é o conselho certo. `not-written` só quando a linha ESTÁ lá
|
|
159
|
+
* e o valor não está escrito nela.
|
|
160
|
+
*/
|
|
161
|
+
const before = originals.get(f.file)?.[idx];
|
|
131
162
|
skipped.push({
|
|
132
163
|
file: f.file,
|
|
133
164
|
line: f.line,
|
|
134
165
|
literal: f.literal,
|
|
135
|
-
because:
|
|
166
|
+
because: before === undefined || before.includes(f.literal)
|
|
167
|
+
? "moved"
|
|
168
|
+
: "not-written",
|
|
136
169
|
});
|
|
137
170
|
continue;
|
|
138
171
|
}
|
|
@@ -176,6 +209,8 @@ export async function readerFor(root, files) {
|
|
|
176
209
|
export function describeFix(result, dry) {
|
|
177
210
|
const { applied, skipped } = result;
|
|
178
211
|
const moved = skipped.filter((s) => s.because === "moved").length;
|
|
212
|
+
/** O valor veio de forma abreviada - ver `not-written` em `planFix`. */
|
|
213
|
+
const notWritten = skipped.filter((s) => s.because === "not-written").length;
|
|
179
214
|
const coincidence = skipped.filter((s) => s.because === "cross-family").length;
|
|
180
215
|
const unread = skipped.filter((s) => s.because === "unreadable").length;
|
|
181
216
|
const decisions = skipped.filter((s) => s.because === "no-token").length;
|
|
@@ -187,23 +222,55 @@ export function describeFix(result, dry) {
|
|
|
187
222
|
? `Nothing to apply. All ${decisions} findings are values your system has no name for - those are design decisions, not fixes.`
|
|
188
223
|
: relative > 0
|
|
189
224
|
? `Nothing to apply. All ${relative} findings are lengths in \`em\`, which follow the element's font size - swapping them can move the layout, so that call is yours.`
|
|
190
|
-
:
|
|
225
|
+
: /**
|
|
226
|
+
* E QUANDO NADA É APLICÁVEL, O MOTIVO SAI AQUI TAMBÉM - senão a saída inteira é
|
|
227
|
+
* "Nothing to apply." e a pessoa não tem como saber que o valor existe, tem nome, e só
|
|
228
|
+
* não está escrito como texto.
|
|
229
|
+
*/
|
|
230
|
+
notWritten > 0
|
|
231
|
+
? `Nothing to apply. All ${notWritten} findings are written as a shorthand - a utility class like \`duration-300\` carries the value in its name, so there is no literal on the line to replace. Changing them is a different rewrite.`
|
|
232
|
+
: "Nothing to apply.");
|
|
191
233
|
return lines;
|
|
192
234
|
}
|
|
193
235
|
lines.push(`${dry ? "Would replace" : "Replaced"} ${applied.length} hand-written value${applied.length === 1 ? "" : "s"} with the token your system already has, across ${result.files} file${result.files === 1 ? "" : "s"}.`);
|
|
194
236
|
const byToken = new Map();
|
|
195
237
|
for (const a of applied)
|
|
196
238
|
byToken.set(a.token, (byToken.get(a.token) ?? 0) + 1);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
239
|
+
const ranked = [...byToken.entries()].sort((a, b) => b[1] - a[1]);
|
|
240
|
+
const SHOWN = 6;
|
|
241
|
+
for (const [token, n] of ranked.slice(0, SHOWN))
|
|
200
242
|
lines.push(` var(${token}) · ${n} time${n === 1 ? "" : "s"}`);
|
|
243
|
+
/**
|
|
244
|
+
* O QUE O CORTE ESCONDEU, DITO - e é a mesma lei que a linha abaixo já obedece.
|
|
245
|
+
*
|
|
246
|
+
* O QUE O DONO VIU em 05/09: `Would replace 143 ... across 20 files` e seis linhas somando 81. As
|
|
247
|
+
* outras 62 ocorrências não apareciam em lugar nenhum, e ele aprovaria a escrita sem saber que
|
|
248
|
+
* existiam.
|
|
249
|
+
*
|
|
250
|
+
* O comentário três linhas abaixo já enuncia a regra - *"DITO SEMPRE QUE ACONTECE, senão o número
|
|
251
|
+
* some da conta sem explicação"* -, aplicada ao `relative` e ao `moved` e não ao próprio corte
|
|
252
|
+
* desta lista. Terceira vez nesta família num dia: o `gaps` (#1327) e a tela de admin (#1304).
|
|
253
|
+
*/
|
|
254
|
+
const rest = ranked.slice(SHOWN);
|
|
255
|
+
if (rest.length > 0) {
|
|
256
|
+
const restUses = rest.reduce((n, [, uses]) => n + uses, 0);
|
|
257
|
+
lines.push(` and ${restUses} more across ${rest.length} other token${rest.length === 1 ? "" : "s"}, not listed here`);
|
|
258
|
+
}
|
|
201
259
|
/**
|
|
202
260
|
* DITO SEMPRE QUE ACONTECE - senão o número de "trocado" some da conta sem explicação, e a pessoa
|
|
203
261
|
* conclui que o comando falhou onde ele se recusou de propósito.
|
|
204
262
|
*/
|
|
205
263
|
if (relative > 0)
|
|
206
264
|
lines.push(` ${relative} more ${relative === 1 ? "is a length" : "are lengths"} in \`em\`, which follow the element's font size - your system names the same number, but the pixel may differ. Left for you to decide.`);
|
|
265
|
+
/**
|
|
266
|
+
* O VALOR QUE NÃO ESTÁ ESCRITO - a frase que substituiu uma afirmação falsa sobre o repositório.
|
|
267
|
+
*
|
|
268
|
+
* Isto saía como *"N lines changed since the scan - run it again"*, e na tela do dono nada tinha
|
|
269
|
+
* mudado: 13 de 13 eram `duration-300` e afins. Mandar rodar de novo não muda nada, e mandar
|
|
270
|
+
* alguém procurar uma edição que ela não fez é pior que calar.
|
|
271
|
+
*/
|
|
272
|
+
if (notWritten > 0)
|
|
273
|
+
lines.push(` ${notWritten} more ${notWritten === 1 ? "is" : "are"} written as a shorthand - a utility class like \`duration-300\` carries the value in its name, so there is no literal on the line to replace. Changing ${notWritten === 1 ? "it" : "those"} is a different rewrite. Left alone.`);
|
|
207
274
|
if (moved > 0)
|
|
208
275
|
lines.push(`${moved} line${moved === 1 ? "" : "s"} changed since the scan and ${moved === 1 ? "was" : "were"} left alone - run it again.`);
|
|
209
276
|
if (coincidence > 0)
|
package/package.json
CHANGED