aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-15 00:49:58 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-15 00:49:58 +0300
commitc91348e68b9e438159ffecc3e26bc6f971e0fa2a (patch)
tree30d2abbe5c001d044e66436effa5470783571d77 /src
parent3015ce0f9d120df7583f838d1a47cb469fe5a727 (diff)
fix: require plugin min herdr version
Diffstat (limited to 'src')
-rw-r--r--src/api/schema/plugins.rs2
-rw-r--r--src/api/schema/tests.rs1
-rw-r--r--src/app/api/plugins/manifest.rs35
-rw-r--r--src/app/api/plugins/mod.rs74
-rw-r--r--src/cli/plugin.rs1
-rw-r--r--src/persist/plugin_registry.rs2
6 files changed, 115 insertions, 0 deletions
diff --git a/src/api/schema/plugins.rs b/src/api/schema/plugins.rs
index 907f243b..c809be58 100644
--- a/src/api/schema/plugins.rs
+++ b/src/api/schema/plugins.rs
@@ -37,6 +37,8 @@ pub struct InstalledPluginInfo {
pub plugin_id: String,
pub name: String,
pub version: String,
+ #[serde(default)]
+ pub min_herdr_version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub manifest_path: String,
diff --git a/src/api/schema/tests.rs b/src/api/schema/tests.rs
index 05ac9291..3a3684e5 100644
--- a/src/api/schema/tests.rs
+++ b/src/api/schema/tests.rs
@@ -566,6 +566,7 @@ fn plugin_link_list_unlink_round_trip() {
plugin_id: "example.worktree-bootstrap".into(),
name: "Worktree Bootstrap".into(),
version: "0.1.0".into(),
+ min_herdr_version: crate::build_info::BASE_VERSION.into(),
description: Some("Prepare new worktrees".into()),
manifest_path: "/plugins/worktree-bootstrap/herdr-plugin.toml".into(),
plugin_root: "/plugins/worktree-bootstrap".into(),
diff --git a/src/app/api/plugins/manifest.rs b/src/app/api/plugins/manifest.rs
index 92799aae..92ab4754 100644
--- a/src/app/api/plugins/manifest.rs
+++ b/src/app/api/plugins/manifest.rs
@@ -13,6 +13,8 @@ struct RawPluginManifest {
name: String,
version: String,
#[serde(default)]
+ min_herdr_version: Option<String>,
+ #[serde(default)]
description: Option<String>,
#[serde(default)]
platforms: Option<Vec<RawPlatform>>,
@@ -133,6 +135,7 @@ pub(crate) fn load_plugin_manifest(
"invalid_plugin_version",
"plugin version is required",
)?;
+ let min_herdr_version = validate_min_herdr_version(raw.min_herdr_version.as_deref())?;
let description = raw
.description
.map(|description| description.trim().to_string())
@@ -180,6 +183,7 @@ pub(crate) fn load_plugin_manifest(
plugin_id,
name,
version,
+ min_herdr_version,
description,
manifest_path: manifest_path.display().to_string(),
plugin_root: plugin_root.display().to_string(),
@@ -195,6 +199,37 @@ pub(crate) fn load_plugin_manifest(
})
}
+fn validate_min_herdr_version(value: Option<&str>) -> Result<String, (&'static str, String)> {
+ let Some(value) = value else {
+ return Err((
+ "invalid_plugin_min_herdr_version",
+ "plugin min_herdr_version is required".to_string(),
+ ));
+ };
+ let value = non_empty_trimmed(
+ value,
+ "invalid_plugin_min_herdr_version",
+ "plugin min_herdr_version is required",
+ )?;
+ let required = crate::update::Version::parse(&value).ok_or_else(|| {
+ (
+ "invalid_plugin_min_herdr_version",
+ format!(
+ "plugin min_herdr_version must be a semantic version like {}",
+ crate::build_info::BASE_VERSION
+ ),
+ )
+ })?;
+ let current = crate::update::Version::current();
+ if required > current {
+ return Err((
+ "plugin_requires_newer_herdr",
+ format!("plugin requires Herdr {required} or newer; current Herdr is {current}"),
+ ));
+ }
+ Ok(required.to_string())
+}
+
fn normalize_manifest_build(
build: RawPluginManifestBuild,
) -> Result<PluginManifestBuild, (&'static str, String)> {
diff --git a/src/app/api/plugins/mod.rs b/src/app/api/plugins/mod.rs
index 89f7a23d..1938589c 100644
--- a/src/app/api/plugins/mod.rs
+++ b/src/app/api/plugins/mod.rs
@@ -704,6 +704,7 @@ mod tests {
id = "example.worktree-bootstrap"
name = "Worktree Bootstrap"
version = "0.1.0"
+min_herdr_version = "0.6.10"
description = "Prepare new worktrees"
platforms = ["linux", "macos", "windows"]
@@ -772,6 +773,7 @@ action = "bootstrap"
id = "example.config-dirs"
name = "Config Dirs"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos", "windows"]
"#,
);
@@ -806,6 +808,7 @@ platforms = ["linux", "macos", "windows"]
id = "example.legacy-config"
name = "Legacy Config"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos", "windows"]
"#,
);
@@ -925,6 +928,56 @@ platforms = ["linux", "macos", "windows"]
}
#[test]
+ fn link_rejects_invalid_min_herdr_versions() {
+ let cases = [
+ (
+ "plugin-missing-min-herdr",
+ r#"
+id = "example.missing-min-herdr"
+name = "Missing Min Herdr"
+version = "0.1.0"
+platforms = ["linux", "macos", "windows"]
+"#,
+ "invalid_plugin_min_herdr_version",
+ ),
+ (
+ "plugin-invalid-min-herdr",
+ r#"
+id = "example.invalid-min-herdr"
+name = "Invalid Min Herdr"
+version = "0.1.0"
+min_herdr_version = "soon"
+platforms = ["linux", "macos", "windows"]
+"#,
+ "invalid_plugin_min_herdr_version",
+ ),
+ (
+ "plugin-future-min-herdr",
+ r#"
+id = "example.future-min-herdr"
+name = "Future Min Herdr"
+version = "0.1.0"
+min_herdr_version = "999.0.0"
+platforms = ["linux", "macos", "windows"]
+"#,
+ "plugin_requires_newer_herdr",
+ ),
+ ];
+
+ for (name, manifest, expected_code) in cases {
+ let root = unique_temp_path(name);
+ write_manifest_content(&root, manifest);
+
+ let result = load_plugin_manifest(&root.display().to_string(), true);
+ assert!(
+ matches!(result, Err((code, _)) if code == expected_code),
+ "{name}: expected {expected_code}, got {result:?}"
+ );
+ let _ = std::fs::remove_dir_all(root);
+ }
+ }
+
+ #[test]
fn link_rejects_duplicate_action_ids() {
let root = unique_temp_path("plugin-duplicate-action");
write_manifest_content(
@@ -933,6 +986,7 @@ platforms = ["linux", "macos", "windows"]
id = "example.duplicate"
name = "Duplicate"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos", "windows"]
[[actions]]
@@ -961,6 +1015,7 @@ command = ["echo", "b"]
id = "example.dotted-action"
name = "Dotted Action"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos", "windows"]
[[actions]]
@@ -984,6 +1039,7 @@ command = ["echo", "build"]
id = "example.duplicate-pane"
name = "Duplicate Pane"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos", "windows"]
[[panes]]
@@ -1099,6 +1155,7 @@ command = ["echo", "b"]
id = "example.pane"
name = "Pane Plugin"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos"]
[[panes]]
@@ -1205,6 +1262,7 @@ command = ["sh", "-c", "printf '%s\n%s\n%s\n%s\n%s\n%s\n%s\n' \"$PWD\" \"$HERDR_
id = "example.path-env"
name = "Path Env"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos"]
[[panes]]
@@ -1310,6 +1368,7 @@ command = ["sh", "-c", "printf '%s\n%s\n%s\n' \"$HERDR_PLUGIN_ROOT\" \"$HERDR_PL
id = "example.tab"
name = "Tab Plugin"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos"]
[[panes]]
@@ -1514,6 +1573,7 @@ command = ["sh", "-c", "sleep 1"]
id = "example.runner"
name = "Runner"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos"]
[[actions]]
@@ -1580,6 +1640,7 @@ command = ["sh", "-c", "printf '%s' \"$HERDR_PLUGIN_ACTION_ID\""]
id = "example.action-paths"
name = "Action Paths"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos"]
[[actions]]
@@ -1702,6 +1763,7 @@ command = ["sh", "-c", "printf '%s\n%s\n%s' \"$HERDR_PLUGIN_ROOT\" \"$HERDR_PLUG
id = "example.event-context"
name = "Event Context"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos"]
[[events]]
@@ -1851,6 +1913,7 @@ command = ["sh", "-c", "printf '%s' \"$HERDR_PLUGIN_CONTEXT_JSON\" > {}"]
id = "example.links"
name = "Links"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos"]
[[actions]]
@@ -1918,6 +1981,7 @@ action = "open"
id = "example.link-order"
name = "Link Order"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos", "windows"]
[[actions]]
@@ -1964,6 +2028,7 @@ action = "generic"
id = "example.bad-links"
name = "Bad Links"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos", "windows"]
[[actions]]
@@ -2006,6 +2071,7 @@ action = "open"
id = "example.bad-link-action"
name = "Bad Link Action"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos", "windows"]
[[actions]]
@@ -2079,6 +2145,7 @@ action = "missing"
id = "example.context"
name = "Context"
version = "0.1.0"
+min_herdr_version = "0.6.10"
[[actions]]
id = "show"
@@ -2175,6 +2242,7 @@ command = ["show-ctx"]
id = "example.bad-event"
name = "Bad Event Plugin"
version = "0.1.0"
+min_herdr_version = "0.6.10"
[[events]]
on = "worktree.craeted"
@@ -2458,6 +2526,7 @@ command = ["sh", "-c", "echo ok"]
id = "example.platforms"
name = "Platforms"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos"]
[[actions]]
@@ -2540,6 +2609,7 @@ command = ["run.bat"]
id = "example.reject"
name = "Reject"
version = "0.1.0"
+min_herdr_version = "0.6.10"
{excluded_platforms}
[[actions]]
@@ -2604,6 +2674,7 @@ command = ["act"]
id = "example.override"
name = "Override"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos", "windows"]
[[actions]]
@@ -2654,6 +2725,7 @@ command = ["act"]
id = "example.nodecl"
name = "No Decl"
version = "0.1.0"
+min_herdr_version = "0.6.10"
[[actions]]
id = "act"
@@ -2711,6 +2783,7 @@ command = ["act"]
id = "example.badplatform"
name = "Bad Platform"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "beos"]
[[actions]]
@@ -2742,6 +2815,7 @@ command = ["act"]
id = "example.platform-rt"
name = "Platform RT"
version = "0.1.0"
+min_herdr_version = "0.6.10"
platforms = ["linux", "macos"]
[[actions]]
diff --git a/src/cli/plugin.rs b/src/cli/plugin.rs
index ffdcd711..261540c7 100644
--- a/src/cli/plugin.rs
+++ b/src/cli/plugin.rs
@@ -1631,6 +1631,7 @@ mod tests {
plugin_id: id.to_string(),
name: "Test Plugin".to_string(),
version: "0.1.0".to_string(),
+ min_herdr_version: crate::build_info::BASE_VERSION.to_string(),
description: None,
manifest_path: format!("/tmp/{id}/herdr-plugin.toml"),
plugin_root: format!("/tmp/{id}"),
diff --git a/src/persist/plugin_registry.rs b/src/persist/plugin_registry.rs
index b2eaac9f..34796a61 100644
--- a/src/persist/plugin_registry.rs
+++ b/src/persist/plugin_registry.rs
@@ -116,6 +116,7 @@ mod tests {
plugin_id: id.to_string(),
name: "Test Plugin".to_string(),
version: "0.1.0".to_string(),
+ min_herdr_version: crate::build_info::BASE_VERSION.to_string(),
description: None,
manifest_path: format!("/tmp/{id}/herdr-plugin.toml"),
plugin_root: format!("/tmp/{id}"),
@@ -199,6 +200,7 @@ mod tests {
plugin_id: "example.reload".to_string(),
name: "Fresh Name".to_string(),
version: "0.2.0".to_string(),
+ min_herdr_version: crate::build_info::BASE_VERSION.to_string(),
description: Some("refreshed".to_string()),
manifest_path: "/tmp/example.reload/herdr-plugin.toml".to_string(),
plugin_root: "/tmp/example.reload".to_string(),