splent-cli 1.34.0__tar.gz → 1.35.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.0/src/splent_cli.egg-info → splent_cli-1.35.0}/PKG-INFO +1 -1
- {splent_cli-1.34.0 → splent_cli-1.35.0}/pyproject.toml +1 -1
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/cli.py +48 -1
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_compat.py +135 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_create.py +9 -5
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_detach.py +4 -3
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_env.py +4 -1
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_refine.py +7 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_remove.py +5 -4
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_test.py +5 -20
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_build.py +1 -20
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_create.py +2 -5
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_resolve.py +8 -1
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_sync_template.py +23 -1
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_validate.py +9 -1
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/release/release_resume.py +19 -15
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/marketplace.py +2 -6
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/release.py +61 -16
- splent_cli-1.35.0/src/splent_cli/services/web_container.py +66 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +7 -1
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +15 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/cache_utils.py +34 -0
- splent_cli-1.35.0/src/splent_cli/utils/feature_naming.py +58 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/integrity.py +2 -2
- splent_cli-1.35.0/src/splent_cli/utils/running_version.py +28 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/template_drift.py +2 -5
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/upgrade_plan.py +3 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli.egg-info/SOURCES.txt +3 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/COPYING +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/COPYING.LESSER +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/README.md +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/setup.cfg +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_release.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_derive.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/deploy_bundle.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/image_registry.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/preflight.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/pypi_quota.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/release_dry_run.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/services/spl_store.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/licenses/COPYING +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_400.html +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_401.html +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_404.html +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_500.html +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_app.css +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_app.js +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_own.css +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/db_client.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/forge.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/redact.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/refinement_facts.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/utils/toml_edit.py +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.34.0 → splent_cli-1.35.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)
|
|
@@ -411,6 +411,141 @@ def _analyse_all(labeled_contracts: list[tuple[str, dict]]) -> list[dict]:
|
|
|
411
411
|
return sorted(findings, key=lambda f: SEVERITY_ORDER[f["severity"]])
|
|
412
412
|
|
|
413
413
|
|
|
414
|
+
def _contract_env(path: Path) -> str | None:
|
|
415
|
+
"""The environment a feature's contract confines it to, if any."""
|
|
416
|
+
pyproject = path / "pyproject.toml"
|
|
417
|
+
if not pyproject.exists():
|
|
418
|
+
return None
|
|
419
|
+
data = load_toml(pyproject, what=f"pyproject.toml for {path.name}")
|
|
420
|
+
return data.get("tool", {}).get("splent", {}).get("contract", {}).get("env")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def env_scope_findings(workspace: str, product_dir: str) -> list[dict]:
|
|
424
|
+
"""Features confined to one environment, in a list that installs them in
|
|
425
|
+
the other.
|
|
426
|
+
|
|
427
|
+
mailhog and phpmyadmin declare env = "dev" in their contract. In the base
|
|
428
|
+
list the production image installed them too, phpMyAdmin with autologin
|
|
429
|
+
to the database, and nothing said so.
|
|
430
|
+
"""
|
|
431
|
+
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
432
|
+
|
|
433
|
+
reader = PyprojectReader.for_product(product_dir)
|
|
434
|
+
lists = {
|
|
435
|
+
"features": reader.features,
|
|
436
|
+
"features_dev": reader.features_dev,
|
|
437
|
+
"features_prod": reader.features_prod,
|
|
438
|
+
}
|
|
439
|
+
findings = []
|
|
440
|
+
for key, entries in lists.items():
|
|
441
|
+
for entry in entries:
|
|
442
|
+
ns, name, version = _parse_pyproject_entry(entry)
|
|
443
|
+
path = product_feature_dir(workspace, product_dir, ns, name, version)
|
|
444
|
+
if path is None:
|
|
445
|
+
continue
|
|
446
|
+
scope = _contract_env(path)
|
|
447
|
+
short = name.removeprefix("splent_feature_")
|
|
448
|
+
if scope == "dev" and key != "features_dev":
|
|
449
|
+
message = (
|
|
450
|
+
f"{short} is a development tool (its contract says env = "
|
|
451
|
+
f'"dev"), but [tool.splent] {key} puts it in the production '
|
|
452
|
+
"image too. Move it to features_dev."
|
|
453
|
+
)
|
|
454
|
+
elif scope == "prod" and key == "features_dev":
|
|
455
|
+
message = (
|
|
456
|
+
f"{short} only runs in production (its contract says env = "
|
|
457
|
+
'"prod"), but it is in [tool.splent] features_dev. Move it '
|
|
458
|
+
"to features_prod."
|
|
459
|
+
)
|
|
460
|
+
else:
|
|
461
|
+
continue
|
|
462
|
+
findings.append(
|
|
463
|
+
{
|
|
464
|
+
"severity": "error",
|
|
465
|
+
"field": "env",
|
|
466
|
+
"message": message,
|
|
467
|
+
"features": [short],
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
return findings
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _joins_the_shared_network(compose_file: Path) -> bool:
|
|
474
|
+
"""Whether a compose file puts its services on the shared splent_network
|
|
475
|
+
instead of the product's own (``name: ${SPLENT_NETWORK:-splent_network}``)."""
|
|
476
|
+
import yaml
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
data = yaml.safe_load(compose_file.read_text()) or {}
|
|
480
|
+
except (OSError, yaml.YAMLError):
|
|
481
|
+
return False
|
|
482
|
+
networks = data.get("networks") or {}
|
|
483
|
+
spec = networks.get("splent_network")
|
|
484
|
+
if not isinstance(spec, dict) or not spec.get("external"):
|
|
485
|
+
return False
|
|
486
|
+
return "SPLENT_NETWORK" not in str(spec.get("name", ""))
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def network_findings(workspace: str, product_dir: str) -> list[dict]:
|
|
490
|
+
"""Feature services the product cannot reach.
|
|
491
|
+
|
|
492
|
+
Each product has a network of its own, and a feature's services join it
|
|
493
|
+
through SPLENT_NETWORK. mailhog and phpmyadmin named the shared network
|
|
494
|
+
instead, so the app could not send mail to Mailhog and phpMyAdmin could
|
|
495
|
+
not reach the database, and nothing said why.
|
|
496
|
+
"""
|
|
497
|
+
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
498
|
+
|
|
499
|
+
reader = PyprojectReader.for_product(product_dir)
|
|
500
|
+
findings = []
|
|
501
|
+
for entry in reader.features + reader.features_dev + reader.features_prod:
|
|
502
|
+
ns, name, version = _parse_pyproject_entry(entry)
|
|
503
|
+
path = product_feature_dir(workspace, product_dir, ns, name, version)
|
|
504
|
+
if path is None:
|
|
505
|
+
continue
|
|
506
|
+
files = sorted((path / "docker").glob("docker-compose*.yml"))
|
|
507
|
+
if any(_joins_the_shared_network(f) for f in files):
|
|
508
|
+
short = name.removeprefix("splent_feature_")
|
|
509
|
+
findings.append(
|
|
510
|
+
{
|
|
511
|
+
"severity": "error",
|
|
512
|
+
"field": "docker",
|
|
513
|
+
"message": (
|
|
514
|
+
f"{short}'s services join the shared splent_network, "
|
|
515
|
+
"where the product's app cannot reach them. Its compose "
|
|
516
|
+
"file needs name: ${SPLENT_NETWORK:-splent_network} "
|
|
517
|
+
"under networks.splent_network."
|
|
518
|
+
),
|
|
519
|
+
"features": [short],
|
|
520
|
+
}
|
|
521
|
+
)
|
|
522
|
+
return findings
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def naming_findings(workspace: str, product_dir: str) -> list[dict]:
|
|
526
|
+
"""Features named after a product (auth_uvlhub). A warning, so the debt
|
|
527
|
+
shows in every validation until the product drops them."""
|
|
528
|
+
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
529
|
+
|
|
530
|
+
from splent_cli.utils.feature_naming import product_suffix, product_suffix_message
|
|
531
|
+
|
|
532
|
+
reader = PyprojectReader.for_product(product_dir)
|
|
533
|
+
findings = []
|
|
534
|
+
for entry in reader.features + reader.features_dev + reader.features_prod:
|
|
535
|
+
_, name, _ = _parse_pyproject_entry(entry)
|
|
536
|
+
product = product_suffix(name, workspace)
|
|
537
|
+
if product:
|
|
538
|
+
findings.append(
|
|
539
|
+
{
|
|
540
|
+
"severity": "warning",
|
|
541
|
+
"field": "name",
|
|
542
|
+
"message": product_suffix_message(name, product),
|
|
543
|
+
"features": [name.removeprefix("splent_feature_")],
|
|
544
|
+
}
|
|
545
|
+
)
|
|
546
|
+
return findings
|
|
547
|
+
|
|
548
|
+
|
|
414
549
|
def run_all_product_check(workspace: str, product_dir: str) -> list[dict]:
|
|
415
550
|
"""Run the full product compatibility check. Returns findings list."""
|
|
416
551
|
try:
|
|
@@ -9,13 +9,11 @@ from splent_cli.utils.feature_utils import (
|
|
|
9
9
|
require_valid_feature_ref,
|
|
10
10
|
)
|
|
11
11
|
from splent_cli.services import context
|
|
12
|
+
from splent_cli.utils.feature_naming import product_suffix, product_suffix_message
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
from importlib.metadata import version as _pkg_version
|
|
14
|
+
from splent_cli.utils.running_version import cli_version as _running_cli_version
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
except Exception:
|
|
18
|
-
_CLI_VERSION = "dev"
|
|
16
|
+
_CLI_VERSION = _running_cli_version()
|
|
19
17
|
|
|
20
18
|
|
|
21
19
|
FEATURE_TYPES = ("full", "light", "config", "service")
|
|
@@ -226,6 +224,12 @@ def make_feature(full_name, feature_type, docker):
|
|
|
226
224
|
|
|
227
225
|
# --- Target directory (workspace root for editable features) ---
|
|
228
226
|
workspace = str(context.workspace())
|
|
227
|
+
|
|
228
|
+
product = product_suffix(feature_name, workspace)
|
|
229
|
+
if product:
|
|
230
|
+
click.secho(f"❌ {product_suffix_message(feature_name, product)}", fg="red")
|
|
231
|
+
raise SystemExit(1)
|
|
232
|
+
|
|
229
233
|
feature_dir = os.path.join(workspace, feature_name)
|
|
230
234
|
src_path = os.path.join(feature_dir, "src", org_safe, feature_name)
|
|
231
235
|
|
|
@@ -25,7 +25,7 @@ from splent_cli.utils.manifest import (
|
|
|
25
25
|
|
|
26
26
|
@click.command(
|
|
27
27
|
"feature:detach",
|
|
28
|
-
short_help="
|
|
28
|
+
short_help="Unpin a released version: the entry goes back to editable (cache is kept).",
|
|
29
29
|
)
|
|
30
30
|
@click.argument("feature_identifier", required=True)
|
|
31
31
|
@click.argument("version", required=True)
|
|
@@ -36,9 +36,10 @@ from splent_cli.utils.manifest import (
|
|
|
36
36
|
)
|
|
37
37
|
def feature_detach(feature_identifier, version, force):
|
|
38
38
|
"""
|
|
39
|
-
|
|
39
|
+
Unpin a released version, the reverse of feature:attach.
|
|
40
40
|
|
|
41
|
-
-
|
|
41
|
+
- Drops @version from the entry in pyproject.toml, which stays as the
|
|
42
|
+
bare, editable namespace/feature (feature:remove takes it out)
|
|
42
43
|
- Removes symlink in features/<namespace>/
|
|
43
44
|
- Leaves .splent_cache intact
|
|
44
45
|
"""
|
|
@@ -119,7 +119,10 @@ def feature_env(feature_name, generate, env_name):
|
|
|
119
119
|
|
|
120
120
|
if selected:
|
|
121
121
|
try:
|
|
122
|
-
|
|
122
|
+
# copyfile, not copy: a pinned feature's cache is read-only, and
|
|
123
|
+
# copying the mode made its .env read-only too, so product:env
|
|
124
|
+
# --merge failed writing __FEATURE_HOST_DIR__ into it.
|
|
125
|
+
shutil.copyfile(selected, env_file)
|
|
123
126
|
except OSError as exc:
|
|
124
127
|
click.echo(f"❌ Failed to create {env_file} from {selected}: {exc}")
|
|
125
128
|
raise SystemExit(1)
|
|
@@ -14,6 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
import click
|
|
15
15
|
|
|
16
16
|
from splent_cli.services import context
|
|
17
|
+
from splent_cli.utils.feature_naming import product_suffix, product_suffix_message
|
|
17
18
|
from splent_cli.utils.feature_utils import normalize_namespace, read_features_from_data
|
|
18
19
|
from splent_cli.utils.refinement_facts import (
|
|
19
20
|
FeatureSource,
|
|
@@ -1009,6 +1010,12 @@ def feature_refinement(refiner_name):
|
|
|
1009
1010
|
# A feature other features refine cannot refine anything (a chain)
|
|
1010
1011
|
_refuse_if_refined(_get_product_features(workspace, product), refiner_name)
|
|
1011
1012
|
|
|
1013
|
+
# A refiner named after a product is a product patch, not variability.
|
|
1014
|
+
named_after = product_suffix(refiner_name, workspace)
|
|
1015
|
+
if named_after:
|
|
1016
|
+
click.secho(f" {product_suffix_message(refiner_name, named_after)}", fg="red")
|
|
1017
|
+
raise SystemExit(1)
|
|
1018
|
+
|
|
1012
1019
|
# Check if refinement already configured
|
|
1013
1020
|
refiner_data = load_toml(refiner_pyproject, what=f"{refiner_name} pyproject.toml")
|
|
1014
1021
|
existing_refinement = (
|
|
@@ -24,7 +24,7 @@ from splent_cli.utils.manifest import (
|
|
|
24
24
|
|
|
25
25
|
@click.command(
|
|
26
26
|
"feature:remove",
|
|
27
|
-
short_help="
|
|
27
|
+
short_help="Remove a feature from the active product, pinned or editable (keeps files).",
|
|
28
28
|
)
|
|
29
29
|
@click.argument("feature_name", required=True)
|
|
30
30
|
@click.option("--namespace", "-n", help="Namespace (defaults to 'splent-io').")
|
|
@@ -47,9 +47,10 @@ from splent_cli.utils.manifest import (
|
|
|
47
47
|
)
|
|
48
48
|
def feature_remove(feature_name, namespace, env_scope, force):
|
|
49
49
|
"""
|
|
50
|
-
Removes a
|
|
51
|
-
- Removes entry from
|
|
52
|
-
- Removes symlink under /workspace/<product>/features/<namespace
|
|
50
|
+
Removes a feature from the current SPLENT product, pinned or editable:
|
|
51
|
+
- Removes its entry from the feature lists in pyproject.toml
|
|
52
|
+
- Removes its symlink under /workspace/<product>/features/<namespace>/
|
|
53
|
+
The checkout and the cached release stay on disk.
|
|
53
54
|
|
|
54
55
|
\b
|
|
55
56
|
Without flags every list is searched (features, features_dev, features_prod).
|
|
@@ -13,7 +13,7 @@ from splent_cli.utils.feature_utils import (
|
|
|
13
13
|
get_normalize_feature_name_in_splent_format,
|
|
14
14
|
parse_feature_entry,
|
|
15
15
|
)
|
|
16
|
-
from splent_cli.services import context
|
|
16
|
+
from splent_cli.services import context, web_container
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
# Test levels in execution order (default = first three)
|
|
@@ -21,28 +21,13 @@ TEST_LEVELS = ("unit", "integration", "functional", "e2e", "load")
|
|
|
21
21
|
DEFAULT_LEVELS = ("unit", "integration", "functional")
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
SPLENT_CONTAINER is the explicit answer when set (the CLI container says
|
|
28
|
-
"cli", product images say "product"); /.dockerenv covers containers older
|
|
29
|
-
than the variable.
|
|
30
|
-
"""
|
|
31
|
-
container = os.getenv("SPLENT_CONTAINER")
|
|
32
|
-
if container:
|
|
33
|
-
return container != "cli"
|
|
34
|
-
return os.path.exists("/.dockerenv")
|
|
24
|
+
# Shared with every command that needs the product's app (see cli.py).
|
|
25
|
+
_is_product_container = web_container.is_product_container
|
|
26
|
+
_container_running = web_container.running
|
|
35
27
|
|
|
36
28
|
|
|
37
29
|
def _web_container() -> str:
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _container_running(name: str) -> bool:
|
|
42
|
-
result = run(
|
|
43
|
-
["docker", "ps", "-q", "-f", f"name=^{name}$"], check=False, capture=True
|
|
44
|
-
)
|
|
45
|
-
return result.returncode == 0 and bool(result.stdout.strip())
|
|
30
|
+
return web_container.name(context.require_app())
|
|
46
31
|
|
|
47
32
|
|
|
48
33
|
_PASSTHROUGH_ENV = ("PYTEST_ADDOPTS", "PYTHONDONTWRITEBYTECODE")
|
|
@@ -9,6 +9,7 @@ import click
|
|
|
9
9
|
from splent_cli.services import context, compose, deploy_bundle
|
|
10
10
|
from splent_cli.services.preflight import run_preflight
|
|
11
11
|
from splent_cli.utils.feature_utils import read_features_from_data
|
|
12
|
+
from splent_cli.utils.running_version import cli_version
|
|
12
13
|
from splent_cli.utils.io_utils import (
|
|
13
14
|
atomic_write,
|
|
14
15
|
env_line,
|
|
@@ -406,26 +407,6 @@ def framework_version(workspace: str) -> str:
|
|
|
406
407
|
)
|
|
407
408
|
|
|
408
409
|
|
|
409
|
-
def cli_version() -> str:
|
|
410
|
-
"""The version of the CLI that is running, the one the image installs too.
|
|
411
|
-
|
|
412
|
-
Read from the source tree it runs from when that is a checkout: an
|
|
413
|
-
editable install records its version once, when it is installed, and the
|
|
414
|
-
checkout moves on with every release.
|
|
415
|
-
"""
|
|
416
|
-
from importlib.metadata import version
|
|
417
|
-
from pathlib import Path
|
|
418
|
-
|
|
419
|
-
import splent_cli
|
|
420
|
-
|
|
421
|
-
pyproject = Path(splent_cli.__file__).resolve().parents[2] / "pyproject.toml"
|
|
422
|
-
if pyproject.is_file():
|
|
423
|
-
project = load_toml(str(pyproject)).get("project", {})
|
|
424
|
-
if project.get("name") == "splent_cli" and project.get("version"):
|
|
425
|
-
return str(project["version"])
|
|
426
|
-
return version("splent_cli")
|
|
427
|
-
|
|
428
|
-
|
|
429
410
|
def runtime_pins(workspace: str) -> dict:
|
|
430
411
|
"""{package: version} the production image installs from PyPI."""
|
|
431
412
|
return {
|
|
@@ -20,12 +20,9 @@ from splent_cli.utils.template_drift import (
|
|
|
20
20
|
)
|
|
21
21
|
from splent_cli.utils.template_drift import _forge_host as _template_forge_host
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
from importlib.metadata import version as _pkg_version
|
|
23
|
+
from splent_cli.utils.running_version import cli_version as _running_cli_version
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
except Exception:
|
|
28
|
-
_CLI_VERSION = "dev"
|
|
25
|
+
_CLI_VERSION = _running_cli_version()
|
|
29
26
|
|
|
30
27
|
|
|
31
28
|
PRODUCT_NAME_RE = re.compile(r"^[a-z_][a-z0-9_]*$")
|
|
@@ -9,7 +9,7 @@ from splent_cli.utils.feature_utils import (
|
|
|
9
9
|
parse_feature_entry,
|
|
10
10
|
read_features_from_data,
|
|
11
11
|
)
|
|
12
|
-
from splent_cli.utils.cache_utils import make_feature_writable
|
|
12
|
+
from splent_cli.utils.cache_utils import make_feature_writable, restore_snapshot
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def _splent_list(data: dict, key: str) -> list[str]:
|
|
@@ -194,11 +194,18 @@ def product_sync(ctx, force, yes, mode=None, strict=False):
|
|
|
194
194
|
failed += 1
|
|
195
195
|
continue
|
|
196
196
|
|
|
197
|
+
restored = [] if cloned else restore_snapshot(cache_dir)
|
|
197
198
|
_create_symlink(cache_dir, product_features_dir, link_path)
|
|
198
199
|
marker = click.style(" ✓", fg="green")
|
|
199
200
|
label = f" {short:<{max_w_r}}"
|
|
200
201
|
ver = click.style(f" {version}", dim=True)
|
|
201
202
|
src = click.style(" cached" if not cloned else " cloned", dim=True)
|
|
203
|
+
if restored:
|
|
204
|
+
src += click.style(
|
|
205
|
+
f" restored {len(restored)} file(s) to the release: "
|
|
206
|
+
+ ", ".join(restored),
|
|
207
|
+
fg="yellow",
|
|
208
|
+
)
|
|
202
209
|
tag = _feature_env_tag(entry, dev_set, prod_set)
|
|
203
210
|
click.echo(f" {marker}{label}{ver}{src}{tag}")
|
|
204
211
|
|
{splent_cli-1.34.0 → splent_cli-1.35.0}/src/splent_cli/commands/product/product_sync_template.py
RENAMED
|
@@ -2,8 +2,9 @@ import click
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
from splent_cli.services import context
|
|
5
|
-
from splent_cli.utils.io_utils import atomic_write
|
|
5
|
+
from splent_cli.utils.io_utils import atomic_write, load_toml
|
|
6
6
|
from splent_cli.utils.template_drift import (
|
|
7
|
+
CLI_VERSION,
|
|
7
8
|
PRODUCT_GROUPS,
|
|
8
9
|
count_changed_lines,
|
|
9
10
|
file_diff,
|
|
@@ -12,6 +13,23 @@ from splent_cli.utils.template_drift import (
|
|
|
12
13
|
resolve_product_dir,
|
|
13
14
|
resolve_product_rel,
|
|
14
15
|
)
|
|
16
|
+
from splent_cli.utils.toml_edit import write_toml
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _record_cli_version(product_path: Path) -> None:
|
|
20
|
+
"""Store the CLI whose templates the product now matches.
|
|
21
|
+
|
|
22
|
+
product:drift reports it as "generated with CLI vX". Only product:create
|
|
23
|
+
wrote it, so a product brought fully up to date kept naming the CLI it was
|
|
24
|
+
created with. A partial sync (--scripts, --docker...) leaves it alone,
|
|
25
|
+
because the other groups may still be older.
|
|
26
|
+
"""
|
|
27
|
+
pyproject = product_path / "pyproject.toml"
|
|
28
|
+
data = load_toml(pyproject, what="pyproject.toml")
|
|
29
|
+
splent = data.setdefault("tool", {}).setdefault("splent", {})
|
|
30
|
+
if splent.get("cli_version") != CLI_VERSION:
|
|
31
|
+
splent["cli_version"] = CLI_VERSION
|
|
32
|
+
write_toml(pyproject, data)
|
|
15
33
|
|
|
16
34
|
|
|
17
35
|
@click.command(
|
|
@@ -112,6 +130,8 @@ def product_sync_template(
|
|
|
112
130
|
changes[rel_path] = (abs_path, expected, n)
|
|
113
131
|
|
|
114
132
|
if not changes:
|
|
133
|
+
if not filter_active and not dry_run:
|
|
134
|
+
_record_cli_version(product_path)
|
|
115
135
|
click.echo()
|
|
116
136
|
click.secho(" ✅ All SPLENT-owned files are already up to date.", fg="green")
|
|
117
137
|
click.echo()
|
|
@@ -138,6 +158,8 @@ def product_sync_template(
|
|
|
138
158
|
# Atomic: an interrupted run never leaves a half-written script.
|
|
139
159
|
atomic_write(abs_path, expected)
|
|
140
160
|
click.secho(f" ✔ {rel_path}", fg="green")
|
|
161
|
+
if not filter_active:
|
|
162
|
+
_record_cli_version(product_path)
|
|
141
163
|
|
|
142
164
|
click.echo()
|
|
143
165
|
click.secho(" Done.", fg="cyan")
|
|
@@ -110,9 +110,17 @@ def _run_sat_check(workspace, app_name, data, feature_list, print_config):
|
|
|
110
110
|
|
|
111
111
|
def _run_compat_check(workspace, product_dir):
|
|
112
112
|
"""Run contract compatibility check. Returns (findings, errors, warnings)."""
|
|
113
|
-
from splent_cli.commands.feature.feature_compat import
|
|
113
|
+
from splent_cli.commands.feature.feature_compat import (
|
|
114
|
+
env_scope_findings,
|
|
115
|
+
naming_findings,
|
|
116
|
+
network_findings,
|
|
117
|
+
run_all_product_check,
|
|
118
|
+
)
|
|
114
119
|
|
|
115
120
|
findings = run_all_product_check(workspace, product_dir)
|
|
121
|
+
findings += env_scope_findings(workspace, product_dir)
|
|
122
|
+
findings += network_findings(workspace, product_dir)
|
|
123
|
+
findings += naming_findings(workspace, product_dir)
|
|
116
124
|
errors = [f for f in findings if f["severity"] == "error"]
|
|
117
125
|
warnings = [f for f in findings if f["severity"] == "warning"]
|
|
118
126
|
return findings, errors, warnings
|
|
@@ -21,7 +21,7 @@ something is already wrong:
|
|
|
21
21
|
|
|
22
22
|
and two more keep what it publishes equal to what the tag says:
|
|
23
23
|
|
|
24
|
-
* origin
|
|
24
|
+
* the release branch on origin only ever receives the commit the tag describes, as a
|
|
25
25
|
fast-forward. A divergence leaves the forge half stuck, never a tag on a
|
|
26
26
|
commit no remote branch contains
|
|
27
27
|
* nothing is built from uncommitted changes, and the Docker image, whose
|
|
@@ -153,25 +153,26 @@ def _is_ancestor(path: str, ancestor: str, descendant: str) -> bool:
|
|
|
153
153
|
|
|
154
154
|
|
|
155
155
|
def plan_main_push(path: str, tag: str) -> tuple[str, str | None]:
|
|
156
|
-
"""How origin
|
|
156
|
+
"""How the release branch on origin comes to carry the commit *tag* describes.
|
|
157
157
|
|
|
158
158
|
Returns ``(state, sha)``, where *sha* is the commit that is or will be
|
|
159
159
|
tagged: the tag's commit when the tag exists locally, otherwise HEAD,
|
|
160
160
|
because that is where the tag will be created. Never HEAD when a tag
|
|
161
161
|
exists, since the operator may have committed more work since.
|
|
162
162
|
|
|
163
|
-
``contained`` origin
|
|
164
|
-
``push`` pushing it is a fast-forward of
|
|
163
|
+
``contained`` origin already has it on the branch, nothing to push
|
|
164
|
+
``push`` pushing it is a fast-forward of that branch
|
|
165
165
|
``diverged`` it is neither, so no tag can be published on it
|
|
166
|
-
``unknown``
|
|
166
|
+
``unknown`` origin has no such branch to compare with, nothing is pushed
|
|
167
167
|
``timeout`` origin did not answer in time, so nothing can be compared
|
|
168
168
|
"""
|
|
169
|
+
branch = release.release_branch(path)
|
|
169
170
|
ref = (
|
|
170
171
|
f"refs/tags/{tag}^{{commit}}" if release.local_tag_exists(path, tag) else "HEAD"
|
|
171
172
|
)
|
|
172
173
|
try:
|
|
173
174
|
subprocess.run(
|
|
174
|
-
["git", "fetch", "origin",
|
|
175
|
+
["git", "fetch", "origin", branch],
|
|
175
176
|
cwd=path,
|
|
176
177
|
capture_output=True,
|
|
177
178
|
env=_non_interactive_env(),
|
|
@@ -180,7 +181,7 @@ def plan_main_push(path: str, tag: str) -> tuple[str, str | None]:
|
|
|
180
181
|
except subprocess.TimeoutExpired:
|
|
181
182
|
return "timeout", _git_out(path, ["rev-parse", "--verify", ref])
|
|
182
183
|
sha = _git_out(path, ["rev-parse", "--verify", ref])
|
|
183
|
-
remote = _git_out(path, ["rev-parse", "--verify", "refs/remotes/origin/
|
|
184
|
+
remote = _git_out(path, ["rev-parse", "--verify", f"refs/remotes/origin/{branch}"])
|
|
184
185
|
if not sha or not remote:
|
|
185
186
|
return "unknown", sha
|
|
186
187
|
if sha == remote or _is_ancestor(path, sha, remote):
|
|
@@ -191,10 +192,11 @@ def plan_main_push(path: str, tag: str) -> tuple[str, str | None]:
|
|
|
191
192
|
|
|
192
193
|
|
|
193
194
|
def push_commit_to_main(path: str, sha: str) -> str | None:
|
|
194
|
-
"""Fast-forward origin
|
|
195
|
+
"""Fast-forward the release branch on origin to *sha*. None on success, else the reason."""
|
|
196
|
+
branch = release.release_branch(path)
|
|
195
197
|
try:
|
|
196
198
|
r = subprocess.run(
|
|
197
|
-
["git", "push", "origin", f"{sha}:refs/heads/
|
|
199
|
+
["git", "push", "origin", f"{sha}:refs/heads/{branch}"],
|
|
198
200
|
cwd=path,
|
|
199
201
|
capture_output=True,
|
|
200
202
|
text=True,
|
|
@@ -207,7 +209,7 @@ def push_commit_to_main(path: str, sha: str) -> str | None:
|
|
|
207
209
|
)
|
|
208
210
|
if r.returncode != 0:
|
|
209
211
|
return (r.stderr or "").strip() or f"git push exited {r.returncode}"
|
|
210
|
-
click.echo(f" push origin/
|
|
212
|
+
click.echo(f" push origin/{branch} updated to {sha[:12]}")
|
|
211
213
|
return None
|
|
212
214
|
|
|
213
215
|
|
|
@@ -419,7 +421,7 @@ def release_resume(target, assume_yes, no_pypi, no_forge, no_github, no_docker,
|
|
|
419
421
|
push_tag = make_release = False
|
|
420
422
|
main_sha = None
|
|
421
423
|
summary = (
|
|
422
|
-
f"origin/
|
|
424
|
+
f"origin/{release.release_branch(path)} and the commit {tag} describes have diverged, so "
|
|
423
425
|
"the tag was not pushed"
|
|
424
426
|
if main_state == "diverged"
|
|
425
427
|
else "origin did not answer 'git fetch' within "
|
|
@@ -522,7 +524,9 @@ def release_resume(target, assume_yes, no_pypi, no_forge, no_github, no_docker,
|
|
|
522
524
|
|
|
523
525
|
steps = []
|
|
524
526
|
if main_sha:
|
|
525
|
-
steps.append(
|
|
527
|
+
steps.append(
|
|
528
|
+
f"push {main_sha[:12]} to origin/{release.release_branch(path)}, the commit {tag} describes"
|
|
529
|
+
)
|
|
526
530
|
if upload:
|
|
527
531
|
steps.append(f"build and upload {package} {normalized} to PyPI")
|
|
528
532
|
if push_tag:
|
|
@@ -590,9 +594,9 @@ def release_resume(target, assume_yes, no_pypi, no_forge, no_github, no_docker,
|
|
|
590
594
|
stuck.append(
|
|
591
595
|
release_gate.ChannelStatus(
|
|
592
596
|
release_gate.FORGE,
|
|
593
|
-
summary=f"could not push {main_sha[:12]} to origin/
|
|
597
|
+
summary=f"could not push {main_sha[:12]} to origin/{release.release_branch(path)}, {failure}",
|
|
594
598
|
hints=[
|
|
595
|
-
"Fix the push to origin/
|
|
599
|
+
f"Fix the push to origin/{release.release_branch(path)}, then run "
|
|
596
600
|
f"'splent release:resume {hint_target}' again.",
|
|
597
601
|
],
|
|
598
602
|
)
|
|
@@ -604,7 +608,7 @@ def release_resume(target, assume_yes, no_pypi, no_forge, no_github, no_docker,
|
|
|
604
608
|
push_image=push_image,
|
|
605
609
|
summary=f"not built, there is no {tag} to build it from",
|
|
606
610
|
hint=f"Nothing is built before {tag} exists. Fix the push "
|
|
607
|
-
f"to origin/
|
|
611
|
+
f"to origin/{release.release_branch(path)}, then run 'splent release:resume "
|
|
608
612
|
f"{hint_target}' again.",
|
|
609
613
|
)
|
|
610
614
|
upload = push_image = False
|