synthesisui 0.16.289 → 0.16.290
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/add.js +25 -0
- package/dist/commands/component.js +65 -3
- package/dist/install-marks.js +1 -1
- package/dist/repo-state.js +9 -0
- package/dist/their-tongue.js +47 -0
- package/dist/their-vars.js +10 -3
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -268,6 +268,8 @@ export async function add(slug, opts) {
|
|
|
268
268
|
* repositório, e é ele que avisa quando uma delas deixa de resolver.
|
|
269
269
|
*/
|
|
270
270
|
...(theirVars.pointed > 0 ? { theirVars: theirVars.pointed } : {}),
|
|
271
|
+
/** Ver `RootLock.tokenMap`: o fato, ao lado do número que ele produz. */
|
|
272
|
+
...(theirVars.pairs.length > 0 ? { tokenMap: theirVars.pairs } : {}),
|
|
271
273
|
...(scope ? { scope } : {}),
|
|
272
274
|
...(systems.length > 0 ? { scopes: systems } : {}),
|
|
273
275
|
...(usage.length > 0 ? { usage } : {}),
|
|
@@ -394,6 +396,29 @@ export async function add(slug, opts) {
|
|
|
394
396
|
* `var(--color-ink-500)` num arquivo "gerenciado pelo synthesisui" e não tem como saber de onde
|
|
395
397
|
* veio. Dito assim, ele lê a linha como o que ela é - o sistema seguindo o vocabulário dele.
|
|
396
398
|
*/
|
|
399
|
+
/**
|
|
400
|
+
* ELE RENOMEOU UM TOKEN, E A GENTE DIZ - antes era um silêncio que o sistema pagava.
|
|
401
|
+
*
|
|
402
|
+
* `their-vars.ts` decidiu NÃO emitir `var(--x, #valor)` justamente para não pintar o valor velho
|
|
403
|
+
* por cima de um rename dele: *"se ele renomear, quebra, e o `doctor` diz qual"* (dono, 22/08). A
|
|
404
|
+
* quebra é deliberada e continua. O que faltava era ele saber ANTES de abrir a tela e ver a cor
|
|
405
|
+
* sumida - o mapa anterior está no `.lock`, o novo acabou de ser calculado, e a diferença é uma
|
|
406
|
+
* comparação.
|
|
407
|
+
*
|
|
408
|
+
* SÓ O RENAME, e não a troca de valor: trocar o valor não quebra nada - a folha aponta para o nome
|
|
409
|
+
* dele e a cor nova passa a valer, que é o comportamento que a gente promete. Renomear é o que
|
|
410
|
+
* deixa a referência apontando para o vazio.
|
|
411
|
+
*/
|
|
412
|
+
const renamed = (prev?.tokenMap ?? []).filter((before) => {
|
|
413
|
+
const now = theirVars.pairs.find((p) => p.ours === before.ours);
|
|
414
|
+
return now && now.theirs !== before.theirs;
|
|
415
|
+
});
|
|
416
|
+
if (renamed.length > 0) {
|
|
417
|
+
console.log(` ${renamed.length} token${renamed.length === 1 ? "" : "s"} you renamed since the last install: ${renamed
|
|
418
|
+
.slice(0, 3)
|
|
419
|
+
.map((r) => `${r.theirs} → ${theirVars.pairs.find((p) => p.ours === r.ours)?.theirs ?? "?"}`)
|
|
420
|
+
.join(", ")}${renamed.length > 3 ? ", …" : ""}. The system follows the new name from here.`);
|
|
421
|
+
}
|
|
397
422
|
if (theirVars.pointed > 0)
|
|
398
423
|
console.log(` ${theirVars.pointed} value${theirVars.pointed === 1 ? "" : "s"} in tokens.css now point at the name YOUR code already gives ${theirVars.pointed === 1 ? "it" : "them"} - change yours and the system follows${theirVars.pruned > 0 ? `; ${theirVars.pruned} matched but your build does not emit ${theirVars.pruned === 1 ? "that name" : "those names"}, so ${theirVars.pruned === 1 ? "it keeps" : "they keep"} the value` : ""}`);
|
|
399
424
|
/**
|
|
@@ -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 { inTheirTongue, tongueOf } from "../their-tongue.js";
|
|
11
12
|
import { readCensus, unreadComment, unreadForComponent, } from "../unread-for-component.js";
|
|
12
13
|
/**
|
|
13
14
|
* Writes the shared `cn.ts` next to the components, built from THIS project's
|
|
@@ -65,12 +66,41 @@ export async function component(slug, name, opts) {
|
|
|
65
66
|
// o .tsx é materializado logo abaixo, ninguém as lê depois (medido na
|
|
66
67
|
// auditoria de 16/08), e cada arquivo a mais no repo dele é superfície.
|
|
67
68
|
const config = await readProjectConfig(root);
|
|
69
|
+
/**
|
|
70
|
+
* O CSS PASSA A FALAR A LÍNGUA DELE - ver `inTheirTongue`.
|
|
71
|
+
*
|
|
72
|
+
* O QUE MUDA NO ARQUIVO QUE ELE ABRE: `color: var(--ds-color-ink-900)` vira
|
|
73
|
+
* `color: var(--color-ink-900)`, o nome que o código dele já dá àquele valor. É isso que faz a
|
|
74
|
+
* nossa folha de 3945 linhas deixar de ser necessária no repositório de origem - o `globals.css`
|
|
75
|
+
* dele já declara o token, e a decisão continua sendo dele.
|
|
76
|
+
*
|
|
77
|
+
* AQUI E NÃO NO SERVIDOR, que é onde o CSS é compilado: o mapa descreve ESTE repositório. O mesmo
|
|
78
|
+
* sistema instalado em outro projeto dele não tem os mesmos nomes declarados, e traduzir lá
|
|
79
|
+
* apagaria a cor. Um projeto de destino chega sem mapa no `.lock`, nada é traduzido, e a folha
|
|
80
|
+
* continua sendo o caminho - o comando DIZ qual dos dois aconteceu.
|
|
81
|
+
*/
|
|
82
|
+
const tongue = await tongueFor(root, slug);
|
|
83
|
+
const spoken = tongue ? inTheirTongue(res.css, tongue) : null;
|
|
84
|
+
const css = spoken ? spoken.css : res.css;
|
|
85
|
+
/**
|
|
86
|
+
* E ELE FICA SABENDO - traduzir em silêncio é a outra metade do mesmo erro.
|
|
87
|
+
*
|
|
88
|
+
* A última linha é a que decide se a nossa folha ainda é necessária aqui, e ela é a diferença
|
|
89
|
+
* entre "seu repositório dispensa a folha" e "quase" - dizer a primeira quando a verdade é a
|
|
90
|
+
* segunda é a promessa que quebra na tela dele.
|
|
91
|
+
*/
|
|
92
|
+
if (spoken && (spoken.named > 0 || spoken.inlined > 0)) {
|
|
93
|
+
console.log(` ${spoken.named} reference${spoken.named === 1 ? "" : "s"} now speak${spoken.named === 1 ? "s" : ""} the name YOUR code gives the value${spoken.inlined > 0 ? `, and ${spoken.inlined} carr${spoken.inlined === 1 ? "ies" : "y"} the value because your code names no token for it` : ""}.`);
|
|
94
|
+
console.log(spoken.left.length === 0
|
|
95
|
+
? ` Nothing in this file points at our stylesheet - it renders on your own tokens alone.`
|
|
96
|
+
: ` ${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 is still needed here.`);
|
|
97
|
+
}
|
|
68
98
|
const artifactsAreTheProduct = opts.artifactsOnly === true || config.target !== "next";
|
|
69
99
|
if (artifactsAreTheProduct) {
|
|
70
100
|
const dir = join(root, "_synthesisui", "ds", slug, "components");
|
|
71
101
|
await mkdir(dir, { recursive: true });
|
|
72
102
|
await writeFile(join(dir, `${res.name}.json`), `${JSON.stringify(res.recipe, null, 2)}\n`, "utf8");
|
|
73
|
-
await writeFile(join(dir, `${res.name}.css`), `${
|
|
103
|
+
await writeFile(join(dir, `${res.name}.css`), `${css}\n`, "utf8");
|
|
74
104
|
console.log(`✓ ${res.name} → _synthesisui/ds/${slug}/components/${res.name}.{json,css} (${slug} v${res.version})`);
|
|
75
105
|
}
|
|
76
106
|
// 2. YOUR component - a real, importable `export function <Pascal>()` in the
|
|
@@ -151,12 +181,12 @@ export async function component(slug, name, opts) {
|
|
|
151
181
|
// template drives itself off the .ds-* classes.
|
|
152
182
|
const tsx = interactiveTemplate(res.name);
|
|
153
183
|
await writeFile(join(compDir, `${res.name}.tsx`), tsx, "utf8");
|
|
154
|
-
await writeFile(join(compDir, `${res.name}.css`), `${
|
|
184
|
+
await writeFile(join(compDir, `${res.name}.css`), `${css}\n`, "utf8");
|
|
155
185
|
await writeFile(join(compDir, "index.ts"), `export * from "./${res.name}";\n`, "utf8");
|
|
156
186
|
filenames = [`${res.name}.tsx`, `${res.name}.css`, "index.ts"];
|
|
157
187
|
}
|
|
158
188
|
else {
|
|
159
|
-
const files = generateComponentFiles(slug, res.name, res.recipe,
|
|
189
|
+
const files = generateComponentFiles(slug, res.name, res.recipe, css, res.version, config.styles, await reactMajorOf(root),
|
|
160
190
|
/**
|
|
161
191
|
* THE SPELLING, FROM THE VERSION WE JUST FETCHED.
|
|
162
192
|
*
|
|
@@ -246,3 +276,35 @@ export async function component(slug, name, opts) {
|
|
|
246
276
|
}
|
|
247
277
|
console.log("");
|
|
248
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* O VOCABULÁRIO DESTE REPOSITÓRIO, do que o `add` já deixou na pasta - ver `tongueOf`.
|
|
281
|
+
*
|
|
282
|
+
* `null` quando não há mapa: é a resposta de um projeto de destino, de um repositório que nunca
|
|
283
|
+
* buildou, ou de um install feito por um CLI anterior a 0.16.290. Nos três casos nada é traduzido e
|
|
284
|
+
* a folha instalada continua sendo o caminho, que é o comportamento de sempre.
|
|
285
|
+
*/
|
|
286
|
+
async function tongueFor(root, slug) {
|
|
287
|
+
const dir = join(root, "_synthesisui", "ds", slug);
|
|
288
|
+
const lock = await readFile(join(dir, ".lock"), "utf8").catch(() => null);
|
|
289
|
+
if (!lock)
|
|
290
|
+
return null;
|
|
291
|
+
let map = [];
|
|
292
|
+
let version = 0;
|
|
293
|
+
try {
|
|
294
|
+
const parsed = JSON.parse(lock);
|
|
295
|
+
map = parsed.tokenMap ?? [];
|
|
296
|
+
version = parsed.version ?? 0;
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
if (map.length === 0)
|
|
302
|
+
return null;
|
|
303
|
+
/**
|
|
304
|
+
* O VALOR COMPILADO MORA NA PASTA DA VERSÃO - a folha da raiz é um re-export de uma linha, escrito
|
|
305
|
+
* assim de propósito para que o `@import` dele nunca mude entre updates (ver `add.ts`). É de lá
|
|
306
|
+
* que sai o literal para as variáveis que não têm nome dele.
|
|
307
|
+
*/
|
|
308
|
+
const installed = await readFile(join(dir, `v${version}`, "tokens.css"), "utf8").catch(() => "");
|
|
309
|
+
return tongueOf(map, installed);
|
|
310
|
+
}
|
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.290";
|
|
132
132
|
/**
|
|
133
133
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
134
134
|
*
|
package/dist/repo-state.js
CHANGED
|
@@ -63,6 +63,15 @@ export async function repoStateOf(projectRoot, slug, cli) {
|
|
|
63
63
|
const payload = {
|
|
64
64
|
...(typeof lock?.version === "number" ? { installed: lock.version } : {}),
|
|
65
65
|
...(lock?.adopted === true ? { adopted: true } : {}),
|
|
66
|
+
/**
|
|
67
|
+
* O MAPA, do `.lock` - ver `RepoStatePayload.tokenMap`.
|
|
68
|
+
*
|
|
69
|
+
* Lido e não recalculado: o cruzamento é caro (varre as folhas dele e o build) e já foi feito
|
|
70
|
+
* pelo `add`, que é quem tinha o payload em mãos. Aqui ele só viaja.
|
|
71
|
+
*/
|
|
72
|
+
...(lock?.tokenMap && lock.tokenMap.length > 0
|
|
73
|
+
? { tokenMap: lock.tokenMap }
|
|
74
|
+
: {}),
|
|
66
75
|
cli,
|
|
67
76
|
...(doctor?.at ? { doctorAt: doctor.at } : {}),
|
|
68
77
|
...(typeof doctor?.phantoms === "number"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** `var(--ds-x)` e `var(--ds-x, fallback)` - o que a folha compilada escreve. */
|
|
2
|
+
const OUR_VAR = /var\(\s*(--ds-[a-zA-Z0-9_-]+)\s*(?:,([^()]*))?\)/g;
|
|
3
|
+
export function inTheirTongue(css, tongue) {
|
|
4
|
+
let named = 0;
|
|
5
|
+
let inlined = 0;
|
|
6
|
+
const left = new Set();
|
|
7
|
+
const out = css.replace(OUR_VAR, (whole, name, fallback) => {
|
|
8
|
+
const theirName = tongue.names.get(name);
|
|
9
|
+
if (theirName) {
|
|
10
|
+
named += 1;
|
|
11
|
+
/**
|
|
12
|
+
* O FALLBACK NÃO VIAJA. `var(--x, #valor)` sobreviveria a um rename DELE pintando o valor
|
|
13
|
+
* velho - a plataforma decidindo por cima da decisão dele. Se ele renomear, quebra, e o
|
|
14
|
+
* `doctor` diz qual (dono, 22/08).
|
|
15
|
+
*/
|
|
16
|
+
return `var(${theirName})`;
|
|
17
|
+
}
|
|
18
|
+
const value = tongue.values.get(name);
|
|
19
|
+
if (value) {
|
|
20
|
+
inlined += 1;
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
left.add(name);
|
|
24
|
+
return fallback ? whole : whole;
|
|
25
|
+
});
|
|
26
|
+
return { css: out, named, inlined, left: [...left].sort() };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* O VOCABULÁRIO DESTE REPOSITÓRIO, montado do que o `add` já deixou na pasta.
|
|
30
|
+
*
|
|
31
|
+
* `map` vem do `.lock` (`tokenMap`) e é o que a máquina dele descobriu; `declared` vem do
|
|
32
|
+
* `tokens.css` instalado, que é onde cada variável nossa tem o valor compilado. Nenhuma medição
|
|
33
|
+
* nova: as duas coisas já estão no disco.
|
|
34
|
+
*/
|
|
35
|
+
export function tongueOf(map, installedCss) {
|
|
36
|
+
const names = new Map(map.map((p) => [p.ours, p.theirs]));
|
|
37
|
+
const values = new Map();
|
|
38
|
+
for (const m of installedCss.matchAll(/^\s*(--ds-[a-zA-Z0-9_-]+)\s*:\s*([^;]+);/gm)) {
|
|
39
|
+
const value = m[2].trim();
|
|
40
|
+
/** Uma linha que aponta para outra variável não é um valor - seguir a cadeia é do navegador. */
|
|
41
|
+
if (value.includes("var("))
|
|
42
|
+
continue;
|
|
43
|
+
if (!values.has(m[1]))
|
|
44
|
+
values.set(m[1], value);
|
|
45
|
+
}
|
|
46
|
+
return { names, values };
|
|
47
|
+
}
|
package/dist/their-vars.js
CHANGED
|
@@ -95,12 +95,18 @@ export async function resolvableVars(root) {
|
|
|
95
95
|
* ou `null` quando não há build - e aí nada é reescrito.
|
|
96
96
|
*/
|
|
97
97
|
export function pointAtTheirNames(css, theirs, resolvable) {
|
|
98
|
+
/**
|
|
99
|
+
* SEM BUILD, SEM MAPA - a mesma regra da reescrita, e pelo mesmo motivo: um repositório que nunca
|
|
100
|
+
* buildou não tem como provar o que resolve, e um par afirmado sem prova é pior que par nenhum.
|
|
101
|
+
*/
|
|
98
102
|
if (!resolvable || theirs.byName.size === 0)
|
|
99
|
-
return { css, pointed: 0, pruned: 0 };
|
|
103
|
+
return { css, pointed: 0, pruned: 0, pairs: [] };
|
|
100
104
|
const ours = buildTable({ css, source: "installed" });
|
|
101
105
|
const alias = theirNames(ours, theirs);
|
|
102
106
|
let pointed = 0;
|
|
103
107
|
let pruned = 0;
|
|
108
|
+
/** Ver `PointedAt.pairs`: o mapa sai do mesmo casamento que reescreve a linha. */
|
|
109
|
+
const pairs = [];
|
|
104
110
|
const out = css.replace(/^(\s*)(--ds-[a-zA-Z0-9_-]+)(\s*:\s*)([^;]+);/gm, (line, indent, name, sep, value) => {
|
|
105
111
|
/** Uma linha que já aponta para outra variável não é uma cópia de valor. */
|
|
106
112
|
if (value.includes("var("))
|
|
@@ -116,9 +122,10 @@ export function pointAtTheirNames(css, theirs, resolvable) {
|
|
|
116
122
|
return line;
|
|
117
123
|
}
|
|
118
124
|
pointed += 1;
|
|
125
|
+
pairs.push({ ours: name, theirs: theirName, value: value.trim() });
|
|
119
126
|
return `${indent}${name}${sep}var(${theirName});`;
|
|
120
127
|
});
|
|
121
|
-
return { css: out, pointed, pruned };
|
|
128
|
+
return { css: out, pointed, pruned, pairs };
|
|
122
129
|
}
|
|
123
130
|
/**
|
|
124
131
|
* O PREFIXO DA NOSSA VARIÁVEL -> A FAMÍLIA, e a chave que `theirNames` devolve é `<família>:<valor>`.
|
|
@@ -142,7 +149,7 @@ const FAMILY_KIND = {
|
|
|
142
149
|
export async function pointTokensAtTheirNames(root, payload) {
|
|
143
150
|
const css = payload.artifacts["tokens.css"] ?? "";
|
|
144
151
|
if (!css)
|
|
145
|
-
return { css, pointed: 0, pruned: 0 };
|
|
152
|
+
return { css, pointed: 0, pruned: 0, pairs: [] };
|
|
146
153
|
const [theirs, resolvable] = await Promise.all([
|
|
147
154
|
harvestTheirCss(root),
|
|
148
155
|
resolvableVars(root),
|
package/package.json
CHANGED