splent-cli 1.34.1__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.34.1/src/splent_cli.egg-info → splent_cli-1.36.0}/PKG-INFO +1 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/pyproject.toml +1 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/cli.py +48 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_contracts.py +4 -2
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/export/export_puml.py +4 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_add.py +2 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_attach.py +2 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_compat.py +312 -5
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_contract.py +115 -55
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_create.py +160 -7
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_detach.py +6 -3
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_drift.py +9 -134
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_env.py +4 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_info.py +8 -3
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_install.py +7 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_pin.py +2 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_refine.py +10 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_release.py +457 -61
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_remove.py +7 -4
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_rename.py +2 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_sync_template.py +7 -3
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_test.py +97 -26
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_unlock.py +2 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_upgrade.py +2 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_auto_require.py +2 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_configure.py +2 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_create.py +12 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_derive.py +2 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_drift.py +42 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_resolve.py +10 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_sync_template.py +82 -3
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_validate.py +9 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/context.py +11 -2
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/marketplace.py +38 -4
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/preflight.py +4 -2
- splent_cli-1.36.0/src/splent_cli/services/web_container.py +66 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +1 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_config.py.j2 +2 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +1 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +1 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +1 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +1 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +2 -2
- {splent_cli-1.34.1 → 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.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_config.py.j2 +2 -2
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_own.css +1 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/cache_utils.py +34 -0
- 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_naming.py +58 -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.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/integrity.py +2 -2
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/manifest.py +5 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/template_drift.py +68 -6
- {splent_cli-1.34.1 → splent_cli-1.36.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli.egg-info/SOURCES.txt +6 -0
- splent_cli-1.34.1/src/splent_cli/templates/product/product_ci_validate.yml.j2 +0 -50
- {splent_cli-1.34.1 → splent_cli-1.36.0}/COPYING +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/COPYING.LESSER +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/README.md +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/setup.cfg +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_build.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/release/release_resume.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/deploy_bundle.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/image_registry.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/pypi_quota.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/release_dry_run.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/services/spl_store.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/licenses/COPYING +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_400.html +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_401.html +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_404.html +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_500.html +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_app.css +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_app.js +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/db_client.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/forge.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/redact.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/refinement_facts.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/running_version.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/toml_edit.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.34.1 → splent_cli-1.36.0}/src/splent_cli.egg-info/top_level.txt +0 -0
|
@@ -11,6 +11,15 @@ from splent_cli.utils.command_loader import load_commands
|
|
|
11
11
|
load_dotenv()
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
def _missing_module(exc: BaseException) -> str | None:
|
|
15
|
+
"""The module whose absence made building the app fail, if that is why."""
|
|
16
|
+
while exc is not None:
|
|
17
|
+
if isinstance(exc, ModuleNotFoundError) and exc.name:
|
|
18
|
+
return exc.name
|
|
19
|
+
exc = exc.__cause__ or exc.__context__
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
14
23
|
class SPLENTCLI(click.Group):
|
|
15
24
|
"""
|
|
16
25
|
Main SPLENT CLI class.
|
|
@@ -101,15 +110,53 @@ class SPLENTCLI(click.Group):
|
|
|
101
110
|
|
|
102
111
|
# ── App context injection ─────────────────────────────────────
|
|
103
112
|
|
|
113
|
+
def _delegate_to_the_product(self, ctx) -> None:
|
|
114
|
+
"""Run this command in the product's web container, and exit with its
|
|
115
|
+
code; return when that container is not running.
|
|
116
|
+
|
|
117
|
+
Called only when the app cannot be built here: building it imports
|
|
118
|
+
what the features declare, which the product's image installs and the
|
|
119
|
+
CLI's may not (see services/web_container.py).
|
|
120
|
+
"""
|
|
121
|
+
from splent_cli.services import web_container
|
|
122
|
+
|
|
123
|
+
product = os.getenv("SPLENT_APP")
|
|
124
|
+
if not product or web_container.is_product_container():
|
|
125
|
+
return
|
|
126
|
+
if not web_container.running(web_container.name(product)):
|
|
127
|
+
return
|
|
128
|
+
argv = [*ctx.protected_args, *ctx.args]
|
|
129
|
+
raise SystemExit(web_container.delegate(product, argv))
|
|
130
|
+
|
|
104
131
|
def invoke(self, ctx):
|
|
105
132
|
cmd_name = ctx.protected_args[0] if ctx.protected_args else None
|
|
106
133
|
command = self.get_command(ctx, cmd_name)
|
|
107
134
|
|
|
135
|
+
# A feature's own commands are only found by building the app, so
|
|
136
|
+
# here they may be missing although the product has them.
|
|
137
|
+
if command is None and cmd_name and cmd_name.startswith("feature:"):
|
|
138
|
+
self._delegate_to_the_product(ctx)
|
|
139
|
+
|
|
108
140
|
if command and getattr(command, "requires_app", False):
|
|
109
141
|
from splent_cli.utils.dynamic_imports import get_app
|
|
110
142
|
from splent_cli.utils.db_utils import check_db_connection
|
|
111
143
|
|
|
112
|
-
|
|
144
|
+
try:
|
|
145
|
+
app = get_app()
|
|
146
|
+
except Exception as exc:
|
|
147
|
+
missing = _missing_module(exc)
|
|
148
|
+
if missing is None:
|
|
149
|
+
raise
|
|
150
|
+
# Only then: where the app builds here, the command runs here
|
|
151
|
+
# with the tools the CLI image has (ruff formats migrations).
|
|
152
|
+
self._delegate_to_the_product(ctx)
|
|
153
|
+
product = os.getenv("SPLENT_APP", "The product")
|
|
154
|
+
raise click.ClickException(
|
|
155
|
+
f"{product}'s features need {missing}, which the product's "
|
|
156
|
+
"image installs and the CLI's does not.\n"
|
|
157
|
+
" Start the product (splent product:up --dev) and run the "
|
|
158
|
+
"command again: it then runs in the product's web container."
|
|
159
|
+
) from exc
|
|
113
160
|
if getattr(command, "requires_db", False):
|
|
114
161
|
if not check_db_connection(app):
|
|
115
162
|
raise SystemExit(1)
|
|
@@ -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:
|
|
@@ -411,16 +426,308 @@ def _analyse_all(labeled_contracts: list[tuple[str, dict]]) -> list[dict]:
|
|
|
411
426
|
return sorted(findings, key=lambda f: SEVERITY_ORDER[f["severity"]])
|
|
412
427
|
|
|
413
428
|
|
|
414
|
-
def
|
|
415
|
-
"""
|
|
429
|
+
def _contract_env(path: Path) -> str | None:
|
|
430
|
+
"""The environment a feature's contract confines it to, if any."""
|
|
431
|
+
pyproject = path / "pyproject.toml"
|
|
432
|
+
if not pyproject.exists():
|
|
433
|
+
return None
|
|
434
|
+
data = load_toml(pyproject, what=f"pyproject.toml for {path.name}")
|
|
435
|
+
return data.get("tool", {}).get("splent", {}).get("contract", {}).get("env")
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def env_scope_findings(workspace: str, product_dir: str) -> list[dict]:
|
|
439
|
+
"""Features confined to one environment, in a list that installs them in
|
|
440
|
+
the other.
|
|
441
|
+
|
|
442
|
+
mailhog and phpmyadmin declare env = "dev" in their contract. In the base
|
|
443
|
+
list the production image installed them too, phpMyAdmin with autologin
|
|
444
|
+
to the database, and nothing said so.
|
|
445
|
+
"""
|
|
446
|
+
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
447
|
+
|
|
448
|
+
reader = PyprojectReader.for_product(product_dir)
|
|
449
|
+
lists = {
|
|
450
|
+
"features": reader.features,
|
|
451
|
+
"features_dev": reader.features_dev,
|
|
452
|
+
"features_prod": reader.features_prod,
|
|
453
|
+
}
|
|
454
|
+
findings = []
|
|
455
|
+
for key, entries in lists.items():
|
|
456
|
+
for entry in entries:
|
|
457
|
+
ns, name, version = _parse_pyproject_entry(entry)
|
|
458
|
+
path = product_feature_dir(workspace, product_dir, ns, name, version)
|
|
459
|
+
if path is None:
|
|
460
|
+
continue
|
|
461
|
+
scope = _contract_env(path)
|
|
462
|
+
short = name.removeprefix("splent_feature_")
|
|
463
|
+
if scope == "dev" and key != "features_dev":
|
|
464
|
+
message = (
|
|
465
|
+
f"{short} is a development tool (its contract says env = "
|
|
466
|
+
f'"dev"), but [tool.splent] {key} puts it in the production '
|
|
467
|
+
"image too. Move it to features_dev."
|
|
468
|
+
)
|
|
469
|
+
elif scope == "prod" and key == "features_dev":
|
|
470
|
+
message = (
|
|
471
|
+
f"{short} only runs in production (its contract says env = "
|
|
472
|
+
'"prod"), but it is in [tool.splent] features_dev. Move it '
|
|
473
|
+
"to features_prod."
|
|
474
|
+
)
|
|
475
|
+
else:
|
|
476
|
+
continue
|
|
477
|
+
findings.append(
|
|
478
|
+
{
|
|
479
|
+
"severity": "error",
|
|
480
|
+
"field": "env",
|
|
481
|
+
"message": message,
|
|
482
|
+
"features": [short],
|
|
483
|
+
}
|
|
484
|
+
)
|
|
485
|
+
return findings
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _joins_the_shared_network(compose_file: Path) -> bool:
|
|
489
|
+
"""Whether a compose file puts its services on the shared splent_network
|
|
490
|
+
instead of the product's own (``name: ${SPLENT_NETWORK:-splent_network}``)."""
|
|
491
|
+
import yaml
|
|
492
|
+
|
|
493
|
+
try:
|
|
494
|
+
data = yaml.safe_load(compose_file.read_text()) or {}
|
|
495
|
+
except (OSError, yaml.YAMLError):
|
|
496
|
+
return False
|
|
497
|
+
networks = data.get("networks") or {}
|
|
498
|
+
spec = networks.get("splent_network")
|
|
499
|
+
if not isinstance(spec, dict) or not spec.get("external"):
|
|
500
|
+
return False
|
|
501
|
+
return "SPLENT_NETWORK" not in str(spec.get("name", ""))
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def network_findings(workspace: str, product_dir: str) -> list[dict]:
|
|
505
|
+
"""Feature services the product cannot reach.
|
|
506
|
+
|
|
507
|
+
Each product has a network of its own, and a feature's services join it
|
|
508
|
+
through SPLENT_NETWORK. mailhog and phpmyadmin named the shared network
|
|
509
|
+
instead, so the app could not send mail to Mailhog and phpMyAdmin could
|
|
510
|
+
not reach the database, and nothing said why.
|
|
511
|
+
"""
|
|
512
|
+
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
513
|
+
|
|
514
|
+
reader = PyprojectReader.for_product(product_dir)
|
|
515
|
+
findings = []
|
|
516
|
+
for entry in reader.features + reader.features_dev + reader.features_prod:
|
|
517
|
+
ns, name, version = _parse_pyproject_entry(entry)
|
|
518
|
+
path = product_feature_dir(workspace, product_dir, ns, name, version)
|
|
519
|
+
if path is None:
|
|
520
|
+
continue
|
|
521
|
+
files = sorted((path / "docker").glob("docker-compose*.yml"))
|
|
522
|
+
if any(_joins_the_shared_network(f) for f in files):
|
|
523
|
+
short = name.removeprefix("splent_feature_")
|
|
524
|
+
findings.append(
|
|
525
|
+
{
|
|
526
|
+
"severity": "error",
|
|
527
|
+
"field": "docker",
|
|
528
|
+
"message": (
|
|
529
|
+
f"{short}'s services join the shared splent_network, "
|
|
530
|
+
"where the product's app cannot reach them. Its compose "
|
|
531
|
+
"file needs name: ${SPLENT_NETWORK:-splent_network} "
|
|
532
|
+
"under networks.splent_network."
|
|
533
|
+
),
|
|
534
|
+
"features": [short],
|
|
535
|
+
}
|
|
536
|
+
)
|
|
537
|
+
return findings
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def naming_findings(workspace: str, product_dir: str) -> list[dict]:
|
|
541
|
+
"""Features named after a product (auth_uvlhub). A warning, so the debt
|
|
542
|
+
shows in every validation until the product drops them."""
|
|
543
|
+
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
544
|
+
|
|
545
|
+
from splent_cli.utils.feature_naming import product_suffix, product_suffix_message
|
|
546
|
+
|
|
547
|
+
reader = PyprojectReader.for_product(product_dir)
|
|
548
|
+
findings = []
|
|
549
|
+
for entry in reader.features + reader.features_dev + reader.features_prod:
|
|
550
|
+
_, name, _ = _parse_pyproject_entry(entry)
|
|
551
|
+
product = product_suffix(name, workspace)
|
|
552
|
+
if product:
|
|
553
|
+
findings.append(
|
|
554
|
+
{
|
|
555
|
+
"severity": "warning",
|
|
556
|
+
"field": "name",
|
|
557
|
+
"message": product_suffix_message(name, product),
|
|
558
|
+
"features": [name.removeprefix("splent_feature_")],
|
|
559
|
+
}
|
|
560
|
+
)
|
|
561
|
+
return findings
|
|
562
|
+
|
|
563
|
+
|
|
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
|
+
"""
|
|
416
718
|
try:
|
|
417
719
|
labeled_paths = _resolve_all_product_features(product_dir, workspace)
|
|
418
720
|
except SystemExit:
|
|
419
721
|
return []
|
|
420
722
|
if not labeled_paths:
|
|
421
723
|
return []
|
|
724
|
+
if env is None:
|
|
725
|
+
env = os.getenv("SPLENT_ENV") or "dev"
|
|
422
726
|
labeled_contracts = [(lbl, _read_contract(path)) for lbl, path in labeled_paths]
|
|
423
|
-
|
|
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"]])
|
|
424
731
|
|
|
425
732
|
|
|
426
733
|
# ─────────────────────────────────────────────────────────────────────────────
|