lean_ctx/core/patterns/
next_build.rs1macro_rules! static_regex {
2 ($pattern:expr) => {{
3 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
4 RE.get_or_init(|| {
5 regex::Regex::new($pattern).expect(concat!("BUG: invalid static regex: ", $pattern))
6 })
7 }};
8}
9
10fn route_re() -> &'static regex::Regex {
11 static_regex!(r"^[○●λƒ◐]\s+(/\S*)")
12}
13fn size_re() -> &'static regex::Regex {
14 static_regex!(r"(\d+\.?\d*)\s*(kB|MB|B)\b")
15}
16fn build_time_re() -> &'static regex::Regex {
17 static_regex!(r"(?:compiled|built|done)\s+(?:in\s+)?(\d+\.?\d*\s*[ms]+)")
18}
19fn vite_chunk_re() -> &'static regex::Regex {
20 static_regex!(r"dist/(\S+)\s+(\d+\.?\d*\s*[kKMm]?B)")
21}
22
23pub fn compress(command: &str, output: &str) -> Option<String> {
24 if command.contains("vite") {
25 return Some(compress_vite(output));
26 }
27 Some(compress_next(output))
28}
29
30fn compress_next(output: &str) -> String {
31 let trimmed = output.trim();
32 if trimmed.is_empty() {
33 return "ok".to_string();
34 }
35
36 let mut routes = Vec::new();
37 let mut total_size = 0f64;
38 let mut build_time = String::new();
39 let mut errors = Vec::new();
40
41 for line in trimmed.lines() {
42 if let Some(caps) = route_re().captures(line) {
43 let route = &caps[1];
44 let size = extract_size(line);
45 routes.push(format!("{route} ({size})"));
46 }
47 if let Some(caps) = build_time_re().captures(line) {
48 build_time = caps[1].to_string();
49 }
50 if line.to_lowercase().contains("error") && !line.contains("0 error") {
51 errors.push(line.trim().to_string());
52 }
53 if let Some(caps) = size_re().captures(line) {
54 let val: f64 = caps[1].parse().unwrap_or(0.0);
55 let unit = &caps[2];
56 total_size += match unit {
57 "MB" => val * 1024.0,
58 "kB" => val,
59 _ => val / 1024.0,
60 };
61 }
62 }
63
64 if !errors.is_empty() {
65 return format!("BUILD ERROR:\n{}", errors.join("\n"));
66 }
67
68 let mut parts = Vec::new();
69 if build_time.is_empty() {
70 parts.push("built".to_string());
71 } else {
72 parts.push(format!("built ({build_time})"));
73 }
74
75 if !routes.is_empty() {
76 parts.push(format!("{} routes:", routes.len()));
77 for r in routes.iter().take(15) {
78 parts.push(format!(" {r}"));
79 }
80 if routes.len() > 15 {
81 parts.push(format!(" ... +{} more", routes.len() - 15));
82 }
83 }
84
85 if total_size > 0.0 {
86 if total_size > 1024.0 {
87 parts.push(format!("total: {:.1} MB", total_size / 1024.0));
88 } else {
89 parts.push(format!("total: {total_size:.0} kB"));
90 }
91 }
92
93 if parts.len() == 1 && parts[0] == "built" {
94 return compact_output(trimmed, 10);
95 }
96
97 parts.join("\n")
98}
99
100fn compress_vite(output: &str) -> String {
101 let trimmed = output.trim();
102 if trimmed.is_empty() {
103 return "ok".to_string();
104 }
105
106 let mut chunks = Vec::new();
107 let mut build_time = String::new();
108
109 for line in trimmed.lines() {
110 if let Some(caps) = vite_chunk_re().captures(line) {
111 chunks.push(format!("{}: {}", &caps[1], &caps[2]));
112 }
113 if let Some(caps) = build_time_re().captures(line) {
114 build_time = caps[1].to_string();
115 }
116 }
117
118 let mut parts = Vec::new();
119 if build_time.is_empty() {
120 parts.push("built".to_string());
121 } else {
122 parts.push(format!("built ({build_time})"));
123 }
124
125 if !chunks.is_empty() {
126 parts.push(format!("{} chunks:", chunks.len()));
127 for c in chunks.iter().take(10) {
128 parts.push(format!(" {c}"));
129 }
130 if chunks.len() > 10 {
131 parts.push(format!(" ... +{} more", chunks.len() - 10));
132 }
133 }
134
135 if parts.len() == 1 {
136 return compact_output(trimmed, 10);
137 }
138 parts.join("\n")
139}
140
141fn extract_size(line: &str) -> String {
142 if let Some(caps) = size_re().captures(line) {
143 format!("{} {}", &caps[1], &caps[2])
144 } else {
145 "?".to_string()
146 }
147}
148
149fn compact_output(text: &str, max: usize) -> String {
150 let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
151 if lines.len() <= max {
152 return lines.join("\n");
153 }
154 format!(
155 "{}\n... ({} more lines)",
156 lines[..max].join("\n"),
157 lines.len() - max
158 )
159}