splent-cli 1.41.0__tar.gz → 1.42.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.41.0/src/splent_cli.egg-info → splent_cli-1.42.0}/PKG-INFO +1 -1
- {splent_cli-1.41.0 → splent_cli-1.42.0}/pyproject.toml +1 -1
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/cli.py +93 -34
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_test.py +8 -2
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_commands.py +71 -37
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_test.py +74 -4
- splent_cli-1.42.0/src/splent_cli/services/own_commands.py +76 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli.egg-info/SOURCES.txt +1 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/COPYING +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/COPYING.LESSER +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/README.md +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/setup.cfg +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_vendor.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_release.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_build.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_create.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_derive.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_params.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_validate.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/release/release_resume.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/deploy_bundle.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/image_registry.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/marketplace.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/neutral_app.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/preflight.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/pypi_quota.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/release_dry_run.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/spl_store.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/web_container.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_services_service.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/licenses/COPYING +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_400.html +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_401.html +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_404.html +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_500.html +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_app.css +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_app.js +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_own.css +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/ci_services.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/ci_workflow.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/db_client.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_naming.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_params.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_requirements.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_resolution.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/forge.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/menu_keys.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/migration_lock.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/redact.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/refinement_facts.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/running_version.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/toml_edit.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/uvl_params.py +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli.egg-info/top_level.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import logging
|
|
2
3
|
import os
|
|
3
4
|
|
|
@@ -20,6 +21,27 @@ def _missing_module(exc: BaseException) -> str | None:
|
|
|
20
21
|
return None
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
@contextlib.contextmanager
|
|
25
|
+
def _quiet_unless(debug: bool):
|
|
26
|
+
"""The framework's logging muted while the app is built for discovery.
|
|
27
|
+
|
|
28
|
+
Discovery runs before every command, including the ones a product that
|
|
29
|
+
is not built yet (selected, not configured or derived) runs while
|
|
30
|
+
getting there. Building its app then fails, and the framework logged
|
|
31
|
+
that failure with a full traceback on every single command, although
|
|
32
|
+
nothing was wrong. The failure is expected there and handled, so the
|
|
33
|
+
framework's logging is muted for the attempt.
|
|
34
|
+
"""
|
|
35
|
+
if debug:
|
|
36
|
+
yield
|
|
37
|
+
return
|
|
38
|
+
logging.disable(logging.CRITICAL)
|
|
39
|
+
try:
|
|
40
|
+
yield
|
|
41
|
+
finally:
|
|
42
|
+
logging.disable(logging.NOTSET)
|
|
43
|
+
|
|
44
|
+
|
|
23
45
|
class SPLENTCLI(click.Group):
|
|
24
46
|
"""
|
|
25
47
|
Main SPLENT CLI class.
|
|
@@ -27,6 +49,7 @@ class SPLENTCLI(click.Group):
|
|
|
27
49
|
- Automatically injects the Flask app context for commands marked with `requires_app = True`.
|
|
28
50
|
- Checks DB connectivity for commands marked with `requires_db = True`.
|
|
29
51
|
- Discovers CLI commands contributed by features via ``app.extensions["splent_feature_commands"]``.
|
|
52
|
+
- Discovers the product's own commands, ``product:<name>`` (services/own_commands.py).
|
|
30
53
|
- Displays commands grouped by category for a cleaner, more readable help output.
|
|
31
54
|
"""
|
|
32
55
|
|
|
@@ -49,44 +72,65 @@ class SPLENTCLI(click.Group):
|
|
|
49
72
|
return self._feature_cmds_cache
|
|
50
73
|
|
|
51
74
|
self._feature_cmds_cache: dict[str, click.BaseCommand] = {}
|
|
52
|
-
# Discovery runs before every command, including the ones a product
|
|
53
|
-
# that is not built yet (selected, not configured or derived) runs
|
|
54
|
-
# while getting there. Building its app then fails, and the framework
|
|
55
|
-
# logged that failure with a full traceback on every single command,
|
|
56
|
-
# although nothing was wrong. The failure is expected here and
|
|
57
|
-
# handled below, so the framework's logging is muted for the attempt.
|
|
58
75
|
debug = bool(os.getenv("SPLENT_DEBUG"))
|
|
59
|
-
if not debug:
|
|
60
|
-
logging.disable(logging.CRITICAL)
|
|
61
76
|
try:
|
|
62
|
-
|
|
77
|
+
with _quiet_unless(debug):
|
|
78
|
+
from splent_cli.utils.dynamic_imports import get_app
|
|
63
79
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
app = get_app()
|
|
81
|
+
with app.app_context():
|
|
82
|
+
registry = app.extensions.get("splent_feature_commands", {})
|
|
83
|
+
for feature_short, commands in registry.items():
|
|
84
|
+
cmd_names = ", ".join(c.name for c in commands if c.name)
|
|
85
|
+
group = click.Group(
|
|
86
|
+
name=f"feature:{feature_short}",
|
|
87
|
+
help=f"Commands contributed by splent_feature_{feature_short}.",
|
|
88
|
+
short_help=f"Subcommands: {cmd_names}"
|
|
89
|
+
if cmd_names
|
|
90
|
+
else f"Commands from splent_feature_{feature_short}.",
|
|
91
|
+
)
|
|
92
|
+
group.requires_app = True # type: ignore[attr-defined]
|
|
93
|
+
for cmd in commands:
|
|
94
|
+
group.add_command(cmd)
|
|
95
|
+
self._feature_cmds_cache[group.name] = group
|
|
80
96
|
except Exception as e:
|
|
81
97
|
if debug:
|
|
82
98
|
click.secho(
|
|
83
99
|
f" ⚠ Feature commands not loaded: {e}", fg="yellow", err=True
|
|
84
100
|
)
|
|
85
|
-
finally:
|
|
86
|
-
if not debug:
|
|
87
|
-
logging.disable(logging.NOTSET)
|
|
88
101
|
return self._feature_cmds_cache
|
|
89
102
|
|
|
103
|
+
def load_own_commands(self):
|
|
104
|
+
"""The product's own commands, ``product:<name>``, as an
|
|
105
|
+
:class:`~splent_cli.services.own_commands.OwnCommands`.
|
|
106
|
+
|
|
107
|
+
Found like a feature's: once the product's app is built, which
|
|
108
|
+
fails quietly here for a product that cannot be built yet. The
|
|
109
|
+
CLI's own product:* commands win over a product's.
|
|
110
|
+
"""
|
|
111
|
+
if hasattr(self, "_own_cmds_cache"):
|
|
112
|
+
return self._own_cmds_cache
|
|
113
|
+
|
|
114
|
+
from splent_cli.services import own_commands
|
|
115
|
+
|
|
116
|
+
self._own_cmds_cache = own_commands.NONE
|
|
117
|
+
debug = bool(os.getenv("SPLENT_DEBUG"))
|
|
118
|
+
try:
|
|
119
|
+
with _quiet_unless(debug):
|
|
120
|
+
from splent_cli.utils.dynamic_imports import get_app
|
|
121
|
+
|
|
122
|
+
app = get_app()
|
|
123
|
+
with app.app_context():
|
|
124
|
+
self._own_cmds_cache = own_commands.collect(
|
|
125
|
+
os.getenv("SPLENT_APP"), set(self.commands)
|
|
126
|
+
)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
if debug:
|
|
129
|
+
click.secho(
|
|
130
|
+
f" ⚠ Product commands not loaded: {e}", fg="yellow", err=True
|
|
131
|
+
)
|
|
132
|
+
return self._own_cmds_cache
|
|
133
|
+
|
|
90
134
|
def get_command(self, ctx, cmd_name):
|
|
91
135
|
# "feature:<short>" belongs to the feature called <short> whenever the
|
|
92
136
|
# product installs one. Built-ins win everywhere else.
|
|
@@ -100,13 +144,19 @@ class SPLENTCLI(click.Group):
|
|
|
100
144
|
feat_cmds = self._load_feature_commands()
|
|
101
145
|
if cmd_name in feat_cmds:
|
|
102
146
|
return feat_cmds[cmd_name]
|
|
103
|
-
|
|
147
|
+
command = super().get_command(ctx, cmd_name)
|
|
148
|
+
# "product:<name>" is the product's only where the CLI has no
|
|
149
|
+
# command of that name, so asking for a built-in never imports it.
|
|
150
|
+
if command is None and cmd_name and cmd_name.startswith("product:"):
|
|
151
|
+
command = self.load_own_commands().commands.get(cmd_name)
|
|
152
|
+
return command
|
|
104
153
|
|
|
105
154
|
def list_commands(self, ctx):
|
|
106
155
|
builtin = super().list_commands(ctx)
|
|
107
156
|
feat = sorted(self._load_feature_commands().keys())
|
|
157
|
+
own = sorted(self.load_own_commands().commands)
|
|
108
158
|
# A name a feature claimed is listed once, as the feature's.
|
|
109
|
-
return [name for name in builtin if name not in feat] + feat
|
|
159
|
+
return [name for name in builtin if name not in feat] + feat + own
|
|
110
160
|
|
|
111
161
|
# ── App context injection ─────────────────────────────────────
|
|
112
162
|
|
|
@@ -132,9 +182,14 @@ class SPLENTCLI(click.Group):
|
|
|
132
182
|
cmd_name = ctx.protected_args[0] if ctx.protected_args else None
|
|
133
183
|
command = self.get_command(ctx, cmd_name)
|
|
134
184
|
|
|
135
|
-
# A feature's own
|
|
136
|
-
# here they may be missing although the
|
|
137
|
-
|
|
185
|
+
# A feature's commands, and the product's own, are only found by
|
|
186
|
+
# building the app, so here they may be missing although the
|
|
187
|
+
# product has them.
|
|
188
|
+
if (
|
|
189
|
+
command is None
|
|
190
|
+
and cmd_name
|
|
191
|
+
and cmd_name.startswith(("feature:", "product:"))
|
|
192
|
+
):
|
|
138
193
|
self._delegate_to_the_product(ctx)
|
|
139
194
|
|
|
140
195
|
if command and getattr(command, "requires_app", False):
|
|
@@ -169,6 +224,7 @@ class SPLENTCLI(click.Group):
|
|
|
169
224
|
"""Group SPLENT commands by category in the CLI help output."""
|
|
170
225
|
all_cmds = self.list_commands(ctx)
|
|
171
226
|
feat_cmds = self._load_feature_commands()
|
|
227
|
+
own_cmds = self.load_own_commands().commands
|
|
172
228
|
groups = {
|
|
173
229
|
"🌿 Feature Management": [
|
|
174
230
|
cmd
|
|
@@ -176,7 +232,9 @@ class SPLENTCLI(click.Group):
|
|
|
176
232
|
if cmd.startswith("feature:") and cmd not in feat_cmds
|
|
177
233
|
],
|
|
178
234
|
"🏗️ Product Management": [
|
|
179
|
-
cmd
|
|
235
|
+
cmd
|
|
236
|
+
for cmd in all_cmds
|
|
237
|
+
if cmd.startswith("product:") and cmd not in own_cmds
|
|
180
238
|
],
|
|
181
239
|
"🧬 SPL & Variability": [
|
|
182
240
|
cmd for cmd in all_cmds if cmd.startswith(("spl:", "uvl:"))
|
|
@@ -207,6 +265,7 @@ class SPLENTCLI(click.Group):
|
|
|
207
265
|
if cmd in ("lint", "coverage", "selenium", "locust", "locust:stop")
|
|
208
266
|
],
|
|
209
267
|
"🔌 Feature Commands": [cmd for cmd in all_cmds if cmd in feat_cmds],
|
|
268
|
+
"🔌 Product Commands": [cmd for cmd in all_cmds if cmd in own_cmds],
|
|
210
269
|
}
|
|
211
270
|
total = 0
|
|
212
271
|
for title, cmds in groups.items():
|
|
@@ -150,7 +150,7 @@ def _workspace_owner() -> tuple[int, int] | None:
|
|
|
150
150
|
return owner.st_uid, owner.st_gid
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
def run_tests_in_web_container(product: str, argv: list[str]) -> int:
|
|
153
|
+
def run_tests_in_web_container(product: str, argv: list[str], env=()) -> int:
|
|
154
154
|
"""Run ``splent <argv>`` in PRODUCT's web container, as a test run.
|
|
155
155
|
|
|
156
156
|
Returns its exit code, or 1 when the container is not running. The run
|
|
@@ -158,7 +158,8 @@ def run_tests_in_web_container(product: str, argv: list[str]) -> int:
|
|
|
158
158
|
process does (see :func:`_tied_to_the_cli`), and runs as the workspace's
|
|
159
159
|
owner (:func:`workspace_user`). feature:test runs there this way, and so
|
|
160
160
|
do the product's own tests (product:test) and the release gate, which
|
|
161
|
-
runs feature:test.
|
|
161
|
+
runs feature:test. ``env`` names more variables of this process's
|
|
162
|
+
environment for the run (product:test's [tool.splent.test] env).
|
|
162
163
|
"""
|
|
163
164
|
name = web_container.name(product)
|
|
164
165
|
if not _container_running(name):
|
|
@@ -180,6 +181,11 @@ def run_tests_in_web_container(product: str, argv: list[str]) -> int:
|
|
|
180
181
|
value = os.environ.get(var)
|
|
181
182
|
if value:
|
|
182
183
|
passthrough += ["-e", f"{var}={value}"]
|
|
184
|
+
# By name alone: docker reads each value from this process's
|
|
185
|
+
# environment, so a password never shows in the exec's arguments.
|
|
186
|
+
for var in env:
|
|
187
|
+
if var in os.environ:
|
|
188
|
+
passthrough += ["-e", var]
|
|
183
189
|
# The exec reads a pipe only this process writes to, so it sees the end
|
|
184
190
|
# of its input exactly when this process ends, however it ends.
|
|
185
191
|
read_end, write_end = os.pipe()
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
1
3
|
import click
|
|
2
4
|
from flask import current_app
|
|
3
5
|
|
|
4
|
-
from splent_cli.services import context
|
|
6
|
+
from splent_cli.services import context, own_commands
|
|
5
7
|
from splent_cli.utils.decorators import requires_app
|
|
6
8
|
|
|
7
9
|
|
|
@@ -25,46 +27,20 @@ def _format_flags(cmd) -> str:
|
|
|
25
27
|
return ", ".join(parts)
|
|
26
28
|
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
def product_commands():
|
|
35
|
-
"""Show CLI commands registered by features in the active product."""
|
|
36
|
-
registry = current_app.extensions.get("splent_feature_commands", {})
|
|
37
|
-
|
|
38
|
-
if not registry:
|
|
39
|
-
click.echo()
|
|
40
|
-
click.secho(" No feature commands registered.", fg="yellow")
|
|
41
|
-
click.echo()
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
# Build rows: (feature, invocation, flags, description)
|
|
45
|
-
rows = []
|
|
46
|
-
for feature_short, commands in sorted(registry.items()):
|
|
47
|
-
for cmd in sorted(commands, key=lambda c: c.name):
|
|
48
|
-
desc = cmd.get_short_help_str() or "—"
|
|
49
|
-
invocation = f"feature:{feature_short} {cmd.name}"
|
|
50
|
-
flags = _format_flags(cmd)
|
|
51
|
-
rows.append((feature_short, invocation, flags, desc))
|
|
52
|
-
|
|
53
|
-
# Column widths based on visible text
|
|
54
|
-
col_feat = max(len(r[0]) for r in rows)
|
|
55
|
-
col_feat = max(col_feat, len("Feature"))
|
|
56
|
-
col_cmd = max(len(r[1]) for r in rows)
|
|
57
|
-
col_cmd = max(col_cmd, len("Command"))
|
|
58
|
-
col_flags = max(len(r[2]) for r in rows)
|
|
59
|
-
col_flags = max(col_flags, len("Flags"))
|
|
30
|
+
def _table(title: str, owner: str, rows) -> None:
|
|
31
|
+
"""Print ``rows`` of (owner, invocation, flags, description) under TITLE,
|
|
32
|
+
the owner named once per run of its rows."""
|
|
33
|
+
col_feat = max(max(len(r[0]) for r in rows), len(owner))
|
|
34
|
+
col_cmd = max(max(len(r[1]) for r in rows), len("Command"))
|
|
35
|
+
col_flags = max(max(len(r[2]) for r in rows), len("Flags"))
|
|
60
36
|
|
|
61
37
|
click.echo()
|
|
62
|
-
click.secho("
|
|
38
|
+
click.secho(f" {title}", bold=True, fg="cyan")
|
|
63
39
|
click.echo()
|
|
64
40
|
|
|
65
41
|
# Header
|
|
66
42
|
click.echo(
|
|
67
|
-
f" {
|
|
43
|
+
f" {owner:<{col_feat}} {'Command':<{col_cmd}} {'Flags':<{col_flags}} Description"
|
|
68
44
|
)
|
|
69
45
|
click.echo(f" {'-' * col_feat} {'-' * col_cmd} {'-' * col_flags} {'-' * 30}")
|
|
70
46
|
|
|
@@ -85,8 +61,66 @@ def product_commands():
|
|
|
85
61
|
)
|
|
86
62
|
prev_feat = feat
|
|
87
63
|
|
|
88
|
-
|
|
89
|
-
|
|
64
|
+
|
|
65
|
+
def _own_commands():
|
|
66
|
+
"""The product's own commands, against the CLI's built-in names."""
|
|
67
|
+
from splent_cli.cli import cli
|
|
68
|
+
|
|
69
|
+
return own_commands.collect(os.getenv("SPLENT_APP"), set(cli.commands))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@requires_app
|
|
73
|
+
@click.command(
|
|
74
|
+
"product:commands",
|
|
75
|
+
short_help="List the CLI commands of the product's features and its own.",
|
|
76
|
+
)
|
|
77
|
+
@context.requires_product
|
|
78
|
+
def product_commands():
|
|
79
|
+
"""Show the CLI commands the active product's features register, and the
|
|
80
|
+
product's own (product:<name>, from its commands module)."""
|
|
81
|
+
registry = current_app.extensions.get("splent_feature_commands", {})
|
|
82
|
+
own = _own_commands()
|
|
83
|
+
product = os.getenv("SPLENT_APP", "product")
|
|
84
|
+
|
|
85
|
+
if not registry and not own.commands and not own.shadowed and not own.error:
|
|
86
|
+
click.echo()
|
|
87
|
+
click.secho(" No feature or product commands registered.", fg="yellow")
|
|
88
|
+
click.echo()
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Build rows: (feature, invocation, flags, description)
|
|
92
|
+
rows = []
|
|
93
|
+
for feature_short, commands in sorted(registry.items()):
|
|
94
|
+
for cmd in sorted(commands, key=lambda c: c.name):
|
|
95
|
+
desc = cmd.get_short_help_str() or "—"
|
|
96
|
+
invocation = f"feature:{feature_short} {cmd.name}"
|
|
97
|
+
rows.append((feature_short, invocation, _format_flags(cmd), desc))
|
|
98
|
+
if rows:
|
|
99
|
+
_table("Feature Commands", "Feature", rows)
|
|
100
|
+
click.echo()
|
|
101
|
+
click.secho(
|
|
102
|
+
f" {len(rows)} command(s) from {len(registry)} feature(s).", dim=True
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
own_rows = [
|
|
106
|
+
(product, name, _format_flags(cmd), cmd.get_short_help_str() or "—")
|
|
107
|
+
for name, cmd in sorted(own.commands.items())
|
|
108
|
+
]
|
|
109
|
+
if own_rows:
|
|
110
|
+
_table("Product Commands", "Product", own_rows)
|
|
111
|
+
click.echo()
|
|
112
|
+
click.secho(f" {len(own_rows)} command(s) of {product}'s own.", dim=True)
|
|
113
|
+
for name in own.shadowed:
|
|
114
|
+
click.secho(
|
|
115
|
+
f" ⚠ {product} has a command {name}, which is the CLI's own and "
|
|
116
|
+
"wins: rename it to reach it.",
|
|
117
|
+
fg="yellow",
|
|
118
|
+
)
|
|
119
|
+
if own.error:
|
|
120
|
+
click.secho(
|
|
121
|
+
f" ⚠ {product}'s own commands are not available: {own.error}",
|
|
122
|
+
fg="yellow",
|
|
123
|
+
)
|
|
90
124
|
click.echo()
|
|
91
125
|
|
|
92
126
|
|
|
@@ -12,16 +12,26 @@ check the configuration the product is, such as the URLs it promises to keep
|
|
|
12
12
|
serving, which no feature's tests can, so a product that ships them has them
|
|
13
13
|
run wherever its features' tests run: here, in its release checks and in its
|
|
14
14
|
CI. A product without them is tested as before.
|
|
15
|
+
|
|
16
|
+
Its own tests may need what only the caller has, such as the address of a
|
|
17
|
+
throwaway server the caller started for them. The product names those
|
|
18
|
+
variables in ``[tool.splent.test] env`` and product:test hands them over
|
|
19
|
+
from the caller's environment, into the web container too. It never starts
|
|
20
|
+
anything for them.
|
|
15
21
|
"""
|
|
16
22
|
|
|
17
23
|
import os
|
|
24
|
+
import re
|
|
18
25
|
import sys
|
|
19
26
|
from pathlib import Path
|
|
20
27
|
|
|
21
28
|
import click
|
|
22
29
|
|
|
23
30
|
from splent_cli.services import context, web_container
|
|
24
|
-
from splent_cli.utils.feature_utils import
|
|
31
|
+
from splent_cli.utils.feature_utils import (
|
|
32
|
+
load_product_features,
|
|
33
|
+
load_product_pyproject,
|
|
34
|
+
)
|
|
25
35
|
from splent_cli.utils.proc import run
|
|
26
36
|
|
|
27
37
|
# Where a product keeps its own tests, and the files pytest collects there
|
|
@@ -29,6 +39,56 @@ from splent_cli.utils.proc import run
|
|
|
29
39
|
OWN_TESTS_DIR = "tests"
|
|
30
40
|
_OWN_TEST_PATTERNS = ("test_*.py", "*_test.py")
|
|
31
41
|
|
|
42
|
+
# An entry of [tool.splent.test] env: a variable's name, or a prefix of
|
|
43
|
+
# names ending in *.
|
|
44
|
+
_ENV_ENTRY = re.compile(r"[A-Za-z_][A-Za-z0-9_]*\*?")
|
|
45
|
+
# Never handed over, whatever a pattern matches: the run sets SPLENT_APP and
|
|
46
|
+
# the like itself, and the tests use the web container's database settings,
|
|
47
|
+
# so a caller's MARIADB_DATABASE cannot point them at another database.
|
|
48
|
+
_NOT_HANDED = ("SPLENT_", "MARIADB_")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def declared_test_env(product_path) -> list[str]:
|
|
52
|
+
"""What ``[tool.splent.test] env`` of the product names; [] without it.
|
|
53
|
+
|
|
54
|
+
Raises click.ClickException for an entry that is neither a variable's
|
|
55
|
+
name nor a prefix ending in ``*``, so a typo is not a test that quietly
|
|
56
|
+
goes without its server.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
data = load_product_pyproject(str(product_path))
|
|
60
|
+
except FileNotFoundError:
|
|
61
|
+
return []
|
|
62
|
+
entries = data.get("tool", {}).get("splent", {}).get("test", {}).get("env", [])
|
|
63
|
+
if not isinstance(entries, list):
|
|
64
|
+
raise click.ClickException(
|
|
65
|
+
"[tool.splent.test] env is a list of variable names, such as "
|
|
66
|
+
'["MY_TESTS_DB_HOST", "MY_TESTS_*"]'
|
|
67
|
+
)
|
|
68
|
+
for entry in entries:
|
|
69
|
+
if not isinstance(entry, str) or not _ENV_ENTRY.fullmatch(entry):
|
|
70
|
+
raise click.ClickException(
|
|
71
|
+
f"[tool.splent.test] env names {entry!r}, which is neither a "
|
|
72
|
+
"variable's name nor a prefix ending in *"
|
|
73
|
+
)
|
|
74
|
+
return entries
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def handed_test_env(product_path, environ=None) -> list[str]:
|
|
78
|
+
"""The variables of ``environ`` (this process's) that the product's own
|
|
79
|
+
tests are handed: the ones its ``[tool.splent.test] env`` names that are
|
|
80
|
+
set, but for SPLENT_* and MARIADB_*."""
|
|
81
|
+
environ = os.environ if environ is None else environ
|
|
82
|
+
entries = declared_test_env(product_path)
|
|
83
|
+
names = {e for e in entries if not e.endswith("*")}
|
|
84
|
+
prefixes = tuple(e[:-1] for e in entries if e.endswith("*"))
|
|
85
|
+
return sorted(
|
|
86
|
+
name
|
|
87
|
+
for name in environ
|
|
88
|
+
if (name in names or (prefixes and name.startswith(prefixes)))
|
|
89
|
+
and not name.startswith(_NOT_HANDED)
|
|
90
|
+
)
|
|
91
|
+
|
|
32
92
|
|
|
33
93
|
def own_test_files(product_path) -> list[Path]:
|
|
34
94
|
"""The product's own test files, under ``<product>/tests``; [] without any."""
|
|
@@ -106,7 +166,8 @@ def _run_own_tests(product, verbose) -> None:
|
|
|
106
166
|
from the CLI they run in the web container, as feature:test does, one
|
|
107
167
|
test run at a time and stopped with the CLI. There, or in the CI job
|
|
108
168
|
that stands in for it, they run against the test database and against
|
|
109
|
-
the features as the product's app loads them
|
|
169
|
+
the features as the product's app loads them, with the variables of
|
|
170
|
+
this environment the product declares in ``[tool.splent.test] env``.
|
|
110
171
|
"""
|
|
111
172
|
from splent_cli.commands.feature.feature_test import (
|
|
112
173
|
_validate_testing_environment,
|
|
@@ -122,7 +183,15 @@ def _run_own_tests(product, verbose) -> None:
|
|
|
122
183
|
product = context.require_app()
|
|
123
184
|
if not web_container.is_product_container():
|
|
124
185
|
argv = ["product:test", "--own-tests"] + (["-v"] if verbose else [])
|
|
125
|
-
|
|
186
|
+
# Here the variables a product declares are handed over by name; in
|
|
187
|
+
# the container, and in the CI job, the whole environment is.
|
|
188
|
+
handed = handed_test_env(context.workspace() / product)
|
|
189
|
+
if handed:
|
|
190
|
+
click.secho(
|
|
191
|
+
f" handing the tests {', '.join(handed)} ([tool.splent.test] env)",
|
|
192
|
+
dim=True,
|
|
193
|
+
)
|
|
194
|
+
raise SystemExit(run_tests_in_web_container(product, argv, env=handed))
|
|
126
195
|
|
|
127
196
|
_validate_testing_environment()
|
|
128
197
|
|
|
@@ -196,7 +265,8 @@ def product_test(
|
|
|
196
265
|
run after the features', in the product's web container, against the
|
|
197
266
|
features as its app loads them (the versions it pins, the working copy
|
|
198
267
|
of an editable one), and only without a level flag. A product without a
|
|
199
|
-
tests/ folder runs its features' tests alone.
|
|
268
|
+
tests/ folder runs its features' tests alone. The variables of your
|
|
269
|
+
environment the product names in [tool.splent.test] env reach them.
|
|
200
270
|
|
|
201
271
|
\b
|
|
202
272
|
Examples:
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""A product's own CLI commands, ``splent product:<name>``.
|
|
2
|
+
|
|
3
|
+
A product declares them as a feature does: a ``commands`` module in its
|
|
4
|
+
package with a ``cli_commands`` list of Click commands. A feature's become
|
|
5
|
+
subcommands of ``feature:<short>``; a product's are commands of their own,
|
|
6
|
+
``product:<name>``, since one product is active at a time. Like a
|
|
7
|
+
feature's, they are found once the product's app is built, and run in its
|
|
8
|
+
context.
|
|
9
|
+
|
|
10
|
+
The CLI's own ``product:*`` commands win. A product command named like one
|
|
11
|
+
of them cannot be reached, and product:commands says so: a CLI that ships a
|
|
12
|
+
new built-in never takes a product's command away unnoticed.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import importlib
|
|
18
|
+
from typing import NamedTuple
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
|
|
22
|
+
PREFIX = "product:"
|
|
23
|
+
MODULE = "commands"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OwnCommands(NamedTuple):
|
|
27
|
+
"""What a product contributes: ``commands`` by the name they are run
|
|
28
|
+
with, ``shadowed`` the names a built-in took, and ``error`` why its
|
|
29
|
+
commands module could not be imported, None when it could."""
|
|
30
|
+
|
|
31
|
+
commands: dict[str, click.Command]
|
|
32
|
+
shadowed: list[str]
|
|
33
|
+
error: str | None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
NONE = OwnCommands({}, [], None)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def declared(product: str) -> list[click.Command]:
|
|
40
|
+
"""The Click commands ``<product>.commands`` lists in ``cli_commands``.
|
|
41
|
+
|
|
42
|
+
[] when the product has no such module. Whatever else importing it
|
|
43
|
+
raises is raised, so the caller can say why its commands are missing.
|
|
44
|
+
"""
|
|
45
|
+
name = f"{product}.{MODULE}"
|
|
46
|
+
try:
|
|
47
|
+
module = importlib.import_module(name)
|
|
48
|
+
except ModuleNotFoundError as e:
|
|
49
|
+
if e.name == name:
|
|
50
|
+
return []
|
|
51
|
+
raise
|
|
52
|
+
commands = getattr(module, "cli_commands", None) or ()
|
|
53
|
+
return [command for command in commands if isinstance(command, click.Command)]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def collect(product: str | None, builtin) -> OwnCommands:
|
|
57
|
+
"""The commands PRODUCT contributes, each under ``product:<its name>``,
|
|
58
|
+
but for those whose name is in ``builtin`` (the CLI's own)."""
|
|
59
|
+
if not product:
|
|
60
|
+
return NONE
|
|
61
|
+
try:
|
|
62
|
+
commands = declared(product)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
return OwnCommands({}, [], f"{product}.{MODULE}: {e}")
|
|
65
|
+
found, shadowed = {}, []
|
|
66
|
+
for command in commands:
|
|
67
|
+
if not command.name:
|
|
68
|
+
continue
|
|
69
|
+
name = PREFIX + command.name
|
|
70
|
+
if name in builtin:
|
|
71
|
+
shadowed.append(name)
|
|
72
|
+
continue
|
|
73
|
+
# Run in the app's context, as a feature's (cli.py).
|
|
74
|
+
command.requires_app = True # type: ignore[attr-defined]
|
|
75
|
+
found[name] = command
|
|
76
|
+
return OwnCommands(found, shadowed, None)
|
|
@@ -171,6 +171,7 @@ src/splent_cli/services/marketplace.py
|
|
|
171
171
|
src/splent_cli/services/marketplace_api.py
|
|
172
172
|
src/splent_cli/services/marketplace_url.py
|
|
173
173
|
src/splent_cli/services/neutral_app.py
|
|
174
|
+
src/splent_cli/services/own_commands.py
|
|
174
175
|
src/splent_cli/services/preflight.py
|
|
175
176
|
src/splent_cli/services/pypi_quota.py
|
|
176
177
|
src/splent_cli/services/registry.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|