splent-cli 1.2.6__tar.gz → 1.2.7__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.2.6/src/splent_cli.egg-info → splent_cli-1.2.7}/PKG-INFO +1 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/pyproject.toml +1 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_orphans.py +1 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_status.py +18 -7
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_deps.py +56 -23
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_env.py +13 -5
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_features.py +14 -6
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_pyproject.py +11 -4
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_console.py +8 -3
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_migrate.py +27 -10
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_reset.py +12 -3
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_rollback.py +7 -2
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_seed.py +12 -4
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_status.py +13 -4
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_upgrade.py +9 -2
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/doctor.py +17 -13
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/export_puml.py +187 -97
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_add.py +12 -3
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_attach.py +13 -5
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_clone.py +19 -4
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_contract.py +24 -18
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_create.py +20 -6
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_detach.py +2 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_diff.py +231 -142
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_drift.py +4 -7
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_edit.py +22 -5
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_hook_add.py +3 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_hook_remove.py +2 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_hooks.py +5 -2
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_list.py +5 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_order.py +40 -15
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_release.py +24 -12
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_remove.py +6 -2
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_rename.py +4 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_status.py +25 -13
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_sync_template.py +3 -9
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_test.py +11 -4
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_upgrade.py +37 -8
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_versions.py +91 -29
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature_compile.py +46 -13
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/linter.py +10 -10
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_create.py +1 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_derive.py +3 -5
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_drift.py +2 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_status.py +28 -8
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_sync.py +15 -5
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_sync_template.py +2 -6
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/tokens.py +1 -3
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_check.py +10 -4
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_fix.py +35 -10
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_utils.py +3 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/version.py +3 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/services/compose.py +10 -2
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/command_loader.py +3 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/db_utils.py +4 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/feature_installer.py +3 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/feature_utils.py +3 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/lifecycle.py +23 -21
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/manifest.py +5 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/template_drift.py +43 -51
- {splent_cli-1.2.6 → splent_cli-1.2.7/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.2.6 → splent_cli-1.2.7}/LICENSE +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/README.md +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/setup.cfg +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/clear_cache.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/clear_log.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/clear_uploads.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/command_create.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_build.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/route_list.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_configs.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_deps.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_features.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_fetch.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_info.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_missing.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_sync.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_valid.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli.egg-info/SOURCES.txt +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.2.6 → splent_cli-1.2.7}/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.2.
|
|
3
|
+
Version: 1.2.7
|
|
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
|
|
@@ -40,6 +40,7 @@ def _get_cache_entries(cache_root: Path) -> list:
|
|
|
40
40
|
def _get_all_product_refs(workspace: Path) -> set:
|
|
41
41
|
"""Returns set of 'name' and 'name@version' (no namespace) from all products' pyproject.toml."""
|
|
42
42
|
import tomllib
|
|
43
|
+
|
|
43
44
|
refs = set()
|
|
44
45
|
for product_dir in sorted(workspace.iterdir()):
|
|
45
46
|
if not product_dir.is_dir() or product_dir.name.startswith("."):
|
|
@@ -37,10 +37,12 @@ def _get_workspace_root_features(workspace: Path) -> dict:
|
|
|
37
37
|
if not src.is_dir():
|
|
38
38
|
continue
|
|
39
39
|
for ns_dir in src.iterdir():
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
if (
|
|
41
|
+
ns_dir.is_dir()
|
|
42
|
+
and not ns_dir.name.startswith(("_", "."))
|
|
43
|
+
and "." not in ns_dir.name
|
|
44
|
+
and (ns_dir / entry.name).is_dir()
|
|
45
|
+
):
|
|
44
46
|
grouped[f"{ns_dir.name}/{entry.name}"].append("workspace")
|
|
45
47
|
break
|
|
46
48
|
return grouped
|
|
@@ -75,11 +77,20 @@ def cache_status():
|
|
|
75
77
|
for i, v in enumerate(sorted_versions):
|
|
76
78
|
connector = "└──" if i == len(sorted_versions) - 1 else "├──"
|
|
77
79
|
if v == "workspace":
|
|
78
|
-
click.echo(
|
|
80
|
+
click.echo(
|
|
81
|
+
f" {connector} "
|
|
82
|
+
+ click.style("editable (workspace root)", fg="magenta")
|
|
83
|
+
)
|
|
79
84
|
elif v is None:
|
|
80
|
-
click.echo(
|
|
85
|
+
click.echo(
|
|
86
|
+
f" {connector} " + click.style("editable (cache)", fg="blue")
|
|
87
|
+
)
|
|
81
88
|
else:
|
|
82
|
-
click.echo(
|
|
89
|
+
click.echo(
|
|
90
|
+
f" {connector} "
|
|
91
|
+
+ click.style(f"@{v}", fg="green")
|
|
92
|
+
+ click.style(" (pinned)", fg="bright_black")
|
|
93
|
+
)
|
|
83
94
|
click.echo()
|
|
84
95
|
|
|
85
96
|
|
|
@@ -16,7 +16,6 @@ import re
|
|
|
16
16
|
import click
|
|
17
17
|
|
|
18
18
|
from splent_cli.services import context
|
|
19
|
-
from splent_cli.utils.feature_utils import read_features_from_data
|
|
20
19
|
from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
21
20
|
|
|
22
21
|
|
|
@@ -24,6 +23,7 @@ from splent_framework.utils.pyproject_reader import PyprojectReader
|
|
|
24
23
|
# UVL parser — extract dependency graph
|
|
25
24
|
# ---------------------------------------------------------------------------
|
|
26
25
|
|
|
26
|
+
|
|
27
27
|
def _parse_uvl_deps(uvl_path: str) -> tuple[dict[str, str], dict[str, set[str]]]:
|
|
28
28
|
"""Parse UVL file and return (package_map, allowed_deps).
|
|
29
29
|
|
|
@@ -82,7 +82,10 @@ def _parse_uvl_deps(uvl_path: str) -> tuple[dict[str, str], dict[str, set[str]]]
|
|
|
82
82
|
# Source code scanner — find actual imports of other features
|
|
83
83
|
# ---------------------------------------------------------------------------
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
|
|
86
|
+
def _scan_feature_imports(
|
|
87
|
+
feature_path: str, feature_name: str, all_packages: set[str]
|
|
88
|
+
) -> set[str]:
|
|
86
89
|
"""Scan all .py files in a feature and return set of other feature packages imported."""
|
|
87
90
|
imported: set[str] = set()
|
|
88
91
|
|
|
@@ -109,7 +112,9 @@ def _scan_feature_imports(feature_path: str, feature_name: str, all_packages: se
|
|
|
109
112
|
continue
|
|
110
113
|
|
|
111
114
|
# Find imports: from splent_io.splent_feature_X... or import splent_io.splent_feature_X
|
|
112
|
-
for match in re.findall(
|
|
115
|
+
for match in re.findall(
|
|
116
|
+
r"(?:from|import)\s+splent_io\.(splent_feature_\w+)", content
|
|
117
|
+
):
|
|
113
118
|
if match != feature_name and match in all_packages:
|
|
114
119
|
imported.add(match)
|
|
115
120
|
|
|
@@ -120,7 +125,10 @@ def _scan_feature_imports(feature_path: str, feature_name: str, all_packages: se
|
|
|
120
125
|
# Feature path resolver
|
|
121
126
|
# ---------------------------------------------------------------------------
|
|
122
127
|
|
|
123
|
-
|
|
128
|
+
|
|
129
|
+
def _resolve_feature_paths(
|
|
130
|
+
workspace: str, product: str, features: list[str]
|
|
131
|
+
) -> dict[str, str]:
|
|
124
132
|
"""Return {package_name: feature_path} for each declared feature."""
|
|
125
133
|
result = {}
|
|
126
134
|
features_dir = os.path.join(workspace, product, "features")
|
|
@@ -144,6 +152,7 @@ def _resolve_feature_paths(workspace: str, product: str, features: list[str]) ->
|
|
|
144
152
|
# Command
|
|
145
153
|
# ---------------------------------------------------------------------------
|
|
146
154
|
|
|
155
|
+
|
|
147
156
|
@click.command(
|
|
148
157
|
"check:deps",
|
|
149
158
|
short_help="Validate that feature imports respect UVL dependency constraints.",
|
|
@@ -202,17 +211,21 @@ def check_deps():
|
|
|
202
211
|
fpath = feature_paths.get(pkg_name)
|
|
203
212
|
|
|
204
213
|
if not fpath:
|
|
205
|
-
click.echo(
|
|
206
|
-
|
|
214
|
+
click.echo(
|
|
215
|
+
click.style(f" {short}", bold=True)
|
|
216
|
+
+ click.style(" (not in cache, skipped)", fg="bright_black")
|
|
217
|
+
)
|
|
207
218
|
continue
|
|
208
219
|
|
|
209
220
|
actual_imports = _scan_feature_imports(fpath, pkg_name, all_packages)
|
|
210
221
|
allowed = allowed_deps.get(pkg_name, set())
|
|
211
222
|
|
|
212
223
|
if not actual_imports:
|
|
213
|
-
click.echo(
|
|
214
|
-
|
|
215
|
-
|
|
224
|
+
click.echo(
|
|
225
|
+
click.style(" [✔] ", fg="green")
|
|
226
|
+
+ click.style(f"{short}", bold=True)
|
|
227
|
+
+ " — no cross-feature imports"
|
|
228
|
+
)
|
|
216
229
|
ok += 1
|
|
217
230
|
continue
|
|
218
231
|
|
|
@@ -221,24 +234,38 @@ def check_deps():
|
|
|
221
234
|
imp_short = pkg_to_short.get(imp, imp)
|
|
222
235
|
|
|
223
236
|
if imp in allowed:
|
|
224
|
-
click.echo(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
237
|
+
click.echo(
|
|
238
|
+
click.style(" [✔] ", fg="green")
|
|
239
|
+
+ click.style(f"{short}", bold=True)
|
|
240
|
+
+ f" imports {imp_short}"
|
|
241
|
+
+ click.style(
|
|
242
|
+
f" (allowed: {short} => {imp_short})", fg="bright_black"
|
|
243
|
+
)
|
|
244
|
+
)
|
|
228
245
|
ok += 1
|
|
229
246
|
else:
|
|
230
247
|
# Check if the reverse is declared (inverted dependency)
|
|
231
248
|
reverse_allowed = allowed_deps.get(imp, set())
|
|
232
249
|
if pkg_name in reverse_allowed:
|
|
233
|
-
click.echo(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
250
|
+
click.echo(
|
|
251
|
+
click.style(" [✖] ", fg="red")
|
|
252
|
+
+ click.style(f"{short}", bold=True)
|
|
253
|
+
+ f" imports {imp_short}"
|
|
254
|
+
+ click.style(
|
|
255
|
+
f" INVERTED — UVL says {imp_short} => {short}, not the reverse",
|
|
256
|
+
fg="red",
|
|
257
|
+
)
|
|
258
|
+
)
|
|
237
259
|
else:
|
|
238
|
-
click.echo(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
260
|
+
click.echo(
|
|
261
|
+
click.style(" [✖] ", fg="red")
|
|
262
|
+
+ click.style(f"{short}", bold=True)
|
|
263
|
+
+ f" imports {imp_short}"
|
|
264
|
+
+ click.style(
|
|
265
|
+
f" UNDECLARED — no UVL constraint between {short} and {imp_short}",
|
|
266
|
+
fg="red",
|
|
267
|
+
)
|
|
268
|
+
)
|
|
242
269
|
violations += 1
|
|
243
270
|
has_violation = True
|
|
244
271
|
|
|
@@ -247,10 +274,16 @@ def check_deps():
|
|
|
247
274
|
|
|
248
275
|
click.echo()
|
|
249
276
|
if violations:
|
|
250
|
-
click.secho(
|
|
277
|
+
click.secho(
|
|
278
|
+
f" {violations} violation(s) found. Fix the code or update the UVL.",
|
|
279
|
+
fg="red",
|
|
280
|
+
)
|
|
251
281
|
raise SystemExit(1)
|
|
252
282
|
else:
|
|
253
|
-
click.secho(
|
|
283
|
+
click.secho(
|
|
284
|
+
f" ✅ All cross-feature imports are consistent with UVL ({ok} checks passed).",
|
|
285
|
+
fg="green",
|
|
286
|
+
)
|
|
254
287
|
click.echo()
|
|
255
288
|
|
|
256
289
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
check:env — Validate workspace environment variables and tool versions.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import os
|
|
5
6
|
import sys
|
|
6
7
|
|
|
@@ -15,21 +16,26 @@ def _pkg_version(name: str) -> str | None:
|
|
|
15
16
|
return None
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
@click.command(
|
|
19
|
+
@click.command(
|
|
20
|
+
"check:env", short_help="Validate workspace environment and tool versions."
|
|
21
|
+
)
|
|
19
22
|
def check_env():
|
|
20
23
|
"""Check Python version, SPLENT env vars, CLI/framework compatibility."""
|
|
21
24
|
ok = fail = warn = 0
|
|
22
25
|
|
|
23
26
|
def _ok(msg):
|
|
24
|
-
nonlocal ok
|
|
27
|
+
nonlocal ok
|
|
28
|
+
ok += 1
|
|
25
29
|
click.echo(click.style(" [✔] ", fg="green") + msg)
|
|
26
30
|
|
|
27
31
|
def _fail(msg):
|
|
28
|
-
nonlocal fail
|
|
32
|
+
nonlocal fail
|
|
33
|
+
fail += 1
|
|
29
34
|
click.echo(click.style(" [✖] ", fg="red") + msg)
|
|
30
35
|
|
|
31
36
|
def _warn(msg):
|
|
32
|
-
nonlocal warn
|
|
37
|
+
nonlocal warn
|
|
38
|
+
warn += 1
|
|
33
39
|
click.echo(click.style(" [⚠] ", fg="yellow") + msg)
|
|
34
40
|
|
|
35
41
|
click.echo()
|
|
@@ -78,7 +84,9 @@ def check_env():
|
|
|
78
84
|
else:
|
|
79
85
|
_fail(f"CLI {cli_v} / Framework {fw_v} — major version mismatch")
|
|
80
86
|
else:
|
|
81
|
-
_fail(
|
|
87
|
+
_fail(
|
|
88
|
+
f"CLI={'?' if not cli_v else cli_v} / Framework={'?' if not fw_v else fw_v}"
|
|
89
|
+
)
|
|
82
90
|
|
|
83
91
|
# Credentials
|
|
84
92
|
if os.getenv("GITHUB_TOKEN"):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
check:features — Validate feature cache, symlinks, pip install, and git state.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import os
|
|
5
6
|
import subprocess
|
|
6
7
|
import importlib.metadata
|
|
@@ -20,7 +21,9 @@ def _pkg_installed(name: str) -> bool:
|
|
|
20
21
|
return False
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
@click.command(
|
|
24
|
+
@click.command(
|
|
25
|
+
"check:features", short_help="Validate feature cache, symlinks, and install state."
|
|
26
|
+
)
|
|
24
27
|
def check_features():
|
|
25
28
|
"""Check every declared feature: cache entry, symlink, pip install, git state."""
|
|
26
29
|
workspace = str(context.workspace())
|
|
@@ -31,15 +34,18 @@ def check_features():
|
|
|
31
34
|
ok = fail = warn = 0
|
|
32
35
|
|
|
33
36
|
def _ok(msg):
|
|
34
|
-
nonlocal ok
|
|
37
|
+
nonlocal ok
|
|
38
|
+
ok += 1
|
|
35
39
|
click.echo(click.style(" [✔] ", fg="green") + msg)
|
|
36
40
|
|
|
37
41
|
def _fail(msg):
|
|
38
|
-
nonlocal fail
|
|
42
|
+
nonlocal fail
|
|
43
|
+
fail += 1
|
|
39
44
|
click.echo(click.style(" [✖] ", fg="red") + msg)
|
|
40
45
|
|
|
41
46
|
def _warn(msg):
|
|
42
|
-
nonlocal warn
|
|
47
|
+
nonlocal warn
|
|
48
|
+
warn += 1
|
|
43
49
|
click.echo(click.style(" [⚠] ", fg="yellow") + msg)
|
|
44
50
|
|
|
45
51
|
click.echo()
|
|
@@ -102,7 +108,7 @@ def check_features():
|
|
|
102
108
|
if os.path.exists(link_path):
|
|
103
109
|
target = os.readlink(link_path)
|
|
104
110
|
if os.path.isabs(target):
|
|
105
|
-
_warn(
|
|
111
|
+
_warn("Symlink uses absolute path (should be relative)")
|
|
106
112
|
else:
|
|
107
113
|
_ok("Symlink OK (relative)")
|
|
108
114
|
else:
|
|
@@ -123,7 +129,9 @@ def check_features():
|
|
|
123
129
|
try:
|
|
124
130
|
r = subprocess.run(
|
|
125
131
|
["git", "-C", feature_dir, "status", "--porcelain"],
|
|
126
|
-
capture_output=True,
|
|
132
|
+
capture_output=True,
|
|
133
|
+
text=True,
|
|
134
|
+
timeout=5,
|
|
127
135
|
)
|
|
128
136
|
if r.returncode != 0:
|
|
129
137
|
_warn("Not a git repo")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
check:pyproject — Validate the active product's pyproject.toml.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import os
|
|
5
6
|
import re
|
|
6
7
|
|
|
@@ -13,6 +14,7 @@ from splent_cli.utils.feature_utils import read_features_from_data
|
|
|
13
14
|
|
|
14
15
|
def _find_missing_pkgs(deps: list) -> list:
|
|
15
16
|
import importlib.metadata
|
|
17
|
+
|
|
16
18
|
missing = []
|
|
17
19
|
for dep in deps:
|
|
18
20
|
pkg = re.split(r"[=<>!~\[]", dep)[0].strip()
|
|
@@ -24,7 +26,9 @@ def _find_missing_pkgs(deps: list) -> list:
|
|
|
24
26
|
return missing
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
@click.command(
|
|
29
|
+
@click.command(
|
|
30
|
+
"check:pyproject", short_help="Validate pyproject.toml and dependencies."
|
|
31
|
+
)
|
|
28
32
|
def check_pyproject():
|
|
29
33
|
"""Parse pyproject.toml, check dependencies, and validate feature declarations."""
|
|
30
34
|
workspace = str(context.workspace())
|
|
@@ -34,15 +38,18 @@ def check_pyproject():
|
|
|
34
38
|
ok = fail = warn = 0
|
|
35
39
|
|
|
36
40
|
def _ok(msg):
|
|
37
|
-
nonlocal ok
|
|
41
|
+
nonlocal ok
|
|
42
|
+
ok += 1
|
|
38
43
|
click.echo(click.style(" [✔] ", fg="green") + msg)
|
|
39
44
|
|
|
40
45
|
def _fail(msg):
|
|
41
|
-
nonlocal fail
|
|
46
|
+
nonlocal fail
|
|
47
|
+
fail += 1
|
|
42
48
|
click.echo(click.style(" [✖] ", fg="red") + msg)
|
|
43
49
|
|
|
44
50
|
def _warn(msg):
|
|
45
|
-
nonlocal warn
|
|
51
|
+
nonlocal warn
|
|
52
|
+
warn += 1
|
|
46
53
|
click.echo(click.style(" [⚠] ", fg="yellow") + msg)
|
|
47
54
|
|
|
48
55
|
click.echo()
|
|
@@ -16,18 +16,23 @@ def db_console():
|
|
|
16
16
|
mariadb_database = os.getenv("MARIADB_DATABASE")
|
|
17
17
|
|
|
18
18
|
missing = [
|
|
19
|
-
name
|
|
19
|
+
name
|
|
20
|
+
for name, val in {
|
|
20
21
|
"MARIADB_HOSTNAME": mariadb_hostname,
|
|
21
22
|
"MARIADB_USER": mariadb_user,
|
|
22
23
|
"MARIADB_PASSWORD": mariadb_password,
|
|
23
24
|
"MARIADB_DATABASE": mariadb_database,
|
|
24
|
-
}.items()
|
|
25
|
+
}.items()
|
|
26
|
+
if not val
|
|
25
27
|
]
|
|
26
28
|
if missing:
|
|
27
29
|
click.secho("❌ Missing required environment variables:", fg="red")
|
|
28
30
|
for var in missing:
|
|
29
31
|
click.secho(f" - {var}", fg="red")
|
|
30
|
-
click.secho(
|
|
32
|
+
click.secho(
|
|
33
|
+
"\n Make sure the product .env is loaded: splent product:env --merge --dev",
|
|
34
|
+
fg="yellow",
|
|
35
|
+
)
|
|
31
36
|
raise SystemExit(1)
|
|
32
37
|
|
|
33
38
|
try:
|
|
@@ -5,7 +5,11 @@ from flask import current_app
|
|
|
5
5
|
from flask_migrate import migrate as alembic_migrate, upgrade as alembic_upgrade
|
|
6
6
|
|
|
7
7
|
from splent_cli.utils.decorators import requires_db
|
|
8
|
-
from splent_cli.utils.lifecycle import
|
|
8
|
+
from splent_cli.utils.lifecycle import (
|
|
9
|
+
advance_state,
|
|
10
|
+
resolve_feature_key_from_entry,
|
|
11
|
+
require_editable,
|
|
12
|
+
)
|
|
9
13
|
from splent_framework.managers.migration_manager import MigrationManager
|
|
10
14
|
from splent_framework.utils.feature_utils import get_features_from_pyproject
|
|
11
15
|
from splent_framework.utils.path_utils import PathUtils
|
|
@@ -25,16 +29,17 @@ def _is_empty_migration(path: str) -> bool:
|
|
|
25
29
|
content = f.read()
|
|
26
30
|
# Strip upgrade() and downgrade() bodies — if both are just pass, it's empty
|
|
27
31
|
import re
|
|
32
|
+
|
|
28
33
|
upgrades = re.findall(r"def upgrade\(\).*?(?=\ndef |\Z)", content, re.DOTALL)
|
|
29
34
|
downgrades = re.findall(r"def downgrade\(\).*?(?=\ndef |\Z)", content, re.DOTALL)
|
|
30
35
|
for body in upgrades + downgrades:
|
|
31
36
|
# Remove comments, docstrings, and whitespace — if only 'pass' remains, it's empty
|
|
32
37
|
lines = [
|
|
33
|
-
|
|
34
|
-
for
|
|
35
|
-
if
|
|
36
|
-
and not
|
|
37
|
-
and not
|
|
38
|
+
line.strip()
|
|
39
|
+
for line in body.splitlines()
|
|
40
|
+
if line.strip()
|
|
41
|
+
and not line.strip().startswith("#")
|
|
42
|
+
and not line.strip().startswith("def ")
|
|
38
43
|
]
|
|
39
44
|
if any(line != "pass" for line in lines):
|
|
40
45
|
return False
|
|
@@ -97,6 +102,7 @@ def db_migrate(feature):
|
|
|
97
102
|
|
|
98
103
|
# Suppress Alembic's verbose output during generation
|
|
99
104
|
import logging
|
|
105
|
+
|
|
100
106
|
alembic_logger = logging.getLogger("alembic")
|
|
101
107
|
prev_level = alembic_logger.level
|
|
102
108
|
alembic_logger.setLevel(logging.WARNING)
|
|
@@ -114,14 +120,20 @@ def db_migrate(feature):
|
|
|
114
120
|
if after > before:
|
|
115
121
|
versions_dir = os.path.join(mdir, "versions")
|
|
116
122
|
newest = max(
|
|
117
|
-
(
|
|
123
|
+
(
|
|
124
|
+
os.path.join(versions_dir, f)
|
|
125
|
+
for f in os.listdir(versions_dir)
|
|
126
|
+
if f.endswith(".py")
|
|
127
|
+
),
|
|
118
128
|
key=os.path.getmtime,
|
|
119
129
|
)
|
|
120
130
|
if _is_empty_migration(newest):
|
|
121
131
|
os.remove(newest)
|
|
122
132
|
click.echo(click.style(f" ✔ {feat}: up to date", fg="green"))
|
|
123
133
|
else:
|
|
124
|
-
click.echo(
|
|
134
|
+
click.echo(
|
|
135
|
+
click.style(f" 📝 {feat}: new migration generated", fg="cyan")
|
|
136
|
+
)
|
|
125
137
|
else:
|
|
126
138
|
click.echo(click.style(f" ✔ {feat}: up to date", fg="green"))
|
|
127
139
|
|
|
@@ -139,8 +151,13 @@ def db_migrate(feature):
|
|
|
139
151
|
if info:
|
|
140
152
|
key, ns, name, version = info
|
|
141
153
|
advance_state(
|
|
142
|
-
product_path,
|
|
143
|
-
|
|
154
|
+
product_path,
|
|
155
|
+
product_name,
|
|
156
|
+
key,
|
|
157
|
+
to="migrated",
|
|
158
|
+
namespace=ns,
|
|
159
|
+
name=name,
|
|
160
|
+
version=version,
|
|
144
161
|
)
|
|
145
162
|
except Exception as e:
|
|
146
163
|
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
@@ -92,7 +92,11 @@ def db_reset(yes):
|
|
|
92
92
|
if not dirs:
|
|
93
93
|
click.echo(click.style("⚠️ No feature migrations found.", fg="yellow"))
|
|
94
94
|
else:
|
|
95
|
-
click.echo(
|
|
95
|
+
click.echo(
|
|
96
|
+
click.style(
|
|
97
|
+
f"⬆️ Applying migrations for {len(dirs)} features...", fg="cyan"
|
|
98
|
+
)
|
|
99
|
+
)
|
|
96
100
|
for feat, mdir in dirs.items():
|
|
97
101
|
try:
|
|
98
102
|
alembic_upgrade(directory=mdir)
|
|
@@ -109,8 +113,13 @@ def db_reset(yes):
|
|
|
109
113
|
if info:
|
|
110
114
|
key, ns, name, version = info
|
|
111
115
|
advance_state(
|
|
112
|
-
product_path,
|
|
113
|
-
|
|
116
|
+
product_path,
|
|
117
|
+
product_name,
|
|
118
|
+
key,
|
|
119
|
+
to="migrated",
|
|
120
|
+
namespace=ns,
|
|
121
|
+
name=name,
|
|
122
|
+
version=version,
|
|
114
123
|
)
|
|
115
124
|
except Exception as e:
|
|
116
125
|
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|
|
@@ -49,8 +49,13 @@ def db_rollback(feature, steps):
|
|
|
49
49
|
if name == feature:
|
|
50
50
|
target = "installed" if revision is None else "migrated"
|
|
51
51
|
advance_state(
|
|
52
|
-
product_path,
|
|
53
|
-
|
|
52
|
+
product_path,
|
|
53
|
+
product_name,
|
|
54
|
+
key,
|
|
55
|
+
to=target,
|
|
56
|
+
namespace=ns,
|
|
57
|
+
name=name,
|
|
58
|
+
version=version,
|
|
54
59
|
)
|
|
55
60
|
break
|
|
56
61
|
except Exception as e:
|
|
@@ -84,7 +84,6 @@ def get_installed_seeders(specific_module=None):
|
|
|
84
84
|
|
|
85
85
|
def _truncate_data():
|
|
86
86
|
"""Delete all row data from feature tables, preserving schema and migrations."""
|
|
87
|
-
from flask import current_app
|
|
88
87
|
from splent_framework.db import db
|
|
89
88
|
from sqlalchemy import text, MetaData
|
|
90
89
|
from splent_framework.managers.migration_manager import SPLENT_MIGRATIONS_TABLE
|
|
@@ -110,13 +109,20 @@ def _truncate_data():
|
|
|
110
109
|
@click.command(
|
|
111
110
|
"db:seed", short_help="Populate the database using feature-level seeders."
|
|
112
111
|
)
|
|
113
|
-
@click.option(
|
|
112
|
+
@click.option(
|
|
113
|
+
"--reset",
|
|
114
|
+
is_flag=True,
|
|
115
|
+
help="Clear all data before seeding (keeps schema and migrations).",
|
|
116
|
+
)
|
|
114
117
|
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation prompts.")
|
|
115
118
|
@click.argument("module", required=False)
|
|
116
119
|
def db_seed(reset, yes, module):
|
|
117
120
|
if reset:
|
|
118
121
|
if yes or click.confirm(
|
|
119
|
-
click.style(
|
|
122
|
+
click.style(
|
|
123
|
+
"⚠️ This will delete all data (tables and migrations are preserved). Continue?",
|
|
124
|
+
fg="red",
|
|
125
|
+
),
|
|
120
126
|
abort=True,
|
|
121
127
|
):
|
|
122
128
|
click.echo(click.style("🔄 Clearing data...", fg="yellow"))
|
|
@@ -158,7 +164,9 @@ def db_seed(reset, yes, module):
|
|
|
158
164
|
)
|
|
159
165
|
else:
|
|
160
166
|
click.echo(
|
|
161
|
-
click.style(
|
|
167
|
+
click.style(
|
|
168
|
+
f"❌ Error in {seeder.__class__.__name__}: {e}", fg="red"
|
|
169
|
+
)
|
|
162
170
|
)
|
|
163
171
|
success = False
|
|
164
172
|
break
|
|
@@ -24,7 +24,9 @@ def _get_filesystem_head(mdir: str) -> str | None:
|
|
|
24
24
|
with open(path, "r", encoding="utf-8") as fh:
|
|
25
25
|
content = fh.read()
|
|
26
26
|
rev_m = re.search(r"^revision\s*=\s*['\"](\w+)['\"]", content, re.MULTILINE)
|
|
27
|
-
down_m = re.search(
|
|
27
|
+
down_m = re.search(
|
|
28
|
+
r"^down_revision\s*=\s*['\"](\w+)['\"]", content, re.MULTILINE
|
|
29
|
+
)
|
|
28
30
|
if rev_m:
|
|
29
31
|
revisions[rev_m.group(1)] = down_m.group(1) if down_m else None
|
|
30
32
|
except Exception:
|
|
@@ -72,7 +74,9 @@ def db_status():
|
|
|
72
74
|
col_feat = max(col_feat, len("Feature"))
|
|
73
75
|
col_rev = 14
|
|
74
76
|
|
|
75
|
-
click.echo(
|
|
77
|
+
click.echo(
|
|
78
|
+
f" {'Feature':<{col_feat}} {'Applied':<{col_rev}} {'Latest':<{col_rev}} Status"
|
|
79
|
+
)
|
|
76
80
|
click.echo(f" {'-' * col_feat} {'-' * col_rev} {'-' * col_rev} {'-' * 12}")
|
|
77
81
|
|
|
78
82
|
issues = 0
|
|
@@ -98,11 +102,16 @@ def db_status():
|
|
|
98
102
|
else:
|
|
99
103
|
status = click.style("— none", fg="bright_black")
|
|
100
104
|
|
|
101
|
-
click.echo(
|
|
105
|
+
click.echo(
|
|
106
|
+
f" {feat:<{col_feat}} {db_display:<{col_rev}} {fs_display:<{col_rev}} {status}"
|
|
107
|
+
)
|
|
102
108
|
|
|
103
109
|
click.echo()
|
|
104
110
|
if issues:
|
|
105
|
-
click.secho(
|
|
111
|
+
click.secho(
|
|
112
|
+
f" {issues} feature(s) out of sync. Run 'splent db:upgrade' to apply pending migrations.",
|
|
113
|
+
fg="yellow",
|
|
114
|
+
)
|
|
106
115
|
click.echo()
|
|
107
116
|
|
|
108
117
|
|
|
@@ -12,6 +12,7 @@ from splent_framework.utils.path_utils import PathUtils
|
|
|
12
12
|
def _resolve_product():
|
|
13
13
|
"""Return (product_path, product_name) from env."""
|
|
14
14
|
import os
|
|
15
|
+
|
|
15
16
|
product = os.getenv("SPLENT_APP", "")
|
|
16
17
|
product_path = PathUtils.get_app_base_dir()
|
|
17
18
|
return product_path, product
|
|
@@ -54,6 +55,7 @@ def db_upgrade(feature):
|
|
|
54
55
|
|
|
55
56
|
# Suppress Alembic's verbose INFO output
|
|
56
57
|
import logging
|
|
58
|
+
|
|
57
59
|
logging.getLogger("alembic").setLevel(logging.WARNING)
|
|
58
60
|
|
|
59
61
|
for feat, mdir in dirs.items():
|
|
@@ -70,8 +72,13 @@ def db_upgrade(feature):
|
|
|
70
72
|
if info:
|
|
71
73
|
key, ns, name, version = info
|
|
72
74
|
advance_state(
|
|
73
|
-
product_path,
|
|
74
|
-
|
|
75
|
+
product_path,
|
|
76
|
+
product_name,
|
|
77
|
+
key,
|
|
78
|
+
to="migrated",
|
|
79
|
+
namespace=ns,
|
|
80
|
+
name=name,
|
|
81
|
+
version=version,
|
|
75
82
|
)
|
|
76
83
|
except Exception as e:
|
|
77
84
|
click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
|