splent-cli 1.27.0__tar.gz → 1.28.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.27.0/src/splent_cli.egg-info → splent_cli-1.28.0}/PKG-INFO +2 -2
- {splent_cli-1.27.0 → splent_cli-1.28.0}/pyproject.toml +6 -3
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_product.py +99 -5
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_console.py +5 -6
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_dump.py +5 -6
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_migrate.py +2 -3
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_restore.py +7 -12
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_rollback.py +28 -3
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_seed.py +7 -3
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_add.py +11 -9
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_attach.py +27 -21
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_compile.py +148 -4
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_detach.py +3 -3
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_order.py +15 -7
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_outdated.py +15 -19
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_pin.py +3 -3
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_refine.py +349 -171
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_release.py +105 -15
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_remove.py +3 -3
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_rename.py +3 -3
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_status.py +46 -13
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_test.py +18 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_unlock.py +11 -5
- splent_cli-1.28.0/src/splent_cli/commands/feature/feature_upgrade.py +757 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_auto_require.py +5 -8
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_build.py +381 -5
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_configure.py +3 -3
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_create.py +4 -2
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_deploy.py +44 -6
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_derive.py +2 -1
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_env.py +2 -6
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_release.py +3 -1
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_up.py +20 -11
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_validate.py +198 -14
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/credentials.py +1 -1
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/preflight.py +40 -0
- splent_cli-1.28.0/src/splent_cli/utils/db_client.py +38 -0
- splent_cli-1.28.0/src/splent_cli/utils/integrity.py +433 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/io_utils.py +5 -1
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/lifecycle.py +4 -4
- splent_cli-1.28.0/src/splent_cli/utils/refinement_facts.py +1651 -0
- splent_cli-1.28.0/src/splent_cli/utils/toml_edit.py +765 -0
- splent_cli-1.28.0/src/splent_cli/utils/upgrade_plan.py +1054 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0/src/splent_cli.egg-info}/PKG-INFO +2 -2
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli.egg-info/SOURCES.txt +5 -1
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli.egg-info/requires.txt +1 -1
- splent_cli-1.27.0/src/splent_cli/commands/feature/feature_upgrade.py +0 -336
- splent_cli-1.27.0/src/splent_cli/utils/integrity.py +0 -241
- {splent_cli-1.27.0 → splent_cli-1.28.0}/LICENSE +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/README.md +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/setup.cfg +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/release/release_resume.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/marketplace.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/pypi_quota.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/release_dry_run.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/services/spl_store.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/forge.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/redact.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.27.0 → splent_cli-1.28.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.28.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
|
|
@@ -14,7 +14,7 @@ Requires-Dist: packaging>=24.0
|
|
|
14
14
|
Requires-Dist: python-dotenv>=1.0
|
|
15
15
|
Requires-Dist: pyyaml>=6.0
|
|
16
16
|
Requires-Dist: requests>=2.31.0
|
|
17
|
-
Requires-Dist: splent_framework<2,>=1.
|
|
17
|
+
Requires-Dist: splent_framework<2,>=1.18.0
|
|
18
18
|
Requires-Dist: tomli_w>=1.0.0
|
|
19
19
|
Provides-Extra: dev
|
|
20
20
|
Requires-Dist: setuptools==80.3.1; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "splent_cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.28.0"
|
|
8
8
|
description = "SPLENT-CLI is a CLI to be able to work on your development more easily."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -23,8 +23,11 @@ dependencies = [
|
|
|
23
23
|
"pyyaml>=6.0",
|
|
24
24
|
"requests>=2.31.0",
|
|
25
25
|
# The CLI drives the framework (paths, feature order, migrations, the
|
|
26
|
-
# app factory) and only works with the same major version of it.
|
|
27
|
-
|
|
26
|
+
# app factory) and only works with the same major version of it. 1.18.0
|
|
27
|
+
# resolves a feature to the copy feature:test names (SPLENT_FEATURE_SOURCES)
|
|
28
|
+
# and brings the production defaults the deploy templates explain; 1.17.0
|
|
29
|
+
# the refinement rules product:validate shares with the boot.
|
|
30
|
+
"splent_framework>=1.18.0,<2",
|
|
28
31
|
"tomli_w>=1.0.0",
|
|
29
32
|
]
|
|
30
33
|
|
|
@@ -6,9 +6,11 @@ Validates that the active product is correctly wired:
|
|
|
6
6
|
2. Feature symlinks resolve to real directories
|
|
7
7
|
3. Config overwrites are flagged
|
|
8
8
|
4. Features that loaded but registered no blueprints are warned
|
|
9
|
+
5. Product templates that link a static file that does not exist are warned
|
|
9
10
|
"""
|
|
10
11
|
|
|
11
12
|
import os
|
|
13
|
+
import re
|
|
12
14
|
import tomllib
|
|
13
15
|
|
|
14
16
|
import click
|
|
@@ -304,6 +306,94 @@ def _check_blueprints(counters):
|
|
|
304
306
|
counters["ok"] += 1
|
|
305
307
|
|
|
306
308
|
|
|
309
|
+
# ── Static links ─────────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
# A literal filename only. One built at render time ('img/' ~ name) cannot be
|
|
312
|
+
# checked without rendering, so it is left alone.
|
|
313
|
+
_STATIC_LINK = re.compile(
|
|
314
|
+
r"""url_for\(\s*['"]static['"]\s*,\s*filename\s*=\s*['"]([^'"{}~]+)['"]\s*\)"""
|
|
315
|
+
)
|
|
316
|
+
# The icon a broken icon link should point at instead, best first: the
|
|
317
|
+
# scaffold's favicon.svg, then what products that replaced it tend to ship.
|
|
318
|
+
_FAVICONS = (
|
|
319
|
+
("favicon.svg", "image/svg+xml"),
|
|
320
|
+
("favicon.ico", "image/x-icon"),
|
|
321
|
+
("favicon-32.png", "image/png"),
|
|
322
|
+
("favicon.png", "image/png"),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _favicon_line(static: str) -> str | None:
|
|
327
|
+
for filename, mime in _FAVICONS:
|
|
328
|
+
if os.path.isfile(os.path.join(static, filename)):
|
|
329
|
+
return (
|
|
330
|
+
f'<link rel="icon" type="{mime}" '
|
|
331
|
+
f"href=\"{{{{ url_for('static', filename='{filename}') }}}}\">"
|
|
332
|
+
)
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _broken_static_links(product_path: str, product: str) -> list[tuple]:
|
|
337
|
+
"""(template, line number, line, filename) for each link to a missing file.
|
|
338
|
+
|
|
339
|
+
Only the product's own templates and the app's static folder, which is
|
|
340
|
+
what url_for('static', ...) serves. A feature's templates link their
|
|
341
|
+
blueprint's static folder and ship with the feature.
|
|
342
|
+
"""
|
|
343
|
+
package = os.path.join(product_path, "src", product)
|
|
344
|
+
templates = os.path.join(package, "templates")
|
|
345
|
+
static = os.path.join(package, "static")
|
|
346
|
+
broken = []
|
|
347
|
+
if not os.path.isdir(templates):
|
|
348
|
+
return broken
|
|
349
|
+
for root, dirs, files in os.walk(templates):
|
|
350
|
+
dirs.sort()
|
|
351
|
+
for name in sorted(files):
|
|
352
|
+
if not name.endswith((".html", ".j2", ".jinja", ".jinja2")):
|
|
353
|
+
continue
|
|
354
|
+
path = os.path.join(root, name)
|
|
355
|
+
try:
|
|
356
|
+
with open(path, encoding="utf-8") as fh:
|
|
357
|
+
lines = fh.read().splitlines()
|
|
358
|
+
except (OSError, UnicodeDecodeError):
|
|
359
|
+
continue
|
|
360
|
+
rel = os.path.relpath(path, package).replace(os.sep, "/")
|
|
361
|
+
for number, line in enumerate(lines, start=1):
|
|
362
|
+
for filename in _STATIC_LINK.findall(line):
|
|
363
|
+
target = os.path.join(static, *filename.strip("/").split("/"))
|
|
364
|
+
if not os.path.isfile(target):
|
|
365
|
+
broken.append((rel, number, line.strip(), filename))
|
|
366
|
+
return broken
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _check_static_links(product_path, product, counters):
|
|
370
|
+
"""Warn about product templates linking a static file that is not there."""
|
|
371
|
+
click.echo()
|
|
372
|
+
click.secho(" Static links", bold=True, fg="cyan")
|
|
373
|
+
click.echo()
|
|
374
|
+
|
|
375
|
+
broken = _broken_static_links(product_path, product)
|
|
376
|
+
if not broken:
|
|
377
|
+
click.echo(" All static links in the product templates resolve.")
|
|
378
|
+
counters["ok"] += 1
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
favicon_line = _favicon_line(os.path.join(product_path, "src", product, "static"))
|
|
382
|
+
for rel, number, line, filename in broken:
|
|
383
|
+
click.echo(
|
|
384
|
+
click.style(" ⚠ ", fg="yellow")
|
|
385
|
+
+ f"{rel} line {number} links static/{filename}, which does not exist."
|
|
386
|
+
)
|
|
387
|
+
if favicon_line and re.search(r"rel=['\"](shortcut )?icon['\"]", line):
|
|
388
|
+
click.echo(f" Replace that line with {favicon_line}")
|
|
389
|
+
else:
|
|
390
|
+
click.echo(
|
|
391
|
+
f" Add the file under src/{product}/static or point the link "
|
|
392
|
+
"at a file that exists there."
|
|
393
|
+
)
|
|
394
|
+
counters["warn"] += len(broken)
|
|
395
|
+
|
|
396
|
+
|
|
307
397
|
# ── Command ──────────────────────────────────────────────────────────────────
|
|
308
398
|
|
|
309
399
|
|
|
@@ -329,9 +419,11 @@ def check_product():
|
|
|
329
419
|
click.secho(" pyproject.toml not found", fg="red")
|
|
330
420
|
raise SystemExit(1)
|
|
331
421
|
|
|
422
|
+
_check_static_links(product_path, product, counters)
|
|
423
|
+
|
|
332
424
|
if not features:
|
|
333
425
|
click.secho(" No features declared.", fg="yellow")
|
|
334
|
-
|
|
426
|
+
_print_summary(counters)
|
|
335
427
|
return
|
|
336
428
|
|
|
337
429
|
# Report stale contracts before reading them. A check never rewrites a
|
|
@@ -345,8 +437,13 @@ def check_product():
|
|
|
345
437
|
_check_symlinks(product_path, features, counters)
|
|
346
438
|
_check_config_overwrites(counters)
|
|
347
439
|
_check_blueprints(counters)
|
|
440
|
+
_print_summary(counters)
|
|
441
|
+
|
|
442
|
+
if counters["fail"]:
|
|
443
|
+
raise SystemExit(1)
|
|
348
444
|
|
|
349
|
-
|
|
445
|
+
|
|
446
|
+
def _print_summary(counters):
|
|
350
447
|
click.echo()
|
|
351
448
|
parts = []
|
|
352
449
|
if counters["ok"]:
|
|
@@ -358,8 +455,5 @@ def check_product():
|
|
|
358
455
|
click.echo(" " + ", ".join(parts))
|
|
359
456
|
click.echo()
|
|
360
457
|
|
|
361
|
-
if counters["fail"]:
|
|
362
|
-
raise SystemExit(1)
|
|
363
|
-
|
|
364
458
|
|
|
365
459
|
cli_command = check_product
|
|
@@ -3,6 +3,7 @@ from dotenv import load_dotenv
|
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
5
|
from splent_cli.services import context
|
|
6
|
+
from splent_cli.utils import db_client
|
|
6
7
|
from splent_cli.utils.proc import run, require_tool
|
|
7
8
|
|
|
8
9
|
|
|
@@ -38,16 +39,14 @@ def db_console():
|
|
|
38
39
|
)
|
|
39
40
|
raise SystemExit(1)
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"Install the MariaDB/MySQL client (e.g. 'mariadb-client') and ensure it is on your PATH.",
|
|
44
|
-
)
|
|
42
|
+
tool = db_client.client_tool()
|
|
43
|
+
require_tool(tool, db_client.INSTALL_HINT)
|
|
45
44
|
|
|
46
45
|
# The password goes in MYSQL_PWD, as in db:dump and db:restore, so it
|
|
47
46
|
# never shows up in the process list.
|
|
48
47
|
result = run(
|
|
49
48
|
[
|
|
50
|
-
|
|
49
|
+
tool,
|
|
51
50
|
f"-h{mariadb_hostname}",
|
|
52
51
|
f"-u{mariadb_user}",
|
|
53
52
|
mariadb_database,
|
|
@@ -57,7 +56,7 @@ def db_console():
|
|
|
57
56
|
)
|
|
58
57
|
if result.returncode != 0:
|
|
59
58
|
click.secho(
|
|
60
|
-
f"❌ Could not open the MariaDB console (
|
|
59
|
+
f"❌ Could not open the MariaDB console ({tool} exited {result.returncode}).",
|
|
61
60
|
fg="red",
|
|
62
61
|
)
|
|
63
62
|
click.secho(
|
|
@@ -8,6 +8,7 @@ import tempfile
|
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
|
|
10
10
|
from splent_cli.services import context
|
|
11
|
+
from splent_cli.utils import db_client
|
|
11
12
|
from splent_cli.utils.proc import run, require_tool
|
|
12
13
|
|
|
13
14
|
# A dump below this size cannot be a real database export (even an empty
|
|
@@ -133,10 +134,8 @@ def db_dump(filename, gzip_output, retention):
|
|
|
133
134
|
"Create it first or choose another path."
|
|
134
135
|
)
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"Install the MariaDB/MySQL client tools (provides 'mysqldump').",
|
|
139
|
-
)
|
|
137
|
+
dump_tool = db_client.dump_tool()
|
|
138
|
+
require_tool(dump_tool, db_client.INSTALL_HINT)
|
|
140
139
|
|
|
141
140
|
env = {**os.environ, "MYSQL_PWD": mariadb_password or ""}
|
|
142
141
|
|
|
@@ -153,7 +152,7 @@ def db_dump(filename, gzip_output, retention):
|
|
|
153
152
|
with os.fdopen(fd, "wb") as out:
|
|
154
153
|
run(
|
|
155
154
|
[
|
|
156
|
-
|
|
155
|
+
dump_tool,
|
|
157
156
|
# Reliability flags. --single-transaction takes a
|
|
158
157
|
# consistent snapshot without locking tables, --quick
|
|
159
158
|
# streams rows instead of buffering whole tables in
|
|
@@ -169,7 +168,7 @@ def db_dump(filename, gzip_output, retention):
|
|
|
169
168
|
stdout=out,
|
|
170
169
|
text=False,
|
|
171
170
|
env=env,
|
|
172
|
-
tool_hint=
|
|
171
|
+
tool_hint=db_client.INSTALL_HINT,
|
|
173
172
|
)
|
|
174
173
|
|
|
175
174
|
# A truncated dump restores as an empty or partial database, which is
|
|
@@ -110,8 +110,7 @@ def _hand_to_directory_owner(path: str) -> None:
|
|
|
110
110
|
is_flag=True,
|
|
111
111
|
help=(
|
|
112
112
|
"Generate a blank migration (no autogenerate) and keep it for manual "
|
|
113
|
-
"editing
|
|
114
|
-
"feature's table — Alembic cannot autodetect runtime-injected mixins."
|
|
113
|
+
"editing (data migrations or hand-written changes)."
|
|
115
114
|
),
|
|
116
115
|
)
|
|
117
116
|
@context.requires_product
|
|
@@ -243,7 +242,7 @@ def db_migrate(feature, message, empty):
|
|
|
243
242
|
)
|
|
244
243
|
|
|
245
244
|
# A blank migration requested with --empty is intentional: keep it so the
|
|
246
|
-
# user can fill in upgrade()/downgrade() by hand (e.g.
|
|
245
|
+
# user can fill in upgrade()/downgrade() by hand (e.g. a data migration).
|
|
247
246
|
if empty:
|
|
248
247
|
if newest:
|
|
249
248
|
_format_migration(newest)
|
|
@@ -10,6 +10,7 @@ from datetime import datetime
|
|
|
10
10
|
from dotenv import load_dotenv
|
|
11
11
|
|
|
12
12
|
from splent_cli.services import context
|
|
13
|
+
from splent_cli.utils import db_client
|
|
13
14
|
from splent_cli.utils.proc import run
|
|
14
15
|
|
|
15
16
|
# Same-second collisions get _1, _2, ... ; this many tries means something
|
|
@@ -108,7 +109,7 @@ def db_restore(filename, yes):
|
|
|
108
109
|
tmp_sql_path = None
|
|
109
110
|
|
|
110
111
|
try:
|
|
111
|
-
# Accept gzip-compressed dumps transparently.
|
|
112
|
+
# Accept gzip-compressed dumps transparently. The client needs a plain SQL
|
|
112
113
|
# stream on stdin, so decompress to a temp file next to the dump and
|
|
113
114
|
# feed that instead. Doing this before the pre-restore backup means a
|
|
114
115
|
# corrupt archive aborts the command before anything else happens.
|
|
@@ -140,7 +141,7 @@ def db_restore(filename, yes):
|
|
|
140
141
|
with os.fdopen(backup_fd, "wb") as backup_out:
|
|
141
142
|
run(
|
|
142
143
|
[
|
|
143
|
-
|
|
144
|
+
db_client.dump_tool(),
|
|
144
145
|
# Same reliability flags as db:dump, so the safety-net
|
|
145
146
|
# backup is itself a consistent, restorable dump.
|
|
146
147
|
"--single-transaction",
|
|
@@ -152,13 +153,10 @@ def db_restore(filename, yes):
|
|
|
152
153
|
],
|
|
153
154
|
stdout=backup_out,
|
|
154
155
|
env=env,
|
|
155
|
-
tool_hint=
|
|
156
|
-
"Install the MariaDB/MySQL client tools (provides 'mysqldump'), "
|
|
157
|
-
"e.g. 'mysql-client' / 'mariadb-client'."
|
|
158
|
-
),
|
|
156
|
+
tool_hint=db_client.INSTALL_HINT,
|
|
159
157
|
)
|
|
160
158
|
except click.ClickException:
|
|
161
|
-
#
|
|
159
|
+
# The dump tool missing or failed (often a connection failure): do not proceed
|
|
162
160
|
# to overwrite the live database without a safety net.
|
|
163
161
|
# Only ever the file this run created (O_EXCL above).
|
|
164
162
|
if backup_filename and os.path.exists(backup_filename):
|
|
@@ -176,13 +174,10 @@ def db_restore(filename, yes):
|
|
|
176
174
|
try:
|
|
177
175
|
with open(restore_path, "rb") as sql_file:
|
|
178
176
|
run(
|
|
179
|
-
[
|
|
177
|
+
[db_client.client_tool(), f"-h{host}", f"-u{user}", database],
|
|
180
178
|
stdin=sql_file,
|
|
181
179
|
env=env,
|
|
182
|
-
tool_hint=
|
|
183
|
-
"Install the MariaDB/MySQL client tools (provides 'mysql'), "
|
|
184
|
-
"e.g. 'mysql-client' / 'mariadb-client'."
|
|
185
|
-
),
|
|
180
|
+
tool_hint=db_client.INSTALL_HINT,
|
|
186
181
|
)
|
|
187
182
|
click.secho(f"✅ Database restored from: {filename}", fg="green")
|
|
188
183
|
except click.ClickException as e:
|
|
@@ -14,6 +14,7 @@ from splent_cli.commands.database.db_upgrade import (
|
|
|
14
14
|
from splent_cli.utils.decorators import requires_db
|
|
15
15
|
from splent_cli.services import context
|
|
16
16
|
from splent_cli.utils.lifecycle import advance_state, resolve_feature_key_from_entry
|
|
17
|
+
from splent_cli.utils.refinement_facts import read_refines
|
|
17
18
|
from splent_framework.managers.migration_manager import MigrationManager
|
|
18
19
|
from splent_framework.managers.feature_order import FeatureLoadOrderResolver
|
|
19
20
|
from splent_framework.utils.feature_utils import get_features_from_pyproject
|
|
@@ -22,11 +23,35 @@ from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def _find_dependents(feature: str, product_dir: str) -> list[str]:
|
|
25
|
-
"""Find features that depend on `feature
|
|
26
|
+
"""Find the features that depend on `feature`, to roll back first.
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
The UVL constraints name them (``A => feature``, in constraint order, the
|
|
29
|
+
logic feature:order uses), and so does the refinement metadata the boot
|
|
30
|
+
reads: a refiner's migrations change its base's tables, so it is rolled
|
|
31
|
+
back before its base even when the UVL does not relate them. Returns
|
|
32
|
+
package names, the UVL dependents first and then any other refiner.
|
|
29
33
|
"""
|
|
34
|
+
dependents = _uvl_dependents(feature, product_dir)
|
|
35
|
+
for refiner in _refiners_of(feature, product_dir):
|
|
36
|
+
if refiner not in dependents:
|
|
37
|
+
dependents.append(refiner)
|
|
38
|
+
return dependents
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _refiners_of(feature: str, product_dir: str) -> list[str]:
|
|
42
|
+
"""The product's features that refine `feature`, in declared order."""
|
|
43
|
+
try:
|
|
44
|
+
entries = PyprojectReader.for_product(product_dir).features_for_env(
|
|
45
|
+
os.getenv("SPLENT_ENV")
|
|
46
|
+
)
|
|
47
|
+
refines = read_refines(product_dir, entries)
|
|
48
|
+
except Exception:
|
|
49
|
+
return []
|
|
50
|
+
return [refiner for refiner, base in refines.items() if base == feature]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _uvl_dependents(feature: str, product_dir: str) -> list[str]:
|
|
54
|
+
"""Features whose UVL constraint says they require `feature`."""
|
|
30
55
|
from splent_cli.services import spl_store
|
|
31
56
|
|
|
32
57
|
try:
|
|
@@ -6,6 +6,7 @@ import click
|
|
|
6
6
|
from splent_cli.utils.decorators import requires_db
|
|
7
7
|
from splent_cli.services import context
|
|
8
8
|
from splent_cli.utils.lifecycle import resolve_feature_key_from_entry
|
|
9
|
+
from splent_cli.utils.refinement_facts import read_refines
|
|
9
10
|
from splent_cli.utils.feature_utils import (
|
|
10
11
|
get_features_from_pyproject,
|
|
11
12
|
normalize_namespace,
|
|
@@ -16,9 +17,11 @@ from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def _resolve_feature_order(features_raw: list[str]) -> list[str]:
|
|
19
|
-
"""Return features in
|
|
20
|
+
"""Return features in the order the product loads them.
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
The UVL constraints order them, and every refiner comes after the
|
|
23
|
+
feature it refines (the refinement metadata the boot reads), with or
|
|
24
|
+
without a UVL. Features with neither keep their declared order.
|
|
22
25
|
"""
|
|
23
26
|
splent_app = os.getenv("SPLENT_APP", "")
|
|
24
27
|
working_dir = os.getenv("WORKING_DIR", "/workspace")
|
|
@@ -39,7 +42,8 @@ def _resolve_feature_order(features_raw: list[str]) -> list[str]:
|
|
|
39
42
|
except (OSError, KeyError, AttributeError):
|
|
40
43
|
pass
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
refines = read_refines(product_dir, features_raw) if product_dir else {}
|
|
46
|
+
return FeatureLoadOrderResolver().resolve(features_raw, uvl_path, refines=refines)
|
|
43
47
|
|
|
44
48
|
|
|
45
49
|
def _collect_seeders(module_name):
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import tomli_w
|
|
3
2
|
import click
|
|
4
3
|
from splent_cli.services import context
|
|
5
4
|
from splent_cli.utils.feature_utils import (
|
|
@@ -14,7 +13,8 @@ from splent_cli.utils.feature_utils import (
|
|
|
14
13
|
remove_feature_link,
|
|
15
14
|
write_features_to_data,
|
|
16
15
|
)
|
|
17
|
-
from splent_cli.utils.io_utils import load_toml
|
|
16
|
+
from splent_cli.utils.io_utils import load_toml
|
|
17
|
+
from splent_cli.utils.toml_edit import write_toml
|
|
18
18
|
from splent_cli.utils.manifest import feature_key, remove_feature, set_feature_state
|
|
19
19
|
|
|
20
20
|
|
|
@@ -126,6 +126,15 @@ def feature_add(full_name, env_scope):
|
|
|
126
126
|
click.echo(f" {short} already in {where}.")
|
|
127
127
|
return
|
|
128
128
|
|
|
129
|
+
drop_feature_entries(data, feature_name, namespace=org_safe)
|
|
130
|
+
for key in target_keys:
|
|
131
|
+
features = read_feature_list(data, key)
|
|
132
|
+
features.append(full_name)
|
|
133
|
+
write_features_to_data(data, features, key=key)
|
|
134
|
+
# Written first: when the edit cannot be made exactly it is refused with
|
|
135
|
+
# nothing written, and the stale links and manifest entries are still there.
|
|
136
|
+
write_toml(pyproject_path, data)
|
|
137
|
+
|
|
129
138
|
# Drop the stale declarations (other list, pinned version, other spelling of
|
|
130
139
|
# the namespace) together with what each one created, so the feature ends up
|
|
131
140
|
# declared once per list and with a single symlink.
|
|
@@ -137,13 +146,6 @@ def feature_add(full_name, env_scope):
|
|
|
137
146
|
)
|
|
138
147
|
click.echo(click.style(f" replacing {stale_entry} in {stale_key}", dim=True))
|
|
139
148
|
|
|
140
|
-
drop_feature_entries(data, feature_name, namespace=org_safe)
|
|
141
|
-
for key in target_keys:
|
|
142
|
-
features = read_feature_list(data, key)
|
|
143
|
-
features.append(full_name)
|
|
144
|
-
write_features_to_data(data, features, key=key)
|
|
145
|
-
atomic_write(pyproject_path, tomli_w.dumps(data))
|
|
146
|
-
|
|
147
149
|
scope_label = f" ({env_scope} only)" if env_scope else ""
|
|
148
150
|
click.echo(f" {short} added to {where}{scope_label}")
|
|
149
151
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import tomli_w
|
|
3
2
|
import click
|
|
4
3
|
from splent_cli.services import context, compose
|
|
5
4
|
from splent_cli.utils.feature_utils import (
|
|
@@ -15,7 +14,8 @@ from splent_cli.utils.feature_utils import (
|
|
|
15
14
|
set_feature_distribution,
|
|
16
15
|
write_features_to_data,
|
|
17
16
|
)
|
|
18
|
-
from splent_cli.utils.io_utils import load_toml
|
|
17
|
+
from splent_cli.utils.io_utils import load_toml
|
|
18
|
+
from splent_cli.utils.toml_edit import write_toml
|
|
19
19
|
from splent_cli.utils.manifest import feature_key, remove_feature, set_feature_state
|
|
20
20
|
|
|
21
21
|
|
|
@@ -156,7 +156,29 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
156
156
|
stale = [pair for pair in declared if pair not in wanted]
|
|
157
157
|
where = ", ".join(target_keys)
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
attaching = sorted(declared) != sorted(wanted)
|
|
160
|
+
if attaching:
|
|
161
|
+
drop_feature_entries(data, feature_name, namespace=namespace_fs)
|
|
162
|
+
for key in target_keys:
|
|
163
|
+
features = read_feature_list(data, key)
|
|
164
|
+
features.append(full_name)
|
|
165
|
+
write_features_to_data(data, features, key=key)
|
|
166
|
+
|
|
167
|
+
# ── PyPI name, when it is not the feature's own ──────────────────
|
|
168
|
+
# The production image installs pinned features from PyPI, so it has to
|
|
169
|
+
# know the name this version was published under. A release under a name
|
|
170
|
+
# of one's own (splent-feature-notes-alice) keeps the import package, and
|
|
171
|
+
# this pin, as splent_feature_notes.
|
|
172
|
+
distribution = read_project_name(versioned_dir)
|
|
173
|
+
renamed = set_feature_distribution(data, feature_name, distribution)
|
|
174
|
+
|
|
175
|
+
# One write, before anything else changes. When the edit cannot be made
|
|
176
|
+
# exactly it is refused with nothing written, and the old links and
|
|
177
|
+
# manifest entries are still there.
|
|
178
|
+
if attaching or renamed:
|
|
179
|
+
write_toml(pyproject_path, data)
|
|
180
|
+
|
|
181
|
+
if not attaching:
|
|
160
182
|
click.echo(f" {short}@{version} already in {where}.")
|
|
161
183
|
else:
|
|
162
184
|
# Drop the stale declarations (other list, other version, other spelling
|
|
@@ -173,13 +195,6 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
173
195
|
click.echo(
|
|
174
196
|
click.style(f" replacing {stale_entry} in {stale_key}", dim=True)
|
|
175
197
|
)
|
|
176
|
-
|
|
177
|
-
drop_feature_entries(data, feature_name, namespace=namespace_fs)
|
|
178
|
-
for key in target_keys:
|
|
179
|
-
features = read_feature_list(data, key)
|
|
180
|
-
features.append(full_name)
|
|
181
|
-
write_features_to_data(data, features, key=key)
|
|
182
|
-
atomic_write(pyproject_path, tomli_w.dumps(data))
|
|
183
198
|
if env_scope:
|
|
184
199
|
scope_label = f" ({env_scope} only)"
|
|
185
200
|
elif target_keys != ["features"]:
|
|
@@ -188,17 +203,8 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
188
203
|
scope_label = ""
|
|
189
204
|
click.echo(f" {short}@{version} attached{scope_label}")
|
|
190
205
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
# know the name this version was published under. A release under a name
|
|
194
|
-
# of one's own (splent-feature-notes-alice) keeps the import package, and
|
|
195
|
-
# this pin, as splent_feature_notes.
|
|
196
|
-
distribution = read_project_name(versioned_dir)
|
|
197
|
-
data = load_toml(pyproject_path, what="pyproject.toml")
|
|
198
|
-
if set_feature_distribution(data, feature_name, distribution):
|
|
199
|
-
atomic_write(pyproject_path, tomli_w.dumps(data))
|
|
200
|
-
if distribution and distribution != feature_name:
|
|
201
|
-
click.echo(f" pypi {short} is published on PyPI as {distribution}")
|
|
206
|
+
if renamed and distribution and distribution != feature_name:
|
|
207
|
+
click.echo(f" pypi {short} is published on PyPI as {distribution}")
|
|
202
208
|
|
|
203
209
|
# ── Create/update symlink ─────────────────────────────────────────
|
|
204
210
|
product_features_dir = os.path.join(product_path, "features", namespace_fs)
|