roast-my-design-system 7.7.0 → 7.8.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/package.json +1 -1
- package/skills/roast-my-design-system/scripts/harvest/paint.mjs +3 -2
- package/skills/roast-my-design-system/scripts/harvest/tokens.mjs +1 -3
- package/skills/roast-my-design-system/scripts/lib/exempt.mjs +8 -0
- package/skills/roast-my-design-system/scripts/lib/guard-api.mjs +46 -3
- package/skills/roast-my-design-system/scripts/lib/version.mjs +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.8.0",
|
|
4
4
|
"mcpName": "io.github.gregkozakiewicz/roast-my-design-system",
|
|
5
5
|
"description": "Your AI can write the UI. This makes sure it writes your UI. A deterministic scanner scores your design system 0-100 against 34 public repos, reads React and web components (Stencil, Lit), hands you a copy-paste fix prompt for each top finding, writes rules for Claude, Cursor, Copilot and Windsurf with --apply, and runs as a local MCP server with --mcp.",
|
|
6
6
|
"keywords": [
|
|
@@ -30,7 +30,8 @@ import { PALETTE } from '../profiles/shadcn-data.mjs';
|
|
|
30
30
|
// and any opacity suffix. Word boundary at the front stops `text-primary` and
|
|
31
31
|
// `bg-sidebar` from matching; the shade stops `border-gray` (no shade) which
|
|
32
32
|
// is not a Tailwind class.
|
|
33
|
-
|
|
33
|
+
// exported for the guard: the same pattern judges an added line
|
|
34
|
+
export const PALETTE_CLASS_RE = new RegExp(`(?<![\\w-])(?:[\\w-]+:)*(?:bg|text|border|ring|outline|from|to|via|fill|stroke|divide|decoration|placeholder|caret|accent|shadow)-(?:${PALETTE})-(?:50|[1-9]00|950)(?:/\\d+)?(?![\\w-])`, 'g');
|
|
34
35
|
// Evening overrides painted by hand with white or black (the palette shades
|
|
35
36
|
// are already caught above with their dark: prefix).
|
|
36
37
|
const DARK_WB_RE = /(?<![\w-])dark:(?:bg|text|border)-(?:white|black)(?:\/\d+)?(?![\w-])/g;
|
|
@@ -70,7 +71,7 @@ export function countPaint(root, codeFiles, { uiDirs = [], kitNames = new Set()
|
|
|
70
71
|
if (src === null || exemptReason(f, src)) continue;
|
|
71
72
|
ownFiles += 1;
|
|
72
73
|
|
|
73
|
-
const tinHits = [...src.matchAll(
|
|
74
|
+
const tinHits = [...src.matchAll(PALETTE_CLASS_RE)].map((m) => m[0]);
|
|
74
75
|
const wbHits = [...src.matchAll(DARK_WB_RE)].map((m) => m[0]);
|
|
75
76
|
const all = [...tinHits, ...wbHits];
|
|
76
77
|
if (all.length) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync } from 'node:fs';
|
|
10
10
|
import {
|
|
11
|
-
EMAIL_PRINT_RE, ARTWORK_NAME_RE, RENDER_TO_IMAGE_RE, OG_ROUTE_RE, RENDERER_PATH_RE, CRASH_PAGE_RE, svgHeavy, exemptReason, isLibraryClass,
|
|
11
|
+
EMAIL_PRINT_RE, ARTWORK_NAME_RE, RENDER_TO_IMAGE_RE, OG_ROUTE_RE, RENDERER_PATH_RE, CRASH_PAGE_RE, svgHeavy, exemptReason, isLibraryClass, WIDGET_CSS_RE, WIDGET_CONFIG_RE,
|
|
12
12
|
} from '../lib/exempt.mjs';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
import { canonical, parseColor } from '../lib/color.mjs';
|
|
@@ -297,8 +297,6 @@ export function harvestTokens(root, styleFiles, codeFiles) {
|
|
|
297
297
|
// page and must beat the host's CSS: Tailwind imported with the important
|
|
298
298
|
// flag, or a config scoping every utility under an id. There, !important is
|
|
299
299
|
// the medium, like inline styles in email. Skipped, named with the reason.
|
|
300
|
-
const WIDGET_CSS_RE = /@import\s+["']tailwindcss(?:\/utilities\.css)?["'][^;]*\bimportant\b/;
|
|
301
|
-
const WIDGET_CONFIG_RE = /\bimportant\s*:\s*["']#/;
|
|
302
300
|
const widgetDirs = codeFiles.filter((f) => /(^|\/)tailwind\.config\.[mc]?[jt]s$/.test(f))
|
|
303
301
|
.filter((f) => WIDGET_CONFIG_RE.test(readSource(join(root, f)) ?? ''))
|
|
304
302
|
.map((f) => f.slice(0, f.lastIndexOf('/') + 1));
|
|
@@ -64,3 +64,11 @@ export function exemptReason(file, text = '') {
|
|
|
64
64
|
*/
|
|
65
65
|
export const LIBRARY_CLASS_RE = /^(cm-|ͼ|ProseMirror|monaco-|sp-|react-datepicker|react-tel-input|DateInput_|DayPicker|react-grid-|react-resizable|react-flow|react-select|react-toastify|Toastify|select2|EmojiPickerReact|epr-|notion-|fc-|fc$|tippy-|hljs|language-|ps__|ace_|ng-|docsearch|DocSearch|grecaptcha|rr-|sbdocs|docs-story|mapboxgl-|leaflet-|swiper-|slick-|rc-|ant-|Mui|ck-|ql-|tox-|mce-|DraftEditor|public-Draft|w-md-editor|rdp-|recharts-|apexcharts-|intercom-|crisp-|hubspot|shiki|katex|mermaid|prism-)/;
|
|
66
66
|
export const isLibraryClass = (c) => LIBRARY_CLASS_RE.test(c);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* An embedded widget (a survey, a chat bubble) lives inside a stranger's page
|
|
70
|
+
* and must beat the host's CSS: Tailwind imported with the important flag, or
|
|
71
|
+
* a config scoping every utility under an id. There, !important is the medium.
|
|
72
|
+
*/
|
|
73
|
+
export const WIDGET_CSS_RE = /@import\s+["']tailwindcss(?:\/utilities\.css)?["'][^;]*\bimportant\b/;
|
|
74
|
+
export const WIDGET_CONFIG_RE = /\bimportant\s*:\s*["']#/;
|
|
@@ -10,10 +10,13 @@
|
|
|
10
10
|
* text for styling, and say which on-system value a stray most resembles.
|
|
11
11
|
*/
|
|
12
12
|
import { extname, join } from 'node:path';
|
|
13
|
-
import { walkRepo, readSource } from '../harvest/walk.mjs';
|
|
13
|
+
import { walkRepo, readSource, profileRepo } from '../harvest/walk.mjs';
|
|
14
|
+
import { decideProfile, profileOf, installedDirs } from '../profiles/index.mjs';
|
|
15
|
+
import { PALETTE_CLASS_RE } from '../harvest/paint.mjs';
|
|
14
16
|
import { harvestTokens, extractStyling, normalizeHex, isGrey } from '../harvest/tokens.mjs';
|
|
15
17
|
import { harvestComponents, definedComponents } from '../harvest/components.mjs';
|
|
16
18
|
import { loadExclusions } from './exclusions.mjs';
|
|
19
|
+
import { WIDGET_CONFIG_RE } from './exempt.mjs';
|
|
17
20
|
import { hexRgb } from './nearpairs.mjs';
|
|
18
21
|
import { typefaceOf, GENERIC_FONTS } from './typefaces.mjs';
|
|
19
22
|
|
|
@@ -26,6 +29,13 @@ export { definedComponents };
|
|
|
26
29
|
// The files no checker should judge. Exported so a guard reads the same list
|
|
27
30
|
// as the engine rather than keeping a copy that drifts.
|
|
28
31
|
export { EMAIL_PRINT_RE, ARTWORK_NAME_RE, SVG_MARKUP_RE, exemptReason } from './exempt.mjs';
|
|
32
|
+
// !important as the medium (7.5): a widget stylesheet that must beat its host
|
|
33
|
+
// page, and a selector aimed at a library's own class names. The guard reads
|
|
34
|
+
// the same patterns, so the two checkers agree about the same declaration.
|
|
35
|
+
export { WIDGET_CSS_RE, WIDGET_CONFIG_RE, LIBRARY_CLASS_RE, isLibraryClass } from './exempt.mjs';
|
|
36
|
+
// A palette class (bg-blue-500, text-gray-600) where a theme variable exists:
|
|
37
|
+
// the shadcn paint check (7.2). Only meaningful when system.profile.paletteReady.
|
|
38
|
+
export { PALETTE_CLASS_RE };
|
|
29
39
|
|
|
30
40
|
// Radius, font size, shadow and typeface: the patterns, so both checkers agree
|
|
31
41
|
// on what a declaration is and what counts as a disciplined value.
|
|
@@ -63,6 +73,14 @@ export function learnSystem(repoRoot, { exclude = [] } = {}) {
|
|
|
63
73
|
const exclusions = loadExclusions(repoRoot, exclude);
|
|
64
74
|
const files = walkRepo(repoRoot, 14, exclusions);
|
|
65
75
|
const t = harvestTokens(repoRoot, files.styles, files.code);
|
|
76
|
+
const components = harvestComponents(repoRoot, files.code).components
|
|
77
|
+
.map(({ name, file, usageCount, isPage }) => ({ name, file, usageCount, isPage }));
|
|
78
|
+
const profile = profileRepo(repoRoot, files);
|
|
79
|
+
decideProfile(profile, components, files, repoRoot);
|
|
80
|
+
const P = profileOf(profile);
|
|
81
|
+
const widgetDirs = files.code.filter((f) => /(^|\/)tailwind\.config\.[mc]?[jt]s$/.test(f))
|
|
82
|
+
.filter((f) => WIDGET_CONFIG_RE.test(readSource(join(repoRoot, f)) ?? ''))
|
|
83
|
+
.map((f) => f.slice(0, f.lastIndexOf('/') + 1));
|
|
66
84
|
|
|
67
85
|
// Every --var definition, as normalizedValue → name, so a guard can say
|
|
68
86
|
// "use var(--blue-500)" instead of leaving the reader to hunt the hex.
|
|
@@ -97,9 +115,34 @@ export function learnSystem(repoRoot, { exclude = [] } = {}) {
|
|
|
97
115
|
// Trimmed to the four fields a guard can act on: what it is called, where
|
|
98
116
|
// it lives, how much the repo leans on it, and whether it is a page (pages
|
|
99
117
|
// are routes, not reusable parts, so two of a name is not a duplicate).
|
|
100
|
-
components
|
|
101
|
-
.map(({ name, file, usageCount, isPage }) => ({ name, file, usageCount, isPage })),
|
|
118
|
+
components,
|
|
102
119
|
files: { styles: files.styles.length, code: files.code.length },
|
|
120
|
+
// How the repo was read (7.1 to 7.7), decided once by the same profiles
|
|
121
|
+
// the report uses, so a guard treats installed code, a registry's
|
|
122
|
+
// published folders and a widget's stylesheet the way the score does.
|
|
123
|
+
profile: {
|
|
124
|
+
kind: P.kind,
|
|
125
|
+
role: P.role,
|
|
126
|
+
// folders the team did not write (a shadcn catalogue, installed
|
|
127
|
+
// registries, kit blocks): their brackets and copies are not the PR's
|
|
128
|
+
// sin. Empty on a registry: what it publishes is its own work.
|
|
129
|
+
// only a shadcn kit has installed code; a hand-written components/ui on a
|
|
130
|
+
// plain product is the team's own (the report splits nothing there either)
|
|
131
|
+
installedDirs: P.isShadcn && !P.isRegistry ? installedDirs(P) : [],
|
|
132
|
+
// a shadcn kit whose theme file holds the variables, in CSS-variable
|
|
133
|
+
// mode: a palette class in own code is paint from a tin
|
|
134
|
+
paletteReady: P.isShadcn && (P.shadcn?.sheet?.shadcnPresent ?? 0) >= 5 && P.designSystem?.cssVariables !== false,
|
|
135
|
+
sheetFile: P.shadcn?.sheet?.found ? P.shadcn.sheet.file : null,
|
|
136
|
+
// packages whose Tailwind config scopes utilities under an id: widgets
|
|
137
|
+
widgetDirs,
|
|
138
|
+
// a registry: only the published folders are counted; sibling variants
|
|
139
|
+
// and blocks hold the same names by design
|
|
140
|
+
registry: P.isRegistry && P.registry ? {
|
|
141
|
+
countedDirs: P.registry.publishedDirs ?? [],
|
|
142
|
+
variants: P.registry.variants ?? [],
|
|
143
|
+
blockDirs: P.registry.blockDirs ?? [],
|
|
144
|
+
} : null,
|
|
145
|
+
},
|
|
103
146
|
};
|
|
104
147
|
}
|
|
105
148
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Single version constant for the engine — imported by diagnose (report
|
|
2
2
|
// footer) and rules (generated-by line). This is the bump spot that used to
|
|
3
3
|
// live as a const inside diagnose/index.mjs.
|
|
4
|
-
export const VERSION = '7.
|
|
4
|
+
export const VERSION = '7.8.0';
|
|
5
5
|
// The shape of harvest.json and summary.json. Bumped only when a field is
|
|
6
6
|
// renamed, removed or changes meaning; a new field is not a new schema. A
|
|
7
7
|
// tool comparing two scans compares like with like by this number, not by VERSION.
|