splent-cli 1.11.0__tar.gz → 1.12.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 (171) hide show
  1. {splent_cli-1.11.0/src/splent_cli.egg-info → splent_cli-1.12.0}/PKG-INFO +1 -1
  2. {splent_cli-1.11.0 → splent_cli-1.12.0}/pyproject.toml +1 -1
  3. splent_cli-1.12.0/src/splent_cli/commands/database/db_reset.py +253 -0
  4. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_unlock.py +10 -2
  5. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_add_feature.py +33 -36
  6. splent_cli-1.12.0/src/splent_cli/commands/spl/spl_compare.py +88 -0
  7. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_create.py +4 -1
  8. {splent_cli-1.11.0 → splent_cli-1.12.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
  9. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/SOURCES.txt +1 -0
  10. splent_cli-1.11.0/src/splent_cli/commands/database/db_reset.py +0 -159
  11. {splent_cli-1.11.0 → splent_cli-1.12.0}/LICENSE +0 -0
  12. {splent_cli-1.11.0 → splent_cli-1.12.0}/README.md +0 -0
  13. {splent_cli-1.11.0 → splent_cli-1.12.0}/setup.cfg +0 -0
  14. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/__init__.py +0 -0
  15. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/__main__.py +0 -0
  16. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/cli.py +0 -0
  17. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/__init__.py +0 -0
  18. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  19. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  20. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  21. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  22. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  23. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  24. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  25. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  26. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  27. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/__init__.py +0 -0
  28. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  29. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  30. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_env.py +0 -0
  31. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_features.py +0 -0
  32. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_github.py +0 -0
  33. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_infra.py +0 -0
  34. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_product.py +0 -0
  35. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
  36. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  37. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/__init__.py +0 -0
  38. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
  39. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
  40. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  41. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/command/__init__.py +0 -0
  42. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/command/command_create.py +0 -0
  43. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/coverage.py +0 -0
  44. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_console.py +0 -0
  45. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_dump.py +0 -0
  46. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
  47. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  48. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
  49. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_seed.py +0 -0
  50. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_status.py +0 -0
  51. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  52. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/doctor.py +0 -0
  53. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/env/env_list.py +0 -0
  54. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/env/env_set.py +0 -0
  55. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/env/env_show.py +0 -0
  56. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/export/__init__.py +0 -0
  57. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/export/export_puml.py +0 -0
  58. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
  59. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
  60. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  61. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  62. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  63. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
  64. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  65. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
  66. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  67. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  68. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  69. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  70. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
  71. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  72. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  73. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  74. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  75. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  76. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  77. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  78. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
  79. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
  80. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  81. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  82. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  83. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  84. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  85. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  86. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_release.py +0 -0
  87. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  88. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  89. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  90. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
  91. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  92. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
  93. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  94. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  95. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  96. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  97. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/lint.py +0 -0
  98. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/locust.py +0 -0
  99. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/__init__.py +0 -0
  100. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  101. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_build.py +0 -0
  102. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_clean.py +0 -0
  103. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_commands.py +0 -0
  104. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_config.py +0 -0
  105. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_configure.py +0 -0
  106. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_console.py +0 -0
  107. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_containers.py +0 -0
  108. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_create.py +0 -0
  109. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
  110. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_derive.py +0 -0
  111. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  112. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_down.py +0 -0
  113. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  114. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_env.py +0 -0
  115. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_list.py +0 -0
  116. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  117. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_missing.py +0 -0
  118. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_port.py +0 -0
  119. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_release.py +0 -0
  120. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
  121. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_restart.py +0 -0
  122. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  123. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_run.py +0 -0
  124. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_select.py +0 -0
  125. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  126. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  127. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  128. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_test.py +0 -0
  129. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_up.py +0 -0
  130. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/product/product_validate.py +0 -0
  131. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/release/__init__.py +0 -0
  132. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/release/release_core.py +0 -0
  133. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/selenium.py +0 -0
  134. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/__init__.py +0 -0
  135. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  136. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  137. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  138. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
  139. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  140. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  141. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  142. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  143. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/tokens_setup.py +0 -0
  144. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  145. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/commands/version.py +0 -0
  146. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/services/__init__.py +0 -0
  147. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/services/compose.py +0 -0
  148. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/services/context.py +0 -0
  149. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/services/preflight.py +0 -0
  150. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/services/release.py +0 -0
  151. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/__init__.py +0 -0
  152. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/cache_utils.py +0 -0
  153. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/command_loader.py +0 -0
  154. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  155. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/db_utils.py +0 -0
  156. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/decorators.py +0 -0
  157. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  158. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/feature_installer.py +0 -0
  159. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/feature_utils.py +0 -0
  160. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/git_url.py +0 -0
  161. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/integrity.py +0 -0
  162. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/io_utils.py +0 -0
  163. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/lifecycle.py +0 -0
  164. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/manifest.py +0 -0
  165. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/path_utils.py +0 -0
  166. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/proc.py +0 -0
  167. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli/utils/template_drift.py +0 -0
  168. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  169. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  170. {splent_cli-1.11.0 → splent_cli-1.12.0}/src/splent_cli.egg-info/requires.txt +0 -0
  171. {splent_cli-1.11.0 → splent_cli-1.12.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.11.0
3
+ Version: 1.12.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splent_cli"
7
- version = "1.11.0"
7
+ version = "1.12.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"
@@ -0,0 +1,253 @@
1
+ import glob
2
+ import importlib
3
+ import os
4
+ import tomllib
5
+
6
+ import click
7
+ from flask import current_app
8
+ from flask_migrate import upgrade as alembic_upgrade
9
+ from sqlalchemy import text, MetaData
10
+
11
+ from splent_cli.utils.decorators import requires_db
12
+ from splent_cli.services import context
13
+ from splent_cli.utils.lifecycle import advance_state, resolve_feature_key_from_entry
14
+ from splent_framework.db import db
15
+ from splent_framework.managers.migration_manager import (
16
+ MigrationManager,
17
+ SPLENT_MIGRATIONS_TABLE,
18
+ alembic_version_table,
19
+ )
20
+ from splent_framework.utils.feature_utils import get_features_from_pyproject
21
+ from splent_framework.utils.path_utils import PathUtils
22
+ from splent_cli.commands.clear.clear_uploads import clear_uploads
23
+
24
+
25
+ # =====================================================================
26
+ # Helpers for the per-feature reset
27
+ # =====================================================================
28
+ def _normalize(feature_name: str) -> str:
29
+ """Accept 'projects' or 'splent_feature_projects' -> full package name."""
30
+ short = feature_name.split("/")[-1].replace("splent_feature_", "")
31
+ return f"splent_feature_{short}"
32
+
33
+
34
+ def _tables_owned_by(feature_full: str) -> list[str]:
35
+ """Tables whose model classes live in the given feature's package."""
36
+ import_name = f"splent_io.{feature_full}"
37
+ try:
38
+ importlib.import_module(f"{import_name}.models")
39
+ except Exception:
40
+ pass
41
+ tables = []
42
+ for mapper in db.Model.registry.mappers:
43
+ cls = mapper.class_
44
+ if getattr(cls, "__module__", "").startswith(import_name):
45
+ tables.append(cls.__table__.name)
46
+ return sorted(set(tables))
47
+
48
+
49
+ def _refiners_of(feature_full: str, product_path: str) -> list[str]:
50
+ """Installed features whose [tool.splent.refinement].refines == feature."""
51
+ refiners = []
52
+ pattern = os.path.join(product_path, "features", "*", "*", "pyproject.toml")
53
+ for pp in glob.glob(pattern):
54
+ try:
55
+ with open(pp, "rb") as f:
56
+ data = tomllib.load(f)
57
+ except Exception:
58
+ continue
59
+ ref = data.get("tool", {}).get("splent", {}).get("refinement", {})
60
+ if ref.get("refines") == feature_full:
61
+ name = data.get("project", {}).get("name", "")
62
+ if name:
63
+ refiners.append(name)
64
+ return refiners
65
+
66
+
67
+ def _reapply(app, feats: list[str], entry_lookup: dict, product_path, product_name):
68
+ dirs = MigrationManager.get_all_feature_migration_dirs()
69
+ for feat in feats:
70
+ mdir = dirs.get(feat)
71
+ if not mdir:
72
+ continue
73
+ try:
74
+ alembic_upgrade(directory=mdir)
75
+ revision = MigrationManager.get_current_feature_revision(feat, db.engine)
76
+ MigrationManager.update_feature_status(app, feat, revision)
77
+ click.echo(click.style(f" ✅ {feat} → {revision or 'head'}", fg="green"))
78
+ info = entry_lookup.get(feat)
79
+ if info:
80
+ key, ns, name, version = info
81
+ advance_state(
82
+ product_path,
83
+ product_name,
84
+ key,
85
+ to="migrated",
86
+ namespace=ns,
87
+ name=name,
88
+ version=version,
89
+ )
90
+ except ImportError as e:
91
+ if "models" in str(e):
92
+ continue
93
+ click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
94
+ except Exception as e:
95
+ click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
96
+
97
+
98
+ def _entry_lookup() -> dict:
99
+ lookup = {}
100
+ for entry in get_features_from_pyproject() or []:
101
+ key, ns, name, version = resolve_feature_key_from_entry(entry)
102
+ lookup[name] = (key, ns, name, version)
103
+ return lookup
104
+
105
+
106
+ # =====================================================================
107
+ # Command
108
+ # =====================================================================
109
+ @requires_db
110
+ @click.command(
111
+ "db:reset",
112
+ short_help="Drop all tables (or just one feature's) and re-apply migrations.",
113
+ )
114
+ @click.argument("feature_name", required=False)
115
+ @click.option("-y", "--yes", is_flag=True, help="Confirm without prompting.")
116
+ @context.requires_product
117
+ def db_reset(feature_name, yes):
118
+ """Reset the database.
119
+
120
+ With no argument: drops ALL tables and re-applies every feature migration.
121
+
122
+ With FEATURE_NAME (e.g. ``db:reset projects``): drops only the tables that
123
+ feature owns, clears their migration tracking, and re-applies that
124
+ feature's migrations plus any feature that refines it — leaving every other
125
+ feature's data untouched. Useful after changing a model/refinement.
126
+ """
127
+ app = current_app
128
+ product_path = PathUtils.get_app_base_dir()
129
+ product_name = os.getenv("SPLENT_APP", "")
130
+
131
+ if feature_name:
132
+ _reset_one_feature(app, feature_name, yes, product_path, product_name)
133
+ return
134
+
135
+ _reset_everything(app, yes, product_path, product_name)
136
+
137
+
138
+ def _reset_one_feature(app, feature_name, yes, product_path, product_name):
139
+ feature_full = _normalize(feature_name)
140
+ tables = _tables_owned_by(feature_full)
141
+ refiners = _refiners_of(feature_full, product_path)
142
+ feats = [feature_full] + refiners
143
+
144
+ if not tables:
145
+ click.secho(
146
+ f"⚠️ '{feature_full}' owns no tables (nothing to drop). "
147
+ f"If it is a refinement, reset the base feature instead.",
148
+ fg="yellow",
149
+ )
150
+ return
151
+
152
+ click.echo()
153
+ click.secho(f"Reset feature: {feature_full}", fg="cyan", bold=True)
154
+ click.echo(f" tables to drop : {', '.join(tables)}")
155
+ if refiners:
156
+ click.echo(f" also re-applies: {', '.join(refiners)} (refiners)")
157
+ if not yes and not click.confirm(
158
+ "⚠️ This drops those tables and re-applies their migrations. Continue?",
159
+ abort=True,
160
+ ):
161
+ return
162
+
163
+ # Drop the feature's tables
164
+ with db.engine.begin() as conn:
165
+ conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
166
+ for t in tables:
167
+ conn.execute(text(f"DROP TABLE IF EXISTS `{t}`"))
168
+ click.echo(click.style(f" Dropped {t}", fg="bright_black"))
169
+ conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
170
+
171
+ # Clear migration tracking for the feature and its refiners — both the
172
+ # central splent_migrations row AND each feature's alembic version table —
173
+ # so their migrations are re-applied from scratch onto the fresh tables.
174
+ with db.engine.begin() as conn:
175
+ conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
176
+ for feat in feats:
177
+ conn.execute(
178
+ text(f"DELETE FROM `{SPLENT_MIGRATIONS_TABLE}` WHERE feature = :f"),
179
+ {"f": feat},
180
+ )
181
+ conn.execute(text(f"DROP TABLE IF EXISTS `{alembic_version_table(feat)}`"))
182
+ conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
183
+
184
+ click.secho("⬆️ Re-applying migrations...", fg="cyan")
185
+ _reapply(app, feats, _entry_lookup(), product_path, product_name)
186
+ click.secho(f"\n🎉 Feature '{feature_full}' reset complete.", fg="green")
187
+
188
+
189
+ def _reset_everything(app, yes, product_path, product_name):
190
+ if not yes and not click.confirm(
191
+ "⚠️ WARNING: This will DROP all tables and clear uploads. Are you sure?",
192
+ abort=True,
193
+ ):
194
+ return
195
+
196
+ click.echo(click.style("🗑️ Dropping all tables...", fg="yellow"))
197
+ try:
198
+ with db.engine.begin() as conn:
199
+ conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
200
+ meta = MetaData()
201
+ meta.reflect(bind=db.engine)
202
+ for table in meta.sorted_tables:
203
+ conn.execute(text(f"DROP TABLE IF EXISTS `{table.name}`"))
204
+ click.echo(click.style(f" Dropped {table.name}", fg="bright_black"))
205
+ conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
206
+ click.echo(click.style("✅ All tables dropped.", fg="yellow"))
207
+ except Exception as e:
208
+ click.echo(click.style(f"❌ Error dropping tables: {e}", fg="red"))
209
+ click.echo(
210
+ click.style(
211
+ "⚠️ The database may now be in a PARTIAL state: some tables "
212
+ "may have been dropped while others remain.\n"
213
+ " To recover, re-run this command once the database is "
214
+ "reachable again (it is safe to repeat — drops use "
215
+ "DROP TABLE IF EXISTS), or manually drop the remaining "
216
+ "tables / re-create the schema from migrations.",
217
+ fg="red",
218
+ )
219
+ )
220
+ raise SystemExit(1)
221
+
222
+ click.echo(click.style("📋 Recreating splent_migrations table...", fg="cyan"))
223
+ with db.engine.begin() as conn:
224
+ conn.execute(
225
+ text(
226
+ f"""
227
+ CREATE TABLE IF NOT EXISTS `{SPLENT_MIGRATIONS_TABLE}` (
228
+ `feature` VARCHAR(255) NOT NULL,
229
+ `last_migration` VARCHAR(255) DEFAULT NULL,
230
+ PRIMARY KEY (`feature`)
231
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
232
+ """
233
+ )
234
+ )
235
+
236
+ ctx = click.get_current_context()
237
+ ctx.invoke(clear_uploads)
238
+
239
+ dirs = MigrationManager.get_all_feature_migration_dirs()
240
+ if not dirs:
241
+ click.echo(click.style("⚠️ No feature migrations found.", fg="yellow"))
242
+ else:
243
+ click.echo(
244
+ click.style(
245
+ f"⬆️ Applying migrations for {len(dirs)} features...", fg="cyan"
246
+ )
247
+ )
248
+ _reapply(app, list(dirs.keys()), _entry_lookup(), product_path, product_name)
249
+
250
+ click.echo(click.style("\n🎉 Database reset complete.", fg="green"))
251
+
252
+
253
+ cli_command = db_reset
@@ -4,7 +4,6 @@ import tomllib
4
4
  import click
5
5
  import requests
6
6
  from splent_cli.services import context, compose
7
- from splent_cli.utils.feature_utils import read_features_from_data
8
7
  from splent_cli.utils.cache_utils import make_feature_writable
9
8
  from splent_cli.utils.proc import run
10
9
  from splent_cli.utils.io_utils import load_toml, atomic_write
@@ -343,7 +342,16 @@ def feature_edit(feature_name, edit_all, force):
343
342
  with open(pyproject_path, "rb") as f:
344
343
  data = tomllib.load(f)
345
344
 
346
- features = read_features_from_data(data)
345
+ # Look across all feature lists (features, features_dev, features_prod) so
346
+ # that dev/prod-only features (e.g. admin) can also be unlocked, not just
347
+ # the default `features` list.
348
+ splent_cfg = data.get("tool", {}).get("splent", {})
349
+ features = [
350
+ entry
351
+ for key in ("features", "features_dev", "features_prod")
352
+ for entry in (splent_cfg.get(key) or [])
353
+ if isinstance(entry, str)
354
+ ]
347
355
 
348
356
  # ── Single feature ────────────────────────────────────────────────
349
357
  if feature_name:
@@ -219,45 +219,42 @@ def spl_add_feature(spl_name, feature_package, org):
219
219
  with open(uvl_path, "r", encoding="utf-8") as f:
220
220
  lines = f.read().splitlines()
221
221
 
222
- # Find the line before "constraints" to insert the feature
223
- insert_idx = None
224
- constraints_idx = None
225
- for i, line in enumerate(lines):
226
- if line.strip() == "constraints":
227
- constraints_idx = i
228
- insert_idx = i
229
- break
230
-
231
- if insert_idx is None:
232
- insert_idx = len(lines)
233
-
234
- # The line-insertion below assumes "constraints" lives on its own line.
235
- # If it doesn't (e.g. malformed or unexpected layout), bail out before
236
- # touching the file rather than silently corrupting the model.
237
- if detected_deps and constraints_idx is None:
238
- click.secho(
239
- f" ❌ No 'constraints' section found in {uvl_path}.\n"
240
- f" Refusing to edit: the UVL layout is not what this command "
241
- f"expects (a 'constraints' header on its own line). "
242
- f"Add the feature and constraints manually.",
243
- fg="red",
244
- )
245
- raise SystemExit(1)
246
-
247
- # Insert feature declaration
248
- feature_line = (
249
- f"\t\toptional\n\t\t\t{short_name} {{org '{org}', package '{feature_package}'}}"
222
+ # Locate the (optional) "constraints" header and an existing "optional" group.
223
+ # A fresh SPL has neither. UVL rejects an EMPTY "constraints" section, so we
224
+ # only create that section when there is at least one constraint to add.
225
+ constraints_idx = next(
226
+ (i for i, line in enumerate(lines) if line.strip() == "constraints"), None
250
227
  )
251
- lines.insert(insert_idx, feature_line)
228
+ optional_idx = next(
229
+ (i for i, line in enumerate(lines) if line.strip() == "optional"), None
230
+ )
231
+
232
+ # Insert the feature under a SINGLE shared "optional" group, reusing it if it
233
+ # already exists (so repeated calls don't emit multiple 'optional' headers,
234
+ # which produced invalid UVL). The feature goes just before "constraints" if
235
+ # present, otherwise at the end of the features block. Indentation is tabs
236
+ # throughout, consistent with spl:create.
237
+ feat_at = constraints_idx if constraints_idx is not None else len(lines)
238
+ feature_decl = f"\t\t\t{short_name} {{org '{org}', package '{feature_package}'}}"
239
+ if optional_idx is not None:
240
+ lines.insert(feat_at, feature_decl)
241
+ if constraints_idx is not None:
242
+ constraints_idx += 1
243
+ else:
244
+ lines.insert(feat_at, "\t\toptional")
245
+ lines.insert(feat_at + 1, feature_decl)
246
+ if constraints_idx is not None:
247
+ constraints_idx += 2
252
248
 
253
- # Insert constraints after "constraints" line
254
- if detected_deps and constraints_idx is not None:
255
- # constraints_idx shifted by 1 due to insertion above
249
+ # Add constraints, creating the section on demand (never leave it empty).
250
+ if detected_deps:
251
+ if constraints_idx is None:
252
+ lines.append("constraints")
253
+ constraints_idx = len(lines) - 1
254
+ ci = constraints_idx
256
255
  for dep in sorted(detected_deps):
257
- constraints_idx += (
258
- 2 # +1 for feature line (2 lines), +1 for each constraint
259
- )
260
- lines.insert(constraints_idx, f"\t{short_name} => {dep}")
256
+ ci += 1
257
+ lines.insert(ci, f"\t{short_name} => {dep}")
261
258
 
262
259
  # Back up the original UVL, then write atomically so a crash mid-write can
263
260
  # never leave a half-written / truncated model.
@@ -0,0 +1,88 @@
1
+ import click
2
+
3
+ from splent_cli.services import context
4
+ from splent_cli.utils.feature_utils import (
5
+ load_product_features,
6
+ parse_feature_entry,
7
+ )
8
+
9
+
10
+ def _load_feature_map(product_name: str) -> dict[tuple[str, str], str]:
11
+ """Return {(org, package): "org/package"} for a product in the workspace.
12
+
13
+ Features are keyed by (organisation, package) so that two features only
14
+ match when BOTH the org/user and the package coincide. The version is
15
+ ignored on purpose — a product on v1.2.0 and another on v1.3.0 of the same
16
+ feature still share that feature.
17
+ """
18
+ product_dir = context.workspace() / product_name
19
+ if not (product_dir / "pyproject.toml").is_file():
20
+ raise click.ClickException(
21
+ f"Product not found: '{product_name}' (no pyproject.toml in {product_dir})"
22
+ )
23
+
24
+ feature_map: dict[tuple[str, str], str] = {}
25
+ for entry in load_product_features(str(product_dir)):
26
+ org, package, _version = parse_feature_entry(entry)
27
+ feature_map[(org, package)] = f"{org}/{package}"
28
+ return feature_map
29
+
30
+
31
+ def _print_block(title: str, color: str, keys: list[tuple[str, str]]) -> None:
32
+ click.secho(f" {title} ({len(keys)})", fg=color, bold=True)
33
+ if not keys:
34
+ click.secho(" —", fg="bright_black")
35
+ for org, package in keys:
36
+ click.echo(f" {org}/{package}")
37
+ click.echo()
38
+
39
+
40
+ @click.command(
41
+ "spl:compare",
42
+ short_help="Compare the features of two products (shared vs product-specific)",
43
+ )
44
+ @click.argument("product_a")
45
+ @click.argument("product_b")
46
+ @context.requires_detached
47
+ def spl_compare(product_a, product_b):
48
+ """Compare two products in the workspace by their feature sets.
49
+
50
+ Reports the features they SHARE and the features SPECIFIC to each one.
51
+ Matching is done by organisation/package — two features are "the same"
52
+ only when both the org (or user) and the package name coincide.
53
+
54
+ \b
55
+ Example:
56
+ splent spl:compare innosoft_app diversolab_app
57
+ """
58
+ a = _load_feature_map(product_a)
59
+ b = _load_feature_map(product_b)
60
+
61
+ a_keys, b_keys = set(a), set(b)
62
+ shared = sorted(a_keys & b_keys)
63
+ only_a = sorted(a_keys - b_keys)
64
+ only_b = sorted(b_keys - a_keys)
65
+
66
+ union = a_keys | b_keys
67
+ reuse_pct = (len(shared) / len(union) * 100) if union else 0.0
68
+
69
+ click.echo()
70
+ click.secho(f" {product_a} ↔ {product_b}", fg="cyan", bold=True)
71
+ click.echo(
72
+ f" {len(a_keys)} feature(s) in {product_a}"
73
+ f" · {len(b_keys)} feature(s) in {product_b}"
74
+ )
75
+ reuse_color = "green" if reuse_pct >= 50 else "yellow"
76
+ click.secho(
77
+ f" Reuse: {len(shared)}/{len(union)} shared ({reuse_pct:.0f}%)",
78
+ fg=reuse_color,
79
+ bold=True,
80
+ )
81
+ click.echo()
82
+
83
+ _print_block("Shared", "green", shared)
84
+ _print_block(f"Only in {product_a}", "yellow", only_a)
85
+ _print_block(f"Only in {product_b}", "magenta", only_b)
86
+
87
+
88
+ cli_command = spl_compare
@@ -35,7 +35,10 @@ def spl_create(name):
35
35
 
36
36
  # {name}.uvl
37
37
  uvl_path = os.path.join(spl_dir, f"{name}.uvl")
38
- uvl_content = f"features\n {name}\n mandatory\n\nconstraints\n"
38
+ # A fresh SPL has no features yet — emit a minimal VALID model (just the
39
+ # root, no empty 'mandatory'/'optional' group, which UVL rejects). Tabs are
40
+ # used for indentation, consistent with spl:add-feature.
41
+ uvl_content = f"features\n\t{name}\n"
39
42
  with open(uvl_path, "w") as f:
40
43
  f.write(uvl_content)
41
44
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.11.0
3
+ Version: 1.12.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
@@ -134,6 +134,7 @@ src/splent_cli/commands/release/release_core.py
134
134
  src/splent_cli/commands/spl/__init__.py
135
135
  src/splent_cli/commands/spl/spl_add_constraints.py
136
136
  src/splent_cli/commands/spl/spl_add_feature.py
137
+ src/splent_cli/commands/spl/spl_compare.py
137
138
  src/splent_cli/commands/spl/spl_configurations.py
138
139
  src/splent_cli/commands/spl/spl_create.py
139
140
  src/splent_cli/commands/spl/spl_deps.py
@@ -1,159 +0,0 @@
1
- import os
2
-
3
- import click
4
- from flask import current_app
5
- from flask_migrate import upgrade as alembic_upgrade
6
- from sqlalchemy import text, MetaData
7
-
8
- from splent_cli.utils.decorators import requires_db
9
- from splent_cli.services import context
10
- from splent_cli.utils.lifecycle import advance_state, resolve_feature_key_from_entry
11
- from splent_framework.db import db
12
- from splent_framework.managers.migration_manager import (
13
- MigrationManager,
14
- SPLENT_MIGRATIONS_TABLE,
15
- )
16
- from splent_framework.utils.feature_utils import get_features_from_pyproject
17
- from splent_framework.utils.path_utils import PathUtils
18
- from splent_cli.commands.clear.clear_uploads import clear_uploads
19
-
20
-
21
- @requires_db
22
- @click.command(
23
- "db:reset",
24
- short_help="Drop all tables and re-apply migrations from scratch.",
25
- )
26
- @click.option("-y", "--yes", is_flag=True, help="Confirm without prompting.")
27
- @context.requires_product
28
- def db_reset(yes):
29
- """
30
- Full database reset: drops ALL tables (data, alembic tracking,
31
- splent_migrations), then re-applies existing feature migrations.
32
-
33
- Feature migration files are NOT deleted — only the database is wiped.
34
- """
35
- app = current_app
36
-
37
- if not yes and not click.confirm(
38
- "⚠️ WARNING: This will DROP all tables and clear uploads. Are you sure?",
39
- abort=True,
40
- ):
41
- return
42
-
43
- # --- STEP 1: Drop ALL tables (including alembic_* and splent_migrations) ---
44
- # Run every DROP inside a single transaction (engine.begin) so that a
45
- # mid-operation connection failure rolls back as a unit rather than
46
- # leaving a half-dropped schema. Note: MySQL/MariaDB issue an implicit
47
- # COMMIT on DDL, so a full rollback is not guaranteed there; we therefore
48
- # also surface explicit partial-state recovery guidance on failure.
49
- click.echo(click.style("🗑️ Dropping all tables...", fg="yellow"))
50
- try:
51
- with db.engine.begin() as conn:
52
- conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
53
- meta = MetaData()
54
- meta.reflect(bind=db.engine)
55
- for table in meta.sorted_tables:
56
- conn.execute(text(f"DROP TABLE IF EXISTS `{table.name}`"))
57
- click.echo(click.style(f" Dropped {table.name}", fg="bright_black"))
58
- conn.execute(text("SET FOREIGN_KEY_CHECKS = 1"))
59
- click.echo(click.style("✅ All tables dropped.", fg="yellow"))
60
- except Exception as e:
61
- click.echo(click.style(f"❌ Error dropping tables: {e}", fg="red"))
62
- click.echo(
63
- click.style(
64
- "⚠️ The database may now be in a PARTIAL state: some tables "
65
- "may have been dropped while others remain.\n"
66
- " To recover, re-run this command once the database is "
67
- "reachable again (it is safe to repeat — drops use "
68
- "DROP TABLE IF EXISTS), or manually drop the remaining "
69
- "tables / re-create the schema from migrations.",
70
- fg="red",
71
- )
72
- )
73
- raise SystemExit(1)
74
-
75
- # --- STEP 2: Recreate splent_migrations tracking table ---
76
- click.echo(click.style("📋 Recreating splent_migrations table...", fg="cyan"))
77
- try:
78
- with db.engine.begin() as conn:
79
- conn.execute(
80
- text(
81
- f"""
82
- CREATE TABLE IF NOT EXISTS `{SPLENT_MIGRATIONS_TABLE}` (
83
- `feature` VARCHAR(255) NOT NULL,
84
- `last_migration` VARCHAR(255) DEFAULT NULL,
85
- PRIMARY KEY (`feature`)
86
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
87
- """
88
- )
89
- )
90
- except Exception as e:
91
- click.echo(click.style(f"❌ Error creating tracking table: {e}", fg="red"))
92
- click.echo(
93
- click.style(
94
- "⚠️ All tables were dropped but the tracking table could not "
95
- "be recreated. Re-run this command once the database is "
96
- "reachable to finish the reset.",
97
- fg="red",
98
- )
99
- )
100
- raise SystemExit(1)
101
-
102
- # --- STEP 3: Clear uploads ---
103
- ctx = click.get_current_context()
104
- ctx.invoke(clear_uploads)
105
-
106
- # --- STEP 4: Re-apply all existing feature migrations ---
107
- dirs = MigrationManager.get_all_feature_migration_dirs()
108
-
109
- # Build entry→key lookup for manifest updates
110
- product_path = PathUtils.get_app_base_dir()
111
- product_name = os.getenv("SPLENT_APP", "")
112
- entry_lookup = {}
113
- for entry in get_features_from_pyproject() or []:
114
- key, ns, name, version = resolve_feature_key_from_entry(entry)
115
- entry_lookup[name] = (key, ns, name, version)
116
-
117
- if not dirs:
118
- click.echo(click.style("⚠️ No feature migrations found.", fg="yellow"))
119
- else:
120
- click.echo(
121
- click.style(
122
- f"⬆️ Applying migrations for {len(dirs)} features...", fg="cyan"
123
- )
124
- )
125
- for feat, mdir in dirs.items():
126
- try:
127
- alembic_upgrade(directory=mdir)
128
- revision = MigrationManager.get_current_feature_revision(
129
- feat, db.engine
130
- )
131
- MigrationManager.update_feature_status(app, feat, revision)
132
- click.echo(
133
- click.style(f" ✅ {feat} → {revision or 'head'}", fg="green")
134
- )
135
-
136
- # Advance lifecycle state to "migrated"
137
- info = entry_lookup.get(feat)
138
- if info:
139
- key, ns, name, version = info
140
- advance_state(
141
- product_path,
142
- product_name,
143
- key,
144
- to="migrated",
145
- namespace=ns,
146
- name=name,
147
- version=version,
148
- )
149
- except ImportError as e:
150
- if "models" in str(e):
151
- continue
152
- click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
153
- except Exception as e:
154
- click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
155
-
156
- click.echo(click.style("\n🎉 Database reset complete.", fg="green"))
157
-
158
-
159
- cli_command = db_reset
File without changes
File without changes
File without changes