@finggujadhav/compiler 0.9.3 → 0.9.4

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.
Files changed (3) hide show
  1. package/cli.js +104 -25
  2. package/engine.js +41 -11
  3. package/package.json +1 -1
package/cli.js CHANGED
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * FingguFlux CLI
4
- * Phase 6A: Compiler CLI Foundation
4
+ * v0.9.4 Intelligence Upgrade
5
5
  */
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
8
  import { scanFiles, getProjectFiles } from './scanner.js';
9
9
  import { CompilerEngine } from './engine.js';
10
10
 
11
- // Basic named argument parser
12
11
  const args = process.argv.slice(2);
12
+ const command = args[0] || 'build';
13
+
13
14
  const getArg = (flag) => {
14
15
  const idx = args.indexOf(flag);
15
16
  return idx !== -1 && args[idx + 1] ? args[idx + 1] : null;
@@ -19,27 +20,37 @@ const mode = getArg('--mode') || 'dev';
19
20
  const inputDir = getArg('--input') || './';
20
21
  const outputDir = getArg('--output') || './dist';
21
22
 
22
- async function runBuild() {
23
- console.log(`\nšŸš€ FingguFlux Compiler [Mode: ${mode.toUpperCase()}]`);
23
+ async function main() {
24
+ switch (command) {
25
+ case 'build':
26
+ await runBuild();
27
+ break;
28
+ case 'analyze':
29
+ await runAnalyze();
30
+ break;
31
+ case 'doctor':
32
+ await runDoctor();
33
+ break;
34
+ default:
35
+ console.error(`Unknown command: ${command}`);
36
+ console.log('Available commands: build, analyze, doctor');
37
+ process.exit(1);
38
+ }
39
+ }
24
40
 
25
- // 1. Scan for used classes
26
- console.log(`šŸ” Scanning files in ${inputDir}...`);
41
+ async function prepareEngine() {
27
42
  const files = getProjectFiles(path.resolve(inputDir));
28
43
  const usedClasses = scanFiles(files);
29
- console.log(`āœ… Found ${usedClasses.length} used FingguFlux classes.`);
30
-
31
- // 2. Initialize Engine
32
44
  const engine = new CompilerEngine({ mode });
33
45
  engine.setUsedClasses(usedClasses);
34
46
 
35
- // 3. Collect CSS source files
47
+ // Collect CSS source files
36
48
  let combinedCSS = '';
37
49
  const possiblePaths = [
38
50
  path.resolve('./node_modules/@finggujadhav/core'),
39
51
  path.resolve('./packages/core'),
40
52
  path.resolve('../../packages/core')
41
53
  ];
42
-
43
54
  let corePath = possiblePaths.find(p => fs.existsSync(p));
44
55
  if (!corePath) throw new Error('Could not find @finggujadhav/core CSS sources.');
45
56
 
@@ -55,32 +66,100 @@ async function runBuild() {
55
66
  }
56
67
  });
57
68
  };
58
-
59
69
  collectCSS(corePath);
60
70
 
61
- // 4. Process CSS
62
- console.log(`šŸ› ļø Processing CSS and applying tree-shaking...`);
63
71
  const finalCSS = engine.processCSS(combinedCSS);
64
- const mapping = engine.getMapping();
72
+ return { engine, finalCSS, usedClasses };
73
+ }
65
74
 
