scss-variable-extractor 1.4.0 → 1.5.1
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/README.md +44 -0
- package/bin/cli.js +7 -27
- package/package.json +1 -1
- package/src/bootstrap-migrator.js +53 -21
- package/src/scanner.js +26 -1
- package/test/bootstrap-migrator.test.js +38 -0
- package/test/scanner.test.js +8 -1
package/README.md
CHANGED
|
@@ -701,6 +701,50 @@ MIT License - see [LICENSE](./LICENSE) file for details
|
|
|
701
701
|
|
|
702
702
|
## Changelog
|
|
703
703
|
|
|
704
|
+
### 1.5.1 (2026-02-12)
|
|
705
|
+
|
|
706
|
+
- **Enhanced:** HTML class detection now handles spaces around `=` (e.g., `class = "py-3"`)
|
|
707
|
+
- **Added:** Support for backtick quotes in class attributes (e.g., `class=\`py-3\``)
|
|
708
|
+
- **Added:** Support for Angular property binding (e.g., `[class]="py-3"`)
|
|
709
|
+
- **Improved:** More robust Bootstrap class detection across different HTML/template formats
|
|
710
|
+
|
|
711
|
+
### 1.5.0 (2026-02-12)
|
|
712
|
+
|
|
713
|
+
- **Fixed:** Bootstrap detection now scans SCSS class selectors (`.py-3`, `.mt-4`, etc.)
|
|
714
|
+
- **Enhanced:** Bootstrap commands now scan SCSS, HTML, and TypeScript files
|
|
715
|
+
- **Added:** New `scanTemplateFiles` function for comprehensive file scanning
|
|
716
|
+
- **Improved:** Bootstrap class detection with deduplication
|
|
717
|
+
|
|
718
|
+
### 1.4.0 (2026-02-12)
|
|
719
|
+
|
|
720
|
+
- **Added:** Bootstrap to Angular Material migration capability
|
|
721
|
+
- **Added:** `detect-bootstrap` command for analyzing Bootstrap usage
|
|
722
|
+
- **Added:** `migrate-bootstrap` command for converting Bootstrap classes
|
|
723
|
+
- **Added:** Custom utilities generation for flex/grid/spacing classes
|
|
724
|
+
- **Added:** Material Design spacing scale (0, 4px, 8px, 16px, 24px, 48px)
|
|
725
|
+
- 100+ Bootstrap class mappings
|
|
726
|
+
|
|
727
|
+
### 1.3.0 (2026-02-12)
|
|
728
|
+
|
|
729
|
+
- **Added:** Angular.json integration for automatic project configuration
|
|
730
|
+
- **Added:** Auto-detection of source directory and style preprocessor
|
|
731
|
+
- **Added:** Support for multi-project Angular workspaces
|
|
732
|
+
- All commands support `--angular-json`, `--project`, `--no-angular` options
|
|
733
|
+
|
|
734
|
+
### 1.2.0 (2026-02-12)
|
|
735
|
+
|
|
736
|
+
- **Added:** Angular Material v15+ pattern refactoring
|
|
737
|
+
- **Added:** `analyze-patterns` command to detect anti-patterns
|
|
738
|
+
- **Added:** `modernize` command to remove `::ng-deep` and `!important`
|
|
739
|
+
- Best practice enforcement with migration suggestions
|
|
740
|
+
|
|
741
|
+
### 1.1.0 (2026-02-12)
|
|
742
|
+
|
|
743
|
+
- **Added:** Global installation support via npm link/publish
|
|
744
|
+
- **Added:** Positional `[src]` argument for all commands
|
|
745
|
+
- **Enhanced:** Scan any folder in your project
|
|
746
|
+
- **Improved:** CLI help and documentation
|
|
747
|
+
|
|
704
748
|
### 1.0.0 (2026-02-12)
|
|
705
749
|
|
|
706
750
|
- Initial release
|
package/bin/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ const { Command } = require('commander');
|
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { loadConfig } = require('../src/config');
|
|
7
|
-
const { scanScssFiles } = require('../src/scanner');
|
|
7
|
+
const { scanScssFiles, scanTemplateFiles } = require('../src/scanner');
|
|
8
8
|
const { parseScss } = require('../src/parser');
|
|
9
9
|
const { analyzeValues } = require('../src/analyzer');
|
|
10
10
|
const { generateVariablesFile, generateReport } = require('../src/generator');
|
|
@@ -448,19 +448,9 @@ program
|
|
|
448
448
|
|
|
449
449
|
console.log(chalk.gray(`Scanning: ${config.src}\n`));
|
|
450
450
|
|
|
451
|
-
// Scan files - look for HTML and
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
const glob = require('glob');
|
|
455
|
-
const htmlFiles = await new Promise((resolve, reject) => {
|
|
456
|
-
glob(htmlPattern, { ignore: config.ignore }, (err, files) => {
|
|
457
|
-
if (err) reject(err);
|
|
458
|
-
else resolve(files.map(f => path.resolve(f)));
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
const allFiles = [...scssFiles, ...htmlFiles];
|
|
463
|
-
console.log(chalk.green(`✓ Found ${scssFiles.length} SCSS and ${htmlFiles.length} HTML files\n`));
|
|
451
|
+
// Scan files - look for SCSS, HTML, and TS files
|
|
452
|
+
const allFiles = await scanTemplateFiles(config.src, config.ignore);
|
|
453
|
+
console.log(chalk.green(`✓ Found ${allFiles.length} files (SCSS, HTML, TS)\n`));
|
|
464
454
|
|
|
465
455
|
// Detect Bootstrap
|
|
466
456
|
const detection = detectBootstrap(allFiles);
|
|
@@ -524,19 +514,9 @@ program
|
|
|
524
514
|
console.log(chalk.gray(`Scanning: ${config.src}`));
|
|
525
515
|
console.log(chalk.gray(`Utilities output: ${options.utilities}\n`));
|
|
526
516
|
|
|
527
|
-
// Scan files
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
const glob = require('glob');
|
|
531
|
-
const htmlFiles = await new Promise((resolve, reject) => {
|
|
532
|
-
glob(htmlPattern, { ignore: config.ignore }, (err, files) => {
|
|
533
|
-
if (err) reject(err);
|
|
534
|
-
else resolve(files.map(f => path.resolve(f)));
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
const allFiles = [...scssFiles, ...htmlFiles];
|
|
539
|
-
console.log(chalk.green(`✓ Found ${scssFiles.length} SCSS and ${htmlFiles.length} HTML files\n`));
|
|
517
|
+
// Scan files - look for SCSS, HTML, and TS files
|
|
518
|
+
const allFiles = await scanTemplateFiles(config.src, config.ignore);
|
|
519
|
+
console.log(chalk.green(`✓ Found ${allFiles.length} files (SCSS, HTML, TS)\n`));
|
|
540
520
|
|
|
541
521
|
// Migrate Bootstrap
|
|
542
522
|
const results = migrateBootstrapToMaterial(allFiles, {
|
package/package.json
CHANGED
|
@@ -160,28 +160,60 @@ function detectBootstrapInFile(content, filePath) {
|
|
|
160
160
|
utilities: 0
|
|
161
161
|
};
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
const foundClasses = new Set();
|
|
164
|
+
|
|
165
|
+
// Detect class attributes in HTML/TS templates (with optional whitespace)
|
|
166
|
+
// Supports: class="...", class='...', class=`...`, [class]="..."
|
|
167
|
+
const classPatterns = [
|
|
168
|
+
/class\s*=\s*["']([^"']+)["']/g,
|
|
169
|
+
/class\s*=\s*`([^`]+)`/g,
|
|
170
|
+
/\[class\]\s*=\s*["']([^"']+)["']/g
|
|
171
|
+
];
|
|
166
172
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
173
|
+
classPatterns.forEach(classRegex => {
|
|
174
|
+
let match;
|
|
175
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
176
|
+
const classes = match[1].split(/\s+/);
|
|
177
|
+
classes.forEach(cls => {
|
|
178
|
+
if (cls && isBootstrapClass(cls) && !foundClasses.has(cls)) {
|
|
179
|
+
foundClasses.add(cls);
|
|
180
|
+
const mapping = getBootstrapMapping(cls);
|
|
181
|
+
detected.classes.push({
|
|
182
|
+
class: cls,
|
|
183
|
+
line: getLineNumber(content, match.index),
|
|
184
|
+
mapping: mapping,
|
|
185
|
+
type: mapping.component ? 'component' : 'utility'
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (mapping.material) detected.materialMappable++;
|
|
189
|
+
if (mapping.needsCustom) detected.needsCustomStyles++;
|
|
190
|
+
if (mapping.component) detected.components++;
|
|
191
|
+
else detected.utilities++;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Detect CSS class selectors in SCSS files (e.g., .py-3, .mt-4)
|
|
198
|
+
const selectorRegex = /\.([a-z][a-z0-9-]*)/gi;
|
|
199
|
+
let match;
|
|
200
|
+
while ((match = selectorRegex.exec(content)) !== null) {
|
|
201
|
+
const cls = match[1];
|
|
202
|
+
if (cls && isBootstrapClass(cls) && !foundClasses.has(cls)) {
|
|
203
|
+
foundClasses.add(cls);
|
|
204
|
+
const mapping = getBootstrapMapping(cls);
|
|
205
|
+
detected.classes.push({
|
|
206
|
+
class: cls,
|
|
207
|
+
line: getLineNumber(content, match.index),
|
|
208
|
+
mapping: mapping,
|
|
209
|
+
type: mapping.component ? 'component' : 'utility'
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (mapping.material) detected.materialMappable++;
|
|
213
|
+
if (mapping.needsCustom) detected.needsCustomStyles++;
|
|
214
|
+
if (mapping.component) detected.components++;
|
|
215
|
+
else detected.utilities++;
|
|
216
|
+
}
|
|
185
217
|
}
|
|
186
218
|
|
|
187
219
|
// Detect Bootstrap variables
|
package/src/scanner.js
CHANGED
|
@@ -25,6 +25,31 @@ async function scanScssFiles(srcPath, ignorePatterns = []) {
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Recursively scans for SCSS and template files (HTML, TS) in the given source directory
|
|
30
|
+
* @param {string} srcPath - Source directory to scan
|
|
31
|
+
* @param {Array<string>} ignorePatterns - Patterns to ignore
|
|
32
|
+
* @returns {Promise<Array<string>>} - Array of absolute file paths
|
|
33
|
+
*/
|
|
34
|
+
async function scanTemplateFiles(srcPath, ignorePatterns = []) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const pattern = path.join(srcPath, '**/*.{scss,html,ts}').replace(/\\/g, '/');
|
|
37
|
+
|
|
38
|
+
glob(pattern, {
|
|
39
|
+
ignore: ignorePatterns
|
|
40
|
+
}, (err, files) => {
|
|
41
|
+
if (err) {
|
|
42
|
+
reject(err);
|
|
43
|
+
} else {
|
|
44
|
+
// Ensure absolute paths
|
|
45
|
+
const absoluteFiles = files.map(f => path.resolve(f));
|
|
46
|
+
resolve(absoluteFiles);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
module.exports = {
|
|
29
|
-
scanScssFiles
|
|
53
|
+
scanScssFiles,
|
|
54
|
+
scanTemplateFiles
|
|
30
55
|
};
|
|
@@ -77,6 +77,44 @@ describe('Bootstrap Migrator', () => {
|
|
|
77
77
|
expect(results.summary.filesWithBootstrap).toBe(0);
|
|
78
78
|
expect(results.summary.totalBootstrapClasses).toBe(0);
|
|
79
79
|
});
|
|
80
|
+
|
|
81
|
+
test('should detect Bootstrap classes in SCSS selectors', () => {
|
|
82
|
+
// Create a temp SCSS file with Bootstrap class selectors
|
|
83
|
+
const tempScssPath = path.join(__dirname, 'fixtures/apps/subapp/src/app/bootstrap-component/temp-scss-test.scss');
|
|
84
|
+
const scssContent = `
|
|
85
|
+
.component {
|
|
86
|
+
.py-3 {
|
|
87
|
+
color: blue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.mt-4 {
|
|
91
|
+
background: red;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.d-flex {
|
|
95
|
+
display: flex;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
99
|
+
fs.writeFileSync(tempScssPath, scssContent, 'utf8');
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const results = detectBootstrap([tempScssPath]);
|
|
103
|
+
|
|
104
|
+
expect(results.summary.filesWithBootstrap).toBe(1);
|
|
105
|
+
expect(results.summary.totalBootstrapClasses).toBeGreaterThan(0);
|
|
106
|
+
|
|
107
|
+
const classNames = results.files[0].classes.map(c => c.class);
|
|
108
|
+
expect(classNames).toContain('py-3');
|
|
109
|
+
expect(classNames).toContain('mt-4');
|
|
110
|
+
expect(classNames).toContain('d-flex');
|
|
111
|
+
} finally {
|
|
112
|
+
// Clean up
|
|
113
|
+
if (fs.existsSync(tempScssPath)) {
|
|
114
|
+
fs.unlinkSync(tempScssPath);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
80
118
|
});
|
|
81
119
|
|
|
82
120
|
describe('migrateBootstrapToMaterial', () => {
|
package/test/scanner.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { scanScssFiles } = require('../src/scanner');
|
|
1
|
+
const { scanScssFiles, scanTemplateFiles } = require('../src/scanner');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
4
|
describe('Scanner', () => {
|
|
@@ -22,4 +22,11 @@ describe('Scanner', () => {
|
|
|
22
22
|
|
|
23
23
|
expect(files.every(f => path.isAbsolute(f))).toBe(true);
|
|
24
24
|
});
|
|
25
|
+
|
|
26
|
+
test('should find template files (SCSS, HTML, TS) in directory', async () => {
|
|
27
|
+
const files = await scanTemplateFiles(fixturesPath);
|
|
28
|
+
|
|
29
|
+
expect(files.length).toBeGreaterThan(0);
|
|
30
|
+
expect(files.every(f => f.endsWith('.scss') || f.endsWith('.html') || f.endsWith('.ts'))).toBe(true);
|
|
31
|
+
});
|
|
25
32
|
});
|