splent-cli 1.6.0__tar.gz → 1.7.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 (168) hide show
  1. {splent_cli-1.6.0/src/splent_cli.egg-info → splent_cli-1.7.0}/PKG-INFO +1 -1
  2. {splent_cli-1.6.0 → splent_cli-1.7.0}/pyproject.toml +1 -1
  3. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_reset.py +4 -0
  4. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_upgrade.py +5 -0
  5. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_add.py +19 -0
  6. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_attach.py +21 -0
  7. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_env.py +12 -14
  8. splent_cli-1.7.0/src/splent_cli/commands/feature/feature_impact.py +161 -0
  9. splent_cli-1.7.0/src/splent_cli/commands/feature/feature_install.py +425 -0
  10. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_test.py +48 -0
  11. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_build.py +153 -11
  12. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_configure.py +66 -3
  13. splent_cli-1.7.0/src/splent_cli/commands/product/product_containers.py +316 -0
  14. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_deploy.py +60 -6
  15. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_env.py +33 -0
  16. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_restart.py +36 -0
  17. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_test.py +14 -0
  18. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_up.py +12 -10
  19. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/services/preflight.py +70 -1
  20. {splent_cli-1.6.0 → splent_cli-1.7.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
  21. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli.egg-info/SOURCES.txt +2 -0
  22. splent_cli-1.6.0/src/splent_cli/commands/product/product_containers.py +0 -196
  23. {splent_cli-1.6.0 → splent_cli-1.7.0}/LICENSE +0 -0
  24. {splent_cli-1.6.0 → splent_cli-1.7.0}/README.md +0 -0
  25. {splent_cli-1.6.0 → splent_cli-1.7.0}/setup.cfg +0 -0
  26. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/__init__.py +0 -0
  27. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/__main__.py +0 -0
  28. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/cli.py +0 -0
  29. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/__init__.py +0 -0
  30. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  31. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  32. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  33. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  34. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  35. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  36. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  37. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  38. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  39. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/check/__init__.py +0 -0
  40. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  41. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  42. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_env.py +0 -0
  43. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_features.py +0 -0
  44. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_github.py +0 -0
  45. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_infra.py +0 -0
  46. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_product.py +0 -0
  47. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
  48. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  49. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/clear/__init__.py +0 -0
  50. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
  51. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
  52. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  53. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/command/__init__.py +0 -0
  54. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/command/command_create.py +0 -0
  55. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/coverage.py +0 -0
  56. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_console.py +0 -0
  57. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_dump.py +0 -0
  58. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
  59. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  60. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
  61. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_seed.py +0 -0
  62. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_status.py +0 -0
  63. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/doctor.py +0 -0
  64. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/env/env_list.py +0 -0
  65. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/env/env_set.py +0 -0
  66. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/env/env_show.py +0 -0
  67. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/export/__init__.py +0 -0
  68. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/export/export_puml.py +0 -0
  69. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  70. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  71. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  72. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
  73. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  74. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
  75. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  76. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  77. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  78. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  79. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  80. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  81. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  82. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  83. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  84. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  85. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
  86. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  87. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  88. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  89. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  90. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  91. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  92. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_release.py +0 -0
  93. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  94. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  95. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  96. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
  97. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  98. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  99. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
  100. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  101. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  102. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  103. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/lint.py +0 -0
  104. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/locust.py +0 -0
  105. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/__init__.py +0 -0
  106. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  107. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_clean.py +0 -0
  108. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_commands.py +0 -0
  109. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_config.py +0 -0
  110. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_console.py +0 -0
  111. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_create.py +0 -0
  112. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_derive.py +0 -0
  113. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  114. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_down.py +0 -0
  115. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  116. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_list.py +0 -0
  117. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  118. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_missing.py +0 -0
  119. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_port.py +0 -0
  120. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_release.py +0 -0
  121. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
  122. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  123. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_run.py +0 -0
  124. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_select.py +0 -0
  125. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  126. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  127. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  128. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_validate.py +0 -0
  129. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/release/__init__.py +0 -0
  130. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/release/release_core.py +0 -0
  131. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/selenium.py +0 -0
  132. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/__init__.py +0 -0
  133. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  134. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  135. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  136. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  137. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  138. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
  139. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  140. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  141. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  142. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  143. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/tokens_setup.py +0 -0
  144. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  145. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/commands/version.py +0 -0
  146. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/services/__init__.py +0 -0
  147. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/services/compose.py +0 -0
  148. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/services/context.py +0 -0
  149. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/services/release.py +0 -0
  150. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/__init__.py +0 -0
  151. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/cache_utils.py +0 -0
  152. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/command_loader.py +0 -0
  153. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  154. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/db_utils.py +0 -0
  155. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/decorators.py +0 -0
  156. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  157. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/feature_installer.py +0 -0
  158. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/feature_utils.py +0 -0
  159. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/git_url.py +0 -0
  160. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/integrity.py +0 -0
  161. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/lifecycle.py +0 -0
  162. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/manifest.py +0 -0
  163. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/path_utils.py +0 -0
  164. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli/utils/template_drift.py +0 -0
  165. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  166. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  167. {splent_cli-1.6.0 → splent_cli-1.7.0}/src/splent_cli.egg-info/requires.txt +0 -0
  168. {splent_cli-1.6.0 → splent_cli-1.7.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.6.0
3
+ Version: 1.7.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.6.0"
7
+ version = "1.7.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"
@@ -123,6 +123,10 @@ def db_reset(yes):
123
123
  name=name,
124
124
  version=version,
125
125
  )
126
+ except ImportError as e:
127
+ if "models" in str(e):
128
+ continue
129
+ click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
126
130
  except Exception as e:
127
131
  click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
128
132
 
@@ -84,6 +84,11 @@ def db_upgrade(feature):
84
84
  name=name,
85
85
  version=version,
86
86
  )
