synthesisui 0.6.0 → 0.7.0
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/README.md +22 -2
- package/dist/commands/doctor.js +53 -5
- package/dist/doctor/overrides.js +18 -1
- package/dist/index.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,8 +99,28 @@ worse than none.
|
|
|
99
99
|
With no system installed it still finds every hand-written value and counts
|
|
100
100
|
the distinct ones. Runs offline, needs no account, writes nothing.
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
### What your system says
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
── What your system says about what you use ──────────────────────
|
|
106
|
+
|
|
107
|
+
ds-badge · 3 places
|
|
108
|
+
Badges whisper status in periwinkle; danger appears only for money at risk.
|
|
109
|
+
|
|
110
|
+
ds-button · 3 places
|
|
111
|
+
One indigo action per view; gold is reserved for moments of ceremony, never buttons.
|
|
112
|
+
Buttons speak quietly - sentence case, no exclamation.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The usage laws your team wrote, for the components this project actually uses,
|
|
116
|
+
ordered by how much you use them. They are prose, so nothing verifies them -
|
|
117
|
+
the value is putting them in front of whoever is touching the component. No
|
|
118
|
+
other tool is positioned to do it, because no other tool knows these laws
|
|
119
|
+
exist.
|
|
120
|
+
|
|
121
|
+
`--strict` exits 1 when drift is found, for CI. `--all` lists every finding
|
|
122
|
+
instead of the loudest files. `--laws` shows every law instead of the busiest
|
|
123
|
+
components.
|
|
104
124
|
|
|
105
125
|
## What `add` materializes
|
|
106
126
|
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join, relative, resolve } from "node:path";
|
|
3
|
-
import { bindingsFromDocument, findOverrides, } from "../doctor/overrides.js";
|
|
3
|
+
import { bindingsFromDocument, countComponents, findOverrides, } from "../doctor/overrides.js";
|
|
4
4
|
import { diagnose, scanSource, } from "../doctor/scan.js";
|
|
5
5
|
import { buildTable, EMPTY_TABLE } from "../doctor/tokens.js";
|
|
6
6
|
import { body, section, snippet } from "../output.js";
|
|
@@ -132,7 +132,7 @@ function meter(pct, width = 24) {
|
|
|
132
132
|
const filled = Math.round((pct / 100) * width);
|
|
133
133
|
return `${"█".repeat(filled)}${"░".repeat(width - filled)}`;
|
|
134
134
|
}
|
|
135
|
-
function verdict(d, hasSystem) {
|
|
135
|
+
function verdict(d, hasSystem, overruled) {
|
|
136
136
|
// Nothing found and nothing installed: a utility package, a config folder,
|
|
137
137
|
// the wrong directory. Selling a design system here would be noise.
|
|
138
138
|
if (!hasSystem && d.findings.length === 0) {
|
|
@@ -152,12 +152,26 @@ function verdict(d, hasSystem) {
|
|
|
152
152
|
body("Browse systems at https://www.synthesisui.com/gallery"),
|
|
153
153
|
];
|
|
154
154
|
}
|
|
155
|
-
if (d.findings.length === 0) {
|
|
155
|
+
if (d.findings.length === 0 && overruled === 0) {
|
|
156
156
|
return [
|
|
157
157
|
body("No drift. Every design value in this project comes from the"),
|
|
158
158
|
body("system. That is a rarer sentence than it sounds."),
|
|
159
159
|
];
|
|
160
160
|
}
|
|
161
|
+
// A report that accuses a component of being overruled and then closes with
|
|
162
|
+
// "no drift" is a report nobody believes twice.
|
|
163
|
+
if (d.findings.length === 0) {
|
|
164
|
+
return [
|
|
165
|
+
body("No loose values - everything is a token."),
|
|
166
|
+
body(overruled === 1
|
|
167
|
+
? "But one place takes a component the system defines and"
|
|
168
|
+
: `But ${overruled} places take a component the system defines and`),
|
|
169
|
+
body(overruled === 1
|
|
170
|
+
? "overrules it locally. Either the recipe should change, or"
|
|
171
|
+
: "overrule it locally. Either the recipe should change, or"),
|
|
172
|
+
body("that override should not be there."),
|
|
173
|
+
];
|
|
174
|
+
}
|
|
161
175
|
const lines = [
|
|
162
176
|
body(`${d.named} of ${d.findings.length} already have a name in your system.`),
|
|
163
177
|
body("Those are the cheap ones: swap the literal for the token."),
|
|
@@ -173,6 +187,8 @@ export async function doctor(opts) {
|
|
|
173
187
|
const hasSystem = table.byName.size > 0;
|
|
174
188
|
const reports = [];
|
|
175
189
|
const overrides = [];
|
|
190
|
+
const used = new Map();
|
|
191
|
+
const known = new Set(recipes.keys());
|
|
176
192
|
for await (const file of walk(root)) {
|
|
177
193
|
const src = await readFile(file, "utf8").catch(() => "");
|
|
178
194
|
if (!src)
|
|
@@ -182,6 +198,9 @@ export async function doctor(opts) {
|
|
|
182
198
|
if (recipes.size > 0) {
|
|
183
199
|
for (const o of findOverrides(src, recipes))
|
|
184
200
|
overrides.push({ ...o, file: rel });
|
|
201
|
+
for (const [name, n] of countComponents(src, known)) {
|
|
202
|
+
used.set(name, (used.get(name) ?? 0) + n);
|
|
203
|
+
}
|
|
185
204
|
}
|
|
186
205
|
}
|
|
187
206
|
if (reports.length === 0) {
|
|
@@ -193,7 +212,10 @@ export async function doctor(opts) {
|
|
|
193
212
|
console.log(body(hasSystem
|
|
194
213
|
? `${table.name ?? table.slug} v${table.version ?? "?"} - ${table.byName.size} tokens, ${d.scanned} files read`
|
|
195
214
|
: `No system installed - ${d.scanned} files read`));
|
|
196
|
-
|
|
215
|
+
// 0 of 0 is not a perfect score, it is an empty measurement - printing a
|
|
216
|
+
// full bar there would be the report's first lie.
|
|
217
|
+
const measurable = d.tokenUses + d.findings.length > 0;
|
|
218
|
+
if (hasSystem && measurable) {
|
|
197
219
|
console.log("");
|
|
198
220
|
console.log(body(`Token coverage ${meter(d.coverage)} ${String(d.coverage).padStart(3)}%`));
|
|
199
221
|
console.log(body(` ${d.tokenUses} from the system, ${d.findings.length} by hand`));
|
|
@@ -271,8 +293,34 @@ export async function doctor(opts) {
|
|
|
271
293
|
console.log(body(`+${byFile.size - shown.length} more files. Run with --all.`));
|
|
272
294
|
}
|
|
273
295
|
}
|
|
296
|
+
// The system's own words, for the components this project actually uses.
|
|
297
|
+
// Prose, so nothing verifies it - the value is putting it in front of
|
|
298
|
+
// whoever is touching the component, which no other tool is positioned to
|
|
299
|
+
// do because no other tool knows these laws exist.
|
|
300
|
+
const inUse = [...used.entries()]
|
|
301
|
+
.filter(([name]) => (recipes.get(name)?.usage.length ?? 0) > 0)
|
|
302
|
+
.sort((a, b) => b[1] - a[1]);
|
|
303
|
+
if (inUse.length > 0) {
|
|
304
|
+
console.log(section("What your system says about what you use"));
|
|
305
|
+
const shownComps = opts.laws || opts.all ? inUse : inUse.slice(0, 4);
|
|
306
|
+
for (const [name, count] of shownComps) {
|
|
307
|
+
const laws = recipes.get(name)?.usage ?? [];
|
|
308
|
+
const shown = opts.laws || opts.all ? laws : laws.slice(0, 2);
|
|
309
|
+
console.log(body(`ds-${name} · ${count} place${count === 1 ? "" : "s"}`));
|
|
310
|
+
for (const law of shown)
|
|
311
|
+
console.log(` ${law}`);
|
|
312
|
+
if (laws.length > shown.length) {
|
|
313
|
+
console.log(` +${laws.length - shown.length} more`);
|
|
314
|
+
}
|
|
315
|
+
console.log("");
|
|
316
|
+
}
|
|
317
|
+
if (inUse.length > shownComps.length) {
|
|
318
|
+
console.log(body(`+${inUse.length - shownComps.length} more components carry laws. Run with --laws.`));
|
|
319
|
+
console.log("");
|
|
320
|
+
}
|
|
321
|
+
}
|
|
274
322
|
console.log(section("What this means"));
|
|
275
|
-
for (const line of verdict(d, hasSystem))
|
|
323
|
+
for (const line of verdict(d, hasSystem, overrides.length))
|
|
276
324
|
console.log(line);
|
|
277
325
|
console.log("");
|
|
278
326
|
if (opts.strict && (d.findings.length > 0 || overrides.length > 0))
|
package/dist/doctor/overrides.js
CHANGED
|
@@ -46,7 +46,24 @@ export function bindingsOf(recipe) {
|
|
|
46
46
|
collect(r.variants, false);
|
|
47
47
|
collect(r.states, false);
|
|
48
48
|
collect(r.parts, false);
|
|
49
|
-
|
|
49
|
+
const usage = Array.isArray(r.usage)
|
|
50
|
+
? r.usage.filter((x) => typeof x === "string")
|
|
51
|
+
: [];
|
|
52
|
+
return { props, baseValues, usage };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* How many places each known component is used. An inventory nobody has today:
|
|
56
|
+
* a design system's own report can say "ds-button, 14 places" because it knows
|
|
57
|
+
* which names are components rather than guessing at class strings.
|
|
58
|
+
*/
|
|
59
|
+
export function countComponents(source, known) {
|
|
60
|
+
const out = new Map();
|
|
61
|
+
for (const m of source.matchAll(DS_IN_CLASS)) {
|
|
62
|
+
if (!known.has(m[1]))
|
|
63
|
+
continue;
|
|
64
|
+
out.set(m[1], (out.get(m[1]) ?? 0) + 1);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
50
67
|
}
|
|
51
68
|
/**
|
|
52
69
|
* Tailwind utilities that plainly set a CSS property. Deliberately short:
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,8 @@ Usage - deterministic, FREE:
|
|
|
27
27
|
synthesisui use <slug> "<intent>" print a ready-to-paste agent prompt to build/modify on-system
|
|
28
28
|
synthesisui clean [--force] strip create-next-app boilerplate (dry run without --force)
|
|
29
29
|
synthesisui doctor [--strict] [--all] audit this repo for DRIFT: every design value written by
|
|
30
|
-
hand,
|
|
30
|
+
hand, the token your system already has for it, and the
|
|
31
|
+
laws your system carries for what you use
|
|
31
32
|
|
|
32
33
|
Usage - AI, USES CREDITS (login required):
|
|
33
34
|
synthesisui generate "<desc>" AI-create a NEW component your DS doesn't have (token-only recipe)
|
|
@@ -53,6 +54,7 @@ Options:
|
|
|
53
54
|
--force clean: apply the changes (without it, dry run)
|
|
54
55
|
--strict doctor: exit 1 when drift is found (for CI)
|
|
55
56
|
--all doctor: list every finding, not just the loudest files
|
|
57
|
+
--laws doctor: show every usage law, not just the busiest components
|
|
56
58
|
--out <path> output path for the generated template (default: <pagesDir>/<file>)
|
|
57
59
|
-h, --help this help
|
|
58
60
|
|
|
@@ -122,6 +124,7 @@ async function main() {
|
|
|
122
124
|
dir,
|
|
123
125
|
strict: flags.strict === true,
|
|
124
126
|
all: flags.all === true,
|
|
127
|
+
laws: flags.laws === true,
|
|
125
128
|
});
|
|
126
129
|
break;
|
|
127
130
|
case "list":
|
package/package.json
CHANGED