base-cli 0.4.3__tar.gz → 0.5.0__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 (261) hide show
  1. base_cli-0.5.0/.github/ISSUE_TEMPLATE/bug_report.yml +50 -0
  2. base_cli-0.5.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. base_cli-0.5.0/.github/ISSUE_TEMPLATE/documentation.yml +25 -0
  4. base_cli-0.5.0/.github/ISSUE_TEMPLATE/feature_request.yml +25 -0
  5. base_cli-0.5.0/.github/ISSUE_TEMPLATE/support.md +12 -0
  6. base_cli-0.5.0/.github/ISSUE_TEMPLATE/support.yml +30 -0
  7. {base_cli-0.4.3 → base_cli-0.5.0}/.github/workflows/compatibility.yml +37 -4
  8. {base_cli-0.4.3 → base_cli-0.5.0}/.github/workflows/dependency-matrix.yml +1 -1
  9. {base_cli-0.4.3 → base_cli-0.5.0}/.github/workflows/docs.yml +6 -0
  10. {base_cli-0.4.3 → base_cli-0.5.0}/.github/workflows/package.yml +188 -18
  11. base_cli-0.5.0/.github/workflows/project-intake.yml +309 -0
  12. base_cli-0.5.0/.github/workflows/tests.yml +201 -0
  13. {base_cli-0.4.3 → base_cli-0.5.0}/CHANGELOG.md +101 -8
  14. {base_cli-0.4.3 → base_cli-0.5.0}/CONTRIBUTING.md +7 -10
  15. {base_cli-0.4.3 → base_cli-0.5.0}/PKG-INFO +120 -36
  16. {base_cli-0.4.3 → base_cli-0.5.0}/README.md +113 -32
  17. base_cli-0.5.0/VERSION +1 -0
  18. {base_cli-0.4.3 → base_cli-0.5.0}/base_manifest.yaml +6 -1
  19. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/README.md +22 -9
  20. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/atlas_click/README.md +2 -1
  21. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/atlas_click/pyproject.toml +1 -1
  22. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/atlas_click/tests/test_consumer.py +0 -1
  23. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/beacon_typer/README.md +5 -4
  24. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/beacon_typer/pyproject.toml +1 -1
  25. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/beacon_typer/src/beacon_typer/cli.py +0 -1
  26. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/beacon_typer/tests/test_consumer.py +0 -1
  27. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/cinder_automation/README.md +1 -0
  28. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/cinder_automation/pyproject.toml +1 -1
  29. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/cinder_automation/src/cinder_automation/cli.py +4 -3
  30. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/cinder_automation/tests/test_consumer.py +0 -1
  31. {base_cli-0.4.3 → base_cli-0.5.0}/docs/adopter-readiness.md +4 -3
  32. {base_cli-0.4.3 → base_cli-0.5.0}/docs/adoption-evidence.md +13 -2
  33. {base_cli-0.4.3 → base_cli-0.5.0}/docs/api-reference.md +32 -32
  34. {base_cli-0.4.3 → base_cli-0.5.0}/docs/api-stability.md +13 -4
  35. {base_cli-0.4.3 → base_cli-0.5.0}/docs/cache-ownership-and-layout.md +23 -2
  36. {base_cli-0.4.3 → base_cli-0.5.0}/docs/compatibility-dashboard.md +2 -2
  37. {base_cli-0.4.3 → base_cli-0.5.0}/docs/consumer-profiles.md +28 -0
  38. base_cli-0.5.0/docs/consumer-quickstart.md +92 -0
  39. {base_cli-0.4.3 → base_cli-0.5.0}/docs/coverage-policy.md +5 -0
  40. {base_cli-0.4.3 → base_cli-0.5.0}/docs/dependency-support.md +3 -3
  41. {base_cli-0.4.3 → base_cli-0.5.0}/docs/extensions.md +6 -0
  42. {base_cli-0.4.3 → base_cli-0.5.0}/docs/index.md +10 -0
  43. base_cli-0.5.0/docs/integrations.md +85 -0
  44. {base_cli-0.4.3 → base_cli-0.5.0}/docs/json-contracts.md +68 -4
  45. base_cli-0.5.0/docs/local-config.md +62 -0
  46. base_cli-0.5.0/docs/migration-0.5.md +33 -0
  47. base_cli-0.5.0/docs/optional-output-dependencies.md +78 -0
  48. {base_cli-0.4.3 → base_cli-0.5.0}/docs/output-contracts.md +11 -0
  49. base_cli-0.5.0/docs/performance.md +210 -0
  50. {base_cli-0.4.3 → base_cli-0.5.0}/docs/platform-support.md +30 -5
  51. base_cli-0.5.0/docs/release-0.5.0-checklist.md +55 -0
  52. {base_cli-0.4.3 → base_cli-0.5.0}/docs/releasing.md +81 -14
  53. {base_cli-0.4.3 → base_cli-0.5.0}/docs/security-review.md +1 -1
  54. {base_cli-0.4.3 → base_cli-0.5.0}/docs/security-threat-model.md +19 -2
  55. base_cli-0.5.0/docs/strict-json-consumer.md +93 -0
  56. base_cli-0.5.0/docs/testing.md +86 -0
  57. {base_cli-0.4.3 → base_cli-0.5.0}/docs/typer-adapter.md +16 -6
  58. {base_cli-0.4.3 → base_cli-0.5.0}/examples/automation_observability_app/src/automation_observability_app/cli.py +3 -1
  59. {base_cli-0.4.3 → base_cli-0.5.0}/examples/minimal_cli/src/minimal_cli/cli.py +3 -1
  60. {base_cli-0.4.3 → base_cli-0.5.0}/examples/typer_app/README.md +6 -3
  61. {base_cli-0.4.3 → base_cli-0.5.0}/examples/typer_app/pyproject.toml +1 -1
  62. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/_app_core.py +71 -14
  63. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/_attach.py +12 -1
  64. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/_click_compat.py +37 -7
  65. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/_lifecycle.py +8 -1
  66. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/_lifecycle_install.py +46 -5
  67. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/_private_files.py +31 -5
  68. base_cli-0.5.0/lib/python/base_cli/_run.py +868 -0
  69. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/_runtime.py +354 -29
  70. base_cli-0.5.0/lib/python/base_cli/_stdout_capture.py +117 -0
  71. base_cli-0.5.0/lib/python/base_cli/_windows_retention.py +100 -0
  72. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/config.py +187 -15
  73. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/context.py +13 -0
  74. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/extensions.py +28 -23
  75. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/inspection.py +3 -2
  76. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/integrations.py +32 -8
  77. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/json_contracts.py +61 -16
  78. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/lifecycle_options.py +20 -1
  79. base_cli-0.5.0/lib/python/base_cli/logging.py +448 -0
  80. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/output.py +115 -39
  81. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/paths.py +36 -0
  82. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/profile.py +43 -11
  83. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/redaction.py +56 -6
  84. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/typer.py +1 -1
  85. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli.egg-info/PKG-INFO +120 -36
  86. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli.egg-info/SOURCES.txt +41 -1
  87. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli.egg-info/requires.txt +5 -2
  88. {base_cli-0.4.3 → base_cli-0.5.0}/pyproject.toml +7 -5
  89. base_cli-0.5.0/scripts/benchmark_runtime.py +912 -0
  90. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/generate_api_reference.py +41 -2
  91. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/generate_release_metadata.py +20 -10
  92. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/record_compatibility_evidence.py +17 -1
  93. base_cli-0.5.0/scripts/release_metadata_helpers.py +24 -0
  94. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_changelog.py +88 -1
  95. base_cli-0.5.0/scripts/validate_consumer_typing.py +48 -0
  96. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_coverage.py +25 -3
  97. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_docs.py +6 -0
  98. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_package_artifact.py +1 -1
  99. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_release_metadata.py +28 -10
  100. base_cli-0.5.0/scripts/validate_release_provenance.py +181 -0
  101. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_release_ref.py +8 -2
  102. base_cli-0.5.0/scripts/verify_release_assets.py +178 -0
  103. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_adversarial_regressions.py +4 -1
  104. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_lifecycle.py +4 -0
  105. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_registration.py +16 -3
  106. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_run.py +75 -0
  107. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_run_metadata.py +185 -3
  108. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_runtime_boundary.py +9 -0
  109. base_cli-0.5.0/tests/test_batteries_included_config.py +362 -0
  110. base_cli-0.5.0/tests/test_benchmark_runtime.py +235 -0
  111. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_cleanup_security.py +35 -2
  112. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_click_tree_attachment.py +157 -3
  113. base_cli-0.5.0/tests/test_community_issue_templates.py +18 -0
  114. base_cli-0.5.0/tests/test_config_discovery_trust.py +93 -0
  115. base_cli-0.5.0/tests/test_consumer_quickstart_docs.py +28 -0
  116. base_cli-0.5.0/tests/test_dependency_documentation.py +29 -0
  117. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_explicit_config_validation.py +1 -1
  118. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_extensions.py +85 -0
  119. base_cli-0.5.0/tests/test_framework_config_runtime.py +316 -0
  120. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_generate_compatibility_dashboard.py +3 -3
  121. base_cli-0.5.0/tests/test_generated_api_reference.py +17 -0
  122. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_integrations.py +68 -0
  123. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_json_contracts.py +205 -2
  124. base_cli-0.5.0/tests/test_json_descriptor_capture.py +78 -0
  125. base_cli-0.5.0/tests/test_logger_ownership.py +111 -0
  126. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_logging.py +86 -0
  127. base_cli-0.5.0/tests/test_logging_hot_path.py +217 -0
  128. base_cli-0.5.0/tests/test_optional_output_dependencies_docs.py +29 -0
  129. base_cli-0.5.0/tests/test_optional_yaml_dependency.py +87 -0
  130. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_output.py +125 -0
  131. base_cli-0.5.0/tests/test_package_workflow.py +38 -0
  132. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_paths.py +36 -1
  133. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_platform_edge_paths.py +89 -2
  134. base_cli-0.5.0/tests/test_project_intake.py +509 -0
  135. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_public_api.py +1 -0
  136. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_redaction_security.py +84 -0
  137. base_cli-0.5.0/tests/test_retention_contention.py +93 -0
  138. base_cli-0.5.0/tests/test_retention_platform.py +61 -0
  139. base_cli-0.5.0/tests/test_run_app_reentrancy.py +122 -0
  140. base_cli-0.5.0/tests/test_run_bundle_retention.py +432 -0
  141. base_cli-0.5.0/tests/test_run_json_preflight.py +227 -0
  142. base_cli-0.5.0/tests/test_strict_json_consumer_docs.py +27 -0
  143. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_typer_adapter.py +38 -0
  144. base_cli-0.5.0/tests/test_validate_changelog.py +120 -0
  145. base_cli-0.5.0/tests/test_validate_consumer_typing.py +34 -0
  146. base_cli-0.5.0/tests/test_validate_coverage.py +26 -0
  147. base_cli-0.5.0/tests/test_validate_package_artifact.py +11 -0
  148. base_cli-0.5.0/tests/test_validate_release_provenance.py +227 -0
  149. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_validate_release_ref.py +4 -0
  150. base_cli-0.5.0/tests/test_verify_release_assets.py +139 -0
  151. base_cli-0.5.0/tests/test_windows_retention.py +43 -0
  152. base_cli-0.4.3/.github/ISSUE_TEMPLATE/support.md +0 -28
  153. base_cli-0.4.3/.github/workflows/project-intake.yml +0 -255
  154. base_cli-0.4.3/.github/workflows/tests.yml +0 -147
  155. base_cli-0.4.3/VERSION +0 -1
  156. base_cli-0.4.3/docs/integrations.md +0 -54
  157. base_cli-0.4.3/docs/local-config.md +0 -24
  158. base_cli-0.4.3/docs/performance.md +0 -33
  159. base_cli-0.4.3/lib/python/base_cli/_run.py +0 -431
  160. base_cli-0.4.3/lib/python/base_cli/logging.py +0 -292
  161. base_cli-0.4.3/scripts/benchmark_runtime.py +0 -114
  162. base_cli-0.4.3/tests/test_batteries_included_config.py +0 -173
  163. base_cli-0.4.3/tests/test_benchmark_runtime.py +0 -21
  164. base_cli-0.4.3/tests/test_optional_yaml_dependency.py +0 -42
  165. base_cli-0.4.3/tests/test_run_bundle_retention.py +0 -190
  166. base_cli-0.4.3/tests/test_validate_changelog.py +0 -64
  167. {base_cli-0.4.3 → base_cli-0.5.0}/.github/base-project.yml +0 -0
  168. {base_cli-0.4.3 → base_cli-0.5.0}/.github/pull_request_template.md +0 -0
  169. {base_cli-0.4.3 → base_cli-0.5.0}/.github/workflows/examples.yml +0 -0
  170. {base_cli-0.4.3 → base_cli-0.5.0}/.github/workflows/issue-branch-policy.yml +0 -0
  171. {base_cli-0.4.3 → base_cli-0.5.0}/LICENSE +0 -0
  172. {base_cli-0.4.3 → base_cli-0.5.0}/MANIFEST.in +0 -0
  173. {base_cli-0.4.3 → base_cli-0.5.0}/SECURITY.md +0 -0
  174. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/atlas_click/src/atlas_click/__init__.py +0 -0
  175. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/atlas_click/src/atlas_click/cli.py +1 -1
  176. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/beacon_typer/src/beacon_typer/__init__.py +0 -0
  177. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/cinder_automation/src/cinder_automation/__init__.py +0 -0
  178. {base_cli-0.4.3 → base_cli-0.5.0}/compatibility/consumers/manifest.json +0 -0
  179. {base_cli-0.4.3 → base_cli-0.5.0}/docs/framework-choice.md +0 -0
  180. {base_cli-0.4.3 → base_cli-0.5.0}/docs/migration-argparse.md +0 -0
  181. {base_cli-0.4.3 → base_cli-0.5.0}/docs/migration-cement.md +0 -0
  182. {base_cli-0.4.3 → base_cli-0.5.0}/docs/migration-click.md +0 -0
  183. {base_cli-0.4.3 → base_cli-0.5.0}/docs/migration-typer.md +0 -0
  184. {base_cli-0.4.3 → base_cli-0.5.0}/docs/migrations.md +0 -0
  185. {base_cli-0.4.3 → base_cli-0.5.0}/docs/schemas.md +0 -0
  186. {base_cli-0.4.3 → base_cli-0.5.0}/docs/user-config-typing.md +0 -0
  187. {base_cli-0.4.3 → base_cli-0.5.0}/examples/README.md +0 -0
  188. {base_cli-0.4.3 → base_cli-0.5.0}/examples/automation_observability_app/README.md +0 -0
  189. {base_cli-0.4.3 → base_cli-0.5.0}/examples/automation_observability_app/pyproject.toml +0 -0
  190. {base_cli-0.4.3 → base_cli-0.5.0}/examples/automation_observability_app/src/automation_observability_app/__init__.py +0 -0
  191. {base_cli-0.4.3 → base_cli-0.5.0}/examples/automation_observability_app/tests/test_cli.py +0 -0
  192. {base_cli-0.4.3 → base_cli-0.5.0}/examples/minimal_cli/README.md +0 -0
  193. {base_cli-0.4.3 → base_cli-0.5.0}/examples/minimal_cli/pyproject.toml +0 -0
  194. {base_cli-0.4.3 → base_cli-0.5.0}/examples/minimal_cli/src/minimal_cli/__init__.py +0 -0
  195. {base_cli-0.4.3 → base_cli-0.5.0}/examples/minimal_cli/tests/test_cli.py +0 -0
  196. {base_cli-0.4.3 → base_cli-0.5.0}/examples/nested_click_app/README.md +0 -0
  197. {base_cli-0.4.3 → base_cli-0.5.0}/examples/nested_click_app/pyproject.toml +0 -0
  198. {base_cli-0.4.3 → base_cli-0.5.0}/examples/nested_click_app/src/nested_click_app/__init__.py +0 -0
  199. {base_cli-0.4.3 → base_cli-0.5.0}/examples/nested_click_app/src/nested_click_app/cli.py +0 -0
  200. {base_cli-0.4.3 → base_cli-0.5.0}/examples/nested_click_app/src/nested_click_app/plugin.py +0 -0
  201. {base_cli-0.4.3 → base_cli-0.5.0}/examples/nested_click_app/tests/test_cli.py +0 -0
  202. {base_cli-0.4.3 → base_cli-0.5.0}/examples/typed_consumer.py +0 -0
  203. {base_cli-0.4.3 → base_cli-0.5.0}/examples/typer_app/src/typer_app/__init__.py +0 -0
  204. {base_cli-0.4.3 → base_cli-0.5.0}/examples/typer_app/src/typer_app/cli.py +0 -0
  205. {base_cli-0.4.3 → base_cli-0.5.0}/examples/typer_app/tests/test_cli.py +0 -0
  206. {base_cli-0.4.3 → base_cli-0.5.0}/examples/typer_consumer.py +0 -0
  207. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/__init__.py +0 -0
  208. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/_cleanup.py +0 -0
  209. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/_dependencies.py +0 -0
  210. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/app.py +0 -0
  211. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/asyncio_adapter.py +0 -0
  212. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/attachment.py +0 -0
  213. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/command_filters.py +0 -0
  214. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/command_protocol.py +0 -0
  215. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/deprecations.py +0 -0
  216. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/errors.py +0 -0
  217. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/exit_codes.py +0 -0
  218. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/experimental.py +0 -0
  219. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/history.py +0 -0
  220. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/py.typed +0 -0
  221. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/runtime.py +0 -0
  222. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/schemas/v1/command-protocol.schema.json +0 -0
  223. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/schemas/v1/error.schema.json +0 -0
  224. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/schemas/v1/inspection.schema.json +0 -0
  225. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/schemas/v1/log.schema.json +0 -0
  226. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/schemas/v1/ndjson.schema.json +0 -0
  227. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/schemas/v1/output.schema.json +0 -0
  228. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli/testing.py +0 -0
  229. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli.egg-info/dependency_links.txt +0 -0
  230. {base_cli-0.4.3 → base_cli-0.5.0}/lib/python/base_cli.egg-info/top_level.txt +0 -0
  231. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/generate_compatibility_dashboard.py +0 -0
  232. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_consumers.py +0 -0
  233. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_contract_fixtures.py +0 -0
  234. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_examples.py +0 -0
  235. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_installed_package.py +0 -0
  236. {base_cli-0.4.3 → base_cli-0.5.0}/scripts/validate_schemas.py +0 -0
  237. {base_cli-0.4.3 → base_cli-0.5.0}/setup.cfg +0 -0
  238. {base_cli-0.4.3 → base_cli-0.5.0}/tests/conftest.py +0 -0
  239. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_api_stability.py +0 -0
  240. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_dry_run.py +0 -0
  241. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_log_retention.py +0 -0
  242. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_module_boundaries.py +0 -0
  243. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_quiet.py +0 -0
  244. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_runtime_errors.py +0 -0
  245. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_security_boundaries.py +0 -0
  246. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_startup_transaction.py +0 -0
  247. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_app_subcommands.py +0 -0
  248. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_command_filters.py +0 -0
  249. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_command_protocol.py +0 -0
  250. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_context_workspace.py +0 -0
  251. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_generic_core.py +0 -0
  252. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_history.py +0 -0
  253. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_inspection.py +0 -0
  254. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_invocation_parity.py +0 -0
  255. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_lifecycle_options.py +0 -0
  256. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_profile.py +0 -0
  257. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_security_documentation.py +0 -0
  258. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_testing.py +0 -0
  259. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_typed_contracts.py +0 -0
  260. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_validate_docs.py +0 -0
  261. {base_cli-0.4.3 → base_cli-0.5.0}/tests/test_validate_schemas.py +0 -0
