splent-cli 1.5.1__tar.gz → 1.7.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.5.1/src/splent_cli.egg-info → splent_cli-1.7.0}/PKG-INFO +4 -17
- splent_cli-1.7.0/README.md +23 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/pyproject.toml +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/cli.py +18 -10
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_clear.py +4 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_outdated.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_github.py +8 -4
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_infra.py +110 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_product.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_pypi.py +7 -3
- splent_cli-1.5.1/src/splent_cli/commands/clear_cache.py → splent_cli-1.7.0/src/splent_cli/commands/clear/clear_build.py +2 -2
- {splent_cli-1.5.1/src/splent_cli/commands → splent_cli-1.7.0/src/splent_cli/commands/clear}/clear_log.py +1 -1
- {splent_cli-1.5.1/src/splent_cli/commands → splent_cli-1.7.0/src/splent_cli/commands/clear}/clear_uploads.py +1 -1
- {splent_cli-1.5.1/src/splent_cli/commands → splent_cli-1.7.0/src/splent_cli/commands/command}/command_create.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/coverage.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_console.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_dump.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_migrate.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_reset.py +5 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_upgrade.py +5 -0
- splent_cli-1.7.0/src/splent_cli/commands/doctor.py +142 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/env/env_list.py +4 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/env/env_show.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_add.py +20 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_attach.py +22 -1
- splent_cli-1.7.0/src/splent_cli/commands/feature/feature_clean.py +365 -0
- splent_cli-1.5.1/src/splent_cli/commands/feature/feature_diff.py → splent_cli-1.7.0/src/splent_cli/commands/feature/feature_compat.py +95 -214
- {splent_cli-1.5.1/src/splent_cli/commands → splent_cli-1.7.0/src/splent_cli/commands/feature}/feature_compile.py +2 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_contract.py +31 -0
- splent_cli-1.7.0/src/splent_cli/commands/feature/feature_create.py +275 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_delete.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_detach.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_discard.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_drift.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_env.py +13 -15
- splent_cli-1.7.0/src/splent_cli/commands/feature/feature_impact.py +161 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_inject_config.py +1 -1
- splent_cli-1.7.0/src/splent_cli/commands/feature/feature_install.py +425 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_list.py +2 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_outdated.py +2 -2
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_pin.py +1 -1
- splent_cli-1.5.1/src/splent_cli/commands/feature/feature_refinement.py → splent_cli-1.7.0/src/splent_cli/commands/feature/feature_refine.py +6 -6
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_release.py +106 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_remove.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_rename.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_status.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_test.py +48 -0
- splent_cli-1.5.1/src/splent_cli/commands/feature/feature_edit.py → splent_cli-1.7.0/src/splent_cli/commands/feature/feature_unlock.py +12 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_upgrade.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_xray.py +4 -1
- splent_cli-1.5.1/src/splent_cli/commands/linter.py → splent_cli-1.7.0/src/splent_cli/commands/lint.py +1 -3
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/locust.py +2 -4
- splent_cli-1.5.1/src/splent_cli/commands/product/product_complete.py → splent_cli-1.7.0/src/splent_cli/commands/product/product_auto_require.py +2 -2
- splent_cli-1.7.0/src/splent_cli/commands/product/product_build.py +526 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_clean.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_config.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_configure.py +68 -5
- splent_cli-1.7.0/src/splent_cli/commands/product/product_containers.py +316 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_create.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_deploy.py +62 -6
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_derive.py +4 -4
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_env.py +33 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_missing.py +1 -1
- splent_cli-1.5.1/src/splent_cli/commands/product/product_sync.py → splent_cli-1.7.0/src/splent_cli/commands/product/product_resolve.py +3 -3
- splent_cli-1.7.0/src/splent_cli/commands/product/product_restart.py +284 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_select.py +1 -1
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_test.py +14 -0
- splent_cli-1.7.0/src/splent_cli/commands/product/product_up.py +269 -0
- splent_cli-1.7.0/src/splent_cli/commands/product/product_validate.py +371 -0
- splent_cli-1.7.0/src/splent_cli/commands/spl/__init__.py +0 -0
- splent_cli-1.5.1/src/splent_cli/commands/spl/spl_fix.py → splent_cli-1.7.0/src/splent_cli/commands/spl/spl_add_constraints.py +3 -3
- splent_cli-1.5.1/src/splent_cli/commands/spl/spl_configs.py → splent_cli-1.7.0/src/splent_cli/commands/spl/spl_configurations.py +2 -2
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_features.py +1 -1
- splent_cli-1.5.1/src/splent_cli/commands/tokens.py → splent_cli-1.7.0/src/splent_cli/commands/tokens_setup.py +2 -2
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/version.py +4 -1
- splent_cli-1.7.0/src/splent_cli/services/__init__.py +0 -0
- splent_cli-1.7.0/src/splent_cli/services/preflight.py +157 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/services/release.py +2 -2
- splent_cli-1.7.0/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/integrity.py +2 -2
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/lifecycle.py +3 -3
- {splent_cli-1.5.1 → splent_cli-1.7.0/src/splent_cli.egg-info}/PKG-INFO +4 -17
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli.egg-info/SOURCES.txt +22 -16
- splent_cli-1.5.1/README.md +0 -36
- splent_cli-1.5.1/src/splent_cli/commands/doctor.py +0 -140
- splent_cli-1.5.1/src/splent_cli/commands/feature/feature_create.py +0 -218
- splent_cli-1.5.1/src/splent_cli/commands/product/product_build.py +0 -270
- splent_cli-1.5.1/src/splent_cli/commands/product/product_restart.py +0 -114
- splent_cli-1.5.1/src/splent_cli/commands/product/product_status.py +0 -196
- splent_cli-1.5.1/src/splent_cli/commands/product/product_up.py +0 -105
- splent_cli-1.5.1/src/splent_cli/commands/product/product_validate.py +0 -157
- splent_cli-1.5.1/src/splent_cli/services/preflight.py +0 -70
- {splent_cli-1.5.1 → splent_cli-1.7.0}/LICENSE +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/setup.cfg +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.5.1/src/splent_cli/commands/product → splent_cli-1.7.0/src/splent_cli/commands/clear}/__init__.py +0 -0
- {splent_cli-1.5.1/src/splent_cli/commands/release → splent_cli-1.7.0/src/splent_cli/commands/command}/__init__.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.5.1/src/splent_cli/commands/spl → splent_cli-1.7.0/src/splent_cli/commands/export}/__init__.py +0 -0
- {splent_cli-1.5.1/src/splent_cli/commands → splent_cli-1.7.0/src/splent_cli/commands/export}/export_puml.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.5.1/src/splent_cli/services → splent_cli-1.7.0/src/splent_cli/commands/product}/__init__.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.5.1/src/splent_cli/utils → splent_cli-1.7.0/src/splent_cli/commands/release}/__init__.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
|
|
5
5
|
Author-email: DiversoLab <diversolab@us.es>
|
|
6
6
|
Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
|
|
@@ -47,23 +47,10 @@ Command-line tool for managing SPLENT products, features, databases, and environ
|
|
|
47
47
|
## Quick start
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
make setup
|
|
51
|
-
splent --help
|
|
50
|
+
make setup # Prepare .env, start Docker, enter CLI container
|
|
51
|
+
splent --help # See all available commands
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
## Key commands
|
|
55
|
-
|
|
56
|
-
| Command | Description |
|
|
57
|
-
|---------|-------------|
|
|
58
|
-
| `product:create` | Create a new product |
|
|
59
|
-
| `product:derive --dev` | Full SPL derivation pipeline |
|
|
60
|
-
| `feature:add` / `feature:attach` | Add features to a product |
|
|
61
|
-
| `feature:status` | Show feature lifecycle states |
|
|
62
|
-
| `feature:release` | Release a feature (tag + PyPI + GitHub) |
|
|
63
|
-
| `db:migrate` / `db:upgrade` | Manage per-feature migrations |
|
|
64
|
-
| `export:puml` | Generate PlantUML diagrams |
|
|
65
|
-
| `doctor` | System health check |
|
|
66
|
-
|
|
67
54
|
## Requirements
|
|
68
55
|
|
|
69
56
|
- Docker + Docker Compose
|
|
@@ -71,7 +58,7 @@ splent --help # See all available commands
|
|
|
71
58
|
|
|
72
59
|
## Documentation
|
|
73
60
|
|
|
74
|
-
Full
|
|
61
|
+
Full docs, tutorials, and command reference at **[docs.splent.io](https://docs.splent.io)**
|
|
75
62
|
|
|
76
63
|
## License
|
|
77
64
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# SPLENT CLI
|
|
2
|
+
|
|
3
|
+
Command-line tool for managing SPLENT products, features, databases, and environments.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
make setup # Prepare .env, start Docker, enter CLI container
|
|
9
|
+
splent --help # See all available commands
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Docker + Docker Compose
|
|
15
|
+
- Python 3.13+
|
|
16
|
+
|
|
17
|
+
## Documentation
|
|
18
|
+
|
|
19
|
+
Full docs, tutorials, and command reference at **[docs.splent.io](https://docs.splent.io)**
|
|
20
|
+
|
|
21
|
+
## License
|
|
22
|
+
|
|
23
|
+
Creative Commons CC BY 4.0 - SPLENT - Diverso Lab
|
|
@@ -44,9 +44,13 @@ class SPLENTCLI(click.Group):
|
|
|
44
44
|
with app.app_context():
|
|
45
45
|
registry = app.extensions.get("splent_feature_commands", {})
|
|
46
46
|
for feature_short, commands in registry.items():
|
|
47
|
+
cmd_names = ", ".join(c.name for c in commands if c.name)
|
|
47
48
|
group = click.Group(
|
|
48
49
|
name=f"feature:{feature_short}",
|
|
49
50
|
help=f"Commands contributed by splent_feature_{feature_short}.",
|
|
51
|
+
short_help=f"Subcommands: {cmd_names}"
|
|
52
|
+
if cmd_names
|
|
53
|
+
else f"Commands from splent_feature_{feature_short}.",
|
|
50
54
|
)
|
|
51
55
|
group.requires_app = True # type: ignore[attr-defined]
|
|
52
56
|
for cmd in commands:
|
|
@@ -92,12 +96,12 @@ class SPLENTCLI(click.Group):
|
|
|
92
96
|
def format_commands(self, ctx, formatter):
|
|
93
97
|
"""Group SPLENT commands by category in the CLI help output."""
|
|
94
98
|
all_cmds = self.list_commands(ctx)
|
|
99
|
+
feat_cmds = self._load_feature_commands()
|
|
95
100
|
groups = {
|
|
96
101
|
"🌿 Feature Management": [
|
|
97
102
|
cmd
|
|
98
103
|
for cmd in all_cmds
|
|
99
|
-
if cmd.startswith("feature:")
|
|
100
|
-
and cmd not in self._load_feature_commands()
|
|
104
|
+
if cmd.startswith("feature:") and cmd not in feat_cmds
|
|
101
105
|
],
|
|
102
106
|
"🏗️ Product Management": [
|
|
103
107
|
cmd for cmd in all_cmds if cmd.startswith("product:")
|
|
@@ -107,27 +111,28 @@ class SPLENTCLI(click.Group):
|
|
|
107
111
|
],
|
|
108
112
|
"🧱 Database": [cmd for cmd in all_cmds if cmd.startswith("db:")],
|
|
109
113
|
"💾 Cache": [cmd for cmd in all_cmds if cmd.startswith("cache:")],
|
|
114
|
+
"🔍 Checks": [cmd for cmd in all_cmds if cmd.startswith("check:")],
|
|
115
|
+
"📦 Export": [cmd for cmd in all_cmds if cmd.startswith("export:")],
|
|
116
|
+
"🚀 Release": [cmd for cmd in all_cmds if cmd.startswith("release:")],
|
|
110
117
|
"🧰 Utilities": [
|
|
111
118
|
cmd
|
|
112
119
|
for cmd in all_cmds
|
|
113
|
-
if cmd.startswith(
|
|
114
|
-
|
|
115
|
-
)
|
|
120
|
+
if cmd.startswith(("clear:", "command:", "env:", "env"))
|
|
121
|
+
or cmd in ("doctor", "tokens:setup", "version")
|
|
116
122
|
],
|
|
117
123
|
"🐍 Development & QA": [
|
|
118
124
|
cmd
|
|
119
125
|
for cmd in all_cmds
|
|
120
|
-
if cmd
|
|
121
|
-
],
|
|
122
|
-
"🔌 Feature Commands": [
|
|
123
|
-
cmd for cmd in all_cmds if cmd in self._load_feature_commands()
|
|
126
|
+
if cmd in ("lint", "coverage", "selenium", "locust", "locust:stop")
|
|
124
127
|
],
|
|
128
|
+
"🔌 Feature Commands": [cmd for cmd in all_cmds if cmd in feat_cmds],
|
|
125
129
|
}
|
|
130
|
+
total = 0
|
|
126
131
|
for title, cmds in groups.items():
|
|
127
132
|
if not cmds:
|
|
128
133
|
continue
|
|
129
134
|
|
|
130
|
-
with formatter.section(title):
|
|
135
|
+
with formatter.section(f"{title} ({len(cmds)})"):
|
|
131
136
|
rows = []
|
|
132
137
|
for cmd_name in sorted(cmds):
|
|
133
138
|
cmd = self.get_command(ctx, cmd_name)
|
|
@@ -136,6 +141,9 @@ class SPLENTCLI(click.Group):
|
|
|
136
141
|
rows.append((cmd_name, cmd.get_short_help_str()))
|
|
137
142
|
if rows:
|
|
138
143
|
formatter.write_dl(rows)
|
|
144
|
+
total += len(cmds)
|
|
145
|
+
|
|
146
|
+
formatter.write(f"\n {total} commands available.\n")
|
|
139
147
|
|
|
140
148
|
|
|
141
149
|
@click.group(cls=SPLENTCLI)
|
|
@@ -4,7 +4,10 @@ import click
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
@click.command(
|
|
7
|
+
@click.command(
|
|
8
|
+
"cache:clear",
|
|
9
|
+
short_help="Clear the feature version cache (.splent_cache), totally or partially.",
|
|
10
|
+
)
|
|
8
11
|
@click.option(
|
|
9
12
|
"--namespace",
|
|
10
13
|
default=None,
|
|
@@ -68,7 +68,7 @@ def _get_product_features(workspace: Path) -> dict:
|
|
|
68
68
|
|
|
69
69
|
@click.command(
|
|
70
70
|
"cache:outdated",
|
|
71
|
-
short_help="Show products using older versions than what
|
|
71
|
+
short_help="Show products using older feature versions than what is in cache.",
|
|
72
72
|
)
|
|
73
73
|
def cache_outdated():
|
|
74
74
|
"""
|
|
@@ -18,13 +18,17 @@ def check_github():
|
|
|
18
18
|
# --- presence checks ---
|
|
19
19
|
if not user:
|
|
20
20
|
click.echo(click.style("[✖] ", fg="red") + "GITHUB_USER not set in .env")
|
|
21
|
-
click.secho(
|
|
21
|
+
click.secho(
|
|
22
|
+
" Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
|
|
23
|
+
)
|
|
22
24
|
raise SystemExit(1)
|
|
23
25
|
click.echo(click.style("[✔] ", fg="green") + f"GITHUB_USER = {user}")
|
|
24
26
|
|
|
25
27
|
if not token:
|
|
26
28
|
click.echo(click.style("[✖] ", fg="red") + "GITHUB_TOKEN not set in .env")
|
|
27
|
-
click.secho(
|
|
29
|
+
click.secho(
|
|
30
|
+
" Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
|
|
31
|
+
)
|
|
28
32
|
raise SystemExit(1)
|
|
29
33
|
|
|
30
34
|
masked = token[:4] + "*" * (len(token) - 8) + token[-4:]
|
|
@@ -54,7 +58,7 @@ def check_github():
|
|
|
54
58
|
+ "Token invalid or expired (401 Unauthorized)"
|
|
55
59
|
)
|
|
56
60
|
click.secho(
|
|
57
|
-
" Run 'splent tokens' for instructions on obtaining a valid token.",
|
|
61
|
+
" Run 'splent tokens:setup' for instructions on obtaining a valid token.",
|
|
58
62
|
fg="bright_black",
|
|
59
63
|
)
|
|
60
64
|
elif e.code == 403:
|
|
@@ -63,7 +67,7 @@ def check_github():
|
|
|
63
67
|
+ "Token lacks required permissions (403 Forbidden)"
|
|
64
68
|
)
|
|
65
69
|
click.secho(
|
|
66
|
-
" Run 'splent tokens' — ensure the token has 'repo' scope.",
|
|
70
|
+
" Run 'splent tokens:setup' — ensure the token has 'repo' scope.",
|
|
67
71
|
fg="bright_black",
|
|
68
72
|
)
|
|
69
73
|
else:
|
|
@@ -220,6 +220,116 @@ def check_infra():
|
|
|
220
220
|
else:
|
|
221
221
|
_ok("No external networks required")
|
|
222
222
|
|
|
223
|
+
# --- Check 4: Dockerfile build contexts ---
|
|
224
|
+
click.echo()
|
|
225
|
+
click.echo(click.style(" Build contexts", bold=True))
|
|
226
|
+
build_count = 0
|
|
227
|
+
for label, cf in compose_files:
|
|
228
|
+
docker_dir = os.path.dirname(cf)
|
|
229
|
+
result = subprocess.run(
|
|
230
|
+
["docker", "compose", "-f", cf, "config", "--format", "json"],
|
|
231
|
+
capture_output=True,
|
|
232
|
+
text=True,
|
|
233
|
+
)
|
|
234
|
+
if result.returncode != 0:
|
|
235
|
+
continue
|
|
236
|
+
try:
|
|
237
|
+
config = json.loads(result.stdout)
|
|
238
|
+
except json.JSONDecodeError:
|
|
239
|
+
continue
|
|
240
|
+
for svc_name, svc_def in config.get("services", {}).items():
|
|
241
|
+
build_cfg = svc_def.get("build")
|
|
242
|
+
if not build_cfg:
|
|
243
|
+
continue
|
|
244
|
+
build_count += 1
|
|
245
|
+
if isinstance(build_cfg, dict):
|
|
246
|
+
ctx = build_cfg.get("context", ".")
|
|
247
|
+
dockerfile = build_cfg.get("dockerfile", "Dockerfile")
|
|
248
|
+
df_path = (
|
|
249
|
+
os.path.join(ctx, dockerfile)
|
|
250
|
+
if os.path.isabs(ctx)
|
|
251
|
+
else os.path.join(docker_dir, ctx, dockerfile)
|
|
252
|
+
)
|
|
253
|
+
else:
|
|
254
|
+
df_path = os.path.join(docker_dir, build_cfg, "Dockerfile")
|
|
255
|
+
|
|
256
|
+
if os.path.isfile(df_path):
|
|
257
|
+
_ok(f"{label}/{svc_name}: Dockerfile found")
|
|
258
|
+
else:
|
|
259
|
+
_fail(f"{label}/{svc_name}: Dockerfile not found at {df_path}")
|
|
260
|
+
|
|
261
|
+
if build_count == 0:
|
|
262
|
+
_ok("No custom builds (all services use pre-built images)")
|
|
263
|
+
|
|
264
|
+
# --- Check 5: Healthcheck coverage ---
|
|
265
|
+
click.echo()
|
|
266
|
+
click.echo(click.style(" Health checks", bold=True))
|
|
267
|
+
services_with_hc: set[str] = set()
|
|
268
|
+
services_depended_on: dict[str, str] = {} # depended_svc -> by_svc
|
|
269
|
+
|
|
270
|
+
for label, cf in compose_files:
|
|
271
|
+
result = subprocess.run(
|
|
272
|
+
["docker", "compose", "-f", cf, "config", "--format", "json"],
|
|
273
|
+
capture_output=True,
|
|
274
|
+
text=True,
|
|
275
|
+
)
|
|
276
|
+
if result.returncode != 0:
|
|
277
|
+
continue
|
|
278
|
+
try:
|
|
279
|
+
config = json.loads(result.stdout)
|
|
280
|
+
except json.JSONDecodeError:
|
|
281
|
+
continue
|
|
282
|
+
for svc_name, svc_def in config.get("services", {}).items():
|
|
283
|
+
if "healthcheck" in svc_def:
|
|
284
|
+
services_with_hc.add(svc_name)
|
|
285
|
+
for dep_svc, dep_cfg in (svc_def.get("depends_on") or {}).items():
|
|
286
|
+
if (
|
|
287
|
+
isinstance(dep_cfg, dict)
|
|
288
|
+
and dep_cfg.get("condition") == "service_healthy"
|
|
289
|
+
):
|
|
290
|
+
services_depended_on[dep_svc] = svc_name
|
|
291
|
+
|
|
292
|
+
missing_hc = {
|
|
293
|
+
svc: by
|
|
294
|
+
for svc, by in services_depended_on.items()
|
|
295
|
+
if svc not in services_with_hc
|
|
296
|
+
}
|
|
297
|
+
if missing_hc:
|
|
298
|
+
for svc, by in sorted(missing_hc.items()):
|
|
299
|
+
_fail(
|
|
300
|
+
f"'{by}' depends on '{svc}' being healthy, but '{svc}' has no healthcheck"
|
|
301
|
+
)
|
|
302
|
+
elif services_with_hc:
|
|
303
|
+
_ok(f"{len(services_with_hc)} service(s) with health checks")
|
|
304
|
+
else:
|
|
305
|
+
_ok("No health checks declared (none required)")
|
|
306
|
+
|
|
307
|
+
# --- Check 6: Volume name conflicts ---
|
|
308
|
+
click.echo()
|
|
309
|
+
click.echo(click.style(" Volumes", bold=True))
|
|
310
|
+
all_volumes: dict[str, list[str]] = {} # vol_name -> [labels]
|
|
311
|
+
for label, cf in compose_files:
|
|
312
|
+
result = subprocess.run(
|
|
313
|
+
["docker", "compose", "-f", cf, "config", "--format", "json"],
|
|
314
|
+
capture_output=True,
|
|
315
|
+
text=True,
|
|
316
|
+
)
|
|
317
|
+
if result.returncode != 0:
|
|
318
|
+
continue
|
|
319
|
+
try:
|
|
320
|
+
config = json.loads(result.stdout)
|
|
321
|
+
except json.JSONDecodeError:
|
|
322
|
+
continue
|
|
323
|
+
for vol_name in config.get("volumes", {}):
|
|
324
|
+
all_volumes.setdefault(vol_name, []).append(label)
|
|
325
|
+
|
|
326
|
+
vol_conflicts = {v: srcs for v, srcs in all_volumes.items() if len(srcs) > 1}
|
|
327
|
+
if vol_conflicts:
|
|
328
|
+
for vol, sources in sorted(vol_conflicts.items()):
|
|
329
|
+
_warn(f"Volume '{vol}' declared by multiple features: {', '.join(sources)}")
|
|
330
|
+
else:
|
|
331
|
+
_ok(f"No volume name conflicts ({len(all_volumes)} volumes)")
|
|
332
|
+
|
|
223
333
|
# --- Summary ---
|
|
224
334
|
click.echo()
|
|
225
335
|
if fail:
|
|
@@ -291,7 +291,7 @@ def _check_blueprints(counters):
|
|
|
291
291
|
|
|
292
292
|
@click.command(
|
|
293
293
|
"check:product",
|
|
294
|
-
short_help="
|
|
294
|
+
short_help="Quick health check: env vars, symlinks, and config files.",
|
|
295
295
|
)
|
|
296
296
|
@context.requires_product
|
|
297
297
|
def check_product():
|
|
@@ -26,7 +26,9 @@ def check_pypi(test: bool):
|
|
|
26
26
|
# --- presence checks ---
|
|
27
27
|
if not username:
|
|
28
28
|
click.echo(click.style("[✖] ", fg="red") + "TWINE_USERNAME not set in .env")
|
|
29
|
-
click.secho(
|
|
29
|
+
click.secho(
|
|
30
|
+
" Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
|
|
31
|
+
)
|
|
30
32
|
raise SystemExit(1)
|
|
31
33
|
click.echo(click.style("[✔] ", fg="green") + f"TWINE_USERNAME = {username}")
|
|
32
34
|
|
|
@@ -35,7 +37,9 @@ def check_pypi(test: bool):
|
|
|
35
37
|
click.style("[✖] ", fg="red")
|
|
36
38
|
+ "TWINE_PASSWORD (or PYPI_PASSWORD) not set in .env"
|
|
37
39
|
)
|
|
38
|
-
click.secho(
|
|
40
|
+
click.secho(
|
|
41
|
+
" Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
|
|
42
|
+
)
|
|
39
43
|
raise SystemExit(1)
|
|
40
44
|
|
|
41
45
|
masked = (
|
|
@@ -94,7 +98,7 @@ def check_pypi(test: bool):
|
|
|
94
98
|
+ f"Credentials rejected by {registry} (HTTP {e.code}) — token invalid or expired"
|
|
95
99
|
)
|
|
96
100
|
click.secho(
|
|
97
|
-
" Run 'splent tokens' for instructions on obtaining a valid token.",
|
|
101
|
+
" Run 'splent tokens:setup' for instructions on obtaining a valid token.",
|
|
98
102
|
fg="bright_black",
|
|
99
103
|
)
|
|
100
104
|
raise SystemExit(1)
|
|
@@ -52,8 +52,8 @@ def clean_build_artifacts(target_path: str | Path, *, quiet: bool = False):
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
@click.command(
|
|
55
|
-
"clear:
|
|
56
|
-
short_help="
|
|
55
|
+
"clear:build",
|
|
56
|
+
short_help="Remove __pycache__, .pytest_cache, dist/, build/, and egg-info.",
|
|
57
57
|
)
|
|
58
58
|
def clear_cache():
|
|
59
59
|
if not click.confirm("Are you sure you want to clear caches and build artifacts?"):
|
|
@@ -5,7 +5,7 @@ from splent_cli.utils.path_utils import PathUtils
|
|
|
5
5
|
from splent_cli.services import context
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
@click.command("clear:log", short_help="
|
|
8
|
+
@click.command("clear:log", short_help="Clear the app.log file.")
|
|
9
9
|
@context.requires_product
|
|
10
10
|
def clear_log():
|
|
11
11
|
log_file_path = PathUtils.get_app_log_dir()
|
|
@@ -8,7 +8,7 @@ from splent_cli.services import context
|
|
|
8
8
|
|
|
9
9
|
@click.command(
|
|
10
10
|
"clear:uploads",
|
|
11
|
-
short_help="
|
|
11
|
+
short_help="Clear the contents of the uploads directory.",
|
|
12
12
|
)
|
|
13
13
|
@context.requires_product
|
|
14
14
|
def clear_uploads():
|
|
@@ -5,7 +5,7 @@ import click
|
|
|
5
5
|
from splent_cli.utils.path_utils import PathUtils
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
@click.command("command:create",
|
|
8
|
+
@click.command("command:create", short_help="Create a new CLI command skeleton.")
|
|
9
9
|
@click.argument("name")
|
|
10
10
|
def command_create(name):
|
|
11
11
|
commands_dir = PathUtils.get_commands_path()
|
|
@@ -8,7 +8,7 @@ from splent_cli.services import context
|
|
|
8
8
|
|
|
9
9
|
@click.command(
|
|
10
10
|
"coverage",
|
|
11
|
-
short_help="
|
|
11
|
+
short_help="Run pytest with coverage reporting for a feature.",
|
|
12
12
|
)
|
|
13
13
|
@click.argument("module_name", required=False)
|
|
14
14
|
@click.option("--html", is_flag=True, help="Generates an HTML coverage report.")
|
|
@@ -7,7 +7,7 @@ from splent_cli.services import context
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@click.command(
|
|
10
|
-
"db:console", short_help="
|
|
10
|
+
"db:console", short_help="Open a MariaDB console with credentials from .env."
|
|
11
11
|
)
|
|
12
12
|
@context.requires_product
|
|
13
13
|
def db_console():
|
|
@@ -9,7 +9,7 @@ from splent_cli.services import context
|
|
|
9
9
|
|
|
10
10
|
@click.command(
|
|
11
11
|
"db:dump",
|
|
12
|
-
short_help="
|
|
12
|
+
short_help="Create a SQL dump of the MariaDB database.",
|
|
13
13
|
)
|
|
14
14
|
@click.argument("filename", required=False)
|
|
15
15
|
@context.requires_product
|
|
@@ -48,7 +48,7 @@ def _is_empty_migration(path: str) -> bool:
|
|
|
48
48
|
@requires_db
|
|
49
49
|
@click.command(
|
|
50
50
|
"db:migrate",
|
|
51
|
-
short_help="Generate
|
|
51
|
+
short_help="Generate a new migration from model changes and apply it.",
|
|
52
52
|
)
|
|
53
53
|
@click.argument("feature", required=False, default=None)
|
|
54
54
|
@click.option(
|
|
@@ -15,7 +15,7 @@ from splent_framework.managers.migration_manager import (
|
|
|
15
15
|
)
|
|
16
16
|
from splent_framework.utils.feature_utils import get_features_from_pyproject
|
|
17
17
|
from splent_framework.utils.path_utils import PathUtils
|
|
18
|
-
from splent_cli.commands.clear_uploads import clear_uploads
|
|
18
|
+
from splent_cli.commands.clear.clear_uploads import clear_uploads
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
@requires_db
|
|
@@ -123,6 +123,10 @@ def db_reset(yes):
|
|
|
123
123
|
name=name,
|
|
124
124
|
version=version,
|
|
125
125
|
)
|
|
126
|
+
except ImportError as e:
|
|
127
|
+
if "models" in str(e):
|
|
128
|
+
continue
|
|
129
|
+
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
126
130
|
except Exception as e:
|
|
127
131
|
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
128
132
|
|
|
@@ -84,6 +84,11 @@ def db_upgrade(feature):
|
|
|
84
84
|
name=name,
|
|
85
85
|
version=version,
|
|
86
86
|
)
|
|
87
|
+
except ImportError as e:
|
|
88
|
+
if "models" in str(e):
|
|
89
|
+
# Feature has migrations/ dir but no models module — skip silently
|
|
90
|
+
continue
|
|
91
|
+
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
87
92
|
except Exception as e:
|
|
88
93
|
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
89
94
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
splent doctor — Run all diagnostic checks on the workspace.
|
|
3
|
+
|
|
4
|
+
Organized in sections: environment, product, services, and credentials.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from splent_cli.commands.check.check_env import check_env
|
|
10
|
+
from splent_cli.commands.check.check_pyproject import check_pyproject
|
|
11
|
+
from splent_cli.commands.check.check_features import check_features
|
|
12
|
+
from splent_cli.commands.check.check_docker import check_docker
|
|
13
|
+
from splent_cli.commands.check.check_github import check_github
|
|
14
|
+
from splent_cli.commands.check.check_pypi import check_pypi
|
|
15
|
+
from splent_cli.commands.check.check_infra import check_infra
|
|
16
|
+
from splent_cli.commands.check.check_product import check_product
|
|
17
|
+
from splent_cli.commands.feature.feature_status import feature_status
|
|
18
|
+
from splent_cli.commands.database.db_status import db_status
|
|
19
|
+
from splent_cli.commands.product.product_containers import product_docker
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
SECTIONS = [
|
|
23
|
+
(
|
|
24
|
+
"Environment",
|
|
25
|
+
[
|
|
26
|
+
("check:env", check_env, False, False),
|
|
27
|
+
("check:pyproject", check_pyproject, False, False),
|
|
28
|
+
("check:docker", check_docker, False, False),
|
|
29
|
+
("check:infra", check_infra, False, False),
|
|
30
|
+
],
|
|
31
|
+
),
|
|
32
|
+
(
|
|
33
|
+
"Product",
|
|
34
|
+
[
|
|
35
|
+
("check:product", check_product, False, False),
|
|
36
|
+
("check:features", check_features, False, False),
|
|
37
|
+
("feature:status", feature_status, False, False),
|
|
38
|
+
],
|
|
39
|
+
),
|
|
40
|
+
(
|
|
41
|
+
"Services",
|
|
42
|
+
[
|
|
43
|
+
("product:containers", product_docker, False, False),
|
|
44
|
+
("db:status", db_status, True, False),
|
|
45
|
+
],
|
|
46
|
+
),
|
|
47
|
+
(
|
|
48
|
+
"Credentials",
|
|
49
|
+
[
|
|
50
|
+
("check:github", check_github, False, True),
|
|
51
|
+
("check:pypi", check_pypi, False, True),
|
|
52
|
+
],
|
|
53
|
+
),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@click.command(
|
|
58
|
+
"doctor", short_help="Run all diagnostic checks sequentially and report a summary."
|
|
59
|
+
)
|
|
60
|
+
@click.option("--fast", is_flag=True, help="Skip slow checks (network, database).")
|
|
61
|
+
def doctor(fast):
|
|
62
|
+
"""
|
|
63
|
+
Run all diagnostic checks on the workspace and report a summary.
|
|
64
|
+
|
|
65
|
+
\b
|
|
66
|
+
Sections:
|
|
67
|
+
Environment — Python, env vars, pyproject, Docker, infrastructure
|
|
68
|
+
Product — product health, feature cache/symlinks, feature status
|
|
69
|
+
Services — containers, database migrations
|
|
70
|
+
Credentials — GitHub and PyPI tokens (skipped with --fast)
|
|
71
|
+
|
|
72
|
+
\b
|
|
73
|
+
Use --fast to skip network checks (GitHub, PyPI) and database checks.
|
|
74
|
+
|
|
75
|
+
\b
|
|
76
|
+
For full product validation (UVL + contracts + imports), run:
|
|
77
|
+
splent product:validate
|
|
78
|
+
"""
|
|
79
|
+
click.echo(click.style("\n SPLENT Doctor\n", fg="cyan", bold=True))
|
|
80
|
+
|
|
81
|
+
passed = 0
|
|
82
|
+
failed = 0
|
|
83
|
+
skipped = 0
|
|
84
|
+
|
|
85
|
+
for section_name, checks in SECTIONS:
|
|
86
|
+
click.echo(click.style(f" ── {section_name} ──", fg="cyan", bold=True))
|
|
87
|
+
click.echo()
|
|
88
|
+
|
|
89
|
+
for name, cmd, requires_db, network_only in checks:
|
|
90
|
+
if fast and (network_only or requires_db):
|
|
91
|
+
click.echo(
|
|
92
|
+
click.style(f" {name}", fg="bright_black")
|
|
93
|
+
+ click.style(" (skipped)", fg="bright_black")
|
|
94
|
+
)
|
|
95
|
+
skipped += 1
|
|
96
|
+
click.echo()
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
click.echo(click.style(f" {name}", bold=True))
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
ctx = click.Context(
|
|
103
|
+
cmd, info_name=name, parent=click.get_current_context()
|
|
104
|
+
)
|
|
105
|
+
if requires_db:
|
|
106
|
+
from splent_cli.utils.dynamic_imports import get_app
|
|
107
|
+
|
|
108
|
+
app = get_app()
|
|
109
|
+
with app.app_context():
|
|
110
|
+
ctx.invoke(cmd)
|
|
111
|
+
else:
|
|
112
|
+
ctx.invoke(cmd)
|
|
113
|
+
passed += 1
|
|
114
|
+
except SystemExit as e:
|
|
115
|
+
if e.code and e.code != 0:
|
|
116
|
+
failed += 1
|
|
117
|
+
else:
|
|
118
|
+
passed += 1
|
|
119
|
+
except Exception as e:
|
|
120
|
+
click.secho(f" Unexpected error: {e}", fg="red")
|
|
121
|
+
failed += 1
|
|
122
|
+
|
|
123
|
+
click.echo()
|
|
124
|
+
|
|
125
|
+
# Summary
|
|
126
|
+
click.echo(click.style(" ── Summary ──", fg="cyan", bold=True))
|
|
127
|
+
click.echo(f" Passed: {passed}")
|
|
128
|
+
if skipped:
|
|
129
|
+
click.echo(f" Skipped: {skipped}")
|
|
130
|
+
if failed:
|
|
131
|
+
click.echo(f" Failed: {failed}")
|
|
132
|
+
click.echo()
|
|
133
|
+
click.secho(" Some checks failed. Review the output above.", fg="red")
|
|
134
|
+
raise SystemExit(1)
|
|
135
|
+
else:
|
|
136
|
+
click.echo()
|
|
137
|
+
click.secho(" All checks passed.", fg="green")
|
|
138
|
+
|
|
139
|
+
click.echo()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
cli_command = doctor
|
|
@@ -51,7 +51,10 @@ def _read_env_file(path: Path) -> dict[str, str]:
|
|
|
51
51
|
return result
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
@click.command(
|
|
54
|
+
@click.command(
|
|
55
|
+
"env:list",
|
|
56
|
+
short_help="List all variables in the workspace .env file (grouped, with masking).",
|
|
57
|
+
)
|
|
55
58
|
@click.argument("filter", required=False, metavar="FILTER")
|
|
56
59
|
@click.option(
|
|
57
60
|
"--keys-only", is_flag=True, help="Print only variable names, one per line."
|
|
@@ -12,7 +12,7 @@ def _mask(value: str, key: str) -> str:
|
|
|
12
12
|
|
|
13
13
|
@click.command(
|
|
14
14
|
"env:show",
|
|
15
|
-
short_help="
|
|
15
|
+
short_help="Compare .env file values against what is loaded in the shell.",
|
|
16
16
|
)
|
|
17
17
|
def env_show():
|
|
18
18
|
"""Check which variables from .env are actually available in the current Bash shell."""
|