87
+ except ImportError as e:
88
+ if "models" in str(e):
89
+ # Feature has migrations/ dir but no models module — skip silently
90
+ continue
91
+ click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
87
92
  except Exception as e:
88
93
  click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
89
94
 
@@ -59,6 +59,25 @@ def feature_add(full_name, env_scope):
59
59
  )
60
60
  raise SystemExit(1)
61
61
 
62
+ # ── Auto-detect env scope from feature contract ───────────────────
63
+ if not env_scope:
64
+ feat_pyproject = os.path.join(feature_dir, "pyproject.toml")
65
+ if os.path.isfile(feat_pyproject):
66
+ with open(feat_pyproject, "rb") as f:
67
+ feat_data = tomllib.load(f)
68
+ contract_env = (
69
+ feat_data.get("tool", {})
70
+ .get("splent", {})
71
+ .get("contract", {})
72
+ .get("env")
73
+ )
74
+ if contract_env:
75
+ env_scope = contract_env
76
+ click.echo(
77
+ click.style(" scope ", dim=True)
78
+ + f"contract declares env={contract_env} → features_{contract_env}"
79
+ )
80
+
62
81
  # ── Update pyproject.toml ─────────────────────────────────────────
63
82
  pyproject_path = os.path.join(workspace, product, "pyproject.toml")
64
83
  if not os.path.exists(pyproject_path):
@@ -64,6 +64,27 @@ def feature_attach(feature_identifier, version, env_scope):
64
64
 
65
65
  short = feature_name.replace("splent_feature_", "")
66
66
 
67
+ # ── Auto-detect env scope from feature contract ───────────────────
68
+ if not env_scope:
69
+ feat_pyproject = os.path.join(versioned_dir, "pyproject.toml")
70
+ if os.path.isfile(feat_pyproject):
71
+ import tomllib as _tomllib
72
+
73
+ with open(feat_pyproject, "rb") as f:
74
+ feat_data = _tomllib.load(f)
75
+ contract_env = (
76
+ feat_data.get("tool", {})
77
+ .get("splent", {})
78
+ .get("contract", {})
79
+ .get("env")
80
+ )
81
+ if contract_env:
82
+ env_scope = contract_env
83
+ click.echo(
84
+ click.style(" scope ", dim=True)
85
+ + f"contract declares env={contract_env} → features_{contract_env}"
86
+ )
87
+
67
88
  # ── Update pyproject.toml ─────────────────────────────────────────
68
89
  full_name = f"{namespace}/{feature_name}@{version}"
69
90
  bare_name = f"{namespace}/{feature_name}"
@@ -45,19 +45,26 @@ def feature_env(feature_name, generate, env_name):
45
45
  with open(pyproject_path, "rb") as f:
