Merge _configs/ into config/ for single configuration directory. Update all path references. Changes: - Move _configs/* to config/ - Update .gitignore for new patterns - No code references to _configs/ found Impact: -1 root directory (layout_conventions.md compliance)
481 lines
12 KiB
Markdown
481 lines
12 KiB
Markdown
# Tools Shared Utilities Integration Guide
|
|
|
|
This guide explains how to integrate shared utilities into your Tools ecosystem Rust CLI.
|
|
|
|
## Quick Start
|
|
|
|
### 1. Add Dependency to Cargo.toml
|
|
|
|
In your CLI crate (e.g., `crates/your-cli/Cargo.toml`):
|
|
|
|
```toml
|
|
[dependencies]
|
|
tools-shared = { path = "../../../shared/rust" }
|
|
|
|
# For interactive project selection
|
|
tools-shared = { path = "../../../shared/rust", features = ["interactive"] }
|
|
|
|
# For manifest management
|
|
tools-shared = { path = "../../../shared/rust", features = ["manifest"] }
|
|
|
|
# All features
|
|
tools-shared = { path = "../../../shared/rust", features = ["interactive", "manifest"] }
|
|
```
|
|
|
|
### 2. Import in main.rs
|
|
|
|
```rust
|
|
use tools_shared::{
|
|
// Configuration discovery
|
|
find_config_path, find_db_path,
|
|
|
|
// Project features
|
|
detect_project_from_cwd, SelectableProject, select_project,
|
|
|
|
// Database migrations
|
|
find_migration_files, run_migration_file,
|
|
|
|
// XDG directories
|
|
tool_data_dir, tool_config_dir, ensure_dir,
|
|
|
|
// Global database
|
|
find_global_db_path,
|
|
};
|
|
```
|
|
|
|
### 3. Use in Your Code
|
|
|
|
See examples below for each feature.
|
|
|
|
## Feature Integration Examples
|
|
|
|
### Configuration Discovery
|
|
|
|
Find configuration files automatically:
|
|
|
|
```rust
|
|
use tools_shared::find_config_path;
|
|
|
|
#[derive(Parser)]
|
|
struct Cli {
|
|
#[arg(short, long)]
|
|
config: Option<String>,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let cli = Cli::parse();
|
|
|
|
// Use provided config or search
|
|
let config_path = cli.config
|
|
.map(PathBuf::from)
|
|
.or_else(|| find_config_path("my-tool.toml"))
|
|
.ok_or_else(|| anyhow::anyhow!("No config found"))?;
|
|
|
|
let config = Config::load(&config_path)?;
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Project Auto-Detection
|
|
|
|
Add a global `--project` flag that auto-detects projects:
|
|
|
|
```rust
|
|
use tools_shared::detect_project_from_cwd;
|
|
|
|
#[derive(Parser)]
|
|
struct Cli {
|
|
#[arg(short, long, global = true, help = "Project name (auto-detected if omitted)")]
|
|
project: Option<String>,
|
|
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let cli = Cli::parse();
|
|
|
|
// Auto-detect if not specified
|
|
let project_name = cli.project
|
|
.clone()
|
|
.or_else(|| {
|
|
detect_project_from_cwd("config.toml")
|
|
.map(|p| p.name)
|
|
});
|
|
|
|
if let Some(name) = project_name {
|
|
println!("Working on project: {}", name);
|
|
} else {
|
|
println!("No project specified or detected");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Interactive Project Selection
|
|
|
|
Add a command that lets users select a project:
|
|
|
|
```rust
|
|
use tools_shared::{select_project, SelectableProject};
|
|
|
|
#[derive(Subcommand)]
|
|
enum Commands {
|
|
/// Select a project interactively
|
|
SelectProject,
|
|
}
|
|
|
|
async fn select_project_command(projects: Vec<ProjectEntity>) -> Result<String> {
|
|
let selectable: Vec<SelectableProject> = projects
|
|
.into_iter()
|
|
.map(|p| SelectableProject {
|
|
id: p.id.clone(),
|
|
name: p.name,
|
|
path: None,
|
|
metadata: Some(format!("Phase: {:?}", p.current_phase)),
|
|
})
|
|
.collect();
|
|
|
|
select_project(selectable, "Select a project:", None)
|
|
.map_err(|e| anyhow::anyhow!("Selection failed: {}", e))
|
|
}
|
|
```
|
|
|
|
### Database Migrations
|
|
|
|
Add migration support:
|
|
|
|
```rust
|
|
use tools_shared::{find_migration_files, run_migration_file};
|
|
|
|
#[derive(Subcommand)]
|
|
enum Commands {
|
|
/// Run migrations
|
|
Migrate {
|
|
/// Path to migration file
|
|
#[arg(long)]
|
|
file: Option<String>,
|
|
|
|
/// Find and run all migrations in data/migration/
|
|
#[arg(long)]
|
|
all: Option<String>,
|
|
},
|
|
}
|
|
|
|
async fn handle_migrate(file: Option<String>, all: Option<String>) -> Result<()> {
|
|
if let Some(sql_file) = file {
|
|
// Run single migration
|
|
run_migration_file(&sql_file)?;
|
|
println!("✅ Migration executed");
|
|
} else if let Some(root) = all {
|
|
// Find and run all migrations
|
|
let migrations = find_migration_files(&root)?;
|
|
println!("Found {} migrations", migrations.len());
|
|
|
|
for migration in migrations {
|
|
if let Some(name) = migration.file_name() {
|
|
println!("Running: {:?}", name);
|
|
// Execute migration (requires sqlx or other DB driver)
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### XDG Directories for Data Storage
|
|
|
|
Use standard directories for your tool's data:
|
|
|
|
```rust
|
|
use tools_shared::{tool_data_dir, tool_config_dir, ensure_dir};
|
|
use std::path::PathBuf;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let tool_name = "my-tool";
|
|
|
|
// Create standard directories
|
|
let data_dir = tool_data_dir(tool_name);
|
|
let config_dir = tool_config_dir(tool_name);
|
|
|
|
ensure_dir(&data_dir)?;
|
|
ensure_dir(&config_dir)?;
|
|
|
|
// Now use them
|
|
let db_path = data_dir.join("my-tool.db");
|
|
let config_path = config_dir.join("config.toml");
|
|
|
|
println!("Data: {}", db_path.display());
|
|
println!("Config: {}", config_path.display());
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Global Multi-Project Database
|
|
|
|
Use a single global database to manage multiple projects:
|
|
|
|
```rust
|
|
use tools_shared::find_global_db_path;
|
|
use sqlx::sqlite::SqlitePool;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
// Get global database path
|
|
let db_path = find_global_db_path("my-tool");
|
|
let db_url = format!("sqlite://{}", db_path.display());
|
|
|
|
// Create database if it doesn't exist
|
|
if !db_path.exists() {
|
|
std::fs::create_dir_all(db_path.parent().unwrap())?;
|
|
}
|
|
|
|
// Connect to database
|
|
let pool = SqlitePool::connect(&db_url).await?;
|
|
|
|
// Run migrations
|
|
sqlx::query("CREATE TABLE IF NOT EXISTS projects (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
phase TEXT NOT NULL
|
|
)")
|
|
.execute(&pool)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
## Integration Checklist
|
|
|
|
When adding shared features to your CLI:
|
|
|
|
### Configuration Discovery
|
|
- [ ] Add `tools-shared` dependency
|
|
- [ ] Update `Cargo.toml` help text to document search order
|
|
- [ ] Use `find_config_path()` to discover config files
|
|
- [ ] Test configuration discovery from multiple locations
|
|
|
|
### Project Detection
|
|
- [ ] Add global `--project` flag to CLI struct
|
|
- [ ] Implement fallback to `detect_project_from_cwd()`
|
|
- [ ] Add `project detect` command to show detected project
|
|
- [ ] Cache project selection with `save_last_project()`
|
|
|
|
### Project Selection
|
|
- [ ] Add `features = ["interactive"]` to tools-shared
|
|
- [ ] Create struct list of available projects
|
|
- [ ] Implement project selection command
|
|
- [ ] Use `select_project()` for interactive picker
|
|
|
|
### Database Migrations
|
|
- [ ] Add migration file discovery
|
|
- [ ] Create `data/migration/` directory in examples
|
|
- [ ] Implement migration commands (`migrate --file` and `migrate --all`)
|
|
- [ ] Create sample migration files for testing
|
|
|
|
### XDG Directories
|
|
- [ ] Use `tool_data_dir()` for user data
|
|
- [ ] Use `tool_config_dir()` for configuration
|
|
- [ ] Use `tool_cache_dir()` for temporary data
|
|
- [ ] Call `ensure_dir()` before writing files
|
|
|
|
### Global Database
|
|
- [ ] Use `find_global_db_path()` for database location
|
|
- [ ] Create database parent directories if missing
|
|
- [ ] Store per-project data with project IDs
|
|
- [ ] Document multi-project schema
|
|
|
|
### Testing
|
|
- [ ] Test all configuration discovery paths
|
|
- [ ] Test project detection in various directories
|
|
- [ ] Test migration file discovery and execution
|
|
- [ ] Test directory creation with `ensure_dir()`
|
|
- [ ] Test database connectivity
|
|
|
|
### Documentation
|
|
- [ ] Update help text with configuration search order
|
|
- [ ] Document `--project` flag usage
|
|
- [ ] Add examples to README.md
|
|
- [ ] Document migration file format
|
|
|
|
## File Structure for a New Tool
|
|
|
|
When creating a new tool with shared features:
|
|
|
|
```
|
|
my-tool/
|
|
├── Cargo.toml # Workspace root
|
|
├── README.md # Tool documentation
|
|
├── QUICKSTART.md # 5-minute setup guide
|
|
├── crates/
|
|
│ ├── my-core/ # Core library
|
|
│ │ ├── src/
|
|
│ │ │ ├── lib.rs
|
|
│ │ │ ├── error.rs
|
|
│ │ │ └── service.rs
|
|
│ │ └── Cargo.toml
|
|
│ │
|
|
│ └── my-cli/ # CLI using shared features
|
|
│ ├── src/
|
|
│ │ ├── main.rs # Imports tools-shared
|
|
│ │ └── handlers/ # Command handlers
|
|
│ └── Cargo.toml # Has tools-shared dependency
|
|
│
|
|
├── data/
|
|
│ └── migration/ # SQL migrations
|
|
│ ├── 001_initial.sql
|
|
│ └── 002_add_index.sql
|
|
│
|
|
└── examples/
|
|
└── config.toml # Example configuration
|
|
```
|
|
|
|
## Examples in Tools Ecosystem
|
|
|
|
Study these existing implementations:
|
|
|
|
### Simple: hello-tool
|
|
- Basic project detection
|
|
- Migration file discovery
|
|
- Interactive selection example
|
|
|
|
```bash
|
|
# View implementation
|
|
cat /Users/Akasha/Tools/hello-tool/crates/hello-cli/src/main.rs
|
|
```
|
|
|
|
### Advanced: syntaxis
|
|
- Global `--project` flag
|
|
- Project commands (list, show, detect, select)
|
|
- Database migration handler
|
|
- Multi-project global database
|
|
|
|
```bash
|
|
# View implementation
|
|
cat /Users/Akasha/Development/syntaxis/crates/syntaxis-cli/src/main.rs
|
|
```
|
|
|
|
### Migration Examples
|
|
- Check how project-lifecycle uses migrations:
|
|
|
|
```bash
|
|
cat /Users/Akasha/Development/syntaxis/crates/syntaxis-cli/src/handlers/migrate_db.rs
|
|
```
|
|
|
|
## Testing Integration
|
|
|
|
Before publishing, verify:
|
|
|
|
```bash
|
|
# Build your tool
|
|
cargo build -p my-cli
|
|
|
|
# Test project detection
|
|
./target/debug/my-tool project detect
|
|
|
|
# Test configuration discovery
|
|
./target/debug/my-tool --config ./my-tool.toml <command>
|
|
|
|
# Test migration discovery
|
|
./target/debug/my-tool migrate --all .
|
|
|
|
# Run full test suite
|
|
cargo test --workspace
|
|
```
|
|
|
|
## Common Issues
|
|
|
|
### Configuration Not Found
|
|
**Problem**: `find_config_path()` returns `None` even though file exists
|
|
|
|
**Solution**:
|
|
- Check exact filename (case-sensitive)
|
|
- Ensure file is in one of the search locations
|
|
- Run from project root directory
|
|
- Use `find_config_path_warn_conflicts()` to debug
|
|
|
|
### Project Not Detected
|
|
**Problem**: `detect_project_from_cwd()` returns `None`
|
|
|
|
**Solution**:
|
|
- Verify `.project/lifecycle.toml` or `.vapora/lifecycle.toml` exists
|
|
- Check current working directory
|
|
- Run from project subdirectory (detection searches parents)
|
|
- Use `detect_project_from_path()` with explicit path
|
|
|
|
### Interactive Selection Not Working
|
|
**Problem**: `select_project()` shows no options
|
|
|
|
**Solution**:
|
|
- Ensure `features = ["interactive"]` in Cargo.toml
|
|
- Pass non-empty project list
|
|
- Check terminal supports ANSI (for colors/formatting)
|
|
- Test with `--no-interactive` flag if available
|
|
|
|
### Migrations Not Found
|
|
**Problem**: `find_migration_files()` returns empty vector
|
|
|
|
**Solution**:
|
|
- Create `data/migration/` directory
|
|
- Add `.sql` files (lowercase extension)
|
|
- Ensure files are readable
|
|
- Run from correct directory or use absolute path
|
|
|
|
## Migration Guide
|
|
|
|
### From Local Database to Global
|
|
|
|
If your tool previously used per-project databases:
|
|
|
|
```rust
|
|
// Old way (per-project)
|
|
let db_path = format!(".project/data/{}.db", project_name);
|
|
let db = Database::open(&db_path)?;
|
|
|
|
// New way (global, multi-project)
|
|
let db_path = find_global_db_path("my-tool");
|
|
let db = Database::open(&db_path)?;
|
|
|
|
// Store project context in data
|
|
let projects = db.get_projects()?;
|
|
let project = projects.iter().find(|p| p.name == project_name)?;
|
|
let entries = db.get_entries(project.id)?;
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Always handle missing files gracefully** - Don't assume configuration exists
|
|
2. **Provide good error messages** - Tell users where to create config
|
|
3. **Cache project selection** - Use `save_last_project()` for quick re-runs
|
|
4. **Support explicit overrides** - Allow `--project` flag even when auto-detected
|
|
5. **Document search order** - Show users in help text where config is searched
|
|
6. **Use XDG directories** - Don't hardcode paths like `~/.myapp/`
|
|
7. **Support global database** - Use single DB for multi-project management
|
|
8. **Plan migrations** - Create `data/migration/` structure from start
|
|
|
|
## Getting Help
|
|
|
|
- **Read source code**: `/Users/Akasha/Tools/shared/rust/`
|
|
- **Check examples**: `/Users/Akasha/Tools/hello-tool/crates/hello-cli/src/main.rs`
|
|
- **Study advanced usage**: `/Users/Akasha/Development/syntaxis/`
|
|
- **Run tests**: `cargo test -p tools-shared`
|
|
- **API docs**: `cargo doc -p tools-shared --open`
|
|
|
|
## Next Steps
|
|
|
|
1. Add shared dependency to your CLI
|
|
2. Follow "Quick Start" section above
|
|
3. Implement features incrementally
|
|
4. Test each feature thoroughly
|
|
5. Update documentation
|
|
6. Commit with clear commit message
|
|
7. Share with the team!
|