xatriumcss 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.
@@ -0,0 +1,477 @@
1
+ const fs = require('fs');
2
+ const { glob } = require('glob');
3
+
4
+ let nativeScanner;
5
+ try {
6
+ nativeScanner = require('../scripts/index.js');
7
+ } catch (e) {
8
+ console.warn('⚠️ Native scanner not available, falling back to JavaScript');
9
+ nativeScanner = null;
10
+ }
11
+
12
+ function smartSplitNoBrace(str, ...splitChars) {
13
+ if (splitChars.length === 0) splitChars = [' '];
14
+
15
+ const result = [];
16
+ let current = '';
17
+ let bracketDepth = 0;
18
+ let parenDepth = 0;
19
+ let braceDepth = 0;
20
+ let inQuote = null;
21
+
22
+ for (let i = 0; i < str.length; i++) {
23
+ const char = str[i];
24
+ const prevChar = str[i - 1];
25
+
26
+ // Handle quotes (not escaped)
27
+ if ((char === "'" || char === '"') && prevChar !== '\\') {
28
+ if (!inQuote) {
29
+ inQuote = char;
30
+ } else if (inQuote === char) {
31
+ inQuote = null;
32
+ }
33
+ current += char;
34
+ continue;
35
+ }
36
+
37
+ // Track depths only when not in quotes
38
+ if (!inQuote) {
39
+ if (char === '[') bracketDepth++;
40
+ if (char === ']' && bracketDepth > 0) bracketDepth--;
41
+ if (char === '(') parenDepth++;
42
+ if (char === ')' && parenDepth > 0) parenDepth--;
43
+
44
+ // Track brace depth with prefix check
45
+ if (char === '{') {
46
+ // Check if there's content in current token (means there's a prefix)
47
+ const hasPrefix = current.trim().length > 0;
48
+
49
+ // Always increment brace depth
50
+ braceDepth++;
51
+
52
+ // But if there's a prefix, we're in "keep together" mode
53
+ // The depth will prevent splitting until we hit the closing }
54
+ }
55
+
56
+ if (char === '}' && braceDepth > 0) {
57
+ braceDepth--;
58
+ }
59
+ }
60
+
61
+ // ✅ Split on specified characters only if all depths are 0 and not in quotes
62
+ let shouldSplit = false;
63
+ if (bracketDepth === 0 && parenDepth === 0 && braceDepth === 0) {
64
+ // Check single character splits
65
+ if (splitChars.includes(char)) {
66
+ shouldSplit = true;
67
+ }
68
+ // Check multi-character splits (like "::")
69
+ for (let splitStr of splitChars) {
70
+ if (splitStr.length > 1 && str.slice(i, i + splitStr.length) === splitStr) {
71
+ shouldSplit = true;
72
+ i += splitStr.length - 1; // Skip ahead
73
+ break;
74
+ }
75
+ }
76
+ }
77
+
78
+ if (shouldSplit) {
79
+ if (current) result.push(current);
80
+ current = '';
81
+ } else {
82
+ current += char;
83
+ }
84
+ }
85
+
86
+ if (current) result.push(current);
87
+ return result;
88
+ }
89
+
90
+ function collectClasses(contentPaths) {
91
+ const sourceConfig = global.xatriumSourceConfig || {
92
+ basePath: null,
93
+ disableAuto: false,
94
+ explicitPaths: [],
95
+ ignorePaths: []
96
+ };
97
+
98
+ // Use native scanner if available
99
+ if (nativeScanner) {
100
+ try {
101
+ const scanner = new nativeScanner.Scanner();
102
+
103
+ // Add patterns
104
+ let pathsArray = sourceConfig.disableAuto
105
+ ? sourceConfig.explicitPaths
106
+ : [...(Array.isArray(contentPaths) ? contentPaths : [contentPaths]), ...sourceConfig.explicitPaths];
107
+
108
+ pathsArray.forEach(pattern => scanner.addPattern(pattern));
109
+ sourceConfig.ignorePaths.forEach(pattern => scanner.addIgnorePattern(pattern));
110
+
111
+ console.log('🚀 Using native scanner (Rust)');
112
+ const rawClasses = scanner.scanAndExtract();
113
+
114
+ return processClasses(rawClasses);
115
+ } catch (error) {
116
+ console.warn('⚠️ Native scanner failed, falling back:', error.message);
117
+ }
118
+ }
119
+
120
+ // Fallback to JavaScript implementation
121
+ return collectClassesJS(contentPaths);
122
+ }
123
+
124
+ function processClasses(rawClasses) {
125
+ const allClasses = new Set();
126
+
127
+ rawClasses.forEach(part => {
128
+ let cls = part.trim();
129
+ if (!cls) return;
130
+
131
+ // Normalize whitespace to single spaces, but preserve inside ( [ { " '
132
+ let normalizedCls = "";
133
+ let parenDepth = 0;
134
+ let bracketDepth = 0;
135
+ let braceDepth = 0;
136
+ let inSingleQuote = false;
137
+ let inDoubleQuote = false;
138
+
139
+ for (let i = 0; i < cls.length; i++) {
140
+ const char = cls[i];
141
+
142
+ // Track quote state
143
+ if (char === "'" && !inDoubleQuote) inSingleQuote = !inSingleQuote;
144
+ else if (char === '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote;
145
+
146
+ const inQuotes = inSingleQuote || inDoubleQuote;
147
+
148
+ // Track bracket depths (only if not in quotes)
149
+ if (!inQuotes) {
150
+ if (char === "(") parenDepth++;
151
+ else if (char === ")") parenDepth--;
152
+ else if (char === "[") bracketDepth++;
153
+ else if (char === "]") bracketDepth--;
154
+ else if (char === "{") braceDepth++;
155
+ else if (char === "}") braceDepth--;
156
+ }
157
+
158
+ const insideGroup = parenDepth > 0 || bracketDepth > 0 || braceDepth > 0 || inQuotes;
159
+
160
+ // Normalize whitespace only when NOT inside groups
161
+ if (/\s/.test(char)) {
162
+ if (insideGroup) {
163
+ normalizedCls += char;
164
+ } else {
165
+ // Collapse consecutive whitespace to single space
166
+ if (normalizedCls.length > 0 && !/\s/.test(normalizedCls[normalizedCls.length - 1])) {
167
+ normalizedCls += " ";
168
+ }
169
+ }
170
+ } else {
171
+ normalizedCls += char;
172
+ }
173
+ }
174
+
175
+ normalizedCls = normalizedCls.trim();
176
+ const innerParts = smartSplitNoBrace(normalizedCls, " ");
177
+
178
+ const spawnShorthandRegex = /^(.*?:)?spawn-((?:from|to|back|half|\[[^\]]+\])(?:\/(?:from|to|back|half|\[[^\]]+\]))*):(.+)$/;
179
+ const scrollShorthandRegex = /^(.*?:)?scroll-((?:from|to|back|half|\[[^\]]+\])(?:\/(?:from|to|back|half|\[[^\]]+\]))*):(.+)$/;
180
+ const scrollPrefixRegex = /^(.*?:)?scroll:(.+)$/;
181
+
182
+ // Group by prefix for spawn/scroll classes
183
+ const spawnByPrefix = {};
184
+ const scrollByPrefix = {};
185
+ const nonShorthandParts = [];
186
+
187
+ innerParts.forEach(part => {
188
+ if (spawnShorthandRegex.test(part)) {
189
+ const match = part.match(spawnShorthandRegex);
190
+ const prefix = match[1] || ''; // Empty string for no prefix
191
+
192
+ if (!spawnByPrefix[prefix]) {
193
+ spawnByPrefix[prefix] = [];
194
+ }
195
+ spawnByPrefix[prefix].push(part);
196
+ } else if (scrollShorthandRegex.test(part) || scrollPrefixRegex.test(part)) {
197
+ const match = part.match(scrollShorthandRegex) || part.match(scrollPrefixRegex);
198
+ const prefix = match[1] || ''; // Empty string for no prefix
199
+
200
+ if (!scrollByPrefix[prefix]) {
201
+ scrollByPrefix[prefix] = [];
202
+ }
203
+ scrollByPrefix[prefix].push(part);
204
+ } else {
205
+ nonShorthandParts.push(part);
206
+ }
207
+ });
208
+
209
+ // Add non-shorthand classes normally
210
+ nonShorthandParts.forEach(c => allClasses.add(c));
211
+
212
+ // Add spawn classes grouped by prefix
213
+ Object.values(spawnByPrefix).forEach(group => {
214
+ if (group.length > 0) {
215
+ allClasses.add(group.join(' '));
216
+ }
217
+ });
218
+
219
+ // Add scroll classes grouped by prefix
220
+ Object.values(scrollByPrefix).forEach(group => {
221
+ if (group.length > 0) {
222
+ allClasses.add(group.join(' '));
223
+ }
224
+ });
225
+ });
226
+
227
+ // Group and sort motion classes by prefix length
228
+ const motionRegex = /^(.*?)motion-\[([^\]]+)\]:/;
229
+ const motionByName = {};
230
+ const nonMotionClasses = [];
231
+
232
+ Array.from(allClasses).forEach(cls => {
233
+ const match = cls.match(motionRegex);
234
+ if (match) {
235
+ const prefix = match[1]; // Everything before "motion-["
236
+ const animName = match[2];
237
+
238
+ if (!motionByName[animName]) {
239
+ motionByName[animName] = [];
240
+ }
241
+
242
+ motionByName[animName].push({
243
+ cls: cls,
244
+ prefix: prefix,
245
+ prefixLength: prefix.length
246
+ });
247
+ } else {
248
+ nonMotionClasses.push(cls);
249
+ }
250
+ });
251
+
252
+ // Sort each animation's classes by prefix length (shortest first)
253
+ const sortedMotionClasses = [];
254
+ Object.values(motionByName).forEach(group => {
255
+ group.sort((a, b) => a.prefixLength - b.prefixLength);
256
+ sortedMotionClasses.push(...group.map(item => item.cls));
257
+ });
258
+
259
+ // Rebuild allClasses: non-motion first, then sorted motion
260
+ allClasses.clear();
261
+ nonMotionClasses.forEach(c => allClasses.add(c));
262
+ sortedMotionClasses.forEach(c => allClasses.add(c));
263
+
264
+ return Array.from(allClasses);
265
+ }
266
+
267
+ function collectClassesJS(contentPaths) {
268
+ const allClasses = new Set();
269
+
270
+ // Get source configuration
271
+ const sourceConfig = global.xatriumSourceConfig || {
272
+ basePath: null,
273
+ disableAuto: false,
274
+ explicitPaths: [],
275
+ ignorePaths: []
276
+ };
277
+
278
+ let pathsArray = [];
279
+
280
+ // If source(none) is set, only use explicit paths
281
+ if (sourceConfig.disableAuto) {
282
+ pathsArray = sourceConfig.explicitPaths;
283
+ console.log('🔒 Auto-detection disabled - using explicit paths only:', pathsArray);
284
+ } else {
285
+ // Use provided contentPaths or defaults
286
+ pathsArray = Array.isArray(contentPaths) ? contentPaths : (contentPaths ? [contentPaths] : []);
287
+
288
+ // Add explicit paths from @source directives
289
+ if (sourceConfig.explicitPaths.length > 0) {
290
+ pathsArray = [...pathsArray, ...sourceConfig.explicitPaths];
291
+ console.log('➕ Added explicit paths:', sourceConfig.explicitPaths);
292
+ }
293
+ }
294
+
295
+ // Apply base path if set
296
+ if (sourceConfig.basePath) {
297
+ const path = require('path');
298
+ pathsArray = pathsArray.map(p => {
299
+ const resolved = path.resolve(path.dirname(process.cwd()), sourceConfig.basePath, p);
300
+ return resolved;
301
+ });
302
+ console.log('📂 Applied base path:', sourceConfig.basePath);
303
+ }
304
+
305
+ console.log('🔍 Scanning paths:', pathsArray);
306
+
307
+ // Scan all files matching the patterns
308
+ let files = [];
309
+ pathsArray.forEach(pattern => {
310
+ try {
311
+ const matches = glob.sync(pattern, {
312
+ ignore: sourceConfig.ignorePaths.length > 0 ? sourceConfig.ignorePaths : undefined
313
+ });
314
+ files.push(...matches);
315
+
316
+ if (sourceConfig.ignorePaths.length > 0) {
317
+ console.log(`🚫 Ignoring paths:`, sourceConfig.ignorePaths);
318
+ }
319
+ } catch (err) {
320
+ console.error(`❌ Error scanning pattern ${pattern}:`, err.message);
321
+ }
322
+ });
323
+
324
+ console.log(`📄 Found ${files.length} files to scan`);
325
+
326
+ if (files.length === 0) {
327
+ return [];
328
+ }
329
+
330
+ files.forEach(filePath => {
331
+ const fileContent = fs.readFileSync(filePath, 'utf8');
332
+
333
+ // Grab only strings inside single or double quotes
334
+ const parts = Array.from(fileContent.matchAll(/(['"])(.*?)\1/g), m => m[2]);
335
+
336
+ parts.forEach(part => {
337
+ let cls = part.trim();
338
+ if (!cls) return;
339
+
340
+ // Normalize whitespace to single spaces, but preserve inside ( [ { " '
341
+ let normalizedCls = "";
342
+ let parenDepth = 0;
343
+ let bracketDepth = 0;
344
+ let braceDepth = 0;
345
+ let inSingleQuote = false;
346
+ let inDoubleQuote = false;
347
+
348
+ for (let i = 0; i < cls.length; i++) {
349
+ const char = cls[i];
350
+
351
+ // Track quote state
352
+ if (char === "'" && !inDoubleQuote) inSingleQuote = !inSingleQuote;
353
+ else if (char === '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote;
354
+
355
+ const inQuotes = inSingleQuote || inDoubleQuote;
356
+
357
+ // Track bracket depths (only if not in quotes)
358
+ if (!inQuotes) {
359
+ if (char === "(") parenDepth++;
360
+ else if (char === ")") parenDepth--;
361
+ else if (char === "[") bracketDepth++;
362
+ else if (char === "]") bracketDepth--;
363
+ else if (char === "{") braceDepth++;
364
+ else if (char === "}") braceDepth--;
365
+ }
366
+
367
+ const insideGroup = parenDepth > 0 || bracketDepth > 0 || braceDepth > 0 || inQuotes;
368
+
369
+ // Normalize whitespace only when NOT inside groups
370
+ if (/\s/.test(char)) {
371
+ if (insideGroup) {
372
+ normalizedCls += char;
373
+ } else {
374
+ // Collapse consecutive whitespace to single space
375
+ if (normalizedCls.length > 0 && !/\s/.test(normalizedCls[normalizedCls.length - 1])) {
376
+ normalizedCls += " ";
377
+ }
378
+ }
379
+ } else {
380
+ normalizedCls += char;
381
+ }
382
+ }
383
+
384
+ normalizedCls = normalizedCls.trim();
385
+ const innerParts = smartSplitNoBrace(normalizedCls, " ");
386
+
387
+ const spawnShorthandRegex = /^(.*?:)?spawn-((?:from|to|back|half|\[[^\]]+\])(?:\/(?:from|to|back|half|\[[^\]]+\]))*):(.+)$/;
388
+ const scrollShorthandRegex = /^(.*?:)?scroll-((?:from|to|back|half|\[[^\]]+\])(?:\/(?:from|to|back|half|\[[^\]]+\]))*):(.+)$/;
389
+ const scrollPrefixRegex = /^(.*?:)?scroll:(.+)$/;
390
+
391
+ // Group by prefix for spawn/scroll classes
392
+ const spawnByPrefix = {};
393
+ const scrollByPrefix = {};
394
+ const nonShorthandParts = [];
395
+
396
+ innerParts.forEach(part => {
397
+ if (spawnShorthandRegex.test(part)) {
398
+ const match = part.match(spawnShorthandRegex);
399
+ const prefix = match[1] || ''; // Empty string for no prefix
400
+
401
+ if (!spawnByPrefix[prefix]) {
402
+ spawnByPrefix[prefix] = [];
403
+ }
404
+ spawnByPrefix[prefix].push(part);
405
+ } else if (scrollShorthandRegex.test(part) || scrollPrefixRegex.test(part)) {
406
+ const match = part.match(scrollShorthandRegex) || part.match(scrollPrefixRegex);
407
+ const prefix = match[1] || ''; // Empty string for no prefix
408
+
409
+ if (!scrollByPrefix[prefix]) {
410
+ scrollByPrefix[prefix] = [];
411
+ }
412
+ scrollByPrefix[prefix].push(part);
413
+ } else {
414
+ nonShorthandParts.push(part);
415
+ }
416
+ });
417
+
418
+ // Add non-shorthand classes normally
419
+ nonShorthandParts.forEach(c => allClasses.add(c));
420
+
421
+ // Add spawn classes grouped by prefix
422
+ Object.values(spawnByPrefix).forEach(group => {
423
+ if (group.length > 0) {
424
+ allClasses.add(group.join(' '));
425
+ }
426
+ });
427
+
428
+ // Add scroll classes grouped by prefix
429
+ Object.values(scrollByPrefix).forEach(group => {
430
+ if (group.length > 0) {
431
+ allClasses.add(group.join(' '));
432
+ }
433
+ });
434
+ });
435
+ });
436
+
437
+ // Group and sort motion classes by prefix length
438
+ const motionRegex = /^(.*?)motion-\[([^\]]+)\]:/;
439
+ const motionByName = {};
440
+ const nonMotionClasses = [];
441
+
442
+ Array.from(allClasses).forEach(cls => {
443
+ const match = cls.match(motionRegex);
444
+ if (match) {
445
+ const prefix = match[1]; // Everything before "motion-["
446
+ const animName = match[2];
447
+
448
+ if (!motionByName[animName]) {
449
+ motionByName[animName] = [];
450
+ }
451
+
452
+ motionByName[animName].push({
453
+ cls: cls,
454
+ prefix: prefix,
455
+ prefixLength: prefix.length
456
+ });
457
+ } else {
458
+ nonMotionClasses.push(cls);
459
+ }
460
+ });
461
+
462
+ // Sort each animation's classes by prefix length (shortest first)
463
+ const sortedMotionClasses = [];
464
+ Object.values(motionByName).forEach(group => {
465
+ group.sort((a, b) => a.prefixLength - b.prefixLength);
466
+ sortedMotionClasses.push(...group.map(item => item.cls));
467
+ });
468
+
469
+ // Rebuild allClasses: non-motion first, then sorted motion
470
+ allClasses.clear();
471
+ nonMotionClasses.forEach(c => allClasses.add(c));
472
+ sortedMotionClasses.forEach(c => allClasses.add(c));
473
+
474
+ return Array.from(allClasses);
475
+ }
476
+
477
+ module.exports = collectClasses;
package/src/index.js ADDED
@@ -0,0 +1,26 @@
1
+ // scripts/index.js
2
+ const { platform, arch } = process;
3
+
4
+ const platformMap = {
5
+ 'win32-x64': 'win32-x64-msvc',
6
+ 'darwin-x64': 'darwin-x64',
7
+ 'darwin-arm64': 'darwin-arm64',
8
+ 'linux-x64': 'linux-x64-gnu',
9
+ 'linux-arm64': 'linux-arm64-gnu'
10
+ };
11
+
12
+ const platformKey = `${platform}-${arch}`;
13
+ const nativeName = platformMap[platformKey];
14
+
15
+ if (!nativeName) {
16
+ throw new Error(`Unsupported platform: ${platformKey}`);
17
+ }
18
+
19
+ let native;
20
+ try {
21
+ native = require(`./xatriumcss.${nativeName}.node`);
22
+ } catch (err) {
23
+ throw new Error(`Failed to load native module for ${platformKey}: ${err.message}`);
24
+ }
25
+
26
+ module.exports = native;