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 .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
|
||||
try:
|
||||
@@ -175,10 +176,20 @@ def format_output(data, output_format):
|
||||
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.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('--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
|
||||
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
|
||||
|
||||
# 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()
|
||||
@click.argument('file_path', type=click.Path(exists=True))
|
||||
|
||||
Reference in New Issue
Block a user