invoke-toolkit 0.0.66__tar.gz → 0.0.68__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/PKG-INFO +1 -1
  2. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/collections.py +7 -0
  3. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_collection.py +24 -0
  4. invoke_toolkit-0.0.68/tests/test_release_guard.py +85 -0
  5. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/.gitignore +0 -0
  6. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/LICENSE.txt +0 -0
  7. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/README.md +0 -0
  8. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/pyproject.toml +0 -0
  9. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/__init__.py +0 -0
  10. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/__main__.py +0 -0
  11. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/completion.py +0 -0
  12. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/config/__init__.py +0 -0
  13. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/config/config.py +0 -0
  14. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/config/registry.py +0 -0
  15. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/config/schema.py +0 -0
  16. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/config/status_helper.py +0 -0
  17. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/context/__init__.py +0 -0
  18. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/context/async_tools.py +0 -0
  19. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/context/context.py +0 -0
  20. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/context/types.py +0 -0
  21. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/executor.py +0 -0
  22. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/extensions/__init__.py +0 -0
  23. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/extensions/tasks/__init__.py +0 -0
  24. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/extensions/tasks/config.py +0 -0
  25. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/extensions/tasks/create.py +0 -0
  26. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/extensions/tasks/dist.py +0 -0
  27. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/extensions/tasks/shell.py +0 -0
  28. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/field_resolvers.py +0 -0
  29. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/loader/entrypoint.py +0 -0
  30. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/log/__init__.py +0 -0
  31. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/log/logger.py +0 -0
  32. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/output/__init__.py +0 -0
  33. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/output/console.py +0 -0
  34. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/output/utils.py +0 -0
  35. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/parser.py +0 -0
  36. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/program/__init__.py +0 -0
  37. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/program/main.py +0 -0
  38. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/program/program.py +0 -0
  39. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/py.typed +0 -0
  40. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/runners/__init__.py +0 -0
  41. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/runners/rich.py +0 -0
  42. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/scripts/__init__.py +0 -0
  43. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/scripts/loader.py +0 -0
  44. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/tasks/__init__.py +0 -0
  45. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/tasks/autocomplete.py +0 -0
  46. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/tasks/cache.py +0 -0
  47. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/tasks/fields.py +0 -0
  48. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/tasks/fields.pyi +0 -0
  49. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/tasks/tasks.py +0 -0
  50. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/tasks/types.py +0 -0
  51. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/testing.py +0 -0
  52. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/utils/__init__.py +0 -0
  53. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/utils/fzf.py +0 -0
  54. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/utils/inspection.py +0 -0
  55. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/utils/singleton.py +0 -0
  56. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/src/invoke_toolkit/utils/text.py +0 -0
  57. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/package-template/.gitignore.jinja +0 -0
  58. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/package-template/README.md.jinja +0 -0
  59. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/package-template/copier.yml +0 -0
  60. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/package-template/pyproject.toml.jinja +0 -0
  61. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/package-template/src/{{package_slug}}/__init__.py.jinja +0 -0
  62. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/package-template/src/{{package_slug}}/tasks.py.jinja +0 -0
  63. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/provider-template/.gitignore.jinja +0 -0
  64. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/provider-template/README.md.jinja +0 -0
  65. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/provider-template/copier.yml +0 -0
  66. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/provider-template/pyproject.toml.jinja +0 -0
  67. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/provider-template/src/{{package_slug}}/__init__.py.jinja +0 -0
  68. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/templates/provider-template/tests/test_resolver.py.jinja +0 -0
  69. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/__init__.py +0 -0
  70. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/conftest.py +0 -0
  71. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/examples/cached_completion/tasks.py +0 -0
  72. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/examples/completion_with_config/.gitignore +0 -0
  73. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/examples/completion_with_config/tasks.py +0 -0
  74. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/examples/config_schema/tasks.py +0 -0
  75. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/examples/enum_select_size/tasks.py +0 -0
  76. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/examples/fzf_selector/tasks.py +0 -0
  77. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/examples/literal_set_level/tasks.py +0 -0
  78. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/examples/local_field_resolver/tasks.py +0 -0
  79. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/examples/local_tasks/local_tasks.py +0 -0
  80. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/examples/local_tasks/tasks.py +0 -0
  81. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/extensions/conftest.py +0 -0
  82. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/extensions/test_config_tasks.py +0 -0
  83. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/extensions/test_create.py +0 -0
  84. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/extensions/test_package_template_entrypoint.py +0 -0
  85. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/extensions/test_shell_tasks.py +0 -0
  86. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/fixtures/future_fields.py +0 -0
  87. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/__init__.py +0 -0
  88. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/all-four/invoke.json +0 -0
  89. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/all-four/invoke.py +0 -0
  90. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/all-four/invoke.yml +0 -0
  91. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/collection.py +0 -0
  92. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/echo.yaml +0 -0
  93. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/json/invoke.json +0 -0
  94. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/json-and-python/invoke.json +0 -0
  95. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/json-and-python/invoke.py +0 -0
  96. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/no-dedupe.yaml +0 -0
  97. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/no-echo.yaml +0 -0
  98. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/package/invoke.yml +0 -0
  99. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/package/tasks/__init__.py +0 -0
  100. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/python/invoke.py +0 -0
  101. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/runtime.py +0 -0
  102. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/three-of-em/invoke.json +0 -0
  103. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/three-of-em/invoke.py +0 -0
  104. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/three-of-em/invoke.yml +0 -0
  105. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/underscores/tasks.py +0 -0
  106. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/yaml/explicit.py +0 -0
  107. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/yaml/tasks.py +0 -0
  108. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/yml/explicit.py +0 -0
  109. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/yml/invoke.yml +0 -0
  110. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/configs/yml/tasks.py +0 -0
  111. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/_support/has_modules.py +0 -0
  112. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/conftest.py +0 -0
  113. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/original_invoke/test_config.py +0 -0
  114. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/program/main.py +0 -0
  115. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/program/tasks/__init__.py +0 -0
  116. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/program/tasks/coll1.py +0 -0
  117. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/script/test_script.py +0 -0
  118. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/tasks/test_cache.py +0 -0
  119. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/tasks/test_extensions_config.py +0 -0
  120. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_alias_self_reference.py +0 -0
  121. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_annotated_help.py +0 -0
  122. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_async_edge_cases.py +0 -0
  123. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_async_support.py +0 -0
  124. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_cofig_class.py +0 -0
  125. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_collection_configure.py +0 -0
  126. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_completion_with_choices.py +0 -0
  127. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_config_helper.py +0 -0
  128. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_config_registry.py +0 -0
  129. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_config_schema.py +0 -0
  130. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_console.py +0 -0
  131. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_context_class.py +0 -0
  132. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_disable_status_cli.py +0 -0
  133. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_enum_arguments.py +0 -0
  134. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_executor.py +0 -0
  135. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_field_typing.py +0 -0
  136. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_fields.py +0 -0
  137. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_file_completion.py +0 -0
  138. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_global_context.py +0 -0
  139. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_help_flags.py +0 -0
  140. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_invoke_3_compat.py +0 -0
  141. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_invoke_compatibility.py +0 -0
  142. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_list_filter_flags.py +0 -0
  143. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_loader.py +0 -0
  144. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_parsing.py +0 -0
  145. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_proctitle.py +0 -0
  146. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_program_with_collection.py +0 -0
  147. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/test_toplevel.py +0 -0
  148. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/typecheck/fields.py +0 -0
  149. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/typecheck/fields_negative.py.fixture +0 -0
  150. {invoke_toolkit-0.0.66 → invoke_toolkit-0.0.68}/tests/utils/test_fzf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: invoke-toolkit
