@getcoherent/cli 0.5.8 → 0.5.10
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/LICENSE +21 -0
- package/dist/index.js +154 -1
- package/package.json +10 -10
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sergei Kovtun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
CHANGED
|
@@ -3613,6 +3613,16 @@ ANTI-PATTERNS (NEVER DO):
|
|
|
3613
3613
|
- Mixing link styles on the same page \u2192 pick ONE style
|
|
3614
3614
|
- Interactive elements without hover/focus states
|
|
3615
3615
|
|
|
3616
|
+
COMPONENT VARIANT RULES (CRITICAL):
|
|
3617
|
+
- NEVER use <Button> with custom bg-*/text-* classes for navigation or tabs without variant="ghost".
|
|
3618
|
+
The default Button variant sets bg-primary, so custom text-muted-foreground or bg-accent classes will conflict.
|
|
3619
|
+
BAD: <Button className="text-muted-foreground hover:bg-accent">Tab</Button>
|
|
3620
|
+
GOOD: <Button variant="ghost" className="text-muted-foreground">Tab</Button>
|
|
3621
|
+
BEST: Use shadcn <Tabs> / <TabsList> / <TabsTrigger> for tab-style navigation.
|
|
3622
|
+
- For sidebar navigation buttons, ALWAYS use variant="ghost" with active-state classes:
|
|
3623
|
+
<Button variant="ghost" className={cn("w-full justify-start", isActive && "bg-accent font-medium")}>
|
|
3624
|
+
- For filter toggle buttons, use variant={isActive ? 'default' : 'outline'} \u2014 NOT className toggling.
|
|
3625
|
+
|
|
3616
3626
|
CONTENT (zero placeholders):
|
|
3617
3627
|
- NEVER: "Lorem ipsum", "Card content", "Description here"
|
|
3618
3628
|
- ALWAYS: Real, contextual content. Realistic metric names, values, dates.
|
|
@@ -5060,6 +5070,121 @@ function fixGlobalsCss(projectRoot, config2) {
|
|
|
5060
5070
|
writeFileSync7(layoutPath, layoutContent, "utf-8");
|
|
5061
5071
|
}
|
|
5062
5072
|
|
|
5073
|
+
// src/utils/component-rules.ts
|
|
5074
|
+
function extractJsxElementProps(code, openTagStart) {
|
|
5075
|
+
let i = openTagStart;
|
|
5076
|
+
if (code[i] !== "<") return null;
|
|
5077
|
+
i++;
|
|
5078
|
+
let braceDepth = 0;
|
|
5079
|
+
let inSingleQuote = false;
|
|
5080
|
+
let inDoubleQuote = false;
|
|
5081
|
+
let inTemplateLiteral = false;
|
|
5082
|
+
let escaped = false;
|
|
5083
|
+
while (i < code.length) {
|
|
5084
|
+
const ch = code[i];
|
|
5085
|
+
if (escaped) {
|
|
5086
|
+
escaped = false;
|
|
5087
|
+
i++;
|
|
5088
|
+
continue;
|
|
5089
|
+
}
|
|
5090
|
+
if (ch === "\\" && (inSingleQuote || inDoubleQuote || inTemplateLiteral)) {
|
|
5091
|
+
escaped = true;
|
|
5092
|
+
i++;
|
|
5093
|
+
continue;
|
|
5094
|
+
}
|
|
5095
|
+
if (!inDoubleQuote && !inTemplateLiteral && ch === "'" && braceDepth > 0) {
|
|
5096
|
+
inSingleQuote = !inSingleQuote;
|
|
5097
|
+
} else if (!inSingleQuote && !inTemplateLiteral && ch === '"') {
|
|
5098
|
+
inDoubleQuote = !inDoubleQuote;
|
|
5099
|
+
} else if (!inSingleQuote && !inDoubleQuote && ch === "`") {
|
|
5100
|
+
inTemplateLiteral = !inTemplateLiteral;
|
|
5101
|
+
}
|
|
5102
|
+
if (!inSingleQuote && !inDoubleQuote && !inTemplateLiteral) {
|
|
5103
|
+
if (ch === "{") braceDepth++;
|
|
5104
|
+
else if (ch === "}") braceDepth--;
|
|
5105
|
+
else if (ch === ">" && braceDepth === 0) {
|
|
5106
|
+
return code.slice(openTagStart, i + 1);
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
i++;
|
|
5110
|
+
}
|
|
5111
|
+
return null;
|
|
5112
|
+
}
|
|
5113
|
+
var NAV_STYLE_SIGNAL = /text-muted-foreground/;
|
|
5114
|
+
var buttonMissingGhostVariant = {
|
|
5115
|
+
id: "button-missing-ghost-variant",
|
|
5116
|
+
component: "Button",
|
|
5117
|
+
detect(code) {
|
|
5118
|
+
const issues = [];
|
|
5119
|
+
const buttonRe = /<Button\s/g;
|
|
5120
|
+
let match;
|
|
5121
|
+
while ((match = buttonRe.exec(code)) !== null) {
|
|
5122
|
+
const props = extractJsxElementProps(code, match.index);
|
|
5123
|
+
if (!props) continue;
|
|
5124
|
+
if (/\bvariant\s*=/.test(props)) continue;
|
|
5125
|
+
if (!NAV_STYLE_SIGNAL.test(props)) continue;
|
|
5126
|
+
const line = code.slice(0, match.index).split("\n").length;
|
|
5127
|
+
issues.push({
|
|
5128
|
+
line,
|
|
5129
|
+
type: "BUTTON_MISSING_VARIANT",
|
|
5130
|
+
message: '<Button> with navigation-style classes (text-muted-foreground) but no variant \u2014 add variant="ghost"',
|
|
5131
|
+
severity: "error"
|
|
5132
|
+
});
|
|
5133
|
+
}
|
|
5134
|
+
return issues;
|
|
5135
|
+
},
|
|
5136
|
+
fix(code) {
|
|
5137
|
+
let result = code;
|
|
5138
|
+
let applied = false;
|
|
5139
|
+
const buttonRe = /<Button\s/g;
|
|
5140
|
+
let match;
|
|
5141
|
+
let offset = 0;
|
|
5142
|
+
while ((match = buttonRe.exec(code)) !== null) {
|
|
5143
|
+
const adjustedIndex = match.index + offset;
|
|
5144
|
+
const props = extractJsxElementProps(result, adjustedIndex);
|
|
5145
|
+
if (!props) continue;
|
|
5146
|
+
if (/\bvariant\s*=/.test(props)) continue;
|
|
5147
|
+
if (!NAV_STYLE_SIGNAL.test(props)) continue;
|
|
5148
|
+
const insertPos = adjustedIndex + "<Button".length;
|
|
5149
|
+
const insertion = "\n" + getIndent(result, adjustedIndex) + ' variant="ghost"';
|
|
5150
|
+
result = result.slice(0, insertPos) + insertion + result.slice(insertPos);
|
|
5151
|
+
offset += insertion.length;
|
|
5152
|
+
applied = true;
|
|
5153
|
+
}
|
|
5154
|
+
return {
|
|
5155
|
+
code: result,
|
|
5156
|
+
applied,
|
|
5157
|
+
description: 'added variant="ghost" to Button with nav-style classes'
|
|
5158
|
+
};
|
|
5159
|
+
}
|
|
5160
|
+
};
|
|
5161
|
+
function getIndent(code, pos) {
|
|
5162
|
+
const lineStart = code.lastIndexOf("\n", pos);
|
|
5163
|
+
const lineContent = code.slice(lineStart + 1, pos);
|
|
5164
|
+
const indentMatch = lineContent.match(/^(\s*)/);
|
|
5165
|
+
return indentMatch ? indentMatch[1] : "";
|
|
5166
|
+
}
|
|
5167
|
+
var rules = [buttonMissingGhostVariant];
|
|
5168
|
+
function detectComponentIssues(code) {
|
|
5169
|
+
const issues = [];
|
|
5170
|
+
for (const rule of rules) {
|
|
5171
|
+
issues.push(...rule.detect(code));
|
|
5172
|
+
}
|
|
5173
|
+
return issues;
|
|
5174
|
+
}
|
|
5175
|
+
function applyComponentRules(code) {
|
|
5176
|
+
const fixes = [];
|
|
5177
|
+
let result = code;
|
|
5178
|
+
for (const rule of rules) {
|
|
5179
|
+
const { code: fixed, applied, description } = rule.fix(result);
|
|
5180
|
+
if (applied) {
|
|
5181
|
+
result = fixed;
|
|
5182
|
+
fixes.push(description);
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
return { code: result, fixes };
|
|
5186
|
+
}
|
|
5187
|
+
|
|
5063
5188
|
// src/utils/quality-validator.ts
|
|
5064
5189
|
var RAW_COLOR_RE = /(?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?(?:bg|text|border|ring|outline|from|to|via)-(gray|blue|red|green|yellow|purple|pink|indigo|orange|slate|zinc|stone|neutral|emerald|teal|cyan|sky|violet|fuchsia|rose|amber|lime)-\d+/g;
|
|
5065
5190
|
var HEX_IN_CLASS_RE = /className="[^"]*#[0-9a-fA-F]{3,8}[^"]*"/g;
|
|
@@ -5482,6 +5607,7 @@ function validatePageQuality(code, validRoutes) {
|
|
|
5482
5607
|
severity: "error"
|
|
5483
5608
|
});
|
|
5484
5609
|
}
|
|
5610
|
+
issues.push(...detectComponentIssues(code));
|
|
5485
5611
|
return issues;
|
|
5486
5612
|
}
|
|
5487
5613
|
function replaceRawColors(classes, colorMap) {
|
|
@@ -5919,6 +6045,11 @@ ${selectImport}`
|
|
|
5919
6045
|
if (fixed !== beforeLinkFix) {
|
|
5920
6046
|
fixes.push("Link>Button \u2192 Button asChild>Link (DOM nesting fix)");
|
|
5921
6047
|
}
|
|
6048
|
+
const { code: fixedByRules, fixes: ruleFixes } = applyComponentRules(fixed);
|
|
6049
|
+
if (ruleFixes.length > 0) {
|
|
6050
|
+
fixed = fixedByRules;
|
|
6051
|
+
fixes.push(...ruleFixes);
|
|
6052
|
+
}
|
|
5922
6053
|
fixed = fixed.replace(/className="([^"]*)"/g, (_match, inner) => {
|
|
5923
6054
|
const cleaned = inner.replace(/\s{2,}/g, " ").trim();
|
|
5924
6055
|
return `className="${cleaned}"`;
|
|
@@ -6944,7 +7075,29 @@ function extractAppNameFromPrompt(prompt) {
|
|
|
6944
7075
|
const m = prompt.match(re);
|
|
6945
7076
|
if (m && m[1] && m[1].length >= 2 && m[1].length <= 30) {
|
|
6946
7077
|
const name = m[1].replace(/[.,;:!?]$/, "");
|
|
6947
|
-
const skip = /* @__PURE__ */ new Set([
|
|
7078
|
+
const skip = /* @__PURE__ */ new Set([
|
|
7079
|
+
"a",
|
|
7080
|
+
"an",
|
|
7081
|
+
"the",
|
|
7082
|
+
"my",
|
|
7083
|
+
"our",
|
|
7084
|
+
"new",
|
|
7085
|
+
"full",
|
|
7086
|
+
"complete",
|
|
7087
|
+
"simple",
|
|
7088
|
+
"modern",
|
|
7089
|
+
"beautiful",
|
|
7090
|
+
"responsive",
|
|
7091
|
+
"fast",
|
|
7092
|
+
"cool",
|
|
7093
|
+
"great",
|
|
7094
|
+
"basic",
|
|
7095
|
+
"quick",
|
|
7096
|
+
"small",
|
|
7097
|
+
"large",
|
|
7098
|
+
"custom",
|
|
7099
|
+
"nice"
|
|
7100
|
+
]);
|
|
6948
7101
|
if (skip.has(name.toLowerCase())) continue;
|
|
6949
7102
|
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
6950
7103
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.5.
|
|
6
|
+
"version": "0.5.10",
|
|
7
7
|
"description": "CLI interface for Coherent Design Method",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.js",
|
|
@@ -33,15 +33,8 @@
|
|
|
33
33
|
],
|
|
34
34
|
"author": "Coherent Design Method",
|
|
35
35
|
"license": "MIT",
|
|
36
|
-
"scripts": {
|
|
37
|
-
"dev": "tsup --watch",
|
|
38
|
-
"build": "tsup",
|
|
39
|
-
"typecheck": "tsc --noEmit",
|
|
40
|
-
"test": "vitest"
|
|
41
|
-
},
|
|
42
36
|
"dependencies": {
|
|
43
37
|
"@anthropic-ai/sdk": "^0.32.0",
|
|
44
|
-
"@getcoherent/core": "workspace:*",
|
|
45
38
|
"chalk": "^5.3.0",
|
|
46
39
|
"chokidar": "^4.0.1",
|
|
47
40
|
"commander": "^11.1.0",
|
|
@@ -49,12 +42,19 @@
|
|
|
49
42
|
"open": "^10.1.0",
|
|
50
43
|
"ora": "^7.0.1",
|
|
51
44
|
"prompts": "^2.4.2",
|
|
52
|
-
"zod": "^3.22.4"
|
|
45
|
+
"zod": "^3.22.4",
|
|
46
|
+
"@getcoherent/core": "0.5.10"
|
|
53
47
|
},
|
|
54
48
|
"devDependencies": {
|
|
55
49
|
"@types/node": "^20.11.0",
|
|
56
50
|
"@types/prompts": "^2.4.9",
|
|
57
51
|
"tsup": "^8.0.1",
|
|
58
52
|
"typescript": "^5.3.3"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "vitest"
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|