roast-my-design-system 4.2.4 → 4.2.6
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/bin/roast.mjs +15 -2
- package/package.json +1 -1
- package/skills/roast-my-design-system/scripts/diagnose/index.mjs +2 -2
- package/skills/roast-my-design-system/scripts/harvest/index.mjs +9 -43
- package/skills/roast-my-design-system/scripts/harvest/summary.mjs +61 -0
- package/skills/roast-my-design-system/scripts/lib/version.mjs +1 -1
package/bin/roast.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
12
12
|
import { mkdtempSync, rmSync, readFileSync, existsSync, statSync } from 'node:fs';
|
|
13
13
|
import { join, dirname, resolve } from 'node:path';
|
|
14
14
|
import { tmpdir } from 'node:os';
|
|
15
|
-
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
16
16
|
|
|
17
17
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
18
18
|
const SCRIPTS = join(HERE, '../skills/roast-my-design-system/scripts');
|
|
@@ -106,7 +106,7 @@ function run(script, args, env) {
|
|
|
106
106
|
}
|
|
107
107
|
const say = (s) => { if (!asJson) console.log(s); };
|
|
108
108
|
|
|
109
|
-
say(`roast-my-design-system ${VERSION} · read-only scan, nothing leaves your machine
|
|
109
|
+
say(`roast-my-design-system ${VERSION} · read-only scan, nothing leaves your machine`);
|
|
110
110
|
// the harvest goes to a temp dir this wrapper deletes right after; tell the
|
|
111
111
|
// script so it does not print a path that will be gone seconds later
|
|
112
112
|
run('harvest/index.mjs', [target, '--out', harvestPath,
|
|
@@ -115,6 +115,19 @@ say('');
|
|
|
115
115
|
run('diagnose/index.mjs', [harvestPath, '--out', outPath, '--theme', theme, '--summary', summaryPath,
|
|
116
116
|
...(commissionedBy ? ['--by', commissionedBy] : [])]);
|
|
117
117
|
|
|
118
|
+
// The verdict leads, the evidence follows: harvest details print here, after
|
|
119
|
+
// the diagnosis, rendered from harvest.json via the same lines the direct
|
|
120
|
+
// harvest run uses.
|
|
121
|
+
if (!asJson) {
|
|
122
|
+
try {
|
|
123
|
+
const harvestData = JSON.parse(readFileSync(harvestPath, 'utf8'));
|
|
124
|
+
const { detailLines } = await import(pathToFileURL(join(SCRIPTS, 'harvest/summary.mjs')).href);
|
|
125
|
+
console.log('');
|
|
126
|
+
for (const l of detailLines(harvestData)) console.log(l);
|
|
127
|
+
console.log(`\n scanned in ${harvestData.tookMs}ms`);
|
|
128
|
+
} catch { /* details are garnish; the report exists either way */ }
|
|
129
|
+
}
|
|
130
|
+
|
|
118
131
|
const rulesPath = resolve(join(target, 'design-system-rules.md'));
|
|
119
132
|
if (wantRules) {
|
|
120
133
|
say('');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.6",
|
|
4
4
|
"description": "Your AI can write the UI. This makes sure it writes your UI. A deterministic scanner counts every colour, spacing value and duplicate component, scores you 0-100 against 34 public repos, scopes the scan with .roastignore, injects agent rules with --apply.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design-system",
|
|
@@ -1324,5 +1324,5 @@ if (summaryPath) {
|
|
|
1324
1324
|
|
|
1325
1325
|
console.log(`✓ Diagnosis for ${repoName}`);
|
|
1326
1326
|
if (healthScore !== null) console.log(` score: ${healthScore}/100`);
|
|
1327
|
-
console.log(
|
|
1328
|
-
console.log(` → ${outPath}`);
|
|
1327
|
+
console.log(`\n Verdict: ${verdict}`);
|
|
1328
|
+
console.log(` Your free report is here → ${outPath}`);
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { writeFileSync } from 'node:fs';
|
|
16
16
|
import { resolve } from 'node:path';
|
|
17
17
|
import { walkRepo, profileRepo } from './walk.mjs';
|
|
18
|
+
import { headerLines, detailLines } from './summary.mjs';
|
|
18
19
|
import { harvestComponents } from './components.mjs';
|
|
19
20
|
import { harvestTokens } from './tokens.mjs';
|
|
20
21
|
import { findDuplicates } from './duplicates.mjs';
|
|
@@ -157,47 +158,12 @@ harvest.tookMs = Date.now() - t0;
|
|
|
157
158
|
writeFileSync(outPath, JSON.stringify(harvest, null, 2));
|
|
158
159
|
|
|
159
160
|
// ---------- one-screen summary ----------
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
};
|
|
169
|
-
// A "top" list is only news when something repeats; all-×1 says nothing.
|
|
170
|
-
const top = (list, n = 5) => list.some((e) => e.count > 1)
|
|
171
|
-
? ` top: ${list.slice(0, n).map((e) => `${showVal(e.value)} ×${e.count}`).join(', ')}`
|
|
172
|
-
: (list.length ? ', none repeated' : '');
|
|
173
|
-
|
|
174
|
-
console.log(`\nHarvest: ${profile.name ?? target}`);
|
|
175
|
-
console.log(` framework: ${profile.framework}${profile.typescript ? ' + TS' : ''} design system: ${profile.designSystem.kind}${profile.designSystem.name ? ` (${profile.designSystem.name})` : ''} styling: ${profile.stylingDeps.join(', ') || 'none detected'}`);
|
|
176
|
-
console.log(` files: ${files.code.length} code, ${files.styles.length} style`);
|
|
177
|
-
if (exclusions.patterns.length) {
|
|
178
|
-
// Same voice as the report header: source named once, slashes on folders,
|
|
179
|
-
// and the total at the end because that is the number that lands.
|
|
180
|
-
const groups = [...new Set(exclusions.patterns.map((p) => p.source))].map((src) => {
|
|
181
|
-
const own = exclusions.patterns.filter((p) => p.source === src);
|
|
182
|
-
return `(${src}): ${own.map((p, i) => `${p.pattern}/ ${p.files}${i === 0 ? ' files' : ''}`).join(', ')}`;
|
|
183
|
-
});
|
|
184
|
-
const total = exclusions.patterns.reduce((sum, p) => sum + p.files, 0);
|
|
185
|
-
console.log(` excluded by you ${groups.join(' · ')} · ${total} files kept out of this scan`);
|
|
186
|
-
}
|
|
187
|
-
console.log(`\n components: ${components.length} defined (${nonPage.length} reusable, ${components.length - nonPage.length} pages)`);
|
|
188
|
-
console.log(` duplicates: ${duplicates.exactDuplicates.length} exact same-name, ${duplicates.families.length} name families`);
|
|
189
|
-
for (const d of duplicates.exactDuplicates.slice(0, 3)) console.log(` · ${d.name} defined in ${d.files.length} files`);
|
|
190
|
-
for (const f of duplicates.families.slice(0, 3)) console.log(` · ${f.root} family: ${f.members.map((m) => m.name).join(', ')}`);
|
|
191
|
-
console.log(`\n colours: ${tokens.colors.length} distinct (${hexColors.length} hex, of which ${tokens.greyCount} greys)${top(tokens.colors, 4)}`);
|
|
192
|
-
console.log(` spacing: ${tokens.spacing.length} distinct CSS values${top(tokens.spacing, 5)}`);
|
|
193
|
-
console.log(` radii: ${tokens.radii.length} font sizes: ${tokens.fontSizes.length} font families: ${tokens.fontFamilies.length} shadows: ${tokens.shadows.length}`);
|
|
194
|
-
console.log(` tailwind: ${tokens.tailwind.colors.length} colour utils, ${tokens.tailwind.spacing.length} spacing utils, ${tokens.tailwind.textSizes.length} text sizes`);
|
|
195
|
-
console.log(` inline styles: ${tokens.inlineStyles.count} blocks`);
|
|
196
|
-
console.log(` context files: ${context.map((c) => c.file).join(', ') || 'none'}`);
|
|
197
|
-
// The npx wrapper writes the harvest to a temp dir it deletes right after;
|
|
198
|
-
// pointing the user at a path that will not exist is noise there.
|
|
199
|
-
if (process.env.ROAST_EPHEMERAL_OUT === '1') {
|
|
200
|
-
console.log(`\n scanned in ${harvest.tookMs}ms\n`);
|
|
201
|
-
} else {
|
|
202
|
-
console.log(`\n → ${outPath} (${harvest.tookMs}ms)\n`);
|
|
161
|
+
// The npx wrapper reorders the story (header, then the diagnosis, then these
|
|
162
|
+
// details), so the lines live in summary.mjs and the wrapper prints details
|
|
163
|
+
// itself from harvest.json. Run directly (the skill flow), everything prints
|
|
164
|
+
// here in one go, with the real output path at the end.
|
|
165
|
+
for (const l of headerLines(harvest)) console.log(l);
|
|
166
|
+
if (process.env.ROAST_EPHEMERAL_OUT !== '1') {
|
|
167
|
+
for (const l of detailLines(harvest)) console.log(l);
|
|
168
|
+
console.log(`\n → ${outPath} (${harvest.tookMs}ms)`);
|
|
203
169
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal summary lines for a harvest, split so the npx wrapper can change
|
|
3
|
+
* the running order: header first, diagnosis from step 2 next, details after.
|
|
4
|
+
* Running harvest/index.mjs directly (the skill flow) prints both in one go.
|
|
5
|
+
* Everything reads from the harvest object itself, so the wrapper can render
|
|
6
|
+
* details from harvest.json after the harvest process has exited.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// A truncated capture like "rgba(var(--ink-rgb)" reads as a glitch in the
|
|
10
|
+
// terminal; the token reference inside it is the real story, so show that.
|
|
11
|
+
const showVal = (v) => {
|
|
12
|
+
const m = /var\((--[A-Za-z0-9_-]+)/.exec(v);
|
|
13
|
+
const balanced = (v.match(/\(/g) ?? []).length === (v.match(/\)/g) ?? []).length;
|
|
14
|
+
return m && !balanced ? `var(${m[1]})` : v;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// A "top" list is only news when something repeats; all-×1 says nothing.
|
|
18
|
+
const top = (list, n = 5) => list.some((e) => e.count > 1)
|
|
19
|
+
? ` top: ${list.slice(0, n).map((e) => `${showVal(e.value)} ×${e.count}`).join(', ')}`
|
|
20
|
+
: (list.length ? ', none repeated' : '');
|
|
21
|
+
|
|
22
|
+
export function headerLines(h) {
|
|
23
|
+
const p = h.profile;
|
|
24
|
+
return [
|
|
25
|
+
`Harvest: ${p.name ?? h.repo}`,
|
|
26
|
+
` → framework: ${p.framework}${p.typescript ? ' + TS' : ''} design system: ${p.designSystem.kind}${p.designSystem.name ? ` (${p.designSystem.name})` : ''} styling: ${p.stylingDeps.join(', ') || 'none detected'}`,
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function detailLines(h) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
const components = h.components ?? [];
|
|
33
|
+
const duplicates = h.duplicates ?? { exactDuplicates: [], families: [] };
|
|
34
|
+
const tokens = h.tokens;
|
|
35
|
+
const nonPage = components.filter((c) => !c.isPage);
|
|
36
|
+
const hexColors = tokens.colors.filter((c) => c.value.startsWith('#'));
|
|
37
|
+
|
|
38
|
+
lines.push(` files: ${h.files.code} code, ${h.files.styles} style`);
|
|
39
|
+
if (h.exclusions?.patterns?.length) {
|
|
40
|
+
// Same voice as the report header: source named once, slashes on folders,
|
|
41
|
+
// and the total at the end because that is the number that lands.
|
|
42
|
+
const groups = [...new Set(h.exclusions.patterns.map((p) => p.source))].map((src) => {
|
|
43
|
+
const own = h.exclusions.patterns.filter((p) => p.source === src);
|
|
44
|
+
return `(${src}): ${own.map((p, i) => `${p.pattern}/ ${p.files}${i === 0 ? ' files' : ''}`).join(', ')}`;
|
|
45
|
+
});
|
|
46
|
+
lines.push(` excluded by you ${groups.join(' · ')} · ${h.exclusions.filesExcluded} files kept out of this scan`);
|
|
47
|
+
}
|
|
48
|
+
lines.push('');
|
|
49
|
+
lines.push(` components: ${components.length} defined (${nonPage.length} reusable, ${components.length - nonPage.length} pages)`);
|
|
50
|
+
lines.push(` duplicates: ${duplicates.exactDuplicates.length} exact same-name, ${duplicates.families.length} name families`);
|
|
51
|
+
for (const d of duplicates.exactDuplicates.slice(0, 3)) lines.push(` · ${d.name} defined in ${d.files.length} files`);
|
|
52
|
+
for (const f of duplicates.families.slice(0, 3)) lines.push(` · ${f.root} family: ${f.members.map((m) => m.name).join(', ')}`);
|
|
53
|
+
lines.push('');
|
|
54
|
+
lines.push(` colours: ${tokens.colors.length} distinct (${hexColors.length} hex, of which ${tokens.greyCount} greys)${top(tokens.colors, 4)}`);
|
|
55
|
+
lines.push(` spacing: ${tokens.spacing.length} distinct CSS values${top(tokens.spacing, 5)}`);
|
|
56
|
+
lines.push(` radii: ${tokens.radii.length} font sizes: ${tokens.fontSizes.length} font families: ${tokens.fontFamilies.length} shadows: ${tokens.shadows.length}`);
|
|
57
|
+
lines.push(` tailwind: ${tokens.tailwind.colors.length} colour utils, ${tokens.tailwind.spacing.length} spacing utils, ${tokens.tailwind.textSizes.length} text sizes`);
|
|
58
|
+
lines.push(` inline styles: ${tokens.inlineStyles.count} blocks`);
|
|
59
|
+
lines.push(` context files: ${(h.context ?? []).map((c) => c.file).join(', ') || 'none'}`);
|
|
60
|
+
return lines;
|
|
61
|
+
}
|