feat: implement comprehensive release process and automation (issue #81)

- Add complete release automation script (release.py) with version management
- Add semantic versioning validation and git integration
- Create automated changelog generation from git commits
- Add comprehensive Makefile targets for release workflow
- Set up package building with source and wheel distributions
- Add git tagging and repository management
- Create extensive release documentation (RELEASE.md)
- Add CHANGELOG.md with standardized format
- Update dependencies in pyproject.toml (add toml package)

Release commands added:
- make release-status - Show current release status
- make release-validate - Validate repository for release
- make release-prepare VERSION=x.y.z - Prepare new release
- make release-build - Build release packages
- make release-publish VERSION=x.y.z - Complete release workflow
- make release-dry-run VERSION=x.y.z - Test release preparation

Features:
- Semantic versioning with pre-release support
- Automated version updates across files
- Git status validation and branch checking
- Test execution validation
- Package building with build tool integration
- Git tagging with proper annotations
- Comprehensive error handling and validation

Resolves #81

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-03 06:07:10 +02:00
parent 8e6ba272ca
commit 9270a2e353
5 changed files with 934 additions and 2 deletions

63
CHANGELOG.md Normal file
View File

@@ -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

View File

@@ -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..."

332
RELEASE.md Normal file
View File

@@ -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

View File

@@ -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"

492
release.py Executable file
View File

@@ -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()