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 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 SCSS files
452
- const scssFiles = await scanScssFiles(config.src, config.ignore);
453
- const htmlPattern = path.join(config.src, '**/*.html').replace(/\\/g, '/');
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 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
- });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scss-variable-extractor",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Analyzes Angular SCSS files and extracts repeated hardcoded values into reusable variables",
5
5
  "bin": {
6
6
  "scss-extract": "./bin/cli.js"
@@ -160,28 +160,60 @@ function detectBootstrapInFile(content, filePath) {
160
160
  utilities: 0
161
161
  };
162
162
 
163
- // Detect class attributes in templates (if included in SCSS via ::ng-deep)
164
- const classRegex = /class=["']([^"']+)["']/g;
165
- let match;
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
- while ((match = classRegex.exec(content)) !== null) {
168
- const classes = match[1].split(/\s+/);
169
- classes.forEach(cls => {
170
- if (isBootstrapClass(cls)) {
171
- const mapping = getBootstrapMapping(cls);
172
- detected.classes.push({
173
- class: cls,
174
- line: getLineNumber(content, match.index),
175
- mapping: mapping,
176
- type: mapping.component ? 'component' : 'utility'
177
- });
178
-
179
- if (mapping.material) detected.materialMappable++;
180
- if (mapping.needsCustom) detected.needsCustomStyles++;
181
- if (mapping.component) detected.components++;
182
- else detected.utilities++;
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', () => {
@@ -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
  });