- 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>
252 lines
7.7 KiB
Python
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() |