From cab3a4a956979cf7edb2a372724741d6fc0cc382 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Wed, 11 Mar 2026 11:57:08 +0100 Subject: [PATCH] feat(pack): add list subcommand to show installed packages list_installed_roots() searches WEFT_APP_STORE, then ~/.local/share/weft/apps, then /usr/share/weft/apps (same priority order as weft-runtime and weft-appd). list_installed() deduplicates by app_id (first root wins), sorts alphabetically within each root, and prints id/name/version per line. Prints 'no packages installed' when the store is empty or absent. --- crates/weft-pack/src/main.rs | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/crates/weft-pack/src/main.rs b/crates/weft-pack/src/main.rs index 6e51983..c8a17ed 100644 --- a/crates/weft-pack/src/main.rs +++ b/crates/weft-pack/src/main.rs @@ -51,12 +51,16 @@ fn main() -> anyhow::Result<()> { let app_id = args.get(2).context("usage: weft-pack uninstall ")?; uninstall_package(app_id)?; } + Some("list") => { + list_installed(); + } _ => { eprintln!("usage:"); eprintln!(" weft-pack check validate a package directory"); eprintln!(" weft-pack info print package metadata"); eprintln!(" weft-pack install install package to app store"); eprintln!(" weft-pack uninstall remove installed package"); + eprintln!(" weft-pack list list installed packages"); std::process::exit(1); } } @@ -228,6 +232,55 @@ fn uninstall_package_from(app_id: &str, store_root: &Path) -> anyhow::Result<()> Ok(()) } +fn list_installed_roots() -> Vec { + if let Ok(explicit) = std::env::var("WEFT_APP_STORE") { + return vec![PathBuf::from(explicit)]; + } + let mut roots = Vec::new(); + if let Ok(home) = std::env::var("HOME") { + roots.push( + PathBuf::from(home) + .join(".local") + .join("share") + .join("weft") + .join("apps"), + ); + } + roots.push(PathBuf::from("/usr/share/weft/apps")); + roots +} + +fn list_installed() { + let mut seen = std::collections::HashSet::new(); + let mut count = 0usize; + for root in list_installed_roots() { + let Ok(entries) = std::fs::read_dir(&root) else { + continue; + }; + let mut pkgs: Vec<(String, String, String)> = Vec::new(); + for entry in entries.flatten() { + let manifest_path = entry.path().join("wapp.toml"); + let Ok(contents) = std::fs::read_to_string(&manifest_path) else { + continue; + }; + let Ok(m) = toml::from_str::(&contents) else { + continue; + }; + if seen.insert(m.package.id.clone()) { + pkgs.push((m.package.id, m.package.name, m.package.version)); + } + } + pkgs.sort_by(|a, b| a.0.cmp(&b.0)); + for (id, name, version) in pkgs { + println!("{id} {name} {version}"); + count += 1; + } + } + if count == 0 { + println!("no packages installed"); + } +} + fn copy_dir(src: &Path, dst: &Path) -> anyhow::Result<()> { std::fs::create_dir_all(dst)?; for entry in std::fs::read_dir(src)? {