marty_plugin_protocol

Crates.iomarty_plugin_protocol
lib.rsmarty_plugin_protocol
version0.3.0
created_at2025-10-02 20:25:42.597143+00
updated_at2025-10-02 20:25:42.597143+00
descriptionProtocol definitions for Marty workspace provider plugins
homepagehttps://github.com/codyspate/marty
repositoryhttps://github.com/codyspate/marty
max_upload_size
id1865017
size80,882
Cody Spate (codyspate)

documentation

README

Marty Plugin Protocol

Crates.io Documentation License: MIT OR Apache-2.0

The official SDK for creating Marty workspace plugins as dynamic libraries. This crate provides everything you need to build plugins that automatically discover and manage projects within monorepos.

๐Ÿš€ Quick Start

1. Create a New Plugin

# Cargo.toml
[package]
name = "marty-plugin-myframework"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]  # Required for dynamic library

[dependencies]
marty_plugin_protocol = "0.2"
serde_json = "1.0"

2. Implement Your Plugin

use marty_plugin_protocol::{
    dylib::export_plugin, InferredProject, MartyPlugin, Workspace, WorkspaceProvider,
};
use serde_json::{json, Value as JsonValue};
use std::path::Path;

// Define your plugin
pub struct MyFrameworkPlugin;

impl MyFrameworkPlugin {
    pub const fn new() -> Self { Self }
}

impl Default for MyFrameworkPlugin {
    fn default() -> Self { Self::new() }
}

// Define project detection logic
pub struct MyFrameworkWorkspaceProvider;

impl WorkspaceProvider for MyFrameworkWorkspaceProvider {
    fn include_path_globs(&self) -> Vec<String> {
        vec!["**/my-framework.json".to_string()]
    }

    fn exclude_path_globs(&self) -> Vec<String> {
        vec!["**/node_modules/**".to_string()]
    }

    fn on_file_found(&self, _workspace: &Workspace, path: &Path) -> Option<InferredProject> {
        if path.file_name()?.to_str()? != "my-framework.json" {
            return None;
        }

        let project_dir = path.parent()?.to_path_buf();
        let name = project_dir.file_name()?.to_str()?.to_string();

        Some(InferredProject {
            name,
            project_dir,
            discovered_by: "my-framework".to_string(),
            workspace_dependencies: Vec::new(), // Parse from config file
        })
    }
}

// Connect plugin to workspace provider
impl MartyPlugin for MyFrameworkPlugin {
    fn name(&self) -> &str { "My Framework Plugin" }
    fn key(&self) -> &str { "my-framework" }
    fn workspace_provider(&self) -> &dyn WorkspaceProvider {
        &MyFrameworkWorkspaceProvider
    }
}

// Export for dynamic loading
export_plugin!(MyFrameworkPlugin);

3. Build and Install

# Build the plugin
cargo build --lib --release

# Install to Marty's plugin directory
cp target/release/libmarty_plugin_myframework.so ~/.marty/plugins/

๐Ÿง  Core Concepts

Plugin Discovery Process

  1. Scanning: Marty walks the workspace using your include_path_globs() patterns
  2. Filtering: Files matching exclude_path_globs() are skipped
  3. Detection: on_file_found() is called for each matching file
  4. Project Creation: Valid projects become InferredProject instances

Workspace Dependencies โš ๏ธ

Critical: workspace_dependencies represents dependencies between projects within the same workspace, NOT external packages.

// โœ… CORRECT: Internal workspace projects
vec![
    "shared-utils".to_string(),    // Another project in workspace
    "common-types".to_string(),    // Internal dependency
]

// โŒ INCORRECT: External packages
vec![
    "serde".to_string(),          // External crate
    "lodash".to_string(),         // External npm package
]

This is used for:

  • Build ordering: Dependencies built first
  • Change impact: Which projects are affected by changes
  • Task orchestration: Proper execution order across projects

๐Ÿ“š Examples by Language

Rust (Cargo.toml)

