splent-cli 1.4.5__tar.gz → 1.5.1__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 (163) hide show
  1. {splent_cli-1.4.5/src/splent_cli.egg-info → splent_cli-1.5.1}/PKG-INFO +3 -2
  2. {splent_cli-1.4.5 → splent_cli-1.5.1}/pyproject.toml +4 -2
  3. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/database/db_migrate.py +6 -33
  4. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/database/db_rollback.py +22 -1
  5. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/database/db_upgrade.py +3 -1
  6. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_add.py +29 -29
  7. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_attach.py +22 -15
  8. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_contract.py +4 -0
  9. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_create.py +4 -0
  10. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_detach.py +19 -22
  11. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_edit.py +59 -118
  12. splent_cli-1.5.1/src/splent_cli/commands/feature/feature_refinement.py +879 -0
  13. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_release.py +39 -18
  14. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_remove.py +18 -32
  15. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_xray.py +206 -181
  16. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature_compile.py +1 -1
  17. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_build.py +25 -9
  18. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_configure.py +6 -0
  19. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_create.py +64 -13
  20. splent_cli-1.5.1/src/splent_cli/commands/product/product_deploy.py +274 -0
  21. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_derive.py +7 -6
  22. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_deselect.py +9 -4
  23. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_env.py +32 -49
  24. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_port.py +10 -8
  25. splent_cli-1.5.1/src/splent_cli/commands/product/product_restart.py +114 -0
  26. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_run.py +2 -3
  27. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_up.py +22 -12
  28. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_validate.py +11 -5
  29. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/spl_configs.py +5 -2
  30. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/tokens.py +4 -1
  31. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/uvl/uvl_utils.py +17 -2
  32. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/services/compose.py +5 -3
  33. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/services/context.py +3 -3
  34. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/feature_utils.py +89 -0
  35. {splent_cli-1.4.5 → splent_cli-1.5.1/src/splent_cli.egg-info}/PKG-INFO +3 -2
  36. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli.egg-info/SOURCES.txt +1 -0
  37. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli.egg-info/requires.txt +3 -1
  38. splent_cli-1.4.5/src/splent_cli/commands/product/product_deploy.py +0 -173
  39. splent_cli-1.4.5/src/splent_cli/commands/product/product_restart.py +0 -64
  40. {splent_cli-1.4.5 → splent_cli-1.5.1}/LICENSE +0 -0
  41. {splent_cli-1.4.5 → splent_cli-1.5.1}/README.md +0 -0
  42. {splent_cli-1.4.5 → splent_cli-1.5.1}/setup.cfg +0 -0
  43. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/__init__.py +0 -0
  44. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/__main__.py +0 -0
  45. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/cli.py +0 -0
  46. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/__init__.py +0 -0
  47. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/cache/__init__.py +0 -0
  48. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  49. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  50. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  51. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  52. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/cache/cache_size.py +0 -0
  53. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/cache/cache_status.py +0 -0
  54. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  55. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  56. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/check/__init__.py +0 -0
  57. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/check/check_deps.py +0 -0
  58. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/check/check_docker.py +0 -0
  59. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/check/check_env.py +0 -0
  60. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/check/check_features.py +0 -0
  61. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/check/check_github.py +0 -0
  62. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/check/check_infra.py +0 -0
  63. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/check/check_product.py +0 -0
  64. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/check/check_pypi.py +0 -0
  65. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  66. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/clear_cache.py +0 -0
  67. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/clear_log.py +0 -0
  68. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/clear_uploads.py +0 -0
  69. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/command_create.py +0 -0
  70. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/coverage.py +0 -0
  71. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/database/db_console.py +0 -0
  72. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/database/db_dump.py +0 -0
  73. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/database/db_reset.py +0 -0
  74. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/database/db_restore.py +0 -0
  75. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/database/db_seed.py +0 -0
  76. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/database/db_status.py +0 -0
  77. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/doctor.py +0 -0
  78. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/env/env_list.py +0 -0
  79. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/env/env_set.py +0 -0
  80. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/env/env_show.py +0 -0
  81. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/export_puml.py +0 -0
  82. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  83. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  84. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_diff.py +0 -0
  85. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  86. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  87. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_env.py +0 -0
  88. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  89. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_git.py +0 -0
  90. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  91. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  92. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  93. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  94. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_list.py +0 -0
  95. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_order.py +0 -0
  96. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  97. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  98. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  99. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  100. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  101. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_search.py +0 -0
  102. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_status.py +0 -0
  103. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  104. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_test.py +0 -0
  105. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  106. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  107. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  108. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/linter.py +0 -0
  109. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/locust.py +0 -0
  110. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/__init__.py +0 -0
  111. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_clean.py +0 -0
  112. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_commands.py +0 -0
  113. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_complete.py +0 -0
  114. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_config.py +0 -0
  115. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_console.py +0 -0
  116. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_down.py +0 -0
  117. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_drift.py +0 -0
  118. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_list.py +0 -0
  119. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_logs.py +0 -0
  120. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_missing.py +0 -0
  121. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_release.py +0 -0
  122. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_routes.py +0 -0
  123. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_select.py +0 -0
  124. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_shell.py +0 -0
  125. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_signals.py +0 -0
  126. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_status.py +0 -0
  127. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_sync.py +0 -0
  128. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  129. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/product/product_test.py +0 -0
  130. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/release/__init__.py +0 -0
  131. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/release/release_core.py +0 -0
  132. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/selenium.py +0 -0
  133. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/__init__.py +0 -0
  134. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  135. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/spl_create.py +0 -0
  136. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  137. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/spl_features.py +0 -0
  138. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  139. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/spl_fix.py +0 -0
  140. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/spl_info.py +0 -0
  141. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/spl_list.py +0 -0
  142. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  143. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/commands/version.py +0 -0
  144. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/services/__init__.py +0 -0
  145. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/services/preflight.py +0 -0
  146. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/services/release.py +0 -0
  147. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/__init__.py +0 -0
  148. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/cache_utils.py +0 -0
  149. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/command_loader.py +0 -0
  150. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/contract_freshness.py +0 -0
  151. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/db_utils.py +0 -0
  152. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/decorators.py +0 -0
  153. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/dynamic_imports.py +0 -0
  154. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/feature_installer.py +0 -0
  155. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/git_url.py +0 -0
  156. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/integrity.py +0 -0
  157. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/lifecycle.py +0 -0
  158. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/manifest.py +0 -0
  159. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/path_utils.py +0 -0
  160. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli/utils/template_drift.py +0 -0
  161. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  162. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli.egg-info/entry_points.txt +0 -0
  163. {splent_cli-1.4.5 → splent_cli-1.5.1}/src/splent_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.4.5
