diff --git a/commodore/cli/component.py b/commodore/cli/component.py index 5ff10a98..6c816021 100644 --- a/commodore/cli/component.py +++ b/commodore/cli/component.py @@ -189,6 +189,14 @@ def decorator(cmd): multiple=True, help=_generate_automerge_depname_help(level="patch"), )(cmd) + click.option( + "--autorelease / --no-autorelease", + is_flag=True, + default=True if new_cmd else None, + help="Enable autorelease GitHub action. " + + "When autorelease is enabled, new releases will be generated " + + "for automerged dependency PRs.", + )(cmd) click.option( "--automerge-patch-v0 / --no-automerge-patch-v0", is_flag=True, @@ -310,6 +318,7 @@ def component_new( additional_test_case: Iterable[str], automerge_patch: bool, automerge_patch_v0: bool, + autorelease: bool, add_automerge_patch_block_depname: Iterable[str], add_automerge_patch_block_pattern: Iterable[str], add_automerge_patch_v0_allow_depname: Iterable[str], @@ -330,6 +339,7 @@ def component_new( t.test_cases = ["defaults"] + list(additional_test_case) t.automerge_patch = automerge_patch t.automerge_patch_v0 = automerge_patch_v0 + t.autorelease = autorelease for name in add_automerge_patch_block_depname: t.add_automerge_patch_block_depname(name) for pattern in add_automerge_patch_block_pattern: @@ -437,6 +447,7 @@ def component_update( commit: bool, automerge_patch: Optional[bool], automerge_patch_v0: Optional[bool], + autorelease: Optional[bool], add_automerge_patch_block_depname: Iterable[str], add_automerge_patch_block_pattern: Iterable[str], add_automerge_patch_v0_allow_depname: Iterable[str], @@ -479,6 +490,8 @@ def component_update( t.automerge_patch = automerge_patch if automerge_patch_v0 is not None: t.automerge_patch_v0 = automerge_patch_v0 + if autorelease is not None: + t.autorelease = autorelease test_cases = t.test_cases test_cases.extend(additional_test_case) diff --git a/commodore/component/template.py b/commodore/component/template.py index 70e401d1..0ed365f7 100644 --- a/commodore/component/template.py +++ b/commodore/component/template.py @@ -19,6 +19,7 @@ class ComponentTemplater(Templater): post_process: bool _automerge_patch: bool automerge_patch_v0: bool + autorelease: bool _matrix_tests: bool _automerge_patch_blocklist: set[str] _automerge_patch_v0_allowlist: set[str] @@ -108,6 +109,7 @@ def _initialize_from_cookiecutter_args(self, cookiecutter_args: dict[str, str]): self.matrix_tests = cookiecutter_args["add_matrix"] == "y" self.automerge_patch = cookiecutter_args["automerge_patch"] == "y" self.automerge_patch_v0 = cookiecutter_args["automerge_patch_v0"] == "y" + self.autorelease = cookiecutter_args["auto_release"] == "y" self._initialize_automerge_pattern_lists_from_cookiecutter_args( cookiecutter_args @@ -148,6 +150,7 @@ def cookiecutter_args(self) -> dict[str, str]: args["add_matrix"] = "y" if self.matrix_tests else "n" args["automerge_patch"] = "y" if self.automerge_patch else "n" args["automerge_patch_v0"] = "y" if self.automerge_patch_v0 else "n" + args["auto_release"] = "y" if self.autorelease else "n" args["automerge_patch_regexp_blocklist"] = ";".join( sorted(self._automerge_patch_blocklist) ) diff --git a/docs/modules/ROOT/pages/reference/cli.adoc b/docs/modules/ROOT/pages/reference/cli.adoc index 913dc485..0af3ad2a 100644 --- a/docs/modules/ROOT/pages/reference/cli.adoc +++ b/docs/modules/ROOT/pages/reference/cli.adoc @@ -216,6 +216,11 @@ Defaults to `--no-force`. + NOTE: Enabling automerging of patch-level dependency PRs for v0.x dependencies implicitly enables automerging of all patch-level dependency PRs. +*--autorelease / --no-autorelease*:: + Enable autorelease GitHub action ++ +NOTE: If autorelease is enabled, new releases will be generated for automerged dependency PRs. + *--add-automerge-patch-block-depname* NAME:: Add dependency name that should be excluded from automerging of patch updates. Can be repeated. diff --git a/tests/test_component_template.py b/tests/test_component_template.py index d31b58f7..8aea1e70 100644 --- a/tests/test_component_template.py +++ b/tests/test_component_template.py @@ -34,6 +34,7 @@ def call_component_new( matrix="--no-matrix-tests", automerge_patch="--no-automerge-patch", automerge_patch_v0="--no-automerge-patch-v0", + autorelease="--no-autorelease", output_dir="", extra_args: list[str] = [], ): @@ -41,7 +42,16 @@ def call_component_new( if output_dir: args.extend(["--output-dir", str(output_dir)]) args.extend( - [component_name, lib, pp, golden, matrix, automerge_patch, automerge_patch_v0] + [ + component_name, + lib, + pp, + golden, + matrix, + automerge_patch, + automerge_patch_v0, + autorelease, + ] ) args.extend(extra_args) result = cli_runner(args) @@ -171,6 +181,7 @@ def _validate_rendered_component( has_matrix: bool, has_automerge_patch: bool, has_automerge_patch_v0: bool, + has_autorelease: bool, test_cases: list[str] = ["defaults"], ): expected_files = [ @@ -206,6 +217,16 @@ def _validate_rendered_component( f"{component_name}.yaml", ) ) + autorelease_file = P(".github", "workflows", "auto-release.yaml") + if has_autorelease: + # we just check that the auto-release action only exists when the feature is + # enabled. The contents of the file are static, so we don't need to check them. + expected_files.append(autorelease_file) + else: + # if autorelease is not enabled, ensure the file doesn't exist + assert not ( + tmp_path / "dependencies" / component_name / autorelease_file + ).exists() for file in expected_files: assert (tmp_path / "dependencies" / component_name / file).exists() # Check that we created a worktree @@ -357,6 +378,7 @@ def test_run_component_new_command( has_matrix, False, False, + False, ) @@ -395,6 +417,7 @@ def test_run_component_new_with_additional_test_cases( True, False, False, + False, ["defaults"] + test_cases, ) @@ -423,6 +446,7 @@ def test_run_component_new_force_matrix_additional_test_cases( True, False, False, + False, ["defaults", "foo"], ) @@ -488,11 +512,19 @@ def test_run_component_new_command_with_name(tmp_path: P): "--no-automerge-patch-v0", ], ) +@pytest.mark.parametrize( + "autorelease", + [ + "--autorelease", + "--no-autorelease", + ], +) def test_run_component_new_automerge_patch_options( tmp_path: P, cli_runner: RunnerFunc, - automerge_patch, - automerge_patch_v0, + automerge_patch: str, + automerge_patch_v0: str, + autorelease: str, ): result = call_component_new( tmp_path, @@ -501,12 +533,14 @@ def test_run_component_new_automerge_patch_options( matrix="--matrix-tests", automerge_patch=automerge_patch, automerge_patch_v0=automerge_patch_v0, + autorelease=autorelease, ) has_automerge_patch_v0 = automerge_patch_v0 == "--automerge-patch-v0" has_automerge_patch = ( automerge_patch == "--automerge-patch" or has_automerge_patch_v0 ) + has_autorelease = autorelease == "--autorelease" if automerge_patch == "--no-automerge-patch" and has_automerge_patch_v0: assert ( " > Forcing automerging of patch dependencies to be enabled " @@ -522,6 +556,7 @@ def test_run_component_new_automerge_patch_options( True, has_automerge_patch, has_automerge_patch_v0, + has_autorelease, ) @@ -563,6 +598,7 @@ def test_run_component_new_automerge_patch_blocklist( True, True, False, + False, ) _validate_renovatejson_packagerules( tmp_path / "dependencies" / "test-component", @@ -627,6 +663,7 @@ def test_run_component_new_automerge_patch_v0_allowlist( True, True, has_automerge_patch_v0, + False, ) _validate_renovatejson_packagerules( tmp_path / "dependencies" / "test-component", @@ -669,6 +706,7 @@ def test_run_component_new_automerge_patch_v0_selective_only( # setting this to True since we end up having a package rule which otherwise confuses the # inexact validation. True, + False, ) _validate_renovatejson_packagerules( tmp_path / "dependencies" / "test-component", @@ -725,6 +763,7 @@ def test_run_component_new_automerge_minor_allowlist( True, True, False, + False, ) _validate_renovatejson_packagerules( tmp_path / "dependencies" / "test-component", @@ -763,6 +802,7 @@ def test_run_component_new_automerge_all_options( True, True, False, + False, ) _validate_renovatejson_packagerules( tmp_path / "dependencies" / "test-component", @@ -886,11 +926,13 @@ def test_check_golden_diff(tmp_path: P): ([], ["--automerge-patch"]), ([], ["--automerge-patch-v0"]), ([], ["--automerge-patch", "--automerge-patch-v0"]), + ([], ["--autorelease"]), (["--automerge-patch"], ["--no-automerge-patch"]), (["--automerge-patch-v0"], ["--no-automerge-patch-v0"]), (["--no-automerge-patch"], ["--automerge-patch-v0"]), (["--automerge-patch-v0"], ["--no-automerge-patch"]), (["--automerge-patch-v0"], ["--no-automerge-patch-v0"]), + (["--autorelease"], ["--no-autorelease"]), ], ) def test_component_update_bool_flags( @@ -908,6 +950,7 @@ def test_component_update_bool_flags( "--no-matrix-tests", "--no-automerge-patch", "--no-automerge-patch-v0", + "--no-autorelease", component_name, ] @@ -919,6 +962,7 @@ def test_component_update_bool_flags( "--automerge-patch" in new_args or "--automerge-patch-v0" in new_args ) has_automerge_patch_v0 = "--automerge-patch-v0" in new_args + has_autorelease = "--autorelease" in new_args result = cli_runner(new_cmd + new_args) assert result.exit_code == 0 @@ -932,6 +976,7 @@ def test_component_update_bool_flags( has_matrix, has_automerge_patch, has_automerge_patch_v0, + has_autorelease, ) update_cmd = [ @@ -958,6 +1003,7 @@ def test_component_update_bool_flags( or (has_automerge_patch and "--no-automerge-patch" not in update_args) or has_automerge_patch_v0_update ) + has_autorelease = "--autorelease" in update_args result = cli_runner(update_cmd + update_args) assert result.exit_code == 0 @@ -971,6 +1017,7 @@ def test_component_update_bool_flags( has_matrix, has_automerge_patch_update, has_automerge_patch_v0_update, + has_autorelease, ) @@ -1099,7 +1146,16 @@ def test_component_update_test_cases( orig_cases = ["defaults"] + initial_cases _validate_rendered_component( - tmp_path, component_name, False, False, True, True, False, False, orig_cases + tmp_path, + component_name, + False, + False, + True, + True, + False, + False, + False, + orig_cases, ) update_args = _format_test_case_args("--additional-test-case", additional_cases) @@ -1123,7 +1179,16 @@ def test_component_update_test_cases( final_cases = updated_cases _validate_rendered_component( - tmp_path, component_name, False, False, True, True, False, False, final_cases + tmp_path, + component_name, + False, + False, + True, + True, + False, + False, + False, + final_cases, ) @@ -1562,6 +1627,7 @@ def test_cookiecutter_args_no_cruft_json(tmp_path: P, config: Config): t.github_owner = "projectsyn" t.automerge_patch = True t.automerge_patch_v0 = False + t.autorelease = False templater_cookiecutter_args = t.cookiecutter_args