3
- Version: 0.0.66
3
+ Version: 0.0.68
4
4
  Summary: Extended APIs for PyInvoke: composable task plugins, typed CLI args, Rich output, shell completion, and PEP 723 script support
5
5
  Project-URL: Documentation, https://github.com/D3f0/invoke-toolkit#readme
6
6
  Project-URL: Issues, https://github.com/D3f0/invoke-toolkit/issues
@@ -250,6 +250,13 @@ class ToolkitCollection(Collection):
250
250
  if not local_tasks_file.exists():
251
251
  debug(f"No local_tasks.py found at {local_tasks_file}")
252
252
  return
253
+ if "local" in self.tasks:
254
+ logger.warning(
255
+ "Skipping local_tasks.py at %s because tasks.py already defines "
256
+ "the 'local' task namespace",
257
+ local_tasks_file,
258
+ )
259
+ return
253
260
 
254
261
  debug(f"Loading local tasks from {local_tasks_file}")
255
262
 
@@ -1,10 +1,13 @@
1
1
  import ast
2
+ import logging
2
3
  import os
3
4
  import subprocess
4
5
  import sys
5
6
  from pathlib import Path
6
7
  from textwrap import dedent
8
+ from typing import Any, cast
7
9
 
10
+ from invoke_toolkit import Task, task
8
11
  from invoke_toolkit.collections import ToolkitCollection
9
12
 
10
13
 
@@ -216,6 +219,27 @@ def test_load_local_tasks_without_main_tasks(tmp_path: Path):
216
219
  assert "standalone-task" in local_col.tasks
217
220
 
218
221
 
