roast-my-design-system 4.2.2 → 4.2.4
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 +6 -3
- package/package.json +1 -1
- package/skills/roast-my-design-system/scripts/diagnose/index.mjs +1 -1
- package/skills/roast-my-design-system/scripts/harvest/components.mjs +1 -1
- package/skills/roast-my-design-system/scripts/harvest/index.mjs +29 -7
- package/skills/roast-my-design-system/scripts/harvest/walk.mjs +18 -2
- package/skills/roast-my-design-system/scripts/lib/version.mjs +1 -1
package/bin/roast.mjs
CHANGED
|
@@ -95,9 +95,10 @@ const tmp = mkdtempSync(join(tmpdir(), 'roast-'));
|
|
|
95
95
|
const harvestPath = join(tmp, 'harvest.json');
|
|
96
96
|
const summaryPath = join(tmp, 'summary.json');
|
|
97
97
|
|
|
98
|
-
function run(script, args) {
|
|
98
|
+
function run(script, args, env) {
|
|
99
99
|
// --json keeps stdout clean for the JSON payload; child chatter is dropped
|
|
100
|
-
const r = spawnSync(process.execPath, [join(SCRIPTS, script), ...args],
|
|
100
|
+
const r = spawnSync(process.execPath, [join(SCRIPTS, script), ...args],
|
|
101
|
+
{ stdio: asJson ? 'ignore' : 'inherit', ...(env ? { env: { ...process.env, ...env } } : {}) });
|
|
101
102
|
if (r.status !== 0) {
|
|
102
103
|
rmSync(tmp, { recursive: true, force: true });
|
|
103
104
|
process.exit(r.status ?? 1);
|
|
@@ -106,8 +107,10 @@ function run(script, args) {
|
|
|
106
107
|
const say = (s) => { if (!asJson) console.log(s); };
|
|
107
108
|
|
|
108
109
|
say(`roast-my-design-system ${VERSION} · read-only scan, nothing leaves your machine\n`);
|
|
110
|
+
// the harvest goes to a temp dir this wrapper deletes right after; tell the
|
|
111
|
+
// script so it does not print a path that will be gone seconds later
|
|
109
112
|
run('harvest/index.mjs', [target, '--out', harvestPath,
|
|
110
|
-
...excludes.flatMap((e) => ['--exclude', e])]);
|
|
113
|
+
...excludes.flatMap((e) => ['--exclude', e])], { ROAST_EPHEMERAL_OUT: '1' });
|
|
111
114
|
say('');
|
|
112
115
|
run('diagnose/index.mjs', [harvestPath, '--out', outPath, '--theme', theme, '--summary', summaryPath,
|
|
113
116
|
...(commissionedBy ? ['--by', commissionedBy] : [])]);
|
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.4",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Diagnose — step 2 of the
|
|
3
|
+
* Diagnose — step 2 of the pipeline. Renders a harvest.json into a
|
|
4
4
|
* single self-contained HTML report: the visceral, shareable "here's your
|
|
5
5
|
* mess" page with real file paths. No dependencies, no server — one file.
|
|
6
6
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Component harvest — every component defined in the repo, with its REAL prop
|
|
3
3
|
* signature and where it is actually used. Parsers lifted from 1.0's
|
|
4
|
-
* compile-skill.mjs
|
|
4
|
+
* compile-skill.mjs — brace/string-aware regex parsing,
|
|
5
5
|
* no AST dependency — then extended to scan the whole repo, not one ui/ dir.
|
|
6
6
|
*/
|
|
7
7
|
import { readFileSync } from 'node:fs';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Harvest — step 1 of the
|
|
3
|
+
* Harvest — step 1 of the pipeline. Non-destructive scan of a repo:
|
|
4
4
|
* components (real props, real usages), all styling, implicit tokens with
|
|
5
5
|
* frequency counts, duplicates, context files. Writes harvest.json; prints a
|
|
6
6
|
* one-screen summary (the seed of the step-2 diagnosis).
|
|
@@ -159,23 +159,45 @@ writeFileSync(outPath, JSON.stringify(harvest, null, 2));
|
|
|
159
159
|
// ---------- one-screen summary ----------
|
|
160
160
|
const nonPage = components.filter((c) => !c.isPage);
|
|
161
161
|
const hexColors = tokens.colors.filter((c) => c.value.startsWith('#'));
|
|
162
|
-
|
|
162
|
+
// A truncated capture like "rgba(var(--ink-rgb)" reads as a glitch in the
|
|
163
|
+
// terminal; the token reference inside it is the real story, so show that.
|
|
164
|
+
const showVal = (v) => {
|
|
165
|
+
const m = /var\((--[A-Za-z0-9_-]+)/.exec(v);
|
|
166
|
+
const balanced = (v.match(/\(/g) ?? []).length === (v.match(/\)/g) ?? []).length;
|
|
167
|
+
return m && !balanced ? `var(${m[1]})` : v;
|
|
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' : '');
|
|
163
173
|
|
|
164
174
|
console.log(`\nHarvest: ${profile.name ?? target}`);
|
|
165
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'}`);
|
|
166
176
|
console.log(` files: ${files.code.length} code, ${files.styles.length} style`);
|
|
167
177
|
if (exclusions.patterns.length) {
|
|
168
|
-
|
|
169
|
-
|
|
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`);
|
|
170
186
|
}
|
|
171
187
|
console.log(`\n components: ${components.length} defined (${nonPage.length} reusable, ${components.length - nonPage.length} pages)`);
|
|
172
188
|
console.log(` duplicates: ${duplicates.exactDuplicates.length} exact same-name, ${duplicates.families.length} name families`);
|
|
173
189
|
for (const d of duplicates.exactDuplicates.slice(0, 3)) console.log(` · ${d.name} defined in ${d.files.length} files`);
|
|
174
190
|
for (const f of duplicates.families.slice(0, 3)) console.log(` · ${f.root} family: ${f.members.map((m) => m.name).join(', ')}`);
|
|
175
|
-
console.log(`\n colours: ${tokens.colors.length} distinct (${hexColors.length} hex, of which ${tokens.greyCount} greys)
|
|
176
|
-
console.log(` spacing: ${tokens.spacing.length} distinct CSS values
|
|
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)}`);
|
|
177
193
|
console.log(` radii: ${tokens.radii.length} font sizes: ${tokens.fontSizes.length} font families: ${tokens.fontFamilies.length} shadows: ${tokens.shadows.length}`);
|
|
178
194
|
console.log(` tailwind: ${tokens.tailwind.colors.length} colour utils, ${tokens.tailwind.spacing.length} spacing utils, ${tokens.tailwind.textSizes.length} text sizes`);
|
|
179
195
|
console.log(` inline styles: ${tokens.inlineStyles.count} blocks`);
|
|
180
196
|
console.log(` context files: ${context.map((c) => c.file).join(', ') || 'none'}`);
|
|
181
|
-
|
|
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`);
|
|
203
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* File walker + repo profile. Adapted from
|
|
2
|
+
* File walker + repo profile. Adapted from an earlier internal repo inspector,
|
|
3
3
|
* widened: the harvester must see EVERYTHING that styles the app — code,
|
|
4
4
|
* stylesheets of any flavor, and config — not just a happy-path shadcn layout.
|
|
5
5
|
*/
|
|
@@ -142,11 +142,15 @@ export function profileRepo(root, files) {
|
|
|
142
142
|
deps = { ...deps, ...pkg.dependencies, ...pkg.devDependencies };
|
|
143
143
|
const monorepo = pkgFiles.some((f) => f !== 'package.json');
|
|
144
144
|
|
|
145
|
+
// Hand-built sites are a real category, not a detection failure: "unknown"
|
|
146
|
+
// next to a good score reads like a shrug, so name what is actually there.
|
|
147
|
+
const htmlFiles = files.other.filter((f) => /\.html?$/.test(f)).length;
|
|
145
148
|
const framework =
|
|
146
149
|
deps.next ? 'next'
|
|
147
150
|
: deps['@remix-run/react'] || deps['@react-router/dev'] ? 'remix'
|
|
148
151
|
: deps.vite && deps.react ? 'vite-react'
|
|
149
152
|
: deps.react ? 'react'
|
|
153
|
+
: htmlFiles > 0 ? 'static HTML/CSS'
|
|
150
154
|
: 'unknown';
|
|
151
155
|
|
|
152
156
|
const componentsJson = readJSON(join(root, 'components.json'));
|
|
@@ -156,10 +160,19 @@ export function profileRepo(root, files) {
|
|
|
156
160
|
const knownLib = KNOWN_LIBRARIES.find((d) => deps[d.pkg]);
|
|
157
161
|
const homegrown = files.code.filter((f) => /(^|\/)components\//.test(f) && !/\/components\/ui\//.test(f) && /\.(tsx|jsx)$/.test(f));
|
|
158
162
|
|
|
163
|
+
// A design system does not have to arrive through npm. A stylesheet defining
|
|
164
|
+
// a real set of CSS custom properties IS one — arguably the purest form —
|
|
165
|
+
// and calling it "none" undersells exactly the discipline the ideal asks for.
|
|
166
|
+
const tokenDefs = files.styles.slice(0, 8).reduce((sum, f) => {
|
|
167
|
+
const css = read(join(root, f));
|
|
168
|
+
return sum + (css ? (css.match(/--[A-Za-z0-9_-]+\s*:/g) ?? []).length : 0);
|
|
169
|
+
}, 0);
|
|
170
|
+
|
|
159
171
|
let designSystem;
|
|
160
172
|
if (isShadcn) designSystem = { kind: 'shadcn', name: 'shadcn/ui', confidence: 'high' };
|
|
161
173
|
else if (knownLib) designSystem = { kind: 'library', name: knownLib.name, pkg: knownLib.pkg, confidence: 'high' };
|
|
162
174
|
else if (homegrown.length >= 3) designSystem = { kind: 'custom', name: 'custom (unrecognized)', confidence: 'low' };
|
|
175
|
+
else if (tokenDefs >= 5) designSystem = { kind: 'custom', name: 'CSS tokens', confidence: 'medium' };
|
|
163
176
|
else designSystem = { kind: 'none', confidence: 'high' };
|
|
164
177
|
|
|
165
178
|
// Import alias from tsconfig paths or components.json
|
|
@@ -171,7 +184,10 @@ export function profileRepo(root, files) {
|
|
|
171
184
|
if (key) alias = key.replace('/*', '');
|
|
172
185
|
}
|
|
173
186
|
|
|
174
|
-
|
|
187
|
+
// No toolchain in package.json does not mean no styling: if the walk found
|
|
188
|
+
// real stylesheets, plain CSS is what is there — say so, not "none detected".
|
|
189
|
+
let styling = STYLING_DEPS.filter((s) => deps[s.pkg]).map((s) => s.label);
|
|
190
|
+
if (!styling.length && files.styles.length) styling = ['plain CSS'];
|
|
175
191
|
|
|
176
192
|
// The git remote is a far better identity than package.json's name field
|
|
177
193
|
// ("chatbot" vs "vercel/ai-chatbot"). Parsed from .git/config, no git exec.
|