splent-cli 1.1.1__tar.gz → 1.2.6__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.1.1/src/splent_cli.egg-info → splent_cli-1.2.6}/PKG-INFO +38 -2
- splent_cli-1.2.6/README.md +36 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/pyproject.toml +6 -1
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/cli.py +7 -4
- splent_cli-1.2.6/src/splent_cli/commands/cache/cache_clear.py +85 -0
- splent_cli-1.2.6/src/splent_cli/commands/cache/cache_orphans.py +97 -0
- splent_cli-1.2.6/src/splent_cli/commands/cache/cache_outdated.py +127 -0
- splent_cli-1.2.6/src/splent_cli/commands/cache/cache_prune.py +123 -0
- splent_cli-1.2.6/src/splent_cli/commands/cache/cache_size.py +57 -0
- splent_cli-1.2.6/src/splent_cli/commands/cache/cache_status.py +86 -0
- splent_cli-1.2.6/src/splent_cli/commands/cache/cache_usage.py +59 -0
- splent_cli-1.2.6/src/splent_cli/commands/cache/cache_versions.py +46 -0
- splent_cli-1.2.6/src/splent_cli/commands/check/check_deps.py +257 -0
- splent_cli-1.2.6/src/splent_cli/commands/check/check_docker.py +96 -0
- splent_cli-1.2.6/src/splent_cli/commands/check/check_env.py +100 -0
- splent_cli-1.2.6/src/splent_cli/commands/check/check_features.py +147 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/check/check_github.py +25 -4
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/check/check_pypi.py +43 -10
- splent_cli-1.2.6/src/splent_cli/commands/check/check_pyproject.py +114 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/clear_cache.py +5 -6
- splent_cli-1.2.6/src/splent_cli/commands/database/db_console.py +48 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/database/db_dump.py +15 -6
- splent_cli-1.2.6/src/splent_cli/commands/database/db_migrate.py +149 -0
- splent_cli-1.2.6/src/splent_cli/commands/database/db_reset.py +121 -0
- splent_cli-1.2.6/src/splent_cli/commands/database/db_restore.py +72 -0
- splent_cli-1.2.6/src/splent_cli/commands/database/db_rollback.py +60 -0
- splent_cli-1.2.6/src/splent_cli/commands/database/db_seed.py +170 -0
- splent_cli-1.2.6/src/splent_cli/commands/database/db_status.py +109 -0
- splent_cli-1.2.6/src/splent_cli/commands/database/db_upgrade.py +80 -0
- splent_cli-1.2.6/src/splent_cli/commands/doctor.py +126 -0
- splent_cli-1.2.6/src/splent_cli/commands/env/env_list.py +139 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/env/env_set.py +25 -15
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/env/env_show.py +6 -7
- splent_cli-1.2.6/src/splent_cli/commands/export_puml.py +769 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/feature/feature_add.py +26 -16
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/feature/feature_attach.py +48 -34
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/feature/feature_clone.py +39 -18
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_contract.py +239 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/feature/feature_create.py +37 -29
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/feature/feature_delete.py +11 -20
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_detach.py +101 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_diff.py +523 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/feature/feature_discard.py +10 -8
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_drift.py +147 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_edit.py +262 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/feature/feature_env.py +16 -11
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/feature/feature_fork.py +14 -1
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_git.py +78 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_hook_add.py +197 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_hook_remove.py +199 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_hooks.py +141 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_list.py +51 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_order.py +159 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_pull.py +102 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_release.py +447 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_remove.py +115 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/feature/feature_rename.py +10 -10
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_search.py +126 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_status.py +188 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_sync_template.py +96 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_test.py +269 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_upgrade.py +232 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature/feature_versions.py +410 -0
- splent_cli-1.2.6/src/splent_cli/commands/feature_compile.py +140 -0
- splent_cli-1.2.6/src/splent_cli/commands/linter.py +135 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/product/product_build.py +3 -7
- splent_cli-1.2.6/src/splent_cli/commands/product/product_clean.py +113 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/product/product_create.py +16 -4
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/product/product_deploy.py +12 -11
- splent_cli-1.2.6/src/splent_cli/commands/product/product_derive.py +136 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/product/product_down.py +16 -42
- splent_cli-1.2.6/src/splent_cli/commands/product/product_drift.py +139 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/product/product_env.py +21 -28
- splent_cli-1.2.6/src/splent_cli/commands/product/product_list.py +58 -0
- splent_cli-1.2.6/src/splent_cli/commands/product/product_logs.py +64 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/product/product_port.py +22 -17
- splent_cli-1.2.6/src/splent_cli/commands/product/product_release.py +113 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/product/product_run.py +8 -32
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/product/product_select.py +6 -4
- splent_cli-1.2.6/src/splent_cli/commands/product/product_shell.py +89 -0
- splent_cli-1.2.6/src/splent_cli/commands/product/product_status.py +131 -0
- splent_cli-1.2.6/src/splent_cli/commands/product/product_sync.py +143 -0
- splent_cli-1.2.6/src/splent_cli/commands/product/product_sync_template.py +132 -0
- splent_cli-1.2.6/src/splent_cli/commands/product/product_up.py +66 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/release/release_core.py +79 -23
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/route_list.py +2 -2
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/selenium.py +2 -1
- splent_cli-1.2.6/src/splent_cli/commands/tokens.py +71 -0
- splent_cli-1.2.6/src/splent_cli/commands/uvl/uvl_check.py +150 -0
- splent_cli-1.2.6/src/splent_cli/commands/uvl/uvl_configs.py +58 -0
- splent_cli-1.2.6/src/splent_cli/commands/uvl/uvl_deps.py +93 -0
- splent_cli-1.2.6/src/splent_cli/commands/uvl/uvl_features.py +49 -0
- splent_cli-1.2.6/src/splent_cli/commands/uvl/uvl_fetch.py +56 -0
- splent_cli-1.2.6/src/splent_cli/commands/uvl/uvl_fix.py +278 -0
- splent_cli-1.2.6/src/splent_cli/commands/uvl/uvl_info.py +61 -0
- splent_cli-1.2.6/src/splent_cli/commands/uvl/uvl_missing.py +92 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/uvl/uvl_sync.py +54 -170
- splent_cli-1.2.6/src/splent_cli/commands/uvl/uvl_utils.py +183 -0
- splent_cli-1.2.6/src/splent_cli/commands/uvl/uvl_valid.py +81 -0
- splent_cli-1.2.6/src/splent_cli/commands/version.py +208 -0
- splent_cli-1.2.6/src/splent_cli/services/__init__.py +0 -0
- splent_cli-1.2.6/src/splent_cli/services/compose.py +137 -0
- splent_cli-1.2.6/src/splent_cli/services/context.py +23 -0
- splent_cli-1.2.6/src/splent_cli/services/release.py +158 -0
- splent_cli-1.2.6/src/splent_cli/utils/__init__.py +0 -0
- splent_cli-1.2.6/src/splent_cli/utils/cache_utils.py +54 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/utils/command_loader.py +5 -4
- splent_cli-1.2.6/src/splent_cli/utils/db_utils.py +28 -0
- splent_cli-1.2.6/src/splent_cli/utils/decorators.py +17 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/utils/dynamic_imports.py +1 -1
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/utils/feature_installer.py +5 -3
- splent_cli-1.2.6/src/splent_cli/utils/feature_utils.py +71 -0
- splent_cli-1.2.6/src/splent_cli/utils/lifecycle.py +238 -0
- splent_cli-1.2.6/src/splent_cli/utils/manifest.py +216 -0
- splent_cli-1.2.6/src/splent_cli/utils/path_utils.py +37 -0
- splent_cli-1.2.6/src/splent_cli/utils/template_drift.py +173 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6/src/splent_cli.egg-info}/PKG-INFO +38 -2
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli.egg-info/SOURCES.txt +54 -4
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli.egg-info/requires.txt +1 -0
- splent_cli-1.1.1/README.md +0 -1
- splent_cli-1.1.1/src/splent_cli/commands/clear_features.py +0 -64
- splent_cli-1.1.1/src/splent_cli/commands/database/db_console.py +0 -23
- splent_cli-1.1.1/src/splent_cli/commands/database/db_migrate.py +0 -48
- splent_cli-1.1.1/src/splent_cli/commands/database/db_reset.py +0 -107
- splent_cli-1.1.1/src/splent_cli/commands/database/db_rollback.py +0 -34
- splent_cli-1.1.1/src/splent_cli/commands/database/db_seed.py +0 -103
- splent_cli-1.1.1/src/splent_cli/commands/database/db_status.py +0 -40
- splent_cli-1.1.1/src/splent_cli/commands/database/db_upgrade.py +0 -42
- splent_cli-1.1.1/src/splent_cli/commands/doctor.py +0 -281
- splent_cli-1.1.1/src/splent_cli/commands/feature/feature_detach.py +0 -74
- splent_cli-1.1.1/src/splent_cli/commands/feature/feature_edit.py +0 -193
- splent_cli-1.1.1/src/splent_cli/commands/feature/feature_list.py +0 -31
- splent_cli-1.1.1/src/splent_cli/commands/feature/feature_release.py +0 -316
- splent_cli-1.1.1/src/splent_cli/commands/feature/feature_remove.py +0 -72
- splent_cli-1.1.1/src/splent_cli/commands/linter.py +0 -43
- splent_cli-1.1.1/src/splent_cli/commands/product/product_release.py +0 -249
- splent_cli-1.1.1/src/splent_cli/commands/product/product_sync.py +0 -105
- splent_cli-1.1.1/src/splent_cli/commands/product/product_up.py +0 -93
- splent_cli-1.1.1/src/splent_cli/commands/test.py +0 -122
- splent_cli-1.1.1/src/splent_cli/commands/uvl/uvl_check.py +0 -234
- splent_cli-1.1.1/src/splent_cli/commands/uvl/uvl_configs.py +0 -86
- splent_cli-1.1.1/src/splent_cli/commands/uvl/uvl_deps.py +0 -130
- splent_cli-1.1.1/src/splent_cli/commands/uvl/uvl_features.py +0 -124
- splent_cli-1.1.1/src/splent_cli/commands/uvl/uvl_fetch.py +0 -99
- splent_cli-1.1.1/src/splent_cli/commands/uvl/uvl_info.py +0 -98
- splent_cli-1.1.1/src/splent_cli/commands/uvl/uvl_missing.py +0 -211
- splent_cli-1.1.1/src/splent_cli/commands/uvl/uvl_valid.py +0 -164
- splent_cli-1.1.1/src/splent_cli/commands/version.py +0 -60
- splent_cli-1.1.1/src/splent_cli/commands/webpack_compile.py +0 -118
- splent_cli-1.1.1/src/splent_cli/utils/decorators.py +0 -6
- splent_cli-1.1.1/src/splent_cli/utils/feature_utils.py +0 -39
- splent_cli-1.1.1/src/splent_cli/utils/path_utils.py +0 -90
- {splent_cli-1.1.1 → splent_cli-1.2.6}/LICENSE +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/setup.cfg +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.1.1/src/splent_cli/commands/check → splent_cli-1.2.6/src/splent_cli/commands/cache}/__init__.py +0 -0
- {splent_cli-1.1.1/src/splent_cli/commands/product → splent_cli-1.2.6/src/splent_cli/commands/check}/__init__.py +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/clear_log.py +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/clear_uploads.py +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/command_create.py +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.1.1/src/splent_cli/commands/release → splent_cli-1.2.6/src/splent_cli/commands/product}/__init__.py +0 -0
- {splent_cli-1.1.1/src/splent_cli/utils → splent_cli-1.2.6/src/splent_cli/commands/release}/__init__.py +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.1.1 → splent_cli-1.2.6}/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.2.6
|
|
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
|
|
@@ -9,6 +9,7 @@ Requires-Python: >=3.13
|
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: click==8.1.8
|
|
12
|
+
Requires-Dist: packaging>=24.0
|
|
12
13
|
Provides-Extra: dev
|
|
13
14
|
Requires-Dist: setuptools==80.3.1; extra == "dev"
|
|
14
15
|
Requires-Dist: pytest==8.3.4; extra == "dev"
|
|
@@ -38,4 +39,39 @@ Requires-Dist: tomli_w==1.2.0; extra == "dev"
|
|
|
38
39
|
Requires-Dist: PyYAML==6.0.3; extra == "dev"
|
|
39
40
|
Dynamic: license-file
|
|
40
41
|
|
|
41
|
-
#
|
|
42
|
+
# SPLENT CLI
|
|
43
|
+
|
|
44
|
+
Command-line tool for managing SPLENT products, features, databases, and environments.
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
make setup # Prepare .env, start Docker, enter CLI container
|
|
50
|
+
splent --help # See all available commands
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Key commands
|
|
54
|
+
|
|
55
|
+
| Command | Description |
|
|
56
|
+
|---------|-------------|
|
|
57
|
+
| `product:create` | Create a new product |
|
|
58
|
+
| `product:derive --dev` | Full SPL derivation pipeline |
|
|
59
|
+
| `feature:add` / `feature:attach` | Add features to a product |
|
|
60
|
+
| `feature:status` | Show feature lifecycle states |
|
|
61
|
+
| `feature:release` | Release a feature (tag + PyPI + GitHub) |
|
|
62
|
+
| `db:migrate` / `db:upgrade` | Manage per-feature migrations |
|
|
63
|
+
| `export:puml` | Generate PlantUML diagrams |
|
|
64
|
+
| `doctor` | System health check |
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
|
|
68
|
+
- Docker + Docker Compose
|
|
69
|
+
- Python 3.13+
|
|
70
|
+
|
|
71
|
+
## Documentation
|
|
72
|
+
|
|
73
|
+
Full documentation at **[docs.splent.io](https://docs.splent.io)**
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
Creative Commons CC BY 4.0 - SPLENT - Diverso Lab
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# SPLENT CLI
|
|
2
|
+
|
|
3
|
+
Command-line tool for managing SPLENT products, features, databases, and environments.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
make setup # Prepare .env, start Docker, enter CLI container
|
|
9
|
+
splent --help # See all available commands
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Key commands
|
|
13
|
+
|
|
14
|
+
| Command | Description |
|
|
15
|
+
|---------|-------------|
|
|
16
|
+
| `product:create` | Create a new product |
|
|
17
|
+
| `product:derive --dev` | Full SPL derivation pipeline |
|
|
18
|
+
| `feature:add` / `feature:attach` | Add features to a product |
|
|
19
|
+
| `feature:status` | Show feature lifecycle states |
|
|
20
|
+
| `feature:release` | Release a feature (tag + PyPI + GitHub) |
|
|
21
|
+
| `db:migrate` / `db:upgrade` | Manage per-feature migrations |
|
|
22
|
+
| `export:puml` | Generate PlantUML diagrams |
|
|
23
|
+
| `doctor` | System health check |
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
- Docker + Docker Compose
|
|
28
|
+
- Python 3.13+
|
|
29
|
+
|
|
30
|
+
## Documentation
|
|
31
|
+
|
|
32
|
+
Full documentation at **[docs.splent.io](https://docs.splent.io)**
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
Creative Commons CC BY 4.0 - SPLENT - Diverso Lab
|
|
@@ -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.2.6"
|
|
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"
|
|
@@ -13,6 +13,7 @@ license-files = ["LICENSE"]
|
|
|
13
13
|
|
|
14
14
|
dependencies = [
|
|
15
15
|
"click==8.1.8",
|
|
16
|
+
"packaging>=24.0",
|
|
16
17
|
]
|
|
17
18
|
|
|
18
19
|
[project.optional-dependencies]
|
|
@@ -54,6 +55,10 @@ package-dir = { "" = "src" }
|
|
|
54
55
|
[tool.setuptools.packages.find]
|
|
55
56
|
where = ["src"]
|
|
56
57
|
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
pythonpath = ["src"]
|
|
61
|
+
|
|
57
62
|
[tool.black]
|
|
58
63
|
line-length = 79
|
|
59
64
|
|
|
@@ -3,6 +3,7 @@ from dotenv import load_dotenv
|
|
|
3
3
|
|
|
4
4
|
from splent_cli.utils.dynamic_imports import get_app
|
|
5
5
|
from splent_cli.utils.command_loader import load_commands
|
|
6
|
+
from splent_cli.utils.db_utils import check_db_connection
|
|
6
7
|
|
|
7
8
|
load_dotenv()
|
|
8
9
|
|
|
@@ -12,6 +13,7 @@ class SPLENTCLI(click.Group):
|
|
|
12
13
|
Main SPLENT CLI class.
|
|
13
14
|
|
|
14
15
|
- Automatically injects the Flask app context for commands marked with `requires_app = True`.
|
|
16
|
+
- Checks DB connectivity for commands marked with `requires_db = True`.
|
|
15
17
|
- Displays commands grouped by category for a cleaner, more readable help output.
|
|
16
18
|
"""
|
|
17
19
|
|
|
@@ -21,6 +23,9 @@ class SPLENTCLI(click.Group):
|
|
|
21
23
|
|
|
22
24
|
if command and getattr(command, "requires_app", False):
|
|
23
25
|
app = get_app()
|
|
26
|
+
if getattr(command, "requires_db", False):
|
|
27
|
+
if not check_db_connection(app):
|
|
28
|
+
raise SystemExit(1)
|
|
24
29
|
with app.app_context():
|
|
25
30
|
return super().invoke(ctx)
|
|
26
31
|
|
|
@@ -39,11 +44,12 @@ class SPLENTCLI(click.Group):
|
|
|
39
44
|
cmd for cmd in self.commands if cmd.startswith("uvl:")
|
|
40
45
|
],
|
|
41
46
|
"🧱 Database": [cmd for cmd in self.commands if cmd.startswith("db:")],
|
|
47
|
+
"💾 Cache": [cmd for cmd in self.commands if cmd.startswith("cache:")],
|
|
42
48
|
"🧰 Utilities": [
|
|
43
49
|
cmd
|
|
44
50
|
for cmd in self.commands
|
|
45
51
|
if cmd.startswith(
|
|
46
|
-
("clear:", "env", "select", "info", "version", "doctor")
|
|
52
|
+
("clear:", "env", "select", "info", "version", "doctor", "tokens")
|
|
47
53
|
)
|
|
48
54
|
],
|
|
49
55
|
"🐍 Development & QA": [
|
|
@@ -51,9 +57,6 @@ class SPLENTCLI(click.Group):
|
|
|
51
57
|
for cmd in self.commands
|
|
52
58
|
if cmd.startswith(("linter", "test", "coverage", "locust"))
|
|
53
59
|
],
|
|
54
|
-
"⚙️ Build & Assets": [
|
|
55
|
-
cmd for cmd in self.commands if cmd.startswith("webpack:")
|
|
56
|
-
],
|
|
57
60
|
}
|
|
58
61
|
for title, cmds in groups.items():
|
|
59
62
|
cmds = [c for c in cmds if c in self.commands]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from splent_cli.services import context, compose
|
|
2
|
+
import shutil
|
|
3
|
+
import click
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command("cache:clear", short_help="Clear the feature cache (total or partial).")
|
|
8
|
+
@click.option(
|
|
9
|
+
"--namespace",
|
|
10
|
+
default=None,
|
|
11
|
+
help="Clear only a specific namespace (e.g. splent_io).",
|
|
12
|
+
)
|
|
13
|
+
@click.option(
|
|
14
|
+
"--feature",
|
|
15
|
+
default=None,
|
|
16
|
+
help="Clear all entries for a specific feature (e.g. splent_feature_auth).",
|
|
17
|
+
)
|
|
18
|
+
@click.option("--yes", is_flag=True, help="Skip confirmation prompt.")
|
|
19
|
+
def cache_clear(namespace, feature, yes):
|
|
20
|
+
"""
|
|
21
|
+
Deletes entries from the feature cache and removes broken symlinks in products.
|
|
22
|
+
|
|
23
|
+
\b
|
|
24
|
+
Scope (most to least specific):
|
|
25
|
+
--feature splent_feature_auth → removes all cache entries for that feature
|
|
26
|
+
--namespace splent_io → removes the entire namespace folder
|
|
27
|
+
(no options) → clears the entire feature cache
|
|
28
|
+
"""
|
|
29
|
+
workspace = context.workspace()
|
|
30
|
+
cache_root = workspace / ".splent_cache" / "features"
|
|
31
|
+
|
|
32
|
+
if not cache_root.exists():
|
|
33
|
+
click.secho("⚠️ No .splent_cache/features directory found.", fg="yellow")
|
|
34
|
+
raise SystemExit(0)
|
|
35
|
+
|
|
36
|
+
# Resolve targets
|
|
37
|
+
if feature:
|
|
38
|
+
targets = []
|
|
39
|
+
search_in = [cache_root / namespace] if namespace else cache_root.iterdir()
|
|
40
|
+
for ns_dir in search_in if namespace else cache_root.iterdir():
|
|
41
|
+
ns_dir = Path(ns_dir)
|
|
42
|
+
if not ns_dir.is_dir():
|
|
43
|
+
continue
|
|
44
|
+
for feat_dir in ns_dir.iterdir():
|
|
45
|
+
base = feat_dir.name.split("@")[0]
|
|
46
|
+
if base == feature:
|
|
47
|
+
targets.append(feat_dir)
|
|
48
|
+
if not targets:
|
|
49
|
+
label = f"'{feature}'" + (
|
|
50
|
+
f" in namespace '{namespace}'" if namespace else ""
|
|
51
|
+
)
|
|
52
|
+
click.secho(f"⚠️ No cache entries found for {label}.", fg="yellow")
|
|
53
|
+
raise SystemExit(0)
|
|
54
|
+
description = f"{len(targets)} entry/entries for feature '{feature}'"
|
|
55
|
+
elif namespace:
|
|
56
|
+
targets = [cache_root / namespace]
|
|
57
|
+
if not targets[0].exists():
|
|
58
|
+
click.secho(f"⚠️ Namespace '{namespace}' not found in cache.", fg="yellow")
|
|
59
|
+
raise SystemExit(0)
|
|
60
|
+
description = f"namespace '{namespace}'"
|
|
61
|
+
else:
|
|
62
|
+
targets = [cache_root]
|
|
63
|
+
description = "entire feature cache"
|
|
64
|
+
|
|
65
|
+
if not yes and not click.confirm(
|
|
66
|
+
f"⚠️ This will permanently delete the {description}. Continue?"
|
|
67
|
+
):
|
|
68
|
+
click.echo("❎ Cancelled.")
|
|
69
|
+
raise SystemExit(0)
|
|
70
|
+
|
|
71
|
+
# Delete
|
|
72
|
+
for t in targets:
|
|
73
|
+
shutil.rmtree(t)
|
|
74
|
+
cache_root.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
click.secho(f"🧹 Cleared: {description}.", fg="green")
|
|
76
|
+
|
|
77
|
+
# Clean broken symlinks
|
|
78
|
+
removed = compose.remove_broken_symlinks(workspace)
|
|
79
|
+
if removed:
|
|
80
|
+
click.secho(f"🔗 Removed {removed} broken feature symlink(s).", fg="yellow")
|
|
81
|
+
else:
|
|
82
|
+
click.secho("✅ No broken symlinks found.", fg="green")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
cli_command = cache_clear
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from splent_cli.services import context
|
|
2
|
+
import click
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _get_cache_entries(cache_root: Path) -> list:
|
|
7
|
+
"""Returns list of {namespace, name, version, is_versioned} dicts."""
|
|
8
|
+
entries = []
|
|
9
|
+
if not cache_root.exists():
|
|
10
|
+
return entries
|
|
11
|
+
for ns_dir in sorted(cache_root.iterdir()):
|
|
12
|
+
if not ns_dir.is_dir():
|
|
13
|
+
continue
|
|
14
|
+
for feat_dir in sorted(ns_dir.iterdir()):
|
|
15
|
+
if not feat_dir.is_dir():
|
|
16
|
+
continue
|
|
17
|
+
feat = feat_dir.name
|
|
18
|
+
if "@" in feat:
|
|
19
|
+
name, version = feat.split("@", 1)
|
|
20
|
+
entries.append(
|
|
21
|
+
{
|
|
22
|
+
"namespace": ns_dir.name,
|
|
23
|
+
"name": name,
|
|
24
|
+
"version": version,
|
|
25
|
+
"is_versioned": True,
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
else:
|
|
29
|
+
entries.append(
|
|
30
|
+
{
|
|
31
|
+
"namespace": ns_dir.name,
|
|
32
|
+
"name": feat,
|
|
33
|
+
"version": None,
|
|
34
|
+
"is_versioned": False,
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
return entries
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_all_product_refs(workspace: Path) -> set:
|
|
41
|
+
"""Returns set of 'name' and 'name@version' (no namespace) from all products' pyproject.toml."""
|
|
42
|
+
import tomllib
|
|
43
|
+
refs = set()
|
|
44
|
+
for product_dir in sorted(workspace.iterdir()):
|
|
45
|
+
if not product_dir.is_dir() or product_dir.name.startswith("."):
|
|
46
|
+
continue
|
|
47
|
+
pyproject = product_dir / "pyproject.toml"
|
|
48
|
+
if not pyproject.exists():
|
|
49
|
+
continue
|
|
50
|
+
try:
|
|
51
|
+
with open(pyproject, "rb") as f:
|
|
52
|
+
data = tomllib.load(f)
|
|
53
|
+
splent = data.get("tool", {}).get("splent", {})
|
|
54
|
+
feats = list(splent.get("features", []))
|
|
55
|
+
feats += list(splent.get("features_dev", []))
|
|
56
|
+
feats += list(splent.get("features_prod", []))
|
|
57
|
+
for entry in feats:
|
|
58
|
+
ref = entry.split("/", 1)[1] if "/" in entry else entry
|
|
59
|
+
refs.add(ref)
|
|
60
|
+
except Exception:
|
|
61
|
+
continue
|
|
62
|
+
return refs
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@click.command(
|
|
66
|
+
"cache:orphans", short_help="Show cached features not referenced by any product."
|
|
67
|
+
)
|
|
68
|
+
def cache_orphans():
|
|
69
|
+
"""Lists features in the cache that no product references in its pyproject.toml."""
|
|
70
|
+
workspace = context.workspace()
|
|
71
|
+
cache_root = workspace / ".splent_cache" / "features"
|
|
72
|
+
|
|
73
|
+
entries = _get_cache_entries(cache_root)
|
|
74
|
+
if not entries:
|
|
75
|
+
click.secho("ℹ️ Feature cache is empty.", fg="yellow")
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
refs = _get_all_product_refs(workspace)
|
|
79
|
+
|
|
80
|
+
orphans = []
|
|
81
|
+
for e in entries:
|
|
82
|
+
# Match against just name[@version] — pyproject doesn't include namespace
|
|
83
|
+
ref_key = f"{e['name']}@{e['version']}" if e["is_versioned"] else e["name"]
|
|
84
|
+
if ref_key not in refs:
|
|
85
|
+
display_key = f"{e['namespace']}/{ref_key}"
|
|
86
|
+
orphans.append(display_key)
|
|
87
|
+
|
|
88
|
+
if not orphans:
|
|
89
|
+
click.secho("✅ No orphaned features in cache.", fg="green")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
click.secho(f"🗑 Orphaned cache entries ({len(orphans)}):", fg="yellow")
|
|
93
|
+
for o in orphans:
|
|
94
|
+
click.echo(f" - {o}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
cli_command = cache_orphans
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from splent_cli.services import context
|
|
2
|
+
import re
|
|
3
|
+
import click
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from packaging.version import Version, InvalidVersion
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _get_cache_versions(cache_root: Path) -> dict:
|
|
10
|
+
"""Returns {name: [version, ...]} with all versioned snapshots in cache."""
|
|
11
|
+
versions = defaultdict(list)
|
|
12
|
+
if not cache_root.exists():
|
|
13
|
+
return versions
|
|
14
|
+
for ns_dir in cache_root.iterdir():
|
|
15
|
+
if not ns_dir.is_dir():
|
|
16
|
+
continue
|
|
17
|
+
for feat_dir in ns_dir.iterdir():
|
|
18
|
+
if not feat_dir.is_dir() or "@" not in feat_dir.name:
|
|
19
|
+
continue
|
|
20
|
+
name, version = feat_dir.name.split("@", 1)
|
|
21
|
+
versions[name].append(version)
|
|
22
|
+
return versions
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _latest(versions: list) -> str:
|
|
26
|
+
"""Returns the latest version string, using semver if possible."""
|
|
27
|
+
|
|
28
|
+
def sort_key(v):
|
|
29
|
+
try:
|
|
30
|
+
return Version(v.lstrip("v"))
|
|
31
|
+
except InvalidVersion:
|
|
32
|
+
return Version("0")
|
|
33
|
+
|
|
34
|
+
return max(versions, key=sort_key)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_product_features(workspace: Path) -> dict:
|
|
38
|
+
"""Returns {product_name: {feature_name: version_or_None}}."""
|
|
39
|
+
products = {}
|
|
40
|
+
for product_dir in sorted(workspace.iterdir()):
|
|
41
|
+
if not product_dir.is_dir() or product_dir.name.startswith("."):
|
|
42
|
+
continue
|
|
43
|
+
pyproject = product_dir / "pyproject.toml"
|
|
44
|
+
if not pyproject.exists():
|
|
45
|
+
continue
|
|
46
|
+
content = pyproject.read_text()
|
|
47
|
+
m = re.search(
|
|
48
|
+
r"\[project\.optional-dependencies\].*?features\s*=\s*\[(.*?)\]",
|
|
49
|
+
content,
|
|
50
|
+
re.DOTALL,
|
|
51
|
+
)
|
|
52
|
+
if not m:
|
|
53
|
+
continue
|
|
54
|
+
features = {}
|
|
55
|
+
for raw in re.findall(r'"([^"]+)"|\'([^\']+)\'', m.group(1)):
|
|
56
|
+
ref = raw[0] or raw[1]
|
|
57
|
+
if "/" in ref:
|
|
58
|
+
ref = ref.split("/", 1)[1]
|
|
59
|
+
if "@" in ref:
|
|
60
|
+
name, version = ref.split("@", 1)
|
|
61
|
+
features[name] = version
|
|
62
|
+
else:
|
|
63
|
+
features[ref] = None
|
|
64
|
+
if features:
|
|
65
|
+
products[product_dir.name] = features
|
|
66
|
+
return products
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@click.command(
|
|
70
|
+
"cache:outdated",
|
|
71
|
+
short_help="Show products using older versions than what's in cache.",
|
|
72
|
+
)
|
|
73
|
+
def cache_outdated():
|
|
74
|
+
"""
|
|
75
|
+
Compares the version each product uses against all versions available in cache.
|
|
76
|
+
Reports features where a newer version exists locally.
|
|
77
|
+
"""
|
|
78
|
+
workspace = context.workspace()
|
|
79
|
+
cache_root = workspace / ".splent_cache" / "features"
|
|
80
|
+
|
|
81
|
+
cache_versions = _get_cache_versions(cache_root)
|
|
82
|
+
if not cache_versions:
|
|
83
|
+
click.secho("ℹ️ No versioned snapshots in cache.", fg="yellow")
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
products = _get_product_features(workspace)
|
|
87
|
+
if not products:
|
|
88
|
+
click.secho("ℹ️ No products with declared features found.", fg="yellow")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
outdated = []
|
|
92
|
+
for product, features in sorted(products.items()):
|
|
93
|
+
for name, current_ver in features.items():
|
|
94
|
+
available = cache_versions.get(name)
|
|
95
|
+
if not available:
|
|
96
|
+
continue
|
|
97
|
+
if current_ver is None:
|
|
98
|
+
# Editable — show available versions as informational
|
|
99
|
+
outdated.append(
|
|
100
|
+
(product, name, "(editable)", _latest(available), available)
|
|
101
|
+
)
|
|
102
|
+
continue
|
|
103
|
+
latest = _latest(available)
|
|
104
|
+
try:
|
|
105
|
+
is_old = Version(latest.lstrip("v")) > Version(current_ver.lstrip("v"))
|
|
106
|
+
except InvalidVersion:
|
|
107
|
+
is_old = latest != current_ver
|
|
108
|
+
if is_old:
|
|
109
|
+
outdated.append((product, name, current_ver, latest, available))
|
|
110
|
+
|
|
111
|
+
if not outdated:
|
|
112
|
+
click.secho("✅ All products are on the latest cached version.", fg="green")
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
click.secho(f"Outdated features ({len(outdated)}):\n", fg="yellow")
|
|
116
|
+
for product, name, current, latest, available in outdated:
|
|
117
|
+
click.echo(
|
|
118
|
+
f" {click.style(product, bold=True)} {name} "
|
|
119
|
+
f"{click.style(current, fg='red')} → {click.style(latest, fg='green')}"
|
|
120
|
+
)
|
|
121
|
+
others = sorted(set(available) - {latest})
|
|
122
|
+
if others:
|
|
123
|
+
click.echo(f" also cached: {', '.join(others)}")
|
|
124
|
+
click.echo()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
cli_command = cache_outdated
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from splent_cli.services import context, compose
|
|
2
|
+
import re
|
|
3
|
+
import shutil
|
|
4
|
+
import click
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _get_cache_entries(cache_root: Path) -> list:
|
|
9
|
+
entries = []
|
|
10
|
+
if not cache_root.exists():
|
|
11
|
+
return entries
|
|
12
|
+
for ns_dir in sorted(cache_root.iterdir()):
|
|
13
|
+
if not ns_dir.is_dir():
|
|
14
|
+
continue
|
|
15
|
+
for feat_dir in sorted(ns_dir.iterdir()):
|
|
16
|
+
if not feat_dir.is_dir():
|
|
17
|
+
continue
|
|
18
|
+
feat = feat_dir.name
|
|
19
|
+
if "@" in feat:
|
|
20
|
+
name, version = feat.split("@", 1)
|
|
21
|
+
entries.append(
|
|
22
|
+
{
|
|
23
|
+
"namespace": ns_dir.name,
|
|
24
|
+
"name": name,
|
|
25
|
+
"version": version,
|
|
26
|
+
"is_versioned": True,
|
|
27
|
+
"path": feat_dir,
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
else:
|
|
31
|
+
entries.append(
|
|
32
|
+
{
|
|
33
|
+
"namespace": ns_dir.name,
|
|
34
|
+
"name": feat,
|
|
35
|
+
"version": None,
|
|
36
|
+
"is_versioned": False,
|
|
37
|
+
"path": feat_dir,
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
return entries
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_all_product_refs(workspace: Path) -> set:
|
|
44
|
+
refs = set()
|
|
45
|
+
for product_dir in sorted(workspace.iterdir()):
|
|
46
|
+
if not product_dir.is_dir() or product_dir.name.startswith("."):
|
|
47
|
+
continue
|
|
48
|
+
pyproject = product_dir / "pyproject.toml"
|
|
49
|
+
if not pyproject.exists():
|
|
50
|
+
continue
|
|
51
|
+
content = pyproject.read_text()
|
|
52
|
+
m = re.search(
|
|
53
|
+
r"\[project\.optional-dependencies\].*?features\s*=\s*\[(.*?)\]",
|
|
54
|
+
content,
|
|
55
|
+
re.DOTALL,
|
|
56
|
+
)
|
|
57
|
+
if not m:
|
|
58
|
+
continue
|
|
59
|
+
for raw in re.findall(r'"([^"]+)"|\'([^\']+)\'', m.group(1)):
|
|
60
|
+
ref = raw[0] or raw[1]
|
|
61
|
+
if "/" in ref:
|
|
62
|
+
ref = ref.split("/", 1)[1]
|
|
63
|
+
refs.add(ref)
|
|
64
|
+
return refs
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@click.command(
|
|
68
|
+
"cache:prune", short_help="Remove orphaned cache entries not used by any product."
|
|
69
|
+
)
|
|
70
|
+
@click.option("--yes", is_flag=True, help="Skip confirmation prompt.")
|
|
71
|
+
def cache_prune(yes):
|
|
72
|
+
"""
|
|
73
|
+
Removes cache entries that no product references in its pyproject.toml,
|
|
74
|
+
then cleans up broken symlinks in products.
|
|
75
|
+
"""
|
|
76
|
+
workspace = context.workspace()
|
|
77
|
+
cache_root = workspace / ".splent_cache" / "features"
|
|
78
|
+
|
|
79
|
+
entries = _get_cache_entries(cache_root)
|
|
80
|
+
if not entries:
|
|
81
|
+
click.secho("ℹ️ Feature cache is empty.", fg="yellow")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
refs = _get_all_product_refs(workspace)
|
|
85
|
+
|
|
86
|
+
orphans = [
|
|
87
|
+
e
|
|
88
|
+
for e in entries
|
|
89
|
+
if (f"{e['name']}@{e['version']}" if e["is_versioned"] else e["name"])
|
|
90
|
+
not in refs
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
if not orphans:
|
|
94
|
+
click.secho("✅ Nothing to prune — no orphaned entries.", fg="green")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
click.secho(f"Orphaned entries to remove ({len(orphans)}):", fg="yellow")
|
|
98
|
+
for e in orphans:
|
|
99
|
+
label = (
|
|
100
|
+
f"{e['name']}@{e['version']}"
|
|
101
|
+
if e["is_versioned"]
|
|
102
|
+
else f"{e['name']} (editable)"
|
|
103
|
+
)
|
|
104
|
+
click.echo(f" - {e['namespace']}/{label}")
|
|
105
|
+
|
|
106
|
+
click.echo()
|
|
107
|
+
if not yes and not click.confirm("Remove all of the above?"):
|
|
108
|
+
click.echo("❎ Cancelled.")
|
|
109
|
+
raise SystemExit(0)
|
|
110
|
+
|
|
111
|
+
for e in orphans:
|
|
112
|
+
shutil.rmtree(e["path"])
|
|
113
|
+
|
|
114
|
+
click.secho(f"🧹 Pruned {len(orphans)} orphaned cache entry/entries.", fg="green")
|
|
115
|
+
|
|
116
|
+
removed = compose.remove_broken_symlinks(workspace)
|
|
117
|
+
if removed:
|
|
118
|
+
click.secho(f"🔗 Removed {removed} broken feature symlink(s).", fg="yellow")
|
|
119
|
+
else:
|
|
120
|
+
click.secho("✅ No broken symlinks found.", fg="green")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
cli_command = cache_prune
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from splent_cli.services import context
|
|
2
|
+
import click
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _dir_size(path: Path) -> int:
|
|
7
|
+
return sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _human(size: int) -> str:
|
|
11
|
+
for unit in ("B", "KB", "MB", "GB"):
|
|
12
|
+
if size < 1024:
|
|
13
|
+
return f"{size:.1f} {unit}"
|
|
14
|
+
size /= 1024
|
|
15
|
+
return f"{size:.1f} TB"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command("cache:size", short_help="Show disk usage of the feature cache.")
|
|
19
|
+
def cache_size():
|
|
20
|
+
"""Shows disk usage per namespace and feature entry in the cache."""
|
|
21
|
+
workspace = context.workspace()
|
|
22
|
+
cache_root = workspace / ".splent_cache" / "features"
|
|
23
|
+
|
|
24
|
+
if not cache_root.exists():
|
|
25
|
+
click.secho("ℹ️ Feature cache is empty.", fg="yellow")
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
total = 0
|
|
29
|
+
namespaces = sorted(d for d in cache_root.iterdir() if d.is_dir())
|
|
30
|
+
|
|
31
|
+
if not namespaces:
|
|
32
|
+
click.secho("ℹ️ Feature cache is empty.", fg="yellow")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
for ns_dir in namespaces:
|
|
36
|
+
ns_size = _dir_size(ns_dir)
|
|
37
|
+
total += ns_size
|
|
38
|
+
click.secho(f" {ns_dir.name} {_human(ns_size)}", bold=True)
|
|
39
|
+
|
|
40
|
+
entries = sorted(d for d in ns_dir.iterdir() if d.is_dir())
|
|
41
|
+
for i, feat_dir in enumerate(entries):
|
|
42
|
+
size = _dir_size(feat_dir)
|
|
43
|
+
connector = "└──" if i == len(entries) - 1 else "├──"
|
|
44
|
+
name = feat_dir.name
|
|
45
|
+
label = (
|
|
46
|
+
click.style(f"@{name.split('@')[1]}", fg="green")
|
|
47
|
+
if "@" in name
|
|
48
|
+
else click.style("editable", fg="blue")
|
|
49
|
+
)
|
|
50
|
+
base = name.split("@")[0] if "@" in name else name
|
|
51
|
+
click.echo(f" {connector} {base} {label} {_human(size)}")
|
|
52
|
+
click.echo()
|
|
53
|
+
|
|
54
|
+
click.secho(f" Total: {_human(total)}", fg="cyan", bold=True)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
cli_command = cache_size
|