3
+ Version: 1.5.1
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
@@ -21,7 +21,6 @@ Requires-Dist: black==24.10.0; extra == "dev"
21
21
  Requires-Dist: coverage==7.6.10; extra == "dev"
22
22
  Requires-Dist: docker==7.1.0; extra == "dev"
23
23
  Requires-Dist: Faker==33.3.1; extra == "dev"
24
- Requires-Dist: flamapy==2.5.0; extra == "dev"
25
24
  Requires-Dist: flake8==7.1.1; extra == "dev"
26
25
  Requires-Dist: graphviz==0.20.3; extra == "dev"
27
26
  Requires-Dist: iniconfig==2.0.0; extra == "dev"
@@ -37,6 +36,8 @@ Requires-Dist: selenium-wire==5.1.0; extra == "dev"
37
36
  Requires-Dist: pip-tools==7.4.1; extra == "dev"
38
37
  Requires-Dist: tomli_w==1.2.0; extra == "dev"
39
38
  Requires-Dist: PyYAML==6.0.3; extra == "dev"
39
+ Provides-Extra: uvl
40
+ Requires-Dist: flamapy==2.5.0; extra == "uvl"
40
41
  Dynamic: license-file
41
42
 
42
43
  # SPLENT CLI
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splent_cli"
7
- version = "1.4.5"
7
+ version = "1.5.1"
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"
@@ -28,7 +28,6 @@ dev = [
28
28
  "coverage==7.6.10",
29
29
  "docker==7.1.0",
30
30
  "Faker==33.3.1",
31
- "flamapy==2.5.0",
32
31
  "flake8==7.1.1",
33
32
  "graphviz==0.20.3",
34
33
  "iniconfig==2.0.0",
@@ -45,6 +44,9 @@ dev = [
45
44
  "tomli_w==1.2.0",
46
45
  "PyYAML==6.0.3"
47
46
  ]
47
+ uvl = [
48
+ "flamapy==2.5.0",
49
+ ]
48
50
 
49
51
  [project.scripts]
50
52
  splent = "splent_cli.__main__:main"
@@ -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
  )
