synthesisui 0.16.277 → 0.16.278
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 +15 -1
- package/dist/commands/mcp.js +24 -4
- package/dist/commands/upgrade.js +12 -1
- package/dist/index.js +10 -0
- package/dist/install-marks.js +1 -1
- package/dist/unread-for-component.js +127 -0
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import { hasInteractiveTemplate, interactiveTemplate, } from "../interactive-tem
|
|
|
8
8
|
import { body, section, snippet } from "../output.js";
|
|
9
9
|
import { findCollision, reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
|
|
10
10
|
import { fetchComponent, RegistryError } from "../registry.js";
|
|
11
|
+
import { readCensus, unreadComment, unreadForComponent, } from "../unread-for-component.js";
|
|
11
12
|
/**
|
|
12
13
|
* Writes the shared `cn.ts` next to the components, built from THIS project's
|
|
13
14
|
* installed theme.
|
|
@@ -165,8 +166,21 @@ export async function component(slug, name, opts) {
|
|
|
165
166
|
* construction. Falling back to disk keeps an older registry working.
|
|
166
167
|
*/
|
|
167
168
|
res.classNames ?? (await readInstalledConvention(root, slug)), res.name, await readInstalledScheme(root, slug));
|
|
169
|
+
/**
|
|
170
|
+
* AS DECLARAÇÕES DO ARQUIVO DELE QUE O INTERPRETADOR NÃO LEU - ver `unread-for-component.ts`.
|
|
171
|
+
*
|
|
172
|
+
* Sem isto ele recebe o componente sem elas e nada diz que faltou algo: ele descobre
|
|
173
|
+
* comparando o resultado com o código original. Elas saem em comentário no `.tsx`, com
|
|
174
|
+
* caminho e linha, porque a plataforma não sabe a qual parte do componente elas se aplicam -
|
|
175
|
+
* escrevê-las como estilo seria supor o destino e entregar um componente errado em vez de um
|
|
176
|
+
* incompleto.
|
|
177
|
+
*
|
|
178
|
+
* Vazio não vira comentário, e o censo ausente não é erro: um projeto que só instalou o
|
|
179
|
+
* sistema não tem censo nenhum, e ele recebe o que sempre recebeu.
|
|
180
|
+
*/
|
|
181
|
+
const nota = unreadComment(unreadForComponent(await readCensus(root), res.name));
|
|
168
182
|
for (const file of files) {
|
|
169
|
-
await writeFile(join(compDir, file.filename), file.code, "utf8");
|
|
183
|
+
await writeFile(join(compDir, file.filename), file.filename.endsWith(".tsx") ? `${nota}${file.code}` : file.code, "utf8");
|
|
170
184
|
}
|
|
171
185
|
if (config.styles === "tailwind")
|
|
172
186
|
await writeCn(root, compDir, slug);
|
package/dist/commands/mcp.js
CHANGED
|
@@ -151,7 +151,7 @@ const TOOLS = [
|
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
name: "list_components",
|
|
154
|
-
description: "The components this design system defines, with what each is for. Look here BEFORE writing any UI element from scratch - if something covers the purpose, materialize it with
|
|
154
|
+
description: "The components this design system defines, with what each is for. Look here BEFORE writing any UI element from scratch - if something covers the purpose, materialize it with add_blueprint instead.",
|
|
155
155
|
inputSchema: { type: "object", properties: {} },
|
|
156
156
|
},
|
|
157
157
|
{
|
|
@@ -169,7 +169,7 @@ const TOOLS = [
|
|
|
169
169
|
},
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
|
-
name: "
|
|
172
|
+
name: "add_blueprint",
|
|
173
173
|
description: "Materialize a component from the design system as real typed code in this project, ready to import and extend. Use it yourself - the person who asked for a feature should never have to know component names.",
|
|
174
174
|
inputSchema: {
|
|
175
175
|
type: "object",
|
|
@@ -570,7 +570,7 @@ async function listComponents(root) {
|
|
|
570
570
|
if (rows.length === 0)
|
|
571
571
|
return "This system defines no components yet - write what you need with its tokens.";
|
|
572
572
|
return [
|
|
573
|
-
`${rows.length} components. Materialize with
|
|
573
|
+
`${rows.length} components. Materialize with add_blueprint before writing one from scratch:`,
|
|
574
574
|
"",
|
|
575
575
|
...rows.sort(),
|
|
576
576
|
].join("\n");
|
|
@@ -1311,7 +1311,7 @@ async function describeComponent(root, name) {
|
|
|
1311
1311
|
if (states.length > 0) {
|
|
1312
1312
|
out.push(` states the recipe already carries: ${states.join(", ")} - they are CSS, so do not write them again in JS.`);
|
|
1313
1313
|
}
|
|
1314
|
-
out.push(` Or call
|
|
1314
|
+
out.push(` Or call add_blueprint { "name": "${name}" } and get it as typed code, one prop per axis.`);
|
|
1315
1315
|
}
|
|
1316
1316
|
/**
|
|
1317
1317
|
* AS INSTRUÇÕES DE RENDER QUE A LEITURA MEDIU - e até aqui elas morriam no
|
|
@@ -1387,6 +1387,13 @@ async function addComponent(root, name) {
|
|
|
1387
1387
|
*/
|
|
1388
1388
|
const BY_COMPONENT = {
|
|
1389
1389
|
describe_component: "describe",
|
|
1390
|
+
/**
|
|
1391
|
+
* OS DOIS NOMES GRAVAM `add`, e é isso que impede a métrica de se dividir em duas.
|
|
1392
|
+
*
|
|
1393
|
+
* Sem esta linha o uso da ferramenta cairia pela metade no dia do apelido sem nada ter caído - e
|
|
1394
|
+
* uma métrica que despenca por causa de um rename é uma métrica que ninguém confia depois.
|
|
1395
|
+
*/
|
|
1396
|
+
add_blueprint: "add",
|
|
1390
1397
|
add_component: "add",
|
|
1391
1398
|
};
|
|
1392
1399
|
/**
|
|
@@ -1419,6 +1426,7 @@ const BY_COMPONENT = {
|
|
|
1419
1426
|
*/
|
|
1420
1427
|
const SUBJECT_ARG = {
|
|
1421
1428
|
describe_component: "name",
|
|
1429
|
+
add_blueprint: "name",
|
|
1422
1430
|
add_component: "name",
|
|
1423
1431
|
/** A família pedida - o sinal que o A1 precisa e que não estava sendo guardado. */
|
|
1424
1432
|
compose_context: "family",
|
|
@@ -1520,6 +1528,18 @@ cli) {
|
|
|
1520
1528
|
return fromContract(await listComponents(root));
|
|
1521
1529
|
case "describe_component":
|
|
1522
1530
|
return describeComponent(root, String(args.name ?? ""));
|
|
1531
|
+
/**
|
|
1532
|
+
* OS DOIS NOMES CHAMAM A MESMA FUNÇÃO, e o antigo sai numa versão futura.
|
|
1533
|
+
*
|
|
1534
|
+
* `blueprint` é o nome público do que a plataforma produz, e `add_component` era o único lugar
|
|
1535
|
+
* da superfície pública que ainda usava o nome antigo. Renomear SUBSTITUINDO quebraria o agente
|
|
1536
|
+
* que já carregou a lista de ferramentas na sessão em curso: ele chamaria um nome que não
|
|
1537
|
+
* existe mais e a sessão dele falharia sem explicação.
|
|
1538
|
+
*
|
|
1539
|
+
* A remoção do apelido fica para quando a telemetria mostrar que ninguém mais o chama - e aí
|
|
1540
|
+
* ela é medida, não estimada.
|
|
1541
|
+
*/
|
|
1542
|
+
case "add_blueprint":
|
|
1523
1543
|
case "add_component":
|
|
1524
1544
|
return addComponent(root, String(args.name ?? ""));
|
|
1525
1545
|
case "playbook": {
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -9,6 +9,7 @@ import { installedBehind, MATERIALISER_SINCE } from "../install-marks.js";
|
|
|
9
9
|
import { body, section, snippet } from "../output.js";
|
|
10
10
|
import { reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
|
|
11
11
|
import { fetchChangelog, fetchComponent, fetchDesignSystem, RegistryError, } from "../registry.js";
|
|
12
|
+
import { readCensus, unreadComment, unreadForComponent, } from "../unread-for-component.js";
|
|
12
13
|
import { add } from "./add.js";
|
|
13
14
|
import { reportWhatIsLeft } from "./align.js";
|
|
14
15
|
import { doctor } from "./doctor.js";
|
|
@@ -301,8 +302,18 @@ export async function upgrade(asked, opts) {
|
|
|
301
302
|
// components somebody already has: without the convention it would have
|
|
302
303
|
// taken a working component and stripped its styles.
|
|
303
304
|
await reactMajorOf(root), res.classNames ?? (await readInstalledConvention(root, slug)), res.name, await readInstalledScheme(root, slug));
|
|
305
|
+
/**
|
|
306
|
+
* A NOTA DAS DECLARAÇÕES NÃO INTERPRETADAS SOBREVIVE AO UPGRADE.
|
|
307
|
+
*
|
|
308
|
+
* Este comando REESCREVE componentes que a pessoa já tem. Sem esta linha, a nota apareceria
|
|
309
|
+
* no `component`/`add_blueprint` e sumiria no próximo `upgrade` - o cliente veria a
|
|
310
|
+
* informação uma vez e a perderia sem nada dizer que ela existiu.
|
|
311
|
+
*
|
|
312
|
+
* Ver `unread-for-component.ts`: censo ausente responde vazio, e vazio não vira comentário.
|
|
313
|
+
*/
|
|
314
|
+
const nota = unreadComment(unreadForComponent(await readCensus(root), res.name));
|
|
304
315
|
for (const file of files) {
|
|
305
|
-
await writeFile(join(componentsRoot, entry, file.filename), file.code, "utf8");
|
|
316
|
+
await writeFile(join(componentsRoot, entry, file.filename), file.filename.endsWith(".tsx") ? `${nota}${file.code}` : file.code, "utf8");
|
|
306
317
|
}
|
|
307
318
|
regenerated.push(entry);
|
|
308
319
|
}
|
package/dist/index.js
CHANGED
|
@@ -44,6 +44,7 @@ Usage - deterministic, FREE:
|
|
|
44
44
|
synthesisui list [options] list the published design systems
|
|
45
45
|
synthesisui list --mine your own systems, with their group
|
|
46
46
|
synthesisui add <slug> [options] materialize a DS into _synthesisui/ds/<slug>/
|
|
47
|
+
synthesisui bp <slug> [options] the same command, short for blueprint
|
|
47
48
|
synthesisui component <slug> <name> bring one EXISTING component in as YOUR <Pascal>.tsx
|
|
48
49
|
synthesisui template <slug> <name> materialize a whole page from a DS template
|
|
49
50
|
(--as landing-home names the output - multi-page safe)
|
|
@@ -348,6 +349,15 @@ async function main() {
|
|
|
348
349
|
*/
|
|
349
350
|
await list({ registry, mine: flags.mine === true });
|
|
350
351
|
break;
|
|
352
|
+
/**
|
|
353
|
+
* `bp` É O MESMO COMANDO QUE `add`, e o apelido existe porque `blueprint` é o nome público do
|
|
354
|
+
* que a plataforma produz.
|
|
355
|
+
*
|
|
356
|
+
* Apelido e não substituição: `add` está escrito em toda mensagem que o CLI já imprimiu, em
|
|
357
|
+
* todo GUIDE.md já materializado e na cabeça de quem usa. Trocá-lo faria um comando que a pessoa
|
|
358
|
+
* digitou ontem parar de existir hoje.
|
|
359
|
+
*/
|
|
360
|
+
case "bp":
|
|
351
361
|
case "add": {
|
|
352
362
|
const slug = args[0];
|
|
353
363
|
if (!slug) {
|
package/dist/install-marks.js
CHANGED
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
* é sempre o bump deste PR - nunca o número que o `package.json` já carrega, porque alguém pode
|
|
129
129
|
* publicar no meio.
|
|
130
130
|
*/
|
|
131
|
-
export const MATERIALISER_SINCE = "0.16.
|
|
131
|
+
export const MATERIALISER_SINCE = "0.16.278";
|
|
132
132
|
/**
|
|
133
133
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
134
134
|
*
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AS DECLARAÇÕES QUE O INTERPRETADOR NÃO LEU, entregues junto com o componente.
|
|
3
|
+
*
|
|
4
|
+
* O QUE O CLIENTE GANHA. Ele recebe o componente com as declarações que a plataforma interpretou,
|
|
5
|
+
* mais as que ela não interpretou, identificadas com arquivo e linha. Hoje ele recebe só as
|
|
6
|
+
* primeiras e nada diz que faltou algo - ele descobre comparando o resultado com o código original.
|
|
7
|
+
*
|
|
8
|
+
* O QUE MUDA NO CÓDIGO. O gerador passa a ler `ledger.unread[].unreadable` do censo e a casar o
|
|
9
|
+
* arquivo de cada declaração com o arquivo do componente. As que casam saem em comentário no arquivo
|
|
10
|
+
* gerado.
|
|
11
|
+
*
|
|
12
|
+
* O QUE O CLIENTE PERCEBE. O mesmo componente que ele recebe hoje, mais um bloco dizendo quais
|
|
13
|
+
* declarações do arquivo dele não estão governadas por token ainda - com o caminho e a linha, para
|
|
14
|
+
* ele decidir o que fazer. Ele não para de codificar por causa de uma lacuna nossa.
|
|
15
|
+
*
|
|
16
|
+
* POR QUE COMENTÁRIO E NÃO CÓDIGO. Uma declaração não interpretada não tem lugar conhecido na
|
|
17
|
+
* recipe: a plataforma não sabe a qual parte do componente ela se aplica. Escrevê-la como estilo
|
|
18
|
+
* seria supor onde ela vai, e supor aqui produz um componente errado em vez de um incompleto. O
|
|
19
|
+
* comentário entrega a informação sem inventar o destino.
|
|
20
|
+
*
|
|
21
|
+
* O CAMINHO PRECISA DO PREFIXO DO ESCOPO. `ledger.unread[].unreadable[].file` é relativo ao escopo
|
|
22
|
+
* medido (`src/Card/Card.tsx`) e `looks[X].api.file` é relativo à raiz do repositório
|
|
23
|
+
* (`packages/ui/src/Card/Card.tsx`). Comparar os dois direto não casa nenhum - medido no censo do
|
|
24
|
+
* cliente: 0 casam sem o prefixo, 4 casam com ele.
|
|
25
|
+
*
|
|
26
|
+
* E A MAIOR PARTE NÃO É DE COMPONENTE NENHUM. Das 194 declarações não interpretadas daquele censo,
|
|
27
|
+
* 190 estão no `globals.css` - reset, `@theme`, `@utility` - que não veste componente. Elas têm
|
|
28
|
+
* destino próprio (a nota do sistema) e não podem entrar aqui: 190 linhas de reset dentro de um
|
|
29
|
+
* `Card` é ruído que faz alguém parar de ler o bloco inteiro.
|
|
30
|
+
*/
|
|
31
|
+
import { readFile } from "node:fs/promises";
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
/**
|
|
34
|
+
* AS DECLARAÇÕES NÃO INTERPRETADAS DO ARQUIVO DAQUELE COMPONENTE.
|
|
35
|
+
*
|
|
36
|
+
* `name` é o nome do componente COMO O CENSO O CONHECE - a chave de `looks`. O chamador tem o slug
|
|
37
|
+
* da recipe (`metric-card`), então ele precisa resolver o nome antes; a resolução não mora aqui
|
|
38
|
+
* porque ela é do crosswalk, e duas cópias dela discordariam.
|
|
39
|
+
*/
|
|
40
|
+
export function unreadForComponent(census, name) {
|
|
41
|
+
const file = census.looks?.[name]?.api?.file ?? fileForSlug(census, name);
|
|
42
|
+
if (!file)
|
|
43
|
+
return [];
|
|
44
|
+
const scope = census.scope ? `${census.scope}/` : "";
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const group of census.ledger?.unread ?? []) {
|
|
47
|
+
for (const one of group.unreadable ?? []) {
|
|
48
|
+
if (!one.file || !one.text)
|
|
49
|
+
continue;
|
|
50
|
+
if (`${scope}${one.file}` !== file)
|
|
51
|
+
continue;
|
|
52
|
+
out.push({
|
|
53
|
+
file: one.file,
|
|
54
|
+
...(typeof one.line === "number" ? { line: one.line } : {}),
|
|
55
|
+
text: one.text,
|
|
56
|
+
reason: group.reason ?? "unread",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* O CENSO CONHECE O COMPONENTE PELO NOME DELE; QUEM CHAMA TEM O SLUG DA RECIPE.
|
|
64
|
+
*
|
|
65
|
+
* `MetricCard` no censo é `metric-card` na recipe, e o comando `add` recebe o segundo. A conversão
|
|
66
|
+
* é a mesma que `exclusive-contract.ts` faz do outro lado - se as duas divergirem, o componente
|
|
67
|
+
* certo perde as declarações dele e nenhum erro aparece.
|
|
68
|
+
*
|
|
69
|
+
* Busca reversa e não um mapa novo: o censo já tem os nomes, e guardar a correspondência seria uma
|
|
70
|
+
* segunda fonte para uma pergunta que a primeira já responde.
|
|
71
|
+
*/
|
|
72
|
+
function fileForSlug(census, slug) {
|
|
73
|
+
const kebab = (v) => v
|
|
74
|
+
.split(".")
|
|
75
|
+
.join("-")
|
|
76
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
77
|
+
.toLowerCase()
|
|
78
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
79
|
+
.replace(/-+/g, "-")
|
|
80
|
+
.replace(/^-|-$/g, "");
|
|
81
|
+
for (const [name, look] of Object.entries(census.looks ?? {}))
|
|
82
|
+
if (kebab(name) === slug && look?.api?.file)
|
|
83
|
+
return look.api.file;
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* O BLOCO DE COMENTÁRIO, ou string vazia quando não há nada a dizer.
|
|
88
|
+
*
|
|
89
|
+
* Vazio não vira comentário: um bloco que aparece em todo arquivo dizendo "nada aqui" ensina a
|
|
90
|
+
* pular o bloco, e aí ele não é lido no arquivo onde ele importa.
|
|
91
|
+
*
|
|
92
|
+
* A frase diz as três coisas que decidem o que a pessoa faz: que aquilo NÃO está governado por
|
|
93
|
+
* token, ONDE está no código dela, e que a plataforma está resolvendo do lado dela. Sem a terceira,
|
|
94
|
+
* a leitura é "a plataforma não deu conta"; com ela, é "isto chega governado depois".
|
|
95
|
+
*/
|
|
96
|
+
export function unreadComment(lines) {
|
|
97
|
+
if (lines.length === 0)
|
|
98
|
+
return "";
|
|
99
|
+
const rows = lines.map((l) => ` * ${l.file}${l.line ? `:${l.line}` : ""} ${l.text}`);
|
|
100
|
+
const n = lines.length;
|
|
101
|
+
return `/**
|
|
102
|
+
* ${n} declaration${n === 1 ? "" : "s"} from your own file ${n === 1 ? "was" : "were"} not interpreted, so ${n === 1 ? "it is" : "they are"} not
|
|
103
|
+
* governed by a token here yet. ${n === 1 ? "It" : "They"} still work${n === 1 ? "s" : ""} in your project - this is a gap in
|
|
104
|
+
* what this pipeline can read, not in your code, and it is being closed on our side.
|
|
105
|
+
*
|
|
106
|
+
${rows.join("\n")}
|
|
107
|
+
*/
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* O CENSO DO PROJETO, ou vazio quando não existe.
|
|
112
|
+
*
|
|
113
|
+
* Um projeto que só instalou o sistema nunca rodou `import` e não tem censo. Isso não é erro: é o
|
|
114
|
+
* estado normal de quem CONSOME o design system sem alimentá-lo. Ele recebe o componente como
|
|
115
|
+
* sempre recebeu, sem a nota - e um erro aqui transformaria uma ausência normal em falha do comando.
|
|
116
|
+
*/
|
|
117
|
+
export async function readCensus(root) {
|
|
118
|
+
const raw = await readFile(join(root, "_synthesisui", "census.json"), "utf8").catch(() => null);
|
|
119
|
+
if (!raw)
|
|
120
|
+
return {};
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(raw);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return {};
|
|
126
|
+
}
|
|
127
|
+
}
|
package/package.json
CHANGED