splent-cli 1.35.0__tar.gz → 1.36.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {splent_cli-1.35.0/src/splent_cli.egg-info → splent_cli-1.36.0}/PKG-INFO +1 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/pyproject.toml +1 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_contracts.py +4 -2
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/export/export_puml.py +4 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_add.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_attach.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_compat.py +177 -5
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_contract.py +115 -55
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_create.py +153 -7
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_detach.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_drift.py +9 -134
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_info.py +8 -3
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_install.py +7 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_pin.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_refine.py +3 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_release.py +457 -61
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_remove.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_rename.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_sync_template.py +7 -3
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_test.py +92 -6
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_unlock.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_upgrade.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_auto_require.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_configure.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_create.py +12 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_derive.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_drift.py +42 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_resolve.py +2 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_sync_template.py +61 -4
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/context.py +11 -2
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/marketplace.py +38 -4
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/preflight.py +4 -2
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +1 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_config.py.j2 +2 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +1 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +1 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +1 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +1 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +2 -2
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +4 -1
- splent_cli-1.36.0/src/splent_cli/templates/product/product_ci_validate.yml.j2 +201 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_config.py.j2 +2 -2
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_own.css +1 -1
- splent_cli-1.36.0/src/splent_cli/utils/ci_services.py +506 -0
- splent_cli-1.36.0/src/splent_cli/utils/ci_workflow.py +338 -0
- splent_cli-1.36.0/src/splent_cli/utils/feature_requirements.py +116 -0
- splent_cli-1.36.0/src/splent_cli/utils/feature_resolution.py +170 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/manifest.py +5 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/template_drift.py +68 -6
- {splent_cli-1.35.0 → splent_cli-1.36.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli.egg-info/SOURCES.txt +4 -0
- splent_cli-1.35.0/src/splent_cli/templates/product/product_ci_validate.yml.j2 +0 -65
- {splent_cli-1.35.0 → splent_cli-1.36.0}/COPYING +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/COPYING.LESSER +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/README.md +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/setup.cfg +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_build.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_validate.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/release/release_resume.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/deploy_bundle.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/image_registry.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/pypi_quota.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/release_dry_run.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/spl_store.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/services/web_container.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/licenses/COPYING +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_400.html +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_401.html +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_404.html +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_500.html +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_app.css +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_app.js +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/db_client.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/feature_naming.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/forge.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/redact.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/refinement_facts.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/running_version.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/toml_edit.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.35.0 → splent_cli-1.36.0}/src/splent_cli.egg-info/top_level.txt +0 -0
|
@@ -18,6 +18,7 @@ import tomllib
|
|
|
18
18
|
|
|
19
19
|
from splent_cli.services import context, marketplace, spl_store
|
|
20
20
|
from splent_cli.utils import forge
|
|
21
|
+
from splent_cli.utils.feature_requirements import requirement_names
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def _snapshot_version_key(pyproject_path: Path) -> tuple:
|
|
@@ -58,9 +59,10 @@ def _local_contract_requires(workspace: str, package: str) -> list[str] | None:
|
|
|
58
59
|
.get("contract", {})
|
|
59
60
|
.get("requires", {})
|
|
60
61
|
)
|
|
62
|
+
# Names only: an entry may carry a version specifier ("auth>=1.20.0").
|
|
61
63
|
return {
|
|
62
|
-
"features": requires.get("features", []),
|
|
63
|
-
"optional": requires.get("features_optional", []),
|
|
64
|
+
"features": requirement_names(requires.get("features", [])),
|
|
65
|
+
"optional": requirement_names(requires.get("features_optional", [])),
|
|
64
66
|
}
|
|
65
67
|
return None
|
|
66
68
|
|
|
@@ -22,6 +22,7 @@ import click
|
|
|
22
22
|
from splent_cli.commands.uvl.uvl_utils import extract_implications_from_uvl_text
|
|
23
23
|
from splent_cli.services import context
|
|
24
24
|
from splent_cli.utils import proc
|
|
25
|
+
from splent_cli.utils.feature_requirements import requirement_names
|
|
25
26
|
from splent_cli.utils.feature_utils import normalize_namespace
|
|
26
27
|
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
27
28
|
|
|
@@ -381,7 +382,9 @@ def _generate_feature_puml(product_name: str, uvl_data: dict, contracts: dict) -
|
|
|
381
382
|
env_vars = requires.get("env_vars", [])
|
|
382
383
|
if env_vars:
|
|
383
384
|
details.append(f"**Env vars**: {_puml_join(env_vars)}")
|
|
384
|
-
|
|
385
|
+
# Names only: an entry may carry a version specifier
|
|
386
|
+
# ("auth>=1.20.0"), which the diagram has no use for.
|
|
387
|
+
req_features = requirement_names(requires.get("features", []))
|
|
385
388
|
if req_features:
|
|
386
389
|
details.append(f"**Requires**: {_puml_join(req_features)}")
|
|
387
390
|
|
|
@@ -16,6 +16,7 @@ from splent_cli.utils.feature_utils import (
|
|
|
16
16
|
from splent_cli.utils.io_utils import load_toml
|
|
17
17
|
from splent_cli.utils.toml_edit import write_toml
|
|
18
18
|
from splent_cli.utils.manifest import feature_key, remove_feature, set_feature_state
|
|
19
|
+
from splent_cli.utils.ci_workflow import keeps_ci_workflows
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
@click.command(
|
|
@@ -35,6 +36,7 @@ from splent_cli.utils.manifest import feature_key, remove_feature, set_feature_s
|
|
|
35
36
|
flag_value="prod",
|
|
36
37
|
help="Add to features_prod (production only).",
|
|
37
38
|
)
|
|
39
|
+
@keeps_ci_workflows
|
|
38
40
|
def feature_add(full_name, env_scope):
|
|
39
41
|
"""
|
|
40
42
|
Adds a local feature (no version, no repo) to the current SPLENT product.
|
|
@@ -17,6 +17,7 @@ from splent_cli.utils.feature_utils import (
|
|
|
17
17
|
from splent_cli.utils.io_utils import load_toml
|
|
18
18
|
from splent_cli.utils.toml_edit import write_toml
|
|
19
19
|
from splent_cli.utils.manifest import feature_key, remove_feature, set_feature_state
|
|
20
|
+
from splent_cli.utils.ci_workflow import keeps_ci_workflows
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _target_lists(env_scope, declared):
|
|
@@ -50,6 +51,7 @@ def _target_lists(env_scope, declared):
|
|
|
50
51
|
flag_value="prod",
|
|
51
52
|
help="Add to features_prod (production only).",
|
|
52
53
|
)
|
|
54
|
+
@keeps_ci_workflows
|
|
53
55
|
def feature_attach(feature_identifier, version, env_scope):
|
|
54
56
|
"""
|
|
55
57
|
Attach a cached feature version to the current product.
|
|
@@ -11,6 +11,11 @@ import click
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
13
|
from splent_cli.services import context
|
|
14
|
+
from splent_cli.utils.feature_requirements import (
|
|
15
|
+
parse_requirement,
|
|
16
|
+
requirement_names,
|
|
17
|
+
satisfies,
|
|
18
|
+
)
|
|
14
19
|
from splent_cli.utils.feature_utils import normalize_namespace
|
|
15
20
|
from splent_cli.utils.io_utils import load_toml
|
|
16
21
|
|
|
@@ -118,7 +123,9 @@ def _read_contract(cache_path: Path) -> dict:
|
|
|
118
123
|
"docker": raw.get("provides", {}).get("docker", []),
|
|
119
124
|
},
|
|
120
125
|
"requires": {
|
|
121
|
-
|
|
126
|
+
# Names only: an entry may carry a version specifier
|
|
127
|
+
# ("auth>=1.20.0"), which version_floor_findings reads.
|
|
128
|
+
"features": requirement_names(raw.get("requires", {}).get("features", [])),
|
|
122
129
|
"env_vars": raw.get("requires", {}).get("env_vars", []),
|
|
123
130
|
},
|
|
124
131
|
}
|
|
@@ -266,8 +273,16 @@ def _resolve_all_product_features(
|
|
|
266
273
|
if not features_raw:
|
|
267
274
|
raise SystemExit(" No features declared in pyproject.toml.")
|
|
268
275
|
|
|
276
|
+
return _resolve_entries(features_raw, product_dir, workspace)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _resolve_entries(
|
|
280
|
+
entries: list[str], product_dir: str, workspace: str
|
|
281
|
+
) -> list[tuple[str, Path]]:
|
|
282
|
+
"""Feature entries of the product as ``(label, path)``, skipping, with a
|
|
283
|
+
warning, those that are not in the workspace."""
|
|
269
284
|
resolved = []
|
|
270
|
-
for entry in
|
|
285
|
+
for entry in entries:
|
|
271
286
|
ns, name, version = _parse_pyproject_entry(entry)
|
|
272
287
|
label = f"{name}@{version}" if version else name
|
|
273
288
|
if version:
|
|
@@ -546,16 +561,173 @@ def naming_findings(workspace: str, product_dir: str) -> list[dict]:
|
|
|
546
561
|
return findings
|
|
547
562
|
|
|
548
563
|
|
|
549
|
-
def
|
|
550
|
-
"""
|
|
564
|
+
def _written_requires(path: Path) -> dict[str, list]:
|
|
565
|
+
"""The requires lists of a feature's contract, entries as written."""
|
|
566
|
+
pyproject = path / "pyproject.toml"
|
|
567
|
+
if not pyproject.exists():
|
|
568
|
+
return {}
|
|
569
|
+
data = load_toml(pyproject, what=f"pyproject.toml for {path.name}")
|
|
570
|
+
requires = (
|
|
571
|
+
data.get("tool", {}).get("splent", {}).get("contract", {}).get("requires", {})
|
|
572
|
+
)
|
|
573
|
+
if not isinstance(requires, dict):
|
|
574
|
+
return {}
|
|
575
|
+
return {
|
|
576
|
+
key: requires.get(key)
|
|
577
|
+
for key in ("features", "features_manual", "features_optional")
|
|
578
|
+
if isinstance(requires.get(key), list)
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def _project_version(path: Path) -> str | None:
|
|
583
|
+
pyproject = path / "pyproject.toml"
|
|
584
|
+
if not pyproject.exists():
|
|
585
|
+
return None
|
|
586
|
+
data = load_toml(pyproject, what=f"pyproject.toml for {path.name}")
|
|
587
|
+
version = data.get("project", {}).get("version")
|
|
588
|
+
return str(version) if version else None
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def version_floor_findings(labeled_paths: list[tuple[str, Path]]) -> list[dict]:
|
|
592
|
+
"""Requirements whose version specifier the product's copy does not meet.
|
|
593
|
+
|
|
594
|
+
A requires entry may say which releases of another feature it works with
|
|
595
|
+
(``auth>=1.20.0``), because it calls an API that release added. Against
|
|
596
|
+
a version the product pins, a miss is an error: the product would run a
|
|
597
|
+
release the feature was never written for. Against an editable feature
|
|
598
|
+
it is a warning, read from the working copy's ``[project].version``,
|
|
599
|
+
since that copy is not released yet. A specifier that is not PEP 440 is
|
|
600
|
+
an error, and a feature the product lacks is reported elsewhere.
|
|
601
|
+
"""
|
|
602
|
+
present: dict[str, tuple[str, Path, str | None]] = {}
|
|
603
|
+
for label, path in labeled_paths:
|
|
604
|
+
name, _, version = label.partition("@")
|
|
605
|
+
present[name.removeprefix("splent_feature_")] = (label, path, version or None)
|
|
606
|
+
|
|
607
|
+
findings: list[dict] = []
|
|
608
|
+
for label, path in labeled_paths:
|
|
609
|
+
short = label.split("@", 1)[0].removeprefix("splent_feature_")
|
|
610
|
+
seen: set[tuple[str, str]] = set()
|
|
611
|
+
for key, entries in _written_requires(path).items():
|
|
612
|
+
for entry in entries:
|
|
613
|
+
try:
|
|
614
|
+
req = parse_requirement(entry)
|
|
615
|
+
except ValueError as e:
|
|
616
|
+
findings.append(
|
|
617
|
+
{
|
|
618
|
+
"severity": "error",
|
|
619
|
+
"field": "requires",
|
|
620
|
+
"values": [str(entry)],
|
|
621
|
+
"features": [label],
|
|
622
|
+
"message": f"In {short}'s requires.{key}, {e}.",
|
|
623
|
+
}
|
|
624
|
+
)
|
|
625
|
+
continue
|
|
626
|
+
if not req.specifier or (req.name, req.specifier) in seen:
|
|
627
|
+
continue
|
|
628
|
+
seen.add((req.name, req.specifier))
|
|
629
|
+
target = present.get(req.name)
|
|
630
|
+
if target is None:
|
|
631
|
+
continue
|
|
632
|
+
target_label, target_path, pinned = target
|
|
633
|
+
if pinned:
|
|
634
|
+
met = satisfies(req.specifier, pinned)
|
|
635
|
+
if met is None:
|
|
636
|
+
findings.append(
|
|
637
|
+
{
|
|
638
|
+
"severity": "warning",
|
|
639
|
+
"field": "requires",
|
|
640
|
+
"values": [str(req)],
|
|
641
|
+
"features": [label, target_label],
|
|
642
|
+
"message": (
|
|
643
|
+
f"{short} requires {req}, and the product pins "
|
|
644
|
+
f"{req.name} at {pinned}, which is not a "
|
|
645
|
+
"version that can be compared."
|
|
646
|
+
),
|
|
647
|
+
}
|
|
648
|
+
)
|
|
649
|
+
elif not met:
|
|
650
|
+
findings.append(
|
|
651
|
+
{
|
|
652
|
+
"severity": "error",
|
|
653
|
+
"field": "requires",
|
|
654
|
+
"values": [str(req)],
|
|
655
|
+
"features": [label, target_label],
|
|
656
|
+
"message": (
|
|
657
|
+
f"{short} requires {req}, but the product pins "
|
|
658
|
+
f"{req.name} at {pinned}. Pin a release of "
|
|
659
|
+
f"{req.name} that satisfies it."
|
|
660
|
+
),
|
|
661
|
+
}
|
|
662
|
+
)
|
|
663
|
+
continue
|
|
664
|
+
working = _project_version(target_path)
|
|
665
|
+
met = satisfies(req.specifier, working) if working else None
|
|
666
|
+
if met is False or met is None:
|
|
667
|
+
found = (
|
|
668
|
+
f"the working copy of {req.name} is {working}"
|
|
669
|
+
if working
|
|
670
|
+
else f"the version of the working copy of {req.name} "
|
|
671
|
+
"cannot be read"
|
|
672
|
+
)
|
|
673
|
+
findings.append(
|
|
674
|
+
{
|
|
675
|
+
"severity": "warning",
|
|
676
|
+
"field": "requires",
|
|
677
|
+
"values": [str(req)],
|
|
678
|
+
"features": [label, target_label],
|
|
679
|
+
"message": (
|
|
680
|
+
f"{short} requires {req}, and {found} "
|
|
681
|
+
f"([project].version). Release {req.name} at a "
|
|
682
|
+
"version that satisfies it before the product "
|
|
683
|
+
"pins it."
|
|
684
|
+
),
|
|
685
|
+
}
|
|
686
|
+
)
|
|
687
|
+
return findings
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def _environment_only_features(
|
|
691
|
+
product_dir: str, workspace: str, env: str | None
|
|
692
|
+
) -> list[tuple[str, Path]]:
|
|
693
|
+
"""The features ``features_<env>`` adds to the base list, resolved."""
|
|
694
|
+
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
695
|
+
|
|
696
|
+
env = context.normalize_env(env)
|
|
697
|
+
if not env:
|
|
698
|
+
return []
|
|
699
|
+
try:
|
|
700
|
+
reader = PyprojectReader.for_product(product_dir)
|
|
701
|
+
base = set(reader.features)
|
|
702
|
+
extra = [e for e in reader.features_for_env(env) if e not in base]
|
|
703
|
+
except (FileNotFoundError, ValueError):
|
|
704
|
+
return []
|
|
705
|
+
return _resolve_entries(extra, product_dir, workspace)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def run_all_product_check(
|
|
709
|
+
workspace: str, product_dir: str, env: str | None = None
|
|
710
|
+
) -> list[dict]:
|
|
711
|
+
"""Run the full product compatibility check. Returns findings list.
|
|
712
|
+
|
|
713
|
+
The collisions are checked between the base features. The version
|
|
714
|
+
floors are checked across the features the product loads in ``env``
|
|
715
|
+
(SPLENT_ENV when None, ``dev`` when that is unset too), base plus
|
|
716
|
+
``features_dev`` or ``features_prod``, the selection phase 1 checks.
|
|
717
|
+
"""
|
|
551
718
|
try:
|
|
552
719
|
labeled_paths = _resolve_all_product_features(product_dir, workspace)
|
|
553
720
|
except SystemExit:
|
|
554
721
|
return []
|
|
555
722
|
if not labeled_paths:
|
|
556
723
|
return []
|
|
724
|
+
if env is None:
|
|
725
|
+
env = os.getenv("SPLENT_ENV") or "dev"
|
|
557
726
|
labeled_contracts = [(lbl, _read_contract(path)) for lbl, path in labeled_paths]
|
|
558
|
-
|
|
727
|
+
findings = _analyse_all(labeled_contracts) + version_floor_findings(
|
|
728
|
+
labeled_paths + _environment_only_features(product_dir, workspace, env)
|
|
729
|
+
)
|
|
730
|
+
return sorted(findings, key=lambda f: SEVERITY_ORDER[f["severity"]])
|
|
559
731
|
|
|
560
732
|
|
|
561
733
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -20,7 +20,13 @@ import click
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
|
|
22
22
|
from splent_cli.services import context
|
|
23
|
-
from splent_cli.commands.feature.feature_release import
|
|
23
|
+
from splent_cli.commands.feature.feature_release import (
|
|
24
|
+
infer_contract,
|
|
25
|
+
merge_preserved,
|
|
26
|
+
requirement_problems,
|
|
27
|
+
write_contract,
|
|
28
|
+
)
|
|
29
|
+
from splent_cli.utils.feature_resolution import package_identifier, resolve_feature_ref
|
|
24
30
|
from splent_cli.utils.feature_utils import normalize_namespace
|
|
25
31
|
from splent_cli.utils.contract_freshness import mark_contract_fresh
|
|
26
32
|
from splent_cli.utils.io_utils import load_toml
|
|
@@ -51,39 +57,24 @@ def update_contract(feature_path: str, namespace: str, feature_name: str) -> dic
|
|
|
51
57
|
|
|
52
58
|
|
|
53
59
|
def _resolve_feature(feature_ref: str, workspace: str) -> tuple[Path, str, str]:
|
|
54
|
-
"""Resolve a feature
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
# 1. Workspace root (editable features)
|
|
65
|
-
workspace_path = Path(workspace) / name
|
|
66
|
-
if workspace_path.exists():
|
|
67
|
-
return workspace_path, ns, name
|
|
68
|
-
|
|
69
|
-
# 2. Cache (pinned features)
|
|
70
|
-
cache_base = Path(workspace) / ".splent_cache" / "features" / ns
|
|
71
|
-
|
|
72
|
-
if version:
|
|
73
|
-
cache_path = cache_base / f"{name}@{version}"
|
|
74
|
-
else:
|
|
75
|
-
cache_path = cache_base / name
|
|
76
|
-
if not cache_path.exists():
|
|
77
|
-
candidates = sorted(cache_base.glob(f"{name}@*"))
|
|
78
|
-
if candidates:
|
|
79
|
-
cache_path = candidates[-1]
|
|
80
|
-
|
|
81
|
-
if not cache_path.exists():
|
|
60
|
+
"""Resolve a feature as feature:drift does, see
|
|
61
|
+
:mod:`splent_cli.utils.feature_resolution`, or exit naming where it was
|
|
62
|
+
looked for."""
|
|
63
|
+
found = resolve_feature_ref(feature_ref, Path(workspace))
|
|
64
|
+
if found is None:
|
|
65
|
+
identifier = package_identifier(feature_ref)
|
|
66
|
+
base, at, version = identifier.partition("@")
|
|
67
|
+
ns_raw, slash, name = base.rpartition("/")
|
|
68
|
+
ns = normalize_namespace(ns_raw) if slash else DEFAULT_NAMESPACE
|
|
69
|
+
wanted = f"{name}@{version}" if at else name
|
|
82
70
|
raise SystemExit(
|
|
83
|
-
f"❌ Feature not found: {
|
|
71
|
+
f"❌ Feature not found: {wanted}\n"
|
|
72
|
+
f" Looked for it at the workspace root and in "
|
|
73
|
+
f".splent_cache/features/{ns}/.\n"
|
|
84
74
|
f" Run: splent feature:clone {ns.replace('_', '-')}/{name}"
|
|
85
75
|
)
|
|
86
|
-
|
|
76
|
+
path, ns, name = found
|
|
77
|
+
return path, ns, name
|
|
87
78
|
|
|
88
79
|
|
|
89
80
|
def _in_cache(path: Path, workspace: str) -> bool:
|
|
@@ -101,6 +92,15 @@ def _in_cache(path: Path, workspace: str) -> bool:
|
|
|
101
92
|
return True
|
|
102
93
|
|
|
103
94
|
|
|
95
|
+
def _written_contract(pyproject_path: Path) -> dict:
|
|
96
|
+
"""The ``[tool.splent.contract]`` table as written, or empty."""
|
|
97
|
+
if not pyproject_path.exists():
|
|
98
|
+
return {}
|
|
99
|
+
data = load_toml(pyproject_path, what="pyproject.toml")
|
|
100
|
+
raw = data.get("tool", {}).get("splent", {}).get("contract", {})
|
|
101
|
+
return raw if isinstance(raw, dict) else {}
|
|
102
|
+
|
|
103
|
+
|
|
104
104
|
def _read_current_contract(pyproject_path: Path) -> dict:
|
|
105
105
|
"""Read the contract block currently written in pyproject.toml."""
|
|
106
106
|
if not pyproject_path.exists():
|
|
@@ -116,6 +116,7 @@ def _read_current_contract(pyproject_path: Path) -> dict:
|
|
|
116
116
|
"blueprints": raw.get("provides", {}).get("blueprints", []),
|
|
117
117
|
"models": raw.get("provides", {}).get("models", []),
|
|
118
118
|
"commands": raw.get("provides", {}).get("commands", []),
|
|
119
|
+
"commands_help": raw.get("provides", {}).get("commands_help", {}),
|
|
119
120
|
"hooks": raw.get("provides", {}).get("hooks", []),
|
|
120
121
|
"services": raw.get("provides", {}).get("services", []),
|
|
121
122
|
"signals": raw.get("provides", {}).get("signals", []),
|
|
@@ -153,6 +154,44 @@ def _diff_field(label: str, old: list, new: list) -> list[str]:
|
|
|
153
154
|
return lines
|
|
154
155
|
|
|
155
156
|
|
|
157
|
+
def _help_entries(table) -> dict[str, tuple[str, str]]:
|
|
158
|
+
"""``provides.commands_help`` as ``{name: (usage, help)}``."""
|
|
159
|
+
if not isinstance(table, dict):
|
|
160
|
+
return {}
|
|
161
|
+
entries = {}
|
|
162
|
+
for name, entry in table.items():
|
|
163
|
+
entry = entry if isinstance(entry, dict) else {}
|
|
164
|
+
entries[str(name)] = (
|
|
165
|
+
str(entry.get("usage", "") or ""),
|
|
166
|
+
str(entry.get("help", "") or ""),
|
|
167
|
+
)
|
|
168
|
+
return entries
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _diff_commands_help(old_table, new_table) -> list[str]:
|
|
172
|
+
"""What a rewrite changes in ``provides.commands_help``: the usage and
|
|
173
|
+
the help line of each command, which come from its decorators and its
|
|
174
|
+
docstring."""
|
|
175
|
+
old, new = _help_entries(old_table), _help_entries(new_table)
|
|
176
|
+
label = "provides.commands_help"
|
|
177
|
+
lines = []
|
|
178
|
+
for name in sorted(set(old) | set(new)):
|
|
179
|
+
if name not in old:
|
|
180
|
+
lines.append(click.style(f" + {label}: {name}", fg="green"))
|
|
181
|
+
elif name not in new:
|
|
182
|
+
lines.append(click.style(f" - {label}: {name}", fg="red"))
|
|
183
|
+
else:
|
|
184
|
+
for field, before, after in zip(("usage", "help"), old[name], new[name]):
|
|
185
|
+
if before != after:
|
|
186
|
+
lines.append(
|
|
187
|
+
click.style(
|
|
188
|
+
f" ~ {label}.{name}.{field}: {before!r} → {after!r}",
|
|
189
|
+
fg="green",
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
return lines
|
|
193
|
+
|
|
194
|
+
|
|
156
195
|
def _print_contract(contract: dict, feature_name: str) -> None:
|
|
157
196
|
"""Pretty-print an inferred contract dict."""
|
|
158
197
|
|
|
@@ -264,6 +303,13 @@ def _print_diff(current: dict, inferred: dict) -> bool:
|
|
|
264
303
|
diff_lines.extend(
|
|
265
304
|
_diff_field(label, current.get(key, []), inferred.get(key, []))
|
|
266
305
|
)
|
|
306
|
+
# A changed docstring changes only this table, which --write rewrites
|
|
307
|
+
# too: compared here, or the contract never got it.
|
|
308
|
+
diff_lines.extend(
|
|
309
|
+
_diff_commands_help(
|
|
310
|
+
current.get("commands_help", {}), inferred.get("commands_help", {})
|
|
311
|
+
)
|
|
312
|
+
)
|
|
267
313
|
|
|
268
314
|
if not diff_lines:
|
|
269
315
|
return False
|
|
@@ -304,9 +350,14 @@ def feature_contract(feature_ref, write):
|
|
|
304
350
|
Use --write to update [tool.splent.contract] in pyproject.toml without
|
|
305
351
|
touching version, git tags, or publishing anything.
|
|
306
352
|
|
|
353
|
+
\b
|
|
354
|
+
FEATURE_REF is a short name (notes), a package name
|
|
355
|
+
(splent_feature_notes), either one under a namespace (splent-io/notes),
|
|
356
|
+
or a pinned snapshot (splent-io/notes@v1.2.0).
|
|
357
|
+
|
|
307
358
|
\b
|
|
308
359
|
Examples:
|
|
309
|
-
splent feature:contract
|
|
360
|
+
splent feature:contract notes
|
|
310
361
|
splent feature:contract splent_feature_notes --write
|
|
311
362
|
splent feature:contract splent-io/splent_feature_notes --write
|
|
312
363
|
"""
|
|
@@ -333,33 +384,42 @@ def feature_contract(feature_ref, write):
|
|
|
333
384
|
click.echo(" 🔍 Scanning source code…")
|
|
334
385
|
inferred = infer_contract(str(cache_path), ns, name)
|
|
335
386
|
|
|
336
|
-
#
|
|
337
|
-
#
|
|
338
|
-
# what --write would produce
|
|
387
|
+
# What --write keeps from the written contract (features_manual,
|
|
388
|
+
# routes_manual, features_optional) is merged here the same way, so the
|
|
389
|
+
# dry-run diff shows exactly what --write would produce, including real
|
|
390
|
+
# drops of stale inferred deps.
|
|
339
391
|
current = _read_current_contract(pyproject_path)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
)
|
|
392
|
+
scanned_optional = set(inferred.get("requires_features_optional", []))
|
|
393
|
+
inferred = merge_preserved(inferred, _written_contract(pyproject_path))
|
|
394
|
+
for label, values in (
|
|
395
|
+
("requires.features_manual", inferred["requires_features_manual"]),
|
|
396
|
+
("provides.routes_manual", inferred["routes_manual"]),
|
|
397
|
+
(
|
|
398
|
+
"requires.features_optional the scan does not find",
|
|
399
|
+
sorted(set(inferred["requires_features_optional"]) - scanned_optional),
|
|
400
|
+
),
|
|
401
|
+
):
|
|
402
|
+
if values:
|
|
403
|
+
click.echo(
|
|
404
|
+
click.style(f" Preserving {label}: ", fg="bright_black")
|
|
405
|
+
+ ", ".join(values)
|
|
406
|
+
)
|
|
349
407
|
|
|
350
|
-
#
|
|
351
|
-
#
|
|
352
|
-
#
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
408
|
+
# A version specifier is written by hand after a name. One that is not
|
|
409
|
+
# PEP 440 would break every reader of the contract, so it is refused
|
|
410
|
+
# here, in the dry run too, before anyone publishes it.
|
|
411
|
+
problems = requirement_problems(inferred)
|
|
412
|
+
if problems:
|
|
413
|
+
click.secho(
|
|
414
|
+
" ❌ These requirements are not valid, so the contract cannot be written:",
|
|
415
|
+
fg="red",
|
|
416
|
+
)
|
|
417
|
+
for problem in problems:
|
|
418
|
+
click.secho(f" {problem}", fg="red")
|
|
359
419
|
click.echo(
|
|
360
|
-
|
|
361
|
-
+ ", ".join(manual_routes)
|
|
420
|
+
' Write a short name with an optional PEP 440 specifier, such as "auth>=1.20.0".'
|
|
362
421
|
)
|
|
422
|
+
raise SystemExit(1)
|
|
363
423
|
|
|
364
424
|
# Show inferred contract
|
|
365
425
|
click.echo(click.style(" Inferred contract:", bold=True))
|