@bookklik/senangstart-css 0.2.9 → 0.2.12
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/.agent/skills/add-utility/SKILL.md +65 -0
- package/.agent/workflows/add-utility.md +2 -0
- package/.agent/workflows/build.md +2 -0
- package/.agent/workflows/dev.md +2 -0
- package/AGENTS.md +30 -0
- package/dist/senangstart-css.js +607 -180
- package/dist/senangstart-css.min.js +234 -195
- package/dist/senangstart-tw.js +274 -8
- package/dist/senangstart-tw.min.js +1 -1
- package/docs/SYNTAX-REFERENCE.md +1731 -1590
- package/docs/guide/preflight.md +20 -1
- package/docs/ms/guide/preflight.md +19 -0
- package/docs/ms/reference/breakpoints.md +14 -0
- package/docs/ms/reference/visual/border-radius.md +50 -10
- package/docs/ms/reference/visual/contain.md +57 -0
- package/docs/ms/reference/visual/content-visibility.md +53 -0
- package/docs/ms/reference/visual/placeholder-color.md +92 -0
- package/docs/ms/reference/visual/ring-color.md +2 -2
- package/docs/ms/reference/visual/ring-offset.md +3 -3
- package/docs/ms/reference/visual/ring.md +5 -5
- package/docs/ms/reference/visual/writing-mode.md +53 -0
- package/docs/ms/reference/visual.md +6 -0
- package/docs/public/assets/senangstart-css.min.js +234 -195
- package/docs/public/llms.txt +45 -12
- package/docs/reference/breakpoints.md +14 -0
- package/docs/reference/visual/border-radius.md +50 -10
- package/docs/reference/visual/contain.md +57 -0
- package/docs/reference/visual/content-visibility.md +53 -0
- package/docs/reference/visual/placeholder-color.md +92 -0
- package/docs/reference/visual/ring-color.md +2 -2
- package/docs/reference/visual/ring-offset.md +3 -3
- package/docs/reference/visual/ring.md +5 -5
- package/docs/reference/visual/writing-mode.md +53 -0
- package/docs/reference/visual.md +7 -0
- package/docs/syntax-reference.json +2185 -2009
- package/package.json +1 -1
- package/scripts/convert-tailwind.js +300 -26
- package/scripts/generate-docs.js +403 -403
- package/src/cdn/senangstart-engine.js +5 -5
- package/src/cdn/tw-conversion-engine.js +305 -8
- package/src/cli/commands/build.js +51 -13
- package/src/cli/commands/dev.js +157 -93
- package/src/compiler/generators/css.js +467 -208
- package/src/compiler/generators/preflight.js +26 -13
- package/src/compiler/generators/typescript.js +3 -1
- package/src/compiler/index.js +27 -3
- package/src/compiler/parser.js +13 -6
- package/src/compiler/tokenizer.js +25 -23
- package/src/config/defaults.js +3 -0
- package/src/core/tokenizer-core.js +46 -19
- package/src/definitions/index.js +4 -1
- package/src/definitions/visual-borders.js +10 -10
- package/src/definitions/visual-performance.js +126 -0
- package/src/definitions/visual.js +25 -9
- package/src/utils/common.js +456 -27
- package/src/utils/node-io.js +82 -0
- package/tests/integration/dev-recovery.test.js +231 -0
- package/tests/unit/cli/memory-limits.test.js +169 -0
- package/tests/unit/compiler/css-generation-error-handling.test.js +204 -0
- package/tests/unit/compiler/generators/css-errors.test.js +102 -0
- package/tests/unit/compiler/generators/css.test.js +102 -5
- package/tests/unit/convert-tailwind.test.js +518 -431
- package/tests/unit/utils/common.test.js +376 -26
- package/tests/unit/utils/file-timeout.test.js +154 -0
- package/tests/unit/utils/theme-validation.test.js +181 -0
- package/tests/unit/compiler/generators/css.coverage.test.js +0 -833
- package/tests/unit/convert-tailwind.cli.test.js +0 -95
- package/tests/unit/security.test.js +0 -206
- /package/tests/unit/{convert-tailwind.coverage.test.js → convert-tailwind-edgecases.test.js} +0 -0
|
@@ -93,11 +93,11 @@ import { defaultConfig, mergeConfig } from '../config/defaults.js';
|
|
|
93
93
|
function compileCSS(domTokens, config) {
|
|
94
94
|
// 1. Convert raw DOM tokens (Sets) to parsed token array
|
|
95
95
|
// tokenizeAll expects { layout: Set, ... } which matches domTokens structure
|
|
96
|
-
const tokens = tokenizeAll(domTokens);
|
|
97
|
-
|
|
98
|
-
// 2. Generate CSS using the core generator
|
|
99
|
-
// generateCSS expects array of tokens and config
|
|
100
|
-
return generateCSS(tokens, config);
|
|
96
|
+
const tokens = tokenizeAll(domTokens);
|
|
97
|
+
|
|
98
|
+
// 2. Generate CSS using the core generator
|
|
99
|
+
// generateCSS expects array of tokens and config
|
|
100
|
+
return generateCSS(tokens, config);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// ============================================
|
|
@@ -108,6 +108,45 @@ const fontSizeScale = {
|
|
|
108
108
|
"9xl": "hero", // 8rem → hero
|
|
109
109
|
};
|
|
110
110
|
|
|
111
|
+
// Line height scale mapping Tailwind values to SenangStart semantic values
|
|
112
|
+
// Engine native values: none(1), tight(1.25), snug(1.375), normal(1.5), relaxed(1.625), loose(2)
|
|
113
|
+
const lineHeightScale = {
|
|
114
|
+
none: "none", // line-height: 1
|
|
115
|
+
tight: "tight", // line-height: 1.25
|
|
116
|
+
snug: "snug", // line-height: 1.375
|
|
117
|
+
normal: "normal", // line-height: 1.5
|
|
118
|
+
relaxed: "relaxed", // line-height: 1.625
|
|
119
|
+
loose: "loose" // line-height: 2
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Letter spacing scale mapping Tailwind values to SenangStart semantic values
|
|
123
|
+
// Engine native values: tighter(-0.05em), tight(-0.025em), normal(0), wide(0.025em), wider(0.05em), widest(0.1em)
|
|
124
|
+
const letterSpacingScale = {
|
|
125
|
+
tighter: "tighter", // letter-spacing: -0.05em
|
|
126
|
+
tight: "tight", // letter-spacing: -0.025em
|
|
127
|
+
normal: "normal", // letter-spacing: 0
|
|
128
|
+
wide: "wide", // letter-spacing: 0.025em
|
|
129
|
+
wider: "wider", // letter-spacing: 0.05em
|
|
130
|
+
widest: "widest" // letter-spacing: 0.1em
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Z-index scale mapping Tailwind values to SenangStart semantic values
|
|
134
|
+
// Engine native values: base(0), low(10), mid(50), high(100), top(9999)
|
|
135
|
+
const zIndexScale = {
|
|
136
|
+
0: "base", // z-index: 0
|
|
137
|
+
10: "low", // z-index: 10
|
|
138
|
+
20: "low", // z-index: 20
|
|
139
|
+
30: "low", // z-index: 30
|
|
140
|
+
40: "low", // z-index: 40
|
|
141
|
+
50: "mid", // z-index: 50
|
|
142
|
+
60: "high", // z-index: 60
|
|
143
|
+
70: "high", // z-index: 70
|
|
144
|
+
80: "high", // z-index: 80
|
|
145
|
+
90: "high", // z-index: 90
|
|
146
|
+
100: "high", // z-index: 100
|
|
147
|
+
auto: "auto" // z-index: auto
|
|
148
|
+
};
|
|
149
|
+
|
|
111
150
|
// Fraction scale mapping Tailwind fractions to SenangStart semantic values
|
|
112
151
|
// Used for positioning (left-1/2) and transforms (translate-x-1/2)
|
|
113
152
|
const fractionScale = {
|
|
@@ -181,23 +220,108 @@ const layoutMappings = {
|
|
|
181
220
|
};
|
|
182
221
|
|
|
183
222
|
const visualKeywords = {
|
|
223
|
+
// Font style
|
|
184
224
|
italic: "italic",
|
|
185
225
|
"not-italic": "not-italic",
|
|
226
|
+
|
|
227
|
+
// Font smoothing
|
|
186
228
|
antialiased: "antialiased",
|
|
229
|
+
"subpixel-antialiased": "subpixel-antialiased",
|
|
230
|
+
|
|
231
|
+
// Text transform
|
|
187
232
|
uppercase: "uppercase",
|
|
188
233
|
lowercase: "lowercase",
|
|
189
234
|
capitalize: "capitalize",
|
|
190
235
|
"normal-case": "normal-case",
|
|
236
|
+
|
|
237
|
+
// Text decoration
|
|
191
238
|
underline: "underline",
|
|
239
|
+
overline: "overline",
|
|
192
240
|
"line-through": "line-through",
|
|
193
241
|
"no-underline": "no-underline",
|
|
242
|
+
|
|
243
|
+
// Text decoration style
|
|
244
|
+
"decoration-solid": "decoration-solid",
|
|
245
|
+
"decoration-double": "decoration-double",
|
|
246
|
+
"decoration-dotted": "decoration-dotted",
|
|
247
|
+
"decoration-dashed": "decoration-dashed",
|
|
248
|
+
"decoration-wavy": "decoration-wavy",
|
|
249
|
+
|
|
250
|
+
// Text overflow
|
|
194
251
|
truncate: "truncate",
|
|
195
|
-
"
|
|
252
|
+
"text-ellipsis": "text-ellipsis",
|
|
253
|
+
"text-clip": "text-clip",
|
|
254
|
+
|
|
255
|
+
// Text wrap
|
|
256
|
+
"text-wrap": "text-wrap",
|
|
257
|
+
"text-nowrap": "text-nowrap",
|
|
258
|
+
"text-balance": "text-balance",
|
|
259
|
+
"text-pretty": "text-pretty",
|
|
260
|
+
|
|
261
|
+
// Whitespace
|
|
262
|
+
"whitespace-normal": "whitespace-normal",
|
|
263
|
+
"whitespace-nowrap": "whitespace-nowrap",
|
|
264
|
+
"whitespace-pre": "whitespace-pre",
|
|
265
|
+
"whitespace-pre-line": "whitespace-pre-line",
|
|
266
|
+
"whitespace-pre-wrap": "whitespace-pre-wrap",
|
|
267
|
+
"whitespace-break-spaces": "whitespace-break-spaces",
|
|
268
|
+
|
|
269
|
+
// Word break
|
|
270
|
+
"break-normal": "break-normal",
|
|
271
|
+
"break-words": "break-words",
|
|
272
|
+
"break-all": "break-all",
|
|
273
|
+
"break-keep": "break-keep",
|
|
274
|
+
|
|
275
|
+
// Hyphens
|
|
276
|
+
"hyphens-none": "hyphens-none",
|
|
277
|
+
"hyphens-manual": "hyphens-manual",
|
|
278
|
+
"hyphens-auto": "hyphens-auto",
|
|
279
|
+
|
|
280
|
+
// List style
|
|
281
|
+
"list-none": "list-none",
|
|
282
|
+
"list-disc": "list-disc",
|
|
283
|
+
"list-decimal": "list-decimal",
|
|
284
|
+
"list-inside": "list-inside",
|
|
285
|
+
"list-outside": "list-outside",
|
|
286
|
+
|
|
287
|
+
// Cursor
|
|
288
|
+
"cursor-auto": "cursor:auto",
|
|
196
289
|
"cursor-default": "cursor:default",
|
|
290
|
+
"cursor-pointer": "cursor:pointer",
|
|
291
|
+
"cursor-wait": "cursor:wait",
|
|
292
|
+
"cursor-text": "cursor:text",
|
|
293
|
+
"cursor-move": "cursor:move",
|
|
197
294
|
"cursor-not-allowed": "cursor:not-allowed",
|
|
295
|
+
"cursor-grab": "cursor:grab",
|
|
296
|
+
"cursor-grabbing": "cursor:grabbing",
|
|
297
|
+
|
|
298
|
+
// User select
|
|
198
299
|
"select-none": "select:none",
|
|
199
300
|
"select-text": "select:text",
|
|
200
301
|
"select-all": "select:all",
|
|
302
|
+
"select-auto": "select:auto",
|
|
303
|
+
|
|
304
|
+
// Pointer events
|
|
305
|
+
"pointer-events-none": "pointer-events:none",
|
|
306
|
+
"pointer-events-auto": "pointer-events:auto",
|
|
307
|
+
|
|
308
|
+
// Appearance
|
|
309
|
+
"appearance-none": "appearance:none",
|
|
310
|
+
"appearance-auto": "appearance:auto",
|
|
311
|
+
|
|
312
|
+
// 3D Transforms
|
|
313
|
+
perspective: "perspective",
|
|
314
|
+
"perspective-origin": "perspective-origin",
|
|
315
|
+
"transform-style": "transform-style",
|
|
316
|
+
"backface-visibility": "backface",
|
|
317
|
+
mask: "mask",
|
|
318
|
+
"mask-image": "mask-image",
|
|
319
|
+
"mask-mode": "mask-mode",
|
|
320
|
+
"mask-origin": "mask-origin",
|
|
321
|
+
"mask-position": "mask-position",
|
|
322
|
+
"mask-repeat": "mask-repeat",
|
|
323
|
+
"mask-size": "mask-size",
|
|
324
|
+
"mask-type": "mask-type"
|
|
201
325
|
};
|
|
202
326
|
|
|
203
327
|
// ============================
|
|
@@ -206,7 +330,7 @@ const visualKeywords = {
|
|
|
206
330
|
|
|
207
331
|
function getSpacing(value, exact) {
|
|
208
332
|
// Check if it's already an arbitrary value with brackets
|
|
209
|
-
if (value.startsWith('[') && value.endsWith(']')) {
|
|
333
|
+
if (value && value.startsWith('[') && value.endsWith(']')) {
|
|
210
334
|
return value; // Return as-is, don't double-wrap
|
|
211
335
|
}
|
|
212
336
|
if (exact) {
|
|
@@ -343,6 +467,20 @@ function convertClass(twClass, exact) {
|
|
|
343
467
|
return attachExtra({ cat: "visual", val: prefix + "text-size:" + size });
|
|
344
468
|
}
|
|
345
469
|
|
|
470
|
+
// Line height
|
|
471
|
+
const leadingMatch = baseClass.match(/^leading-(\[.+\]|none|tight|snug|normal|relaxed|loose)$/);
|
|
472
|
+
if (leadingMatch) {
|
|
473
|
+
const val = leadingMatch[1];
|
|
474
|
+
return attachExtra({ cat: "visual", val: prefix + "leading:" + (lineHeightScale[val] || val) });
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Letter spacing
|
|
478
|
+
const trackingMatch = baseClass.match(/^tracking-(\[.+\]|tighter|tight|normal|wide|wider|widest)$/);
|
|
479
|
+
if (trackingMatch) {
|
|
480
|
+
const val = trackingMatch[1];
|
|
481
|
+
return attachExtra({ cat: "visual", val: prefix + "tracking:" + (letterSpacingScale[val] || val) });
|
|
482
|
+
}
|
|
483
|
+
|
|
346
484
|
// Background color
|
|
347
485
|
const bgMatch = baseClass.match(
|
|
348
486
|
/^bg-((?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|white|black)(?:-\d+)?|transparent|current|inherit)$/
|
|
@@ -410,7 +548,7 @@ function convertClass(twClass, exact) {
|
|
|
410
548
|
const side = marginMatch[1] ? "-" + marginMatch[1] : "";
|
|
411
549
|
let val = getSpacing(marginMatch[2], exact);
|
|
412
550
|
|
|
413
|
-
if (isNeg) {
|
|
551
|
+
if (isNeg && val) {
|
|
414
552
|
if (val.startsWith('[') && val.endsWith(']')) {
|
|
415
553
|
// Handle arbitrary value: [10px] -> [-10px]
|
|
416
554
|
const inner = val.slice(1, -1);
|
|
@@ -508,14 +646,14 @@ function convertClass(twClass, exact) {
|
|
|
508
646
|
if (positionMatch) {
|
|
509
647
|
const prop = positionMatch[1];
|
|
510
648
|
let val = positionMatch[2];
|
|
511
|
-
|
|
512
|
-
if (val === '0') {
|
|
513
|
-
val = 'none';
|
|
514
|
-
} else if (val.startsWith('[') && val.endsWith(']')) {
|
|
649
|
+
if (val && val.startsWith('[') && val.endsWith(']')) {
|
|
515
650
|
// Keep arbitrary values as-is
|
|
516
651
|
} else if (fractionScale[val]) {
|
|
517
652
|
// Map fractions to semantic names (1/2 → half, etc.)
|
|
518
653
|
val = fractionScale[val];
|
|
654
|
+
} else if (val === '0') {
|
|
655
|
+
// Keep 0 as-is for positioning (CSS: top: 0, not top: none)
|
|
656
|
+
val = '0';
|
|
519
657
|
} else {
|
|
520
658
|
val = getSpacing(val, exact);
|
|
521
659
|
}
|
|
@@ -530,7 +668,7 @@ function convertClass(twClass, exact) {
|
|
|
530
668
|
let val = translateMatch[3];
|
|
531
669
|
|
|
532
670
|
// Map fractions and values
|
|
533
|
-
if (val.startsWith('[') && val.endsWith(']')) {
|
|
671
|
+
if (val && val.startsWith('[') && val.endsWith(']')) {
|
|
534
672
|
// Keep arbitrary values as-is, but handle negative
|
|
535
673
|
if (isNeg) {
|
|
536
674
|
const inner = val.slice(1, -1);
|
|
@@ -560,6 +698,33 @@ function convertClass(twClass, exact) {
|
|
|
560
698
|
return attachExtra({ cat: "layout", val: prefix + "order:" + orderMatch[1] });
|
|
561
699
|
}
|
|
562
700
|
|
|
701
|
+
// Z-index
|
|
702
|
+
const zIndexMatch = baseClass.match(/^-?z-(\d+|auto)$/);
|
|
703
|
+
if (zIndexMatch) {
|
|
704
|
+
const isNeg = baseClass.startsWith("-");
|
|
705
|
+
const val = zIndexMatch[1];
|
|
706
|
+
let zIndexVal = zIndexScale[val] || val;
|
|
707
|
+
if (isNeg) {
|
|
708
|
+
zIndexVal = `-${zIndexVal}`;
|
|
709
|
+
}
|
|
710
|
+
return attachExtra({ cat: "layout", val: prefix + "z:" + zIndexVal });
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Flex basis
|
|
714
|
+
const basisMatch = baseClass.match(/^basis-(\[.+\]|\d+\.?\d*|auto|full|1\/2|1\/3|2\/3|1\/4|2\/4|3\/4)$/);
|
|
715
|
+
if (basisMatch) {
|
|
716
|
+
let val = basisMatch[1];
|
|
717
|
+
if (val.startsWith('[') && val.endsWith(']')) {
|
|
718
|
+
// Keep arbitrary values as-is
|
|
719
|
+
} else if (fractionScale[val]) {
|
|
720
|
+
// Map fractions to semantic names (1/2 → half, etc.)
|
|
721
|
+
val = fractionScale[val];
|
|
722
|
+
} else if (val === '0') {
|
|
723
|
+
val = '0';
|
|
724
|
+
}
|
|
725
|
+
return attachExtra({ cat: "layout", val: prefix + "basis:" + val });
|
|
726
|
+
}
|
|
727
|
+
|
|
563
728
|
// Grid columns
|
|
564
729
|
const gridColsMatch = baseClass.match(/^grid-cols-(\d+|none)$/);
|
|
565
730
|
if (gridColsMatch) {
|
|
@@ -750,6 +915,138 @@ function convertClass(twClass, exact) {
|
|
|
750
915
|
});
|
|
751
916
|
}
|
|
752
917
|
|
|
918
|
+
// Border style
|
|
919
|
+
const borderStyleMatch = baseClass.match(/^border-(solid|dashed|dotted|double|none)$/);
|
|
920
|
+
if (borderStyleMatch) {
|
|
921
|
+
return attachExtra({
|
|
922
|
+
cat: "visual",
|
|
923
|
+
val: prefix + "border-style:" + borderStyleMatch[1],
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Filter utilities
|
|
928
|
+
// Blur
|
|
929
|
+
const blurMatch = baseClass.match(/^blur-(0|sm|md|lg|xl|2xl|3xl)$/);
|
|
930
|
+
if (blurMatch) {
|
|
931
|
+
const blurScale = {
|
|
932
|
+
'0': 'none',
|
|
933
|
+
'sm': 'tiny',
|
|
934
|
+
'md': 'small',
|
|
935
|
+
'lg': 'medium',
|
|
936
|
+
'xl': 'big',
|
|
937
|
+
'2xl': 'giant',
|
|
938
|
+
'3xl': 'vast'
|
|
939
|
+
};
|
|
940
|
+
return attachExtra({
|
|
941
|
+
cat: "visual",
|
|
942
|
+
val: prefix + "blur:" + blurScale[blurMatch[1]],
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Brightness
|
|
947
|
+
const brightnessMatch = baseClass.match(/^brightness-(0|50|75|90|95|100|105|110|125|150|200)$/);
|
|
948
|
+
if (brightnessMatch) {
|
|
949
|
+
const brightnessScale = {
|
|
950
|
+
'0': 'dim',
|
|
951
|
+
'50': 'dim',
|
|
952
|
+
'75': 'dark',
|
|
953
|
+
'90': 'dark',
|
|
954
|
+
'95': 'dark',
|
|
955
|
+
'100': 'normal',
|
|
956
|
+
'105': 'bright',
|
|
957
|
+
'110': 'bright',
|
|
958
|
+
'125': 'vivid',
|
|
959
|
+
'150': 'vivid',
|
|
960
|
+
'200': 'vivid'
|
|
961
|
+
};
|
|
962
|
+
return attachExtra({
|
|
963
|
+
cat: "visual",
|
|
964
|
+
val: prefix + "brightness:" + brightnessScale[brightnessMatch[1]],
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Contrast
|
|
969
|
+
const contrastMatch = baseClass.match(/^contrast-(0|50|75|100|125|150|200)$/);
|
|
970
|
+
if (contrastMatch) {
|
|
971
|
+
const contrastScale = {
|
|
972
|
+
'0': 'low',
|
|
973
|
+
'50': 'low',
|
|
974
|
+
'75': 'reduced',
|
|
975
|
+
'100': 'normal',
|
|
976
|
+
'125': 'high',
|
|
977
|
+
'150': 'high',
|
|
978
|
+
'200': 'max'
|
|
979
|
+
};
|
|
980
|
+
return attachExtra({
|
|
981
|
+
cat: "visual",
|
|
982
|
+
val: prefix + "contrast:" + contrastScale[contrastMatch[1]],
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Grayscale
|
|
987
|
+
const grayscaleMatch = baseClass.match(/^grayscale(0)?$/);
|
|
988
|
+
if (grayscaleMatch) {
|
|
989
|
+
const val = grayscaleMatch[1] === '0' ? 'none' : 'full';
|
|
990
|
+
return attachExtra({
|
|
991
|
+
cat: "visual",
|
|
992
|
+
val: prefix + "grayscale:" + val,
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Hue rotate
|
|
997
|
+
const hueRotateMatch = baseClass.match(/^hue-rotate-(0|15|30|60|90|180)$/);
|
|
998
|
+
if (hueRotateMatch) {
|
|
999
|
+
return attachExtra({
|
|
1000
|
+
cat: "visual",
|
|
1001
|
+
val: prefix + "hue-rotate:" + hueRotateMatch[1],
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// Invert
|
|
1006
|
+
const invertMatch = baseClass.match(/^invert(0)?$/);
|
|
1007
|
+
if (invertMatch) {
|
|
1008
|
+
const val = invertMatch[1] === '0' ? 'none' : 'full';
|
|
1009
|
+
return attachExtra({
|
|
1010
|
+
cat: "visual",
|
|
1011
|
+
val: prefix + "invert:" + val,
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Saturate
|
|
1016
|
+
const saturateMatch = baseClass.match(/^saturate-(0|50|100|150|200)$/);
|
|
1017
|
+
if (saturateMatch) {
|
|
1018
|
+
const saturateScale = {
|
|
1019
|
+
'0': 'none',
|
|
1020
|
+
'50': 'low',
|
|
1021
|
+
'100': 'normal',
|
|
1022
|
+
'150': 'high',
|
|
1023
|
+
'200': 'vivid'
|
|
1024
|
+
};
|
|
1025
|
+
return attachExtra({
|
|
1026
|
+
cat: "visual",
|
|
1027
|
+
val: prefix + "saturate:" + saturateScale[saturateMatch[1]],
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Sepia
|
|
1032
|
+
const sepiaMatch = baseClass.match(/^sepia(0)?$/);
|
|
1033
|
+
if (sepiaMatch) {
|
|
1034
|
+
const val = sepiaMatch[1] === '0' ? 'none' : 'full';
|
|
1035
|
+
return attachExtra({
|
|
1036
|
+
cat: "visual",
|
|
1037
|
+
val: prefix + "sepia:" + val,
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Animation utilities
|
|
1042
|
+
const animateMatch = baseClass.match(/^animate-(none|spin|ping|pulse|bounce)$/);
|
|
1043
|
+
if (animateMatch) {
|
|
1044
|
+
return attachExtra({
|
|
1045
|
+
cat: "visual",
|
|
1046
|
+
val: prefix + "animate:" + animateMatch[1],
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
|
|
753
1050
|
return null;
|
|
754
1051
|
}
|
|
755
1052
|
|
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
* One-time compilation of CSS from source files
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from 'fs';
|
|
7
7
|
import { join, dirname, resolve } from 'path';
|
|
8
8
|
import { defaultConfig, mergeConfig } from '../../config/defaults.js';
|
|
9
9
|
import { parseSource } from '../../compiler/parser.js';
|
|
10
|
-
import { tokenizeAll } from '../../compiler/tokenizer.js';
|
|
10
|
+
import { tokenizeAll, tokenizeAllWithBatching } from '../../compiler/tokenizer.js';
|
|
11
11
|
import { generateCSS, minifyCSS } from '../../compiler/generators/css.js';
|
|
12
12
|
import { generateAIContext } from '../../compiler/generators/ai-context.js';
|
|
13
13
|
import { generateTypeScript } from '../../compiler/generators/typescript.js';
|
|
14
14
|
import logger from '../../utils/logger.js';
|
|
15
|
+
import { validateThemeValue, getMemoryUsage } from '../../utils/common.js';
|
|
16
|
+
import { readMultipleFilesWithTimeout } from '../../utils/node-io.js';
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Find files matching content patterns
|
|
@@ -118,31 +120,67 @@ export async function build(options = {}) {
|
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
logger.info(`Found ${files.length} source files`);
|
|
121
|
-
|
|
122
|
-
// Parse all files
|
|
123
|
+
|
|
124
|
+
// Parse all files with timeout protection
|
|
123
125
|
const allTokens = {
|
|
124
126
|
layout: new Set(),
|
|
125
127
|
space: new Set(),
|
|
126
128
|
visual: new Set()
|
|
127
129
|
};
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
|
|
131
|
+
let failedFiles = 0;
|
|
132
|
+
const fileReadResults = await readMultipleFilesWithTimeout(files, 5000);
|
|
133
|
+
|
|
134
|
+
for (const { path: filePath, content, error } of fileReadResults) {
|
|
135
|
+
if (error) {
|
|
136
|
+
logger.warn(`Skipping ${filePath}: ${error.message}`);
|
|
137
|
+
failedFiles++;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
130
141
|
try {
|
|
131
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
132
142
|
const parsed = parseSource(content);
|
|
133
|
-
|
|
143
|
+
|
|
134
144
|
parsed.layout.forEach(t => allTokens.layout.add(t));
|
|
135
145
|
parsed.space.forEach(t => allTokens.space.add(t));
|
|
136
146
|
parsed.visual.forEach(t => allTokens.visual.add(t));
|
|
137
147
|
} catch (e) {
|
|
138
|
-
logger.warn(`Could not parse ${filePath}`);
|
|
148
|
+
logger.warn(`Could not parse ${filePath}: ${e.message}`);
|
|
149
|
+
failedFiles++;
|
|
139
150
|
}
|
|
140
151
|
}
|
|
141
|
-
|
|
152
|
+
|
|
153
|
+
if (failedFiles > 0) {
|
|
154
|
+
logger.warn(`${failedFiles} file(s) failed to process`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Calculate total token count
|
|
158
|
+
const totalTokens = allTokens.layout.size + allTokens.space.size + allTokens.visual.size;
|
|
159
|
+
logger.info(`Found ${totalTokens} unique token values`);
|
|
160
|
+
|
|
161
|
+
// Check memory usage and decide whether to use batch processing
|
|
162
|
+
const currentMemory = getMemoryUsage();
|
|
163
|
+
const useBatching = totalTokens > 10000 || currentMemory > 200;
|
|
164
|
+
|
|
142
165
|
// Tokenize
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
let tokens;
|
|
167
|
+
if (useBatching) {
|
|
168
|
+
logger.info('Using batch processing for memory protection');
|
|
169
|
+
tokens = await tokenizeAllWithBatching(allTokens, 1000);
|
|
170
|
+
} else {
|
|
171
|
+
tokens = tokenizeAll(allTokens);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
logger.info(`Generated ${tokens.length} tokens`);
|
|
175
|
+
|
|
176
|
+
// Check for invalid tokens
|
|
177
|
+
const invalidTokens = tokens.filter(token => token.error);
|
|
178
|
+
if (invalidTokens.length > 0) {
|
|
179
|
+
logger.warn(`${invalidTokens.length} error(s) found in source:`);
|
|
180
|
+
for (const token of invalidTokens) {
|
|
181
|
+
logger.warn(` • ${token.raw} (${token.attrType}): ${token.error}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
146
184
|
|
|
147
185
|
// Generate CSS
|
|
148
186
|
let css = generateCSS(tokens, config);
|