Coverage for src / mafw / devtools / cli / release / create.py: 99%

62 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-06-28 13:34 +0000

1# Copyright 2026 European Union 

2# Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu) 

3# SPDX-License-Identifier: EUPL-1.2 

4"""Click command for creating a new MAFw release.""" 

5 

6from __future__ import annotations 

7 

8import click 

9 

10from mafw.devtools.dependencies.freeze import ( 

11 freeze_dependencies, 

12 unfreeze_dependencies, 

13 update_requirements_and_readme, 

14) 

15from mafw.devtools.git import ( 

16 check_main_branch, 

17 commit_changes, 

18 commit_dependency_unfreeze, 

19 create_tag, 

20 ensure_clean_git, 

21 prevent_duplicate_tag, 

22 push_changes, 

23) 

24from mafw.devtools.release.changelog import CHANGELOG_FILE, generate_changelog 

25from mafw.devtools.release.checks import check_missing_release 

26from mafw.devtools.release.notes import create_release_note 

27from mafw.devtools.release.versioning import ( 

28 ABOUT_FILE, 

29 NOTICE_FILE, 

30 DocTargetVersionPrompt, 

31 bump_version, 

32 compute_doc_target_version, 

33 next_minor_version, 

34 normalize_hatch_segments, 

35 read_current_version, 

36 update_doc_target_version, 

37 update_notice_version, 

38 validate_doc_target_version, 

39) 

40from mafw.tools.shell_tools import CONSOLE 

41 

42 

43@click.command( 

44 name='create', 

45 context_settings={'help_option_names': ['-h', '--help']}, 

46 help=( 

47 'Prepare a new MAFw release from the main branch.\n\n' 

48 'The command bumps the project version via hatch, updates the documentation target version, ' 

49 'updates NOTICE.txt, regenerates CHANGELOG.md, optionally generates release notes, ' 

50 'commits tracked release files, creates a local git tag, and optionally pushes ' 

51 'commits and tags to code.europa.eu/main using ssh.' 

52 ), 

53) 

54@click.argument('segments', type=str) 

55@click.option('--dry-run/--no-dry-run', default=False, show_default=True, help='Print commands without executing.') 

56@click.option('--with-push/--without-push', default=True, show_default=True, help='Enable or skip pushing to remote.') 

57@click.option( 

58 '--with-release-note/--without-release-note', 

59 default=True, 

60 show_default=True, 

61 help='Enable or skip release note generation.', 

62) 

63@click.option( 

64 '--with-missing-release-check/--without-missing-release-check', 

65 default=True, 

66 show_default=True, 

67 help='Enable or skip missing release guard.', 

68) 

69@click.option( 

70 '--auto-doc-version/--manual-doc-version', 

71 default=False, 

72 show_default=True, 

73 help='Auto-update doc target version without prompting.', 

74) 

75@click.option( 

76 '--new-doc-version', default=None, type=click.STRING, help='Override documentation target version (major.minor).' 

77) 

78def create( 

79 segments: str, 

80 dry_run: bool, 

81 with_push: bool, 

82 with_release_note: bool, 

83 with_missing_release_check: bool, 

84 auto_doc_version: bool, 

85 new_doc_version: str | None, 

86) -> None: 

87 """Prepare a new MAFw release from the main branch.""" 

88 current_version = read_current_version() 

89 normalized_segments = normalize_hatch_segments(segments) 

90 

91 if dry_run: 

92 CONSOLE.print( 

93 'Dry-run mode enabled: commands are printed; git/changelog writes are skipped; ' 

94 f'version is resolved via hatch and {ABOUT_FILE} is restored afterwards.' 

95 ) 

96 else: 

97 check_main_branch() 

98 ensure_clean_git() 

99 if with_missing_release_check: 99 ↛ 102line 99 didn't jump to line 102 because the condition on line 99 was always true

100 check_missing_release(current_version) 

101 

102 original_pyproject_toml = freeze_dependencies(dry_run=dry_run) 

103 

104 version = bump_version(dry_run=dry_run, segment=normalized_segments) 

105 

106 doc_target_version = compute_doc_target_version(version, normalized_segments, new_doc_version) 

107 if auto_doc_version: 

108 CONSOLE.print(f'Documentation target version set to {doc_target_version}.') 

109 else: 

110 CONSOLE.print('Provide a new documentation target version.') 

111 prompt_default = validate_doc_target_version(doc_target_version) 

112 doc_target_version = DocTargetVersionPrompt( 

113 'Documentation target version', 

114 show_default=True, 

115 min_version=next_minor_version(version), 

116 )(default=prompt_default) 

117 CONSOLE.print(f'Documentation target version selected: {doc_target_version}.') 

118 

119 update_doc_target_version(doc_target_version, dry_run=dry_run) 

120 

121 if not dry_run: 

122 prevent_duplicate_tag(version) 

123 

124 update_notice_version(version, dry_run=dry_run) 

125 generate_changelog(version, dry_run=dry_run) 

126 update_requirements_and_readme(dry_run=dry_run) 

127 

128 release_note_path = None 

129 if with_release_note: 

130 release_note_path = create_release_note(version, dry_run=dry_run) 

131 CONSOLE.print(f'Release note file: {release_note_path}') 

132 

133 commit_changes(version, dry_run=dry_run, include_changelog=True) 

134 tag = create_tag(version, dry_run=dry_run) 

135 

136 unfreeze_dependencies(original_pyproject_toml, dry_run=dry_run) 

137 update_requirements_and_readme(dry_run=dry_run) 

138 commit_dependency_unfreeze(dry_run=dry_run) 

139 

140 if with_push: 

141 push_changes(dry_run=dry_run) 

142 else: 

143 CONSOLE.print('Skipping remote push because --without-push is set.') 

144 

145 CONSOLE.print('') 

146 CONSOLE.print('Release pipeline completed.') 

147 CONSOLE.print(f'Version: v{version}') 

148 CONSOLE.print(f'Documentation target: {doc_target_version}') 

149 CONSOLE.print(f'Tag: {tag}') 

150 if with_release_note and release_note_path is not None: 

151 CONSOLE.print(f'Release note: {release_note_path} (left untracked)') 

152 CONSOLE.print(f'{CHANGELOG_FILE} updated.') 

153 CONSOLE.print(f'{NOTICE_FILE} updated.')