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.
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/package.yml +105 -21
- {base_cli-0.5.0 → base_cli-0.5.1}/CHANGELOG.md +11 -1
- {base_cli-0.5.0 → base_cli-0.5.1}/PKG-INFO +6 -8
- {base_cli-0.5.0 → base_cli-0.5.1}/README.md +5 -7
- base_cli-0.5.1/VERSION +1 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/adoption-evidence.md +3 -5
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/index.md +1 -1
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/releasing.md +16 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli.egg-info/PKG-INFO +6 -8
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli.egg-info/SOURCES.txt +4 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/generate_release_metadata.py +1 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_package_artifact.py +46 -4
- base_cli-0.5.1/scripts/validate_release_docs.py +66 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_release_metadata.py +3 -0
- base_cli-0.5.1/scripts/verify_pypi_metadata.py +76 -0
- base_cli-0.5.1/tests/test_package_workflow.py +78 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_package_artifact.py +5 -1
- base_cli-0.5.1/tests/test_validate_release_docs.py +49 -0
- base_cli-0.5.1/tests/test_verify_pypi_metadata.py +34 -0
- base_cli-0.5.0/VERSION +0 -1
- base_cli-0.5.0/tests/test_package_workflow.py +0 -38
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/documentation.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/support.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/ISSUE_TEMPLATE/support.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/base-project.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/pull_request_template.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/compatibility.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/dependency-matrix.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/docs.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/examples.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/issue-branch-policy.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/project-intake.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/.github/workflows/tests.yml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/CONTRIBUTING.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/LICENSE +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/MANIFEST.in +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/SECURITY.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/base_manifest.yaml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/README.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/atlas_click/README.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/atlas_click/pyproject.toml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/atlas_click/src/atlas_click/__init__.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/atlas_click/src/atlas_click/cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/atlas_click/tests/test_consumer.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/beacon_typer/README.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/beacon_typer/pyproject.toml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/beacon_typer/src/beacon_typer/__init__.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/beacon_typer/src/beacon_typer/cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/beacon_typer/tests/test_consumer.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/cinder_automation/README.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/cinder_automation/pyproject.toml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/cinder_automation/src/cinder_automation/__init__.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/cinder_automation/src/cinder_automation/cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/cinder_automation/tests/test_consumer.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/compatibility/consumers/manifest.json +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/adopter-readiness.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/api-reference.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/api-stability.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/cache-ownership-and-layout.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/compatibility-dashboard.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/consumer-profiles.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/consumer-quickstart.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/coverage-policy.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/dependency-support.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/extensions.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/framework-choice.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/integrations.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/json-contracts.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/local-config.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/migration-0.5.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/migration-argparse.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/migration-cement.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/migration-click.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/migration-typer.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/migrations.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/optional-output-dependencies.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/output-contracts.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/performance.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/platform-support.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/release-0.5.0-checklist.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/schemas.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/security-review.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/security-threat-model.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/strict-json-consumer.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/testing.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/typer-adapter.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/docs/user-config-typing.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/README.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/automation_observability_app/README.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/automation_observability_app/pyproject.toml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/automation_observability_app/src/automation_observability_app/__init__.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/automation_observability_app/src/automation_observability_app/cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/automation_observability_app/tests/test_cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/minimal_cli/README.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/minimal_cli/pyproject.toml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/minimal_cli/src/minimal_cli/__init__.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/minimal_cli/src/minimal_cli/cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/minimal_cli/tests/test_cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/README.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/pyproject.toml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/src/nested_click_app/__init__.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/src/nested_click_app/cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/src/nested_click_app/plugin.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/nested_click_app/tests/test_cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/typed_consumer.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_app/README.md +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_app/pyproject.toml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_app/src/typer_app/__init__.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_app/src/typer_app/cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_app/tests/test_cli.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/examples/typer_consumer.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/__init__.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_app_core.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_attach.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_cleanup.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_click_compat.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_dependencies.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_lifecycle.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_lifecycle_install.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_private_files.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_run.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_runtime.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_stdout_capture.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/_windows_retention.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/app.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/asyncio_adapter.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/attachment.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/command_filters.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/command_protocol.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/config.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/context.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/deprecations.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/errors.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/exit_codes.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/experimental.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/extensions.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/history.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/inspection.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/integrations.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/json_contracts.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/lifecycle_options.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/logging.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/output.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/paths.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/profile.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/py.typed +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/redaction.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/runtime.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/command-protocol.schema.json +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/error.schema.json +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/inspection.schema.json +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/log.schema.json +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/ndjson.schema.json +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/schemas/v1/output.schema.json +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/testing.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli/typer.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli.egg-info/dependency_links.txt +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli.egg-info/requires.txt +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/lib/python/base_cli.egg-info/top_level.txt +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/pyproject.toml +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/benchmark_runtime.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/generate_api_reference.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/generate_compatibility_dashboard.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/record_compatibility_evidence.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/release_metadata_helpers.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_changelog.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_consumer_typing.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_consumers.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_contract_fixtures.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_coverage.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_docs.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_examples.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_installed_package.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_release_provenance.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_release_ref.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/validate_schemas.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/scripts/verify_release_assets.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/setup.cfg +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/conftest.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_adversarial_regressions.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_api_stability.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_dry_run.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_lifecycle.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_log_retention.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_module_boundaries.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_quiet.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_registration.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_run.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_run_metadata.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_runtime_boundary.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_runtime_errors.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_security_boundaries.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_startup_transaction.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_app_subcommands.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_batteries_included_config.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_benchmark_runtime.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_cleanup_security.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_click_tree_attachment.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_command_filters.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_command_protocol.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_community_issue_templates.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_config_discovery_trust.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_consumer_quickstart_docs.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_context_workspace.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_dependency_documentation.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_explicit_config_validation.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_extensions.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_framework_config_runtime.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_generate_compatibility_dashboard.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_generated_api_reference.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_generic_core.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_history.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_inspection.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_integrations.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_invocation_parity.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_json_contracts.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_json_descriptor_capture.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_lifecycle_options.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_logger_ownership.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_logging.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_logging_hot_path.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_optional_output_dependencies_docs.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_optional_yaml_dependency.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_output.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_paths.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_platform_edge_paths.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_profile.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_project_intake.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_public_api.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_redaction_security.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_retention_contention.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_retention_platform.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_run_app_reentrancy.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_run_bundle_retention.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_run_json_preflight.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_security_documentation.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_strict_json_consumer_docs.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_testing.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_typed_contracts.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_typer_adapter.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_changelog.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_consumer_typing.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_coverage.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_docs.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_release_provenance.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_release_ref.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_validate_schemas.py +0 -0
- {base_cli-0.5.0 → base_cli-0.5.1}/tests/test_verify_release_assets.py +0 -0
- {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
|
-
|
|
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 [[ "$
|
|
84
|
-
echo "Release tag must be v$
|
|
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" && "$
|
|
88
|
-
echo "PyPI publication requires
|
|
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: ${{
|
|
136
|
+
if: ${{ steps.metadata.outputs.source_ref_type == 'tag' }}
|
|
97
137
|
env:
|
|
98
|
-
RELEASE_TAG: ${{
|
|
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 "$
|
|
119
|
-
git archive "$
|
|
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: ${{
|
|
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: ${{
|
|
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
|
-
|
|
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@
|
|
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@
|
|
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="$
|
|
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" != "$
|
|
367
|
-
echo "Release tag $tag resolves to $tag_commit, not reviewed commit $
|
|
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 "$
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
72
|
-
The adopter documentation uses `~=0.5.0` for the release
|
|
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.
|
|
136
|
-
`base-cli>=0.
|
|
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.
|
|
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
|
-
|
|
13
|
-
The adopter documentation uses `~=0.5.0` for the release
|
|
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.
|
|
77
|
-
`base-cli>=0.
|
|
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.
|
|
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
|
-
|
|
39
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
72
|
-
The adopter documentation uses `~=0.5.0` for the release
|
|
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.
|
|
136
|
-
`base-cli>=0.
|
|
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
|
|
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
|
-
|
|
182
|
-
|
|
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")
|