#![allow(dead_code)]
use std::path::Path;
use clap::{Args as ClapArgs, ValueEnum};
use crate::error::CliError;
use crate::fetch;
use crate::github::GithubClient;
use crate::manifest;
use crate::volume;
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum Target {
Kernel,
Distro,
}
#[derive(Debug, ClapArgs)]
pub struct Args {
#[arg(value_enum, default_value_t = Target::Kernel)]
pub target: Target,
}
pub fn run(args: &Args) -> Result<(), CliError> {
let cwd = std::env::current_dir()
.map_err(|e| CliError::Io(format!("cannot determine current directory: {e}")))?;
let root = volume::find_omne_root(&cwd).ok_or(CliError::NotAVolume)?;
let github = GithubClient::from_env("https://api.github.com", "omne-cli");
upgrade_with_client(args.target, &root, &github)
}
pub fn upgrade_with_client(
target: Target,
root: &Path,
github: &GithubClient,
) -> Result<(), CliError> {
let omne = root.join(".omne");
let readme_path = omne.join("omne.md");
let readme_content = std::fs::read_to_string(&readme_path).map_err(|e| {
CliError::Io(format!(
"cannot read {}: {e} — is this an omne volume?",
readme_path.display()
))
})?;
let frontmatter = manifest::parse_frontmatter(&readme_content)?;
let (org, repo, target_dir, label, expected_top_level) = match target {
Target::Kernel => {
let (org, repo) = parse_source(&frontmatter.kernel_source)?;
let target_dir = omne.join("core");
(org, repo, target_dir, "kernel".to_string(), "core")
}
Target::Distro => {
let (org, repo) = parse_source(&frontmatter.distro_source)?;
let target_dir = omne.join("dist");
(
org,
repo.clone(),
target_dir,
format!("distro ({repo})"),
"dist",
)
}
};
if target_dir.exists() {
verify_no_symlinks(&target_dir, &omne)?;
}
let tag = github.latest_release_tag(&org, &repo)?;
eprintln!("Upgrading {label} to {tag}...");
if target_dir.exists() {
verify_no_symlinks(&target_dir, &omne)?;
std::fs::remove_dir_all(&target_dir)?;
}
fetch::download_and_extract(github, &org, &repo, &tag, &omne, expected_top_level)?;
eprintln!("\x1b[32m✓\x1b[0m Upgrade complete ({label}).");
Ok(())
}
fn parse_source(source: &str) -> Result<(String, String), CliError> {
let (org, repo) =
source
.split_once('/')
.ok_or_else(|| crate::manifest::Error::InvalidSourceFormat {
value: source.to_string(),
})?;
Ok((org.to_string(), repo.to_string()))
}
fn verify_no_symlinks(target_dir: &Path, boundary: &Path) -> Result<(), CliError> {
if target_dir.symlink_metadata()?.file_type().is_symlink() {
return Err(CliError::UnsafeTarget {
path: target_dir.to_path_buf(),
});
}
let mut current = target_dir.to_path_buf();
while let Some(parent) = current.parent() {
if parent.symlink_metadata()?.file_type().is_symlink() {
return Err(CliError::UnsafeTarget {
path: parent.to_path_buf(),
});
}
if parent == boundary {
break;
}
current = parent.to_path_buf();
}
Ok(())
}