@@ -0,0 +1,50 @@
1
+ name: Bug report
2
+ description: Report a reproducible base-cli defect
3
+ title: "bug: "
4
+ labels:
5
+ - bug
6
+ body:
7
+ - type: markdown
8
+ attributes:
9
+ value: |
10
+ Do not include secrets, personal data, or vulnerability details. Use SECURITY.md for private reports.
11
+ - type: input
12
+ id: version
13
+ attributes:
14
+ label: Version or commit
15
+ description: Include the installed release, package revision, or commit.
16
+ validations:
17
+ required: true
18
+ - type: input
19
+ id: environment
20
+ attributes:
21
+ label: Environment
22
+ description: Include Python, Click/Typer, OS, architecture, and relevant extras.
23
+ validations:
24
+ required: true
25
+ - type: textarea
26
+ id: reproduction
27
+ attributes:
28
+ label: Reproduction
29
+ description: Give the smallest command, fixture, or test that reproduces the problem.
30
+ validations:
31
+ required: true
32
+ - type: textarea
33
+ id: expected
34
+ attributes:
35
+ label: Expected behavior
36
+ validations:
37
+ required: true
38
+ - type: textarea
39
+ id: actual
40
+ attributes:
41
+ label: Actual behavior
42
+ render: text
43
+ validations:
44
+ required: true
45
+ - type: textarea
46
+ id: logs
47
+ attributes:
48
+ label: Relevant logs
49
+ description: Redact credentials, tokens, personal data, and private paths.
50
+ render: text
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Support and adoption questions
4
+ url: https://github.com/orgs/basefoundry/discussions
5
+ about: Ask ecosystem-wide usage and onboarding questions in the canonical public forum.
6
+ - name: Private security report
7
+ url: https://github.com/basefoundry/base-cli/security/advisories/new
8
+ about: Never disclose vulnerabilities or sensitive data in a public issue.
@@ -0,0 +1,25 @@
1
+ name: Documentation improvement
2
+ description: Report missing, incorrect, or confusing base-cli documentation
3
+ title: "docs: "
4
+ labels:
5
+ - documentation
6
+ body:
7
+ - type: input
8
+ id: location
9
+ attributes:
10
+ label: Documentation location
11
+ description: Give the URL or repository path.
12
+ validations:
13
+ required: true
14
+ - type: textarea
15
+ id: problem
16
+ attributes:
17
+ label: Problem
18
+ description: Explain what a reader cannot understand or do.
19
+ validations:
20
+ required: true
21
+ - type: textarea
22
+ id: suggestion
23
+ attributes:
24
+ label: Suggested improvement
25
+ description: Describe the corrected or clearer outcome.
@@ -0,0 +1,25 @@
1
+ name: Feature proposal
2
+ description: Propose a user-facing base-cli capability or improvement
3
+ title: "feature: "
4
+ labels:
5
+ - enhancement
6
+ body:
7
+ - type: textarea
8
+ id: problem
9
+ attributes:
10
+ label: User problem
11
+ description: Who is affected, and what are they unable to do today?
12
+ validations:
13
+ required: true
14
+ - type: textarea
15
+ id: outcome
16
+ attributes:
17
+ label: Desired outcome
18
+ description: Describe what success looks like without requiring a particular implementation.
19
+ validations:
20
+ required: true
21
+ - type: textarea
22
+ id: alternatives
23
+ attributes:
24
+ label: Alternatives and context
25
+ description: Include workarounds, constraints, or compatibility concerns.
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: Adoption support
3
+ about: Request migration or downstream compatibility help
4
+ title: "[Support] "
5
+ labels: ""
6
+ assignees: ""
7
+ ---
8
+
9
+ Use the Support and adoption question form when it is available. Include the
10
+ base-cli version, Python and operating-system versions, a minimal
11
+ reproduction, and redacted evidence. Never include secrets; use SECURITY.md
12
+ for private vulnerability reports.
@@ -0,0 +1,30 @@
1
+ name: Support and adoption question
2
+ description: Ask for help using or evaluating base-cli
3
+ title: "support: "
4
+ labels:
5
+ - adoption support
6
+ body:
7
+ - type: markdown
8
+ attributes:
9
+ value: |
10
+ For general ecosystem questions, prefer Base Discussions. Do not include secrets or security-sensitive details here.
11
+ - type: input
12
+ id: version
13
+ attributes:
14
+ label: Version or commit
15
+ validations:
16
+ required: true
17
+ - type: textarea
18
+ id: environment
19
+ attributes:
20
+ label: Environment
21
+ description: Include Python, Click/Typer, OS, architecture, and relevant extras.
22
+ validations:
23
+ required: true
24
+ - type: textarea
25
+ id: goal
26
+ attributes:
27
+ label: Goal and evidence
28
+ description: What are you trying to do, what did you try, and what happened?
29
+ validations:
30
+ required: true
@@ -37,6 +37,33 @@ jobs:
37
37
  - name: Run adapter and Beacon compatibility tests
