codescoop 1.0.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/LICENSE +21 -0
- package/README.md +249 -0
- package/bin/codescoop.js +276 -0
- package/package.json +75 -0
- package/src/cli/interactive.js +153 -0
- package/src/index.js +303 -0
- package/src/output/conversion-generator.js +501 -0
- package/src/output/markdown.js +562 -0
- package/src/parsers/css-analyzer.js +488 -0
- package/src/parsers/html-parser.js +455 -0
- package/src/parsers/js-analyzer.js +413 -0
- package/src/utils/file-scanner.js +191 -0
- package/src/utils/ghost-detector.js +174 -0
- package/src/utils/library-detector.js +335 -0
- package/src/utils/specificity-calculator.js +251 -0
- package/src/utils/template-parser.js +260 -0
- package/src/utils/url-fetcher.js +123 -0
- package/src/utils/validation.js +278 -0
- package/src/utils/variable-extractor.js +271 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Output Generator
|
|
3
|
+
* Generates LLM-ready markdown report from analysis results
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { formatVariablesAsMarkdown } = require('../utils/variable-extractor');
|
|
8
|
+
const { detectGhostClasses, formatGhostClassesMarkdown } = require('../utils/ghost-detector');
|
|
9
|
+
const { analyzeConflicts, formatConflictsMarkdown } = require('../utils/specificity-calculator');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate markdown report from analysis
|
|
13
|
+
* @param {Object} analysis - Analysis results
|
|
14
|
+
* @returns {string} Markdown content
|
|
15
|
+
*/
|
|
16
|
+
function generateMarkdown(analysis) {
|
|
17
|
+
const {
|
|
18
|
+
targetInfo,
|
|
19
|
+
htmlPath,
|
|
20
|
+
projectDir,
|
|
21
|
+
cssResults,
|
|
22
|
+
jsResults,
|
|
23
|
+
cssLibraryResults = [],
|
|
24
|
+
jsLibraryResults = [],
|
|
25
|
+
detectedLibraries = {},
|
|
26
|
+
inlineStyles,
|
|
27
|
+
inlineScripts,
|
|
28
|
+
missingImports,
|
|
29
|
+
variableData = {},
|
|
30
|
+
generatedAt,
|
|
31
|
+
outputOptions = {}
|
|
32
|
+
} = analysis;
|
|
33
|
+
|
|
34
|
+
// Extract output options with defaults
|
|
35
|
+
const {
|
|
36
|
+
compact = false,
|
|
37
|
+
forConversion = false,
|
|
38
|
+
maxRulesPerFile = compact ? 20 : Infinity,
|
|
39
|
+
maxJsPerFile = compact ? 10 : Infinity,
|
|
40
|
+
summaryOnly = false,
|
|
41
|
+
skipMinified = false
|
|
42
|
+
} = outputOptions;
|
|
43
|
+
|
|
44
|
+
// If forConversion mode, use specialized conversion context generator
|
|
45
|
+
if (forConversion) {
|
|
46
|
+
const { generateConversionContext } = require('./conversion-generator');
|
|
47
|
+
return generateConversionContext(analysis);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Apply filters based on options
|
|
51
|
+
let filteredCssResults = cssResults;
|
|
52
|
+
let filteredJsResults = jsResults;
|
|
53
|
+
|
|
54
|
+
if (skipMinified) {
|
|
55
|
+
filteredCssResults = cssResults.filter(r => !r.isMinified);
|
|
56
|
+
filteredJsResults = jsResults.filter(r => !r.isMinified);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Apply limits per file
|
|
60
|
+
if (compact || maxRulesPerFile < Infinity) {
|
|
61
|
+
filteredCssResults = filteredCssResults.map(r => ({
|
|
62
|
+
...r,
|
|
63
|
+
matches: r.matches.slice(0, maxRulesPerFile),
|
|
64
|
+
truncated: r.matches.length > maxRulesPerFile,
|
|
65
|
+
originalCount: r.matches.length
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (compact || maxJsPerFile < Infinity) {
|
|
70
|
+
filteredJsResults = filteredJsResults.map(r => ({
|
|
71
|
+
...r,
|
|
72
|
+
matches: r.matches.slice(0, maxJsPerFile),
|
|
73
|
+
truncated: r.matches.length > maxJsPerFile,
|
|
74
|
+
originalCount: r.matches.length
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sections = [];
|
|
79
|
+
|
|
80
|
+
// Header
|
|
81
|
+
sections.push(generateHeader(targetInfo, htmlPath, generatedAt, compact));
|
|
82
|
+
|
|
83
|
+
// Libraries section (NEW!)
|
|
84
|
+
const hasLibraries =
|
|
85
|
+
Object.keys(detectedLibraries.fromFiles || {}).length > 0 ||
|
|
86
|
+
(detectedLibraries.fromCDN || []).length > 0 ||
|
|
87
|
+
(detectedLibraries.fromClasses || []).length > 0;
|
|
88
|
+
|
|
89
|
+
if (hasLibraries) {
|
|
90
|
+
sections.push(generateLibrariesSection(detectedLibraries, cssLibraryResults, jsLibraryResults, projectDir));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Missing imports warning (if any)
|
|
94
|
+
if (missingImports.length > 0) {
|
|
95
|
+
sections.push(generateMissingImportsWarning(missingImports, projectDir));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// In summary-only mode, skip detailed code blocks
|
|
99
|
+
if (summaryOnly) {
|
|
100
|
+
sections.push(generateSummaryOnlySection(filteredCssResults, filteredJsResults, projectDir));
|
|
101
|
+
sections.push(generateSummary(cssResults, jsResults, inlineStyles, inlineScripts, missingImports, detectedLibraries));
|
|
102
|
+
return sections.join('\n\n---\n\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Target HTML
|
|
106
|
+
sections.push(generateHTMLSection(targetInfo));
|
|
107
|
+
|
|
108
|
+
// CSS/SCSS Variables section
|
|
109
|
+
if (variableData && variableData.usedVariables && variableData.usedVariables.length > 0) {
|
|
110
|
+
const varsSection = `## 🎨 CSS/SCSS Variables\n\n> These variables are used in the matched CSS rules. Definitions are included for context.\n\n${formatVariablesAsMarkdown(variableData)}`;
|
|
111
|
+
sections.push(varsSection);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// CSS Houdini @property definitions
|
|
115
|
+
const houdiniSection = generateHoudiniSection(cssResults, projectDir);
|
|
116
|
+
if (houdiniSection) {
|
|
117
|
+
sections.push(houdiniSection);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// CSS Dependencies (custom code only)
|
|
121
|
+
const linkedCSS = filteredCssResults.filter(r => r.isLinked);
|
|
122
|
+
const unlinkedCSS = filteredCssResults.filter(r => !r.isLinked);
|
|
123
|
+
|
|
124
|
+
if (linkedCSS.length > 0 || inlineStyles.length > 0) {
|
|
125
|
+
sections.push(generateCSSSection(linkedCSS, inlineStyles, projectDir, 'Custom CSS Dependencies', compact));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (unlinkedCSS.length > 0) {
|
|
129
|
+
sections.push(generateCSSSection(unlinkedCSS, [], projectDir, '⚠️ Custom CSS Files NOT Linked', compact));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Shadow DOM styles
|
|
133
|
+
const shadowDOMSection = generateShadowDOMSection(cssResults, projectDir);
|
|
134
|
+
if (shadowDOMSection) {
|
|
135
|
+
sections.push(shadowDOMSection);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// JavaScript References (custom code only)
|
|
139
|
+
const linkedJS = filteredJsResults.filter(r => r.isLinked);
|
|
140
|
+
const unlinkedJS = filteredJsResults.filter(r => !r.isLinked);
|
|
141
|
+
|
|
142
|
+
if (linkedJS.length > 0 || inlineScripts.length > 0) {
|
|
143
|
+
sections.push(generateJSSection(linkedJS, inlineScripts, projectDir, 'Custom JavaScript References', compact));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (unlinkedJS.length > 0) {
|
|
147
|
+
sections.push(generateJSSection(unlinkedJS, [], projectDir, '⚠️ Custom JS Files NOT Linked', compact));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// CSS Conflict Detection (specificity analysis)
|
|
151
|
+
const conflicts = analyzeConflicts(cssResults, { css: cssResults.filter(r => r.isLinked).map(r => r.file) });
|
|
152
|
+
const conflictsMarkdown = formatConflictsMarkdown(conflicts);
|
|
153
|
+
if (conflictsMarkdown) {
|
|
154
|
+
sections.push(conflictsMarkdown);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Ghost Classes Detection
|
|
158
|
+
const ghostData = detectGhostClasses(targetInfo, cssResults, cssLibraryResults, inlineStyles);
|
|
159
|
+
if (ghostData.hasGhosts) {
|
|
160
|
+
sections.push(formatGhostClassesMarkdown(ghostData));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Summary
|
|
164
|
+
sections.push(generateSummary(cssResults, jsResults, inlineStyles, inlineScripts, missingImports, detectedLibraries, ghostData));
|
|
165
|
+
|
|
166
|
+
return sections.join('\n\n---\n\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Generate libraries section
|
|
171
|
+
*/
|
|
172
|
+
function generateLibrariesSection(detectedLibraries, cssLibraryResults, jsLibraryResults, projectDir) {
|
|
173
|
+
let content = `## 📚 Libraries Detected\n\n`;
|
|
174
|
+
content += `> These libraries are used by this component. Make sure they are properly imported.\n\n`;
|
|
175
|
+
|
|
176
|
+
// Libraries from CDN
|
|
177
|
+
const cdnLibs = detectedLibraries.fromCDN || [];
|
|
178
|
+
if (cdnLibs.length > 0) {
|
|
179
|
+
content += `### Loaded via CDN ✅\n\n`;
|
|
180
|
+
content += `| Library | Type | URL |\n`;
|
|
181
|
+
content += `|---------|------|-----|\n`;
|
|
182
|
+
cdnLibs.forEach(lib => {
|
|
183
|
+
const shortUrl = lib.url.length > 50 ? lib.url.substring(0, 47) + '...' : lib.url;
|
|
184
|
+
content += `| **${lib.name}** | ${lib.type} | \`${shortUrl}\` |\n`;
|
|
185
|
+
});
|
|
186
|
+
content += '\n';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Libraries from local files
|
|
190
|
+
const fileLibs = detectedLibraries.fromFiles || {};
|
|
191
|
+
const libNames = Object.keys(fileLibs);
|
|
192
|
+
if (libNames.length > 0) {
|
|
193
|
+
content += `### Local Library Files\n\n`;
|
|
194
|
+
content += `| Library | Type | Status | Website |\n`;
|
|
195
|
+
content += `|---------|------|--------|--------|\n`;
|
|
196
|
+
|
|
197
|
+
libNames.forEach(name => {
|
|
198
|
+
const lib = fileLibs[name];
|
|
199
|
+
const hasLinkedFiles = lib.files?.some(f => {
|
|
200
|
+
const allResults = [...cssLibraryResults, ...jsLibraryResults];
|
|
201
|
+
return allResults.find(r => r.filePath === f && r.isLinked);
|
|
202
|
+
});
|
|
203
|
+
const status = hasLinkedFiles ? '✅ Linked' : '⚠️ Not Linked';
|
|
204
|
+
const website = lib.website ? `[Docs](${lib.website})` : '-';
|
|
205
|
+
content += `| **${name}** | ${lib.type} | ${status} | ${website} |\n`;
|
|
206
|
+
});
|
|
207
|
+
content += '\n';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Libraries detected from class names
|
|
211
|
+
const classLibs = detectedLibraries.fromClasses || [];
|
|
212
|
+
if (classLibs.length > 0) {
|
|
213
|
+
content += `### Detected from Class Names\n\n`;
|
|
214
|
+
content += `The following libraries appear to be used based on class naming conventions:\n\n`;
|
|
215
|
+
classLibs.forEach(name => {
|
|
216
|
+
content += `- **${name}**\n`;
|
|
217
|
+
});
|
|
218
|
+
content += '\n';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Note about library code
|
|
222
|
+
content += `> **Note:** Library code is not shown in detail below to keep the report focused on your custom code.\n`;
|
|
223
|
+
content += `> The component uses ${cssLibraryResults.length} CSS rules and ${jsLibraryResults.length} JS references from libraries.`;
|
|
224
|
+
|
|
225
|
+
return content;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Generate header section
|
|
230
|
+
*/
|
|
231
|
+
function generateHeader(targetInfo, htmlPath, generatedAt) {
|
|
232
|
+
const date = new Date(generatedAt).toLocaleString();
|
|
233
|
+
|
|
234
|
+
return `# Component Analysis: ${targetInfo.selector}
|
|
235
|
+
|
|
236
|
+
> Generated by **CodeScoop** on ${date}
|
|
237
|
+
>
|
|
238
|
+
> Source: \`${path.basename(htmlPath)}\`
|
|
239
|
+
|
|
240
|
+
## Target Component
|
|
241
|
+
|
|
242
|
+
| Property | Value |
|
|
243
|
+
|----------|-------|
|
|
244
|
+
| **Selector** | \`${targetInfo.selector}\` |
|
|
245
|
+
| **Tag** | \`<${targetInfo.tagName}>\` |
|
|
246
|
+
| **Classes** | ${targetInfo.classes.length > 0 ? targetInfo.classes.map(c => '`.' + c + '`').join(', ') : '_none_'} |
|
|
247
|
+
| **IDs** | ${targetInfo.ids.length > 0 ? targetInfo.ids.map(id => '`#' + id + '`').join(', ') : '_none_'} |
|
|
248
|
+
| **Line Range** | ${targetInfo.startLine ? `Lines ${targetInfo.startLine}-${targetInfo.endLine}` : '_unknown_'} |`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Generate missing imports warning
|
|
253
|
+
*/
|
|
254
|
+
function generateMissingImportsWarning(missingImports, projectDir) {
|
|
255
|
+
const fileList = missingImports.map(f => `- \`${path.relative(projectDir, f)}\``).join('\n');
|
|
256
|
+
|
|
257
|
+
return `## ⚠️ Missing Imports Detected
|
|
258
|
+
|
|
259
|
+
The following files contain code relevant to this component but are **NOT imported** in the HTML file:
|
|
260
|
+
|
|
261
|
+
${fileList}
|
|
262
|
+
|
|
263
|
+
> **This may cause the component to not work correctly!**
|
|
264
|
+
> Add the appropriate \`<link>\` or \`<script>\` tags to import these files.`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Generate HTML section
|
|
269
|
+
*/
|
|
270
|
+
function generateHTMLSection(targetInfo) {
|
|
271
|
+
return `## Target HTML
|
|
272
|
+
${targetInfo.startLine ? `**Lines:** ${targetInfo.startLine}-${targetInfo.endLine}` : ''}
|
|
273
|
+
|
|
274
|
+
\`\`\`html
|
|
275
|
+
${targetInfo.html}
|
|
276
|
+
\`\`\``;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Generate CSS section
|
|
281
|
+
*/
|
|
282
|
+
function generateCSSSection(cssResults, inlineStyles, projectDir, title) {
|
|
283
|
+
let content = `## ${title}\n\n`;
|
|
284
|
+
|
|
285
|
+
// Inline styles first
|
|
286
|
+
if (inlineStyles.length > 0) {
|
|
287
|
+
content += `### Inline \`<style>\` Blocks\n\n`;
|
|
288
|
+
|
|
289
|
+
inlineStyles.forEach((style, index) => {
|
|
290
|
+
content += `#### Block ${index + 1}\n\n`;
|
|
291
|
+
content += '```css\n' + style.content + '\n```\n\n';
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// External CSS files
|
|
296
|
+
cssResults.forEach(result => {
|
|
297
|
+
const relativePath = path.relative(projectDir, result.filePath);
|
|
298
|
+
const fileType = result.isScss ? 'SCSS' : (result.isMinified ? 'CSS (minified)' : 'CSS');
|
|
299
|
+
|
|
300
|
+
content += `### From: \`${relativePath}\`\n`;
|
|
301
|
+
content += `**Type:** ${fileType}\n\n`;
|
|
302
|
+
|
|
303
|
+
result.matches.forEach((match, index) => {
|
|
304
|
+
if (match.atRuleContext) {
|
|
305
|
+
content += `**Context:** \`${match.atRuleContext}\`\n`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
content += `**Lines:** ${match.startLine}-${match.endLine} | `;
|
|
309
|
+
content += `**Matched on:** ${match.matchedOn.join(', ')}\n\n`;
|
|
310
|
+
|
|
311
|
+
const lang = result.isScss ? 'scss' : 'css';
|
|
312
|
+
content += '```' + lang + '\n' + match.content + '\n```\n\n';
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return content;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Generate JavaScript section
|
|
321
|
+
*/
|
|
322
|
+
function generateJSSection(jsResults, inlineScripts, projectDir, title) {
|
|
323
|
+
let content = `## ${title}\n\n`;
|
|
324
|
+
|
|
325
|
+
// Inline scripts first
|
|
326
|
+
if (inlineScripts.length > 0) {
|
|
327
|
+
content += `### Inline \`<script>\` Blocks\n\n`;
|
|
328
|
+
|
|
329
|
+
inlineScripts.forEach((script, index) => {
|
|
330
|
+
content += `#### Block ${index + 1}\n\n`;
|
|
331
|
+
content += '```javascript\n' + script.content + '\n```\n\n';
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// External JS files
|
|
336
|
+
jsResults.forEach(result => {
|
|
337
|
+
const relativePath = path.relative(projectDir, result.filePath);
|
|
338
|
+
const fileType = result.isMinified ? 'JS (minified, beautified for display)' : 'JavaScript';
|
|
339
|
+
|
|
340
|
+
content += `### From: \`${relativePath}\`\n`;
|
|
341
|
+
content += `**Type:** ${fileType}\n\n`;
|
|
342
|
+
|
|
343
|
+
result.matches.forEach((match, index) => {
|
|
344
|
+
content += `**Lines:** ${match.startLine}-${match.endLine} | `;
|
|
345
|
+
content += `**Type:** ${match.type} | `;
|
|
346
|
+
content += `**Matched on:** ${match.matchedOn.join(', ')}\n`;
|
|
347
|
+
|
|
348
|
+
if (match.selector) {
|
|
349
|
+
content += `**Selector:** \`${match.selector}\`\n`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
content += '\n```javascript\n' + match.content + '\n```\n\n';
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return content;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Generate summary section
|
|
361
|
+
*/
|
|
362
|
+
function generateSummary(cssResults, jsResults, inlineStyles, inlineScripts, missingImports, detectedLibraries = {}, ghostData = {}) {
|
|
363
|
+
const totalCSSRules = cssResults.reduce((sum, r) => sum + r.matches.length, 0) + inlineStyles.length;
|
|
364
|
+
const totalJSRefs = jsResults.reduce((sum, r) => sum + r.matches.length, 0) + inlineScripts.length;
|
|
365
|
+
|
|
366
|
+
const linkedCSSFiles = cssResults.filter(r => r.isLinked).length;
|
|
367
|
+
const linkedJSFiles = jsResults.filter(r => r.isLinked).length;
|
|
368
|
+
const unlinkedCSSFiles = cssResults.filter(r => !r.isLinked).length;
|
|
369
|
+
const unlinkedJSFiles = jsResults.filter(r => !r.isLinked).length;
|
|
370
|
+
|
|
371
|
+
const libraryCount = Object.keys(detectedLibraries.fromFiles || {}).length +
|
|
372
|
+
(detectedLibraries.fromCDN || []).length;
|
|
373
|
+
|
|
374
|
+
const ghostCount = ghostData.ghostClasses?.length || 0;
|
|
375
|
+
|
|
376
|
+
const advancedFeaturesSummary = updateSummaryForAdvancedFeatures(cssResults);
|
|
377
|
+
|
|
378
|
+
return `## Summary
|
|
379
|
+
|
|
380
|
+
| Metric | Count |
|
|
381
|
+
|--------|-------|
|
|
382
|
+
| **Libraries Detected** | ${libraryCount} |
|
|
383
|
+
| **Custom CSS Rules Found** | ${totalCSSRules} |
|
|
384
|
+
| **Custom JS References Found** | ${totalJSRefs} |
|
|
385
|
+
| **Linked CSS Files** | ${linkedCSSFiles} |
|
|
386
|
+
| **Linked JS Files** | ${linkedJSFiles} |
|
|
387
|
+
| **Unlinked CSS Files (potential issues)** | ${unlinkedCSSFiles} |
|
|
388
|
+
| **Unlinked JS Files (potential issues)** | ${unlinkedJSFiles} |
|
|
389
|
+
| **Inline Styles** | ${inlineStyles.length} |
|
|
390
|
+
| **Inline Scripts** | ${inlineScripts.length} |
|
|
391
|
+
${ghostCount > 0 ? `| **👻 Ghost Classes (no CSS)** | ${ghostCount} |\n` : ''}${advancedFeaturesSummary}
|
|
392
|
+
${missingImports.length > 0 ? `
|
|
393
|
+
### ⚠️ Action Required
|
|
394
|
+
${missingImports.length} file(s) contain relevant code but are not imported. Review the "Missing Imports" section above.
|
|
395
|
+
` : '✅ All custom files with relevant code appear to be properly linked.'}
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
*This report was generated by CodeScoop to help debug component dependencies. Feed this file to an LLM for assistance converting to React/Next.js or debugging issues.*`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Generate summary-only section (no code blocks)
|
|
404
|
+
*/
|
|
405
|
+
function generateSummaryOnlySection(cssResults, jsResults, projectDir) {
|
|
406
|
+
let content = `## 📋 Files Summary (Summary-Only Mode)\n\n`;
|
|
407
|
+
content += `> Code blocks omitted. Use without \`--summary-only\` flag to see full code.\n\n`;
|
|
408
|
+
|
|
409
|
+
// CSS Files
|
|
410
|
+
const cssWithMatches = cssResults.filter(r => r.matches && r.matches.length > 0);
|
|
411
|
+
if (cssWithMatches.length > 0) {
|
|
412
|
+
content += `### CSS Files (${cssWithMatches.length} files)\n\n`;
|
|
413
|
+
content += `| File | Matches | Linked |\n`;
|
|
414
|
+
content += `|------|---------|--------|\n`;
|
|
415
|
+
cssWithMatches.forEach(result => {
|
|
416
|
+
const relPath = path.relative(projectDir, result.filePath);
|
|
417
|
+
const count = result.originalCount || result.matches.length;
|
|
418
|
+
const linked = result.isLinked ? '✅' : '❌';
|
|
419
|
+
content += `| \`${relPath}\` | ${count} rules | ${linked} |\n`;
|
|
420
|
+
});
|
|
421
|
+
content += '\n';
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// JS Files
|
|
425
|
+
const jsWithMatches = jsResults.filter(r => r.matches && r.matches.length > 0);
|
|
426
|
+
if (jsWithMatches.length > 0) {
|
|
427
|
+
content += `### JavaScript Files (${jsWithMatches.length} files)\n\n`;
|
|
428
|
+
content += `| File | Matches | Linked |\n`;
|
|
429
|
+
content += `|------|---------|--------|\n`;
|
|
430
|
+
jsWithMatches.forEach(result => {
|
|
431
|
+
const relPath = path.relative(projectDir, result.filePath);
|
|
432
|
+
const count = result.originalCount || result.matches.length;
|
|
433
|
+
const linked = result.isLinked ? '✅' : '❌';
|
|
434
|
+
content += `| \`${relPath}\` | ${count} refs | ${linked} |\n`;
|
|
435
|
+
});
|
|
436
|
+
content += '\n';
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return content;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Generate Shadow DOM section (add to generateMarkdown after CSS section)
|
|
444
|
+
*/
|
|
445
|
+
function generateShadowDOMSection(cssResults, projectDir) {
|
|
446
|
+
// Collect all Shadow DOM rules from all CSS results
|
|
447
|
+
const allShadowDOMRules = [];
|
|
448
|
+
|
|
449
|
+
for (const result of cssResults) {
|
|
450
|
+
if (result.shadowDOMRules && result.shadowDOMRules.length > 0) {
|
|
451
|
+
allShadowDOMRules.push({
|
|
452
|
+
file: result.filePath,
|
|
453
|
+
rules: result.shadowDOMRules
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (allShadowDOMRules.length === 0) {
|
|
459
|
+
return '';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let content = `## 🔮 Shadow DOM Styles\n\n`;
|
|
463
|
+
content += `> These styles target Web Component shadow DOM via \`::part()\` or \`::slotted()\`.\n`;
|
|
464
|
+
content += `> Shadow DOM provides style encapsulation for custom elements.\n\n`;
|
|
465
|
+
|
|
466
|
+
for (const { file, rules } of allShadowDOMRules) {
|
|
467
|
+
const relativePath = path.relative(projectDir, file);
|
|
468
|
+
content += `### From: \`${relativePath}\`\n\n`;
|
|
469
|
+
|
|
470
|
+
for (const rule of rules) {
|
|
471
|
+
if (rule.atRuleContext) {
|
|
472
|
+
content += `**Context:** \`${rule.atRuleContext}\`\n`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
content += `**Type:** \`${rule.shadowDOMType}\` | `;
|
|
476
|
+
content += `**Lines:** ${rule.startLine}-${rule.endLine} | `;
|
|
477
|
+
content += `**Matched on:** ${rule.matchedOn.join(', ')}\n\n`;
|
|
478
|
+
|
|
479
|
+
content += '```css\n' + rule.content + '\n```\n\n';
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
content += `> **Note:** Shadow DOM styles are isolated from global styles. `;
|
|
484
|
+
content += `\`::part()\` exposes specific shadow DOM elements for styling from outside.\n`;
|
|
485
|
+
|
|
486
|
+
return content;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Generate CSS Houdini section (add to generateMarkdown after variables section)
|
|
491
|
+
*/
|
|
492
|
+
function generateHoudiniSection(cssResults, projectDir) {
|
|
493
|
+
// Collect all Houdini properties
|
|
494
|
+
const allHoudiniProps = [];
|
|
495
|
+
|
|
496
|
+
for (const result of cssResults) {
|
|
497
|
+
if (result.houdiniProperties && result.houdiniProperties.length > 0) {
|
|
498
|
+
allHoudiniProps.push({
|
|
499
|
+
file: result.filePath,
|
|
500
|
+
properties: result.houdiniProperties
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (allHoudiniProps.length === 0) {
|
|
506
|
+
return '';
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
let content = `## 🎨 CSS Houdini Custom Properties\n\n`;
|
|
510
|
+
content += `> CSS Houdini \`@property\` rules define custom properties with type checking and default values.\n`;
|
|
511
|
+
content += `> These provide more control than standard CSS variables.\n\n`;
|
|
512
|
+
|
|
513
|
+
for (const { file, properties } of allHoudiniProps) {
|
|
514
|
+
const relativePath = path.relative(projectDir, file);
|
|
515
|
+
content += `### From: \`${relativePath}\`\n\n`;
|
|
516
|
+
|
|
517
|
+
for (const prop of properties) {
|
|
518
|
+
if (prop.atRuleContext) {
|
|
519
|
+
content += `**Context:** \`${prop.atRuleContext}\`\n`;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
content += `**Property:** \`${prop.propertyName}\` | `;
|
|
523
|
+
content += `**Lines:** ${prop.startLine}-${prop.endLine}\n\n`;
|
|
524
|
+
|
|
525
|
+
content += '```css\n' + prop.content + '\n```\n\n';
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
content += `> **Browser Support:** CSS Houdini has limited support. Check [caniuse.com](https://caniuse.com/css-properties-and-values-api) for compatibility.\n`;
|
|
530
|
+
|
|
531
|
+
return content;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Add to your summary section to track Shadow DOM and Houdini usage
|
|
536
|
+
*/
|
|
537
|
+
function updateSummaryForAdvancedFeatures(cssResults) {
|
|
538
|
+
const shadowDOMCount = cssResults.reduce((sum, r) =>
|
|
539
|
+
sum + (r.shadowDOMRules?.length || 0), 0);
|
|
540
|
+
|
|
541
|
+
const houdiniCount = cssResults.reduce((sum, r) =>
|
|
542
|
+
sum + (r.houdiniProperties?.length || 0), 0);
|
|
543
|
+
|
|
544
|
+
let summaryAddition = '';
|
|
545
|
+
|
|
546
|
+
if (shadowDOMCount > 0) {
|
|
547
|
+
summaryAddition += `| **Shadow DOM Rules** | ${shadowDOMCount} |\n`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (houdiniCount > 0) {
|
|
551
|
+
summaryAddition += `| **Houdini @property Rules** | ${houdiniCount} |\n`;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return summaryAddition;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
module.exports = {
|
|
558
|
+
generateMarkdown,
|
|
559
|
+
generateShadowDOMSection,
|
|
560
|
+
generateHoudiniSection,
|
|
561
|
+
updateSummaryForAdvancedFeatures
|
|
562
|
+
};
|