synthesisui 0.16.286 → 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/anatomy-read.js +20 -5
- package/dist/commands/add.js +25 -0
- package/dist/commands/component.js +65 -3
- package/dist/commands/import.js +92 -16
- package/dist/doctor/broken-refs.js +15 -1
- package/dist/doctor/component-gate.js +59 -2
- package/dist/doctor/css-modules.js +77 -47
- package/dist/doctor/definitions-scan.js +59 -3
- package/dist/doctor/transcribe.js +9 -51
- package/dist/doctor/variant-read.js +31 -5
- package/dist/global-wear.js +44 -0
- package/dist/install-marks.js +2 -2
- package/dist/namespace-pairs.js +80 -0
- package/dist/not-expressed.js +76 -0
- package/dist/repo-state.js +9 -0
- package/dist/their-tongue.js +47 -0
- package/dist/their-vars.js +10 -3
- package/dist/token-ref.js +93 -0
- package/package.json +1 -1
package/dist/anatomy-read.js
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
import { readSxProps } from "./doctor/style-props.js";
|
|
33
33
|
import { transcribe, } from "./doctor/transcribe.js";
|
|
34
34
|
import { frontierKind, packageRoot } from "./frontier-kind.js";
|
|
35
|
+
import { withGlobal } from "./global-wear.js";
|
|
35
36
|
/** The ten forms, closed. A renderer that accepts any tag draws anything. */
|
|
36
37
|
const FORMS = new Set([
|
|
37
38
|
"image",
|
|
@@ -252,7 +253,18 @@ sketch,
|
|
|
252
253
|
* A escala de espaçamento que o projeto DELES declara para o `sx`. Ausente = o default
|
|
253
254
|
* documentado do MUI, que é fato da biblioteca que eles escolheram - ver `spacingOf`.
|
|
254
255
|
*/
|
|
255
|
-
sxSpacing
|
|
256
|
+
sxSpacing,
|
|
257
|
+
/**
|
|
258
|
+
* AS CLASSES DA FOLHA GLOBAL DELE - ver `Census.globalClasses` e `wearGlobal`.
|
|
259
|
+
*
|
|
260
|
+
* O QUE ISSO ALCANÇA QUE NADA MAIS ALCANÇAVA: uma utility que ele escreve num NÓ FILHO. O
|
|
261
|
+
* `LeaderboardRow` do `codelevel-ui` põe `text-grad-xp` num `<span>` dentro de um ternário, e o
|
|
262
|
+
* `QuestItem` põe `text-grad` numa tabela que pertence ao filho - as duas passam por aqui, e não
|
|
263
|
+
* pela raiz nem pela tabela de variantes do componente.
|
|
264
|
+
*
|
|
265
|
+
* Ausente é a resposta de quem não tem folha global, e nesse caso nada muda.
|
|
266
|
+
*/
|
|
267
|
+
globals) {
|
|
256
268
|
/**
|
|
257
269
|
* THE PACKAGE THE RETURNED ELEMENT CAME FROM, measured rather than spelled.
|
|
258
270
|
* Node 0 of the sketch IS what the component returns, and the reader stamped its
|
|
@@ -474,7 +486,7 @@ sxSpacing) {
|
|
|
474
486
|
? (sketch?.[node.at]?.classes ?? "")
|
|
475
487
|
: "";
|
|
476
488
|
const classes = raw.split(/\s+/).filter(Boolean);
|
|
477
|
-
const t = transcribe(classes, declared);
|
|
489
|
+
const t = withGlobal(transcribe(classes, declared), classes, globals);
|
|
478
490
|
/**
|
|
479
491
|
* A PROP DE ESTILO DESTE NÓ, sob a classe dele.
|
|
480
492
|
*
|
|
@@ -618,7 +630,7 @@ sxSpacing) {
|
|
|
618
630
|
* otherwise. So a drawn root sends nothing here at all.
|
|
619
631
|
*/
|
|
620
632
|
const rootStyle = rootPainted
|
|
621
|
-
? transcribe(rootPainted.split(/\s+/).filter(Boolean), declared)
|
|
633
|
+
? withGlobal(transcribe(rootPainted.split(/\s+/).filter(Boolean), declared), rootPainted.split(/\s+/).filter(Boolean), globals)
|
|
622
634
|
: null;
|
|
623
635
|
const paints = rootStyle && Object.keys(rootStyle.base).length > 0
|
|
624
636
|
? { style: rootStyle.base }
|
|
@@ -710,13 +722,16 @@ sxSpacing) {
|
|
|
710
722
|
* correctly, and the absence of a tree means the preview lays them out in a row
|
|
711
723
|
* exactly as it did before.
|
|
712
724
|
*/
|
|
713
|
-
export function resolveFlatParts(read, declared
|
|
725
|
+
export function resolveFlatParts(read, declared,
|
|
726
|
+
/** As classes da folha global dele - ver `wearGlobal`. */
|
|
727
|
+
globals) {
|
|
714
728
|
const parts = {};
|
|
715
729
|
for (const part of read) {
|
|
716
730
|
const name = safePartName(String(part?.name ?? ""));
|
|
717
731
|
if (!name || typeof part.classes !== "string")
|
|
718
732
|
continue;
|
|
719
|
-
const
|
|
733
|
+
const partClasses = part.classes.split(/\s+/).filter(Boolean);
|
|
734
|
+
const t = withGlobal(transcribe(partClasses, declared), partClasses, globals);
|
|
720
735
|
parts[name] = { base: t.base, dark: t.dark, states: t.states };
|
|
721
736
|
}
|
|
722
737
|
return { parts, tree: [], composes: [], external: [], notes: [], partAt: {} };
|
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/commands/import.js
CHANGED
|
@@ -40,7 +40,9 @@ import { keyframesInSheets } from "../global-keyframes.js";
|
|
|
40
40
|
import { withLibraryStructure } from "../library-structure.js";
|
|
41
41
|
import { mergeCensus } from "../merge-census.js";
|
|
42
42
|
import { claimName } from "../name-claim.js";
|
|
43
|
+
import { mergeNamespacePairs } from "../namespace-pairs.js";
|
|
43
44
|
import { namingQueue } from "../naming-queue.js";
|
|
45
|
+
import { notExpressed } from "../not-expressed.js";
|
|
44
46
|
import { body, paint, section } from "../output.js";
|
|
45
47
|
import { outsideScope } from "../outside-scope.js";
|
|
46
48
|
import { phase, startProgress } from "../progress.js";
|
|
@@ -367,6 +369,8 @@ function screenOf(rel) {
|
|
|
367
369
|
const after = parts.slice(i + 1).filter((p) => !p.startsWith("("));
|
|
368
370
|
return `${parts[i]}/${after.slice(0, 2).join("/")}`;
|
|
369
371
|
}
|
|
372
|
+
/** Quantas classes da folha global viajam no censo. Ver o aviso em `takeCensus`. */
|
|
373
|
+
const MAX_GLOBAL_CLASSES = 300;
|
|
370
374
|
export async function takeCensus(root, opts) {
|
|
371
375
|
/**
|
|
372
376
|
* O RELATÓRIO SÓ SAI QUANDO ALGUÉM O PEDIU - `say` cala quando o chamador é uma RE-medição.
|
|
@@ -450,6 +454,17 @@ export async function takeCensus(root, opts) {
|
|
|
450
454
|
* porque é isso que o juiz do ledger precisa para chamar a classe e a regra de LIDAS.
|
|
451
455
|
*/
|
|
452
456
|
const globalClasses = readGlobalClasses(globalSheets, declaredValues);
|
|
457
|
+
/**
|
|
458
|
+
* TETO DAS CLASSES GLOBAIS QUE VIAJAM NO CENSO - e quando ele corta, ele fala.
|
|
459
|
+
*
|
|
460
|
+
* Medido nas três populações: 24 classes na folha do `codelevel-ui`, 22 na nossa, 0 no
|
|
461
|
+
* `apps/web-dashboard` do repo real, que é CSS Modules. O teto é para a folha de um app que
|
|
462
|
+
* escreva utilitário por atacado, e existe porque um censo que dobra de tamanho em silêncio é o
|
|
463
|
+
* tipo de surpresa que ninguém pede.
|
|
464
|
+
*/
|
|
465
|
+
if (globalClasses.size > MAX_GLOBAL_CLASSES) {
|
|
466
|
+
console.log(body(paint.faint(`${globalClasses.size} classes in your global sheet, and ${globalClasses.size - MAX_GLOBAL_CLASSES} of them are not travelling with this census - the first ${MAX_GLOBAL_CLASSES} are.`)));
|
|
467
|
+
}
|
|
453
468
|
/**
|
|
454
469
|
* OS KEYFRAMES DA FOLHA DELE, colhidos onde as folhas globais já estão na mão.
|
|
455
470
|
*
|
|
@@ -735,16 +750,18 @@ export async function takeCensus(root, opts) {
|
|
|
735
750
|
/**
|
|
736
751
|
* Ver `CensusLook.aside` - o segundo componente do arquivo também.
|
|
737
752
|
*
|
|
738
|
-
*
|
|
739
|
-
* componente do arquivo,
|
|
740
|
-
*
|
|
741
|
-
* `
|
|
753
|
+
* E COM OS EIXOS QUE O TIPO DELE DECLARA. `transcribeVariants` roda para o primeiro
|
|
754
|
+
* componente do arquivo, então aqui não há tabela de variantes - mas `extra.axes` é a
|
|
755
|
+
* declaração da interface, lida por `scanDefinitions` para TODA definição do arquivo.
|
|
756
|
+
* Era `undefined`, e isso pôs de lado o `ToastIcon` dele: terceiro componente do
|
|
757
|
+
* `Toast.tsx`, cinco tons declarados no tipo, um gradiente para cada. Ver
|
|
758
|
+
* `classifyAside`.
|
|
742
759
|
*/
|
|
743
|
-
...(classifyAside({ name: extra.name, declaredAxes:
|
|
760
|
+
...(classifyAside({ name: extra.name, declaredAxes: extra.axes })
|
|
744
761
|
? {
|
|
745
762
|
aside: classifyAside({
|
|
746
763
|
name: extra.name,
|
|
747
|
-
declaredAxes:
|
|
764
|
+
declaredAxes: extra.axes,
|
|
748
765
|
}),
|
|
749
766
|
}
|
|
750
767
|
: {}),
|
|
@@ -782,7 +799,7 @@ export async function takeCensus(root, opts) {
|
|
|
782
799
|
* for the result, which is the "valid and unread" failure this pipeline has
|
|
783
800
|
* paid for twice already.
|
|
784
801
|
*/
|
|
785
|
-
const v = transcribeVariants(src, declaredValues);
|
|
802
|
+
const v = transcribeVariants(src, declaredValues, globalClasses);
|
|
786
803
|
/**
|
|
787
804
|
* THE RESTING OPTION, from the definition itself. `cva` says it in
|
|
788
805
|
* `defaultVariants`; a lookup-record component says it in the destructuring -
|
|
@@ -1040,14 +1057,20 @@ export async function takeCensus(root, opts) {
|
|
|
1040
1057
|
/**
|
|
1041
1058
|
* Ver `CensusLook.aside`: o fato viaja, a regra fica de um lado só.
|
|
1042
1059
|
*
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1060
|
+
* AS DUAS FONTES DE EIXO, e é a mesma decisão do autor nas duas. `found[0].axes` é o que
|
|
1061
|
+
* o TIPO declara (`interface ToastIconProps { tone?: "gold" | "cool" }`); `v.axes` é o
|
|
1062
|
+
* que a TABELA declara (`cva({ variants: … })`). Consultar só a segunda pôs de lado um
|
|
1063
|
+
* componente que pinta por `Record<tone, gradiente>` - forma que a tabela não vê.
|
|
1064
|
+
* Ver `classifyAside`.
|
|
1045
1065
|
*/
|
|
1046
|
-
...(classifyAside({
|
|
1066
|
+
...(classifyAside({
|
|
1067
|
+
name: found[0].name,
|
|
1068
|
+
declaredAxes: { ...found[0].axes, ...v.axes },
|
|
1069
|
+
})
|
|
1047
1070
|
? {
|
|
1048
1071
|
aside: classifyAside({
|
|
1049
1072
|
name: found[0].name,
|
|
1050
|
-
declaredAxes: v.axes,
|
|
1073
|
+
declaredAxes: { ...found[0].axes, ...v.axes },
|
|
1051
1074
|
}),
|
|
1052
1075
|
}
|
|
1053
1076
|
: {}),
|
|
@@ -1864,7 +1887,14 @@ export async function takeCensus(root, opts) {
|
|
|
1864
1887
|
list.push(kebab(pair.child));
|
|
1865
1888
|
companionsOf.set(pair.component, list);
|
|
1866
1889
|
}
|
|
1867
|
-
|
|
1890
|
+
/**
|
|
1891
|
+
* `Modal.Header` E `ModalHeader` CONTADOS UMA VEZ - ver `mergeNamespacePairs`.
|
|
1892
|
+
*
|
|
1893
|
+
* Antes do enriquecimento porque veredito, leis e companheiros são indexados por NOME: fundir
|
|
1894
|
+
* depois deixaria metade do que se sabe sobre a peça pendurado no nome que deixou de existir.
|
|
1895
|
+
*/
|
|
1896
|
+
const paired = mergeNamespacePairs(merged, new Set(defined.map((d) => d.name)));
|
|
1897
|
+
const components = paired.map((c) => ({
|
|
1868
1898
|
...c,
|
|
1869
1899
|
...(verdicts.get(idOf(c)) ?? {}),
|
|
1870
1900
|
...(laws.has(c.name) ? { laws: laws.get(c.name) } : {}),
|
|
@@ -2071,6 +2101,25 @@ export async function takeCensus(root, opts) {
|
|
|
2071
2101
|
}
|
|
2072
2102
|
: {}),
|
|
2073
2103
|
...(conventions.length > 0 ? { conventions } : {}),
|
|
2104
|
+
/**
|
|
2105
|
+
* AS CLASSES DA FOLHA GLOBAL DELE - ver `Census.globalClasses`.
|
|
2106
|
+
*
|
|
2107
|
+
* TODAS AS QUE A FOLHA DECLARA, e não só as que a raiz vestiu. A primeira tentativa levou apenas
|
|
2108
|
+
* as reivindicadas por `globalWear`, que só roda na raiz do componente - e as utilities usadas
|
|
2109
|
+
* numa tabela de variantes ou num nó filho, que são a maioria, ficavam de fora justamente do
|
|
2110
|
+
* campo que existe para alcançá-las. Duas de 24 chegavam.
|
|
2111
|
+
*
|
|
2112
|
+
* O CUSTO ESTÁ MEDIDO: a folha do `codelevel-ui` declara 24 classes, a nossa 22, e o
|
|
2113
|
+
* `apps/web-dashboard` do repo real declara 0 - ele é CSS Modules. Nas três populações isso é
|
|
2114
|
+
* ruído no tamanho do censo. O teto de `MAX_GLOBAL_CLASSES` existe para a folha de um app que
|
|
2115
|
+
* escreva utilitário por atacado, e quando ele corta, o número cortado é DITO em vez de
|
|
2116
|
+
* desaparecer.
|
|
2117
|
+
*/
|
|
2118
|
+
...(globalClasses.size > 0
|
|
2119
|
+
? {
|
|
2120
|
+
globalClasses: Object.fromEntries([...globalClasses.entries()].slice(0, MAX_GLOBAL_CLASSES)),
|
|
2121
|
+
}
|
|
2122
|
+
: {}),
|
|
2074
2123
|
classStyle,
|
|
2075
2124
|
...(Object.keys(looks).length > 0 ? { looks } : {}),
|
|
2076
2125
|
...(naming.components > 0 ? { naming } : {}),
|
|
@@ -3166,6 +3215,16 @@ export function rootBehaviour(tag, sketch) {
|
|
|
3166
3215
|
export async function resolveReadParts(census, root,
|
|
3167
3216
|
/** `true` numa re-medição: o resumo desta etapa fica para o `sync --full` - ver `say`. */
|
|
3168
3217
|
quiet = false) {
|
|
3218
|
+
/**
|
|
3219
|
+
* AS CLASSES DA FOLHA GLOBAL DELE, DO CENSO - ver `Census.globalClasses`.
|
|
3220
|
+
*
|
|
3221
|
+
* Esta etapa roda depois da medição, e também no `sync` e do lado da plataforma, sobre censo já
|
|
3222
|
+
* guardado - então ela não tem como voltar à folha no disco. Vindo do censo, uma utility que ele
|
|
3223
|
+
* escreve num NÓ FILHO chega ao look: o `LeaderboardRow` põe `text-grad-xp` num `<span>` dentro de
|
|
3224
|
+
* um ternário, e é aqui que aquele nó é lido.
|
|
3225
|
+
*/
|
|
3226
|
+
const globalClasses = new Map(Object.entries(census
|
|
3227
|
+
.globalClasses ?? {}));
|
|
3169
3228
|
const say = quiet
|
|
3170
3229
|
? () => { }
|
|
3171
3230
|
: (line) => {
|
|
@@ -3278,13 +3337,13 @@ quiet = false) {
|
|
|
3278
3337
|
? resolveAnatomy(framed, declared, deps, resolveRef, entry?.root,
|
|
3279
3338
|
// The component's own sketch, so a node naming itself by index reads
|
|
3280
3339
|
// the exact class string the census measured (dono, 01/08).
|
|
3281
|
-
looks[component]?.sketch, census.sxSpacing)
|
|
3340
|
+
looks[component]?.sketch, census.sxSpacing, globalClasses)
|
|
3282
3341
|
: patched
|
|
3283
|
-
? resolveAnatomy(patched.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing)
|
|
3342
|
+
? resolveAnatomy(patched.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing, globalClasses)
|
|
3284
3343
|
: derived && derived.read.length > 0
|
|
3285
|
-
? resolveAnatomy(derived.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing)
|
|
3344
|
+
? resolveAnatomy(derived.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing, globalClasses)
|
|
3286
3345
|
: Array.isArray(entry?.parts) && entry.parts.length > 0
|
|
3287
|
-
? resolveFlatParts(entry.parts, declared)
|
|
3346
|
+
? resolveFlatParts(entry.parts, declared, globalClasses)
|
|
3288
3347
|
: null;
|
|
3289
3348
|
if (!resolved)
|
|
3290
3349
|
continue;
|
|
@@ -3699,6 +3758,23 @@ export async function runImport(opts) {
|
|
|
3699
3758
|
const out = opts.census ?? join(root, "_synthesisui", "census.json");
|
|
3700
3759
|
await mkdir(dirname(out), { recursive: true });
|
|
3701
3760
|
await writeFile(out, `${JSON.stringify(census, null, 2)}\n`, "utf8");
|
|
3761
|
+
/**
|
|
3762
|
+
* O QUE NÃO CHEGOU, DECLARADO PELA ESTEIRA - ver `notExpressed`.
|
|
3763
|
+
*
|
|
3764
|
+
* Escrito ao lado do censo e na mesma rodada, porque a lacuna e a medição são a mesma verdade
|
|
3765
|
+
* vista dos dois lados. Era o agente que escrevia este arquivo, instruído por uma frase dentro da
|
|
3766
|
+
* descrição de uma tool - e em 23/08 o mesmo repositório foi importado duas vezes, a primeira
|
|
3767
|
+
* declarando 37 linhas de lacuna e a segunda nenhuma, com as lacunas todas no lugar. Um pedido a
|
|
3768
|
+
* um modelo não é um artefato.
|
|
3769
|
+
*
|
|
3770
|
+
* O agente ainda ACRESCENTA o que só ele vê. O piso deixou de depender dele.
|
|
3771
|
+
*/
|
|
3772
|
+
const gaps = notExpressed(census);
|
|
3773
|
+
if (gaps) {
|
|
3774
|
+
const gapFile = join(dirname(out), "not-expressed.md");
|
|
3775
|
+
await writeFile(gapFile, gaps, "utf8");
|
|
3776
|
+
console.log(body(`Gaps written to ${paint.strong(relative(root, gapFile))}`));
|
|
3777
|
+
}
|
|
3702
3778
|
console.log("");
|
|
3703
3779
|
console.log(body(`Written to ${paint.strong(relative(root, out))}`));
|
|
3704
3780
|
if (opts.dry) {
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { frontierKind } from "../frontier-kind.js";
|
|
21
21
|
import { importMap } from "./imports.js";
|
|
22
|
+
/** Quantos lugares viajam por referência quebrada. Ver `BrokenRef.at`. */
|
|
23
|
+
const MAX_PLACES = 3;
|
|
22
24
|
/** `var(--x)`, including inside a Tailwind arbitrary value: `bg-[var(--x)]`. */
|
|
23
25
|
const VAR_REF = /var\(\s*(--[a-zA-Z0-9_-]+)/g;
|
|
24
26
|
/**
|
|
@@ -96,9 +98,20 @@ export function findBrokenRefs(sources, declared) {
|
|
|
96
98
|
continue;
|
|
97
99
|
if (runtime && (RUNTIME_ANCHOR.has(name) || RUNTIME_PREFIX.test(name)))
|
|
98
100
|
continue;
|
|
99
|
-
const hit = seen.get(name) ?? {
|
|
101
|
+
const hit = seen.get(name) ?? {
|
|
102
|
+
count: 0,
|
|
103
|
+
files: new Set(),
|
|
104
|
+
at: [],
|
|
105
|
+
};
|
|
100
106
|
hit.count += 1;
|
|
101
107
|
hit.files.add(file);
|
|
108
|
+
/** A linha contada do índice do match - ver `BrokenRef.at` para o teto. */
|
|
109
|
+
if (hit.at.length < MAX_PLACES) {
|
|
110
|
+
hit.at.push({
|
|
111
|
+
file,
|
|
112
|
+
line: source.slice(0, m.index ?? 0).split("\n").length,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
102
115
|
seen.set(name, hit);
|
|
103
116
|
}
|
|
104
117
|
}
|
|
@@ -111,6 +124,7 @@ export function findBrokenRefs(sources, declared) {
|
|
|
111
124
|
count: hit.count,
|
|
112
125
|
files: hit.files.size,
|
|
113
126
|
...(meant ? { meant } : {}),
|
|
127
|
+
...(hit.at.length > 0 ? { at: hit.at } : {}),
|
|
114
128
|
});
|
|
115
129
|
}
|
|
116
130
|
// A broken reference somebody typed nine times is worse than one typed once,
|
|
@@ -61,6 +61,8 @@ const PROVIDER_SUFFIX = /(Provider|Providers|Context|Boundary|Guard|Wrapper)$/;
|
|
|
61
61
|
/** Any JSX at all in the file. A capitalised export with none is a constant, a
|
|
62
62
|
* config object or a helper - `export const ROUTES = {…}` is not a component. */
|
|
63
63
|
const HAS_JSX = /<[A-Za-z][^>]*>|<>/;
|
|
64
|
+
/** `ICON_SIZE`, `AURORA_PALETTES`, `ARTICLE_TAB` - a convenção universal para valor, não para peça. */
|
|
65
|
+
const SCREAMING_SNAKE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$/;
|
|
64
66
|
/**
|
|
65
67
|
* Any sign that this file decides how something LOOKS.
|
|
66
68
|
*
|
|
@@ -71,7 +73,30 @@ const HAS_JSX = /<[A-Za-z][^>]*>|<>/;
|
|
|
71
73
|
const HAS_STYLING = /className|class=|style=|styled[.(]|css`|cva\(|\btv\(|makeStyles|sx=|tw`/;
|
|
72
74
|
/** `createContext` + a `.Provider` in the return is a provider whatever it is
|
|
73
75
|
* called - `RootWrapper` and `CommonProviders` both read this way. */
|
|
74
|
-
|
|
76
|
+
/**
|
|
77
|
+
* QUEM RECEBE O `createContext` - o nome, e não o arquivo em volta dele.
|
|
78
|
+
*
|
|
79
|
+
* O QUE O CLIENTE PERDIA: o `Tabs` inteiro. `Tabs.tsx` declara `const TabsCtx = createContext(…)`, e
|
|
80
|
+
* a pergunta era se o ARQUIVO tem contexto - então `Tabs`, `TabsList`, `TabsTrigger` e `TabsPanel`
|
|
81
|
+
* saíram juntos, com `cn("flex flex-col gap-4")` escrito neles (`codelevel-ui`, 23/08). Composto com
|
|
82
|
+
* contexto é o padrão dominante de React - Tabs, Accordion, Select, Dropdown -, então recusar o
|
|
83
|
+
* arquivo recusa a família toda.
|
|
84
|
+
*
|
|
85
|
+
* É a mesma falha de forma que `HAS_JSX` já cometeu do outro lado: uma pergunta sobre o arquivo
|
|
86
|
+
* respondendo sobre o export. Um provider é UM nome; os vizinhos respondem por si.
|
|
87
|
+
*
|
|
88
|
+
* Medido em três populações - `codelevel-ui` recupera 4, o dashboard do `frontend-hub` recupera 7
|
|
89
|
+
* (o `Pagination` inteiro), e os 6 que terminam em `Provider` continuam fora pelo sufixo.
|
|
90
|
+
*/
|
|
91
|
+
const CONTEXT_OWNER = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=]*)?=\s*(?:React\.)?createContext\s*[<(]/g;
|
|
92
|
+
/** Os nomes que ESTE arquivo atribui a um `createContext`. */
|
|
93
|
+
function contextNames(source) {
|
|
94
|
+
CONTEXT_OWNER.lastIndex = 0;
|
|
95
|
+
const out = new Set();
|
|
96
|
+
for (const m of source.matchAll(CONTEXT_OWNER))
|
|
97
|
+
out.add(m[1]);
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
75
100
|
/** A lowercase JSX tag: an html element this file renders ITSELF. */
|
|
76
101
|
const OWN_ELEMENT = /<(?:[a-z][a-z0-9]*)(\s|>|\/)/;
|
|
77
102
|
/** Every capitalised JSX tag the file opens, dots included. */
|
|
@@ -205,7 +230,7 @@ export function gateComponent(input) {
|
|
|
205
230
|
because: `\`${name}\` names a screen, and composes ${shape.composes} piece${shape.composes === 1 ? "" : "s"} across ${shape.nodes} element${shape.nodes === 1 ? "" : "s"} - too thin to be a template of yours. A design system is made of the pieces a screen is built FROM`,
|
|
206
231
|
};
|
|
207
232
|
}
|
|
208
|
-
if (PROVIDER_SUFFIX.test(name) ||
|
|
233
|
+
if (PROVIDER_SUFFIX.test(name) || contextNames(source).has(name)) {
|
|
209
234
|
return {
|
|
210
235
|
ok: false,
|
|
211
236
|
why: "provider",
|
|
@@ -232,6 +257,38 @@ export function gateComponent(input) {
|
|
|
232
257
|
*/
|
|
233
258
|
if (STYLED_FACTORY.test(source))
|
|
234
259
|
return { ok: true };
|
|
260
|
+
/**
|
|
261
|
+
* UMA CONSTANTE NÃO É COMPONENTE, e o arquivo em volta dela não muda isso.
|
|
262
|
+
*
|
|
263
|
+
* O QUE O CLIENTE VIA: `ICON_SIZE` e `AURORA_PALETTES` ocupando vaga na vitrine do
|
|
264
|
+
* `codelevel-ui`, e uma delas com forma desenhada - um mapa de tamanhos renderizado como se fosse
|
|
265
|
+
* peça. Ele contava 59 componentes no design system dele e dois não existiam.
|
|
266
|
+
*
|
|
267
|
+
* O teste de markup logo abaixo já recusa constante, e ele pergunta pelo ARQUIVO INTEIRO:
|
|
268
|
+
* `ICON_SIZE` mora dentro do `Icon.tsx`, que tem JSX de sobra, então ela entrava pela porta do
|
|
269
|
+
* vizinho. Vem ANTES dele por isso - é a mesma falha de forma que o teste de contexto acima
|
|
270
|
+
* corrigiu do outro lado.
|
|
271
|
+
*
|
|
272
|
+
* MEDIDO EM QUATRO POPULAÇÕES, e as três encontradas são objeto literal no código real:
|
|
273
|
+
*
|
|
274
|
+
* codelevel-ui packages/ui 75 nomes · 2 (ICON_SIZE, AURORA_PALETTES)
|
|
275
|
+
* frontend-hub packages/ui 69 nomes · 0
|
|
276
|
+
* frontend-hub apps/web-dashboard 526 nomes · 1 (ARTICLE_TAB, um enum de aba)
|
|
277
|
+
* synthesisui apps/web · 0
|
|
278
|
+
*
|
|
279
|
+
* Nenhum componente das quatro usa este formato de nome - JSX escreve `<Button>` e nunca
|
|
280
|
+
* `<ICON_SIZE>` -, então a regra não recusa peça de ninguém. E ela só pôde entrar junto com o
|
|
281
|
+
* leitor de `export default` em linha própria: sozinha, ela deixava órfãs as 294 declarações de
|
|
282
|
+
* CSS Modules do único arquivo do dashboard onde a constante era a ÚNICA coisa que o leitor via.
|
|
283
|
+
* O dono daquele arquivo é o `CurateContentSection`, que agora é lido.
|
|
284
|
+
*/
|
|
285
|
+
if (SCREAMING_SNAKE.test(name)) {
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
why: "config",
|
|
289
|
+
because: `\`${name}\` is spelled as a constant - a value the code reads, not a component anything renders`,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
235
292
|
if (!HAS_JSX.test(source)) {
|
|
236
293
|
return {
|
|
237
294
|
ok: false,
|