plugin: distinguish present invalid packages

This commit is contained in:
Keisuke Hirata 2026-06-19 23:08:06 +09:00
parent a5f3b0b554
commit 0142ef1d3f
No known key found for this signature in database

View File

@ -317,6 +317,7 @@ fn snapshot_from_resolution(
.entry(key.clone()) .entry(key.clone())
.or_insert_with(|| ItemBuilder::new(key)); .or_insert_with(|| ItemBuilder::new(key));
builder.discovered = true; builder.discovered = true;
builder.package_present = true;
builder.source = Some(package.identity.source.to_string()); builder.source = Some(package.identity.source.to_string());
builder.package = Some(package.package_label.clone()); builder.package = Some(package.package_label.clone());
builder.package_path = Some(package.package_path.clone()); builder.package_path = Some(package.package_path.clone());
@ -382,11 +383,18 @@ fn snapshot_from_resolution(
{ {
let rendered = DiagnosticSummary::from(diagnostic); let rendered = DiagnosticSummary::from(diagnostic);
if let Some(reference) = diagnostic_reference(diagnostic) { if let Some(reference) = diagnostic_reference(diagnostic) {
builders let builder = builders
.entry(reference.clone()) .entry(reference.clone())
.or_insert_with(|| ItemBuilder::new(reference)) .or_insert_with(|| ItemBuilder::new(reference));
.diagnostics if let (Some(source), Some(package)) = (diagnostic.source, diagnostic.package.as_ref())
.push(rendered); {
builder.package_present = true;
builder.package.get_or_insert_with(|| package.clone());
builder
.package_path
.get_or_insert_with(|| package_path_for_source(&workspace, source, package));
}
builder.diagnostics.push(rendered);
} else if let (Some(source), Some(package)) = } else if let (Some(source), Some(package)) =
(diagnostic.source, diagnostic.package.as_ref()) (diagnostic.source, diagnostic.package.as_ref())
{ {
@ -396,6 +404,7 @@ fn snapshot_from_resolution(
.entry(key.clone()) .entry(key.clone())
.or_insert_with(|| ItemBuilder::new(key)); .or_insert_with(|| ItemBuilder::new(key));
builder.source.get_or_insert_with(|| source.to_string()); builder.source.get_or_insert_with(|| source.to_string());
builder.package_present = true;
builder.package.get_or_insert_with(|| package.clone()); builder.package.get_or_insert_with(|| package.clone());
builder builder
.package_path .package_path
@ -423,6 +432,7 @@ fn snapshot_from_resolution(
fn fill_resolved(builder: &mut ItemBuilder, resolved: &ResolvedPlugin) { fn fill_resolved(builder: &mut ItemBuilder, resolved: &ResolvedPlugin) {
builder.configured = true; builder.configured = true;
builder.discovered = true; builder.discovered = true;
builder.package_present = true;
builder.resolved = true; builder.resolved = true;
builder.source = Some(resolved.identity.source.to_string()); builder.source = Some(resolved.identity.source.to_string());
builder.package = Some(resolved.package_label.clone()); builder.package = Some(resolved.package_label.clone());
@ -653,6 +663,7 @@ struct ItemBuilder {
reference: String, reference: String,
configured: bool, configured: bool,
discovered: bool, discovered: bool,
package_present: bool,
resolved: bool, resolved: bool,
source: Option<String>, source: Option<String>,
package: Option<String>, package: Option<String>,
@ -677,6 +688,7 @@ impl ItemBuilder {
reference, reference,
configured: false, configured: false,
discovered: false, discovered: false,
package_present: false,
resolved: false, resolved: false,
source: None, source: None,
package: None, package: None,
@ -731,7 +743,7 @@ impl ItemBuilder {
} else if self.discovered && !self.configured { } else if self.discovered && !self.configured {
"disabled" "disabled"
} else if self.configured && !self.discovered { } else if self.configured && !self.discovered {
if has_non_missing_diagnostic { if self.package_present || has_non_missing_diagnostic {
"rejected" "rejected"
} else { } else {
"missing" "missing"
@ -1071,6 +1083,77 @@ mod tests {
assert!(show_output.contains("diagnostics:")); assert!(show_output.contains("diagnostics:"));
} }
#[test]
fn configured_present_package_with_missing_manifest_entries_is_rejected_not_missing() {
let dir = tempdir().unwrap();
let workspace = dir.path();
fs::create_dir_all(workspace.join(".yoi/plugins")).unwrap();
write_stored_zip(
&workspace.join(".yoi/plugins/no_manifest.yoi-plugin"),
&[("plugin.wasm", b"not wasm")],
);
let missing_runtime_manifest = plugin_manifest_missing_runtime_entry("missing_runtime");
write_stored_zip(
&workspace.join(".yoi/plugins/missing_runtime.yoi-plugin"),
&[("plugin.toml", missing_runtime_manifest.as_bytes())],
);
let mut config = PluginConfig::default();
config.enabled.push(enablement_without_digest(
"project:no_manifest",
"0.1.0",
&["Echo"],
));
config.enabled.push(enablement_without_digest(
"project:missing_runtime",
"0.1.0",
&["Echo"],
));
let snapshot = inspect_snapshot(workspace, &config);
let no_manifest = select_item(&snapshot, "project:no_manifest").unwrap();
let missing_runtime = select_item(&snapshot, "project:missing_runtime").unwrap();
assert_eq!(no_manifest.status, "rejected");
assert_eq!(missing_runtime.status, "rejected");
assert!(no_manifest.configured);
assert!(!no_manifest.discovered);
assert!(missing_runtime.configured);
assert!(!missing_runtime.discovered);
assert!(
no_manifest
.diagnostics
.iter()
.any(|diagnostic| diagnostic.kind == "missing"
&& diagnostic.message.contains("plugin.toml"))
);
assert!(
missing_runtime
.diagnostics
.iter()
.any(|diagnostic| diagnostic.kind == "missing"
&& diagnostic.message.contains("path not present"))
);
let list_json = serde_json::to_value(&snapshot).unwrap();
assert!(list_json["items"].as_array().unwrap().iter().any(|item| {
item["reference"] == "project:no_manifest"
&& item["status"] == "rejected"
&& item["diagnostics"][0]["kind"] == "missing"
}));
let show_json = serde_json::to_value(missing_runtime).unwrap();
assert_eq!(show_json["status"], "rejected");
assert_eq!(show_json["diagnostics"][0]["kind"], "missing");
let list_output = render_list_snapshot_human(&snapshot).unwrap();
assert!(list_output.contains("project:no_manifest [rejected]"));
assert!(list_output.contains("project:missing_runtime [rejected]"));
assert!(!list_output.contains("project:no_manifest [missing]"));
assert!(!list_output.contains("project:missing_runtime [missing]"));
let show_output = render_item_human(no_manifest).unwrap();
assert!(show_output.contains("status: rejected"));
assert!(show_output.contains("plugin.toml"));
}
#[test] #[test]
fn invalid_tool_schema_and_name_are_rejected_in_json_and_human_output() { fn invalid_tool_schema_and_name_are_rejected_in_json_and_human_output() {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
@ -1248,6 +1331,29 @@ mod tests {
plugin_manifest_with_schema_and_tool(id, tool_name, "object", &[tool_name], schema_version) plugin_manifest_with_schema_and_tool(id, tool_name, "object", &[tool_name], schema_version)
} }
fn plugin_manifest_missing_runtime_entry(id: &str) -> String {
format!(
r#"
schema_version = 1
id = "{id}"
name = "{id}"
version = "0.1.0"
surfaces = ["tool"]
permissions = [{{ kind = "surface", surface = "tool" }}, {{ kind = "tool", name = "Echo" }}]
[runtime]
kind = "wasm"
entry = "missing.wasm"
abi = "yoi-plugin-wasm-1"
[[tools]]
name = "Echo"
description = "Test tool"
input_schema = {{ type = "object" }}
"#
)
}
fn plugin_manifest_with_schema_and_tool( fn plugin_manifest_with_schema_and_tool(
id: &str, id: &str,
tool_name: &str, tool_name: &str,