Files
mercadodevida/scripts/validate_conventional_commit.py

94 lines
2.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Validate Conventional Commit messages for Orquestra.
Allowed format:
<type>[optional scope][!]: <description>
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'^(?P<type>build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)'
r'(?:\((?P<scope>[A-Za-z0-9._/-]+)\))?'
r'(?P<breaking>!)?: (?P<description>\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: "
"<type>[optional scope][!]: <description>",
)
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())