1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use anyhow::{Context, Result};
/// Plugin discovery and delegation
///
/// Enables `wj <plugin> <args>` to delegate to `wj-<plugin>` binaries.
/// Plugins are external binaries, not part of core compiler.
use std::path::PathBuf;
use std::process::Command;
/// Execute a plugin
pub fn execute_plugin(plugin_name: &str, args: &[String]) -> Result<i32> {
let binary_name = format!("wj-{}", plugin_name);
// Search order (TDD: support multiple plugin locations):
// 1. ./wj-plugins/wj-<plugin>/target/release/wj-<plugin> (standard)
// 2. ./wj-<plugin>/target/release/wj-<plugin> (alternate, e.g. windjammer-game repo)
// 3. wj-<plugin> in $PATH (global)
let locations = vec![
PathBuf::from(".")
.join("wj-plugins")
.join(&binary_name)
.join("target/release")
.join(&binary_name),
PathBuf::from(".")
.join(&binary_name)
.join("target/release")
.join(&binary_name),
PathBuf::from("..")
.join("windjammer-game")
.join("wj-plugins")
.join(&binary_name)
.join("target/release")
.join(&binary_name),
];
for local_plugin in &locations {
if local_plugin.exists() {
// Execute local plugin
let status = Command::new(local_plugin)
.args(args)
.status()
.with_context(|| {
format!("Failed to execute local plugin: {}", local_plugin.display())
})?;
return Ok(status.code().unwrap_or(1));
}
}
// Try $PATH as fallback
let status = Command::new(&binary_name)
.args(args)
.status()
.with_context(|| {
format!(
"Plugin '{}' not found!\n\
Tried:\n\
1. Local (standard): {}\n\
2. Local (alternate): {}\n\
3. Global: {} in $PATH\n\
\n\
Install with:\n\
cargo build --release --manifest-path wj-plugins/{}/Cargo.toml",
plugin_name,
locations[0].display(),
locations[1].display(),
binary_name,
binary_name
)
})?;
Ok(status.code().unwrap_or(1))
}