feat: Implement centralized version management and sync process
This commit is contained in:
parent
c1133ae8e9
commit
0d9464854d
7 changed files with 523 additions and 45 deletions
152
scripts/sync_version.py
Normal file
152
scripts/sync_version.py
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
"""Sync version from __init__.py to changelog.
|
||||
|
||||
This script reads the version from src/webdrop_bridge/__init__.py and
|
||||
updates the CHANGELOG.md. Config and pyproject.toml automatically read
|
||||
from __init__.py, so no manual sync needed for those files.
|
||||
|
||||
This script uses shared version utilities (build/scripts/version_utils.py)
|
||||
to ensure consistent version reading across all build scripts.
|
||||
|
||||
Usage:
|
||||
python scripts/sync_version.py [--version VERSION]
|
||||
|
||||
Examples:
|
||||
python scripts/sync_version.py # Use version from __init__.py
|
||||
python scripts/sync_version.py --version 2.0.0 # Override with new version
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
# Import shared version utilities
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "build" / "scripts"))
|
||||
from version_utils import get_current_version, get_project_root
|
||||
|
||||
PROJECT_ROOT = get_project_root()
|
||||
|
||||
|
||||
def get_current_version_from_init() -> str:
|
||||
"""Get version from __init__.py using shared utility.
|
||||
|
||||
Returns:
|
||||
Current version string from __init__.py
|
||||
|
||||
Raises:
|
||||
ValueError: If __version__ cannot be found
|
||||
"""
|
||||
return get_current_version()
|
||||
|
||||
|
||||
def update_init_version(version: str) -> None:
|
||||
"""Update version in __init__.py.
|
||||
|
||||
Args:
|
||||
version: New version string to set
|
||||
"""
|
||||
init_file = PROJECT_ROOT / "src/webdrop_bridge/__init__.py"
|
||||
content = init_file.read_text()
|
||||
new_content = re.sub(
|
||||
r'__version__\s*=\s*["\'][^"\']+["\']',
|
||||
f'__version__ = "{version}"',
|
||||
content,
|
||||
)
|
||||
init_file.write_text(new_content)
|
||||
print(f"✓ Updated src/webdrop_bridge/__init__.py to {version}")
|
||||
|
||||
|
||||
def update_env_example(version: str) -> None:
|
||||
"""Update APP_VERSION in .env.example (optional).
|
||||
|
||||
Note: config.py now reads from __init__.py by default.
|
||||
Only update if .env.example explicitly sets APP_VERSION for testing.
|
||||
|
||||
Args:
|
||||
version: New version string to set
|
||||
"""
|
||||
env_file = PROJECT_ROOT / ".env.example"
|
||||
if env_file.exists():
|
||||
content = env_file.read_text()
|
||||
# Only update if APP_VERSION is explicitly set
|
||||
if 'APP_VERSION=' in content:
|
||||
new_content = re.sub(
|
||||
r'APP_VERSION=[^\n]+',
|
||||
f'APP_VERSION={version}',
|
||||
content,
|
||||
)
|
||||
env_file.write_text(new_content)
|
||||
print(f"✓ Updated .env.example to {version}")
|
||||
else:
|
||||
print(
|
||||
f"ℹ️ .env.example does not override APP_VERSION "
|
||||
f"(uses __init__.py)"
|
||||
)
|
||||
|
||||
|
||||
def update_changelog(version: str) -> None:
|
||||
"""Add version header to CHANGELOG.md if not present.
|
||||
|
||||
Args:
|
||||
version: New version string to add
|
||||
"""
|
||||
changelog = PROJECT_ROOT / "CHANGELOG.md"
|
||||
if changelog.exists():
|
||||
content = changelog.read_text()
|
||||
if f"## [{version}]" not in content and f"## {version}" not in content:
|
||||
date_str = datetime.now().strftime("%Y-%m-%d")
|
||||
header = (
|
||||
f"## [{version}] - {date_str}\n\n"
|
||||
"### Added\n\n### Changed\n\n### Fixed\n\n"
|
||||
)
|
||||
new_content = header + content
|
||||
changelog.write_text(new_content)
|
||||
print(f"✓ Added version header to CHANGELOG.md for {version}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Sync version across project.
|
||||
|
||||
Updates __init__.py (source of truth) and changelog.
|
||||
Config and pyproject.toml automatically read from __init__.py.
|
||||
|
||||
Returns:
|
||||
0 on success, 1 on error
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Sync version from __init__.py to dependent files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
type=str,
|
||||
help="Version to set (if not provided, reads from __init__.py)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.version:
|
||||
if not re.match(r"^\d+\.\d+\.\d+", args.version):
|
||||
print(
|
||||
"❌ Invalid version format. Use semantic versioning"
|
||||
" (e.g., 1.2.3)"
|
||||
)
|
||||
return 1
|
||||
version = args.version
|
||||
update_init_version(version)
|
||||
else:
|
||||
version = get_current_version_from_init()
|
||||
print(f"📍 Current version from __init__.py: {version}")
|
||||
|
||||
update_env_example(version)
|
||||
update_changelog(version)
|
||||
print(f"\n✅ Version sync complete: {version}")
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue