splent-cli 1.9.2__tar.gz → 1.11.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.9.2/src/splent_cli.egg-info → splent_cli-1.11.0}/PKG-INFO +2 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/pyproject.toml +7 -3
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/check/check_features.py +9 -2
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/check/check_infra.py +51 -49
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/coverage.py +7 -4
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/database/db_console.py +28 -13
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/database/db_dump.py +25 -10
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/database/db_migrate.py +25 -7
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/database/db_reset.py +27 -4
- splent_cli-1.11.0/src/splent_cli/commands/database/db_restore.py +117 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/database/db_rollback.py +44 -3
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/database/db_seed.py +16 -16
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/database/db_upgrade.py +8 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/doctor.py +49 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_add.py +4 -7
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_attach.py +4 -9
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_clean.py +13 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_clone.py +52 -53
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_compat.py +2 -3
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_compile.py +24 -9
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_contract.py +3 -4
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_create.py +6 -6
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_detach.py +31 -10
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_discard.py +13 -3
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_env.py +6 -2
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_fork.py +41 -6
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_git.py +2 -2
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_hook_add.py +17 -32
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_install.py +78 -49
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_order.py +2 -3
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_outdated.py +9 -11
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_pin.py +3 -5
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_pip_install.py +7 -7
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_pull.py +6 -2
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_refine.py +91 -13
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_release.py +44 -5
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_remove.py +3 -5
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_rename.py +81 -37
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_search.py +19 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_status.py +32 -4
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_test.py +33 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_translate.py +32 -12
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_unlock.py +82 -18
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_upgrade.py +43 -15
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_versions.py +19 -8
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_xray.py +4 -7
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/lint.py +18 -6
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/locust.py +85 -14
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_auto_require.py +24 -90
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_build.py +15 -11
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_clean.py +20 -9
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_config.py +4 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_configure.py +41 -38
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_console.py +4 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_create.py +33 -27
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_deploy.py +13 -2
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_derive.py +3 -3
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_deselect.py +2 -2
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_env.py +56 -16
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_list.py +6 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_release.py +56 -36
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_resolve.py +66 -3
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_restart.py +55 -36
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_routes.py +4 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_run.py +26 -8
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_select.py +9 -2
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_signals.py +4 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_test.py +4 -3
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_up.py +57 -35
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/release/release_core.py +26 -4
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/selenium.py +16 -12
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/spl_add_constraints.py +36 -3
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/spl_add_feature.py +49 -2
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/uvl/uvl_utils.py +7 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/services/preflight.py +43 -27
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/services/release.py +155 -17
- splent_cli-1.11.0/src/splent_cli/utils/cache_utils.py +115 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/command_loader.py +6 -3
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/contract_freshness.py +10 -2
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/dynamic_imports.py +24 -12
- splent_cli-1.11.0/src/splent_cli/utils/feature_installer.py +86 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/feature_utils.py +66 -9
- splent_cli-1.11.0/src/splent_cli/utils/git_url.py +221 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/integrity.py +26 -12
- splent_cli-1.11.0/src/splent_cli/utils/io_utils.py +89 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/manifest.py +12 -2
- splent_cli-1.11.0/src/splent_cli/utils/proc.py +109 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/template_drift.py +12 -5
- {splent_cli-1.9.2 → splent_cli-1.11.0/src/splent_cli.egg-info}/PKG-INFO +2 -1
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli.egg-info/SOURCES.txt +2 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli.egg-info/requires.txt +1 -0
- splent_cli-1.9.2/src/splent_cli/commands/database/db_restore.py +0 -77
- splent_cli-1.9.2/src/splent_cli/utils/cache_utils.py +0 -54
- splent_cli-1.9.2/src/splent_cli/utils/feature_installer.py +0 -54
- splent_cli-1.9.2/src/splent_cli/utils/git_url.py +0 -67
- {splent_cli-1.9.2 → splent_cli-1.11.0}/LICENSE +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/README.md +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/setup.cfg +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/product/product_validate.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.9.2 → splent_cli-1.11.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.11.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
|
|
@@ -11,6 +11,7 @@ License-File: LICENSE
|
|
|
11
11
|
Requires-Dist: click==8.1.8
|
|
12
12
|
Requires-Dist: packaging>=24.0
|
|
13
13
|
Requires-Dist: requests>=2.31.0
|
|
14
|
+
Requires-Dist: tomli_w>=1.0.0
|
|
14
15
|
Provides-Extra: dev
|
|
15
16
|
Requires-Dist: setuptools==80.3.1; extra == "dev"
|
|
16
17
|
Requires-Dist: pytest==8.3.4; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "splent_cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.11.0"
|
|
8
8
|
description = "SPLENT-CLI is a CLI to be able to work on your development more easily."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -15,6 +15,7 @@ dependencies = [
|
|
|
15
15
|
"click==8.1.8",
|
|
16
16
|
"packaging>=24.0",
|
|
17
17
|
"requests>=2.31.0",
|
|
18
|
+
"tomli_w>=1.0.0",
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
[project.optional-dependencies]
|
|
@@ -62,8 +63,11 @@ where = ["src"]
|
|
|
62
63
|
testpaths = ["tests"]
|
|
63
64
|
pythonpath = ["src"]
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
line
|
|
66
|
+
# Canonical formatter/linter is Ruff (`splent lint` runs `ruff check` +
|
|
67
|
+
# `ruff format`). Ruff's default line length (88) is the project standard;
|
|
68
|
+
# set explicitly here so it is unambiguous.
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 88
|
|
67
71
|
|
|
68
72
|
[project.urls]
|
|
69
73
|
Homepage = "https://github.com/diverso-lab/splent_cli"
|
|
@@ -54,8 +54,15 @@ def check_features():
|
|
|
54
54
|
_fail("pyproject.toml not found")
|
|
55
55
|
raise SystemExit(1)
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
try:
|
|
58
|
+
with open(pyproject_path, "rb") as f:
|
|
59
|
+
data = tomllib.load(f)
|
|
60
|
+
except tomllib.TOMLDecodeError as e:
|
|
61
|
+
_fail(f"pyproject.toml is not valid TOML: {e}")
|
|
62
|
+
raise SystemExit(1)
|
|
63
|
+
except OSError as e:
|
|
64
|
+
_fail(f"Could not read pyproject.toml: {e}")
|
|
65
|
+
raise SystemExit(1)
|
|
59
66
|
|
|
60
67
|
env = os.getenv("SPLENT_ENV")
|
|
61
68
|
features = read_features_from_data(data, env)
|
|
@@ -7,18 +7,43 @@ import subprocess
|
|
|
7
7
|
import json
|
|
8
8
|
|
|
9
9
|
import click
|
|
10
|
-
import tomllib
|
|
11
10
|
|
|
12
11
|
from splent_cli.services import context, compose
|
|
13
12
|
from splent_cli.utils.feature_utils import read_features_from_data
|
|
13
|
+
from splent_cli.utils.io_utils import load_toml
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _Result:
|
|
17
|
+
"""Minimal stand-in for CompletedProcess used by the guarded ``_run``."""
|
|
18
|
+
|
|
19
|
+
__slots__ = ("returncode", "stdout", "stderr")
|
|
20
|
+
|
|
21
|
+
def __init__(self, returncode: int, stdout: str = "", stderr: str = ""):
|
|
22
|
+
self.returncode = returncode
|
|
23
|
+
self.stdout = stdout
|
|
24
|
+
self.stderr = stderr
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _run(cmd: list) -> _Result:
|
|
28
|
+
"""Run a docker command, never crashing the diagnostic.
|
|
29
|
+
|
|
30
|
+
Missing tool (FileNotFoundError) and timeouts are mapped to a non-zero
|
|
31
|
+
returncode so callers treat them as a normal FAIL/skip, exactly like
|
|
32
|
+
check_docker._run.
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
r = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
36
|
+
return _Result(r.returncode, r.stdout, r.stderr)
|
|
37
|
+
except FileNotFoundError:
|
|
38
|
+
return _Result(1, "", "command not found")
|
|
39
|
+
except subprocess.TimeoutExpired:
|
|
40
|
+
return _Result(1, "", "timed out")
|
|
14
41
|
|
|
15
42
|
|
|
16
43
|
def _parse_compose_ports(compose_file: str) -> list[tuple[int, str, str]]:
|
|
17
44
|
"""Return [(host_port, service_name, source_label)] from a compose file."""
|
|
18
|
-
result =
|
|
19
|
-
["docker", "compose", "-f", compose_file, "config", "--format", "json"]
|
|
20
|
-
capture_output=True,
|
|
21
|
-
text=True,
|
|
45
|
+
result = _run(
|
|
46
|
+
["docker", "compose", "-f", compose_file, "config", "--format", "json"]
|
|
22
47
|
)
|
|
23
48
|
if result.returncode != 0:
|
|
24
49
|
return []
|
|
@@ -41,10 +66,8 @@ def _parse_compose_ports(compose_file: str) -> list[tuple[int, str, str]]:
|
|
|
41
66
|
|
|
42
67
|
def _parse_compose_services(compose_file: str) -> list[tuple[str, str, str]]:
|
|
43
68
|
"""Return [(service_name, container_name_or_None, source_label)]."""
|
|
44
|
-
result =
|
|
45
|
-
["docker", "compose", "-f", compose_file, "config", "--format", "json"]
|
|
46
|
-
capture_output=True,
|
|
47
|
-
text=True,
|
|
69
|
+
result = _run(
|
|
70
|
+
["docker", "compose", "-f", compose_file, "config", "--format", "json"]
|
|
48
71
|
)
|
|
49
72
|
if result.returncode != 0:
|
|
50
73
|
return []
|
|
@@ -97,8 +120,7 @@ def check_infra():
|
|
|
97
120
|
_fail("pyproject.toml not found")
|
|
98
121
|
raise SystemExit(1)
|
|
99
122
|
|
|
100
|
-
|
|
101
|
-
data = tomllib.load(f)
|
|
123
|
+
data = load_toml(pyproject_path, what="pyproject.toml")
|
|
102
124
|
|
|
103
125
|
env = os.getenv("SPLENT_ENV", "dev")
|
|
104
126
|
features = read_features_from_data(data, env)
|
|
@@ -132,21 +154,19 @@ def check_infra():
|
|
|
132
154
|
else:
|
|
133
155
|
_ok(f"No port conflicts ({len(all_ports)} ports declared)")
|
|
134
156
|
|
|
135
|
-
# Check against running containers
|
|
157
|
+
# Check against running containers (query docker ps once, not per-port)
|
|
136
158
|
running_conflicts = []
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if f":{port}->" in ports_str:
|
|
149
|
-
running_conflicts.append((port, name))
|
|
159
|
+
ps_result = _run(["docker", "ps", "--format", "{{.ID}}\t{{.Names}}\t{{.Ports}}"])
|
|
160
|
+
if ps_result.returncode == 0:
|
|
161
|
+
ps_lines = ps_result.stdout.splitlines()
|
|
162
|
+
for port in all_ports:
|
|
163
|
+
for line in ps_lines:
|
|
164
|
+
parts = line.split("\t", 2)
|
|
165
|
+
if len(parts) < 3:
|
|
166
|
+
continue
|
|
167
|
+
cid, name, ports_str = parts
|
|
168
|
+
if f":{port}->" in ports_str:
|
|
169
|
+
running_conflicts.append((port, name))
|
|
150
170
|
|
|
151
171
|
if running_conflicts:
|
|
152
172
|
for port, cname in running_conflicts:
|
|
@@ -189,11 +209,7 @@ def check_infra():
|
|
|
189
209
|
click.echo(click.style(" Networks", bold=True))
|
|
190
210
|
required_networks: set[str] = set()
|
|
191
211
|
for label, cf in compose_files:
|
|
192
|
-
result =
|
|
193
|
-
["docker", "compose", "-f", cf, "config", "--format", "json"],
|
|
194
|
-
capture_output=True,
|
|
195
|
-
text=True,
|
|
196
|
-
)
|
|
212
|
+
result = _run(["docker", "compose", "-f", cf, "config", "--format", "json"])
|
|
197
213
|
if result.returncode != 0:
|
|
198
214
|
continue
|
|
199
215
|
try:
|
|
@@ -205,10 +221,8 @@ def check_infra():
|
|
|
205
221
|
required_networks.add(net_name)
|
|
206
222
|
|
|
207
223
|
if required_networks:
|
|
208
|
-
existing_networks =
|
|
209
|
-
["docker", "network", "ls", "--format", "{{.Name}}"]
|
|
210
|
-
capture_output=True,
|
|
211
|
-
text=True,
|
|
224
|
+
existing_networks = _run(
|
|
225
|
+
["docker", "network", "ls", "--format", "{{.Name}}"]
|
|
212
226
|
).stdout.splitlines()
|
|
213
227
|
for net in sorted(required_networks):
|
|
214
228
|
if net in existing_networks:
|
|
@@ -226,11 +240,7 @@ def check_infra():
|
|
|
226
240
|
build_count = 0
|
|
227
241
|
for label, cf in compose_files:
|
|
228
242
|
docker_dir = os.path.dirname(cf)
|
|
229
|
-
result =
|
|
230
|
-
["docker", "compose", "-f", cf, "config", "--format", "json"],
|
|
231
|
-
capture_output=True,
|
|
232
|
-
text=True,
|
|
233
|
-
)
|
|
243
|
+
result = _run(["docker", "compose", "-f", cf, "config", "--format", "json"])
|
|
234
244
|
if result.returncode != 0:
|
|
235
245
|
continue
|
|
236
246
|
try:
|
|
@@ -268,11 +278,7 @@ def check_infra():
|
|
|
268
278
|
services_depended_on: dict[str, str] = {} # depended_svc -> by_svc
|
|
269
279
|
|
|
270
280
|
for label, cf in compose_files:
|
|
271
|
-
result =
|
|
272
|
-
["docker", "compose", "-f", cf, "config", "--format", "json"],
|
|
273
|
-
capture_output=True,
|
|
274
|
-
text=True,
|
|
275
|
-
)
|
|
281
|
+
result = _run(["docker", "compose", "-f", cf, "config", "--format", "json"])
|
|
276
282
|
if result.returncode != 0:
|
|
277
283
|
continue
|
|
278
284
|
try:
|
|
@@ -309,11 +315,7 @@ def check_infra():
|
|
|
309
315
|
click.echo(click.style(" Volumes", bold=True))
|
|
310
316
|
all_volumes: dict[str, list[str]] = {} # vol_name -> [labels]
|
|
311
317
|
for label, cf in compose_files:
|
|
312
|
-
result =
|
|
313
|
-
["docker", "compose", "-f", cf, "config", "--format", "json"],
|
|
314
|
-
capture_output=True,
|
|
315
|
-
text=True,
|
|
316
|
-
)
|
|
318
|
+
result = _run(["docker", "compose", "-f", cf, "config", "--format", "json"])
|
|
317
319
|
if result.returncode != 0:
|
|
318
320
|
continue
|
|
319
321
|
try:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import click
|
|
2
|
-
import subprocess
|
|
3
2
|
import os
|
|
4
3
|
|
|
5
4
|
from splent_cli.utils.path_utils import PathUtils
|
|
5
|
+
from splent_cli.utils.proc import run
|
|
6
6
|
from splent_cli.services import context
|
|
7
7
|
|
|
8
8
|
|
|
@@ -36,9 +36,12 @@ def coverage(module_name, html):
|
|
|
36
36
|
if html:
|
|
37
37
|
coverage_cmd.extend(["--cov-report", "html"])
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
result = run(
|
|
40
|
+
coverage_cmd,
|
|
41
|
+
check=False,
|
|
42
|
+
tool_hint="Install it with: pip install pytest pytest-cov",
|
|
43
|
+
)
|
|
44
|
+
if result.returncode != 0:
|
|
42
45
|
click.echo(
|
|
43
46
|
click.style("❌ Coverage run failed (tests may be failing).", fg="red")
|
|
44
47
|
)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import click
|
|
2
|
-
import subprocess
|
|
3
2
|
from dotenv import load_dotenv
|
|
4
3
|
import os
|
|
5
4
|
|
|
6
5
|
from splent_cli.services import context
|
|
6
|
+
from splent_cli.utils.proc import run, require_tool
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@click.command(
|
|
@@ -38,19 +38,34 @@ def db_console():
|
|
|
38
38
|
)
|
|
39
39
|
raise SystemExit(1)
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
require_tool(
|
|
42
|
+
"mysql",
|
|
43
|
+
"Install the MariaDB/MySQL client (e.g. 'mariadb-client') and ensure it is on your PATH.",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
result = run(
|
|
47
|
+
[
|
|
48
|
+
"mysql",
|
|
49
|
+
f"-h{mariadb_hostname}",
|
|
50
|
+
f"-u{mariadb_user}",
|
|
51
|
+
f"-p{mariadb_password}",
|
|
52
|
+
mariadb_database,
|
|
53
|
+
],
|
|
54
|
+
check=False,
|
|
55
|
+
)
|
|
56
|
+
if result.returncode != 0:
|
|
57
|
+
click.secho(
|
|
58
|
+
f"❌ Could not open the MariaDB console (mysql exited {result.returncode}).",
|
|
59
|
+
fg="red",
|
|
60
|
+
)
|
|
61
|
+
click.secho(
|
|
62
|
+
" Is the database container running and reachable?\n"
|
|
63
|
+
f" Tried to connect to host '{mariadb_hostname}' as user "
|
|
64
|
+
f"'{mariadb_user}'.\n"
|
|
65
|
+
" Start it with: splent product:up (or check 'docker ps').",
|
|
66
|
+
fg="yellow",
|
|
51
67
|
)
|
|
52
|
-
|
|
53
|
-
click.secho(f"❌ Error opening MariaDB console: {e}", fg="red")
|
|
68
|
+
raise SystemExit(1)
|
|
54
69
|
|
|
55
70
|
|
|
56
71
|
cli_command = db_console
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import click
|
|
2
|
-
import subprocess
|
|
3
2
|
from dotenv import load_dotenv
|
|
4
3
|
import os
|
|
4
|
+
import tempfile
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
|
|
7
7
|
from splent_cli.services import context
|
|
8
|
+
from splent_cli.utils.proc import run, require_tool
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
@click.command(
|
|
@@ -44,10 +45,23 @@ def db_dump(filename):
|
|
|
44
45
|
if not filename.endswith(".sql"):
|
|
45
46
|
filename += ".sql"
|
|
46
47
|
|
|
48
|
+
require_tool(
|
|
49
|
+
"mysqldump",
|
|
50
|
+
"Install the MariaDB/MySQL client tools (provides 'mysqldump').",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
env = {**os.environ, "MYSQL_PWD": mariadb_password or ""}
|
|
54
|
+
|
|
55
|
+
# Dump to a temp file first and only replace the target on success, so a
|
|
56
|
+
# pre-existing file with the same name is never truncated or deleted if the
|
|
57
|
+
# dump fails midway.
|
|
58
|
+
target_dir = os.path.dirname(os.path.abspath(filename)) or "."
|
|
59
|
+
fd, tmp_path = tempfile.mkstemp(
|
|
60
|
+
prefix=".db_dump_", suffix=".sql.tmp", dir=target_dir
|
|
61
|
+
)
|
|
47
62
|
try:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
subprocess.run(
|
|
63
|
+
with os.fdopen(fd, "wb") as out:
|
|
64
|
+
run(
|
|
51
65
|
[
|
|
52
66
|
"mysqldump",
|
|
53
67
|
f"-h{mariadb_hostname}",
|
|
@@ -55,17 +69,18 @@ def db_dump(filename):
|
|
|
55
69
|
mariadb_database,
|
|
56
70
|
],
|
|
57
71
|
stdout=out,
|
|
58
|
-
|
|
72
|
+
text=False,
|
|
59
73
|
env=env,
|
|
74
|
+
tool_hint="Install the MariaDB/MySQL client tools (provides 'mysqldump').",
|
|
60
75
|
)
|
|
76
|
+
os.replace(tmp_path, filename)
|
|
61
77
|
click.echo(
|
|
62
78
|
click.style(f"Database dump created successfully: {filename}", fg="green")
|
|
63
79
|
)
|
|
64
|
-
except
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
click.echo(click.style(f"Partial file removed: {filename}", fg="yellow"))
|
|
80
|
+
except BaseException:
|
|
81
|
+
if os.path.exists(tmp_path):
|
|
82
|
+
os.remove(tmp_path)
|
|
83
|
+
raise
|
|
69
84
|
|
|
70
85
|
|
|
71
86
|
cli_command = db_dump
|
|
@@ -75,9 +75,7 @@ def db_migrate(feature, message, empty):
|
|
|
75
75
|
and kept so you can write ``upgrade()``/``downgrade()`` by hand.
|
|
76
76
|
"""
|
|
77
77
|
if empty and not feature:
|
|
78
|
-
click.echo(
|
|
79
|
-
click.style("❌ --empty requires a FEATURE argument.", fg="red")
|
|
80
|
-
)
|
|
78
|
+
click.echo(click.style("❌ --empty requires a FEATURE argument.", fg="red"))
|
|
81
79
|
raise SystemExit(1)
|
|
82
80
|
|
|
83
81
|
if feature:
|
|
@@ -125,19 +123,39 @@ def db_migrate(feature, message, empty):
|
|
|
125
123
|
prev_level = alembic_logger.level
|
|
126
124
|
alembic_logger.setLevel(logging.WARNING)
|
|
127
125
|
|
|
126
|
+
gen_error = None
|
|
128
127
|
try:
|
|
129
128
|
if empty:
|
|
130
129
|
alembic_revision(directory=mdir, message=message or feat)
|
|
131
130
|
else:
|
|
132
131
|
alembic_migrate(directory=mdir, message=message or feat)
|
|
133
132
|
except Exception as e:
|
|
134
|
-
|
|
135
|
-
click.secho(
|
|
136
|
-
f" ⚠️ {feat}: migration generation skipped ({e})", fg="yellow"
|
|
137
|
-
)
|
|
133
|
+
gen_error = e
|
|
138
134
|
finally:
|
|
139
135
|
alembic_logger.setLevel(prev_level)
|
|
140
136
|
|
|
137
|
+
# Always surface generation failures — never report "up to date" when the
|
|
138
|
+
# migration actually failed to generate. Show a one-line error (full
|
|
139
|
+
# detail only under SPLENT_DEBUG).
|
|
140
|
+
if gen_error is not None:
|
|
141
|
+
click.echo(
|
|
142
|
+
click.style(
|
|
143
|
+
f" ❌ {feat}: migration generation failed ({gen_error})",
|
|
144
|
+
fg="red",
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
if os.getenv("SPLENT_DEBUG"):
|
|
148
|
+
import traceback
|
|
149
|
+
|
|
150
|
+
click.echo(
|
|
151
|
+
"".join(
|
|
152
|
+
traceback.format_exception(
|
|
153
|
+
type(gen_error), gen_error, gen_error.__traceback__
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
continue
|
|
158
|
+
|
|
141
159
|
after = _count_versions(mdir)
|
|
142
160
|
|
|
143
161
|
# A blank migration requested with --empty is intentional: keep it so the
|
|
@@ -41,9 +41,14 @@ def db_reset(yes):
|
|
|
41
41
|
return
|
|
42
42
|
|
|
43
43
|
# --- STEP 1: Drop ALL tables (including alembic_* and splent_migrations) ---
|
|
44
|
+
# Run every DROP inside a single transaction (engine.begin) so that a
|
|
45
|
+
# mid-operation connection failure rolls back as a unit rather than
|
|
46
|
+
# leaving a half-dropped schema. Note: MySQL/MariaDB issue an implicit
|
|
47
|
+
# COMMIT on DDL, so a full rollback is not guaranteed there; we therefore
|
|
48
|
+
# also surface explicit partial-state recovery guidance on failure.
|
|
44
49
|
click.echo(click.style("🗑️ Dropping all tables...", fg="yellow"))
|
|
45
50
|
try:
|
|
46
|
-
with db.engine.
|
|
51
|
+
with db.engine.begin() as conn:
|
|
47
52
|
conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
|
|
48
53
|
meta = MetaData()
|
|
49
54
|
meta.reflect(bind=db.engine)
|
|
@@ -51,11 +56,21 @@ def db_reset(yes):
|
|
|
51
56
|
conn.execute(text(f"DROP TABLE IF EXISTS `{table.name}`"))
|
|
52
57
|
click.echo(click.style(f" Dropped {table.name}", fg="bright_black"))
|
|
53
58
|
conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
|
|
54
|
-
conn.commit()
|
|
55
59
|
click.echo(click.style("✅ All tables dropped.", fg="yellow"))
|
|
56
60
|
except Exception as e:
|
|
57
61
|
click.echo(click.style(f"❌ Error dropping tables: {e}", fg="red"))
|
|
58
|
-
|
|
62
|
+
click.echo(
|
|
63
|
+
click.style(
|
|
64
|
+
"⚠️ The database may now be in a PARTIAL state: some tables "
|
|
65
|
+
"may have been dropped while others remain.\n"
|
|
66
|
+
" To recover, re-run this command once the database is "
|
|
67
|
+
"reachable again (it is safe to repeat — drops use "
|
|
68
|
+
"DROP TABLE IF EXISTS), or manually drop the remaining "
|
|
69
|
+
"tables / re-create the schema from migrations.",
|
|
70
|
+
fg="red",
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
raise SystemExit(1)
|
|
59
74
|
|
|
60
75
|
# --- STEP 2: Recreate splent_migrations tracking table ---
|
|
61
76
|
click.echo(click.style("📋 Recreating splent_migrations table...", fg="cyan"))
|
|
@@ -74,7 +89,15 @@ def db_reset(yes):
|
|
|
74
89
|
)
|
|
75
90
|
except Exception as e:
|
|
76
91
|
click.echo(click.style(f"❌ Error creating tracking table: {e}", fg="red"))
|
|
77
|
-
|
|
92
|
+
click.echo(
|
|
93
|
+
click.style(
|
|
94
|
+
"⚠️ All tables were dropped but the tracking table could not "
|
|
95
|
+
"be recreated. Re-run this command once the database is "
|
|
96
|
+
"reachable to finish the reset.",
|
|
97
|
+
fg="red",
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
raise SystemExit(1)
|
|
78
101
|
|
|
79
102
|
# --- STEP 3: Clear uploads ---
|
|
80
103
|
ctx = click.get_current_context()
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import click
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
|
|
6
|
+
from splent_cli.services import context
|
|
7
|
+
from splent_cli.utils.proc import run
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command(
|
|
11
|
+
"db:restore",
|
|
12
|
+
short_help="Restore a MariaDB database from a SQL dump file.",
|
|
13
|
+
)
|
|
14
|
+
@click.argument("filename")
|
|
15
|
+
@click.option("--yes", is_flag=True, help="Skip confirmation prompt.")
|
|
16
|
+
@context.requires_product
|
|
17
|
+
def db_restore(filename, yes):
|
|
18
|
+
"""
|
|
19
|
+
Restore the database from FILENAME (a .sql dump).
|
|
20
|
+
|
|
21
|
+
Reads connection credentials from the workspace .env file
|
|
22
|
+
(MARIADB_HOSTNAME, MARIADB_USER, MARIADB_PASSWORD, MARIADB_DATABASE).
|
|
23
|
+
|
|
24
|
+
\b
|
|
25
|
+
Example:
|
|
26
|
+
splent db:restore dump_20250101_120000.sql
|
|
27
|
+
"""
|
|
28
|
+
load_dotenv()
|
|
29
|
+
|
|
30
|
+
if not os.path.exists(filename):
|
|
31
|
+
click.secho(f"❌ File not found: {filename}", fg="red")
|
|
32
|
+
raise SystemExit(1)
|
|
33
|
+
|
|
34
|
+
host = os.getenv("MARIADB_HOSTNAME")
|
|
35
|
+
user = os.getenv("MARIADB_USER")
|
|
36
|
+
password = os.getenv("MARIADB_PASSWORD")
|
|
37
|
+
database = os.getenv("MARIADB_DATABASE")
|
|
38
|
+
|
|
39
|
+
missing = [
|
|
40
|
+
k
|
|
41
|
+
for k, v in {
|
|
42
|
+
"MARIADB_HOSTNAME": host,
|
|
43
|
+
"MARIADB_USER": user,
|
|
44
|
+
"MARIADB_PASSWORD": password,
|
|
45
|
+
"MARIADB_DATABASE": database,
|
|
46
|
+
}.items()
|
|
47
|
+
if not v
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
if missing:
|
|
51
|
+
click.secho(f"❌ Missing env vars: {', '.join(missing)}", fg="red")
|
|
52
|
+
raise SystemExit(1)
|
|
53
|
+
|
|
54
|
+
if not yes:
|
|
55
|
+
click.secho(
|
|
56
|
+
f"⚠️ This will overwrite the database '{database}' with the contents of '{filename}'.",
|
|
57
|
+
fg="yellow",
|
|
58
|
+
)
|
|
59
|
+
if not click.confirm("Continue?"):
|
|
60
|
+
click.echo("❎ Cancelled.")
|
|
61
|
+
raise SystemExit(0)
|
|
62
|
+
|
|
63
|
+
env = {**os.environ, "MYSQL_PWD": password or ""}
|
|
64
|
+
|
|
65
|
+
# Take an automatic pre-restore backup so a corrupt/partial dump cannot
|
|
66
|
+
# leave the live database half-overwritten with no way back.
|
|
67
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
68
|
+
backup_filename = f"pre_restore_{database}_{timestamp}.sql"
|
|
69
|
+
try:
|
|
70
|
+
with open(backup_filename, "wb") as backup_out:
|
|
71
|
+
run(
|
|
72
|
+
["mysqldump", f"-h{host}", f"-u{user}", database],
|
|
73
|
+
stdout=backup_out,
|
|
74
|
+
env=env,
|
|
75
|
+
tool_hint=(
|
|
76
|
+
"Install the MariaDB/MySQL client tools (provides 'mysqldump'), "
|
|
77
|
+
"e.g. 'mysql-client' / 'mariadb-client'."
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
except click.ClickException:
|
|
81
|
+
# mysqldump missing/failed (often a connection failure): do not proceed
|
|
82
|
+
# to overwrite the live database without a safety net.
|
|
83
|
+
if os.path.exists(backup_filename):
|
|
84
|
+
os.remove(backup_filename)
|
|
85
|
+
click.secho(
|
|
86
|
+
f"❌ Could not create a pre-restore backup of '{database}' "
|
|
87
|
+
f"(check the database is reachable at '{host}' and credentials are correct).\n"
|
|
88
|
+
"Aborting before overwriting the live database.",
|
|
89
|
+
fg="red",
|
|
90
|
+
)
|
|
91
|
+
raise SystemExit(1)
|
|
92
|
+
|
|
93
|
+
click.secho(f"🛟 Pre-restore backup saved to: {backup_filename}", fg="cyan")
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
with open(filename, "rb") as sql_file:
|
|
97
|
+
run(
|
|
98
|
+
["mysql", f"-h{host}", f"-u{user}", database],
|
|
99
|
+
stdin=sql_file,
|
|
100
|
+
env=env,
|
|
101
|
+
tool_hint=(
|
|
102
|
+
"Install the MariaDB/MySQL client tools (provides 'mysql'), "
|
|
103
|
+
"e.g. 'mysql-client' / 'mariadb-client'."
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
click.secho(f"✅ Database restored from: {filename}", fg="green")
|
|
107
|
+
except click.ClickException as e:
|
|
108
|
+
click.secho(f"❌ Error restoring database: {e.format_message()}", fg="red")
|
|
109
|
+
click.secho(
|
|
110
|
+
"The database may be in an inconsistent state. To roll back, restore "
|
|
111
|
+
f"the pre-restore backup:\n splent db:restore {backup_filename} --yes",
|
|
112
|
+
fg="yellow",
|
|
113
|
+
)
|
|
114
|
+
raise SystemExit(1)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
cli_command = db_restore
|