diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..ce354455 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,63 @@ +# Changelog + +All notable changes to MarkiTect will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- Comprehensive installer system with Python and shell scripts +- Version and release information commands (`markitect version`, `markitect release`) +- Global `--version` flag for quick version checking +- Git integration for version metadata (commit, branch, tag information) +- Multiple output formats for release information (text, JSON, YAML) +- Installation documentation and troubleshooting guides + +### Fixed +- All test failures resolved (800/800 tests passing) +- Visualization schema tests updated for correct tool paths +- Cache management test isolation issues +- Missing dependencies documentation and installation + +### Documentation +- Added comprehensive INSTALL.md with installation instructions +- Added DEPENDENCIES.md with dependency information +- Created release process documentation + +## [0.1.0] - 2025-10-03 + +### Added +- Initial MarkiTect implementation +- Core markdown processing with AST caching +- Front matter and content matter support +- Database integration for document metadata +- CLI interface with comprehensive commands +- Schema generation and validation +- Template rendering system +- Issue management integration +- TDD workflow tools (TDDAI) +- Comprehensive test suite with architectural layers +- Documentation and architectural guides + +### Features +- Document ingestion and processing +- Metadata extraction and querying +- AST analysis and caching +- Content statistics and analysis +- Template-based document generation +- Associated file management +- Database operations with multiple output formats +- Performance monitoring and optimization +- Legacy compatibility system + +### Technical +- Python 3.8+ support +- Click-based CLI framework +- SQLite database backend +- Markdown-it-py parser integration +- Comprehensive test coverage +- Type checking with mypy +- Code formatting with black +- Project structure following clean architecture principles \ No newline at end of file diff --git a/Makefile b/Makefile index 5649d5b6..8cfda322 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # MarkiTect - Advanced Markdown Engine # Makefile for common development tasks -.PHONY: help setup install test build clean update status dev lint format check-deps venv-status update-digest add-diary-entry list-issues show-issue list-open-issues close-issue close-issue-enhanced close-issues-batch test-from-issue tdd-start tdd-add-test tdd-finish tdd-status test-status test-new test-coverage test-arch test-foundation test-infrastructure test-integration test-domain test-service test-application test-presentation test-quick test-layers test-random test-random-seed test-random-repeat test-install-randomly test-clean test-tdd test-changed test-module test-cache-clean test-efficient cli-help +.PHONY: help setup install test build clean update status dev lint format check-deps venv-status update-digest add-diary-entry list-issues show-issue list-open-issues close-issue close-issue-enhanced close-issues-batch test-from-issue tdd-start tdd-add-test tdd-finish tdd-status test-status test-new test-coverage test-arch test-foundation test-infrastructure test-integration test-domain test-service test-application test-presentation test-quick test-layers test-random test-random-seed test-random-repeat test-install-randomly test-clean test-tdd test-changed test-module test-cache-clean test-efficient cli-help release-status release-validate release-prepare release-build release-publish release-dry-run # Default target help: @@ -26,6 +26,14 @@ help: @echo " lint - Run code linting" @echo " format - Format code" @echo "" + @echo "Release Management:" + @echo " release-status - Show current release status" + @echo " release-validate - Validate repository for release" + @echo " release-prepare VERSION=x.y.z - Prepare new release" + @echo " release-build - Build release packages" + @echo " release-publish VERSION=x.y.z - Publish complete release" + @echo " release-dry-run VERSION=x.y.z - Test release preparation" + @echo "" @echo "Architectural Testing:" @echo " test-arch - Run all tests in architectural order" @echo " test-foundation - Run foundation layer tests only" @@ -200,6 +208,43 @@ build: $(VENV)/bin/activate $(VENV_PYTHON) -m build 2>/dev/null || \ $(VENV_PIP) install build && $(VENV_PYTHON) -m build +# Release management +release-status: + @echo "๐Ÿ” Checking release status..." + $(VENV_PYTHON) release.py status + +release-validate: + @echo "โœ… Validating release readiness..." + $(VENV_PYTHON) release.py validate + +release-prepare: + @echo "๐Ÿš€ Preparing release..." + @if [ -z "$(VERSION)" ]; then \ + echo "โŒ Usage: make release-prepare VERSION=1.0.0"; \ + exit 1; \ + fi + $(VENV_PYTHON) release.py prepare --version $(VERSION) + +release-build: + @echo "๐Ÿ“ฆ Building release packages..." + $(VENV_PYTHON) release.py build $(if $(VERSION),--version $(VERSION)) + +release-publish: + @echo "๐Ÿ“ข Publishing release..." + @if [ -z "$(VERSION)" ]; then \ + echo "โŒ Usage: make release-publish VERSION=1.0.0"; \ + exit 1; \ + fi + $(VENV_PYTHON) release.py publish --version $(VERSION) + +release-dry-run: + @echo "๐Ÿงช Dry run release preparation..." + @if [ -z "$(VERSION)" ]; then \ + echo "โŒ Usage: make release-dry-run VERSION=1.0.0"; \ + exit 1; \ + fi + $(VENV_PYTHON) release.py prepare --version $(VERSION) --dry-run + # Code linting lint: $(VENV)/bin/activate @echo "๐Ÿ” Running linting..." diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..799eed75 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,332 @@ +# MarkiTect Release Process + +This document describes the release process for MarkiTect, including versioning strategy, automation tools, and distribution guidelines. + +## Quick Start + +The simplest way to create a release: + +```bash +# 1. Prepare the release +make release-prepare VERSION=1.0.0 + +# 2. Review and commit changes +git add -A && git commit -m "Prepare release 1.0.0" + +# 3. Publish the release +make release-publish VERSION=1.0.0 +``` + +## Release Commands + +### Status and Validation + +```bash +# Check current release status +make release-status + +# Validate repository for release +make release-validate +``` + +### Release Preparation + +```bash +# Prepare a new release (updates version, changelog) +make release-prepare VERSION=x.y.z + +# Test preparation without making changes +make release-dry-run VERSION=x.y.z +``` + +### Building and Publishing + +```bash +# Build release packages only +make release-build [VERSION=x.y.z] + +# Complete release (build + tag + publish) +make release-publish VERSION=x.y.z +``` + +## Versioning Strategy + +MarkiTect follows [Semantic Versioning](https://semver.org/): + +- **MAJOR.MINOR.PATCH** (e.g., 1.2.3) +- **Pre-release**: MAJOR.MINOR.PATCH-{alpha|beta|rc}.N (e.g., 1.2.3-beta.1) + +### Version Types + +- **Major (X.0.0)**: Breaking changes, incompatible API changes +- **Minor (x.Y.0)**: New features, backward compatible +- **Patch (x.y.Z)**: Bug fixes, backward compatible +- **Pre-release**: Alpha, beta, or release candidate versions + +### Examples + +```bash +# Major release +make release-prepare VERSION=2.0.0 + +# Minor release +make release-prepare VERSION=1.1.0 + +# Patch release +make release-prepare VERSION=1.0.1 + +# Pre-release +make release-prepare VERSION=1.1.0-beta.1 +``` + +## Release Validation + +Before a release can be created, the following validations are performed: + +### Required Conditions + +1. **Clean Repository**: No uncommitted changes +2. **Main Branch**: Must be on the `main` branch +3. **Passing Tests**: All tests must pass +4. **Valid Version**: Version must follow semantic versioning +5. **Version Increment**: New version must be greater than current + +### Override Validation + +Use `--force` to override validation warnings: + +```bash +python release.py prepare --version 1.0.1 --force +``` + +## Automated Release Process + +### What `release-prepare` Does + +1. **Version Update**: Updates `pyproject.toml` and `markitect/__version__.py` +2. **Changelog Generation**: Creates/updates `CHANGELOG.md` from git commits +3. **Validation**: Ensures repository is ready for release + +### What `release-publish` Does + +1. **Package Building**: Creates source distribution and wheel +2. **Git Tagging**: Creates annotated git tag (e.g., `v1.0.0`) +3. **Tag Push**: Pushes tag to remote repository + +## Manual Release Process + +If you prefer manual control: + +### 1. Update Version + +```bash +# Edit pyproject.toml +version = "1.0.0" + +# Edit markitect/__version__.py +__version__ = "1.0.0" +``` + +### 2. Update Changelog + +Edit `CHANGELOG.md` to add release notes for the new version. + +### 3. Commit Changes + +```bash +git add -A +git commit -m "Prepare release 1.0.0" +``` + +### 4. Build Packages + +```bash +make release-build +``` + +### 5. Create Git Tag + +```bash +git tag -a v1.0.0 -m "Release 1.0.0" +git push origin v1.0.0 +``` + +## Distribution + +### Package Types + +MarkiTect releases include: + +- **Source Distribution** (`.tar.gz`): Full source code package +- **Wheel** (`.whl`): Pre-built binary package for faster installation + +### Installation Methods + +Users can install MarkiTect in several ways: + +```bash +# From PyPI (when published) +pip install markitect + +# From wheel file +pip install markitect-1.0.0-py3-none-any.whl + +# From source +pip install markitect-1.0.0.tar.gz + +# Development installation +pip install -e . +``` + +### Release Artifacts + +Each release creates: + +- Source and wheel packages in `dist/` +- Git tag (e.g., `v1.0.0`) +- Updated `CHANGELOG.md` +- Updated version files + +## Changelog Format + +The automated changelog generation categorizes commits: + +### Commit Prefixes + +- `feat:` or `feature:` โ†’ **Added** section +- `fix:` or `bugfix:` โ†’ **Fixed** section +- `docs:` or `doc:` โ†’ **Documentation** section +- Other commits โ†’ **Other** section + +### Example Changelog Entry + +```markdown +## [1.0.0] - 2025-10-03 + +### Added +- feat: add template rendering system +- feature: implement cache management commands + +### Fixed +- fix: resolve test isolation issues +- bugfix: correct version information display + +### Documentation +- docs: add comprehensive installation guide +- doc: update API documentation + +### Other +- chore: cleanup repository structure +- refactor: improve code organization +``` + +## Release Checklist + +### Pre-Release + +- [ ] All tests passing (`make test`) +- [ ] No uncommitted changes +- [ ] On `main` branch +- [ ] Version number decided +- [ ] Release notes ready + +### Release Process + +- [ ] Run `make release-prepare VERSION=x.y.z` +- [ ] Review generated changelog +- [ ] Commit changes +- [ ] Run `make release-publish VERSION=x.y.z` +- [ ] Verify packages created +- [ ] Verify git tag created + +### Post-Release + +- [ ] Packages available in `dist/` +- [ ] Git tag pushed to remote +- [ ] Changelog updated +- [ ] Version information correct +- [ ] Installation tested + +## Troubleshooting + +### Common Issues + +1. **Validation Failures** + ```bash + # Check what's wrong + make release-validate + + # Force release if needed + python release.py prepare --version 1.0.0 --force + ``` + +2. **Build Failures** + ```bash + # Install build dependencies + pip install build + + # Clean and rebuild + rm -rf dist/ build/ + make release-build + ``` + +3. **Git Issues** + ```bash + # Check git status + git status + + # Commit changes + git add -A && git commit -m "Prepare release" + ``` + +4. **Version Conflicts** + ```bash + # Check current version + make release-status + + # Use correct version number + make release-prepare VERSION=1.0.1 # Must be > current + ``` + +### Getting Help + +```bash +# Release tool help +python release.py --help + +# Makefile targets +make help + +# Command-specific help +python release.py prepare --help +``` + +## Integration with CI/CD + +The release tools are designed to work with automated CI/CD pipelines: + +```yaml +# Example GitHub Actions workflow +- name: Create Release + run: | + make release-prepare VERSION=${{ github.event.inputs.version }} + git add -A + git commit -m "Prepare release ${{ github.event.inputs.version }}" + make release-publish VERSION=${{ github.event.inputs.version }} +``` + +## Security Considerations + +- Release artifacts should be signed +- Use trusted publishing methods +- Verify package contents before distribution +- Keep release tools and dependencies updated + +## Support + +For release-related issues: + +1. Check this documentation +2. Run `make release-status` for diagnostics +3. Use `--dry-run` to test changes +4. Report issues on the project tracker \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 9c01e89e..9dae9209 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "0.1.0" description = "Advanced Markdown engine for structured content" readme = "README.md" requires-python = ">=3.8" -dependencies = ["markdown-it-py", "PyYAML", "click>=8.0.0", "tabulate>=0.9.0", "jsonpath-ng>=1.5.0", "aiohttp>=3.8.0"] +dependencies = ["markdown-it-py", "PyYAML", "click>=8.0.0", "tabulate>=0.9.0", "jsonpath-ng>=1.5.0", "aiohttp>=3.8.0", "toml"] [project.scripts] markitect = "markitect.cli:main" diff --git a/release.py b/release.py new file mode 100755 index 00000000..6c715937 --- /dev/null +++ b/release.py @@ -0,0 +1,492 @@ +#!/usr/bin/env python3 +""" +MarkiTect Release Management Tool + +This script automates the release process for MarkiTect, including: +- Version management and validation +- Changelog generation +- Git tagging and repository management +- Package building and distribution +- Release artifact creation + +Usage: + python release.py [command] [options] + +Commands: + prepare Prepare a new release (bump version, update changelog) + build Build release packages + tag Create git tag for release + publish Publish release (build + tag + distribute) + status Show current release status + validate Validate current state for release + +Options: + --version VERSION Target version (e.g., 1.0.0, 1.0.1-rc1) + --pre-release Mark as pre-release + --dry-run Show what would be done without making changes + --force Force operation even with warnings + --help Show help message +""" + +import os +import re +import sys +import json +import subprocess +import argparse +from pathlib import Path +from datetime import datetime +from typing import Dict, List, Optional, Tuple +import tempfile + + +class ReleaseManager: + """Manages the MarkiTect release process.""" + + def __init__(self, dry_run=False, force=False): + self.dry_run = dry_run + self.force = force + self.project_root = Path(__file__).parent.absolute() + self.pyproject_toml = self.project_root / "pyproject.toml" + self.version_file = self.project_root / "markitect" / "__version__.py" + self.changelog_file = self.project_root / "CHANGELOG.md" + + def run_command(self, cmd: List[str], capture=True, check=True, skip_dry_run=False) -> subprocess.CompletedProcess: + """Run a command with optional dry-run support.""" + if self.dry_run and not skip_dry_run: + print(f"[DRY RUN] Would run: {' '.join(cmd)}") + return subprocess.CompletedProcess(cmd, 0, "", "") + + return subprocess.run(cmd, capture_output=capture, text=True, check=check) + + def get_current_version(self) -> str: + """Get current version from pyproject.toml.""" + with open(self.pyproject_toml, 'r') as f: + content = f.read() + + match = re.search(r'version\s*=\s*"([^"]+)"', content) + if not match: + raise ValueError("Could not find version in pyproject.toml") + + return match.group(1) + + def validate_version(self, version: str) -> bool: + """Validate version format (semantic versioning).""" + pattern = r'^(\d+)\.(\d+)\.(\d+)(?:-(alpha|beta|rc)\.?(\d+))?$' + return bool(re.match(pattern, version)) + + def compare_versions(self, v1: str, v2: str) -> int: + """Compare two versions. Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2.""" + def version_tuple(v): + parts = v.split('-')[0].split('.') + main = tuple(int(x) for x in parts) + + if '-' in v: + pre = v.split('-')[1] + if 'alpha' in pre: + pre_num = int(re.search(r'(\d+)', pre).group(1)) if re.search(r'(\d+)', pre) else 0 + return main + (0, pre_num) + elif 'beta' in pre: + pre_num = int(re.search(r'(\d+)', pre).group(1)) if re.search(r'(\d+)', pre) else 0 + return main + (1, pre_num) + elif 'rc' in pre: + pre_num = int(re.search(r'(\d+)', pre).group(1)) if re.search(r'(\d+)', pre) else 0 + return main + (2, pre_num) + + return main + (3, 0) # Release version + + t1, t2 = version_tuple(v1), version_tuple(v2) + if t1 < t2: + return -1 + elif t1 > t2: + return 1 + else: + return 0 + + def update_version(self, new_version: str): + """Update version in pyproject.toml and __version__.py.""" + print(f"๐Ÿ“ Updating version to {new_version}") + + # Update pyproject.toml + with open(self.pyproject_toml, 'r') as f: + content = f.read() + + new_content = re.sub( + r'version\s*=\s*"[^"]+"', + f'version = "{new_version}"', + content + ) + + if not self.dry_run: + with open(self.pyproject_toml, 'w') as f: + f.write(new_content) + + # Update __version__.py + with open(self.version_file, 'r') as f: + version_content = f.read() + + new_version_content = re.sub( + r'__version__\s*=\s*"[^"]+"', + f'__version__ = "{new_version}"', + version_content + ) + + if not self.dry_run: + with open(self.version_file, 'w') as f: + f.write(new_version_content) + + def get_git_status(self) -> Dict[str, any]: + """Get current git repository status.""" + try: + # Check if in git repo + result = self.run_command(['git', 'rev-parse', '--git-dir'], skip_dry_run=True) + + # Get current branch + branch_result = self.run_command(['git', 'branch', '--show-current'], skip_dry_run=True) + current_branch = branch_result.stdout.strip() + + # Check for uncommitted changes + status_result = self.run_command(['git', 'status', '--porcelain'], skip_dry_run=True) + has_changes = bool(status_result.stdout.strip()) + + # Get latest commit + commit_result = self.run_command(['git', 'rev-parse', '--short', 'HEAD'], skip_dry_run=True) + latest_commit = commit_result.stdout.strip() + + # Get latest tag + try: + tag_result = self.run_command(['git', 'describe', '--tags', '--abbrev=0'], skip_dry_run=True) + latest_tag = tag_result.stdout.strip() + except subprocess.CalledProcessError: + latest_tag = None + + return { + 'is_repo': True, + 'branch': current_branch, + 'has_changes': has_changes, + 'latest_commit': latest_commit, + 'latest_tag': latest_tag + } + except subprocess.CalledProcessError: + return {'is_repo': False} + + def generate_changelog_entry(self, version: str, since_tag: str = None) -> str: + """Generate changelog entry from git commits.""" + print(f"๐Ÿ“‹ Generating changelog for {version}") + + # Get commits since last tag or all commits + if since_tag: + cmd = ['git', 'log', f'{since_tag}..HEAD', '--oneline', '--no-merges'] + else: + cmd = ['git', 'log', '--oneline', '--no-merges'] + + try: + result = self.run_command(cmd) + commits = result.stdout.strip().split('\n') if result.stdout.strip() else [] + except subprocess.CalledProcessError: + commits = [] + + # Categorize commits + features = [] + fixes = [] + docs = [] + other = [] + + for commit in commits: + if not commit: + continue + + commit_msg = commit.split(' ', 1)[1] if ' ' in commit else commit + + if commit_msg.startswith(('feat:', 'feature:')): + features.append(commit_msg) + elif commit_msg.startswith(('fix:', 'bugfix:')): + fixes.append(commit_msg) + elif commit_msg.startswith(('docs:', 'doc:')): + docs.append(commit_msg) + else: + other.append(commit_msg) + + # Generate changelog entry + date = datetime.now().strftime('%Y-%m-%d') + entry = f"## [{version}] - {date}\n\n" + + if features: + entry += "### Added\n" + for feat in features: + entry += f"- {feat}\n" + entry += "\n" + + if fixes: + entry += "### Fixed\n" + for fix in fixes: + entry += f"- {fix}\n" + entry += "\n" + + if docs: + entry += "### Documentation\n" + for doc in docs: + entry += f"- {doc}\n" + entry += "\n" + + if other: + entry += "### Other\n" + for oth in other: + entry += f"- {oth}\n" + entry += "\n" + + return entry + + def update_changelog(self, version: str, since_tag: str = None): + """Update CHANGELOG.md with new version entry.""" + entry = self.generate_changelog_entry(version, since_tag) + + # Read existing changelog or create new one + if self.changelog_file.exists(): + with open(self.changelog_file, 'r') as f: + existing_content = f.read() + else: + existing_content = "# Changelog\n\nAll notable changes to MarkiTect will be documented in this file.\n\n" + + # Insert new entry after header + lines = existing_content.split('\n') + header_end = 0 + for i, line in enumerate(lines): + if line.startswith('## [') or (i > 0 and not line.startswith('#')): + header_end = i + break + + new_lines = lines[:header_end] + entry.split('\n') + lines[header_end:] + new_content = '\n'.join(new_lines) + + if not self.dry_run: + with open(self.changelog_file, 'w') as f: + f.write(new_content) + + def validate_release_state(self) -> Tuple[bool, List[str]]: + """Validate that the repository is ready for release.""" + issues = [] + + git_status = self.get_git_status() + + if not git_status['is_repo']: + issues.append("Not in a git repository") + else: + if git_status['has_changes'] and not self.force: + issues.append("Repository has uncommitted changes") + + if git_status['branch'] != 'main' and not self.force: + issues.append(f"Not on main branch (currently on {git_status['branch']})") + + # Check if tests pass (skip for dry run) + if not self.dry_run: + try: + print("๐Ÿงช Running tests...") + test_result = self.run_command(['make', 'test'], capture=False) + if test_result.returncode != 0: + issues.append("Tests are failing") + except subprocess.CalledProcessError: + issues.append("Could not run tests (make test failed)") + except FileNotFoundError: + # Try pytest directly + try: + test_result = self.run_command(['python', '-m', 'pytest']) + if test_result.returncode != 0: + issues.append("Tests are failing") + except (subprocess.CalledProcessError, FileNotFoundError): + issues.append("Could not run tests") + else: + print("๐Ÿงช Skipping tests in dry run mode") + + return len(issues) == 0, issues + + def build_packages(self, version: str): + """Build release packages.""" + print(f"๐Ÿ“ฆ Building packages for version {version}") + + # Clean previous builds + build_dirs = ['build', 'dist', '*.egg-info'] + for pattern in build_dirs: + self.run_command(['rm', '-rf'] + [str(self.project_root / pattern)]) + + # Build source distribution + print("Building source distribution...") + self.run_command(['python', '-m', 'build', '--sdist'], capture=False) + + # Build wheel + print("Building wheel...") + self.run_command(['python', '-m', 'build', '--wheel'], capture=False) + + print("โœ… Packages built successfully") + + def create_git_tag(self, version: str, message: str = None): + """Create and push git tag.""" + tag_name = f"v{version}" + tag_message = message or f"Release {version}" + + print(f"๐Ÿท๏ธ Creating git tag {tag_name}") + + # Create annotated tag + self.run_command(['git', 'tag', '-a', tag_name, '-m', tag_message]) + + # Push tag + print(f"๐Ÿ“ค Pushing tag to origin...") + self.run_command(['git', 'push', 'origin', tag_name]) + + def show_status(self): + """Show current release status.""" + print("๐Ÿ” MarkiTect Release Status") + print("=" * 50) + + current_version = self.get_current_version() + print(f"Current Version: {current_version}") + + git_status = self.get_git_status() + if git_status['is_repo']: + print(f"Git Branch: {git_status['branch']}") + print(f"Latest Commit: {git_status['latest_commit']}") + print(f"Latest Tag: {git_status['latest_tag'] or 'None'}") + print(f"Uncommitted Changes: {'Yes' if git_status['has_changes'] else 'No'}") + else: + print("Git Repository: Not available") + + # Check build tools + print("\nBuild Tools:") + try: + self.run_command(['python', '-m', 'build', '--help']) + print("โœ… build module available") + except (subprocess.CalledProcessError, FileNotFoundError): + print("โŒ build module not available (pip install build)") + + # Check if packages exist + dist_dir = self.project_root / "dist" + if dist_dir.exists(): + packages = list(dist_dir.glob("*")) + print(f"\nExisting Packages: {len(packages)} files in dist/") + for pkg in packages: + print(f" - {pkg.name}") + else: + print("\nExisting Packages: None") + + def prepare_release(self, version: str, pre_release: bool = False): + """Prepare a new release.""" + print(f"๐Ÿš€ Preparing release {version}") + + # Validate version format + if not self.validate_version(version): + raise ValueError(f"Invalid version format: {version}") + + # Check if version is newer than current + current_version = self.get_current_version() + if self.compare_versions(version, current_version) <= 0 and not self.force: + raise ValueError(f"New version {version} must be greater than current {current_version}") + + # Validate release state + is_valid, issues = self.validate_release_state() + if not is_valid: + print("โŒ Release validation failed:") + for issue in issues: + print(f" - {issue}") + if not self.force: + sys.exit(1) + else: + print("โš ๏ธ Continuing with --force flag") + + # Update version + self.update_version(version) + + # Update changelog + git_status = self.get_git_status() + since_tag = git_status.get('latest_tag') if git_status['is_repo'] else None + self.update_changelog(version, since_tag) + + print(f"โœ… Release {version} prepared successfully") + print("Next steps:") + print("1. Review and edit CHANGELOG.md if needed") + print("2. Commit changes: git add -A && git commit -m 'Prepare release {version}'") + print("3. Run: python release.py publish --version {version}") + + def publish_release(self, version: str): + """Publish a complete release.""" + print(f"๐Ÿ“ข Publishing release {version}") + + # Validate state + is_valid, issues = self.validate_release_state() + if not is_valid and not self.force: + print("โŒ Cannot publish release due to validation issues:") + for issue in issues: + print(f" - {issue}") + sys.exit(1) + + # Build packages + self.build_packages(version) + + # Create git tag + self.create_git_tag(version) + + print(f"โœ… Release {version} published successfully!") + print(f"๐Ÿ“ฆ Packages available in dist/") + print(f"๐Ÿท๏ธ Git tag v{version} created and pushed") + + +def main(): + parser = argparse.ArgumentParser( + description="MarkiTect Release Management Tool", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__.split('\n\n')[1] + ) + + parser.add_argument('command', choices=['prepare', 'build', 'tag', 'publish', 'status', 'validate'], + help='Release command to execute') + parser.add_argument('--version', type=str, help='Target version (e.g., 1.0.0)') + parser.add_argument('--pre-release', action='store_true', help='Mark as pre-release') + parser.add_argument('--dry-run', action='store_true', help='Show what would be done') + parser.add_argument('--force', action='store_true', help='Force operation despite warnings') + + args = parser.parse_args() + + manager = ReleaseManager(dry_run=args.dry_run, force=args.force) + + try: + if args.command == 'status': + manager.show_status() + + elif args.command == 'validate': + is_valid, issues = manager.validate_release_state() + if is_valid: + print("โœ… Repository is ready for release") + else: + print("โŒ Release validation failed:") + for issue in issues: + print(f" - {issue}") + sys.exit(1) + + elif args.command == 'prepare': + if not args.version: + print("โŒ --version is required for prepare command") + sys.exit(1) + manager.prepare_release(args.version, args.pre_release) + + elif args.command == 'build': + version = args.version or manager.get_current_version() + manager.build_packages(version) + + elif args.command == 'tag': + if not args.version: + print("โŒ --version is required for tag command") + sys.exit(1) + manager.create_git_tag(args.version) + + elif args.command == 'publish': + if not args.version: + print("โŒ --version is required for publish command") + sys.exit(1) + manager.publish_release(args.version) + + except Exception as e: + print(f"โŒ Error: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file