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,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ghost Classes Detector
|
|
3
|
+
* Finds classes in HTML that have no matching CSS rules anywhere in the project
|
|
4
|
+
*
|
|
5
|
+
* UPDATES:
|
|
6
|
+
* - Enhanced documentation
|
|
7
|
+
* - Better handling of modern CSS frameworks
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect ghost classes (classes with no CSS definitions)
|
|
12
|
+
* @param {Object} targetInfo - Target element info with classes
|
|
13
|
+
* @param {Array} cssResults - CSS analysis results
|
|
14
|
+
* @param {Array} cssLibraryResults - Library CSS results
|
|
15
|
+
* @param {Array} inlineStyles - Inline style results
|
|
16
|
+
* @returns {Object} Ghost class detection results
|
|
17
|
+
*/
|
|
18
|
+
function detectGhostClasses(targetInfo, cssResults = [], cssLibraryResults = [], inlineStyles = []) {
|
|
19
|
+
const { classes = [] } = targetInfo;
|
|
20
|
+
|
|
21
|
+
if (classes.length === 0) {
|
|
22
|
+
return {
|
|
23
|
+
ghostClasses: [],
|
|
24
|
+
definedClasses: [],
|
|
25
|
+
totalClasses: 0
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Collect all classes that have CSS definitions
|
|
30
|
+
const definedClasses = new Set();
|
|
31
|
+
|
|
32
|
+
// Helper function to process matchedOn (can be string or array)
|
|
33
|
+
const processMatchedOn = (matchedOn) => {
|
|
34
|
+
const items = Array.isArray(matchedOn) ? matchedOn : [matchedOn];
|
|
35
|
+
for (const item of items) {
|
|
36
|
+
if (typeof item === 'string' && item.startsWith('class:')) {
|
|
37
|
+
definedClasses.add('.' + item.replace('class: ', '').replace('class:', '').trim());
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Check custom CSS results
|
|
43
|
+
for (const result of cssResults) {
|
|
44
|
+
for (const match of result.matches || []) {
|
|
45
|
+
// Extract class name from match info
|
|
46
|
+
processMatchedOn(match.matchedOn);
|
|
47
|
+
|
|
48
|
+
// Also check the selector directly (handles :is(), :where(), etc.)
|
|
49
|
+
const selector = match.selector || '';
|
|
50
|
+
for (const cls of classes) {
|
|
51
|
+
if (selector.includes(cls)) {
|
|
52
|
+
definedClasses.add(cls);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check library CSS results
|
|
59
|
+
for (const result of cssLibraryResults) {
|
|
60
|
+
for (const match of result.matches || []) {
|
|
61
|
+
processMatchedOn(match.matchedOn);
|
|
62
|
+
|
|
63
|
+
const selector = match.selector || '';
|
|
64
|
+
for (const cls of classes) {
|
|
65
|
+
if (selector.includes(cls)) {
|
|
66
|
+
definedClasses.add(cls);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check inline styles (less common to match classes, but check anyway)
|
|
73
|
+
for (const style of inlineStyles) {
|
|
74
|
+
const content = style.content || '';
|
|
75
|
+
for (const cls of classes) {
|
|
76
|
+
if (content.includes(cls)) {
|
|
77
|
+
definedClasses.add(cls);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Common utility patterns to ignore (Tailwind, Bootstrap, State classes)
|
|
83
|
+
const IGNORED_PATTERNS = [
|
|
84
|
+
// Tailwind / Utility patterns
|
|
85
|
+
/^(p|m)[xytrbl]?-\d+/, // Spacing: p-4, my-2, mt-10
|
|
86
|
+
/^text-(xs|sm|base|lg|xl|\d+xl)/, // Typo sizes
|
|
87
|
+
/^text-(black|white|gray|red|blue|green|yellow|indigo|purple|pink)/, // Colors
|
|
88
|
+
/^bg-(black|white|gray|red|blue|green|yellow|indigo|purple|pink)/, // Backgrounds
|
|
89
|
+
/^font-(sans|serif|mono|bold|medium|light)/, // Fonts
|
|
90
|
+
/^flex/, /^grid/, /^block/, /^hidden/, /^inline/, // Layout
|
|
91
|
+
/^w-\d+/, /^h-\d+/, /^w-full/, /^h-full/, /^min-w/, /^max-w/, // Sizing
|
|
92
|
+
/^border/, /^rounded/, /^shadow/, // Decorations
|
|
93
|
+
/^items-/, /^justify-/, /^place-/, /^gap-/, // Flex/Grid alignment
|
|
94
|
+
/^absolute/, /^relative/, /^fixed/, /^sticky/, // Position
|
|
95
|
+
/^top-/, /^bottom-/, /^left-/, /^right-/, /^z-/, // Positioning
|
|
96
|
+
/^opacity-/, /^cursor-/, /^pointer-events-/, // Interaction
|
|
97
|
+
/^transition/, /^duration-/, /^ease-/, // Transitions
|
|
98
|
+
/^hover:/, /^focus:/, /^active:/, /^group-hover:/, // State modifiers
|
|
99
|
+
/^sm:/, /^md:/, /^lg:/, /^xl:/, /^2xl:/, // Responsive modifiers
|
|
100
|
+
|
|
101
|
+
// Bootstrap patterns
|
|
102
|
+
/^col-/, /^row/, /^container/, /^btn-/, /^alert-/, /^card-/,
|
|
103
|
+
/^navbar-/, /^nav-/, /^dropdown-/, /^modal-/, /^tab-/, /^form-/,
|
|
104
|
+
|
|
105
|
+
// Icons
|
|
106
|
+
/^fa-/, /^icon-/, /^material-/, /^bi-/, /^ri-/, /^bx-/,
|
|
107
|
+
|
|
108
|
+
// State hooks & JS hooks (often not defined in CSS directly but added by JS)
|
|
109
|
+
/^js-/, /^is-/, /^has-/, /^active/, /^open/, /^show/, /^visible/,
|
|
110
|
+
|
|
111
|
+
// Animation libraries
|
|
112
|
+
/^animate__/, /^aos-/, /^fade/, /^slide/, /^zoom/,
|
|
113
|
+
|
|
114
|
+
// Utility libraries
|
|
115
|
+
/^u-/, /^util-/, /^helper-/
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
// Find ghost classes (in HTML but not in any CSS)
|
|
119
|
+
const ghostClasses = classes.filter(cls => {
|
|
120
|
+
const className = cls.startsWith('.') ? cls : '.' + cls;
|
|
121
|
+
const cleanName = cls.startsWith('.') ? cls.substring(1) : cls;
|
|
122
|
+
|
|
123
|
+
// Check if it exists in CSS
|
|
124
|
+
if (definedClasses.has(className) || definedClasses.has(cls)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check against allowlist (ignore utilities)
|
|
129
|
+
const isUtility = IGNORED_PATTERNS.some(pattern => pattern.test(cleanName));
|
|
130
|
+
if (isUtility) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
ghostClasses,
|
|
139
|
+
definedClasses: Array.from(definedClasses),
|
|
140
|
+
totalClasses: classes.length,
|
|
141
|
+
hasGhosts: ghostClasses.length > 0
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Format ghost classes as markdown section
|
|
147
|
+
* @param {Object} ghostData - Ghost detection results
|
|
148
|
+
* @returns {string} Markdown section
|
|
149
|
+
*/
|
|
150
|
+
function formatGhostClassesMarkdown(ghostData) {
|
|
151
|
+
if (!ghostData.hasGhosts) {
|
|
152
|
+
return '';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let md = `\n---\n\n## 👻 Ghost Classes Detected\n\n`;
|
|
156
|
+
md += `> These classes are used in the HTML but have **no matching CSS rules** in the project.\n`;
|
|
157
|
+
md += `> This may indicate dead code, missing stylesheets, or dynamically applied styles.\n\n`;
|
|
158
|
+
|
|
159
|
+
md += `| Class | Status |\n`;
|
|
160
|
+
md += `|-------|--------|\n`;
|
|
161
|
+
|
|
162
|
+
for (const ghost of ghostData.ghostClasses) {
|
|
163
|
+
md += `| \`${ghost}\` | ⚠️ No CSS found |\n`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
md += `\n**${ghostData.ghostClasses.length}** ghost class(es) out of **${ghostData.totalClasses}** total.\n`;
|
|
167
|
+
|
|
168
|
+
return md;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
detectGhostClasses,
|
|
173
|
+
formatGhostClassesMarkdown
|
|
174
|
+
};
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library Detector Module
|
|
3
|
+
* Identifies common frontend libraries and frameworks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Known libraries and their detection patterns
|
|
10
|
+
*/
|
|
11
|
+
const KNOWN_LIBRARIES = {
|
|
12
|
+
// CSS Frameworks
|
|
13
|
+
'Bootstrap': {
|
|
14
|
+
type: 'css-framework',
|
|
15
|
+
patterns: [/bootstrap/i],
|
|
16
|
+
cdnPatterns: [/cdn.*bootstrap/i, /bootstrapcdn/i],
|
|
17
|
+
classPatterns: [/^btn-/, /^col-/, /^row$/, /^container/, /^navbar-/, /^modal-/, /^carousel-/],
|
|
18
|
+
website: 'https://getbootstrap.com'
|
|
19
|
+
},
|
|
20
|
+
'Tailwind CSS': {
|
|
21
|
+
type: 'css-framework',
|
|
22
|
+
patterns: [/tailwind/i],
|
|
23
|
+
classPatterns: [/^(flex|grid|p-|m-|text-|bg-|w-|h-)/],
|
|
24
|
+
website: 'https://tailwindcss.com'
|
|
25
|
+
},
|
|
26
|
+
'Foundation': {
|
|
27
|
+
type: 'css-framework',
|
|
28
|
+
patterns: [/foundation/i],
|
|
29
|
+
website: 'https://get.foundation'
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// JS Libraries
|
|
33
|
+
'jQuery': {
|
|
34
|
+
type: 'js-library',
|
|
35
|
+
patterns: [/jquery/i],
|
|
36
|
+
cdnPatterns: [/code\.jquery\.com/i, /cdn.*jquery/i],
|
|
37
|
+
codePatterns: [/\$\s*\(/, /jQuery\s*\(/],
|
|
38
|
+
website: 'https://jquery.com'
|
|
39
|
+
},
|
|
40
|
+
'GSAP': {
|
|
41
|
+
type: 'js-library',
|
|
42
|
+
patterns: [/gsap/i, /greensock/i],
|
|
43
|
+
codePatterns: [/gsap\./i, /TweenMax/i, /TweenLite/i, /TimelineMax/i],
|
|
44
|
+
website: 'https://greensock.com/gsap'
|
|
45
|
+
},
|
|
46
|
+
'ScrollMagic': {
|
|
47
|
+
type: 'js-library',
|
|
48
|
+
patterns: [/scrollmagic/i],
|
|
49
|
+
codePatterns: [/ScrollMagic/i],
|
|
50
|
+
website: 'https://scrollmagic.io'
|
|
51
|
+
},
|
|
52
|
+
'Swiper': {
|
|
53
|
+
type: 'js-library',
|
|
54
|
+
patterns: [/swiper/i],
|
|
55
|
+
classPatterns: [/^swiper-/],
|
|
56
|
+
codePatterns: [/new Swiper/i],
|
|
57
|
+
website: 'https://swiperjs.com'
|
|
58
|
+
},
|
|
59
|
+
'Isotope': {
|
|
60
|
+
type: 'js-library',
|
|
61
|
+
patterns: [/isotope/i, /isotop/i],
|
|
62
|
+
codePatterns: [/\.isotope\(/i],
|
|
63
|
+
website: 'https://isotope.metafizzy.co'
|
|
64
|
+
},
|
|
65
|
+
'Masonry': {
|
|
66
|
+
type: 'js-library',
|
|
67
|
+
patterns: [/masonry/i],
|
|
68
|
+
codePatterns: [/\.masonry\(/i, /new Masonry/i],
|
|
69
|
+
website: 'https://masonry.desandro.com'
|
|
70
|
+
},
|
|
71
|
+
'AOS': {
|
|
72
|
+
type: 'js-library',
|
|
73
|
+
patterns: [/aos\.js/i, /aos\.css/i],
|
|
74
|
+
classPatterns: [/^aos-/],
|
|
75
|
+
codePatterns: [/AOS\.init/i],
|
|
76
|
+
website: 'https://michalsnik.github.io/aos'
|
|
77
|
+
},
|
|
78
|
+
'Animate.css': {
|
|
79
|
+
type: 'css-library',
|
|
80
|
+
patterns: [/animate\.css/i, /animate\.min\.css/i],
|
|
81
|
+
classPatterns: [/^animate__/, /^animated$/],
|
|
82
|
+
website: 'https://animate.style'
|
|
83
|
+
},
|
|
84
|
+
'Locomotive Scroll': {
|
|
85
|
+
type: 'js-library',
|
|
86
|
+
patterns: [/locomotive/i],
|
|
87
|
+
codePatterns: [/LocomotiveScroll/i],
|
|
88
|
+
website: 'https://locomotivemtl.github.io/locomotive-scroll'
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// Icon Libraries
|
|
92
|
+
'Font Awesome': {
|
|
93
|
+
type: 'icon-library',
|
|
94
|
+
patterns: [/fontawesome/i, /font-awesome/i],
|
|
95
|
+
classPatterns: [/^fa-/, /^fas$/, /^fab$/, /^far$/, /^fal$/],
|
|
96
|
+
website: 'https://fontawesome.com'
|
|
97
|
+
},
|
|
98
|
+
'Material Icons': {
|
|
99
|
+
type: 'icon-library',
|
|
100
|
+
patterns: [/material-icons/i, /material-design-icons/i],
|
|
101
|
+
classPatterns: [/^material-icons/],
|
|
102
|
+
website: 'https://fonts.google.com/icons'
|
|
103
|
+
},
|
|
104
|
+
'Boxicons': {
|
|
105
|
+
type: 'icon-library',
|
|
106
|
+
patterns: [/boxicons/i],
|
|
107
|
+
classPatterns: [/^bx-/, /^bx$/],
|
|
108
|
+
website: 'https://boxicons.com'
|
|
109
|
+
},
|
|
110
|
+
'Remix Icon': {
|
|
111
|
+
type: 'icon-library',
|
|
112
|
+
patterns: [/remixicon/i],
|
|
113
|
+
classPatterns: [/^ri-/],
|
|
114
|
+
website: 'https://remixicon.com'
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Utility Libraries
|
|
118
|
+
'Lodash': {
|
|
119
|
+
type: 'js-library',
|
|
120
|
+
patterns: [/lodash/i],
|
|
121
|
+
codePatterns: [/_\.\w+\(/],
|
|
122
|
+
website: 'https://lodash.com'
|
|
123
|
+
},
|
|
124
|
+
'Axios': {
|
|
125
|
+
type: 'js-library',
|
|
126
|
+
patterns: [/axios/i],
|
|
127
|
+
codePatterns: [/axios\.(get|post|put|delete)/i],
|
|
128
|
+
website: 'https://axios-http.com'
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// Image/Media
|
|
132
|
+
'Lightbox': {
|
|
133
|
+
type: 'js-library',
|
|
134
|
+
patterns: [/lightbox/i],
|
|
135
|
+
codePatterns: [/lightbox/i],
|
|
136
|
+
website: 'https://lokeshdhakar.com/projects/lightbox2'
|
|
137
|
+
},
|
|
138
|
+
'Fancybox': {
|
|
139
|
+
type: 'js-library',
|
|
140
|
+
patterns: [/fancybox/i],
|
|
141
|
+
codePatterns: [/fancybox/i],
|
|
142
|
+
website: 'https://fancyapps.com/fancybox'
|
|
143
|
+
},
|
|
144
|
+
'ImagesLoaded': {
|
|
145
|
+
type: 'js-library',
|
|
146
|
+
patterns: [/imagesloaded/i],
|
|
147
|
+
codePatterns: [/imagesLoaded/i],
|
|
148
|
+
website: 'https://imagesloaded.desandro.com'
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
// Form Libraries
|
|
152
|
+
'Select2': {
|
|
153
|
+
type: 'js-library',
|
|
154
|
+
patterns: [/select2/i],
|
|
155
|
+
classPatterns: [/^select2-/],
|
|
156
|
+
codePatterns: [/\.select2\(/i],
|
|
157
|
+
website: 'https://select2.org'
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Charts
|
|
161
|
+
'Chart.js': {
|
|
162
|
+
type: 'js-library',
|
|
163
|
+
patterns: [/chart\.js/i, /chartjs/i],
|
|
164
|
+
codePatterns: [/new Chart/i],
|
|
165
|
+
website: 'https://www.chartjs.org'
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// Scroll Libraries
|
|
169
|
+
'Smooth Scroll': {
|
|
170
|
+
type: 'js-library',
|
|
171
|
+
patterns: [/smooth-scroll/i, /smoothscroll/i],
|
|
172
|
+
codePatterns: [/SmoothScroll/i],
|
|
173
|
+
website: ''
|
|
174
|
+
},
|
|
175
|
+
'ScrollTrigger': {
|
|
176
|
+
type: 'js-library',
|
|
177
|
+
patterns: [/scrolltrigger/i, /scrolltiger/i],
|
|
178
|
+
codePatterns: [/ScrollTrigger/i],
|
|
179
|
+
website: 'https://greensock.com/scrolltrigger'
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Detect libraries from file paths
|
|
185
|
+
* @param {string[]} filePaths - Array of file paths
|
|
186
|
+
* @returns {Object} Detected libraries with their import status
|
|
187
|
+
*/
|
|
188
|
+
function detectLibrariesFromPaths(filePaths) {
|
|
189
|
+
const detected = {};
|
|
190
|
+
|
|
191
|
+
for (const filePath of filePaths) {
|
|
192
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
193
|
+
const fullPath = filePath.toLowerCase();
|
|
194
|
+
|
|
195
|
+
for (const [libName, libInfo] of Object.entries(KNOWN_LIBRARIES)) {
|
|
196
|
+
// Check file path patterns
|
|
197
|
+
const matchesPattern = libInfo.patterns?.some(pattern =>
|
|
198
|
+
pattern.test(fileName) || pattern.test(fullPath)
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (matchesPattern) {
|
|
202
|
+
if (!detected[libName]) {
|
|
203
|
+
detected[libName] = {
|
|
204
|
+
...libInfo,
|
|
205
|
+
name: libName,
|
|
206
|
+
files: [],
|
|
207
|
+
importedFiles: [],
|
|
208
|
+
missingFiles: []
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
detected[libName].files.push(filePath);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return detected;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Detect libraries from HTML content (CDN links)
|
|
221
|
+
* @param {CheerioAPI} $ - Cheerio instance
|
|
222
|
+
* @returns {Object} Libraries loaded via CDN
|
|
223
|
+
*/
|
|
224
|
+
function detectLibrariesFromHTML($) {
|
|
225
|
+
const cdnLibraries = [];
|
|
226
|
+
|
|
227
|
+
// Check link tags
|
|
228
|
+
$('link[href]').each((_, el) => {
|
|
229
|
+
const href = $(el).attr('href') || '';
|
|
230
|
+
for (const [libName, libInfo] of Object.entries(KNOWN_LIBRARIES)) {
|
|
231
|
+
const matchesCDN = libInfo.cdnPatterns?.some(pattern => pattern.test(href));
|
|
232
|
+
const matchesPattern = libInfo.patterns?.some(pattern => pattern.test(href));
|
|
233
|
+
|
|
234
|
+
if (matchesCDN || (matchesPattern && href.includes('cdn'))) {
|
|
235
|
+
cdnLibraries.push({
|
|
236
|
+
name: libName,
|
|
237
|
+
type: libInfo.type,
|
|
238
|
+
source: 'cdn',
|
|
239
|
+
url: href,
|
|
240
|
+
website: libInfo.website
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Check script tags
|
|
247
|
+
$('script[src]').each((_, el) => {
|
|
248
|
+
const src = $(el).attr('src') || '';
|
|
249
|
+
for (const [libName, libInfo] of Object.entries(KNOWN_LIBRARIES)) {
|
|
250
|
+
const matchesCDN = libInfo.cdnPatterns?.some(pattern => pattern.test(src));
|
|
251
|
+
const matchesPattern = libInfo.patterns?.some(pattern => pattern.test(src));
|
|
252
|
+
|
|
253
|
+
if (matchesCDN || (matchesPattern && src.includes('cdn'))) {
|
|
254
|
+
cdnLibraries.push({
|
|
255
|
+
name: libName,
|
|
256
|
+
type: libInfo.type,
|
|
257
|
+
source: 'cdn',
|
|
258
|
+
url: src,
|
|
259
|
+
website: libInfo.website
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return cdnLibraries;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Detect libraries from class names in HTML
|
|
270
|
+
* @param {string[]} classes - Array of class names
|
|
271
|
+
* @returns {string[]} Library names detected from classes
|
|
272
|
+
*/
|
|
273
|
+
function detectLibrariesFromClasses(classes) {
|
|
274
|
+
const detected = new Set();
|
|
275
|
+
|
|
276
|
+
for (const className of classes) {
|
|
277
|
+
for (const [libName, libInfo] of Object.entries(KNOWN_LIBRARIES)) {
|
|
278
|
+
if (libInfo.classPatterns) {
|
|
279
|
+
const matches = libInfo.classPatterns.some(pattern => pattern.test(className));
|
|
280
|
+
if (matches) {
|
|
281
|
+
detected.add(libName);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return Array.from(detected);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Check if a file is a library file
|
|
292
|
+
* @param {string} filePath - Path to the file
|
|
293
|
+
* @returns {Object|null} Library info if it's a library file
|
|
294
|
+
*/
|
|
295
|
+
function isLibraryFile(filePath) {
|
|
296
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
297
|
+
const fullPath = filePath.toLowerCase();
|
|
298
|
+
|
|
299
|
+
for (const [libName, libInfo] of Object.entries(KNOWN_LIBRARIES)) {
|
|
300
|
+
const matchesPattern = libInfo.patterns?.some(pattern =>
|
|
301
|
+
pattern.test(fileName) || pattern.test(fullPath)
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
if (matchesPattern) {
|
|
305
|
+
return { name: libName, ...libInfo };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Also check for common library indicators
|
|
310
|
+
if (fileName.includes('.min.') &&
|
|
311
|
+
(fileName.includes('plugin') ||
|
|
312
|
+
fullPath.includes('/plugins/') ||
|
|
313
|
+
fullPath.includes('/vendor/') ||
|
|
314
|
+
fullPath.includes('/lib/'))) {
|
|
315
|
+
return { name: 'Unknown Plugin', type: 'plugin' };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get all known library names
|
|
323
|
+
*/
|
|
324
|
+
function getKnownLibraryNames() {
|
|
325
|
+
return Object.keys(KNOWN_LIBRARIES);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = {
|
|
329
|
+
KNOWN_LIBRARIES,
|
|
330
|
+
detectLibrariesFromPaths,
|
|
331
|
+
detectLibrariesFromHTML,
|
|
332
|
+
detectLibrariesFromClasses,
|
|
333
|
+
isLibraryFile,
|
|
334
|
+
getKnownLibraryNames
|
|
335
|
+
};
|