splent-cli 1.4.3__tar.gz → 1.4.4__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.4.3/src/splent_cli.egg-info → splent_cli-1.4.4}/PKG-INFO +1 -1
- {splent_cli-1.4.3 → splent_cli-1.4.4}/pyproject.toml +1 -1
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/cli.py +8 -5
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/check/check_deps.py +7 -5
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/check/check_infra.py +29 -12
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/check/check_product.py +23 -8
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/clear_cache.py +5 -6
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/database/db_dump.py +1 -3
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/database/db_rollback.py +28 -11
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/database/db_seed.py +7 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/database/db_status.py +1 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/export_puml.py +2 -3
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_add.py +16 -4
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_attach.py +16 -4
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_clone.py +9 -3
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_contract.py +11 -3
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_create.py +1 -1
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_diff.py +1 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_edit.py +5 -4
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_inject_config.py +3 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_order.py +11 -5
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_outdated.py +21 -6
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_pin.py +10 -8
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_pip_install.py +6 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_release.py +39 -11
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_status.py +21 -10
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_translate.py +16 -6
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_upgrade.py +1 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_xray.py +127 -49
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/locust.py +2 -6
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_build.py +6 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_clean.py +7 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_commands.py +1 -3
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_complete.py +3 -1
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_config.py +41 -37
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_configure.py +45 -53
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_console.py +1 -6
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_create.py +11 -4
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_deploy.py +3 -1
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_derive.py +37 -20
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_deselect.py +1 -3
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_down.py +1 -1
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_release.py +10 -4
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_restart.py +8 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_routes.py +7 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_select.py +1 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_signals.py +4 -4
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_sync.py +5 -5
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_test.py +12 -3
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_validate.py +7 -3
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/spl_add_feature.py +34 -16
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/spl_configs.py +4 -5
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/spl_create.py +4 -10
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/spl_deps.py +1 -1
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/spl_features.py +1 -1
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/spl_fix.py +1 -3
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/spl_list.py +6 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/spl_utils.py +2 -6
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/uvl/uvl_utils.py +8 -3
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/services/context.py +1 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/services/preflight.py +0 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/services/release.py +68 -21
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/contract_freshness.py +3 -6
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/git_url.py +9 -2
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/integrity.py +60 -37
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/template_drift.py +1 -1
- {splent_cli-1.4.3 → splent_cli-1.4.4/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.4.3 → splent_cli-1.4.4}/LICENSE +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/README.md +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/setup.cfg +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/clear_log.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/clear_uploads.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/command_create.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_test.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/feature_compile.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/linter.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_status.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/tokens.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli.egg-info/SOURCES.txt +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.4.3 → splent_cli-1.4.4}/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.4.
|
|
3
|
+
Version: 1.4.4
|
|
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
|
|
@@ -54,7 +54,9 @@ class SPLENTCLI(click.Group):
|
|
|
54
54
|
self._feature_cmds_cache[group.name] = group
|
|
55
55
|
except Exception as e:
|
|
56
56
|
if os.getenv("SPLENT_DEBUG"):
|
|
57
|
-
click.secho(
|
|
57
|
+
click.secho(
|
|
58
|
+
f" ⚠ Feature commands not loaded: {e}", fg="yellow", err=True
|
|
59
|
+
)
|
|
58
60
|
return self._feature_cmds_cache
|
|
59
61
|
|
|
60
62
|
def get_command(self, ctx, cmd_name):
|
|
@@ -92,8 +94,10 @@ class SPLENTCLI(click.Group):
|
|
|
92
94
|
all_cmds = self.list_commands(ctx)
|
|
93
95
|
groups = {
|
|
94
96
|
"🌿 Feature Management": [
|
|
95
|
-
cmd
|
|
96
|
-
|
|
97
|
+
cmd
|
|
98
|
+
for cmd in all_cmds
|
|
99
|
+
if cmd.startswith("feature:")
|
|
100
|
+
and cmd not in self._load_feature_commands()
|
|
97
101
|
],
|
|
98
102
|
"🏗️ Product Management": [
|
|
99
103
|
cmd for cmd in all_cmds if cmd.startswith("product:")
|
|
@@ -116,8 +120,7 @@ class SPLENTCLI(click.Group):
|
|
|
116
120
|
if cmd.startswith(("linter", "test", "coverage", "locust"))
|
|
117
121
|
],
|
|
118
122
|
"🔌 Feature Commands": [
|
|
119
|
-
cmd for cmd in all_cmds
|
|
120
|
-
if cmd in self._load_feature_commands()
|
|
123
|
+
cmd for cmd in all_cmds if cmd in self._load_feature_commands()
|
|
121
124
|
],
|
|
122
125
|
}
|
|
123
126
|
for title, cmds in groups.items():
|
|
@@ -202,12 +202,17 @@ def check_deps():
|
|
|
202
202
|
# 1. Catalog: [tool.splent].spl
|
|
203
203
|
spl_name = reader.splent_config.get("spl")
|
|
204
204
|
if spl_name:
|
|
205
|
-
uvl_path = os.path.join(
|
|
205
|
+
uvl_path = os.path.join(
|
|
206
|
+
workspace, "splent_catalog", spl_name, f"{spl_name}.uvl"
|
|
207
|
+
)
|
|
206
208
|
else:
|
|
207
209
|
# 2. Legacy: [tool.splent.uvl].file
|
|
208
210
|
uvl_file = reader.uvl_config.get("file")
|
|
209
211
|
if not uvl_file:
|
|
210
|
-
click.secho(
|
|
212
|
+
click.secho(
|
|
213
|
+
" [✖] No UVL configured. Set [tool.splent].spl or [tool.splent.uvl].file.",
|
|
214
|
+
fg="red",
|
|
215
|
+
)
|
|
211
216
|
raise SystemExit(1)
|
|
212
217
|
uvl_path = os.path.join(product_dir, "uvl", uvl_file)
|
|
213
218
|
except (FileNotFoundError, RuntimeError) as e:
|
|
@@ -256,8 +261,6 @@ def check_deps():
|
|
|
256
261
|
ok += 1
|
|
257
262
|
continue
|
|
258
263
|
|
|
259
|
-
has_violation = False
|
|
260
|
-
|
|
261
264
|
for imp in sorted(all_deps):
|
|
262
265
|
imp_short = pkg_to_short.get(imp, imp)
|
|
263
266
|
source = "imports" if imp in py_imports else "references (template)"
|
|
@@ -295,7 +298,6 @@ def check_deps():
|
|
|
295
298
|
)
|
|
296
299
|
)
|
|
297
300
|
violations += 1
|
|
298
|
-
has_violation = True
|
|
299
301
|
|
|
300
302
|
click.echo()
|
|
301
303
|
if violations:
|
|
@@ -17,7 +17,8 @@ def _parse_compose_ports(compose_file: str) -> list[tuple[int, str, str]]:
|
|
|
17
17
|
"""Return [(host_port, service_name, source_label)] from a compose file."""
|
|
18
18
|
result = subprocess.run(
|
|
19
19
|
["docker", "compose", "-f", compose_file, "config", "--format", "json"],
|
|
20
|
-
capture_output=True,
|
|
20
|
+
capture_output=True,
|
|
21
|
+
text=True,
|
|
21
22
|
)
|
|
22
23
|
if result.returncode != 0:
|
|
23
24
|
return []
|
|
@@ -42,7 +43,8 @@ def _parse_compose_services(compose_file: str) -> list[tuple[str, str, str]]:
|
|
|
42
43
|
"""Return [(service_name, container_name_or_None, source_label)]."""
|
|
43
44
|
result = subprocess.run(
|
|
44
45
|
["docker", "compose", "-f", compose_file, "config", "--format", "json"],
|
|
45
|
-
capture_output=True,
|
|
46
|
+
capture_output=True,
|
|
47
|
+
text=True,
|
|
46
48
|
)
|
|
47
49
|
if result.returncode != 0:
|
|
48
50
|
return []
|
|
@@ -58,7 +60,10 @@ def _parse_compose_services(compose_file: str) -> list[tuple[str, str, str]]:
|
|
|
58
60
|
return services
|
|
59
61
|
|
|
60
62
|
|
|
61
|
-
@click.command(
|
|
63
|
+
@click.command(
|
|
64
|
+
"check:infra",
|
|
65
|
+
short_help="Validate Docker infrastructure (ports, services, networks).",
|
|
66
|
+
)
|
|
62
67
|
def check_infra():
|
|
63
68
|
"""Check for port conflicts, duplicate services, container name collisions,
|
|
64
69
|
and network availability across all features and the product."""
|
|
@@ -132,7 +137,8 @@ def check_infra():
|
|
|
132
137
|
for port in all_ports:
|
|
133
138
|
result = subprocess.run(
|
|
134
139
|
["docker", "ps", "--format", "{{.ID}}\t{{.Names}}\t{{.Ports}}"],
|
|
135
|
-
capture_output=True,
|
|
140
|
+
capture_output=True,
|
|
141
|
+
text=True,
|
|
136
142
|
)
|
|
137
143
|
for line in result.stdout.splitlines():
|
|
138
144
|
parts = line.split("\t", 2)
|
|
@@ -170,9 +176,13 @@ def check_infra():
|
|
|
170
176
|
cn_conflicts = {c: srcs for c, srcs in all_container_names.items() if len(srcs) > 1}
|
|
171
177
|
if cn_conflicts:
|
|
172
178
|
for cn, sources in sorted(cn_conflicts.items()):
|
|
173
|
-
_fail(
|
|
179
|
+
_fail(
|
|
180
|
+
f"Container name '{cn}' used by multiple features: {', '.join(sources)}"
|
|
181
|
+
)
|
|
174
182
|
else:
|
|
175
|
-
_ok(
|
|
183
|
+
_ok(
|
|
184
|
+
f"No container name collisions ({len(all_container_names)} named containers)"
|
|
185
|
+
)
|
|
176
186
|
|
|
177
187
|
# --- Check 3: Network availability ---
|
|
178
188
|
click.echo()
|
|
@@ -181,7 +191,8 @@ def check_infra():
|
|
|
181
191
|
for label, cf in compose_files:
|
|
182
192
|
result = subprocess.run(
|
|
183
193
|
["docker", "compose", "-f", cf, "config", "--format", "json"],
|
|
184
|
-
capture_output=True,
|
|
194
|
+
capture_output=True,
|
|
195
|
+
text=True,
|
|
185
196
|
)
|
|
186
197
|
if result.returncode != 0:
|
|
187
198
|
continue
|
|
@@ -196,24 +207,30 @@ def check_infra():
|
|
|
196
207
|
if required_networks:
|
|
197
208
|
existing_networks = subprocess.run(
|
|
198
209
|
["docker", "network", "ls", "--format", "{{.Name}}"],
|
|
199
|
-
capture_output=True,
|
|
210
|
+
capture_output=True,
|
|
211
|
+
text=True,
|
|
200
212
|
).stdout.splitlines()
|
|
201
213
|
for net in sorted(required_networks):
|
|
202
214
|
if net in existing_networks:
|
|
203
215
|
_ok(f"Network '{net}' exists")
|
|
204
216
|
else:
|
|
205
|
-
_fail(
|
|
217
|
+
_fail(
|
|
218
|
+
f"External network '{net}' does not exist (run: docker network create {net})"
|
|
219
|
+
)
|
|
206
220
|
else:
|
|
207
221
|
_ok("No external networks required")
|
|
208
222
|
|
|
209
223
|
# --- Summary ---
|
|
210
224
|
click.echo()
|
|
211
|
-
total = ok + fail + warn
|
|
212
225
|
if fail:
|
|
213
|
-
click.secho(
|
|
226
|
+
click.secho(
|
|
227
|
+
f" {fail} check(s) failed, {warn} warning(s), {ok} passed.", fg="red"
|
|
228
|
+
)
|
|
214
229
|
raise SystemExit(1)
|
|
215
230
|
elif warn:
|
|
216
|
-
click.secho(
|
|
231
|
+
click.secho(
|
|
232
|
+
f" All passed with {warn} warning(s) ({ok} checks OK).", fg="yellow"
|
|
233
|
+
)
|
|
217
234
|
else:
|
|
218
235
|
click.secho(f" All {ok} checks passed.", fg="green")
|
|
219
236
|
click.echo()
|
|
@@ -16,7 +16,6 @@ import click
|
|
|
16
16
|
from splent_cli.services import context
|
|
17
17
|
from splent_cli.utils.feature_utils import (
|
|
18
18
|
load_product_features,
|
|
19
|
-
normalize_namespace,
|
|
20
19
|
parse_feature_entry,
|
|
21
20
|
)
|
|
22
21
|
|
|
@@ -31,7 +30,9 @@ def _resolve_feature_pyproject(workspace, product_path, ns_safe, name, version):
|
|
|
31
30
|
|
|
32
31
|
# 2. Product symlink
|
|
33
32
|
dir_name = f"{name}@{version}" if version else name
|
|
34
|
-
candidate = os.path.join(
|
|
33
|
+
candidate = os.path.join(
|
|
34
|
+
product_path, "features", ns_safe, dir_name, "pyproject.toml"
|
|
35
|
+
)
|
|
35
36
|
if os.path.isfile(candidate):
|
|
36
37
|
with open(candidate, "rb") as f:
|
|
37
38
|
return tomllib.load(f)
|
|
@@ -85,7 +86,9 @@ def _check_env_vars(workspace, product_path, features, counters):
|
|
|
85
86
|
per_feature: dict[str, list[str]] = {}
|
|
86
87
|
for entry in features:
|
|
87
88
|
ns_safe, name, version = parse_feature_entry(entry)
|
|
88
|
-
data = _resolve_feature_pyproject(
|
|
89
|
+
data = _resolve_feature_pyproject(
|
|
90
|
+
workspace, product_path, ns_safe, name, version
|
|
91
|
+
)
|
|
89
92
|
if not data:
|
|
90
93
|
continue
|
|
91
94
|
env_vars = (
|
|
@@ -106,6 +109,7 @@ def _check_env_vars(workspace, product_path, features, counters):
|
|
|
106
109
|
injected_keys: set[str] = set()
|
|
107
110
|
try:
|
|
108
111
|
from splent_cli.utils.dynamic_imports import get_app
|
|
112
|
+
|
|
109
113
|
app = get_app()
|
|
110
114
|
trace = app.extensions.get("splent_config_trace", {})
|
|
111
115
|
injected_keys = set(trace.keys())
|
|
@@ -123,7 +127,9 @@ def _check_env_vars(workspace, product_path, features, counters):
|
|
|
123
127
|
col_feat = 25
|
|
124
128
|
col_src = 14
|
|
125
129
|
|
|
126
|
-
click.echo(
|
|
130
|
+
click.echo(
|
|
131
|
+
f" {'Variable':<{col_var}} {'Feature(s)':<{col_feat}} {'Source':<{col_src}} Status"
|
|
132
|
+
)
|
|
127
133
|
click.echo(f" {'-' * col_var} {'-' * col_feat} {'-' * col_src} {'-' * 10}")
|
|
128
134
|
|
|
129
135
|
has_missing = False
|
|
@@ -142,7 +148,9 @@ def _check_env_vars(workspace, product_path, features, counters):
|
|
|
142
148
|
has_missing = True
|
|
143
149
|
counters["fail"] += 1
|
|
144
150
|
|
|
145
|
-
click.echo(
|
|
151
|
+
click.echo(
|
|
152
|
+
f" {var:<{col_var}} {feats:<{col_feat}} {src:<{col_src}} {status}"
|
|
153
|
+
)
|
|
146
154
|
|
|
147
155
|
if not has_missing:
|
|
148
156
|
counters["ok"] += 1
|
|
@@ -178,12 +186,16 @@ def _check_symlinks(product_path, features, counters):
|
|
|
178
186
|
feat_label += f"@{version}"
|
|
179
187
|
|
|
180
188
|
if os.path.islink(link) and not os.path.exists(link):
|
|
181
|
-
click.echo(
|
|
189
|
+
click.echo(
|
|
190
|
+
f" {feat_label:<{col_feat}} {click.style('✖ broken', fg='red')}"
|
|
191
|
+
)
|
|
182
192
|
broken_count += 1
|
|
183
193
|
elif os.path.exists(link):
|
|
184
194
|
click.echo(f" {feat_label:<{col_feat}} {click.style('✔', fg='green')}")
|
|
185
195
|
else:
|
|
186
|
-
click.echo(
|
|
196
|
+
click.echo(
|
|
197
|
+
f" {feat_label:<{col_feat}} {click.style('— not linked', fg='bright_black')}"
|
|
198
|
+
)
|
|
187
199
|
|
|
188
200
|
if broken_count:
|
|
189
201
|
counters["fail"] += broken_count
|
|
@@ -202,6 +214,7 @@ def _check_config_overwrites(counters):
|
|
|
202
214
|
|
|
203
215
|
try:
|
|
204
216
|
from splent_cli.utils.dynamic_imports import get_app
|
|
217
|
+
|
|
205
218
|
app = get_app()
|
|
206
219
|
except Exception:
|
|
207
220
|
click.echo(click.style(" ⚠ Could not boot app — skipping", fg="yellow"))
|
|
@@ -212,7 +225,7 @@ def _check_config_overwrites(counters):
|
|
|
212
225
|
overwrites = {k: v for k, v in trace.items() if v.get("action") == "overwritten"}
|
|
213
226
|
|
|
214
227
|
if not overwrites:
|
|
215
|
-
click.echo(
|
|
228
|
+
click.echo(" No config key overwrites detected.")
|
|
216
229
|
counters["ok"] += 1
|
|
217
230
|
else:
|
|
218
231
|
col_key = 25
|
|
@@ -247,6 +260,7 @@ def _check_blueprints(counters):
|
|
|
247
260
|
|
|
248
261
|
try:
|
|
249
262
|
from splent_cli.utils.dynamic_imports import get_app
|
|
263
|
+
|
|
250
264
|
app = get_app()
|
|
251
265
|
except Exception:
|
|
252
266
|
click.echo(click.style(" ⚠ Could not boot app — skipping", fg="yellow"))
|
|
@@ -304,6 +318,7 @@ def check_product():
|
|
|
304
318
|
|
|
305
319
|
# Check for stale contracts before reading them
|
|
306
320
|
from splent_cli.utils.contract_freshness import check_and_refresh_contracts
|
|
321
|
+
|
|
307
322
|
check_and_refresh_contracts(workspace, features)
|
|
308
323
|
|
|
309
324
|
_check_env_vars(workspace, product_path, features, counters)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
import shutil
|
|
3
|
-
import os
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
|
-
from splent_cli.utils.path_utils import PathUtils
|
|
7
5
|
from splent_cli.services import context
|
|
8
6
|
|
|
9
7
|
|
|
@@ -47,7 +45,10 @@ def clean_build_artifacts(target_path: str | Path, *, quiet: bool = False):
|
|
|
47
45
|
pass
|
|
48
46
|
|
|
49
47
|
if not quiet:
|
|
50
|
-
click.secho(
|
|
48
|
+
click.secho(
|
|
49
|
+
f" Cleared build artifacts ({removed} __pycache__ dirs removed).",
|
|
50
|
+
fg="green",
|
|
51
|
+
)
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
@click.command(
|
|
@@ -55,9 +56,7 @@ def clean_build_artifacts(target_path: str | Path, *, quiet: bool = False):
|
|
|
55
56
|
short_help="Clear __pycache__, .pytest_cache and build artifacts from the workspace.",
|
|
56
57
|
)
|
|
57
58
|
def clear_cache():
|
|
58
|
-
if not click.confirm(
|
|
59
|
-
"Are you sure you want to clear caches and build artifacts?"
|
|
60
|
-
):
|
|
59
|
+
if not click.confirm("Are you sure you want to clear caches and build artifacts?"):
|
|
61
60
|
click.secho(" Cancelled.", fg="yellow")
|
|
62
61
|
return
|
|
63
62
|
|
|
@@ -65,9 +65,7 @@ def db_dump(filename):
|
|
|
65
65
|
click.echo(click.style(f"Error creating database dump: {e}", fg="red"))
|
|
66
66
|
if os.path.exists(filename):
|
|
67
67
|
os.remove(filename)
|
|
68
|
-
click.echo(
|
|
69
|
-
click.style(f"Partial file removed: {filename}", fg="yellow")
|
|
70
|
-
)
|
|
68
|
+
click.echo(click.style(f"Partial file removed: {filename}", fg="yellow"))
|
|
71
69
|
|
|
72
70
|
|
|
73
71
|
cli_command = db_dump
|
|
@@ -28,7 +28,9 @@ def _find_dependents(feature: str, product_dir: str) -> list[str]:
|
|
|
28
28
|
uvl_path = None
|
|
29
29
|
spl_name = reader.splent_config.get("spl")
|
|
30
30
|
if spl_name:
|
|
31
|
-
candidate = os.path.join(
|
|
31
|
+
candidate = os.path.join(
|
|
32
|
+
workspace, "splent_catalog", spl_name, f"{spl_name}.uvl"
|
|
33
|
+
)
|
|
32
34
|
if os.path.isfile(candidate):
|
|
33
35
|
uvl_path = candidate
|
|
34
36
|
if not uvl_path:
|
|
@@ -70,9 +72,7 @@ def _find_dependents(feature: str, product_dir: str) -> list[str]:
|
|
|
70
72
|
@click.option(
|
|
71
73
|
"--steps", default=1, show_default=True, help="Number of migrations to roll back."
|
|
72
74
|
)
|
|
73
|
-
@click.option(
|
|
74
|
-
"--cascade", is_flag=True, help="Also rollback dependent features."
|
|
75
|
-
)
|
|
75
|
+
@click.option("--cascade", is_flag=True, help="Also rollback dependent features.")
|
|
76
76
|
@context.requires_product
|
|
77
77
|
def db_rollback(feature, steps, cascade):
|
|
78
78
|
app = current_app
|
|
@@ -98,18 +98,25 @@ def db_rollback(feature, steps, cascade):
|
|
|
98
98
|
f" ⚠️ Feature '{feature}' is not declared but has a DB entry (orphan).",
|
|
99
99
|
fg="yellow",
|
|
100
100
|
)
|
|
101
|
-
if not click.confirm(
|
|
101
|
+
if not click.confirm(
|
|
102
|
+
" Remove the orphan entry from splent_migrations?", default=False
|
|
103
|
+
):
|
|
102
104
|
click.echo(" ❎ Cancelled.")
|
|
103
105
|
raise SystemExit(1)
|
|
104
106
|
MigrationManager.delete_feature_status(app, feature)
|
|
105
107
|
click.secho(f" ✅ Removed '{feature}' from splent_migrations.", fg="green")
|
|
106
108
|
return
|
|
107
109
|
else:
|
|
108
|
-
click.secho(
|
|
110
|
+
click.secho(
|
|
111
|
+
f"❌ Feature '{feature}' is not declared in this product.", fg="red"
|
|
112
|
+
)
|
|
109
113
|
raise SystemExit(1)
|
|
110
114
|
|
|
111
115
|
if not mdir or not os.path.isdir(mdir):
|
|
112
|
-
click.secho(
|
|
116
|
+
click.secho(
|
|
117
|
+
f" ℹ️ Feature '{feature}' has no migrations — nothing to roll back.",
|
|
118
|
+
fg="yellow",
|
|
119
|
+
)
|
|
113
120
|
return
|
|
114
121
|
|
|
115
122
|
# Check for dependent features
|
|
@@ -159,8 +166,13 @@ def db_rollback(feature, steps, cascade):
|
|
|
159
166
|
if name == dep:
|
|
160
167
|
target = "installed" if dep_rev is None else "migrated"
|
|
161
168
|
advance_state(
|
|
162
|
-
product_path,
|
|
163
|
-
|
|
169
|
+
product_path,
|
|
170
|
+
product_name,
|
|
171
|
+
key,
|
|
172
|
+
to=target,
|
|
173
|
+
namespace=ns,
|
|
174
|
+
name=name,
|
|
175
|
+
version=version,
|
|
164
176
|
)
|
|
165
177
|
break
|
|
166
178
|
except Exception as e:
|
|
@@ -184,8 +196,13 @@ def db_rollback(feature, steps, cascade):
|
|
|
184
196
|
if name == feature:
|
|
185
197
|
target = "installed" if revision is None else "migrated"
|
|
186
198
|
advance_state(
|
|
187
|
-
product_path,
|
|
188
|
-
|
|
199
|
+
product_path,
|
|
200
|
+
product_name,
|
|
201
|
+
key,
|
|
202
|
+
to=target,
|
|
203
|
+
namespace=ns,
|
|
204
|
+
name=name,
|
|
205
|
+
version=version,
|
|
189
206
|
)
|
|
190
207
|
break
|
|
191
208
|
except Exception as e:
|
|
@@ -5,7 +5,10 @@ import click
|
|
|
5
5
|
|
|
6
6
|
from splent_cli.utils.decorators import requires_db
|
|
7
7
|
from splent_cli.services import context
|
|
8
|
-
from splent_cli.utils.feature_utils import
|
|
8
|
+
from splent_cli.utils.feature_utils import (
|
|
9
|
+
get_features_from_pyproject,
|
|
10
|
+
normalize_namespace,
|
|
11
|
+
)
|
|
9
12
|
from splent_framework.seeders.BaseSeeder import BaseSeeder
|
|
10
13
|
from splent_framework.managers.feature_order import FeatureLoadOrderResolver
|
|
11
14
|
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
@@ -26,7 +29,9 @@ def _resolve_feature_order(features_raw: list[str]) -> list[str]:
|
|
|
26
29
|
reader = PyprojectReader.for_product(product_dir)
|
|
27
30
|
spl_name = reader.splent_config.get("spl")
|
|
28
31
|
if spl_name:
|
|
29
|
-
candidate = os.path.join(
|
|
32
|
+
candidate = os.path.join(
|
|
33
|
+
working_dir, "splent_catalog", spl_name, f"{spl_name}.uvl"
|
|
34
|
+
)
|
|
30
35
|
if os.path.isfile(candidate):
|
|
31
36
|
uvl_path = candidate
|
|
32
37
|
if not uvl_path:
|
|
@@ -67,6 +67,7 @@ def db_status():
|
|
|
67
67
|
declared_features = set(dirs.keys())
|
|
68
68
|
try:
|
|
69
69
|
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
70
|
+
|
|
70
71
|
product_dir = os.path.join(str(context.workspace()), context.require_app())
|
|
71
72
|
reader = PyprojectReader.for_product(product_dir)
|
|
72
73
|
env = os.getenv("SPLENT_ENV")
|
|
@@ -803,6 +803,7 @@ def export_puml(
|
|
|
803
803
|
# Check for stale contracts
|
|
804
804
|
from splent_cli.utils.contract_freshness import check_and_refresh_contracts
|
|
805
805
|
from splent_cli.utils.feature_utils import load_product_features
|
|
806
|
+
|
|
806
807
|
try:
|
|
807
808
|
features_raw = load_product_features(product_dir, os.getenv("SPLENT_ENV"))
|
|
808
809
|
check_and_refresh_contracts(workspace, features_raw)
|
|
@@ -841,9 +842,7 @@ def export_puml(
|
|
|
841
842
|
if not os.path.isdir(src_root):
|
|
842
843
|
continue
|
|
843
844
|
for org_dir in os.listdir(src_root):
|
|
844
|
-
models_path = os.path.join(
|
|
845
|
-
src_root, org_dir, package, "models.py"
|
|
846
|
-
)
|
|
845
|
+
models_path = os.path.join(src_root, org_dir, package, "models.py")
|
|
847
846
|
if os.path.isfile(models_path):
|
|
848
847
|
parsed = _parse_models(models_path)
|
|
849
848
|
if parsed:
|
|
@@ -12,8 +12,18 @@ from splent_cli.utils.manifest import feature_key, set_feature_state
|
|
|
12
12
|
short_help="Adds a local (non-versioned) feature to the active product.",
|
|
13
13
|
)
|
|
14
14
|
@click.argument("full_name", required=True)
|
|
15
|
-
@click.option(
|
|
16
|
-
|
|
15
|
+
@click.option(
|
|
16
|
+
"--dev",
|
|
17
|
+
"env_scope",
|
|
18
|
+
flag_value="dev",
|
|
19
|
+
help="Add to features_dev (development only).",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--prod",
|
|
23
|
+
"env_scope",
|
|
24
|
+
flag_value="prod",
|
|
25
|
+
help="Add to features_prod (production only).",
|
|
26
|
+
)
|
|
17
27
|
def feature_add(full_name, env_scope):
|
|
18
28
|
"""
|
|
19
29
|
Adds a local feature (no version, no repo) to the current SPLENT product.
|
|
@@ -66,8 +76,10 @@ def feature_add(full_name, env_scope):
|
|
|
66
76
|
)
|
|
67
77
|
|
|
68
78
|
features_key = f"features_{env_scope}" if env_scope else "features"
|
|
69
|
-
features =
|
|
70
|
-
data
|
|
79
|
+
features = (
|
|
80
|
+
read_features_from_data(data)
|
|
81
|
+
if not env_scope
|
|
82
|
+
else (data.get("tool", {}).get("splent", {}).get(features_key, []))
|
|
71
83
|
)
|
|
72
84
|
|
|
73
85
|
if full_name not in features:
|
|
@@ -12,8 +12,18 @@ from splent_cli.utils.manifest import feature_key, set_feature_state
|
|
|
12
12
|
)
|
|
13
13
|
@click.argument("feature_identifier", required=True)
|
|
14
14
|
@click.argument("version", required=True)
|
|
15
|
-
@click.option(
|
|
16
|
-
|
|
15
|
+
@click.option(
|
|
16
|
+
"--dev",
|
|
17
|
+
"env_scope",
|
|
18
|
+
flag_value="dev",
|
|
19
|
+
help="Add to features_dev (development only).",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--prod",
|
|
23
|
+
"env_scope",
|
|
24
|
+
flag_value="prod",
|
|
25
|
+
help="Add to features_prod (production only).",
|
|
26
|
+
)
|
|
17
27
|
def feature_attach(feature_identifier, version, env_scope):
|
|
18
28
|
"""
|
|
19
29
|
Attach a cached feature version to the current product.
|
|
@@ -65,8 +75,10 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
65
75
|
)
|
|
66
76
|
|
|
67
77
|
features_key = f"features_{env_scope}" if env_scope else "features"
|
|
68
|
-
features =
|
|
69
|
-
data
|
|
78
|
+
features = (
|
|
79
|
+
read_features_from_data(data)
|
|
80
|
+
if not env_scope
|
|
81
|
+
else (data.get("tool", {}).get("splent", {}).get(features_key, []))
|
|
70
82
|
)
|
|
71
83
|
|
|
72
84
|
if full_name in features:
|
|
@@ -30,7 +30,9 @@ def _get_latest_tag(namespace, repo) -> str | None:
|
|
|
30
30
|
name = tag.get("name", "")
|
|
31
31
|
m = re.match(r"v?(\d+)\.(\d+)\.(\d+)", name)
|
|
32
32
|
if m:
|
|
33
|
-
versions.append(
|
|
33
|
+
versions.append(
|
|
34
|
+
(int(m.group(1)), int(m.group(2)), int(m.group(3)), name)
|
|
35
|
+
)
|
|
34
36
|
if not versions:
|
|
35
37
|
return tags[0]["name"]
|
|
36
38
|
versions.sort(reverse=True)
|
|
@@ -45,6 +47,7 @@ def _build_repo_url(namespace, repo):
|
|
|
45
47
|
Returns a tuple (real_url, display_url) where display_url never contains a token.
|
|
46
48
|
"""
|
|
47
49
|
from splent_cli.utils.git_url import build_git_url
|
|
50
|
+
|
|
48
51
|
return build_git_url(namespace, repo)
|
|
49
52
|
|
|
50
53
|
|
|
@@ -67,8 +70,10 @@ def _parse_full_name(full_name: str):
|
|
|
67
70
|
|
|
68
71
|
|
|
69
72
|
def _validate_identifier_part(value: str, label: str):
|
|
70
|
-
if not re.fullmatch(r
|
|
71
|
-
raise SystemExit(
|
|
73
|
+
if not re.fullmatch(r"[a-zA-Z0-9_\-\.]+", value):
|
|
74
|
+
raise SystemExit(
|
|
75
|
+
f"❌ Invalid {label}: '{value}'. Only letters, digits, - _ . allowed."
|
|
76
|
+
)
|
|
72
77
|
|
|
73
78
|
|
|
74
79
|
# =====================================================================
|
|
@@ -148,6 +153,7 @@ def feature_clone(full_name):
|
|
|
148
153
|
)
|
|
149
154
|
except subprocess.CalledProcessError:
|
|
150
155
|
import shutil
|
|
156
|
+
|
|
151
157
|
shutil.rmtree(local_path, ignore_errors=True)
|
|
152
158
|
click.secho(
|
|
153
159
|
f"⚠️ Version '{version}' not found. Cloning main instead.", fg="yellow"
|
|
@@ -291,8 +291,13 @@ def _check_config_py(feature_path, ns, name, inferred):
|
|
|
291
291
|
f" ⚠ config.py not found — {len(env_vars)} env var(s) detected but not injected into app.config.",
|
|
292
292
|
fg="yellow",
|
|
293
293
|
)
|
|
294
|
-
if click.confirm(
|
|
295
|
-
|
|
294
|
+
if click.confirm(
|
|
295
|
+
" Run feature:inject-config to generate it?", default=True
|
|
296
|
+
):
|
|
297
|
+
from splent_cli.commands.feature.feature_inject_config import (
|
|
298
|
+
feature_inject_config,
|
|
299
|
+
)
|
|
300
|
+
|
|
296
301
|
ctx = click.get_current_context()
|
|
297
302
|
ctx.invoke(feature_inject_config, feature_ref=name, dry_run=False)
|
|
298
303
|
return
|
|
@@ -309,7 +314,10 @@ def _check_config_py(feature_path, ns, name, inferred):
|
|
|
309
314
|
fg="yellow",
|
|
310
315
|
)
|
|
311
316
|
if click.confirm(" Run feature:inject-config to update it?", default=True):
|
|
312
|
-
from splent_cli.commands.feature.feature_inject_config import
|
|
317
|
+
from splent_cli.commands.feature.feature_inject_config import (
|
|
318
|
+
feature_inject_config,
|
|
319
|
+
)
|
|
320
|
+
|
|
313
321
|
ctx = click.get_current_context()
|
|
314
322
|
ctx.invoke(feature_inject_config, feature_ref=name, dry_run=False)
|
|
315
323
|
|
|
@@ -86,7 +86,7 @@ def make_feature(full_name):
|
|
|
86
86
|
# Derive short name: splent_feature_notes → notes
|
|
87
87
|
short_name = feature_name
|
|
88
88
|
if short_name.startswith("splent_feature_"):
|
|
89
|
-
short_name = short_name[len("splent_feature_"):]
|
|
89
|
+
short_name = short_name[len("splent_feature_") :]
|
|
90
90
|
|
|
91
91
|
template_ctx = {
|
|
92
92
|
"feature_name": feature_name,
|
|
@@ -540,6 +540,7 @@ def feature_diff(feature_ref_a, feature_ref_b, check_all, as_json, min_severity)
|
|
|
540
540
|
# Check for stale contracts before reading them
|
|
541
541
|
from splent_cli.utils.contract_freshness import check_and_refresh_contracts
|
|
542
542
|
from splent_cli.utils.feature_utils import load_product_features
|
|
543
|
+
|
|
543
544
|
try:
|
|
544
545
|
features_raw = load_product_features(product_dir, os.getenv("SPLENT_ENV"))
|
|
545
546
|
check_and_refresh_contracts(workspace, features_raw)
|