popeye-cli 1.4.7 → 1.6.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/CHANGELOG.md +54 -0
- package/README.md +264 -63
- package/dist/adapters/gemini.d.ts +1 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +9 -4
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/grok.d.ts +1 -0
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +9 -4
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +35 -9
- package/dist/adapters/openai.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +54 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts +29 -0
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +132 -7
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts +8 -2
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +37 -316
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/doc-parser.d.ts +64 -0
- package/dist/generators/doc-parser.d.ts.map +1 -0
- package/dist/generators/doc-parser.js +407 -0
- package/dist/generators/doc-parser.js.map +1 -0
- package/dist/generators/frontend-design-analyzer.d.ts +30 -0
- package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
- package/dist/generators/frontend-design-analyzer.js +208 -0
- package/dist/generators/frontend-design-analyzer.js.map +1 -0
- package/dist/generators/shared-packages.d.ts +45 -0
- package/dist/generators/shared-packages.d.ts.map +1 -0
- package/dist/generators/shared-packages.js +456 -0
- package/dist/generators/shared-packages.js.map +1 -0
- package/dist/generators/templates/index.d.ts +8 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +8 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/generators/templates/website-components.d.ts +33 -0
- package/dist/generators/templates/website-components.d.ts.map +1 -0
- package/dist/generators/templates/website-components.js +303 -0
- package/dist/generators/templates/website-components.js.map +1 -0
- package/dist/generators/templates/website-config.d.ts +55 -0
- package/dist/generators/templates/website-config.d.ts.map +1 -0
- package/dist/generators/templates/website-config.js +425 -0
- package/dist/generators/templates/website-config.js.map +1 -0
- package/dist/generators/templates/website-conversion.d.ts +27 -0
- package/dist/generators/templates/website-conversion.d.ts.map +1 -0
- package/dist/generators/templates/website-conversion.js +326 -0
- package/dist/generators/templates/website-conversion.js.map +1 -0
- package/dist/generators/templates/website-landing.d.ts +24 -0
- package/dist/generators/templates/website-landing.d.ts.map +1 -0
- package/dist/generators/templates/website-landing.js +276 -0
- package/dist/generators/templates/website-landing.js.map +1 -0
- package/dist/generators/templates/website-layout.d.ts +42 -0
- package/dist/generators/templates/website-layout.d.ts.map +1 -0
- package/dist/generators/templates/website-layout.js +408 -0
- package/dist/generators/templates/website-layout.js.map +1 -0
- package/dist/generators/templates/website-pricing.d.ts +11 -0
- package/dist/generators/templates/website-pricing.d.ts.map +1 -0
- package/dist/generators/templates/website-pricing.js +313 -0
- package/dist/generators/templates/website-pricing.js.map +1 -0
- package/dist/generators/templates/website-sections.d.ts +102 -0
- package/dist/generators/templates/website-sections.d.ts.map +1 -0
- package/dist/generators/templates/website-sections.js +444 -0
- package/dist/generators/templates/website-sections.js.map +1 -0
- package/dist/generators/templates/website-seo.d.ts +76 -0
- package/dist/generators/templates/website-seo.d.ts.map +1 -0
- package/dist/generators/templates/website-seo.js +326 -0
- package/dist/generators/templates/website-seo.js.map +1 -0
- package/dist/generators/templates/website.d.ts +10 -83
- package/dist/generators/templates/website.d.ts.map +1 -1
- package/dist/generators/templates/website.js +12 -875
- package/dist/generators/templates/website.js.map +1 -1
- package/dist/generators/website-content-scanner.d.ts +37 -0
- package/dist/generators/website-content-scanner.d.ts.map +1 -0
- package/dist/generators/website-content-scanner.js +165 -0
- package/dist/generators/website-content-scanner.js.map +1 -0
- package/dist/generators/website-context.d.ts +119 -0
- package/dist/generators/website-context.d.ts.map +1 -0
- package/dist/generators/website-context.js +350 -0
- package/dist/generators/website-context.js.map +1 -0
- package/dist/generators/website-debug.d.ts +68 -0
- package/dist/generators/website-debug.d.ts.map +1 -0
- package/dist/generators/website-debug.js +93 -0
- package/dist/generators/website-debug.js.map +1 -0
- package/dist/generators/website.d.ts +5 -0
- package/dist/generators/website.d.ts.map +1 -1
- package/dist/generators/website.js +136 -11
- package/dist/generators/website.js.map +1 -1
- package/dist/generators/workspace-root.d.ts +27 -0
- package/dist/generators/workspace-root.d.ts.map +1 -0
- package/dist/generators/workspace-root.js +100 -0
- package/dist/generators/workspace-root.js.map +1 -0
- package/dist/state/index.d.ts +35 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +40 -0
- package/dist/state/index.js.map +1 -1
- package/dist/types/consensus.d.ts +3 -0
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +1 -0
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/website-strategy.d.ts +263 -0
- package/dist/types/website-strategy.d.ts.map +1 -0
- package/dist/types/website-strategy.js +105 -0
- package/dist/types/website-strategy.js.map +1 -0
- package/dist/types/workflow.d.ts +21 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +8 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/handlers.d.ts +15 -0
- package/dist/upgrade/handlers.d.ts.map +1 -1
- package/dist/upgrade/handlers.js +52 -0
- package/dist/upgrade/handlers.js.map +1 -1
- package/dist/workflow/auto-fix-bundler.d.ts +37 -0
- package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
- package/dist/workflow/auto-fix-bundler.js +320 -0
- package/dist/workflow/auto-fix-bundler.js.map +1 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -1
- package/dist/workflow/auto-fix.js +10 -3
- package/dist/workflow/auto-fix.js.map +1 -1
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +2 -0
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +18 -0
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +4 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +37 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/overview.d.ts +89 -0
- package/dist/workflow/overview.d.ts.map +1 -0
- package/dist/workflow/overview.js +358 -0
- package/dist/workflow/overview.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +6 -4
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +148 -6
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/website-strategy.d.ts +79 -0
- package/dist/workflow/website-strategy.d.ts.map +1 -0
- package/dist/workflow/website-strategy.js +310 -0
- package/dist/workflow/website-strategy.js.map +1 -0
- package/dist/workflow/website-updater.d.ts +17 -0
- package/dist/workflow/website-updater.d.ts.map +1 -0
- package/dist/workflow/website-updater.js +116 -0
- package/dist/workflow/website-updater.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +1 -1
- package/dist/workflow/workflow-logger.d.ts.map +1 -1
- package/dist/workflow/workflow-logger.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/gemini.ts +10 -4
- package/src/adapters/grok.ts +10 -4
- package/src/adapters/openai.ts +38 -6
- package/src/cli/commands/create.ts +58 -4
- package/src/cli/interactive.ts +143 -7
- package/src/generators/all.ts +49 -332
- package/src/generators/doc-parser.ts +449 -0
- package/src/generators/frontend-design-analyzer.ts +261 -0
- package/src/generators/shared-packages.ts +500 -0
- package/src/generators/templates/index.ts +8 -0
- package/src/generators/templates/website-components.ts +330 -0
- package/src/generators/templates/website-config.ts +444 -0
- package/src/generators/templates/website-conversion.ts +341 -0
- package/src/generators/templates/website-landing.ts +331 -0
- package/src/generators/templates/website-layout.ts +443 -0
- package/src/generators/templates/website-pricing.ts +330 -0
- package/src/generators/templates/website-sections.ts +541 -0
- package/src/generators/templates/website-seo.ts +370 -0
- package/src/generators/templates/website.ts +38 -905
- package/src/generators/website-content-scanner.ts +208 -0
- package/src/generators/website-context.ts +493 -0
- package/src/generators/website-debug.ts +130 -0
- package/src/generators/website.ts +178 -20
- package/src/generators/workspace-root.ts +113 -0
- package/src/state/index.ts +56 -0
- package/src/types/consensus.ts +3 -0
- package/src/types/website-strategy.ts +243 -0
- package/src/types/workflow.ts +21 -0
- package/src/upgrade/handlers.ts +65 -0
- package/src/workflow/auto-fix-bundler.ts +392 -0
- package/src/workflow/auto-fix.ts +11 -3
- package/src/workflow/consensus.ts +2 -0
- package/src/workflow/execution-mode.ts +21 -0
- package/src/workflow/index.ts +37 -0
- package/src/workflow/overview.ts +475 -0
- package/src/workflow/plan-mode.ts +193 -8
- package/src/workflow/website-strategy.ts +379 -0
- package/src/workflow/website-updater.ts +142 -0
- package/src/workflow/workflow-logger.ts +1 -0
- package/tests/adapters/persona-switching.test.ts +63 -0
- package/tests/cli/project-naming.test.ts +136 -0
- package/tests/generators/doc-parser.test.ts +121 -0
- package/tests/generators/frontend-design-analyzer.test.ts +90 -0
- package/tests/generators/quality-gate.test.ts +183 -0
- package/tests/generators/shared-packages.test.ts +83 -0
- package/tests/generators/website-components.test.ts +159 -0
- package/tests/generators/website-config.test.ts +84 -0
- package/tests/generators/website-content-scanner.test.ts +181 -0
- package/tests/generators/website-context.test.ts +331 -0
- package/tests/generators/website-debug.test.ts +77 -0
- package/tests/generators/website-landing.test.ts +188 -0
- package/tests/generators/website-pricing.test.ts +98 -0
- package/tests/generators/website-sections.test.ts +245 -0
- package/tests/generators/website-seo-quality.test.ts +246 -0
- package/tests/generators/workspace-root.test.ts +105 -0
- package/tests/upgrade/handlers.test.ts +162 -0
- package/tests/workflow/auto-fix-bundler.test.ts +242 -0
- package/tests/workflow/overview.test.ts +392 -0
- package/tests/workflow/plan-mode.test.ts +111 -1
- package/tests/workflow/website-strategy.test.ts +246 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document parsing helpers for extracting structured content from user docs
|
|
3
|
+
* Used by website-context.ts to populate website templates with project-specific content
|
|
4
|
+
*/
|
|
5
|
+
/** Generic AI preamble patterns to skip */
|
|
6
|
+
const GENERIC_PREAMBLES = [
|
|
7
|
+
"here's a comprehensive",
|
|
8
|
+
"here is a comprehensive",
|
|
9
|
+
"here's a detailed",
|
|
10
|
+
"here is a detailed",
|
|
11
|
+
"based on your idea",
|
|
12
|
+
"based on the idea",
|
|
13
|
+
"here's a software",
|
|
14
|
+
"here is a software",
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Strip markdown code fences that wrap entire doc files (```md ... ```)
|
|
18
|
+
*/
|
|
19
|
+
export function stripCodeFences(text) {
|
|
20
|
+
return text.replace(/```(?:md|markdown)?\s*\n/g, '').replace(/```\s*$/gm, '');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Extract the real product name from docs or specification
|
|
24
|
+
*
|
|
25
|
+
* Priority chain (first match wins):
|
|
26
|
+
* 1. Parsed docs: "# ProductName -- tagline" heading (picks shortest)
|
|
27
|
+
* 2. Specification: "# ProductName" heading
|
|
28
|
+
* 3. Specification: "Product:" / "Name:" / "App:" label
|
|
29
|
+
* 4. package.json "name" field from workspace root (passed via specification context)
|
|
30
|
+
* 5. undefined (caller falls back to directory name)
|
|
31
|
+
*/
|
|
32
|
+
export function extractProductName(docs, specification, packageJsonName) {
|
|
33
|
+
// 1. Collect all "# Name -- tagline" headings, pick the shortest name
|
|
34
|
+
// Reason: sub-documents like "Gateco UI Color System" are longer than "Gateco"
|
|
35
|
+
const headingPattern = /^#\s+([A-Z][a-zA-Z0-9]+(?:[ \t]+[A-Z][a-zA-Z0-9]+)*)(?:[ \t]*(?:--|[—–|:])[ \t])/gm;
|
|
36
|
+
const candidates = [];
|
|
37
|
+
let match;
|
|
38
|
+
while ((match = headingPattern.exec(docs)) !== null) {
|
|
39
|
+
candidates.push(match[1].trim());
|
|
40
|
+
}
|
|
41
|
+
if (candidates.length > 0) {
|
|
42
|
+
candidates.sort((a, b) => a.length - b.length);
|
|
43
|
+
return candidates[0];
|
|
44
|
+
}
|
|
45
|
+
// 2. "# ProductName" heading in specification (standalone heading, not sub-doc)
|
|
46
|
+
// Reason: Exclude common section headings like "Overview", "Introduction", "Summary"
|
|
47
|
+
if (specification) {
|
|
48
|
+
const commonHeadings = /^(Overview|Introduction|Summary|Features|Architecture|Requirements|Setup|Installation|Configuration|Specification|Appendix|Conclusion|References)$/i;
|
|
49
|
+
const specHeading = specification.match(/^#\s+([A-Z][a-zA-Z0-9]+(?:\s+[A-Z][a-zA-Z0-9]+)*)\s*$/m);
|
|
50
|
+
if (specHeading && !commonHeadings.test(specHeading[1].trim())) {
|
|
51
|
+
return specHeading[1].trim();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// 3. "Product:" / "Name:" / "App:" label in specification
|
|
55
|
+
if (specification) {
|
|
56
|
+
const nameMatch = specification.match(/\*\*Project\s+Name\*\*:\s*(.+)/i);
|
|
57
|
+
if (nameMatch)
|
|
58
|
+
return nameMatch[1].trim();
|
|
59
|
+
const labelMatch = specification.match(/(?:^|\n)\s*(?:Product|Name|App)\s*:\s*(.+)/i);
|
|
60
|
+
if (labelMatch) {
|
|
61
|
+
const value = labelMatch[1].trim().replace(/\*\*/g, '');
|
|
62
|
+
if (value.length > 0 && value.length < 60)
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// 4. "# ProductName" in spec/product doc sections only
|
|
67
|
+
const sectionHeading = docs.match(/^---\s+\S*(?:spec|product)\S*\s+---\n#\s+([A-Z][a-zA-Z0-9]+)/im);
|
|
68
|
+
if (sectionHeading)
|
|
69
|
+
return sectionHeading[1].trim();
|
|
70
|
+
// 5. package.json name (strip @scope/ prefix and convert to title case)
|
|
71
|
+
if (packageJsonName) {
|
|
72
|
+
const cleaned = packageJsonName
|
|
73
|
+
.replace(/^@[^/]+\//, '') // Strip scope
|
|
74
|
+
.replace(/[-_]/g, ' ')
|
|
75
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
76
|
+
.trim();
|
|
77
|
+
if (cleaned.length > 0)
|
|
78
|
+
return cleaned;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if a product name looks like a directory name rather than a real product name
|
|
84
|
+
* Used by the quality gate to flag suspicious names
|
|
85
|
+
*
|
|
86
|
+
* @param name - The product name to check
|
|
87
|
+
* @returns True if the name looks suspicious (likely a directory name)
|
|
88
|
+
*/
|
|
89
|
+
export function isSuspiciousProductName(name) {
|
|
90
|
+
const suspiciousNames = ['my-app', 'my-project', 'project', 'app', 'website', 'frontend'];
|
|
91
|
+
if (suspiciousNames.includes(name.toLowerCase()))
|
|
92
|
+
return true;
|
|
93
|
+
// Hyphenated lowercase strings like "read-all-files" are likely directory names
|
|
94
|
+
if (/^[a-z]+-[a-z]+(-[a-z]+)*$/.test(name))
|
|
95
|
+
return true;
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Extract a tagline from docs (text after em-dash in first heading)
|
|
100
|
+
* When productName is provided, prefer tagline from the heading containing that name
|
|
101
|
+
*/
|
|
102
|
+
export function extractTagline(docs, productName) {
|
|
103
|
+
// Collect all "# Name — Tagline" matches
|
|
104
|
+
const taglineMatches = [...docs.matchAll(/^#\s+(.+?)[—\-–]\s*(.{10,80})$/gm)];
|
|
105
|
+
if (taglineMatches.length > 0) {
|
|
106
|
+
// Prefer tagline from the heading that best matches the product name
|
|
107
|
+
// Reason: "Gateco UI Color System — ..." also includes "Gateco", so prefer exact match
|
|
108
|
+
if (productName) {
|
|
109
|
+
const exactMatch = taglineMatches.find((m) => m[1].trim() === productName);
|
|
110
|
+
if (exactMatch)
|
|
111
|
+
return exactMatch[2].trim();
|
|
112
|
+
// Fall back to shortest heading containing the name (closest to just the product name)
|
|
113
|
+
const nameMatches = taglineMatches.filter((m) => m[1].includes(productName));
|
|
114
|
+
if (nameMatches.length > 0) {
|
|
115
|
+
nameMatches.sort((a, b) => a[1].length - b[1].length);
|
|
116
|
+
return nameMatches[0][2].trim();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return taglineMatches[0][2].trim();
|
|
120
|
+
}
|
|
121
|
+
// Bold tagline near the top: "**Secure AI Retrieval. Priced by Use.**"
|
|
122
|
+
const boldMatch = docs.match(/\*\*([A-Z].{15,80}?)\*\*/);
|
|
123
|
+
if (boldMatch && !boldMatch[1].includes('Project Name'))
|
|
124
|
+
return boldMatch[1];
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Extract a meaningful description, skipping generic AI preambles
|
|
129
|
+
*/
|
|
130
|
+
export function extractDescription(docs, specification) {
|
|
131
|
+
// Look for "What is [Product]?" or "About [Product]" sections in docs
|
|
132
|
+
// Collect all matches and prefer the one that looks most like a product description
|
|
133
|
+
// Reason: "What is a Secured Retrieval?" in pricing doc should not beat "What Is Gateco?" in spec
|
|
134
|
+
const descPattern = /##\s+(?:\d+\.\s*)?(?:What\s+Is|About(?!\s+(?:This|the)))\b[^\n]*\n+([\s\S]*?)(?=\n##\s|\n---)/gi;
|
|
135
|
+
const descMatches = [...docs.matchAll(descPattern)];
|
|
136
|
+
// Sort: prefer matches whose heading has just a product name (no articles like "a/an/the")
|
|
137
|
+
descMatches.sort((a, b) => {
|
|
138
|
+
const headingA = a[0].split('\n')[0];
|
|
139
|
+
const headingB = b[0].split('\n')[0];
|
|
140
|
+
const hasArticleA = /what\s+is\s+(?:a|an|the)\s/i.test(headingA) ? 1 : 0;
|
|
141
|
+
const hasArticleB = /what\s+is\s+(?:a|an|the)\s/i.test(headingB) ? 1 : 0;
|
|
142
|
+
return hasArticleA - hasArticleB;
|
|
143
|
+
});
|
|
144
|
+
for (const whatIsMatch of descMatches) {
|
|
145
|
+
const paragraph = whatIsMatch[1].trim().split('\n\n')[0]
|
|
146
|
+
.replace(/\*\*/g, '').replace(/\n/g, ' ').trim();
|
|
147
|
+
if (paragraph.length > 30)
|
|
148
|
+
return paragraph.slice(0, 300);
|
|
149
|
+
}
|
|
150
|
+
// Look in specification, skipping generic lines
|
|
151
|
+
if (specification) {
|
|
152
|
+
for (const line of specification.split('\n')) {
|
|
153
|
+
const trimmed = line.trim();
|
|
154
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.length < 30)
|
|
155
|
+
continue;
|
|
156
|
+
const lower = trimmed.toLowerCase();
|
|
157
|
+
if (GENERIC_PREAMBLES.some((p) => lower.startsWith(p)))
|
|
158
|
+
continue;
|
|
159
|
+
if (lower.startsWith('**project name'))
|
|
160
|
+
continue;
|
|
161
|
+
return trimmed.replace(/^\*\*(.+?)\*\*:?\s*/, '$1: ').slice(0, 300);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
/** Regex to filter out dev-task items that aren't real product features */
|
|
167
|
+
const DEV_TASK_VERBS = /^(?:implement|fix|refactor|add tests|upgrade|migrate|configure|setup|install|deploy|debug|create|build|write|update|remove|delete)/i;
|
|
168
|
+
/**
|
|
169
|
+
* Extract features from docs and specification
|
|
170
|
+
* Docs-first: extracts from docs only; falls back to specification if empty
|
|
171
|
+
* Filters out dev-task items (implement, fix, refactor, etc.)
|
|
172
|
+
*/
|
|
173
|
+
export function extractFeatures(docs, specification) {
|
|
174
|
+
// Docs-first: try docs only, then fall back to spec
|
|
175
|
+
// Reason: specification often contains dev tasks, not user-facing features
|
|
176
|
+
const docsFeatures = extractFeaturesFromSource(docs);
|
|
177
|
+
if (docsFeatures.length > 0)
|
|
178
|
+
return docsFeatures;
|
|
179
|
+
if (specification) {
|
|
180
|
+
return extractFeaturesFromSource(specification);
|
|
181
|
+
}
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Extract features from a single text source
|
|
186
|
+
*/
|
|
187
|
+
function extractFeaturesFromSource(source) {
|
|
188
|
+
const features = [];
|
|
189
|
+
// Split into sections by heading
|
|
190
|
+
const sectionPattern = /^#{1,3}\s+(.+)$/gm;
|
|
191
|
+
const sections = [];
|
|
192
|
+
let lastIndex = 0;
|
|
193
|
+
let lastHeading = '';
|
|
194
|
+
let match;
|
|
195
|
+
while ((match = sectionPattern.exec(source)) !== null) {
|
|
196
|
+
if (lastIndex > 0) {
|
|
197
|
+
sections.push({
|
|
198
|
+
heading: lastHeading,
|
|
199
|
+
content: source.slice(lastIndex, match.index),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
lastHeading = match[1];
|
|
203
|
+
lastIndex = match.index + match[0].length;
|
|
204
|
+
}
|
|
205
|
+
if (lastIndex > 0) {
|
|
206
|
+
sections.push({ heading: lastHeading, content: source.slice(lastIndex) });
|
|
207
|
+
}
|
|
208
|
+
// Find sections about product features, principles, capabilities
|
|
209
|
+
// Reason: pattern must be specific to avoid matching design docs ("Feature Gating", "Enforcement Colors")
|
|
210
|
+
// "features" requires plural or "Key/Core Features" prefix to avoid "Feature Gating"
|
|
211
|
+
const featureKeywords = /(?:key|core|main)?\s*features\b|principle|capabilit|what\s+(?:it|we)\s+do|core\s+design/i;
|
|
212
|
+
for (const section of sections) {
|
|
213
|
+
if (!featureKeywords.test(section.heading))
|
|
214
|
+
continue;
|
|
215
|
+
// Collect bullet points (- or * or +) and numbered items (1. 2. 3.)
|
|
216
|
+
const items = section.content.match(/^(?:[-*+]|\d+\.)\s+.+/gm);
|
|
217
|
+
if (!items)
|
|
218
|
+
continue;
|
|
219
|
+
for (const item of items) {
|
|
220
|
+
const text = item.replace(/^(?:[-*+]|\d+\.)\s+/, '');
|
|
221
|
+
// Try "**bold title** - description" pattern
|
|
222
|
+
const boldWithDesc = text.match(/^\*\*(.+?)\*\*\s*[-–:]\s*(.+)/);
|
|
223
|
+
if (boldWithDesc) {
|
|
224
|
+
const title = boldWithDesc[1].trim();
|
|
225
|
+
// Filter out dev-task items
|
|
226
|
+
if (DEV_TASK_VERBS.test(title))
|
|
227
|
+
continue;
|
|
228
|
+
features.push({
|
|
229
|
+
title,
|
|
230
|
+
description: boldWithDesc[2].trim().slice(0, 150),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
else if (/^\*\*.+\*\*/.test(text)) {
|
|
234
|
+
// Bold title with no trailing description: "**Vector DB agnostic**"
|
|
235
|
+
const title = text.replace(/\*\*/g, '').trim();
|
|
236
|
+
if (DEV_TASK_VERBS.test(title))
|
|
237
|
+
continue;
|
|
238
|
+
if (title.length > 3 && title.length < 80) {
|
|
239
|
+
features.push({ title, description: title });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
const cleaned = text.replace(/\*\*/g, '');
|
|
244
|
+
// Split on sentence-level delimiters only; keep hyphens in compound words
|
|
245
|
+
const titlePart = cleaned.split(/[.,:;—–]/)[0].trim();
|
|
246
|
+
// Filter out dev-task items
|
|
247
|
+
if (DEV_TASK_VERBS.test(titlePart))
|
|
248
|
+
continue;
|
|
249
|
+
if (titlePart.length > 3 && titlePart.length < 60) {
|
|
250
|
+
features.push({
|
|
251
|
+
title: titlePart,
|
|
252
|
+
description: cleaned.slice(0, 150),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (features.length >= 6)
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
if (features.length > 0)
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
return features;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Extract pricing tiers from docs
|
|
266
|
+
* Parses markdown tables and "Plan Positioning" sections
|
|
267
|
+
*/
|
|
268
|
+
export function extractPricing(docs) {
|
|
269
|
+
const tiers = [];
|
|
270
|
+
// Find the pricing section to avoid matching design token tables
|
|
271
|
+
// Reason: "Plan-Based Color Usage" matches "Plans?" - require "Pricing" keyword
|
|
272
|
+
const pricingSection = docs.match(/##\s+(?:[\d.]*\s*)?Pricing\b[^\n]*\n([\s\S]*?)(?=\n##\s(?!.*(?:Plan\s+Positioning|Feature|Comparison))|\n---(?:\s*\n##\s)|$)/i);
|
|
273
|
+
const searchArea = pricingSection ? pricingSection[0] : docs;
|
|
274
|
+
// Look for pricing overview table rows with plan names and actual prices
|
|
275
|
+
const priceMap = new Map();
|
|
276
|
+
const tableRows = searchArea.match(/^\|[^|]*(?:Free|Pro|Enterprise|Starter|Growth|Team|Business)[^|]*\|.+\|$/gm);
|
|
277
|
+
if (tableRows) {
|
|
278
|
+
for (const row of tableRows) {
|
|
279
|
+
const cells = row.split('|').map((c) => c.trim()).filter(Boolean);
|
|
280
|
+
if (cells.length >= 2) {
|
|
281
|
+
const planName = cells[0].replace(/[🟢🔵🟣⚪🟡🟠🔴]\s*/g, '').replace(/\*\*/g, '').trim();
|
|
282
|
+
const price = cells[1].replace(/<br>/g, ' / ').replace(/\*\*/g, '').trim();
|
|
283
|
+
// Require the price to look like an actual price (Free, $amount, Custom, Contact)
|
|
284
|
+
// Reason: avoids matching design tokens like `plan-free` from color-scheme docs
|
|
285
|
+
const looksLikePrice = /^free$/i.test(price) || /^\$/.test(price)
|
|
286
|
+
|| /^custom/i.test(price) || /^contact/i.test(price);
|
|
287
|
+
if (looksLikePrice && !priceMap.has(planName)) {
|
|
288
|
+
priceMap.set(planName, price);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (priceMap.size === 0)
|
|
294
|
+
return undefined;
|
|
295
|
+
// Look for plan descriptions
|
|
296
|
+
const descMap = new Map();
|
|
297
|
+
const positionMatch = docs.match(/##\s+(?:Plan\s+Positioning|Plan\s+Descriptions?)[^\n]*\n([\s\S]*?)(?=\n##\s|\n---)/i);
|
|
298
|
+
if (positionMatch) {
|
|
299
|
+
const descPattern = /[-*]\s+\*\*(.+?)\*\*\s*\n\s+\*(.+?)\*/g;
|
|
300
|
+
let descMatch;
|
|
301
|
+
while ((descMatch = descPattern.exec(positionMatch[1])) !== null) {
|
|
302
|
+
descMap.set(descMatch[1].trim(), descMatch[2].trim());
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Build tier objects
|
|
306
|
+
let index = 0;
|
|
307
|
+
for (const [name, price] of priceMap) {
|
|
308
|
+
let displayPrice = price;
|
|
309
|
+
let period;
|
|
310
|
+
if (/free/i.test(price)) {
|
|
311
|
+
displayPrice = 'Free';
|
|
312
|
+
}
|
|
313
|
+
else if (/custom|contact/i.test(price)) {
|
|
314
|
+
displayPrice = 'Custom';
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
// If there's a "minimum" monthly amount (e.g. "$0.40/1K ... $99/month minimum"),
|
|
318
|
+
// prefer the minimum amount as the display price
|
|
319
|
+
const minMatch = price.match(/(\$[\d,.]+)\s*\/?\s*month\s*minimum/i);
|
|
320
|
+
if (minMatch) {
|
|
321
|
+
displayPrice = minMatch[1];
|
|
322
|
+
period = '/month';
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
const dollarMatch = price.match(/\$[\d,.]+/);
|
|
326
|
+
if (dollarMatch) {
|
|
327
|
+
displayPrice = dollarMatch[0];
|
|
328
|
+
if (/month/i.test(price))
|
|
329
|
+
period = '/month';
|
|
330
|
+
if (/year|annual/i.test(price))
|
|
331
|
+
period = '/year';
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const description = descMap.get(name) || '';
|
|
336
|
+
const cta = /enterprise|custom/i.test(name) ? 'Contact sales'
|
|
337
|
+
: /pro/i.test(name) ? 'Start free trial'
|
|
338
|
+
: 'Get started';
|
|
339
|
+
const featured = index === 1;
|
|
340
|
+
tiers.push({
|
|
341
|
+
name: name.replace(/\(.+?\)/, '').trim(),
|
|
342
|
+
price: displayPrice,
|
|
343
|
+
period,
|
|
344
|
+
description,
|
|
345
|
+
features: [],
|
|
346
|
+
cta,
|
|
347
|
+
featured,
|
|
348
|
+
});
|
|
349
|
+
index++;
|
|
350
|
+
}
|
|
351
|
+
// Extract features per plan from comparison table
|
|
352
|
+
const compTable = docs.match(/\|\s*Feature\s*\/\s*Plan\s*\|(.+)\|[\s\S]*?(?=\n\n|\n##\s|\n---)/i);
|
|
353
|
+
if (compTable && tiers.length > 0) {
|
|
354
|
+
const rows = compTable[0].split('\n').filter((r) => r.startsWith('|'));
|
|
355
|
+
for (const row of rows.slice(2)) {
|
|
356
|
+
const cells = row.split('|').map((c) => c.trim()).filter(Boolean);
|
|
357
|
+
if (cells.length < 2)
|
|
358
|
+
continue;
|
|
359
|
+
const featureName = cells[0].replace(/\*\*/g, '').trim();
|
|
360
|
+
if (!featureName || /^[-=]+$/.test(featureName))
|
|
361
|
+
continue;
|
|
362
|
+
for (let i = 0; i < tiers.length && i + 1 < cells.length; i++) {
|
|
363
|
+
const val = cells[i + 1].trim();
|
|
364
|
+
if (val && val !== '❌') {
|
|
365
|
+
const display = val === '✅' ? featureName : `${featureName}: ${val}`;
|
|
366
|
+
tiers[i].features.push(display);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
for (const tier of tiers) {
|
|
372
|
+
tier.features = tier.features.slice(0, 6);
|
|
373
|
+
}
|
|
374
|
+
return tiers.length > 0 ? tiers : undefined;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Extract the primary brand/accent color, not just any hex color
|
|
378
|
+
* Looks for accent/primary/CTA color tokens, avoids dark backgrounds
|
|
379
|
+
*/
|
|
380
|
+
export function extractPrimaryColor(docs) {
|
|
381
|
+
// Look for "accent-primary" or "accent_primary" token with nearby hex
|
|
382
|
+
const accentMatch = docs.match(/accent[_-]?primary[^#]{0,40}(#[0-9a-fA-F]{6})/i);
|
|
383
|
+
if (accentMatch)
|
|
384
|
+
return accentMatch[1];
|
|
385
|
+
// Look for "Primary Brand" or "Primary CTA" color label near a hex value
|
|
386
|
+
const primaryMatch = docs.match(/(?:primary\s+(?:brand\s+)?(?:accent|color|CTA))[^#]{0,40}(#[0-9a-fA-F]{6})/i);
|
|
387
|
+
if (primaryMatch)
|
|
388
|
+
return primaryMatch[1];
|
|
389
|
+
// Look for CTA/link color
|
|
390
|
+
const ctaMatch = docs.match(/(?:CTA|primary\s+link)[^#]{0,40}(#[0-9a-fA-F]{6})/i);
|
|
391
|
+
if (ctaMatch)
|
|
392
|
+
return ctaMatch[1];
|
|
393
|
+
// Fallback: first hex color that isn't very dark or very light
|
|
394
|
+
const allColors = [...docs.matchAll(/#([0-9a-fA-F]{6})/g)];
|
|
395
|
+
for (const colorMatch of allColors) {
|
|
396
|
+
const hex = colorMatch[1];
|
|
397
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
398
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
399
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
400
|
+
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
|
401
|
+
if (brightness > 60 && brightness < 210) {
|
|
402
|
+
return '#' + hex;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
//# sourceMappingURL=doc-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc-parser.js","sourceRoot":"","sources":["../../src/generators/doc-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend design language analyzer
|
|
3
|
+
* Extracts design tokens (colors, fonts, component library) from an existing
|
|
4
|
+
* frontend app to use as fallback for website generation
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Analyzed design context from a frontend application
|
|
8
|
+
*/
|
|
9
|
+
export interface FrontendDesignContext {
|
|
10
|
+
colors?: Record<string, string>;
|
|
11
|
+
primaryColor?: string;
|
|
12
|
+
fonts?: {
|
|
13
|
+
heading?: string;
|
|
14
|
+
body?: string;
|
|
15
|
+
mono?: string;
|
|
16
|
+
};
|
|
17
|
+
borderRadius?: string;
|
|
18
|
+
componentLibrary?: 'shadcn' | 'radix' | 'mui' | 'chakra' | 'unknown';
|
|
19
|
+
darkMode?: boolean;
|
|
20
|
+
source: 'tailwind-config' | 'css-variables' | 'defaults';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Analyze the frontend app design language
|
|
24
|
+
* Extracts colors, fonts, and component library from the project
|
|
25
|
+
*
|
|
26
|
+
* @param projectDir - Root project directory
|
|
27
|
+
* @returns Design context or null if no frontend app found
|
|
28
|
+
*/
|
|
29
|
+
export declare function analyzeFrontendDesign(projectDir: string): Promise<FrontendDesignContext | null>;
|
|
30
|
+
//# sourceMappingURL=frontend-design-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontend-design-analyzer.d.ts","sourceRoot":"","sources":["../../src/generators/frontend-design-analyzer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,iBAAiB,GAAG,eAAe,GAAG,UAAU,CAAC;CAC1D;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAkDvC"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend design language analyzer
|
|
3
|
+
* Extracts design tokens (colors, fonts, component library) from an existing
|
|
4
|
+
* frontend app to use as fallback for website generation
|
|
5
|
+
*/
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
/**
|
|
9
|
+
* Analyze the frontend app design language
|
|
10
|
+
* Extracts colors, fonts, and component library from the project
|
|
11
|
+
*
|
|
12
|
+
* @param projectDir - Root project directory
|
|
13
|
+
* @returns Design context or null if no frontend app found
|
|
14
|
+
*/
|
|
15
|
+
export async function analyzeFrontendDesign(projectDir) {
|
|
16
|
+
const frontendDir = path.join(projectDir, 'apps', 'frontend');
|
|
17
|
+
// Check if frontend app exists
|
|
18
|
+
try {
|
|
19
|
+
await fs.access(frontendDir);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const context = {
|
|
25
|
+
source: 'defaults',
|
|
26
|
+
};
|
|
27
|
+
// Detect component library from package.json
|
|
28
|
+
context.componentLibrary = await detectComponentLibrary(frontendDir);
|
|
29
|
+
// Try CSS custom properties first (shadcn/ui convention)
|
|
30
|
+
const cssResult = await extractCssVariables(frontendDir);
|
|
31
|
+
if (cssResult) {
|
|
32
|
+
if (cssResult.primaryColor)
|
|
33
|
+
context.primaryColor = cssResult.primaryColor;
|
|
34
|
+
if (cssResult.colors)
|
|
35
|
+
context.colors = cssResult.colors;
|
|
36
|
+
if (cssResult.borderRadius)
|
|
37
|
+
context.borderRadius = cssResult.borderRadius;
|
|
38
|
+
context.darkMode = cssResult.darkMode;
|
|
39
|
+
context.source = 'css-variables';
|
|
40
|
+
}
|
|
41
|
+
// Try tailwind config (regex-based since we can't import user's config)
|
|
42
|
+
const tailwindResult = await extractTailwindConfig(frontendDir);
|
|
43
|
+
if (tailwindResult) {
|
|
44
|
+
// Only override if CSS vars didn't provide the value
|
|
45
|
+
if (!context.primaryColor && tailwindResult.primaryColor) {
|
|
46
|
+
context.primaryColor = tailwindResult.primaryColor;
|
|
47
|
+
}
|
|
48
|
+
if (!context.colors && tailwindResult.colors) {
|
|
49
|
+
context.colors = tailwindResult.colors;
|
|
50
|
+
}
|
|
51
|
+
if (tailwindResult.fonts)
|
|
52
|
+
context.fonts = tailwindResult.fonts;
|
|
53
|
+
if (tailwindResult.darkMode !== undefined && !cssResult) {
|
|
54
|
+
context.darkMode = tailwindResult.darkMode;
|
|
55
|
+
}
|
|
56
|
+
if (context.source === 'defaults')
|
|
57
|
+
context.source = 'tailwind-config';
|
|
58
|
+
}
|
|
59
|
+
// Return null if nothing meaningful was found
|
|
60
|
+
if (!context.primaryColor && !context.colors && !context.componentLibrary) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return context;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Detect component library from package.json dependencies
|
|
67
|
+
*/
|
|
68
|
+
async function detectComponentLibrary(frontendDir) {
|
|
69
|
+
try {
|
|
70
|
+
const pkgContent = await fs.readFile(path.join(frontendDir, 'package.json'), 'utf-8');
|
|
71
|
+
const pkg = JSON.parse(pkgContent);
|
|
72
|
+
const allDeps = {
|
|
73
|
+
...pkg.dependencies,
|
|
74
|
+
...pkg.devDependencies,
|
|
75
|
+
};
|
|
76
|
+
if (allDeps['@shadcn/ui'] || allDeps['shadcn-ui'])
|
|
77
|
+
return 'shadcn';
|
|
78
|
+
if (allDeps['@radix-ui/react-dialog'] || allDeps['@radix-ui/themes'])
|
|
79
|
+
return 'radix';
|
|
80
|
+
if (allDeps['@mui/material'])
|
|
81
|
+
return 'mui';
|
|
82
|
+
if (allDeps['@chakra-ui/react'])
|
|
83
|
+
return 'chakra';
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Package.json not readable
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Extract design tokens from CSS custom properties (index.css or globals.css)
|
|
92
|
+
*/
|
|
93
|
+
async function extractCssVariables(frontendDir) {
|
|
94
|
+
const cssFiles = [
|
|
95
|
+
path.join(frontendDir, 'src', 'index.css'),
|
|
96
|
+
path.join(frontendDir, 'src', 'globals.css'),
|
|
97
|
+
path.join(frontendDir, 'src', 'app', 'globals.css'),
|
|
98
|
+
];
|
|
99
|
+
let cssContent = '';
|
|
100
|
+
for (const cssFile of cssFiles) {
|
|
101
|
+
try {
|
|
102
|
+
cssContent = await fs.readFile(cssFile, 'utf-8');
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!cssContent)
|
|
110
|
+
return null;
|
|
111
|
+
const result = {};
|
|
112
|
+
// Detect shadcn/ui convention: HSL values like --primary: 222.2 47.4% 11.2%
|
|
113
|
+
const primaryHsl = cssContent.match(/--primary:\s*([\d.]+)\s+([\d.]+)%\s+([\d.]+)%/);
|
|
114
|
+
if (primaryHsl) {
|
|
115
|
+
const h = parseFloat(primaryHsl[1]);
|
|
116
|
+
const s = parseFloat(primaryHsl[2]) / 100;
|
|
117
|
+
const l = parseFloat(primaryHsl[3]) / 100;
|
|
118
|
+
result.primaryColor = hslToHex(h / 360, s, l);
|
|
119
|
+
}
|
|
120
|
+
// Detect hex-based CSS variables
|
|
121
|
+
if (!result.primaryColor) {
|
|
122
|
+
const primaryHex = cssContent.match(/--primary(?:-color)?:\s*(#[0-9a-fA-F]{6})/);
|
|
123
|
+
if (primaryHex)
|
|
124
|
+
result.primaryColor = primaryHex[1];
|
|
125
|
+
}
|
|
126
|
+
// Extract border radius
|
|
127
|
+
const radiusMatch = cssContent.match(/--radius:\s*([\d.]+rem)/);
|
|
128
|
+
if (radiusMatch)
|
|
129
|
+
result.borderRadius = radiusMatch[1];
|
|
130
|
+
// Detect dark mode blocks
|
|
131
|
+
result.darkMode = /\.dark\s*\{/.test(cssContent) || /@media\s*\(prefers-color-scheme:\s*dark\)/.test(cssContent);
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Extract design tokens from tailwind config (regex-based)
|
|
136
|
+
*/
|
|
137
|
+
async function extractTailwindConfig(frontendDir) {
|
|
138
|
+
const configFiles = [
|
|
139
|
+
path.join(frontendDir, 'tailwind.config.ts'),
|
|
140
|
+
path.join(frontendDir, 'tailwind.config.js'),
|
|
141
|
+
path.join(frontendDir, 'tailwind.config.mjs'),
|
|
142
|
+
];
|
|
143
|
+
let configContent = '';
|
|
144
|
+
for (const configFile of configFiles) {
|
|
145
|
+
try {
|
|
146
|
+
configContent = await fs.readFile(configFile, 'utf-8');
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!configContent)
|
|
154
|
+
return null;
|
|
155
|
+
const result = {};
|
|
156
|
+
// Extract primary color: primary: { 500: '#...' } or primary: '#...'
|
|
157
|
+
const primary500 = configContent.match(/primary[^}]*?500:\s*['"]?(#[0-9a-fA-F]{6})/);
|
|
158
|
+
if (primary500) {
|
|
159
|
+
result.primaryColor = primary500[1];
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const primaryDirect = configContent.match(/primary:\s*['"]?(#[0-9a-fA-F]{6})/);
|
|
163
|
+
if (primaryDirect)
|
|
164
|
+
result.primaryColor = primaryDirect[1];
|
|
165
|
+
}
|
|
166
|
+
// Extract font family
|
|
167
|
+
const sansFont = configContent.match(/sans:\s*\[['"]([^'"]+)['"]/);
|
|
168
|
+
if (sansFont) {
|
|
169
|
+
result.fonts = { body: sansFont[1] };
|
|
170
|
+
}
|
|
171
|
+
// Detect dark mode configuration
|
|
172
|
+
result.darkMode = /darkMode:\s*['"]class['"]/.test(configContent);
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
// --- Color conversion helper ---
|
|
176
|
+
function hslToHex(h, s, l) {
|
|
177
|
+
const hue2rgb = (p, q, t) => {
|
|
178
|
+
let tn = t;
|
|
179
|
+
if (tn < 0)
|
|
180
|
+
tn += 1;
|
|
181
|
+
if (tn > 1)
|
|
182
|
+
tn -= 1;
|
|
183
|
+
if (tn < 1 / 6)
|
|
184
|
+
return p + (q - p) * 6 * tn;
|
|
185
|
+
if (tn < 1 / 2)
|
|
186
|
+
return q;
|
|
187
|
+
if (tn < 2 / 3)
|
|
188
|
+
return p + (q - p) * (2 / 3 - tn) * 6;
|
|
189
|
+
return p;
|
|
190
|
+
};
|
|
191
|
+
let r, g, b;
|
|
192
|
+
if (s === 0) {
|
|
193
|
+
r = g = b = l;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
197
|
+
const p = 2 * l - q;
|
|
198
|
+
r = hue2rgb(p, q, h + 1 / 3);
|
|
199
|
+
g = hue2rgb(p, q, h);
|
|
200
|
+
b = hue2rgb(p, q, h - 1 / 3);
|
|
201
|
+
}
|
|
202
|
+
const toHex = (n) => {
|
|
203
|
+
const hex = Math.round(n * 255).toString(16);
|
|
204
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
205
|
+
};
|
|
206
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=frontend-design-analyzer.js.map
|