use std::path::{Path, PathBuf};
use super::content::AddonContent;
use super::manifest::PackageManifest;
use super::verify;
pub(crate) fn addons_dir(store_root: &Path, name: &str, version: &str) -> PathBuf {
let safe_name = name.replace('/', "__");
store_root.join("addons").join(safe_name).join(version)
}
pub fn store_root() -> Result<PathBuf, String> {
Ok(crate::core::data_dir::lean_ctx_data_dir()?.join(super::registry::PACKAGES_DIR))
}
pub fn addons_root(store_root: &Path) -> PathBuf {
store_root.join("addons")
}
pub(crate) fn materialize_modules(
store_root: &Path,
manifest: &PackageManifest,
addon: &AddonContent,
) -> Result<PathBuf, String> {
let version_root = addons_dir(store_root, &manifest.name, &manifest.version);
if version_root.exists() {
std::fs::remove_dir_all(&version_root).map_err(|e| e.to_string())?;
}
std::fs::create_dir_all(&version_root)
.map_err(|e| format!("create {}: {e}", version_root.display()))?;
for blob in &addon.modules {
verify::validate_document_path(&blob.path)?;
let plain = blob.decode_verified()?;
let dest = version_root.join(&blob.path);
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
}
std::fs::write(&dest, &plain).map_err(|e| format!("write {}: {e}", dest.display()))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&dest, std::fs::Permissions::from_mode(0o444));
}
}
let manifest_dest = version_root.join("lean-ctx-addon.toml");
std::fs::write(&manifest_dest, addon.manifest_toml.as_bytes())
.map_err(|e| format!("write {}: {e}", manifest_dest.display()))?;
Ok(version_root)
}
#[cfg(any(feature = "wasm", test))]
pub fn installed_modules(store_root: &Path) -> Vec<PathBuf> {
let root = addons_root(store_root);
let Ok(addon_dirs) = std::fs::read_dir(&root) else {
return Vec::new();
};
let mut out = Vec::new();
for addon_dir in addon_dirs.flatten().filter(|e| e.path().is_dir()) {
let Ok(versions) = std::fs::read_dir(addon_dir.path()) else {
continue;
};
let newest = versions
.flatten()
.filter(|e| e.path().is_dir())
.max_by_key(|e| {
e.metadata()
.and_then(|m| m.modified())
.unwrap_or(std::time::UNIX_EPOCH)
});
if let Some(version_dir) = newest {
collect_modules(&version_dir.path(), &mut out, 0);
}
}
out.sort();
out
}
#[cfg(any(feature = "wasm", test))]
fn collect_modules(dir: &Path, out: &mut Vec<PathBuf>, depth: usize) {
if depth > 6 {
return;
}
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_modules(&path, out, depth + 1);
} else if path.extension().and_then(|e| e.to_str()) == Some("wasm") {
out.push(path);
}
}
}
#[cfg(test)]
mod tests {
use super::super::content::DocumentBlob;
use super::*;
fn manifest(name: &str, version: &str) -> PackageManifest {
use super::super::manifest::{
CompatibilitySpec, PackageIntegrity, PackageKind, PackageProvenance, PackageStats,
};
PackageManifest {
schema_version: crate::core::contracts::CONTEXT_PACKAGE_V2_SCHEMA_VERSION,
conformance_level: None,
kind: PackageKind::Addon,
name: name.to_string(),
version: version.to_string(),
description: "test addon".to_string(),
author: None,
scope: name
.starts_with('@')
.then(|| name.split('/').next().unwrap_or_default().to_string()),
created_at: chrono::Utc::now(),
updated_at: None,
layers: Vec::new(),
dependencies: Vec::new(),
tags: Vec::new(),
visibility: None,
integrity: PackageIntegrity {
sha256: "0".repeat(64),
content_hash: "0".repeat(64),
byte_size: 0,
},
provenance: PackageProvenance {
tool: "lean-ctx".into(),
tool_version: env!("CARGO_PKG_VERSION").into(),
project_hash: None,
source_session_id: None,
},
compatibility: CompatibilitySpec::default(),
stats: PackageStats::default(),
signature: None,
graph_summary: None,
marketplace: None,
redistributable: None,
}
}
fn empty_wasm() -> Vec<u8> {
vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]
}
#[test]
fn materializes_modules_and_the_authoring_manifest() {
let tmp = tempfile::tempdir().unwrap();
let addon = AddonContent {
manifest_toml: "[addon]\nname = \"demo\"\n".to_string(),
modules: vec![DocumentBlob::from_plaintext("demo.wasm", &empty_wasm()).unwrap()],
};
let root = materialize_modules(tmp.path(), &manifest("@ns/demo", "1.0.0"), &addon).unwrap();
assert!(root.join("demo.wasm").is_file());
assert_eq!(
std::fs::read(root.join("demo.wasm")).unwrap(),
empty_wasm(),
"the stored bytes must be the verified plaintext"
);
assert!(
root.join("lean-ctx-addon.toml").is_file(),
"the authoring manifest travels with the modules"
);
assert!(
root.to_string_lossy().contains("@ns__demo"),
"scoped names are flattened like every other pack kind: {root:?}"
);
}
#[test]
fn an_addon_without_modules_still_materializes_its_manifest() {
let tmp = tempfile::tempdir().unwrap();
let addon = AddonContent {
manifest_toml: "[addon]\nname = \"@ns/server-only\"\n[mcp]\ncommand = \"srv\"\n"
.to_string(),
modules: Vec::new(),
};
let root =
materialize_modules(tmp.path(), &manifest("@ns/server-only", "1.0.0"), &addon).unwrap();
assert!(root.is_dir(), "the version directory exists");
assert!(
root.join("lean-ctx-addon.toml").is_file(),
"the authoring manifest was written"
);
}
#[test]
fn reinstall_drops_modules_the_new_version_no_longer_ships() {
let tmp = tempfile::tempdir().unwrap();
let m = manifest("@ns/demo", "1.0.0");
let two = AddonContent {
manifest_toml: "[addon]\n".into(),
modules: vec![
DocumentBlob::from_plaintext("a.wasm", &empty_wasm()).unwrap(),
DocumentBlob::from_plaintext("b.wasm", &empty_wasm()).unwrap(),
],
};
materialize_modules(tmp.path(), &m, &two).unwrap();
assert_eq!(installed_modules(tmp.path()).len(), 2);
let one = AddonContent {
manifest_toml: "[addon]\n".into(),
modules: vec![DocumentBlob::from_plaintext("a.wasm", &empty_wasm()).unwrap()],
};
let root = materialize_modules(tmp.path(), &m, &one).unwrap();
assert!(!root.join("b.wasm").exists(), "b.wasm must be gone");
assert_eq!(installed_modules(tmp.path()).len(), 1);
}
#[test]
fn discovery_is_sorted_and_ignores_non_modules() {
let tmp = tempfile::tempdir().unwrap();
let addon = AddonContent {
manifest_toml: "[addon]\n".into(),
modules: vec![
DocumentBlob::from_plaintext("z.wasm", &empty_wasm()).unwrap(),
DocumentBlob::from_plaintext("a.wasm", &empty_wasm()).unwrap(),
],
};
materialize_modules(tmp.path(), &manifest("@ns/demo", "1.0.0"), &addon).unwrap();
let found = installed_modules(tmp.path());
assert_eq!(found.len(), 2, "the .toml must not be picked up: {found:?}");
let names: Vec<String> = found
.iter()
.map(|p| p.file_name().unwrap().to_string_lossy().into_owned())
.collect();
assert_eq!(
names,
vec!["a.wasm", "z.wasm"],
"registration order must be deterministic (#498)"
);
}
#[test]
fn only_the_most_recently_installed_version_is_loaded() {
let tmp = tempfile::tempdir().unwrap();
let addon = |body: &[u8]| AddonContent {
manifest_toml: "[addon]\nname = \"@ns/demo\"\n".into(),
modules: vec![DocumentBlob::from_plaintext("demo.wasm", body).unwrap()],
};
let old = empty_wasm();
let mut new = empty_wasm();
new.extend_from_slice(&[0x00; 4]);
materialize_modules(tmp.path(), &manifest("@ns/demo", "9.0.0"), &addon(&old)).unwrap();
std::thread::sleep(std::time::Duration::from_millis(20));
materialize_modules(tmp.path(), &manifest("@ns/demo", "10.0.0"), &addon(&new)).unwrap();
let found = installed_modules(tmp.path());
assert_eq!(found.len(), 1, "one version's modules, not both: {found:?}");
assert_eq!(
std::fs::read(&found[0]).unwrap(),
new,
"the upgrade must win, not the version that sorts first"
);
assert!(
found[0].to_string_lossy().contains("10.0.0"),
"{:?}",
found[0]
);
}
#[test]
fn an_empty_store_yields_no_modules() {
let tmp = tempfile::tempdir().unwrap();
{
let found = &installed_modules(tmp.path());
assert!(found.is_empty(), "{found:?}");
}
}
}