From d1d02547fd869712cfad5099b3d12dc10de5e0ab Mon Sep 17 00:00:00 2001 From: Jinpy Date: Sat, 26 Sep 2026 13:49:16 +0800 Subject: [PATCH] fix(plugins): list command and menu contributions in manifest schema --- .../src/plugins/manifest.rs | 46 +++++++++++++++ plugins/manifest.schema.json | 58 ++++++++++++++++++- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/crates/dbx-plugin-runtime/src/plugins/manifest.rs b/crates/dbx-plugin-runtime/src/plugins/manifest.rs index e192eb45af..d691397da6 100644 --- a/crates/dbx-plugin-runtime/src/plugins/manifest.rs +++ b/crates/dbx-plugin-runtime/src/plugins/manifest.rs @@ -2392,6 +2392,52 @@ mod tests { Ok(()) } + /// The runtime `PluginContribution` enum is the source of truth for + /// contribution `type` tags; the published schema's `contributions.oneOf` + /// must accept every one of them, or schema-validating tooling (CI, + /// editors) rejects manifests the host itself accepts. Adding a runtime + /// variant without extending the schema fails here. + #[test] + fn manifest_schema_lists_every_runtime_contribution_kind() { + let schema_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join("plugins") + .join("manifest.schema.json"); + let schema: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(&schema_path).unwrap()).unwrap(); + let referenced: Vec = schema["properties"]["contributions"]["items"]["oneOf"] + .as_array() + .expect("contributions.items.oneOf must be an array") + .iter() + .map(|entry| { + entry["$ref"] + .as_str() + .expect("every oneOf entry must be a $ref") + .trim_start_matches("#/$defs/") + .to_string() + }) + .collect(); + + // Mirrors the PluginContribution variants and their kebab-case serde + // tags; the schema def names append "Contribution". + let kinds = [ + ("connection-provider", "connectionProviderContribution"), + ("workbench", "workbenchContribution"), + ("filesystem-provider", "filesystemProviderContribution"), + ("context-menu", "contextMenuContribution"), + ("result-view", "resultViewContribution"), + ("command", "commandContribution"), + ("menus", "menusContribution"), + ]; + assert_eq!(referenced, kinds.iter().map(|(_, def)| def.to_string()).collect::>()); + for (tag, def) in kinds { + assert_eq!( + schema["$defs"][def]["properties"]["type"]["const"], tag, + "schema def {def} must pin the {tag:?} contribution type tag" + ); + } + } + #[test] fn manifest_schema_context_menu_enum_matches_runtime_targets() { let schema_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) diff --git a/plugins/manifest.schema.json b/plugins/manifest.schema.json index e2622efc91..90a7bf1245 100644 --- a/plugins/manifest.schema.json +++ b/plugins/manifest.schema.json @@ -46,7 +46,7 @@ "contributions": { "type": "array", "items": { - "oneOf": [{ "$ref": "#/$defs/connectionProviderContribution" }, { "$ref": "#/$defs/workbenchContribution" }, { "$ref": "#/$defs/filesystemProviderContribution" }, { "$ref": "#/$defs/contextMenuContribution" }, { "$ref": "#/$defs/resultViewContribution" }] + "oneOf": [{ "$ref": "#/$defs/connectionProviderContribution" }, { "$ref": "#/$defs/workbenchContribution" }, { "$ref": "#/$defs/filesystemProviderContribution" }, { "$ref": "#/$defs/contextMenuContribution" }, { "$ref": "#/$defs/resultViewContribution" }, { "$ref": "#/$defs/commandContribution" }, { "$ref": "#/$defs/menusContribution" }] } }, "localizations": { @@ -368,6 +368,62 @@ "items": { "enum": ["read", "write", "delete", "rename", "mkdir"] } } } + }, + "commandContribution": { + "type": "object", + "additionalProperties": false, + "required": ["type", "id", "label", "action"], + "properties": { + "type": { "const": "command" }, + "id": { "$ref": "#/$defs/identifier" }, + "label": { "type": "string", "minLength": 1 }, + "description": { "type": "string" }, + "icon": { "$ref": "#/$defs/assetPath" }, + "action": { "$ref": "#/$defs/commandAction" }, + "enablement": { "$ref": "#/$defs/commandEnablement" } + } + }, + "commandAction": { + "oneOf": [{ "$ref": "#/$defs/openWorkbenchAction" }] + }, + "openWorkbenchAction": { + "type": "object", + "required": ["type", "workbench"], + "properties": { + "type": { "const": "open-workbench" }, + "workbench": { "$ref": "#/$defs/identifier" }, + "presentation": { "enum": ["tab", "panel"] }, + "context": { "type": "object" } + } + }, + "menusContribution": { + "type": "object", + "additionalProperties": false, + "required": ["type", "id"], + "properties": { + "type": { "const": "menus" }, + "id": { "$ref": "#/$defs/identifier" }, + "items": { "type": "array", "items": { "$ref": "#/$defs/menuItem" } } + } + }, + "menuItem": { + "type": "object", + "additionalProperties": false, + "required": ["location", "command", "group", "order"], + "properties": { + "location": { "enum": ["commandPalette", "appToolbar", "appSidebar"] }, + "command": { "type": "string", "minLength": 1 }, + "group": { "type": "string" }, + "order": { "type": "integer" }, + "default_visible": { "type": "boolean" }, + "when": { "$ref": "#/$defs/commandEnablement" } + } + }, + "commandEnablement": { + "type": "object", + "properties": { + "all": { "type": "array", "items": { "type": "object" } } + } } } }