Files
markitect-main/capabilities/release-management/src/release_management/cli/main.py
tegwick d0ffdc057c feat: implement modular capability system with automatic discovery
- Move release management to capabilities/release-management/ with complete Makefile
- Create automatic capability discovery system in scripts/capability_discovery.mk
- Add capability-manager subagent for managing modular architecture
- Implement target delegation system enabling capability-name-target patterns
- Create Makefiles for markitect-content, markitect-utils, and issue-facade capabilities
- Remove legacy release management code and documentation from main project
- Update main Makefile to use capability discovery and delegation
- Add comprehensive capability status, help, and management targets

The capability system provides:
- Automatic discovery of capabilities with Makefiles
- Clean target delegation without conflicts
- Modular architecture following established patterns
- Comprehensive help and status reporting
- Zero-conflict capability integration

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 01:29:15 +01:00

252 lines
7.7 KiB
Python

"""
Main CLI entry point for release management.
This module provides the main CLI interface adapted from the original release.py script.
"""
import click
import sys
from pathlib import Path
from typing import Optional
from ..core.manager import ReleaseManager
from ..utils.version import VersionManager
@click.group(invoke_without_command=True)
@click.option('--dry-run', is_flag=True, help='Show what would be done without making changes')
@click.option('--force', is_flag=True, help='Force operation even with warnings')
@click.option('--project-root', type=click.Path(exists=True, path_type=Path),
help='Project root directory')
@click.pass_context
def main(ctx, dry_run: bool, force: bool, project_root: Optional[Path]):
"""Release management CLI for Python projects."""
ctx.ensure_object(dict)
ctx.obj['dry_run'] = dry_run
ctx.obj['force'] = force
ctx.obj['project_root'] = project_root
# If no command specified, show status
if ctx.invoked_subcommand is None:
ctx.invoke(status)
@main.command()
@click.pass_context
def status(ctx):
"""Show current release status and version information."""
manager = ReleaseManager(
project_root=ctx.obj['project_root'],
dry_run=ctx.obj['dry_run'],
force=ctx.obj['force']
)
print("🔍 Release Status")
print("=" * 60)
status_info = manager.get_release_status()
# Version information
print(f"Current Version: {status_info['version']}")
# Git information
if status_info.get('is_repo'):
print(f"Git Branch: {status_info['branch']}")
print(f"Latest Commit: {status_info['latest_commit']}")
print(f"Latest Tag: {status_info['latest_tag'] or 'None'}")
print(f"Uncommitted Changes: {'Yes' if status_info['has_changes'] else 'No'}")
else:
print("Git Repository: Not available")
# Package information
packages = status_info['packages']
print(f"\\nBuilt Packages: {packages['total_count']} files")
if packages['wheels']:
print(" Wheels:")
for wheel in packages['wheels']:
print(f" - {wheel}")
if packages['sdists']:
print(" Source Distributions:")
for sdist in packages['sdists']:
print(f" - {sdist}")
# Validation status
validation = status_info['validation']
if validation['is_valid']:
print("\\n✅ Repository is ready for release")
else:
print("\\n❌ Release validation issues:")
for issue in validation['issues']:
print(f" - {issue}")
@main.command()
@click.pass_context
def validate(ctx):
"""Validate repository state for release readiness."""
manager = ReleaseManager(
project_root=ctx.obj['project_root'],
dry_run=ctx.obj['dry_run'],
force=ctx.obj['force']
)
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)
@main.command()
@click.option('--version', required=True, help='Version to tag (e.g., 0.8.0)')
@click.option('--message', help='Tag message')
@click.pass_context
def tag(ctx, version: str, message: Optional[str]):
"""Create git tag for version."""
manager = ReleaseManager(
project_root=ctx.obj['project_root'],
dry_run=ctx.obj['dry_run'],
force=ctx.obj['force']
)
if manager.create_tag(version, message):
print(f"✅ Successfully created tag for version {version}")
else:
print(f"❌ Failed to create tag for version {version}")
sys.exit(1)
@main.command()
@click.pass_context
def build(ctx):
"""Build release packages using setuptools-scm."""
manager = ReleaseManager(
project_root=ctx.obj['project_root'],
dry_run=ctx.obj['dry_run'],
force=ctx.obj['force']
)
if manager.build_packages():
print("✅ Packages built successfully")
else:
print("❌ Package build failed")
sys.exit(1)
@main.command()
@click.option('--version', required=True, help='Version to publish (e.g., 0.8.0)')
@click.option('--registry', default='gitea', help='Registry type (gitea, pypi, etc.)')
@click.option('--skip-build', is_flag=True, help='Skip building and use existing packages')
@click.pass_context
def publish(ctx, version: str, registry: str, skip_build: bool):
"""Complete release workflow: tag, build, and publish."""
manager = ReleaseManager(
project_root=ctx.obj['project_root'],
dry_run=ctx.obj['dry_run'],
force=ctx.obj['force']
)
if manager.publish_with_fallback(version, registry, skip_build):
print(f"🎉 Release {version} published successfully!")
else:
print(f"❌ Release {version} failed")
sys.exit(1)
@main.command()
@click.option('--registry', default='gitea', help='Registry type (gitea, pypi, etc.)')
@click.pass_context
def upload(ctx, registry: str):
"""Upload existing packages to registry."""
manager = ReleaseManager(
project_root=ctx.obj['project_root'],
dry_run=ctx.obj['dry_run'],
force=ctx.obj['force']
)
if manager.upload_existing_packages(registry):
print(f"✅ Packages uploaded to {registry}")
else:
print(f"❌ Upload to {registry} failed")
sys.exit(1)
@main.command('registry-info')
@click.option('--registry', default='gitea', help='Registry type to show info for')
@click.pass_context
def registry_info(ctx, registry: str):
"""Show package registry information and status."""
manager = ReleaseManager(
project_root=ctx.obj['project_root'],
dry_run=ctx.obj['dry_run'],
force=ctx.obj['force']
)
info = manager.show_registry_info(registry)
print(f"📦 {registry.title()} Registry Information")
print("=" * 50)
if 'error' in info:
print(f"❌ Error: {info['error']}")
return
for key, value in info.items():
if isinstance(value, bool):
indicator = "" if value else ""
print(f"{key.replace('_', ' ').title()}: {indicator}")
else:
print(f"{key.replace('_', ' ').title()}: {value}")
@main.command()
@click.pass_context
def clean(ctx):
"""Clean build artifacts and temporary files."""
manager = ReleaseManager(
project_root=ctx.obj['project_root'],
dry_run=ctx.obj['dry_run'],
force=ctx.obj['force']
)
manager.clean_build_artifacts()
print("✅ Build artifacts cleaned")
@main.command('version-info')
@click.option('--suggest', is_flag=True, help='Suggest next version options')
@click.pass_context
def version_info(ctx, suggest: bool):
"""Show version information and suggestions."""
version_manager = VersionManager(ctx.obj['project_root'])
current = version_manager.get_current_version()
print(f"Current Version: {current}")
if suggest:
suggestions = version_manager.suggest_version(current)
if 'error' in suggestions:
print(f"{suggestions['error']}")
if 'suggestion' in suggestions:
print(f"💡 {suggestions['suggestion']}")
else:
print("\\nSuggested next versions:")
print(f" Patch: {suggestions['patch']}")
print(f" Minor: {suggestions['minor']}")
print(f" Major: {suggestions['major']}")
# Show version components
version_data = version_manager.parse_version(current)
if 'error' not in version_data:
print("\\nVersion Components:")
for key, value in version_data.items():
if value is not None:
print(f" {key.replace('_', ' ').title()}: {value}")
if __name__ == '__main__':
main()