synthesisui 0.16.318 → 0.16.319
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 +2 -0
- package/dist/commands/component.js +22 -8
- package/dist/commands/generate.js +3 -0
- package/dist/commands/refit.js +3 -0
- package/dist/commands/upgrade.js +38 -2
- package/dist/install-marks.js +8 -1
- package/dist/written.js +70 -0
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -308,6 +308,8 @@ export async function add(slug, opts) {
|
|
|
308
308
|
const reference = lockReference(payload, prev);
|
|
309
309
|
return reference ? { reference } : {};
|
|
310
310
|
})(),
|
|
311
|
+
/** A memória do que NÓS escrevemos atravessa a re-materialização - ver `RootLock.written`. */
|
|
312
|
+
...(prev?.written ? { written: prev.written } : {}),
|
|
311
313
|
};
|
|
312
314
|
const landed = (() => {
|
|
313
315
|
if (!prev?.fetchedAt)
|
|
@@ -11,6 +11,7 @@ import { fetchComponent, RegistryError } from "../registry.js";
|
|
|
11
11
|
import { flavourResolver } from "../styles-flavour.js";
|
|
12
12
|
import { inTheirTongue, projectTongue } from "../their-tongue.js";
|
|
13
13
|
import { readCensus, unreadComment, unreadForComponent, } from "../unread-for-component.js";
|
|
14
|
+
import { recordWritten } from "../written.js";
|
|
14
15
|
/**
|
|
15
16
|
* Writes the shared `cn.ts` next to the components, built from THIS project's
|
|
16
17
|
* installed theme.
|
|
@@ -219,10 +220,16 @@ export async function component(slug, name, opts) {
|
|
|
219
220
|
// it wears (.css) + the barrel. Ignores the css|tailwind flavor - the
|
|
220
221
|
// template drives itself off the .ds-* classes.
|
|
221
222
|
const tsx = interactiveTemplate(res.name);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
const written = [
|
|
224
|
+
{ filename: `${local}.tsx`, content: tsx },
|
|
225
|
+
{ filename: `${local}.css`, content: `${css}\n` },
|
|
226
|
+
{ filename: "index.ts", content: `export * from "./${local}";\n` },
|
|
227
|
+
];
|
|
228
|
+
for (const f of written)
|
|
229
|
+
await writeFile(join(compDir, f.filename), f.content, "utf8");
|
|
230
|
+
/** O fingerprint do que escrevemos - é o que protege a edição dele no upgrade (T7). */
|
|
231
|
+
await recordWritten(join(root, "_synthesisui", "ds", slug), local, written);
|
|
232
|
+
filenames = written.map((f) => f.filename);
|
|
226
233
|
}
|
|
227
234
|
else {
|
|
228
235
|
const files = generateComponentFiles(slug, res.name, res.recipe, css, res.version, flavour, await reactMajorOf(root),
|
|
@@ -258,12 +265,19 @@ export async function component(slug, name, opts) {
|
|
|
258
265
|
* sistema não tem censo nenhum, e ele recebe o que sempre recebeu.
|
|
259
266
|
*/
|
|
260
267
|
const nota = unreadComment(unreadForComponent(await readCensus(root), res.name));
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
268
|
+
/** Os bytes finais, para o fingerprint lembrar EXATAMENTE o que foi a disco (T7). */
|
|
269
|
+
const written = files.map((file) => ({
|
|
270
|
+
filename: file.filename,
|
|
271
|
+
content: file.filename.endsWith(".tsx")
|
|
272
|
+
? `${nota}${file.code}`
|
|
273
|
+
: file.code,
|
|
274
|
+
}));
|
|
275
|
+
for (const f of written)
|
|
276
|
+
await writeFile(join(compDir, f.filename), f.content, "utf8");
|
|
277
|
+
await recordWritten(join(root, "_synthesisui", "ds", slug), local, written);
|
|
264
278
|
if (flavour === "tailwind")
|
|
265
279
|
await writeCn(root, compDir, slug);
|
|
266
|
-
filenames =
|
|
280
|
+
filenames = written.map((f) => f.filename);
|
|
267
281
|
}
|
|
268
282
|
const flavor = wantInteractive ? "interactive" : `styles: ${flavour}`;
|
|
269
283
|
console.log(`✓ ${config.componentsDir}/${local}/ → ${filenames.join(", ")} (${flavor})${local === res.name ? "" : ` - the "${res.name}" blueprint, under your name`}`);
|
|
@@ -6,6 +6,7 @@ import { installedSlugs } from "../installed.js";
|
|
|
6
6
|
import { reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
|
|
7
7
|
import { postGenerate, RegistryError } from "../registry.js";
|
|
8
8
|
import { flavourResolver } from "../styles-flavour.js";
|
|
9
|
+
import { recordWritten } from "../written.js";
|
|
9
10
|
/** PascalCase para o hint de import (course-card → CourseCard). */
|
|
10
11
|
function pascalName(name) {
|
|
11
12
|
return name.replace(/(^|[-_])([a-z0-9])/g, (_, __, c) => c.toUpperCase());
|
|
@@ -75,6 +76,8 @@ export async function generate(description, opts) {
|
|
|
75
76
|
for (const file of files) {
|
|
76
77
|
await writeFile(join(compDir, file.filename), file.code, "utf8");
|
|
77
78
|
}
|
|
79
|
+
/** O fingerprint do que escrevemos - protege a edição dele no upgrade (T7). */
|
|
80
|
+
await recordWritten(join(root, "_synthesisui", "ds", slug), res.name, files.map((x) => ({ filename: x.filename, content: x.code })));
|
|
78
81
|
materialized = true;
|
|
79
82
|
console.log(`✓ ${config.componentsDir}/${res.name}/ → ${files.map((f) => f.filename).join(", ")} (styles: ${config.styles})`);
|
|
80
83
|
}
|
package/dist/commands/refit.js
CHANGED
|
@@ -7,6 +7,7 @@ import { body, section, snippet } from "../output.js";
|
|
|
7
7
|
import { reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
|
|
8
8
|
import { fetchComponent, postRefit, postSaveComponent, RegistryError, } from "../registry.js";
|
|
9
9
|
import { flavourResolver } from "../styles-flavour.js";
|
|
10
|
+
import { recordWritten } from "../written.js";
|
|
10
11
|
/** Slugs INSTALLED under `_synthesisui/ds/` (a `.lock` marks a real install -
|
|
11
12
|
* a folder holding only refit artifacts doesn't count). */
|
|
12
13
|
/** True when the system is actually installed (tokens.css present). */
|
|
@@ -121,6 +122,8 @@ export async function refit(file, opts) {
|
|
|
121
122
|
for (const f of files) {
|
|
122
123
|
await writeFile(join(compDir, f.filename), f.code, "utf8");
|
|
123
124
|
}
|
|
125
|
+
/** O fingerprint do que escrevemos - protege a edição dele no upgrade (T7). */
|
|
126
|
+
await recordWritten(join(root, "_synthesisui", "ds", slug), res.name, files.map((x) => ({ filename: x.filename, content: x.code })));
|
|
124
127
|
materialized = true;
|
|
125
128
|
console.log(`✓ ${config.componentsDir}/${res.name}/ → ${files.map((f) => f.filename).join(", ")} (styles: ${config.styles})`);
|
|
126
129
|
}
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -11,6 +11,7 @@ import { reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../
|
|
|
11
11
|
import { fetchChangelog, fetchComponent, fetchDesignSystem, RegistryError, } from "../registry.js";
|
|
12
12
|
import { flavourResolver } from "../styles-flavour.js";
|
|
13
13
|
import { readCensus, unreadComment, unreadForComponent, } from "../unread-for-component.js";
|
|
14
|
+
import { editedSinceWritten, keptLine, readWritten, recordWritten, } from "../written.js";
|
|
14
15
|
import { add } from "./add.js";
|
|
15
16
|
import { reportWhatIsLeft } from "./align.js";
|
|
16
17
|
import { doctor } from "./doctor.js";
|
|
@@ -283,6 +284,9 @@ export async function upgrade(asked, opts) {
|
|
|
283
284
|
const config = await readProjectConfig(root);
|
|
284
285
|
const regenerated = [];
|
|
285
286
|
const failed = [];
|
|
287
|
+
/** Componentes que ELE editou desde que os escrevemos - mantidos intactos (T7). */
|
|
288
|
+
const kept = [];
|
|
289
|
+
const writtenMap = await readWritten(slugDir);
|
|
286
290
|
if (config.target === "next") {
|
|
287
291
|
const componentsRoot = join(root, config.componentsDir);
|
|
288
292
|
const marker = `from the "${slug}" design system`;
|
|
@@ -308,6 +312,17 @@ export async function upgrade(asked, opts) {
|
|
|
308
312
|
}
|
|
309
313
|
if (!head.includes("Generated by SynthesisUI") || !head.includes(marker))
|
|
310
314
|
continue;
|
|
315
|
+
/**
|
|
316
|
+
* A EDIÇÃO DELE VENCE A REGENERAÇÃO (T7) - ver `written.ts`. O fingerprint do que NÓS
|
|
317
|
+
* escrevemos foi gravado no `.lock` na hora da escrita; se o disco divergiu, ele editou, e o
|
|
318
|
+
* arquivo é DELE - fica intacto e o terminal diz qual. `null` é install de antes do
|
|
319
|
+
* fingerprint: sem memória não há veredito, e o comportamento de sempre continua.
|
|
320
|
+
*/
|
|
321
|
+
const edited = await editedSinceWritten(writtenMap[entry], join(componentsRoot, entry));
|
|
322
|
+
if (edited && edited.length > 0) {
|
|
323
|
+
kept.push({ entry, edited });
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
311
326
|
try {
|
|
312
327
|
const res = await fetchComponent(base, slug, entry);
|
|
313
328
|
const files = generateComponentFiles(slug, res.name, res.recipe, res.css, res.version, flavourOf(res.name),
|
|
@@ -325,9 +340,17 @@ export async function upgrade(asked, opts) {
|
|
|
325
340
|
* Ver `unread-for-component.ts`: censo ausente responde vazio, e vazio não vira comentário.
|
|
326
341
|
*/
|
|
327
342
|
const nota = unreadComment(unreadForComponent(await readCensus(root), res.name));
|
|
328
|
-
|
|
329
|
-
|
|
343
|
+
/** Os bytes finais, para o fingerprint lembrar EXATAMENTE o que foi a disco. */
|
|
344
|
+
const finalFiles = files.map((file) => ({
|
|
345
|
+
filename: file.filename,
|
|
346
|
+
content: file.filename.endsWith(".tsx")
|
|
347
|
+
? `${nota}${file.code}`
|
|
348
|
+
: file.code,
|
|
349
|
+
}));
|
|
350
|
+
for (const file of finalFiles) {
|
|
351
|
+
await writeFile(join(componentsRoot, entry, file.filename), file.content, "utf8");
|
|
330
352
|
}
|
|
353
|
+
await recordWritten(slugDir, entry, finalFiles);
|
|
331
354
|
regenerated.push(entry);
|
|
332
355
|
}
|
|
333
356
|
catch {
|
|
@@ -372,6 +395,13 @@ export async function upgrade(asked, opts) {
|
|
|
372
395
|
.map((n) => `\`${n}\``)
|
|
373
396
|
.join(", ")}.`
|
|
374
397
|
: "- No materialized components needed regeneration.",
|
|
398
|
+
...(kept.length > 0
|
|
399
|
+
? [
|
|
400
|
+
`- These components were edited by hand after we wrote them, so the upgrade KEPT your files: ${kept
|
|
401
|
+
.map((k) => `\`${k.entry}\``)
|
|
402
|
+
.join(", ")}. To take the new version of one, rerun \`npx synthesisui component ${slug} <name>\`.`,
|
|
403
|
+
]
|
|
404
|
+
: []),
|
|
375
405
|
"- The full new contract lives in `design-system.json` / `GUIDE.md` next to this file.",
|
|
376
406
|
"",
|
|
377
407
|
].join("\n");
|
|
@@ -394,6 +424,12 @@ export async function upgrade(asked, opts) {
|
|
|
394
424
|
console.log("");
|
|
395
425
|
console.log(body(`⚠ Could not regenerate: ${failed.join(", ")} (removed/renamed in v${latest.version}?)`));
|
|
396
426
|
}
|
|
427
|
+
/** A prova VISÍVEL do T7: o dono fica sabendo AGORA, com o arquivo nomeado e a saída dita. */
|
|
428
|
+
if (kept.length > 0) {
|
|
429
|
+
console.log("");
|
|
430
|
+
for (const k of kept)
|
|
431
|
+
console.log(body(keptLine(slug, k.entry, k.edited)));
|
|
432
|
+
}
|
|
397
433
|
await checkAfterWriting(root, `v${latest.version}`);
|
|
398
434
|
console.log(section("Migrate the app"));
|
|
399
435
|
console.log(body(`The migration brief is at _synthesisui/ds/${slug}/UPGRADE.md`));
|
package/dist/install-marks.js
CHANGED
|
@@ -137,7 +137,14 @@
|
|
|
137
137
|
* ali, que é exatamente o que esta marca existe para dizer. Quem avisa versão nova passa a ser o
|
|
138
138
|
* próprio align, com o `cli` que o servidor manda no `?meta=1`.
|
|
139
139
|
*/
|
|
140
|
-
|
|
140
|
+
/**
|
|
141
|
+
* 0.16.306 -> 0.16.319 em 26/08 (T7), e o SIM é sobre DUAS coisas que caem na pasta: o `.lock`
|
|
142
|
+
* ganha `written` (o fingerprint do que NÓS escrevemos, por componente e arquivo) e o `upgrade`
|
|
143
|
+
* muda QUAIS bytes reescreve - um componente que o dono editou à mão fica INTACTO, com a frase no
|
|
144
|
+
* terminal dizendo qual arquivo e como tomar a versão nova por escolha. Antes desta versão a
|
|
145
|
+
* reescrita era incondicional e o único aviso era prosa depois do fato.
|
|
146
|
+
*/
|
|
147
|
+
export const MATERIALISER_SINCE = "0.16.319";
|
|
141
148
|
/**
|
|
142
149
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
143
150
|
*
|
package/dist/written.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export function fingerprintOf(content) {
|
|
5
|
+
return createHash("sha256")
|
|
6
|
+
.update(content, "utf8")
|
|
7
|
+
.digest("hex")
|
|
8
|
+
.slice(0, 12);
|
|
9
|
+
}
|
|
10
|
+
/** O mapa gravado, ou vazio - um `.lock` ausente/ilegível responde como install legado. */
|
|
11
|
+
export async function readWritten(slugDir) {
|
|
12
|
+
try {
|
|
13
|
+
const lock = JSON.parse(await readFile(join(slugDir, ".lock"), "utf8"));
|
|
14
|
+
return lock.written ?? {};
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Grava o fingerprint do que acabou de ser escrito. Lê-modifica-escreve o `.lock` inteiro para
|
|
22
|
+
* não perder nenhum campo irmão; sem `.lock` (componente materializado antes do install), não há
|
|
23
|
+
* onde lembrar - silêncio de propósito, o próximo `add` cria o arquivo e o próximo write grava.
|
|
24
|
+
*/
|
|
25
|
+
export async function recordWritten(slugDir, entry, files) {
|
|
26
|
+
const lockPath = join(slugDir, ".lock");
|
|
27
|
+
let lock;
|
|
28
|
+
try {
|
|
29
|
+
lock = JSON.parse(await readFile(lockPath, "utf8"));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const written = (lock.written ?? {});
|
|
35
|
+
written[entry] = Object.fromEntries(files.map((f) => [f.filename, fingerprintOf(f.content)]));
|
|
36
|
+
lock.written = written;
|
|
37
|
+
await writeFile(lockPath, `${JSON.stringify(lock, null, 2)}\n`, "utf8");
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* QUAIS ARQUIVOS ELE EDITOU desde que nós os escrevemos.
|
|
41
|
+
*
|
|
42
|
+
* Um arquivo que sumiu também conta: apagar é uma edição - regenerá-lo desfaria a decisão dele
|
|
43
|
+
* do mesmo jeito. Devolve vazio quando nada divergiu (nosso para regenerar) e `null` quando não
|
|
44
|
+
* há fingerprint gravado (install legado - o chamador mantém o comportamento de sempre).
|
|
45
|
+
*/
|
|
46
|
+
export async function editedSinceWritten(recorded, dir) {
|
|
47
|
+
if (!recorded)
|
|
48
|
+
return null;
|
|
49
|
+
const edited = [];
|
|
50
|
+
for (const [filename, fp] of Object.entries(recorded)) {
|
|
51
|
+
try {
|
|
52
|
+
const current = await readFile(join(dir, filename), "utf8");
|
|
53
|
+
if (fingerprintOf(current) !== fp)
|
|
54
|
+
edited.push(filename);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
edited.push(filename);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return edited;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A FRASE DO TERMINAL, uma por componente mantido - o critério do T7 é a prova VISÍVEL: o dono
|
|
64
|
+
* fica sabendo na hora, com o arquivo nomeado e a saída dita, não num diff depois do fato. A
|
|
65
|
+
* saída oferecida regenera por ESCOLHA (o `component` reescreve e regrava o fingerprint).
|
|
66
|
+
*/
|
|
67
|
+
export function keptLine(slug, entry, edited) {
|
|
68
|
+
return (`! ${entry}: you edited ${edited.join(", ")} after we wrote it - kept YOUR file(s).\n` +
|
|
69
|
+
` To take the new version anyway: npx synthesisui component ${slug} ${entry}`);
|
|
70
|
+
}
|
package/package.json
CHANGED