@@ -53,16 +51,17 @@ def _is_empty_migration(path: str) -> bool:
53
51
  short_help="Generate and apply migrations (all features or a single one).",
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
@@ -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
@@ -59,15 +59,17 @@ def db_upgrade(feature):
59
59
  import logging
60
60
 
61
61
  logging.getLogger("alembic").setLevel(logging.WARNING)
62
+ logging.getLogger("alembic.runtime.migration").setLevel(logging.WARNING)
62
63
 
63
64
  for feat, mdir in dirs.items():
64
65
  try:
66
+ logging.getLogger("alembic.runtime.migration").setLevel(logging.WARNING)
65
67
  alembic_upgrade(directory=mdir)
66
68
  revision = MigrationManager.get_current_feature_revision(
67
69
  feat, app.extensions["migrate"].db.engine
68
70
  )
69
71
  MigrationManager.update_feature_status(app, feat, revision)
70
- click.echo(click.style(f"{feat} {revision or 'head'}", fg="green"))
72
+ click.echo(click.style(f" {feat} -> {revision or 'head'}", fg="green"))
71
73
 
72
74
  # Advance lifecycle state to "migrated"
73
75
  info = entry_lookup.get(feat)
@@ -3,7 +3,7 @@ import tomllib
3
3
  import tomli_w
4
4
  import click
5
5
  from splent_cli.services import context
6
- from splent_cli.utils.feature_utils import normalize_namespace
6
+ from splent_cli.utils.feature_utils import normalize_namespace, hot_reinstall
7
7
  from splent_cli.utils.manifest import feature_key, set_feature_state
8
8
 
9
9
 
@@ -39,11 +39,8 @@ def feature_add(full_name, env_scope):
39
39
  splent feature:add splent-io/splent_feature_admin --dev
