splent-cli 1.18.0__tar.gz → 1.19.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.18.0/src/splent_cli.egg-info → splent_cli-1.19.0}/PKG-INFO +1 -1
- {splent_cli-1.18.0 → splent_cli-1.19.0}/pyproject.toml +1 -1
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_contract.py +84 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_release.py +53 -10
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_upgrade.py +29 -2
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_build.py +28 -12
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_env.py +109 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/compose.py +25 -10
- {splent_cli-1.18.0 → splent_cli-1.19.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.18.0 → splent_cli-1.19.0}/LICENSE +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/README.md +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/setup.cfg +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_create.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_derive.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_validate.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/release/release_resume.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_migrate_catalog.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/marketplace.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/preflight.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/spl_store.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli.egg-info/SOURCES.txt +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.19.0
|
|
4
4
|
Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
|
|
5
5
|
Author-email: DiversoLab <diversolab@us.es>
|
|
6
6
|
Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
|
|
@@ -13,6 +13,9 @@ Works with both editable features (no version) and versioned features (@v1.x.x).
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import os
|
|
16
|
+
import re
|
|
17
|
+
import tomllib
|
|
18
|
+
|
|
16
19
|
import click
|
|
17
20
|
from pathlib import Path
|
|
18
21
|
|
|
@@ -337,6 +340,10 @@ def feature_contract(feature_ref, write):
|
|
|
337
340
|
|
|
338
341
|
if not has_changes:
|
|
339
342
|
click.secho(" ✅ Contract is already up to date.", fg="green")
|
|
343
|
+
# Reported here too: a settings gap has nothing to do with whether
|
|
344
|
+
# the contract changed, and an up to date contract is exactly when
|
|
345
|
+
# nobody would think to look.
|
|
346
|
+
_check_declared_config(cache_path, inferred)
|
|
340
347
|
click.echo()
|
|
341
348
|
return
|
|
342
349
|
|
|
@@ -347,6 +354,7 @@ def feature_contract(feature_ref, write):
|
|
|
347
354
|
+ click.style("--write", bold=True)
|
|
348
355
|
+ " to update pyproject.toml."
|
|
349
356
|
)
|
|
357
|
+
_check_declared_config(cache_path, inferred)
|
|
350
358
|
click.echo()
|
|
351
359
|
return
|
|
352
360
|
|
|
@@ -357,7 +365,83 @@ def feature_contract(feature_ref, write):
|
|
|
357
365
|
# Check if config.py needs updating
|
|
358
366
|
_check_config_py(cache_path, ns, name, inferred)
|
|
359
367
|
|
|
368
|
+
# And whether the settings it reads are declared where a product looks
|
|
369
|
+
_check_declared_config(cache_path, inferred)
|
|
370
|
+
|
|
371
|
+
click.echo()
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# Variables whose value is a credential. A default for one of these does not
|
|
375
|
+
# belong in a committed file, so they are expected to be absent from the
|
|
376
|
+
# declaration and are not reported as missing.
|
|
377
|
+
_SECRET = re.compile(r"(SECRET|PASSWORD|API_KEY|TOKEN|_KEY$)")
|
|
378
|
+
|
|
379
|
+
# Set for every product by the framework and the compose files. A feature
|
|
380
|
+
# that reads one is asking where it is running, not offering a setting, so
|
|
381
|
+
# declaring a default for it would be claiming something it does not own.
|
|
382
|
+
_FRAMEWORK_VARS = {
|
|
383
|
+
"FLASK_APP",
|
|
384
|
+
"PYTHONPATH",
|
|
385
|
+
"SPLENT_APP",
|
|
386
|
+
"SPLENT_ENV",
|
|
387
|
+
"SPLENT_HOST_PROJECT_DIR",
|
|
388
|
+
"SPLENT_NETWORK",
|
|
389
|
+
"WORKING_DIR",
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _check_declared_config(feature_path, inferred):
|
|
394
|
+
"""Report settings the feature reads but declares nowhere.
|
|
395
|
+
|
|
396
|
+
A variable that appears only as an ``os.getenv`` fallback is invisible:
|
|
397
|
+
a product cannot discover the knob exists, and nothing tells a reader of
|
|
398
|
+
the pyproject what happens if it is left alone. ``[tool.splent.config]``
|
|
399
|
+
is where that is written down, whether or not the feature ships a
|
|
400
|
+
container.
|
|
401
|
+
"""
|
|
402
|
+
env_vars = [
|
|
403
|
+
v
|
|
404
|
+
for v in inferred.get("env_vars", [])
|
|
405
|
+
if not _SECRET.search(v) and v not in _FRAMEWORK_VARS
|
|
406
|
+
]
|
|
407
|
+
if not env_vars:
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
pyproject = feature_path / "pyproject.toml"
|
|
411
|
+
try:
|
|
412
|
+
with open(pyproject, "rb") as handle:
|
|
413
|
+
data = tomllib.load(handle)
|
|
414
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
declared = set(data.get("tool", {}).get("splent", {}).get("config", {}))
|
|
418
|
+
# A feature that ships a service may state a default next to its compose
|
|
419
|
+
# file instead, which is equally visible to product:env --merge.
|
|
420
|
+
env_example = feature_path / "docker" / ".env.example"
|
|
421
|
+
if env_example.exists():
|
|
422
|
+
declared |= {
|
|
423
|
+
line.split("=", 1)[0].strip()
|
|
424
|
+
for line in env_example.read_text(
|
|
425
|
+
encoding="utf-8", errors="replace"
|
|
426
|
+
).splitlines()
|
|
427
|
+
if "=" in line and not line.strip().startswith("#")
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
missing = sorted(set(env_vars) - declared)
|
|
431
|
+
if not missing:
|
|
432
|
+
return
|
|
433
|
+
|
|
360
434
|
click.echo()
|
|
435
|
+
click.secho(
|
|
436
|
+
f" ⚠ {len(missing)} setting(s) read but not declared in "
|
|
437
|
+
f"[tool.splent.config]: {', '.join(missing)}",
|
|
438
|
+
fg="yellow",
|
|
439
|
+
)
|
|
440
|
+
click.secho(
|
|
441
|
+
" Add one line per variable, the value being its default, so a\n"
|
|
442
|
+
" product can see the knob and override only what it decides.",
|
|
443
|
+
dim=True,
|
|
444
|
+
)
|
|
361
445
|
|
|
362
446
|
|
|
363
447
|
def _check_config_py(feature_path, ns, name, inferred):
|
|
@@ -551,6 +551,49 @@ def infer_contract(
|
|
|
551
551
|
}
|
|
552
552
|
|
|
553
553
|
|
|
554
|
+
def _sections_not_owned_by_the_contract(tail: str) -> str:
|
|
555
|
+
"""The TOML sections below the contract that the contract did not write.
|
|
556
|
+
|
|
557
|
+
``[tool.splent.contract]`` and its subtables are regenerated from source
|
|
558
|
+
and go; anything else a maintainer put there stays, with the comments
|
|
559
|
+
written above it, since a comment above a table is about that table.
|
|
560
|
+
"""
|
|
561
|
+
chunks: list[list[str]] = []
|
|
562
|
+
lead: list[str] = []
|
|
563
|
+
current: list[str] | None = None
|
|
564
|
+
|
|
565
|
+
for line in tail.splitlines():
|
|
566
|
+
if line.startswith("["):
|
|
567
|
+
if current is not None:
|
|
568
|
+
chunks.append(current)
|
|
569
|
+
current = [*lead, line]
|
|
570
|
+
lead = []
|
|
571
|
+
elif current is None:
|
|
572
|
+
# Before the first table: comments introducing whatever follows.
|
|
573
|
+
lead.append(line)
|
|
574
|
+
elif line.strip().startswith("#") or not line.strip():
|
|
575
|
+
# Might introduce the next table rather than close this one, so
|
|
576
|
+
# it is held until the next line says which.
|
|
577
|
+
lead.append(line)
|
|
578
|
+
else:
|
|
579
|
+
current.extend(lead)
|
|
580
|
+
lead = []
|
|
581
|
+
current.append(line)
|
|
582
|
+
|
|
583
|
+
if current is not None:
|
|
584
|
+
chunks.append(current)
|
|
585
|
+
|
|
586
|
+
kept = []
|
|
587
|
+
for chunk in chunks:
|
|
588
|
+
header = next((line for line in chunk if line.startswith("[")), "")
|
|
589
|
+
if header.startswith("[tool.splent.contract"):
|
|
590
|
+
continue
|
|
591
|
+
body = "\n".join(chunk).strip()
|
|
592
|
+
if body:
|
|
593
|
+
kept.append(body)
|
|
594
|
+
return "\n\n".join(kept)
|
|
595
|
+
|
|
596
|
+
|
|
554
597
|
def write_contract(pyproject_path: str, contract: dict, feature_name: str) -> None:
|
|
555
598
|
path = Path(pyproject_path)
|
|
556
599
|
text = path.read_text()
|
|
@@ -596,20 +639,20 @@ def write_contract(pyproject_path: str, contract: dict, feature_name: str) -> No
|
|
|
596
639
|
"requires_features_manual": sorted(set(manual_requires)),
|
|
597
640
|
}
|
|
598
641
|
|
|
599
|
-
# Preserve any refinement section that comes after the contract
|
|
600
|
-
refinement_block = ""
|
|
601
|
-
ref_match = re.search(
|
|
602
|
-
r"((?:^# -- Refinement.*?\n)?^\[tool\.splent\.refinement\].*)",
|
|
603
|
-
text,
|
|
604
|
-
re.MULTILINE | re.DOTALL,
|
|
605
|
-
)
|
|
606
|
-
if ref_match:
|
|
607
|
-
refinement_block = "\n" + ref_match.group(1).rstrip() + "\n"
|
|
608
|
-
|
|
609
642
|
match = re.search(r"^# ── Feature Contract\b.*$", text, re.MULTILINE)
|
|
610
643
|
if not match:
|
|
611
644
|
match = re.search(r"^\[tool\.splent\.contract\b", text, re.MULTILINE)
|
|
645
|
+
|
|
646
|
+
# Whatever a maintainer wrote below the contract is theirs, and the
|
|
647
|
+
# contract is refreshed by truncating from its header and writing a new
|
|
648
|
+
# block: everything after it went with it. That is how
|
|
649
|
+
# [tool.splent.config] vanished from five features the moment they were
|
|
650
|
+
# released, silently taking every declared default with them.
|
|
651
|
+
refinement_block = ""
|
|
612
652
|
if match:
|
|
653
|
+
preserved = _sections_not_owned_by_the_contract(text[match.start() :])
|
|
654
|
+
if preserved:
|
|
655
|
+
refinement_block = "\n" + preserved + "\n"
|
|
613
656
|
text = text[: match.start()]
|
|
614
657
|
# Normalize trailing whitespace on both paths so a first write and a
|
|
615
658
|
# rewrite produce byte-identical output.
|
|
@@ -50,8 +50,13 @@ def _read_features(pyproject_path: Path) -> list[dict]:
|
|
|
50
50
|
for ref in refs:
|
|
51
51
|
if "/" in ref:
|
|
52
52
|
ns_part, rest = ref.split("/", 1)
|
|
53
|
-
ns_github = ns_part
|
|
54
53
|
ns_fs = normalize_namespace(ns_part)
|
|
54
|
+
# A pyproject writes the namespace the way a Python package
|
|
55
|
+
# needs it, splent_io, and GitHub serves it as splent-io. Taking
|
|
56
|
+
# it verbatim asked the API about an organisation that does not
|
|
57
|
+
# exist, and every feature was then reported as already up to
|
|
58
|
+
# date.
|
|
59
|
+
ns_github = ns_fs.replace("_", "-")
|
|
55
60
|
else:
|
|
56
61
|
ns_github = "splent-io"
|
|
57
62
|
ns_fs = "splent_io"
|
|
@@ -178,9 +183,13 @@ def feature_upgrade(feature_ref, yes):
|
|
|
178
183
|
click.secho(" Checking latest versions on GitHub...", fg="bright_black")
|
|
179
184
|
|
|
180
185
|
upgrades = []
|
|
186
|
+
unchecked = []
|
|
181
187
|
for feat in features:
|
|
182
188
|
latest = _latest_remote_version(feat["ns_github"], feat["name"], token)
|
|
183
189
|
if not latest:
|
|
190
|
+
# Not the same as being up to date, and saying so would be a
|
|
191
|
+
# lie about the one thing this command is for.
|
|
192
|
+
unchecked.append(feat["name"])
|
|
184
193
|
continue
|
|
185
194
|
|
|
186
195
|
current = feat["version"]
|
|
@@ -197,9 +206,27 @@ def feature_upgrade(feature_ref, yes):
|
|
|
197
206
|
if is_newer:
|
|
198
207
|
upgrades.append({**feat, "latest": latest})
|
|
199
208
|
|
|
209
|
+
if unchecked:
|
|
210
|
+
click.echo()
|
|
211
|
+
click.secho(
|
|
212
|
+
f" ⚠ Could not check {len(unchecked)}: {', '.join(unchecked)}",
|
|
213
|
+
fg="yellow",
|
|
214
|
+
)
|
|
215
|
+
click.secho(
|
|
216
|
+
" Whatever is declared for these was left alone. They may or\n"
|
|
217
|
+
" may not have a newer version.",
|
|
218
|
+
dim=True,
|
|
219
|
+
)
|
|
220
|
+
|
|
200
221
|
if not upgrades:
|
|
201
222
|
click.echo()
|
|
202
|
-
|
|
223
|
+
if unchecked and len(unchecked) == len(features):
|
|
224
|
+
click.secho(" Nothing could be checked.", fg="yellow")
|
|
225
|
+
else:
|
|
226
|
+
click.secho(
|
|
227
|
+
" ✅ Every feature that could be checked is at its latest version.",
|
|
228
|
+
fg="green",
|
|
229
|
+
)
|
|
203
230
|
click.echo()
|
|
204
231
|
return
|
|
205
232
|
|
|
@@ -7,7 +7,11 @@ from splent_cli.services import context, compose
|
|
|
7
7
|
from splent_cli.services.preflight import run_preflight
|
|
8
8
|
from splent_cli.utils.feature_utils import read_features_from_data
|
|
9
9
|
from splent_cli.utils.io_utils import load_toml
|
|
10
|
-
from splent_cli.commands.product.product_env import
|
|
10
|
+
from splent_cli.commands.product.product_env import (
|
|
11
|
+
_declared_config,
|
|
12
|
+
_feature_pyproject,
|
|
13
|
+
_is_port_var,
|
|
14
|
+
)
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
# ── User-tunable env vars ─────────────────────────────────────────────────────
|
|
@@ -415,19 +419,26 @@ def product_build(no_image, skip_preflight):
|
|
|
415
419
|
seen_features.add(clean)
|
|
416
420
|
|
|
417
421
|
f_docker = compose.feature_docker_dir(workspace, clean)
|
|
418
|
-
if
|
|
419
|
-
|
|
422
|
+
if os.path.isdir(f_docker):
|
|
423
|
+
feature_env_file = (
|
|
424
|
+
os.path.join(f_docker, ".env.prod.example")
|
|
425
|
+
if os.path.isfile(os.path.join(f_docker, ".env.prod.example"))
|
|
426
|
+
else os.path.join(f_docker, ".env.example")
|
|
427
|
+
)
|
|
420
428
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
429
|
+
feature_env, feature_tunable = load_env_file_with_markers(feature_env_file)
|
|
430
|
+
feature_defaults = merge_env_dicts(feature_defaults, feature_env)
|
|
431
|
+
tunable_keys |= feature_tunable
|
|
432
|
+
|
|
433
|
+
# What the feature declares in its own pyproject, applied after its
|
|
434
|
+
# env example so the pyproject wins. Outside the isdir() guard on
|
|
435
|
+
# purpose: most features ship no docker/ at all, and those are the
|
|
436
|
+
# ones with nowhere else to state a default. Skipping them would put
|
|
437
|
+
# a deploy template together out of whatever config.py falls back to.
|
|
438
|
+
feature_defaults = merge_env_dicts(
|
|
439
|
+
feature_defaults, _declared_config(_feature_pyproject(workspace, clean))
|
|
425
440
|
)
|
|
426
441
|
|
|
427
|
-
feature_env, feature_tunable = load_env_file_with_markers(feature_env_file)
|
|
428
|
-
feature_defaults = merge_env_dicts(feature_defaults, feature_env)
|
|
429
|
-
tunable_keys |= feature_tunable
|
|
430
|
-
|
|
431
442
|
# Apply the product port offset to feature port variables. Only to those:
|
|
432
443
|
# the offset exists so two products sharing a feature default do not fight
|
|
433
444
|
# over the same host port, and a port the product wrote itself is already
|
|
@@ -442,7 +453,12 @@ def product_build(no_image, skip_preflight):
|
|
|
442
453
|
except ValueError:
|
|
443
454
|
pass
|
|
444
455
|
|
|
445
|
-
|
|
456
|
+
# And what the product decides about those features, which beats their
|
|
457
|
+
# defaults and loses to the product's own env file, exactly as in
|
|
458
|
+
# product:env --merge. The two paths have to agree: a deploy template
|
|
459
|
+
# that disagreed with dev would only be found in production.
|
|
460
|
+
env_result = merge_env_dicts(feature_defaults, _declared_config(pyproject_path))
|
|
461
|
+
env_result = merge_env_dicts(env_result, product_env)
|
|
446
462
|
|
|
447
463
|
# Resolve __PRODUCT__ placeholder
|
|
448
464
|
for k, v in env_result.items():
|
|
@@ -43,6 +43,46 @@ def _parse_env_line(line: str):
|
|
|
43
43
|
return key, value
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def _declared_config(pyproject_path: str) -> dict:
|
|
47
|
+
"""The ``[tool.splent.config]`` block of a pyproject, as strings.
|
|
48
|
+
|
|
49
|
+
A feature declares the variables it reads and what they default to; a
|
|
50
|
+
product declares the ones it decides differently. Both use the same
|
|
51
|
+
key, because they are the same kind of statement made by two different
|
|
52
|
+
owners, and the merge decides which wins.
|
|
53
|
+
|
|
54
|
+
Values are stringified because an env file has no types. A boolean
|
|
55
|
+
written as ``true`` in TOML reaches the environment as ``true``, which
|
|
56
|
+
is what every config.py in this workspace already parses.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
with open(pyproject_path, "rb") as handle:
|
|
60
|
+
data = tomllib.load(handle)
|
|
61
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
62
|
+
return {}
|
|
63
|
+
|
|
64
|
+
declared = data.get("tool", {}).get("splent", {}).get("config", {})
|
|
65
|
+
if not isinstance(declared, dict):
|
|
66
|
+
return {}
|
|
67
|
+
|
|
68
|
+
out = {}
|
|
69
|
+
for key, value in declared.items():
|
|
70
|
+
if isinstance(value, bool):
|
|
71
|
+
out[key] = "true" if value else "false"
|
|
72
|
+
elif isinstance(value, (int, float, str)):
|
|
73
|
+
out[key] = str(value)
|
|
74
|
+
elif isinstance(value, list):
|
|
75
|
+
# A list is the natural way to write something the reader of
|
|
76
|
+
# the .env will see comma separated, like a set of locales.
|
|
77
|
+
out[key] = ",".join(str(item) for item in value)
|
|
78
|
+
return out
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _feature_pyproject(workspace: str, clean_ref: str) -> str:
|
|
82
|
+
"""Where a feature's own pyproject lives, editable or pinned."""
|
|
83
|
+
return os.path.join(compose.feature_dir(workspace, clean_ref), "pyproject.toml")
|
|
84
|
+
|
|
85
|
+
|
|
46
86
|
def _is_port_var(key: str, value: str) -> bool:
|
|
47
87
|
"""Check if an env var looks like a port declaration.
|
|
48
88
|
|
|
@@ -231,6 +271,11 @@ def product_env(generate, merge, env_name, process_all):
|
|
|
231
271
|
k, v = parsed
|
|
232
272
|
merged[k] = v
|
|
233
273
|
|
|
274
|
+
# What the product's own env file states, captured before anything
|
|
275
|
+
# is merged into it. These keys are the product's last word: nothing
|
|
276
|
+
# declared in a pyproject, its own included, may quietly replace one.
|
|
277
|
+
stated_by_the_product = set(merged)
|
|
278
|
+
|
|
234
279
|
# Always set SPLENT_ENV to match the current merge target
|
|
235
280
|
merged["SPLENT_ENV"] = env_name
|
|
236
281
|
|
|
@@ -307,6 +352,59 @@ def product_env(generate, merge, env_name, process_all):
|
|
|
307
352
|
# second product of the same line came up.
|
|
308
353
|
merged["SPLENT_NETWORK"] = compose.network_name(product)
|
|
309
354
|
|
|
355
|
+
# ── What each side declares in its pyproject ─────────────────────
|
|
356
|
+
#
|
|
357
|
+
# A feature declares the variables it reads and their defaults; a
|
|
358
|
+
# product declares the ones it decides differently. Same key on both
|
|
359
|
+
# sides, because it is the same kind of statement made by two owners.
|
|
360
|
+
#
|
|
361
|
+
# This exists so a product's .env example can go back to being
|
|
362
|
+
# infrastructure. Feature settings used to be copied into it, which
|
|
363
|
+
# meant a product carried variables it had no opinion about, and a
|
|
364
|
+
# feature with no container had nowhere to declare a default at all,
|
|
365
|
+
# since .env.example lives under docker/.
|
|
366
|
+
#
|
|
367
|
+
# Precedence, weakest first:
|
|
368
|
+
# feature docker/.env.example (already merged above)
|
|
369
|
+
# feature [tool.splent.config]
|
|
370
|
+
# product [tool.splent.config]
|
|
371
|
+
# product .env.<env>.example (captured in stated_by_the_product)
|
|
372
|
+
#
|
|
373
|
+
# A feature that states a default in both places is saying the same
|
|
374
|
+
# thing twice; the pyproject wins because that is where a product's
|
|
375
|
+
# override sits, so the two can be read side by side.
|
|
376
|
+
declared_by_features = []
|
|
377
|
+
for feature in features:
|
|
378
|
+
clean_ref = compose.normalize_feature_ref(feature)
|
|
379
|
+
for key, value in _declared_config(
|
|
380
|
+
_feature_pyproject(workspace, clean_ref)
|
|
381
|
+
).items():
|
|
382
|
+
if key in stated_by_the_product:
|
|
383
|
+
continue
|
|
384
|
+
if _is_port_var(key, value):
|
|
385
|
+
# Same offset the container defaults get: a port a
|
|
386
|
+
# feature declares is still a port two products would
|
|
387
|
+
# otherwise fight over.
|
|
388
|
+
try:
|
|
389
|
+
merged[key] = str(int(value) + port_offset)
|
|
390
|
+
port_adjusted.append(
|
|
391
|
+
(key, int(value), int(value) + port_offset)
|
|
392
|
+
)
|
|
393
|
+
except ValueError:
|
|
394
|
+
merged[key] = value
|
|
395
|
+
else:
|
|
396
|
+
merged[key] = value
|
|
397
|
+
declared_by_features.append(key)
|
|
398
|
+
|
|
399
|
+
product_declared = _declared_config(py_path)
|
|
400
|
+
for key, value in product_declared.items():
|
|
401
|
+
# Beats the feature's default, which is the point of writing it
|
|
402
|
+
# here, and loses to the product's own env file, which is the
|
|
403
|
+
# escape hatch for a setting an operator needs to force.
|
|
404
|
+
if key in stated_by_the_product:
|
|
405
|
+
continue
|
|
406
|
+
merged[key] = value
|
|
407
|
+
|
|
310
408
|
# Replace __PRODUCT__ placeholder with the actual product name
|
|
311
409
|
product_replaced = []
|
|
312
410
|
for k, v in merged.items():
|
|
@@ -325,6 +423,17 @@ def product_env(generate, merge, env_name, process_all):
|
|
|
325
423
|
+ f"resolved __PRODUCT__ in {len(product_replaced)} variable(s) → {product}"
|
|
326
424
|
)
|
|
327
425
|
|
|
426
|
+
if declared_by_features:
|
|
427
|
+
click.echo(
|
|
428
|
+
click.style(" declared ", dim=True)
|
|
429
|
+
+ f"{len(declared_by_features)} default(s) from feature pyprojects"
|
|
430
|
+
)
|
|
431
|
+
if product_declared:
|
|
432
|
+
click.echo(
|
|
433
|
+
click.style(" product ", dim=True)
|
|
434
|
+
+ f"{len(product_declared)} value(s) this product decides"
|
|
435
|
+
)
|
|
436
|
+
|
|
328
437
|
if port_adjusted:
|
|
329
438
|
click.echo(
|
|
330
439
|
click.style(" ports ", dim=True)
|
|
@@ -437,24 +437,39 @@ def resolve_file(product_path: str, env: str) -> str | None:
|
|
|
437
437
|
return None
|
|
438
438
|
|
|
439
439
|
|
|
440
|
-
def
|
|
441
|
-
"""Return the
|
|
442
|
-
|
|
443
|
-
Accepts refs like 'splent_io/splent_feature_redis@v1.5.4' or bare
|
|
444
|
-
Checks workspace root first (editable
|
|
445
|
-
to the .splent_cache (pinned features).
|
|
440
|
+
def feature_dir(workspace: str, feature: str) -> str:
|
|
441
|
+
"""Return the root directory of a feature: where its pyproject lives.
|
|
442
|
+
|
|
443
|
+
Accepts refs like 'splent_io/splent_feature_redis@v1.5.4' or bare
|
|
444
|
+
'splent_feature_redis'. Checks the workspace root first (editable
|
|
445
|
+
features), then falls back to the .splent_cache (pinned features).
|
|
446
|
+
|
|
447
|
+
Resolution is by the feature's own directory rather than by any file
|
|
448
|
+
inside it, so a feature that ships no docker/ still resolves to its
|
|
449
|
+
editable copy. Keying on docker/ meant a feature with no container
|
|
450
|
+
silently resolved to a stale pinned copy in the cache, and read that
|
|
451
|
+
copy's settings instead of the ones being edited.
|
|
446
452
|
"""
|
|
447
453
|
# Extract bare name (strip org prefix and version)
|
|
448
454
|
name = feature.split("/")[-1] if "/" in feature else feature
|
|
449
455
|
bare_name = name.split("@")[0]
|
|
450
456
|
|
|
451
457
|
# Editable feature at workspace root
|
|
452
|
-
|
|
453
|
-
if os.path.isdir(
|
|
454
|
-
return
|
|
458
|
+
root = os.path.join(workspace, bare_name)
|
|
459
|
+
if os.path.isdir(root):
|
|
460
|
+
return root
|
|
455
461
|
|
|
456
462
|
# Pinned feature in cache (needs full ref with org and version)
|
|
457
|
-
return os.path.join(workspace, ".splent_cache", "features", feature
|
|
463
|
+
return os.path.join(workspace, ".splent_cache", "features", feature)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def feature_docker_dir(workspace: str, feature: str) -> str:
|
|
467
|
+
"""Return the docker/ directory for a feature.
|
|
468
|
+
|
|
469
|
+
The directory need not exist: only features that bring a container
|
|
470
|
+
have one, and callers check before reading.
|
|
471
|
+
"""
|
|
472
|
+
return os.path.join(feature_dir(workspace, feature), "docker")
|
|
458
473
|
|
|
459
474
|
|
|
460
475
|
def normalize_feature_ref(feat: str) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.19.0
|
|
4
4
|
Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
|
|
5
5
|
Author-email: DiversoLab <diversolab@us.es>
|
|
6
6
|
Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|