splent-cli 1.44.0__tar.gz → 1.45.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.44.0/src/splent_cli.egg-info → splent_cli-1.45.0}/PKG-INFO +1 -1
- {splent_cli-1.44.0 → splent_cli-1.45.0}/pyproject.toml +1 -1
- splent_cli-1.45.0/src/splent_cli/__init__.py +42 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/cli.py +6 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_env.py +4 -2
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/env/env_set.py +6 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_attach.py +28 -2
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_compat.py +37 -23
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_contract.py +45 -7
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_release.py +157 -6
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_test.py +6 -61
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_unlock.py +13 -1
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +33 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_build.py +9 -4
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_config.py +2 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_console.py +2 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_derive.py +34 -1
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_restart.py +8 -1
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_routes.py +2 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_signals.py +2 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_validate.py +132 -31
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/release/release_core.py +7 -1
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_publish.py +16 -3
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/version.py +10 -8
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/deploy_bundle.py +6 -6
- splent_cli-1.45.0/src/splent_cli/services/editable_refresh.py +129 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/marketplace_api.py +11 -1
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/neutral_app.py +65 -9
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/preflight.py +4 -1
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/release.py +42 -0
- splent_cli-1.45.0/src/splent_cli/services/web_container.py +146 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +21 -2
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +11 -0
- splent_cli-1.45.0/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +28 -0
- splent_cli-1.45.0/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +51 -0
- splent_cli-1.45.0/src/splent_cli/templates/product/product_base_template.html +200 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +6 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/archetype.py +18 -3
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/ci_workflow.py +17 -0
- splent_cli-1.45.0/src/splent_cli/utils/decorators.py +34 -0
- splent_cli-1.45.0/src/splent_cli/utils/deploy_freshness.py +183 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/forge.py +26 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/integrity.py +5 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/io_utils.py +86 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/menu_keys.py +3 -0
- splent_cli-1.45.0/src/splent_cli/utils/running_version.py +94 -0
- splent_cli-1.45.0/src/splent_cli/utils/uvl_diagnosis.py +190 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/uvl_params.py +20 -6
- {splent_cli-1.44.0 → splent_cli-1.45.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli.egg-info/SOURCES.txt +3 -0
- splent_cli-1.44.0/src/splent_cli/__init__.py +0 -15
- splent_cli-1.44.0/src/splent_cli/services/web_container.py +0 -69
- splent_cli-1.44.0/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -16
- splent_cli-1.44.0/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -18
- splent_cli-1.44.0/src/splent_cli/templates/product/product_base_template.html +0 -163
- splent_cli-1.44.0/src/splent_cli/utils/decorators.py +0 -17
- splent_cli-1.44.0/src/splent_cli/utils/running_version.py +0 -28
- {splent_cli-1.44.0 → splent_cli-1.45.0}/COPYING +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/COPYING.LESSER +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/README.md +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/setup.cfg +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/check/check_vendor.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_create.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_params.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/release/release_resume.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/image_registry.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/marketplace.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/own_commands.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/pypi_quota.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/release_dry_run.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/services/spl_store.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_services_service.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/licenses/COPYING +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_400.html +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_401.html +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_404.html +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_500.html +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_app.css +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_app.js +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_own.css +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/ci_services.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/db_client.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/feature_naming.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/feature_params.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/feature_requirements.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/feature_resolution.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/migration_lock.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/redact.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/refinement_facts.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/toml_edit.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.44.0 → splent_cli-1.45.0}/src/splent_cli.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""The SPLENT command-line interface."""
|
|
2
|
+
|
|
3
|
+
import os as _os
|
|
4
|
+
from types import MappingProxyType as _MappingProxyType
|
|
5
|
+
|
|
6
|
+
# What the operator actually exported, taken before anything reads a .env.
|
|
7
|
+
#
|
|
8
|
+
# This must stay the first thing the package does. cli.py runs load_dotenv(),
|
|
9
|
+
# and so do framework modules it imports (path_utils, configuration), which
|
|
10
|
+
# puts every workspace .env key into os.environ before any command runs. Read
|
|
11
|
+
# afterwards, the shell and the file look identical: env:show could never say
|
|
12
|
+
# "not loaded", env:list never showed "·", and the workspace .env passed for
|
|
13
|
+
# shell values and beat the product's docker/.env. Compare against this
|
|
14
|
+
# instead of os.environ whenever the question is "what did the shell export".
|
|
15
|
+
SHELL_ENV = _MappingProxyType(dict(_os.environ))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _bytecode_away_from_the_working_copies() -> None:
|
|
19
|
+
"""Write root's bytecode outside the workspace.
|
|
20
|
+
|
|
21
|
+
A ``docker exec`` into the CLI's container runs as root unless it says
|
|
22
|
+
``-u``, and root's __pycache__ directories in the working copies of the
|
|
23
|
+
CLI, the framework and the features were the ones the owner's next
|
|
24
|
+
release could not rewrite. Root's go under /tmp instead; everyone else's
|
|
25
|
+
stay where Python puts them. A PYTHONPYCACHEPREFIX already set wins, and
|
|
26
|
+
so does a workspace root itself owns. What the CLI runs (pytest, the
|
|
27
|
+
release build) inherits it through the environment.
|
|
28
|
+
"""
|
|
29
|
+
import sys
|
|
30
|
+
|
|
31
|
+
if sys.pycache_prefix or not hasattr(_os, "geteuid") or _os.geteuid() != 0:
|
|
32
|
+
return
|
|
33
|
+
try:
|
|
34
|
+
owner = _os.stat(_os.environ.get("WORKING_DIR") or "/workspace").st_uid
|
|
35
|
+
except OSError:
|
|
36
|
+
return
|
|
37
|
+
if owner != 0:
|
|
38
|
+
sys.pycache_prefix = "/tmp/splent-root-pycache"
|
|
39
|
+
_os.environ.setdefault("PYTHONPYCACHEPREFIX", sys.pycache_prefix)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
_bytecode_away_from_the_working_copies()
|
|
@@ -199,6 +199,12 @@ class SPLENTCLI(click.Group):
|
|
|
199
199
|
from splent_cli.utils.dynamic_imports import get_app
|
|
200
200
|
from splent_cli.utils.db_utils import check_db_connection
|
|
201
201
|
|
|
202
|
+
# The environment a command that boots the app builds it in when
|
|
203
|
+
# nothing says which (utils.decorators.boots_the_app).
|
|
204
|
+
boot_env = getattr(command, "boot_env", None)
|
|
205
|
+
if boot_env:
|
|
206
|
+
os.environ.setdefault("SPLENT_ENV", boot_env)
|
|
207
|
+
|
|
202
208
|
# For another product than the selection the CLI's container stays
|
|
203
209
|
# on the selection's network (compose.attach_self_to_network), so
|
|
204
210
|
# what reaches this product's services runs where they are.
|
|
@@ -6,16 +6,18 @@ import os
|
|
|
6
6
|
import sys
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
|
-
import importlib.metadata
|
|
10
9
|
|
|
11
10
|
from splent_cli.services import context
|
|
12
11
|
from splent_cli.services.release_gate import pypi_credentials
|
|
13
12
|
from splent_cli.utils import forge
|
|
13
|
+
from splent_cli.utils.running_version import installed_version
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def _pkg_version(name: str) -> str | None:
|
|
17
|
+
# The checkout's version for an editable install, as `splent version`
|
|
18
|
+
# reports it: pip's metadata keeps the version it was installed at.
|
|
17
19
|
try:
|
|
18
|
-
return
|
|
20
|
+
return installed_version(name)
|
|
19
21
|
except Exception:
|
|
20
22
|
return None
|
|
21
23
|
|
|
@@ -263,3 +263,9 @@ def run_wizard():
|
|
|
263
263
|
break
|
|
264
264
|
else:
|
|
265
265
|
click.secho("Invalid option. Try again.", fg="red")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# The group alone. The loader's fallback registers every command it finds in
|
|
269
|
+
# a module, and found the subcommands too, so `splent forge`, `splent pypi`
|
|
270
|
+
# and `splent github` existed as top-level copies of env:set's.
|
|
271
|
+
cli_command = env_set_group
|
|
@@ -3,6 +3,7 @@ import click
|
|
|
3
3
|
from splent_cli.services import context, compose
|
|
4
4
|
from splent_cli.utils.feature_utils import (
|
|
5
5
|
drop_feature_entries,
|
|
6
|
+
entry_matches_feature,
|
|
6
7
|
find_feature_entries,
|
|
7
8
|
hot_reinstall,
|
|
8
9
|
parse_feature_entry,
|
|
@@ -160,10 +161,35 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
160
161
|
|
|
161
162
|
attaching = sorted(declared) != sorted(wanted)
|
|
162
163
|
if attaching:
|
|
163
|
-
|
|
164
|
+
# A list that declares the feature keeps it where it is, so a new pin
|
|
165
|
+
# does not move the entry away from its neighbours and its comment
|
|
166
|
+
# to the end of the list (uvlhub's, after each release --attach).
|
|
167
|
+
# Only a list it is new to gets it at the end, and the lists it
|
|
168
|
+
# leaves lose it.
|
|
169
|
+
keep_in_place: dict[str, int] = {}
|
|
170
|
+
for key, entry in declared:
|
|
171
|
+
if key in target_keys:
|
|
172
|
+
at = read_feature_list(data, key).index(entry)
|
|
173
|
+
keep_in_place[key] = min(at, keep_in_place.get(key, at))
|
|
174
|
+
drop_feature_entries(
|
|
175
|
+
data,
|
|
176
|
+
feature_name,
|
|
177
|
+
namespace=namespace_fs,
|
|
178
|
+
keys=tuple(key for key, _ in declared if key not in keep_in_place),
|
|
179
|
+
)
|
|
164
180
|
for key in target_keys:
|
|
165
181
|
features = read_feature_list(data, key)
|
|
166
|
-
|
|
182
|
+
if key in keep_in_place:
|
|
183
|
+
features = [
|
|
184
|
+
entry
|
|
185
|
+
for entry in features
|
|
186
|
+
if not entry_matches_feature(
|
|
187
|
+
entry, feature_name, namespace=namespace_fs
|
|
188
|
+
)
|
|
189
|
+
]
|
|
190
|
+
features.insert(keep_in_place[key], full_name)
|
|
191
|
+
else:
|
|
192
|
+
features.append(full_name)
|
|
167
193
|
write_features_to_data(data, features, key=key)
|
|
168
194
|
|
|
169
195
|
# ── PyPI name, when it is not the feature's own ──────────────────
|
|
@@ -259,14 +259,22 @@ def _parse_pyproject_entry(entry: str) -> tuple[str, str, str | None]:
|
|
|
259
259
|
return ns, name, version or None
|
|
260
260
|
|
|
261
261
|
|
|
262
|
+
def _product_reader(product_dir: str, pyproject_path: str | None = None):
|
|
263
|
+
"""The reader of the product's pyproject.toml, or of PYPROJECT_PATH, a
|
|
264
|
+
candidate pyproject checked in its place (product:validate --pyproject)."""
|
|
265
|
+
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
266
|
+
|
|
267
|
+
if pyproject_path:
|
|
268
|
+
return PyprojectReader(pyproject_path)
|
|
269
|
+
return PyprojectReader.for_product(product_dir)
|
|
270
|
+
|
|
271
|
+
|
|
262
272
|
def _resolve_all_product_features(
|
|
263
|
-
product_dir: str, workspace: str
|
|
273
|
+
product_dir: str, workspace: str, pyproject_path: str | None = None
|
|
264
274
|
) -> list[tuple[str, Path]]:
|
|
265
275
|
"""Read all features from the product and resolve to (label, path)."""
|
|
266
|
-
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
267
|
-
|
|
268
276
|
try:
|
|
269
|
-
features_raw =
|
|
277
|
+
features_raw = _product_reader(product_dir, pyproject_path).features
|
|
270
278
|
except FileNotFoundError:
|
|
271
279
|
raise SystemExit("❌ pyproject.toml not found in product.")
|
|
272
280
|
|
|
@@ -435,7 +443,9 @@ def _contract_env(path: Path) -> str | None:
|
|
|
435
443
|
return data.get("tool", {}).get("splent", {}).get("contract", {}).get("env")
|
|
436
444
|
|
|
437
445
|
|
|
438
|
-
def env_scope_findings(
|
|
446
|
+
def env_scope_findings(
|
|
447
|
+
workspace: str, product_dir: str, pyproject_path: str | None = None
|
|
448
|
+
) -> list[dict]:
|
|
439
449
|
"""Features confined to one environment, in a list that installs them in
|
|
440
450
|
the other.
|
|
441
451
|
|
|
@@ -443,9 +453,7 @@ def env_scope_findings(workspace: str, product_dir: str) -> list[dict]:
|
|
|
443
453
|
list the production image installed them too, phpMyAdmin with autologin
|
|
444
454
|
to the database, and nothing said so.
|
|
445
455
|
"""
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
reader = PyprojectReader.for_product(product_dir)
|
|
456
|
+
reader = _product_reader(product_dir, pyproject_path)
|
|
449
457
|
lists = {
|
|
450
458
|
"features": reader.features,
|
|
451
459
|
"features_dev": reader.features_dev,
|
|
@@ -501,7 +509,9 @@ def _joins_the_shared_network(compose_file: Path) -> bool:
|
|
|
501
509
|
return "SPLENT_NETWORK" not in str(spec.get("name", ""))
|
|
502
510
|
|
|
503
511
|
|
|
504
|
-
def network_findings(
|
|
512
|
+
def network_findings(
|
|
513
|
+
workspace: str, product_dir: str, pyproject_path: str | None = None
|
|
514
|
+
) -> list[dict]:
|
|
505
515
|
"""Feature services the product cannot reach.
|
|
506
516
|
|
|
507
517
|
Each product has a network of its own, and a feature's services join it
|
|
@@ -509,9 +519,7 @@ def network_findings(workspace: str, product_dir: str) -> list[dict]:
|
|
|
509
519
|
instead, so the app could not send mail to Mailhog and phpMyAdmin could
|
|
510
520
|
not reach the database, and nothing said why.
|
|
511
521
|
"""
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
reader = PyprojectReader.for_product(product_dir)
|
|
522
|
+
reader = _product_reader(product_dir, pyproject_path)
|
|
515
523
|
findings = []
|
|
516
524
|
for entry in reader.features + reader.features_dev + reader.features_prod:
|
|
517
525
|
ns, name, version = _parse_pyproject_entry(entry)
|
|
@@ -537,14 +545,14 @@ def network_findings(workspace: str, product_dir: str) -> list[dict]:
|
|
|
537
545
|
return findings
|
|
538
546
|
|
|
539
547
|
|
|
540
|
-
def naming_findings(
|
|
548
|
+
def naming_findings(
|
|
549
|
+
workspace: str, product_dir: str, pyproject_path: str | None = None
|
|
550
|
+
) -> list[dict]:
|
|
541
551
|
"""Features named after a product (auth_uvlhub). A warning, so the debt
|
|
542
552
|
shows in every validation until the product drops them."""
|
|
543
|
-
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
544
|
-
|
|
545
553
|
from splent_cli.utils.feature_naming import product_suffix, product_suffix_message
|
|
546
554
|
|
|
547
|
-
reader =
|
|
555
|
+
reader = _product_reader(product_dir, pyproject_path)
|
|
548
556
|
findings = []
|
|
549
557
|
for entry in reader.features + reader.features_dev + reader.features_prod:
|
|
550
558
|
_, name, _ = _parse_pyproject_entry(entry)
|
|
@@ -688,16 +696,14 @@ def version_floor_findings(labeled_paths: list[tuple[str, Path]]) -> list[dict]:
|
|
|
688
696
|
|
|
689
697
|
|
|
690
698
|
def _environment_only_features(
|
|
691
|
-
product_dir: str, workspace: str, env: str | None
|
|
699
|
+
product_dir: str, workspace: str, env: str | None, pyproject_path: str | None = None
|
|
692
700
|
) -> list[tuple[str, Path]]:
|
|
693
701
|
"""The features ``features_<env>`` adds to the base list, resolved."""
|
|
694
|
-
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
695
|
-
|
|
696
702
|
env = context.normalize_env(env)
|
|
697
703
|
if not env:
|
|
698
704
|
return []
|
|
699
705
|
try:
|
|
700
|
-
reader =
|
|
706
|
+
reader = _product_reader(product_dir, pyproject_path)
|
|
701
707
|
base = set(reader.features)
|
|
702
708
|
extra = [e for e in reader.features_for_env(env) if e not in base]
|
|
703
709
|
except (FileNotFoundError, ValueError):
|
|
@@ -706,7 +712,10 @@ def _environment_only_features(
|
|
|
706
712
|
|
|
707
713
|
|
|
708
714
|
def run_all_product_check(
|
|
709
|
-
workspace: str,
|
|
715
|
+
workspace: str,
|
|
716
|
+
product_dir: str,
|
|
717
|
+
env: str | None = None,
|
|
718
|
+
pyproject_path: str | None = None,
|
|
710
719
|
) -> list[dict]:
|
|
711
720
|
"""Run the full product compatibility check. Returns findings list.
|
|
712
721
|
|
|
@@ -714,9 +723,13 @@ def run_all_product_check(
|
|
|
714
723
|
floors are checked across the features the product loads in ``env``
|
|
715
724
|
(SPLENT_ENV when None, ``dev`` when that is unset too), base plus
|
|
716
725
|
``features_dev`` or ``features_prod``, the selection phase 1 checks.
|
|
726
|
+
``pyproject_path`` names a candidate pyproject to read the features
|
|
727
|
+
from instead of the product's.
|
|
717
728
|
"""
|
|
718
729
|
try:
|
|
719
|
-
labeled_paths = _resolve_all_product_features(
|
|
730
|
+
labeled_paths = _resolve_all_product_features(
|
|
731
|
+
product_dir, workspace, pyproject_path
|
|
732
|
+
)
|
|
720
733
|
except SystemExit:
|
|
721
734
|
return []
|
|
722
735
|
if not labeled_paths:
|
|
@@ -725,7 +738,8 @@ def run_all_product_check(
|
|
|
725
738
|
env = os.getenv("SPLENT_ENV") or "dev"
|
|
726
739
|
labeled_contracts = [(lbl, _read_contract(path)) for lbl, path in labeled_paths]
|
|
727
740
|
findings = _analyse_all(labeled_contracts) + version_floor_findings(
|
|
728
|
-
labeled_paths
|
|
741
|
+
labeled_paths
|
|
742
|
+
+ _environment_only_features(product_dir, workspace, env, pyproject_path)
|
|
729
743
|
)
|
|
730
744
|
return sorted(findings, key=lambda f: SEVERITY_ORDER[f["severity"]])
|
|
731
745
|
|
|
@@ -21,6 +21,7 @@ from pathlib import Path
|
|
|
21
21
|
|
|
22
22
|
from splent_cli.services import context
|
|
23
23
|
from splent_cli.commands.feature.feature_release import (
|
|
24
|
+
_interactive,
|
|
24
25
|
infer_contract,
|
|
25
26
|
merge_preserved,
|
|
26
27
|
requirement_problems,
|
|
@@ -341,8 +342,16 @@ def _print_diff(current: dict, inferred: dict) -> bool:
|
|
|
341
342
|
is_flag=True,
|
|
342
343
|
help="Write the inferred contract to pyproject.toml (default: dry-run only).",
|
|
343
344
|
)
|
|
345
|
+
@click.option(
|
|
346
|
+
"--inject-config/--no-inject-config",
|
|
347
|
+
"inject_config",
|
|
348
|
+
default=None,
|
|
349
|
+
help="With --write, run feature:inject-config when config.py is missing or "
|
|
350
|
+
"lacks a variable, without asking. Without a terminal and without either "
|
|
351
|
+
"flag it is not run.",
|
|
352
|
+
)
|
|
344
353
|
@context.requires_product
|
|
345
|
-
def feature_contract(feature_ref, write):
|
|
354
|
+
def feature_contract(feature_ref, write, inject_config):
|
|
346
355
|
"""
|
|
347
356
|
Infer the feature contract from source code at any time during development.
|
|
348
357
|
|
|
@@ -362,6 +371,12 @@ def feature_contract(feature_ref, write):
|
|
|
362
371
|
which a product's config.py may set. The keys of Flask, the framework,
|
|
363
372
|
the extensions it sets up and other features are left out.
|
|
364
373
|
|
|
374
|
+
\b
|
|
375
|
+
When --write finds config.py missing, or missing a variable the feature
|
|
376
|
+
reads, it offers feature:inject-config. --inject-config runs it without
|
|
377
|
+
asking and --no-inject-config never does; without a terminal and
|
|
378
|
+
without either flag it is not run, and the command says how to run it.
|
|
379
|
+
|
|
365
380
|
\b
|
|
366
381
|
FEATURE_REF is a short name (notes), a package name
|
|
367
382
|
(splent_feature_notes), either one under a namespace (splent-io/notes),
|
|
@@ -482,7 +497,7 @@ def feature_contract(feature_ref, write):
|
|
|
482
497
|
click.secho(" ✅ Contract written to pyproject.toml.", fg="green")
|
|
483
498
|
|
|
484
499
|
# Check if config.py needs updating
|
|
485
|
-
_check_config_py(cache_path, ns, name, inferred)
|
|
500
|
+
_check_config_py(cache_path, ns, name, inferred, inject_config)
|
|
486
501
|
|
|
487
502
|
# And whether the settings it reads are declared where a product looks
|
|
488
503
|
_check_declared_config(cache_path, inferred)
|
|
@@ -637,8 +652,29 @@ def _report_params(feature_path, name):
|
|
|
637
652
|
)
|
|
638
653
|
|
|
639
654
|
|
|
640
|
-
def
|
|
641
|
-
"""
|
|
655
|
+
def _wants_inject_config(question: str, name: str, inject: bool | None) -> bool:
|
|
656
|
+
"""Whether to run feature:inject-config: the flag when one was given,
|
|
657
|
+
else the answer to QUESTION. Without a terminal nobody can answer, and
|
|
658
|
+
click.confirm aborted the whole command after the contract was written,
|
|
659
|
+
so it is not asked and not run."""
|
|
660
|
+
if inject is not None:
|
|
661
|
+
return inject
|
|
662
|
+
if not _interactive():
|
|
663
|
+
click.secho(
|
|
664
|
+
f" Not asked without a terminal. Run 'splent feature:inject-config "
|
|
665
|
+
f"{name}', or pass --inject-config.",
|
|
666
|
+
dim=True,
|
|
667
|
+
)
|
|
668
|
+
return False
|
|
669
|
+
return click.confirm(question, default=True)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def _check_config_py(feature_path, ns, name, inferred, inject=None):
|
|
673
|
+
"""Warn if config.py is missing or stale relative to the inferred contract.
|
|
674
|
+
|
|
675
|
+
``inject`` is --inject-config (True), --no-inject-config (False) or
|
|
676
|
+
neither (None), see :func:`_wants_inject_config`.
|
|
677
|
+
"""
|
|
642
678
|
from splent_cli.commands.feature.feature_inject_config import (
|
|
643
679
|
_is_regenerable,
|
|
644
680
|
read_config_env_vars,
|
|
@@ -660,8 +696,8 @@ def _check_config_py(feature_path, ns, name, inferred):
|
|
|
660
696
|
f" ⚠ config.py not found — {len(env_vars)} env var(s) detected but not injected into app.config.",
|
|
661
697
|
fg="yellow",
|
|
662
698
|
)
|
|
663
|
-
if
|
|
664
|
-
" Run feature:inject-config to generate it?",
|
|
699
|
+
if _wants_inject_config(
|
|
700
|
+
" Run feature:inject-config to generate it?", name, inject
|
|
665
701
|
):
|
|
666
702
|
from splent_cli.commands.feature.feature_inject_config import (
|
|
667
703
|
feature_inject_config,
|
|
@@ -691,7 +727,9 @@ def _check_config_py(feature_path, ns, name, inferred):
|
|
|
691
727
|
dim=True,
|
|
692
728
|
)
|
|
693
729
|
return
|
|
694
|
-
if
|
|
730
|
+
if _wants_inject_config(
|
|
731
|
+
" Run feature:inject-config to update it?", name, inject
|
|
732
|
+
):
|
|
695
733
|
from splent_cli.commands.feature.feature_inject_config import (
|
|
696
734
|
feature_inject_config,
|
|
697
735
|
)
|
|
@@ -196,6 +196,35 @@ def _extract_registered_services(init_path: Path) -> list[str]:
|
|
|
196
196
|
)
|
|
197
197
|
|
|
198
198
|
|
|
199
|
+
def _extract_registered_providers(init_path: Path) -> list[str]:
|
|
200
|
+
"""Classes handed to another feature's ``register_<kind>_provider(app, X)``.
|
|
201
|
+
|
|
202
|
+
dataset's register_deposition_provider and captcha's
|
|
203
|
+
register_captcha_provider put the class in the service locator for the
|
|
204
|
+
feature that calls them (zenodo, recaptcha), so it is a service that
|
|
205
|
+
feature provides, as one it registers with register_service is. Only a
|
|
206
|
+
class named in the call counts, not one built there.
|
|
207
|
+
"""
|
|
208
|
+
if not init_path.exists():
|
|
209
|
+
return []
|
|
210
|
+
try:
|
|
211
|
+
tree = ast.parse(init_path.read_text())
|
|
212
|
+
except (SyntaxError, OSError, UnicodeDecodeError):
|
|
213
|
+
return []
|
|
214
|
+
found: set[str] = set()
|
|
215
|
+
for node in ast.walk(tree):
|
|
216
|
+
if not isinstance(node, ast.Call) or len(node.args) < 2:
|
|
217
|
+
continue
|
|
218
|
+
name = _call_name(node).rsplit(".", 1)[-1]
|
|
219
|
+
provider = node.args[1]
|
|
220
|
+
if _PROVIDER_REGISTRATION.fullmatch(name) and isinstance(provider, ast.Name):
|
|
221
|
+
found.add(provider.id)
|
|
222
|
+
return sorted(found)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
_PROVIDER_REGISTRATION = re.compile(r"register_\w+_provider")
|
|
226
|
+
|
|
227
|
+
|
|
199
228
|
def _extract_templates(src_dir: Path) -> list[str]:
|
|
200
229
|
templates_dir = src_dir / "templates"
|
|
201
230
|
if not templates_dir.exists():
|
|
@@ -867,13 +896,16 @@ def _scan_dependencies(
|
|
|
867
896
|
except SyntaxError:
|
|
868
897
|
tree = None
|
|
869
898
|
if tree is not None:
|
|
899
|
+
# An import inside a try that carries on without the module is an
|
|
900
|
+
# optional dependency (_extract_guarded_imports), not a hard one.
|
|
901
|
+
guarded = _guarded_ranges(tree, _IMPORT_TOLERANT_HANDLERS)
|
|
870
902
|
imported: list[str] = []
|
|
871
903
|
for node in ast.walk(tree):
|
|
872
|
-
if isinstance(node, ast.Import):
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
904
|
+
if not isinstance(node, (ast.Import, ast.ImportFrom)):
|
|
905
|
+
continue
|
|
906
|
+
if _in_ranges(node.lineno, guarded):
|
|
907
|
+
continue
|
|
908
|
+
imported.extend(_imported_names(node))
|
|
877
909
|
else:
|
|
878
910
|
# Unparseable file: the line scan, on the code alone.
|
|
879
911
|
imported = [
|
|
@@ -899,6 +931,38 @@ def _scan_dependencies(
|
|
|
899
931
|
return sorted(required_features), sorted(env_vars)
|
|
900
932
|
|
|
901
933
|
|
|
934
|
+
def _imported_names(node) -> list[str]:
|
|
935
|
+
"""The dotted names an import statement mentions."""
|
|
936
|
+
if isinstance(node, ast.Import):
|
|
937
|
+
return [alias.name for alias in node.names]
|
|
938
|
+
return [node.module or "", *(alias.name for alias in node.names)]
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def _extract_guarded_imports(src_dir: Path, own_feature_name: str) -> set[str]:
|
|
942
|
+
"""Other features an import statement names only inside a tolerant try.
|
|
943
|
+
|
|
944
|
+
``try: from splent_io.splent_feature_x import y`` with a handler that
|
|
945
|
+
catches ``ImportError`` (``ModuleNotFoundError``, ``Exception``, a bare
|
|
946
|
+
``except``) and carries on is the static spelling of the optional
|
|
947
|
+
import :func:`_extract_dynamic_imports` reads. The same statement
|
|
948
|
+
outside such a try fails without the feature, so it stays a hard
|
|
949
|
+
requirement there (:func:`_scan_dependencies`).
|
|
950
|
+
"""
|
|
951
|
+
found: set[str] = set()
|
|
952
|
+
for _, tree in _parsed_sources(src_dir):
|
|
953
|
+
guarded = _guarded_ranges(tree, _IMPORT_TOLERANT_HANDLERS)
|
|
954
|
+
for node in ast.walk(tree):
|
|
955
|
+
if not isinstance(node, (ast.Import, ast.ImportFrom)):
|
|
956
|
+
continue
|
|
957
|
+
if not _in_ranges(node.lineno, guarded):
|
|
958
|
+
continue
|
|
959
|
+
for item in _imported_names(node):
|
|
960
|
+
for short in re.findall(r"splent_feature_(\w+)", item):
|
|
961
|
+
if f"splent_feature_{short}" != own_feature_name:
|
|
962
|
+
found.add(short)
|
|
963
|
+
return found
|
|
964
|
+
|
|
965
|
+
|
|
902
966
|
def _extract_service_proxies(src_dir: Path) -> tuple[set[str], set[str]]:
|
|
903
967
|
"""Service class names looked up via ``service_proxy("XService")``,
|
|
904
968
|
split into ``(hard, soft)`` dependencies.
|
|
@@ -1224,6 +1288,8 @@ def build_service_map(workspace: str) -> dict[str, str]:
|
|
|
1224
1288
|
for init_path in feature_dir.glob(f"src/*/{dir_name}/__init__.py"):
|
|
1225
1289
|
for svc in _extract_registered_services(init_path):
|
|
1226
1290
|
_add(svc, short)
|
|
1291
|
+
for svc in _extract_registered_providers(init_path):
|
|
1292
|
+
_add(svc, short)
|
|
1227
1293
|
|
|
1228
1294
|
try:
|
|
1229
1295
|
data = tomllib.loads(py_path.read_text())
|
|
@@ -1420,6 +1486,7 @@ def infer_contract(
|
|
|
1420
1486
|
services = sorted(
|
|
1421
1487
|
set(_extract_services(src_dir / "services.py"))
|
|
1422
1488
|
| set(_extract_registered_services(src_dir / "__init__.py"))
|
|
1489
|
+
| set(_extract_registered_providers(src_dir / "__init__.py"))
|
|
1423
1490
|
)
|
|
1424
1491
|
templates = _extract_templates(src_dir)
|
|
1425
1492
|
template_hook_slots = _extract_template_hook_slots(src_dir)
|
|
@@ -1495,6 +1562,7 @@ def infer_contract(
|
|
|
1495
1562
|
slot_map = _default_map(build_slot_map)
|
|
1496
1563
|
candidates = {service_map.get(svc) for svc in soft_proxies - own_services}
|
|
1497
1564
|
candidates |= soft_imports
|
|
1565
|
+
candidates |= _extract_guarded_imports(src_dir, feature_name)
|
|
1498
1566
|
candidates |= {
|
|
1499
1567
|
signal_map.get(name)
|
|
1500
1568
|
for name in _extract_calls_named(src_dir, "connect_signal")
|
|
@@ -1581,6 +1649,87 @@ def _sections_not_owned_by_the_contract(tail: str) -> str:
|
|
|
1581
1649
|
return "\n\n".join(kept)
|
|
1582
1650
|
|
|
1583
1651
|
|
|
1652
|
+
_KEY_LINE = re.compile(r"^\s*([A-Za-z0-9_-]+|\"[^\"]*\")\s*=(.*)$")
|
|
1653
|
+
|
|
1654
|
+
|
|
1655
|
+
def _inline_comment(rest: str) -> str:
|
|
1656
|
+
"""The comment after a value written on one line, ``""`` without one.
|
|
1657
|
+
|
|
1658
|
+
``rest`` is what follows the ``=``. A ``#`` inside a string is not a
|
|
1659
|
+
comment, so the cut is the first one before which the value parses.
|
|
1660
|
+
"""
|
|
1661
|
+
for index, char in enumerate(rest):
|
|
1662
|
+
if char != "#":
|
|
1663
|
+
continue
|
|
1664
|
+
value = rest[:index].strip()
|
|
1665
|
+
if not value:
|
|
1666
|
+
return ""
|
|
1667
|
+
try:
|
|
1668
|
+
tomllib.loads(f"v = {value}")
|
|
1669
|
+
except tomllib.TOMLDecodeError:
|
|
1670
|
+
continue
|
|
1671
|
+
return rest[index:].strip()
|
|
1672
|
+
return ""
|
|
1673
|
+
|
|
1674
|
+
|
|
1675
|
+
def _contract_comments(region: str) -> dict:
|
|
1676
|
+
"""The comments a maintainer wrote inside the contract's tables.
|
|
1677
|
+
|
|
1678
|
+
``{(table, key): (lines above, inline comment)}``, where ``key`` is None
|
|
1679
|
+
for the comments above a subtable's header. The contract is written
|
|
1680
|
+
again from the source, so the comments go with what they are about: the
|
|
1681
|
+
key under or after them in the same table. The banner above
|
|
1682
|
+
``[tool.splent.contract]`` is the writer's own and is written anyway.
|
|
1683
|
+
"""
|
|
1684
|
+
comments: dict = {}
|
|
1685
|
+
table = None
|
|
1686
|
+
above: list[str] = []
|
|
1687
|
+
for line in region.splitlines():
|
|
1688
|
+
stripped = line.strip()
|
|
1689
|
+
if stripped.startswith("["):
|
|
1690
|
+
header = stripped.split("#", 1)[0].strip()
|
|
1691
|
+
if not header.startswith("[tool.splent.contract"):
|
|
1692
|
+
break
|
|
1693
|
+
if above and header != "[tool.splent.contract]":
|
|
1694
|
+
comments[(header, None)] = (above, "")
|
|
1695
|
+
table, above = header, []
|
|
1696
|
+
elif stripped.startswith("#"):
|
|
1697
|
+
above.append(stripped)
|
|
1698
|
+
elif stripped:
|
|
1699
|
+
match = _KEY_LINE.match(line)
|
|
1700
|
+
if match and table is not None:
|
|
1701
|
+
inline = _inline_comment(match.group(2))
|
|
1702
|
+
if above or inline:
|
|
1703
|
+
comments[(table, match.group(1))] = (above, inline)
|
|
1704
|
+
above = []
|
|
1705
|
+
return comments
|
|
1706
|
+
|
|
1707
|
+
|
|
1708
|
+
def _with_comments(block: str, comments: dict) -> str:
|
|
1709
|
+
"""BLOCK, the contract as written from the source, with COMMENTS back
|
|
1710
|
+
above and after the keys and subtables they were written for."""
|
|
1711
|
+
if not comments:
|
|
1712
|
+
return block
|
|
1713
|
+
out: list[str] = []
|
|
1714
|
+
table = None
|
|
1715
|
+
for line in block.split("\n"):
|
|
1716
|
+
stripped = line.strip()
|
|
1717
|
+
if stripped.startswith("["):
|
|
1718
|
+
table = stripped
|
|
1719
|
+
above, _ = comments.get((table, None), ([], ""))
|
|
1720
|
+
out.extend(above)
|
|
1721
|
+
out.append(line)
|
|
1722
|
+
continue
|
|
1723
|
+
match = _KEY_LINE.match(line)
|
|
1724
|
+
if match and (table, match.group(1)) in comments:
|
|
1725
|
+
above, inline = comments[(table, match.group(1))]
|
|
1726
|
+
out.extend(above)
|
|
1727
|
+
out.append(f"{line} {inline}" if inline else line)
|
|
1728
|
+
continue
|
|
1729
|
+
out.append(line)
|
|
1730
|
+
return "\n".join(out)
|
|
1731
|
+
|
|
1732
|
+
|
|
1584
1733
|
def _toml_value(value) -> str:
|
|
1585
1734
|
"""One value as TOML, escaped: lists inline, everything else via tomli_w.
|
|
1586
1735
|
|
|
@@ -1726,7 +1875,9 @@ def write_contract(pyproject_path: str, contract: dict, feature_name: str) -> No
|
|
|
1726
1875
|
# [tool.splent.config] vanished from five features the moment they were
|
|
1727
1876
|
# released, silently taking every declared default with them.
|
|
1728
1877
|
refinement_block = ""
|
|
1878
|
+
comments: dict = {}
|
|
1729
1879
|
if match:
|
|
1880
|
+
comments = _contract_comments(text[match.start() :])
|
|
1730
1881
|
preserved = _sections_not_owned_by_the_contract(text[match.start() :])
|
|
1731
1882
|
if preserved:
|
|
1732
1883
|
refinement_block = "\n" + preserved + "\n"
|
|
@@ -1828,7 +1979,7 @@ def write_contract(pyproject_path: str, contract: dict, feature_name: str) -> No
|
|
|
1828
1979
|
f"services = {_toml_value(dc.get('depends_on_services', []))}\n"
|
|
1829
1980
|
)
|
|
1830
1981
|
|
|
1831
|
-
path.write_text(text + contract_block + refinement_block)
|
|
1982
|
+
path.write_text(text + _with_comments(contract_block, comments) + refinement_block)
|
|
1832
1983
|
|
|
1833
1984
|
|
|
1834
1985
|
def _commands_help_block(commands_help: dict) -> str:
|