38
38
  run: python -m pytest tests/test_typer_adapter.py compatibility/consumers/beacon_typer/tests
39
39
 
40
+ typer-click-boundaries:
41
+ name: Typer ${{ matrix.typer-version }} / Click ${{ matrix.click-version }} / Python ${{ matrix.python-version }}
42
+ runs-on: ubuntu-latest
43
+ timeout-minutes: 20
44
+ strategy:
45
+ fail-fast: false
46
+ matrix:
47
+ python-version: ["3.10", "3.14"]
48
+ typer-version: ["0.25.1", "0.27.2"]
49
+ click-version: ["8.2.1", "8.5.0"]
50
+ steps:
51
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
52
+ - name: Set up Python
53
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
54
+ with:
55
+ python-version: ${{ matrix.python-version }}
56
+ - name: Install framework and supported Typer adapter
57
+ run: |
58
+ python -m pip install "setuptools<77" "wheel"
59
+ python -m pip install ".[dev,typer]"
60
+ - name: Pin the tested Click/Typer boundary pair
61
+ run: python -m pip install --upgrade "click==${{ matrix.click-version }}" "typer==${{ matrix.typer-version }}"
62
+ - name: Install Beacon fixture without dependency resolution
63
+ run: python -m pip install --no-deps compatibility/consumers/beacon_typer
64
+ - name: Run adapter and Beacon compatibility tests
65
+ run: python -m pytest tests/test_typer_adapter.py compatibility/consumers/beacon_typer/tests
66
+
40
67
  downstream:
