diff --git a/crates/weft-pack/src/main.rs b/crates/weft-pack/src/main.rs
index f3f4923..e76a3a7 100644
--- a/crates/weft-pack/src/main.rs
+++ b/crates/weft-pack/src/main.rs
@@ -47,11 +47,16 @@ fn main() -> anyhow::Result<()> {
let dir = args.get(2).context("usage: weft-pack install
")?;
install_package(Path::new(dir))?;
}
+ Some("uninstall") => {
+ let app_id = args.get(2).context("usage: weft-pack uninstall ")?;
+ uninstall_package(app_id)?;
+ }
_ => {
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 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");
std::process::exit(1);
}
}
@@ -160,13 +165,15 @@ fn resolve_install_root() -> anyhow::Result {
}
fn install_package(dir: &Path) -> anyhow::Result<()> {
+ let root = resolve_install_root()?;
+ install_package_to(dir, &root)
+}
+
+fn install_package_to(dir: &Path, store_root: &Path) -> anyhow::Result<()> {
check_package(dir)?;
let manifest = load_manifest(dir)?;
let app_id = &manifest.package.id;
-
- let store_root = resolve_install_root()?;
let dest = store_root.join(app_id);
-
if dest.exists() {
anyhow::bail!(
"package '{}' is already installed at {}; remove it first",
@@ -174,14 +181,34 @@ fn install_package(dir: &Path) -> anyhow::Result<()> {
dest.display()
);
}
-
copy_dir(dir, &dest)
.with_context(|| format!("copy {} -> {}", dir.display(), dest.display()))?;
-
println!("installed {} -> {}", app_id, dest.display());
Ok(())
}
+fn uninstall_package(app_id: &str) -> anyhow::Result<()> {
+ let root = resolve_install_root()?;
+ uninstall_package_from(app_id, &root)
+}
+
+fn uninstall_package_from(app_id: &str, store_root: &Path) -> anyhow::Result<()> {
+ if !is_valid_app_id(app_id) {
+ anyhow::bail!("'{}' is not a valid app ID", app_id);
+ }
+ let target = store_root.join(app_id);
+ if !target.exists() {
+ anyhow::bail!(
+ "package '{}' is not installed at {}",
+ app_id,
+ target.display()
+ );
+ }
+ std::fs::remove_dir_all(&target).with_context(|| format!("remove {}", target.display()))?;
+ println!("uninstalled {}", app_id);
+ Ok(())
+}
+
fn copy_dir(src: &Path, dst: &Path) -> anyhow::Result<()> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
@@ -260,6 +287,64 @@ entry = "ui/index.html"
let _ = fs::remove_dir_all(&tmp);
}
+ #[test]
+ fn install_package_copies_to_store() {
+ use std::fs;
+ let id = format!("weft.pack.install{}", std::process::id());
+ let src = std::env::temp_dir().join(format!("weft_pack_install_src_{}", id));
+ let store = std::env::temp_dir().join(format!("weft_pack_install_store_{}", id));
+ let ui_dir = src.join("ui");
+ let _ = fs::create_dir_all(&ui_dir);
+ fs::write(src.join("app.wasm"), b"\0asm").unwrap();
+ fs::write(ui_dir.join("index.html"), b"").unwrap();
+ let app_id = format!("com.example.t{}", std::process::id());
+ fs::write(
+ src.join("wapp.toml"),
+ format!(
+ "[package]\nid = \"{app_id}\"\nname = \"Test\"\nversion = \"1.0.0\"\n\n\
+ [runtime]\nmodule = \"app.wasm\"\n\n[ui]\nentry = \"ui/index.html\"\n"
+ ),
+ )
+ .unwrap();
+ let result = install_package_to(&src, &store);
+ assert!(result.is_ok(), "{result:?}");
+ assert!(store.join(&app_id).join("app.wasm").exists());
+ assert!(store.join(&app_id).join("wapp.toml").exists());
+ assert!(store.join(&app_id).join("ui").join("index.html").exists());
+ assert!(install_package_to(&src, &store).is_err());
+ let _ = fs::remove_dir_all(&src);
+ let _ = fs::remove_dir_all(&store);
+ }
+
+ #[test]
+ fn uninstall_package_removes_directory() {
+ use std::fs;
+ let id = format!("weft.pack.uninstall{}", std::process::id());
+ let src = std::env::temp_dir().join(format!("weft_pack_uninstall_src_{}", id));
+ let store = std::env::temp_dir().join(format!("weft_pack_uninstall_store_{}", id));
+ let ui_dir = src.join("ui");
+ let _ = fs::create_dir_all(&ui_dir);
+ fs::write(src.join("app.wasm"), b"\0asm").unwrap();
+ fs::write(ui_dir.join("index.html"), b"").unwrap();
+ let app_id = format!("com.example.u{}", std::process::id());
+ fs::write(
+ src.join("wapp.toml"),
+ format!(
+ "[package]\nid = \"{app_id}\"\nname = \"U\"\nversion = \"1.0.0\"\n\n\
+ [runtime]\nmodule = \"app.wasm\"\n\n[ui]\nentry = \"ui/index.html\"\n"
+ ),
+ )
+ .unwrap();
+ install_package_to(&src, &store).unwrap();
+ assert!(store.join(&app_id).exists());
+ let result = uninstall_package_from(&app_id, &store);
+ assert!(result.is_ok(), "{result:?}");
+ assert!(!store.join(&app_id).exists());
+ assert!(uninstall_package_from(&app_id, &store).is_err());
+ let _ = fs::remove_dir_all(&src);
+ let _ = fs::remove_dir_all(&store);
+ }
+
#[test]
fn check_package_invalid_app_id() {
use std::fs;