splent-cli 1.5.0__tar.gz → 1.6.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.0/src/splent_cli.egg-info → splent_cli-1.6.0}/PKG-INFO +4 -17
- splent_cli-1.6.0/README.md +23 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/pyproject.toml +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/cli.py +18 -10
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_clear.py +4 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_outdated.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_github.py +8 -4
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_infra.py +110 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_product.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_pypi.py +7 -3
- splent_cli-1.5.0/src/splent_cli/commands/clear_cache.py → splent_cli-1.6.0/src/splent_cli/commands/clear/clear_build.py +2 -2
- {splent_cli-1.5.0/src/splent_cli/commands → splent_cli-1.6.0/src/splent_cli/commands/clear}/clear_log.py +1 -1
- {splent_cli-1.5.0/src/splent_cli/commands → splent_cli-1.6.0/src/splent_cli/commands/clear}/clear_uploads.py +1 -1
- {splent_cli-1.5.0/src/splent_cli/commands → splent_cli-1.6.0/src/splent_cli/commands/command}/command_create.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/coverage.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_console.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_dump.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_migrate.py +7 -34
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_reset.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_rollback.py +22 -1
- splent_cli-1.6.0/src/splent_cli/commands/doctor.py +142 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/env/env_list.py +4 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/env/env_show.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_add.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_attach.py +1 -1
- splent_cli-1.6.0/src/splent_cli/commands/feature/feature_clean.py +365 -0
- splent_cli-1.5.0/src/splent_cli/commands/feature/feature_diff.py → splent_cli-1.6.0/src/splent_cli/commands/feature/feature_compat.py +95 -214
- {splent_cli-1.5.0/src/splent_cli/commands → splent_cli-1.6.0/src/splent_cli/commands/feature}/feature_compile.py +2 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_contract.py +35 -0
- splent_cli-1.6.0/src/splent_cli/commands/feature/feature_create.py +275 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_delete.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_detach.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_discard.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_drift.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_env.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_inject_config.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_list.py +2 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_outdated.py +2 -2
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_pin.py +1 -1
- splent_cli-1.6.0/src/splent_cli/commands/feature/feature_refine.py +879 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_release.py +139 -19
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_remove.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_rename.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_status.py +1 -1
- splent_cli-1.5.0/src/splent_cli/commands/feature/feature_edit.py → splent_cli-1.6.0/src/splent_cli/commands/feature/feature_unlock.py +12 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_upgrade.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_xray.py +210 -182
- splent_cli-1.5.0/src/splent_cli/commands/linter.py → splent_cli-1.6.0/src/splent_cli/commands/lint.py +1 -3
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/locust.py +2 -4
- splent_cli-1.5.0/src/splent_cli/commands/product/product_complete.py → splent_cli-1.6.0/src/splent_cli/commands/product/product_auto_require.py +2 -2
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_build.py +142 -12
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_clean.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_config.py +1 -1
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_configure.py +2 -2
- splent_cli-1.5.0/src/splent_cli/commands/product/product_status.py → splent_cli-1.6.0/src/splent_cli/commands/product/product_containers.py +2 -2
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_create.py +1 -1
- splent_cli-1.6.0/src/splent_cli/commands/product/product_deploy.py +276 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_derive.py +4 -4
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_deselect.py +9 -4
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_env.py +1 -3
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_missing.py +1 -1
- splent_cli-1.5.0/src/splent_cli/commands/product/product_sync.py → splent_cli-1.6.0/src/splent_cli/commands/product/product_resolve.py +3 -3
- splent_cli-1.6.0/src/splent_cli/commands/product/product_restart.py +248 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_select.py +1 -1
- splent_cli-1.6.0/src/splent_cli/commands/product/product_up.py +267 -0
- splent_cli-1.6.0/src/splent_cli/commands/product/product_validate.py +371 -0
- splent_cli-1.6.0/src/splent_cli/commands/spl/__init__.py +0 -0
- splent_cli-1.5.0/src/splent_cli/commands/spl/spl_fix.py → splent_cli-1.6.0/src/splent_cli/commands/spl/spl_add_constraints.py +3 -3
- splent_cli-1.5.0/src/splent_cli/commands/spl/spl_configs.py → splent_cli-1.6.0/src/splent_cli/commands/spl/spl_configurations.py +2 -2
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_features.py +1 -1
- splent_cli-1.5.0/src/splent_cli/commands/tokens.py → splent_cli-1.6.0/src/splent_cli/commands/tokens_setup.py +6 -3
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/version.py +4 -1
- splent_cli-1.6.0/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/services/compose.py +5 -3
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/services/context.py +3 -3
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/services/preflight.py +31 -13
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/services/release.py +2 -2
- splent_cli-1.6.0/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/integrity.py +2 -2
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/lifecycle.py +3 -3
- {splent_cli-1.5.0 → splent_cli-1.6.0/src/splent_cli.egg-info}/PKG-INFO +4 -17
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli.egg-info/SOURCES.txt +20 -15
- splent_cli-1.5.0/README.md +0 -36
- splent_cli-1.5.0/src/splent_cli/commands/doctor.py +0 -140
- splent_cli-1.5.0/src/splent_cli/commands/feature/feature_create.py +0 -214
- splent_cli-1.5.0/src/splent_cli/commands/product/product_deploy.py +0 -173
- splent_cli-1.5.0/src/splent_cli/commands/product/product_restart.py +0 -64
- splent_cli-1.5.0/src/splent_cli/commands/product/product_up.py +0 -105
- splent_cli-1.5.0/src/splent_cli/commands/product/product_validate.py +0 -157
- {splent_cli-1.5.0 → splent_cli-1.6.0}/LICENSE +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/setup.cfg +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.5.0/src/splent_cli/commands/product → splent_cli-1.6.0/src/splent_cli/commands/clear}/__init__.py +0 -0
- {splent_cli-1.5.0/src/splent_cli/commands/release → splent_cli-1.6.0/src/splent_cli/commands/command}/__init__.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.5.0/src/splent_cli/commands/spl → splent_cli-1.6.0/src/splent_cli/commands/export}/__init__.py +0 -0
- {splent_cli-1.5.0/src/splent_cli/commands → splent_cli-1.6.0/src/splent_cli/commands/export}/export_puml.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.5.0/src/splent_cli/services → splent_cli-1.6.0/src/splent_cli/commands/product}/__init__.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.5.0/src/splent_cli/utils → splent_cli-1.6.0/src/splent_cli/commands/release}/__init__.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.5.0 → splent_cli-1.6.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.6.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
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
|
-
from
|
|
5
|
-
from flask_migrate import migrate as alembic_migrate, upgrade as alembic_upgrade
|
|
4
|
+
from flask_migrate import migrate as alembic_migrate
|
|
6
5
|
|
|
7
6
|
from splent_cli.utils.decorators import requires_db
|
|
8
7
|
from splent_cli.services import context
|
|
9
8
|
from splent_cli.utils.lifecycle import (
|
|
10
|
-
advance_state,
|
|
11
9
|
resolve_feature_key_from_entry,
|
|
12
10
|
require_editable,
|
|
13
11
|
)
|
|
@@ -50,19 +48,20 @@ def _is_empty_migration(path: str) -> bool:
|
|
|
50
48
|
@requires_db
|
|
51
49
|
@click.command(
|
|
52
50
|
"db:migrate",
|
|
53
|
-
short_help="Generate
|
|
51
|
+
short_help="Generate a new migration from model changes and apply it.",
|
|
54
52
|
)
|
|
55
53
|
@click.argument("feature", required=False, default=None)
|
|
54
|
+
@click.option(
|
|
55
|
+
"-m", "message", default=None, help="Migration message (defaults to feature name)."
|
|
56
|
+
)
|
|
56
57
|
@context.requires_product
|
|
57
|
-
def db_migrate(feature):
|
|
58
|
+
def db_migrate(feature, message):
|
|
58
59
|
"""
|
|
59
60
|
Generate new migration files for features that have schema changes,
|
|
60
61
|
then apply all pending migrations.
|
|
61
62
|
|
|
62
63
|
Empty migrations (no schema changes detected) are automatically removed.
|
|
63
64
|
"""
|
|
64
|
-
app = current_app
|
|
65
|
-
|
|
66
65
|
if feature:
|
|
67
66
|
dirs = {}
|
|
68
67
|
mdir = MigrationManager.get_feature_migration_dir(feature)
|
|
@@ -84,7 +83,6 @@ def db_migrate(feature):
|
|
|
84
83
|
|
|
85
84
|
# Build entry→key lookup for manifest updates
|
|
86
85
|
product_path = PathUtils.get_app_base_dir()
|
|
87
|
-
product_name = os.getenv("SPLENT_APP", "")
|
|
88
86
|
entry_lookup = {}
|
|
89
87
|
for entry in get_features_from_pyproject() or []:
|
|
90
88
|
key, ns, name, version = resolve_feature_key_from_entry(entry)
|
|
@@ -110,7 +108,7 @@ def db_migrate(feature):
|
|
|
110
108
|
alembic_logger.setLevel(logging.WARNING)
|
|
111
109
|
|
|
112
110
|
try:
|
|
113
|
-
alembic_migrate(directory=mdir, message=feat)
|
|
111
|
+
alembic_migrate(directory=mdir, message=message or feat)
|
|
114
112
|
except Exception as e:
|
|
115
113
|
if os.getenv("SPLENT_DEBUG"):
|
|
116
114
|
click.secho(
|
|
@@ -142,30 +140,5 @@ def db_migrate(feature):
|
|
|
142
140
|
else:
|
|
143
141
|
click.echo(click.style(f" ✔ {feat}: up to date", fg="green"))
|
|
144
142
|
|
|
145
|
-
# Apply pending migrations
|
|
146
|
-
try:
|
|
147
|
-
alembic_upgrade(directory=mdir)
|
|
148
|
-
revision = MigrationManager.get_current_feature_revision(
|
|
149
|
-
feat, app.extensions["migrate"].db.engine
|
|
150
|
-
)
|
|
151
|
-
MigrationManager.update_feature_status(app, feat, revision)
|
|
152
|
-
click.echo(click.style(f" ✅ {feat} → {revision or 'head'}", fg="green"))
|
|
153
|
-
|
|
154
|
-
# Advance lifecycle state to "migrated"
|
|
155
|
-
info = entry_lookup.get(feat)
|
|
156
|
-
if info:
|
|
157
|
-
key, ns, name, version = info
|
|
158
|
-
advance_state(
|
|
159
|
-
product_path,
|
|
160
|
-
product_name,
|
|
161
|
-
key,
|
|
162
|
-
to="migrated",
|
|
163
|
-
namespace=ns,
|
|
164
|
-
name=name,
|
|
165
|
-
version=version,
|
|
166
|
-
)
|
|
167
|
-
except Exception as e:
|
|
168
|
-
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
169
|
-
|
|
170
143
|
|
|
171
144
|
cli_command = db_migrate
|
|
@@ -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
|
|
@@ -206,7 +206,28 @@ def db_rollback(feature, steps, cascade):
|
|
|
206
206
|
)
|
|
207
207
|
break
|
|
208
208
|
except Exception as e:
|
|
209
|
-
click.secho(f"
|
|
209
|
+
click.secho(f" {feature}: {e}", fg="red")
|
|
210
|
+
raise SystemExit(1)
|
|
211
|
+
|
|
212
|
+
# Offer to delete migration files if fully rolled back to base
|
|
213
|
+
if revision is None:
|
|
214
|
+
versions_dir = os.path.join(mdir, "versions")
|
|
215
|
+
if os.path.isdir(versions_dir):
|
|
216
|
+
migration_files = [
|
|
217
|
+
f
|
|
218
|
+
for f in os.listdir(versions_dir)
|
|
219
|
+
if f.endswith(".py") and not f.startswith("__")
|
|
220
|
+
]
|
|
221
|
+
if migration_files and click.confirm(
|
|
222
|
+
f" Delete {len(migration_files)} migration file(s)?",
|
|
223
|
+
default=False,
|
|
224
|
+
):
|
|
225
|
+
for f in migration_files:
|
|
226
|
+
os.remove(os.path.join(versions_dir, f))
|
|
227
|
+
click.echo(
|
|
228
|
+
click.style(" deleted: ", dim=True)
|
|
229
|
+
+ f"{len(migration_files)} file(s)"
|
|
230
|
+
)
|
|
210
231
|
|
|
211
232
|
|
|
212
233
|
cli_command = db_rollback
|