splent-cli 1.49.0__tar.gz → 1.50.1__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.49.0/src/splent_cli.egg-info → splent_cli-1.50.1}/PKG-INFO +2 -2
- {splent_cli-1.49.0 → splent_cli-1.50.1}/pyproject.toml +2 -21
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_deps.py +43 -3
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_infra.py +1 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/clear/clear_uploads.py +14 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_compat.py +1 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_compile.py +28 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_install.py +1 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_release.py +52 -16
- splent_cli-1.50.1/src/splent_cli/commands/feature/feature_security.py +114 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_test.py +1 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_unlock.py +12 -2
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_xray.py +1 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_build.py +1 -14
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_config.py +1 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_env.py +6 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_restart.py +43 -14
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_routes.py +1 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_test.py +1 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_up.py +6 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_validate.py +112 -11
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_configurations.py +20 -13
- splent_cli-1.50.1/src/splent_cli/commands/storage/storage_migrate.py +42 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/release_dry_run.py +21 -0
- splent_cli-1.50.1/src/splent_cli/services/security_scan.py +1109 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/spl_store.py +0 -4
- splent_cli-1.50.1/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +61 -0
- splent_cli-1.50.1/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +61 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_.env.prod.example.j2 +3 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +3 -2
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +8 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +4 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_400.html +0 -2
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_401.html +0 -2
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_404.html +0 -2
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_500.html +0 -2
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_base_template.html +17 -5
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +8 -3
- splent_cli-1.50.1/src/splent_cli/templates/product/product_pyproject.toml.j2 +69 -0
- splent_cli-1.50.1/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/dynamic_imports.py +69 -1
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/feature_utils.py +10 -5
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/frontend_check.py +17 -3
- {splent_cli-1.49.0 → splent_cli-1.50.1/src/splent_cli.egg-info}/PKG-INFO +2 -2
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli.egg-info/SOURCES.txt +4 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli.egg-info/requires.txt +1 -1
- splent_cli-1.49.0/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -100
- splent_cli-1.49.0/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -86
- splent_cli-1.49.0/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -93
- {splent_cli-1.49.0 → splent_cli-1.50.1}/COPYING +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/COPYING.LESSER +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/README.md +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/setup.cfg +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/check/check_vendor.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_contract.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_frontend_init.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_npm.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_refine.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_create.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_derive.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_frontend_audit.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_params.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/release/release_resume.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.49.0/src/splent_cli/services → splent_cli-1.50.1/src/splent_cli/commands/storage}/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.49.0/src/splent_cli/utils → splent_cli-1.50.1/src/splent_cli/services}/__init__.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/deploy_bundle.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/editable_refresh.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/frontend.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/image_registry.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/marketplace.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/neutral_app.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/own_commands.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/preflight.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/pypi_quota.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/services/web_container.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_frontend.npmrc.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_frontend_scripts.js.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_services_service.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/licenses/COPYING +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_app.css +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_app.js +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_dropzone.css +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_favicon.svg +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_own.css +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/toolchain/Dockerfile.frontend +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/toolchain/frontend/package-lock.json +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/toolchain/frontend/package.json +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/toolchain/frontend/splent/build.js +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/toolchain/frontend/splent/legacy.js +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/toolchain/frontend/splent/lint.js +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/toolchain/frontend/splent/plugin.js +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/toolchain/frontend/splent/preset.js +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/toolchain/frontend/splent/singletons.json +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/ci_services.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/ci_workflow.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/db_client.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/deploy_freshness.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/feature_naming.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/feature_params.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/feature_requirements.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/feature_resolution.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/forge.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/frontend_licenses.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/menu_keys.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/migration_lock.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/npm_semver.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/redact.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/refinement_facts.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/running_version.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/toml_edit.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/upgrade_plan.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/uvl_diagnosis.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli/utils/uvl_params.py +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.49.0 → splent_cli-1.50.1}/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.50.1
|
|
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
|
License-Expression: LGPL-3.0-or-later
|
|
@@ -16,7 +16,7 @@ Requires-Dist: packaging>=24.0
|
|
|
16
16
|
Requires-Dist: python-dotenv>=1.0
|
|
17
17
|
Requires-Dist: pyyaml>=6.0
|
|
18
18
|
Requires-Dist: requests>=2.31.0
|
|
19
|
-
Requires-Dist: splent_framework<2,>=1.
|
|
19
|
+
Requires-Dist: splent_framework<2,>=1.30.0
|
|
20
20
|
Requires-Dist: tomli_w>=1.0.0
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
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.50.1"
|
|
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"
|
|
@@ -12,10 +12,6 @@ authors = [{ name = "DiversoLab", email = "diversolab@us.es" }]
|
|
|
12
12
|
license = "LGPL-3.0-or-later"
|
|
13
13
|
license-files = ["COPYING", "COPYING.LESSER"]
|
|
14
14
|
|
|
15
|
-
# Everything the CLI imports at module level must be listed here (or come
|
|
16
|
-
# with splent_framework, see tests/unit/test_declared_dependencies.py):
|
|
17
|
-
# startup imports every command module, so one undeclared import breaks
|
|
18
|
-
# `splent --help` on a clean `pip install splent_cli`.
|
|
19
15
|
dependencies = [
|
|
20
16
|
"click==8.1.8",
|
|
21
17
|
"jinja2>=3.1",
|
|
@@ -23,15 +19,7 @@ dependencies = [
|
|
|
23
19
|
"python-dotenv>=1.0",
|
|
24
20
|
"pyyaml>=6.0",
|
|
25
21
|
"requests>=2.31.0",
|
|
26
|
-
|
|
27
|
-
# app factory) and only works with the same major version of it. 1.22.0
|
|
28
|
-
# brings splent_framework.params, the [tool.splent.config_schema] reader
|
|
29
|
-
# product:validate, product:env and product:build share with the boot;
|
|
30
|
-
# 1.18.0 resolves a feature to the copy feature:test names
|
|
31
|
-
# (SPLENT_FEATURE_SOURCES) and brings the production defaults the deploy
|
|
32
|
-
# templates explain; 1.17.0 the refinement rules product:validate shares
|
|
33
|
-
# with the boot.
|
|
34
|
-
"splent_framework>=1.22.0,<2",
|
|
22
|
+
"splent_framework>=1.30.0,<2",
|
|
35
23
|
"tomli_w>=1.0.0",
|
|
36
24
|
]
|
|
37
25
|
|
|
@@ -76,10 +64,6 @@ package-dir = { "" = "src" }
|
|
|
76
64
|
[tool.setuptools.packages.find]
|
|
77
65
|
where = ["src"]
|
|
78
66
|
|
|
79
|
-
# The templates product:create, feature:create and the rest render. They are
|
|
80
|
-
# data, not modules, so without this a wheel from PyPI shipped none of them.
|
|
81
|
-
# The build context of the frontend toolchain image too, so a CLI installed
|
|
82
|
-
# from PyPI can build the image a feature with frontend/ builds in.
|
|
83
67
|
[tool.setuptools.package-data]
|
|
84
68
|
splent_cli = ["templates/**/*", "toolchain/**/*"]
|
|
85
69
|
|
|
@@ -87,9 +71,6 @@ splent_cli = ["templates/**/*", "toolchain/**/*"]
|
|
|
87
71
|
testpaths = ["tests"]
|
|
88
72
|
pythonpath = ["src"]
|
|
89
73
|
|
|
90
|
-
# Canonical formatter/linter is Ruff (`splent lint` runs `ruff check` +
|
|
91
|
-
# `ruff format`). Ruff's default line length (88) is the project standard;
|
|
92
|
-
# set explicitly here so it is unambiguous.
|
|
93
74
|
[tool.ruff]
|
|
94
75
|
line-length = 88
|
|
95
76
|
|
|
@@ -83,6 +83,48 @@ def _parse_uvl_deps(uvl_path: str) -> tuple[dict[str, str], dict[str, set[str]]]
|
|
|
83
83
|
# ---------------------------------------------------------------------------
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def _bare_imports(source: str) -> set[str]:
|
|
87
|
+
"""The features an import statement of SOURCE names outside a guard.
|
|
88
|
+
|
|
89
|
+
An import under a try whose handler tolerates ImportError, or under an
|
|
90
|
+
if that asks whether the product has the feature, is how a feature
|
|
91
|
+
reaches an optional one (feature:release reads it so for the contract),
|
|
92
|
+
and the product does without it. A source that does not parse is read
|
|
93
|
+
as text, every import counting.
|
|
94
|
+
"""
|
|
95
|
+
import ast
|
|
96
|
+
|
|
97
|
+
from splent_cli.commands.feature.feature_release import (
|
|
98
|
+
_IMPORT_TOLERANT_HANDLERS,
|
|
99
|
+
_guarded_ranges,
|
|
100
|
+
_imported_names,
|
|
101
|
+
_in_ranges,
|
|
102
|
+
_module_string_constants,
|
|
103
|
+
_presence_guards,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
tree = ast.parse(source)
|
|
108
|
+
except (SyntaxError, ValueError):
|
|
109
|
+
return set(
|
|
110
|
+
re.findall(r"(?:from|import)\s+splent_io\.(splent_feature_\w+)", source)
|
|
111
|
+
)
|
|
112
|
+
guarded = _guarded_ranges(tree, _IMPORT_TOLERANT_HANDLERS)
|
|
113
|
+
present = _presence_guards(tree, _module_string_constants(tree))
|
|
114
|
+
found = set()
|
|
115
|
+
for node in ast.walk(tree):
|
|
116
|
+
if not isinstance(node, (ast.Import, ast.ImportFrom)):
|
|
117
|
+
continue
|
|
118
|
+
for item in _imported_names(node):
|
|
119
|
+
for short in re.findall(r"^splent_io\.splent_feature_(\w+?)(?:\.|$)", item):
|
|
120
|
+
if _in_ranges(node.lineno, guarded) or _in_ranges(
|
|
121
|
+
node.lineno, present.get(short, ())
|
|
122
|
+
):
|
|
123
|
+
continue
|
|
124
|
+
found.add(f"splent_feature_{short}")
|
|
125
|
+
return found
|
|
126
|
+
|
|
127
|
+
|
|
86
128
|
def _scan_feature_imports(
|
|
87
129
|
feature_path: str, feature_name: str, all_packages: set[str]
|
|
88
130
|
) -> tuple[set[str], set[str]]:
|
|
@@ -130,9 +172,7 @@ def _scan_feature_imports(
|
|
|
130
172
|
except (OSError, PermissionError):
|
|
131
173
|
continue
|
|
132
174
|
|
|
133
|
-
for match in
|
|
134
|
-
r"(?:from|import)\s+splent_io\.(splent_feature_\w+)", content
|
|
135
|
-
):
|
|
175
|
+
for match in _bare_imports(content):
|
|
136
176
|
if match != feature_name and match in all_packages:
|
|
137
177
|
python_imports.add(match)
|
|
138
178
|
|
|
@@ -366,7 +366,7 @@ def check_infra():
|
|
|
366
366
|
|
|
367
367
|
data = load_toml(pyproject_path, what="pyproject.toml")
|
|
368
368
|
|
|
369
|
-
env =
|
|
369
|
+
env = context.resolve_env()
|
|
370
370
|
features = read_features_from_data(data, env)
|
|
371
371
|
|
|
372
372
|
# Every compose file is read through the product's merged .env, the same one
|
|
@@ -32,6 +32,13 @@ _SYSTEM_DIRS = {
|
|
|
32
32
|
"/tmp",
|
|
33
33
|
"/usr",
|
|
34
34
|
"/var",
|
|
35
|
+
# macOS: its own top level, and /private, where /etc, /tmp and /var lead.
|
|
36
|
+
"/Applications",
|
|
37
|
+
"/Library",
|
|
38
|
+
"/System",
|
|
39
|
+
"/Users",
|
|
40
|
+
"/Volumes",
|
|
41
|
+
"/private",
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
# What a directory of code has at its top and a directory of uploads never
|
|
@@ -77,6 +84,12 @@ def _configured_dir(app) -> str:
|
|
|
77
84
|
return os.getenv("PROTECTED_UPLOADS_DIR", "").strip()
|
|
78
85
|
|
|
79
86
|
|
|
87
|
+
def _system_dirs() -> set:
|
|
88
|
+
"""The system directories, and where each leads: on macOS /etc is a link
|
|
89
|
+
to /private/etc, which the realpath of a configured /etc gives."""
|
|
90
|
+
return _SYSTEM_DIRS | {os.path.realpath(path) for path in _SYSTEM_DIRS}
|
|
91
|
+
|
|
92
|
+
|
|
80
93
|
def _refuse(path: str, real: str, why: str):
|
|
81
94
|
shown = path if path == real else f"{path} (resolves to {real})"
|
|
82
95
|
raise UploadsDirError(f"{shown} {why}. Nothing was deleted.")
|
|
@@ -124,7 +137,7 @@ def resolve_uploads_dir(app=None) -> str:
|
|
|
124
137
|
)
|
|
125
138
|
|
|
126
139
|
real = os.path.realpath(uploads_dir)
|
|
127
|
-
if real == os.sep or real in
|
|
140
|
+
if real == os.sep or real in _system_dirs():
|
|
128
141
|
_refuse(uploads_dir, real, "is a system directory")
|
|
129
142
|
if real == os.path.realpath(os.path.expanduser("~")):
|
|
130
143
|
_refuse(uploads_dir, real, "is the home directory")
|
|
@@ -735,7 +735,7 @@ def run_all_product_check(
|
|
|
735
735
|
if not labeled_paths:
|
|
736
736
|
return []
|
|
737
737
|
if env is None:
|
|
738
|
-
env =
|
|
738
|
+
env = context.resolve_env()
|
|
739
739
|
labeled_contracts = [(lbl, _read_contract(path)) for lbl, path in labeled_paths]
|
|
740
740
|
findings = _analyse_all(labeled_contracts) + version_floor_findings(
|
|
741
741
|
labeled_paths
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
+
import hashlib
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
4
5
|
import posixpath
|
|
@@ -553,6 +554,31 @@ def _find_webpack(workspace, product, org_safe, base_name, version):
|
|
|
553
554
|
return None
|
|
554
555
|
|
|
555
556
|
|
|
557
|
+
def _one_watcher(webpack_file: str, product_root: str, shell_cmd: str) -> str:
|
|
558
|
+
"""The shell line that watches ``webpack_file`` and stops its watcher
|
|
559
|
+
before, where webpack runs.
|
|
560
|
+
|
|
561
|
+
Each start of a product in development (product:run --dev) compiles
|
|
562
|
+
with --watch again, and a watcher used to outlive the one that started
|
|
563
|
+
it: eight starts left eight generations of npm, sh and webpack, 2.4 GB
|
|
564
|
+
of a 7.8 GB Docker VM. The watcher now runs in a session of its own
|
|
565
|
+
(setsid), whose id it leaves in a file named after its bundle, and the
|
|
566
|
+
next start ends that whole group first, if it is still a webpack one.
|
|
567
|
+
"""
|
|
568
|
+
folder = "/tmp/splent-watch-$(id -u)"
|
|
569
|
+
name = hashlib.sha1(webpack_file.encode()).hexdigest()[:16]
|
|
570
|
+
pidfile = f"{folder}/{name}.pid"
|
|
571
|
+
inner = f'echo $$ > "{pidfile}"; exec {shell_cmd}'
|
|
572
|
+
return (
|
|
573
|
+
f'mkdir -p "{folder}"; '
|
|
574
|
+
f'if [ -f "{pidfile}" ]; then '
|
|
575
|
+
f'old="$(cat "{pidfile}")"; '
|
|
576
|
+
f'if grep -qa webpack "/proc/$old/cmdline" 2>/dev/null; then '
|
|
577
|
+
f'kill -TERM -- "-$old" 2>/dev/null || true; fi; fi; '
|
|
578
|
+
f"cd {shlex.quote(product_root)} && exec setsid bash -c {shlex.quote(inner)}"
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
|
|
556
582
|
def _compile_in_container(
|
|
557
583
|
container_id,
|
|
558
584
|
feature,
|
|
@@ -645,6 +671,8 @@ def _compile_in_container(
|
|
|
645
671
|
|
|
646
672
|
shell_cmd = " ".join(shlex.quote(p) for p in cmd_parts)
|
|
647
673
|
cd_cmd = f"cd {shlex.quote(product_root)} && {shell_cmd}"
|
|
674
|
+
if watch and not production:
|
|
675
|
+
cd_cmd = _one_watcher(webpack_file, product_root, shell_cmd)
|
|
648
676
|
|
|
649
677
|
# Inside a container: run webpack directly.
|
|
650
678
|
# From the host: run via `docker exec` so webpack uses the container's
|
|
@@ -230,7 +230,7 @@ def feature_install(feature_identifier, env_scope, mode, version):
|
|
|
230
230
|
"""
|
|
231
231
|
product = context.require_app()
|
|
232
232
|
workspace = str(context.workspace())
|
|
233
|
-
env_name =
|
|
233
|
+
env_name = context.resolve_env()
|
|
234
234
|
|
|
235
235
|
# ── Parse identifier ──────────────────────────────────────────────
|
|
236
236
|
namespace, namespace_github, namespace_fs, feature_name = (
|
|
@@ -1505,8 +1505,14 @@ def _local_strings(function, names: dict[str, str]) -> dict[str, str]:
|
|
|
1505
1505
|
return found
|
|
1506
1506
|
|
|
1507
1507
|
|
|
1508
|
+
#: The framework's lookups of a module whose feature the product may not have
|
|
1509
|
+
#: (splent_framework.optional): what they name is always an optional feature.
|
|
1510
|
+
_OPTIONAL_LOOKUPS = ("optional_module", "optional_attr")
|
|
1511
|
+
|
|
1512
|
+
|
|
1508
1513
|
def _import_module_calls(tree, constants: dict[str, str]):
|
|
1509
1514
|
"""``(call, names)`` for every ``importlib.import_module`` call of TREE,
|
|
1515
|
+
and every call of the framework's ``optional_module``/``optional_attr``,
|
|
1510
1516
|
NAMES being what its module may be spelled through: the module's string
|
|
1511
1517
|
constants, and the names the function around the call binds."""
|
|
1512
1518
|
|
|
@@ -1515,9 +1521,9 @@ def _import_module_calls(tree, constants: dict[str, str]):
|
|
|
1515
1521
|
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
1516
1522
|
yield from visit(child, {**names, **_local_strings(child, names)})
|
|
1517
1523
|
continue
|
|
1518
|
-
if isinstance(child, ast.Call) and
|
|
1519
|
-
"import_module",
|
|
1520
|
-
"
|
|
1524
|
+
if isinstance(child, ast.Call) and (
|
|
1525
|
+
_call_name(child) in ("import_module", "importlib.import_module")
|
|
1526
|
+
or _call_name(child).rsplit(".", 1)[-1] in _OPTIONAL_LOOKUPS
|
|
1521
1527
|
):
|
|
1522
1528
|
yield child, names
|
|
1523
1529
|
yield from visit(child, names)
|
|
@@ -1583,8 +1589,9 @@ def _presence_guards(
|
|
|
1583
1589
|
def _extract_dynamic_imports(
|
|
1584
1590
|
src_dir: Path, own_feature_name: str
|
|
1585
1591
|
) -> tuple[set[str], set[str]]:
|
|
1586
|
-
"""Other features imported by name with ``importlib.import_module``,
|
|
1587
|
-
|
|
1592
|
+
"""Other features imported by name with ``importlib.import_module``, or
|
|
1593
|
+
looked up with the framework's ``optional_module``/``optional_attr``
|
|
1594
|
+
(always soft), split into ``(hard, soft)`` short names.
|
|
1588
1595
|
|
|
1589
1596
|
A feature that wants another one only when the product has it imports
|
|
1590
1597
|
it by name inside a ``try`` whose handler catches ``ImportError`` (or
|
|
@@ -1610,9 +1617,12 @@ def _extract_dynamic_imports(
|
|
|
1610
1617
|
module = _spelled_module(node.args[0], names) if node.args else None
|
|
1611
1618
|
if not module:
|
|
1612
1619
|
continue
|
|
1620
|
+
looked_up = _call_name(node).rsplit(".", 1)[-1] in _OPTIONAL_LOOKUPS
|
|
1613
1621
|
for short in _named_features(module, own_feature_name):
|
|
1614
|
-
optional =
|
|
1615
|
-
|
|
1622
|
+
optional = (
|
|
1623
|
+
looked_up
|
|
1624
|
+
or _in_ranges(node.lineno, ranges)
|
|
1625
|
+
or _in_ranges(node.lineno, present.get(short, ()))
|
|
1616
1626
|
)
|
|
1617
1627
|
(soft if optional else hard).add(short)
|
|
1618
1628
|
return hard, soft - hard
|
|
@@ -1890,6 +1900,11 @@ def infer_contract(
|
|
|
1890
1900
|
docker = _extract_docker(feature_root)
|
|
1891
1901
|
docker_contract = _extract_docker_contract(feature_root)
|
|
1892
1902
|
req_features, env_vars = _scan_dependencies(src_dir, feature_name)
|
|
1903
|
+
# A key declared in [tool.splent.config_schema] is read from the
|
|
1904
|
+
# environment by the framework on the feature's behalf, with no
|
|
1905
|
+
# os.getenv left in its code to find: it is as much an environment
|
|
1906
|
+
# variable of the feature as one it reads itself.
|
|
1907
|
+
env_vars = sorted(set(env_vars) | declared_param_keys(feature_root))
|
|
1893
1908
|
# And the settings it reads from app.config, which are as much its
|
|
1894
1909
|
# parameters as what it reads from the environment, but are not
|
|
1895
1910
|
# environment variables: check:product requires every env_var, and a
|
|
@@ -2088,8 +2103,8 @@ def _contract_comments(region: str) -> dict:
|
|
|
2088
2103
|
``{(table, key): (lines above, inline comment)}``, where ``key`` is None
|
|
2089
2104
|
for the comments above a subtable's header. The contract is written
|
|
2090
2105
|
again from the source, so the comments go with what they are about: the
|
|
2091
|
-
key under or after them in the same table. The banner
|
|
2092
|
-
``[tool.splent.contract]`` is
|
|
2106
|
+
key under or after them in the same table. The banner earlier writers
|
|
2107
|
+
put above ``[tool.splent.contract]`` is not kept.
|
|
2093
2108
|
"""
|
|
2094
2109
|
comments: dict = {}
|
|
2095
2110
|
table = None
|
|
@@ -2333,13 +2348,6 @@ def write_contract(pyproject_path: str, contract: dict, feature_name: str) -> No
|
|
|
2333
2348
|
|
|
2334
2349
|
contract_block = (
|
|
2335
2350
|
"\n\n"
|
|
2336
|
-
"# ── Feature Contract (auto-generated) ────────────────────────────────────────\n"
|
|
2337
|
-
"# Run `splent feature:contract --write` to refresh it from the source.\n"
|
|
2338
|
-
"# A refresh keeps description, category, tags, env,\n"
|
|
2339
|
-
"# requires.features_manual (dependencies invisible to static analysis),\n"
|
|
2340
|
-
"# requires.features_optional (what the scan infers is added to it)\n"
|
|
2341
|
-
"# and provides.routes_manual (routes built from configuration at\n"
|
|
2342
|
-
"# registration time, which have no literal path to find in source).\n"
|
|
2343
2351
|
"[tool.splent.contract]\n"
|
|
2344
2352
|
f"description = {_toml_value(existing_description)}\n"
|
|
2345
2353
|
+ (
|
|
@@ -2954,6 +2962,9 @@ def feature_release(
|
|
|
2954
2962
|
_refuse_invalid_requirements(feature_path, namespace, feature_name)
|
|
2955
2963
|
# A migration no database can record, refused the same way.
|
|
2956
2964
|
_refuse_long_revision_ids(feature_path, feature_name)
|
|
2965
|
+
# SQL or markup built from a variable string, and a template that runs
|
|
2966
|
+
# inline code, refused the same way.
|
|
2967
|
+
_refuse_security_findings(feature_path, feature_name)
|
|
2957
2968
|
|
|
2958
2969
|
release.confirm_single_channel(channels, no_forge=no_forge, no_pypi=no_pypi)
|
|
2959
2970
|
|
|
@@ -3209,6 +3220,31 @@ def _refuse_long_revision_ids(feature_path, feature_name) -> None:
|
|
|
3209
3220
|
raise SystemExit(1)
|
|
3210
3221
|
|
|
3211
3222
|
|
|
3223
|
+
def _refuse_security_findings(feature_path, feature_name) -> None:
|
|
3224
|
+
"""Stop the release, before it changes anything, when the feature's
|
|
3225
|
+
sources build SQL or markup from a variable string, render a template
|
|
3226
|
+
from one, or have a template that escapes nothing or runs inline code
|
|
3227
|
+
(splent_cli.services.security_scan). --skip-checks does not skip it.
|
|
3228
|
+
"""
|
|
3229
|
+
from splent_cli.services import security_scan
|
|
3230
|
+
|
|
3231
|
+
findings = security_scan.scan_feature(feature_path)
|
|
3232
|
+
if not findings:
|
|
3233
|
+
return
|
|
3234
|
+
click.echo()
|
|
3235
|
+
click.secho(
|
|
3236
|
+
f" error: {feature_name} has {len(findings)} SQL or XSS sink(s) the "
|
|
3237
|
+
"toolchain refuses, so the release did not start.",
|
|
3238
|
+
fg="red",
|
|
3239
|
+
)
|
|
3240
|
+
security_scan.echo_findings(findings)
|
|
3241
|
+
click.echo(
|
|
3242
|
+
f" {security_scan.ALLOW_HINT}\n"
|
|
3243
|
+
" Nothing was changed. No version bump, no commit, no tag, no release."
|
|
3244
|
+
)
|
|
3245
|
+
raise SystemExit(1)
|
|
3246
|
+
|
|
3247
|
+
|
|
3212
3248
|
def _interactive() -> bool:
|
|
3213
3249
|
"""Whether someone can answer a prompt on stdin."""
|
|
3214
3250
|
import sys
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
feature:security, the SQL and XSS sinks of one feature or of every one.
|
|
3
|
+
|
|
4
|
+
The scan is splent_cli.services.security_scan, the one product:validate
|
|
5
|
+
runs in its Security phase and feature:release runs before it changes
|
|
6
|
+
anything.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from splent_cli.services import context, security_scan
|
|
14
|
+
from splent_cli.utils.feature_resolution import resolve_feature_ref
|
|
15
|
+
from splent_cli.utils.feature_utils import editable_feature_dirs
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _targets(workspace: Path, identifiers) -> list[tuple[str, Path]]:
|
|
19
|
+
"""``(name, root)`` of each feature to scan: the ones named, else every
|
|
20
|
+
editable feature at the workspace root."""
|
|
21
|
+
if not identifiers:
|
|
22
|
+
return [
|
|
23
|
+
(leaf, Path(path)) for _, leaf, path in editable_feature_dirs(workspace)
|
|
24
|
+
]
|
|
25
|
+
targets = []
|
|
26
|
+
for identifier in identifiers:
|
|
27
|
+
found = resolve_feature_ref(identifier, workspace)
|
|
28
|
+
if found is None:
|
|
29
|
+
click.secho(
|
|
30
|
+
f" Feature '{identifier}' not found at the workspace root or "
|
|
31
|
+
"in .splent_cache.",
|
|
32
|
+
fg="red",
|
|
33
|
+
)
|
|
34
|
+
raise SystemExit(1)
|
|
35
|
+
root, _, name = found
|
|
36
|
+
targets.append((root.name if "@" in root.name else name, root))
|
|
37
|
+
return targets
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@click.command(
|
|
41
|
+
"feature:security",
|
|
42
|
+
short_help="Find the SQL and XSS sinks the toolchain refuses in features.",
|
|
43
|
+
)
|
|
44
|
+
@click.argument("feature_identifiers", nargs=-1)
|
|
45
|
+
def feature_security(feature_identifiers):
|
|
46
|
+
"""
|
|
47
|
+
Scan features for SQL injection and XSS sinks no runtime can catch.
|
|
48
|
+
|
|
49
|
+
\b
|
|
50
|
+
Python
|
|
51
|
+
sql-from-string SQL of text(), execute(), exec_driver_sql(),
|
|
52
|
+
literal_column(), or a string given to filter(),
|
|
53
|
+
where(), order_by() or having(), built from a
|
|
54
|
+
variable string (an f-string, %, + or format())
|
|
55
|
+
markup-from-string Markup() of a string built that way from values
|
|
56
|
+
that are not escaped
|
|
57
|
+
template-from-string render_template_string() of a template that is
|
|
58
|
+
not a literal
|
|
59
|
+
Templates (.html, .j2, .xml, .svg under templates/)
|
|
60
|
+
autoescape-off {% autoescape false %}
|
|
61
|
+
inline-handler an on...= attribute
|
|
62
|
+
inline-script a <script> with code, no src and no nonce
|
|
63
|
+
javascript-url href, src, action or formaction with javascript:
|
|
64
|
+
|
|
65
|
+
\b
|
|
66
|
+
A reviewed exception says so on its line.
|
|
67
|
+
# splent: allow <rule> in Python
|
|
68
|
+
{# splent: allow <rule> #} in a template
|
|
69
|
+
|
|
70
|
+
\b
|
|
71
|
+
FEATURE_IDENTIFIERS are short names (notes), package names
|
|
72
|
+
(splent_feature_notes), either under a namespace (splent-io/notes),
|
|
73
|
+
or snapshots (notes@v1.2.0). Without any, every editable feature at
|
|
74
|
+
the workspace root is scanned. Tests are not scanned. Exits 1 when
|
|
75
|
+
there is a finding.
|
|
76
|
+
|
|
77
|
+
\b
|
|
78
|
+
Examples:
|
|
79
|
+
splent feature:security notes
|
|
80
|
+
splent feature:security auth profile
|
|
81
|
+
splent feature:security
|
|
82
|
+
"""
|
|
83
|
+
workspace = Path(context.workspace())
|
|
84
|
+
targets = _targets(workspace, feature_identifiers)
|
|
85
|
+
|
|
86
|
+
click.echo()
|
|
87
|
+
click.echo(click.style(" Security scan (SQL and XSS sinks)", bold=True))
|
|
88
|
+
click.echo()
|
|
89
|
+
|
|
90
|
+
total = 0
|
|
91
|
+
flagged = 0
|
|
92
|
+
for name, root in targets:
|
|
93
|
+
findings = security_scan.scan_feature(root)
|
|
94
|
+
if not findings:
|
|
95
|
+
continue
|
|
96
|
+
flagged += 1
|
|
97
|
+
total += len(findings)
|
|
98
|
+
click.echo(click.style(f" {name}", bold=True))
|
|
99
|
+
security_scan.echo_findings(findings)
|
|
100
|
+
click.echo()
|
|
101
|
+
|
|
102
|
+
scanned = f"{len(targets)} feature" + ("" if len(targets) == 1 else "s")
|
|
103
|
+
if total:
|
|
104
|
+
click.secho(
|
|
105
|
+
f" ❌ {total} finding(s) in {flagged} of {scanned}.", fg="red", bold=True
|
|
106
|
+
)
|
|
107
|
+
click.echo(f" {security_scan.ALLOW_HINT}")
|
|
108
|
+
click.echo()
|
|
109
|
+
raise SystemExit(1)
|
|
110
|
+
click.secho(f" ✅ No findings in {scanned}.", fg="green", bold=True)
|
|
111
|
+
click.echo()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
cli_command = feature_security
|
|
@@ -175,7 +175,7 @@ def _passthrough_args(
|
|
|
175
175
|
|
|
176
176
|
def _is_prod(value) -> bool:
|
|
177
177
|
"""SPLENT_ENV read the way production_active() reads it."""
|
|
178
|
-
return (value
|
|
178
|
+
return context.normalize_env(value) == "prod"
|
|
179
179
|
|
|
180
180
|
|
|
181
181
|
def _is_prod_active(product: str | None = None) -> bool:
|
|
@@ -203,6 +203,11 @@ def replace_pyproject_reference(pyproject_path: str, name: str, version: str):
|
|
|
203
203
|
|
|
204
204
|
if not changed:
|
|
205
205
|
return
|
|
206
|
+
# Back to the editable checkout, which is never installed from PyPI, so
|
|
207
|
+
# the PyPI name the pinned release had is forgotten, as detach does.
|
|
208
|
+
from splent_cli.utils.feature_utils import set_feature_distribution
|
|
209
|
+
|
|
210
|
+
set_feature_distribution(data, name, None)
|
|
206
211
|
|
|
207
212
|
write_toml(pyproject_path, data)
|
|
208
213
|
|
|
@@ -230,7 +235,7 @@ def _compile_assets(workspace: str, product_path: str, name: str):
|
|
|
230
235
|
from splent_cli.services import frontend
|
|
231
236
|
|
|
232
237
|
product = os.path.basename(product_path)
|
|
233
|
-
env =
|
|
238
|
+
env = context.resolve_env()
|
|
234
239
|
|
|
235
240
|
# Find webpack config
|
|
236
241
|
editable_path = os.path.join(workspace, name)
|
|
@@ -457,7 +462,12 @@ def _edit_one(
|
|
|
457
462
|
is_flag=True,
|
|
458
463
|
help="Convert all versioned features to editable.",
|
|
459
464
|
)
|
|
460
|
-
@click.option(
|
|
465
|
+
@click.option(
|
|
466
|
+
"--force",
|
|
467
|
+
is_flag=True,
|
|
468
|
+
hidden=True,
|
|
469
|
+
help="Accepted for scripts that pass it; there is no check to bypass.",
|
|
470
|
+
)
|
|
461
471
|
@keeps_ci_workflows
|
|
462
472
|
def feature_edit(feature_name, edit_all, force):
|
|
463
473
|
workspace = str(context.workspace())
|
|
@@ -135,7 +135,7 @@ def feature_xray(feature_ref, filter_cat, validate, full):
|
|
|
135
135
|
|
|
136
136
|
data = load_toml(pyproject_path, what=f"{product} pyproject.toml")
|
|
137
137
|
|
|
138
|
-
env =
|
|
138
|
+
env = context.resolve_env()
|
|
139
139
|
features = read_features_from_data(data, env)
|
|
140
140
|
|
|
141
141
|
# Check for stale contracts
|
|
@@ -48,11 +48,6 @@ CONTRACTS_FORMAT = 1
|
|
|
48
48
|
# version, anything else is a pip specifier.
|
|
49
49
|
RUNTIME_TABLE = ("tool", "splent", "runtime")
|
|
50
50
|
RUNTIME_DEFAULTS = {"gunicorn": ">=23,<24"}
|
|
51
|
-
RUNTIME_COMMENT = (
|
|
52
|
-
"# What the production image installs besides the features. splent\n"
|
|
53
|
-
"# product:build keeps splent_framework and splent_cli at the versions this\n"
|
|
54
|
-
"# product is built with; a bare version means exactly that version.\n"
|
|
55
|
-
)
|
|
56
51
|
# Where those pins lived before, in a file of their own. Removed by the build
|
|
57
52
|
# that moves them into pyproject.toml.
|
|
58
53
|
LEGACY_REQUIREMENTS_FILE = "requirements.prod.txt"
|
|
@@ -449,15 +444,7 @@ def write_runtime_pins(pyproject_path: str, workspace: str) -> dict | None:
|
|
|
449
444
|
if wanted == current:
|
|
450
445
|
return None
|
|
451
446
|
splent["runtime"] = wanted
|
|
452
|
-
|
|
453
|
-
header = "[" + ".".join(RUNTIME_TABLE) + "]"
|
|
454
|
-
lines = result.splitlines(keepends=True)
|
|
455
|
-
for i, line in enumerate(lines):
|
|
456
|
-
if line.strip() == header and not (i and lines[i - 1].lstrip().startswith("#")):
|
|
457
|
-
newline = "\r\n" if line.endswith("\r\n") else "\n"
|
|
458
|
-
lines[i:i] = [c + newline for c in RUNTIME_COMMENT.splitlines()]
|
|
459
|
-
break
|
|
460
|
-
atomic_write(pyproject_path, "".join(lines), newline="")
|
|
447
|
+
atomic_write(pyproject_path, edit_toml(text, data), newline="")
|
|
461
448
|
return wanted
|
|
462
449
|
|
|
463
450
|
|
|
@@ -190,7 +190,7 @@ def product_config(show_all, filter_feature, filter_key, show_secrets):
|
|
|
190
190
|
col_src = max(col_src, 6)
|
|
191
191
|
|
|
192
192
|
# Header
|
|
193
|
-
env =
|
|
193
|
+
env = context.resolve_env()
|
|
194
194
|
feature_sources = set()
|
|
195
195
|
for v in trace.values():
|
|
196
196
|
src = v.get("source", "")
|
|
@@ -509,6 +509,12 @@ def product_env(generate, merge, env_name, process_all):
|
|
|
509
509
|
"".join(env_line(k, v) for k, v in merged.items()),
|
|
510
510
|
)
|
|
511
511
|
|
|
512
|
+
# This process may hold the file's previous values, and what follows
|
|
513
|
+
# in it (product:derive starting the stacks) must see the new ones.
|
|
514
|
+
from splent_cli.utils.dynamic_imports import reload_product_env
|
|
515
|
+
|
|
516
|
+
reload_product_env(target_env)
|
|
517
|
+
|
|
512
518
|
if product_replaced:
|
|
513
519
|
click.echo(
|
|
514
520
|
click.style(" product ", dim=True)
|