use crate::pkg::types;
use anyhow::{Result, anyhow};
use colored::*;
use serde::Deserialize;
use std::env;
use std::fs;
use zoi_core::types::SourceType;
#[derive(Debug, Deserialize)]
struct RepoFile {
package: String,
}
pub fn run(
repo_spec: &str,
force: bool,
all_optional: bool,
yes: bool,
scope: Option<crate::cli::SetupScope>,
plugin_manager: Option<&crate::pkg::plugin::PluginManager>,
) -> Result<()> {
println!(
"Installing from git repository: {}",
repo_spec.cyan().bold()
);
let (provider, repo_path) = parse_repo_spec(repo_spec)?;
crate::pkg::utils::confirm_untrusted_source(&SourceType::GitRepo(repo_spec.to_string()), yes)?;
let repo_file_names = ["zoi.yaml"];
let mut repo_file_content: Option<String> = None;
let mut used_url = String::new();
for file_name in &repo_file_names {
if let Ok(url) = get_repo_file_url(&provider, &repo_path, file_name) {
println!("Attempting to fetch repo config from: {}", url);
let client = crate::pkg::utils::get_http_client().ok();
if let Some(c) = client
&& let Ok(content_res) = c.get(&url).send()
&& content_res.status().is_success()
{
repo_file_content = Some(content_res.text()?);
used_url = url;
break;
}
}
}
let repo_file_content = repo_file_content.ok_or_else(|| {
anyhow!("Could not find zoi.yaml in the repository on main/master branches.")
})?;
println!("Using repo config from: {}", used_url.cyan());
let repo_file: RepoFile = serde_yaml::from_str(&repo_file_content)?;
let package_source = &repo_file.package;
let scope_override = scope.map(|s| match s {
crate::cli::SetupScope::User => types::Scope::User,
crate::cli::SetupScope::System => types::Scope::System,
});
println!("Starting installation of package from git repo...");
let source_to_install = if package_source.starts_with("http") {
println!("Package source is a URL: {}", package_source.cyan());
let client = crate::pkg::utils::get_http_client()?;
let pkg_content = client.get(package_source).send()?.text()?;
let temp_path = env::temp_dir().join(format!(
"zoi-repo-install-{}.pkg.lua",
chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
));
fs::write(&temp_path, pkg_content)?;
temp_path
.to_str()
.ok_or_else(|| anyhow!("Temporary path contains invalid UTF-8"))?
.to_string()
} else if package_source.ends_with(".pkg.lua")
|| (package_source.contains('/') && !package_source.starts_with('@'))
{
println!(
"Package source is a path in the repo: {}",
package_source.cyan()
);
let pkg_url = get_repo_file_url(&provider, &repo_path, package_source)?;
let client = crate::pkg::utils::get_http_client()?;
let pkg_content = client.get(&pkg_url).send()?.text()?;
let temp_path = env::temp_dir().join(format!(
"zoi-repo-install-{}.pkg.lua",
chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
));
fs::write(&temp_path, pkg_content)?;
temp_path
.to_str()
.ok_or_else(|| anyhow!("Temporary path contains invalid UTF-8"))?
.to_string()
} else {
println!(
"Package source is a package name: {}",
package_source.cyan()
);
package_source.to_string()
};
crate::cmd::install::run(
&[source_to_install],
None,
force,
all_optional,
yes,
scope_override.map(|s| match s {
types::Scope::User => crate::cli::InstallScope::User,
types::Scope::System => crate::cli::InstallScope::System,
types::Scope::Project => crate::cli::InstallScope::Project,
}),
false,
false,
false,
None,
false,
plugin_manager,
false,
false,
false,
false,
3,
false,
false,
None,
)?;
Ok(())
}
fn parse_repo_spec(spec: &str) -> Result<(String, String)> {
if let Some((provider_alias, path)) = spec.split_once(':') {
let provider = match provider_alias {
"gh" | "github" => "github",
"gl" | "gitlab" => "gitlab",
"cb" | "codeberg" => "codeberg",
_ => return Err(anyhow!("Unknown provider alias: {}", provider_alias)),
};
Ok((provider.to_string(), path.to_string()))
} else {
Ok(("github".to_string(), spec.to_string()))
}
}
fn get_repo_file_url(provider: &str, repo_path: &str, file_path: &str) -> Result<String> {
let branches = ["main", "master"];
let client = crate::pkg::utils::get_http_client()?;
for branch in &branches {
let url = match provider {
"github" => format!(
"https://raw.githubusercontent.com/{}/refs/heads/{}/{}",
repo_path, branch, file_path
),
"gitlab" => format!(
"https://gitlab.com/{}/-/raw/{}/{}",
repo_path, branch, file_path
),
"codeberg" => format!(
"https://codeberg.org/{}/raw/branch/{}/{}",
repo_path, branch, file_path
),
_ => return Err(anyhow!("Unknown provider")),
};
let res = client.get(&url).send();
if let Ok(response) = res
&& response.status().is_success()
{
return Ok(url);
}
}
Err(anyhow!(
"Could not find '{}' in repo '{}' on branches main or master.",
file_path,
repo_path
))
}