Skip to content

Commit 9f83531

Browse files
committed
copy global config files as part of migration
1 parent ef64ab8 commit 9f83531

File tree

4 files changed

+150
-2
lines changed

4 files changed

+150
-2
lines changed

Cargo.lock

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ core-foundation-sys = "0.8.7"
5959
core-graphics = "0.24.0"
6060
crossterm = { version = "0.28.1", features = ["event-stream", "events"] }
6161
dashmap = "6.0.1"
62+
dircpy = "0.3.19"
6263
dirs = "5.0.0"
6364
eyre = "0.6.8"
6465
fig_api_client = { path = "crates/fig_api_client" }

crates/fig_install/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ bytes.workspace = true
1919
camino.workspace = true
2020
cfg-if.workspace = true
2121
cookie = "0.18.0"
22+
dircpy.workspace = true
2223
eyre.workspace = true
2324
fig_integrations.workspace = true
2425
fig_os_shim.workspace = true

crates/fig_install/src/migrate.rs

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ use rustix::fs::{
55
FlockOperation,
66
flock,
77
};
8+
use serde_json::Value;
89
use tokio::fs;
910
use tracing::debug;
1011

1112
const KIRO_MIGRATION_KEY: &str = "migration.kiro.completed";
12-
const MIGRATION_TIMEOUT_SECS: u64 = 10;
13+
const MIGRATION_TIMEOUT_SECS: u64 = 60;
1314

1415
pub async fn migrate_if_needed() -> Result<bool> {
16+
let start = std::time::Instant::now();
1517
let status = detect_migration().await?;
1618

1719
match status {
@@ -54,7 +56,7 @@ pub async fn migrate_if_needed() -> Result<bool> {
5456
debug!("Marking migration as completed");
5557
mark_migration_completed()?;
5658

57-
debug!("Migration completed successfully");
59+
debug!("Migration completed successfully in {:?}", start.elapsed());
5860
Ok(true)
5961
}
6062

@@ -116,6 +118,106 @@ async fn copy_essential_files(src: &std::path::Path, dst: &std::path::Path) -> R
116118
}
117119
}
118120

121+
// Copy global files from home directory
122+
copy_global_config_files()?;
123+
124+
Ok(())
125+
}
126+
127+
fn copy_global_config_files() -> Result<()> {
128+
let home_dir = fig_util::directories::home_dir()?;
129+
let kiro_dir = home_dir.join(".kiro");
130+
let legacy_amazonq_dir = home_dir.join(".aws/amazonq");
131+
132+
let src_dir = if legacy_amazonq_dir.exists() {
133+
&legacy_amazonq_dir
134+
} else {
135+
return Ok(());
136+
};
137+
138+
// Use fig_data_dir for knowledge_bases and cli-checkouts
139+
let data_dir = fig_util::directories::fig_data_dir()?;
140+
141+
let files_to_copy = [
142+
// copy to home/.kiro
143+
("cli-agents", kiro_dir.join("agents")),
144+
("prompts", kiro_dir.join("prompts")),
145+
(".cli_bash_history", kiro_dir.join(".cli_bash_history")),
146+
// copy to data-dir
147+
("cli-checkouts", data_dir.join("cli-checkouts")),
148+
("knowledge_bases", data_dir.join("knowledge_bases")),
149+
];
150+
151+
for (src_subpath, dst_path) in files_to_copy {
152+
let src_path = src_dir.join(src_subpath);
153+
154+
if src_path.exists() {
155+
if let Some(parent) = dst_path.parent() {
156+
std::fs::create_dir_all(parent)?;
157+
}
158+
159+
if src_path.is_dir() {
160+
debug!("Copying directory {} to {}", src_path.display(), dst_path.display());
161+
dircpy::copy_dir(&src_path, &dst_path)?;
162+
} else {
163+
debug!("Copying file {} to {}", src_path.display(), dst_path.display());
164+
std::fs::copy(&src_path, &dst_path)?;
165+
}
166+
}
167+
}
168+
169+
// Handle mcp.json separately with merging
170+
let src_mcp = src_dir.join("mcp.json");
171+
let dst_mcp = kiro_dir.join("settings/mcp.json");
172+
if src_mcp.exists() {
173+
merge_mcp_json(&src_mcp, &dst_mcp)?;
174+
}
175+
176+
Ok(())
177+
}
178+
179+
fn merge_mcp_json(src_path: &std::path::Path, dst_path: &std::path::Path) -> Result<()> {
180+
if let Some(parent) = dst_path.parent() {
181+
std::fs::create_dir_all(parent)?;
182+
}
183+
184+
let src_content = std::fs::read_to_string(src_path)?;
185+
let src_json = match serde_json::from_str::<Value>(&src_content) {
186+
Ok(json) => json,
187+
Err(_) => {
188+
debug!("Invalid JSON in source mcp.json, leaving destination unchanged");
189+
return Ok(());
190+
},
191+
};
192+
193+
let merged_json = if dst_path.exists() {
194+
let dst_content = std::fs::read_to_string(dst_path)?;
195+
match serde_json::from_str::<Value>(&dst_content) {
196+
Ok(mut dst_json) => {
197+
if let (Some(src_servers), Some(dst_servers)) = (
198+
src_json.get("mcpServers").and_then(|v| v.as_object()),
199+
dst_json.get_mut("mcpServers").and_then(|v| v.as_object_mut()),
200+
) {
201+
for (key, value) in src_servers {
202+
if !dst_servers.contains_key(key) {
203+
dst_servers.insert(key.clone(), value.clone());
204+
}
205+
}
206+
}
207+
dst_json
208+
},
209+
Err(_) => {
210+
debug!("Invalid JSON in destination mcp.json, overwriting with source");
211+
src_json
212+
},
213+
}
214+
} else {
215+
src_json
216+
};
217+
218+
let merged_content = serde_json::to_string_pretty(&merged_json)?;
219+
std::fs::write(dst_path, merged_content)?;
220+
debug!("Merged mcp.json to {}", dst_path.display());
119221
Ok(())
120222
}
121223

0 commit comments

Comments
 (0)