synthesisui 0.16.421 → 0.16.423
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/claude-md.js +19 -38
- package/dist/commands/add.js +57 -94
- package/dist/commands/component.js +96 -201
- package/dist/commands/connect.js +41 -0
- package/dist/commands/doctor.js +74 -428
- package/dist/commands/generate.js +25 -4
- package/dist/commands/import.js +2 -2
- package/dist/commands/init.js +13 -10
- package/dist/commands/refit.js +13 -2
- package/dist/commands/summary.js +9 -5
- package/dist/commands/sync.js +84 -0
- package/dist/commands/template.js +81 -53
- package/dist/commands/upgrade.js +5 -3
- package/dist/commands/use.js +7 -8
- package/dist/component-codegen.js +50 -23
- package/dist/copy/connect.pt-BR.js +3 -0
- package/dist/doctor/apply-fix.js +2 -2
- package/dist/doctor/scan.js +33 -2
- package/dist/doctor/their-names.js +8 -2
- package/dist/fonts.js +29 -5
- package/dist/global-sheet.js +14 -125
- package/dist/guide.js +131 -83
- package/dist/install-marks.js +4 -4
- package/dist/local-edits.js +257 -0
- package/dist/project-facts.js +138 -32
- package/dist/recipe-css.js +249 -0
- package/dist/skills.js +5 -16
- package/dist/their-tongue.js +180 -43
- package/dist/wired-slugs.js +31 -17
- package/package.json +1 -1
- package/dist/setup-prompt.js +0 -95
- package/dist/sheet-needed.js +0 -317
- package/dist/skill-configure.js +0 -13
- package/dist/wiring-read.js +0 -212
package/dist/project-facts.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* emits. It renders completely unstyled, and nothing anywhere reports a problem.
|
|
15
15
|
*/
|
|
16
16
|
import { readdir, readFile } from "node:fs/promises";
|
|
17
|
-
import { join, relative } from "node:path";
|
|
17
|
+
import { join, relative, sep } from "node:path";
|
|
18
18
|
import { DEFAULT_CONVENTION, } from "./component-codegen.js";
|
|
19
19
|
/**
|
|
20
20
|
* Which React the consumer is on, for the ref-carrying prop type.
|
|
@@ -172,42 +172,148 @@ export async function readInstalledScheme(root, slug) {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
/**
|
|
175
|
-
* O QUE
|
|
175
|
+
* O QUE O `@theme` **DELE** DECLARA, NO APP ONDE O COMPONENTE VAI VIVER - e desde 11/09 é esta a
|
|
176
|
+
* resposta que decide o utilitário.
|
|
176
177
|
*
|
|
177
|
-
* O QUE O CLIENTE
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
178
|
+
* O QUE O CLIENTE GANHA: o componente sai vestindo `rounded-lg` só quando `rounded-lg` já pinta o
|
|
179
|
+
* raio DELE - porque o `@theme` daquele app declara `--radius-lg`. Onde ele não declara, sai o
|
|
180
|
+
* valor arbitrário, que a porta de tradução resolve no nome dele ou no literal. Nos dois casos o
|
|
181
|
+
* arquivo funciona sem nenhuma folha nossa carregada.
|
|
181
182
|
*
|
|
182
|
-
* A
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
183
|
+
* A PERGUNTA MUDOU DE LADO, e é a metade que faltava de `INV-GERAL-13`. Até aqui quem respondia era
|
|
184
|
+
* `installedThemeVars`, sobre a NOSSA folha: o codegen escrevia `rounded-lg` quando o nosso
|
|
185
|
+
* `theme.css` declarava `--radius-lg`, e aquela linha só existe dentro de um arquivo que o app dele
|
|
186
|
+
* teria que importar. Ou seja, o utilitário nomeado era a última dependência de runtime da nossa
|
|
187
|
+
* folha - invisível, porque a classe existe em qualquer projeto com Tailwind e pinta o default sem
|
|
188
|
+
* nada faltar na tela.
|
|
187
189
|
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
+
* `null` É UMA RESPOSTA, e ela significa **não sei** - nunca "ele não declara nada". Os dois casos
|
|
191
|
+
* existem de verdade e pedem coisas opostas:
|
|
190
192
|
*
|
|
191
|
-
* `
|
|
192
|
-
*
|
|
193
|
+
* nenhum `@theme` em lugar nenhum Tailwind v3 (o tema mora no `tailwind.config`), ou projeto
|
|
194
|
+
* sem Tailwind. Ali `rounded-lg` PODE pintar o valor dele, e
|
|
195
|
+
* responder "vazio" faria todo utilitário virar valor
|
|
196
|
+
* arbitrário - a régua acerta o argumento e erra o projeto
|
|
197
|
+
* um `@theme` que não declara ISTO aí o conjunto vem preenchido e a ausência é um fato
|
|
198
|
+
*
|
|
199
|
+
* Quem chama trata `null` como o `minted` sempre tratou: o nome legível fica, porque não há medição
|
|
200
|
+
* que o contradiga.
|
|
201
|
+
*
|
|
202
|
+
* A RESPOSTA É DO APP, E NÃO DO REPOSITÓRIO - e essa é a lição de `INV-VOLTA-12`, que saiu do
|
|
203
|
+
* produto com a instrução de import e não com a topologia. Num monorepo, varrer da raiz faz o
|
|
204
|
+
* `@theme` do app A responder por um componente escrito no app B: emitimos `bg-brand`, aquele app
|
|
205
|
+
* não gera a classe, e a propriedade renderiza NADA - pior que o valor arbitrário, porque nada
|
|
206
|
+
* falta na tela.
|
|
207
|
+
*
|
|
208
|
+
* A RESPOSTA É A INTERSEÇÃO DOS APPS, mais o que é compartilhado - ver o corpo. Um componente
|
|
209
|
+
* materializado vai para uma pasta que num monorepo os dois apps importam, e ele não sabe em qual
|
|
210
|
+
* será usado: o nome só é seguro quando vale em todos.
|
|
193
211
|
*/
|
|
194
|
-
export async function
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
212
|
+
export async function theirThemeVars(root,
|
|
213
|
+
/**
|
|
214
|
+
* As pastas de app deste repositório - `detectAppDirs`. Vazia num projeto de um app só, que é a
|
|
215
|
+
* maioria, e aí a resposta é a do repositório inteiro.
|
|
216
|
+
*/
|
|
217
|
+
apps = []) {
|
|
218
|
+
const shared = new Set();
|
|
219
|
+
let sawTheme = false;
|
|
220
|
+
const collect = async (dir, into, skipApps) => {
|
|
221
|
+
const walk = async (at) => {
|
|
222
|
+
for (const entry of await readdir(at, { withFileTypes: true }).catch(() => [])) {
|
|
223
|
+
if (SKIP.has(entry.name) || entry.name.startsWith("."))
|
|
224
|
+
continue;
|
|
225
|
+
const path = join(at, entry.name);
|
|
226
|
+
const rel = relative(root, path).split(sep).join("/");
|
|
227
|
+
/** Ao varrer o compartilhado, toda pasta que É app (ou contém uma) fica de fora. */
|
|
228
|
+
if (skipApps &&
|
|
229
|
+
apps.some((app) => app === rel || app.startsWith(`${rel}/`)))
|
|
230
|
+
continue;
|
|
231
|
+
if (entry.isDirectory())
|
|
232
|
+
await walk(path);
|
|
233
|
+
else if (/\.css$/i.test(entry.name)) {
|
|
234
|
+
const css = await readFile(path, "utf8").catch(() => "");
|
|
235
|
+
if (!THEME_BLOCK.test(css))
|
|
236
|
+
continue;
|
|
237
|
+
sawTheme = true;
|
|
238
|
+
for (const name of themeVarsIn(css))
|
|
239
|
+
into.add(name);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
await walk(dir);
|
|
244
|
+
};
|
|
245
|
+
/** O que NÃO mora em nenhum app: o pacote compartilhado, e ele vale para todos. */
|
|
246
|
+
await collect(root, shared, apps.length > 0);
|
|
247
|
+
if (apps.length === 0)
|
|
248
|
+
return sawTheme ? shared : null;
|
|
202
249
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
250
|
+
* E O QUE CADA APP DECLARA, INTERSECTADO - porque um componente materializado não sabe em qual
|
|
251
|
+
* app vai ser usado.
|
|
252
|
+
*
|
|
253
|
+
* `components/<nome>/` num monorepo é compartilhado por construção. Se o app A declara
|
|
254
|
+
* `--color-brand` e o B não, escrever `bg-brand` entrega uma propriedade que renderiza NADA no B -
|
|
255
|
+
* pior que o valor arbitrário, porque nada falta na tela. A interseção é a única resposta que vale
|
|
256
|
+
* em qualquer um deles, e ela degrada para "o que o repositório declara" quando há um app só.
|
|
206
257
|
*/
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
258
|
+
let common = null;
|
|
259
|
+
for (const app of apps) {
|
|
260
|
+
const mine = new Set(shared);
|
|
261
|
+
await collect(join(root, ...app.split("/")), mine, false);
|
|
262
|
+
if (common === null)
|
|
263
|
+
common = mine;
|
|
264
|
+
else
|
|
265
|
+
for (const name of [...common])
|
|
266
|
+
if (!mine.has(name))
|
|
267
|
+
common.delete(name);
|
|
268
|
+
}
|
|
269
|
+
return sawTheme ? (common ?? shared) : null;
|
|
270
|
+
}
|
|
271
|
+
/** As pastas que nenhuma varredura de CSS dele deve entrar - `_synthesisui/` é NOSSA. */
|
|
272
|
+
const SKIP = new Set([
|
|
273
|
+
"node_modules",
|
|
274
|
+
".git",
|
|
275
|
+
"dist",
|
|
276
|
+
".next",
|
|
277
|
+
"build",
|
|
278
|
+
"out",
|
|
279
|
+
"_synthesisui",
|
|
280
|
+
]);
|
|
281
|
+
/** Existe um `@theme` neste texto? - a pergunta que separa "não declara" de "não sei". */
|
|
282
|
+
const THEME_BLOCK = /@theme[^{]*\{/;
|
|
283
|
+
/**
|
|
284
|
+
* Os nomes declarados dentro de um `@theme { … }` - a única coisa que o Tailwind v4 lê para gerar
|
|
285
|
+
* utilitário. Uma variável no `:root` não gera classe nenhuma, e tratar as duas como iguais foi o
|
|
286
|
+
* que fez o `theme.css` parecer suficiente quando não era.
|
|
287
|
+
*
|
|
288
|
+
* NADA AQUI ASSUME COMO ELE FORMATA - e essa suposição era legítima enquanto esta função lia a
|
|
289
|
+
* NOSSA folha, que nós escrevemos. Ela deixou de ser no instante em que a pergunta mudou de lado.
|
|
290
|
+
*
|
|
291
|
+
* As duas âncoras antigas eram nossas: `\n\}` para fechar o bloco (uma folha com `@theme { … }`
|
|
292
|
+
* numa linha só nunca fechava) e `^\s*--nome:` para a declaração (nenhuma delas começa a linha num
|
|
293
|
+
* arquivo minificado). Nos dois casos o conjunto saía VAZIO, e vazio não é "não declara": é todo
|
|
294
|
+
* utilitário nomeado virando valor arbitrário, calado.
|
|
295
|
+
*
|
|
296
|
+
* O bloco é contado por CHAVES, e a declaração é delimitada pela pontuação - `{` ou `;` antes, e
|
|
297
|
+
* `;` ou `}` depois, este em lookahead para que o `;` de uma declaração possa abrir a próxima. É a
|
|
298
|
+
* mesma correção que `their-tongue.ts` recebeu no mesmo diff, pelo mesmo motivo.
|
|
299
|
+
*/
|
|
300
|
+
function themeVarsIn(css) {
|
|
301
|
+
const out = [];
|
|
302
|
+
for (const m of css.matchAll(/@theme[^{]*\{/g)) {
|
|
303
|
+
let depth = 1;
|
|
304
|
+
let i = (m.index ?? 0) + m[0].length;
|
|
305
|
+
const start = i;
|
|
306
|
+
for (; i < css.length && depth > 0; i += 1) {
|
|
307
|
+
const c = css[i];
|
|
308
|
+
if (c === "{")
|
|
309
|
+
depth += 1;
|
|
310
|
+
else if (c === "}")
|
|
311
|
+
depth -= 1;
|
|
312
|
+
}
|
|
313
|
+
for (const d of css
|
|
314
|
+
.slice(start, i - 1)
|
|
315
|
+
.matchAll(/(?:^|[{;])\s*(--[a-zA-Z0-9-]+)\s*:\s*[^;}]+(?=[;}]|$)/g))
|
|
316
|
+
out.push(d[1]);
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
213
319
|
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AS REGRAS DE RECEITA QUE UMA PÁGINA VESTE, COPIADAS PARA O CSS DELA.
|
|
3
|
+
*
|
|
4
|
+
* O QUE O CLIENTE GANHA: a página que o `template` escreve renderiza sem nenhuma folha nossa
|
|
5
|
+
* carregada no app dele. Ela veste `.ds-hero`, `.ds-nav` e as outras classes que o sistema declara,
|
|
6
|
+
* e essas regras passam a morar no `.css` colocalizado ao lado da própria página - traduzidas para
|
|
7
|
+
* o vocabulário dele, como todo o resto.
|
|
8
|
+
*
|
|
9
|
+
* O QUE ISTO SUBSTITUI. Uma página é a única materialização que depende da folha por CLASSE, e não
|
|
10
|
+
* por variável: a tradução (`their-tongue.ts`) reescreve `var(--ds-*)` e não tem o que fazer com
|
|
11
|
+
* `.ds-hero`, porque o corpo daquela regra nunca esteve no arquivo. Enquanto isso fosse verdade, o
|
|
12
|
+
* `template` terminava mandando instalar o sistema e colar um `@import` - a instrução que
|
|
13
|
+
* `INV-GERAL-13` proíbe, e a última que sobrava no produto.
|
|
14
|
+
*
|
|
15
|
+
* A PÁGINA VESTE TRÊS CAMADAS, e a primeira versão desta cópia lia UMA.
|
|
16
|
+
*
|
|
17
|
+
* A folha compilada escopa no mesmo `[data-ds]` três coisas diferentes, e a revisão do fecho mediu
|
|
18
|
+
* o que ficava para trás:
|
|
19
|
+
*
|
|
20
|
+
* `@layer components` uma regra por receita - `.ds-hero`, `.ds-nav`
|
|
21
|
+
* `@layer base` o CHÃO do sistema (cor do texto e família do corpo), o piso de
|
|
22
|
+
* `:focus-visible`, e o bloco de `prefers-reduced-motion`
|
|
23
|
+
* `@layer utilities` os utilitários DELE que a folha espelha
|
|
24
|
+
*
|
|
25
|
+
* Copiar só a primeira entregava a página com as receitas e SEM o chão de tipografia, sem o anel de
|
|
26
|
+
* foco e sem a regra de movimento reduzido - uma regra de acessibilidade que o sistema declara e
|
|
27
|
+
* que sumiria sem uma linha em lugar nenhum, sob um terminal dizendo *"nothing to install: this page
|
|
28
|
+
* and its stylesheet stand on their own"*.
|
|
29
|
+
*
|
|
30
|
+
* O QUE A CÓPIA DESFAZ É UMA COISA SÓ: o `@layer`. As camadas existem para o CSS dele poder
|
|
31
|
+
* sobrescrever o nosso sem disputa de especificidade, e isso só faz sentido dentro de uma folha que
|
|
32
|
+
* traz as duas metades. Soltas no arquivo dele, elas mudariam a ordem de cascata daquele arquivo.
|
|
33
|
+
*
|
|
34
|
+
* O `[data-ds="<slug>"]` FICA, e ele é o que impede o vazamento. A página que o `template` escreve
|
|
35
|
+
* já nasce dentro de um wrapper com esse atributo - é assim que o CSS colocalizado dela sempre
|
|
36
|
+
* aplicou -, então o seletor resolve. Desescopar transformaria `.ds-button` numa regra global no
|
|
37
|
+
* app inteiro dele, e um `import "./landing.css"` não isola nada: seria a nossa folha entrando pela
|
|
38
|
+
* porta que esta etapa acabou de fechar.
|
|
39
|
+
*
|
|
40
|
+
* O QUE ELA NÃO FAZ: inventar regra. Só as classes que a página REALMENTE veste são copiadas, e o
|
|
41
|
+
* que a folha não declara não vira nada - a página perde aquilo do mesmo jeito que perderia hoje,
|
|
42
|
+
* e quem chama diz o quê.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* O corpo de CADA `@layer { … }` da folha, com as chaves de dentro contadas.
|
|
46
|
+
*
|
|
47
|
+
* O nome da camada não filtra nada: `base`, `components` e `utilities` carregam, todas as três,
|
|
48
|
+
* regras escopadas que a página veste. O que decide se uma regra é copiada é a CLASSE que ela
|
|
49
|
+
* declara e que o arquivo escreve - a mesma pergunta em qualquer camada.
|
|
50
|
+
*/
|
|
51
|
+
function layerBodies(css) {
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const m of css.matchAll(/@layer\s+[\w-]+\s*\{/g)) {
|
|
54
|
+
let depth = 1;
|
|
55
|
+
let i = (m.index ?? 0) + m[0].length;
|
|
56
|
+
const start = i;
|
|
57
|
+
for (; i < css.length && depth > 0; i += 1) {
|
|
58
|
+
const c = css[i];
|
|
59
|
+
if (c === "{")
|
|
60
|
+
depth += 1;
|
|
61
|
+
else if (c === "}")
|
|
62
|
+
depth -= 1;
|
|
63
|
+
}
|
|
64
|
+
out.push(css.slice(start, i - 1));
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* AS REGRAS DE UM CORPO DE CAMADA, uma a uma - prelúdio + bloco.
|
|
70
|
+
*
|
|
71
|
+
* Uma regra de agrupamento (`@media`, `@supports`) vem inteira, com o corpo dela: cortá-la ao meio
|
|
72
|
+
* produziria CSS inválido, e as media queries são justamente onde mora o comportamento responsivo
|
|
73
|
+
* que a página precisa manter.
|
|
74
|
+
*/
|
|
75
|
+
function rulesIn(body) {
|
|
76
|
+
const out = [];
|
|
77
|
+
let i = 0;
|
|
78
|
+
let start = 0;
|
|
79
|
+
while (i < body.length) {
|
|
80
|
+
if (body[i] !== "{") {
|
|
81
|
+
i += 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const prelude = body.slice(start, i).trim();
|
|
85
|
+
let depth = 1;
|
|
86
|
+
let j = i + 1;
|
|
87
|
+
for (; j < body.length && depth > 0; j += 1) {
|
|
88
|
+
if (body[j] === "{")
|
|
89
|
+
depth += 1;
|
|
90
|
+
else if (body[j] === "}")
|
|
91
|
+
depth -= 1;
|
|
92
|
+
}
|
|
93
|
+
if (prelude)
|
|
94
|
+
out.push({ prelude, block: body.slice(i + 1, j - 1) });
|
|
95
|
+
i = j;
|
|
96
|
+
start = j;
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
/** Os nomes de classe que um prelúdio de seletor menciona - `sm\:ds-hero` conta como `ds-hero`. */
|
|
101
|
+
function classesOf(prelude) {
|
|
102
|
+
const out = [];
|
|
103
|
+
for (const m of prelude.matchAll(/\.((?:\\.|[\w-])+)/g)) {
|
|
104
|
+
const bare = m[1].replace(/\\(.)/g, "$1");
|
|
105
|
+
out.push((bare.split(":").pop() ?? bare).replace(/^[!-]/, ""));
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A REGRA É O ATRIBUTO, E NÃO A FUNÇÃO - e esta capacidade já estava provada antes de nascer aqui.
|
|
111
|
+
*
|
|
112
|
+
* A primeira versão desta função casava só o valor LITERAL de `class`/`className`. Funcionava porque
|
|
113
|
+
* o NOSSO gerador emite literal - ou seja, a régua estava calibrada na forma da nossa própria saída,
|
|
114
|
+
* e o refino que o próprio cabeçalho da página manda ele fazer a deixava para trás.
|
|
115
|
+
*
|
|
116
|
+
* `sheet-needed.ts`, apagado nesta mesma etapa, já tinha resolvido isso, com a medição escrita:
|
|
117
|
+
* `className={cn(…)}` aparece **66 vezes em `packages/ui/src` e 239 em `apps/web-dashboard`** do
|
|
118
|
+
* `frontend-hub` (08/09) - 305 sítios onde a pergunta era feita sobre um texto que não continha a
|
|
119
|
+
* resposta. Uma capacidade provada não se reimplementa mais fraca porque o módulo mudou de nome.
|
|
120
|
+
*
|
|
121
|
+
* POR QUE NÃO O NOME DA FUNÇÃO: casar `cn|clsx|twMerge` seria a lista dos casos de hoje - o próximo
|
|
122
|
+
* projeto chama a dele de `cx`, `classes`, ou passa um ternário direto. O que é estrutural é o
|
|
123
|
+
* ATRIBUTO: dentro do valor de um `class`/`className`, toda string literal é candidata a classe.
|
|
124
|
+
*
|
|
125
|
+
* O QUE ISTO NÃO ALCANÇA, com o número: a tabela de variantes numa const separada (`cva`, `tv`), que
|
|
126
|
+
* chega ao atributo por VARIÁVEL. `sheet-needed.ts` seguia a variável; aqui o erro cai para o lado
|
|
127
|
+
* seguro - uma classe não vista é uma regra não copiada, e a página perde estilo em vez de ganhar
|
|
128
|
+
* regra que ninguém veste. Quando aparecer uma página que a use, o número dela abre a frente.
|
|
129
|
+
*/
|
|
130
|
+
export function classesWorn(source) {
|
|
131
|
+
const out = new Set();
|
|
132
|
+
const collect = (body) => {
|
|
133
|
+
for (const raw of body.split(/\s+/)) {
|
|
134
|
+
if (!raw)
|
|
135
|
+
continue;
|
|
136
|
+
const bare = raw.split(":").pop() ?? raw;
|
|
137
|
+
out.add(bare.replace(/^[!-]/, ""));
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
for (const m of source.matchAll(/class(?:Name)?=(?:"([^"]*)"|'([^']*)'|\{`([^`]*)`\}|\{"([^"]*)"\})/g))
|
|
141
|
+
collect(m[1] ?? m[2] ?? m[3] ?? m[4] ?? "");
|
|
142
|
+
/** E toda string DENTRO da expressão do atributo - ver `attributeExpressions`. */
|
|
143
|
+
for (const expr of attributeExpressions(source))
|
|
144
|
+
for (const lit of expr.matchAll(/"([^"]*)"|'([^']*)'|`([^`]*)`/g))
|
|
145
|
+
collect(lit[1] ?? lit[2] ?? lit[3] ?? "");
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* O VALOR DE UM ATRIBUTO DE CLASSE, QUANDO ELE É UMA EXPRESSÃO - copiado de `sheet-needed.ts`, que
|
|
150
|
+
* saiu nesta etapa, e com a medição que o originou preservada acima.
|
|
151
|
+
*/
|
|
152
|
+
function attributeExpressions(source) {
|
|
153
|
+
const out = [];
|
|
154
|
+
const ATTR = /class(?:Name)?=\{/g;
|
|
155
|
+
for (const m of source.matchAll(ATTR)) {
|
|
156
|
+
let depth = 0;
|
|
157
|
+
let i = (m.index ?? 0) + m[0].length - 1;
|
|
158
|
+
const start = i;
|
|
159
|
+
/** Aspas contam para NÃO deixar uma chave dentro de string fechar a expressão cedo. */
|
|
160
|
+
let quote = null;
|
|
161
|
+
for (; i < source.length; i += 1) {
|
|
162
|
+
const c = source[i];
|
|
163
|
+
if (quote) {
|
|
164
|
+
if (c === "\\")
|
|
165
|
+
i += 1;
|
|
166
|
+
else if (c === quote)
|
|
167
|
+
quote = null;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (c === '"' || c === "'" || c === "`")
|
|
171
|
+
quote = c;
|
|
172
|
+
else if (c === "{")
|
|
173
|
+
depth += 1;
|
|
174
|
+
else if (c === "}") {
|
|
175
|
+
depth -= 1;
|
|
176
|
+
if (depth === 0)
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (depth === 0)
|
|
181
|
+
out.push(source.slice(start + 1, i));
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* AS REGRAS QUE ESTA PÁGINA VESTE, desescopadas e prontas para o `.css` ao lado dela.
|
|
187
|
+
*
|
|
188
|
+
* `sheetCss` é a folha compilada da versão instalada (`_synthesisui/ds/<slug>/v<n>/tokens.css`) -
|
|
189
|
+
* o insumo NOSSO, que continua caindo na pasta e que nada no app dele importa.
|
|
190
|
+
*
|
|
191
|
+
* O seletor sai como está, escopo incluído: a página vive dentro do wrapper que o carrega, e é ele
|
|
192
|
+
* que mantém estas regras dentro da página em vez de soltas no app dele.
|
|
193
|
+
*/
|
|
194
|
+
export function recipeRulesFor(input) {
|
|
195
|
+
const worn = classesWorn(input.source);
|
|
196
|
+
const out = [];
|
|
197
|
+
const classes = new Set();
|
|
198
|
+
for (const body of layerBodies(input.sheetCss))
|
|
199
|
+
for (const { prelude, block } of rulesIn(body)) {
|
|
200
|
+
/** Uma regra de agrupamento entra inteira quando algo dentro dela é vestido. */
|
|
201
|
+
const names = prelude.startsWith("@")
|
|
202
|
+
? [...classesOf(prelude), ...classesOf(block)]
|
|
203
|
+
: classesOf(prelude);
|
|
204
|
+
const hit = names.filter((n) => worn.has(n));
|
|
205
|
+
if (hit.length === 0)
|
|
206
|
+
continue;
|
|
207
|
+
for (const n of hit)
|
|
208
|
+
classes.add(n);
|
|
209
|
+
out.push(`${prelude.replace(/\s+/g, " ").trim()} {${block}}`);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
css: out.join("\n"),
|
|
213
|
+
rules: out.length,
|
|
214
|
+
classes: [...classes].sort(),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* A FOLHA COMPILADA DA VERSÃO INSTALADA - o insumo NOSSO, de onde as regras são copiadas.
|
|
219
|
+
*
|
|
220
|
+
* A folha da raiz é um re-export de uma linha (`@import "./v<n>/tokens.css"`), então quem tem as
|
|
221
|
+
* regras é o arquivo da versão; o `.lock` diz qual é. Vazio quando não há pasta - e aí não há o que
|
|
222
|
+
* copiar, o que é uma resposta e não uma falha.
|
|
223
|
+
*/
|
|
224
|
+
export async function installedSheetCss(root, slug) {
|
|
225
|
+
const { readFile } = await import("node:fs/promises");
|
|
226
|
+
const { join } = await import("node:path");
|
|
227
|
+
const dir = join(root, "_synthesisui", "ds", slug);
|
|
228
|
+
const lock = await readFile(join(dir, ".lock"), "utf8").catch(() => null);
|
|
229
|
+
let version = 0;
|
|
230
|
+
if (lock) {
|
|
231
|
+
try {
|
|
232
|
+
version = JSON.parse(lock).version ?? 0;
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
version = 0;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const versioned = await readFile(join(dir, `v${version}`, "tokens.css"), "utf8").catch(() => "");
|
|
239
|
+
if (versioned)
|
|
240
|
+
return versioned;
|
|
241
|
+
return readFile(join(dir, "tokens.css"), "utf8").catch(() => "");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* O CABEÇALHO DO BLOCO COPIADO - procedência, e nenhuma instrução.
|
|
245
|
+
*
|
|
246
|
+
* Ele responde a pergunta de quem abre o arquivo depois ("de onde vieram estas regras?") sem pedir
|
|
247
|
+
* nada. A versão anterior deste produto respondia a mesma pergunta mandando importar a folha.
|
|
248
|
+
*/
|
|
249
|
+
export const RECIPE_HEADER = "/* From your design system, copied here so this page needs no stylesheet of ours. */";
|
package/dist/skills.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ADAPT_SKILL, ADAPT_SKILL_PATH } from "./skill-adapt.js";
|
|
2
2
|
import { COMPOSE_SKILL, COMPOSE_SKILL_PATH } from "./skill-compose.js";
|
|
3
|
-
import { CONFIGURE_SKILL, CONFIGURE_SKILL_PATH } from "./skill-configure.js";
|
|
4
3
|
import { IMPORT_SKILL, IMPORT_SKILL_PATH } from "./skill-import.js";
|
|
5
4
|
import { INIT_SKILL, INIT_SKILL_PATH } from "./skill-init.js";
|
|
6
5
|
/**
|
|
@@ -11,6 +10,11 @@ import { INIT_SKILL, INIT_SKILL_PATH } from "./skill-init.js";
|
|
|
11
10
|
* conta própria, rodasse `connect` de novo. Ninguém roda um comando de novo sem motivo.
|
|
12
11
|
*
|
|
13
12
|
* É a lei 8 no caso mais barato dela: a lacuna existe, a gente sabe qual é, e dizer custa uma linha.
|
|
13
|
+
*
|
|
14
|
+
* ERAM CINCO E SÃO QUATRO desde 11/09: `/sui-configure-ds` existia para fazer os tokens chegarem ao
|
|
15
|
+
* navegador dele pelo `@import` da nossa folha, e nenhum app dele carrega nada nosso
|
|
16
|
+
* (`INV-GERAL-13`). Quem já tem o arquivo é avisado pelo `connect`, que o NOMEIA sem apagá-lo -
|
|
17
|
+
* ver `RETIRED_SKILLS`.
|
|
14
18
|
*/
|
|
15
19
|
export const SKILLS = [
|
|
16
20
|
{
|
|
@@ -54,19 +58,4 @@ export const SKILLS = [
|
|
|
54
58
|
label: "/sui-compose",
|
|
55
59
|
what: "build what the system does not have",
|
|
56
60
|
},
|
|
57
|
-
/**
|
|
58
|
-
* A DE LIGAR O SISTEMA NO APP, e ela é a única aqui que fecha um passo que a esteira já sabia
|
|
59
|
-
* diagnosticar e não sabia resolver.
|
|
60
|
-
*
|
|
61
|
-
* O `add` IMPRIME a fiação, o `doctor` CONFERE a fiação com três ✓/✗, e nada a ESCREVE. Enquanto
|
|
62
|
-
* ela falta, os tokens não chegam ao navegador e o `doctor --fix` fica recusado de propósito - um
|
|
63
|
-
* fix num projeto sem fiação trocou 210 literais por variáveis que o navegador não resolvia e mudou
|
|
64
|
-
* a página (06/08).
|
|
65
|
-
*/
|
|
66
|
-
{
|
|
67
|
-
path: CONFIGURE_SKILL_PATH,
|
|
68
|
-
source: CONFIGURE_SKILL,
|
|
69
|
-
label: "/sui-configure-ds",
|
|
70
|
-
what: "the tokens, loaded by your app",
|
|
71
|
-
},
|
|
72
61
|
];
|