41
68
  name: Install and test independent consumers
42
69
  runs-on: ubuntu-latest
@@ -56,11 +83,17 @@ jobs:
56
83
  python -m pip install dist/*.whl
57
84
  - name: Validate consumer manifest
58
85
  run: python scripts/validate_consumers.py
59
- - name: Install consumers independently
86
+ - name: Install consumers without dependency resolution
87
+ run: |
88
+ python -m pip install --no-deps compatibility/consumers/atlas_click
89
+ python -m pip install --no-deps compatibility/consumers/beacon_typer
90
+ python -m pip install --no-deps "compatibility/consumers/cinder_automation[observability]"
91
+ - name: Verify the installed framework matches the source release
60
92
  run: |
61
- python -m pip install compatibility/consumers/atlas_click
62
- python -m pip install compatibility/consumers/beacon_typer
63
- python -m pip install "compatibility/consumers/cinder_automation[observability]"
93
+ expected="$(tr -d '\r\n' < VERSION)"
94
+ actual="$(python -c 'from importlib.metadata import version; print(version("base-cli"))')"
95
+ test "$actual" = "$expected"
96
+ python -m pip check
64
97
  - name: Run downstream compatibility suites
65
98
  run: |
66
99
  for tests in compatibility/consumers/*/tests; do
