1use crate::checklist;
2use crate::history;
3use crate::captain::parser::{self, MessageData, ParsedError, ParsedWarning};
4use crate::captain::tide::TideCharts;
5use crate::captain::tide::BuildMetrics;
6use crate::captain::license;
7use colored::*;
8use anyhow::{Result, Context};
9use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
10use std::fs;
11use std::io::{BufRead, BufReader, Write};
12use std::process::{Command, Stdio};
13use std::sync::atomic::{AtomicUsize, Ordering};
14use std::sync::Arc;
15use std::thread;
16use std::time::{Duration, Instant};
17use chrono::Utc;
18use std::collections::{HashMap, HashSet};
19use sha2::{Sha256, Digest};
20use serde::{Serialize, Deserialize};
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ErrorDeduplicator {
23 seen_fingerprints: HashMap<String, ErrorGroup>,
24 similarity_threshold: f32,
25}
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ErrorGroup {
28 primary_error: ParsedError,
29 variations: Vec<ParsedError>,
30 count: usize,
31 first_seen: String,
32 locations: HashSet<String>,
33}
34impl ErrorDeduplicator {
35 pub fn new() -> Self {
36 Self {
37 seen_fingerprints: HashMap::new(),
38 similarity_threshold: 0.8,
39 }
40 }
41 pub fn fingerprint(&self, error: &ParsedError) -> String {
42 let mut hasher = Sha256::new();
43 let normalized = self.normalize_error_message(&error.message);
44 hasher.update(normalized.as_bytes());
45 if !error.file.is_empty() {
46 hasher.update(error.file.as_bytes());
47 if error.line > 0 {
48 hasher.update((error.line / 10).to_string().as_bytes());
49 }
50 }
51 format!("{:x}", hasher.finalize())
52 }
53 fn normalize_error_message(&self, msg: &str) -> String {
54 msg.split_whitespace()
55 .map(|word| {
56 if word.starts_with('`') && word.ends_with('`') {
57 "`<identifier>`"
58 } else {
59 word
60 }
61 })
62 .collect::<Vec<_>>()
63 .join(" ")
64 }
65 pub fn process_errors(&mut self, errors: &[ParsedError]) -> Vec<ErrorGroup> {
66 for error in errors {
67 let fingerprint = self.fingerprint(error);
68 self.seen_fingerprints
69 .entry(fingerprint)
70 .or_insert_with(|| ErrorGroup {
71 primary_error: error.clone(),
72 variations: Vec::new(),
73 count: 0,
74 first_seen: Utc::now().to_rfc3339(),
75 locations: HashSet::new(),
76 })
77 .add_variation(error);
78 }
79 let mut groups: Vec<_> = self.seen_fingerprints.values().cloned().collect();
80 groups.sort_by(|a, b| b.count.cmp(&a.count));
81 groups
82 }
83}
84impl ErrorGroup {
85 pub fn add_variation(&mut self, error: &ParsedError) {
86 self.variations.push(error.clone());
87 self.count += 1;
88 if !error.file.is_empty() {
89 self.locations.insert(format!("{}:{}", error.file, error.line));
90 }
91 }
92}
93#[derive(Debug, Clone)]
94pub struct ErrorPrioritizer {
95 weights: PriorityWeights,
96}
97#[derive(Debug, Clone)]
98pub struct PriorityWeights {
99 never_seen_before: f32,
100 blocking_compilation: f32,
101 has_quick_fix: f32,
102 frequently_ignored: f32,
103 in_dependency: f32,
104 test_only: f32,
105}
106impl Default for PriorityWeights {
107 fn default() -> Self {
108 Self {
109 never_seen_before: 10.0,
110 blocking_compilation: 8.0,
111 has_quick_fix: -2.0,
112 frequently_ignored: -5.0,
113 in_dependency: -3.0,
114 test_only: -1.0,
115 }
116 }
117}
118impl ErrorPrioritizer {
119 pub fn new() -> Self {
120 Self {
121 weights: PriorityWeights::default(),
122 }
123 }
124 pub fn sort_errors(&self, errors: Vec<ParsedError>) -> Vec<ParsedError> {
125 let mut scored_errors: Vec<(ParsedError, f32)> = errors
126 .into_iter()
127 .map(|error| {
128 let mut score = 5.0;
129 score += self.weights.never_seen_before;
130 if self.has_known_fix(&error) {
131 score += self.weights.has_quick_fix;
132 }
133 if error.file.contains("/dependencies/") {
134 score += self.weights.in_dependency;
135 }
136 if error.file.contains("/tests/") || error.file.contains("_test.rs") {
137 score += self.weights.test_only;
138 }
139 (error, score)
140 })
141 .collect();
142 scored_errors.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
143 scored_errors.into_iter().map(|(e, _)| e).collect()
144 }
145 fn has_known_fix(&self, error: &ParsedError) -> bool {
146 false
147 }
148}
149#[derive(Debug, Clone)]
150pub struct BuildCoach {
151 tips: Vec<CoachingTip>,
152 shown_tips: HashSet<String>,
153}
154#[derive(Debug, Clone)]
155pub struct CoachingTip {
156 id: String,
157 condition: BuildCondition,
158 message: String,
159 priority: u8,
160}
161#[derive(Debug, Clone)]
162pub enum BuildCondition {
163 SlowBuild(Duration),
164 ManyWarnings(usize),
165 RecurringErrors,
166 LargeErrorCount(usize),
167}
168impl BuildCoach {
169 pub fn new() -> Self {
170 let mut tips = Vec::new();
171 tips.push(CoachingTip {
172 id: "slow_build".to_string(),
173 condition: BuildCondition::SlowBuild(Duration::from_secs(30)),
174 message: "๐ก Long build? Try `cm optimize aggressive` for faster builds"
175 .to_string(),
176 priority: 5,
177 });
178 tips.push(CoachingTip {
179 id: "many_warnings".to_string(),
180 condition: BuildCondition::ManyWarnings(20),
181 message: "๐ก Many warnings? Use `cm mutiny allow-warnings` temporarily"
182 .to_string(),
183 priority: 3,
184 });
185 tips.push(CoachingTip {
186 id: "recurring_error".to_string(),
187 condition: BuildCondition::RecurringErrors,
188 message: "๐ก Recurring error? Try `cm wtf er` for AI assistance"
189 .to_string(),
190 priority: 8,
191 });
192 tips.push(CoachingTip {
193 id: "many_errors".to_string(),
194 condition: BuildCondition::LargeErrorCount(10),
195 message: "๐ก Many errors? Focus on the first few - they often cascade"
196 .to_string(),
197 priority: 6,
198 });
199 Self {
200 tips,
201 shown_tips: HashSet::new(),
202 }
203 }
204 pub fn check_and_show_tip(&mut self, context: &BuildContext) -> Option<String> {
205 for tip in &self.tips {
206 if !self.shown_tips.contains(&tip.id) && tip.condition.matches(context) {
207 self.shown_tips.insert(tip.id.clone());
208 return Some(tip.message.clone());
209 }
210 }
211 None
212 }
213}
214impl BuildCondition {
215 pub fn matches(&self, context: &BuildContext) -> bool {
216 match self {
217 BuildCondition::SlowBuild(duration) => context.elapsed > *duration,
218 BuildCondition::ManyWarnings(count) => context.warning_count > *count,
219 BuildCondition::RecurringErrors => context.has_recurring_errors,
220 BuildCondition::LargeErrorCount(count) => context.error_count > *count,
221 }
222 }
223}
224#[derive(Debug)]
225pub struct BuildContext {
226 pub elapsed: Duration,
227 pub warning_count: usize,
228 pub error_count: usize,
229 pub has_recurring_errors: bool,
230}
231fn process_and_display_errors(errors: &[ParsedError]) {
232 if errors.is_empty() {
233 return;
234 }
235 let mut deduplicator = ErrorDeduplicator::new();
236 let groups = deduplicator.process_errors(errors);
237 if !groups.is_empty() {
238 println!(
239 "\n{}", format!("๐ด {} Unique Error Patterns:", groups.len()) .red().bold()
240 );
241 for (i, group) in groups.iter().take(5).enumerate() {
242 println!(
243 " {}. {} ({}x across {} locations)", i + 1, group.primary_error.message,
244 group.count, group.locations.len()
245 );
246 if group.variations.len() > 1 {
247 println!(
248 " {} Similar variations grouped", group.variations.len()
249 .to_string().dimmed()
250 );
251 }
252 }
253 }
254}
255pub fn run_cargo_passthrough(args: &[&str]) {
256 let cargo_path = std::env::var("CARGO_BIN_PATH")
257 .unwrap_or_else(|_| "/root/.cargo/bin/cargo".to_string());
258 let status = Command::new(&cargo_path)
259 .args(args)
260 .status()
261 .unwrap_or_else(|e| {
262 eprintln!("Failed to start cargo: {}", e);
263 std::process::exit(1);
264 });
265 std::process::exit(status.code().unwrap_or(1));
266}
267const NAUTICAL_MESSAGES: &[&str] = &[
268 "[ANCHOR] Dropping anchor and securing position...",
269 "[WAVE] Riding the waves with steady resolve...",
270 "[PIRATE] Hoisting the Jolly Roger - compilation begins! [SWORD]",
271 "[MAP] Charting course through dependency seas...",
272 "[SAIL] Catching wind in our dependency sails...",
273 "[SHIP] Setting sail across the Rust seas...",
274 "[COMPASS] Navigating treacherous compilation waters...",
275 "[SUNRISE] Chasing horizons of clean builds...",
276 "[HAMMER] Forging dependencies in the shipyard...",
277 "[GEAR] Machining precision components...",
278 "[BOLT] Tightening bolts in the engine room...",
279 "[WRENCH] Calibrating the build compass...",
280 "[RULER] Measuring twice, compiling once...",
281 "[SCOPE] Inspecting code quality under magnification...",
282 "[FLASK] Distilling pure Rust essence...",
283 "[TEST] Testing the waters before deep diving...",
284 "๐ฆ Loading cargo containers with care...",
285 "๐ Hauling dependencies across the digital dock...",
286 "๐๏ธ Constructing the foundation of your project...",
287 "๐งฑ Laying bricks of reliable code...",
288 "๐ญ Manufacturing robust binaries...",
289 "๐ Checking manifest against the cargo log...",
290 "๐ Scanning for hidden treasures in the code...",
291 "๐งน Sweeping the deck of compilation artifacts...",
292 "โก Full speed ahead - compiling at flank speed! โก",
293 "๐ฏ Setting course for build success...",
294 "๐ Following the North Star of clean code...",
295 "๐ Battling compilation dragons...",
296 "๐ก๏ธ Shielding against compilation errors...",
297 "๐ช Performing the great cargo circus act...",
298 "๐ญ Wearing multiple compilation hats...",
299 "๐ช Juggling dependencies like a master performer...",
300 "๐ Sailing through calm compilation seas...",
301 "โ๏ธ Weathering the storm of complex dependencies...",
302 "๐ช๏ธ Surfing the waves of async compilation...",
303 "๐ Riding the rainbow after the storm...",
304 "๐ Dancing with the tides of build progress...",
305 "๐
Sunset approaches - build almost complete...",
306 "๐ Dawn breaks - new build cycle begins...",
307 "๐ Shooting stars guide our compilation path...",
308 "๐ฅ Manning the compilation stations...",
309 "๐ดโโ ๏ธ Crew chanting sea shanties of success...",
310 "๐งโโ First mate checking the build log...",
311 "๐จโ๐ณ Cook preparing a feast of fresh binaries...",
312 "๐งโ๐ Navigator plotting course through error logs...",
313 "๐จโ๐ง Engineer fine-tuning the compilation engine...",
314 "๐งโ๐จ Artist painting the canvas of clean code...",
315 "๐ฉโโ๏ธ Judge reviewing code quality standards...",
316 "๐ดโโ ๏ธ Searching for buried compilation treasures...",
317 "๐๏ธ Unlocking the secrets of dependency resolution...",
318 "๐ Polishing the gems of optimized code...",
319 "๐บ๏ธ Following the treasure map of build instructions...",
320 "๐ฎ Crystal ball shows successful compilation...",
321 "๐งโโ๏ธ Wizard casting spells of optimization...",
322 "๐ฆ Unicorn blessing the codebase...",
323 "๐ Dragon guarding the gates of compilation success...",
324 "๐ง Twisting the knobs of optimization...",
325 "โ๏ธ Balancing the scales of performance...",
326 "๐ Spinning the wheels of progress...",
327 "๐ Graphing the peaks of build performance...",
328 "๐ต Orchestrating the symphony of compilation...",
329 "๐ญ Directing the play of parallel compilation...",
330 "๐ช Conducting the circus of crate dependencies...",
331 "๐จ Painting the masterpiece of working binaries...",
332 "๐ฆ Crab walking through memory safety checks...",
333 "๐ฆ Pinning ownership to the compilation board...",
334 "๐ฆ Borrowing references from the lending library...",
335 "๐ฆ Sending values across the borrow checker...",
336 "๐ฆ Moving types through the ownership maze...",
337 "๐ฆ Deriving traits from the trait workshop...",
338 "๐ฆ Implementing interfaces in the code factory...",
339 "๐ฆ Matching patterns in the pattern matching parlor...",
340 "๐ช Clown car of dependencies arriving...",
341 "๐ค Robot army assembling your binaries...",
342 "๐ Spaceship preparing for launch sequence...",
343 "๐ง Brain computing optimal compilation path...",
344 "๐ฏ Target acquired - building with precision...",
345 "๐งฉ Piecing together the puzzle of dependencies...",
346 "๐ช Big top compilation show in progress...",
347 "๐ช Tent of dependencies being raised...",
348 "โ๏ธ Sunny compilation day ahead...",
349 "๐ Night shift compilation crew reporting...",
350 "โ๏ธ Cool compilation in progress...",
351 "๐ฅ Hot compilation action heating up...",
352 "๐ช๏ธ Tornado of dependencies spinning up...",
353 "๐ Rainbow compilation bridge forming...",
354 "โญ Starry night compilation under way...",
355 "๐ Galactic compilation sequence initiated...",
356];
357const BUILD_STAGES: &[&str] = &[
358 "๐ Analyzing dependencies in the code harbor...",
359 "๐ฆ Downloading crates from the digital dockyard...",
360 "๐จ Compiling dependencies in the shipyard forge...",
361 "โ๏ธ Building project with precision engineering...",
362 "๐งช Running tests through quality control gauntlet...",
363 "๐ Generating documentation for future explorers...",
364 "๐ Finalizing build - preparing for launch sequence...",
365 "๐ฏ Calibrating build targets and cross-checking manifests...",
366 "๐ฌ Inspecting binaries under the quality microscope...",
367 "๐ Generating build metrics and performance reports...",
368 "๐งน Sweeping up compilation artifacts and loose ends...",
369 "๐ Polishing the final executable to a mirror shine...",
370 "๐ Loading binary into launch tube - ready for deployment...",
371];
372pub fn run_cargo_with_display(args: &[&str]) {
373 let start_time = Instant::now();
374
375 let cmd_name = args.get(0).map(|s| *s).unwrap_or("");
378 let needs_wrapper = cmd_name == "build";
379
380 if !needs_wrapper {
381 let mut command = Command::new("cargo");
385 command.args(args);
386 let status = command.status().unwrap_or_else(|e| {
388 eprintln!("Failed to start cargo: {}", e);
389 std::process::exit(1);
390 });
391 std::process::exit(status.code().unwrap_or(1));
392 }
393
394 let mut error_deduplicator = ErrorDeduplicator::new();
395 let error_prioritizer = ErrorPrioritizer::new();
396 let mut build_coach = BuildCoach::new();
397
398 let mut command = Command::new("cargo");
401 command.args(args);
402
403 if !args.iter().any(|arg| matches!(*arg, "--help" | "-h" | "help")) {
405 command.arg("--message-format=json");
406 }
407
408 let mut child = command
409 .stdout(Stdio::piped())
410 .stderr(Stdio::piped())
411 .spawn()
412 .unwrap_or_else(|e| {
413 eprintln!("Failed to start cargo: {}", e);
414 std::process::exit(1);
415 });
416 let stdout = child.stdout.take().unwrap();
417 let stderr = child.stderr.take().unwrap();
418 let reader = BufReader::new(stdout);
419 let err_reader = BufReader::new(stderr);
420 let mut errors = Vec::new();
421 let mut warnings = Vec::new();
422 let mut artifacts = Vec::new();
423 let mut build_scripts = Vec::new();
424 let error_count = Arc::new(AtomicUsize::new(0));
425 let warning_count = Arc::new(AtomicUsize::new(0));
426 let artifact_count = Arc::new(AtomicUsize::new(0));
427 if let Some(message) = NAUTICAL_MESSAGES.get(0) {
429 println!("{}", message.cyan());
430 }
431
432 let multi_progress = MultiProgress::new();
435 let main_pb = create_main_progress_bar();
436 let main_pb = multi_progress.add(main_pb);
437 let status_pb = create_status_bar();
438 let status_pb = multi_progress.add(status_pb);
439 let file_pb = create_file_counter_bar();
440 let file_pb = multi_progress.add(file_pb);
441
442 main_pb.set_message(format!("๐ข {}", args.join(" ")));
443 status_pb.set_message("โณ Initializing...");
444 file_pb.set_message("๐ 0 files processed");
445 let mut message_index = 0;
446 let mut stage_index = 0;
447 let mut tick_count = 0;
448 let mut last_stage_change = Instant::now();
449 let mut last_output = Instant::now();
450
451 if !NAUTICAL_MESSAGES.is_empty() && message_index >= NAUTICAL_MESSAGES.len() {
452 message_index = 0;
453 }
454 if !BUILD_STAGES.is_empty() && stage_index >= BUILD_STAGES.len() {
455 stage_index = 0;
456 }
457
458 let err_handle = thread::spawn(move || {
461 let reader = BufReader::new(err_reader);
462 for line in reader.lines() {
463 if let Ok(line) = line {
464 eprintln!("{}", line);
465 }
466 }
467 });
468
469 for line in reader.lines() {
471 if let Ok(line) = line {
472 if !line.trim_start().starts_with('{') {
475 println!("{}", line);
476 last_output = Instant::now();
477 continue; }
479
480 if let Some(msg) = parser::parse_cargo_message(&line) {
481 match msg.data {
482 MessageData::CompilerMessage(cm) => {
483 match cm.message.level.as_str() {
484 "error" => {
485 let parsed_error = parser::format_error(&cm.message);
486 errors.push(parsed_error.clone());
487 error_count.store(errors.len(), Ordering::Relaxed);
488
489 if errors.len() <= 3 {
491 println!("๐ด {}", parsed_error);
492 } else if errors.len() == 4 {
493 println!("๐ด ... and more errors");
494 }
495
496 status_pb
497 .set_message(
498 format!(
499 "๐ด {} errors, โ ๏ธ {} warnings", error_count
500 .load(Ordering::Relaxed), warning_count
501 .load(Ordering::Relaxed)
502 ),
503 );
504 error_deduplicator.process_errors(&[parsed_error]);
505 }
506 "warning" => {
507 let parsed_warning = parser::format_warning(&cm.message);
508 warnings.push(parsed_warning.clone());
509 warning_count.store(warnings.len(), Ordering::Relaxed);
510
511 if warnings.len() <= 3 {
513 println!("โ ๏ธ {}", parsed_warning);
514 } else if warnings.len() == 4 {
515 println!("โ ๏ธ ... and more warnings");
516 }
517
518 status_pb
519 .set_message(
520 format!(
521 "๐ด {} errors, โ ๏ธ {} warnings", error_count
522 .load(Ordering::Relaxed), warning_count
523 .load(Ordering::Relaxed)
524 ),
525 );
526 }
527 _ => {}
528 }
529 }
530 MessageData::BuildScriptExecuted(bs) => {
531 build_scripts.push(bs);
532 artifact_count
533 .store(
534 artifact_count.load(Ordering::Relaxed) + 1,
535 Ordering::Relaxed,
536 );
537 file_pb
538 .set_message(
539 format!(
540 "๐ {} files, ๐จ {} build scripts", artifact_count
541 .load(Ordering::Relaxed), build_scripts.len()
542 ),
543 );
544 }
545 MessageData::CompilerArtifact(ca) => {
546 let target_name = ca.target.name.clone();
547 artifacts.push(ca);
548 artifact_count.store(artifacts.len(), Ordering::Relaxed);
549
550 if artifacts.len() <= 3 {
552 println!("๐ฆ Compiled: {}", target_name);
553 }
554
555 file_pb
556 .set_message(
557 format!(
558 "๐ {} files, ๐จ {} build scripts", artifact_count
559 .load(Ordering::Relaxed), build_scripts.len()
560 ),
561 );
562 }
563 _ => {}
564 }
565 tick_count += 1;
566 last_output = Instant::now();
567
568 if last_output.elapsed() > Duration::from_millis(1000) {
571 if tick_count > 1_000_000 {
573 tick_count = 0;
574 }
575 if tick_count % 20 == 0 && !NAUTICAL_MESSAGES.is_empty() {
576 message_index = (message_index + 1) % NAUTICAL_MESSAGES.len();
577 if let Some(message) = NAUTICAL_MESSAGES.get(message_index) {
578 main_pb.set_prefix(message.to_string());
579 }
580 }
581 if last_stage_change.elapsed() > Duration::from_secs(5)
582 && !BUILD_STAGES.is_empty()
583 {
584 stage_index = (stage_index + 1) % BUILD_STAGES.len();
585 if let Some(stage) = BUILD_STAGES.get(stage_index) {
586 status_pb.set_message(stage.to_string());
587 }
588 last_stage_change = Instant::now();
589 }
590 }
591 if tick_count % 5 == 0 {
593 main_pb.tick();
594 status_pb.tick();
595 file_pb.tick();
596 }
597 }
598 }
599 }
600 let elapsed = start_time.elapsed();
601
602 main_pb.finish();
604 status_pb.finish();
605 file_pb.finish();
606
607 let _ = err_handle.join();
608 let status = child.wait().unwrap();
609 let has_recurring_errors = !errors.is_empty()
610 && error_count.load(Ordering::Relaxed) > 1;
611 let build_context = BuildContext {
612 elapsed,
613 warning_count: warnings.len(),
614 error_count: errors.len(),
615 has_recurring_errors,
616 };
617 display_summary(
619 &errors,
620 &warnings,
621 &artifacts,
622 &build_scripts,
623 status.success(),
624 elapsed,
625 );
626
627 if let Some(tip) = build_coach.check_and_show_tip(&build_context) {
629 println!("\n{}", tip.cyan());
630 }
631
632 if !errors.is_empty() {
634 let prioritized_errors = error_prioritizer.sort_errors(errors.clone());
635 process_and_display_errors(&prioritized_errors);
636 }
637
638 save_results(&errors, &warnings, &artifacts, &build_scripts, args);
640 record_build_metrics(args, elapsed, errors.len(), warnings.len(), status.success());
641
642 if !errors.is_empty() || !warnings.is_empty() {
644 checklist::generate_checklist(&errors, &warnings);
645 println!("\n๐ Run {} to see your checklist", "cm checklist".yellow());
646 }
647 display_view_options(&errors, &warnings, &artifacts, &build_scripts);
648}
649fn create_main_progress_bar() -> ProgressBar {
650 let pb = ProgressBar::new_spinner();
651 pb.set_style(
652 ProgressStyle::default_spinner()
653 .template("{prefix:.cyan} {spinner:.green} {msg}")
654 .unwrap()
655 .tick_chars("|-\\|/-"),
656 );
657 pb.enable_steady_tick(Duration::from_millis(80));
658 pb
659}
660fn create_status_bar() -> ProgressBar {
661 let pb = ProgressBar::new_spinner();
662 pb.set_style(
663 ProgressStyle::default_spinner()
664 .template("{spinner:.blue} {msg}")
665 .unwrap()
666 .tick_chars("...oooOOO"),
667 );
668 pb.enable_steady_tick(Duration::from_millis(120));
669 pb
670}
671fn create_file_counter_bar() -> ProgressBar {
672 let pb = ProgressBar::new_spinner();
673 pb.set_style(
674 ProgressStyle::default_spinner()
675 .template("{spinner:.yellow} {msg}")
676 .unwrap()
677 .tick_chars("123456789"),
678 );
679 pb.enable_steady_tick(Duration::from_millis(100));
680 pb
681}
682fn save_results(
683 errors: &[ParsedError],
684 warnings: &[ParsedWarning],
685 artifacts: &[parser::CompilerArtifact],
686 build_scripts: &[parser::BuildScriptExecuted],
687 args: &[&str],
688) {
689 let shipwreck = dirs::home_dir().unwrap().join(".shipwreck");
690 fs::create_dir_all(&shipwreck).unwrap();
691 let error_file = shipwreck.join("errors").join("latest.txt");
692 fs::create_dir_all(error_file.parent().unwrap()).unwrap();
693 let mut f = fs::File::create(&error_file).unwrap();
694 for error in errors {
695 writeln!(f, "{}", error).unwrap();
696 }
697 let warning_file = shipwreck.join("warnings").join("latest.txt");
698 fs::create_dir_all(warning_file.parent().unwrap()).unwrap();
699 let mut f = fs::File::create(&warning_file).unwrap();
700 for warning in warnings {
701 writeln!(f, "{}", warning).unwrap();
702 }
703 let artifact_file = shipwreck.join("artifacts").join("latest.txt");
704 fs::create_dir_all(artifact_file.parent().unwrap()).unwrap();
705 let mut f = fs::File::create(&artifact_file).unwrap();
706 for artifact in artifacts {
707 writeln!(f, "๐ฆ {} -> {}", artifact.target.name, artifact.filenames.join(", "))
708 .unwrap();
709 }
710 let script_file = shipwreck.join("scripts").join("latest.txt");
711 fs::create_dir_all(script_file.parent().unwrap()).unwrap();
712 let mut f = fs::File::create(&script_file).unwrap();
713 for script in build_scripts {
714 writeln!(
715 f, "๐จ {} -> libs: {}, paths: {}, cfgs: {}", script.package_id, script
716 .linked_libs.len(), script.linked_paths.len(), script.cfgs.len()
717 )
718 .unwrap();
719 }
720 history::save_to_history(args.join(" "), errors.to_vec(), warnings.to_vec());
721}
722fn display_summary(
723 errors: &[ParsedError],
724 warnings: &[ParsedWarning],
725 artifacts: &[parser::CompilerArtifact],
726 build_scripts: &[parser::BuildScriptExecuted],
727 success: bool,
728 elapsed: Duration,
729) {
730 println!("\n{}", "โ".repeat(60).blue());
731 if success && errors.is_empty() {
732 println!("{}", "โ
Build Successful!".green().bold());
733 } else {
734 println!("{}", "โ Build Failed!".red().bold());
735 }
736 println!("โฑ๏ธ Build time: {:.1}s", elapsed.as_secs_f32());
737 println!("๐ Files generated: {}", artifacts.len());
738 println!("๐จ Build scripts: {}", build_scripts.len());
739 if !errors.is_empty() {
740 println!("\n{}", format!("๐ด {} Error(s):", errors.len()) .red().bold());
741 for (i, error) in errors.iter().take(3).enumerate() {
743 println!(" {}. {}", i + 1, error);
744 }
745 if errors.len() > 3 {
746 println!(" ... and {} more", errors.len() - 3);
747 }
748 }
749 if !warnings.is_empty() {
750 println!(
751 "\n{}", format!("โ ๏ธ {} Warning(s):", warnings.len()) .yellow().bold()
752 );
753 for (i, warning) in warnings.iter().take(3).enumerate() {
755 println!(" {}. {}", i + 1, warning);
756 }
757 if warnings.len() > 3 {
758 println!(" ... and {} more", warnings.len() - 3);
759 }
760 }
761 println!("{}", "โ".repeat(60).blue());
762}
763fn display_view_options(
764 errors: &[ParsedError],
765 warnings: &[ParsedWarning],
766 _artifacts: &[parser::CompilerArtifact],
767 _build_scripts: &[parser::BuildScriptExecuted],
768) {
769 println!("\n๐ View Options:");
770 println!(" {} - View all errors and warnings", "cm view errors".cyan());
771 println!(" {} - View generated files and locations", "cm view artifacts".cyan());
772 println!(" {} - View build script outputs", "cm view scripts".cyan());
773 println!(" {} - View detailed build history", "cm view history".cyan());
774 println!(" {} - View checklist and fixes", "cm view checklist".cyan());
775 println!(" {} - View all results in one place", "cm view all".cyan());
776 if !errors.is_empty() || !warnings.is_empty() {
777 println!(" {} - Quick view of latest issues", "cm view latest".cyan());
778 }
779 println!(" {} - Open results in file explorer", "cm view open".cyan());
780}
781fn record_build_metrics(
782 args: &[&str],
783 elapsed: Duration,
784 error_count: usize,
785 warning_count: usize,
786 success: bool,
787) {
788 if let Ok(mut tide) = TideCharts::new() {
789 let command = format!("cargo {}", args.join(" "));
790 let profile = determine_profile(args);
791 let features = extract_features(args);
792 let dependencies_compiled = get_dependencies_compiled();
793 let crate_units_compiled = get_crate_units_compiled();
794 let metrics = BuildMetrics {
795 timestamp: Utc::now(),
796 command,
797 duration_seconds: elapsed.as_secs_f64(),
798 success,
799 error_count: error_count.try_into().unwrap(),
800 warning_count: warning_count.try_into().unwrap(),
801 incremental: args.contains(&"--incremental") || args.contains(&"-i"),
802 profile,
803 features,
804 dependencies_compiled: dependencies_compiled.try_into().unwrap(),
805 crate_units_compiled: crate_units_compiled.try_into().unwrap(),
806 memory_peak_mb: None,
807 cpu_usage_percent: None,
808 };
809 if let Err(e) = tide.record_build(metrics) {
810 eprintln!("โ ๏ธ Failed to record build metrics: {}", e);
811 }
812 }
813}
814fn determine_profile(args: &[&str]) -> String {
815 if args.contains(&"--release") {
816 "release".to_string()
817 } else if args.contains(&"--debug") {
818 "debug".to_string()
819 } else {
820 for (i, arg) in args.iter().enumerate() {
821 if *arg == "--profile" && i + 1 < args.len() {
822 return args[i + 1].to_string();
823 }
824 }
825 "debug".to_string()
826 }
827}
828fn extract_features(args: &[&str]) -> Vec<String> {
829 let mut features = Vec::new();
830 let mut found_features = false;
831 for (i, arg) in args.iter().enumerate() {
832 if *arg == "--features" && i + 1 < args.len() {
833 found_features = true;
834 features = args[i + 1].split(',').map(|s| s.trim().to_string()).collect();
835 break;
836 } else if *arg == "--all-features" {
837 features.push("all-features".to_string());
838 break;
839 } else if *arg == "--no-default-features" {
840 features.push("no-default-features".to_string());
841 }
842 }
843 if !found_features && !args.contains(&"--no-default-features")
844 && !args.contains(&"--all-features")
845 {
846 features.push("default".to_string());
847 }
848 features
849}
850fn get_dependencies_compiled() -> usize {
851 match Command::new("cargo").args(&["metadata", "--format-version", "1"]).output() {
852 Ok(output) if output.status.success() => {
853 if let Ok(metadata) = serde_json::from_slice::<
854 serde_json::Value,
855 >(&output.stdout) {
856 if let Some(packages) = metadata
857 .get("packages")
858 .and_then(|p| p.as_array())
859 {
860 if let Some(root) = metadata.get("root").and_then(|r| r.get("name"))
861 {
862 let root_name = root.as_str().unwrap_or("");
863 return packages
864 .iter()
865 .filter(|pkg| {
866 pkg.get("name")
867 .and_then(|n| n.as_str())
868 .map(|name| name != root_name)
869 .unwrap_or(false)
870 })
871 .count();
872 }
873 }
874 }
875 }
876 _ => {}
877 }
878 0
879}
880fn get_crate_units_compiled() -> usize {
881 0
882}
883pub fn check_first_mate_monitor(command: &str) -> Result<bool, anyhow::Error> {
884 println!(
885 "๐ฅฝ First mate monitoring command '{}' - all hands report!", command.cyan()
886 );
887 let license_manager = license::LicenseManager::new();
888 match license_manager?.enforce_license(command) {
889 Ok(_) => {
890 println!(
891 "โ
First mate reports: Command '{}' cleared for action!", command
892 .green()
893 );
894 println!(" ๐ฅฝ All crew stations manned - ready to execute!");
895 Ok(true)
896 }
897 Err(e) => {
898 if e.to_string().contains("limit") {
899 println!("โ ๏ธ First mate's log: Command ration exceeded!");
900 println!(" ๐ฅฝ Resupply at: https://cargo.do/checkout");
901 println!(" ๐ฅฝ Upgrade to unlimited command rations");
902 } else if e.to_string().contains("License not found") {
903 println!("โ First mate reports: No command authority papers!");
904 println!(" ๐ฅฝ Commission with 'cm register <key>'");
905 } else {
906 println!(
907 "โ First mate emergency: Command check failed: {}", e.to_string()
908 .red()
909 );
910 println!(" ๐ฅฝ Secure all stations and alert the captain");
911 }
912 Ok(false)
913 }
914 }
915}
916
917fn handle_publish_version_check() -> Result<(), anyhow::Error> {
918 println!("๐ฆ Checking crates.io for latest version before publish...");
919
920 let cargo_toml = fs::read_to_string("Cargo.toml")
922 .context("Failed to read Cargo.toml")?;
923
924 let package_name = extract_package_name(&cargo_toml)?;
925 let current_version = extract_package_version(&cargo_toml)?;
926
927 println!(" ๐ฆ Package: {}", package_name.cyan());
928 println!(" ๐ฆ Current version: {}", current_version.cyan());
929
930 let client = reqwest::blocking::Client::new();
932 let api_url = format!("https://crates.io/api/v1/crates/{}", package_name);
933
934 let response = client
935 .get(&api_url)
936 .send()
937 .context("Failed to query crates.io API")?;
938
939 if !response.status().is_success() {
940 if response.status() == 404 {
941 println!(" ๐ New package - no existing versions on crates.io");
942 return Ok(());
943 }
944 return Err(anyhow::anyhow!("Crates.io API returned status: {}", response.status()));
945 }
946
947 let api_response: serde_json::Value = response.json()
948 .context("Failed to parse crates.io API response")?;
949
950 if let Some(latest_version) = api_response
951 .get("crate")
952 .and_then(|c| c.get("max_version"))
953 .and_then(|v| v.as_str())
954 {
955 println!(" ๐ฆ Latest published version: {}", latest_version.green());
956
957 if latest_version == current_version {
958 println!(" ๐ Version matches published version - incrementing...");
959 let new_version = increment_version(¤t_version)?;
960 println!(" ๐ฆ New version: {} -> {}", current_version.yellow(), new_version.green());
961
962 update_cargo_toml_version(&cargo_toml, ¤t_version, &new_version)?;
963 println!(" โ
Cargo.toml updated with new version");
964 } else {
965 println!(" โ
Version is newer than published version - proceeding with publish");
966 }
967 } else {
968 println!(" โ ๏ธ Could not determine latest published version");
969 }
970
971 Ok(())
972}
973
974fn extract_package_name(cargo_toml: &str) -> Result<String, anyhow::Error> {
975 for line in cargo_toml.lines() {
976 if line.trim().starts_with("name = ") {
977 let name = line
978 .split('"')
979 .nth(1)
980 .ok_or_else(|| anyhow::anyhow!("Cannot parse package name from Cargo.toml"))?;
981 return Ok(name.to_string());
982 }
983 }
984 Err(anyhow::anyhow!("Package name not found in Cargo.toml"))
985}
986
987fn extract_package_version(cargo_toml: &str) -> Result<String, anyhow::Error> {
988 for line in cargo_toml.lines() {
989 if line.trim().starts_with("version = ") {
990 let version = line
991 .split('"')
992 .nth(1)
993 .ok_or_else(|| anyhow::anyhow!("Cannot parse version from Cargo.toml"))?;
994 return Ok(version.to_string());
995 }
996 }
997 Err(anyhow::anyhow!("Version not found in Cargo.toml"))
998}
999
1000fn increment_version(version: &str) -> Result<String, anyhow::Error> {
1001 let parts: Vec<&str> = version.split('.').collect();
1002 if parts.len() != 3 {
1003 return Err(anyhow::anyhow!("Invalid version format: {}", version));
1004 }
1005
1006 let major: u32 = parts[0].parse()?;
1007 let minor: u32 = parts[1].parse()?;
1008 let patch: u32 = parts[2].parse()?;
1009
1010 let (new_minor, new_patch) = if patch == 9 {
1012 (minor + 1, 0)
1013 } else {
1014 (minor, patch + 1)
1015 };
1016
1017 Ok(format!("{}.{}.{}", major, new_minor, new_patch))
1018}
1019
1020fn update_cargo_toml_version(cargo_toml: &str, old_version: &str, new_version: &str) -> Result<(), anyhow::Error> {
1021 let new_content = cargo_toml.replace(
1022 &format!("version = \"{}\"", old_version),
1023 &format!("version = \"{}\"", new_version)
1024 );
1025
1026 fs::write("Cargo.toml", new_content)
1027 .context("Failed to update Cargo.toml with new version")?;
1028
1029 Ok(())
1030}