46
46
  data = tomllib.load(f)
47
47
 
48
- feature_entries = read_features_from_data(data)
48
+ # 2️⃣ Determinar entorno (antes de leer features, para incluir features_dev/prod)
49
+ env_from_var = os.getenv("SPLENT_ENV")
50
+ if not env_name and env_from_var:
51
+ env_name = env_from_var
52
+ click.echo(f"🌍 Using SPLENT_ENV={env_name}")
53
+ elif not env_name:
54
+ click.echo("❌ You must specify --dev, --prod, or define SPLENT_ENV=dev|prod.")
55
+ raise SystemExit(1)
56
+
57
+ feature_entries = read_features_from_data(data, env_name)
49
58
  if not feature_entries:
50
59
  click.echo("❌ No features declared in pyproject.toml.")
51
60
  raise SystemExit(1)
52
61
 
53
- # 2️⃣ Buscar la feature pedida con su versión
62
+ # 3️⃣ Buscar la feature pedida con su versión
54
63
  feature_entry = next(
55
64
  (f for f in feature_entries if f.startswith(feature_name)), None
56
65
  )
57
66
  if not feature_entry:
58
- click.echo(
59
- f"❌ Feature '{feature_name}' not found in [features] section of {pyproject_path}"
60
- )
67
+ click.echo(f"❌ Feature '{feature_name}' not found in pyproject.toml")
61
68
  raise SystemExit(1)
62
69
 
63
70
  # Derive org_safe from the entry itself (e.g. "splent_io/splent_feature_auth@v1")
@@ -68,15 +75,6 @@ def feature_env(feature_name, generate, env_name):
68
75
  org_safe = "splent_io"
69
76
  entry_basename = feature_entry
70
77
 
71
- # 3️⃣ Determinar entorno
72
- env_from_var = os.getenv("SPLENT_ENV")
73
- if not env_name and env_from_var:
74
- env_name = env_from_var
75
- click.echo(f"🌍 Using SPLENT_ENV={env_name}")
76
- elif not env_name:
77
- click.echo("❌ You must specify --dev, --prod, or define SPLENT_ENV=dev|prod.")
78
- raise SystemExit(1)
79
-
80
78
  # 4️⃣ Ruta del symlink del producto (con versión incluida)