66
- // 5. Output
67
- if (!fs.existsSync(outputDir)) {
68
- fs.mkdirSync(outputDir, { recursive: true });
69
- }
75
+ async function runBuild() {
76
+ console.log(`\nšŸš€ FingguFlux Compiler [Mode: ${mode.toUpperCase()}]`);
77
+ const { engine, finalCSS } = await prepareEngine();
78
+
79
+ if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
70
80
 
71
81
  const cssPath = path.join(outputDir, 'finggu.css');
72
82
  const mappingPath = path.join(outputDir, 'mapping.json');
83
+ const reportPath = path.join(outputDir, 'fingguflux-report.json');
73
84
 
74
85
  fs.writeFileSync(cssPath, finalCSS);
75
- fs.writeFileSync(mappingPath, JSON.stringify(mapping, null, 2));
86
+ fs.writeFileSync(mappingPath, JSON.stringify(engine.getMapping(), null, 2));
87
+ fs.writeFileSync(reportPath, JSON.stringify(engine.generateReport(), null, 2));
76
88
 
77
- console.log(`\nšŸ“¦ Build Complete!`);
89
+ console.log(`āœ… Build Complete!`);
78
90
  console.log(`- CSS: ${cssPath} (${Buffer.byteLength(finalCSS)} bytes)`);
79
- console.log(`- Mapping: ${mappingPath}`);
80
- console.log(`- Mode: ${mode}`);
91
+ console.log(`- Report: ${reportPath}`);
92
+ }
93
+
94
+ async function runAnalyze() {
95
+ console.log(`\nšŸ“Š FingguFlux Intelligence - Analyze Mode`);
96
+ const { engine } = await prepareEngine();
97
+ const stats = engine.getStats();
98
+
99
+ console.log(`-------------------------------------------`);
100
+ console.log(`Total Scanned Classes: ${stats.totalClasses}`);
101
+ console.log(`Used Classes: ${stats.usedClasses}`);
102
+ console.log(`Unused Classes: ${stats.unusedClasses}`);
103
+ console.log(`Extreme Mappings: ${stats.extremeMappings}`);
104
+ console.log(`-------------------------------------------`);
105
+ console.log(`Original Size: ${stats.originalSize} bytes`);
106
+ console.log(`Estimated Final Size: ${stats.finalSize} bytes`);
107
+ console.log(`Gzip Estimate (~30%): ${stats.gzipEstimate} bytes`);
108
+ console.log(`-------------------------------------------`);
109
+
110
+ if (stats.unusedList.length > 0 && args.includes('--verbose')) {
111
+ console.log(`\nšŸ’€ Unused Classes:`);
112
+ stats.unusedList.slice(0, 20).forEach(c => console.log(` - ${c}`));
113
+ if (stats.unusedList.length > 20) console.log(` ... and ${stats.unusedList.length - 20} more.`);
114
+ }
115
+ }
116
+
117
+ async function runDoctor() {
118
+ console.log(`\n🩺 FingguFlux Intelligence - Doctor Mode`);
119
+ const mappingPath = path.resolve(outputDir, 'mapping.json');
120
+ const corePkgPath = path.resolve('./packages/core/package.json');
121
+
122
+ let healthy = true;
123
+
124
+ // 1. Check mapping
125
+ if (fs.existsSync(mappingPath)) {
126
+ console.log(`āœ… mapping.json found.`);
127
+ try {
128
+ const mapping = JSON.parse(fs.readFileSync(mappingPath, 'utf8'));
129
+ if (Object.keys(mapping).length === 0) {
130
+ console.warn(`āš ļø Warning: mapping.json is empty.`);
131
+ }
132
+ } catch {
133
+ console.error(`āŒ Error: mapping.json is corrupted.`);
134
+ healthy = false;
135
+ }
136
+ } else {
137
+ console.warn(`āš ļø Warning: mapping.json not found in ${outputDir}. Run build first.`);
138
+ healthy = false;
139
+ }
140
+
141
+ // 2. Check Core Version
142
+ if (fs.existsSync(corePkgPath)) {
143
+ const corePkg = JSON.parse(fs.readFileSync(corePkgPath, 'utf8'));
144
+ console.log(`āœ… Core version verified: ${corePkg.version}`);
145
+ }
146
+
147
+ // 3. Extreme Mode Compatibility
148
+ const { usedClasses } = await prepareEngine();
149
+ const invalidClasses = usedClasses.filter(c => !c.startsWith('ff-'));
150
+ if (invalidClasses.length > 0) {
151
+ console.error(`āŒ Error: ${invalidClasses.length} classes do not follow ff- prefix!`);
152
+ invalidClasses.slice(0, 5).forEach(c => console.log(` - ${c}`));
153
+ healthy = false;
154
+ } else {
155
+ console.log(`āœ… All used classes follow ff- prefix.`);
156
+ }
157
+
158
+ if (healthy) console.log(`\n✨ Project is healthy!`);
159
+ else console.log(`\nāŒ Project has issues. See details above.`);
81
160
  }
82
161
 
