Skip to content

Commit 0c52faf

Browse files
committed
feat: migrate dotfiles as part of kiro migration
1 parent d0a8b85 commit 0c52faf

File tree

13 files changed

+239
-52
lines changed

13 files changed

+239
-52
lines changed

crates/fig_desktop/src/install.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ fn run_input_method_migration() {
5656
pub async fn run_install(ctx: Arc<Context>, ignore_immediate_update: bool) {
5757
#[cfg(target_os = "macos")]
5858
{
59-
initialize_fig_dir(&fig_os_shim::Env::new()).await.ok();
59+
initialize_fig_dir(&fig_os_shim::Context::new()).await.ok();
6060

6161
if fig_util::directories::home_dir()
6262
.map(|home| home.join("Library/Application Support/fig/credentials.json"))
@@ -201,7 +201,7 @@ async fn symlink(src: impl AsRef<std::path::Path>, dst: impl AsRef<std::path::Pa
201201
}
202202

203203
#[cfg(target_os = "macos")]
204-
pub async fn initialize_fig_dir(env: &fig_os_shim::Env) -> anyhow::Result<()> {
204+
pub async fn initialize_fig_dir(ctx: &fig_os_shim::Context) -> anyhow::Result<()> {
205205
use std::fs;
206206

207207
use fig_integrations::shell::ShellExt;
@@ -381,7 +381,7 @@ pub async fn initialize_fig_dir(env: &fig_os_shim::Env) -> anyhow::Result<()> {
381381
}
382382
}
383383

384-
for shell_integration in shell.get_shell_integrations(env).unwrap_or_default() {
384+
for shell_integration in shell.get_shell_integrations(ctx).unwrap_or_default() {
385385
if let Err(err) = shell_integration.migrate().await {
386386
error!(%err, "Failed installing shell integration {}", shell_integration.describe());
387387
}

crates/fig_desktop/src/request/onboarding.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use fig_integrations::shell::ShellExt;
22
use fig_os_shim::{
33
ContextArcProvider,
44
ContextProvider,
5-
EnvProvider,
65
};
76
use fig_proto::fig::{
87
OnboardingAction,
@@ -38,7 +37,7 @@ where
3837
OnboardingAction::InstallationScript => {
3938
let mut errs: Vec<String> = vec![];
4039
for shell in [Shell::Bash, Shell::Zsh, Shell::Fish] {
41-
match shell.get_shell_integrations(ctx.env()) {
40+
match shell.get_shell_integrations(ctx) {
4241
Ok(integrations) => {
4342
for integration in integrations {
4443
if let Err(err) = integration.install().await {

crates/fig_desktop_api/src/requests/install.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ where
7373
(InstallComponent::Dotfiles, action) => {
7474
let mut errs: Vec<String> = vec![];
7575
for shell in Shell::all() {
76-
match shell.get_shell_integrations(ctx.env()) {
76+
match shell.get_shell_integrations(ctx) {
7777
Ok(integrations) => {
7878
for integration in integrations {
7979
let res = match action {

crates/fig_install/src/common.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use fig_integrations::shell::ShellExt;
66
use fig_integrations::ssh::SshIntegration;
77
use fig_os_shim::{
88
Context,
9-
Env,
9+
EnvProvider,
10+
FsProvider,
11+
PlatformProvider,
1012
};
1113
use fig_util::{
1214
CHAT_BINARY_NAME,
@@ -53,7 +55,7 @@ pub async fn uninstall(components: InstallComponents, ctx: Arc<Context>) -> Resu
5355

5456
let shell_integration_result = {
5557
for shell in [Shell::Bash, Shell::Zsh, Shell::Fish] {
56-
for integration in shell.get_shell_integrations(ctx.env())? {
58+
for integration in shell.get_shell_integrations(&ctx)? {
5759
integration.uninstall().await?;
5860
}
5961
}
@@ -139,11 +141,14 @@ pub async fn uninstall(components: InstallComponents, ctx: Arc<Context>) -> Resu
139141
.and(ssh_result.map_err(|e| e.into()))
140142
}
141143

142-
pub async fn install(components: InstallComponents, env: &Env) -> Result<(), Error> {
144+
pub async fn install<Ctx: FsProvider + EnvProvider + PlatformProvider>(
145+
components: InstallComponents,
146+
ctx: &Ctx,
147+
) -> Result<(), Error> {
143148
if components.contains(InstallComponents::SHELL_INTEGRATIONS) {
144149
let mut errs: Vec<Error> = vec![];
145150
for shell in Shell::all() {
146-
match shell.get_shell_integrations(env) {
151+
match shell.get_shell_integrations(ctx) {
147152
Ok(integrations) => {
148153
for integration in integrations {
149154
if let Err(e) = integration.install().await {

crates/fig_install/src/migrate.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
use std::path::PathBuf;
22

33
use eyre::Result;
4+
use fig_integrations::shell::ShellExt;
5+
use fig_os_shim::Context;
6+
use fig_util::Shell;
47
use rustix::fs::{
58
FlockOperation,
69
flock,
710
};
811
use serde_json::Value;
912
use tokio::fs;
10-
use tracing::debug;
13+
use tracing::{
14+
debug,
15+
warn,
16+
};
1117

1218
const KIRO_MIGRATION_KEY: &str = "migration.kiro.completed";
1319
const MIGRATION_TIMEOUT_SECS: u64 = 60;
@@ -52,6 +58,12 @@ pub async fn migrate_if_needed() -> Result<bool> {
5258
debug!("Copying essential files from old to new directory");
5359
copy_essential_files(&old_dir, &new_dir).await?;
5460

61+
debug!("Migrating shell integrations");
62+
let errors = migrate_dotfiles().await;
63+
if !errors.is_empty() {
64+
warn!(?errors, "errors occurred migrating shell integrations");
65+
}
66+
5567
// Mark migration as completed in database
5668
debug!("Marking migration as completed");
5769
mark_migration_completed()?;
@@ -221,6 +233,52 @@ fn merge_mcp_json(src_path: &std::path::Path, dst_path: &std::path::Path) -> Res
221233
Ok(())
222234
}
223235

236+
async fn migrate_dotfiles() -> Vec<(Shell, fig_integrations::Error)> {
237+
let shells = Shell::all();
238+
let mut errors = Vec::new();
239+
240+
// First, collect all available shell integrations
241+
let mut shell_integrations = Vec::new();
242+
for shell in shells {
243+
match shell.get_shell_integrations(&Context::new()) {
244+
Ok(integrations) => {
245+
for integ in integrations {
246+
shell_integrations.push((*shell, integ));
247+
}
248+
},
249+
Err(err) => errors.push((*shell, err)),
250+
}
251+
}
252+
253+
// Because the fish shell doesn't support detecting legacy installations (and
254+
// therefore migrating), we're going to iterate through all shells (ie, bash and zsh) to see if a
255+
// legacy installation is detected. If so, then perform the migration.
256+
let mut has_legacy_installation = None;
257+
for (shell, integ) in &shell_integrations {
258+
if let Err(fig_integrations::Error::LegacyInstallation(_)) = integ.is_installed().await {
259+
has_legacy_installation = Some(shell);
260+
}
261+
}
262+
263+
if let Some(shell) = has_legacy_installation {
264+
debug!(
265+
?shell,
266+
"detected legacy dotfile installation, installing dotfile integrations"
267+
);
268+
} else {
269+
debug!("no legacy dotfile installation detected, not migrating dotfiles");
270+
return vec![];
271+
}
272+
273+
for (shell, integration) in shell_integrations {
274+
if let Err(err) = integration.install().await {
275+
errors.push((shell, err));
276+
}
277+
}
278+
279+
errors
280+
}
281+
224282
fn mark_migration_completed() -> Result<()> {
225283
let db = fig_settings::sqlite::database()?;
226284
db.set_state_value(KIRO_MIGRATION_KEY, true)?;
@@ -287,3 +345,15 @@ fn migration_lock_path() -> Result<PathBuf> {
287345
fn is_migration_completed() -> Result<bool> {
288346
Ok(fig_settings::state::get_bool_or(KIRO_MIGRATION_KEY, false))
289347
}
348+
349+
#[cfg(test)]
350+
mod tests {
351+
use super::*;
352+
353+
#[tokio::test]
354+
#[ignore = "not in ci"]
355+
async fn integ_test_migration() {
356+
let _ = tracing_subscriber::fmt::try_init();
357+
migrate_if_needed().await.unwrap();
358+
}
359+
}

0 commit comments

Comments
 (0)