40
40
  """
41
41
 
42
- # --------------------------
43
- # 0️⃣ Validate format
44
- # --------------------------
45
42
  if "/" not in full_name:
46
- click.echo("Invalid format. Use: <namespace>/<feature_name>")
43
+ click.secho(" Invalid format. Use: <namespace>/<feature_name>", fg="red")
47
44
  raise SystemExit(1)
48
45
 
49
46
  namespace, feature_name = full_name.split("/", 1)
@@ -55,16 +52,17 @@ def feature_add(full_name, env_scope):
55
52
  # Editable features live at workspace root
56
53
  feature_dir = os.path.join(workspace, feature_name)
57
54
  if not os.path.exists(feature_dir):
58
- click.echo(f" Feature not found at workspace root: {feature_dir}")
59
- click.echo(f" Create it first with: splent feature:create {full_name}")
55
+ click.secho(f" {feature_name} not found at workspace root.", fg="red")
56
+ click.echo(
57
+ click.style(" create it first: ", dim=True)
58
+ + f"splent feature:create {full_name}"
59
+ )
60
60
  raise SystemExit(1)
61
61
 
62
- # --------------------------
63
- # 1️⃣ Update pyproject.toml
64
- # --------------------------
62
+ # ── Update pyproject.toml ─────────────────────────────────────────
65
63
  pyproject_path = os.path.join(workspace, product, "pyproject.toml")
66
64
  if not os.path.exists(pyproject_path):
67
- click.echo("pyproject.toml not found in product directory.")
65
+ click.secho(" pyproject.toml not found.", fg="red")
68
66
  raise SystemExit(1)
69
67
 
70
68
  with open(pyproject_path, "rb") as f:
@@ -82,19 +80,21 @@ def feature_add(full_name, env_scope):
82
80
  else (data.get("tool", {}).get("splent", {}).get(features_key, []))
83
81
  )
84
82
 
85
- if full_name not in features:
86
- features.append(full_name)
87
- write_features_to_data(data, features, key=features_key)
88
- with open(pyproject_path, "wb") as f:
89
- tomli_w.dump(data, f)
90
- scope_label = f" ({env_scope} only)" if env_scope else ""
91
- click.echo(f"🧩 Added '{full_name}' to {features_key}{scope_label}.")
92
- else:
93
- click.echo(f"ℹ️ Feature '{full_name}' already present in {features_key}.")
94
-
95
- # --------------------------
96
- # 2️⃣ Create symlink
97
- # --------------------------
83
+ short = feature_name.replace("splent_feature_", "")
84
+
85
+ if full_name in features:
86
+ click.echo(f" {short} already in {features_key}.")
87
+ return
88
+
89
+ features.append(full_name)
90
+ write_features_to_data(data, features, key=features_key)
91
+ with open(pyproject_path, "wb") as f:
92
+ tomli_w.dump(data, f)
93
+
94
+ scope_label = f" ({env_scope} only)" if env_scope else ""
95
+ click.echo(f" {short} added to {features_key}{scope_label}")
96
+
97
+ # ── Create symlink ────────────────────────────────────────────────
98
98
  product_features_dir = os.path.join(workspace, product, "features", org_safe)
99
99
  os.makedirs(product_features_dir, exist_ok=True)
100
100
 
@@ -105,11 +105,8 @@ def feature_add(full_name, env_scope):
105
105
  except FileExistsError:
106
106
  os.unlink(link_path)
107
107
  os.symlink(rel_target, link_path)
108
- click.echo(f"🔗 Linked {link_path} → {rel_target}")
109
108
 
110
- # --------------------------
111
- # 3️⃣ Update manifest
112
- # --------------------------
109
+ # ── Update manifest ───────────────────────────────────────────────
113
110
  product_path = os.path.join(workspace, product)
114
111
  key = feature_key(namespace, feature_name)
115
112
  set_feature_state(
@@ -123,4 +120,7 @@ def feature_add(full_name, env_scope):
123
120
  mode="editable",
124
121
  )
125
122
 
126
- click.echo(f"✅ Feature '{full_name}' added successfully to product '{product}'.")
123
+ # ── Hot reinstall in web container ────────────────────────────────
124
+ hot_reinstall(product_path, f"/workspace/{feature_name}", feature_name)
125
+
126
+ click.secho(" done.", fg="green")
@@ -3,6 +3,7 @@ import tomllib
3
3
  import tomli_w
4
4
  import click
5
5
  from splent_cli.services import context, compose
6
+ from splent_cli.utils.feature_utils import hot_reinstall
6
7
  from splent_cli.utils.manifest import feature_key, set_feature_state
7
8
 
8
9
 
@@ -32,7 +33,7 @@ def feature_attach(feature_identifier, version, env_scope):
32
33
  If not, run: splent feature:clone <namespace>/<feature>@<version>
33
34
  - Updates pyproject.toml referencing feature@version.
34
35
  - Creates/updates the versioned symlink in features/<namespace>/.
35
- - Updates the manifest state to 'declared'.
36
+ - Reinstalls the feature in the web container for hot reload.
36
37
  """
37
38
  product = context.require_app()
38
39
  ws = context.workspace()
@@ -47,22 +48,23 @@ def feature_attach(feature_identifier, version, env_scope):
47
48
  pyproject_path = os.path.join(product_path, "pyproject.toml")
48
49
 
49
50
  if not os.path.exists(pyproject_path):
50
- click.echo("pyproject.toml not found in product.")
51
+ click.secho(" pyproject.toml not found.", fg="red")
51
52
  raise SystemExit(1)
52
53
 
53
- # --- 1️⃣ Verify feature exists in cache ---------------------------------
54
+ # ── Verify feature exists in cache ────────────────────────────────
54
55
  versioned_dir = os.path.join(cache_base, f"{feature_name}@{version}")
55
56
 
56
57
  if not os.path.exists(versioned_dir):
58
+ click.secho(f" {feature_name}@{version} not found in cache.", fg="red")
57
59
  click.echo(
58
- f" Feature '{namespace}/{feature_name}@{version}' not found in cache.\n"
59
- f" Run first: splent feature:clone {namespace}/{feature_name}@{version}"
60
+ click.style(" clone it first: ", dim=True)
61
+ + f"splent feature:clone {namespace}/{feature_name}@{version}"
60
62
  )
61
63
  raise SystemExit(1)
62
64
 
