aboutsummaryrefslogtreecommitdiff
path: root/src/app/api/plugins
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-13 23:37:26 +0300
committerGitHub <noreply@github.com>2026-09-13 23:37:26 +0300
commit6075d25f470a1145aa8240e64868cdfebc1dc348 (patch)
treeceea1c35f741fb810b6919127b9224546261943e /src/app/api/plugins
parentb847e6cdb6aa6ee113cc026dcbe1fe966a0c750f (diff)
fix: keep plugins working after client-only updates (#4077)
Diffstat (limited to 'src/app/api/plugins')
-rw-r--r--src/app/api/plugins/mod.rs106
-rw-r--r--src/app/api/plugins/panes.rs2
-rw-r--r--src/app/api/plugins/runtime.rs2
3 files changed, 108 insertions, 2 deletions
diff --git a/src/app/api/plugins/mod.rs b/src/app/api/plugins/mod.rs
index cf6327b6..35dbee2f 100644
--- a/src/app/api/plugins/mod.rs
+++ b/src/app/api/plugins/mod.rs
@@ -1738,6 +1738,112 @@ command = ["cmd.exe", "/d", "/c", "slot.cmd", "default"]
}
}
+ #[cfg(target_os = "linux")]
+ #[tokio::test]
+ async fn plugin_launch_survives_executable_replacement() {
+ const CHILD_ROOT: &str = "HERDR_TEST_PLUGIN_REPLACEMENT_ROOT";
+ if let Some(root) = std::env::var_os(CHILD_ROOT) {
+ let root = std::path::PathBuf::from(root);
+ let executable = std::env::current_exe().unwrap();
+ let replacement = root.join("replacement");
+ std::fs::copy(&executable, &replacement).unwrap();
+ std::fs::rename(&replacement, &executable).unwrap();
+ assert!(!std::env::current_exe().unwrap().exists());
+
+ let mut app = test_app();
+ app.state.workspaces = vec![crate::workspace::Workspace::test_new("plugin-update")];
+ app.state.ensure_test_terminals();
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.mode = crate::app::Mode::Terminal;
+ let plugin_root = root.join("plugin");
+ write_manifest_content(
+ &plugin_root,
+ r#"
+id = "example.update"
+name = "Update Probe"
+version = "0.1.0"
+min_herdr_version = "0.6.10"
+platforms = ["linux"]
+
+[[actions]]
+id = "probe"
+title = "Probe executable"
+command = ["sh", "-c", '"$HERDR_BIN_PATH" --list >/dev/null; printf "%s\n" "$?" > action-status']
+
+[[panes]]
+id = "probe"
+title = "Probe executable"
+command = ["sh", "-c", '"$HERDR_BIN_PATH" --list >/dev/null; printf "%s\n" "$?" > pane-status']
+"#,
+ );
+ link_manifest(&mut app, &plugin_root);
+ app.invoke_plugin_action_from_keybind("example.update.probe".into(), None)
+ .unwrap();
+ let action_status = read_capture_when_ready(&plugin_root.join("action-status"), || {
+ app.drain_all_internal_events();
+ });
+ let open = app.handle_api_request(Request {
+ id: "update-pane".into(),
+ method: Method::PluginPaneOpen(PluginPaneOpenParams {
+ plugin_id: "example.update".into(),
+ entrypoint: "probe".into(),
+ placement: Some(PluginPanePlacement::Overlay),
+ width: None,
+ height: None,
+ workspace_id: None,
+ target_pane_id: None,
+ direction: None,
+ cwd: None,
+ focus: true,
+ env: std::collections::HashMap::new(),
+ }),
+ });
+ assert!(matches!(
+ response_result(&open),
+ ResponseResult::PluginPaneOpened { .. }
+ ));
+ let pane_status = read_capture_when_ready(&plugin_root.join("pane-status"), || {});
+ for (_, runtime) in app.terminal_runtimes.drain() {
+ runtime.shutdown();
+ }
+ assert_eq!(
+ (action_status.trim(), pane_status.trim()),
+ ("0", "0"),
+ "plugin action and pane must launch Herdr after its executable is replaced"
+ );
+ return;
+ }
+
+ let root = std::path::PathBuf::from("/var/tmp").join(format!(
+ "herdr-plugin-update-{}-{}",
+ std::process::id(),
+ SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_nanos()
+ ));
+ std::fs::create_dir_all(&root).unwrap();
+ let executable = root.join("herdr test");
+ std::fs::copy(std::env::current_exe().unwrap(), &executable).unwrap();
+ let result = std::process::Command::new(&executable)
+ .args([
+ "--exact",
+ "app::api::plugins::tests::plugin_launch_survives_executable_replacement",
+ "--nocapture",
+ ])
+ .env(CHILD_ROOT, &root)
+ .output();
+ std::fs::remove_dir_all(&root).unwrap();
+ let output = result.unwrap();
+ assert!(
+ output.status.success(),
+ "{}\n{}",
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr)
+ );
+ }
+
#[cfg(unix)]
#[tokio::test]
async fn plugin_pane_open_uses_plugin_root_title_env_and_target_context() {
diff --git a/src/app/api/plugins/panes.rs b/src/app/api/plugins/panes.rs
index 8553ce91..05791886 100644
--- a/src/app/api/plugins/panes.rs
+++ b/src/app/api/plugins/panes.rs
@@ -260,7 +260,7 @@ impl App {
entrypoint.to_string(),
));
env.push(("HERDR_PLUGIN_CONTEXT_JSON".to_string(), context_json));
- if let Ok(current_exe) = std::env::current_exe() {
+ if let Ok(current_exe) = crate::platform::launch_executable() {
env.push((
"HERDR_BIN_PATH".to_string(),
current_exe.display().to_string(),
diff --git a/src/app/api/plugins/runtime.rs b/src/app/api/plugins/runtime.rs
index b956fc98..30e2f7ae 100644
--- a/src/app/api/plugins/runtime.rs
+++ b/src/app/api/plugins/runtime.rs
@@ -46,7 +46,7 @@ impl App {
("HERDR_PLUGIN_ID".to_string(), plugin.plugin_id.clone()),
("HERDR_PLUGIN_CONTEXT_JSON".to_string(), context_json),
]);
- if let Ok(current_exe) = std::env::current_exe() {
+ if let Ok(current_exe) = crate::platform::launch_executable() {
env.push((
"HERDR_BIN_PATH".to_string(),
current_exe.display().to_string(),