[dependencies]
shared-utils = { path = "../shared-utils" }  # โœ… Include "shared-utils"
serde = "1.0"                                # โŒ External - ignore

JavaScript/TypeScript (package.json)

{
  "dependencies": {
    "@myorg/shared": "workspace:*",          // โœ… Include "shared"
    "@myorg/utils": "file:../utils",         // โœ… Include "utils"
    "lodash": "^4.17.21"                     // โŒ External - ignore
  }
}

Python (pyproject.toml)

[tool.poetry.dependencies]
shared-lib = { path = "../shared-lib" }     # โœ… Include "shared-lib"
requests = "^2.25.0"                        # โŒ External - ignore

๐Ÿ”Œ Plugin Architecture

Core Traits

  • MartyPlugin: Main plugin interface with metadata and configuration
  • WorkspaceProvider: Project discovery and analysis logic

Key Data Structures

  • InferredProject: Represents a discovered project
  • Workspace: Context passed to plugins (root path, existing projects)
  • Project: Explicit projects with marty.yml files

Dynamic Library Interface

The export_plugin! macro generates C ABI functions for cross-language compatibility:

  • plugin_name() - Plugin display name
  • plugin_key() - Unique identifier
  • plugin_on_file_found() - Project detection
  • plugin_cleanup_string() - Memory management

๐Ÿงช Testing Your Plugin

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    #[test]
    fn test_project_detection() {
        let provider = MyFrameworkWorkspaceProvider;
        let workspace = Workspace {
            root: PathBuf::from("/workspace"),
            projects: vec![],
            inferred_projects: vec![],
        };
        
        let path = PathBuf::from("/workspace/project/my-framework.json");
        let result = provider.on_file_found(&workspace, &path);
        
        assert!(result.is_some());
        let project = result.unwrap();
        assert_eq!(project.name, "project");
        assert_eq!(project.discovered_by, "my-framework");
    }
}

๐Ÿš€ Performance Tips

  • Specific patterns: Use precise glob patterns to minimize file system operations
  • Fast rejection: Check filenames before reading file contents
  • Efficient parsing: Use streaming parsers for large files
  • Smart exclusions: Add common build directories to exclude_path_globs()

๐Ÿ› ๏ธ Migration from WASM

If you're migrating from WASM plugins:

Build Changes

# OLD: WASM target
# [lib]
# crate-type = ["cdylib"]
# [dependencies]
# serde = { version = "1.0", default-features = false }

# NEW: Dynamic library
[lib]
crate-type = ["cdylib"]
[dependencies]
serde_json = "1.0"  # Full crate ecosystem available!

Code Changes

// OLD: Manual WASM exports
// #[no_mangle]
// pub extern "C" fn _start() { /* ... */ }

// NEW: Simple macro
export_plugin!(MyPlugin);

Benefits

  • โœ… Faster execution - Native vs WASM interpretation
  • โœ… Better debugging - Standard debugging tools work
  • โœ… Full ecosystem - Any Rust crate can be used
  • โœ… Simpler deployment - Standard shared libraries

๐Ÿ“– Documentation

๐Ÿ› Troubleshooting

Common Issues

Plugin not detected:

  • Ensure crate-type = ["cdylib"] in Cargo.toml
  • Check plugin is in ~/.marty/plugins/ directory
  • Verify export_plugin!(YourPlugin) is called

Projects not discovered:

  • Check include_path_globs() patterns match your files
  • Verify on_file_found() returns Some(InferredProject) for valid projects
  • Test patterns don't conflict with excludes

Build errors:

  • Plugin struct must have pub const fn new() -> Self
  • Must implement MartyPlugin trait
  • Import export_plugin from marty_plugin_protocol::dylib

๐Ÿค Contributing

Contributions are welcome! Please see the main repository for contributing guidelines.

๐Ÿ“„ License

Licensed under either of Apache License, Version 2.0 or MIT License at your option.


Built by the Marty team ๐Ÿš€ GitHub | Documentation

Commit count: 0

cargo fmt