Initial commit

This commit is contained in:
Ryan Ward
2026-04-29 10:43:20 -05:00
parent b7b327510d
commit d8d4ac9e5d
28 changed files with 211 additions and 83 deletions
+139 -8
View File
@@ -24,11 +24,15 @@ use crate::{
AppId,
};
/// The name of the directory in which to put non-global Warp-specific files.
/// The name of the directory in which to put non-global Warp Core-specific files.
///
/// This should be used, for example, as the base directory under which
/// repository workflows would be stored (in "./.warp/workflows").
pub const WARP_CONFIG_DIR: &str = ".warp";
/// repository workflows would be stored (in "./.warp-core/workflows").
pub const WARP_CONFIG_DIR: &str = ".warp-core";
/// The legacy config directory name used by Warp before the rename to Warp Core.
/// Used for auto-migration on first launch.
pub const LEGACY_WARP_CONFIG_DIR: &str = ".warp";
/// The name of the folder that stores Warp execution logs and network logs.
/// This is currently only used on Windows to maintain backwards compatibility.
@@ -59,15 +63,142 @@ pub fn warp_home_config_dir_name() -> String {
}
}
/// Returns the home-relative Warp config directory for the current channel and data profile.
/// Returns the home-relative Warp Core config directory for the current channel and data profile.
///
/// Unlike [`data_dir`] and [`config_local_dir`] on non-macOS platforms, this intentionally keeps
/// Warp-authored, user-facing config under a `.warp*` directory in the home directory instead of
/// user-facing config under a `.warp-core*` directory in the home directory instead of
/// using the platform XDG/AppData project directories.
pub fn warp_home_config_dir() -> Option<PathBuf> {
dirs::home_dir().map(|home_dir| home_dir.join(warp_home_config_dir_name()))
}
/// Returns the legacy `~/.warp*` config directory path for the current channel,
/// used to detect and migrate data from a previous Warp installation.
pub fn legacy_warp_home_config_dir() -> Option<PathBuf> {
let base = LEGACY_WARP_CONFIG_DIR;
let dir_name = match ChannelState::channel() {
Channel::Stable | Channel::Preview => base.to_owned(),
Channel::Oss => format!("{base}-oss"),
Channel::Dev => format!("{base}-dev"),
Channel::Integration => format!("{base}-integration"),
Channel::Local => format!("{base}-local"),
};
let dir_name = if let Some(data_profile) = ChannelState::data_profile() {
format!("{dir_name}-{data_profile}")
} else {
dir_name
};
dirs::home_dir().map(|home_dir| home_dir.join(dir_name))
}
/// Migrates the legacy `~/.warp*` config directory to `~/.warp-core*` if needed.
///
/// This runs once on first launch after the rename. It creates symlinks from the
/// old directory entries into the new directory, preserving access to existing
/// configuration (keybindings, themes, workflows, skills, etc.).
///
/// This is a no-op if:
/// - The new directory already exists.
/// - The old directory does not exist.
pub fn migrate_legacy_config_dir_if_needed() {
let Some(old_dir) = legacy_warp_home_config_dir() else {
return;
};
let Some(new_dir) = warp_home_config_dir() else {
return;
};
if new_dir.exists() || !old_dir.exists() {
return;
}
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
if let Err(err) = std::fs::create_dir(&new_dir) {
if err.kind() != std::io::ErrorKind::AlreadyExists {
log::warn!(
"Failed to create config directory {}: {err}",
new_dir.display()
);
return;
}
}
let entries = match std::fs::read_dir(&old_dir) {
Ok(entries) => entries,
Err(err) => {
log::warn!(
"Failed to read legacy config directory {}: {err}",
old_dir.display()
);
return;
}
};
let mut migrated = 0u32;
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str == ".DS_Store" || name_str.starts_with("._") {
continue;
}
let target = old_dir.join(&name);
let link = new_dir.join(&name);
if let Err(err) = symlink(&target, &link) {
log::warn!(
"Failed to symlink {} -> {}: {err}",
link.display(),
target.display()
);
} else {
migrated += 1;
}
}
log::info!(
"Migrated legacy config directory: created {migrated} symlinks in {}",
new_dir.display()
);
}
#[cfg(windows)]
{
fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
let dest = dst.join(entry.file_name());
if ty.is_dir() {
copy_dir_recursive(&entry.path(), &dest)?;
} else {
std::fs::copy(entry.path(), dest)?;
}
}
Ok(())
}
if let Err(err) = copy_dir_recursive(&old_dir, &new_dir) {
log::warn!(
"Failed to copy legacy config directory {} to {}: {err}",
old_dir.display(),
new_dir.display()
);
} else {
log::info!(
"Migrated legacy config directory {} to {}",
old_dir.display(),
new_dir.display()
);
}
}
}
pub fn warp_home_skills_dir() -> Option<PathBuf> {
warp_home_config_dir().map(|warp_config_dir| warp_config_dir.join("skills"))
}
@@ -78,8 +209,8 @@ pub fn warp_home_mcp_config_file_path() -> Option<PathBuf> {
/// Returns the macOS config directory name for the current channel.
///
/// Stable uses `.warp`, while other channels include a channel suffix
/// (e.g., `.warp-dev`, `.warp-local`).
/// Stable uses `.warp-core`, while other channels include a channel suffix
/// (e.g., `.warp-core-dev`, `.warp-core-local`).
///
/// These suffixes are persisted on disk as directory names and must not be
/// changed once established, or existing user data will be orphaned.
@@ -275,7 +406,7 @@ pub fn app_group_container_path() -> Option<PathBuf> {
let fm = NSFileManager::defaultManager();
// Keep in sync with Entitlements.plist
let group_id = format!("{}.dev.warp", crate::macos::APPLE_TEAM_ID);
let group_id = format!("{}.dev.warpcore", crate::macos::APPLE_TEAM_ID);
let group_id = NSString::from_str(&group_id);
// containerURLForSecurityApplicationGroupIdentifier always returns a value on macOS (unlike iOS).
// We have to double-check that the path points to a directory we can actually use. In addition to
+4 -4
View File
@@ -8,7 +8,7 @@ fn test_data_dir_path() {
// ChannelState, by default, is configured for Channel::Oss.
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
assert_eq!(data_dir(), home_dir.join(".warp-oss"));
assert_eq!(data_dir(), home_dir.join(".warp-core-oss"));
} else if #[cfg(target_os = "linux")] {
assert_eq!(data_dir(), home_dir.join(".local/share/warp-oss"));
} else if #[cfg(windows)] {
@@ -25,7 +25,7 @@ fn test_config_local_dir_path() {
// ChannelState, by default, is configured for Channel::Oss.
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
assert_eq!(config_local_dir(), home_dir.join(".warp-oss"));
assert_eq!(config_local_dir(), home_dir.join(".warp-core-oss"));
} else if #[cfg(target_os = "linux")] {
assert_eq!(config_local_dir(), home_dir.join(".config/warp-oss"));
} else if #[cfg(windows)] {
@@ -40,8 +40,8 @@ fn test_config_local_dir_path() {
fn test_warp_home_config_dir_path() {
let home_dir = home_dir().expect("Should be able to compute home directory");
let expected_dir_name = match ChannelState::data_profile() {
Some(data_profile) => format!(".warp-oss-{data_profile}"),
None => ".warp-oss".to_string(),
Some(data_profile) => format!(".warp-core-oss-{data_profile}"),
None => ".warp-core-oss".to_string(),
};
assert_eq!(