Skip to content

Commit 8a07063

Browse files
authored
feat: add plugin system phase 1 (#50)
* feat: add plugins system phase 1 * tests: improve coverage * tests: improve coverage * chore: add mock health plugin
1 parent 9ea436f commit 8a07063

File tree

13 files changed

+1878
-98
lines changed

13 files changed

+1878
-98
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ name = "repos"
33
version = "0.0.9"
44
edition = "2024"
55

6+
[lib]
7+
name = "repos"
8+
path = "src/lib.rs"
9+
10+
[workspace]
11+
members = [
12+
".",
13+
"plugins/repos-health",
14+
]
15+
616
[dependencies]
717
async-trait = "0.1"
818
clap = { version = "4.4", features = ["derive"] }
@@ -22,3 +32,4 @@ uuid = { version = "1.6", features = ["v4"] }
2232

2333
[dev-dependencies]
2434
tempfile = "3.0"
35+
serial_test = "3.0"

docs/plugins.md

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# Plugin System
2+
3+
The `repos` tool supports an extensible plugin system that allows you to add new functionality without modifying the core codebase. This is implemented using Phase 1 of the plugin architecture: external command plugins.
4+
5+
## How It Works
6+
7+
The plugin system follows the same pattern as Git's external subcommands:
8+
9+
- Any executable named `repos-<plugin>` in your `PATH` becomes a plugin
10+
- When you run `repos <plugin> <args>`, the tool automatically finds and executes `repos-<plugin>` with the provided arguments
11+
- This provides complete isolation, crash safety, and the ability to write plugins in any language
12+
13+
## Creating a Plugin
14+
15+
To create a plugin:
16+
17+
1. **Create an executable** named `repos-<name>` where `<name>` is your plugin name
18+
2. **Make it executable** (`chmod +x repos-<name>`)
19+
3. **Add it to your PATH** so the `repos` tool can find it
20+
21+
### Example: Health Plugin
22+
23+
Here's a simple example of a health check plugin written in bash:
24+
25+
```bash
26+
#!/bin/bash
27+
# Save as: repos-health
28+
29+
echo "=== Repository Health Check ==="
30+
31+
# Parse arguments
32+
CONFIG_FILE="config.yaml"
33+
VERBOSE=false
34+
35+
while [[ $# -gt 0 ]]; do
36+
case $1 in
37+
-c|--config)
38+
CONFIG_FILE="$2"
39+
shift 2
40+
;;
41+
-v|--verbose)
42+
VERBOSE=true
43+
shift
44+
;;
45+
*)
46+
echo "Unknown option: $1"
47+
exit 1
48+
;;
49+
esac
50+
done
51+
52+
echo "Using config: $CONFIG_FILE"
53+
echo "Verbose mode: $VERBOSE"
54+
55+
# Add your health check logic here
56+
# You can parse the YAML config file and iterate over repositories
57+
# Example checks:
58+
# - Check for outdated dependencies (cargo outdated, npm outdated, etc.)
59+
# - Analyze cognitive complexity (radon, lizard, etc.)
60+
# - Security audits (cargo audit, npm audit, etc.)
61+
# - Code coverage statistics
62+
# - Git repository health (uncommitted changes, etc.)
63+
64+
echo "Health check completed!"
65+
```
66+
67+
### Example: Security Plugin in Python
68+
69+
```python
70+
#!/usr/bin/env python3
71+
# Save as: repos-security
72+
73+
import argparse
74+
import yaml
75+
import subprocess
76+
import sys
77+
78+
def main():
79+
parser = argparse.ArgumentParser(description='Security audit for repositories')
80+
parser.add_argument('-c', '--config', default='config.yaml', help='Config file path')
81+
parser.add_argument('--fix', action='store_true', help='Attempt to fix issues automatically')
82+
args = parser.parse_args()
83+
84+
# Load configuration
85+
try:
86+
with open(args.config, 'r') as f:
87+
config = yaml.safe_load(f)
88+
except FileNotFoundError:
89+
print(f"Error: Config file '{args.config}' not found", file=sys.stderr)
90+
sys.exit(1)
91+
92+
repositories = config.get('repositories', [])
93+
94+
print("=== Security Audit ===")
95+
96+
for repo in repositories:
97+
name = repo['name']
98+
path = repo.get('path', f"./{name}")
99+
100+
print(f"\n🔍 Auditing {name}...")
101+
102+
# Example security checks
103+
if check_rust_security(path):
104+
print(f"{name}: No security issues found")
105+
else:
106+
print(f" ⚠️ {name}: Security issues detected")
107+
108+
def check_rust_security(repo_path):
109+
"""Run cargo audit for Rust projects"""
110+
try:
111+
result = subprocess.run(
112+
['cargo', 'audit'],
113+
cwd=repo_path,
114+
capture_output=True,
115+
text=True
116+
)
117+
return result.returncode == 0
118+
except FileNotFoundError:
119+
# cargo not available, skip Rust checks
120+
return True
121+
122+
if __name__ == '__main__':
123+
main()
124+
```
125+
126+
## Using Plugins
127+
128+
### List Available Plugins
129+
130+
```bash
131+
repos --list-plugins
132+
```
133+
134+
This command scans your `PATH` for any executables matching the `repos-*` pattern and displays them.
135+
136+
### Execute a Plugin
137+
138+
```bash
139+
repos <plugin-name> [arguments...]
140+
```
141+
142+
Examples:
143+
144+
```bash
145+
# Run health check with default config
146+
repos health
147+
148+
# Run health check with custom config and verbose output
149+
repos health -c my-config.yaml -v
150+
151+
# Run security audit
152+
repos security --config production.yaml
153+
154+
# Run security audit with auto-fix
155+
repos security --fix
156+
```
157+
158+
## Plugin Guidelines
159+
160+
### Naming
161+
162+
- Plugin executables must be named `repos-<name>`
163+
- Choose descriptive, lowercase names
164+
- Use hyphens for multi-word names (e.g., `repos-code-quality`)
165+
166+
### Arguments
167+
168+
- Follow Unix conventions for command-line arguments
169+
- Support `-h` or `--help` for usage information
170+
- Consider supporting `-c` or `--config` for custom config files
171+
- Use long options with double dashes (`--verbose`) for clarity
172+
173+
### Output
174+
175+
- Use clear, structured output
176+
- Consider using emoji or symbols for visual feedback (✅ ❌ ⚠️)
177+
- Write errors to stderr, normal output to stdout
178+
- Use appropriate exit codes (0 for success, non-zero for errors)
179+
180+
### Integration
181+
182+
- Plugins should work with the standard `config.yaml` format
183+
- Parse the YAML configuration to access repository information
184+
- Consider the repository structure (name, path, tags, etc.)
185+
186+
## Plugin Development Tips
187+
188+
### Configuration Access
189+
190+
Most plugins will need to read the repos configuration file. Here's how to parse it in different languages:
191+
192+
**Bash (using yq):**
193+
194+
```bash
195+
# Install yq: brew install yq (macOS) or similar
196+
repos=$(yq eval '.repositories[].name' config.yaml)
197+
```
198+
199+
**Python:**
200+
201+
```python
202+
import yaml
203+
with open('config.yaml', 'r') as f:
204+
config = yaml.safe_load(f)
205+
repositories = config.get('repositories', [])
206+
```
207+
208+
**Rust:**
209+
210+
```rust
211+
use serde_yaml;
212+
use std::fs;
213+
214+
let content = fs::read_to_string("config.yaml")?;
215+
let config: serde_yaml::Value = serde_yaml::from_str(&content)?;
216+
```
217+
218+
### Error Handling
219+
220+
- Always validate input arguments
221+
- Check if required tools are available before using them
222+
- Provide helpful error messages
223+
- Use appropriate exit codes
224+
225+
### Testing
226+
227+
- Create test repositories for development
228+
- Test with different repository structures
229+
- Verify behavior with missing or invalid configurations
230+
231+
## Limitations
232+
233+
This Phase 1 implementation has some limitations that future phases may address:
234+
235+
- No built-in dependency management for plugins
236+
- No plugin metadata or versioning system
237+
- No automatic plugin updates
238+
- Limited inter-plugin communication
239+
240+
## Future Phases
241+
242+
The plugin system is designed for gradual expansion:
243+
244+
- **Phase 2**: Plugin registry and installation system
245+
- **Phase 3**: Plugin API for deeper integration
246+
- **Phase 4**: Plugin dependency management and sandboxing
247+
248+
For now, Phase 1 provides a solid foundation for extending the repos tool with external functionality while maintaining simplicity and safety.

plugins/repos-health/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "repos-health"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[[bin]]
7+
name = "repos-health"
8+
path = "src/main.rs"
9+
10+
[dependencies]
11+
anyhow = "1"
12+
serde = { version = "1", features = ["derive"] }
13+
serde_json = "1"
14+
chrono = { version = "0.4", features = ["serde"] }
15+
16+
[dependencies.repos]
17+
path = "../.."

plugins/repos-health/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# repos-health
2+
3+
A health check plugin for the repos tool that:
4+
5+
- Scans each repository for `package.json` files
6+
- Checks for outdated npm dependencies using `npm outdated`
7+
- Updates dependencies using `npm update`
8+
- Creates git branches and commits changes
9+
- Opens pull requests for dependency updates
10+
11+
## Requirements
12+
13+
- Node.js and npm installed
14+
- Git repository with push permissions
15+
- GitHub token configured for PR creation
16+
17+
## Usage
18+
19+
```bash
20+
repos health
21+
```
22+
23+
The plugin will:
24+
25+
1. Load the default repos configuration
26+
2. Process each repository that contains a `package.json`
27+
3. Check for outdated dependencies
28+
4. Update dependencies if found
29+
5. Create a branch and commit changes
30+
6. Push the branch and open a PR
31+
32+
## Output
33+
34+
The plugin reports:
35+
36+
- Repositories processed
37+
- Outdated packages found
38+
- Successful dependency updates
39+
- PR creation status

0 commit comments

Comments
 (0)