@@ -20,7 +20,7 @@ jobs:
20
20
  fail-fast: false
21
21
  matrix:
22
22
  python-version: ["3.10", "3.14"]
23
- click-version: ["8.1.*", "8.2.*", "8.3.*", "8.4.*"]
23
+ click-version: ["8.1.*", "8.2.*", "8.3.*", "8.4.*", "8.5.*"]
24
24
  pyyaml-version: ["6.0.*"]
25
25
  steps:
26
26
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -22,6 +22,8 @@ on:
22
22
  - ".github/workflows/dependency-matrix.yml"
23
23
  - ".github/workflows/compatibility.yml"
24
24
  - "tests/validate.sh"
25
+ - "tests/full_validate.sh"
26
+ - "docs/testing.md"
25
27
  - ".github/workflows/docs.yml"
26
28
  pull_request:
27
29
  paths:
@@ -33,6 +35,8 @@ on:
33
35
  - "scripts/validate_docs.py"
34
36
  - "scripts/generate_api_reference.py"
35
37
  - "tests/validate.sh"
38
+ - "tests/full_validate.sh"
39
+ - "docs/testing.md"
36
40
  - ".github/workflows/docs.yml"
37
41
  workflow_dispatch:
38
42
 
@@ -51,6 +55,8 @@ jobs:
51
55
  steps:
