scss-variable-extractor 1.4.0 → 1.5.2
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 +58 -2
- package/bin/cli.js +11 -31
- 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
|
@@ -342,7 +342,8 @@ npx scss-extract migrate-bootstrap ./src --custom-utilities ./src/utilities.scss
|
|
|
342
342
|
- `[src]` - Source directory to scan (optional, auto-detected from angular.json)
|
|
343
343
|
|
|
344
344
|
**Options:**
|
|
345
|
-
- `--custom-utilities <path>` - Path for custom utilities file (default: `./src/utilities.scss`)
|
|
345
|
+
- `--custom-utilities <path>` - Path for custom utilities file (default: `./src/styles/utilities.scss`)
|
|
346
|
+
- `--no-custom-utilities` - Skip creating custom utility classes
|
|
346
347
|
- `--dry-run` - Preview changes without modifying files
|
|
347
348
|
- `--angular-json <path>` - Path to angular.json file
|
|
348
349
|
- `--project <name>` - Specify which project to use
|
|
@@ -408,7 +409,13 @@ npx scss-extract migrate-bootstrap --dry-run
|
|
|
408
409
|
# 3. Apply migration
|
|
409
410
|
npx scss-extract migrate-bootstrap
|
|
410
411
|
|
|
411
|
-
# 4.
|
|
412
|
+
# 4. Apply migration with custom utilities path
|
|
413
|
+
npx scss-extract migrate-bootstrap --custom-utilities ./custom/utilities.scss
|
|
414
|
+
|
|
415
|
+
# 5. Skip utilities generation
|
|
416
|
+
npx scss-extract migrate-bootstrap --no-custom-utilities
|
|
417
|
+
|
|
418
|
+
# 6. Review changes and test components
|
|
412
419
|
# Manual review of generated utilities.scss and component templates
|
|
413
420
|
```
|
|
414
421
|
|
|
@@ -701,6 +708,55 @@ MIT License - see [LICENSE](./LICENSE) file for details
|
|
|
701
708
|
|
|
702
709
|
## Changelog
|
|
703
710
|
|
|
711
|
+
### 1.5.2 (2026-02-12)
|
|
712
|
+
|
|
713
|
+
- **Fixed:** `migrate-bootstrap` command option renamed from `--utilities` to `--custom-utilities` to work with `--no-custom-utilities`
|
|
714
|
+
- **Fixed:** Default utilities path corrected to `./src/styles/utilities.scss`
|
|
715
|
+
|
|
716
|
+
### 1.5.1 (2026-02-12)
|
|
717
|
+
|
|
718
|
+
- **Enhanced:** HTML class detection now handles spaces around `=` (e.g., `class = "py-3"`)
|
|
719
|
+
- **Added:** Support for backtick quotes in class attributes (e.g., `class=\`py-3\``)
|
|
720
|
+
- **Added:** Support for Angular property binding (e.g., `[class]="py-3"`)
|
|
721
|
+
- **Improved:** More robust Bootstrap class detection across different HTML/template formats
|
|
722
|
+
|
|
723
|
+
### 1.5.0 (2026-02-12)
|
|
724
|
+
|
|
725
|
+
- **Fixed:** Bootstrap detection now scans SCSS class selectors (`.py-3`, `.mt-4`, etc.)
|
|
726
|
+
- **Enhanced:** Bootstrap commands now scan SCSS, HTML, and TypeScript files
|
|
727
|
+
- **Added:** New `scanTemplateFiles` function for comprehensive file scanning
|
|
728
|
+
- **Improved:** Bootstrap class detection with deduplication
|
|
729
|
+
|
|
730
|
+
### 1.4.0 (2026-02-12)
|
|
731
|
+
|
|
732
|
+
- **Added:** Bootstrap to Angular Material migration capability
|
|
733
|
+
- **Added:** `detect-bootstrap` command for analyzing Bootstrap usage
|
|
734
|
+
- **Added:** `migrate-bootstrap` command for converting Bootstrap classes
|
|
735
|
+
- **Added:** Custom utilities generation for flex/grid/spacing classes
|
|
736
|
+
- **Added:** Material Design spacing scale (0, 4px, 8px, 16px, 24px, 48px)
|
|
737
|
+
- 100+ Bootstrap class mappings
|
|
738
|
+
|
|
739
|
+
### 1.3.0 (2026-02-12)
|
|
740
|
+
|
|
741
|
+
- **Added:** Angular.json integration for automatic project configuration
|
|
742
|
+
- **Added:** Auto-detection of source directory and style preprocessor
|
|
743
|
+
- **Added:** Support for multi-project Angular workspaces
|
|
744
|
+
- All commands support `--angular-json`, `--project`, `--no-angular` options
|
|
745
|
+
|
|
746
|
+
### 1.2.0 (2026-02-12)
|
|
747
|
+
|
|
748
|
+
- **Added:** Angular Material v15+ pattern refactoring
|
|
749
|
+
- **Added:** `analyze-patterns` command to detect anti-patterns
|
|
750
|
+
- **Added:** `modernize` command to remove `::ng-deep` and `!important`
|
|
751
|
+
- Best practice enforcement with migration suggestions
|
|
752
|
+
|
|
753
|
+
### 1.1.0 (2026-02-12)
|
|
754
|
+
|
|
755
|
+
- **Added:** Global installation support via npm link/publish
|
|
756
|
+
- **Added:** Positional `[src]` argument for all commands
|
|
757
|
+
- **Enhanced:** Scan any folder in your project
|
|
758
|
+
- **Improved:** CLI help and documentation
|
|
759
|
+
|
|
704
760
|
### 1.0.0 (2026-02-12)
|
|
705
761
|
|
|
706
762
|
- 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);
|
|
@@ -491,7 +481,7 @@ program
|
|
|
491
481
|
.command('migrate-bootstrap')
|
|
492
482
|
.description('Migrate Bootstrap classes to Angular Material MDC-based styles')
|
|
493
483
|
.argument('[src]', 'Source directory to scan (optional if using angular.json)')
|
|
494
|
-
.option('--utilities <path>', 'Path to custom utilities file', './src/styles/utilities.scss')
|
|
484
|
+
.option('--custom-utilities <path>', 'Path to custom utilities file', './src/styles/utilities.scss')
|
|
495
485
|
.option('--no-custom-utilities', 'Skip creating custom utility classes')
|
|
496
486
|
.option('--no-remove-imports', 'Keep Bootstrap imports')
|
|
497
487
|
.option('--dry-run', 'Preview changes without modifying files')
|
|
@@ -522,26 +512,16 @@ program
|
|
|
522
512
|
}
|
|
523
513
|
|
|
524
514
|
console.log(chalk.gray(`Scanning: ${config.src}`));
|
|
525
|
-
console.log(chalk.gray(`Utilities output: ${options.utilities}\n`));
|
|
526
|
-
|
|
527
|
-
// Scan files
|
|
528
|
-
const scssFiles = await scanScssFiles(config.src, config.ignore);
|
|
529
|
-
const htmlPattern = path.join(config.src, '**/*.html').replace(/\\/g, '/');
|
|
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
|
-
});
|
|
515
|
+
console.log(chalk.gray(`Utilities output: ${options.customUtilities || './src/styles/utilities.scss'}\n`));
|
|
537
516
|
|
|
538
|
-
|
|
539
|
-
|
|
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, {
|
|
543
523
|
createCustomUtilities: options.customUtilities !== false,
|
|
544
|
-
customUtilitiesPath: options.utilities,
|
|
524
|
+
customUtilitiesPath: options.customUtilities || './src/styles/utilities.scss',
|
|
545
525
|
removeBootstrapImports: options.removeImports !== false,
|
|
546
526
|
dryRun: options.dryRun
|
|
547
527
|
});
|
|
@@ -565,7 +545,7 @@ program
|
|
|
565
545
|
}
|
|
566
546
|
|
|
567
547
|
if (results.customUtilities.length > 0) {
|
|
568
|
-
console.log(chalk.cyan(`✓ Generated ${results.customUtilities.length} custom utility class(es) in ${options.utilities}\n`));
|
|
548
|
+
console.log(chalk.cyan(`✓ Generated ${results.customUtilities.length} custom utility class(es) in ${options.customUtilities || './src/styles/utilities.scss'}\n`));
|
|
569
549
|
}
|
|
570
550
|
|
|
571
551
|
if (results.componentMigrations.length > 0) {
|
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
|
});
|