63
- click.echo(f" Cache found → {versioned_dir}")
65
+ short = feature_name.replace("splent_feature_", "")
64
66
 
65
- # --- 2️⃣ Update pyproject.toml ------------------------------------------
67
+ # ── Update pyproject.toml ─────────────────────────────────────────
66
68
  full_name = f"{namespace}/{feature_name}@{version}"
67
69
  bare_name = f"{namespace}/{feature_name}"
68
70
 
@@ -82,9 +84,9 @@ def feature_attach(feature_identifier, version, env_scope):
82
84
  )
83
85
 
84
86
  if full_name in features:
85
- click.echo(f"ℹ️ Feature '{full_name}' already present in {features_key}.")
87
+ click.echo(f" {short}@{version} already in {features_key}.")
86
88
  else:
87
- # Replace bare entry (added by uvl:sync) or old versioned entry if present
89
+ # Replace bare entry or old versioned entry if present
88
90
  features = [
89
91
  f for f in features if f != bare_name and not f.startswith(f"{bare_name}@")
90
92
  ]
@@ -93,9 +95,9 @@ def feature_attach(feature_identifier, version, env_scope):
93
95
  with open(pyproject_path, "wb") as f:
94
96
  tomli_w.dump(data, f)
95
97
  scope_label = f" ({env_scope} only)" if env_scope else ""
96
- click.echo(f"🧩 Updated {features_key}{full_name}{scope_label}")
98
+ click.echo(f" {short}@{version} attached{scope_label}")
97
99
 
98
- # --- 3️⃣ Create/update symlink ------------------------------------------
100
+ # ── Create/update symlink ─────────────────────────────────────────
99
101
  product_features_dir = os.path.join(product_path, "features", namespace_fs)
100
102
  os.makedirs(product_features_dir, exist_ok=True)
101
103
 
@@ -105,9 +107,7 @@ def feature_attach(feature_identifier, version, env_scope):
105
107
  rel_target = os.path.relpath(versioned_dir, product_features_dir)
106
108
  os.symlink(rel_target, new_link)
107
109
 
108
- click.echo(f"🔗 Linked {new_link} {rel_target}")
109
-
110
- # --- 4️⃣ Update manifest ------------------------------------------------
110
+ # ── Update manifest ───────────────────────────────────────────────
111
111
  key = feature_key(namespace_fs, feature_name, version)
112
112
  set_feature_state(
113
113
  product_path,
@@ -120,4 +120,11 @@ def feature_attach(feature_identifier, version, env_scope):
120
120
  mode="pinned",
121
121
  )
122
122
 
123
- click.echo("🎯 Feature successfully attached.")
123
+ # ── Hot reinstall in web container ────────────────────────────────
124
+ # Symlink resolves to cache path — install from there
125
+ install_path = (
126
+ f"/workspace/{product}/features/{namespace_fs}/{feature_name}@{version}"
127
+ )
128
+ hot_reinstall(product_path, install_path, feature_name)
129
+
130
+ click.secho(" done.", fg="green")
@@ -175,6 +175,10 @@ def _print_diff(current: dict, inferred: dict) -> bool:
175
175
  ("signals", "provides.signals"),
176
176
  ("translations", "provides.translations"),
177
177
  ("requires_signals", "requires.signals"),
178
+ ("extensible_services", "extensible.services"),
179
+ ("extensible_templates", "extensible.templates"),
180
+ ("extensible_models", "extensible.models"),
181
+ ("extensible_hooks", "extensible.hooks"),
178
182
  ]
179
183
 
180
184
  diff_lines = []
@@ -88,9 +88,13 @@ def make_feature(full_name):
88
88
  if short_name.startswith("splent_feature_"):
89
89
  short_name = short_name[len("splent_feature_") :]
90
90
 
