From 8e6ba272caa3be25076282a6d917f54e3df8f0c3 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 3 Oct 2025 05:47:02 +0200 Subject: [PATCH] feat: implement markitect installer with version/release commands (issue #80) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .clinerules | 279 ++++++++++++++++++++++++++++ INSTALL.md | 219 ++++++++++++++++++++++ install.py | 388 +++++++++++++++++++++++++++++++++++++++ install.sh | 160 ++++++++++++++++ markitect/__version__.py | 123 +++++++++++++ markitect/cli.py | 68 +++++++ 6 files changed, 1237 insertions(+) create mode 100644 .clinerules create mode 100644 INSTALL.md create mode 100644 install.py create mode 100755 install.sh create mode 100644 markitect/__version__.py diff --git a/.clinerules b/.clinerules new file mode 100644 index 00000000..1873d2c8 --- /dev/null +++ b/.clinerules @@ -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= + +# 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. \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000..1e3cfcf0 --- /dev/null +++ b/INSTALL.md @@ -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 \ No newline at end of file diff --git a/install.py b/install.py new file mode 100644 index 00000000..a4a7cb1f --- /dev/null +++ b/install.py @@ -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() \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 00000000..4e8eefbd --- /dev/null +++ b/install.sh @@ -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 \ No newline at end of file diff --git a/markitect/__version__.py b/markitect/__version__.py new file mode 100644 index 00000000..781cb7c9 --- /dev/null +++ b/markitect/__version__.py @@ -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 + } \ No newline at end of file diff --git a/markitect/cli.py b/markitect/cli.py index e1061498..d4defb76 100644 --- a/markitect/cli.py +++ b/markitect/cli.py @@ -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))