splent-cli 1.5.0__tar.gz → 1.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. {splent_cli-1.5.0/src/splent_cli.egg-info → splent_cli-1.6.0}/PKG-INFO +4 -17
  2. splent_cli-1.6.0/README.md +23 -0
  3. {splent_cli-1.5.0 → splent_cli-1.6.0}/pyproject.toml +1 -1
  4. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/cli.py +18 -10
  5. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_clear.py +4 -1
  6. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_outdated.py +1 -1
  7. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_github.py +8 -4
  8. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_infra.py +110 -0
  9. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_product.py +1 -1
  10. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_pypi.py +7 -3
  11. splent_cli-1.5.0/src/splent_cli/commands/clear_cache.py → splent_cli-1.6.0/src/splent_cli/commands/clear/clear_build.py +2 -2
  12. {splent_cli-1.5.0/src/splent_cli/commands → splent_cli-1.6.0/src/splent_cli/commands/clear}/clear_log.py +1 -1
  13. {splent_cli-1.5.0/src/splent_cli/commands → splent_cli-1.6.0/src/splent_cli/commands/clear}/clear_uploads.py +1 -1
  14. {splent_cli-1.5.0/src/splent_cli/commands → splent_cli-1.6.0/src/splent_cli/commands/command}/command_create.py +1 -1
  15. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/coverage.py +1 -1
  16. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_console.py +1 -1
  17. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_dump.py +1 -1
  18. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_migrate.py +7 -34
  19. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_reset.py +1 -1
  20. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_rollback.py +22 -1
  21. splent_cli-1.6.0/src/splent_cli/commands/doctor.py +142 -0
  22. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/env/env_list.py +4 -1
  23. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/env/env_show.py +1 -1
  24. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_add.py +1 -1
  25. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_attach.py +1 -1
  26. splent_cli-1.6.0/src/splent_cli/commands/feature/feature_clean.py +365 -0
  27. splent_cli-1.5.0/src/splent_cli/commands/feature/feature_diff.py → splent_cli-1.6.0/src/splent_cli/commands/feature/feature_compat.py +95 -214
  28. {splent_cli-1.5.0/src/splent_cli/commands → splent_cli-1.6.0/src/splent_cli/commands/feature}/feature_compile.py +2 -1
  29. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_contract.py +35 -0
  30. splent_cli-1.6.0/src/splent_cli/commands/feature/feature_create.py +275 -0
  31. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_delete.py +1 -1
  32. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_detach.py +1 -1
  33. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_discard.py +1 -1
  34. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_drift.py +1 -1
  35. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_env.py +1 -1
  36. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_inject_config.py +1 -1
  37. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_list.py +2 -1
  38. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_outdated.py +2 -2
  39. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_pin.py +1 -1
  40. splent_cli-1.6.0/src/splent_cli/commands/feature/feature_refine.py +879 -0
  41. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_release.py +139 -19
  42. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_remove.py +1 -1
  43. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_rename.py +1 -1
  44. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_status.py +1 -1
  45. splent_cli-1.5.0/src/splent_cli/commands/feature/feature_edit.py → splent_cli-1.6.0/src/splent_cli/commands/feature/feature_unlock.py +12 -1
  46. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_upgrade.py +1 -1
  47. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_xray.py +210 -182
  48. splent_cli-1.5.0/src/splent_cli/commands/linter.py → splent_cli-1.6.0/src/splent_cli/commands/lint.py +1 -3
  49. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/locust.py +2 -4
  50. splent_cli-1.5.0/src/splent_cli/commands/product/product_complete.py → splent_cli-1.6.0/src/splent_cli/commands/product/product_auto_require.py +2 -2
  51. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_build.py +142 -12
  52. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_clean.py +1 -1
  53. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_config.py +1 -1
  54. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_configure.py +2 -2
  55. splent_cli-1.5.0/src/splent_cli/commands/product/product_status.py → splent_cli-1.6.0/src/splent_cli/commands/product/product_containers.py +2 -2
  56. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_create.py +1 -1
  57. splent_cli-1.6.0/src/splent_cli/commands/product/product_deploy.py +276 -0
  58. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_derive.py +4 -4
  59. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_deselect.py +9 -4
  60. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_env.py +1 -3
  61. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_missing.py +1 -1
  62. splent_cli-1.5.0/src/splent_cli/commands/product/product_sync.py → splent_cli-1.6.0/src/splent_cli/commands/product/product_resolve.py +3 -3
  63. splent_cli-1.6.0/src/splent_cli/commands/product/product_restart.py +248 -0
  64. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_select.py +1 -1
  65. splent_cli-1.6.0/src/splent_cli/commands/product/product_up.py +267 -0
  66. splent_cli-1.6.0/src/splent_cli/commands/product/product_validate.py +371 -0
  67. splent_cli-1.6.0/src/splent_cli/commands/spl/__init__.py +0 -0
  68. splent_cli-1.5.0/src/splent_cli/commands/spl/spl_fix.py → splent_cli-1.6.0/src/splent_cli/commands/spl/spl_add_constraints.py +3 -3
  69. splent_cli-1.5.0/src/splent_cli/commands/spl/spl_configs.py → splent_cli-1.6.0/src/splent_cli/commands/spl/spl_configurations.py +2 -2
  70. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_features.py +1 -1
  71. splent_cli-1.5.0/src/splent_cli/commands/tokens.py → splent_cli-1.6.0/src/splent_cli/commands/tokens_setup.py +6 -3
  72. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/version.py +4 -1
  73. splent_cli-1.6.0/src/splent_cli/services/__init__.py +0 -0
  74. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/services/compose.py +5 -3
  75. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/services/context.py +3 -3
  76. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/services/preflight.py +31 -13
  77. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/services/release.py +2 -2
  78. splent_cli-1.6.0/src/splent_cli/utils/__init__.py +0 -0
  79. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/integrity.py +2 -2
  80. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/lifecycle.py +3 -3
  81. {splent_cli-1.5.0 → splent_cli-1.6.0/src/splent_cli.egg-info}/PKG-INFO +4 -17
  82. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli.egg-info/SOURCES.txt +20 -15
  83. splent_cli-1.5.0/README.md +0 -36
  84. splent_cli-1.5.0/src/splent_cli/commands/doctor.py +0 -140
  85. splent_cli-1.5.0/src/splent_cli/commands/feature/feature_create.py +0 -214
  86. splent_cli-1.5.0/src/splent_cli/commands/product/product_deploy.py +0 -173
  87. splent_cli-1.5.0/src/splent_cli/commands/product/product_restart.py +0 -64
  88. splent_cli-1.5.0/src/splent_cli/commands/product/product_up.py +0 -105
  89. splent_cli-1.5.0/src/splent_cli/commands/product/product_validate.py +0 -157
  90. {splent_cli-1.5.0 → splent_cli-1.6.0}/LICENSE +0 -0
  91. {splent_cli-1.5.0 → splent_cli-1.6.0}/setup.cfg +0 -0
  92. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/__init__.py +0 -0
  93. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/__main__.py +0 -0
  94. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/__init__.py +0 -0
  95. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  96. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  97. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  98. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  99. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  100. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  101. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  102. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/__init__.py +0 -0
  103. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  104. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  105. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_env.py +0 -0
  106. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_features.py +0 -0
  107. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  108. {splent_cli-1.5.0/src/splent_cli/commands/product → splent_cli-1.6.0/src/splent_cli/commands/clear}/__init__.py +0 -0
  109. {splent_cli-1.5.0/src/splent_cli/commands/release → splent_cli-1.6.0/src/splent_cli/commands/command}/__init__.py +0 -0
  110. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  111. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_seed.py +0 -0
  112. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_status.py +0 -0
  113. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  114. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/env/env_set.py +0 -0
  115. {splent_cli-1.5.0/src/splent_cli/commands/spl → splent_cli-1.6.0/src/splent_cli/commands/export}/__init__.py +0 -0
  116. {splent_cli-1.5.0/src/splent_cli/commands → splent_cli-1.6.0/src/splent_cli/commands/export}/export_puml.py +0 -0
  117. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  118. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  119. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  120. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  121. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  122. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  123. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  124. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  125. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  126. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  127. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  128. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
  129. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  130. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  131. {splent_cli-1.5.0/src/splent_cli/services → splent_cli-1.6.0/src/splent_cli/commands/product}/__init__.py +0 -0
  132. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_commands.py +0 -0
  133. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_console.py +0 -0
  134. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_down.py +0 -0
  135. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  136. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_list.py +0 -0
  137. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  138. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_port.py +0 -0
  139. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_release.py +0 -0
  140. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  141. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_run.py +0 -0
  142. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  143. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  144. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  145. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/product/product_test.py +0 -0
  146. {splent_cli-1.5.0/src/splent_cli/utils → splent_cli-1.6.0/src/splent_cli/commands/release}/__init__.py +0 -0
  147. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/release/release_core.py +0 -0
  148. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/selenium.py +0 -0
  149. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  150. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  151. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  152. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  153. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  154. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  155. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  156. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  157. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/cache_utils.py +0 -0
  158. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/command_loader.py +0 -0
  159. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  160. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/db_utils.py +0 -0
  161. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/decorators.py +0 -0
  162. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  163. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/feature_installer.py +0 -0
  164. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/feature_utils.py +0 -0
  165. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/git_url.py +0 -0
  166. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/manifest.py +0 -0
  167. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/path_utils.py +0 -0
  168. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli/utils/template_drift.py +0 -0
  169. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  170. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  171. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli.egg-info/requires.txt +0 -0
  172. {splent_cli-1.5.0 → splent_cli-1.6.0}/src/splent_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.5.0
