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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 html-scan contributors
|
|
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/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
|
|
2
|
+
# CodeScoop
|
|
3
|
+
|
|
4
|
+
> **Scoop the exact code you need for component debugging and migration.**
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/codescoop)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
**Stop guessing which CSS rules apply to your HTML. Stop copy-pasting 5,000 lines of code.**
|
|
10
|
+
|
|
11
|
+
CodeScoop is a forensic tool for your frontend. It extracts the dependency graph of a single component—HTML, specific CSS rules, JS events, and variables—so you can debug legacy sites or feed clean context to an LLM.
|
|
12
|
+
|
|
13
|
+
## The Problem
|
|
14
|
+
|
|
15
|
+
You are trying to fix a bug in a legacy WordPress header, or convert it to Next.js.
|
|
16
|
+
- **The Debugging Nightmare:** You see `class="btn"`, but where is the style coming from? `style.css`? `bootstrap.min.css`? An inline script?
|
|
17
|
+
- **The AI Nightmare:** You paste the HTML into ChatGPT. It hallucinates CSS that doesn't exist or misses the JS logic entirely because you didn't paste the right file.
|
|
18
|
+
|
|
19
|
+
## The Solution
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
codescoop index.html -s "header"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**One command. Complete context.**
|
|
26
|
+
|
|
27
|
+
CodeScoop analyzes your project and generates a forensic report containing:
|
|
28
|
+
|
|
29
|
+
* The exact HTML structure
|
|
30
|
+
* **The Winning CSS:** Specificity scoring shows you exactly which rule wins (and which are overridden)
|
|
31
|
+
* **Ghost Classes:** Identifies classes in HTML that have *no* matching CSS
|
|
32
|
+
* **JS Forensics:** Finds jQuery listeners and vanilla JS events targeting your elements
|
|
33
|
+
* **Variable Resolution:** Inlines values for `var(--primary)` so you don't need to hunt them down
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Key Features
|
|
38
|
+
|
|
39
|
+
### CSS Conflict Detection
|
|
40
|
+
|
|
41
|
+
Don't just list files. **Solve specificity wars.** CodeScoop calculates specificity scores for every matching rule so you can see exactly why your style isn't applying.
|
|
42
|
+
|
|
43
|
+
### Ghost Class Detection
|
|
44
|
+
|
|
45
|
+
Cleaning up legacy code? CodeScoop flags classes in your HTML that have **zero matching CSS rules** in your project. Delete them with confidence.
|
|
46
|
+
|
|
47
|
+
### Smart Extraction
|
|
48
|
+
|
|
49
|
+
It doesn't dump the whole file. It extracts *only* the rules that affect your specific component.
|
|
50
|
+
|
|
51
|
+
* *Result:* A 50-line context file instead of a 5,000-line dump.
|
|
52
|
+
|
|
53
|
+
### React Conversion Mode (`--for-conversion`)
|
|
54
|
+
|
|
55
|
+
If you *are* migrating, this flag analyzes JS patterns and suggests:
|
|
56
|
+
|
|
57
|
+
* State variables (`useState`)
|
|
58
|
+
* Event handlers to implement
|
|
59
|
+
* Animation libraries to install
|
|
60
|
+
|
|
61
|
+
### Works With Everything
|
|
62
|
+
|
|
63
|
+
Static HTML, **Live URLs**, PHP, Blade, Twig, EJS, ERB, Handlebars, JSP, ASP.
|
|
64
|
+
|
|
65
|
+
### Advanced Modern CSS Support
|
|
66
|
+
|
|
67
|
+
* **Shadow DOM:** Native support for `::part()` and `::slotted()` selectors allows you to analyze Web Components styling.
|
|
68
|
+
* **CSS Houdini:** Detects and reports custom properties defined with `@property`, preserving their type syntax and initial values.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Quick Start
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install -g codescoop
|
|
76
|
+
|
|
77
|
+
# 1. Debug a specific component
|
|
78
|
+
codescoop page.html -s ".navbar"
|
|
79
|
+
|
|
80
|
+
# 2. Analyze a live site (e.g., WordPress local dev)
|
|
81
|
+
codescoop http://localhost:8000 -s ".hero" --dir ./wp-content/themes/mytheme
|
|
82
|
+
|
|
83
|
+
# 3. Get a migration-ready bundle for AI
|
|
84
|
+
codescoop page.html -s ".card" --for-conversion
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Usage Scenarios
|
|
90
|
+
|
|
91
|
+
### 1. The "Why is this not working?" Audit
|
|
92
|
+
|
|
93
|
+
You have a button that refuses to turn blue.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
codescoop index.html -s ".btn-primary"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Output:**
|
|
100
|
+
|
|
101
|
+
```markdown
|
|
102
|
+
## CSS Conflict Analysis for `.btn-primary`
|
|
103
|
+
|
|
104
|
+
| Status | Specificity | File | Rule |
|
|
105
|
+
|--------|-------------|------|------|
|
|
106
|
+
| Winner | **(0, 2, 0)** | `theme.css:45` | `background: red !important;` |
|
|
107
|
+
| Overridden | **(0, 1, 0)** | `main.css:12` | `background: blue;` |
|
|
108
|
+
| Overridden | **(0, 0, 1)** | `reset.css:5` | `background: gray;` |
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
*Verdict: `theme.css` is overriding your change with `!important`.*
|
|
112
|
+
|
|
113
|
+
### 2. The Legacy Cleanup
|
|
114
|
+
|
|
115
|
+
You inherited a 5-year-old site. You want to know which classes are useless.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
codescoop old-page.html -s "main"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Output:**
|
|
122
|
+
|
|
123
|
+
```markdown
|
|
124
|
+
## Ghost Classes Detected
|
|
125
|
+
These classes appear in the HTML but have NO matching CSS in the project:
|
|
126
|
+
|
|
127
|
+
| Class | Location |
|
|
128
|
+
|-------|----------|
|
|
129
|
+
| `.clearfix-old` | `line 45` |
|
|
130
|
+
| `.legacy-wrapper-v2` | `line 102` |
|
|
131
|
+
| `.hidden-xs` | `line 15` |
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
*Action: Safe to delete these from your HTML.*
|
|
135
|
+
|
|
136
|
+
### 3. The "Perfect Context" for AI
|
|
137
|
+
|
|
138
|
+
You want to convert a widget to React.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
codescoop widget.php -s ".sidebar-widget" --for-conversion
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Output:**
|
|
145
|
+
|
|
146
|
+
* **Clean HTML:** JSX-ready structure
|
|
147
|
+
* **Scoped CSS:** Only the rules needed for this widget
|
|
148
|
+
* **JS Hints:** "Found `$('.widget').on('click')` -> Implement `onClick` handler"
|
|
149
|
+
* **Variables:** `var(--spacing)` resolved to `16px`
|
|
150
|
+
|
|
151
|
+
*Result: Paste this into Claude/GPT and get a working component on the first try.*
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## How It Works
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
159
|
+
│ Your Input │───▶│ CodeScoop │───▶│ Forensic Rep │
|
|
160
|
+
│ HTML/URL/PHP │ │ │ │ │
|
|
161
|
+
└─────────────────┘ │ ┌────────────┐ │ │ • Specificity │
|
|
162
|
+
│ │ Parse HTML │ │ │ • Ghost Classes│
|
|
163
|
+
┌─────────────────┐ │ ├────────────┤ │ │ • JS Events │
|
|
164
|
+
│ Project Files │───▶│ │ Find CSS │ │ │ • Variables │
|
|
165
|
+
│ CSS/SCSS/JS │ │ ├────────────┤ │ │ • Clean Code │
|
|
166
|
+
└─────────────────┘ │ │ Calc Score │ │ └─────────────────┘
|
|
167
|
+
│ ├────────────┤ │
|
|
168
|
+
│ │ Find Ghosts│ │
|
|
169
|
+
│ └────────────┘ │
|
|
170
|
+
└──────────────────┘
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## CLI Reference
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
codescoop <source> [options]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Option | Short | Description |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| `--selector <sel>` | `-s` | CSS selector to target |
|
|
184
|
+
| `--dir <path>` | `-d` | Project directory to scan (Required for live URLs) |
|
|
185
|
+
| `--for-conversion` | | Add React/Next.js migration hints |
|
|
186
|
+
| `--compact` | `-c` | Minimal output (hides long code blocks) |
|
|
187
|
+
| `--summary-only` | | Just list files and ghost classes |
|
|
188
|
+
| `--skip-minified` | | Exclude `*.min.css` / `*.min.js` |
|
|
189
|
+
| `--max-rules <n>` | | Max CSS rules per file (Default: 50) |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Why Not Just Copy-Paste?
|
|
194
|
+
|
|
195
|
+
| Feature | Manual Copy-Paste | CodeScoop |
|
|
196
|
+
| --- | --- | --- |
|
|
197
|
+
| **Context** | Partial (whatever you see) | Complete (hidden dependencies) |
|
|
198
|
+
| **CSS Conflicts** | Guesswork | **Specificity Scoring** |
|
|
199
|
+
| **Dead Code** | Hard to find | **Ghost Class Detection** |
|
|
200
|
+
| **Variables** | Undefined | Auto-resolved |
|
|
201
|
+
| **Time** | 15+ mins per component | < 10 seconds |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## How CodeScoop Handles Complexity (The "Black Holes")
|
|
208
|
+
|
|
209
|
+
Frontend analysis is hard. Here is how CodeScoop solves the common "black holes" that break other tools:
|
|
210
|
+
|
|
211
|
+
### 1. The "Dynamic Class" Trap (`.menu.is-open`)
|
|
212
|
+
* **Problem:** Classes like `.is-open` are often added by JS and missing from static HTML.
|
|
213
|
+
* **Solution:** CodeScoop uses **Greedy Prefix Matching**. If you target `.menu`, we automatically find `.menu.is-open`, `.menu:hover`, and `.menu::before`. We also filter common state classes (`is-*`, `has-*`) to prevent false alarms.
|
|
214
|
+
|
|
215
|
+
### 2. Nested SCSS (`&__element`)
|
|
216
|
+
* **Problem:** BEM syntax like `&__item` is invisible to standard regex searches.
|
|
217
|
+
* **Solution:** We implement **AST Resolution**. CodeScoop parses your SCSS, unwraps the `&` nesting, and resolves the actual selector (e.g., resolving `&__item` to `.block__item`) to ensure no rule is left behind.
|
|
218
|
+
|
|
219
|
+
### 3. Tailwind & Runtime CSS
|
|
220
|
+
* **Problem:** Utility classes (`p-4`, `flex`) clutter reports and look like "missing" styles if not built.
|
|
221
|
+
* **Solution:** Our **Smart Filter** distinguishes between likely utility classes (Tailwind/Bootstrap patterns) and your actual missing custom styles, keeping your "Ghost Class" report clean and actionable.
|
|
222
|
+
|
|
223
|
+
### 4. The Import Maze
|
|
224
|
+
* **Problem:** Styles buried 5 levels deep in `@import` chains are often missed.
|
|
225
|
+
* **Solution:** CodeScoop performs a **Deep Project Scan**, indexing every CSS/SCSS file in your directory to find relevant rules regardless of where they are imported.
|
|
226
|
+
|
|
227
|
+
### 5. Advanced Modern CSS
|
|
228
|
+
* **Problem:** Modern features like Shadow DOM (`::part`) and CSS Houdini (`@property`) are often ignored by traditional parsers.
|
|
229
|
+
* **Solution:** CodeScoop includes native support for these features, correctly identifying `::part()` and `::slotted()` rules and extracting structured `@property` definitions.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Contributing
|
|
234
|
+
|
|
235
|
+
Found a bug? Want a feature? PRs welcome!
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
git clone https://github.com/yourusername/codescoop.git
|
|
239
|
+
cd codescoop && npm install
|
|
240
|
+
node bin/codescoop.js test/sample.html -s ".navbar"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
<p align="center">
|
|
246
|
+
<b>Debug faster. Migrate smarter. Scoop exactly what you need.</b>
|
|
247
|
+
|
|
248
|
+
<code>npm install -g codescoop</code>
|
|
249
|
+
</p>
|
package/bin/codescoop.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CodeScoop CLI
|
|
5
|
+
* Extract component dependencies for AI-powered React/Next.js conversion
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { program } = require('commander');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const { runAnalysis } = require('../src/index');
|
|
13
|
+
const { runInteractiveMode } = require('../src/cli/interactive');
|
|
14
|
+
const {
|
|
15
|
+
validateHTMLFile,
|
|
16
|
+
validateOutputPath,
|
|
17
|
+
validateProjectDir,
|
|
18
|
+
sanitizeSelector,
|
|
19
|
+
formatError
|
|
20
|
+
} = require('../src/utils/validation');
|
|
21
|
+
const { fetchURL, isURL } = require('../src/utils/url-fetcher');
|
|
22
|
+
const { isTemplateFile, parseTemplateFile } = require('../src/utils/template-parser');
|
|
23
|
+
|
|
24
|
+
// ASCII Art Banner
|
|
25
|
+
const banner = `
|
|
26
|
+
${chalk.magenta('╔═══════════════════════════════════════════╗')}
|
|
27
|
+
${chalk.magenta('║')} ${chalk.bold.white('🍒 CodeScoop')} ${chalk.gray('- Scoop code for AI')} ${chalk.magenta('║')}
|
|
28
|
+
${chalk.magenta('╚═══════════════════════════════════════════╝')}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
program
|
|
32
|
+
.name('codescoop')
|
|
33
|
+
.description('Scoop out component dependencies for AI-powered conversion')
|
|
34
|
+
.version('1.0.0')
|
|
35
|
+
.argument('<source>', 'HTML file, URL, or template (.php, .blade.php, etc.)')
|
|
36
|
+
.option('-s, --selector <selector>', 'CSS selector to target (e.g., ".navbar", "#header")')
|
|
37
|
+
.option('-l, --lines <range>', 'Line range to target (e.g., "45-80")')
|
|
38
|
+
.option('-o, --output <path>', 'Output file path (default: <component>-analysis.md)')
|
|
39
|
+
.option('-d, --dir <path>', 'Project directory to scan (default: directory containing file)')
|
|
40
|
+
.option('-m, --match-index <n>', 'Which match to use if multiple elements found (0-based)', '0')
|
|
41
|
+
.option('-c, --compact', 'Compact mode: limit output size for LLM consumption')
|
|
42
|
+
.option('--for-conversion', 'Generate React/Next.js conversion context for LLMs')
|
|
43
|
+
.option('--max-rules <n>', 'Max CSS rules per file (default: 20 in compact mode)', '20')
|
|
44
|
+
.option('--max-js <n>', 'Max JS references per file (default: 10 in compact mode)', '10')
|
|
45
|
+
.option('--summary-only', 'Only show summary and file list, no code blocks')
|
|
46
|
+
.option('--skip-minified', 'Skip minified files (*.min.css, *.min.js)')
|
|
47
|
+
.option('--no-interactive', 'Skip interactive mode, require --selector or --lines')
|
|
48
|
+
.option('--include-inline', 'Include inline <style> and <script> blocks (default: true)', true)
|
|
49
|
+
.option('--verbose', 'Show detailed logging')
|
|
50
|
+
.action(async (source, options) => {
|
|
51
|
+
console.log(banner);
|
|
52
|
+
|
|
53
|
+
let htmlPath;
|
|
54
|
+
let htmlContent = null;
|
|
55
|
+
let projectDir;
|
|
56
|
+
let sourceType = 'file'; // file, url, or template
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// ============================================
|
|
60
|
+
// STEP 1: Detect source type and get HTML
|
|
61
|
+
// ============================================
|
|
62
|
+
|
|
63
|
+
if (isURL(source)) {
|
|
64
|
+
// URL Mode - fetch live page
|
|
65
|
+
sourceType = 'url';
|
|
66
|
+
console.log(chalk.cyan(`🌐 Fetching URL: ${source}`));
|
|
67
|
+
|
|
68
|
+
const result = await fetchURL(source);
|
|
69
|
+
if (result.statusCode !== 200) {
|
|
70
|
+
console.error(chalk.red(`✖ HTTP ${result.statusCode} - Failed to fetch URL`));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
htmlContent = result.html;
|
|
75
|
+
htmlPath = source;
|
|
76
|
+
console.log(chalk.green(`✓ Fetched ${(htmlContent.length / 1024).toFixed(1)}KB`));
|
|
77
|
+
|
|
78
|
+
// For URL mode, project dir must be specified
|
|
79
|
+
projectDir = options.dir ? path.resolve(options.dir) : null;
|
|
80
|
+
if (!projectDir) {
|
|
81
|
+
console.log(chalk.yellow(`⚠️ No --dir specified. Only inline styles/scripts will be analyzed.`));
|
|
82
|
+
console.log(chalk.gray(` Use --dir /path/to/project to scan local CSS/JS files.`));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
} else {
|
|
86
|
+
// File Mode - local file
|
|
87
|
+
htmlPath = path.resolve(source);
|
|
88
|
+
|
|
89
|
+
// Check file exists
|
|
90
|
+
if (!fs.existsSync(htmlPath)) {
|
|
91
|
+
console.error(chalk.red(`✖ File not found: ${htmlPath}`));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check if it's a template file
|
|
96
|
+
if (isTemplateFile(htmlPath)) {
|
|
97
|
+
sourceType = 'template';
|
|
98
|
+
console.log(chalk.cyan(`📄 Parsing template: ${path.basename(htmlPath)}`));
|
|
99
|
+
|
|
100
|
+
const parsed = parseTemplateFile(htmlPath);
|
|
101
|
+
htmlContent = parsed.html;
|
|
102
|
+
|
|
103
|
+
if (parsed.warnings.length > 0) {
|
|
104
|
+
parsed.warnings.forEach(w => console.warn(chalk.yellow(`⚠️ ${w}`)));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (parsed.dynamicClasses.length > 0) {
|
|
108
|
+
console.log(chalk.yellow(`⚠️ Found ${parsed.dynamicClasses.length} dynamic class attributes`));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(chalk.green(`✓ Extracted HTML from ${parsed.templateType} template`));
|
|
112
|
+
|
|
113
|
+
} else {
|
|
114
|
+
// Standard HTML file
|
|
115
|
+
sourceType = 'file';
|
|
116
|
+
const validation = validateHTMLFile(htmlPath);
|
|
117
|
+
|
|
118
|
+
validation.warnings.forEach(w => console.warn(chalk.yellow(`⚠️ ${w}`)));
|
|
119
|
+
|
|
120
|
+
if (!validation.valid) {
|
|
121
|
+
validation.errors.forEach(e => console.error(chalk.red(`✖ ${e}`)));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Set project directory
|
|
127
|
+
projectDir = options.dir ? path.resolve(options.dir) : path.dirname(htmlPath);
|
|
128
|
+
|
|
129
|
+
// Validate project directory
|
|
130
|
+
const dirValidation = validateProjectDir(projectDir);
|
|
131
|
+
dirValidation.warnings.forEach(w => console.warn(chalk.yellow(`⚠️ ${w}`)));
|
|
132
|
+
|
|
133
|
+
if (!dirValidation.valid) {
|
|
134
|
+
dirValidation.errors.forEach(e => console.error(chalk.red(`✖ ${e}`)));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ============================================
|
|
140
|
+
// STEP 2: Validate options
|
|
141
|
+
// ============================================
|
|
142
|
+
|
|
143
|
+
// Validate output path if provided
|
|
144
|
+
if (options.output) {
|
|
145
|
+
const outputValidation = validateOutputPath(options.output);
|
|
146
|
+
outputValidation.warnings.forEach(w => console.warn(chalk.yellow(`⚠️ ${w}`)));
|
|
147
|
+
|
|
148
|
+
if (!outputValidation.valid) {
|
|
149
|
+
outputValidation.errors.forEach(e => console.error(chalk.red(`✖ ${e}`)));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Validate selector if provided
|
|
155
|
+
if (options.selector) {
|
|
156
|
+
const selectorValidation = sanitizeSelector(options.selector);
|
|
157
|
+
if (!selectorValidation.valid) {
|
|
158
|
+
console.error(chalk.red(`✖ Invalid selector: ${selectorValidation.error}`));
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
options.selector = selectorValidation.selector;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Validate match index
|
|
165
|
+
const matchIndex = parseInt(options.matchIndex, 10);
|
|
166
|
+
if (isNaN(matchIndex) || matchIndex < 0) {
|
|
167
|
+
console.error(chalk.red(`✖ Invalid match-index: "${options.matchIndex}". Must be a non-negative number.`));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Verbose logging
|
|
172
|
+
if (options.verbose) {
|
|
173
|
+
console.log(chalk.gray(`Source type: ${sourceType}`));
|
|
174
|
+
console.log(chalk.gray(`Source: ${htmlPath}`));
|
|
175
|
+
if (projectDir) console.log(chalk.gray(`Project directory: ${projectDir}`));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ============================================
|
|
179
|
+
// STEP 3: Interactive mode if needed
|
|
180
|
+
// ============================================
|
|
181
|
+
|
|
182
|
+
if (!options.selector && !options.lines) {
|
|
183
|
+
if (options.interactive === false) {
|
|
184
|
+
console.error(chalk.red('Error: --selector or --lines required when --no-interactive is set'));
|
|
185
|
+
console.log(chalk.gray('\nExamples:'));
|
|
186
|
+
console.log(chalk.gray(' codescoop page.html -s ".navbar"'));
|
|
187
|
+
console.log(chalk.gray(' codescoop https://site.com --selector "header" --dir ./theme'));
|
|
188
|
+
console.log(chalk.gray(' codescoop template.php -s ".content"'));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Interactive mode only works for local files
|
|
193
|
+
if (sourceType === 'url') {
|
|
194
|
+
console.error(chalk.red('Error: Interactive mode not available for URLs. Use --selector.'));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log(chalk.yellow('No selector specified. Launching interactive mode...\n'));
|
|
199
|
+
const selection = await runInteractiveMode(htmlPath);
|
|
200
|
+
|
|
201
|
+
if (!selection) {
|
|
202
|
+
console.log(chalk.gray('No selection made. Exiting.'));
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
options.selector = selection.selector;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ============================================
|
|
210
|
+
// STEP 4: Run analysis
|
|
211
|
+
// ============================================
|
|
212
|
+
|
|
213
|
+
const result = await runAnalysis({
|
|
214
|
+
htmlPath,
|
|
215
|
+
htmlContent, // Pass pre-fetched content for URL/template modes
|
|
216
|
+
projectDir,
|
|
217
|
+
selector: options.selector,
|
|
218
|
+
lineRange: options.lines,
|
|
219
|
+
matchIndex: matchIndex,
|
|
220
|
+
outputPath: options.output,
|
|
221
|
+
includeInline: options.includeInline,
|
|
222
|
+
verbose: options.verbose,
|
|
223
|
+
sourceType,
|
|
224
|
+
// Compact mode options
|
|
225
|
+
compact: options.compact,
|
|
226
|
+
forConversion: options.forConversion,
|
|
227
|
+
maxRulesPerFile: parseInt(options.maxRules, 10) || 20,
|
|
228
|
+
maxJsPerFile: parseInt(options.maxJs, 10) || 10,
|
|
229
|
+
summaryOnly: options.summaryOnly,
|
|
230
|
+
skipMinified: options.skipMinified
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// ============================================
|
|
234
|
+
// STEP 5: Output results
|
|
235
|
+
// ============================================
|
|
236
|
+
|
|
237
|
+
console.log(chalk.green(`\n✓ Analysis complete!`));
|
|
238
|
+
console.log(chalk.white(` Output: ${result.outputPath}`));
|
|
239
|
+
console.log(chalk.gray(` Found ${result.cssMatches} CSS rules, ${result.jsMatches} JS references`));
|
|
240
|
+
|
|
241
|
+
if (result.libraryCount > 0) {
|
|
242
|
+
console.log(chalk.cyan(` Libraries detected: ${result.libraryCount}`));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (result.missingImports && result.missingImports.length > 0) {
|
|
246
|
+
console.log(chalk.yellow(`\n⚠ ${result.missingImports.length} files contain relevant code but are NOT imported:`));
|
|
247
|
+
result.missingImports.slice(0, 5).forEach(file => {
|
|
248
|
+
console.log(chalk.yellow(` - ${file}`));
|
|
249
|
+
});
|
|
250
|
+
if (result.missingImports.length > 5) {
|
|
251
|
+
console.log(chalk.yellow(` ... and ${result.missingImports.length - 5} more`));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error(chalk.red(`\n✖ ${formatError(error, options.verbose)}`));
|
|
257
|
+
if (options.verbose) {
|
|
258
|
+
console.error(chalk.gray(error.stack));
|
|
259
|
+
}
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Handle uncaught errors gracefully
|
|
265
|
+
process.on('uncaughtException', (error) => {
|
|
266
|
+
console.error(chalk.red(`\n✖ Unexpected error: ${error.message}`));
|
|
267
|
+
console.error(chalk.gray('This is likely a bug. Please report it.'));
|
|
268
|
+
process.exit(1);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
process.on('unhandledRejection', (reason) => {
|
|
272
|
+
console.error(chalk.red(`\n✖ Unhandled promise rejection: ${reason}`));
|
|
273
|
+
process.exit(1);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codescoop",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Extract HTML component dependencies for AI-powered React/Next.js conversion",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codescoop": "./bin/codescoop.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/codescoop.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"html",
|
|
14
|
+
"css",
|
|
15
|
+
"javascript",
|
|
16
|
+
"react",
|
|
17
|
+
"nextjs",
|
|
18
|
+
"conversion",
|
|
19
|
+
"migration",
|
|
20
|
+
"jquery",
|
|
21
|
+
"wordpress",
|
|
22
|
+
"magento",
|
|
23
|
+
"component",
|
|
24
|
+
"extractor",
|
|
25
|
+
"analyzer",
|
|
26
|
+
"cli",
|
|
27
|
+
"llm",
|
|
28
|
+
"ai",
|
|
29
|
+
"gpt",
|
|
30
|
+
"claude",
|
|
31
|
+
"chatgpt",
|
|
32
|
+
"openai",
|
|
33
|
+
"anthropic",
|
|
34
|
+
"debugging",
|
|
35
|
+
"scss",
|
|
36
|
+
"dependencies",
|
|
37
|
+
"php",
|
|
38
|
+
"blade",
|
|
39
|
+
"twig"
|
|
40
|
+
],
|
|
41
|
+
"author": "",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/yourusername/codescoop.git"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/yourusername/codescoop/issues"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/yourusername/codescoop#readme",
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=16.0.0"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"bin/",
|
|
56
|
+
"src/",
|
|
57
|
+
"README.md",
|
|
58
|
+
"LICENSE"
|
|
59
|
+
],
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"acorn": "^8.11.3",
|
|
62
|
+
"acorn-loose": "^8.4.0",
|
|
63
|
+
"acorn-walk": "^8.3.2",
|
|
64
|
+
"chalk": "^4.1.2",
|
|
65
|
+
"cheerio": "^1.0.0-rc.12",
|
|
66
|
+
"commander": "^12.0.0",
|
|
67
|
+
"glob": "^10.3.10",
|
|
68
|
+
"inquirer": "^8.2.6",
|
|
69
|
+
"js-beautify": "^1.14.11",
|
|
70
|
+
"postcss": "^8.4.35",
|
|
71
|
+
"postcss-scss": "^4.0.9",
|
|
72
|
+
"sass": "^1.71.1",
|
|
73
|
+
"specificity": "^1.0.0"
|
|
74
|
+
}
|
|
75
|
+
}
|