use super::*;
pub(super) fn copy_or_move_selected_file(
selected: &Path,
workspace: &Path,
default_name: &str,
move_file: bool,
) -> Result<PathBuf> {
fs::create_dir_all(workspace)
.with_context(|| format!("failed to create {}", workspace.display()))?;
set_private_dir_permissions(workspace)?;
let dest = workspace.join(default_name);
if move_file {
fs::rename(selected, &dest).with_context(|| {
format!(
"failed to move {} to {}",
selected.display(),
dest.display()
)
})?;
} else {
fs::copy(selected, &dest).with_context(|| {
format!(
"failed to copy {} to {}",
selected.display(),
dest.display()
)
})?;
}
set_private_file_permissions(&dest)?;
Ok(dest)
}
pub(super) fn path_is_inside_project(path: &Path, project_dir: &Path) -> bool {
let Ok(project) = project_dir.canonicalize() else {
return false;
};
let Ok(path) = path.canonicalize() else {
return false;
};
path.starts_with(project)
}
pub(super) fn read_env_entries(path: &Path) -> Result<std::collections::BTreeMap<String, String>> {
let mut entries = std::collections::BTreeMap::new();
if !path.exists() {
return Ok(entries);
}
let text = fs::read_to_string(path)?;
for line in text.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let line = line.strip_prefix("export ").unwrap_or(line);
if let Some((key, value)) = line.split_once('=') {
entries.insert(key.trim().to_string(), unquote_env_value(value.trim()));
}
}
Ok(entries)
}
pub(super) fn upsert_env(path: &Path, key: &str, value: &str) -> Result<()> {
let mut entries = read_env_entries(path)?;
entries.insert(key.to_string(), value.to_string());
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
set_private_dir_permissions(parent)?;
}
let mut out = String::new();
out.push_str("# Fission local release environment. Do not commit this file.\n");
out.push_str("# Generated by the publish cockpit.\n");
for (key, value) in entries {
out.push_str("export ");
out.push_str(&key);
out.push('=');
out.push_str("e_env_value(&value));
out.push('\n');
}
fs::write(path, out)?;
set_private_file_permissions(path)?;
Ok(())
}
pub(super) fn quote_env_value(value: &str) -> String {
format!(
"\"{}\"",
value
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('$', "\\$")
)
}
pub(super) fn unquote_env_value(value: &str) -> String {
let trimmed = value.trim();
if trimmed.len() >= 2 && trimmed.starts_with('"') && trimmed.ends_with('"') {
let inner = &trimmed[1..trimmed.len() - 1];
inner
.replace("\\\"", "\"")
.replace("\\$", "$")
.replace("\\\\", "\\")
} else {
trimmed.to_string()
}
}
pub(super) fn sanitize_workspace_name(value: &str) -> String {
let out = value
.chars()
.map(|ch| match ch {
'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' => ch.to_ascii_lowercase(),
_ => '-',
})
.collect::<String>()
.trim_matches(['-', '.', '_'])
.to_string();
if out.is_empty() {
"app".to_string()
} else {
out
}
}
#[cfg(unix)]
pub(super) fn set_private_dir_permissions(path: &Path) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(0o700))?;
Ok(())
}
#[cfg(not(unix))]
pub(super) fn set_private_dir_permissions(_path: &Path) -> Result<()> {
Ok(())
}
#[cfg(unix)]
pub(super) fn set_private_file_permissions(path: &Path) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(0o600))?;
Ok(())
}
#[cfg(not(unix))]
pub(super) fn set_private_file_permissions(_path: &Path) -> Result<()> {
Ok(())
}