3
+ Version: 1.6.0
4
4
  Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
5
5
  Author-email: DiversoLab <diversolab@us.es>
6
6
  Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
@@ -47,23 +47,10 @@ Command-line tool for managing SPLENT products, features, databases, and environ
47
47
  ## Quick start
48
48
 
49
49
  ```bash
50
- make setup # Prepare .env, start Docker, enter CLI container
51
- splent --help # See all available commands
50
+ make setup # Prepare .env, start Docker, enter CLI container
51
+ splent --help # See all available commands
52
52
  ```
53
53
 
54
- ## Key commands
55
-
56
- | Command | Description |
57
- |---------|-------------|
58
- | `product:create` | Create a new product |
59
- | `product:derive --dev` | Full SPL derivation pipeline |
60
- | `feature:add` / `feature:attach` | Add features to a product |
61
- | `feature:status` | Show feature lifecycle states |
62
- | `feature:release` | Release a feature (tag + PyPI + GitHub) |
63
- | `db:migrate` / `db:upgrade` | Manage per-feature migrations |
64
- | `export:puml` | Generate PlantUML diagrams |
65
- | `doctor` | System health check |
66
-
67
54
  ## Requirements
68
55
 
69
56
  - Docker + Docker Compose
@@ -71,7 +58,7 @@ splent --help # See all available commands
71
58
 
