@aihu/css-engine 0.3.0 → 0.4.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.
- package/README.md +11 -11
- package/crates/aihu-css-core/src/apply.rs +314 -0
- package/crates/aihu-css-core/src/bin/main.rs +8 -7
- package/crates/aihu-css-core/src/cache.rs +8 -5
- package/crates/aihu-css-core/src/emit.rs +110 -30
- package/crates/aihu-css-core/src/lib.rs +10 -2
- package/crates/aihu-css-core/src/palette.rs +301 -0
- package/crates/aihu-css-core/src/style_parser.rs +587 -0
- package/crates/aihu-css-core/src/tokens.rs +625 -29
- package/crates/aihu-css-core/src/variants.rs +154 -7
- package/crates/aihu-css-core/tests/apply.rs +203 -0
- package/crates/aihu-css-core/tests/apply_regression.rs +150 -0
- package/crates/aihu-css-core/tests/binary_error.rs +61 -0
- package/crates/aihu-css-core/tests/cache.rs +8 -8
- package/crates/aihu-css-core/tests/emit.rs +95 -36
- package/crates/aihu-css-core/tests/parity.rs +274 -0
- package/crates/aihu-css-core/tests/progressive_snapshot.rs +8 -8
- package/crates/aihu-css-core/tests/scoped_snapshot.rs +49 -11
- package/crates/aihu-css-core/tests/snapshots/apply__apply_inside_nested_rule.snap +11 -0
- package/crates/aihu-css-core/tests/snapshots/apply__arbitrary_value_utility_in_apply.snap +8 -0
- package/crates/aihu-css-core/tests/snapshots/apply__arbitrary_value_variant_in_apply.snap +10 -0
- package/crates/aihu-css-core/tests/snapshots/apply__base_utility_inlines_declarations.snap +9 -0
- package/crates/aihu-css-core/tests/snapshots/apply__dark_variant_cascade_in_apply.snap +10 -0
- package/crates/aihu-css-core/tests/snapshots/apply__data_attribute_variant.snap +10 -0
- package/crates/aihu-css-core/tests/snapshots/apply__multi_token_apply.snap +11 -0
- package/crates/aihu-css-core/tests/snapshots/apply__multiple_apply_directives_per_rule.snap +12 -0
- package/crates/aihu-css-core/tests/snapshots/apply__responsive_variant_wraps_media.snap +12 -0
- package/crates/aihu-css-core/tests/snapshots/apply__single_variant_lifts_to_nested_rule.snap +10 -0
- package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__anchor_snapshot.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__popover_snapshot.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__text_balance_snapshot.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__view_transition_snapshot.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_animate_spin_hoists_keyframes.snap +1 -1
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_divide_y_nested_rule.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_output_for_sfc.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_space_y_nested_rule.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_transition_and_transform.snap +1 -1
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_with_authored_style_block.snap +6 -2
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_with_global_style_block.snap +5 -2
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__standard_variants.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__theme_default_vs_override.snap +2 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__wc_native_variants.snap +1 -0
- package/crates/aihu-css-core/tests/style_parser.rs +257 -0
- package/crates/aihu-css-core/tests/tokens.rs +52 -0
- package/dist/index.d.ts +0 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -18
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
//! Structured `@style`-rule parser tests (T2, R-SHARED-PARSER).
|
|
2
|
+
//!
|
|
3
|
+
//! This parser is the SINGLE source reused by `@apply` (T3) and variant
|
|
4
|
+
//! validation (PR-3), so the tests pin the public API both consumers depend on:
|
|
5
|
+
//! the rule tree, per-rule `@apply` directives, and each rule's selector
|
|
6
|
+
//! context. Codex flagged a naive string scanner as a trap, so the tests
|
|
7
|
+
//! deliberately stress comments, strings, arbitrary-value utilities, `;`-in-
|
|
8
|
+
//! values, and brace nesting.
|
|
9
|
+
|
|
10
|
+
use aihu_css_core::style_parser::{parse_style, StyleNode, StyleParseError};
|
|
11
|
+
|
|
12
|
+
/// Find the first top-level rule, asserting there is one.
|
|
13
|
+
fn first_rule(sheet: &aihu_css_core::StyleSheet) -> &aihu_css_core::StyleRule {
|
|
14
|
+
sheet
|
|
15
|
+
.nodes
|
|
16
|
+
.iter()
|
|
17
|
+
.find_map(|n| match n {
|
|
18
|
+
StyleNode::Rule(r) => Some(r),
|
|
19
|
+
_ => None,
|
|
20
|
+
})
|
|
21
|
+
.expect("expected at least one top-level rule")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[test]
|
|
25
|
+
fn parses_a_simple_rule_with_declarations() {
|
|
26
|
+
let sheet = parse_style(".box { color: red; padding: 4px; }").unwrap();
|
|
27
|
+
let rule = first_rule(&sheet);
|
|
28
|
+
assert_eq!(rule.selector, ".box");
|
|
29
|
+
assert_eq!(rule.declarations.len(), 2);
|
|
30
|
+
assert_eq!(rule.declarations[0].prop, "color");
|
|
31
|
+
assert_eq!(rule.declarations[0].value, "red");
|
|
32
|
+
assert_eq!(rule.declarations[1].prop, "padding");
|
|
33
|
+
assert_eq!(rule.declarations[1].value, "4px");
|
|
34
|
+
assert!(rule.applies.is_empty());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#[test]
|
|
38
|
+
fn parses_multiple_top_level_rules() {
|
|
39
|
+
let sheet = parse_style(".a { color: red; } .b, .c { color: blue; }").unwrap();
|
|
40
|
+
let rules: Vec<_> = sheet
|
|
41
|
+
.nodes
|
|
42
|
+
.iter()
|
|
43
|
+
.filter_map(|n| match n {
|
|
44
|
+
StyleNode::Rule(r) => Some(r),
|
|
45
|
+
_ => None,
|
|
46
|
+
})
|
|
47
|
+
.collect();
|
|
48
|
+
assert_eq!(rules.len(), 2);
|
|
49
|
+
assert_eq!(rules[0].selector, ".a");
|
|
50
|
+
// Selector list preserved verbatim (validation/PR-3 needs the raw context).
|
|
51
|
+
assert_eq!(rules[1].selector, ".b, .c");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn captures_apply_directives_per_rule() {
|
|
56
|
+
let sheet = parse_style(".btn { @apply bg-primary p-4; color: red; @apply hover:bg-accent; }")
|
|
57
|
+
.unwrap();
|
|
58
|
+
let rule = first_rule(&sheet);
|
|
59
|
+
assert_eq!(rule.applies.len(), 2);
|
|
60
|
+
assert_eq!(rule.applies[0].tokens, vec!["bg-primary", "p-4"]);
|
|
61
|
+
assert_eq!(rule.applies[1].tokens, vec!["hover:bg-accent"]);
|
|
62
|
+
// The interleaved declaration is still captured.
|
|
63
|
+
assert_eq!(rule.declarations.len(), 1);
|
|
64
|
+
assert_eq!(rule.declarations[0].prop, "color");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#[test]
|
|
68
|
+
fn apply_keyword_is_not_confused_with_prefix() {
|
|
69
|
+
// `@applyfoo` is NOT an `@apply` directive — it stays an at-statement, not a
|
|
70
|
+
// parsed apply.
|
|
71
|
+
let sheet = parse_style(".x { @applyfoo bar; }").unwrap();
|
|
72
|
+
let rule = first_rule(&sheet);
|
|
73
|
+
assert!(rule.applies.is_empty(), "@applyfoo must not parse as @apply");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[test]
|
|
77
|
+
fn is_comment_aware() {
|
|
78
|
+
// Braces, semicolons, and `@apply`-looking text inside comments must be
|
|
79
|
+
// ignored entirely.
|
|
80
|
+
let css = r#"
|
|
81
|
+
/* a comment with { braces } and ; and @apply fake; */
|
|
82
|
+
.a {
|
|
83
|
+
/* inner comment ; { } */
|
|
84
|
+
color: red; /* trailing */
|
|
85
|
+
}
|
|
86
|
+
"#;
|
|
87
|
+
let sheet = parse_style(css).unwrap();
|
|
88
|
+
let rule = first_rule(&sheet);
|
|
89
|
+
assert_eq!(rule.selector, ".a");
|
|
90
|
+
assert_eq!(rule.declarations.len(), 1);
|
|
91
|
+
assert_eq!(rule.declarations[0].prop, "color");
|
|
92
|
+
assert_eq!(rule.declarations[0].value, "red");
|
|
93
|
+
// The fake @apply inside the comment was NOT captured.
|
|
94
|
+
assert!(rule.applies.is_empty());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#[test]
|
|
98
|
+
fn is_string_aware_for_braces_and_semicolons() {
|
|
99
|
+
// `;` and `}` inside a quoted string must not terminate the declaration or
|
|
100
|
+
// rule.
|
|
101
|
+
let css = r#".q { content: "a; b } c"; color: red; }"#;
|
|
102
|
+
let sheet = parse_style(css).unwrap();
|
|
103
|
+
let rule = first_rule(&sheet);
|
|
104
|
+
assert_eq!(rule.declarations.len(), 2);
|
|
105
|
+
assert_eq!(rule.declarations[0].prop, "content");
|
|
106
|
+
assert_eq!(rule.declarations[0].value, r#""a; b } c""#);
|
|
107
|
+
assert_eq!(rule.declarations[1].prop, "color");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[test]
|
|
111
|
+
fn semicolon_inside_url_value_does_not_split() {
|
|
112
|
+
// A `;` (and `:`) inside `url(...)` / data URI must stay in the value.
|
|
113
|
+
let css = r#".bg { background: url("data:image/svg+xml;base64,AAAA") center; }"#;
|
|
114
|
+
let sheet = parse_style(css).unwrap();
|
|
115
|
+
let rule = first_rule(&sheet);
|
|
116
|
+
assert_eq!(rule.declarations.len(), 1);
|
|
117
|
+
assert_eq!(rule.declarations[0].prop, "background");
|
|
118
|
+
assert_eq!(
|
|
119
|
+
rule.declarations[0].value,
|
|
120
|
+
r#"url("data:image/svg+xml;base64,AAAA") center"#
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#[test]
|
|
125
|
+
fn arbitrary_value_apply_tokens_survive() {
|
|
126
|
+
// `bg-[#fff]` and `w-[calc(100%-1rem)]` must round-trip as single tokens.
|
|
127
|
+
let sheet = parse_style(".c { @apply bg-[#fff] w-[calc(100%-1rem)] p-4; }").unwrap();
|
|
128
|
+
let rule = first_rule(&sheet);
|
|
129
|
+
assert_eq!(
|
|
130
|
+
rule.applies[0].tokens,
|
|
131
|
+
vec!["bg-[#fff]", "w-[calc(100%-1rem)]", "p-4"]
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[test]
|
|
136
|
+
fn parses_nested_media_at_rule() {
|
|
137
|
+
let css = ".a { color: red; @media (min-width: 600px) { &:hover { color: blue; } } }";
|
|
138
|
+
let sheet = parse_style(css).unwrap();
|
|
139
|
+
let rule = first_rule(&sheet);
|
|
140
|
+
assert_eq!(rule.declarations.len(), 1);
|
|
141
|
+
assert_eq!(rule.nested.len(), 1);
|
|
142
|
+
match &rule.nested[0] {
|
|
143
|
+
StyleNode::AtRule(at) => {
|
|
144
|
+
assert_eq!(at.name, "@media");
|
|
145
|
+
assert_eq!(at.prelude, "(min-width: 600px)");
|
|
146
|
+
// The body has one nested rule.
|
|
147
|
+
match &at.body[0] {
|
|
148
|
+
StyleNode::Rule(inner) => {
|
|
149
|
+
assert_eq!(inner.selector, "&:hover");
|
|
150
|
+
assert_eq!(inner.declarations[0].prop, "color");
|
|
151
|
+
}
|
|
152
|
+
other => panic!("expected nested rule, got {other:?}"),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
other => panic!("expected @media at-rule, got {other:?}"),
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
#[test]
|
|
160
|
+
fn parses_top_level_supports_and_container() {
|
|
161
|
+
let css = "@supports (display: grid) { .g { display: grid; } } \
|
|
162
|
+
@container (min-width: 20rem) { .c { color: red; } }";
|
|
163
|
+
let sheet = parse_style(css).unwrap();
|
|
164
|
+
let names: Vec<&str> = sheet
|
|
165
|
+
.nodes
|
|
166
|
+
.iter()
|
|
167
|
+
.filter_map(|n| match n {
|
|
168
|
+
StyleNode::AtRule(at) => Some(at.name.as_str()),
|
|
169
|
+
_ => None,
|
|
170
|
+
})
|
|
171
|
+
.collect();
|
|
172
|
+
assert_eq!(names, vec!["@supports", "@container"]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[test]
|
|
176
|
+
fn parses_nested_style_rule() {
|
|
177
|
+
// Native CSS nesting: a rule inside a rule (with `&`).
|
|
178
|
+
let css = ".card { color: red; &:hover { color: blue; } }";
|
|
179
|
+
let sheet = parse_style(css).unwrap();
|
|
180
|
+
let rule = first_rule(&sheet);
|
|
181
|
+
assert_eq!(rule.declarations.len(), 1);
|
|
182
|
+
assert_eq!(rule.nested.len(), 1);
|
|
183
|
+
match &rule.nested[0] {
|
|
184
|
+
StyleNode::Rule(inner) => assert_eq!(inner.selector, "&:hover"),
|
|
185
|
+
other => panic!("expected nested rule, got {other:?}"),
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#[test]
|
|
190
|
+
fn data_attribute_selector_context_is_preserved() {
|
|
191
|
+
// PR-3 validation needs the raw selector to spot `[data-variant="x"]`.
|
|
192
|
+
let css = r#".btn[data-variant="primary"] { color: red; }"#;
|
|
193
|
+
let sheet = parse_style(css).unwrap();
|
|
194
|
+
let rule = first_rule(&sheet);
|
|
195
|
+
assert_eq!(rule.selector, r#".btn[data-variant="primary"]"#);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#[test]
|
|
199
|
+
fn colon_in_selector_is_not_a_declaration_split() {
|
|
200
|
+
// `:hover` / `:is(...)` in a selector must not be mistaken for a decl `:`.
|
|
201
|
+
let css = ":is(.a, .b):hover { color: red; }";
|
|
202
|
+
let sheet = parse_style(css).unwrap();
|
|
203
|
+
let rule = first_rule(&sheet);
|
|
204
|
+
assert_eq!(rule.selector, ":is(.a, .b):hover");
|
|
205
|
+
assert_eq!(rule.declarations[0].prop, "color");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#[test]
|
|
209
|
+
fn unchanged_block_round_trips_equivalently() {
|
|
210
|
+
// Parse → to_css → re-parse must yield the same rule tree (semantic round-
|
|
211
|
+
// trip; formatting is normalized by `to_css`).
|
|
212
|
+
let css = r#"
|
|
213
|
+
.a { color: red; padding: 4px; }
|
|
214
|
+
.b:hover { color: blue; @apply bg-primary p-4; }
|
|
215
|
+
@media (min-width: 600px) {
|
|
216
|
+
.a { color: green; }
|
|
217
|
+
}
|
|
218
|
+
"#;
|
|
219
|
+
let first = parse_style(css).unwrap();
|
|
220
|
+
let rendered = first.to_css();
|
|
221
|
+
let second = parse_style(&rendered).unwrap();
|
|
222
|
+
assert_eq!(
|
|
223
|
+
first, second,
|
|
224
|
+
"round-trip changed the tree:\n{rendered}"
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#[test]
|
|
229
|
+
fn unbalanced_braces_error() {
|
|
230
|
+
let err = parse_style(".a { color: red; ").unwrap_err();
|
|
231
|
+
assert_eq!(err, StyleParseError::UnbalancedBraces);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#[test]
|
|
235
|
+
fn unterminated_comment_error() {
|
|
236
|
+
let err = parse_style(".a { /* never closed ").unwrap_err();
|
|
237
|
+
assert_eq!(err, StyleParseError::UnterminatedComment);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#[test]
|
|
241
|
+
fn unterminated_string_error() {
|
|
242
|
+
let err = parse_style(r#".a { content: "oops; }"#).unwrap_err();
|
|
243
|
+
assert_eq!(err, StyleParseError::UnterminatedString);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[test]
|
|
247
|
+
fn empty_input_is_empty_sheet() {
|
|
248
|
+
let sheet = parse_style(" \n ").unwrap();
|
|
249
|
+
assert!(sheet.nodes.is_empty());
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#[test]
|
|
253
|
+
fn stray_semicolons_are_ignored() {
|
|
254
|
+
let sheet = parse_style(";; .a { color: red; };").unwrap();
|
|
255
|
+
let rule = first_rule(&sheet);
|
|
256
|
+
assert_eq!(rule.selector, ".a");
|
|
257
|
+
}
|
|
@@ -544,3 +544,55 @@ fn named_container_marker_emits_type_and_name() {
|
|
|
544
544
|
".@container/sidebar { container-type: inline-size; container-name: sidebar; }\n"
|
|
545
545
|
);
|
|
546
546
|
}
|
|
547
|
+
|
|
548
|
+
// ── Issue #280: dictionary misses, grid arbitrary, opacity modifiers ─────────
|
|
549
|
+
|
|
550
|
+
#[test]
|
|
551
|
+
fn font_family_utilities_emit() {
|
|
552
|
+
assert_eq!(css(&["font-mono"]), ".font-mono { font-family: var(--font-mono); }\n");
|
|
553
|
+
assert_eq!(css(&["font-sans"]), ".font-sans { font-family: var(--font-sans); }\n");
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
#[test]
|
|
557
|
+
fn bare_directional_borders_emit_1px() {
|
|
558
|
+
assert_eq!(css(&["border-t"]), ".border-t { border-top-width: 1px; }\n");
|
|
559
|
+
assert_eq!(css(&["border-r"]), ".border-r { border-right-width: 1px; }\n");
|
|
560
|
+
assert_eq!(css(&["border-b"]), ".border-b { border-bottom-width: 1px; }\n");
|
|
561
|
+
assert_eq!(css(&["border-l"]), ".border-l { border-left-width: 1px; }\n");
|
|
562
|
+
assert_eq!(css(&["border-x"]), ".border-x { border-inline-width: 1px; }\n");
|
|
563
|
+
assert_eq!(css(&["border-y"]), ".border-y { border-block-width: 1px; }\n");
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
#[test]
|
|
567
|
+
fn grid_arbitrary_template_columns_emits() {
|
|
568
|
+
assert_eq!(
|
|
569
|
+
css(&["grid-cols-[2fr_1fr_1fr_1.5fr_1.5fr]"]),
|
|
570
|
+
".grid-cols-[2fr_1fr_1fr_1.5fr_1.5fr] { grid-template-columns: 2fr 1fr 1fr 1.5fr 1.5fr; }\n"
|
|
571
|
+
);
|
|
572
|
+
assert_eq!(
|
|
573
|
+
css(&["grid-rows-[auto_1fr]"]),
|
|
574
|
+
".grid-rows-[auto_1fr] { grid-template-rows: auto 1fr; }\n"
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
#[test]
|
|
579
|
+
fn color_opacity_modifier_emits_color_mix() {
|
|
580
|
+
assert_eq!(
|
|
581
|
+
css(&["bg-accent/15"]),
|
|
582
|
+
".bg-accent/15 { background-color: color-mix(in oklab, var(--color-accent) 15%, transparent); }\n"
|
|
583
|
+
);
|
|
584
|
+
assert_eq!(
|
|
585
|
+
css(&["bg-primary/50"]),
|
|
586
|
+
".bg-primary/50 { background-color: color-mix(in oklab, var(--color-primary) 50%, transparent); }\n"
|
|
587
|
+
);
|
|
588
|
+
assert_eq!(
|
|
589
|
+
css(&["text-red-500/30"]),
|
|
590
|
+
".text-red-500/30 { color: color-mix(in oklab, var(--color-red-500) 30%, transparent); }\n"
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
#[test]
|
|
595
|
+
fn sizing_fraction_not_treated_as_opacity() {
|
|
596
|
+
// `w-1/2` is a width fraction, not a color opacity — must stay 50%.
|
|
597
|
+
assert_eq!(css(&["w-1/2"]), ".w-1/2 { width: 50%; }\n");
|
|
598
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,15 +21,6 @@ import { i as defineStylePack, n as StylePackInput, r as TokenMap, t as StylePac
|
|
|
21
21
|
* still rejects a zero-byte placeholder.
|
|
22
22
|
*/
|
|
23
23
|
declare function isUsableExecutable(candidate: string): boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Compile a list of utility class names to CSS.
|
|
26
|
-
*
|
|
27
|
-
* Plan 1 bootstrap — supports a hardcoded subset; see crates/aihu-css-core/src/tokens.rs.
|
|
28
|
-
* Plan 2 wires the AST scanner so callers pass `.aihu` SFC ASTs instead of raw class lists.
|
|
29
|
-
*
|
|
30
|
-
* @param classes - utility class names like `['bg-primary', 'p-4']`
|
|
31
|
-
* @returns CSS string with one rule per known class
|
|
32
|
-
*/
|
|
33
24
|
declare function compile(classes: string[]): string;
|
|
34
25
|
/**
|
|
35
26
|
* Compile a `.aihu` SFC source string to scoped, shadow-DOM-embedded CSS.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;AA8FA;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;AA8FA;;;;;AA2IA;;;;;AAqBA;;;;;;;iBAhKgB,kBAAA,CAAmB,SAAA;AAAA,iBA2InB,OAAA,CAAQ,OAAA;;;;;;;;;;;;;;iBAqBR,UAAA,CAAW,MAAA,UAAgB,EAAA"}
|
package/dist/index.js
CHANGED
|
@@ -114,17 +114,33 @@ function buildMissingBinaryError(descriptor, devCandidates) {
|
|
|
114
114
|
* @param classes - utility class names like `['bg-primary', 'p-4']`
|
|
115
115
|
* @returns CSS string with one rule per known class
|
|
116
116
|
*/
|
|
117
|
+
/**
|
|
118
|
+
* Spawn the native compiler, returning stdout on success. On a non-zero exit
|
|
119
|
+
* (the binary's R-RESULT error path) throw an `Error` carrying the binary's
|
|
120
|
+
* stderr message rather than letting `execFileSync`'s opaque status error
|
|
121
|
+
* surface. stderr is PIPED (not inherited) so the message lands in the thrown
|
|
122
|
+
* error instead of the parent's console.
|
|
123
|
+
*/
|
|
124
|
+
function runBinary(bin, args, input) {
|
|
125
|
+
try {
|
|
126
|
+
return execFileSync(bin, args, {
|
|
127
|
+
input,
|
|
128
|
+
encoding: "utf-8",
|
|
129
|
+
stdio: [
|
|
130
|
+
"pipe",
|
|
131
|
+
"pipe",
|
|
132
|
+
"pipe"
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
const e = err;
|
|
137
|
+
const detail = (typeof e.stderr === "string" ? e.stderr : e.stderr instanceof Buffer ? e.stderr.toString("utf-8") : "").trim() || e.message || "unknown error";
|
|
138
|
+
throw new Error(`[@aihu/css-engine] CSS compile failed: ${detail}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
117
141
|
function compile(classes) {
|
|
118
142
|
if (classes.length === 0) return "";
|
|
119
|
-
return
|
|
120
|
-
input: classes.join("\n"),
|
|
121
|
-
encoding: "utf-8",
|
|
122
|
-
stdio: [
|
|
123
|
-
"pipe",
|
|
124
|
-
"pipe",
|
|
125
|
-
"inherit"
|
|
126
|
-
]
|
|
127
|
-
});
|
|
143
|
+
return runBinary(resolveBinary(), [], classes.join("\n"));
|
|
128
144
|
}
|
|
129
145
|
/**
|
|
130
146
|
* Compile a `.aihu` SFC source string to scoped, shadow-DOM-embedded CSS.
|
|
@@ -141,15 +157,7 @@ function compile(classes) {
|
|
|
141
157
|
*/
|
|
142
158
|
function compileSfc(source, id) {
|
|
143
159
|
const ast = compileToAst(source, id);
|
|
144
|
-
return
|
|
145
|
-
input: JSON.stringify(ast),
|
|
146
|
-
encoding: "utf-8",
|
|
147
|
-
stdio: [
|
|
148
|
-
"pipe",
|
|
149
|
-
"pipe",
|
|
150
|
-
"inherit"
|
|
151
|
-
]
|
|
152
|
-
});
|
|
160
|
+
return runBinary(resolveBinary(), ["--ast-json"], JSON.stringify(ast));
|
|
153
161
|
}
|
|
154
162
|
//#endregion
|
|
155
163
|
export { compile, compileSfc, defineStylePack, isUsableExecutable };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process'\nimport { accessSync, constants, existsSync, statSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { compileToAst } from '@aihu/compiler'\n\nexport {\n defineStylePack,\n type StylePack,\n type StylePackInput,\n type TokenMap,\n} from './define-style-pack.ts'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n// ---------------------------------------------------------------------------\n// Platform support matrix\n// ---------------------------------------------------------------------------\n//\n// Maps process.platform + process.arch to the per-platform npm package that\n// ships the prebuilt `aihu-css-compile` executable, plus the binary's filename\n// inside that package. Mirrors @aihu/server/src/native.ts detectPlatform() and\n// the package directory names under packages/css-engine/npm/<platform>/.\n//\n// Unlike @aihu/server (a napi `.node` addon loaded via require), the css engine\n// invokes `aihu-css-compile` as a CLI SUBPROCESS (execFileSync against the\n// executable on disk). So the platform package exposes a raw executable file,\n// and we resolve its absolute PATH — we never `require()` the binary itself.\n\ninterface PlatformDescriptor {\n readonly platformId: string\n readonly packageName: string\n /** Executable filename inside the platform package. */\n readonly binFile: string\n}\n\nfunction detectPlatform(): PlatformDescriptor | null {\n if (typeof process === 'undefined' || !process.platform || !process.arch) {\n return null\n }\n const key = `${process.platform}-${process.arch}`\n switch (key) {\n case 'darwin-arm64':\n return {\n platformId: 'darwin-arm64',\n packageName: '@aihu/css-engine-darwin-arm64',\n binFile: 'aihu-css-compile',\n }\n case 'darwin-x64':\n return {\n platformId: 'darwin-x64',\n packageName: '@aihu/css-engine-darwin-x64',\n binFile: 'aihu-css-compile',\n }\n case 'linux-x64':\n // We only ship glibc; musl users fall through to the dev/source path.\n return {\n platformId: 'linux-x64-gnu',\n packageName: '@aihu/css-engine-linux-x64-gnu',\n binFile: 'aihu-css-compile',\n }\n case 'win32-x64':\n return {\n platformId: 'win32-x64-msvc',\n packageName: '@aihu/css-engine-win32-x64-msvc',\n binFile: 'aihu-css-compile.exe',\n }\n default:\n return null\n }\n}\n\nlet _binPath: string | null = null\n\n/**\n * Whether `candidate` is a usable `aihu-css-compile` executable — NOT merely a\n * present file.\n *\n * The per-platform packages (`@aihu/css-engine-<platform>`) carry a placeholder\n * `aihu-css-compile` in source; the real prebuilt binary is only injected by\n * the release CI. Once those packages become resolvable in the workspace (e.g.\n * after a `bun.lock` refresh that pins them as optionalDependencies), a bare\n * `existsSync` would happily return the non-executable placeholder, which then\n * blows up with EACCES inside `spawnSync`/`execFileSync`. So we must verify the\n * candidate is actually runnable before accepting it.\n *\n * POSIX: require the execute bit (X_OK). A zero-byte/text placeholder without\n * +x fails here and we fall through to the dev `target/` fallback.\n *\n * Windows: there is no execute bit — `accessSync(_, X_OK)` is effectively\n * always true — so we additionally require a non-empty regular file, which\n * still rejects a zero-byte placeholder.\n */\nexport function isUsableExecutable(candidate: string): boolean {\n try {\n const st = statSync(candidate)\n if (!st.isFile() || st.size === 0) return false\n accessSync(candidate, constants.X_OK)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the absolute path to the `aihu-css-compile` executable.\n *\n * Resolution order:\n * 1. The per-platform optionalDependency package\n * (`@aihu/css-engine-<platform>`) shipped to npm consumers — resolved via\n * `createRequire(...).resolve('<pkg>/package.json')` so it works in both\n * ESM and CJS and respects the consumer's node_modules layout.\n * 2. Dev fallback: the monorepo workspace `target/release|debug/` — only\n * present in a dev clone with a Rust toolchain (`cargo build --release -p\n * aihu-css-core`). Kept so in-repo builds + tests work without publishing.\n *\n * If the current platform is SUPPORTED but neither path yields a binary, throws\n * a structured error pointing at the missing optionalDependency (mirrors\n * @aihu/server's failure-loud contract). If the platform is UNSUPPORTED, the\n * error lists the dev fallback so source builds still have a clear remedy.\n */\nfunction resolveBinary(): string {\n if (_binPath !== null) return _binPath\n\n const descriptor = detectPlatform()\n\n // 1. Per-platform optionalDependency package (the published-consumer path).\n //\n // Accept the candidate ONLY if it is a usable executable. A present-but-\n // non-executable placeholder (the in-source stub that becomes resolvable once\n // the per-platform packages are pinned in the lockfile) must NOT be returned —\n // doing so spawns a non-executable file and fails with EACCES. In that case we\n // deliberately fall THROUGH to the dev `target/` fallback below.\n if (descriptor) {\n const requireFn = createRequire(import.meta.url)\n try {\n const pkgJson = requireFn.resolve(`${descriptor.packageName}/package.json`)\n const candidate = join(dirname(pkgJson), descriptor.binFile)\n if (isUsableExecutable(candidate)) {\n _binPath = candidate\n return _binPath\n }\n } catch {\n // Package not installed (optionalDependency skipped for this platform, or\n // a partial install). Fall through to the dev/source path, then error.\n }\n }\n\n // 2. Dev fallback: monorepo workspace target/. Only exists in a dev clone.\n const ext = process.platform === 'win32' ? '.exe' : ''\n const devCandidates = [\n resolve(__dirname, '../../../target/release', `aihu-css-compile${ext}`),\n resolve(__dirname, '../../../target/debug', `aihu-css-compile${ext}`),\n ]\n for (const c of devCandidates) {\n if (existsSync(c)) {\n _binPath = c\n return _binPath\n }\n }\n\n throw buildMissingBinaryError(descriptor, devCandidates)\n}\n\nfunction buildMissingBinaryError(\n descriptor: PlatformDescriptor | null,\n devCandidates: string[],\n): Error {\n if (descriptor === null) {\n return new Error(\n `[@aihu/css-engine] No prebuilt aihu-css-compile binary for this platform.\\n\\n` +\n ` Platform: ${typeof process !== 'undefined' ? `${process.platform}-${process.arch}` : 'unknown'}\\n\\n` +\n ` @aihu/css-engine ships prebuilt binaries for darwin-arm64, darwin-x64,\\n` +\n ` linux-x64-gnu (glibc) and win32-x64-msvc. Your platform is not in that set.\\n\\n` +\n ` To build from source you need a Rust toolchain, then run from the repo root:\\n` +\n ` cargo build --release -p aihu-css-core\\n\\n` +\n ` Checked dev fallback paths: ${devCandidates.join(', ')}`,\n )\n }\n return new Error(\n `[@aihu/css-engine] Native CSS compiler binary not found for this platform.\\n\\n` +\n ` Platform: ${descriptor.platformId}\\n` +\n ` Expected package: ${descriptor.packageName}\\n` +\n ` Expected file: ${descriptor.packageName}/${descriptor.binFile}\\n\\n` +\n ` This binary is distributed as an optionalDependency of @aihu/css-engine.\\n` +\n ` Your package manager may have skipped it (optionalDependencies are\\n` +\n ` silently dropped on install failure).\\n\\n` +\n ` To reinstall:\\n` +\n ` npm install @aihu/css-engine\\n` +\n ` # or: pnpm install or: bun install\\n\\n` +\n ` If you are working in the aihu monorepo, build from source instead:\\n` +\n ` cargo build --release -p aihu-css-core\\n` +\n ` Checked dev fallback paths: ${devCandidates.join(', ')}`,\n )\n}\n\n/**\n * Compile a list of utility class names to CSS.\n *\n * Plan 1 bootstrap — supports a hardcoded subset; see crates/aihu-css-core/src/tokens.rs.\n * Plan 2 wires the AST scanner so callers pass `.aihu` SFC ASTs instead of raw class lists.\n *\n * @param classes - utility class names like `['bg-primary', 'p-4']`\n * @returns CSS string with one rule per known class\n */\nexport function compile(classes: string[]): string {\n if (classes.length === 0) return ''\n\n const bin = resolveBinary()\n const input = classes.join('\\n')\n const result = execFileSync(bin, [], {\n input,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'inherit'],\n })\n return result\n}\n\n/**\n * Compile a `.aihu` SFC source string to scoped, shadow-DOM-embedded CSS.\n *\n * Pipeline (Plan 2 Task 9): `compileToAst(source)` (from `@aihu/compiler`)\n * → AST JSON → `aihu-css-compile --ast-json` → scoped CSS. The output is the\n * per-SFC stylesheet the compiler folds into the component's shadow `<style>`:\n * `:host`-level theme tokens, variant-resolved utility rules, and the folded\n * authored `@style` block. There is NO global utility stylesheet.\n *\n * @param source - the `.aihu` SFC source text\n * @param id - optional file path/id (used to derive the tag stem + `@route` checks)\n * @returns the scoped CSS string for the SFC\n */\nexport function compileSfc(source: string, id?: string): string {\n const ast = compileToAst(source, id)\n const bin = resolveBinary()\n return execFileSync(bin, ['--ast-json'], {\n input: JSON.stringify(ast),\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'inherit'],\n })\n}\n"],"mappings":";;;;;;;;AAcA,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAuBzD,SAAS,iBAA4C;CACnD,IAAI,OAAO,YAAY,eAAe,CAAC,QAAQ,YAAY,CAAC,QAAQ,MAClE,OAAO;CAGT,QAAQ,GADO,QAAQ,SAAS,GAAG,QAAQ,QAC3C;EACE,KAAK,gBACH,OAAO;GACL,YAAY;GACZ,aAAa;GACb,SAAS;GACV;EACH,KAAK,cACH,OAAO;GACL,YAAY;GACZ,aAAa;GACb,SAAS;GACV;EACH,KAAK,aAEH,OAAO;GACL,YAAY;GACZ,aAAa;GACb,SAAS;GACV;EACH,KAAK,aACH,OAAO;GACL,YAAY;GACZ,aAAa;GACb,SAAS;GACV;EACH,SACE,OAAO;;;AAIb,IAAI,WAA0B;;;;;;;;;;;;;;;;;;;;AAqB9B,SAAgB,mBAAmB,WAA4B;CAC7D,IAAI;EACF,MAAM,KAAK,SAAS,UAAU;EAC9B,IAAI,CAAC,GAAG,QAAQ,IAAI,GAAG,SAAS,GAAG,OAAO;EAC1C,WAAW,WAAW,UAAU,KAAK;EACrC,OAAO;SACD;EACN,OAAO;;;;;;;;;;;;;;;;;;;;AAqBX,SAAS,gBAAwB;CAC/B,IAAI,aAAa,MAAM,OAAO;CAE9B,MAAM,aAAa,gBAAgB;CASnC,IAAI,YAAY;EACd,MAAM,YAAY,cAAc,OAAO,KAAK,IAAI;EAChD,IAAI;GAEF,MAAM,YAAY,KAAK,QADP,UAAU,QAAQ,GAAG,WAAW,YAAY,eACtB,CAAC,EAAE,WAAW,QAAQ;GAC5D,IAAI,mBAAmB,UAAU,EAAE;IACjC,WAAW;IACX,OAAO;;UAEH;;CAOV,MAAM,MAAM,QAAQ,aAAa,UAAU,SAAS;CACpD,MAAM,gBAAgB,CACpB,QAAQ,WAAW,2BAA2B,mBAAmB,MAAM,EACvE,QAAQ,WAAW,yBAAyB,mBAAmB,MAAM,CACtE;CACD,KAAK,MAAM,KAAK,eACd,IAAI,WAAW,EAAE,EAAE;EACjB,WAAW;EACX,OAAO;;CAIX,MAAM,wBAAwB,YAAY,cAAc;;AAG1D,SAAS,wBACP,YACA,eACO;CACP,IAAI,eAAe,MACjB,uBAAO,IAAI,MACT,mGACwB,OAAO,YAAY,cAAc,GAAG,QAAQ,SAAS,GAAG,QAAQ,SAAS,UAAU,6TAKxE,cAAc,KAAK,KAAK,GAC5D;CAEH,uBAAO,IAAI,MACT,qGACyB,WAAW,WAAW,wBACtB,WAAW,YAAY,wBACvB,WAAW,YAAY,GAAG,WAAW,QAAQ,mbASnC,cAAc,KAAK,KAAK,GAC5D;;;;;;;;;;;AAYH,SAAgB,QAAQ,SAA2B;CACjD,IAAI,QAAQ,WAAW,GAAG,OAAO;CASjC,OALe,aAFH,eAEmB,EAAE,EAAE,EAAE;EACnC,OAFY,QAAQ,KAAK,KAEpB;EACL,UAAU;EACV,OAAO;GAAC;GAAQ;GAAQ;GAAU;EACnC,CACY;;;;;;;;;;;;;;;AAgBf,SAAgB,WAAW,QAAgB,IAAqB;CAC9D,MAAM,MAAM,aAAa,QAAQ,GAAG;CAEpC,OAAO,aADK,eACW,EAAE,CAAC,aAAa,EAAE;EACvC,OAAO,KAAK,UAAU,IAAI;EAC1B,UAAU;EACV,OAAO;GAAC;GAAQ;GAAQ;GAAU;EACnC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process'\nimport { accessSync, constants, existsSync, statSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { compileToAst } from '@aihu/compiler'\n\nexport {\n defineStylePack,\n type StylePack,\n type StylePackInput,\n type TokenMap,\n} from './define-style-pack.ts'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n// ---------------------------------------------------------------------------\n// Platform support matrix\n// ---------------------------------------------------------------------------\n//\n// Maps process.platform + process.arch to the per-platform npm package that\n// ships the prebuilt `aihu-css-compile` executable, plus the binary's filename\n// inside that package. Mirrors @aihu/server/src/native.ts detectPlatform() and\n// the package directory names under packages/css-engine/npm/<platform>/.\n//\n// Unlike @aihu/server (a napi `.node` addon loaded via require), the css engine\n// invokes `aihu-css-compile` as a CLI SUBPROCESS (execFileSync against the\n// executable on disk). So the platform package exposes a raw executable file,\n// and we resolve its absolute PATH — we never `require()` the binary itself.\n\ninterface PlatformDescriptor {\n readonly platformId: string\n readonly packageName: string\n /** Executable filename inside the platform package. */\n readonly binFile: string\n}\n\nfunction detectPlatform(): PlatformDescriptor | null {\n if (typeof process === 'undefined' || !process.platform || !process.arch) {\n return null\n }\n const key = `${process.platform}-${process.arch}`\n switch (key) {\n case 'darwin-arm64':\n return {\n platformId: 'darwin-arm64',\n packageName: '@aihu/css-engine-darwin-arm64',\n binFile: 'aihu-css-compile',\n }\n case 'darwin-x64':\n return {\n platformId: 'darwin-x64',\n packageName: '@aihu/css-engine-darwin-x64',\n binFile: 'aihu-css-compile',\n }\n case 'linux-x64':\n // We only ship glibc; musl users fall through to the dev/source path.\n return {\n platformId: 'linux-x64-gnu',\n packageName: '@aihu/css-engine-linux-x64-gnu',\n binFile: 'aihu-css-compile',\n }\n case 'win32-x64':\n return {\n platformId: 'win32-x64-msvc',\n packageName: '@aihu/css-engine-win32-x64-msvc',\n binFile: 'aihu-css-compile.exe',\n }\n default:\n return null\n }\n}\n\nlet _binPath: string | null = null\n\n/**\n * Whether `candidate` is a usable `aihu-css-compile` executable — NOT merely a\n * present file.\n *\n * The per-platform packages (`@aihu/css-engine-<platform>`) carry a placeholder\n * `aihu-css-compile` in source; the real prebuilt binary is only injected by\n * the release CI. Once those packages become resolvable in the workspace (e.g.\n * after a `bun.lock` refresh that pins them as optionalDependencies), a bare\n * `existsSync` would happily return the non-executable placeholder, which then\n * blows up with EACCES inside `spawnSync`/`execFileSync`. So we must verify the\n * candidate is actually runnable before accepting it.\n *\n * POSIX: require the execute bit (X_OK). A zero-byte/text placeholder without\n * +x fails here and we fall through to the dev `target/` fallback.\n *\n * Windows: there is no execute bit — `accessSync(_, X_OK)` is effectively\n * always true — so we additionally require a non-empty regular file, which\n * still rejects a zero-byte placeholder.\n */\nexport function isUsableExecutable(candidate: string): boolean {\n try {\n const st = statSync(candidate)\n if (!st.isFile() || st.size === 0) return false\n accessSync(candidate, constants.X_OK)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the absolute path to the `aihu-css-compile` executable.\n *\n * Resolution order:\n * 1. The per-platform optionalDependency package\n * (`@aihu/css-engine-<platform>`) shipped to npm consumers — resolved via\n * `createRequire(...).resolve('<pkg>/package.json')` so it works in both\n * ESM and CJS and respects the consumer's node_modules layout.\n * 2. Dev fallback: the monorepo workspace `target/release|debug/` — only\n * present in a dev clone with a Rust toolchain (`cargo build --release -p\n * aihu-css-core`). Kept so in-repo builds + tests work without publishing.\n *\n * If the current platform is SUPPORTED but neither path yields a binary, throws\n * a structured error pointing at the missing optionalDependency (mirrors\n * @aihu/server's failure-loud contract). If the platform is UNSUPPORTED, the\n * error lists the dev fallback so source builds still have a clear remedy.\n */\nfunction resolveBinary(): string {\n if (_binPath !== null) return _binPath\n\n const descriptor = detectPlatform()\n\n // 1. Per-platform optionalDependency package (the published-consumer path).\n //\n // Accept the candidate ONLY if it is a usable executable. A present-but-\n // non-executable placeholder (the in-source stub that becomes resolvable once\n // the per-platform packages are pinned in the lockfile) must NOT be returned —\n // doing so spawns a non-executable file and fails with EACCES. In that case we\n // deliberately fall THROUGH to the dev `target/` fallback below.\n if (descriptor) {\n const requireFn = createRequire(import.meta.url)\n try {\n const pkgJson = requireFn.resolve(`${descriptor.packageName}/package.json`)\n const candidate = join(dirname(pkgJson), descriptor.binFile)\n if (isUsableExecutable(candidate)) {\n _binPath = candidate\n return _binPath\n }\n } catch {\n // Package not installed (optionalDependency skipped for this platform, or\n // a partial install). Fall through to the dev/source path, then error.\n }\n }\n\n // 2. Dev fallback: monorepo workspace target/. Only exists in a dev clone.\n const ext = process.platform === 'win32' ? '.exe' : ''\n const devCandidates = [\n resolve(__dirname, '../../../target/release', `aihu-css-compile${ext}`),\n resolve(__dirname, '../../../target/debug', `aihu-css-compile${ext}`),\n ]\n for (const c of devCandidates) {\n if (existsSync(c)) {\n _binPath = c\n return _binPath\n }\n }\n\n throw buildMissingBinaryError(descriptor, devCandidates)\n}\n\nfunction buildMissingBinaryError(\n descriptor: PlatformDescriptor | null,\n devCandidates: string[],\n): Error {\n if (descriptor === null) {\n return new Error(\n `[@aihu/css-engine] No prebuilt aihu-css-compile binary for this platform.\\n\\n` +\n ` Platform: ${typeof process !== 'undefined' ? `${process.platform}-${process.arch}` : 'unknown'}\\n\\n` +\n ` @aihu/css-engine ships prebuilt binaries for darwin-arm64, darwin-x64,\\n` +\n ` linux-x64-gnu (glibc) and win32-x64-msvc. Your platform is not in that set.\\n\\n` +\n ` To build from source you need a Rust toolchain, then run from the repo root:\\n` +\n ` cargo build --release -p aihu-css-core\\n\\n` +\n ` Checked dev fallback paths: ${devCandidates.join(', ')}`,\n )\n }\n return new Error(\n `[@aihu/css-engine] Native CSS compiler binary not found for this platform.\\n\\n` +\n ` Platform: ${descriptor.platformId}\\n` +\n ` Expected package: ${descriptor.packageName}\\n` +\n ` Expected file: ${descriptor.packageName}/${descriptor.binFile}\\n\\n` +\n ` This binary is distributed as an optionalDependency of @aihu/css-engine.\\n` +\n ` Your package manager may have skipped it (optionalDependencies are\\n` +\n ` silently dropped on install failure).\\n\\n` +\n ` To reinstall:\\n` +\n ` npm install @aihu/css-engine\\n` +\n ` # or: pnpm install or: bun install\\n\\n` +\n ` If you are working in the aihu monorepo, build from source instead:\\n` +\n ` cargo build --release -p aihu-css-core\\n` +\n ` Checked dev fallback paths: ${devCandidates.join(', ')}`,\n )\n}\n\n/**\n * Compile a list of utility class names to CSS.\n *\n * Plan 1 bootstrap — supports a hardcoded subset; see crates/aihu-css-core/src/tokens.rs.\n * Plan 2 wires the AST scanner so callers pass `.aihu` SFC ASTs instead of raw class lists.\n *\n * @param classes - utility class names like `['bg-primary', 'p-4']`\n * @returns CSS string with one rule per known class\n */\n/**\n * Spawn the native compiler, returning stdout on success. On a non-zero exit\n * (the binary's R-RESULT error path) throw an `Error` carrying the binary's\n * stderr message rather than letting `execFileSync`'s opaque status error\n * surface. stderr is PIPED (not inherited) so the message lands in the thrown\n * error instead of the parent's console.\n */\nfunction runBinary(bin: string, args: string[], input: string): string {\n try {\n return execFileSync(bin, args, {\n input,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n } catch (err) {\n const e = err as { stderr?: Buffer | string; message?: string }\n const stderr =\n typeof e.stderr === 'string'\n ? e.stderr\n : e.stderr instanceof Buffer\n ? e.stderr.toString('utf-8')\n : ''\n const detail = stderr.trim() || e.message || 'unknown error'\n throw new Error(`[@aihu/css-engine] CSS compile failed: ${detail}`)\n }\n}\n\nexport function compile(classes: string[]): string {\n if (classes.length === 0) return ''\n\n const bin = resolveBinary()\n const input = classes.join('\\n')\n return runBinary(bin, [], input)\n}\n\n/**\n * Compile a `.aihu` SFC source string to scoped, shadow-DOM-embedded CSS.\n *\n * Pipeline (Plan 2 Task 9): `compileToAst(source)` (from `@aihu/compiler`)\n * → AST JSON → `aihu-css-compile --ast-json` → scoped CSS. The output is the\n * per-SFC stylesheet the compiler folds into the component's shadow `<style>`:\n * `:host`-level theme tokens, variant-resolved utility rules, and the folded\n * authored `@style` block. There is NO global utility stylesheet.\n *\n * @param source - the `.aihu` SFC source text\n * @param id - optional file path/id (used to derive the tag stem + `@route` checks)\n * @returns the scoped CSS string for the SFC\n */\nexport function compileSfc(source: string, id?: string): string {\n const ast = compileToAst(source, id)\n const bin = resolveBinary()\n return runBinary(bin, ['--ast-json'], JSON.stringify(ast))\n}\n"],"mappings":";;;;;;;;AAcA,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAuBzD,SAAS,iBAA4C;CACnD,IAAI,OAAO,YAAY,eAAe,CAAC,QAAQ,YAAY,CAAC,QAAQ,MAClE,OAAO;CAGT,QAAQ,GADO,QAAQ,SAAS,GAAG,QAAQ,QAC3C;EACE,KAAK,gBACH,OAAO;GACL,YAAY;GACZ,aAAa;GACb,SAAS;GACV;EACH,KAAK,cACH,OAAO;GACL,YAAY;GACZ,aAAa;GACb,SAAS;GACV;EACH,KAAK,aAEH,OAAO;GACL,YAAY;GACZ,aAAa;GACb,SAAS;GACV;EACH,KAAK,aACH,OAAO;GACL,YAAY;GACZ,aAAa;GACb,SAAS;GACV;EACH,SACE,OAAO;;;AAIb,IAAI,WAA0B;;;;;;;;;;;;;;;;;;;;AAqB9B,SAAgB,mBAAmB,WAA4B;CAC7D,IAAI;EACF,MAAM,KAAK,SAAS,UAAU;EAC9B,IAAI,CAAC,GAAG,QAAQ,IAAI,GAAG,SAAS,GAAG,OAAO;EAC1C,WAAW,WAAW,UAAU,KAAK;EACrC,OAAO;SACD;EACN,OAAO;;;;;;;;;;;;;;;;;;;;AAqBX,SAAS,gBAAwB;CAC/B,IAAI,aAAa,MAAM,OAAO;CAE9B,MAAM,aAAa,gBAAgB;CASnC,IAAI,YAAY;EACd,MAAM,YAAY,cAAc,OAAO,KAAK,IAAI;EAChD,IAAI;GAEF,MAAM,YAAY,KAAK,QADP,UAAU,QAAQ,GAAG,WAAW,YAAY,eACtB,CAAC,EAAE,WAAW,QAAQ;GAC5D,IAAI,mBAAmB,UAAU,EAAE;IACjC,WAAW;IACX,OAAO;;UAEH;;CAOV,MAAM,MAAM,QAAQ,aAAa,UAAU,SAAS;CACpD,MAAM,gBAAgB,CACpB,QAAQ,WAAW,2BAA2B,mBAAmB,MAAM,EACvE,QAAQ,WAAW,yBAAyB,mBAAmB,MAAM,CACtE;CACD,KAAK,MAAM,KAAK,eACd,IAAI,WAAW,EAAE,EAAE;EACjB,WAAW;EACX,OAAO;;CAIX,MAAM,wBAAwB,YAAY,cAAc;;AAG1D,SAAS,wBACP,YACA,eACO;CACP,IAAI,eAAe,MACjB,uBAAO,IAAI,MACT,mGACwB,OAAO,YAAY,cAAc,GAAG,QAAQ,SAAS,GAAG,QAAQ,SAAS,UAAU,6TAKxE,cAAc,KAAK,KAAK,GAC5D;CAEH,uBAAO,IAAI,MACT,qGACyB,WAAW,WAAW,wBACtB,WAAW,YAAY,wBACvB,WAAW,YAAY,GAAG,WAAW,QAAQ,mbASnC,cAAc,KAAK,KAAK,GAC5D;;;;;;;;;;;;;;;;;;AAmBH,SAAS,UAAU,KAAa,MAAgB,OAAuB;CACrE,IAAI;EACF,OAAO,aAAa,KAAK,MAAM;GAC7B;GACA,UAAU;GACV,OAAO;IAAC;IAAQ;IAAQ;IAAO;GAChC,CAAC;UACK,KAAK;EACZ,MAAM,IAAI;EAOV,MAAM,UALJ,OAAO,EAAE,WAAW,WAChB,EAAE,SACF,EAAE,kBAAkB,SAClB,EAAE,OAAO,SAAS,QAAQ,GAC1B,IACc,MAAM,IAAI,EAAE,WAAW;EAC7C,MAAM,IAAI,MAAM,0CAA0C,SAAS;;;AAIvE,SAAgB,QAAQ,SAA2B;CACjD,IAAI,QAAQ,WAAW,GAAG,OAAO;CAIjC,OAAO,UAFK,eAEQ,EAAE,EAAE,EADV,QAAQ,KAAK,KACI,CAAC;;;;;;;;;;;;;;;AAgBlC,SAAgB,WAAW,QAAgB,IAAqB;CAC9D,MAAM,MAAM,aAAa,QAAQ,GAAG;CAEpC,OAAO,UADK,eACQ,EAAE,CAAC,aAAa,EAAE,KAAK,UAAU,IAAI,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aihu/css-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
],
|
|
38
38
|
"sideEffects": false,
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@aihu/compiler": "^0.
|
|
40
|
+
"@aihu/compiler": "^0.6.0"
|
|
41
41
|
},
|
|
42
42
|
"optionalDependencies": {
|
|
43
|
-
"@aihu/css-engine-darwin-arm64": "0.1.
|
|
44
|
-
"@aihu/css-engine-darwin-x64": "0.1.
|
|
45
|
-
"@aihu/css-engine-linux-x64-gnu": "0.1.
|
|
46
|
-
"@aihu/css-engine-win32-x64-msvc": "0.1.
|
|
43
|
+
"@aihu/css-engine-darwin-arm64": "0.1.3",
|
|
44
|
+
"@aihu/css-engine-darwin-x64": "0.1.3",
|
|
45
|
+
"@aihu/css-engine-linux-x64-gnu": "0.1.3",
|
|
46
|
+
"@aihu/css-engine-win32-x64-msvc": "0.1.3"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "bun run gen:style-packs && rolldown -c",
|