83
- runBuild().catch(err => {
84
- console.error('Build failed:', err);
162
+ main().catch(err => {
163
+ console.error('Command failed:', err);
85
164
  process.exit(1);
86
165
  });
package/engine.js CHANGED
@@ -72,6 +72,15 @@ export class CompilerEngine {
72
72
 
73
73
  processCSS(cssContent) {
74
74
  let processed = cssContent;
75
+ this.stats = {
76
+ totalClasses: 0,
77
+ usedClasses: 0,
78
+ unusedClasses: 0,
79
+ originalSize: Buffer.byteLength(cssContent),
80
+ finalSize: 0,
81
+ extremeMappings: 0,
82
+ unusedList: []
83
+ };
75
84
 
76
85
  // 1. Identify all .ff- selectors in the source
77
86
  const classRegex = /\.ff-([\w-]+)/g;
@@ -80,13 +89,21 @@ export class CompilerEngine {
80
89
  while ((match = classRegex.exec(cssContent)) !== null) {
81
90
  foundInCSS.add(`ff-${match[1]}`);
82
91
  }
92
+ this.stats.totalClasses = foundInCSS.size;
83
93
 
84
94
  // 2. Generate mappings only for USED classes
85
95
  foundInCSS.forEach(cls => {
86
96
  if (this.usedClasses.has(cls)) {
87
97
  this.generateMapping(cls);
98
+ } else {
99
+ this.stats.unusedList.push(cls);
88
100
  }
89
101
  });
102
+ this.stats.usedClasses = Object.keys(this.mapping).length;
103
+ this.stats.unusedClasses = this.stats.totalClasses - this.stats.usedClasses;
104
+ if (this.mode === 'ext') {
105
+ this.stats.extremeMappings = this.stats.usedClasses;
106
+ }
90
107
 
91
108
  // 3. Tree-shaking (Block-level pruning) - DO THIS FIRST while names are original
92
109
  const blocks = processed.split('}');
@@ -99,8 +116,6 @@ export class CompilerEngine {
99
116
  if (selector.includes('.ff-')) {
100
117
  const ffClassesInSelector = selector.match(/\.ff-[\w-]+/g);
101
118
  if (ffClassesInSelector) {
102
- // Prune block if ANY ff- class in the selector is unused
103
- // (Matches our strict tree-shaking policy for components/utilities)
104
119
  const hasUnused = ffClassesInSelector.some(cls => {
105
120
  const baseCls = cls.substring(1);
106
121
  return !this.usedClasses.has(baseCls);
@@ -114,8 +129,6 @@ export class CompilerEngine {
114
129
  processed = filteredBlocks.join('}') + (filteredBlocks.length > 0 ? '}' : '');
115
130
 
116
131
  // 4. Replace selectors with mapped hashes
117
- // NOTE: We explicitly DO NOT hash CSS variables starting with --ff-
118
- // This ensures runtime theme switching remains operational.
119
132
  const sortedClasses = Object.keys(this.mapping).sort((a, b) => b.length - a.length);
120
133
  sortedClasses.forEach(cls => {
121
134
  const mapped = this.mapping[cls];
@@ -126,17 +139,37 @@ export class CompilerEngine {
126
139
  }
127
140
  });
128
141
 
129
- // 5. Keyframe Pruning (Post-pruning)
130
- return this.pruneKeyframes(processed);
142
+ // 5. Keyframe Pruning
143
+ processed = this.pruneKeyframes(processed);
144
+ this.stats.finalSize = Buffer.byteLength(processed);
145
+ return processed;
146
+ }
147
+
148
+ /**
149
+ * @returns {Object} v0.9.4 Intelligence Stats
150
+ */
151
+ getStats() {
152
+ return {
153
+ ...this.stats,
154
+ gzipEstimate: Math.round(this.stats.finalSize * 0.3) // Rough estimate for reporting
155
+ };
156
+ }
157
+
158
+ generateReport() {
159
+ return {
160
+ timestamp: new Date().toISOString(),
161
+ engineVersion: "0.9.4",
162
+ mode: this.mode,
163
+ stats: this.getStats(),
164
+ mapping: this.getMapping()
165
+ };
131
166
  }
132
167
 
133
168
  pruneKeyframes(css) {
134
- // Find all referenced animation names
135
169
  const animationRegex = /animation(?:\-name)?\s*:\s*([^;!}]+)/g;
136
170
  const usedAnimations = new Set();
137
171
  let match;
138
172
  while ((match = animationRegex.exec(css)) !== null) {
139
- // Split by space/comma and filter out durations/easings/etc
140
173
  const parts = match[1].split(/[,\s]+/).map(p => p.trim());
141
174
  parts.forEach(p => {
142
175
  if (p && !/^\d|ms|s|infinite|linear|ease|both|forwards|backwards/.test(p)) {
@@ -145,7 +178,6 @@ export class CompilerEngine {
145
178
  });
146
179
  }
147
180
 
148
- // Prune unused @keyframes blocks
149
181
  const keyframeBlocks = css.split(/@keyframes\s+([\w-]+)\s*\{/);
150
182
  if (keyframeBlocks.length <= 1) return css;
151
183
 
@@ -154,7 +186,6 @@ export class CompilerEngine {
154
186
  const name = keyframeBlocks[i];
155
187
  const contentAndRest = keyframeBlocks[i + 1];
156
188
 
157
- // Find the end of this @keyframes block (handling nested braces if any)
158
189
  let braceCount = 1;
159
190
  let endOfBlock = -1;
160
191
  for (let j = 0; j < contentAndRest.length; j++) {
@@ -179,7 +210,6 @@ export class CompilerEngine {
179
210
  }
180
211
 
181
212
  getMapping() {
182
- // Return sorted mapping for stability
183
213
  const sortedMapping = {};
184
214
  Object.keys(this.mapping).sort().forEach(key => {
185
215
  sortedMapping[key] = this.mapping[key];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finggujadhav/compiler",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "description": "The FingguFlux static analysis and selector hardening engine.",
5
5
  "main": "engine.js",
6
6
  "bin": {