blueprint_engine_core/
package.rs1use crate::{BlueprintError, Result};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone)]
5pub struct PackageSpec {
6 pub user: String,
7 pub repo: String,
8 pub version: String,
9}
10
11impl PackageSpec {
12 pub fn parse(package: &str) -> Result<Self> {
13 let path = package.strip_prefix('@').unwrap_or(package);
14
15 let (repo_path, version) = if let Some(idx) = path.find('#') {
16 (&path[..idx], Some(&path[idx + 1..]))
17 } else {
18 (path, None)
19 };
20
21 let parts: Vec<&str> = repo_path.splitn(2, '/').collect();
22 if parts.len() != 2 {
23 return Err(BlueprintError::ArgumentError {
24 message: "Invalid package format. Expected @user/repo or @user/repo#version".into(),
25 });
26 }
27
28 Ok(Self {
29 user: parts[0].to_string(),
30 repo: parts[1].to_string(),
31 version: version.unwrap_or("main").to_string(),
32 })
33 }
34
35 pub fn display_name(&self) -> String {
36 format!("@{}/{}#{}", self.user, self.repo, self.version)
37 }
38
39 pub fn dir_name(&self) -> String {
40 format!("{}#{}", self.repo, self.version)
41 }
42}
43
44pub fn find_workspace_root() -> Option<PathBuf> {
45 find_workspace_root_from(std::env::current_dir().ok()?)
46}
47
48pub fn find_workspace_root_from(start: PathBuf) -> Option<PathBuf> {
49 let mut current = start;
50 loop {
51 let bp_toml = current.join("BP.toml");
52 if bp_toml.exists() {
53 return Some(current);
54 }
55 if !current.pop() {
56 break;
57 }
58 }
59 None
60}
61
62pub fn get_packages_dir() -> PathBuf {
63 if let Some(workspace) = find_workspace_root() {
64 workspace.join(".blueprint").join("packages")
65 } else {
66 let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
67 PathBuf::from(&home).join(".blueprint").join("packages")
68 }
69}
70
71pub fn get_packages_dir_from(start: Option<PathBuf>) -> PathBuf {
72 let workspace = start.and_then(find_workspace_root_from);
73 if let Some(ws) = workspace {
74 ws.join(".blueprint").join("packages")
75 } else {
76 let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
77 PathBuf::from(&home).join(".blueprint").join("packages")
78 }
79}
80
81pub fn fetch_package(spec: &PackageSpec, dest: &PathBuf) -> Result<()> {
82 let repo_url = format!("https://github.com/{}/{}.git", spec.user, spec.repo);
83
84 let output = std::process::Command::new("git")
85 .args(["clone", "--depth", "1", "--branch", &spec.version, &repo_url])
86 .arg(dest)
87 .output()
88 .map_err(|e| BlueprintError::IoError {
89 path: repo_url.clone(),
90 message: e.to_string(),
91 })?;
92
93 if !output.status.success() {
94 std::fs::remove_dir_all(dest).ok();
95 let stderr = String::from_utf8_lossy(&output.stderr);
96 return Err(BlueprintError::IoError {
97 path: repo_url,
98 message: format!("Failed to clone: {}", stderr.trim()),
99 });
100 }
101
102 std::fs::remove_dir_all(dest.join(".git")).ok();
103
104 Ok(())
105}