222
+ def test_load_local_tasks_preserves_conflicting_main_task(tmp_path: Path, caplog):
223
+ """A main task named local takes precedence over local_tasks.py."""
224
+
225
+ def main_task(ctx):
226
+ pass
227
+
228
+ decorated_main_task = cast(Task[Any], task()(main_task))
229
+ ns = ToolkitCollection()
230
+ ns.add_task(decorated_main_task, aliases=("local",))
231
+ (tmp_path / "local_tasks.py").write_text(
232
+ "from invoke_toolkit import task\n\n@task()\ndef local_task(ctx):\n pass\n"
233
+ )
234
+
235
+ with caplog.at_level(logging.WARNING, logger="invoke"):
236
+ ns.load_local_tasks(search_path=tmp_path)
237
+
238
+ assert ns.tasks["local"] is decorated_main_task
239
+ assert "local" not in ns.collections
240
+ assert "Skipping local_tasks.py" in caplog.text
241
+
242
+
219
243
  def test_load_local_tasks_from_multiple_directories_without_module_collision(
220
244
  tmp_path: Path,
221
245
  ):
@@ -0,0 +1,85 @@
1
+ import os
2
+ import subprocess
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+
8
+ REPOSITORY_ROOT = Path(__file__).parents[1]
9
+ GUARD = REPOSITORY_ROOT / "scripts" / "validate-release-tag.sh"
10
+
11
+
12
+ def run(command: list[str], cwd: Path) -> subprocess.CompletedProcess[str]:
13
+ return subprocess.run(command, cwd=cwd, check=True, capture_output=True, text=True)
14
+
15
+
16
+ @pytest.fixture
17
+ def release_repository(tmp_path: Path) -> Path:
18
+ remote = tmp_path / "remote.git"
19
+ repository = tmp_path / "repository"
20
+
21
+ run(["git", "init", "--bare", str(remote)], tmp_path)
22
+ run(["git", "clone", str(remote), str(repository)], tmp_path)
23
+ run(["git", "config", "user.email", "test@example.com"], repository)
24
+ run(["git", "config", "user.name", "Test User"], repository)
25
+ (repository / "README").write_text("initial\n")
26
+ run(["git", "add", "README"], repository)
27
+ run(["git", "commit", "-m", "initial"], repository)
28
+ run(["git", "branch", "-M", "main"], repository)
29
+ run(["git", "push", "-u", "origin", "main"], repository)
30
+ return repository
31
+
32
+
33
+ def guard(tag_name: str, repository: Path) -> subprocess.CompletedProcess[str]:
34
+ output = repository / "github-output"
35
+ return subprocess.run(
36
+ [str(GUARD)],
37
+ cwd=repository,
38
+ env={**os.environ, "TAG_NAME": tag_name, "GITHUB_OUTPUT": str(output)},
39
+ capture_output=True,
40
+ text=True,
41
+ check=False,
42
+ )
43
+
44
+
45
+ def test_accepts_canonical_tag_at_origin_main(release_repository: Path) -> None:
46
+ run(["git", "tag", "v0.0.67"], release_repository)
47
+ run(["git", "push", "origin", "v0.0.67"], release_repository)
48
+
49
+ result = guard("v0.0.67", release_repository)
50
+
51
+ assert result.returncode == 0
52
+
53
+
54
+ def test_rejects_noncanonical_uppercase_tag(release_repository: Path) -> None:
55
+ run(["git", "tag", "V0.0.67"], release_repository)
56
+ run(["git", "push", "origin", "V0.0.67"], release_repository)
57
+
58
+ result = guard("V0.0.67", release_repository)
59
+
60
+ assert result.returncode != 0
61
+ assert "canonical lowercase tag" in result.stderr
62
+
63
+
64
+ def test_rejects_tag_behind_updated_origin_main(
65
+ release_repository: Path, tmp_path: Path
66
+ ) -> None:
67
+ run(["git", "tag", "v0.0.67"], release_repository)
68
+ run(["git", "push", "origin", "v0.0.67"], release_repository)
69
+
70
+ updater = tmp_path / "updater"
71
+ run(
72
+ ["git", "clone", str(release_repository.parent / "remote.git"), str(updater)],
73
+ tmp_path,
74
+ )
75
+ run(["git", "config", "user.email", "test@example.com"], updater)
76
+ run(["git", "config", "user.name", "Test User"], updater)
77
+ run(["git", "checkout", "main"], updater)
78
+ (updater / "README").write_text("updated\n")
79
+ run(["git", "commit", "-am", "advance main"], updater)
80
+ run(["git", "push"], updater)
81
+
82
+ result = guard("v0.0.67", release_repository)
83
+
84
+ assert result.returncode != 0
85
+ assert "does not point to origin/main" in result.stderr