synthesisui 0.16.22 → 0.16.25
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/cn-codegen.js +31 -1
- package/dist/commands/sync.js +98 -0
- package/dist/fonts.js +11 -1
- package/dist/index.js +4 -0
- package/package.json +1 -1
package/dist/cn-codegen.js
CHANGED
|
@@ -104,9 +104,26 @@ const PREFIXES = [
|
|
|
104
104
|
"gap-x", "gap-y", "gap",
|
|
105
105
|
"w", "h", "min-w", "min-h", "max-w", "max-h",
|
|
106
106
|
"leading", "tracking",
|
|
107
|
-
"
|
|
107
|
+
"justify-items", "justify-self", "justify",
|
|
108
|
+
"items", "self", "order", "z",
|
|
108
109
|
];
|
|
109
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Utilities that set \`display\`. One group, so \`hidden\` can evict \`flex\` -
|
|
113
|
+
* and kept OUT of the prefix walk, because \`flex\` also heads whole families
|
|
114
|
+
* that set OTHER properties. Read as a prefix, \`flex-row\` evicted the \`flex\`
|
|
115
|
+
* that makes the box a flex container, and every Row and Stack in the project
|
|
116
|
+
* stacked its children in silence. Found by an agent in the field (test03,
|
|
117
|
+
* 29/07), diagnosed against computed output, fixed locally, and FILED - this
|
|
118
|
+
* is its fix, ported to the source so it stops evaporating on regeneration.
|
|
119
|
+
*/
|
|
120
|
+
const DISPLAYS = new Set([
|
|
121
|
+
"flex", "grid", "block", "inline", "inline-flex", "inline-grid",
|
|
122
|
+
"inline-block", "hidden", "contents", "flow-root", "table", "list-item",
|
|
123
|
+
]);
|
|
124
|
+
const FLEX_DIRECTIONS = new Set(["row", "col", "row-reverse", "col-reverse"]);
|
|
125
|
+
const FLEX_WRAPS = new Set(["wrap", "nowrap", "wrap-reverse"]);
|
|
126
|
+
|
|
110
127
|
/**
|
|
111
128
|
* The property this class sets, or the class itself when we cannot tell.
|
|
112
129
|
*
|
|
@@ -124,10 +141,23 @@ function groupOf(cls: string): string {
|
|
|
124
141
|
const negative = base.startsWith("-");
|
|
125
142
|
if (negative) base = base.slice(1);
|
|
126
143
|
|
|
144
|
+
if (DISPLAYS.has(base)) return \`\${variants}display\`;
|
|
145
|
+
|
|
127
146
|
const dash = base.indexOf("-");
|
|
128
147
|
const head = dash === -1 ? base : base.slice(0, dash);
|
|
129
148
|
const rest = dash === -1 ? "" : base.slice(dash + 1);
|
|
130
149
|
|
|
150
|
+
if (head === "flex") {
|
|
151
|
+
if (FLEX_DIRECTIONS.has(rest)) return \`\${variants}flex-direction\`;
|
|
152
|
+
if (FLEX_WRAPS.has(rest)) return \`\${variants}flex-wrap\`;
|
|
153
|
+
return \`\${variants}flex\`; // flex-1 / flex-auto / flex-none / flex-initial
|
|
154
|
+
}
|
|
155
|
+
if (head === "grid") {
|
|
156
|
+
// grid-cols-* / grid-rows-* / grid-flow-* each own their property.
|
|
157
|
+
const sub = rest.indexOf("-") === -1 ? rest : rest.slice(0, rest.indexOf("-"));
|
|
158
|
+
return \`\${variants}grid-\${sub}\`;
|
|
159
|
+
}
|
|
160
|
+
|
|
131
161
|
// The two families a prefix cannot settle, answered by this system's names.
|
|
132
162
|
if (head === "text") {
|
|
133
163
|
if (TEXT_SIZES.has(rest)) return \`\${variants}font-size\`;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { readToken, resolveRegistry } from "../config.js";
|
|
4
|
+
import { readEvents } from "../doctor/ledger.js";
|
|
5
|
+
import { readRequests } from "../doctor/requests.js";
|
|
6
|
+
import { body, section } from "../output.js";
|
|
7
|
+
/**
|
|
8
|
+
* `synthesisui sync` - the ledger travels, because a person said so.
|
|
9
|
+
*
|
|
10
|
+
* The hook writes the ledger and NEVER touches the network; the doctor reads
|
|
11
|
+
* it and never sends it. This command is the one place the record leaves the
|
|
12
|
+
* machine, and it only runs by hand. That split is load-bearing: a check that
|
|
13
|
+
* phones home is a check that gets uninstalled, and the entire governance
|
|
14
|
+
* stack stands on the check staying installed.
|
|
15
|
+
*
|
|
16
|
+
* What it buys: the dashboard answer to the question a design-system lead
|
|
17
|
+
* asks before paying for anything - is my team's drift going up or down? Run
|
|
18
|
+
* it after a session, from CI after a merge, or never; the local record is
|
|
19
|
+
* complete either way.
|
|
20
|
+
*
|
|
21
|
+
* Re-running is free and safe. The server dedupes events on their identity
|
|
22
|
+
* and treats the request queue as a snapshot, so the local files remain the
|
|
23
|
+
* single source of truth.
|
|
24
|
+
*/
|
|
25
|
+
export async function sync(opts) {
|
|
26
|
+
const root = resolve(opts.dir ?? process.cwd());
|
|
27
|
+
const base = resolveRegistry(opts.registry);
|
|
28
|
+
const token = await readToken();
|
|
29
|
+
if (!token) {
|
|
30
|
+
console.log(section("Sync"));
|
|
31
|
+
console.log(body("Not logged in. The record stays local until you say so:"));
|
|
32
|
+
console.log(body(" synthesisui login"));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Which system does this project's record belong to? The .lock says.
|
|
36
|
+
const dsDir = join(root, "_synthesisui", "ds");
|
|
37
|
+
let slug = null;
|
|
38
|
+
try {
|
|
39
|
+
const slugs = (await readdir(dsDir, { withFileTypes: true }))
|
|
40
|
+
.filter((e) => e.isDirectory())
|
|
41
|
+
.map((e) => e.name);
|
|
42
|
+
for (const s of slugs) {
|
|
43
|
+
const raw = await readFile(join(dsDir, s, ".lock"), "utf8").catch(() => "");
|
|
44
|
+
try {
|
|
45
|
+
const lock = JSON.parse(raw);
|
|
46
|
+
// An adopted system is described, not owned on the platform - there is
|
|
47
|
+
// no dashboard row to sync into (yet).
|
|
48
|
+
if (lock.slug && !lock.adopted) {
|
|
49
|
+
slug = lock.slug;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// unreadable lock - try the next
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// no _synthesisui at all
|
|
60
|
+
}
|
|
61
|
+
if (!slug) {
|
|
62
|
+
console.log(section("Sync"));
|
|
63
|
+
console.log(body("No installed system here, so there is nowhere to sync to."));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const events = await readEvents(root);
|
|
67
|
+
const requests = await readRequests(root);
|
|
68
|
+
if (events.length === 0 && requests.length === 0) {
|
|
69
|
+
console.log(section("Sync"));
|
|
70
|
+
console.log(body("Nothing recorded yet. The record fills as the hook checks writes and doctor runs."));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const res = await fetch(`${base}/api/ledger`, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: {
|
|
76
|
+
"content-type": "application/json",
|
|
77
|
+
Authorization: `Bearer ${token}`,
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({ slug, events, requests }),
|
|
80
|
+
}).catch(() => null);
|
|
81
|
+
if (!res) {
|
|
82
|
+
console.log(section("Sync"));
|
|
83
|
+
console.log(body("Could not reach the registry. The record is safe locally."));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
const err = (await res.json().catch(() => ({})));
|
|
88
|
+
console.log(section("Sync"));
|
|
89
|
+
console.log(body(err.message ?? `The registry answered ${res.status}.`));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const out = (await res.json());
|
|
93
|
+
console.log(section("Sync"));
|
|
94
|
+
console.log(body(`${out.eventsReceived} checks sent, ${out.eventsNew} new. ${out.requestsNow} open request${out.requestsNow === 1 ? "" : "s"}.`));
|
|
95
|
+
console.log("");
|
|
96
|
+
console.log(body(`The record lives on your system's overview:`));
|
|
97
|
+
console.log(body(` ${base}/dashboard/mine/${slug}`));
|
|
98
|
+
}
|
package/dist/fonts.js
CHANGED
|
@@ -62,7 +62,17 @@ export function nextFontSnippet(families, slug, appDir = "app") {
|
|
|
62
62
|
if (!seen.has(name)) {
|
|
63
63
|
seen.set(name, role);
|
|
64
64
|
importNames.push(importName(name));
|
|
65
|
-
consts.push(`export const ${seen.get(name)} = ${importName(name)}({`, ` subsets: ["latin"],`,
|
|
65
|
+
consts.push(`export const ${seen.get(name)} = ${importName(name)}({`, ` subsets: ["latin"],`,
|
|
66
|
+
// WEIGHTS ARE ALWAYS SPELLED OUT, because next/font only allows
|
|
67
|
+
// omitting them for VARIABLE fonts - and plenty of Google families
|
|
68
|
+
// ship as static cuts (IBM Plex Mono, for one). The generated file
|
|
69
|
+
// assumed variable and broke the consumer's first `next build`; an
|
|
70
|
+
// agent caught it in the field (test03, 29/07) and had to repair our
|
|
71
|
+
// output by hand. Explicit weights are valid for BOTH kinds, so
|
|
72
|
+
// emitting them always can never break - and these three are the
|
|
73
|
+
// steps every generated system actually uses (regular/medium/
|
|
74
|
+
// semibold), so no static cut is downloaded for nothing.
|
|
75
|
+
` weight: ["400", "500", "600"],`, ` variable: "--font-ds-${seen.get(name)}",`, `});`);
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
const fontsFile = [
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { login } from "./commands/login.js";
|
|
|
15
15
|
import { mcp } from "./commands/mcp.js";
|
|
16
16
|
import { refit } from "./commands/refit.js";
|
|
17
17
|
import { request } from "./commands/request.js";
|
|
18
|
+
import { sync } from "./commands/sync.js";
|
|
18
19
|
import { template } from "./commands/template.js";
|
|
19
20
|
import { upgrade } from "./commands/upgrade.js";
|
|
20
21
|
import { use } from "./commands/use.js";
|
|
@@ -331,6 +332,9 @@ async function main() {
|
|
|
331
332
|
case "clean":
|
|
332
333
|
await clean({ dir, force: flags.force === true });
|
|
333
334
|
break;
|
|
335
|
+
case "sync":
|
|
336
|
+
await sync({ dir, registry });
|
|
337
|
+
break;
|
|
334
338
|
case "request": {
|
|
335
339
|
// `synthesisui request` lists; `request component --name x --for "..."`
|
|
336
340
|
// files; `request --done <id>` closes. The agent's front door is the MCP
|
package/package.json
CHANGED