synthesisui 0.16.496 → 0.16.498
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 +8 -2
- package/dist/commands/add.js +18 -2
- package/dist/commands/init.js +39 -9
- package/dist/commands/landing.js +47 -0
- package/dist/commands/mcp.js +12 -1
- package/dist/commands/use.js +11 -0
- package/dist/index.js +15 -0
- package/dist/install-marks.js +11 -1
- package/dist/new-project-theme.js +65 -13
- package/dist/registry.js +24 -0
- package/dist/their-vars.js +11 -1
- package/package.json +1 -1
package/dist/claude-md.js
CHANGED
|
@@ -463,6 +463,12 @@ written now** - read the code before you edit it, never a remembered signature.`
|
|
|
463
463
|
* as duas portas e a regra que as mantém fechadas no dia a dia: quem cria sem pedir continua livre.
|
|
464
464
|
* Basta o servidor registrado - a preferência não depende do sistema instalado.
|
|
465
465
|
*/
|
|
466
|
+
/**
|
|
467
|
+
* O PEDIDO LIVRE SAI MASTIGADO (jornada "o pedido livre sai mastigado", E03, 15/10): uma landing pedida em palavras
|
|
468
|
+
* livres passa pelo `landing` antes de qualquer linha - ele lista as seções do catálogo e devolve o prompt da pilha, com
|
|
469
|
+
* as decisões tomadas. Sem servidor também: o `landing` fala com o registro direto.
|
|
470
|
+
*/
|
|
471
|
+
const landingLine = '\n\n**A landing asked in free words:** run `npx synthesisui@latest landing <slug> "<their words>"` first, and follow it.';
|
|
466
472
|
const preferencesLine = recall.available
|
|
467
473
|
? `
|
|
468
474
|
|
|
@@ -523,7 +529,7 @@ If nothing covers it, say which indexed entry you considered and why it did not
|
|
|
523
529
|
project's OWN custom properties, exactly as the guide lists them. Do not write raw colours,
|
|
524
530
|
spacings or radii that a token already covers, and do not invent a new token silently - say so
|
|
525
531
|
instead, because a new token is a decision for a person to make. There is no component
|
|
526
|
-
manifest for an adopted system - the tokens ARE the contract.${theirsLine}${selfCheck}`
|
|
532
|
+
manifest for an adopted system - the tokens ARE the contract.${theirsLine}${landingLine}${selfCheck}`
|
|
527
533
|
: `**When creating or editing components, read the system's GUIDE.md and follow it:** use the
|
|
528
534
|
vocabulary it lists - every name in it is a name THIS project already declares, written exactly as
|
|
529
535
|
you would write it. Do not use raw values outside the system's scale, and never a name from a
|
|
@@ -559,7 +565,7 @@ still yours: the \`request_component\` MCP tool, or \`npx synthesisui@latest req
|
|
|
559
565
|
what the system's author works from - a refusal said only in chat evaporates. If you want to eyeball a
|
|
560
566
|
component on its own, an isolated scratch page is optional and never required - and if you make
|
|
561
567
|
one, put it somewhere that does not become a route in this app, and delete it when you are done.
|
|
562
|
-
Do not apply a component to real production pages unless asked.${selfCheck}`;
|
|
568
|
+
Do not apply a component to real production pages unless asked.${landingLine}${selfCheck}`;
|
|
563
569
|
const locale = await readInterfaceLanguage(projectRoot);
|
|
564
570
|
const language = locale === null
|
|
565
571
|
? ""
|
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/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
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { resolveRegistry } from "../config.js";
|
|
2
|
+
import { fetchLandingParts, fetchLandingPrompt } from "../registry.js";
|
|
3
|
+
/**
|
|
4
|
+
* `synthesisui landing <slug> "<pedido>"` - O PEDIDO LIVRE SAI MASTIGADO (jornada "o pedido livre sai mastigado", 15/10).
|
|
5
|
+
*
|
|
6
|
+
* Em dois passos, e o agente faz os dois:
|
|
7
|
+
*
|
|
8
|
+
* sem --sections a lista das seções de landing do catálogo, com o lugar, o tipo e o nome, e o que fazer em seguida
|
|
9
|
+
* com --sections o MESMO prompt do "Copy prompt" da pilha para essas seções - um template por seção, fontes, menu do
|
|
10
|
+
* celular -, com o conteúdo vindo do pedido
|
|
11
|
+
*
|
|
12
|
+
* A única decisão que fica com o agente é escolher da lista; o resto é regra nossa, já tomada. Fala com o registro
|
|
13
|
+
* (a mesma fonte do site), por isso não é o `use`, que promete não usar a rede (`architecture/no-network.spec.ts`).
|
|
14
|
+
*/
|
|
15
|
+
export async function landing(slug, requestText, opts) {
|
|
16
|
+
const base = resolveRegistry(opts.registry);
|
|
17
|
+
const request = requestText.trim() || "a landing page";
|
|
18
|
+
const chosen = (opts.sections ?? "")
|
|
19
|
+
.split(",")
|
|
20
|
+
.map((s) => s.trim())
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
if (chosen.length === 0) {
|
|
23
|
+
const parts = await fetchLandingParts(base, slug);
|
|
24
|
+
const byPlace = (place) => parts.filter((p) => p.place === place);
|
|
25
|
+
const line = (p) => ` ${p.id.padEnd(20)} ${p.type} · ${p.name}${p.note ? ` (${p.note})` : ""}`;
|
|
26
|
+
const out = [
|
|
27
|
+
`The landing sections of "${slug}". Pick one navigation, one hero, the body sections in order, and one footer -`,
|
|
28
|
+
"the ones that fit this request:",
|
|
29
|
+
` "${request}"`,
|
|
30
|
+
"",
|
|
31
|
+
"Top",
|
|
32
|
+
...byPlace("nav").map(line),
|
|
33
|
+
"Hero",
|
|
34
|
+
...byPlace("hero").map(line),
|
|
35
|
+
"Body",
|
|
36
|
+
...byPlace("body").map(line),
|
|
37
|
+
"Bottom",
|
|
38
|
+
...byPlace("footer").map(line),
|
|
39
|
+
"",
|
|
40
|
+
"Then run, with your picks in page order, and follow the prompt it prints:",
|
|
41
|
+
` npx synthesisui@latest landing ${slug} --sections <nav>,<hero>,<body...>,<footer> "${request}"`,
|
|
42
|
+
];
|
|
43
|
+
console.log(out.join("\n"));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
console.log(await fetchLandingPrompt(base, slug, chosen, request));
|
|
47
|
+
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1966,7 +1966,18 @@ cli) {
|
|
|
1966
1966
|
const prefs = await fetchPreferences({ base: resolveRegistry(), token }, kind, section, await installedSlug(root));
|
|
1967
1967
|
if ("failed" in prefs)
|
|
1968
1968
|
return fromContract(`Their preferences could not be read: ${prefs.failed}. Build with the system as usual and say so in one line.`);
|
|
1969
|
-
|
|
1969
|
+
const answer = preferencesAnswer(kind, prefs, await readSelection(root), await readOff(root));
|
|
1970
|
+
/**
|
|
1971
|
+
* O PEDIDO LIVRE SAI MASTIGADO (jornada "o pedido livre sai mastigado", E03, 15/10): a landing inteira passa pelo
|
|
1972
|
+
* `landing`, que escolhe do catálogo e devolve o prompt com as decisões tomadas. Mora AQUI, e não no CLAUDE.md, porque
|
|
1973
|
+
* com o servidor o bloco tem orçamento (`claude-md.f0.spec.ts`) e esta é a ferramenta que o agente já chama antes de
|
|
1974
|
+
* uma página. Uma parte só (`section`) não passa por ele: é uma seção, e o pedido é dela.
|
|
1975
|
+
*/
|
|
1976
|
+
if (kind === "landing" && section === undefined) {
|
|
1977
|
+
const slug = (await installedSlug(root)) ?? "<slug>";
|
|
1978
|
+
answer.body = `For the whole landing, first run \`npx synthesisui@latest landing ${slug} "<their words>"\`: pick the sections it lists, run it again with \`--sections\`, and build from the prompt it prints - every decision in it is already made. Apply what follows on top of it.\n\n${answer.body}`;
|
|
1979
|
+
}
|
|
1980
|
+
return answer;
|
|
1970
1981
|
}
|
|
1971
1982
|
case "remember_preference": {
|
|
1972
1983
|
const { kind, layer } = args;
|
package/dist/commands/use.js
CHANGED
|
@@ -97,9 +97,20 @@ export async function use(slug, intent, opts) {
|
|
|
97
97
|
`(\`npx synthesisui component ${slug} <name>\`) and use this project's own Tailwind utilities ` +
|
|
98
98
|
`(${utilities}…). Never use raw hex/px outside the system's scale.`;
|
|
99
99
|
const task = intent.trim() || "build the UI I describe next";
|
|
100
|
+
/**
|
|
101
|
+
* UMA LANDING TEM CAMINHO PRÓPRIO (jornada "o pedido livre sai mastigado", E01, 15/10): o `landing` devolve as seções do
|
|
102
|
+
* catálogo e o prompt com as decisões tomadas. O `use` não busca nada - ele promete não usar a rede -, só aponta.
|
|
103
|
+
*/
|
|
104
|
+
const isLanding = /\blanding\b|\bhome ?page\b/i.test(task);
|
|
100
105
|
const prompt = [
|
|
101
106
|
`Use the "${name}" design system (slug: ${slug}, v${version}) to: ${task}`,
|
|
102
107
|
"",
|
|
108
|
+
...(isLanding
|
|
109
|
+
? [
|
|
110
|
+
`This is a landing page: before writing anything, run \`npx synthesisui@latest landing ${slug} "${task.replace(/"/g, "'")}"\` - it lists the catalog's sections for you to pick, then prints the full prompt with every decision made. Follow that prompt instead of the rest of this one.`,
|
|
111
|
+
"",
|
|
112
|
+
]
|
|
113
|
+
: []),
|
|
103
114
|
"Read these files in the project first (highest authority first):",
|
|
104
115
|
...readFirst,
|
|
105
116
|
"",
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { generate } from "./commands/generate.js";
|
|
|
20
20
|
import { hook } from "./commands/hook.js";
|
|
21
21
|
import { runImport } from "./commands/import.js";
|
|
22
22
|
import { init } from "./commands/init.js";
|
|
23
|
+
import { landing } from "./commands/landing.js";
|
|
23
24
|
import { list } from "./commands/list.js";
|
|
24
25
|
import { login } from "./commands/login.js";
|
|
25
26
|
import { logout } from "./commands/logout.js";
|
|
@@ -61,6 +62,7 @@ Usage - deterministic, FREE:
|
|
|
61
62
|
synthesisui upgrade <slug> update an installed DS + regenerate your components + migration brief
|
|
62
63
|
synthesisui colours [slug] write the colours you chose on the site into your CSS, in your names (asks first)
|
|
63
64
|
synthesisui use <slug> "<intent>" print a ready-to-paste agent prompt to build/modify on-system
|
|
65
|
+
synthesisui landing <slug> "<request>" the catalog's landing sections, then (--sections a,b,c) the prompt with every decision made
|
|
64
66
|
synthesisui clean [--force] strip create-next-app boilerplate (dry run without --force)
|
|
65
67
|
|
|
66
68
|
Usage - governance (deterministic, FREE):
|
|
@@ -719,6 +721,19 @@ async function main() {
|
|
|
719
721
|
});
|
|
720
722
|
break;
|
|
721
723
|
}
|
|
724
|
+
case "landing": {
|
|
725
|
+
const slug = args[0];
|
|
726
|
+
if (!slug) {
|
|
727
|
+
console.error('error: provide the slug and your request - `synthesisui landing <slug> "<request>"`');
|
|
728
|
+
process.exitCode = 1;
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
await landing(slug, args.slice(1).join(" "), {
|
|
732
|
+
sections: typeof flags.sections === "string" ? flags.sections : undefined,
|
|
733
|
+
registry: typeof flags.registry === "string" ? flags.registry : undefined,
|
|
734
|
+
});
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
722
737
|
case "use": {
|
|
723
738
|
const slug = args[0];
|
|
724
739
|
if (!slug) {
|
package/dist/install-marks.js
CHANGED
|
@@ -328,7 +328,17 @@
|
|
|
328
328
|
* 0.16.492 -> 0.16.493 em 06/10, e o passo 1 dá **SIM** ("a galeria aprende", E02): o `init` declara as durações do
|
|
329
329
|
* sistema no @theme (`--duration-fast`), e o blueprint as escreve pelo nome. Um componente escrito antes traz `100ms`.
|
|
330
330
|
*/
|
|
331
|
-
|
|
331
|
+
/**
|
|
332
|
+
* 0.16.494 -> 0.16.497 em 15/10, e o passo 1 dá **SIM** (o pedido livre sai mastigado, E03): o bloco do `CLAUDE.md` sem o
|
|
333
|
+
* servidor diz que uma landing pedida em palavras livres passa primeiro pelo `landing`. Uma pasta escrita antes deixa o
|
|
334
|
+
* agente decidir a página inteira sozinho.
|
|
335
|
+
*/
|
|
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
|
+
export const MATERIALISER_SINCE = "0.16.498";
|
|
332
342
|
/**
|
|
333
343
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
334
344
|
*
|
|
@@ -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,21 @@ 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
|
+
function systemPairs(tokensCss, lookup = new Map()) {
|
|
282
|
+
const { names, values } = tongueOf([], tokensCss);
|
|
283
|
+
const out = [];
|
|
284
|
+
for (const ours of new Set([...values.keys(), ...names.keys()])) {
|
|
285
|
+
const theirs = theirNameFor(ours);
|
|
286
|
+
if (!theirs)
|
|
287
|
+
continue;
|
|
288
|
+
const pointed = names.get(ours);
|
|
289
|
+
const value = pointed ? lookup.get(pointed) : values.get(ours);
|
|
290
|
+
if (value && !value.includes("var(--ds-"))
|
|
291
|
+
out.push({ ours, theirs, value });
|
|
292
|
+
}
|
|
293
|
+
return out;
|
|
294
|
+
}
|
|
257
295
|
function systemNames(tokensCss, lookup = new Map()) {
|
|
258
296
|
const { names, values } = tongueOf([], tokensCss);
|
|
259
297
|
const out = [];
|
|
@@ -273,7 +311,9 @@ export async function readProjectTheme(root, tokensCss, slug) {
|
|
|
273
311
|
const r = await readTheirs(root, slug);
|
|
274
312
|
const base = { entry: r.entry, tokensFile: r.tokensFile };
|
|
275
313
|
const colourValues = [
|
|
276
|
-
...new Set(systemNames(tokensCss, r.every)
|
|
314
|
+
...new Set(systemNames(tokensCss, r.every)
|
|
315
|
+
.filter(([n]) => n.startsWith("--color-"))
|
|
316
|
+
.map(([, v]) => v.toLowerCase())),
|
|
277
317
|
];
|
|
278
318
|
const held = new Set([...r.values.values()].map((v) => v.trim().toLowerCase()));
|
|
279
319
|
const matched = colourValues.filter((v) => held.has(v)).length;
|
|
@@ -284,7 +324,11 @@ export async function readProjectTheme(root, tokensCss, slug) {
|
|
|
284
324
|
return { kind: "many-tailwind-entries", ...counts, ...base };
|
|
285
325
|
if (r.own.size === 0)
|
|
286
326
|
return { kind: "new", ...counts, ...base };
|
|
287
|
-
return {
|
|
327
|
+
return {
|
|
328
|
+
kind: matched * 2 >= colourValues.length && matched > 0 ? "matches" : "differs",
|
|
329
|
+
...counts,
|
|
330
|
+
...base,
|
|
331
|
+
};
|
|
288
332
|
}
|
|
289
333
|
/**
|
|
290
334
|
* O BLOCO DO SISTEMA AO LADO DO DELE - o terceiro caso.
|
|
@@ -305,7 +349,9 @@ export async function readProjectTheme(root, tokensCss, slug) {
|
|
|
305
349
|
export async function writeBesideTheirs(root, tokensCss, slug, version, where) {
|
|
306
350
|
const r = await readTheirs(root, slug);
|
|
307
351
|
const entryPath = r.entry ? join(root, r.entry) : null;
|
|
308
|
-
const separatePath = entryPath
|
|
352
|
+
const separatePath = entryPath
|
|
353
|
+
? join(dirname(entryPath), `${slug}.css`)
|
|
354
|
+
: null;
|
|
309
355
|
/** Tira de todo lugar o que é nosso, antes de escrever no lugar escolhido. */
|
|
310
356
|
const cleaned = new Map();
|
|
311
357
|
for (const [path, css] of r.sheets) {
|
|
@@ -317,7 +363,9 @@ export async function writeBesideTheirs(root, tokensCss, slug, version, where) {
|
|
|
317
363
|
for (const [path, css] of cleaned)
|
|
318
364
|
if (css !== r.sheets.get(path))
|
|
319
365
|
await writeFile(path, css, "utf8");
|
|
320
|
-
if (separatePath &&
|
|
366
|
+
if (separatePath &&
|
|
367
|
+
r.sheets.get(separatePath)?.startsWith(begin(slug)) &&
|
|
368
|
+
where !== "separate")
|
|
321
369
|
await rm(separatePath, { force: true });
|
|
322
370
|
};
|
|
323
371
|
const empty = { written: null, collisions: [], throughInline: [], added: 0 };
|
|
@@ -332,7 +380,9 @@ export async function writeBesideTheirs(root, tokensCss, slug, version, where) {
|
|
|
332
380
|
}
|
|
333
381
|
const missing = system.filter(([n]) => !r.own.has(n) && !r.values.has(n));
|
|
334
382
|
const collisions = system
|
|
335
|
-
.filter(([n, v]) => r.values.has(n) &&
|
|
383
|
+
.filter(([n, v]) => r.values.has(n) &&
|
|
384
|
+
!r.inline.has(n) &&
|
|
385
|
+
!same(r.values.get(n), v))
|
|
336
386
|
.map(([n]) => n);
|
|
337
387
|
const throughInline = [];
|
|
338
388
|
const scoped = system.map(([n, v]) => {
|
|
@@ -346,7 +396,9 @@ export async function writeBesideTheirs(root, tokensCss, slug, version, where) {
|
|
|
346
396
|
const block = [
|
|
347
397
|
begin(slug),
|
|
348
398
|
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
|
|
399
|
+
...(missing.length > 0
|
|
400
|
+
? ["@theme {", ...missing.map(([n, v]) => ` ${n}: ${v};`), "}", ""]
|
|
401
|
+
: []),
|
|
350
402
|
`[data-theme="${slug}"] {`,
|
|
351
403
|
...scoped,
|
|
352
404
|
"}",
|
package/dist/registry.js
CHANGED
|
@@ -230,3 +230,27 @@ export async function postSaveComponent(base, payload) {
|
|
|
230
230
|
}
|
|
231
231
|
return (await res.json());
|
|
232
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* O PEDIDO LIVRE SAI MASTIGADO (jornada "o pedido livre sai mastigado", 15/10): a lista de seções de landing do catálogo
|
|
235
|
+
* e o prompt da pilha para as escolhidas. Os dois saem do registro - a mesma fonte do site -, e nunca de uma cópia aqui.
|
|
236
|
+
*/
|
|
237
|
+
export async function fetchLandingParts(base, slug) {
|
|
238
|
+
const url = new URL(`${base}/api/registry/ds/${encodeURIComponent(slug)}`);
|
|
239
|
+
url.searchParams.set("landing", "parts");
|
|
240
|
+
const res = await request(url.toString());
|
|
241
|
+
const body = (await res.json().catch(() => ({})));
|
|
242
|
+
if (!res.ok || !body.parts)
|
|
243
|
+
throw new RegistryError(body.message ?? `Registry responded ${res.status} for "${slug}".`);
|
|
244
|
+
return body.parts;
|
|
245
|
+
}
|
|
246
|
+
export async function fetchLandingPrompt(base, slug, sections, requestText) {
|
|
247
|
+
const url = new URL(`${base}/api/registry/ds/${encodeURIComponent(slug)}`);
|
|
248
|
+
url.searchParams.set("landing", "prompt");
|
|
249
|
+
url.searchParams.set("sections", sections.join(","));
|
|
250
|
+
url.searchParams.set("request", requestText);
|
|
251
|
+
const res = await request(url.toString());
|
|
252
|
+
const body = (await res.json().catch(() => ({})));
|
|
253
|
+
if (!res.ok || !body.prompt)
|
|
254
|
+
throw new RegistryError(body.message ?? `Registry responded ${res.status} for "${slug}".`);
|
|
255
|
+
return body.prompt;
|
|
256
|
+
}
|
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