synthesisui 0.16.458 → 0.16.460
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 +7 -1
- package/dist/commands/align.js +1 -1
- package/dist/commands/component.js +14 -3
- package/dist/commands/init.js +1 -0
- package/dist/commands/mcp.js +7 -1
- package/dist/commands/sync.js +30 -0
- package/dist/commands/upgrade.js +3 -2
- package/dist/component-codegen.js +77 -8
- package/dist/index.js +1 -0
- package/dist/install-marks.js +7 -1
- package/dist/project-facts.js +119 -1
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -344,6 +344,8 @@ export async function add(slug, opts) {
|
|
|
344
344
|
version: payload.version,
|
|
345
345
|
registry: base,
|
|
346
346
|
...(opts.cli ? { cli: opts.cli } : {}),
|
|
347
|
+
/** O que o `sync` aprendeu sobre a leitura não se perde no próximo `connect` (27/09). */
|
|
348
|
+
...(prev?.reading === "none" ? { reading: "none" } : {}),
|
|
347
349
|
/** Ver `RegistryPayload.compiler`: é o que faz um conserto de CSS chegar a um install. */
|
|
348
350
|
...(payload.compiler != null ? { compiler: payload.compiler } : {}),
|
|
349
351
|
...(payload.rulesStamp ? { rules: payload.rulesStamp } : {}),
|
|
@@ -601,7 +603,11 @@ export async function add(slug, opts) {
|
|
|
601
603
|
* Entao a linha diz o FATO que ela tem - a classe dele continua significando o que
|
|
602
604
|
* significa - e nomeia os primeiros, para quem quiser discordar de qualquer um.
|
|
603
605
|
*/
|
|
604
|
-
|
|
606
|
+
/**
|
|
607
|
+
* Com ZERO, a frase falava de decisões que um projeto novo não tem - rodada 1 do refinamento,
|
|
608
|
+
* 27/09: "0 Tailwind utilities now point at your own decisions", num Next recém-criado.
|
|
609
|
+
*/
|
|
610
|
+
` ${aligned.kept > 0 ? `${aligned.kept} Tailwind utilit${aligned.kept === 1 ? "y" : "ies"} now point at your own decisions` : "Your code declares no Tailwind names of its own yet"}${aligned.own.length > 0 ? `; ${aligned.own.length} more name${aligned.own.length === 1 ? "s a utility" : " utilities"} only this system has (${aligned.own.slice(0, 3).join(", ")}${aligned.own.length > 3 ? ", …" : ""}), so they are declared here` : ""}${aligned.dropped.length > 0 ? `; ${aligned.dropped.length} were left as ${aligned.dropped.length === 1 ? "it is" : "they are"} (${aligned.dropped.slice(0, 3).join(", ")}${aligned.dropped.length > 3 ? ", …" : ""}) - your classes keep meaning what they mean today` : ""}.`));
|
|
605
611
|
}
|
|
606
612
|
/**
|
|
607
613
|
* O CICLO RECUSADO É DITO, e não engolido - senão o cliente lê um número menor sem saber por quê.
|
package/dist/commands/align.js
CHANGED
|
@@ -210,7 +210,7 @@ opts = {}) {
|
|
|
210
210
|
* sistema de 36).
|
|
211
211
|
*/
|
|
212
212
|
const scope = await measuredScope(root, lock.slug);
|
|
213
|
-
if (!scope.system && scope.usage.length === 0)
|
|
213
|
+
if (!scope.system && scope.usage.length === 0 && lock.reading !== "none")
|
|
214
214
|
out.push({
|
|
215
215
|
says: `"${lock.slug}" does not record where it was measured, so a re-measure would read this whole repo instead of the folder the system came from. Sync asks once and remembers the answer.`,
|
|
216
216
|
/**
|
|
@@ -7,7 +7,7 @@ import { readProjectConfig, resolveRegistry } from "../config.js";
|
|
|
7
7
|
import { detectAppDirs } from "../global-sheet.js";
|
|
8
8
|
import { hasInteractiveTemplate, interactiveTemplate, } from "../interactive-templates.js";
|
|
9
9
|
import { body, section, snippet } from "../output.js";
|
|
10
|
-
import { findCollision, reactMajorOf, readInstalledConvention, readInstalledScheme, theirThemeVars, } from "../project-facts.js";
|
|
10
|
+
import { findCollision, reactMajorOf, readInstalledConvention, readInstalledScheme, theirThemeVars, namedUtilitiesIn, themeVocabOf, } from "../project-facts.js";
|
|
11
11
|
import { fetchComponent, fetchDesignSystem, RegistryError, } from "../registry.js";
|
|
12
12
|
import { flavourResolver } from "../styles-flavour.js";
|
|
13
13
|
import { inTheirTongue, projectTongue, sumSpoken, tongueFromArtifacts, } from "../their-tongue.js";
|
|
@@ -356,6 +356,7 @@ export async function component(slug, name, opts) {
|
|
|
356
356
|
filenames = written.map((f) => f.filename);
|
|
357
357
|
}
|
|
358
358
|
else {
|
|
359
|
+
const vocab = await themeVocabOf(root, slug, await theirThemeVars(root, await detectAppDirs(root, config.pagesDir)));
|
|
359
360
|
const { files, spoken: said, root: generatedRoot, } = generateComponentFiles(slug, res.name, res.recipe, res.css, res.version, flavour, await reactMajorOf(root),
|
|
360
361
|
/**
|
|
361
362
|
* THE SPELLING, FROM THE VERSION WE JUST FETCHED.
|
|
@@ -385,8 +386,18 @@ export async function component(slug, name, opts) {
|
|
|
385
386
|
* propriedade renderiza NADA - pior que o valor arbitrário, porque nada falta na tela. É a
|
|
386
387
|
* mesma lição de `INV-VOLTA-12`, que saiu com a instrução de import e não com a topologia.
|
|
387
388
|
*/
|
|
388
|
-
|
|
389
|
-
|
|
389
|
+
vocab);
|
|
390
|
+
/**
|
|
391
|
+
* O UTILITÁRIO NOMEADO TAMBÉM É O NOME DELE - rodada 1 do refinamento, 27/09. A conta somava só
|
|
392
|
+
* as `var()` que a porta traduziu, e o leigo leu "0 references now speak the name YOUR code gives
|
|
393
|
+
* the value" sobre um botão com `bg-primary`, `text-muted` e `p-md`: "se eu acreditasse na
|
|
394
|
+
* mensagem, ia achar que não tinha funcionado".
|
|
395
|
+
*/
|
|
396
|
+
const utilities = vocab
|
|
397
|
+
? files.reduce((n, f) => n + namedUtilitiesIn(f.code, vocab), 0)
|
|
398
|
+
: 0;
|
|
399
|
+
spoken =
|
|
400
|
+
said && utilities > 0 ? { ...said, named: said.named + utilities } : said;
|
|
390
401
|
root_ = generatedRoot;
|
|
391
402
|
/**
|
|
392
403
|
* AS DECLARAÇÕES DO ARQUIVO DELE QUE O INTERPRETADOR NÃO LEU - ver `unread-for-component.ts`.
|
package/dist/commands/init.js
CHANGED
package/dist/commands/mcp.js
CHANGED
|
@@ -1891,8 +1891,14 @@ cli) {
|
|
|
1891
1891
|
carried: [],
|
|
1892
1892
|
groups: [],
|
|
1893
1893
|
omitted: [],
|
|
1894
|
+
/**
|
|
1895
|
+
* 404 é "nada gravado para este sistema", e dizer o número fazia o leigo achar que algo tinha
|
|
1896
|
+
* quebrado num projeto que acabou de nascer (rodada 1 do refinamento, 27/09).
|
|
1897
|
+
*/
|
|
1894
1898
|
because: res
|
|
1895
|
-
?
|
|
1899
|
+
? res.status === 404
|
|
1900
|
+
? "nothing has been decided for this system yet"
|
|
1901
|
+
: `the platform answered ${res.status}`
|
|
1896
1902
|
: "the platform could not be reached",
|
|
1897
1903
|
};
|
|
1898
1904
|
if (!res?.ok)
|
package/dist/commands/sync.js
CHANGED
|
@@ -349,6 +349,20 @@ export async function remeasure(args) {
|
|
|
349
349
|
const head = await fetch(`${base}/api/registry/ds/${slug}/reading`, {
|
|
350
350
|
headers: { Authorization: `Bearer ${token}` },
|
|
351
351
|
}).catch(() => null);
|
|
352
|
+
/**
|
|
353
|
+
* 404 É UMA RESPOSTA, NÃO UMA FALHA - rodada 1 do refinamento, 27/09.
|
|
354
|
+
*
|
|
355
|
+
* A plataforma não tem leitura de um sistema que ninguém importou de um repositório - o da galeria,
|
|
356
|
+
* ou um nascido nela. O leigo, com o Oscilla da galeria num Next novo, ouvia "The registry answered 404
|
|
357
|
+
* for this system's reading" e ficava com um aviso de "fora de alinhamento" que nada fazia sumir. Agora
|
|
358
|
+
* a frase diz que é o esperado, e o `.lock` guarda isso, para o `align` não pedir de novo.
|
|
359
|
+
*/
|
|
360
|
+
if (head?.status === 404) {
|
|
361
|
+
await markNoReading(root, slug);
|
|
362
|
+
console.log(section("Sync"));
|
|
363
|
+
console.log(body(`"${slug}" was not read from a repository - it comes from the gallery or was built on the platform - so there is nothing here to re-measure. That is expected, and this repo will stop asking.`));
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
352
366
|
if (!head?.ok) {
|
|
353
367
|
console.log(section("Sync"));
|
|
354
368
|
console.log(body(head
|
|
@@ -993,3 +1007,19 @@ async function askForScope(root) {
|
|
|
993
1007
|
export async function installedSlug(root) {
|
|
994
1008
|
return (await installedSlugs(root))[0] ?? null;
|
|
995
1009
|
}
|
|
1010
|
+
/** Anota no `.lock` que a plataforma não tem leitura deste sistema - ver o 404 em `remeasure`. */
|
|
1011
|
+
async function markNoReading(root, slug) {
|
|
1012
|
+
const path = join(root, "_synthesisui", "ds", slug, ".lock");
|
|
1013
|
+
const raw = await readFile(path, "utf8").catch(() => "");
|
|
1014
|
+
if (!raw)
|
|
1015
|
+
return;
|
|
1016
|
+
try {
|
|
1017
|
+
const lock = JSON.parse(raw);
|
|
1018
|
+
if (lock.reading === "none")
|
|
1019
|
+
return;
|
|
1020
|
+
await writeFile(path, `${JSON.stringify({ ...lock, reading: "none" }, null, 2)}\n`, "utf8");
|
|
1021
|
+
}
|
|
1022
|
+
catch {
|
|
1023
|
+
/** Um `.lock` ilegível não é deste passo consertar. */
|
|
1024
|
+
}
|
|
1025
|
+
}
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -10,7 +10,7 @@ import { diffLocalDocuments, localChangelogMarkdown, } from "../document-diff.js
|
|
|
10
10
|
import { detectAppDirs } from "../global-sheet.js";
|
|
11
11
|
import { installedBehind, MATERIALISER_SINCE } from "../install-marks.js";
|
|
12
12
|
import { body, section, snippet } from "../output.js";
|
|
13
|
-
import { reactMajorOf, readInstalledConvention, readInstalledScheme, theirThemeVars, } from "../project-facts.js";
|
|
13
|
+
import { reactMajorOf, readInstalledConvention, readInstalledScheme, theirThemeVars, themeVocabOf, } from "../project-facts.js";
|
|
14
14
|
import { fetchChangelog, fetchComponent, fetchDesignSystem, RegistryError, } from "../registry.js";
|
|
15
15
|
import { flavourResolver } from "../styles-flavour.js";
|
|
16
16
|
import { projectTongue } from "../their-tongue.js";
|
|
@@ -422,7 +422,8 @@ export async function upgrade(asked, opts) {
|
|
|
422
422
|
const tongue = await projectTongue(root, slug);
|
|
423
423
|
/** O `@theme` DELE é o MESMO para a corrida inteira - ver `theirThemeVars`. */
|
|
424
424
|
/** As pastas de app - a resposta é a INTERSEÇÃO delas. Ver `theirThemeVars`. */
|
|
425
|
-
|
|
425
|
+
/** Com os valores - o mesmo vocabulário do `component`, para os dois reescreverem igual (27/09). */
|
|
426
|
+
const themeVars = await themeVocabOf(root, slug, await theirThemeVars(root, await detectAppDirs(root, config.pagesDir)));
|
|
426
427
|
for (const entry of entries) {
|
|
427
428
|
const tsxPath = join(componentsRoot, entry, `${entry}.tsx`);
|
|
428
429
|
let head = "";
|
|
@@ -563,6 +563,61 @@ function remToTwScale(value) {
|
|
|
563
563
|
* legível não custa nada.
|
|
564
564
|
*/
|
|
565
565
|
const minted = (themeVars, cssVar) => themeVars === null || themeVars.has(cssVar);
|
|
566
|
+
/**
|
|
567
|
+
* O VOCABULÁRIO DELE COM OS VALORES - rodada 1 do refinamento, 27/09.
|
|
568
|
+
*
|
|
569
|
+
* O `Set` de nomes respondia só "ele declara `--color-x`?". Dois defeitos saíam daí, medidos no Oscilla
|
|
570
|
+
* v9 num Next novo: o `hover` do cartão pede um DEGRAU (`{color.phosphor.500}`) que o projeto chama de
|
|
571
|
+
* `--color-faint` pelo mesmo `#5d675f`, e o gerador não achava; e o `foreground` do `create-next-app`
|
|
572
|
+
* (`#171717`) casava pelo NOME com o `foreground` claro do sistema, e o botão saía com texto quase preto
|
|
573
|
+
* sobre o fundo escuro.
|
|
574
|
+
*
|
|
575
|
+
* Continua sendo um `Set`, então todo lugar que só pergunta o nome segue igual. Quem sabe pedir mais
|
|
576
|
+
* pergunta `nameFor`.
|
|
577
|
+
*/
|
|
578
|
+
export class ThemeVocab extends Set {
|
|
579
|
+
theirValues;
|
|
580
|
+
dsValues;
|
|
581
|
+
constructor(
|
|
582
|
+
/** O valor LITERAL de cada variável dele, com as indireções dele já seguidas. */
|
|
583
|
+
theirValues,
|
|
584
|
+
/** O valor LITERAL de cada variável nossa (`--ds-*`), com as indireções seguidas. */
|
|
585
|
+
dsValues) {
|
|
586
|
+
super(theirValues.keys());
|
|
587
|
+
this.theirValues = theirValues;
|
|
588
|
+
this.dsValues = dsValues;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* O nome DELE para uma referência nossa no namespace `ns` (`color`), ou `null`.
|
|
592
|
+
*
|
|
593
|
+
* O nome que casa vale se o valor também casa - ou se um dos dois lados não tem valor conhecido, que
|
|
594
|
+
* é o comportamento de antes. Sem nome que case, o VALOR acha o nome, mas só quando exatamente uma
|
|
595
|
+
* variável dele o segura: duas seria adivinhar qual das duas ele quis dizer.
|
|
596
|
+
*/
|
|
597
|
+
nameFor(ref, ns, byName) {
|
|
598
|
+
const path = /^\{([^}]+)\}$/.exec(ref.trim())?.[1];
|
|
599
|
+
const want = path ? this.dsValues.get(`--ds-${path.replace(/\./g, "-")}`) : undefined;
|
|
600
|
+
if (byName && this.has(`--${ns}-${byName}`)) {
|
|
601
|
+
const theirs = this.theirValues.get(`--${ns}-${byName}`);
|
|
602
|
+
if (!want || !theirs || sameValue(want, theirs))
|
|
603
|
+
return byName;
|
|
604
|
+
}
|
|
605
|
+
if (!want)
|
|
606
|
+
return null;
|
|
607
|
+
const hits = [...this.theirValues]
|
|
608
|
+
.filter(([n, v]) => n.startsWith(`--${ns}-`) && sameValue(v, want))
|
|
609
|
+
.map(([n]) => n.slice(ns.length + 3));
|
|
610
|
+
return hits.length === 1 ? hits[0] : null;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const sameValue = (a, b) => a.trim().toLowerCase().replace(/\s+/g, " ") === b.trim().toLowerCase().replace(/\s+/g, " ");
|
|
614
|
+
/** A chave de cor que ele escreve: pelo `ThemeVocab` quando há valores, pelo nome quando não. */
|
|
615
|
+
const theirColorKey = (value, themeVars) => {
|
|
616
|
+
const key = colorKey(value);
|
|
617
|
+
if (themeVars instanceof ThemeVocab)
|
|
618
|
+
return themeVars.nameFor(value, "color", key);
|
|
619
|
+
return key && minted(themeVars, `--color-${key}`) ? key : null;
|
|
620
|
+
};
|
|
566
621
|
/** One declaration → Tailwind classes (pretty when mappable, arbitrary-property
|
|
567
622
|
* otherwise - never dropped). */
|
|
568
623
|
function declToTailwind(prop, value, themeVars) {
|
|
@@ -573,20 +628,20 @@ function declToTailwind(prop, value, themeVars) {
|
|
|
573
628
|
case "backgroundColor": {
|
|
574
629
|
if (value === "transparent")
|
|
575
630
|
return ["bg-transparent"];
|
|
576
|
-
const key =
|
|
577
|
-
if (key
|
|
631
|
+
const key = theirColorKey(value, themeVars);
|
|
632
|
+
if (key)
|
|
578
633
|
return [`bg-${key}`];
|
|
579
634
|
break;
|
|
580
635
|
}
|
|
581
636
|
case "color": {
|
|
582
|
-
const key =
|
|
583
|
-
if (key
|
|
637
|
+
const key = theirColorKey(value, themeVars);
|
|
638
|
+
if (key)
|
|
584
639
|
return [`text-${key}`];
|
|
585
640
|
break;
|
|
586
641
|
}
|
|
587
642
|
case "borderColor": {
|
|
588
|
-
const key =
|
|
589
|
-
if (key
|
|
643
|
+
const key = theirColorKey(value, themeVars);
|
|
644
|
+
if (key)
|
|
590
645
|
return [`border-${key}`];
|
|
591
646
|
break;
|
|
592
647
|
}
|
|
@@ -594,12 +649,26 @@ function declToTailwind(prop, value, themeVars) {
|
|
|
594
649
|
// "1px solid {color.semantic.x}" → border + border-<x>
|
|
595
650
|
const m = value.match(/^1px\s+solid\s+(\{[^}]+\})$/);
|
|
596
651
|
if (m) {
|
|
597
|
-
const key =
|
|
598
|
-
if (key
|
|
652
|
+
const key = theirColorKey(m[1], themeVars);
|
|
653
|
+
if (key)
|
|
599
654
|
return ["border", `border-${key}`];
|
|
600
655
|
}
|
|
601
656
|
break;
|
|
602
657
|
}
|
|
658
|
+
case "outline": {
|
|
659
|
+
/**
|
|
660
|
+
* "2px solid {color.semantic.accent}" → outline-2 + outline-<x>. Não tinha tradução: o foco de
|
|
661
|
+
* todo botão saía `[outline:2px_solid_#4cd694]`, com o nome dele declarado (rodada 1, 27/09).
|
|
662
|
+
* No Tailwind v4, `outline-<n>` já põe o estilo sólido.
|
|
663
|
+
*/
|
|
664
|
+
const m = value.match(/^(\d+)px\s+solid\s+(\{[^}]+\})$/);
|
|
665
|
+
if (m) {
|
|
666
|
+
const key = theirColorKey(m[2], themeVars);
|
|
667
|
+
if (key)
|
|
668
|
+
return [m[1] === "1" ? "outline" : `outline-${m[1]}`, `outline-${key}`];
|
|
669
|
+
}
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
603
672
|
case "borderRadius": {
|
|
604
673
|
const key = nsKey(value, "radius");
|
|
605
674
|
if (key && minted(themeVars, `--radius-${key}`))
|
package/dist/index.js
CHANGED
package/dist/install-marks.js
CHANGED
|
@@ -252,7 +252,13 @@
|
|
|
252
252
|
* dizia que o hook "reports on every file you write" - a instrução que o agente dele lê sobre o próprio
|
|
253
253
|
* mecanismo. Passa a dizer o fim do turno (E04), e só o `upgrade` reescreve o bloco.
|
|
254
254
|
*/
|
|
255
|
-
|
|
255
|
+
/**
|
|
256
|
+
* 0.16.455 -> 0.16.459 em 27/09, e o passo 1 dá **SIM**: o gerador que o `upgrade` usa para reescrever os
|
|
257
|
+
* componentes passa a traduzir `outline` (o foco de todo botão saía `[outline:2px_solid_#…]`) e a achar
|
|
258
|
+
* o nome dele pelo valor quando a receita pede um degrau - rodada 1 do refinamento. Um componente escrito
|
|
259
|
+
* antes carrega o valor cru onde o projeto tem nome.
|
|
260
|
+
*/
|
|
261
|
+
export const MATERIALISER_SINCE = "0.16.459";
|
|
256
262
|
/**
|
|
257
263
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
258
264
|
*
|
package/dist/project-facts.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { readdir, readFile } from "node:fs/promises";
|
|
17
17
|
import { join, relative, sep } from "node:path";
|
|
18
|
-
import { DEFAULT_CONVENTION, } from "./component-codegen.js";
|
|
18
|
+
import { DEFAULT_CONVENTION, ThemeVocab, } from "./component-codegen.js";
|
|
19
19
|
/**
|
|
20
20
|
* Which React the consumer is on, for the ref-carrying prop type.
|
|
21
21
|
*
|
|
@@ -317,3 +317,121 @@ function themeVarsIn(css) {
|
|
|
317
317
|
}
|
|
318
318
|
return out;
|
|
319
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* OS NOMES DELE COM OS VALORES, e os nossos também - para o gerador achar o nome pelo valor.
|
|
322
|
+
*
|
|
323
|
+
* Rodada 1 do refinamento, 27/09: com só os nomes, o gerador não via que o `--color-faint` dele segura o
|
|
324
|
+
* mesmo `#5d675f` do degrau `{color.phosphor.500}` que a receita pede, e casava o `--color-foreground` do
|
|
325
|
+
* `create-next-app` (`#171717`) com o `foreground` claro do sistema. Ver `ThemeVocab`.
|
|
326
|
+
*
|
|
327
|
+
* `names` é a resposta de `theirThemeVars` - a interseção das pastas de app -, e só esses nomes entram:
|
|
328
|
+
* o valor não pode trazer de volta um nome que aquele app não gera. As indireções são seguidas dos dois
|
|
329
|
+
* lados; um valor que termina numa variável nossa (`var(--ds-…)`) é resolvido pela folha instalada.
|
|
330
|
+
*/
|
|
331
|
+
export async function themeVocabOf(root, slug, names) {
|
|
332
|
+
if (!names)
|
|
333
|
+
return null;
|
|
334
|
+
const ds = join(root, "_synthesisui", "ds", slug);
|
|
335
|
+
const pointer = await readFile(join(ds, "tokens.css"), "utf8").catch(() => "");
|
|
336
|
+
const inner = /@import\s+["']\.\/([^"']+)["']/.exec(pointer)?.[1];
|
|
337
|
+
const installed = inner ? await readFile(join(ds, inner), "utf8").catch(() => "") : pointer;
|
|
338
|
+
const dsRaw = declarationsIn(installed, /--ds-[a-zA-Z0-9_-]+/);
|
|
339
|
+
const theirRaw = new Map();
|
|
340
|
+
const walk = async (at) => {
|
|
341
|
+
for (const entry of await readdir(at, { withFileTypes: true }).catch(() => [])) {
|
|
342
|
+
if (SKIP.has(entry.name) || entry.name.startsWith("."))
|
|
343
|
+
continue;
|
|
344
|
+
const path = join(at, entry.name);
|
|
345
|
+
if (entry.isDirectory())
|
|
346
|
+
await walk(path);
|
|
347
|
+
else if (/\.css$/i.test(entry.name)) {
|
|
348
|
+
const css = await readFile(path, "utf8").catch(() => "");
|
|
349
|
+
if (!THEME_BLOCK.test(css) && !/:root\b/.test(css))
|
|
350
|
+
continue;
|
|
351
|
+
for (const [n, v] of declarationsIn(css, /--(?!ds-)[a-zA-Z0-9_-]+/))
|
|
352
|
+
if (!theirRaw.has(n))
|
|
353
|
+
theirRaw.set(n, v);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
await walk(root);
|
|
358
|
+
const resolveIn = (table) => {
|
|
359
|
+
const seen = new Set();
|
|
360
|
+
const go = (value) => {
|
|
361
|
+
const ref = /^var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,([^)]*))?\)$/.exec(value.trim());
|
|
362
|
+
if (!ref)
|
|
363
|
+
return value.trim();
|
|
364
|
+
const target = ref[1];
|
|
365
|
+
if (seen.has(target))
|
|
366
|
+
return null;
|
|
367
|
+
seen.add(target);
|
|
368
|
+
const next = target.startsWith("--ds-") ? dsRaw.get(target) : table.get(target);
|
|
369
|
+
if (next !== undefined)
|
|
370
|
+
return go(next);
|
|
371
|
+
return ref[2]?.trim() || null;
|
|
372
|
+
};
|
|
373
|
+
return (value) => {
|
|
374
|
+
seen.clear();
|
|
375
|
+
return go(value);
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
const theirs = new Map();
|
|
379
|
+
const resolveTheirs = resolveIn(theirRaw);
|
|
380
|
+
for (const n of names) {
|
|
381
|
+
const raw = theirRaw.get(n);
|
|
382
|
+
const value = raw === undefined ? null : resolveTheirs(raw);
|
|
383
|
+
if (value)
|
|
384
|
+
theirs.set(n, value);
|
|
385
|
+
}
|
|
386
|
+
const dsValues = new Map();
|
|
387
|
+
const resolveDs = resolveIn(dsRaw);
|
|
388
|
+
for (const [n, raw] of dsRaw) {
|
|
389
|
+
const value = resolveDs(raw);
|
|
390
|
+
if (value)
|
|
391
|
+
dsValues.set(n, value);
|
|
392
|
+
}
|
|
393
|
+
/** Um nome sem valor conhecido continua no conjunto: `nameFor` o trata como antes. */
|
|
394
|
+
const vocab = new ThemeVocab(theirs, dsValues);
|
|
395
|
+
for (const n of names)
|
|
396
|
+
vocab.add(n);
|
|
397
|
+
return vocab;
|
|
398
|
+
}
|
|
399
|
+
/** `--nome: valor` em qualquer formatação - a primeira declaração de cada nome vence. */
|
|
400
|
+
function declarationsIn(css, name) {
|
|
401
|
+
const out = new Map();
|
|
402
|
+
const re = new RegExp(`(?:^|[{;])\\s*(${name.source})\\s*:\\s*([^;}]+)(?=[;}])`, "g");
|
|
403
|
+
for (const m of css.matchAll(re))
|
|
404
|
+
if (!out.has(m[1]))
|
|
405
|
+
out.set(m[1], m[2].trim());
|
|
406
|
+
return out;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* QUANTOS UTILITÁRIOS DESTE CÓDIGO SÃO NOMES DELE - `bg-primary` quando ele declara `--color-primary`.
|
|
410
|
+
*
|
|
411
|
+
* Conta pela classe inteira, com a variante tirada (`hover:border-faint` conta), e só nos namespaces que
|
|
412
|
+
* o gerador traduz. Uma classe que não bate com nome nenhum dele não conta - nem para mais, nem para menos.
|
|
413
|
+
*/
|
|
414
|
+
export function namedUtilitiesIn(code, names) {
|
|
415
|
+
const NS = [
|
|
416
|
+
[/^(?:bg|text|border(?:-[trblxy])?|outline|fill|stroke|ring)-(.+)$/, ["color"]],
|
|
417
|
+
[/^rounded(?:-[trbl]{1,2})?-(.+)$/, ["radius"]],
|
|
418
|
+
[/^(?:p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y)-(.+)$/, ["spacing"]],
|
|
419
|
+
[/^text-(.+)$/, ["text"]],
|
|
420
|
+
[/^font-(.+)$/, ["font", "font-weight"]],
|
|
421
|
+
[/^shadow-(.+)$/, ["shadow"]],
|
|
422
|
+
];
|
|
423
|
+
let n = 0;
|
|
424
|
+
for (const lit of code.matchAll(/"([^"\n]*)"/g)) {
|
|
425
|
+
for (const raw of lit[1].split(/\s+/)) {
|
|
426
|
+
const cls = raw.slice(raw.lastIndexOf(":") + 1);
|
|
427
|
+
if (!cls || cls.startsWith("["))
|
|
428
|
+
continue;
|
|
429
|
+
if (NS.some(([re, nss]) => {
|
|
430
|
+
const key = re.exec(cls)?.[1];
|
|
431
|
+
return key !== undefined && nss.some((ns) => names.has(`--${ns}-${key}`));
|
|
432
|
+
}))
|
|
433
|
+
n += 1;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return n;
|
|
437
|
+
}
|
package/package.json
CHANGED