grex_cli/cli/verbs/
import.rs1use crate::cli::args::{GlobalFlags, ImportArgs};
4use anyhow::{anyhow, Context, Result};
5use grex_core::import::{import_from_repos_json, ImportOpts, ImportPlan, SkipReason};
6use grex_core::manifest::{ensure_event_log_migrated, find_workspace_root};
7use tokio_util::sync::CancellationToken;
8
9pub fn run(args: ImportArgs, global: &GlobalFlags, _cancel: &CancellationToken) -> Result<()> {
10 let from = args
11 .from_repos_json
12 .as_deref()
13 .ok_or_else(|| anyhow!("--from-repos-json <path> is required"))?;
14
15 let manifest = match args.manifest.clone() {
20 Some(p) => p,
21 None => {
22 let cwd = std::env::current_dir().context("resolve cwd for workspace root")?;
23 let workspace = find_workspace_root(&cwd);
24 ensure_event_log_migrated(&workspace).context("migrate v1.x event log")?
25 }
26 };
27
28 let dry_run = args.dry_run || global.dry_run;
29
30 let plan = import_from_repos_json(from, &manifest, ImportOpts { dry_run })
31 .context("grex import failed")?;
32
33 if global.json {
34 emit_json(&plan, dry_run)?;
35 } else {
36 emit_human(&plan, dry_run);
37 }
38 Ok(())
39}
40
41fn emit_human(plan: &ImportPlan, dry_run: bool) {
42 for entry in &plan.imported {
43 let prefix = if dry_run { "DRY-RUN: would add" } else { "added" };
44 println!(
45 "{prefix} {path:<32} {kind:<12} {url}",
46 path = entry.path,
47 kind = entry.kind.as_str(),
48 url = if entry.url.is_empty() { "-" } else { &entry.url },
49 );
50 }
51 for skip in &plan.skipped {
52 let reason = match skip.reason {
53 SkipReason::PathCollision => "path-collision",
54 SkipReason::DuplicateInInput => "duplicate-in-input",
55 };
56 eprintln!("skip {:<32} {}", skip.path, reason);
57 }
58 println!(
59 "\nsummary: imported={} skipped={} failed={}",
60 plan.imported.len(),
61 plan.skipped.len(),
62 plan.failed.len(),
63 );
64}
65
66fn emit_json(plan: &ImportPlan, dry_run: bool) -> Result<()> {
74 let imported: Vec<_> = plan
75 .imported
76 .iter()
77 .map(|e| {
78 serde_json::json!({
79 "path": e.path,
80 "url": e.url,
81 "kind": e.kind.as_str(),
82 "would_dispatch": e.would_dispatch,
83 })
84 })
85 .collect();
86 let skipped: Vec<_> = plan
87 .skipped
88 .iter()
89 .map(|s| {
90 serde_json::json!({
91 "path": s.path,
92 "reason": match s.reason {
93 SkipReason::PathCollision => "path_collision",
94 SkipReason::DuplicateInInput => "duplicate_in_input",
95 },
96 })
97 })
98 .collect();
99 let failed: Vec<_> = plan
100 .failed
101 .iter()
102 .map(|f| {
103 serde_json::json!({
104 "path": f.path,
105 "error": f.error,
106 })
107 })
108 .collect();
109 let out = serde_json::json!({
110 "dry_run": dry_run,
111 "imported": imported,
112 "skipped": skipped,
113 "failed": failed,
114 });
115 println!("{}", serde_json::to_string(&out)?);
118 Ok(())
119}