@monoes/monodesign 1.2.11 → 1.2.13
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/cli/engine/browser/injected/index.mjs +16 -18
- package/cli/engine/cli/main.mjs +3 -3
- package/cli/engine/detect-antipatterns-browser.js +67 -47
- package/cli/engine/engines/regex/detect-text.mjs +6 -6
- package/cli/engine/engines/static-html/css-cascade.mjs +6 -5
- package/cli/engine/engines/static-html/detect-html.mjs +6 -6
- package/cli/engine/fix/index.mjs +3 -3
- package/cli/engine/node/file-system.mjs +1 -1
- package/cli/engine/registry/antipatterns.mjs +1 -1
- package/cli/engine/rules/checks.mjs +24 -25
- package/cli/engine/shared/color.mjs +2 -2
- package/cli/engine/shared/constants.mjs +1 -1
- package/cli/engine/shared/inline-ignores.mjs +2 -2
- package/cli/lib/monodesign-config.mjs +5 -5
- package/package.json +1 -1
- package/skill/scripts/context.mjs +4 -4
- package/skill/scripts/critique-storage.mjs +1 -1
- package/skill/scripts/detect-csp.mjs +1 -6
- package/skill/scripts/hook-admin.mjs +6 -6
- package/skill/scripts/hook-before-edit.mjs +4 -4
- package/skill/scripts/hook-lib.mjs +20 -11
- package/skill/scripts/hook.mjs +1 -1
- package/skill/scripts/lib/design-parser.mjs +9 -42
- package/skill/scripts/lib/is-generated.mjs +1 -1
- package/skill/scripts/lib/monodesign-paths.mjs +1 -1
- package/skill/scripts/live/event-validation.mjs +13 -13
- package/skill/scripts/live/manual-apply.mjs +4 -4
- package/skill/scripts/live/manual-edits-buffer.mjs +1 -1
- package/skill/scripts/live/session-store.mjs +5 -5
- package/skill/scripts/live/svelte-component.mjs +13 -13
- package/skill/scripts/live/sveltekit-adapter.mjs +4 -4
- package/skill/scripts/live/ui-core.mjs +1 -1
- package/skill/scripts/live-accept.mjs +24 -24
- package/skill/scripts/live-browser-dom.js +8 -7
- package/skill/scripts/live-browser-session.js +8 -7
- package/skill/scripts/live-browser.js +405 -432
- package/skill/scripts/live-commit-manual-edits.mjs +6 -6
- package/skill/scripts/live-copy-edit-agent.mjs +2 -2
- package/skill/scripts/live-discard-manual-edits.mjs +1 -1
- package/skill/scripts/live-inject.mjs +7 -7
- package/skill/scripts/live-insert.mjs +11 -11
- package/skill/scripts/live-manual-edit-evidence.mjs +4 -4
- package/skill/scripts/live-poll.mjs +5 -5
- package/skill/scripts/live-server.mjs +10 -10
- package/skill/scripts/live-wrap.mjs +31 -41
- package/skill/scripts/live.mjs +5 -5
- package/skill/scripts/palette.mjs +3 -1
- package/skill/scripts/pin.mjs +1 -1
|
@@ -74,14 +74,14 @@ function checkStaticPageTypography(document, window) {
|
|
|
74
74
|
const sorted = [...sizes].sort((a, b) => a - b);
|
|
75
75
|
const ratio = sorted[sorted.length - 1] / sorted[0];
|
|
76
76
|
if (ratio < 2.0) {
|
|
77
|
-
findings.push({ id: 'flat-type-hierarchy', snippet: `Sizes: ${sorted.map(s => s
|
|
77
|
+
findings.push({ id: 'flat-type-hierarchy', snippet: `Sizes: ${sorted.map(s => `${s}px`).join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
return findings;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
function checkElementBrokenImage(el) {
|
|
84
|
-
const src =
|
|
84
|
+
const src = el.getAttribute?.('src') ?? el.attribs?.src;
|
|
85
85
|
// Missing src attribute entirely
|
|
86
86
|
if (src === undefined || src === null) {
|
|
87
87
|
return [{ id: 'broken-image', snippet: '<img> with no src attribute' }];
|
|
@@ -95,10 +95,10 @@ function checkElementBrokenImage(el) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
const STATIC_ELEMENT_RULES = [
|
|
98
|
-
{ id: 'border-rules', selector: '*', run: (el, tag, style, window,
|
|
98
|
+
{ id: 'border-rules', selector: '*', run: (el, tag, style, window, _customPropMap) => checkElementBorders(tag, style, null, resolveBorderRadiusPx(el, style, parseFloat(style.width) || 0, window)) },
|
|
99
99
|
{ id: 'color-rules', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementColors(el, style, tag, window, customPropMap, false) },
|
|
100
100
|
{ id: 'dark-glow', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementGlow(tag, style, resolveBackground(el.parentElement || el, window, customPropMap)) },
|
|
101
|
-
{ id: 'motion-rules', selector: '*', run: (
|
|
101
|
+
{ id: 'motion-rules', selector: '*', run: (_el, tag, style) => checkElementMotion(tag, style) },
|
|
102
102
|
{ id: 'icon-tile-stack', selector: 'h1,h2,h3,h4,h5,h6', run: (el, tag, _style, window) => checkElementIconTile(el, tag, window) },
|
|
103
103
|
{ id: 'italic-serif-display', selector: 'h1,h2', run: (el, tag, style) => checkElementItalicSerif(el, style, tag) },
|
|
104
104
|
{ id: 'hero-eyebrow-chip', selector: 'h1', run: (el, tag, style, window, customPropMap) => checkElementHeroEyebrow(el, style, tag, window, customPropMap) },
|
|
@@ -106,8 +106,8 @@ const STATIC_ELEMENT_RULES = [
|
|
|
106
106
|
{ id: 'quality-rules', selector: '*', run: (el, tag, style, window) => checkElementQuality(el, style, tag, window) },
|
|
107
107
|
{ id: 'oversized-h1', selector: 'h1', run: (el, tag, style, window) => checkElementOversizedH1(el, style, tag, window) },
|
|
108
108
|
{ id: 'clipped-overflow-container', selector: '*', run: (el, tag, style, window) => checkElementClippedOverflow(el, style, tag, window) },
|
|
109
|
-
{ id: 'gpt-thin-border-wide-shadow', selector: '*', run: (el,
|
|
110
|
-
{ id: 'image-missing-dimensions', selector: 'img', run: (el,
|
|
109
|
+
{ id: 'gpt-thin-border-wide-shadow', selector: '*', run: (el, _tag, style) => checkElementGptBorderShadow(el, style) },
|
|
110
|
+
{ id: 'image-missing-dimensions', selector: 'img', run: (el, _tag, style) => checkElementImageDimensions(el, style) },
|
|
111
111
|
{ id: 'small-touch-target', selector: 'a,button,input,select,summary,[role=button],[role=link],[role=menuitem],[role=tab],[role=switch]', run: (el, tag, style) => checkElementSmallTouchTarget(el, style, tag) },
|
|
112
112
|
];
|
|
113
113
|
|
package/cli/engine/fix/index.mjs
CHANGED
|
@@ -130,7 +130,7 @@ function groupByRule(findings) {
|
|
|
130
130
|
async function runFix(targets, options = {}) {
|
|
131
131
|
const cwd = options.cwd || process.cwd();
|
|
132
132
|
const dryRun = Boolean(options.dryRun);
|
|
133
|
-
const ruleFilter = options.rules
|
|
133
|
+
const ruleFilter = options.rules?.length ? new Set(options.rules) : null;
|
|
134
134
|
const config = options.noConfig
|
|
135
135
|
? { ignoreRules: [], ignoreFiles: [], ignoreValues: [] }
|
|
136
136
|
: readDetectionConfig(cwd);
|
|
@@ -409,8 +409,8 @@ async function runFixCli(args) {
|
|
|
409
409
|
const report = await runFix(targets, options);
|
|
410
410
|
for (const warning of report.warnings) process.stderr.write(`${warning}\n`);
|
|
411
411
|
const formatted = formatFixReport(report, jsonMode);
|
|
412
|
-
if (jsonMode || report.dryRun) process.stdout.write(formatted
|
|
413
|
-
else process.stderr.write(formatted
|
|
412
|
+
if (jsonMode || report.dryRun) process.stdout.write(`${formatted}\n`);
|
|
413
|
+
else process.stderr.write(`${formatted}\n`);
|
|
414
414
|
process.exit(!report.dryRun && report.remaining.length > 0 ? 2 : 0);
|
|
415
415
|
}
|
|
416
416
|
|
|
@@ -46,7 +46,7 @@ function resolveImport(specifier, fromDir, fileSet) {
|
|
|
46
46
|
}
|
|
47
47
|
// index file convention
|
|
48
48
|
for (const ext of SCANNABLE_EXTENSIONS) {
|
|
49
|
-
const indexFile = path.join(base,
|
|
49
|
+
const indexFile = path.join(base, `index${ext}`);
|
|
50
50
|
if (fileSet.has(indexFile)) return indexFile;
|
|
51
51
|
}
|
|
52
52
|
return null;
|
|
@@ -527,7 +527,7 @@ function filterByProviders(findings, providers = []) {
|
|
|
527
527
|
if (!GATED_PROVIDERS.size) return findings;
|
|
528
528
|
return findings.filter(f => {
|
|
529
529
|
const rule = getAntipattern(f.antipattern);
|
|
530
|
-
if (!rule
|
|
530
|
+
if (!rule?.gated) return true;
|
|
531
531
|
return enabled.has(rule.gated);
|
|
532
532
|
});
|
|
533
533
|
}
|
|
@@ -82,7 +82,7 @@ function checkColors(opts) {
|
|
|
82
82
|
if (hasDirectText && textColor && !isEmojiOnly) {
|
|
83
83
|
// Run background-dependent checks against either a solid bg or, if the
|
|
84
84
|
// ancestor is a gradient, against every gradient stop (use the worst case).
|
|
85
|
-
const bgs = effectiveBg ? [effectiveBg] : (effectiveBgStops
|
|
85
|
+
const bgs = effectiveBg ? [effectiveBg] : (effectiveBgStops?.length ? effectiveBgStops : null);
|
|
86
86
|
if (bgs) {
|
|
87
87
|
// Gray on colored background — flag if every stop is chromatic
|
|
88
88
|
const textLum = relativeLuminance(textColor);
|
|
@@ -263,7 +263,6 @@ function isAccentColor(cssColor) {
|
|
|
263
263
|
if (!cssColor) return false;
|
|
264
264
|
const s = String(cssColor).trim();
|
|
265
265
|
// rgb / rgba — direct channel-distance check.
|
|
266
|
-
const rgbM = /rgba?\(\s*(\d+)\s*,?\s+|\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/.exec(s.replace(/rgba?\(\s*/, 'rgb(').replace(/,/g, ', '));
|
|
267
266
|
const rgbStrict = /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/.exec(s);
|
|
268
267
|
if (rgbStrict) {
|
|
269
268
|
const r = +rgbStrict[1], g = +rgbStrict[2], b = +rgbStrict[3];
|
|
@@ -308,7 +307,7 @@ function isAccentColor(cssColor) {
|
|
|
308
307
|
// uppercase eyebrow OR the modern accent-colored bold eyebrow.
|
|
309
308
|
function checkHeroEyebrow(opts) {
|
|
310
309
|
const {
|
|
311
|
-
headingTag, headingText,
|
|
310
|
+
headingTag, headingText,
|
|
312
311
|
siblingTag, siblingText, siblingTextTransform,
|
|
313
312
|
siblingFontSize, siblingLetterSpacing,
|
|
314
313
|
siblingFontWeight, siblingColor,
|
|
@@ -779,7 +778,7 @@ function parseRadiusToPx(value, widthPx) {
|
|
|
779
778
|
return num;
|
|
780
779
|
}
|
|
781
780
|
|
|
782
|
-
function resolveBorderRadiusPx(
|
|
781
|
+
function resolveBorderRadiusPx(_el, style, widthPx, _win) {
|
|
783
782
|
const fromComputed = parseRadiusToPx(style.borderRadius, widthPx);
|
|
784
783
|
if (fromComputed !== null) return fromComputed;
|
|
785
784
|
return 0;
|
|
@@ -822,7 +821,7 @@ function checkElementColorsDOM(el) {
|
|
|
822
821
|
effectiveBg,
|
|
823
822
|
effectiveBgStops: effectiveBg ? null : resolveGradientStops(el),
|
|
824
823
|
fontSize: parseFloat(style.fontSize) || 16,
|
|
825
|
-
fontWeight: parseInt(style.fontWeight) || 400,
|
|
824
|
+
fontWeight: parseInt(style.fontWeight, 10) || 400,
|
|
826
825
|
hasDirectText,
|
|
827
826
|
isEmojiOnly: isEmojiOnlyText(directText),
|
|
828
827
|
bgClip: style.webkitBackgroundClip || style.backgroundClip || '',
|
|
@@ -930,7 +929,7 @@ function buildCustomPropMap(document) {
|
|
|
930
929
|
if (!style) continue;
|
|
931
930
|
for (let i = 0; i < style.length; i++) {
|
|
932
931
|
const prop = style[i];
|
|
933
|
-
if (!prop
|
|
932
|
+
if (!prop?.startsWith('--')) continue;
|
|
934
933
|
const val = style.getPropertyValue(prop).trim();
|
|
935
934
|
if (val) map.set(prop, val);
|
|
936
935
|
}
|
|
@@ -972,7 +971,7 @@ function oklchToRgb(L, C, H) {
|
|
|
972
971
|
const bLin = -0.0041960863 * lc - 0.7034186147 * mc + 1.7076147010 * sc;
|
|
973
972
|
const enc = (x) => {
|
|
974
973
|
const c = Math.max(0, Math.min(1, x));
|
|
975
|
-
return c <= 0.0031308 ? 12.92 * c : 1.055 *
|
|
974
|
+
return c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
|
|
976
975
|
};
|
|
977
976
|
return {
|
|
978
977
|
r: Math.round(enc(rLin) * 255),
|
|
@@ -1274,7 +1273,7 @@ function resolveFontSizePx(el, win) {
|
|
|
1274
1273
|
const v = chain[i];
|
|
1275
1274
|
if (!v || v === 'inherit') continue;
|
|
1276
1275
|
const num = parseFloat(v);
|
|
1277
|
-
if (isNaN(num)) continue;
|
|
1276
|
+
if (Number.isNaN(num)) continue;
|
|
1278
1277
|
if (v.endsWith('px')) px = num;
|
|
1279
1278
|
else if (v.endsWith('rem')) px = num * 16;
|
|
1280
1279
|
else if (v.endsWith('em')) px = num * px;
|
|
@@ -1289,7 +1288,7 @@ function resolveFontSizePx(el, win) {
|
|
|
1289
1288
|
function resolveLengthPx(value, fontSizePx) {
|
|
1290
1289
|
if (!value || value === 'normal' || value === 'auto' || value === 'inherit') return null;
|
|
1291
1290
|
const num = parseFloat(value);
|
|
1292
|
-
if (isNaN(num)) return null;
|
|
1291
|
+
if (Number.isNaN(num)) return null;
|
|
1293
1292
|
if (value.endsWith('px')) return num;
|
|
1294
1293
|
if (value.endsWith('rem')) return num * 16;
|
|
1295
1294
|
if (value.endsWith('em')) return num * fontSizePx;
|
|
@@ -1405,7 +1404,7 @@ function checkQuality(opts) {
|
|
|
1405
1404
|
// font-size — bigger text demands proportionally more padding.
|
|
1406
1405
|
// vertical: max(4px, fontSize × 0.3)
|
|
1407
1406
|
// horizontal: max(8px, fontSize × 0.5)
|
|
1408
|
-
const isInlineCode = tag === 'code' && !
|
|
1407
|
+
const isInlineCode = tag === 'code' && !el.closest?.('pre');
|
|
1409
1408
|
if (!isInlineCode && rect && hasDirectText && textLen > 20 && rect.width > 100 && rect.height > 30) {
|
|
1410
1409
|
const borders = {
|
|
1411
1410
|
top: parseFloat(style.borderTopWidth) || 0,
|
|
@@ -1521,7 +1520,7 @@ function checkQuality(opts) {
|
|
|
1521
1520
|
const CHILD_INSULATE_THRESHOLD = 4;
|
|
1522
1521
|
const childrenInsulate = { top: false, right: false, bottom: false, left: false };
|
|
1523
1522
|
for (const child of el.children) {
|
|
1524
|
-
|
|
1523
|
+
const childStyle = getComputedStyleFor(win, child);
|
|
1525
1524
|
if (!childStyle) continue;
|
|
1526
1525
|
const childPad = {
|
|
1527
1526
|
top: resolveLengthPx(childStyle.paddingTop, fontSize) ?? 0,
|
|
@@ -1653,7 +1652,7 @@ function checkQuality(opts) {
|
|
|
1653
1652
|
// Only flag actual body content, not UI labels (buttons, tabs, badges, captions, footer text, etc.)
|
|
1654
1653
|
if (hasDirectText && textLen > 20 && fontSize < 12) {
|
|
1655
1654
|
const skipTags = ['sub', 'sup', 'code', 'kbd', 'samp', 'var', 'caption', 'figcaption'];
|
|
1656
|
-
const inUIContext = el.closest
|
|
1655
|
+
const inUIContext = el.closest?.('button, a, label, summary, pre, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"], nav, footer, [aria-hidden="true"], [class*="badge" i], [class*="caption" i], [class*="chip" i], [class*="code" i], [class*="console" i], [class*="diff" i], [class*="label" i], [class*="meta" i], [class*="mock" i], [class*="pill" i], [class*="preview" i], [class*="tag" i], [class*="terminal" i], [class*="writes" i]');
|
|
1657
1656
|
const isUppercase = style.textTransform === 'uppercase';
|
|
1658
1657
|
if (!skipTags.includes(tag) && !inUIContext && !isUppercase) {
|
|
1659
1658
|
findings.push({ id: 'tiny-text', snippet: `${fontSize}px body text` });
|
|
@@ -1718,7 +1717,7 @@ function checkPageQualityFromDoc(doc) {
|
|
|
1718
1717
|
let prevLevel = 0;
|
|
1719
1718
|
let prevText = '';
|
|
1720
1719
|
for (const h of headings) {
|
|
1721
|
-
const level = parseInt(h.tagName[1]);
|
|
1720
|
+
const level = parseInt(h.tagName[1], 10);
|
|
1722
1721
|
const text = (h.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 60);
|
|
1723
1722
|
if (prevLevel > 0 && level > prevLevel + 1) {
|
|
1724
1723
|
findings.push({
|
|
@@ -1766,7 +1765,7 @@ function checkElementBorders(tag, style, overrides, resolvedRadius) {
|
|
|
1766
1765
|
if (widths[s] === 0 && overrides && overrides[s]) {
|
|
1767
1766
|
widths[s] = overrides[s].width;
|
|
1768
1767
|
colors[s] = overrides[s].color;
|
|
1769
|
-
} else if (colors[s]
|
|
1768
|
+
} else if (colors[s]?.startsWith('var(') && overrides?.[s]) {
|
|
1770
1769
|
// Longhand case: jsdom kept the width but left the color as the
|
|
1771
1770
|
// literal `var(...)` string. Substitute the resolved color.
|
|
1772
1771
|
colors[s] = overrides[s].color;
|
|
@@ -1827,7 +1826,7 @@ function checkElementColors(el, style, tag, window, customPropMap, hasAnchorInhe
|
|
|
1827
1826
|
effectiveBg,
|
|
1828
1827
|
effectiveBgStops: effectiveBg ? null : resolveGradientStops(el, window),
|
|
1829
1828
|
fontSize: parseFloat(style.fontSize) || 16,
|
|
1830
|
-
fontWeight: parseInt(style.fontWeight) || 400,
|
|
1829
|
+
fontWeight: parseInt(style.fontWeight, 10) || 400,
|
|
1831
1830
|
hasDirectText,
|
|
1832
1831
|
isEmojiOnly: isEmojiOnlyText(directText),
|
|
1833
1832
|
bgClip: style.webkitBackgroundClip || style.backgroundClip || '',
|
|
@@ -1954,7 +1953,7 @@ function checkTypography() {
|
|
|
1954
1953
|
let totalTextElements = 0;
|
|
1955
1954
|
for (const el of document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, td, th, dd, blockquote, figcaption, a, button, label, span')) {
|
|
1956
1955
|
// Skip monodesign's own elements
|
|
1957
|
-
if (el.closest
|
|
1956
|
+
if (el.closest?.('.monodesign-overlay, .monodesign-label, .monodesign-banner, .monodesign-tooltip')) continue;
|
|
1958
1957
|
// Only count elements that actually have visible direct text
|
|
1959
1958
|
const hasText = [...el.childNodes].some(n => n.nodeType === 3 && n.textContent.trim().length > 0);
|
|
1960
1959
|
if (!hasText) continue;
|
|
@@ -1995,7 +1994,7 @@ function checkTypography() {
|
|
|
1995
1994
|
const sorted = [...sizes].sort((a, b) => a - b);
|
|
1996
1995
|
const ratio = sorted[sorted.length - 1] / sorted[0];
|
|
1997
1996
|
if (ratio < 2.0) {
|
|
1998
|
-
findings.push({ type: 'flat-type-hierarchy', detail: `Sizes: ${sorted.map(s => s
|
|
1997
|
+
findings.push({ type: 'flat-type-hierarchy', detail: `Sizes: ${sorted.map(s => `${s}px`).join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
|
|
1999
1998
|
}
|
|
2000
1999
|
}
|
|
2001
2000
|
|
|
@@ -2114,7 +2113,7 @@ function checkPageTypography(doc, win) {
|
|
|
2114
2113
|
const sorted = [...sizes].sort((a, b) => a - b);
|
|
2115
2114
|
const ratio = sorted[sorted.length - 1] / sorted[0];
|
|
2116
2115
|
if (ratio < 2.0) {
|
|
2117
|
-
findings.push({ id: 'flat-type-hierarchy', snippet: `Sizes: ${sorted.map(s => s
|
|
2116
|
+
findings.push({ id: 'flat-type-hierarchy', snippet: `Sizes: ${sorted.map(s => `${s}px`).join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
|
|
2118
2117
|
}
|
|
2119
2118
|
}
|
|
2120
2119
|
|
|
@@ -2246,7 +2245,7 @@ function checkCreamPalette(doc, win) {
|
|
|
2246
2245
|
// 2. Tailwind class fallback — for the static path, where utility classes
|
|
2247
2246
|
// never resolve to computed CSS.
|
|
2248
2247
|
for (const el of [body, html]) {
|
|
2249
|
-
const tok = creamFromClassList(el
|
|
2248
|
+
const tok = creamFromClassList(el?.getAttribute ? el.getAttribute('class') : '');
|
|
2250
2249
|
if (tok) {
|
|
2251
2250
|
findings.push({ id: 'cream-palette', snippet: `cream/beige page background (Tailwind ${tok})` });
|
|
2252
2251
|
break;
|
|
@@ -2282,7 +2281,7 @@ function checkOversizedH1({ tag, fontSize, headingText, rect = null, viewportWid
|
|
|
2282
2281
|
return [];
|
|
2283
2282
|
}
|
|
2284
2283
|
|
|
2285
|
-
function checkElementOversizedH1(el,
|
|
2284
|
+
function checkElementOversizedH1(el, _style, tag, window) {
|
|
2286
2285
|
if (tag !== 'h1') return [];
|
|
2287
2286
|
const fontSize = resolveFontSizePx(el, window);
|
|
2288
2287
|
const headingText = (el.textContent || '').trim().replace(/\s+/g, ' ');
|
|
@@ -2366,7 +2365,7 @@ function borderColorsFromStyle(style) {
|
|
|
2366
2365
|
];
|
|
2367
2366
|
}
|
|
2368
2367
|
|
|
2369
|
-
function checkElementGptBorderShadow(
|
|
2368
|
+
function checkElementGptBorderShadow(_el, style) {
|
|
2370
2369
|
return checkGptThinBorderWideShadow({ borderWidths: borderWidthsFromStyle(style), borderColors: borderColorsFromStyle(style), boxShadow: style.boxShadow || '' });
|
|
2371
2370
|
}
|
|
2372
2371
|
|
|
@@ -2516,7 +2515,7 @@ function checkClippedOverflow(el, style, getStyle) {
|
|
|
2516
2515
|
return [];
|
|
2517
2516
|
}
|
|
2518
2517
|
|
|
2519
|
-
function checkElementClippedOverflow(el, style,
|
|
2518
|
+
function checkElementClippedOverflow(el, style, _tag, window) {
|
|
2520
2519
|
return checkClippedOverflow(el, style, (n) => window.getComputedStyle(n));
|
|
2521
2520
|
}
|
|
2522
2521
|
|
|
@@ -2862,7 +2861,7 @@ function checkElementImageDimensions(el, style) {
|
|
|
2862
2861
|
const parts = [el.getAttribute?.('style') || el.attribs?.style || ''];
|
|
2863
2862
|
const ar = style && (style.aspectRatio || style['aspect-ratio']);
|
|
2864
2863
|
if (ar && ar !== 'auto') parts.push(`aspect-ratio:${ar}`);
|
|
2865
|
-
const h = style
|
|
2864
|
+
const h = style?.height;
|
|
2866
2865
|
if (h && h !== 'auto' && h !== '') parts.push(`height:${h}`);
|
|
2867
2866
|
return checkImageDimensions({ hasWidthAttr, hasHeightAttr, style: parts.join(';') });
|
|
2868
2867
|
}
|
|
@@ -2922,7 +2921,7 @@ function checkElementSmallTouchTarget(el, style, tag) {
|
|
|
2922
2921
|
// (button-like). jsdom returns '' for the default inline display, so key
|
|
2923
2922
|
// off "not blockish" rather than "== inline".
|
|
2924
2923
|
const isBlockish = /^(block|flex|grid|inline-block|inline-flex|table)/.test(style.display || '');
|
|
2925
|
-
const inProse = !!
|
|
2924
|
+
const inProse = !!el.closest?.(PROSE_ANCESTOR_SELECTOR);
|
|
2926
2925
|
const isInlineLinkInProse = tag === 'a' && !isBlockish && inProse;
|
|
2927
2926
|
return checkSmallTouchTarget({ tag, role, href, inputType, widthPx, heightPx, isInlineLinkInProse });
|
|
2928
2927
|
}
|
|
@@ -2936,7 +2935,7 @@ function checkElementSmallTouchTargetDOM(el) {
|
|
|
2936
2935
|
const inputType = el.getAttribute('type') || '';
|
|
2937
2936
|
const style = getComputedStyle(el);
|
|
2938
2937
|
const isBlockish = /^(block|flex|grid|inline-block|inline-flex|table)/.test(style.display || '');
|
|
2939
|
-
const inProse = !!
|
|
2938
|
+
const inProse = !!el.closest?.(PROSE_ANCESTOR_SELECTOR);
|
|
2940
2939
|
const isInlineLinkInProse = tag === 'a' && !isBlockish && inProse;
|
|
2941
2940
|
return checkSmallTouchTarget({ tag, role, href, inputType, widthPx: rect.width, heightPx: rect.height, isInlineLinkInProse });
|
|
2942
2941
|
}
|
|
@@ -72,7 +72,7 @@ function contrastRatio(c1, c2) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function parseGradientColors(bgImage) {
|
|
75
|
-
if (!bgImage
|
|
75
|
+
if (!bgImage?.includes('gradient')) return [];
|
|
76
76
|
const colors = [];
|
|
77
77
|
for (const m of bgImage.matchAll(/rgba?\([^)]+\)/g)) {
|
|
78
78
|
const c = parseRgb(m[0]);
|
|
@@ -109,7 +109,7 @@ function getHue(c) {
|
|
|
109
109
|
|
|
110
110
|
function colorToHex(c) {
|
|
111
111
|
if (!c) return '?';
|
|
112
|
-
return
|
|
112
|
+
return `#${[c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('')}`;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
export {
|
|
@@ -53,7 +53,7 @@ function isBrandFontOnOwnDomain(font) {
|
|
|
53
53
|
const allowed = BRAND_FONT_DOMAINS[font];
|
|
54
54
|
if (!allowed) return false;
|
|
55
55
|
const host = location.hostname.toLowerCase();
|
|
56
|
-
return allowed.some(suffix => host === suffix || host.endsWith(
|
|
56
|
+
return allowed.some(suffix => host === suffix || host.endsWith(`.${suffix}`));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
const GENERIC_FONTS = new Set([
|
|
@@ -117,10 +117,10 @@ function setMatches(set, rule) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
function isInlineIgnored(finding, directives) {
|
|
120
|
-
const rule = normalizeRule(finding
|
|
120
|
+
const rule = normalizeRule(finding?.antipattern);
|
|
121
121
|
if (!rule) return false;
|
|
122
122
|
if (setMatches(directives.file, rule)) return true;
|
|
123
|
-
const line = Number(finding
|
|
123
|
+
const line = Number(finding?.line) || 0;
|
|
124
124
|
if (line > 0) {
|
|
125
125
|
if (setMatches(directives.line.get(line), rule)) return true;
|
|
126
126
|
if (setMatches(directives.nextLine.get(line), rule)) return true;
|
|
@@ -36,11 +36,11 @@ function safeReadJson(filePath) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function hookSection(raw) {
|
|
39
|
-
return raw
|
|
39
|
+
return raw?.hook && typeof raw.hook === 'object' && !Array.isArray(raw.hook) ? raw.hook : null;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function detectorSection(raw) {
|
|
43
|
-
return raw
|
|
43
|
+
return raw?.detector && typeof raw.detector === 'object' && !Array.isArray(raw.detector) ? raw.detector : null;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const DETECTOR_CONFIG_KEYS = new Set(['ignoreRules', 'ignoreFiles', 'ignoreValues', 'designSystem']);
|
|
@@ -74,7 +74,7 @@ function applyDetectionConfigSource(config, raw) {
|
|
|
74
74
|
if (raw.designSystem && typeof raw.designSystem === 'object' && !Array.isArray(raw.designSystem)) {
|
|
75
75
|
config.designSystem = {
|
|
76
76
|
...config.designSystem,
|
|
77
|
-
enabled: raw.designSystem.enabled
|
|
77
|
+
enabled: raw.designSystem.enabled !== false,
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
if (Array.isArray(raw.ignoreRules)) {
|
|
@@ -153,7 +153,7 @@ function normalizeDetectionConfigForWrite(config) {
|
|
|
153
153
|
out.ignoreValues = normalizeIgnoreValueEntries(config?.ignoreValues || []);
|
|
154
154
|
if (config?.designSystem && typeof config.designSystem === 'object' && !Array.isArray(config.designSystem)) {
|
|
155
155
|
out.designSystem = {
|
|
156
|
-
enabled: config.designSystem.enabled
|
|
156
|
+
enabled: config.designSystem.enabled !== false,
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
159
|
return out;
|
|
@@ -242,7 +242,7 @@ function splitColorArgs(body) {
|
|
|
242
242
|
if (text.includes(',')) {
|
|
243
243
|
const parts = text.split(',').map((part) => part.trim()).filter(Boolean);
|
|
244
244
|
const last = parts[parts.length - 1];
|
|
245
|
-
if (last
|
|
245
|
+
if (last?.includes('/')) {
|
|
246
246
|
const split = last.split('/').map((part) => part.trim()).filter(Boolean);
|
|
247
247
|
return [...parts.slice(0, -1), ...split];
|
|
248
248
|
}
|
package/package.json
CHANGED
|
@@ -205,7 +205,7 @@ function resolveLocalContextDir(root) {
|
|
|
205
205
|
|
|
206
206
|
function resolveEnvContextDir(cwd) {
|
|
207
207
|
const envDir = process.env.MONODESIGN_CONTEXT_DIR;
|
|
208
|
-
if (!envDir
|
|
208
|
+
if (!envDir?.trim()) return null;
|
|
209
209
|
const trimmed = envDir.trim();
|
|
210
210
|
return path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);
|
|
211
211
|
}
|
|
@@ -930,7 +930,7 @@ async function cli() {
|
|
|
930
930
|
const targetExists = targetProvided ? pathExistsForTarget(process.cwd(), cliOptions.targetPath) : null;
|
|
931
931
|
const selection = resolveTargetSelection(process.cwd(), cliOptions);
|
|
932
932
|
if (selection) {
|
|
933
|
-
process.stdout.write(buildTargetSelectionDirective(selection)
|
|
933
|
+
process.stdout.write(`${buildTargetSelectionDirective(selection)}\n`);
|
|
934
934
|
process.exit(0);
|
|
935
935
|
}
|
|
936
936
|
const ctx = loadContext(process.cwd(), cliOptions);
|
|
@@ -952,7 +952,7 @@ async function cli() {
|
|
|
952
952
|
parts.push(buildMissingTargetDirective());
|
|
953
953
|
}
|
|
954
954
|
if (updateDirective) parts.push(updateDirective);
|
|
955
|
-
process.stdout.write(parts.join('\n\n---\n\n')
|
|
955
|
+
process.stdout.write(`${parts.join('\n\n---\n\n')}\n`);
|
|
956
956
|
process.exit(0);
|
|
957
957
|
}
|
|
958
958
|
const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`];
|
|
@@ -989,7 +989,7 @@ async function cli() {
|
|
|
989
989
|
}
|
|
990
990
|
}
|
|
991
991
|
if (updateDirective) parts.push(updateDirective);
|
|
992
|
-
process.stdout.write(parts.join('\n\n---\n\n')
|
|
992
|
+
process.stdout.write(`${parts.join('\n\n---\n\n')}\n`);
|
|
993
993
|
}
|
|
994
994
|
|
|
995
995
|
function parseCliOptions(args) {
|
|
@@ -381,7 +381,7 @@ function main(argv) {
|
|
|
381
381
|
}
|
|
382
382
|
case 'trend': {
|
|
383
383
|
const rows = readTrend(args[0], { limit: args[1] ? Number(args[1]) : 5 });
|
|
384
|
-
process.stdout.write(JSON.stringify(rows, null, 2)
|
|
384
|
+
process.stdout.write(`${JSON.stringify(rows, null, 2)}\n`);
|
|
385
385
|
return;
|
|
386
386
|
}
|
|
387
387
|
case 'recall': {
|
|
@@ -71,11 +71,6 @@ const INLINE_HEADER_SIGNALS = [
|
|
|
71
71
|
/\bscript-src\b/,
|
|
72
72
|
/\bconnect-src\b/,
|
|
73
73
|
];
|
|
74
|
-
const NUXT_ROUTE_RULES_SIGNALS = [
|
|
75
|
-
/\brouteRules\b/,
|
|
76
|
-
/Content-Security-Policy/i,
|
|
77
|
-
/\bscript-src\b/,
|
|
78
|
-
];
|
|
79
74
|
|
|
80
75
|
const MIDDLEWARE_HINT = /headers\.set\(\s*["']Content-Security-Policy["']/i;
|
|
81
76
|
const META_TAG_HINT = /http-equiv\s*=\s*["']Content-Security-Policy["']/i;
|
|
@@ -91,7 +86,7 @@ export function detectCsp(cwd = process.cwd()) {
|
|
|
91
86
|
const ext = path.extname(absPath);
|
|
92
87
|
const base = path.basename(absPath).toLowerCase();
|
|
93
88
|
const isConfig = (name) =>
|
|
94
|
-
new RegExp(
|
|
89
|
+
new RegExp(`(^|/)${name}\\.config\\.`).test(relPath);
|
|
95
90
|
|
|
96
91
|
// === append-arrays candidates ===
|
|
97
92
|
|
|
@@ -184,7 +184,7 @@ function writeHookConfig(cwd, hookConfig, opts = {}) {
|
|
|
184
184
|
// (consent, quiet, auditLog) survive an Monodesign hooks edit.
|
|
185
185
|
const next = { ...existing, hook: { ...existingHook, ...hookConfig } };
|
|
186
186
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
187
|
-
fs.writeFileSync(filePath, JSON.stringify(next, null, 2)
|
|
187
|
+
fs.writeFileSync(filePath, `${JSON.stringify(next, null, 2)}\n`);
|
|
188
188
|
return filePath;
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -202,14 +202,14 @@ function writeDetectorConfig(cwd, detectorConfig, opts = {}) {
|
|
|
202
202
|
if (Object.keys(nextHook).length > 0) next.hook = nextHook;
|
|
203
203
|
else delete next.hook;
|
|
204
204
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
205
|
-
fs.writeFileSync(filePath, JSON.stringify(next, null, 2)
|
|
205
|
+
fs.writeFileSync(filePath, `${JSON.stringify(next, null, 2)}\n`);
|
|
206
206
|
return filePath;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
function mergeHookConfig(existing) {
|
|
210
210
|
const base = existing && typeof existing === 'object' ? existing : {};
|
|
211
211
|
return {
|
|
212
|
-
enabled: base.enabled
|
|
212
|
+
enabled: base.enabled !== false,
|
|
213
213
|
limits: {
|
|
214
214
|
maxFindings: Number.isFinite(base?.limits?.maxFindings) ? base.limits.maxFindings : DEFAULT_CONFIG.limits.maxFindings,
|
|
215
215
|
maxChars: Number.isFinite(base?.limits?.maxChars) ? base.limits.maxChars : DEFAULT_CONFIG.limits.maxChars,
|
|
@@ -234,7 +234,7 @@ function mergeDetectorConfig(existing, seed = null) {
|
|
|
234
234
|
if (base.designSystem && typeof base.designSystem === 'object' && !Array.isArray(base.designSystem)) {
|
|
235
235
|
out.designSystem = {
|
|
236
236
|
...(out.designSystem || {}),
|
|
237
|
-
enabled: base.designSystem.enabled
|
|
237
|
+
enabled: base.designSystem.enabled !== false,
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
if (Array.isArray(base.ignoreRules)) {
|
|
@@ -644,7 +644,7 @@ function reset(cwd) {
|
|
|
644
644
|
if (Object.keys(rest).length === 0) {
|
|
645
645
|
fs.unlinkSync(filePath);
|
|
646
646
|
} else {
|
|
647
|
-
fs.writeFileSync(filePath, JSON.stringify(rest, null, 2)
|
|
647
|
+
fs.writeFileSync(filePath, `${JSON.stringify(rest, null, 2)}\n`);
|
|
648
648
|
}
|
|
649
649
|
removed.push(path.relative(cwd, filePath).split(path.sep).join('/') || filePath);
|
|
650
650
|
} catch { /* ignore */ }
|
|
@@ -684,7 +684,7 @@ function main() {
|
|
|
684
684
|
case 'ignore-value': out = addIgnoreValue(cwd, rest); break;
|
|
685
685
|
case 'reset': out = reset(cwd); break;
|
|
686
686
|
}
|
|
687
|
-
process.stdout.write(out
|
|
687
|
+
process.stdout.write(`${out}\n`);
|
|
688
688
|
} catch (err) {
|
|
689
689
|
process.stderr.write(`Error: ${err.message || err}\n`);
|
|
690
690
|
process.exit(1);
|
|
@@ -17,11 +17,11 @@ import path from 'node:path';
|
|
|
17
17
|
import {
|
|
18
18
|
ALLOWED_EXTS,
|
|
19
19
|
EDIT_COUNT_THRESHOLD,
|
|
20
|
-
GENERATED_PATH,
|
|
21
20
|
SENSITIVE_PATH,
|
|
22
21
|
appendDesignSystemNote,
|
|
23
22
|
designSystemOptions,
|
|
24
23
|
filterFindings,
|
|
24
|
+
isGeneratedPath,
|
|
25
25
|
isNativePlatform,
|
|
26
26
|
loadDetector,
|
|
27
27
|
matchConfiguredExtension,
|
|
@@ -162,7 +162,7 @@ function replaceOnce(original, oldString, newString) {
|
|
|
162
162
|
|
|
163
163
|
function readExistingProjectFile(filePath, cwd) {
|
|
164
164
|
if (!isInsideProject(filePath, cwd)) return null;
|
|
165
|
-
if (SENSITIVE_PATH.test(filePath) ||
|
|
165
|
+
if (SENSITIVE_PATH.test(filePath) || isGeneratedPath(filePath, cwd)) return null;
|
|
166
166
|
try {
|
|
167
167
|
const stat = fs.statSync(filePath);
|
|
168
168
|
if (!stat.isFile() || stat.size > 1024 * 1024) return null;
|
|
@@ -233,7 +233,7 @@ function shellCopiedFileContent(command, cwd) {
|
|
|
233
233
|
if (!source) return '';
|
|
234
234
|
const sourcePath = path.isAbsolute(source) ? source : path.resolve(cwd, source);
|
|
235
235
|
if (!isInsideProject(sourcePath, cwd)) return '';
|
|
236
|
-
if (SENSITIVE_PATH.test(sourcePath) ||
|
|
236
|
+
if (SENSITIVE_PATH.test(sourcePath) || isGeneratedPath(sourcePath, cwd)) return '';
|
|
237
237
|
try {
|
|
238
238
|
const stat = fs.statSync(sourcePath);
|
|
239
239
|
if (!stat.isFile() || stat.size > 1024 * 1024) return '';
|
|
@@ -416,7 +416,7 @@ async function main() {
|
|
|
416
416
|
if (!filePath) return allow({ ...audit, skipped: 'no-file-path', durationMs: Date.now() - started });
|
|
417
417
|
if (!isInsideProject(filePath, cwd)) return allow({ ...audit, skipped: 'outside-project', durationMs: Date.now() - started });
|
|
418
418
|
if (SENSITIVE_PATH.test(filePath)) return allow({ ...audit, skipped: 'sensitive', durationMs: Date.now() - started });
|
|
419
|
-
if (
|
|
419
|
+
if (isGeneratedPath(filePath, cwd)) return allow({ ...audit, skipped: 'generated', durationMs: Date.now() - started });
|
|
420
420
|
|
|
421
421
|
// Config is read before the extension gate so `detector.extensions` entries
|
|
422
422
|
// (e.g. `.blade.php` template files, issue #316) can widen it.
|