#!/usr/bin/env python3 """Validate Conventional Commit messages for Orquestra. Allowed format: [optional scope][!]: Examples: feat(orders): add awaiting-payment notifications fix(DEPLOY-MONOLITH-CLEANUP): clean stale deployments chore(release)!: v1.0.0 """ from __future__ import annotations import argparse import re import sys from pathlib import Path ALLOWED_TYPES = { 'build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test', } HEADER_RE = re.compile( r'^(?Pbuild|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)' r'(?:\((?P[A-Za-z0-9._/-]+)\))?' r'(?P!)?: (?P\S.{0,119})$' ) def validate(message: str) -> tuple[bool, str | None]: lines = message.strip('\n').splitlines() if not lines or not lines[0].strip(): return False, 'empty commit message' header = lines[0].strip() match = HEADER_RE.match(header) if not match: return ( False, "first line must be Conventional Commit: " "[optional scope][!]: ", ) commit_type = match.group('type') if commit_type not in ALLOWED_TYPES: return False, f"unsupported type '{commit_type}'" description = match.group('description').strip() if description.endswith('.'): return False, 'description must not end with a period' if any(line.rstrip() != line for line in lines): return False, 'commit message contains trailing whitespace' if len(lines) > 1 and lines[1].strip(): return False, 'second line must be blank before body/footer' return True, None def main() -> int: parser = argparse.ArgumentParser(description='Validate Conventional Commit message') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--file', type=Path, help='commit message file') group.add_argument('--message', help='commit message string') args = parser.parse_args() message = args.message if args.message is not None else args.file.read_text(encoding='utf-8') ok, error = validate(message) if ok: return 0 print(f'[FAIL] Invalid commit message: {error}', file=sys.stderr) print('', file=sys.stderr) print('Examples:', file=sys.stderr) print(' feat(orders): add awaiting-payment notifications', file=sys.stderr) print(' fix(monolith): clean stale deployments before start', file=sys.stderr) print(' chore(release): v0.2.0', file=sys.stderr) return 1 if __name__ == '__main__': raise SystemExit(main())