-
Notifications
You must be signed in to change notification settings - Fork 0
Plugin Development
LPM supports plugins as separate executables that extend the core functionality. This guide explains how to create and distribute LPM plugins.
Plugins are standalone Rust binaries named lpm-<name> that can be installed globally and are automatically discovered by the main lpm CLI. When you run lpm <plugin-name>, LPM will find and execute the corresponding lpm-<plugin-name> binary.
LPM discovers plugins in two locations:
-
Global installation directory:
~/.lpm/bin/lpm-<name>(when installed vialpm install -g) -
System PATH: Any
lpm-<name>executable found in your PATH
The discovery mechanism is implemented in src/cli/plugin.rs:
pub fn find_plugin(plugin_name: &str) -> Option<PathBuf> {
// Check ~/.lpm/bin/lpm-{name}
if let Ok(lpm_home) = lpm_home() {
let plugin_path = lpm_home.join("bin").join(format!("lpm-{}", plugin_name));
if plugin_path.exists() {
return Some(plugin_path);
}
}
// Check PATH for lpm-{name}
which::which(format!("lpm-{}", plugin_name)).ok()
}Create a new Rust project for your plugin:
cargo new --bin lpm-myplugin
cd lpm-mypluginYour plugin's Cargo.toml should:
- Name the binary
lpm-<name> - Depend on
lpm-corefor shared utilities - Include any plugin-specific dependencies
Example:
[package]
name = "lpm-myplugin"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "lpm-myplugin"
path = "src/main.rs"
[dependencies]
# Core LPM utilities
lpm-core = { path = "../lpm-core" } # Or from crates.io when published
# CLI
clap = { version = "4.4", features = ["derive"] }
# Your plugin's dependencies
# ...Your plugin's main.rs should:
- Use
clapfor CLI argument parsing - Return
Result<(), lpm_core::LpmError> - Handle errors gracefully
Example:
use clap::{Parser, Subcommand};
use lpm_core::LpmError;
#[derive(Parser)]
#[command(name = "lpm-myplugin")]
#[command(about = "Description of your plugin")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Do something
DoSomething {
/// Some option
#[arg(short, long)]
option: Option<String>,
},
}
fn main() -> Result<(), LpmError> {
let cli = Cli::parse();
match cli.command {
Commands::DoSomething { option } => {
// Your plugin logic here
println!("Doing something with {:?}", option);
Ok(())
}
}
}The lpm-core crate provides utilities that plugins can use:
use lpm_core::{LpmError, LpmResult};
fn my_function() -> LpmResult<()> {
// Use LpmError for errors
Err(LpmError::Package("Something went wrong".to_string()))
}use lpm_core::core::path::{find_project_root, lua_modules_dir};
// Find the project root (directory containing package.yaml)
let project_root = find_project_root(&std::env::current_dir()?)?;
// Get the lua_modules directory
let lua_modules = lua_modules_dir(&project_root);use lpm_core::package::manifest::PackageManifest;
// Load package.yaml
let manifest = PackageManifest::load(&project_root)?;
// Access manifest fields
println!("Package: {}", manifest.name);
println!("Version: {}", manifest.version);
println!("Dependencies: {:?}", manifest.dependencies);use lpm_core::path_setup::runner::{LuaRunner, RunOptions};
// Run a Lua script with proper path setup
let options = RunOptions {
cwd: Some(project_root.to_string_lossy().to_string()),
lua_args: vec!["script.lua".to_string()],
env: vec![],
};
let exit_code = LuaRunner::run_script(&script_path, options)?;use lpm_core::path_setup::loader::PathSetup;
// Ensure lpm.loader is installed in the project
PathSetup::install_loader(&project_root)?;Here's a complete minimal plugin example:
use clap::Parser;
use lpm_core::{LpmError, LpmResult};
use lpm_core::core::path::find_project_root;
#[derive(Parser)]
#[command(name = "lpm-hello")]
#[command(about = "A simple hello world plugin")]
struct Cli {
/// Name to greet
#[arg(short, long, default_value = "world")]
name: String,
}
fn main() -> Result<(), LpmError> {
let cli = Cli::parse();
// Find project root
let current_dir = std::env::current_dir()?;
let project_root = find_project_root(¤t_dir)?;
println!("Hello, {}!", cli.name);
println!("Project root: {}", project_root.display());
Ok(())
}Plugins can read configuration from package.yaml by parsing the YAML directly:
use serde_yaml::Value;
use std::fs;
fn load_plugin_config(project_root: &Path) -> LpmResult<Option<Value>> {
let package_yaml_path = project_root.join("package.yaml");
if !package_yaml_path.exists() {
return Ok(None);
}
let content = fs::read_to_string(&package_yaml_path)?;
let yaml: Value = serde_yaml::from_str(&content)
.map_err(|e| LpmError::Package(format!("Failed to parse package.yaml: {}", e)))?;
// Access plugin-specific section
Ok(yaml.get("myplugin").cloned())
}Example package.yaml:
name: my-project
version: 1.0.0
# Plugin-specific configuration
myplugin:
setting1: value1
setting2: value2During development, you can test your plugin by:
-
Building it:
cargo build --release
-
Running it directly:
./target/release/lpm-myplugin
-
Or symlinking it to test discovery:
ln -s $(pwd)/target/release/lpm-myplugin ~/.lpm/bin/lpm-myplugin lpm myplugin # Should now work
Test that your plugin integrates correctly with LPM:
# Build and install globally
cargo build --release
cp target/release/lpm-myplugin ~/.lpm/bin/
# Test discovery
lpm myplugin --help
# Test execution
lpm mypluginWhen lpm-core is published to crates.io, plugins can depend on it:
[dependencies]
lpm-core = "0.1.0" # Version matching LPM releaseUsers can install plugins globally:
# If published as a crate
lpm install -g lpm-myplugin
# Or manually
cargo install lpm-myplugin-
Error Handling: Always return
LpmResult<()>and useLpmErrorfor errors -
Project Root: Use
find_project_root()to locate the LPM project -
Path Setup: Use
LuaRunnerorPathSetupwhen running Lua code -
CLI Design: Follow LPM's CLI conventions (use
clap, clear help text) - Documentation: Document your plugin's usage and configuration
- Testing: Test your plugin in real LPM projects
See lpm-watch for a complete plugin implementation:
- File watching with
notifyandnotify-debouncer-mini - Configuration from
package.yaml - Integration with
lpm-coreutilities - Proper error handling and user feedback
- Multiple commands support
- Custom file type handlers
- WebSocket server for browser reload
- Enhanced terminal UI with colored output
- Plugins are separate processes, so they can't directly access LPM's internal state
- Plugin discovery happens at runtime, not compile time
- Plugins must be installed separately from the main LPM binary
- Check existing plugins for examples (
lpm-watch,lpm-bundle) - Review
lpm-coreAPI documentation - Open an issue for questions or suggestions