91
+ # PascalCase for class names: notes_tags → NotesTags
92
+ pascal_name = "".join(w.capitalize() for w in short_name.split("_"))
93
+
91
94
  template_ctx = {
92
95
  "feature_name": feature_name,
93
96
  "short_name": short_name,
97
+ "pascal_name": pascal_name,
94
98
  "org_safe": org_safe,
95
99
  "feature_import": f"{org_safe}.{feature_name}",
96
100
  "cli_version": _CLI_VERSION,
@@ -2,6 +2,7 @@ import os
2
2
  import re
3
3
  import click
4
4
  from splent_cli.services import context, compose
5
+ from splent_cli.utils.feature_utils import hot_uninstall
5
6
  from splent_cli.utils.manifest import (
6
7
  feature_key,
7
8
  remove_feature,
@@ -39,39 +40,37 @@ def feature_detach(feature_identifier, version, force):
39
40
 
40
41
  product_path = str(ws / product)
41
42
  pyproject_path = os.path.join(product_path, "pyproject.toml")
43
+ short = feature_name.replace("splent_feature_", "")
42
44
 
43
45
  if not os.path.exists(pyproject_path):
44
- click.echo("pyproject.toml not found in product.")
46
+ click.secho(" pyproject.toml not found.", fg="red")
45
47
  raise SystemExit(1)
46
48
 
47
49
  if not force:
48
- # --- Guard: dependency check ----------------------------------------
50
+ # Guard: dependency check
49
51
  dependents = get_dependents(product_path, feature_name)
50
52
  if dependents:
51
53
  click.secho(
52
- f"Cannot detach '{feature_name}': the following installed features depend on it:\n"
53
- + "".join(f" {d}\n" for d in dependents)
54
- + " Remove those features first, or use --force to bypass.",
54
+ f" Cannot detach '{short}': the following features depend on it:\n"
55
+ + "".join(f" - {d}\n" for d in dependents)
56
+ + " Remove those first, or use --force.",
55
57
  fg="red",
56
58
  )
57
59
  raise SystemExit(1)
58
60
 
59
- # --- Guard: migration state -----------------------------------------
61
+ # Guard: migration state
60
62
  key = feature_key(namespace_fs, feature_name, version)
61
63
  state = get_feature_state(product_path, key)
62
64
  if state in ("migrated", "active"):
63
65
  click.secho(
64
- f"❌ Feature '{feature_name}' has migrations applied (state: {state}).\n"
65
- f" Roll them back first:\n"
66
- f" splent db:rollback {feature_name} --steps 999\n"
67
- f" Or use --force to skip this check.",
66
+ f" {short} has migrations applied (state: {state}).\n"
67
+ f" Roll them back first: splent db:rollback {feature_name} --steps 999\n"
68
+ f" Or use --force.",
68
69
  fg="red",
69
70
  )
70
71
  raise SystemExit(1)
71
72
 
72
- # --- 1️⃣ Remove versioned reference from pyproject ----------------------
73
- click.echo(f"🧹 Removing {feature_name}@{version} from pyproject.toml...")
74
-
73
+ # ── Remove versioned reference from pyproject ─────────────────────
75
74
  with open(pyproject_path, "r", encoding="utf-8") as f:
76
75
  content = f.read()
77
76
 
@@ -81,22 +80,20 @@ def feature_detach(feature_identifier, version, force):
81
80
  with open(pyproject_path, "w", encoding="utf-8") as f:
82
81
  f.write(new_content)
83
82
 
84
- click.echo("🧩 pyproject.toml cleaned.")
83
+ click.echo(f" {short}@{version} removed from pyproject.toml")
85
84
 
86
- # --- 2️⃣ Remove symlink --------------------------------------------------
85
+ # ── Remove symlink ────────────────────────────────────────────────
87
86
  product_features_dir = os.path.join(product_path, "features", namespace_fs)
88
87
  link_path = os.path.join(product_features_dir, f"{feature_name}@{version}")
89
88
 
90
89
  if os.path.islink(link_path):
91
90
  os.unlink(link_path)
92
- click.echo(f"🔗 Removed symlink: {link_path}")
93
- else:
94
- click.echo(
95
- f"⚠️ No symlink found for {feature_name}@{version} in {namespace_fs}/"
96
- )
97
91
 
98
- # --- 3️⃣ Update manifest ------------------------------------------------
92
+ # ── Update manifest ───────────────────────────────────────────────
99
93
  key = feature_key(namespace_fs, feature_name, version)
100
94
  remove_feature(str(ws / product), product, key)
101
95
 
102
- click.echo("🎯 Feature successfully detached.")
96
+ # ── Hot uninstall from web container ──────────────────────────────
97
+ hot_uninstall(product_path, feature_name)
98
+
99
+ click.secho(" done.", fg="green")