81
79
  symlink_path = os.path.join(
82
80
  workspace, product, "features", org_safe, entry_basename
@@ -0,0 +1,161 @@
1
+ """
2
+ splent feature:impact
3
+
4
+ Shows the full dependency impact of a feature within the active product:
5
+ what it requires, what depends on it, and what would break if removed.
6
+
7
+ All dependency information is resolved from the product's UVL constraints.
8
+ """
9
+
10
+ import os
11
+
12
+ import click
13
+
14
+ from splent_cli.services import context
15
+ from splent_cli.commands.feature.feature_order import (
16
+ _uvl_path,
17
+ _build_requires_map,
18
+ _build_reverse_map,
19
+ _closure,
20
+ _parse_entry,
21
+ )
22
+ from splent_framework.utils.pyproject_reader import PyprojectReader
23
+
24
+
25
+ def _short_name(pkg: str) -> str:
26
+ """splent_feature_auth → auth"""
27
+ return pkg.removeprefix("splent_feature_")
28
+
29
+
30
+ @click.command(
31
+ "feature:impact",
32
+ short_help="Show the dependency impact of adding or removing a feature.",
33
+ )
34
+ @click.argument("feature_ref", required=True)
35
+ def feature_impact(feature_ref):
36
+ """
37
+ Show what FEATURE_REF depends on and what depends on it.
38
+
39
+ Reads the product's UVL constraints to build a bidirectional dependency
40
+ graph and shows:
41
+
42
+ \b
43
+ 1. Dependencies — features this one requires to work.
44
+ 2. Dependents — features that require this one.
45
+ 3. Impact — transitive set of features that would break
46
+ if this feature were removed.
47
+
48
+ FEATURE_REF can be a short name (auth) or full package name
49
+ (splent_feature_auth).
50
+
51
+ \b
52
+ Examples:
53
+ splent feature:impact auth
54
+ splent feature:impact splent_feature_mail
55
+ """
56
+ product = context.require_app()
57
+ workspace = str(context.workspace())
58
+ product_dir = os.path.join(workspace, product)
59
+
60
+ try:
61
+ reader = PyprojectReader.for_product(product_dir)
62
+ env = os.getenv("SPLENT_ENV")
63
+ features_raw = reader.features_for_env(env)
64
+ except FileNotFoundError:
65
+ click.secho(" pyproject.toml not found.", fg="red")
66
+ raise SystemExit(1)
67
+
68
+ uvl = _uvl_path(product_dir)
69
+ if not uvl or not os.path.isfile(uvl):
70
+ click.secho(" No UVL file found — cannot compute impact.", fg="red")
71
+ raise SystemExit(1)
72
+
73
+ # Normalize input: accept "auth" or "splent_feature_auth"
74
+ target = feature_ref
75
+ if target.startswith("splent_feature_"):
76
+ target = target[len("splent_feature_") :]
77
+ pkg_target = f"splent_feature_{target}"
78
+
79
+ # Build declared feature set
80
+ declared_pkgs = set()
81
+ for entry in features_raw:
82
+ _, name, _ = _parse_entry(entry)
83
+ declared_pkgs.add(name)
84
+
85
+ if pkg_target not in declared_pkgs:
86
+ click.secho(f" '{feature_ref}' is not declared in this product.", fg="red")
87
+ raise SystemExit(1)
88
+
89
+ # Build dependency graphs
90
+ requires_map = _build_requires_map(uvl)
91
+ reverse_map = _build_reverse_map(requires_map)
92
+
93
+ # Direct dependencies (what I need)
94
+ direct_deps = requires_map.get(pkg_target, [])
95
+
96
+ # Full dependency closure (transitive: what I need, and what they need, etc.)
97
+ all_deps = _closure(pkg_target, requires_map, declared_pkgs)
98
+
99
+ # Direct dependents (who needs me)
100
+ direct_dependents = reverse_map.get(pkg_target, [])
101
+
102
+ # Full reverse closure (transitive: who breaks if I'm removed)
103
+ all_affected = _closure(pkg_target, reverse_map, declared_pkgs)
104
+
105
+ # ── Output ────────────────────────────────────────────────────────
106
+
107
+ click.echo()
108
+ click.secho(f" feature:impact — {target}", bold=True)
109
+ click.echo(click.style(f" Product: {product}", fg="bright_black"))
110
+ click.echo(click.style(f" UVL: {os.path.basename(uvl)}", fg="bright_black"))
111
+
112
+ # Section 1: What I depend on
113
+ click.echo()
114
+ click.secho(" Depends on", bold=True)
115
+
116
+ if not all_deps:
117
+ click.echo(click.style(" (none) — this feature is independent", fg="green"))
118
+ else:
119
+ direct_set = set(direct_deps)
120
+ for dep in all_deps:
121
+ marker = "direct" if dep in direct_set else "transitive"
122
+ icon = "→" if marker == "direct" else " →"
123
+ color = "cyan" if marker == "direct" else "bright_black"
124
+ click.echo(
125
+ f" {icon} {click.style(_short_name(dep), fg=color)} ({marker})"
126
+ )
127
+
128
+ # Section 2: Who depends on me + impact
129
+ click.echo()
130
+ click.secho(" Depended on by", bold=True)
131
+
132
+ if not all_affected:
133
+ click.echo(click.style(" (none) — safe to remove", fg="green"))
134
+ else:
135
+ direct_set = set(direct_dependents)
136
+ for dep in all_affected:
137
+ marker = "direct" if dep in direct_set else "transitive"
138
+ icon = "←" if marker == "direct" else " ←"
139
+ color = "red" if marker == "direct" else "yellow"
140
+ click.echo(
141
+ f" {icon} {click.style(_short_name(dep), fg=color)} ({marker})"
142
+ )
143
+
144
+ # Section 3: Summary
145
+ click.echo()
146
+ if all_affected:
147
+ click.secho(
148
+ f" Removing {target} would break {len(all_affected)} feature(s).",
149
+ fg="red",
150
+ bold=True,
151
+ )
152
+ else:
153
+ click.secho(
154
+ f" Removing {target} has no impact on other features.",
155
+ fg="green",
156
+ bold=True,
157
+ )
158
+ click.echo()
159
+
160
+
161
+ cli_command = feature_impact