52
56
  - name: Check out source
53
57
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
58
+ with:
59
+ fetch-depth: 0
54
60
 
55
61
  - name: Set up Python
56
62
  uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -14,12 +14,19 @@ on:
14
14
  - "scripts/validate_examples.py"
15
15
  - "scripts/generate_release_metadata.py"
16
16
  - "scripts/validate_release_metadata.py"
17
+ - "scripts/verify_release_assets.py"
17
18
  - "scripts/validate_changelog.py"
18
19
  - "scripts/validate_release_ref.py"
20
+ - "scripts/validate_release_provenance.py"
19
21
  - "CHANGELOG.md"
20
22
  - "examples/**"
21
23
  - "compatibility/**"
22
24
  - "docs/releasing.md"
25
+ - "tests/test_package_workflow.py"
26
+ - "tests/test_verify_release_assets.py"
27
+ - "tests/test_validate_release_provenance.py"
28
+ - "requirements/release.in"
29
+ - "requirements/release.txt"
23
30
  - ".github/workflows/package.yml"
24
31
  push:
25
32
  branches:
@@ -57,6 +64,8 @@ jobs:
57
64
  steps:
58
65
  - name: Check out source
59
66
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
67
+ with:
68
+ fetch-depth: 0
60
69
 
61
70
  - name: Set up Python
62
71
  uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -80,9 +89,6 @@ jobs:
80
89
  exit 1
81
90
  fi
82
91
 
83
- - name: Validate repository baseline
84
- run: ./tests/validate.sh
85
-
86
92
  - name: Validate changelog
87
93
  run: python scripts/validate_changelog.py
88
94
 
@@ -98,10 +104,48 @@ jobs:
98
104
  mkdir -p dist
99
105
 
100
106
  - name: Install build and validation tools
101
- run: python -m pip install --upgrade build twine
107
+ run: python -m pip install --require-hashes --no-deps -r requirements/release.txt
102
108
 
103
109
  - name: Build sdist and wheel
