synthesisui 0.16.421 → 0.16.423
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-md.js +19 -38
- package/dist/commands/add.js +57 -94
- package/dist/commands/component.js +96 -201
- package/dist/commands/connect.js +41 -0
- package/dist/commands/doctor.js +74 -428
- package/dist/commands/generate.js +25 -4
- package/dist/commands/import.js +2 -2
- package/dist/commands/init.js +13 -10
- package/dist/commands/refit.js +13 -2
- package/dist/commands/summary.js +9 -5
- package/dist/commands/sync.js +84 -0
- package/dist/commands/template.js +81 -53
- package/dist/commands/upgrade.js +5 -3
- package/dist/commands/use.js +7 -8
- package/dist/component-codegen.js +50 -23
- package/dist/copy/connect.pt-BR.js +3 -0
- package/dist/doctor/apply-fix.js +2 -2
- package/dist/doctor/scan.js +33 -2
- package/dist/doctor/their-names.js +8 -2
- package/dist/fonts.js +29 -5
- package/dist/global-sheet.js +14 -125
- package/dist/guide.js +131 -83
- package/dist/install-marks.js +4 -4
- package/dist/local-edits.js +257 -0
- package/dist/project-facts.js +138 -32
- package/dist/recipe-css.js +249 -0
- package/dist/skills.js +5 -16
- package/dist/their-tongue.js +180 -43
- package/dist/wired-slugs.js +31 -17
- package/package.json +1 -1
- package/dist/setup-prompt.js +0 -95
- package/dist/sheet-needed.js +0 -317
- package/dist/skill-configure.js +0 -13
- package/dist/wiring-read.js +0 -212
|
@@ -470,8 +470,16 @@ const scaleKey = (v) => {
|
|
|
470
470
|
const m = v.match(/^\{typography\.scale\.([a-zA-Z0-9-]+)\.fontSize\}$/);
|
|
471
471
|
return m ? kebab(m[1]) : null;
|
|
472
472
|
};
|
|
473
|
-
/**
|
|
474
|
-
*
|
|
473
|
+
/**
|
|
474
|
+
* "{color.semantic.primary}" → "var(--ds-color-semantic-primary)" - a grafia INTERNA, que a porta
|
|
475
|
+
* de tradução (`speak`) reescreve no nome dele ou no valor literal antes de qualquer byte ir a
|
|
476
|
+
* disco.
|
|
477
|
+
*
|
|
478
|
+
* ELA NUNCA SOBREVIVE ATÉ O ARQUIVO DELE (`INV-GERAL-13`): `speak` é a última coisa que roda em
|
|
479
|
+
* `generateComponentFiles`, e `nothing-of-ours-is-asked-for.spec.ts` reprova o texto que sair daqui
|
|
480
|
+
* com a nossa grafia dentro. Existe como forma intermediária porque é ela que carrega o caminho da
|
|
481
|
+
* referência (`color.semantic.primary`) até a folha que sabe o valor.
|
|
482
|
+
*/
|
|
475
483
|
const refToDsVar = (v) => v.replace(/\{([a-z0-9.-]+)\}/gi, (_, path) => {
|
|
476
484
|
return `var(--ds-${path.split(".").map(kebab).join("-")})`;
|
|
477
485
|
});
|
|
@@ -531,25 +539,28 @@ function remToTwScale(value) {
|
|
|
531
539
|
return Number.isInteger(n) && n >= 0 && n <= 96 ? String(n) : null;
|
|
532
540
|
}
|
|
533
541
|
/**
|
|
534
|
-
* O UTILITÁRIO NOMEADO SÓ VALE QUANDO
|
|
542
|
+
* O UTILITÁRIO NOMEADO SÓ VALE QUANDO O `@theme` **DELE** O DECLARA - e é isso que decide o VALOR.
|
|
535
543
|
*
|
|
536
544
|
* `rounded-lg` existe em qualquer projeto com Tailwind, então a classe nunca "falta". O que muda é
|
|
537
|
-
* de quem é o valor: quando o
|
|
538
|
-
*
|
|
539
|
-
* mente em silêncio, que é pior que uma classe morta, porque nada na tela parece faltar.
|
|
545
|
+
* de quem é o valor: quando o `@theme` do projeto dele declara `--radius-lg`, a classe pinta o raio
|
|
546
|
+
* DELE, e a decisão continua sendo dele; quando não declara, ela pinta o default do Tailwind - e o
|
|
547
|
+
* componente mente em silêncio, que é pior que uma classe morta, porque nada na tela parece faltar.
|
|
540
548
|
*
|
|
541
|
-
* A
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
549
|
+
* A PERGUNTA ERA SOBRE A NOSSA FOLHA ATÉ 11/09, e era a última dependência de runtime que o código
|
|
550
|
+
* entregue carregava. `rounded-lg` escrito porque o NOSSO `theme.css` declara `--radius-lg` só pinta
|
|
551
|
+
* o sistema num app que importa aquele arquivo - e `INV-GERAL-13` diz que nenhum app dele importa. O
|
|
552
|
+
* componente saía com a aparência certa na nossa cabeça e o raio do Tailwind na tela dele.
|
|
545
553
|
*
|
|
546
|
-
*
|
|
554
|
+
* QUANDO ELE NÃO DECLARA, a resposta é o valor arbitrário - e a porta de tradução o resolve no nome
|
|
555
|
+
* dele ou no literal, nunca numa variável nossa.
|
|
547
556
|
*
|
|
548
|
-
*
|
|
557
|
+
* MEDIDO em 07/09 sobre TODOS os componentes das duas populações, com a folha NOSSA respondendo:
|
|
558
|
+
*
|
|
559
|
+
* codelevel, repo real 58 de 110 utilitários de token (53%) já caíam no arbitrário
|
|
549
560
|
* ember, app novo 34 de 241 (14%)
|
|
550
561
|
*
|
|
551
|
-
* `null` é
|
|
552
|
-
*
|
|
562
|
+
* `null` é "ninguém mediu" - `refit` e `generate` já dizem que nada pinta sem o `add`, e ali o nome
|
|
563
|
+
* legível não custa nada.
|
|
553
564
|
*/
|
|
554
565
|
const minted = (themeVars, cssVar) => themeVars === null || themeVars.has(cssVar);
|
|
555
566
|
/** One declaration → Tailwind classes (pretty when mappable, arbitrary-property
|
|
@@ -890,15 +901,27 @@ function variantOwnedProps(variants, axis) {
|
|
|
890
901
|
return [...props];
|
|
891
902
|
}
|
|
892
903
|
// ── Emission ─────────────────────────────────────────────────────────────────
|
|
904
|
+
/**
|
|
905
|
+
* O CABEÇALHO DO ARQUIVO DELE - e as duas linhas de setup saíram dele em 11/09.
|
|
906
|
+
*
|
|
907
|
+
* O QUE ELAS DIZIAM, dentro de TODO `.tsx` que a plataforma escreve no repositório dele:
|
|
908
|
+
* *"Global setup (once per app): import _synthesisui/ds/<slug>/tokens.css"* e *"put data-ds=<slug>
|
|
909
|
+
* on a root element"*. Era a instrução que o produto promete não dar, escrita no lugar onde ela
|
|
910
|
+
* sobrevive a qualquer conserto do terminal - e ela ficava lá, num arquivo dele, para todo agente
|
|
911
|
+
* e toda pessoa lerem depois (`INV-GERAL-13`).
|
|
912
|
+
*
|
|
913
|
+
* E ELA FAZIA UM SEGUNDO ESTRAGO, medido em 10/09: `readWiring` procurava essas mesmas strings no
|
|
914
|
+
* repositório para responder *"este projeto carrega o sistema?"*, então a partir do primeiro
|
|
915
|
+
* componente que NÓS escrevemos ela respondia `imported: true` sobre um projeto que não importa
|
|
916
|
+
* nada. Um comentário nosso nunca provou nada sobre o projeto dele.
|
|
917
|
+
*
|
|
918
|
+
* O que fica é a PROCEDÊNCIA - quem escreveu, de qual sistema, em que versão. Ela não pede nada; ela
|
|
919
|
+
* responde a pergunta de quem abre o arquivo daqui a seis meses.
|
|
920
|
+
*/
|
|
893
921
|
function header(slug, name, version, mode) {
|
|
894
|
-
const setup = mode === "tailwind"
|
|
895
|
-
? `import _synthesisui/ds/${slug}/theme.css (Tailwind adapter) + tokens.css`
|
|
896
|
-
: `import _synthesisui/ds/${slug}/tokens.css`;
|
|
897
922
|
return [
|
|
898
923
|
`// Generated by SynthesisUI - "${name}" from the "${slug}" design system (v${version}).`,
|
|
899
|
-
`//
|
|
900
|
-
`// Global setup (once per app): ${setup}`,
|
|
901
|
-
`// and put data-ds="${slug}" on a root element (e.g. <body data-ds="${slug}">).`,
|
|
924
|
+
`// Every value here is a name YOUR code declares, or the value itself - nothing to import.`,
|
|
902
925
|
...(mode === "tailwind" ? [OVERRIDE_WARNING] : []),
|
|
903
926
|
].join("\n");
|
|
904
927
|
}
|
|
@@ -1507,12 +1530,16 @@ scheme,
|
|
|
1507
1530
|
*/
|
|
1508
1531
|
tongue,
|
|
1509
1532
|
/**
|
|
1510
|
-
* O QUE
|
|
1533
|
+
* O QUE O `@theme` **DELE** DECLARA - `theirThemeVars(root)`.
|
|
1511
1534
|
*
|
|
1512
1535
|
* OBRIGATÓRIO, pelo mesmo argumento do `scheme` e do `tongue`: o utilitário nomeado que este
|
|
1513
|
-
* módulo escreve só carrega
|
|
1536
|
+
* módulo escreve só carrega um valor de design quando o `@theme` do PROJETO dele declara a
|
|
1514
1537
|
* variável. Sem essa resposta o codegen escreve `rounded-lg` e a classe pinta o default do
|
|
1515
|
-
* Tailwind - o componente mente sem nada faltar na tela.
|
|
1538
|
+
* Tailwind - o componente mente sem nada faltar na tela.
|
|
1539
|
+
*
|
|
1540
|
+
* ERA A NOSSA FOLHA QUE RESPONDIA ISTO, e essa era a última dependência de runtime que o código
|
|
1541
|
+
* entregue ainda tinha (`INV-GERAL-13`, 11/09). `null` é "ninguém mediu", e continua significando
|
|
1542
|
+
* que o nome legível não custa nada - `refit` e `generate` já dizem que nada pinta sem o `add`.
|
|
1516
1543
|
*/
|
|
1517
1544
|
themeVars) {
|
|
1518
1545
|
const files = [];
|
|
@@ -66,6 +66,9 @@ register("pt-BR", {
|
|
|
66
66
|
"already current": "já está em dia",
|
|
67
67
|
"updated to this CLI's pipeline": "atualizada para esta versão",
|
|
68
68
|
"removed - renamed to /sui-import-ds": "removida - virou /sui-import-ds",
|
|
69
|
+
/** A skill aposentada fica no repositório dele, nomeada - ver `RETIRED_SKILLS`. */
|
|
70
|
+
"retired - nothing of ours loads in your app any more": "aposentada - nada nosso carrega no seu app",
|
|
71
|
+
"it stays in your repo - yours to delete": "ela fica no seu repositório - sua para apagar",
|
|
69
72
|
// ── onde o bloco de regras caiu ──
|
|
70
73
|
"how to turn this repo into your system": "como transformar este repo no seu sistema",
|
|
71
74
|
"rewritten for what is installed": "reescrito para o que está instalado",
|
package/dist/doctor/apply-fix.js
CHANGED
|
@@ -437,7 +437,7 @@ export function describeFix(result, dry) {
|
|
|
437
437
|
const lines = [];
|
|
438
438
|
if (applied.length === 0) {
|
|
439
439
|
lines.push(decisions > 0
|
|
440
|
-
? `Nothing to apply. All ${decisions} findings are values your
|
|
440
|
+
? `Nothing to apply. All ${decisions} findings are values your own code names nowhere - those are design decisions, not fixes. Name one and \`--fix\` picks it up.`
|
|
441
441
|
: relative > 0
|
|
442
442
|
? `Nothing to apply. All ${relative} findings are lengths in \`em\`, which follow the element's font size - swapping them can move the layout, so that call is yours.`
|
|
443
443
|
: /**
|
|
@@ -531,6 +531,6 @@ export function describeFix(result, dry) {
|
|
|
531
531
|
const rewritten = new Set(applied.map((a) => `${a.file}:${a.line}`));
|
|
532
532
|
const halfWritten = skipped.filter((s) => rewritten.has(`${s.file}:${s.line}`)).length;
|
|
533
533
|
if (halfWritten > 0)
|
|
534
|
-
lines.push(` ${halfWritten} of those ${halfWritten === 1 ? "sits" : "sit"} on a line this command just rewrote - a shorthand comes back with a token and a literal side by side (\`padding: 8px var(--
|
|
534
|
+
lines.push(` ${halfWritten} of those ${halfWritten === 1 ? "sits" : "sit"} on a line this command just rewrote - a shorthand comes back with a token and a literal side by side (\`padding: 8px var(--spacing-24)\`). See them by value: npx synthesisui doctor --migrate`);
|
|
535
535
|
return lines;
|
|
536
536
|
}
|
package/dist/doctor/scan.js
CHANGED
|
@@ -12,14 +12,40 @@
|
|
|
12
12
|
import { utilitiesOn } from "./idiom-names.js";
|
|
13
13
|
import { normalizeValue, tokenMatch } from "./tokens.js";
|
|
14
14
|
/**
|
|
15
|
-
* O NOME QUE SE ESCREVE NO ARQUIVO DELE - e
|
|
15
|
+
* O NOME QUE SE ESCREVE NO ARQUIVO DELE - e ele é SEMPRE do vocabulário dele (`INV-GERAL-13`).
|
|
16
16
|
*
|
|
17
17
|
* Seis lugares decidem o que a pessoa vê ou o que o `--fix` grava: o relatório, o plano, o hook, o
|
|
18
18
|
* MCP, a aplicação e a contagem. Deixar cada um lembrar de preferir `theirToken` é o padrão do
|
|
19
19
|
* argumento opcional que se esquece - e o sintoma seria o pior possível: o comando aconselhando um
|
|
20
20
|
* nome e o hook aconselhando outro, sobre o mesmo arquivo.
|
|
21
|
+
*
|
|
22
|
+
* O NOSSO NOME SAIU DAQUI EM 11/09, e ele era a metade que contrariava a decisão de 07/09.
|
|
23
|
+
*
|
|
24
|
+
* A regra é literal: *"tem nome no sistema DELE? SIM -> a nossa receita usa o nome dele. NÃO ->
|
|
25
|
+
* mantém HARDCODED, e a plataforma só AVISA"*. Esta função devolvia `theirToken ?? token`, e o
|
|
26
|
+
* `token` é o nome COMPILADO do sistema - a grafia `--ds-*`, que é invenção nossa. Então o comando
|
|
27
|
+
* que promete adotar o sistema trocava um literal que funciona por uma variável que só resolve com
|
|
28
|
+
* uma folha nossa carregada no app dele.
|
|
29
|
+
*
|
|
30
|
+
* MEDIDO no `codelevel` em 04/09, sobre as 216 trocas com nome esperando: **60 (28%) escreviam a
|
|
31
|
+
* nossa grafia**. Eram exatamente as que exigiam o `@import` - e para protegê-las o comando recusava
|
|
32
|
+
* as outras 156 por inteiro, cobrando a folha em troca.
|
|
33
|
+
*
|
|
34
|
+
* O valor que só o nosso lado nomeia não desaparece do relatório: ele vira AVISO, com o valor e a
|
|
35
|
+
* linha, e a decisão de batizá-lo continua sendo dele. Ver `ourNameOnly`.
|
|
21
36
|
*/
|
|
22
|
-
export const nameToWrite = (f) => f.theirToken ?? f.token;
|
|
37
|
+
export const nameToWrite = (f) => f.theirToken ?? (f.tokenIsTheirs ? f.token : null);
|
|
38
|
+
/**
|
|
39
|
+
* O VALOR QUE **SÓ** O NOSSO LADO NOMEIA - o que a plataforma AVISA em vez de escrever.
|
|
40
|
+
*
|
|
41
|
+
* É a outra metade de `nameToWrite`, e ela existe para o silêncio não acontecer: um achado sem nome
|
|
42
|
+
* dele e com nome nosso some das duas listas se ninguém perguntar por ele, e sumir é
|
|
43
|
+
* indistinguível de "está tudo nomeado" para quem lê (lei 8).
|
|
44
|
+
*
|
|
45
|
+
* Quem chama DIZ o valor e onde ele está - nunca propõe o nome. É a escolha dele de 10/09 entre as
|
|
46
|
+
* duas alternativas, e a mesma que o hook já segue.
|
|
47
|
+
*/
|
|
48
|
+
export const ourNameOnly = (f) => !f.theirToken && !f.tokenIsTheirs && Boolean(f.token);
|
|
23
49
|
/**
|
|
24
50
|
* `next/og` renders JSX to a PNG on the server. There is no document, so there
|
|
25
51
|
* is no `var(--ds-*)` to read: every colour in such a file MUST be a literal.
|
|
@@ -607,6 +633,8 @@ function scanCore(file, source, table) {
|
|
|
607
633
|
* `--spacing` no vocabulário dele, e qual dos dois está certo depende de onde o literal está.
|
|
608
634
|
*/
|
|
609
635
|
const theirs = table.aliases.get(`${kind}:${normalizeValue(literal, table.rootPx)}`);
|
|
636
|
+
/** A procedência do `token`, lida da tabela que o achou - ver `tokenIsTheirs`. */
|
|
637
|
+
const tableIsTheirs = table.source !== null && table.source !== "installed";
|
|
610
638
|
findings.push({
|
|
611
639
|
kind,
|
|
612
640
|
line: at,
|
|
@@ -617,6 +645,9 @@ function scanCore(file, source, table) {
|
|
|
617
645
|
? { fontRelative: true }
|
|
618
646
|
: {}),
|
|
619
647
|
...(theirs?.writable ? { theirToken: theirs.name } : {}),
|
|
648
|
+
...(tableIsTheirs && match?.token
|
|
649
|
+
? { tokenIsTheirs: true }
|
|
650
|
+
: {}),
|
|
620
651
|
/**
|
|
621
652
|
* O NOME QUE SÓ SE DIZ - `$gray_dark` do Sass, `@brand` do Less.
|
|
622
653
|
*
|
|
@@ -196,8 +196,14 @@ export function theirNames(ours, theirs) {
|
|
|
196
196
|
* 66 delas gravadas por um `--fix --write`, e a pior na PRIMEIRA página do relatório -
|
|
197
197
|
* `0.25em → --radius-xs` em 116 arquivos. Ver `FAMILY_WORDS`.
|
|
198
198
|
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
199
|
+
* E DESDE 11/09 RECUSAR AQUI SIGNIFICA SILÊNCIO, não um nome nosso no lugar.
|
|
200
|
+
*
|
|
201
|
+
* Esta linha dizia que recusar não perdia informação, *"porque sem alias `nameToWrite` cai no
|
|
202
|
+
* NOSSO token"*. Ele não cai mais: a nossa grafia deixou de ser escrita no código dele
|
|
203
|
+
* (`INV-GERAL-13`), então um valor cuja categoria não fecha sai da lista de trocas em vez de
|
|
204
|
+
* sair com o nosso nome. Isso é o desfecho certo - a troca errada era escrever um token de
|
|
205
|
+
* tipo num `border-radius` -, e o valor não desaparece: ele é DITO, com o valor e a linha,
|
|
206
|
+
* pelo caminho que `ourNameOnly` alimenta.
|
|
201
207
|
*/
|
|
202
208
|
const usable = formDecides(value)
|
|
203
209
|
? candidates
|
package/dist/fonts.js
CHANGED
|
@@ -89,7 +89,7 @@ export function googleFontsHref(families) {
|
|
|
89
89
|
*/
|
|
90
90
|
export const FAMILY_SEAM_PREFIX = "--ds-typography-families-";
|
|
91
91
|
export function nextFontSnippet(input) {
|
|
92
|
-
const { families,
|
|
92
|
+
const { families, appDir = "app", facts, declaredWeights } = input;
|
|
93
93
|
/**
|
|
94
94
|
* A ORDEM É DETERMINÍSTICA e os duplicados saem: isto vira uma linha de arquivo gerado, e um
|
|
95
95
|
* conjunto que muda de ordem entre rodadas produz diff onde nada mudou.
|
|
@@ -219,15 +219,39 @@ export function nextFontSnippet(input) {
|
|
|
219
219
|
* projeto dele deixaria de compilar por causa da linha que a gente mandou colar.
|
|
220
220
|
*/
|
|
221
221
|
const emitted = roles.filter((role) => constOf(role) !== undefined);
|
|
222
|
+
/**
|
|
223
|
+
* O `data-ds` SAIU DO LAYOUT DELE EM 11/09 - `INV-GERAL-13`.
|
|
224
|
+
*
|
|
225
|
+
* O atributo existia para a NOSSA folha aplicar dentro dele, e nenhum app dele carrega a nossa
|
|
226
|
+
* folha. O que resta na linha é o que o `next/font` exige e é dele: a classe que carrega a
|
|
227
|
+
* variável de cada fonte.
|
|
228
|
+
*/
|
|
222
229
|
const layout = [
|
|
223
230
|
`// ${appDir}/layout.tsx`,
|
|
224
231
|
`import { ${[...new Set(emitted.map(constOf))].join(", ")} } from "./fonts";`,
|
|
225
|
-
`<body
|
|
232
|
+
`<body className={\`${[...new Set(emitted.map((r) => `\${${constOf(r)}.variable}`))].join(" ")}\`}>`,
|
|
226
233
|
];
|
|
234
|
+
/**
|
|
235
|
+
* O BLOCO DE CSS PASSA A ESCREVER NO VOCABULÁRIO **DELE** - o conserto de 11/09.
|
|
236
|
+
*
|
|
237
|
+
* O QUE ELE ESCREVIA: um `[data-ds="<slug>"]` com `--ds-typography-families-<role>` apontando para
|
|
238
|
+
* a fonte. Nosso escopo, nossa variável, dentro da folha dele - e só resolvia com a nossa folha
|
|
239
|
+
* carregada, que é o que `INV-GERAL-13` proíbe.
|
|
240
|
+
*
|
|
241
|
+
* O QUE ELE ESCREVE AGORA: um `@theme` com `--font-<role>`, que é o namespace de fonte do Tailwind
|
|
242
|
+
* DELE. Duas coisas acontecem de uma vez, e as duas são dele: a utility `font-<role>` passa a
|
|
243
|
+
* existir no projeto dele carregando a fonte que o `next/font` baixou, e `theirThemeVars` -
|
|
244
|
+
* o leitor que decide o utilitário nomeado no codegen - passa a ver aquele nome. Daí em diante o
|
|
245
|
+
* componente que a plataforma escreve veste `font-<role>` em vez de um literal, porque o `@theme`
|
|
246
|
+
* DELE declara o nome.
|
|
247
|
+
*
|
|
248
|
+
* Nenhuma linha aqui menciona o sistema, o slug ou uma variável nossa: é a fonte dele, ligada ao
|
|
249
|
+
* Tailwind dele.
|
|
250
|
+
*/
|
|
227
251
|
const css = [
|
|
228
|
-
`/* ${appDir}/globals.css -
|
|
229
|
-
|
|
230
|
-
...emitted.map((role) => `
|
|
252
|
+
`/* ${appDir}/globals.css - in your own @theme */`,
|
|
253
|
+
`@theme {`,
|
|
254
|
+
...emitted.map((role) => ` --font-${role}: var(${roleVar(role)});`),
|
|
231
255
|
`}`,
|
|
232
256
|
];
|
|
233
257
|
/**
|
package/dist/global-sheet.js
CHANGED
|
@@ -1,134 +1,23 @@
|
|
|
1
|
-
import { readdir
|
|
2
|
-
import {
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
3
|
import { exists } from "./agent-wiring.js";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* AS PASTAS DE APP DESTE REPOSITÓRIO - e era um módulo sobre a folha global dele até 11/09.
|
|
6
6
|
*
|
|
7
|
-
* O QUE
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* styles/"*. A instrução apontava para o arquivo errado, e a convenção certa estava escrita ali do
|
|
11
|
-
* lado.
|
|
7
|
+
* O QUE MORAVA AQUI: `globalSheetOf`, `sheetChainOf`, `prefixFrom` e o resolvedor de `exports` do
|
|
8
|
+
* workspace. Os quatro existiam para uma coisa - achar a folha que os apps dele realmente carregam,
|
|
9
|
+
* e contar o `../` até a raiz, para a instrução de `@import` apontar para o arquivo certo.
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* Era a parte mais cuidadosa daquele texto. MEDIDO em 01/09 no `codelevel`: os dois apps dele fazem
|
|
12
|
+
* `@import "@repo/ui/styles/globals.css"`, e o `globals.css` do app diz, em comentário DELE, *"Do
|
|
13
|
+
* not redeclare tokens or fonts here"*. A instrução apontava para o arquivo errado, com o caminho
|
|
14
|
+
* relativo de outro, e o conserto foi seguir a cadeia em vez de assumir.
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* 2. procura `@import "<spec>"` que aponte para um CSS DENTRO deste repositório
|
|
22
|
-
* 3. resolve o `<spec>`: caminho relativo, ou pacote do workspace pelo `exports`
|
|
23
|
-
* 4. repete na folha encontrada, até ela não re-exportar mais
|
|
24
|
-
*
|
|
25
|
-
* O QUE NÃO É SEGUIDO: `tailwindcss` e qualquer coisa que não resolva para um arquivo daqui. Um
|
|
26
|
-
* `@import "tailwindcss"` é a biblioteca, e mandar o cliente editá-la seria pior que a instrução que
|
|
27
|
-
* isto conserta.
|
|
16
|
+
* O TEXTO INTEIRO DEIXOU DE EXISTIR (`INV-GERAL-13`): nada que a plataforma escreve no repositório
|
|
17
|
+
* dele depende de folha nossa, então não há `@import` para apontar. O que sobrevive é a única
|
|
18
|
+
* pergunta que não era sobre a nossa folha - *"quais pastas deste repositório são app?"* -, porque é
|
|
19
|
+
* onde o `fonts.ts` do `next/font` DELE entra, um por app.
|
|
28
20
|
*/
|
|
29
|
-
/** Quantos saltos seguir antes de desistir - um ciclo de imports não pode travar um `add`. */
|
|
30
|
-
const MAX_HOPS = 5;
|
|
31
|
-
const IMPORT = /@import\s+["']([^"']+)["']/g;
|
|
32
|
-
/** `@repo/ui/styles/globals.css` → `packages/ui/src/styles/globals.css`, pelo `exports` dele. */
|
|
33
|
-
async function fromWorkspace(root, spec) {
|
|
34
|
-
for (const group of ["packages", "apps", "libs"]) {
|
|
35
|
-
let entries;
|
|
36
|
-
try {
|
|
37
|
-
const { readdir } = await import("node:fs/promises");
|
|
38
|
-
entries = await readdir(join(root, group));
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
for (const entry of entries) {
|
|
44
|
-
const pkgPath = join(root, group, entry, "package.json");
|
|
45
|
-
const raw = await readFile(pkgPath, "utf8").catch(() => null);
|
|
46
|
-
if (!raw)
|
|
47
|
-
continue;
|
|
48
|
-
let pkg;
|
|
49
|
-
try {
|
|
50
|
-
pkg = JSON.parse(raw);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if (!pkg.name || !spec.startsWith(`${pkg.name}/`))
|
|
56
|
-
continue;
|
|
57
|
-
const sub = `./${spec.slice(pkg.name.length + 1)}`;
|
|
58
|
-
const target = pkg.exports?.[sub];
|
|
59
|
-
if (typeof target !== "string")
|
|
60
|
-
continue;
|
|
61
|
-
return posix.join(group, entry, target.replace(/^\.\//, ""));
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* A folha onde os tokens dele devem entrar, relativa à raiz - `appSheet` quando nada a re-exporta.
|
|
68
|
-
*
|
|
69
|
-
* DELEGA, e não caminha: quem caminha é `sheetChainOf`. Dois caminhadores sobre a mesma cadeia é
|
|
70
|
-
* como um deles para de seguir um salto que o outro segue, e ninguém descobre até um cliente
|
|
71
|
-
* receber a instrução apontando para a folha errada - que é exatamente o defeito que
|
|
72
|
-
* `INV-VOLTA-12` fechou.
|
|
73
|
-
*/
|
|
74
|
-
export async function globalSheetOf(root, appSheet) {
|
|
75
|
-
const chain = await sheetChainOf(root, appSheet);
|
|
76
|
-
return chain[chain.length - 1] ?? appSheet;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* A CADEIA INTEIRA, da folha do app até a última que ninguém re-exporta - e por que ela é pública.
|
|
80
|
-
*
|
|
81
|
-
* O QUE O CLIENTE GANHA: a resposta "este app carrega o meu design system?" medida no app DELE, e
|
|
82
|
-
* não no projeto. `readWiring` varria a raiz e devolvia "existe em algum lugar daqui": num monorepo
|
|
83
|
-
* com dois apps servidos, um fiado e outro não, o fiado respondia pelo outro - e a pessoa recebia
|
|
84
|
-
* "está tudo certo" sobre o app que não carrega nada.
|
|
85
|
-
*
|
|
86
|
-
* A pergunta dos IMPORTS não se responde varrendo pasta: ela se responde seguindo a cadeia de
|
|
87
|
-
* `@import` a partir da folha daquele app, porque a folha que carrega os tokens quase nunca é a do
|
|
88
|
-
* app - num monorepo é a do pacote compartilhado, e os dois apps chegam nela.
|
|
89
|
-
*
|
|
90
|
-
* A ordem é do app para fora, e o teto de saltos é o mesmo: um ciclo de imports não pode travar um
|
|
91
|
-
* comando.
|
|
92
|
-
*/
|
|
93
|
-
export async function sheetChainOf(root, appSheet) {
|
|
94
|
-
const chain = [];
|
|
95
|
-
let current = appSheet;
|
|
96
|
-
for (let hop = 0; hop < MAX_HOPS; hop += 1) {
|
|
97
|
-
chain.push(current);
|
|
98
|
-
const raw = await readFile(join(root, current), "utf8").catch(() => null);
|
|
99
|
-
if (!raw)
|
|
100
|
-
return chain;
|
|
101
|
-
let next = null;
|
|
102
|
-
for (const m of raw.matchAll(IMPORT)) {
|
|
103
|
-
const spec = m[1];
|
|
104
|
-
if (!spec.endsWith(".css"))
|
|
105
|
-
continue;
|
|
106
|
-
const candidate = spec.startsWith(".")
|
|
107
|
-
? posix.normalize(posix.join(posix.dirname(current), spec))
|
|
108
|
-
: await fromWorkspace(root, spec);
|
|
109
|
-
if (!candidate)
|
|
110
|
-
continue;
|
|
111
|
-
/** Fora da raiz não é folha dele para editar - e um `..` demais sai do repositório. */
|
|
112
|
-
if (candidate.startsWith(".."))
|
|
113
|
-
continue;
|
|
114
|
-
const readable = await readFile(join(root, candidate), "utf8").catch(() => null);
|
|
115
|
-
if (readable === null)
|
|
116
|
-
continue;
|
|
117
|
-
next = candidate;
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
/** Uma folha que aponta para si mesma encerra a cadeia em vez de gastar o teto de saltos. */
|
|
121
|
-
if (!next || next === current || chain.includes(next))
|
|
122
|
-
return chain;
|
|
123
|
-
current = next;
|
|
124
|
-
}
|
|
125
|
-
return chain;
|
|
126
|
-
}
|
|
127
|
-
/** O `../` que leva daquela folha até a raiz do repositório - o prefixo do `@import`. */
|
|
128
|
-
export function prefixFrom(sheet) {
|
|
129
|
-
const up = relative(dirname(resolve("/r", sheet)), "/r");
|
|
130
|
-
return up === "" ? "./" : `${up.split(/[\\/]/).join("/")}/`;
|
|
131
|
-
}
|
|
132
21
|
/**
|
|
133
22
|
* A RAIZ DE CADA APP DESTE REPOSITÓRIO - onde o escopo e a tipografia entram, um por app.
|
|
134
23
|
*
|