Coverage for src/mafw/devtools/cli/release/create.py: 99%
62 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-09-01 19:49 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-09-01 19:49 +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."""
6from __future__ import annotations
8import click
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
43@click.command(
44 name='create',
45 context_settings={'help_option_names': ['-h', '--help']},
46)
47@click.argument('segments', type=str, help="Hatch version segment to bump (e.g. 'minor', 'micro', 'rc')")
48@click.option('--dry-run/--no-dry-run', default=False, show_default=True, help='Print commands without executing.')
49@click.option('--with-push/--without-push', default=True, show_default=True, help='Enable or skip pushing to remote.')
50@click.option(
51 '--with-release-note/--without-release-note',
52 default=True,
53 show_default=True,
54 help='Enable or skip release note generation.',
55)
56@click.option(
57 '--with-missing-release-check/--without-missing-release-check',
58 default=True,
59 show_default=True,
60 help='Enable or skip missing release guard.',
61)
62@click.option(
63 '--auto-doc-version/--manual-doc-version',
64 default=False,
65 show_default=True,
66 help='Auto-update doc target version without prompting.',
67)
68@click.option(
69 '--new-doc-version', default=None, type=click.STRING, help='Override documentation target version (major.minor).'
70)
71def create(
72 segments: str,
73 dry_run: bool,
74 with_push: bool,
75 with_release_note: bool,
76 with_missing_release_check: bool,
77 auto_doc_version: bool,
78 new_doc_version: str | None,
79) -> None:
80 """Prepare a new MAFw release from the main branch.
82 The command bumps the project version via hatch, updates the documentation
83 target version, updates NOTICE.txt, regenerates CHANGELOG.md, optionally
84 generates release notes, commits tracked release files, creates a local git
85 tag, and optionally pushes commits and tags to code.europa.eu/main using ssh.
87 Accepted segments: major, minor, micro, rc, alpha, beta, release.
89 Segments can be combined with a comma to produce compound versions.
90 Use at most one core segment (major, minor, micro) and at most one
91 pre-release segment (rc, alpha, beta).
93 Examples:
95 \b
96 minor bump minor version (e.g. 2.3.0 -> 2.4.0)
97 micro bump micro/patch version (e.g. 2.3.0 -> 2.3.1)
98 minor,rc bump minor + start release candidate (2.3.0 -> 2.4.0rc0)
99 rc increment the RC counter (e.g. 2.4.0rc0 -> 2.4.0rc1)
100 minor,alpha bump minor + start alpha (e.g. 2.3.0 -> 2.4.0a0)
101 release promote pre-release to stable (e.g. 2.4.0rc1 -> 2.4.0)
102 """
103 current_version = read_current_version()
104 normalized_segments = normalize_hatch_segments(segments)
106 if dry_run:
107 CONSOLE.print(
108 'Dry-run mode enabled: commands are printed; git/changelog writes are skipped; '
109 f'version is resolved via hatch and {ABOUT_FILE} is restored afterwards.'
110 )
111 else:
112 check_main_branch()
113 ensure_clean_git()
114 if with_missing_release_check: 114 ↛ 117line 114 didn't jump to line 117 because the condition on line 114 was always true
115 check_missing_release(current_version)
117 original_pyproject_toml = freeze_dependencies(dry_run=dry_run)
119 version = bump_version(dry_run=dry_run, segment=normalized_segments)
121 doc_target_version = compute_doc_target_version(version, normalized_segments, new_doc_version)
122 if auto_doc_version:
123 CONSOLE.print(f'Documentation target version set to {doc_target_version}.')
124 else:
125 CONSOLE.print('Provide a new documentation target version.')
126 prompt_default = validate_doc_target_version(doc_target_version)
127 doc_target_version = DocTargetVersionPrompt(
128 'Documentation target version',
129 show_default=True,
130 min_version=next_minor_version(version),
131 )(default=prompt_default)
132 CONSOLE.print(f'Documentation target version selected: {doc_target_version}.')
134 update_doc_target_version(doc_target_version, dry_run=dry_run)
136 if not dry_run:
137 prevent_duplicate_tag(version)
139 update_notice_version(version, dry_run=dry_run)
140 generate_changelog(version, dry_run=dry_run)
141 update_requirements_and_readme(dry_run=dry_run)
143 release_note_path = None
144 if with_release_note:
145 release_note_path = create_release_note(version, dry_run=dry_run)
146 CONSOLE.print(f'Release note file: {release_note_path}')
148 commit_changes(version, dry_run=dry_run, include_changelog=True)
149 tag = create_tag(version, dry_run=dry_run)
151 unfreeze_dependencies(original_pyproject_toml, dry_run=dry_run)
152 update_requirements_and_readme(dry_run=dry_run)
153 commit_dependency_unfreeze(dry_run=dry_run)
155 if with_push:
156 push_changes(dry_run=dry_run)
157 else:
158 CONSOLE.print('Skipping remote push because --without-push is set.')
160 CONSOLE.print('')
161 CONSOLE.print('Release pipeline completed.')
162 CONSOLE.print(f'Version: v{version}')
163 CONSOLE.print(f'Documentation target: {doc_target_version}')
164 CONSOLE.print(f'Tag: {tag}')
165 if with_release_note and release_note_path is not None:
166 CONSOLE.print(f'Release note: {release_note_path} (left untracked)')
167 CONSOLE.print(f'{CHANGELOG_FILE} updated.')
168 CONSOLE.print(f'{NOTICE_FILE} updated.')