72
59
  ## Documentation
73
60
 
74
- Full documentation at **[docs.splent.io](https://docs.splent.io)**
61
+ Full docs, tutorials, and command reference at **[docs.splent.io](https://docs.splent.io)**
75
62
 
76
63
  ## License
77
64
 
@@ -0,0 +1,23 @@
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
+ ## Requirements
13
+
14
+ - Docker + Docker Compose
15
+ - Python 3.13+
16
+
17
+ ## Documentation
18
+
19
+ Full docs, tutorials, and command reference at **[docs.splent.io](https://docs.splent.io)**
20
+
21
+ ## License
22
+
23
+ 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.5.0"
7
+ version = "1.6.0"
8
8
  description = "SPLENT-CLI is a CLI to be able to work on your development more easily."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -44,9 +44,13 @@ class SPLENTCLI(click.Group):
44
44
  with app.app_context():
45
45
  registry = app.extensions.get("splent_feature_commands", {})
46
46
  for feature_short, commands in registry.items():
47
+ cmd_names = ", ".join(c.name for c in commands if c.name)
47
48
  group = click.Group(
48
49
  name=f"feature:{feature_short}",
49
50
  help=f"Commands contributed by splent_feature_{feature_short}.",
51
+ short_help=f"Subcommands: {cmd_names}"
52
+ if cmd_names
53
+ else f"Commands from splent_feature_{feature_short}.",
50
54
  )
51
55
  group.requires_app = True # type: ignore[attr-defined]
52
56
  for cmd in commands:
@@ -92,12 +96,12 @@ class SPLENTCLI(click.Group):
92
96
  def format_commands(self, ctx, formatter):
93
97
  """Group SPLENT commands by category in the CLI help output."""
94
98
  all_cmds = self.list_commands(ctx)
99
+ feat_cmds = self._load_feature_commands()
95
100
  groups = {
96
101
  "🌿 Feature Management": [
97
102
  cmd
98
103
  for cmd in all_cmds
99
- if cmd.startswith("feature:")
100
- and cmd not in self._load_feature_commands()
104
+ if cmd.startswith("feature:") and cmd not in feat_cmds
101
105
  ],
102
106
  "🏗️ Product Management": [
103
107
  cmd for cmd in all_cmds if cmd.startswith("product:")
@@ -107,27 +111,28 @@ class SPLENTCLI(click.Group):
107
111
  ],
108
112
  "🧱 Database": [cmd for cmd in all_cmds if cmd.startswith("db:")],
109
113
  "💾 Cache": [cmd for cmd in all_cmds if cmd.startswith("cache:")],
114
+ "🔍 Checks": [cmd for cmd in all_cmds if cmd.startswith("check:")],
115
+ "📦 Export": [cmd for cmd in all_cmds if cmd.startswith("export:")],
116
+ "🚀 Release": [cmd for cmd in all_cmds if cmd.startswith("release:")],
110
117
  "🧰 Utilities": [
111
118
  cmd
112
119
  for cmd in all_cmds
113
- if cmd.startswith(
114
- ("clear:", "env", "select", "info", "version", "doctor", "tokens")
115
- )
120
+ if cmd.startswith(("clear:", "command:", "env:", "env"))
121
+ or cmd in ("doctor", "tokens:setup", "version")
116
122
  ],
117
123
  "🐍 Development & QA": [
118
124
  cmd
119
125
  for cmd in all_cmds
120
- if cmd.startswith(("linter", "test", "coverage", "locust"))
121
- ],
122
- "🔌 Feature Commands": [
123
- cmd for cmd in all_cmds if cmd in self._load_feature_commands()
126
+ if cmd in ("lint", "coverage", "selenium", "locust", "locust:stop")
124
127
  ],
128
+ "🔌 Feature Commands": [cmd for cmd in all_cmds if cmd in feat_cmds],
125
129
  }
130
+ total = 0
126
131
  for title, cmds in groups.items():
127
132
  if not cmds:
128
133
  continue
129
134
 
130
- with formatter.section(title):
135
+ with formatter.section(f"{title} ({len(cmds)})"):
131
136
  rows = []
132
137
  for cmd_name in sorted(cmds):
133
138
  cmd = self.get_command(ctx, cmd_name)
@@ -136,6 +141,9 @@ class SPLENTCLI(click.Group):
136
141
  rows.append((cmd_name, cmd.get_short_help_str()))
137
142
  if rows:
138
143
  formatter.write_dl(rows)
144
+ total += len(cmds)
145
+
146
+ formatter.write(f"\n {total} commands available.\n")
139
147
 
140
148
 
141
149
  @click.group(cls=SPLENTCLI)
@@ -4,7 +4,10 @@ import click
4
4
  from pathlib import Path
5
5
 
6
6
 
7
- @click.command("cache:clear", short_help="Clear the feature cache (total or partial).")
7
+ @click.command(
8
+ "cache:clear",
9
+ short_help="Clear the feature version cache (.splent_cache), totally or partially.",
10
+ )
8
11
  @click.option(
9
12
  "--namespace",
10
13
  default=None,
@@ -68,7 +68,7 @@ def _get_product_features(workspace: Path) -> dict:
68
68
 
69
69
  @click.command(
70
70
  "cache:outdated",
71
- short_help="Show products using older versions than what's in cache.",
71
+ short_help="Show products using older feature versions than what is in cache.",
72
72
  )
73
73
  def cache_outdated():
74
74
  """
@@ -18,13 +18,17 @@ def check_github():
18
18
  # --- presence checks ---
19
19
  if not user:
20
20
  click.echo(click.style("[✖] ", fg="red") + "GITHUB_USER not set in .env")
21
- click.secho(" Run 'splent tokens' for setup instructions.", fg="bright_black")
21
+ click.secho(
22
+ " Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
23
+ )
22
24
  raise SystemExit(1)
23
25
  click.echo(click.style("[✔] ", fg="green") + f"GITHUB_USER = {user}")
24
26
 
25
27
  if not token:
26
28
  click.echo(click.style("[✖] ", fg="red") + "GITHUB_TOKEN not set in .env")
27
- click.secho(" Run 'splent tokens' for setup instructions.", fg="bright_black")
29
+ click.secho(
30
+ " Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
31
+ )
28
32
  raise SystemExit(1)
29
33
 
30
34
  masked = token[:4] + "*" * (len(token) - 8) + token[-4:]
@@ -54,7 +58,7 @@ def check_github():
54
58
  + "Token invalid or expired (401 Unauthorized)"
55
59
  )
56
60
  click.secho(
57
- " Run 'splent tokens' for instructions on obtaining a valid token.",
61
+ " Run 'splent tokens:setup' for instructions on obtaining a valid token.",
58
62
  fg="bright_black",
59
63
  )
60
64
  elif e.code == 403:
@@ -63,7 +67,7 @@ def check_github():
63
67
  + "Token lacks required permissions (403 Forbidden)"
64
68
  )
65
69
  click.secho(
66
- " Run 'splent tokens' — ensure the token has 'repo' scope.",
70
+ " Run 'splent tokens:setup' — ensure the token has 'repo' scope.",
67
71
  fg="bright_black",
68
72
  )
69
73
  else:
@@ -220,6 +220,116 @@ def check_infra():
220
220
  else:
221
221
  _ok("No external networks required")
222
222
 
223
+ # --- Check 4: Dockerfile build contexts ---
224
+ click.echo()
225
+ click.echo(click.style(" Build contexts", bold=True))
226
+ build_count = 0
227
+ for label, cf in compose_files:
228
+ docker_dir = os.path.dirname(cf)
229
+ result = subprocess.run(
230
+ ["docker", "compose", "-f", cf, "config", "--format", "json"],
231
+ capture_output=True,
232
+ text=True,
233
+ )
234
+ if result.returncode != 0:
235
+ continue
236
+ try:
237
+ config = json.loads(result.stdout)
238
+ except json.JSONDecodeError:
239
+ continue
240
+ for svc_name, svc_def in config.get("services", {}).items():
241
+ build_cfg = svc_def.get("build")
242
+ if not build_cfg:
243
+ continue
244
+ build_count += 1
245
+ if isinstance(build_cfg, dict):
246
+ ctx = build_cfg.get("context", ".")
247
+ dockerfile = build_cfg.get("dockerfile", "Dockerfile")
248
+ df_path = (
249
+ os.path.join(ctx, dockerfile)
250
+ if os.path.isabs(ctx)
251
+ else os.path.join(docker_dir, ctx, dockerfile)
252
+ )
253
+ else:
254
+ df_path = os.path.join(docker_dir, build_cfg, "Dockerfile")
255
+
256
+ if os.path.isfile(df_path):
257
+ _ok(f"{label}/{svc_name}: Dockerfile found")
258
+ else:
259
+ _fail(f"{label}/{svc_name}: Dockerfile not found at {df_path}")
260
+
261
+ if build_count == 0:
262
+ _ok("No custom builds (all services use pre-built images)")
263
+
264
+ # --- Check 5: Healthcheck coverage ---
265
+ click.echo()
266
+ click.echo(click.style(" Health checks", bold=True))
267
+ services_with_hc: set[str] = set()
268
+ services_depended_on: dict[str, str] = {} # depended_svc -> by_svc
269
+
270
+ for label, cf in compose_files:
271
+ result = subprocess.run(
272
+ ["docker", "compose", "-f", cf, "config", "--format", "json"],
273
+ capture_output=True,
274
+ text=True,
275
+ )
276
+ if result.returncode != 0:
277
+ continue
278
+ try:
279
+ config = json.loads(result.stdout)
280
+ except json.JSONDecodeError:
281
+ continue
282
+ for svc_name, svc_def in config.get("services", {}).items():
283
+ if "healthcheck" in svc_def:
284
+ services_with_hc.add(svc_name)
285
+ for dep_svc, dep_cfg in (svc_def.get("depends_on") or {}).items():
286
+ if (
287
+ isinstance(dep_cfg, dict)
288
+ and dep_cfg.get("condition") == "service_healthy"
289
+ ):
290
+ services_depended_on[dep_svc] = svc_name
291
+
292
+ missing_hc = {
293
+ svc: by
294
+ for svc, by in services_depended_on.items()
295
+ if svc not in services_with_hc
296
+ }
297
+ if missing_hc:
298
+ for svc, by in sorted(missing_hc.items()):
299
+ _fail(
300
+ f"'{by}' depends on '{svc}' being healthy, but '{svc}' has no healthcheck"
301
+ )
302
+ elif services_with_hc:
303
+ _ok(f"{len(services_with_hc)} service(s) with health checks")
304
+ else:
305
+ _ok("No health checks declared (none required)")
306
+
307
+ # --- Check 6: Volume name conflicts ---
308
+ click.echo()
309
+ click.echo(click.style(" Volumes", bold=True))
310
+ all_volumes: dict[str, list[str]] = {} # vol_name -> [labels]
311
+ for label, cf in compose_files:
312
+ result = subprocess.run(
313
+ ["docker", "compose", "-f", cf, "config", "--format", "json"],
314
+ capture_output=True,
315
+ text=True,
316
+ )
317
+ if result.returncode != 0:
318
+ continue
319
+ try:
320
+ config = json.loads(result.stdout)
321
+ except json.JSONDecodeError:
322
+ continue
323
+ for vol_name in config.get("volumes", {}):
324
+ all_volumes.setdefault(vol_name, []).append(label)
325
+
326
+ vol_conflicts = {v: srcs for v, srcs in all_volumes.items() if len(srcs) > 1}
327
+ if vol_conflicts:
328
+ for vol, sources in sorted(vol_conflicts.items()):
329
+ _warn(f"Volume '{vol}' declared by multiple features: {', '.join(sources)}")
330
+ else:
331
+ _ok(f"No volume name conflicts ({len(all_volumes)} volumes)")
332
+
223
333
  # --- Summary ---
224
334
  click.echo()
225
335
  if fail:
@@ -291,7 +291,7 @@ def _check_blueprints(counters):
291
291
 
292
292
  @click.command(
293
293
  "check:product",
294
- short_help="Validate product health: env vars, symlinks, config.",
294
+ short_help="Quick health check: env vars, symlinks, and config files.",
295
295
  )
296
296
  @context.requires_product
297
297
  def check_product():
@@ -26,7 +26,9 @@ def check_pypi(test: bool):
26
26
  # --- presence checks ---
27
27
  if not username:
28
28
  click.echo(click.style("[✖] ", fg="red") + "TWINE_USERNAME not set in .env")
29
- click.secho(" Run 'splent tokens' for setup instructions.", fg="bright_black")
29
+ click.secho(
30
+ " Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
31
+ )
30
32
  raise SystemExit(1)
31
33
  click.echo(click.style("[✔] ", fg="green") + f"TWINE_USERNAME = {username}")
32
34
 
@@ -35,7 +37,9 @@ def check_pypi(test: bool):
35
37
  click.style("[✖] ", fg="red")
36
38
  + "TWINE_PASSWORD (or PYPI_PASSWORD) not set in .env"
37
39
  )
38
- click.secho(" Run 'splent tokens' for setup instructions.", fg="bright_black")
40
+ click.secho(
41
+ " Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
42
+ )
39
43
  raise SystemExit(1)
40
44
 
41
45
  masked = (
@@ -94,7 +98,7 @@ def check_pypi(test: bool):
94
98
  + f"Credentials rejected by {registry} (HTTP {e.code}) — token invalid or expired"
95
99
  )
96
100
  click.secho(
97
- " Run 'splent tokens' for instructions on obtaining a valid token.",
101
+ " Run 'splent tokens:setup' for instructions on obtaining a valid token.",
98
102
  fg="bright_black",
99
103
  )
100
104
  raise SystemExit(1)
@@ -52,8 +52,8 @@ def clean_build_artifacts(target_path: str | Path, *, quiet: bool = False):
52
52
 
53
53
 
54
54
  @click.command(
55
- "clear:pycache",
56
- short_help="Clear __pycache__, .pytest_cache and build artifacts from the workspace.",
55
+ "clear:build",
56
+ short_help="Remove __pycache__, .pytest_cache, dist/, build/, and egg-info.",
57
57
  )
58
58
  def clear_cache():
59
59
  if not click.confirm("Are you sure you want to clear caches and build artifacts?"):
@@ -5,7 +5,7 @@ from splent_cli.utils.path_utils import PathUtils
5
5
  from splent_cli.services import context
6
6
 
7
7
 
8
- @click.command("clear:log", short_help="Clears the 'app.log' file.")
8
+ @click.command("clear:log", short_help="Clear the app.log file.")
9
9
  @context.requires_product
10
10
  def clear_log():
11
11
  log_file_path = PathUtils.get_app_log_dir()
@@ -8,7 +8,7 @@ from splent_cli.services import context
8
8
 
9
9
  @click.command(
10
10
  "clear:uploads",
11
- short_help="Clears the contents of the 'uploads' directory without removing the folder.",
11
+ short_help="Clear the contents of the uploads directory.",
12
12
  )
13
13
  @context.requires_product
14
14
  def clear_uploads():
@@ -5,7 +5,7 @@ import click
5
5
  from splent_cli.utils.path_utils import PathUtils
6
6
 
7
7
 
8
- @click.command("command:create", help="Creates a new CLI command skeleton.")
8
+ @click.command("command:create", short_help="Create a new CLI command skeleton.")
9
9
  @click.argument("name")
10
10
  def command_create(name):
11
11
  commands_dir = PathUtils.get_commands_path()
@@ -8,7 +8,7 @@ from splent_cli.services import context
8
8
 
9
9
  @click.command(
10
10
  "coverage",
11
- short_help="Runs pytest coverage on the selected feature",
11
+ short_help="Run pytest with coverage reporting for a feature.",
12
12
  )
13
13
  @click.argument("module_name", required=False)
14
14
  @click.option("--html", is_flag=True, help="Generates an HTML coverage report.")
@@ -7,7 +7,7 @@ from splent_cli.services import context
7
7
 
8
8
 
9
9
  @click.command(
10
- "db:console", short_help="Opens a MariaDB console with credentials from .env."
10
+ "db:console", short_help="Open a MariaDB console with credentials from .env."
11
11
  )
12
12
  @context.requires_product
13
13
  def db_console():
@@ -9,7 +9,7 @@ from splent_cli.services import context
9
9
 
10
10
  @click.command(
11
11
  "db:dump",
12
- short_help="Creates a dump of the MariaDB database with credentials from .env.",
12
+ short_help="Create a SQL dump of the MariaDB database.",
13
13
  )
14
14
  @click.argument("filename", required=False)
15
15
  @context.requires_product
@@ -1,13 +1,11 @@
1
1
  import os
2
2
 
3
3
  import click
4
- from flask import current_app
5
- from flask_migrate import migrate as alembic_migrate, upgrade as alembic_upgrade
4
+ from flask_migrate import migrate as alembic_migrate
6
5
 
7
6
  from splent_cli.utils.decorators import requires_db
8
7
  from splent_cli.services import context
9
8
  from splent_cli.utils.lifecycle import (
10
- advance_state,
11
9
  resolve_feature_key_from_entry,
12
10
  require_editable,
13
11
  )
@@ -50,19 +48,20 @@ def _is_empty_migration(path: str) -> bool:
50
48
  @requires_db
51
49
  @click.command(
52
50
  "db:migrate",
53
- short_help="Generate and apply migrations (all features or a single one).",
51
+ short_help="Generate a new migration from model changes and apply it.",
54
52
  )
55
53
  @click.argument("feature", required=False, default=None)
54
+ @click.option(
55
+ "-m", "message", default=None, help="Migration message (defaults to feature name)."
56
+ )
56
57
  @context.requires_product
57
- def db_migrate(feature):
58
+ def db_migrate(feature, message):
58
59
  """
59
60
  Generate new migration files for features that have schema changes,
60
61
  then apply all pending migrations.
61
62
 
62
63
  Empty migrations (no schema changes detected) are automatically removed.
63
64
  """
64
- app = current_app
65
-
66
65
  if feature:
67
66
  dirs = {}
68
67
  mdir = MigrationManager.get_feature_migration_dir(feature)
@@ -84,7 +83,6 @@ def db_migrate(feature):
84
83
 
85
84
  # Build entry→key lookup for manifest updates
86
85
  product_path = PathUtils.get_app_base_dir()
87
- product_name = os.getenv("SPLENT_APP", "")
88
86
  entry_lookup = {}
89
87
  for entry in get_features_from_pyproject() or []:
90
88
  key, ns, name, version = resolve_feature_key_from_entry(entry)
@@ -110,7 +108,7 @@ def db_migrate(feature):
110
108
  alembic_logger.setLevel(logging.WARNING)
111
109
 
112
110
  try:
113
- alembic_migrate(directory=mdir, message=feat)
111
+ alembic_migrate(directory=mdir, message=message or feat)
114
112
  except Exception as e:
115
113
  if os.getenv("SPLENT_DEBUG"):
116
114
  click.secho(
@@ -142,30 +140,5 @@ def db_migrate(feature):
142
140
  else:
143
141
  click.echo(click.style(f" ✔ {feat}: up to date", fg="green"))
144
142
 
145
- # Apply pending migrations
146
- try:
147
- alembic_upgrade(directory=mdir)
148
- revision = MigrationManager.get_current_feature_revision(
149
- feat, app.extensions["migrate"].db.engine
150
- )
151
- MigrationManager.update_feature_status(app, feat, revision)
152
- click.echo(click.style(f" ✅ {feat} → {revision or 'head'}", fg="green"))
153
-
154
- # Advance lifecycle state to "migrated"
155
- info = entry_lookup.get(feat)
156
- if info:
157
- key, ns, name, version = info
158
- advance_state(
159
- product_path,
160
- product_name,
161
- key,
162
- to="migrated",
163
- namespace=ns,
164
- name=name,
165
- version=version,
166
- )
167
- except Exception as e:
168
- click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
169
-
170
143
 
171
144
  cli_command = db_migrate
@@ -15,7 +15,7 @@ from splent_framework.managers.migration_manager import (
15
15
  )
16
16
  from splent_framework.utils.feature_utils import get_features_from_pyproject
17
17
  from splent_framework.utils.path_utils import PathUtils
18
- from splent_cli.commands.clear_uploads import clear_uploads
18
+ from splent_cli.commands.clear.clear_uploads import clear_uploads
19
19
 
20
20
 
21
21
  @requires_db
@@ -206,7 +206,28 @@ def db_rollback(feature, steps, cascade):
206
206
  )
207
207
  break
208
208
  except Exception as e:
209
- click.secho(f" {feature}: {e}", fg="red")
209
+ click.secho(f" {feature}: {e}", fg="red")
210
+ raise SystemExit(1)
211
+
212
+ # Offer to delete migration files if fully rolled back to base
213
+ if revision is None:
214
+ versions_dir = os.path.join(mdir, "versions")
215
+ if os.path.isdir(versions_dir):
216
+ migration_files = [
217
+ f
218
+ for f in os.listdir(versions_dir)
219
+ if f.endswith(".py") and not f.startswith("__")
220
+ ]
221
+ if migration_files and click.confirm(
222
+ f" Delete {len(migration_files)} migration file(s)?",
223
+ default=False,
224
+ ):
225
+ for f in migration_files:
226
+ os.remove(os.path.join(versions_dir, f))
227
+ click.echo(
228
+ click.style(" deleted: ", dim=True)
229
+ + f"{len(migration_files)} file(s)"
230
+ )
210
231
 
211
232
 
212
233
  cli_command = db_rollback