splent-cli 1.10.0__tar.gz → 1.12.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.10.0/src/splent_cli.egg-info → splent_cli-1.12.0}/PKG-INFO +1 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/pyproject.toml +6 -3
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_infra.py +4 -12
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_migrate.py +1 -3
- splent_cli-1.12.0/src/splent_cli/commands/database/db_reset.py +253 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_seed.py +1 -3
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/doctor.py +3 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_clone.py +46 -99
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_compile.py +1 -3
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_env.py +1 -3
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_install.py +21 -28
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_refine.py +2 -5
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_release.py +8 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_search.py +2 -6
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_unlock.py +10 -2
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_upgrade.py +30 -14
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/lint.py +3 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/locust.py +2 -6
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_clean.py +3 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_create.py +1 -7
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_env.py +1 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_release.py +8 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_run.py +3 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_up.py +14 -3
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/release/release_core.py +26 -4
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_add_feature.py +34 -39
- splent_cli-1.12.0/src/splent_cli/commands/spl/spl_compare.py +88 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_create.py +4 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/uvl/uvl_utils.py +1 -3
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/services/release.py +112 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/dynamic_imports.py +1 -3
- splent_cli-1.12.0/src/splent_cli/utils/git_url.py +221 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/integrity.py +4 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/io_utils.py +3 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/SOURCES.txt +1 -0
- splent_cli-1.10.0/src/splent_cli/commands/database/db_reset.py +0 -159
- splent_cli-1.10.0/src/splent_cli/utils/git_url.py +0 -82
- {splent_cli-1.10.0 → splent_cli-1.12.0}/LICENSE +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/README.md +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/setup.cfg +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_build.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_derive.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_validate.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/services/preflight.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.10.0 → splent_cli-1.12.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.12.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
|
|
@@ -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.12.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"
|
|
@@ -63,8 +63,11 @@ where = ["src"]
|
|
|
63
63
|
testpaths = ["tests"]
|
|
64
64
|
pythonpath = ["src"]
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
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
|
|
68
71
|
|
|
69
72
|
[project.urls]
|
|
70
73
|
Homepage = "https://github.com/diverso-lab/splent_cli"
|
|
@@ -209,9 +209,7 @@ def check_infra():
|
|
|
209
209
|
click.echo(click.style(" Networks", bold=True))
|
|
210
210
|
required_networks: set[str] = set()
|
|
211
211
|
for label, cf in compose_files:
|
|
212
|
-
result = _run(
|
|
213
|
-
["docker", "compose", "-f", cf, "config", "--format", "json"]
|
|
214
|
-
)
|
|
212
|
+
result = _run(["docker", "compose", "-f", cf, "config", "--format", "json"])
|
|
215
213
|
if result.returncode != 0:
|
|
216
214
|
continue
|
|
217
215
|
try:
|
|
@@ -242,9 +240,7 @@ def check_infra():
|
|
|
242
240
|
build_count = 0
|
|
243
241
|
for label, cf in compose_files:
|
|
244
242
|
docker_dir = os.path.dirname(cf)
|
|
245
|
-
result = _run(
|
|
246
|
-
["docker", "compose", "-f", cf, "config", "--format", "json"]
|
|
247
|
-
)
|
|
243
|
+
result = _run(["docker", "compose", "-f", cf, "config", "--format", "json"])
|
|
248
244
|
if result.returncode != 0:
|
|
249
245
|
continue
|
|
250
246
|
try:
|
|
@@ -282,9 +278,7 @@ def check_infra():
|
|
|
282
278
|
services_depended_on: dict[str, str] = {} # depended_svc -> by_svc
|
|
283
279
|
|
|
284
280
|
for label, cf in compose_files:
|
|
285
|
-
result = _run(
|
|
286
|
-
["docker", "compose", "-f", cf, "config", "--format", "json"]
|
|
287
|
-
)
|
|
281
|
+
result = _run(["docker", "compose", "-f", cf, "config", "--format", "json"])
|
|
288
282
|
if result.returncode != 0:
|
|
289
283
|
continue
|
|
290
284
|
try:
|
|
@@ -321,9 +315,7 @@ def check_infra():
|
|
|
321
315
|
click.echo(click.style(" Volumes", bold=True))
|
|
322
316
|
all_volumes: dict[str, list[str]] = {} # vol_name -> [labels]
|
|
323
317
|
for label, cf in compose_files:
|
|
324
|
-
result = _run(
|
|
325
|
-
["docker", "compose", "-f", cf, "config", "--format", "json"]
|
|
326
|
-
)
|
|
318
|
+
result = _run(["docker", "compose", "-f", cf, "config", "--format", "json"])
|
|
327
319
|
if result.returncode != 0:
|
|
328
320
|
continue
|
|
329
321
|
try:
|
|
@@ -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:
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import importlib
|
|
3
|
+
import os
|
|
4
|
+
import tomllib
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from flask import current_app
|
|
8
|
+
from flask_migrate import upgrade as alembic_upgrade
|
|
9
|
+
from sqlalchemy import text, MetaData
|
|
10
|
+
|
|
11
|
+
from splent_cli.utils.decorators import requires_db
|
|
12
|
+
from splent_cli.services import context
|
|
13
|
+
from splent_cli.utils.lifecycle import advance_state, resolve_feature_key_from_entry
|
|
14
|
+
from splent_framework.db import db
|
|
15
|
+
from splent_framework.managers.migration_manager import (
|
|
16
|
+
MigrationManager,
|
|
17
|
+
SPLENT_MIGRATIONS_TABLE,
|
|
18
|
+
alembic_version_table,
|
|
19
|
+
)
|
|
20
|
+
from splent_framework.utils.feature_utils import get_features_from_pyproject
|
|
21
|
+
from splent_framework.utils.path_utils import PathUtils
|
|
22
|
+
from splent_cli.commands.clear.clear_uploads import clear_uploads
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# =====================================================================
|
|
26
|
+
# Helpers for the per-feature reset
|
|
27
|
+
# =====================================================================
|
|
28
|
+
def _normalize(feature_name: str) -> str:
|
|
29
|
+
"""Accept 'projects' or 'splent_feature_projects' -> full package name."""
|
|
30
|
+
short = feature_name.split("/")[-1].replace("splent_feature_", "")
|
|
31
|
+
return f"splent_feature_{short}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _tables_owned_by(feature_full: str) -> list[str]:
|
|
35
|
+
"""Tables whose model classes live in the given feature's package."""
|
|
36
|
+
import_name = f"splent_io.{feature_full}"
|
|
37
|
+
try:
|
|
38
|
+
importlib.import_module(f"{import_name}.models")
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
tables = []
|
|
42
|
+
for mapper in db.Model.registry.mappers:
|
|
43
|
+
cls = mapper.class_
|
|
44
|
+
if getattr(cls, "__module__", "").startswith(import_name):
|
|
45
|
+
tables.append(cls.__table__.name)
|
|
46
|
+
return sorted(set(tables))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _refiners_of(feature_full: str, product_path: str) -> list[str]:
|
|
50
|
+
"""Installed features whose [tool.splent.refinement].refines == feature."""
|
|
51
|
+
refiners = []
|
|
52
|
+
pattern = os.path.join(product_path, "features", "*", "*", "pyproject.toml")
|
|
53
|
+
for pp in glob.glob(pattern):
|
|
54
|
+
try:
|
|
55
|
+
with open(pp, "rb") as f:
|
|
56
|
+
data = tomllib.load(f)
|
|
57
|
+
except Exception:
|
|
58
|
+
continue
|
|
59
|
+
ref = data.get("tool", {}).get("splent", {}).get("refinement", {})
|
|
60
|
+
if ref.get("refines") == feature_full:
|
|
61
|
+
name = data.get("project", {}).get("name", "")
|
|
62
|
+
if name:
|
|
63
|
+
refiners.append(name)
|
|
64
|
+
return refiners
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _reapply(app, feats: list[str], entry_lookup: dict, product_path, product_name):
|
|
68
|
+
dirs = MigrationManager.get_all_feature_migration_dirs()
|
|
69
|
+
for feat in feats:
|
|
70
|
+
mdir = dirs.get(feat)
|
|
71
|
+
if not mdir:
|
|
72
|
+
continue
|
|
73
|
+
try:
|
|
74
|
+
alembic_upgrade(directory=mdir)
|
|
75
|
+
revision = MigrationManager.get_current_feature_revision(feat, db.engine)
|
|
76
|
+
MigrationManager.update_feature_status(app, feat, revision)
|
|
77
|
+
click.echo(click.style(f" ✅ {feat} → {revision or 'head'}", fg="green"))
|
|
78
|
+
info = entry_lookup.get(feat)
|
|
79
|
+
if info:
|
|
80
|
+
key, ns, name, version = info
|
|
81
|
+
advance_state(
|
|
82
|
+
product_path,
|
|
83
|
+
product_name,
|
|
84
|
+
key,
|
|
85
|
+
to="migrated",
|
|
86
|
+
namespace=ns,
|
|
87
|
+
name=name,
|
|
88
|
+
version=version,
|
|
89
|
+
)
|
|
90
|
+
except ImportError as e:
|
|
91
|
+
if "models" in str(e):
|
|
92
|
+
continue
|
|
93
|
+
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
94
|
+
except Exception as e:
|
|
95
|
+
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _entry_lookup() -> dict:
|
|
99
|
+
lookup = {}
|
|
100
|
+
for entry in get_features_from_pyproject() or []:
|
|
101
|
+
key, ns, name, version = resolve_feature_key_from_entry(entry)
|
|
102
|
+
lookup[name] = (key, ns, name, version)
|
|
103
|
+
return lookup
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# =====================================================================
|
|
107
|
+
# Command
|
|
108
|
+
# =====================================================================
|
|
109
|
+
@requires_db
|
|
110
|
+
@click.command(
|
|
111
|
+
"db:reset",
|
|
112
|
+
short_help="Drop all tables (or just one feature's) and re-apply migrations.",
|
|
113
|
+
)
|
|
114
|
+
@click.argument("feature_name", required=False)
|
|
115
|
+
@click.option("-y", "--yes", is_flag=True, help="Confirm without prompting.")
|
|
116
|
+
@context.requires_product
|
|
117
|
+
def db_reset(feature_name, yes):
|
|
118
|
+
"""Reset the database.
|
|
119
|
+
|
|
120
|
+
With no argument: drops ALL tables and re-applies every feature migration.
|
|
121
|
+
|
|
122
|
+
With FEATURE_NAME (e.g. ``db:reset projects``): drops only the tables that
|
|
123
|
+
feature owns, clears their migration tracking, and re-applies that
|
|
124
|
+
feature's migrations plus any feature that refines it — leaving every other
|
|
125
|
+
feature's data untouched. Useful after changing a model/refinement.
|
|
126
|
+
"""
|
|
127
|
+
app = current_app
|
|
128
|
+
product_path = PathUtils.get_app_base_dir()
|
|
129
|
+
product_name = os.getenv("SPLENT_APP", "")
|
|
130
|
+
|
|
131
|
+
if feature_name:
|
|
132
|
+
_reset_one_feature(app, feature_name, yes, product_path, product_name)
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
_reset_everything(app, yes, product_path, product_name)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _reset_one_feature(app, feature_name, yes, product_path, product_name):
|
|
139
|
+
feature_full = _normalize(feature_name)
|
|
140
|
+
tables = _tables_owned_by(feature_full)
|
|
141
|
+
refiners = _refiners_of(feature_full, product_path)
|
|
142
|
+
feats = [feature_full] + refiners
|
|
143
|
+
|
|
144
|
+
if not tables:
|
|
145
|
+
click.secho(
|
|
146
|
+
f"⚠️ '{feature_full}' owns no tables (nothing to drop). "
|
|
147
|
+
f"If it is a refinement, reset the base feature instead.",
|
|
148
|
+
fg="yellow",
|
|
149
|
+
)
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
click.echo()
|
|
153
|
+
click.secho(f"Reset feature: {feature_full}", fg="cyan", bold=True)
|
|
154
|
+
click.echo(f" tables to drop : {', '.join(tables)}")
|
|
155
|
+
if refiners:
|
|
156
|
+
click.echo(f" also re-applies: {', '.join(refiners)} (refiners)")
|
|
157
|
+
if not yes and not click.confirm(
|
|
158
|
+
"⚠️ This drops those tables and re-applies their migrations. Continue?",
|
|
159
|
+
abort=True,
|
|
160
|
+
):
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
# Drop the feature's tables
|
|
164
|
+
with db.engine.begin() as conn:
|
|
165
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
|
|
166
|
+
for t in tables:
|
|
167
|
+
conn.execute(text(f"DROP TABLE IF EXISTS `{t}`"))
|
|
168
|
+
click.echo(click.style(f" Dropped {t}", fg="bright_black"))
|
|
169
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
|
|
170
|
+
|
|
171
|
+
# Clear migration tracking for the feature and its refiners — both the
|
|
172
|
+
# central splent_migrations row AND each feature's alembic version table —
|
|
173
|
+
# so their migrations are re-applied from scratch onto the fresh tables.
|
|
174
|
+
with db.engine.begin() as conn:
|
|
175
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
|
|
176
|
+
for feat in feats:
|
|
177
|
+
conn.execute(
|
|
178
|
+
text(f"DELETE FROM `{SPLENT_MIGRATIONS_TABLE}` WHERE feature = :f"),
|
|
179
|
+
{"f": feat},
|
|
180
|
+
)
|
|
181
|
+
conn.execute(text(f"DROP TABLE IF EXISTS `{alembic_version_table(feat)}`"))
|
|
182
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
|
|
183
|
+
|
|
184
|
+
click.secho("⬆️ Re-applying migrations...", fg="cyan")
|
|
185
|
+
_reapply(app, feats, _entry_lookup(), product_path, product_name)
|
|
186
|
+
click.secho(f"\n🎉 Feature '{feature_full}' reset complete.", fg="green")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _reset_everything(app, yes, product_path, product_name):
|
|
190
|
+
if not yes and not click.confirm(
|
|
191
|
+
"⚠️ WARNING: This will DROP all tables and clear uploads. Are you sure?",
|
|
192
|
+
abort=True,
|
|
193
|
+
):
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
click.echo(click.style("🗑️ Dropping all tables...", fg="yellow"))
|
|
197
|
+
try:
|
|
198
|
+
with db.engine.begin() as conn:
|
|
199
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
|
|
200
|
+
meta = MetaData()
|
|
201
|
+
meta.reflect(bind=db.engine)
|
|
202
|
+
for table in meta.sorted_tables:
|
|
203
|
+
conn.execute(text(f"DROP TABLE IF EXISTS `{table.name}`"))
|
|
204
|
+
click.echo(click.style(f" Dropped {table.name}", fg="bright_black"))
|
|
205
|
+
conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
|
|
206
|
+
click.echo(click.style("✅ All tables dropped.", fg="yellow"))
|
|
207
|
+
except Exception as e:
|
|
208
|
+
click.echo(click.style(f"❌ Error dropping tables: {e}", fg="red"))
|
|
209
|
+
click.echo(
|
|
210
|
+
click.style(
|
|
211
|
+
"⚠️ The database may now be in a PARTIAL state: some tables "
|
|
212
|
+
"may have been dropped while others remain.\n"
|
|
213
|
+
" To recover, re-run this command once the database is "
|
|
214
|
+
"reachable again (it is safe to repeat — drops use "
|
|
215
|
+
"DROP TABLE IF EXISTS), or manually drop the remaining "
|
|
216
|
+
"tables / re-create the schema from migrations.",
|
|
217
|
+
fg="red",
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
raise SystemExit(1)
|
|
221
|
+
|
|
222
|
+
click.echo(click.style("📋 Recreating splent_migrations table...", fg="cyan"))
|
|
223
|
+
with db.engine.begin() as conn:
|
|
224
|
+
conn.execute(
|
|
225
|
+
text(
|
|
226
|
+
f"""
|
|
227
|
+
CREATE TABLE IF NOT EXISTS `{SPLENT_MIGRATIONS_TABLE}` (
|
|
228
|
+
`feature` VARCHAR(255) NOT NULL,
|
|
229
|
+
`last_migration` VARCHAR(255) DEFAULT NULL,
|
|
230
|
+
PRIMARY KEY (`feature`)
|
|
231
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
232
|
+
"""
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
ctx = click.get_current_context()
|
|
237
|
+
ctx.invoke(clear_uploads)
|
|
238
|
+
|
|
239
|
+
dirs = MigrationManager.get_all_feature_migration_dirs()
|
|
240
|
+
if not dirs:
|
|
241
|
+
click.echo(click.style("⚠️ No feature migrations found.", fg="yellow"))
|
|
242
|
+
else:
|
|
243
|
+
click.echo(
|
|
244
|
+
click.style(
|
|
245
|
+
f"⬆️ Applying migrations for {len(dirs)} features...", fg="cyan"
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
_reapply(app, list(dirs.keys()), _entry_lookup(), product_path, product_name)
|
|
249
|
+
|
|
250
|
+
click.echo(click.style("\n🎉 Database reset complete.", fg="green"))
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
cli_command = db_reset
|
|
@@ -179,9 +179,7 @@ def db_seed(reset, yes, module):
|
|
|
179
179
|
break
|
|
180
180
|
except Exception as e:
|
|
181
181
|
click.echo(
|
|
182
|
-
click.style(
|
|
183
|
-
f"❌ Error in {seeder.__class__.__name__}: {e}", fg="red"
|
|
184
|
-
)
|
|
182
|
+
click.style(f"❌ Error in {seeder.__class__.__name__}: {e}", fg="red")
|
|
185
183
|
)
|
|
186
184
|
success = False
|
|
187
185
|
break
|
|
@@ -68,7 +68,9 @@ def _check_workspace_precondition():
|
|
|
68
68
|
wd = os.getenv("WORKING_DIR")
|
|
69
69
|
workspace = wd or os.getcwd()
|
|
70
70
|
if not wd:
|
|
71
|
-
problems.append(
|
|
71
|
+
problems.append(
|
|
72
|
+
"WORKING_DIR is not set (falling back to the current directory)"
|
|
73
|
+
)
|
|
72
74
|
elif not os.path.isdir(wd):
|
|
73
75
|
problems.append(f"WORKING_DIR points to a missing directory: {wd}")
|
|
74
76
|
|
|
@@ -6,7 +6,7 @@ import click
|
|
|
6
6
|
from splent_cli.services import context
|
|
7
7
|
from splent_cli.utils.cache_utils import make_feature_readonly
|
|
8
8
|
from splent_cli.utils.feature_utils import normalize_namespace
|
|
9
|
-
from splent_cli.utils.proc import
|
|
9
|
+
from splent_cli.utils.proc import require_tool
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
DEFAULT_NAMESPACE = os.getenv("SPLENT_DEFAULT_NAMESPACE", "splent_io")
|
|
@@ -20,8 +20,15 @@ DEFAULT_NAMESPACE = os.getenv("SPLENT_DEFAULT_NAMESPACE", "splent_io")
|
|
|
20
20
|
def _get_latest_tag(namespace, repo) -> str | None:
|
|
21
21
|
"""Fetch the highest semver tag from GitHub. Returns None if unreachable or no tags."""
|
|
22
22
|
api_url = f"https://api.github.com/repos/{namespace}/{repo}/tags?per_page=100"
|
|
23
|
+
# Authenticate when a token is present so version resolution also works for
|
|
24
|
+
# PRIVATE feature repos (an unauthenticated call 404s on those) and to raise
|
|
25
|
+
# the API rate limit.
|
|
26
|
+
headers = {}
|
|
27
|
+
token = os.getenv("GITHUB_TOKEN")
|
|
28
|
+
if token:
|
|
29
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
23
30
|
try:
|
|
24
|
-
r = requests.get(api_url, timeout=5)
|
|
31
|
+
r = requests.get(api_url, headers=headers, timeout=5)
|
|
25
32
|
r.raise_for_status()
|
|
26
33
|
tags = r.json()
|
|
27
34
|
if not tags:
|
|
@@ -42,16 +49,6 @@ def _get_latest_tag(namespace, repo) -> str | None:
|
|
|
42
49
|
return None
|
|
43
50
|
|
|
44
51
|
|
|
45
|
-
def _build_repo_url(namespace, repo):
|
|
46
|
-
"""Build the Git URL, trying SSH first with HTTPS fallback.
|
|
47
|
-
|
|
48
|
-
Returns a tuple (real_url, display_url) where display_url never contains a token.
|
|
49
|
-
"""
|
|
50
|
-
from splent_cli.utils.git_url import build_git_url
|
|
51
|
-
|
|
52
|
-
return build_git_url(namespace, repo)
|
|
53
|
-
|
|
54
|
-
|
|
55
52
|
def _parse_full_name(full_name: str):
|
|
56
53
|
"""
|
|
57
54
|
full_name = <namespace>/<repo>[@version]
|
|
@@ -115,9 +112,6 @@ def feature_clone(full_name):
|
|
|
115
112
|
)
|
|
116
113
|
raise SystemExit(1)
|
|
117
114
|
|
|
118
|
-
# Build Git URL based on your ownership
|
|
119
|
-
fork_url, display_url = _build_repo_url(namespace, repo)
|
|
120
|
-
|
|
121
115
|
# Local destination
|
|
122
116
|
namespace_safe = normalize_namespace(namespace)
|
|
123
117
|
workspace = str(context.workspace())
|
|
@@ -137,98 +131,51 @@ def feature_clone(full_name):
|
|
|
137
131
|
"Install Git from https://git-scm.com/downloads and make sure it is on your PATH.",
|
|
138
132
|
)
|
|
139
133
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
result = run(
|
|
145
|
-
[
|
|
146
|
-
"git",
|
|
147
|
-
"-c",
|
|
148
|
-
"advice.detachedHead=false",
|
|
149
|
-
"clone",
|
|
150
|
-
"--depth",
|
|
151
|
-
"1",
|
|
152
|
-
"--branch",
|
|
153
|
-
version,
|
|
154
|
-
"--quiet",
|
|
155
|
-
fork_url,
|
|
156
|
-
local_path,
|
|
157
|
-
],
|
|
158
|
-
check=False,
|
|
159
|
-
capture=True,
|
|
134
|
+
from splent_cli.utils.git_url import (
|
|
135
|
+
clone as git_clone,
|
|
136
|
+
CLONE_SUCCESS,
|
|
137
|
+
CLONE_REF_NOT_FOUND,
|
|
160
138
|
)
|
|
161
139
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
auth_or_network = any(
|
|
177
|
-
token in stderr_lc
|
|
178
|
-
for token in (
|
|
179
|
-
"authentication failed",
|
|
180
|
-
"permission denied",
|
|
181
|
-
"could not read from remote repository",
|
|
182
|
-
"repository not found",
|
|
183
|
-
"could not resolve host",
|
|
184
|
-
"connection timed out",
|
|
185
|
-
"connection refused",
|
|
186
|
-
"network is unreachable",
|
|
187
|
-
"failed to connect",
|
|
188
|
-
"ssl",
|
|
189
|
-
"tls",
|
|
190
|
-
)
|
|
140
|
+
click.secho(f"⬇️ Cloning {namespace}/{repo}@{version}", fg="cyan")
|
|
141
|
+
|
|
142
|
+
# Always try SSH first, then HTTPS (GITHUB_TOKEN if set, else anonymous). The
|
|
143
|
+
# transport is decided per repo from the real clone result (see
|
|
144
|
+
# git_url.clone), so a working SSH key that simply can't read THIS repo no
|
|
145
|
+
# longer blocks the HTTPS path.
|
|
146
|
+
outcome, used_url, stderr = git_clone(namespace, repo, local_path, ref=version)
|
|
147
|
+
|
|
148
|
+
# A genuinely missing tag/branch is the ONLY case where falling back to the
|
|
149
|
+
# default branch is correct (the repo itself was reachable).
|
|
150
|
+
if outcome == CLONE_REF_NOT_FOUND:
|
|
151
|
+
click.secho(
|
|
152
|
+
f"⚠️ Version '{version}' not found. Cloning default branch instead.",
|
|
153
|
+
fg="yellow",
|
|
191
154
|
)
|
|
155
|
+
outcome, used_url, stderr = git_clone(namespace, repo, local_path, ref=None)
|
|
192
156
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if detail:
|
|
210
|
-
click.secho(detail, fg="red")
|
|
211
|
-
raise SystemExit(1)
|
|
212
|
-
else:
|
|
213
|
-
# Network / auth / unknown git failure: do NOT fall back to main,
|
|
214
|
-
# which would silently install the wrong (unpinned) code.
|
|
215
|
-
click.secho(
|
|
216
|
-
f"❌ Failed to clone '{namespace}/{repo}@{version}'.", fg="red"
|
|
217
|
-
)
|
|
218
|
-
click.secho(
|
|
219
|
-
"This looks like a network, authentication, or repository-access "
|
|
220
|
-
"problem rather than a missing version. Check your connection and "
|
|
221
|
-
"credentials, then try again.",
|
|
222
|
-
fg="red",
|
|
223
|
-
)
|
|
224
|
-
if stderr:
|
|
225
|
-
click.secho(stderr, fg="red")
|
|
226
|
-
raise SystemExit(1)
|
|
157
|
+
if outcome != CLONE_SUCCESS:
|
|
158
|
+
shutil.rmtree(local_path, ignore_errors=True)
|
|
159
|
+
click.secho(
|
|
160
|
+
f"❌ Repository '{namespace}/{repo}' not found or not accessible "
|
|
161
|
+
"(tried SSH and HTTPS).",
|
|
162
|
+
fg="red",
|
|
163
|
+
)
|
|
164
|
+
click.secho(
|
|
165
|
+
"Check your network, that the repo exists, and that you have read "
|
|
166
|
+
"access — an SSH key authorised for the repo, or a GITHUB_TOKEN with "
|
|
167
|
+
"read scope in your .env.",
|
|
168
|
+
fg="red",
|
|
169
|
+
)
|
|
170
|
+
if stderr:
|
|
171
|
+
click.secho(stderr, fg="red")
|
|
172
|
+
raise SystemExit(1)
|
|
227
173
|
|
|
228
174
|
# Lock files as read-only to prevent accidental edits on pinned features
|
|
229
175
|
make_feature_readonly(local_path)
|
|
230
176
|
|
|
231
177
|
click.secho(
|
|
232
|
-
f"✅ Feature '{namespace}/{repo}@{version}' cloned successfully.",
|
|
178
|
+
f"✅ Feature '{namespace}/{repo}@{version}' cloned successfully ({used_url}).",
|
|
179
|
+
fg="green",
|
|
233
180
|
)
|
|
234
181
|
click.secho(f"🔒 Cached (read-only) at: {local_path}", fg="blue")
|
|
@@ -157,9 +157,7 @@ def _compile_in_container(container_id, feature, watch, production, workspace, p
|
|
|
157
157
|
raise click.ClickException(
|
|
158
158
|
f"'{run_cmd[0]}' is not installed or not on PATH.\n{tool_hint}"
|
|
159
159
|
)
|
|
160
|
-
click.echo(
|
|
161
|
-
click.style(f"👀 Watching {feature} in {mode} mode...", fg="blue")
|
|
162
|
-
)
|
|
160
|
+
click.echo(click.style(f"👀 Watching {feature} in {mode} mode...", fg="blue"))
|
|
163
161
|
else:
|
|
164
162
|
result = run(run_cmd, check=False, tool_hint=tool_hint)
|
|
165
163
|
if result.returncode != 0:
|
|
@@ -121,9 +121,7 @@ def feature_env(feature_name, generate, env_name):
|
|
|
121
121
|
try:
|
|
122
122
|
shutil.copy(selected, env_file)
|
|
123
123
|
except OSError as exc:
|
|
124
|
-
click.echo(
|
|
125
|
-
f"❌ Failed to create {env_file} from {selected}: {exc}"
|
|
126
|
-
)
|
|
124
|
+
click.echo(f"❌ Failed to create {env_file} from {selected}: {exc}")
|
|
127
125
|
raise SystemExit(1)
|
|
128
126
|
click.echo(
|
|
129
127
|
f"📄 Created {feature_entry}/docker/.env from {os.path.basename(selected)}"
|
|
@@ -241,7 +241,8 @@ def feature_install(feature_identifier, env_scope, mode, version):
|
|
|
241
241
|
)
|
|
242
242
|
if result.returncode != 0:
|
|
243
243
|
click.secho(
|
|
244
|
-
f" Clone failed: {(result.stderr or '').strip()}",
|
|
244
|
+
f" Clone failed: {(result.stderr or '').strip()}",
|
|
245
|
+
fg="red",
|
|
245
246
|
)
|
|
246
247
|
raise SystemExit(1)
|
|
247
248
|
else:
|
|
@@ -291,9 +292,7 @@ def feature_install(feature_identifier, env_scope, mode, version):
|
|
|
291
292
|
check=False,
|
|
292
293
|
)
|
|
293
294
|
if dep.returncode != 0:
|
|
294
|
-
click.secho(
|
|
295
|
-
f" Installing dependency {m} failed.", fg="red"
|
|
296
|
-
)
|
|
295
|
+
click.secho(f" Installing dependency {m} failed.", fg="red")
|
|
297
296
|
raise SystemExit(1)
|
|
298
297
|
|
|
299
298
|
# Attach to product
|
|
@@ -315,32 +314,27 @@ def feature_install(feature_identifier, env_scope, mode, version):
|
|
|
315
314
|
# Clone to workspace root as editable
|
|
316
315
|
click.echo(click.style(" clone ", dim=True) + f"{short} (editable)")
|
|
317
316
|
from splent_cli.commands.feature.feature_clone import (
|
|
318
|
-
_build_repo_url,
|
|
319
317
|
_get_latest_tag,
|
|
320
318
|
)
|
|
319
|
+
from splent_cli.utils.git_url import (
|
|
320
|
+
clone as git_clone,
|
|
321
|
+
CLONE_SUCCESS,
|
|
322
|
+
CLONE_REF_NOT_FOUND,
|
|
323
|
+
)
|
|
321
324
|
|
|
325
|
+
# SSH first, then HTTPS (per repo). ref=tag (None → default branch).
|
|
322
326
|
tag = _get_latest_tag(namespace_github, feature_name)
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
clone = run(
|
|
326
|
-
[
|
|
327
|
-
"git",
|
|
328
|
-
"clone",
|
|
329
|
-
"--depth",
|
|
330
|
-
"1",
|
|
331
|
-
"--branch",
|
|
332
|
-
branch,
|
|
333
|
-
"--quiet",
|
|
334
|
-
repo_url,
|
|
335
|
-
feature_dir,
|
|
336
|
-
],
|
|
337
|
-
check=False,
|
|
338
|
-
capture=True,
|
|
327
|
+
outcome, _used, err = git_clone(
|
|
328
|
+
namespace_github, feature_name, feature_dir, ref=tag
|
|
339
329
|
)
|
|
340
|
-
if
|
|
330
|
+
if outcome == CLONE_REF_NOT_FOUND:
|
|
331
|
+
outcome, _used, err = git_clone(
|
|
332
|
+
namespace_github, feature_name, feature_dir, ref=None
|
|
333
|
+
)
|
|
334
|
+
if outcome != CLONE_SUCCESS:
|
|
341
335
|
click.secho(
|
|
342
|
-
f" Could not clone {namespace_github}/{feature_name}
|
|
343
|
-
f"
|
|
336
|
+
f" Could not clone {namespace_github}/{feature_name} "
|
|
337
|
+
f"(tried SSH and HTTPS): {err}",
|
|
344
338
|
fg="red",
|
|
345
339
|
)
|
|
346
340
|
raise SystemExit(1)
|
|
@@ -386,9 +380,7 @@ def feature_install(feature_identifier, env_scope, mode, version):
|
|
|
386
380
|
check=False,
|
|
387
381
|
)
|
|
388
382
|
if dep.returncode != 0:
|
|
389
|
-
click.secho(
|
|
390
|
-
f" Installing dependency {m} failed.", fg="red"
|
|
391
|
-
)
|
|
383
|
+
click.secho(f" Installing dependency {m} failed.", fg="red")
|
|
392
384
|
raise SystemExit(1)
|
|
393
385
|
|
|
394
386
|
# Add to product
|
|
@@ -454,7 +446,8 @@ def feature_install(feature_identifier, env_scope, mode, version):
|
|
|
454
446
|
)
|
|
455
447
|
if result.returncode != 0:
|
|
456
448
|
click.secho(
|
|
457
|
-
f" Docker failed: {(result.stderr or '').strip()}",
|
|
449
|
+
f" Docker failed: {(result.stderr or '').strip()}",
|
|
450
|
+
fg="yellow",
|
|
458
451
|
)
|
|
459
452
|
|
|
460
453
|
click.echo()
|