feat: implement markitect installer with version/release commands (issue #80)
- Add comprehensive version information system with git integration - Add `markitect version` and `markitect release` commands with multiple output formats - Add global `--version` flag for quick version checking - Create Python installer script with advanced options (install.py) - Create shell installer wrapper for easy installation (install.sh) - Add comprehensive installation documentation (INSTALL.md) - Support user and system-wide installations with virtual environments - Include development mode installation with test dependencies - Add installation status checking and uninstall functionality Commands added: - `markitect --version` - Quick version display - `markitect version [--short]` - Detailed version information - `markitect release [--format text|json|yaml]` - Release information Installer features: - Automatic virtual environment creation - Symbolic link management for global access - Custom installation paths and prefixes - Development mode with test dependencies - Installation validation and troubleshooting Resolves #80 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
279
.clinerules
Normal file
279
.clinerules
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
# MarkiTect Project - Claude Code Rules
|
||||||
|
# =====================================
|
||||||
|
# Guidelines for Claude Code when working with the MarkiTect project
|
||||||
|
# This project follows TDD8 methodology with Clean Architecture
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
This is a high-performance markdown processing engine with database integration,
|
||||||
|
AST-based parsing, and sophisticated caching. The project follows Clean Architecture
|
||||||
|
principles with strict separation of concerns.
|
||||||
|
|
||||||
|
## Directory Structure & Clean Architecture
|
||||||
|
```
|
||||||
|
markitect_project/
|
||||||
|
├── domain/ # Business logic (innermost layer)
|
||||||
|
├── application/ # Use cases and workflows
|
||||||
|
├── infrastructure/ # External interfaces (database, file system)
|
||||||
|
├── cli/ # Presentation layer (CLI interface)
|
||||||
|
├── markitect/ # Core markdown processing engine
|
||||||
|
├── tests/ # Comprehensive test suite (TDD8 methodology)
|
||||||
|
├── docs/ # Architecture and user documentation
|
||||||
|
└── tddai/ # TDD workflow tools and utilities
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
### 1. TDD8 Methodology - ALWAYS FOLLOW
|
||||||
|
1. **ISSUE**: Analyze GitHub issue and extract requirements
|
||||||
|
2. **TEST**: Write comprehensive tests BEFORE implementation
|
||||||
|
3. **RED**: Ensure tests fail initially (validate test correctness)
|
||||||
|
4. **GREEN**: Implement minimum viable solution to pass tests
|
||||||
|
5. **REFACTOR**: Improve code quality and design
|
||||||
|
6. **DOCUMENT**: Update documentation and examples
|
||||||
|
7. **REFINE**: Performance optimization and edge cases
|
||||||
|
8. **PUBLISH**: Integration validation and delivery
|
||||||
|
|
||||||
|
### 2. Clean Architecture Dependency Rules
|
||||||
|
- **NEVER violate dependency inversion**: Outer layers depend on inner layers, never reverse
|
||||||
|
- **Domain layer**: Pure business logic, no external dependencies
|
||||||
|
- **Application layer**: Use cases, may depend only on domain
|
||||||
|
- **Infrastructure layer**: External concerns (database, CLI, API)
|
||||||
|
- **Presentation layer**: User interfaces (CLI commands)
|
||||||
|
|
||||||
|
### 3. Testing Requirements
|
||||||
|
- **Minimum 80% test coverage** - Use `pytest --cov=markitect --cov-report=html`
|
||||||
|
- **Test naming**: `test_issue_{issue_num}_{scenario}.py` pattern
|
||||||
|
- **Architectural testing**: Run tests by layer (`make test-domain`, `make test-infrastructure`)
|
||||||
|
- **Performance validation**: All cache operations must be <50% of parsing time
|
||||||
|
- **TDD workspace**: Use `.tddai_workspace/` for issue-specific development
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Starting Work on an Issue
|
||||||
|
```bash
|
||||||
|
# Always start with TDD workspace
|
||||||
|
make tdd-start NUM=<issue_number>
|
||||||
|
|
||||||
|
# Analyze requirements first
|
||||||
|
make validate-requirements
|
||||||
|
|
||||||
|
# Create tests before implementation
|
||||||
|
make tdd-add-test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality Gates
|
||||||
|
```bash
|
||||||
|
# Run before any commit
|
||||||
|
make test # All tests must pass
|
||||||
|
make lint # Code style compliance
|
||||||
|
make test-coverage NUM=X # Verify coverage targets
|
||||||
|
make validate-mocks # Mock compatibility
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance Requirements
|
||||||
|
- **Cache operations**: <50% of initial parsing time (enforced by tests)
|
||||||
|
- **Memory usage**: <50MB baseline for normal operations
|
||||||
|
- **Database queries**: Sub-millisecond metadata retrieval
|
||||||
|
- **Bulk operations**: Linear scaling with document count
|
||||||
|
|
||||||
|
## Technology Stack & Dependencies
|
||||||
|
|
||||||
|
### Core Technologies
|
||||||
|
- **Python 3.8+** with type hints (gradual mypy adoption)
|
||||||
|
- **SQLite** for database operations (ACID compliance required)
|
||||||
|
- **markdown-it-py** for AST processing
|
||||||
|
- **pytest** for testing with comprehensive fixtures
|
||||||
|
- **Click** for CLI framework
|
||||||
|
|
||||||
|
### Key Libraries
|
||||||
|
- `PyYAML` - Front matter processing
|
||||||
|
- `jsonpath-ng` - AST querying
|
||||||
|
- `tabulate` - Output formatting
|
||||||
|
- `aiohttp` - Async HTTP operations
|
||||||
|
|
||||||
|
## Coding Standards
|
||||||
|
|
||||||
|
### Python Code Style
|
||||||
|
- **Type hints**: Use where possible (gradual mypy adoption)
|
||||||
|
- **Docstrings**: Required for all public methods
|
||||||
|
- **Error handling**: Comprehensive exception handling and validation
|
||||||
|
- **Security**: Never log secrets, validate all inputs, prevent SQL injection
|
||||||
|
|
||||||
|
### File Organization
|
||||||
|
- **One concept per file**: Clear separation of responsibilities
|
||||||
|
- **Interface segregation**: Clean interfaces between layers
|
||||||
|
- **Plugin architecture**: Support modular extensions
|
||||||
|
|
||||||
|
### Database Operations
|
||||||
|
- **Read-only queries**: Default to safe operations
|
||||||
|
- **Transaction safety**: Use ACID compliance for batch operations
|
||||||
|
- **Performance optimization**: Leverage SQLite capabilities
|
||||||
|
- **Migration support**: Schema versioning and updates
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### CLI Command Structure
|
||||||
|
```python
|
||||||
|
@click.command()
|
||||||
|
@click.option('--format', type=click.Choice(['table', 'json', 'yaml']))
|
||||||
|
def command_name(format):
|
||||||
|
"""Command description with clear purpose."""
|
||||||
|
try:
|
||||||
|
# Implementation with proper error handling
|
||||||
|
pass
|
||||||
|
except SpecificException as e:
|
||||||
|
# Provide helpful error messages
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Structure (TDD8 Pattern)
|
||||||
|
```python
|
||||||
|
class TestIssue{N}_{Description}:
|
||||||
|
"""Test suite for issue #{N}: {description}"""
|
||||||
|
|
||||||
|
def test_{scenario}_success(self):
|
||||||
|
"""Test successful operation scenario."""
|
||||||
|
# Arrange
|
||||||
|
# Act
|
||||||
|
# Assert
|
||||||
|
|
||||||
|
def test_{scenario}_error_handling(self):
|
||||||
|
"""Test error handling scenario."""
|
||||||
|
# Test edge cases and error conditions
|
||||||
|
```
|
||||||
|
|
||||||
|
### Domain Model Pattern
|
||||||
|
```python
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DomainEntity:
|
||||||
|
"""Domain entity with business logic."""
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def business_method(self) -> bool:
|
||||||
|
"""Business logic belongs in domain layer."""
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Guidelines
|
||||||
|
|
||||||
|
### AST Caching System
|
||||||
|
- **Cache validation**: Automatic timestamp-based invalidation
|
||||||
|
- **Serialization**: Optimized JSON format for AST storage
|
||||||
|
- **Memory management**: Careful resource cleanup
|
||||||
|
- **Performance contracts**: <50% of parsing time (tested)
|
||||||
|
|
||||||
|
### Database Optimization
|
||||||
|
- **Query optimization**: Use appropriate indexes
|
||||||
|
- **Batch operations**: Minimize database round trips
|
||||||
|
- **Connection management**: Proper connection lifecycle
|
||||||
|
- **Read-only defaults**: Safety-first approach
|
||||||
|
|
||||||
|
## Security Requirements
|
||||||
|
|
||||||
|
### Input Validation
|
||||||
|
- **SQL injection prevention**: Use parameterized queries
|
||||||
|
- **Path traversal protection**: Validate file paths
|
||||||
|
- **Command injection**: Sanitize shell command inputs
|
||||||
|
- **YAML safety**: Safe loading of front matter
|
||||||
|
|
||||||
|
### Secrets Management
|
||||||
|
- **Never log secrets**: Authentication tokens, passwords
|
||||||
|
- **Environment variables**: Use for sensitive configuration
|
||||||
|
- **Git repository**: Never commit credentials
|
||||||
|
- **Error messages**: Don't expose sensitive information
|
||||||
|
|
||||||
|
## Documentation Standards
|
||||||
|
|
||||||
|
### Code Documentation
|
||||||
|
- **API documentation**: Clear method signatures and purposes
|
||||||
|
- **Architecture decisions**: Document in docs/architecture/
|
||||||
|
- **Usage examples**: Include practical examples
|
||||||
|
- **Performance notes**: Document performance characteristics
|
||||||
|
|
||||||
|
### User Documentation
|
||||||
|
- **CLI help**: Comprehensive command documentation
|
||||||
|
- **Configuration**: Clear setup instructions
|
||||||
|
- **Troubleshooting**: Common issues and solutions
|
||||||
|
- **Performance**: Usage optimization guidelines
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### Git Platform Integration
|
||||||
|
- **Gitea API**: Primary integration for issue management
|
||||||
|
- **GitHub compatibility**: Support multiple platforms
|
||||||
|
- **Authentication**: Token-based with multiple sources
|
||||||
|
- **Error handling**: Robust network failure handling
|
||||||
|
|
||||||
|
### Development Tools
|
||||||
|
- **Makefile integration**: Standard development commands
|
||||||
|
- **pytest integration**: Comprehensive test framework
|
||||||
|
- **mypy integration**: Gradual type checking adoption
|
||||||
|
- **CLI tools**: Complete command-line interface
|
||||||
|
|
||||||
|
## Common Mistakes to Avoid
|
||||||
|
|
||||||
|
### Architecture Violations
|
||||||
|
- ❌ **Domain depending on infrastructure**: Never import database in domain
|
||||||
|
- ❌ **Skipping tests**: Never implement without tests first (TDD8)
|
||||||
|
- ❌ **Performance assumptions**: Always validate cache performance
|
||||||
|
- ❌ **Direct database access**: Use repository pattern
|
||||||
|
|
||||||
|
### Security Issues
|
||||||
|
- ❌ **SQL injection**: Always use parameterized queries
|
||||||
|
- ❌ **Logging secrets**: Never log authentication tokens
|
||||||
|
- ❌ **Unsafe YAML**: Use yaml.safe_load() not yaml.load()
|
||||||
|
- ❌ **Path injection**: Validate and sanitize file paths
|
||||||
|
|
||||||
|
### Testing Issues
|
||||||
|
- ❌ **Insufficient coverage**: Maintain >80% test coverage
|
||||||
|
- ❌ **Missing edge cases**: Test error conditions thoroughly
|
||||||
|
- ❌ **Test dependencies**: Tests must be independent
|
||||||
|
- ❌ **Performance tests**: Validate cache performance contracts
|
||||||
|
|
||||||
|
## When Making Changes
|
||||||
|
|
||||||
|
### Before Implementation
|
||||||
|
1. **Read the issue**: Understand requirements completely
|
||||||
|
2. **TDD workspace**: Use `make tdd-start NUM=X`
|
||||||
|
3. **Write tests first**: Follow TDD8 methodology strictly
|
||||||
|
4. **Validate architecture**: Ensure clean dependency flow
|
||||||
|
|
||||||
|
### During Implementation
|
||||||
|
1. **Red-Green-Refactor**: Follow TDD cycle religiously
|
||||||
|
2. **Performance validation**: Test cache performance contracts
|
||||||
|
3. **Security review**: Validate input handling and safety
|
||||||
|
4. **Documentation updates**: Keep docs current with changes
|
||||||
|
|
||||||
|
### Before Completion
|
||||||
|
1. **Full test suite**: `make test` must pass completely
|
||||||
|
2. **Performance benchmarks**: Validate performance requirements
|
||||||
|
3. **Code quality**: `make lint` and type checking
|
||||||
|
4. **Integration tests**: Verify end-to-end functionality
|
||||||
|
|
||||||
|
## Emergency Procedures
|
||||||
|
|
||||||
|
### If Tests Fail
|
||||||
|
1. **Don't ignore**: Never commit with failing tests
|
||||||
|
2. **Isolate issue**: Use `make test-module MODULE=name`
|
||||||
|
3. **Check dependencies**: Verify layer boundary violations
|
||||||
|
4. **Performance regression**: Check cache performance contracts
|
||||||
|
|
||||||
|
### If Performance Degrades
|
||||||
|
1. **Run benchmarks**: Use performance test suite
|
||||||
|
2. **Cache validation**: Verify cache hit rates and timing
|
||||||
|
3. **Memory profiling**: Check for memory leaks
|
||||||
|
4. **Database optimization**: Review query performance
|
||||||
|
|
||||||
|
### If Security Issues Found
|
||||||
|
1. **Immediate assessment**: Evaluate impact and scope
|
||||||
|
2. **Input validation**: Review all user input handling
|
||||||
|
3. **Secrets audit**: Check for credential exposure
|
||||||
|
4. **Dependency updates**: Update vulnerable dependencies
|
||||||
|
|
||||||
|
Remember: This project's success depends on maintaining architectural discipline,
|
||||||
|
comprehensive testing, and performance contracts. When in doubt, ask for clarification
|
||||||
|
and always prioritize correctness over speed of implementation.
|
||||||
219
INSTALL.md
Normal file
219
INSTALL.md
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# MarkiTect Installation Guide
|
||||||
|
|
||||||
|
This document describes how to install MarkiTect and make it available system-wide.
|
||||||
|
|
||||||
|
## Quick Installation
|
||||||
|
|
||||||
|
For most users, the quick installer is the easiest option:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install for current user
|
||||||
|
./install.sh
|
||||||
|
|
||||||
|
# Install system-wide (requires sudo)
|
||||||
|
./install.sh --system
|
||||||
|
|
||||||
|
# Install in development mode with test dependencies
|
||||||
|
./install.sh --dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Installation
|
||||||
|
|
||||||
|
For more control over the installation process, use the Python installer:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install with custom prefix
|
||||||
|
python install.py --prefix /opt/markitect
|
||||||
|
|
||||||
|
# Install with custom virtual environment location
|
||||||
|
python install.py --venv-dir /path/to/custom/venv
|
||||||
|
|
||||||
|
# Install without creating symbolic links (manual PATH setup)
|
||||||
|
python install.py --no-symlinks
|
||||||
|
|
||||||
|
# Force reinstallation over existing installation
|
||||||
|
python install.py --force
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation Options
|
||||||
|
|
||||||
|
### Installation Types
|
||||||
|
|
||||||
|
- **User Installation** (default): Installs to `~/.local/`
|
||||||
|
- **System Installation** (`--system`): Installs to `/usr/local/` (requires sudo)
|
||||||
|
- **Development Installation** (`--dev`): Installs in editable mode with test dependencies
|
||||||
|
|
||||||
|
### Installation Paths
|
||||||
|
|
||||||
|
By default, MarkiTect is installed to:
|
||||||
|
|
||||||
|
- **User installation**: `~/.local/lib/markitect/` (virtual environment)
|
||||||
|
- **System installation**: `/usr/local/lib/markitect/` (virtual environment)
|
||||||
|
- **Binaries**: `~/.local/bin/` or `/usr/local/bin/`
|
||||||
|
|
||||||
|
### Available Commands
|
||||||
|
|
||||||
|
After installation, these commands will be available:
|
||||||
|
|
||||||
|
- `markitect` - Main MarkiTect CLI
|
||||||
|
- `tddai` - TDD workflow management
|
||||||
|
- `issue` - Issue management
|
||||||
|
|
||||||
|
## Checking Installation
|
||||||
|
|
||||||
|
Check if MarkiTect is already installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh --check
|
||||||
|
# or
|
||||||
|
python install.py --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Check version after installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
markitect version
|
||||||
|
markitect version --short
|
||||||
|
markitect release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uninstallation
|
||||||
|
|
||||||
|
To remove MarkiTect:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh --uninstall
|
||||||
|
# or
|
||||||
|
python install.py --uninstall
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Installation
|
||||||
|
|
||||||
|
If you prefer to install manually:
|
||||||
|
|
||||||
|
1. **Create virtual environment:**
|
||||||
|
```bash
|
||||||
|
python -m venv ~/.local/lib/markitect
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Activate virtual environment:**
|
||||||
|
```bash
|
||||||
|
source ~/.local/lib/markitect/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Install MarkiTect:**
|
||||||
|
```bash
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Create symbolic links:**
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.local/bin
|
||||||
|
ln -sf ~/.local/lib/markitect/bin/markitect ~/.local/bin/markitect
|
||||||
|
ln -sf ~/.local/lib/markitect/bin/tddai ~/.local/bin/tddai
|
||||||
|
ln -sf ~/.local/lib/markitect/bin/issue ~/.local/bin/issue
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Add to PATH** (add to `~/.bashrc` or `~/.zshrc`):
|
||||||
|
```bash
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Installation
|
||||||
|
|
||||||
|
For development work:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install in development mode
|
||||||
|
./install.sh --dev
|
||||||
|
|
||||||
|
# This includes:
|
||||||
|
# - Editable installation (changes reflect immediately)
|
||||||
|
# - Test dependencies (pytest, black, flake8, mypy)
|
||||||
|
# - All development tools
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Command not found after installation:**
|
||||||
|
- Make sure `~/.local/bin` is in your PATH
|
||||||
|
- Run: `export PATH="$HOME/.local/bin:$PATH"`
|
||||||
|
- Add the export to your shell profile
|
||||||
|
|
||||||
|
2. **Permission denied on system installation:**
|
||||||
|
- Use `sudo ./install.sh --system`
|
||||||
|
- Or install to user directory instead
|
||||||
|
|
||||||
|
3. **Python version error:**
|
||||||
|
- MarkiTect requires Python 3.8 or higher
|
||||||
|
- Check version: `python3 --version`
|
||||||
|
|
||||||
|
4. **Installation already exists:**
|
||||||
|
- Use `--force` to overwrite: `./install.sh --force`
|
||||||
|
- Or uninstall first: `./install.sh --uninstall`
|
||||||
|
|
||||||
|
### Manual PATH Setup
|
||||||
|
|
||||||
|
If symbolic links don't work, add the virtual environment bin directory to your PATH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For bash/zsh (add to ~/.bashrc or ~/.zshrc)
|
||||||
|
export PATH="$HOME/.local/lib/markitect/bin:$PATH"
|
||||||
|
|
||||||
|
# For fish (add to ~/.config/fish/config.fish)
|
||||||
|
set -gx PATH $HOME/.local/lib/markitect/bin $PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Installation
|
||||||
|
|
||||||
|
After installation, verify everything works:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test basic functionality
|
||||||
|
markitect --help
|
||||||
|
markitect version
|
||||||
|
|
||||||
|
# Test TDD tools
|
||||||
|
tddai --help
|
||||||
|
|
||||||
|
# Test issue management
|
||||||
|
issue --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
MarkiTect automatically installs these dependencies:
|
||||||
|
|
||||||
|
### Production Dependencies
|
||||||
|
- markdown-it-py - Markdown parsing
|
||||||
|
- PyYAML - YAML processing
|
||||||
|
- click>=8.0.0 - CLI framework
|
||||||
|
- tabulate>=0.9.0 - Table formatting
|
||||||
|
- jsonpath-ng>=1.5.0 - JSONPath queries
|
||||||
|
- aiohttp>=3.8.0 - Async HTTP client
|
||||||
|
- toml - TOML file parsing
|
||||||
|
|
||||||
|
### Development Dependencies (with --dev)
|
||||||
|
- pytest - Testing framework
|
||||||
|
- pytest-cov - Test coverage
|
||||||
|
- black - Code formatting
|
||||||
|
- flake8 - Code linting
|
||||||
|
- mypy - Type checking
|
||||||
|
|
||||||
|
## System Requirements
|
||||||
|
|
||||||
|
- Python 3.8 or higher
|
||||||
|
- pip (Python package installer)
|
||||||
|
- git (optional, for version info)
|
||||||
|
- Unix-like system (Linux, macOS) or Windows with Python support
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For installation issues:
|
||||||
|
|
||||||
|
1. Check this guide first
|
||||||
|
2. Run `./install.sh --check` to diagnose problems
|
||||||
|
3. See the main project documentation
|
||||||
|
4. Report issues on the project issue tracker
|
||||||
388
install.py
Normal file
388
install.py
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
MarkiTect Installer
|
||||||
|
|
||||||
|
This script provides an easy way to install MarkiTect and make it available
|
||||||
|
system-wide. It handles virtual environment creation, dependency installation,
|
||||||
|
and creates symbolic links to make the commands available from anywhere.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python install.py [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--prefix PATH Installation prefix (default: ~/.local)
|
||||||
|
--system Install system-wide (requires sudo, uses /usr/local)
|
||||||
|
--venv-dir PATH Custom virtual environment directory
|
||||||
|
--no-symlinks Don't create symbolic links (manual PATH setup required)
|
||||||
|
--force Force reinstallation over existing installation
|
||||||
|
--dev Install in development mode with test dependencies
|
||||||
|
--check Check if MarkiTect is already installed
|
||||||
|
--uninstall Uninstall MarkiTect
|
||||||
|
--help Show this help message
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
class MarkiTectInstaller:
|
||||||
|
"""MarkiTect installation manager."""
|
||||||
|
|
||||||
|
def __init__(self, prefix=None, system=False, venv_dir=None, force=False, dev=False):
|
||||||
|
self.system = system
|
||||||
|
self.force = force
|
||||||
|
self.dev = dev
|
||||||
|
|
||||||
|
# Determine installation paths
|
||||||
|
if system:
|
||||||
|
self.prefix = Path("/usr/local")
|
||||||
|
self.bin_dir = self.prefix / "bin"
|
||||||
|
self.venv_dir = Path(venv_dir) if venv_dir else self.prefix / "lib" / "markitect"
|
||||||
|
else:
|
||||||
|
self.prefix = Path(prefix) if prefix else Path.home() / ".local"
|
||||||
|
self.bin_dir = self.prefix / "bin"
|
||||||
|
self.venv_dir = Path(venv_dir) if venv_dir else self.prefix / "lib" / "markitect"
|
||||||
|
|
||||||
|
self.project_dir = Path(__file__).parent.absolute()
|
||||||
|
|
||||||
|
def check_requirements(self):
|
||||||
|
"""Check system requirements."""
|
||||||
|
print("🔍 Checking system requirements...")
|
||||||
|
|
||||||
|
# Check Python version
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
print("❌ Python 3.8 or higher is required")
|
||||||
|
sys.exit(1)
|
||||||
|
print(f"✅ Python {sys.version.split()[0]} found")
|
||||||
|
|
||||||
|
# Check if pip is available
|
||||||
|
try:
|
||||||
|
subprocess.run([sys.executable, "-m", "pip", "--version"],
|
||||||
|
check=True, capture_output=True)
|
||||||
|
print("✅ pip is available")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print("❌ pip is not available. Please install pip first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check if git is available (optional)
|
||||||
|
try:
|
||||||
|
subprocess.run(["git", "--version"], check=True, capture_output=True)
|
||||||
|
print("✅ git is available")
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
print("⚠️ git is not available (optional for version info)")
|
||||||
|
|
||||||
|
def check_existing_installation(self):
|
||||||
|
"""Check if MarkiTect is already installed."""
|
||||||
|
# Check for existing venv
|
||||||
|
if self.venv_dir.exists():
|
||||||
|
print(f"📁 Existing installation found at {self.venv_dir}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check for existing binaries
|
||||||
|
markitect_bin = self.bin_dir / "markitect"
|
||||||
|
if markitect_bin.exists():
|
||||||
|
print(f"📁 Existing binary found at {markitect_bin}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_directories(self):
|
||||||
|
"""Create necessary directories."""
|
||||||
|
print(f"📁 Creating directories...")
|
||||||
|
|
||||||
|
if self.system and not os.access(self.prefix, os.W_OK):
|
||||||
|
print("❌ System installation requires sudo privileges")
|
||||||
|
print(" Please run with sudo or choose a different installation prefix")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
self.prefix.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.bin_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
print(f"✅ Created directories in {self.prefix}")
|
||||||
|
|
||||||
|
def create_virtual_environment(self):
|
||||||
|
"""Create and set up virtual environment."""
|
||||||
|
print(f"🐍 Creating virtual environment at {self.venv_dir}")
|
||||||
|
|
||||||
|
if self.venv_dir.exists():
|
||||||
|
if self.force:
|
||||||
|
print(f"🗑️ Removing existing installation...")
|
||||||
|
shutil.rmtree(self.venv_dir)
|
||||||
|
else:
|
||||||
|
print("❌ Virtual environment already exists. Use --force to overwrite.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create virtual environment
|
||||||
|
subprocess.run([
|
||||||
|
sys.executable, "-m", "venv", str(self.venv_dir)
|
||||||
|
], check=True)
|
||||||
|
|
||||||
|
# Get paths to venv executables
|
||||||
|
if sys.platform == "win32":
|
||||||
|
venv_python = self.venv_dir / "Scripts" / "python.exe"
|
||||||
|
venv_pip = self.venv_dir / "Scripts" / "pip.exe"
|
||||||
|
else:
|
||||||
|
venv_python = self.venv_dir / "bin" / "python"
|
||||||
|
venv_pip = self.venv_dir / "bin" / "pip"
|
||||||
|
|
||||||
|
# Upgrade pip
|
||||||
|
print("📦 Upgrading pip...")
|
||||||
|
subprocess.run([
|
||||||
|
str(venv_pip), "install", "--upgrade", "pip", "setuptools", "wheel"
|
||||||
|
], check=True)
|
||||||
|
|
||||||
|
return venv_python, venv_pip
|
||||||
|
|
||||||
|
def install_markitect(self, venv_python, venv_pip):
|
||||||
|
"""Install MarkiTect in the virtual environment."""
|
||||||
|
print("📦 Installing MarkiTect...")
|
||||||
|
|
||||||
|
install_cmd = [str(venv_pip), "install"]
|
||||||
|
|
||||||
|
if self.dev:
|
||||||
|
print("🛠️ Installing in development mode with test dependencies...")
|
||||||
|
# Install in editable mode from current directory
|
||||||
|
install_cmd.extend(["-e", str(self.project_dir)])
|
||||||
|
|
||||||
|
# Install test dependencies
|
||||||
|
subprocess.run(install_cmd, check=True)
|
||||||
|
subprocess.run([
|
||||||
|
str(venv_pip), "install", "pytest", "pytest-cov", "black", "flake8", "mypy"
|
||||||
|
], check=True)
|
||||||
|
else:
|
||||||
|
# Install from current directory
|
||||||
|
install_cmd.append(str(self.project_dir))
|
||||||
|
subprocess.run(install_cmd, check=True)
|
||||||
|
|
||||||
|
print("✅ MarkiTect installed successfully")
|
||||||
|
|
||||||
|
def create_symlinks(self, no_symlinks=False):
|
||||||
|
"""Create symbolic links for global access."""
|
||||||
|
if no_symlinks:
|
||||||
|
print("⚠️ Skipping symbolic link creation")
|
||||||
|
self.show_manual_setup()
|
||||||
|
return
|
||||||
|
|
||||||
|
print("🔗 Creating symbolic links...")
|
||||||
|
|
||||||
|
# Get venv bin directory
|
||||||
|
if sys.platform == "win32":
|
||||||
|
venv_bin = self.venv_dir / "Scripts"
|
||||||
|
exe_suffix = ".exe"
|
||||||
|
else:
|
||||||
|
venv_bin = self.venv_dir / "bin"
|
||||||
|
exe_suffix = ""
|
||||||
|
|
||||||
|
# Commands to link
|
||||||
|
commands = ["markitect", "tddai", "issue"]
|
||||||
|
|
||||||
|
for cmd in commands:
|
||||||
|
src = venv_bin / f"{cmd}{exe_suffix}"
|
||||||
|
dst = self.bin_dir / cmd
|
||||||
|
|
||||||
|
if src.exists():
|
||||||
|
# Remove existing symlink/file
|
||||||
|
if dst.exists() or dst.is_symlink():
|
||||||
|
dst.unlink()
|
||||||
|
|
||||||
|
# Create symlink
|
||||||
|
try:
|
||||||
|
dst.symlink_to(src)
|
||||||
|
print(f"✅ Created symlink: {dst} -> {src}")
|
||||||
|
except OSError:
|
||||||
|
# Fallback: create wrapper script
|
||||||
|
self.create_wrapper_script(dst, src)
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Command {cmd} not found in virtual environment")
|
||||||
|
|
||||||
|
def create_wrapper_script(self, dst, src):
|
||||||
|
"""Create a wrapper script when symlinks aren't available."""
|
||||||
|
print(f"🔧 Creating wrapper script: {dst}")
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
# Windows batch file
|
||||||
|
dst = dst.with_suffix(".bat")
|
||||||
|
content = f'@echo off\n"{src}" %*\n'
|
||||||
|
else:
|
||||||
|
# Unix shell script
|
||||||
|
content = f'#!/bin/bash\nexec "{src}" "$@"\n'
|
||||||
|
|
||||||
|
dst.write_text(content)
|
||||||
|
if sys.platform != "win32":
|
||||||
|
os.chmod(dst, 0o755)
|
||||||
|
|
||||||
|
def show_manual_setup(self):
|
||||||
|
"""Show manual PATH setup instructions."""
|
||||||
|
print("\n📋 Manual Setup Instructions:")
|
||||||
|
print("=" * 50)
|
||||||
|
print(f"Add the following to your PATH environment variable:")
|
||||||
|
print(f" {self.venv_dir / 'bin'}")
|
||||||
|
print()
|
||||||
|
print("For bash/zsh, add this line to ~/.bashrc or ~/.zshrc:")
|
||||||
|
print(f' export PATH="{self.venv_dir / "bin"}:$PATH"')
|
||||||
|
print()
|
||||||
|
|
||||||
|
def test_installation(self):
|
||||||
|
"""Test the installation."""
|
||||||
|
print("🧪 Testing installation...")
|
||||||
|
|
||||||
|
# Test markitect command
|
||||||
|
try:
|
||||||
|
markitect_bin = self.bin_dir / "markitect"
|
||||||
|
if not markitect_bin.exists():
|
||||||
|
# Try direct venv path
|
||||||
|
if sys.platform == "win32":
|
||||||
|
markitect_bin = self.venv_dir / "Scripts" / "markitect.exe"
|
||||||
|
else:
|
||||||
|
markitect_bin = self.venv_dir / "bin" / "markitect"
|
||||||
|
|
||||||
|
result = subprocess.run([
|
||||||
|
str(markitect_bin), "version", "--short"
|
||||||
|
], capture_output=True, text=True, check=True)
|
||||||
|
|
||||||
|
version = result.stdout.strip()
|
||||||
|
print(f"✅ MarkiTect installed successfully - version {version}")
|
||||||
|
return True
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
||||||
|
print(f"❌ Installation test failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def uninstall(self):
|
||||||
|
"""Uninstall MarkiTect."""
|
||||||
|
print("🗑️ Uninstalling MarkiTect...")
|
||||||
|
|
||||||
|
removed_something = False
|
||||||
|
|
||||||
|
# Remove virtual environment
|
||||||
|
if self.venv_dir.exists():
|
||||||
|
print(f"🗑️ Removing virtual environment: {self.venv_dir}")
|
||||||
|
shutil.rmtree(self.venv_dir)
|
||||||
|
removed_something = True
|
||||||
|
|
||||||
|
# Remove symlinks
|
||||||
|
commands = ["markitect", "tddai", "issue"]
|
||||||
|
for cmd in commands:
|
||||||
|
for bin_path in [self.bin_dir / cmd, self.bin_dir / f"{cmd}.bat"]:
|
||||||
|
if bin_path.exists() or bin_path.is_symlink():
|
||||||
|
print(f"🗑️ Removing: {bin_path}")
|
||||||
|
bin_path.unlink()
|
||||||
|
removed_something = True
|
||||||
|
|
||||||
|
if removed_something:
|
||||||
|
print("✅ MarkiTect uninstalled successfully")
|
||||||
|
else:
|
||||||
|
print("⚠️ No MarkiTect installation found")
|
||||||
|
|
||||||
|
def install(self, no_symlinks=False):
|
||||||
|
"""Perform the complete installation."""
|
||||||
|
print("🚀 Installing MarkiTect")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
self.check_requirements()
|
||||||
|
|
||||||
|
if not self.force and self.check_existing_installation():
|
||||||
|
print("❌ MarkiTect is already installed. Use --force to reinstall.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
self.create_directories()
|
||||||
|
venv_python, venv_pip = self.create_virtual_environment()
|
||||||
|
self.install_markitect(venv_python, venv_pip)
|
||||||
|
self.create_symlinks(no_symlinks)
|
||||||
|
|
||||||
|
print()
|
||||||
|
if self.test_installation():
|
||||||
|
print("🎉 Installation completed successfully!")
|
||||||
|
print()
|
||||||
|
print("You can now use MarkiTect from anywhere:")
|
||||||
|
print(" markitect --help")
|
||||||
|
print(" markitect version")
|
||||||
|
print(" tddai --help")
|
||||||
|
print(" issue --help")
|
||||||
|
else:
|
||||||
|
print("⚠️ Installation completed but tests failed")
|
||||||
|
self.show_manual_setup()
|
||||||
|
|
||||||
|
def check_installation_status(self):
|
||||||
|
"""Check current installation status."""
|
||||||
|
print("🔍 MarkiTect Installation Status")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Check virtual environment
|
||||||
|
if self.venv_dir.exists():
|
||||||
|
print(f"✅ Virtual environment: {self.venv_dir}")
|
||||||
|
else:
|
||||||
|
print(f"❌ Virtual environment: Not found at {self.venv_dir}")
|
||||||
|
|
||||||
|
# Check binaries
|
||||||
|
commands = ["markitect", "tddai", "issue"]
|
||||||
|
for cmd in commands:
|
||||||
|
bin_path = self.bin_dir / cmd
|
||||||
|
if bin_path.exists():
|
||||||
|
print(f"✅ {cmd}: {bin_path}")
|
||||||
|
else:
|
||||||
|
print(f"❌ {cmd}: Not found at {bin_path}")
|
||||||
|
|
||||||
|
# Try to get version
|
||||||
|
try:
|
||||||
|
result = subprocess.run([
|
||||||
|
"markitect", "version", "--short"
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
version = result.stdout.strip()
|
||||||
|
print(f"✅ Working installation: version {version}")
|
||||||
|
else:
|
||||||
|
print("❌ Installation found but not working")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("❌ markitect command not available in PATH")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="MarkiTect Installer",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog=__doc__.split('\n\n')[1] # Show usage from docstring
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument("--prefix", type=Path,
|
||||||
|
help="Installation prefix (default: ~/.local)")
|
||||||
|
parser.add_argument("--system", action="store_true",
|
||||||
|
help="Install system-wide (requires sudo)")
|
||||||
|
parser.add_argument("--venv-dir", type=Path,
|
||||||
|
help="Custom virtual environment directory")
|
||||||
|
parser.add_argument("--no-symlinks", action="store_true",
|
||||||
|
help="Don't create symbolic links")
|
||||||
|
parser.add_argument("--force", action="store_true",
|
||||||
|
help="Force reinstallation")
|
||||||
|
parser.add_argument("--dev", action="store_true",
|
||||||
|
help="Install in development mode")
|
||||||
|
parser.add_argument("--check", action="store_true",
|
||||||
|
help="Check installation status")
|
||||||
|
parser.add_argument("--uninstall", action="store_true",
|
||||||
|
help="Uninstall MarkiTect")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Create installer instance
|
||||||
|
installer = MarkiTectInstaller(
|
||||||
|
prefix=args.prefix,
|
||||||
|
system=args.system,
|
||||||
|
venv_dir=args.venv_dir,
|
||||||
|
force=args.force,
|
||||||
|
dev=args.dev
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle different actions
|
||||||
|
if args.check:
|
||||||
|
installer.check_installation_status()
|
||||||
|
elif args.uninstall:
|
||||||
|
installer.uninstall()
|
||||||
|
else:
|
||||||
|
installer.install(no_symlinks=args.no_symlinks)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
160
install.sh
Executable file
160
install.sh
Executable file
@@ -0,0 +1,160 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# MarkiTect Quick Installer
|
||||||
|
#
|
||||||
|
# This script provides a simple way to install MarkiTect.
|
||||||
|
# It's a wrapper around the Python installer script.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./install.sh [options]
|
||||||
|
# curl -sSL https://raw.githubusercontent.com/example/markitect/main/install.sh | bash
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --system Install system-wide (requires sudo)
|
||||||
|
# --dev Install in development mode
|
||||||
|
# --check Check installation status
|
||||||
|
# --uninstall Uninstall MarkiTect
|
||||||
|
# --help Show help
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Default options
|
||||||
|
SYSTEM=""
|
||||||
|
DEV=""
|
||||||
|
CHECK=""
|
||||||
|
UNINSTALL=""
|
||||||
|
HELP=""
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Function to print colored output
|
||||||
|
print_info() {
|
||||||
|
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_success() {
|
||||||
|
echo -e "${GREEN}✅ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}❌ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--system)
|
||||||
|
SYSTEM="--system"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--dev)
|
||||||
|
DEV="--dev"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--check)
|
||||||
|
CHECK="--check"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--uninstall)
|
||||||
|
UNINSTALL="--uninstall"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
HELP="--help"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_error "Unknown option: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Show help if requested
|
||||||
|
if [[ -n "$HELP" ]]; then
|
||||||
|
cat << EOF
|
||||||
|
MarkiTect Quick Installer
|
||||||
|
|
||||||
|
Usage: $0 [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--system Install system-wide (requires sudo)
|
||||||
|
--dev Install in development mode with test dependencies
|
||||||
|
--check Check current installation status
|
||||||
|
--uninstall Uninstall MarkiTect
|
||||||
|
--help Show this help message
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$0 # Install for current user
|
||||||
|
$0 --system # Install system-wide
|
||||||
|
$0 --dev # Install in development mode
|
||||||
|
$0 --check # Check installation status
|
||||||
|
$0 --uninstall # Uninstall MarkiTect
|
||||||
|
|
||||||
|
For more advanced options, use the Python installer directly:
|
||||||
|
python install.py --help
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if Python is available
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
print_error "Python 3 is required but not found"
|
||||||
|
print_info "Please install Python 3.8 or higher and try again"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Python version
|
||||||
|
python_version=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
|
||||||
|
required_version="3.8"
|
||||||
|
|
||||||
|
if ! python3 -c "import sys; sys.exit(0 if sys.version_info >= (3, 8) else 1)"; then
|
||||||
|
print_error "Python $required_version or higher is required (found: $python_version)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "Python $python_version found"
|
||||||
|
|
||||||
|
# Determine script directory
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
INSTALLER_SCRIPT="$SCRIPT_DIR/install.py"
|
||||||
|
|
||||||
|
# Check if installer script exists
|
||||||
|
if [[ ! -f "$INSTALLER_SCRIPT" ]]; then
|
||||||
|
print_error "Installer script not found: $INSTALLER_SCRIPT"
|
||||||
|
print_info "Make sure you're running this from the MarkiTect project directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build command
|
||||||
|
cmd="python3 $INSTALLER_SCRIPT"
|
||||||
|
|
||||||
|
if [[ -n "$SYSTEM" ]]; then
|
||||||
|
cmd="$cmd $SYSTEM"
|
||||||
|
print_warning "System installation requires sudo privileges"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$DEV" ]]; then
|
||||||
|
cmd="$cmd $DEV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$CHECK" ]]; then
|
||||||
|
cmd="$cmd $CHECK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$UNINSTALL" ]]; then
|
||||||
|
cmd="$cmd $UNINSTALL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run the installer
|
||||||
|
print_info "Running: $cmd"
|
||||||
|
exec $cmd
|
||||||
123
markitect/__version__.py
Normal file
123
markitect/__version__.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
"""
|
||||||
|
Version information for MarkiTect.
|
||||||
|
|
||||||
|
This module provides version and release information for the MarkiTect package.
|
||||||
|
Version information is sourced from pyproject.toml and git metadata when available.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
# Base version from pyproject.toml
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
|
||||||
|
def get_git_commit_hash() -> Optional[str]:
|
||||||
|
"""Get the current git commit hash if available."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'rev-parse', '--short', 'HEAD'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
cwd=Path(__file__).parent.parent
|
||||||
|
)
|
||||||
|
return result.stdout.strip()
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_git_branch() -> Optional[str]:
|
||||||
|
"""Get the current git branch if available."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'branch', '--show-current'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
cwd=Path(__file__).parent.parent
|
||||||
|
)
|
||||||
|
return result.stdout.strip()
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_git_tag() -> Optional[str]:
|
||||||
|
"""Get the current git tag if available."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'describe', '--tags', '--exact-match'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
cwd=Path(__file__).parent.parent
|
||||||
|
)
|
||||||
|
return result.stdout.strip()
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def is_development_version() -> bool:
|
||||||
|
"""Check if this is a development version (has uncommitted changes)."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'status', '--porcelain'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
cwd=Path(__file__).parent.parent
|
||||||
|
)
|
||||||
|
return bool(result.stdout.strip())
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_version_info() -> dict:
|
||||||
|
"""Get comprehensive version information."""
|
||||||
|
git_commit = get_git_commit_hash()
|
||||||
|
git_branch = get_git_branch()
|
||||||
|
git_tag = get_git_tag()
|
||||||
|
is_dev = is_development_version()
|
||||||
|
|
||||||
|
# Build version string
|
||||||
|
version_parts = [__version__]
|
||||||
|
|
||||||
|
if git_tag and git_tag != f"v{__version__}":
|
||||||
|
# If we have a different tag, use it
|
||||||
|
version_parts = [git_tag.lstrip('v')]
|
||||||
|
|
||||||
|
if git_commit:
|
||||||
|
if is_dev:
|
||||||
|
version_parts.append(f"dev+{git_commit}")
|
||||||
|
elif not git_tag:
|
||||||
|
version_parts.append(f"+{git_commit}")
|
||||||
|
|
||||||
|
if is_dev and not git_commit:
|
||||||
|
version_parts.append("dev")
|
||||||
|
|
||||||
|
full_version = ".".join(version_parts)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"version": __version__,
|
||||||
|
"full_version": full_version,
|
||||||
|
"git_commit": git_commit,
|
||||||
|
"git_branch": git_branch,
|
||||||
|
"git_tag": git_tag,
|
||||||
|
"is_development": is_dev,
|
||||||
|
"is_git_repo": git_commit is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_release_info() -> dict:
|
||||||
|
"""Get release information."""
|
||||||
|
version_info = get_version_info()
|
||||||
|
|
||||||
|
release_type = "development" if version_info["is_development"] else "release"
|
||||||
|
if version_info["git_tag"]:
|
||||||
|
release_type = "tagged-release"
|
||||||
|
elif version_info["git_commit"] and not version_info["is_development"]:
|
||||||
|
release_type = "commit-build"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"release_type": release_type,
|
||||||
|
"build_from": version_info["git_branch"] or "unknown",
|
||||||
|
"commit": version_info["git_commit"] or "unknown",
|
||||||
|
"clean_build": not version_info["is_development"],
|
||||||
|
**version_info
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import builtins
|
|||||||
|
|
||||||
from .database import DatabaseManager
|
from .database import DatabaseManager
|
||||||
from .legacy_compat import LegacyMode, emit_deprecation_warning, legacy_switch_option
|
from .legacy_compat import LegacyMode, emit_deprecation_warning, legacy_switch_option
|
||||||
|
from .__version__ import get_version_info, get_release_info
|
||||||
|
|
||||||
# Import legacy system components for advanced management
|
# Import legacy system components for advanced management
|
||||||
try:
|
try:
|
||||||
@@ -175,10 +176,20 @@ def format_output(data, output_format):
|
|||||||
return format_output(data, 'table')
|
return format_output(data, 'table')
|
||||||
|
|
||||||
|
|
||||||
|
def print_version(ctx, param, value):
|
||||||
|
"""Callback to print version and exit."""
|
||||||
|
if not value or ctx.resilient_parsing:
|
||||||
|
return
|
||||||
|
version_info = get_version_info()
|
||||||
|
click.echo(version_info['full_version'])
|
||||||
|
ctx.exit()
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
|
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
|
||||||
@click.option('--config', 'config_file', type=click.Path(exists=True), help='Configuration file path')
|
@click.option('--config', 'config_file', type=click.Path(exists=True), help='Configuration file path')
|
||||||
@click.option('--database', type=click.Path(), help='Database file path')
|
@click.option('--database', type=click.Path(), help='Database file path')
|
||||||
|
@click.option('--version', is_flag=True, expose_value=False, is_eager=True,
|
||||||
|
callback=print_version, help='Show version and exit')
|
||||||
@pass_config
|
@pass_config
|
||||||
def cli(config, verbose, database, config_file):
|
def cli(config, verbose, database, config_file):
|
||||||
"""
|
"""
|
||||||
@@ -218,6 +229,63 @@ def cli(config, verbose, database, config_file):
|
|||||||
|
|
||||||
# Issue management commands removed - use dedicated 'issue' CLI or 'tddai' CLI instead
|
# Issue management commands removed - use dedicated 'issue' CLI or 'tddai' CLI instead
|
||||||
|
|
||||||
|
# Version and release information commands
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.option('--short', is_flag=True, help='Show only version number')
|
||||||
|
def version(short):
|
||||||
|
"""Show MarkiTect version information."""
|
||||||
|
version_info = get_version_info()
|
||||||
|
|
||||||
|
if short:
|
||||||
|
click.echo(version_info['full_version'])
|
||||||
|
else:
|
||||||
|
click.echo("MarkiTect Version Information")
|
||||||
|
click.echo("============================")
|
||||||
|
click.echo(f"Version: {version_info['full_version']}")
|
||||||
|
click.echo(f"Base Version: {version_info['version']}")
|
||||||
|
|
||||||
|
if version_info['is_git_repo']:
|
||||||
|
click.echo(f"Git Commit: {version_info['git_commit'] or 'N/A'}")
|
||||||
|
click.echo(f"Git Branch: {version_info['git_branch'] or 'N/A'}")
|
||||||
|
if version_info['git_tag']:
|
||||||
|
click.echo(f"Git Tag: {version_info['git_tag']}")
|
||||||
|
click.echo(f"Development Build: {'Yes' if version_info['is_development'] else 'No'}")
|
||||||
|
else:
|
||||||
|
click.echo("Git Repository: Not available")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.option('--format', 'output_format', default='text',
|
||||||
|
type=click.Choice(['text', 'json', 'yaml']),
|
||||||
|
help='Output format (text, json, yaml)')
|
||||||
|
def release(output_format):
|
||||||
|
"""Show MarkiTect release information."""
|
||||||
|
release_info = get_release_info()
|
||||||
|
|
||||||
|
if output_format == 'json':
|
||||||
|
import json
|
||||||
|
click.echo(json.dumps(release_info, indent=2))
|
||||||
|
elif output_format == 'yaml':
|
||||||
|
import yaml
|
||||||
|
click.echo(yaml.dump(release_info, default_flow_style=False))
|
||||||
|
else:
|
||||||
|
# Text format
|
||||||
|
click.echo("MarkiTect Release Information")
|
||||||
|
click.echo("============================")
|
||||||
|
click.echo(f"Version: {release_info['full_version']}")
|
||||||
|
click.echo(f"Release Type: {release_info['release_type']}")
|
||||||
|
click.echo(f"Build From: {release_info['build_from']}")
|
||||||
|
click.echo(f"Commit: {release_info['commit']}")
|
||||||
|
click.echo(f"Clean Build: {'Yes' if release_info['clean_build'] else 'No'}")
|
||||||
|
|
||||||
|
if release_info['is_git_repo']:
|
||||||
|
click.echo(f"Git Repository: Available")
|
||||||
|
if release_info['git_tag']:
|
||||||
|
click.echo(f"Tagged Release: {release_info['git_tag']}")
|
||||||
|
else:
|
||||||
|
click.echo("Git Repository: Not available")
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.argument('file_path', type=click.Path(exists=True))
|
@click.argument('file_path', type=click.Path(exists=True))
|
||||||
|
|||||||
Reference in New Issue
Block a user