104
- run: python -m build --sdist --wheel --outdir dist
110
+ env:
111
+ # Keep the reproducibility input numeric; push-event timestamps are
112
+ # ISO-8601 strings and are not accepted by wheel/build backends.
113
+ SOURCE_DATE_EPOCH: '0'
114
+ run: |
115
+ rm -rf dist-a dist-b "$RUNNER_TEMP/base-cli-build-a" "$RUNNER_TEMP/base-cli-build-b"
116
+ mkdir -p dist-a dist-b
117
+ mkdir -p "$RUNNER_TEMP/base-cli-build-a" "$RUNNER_TEMP/base-cli-build-b"
118
+ git archive "$GITHUB_SHA" | tar -x -C "$RUNNER_TEMP/base-cli-build-a"
119
+ git archive "$GITHUB_SHA" | tar -x -C "$RUNNER_TEMP/base-cli-build-b"
120
+ (cd "$RUNNER_TEMP/base-cli-build-a" && python -m build --sdist --wheel --outdir "$GITHUB_WORKSPACE/dist-a")
121
+ (cd "$RUNNER_TEMP/base-cli-build-b" && python -m build --sdist --wheel --outdir "$GITHUB_WORKSPACE/dist-b")
122
+ python - <<'PY'
123
+ import gzip
124
+ import os
125
+ import tarfile
126
+ from pathlib import Path
127
+
128
+ epoch = int(os.environ["SOURCE_DATE_EPOCH"])
129
+ for directory in (Path("dist-a"), Path("dist-b")):
130
+ for path in directory.glob("*.tar.gz"):
131
+ with tarfile.open(path, "r:gz") as source:
132
+ members = [(member, source.extractfile(member).read() if member.isfile() else None) for member in source.getmembers()]
133
+ temporary = path.with_suffix(path.suffix + ".tmp")
134
+ with temporary.open("wb") as raw:
135
+ with gzip.GzipFile(filename=path.name, mode="wb", fileobj=raw, mtime=epoch) as compressed:
136
+ with tarfile.open(fileobj=compressed, mode="w|") as target:
137
+ for member, payload in sorted(members, key=lambda item: item[0].name):
138
+ member.mtime = epoch
139
+ member.uid = member.gid = 0
140
+ member.uname = member.gname = ""
141
+ member.pax_headers = {}
142
+ target.addfile(member, None if payload is None else __import__("io").BytesIO(payload))
143
+ temporary.replace(path)
144
+ PY
145
+ (cd dist-a && sha256sum * | sort) > dist-a.SHA256SUMS
146
+ (cd dist-b && sha256sum * | sort) > dist-b.SHA256SUMS
147
+ diff -u dist-a.SHA256SUMS dist-b.SHA256SUMS
148
+ cp dist-a/* dist/
105
149
 
106
150
  - name: Validate artifact contents and metadata
107
151
  run: python scripts/validate_package_artifact.py dist
@@ -112,7 +156,7 @@ jobs:
112
156
  - name: Generate release checksums and SPDX SBOM
113
157
  env:
114
158
  SOURCE_REVISION: ${{ github.sha }}
115
- SOURCE_DATE_EPOCH: ${{ github.event.head_commit.timestamp || '0' }}
159
+ SOURCE_DATE_EPOCH: '0'
116
160
  run: python scripts/generate_release_metadata.py dist
117
161
 
118
162
  - name: Validate release checksums and SPDX SBOM
@@ -137,6 +181,7 @@ jobs:
137
181
  path: |
138
182
  dist/SBOM.spdx.json
139
183
  dist/SHA256SUMS
184
+ dist/RELEASE-BOM-ROW.json
140
185
  if-no-files-found: error
141
186
  retention-days: 90
142
187
 
@@ -152,6 +197,8 @@ jobs:
152
197
  steps:
153
198
  - name: Check out source
154
199
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
200
+ with:
201
+ fetch-depth: 0
155
202
 
156
203
  - name: Set up Python
157
204
  uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -190,9 +237,31 @@ jobs:
190
237
  - name: Check installed dependency consistency
191
238
  run: python -m pip check
192
239
 
240
+ provenance:
241
+ name: Verify reviewed release provenance
242
+ needs: [build, smoke]
243
+ if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || github.event_name == 'workflow_dispatch' }}
244
+ runs-on: ubuntu-latest
245
+ timeout-minutes: 10
246
+ permissions:
247
+ contents: read
248
+ steps:
249
+ - name: Check out source
250
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
251
+ with:
252
+ fetch-depth: 0
253
+
254
+ - name: Fetch trusted main history
255
+ run: git fetch --no-tags --prune origin "refs/heads/main:refs/remotes/origin/main"
256
+
257
+ - name: Verify release provenance
258
+ env:
259
+ PUBLISH_TARGET: ${{ inputs.publish_target || '' }}
260
+ run: python scripts/validate_release_provenance.py
261
+
193
262
  publish:
194
263
  name: Publish reviewed distribution
195
- needs: [build, smoke]
264
+ needs: [build, smoke, provenance]
196
265
  if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || github.event_name == 'workflow_dispatch' }}
197
266
  runs-on: ubuntu-latest
198
267
  timeout-minutes: 10
@@ -224,7 +293,7 @@ jobs:
224
293
 
225
294
  attest:
226
295
  name: Attest reviewed release
227
- needs: [build, smoke]
296
+ needs: [build, smoke, provenance]
228
297
  if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || github.event_name == 'workflow_dispatch' }}
229
298
  runs-on: ubuntu-latest
230
299
  timeout-minutes: 10
@@ -258,13 +327,22 @@ jobs:
258
327
 
259
328
  release:
260
329
  name: Create GitHub Release
261
- needs: [build, smoke, publish, attest]
330
+ needs: [build, smoke, provenance, publish, attest]
262
331
  if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
263
332
  runs-on: ubuntu-latest
264
333
  timeout-minutes: 10
265
334
  permissions:
266
335
  contents: write
336
+ attestations: read
267
337
  steps:
338
+ - name: Check out the tag source
339
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
340
+
341
+ - name: Set up Python
342
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
343
+ with:
344
+ python-version: "3.13"
345
+
268
346
  - name: Download reviewed distributions
269
347
  uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
270
348
  with:
@@ -277,18 +355,110 @@ jobs:
277
355
  name: base-cli-release-metadata-${{ github.run_id }}
278
356
  path: dist
279
357
 
280
- - name: Create or update GitHub Release
358
+ - name: Verify and create immutable GitHub Release
281
359
  env:
282
360
  GH_TOKEN: ${{ github.token }}
283
361
  run: |
362
+ set -euo pipefail
284
363
  tag="$GITHUB_REF_NAME"
285
- assets=(dist/*.whl dist/*.tar.gz dist/SHA256SUMS dist/SBOM.spdx.json)
286
- if gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
287
- gh release upload "$tag" "${assets[@]}" --clobber --repo "$GITHUB_REPOSITORY"
288
- else
289
- gh release create "$tag" "${assets[@]}" \
364
+ assets=(dist/*.whl dist/*.tar.gz dist/SHA256SUMS dist/SBOM.spdx.json dist/RELEASE-BOM-ROW.json)
365
+ tag_commit="$(gh api "repos/$GITHUB_REPOSITORY/commits/$tag" --jq .sha)"
366
+ if [[ "$tag_commit" != "$GITHUB_SHA" ]]; then
367
+ echo "Release tag $tag resolves to $tag_commit, not reviewed commit $GITHUB_SHA." >&2
368
+ exit 1
369
+ fi
370
+
371
+ python scripts/validate_release_metadata.py dist
372
+ for asset in dist/*.whl dist/*.tar.gz; do
373
+ gh attestation verify "$asset" \
290
374
  --repo "$GITHUB_REPOSITORY" \
291
- --title "$tag" \
292
- --generate-notes \
293
- --notes "Published distributions and release metadata for $tag. See CHANGELOG.md for the reviewed release notes."
375
+ --signer-workflow "github.com/$GITHUB_REPOSITORY/.github/workflows/package.yml" \
376
+ --source-digest "$GITHUB_SHA" \
377
+ --source-ref "$GITHUB_REF"
378
+ gh attestation verify "$asset" \
379
+ --repo "$GITHUB_REPOSITORY" \
380
+ --signer-workflow "github.com/$GITHUB_REPOSITORY/.github/workflows/package.yml" \
381
+ --source-digest "$GITHUB_SHA" \
382
+ --source-ref "$GITHUB_REF" \
383
+ --predicate-type "https://spdx.dev/Document"
384
+ done
385
+
386
+ release_tmp="$(mktemp -d "$RUNNER_TEMP/base-cli-release.XXXXXX")"
387
+ trap 'rm -rf "$release_tmp"' EXIT
388
+ release_json="$release_tmp/release.json"
389
+ release_error="$release_tmp/release-error.txt"
390
+ release_response="$release_tmp/release-response.txt"
391
+
392
+ read_release_metadata() {
393
+ local retry_404="${1:-false}"
394
+ local attempt=1
395
+ local status_line
396
+ while :; do
397
+ if gh api --include "repos/$GITHUB_REPOSITORY/releases/tags/$tag" \
398
+ >"$release_response" 2>"$release_error"; then
399
+ awk 'BEGIN { body = 0 } { sub(/\r$/, ""); if (body) print; else if ($0 == "") body = 1 }' \
400
+ "$release_response" >"$release_json"
401
+ return 0
402
+ fi
403
+ status_line="$(sed -n '1s/\r$//p' "$release_response")"
404
+ if [[ "$status_line" =~ ^HTTP/[0-9.]+[[:space:]]404[[:space:]] ]]; then
405
+ if [[ "$retry_404" != true || "$attempt" -ge 5 ]]; then
406
+ return 1
407
+ fi
408
+ sleep $((attempt * 2))
409
+ attempt=$((attempt + 1))
410
+ continue
411
+ fi
412
+ cat "$release_error" >&2
413
+ return 2
414
+ done
415
+ }
416
+
417
+ verify_existing_release() {
418
+ local existing_assets="$release_tmp/existing-assets"
419
+ mkdir -p "$existing_assets"
420
+ gh release download "$tag" --repo "$GITHUB_REPOSITORY" --dir "$existing_assets"
421
+ python scripts/verify_release_assets.py \
422
+ --expected-dir dist \
423
+ --existing-dir "$existing_assets" \
424
+ --release-json "$release_json" \
425
+ --version-file VERSION \
426
+ --tag "$tag" \
427
+ --source-commit "$GITHUB_SHA" \
428
+ --resolved-tag-commit "$tag_commit"
429
+ }
430
+
431
+ if read_release_metadata; then
432
+ verify_existing_release
433
+ echo "Existing release $tag is byte-for-byte identical; leaving the immutable release unchanged."
434
+ exit 0
435
+ else
436
+ release_status=$?
437
+ if [[ "$release_status" -ne 1 ]]; then
438
+ exit "$release_status"
439
+ fi
440
+ fi
441
+
442
+ create_status=0
443
+ gh release create "$tag" "${assets[@]}" \
444
+ --repo "$GITHUB_REPOSITORY" \
445
+ --title "$tag" \
446
+ --generate-notes \
447
+ --notes "Published distributions and release metadata for $tag. See CHANGELOG.md for the reviewed release notes." \
448
+ || create_status=$?
449
+
450
+ if read_release_metadata true; then
451
+ verify_existing_release
452
+ if [[ "$create_status" -ne 0 ]]; then
453
+ echo "gh release create failed with status $create_status; refusing to hide the publication error." >&2
454
+ exit "$create_status"
455
+ fi
456
+ echo "Verified immutable release $tag after publication."
457
+ else
458
+ release_status=$?
459
+ if [[ "$create_status" -ne 0 ]]; then
460
+ exit "$create_status"
461
+ fi
462
+ echo "Release $tag was created but could not be read back (HTTP 404)." >&2
463
+ exit "$release_status"
294
464
  fi