synthesisui 0.16.497 → 0.16.499
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 +18 -2
- package/dist/commands/doctor.js +24 -1
- package/dist/commands/init.js +39 -9
- package/dist/doctor/broken-refs.js +1 -1
- package/dist/doctor/scan.js +67 -3
- package/dist/install-marks.js +23 -3
- package/dist/new-project-theme.js +78 -13
- package/dist/their-tongue.js +30 -1
- package/dist/their-vars.js +11 -1
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -21,7 +21,7 @@ import { describeFiltered, ruleApplies, rulesForProject, } from "../rule-filter.
|
|
|
21
21
|
import { detectStack, resolveDeps } from "../stack.js";
|
|
22
22
|
import { onlyWhatMatched } from "../their-theme.js";
|
|
23
23
|
import { tongueOf } from "../their-tongue.js";
|
|
24
|
-
import { pointTokensAtTheirNames } from "../their-vars.js";
|
|
24
|
+
import { namesTheyDeclare, pointTokensAtTheirNames } from "../their-vars.js";
|
|
25
25
|
import { tracksAnyOf } from "../tracked.js";
|
|
26
26
|
/**
|
|
27
27
|
* QUAL METADE DESTA PASTA UM TIME COMMITA.
|
|
@@ -252,6 +252,22 @@ export async function add(slug, opts) {
|
|
|
252
252
|
const theirVars = await pointTokensAtTheirNames(projectRoot, payload,
|
|
253
253
|
/** O que um build anterior provou - ver `provenBefore`. Sem build aqui, é a melhor prova que há. */
|
|
254
254
|
(prev?.tokenMap ?? []).map((p) => p.theirs));
|
|
255
|
+
/**
|
|
256
|
+
* O PAR QUE O `init` DECLAROU CONTINUA (jornada "o pedido livre sai mastigado", 15/10).
|
|
257
|
+
*
|
|
258
|
+
* Num projeto novo o `init` escreve os nomes do sistema no `@theme` dele e grava cada par no `.lock`. O `connect`, logo
|
|
259
|
+
* em seguida, passa por aqui - e o cruzamento acima só reconhece o nome de mesmo formato (`--ds-color-sand-50` com
|
|
260
|
+
* `--color-sand-50`), não o papel (`--ds-color-semantic-primary` com `--color-primary`). O mapa caía de 91 para 27 pares,
|
|
261
|
+
* e o template voltava a 40 valores crus no hero. O par anterior fica enquanto o CSS dele ainda declara aquele nome: o
|
|
262
|
+
* nome sumiu, o par some junto.
|
|
263
|
+
*/
|
|
264
|
+
const reproved = new Set(theirVars.pairs.map((p) => p.ours));
|
|
265
|
+
const before = (prev?.tokenMap ?? []).filter((p) => !reproved.has(p.ours));
|
|
266
|
+
const stillDeclared = await namesTheyDeclare(projectRoot, before.map((p) => p.theirs));
|
|
267
|
+
const tokenMap = [
|
|
268
|
+
...theirVars.pairs,
|
|
269
|
+
...before.filter((p) => stillDeclared.has(p.theirs)),
|
|
270
|
+
];
|
|
255
271
|
/**
|
|
256
272
|
* O `theme.css` ALINHA ONDE HÁ O QUE ALINHAR - ver `onlyWhatMatched`.
|
|
257
273
|
*
|
|
@@ -416,7 +432,7 @@ export async function add(slug, opts) {
|
|
|
416
432
|
*/
|
|
417
433
|
...(theirVars.pointed > 0 ? { theirVars: theirVars.pointed } : {}),
|
|
418
434
|
/** Ver `RootLock.tokenMap`: o fato, ao lado do número que ele produz. */
|
|
419
|
-
...(
|
|
435
|
+
...(tokenMap.length > 0 ? { tokenMap } : {}),
|
|
420
436
|
...(scope ? { scope } : {}),
|
|
421
437
|
...(systems.length > 0 ? { scopes: systems } : {}),
|
|
422
438
|
...(usage.length > 0 ? { usage } : {}),
|
package/dist/commands/doctor.js
CHANGED
|
@@ -2,7 +2,8 @@ import { describeDistinct, describeDistinctVerbose, distinctValues } from "../do
|
|
|
2
2
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { readStyleSheet, scssVariables } from "../doctor/scss-lens.js";
|
|
4
4
|
import { THEME_SOURCE, tsTokenObjects, tsTokensAsVars } from "../doctor/ts-tokens-lens.js";
|
|
5
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { SET_PROPERTY } from "../doctor/broken-refs.js";
|
|
6
7
|
import { describeIntent, intentOf, readProjectConfig } from "../config.js";
|
|
7
8
|
import { describeFix, planFix, readerFor, writeFix, } from "../doctor/apply-fix.js";
|
|
8
9
|
import { baselineOf, compareToBaseline, describeVerdict, githubAnnotations, sarifOf, } from "../doctor/ci-format.js";
|
|
@@ -777,6 +778,28 @@ export async function doctor(opts) {
|
|
|
777
778
|
for await (const file of walkAll([root]))
|
|
778
779
|
if (THEME_SOURCE.test(file) && !/\.d\.ts$/.test(file))
|
|
779
780
|
tsObjects.push(...tsTokenObjects(await readFile(file, "utf8").catch(() => "")));
|
|
781
|
+
/**
|
|
782
|
+
* O QUE O JAVASCRIPT DELE MEDE, por pasta - ver `TokenTable.measuredInJs` (aula 01, 15/10). A reserva de
|
|
783
|
+
* `var(--sui-hero-name, clamp(...))` sai da conta só quando a pasta do CSS é a que grava a variável.
|
|
784
|
+
*/
|
|
785
|
+
const measuredInJs = new Map();
|
|
786
|
+
for await (const file of walkAll([root])) {
|
|
787
|
+
if (!/\.(tsx|jsx|ts|js|mjs)$/.test(file) || /\.d\.ts$/.test(file))
|
|
788
|
+
continue;
|
|
789
|
+
const text = await readFile(file, "utf8").catch(() => "");
|
|
790
|
+
if (!text.includes("setProperty"))
|
|
791
|
+
continue;
|
|
792
|
+
const rel = relative(root, file);
|
|
793
|
+
const dir = rel.includes(sep) ? rel.slice(0, rel.lastIndexOf(sep)).split(sep).join("/") : "";
|
|
794
|
+
for (const m of text.matchAll(SET_PROPERTY)) {
|
|
795
|
+
const name = m[1].toLowerCase();
|
|
796
|
+
const dirs = measuredInJs.get(name) ?? [];
|
|
797
|
+
if (!dirs.includes(dir))
|
|
798
|
+
measuredInJs.set(name, [...dirs, dir]);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
if (measuredInJs.size > 0)
|
|
802
|
+
table = { ...table, measuredInJs };
|
|
780
803
|
const tally = emptyTally();
|
|
781
804
|
const internalSpecs = await internalSpecifiers(root);
|
|
782
805
|
for await (const file of scopes.length > 0
|
package/dist/commands/init.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { DEFAULT_CONFIG, resolveRegistry, writeProjectConfig, } from "../config.js";
|
|
4
|
+
import { readProjectTheme, writeBesideTheirs, writeNewProjectRoot, writeNewProjectTheme, } from "../new-project-theme.js";
|
|
2
5
|
import { body, section, snippet } from "../output.js";
|
|
6
|
+
import { fetchDesignSystem } from "../registry.js";
|
|
3
7
|
import { resolveDeps, stylesFor, tailwindMajor } from "../stack.js";
|
|
4
8
|
import { formMix, measuredStyle } from "../styles-flavour.js";
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { readProjectTheme, writeBesideTheirs, writeNewProjectRoot, writeNewProjectTheme } from "../new-project-theme.js";
|
|
8
9
|
import { add } from "./add.js";
|
|
9
|
-
import { resolveRegistry } from "../config.js";
|
|
10
|
-
import { fetchDesignSystem } from "../registry.js";
|
|
11
10
|
const whereOf = (answer) => answer === "file" || answer === "1"
|
|
12
11
|
? "file"
|
|
13
12
|
: answer === "separate" || answer === "2"
|
|
@@ -195,9 +194,13 @@ export async function init(opts) {
|
|
|
195
194
|
const tokens = version !== null
|
|
196
195
|
? await readFile(join(slugDir, `v${version}`, "tokens.css"), "utf8").catch(() => "")
|
|
197
196
|
: "";
|
|
198
|
-
const plan = tokens
|
|
197
|
+
const plan = tokens
|
|
198
|
+
? await readProjectTheme(root, tokens, opts.ds)
|
|
199
|
+
: null;
|
|
199
200
|
if (plan?.kind === "new" && version !== null) {
|
|
200
201
|
const theme = await writeNewProjectTheme(root, tokens, opts.ds, version);
|
|
202
|
+
if (theme.written)
|
|
203
|
+
await recordDeclaredNames(slugDir, theme.pairs ?? []);
|
|
201
204
|
if (theme.written) {
|
|
202
205
|
console.log("");
|
|
203
206
|
console.log(`✓ ${theme.written}: ${theme.count} names of ${opts.ds} declared in your own @theme, with the values written - your project had none of its own. They are yours: bg-accent, p-md and rounded-lg now paint the system. Fonts come from the GUIDE's next/font recipe; the alternate scheme is not wired.`);
|
|
@@ -235,8 +238,14 @@ export async function init(opts) {
|
|
|
235
238
|
const slugDir = join(root, "_synthesisui", "ds", opts.ds);
|
|
236
239
|
const lock = JSON.parse(await readFile(join(slugDir, ".lock"), "utf8").catch(() => "{}"));
|
|
237
240
|
const version = typeof lock.version === "number" ? lock.version : null;
|
|
238
|
-
const tokens = version !== null
|
|
239
|
-
|
|
241
|
+
const tokens = version !== null
|
|
242
|
+
? await readFile(join(slugDir, `v${version}`, "tokens.css"), "utf8").catch(() => "")
|
|
243
|
+
: "";
|
|
244
|
+
const theme = tokens && version !== null
|
|
245
|
+
? await writeNewProjectRoot(root, tokens, opts.ds, version)
|
|
246
|
+
: null;
|
|
247
|
+
if (theme?.written)
|
|
248
|
+
await recordDeclaredNames(slugDir, theme.pairs ?? []);
|
|
240
249
|
if (theme?.written) {
|
|
241
250
|
console.log("");
|
|
242
251
|
console.log(`✓ ${theme.written}: ${theme.count} names of ${opts.ds} declared in your own :root, with the values written - your project had none of its own. They are yours: var(--color-primary) and var(--spacing-md) now paint the system.`);
|
|
@@ -267,3 +276,24 @@ export async function init(opts) {
|
|
|
267
276
|
console.log(" • synthesisui add <slug> bring a design system in");
|
|
268
277
|
console.log(" • synthesisui template <slug> <name> materialize a full page");
|
|
269
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* O QUE O `init` ACABOU DE DECLARAR VAI PARA O `.lock` (jornada "o pedido livre sai mastigado", 15/10).
|
|
281
|
+
*
|
|
282
|
+
* O `add` roda ANTES de o tema ser escrito, quando o projeto novo ainda não nomeia nada, e grava o `tokenMap` vazio. O
|
|
283
|
+
* `template` e o `component` traduzem pela mesma `tongue` que lê esse mapa - então a primeira landing de um projeto novo
|
|
284
|
+
* vinha com ~260 valores à mão (medido na rodada com agente de 15/10: "0 references now speak the name YOUR code gives
|
|
285
|
+
* the value, and 44 carry the value"), e o agente gastava rodadas convertendo-os de volta. Os pares aqui não são palpite:
|
|
286
|
+
* são os nomes que o próprio `init` escreveu, um a um, com o valor de cada um. O que o mapa já tinha continua.
|
|
287
|
+
*/
|
|
288
|
+
async function recordDeclaredNames(slugDir, pairs) {
|
|
289
|
+
if (pairs.length === 0)
|
|
290
|
+
return;
|
|
291
|
+
const path = join(slugDir, ".lock");
|
|
292
|
+
const lock = JSON.parse(await readFile(path, "utf8").catch(() => "{}"));
|
|
293
|
+
const known = new Set((lock.tokenMap ?? []).map((p) => p.ours));
|
|
294
|
+
const tokenMap = [
|
|
295
|
+
...(lock.tokenMap ?? []),
|
|
296
|
+
...pairs.filter((p) => !known.has(p.ours)),
|
|
297
|
+
];
|
|
298
|
+
await writeFile(path, `${JSON.stringify({ ...lock, tokenMap }, null, 2)}\n`, "utf8");
|
|
299
|
+
}
|
|
@@ -40,7 +40,7 @@ const VAR_REF = /var\(\s*(--[a-zA-Z0-9_-]+)\s*(,)?/g;
|
|
|
40
40
|
* Medido em quatro populações: 2 nomes no `codelevel-ui`, 0 no `packages/ui` do `frontend-hub`, 2 no
|
|
41
41
|
* dashboard dele e 3 no nosso próprio app. Três de quatro escrevem variável por JS.
|
|
42
42
|
*/
|
|
43
|
-
const SET_PROPERTY = /setProperty\(\s*['"`](--[a-zA-Z0-9_-]+)/g;
|
|
43
|
+
export const SET_PROPERTY = /setProperty\(\s*['"`](--[a-zA-Z0-9_-]+)/g;
|
|
44
44
|
/**
|
|
45
45
|
* VARIABLES A HEADLESS LIBRARY SETS AT RUNTIME, which no stylesheet can declare.
|
|
46
46
|
*
|
package/dist/doctor/scan.js
CHANGED
|
@@ -174,6 +174,15 @@ const SPACING_DECL = new RegExp(`(?:padding|margin|gap)(?:[A-Z][a-z]+|(?:-[a-z]+
|
|
|
174
174
|
const SPACING_UTIL = /(?:\b[pmg](?:[trblxy])?-\[|gap-\[)([^\]]*)/g;
|
|
175
175
|
/** A font stack written by hand rather than taken from the type scale. */
|
|
176
176
|
const FONT = /font-family\s*:\s*([^;}\n]+)/g;
|
|
177
|
+
/**
|
|
178
|
+
* O TAMANHO DE TEXTO ESCRITO À MÃO (defeito 3 da aula 01, 15/10) - `font-size: 14px`, `fontSize: "1rem"`,
|
|
179
|
+
* `[font-size:0.875rem]`. Não havia leitor nenhum: `distinct-values` contava esses valores como "by hand" e a cobertura,
|
|
180
|
+
* na mesma tela, não, então o `--fix` não tinha o que trocar com `--text-sm` declarado no projeto. A família é `font`,
|
|
181
|
+
* porque é a palavra que os nomes de escala de tipo carregam (`--text-sm`, `--ds-typography-...`).
|
|
182
|
+
*/
|
|
183
|
+
const FONT_SIZE_DECL = new RegExp(`font-?[Ss]ize${DECL_VALUE}`, "g");
|
|
184
|
+
/** `text-[14px]` - o valor entre colchetes; `text-[#111]` não tem comprimento e não rende nada. */
|
|
185
|
+
const FONT_SIZE_UTIL = /\btext-\[([^\]]*)/g;
|
|
177
186
|
/**
|
|
178
187
|
* MOTION, the family the scan never read (item 12, F3).
|
|
179
188
|
*
|
|
@@ -351,6 +360,38 @@ function fallbackSpans(line, table) {
|
|
|
351
360
|
}
|
|
352
361
|
return out;
|
|
353
362
|
}
|
|
363
|
+
/** `var(--x, clamp(4rem, 17vw, 16rem))` - o nome e a reserva INTEIRA, com os parênteses de dentro contados. */
|
|
364
|
+
function fallbacksOf(line) {
|
|
365
|
+
const out = [];
|
|
366
|
+
for (const m of line.matchAll(VAR_WITH_FALLBACK)) {
|
|
367
|
+
const start = (m.index ?? 0) + m[0].length;
|
|
368
|
+
let depth = 1;
|
|
369
|
+
let end = start;
|
|
370
|
+
for (; end < line.length && depth > 0; end++) {
|
|
371
|
+
if (line[end] === "(")
|
|
372
|
+
depth++;
|
|
373
|
+
else if (line[end] === ")")
|
|
374
|
+
depth--;
|
|
375
|
+
}
|
|
376
|
+
if (depth === 0)
|
|
377
|
+
out.push({ name: m[1].toLowerCase(), span: [start, end - 1] });
|
|
378
|
+
}
|
|
379
|
+
return out;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* A RESERVA DE UM TAMANHO QUE O COMPONENTE MEDE SOZINHO - ver `TokenTable.measuredInJs` (aula 01, 15/10).
|
|
383
|
+
*
|
|
384
|
+
* Só quando um arquivo da MESMA pasta grava a variável: um `var(--qualquer, 18px)` que nada grava continua acusado, senão
|
|
385
|
+
* a reserva vira esconderijo de valor à mão.
|
|
386
|
+
*/
|
|
387
|
+
function measuredSpans(file, line, table) {
|
|
388
|
+
if (!table.measuredInJs?.size)
|
|
389
|
+
return [];
|
|
390
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
391
|
+
return fallbacksOf(line)
|
|
392
|
+
.filter((f) => table.measuredInJs?.get(f.name)?.includes(dir))
|
|
393
|
+
.map((f) => f.span);
|
|
394
|
+
}
|
|
354
395
|
/**
|
|
355
396
|
* ONDE A CASCATA DO APP NÃO ALCANÇA - e trocar por `var()` ali APAGA a cor.
|
|
356
397
|
*
|
|
@@ -754,6 +795,8 @@ function scanCore(file, source, table) {
|
|
|
754
795
|
const declared = declarationSpans(line, inTokenBlock && !opensTokenBlock);
|
|
755
796
|
const inDeclaration = (at) => declared.some(([a, b]) => at >= a && at < b);
|
|
756
797
|
const spans = fallbackSpans(line, table);
|
|
798
|
+
const measured = measuredSpans(file, line, table);
|
|
799
|
+
const inMeasured = (at) => measured.some(([a, b]) => at >= a && at <= b);
|
|
757
800
|
const inFallback = (at) => spans.some(([a, b]) => at >= a && at <= b);
|
|
758
801
|
/** Ver `markupTextSpans`: HTML montado como texto sai do alcance da folha do app. */
|
|
759
802
|
const markup = markupTextSpans(file, line);
|
|
@@ -770,6 +813,10 @@ function scanCore(file, source, table) {
|
|
|
770
813
|
/** A definição do token não é drift - ver `declarationSpans`. */
|
|
771
814
|
if (col >= 0 && inDeclaration(col))
|
|
772
815
|
return;
|
|
816
|
+
if (col >= 0 && inMeasured(col)) {
|
|
817
|
+
setAside("the fallback of a size the component measures itself");
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
773
820
|
if (col >= 0 && inFallback(col)) {
|
|
774
821
|
setAside("a token's own fallback");
|
|
775
822
|
return;
|
|
@@ -800,14 +847,20 @@ function scanCore(file, source, table) {
|
|
|
800
847
|
seen.add(key);
|
|
801
848
|
// The kind is already known here, and without passing it the lookup
|
|
802
849
|
// answers a `gap` with a radius token.
|
|
803
|
-
|
|
850
|
+
/**
|
|
851
|
+
* O `_` DA CLASSE ARBITRÁRIA É O ESPAÇO DO TAILWIND (aula 01, 15/10). O achado guarda o valor como ESTÁ ESCRITO -
|
|
852
|
+
* `Anton,_sans-serif` -, porque é nessa grafia que o `--fix` acha a coluna para trocar; só a busca do nome lê o `_`
|
|
853
|
+
* como espaço, que é como o token declara a pilha.
|
|
854
|
+
*/
|
|
855
|
+
const lookup = kind === "font" ? literal.replace(/_/g, " ") : literal;
|
|
856
|
+
const match = tokenMatch(table, lookup, kind);
|
|
804
857
|
/**
|
|
805
858
|
* O NOME DELE VEM DA MESMA CHAVE QUE A NOSSA FAMÍLIA VALIDOU - ver `theirNames`.
|
|
806
859
|
*
|
|
807
860
|
* A chave carrega o `kind` porque o valor sozinho não decide: `4px` é `--radius-xs` e
|
|
808
861
|
* `--spacing` no vocabulário dele, e qual dos dois está certo depende de onde o literal está.
|
|
809
862
|
*/
|
|
810
|
-
const aliased = table.aliases.get(`${kind}:${normalizeValue(
|
|
863
|
+
const aliased = table.aliases.get(`${kind}:${normalizeValue(lookup, table.rootPx)}`);
|
|
811
864
|
/** O valor igual não basta - o papel também, ver `roleFits` (r13 e r14, 28/09). */
|
|
812
865
|
const theirs = aliased && roleFits(propertyAt(line, col), aliased.name) ? aliased : undefined;
|
|
813
866
|
/** A procedência do `token`, lida da tabela que o achou - ver `tokenIsTheirs`. */
|
|
@@ -896,8 +949,19 @@ function scanCore(file, source, table) {
|
|
|
896
949
|
pushLengths("spacing", m);
|
|
897
950
|
for (const m of line.matchAll(SPACING_DECL))
|
|
898
951
|
pushLengths("spacing", m);
|
|
952
|
+
for (const m of line.matchAll(FONT_SIZE_UTIL))
|
|
953
|
+
pushLengths("font", m);
|
|
954
|
+
for (const m of line.matchAll(FONT_SIZE_DECL))
|
|
955
|
+
pushLengths("font", m);
|
|
899
956
|
for (const m of line.matchAll(FONT)) {
|
|
900
|
-
|
|
957
|
+
/**
|
|
958
|
+
* A CLASSE ARBITRÁRIA DO TAILWIND TERMINA NO `]` (pedido da aula 01, 15/10). Em `[font-family:Inter,_sans-serif]
|
|
959
|
+
* font-medium ...` a regra de CSS - que só para em `;`, `}` ou fim de linha - engolia o resto da string de classes, e
|
|
960
|
+
* o `--verbose` mostrava `Inter,_sans-serif] font-medium [white-space:nowrap] ...` como uma família. Dentro do
|
|
961
|
+
* colchete o valor vai até o `]`; o `_` fica como está escrito, e quem o lê como espaço é a busca do nome, em `push`.
|
|
962
|
+
*/
|
|
963
|
+
const inBrackets = line[(m.index ?? 0) - 1] === "[";
|
|
964
|
+
const stack = (inBrackets ? (m[1].split("]")[0] ?? "") : m[1]).trim();
|
|
901
965
|
// A stack already reading from the system is the point, not a problem.
|
|
902
966
|
if (stack.startsWith("var("))
|
|
903
967
|
continue;
|
package/dist/install-marks.js
CHANGED
|
@@ -333,7 +333,18 @@
|
|
|
333
333
|
* servidor diz que uma landing pedida em palavras livres passa primeiro pelo `landing`. Uma pasta escrita antes deixa o
|
|
334
334
|
* agente decidir a página inteira sozinho.
|
|
335
335
|
*/
|
|
336
|
-
|
|
336
|
+
/**
|
|
337
|
+
* 0.16.497 -> 0.16.498 em 15/10, e o passo 1 dá **SIM** (o pedido livre sai mastigado, a otimização): o `init` de um projeto
|
|
338
|
+
* novo grava no `.lock` os nomes que acabou de declarar, e o `template` e o `component` passam a escrevê-los - 51 nomes e 3
|
|
339
|
+
* valores crus no hero, contra 0 e 54 antes. Uma pasta escrita antes traz a landing com os valores à mão.
|
|
340
|
+
*/
|
|
341
|
+
/**
|
|
342
|
+
* 0.16.498 -> 0.16.499 em 15/10, e o passo 1 dá **SIM** (o defeito 1 da aula 01): o tamanho de texto pedido pelo PAPEL
|
|
343
|
+
* (`--ds-typography-role-sm-font-size`) passa a sair com o nome do degrau que vale o mesmo, quando o projeto o declara -
|
|
344
|
+
* `[font-size:var(--text-sm)]` em vez de `[font-size:0.875rem]`. Um componente escrito antes traz os tamanhos à mão. No
|
|
345
|
+
* mesmo PR, o `init` declara a largura da página (`--container-page`) e a seção a escreve pelo nome, não como `1440px`.
|
|
346
|
+
*/
|
|
347
|
+
export const MATERIALISER_SINCE = "0.16.499";
|
|
337
348
|
/**
|
|
338
349
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
339
350
|
*
|
|
@@ -546,7 +557,12 @@ export const COUNTED_DIFFERENTLY = "this run counts a value as named only when Y
|
|
|
546
557
|
* à que tem o nome dele. Medido no `codelevel`: 33 checagens do componente mais checado chegavam sem componente. Um
|
|
547
558
|
* hook anterior continua mandando essas checagens sem dono, e elas não contam para o degrau.
|
|
548
559
|
*/
|
|
549
|
-
|
|
560
|
+
/**
|
|
561
|
+
* 0.16.487 -> 0.16.499 em 15/10, e o passo 1 dá **SIM** (os defeitos 2 e 3 da aula 01): a checagem passa a acusar o tamanho
|
|
562
|
+
* de texto escrito à mão (`font-size`, `[font-size:X]`, `text-[X]`), e a família de fonte no colchete do Tailwind deixa de
|
|
563
|
+
* engolir o resto da linha. Um hook anterior diz "limpo" sobre um `[font-size:0.875rem]` com `--text-sm` declarado.
|
|
564
|
+
*/
|
|
565
|
+
export const CHECKER_SINCE = "0.16.499";
|
|
550
566
|
/**
|
|
551
567
|
* A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
|
|
552
568
|
*
|
|
@@ -1022,7 +1038,11 @@ export const CHECKER_SINCE = "0.16.487";
|
|
|
1022
1038
|
* mostra quando duas folhas brigam, deixa de fora a folha que ninguém carrega, conta o pages router como rota e diz os
|
|
1023
1039
|
* componentes em Vue, Svelte, Astro e Angular. Um censo lido antes guardou "names no token yet" onde havia tokens.
|
|
1024
1040
|
*/
|
|
1025
|
-
|
|
1041
|
+
/**
|
|
1042
|
+
* 0.16.485 -> 0.16.499 em 15/10, e o passo 1 dá **SIM** (o defeito 3 da aula 01): a varredura lê o tamanho de texto escrito
|
|
1043
|
+
* à mão, e o censo passa a gravá-lo. Um censo lido antes não tem os tamanhos na fila de trabalho dele.
|
|
1044
|
+
*/
|
|
1045
|
+
export const READER_SINCE = "0.16.499";
|
|
1026
1046
|
/**
|
|
1027
1047
|
* O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
|
|
1028
1048
|
*
|
|
@@ -12,7 +12,15 @@ const BOILERPLATE = new Set([
|
|
|
12
12
|
"--font-geist-sans",
|
|
13
13
|
"--font-geist-mono",
|
|
14
14
|
]);
|
|
15
|
-
const SKIP = new Set([
|
|
15
|
+
const SKIP = new Set([
|
|
16
|
+
"node_modules",
|
|
17
|
+
".next",
|
|
18
|
+
"dist",
|
|
19
|
+
"build",
|
|
20
|
+
"out",
|
|
21
|
+
"_synthesisui",
|
|
22
|
+
"coverage",
|
|
23
|
+
]);
|
|
16
24
|
const TAILWIND_IMPORT = /@import\s+[\x22\x27]tailwindcss[\x22\x27]/;
|
|
17
25
|
/**
|
|
18
26
|
* O COMENTÁRIO DO CSS SE ESCREVE COM `\u002a` NO FONTE, e não com o asterisco - a guarda de
|
|
@@ -78,7 +86,9 @@ export async function writeNewProjectTheme(root, tokensCss, slug, version) {
|
|
|
78
86
|
const withoutOurs = css.replace(END_RE(slug), "\n");
|
|
79
87
|
for (const m of withoutOurs.matchAll(/(?:^|[{;\s])(--[a-zA-Z0-9_-]+)\s*:/g)) {
|
|
80
88
|
const name = m[1];
|
|
81
|
-
if (!name.startsWith("--ds-") &&
|
|
89
|
+
if (!name.startsWith("--ds-") &&
|
|
90
|
+
!name.startsWith("--tw-") &&
|
|
91
|
+
!BOILERPLATE.has(name))
|
|
82
92
|
own.add(name);
|
|
83
93
|
}
|
|
84
94
|
}
|
|
@@ -112,7 +122,11 @@ export async function writeNewProjectTheme(root, tokensCss, slug, version) {
|
|
|
112
122
|
].join("\n");
|
|
113
123
|
const base = css.replace(END_RE(slug), "\n").replace(/\n*$/, "\n");
|
|
114
124
|
await writeFile(path, `${base}\n${block}\n`, "utf8");
|
|
115
|
-
return {
|
|
125
|
+
return {
|
|
126
|
+
written: relative(root, path).split("\\").join("/"),
|
|
127
|
+
count: lines.length,
|
|
128
|
+
pairs: systemPairs(tokensCss, (await readTheirs(root, slug)).every),
|
|
129
|
+
};
|
|
116
130
|
}
|
|
117
131
|
/**
|
|
118
132
|
* O VOCABULÁRIO DELE E O CSS GLOBAL, para quem escreve nomes fora do `init` - o `colours` (etapa 05, 28/09). A mesma
|
|
@@ -176,11 +190,17 @@ export async function writeNewProjectRoot(root, tokensCss, slug, version) {
|
|
|
176
190
|
].join("\n");
|
|
177
191
|
const base = css.replace(END_RE(slug), "\n").replace(/\n*$/, "\n");
|
|
178
192
|
await writeFile(path, `${base}\n${block}\n`, "utf8");
|
|
179
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
written: relative(root, path).split("\\").join("/"),
|
|
195
|
+
count: lines.length,
|
|
196
|
+
pairs: systemPairs(tokensCss, r.every),
|
|
197
|
+
};
|
|
180
198
|
}
|
|
181
199
|
const importLine = (slug) => `@import "./${slug}.css";`;
|
|
182
200
|
/** O CSS dele sem nada nosso: sem o bloco e sem a linha que importa o arquivo à parte. */
|
|
183
|
-
const withoutOurs = (css, slug) => css
|
|
201
|
+
const withoutOurs = (css, slug) => css
|
|
202
|
+
.replace(END_RE(slug), "\n")
|
|
203
|
+
.replace(new RegExp(`^${escape(importLine(slug))}\\n`, "m"), "");
|
|
184
204
|
async function readTheirs(root, slug) {
|
|
185
205
|
const sheets = await sheetsOf(root);
|
|
186
206
|
const raw = new Map();
|
|
@@ -228,10 +248,13 @@ async function readTheirs(root, slug) {
|
|
|
228
248
|
const everyRaw = new Map(raw);
|
|
229
249
|
for (const full of sheets.values())
|
|
230
250
|
for (const m of full.matchAll(/(?:^|[{;\s])(--[a-zA-Z0-9_-]+)\s*:\s*([^;}]+)/g))
|
|
231
|
-
if (!everyRaw.has(m[1]) &&
|
|
251
|
+
if (!everyRaw.has(m[1]) &&
|
|
252
|
+
!m[1].startsWith("--ds-"))
|
|
232
253
|
everyRaw.set(m[1], m[2].trim());
|
|
233
254
|
const every = resolved(everyRaw);
|
|
234
|
-
const entries = [...sheets]
|
|
255
|
+
const entries = [...sheets]
|
|
256
|
+
.filter(([, css]) => TAILWIND_IMPORT.test(css))
|
|
257
|
+
.map(([p]) => p);
|
|
235
258
|
const rel = (p) => relative(root, p).split("\\").join("/");
|
|
236
259
|
const top = [...counts].sort((a, b) => b[1] - a[1])[0];
|
|
237
260
|
const entry = entries.length === 1 ? entries[0] : null;
|
|
@@ -254,6 +277,32 @@ async function readTheirs(root, slug) {
|
|
|
254
277
|
* criou (`--ds-color-amber-400: var(--color-amber-400)`). Pular essas linhas encolhia o bloco na segunda
|
|
255
278
|
* rodada: medido na prova de 27/09, 82 nomes viraram 38.
|
|
256
279
|
*/
|
|
280
|
+
/** Os mesmos nomes de `systemNames`, com a variável nossa ao lado - o par que o `.lock` guarda. */
|
|
281
|
+
/**
|
|
282
|
+
* A LARGURA DA PÁGINA - a régua em que as seções do catálogo param o conteúdo (defeito 4 da aula 01, 15/10).
|
|
283
|
+
*
|
|
284
|
+
* Ela não é token do sistema: é a régua do catálogo (`apps/web/src/lib/catalog/page-width.ts`, pedido dele em 14/10), e as
|
|
285
|
+
* seções a escrevem como `var(--ds-layout-page-width, 1440px)`. Aqui ela ganha o nome que o Tailwind v4 lê
|
|
286
|
+
* (`--container-*`), e o par vai para o `.lock`: a seção materializada escreve `var(--container-page)`, e a pessoa muda a
|
|
287
|
+
* largura num lugar só. O valor é o MESMO do catálogo - `page-width-twin.spec.ts` compara os dois.
|
|
288
|
+
*/
|
|
289
|
+
export const PAGE_WIDTH = { ours: "--ds-layout-page-width", theirs: "--container-page", value: "1440px" };
|
|
290
|
+
function systemPairs(tokensCss, lookup = new Map()) {
|
|
291
|
+
const { names, values } = tongueOf([], tokensCss);
|
|
292
|
+
const out = [];
|
|
293
|
+
for (const ours of new Set([...values.keys(), ...names.keys()])) {
|
|
294
|
+
const theirs = theirNameFor(ours);
|
|
295
|
+
if (!theirs)
|
|
296
|
+
continue;
|
|
297
|
+
const pointed = names.get(ours);
|
|
298
|
+
const value = pointed ? lookup.get(pointed) : values.get(ours);
|
|
299
|
+
if (value && !value.includes("var(--ds-"))
|
|
300
|
+
out.push({ ours, theirs, value });
|
|
301
|
+
}
|
|
302
|
+
if (out.length > 0)
|
|
303
|
+
out.push({ ...PAGE_WIDTH });
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
257
306
|
function systemNames(tokensCss, lookup = new Map()) {
|
|
258
307
|
const { names, values } = tongueOf([], tokensCss);
|
|
259
308
|
const out = [];
|
|
@@ -266,6 +315,8 @@ function systemNames(tokensCss, lookup = new Map()) {
|
|
|
266
315
|
if (value && !value.includes("var(--ds-"))
|
|
267
316
|
out.push([theirs, value]);
|
|
268
317
|
}
|
|
318
|
+
if (out.length > 0)
|
|
319
|
+
out.push([PAGE_WIDTH.theirs, PAGE_WIDTH.value]);
|
|
269
320
|
return out;
|
|
270
321
|
}
|
|
271
322
|
const same = (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
@@ -273,7 +324,9 @@ export async function readProjectTheme(root, tokensCss, slug) {
|
|
|
273
324
|
const r = await readTheirs(root, slug);
|
|
274
325
|
const base = { entry: r.entry, tokensFile: r.tokensFile };
|
|
275
326
|
const colourValues = [
|
|
276
|
-
...new Set(systemNames(tokensCss, r.every)
|
|
327
|
+
...new Set(systemNames(tokensCss, r.every)
|
|
328
|
+
.filter(([n]) => n.startsWith("--color-"))
|
|
329
|
+
.map(([, v]) => v.toLowerCase())),
|
|
277
330
|
];
|
|
278
331
|
const held = new Set([...r.values.values()].map((v) => v.trim().toLowerCase()));
|
|
279
332
|
const matched = colourValues.filter((v) => held.has(v)).length;
|
|
@@ -284,7 +337,11 @@ export async function readProjectTheme(root, tokensCss, slug) {
|
|
|
284
337
|
return { kind: "many-tailwind-entries", ...counts, ...base };
|
|
285
338
|
if (r.own.size === 0)
|
|
286
339
|
return { kind: "new", ...counts, ...base };
|
|
287
|
-
return {
|
|
340
|
+
return {
|
|
341
|
+
kind: matched * 2 >= colourValues.length && matched > 0 ? "matches" : "differs",
|
|
342
|
+
...counts,
|
|
343
|
+
...base,
|
|
344
|
+
};
|
|
288
345
|
}
|
|
289
346
|
/**
|
|
290
347
|
* O BLOCO DO SISTEMA AO LADO DO DELE - o terceiro caso.
|
|
@@ -305,7 +362,9 @@ export async function readProjectTheme(root, tokensCss, slug) {
|
|
|
305
362
|
export async function writeBesideTheirs(root, tokensCss, slug, version, where) {
|
|
306
363
|
const r = await readTheirs(root, slug);
|
|
307
364
|
const entryPath = r.entry ? join(root, r.entry) : null;
|
|
308
|
-
const separatePath = entryPath
|
|
365
|
+
const separatePath = entryPath
|
|
366
|
+
? join(dirname(entryPath), `${slug}.css`)
|
|
367
|
+
: null;
|
|
309
368
|
/** Tira de todo lugar o que é nosso, antes de escrever no lugar escolhido. */
|
|
310
369
|
const cleaned = new Map();
|
|
311
370
|
for (const [path, css] of r.sheets) {
|
|
@@ -317,7 +376,9 @@ export async function writeBesideTheirs(root, tokensCss, slug, version, where) {
|
|
|
317
376
|
for (const [path, css] of cleaned)
|
|
318
377
|
if (css !== r.sheets.get(path))
|
|
319
378
|
await writeFile(path, css, "utf8");
|
|
320
|
-
if (separatePath &&
|
|
379
|
+
if (separatePath &&
|
|
380
|
+
r.sheets.get(separatePath)?.startsWith(begin(slug)) &&
|
|
381
|
+
where !== "separate")
|
|
321
382
|
await rm(separatePath, { force: true });
|
|
322
383
|
};
|
|
323
384
|
const empty = { written: null, collisions: [], throughInline: [], added: 0 };
|
|
@@ -332,7 +393,9 @@ export async function writeBesideTheirs(root, tokensCss, slug, version, where) {
|
|
|
332
393
|
}
|
|
333
394
|
const missing = system.filter(([n]) => !r.own.has(n) && !r.values.has(n));
|
|
334
395
|
const collisions = system
|
|
335
|
-
.filter(([n, v]) => r.values.has(n) &&
|
|
396
|
+
.filter(([n, v]) => r.values.has(n) &&
|
|
397
|
+
!r.inline.has(n) &&
|
|
398
|
+
!same(r.values.get(n), v))
|
|
336
399
|
.map(([n]) => n);
|
|
337
400
|
const throughInline = [];
|
|
338
401
|
const scoped = system.map(([n, v]) => {
|
|
@@ -346,7 +409,9 @@ export async function writeBesideTheirs(root, tokensCss, slug, version, where) {
|
|
|
346
409
|
const block = [
|
|
347
410
|
begin(slug),
|
|
348
411
|
open(`${slug} v${version}, beside your own tokens. The @theme adds only the names your project did not have; inside the data-theme selector below every value is the system's, including the names you already use, which keep their value everywhere else. Written by \`init\`, where you chose.`),
|
|
349
|
-
...(missing.length > 0
|
|
412
|
+
...(missing.length > 0
|
|
413
|
+
? ["@theme {", ...missing.map(([n, v]) => ` ${n}: ${v};`), "}", ""]
|
|
414
|
+
: []),
|
|
350
415
|
`[data-theme="${slug}"] {`,
|
|
351
416
|
...scoped,
|
|
352
417
|
"}",
|
package/dist/their-tongue.js
CHANGED
|
@@ -71,7 +71,8 @@ export function inTheirTongue(css, tongue, destination = "stylesheet") {
|
|
|
71
71
|
inlined += 1;
|
|
72
72
|
if (tongue.frozen.has(name))
|
|
73
73
|
froze += 1;
|
|
74
|
-
|
|
74
|
+
/** Nas duas saídas - a classe do Tailwind também (defeito 1 da aula 01, 15/10); `write` escapa o espaço. */
|
|
75
|
+
const font = viaNextFont(name, value, tongue);
|
|
75
76
|
return write(font ?? value);
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
@@ -155,6 +156,8 @@ artifacts) {
|
|
|
155
156
|
*/
|
|
156
157
|
export function tongueOf(map, installedCss) {
|
|
157
158
|
const names = new Map(map.map((p) => [p.ours, p.theirs]));
|
|
159
|
+
/** O valor que o `.lock` mediu para cada par - a folha reescrita já não o declara (`var(--text-sm)`). */
|
|
160
|
+
const pairValue = new Map(map.flatMap((p) => (p.value ? [[p.ours, p.value]] : [])));
|
|
158
161
|
const values = new Map();
|
|
159
162
|
/**
|
|
160
163
|
* O que cada variável nossa declara na folha, antes de qualquer indireção ser seguida.
|
|
@@ -226,6 +229,32 @@ export function tongueOf(map, installedCss) {
|
|
|
226
229
|
else
|
|
227
230
|
values.set(ours, destination.value);
|
|
228
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* O PAPEL DE TIPO FALA O NOME DO DEGRAU QUE VALE O MESMO (defeito 1 da aula 01, 15/10).
|
|
234
|
+
*
|
|
235
|
+
* O componente pede o tamanho pelo PAPEL (`--ds-typography-role-sm-font-size`), e o nome dele só existe para o DEGRAU da
|
|
236
|
+
* escala (`--ds-typography-scale-sm-font-size` -> `--text-sm`). O papel saía como `[font-size:0.875rem]` num projeto
|
|
237
|
+
* que declara `--text-sm: 0.875rem`, e o `doctor` acusava na hora um valor com nome esperando.
|
|
238
|
+
*
|
|
239
|
+
* Só quando o MESMO degrau (`role-sm` -> `scale-sm`) JÁ tem nome dele e vale exatamente o mesmo, na mesma propriedade -
|
|
240
|
+
* é a troca que o `--fix` faria depois. O papel nunca vira um degrau que o projeto não declara: sem nome, continua o valor.
|
|
241
|
+
*/
|
|
242
|
+
for (const [ours, value] of values) {
|
|
243
|
+
const role = /^--ds-typography-role-(.+)-(font-size|line-height|letter-spacing)$/.exec(ours);
|
|
244
|
+
if (!role)
|
|
245
|
+
continue;
|
|
246
|
+
/** O MESMO degrau, nunca outro que por acaso vale o mesmo: `role-sm` -> `scale-sm` (prova de 15/10). */
|
|
247
|
+
const step = `--ds-typography-scale-${role[1]}-${role[2]}`;
|
|
248
|
+
const theirName = names.get(step);
|
|
249
|
+
if (!theirName)
|
|
250
|
+
continue;
|
|
251
|
+
const written = declared.get(step);
|
|
252
|
+
const stepValue = written?.startsWith("var(") ? pairValue.get(step) : written;
|
|
253
|
+
if (stepValue === undefined || stepValue.trim().toLowerCase() !== value.trim().toLowerCase())
|
|
254
|
+
continue;
|
|
255
|
+
names.set(ours, theirName);
|
|
256
|
+
values.delete(ours);
|
|
257
|
+
}
|
|
229
258
|
/**
|
|
230
259
|
* AS QUE MUDAM ENTRE ESQUEMAS E VIRAM LITERAL - o subconjunto que congela.
|
|
231
260
|
*
|
package/dist/their-vars.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
-
import { readStyleSheet } from "./doctor/scss-lens.js";
|
|
3
2
|
import { join } from "node:path";
|
|
3
|
+
import { readStyleSheet } from "./doctor/scss-lens.js";
|
|
4
4
|
import { theirNames } from "./doctor/their-names.js";
|
|
5
5
|
import { buildTable, normalizeValue, } from "./doctor/tokens.js";
|
|
6
6
|
import { FAMILY_SEAM_PREFIX } from "./fonts.js";
|
|
@@ -268,3 +268,13 @@ export function danglingTheirVars(installedCss, theirs) {
|
|
|
268
268
|
}
|
|
269
269
|
return out;
|
|
270
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* OS NOMES QUE O CSS DELE AINDA DECLARA, dentre os pedidos - para o `add` não apagar o par que o `init` gravou
|
|
273
|
+
* (jornada "o pedido livre sai mastigado", 15/10). Ver o uso em `commands/add.ts`.
|
|
274
|
+
*/
|
|
275
|
+
export async function namesTheyDeclare(root, names) {
|
|
276
|
+
if (names.length === 0)
|
|
277
|
+
return new Set();
|
|
278
|
+
const theirs = await harvestTheirCss(root);
|
|
279
|
+
return new Set(names.filter((n) => theirs.byName.has(n)));
|
|
280
|
+
}
|
package/package.json
CHANGED