Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 38 additions & 18 deletions src/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,38 @@ pub struct PackageVariant {
}

impl Package {
/// Load a package from a directory
/// Load a package from a directory containing package.yaml
pub fn load(path: &Path) -> Result<Self> {
let package_file = path.join("package.yaml");

if !package_file.exists() {
anyhow::bail!("Package file not found: {:?}", package_file);
}

let content = std::fs::read_to_string(&package_file)
.with_context(|| format!("Failed to read package: {:?}", package_file))?;


Self::load_from_file(&package_file, Some(path))
}

/// Load a package from a YAML file directly.
/// If `root` is None, the parent directory of the file is used as the package root.
pub fn load_from_file(file_path: &Path, root: Option<&Path>) -> Result<Self> {
if !file_path.exists() {
anyhow::bail!("Package file not found: {:?}", file_path);
}

let content = std::fs::read_to_string(file_path)
.with_context(|| format!("Failed to read package: {:?}", file_path))?;

let mut package: Package = serde_yaml::from_str(&content)
.with_context(|| format!("Failed to parse package: {:?}", package_file))?;

package.root = path.to_path_buf();

.with_context(|| format!("Failed to parse package: {:?}", file_path))?;

package.root = root
.map(|p| p.to_path_buf())
.or_else(|| file_path.parent().map(|p| p.to_path_buf()))
.unwrap_or_default();

// Apply variant for current platform
package.apply_current_variant();

Ok(package)
}

Expand Down Expand Up @@ -107,31 +120,38 @@ impl Package {
}
}

/// Expand environment variables in a value
/// Expand environment variables and tilde in a value
pub fn expand_env_value(&self, value: &str, env: &HashMap<String, String>) -> String {
let mut result = value.to_string();

// Replace ${PACKAGE_ROOT} with actual path
result = result.replace("${PACKAGE_ROOT}", &self.root.to_string_lossy());

// Replace ${VERSION} with package version
result = result.replace("${VERSION}", &self.version);

// Replace ${NAME} with package name
result = result.replace("${NAME}", &self.name);

// Replace other ${VAR} references
for (key, val) in env {
result = result.replace(&format!("${{{}}}", key), val);
}

// Replace remaining ${VAR} with current environment
let re = regex::Regex::new(r"\$\{([^}]+)\}").unwrap();
result = re.replace_all(&result, |caps: &regex::Captures| {
let var = &caps[1];
std::env::var(var).unwrap_or_default()
}).to_string();


// Expand ~ to home directory
if result.starts_with("~/") {
if let Ok(home) = std::env::var("HOME") {
result = format!("{}{}", home, &result[1..]);
}
}

result
}

Expand Down
99 changes: 56 additions & 43 deletions src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,64 +54,77 @@ impl Resolver {
Ok(resolver)
}

/// Scan package paths and load all packages
/// Scan package paths and load all packages.
///
/// Supports two layouts:
///
/// 1. **Flat files** — YAML files directly in the package path:
/// `{package_path}/maya-2026.yaml`
/// The file must contain `name` and `version` fields.
///
/// 2. **Nested directories** — the original `{name}/{version}/package.yaml` layout:
/// `{package_path}/maya/2026/package.yaml`
fn scan_packages(&mut self) -> Result<()> {
for base_path in self.config.all_package_paths() {
debug!("Scanning packages in {:?}", base_path);

if !base_path.exists() {
continue;
}

// Iterate over package directories

for entry in std::fs::read_dir(&base_path)? {
let entry = entry?;
let pkg_dir = entry.path();

if !pkg_dir.is_dir() {
continue;
}

let pkg_name = pkg_dir.file_name()
.and_then(|n| n.to_str())
.map(|s| s.to_string());

let pkg_name = match pkg_name {
Some(n) => n,
None => continue,
};

// Iterate over versions
for version_entry in std::fs::read_dir(&pkg_dir)? {
let version_entry = version_entry?;
let version_dir = version_entry.path();

if !version_dir.is_dir() {
continue;
}

// Check for package.yaml
let package_file = version_dir.join("package.yaml");
if !package_file.exists() {
continue;
let path = entry.path();

if path.is_file() {
// Flat file: load any .yaml / .yml file directly
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
if ext == "yaml" || ext == "yml" {
match Package::load_from_file(&path, None) {
Ok(pkg) => {
debug!("Loaded package (flat): {}-{}", pkg.name, pkg.version);
self.package_cache
.entry(pkg.name.clone())
.or_default()
.insert(pkg.version.clone(), pkg);
}
Err(e) => {
warn!("Failed to load package {:?}: {}", path, e);
}
}
}
match Package::load(&version_dir) {
Ok(pkg) => {
debug!("Loaded package: {}-{}", pkg.name, pkg.version);
self.package_cache
.entry(pkg.name.clone())
.or_default()
.insert(pkg.version.clone(), pkg);
} else if path.is_dir() {
// Nested directory: {name}/{version}/package.yaml
for version_entry in std::fs::read_dir(&path)? {
let version_entry = version_entry?;
let version_dir = version_entry.path();

if !version_dir.is_dir() {
continue;
}
Err(e) => {
warn!("Failed to load package {:?}: {}", version_dir, e);

let package_file = version_dir.join("package.yaml");
if !package_file.exists() {
continue;
}

match Package::load(&version_dir) {
Ok(pkg) => {
debug!("Loaded package (nested): {}-{}", pkg.name, pkg.version);
self.package_cache
.entry(pkg.name.clone())
.or_default()
.insert(pkg.version.clone(), pkg);
}
Err(e) => {
warn!("Failed to load package {:?}: {}", version_dir, e);
}
}
}
}
}
}

info!("Loaded {} packages", self.package_cache.len());
Ok(())
}
Expand Down
Loading