base-cli 0.5.0__tar.gz → 0.5.1__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 (252) hide show
  1. {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/package.yml +105 -21
  2. {base_cli-0.5.0 → base_cli-0.5.1}/CHANGELOG.md +11 -1
  3. {base_cli-0.5.0 → base_cli-0.5.1}/PKG-INFO +6 -8
  4. {base_cli-0.5.0 → base_cli-0.5.1}/README.md +5 -7
  5. base_cli-0.5.1/VERSION +1 -0
  6. {base_cli-0.5.0 → base_cli-0.5.1}/docs/adoption-evidence.md +3 -5
  7. {base_cli-0.5.0 → base_cli-0.5.1}/docs/index.md +1 -1
  8. {base_cli-0.5.0 → base_cli-0.5.1}/docs/releasing.md +16 -0
  9. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli.egg-info/PKG-INFO +6 -8
  10. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli.egg-info/SOURCES.txt +4 -0
  11. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/generate_release_metadata.py +1 -0
  12. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_package_artifact.py +46 -4
  13. base_cli-0.5.1/scripts/validate_release_docs.py +66 -0
  14. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_release_metadata.py +3 -0
  15. base_cli-0.5.1/scripts/verify_pypi_metadata.py +76 -0
  16. base_cli-0.5.1/tests/test_package_workflow.py +78 -0
  17. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_package_artifact.py +5 -1
  18. base_cli-0.5.1/tests/test_validate_release_docs.py +49 -0
  19. base_cli-0.5.1/tests/test_verify_pypi_metadata.py +34 -0
  20. base_cli-0.5.0/VERSION +0 -1
  21. base_cli-0.5.0/tests/test_package_workflow.py +0 -38
  22. {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  23. {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  24. {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/documentation.yml +0 -0
  25. {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  26. {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/support.md +0 -0
  27. {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/support.yml +0 -0
  28. {base_cli-0.5.0 → base_cli-0.5.1}/.github/base-project.yml +0 -0
  29. {base_cli-0.5.0 → base_cli-0.5.1}/.github/pull_request_template.md +0 -0
  30. {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/compatibility.yml +0 -0
  31. {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/dependency-matrix.yml +0 -0
  32. {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/docs.yml +0 -0
  33. {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/examples.yml +0 -0
  34. {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/issue-branch-policy.yml +0 -0
  35. {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/project-intake.yml +0 -0
  36. {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/tests.yml +0 -0
  37. {base_cli-0.5.0 → base_cli-0.5.1}/CONTRIBUTING.md +0 -0
  38. {base_cli-0.5.0 → base_cli-0.5.1}/LICENSE +0 -0
  39. {base_cli-0.5.0 → base_cli-0.5.1}/MANIFEST.in +0 -0
  40. {base_cli-0.5.0 → base_cli-0.5.1}/SECURITY.md +0 -0
  41. {base_cli-0.5.0 → base_cli-0.5.1}/base_manifest.yaml +0 -0
  42. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/README.md +0 -0
  43. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/atlas_click/README.md +0 -0
  44. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/atlas_click/pyproject.toml +0 -0
  45. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/atlas_click/src/atlas_click/__init__.py +0 -0
  46. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/atlas_click/src/atlas_click/cli.py +0 -0
  47. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/atlas_click/tests/test_consumer.py +0 -0
  48. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/beacon_typer/README.md +0 -0
  49. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/beacon_typer/pyproject.toml +0 -0
  50. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/beacon_typer/src/beacon_typer/__init__.py +0 -0
  51. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/beacon_typer/src/beacon_typer/cli.py +0 -0
  52. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/beacon_typer/tests/test_consumer.py +0 -0
  53. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/cinder_automation/README.md +0 -0
  54. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/cinder_automation/pyproject.toml +0 -0
  55. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/cinder_automation/src/cinder_automation/__init__.py +0 -0
  56. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/cinder_automation/src/cinder_automation/cli.py +0 -0
  57. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/cinder_automation/tests/test_consumer.py +0 -0
  58. {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/manifest.json +0 -0
  59. {base_cli-0.5.0 → base_cli-0.5.1}/docs/adopter-readiness.md +0 -0
  60. {base_cli-0.5.0 → base_cli-0.5.1}/docs/api-reference.md +0 -0
  61. {base_cli-0.5.0 → base_cli-0.5.1}/docs/api-stability.md +0 -0
  62. {base_cli-0.5.0 → base_cli-0.5.1}/docs/cache-ownership-and-layout.md +0 -0
  63. {base_cli-0.5.0 → base_cli-0.5.1}/docs/compatibility-dashboard.md +0 -0
  64. {base_cli-0.5.0 → base_cli-0.5.1}/docs/consumer-profiles.md +0 -0
  65. {base_cli-0.5.0 → base_cli-0.5.1}/docs/consumer-quickstart.md +0 -0
  66. {base_cli-0.5.0 → base_cli-0.5.1}/docs/coverage-policy.md +0 -0
  67. {base_cli-0.5.0 → base_cli-0.5.1}/docs/dependency-support.md +0 -0
  68. {base_cli-0.5.0 → base_cli-0.5.1}/docs/extensions.md +0 -0
  69. {base_cli-0.5.0 → base_cli-0.5.1}/docs/framework-choice.md +0 -0
  70. {base_cli-0.5.0 → base_cli-0.5.1}/docs/integrations.md +0 -0
  71. {base_cli-0.5.0 → base_cli-0.5.1}/docs/json-contracts.md +0 -0
  72. {base_cli-0.5.0 → base_cli-0.5.1}/docs/local-config.md +0 -0
  73. {base_cli-0.5.0 → base_cli-0.5.1}/docs/migration-0.5.md +0 -0
  74. {base_cli-0.5.0 → base_cli-0.5.1}/docs/migration-argparse.md +0 -0
  75. {base_cli-0.5.0 → base_cli-0.5.1}/docs/migration-cement.md +0 -0
  76. {base_cli-0.5.0 → base_cli-0.5.1}/docs/migration-click.md +0 -0
  77. {base_cli-0.5.0 → base_cli-0.5.1}/docs/migration-typer.md +0 -0
  78. {base_cli-0.5.0 → base_cli-0.5.1}/docs/migrations.md +0 -0
  79. {base_cli-0.5.0 → base_cli-0.5.1}/docs/optional-output-dependencies.md +0 -0
  80. {base_cli-0.5.0 → base_cli-0.5.1}/docs/output-contracts.md +0 -0
  81. {base_cli-0.5.0 → base_cli-0.5.1}/docs/performance.md +0 -0
  82. {base_cli-0.5.0 → base_cli-0.5.1}/docs/platform-support.md +0 -0
  83. {base_cli-0.5.0 → base_cli-0.5.1}/docs/release-0.5.0-checklist.md +0 -0
  84. {base_cli-0.5.0 → base_cli-0.5.1}/docs/schemas.md +0 -0
  85. {base_cli-0.5.0 → base_cli-0.5.1}/docs/security-review.md +0 -0
  86. {base_cli-0.5.0 → base_cli-0.5.1}/docs/security-threat-model.md +0 -0
  87. {base_cli-0.5.0 → base_cli-0.5.1}/docs/strict-json-consumer.md +0 -0
  88. {base_cli-0.5.0 → base_cli-0.5.1}/docs/testing.md +0 -0
  89. {base_cli-0.5.0 → base_cli-0.5.1}/docs/typer-adapter.md +0 -0
  90. {base_cli-0.5.0 → base_cli-0.5.1}/docs/user-config-typing.md +0 -0
  91. {base_cli-0.5.0 → base_cli-0.5.1}/examples/README.md +0 -0
  92. {base_cli-0.5.0 → base_cli-0.5.1}/examples/automation_observability_app/README.md +0 -0
  93. {base_cli-0.5.0 → base_cli-0.5.1}/examples/automation_observability_app/pyproject.toml +0 -0
  94. {base_cli-0.5.0 → base_cli-0.5.1}/examples/automation_observability_app/src/automation_observability_app/__init__.py +0 -0
  95. {base_cli-0.5.0 → base_cli-0.5.1}/examples/automation_observability_app/src/automation_observability_app/cli.py +0 -0
  96. {base_cli-0.5.0 → base_cli-0.5.1}/examples/automation_observability_app/tests/test_cli.py +0 -0
  97. {base_cli-0.5.0 → base_cli-0.5.1}/examples/minimal_cli/README.md +0 -0
  98. {base_cli-0.5.0 → base_cli-0.5.1}/examples/minimal_cli/pyproject.toml +0 -0
  99. {base_cli-0.5.0 → base_cli-0.5.1}/examples/minimal_cli/src/minimal_cli/__init__.py +0 -0
  100. {base_cli-0.5.0 → base_cli-0.5.1}/examples/minimal_cli/src/minimal_cli/cli.py +0 -0
  101. {base_cli-0.5.0 → base_cli-0.5.1}/examples/minimal_cli/tests/test_cli.py +0 -0
  102. {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/README.md +0 -0
  103. {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/pyproject.toml +0 -0
  104. {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/src/nested_click_app/__init__.py +0 -0
  105. {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/src/nested_click_app/cli.py +0 -0
  106. {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/src/nested_click_app/plugin.py +0 -0
  107. {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/tests/test_cli.py +0 -0
  108. {base_cli-0.5.0 → base_cli-0.5.1}/examples/typed_consumer.py +0 -0
  109. {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_app/README.md +0 -0
  110. {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_app/pyproject.toml +0 -0
  111. {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_app/src/typer_app/__init__.py +0 -0
  112. {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_app/src/typer_app/cli.py +0 -0
  113. {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_app/tests/test_cli.py +0 -0
  114. {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_consumer.py +0 -0
  115. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/__init__.py +0 -0
  116. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_app_core.py +0 -0
  117. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_attach.py +0 -0
  118. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_cleanup.py +0 -0
  119. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_click_compat.py +0 -0
  120. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_dependencies.py +0 -0
  121. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_lifecycle.py +0 -0
  122. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_lifecycle_install.py +0 -0
  123. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_private_files.py +0 -0
  124. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_run.py +0 -0
  125. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_runtime.py +0 -0
  126. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_stdout_capture.py +0 -0
  127. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_windows_retention.py +0 -0
  128. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/app.py +0 -0
  129. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/asyncio_adapter.py +0 -0
  130. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/attachment.py +0 -0
  131. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/command_filters.py +0 -0
  132. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/command_protocol.py +0 -0
  133. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/config.py +0 -0
  134. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/context.py +0 -0
  135. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/deprecations.py +0 -0
  136. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/errors.py +0 -0
  137. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/exit_codes.py +0 -0
  138. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/experimental.py +0 -0
  139. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/extensions.py +0 -0
  140. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/history.py +0 -0
  141. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/inspection.py +0 -0
  142. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/integrations.py +0 -0
  143. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/json_contracts.py +0 -0
  144. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/lifecycle_options.py +0 -0
  145. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/logging.py +0 -0
  146. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/output.py +0 -0
  147. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/paths.py +0 -0
  148. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/profile.py +0 -0
  149. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/py.typed +0 -0
  150. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/redaction.py +0 -0
  151. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/runtime.py +0 -0
  152. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/command-protocol.schema.json +0 -0
  153. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/error.schema.json +0 -0
  154. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/inspection.schema.json +0 -0
  155. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/log.schema.json +0 -0
  156. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/ndjson.schema.json +0 -0
  157. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/output.schema.json +0 -0
  158. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/testing.py +0 -0
  159. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/typer.py +0 -0
  160. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli.egg-info/dependency_links.txt +0 -0
  161. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli.egg-info/requires.txt +0 -0
  162. {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli.egg-info/top_level.txt +0 -0
  163. {base_cli-0.5.0 → base_cli-0.5.1}/pyproject.toml +0 -0
  164. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/benchmark_runtime.py +0 -0
  165. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/generate_api_reference.py +0 -0
  166. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/generate_compatibility_dashboard.py +0 -0
  167. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/record_compatibility_evidence.py +0 -0
  168. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/release_metadata_helpers.py +0 -0
  169. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_changelog.py +0 -0
  170. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_consumer_typing.py +0 -0
  171. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_consumers.py +0 -0
  172. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_contract_fixtures.py +0 -0
  173. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_coverage.py +0 -0
  174. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_docs.py +0 -0
  175. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_examples.py +0 -0
  176. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_installed_package.py +0 -0
  177. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_release_provenance.py +0 -0
  178. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_release_ref.py +0 -0
  179. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_schemas.py +0 -0
  180. {base_cli-0.5.0 → base_cli-0.5.1}/scripts/verify_release_assets.py +0 -0
  181. {base_cli-0.5.0 → base_cli-0.5.1}/setup.cfg +0 -0
  182. {base_cli-0.5.0 → base_cli-0.5.1}/tests/conftest.py +0 -0
  183. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_adversarial_regressions.py +0 -0
  184. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_api_stability.py +0 -0
  185. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_dry_run.py +0 -0
  186. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_lifecycle.py +0 -0
  187. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_log_retention.py +0 -0
  188. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_module_boundaries.py +0 -0
  189. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_quiet.py +0 -0
  190. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_registration.py +0 -0
  191. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_run.py +0 -0
  192. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_run_metadata.py +0 -0
  193. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_runtime_boundary.py +0 -0
  194. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_runtime_errors.py +0 -0
  195. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_security_boundaries.py +0 -0
  196. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_startup_transaction.py +0 -0
  197. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_subcommands.py +0 -0
  198. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_batteries_included_config.py +0 -0
  199. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_benchmark_runtime.py +0 -0
  200. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_cleanup_security.py +0 -0
  201. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_click_tree_attachment.py +0 -0
  202. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_command_filters.py +0 -0
  203. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_command_protocol.py +0 -0
  204. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_community_issue_templates.py +0 -0
  205. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_config_discovery_trust.py +0 -0
  206. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_consumer_quickstart_docs.py +0 -0
  207. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_context_workspace.py +0 -0
  208. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_dependency_documentation.py +0 -0
  209. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_explicit_config_validation.py +0 -0
  210. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_extensions.py +0 -0
  211. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_framework_config_runtime.py +0 -0
  212. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_generate_compatibility_dashboard.py +0 -0
  213. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_generated_api_reference.py +0 -0
  214. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_generic_core.py +0 -0
  215. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_history.py +0 -0
  216. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_inspection.py +0 -0
  217. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_integrations.py +0 -0
  218. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_invocation_parity.py +0 -0
  219. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_json_contracts.py +0 -0
  220. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_json_descriptor_capture.py +0 -0
  221. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_lifecycle_options.py +0 -0
  222. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_logger_ownership.py +0 -0
  223. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_logging.py +0 -0
  224. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_logging_hot_path.py +0 -0
  225. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_optional_output_dependencies_docs.py +0 -0
  226. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_optional_yaml_dependency.py +0 -0
  227. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_output.py +0 -0
  228. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_paths.py +0 -0
  229. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_platform_edge_paths.py +0 -0
  230. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_profile.py +0 -0
  231. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_project_intake.py +0 -0
  232. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_public_api.py +0 -0
  233. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_redaction_security.py +0 -0
  234. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_retention_contention.py +0 -0
  235. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_retention_platform.py +0 -0
  236. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_run_app_reentrancy.py +0 -0
  237. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_run_bundle_retention.py +0 -0
  238. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_run_json_preflight.py +0 -0
  239. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_security_documentation.py +0 -0
  240. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_strict_json_consumer_docs.py +0 -0
  241. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_testing.py +0 -0
  242. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_typed_contracts.py +0 -0
  243. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_typer_adapter.py +0 -0
  244. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_changelog.py +0 -0
  245. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_consumer_typing.py +0 -0
  246. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_coverage.py +0 -0
  247. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_docs.py +0 -0
  248. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_release_provenance.py +0 -0
  249. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_release_ref.py +0 -0
  250. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_schemas.py +0 -0
  251. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_verify_release_assets.py +0 -0
  252. {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_windows_retention.py +0 -0
@@ -17,12 +17,17 @@ on:
17
17
  - "scripts/verify_release_assets.py"
18
18
  - "scripts/validate_changelog.py"
19
19
  - "scripts/validate_release_ref.py"
20
+ - "scripts/validate_release_docs.py"
20
21
  - "scripts/validate_release_provenance.py"
22
+ - "scripts/verify_pypi_metadata.py"
21
23
  - "CHANGELOG.md"
22
24
  - "examples/**"
23
25
  - "compatibility/**"
24
26
  - "docs/releasing.md"
25
27
  - "tests/test_package_workflow.py"
28
+ - "tests/test_validate_package_artifact.py"
29
+ - "tests/test_validate_release_docs.py"
30
+ - "tests/test_verify_pypi_metadata.py"
26
31
  - "tests/test_verify_release_assets.py"
27
32
  - "tests/test_validate_release_provenance.py"
28
33
  - "requirements/release.in"
@@ -43,6 +48,11 @@ on:
43
48
  - testpypi
44
49
  - pypi
45
50
  default: testpypi
51
+ release_tag:
52
+ description: "Annotated version tag to publish when dispatching from the default branch"
53
+ required: false
54
+ type: string
55
+ default: ""
46
56
 
47
57
  permissions:
48
58
  contents: read
@@ -61,11 +71,15 @@ jobs:
61
71
  timeout-minutes: 10
62
72
  outputs:
63
73
  version: ${{ steps.metadata.outputs.version }}
74
+ source_commit: ${{ steps.metadata.outputs.source_commit }}
75
+ release_tag: ${{ steps.metadata.outputs.release_tag }}
76
+ source_ref_type: ${{ steps.metadata.outputs.source_ref_type }}
64
77
  steps:
65
78
  - name: Check out source
66
79
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
67
80
  with:
68
81
  fetch-depth: 0
82
+ ref: ${{ inputs.release_tag || github.ref }}
69
83
 
70
84
  - name: Set up Python
71
85
  uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -74,18 +88,44 @@ jobs:
74
88
 
75
89
  - name: Read package version
76
90
  id: metadata
77
- run: echo "version=$(tr -d '\\r\\n' < VERSION)" >> "$GITHUB_OUTPUT"
91
+ env:
92
+ REQUESTED_RELEASE_TAG: ${{ inputs.release_tag || '' }}
93
+ run: |
94
+ version="$(tr -d '\\r\\n' < VERSION)"
95
+ release_tag="$REQUESTED_RELEASE_TAG"
96
+ if [[ -n "$release_tag" ]]; then
97
+ source_ref_type=tag
98
+ source_commit="$(git rev-parse --verify "refs/tags/$release_tag^{}")"
99
+ else
100
+ source_ref_type="$GITHUB_REF_TYPE"
101
+ source_commit="$GITHUB_SHA"
102
+ if [[ "$source_ref_type" == tag ]]; then
103
+ release_tag="$GITHUB_REF_NAME"
104
+ fi
105
+ fi
106
+ echo "version=$version" >> "$GITHUB_OUTPUT"
107
+ echo "source_commit=$source_commit" >> "$GITHUB_OUTPUT"
108
+ echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT"
109
+ echo "source_ref_type=$source_ref_type" >> "$GITHUB_OUTPUT"
78
110
 
79
111
  - name: Validate release ref
80
112
  env:
81
113
  PUBLISH_TARGET: ${{ inputs.publish_target || '' }}
114
+ REQUESTED_RELEASE_TAG: ${{ inputs.release_tag || '' }}
115
+ SOURCE_REF_TYPE: ${{ steps.metadata.outputs.source_ref_type }}
116
+ RELEASE_TAG: ${{ steps.metadata.outputs.release_tag }}
117
+ VERSION: ${{ steps.metadata.outputs.version }}
82
118
  run: |
83
- if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" != "v${{ steps.metadata.outputs.version }}" ]]; then
84
- echo "Release tag must be v${{ steps.metadata.outputs.version }}; got $GITHUB_REF_NAME" >&2
119
+ if [[ "$SOURCE_REF_TYPE" == "tag" && "$RELEASE_TAG" != "v$VERSION" ]]; then
120
+ echo "Release tag must be v$VERSION; got $RELEASE_TAG" >&2
121
+ exit 1
122
+ fi
123
+ if [[ "$PUBLISH_TARGET" == "pypi" && "$SOURCE_REF_TYPE" != "tag" ]]; then
124
+ echo "PyPI publication requires a matching version tag." >&2
85
125
  exit 1
86
126
  fi
87
- if [[ "$PUBLISH_TARGET" == "pypi" && "$GITHUB_REF_TYPE" != "tag" ]]; then
88
- echo "PyPI publication requires dispatching this workflow from the matching version tag." >&2
127
+ if [[ "$PUBLISH_TARGET" == "pypi" && "$GITHUB_EVENT_NAME" == workflow_dispatch && -z "$REQUESTED_RELEASE_TAG" ]]; then
128
+ echo "Manual PyPI publication requires the release_tag input." >&2
89
129
  exit 1
90
130
  fi
91
131
 
@@ -93,11 +133,16 @@ jobs:
93
133
  run: python scripts/validate_changelog.py
94
134
 
95
135
  - name: Validate tagged release notes
96
- if: ${{ github.ref_type == 'tag' }}
136
+ if: ${{ steps.metadata.outputs.source_ref_type == 'tag' }}
97
137
  env:
98
- RELEASE_TAG: ${{ github.ref_name }}
138
+ RELEASE_TAG: ${{ steps.metadata.outputs.release_tag }}
99
139
  run: python scripts/validate_release_ref.py
100
140
 
141
+ - name: Validate release-facing documentation
142
+ env:
143
+ RELEASE_TAG: ${{ steps.metadata.outputs.release_tag }}
144
+ run: python scripts/validate_release_docs.py
145
+
101
146
  - name: Prepare clean artifact destination
102
147
  run: |
103
148
  git clean -ffdx
@@ -111,12 +156,13 @@ jobs:
111
156
  # Keep the reproducibility input numeric; push-event timestamps are
112
157
  # ISO-8601 strings and are not accepted by wheel/build backends.
113
158
  SOURCE_DATE_EPOCH: '0'
159
+ SOURCE_COMMIT: ${{ steps.metadata.outputs.source_commit }}
114
160
  run: |
115
161
  rm -rf dist-a dist-b "$RUNNER_TEMP/base-cli-build-a" "$RUNNER_TEMP/base-cli-build-b"
116
162
  mkdir -p dist-a dist-b
117
163
  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"
164
+ git archive "$SOURCE_COMMIT" | tar -x -C "$RUNNER_TEMP/base-cli-build-a"
165
+ git archive "$SOURCE_COMMIT" | tar -x -C "$RUNNER_TEMP/base-cli-build-b"
120
166
  (cd "$RUNNER_TEMP/base-cli-build-a" && python -m build --sdist --wheel --outdir "$GITHUB_WORKSPACE/dist-a")
121
167
  (cd "$RUNNER_TEMP/base-cli-build-b" && python -m build --sdist --wheel --outdir "$GITHUB_WORKSPACE/dist-b")
122
168
  python - <<'PY'
@@ -155,13 +201,13 @@ jobs:
155
201
 
156
202
  - name: Generate release checksums and SPDX SBOM
157
203
  env:
158
- SOURCE_REVISION: ${{ github.sha }}
204
+ SOURCE_REVISION: ${{ steps.metadata.outputs.source_commit }}
159
205
  SOURCE_DATE_EPOCH: '0'
160
206
  run: python scripts/generate_release_metadata.py dist
161
207
 
162
208
  - name: Validate release checksums and SPDX SBOM
163
209
  env:
164
- SOURCE_REVISION: ${{ github.sha }}
210
+ SOURCE_REVISION: ${{ steps.metadata.outputs.source_commit }}
165
211
  run: python scripts/validate_release_metadata.py dist
166
212
 
167
213
  - name: Upload reviewed distributions
@@ -199,6 +245,7 @@ jobs:
199
245
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
200
246
  with:
201
247
  fetch-depth: 0
248
+ ref: ${{ needs.build.outputs.source_commit }}
202
249
 
203
250
  - name: Set up Python
204
251
  uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -250,6 +297,7 @@ jobs:
250
297
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
251
298
  with:
252
299
  fetch-depth: 0
300
+ ref: ${{ needs.build.outputs.source_commit }}
253
301
 
254
302
  - name: Fetch trusted main history
255
303
  run: git fetch --no-tags --prune origin "refs/heads/main:refs/remotes/origin/main"
@@ -257,7 +305,15 @@ jobs:
257
305
  - name: Verify release provenance
258
306
  env:
259
307
  PUBLISH_TARGET: ${{ inputs.publish_target || '' }}
260
- run: python scripts/validate_release_provenance.py
308
+ SOURCE_REF_TYPE: ${{ needs.build.outputs.source_ref_type }}
309
+ RELEASE_TAG: ${{ needs.build.outputs.release_tag }}
310
+ SOURCE_COMMIT: ${{ needs.build.outputs.source_commit }}
311
+ run: |
312
+ python scripts/validate_release_provenance.py \
313
+ --ref-type "$SOURCE_REF_TYPE" \
314
+ --tag "$RELEASE_TAG" \
315
+ --publish-target "$PUBLISH_TARGET" \
316
+ --source-commit "$SOURCE_COMMIT"
261
317
 
262
318
  publish:
263
319
  name: Publish reviewed distribution
@@ -280,14 +336,14 @@ jobs:
280
336
 
281
337
  - name: Publish to TestPyPI
282
338
  if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_target == 'testpypi' }}
283
- uses: pypa/gh-action-pypi-publish@4bb033805d9e19112d8c697528791ff53f6c2f74
339
+ uses: pypa/gh-action-pypi-publish@v1.14.2 # v1.14.2 image tag is required by this composite action
284
340
  with:
285
341
  packages-dir: dist
286
342
  repository-url: https://test.pypi.org/legacy/
287
343
 
288
344
  - name: Publish to PyPI
289
345
  if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi') }}
290
- uses: pypa/gh-action-pypi-publish@4bb033805d9e19112d8c697528791ff53f6c2f74
346
+ uses: pypa/gh-action-pypi-publish@v1.14.2 # v1.14.2 image tag is required by this composite action
291
347
  with:
292
348
  packages-dir: dist
293
349
 
@@ -325,10 +381,32 @@ jobs:
325
381
  subject-checksums: dist/SHA256SUMS
326
382
  sbom-path: dist/SBOM.spdx.json
327
383
 
384
+ verify_pypi:
385
+ name: Verify published PyPI metadata
386
+ needs: [build, publish, attest]
387
+ if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi' && inputs.release_tag != '') }}
388
+ runs-on: ubuntu-latest
389
+ timeout-minutes: 5
390
+ permissions:
391
+ contents: read
392
+ steps:
393
+ - name: Check out source
394
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
395
+ with:
396
+ ref: ${{ needs.build.outputs.source_commit }}
397
+
398
+ - name: Set up Python
399
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
400
+ with:
401
+ python-version: "3.13"
402
+
403
+ - name: Verify PyPI description and version
404
+ run: python scripts/verify_pypi_metadata.py --version "${{ needs.build.outputs.version }}"
405
+
328
406
  release:
329
407
  name: Create GitHub Release
330
- needs: [build, smoke, provenance, publish, attest]
331
- if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
408
+ needs: [build, smoke, provenance, publish, attest, verify_pypi]
409
+ if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi' && inputs.release_tag != '') }}
332
410
  runs-on: ubuntu-latest
333
411
  timeout-minutes: 10
334
412
  permissions:
@@ -337,6 +415,9 @@ jobs:
337
415
  steps:
338
416
  - name: Check out the tag source
339
417
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
418
+ with:
419
+ fetch-depth: 0
420
+ ref: ${{ needs.build.outputs.source_commit }}
340
421
 
341
422
  - name: Set up Python
342
423
  uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -358,13 +439,17 @@ jobs:
358
439
  - name: Verify and create immutable GitHub Release
359
440
  env:
360
441
  GH_TOKEN: ${{ github.token }}
442
+ SOURCE_REVISION: ${{ needs.build.outputs.source_commit }}
443
+ RELEASE_TAG: ${{ needs.build.outputs.release_tag }}
444
+ SOURCE_COMMIT: ${{ needs.build.outputs.source_commit }}
361
445
  run: |
362
446
  set -euo pipefail
363
- tag="$GITHUB_REF_NAME"
447
+ tag="$RELEASE_TAG"
448
+ source_commit="$SOURCE_COMMIT"
364
449
  assets=(dist/*.whl dist/*.tar.gz dist/SHA256SUMS dist/SBOM.spdx.json dist/RELEASE-BOM-ROW.json)
365
450
  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
451
+ if [[ "$tag_commit" != "$source_commit" ]]; then
452
+ echo "Release tag $tag resolves to $tag_commit, not reviewed commit $source_commit." >&2
368
453
  exit 1
369
454
  fi
370
455
 
@@ -424,7 +509,7 @@ jobs:
424
509
  --release-json "$release_json" \
425
510
  --version-file VERSION \
426
511
  --tag "$tag" \
427
- --source-commit "$GITHUB_SHA" \
512
+ --source-commit "$source_commit" \
428
513
  --resolved-tag-commit "$tag_commit"
429
514
  }
430
515
 
@@ -443,7 +528,6 @@ jobs:
443
528
  gh release create "$tag" "${assets[@]}" \
444
529
  --repo "$GITHUB_REPOSITORY" \
445
530
  --title "$tag" \
446
- --generate-notes \
447
531
  --notes "Published distributions and release metadata for $tag. See CHANGELOG.md for the reviewed release notes." \
448
532
  || create_status=$?
449
533
 
@@ -15,6 +15,15 @@ and versions are tracked in the repo-root `VERSION` file.
15
15
 
16
16
  - Include `uv.lock` freshness in the authoritative local validation aggregate (#424).
17
17
 
18
+ ## [0.5.1] - 2026-10-10
19
+
20
+ ### Changed
21
+
22
+ - Align the release-facing README and reference-consumer documentation with the
23
+ published `base-cli` 0.5.x and `base-cli-demo` 0.2.0 lines.
24
+ - Add release preflight, package-description, and PyPI metadata readback gates so
25
+ published documentation stays bound to the reviewed tag.
26
+
18
27
  ## [0.5.0] - 2026-10-03
19
28
 
20
29
  ### Added
@@ -401,7 +410,8 @@ the API stability policy and migration guide before upgrading from `0.3.x`.
401
410
  - Pinned the build backend to metadata compatible with the bundled publication
402
411
  action and made license-file validation portable across setuptools versions.
403
412
 
404
- [Unreleased]: https://github.com/basefoundry/base-cli/compare/v0.5.0...HEAD
413
+ [Unreleased]: https://github.com/basefoundry/base-cli/compare/v0.5.1...HEAD
414
+ [0.5.1]: https://github.com/basefoundry/base-cli/compare/v0.5.0...v0.5.1
405
415
  [0.5.0]: https://github.com/basefoundry/base-cli/compare/v0.4.3...v0.5.0
406
416
  [0.4.3]: https://github.com/basefoundry/base-cli/compare/v0.4.2...v0.4.3
407
417
  [0.4.2]: https://github.com/basefoundry/base-cli/compare/v0.4.1...v0.4.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: base-cli
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: The production lifecycle layer for Click and Typer Python CLIs
5
5
  Author: Base Foundry
6
6
  License: Apache-2.0
@@ -66,11 +66,10 @@ Dynamic: license-file
66
66
 
67
67
  | Version | License | Install | Release notes |
68
68
  | --- | --- | --- | --- |
69
- | `0.4.3` | [Apache-2.0](LICENSE) | `python -m pip install base-cli` | [v0.4.3](https://github.com/basefoundry/base-cli/releases/tag/v0.4.3) |
69
+ | `0.5.1` | [Apache-2.0](LICENSE) | `python -m pip install base-cli` | [v0.5.1](https://github.com/basefoundry/base-cli/releases/tag/v0.5.1) |
70
70
 
71
- Source release candidate: 0.5.0. Publication is pending the [release checklist](docs/release-0.5.0-checklist.md); PyPI remains the authority for available versions.
72
- The adopter documentation uses `~=0.5.0` for the release-preparation target;
73
- use that pin only after the 0.5.0 artifact is published and verified.
71
+ The 0.5.1 release is bound to the immutable `v0.5.1` tag; PyPI remains the authority for available versions.
72
+ The adopter documentation uses `~=0.5.0` for the supported minor-release line.
74
73
 
75
74
  `base-cli` is the production lifecycle layer for Click and Typer Python CLIs.
76
75
  It standardizes context, logging, configuration, cleanup, and machine-readable
@@ -132,9 +131,8 @@ options are documented below.
132
131
 
133
132
  For a complete maintained example, see the independent
134
133
  [`base-cli-demo`](https://github.com/basefoundry/base-cli-demo) reference
135
- consumer. It is versioned separately (`0.1.0`) and currently tests the released
136
- `base-cli>=0.4.3,<0.5` line; its 0.5.0 upgrade is tracked in
137
- [base-cli-demo#47](https://github.com/basefoundry/base-cli-demo/issues/47).
134
+ consumer. It is versioned separately (`0.2.0`) and currently tests the released
135
+ `base-cli>=0.5.0,<0.6` line.
138
136
 
139
137
  Release builds, TestPyPI rehearsals, and protected PyPI publication are
140
138
  documented in [`docs/releasing.md`](https://basefoundry.github.io/base-cli/releasing/). The package exposes
@@ -7,11 +7,10 @@
7
7
 
8
8
  | Version | License | Install | Release notes |
9
9
  | --- | --- | --- | --- |
10
- | `0.4.3` | [Apache-2.0](LICENSE) | `python -m pip install base-cli` | [v0.4.3](https://github.com/basefoundry/base-cli/releases/tag/v0.4.3) |
10
+ | `0.5.1` | [Apache-2.0](LICENSE) | `python -m pip install base-cli` | [v0.5.1](https://github.com/basefoundry/base-cli/releases/tag/v0.5.1) |
11
11
 
12
- Source release candidate: 0.5.0. Publication is pending the [release checklist](docs/release-0.5.0-checklist.md); PyPI remains the authority for available versions.
13
- The adopter documentation uses `~=0.5.0` for the release-preparation target;
14
- use that pin only after the 0.5.0 artifact is published and verified.
12
+ The 0.5.1 release is bound to the immutable `v0.5.1` tag; PyPI remains the authority for available versions.
13
+ The adopter documentation uses `~=0.5.0` for the supported minor-release line.
15
14
 
16
15
  `base-cli` is the production lifecycle layer for Click and Typer Python CLIs.
17
16
  It standardizes context, logging, configuration, cleanup, and machine-readable
@@ -73,9 +72,8 @@ options are documented below.
73
72
 
74
73
  For a complete maintained example, see the independent
75
74
  [`base-cli-demo`](https://github.com/basefoundry/base-cli-demo) reference
76
- consumer. It is versioned separately (`0.1.0`) and currently tests the released
77
- `base-cli>=0.4.3,<0.5` line; its 0.5.0 upgrade is tracked in
78
- [base-cli-demo#47](https://github.com/basefoundry/base-cli-demo/issues/47).
75
+ consumer. It is versioned separately (`0.2.0`) and currently tests the released
76
+ `base-cli>=0.5.0,<0.6` line.
79
77
 
80
78
  Release builds, TestPyPI rehearsals, and protected PyPI publication are
81
79
  documented in [`docs/releasing.md`](https://basefoundry.github.io/base-cli/releasing/). The package exposes
base_cli-0.5.1/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.1
@@ -31,14 +31,12 @@ tests. Base, base-demo, `base-cli-demo`, and other adjacent repositories are
31
31
  explicitly excluded from the independent-adopter count.
32
32
 
33
33
  `base-cli-demo` is a maintained reference and learning consumer, versioned
34
- independently (`0.1.0`). Its CI demonstrates that a real consumer can exercise
34
+ independently (`0.2.0`). Its CI demonstrates that a real consumer can exercise
35
35
  the public facade, lifecycle, output, error, and optional-integration paths for
36
36
  the exact framework dependency line it declares; it is not an independent
37
37
  adopter result or a substitute for a dated compatibility-fixture artifact.
38
- While its current released line is `base-cli>=0.4.3,<0.5`, the 0.5.0 upgrade is
39
- tracked in [base-cli-demo#47](https://github.com/basefoundry/base-cli-demo/issues/47)
40
- and must be green against the published framework before being described as
41
- 0.5.0 evidence.
38
+ Its current released line is `base-cli>=0.5.0,<0.6`, and its release evidence is
39
+ maintained against the published framework package.
42
40
 
43
41
  ## Adopter program
44
42
 
@@ -96,6 +96,6 @@ Continue with the framework guides:
96
96
  - [`base-cli-demo`](https://github.com/basefoundry/base-cli-demo) for the
97
97
  maintained end-to-end reference consumer, optional integration scenarios,
98
98
  and runnable documentation examples. It is versioned independently and its
99
- current released dependency line is `base-cli>=0.4.3,<0.5`.
99
+ current released dependency line is `base-cli>=0.5.0,<0.6`.
100
100
  - [Installation and packaging](adopter-readiness.md) for dependency extras,
101
101
  distribution checks, and release guidance.
@@ -125,6 +125,22 @@ the downloaded artifact's digest with `SHA256SUMS`, confirm the SBOM namespace
125
125
  contains the expected tag commit, and inspect the attestation's workflow and
126
126
  repository identity before installation.
127
127
 
128
+ ## Release-facing documentation guardrails
129
+
130
+ The package README is part of the published PyPI metadata. Before any tagged
131
+ build, the Package workflow runs `scripts/validate_release_docs.py` to require a
132
+ single release row whose version and GitHub release link match `VERSION` and to
133
+ reject pre-publication wording. `scripts/validate_package_artifact.py` then
134
+ compares the wheel metadata and sdist `PKG-INFO` description with the tagged
135
+ README, and the release BOM records the README SHA-256.
136
+
137
+ After a production PyPI upload, the workflow runs
138
+ `scripts/verify_pypi_metadata.py` before creating the GitHub Release. It reads
139
+ the published PyPI JSON metadata, retries propagation delays, and requires the
140
+ published version and long description to match the reviewed source. A mismatch
141
+ blocks release completion; published PyPI bytes remain immutable and require a
142
+ new patch version for correction.
143
+
128
144
  ## Changelog and release notes
129
145
 
130
146
  `CHANGELOG.md` follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: base-cli
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: The production lifecycle layer for Click and Typer Python CLIs
5
5
  Author: Base Foundry
6
6
  License: Apache-2.0
@@ -66,11 +66,10 @@ Dynamic: license-file
66
66
 
67
67
  | Version | License | Install | Release notes |
68
68
  | --- | --- | --- | --- |
69
- | `0.4.3` | [Apache-2.0](LICENSE) | `python -m pip install base-cli` | [v0.4.3](https://github.com/basefoundry/base-cli/releases/tag/v0.4.3) |
69
+ | `0.5.1` | [Apache-2.0](LICENSE) | `python -m pip install base-cli` | [v0.5.1](https://github.com/basefoundry/base-cli/releases/tag/v0.5.1) |
70
70
 
71
- Source release candidate: 0.5.0. Publication is pending the [release checklist](docs/release-0.5.0-checklist.md); PyPI remains the authority for available versions.
72
- The adopter documentation uses `~=0.5.0` for the release-preparation target;
73
- use that pin only after the 0.5.0 artifact is published and verified.
71
+ The 0.5.1 release is bound to the immutable `v0.5.1` tag; PyPI remains the authority for available versions.
72
+ The adopter documentation uses `~=0.5.0` for the supported minor-release line.
74
73
 
75
74
  `base-cli` is the production lifecycle layer for Click and Typer Python CLIs.
76
75
  It standardizes context, logging, configuration, cleanup, and machine-readable
@@ -132,9 +131,8 @@ options are documented below.
132
131
 
133
132
  For a complete maintained example, see the independent
134
133
  [`base-cli-demo`](https://github.com/basefoundry/base-cli-demo) reference
135
- consumer. It is versioned separately (`0.1.0`) and currently tests the released
136
- `base-cli>=0.4.3,<0.5` line; its 0.5.0 upgrade is tracked in
137
- [base-cli-demo#47](https://github.com/basefoundry/base-cli-demo/issues/47).
134
+ consumer. It is versioned separately (`0.2.0`) and currently tests the released
135
+ `base-cli>=0.5.0,<0.6` line.
138
136
 
139
137
  Release builds, TestPyPI rehearsals, and protected PyPI publication are
140
138
  documented in [`docs/releasing.md`](https://basefoundry.github.io/base-cli/releasing/). The package exposes
@@ -164,10 +164,12 @@ scripts/validate_docs.py
164
164
  scripts/validate_examples.py
165
165
  scripts/validate_installed_package.py
166
166
  scripts/validate_package_artifact.py
167
+ scripts/validate_release_docs.py
167
168
  scripts/validate_release_metadata.py
168
169
  scripts/validate_release_provenance.py
169
170
  scripts/validate_release_ref.py
170
171
  scripts/validate_schemas.py
172
+ scripts/verify_pypi_metadata.py
171
173
  scripts/verify_release_assets.py
172
174
  tests/conftest.py
173
175
  tests/test_adversarial_regressions.py
@@ -237,8 +239,10 @@ tests/test_validate_consumer_typing.py
237
239
  tests/test_validate_coverage.py
238
240
  tests/test_validate_docs.py
239
241
  tests/test_validate_package_artifact.py
242
+ tests/test_validate_release_docs.py
240
243
  tests/test_validate_release_provenance.py
241
244
  tests/test_validate_release_ref.py
242
245
  tests/test_validate_schemas.py
246
+ tests/test_verify_pypi_metadata.py
243
247
  tests/test_verify_release_assets.py
244
248
  tests/test_windows_retention.py
@@ -136,6 +136,7 @@ def generate(dist: Path, root: Path) -> None:
136
136
  "version": version,
137
137
  "tag": f"v{version}",
138
138
  "commit": revision,
139
+ "readme_sha256": sha256_file(root / "README.md"),
139
140
  "source_mode": "release",
140
141
  "api_schema_version": f"base-cli-api@{version}",
141
142
  "platforms": ["macos", "ubuntu", "windows"],
@@ -67,7 +67,31 @@ def expected_package_files() -> set[str]:
67
67
  return {path.relative_to(package_root).as_posix() for path in package_root.rglob("*") if path.is_file()}
68
68
 
69
69
 
70
- def validate_wheel(path: Path, expected_version: str, package_files: set[str]) -> None:
70
+ def read_expected_readme() -> str:
71
+ readme_path = Path(__file__).resolve().parents[1] / "README.md"
72
+ try:
73
+ return readme_path.read_text(encoding="utf-8")
74
+ except (OSError, UnicodeDecodeError) as exc:
75
+ fail(f"could not read the source README.md: {exc}")
76
+
77
+
78
+ def normalized_text(value: str) -> str:
79
+ return value.replace("\r\n", "\n").strip()
80
+
81
+
82
+ def validate_metadata_description(metadata: email.message.Message, expected_readme: str, artifact_name: str) -> None:
83
+ payload = metadata.get_payload(decode=True)
84
+ if not isinstance(payload, bytes):
85
+ fail(f"{artifact_name} has no text long description")
86
+ try:
87
+ description = payload.decode("utf-8")
88
+ except UnicodeDecodeError as exc:
89
+ fail(f"{artifact_name} long description is not UTF-8: {exc}")
90
+ if normalized_text(description) != normalized_text(expected_readme):
91
+ fail(f"{artifact_name} long description does not match the source README.md")
92
+
93
+
94
+ def validate_wheel(path: Path, expected_version: str, package_files: set[str], expected_readme: str) -> None:
71
95
  with zipfile.ZipFile(path) as archive:
72
96
  names = archive.namelist()
73
97
  metadata_names = [name for name in names if name.endswith(".dist-info/METADATA")]
@@ -84,6 +108,7 @@ def validate_wheel(path: Path, expected_version: str, package_files: set[str]) -
84
108
  for header, expected in expected_headers.items():
85
109
  if metadata.get(header) != expected:
86
110
  fail(f"{path.name} has {header}={metadata.get(header)!r}; expected {expected!r}")
111
+ validate_metadata_description(metadata, expected_readme, path.name)
87
112
 
88
113
  dependencies = set(metadata.get_all("Requires-Dist", []))
89
114
  for dependency in REQUIRED_DEPENDENCIES:
@@ -131,7 +156,7 @@ def validate_wheel(path: Path, expected_version: str, package_files: set[str]) -
131
156
  fail(f"{path.name} contains non-allowlisted archive member {name!r}")
132
157
 
133
158
 
134
- def validate_sdist(path: Path, expected_version: str) -> None:
159
+ def validate_sdist(path: Path, expected_version: str, expected_readme: str) -> None:
135
160
  with tarfile.open(path, "r:gz") as archive:
136
161
  members = archive.getmembers()
137
162
  names = [member.name for member in members]
@@ -160,6 +185,22 @@ def validate_sdist(path: Path, expected_version: str) -> None:
160
185
  version_text = archive.extractfile(version_members[0])
161
186
  if version_text is None or version_text.read().decode().splitlines()[0].strip() != expected_version:
162
187
  fail(f"{path.name} VERSION does not match {expected_version}")
188
+ readme_members = [member for member in members if member.name == f"{root_prefix}README.md"]
189
+ if len(readme_members) != 1:
190
+ fail(f"{path.name} must contain exactly one README.md")
191
+ readme_text = archive.extractfile(readme_members[0])
192
+ if readme_text is None or normalized_text(readme_text.read().decode("utf-8")) != normalized_text(
193
+ expected_readme
194
+ ):
195
+ fail(f"{path.name} README.md does not match the source README.md")
196
+ pkg_info_members = [member for member in members if member.name == f"{root_prefix}PKG-INFO"]
197
+ if len(pkg_info_members) != 1:
198
+ fail(f"{path.name} must contain exactly one PKG-INFO")
199
+ pkg_info = archive.extractfile(pkg_info_members[0])
200
+ if pkg_info is None:
201
+ fail(f"{path.name} PKG-INFO could not be read")
202
+ metadata = email.message_from_bytes(pkg_info.read())
203
+ validate_metadata_description(metadata, expected_readme, path.name)
163
204
 
164
205
 
165
206
  def main() -> None:
@@ -178,8 +219,9 @@ def main() -> None:
178
219
  if not wheels[0].name.startswith(expected_stem) or not sdists[0].name.startswith(expected_stem):
179
220
  fail(f"artifact filenames do not match version {expected_version}")
180
221
 
181
- validate_wheel(wheels[0], expected_version, expected_package_files())
182
- validate_sdist(sdists[0], expected_version)
222
+ expected_readme = read_expected_readme()
223
+ validate_wheel(wheels[0], expected_version, expected_package_files(), expected_readme)
224
+ validate_sdist(sdists[0], expected_version, expected_readme)
183
225
  print(f"Validated {PACKAGE_NAME} {expected_version}: wheel, sdist, metadata, package data, and test boundary.")
184
226
 
185
227
 
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python3
2
+ """Validate release-facing documentation before building a tagged package."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import os
8
+ import re
9
+ from pathlib import Path
10
+
11
+ VERSION_PATTERN = re.compile(r"^[0-9]+(?:\.[0-9]+)+(?:[-+][0-9A-Za-z.-]+)?$")
12
+ RELEASE_ROW_PATTERN = re.compile(
13
+ r"^\| `(?P<version>[^`]+)` \| \[Apache-2\.0\]\(LICENSE\) \| "
14
+ r"`python -m pip install base-cli` \| \[v(?P<link_version>[^]]+)\]"
15
+ r"\(https://github\.com/basefoundry/base-cli/releases/tag/v(?P<tag_version>[^)]+)\) \|$"
16
+ )
17
+
18
+
19
+ def validate_release_docs(version_path: Path, readme_path: Path, tag: str = "") -> list[str]:
20
+ """Return release documentation violations for *version_path* and README."""
21
+
22
+ errors: list[str] = []
23
+ version = version_path.read_text(encoding="utf-8").strip()
24
+ if not VERSION_PATTERN.fullmatch(version):
25
+ errors.append(f"VERSION is not a valid release version: {version!r}")
26
+ return errors
27
+ expected_tag = f"v{version}"
28
+ if tag and tag != expected_tag:
29
+ errors.append(f"release tag {tag!r} does not match VERSION {version!r}")
30
+
31
+ readme = readme_path.read_text(encoding="utf-8")
32
+ rows = [RELEASE_ROW_PATTERN.match(line) for line in readme.splitlines()]
33
+ matching_rows = [match for match in rows if match is not None]
34
+ if len(matching_rows) != 1:
35
+ errors.append("README.md must contain exactly one canonical release row")
36
+ else:
37
+ row = matching_rows[0]
38
+ assert row is not None
39
+ values = (row.group("version"), row.group("link_version"), row.group("tag_version"))
40
+ if values != (version, version, version):
41
+ errors.append(f"README.md release row versions {values!r} do not match {version!r}")
42
+
43
+ lowered = readme.casefold()
44
+ for forbidden in ("source release candidate", "publication is pending"):
45
+ if forbidden in lowered:
46
+ errors.append(f"README.md still contains pre-publication wording: {forbidden!r}")
47
+ return errors
48
+
49
+
50
+ def main() -> None:
51
+ parser = argparse.ArgumentParser(description=__doc__)
52
+ parser.add_argument("--version-file", type=Path, default=Path("VERSION"))
53
+ parser.add_argument("--readme", type=Path, default=Path("README.md"))
54
+ parser.add_argument("--tag", default=os.environ.get("RELEASE_TAG", ""))
55
+ args = parser.parse_args()
56
+ errors = validate_release_docs(args.version_file, args.readme, args.tag)
57
+ if errors:
58
+ for error in errors:
59
+ print(f"release documentation validation failed: {error}")
60
+ raise SystemExit(1)
61
+ version = args.version_file.read_text(encoding="utf-8").strip()
62
+ print(f"Validated release-facing documentation for {args.tag or version}.")
63
+
64
+
65
+ if __name__ == "__main__":
66
+ main()
@@ -71,6 +71,9 @@ def main() -> None:
71
71
  _fail(f"{BOM_ROW_NAME} repository must be basefoundry/base-cli")
72
72
  if bom_row.get("version") != version or bom_row.get("tag") != f"v{version}":
73
73
  _fail(f"{BOM_ROW_NAME} version/tag does not match the release")
74
+ readme_path = Path(__file__).resolve().parents[1] / "README.md"
75
+ if bom_row.get("readme_sha256") != sha256_file(readme_path):
76
+ _fail(f"{BOM_ROW_NAME} README hash is not bound to the reviewed source")
74
77
  commit = bom_row.get("commit")
75
78
  if not isinstance(commit, str) or not SHA_RE.fullmatch(commit):
76
79
  _fail(f"{BOM_ROW_NAME} commit must be a lowercase full 40-character SHA")